feat: uuid ids everywhere, pickers for linked tickets, @pi/ui checkboxes

- Postgres: every table has a uuid `id` (gen_random_uuid()); the event
  store keeps its relay order in `seq`, and the lookup tables their
  uniqueness in constraints of their own.
- MongoDB: `_id` is Mongo's ObjectId; `id` is the domain id (the same
  uuid as the aggregate's stream in Postgres), unique, and every query
  goes by it. Production starts from empty databases.
- Web: linking tickets and hanging one from a parent are picked from a
  searchable select (links across projects, parents within the project)
  instead of typing a key; every checkbox is @pi/ui's pi-checkbox.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 19:16:43 +02:00
parent 10c8f21ff4
commit 2c501a50d0
30 changed files with 292 additions and 207 deletions

View File

@@ -2,6 +2,7 @@ import {
Column,
CreateDateColumn,
Entity,
Generated,
Index,
PrimaryGeneratedColumn,
Unique,
@@ -13,12 +14,21 @@ import {
*/
@Entity('events')
@Unique('uq_events_stream_position', ['streamId', 'position'])
@Index('idx_events_unpublished', ['id'], { where: '"published_at" IS NULL' })
@Index('idx_events_unpublished', ['seq'], { where: '"published_at" IS NULL' })
export class EventEntity {
/** Global order of the store, used to relay events in sequence. */
@PrimaryGeneratedColumn('increment', { type: 'bigint' })
/** The event's own id. */
@PrimaryGeneratedColumn('uuid')
id!: string;
/**
* Global order of the store, used to relay events in sequence. A uuid has
* no reliable order, so it is kept apart.
*/
@Index('uq_events_seq', { unique: true })
@Column({ type: 'bigint' })
@Generated('increment')
seq!: string;
@Column({ name: 'stream_id', length: 64 })
streamId!: string;

View File

@@ -73,7 +73,7 @@ export class EventStoreRelay
.getRepository(EventEntity)
.createQueryBuilder('e')
.where('e.published_at IS NULL')
.orderBy('e.id', 'ASC')
.orderBy('e.seq', 'ASC')
.limit(BATCH_SIZE)
.setLock('pessimistic_write')
.setOnLocked('skip_locked')