@murumets-ee/db 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -412,6 +412,41 @@ interface ClaimOptions<TCols extends Record<string, ColumnFactory>, TTable exten
412
412
  /** Maximum number of rows to claim. Must be 1..MAX_LIMIT. */
413
413
  limit: number;
414
414
  }
415
+ /**
416
+ * Options for the `tryClaim()` atomic insert-or-replace-if operation.
417
+ *
418
+ * @see {@link TableClient.tryClaim}
419
+ */
420
+ interface TryClaimOptions<TCols extends Record<string, ColumnFactory>, TTable extends PgTable = PgTable> {
421
+ /**
422
+ * Unique-constraint column(s) that should trigger the conflict path.
423
+ *
424
+ * Single string for a single-column unique constraint; array for a
425
+ * composite unique. Must reference a real column on this table.
426
+ */
427
+ target: (keyof TCols & string) | (keyof TCols & string)[];
428
+ /**
429
+ * Predicate that decides whether an existing conflicting row may be
430
+ * **replaced**. Required and must be non-empty.
431
+ *
432
+ * - On INSERT (no conflict): always wins; predicate is irrelevant.
433
+ * - On CONFLICT + predicate matches: row is replaced; result is
434
+ * `{ acquired: true, row }`.
435
+ * - On CONFLICT + predicate does NOT match: existing row is preserved;
436
+ * result is `{ acquired: false, row }` (where `row` is the surviving
437
+ * existing row).
438
+ *
439
+ * Use `$or` to express composite re-entrancy rules — e.g.
440
+ * `{ $or: [{ expiresAt: { lt: now } }, { lockedBy: callerId }] }` for
441
+ * "replace if expired, or if I already hold it".
442
+ */
443
+ replaceWhen: WhereClause<TCols>;
444
+ /**
445
+ * Patch applied on the replace path. Defaults to the full `values`
446
+ * payload (last-write-wins on the columns being inserted).
447
+ */
448
+ set?: Partial<InferInsertModel<TTable>>;
449
+ }
415
450
  /**
416
451
  * Specification for a single aggregate function.
417
452
  */
@@ -633,6 +668,56 @@ declare class TableClient<TCols extends Record<string, ColumnFactory>, TTable ex
633
668
  set?: Partial<InferInsertModel<TTable>>; /** Conditional WHERE on the conflict update path. Empty objects are ignored. */
634
669
  setWhere?: WhereClause<TCols>;
635
670
  }): Promise<Row<TCols>>;
671
+ /**
672
+ * Atomically claim a row by either inserting it or replacing an existing
673
+ * conflicting row that satisfies `replaceWhen`. The single SQL statement
674
+ * makes this race-free — concurrent callers cannot both observe an empty
675
+ * slot and both INSERT.
676
+ *
677
+ * Distinct from {@link upsert}:
678
+ * - `upsert` always wins on conflict (or hits its `setWhere` block and
679
+ * silently falls back to the existing row). It returns a single
680
+ * `Row<TCols>` and the caller cannot tell which path fired.
681
+ * - `tryClaim` returns a discriminated union — the caller knows whether
682
+ * they took the slot or someone else still holds it.
683
+ *
684
+ * Use case: leasing/locking primitives where the caller must distinguish
685
+ * "I now hold this lock" from "someone else holds it, here is who".
686
+ *
687
+ * @example Acquire a lock, replacing only if expired
688
+ * ```ts
689
+ * const result = await locks.tryClaim(
690
+ * { entityId, lockedBy: me, expiresAt: in5Min },
691
+ * {
692
+ * target: 'entityId',
693
+ * replaceWhen: { expiresAt: { lt: now } },
694
+ * },
695
+ * )
696
+ * if (result.acquired) {
697
+ * // I have the lock
698
+ * } else {
699
+ * // result.row.lockedBy holds it (and lock is fresh)
700
+ * }
701
+ * ```
702
+ *
703
+ * @example Re-entrant lock (claim if expired, or if I already hold it)
704
+ * ```ts
705
+ * await locks.tryClaim(
706
+ * { entityId, lockedBy: me, expiresAt: in5Min },
707
+ * {
708
+ * target: 'entityId',
709
+ * replaceWhen: { $or: [{ expiresAt: { lt: now } }, { lockedBy: me }] },
710
+ * },
711
+ * )
712
+ * ```
713
+ */
714
+ tryClaim(values: InferInsertModel<TTable>, opts: TryClaimOptions<TCols, TTable>): Promise<{
715
+ acquired: true;
716
+ row: Row<TCols>;
717
+ } | {
718
+ acquired: false;
719
+ row: Row<TCols>;
720
+ }>;
636
721
  /**
637
722
  * Insert many rows in a single statement. Capped at {@link MAX_BATCH}.
638
723
  * Returns all inserted rows in input order.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/client.ts","../src/migrate.ts","../src/query-counter.ts","../src/schema-registry.ts","../src/table/types.ts","../src/table/client.ts","../src/table/columns.ts","../src/table/define.ts","../src/table/registry.ts","../src/table/where-builder.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/client.ts","../src/migrate.ts","../src/query-counter.ts","../src/schema-registry.ts","../src/table/types.ts","../src/table/client.ts","../src/table/columns.ts","../src/table/define.ts","../src/table/registry.ts","../src/table/where-builder.ts"],"mappings":";;;;;;;;UAKiB,QAAA;EACf,GAAA;EACA,WAAA;EACA,OAAA;EACA,OAAA;AAAA;;AAJF;;iBAcgB,cAAA,CAAe,MAAA,EAAQ,QAAA,GAAW,kBAAA;;;;;;;;AAAlD;;;;;;;iBAyBgB,oBAAA,CAAqB,MAAA,EAAQ,QAAA,GAAW,kBAAA;;;;;;;;;;AAvCxD;;;UCwBiB,WAAA;EACf,EAAA,EAAI,kBAAA;AAAA;;;;;ADXN;;UCoBiB,eAAA;EACf,EAAA,GAAK,IAAA,EAAM,WAAA,KAAgB,OAAA;EAC3B,IAAA,IAAQ,IAAA,EAAM,WAAA,KAAgB,OAAA;AAAA;AAAA,UAGf,eAAA;EDzBmD;EC2BlE,IAAA;EDFc;ECId,SAAA;;EAEA,IAAA;AAAA;AAAA,UAGe,eAAA;EACf,OAAA,EAAS,eAAA;EACT,OAAA,EAAS,eAAA;AAAA;;;;AA1BX;;;;;KAqCY,eAAA,IAAmB,YAAA,aAAyB,OAAA,CAAQ,eAAA;;iBAqD1C,kBAAA,CACpB,EAAA,EAAI,kBAAA,EACJ,WAAA,WACC,OAAA,CAAQ,eAAA;;;;;;;;;;;;;iBAgCW,aAAA,CACpB,EAAA,EAAI,kBAAA,EACJ,WAAA,UACA,MAAA,EAAQ,eAAA,GACP,OAAA;;;;AAlHH;;;;iBAkKsB,kBAAA,CACpB,EAAA,EAAI,kBAAA,EACJ,WAAA,UACA,MAAA,EAAQ,eAAA,EACR,KAAA,YACC,OAAA;;;;;;;;;;AD9MH;;;;;;UEYiB,UAAA;EACf,KAAA;AAAA;AAAA,iBAKc,aAAA,CAAA,GAAiB,UAAA;AAAA,iBAIjB,iBAAA,GAAA,CAAqB,EAAA,GAAK,KAAA,EAAO,UAAA,KAAe,OAAA,CAAQ,CAAA,IAAK,OAAA,CAAQ,CAAA;AAAA,cAYxE,mBAAA,EAAqB,MAAA;AAAA,iBAElB,qBAAA,CAAA;;;;;;;;;;AFpChB;KGOK,QAAA,GAAW,kBAAA;AAAA,UAEC,cAAA;EACf,QAAA,CAAS,IAAA,UAAc,MAAA,EAAQ,QAAA;EAC/B,GAAA,CAAI,IAAA,WAAe,QAAA;EACnB,GAAA,IAAO,MAAA,SAAe,QAAA;EACtB,GAAA,CAAI,IAAA;AAAA;AAAA,cAGA,kBAAA,YAA8B,cAAA;EAAA,QAC1B,OAAA;EAER,QAAA,CAAS,IAAA,UAAc,MAAA,EAAQ,QAAA;EAI/B,GAAA,CAAI,IAAA,WAAe,QAAA;EAInB,GAAA,CAAA,GAAO,MAAA,SAAe,QAAA;EAQtB,GAAA,CAAI,IAAA;AAAA;;;;;cASO,cAAA,EAAc,kBAAA;;;;;AH9B3B;;;;;KIMY,UAAA;;;;AJmBZ;;;;;;;;;UIKiB,aAAA,gCAED,UAAA,GAAa,UAAA;;GAK1B,IAAA,WAAe,mBAAA;EAAA,SACP,MAAA,EAAQ,KAAA;EAAA,SACR,MAAA,EAAQ,KAAA;EAAA,SACR,SAAA,EAAW,QAAA;EAAA,SACX,YAAA,EAAc,WAAA;EH9BD;AASxB;;;;EATwB,SGoCb,QAAA;EHzBK;EAAA,SG2BL,YAAA;AAAA;;;;;;;KASC,WAAA,MACV,CAAA,SAAU,aAAA,UAAuB,UAAA,cAAwB,CAAA,gBAAiB,CAAA,GAAI,CAAA;;;;KAKpE,GAAA,eAAkB,MAAA,SAAe,aAAA,mBAC/B,KAAA,GAAQ,WAAA,CAAY,KAAA,CAAM,CAAA;;;;;;;;KAU5B,SAAA,eAAwB,MAAA,SAAe,aAAA,mBAErC,KAAA,IAAS,KAAA,CAAM,CAAA,UAAW,aAAA,UAAuB,UAAA,iBACzD,CAAA,WACQ,WAAA,CAAY,KAAA,CAAM,CAAA,qBAGlB,KAAA,IAAS,KAAA,CAAM,CAAA,UAAW,aAAA,UAAuB,UAAA,yBAEzD,CAAA,IAAK,WAAA,CAAY,KAAA,CAAM,CAAA;;;;;;AHrC7B;;;;;;;;;AAqDA;;;;;KGUY,eAAA,MACR,CAAA;EAEE,EAAA,EAAI,CAAA;AAAA;EACJ,EAAA,EAAI,CAAA;AAAA;EACJ,EAAA,EAAI,CAAA;AAAA;EACJ,KAAA,EAAO,CAAA;AAAA;EACP,EAAA,EAAI,CAAA;AAAA;EACJ,GAAA,EAAK,CAAA;AAAA;EACL,EAAA,EAAI,CAAA;AAAA;EACJ,GAAA,EAAK,CAAA;AAAA;EACL,MAAA;AAAA;EACA,SAAA;AAAA;EACA,KAAA;AAAA;EACA,UAAA;AAAA;;AH+DN;;;;;;;;;;;;;;;;;;;;AC7LA;;;;;AAMA;;;;;KEyJY,WAAA,eAA0B,MAAA,SAAe,aAAA,mBACvC,KAAA,IAAS,eAAA,CAAgB,WAAA,CAAY,KAAA,CAAM,CAAA;EAAA,CAGtD,GAAA,0BAA6B,eAAA;AAAA;EAE9B,IAAA,GAAO,WAAA,CAAY,KAAA;EACnB,GAAA,GAAM,WAAA,CAAY,KAAA;EAClB,IAAA,GAAO,WAAA,CAAY,KAAA;AAAA;;;;;;;;UAcJ,eAAA,eAA8B,MAAA,SAAe,aAAA;EAC5D,EAAA,SAAW,KAAA;EACX,IAAA;AAAA;AFjKF;;;;;AAEA;;AAFA,UE2KiB,gBAAA,eAA+B,MAAA,SAAe,aAAA;EAC7D,EAAA,SAAW,KAAA;EACX,IAAA;AAAA;;;ADpN2D;;;;;AAc7D;;;;;;;;;;UC0NiB,eAAA,eAA8B,MAAA,SAAe,aAAA;EDzN7B;;;;;;;ECiO/B,IAAA;EACA,OAAA,EAAS,KAAA;ED/NL;;;AACL;;;;;ECuOC,UAAA,SAAmB,KAAA,UAAe,KAAA;EAClC,MAAA,GAAS,gBAAA,CAAiB,KAAA;EAC1B,OAAA,GAAU,eAAA,CAAgB,KAAA;AAAA;;;AJhN5B;;;;;;AAAA,cK4Ba,aAAA;;;;;cAMA,SAAA;AJjDb;;;AAAA,cIsDa,SAAA;;AJ5Cb;;;;;;cIqDa,gBAAA,SAAyB,KAAA;cACxB,OAAA;AAAA;;;;UASG,WAAA,eAA0B,MAAA,SAAe,aAAA;EACxD,MAAA,QAAc,KAAA;EACd,GAAA;AAAA;;;;UAMe,eAAA,eAA8B,MAAA,SAAe,aAAA;EAC5D,KAAA,GAAQ,WAAA,CAAY,KAAA;EACpB,OAAA,GAAU,WAAA,CAAY,KAAA;EACtB,KAAA;EACA,MAAA;AAAA;;;;AJ7DF;;UIqEiB,YAAA,eACD,MAAA,SAAe,aAAA,kBACd,OAAA,GAAU,OAAA;EJrED;EIwExB,KAAA,EAAO,WAAA,CAAY,KAAA;EJzEV;EI2ET,GAAA,GAAM,OAAA,CAAQ,gBAAA,CAAiB,MAAA;EJ1EtB;EI4ET,MAAA,GAAS,MAAA,SAAe,GAAA;EJ5EA;EI8ExB,OAAA,GAAU,WAAA,CAAY,KAAA;EJnEG;EIqEzB,KAAA;AAAA;;;;;;UAQe,eAAA,eACD,MAAA,SAAe,aAAA,kBACd,OAAA,GAAU,OAAA;EJ1Ba;;;;;;EIkCtC,MAAA,SAAe,KAAA,oBAAyB,KAAA;EJjCpC;;;;;;;AAkCN;;;;;;;;EIeE,WAAA,EAAa,WAAA,CAAY,KAAA;EJdzB;;;;EImBA,GAAA,GAAM,OAAA,CAAQ,gBAAA,CAAiB,MAAA;AAAA;;AJgCjC;;KI1BY,aAAA,eAA4B,MAAA,SAAe,aAAA;EACjD,EAAA;AAAA;EACA,EAAA;EAAa,MAAA,QAAc,KAAA;AAAA;EAC3B,EAAA;EAAmC,MAAA,QAAc,KAAA;AAAA;;;;UAKtC,gBAAA,eAA+B,MAAA,SAAe,aAAA;EJuBrD;EIrBR,MAAA,EAAQ,MAAA,SAAe,aAAA,CAAc,KAAA;;EAErC,OAAA,UAAiB,KAAA;;EAEjB,KAAA,GAAQ,WAAA,CAAY,KAAA;EHjLK;EGmLzB,OAAA,GAAU,WAAA,CAAY,KAAA;EHlLtB;EGoLA,KAAA;AAAA;;;;;AH3KF;;;;;;;;;;;;;cG+La,WAAA,eACG,MAAA,SAAe,aAAA,kBACd,OAAA,GAAU,OAAA;EHjM6C;EAAA,SGwMpD,KAAA,EAAO,MAAA;EHxMkD;EAAA,iBG0MxD,WAAA;EH1MiE;;AAYtF;;;EAZsF,iBGgNjE,EAAA;EHpM+C;EAAA,iBGsM/C,iBAAA;EHpMgB;;;;;EG0LjB,KAAA,EAAO,MAAA;;EAEN,WAAA,EAAa,QAAA,CAAS,MAAA,SAAe,UAAA;EFzN7C;;;;AAEb;;EE6NqB,EAAA,EAAI,kBAAA;;EAEJ,iBAAA;EF7NA;;;;;;;;;;;;;EAAA,YE6OP,YAAA,CAAA;EF5OU;;;;;AAEvB;EAFuB,QE+Pd,gBAAA;;;;;UAYA,YAAA;EFvQ0B;;;;;;EAAA,QEoR1B,oBAAA;EFjRuB;;;;;;EAAA,QE6RvB,kBAAA;EFrRc;;;;;AAiBxB;;;;EE2RQ,OAAA,CAAQ,KAAA,EAAO,WAAA,CAAY,KAAA,IAAS,OAAA,CAAQ,GAAA,CAAI,KAAA;;;;ADnTxD;;;;;AAwBA;;;;;EC+SQ,QAAA,CAAS,OAAA,GAAS,eAAA,CAAgB,KAAA,IAAc,OAAA,CAAQ,GAAA,CAAI,KAAA;EDvSjD;;;;;;;;;;EC4UX,KAAA,CAAM,KAAA,GAAQ,WAAA,CAAY,KAAA,IAAS,OAAA;EDhVzC;;;;;EC6VM,MAAA,CAAO,KAAA,EAAO,WAAA,CAAY,KAAA,IAAS,OAAA;EDxVxB;;;;;;;;;AAmBnB;;;;;;;;;;;ECoWQ,QAAA,iBAAyB,KAAA,UAAA,CAC7B,MAAA,EAAQ,CAAA,EACR,OAAA;IACE,KAAA,GAAQ,WAAA,CAAY,KAAA;IACpB,OAAA,mBDvW+C;ICyW/C,WAAA;EAAA,IAED,OAAA,CAAQ,WAAA,CAAY,KAAA,CAAM,CAAA;ED3WiD;;;AAKhF;EC0YQ,MAAA,CAAO,MAAA,EAAQ,gBAAA,CAAiB,MAAA,IAAU,OAAA,CAAQ,GAAA,CAAI,KAAA;ED1Y/C;;;;;;;;;;;;;;;;;;;AAWf;;;;;;;;;;;;ECmaQ,MAAA,CACJ,MAAA,EAAQ,gBAAA,CAAiB,MAAA,GACzB,IAAA;IACE,MAAA,SAAe,KAAA,oBAAyB,KAAA;IACxC,GAAA,GAAM,OAAA,CAAQ,gBAAA,CAAiB,MAAA,IDhad;ICkajB,QAAA,GAAW,WAAA,CAAY,KAAA;EAAA,IAExB,OAAA,CAAQ,GAAA,CAAI,KAAA;EDpauB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BxC;;;;;;;;;;;EC2eQ,QAAA,CACJ,MAAA,EAAQ,gBAAA,CAAiB,MAAA,GACzB,IAAA,EAAM,eAAA,CAAgB,KAAA,EAAO,MAAA,IAC5B,OAAA;IAAU,QAAA;IAAgB,GAAA,EAAK,GAAA,CAAI,KAAA;EAAA;IAAa,QAAA;IAAiB,GAAA,EAAK,GAAA,CAAI,KAAA;EAAA;ED1ezE;;;;ECkiBE,UAAA,CAAW,MAAA,EAAQ,gBAAA,CAAiB,MAAA,MAAY,OAAA,CAAQ,GAAA,CAAI,KAAA;EDhiBvD;;;;;;;;;;;;;;;AAyCb;;;;;;ECwhBQ,MAAA,CACJ,KAAA,EAAO,WAAA,CAAY,KAAA,GACnB,KAAA,EAAO,OAAA,CAAQ,gBAAA,CAAiB,MAAA,KAC/B,OAAA,CAAQ,GAAA,CAAI,KAAA;ED1hBwC;;;;EC4iBjD,UAAA,CACJ,KAAA,EAAO,WAAA,CAAY,KAAA,GACnB,KAAA,EAAO,OAAA,CAAQ,gBAAA,CAAiB,MAAA,KAC/B,OAAA,CAAQ,GAAA,CAAI,KAAA;ED1iBR;;;;;;;;;;;;;;;ECikBD,MAAA,CAAO,KAAA,EAAO,WAAA,CAAY,KAAA,IAAS,OAAA,CAAQ,GAAA,CAAI,KAAA;EDnkBpD;;;;EColBK,UAAA,CAAW,KAAA,EAAO,WAAA,CAAY,KAAA,IAAS,OAAA,CAAQ,GAAA,CAAI,KAAA;EDjlBzD;;;;;;;;AAeF;;;;;;;;EC6lBQ,WAAA,GAAA,CAAe,EAAA,GAAK,EAAA,EAAI,WAAA,CAAY,KAAA,EAAO,MAAA,MAAY,OAAA,CAAQ,CAAA,IAAK,OAAA,CAAQ,CAAA;ED7lBrC;;;;;;;AAY/C;;;;;;;;;;;;;;;ECunBQ,KAAA,CAAM,OAAA,EAAS,YAAA,CAAa,KAAA,EAAO,MAAA,IAAU,OAAA,CAAQ,GAAA,CAAI,KAAA;EDjmBhD;;;;;;;;;;;;;;;;;;;;;;;;;ECutBT,SAAA,iBAA0B,MAAA,oBAA0B,MAAA,kBAAA,CACxD,OAAA,EAAS,gBAAA,CAAiB,KAAA,IACzB,OAAA,CAAQ,OAAA;EDrsBX;;;;;;;;ACpLF;;;;;AAMA;;;;;AAKA;;EAw7BQ,aAAA,CAAA,GAAiB,OAAA;EAx7BH;EAAA,QAy8BZ,kBAAA;EAAA,QAyBA,iBAAA;EAAA,QAeA,aAAA;AAAA;;;;ALxhCV;;;;;;;;;;;;UMoDU,cAAA;EACR,IAAA,6GAIE,IAAA;IACA,UAAA,GAAa,WAAA;IACb,OAAA,GAAU,QAAA;IACV,aAAA,GAAgB,WAAA;IAChB,MAAA;EAAA,IACE,aAAA,iBAGF,WAAA,uBAAkC,QAAA,8BAClC,WAAA,uBAAkC,WAAA;EAIpC,OAAA,mCAA0C,IAAA;IACxC,MAAA;IACA,OAAA,GAAU,QAAA;IACV,OAAA;IACA,MAAA;EAAA,IACE,aAAA,oBAAiC,QAAA;EAErC,OAAA,mCAA0C,IAAA;IACxC,MAAA;IACA,OAAA,GAAU,QAAA;IACV,MAAA;EAAA,IACE,aAAA,oBAAiC,QAAA;EAGrC,IAAA,mCAAuC,IAAA;IACrC,OAAA,GAAU,QAAA;IACV,OAAA;IACA,MAAA;EAAA,IACE,aAAA,iBAA8B,QAAA;EAElC,IAAA,mCAAuC,IAAA;IACrC,OAAA,GAAU,QAAA;IACV,MAAA;EAAA,IACE,aAAA,iBAA8B,QAAA;EAGlC,OAAA,mCAA0C,IAAA;IACxC,OAAA,GAAU,QAAA;IACV,OAAA;IACA,MAAA;EAAA,IACE,aAAA,oBAAiC,QAAA;EAErC,OAAA,mCAA0C,IAAA;IACxC,OAAA,GAAU,QAAA;IACV,MAAA;EAAA,IACE,aAAA,oBAAiC,QAAA;EAGrC,MAAA,iFAAuF,IAAA;IACrF,IAAA,GAAO,KAAA;IACP,OAAA,GAAU,QAAA;IACV,OAAA,EAAS,KAAA;IACT,MAAA;EAAA,IACE,aAAA,CACF,KAAA,+CAEA,QAAA;EAIF,MAAA,iFAAuF,IAAA;IACrF,IAAA,GAAO,KAAA;IACP,OAAA,GAAU,QAAA;IACV,MAAA;EAAA,IACE,aAAA,CACF,KAAA,+CAEA,QAAA;EAKF,MAAA,mCAAyC,IAAA;IACvC,OAAA,GAAU,QAAA;IACV,OAAA;IACA,MAAA;EAAA,IACE,aAAA,mBAAgC,QAAA;EAEpC,MAAA,mCAAyC,IAAA;IACvC,OAAA,GAAU,QAAA;IACV,MAAA;EAAA,IACE,aAAA,mBAAgC,QAAA;EAGpC,OAAA,mCAA0C,IAAA;IACxC,OAAA,GAAU,QAAA;IACV,OAAA;IACA,MAAA;EAAA,IACE,aAAA,qBAAkC,QAAA;EAEtC,OAAA,mCAA0C,IAAA;IACxC,OAAA,GAAU,QAAA;IACV,MAAA;EAAA,IACE,aAAA,qBAAkC,QAAA;EAEtC,SAAA,wEAAiF,IAAA;IAC/E,OAAA,GAAU,QAAA;IACV,UAAA,GAAa,WAAA;IACb,YAAA;IACA,MAAA;EAAA,IACE,aAAA,CACF,IAAA,eAEA,QAAA,8BACA,WAAA;EAIF,KAAA,gDAAqD,IAAA;IACnD,OAAA,GAAU,QAAA;IACV,OAAA,EAAS,CAAA;IACT,MAAA;EAAA,IACE,aAAA,CAAc,CAAA,WAAY,QAAA;EAE9B,KAAA,gDAAqD,IAAA;IACnD,OAAA,GAAU,QAAA;IACV,MAAA;EAAA,IACE,aAAA,CAAc,CAAA,WAAY,QAAA;EAE9B,SAAA,mCAA4C,IAAA;IAC1C,OAAA,GAAU,QAAA;IACV,MAAA;EAAA,IACE,aAAA,wBAAqC,QAAA;AAAA;;;;;;;cAuT9B,MAAA,EAAQ,cAAA;;;AL5frB;;;;;AAUA;;;;;AAVA,UMoEiB,YAAA,eACD,MAAA,SAAe,aAAA,kBACd,OAAA,GAAU,kBAAA,CAAmB,WAAA;EN1Dd;;;;;EMiE9B,KAAA,EAAO,MAAA;ENlEoB;;;;EMuE3B,MAAA,EAAQ,eAAA,CAAgB,KAAA;ENtEa;;AAGvC;;EMwEE,WAAA,EAAa,QAAA,CAAS,MAAA,SAAe,UAAA;ENxEP;;;;;EM8E9B,iBAAA;ENrEe;;;;;;;EM6Ef,UAAA,CAAW,EAAA,EAAI,kBAAA,GAAqB,WAAA,CAAY,KAAA,EAAO,MAAA;AAAA;;ANhEzD;;;;;;;;;AAqDA;iBMyBgB,WAAA,qBAAgC,MAAA,SAAe,aAAA,EAAA,CAC7D,GAAA,EAAK,eAAA,CAAgB,KAAA;;;;;;;;kBAAD,cAAA,CAAA,cAAA;;;;;;;;;;;;;;;;;;;;mBA6JD,kBAAA,KAAkB,WAAA,CAAA,KAAA,EAAA,kBAAA;;;;;;;kBAAA,cAAA,CAAA,cAAA;;;;;;;;;;;;;;;;;;;;cClRjC,iBAAA;EAAA,QACI,MAAA;ERV0D;;;;;EQiBlE,QAAA,CAAS,IAAA,UAAc,KAAA,EAAO,OAAA,EAAS,MAAA;ERQzB;;;EQcd,GAAA,CAAI,IAAA,WAAe,OAAA;ERdwB;;;EQqB3C,GAAA,CAAI,IAAA;ERrBoE;;;;ACf1E;;EO8CE,SAAA,CAAA,GAAa,MAAA,SAAe,OAAA;EP7C5B;;AASF;;;;;;;;;;EOwDE,KAAA,CAAA;AAAA;;;;;;;;cAmBW,aAAA,EAAa,iBAAA;;;;;;;;;cCxBb,iBAAA,SAA0B,KAAA;cACzB,OAAA;AAAA;;;;;;;;;iBAiUE,eAAA,CAAgB,MAAA"}
package/dist/index.mjs CHANGED
@@ -15,6 +15,6 @@ import{drizzle as e}from"drizzle-orm/postgres-js";import t from"postgres";import
15
15
  `)}),console.log(` ✓ Applied ${t.namespace}/${t.name}`)}catch(e){throw console.error(` ✗ Failed to apply ${t.namespace}/${t.name}:`,e),e}}console.log(`Successfully applied ${r.length} migration${r.length===1?``:`s`}`)}async function I(e,t,n,r=1){await M(e);let{applied:i}=await P(e,t);if(i.length===0){console.log(`No applied migrations to roll back`);return}let a=i.slice(-r).reverse(),o=await Promise.all(a.map(async e=>({meta:e,mod:await n(e.path)})));for(let{meta:e,mod:t}of o)if(typeof t.down!=`function`)throw Error(`Migration ${e.namespace}/${e.name} has no \`down\` export — cannot roll back. Add an async \`down({ db })\` function to the migration file.`);for(let{meta:t,mod:n}of o){console.log(` Rolling back ${t.namespace}/${t.name}...`);try{await e.transaction(async e=>{await n.down({db:e}),await e.execute(h`
16
16
  DELETE FROM _toolkit_migrations
17
17
  WHERE namespace = ${t.namespace} AND name = ${t.name}
18
- `)}),console.log(` ✓ Rolled back ${t.namespace}/${t.name}`)}catch(e){throw console.error(` ✗ Failed to roll back ${t.namespace}/${t.name}:`,e),e}}console.log(`Successfully rolled back ${o.length} migration${o.length===1?``:`s`}`)}const L=new class{schemas=new Map;register(e,t){this.schemas.set(e,t)}get(e){return this.schemas.get(e)}all(){let e={};for(let[t,n]of this.schemas.entries())e[t]=n;return e}has(e){return this.schemas.has(e)}},R=/^[a-zA-Z_][a-zA-Z0-9_-]*$|^\d+$/,z=new Set([`$and`,`$or`,`$not`]),B=new Set([`eq`,`ne`,`in`,`notIn`,`gt`,`gte`,`lt`,`lte`,`isNull`,`isNotNull`,`ilike`,`startsWith`]);var V=class extends Error{constructor(e){super(`WhereBuilder: ${e}`),this.name=`WhereBuilderError`}};function H(e){return e.replace(/[\\%_]/g,`\\$&`)}function me(e,t,n){let r=c(e);if(!n.includes(`.`)){let e=r[n];if(!e)throw new V(`unknown column "${n}"`);return e}let[i,...a]=n.split(`.`);if(!i||a.length===0)throw new V(`malformed dotted key "${n}"`);let o=t[i];if(!o)throw new V(`unknown column "${i}" in dotted key "${n}"`);if(o!==`jsonb`)throw new V(`dotted-path access is only supported on jsonb columns, but "${i}" is ${o}`);for(let e of a)if(!R.test(e))throw new V(`invalid jsonb path segment "${e}" — must match [a-zA-Z_][a-zA-Z0-9_-]* or be a non-negative integer`);let s=r[i];if(!s)throw new V(`unknown column "${i}"`);let l=h`${s}`;for(let e=0;e<a.length;e++){let t=a[e],n=e===a.length-1;if(/^\d+$/.test(t)){if(t.length>10)throw new V(`jsonb path index "${t}" is too large`);let e=h.raw(t);l=n?h`${l}->>${e}`:h`${l}->${e}`}else l=n?h`${l}->>${t}`:h`${l}->${t}`}return l}function he(e,t){if(t===null)return m(e);if(typeof t==`string`||typeof t==`number`||typeof t==`boolean`||typeof t==`bigint`||t instanceof Date)return s(e,t);if(Array.isArray(t))throw new V(`arrays must be wrapped in an operator (e.g. { in: [...] }) — bare arrays are not allowed`);if(typeof t!=`object`)throw new V(`unsupported value type: ${typeof t}`);let n=t,r=Object.keys(n);if(r.length===0)throw new V(`empty operator object`);let i=[];for(let t of r){if(!B.has(t))throw new V(`unknown operator "${t}"`);let r=n[t];switch(t){case`eq`:i.push(s(e,r));break;case`ne`:i.push(re(e,r));break;case`in`:if(!Array.isArray(r))throw new V(`"in" operator requires an array`);r.length===0?i.push(h`false`):i.push(f(e,r));break;case`notIn`:if(!Array.isArray(r))throw new V(`"notIn" operator requires an array`);r.length===0?i.push(h`true`):i.push(ae(e,r));break;case`gt`:i.push(u(e,r));break;case`gte`:i.push(ee(e,r));break;case`lt`:i.push(te(e,r));break;case`lte`:i.push(ne(e,r));break;case`isNull`:if(r!==!0)throw new V('"isNull" operator must be `true`');i.push(m(e));break;case`isNotNull`:if(r!==!0)throw new V('"isNotNull" operator must be `true`');i.push(p(e));break;case`ilike`:if(typeof r!=`string`)throw new V(`"ilike" operator requires a string`);i.push(d(e,`%${H(r)}%`));break;case`startsWith`:if(typeof r!=`string`)throw new V(`"startsWith" operator requires a string`);i.push(d(e,`${H(r)}%`));break}}if(i.length!==0)return i.length===1?i[0]:a(...i)}function U(e,t,n,r=0){if(!n)return;if(typeof n!=`object`||Array.isArray(n))throw new V(`where clause must be a plain object`);if(r>16)throw new V(`where clause exceeds maximum nesting depth of 16 — real queries should not need this much nesting; if yours does, reconsider whether it should be expressed differently`);let i=[];for(let o of Object.keys(n)){let s=n[o];if(o===`$and`){if(!Array.isArray(s))throw new V(`$and must be an array`);if(s.length===0)throw new V(`$and must contain at least one clause`);let n=s.map(n=>U(e,t,n,r+1)).filter(e=>e!==void 0);n.length>0&&i.push(a(...n));continue}if(o===`$or`){if(!Array.isArray(s))throw new V(`$or must be an array`);if(s.length===0)throw new V(`$or must contain at least one clause`);let n=s.map(n=>U(e,t,n,r+1)).filter(e=>e!==void 0);n.length>0&&i.push(oe(...n));continue}if(o===`$not`){if(!W(s))throw new V(`$not must contain a non-empty clause`);let n=U(e,t,s,r+1);n&&i.push(ie(n));continue}if(z.has(o))continue;let c=he(me(e,t,o),s);c&&i.push(c)}if(i.length!==0)return i.length===1?i[0]:a(...i)}function W(e){return typeof e!=`object`||!e||Array.isArray(e)?!1:Object.keys(e).length>0}const G=/^[a-zA-Z_][a-zA-Z0-9_]*$/,K=y(`pg_class`,{relname:w(`relname`,{length:63}).notNull(),reltuples:de(`reltuples`).notNull()}),ge=100,q=1e3,J=1e3;var Y=class extends Error{constructor(e){super(`TableClient: ${e}`),this.name=`TableClientError`}},X=class e{constructor(e,t,n,r=[]){this.table=e,this.columnKinds=t,this.db=n,this.primaryKeyColumns=r}get _selectTable(){return this.table}getColumnOrThrow(e,t){let n=c(this.table)[e];if(!n)throw new Y(`${t}: unknown column "${e}"`);return n}buildOrderBy(e,t){return e.map(e=>{let n=this.getColumnOrThrow(e.column,`${t}: orderBy`);return e.dir===`desc`?h`${n} DESC`:h`${n} ASC`})}requireNonEmptyWhere(e,t){if(!W(e))throw new Y(`${t} requires a non-empty where clause`)}validateColumnKeys(e,t){if(e.length===0)return;let n=c(this.table);for(let r of e)if(!Object.hasOwn(n,r))throw new Y(`${t}: unknown column "${r}"`)}async findOne(e){this.requireNonEmptyWhere(e,`findOne`);let t=this.buildWhereOrThrow(e);return(await this.db.select().from(this._selectTable).where(t).limit(1))[0]??null}async findMany(e={}){let t=this.validateLimit(e.limit);if(e.offset!==void 0&&(!Number.isInteger(e.offset)||e.offset<0))throw new Y(`offset must be a non-negative integer, got ${e.offset}`);let n=e.orderBy&&e.orderBy.length>0?this.buildOrderBy(e.orderBy,`findMany`):void 0,r=e.where?this.buildWhereOrThrow(e.where):void 0,i=this.db.select().from(this._selectTable).$dynamic();return r&&(i=i.where(r)),n&&(i=i.orderBy(...n)),i=i.limit(t),e.offset!==void 0&&(i=i.offset(e.offset)),await i}async count(e){let t=e?this.buildWhereOrThrow(e):void 0,n=this.db.select({value:o()}).from(this._selectTable).$dynamic();t&&(n=n.where(t));let r=await n;return Number(r[0]?.value??0)}async exists(e){this.requireNonEmptyWhere(e,`exists`);let t=this.buildWhereOrThrow(e);return(await this.db.select({one:h`1`}).from(this._selectTable).where(t).limit(1)).length>0}async distinct(e,t){let n=this.getColumnOrThrow(e,`distinct`),r=[];t?.includeNull||r.push(p(n)),t?.where&&r.push(this.buildWhereOrThrow(t.where));let i=this.db.selectDistinct({value:n}).from(this._selectTable).$dynamic();if(r.length===1?i=i.where(r[0]):r.length>1&&(i=i.where(a(...r))),t?.orderBy){let e=t.orderBy===`desc`?h`${n} DESC`:h`${n} ASC`;i=i.orderBy(e)}return(await i).map(e=>e.value)}async insert(e){return(await this.db.insert(this.table).values(e).returning())[0]}async upsert(e,t){let n=Array.isArray(t.target)?t.target:[t.target];if(n.length===0)throw new Y(`upsert requires at least one target column`);let r=n.map(e=>this.getColumnOrThrow(e,`upsert target`)),i=t.set??e;t.set&&this.validateColumnKeys(Object.keys(t.set),`upsert set`);let a=t.setWhere&&W(t.setWhere)?this.buildWhereOrThrow(t.setWhere):void 0,o=await this.db.insert(this.table).values(e).onConflictDoUpdate({target:r,set:i,...a?{setWhere:a}:{}}).returning();if(o.length>0)return o[0];let s={};for(let t of n)s[t]=e[t];let c=await this.findOne(s);if(!c)throw new Y(`upsert with setWhere: conflict row not found after blocked update — this should not happen`);return c}async insertMany(e){if(!Array.isArray(e))throw new Y(`insertMany requires an array of rows`);if(e.length===0)return[];if(e.length>1e3)throw new Y(`insertMany batch too large: ${e.length} > ${J}`);return await this.db.insert(this.table).values(e).returning()}async update(e,t){this.requireNonEmptyWhere(e,`update`),this.validateColumnKeys(Object.keys(t),`update`);let n=this.buildWhereOrThrow(e),r=await this.db.update(this.table).set(t).where(n).returning();if(r.length>1)throw new Y(`update matched ${r.length} rows but is expected to match at most one — use updateMany for bulk updates. WARNING: the update has already been applied; wrap in a transaction if you need rollback.`);return r[0]??null}async updateMany(e,t){this.requireNonEmptyWhere(e,`updateMany`),this.validateColumnKeys(Object.keys(t),`updateMany`);let n=this.buildWhereOrThrow(e);return await this.db.update(this.table).set(t).where(n).returning()}async delete(e){this.requireNonEmptyWhere(e,`delete`);let t=this.buildWhereOrThrow(e),n=await this.db.delete(this.table).where(t).returning();if(n.length>1)throw new Y(`delete matched ${n.length} rows but is expected to match at most one — use deleteMany for bulk deletes. WARNING: the delete has already been applied; wrap in a transaction if you need rollback.`);return n[0]??null}async deleteMany(e){this.requireNonEmptyWhere(e,`deleteMany`);let t=this.buildWhereOrThrow(e);return await this.db.delete(this.table).where(t).returning()}async transaction(t){return await this.db.transaction(async n=>await t(new e(this.table,this.columnKinds,n,this.primaryKeyColumns)))}async claim(e){if(this.primaryKeyColumns.length===0)throw new Y(`claim() requires the table to have a primary key — declare primaryKey in defineTable() or use column.uuid({ primaryKey: true })`);if(this.primaryKeyColumns.length>1)throw new Y(`claim() does not support composite primary keys — use a single-column primary key or call transaction() with FOR UPDATE directly`);if(this.requireNonEmptyWhere(e.where,`claim`),!Number.isInteger(e.limit)||e.limit<1)throw new Y(`claim limit must be a positive integer, got ${e.limit}`);if(e.limit>1e3)throw new Y(`claim limit ${e.limit} exceeds MAX_LIMIT ${q}`);let t=Object.keys(e.set??{}),n=Object.keys(e.setSql??{});if(t.length===0&&n.length===0)throw new Y(`claim requires at least one field in set or setSql`);this.validateColumnKeys(t,`claim set`),this.validateColumnKeys(n,`claim setSql`);let r=this.buildWhereOrThrow(e.where),i=e.orderBy&&e.orderBy.length>0?this.buildOrderBy(e.orderBy,`claim`):void 0,a=this.primaryKeyColumns[0],o=this.getColumnOrThrow(a,`claim primary key`);return await this.db.transaction(async t=>{let n=t.select({_pk:o}).from(this._selectTable).where(r).limit(e.limit).for(`update`,{skipLocked:!0}).$dynamic();i&&(n=n.orderBy(...i));let a=await n;if(a.length===0)return[];let s=a.map(e=>e._pk),c={...e.set??{}};if(e.setSql)for(let[t,n]of Object.entries(e.setSql))c[t]=n;return await t.update(this.table).set(c).where(f(o,s)).returning()})}async aggregate(e){let t=Object.entries(e.select);if(t.length===0)throw new Y(`aggregate requires at least one select field`);for(let[e]of t)if(!G.test(e))throw new Y(`aggregate: invalid output alias "${e}" — must match ${G.source}`);let n={};if(e.groupBy)for(let t of e.groupBy)n[t]=this.getColumnOrThrow(t,`aggregate groupBy`);for(let[e,r]of t)n[e]=this.buildAggregateExpr(r);let r=this.db.select(n).from(this._selectTable).$dynamic();if(e.where&&(r=r.where(this.buildWhereOrThrow(e.where))),e.groupBy&&e.groupBy.length>0){let t=e.groupBy.map(e=>this.getColumnOrThrow(e,`aggregate groupBy`));r=r.groupBy(...t)}return e.orderBy&&e.orderBy.length>0&&(r=r.orderBy(...this.buildOrderBy(e.orderBy,`aggregate`))),e.limit!==void 0&&(r=r.limit(this.validateLimit(e.limit))),await r}async countEstimate(){let e=l(this.table),[t]=await this.db.select({estimate:h`greatest(${K.reltuples}::bigint, 0)`}).from(K).where(s(K.relname,e)).limit(1);return t?Number(t.estimate):0}buildAggregateExpr(e){if(`column`in e&&e.column){let t=this.getColumnOrThrow(e.column,`aggregate`);switch(e.fn){case`count`:return h`count(${t})::int`;case`sum`:return h`sum(${t})`;case`avg`:return h`avg(${t})`;case`min`:return h`min(${t})`;case`max`:return h`max(${t})`;default:throw new Y(`aggregate: unknown function "${e.fn}"`)}}if(e.fn===`count`)return h`count(*)::int`;throw new Y(`aggregate: ${e.fn} requires a column`)}buildWhereOrThrow(e){try{let t=U(this.table,this.columnKinds,e);if(!t)throw new Y(`where clause produced no SQL predicate`);return t}catch(e){throw e instanceof V?new Y(e.message.replace(/^WhereBuilder: /,``)):e}}validateLimit(e){if(e===void 0)return 100;if(!Number.isInteger(e)||e<1)throw new Y(`limit must be a positive integer, got ${e}`);if(e>1e3)throw new Y(`limit ${e} exceeds MAX_LIMIT ${q} — paginate or use claim() for batch processing`);return e}};function Z(e,t){let n=e;return Object.defineProperty(n,`__kind`,{value:t.kind,enumerable:!1}),Object.defineProperty(n,`__notNull`,{value:t.notNull,enumerable:!1}),Object.defineProperty(n,`__hasDefault`,{value:t.hasDefault,enumerable:!1}),Object.defineProperty(n,`__type`,{value:void 0,enumerable:!1}),t.pgName&&Object.defineProperty(n,`__pgName`,{value:t.pgName,enumerable:!1}),t.primaryKey&&Object.defineProperty(n,`__primaryKey`,{value:!0,enumerable:!1}),n}const _e={uuid(e){let t=e?.notNull??!1,n=e?.defaultRandom??!1,r=e?.primaryKey??!1;return Z(n=>{let i=C(n);return r&&(i=i.primaryKey()),e?.defaultRandom&&(i=i.defaultRandom()),t&&!r&&(i=i.notNull()),i},{kind:`uuid`,notNull:t||r,hasDefault:n||r,pgName:e?.pgName,primaryKey:r||void 0})},varchar(e){if(!Number.isInteger(e.length)||e.length<1||e.length>1073741823)throw Error(`column.varchar: length must be an integer between 1 and 1073741823, got ${e.length}`);let t=e.notNull??!1;return Z(n=>{let r=w(n,{length:e.length});return t&&(r=r.notNull()),e.default!==void 0&&(r=r.default(e.default)),r},{kind:`varchar`,notNull:t,hasDefault:e.default!==void 0,pgName:e.pgName})},text(e){let t=e?.notNull??!1;return Z(n=>{let r=b(n);return t&&(r=r.notNull()),e?.default!==void 0&&(r=r.default(e.default)),r},{kind:`text`,notNull:t,hasDefault:e?.default!==void 0,pgName:e?.pgName})},integer(e){let t=e?.notNull??!1;return Z(n=>{let r=_(n);return t&&(r=r.notNull()),e?.default!==void 0&&(r=r.default(e.default)),r},{kind:`integer`,notNull:t,hasDefault:e?.default!==void 0,pgName:e?.pgName})},bigint(e){let t=e?.mode??`number`,n=e?.notNull??!1;return Z(r=>{let i=se(r,{mode:t});return n&&(i=i.notNull()),e?.default!==void 0&&(i=i.default(e.default)),i},{kind:`bigint`,notNull:n,hasDefault:e?.default!==void 0,pgName:e?.pgName})},double(e){let t=e?.notNull??!1;return Z(n=>{let r=g(n);return t&&(r=r.notNull()),e?.default!==void 0&&(r=r.default(e.default)),r},{kind:`double`,notNull:t,hasDefault:e?.default!==void 0,pgName:e?.pgName})},boolean(e){let t=e?.notNull??!1;return Z(n=>{let r=ce(n);return t&&(r=r.notNull()),e?.default!==void 0&&(r=r.default(e.default)),r},{kind:`boolean`,notNull:t,hasDefault:e?.default!==void 0,pgName:e?.pgName})},timestamp(e){let t=e?.notNull??!1,n=e?.defaultNow??!1,r=e?.withTimezone??!0;return Z(n=>{let i=x(n,{withTimezone:r});return t&&(i=i.notNull()),e?.defaultNow&&(i=i.defaultNow()),i},{kind:`timestamp`,notNull:t,hasDefault:n,pgName:e?.pgName})},jsonb(e){let t=e?.notNull??!1;return Z(n=>{let r=v(n);return t&&(r=r.notNull()),e?.default!==void 0&&(r=r.default(e.default)),r},{kind:`jsonb`,notNull:t,hasDefault:e?.default!==void 0,pgName:e?.pgName})},uuidArray(e){let t=e?.notNull??!1;return Z(e=>{let n=C(e).array();return t?n.notNull():n},{kind:`uuidArray`,notNull:t,hasDefault:!1,pgName:e?.pgName})}},Q=new class{byName=new Map;register(e,t,n){let r=this.byName.get(e);if(r){if(r.table===t||r.source&&n&&r.source===n)return;throw Error(`tableRegistry: a different table is already registered under name "${e}". Did you call defineTable twice? First registered from ${r.source??`<unknown>`}, now from ${n??`<unknown>`}.`)}this.byName.set(e,{name:e,table:t,source:n})}get(e){return this.byName.get(e)?.table}has(e){return this.byName.has(e)}allTables(){let e={};for(let t of this.byName.values())e[t.name]=t.table;return e}clear(){if(process.env.NODE_ENV===`production`)throw Error(`tableRegistry.clear() is not allowed when NODE_ENV=production — this is a test-only utility. If you reached this from production code, you have a bug.`);this.byName.clear()}},ve=/^[a-z][a-z0-9_]*$/;function $(e,t,n){if(e.length>63)throw Error(`defineTable: ${t} "${e}" is ${e.length} characters — Postgres truncates identifiers at 63 chars silently, which can cause name collisions${n?`. ${n}`:``}`)}function ye(e){if(!ve.test(e.name))throw Error(`defineTable: invalid table name "${e.name}" — must match /^[a-z][a-z0-9_]*$/`);$(e.name,`table name`);let t={},n={};for(let[r,i]of Object.entries(e.columns)){if(typeof i!=`function`)throw Error(`defineTable: column "${r}" is not a column factory — did you call e.g. column.uuid() with parens?`);$(r,`column name in table "${e.name}"`);let a=i.__pgName;a&&$(a,`pgName for column "${r}" in table "${e.name}"`),t[r]=i(a??r),n[r]=i.__kind}let r=new Set(Object.keys(t));if(e.primaryKey!==void 0){let t=Array.isArray(e.primaryKey)?e.primaryKey:[e.primaryKey];for(let e of t)if(!r.has(String(e)))throw Error(`defineTable: primaryKey references unknown column "${String(e)}"`)}if(e.unique)for(let t of e.unique){for(let e of t.on)if(!r.has(String(e)))throw Error(`defineTable: unique constraint references unknown column "${String(e)}"`);$(t.name??`uq_${e.name}_${t.on.map(String).join(`_`)}`,t.name?`unique constraint name`:`auto-generated unique constraint name`,t.name?void 0:"Supply a shorter explicit `name` to the unique definition.")}if(e.indexes)for(let t of e.indexes){for(let e of t.on)if(!r.has(String(e)))throw Error(`defineTable: index references unknown column "${String(e)}"`);$(t.name??`idx_${e.name}_${t.on.map(String).join(`_`)}`,t.name?`index name`:`auto-generated index name`,t.name?void 0:"Supply a shorter explicit `name` to the index definition.")}let i=e.primaryKey!==void 0||e.unique&&e.unique.length>0||e.indexes&&e.indexes.length>0;function a(e,t,n){let r=e[t];if(!r)throw Error(`defineTable: ${n} references unknown column "${t}"`);return r}let o=i?y(e.name,t,t=>{let n=t,r=[];if(e.primaryKey!==void 0){let t=Array.isArray(e.primaryKey)?e.primaryKey:[e.primaryKey],i=a(n,String(t[0]),`primaryKey`),o=t.slice(1).map(e=>a(n,String(e),`primaryKey`));r.push(ue({columns:[i,...o]}))}if(e.unique)for(let t of e.unique){let e=a(n,String(t.on[0]),`unique constraint`),i=t.on.slice(1).map(e=>a(n,String(e),`unique constraint`));r.push(S().on(e,...i))}if(e.indexes)for(let t of e.indexes){let i=a(n,String(t.on[0]),`index`),o=t.on.slice(1).map(e=>a(n,String(e),`index`));r.push(le(t.name??`idx_${e.name}_${t.on.map(String).join(`_`)}`).on(i,...o))}return r}):y(e.name,t),s=be();Q.register(e.name,o,s);let c=Object.freeze({...n}),l;if(e.primaryKey!==void 0)l=Array.isArray(e.primaryKey)?e.primaryKey.map(String):[String(e.primaryKey)];else{l=[];for(let[t,n]of Object.entries(e.columns))n.__primaryKey&&l.push(t)}let u=Object.freeze([...l]);return{table:o,schema:e,columnKinds:c,primaryKeyColumns:u,makeClient:e=>new X(o,c,e,u)}}function be(){let e=Error().stack;if(!e)return;let t=e.split(`
18
+ `)}),console.log(` ✓ Rolled back ${t.namespace}/${t.name}`)}catch(e){throw console.error(` ✗ Failed to roll back ${t.namespace}/${t.name}:`,e),e}}console.log(`Successfully rolled back ${o.length} migration${o.length===1?``:`s`}`)}const L=new class{schemas=new Map;register(e,t){this.schemas.set(e,t)}get(e){return this.schemas.get(e)}all(){let e={};for(let[t,n]of this.schemas.entries())e[t]=n;return e}has(e){return this.schemas.has(e)}},R=/^[a-zA-Z_][a-zA-Z0-9_-]*$|^\d+$/,z=new Set([`$and`,`$or`,`$not`]),B=new Set([`eq`,`ne`,`in`,`notIn`,`gt`,`gte`,`lt`,`lte`,`isNull`,`isNotNull`,`ilike`,`startsWith`]);var V=class extends Error{constructor(e){super(`WhereBuilder: ${e}`),this.name=`WhereBuilderError`}};function H(e){return e.replace(/[\\%_]/g,`\\$&`)}function me(e,t,n){let r=c(e);if(!n.includes(`.`)){let e=r[n];if(!e)throw new V(`unknown column "${n}"`);return e}let[i,...a]=n.split(`.`);if(!i||a.length===0)throw new V(`malformed dotted key "${n}"`);let o=t[i];if(!o)throw new V(`unknown column "${i}" in dotted key "${n}"`);if(o!==`jsonb`)throw new V(`dotted-path access is only supported on jsonb columns, but "${i}" is ${o}`);for(let e of a)if(!R.test(e))throw new V(`invalid jsonb path segment "${e}" — must match [a-zA-Z_][a-zA-Z0-9_-]* or be a non-negative integer`);let s=r[i];if(!s)throw new V(`unknown column "${i}"`);let l=h`${s}`;for(let e=0;e<a.length;e++){let t=a[e],n=e===a.length-1;if(/^\d+$/.test(t)){if(t.length>10)throw new V(`jsonb path index "${t}" is too large`);let e=h.raw(t);l=n?h`${l}->>${e}`:h`${l}->${e}`}else l=n?h`${l}->>${t}`:h`${l}->${t}`}return l}function he(e,t){if(t===null)return m(e);if(typeof t==`string`||typeof t==`number`||typeof t==`boolean`||typeof t==`bigint`||t instanceof Date)return s(e,t);if(Array.isArray(t))throw new V(`arrays must be wrapped in an operator (e.g. { in: [...] }) — bare arrays are not allowed`);if(typeof t!=`object`)throw new V(`unsupported value type: ${typeof t}`);let n=t,r=Object.keys(n);if(r.length===0)throw new V(`empty operator object`);let i=[];for(let t of r){if(!B.has(t))throw new V(`unknown operator "${t}"`);let r=n[t];switch(t){case`eq`:i.push(s(e,r));break;case`ne`:i.push(re(e,r));break;case`in`:if(!Array.isArray(r))throw new V(`"in" operator requires an array`);r.length===0?i.push(h`false`):i.push(f(e,r));break;case`notIn`:if(!Array.isArray(r))throw new V(`"notIn" operator requires an array`);r.length===0?i.push(h`true`):i.push(ae(e,r));break;case`gt`:i.push(u(e,r));break;case`gte`:i.push(ee(e,r));break;case`lt`:i.push(te(e,r));break;case`lte`:i.push(ne(e,r));break;case`isNull`:if(r!==!0)throw new V('"isNull" operator must be `true`');i.push(m(e));break;case`isNotNull`:if(r!==!0)throw new V('"isNotNull" operator must be `true`');i.push(p(e));break;case`ilike`:if(typeof r!=`string`)throw new V(`"ilike" operator requires a string`);i.push(d(e,`%${H(r)}%`));break;case`startsWith`:if(typeof r!=`string`)throw new V(`"startsWith" operator requires a string`);i.push(d(e,`${H(r)}%`));break}}if(i.length!==0)return i.length===1?i[0]:a(...i)}function U(e,t,n,r=0){if(!n)return;if(typeof n!=`object`||Array.isArray(n))throw new V(`where clause must be a plain object`);if(r>16)throw new V(`where clause exceeds maximum nesting depth of 16 — real queries should not need this much nesting; if yours does, reconsider whether it should be expressed differently`);let i=[];for(let o of Object.keys(n)){let s=n[o];if(o===`$and`){if(!Array.isArray(s))throw new V(`$and must be an array`);if(s.length===0)throw new V(`$and must contain at least one clause`);let n=s.map(n=>U(e,t,n,r+1)).filter(e=>e!==void 0);n.length>0&&i.push(a(...n));continue}if(o===`$or`){if(!Array.isArray(s))throw new V(`$or must be an array`);if(s.length===0)throw new V(`$or must contain at least one clause`);let n=s.map(n=>U(e,t,n,r+1)).filter(e=>e!==void 0);n.length>0&&i.push(oe(...n));continue}if(o===`$not`){if(!W(s))throw new V(`$not must contain a non-empty clause`);let n=U(e,t,s,r+1);n&&i.push(ie(n));continue}if(z.has(o))continue;let c=he(me(e,t,o),s);c&&i.push(c)}if(i.length!==0)return i.length===1?i[0]:a(...i)}function W(e){return typeof e!=`object`||!e||Array.isArray(e)?!1:Object.keys(e).length>0}const G=/^[a-zA-Z_][a-zA-Z0-9_]*$/,K=y(`pg_class`,{relname:w(`relname`,{length:63}).notNull(),reltuples:de(`reltuples`).notNull()}),ge=100,q=1e3,J=1e3;var Y=class extends Error{constructor(e){super(`TableClient: ${e}`),this.name=`TableClientError`}},X=class e{constructor(e,t,n,r=[]){this.table=e,this.columnKinds=t,this.db=n,this.primaryKeyColumns=r}get _selectTable(){return this.table}getColumnOrThrow(e,t){let n=c(this.table)[e];if(!n)throw new Y(`${t}: unknown column "${e}"`);return n}buildOrderBy(e,t){return e.map(e=>{let n=this.getColumnOrThrow(e.column,`${t}: orderBy`);return e.dir===`desc`?h`${n} DESC`:h`${n} ASC`})}requireNonEmptyWhere(e,t){if(!W(e))throw new Y(`${t} requires a non-empty where clause`)}validateColumnKeys(e,t){if(e.length===0)return;let n=c(this.table);for(let r of e)if(!Object.hasOwn(n,r))throw new Y(`${t}: unknown column "${r}"`)}async findOne(e){this.requireNonEmptyWhere(e,`findOne`);let t=this.buildWhereOrThrow(e);return(await this.db.select().from(this._selectTable).where(t).limit(1))[0]??null}async findMany(e={}){let t=this.validateLimit(e.limit);if(e.offset!==void 0&&(!Number.isInteger(e.offset)||e.offset<0))throw new Y(`offset must be a non-negative integer, got ${e.offset}`);let n=e.orderBy&&e.orderBy.length>0?this.buildOrderBy(e.orderBy,`findMany`):void 0,r=e.where?this.buildWhereOrThrow(e.where):void 0,i=this.db.select().from(this._selectTable).$dynamic();return r&&(i=i.where(r)),n&&(i=i.orderBy(...n)),i=i.limit(t),e.offset!==void 0&&(i=i.offset(e.offset)),await i}async count(e){let t=e?this.buildWhereOrThrow(e):void 0,n=this.db.select({value:o()}).from(this._selectTable).$dynamic();t&&(n=n.where(t));let r=await n;return Number(r[0]?.value??0)}async exists(e){this.requireNonEmptyWhere(e,`exists`);let t=this.buildWhereOrThrow(e);return(await this.db.select({one:h`1`}).from(this._selectTable).where(t).limit(1)).length>0}async distinct(e,t){let n=this.getColumnOrThrow(e,`distinct`),r=[];t?.includeNull||r.push(p(n)),t?.where&&r.push(this.buildWhereOrThrow(t.where));let i=this.db.selectDistinct({value:n}).from(this._selectTable).$dynamic();if(r.length===1?i=i.where(r[0]):r.length>1&&(i=i.where(a(...r))),t?.orderBy){let e=t.orderBy===`desc`?h`${n} DESC`:h`${n} ASC`;i=i.orderBy(e)}return(await i).map(e=>e.value)}async insert(e){return(await this.db.insert(this.table).values(e).returning())[0]}async upsert(e,t){let n=Array.isArray(t.target)?t.target:[t.target];if(n.length===0)throw new Y(`upsert requires at least one target column`);let r=n.map(e=>this.getColumnOrThrow(e,`upsert target`)),i=t.set??e;t.set&&this.validateColumnKeys(Object.keys(t.set),`upsert set`);let a=t.setWhere&&W(t.setWhere)?this.buildWhereOrThrow(t.setWhere):void 0,o=await this.db.insert(this.table).values(e).onConflictDoUpdate({target:r,set:i,...a?{setWhere:a}:{}}).returning();if(o.length>0)return o[0];let s={};for(let t of n)s[t]=e[t];let c=await this.findOne(s);if(!c)throw new Y(`upsert with setWhere: conflicting row was deleted concurrently — retry the operation`);return c}async tryClaim(e,t){let n=Array.isArray(t.target)?t.target:[t.target];if(n.length===0)throw new Y(`tryClaim requires at least one target column`);let r=n.map(e=>this.getColumnOrThrow(e,`tryClaim target`)),i=t.set??e;if(t.set&&this.validateColumnKeys(Object.keys(t.set),`tryClaim set`),!W(t.replaceWhen))throw new Y(`tryClaim requires a non-empty replaceWhen clause`);let a=this.buildWhereOrThrow(t.replaceWhen),o=await this.db.insert(this.table).values(e).onConflictDoUpdate({target:r,set:i,setWhere:a}).returning();if(o.length>0)return{acquired:!0,row:o[0]};let s={};for(let t of n)s[t]=e[t];let c=await this.findOne(s);if(!c)throw new Y(`tryClaim: conflicting row was deleted concurrently — retry the operation`);return{acquired:!1,row:c}}async insertMany(e){if(!Array.isArray(e))throw new Y(`insertMany requires an array of rows`);if(e.length===0)return[];if(e.length>1e3)throw new Y(`insertMany batch too large: ${e.length} > ${J}`);return await this.db.insert(this.table).values(e).returning()}async update(e,t){this.requireNonEmptyWhere(e,`update`),this.validateColumnKeys(Object.keys(t),`update`);let n=this.buildWhereOrThrow(e),r=await this.db.update(this.table).set(t).where(n).returning();if(r.length>1)throw new Y(`update matched ${r.length} rows but is expected to match at most one — use updateMany for bulk updates. WARNING: the update has already been applied; wrap in a transaction if you need rollback.`);return r[0]??null}async updateMany(e,t){this.requireNonEmptyWhere(e,`updateMany`),this.validateColumnKeys(Object.keys(t),`updateMany`);let n=this.buildWhereOrThrow(e);return await this.db.update(this.table).set(t).where(n).returning()}async delete(e){this.requireNonEmptyWhere(e,`delete`);let t=this.buildWhereOrThrow(e),n=await this.db.delete(this.table).where(t).returning();if(n.length>1)throw new Y(`delete matched ${n.length} rows but is expected to match at most one — use deleteMany for bulk deletes. WARNING: the delete has already been applied; wrap in a transaction if you need rollback.`);return n[0]??null}async deleteMany(e){this.requireNonEmptyWhere(e,`deleteMany`);let t=this.buildWhereOrThrow(e);return await this.db.delete(this.table).where(t).returning()}async transaction(t){return await this.db.transaction(async n=>await t(new e(this.table,this.columnKinds,n,this.primaryKeyColumns)))}async claim(e){if(this.primaryKeyColumns.length===0)throw new Y(`claim() requires the table to have a primary key — declare primaryKey in defineTable() or use column.uuid({ primaryKey: true })`);if(this.primaryKeyColumns.length>1)throw new Y(`claim() does not support composite primary keys — use a single-column primary key or call transaction() with FOR UPDATE directly`);if(this.requireNonEmptyWhere(e.where,`claim`),!Number.isInteger(e.limit)||e.limit<1)throw new Y(`claim limit must be a positive integer, got ${e.limit}`);if(e.limit>1e3)throw new Y(`claim limit ${e.limit} exceeds MAX_LIMIT ${q}`);let t=Object.keys(e.set??{}),n=Object.keys(e.setSql??{});if(t.length===0&&n.length===0)throw new Y(`claim requires at least one field in set or setSql`);this.validateColumnKeys(t,`claim set`),this.validateColumnKeys(n,`claim setSql`);let r=this.buildWhereOrThrow(e.where),i=e.orderBy&&e.orderBy.length>0?this.buildOrderBy(e.orderBy,`claim`):void 0,a=this.primaryKeyColumns[0],o=this.getColumnOrThrow(a,`claim primary key`);return await this.db.transaction(async t=>{let n=t.select({_pk:o}).from(this._selectTable).where(r).limit(e.limit).for(`update`,{skipLocked:!0}).$dynamic();i&&(n=n.orderBy(...i));let a=await n;if(a.length===0)return[];let s=a.map(e=>e._pk),c={...e.set??{}};if(e.setSql)for(let[t,n]of Object.entries(e.setSql))c[t]=n;return await t.update(this.table).set(c).where(f(o,s)).returning()})}async aggregate(e){let t=Object.entries(e.select);if(t.length===0)throw new Y(`aggregate requires at least one select field`);for(let[e]of t)if(!G.test(e))throw new Y(`aggregate: invalid output alias "${e}" — must match ${G.source}`);let n={};if(e.groupBy)for(let t of e.groupBy)n[t]=this.getColumnOrThrow(t,`aggregate groupBy`);for(let[e,r]of t)n[e]=this.buildAggregateExpr(r);let r=this.db.select(n).from(this._selectTable).$dynamic();if(e.where&&(r=r.where(this.buildWhereOrThrow(e.where))),e.groupBy&&e.groupBy.length>0){let t=e.groupBy.map(e=>this.getColumnOrThrow(e,`aggregate groupBy`));r=r.groupBy(...t)}return e.orderBy&&e.orderBy.length>0&&(r=r.orderBy(...this.buildOrderBy(e.orderBy,`aggregate`))),e.limit!==void 0&&(r=r.limit(this.validateLimit(e.limit))),await r}async countEstimate(){let e=l(this.table),[t]=await this.db.select({estimate:h`greatest(${K.reltuples}::bigint, 0)`}).from(K).where(s(K.relname,e)).limit(1);return t?Number(t.estimate):0}buildAggregateExpr(e){if(`column`in e&&e.column){let t=this.getColumnOrThrow(e.column,`aggregate`);switch(e.fn){case`count`:return h`count(${t})::int`;case`sum`:return h`sum(${t})`;case`avg`:return h`avg(${t})`;case`min`:return h`min(${t})`;case`max`:return h`max(${t})`;default:throw new Y(`aggregate: unknown function "${e.fn}"`)}}if(e.fn===`count`)return h`count(*)::int`;throw new Y(`aggregate: ${e.fn} requires a column`)}buildWhereOrThrow(e){try{let t=U(this.table,this.columnKinds,e);if(!t)throw new Y(`where clause produced no SQL predicate`);return t}catch(e){throw e instanceof V?new Y(e.message.replace(/^WhereBuilder: /,``)):e}}validateLimit(e){if(e===void 0)return 100;if(!Number.isInteger(e)||e<1)throw new Y(`limit must be a positive integer, got ${e}`);if(e>1e3)throw new Y(`limit ${e} exceeds MAX_LIMIT ${q} — paginate or use claim() for batch processing`);return e}};function Z(e,t){let n=e;return Object.defineProperty(n,`__kind`,{value:t.kind,enumerable:!1}),Object.defineProperty(n,`__notNull`,{value:t.notNull,enumerable:!1}),Object.defineProperty(n,`__hasDefault`,{value:t.hasDefault,enumerable:!1}),Object.defineProperty(n,`__type`,{value:void 0,enumerable:!1}),t.pgName&&Object.defineProperty(n,`__pgName`,{value:t.pgName,enumerable:!1}),t.primaryKey&&Object.defineProperty(n,`__primaryKey`,{value:!0,enumerable:!1}),n}const _e={uuid(e){let t=e?.notNull??!1,n=e?.defaultRandom??!1,r=e?.primaryKey??!1;return Z(n=>{let i=C(n);return r&&(i=i.primaryKey()),e?.defaultRandom&&(i=i.defaultRandom()),t&&!r&&(i=i.notNull()),i},{kind:`uuid`,notNull:t||r,hasDefault:n||r,pgName:e?.pgName,primaryKey:r||void 0})},varchar(e){if(!Number.isInteger(e.length)||e.length<1||e.length>1073741823)throw Error(`column.varchar: length must be an integer between 1 and 1073741823, got ${e.length}`);let t=e.notNull??!1;return Z(n=>{let r=w(n,{length:e.length});return t&&(r=r.notNull()),e.default!==void 0&&(r=r.default(e.default)),r},{kind:`varchar`,notNull:t,hasDefault:e.default!==void 0,pgName:e.pgName})},text(e){let t=e?.notNull??!1;return Z(n=>{let r=b(n);return t&&(r=r.notNull()),e?.default!==void 0&&(r=r.default(e.default)),r},{kind:`text`,notNull:t,hasDefault:e?.default!==void 0,pgName:e?.pgName})},integer(e){let t=e?.notNull??!1;return Z(n=>{let r=_(n);return t&&(r=r.notNull()),e?.default!==void 0&&(r=r.default(e.default)),r},{kind:`integer`,notNull:t,hasDefault:e?.default!==void 0,pgName:e?.pgName})},bigint(e){let t=e?.mode??`number`,n=e?.notNull??!1;return Z(r=>{let i=se(r,{mode:t});return n&&(i=i.notNull()),e?.default!==void 0&&(i=i.default(e.default)),i},{kind:`bigint`,notNull:n,hasDefault:e?.default!==void 0,pgName:e?.pgName})},double(e){let t=e?.notNull??!1;return Z(n=>{let r=g(n);return t&&(r=r.notNull()),e?.default!==void 0&&(r=r.default(e.default)),r},{kind:`double`,notNull:t,hasDefault:e?.default!==void 0,pgName:e?.pgName})},boolean(e){let t=e?.notNull??!1;return Z(n=>{let r=ce(n);return t&&(r=r.notNull()),e?.default!==void 0&&(r=r.default(e.default)),r},{kind:`boolean`,notNull:t,hasDefault:e?.default!==void 0,pgName:e?.pgName})},timestamp(e){let t=e?.notNull??!1,n=e?.defaultNow??!1,r=e?.withTimezone??!0;return Z(n=>{let i=x(n,{withTimezone:r});return t&&(i=i.notNull()),e?.defaultNow&&(i=i.defaultNow()),i},{kind:`timestamp`,notNull:t,hasDefault:n,pgName:e?.pgName})},jsonb(e){let t=e?.notNull??!1;return Z(n=>{let r=v(n);return t&&(r=r.notNull()),e?.default!==void 0&&(r=r.default(e.default)),r},{kind:`jsonb`,notNull:t,hasDefault:e?.default!==void 0,pgName:e?.pgName})},uuidArray(e){let t=e?.notNull??!1;return Z(e=>{let n=C(e).array();return t?n.notNull():n},{kind:`uuidArray`,notNull:t,hasDefault:!1,pgName:e?.pgName})}},Q=new class{byName=new Map;register(e,t,n){let r=this.byName.get(e);if(r){if(r.table===t||r.source&&n&&r.source===n)return;throw Error(`tableRegistry: a different table is already registered under name "${e}". Did you call defineTable twice? First registered from ${r.source??`<unknown>`}, now from ${n??`<unknown>`}.`)}this.byName.set(e,{name:e,table:t,source:n})}get(e){return this.byName.get(e)?.table}has(e){return this.byName.has(e)}allTables(){let e={};for(let t of this.byName.values())e[t.name]=t.table;return e}clear(){if(process.env.NODE_ENV===`production`)throw Error(`tableRegistry.clear() is not allowed when NODE_ENV=production — this is a test-only utility. If you reached this from production code, you have a bug.`);this.byName.clear()}},ve=/^[a-z][a-z0-9_]*$/;function $(e,t,n){if(e.length>63)throw Error(`defineTable: ${t} "${e}" is ${e.length} characters — Postgres truncates identifiers at 63 chars silently, which can cause name collisions${n?`. ${n}`:``}`)}function ye(e){if(!ve.test(e.name))throw Error(`defineTable: invalid table name "${e.name}" — must match /^[a-z][a-z0-9_]*$/`);$(e.name,`table name`);let t={},n={};for(let[r,i]of Object.entries(e.columns)){if(typeof i!=`function`)throw Error(`defineTable: column "${r}" is not a column factory — did you call e.g. column.uuid() with parens?`);$(r,`column name in table "${e.name}"`);let a=i.__pgName;a&&$(a,`pgName for column "${r}" in table "${e.name}"`),t[r]=i(a??r),n[r]=i.__kind}let r=new Set(Object.keys(t));if(e.primaryKey!==void 0){let t=Array.isArray(e.primaryKey)?e.primaryKey:[e.primaryKey];for(let e of t)if(!r.has(String(e)))throw Error(`defineTable: primaryKey references unknown column "${String(e)}"`)}if(e.unique)for(let t of e.unique){for(let e of t.on)if(!r.has(String(e)))throw Error(`defineTable: unique constraint references unknown column "${String(e)}"`);$(t.name??`uq_${e.name}_${t.on.map(String).join(`_`)}`,t.name?`unique constraint name`:`auto-generated unique constraint name`,t.name?void 0:"Supply a shorter explicit `name` to the unique definition.")}if(e.indexes)for(let t of e.indexes){for(let e of t.on)if(!r.has(String(e)))throw Error(`defineTable: index references unknown column "${String(e)}"`);$(t.name??`idx_${e.name}_${t.on.map(String).join(`_`)}`,t.name?`index name`:`auto-generated index name`,t.name?void 0:"Supply a shorter explicit `name` to the index definition.")}let i=e.primaryKey!==void 0||e.unique&&e.unique.length>0||e.indexes&&e.indexes.length>0;function a(e,t,n){let r=e[t];if(!r)throw Error(`defineTable: ${n} references unknown column "${t}"`);return r}let o=i?y(e.name,t,t=>{let n=t,r=[];if(e.primaryKey!==void 0){let t=Array.isArray(e.primaryKey)?e.primaryKey:[e.primaryKey],i=a(n,String(t[0]),`primaryKey`),o=t.slice(1).map(e=>a(n,String(e),`primaryKey`));r.push(ue({columns:[i,...o]}))}if(e.unique)for(let t of e.unique){let e=a(n,String(t.on[0]),`unique constraint`),i=t.on.slice(1).map(e=>a(n,String(e),`unique constraint`));r.push(S().on(e,...i))}if(e.indexes)for(let t of e.indexes){let i=a(n,String(t.on[0]),`index`),o=t.on.slice(1).map(e=>a(n,String(e),`index`));r.push(le(t.name??`idx_${e.name}_${t.on.map(String).join(`_`)}`).on(i,...o))}return r}):y(e.name,t),s=be();Q.register(e.name,o,s);let c=Object.freeze({...n}),l;if(e.primaryKey!==void 0)l=Array.isArray(e.primaryKey)?e.primaryKey.map(String):[String(e.primaryKey)];else{l=[];for(let[t,n]of Object.entries(e.columns))n.__primaryKey&&l.push(t)}let u=Object.freeze([...l]);return{table:o,schema:e,columnKinds:c,primaryKeyColumns:u,makeClient:e=>new X(o,c,e,u)}}function be(){let e=Error().stack;if(!e)return;let t=e.split(`
19
19
  `).slice(1);for(let e of t){if(e.includes(`/table/define.`)||e.includes(`/table/registry.`))continue;let t=e.match(/\(([^)]+)\)/)??e.match(/at (.+)$/);if(t)return t[1].trim()}}export{ge as DEFAULT_LIMIT,J as MAX_BATCH,q as MAX_LIMIT,X as TableClient,Y as TableClientError,V as WhereBuilderError,_e as column,k as createDbClient,A as createReadOnlyClient,ye as defineTable,P as getMigrationStatus,fe as getQueryStats,W as isNonEmptyWhere,D as isQueryCounterEnabled,E as queryCountingLogger,I as rollbackMigrations,F as runMigrations,pe as runWithQueryStats,L as schemaRegistry,Q as tableRegistry};
20
20
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["countFn","andFn","drizzlePrimaryKey","drizzleUnique","drizzleIndex"],"sources":["../src/query-counter.ts","../src/client.ts","../src/migrate.ts","../src/schema-registry.ts","../src/table/where-builder.ts","../src/table/client.ts","../src/table/columns.ts","../src/table/registry.ts","../src/table/define.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\nimport type { Logger } from 'drizzle-orm/logger'\n\n/**\n * Per-request DB query counter — opt-in, off by default.\n *\n * Wired into the Drizzle client when `LUMI_DB_QUERY_COUNTER=1`. The harness\n * (apps/perf-harness) wraps each HTTP request in `runWithQueryStats` and emits\n * the count as an `x-db-query-count` response header, so k6 scenarios can\n * assert per-endpoint query budgets like:\n *\n * checks: { 'findById ≤ 2 queries': (r) => r.headers['X-Db-Query-Count'] <= '2' }\n *\n * No-op for any code path that does not enter `runWithQueryStats` — the ALS\n * store is undefined and `logQuery` returns immediately.\n */\n\nexport interface QueryStats {\n count: number\n}\n\nconst queryStatsAls = new AsyncLocalStorage<QueryStats>()\n\nexport function getQueryStats(): QueryStats | undefined {\n return queryStatsAls.getStore()\n}\n\nexport function runWithQueryStats<T>(fn: (stats: QueryStats) => Promise<T>): Promise<T> {\n const stats: QueryStats = { count: 0 }\n return queryStatsAls.run(stats, () => fn(stats))\n}\n\nclass QueryCountingLogger implements Logger {\n logQuery(_query: string, _params: unknown[]): void {\n const stats = queryStatsAls.getStore()\n if (stats) stats.count++\n }\n}\n\nexport const queryCountingLogger: Logger = new QueryCountingLogger()\n\nexport function isQueryCounterEnabled(): boolean {\n return process.env.LUMI_DB_QUERY_COUNTER === '1'\n}\n","import type { Logger } from 'drizzle-orm/logger'\nimport { drizzle, type PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport postgres from 'postgres'\nimport { isQueryCounterEnabled, queryCountingLogger } from './query-counter.js'\n\nexport interface DbConfig {\n url: string\n readOnlyUrl?: string\n poolMin?: number\n poolMax?: number\n}\n\nfunction drizzleConfig(): { logger: Logger } | undefined {\n return isQueryCounterEnabled() ? { logger: queryCountingLogger } : undefined\n}\n\n/**\n * Create a full read-write database client.\n */\nexport function createDbClient(config: DbConfig): PostgresJsDatabase {\n const sql = postgres(config.url, {\n max: config.poolMax || 10,\n idle_timeout: 20,\n max_lifetime: 60 * 30,\n })\n\n const cfg = drizzleConfig()\n return cfg ? drizzle(sql, cfg) : drizzle(sql)\n}\n\n/**\n * Create a read-only database client.\n *\n * Sets `default_transaction_read_only = on` as a startup parameter on every\n * connection in the pool, so any implicit transaction issued through this\n * client is read-only at the PostgreSQL level. This is the defense-in-depth\n * layer beneath the application's own routing of writes through `AdminClient`\n * — even if a write somehow reaches a read-only client, Postgres rejects it.\n *\n * Implementation note: postgres-js's `connection` config object is sent in\n * the PostgreSQL startup packet for every new pool connection, so the GUC is\n * applied uniformly across the whole pool — not just whichever connection\n * happened to run a one-off `SET` statement.\n */\nexport function createReadOnlyClient(config: DbConfig): PostgresJsDatabase {\n const url = config.readOnlyUrl || config.url\n\n const sql = postgres(url, {\n max: config.poolMax || 10,\n idle_timeout: 20,\n max_lifetime: 60 * 30,\n connection: {\n application_name: 'toolkit_readonly',\n default_transaction_read_only: true,\n },\n onnotice: () => {}, // Suppress NOTICE messages\n })\n\n const cfg = drizzleConfig()\n return cfg ? drizzle(sql, cfg) : drizzle(sql)\n}\n","import { readdir } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { sql } from 'drizzle-orm'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\n\n/**\n * Read a directory's `.ts` filenames, returning `[]` if the directory does\n * not exist. Single round-trip — avoids the `existsSync` + `readdir` race.\n */\nasync function readTsFilesOrEmpty(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir)\n return entries.filter((name) => name.endsWith('.ts'))\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return []\n throw err\n }\n}\n\n/**\n * Arguments passed to migration `up` / `down` functions.\n *\n * Intentionally tiny: we hand over a read-write Drizzle handle bound to\n * the current transaction. If a migration needs anything beyond raw SQL\n * (e.g. calling an AdminClient for a data backfill), it can import the\n * relevant toolkit helpers inside the migration file — the CLI loader\n * runs the migration in the same process as the config, so all package\n * imports resolve normally.\n */\nexport interface MigrateArgs {\n db: PostgresJsDatabase\n}\n\n/**\n * Shape of a migration module loaded from a `.ts` file.\n *\n * At minimum a migration must export `up`. `down` is optional — when\n * absent, `lumi migrate:rollback` refuses to roll that file back.\n */\nexport interface MigrationModule {\n up: (args: MigrateArgs) => Promise<void>\n down?: (args: MigrateArgs) => Promise<void>\n}\n\nexport interface MigrationSource {\n /** Absolute path to the migration file on disk. */\n path: string\n /** `project` for user migrations, `toolkit` for shipped-with-toolkit ones. */\n namespace: 'toolkit' | 'project'\n /** Filename only, used for ordering + tracking. */\n name: string\n}\n\nexport interface MigrationStatus {\n applied: MigrationSource[]\n pending: MigrationSource[]\n}\n\n/**\n * Loader callback — imports a migration `.ts` file and returns its exports.\n *\n * The CLI owns `jiti` (already used to load `lumi.config.ts`), so\n * `@murumets-ee/db` stays free of TS-runtime loader dependencies.\n * Callers who never apply migrations (read-only servers, tests) never\n * pay the loader cost.\n */\nexport type MigrationLoader = (absolutePath: string) => Promise<MigrationModule>\n\n/** Ensure the `_toolkit_migrations` tracking table exists. */\nasync function ensureMigrationsTable(db: PostgresJsDatabase): Promise<void> {\n await db.execute(sql`\n CREATE TABLE IF NOT EXISTS _toolkit_migrations (\n id SERIAL PRIMARY KEY,\n namespace VARCHAR(50) NOT NULL,\n name VARCHAR(255) NOT NULL,\n applied_at TIMESTAMP NOT NULL DEFAULT NOW(),\n UNIQUE(namespace, name)\n )\n `)\n}\n\n/**\n * Discover all migration files under `projectRoot/migrations`.\n *\n * Layout:\n * migrations/\n * 20260411_180000_add_articles.ts ← user migrations (sort chronologically)\n * 20260411_180000_add_articles.snapshot.json\n * .toolkit/\n * 0001_initial_schema.ts ← toolkit-owned migrations (sort numerically)\n *\n * Module-private — callers reach migrations via {@link getMigrationStatus}\n * (returns `applied` + `pending`) or {@link runMigrations} / {@link rollbackMigrations}.\n */\nasync function discoverMigrations(projectRoot: string): Promise<MigrationSource[]> {\n const migrationsDir = join(projectRoot, 'migrations')\n const toolkitDir = join(migrationsDir, '.toolkit')\n\n const [toolkitFiles, projectEntries] = await Promise.all([\n readTsFilesOrEmpty(toolkitDir),\n readTsFilesOrEmpty(migrationsDir),\n ])\n\n const toolkit: MigrationSource[] = toolkitFiles\n .map((name) => ({ path: join(toolkitDir, name), namespace: 'toolkit' as const, name }))\n .sort((a, b) => a.name.localeCompare(b.name))\n\n const project: MigrationSource[] = projectEntries\n // The `.toolkit` directory itself does not end in `.ts`, so it is filtered\n // out by `readTsFilesOrEmpty`. Filtering here is just defence-in-depth.\n .filter((name) => name !== '.toolkit')\n .map((name) => ({ path: join(migrationsDir, name), namespace: 'project' as const, name }))\n .sort((a, b) => a.name.localeCompare(b.name))\n\n // Stable ordering: toolkit first (numeric prefix), then project (timestamp prefix).\n return [...toolkit, ...project]\n}\n\n/** Split discovered migrations into applied vs pending by consulting the tracking table. */\nexport async function getMigrationStatus(\n db: PostgresJsDatabase,\n projectRoot: string,\n): Promise<MigrationStatus> {\n await ensureMigrationsTable(db)\n\n const all = await discoverMigrations(projectRoot)\n\n const appliedRows = await db.execute<{ namespace: string; name: string }>(sql`\n SELECT namespace, name FROM _toolkit_migrations\n ORDER BY applied_at ASC\n `)\n const appliedSet = new Set(appliedRows.map((row) => `${row.namespace}:${row.name}`))\n\n const applied: MigrationSource[] = []\n const pending: MigrationSource[] = []\n for (const m of all) {\n if (appliedSet.has(`${m.namespace}:${m.name}`)) applied.push(m)\n else pending.push(m)\n }\n return { applied, pending }\n}\n\n/**\n * Apply every pending migration in order.\n *\n * Each file runs inside its own transaction: the migration's `up()`\n * executes first, then the `_toolkit_migrations` insert commits. If\n * `up()` throws, the transaction rolls back and the whole command\n * aborts — nothing gets half-applied.\n *\n * The `loader` callback is injected so `@murumets-ee/db` itself doesn't\n * depend on a TS-runtime loader. Callers pass a jiti-backed loader from\n * the CLI or their own equivalent.\n */\nexport async function runMigrations(\n db: PostgresJsDatabase,\n projectRoot: string,\n loader: MigrationLoader,\n): Promise<void> {\n await ensureMigrationsTable(db)\n\n const { pending } = await getMigrationStatus(db, projectRoot)\n\n if (pending.length === 0) {\n console.log('No pending migrations')\n return\n }\n\n console.log(`Running ${pending.length} pending migration${pending.length === 1 ? '' : 's'}...`)\n\n for (const migration of pending) {\n console.log(` Applying ${migration.namespace}/${migration.name}...`)\n\n const mod = await loader(migration.path)\n if (typeof mod.up !== 'function') {\n throw new Error(\n `Migration ${migration.namespace}/${migration.name} has no \\`up\\` export. ` +\n 'Every migration file must export an async `up({ db })` function.',\n )\n }\n\n try {\n await db.transaction(async (tx) => {\n await mod.up({ db: tx as PostgresJsDatabase })\n await tx.execute(sql`\n INSERT INTO _toolkit_migrations (namespace, name)\n VALUES (${migration.namespace}, ${migration.name})\n `)\n })\n console.log(` ✓ Applied ${migration.namespace}/${migration.name}`)\n } catch (error) {\n console.error(` ✗ Failed to apply ${migration.namespace}/${migration.name}:`, error)\n throw error\n }\n }\n\n console.log(`Successfully applied ${pending.length} migration${pending.length === 1 ? '' : 's'}`)\n}\n\n/**\n * Roll back the most recent applied migration (or N most recent).\n *\n * Each file's `down()` runs in its own transaction. Files without a\n * `down` export cause the rollback to abort before running anything —\n * we never roll back some but not all of a requested batch.\n */\nexport async function rollbackMigrations(\n db: PostgresJsDatabase,\n projectRoot: string,\n loader: MigrationLoader,\n count = 1,\n): Promise<void> {\n await ensureMigrationsTable(db)\n const { applied } = await getMigrationStatus(db, projectRoot)\n\n if (applied.length === 0) {\n console.log('No applied migrations to roll back')\n return\n }\n\n // Take the last `count` applied, in reverse application order.\n const toRollback = applied.slice(-count).reverse()\n\n // Load all requested modules first and verify every one has `down`.\n const modules = await Promise.all(\n toRollback.map(async (m) => ({ meta: m, mod: await loader(m.path) })),\n )\n for (const { meta, mod } of modules) {\n if (typeof mod.down !== 'function') {\n throw new Error(\n `Migration ${meta.namespace}/${meta.name} has no \\`down\\` export — cannot roll back. ` +\n 'Add an async `down({ db })` function to the migration file.',\n )\n }\n }\n\n for (const { meta, mod } of modules) {\n console.log(` Rolling back ${meta.namespace}/${meta.name}...`)\n try {\n await db.transaction(async (tx) => {\n // biome-ignore lint/style/noNonNullAssertion: guarded above\n await mod.down!({ db: tx as PostgresJsDatabase })\n await tx.execute(sql`\n DELETE FROM _toolkit_migrations\n WHERE namespace = ${meta.namespace} AND name = ${meta.name}\n `)\n })\n console.log(` ✓ Rolled back ${meta.namespace}/${meta.name}`)\n } catch (error) {\n console.error(` ✗ Failed to roll back ${meta.namespace}/${meta.name}:`, error)\n throw error\n }\n }\n\n console.log(\n `Successfully rolled back ${modules.length} migration${modules.length === 1 ? '' : 's'}`,\n )\n}\n","import type { PgTableWithColumns } from 'drizzle-orm/pg-core'\n\n/**\n * Schema registry for storing Drizzle schemas\n * This will be populated by @org/entity when schemas are generated from entity definitions\n *\n * We use PgTableWithColumns<any> because entity table schemas are dynamic (columns\n * not known at compile time) and we need runtime property access (.id, .status, etc.).\n * AnyPgTable won't work here — it doesn't expose column accessors.\n */\n\n// biome-ignore lint/suspicious/noExplicitAny: dynamic table columns require PgTableWithColumns<any> for property access\ntype AnyTable = PgTableWithColumns<any>\n\nexport interface SchemaRegistry {\n register(name: string, schema: AnyTable): void\n get(name: string): AnyTable | undefined\n all(): Record<string, AnyTable>\n has(name: string): boolean\n}\n\nclass SchemaRegistryImpl implements SchemaRegistry {\n private schemas: Map<string, AnyTable> = new Map()\n\n register(name: string, schema: AnyTable): void {\n this.schemas.set(name, schema)\n }\n\n get(name: string): AnyTable | undefined {\n return this.schemas.get(name)\n }\n\n all(): Record<string, AnyTable> {\n const result: Record<string, AnyTable> = {}\n for (const [name, schema] of this.schemas.entries()) {\n result[name] = schema\n }\n return result\n }\n\n has(name: string): boolean {\n return this.schemas.has(name)\n }\n}\n\n/**\n * Global schema registry instance\n * Entities will register their schemas here when they are defined\n */\nexport const schemaRegistry = new SchemaRegistryImpl()\n","/**\n * WhereClause → Drizzle SQL translator.\n *\n * This module is the *only* place in the table layer that touches Drizzle's\n * query operators. All `findOne`/`findMany`/`update`/`delete`/`count` calls\n * funnel their where-objects through `buildWhere()` here. That makes this\n * file the single security-review surface for query construction.\n *\n * Safety properties:\n *\n * 1. **Values are always parameterized.** Every operator passes its value\n * through Drizzle's typed operators (`eq`, `gt`, etc.) which use\n * parameter binding. Raw `sql` template literals are NEVER constructed\n * from caller-supplied values.\n * 2. **Column identifiers are looked up by key.** The `where` object's keys\n * are matched against the table's known column set; unknown keys throw.\n * No dynamic identifier interpolation.\n * 3. **jsonb path segments are whitelist-validated.** Each segment must\n * match `/^[a-zA-Z_][a-zA-Z0-9_-]*$|^\\d+$/`. Anything else throws.\n * 4. **`ilike`/`startsWith` values are escaped.** `%` and `_` in caller-\n * supplied strings are backslash-escaped before wrapping with wildcards\n * so callers cannot accidentally inject pattern characters.\n *\n * @internal\n */\n\nimport {\n and,\n eq,\n getTableColumns,\n gt,\n gte,\n ilike,\n inArray,\n isNotNull,\n isNull,\n lt,\n lte,\n ne,\n not,\n notInArray,\n or,\n type SQL,\n type SQLWrapper,\n sql,\n} from 'drizzle-orm'\nimport type { AnyPgColumn, PgTable } from 'drizzle-orm/pg-core'\nimport type { ColumnFactory, ColumnKind, WhereClause } from './types.js'\n\n/** Identifier shape allowed for jsonb path segments. */\nconst JSONB_SEGMENT_RE = /^[a-zA-Z_][a-zA-Z0-9_-]*$|^\\d+$/\n\n/** Reserved meta-keys in a where-clause object. */\nconst META_KEYS = new Set(['$and', '$or', '$not'])\n\n/**\n * Maximum nesting depth for `$and` / `$or` / `$not` recursion.\n *\n * Real production where-clauses are 2-3 levels deep at most. The cap\n * exists to prevent stack-overflow DoS attacks if anyone ever passes\n * untrusted user input through the where-builder (which the design\n * docs forbid, but warnings are not code).\n *\n * Bumping this is fine if a real consumer needs it — but think hard\n * about why a query needs more than 16 levels of nesting first.\n */\nconst MAX_WHERE_DEPTH = 16\n\n/** Operator keys recognised in a column-operator object. */\nconst OP_KEYS = new Set([\n 'eq',\n 'ne',\n 'in',\n 'notIn',\n 'gt',\n 'gte',\n 'lt',\n 'lte',\n 'isNull',\n 'isNotNull',\n 'ilike',\n 'startsWith',\n])\n\n/**\n * A custom error thrown by the where-builder for any caller-side mistake.\n *\n * Catching `WhereBuilderError` lets the table client surface a 400-style\n * error to its caller without conflating it with database errors.\n */\nexport class WhereBuilderError extends Error {\n constructor(message: string) {\n super(`WhereBuilder: ${message}`)\n this.name = 'WhereBuilderError'\n }\n}\n\n/**\n * Escape `%` and `_` (and the escape char `\\`) in a caller-supplied LIKE\n * pattern value. The result is safe to wrap with our own wildcards without\n * leaking pattern syntax to the caller.\n */\nfunction escapeLikeValue(value: string): string {\n return value.replace(/[\\\\%_]/g, '\\\\$&')\n}\n\n/**\n * Resolve a where-clause key to a Drizzle column reference or a jsonb path\n * SQL expression.\n *\n * - Plain column key: `'userId'` → `table.userId` (typed Drizzle column)\n * - jsonb dotted path: `'metadata.author.name'` → `metadata->'author'->>'name'`\n *\n * The parent column for any dotted path MUST be of kind `jsonb`. The\n * column kind is read from the column factory metadata stored at table\n * definition time.\n */\nfunction resolveTarget(\n table: PgTable,\n columnKinds: Readonly<Record<string, ColumnKind>>,\n key: string,\n): SQLWrapper {\n const cols = getTableColumns(table)\n if (!key.includes('.')) {\n const col = cols[key]\n if (!col) {\n throw new WhereBuilderError(`unknown column \"${key}\"`)\n }\n return col\n }\n\n const segments = key.split('.')\n const [columnName, ...path] = segments\n if (!columnName || path.length === 0) {\n throw new WhereBuilderError(`malformed dotted key \"${key}\"`)\n }\n const kind = columnKinds[columnName]\n if (!kind) {\n throw new WhereBuilderError(`unknown column \"${columnName}\" in dotted key \"${key}\"`)\n }\n if (kind !== 'jsonb') {\n throw new WhereBuilderError(\n `dotted-path access is only supported on jsonb columns, but \"${columnName}\" is ${kind}`,\n )\n }\n for (const seg of path) {\n if (!JSONB_SEGMENT_RE.test(seg)) {\n throw new WhereBuilderError(\n `invalid jsonb path segment \"${seg}\" — must match [a-zA-Z_][a-zA-Z0-9_-]* or be a non-negative integer`,\n )\n }\n }\n\n const col = cols[columnName]\n if (!col) {\n throw new WhereBuilderError(`unknown column \"${columnName}\"`)\n }\n\n // Build: col->'a'->'b'->>'c' (intermediate -> for nested object access,\n // final ->> for text extraction so comparison/ilike operators work).\n //\n // String segments are parameterized normally — Postgres binds them as\n // text and `->'key'` does object access.\n //\n // Numeric segments are inlined as raw SQL because parameter binding\n // doesn't reliably dispatch to the array-index variant of `->`/`->>`\n // (Postgres often binds bigint, but the array-index operator overload\n // requires a literal int). The segment is already whitelist-validated\n // as `^\\d+$`, so inlining is safe by construction. Re-validate here as\n // belt-and-braces.\n let target: SQL = sql`${col}`\n for (let i = 0; i < path.length; i++) {\n const seg = path[i]\n const isLast = i === path.length - 1\n if (/^\\d+$/.test(seg)) {\n // Defence-in-depth re-check: must be pure digits, no leading zeros\n // beyond a single 0, length capped to avoid runaway integers.\n if (seg.length > 10) {\n throw new WhereBuilderError(`jsonb path index \"${seg}\" is too large`)\n }\n const literal = sql.raw(seg)\n target = isLast ? sql`${target}->>${literal}` : sql`${target}->${literal}`\n } else {\n target = isLast ? sql`${target}->>${seg}` : sql`${target}->${seg}`\n }\n }\n return target\n}\n\n/**\n * Apply a single column-operator object (or shorthand value) to a target\n * (column or jsonb-path expression). Returns the corresponding Drizzle SQL\n * predicate.\n */\nfunction applyOperator(target: SQLWrapper, value: unknown): SQL | undefined {\n // Shorthand: bare value → eq, bare null → isNull\n if (value === null) {\n return isNull(target as AnyPgColumn)\n }\n if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean' ||\n typeof value === 'bigint' ||\n value instanceof Date\n ) {\n return eq(target as AnyPgColumn, value as never)\n }\n if (Array.isArray(value)) {\n throw new WhereBuilderError(\n 'arrays must be wrapped in an operator (e.g. { in: [...] }) — bare arrays are not allowed',\n )\n }\n if (typeof value !== 'object') {\n throw new WhereBuilderError(`unsupported value type: ${typeof value}`)\n }\n\n const obj = value as Record<string, unknown>\n const opKeys = Object.keys(obj)\n if (opKeys.length === 0) {\n throw new WhereBuilderError('empty operator object')\n }\n\n // Multiple operators in one object are AND-ed together\n const parts: SQL[] = []\n for (const op of opKeys) {\n if (!OP_KEYS.has(op)) {\n throw new WhereBuilderError(`unknown operator \"${op}\"`)\n }\n const v = obj[op]\n switch (op) {\n case 'eq':\n parts.push(eq(target as AnyPgColumn, v as never))\n break\n case 'ne':\n parts.push(ne(target as AnyPgColumn, v as never))\n break\n case 'in':\n if (!Array.isArray(v)) {\n throw new WhereBuilderError('\"in\" operator requires an array')\n }\n if (v.length === 0) {\n // IN () is invalid SQL; an empty IN should match nothing\n parts.push(sql`false`)\n } else {\n parts.push(inArray(target as AnyPgColumn, v as never[]))\n }\n break\n case 'notIn':\n if (!Array.isArray(v)) {\n throw new WhereBuilderError('\"notIn\" operator requires an array')\n }\n if (v.length === 0) {\n // NOT IN () matches everything\n parts.push(sql`true`)\n } else {\n parts.push(notInArray(target as AnyPgColumn, v as never[]))\n }\n break\n case 'gt':\n parts.push(gt(target as AnyPgColumn, v as never))\n break\n case 'gte':\n parts.push(gte(target as AnyPgColumn, v as never))\n break\n case 'lt':\n parts.push(lt(target as AnyPgColumn, v as never))\n break\n case 'lte':\n parts.push(lte(target as AnyPgColumn, v as never))\n break\n case 'isNull':\n if (v !== true) {\n throw new WhereBuilderError('\"isNull\" operator must be `true`')\n }\n parts.push(isNull(target as AnyPgColumn))\n break\n case 'isNotNull':\n if (v !== true) {\n throw new WhereBuilderError('\"isNotNull\" operator must be `true`')\n }\n parts.push(isNotNull(target as AnyPgColumn))\n break\n case 'ilike': {\n if (typeof v !== 'string') {\n throw new WhereBuilderError('\"ilike\" operator requires a string')\n }\n // Caller's value is treated as a literal string — outer wildcards\n // come from the operator semantics, not from caller-supplied %.\n // We escape any wildcard chars in the value to prevent accidental\n // pattern injection.\n parts.push(ilike(target as AnyPgColumn, `%${escapeLikeValue(v)}%`))\n break\n }\n case 'startsWith': {\n if (typeof v !== 'string') {\n throw new WhereBuilderError('\"startsWith\" operator requires a string')\n }\n parts.push(ilike(target as AnyPgColumn, `${escapeLikeValue(v)}%`))\n break\n }\n }\n }\n\n if (parts.length === 0) return undefined\n if (parts.length === 1) return parts[0]\n return and(...parts)\n}\n\n/**\n * Translate a {@link WhereClause} object into a single Drizzle SQL\n * expression suitable for `db.select(...).where(here)`.\n *\n * Returns `undefined` for an empty clause (no filtering). Callers that\n * require a non-empty where (update, delete) should reject `undefined`\n * before invoking the SQL.\n *\n * @param depth - internal recursion depth, used to enforce\n * {@link MAX_WHERE_DEPTH}. External callers should not pass this.\n * @throws {@link WhereBuilderError} for any malformed clause, unknown\n * column, invalid operator, invalid jsonb path segment, or recursion\n * deeper than {@link MAX_WHERE_DEPTH}.\n */\nexport function buildWhere<TCols extends Record<string, ColumnFactory>>(\n table: PgTable,\n columnKinds: Readonly<Record<string, ColumnKind>>,\n clause: WhereClause<TCols> | undefined,\n depth = 0,\n): SQL | undefined {\n if (!clause) return undefined\n if (typeof clause !== 'object' || Array.isArray(clause)) {\n throw new WhereBuilderError('where clause must be a plain object')\n }\n if (depth > MAX_WHERE_DEPTH) {\n throw new WhereBuilderError(\n `where clause exceeds maximum nesting depth of ${MAX_WHERE_DEPTH} — ` +\n 'real queries should not need this much nesting; if yours does, ' +\n 'reconsider whether it should be expressed differently',\n )\n }\n\n const parts: SQL[] = []\n\n for (const key of Object.keys(clause)) {\n const value = (clause as Record<string, unknown>)[key]\n\n if (key === '$and') {\n if (!Array.isArray(value)) {\n throw new WhereBuilderError('$and must be an array')\n }\n if (value.length === 0) {\n // $and: [] is almost certainly a bug — surface it loudly,\n // matching the $or: [] behaviour for consistency.\n throw new WhereBuilderError('$and must contain at least one clause')\n }\n const subParts = (value as WhereClause<TCols>[])\n .map((c) => buildWhere(table, columnKinds, c, depth + 1))\n .filter((p): p is SQL => p !== undefined)\n if (subParts.length > 0) parts.push(and(...subParts) as SQL)\n continue\n }\n\n if (key === '$or') {\n if (!Array.isArray(value)) {\n throw new WhereBuilderError('$or must be an array')\n }\n if (value.length === 0) {\n // $or: [] would match nothing; reject explicitly to surface the bug\n throw new WhereBuilderError('$or must contain at least one clause')\n }\n const subParts = (value as WhereClause<TCols>[])\n .map((c) => buildWhere(table, columnKinds, c, depth + 1))\n .filter((p): p is SQL => p !== undefined)\n if (subParts.length > 0) parts.push(or(...subParts) as SQL)\n continue\n }\n\n if (key === '$not') {\n if (!isNonEmptyWhere(value)) {\n // $not: {} or $not: undefined is almost certainly a bug — the\n // negation of \"match everything\" or \"no filter\" produces\n // surprising semantics.\n throw new WhereBuilderError('$not must contain a non-empty clause')\n }\n const sub = buildWhere(table, columnKinds, value as WhereClause<TCols>, depth + 1)\n if (sub) parts.push(not(sub))\n continue\n }\n\n if (META_KEYS.has(key)) {\n // unreachable — guarded above\n continue\n }\n\n const target = resolveTarget(table, columnKinds, key)\n const predicate = applyOperator(target, value)\n if (predicate) parts.push(predicate)\n }\n\n if (parts.length === 0) return undefined\n if (parts.length === 1) return parts[0]\n return and(...parts) as SQL\n}\n\n/**\n * Returns true if a {@link WhereClause} produces a non-trivial filter (at\n * least one column predicate or sub-clause). Useful for rejecting empty\n * `where` arguments to `update`/`delete`/`updateMany`/`deleteMany`.\n *\n * Note: this does not check whether `buildWhere()` would actually emit SQL\n * for the clause — it just checks that the user supplied something.\n */\nexport function isNonEmptyWhere(clause: unknown): boolean {\n if (clause === undefined || clause === null) return false\n if (typeof clause !== 'object') return false\n if (Array.isArray(clause)) return false\n return Object.keys(clause as object).length > 0\n}\n","/**\n * Generic typed CRUD client for `defineTable` tables.\n *\n * Single-table operations only — no joins, no aggregates beyond `count`,\n * no hooks. For typed multi-table queries, callers reach for the\n * underlying `table` object exposed by `defineTable` and write plain\n * Drizzle. That's the one sanctioned escape valve.\n *\n * Hard rules baked into the client (not just at the SQL level):\n *\n * - `update`, `updateMany`, `delete`, `deleteMany` REQUIRE a non-empty\n * `where` clause. Empty wheres throw `TableClientError` at the client,\n * not just \"0 rows updated\".\n * - `findMany.limit` defaults to {@link DEFAULT_LIMIT} (100) and is hard-\n * capped at {@link MAX_LIMIT} (1000). Larger values get clamped and a\n * warning is logged.\n * - `insertMany` is capped at {@link MAX_BATCH} (1000) per call to keep a\n * single round trip from monopolising the connection.\n * - All write methods return the affected row(s). No silent updates.\n *\n * For more advanced operations (`claim()`, `aggregate()`,\n * `countEstimate()`) see follow-up PRs in the `feat/define-table` series.\n */\n\nimport {\n and as andFn,\n count as countFn,\n eq,\n getTableColumns,\n getTableName,\n type InferInsertModel,\n type InferSelectModel,\n inArray,\n isNotNull,\n type SQL,\n sql,\n} from 'drizzle-orm'\nimport {\n type AnyPgColumn,\n type PgTable,\n type PgUpdateSetSource,\n pgTable,\n real,\n varchar,\n} from 'drizzle-orm/pg-core'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport type { ColumnFactory, ColumnKind, ColumnValue, Row, WhereClause } from './types.js'\nimport { buildWhere, isNonEmptyWhere, WhereBuilderError } from './where-builder.js'\n\n/**\n * Allowed shape for a SQL identifier — used to validate caller-supplied\n * `aggregate()` output aliases before they are emitted as `... AS \"alias\"`.\n * Drizzle quotes identifiers, but accepting only the standard shape keeps\n * the surface predictable and removes implicit reliance on the quoting.\n */\nconst SQL_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/\n\n/**\n * Minimal Drizzle table definition for Postgres `pg_class` system catalog.\n * Used by `countEstimate()` to query row count statistics without raw SQL.\n */\nconst pgClassTable = pgTable('pg_class', {\n relname: varchar('relname', { length: 63 }).notNull(),\n reltuples: real('reltuples').notNull(),\n})\n\n/**\n * Default page size for `findMany` when no `limit` is supplied.\n *\n * Chosen as a safe upper bound for \"show me a page of stuff\" — anything\n * above this should be paginated explicitly.\n */\nexport const DEFAULT_LIMIT = 100\n\n/**\n * Hard cap on `findMany.limit`. Caller-supplied values above this are\n * clamped and a warning is emitted via `console.warn`.\n */\nexport const MAX_LIMIT = 1000\n\n/**\n * Hard cap on `insertMany.values.length` per single call.\n */\nexport const MAX_BATCH = 1000\n\n/**\n * A custom error thrown by the table client for any caller-side mistake\n * (empty where on update/delete, batch too large, malformed where, …).\n *\n * Database errors are NOT wrapped — they propagate as-is from postgres-js\n * so callers can match on Postgres error codes.\n */\nexport class TableClientError extends Error {\n constructor(message: string) {\n super(`TableClient: ${message}`)\n this.name = 'TableClientError'\n }\n}\n\n/**\n * Order-by spec accepted by `findMany`.\n */\nexport interface OrderBySpec<TCols extends Record<string, ColumnFactory>> {\n column: keyof TCols & string\n dir?: 'asc' | 'desc'\n}\n\n/**\n * `findMany` options.\n */\nexport interface FindManyOptions<TCols extends Record<string, ColumnFactory>> {\n where?: WhereClause<TCols>\n orderBy?: OrderBySpec<TCols>[]\n limit?: number\n offset?: number\n}\n\n/**\n * Options for the `claim()` atomic locking operation.\n *\n * @see {@link TableClient.claim}\n */\nexport interface ClaimOptions<\n TCols extends Record<string, ColumnFactory>,\n TTable extends PgTable = PgTable,\n> {\n /** Filter for rows eligible to be claimed. Must be non-empty. */\n where: WhereClause<TCols>\n /** Literal values to SET on claimed rows. */\n set?: Partial<InferInsertModel<TTable>>\n /** Raw SQL expressions to SET (e.g. `{ attempts: sql\\`attempts + 1\\` }`). */\n setSql?: Record<string, SQL>\n /** ORDER BY for the subselect — controls claim priority. */\n orderBy?: OrderBySpec<TCols>[]\n /** Maximum number of rows to claim. Must be 1..MAX_LIMIT. */\n limit: number\n}\n\n/**\n * Specification for a single aggregate function.\n */\nexport type AggregateSpec<TCols extends Record<string, ColumnFactory>> =\n | { fn: 'count' }\n | { fn: 'count'; column: keyof TCols & string }\n | { fn: 'sum' | 'avg' | 'min' | 'max'; column: keyof TCols & string }\n\n/**\n * Options for the `aggregate()` method.\n */\nexport interface AggregateOptions<TCols extends Record<string, ColumnFactory>> {\n /** Named aggregate expressions. Each key becomes an output column. */\n select: Record<string, AggregateSpec<TCols>>\n /** Columns to GROUP BY. Included automatically in the result. */\n groupBy?: (keyof TCols & string)[]\n /** Filter rows before aggregation (WHERE). */\n where?: WhereClause<TCols>\n /** Order results. Can reference both grouped columns and aliases. */\n orderBy?: OrderBySpec<TCols>[]\n /** Limit the number of groups returned. */\n limit?: number\n}\n\n/**\n * Generic CRUD client. Constructed by `defineTable` — never instantiated\n * directly by callers.\n *\n * The same instance shape is exposed both at the top level (using the\n * package's main `db` connection) and inside a `transaction()` callback\n * (using the transactional `tx` handle), so user code is identical\n * regardless of transactional context.\n *\n * @template TCols The columns record passed to `defineTable`. Drives the\n * where-builder DSL and the `Row<TCols>` / `InsertRow<TCols>` shapes.\n * @template TTable The underlying Drizzle pgTable type, inferred from the\n * table value passed to `new TableClient(...)`. Carrying this through\n * lets `.insert().values(...)`, `.update().set(...)`, and `.returning()`\n * use Drizzle's own `InferInsertModel` / `InferSelectModel` instead of\n * falling back to `as never` casts.\n */\nexport class TableClient<\n TCols extends Record<string, ColumnFactory>,\n TTable extends PgTable = PgTable,\n> {\n /**\n * @internal — instances are created by `defineTable()`.\n */\n constructor(\n /** The Drizzle table object. Exposed via `defineTable().table` for typed JOINs. */\n public readonly table: TTable,\n /** Per-column kind metadata. Used by the where-builder for jsonb path validation. */\n private readonly columnKinds: Readonly<Record<string, ColumnKind>>,\n /**\n * The active database handle. May be a top-level `PostgresJsDatabase`\n * or a transaction handle of compatible shape — the client doesn't\n * distinguish.\n */\n private readonly db: PostgresJsDatabase,\n /** Primary key column names. Required by `claim()`. Empty for tables without a declared PK. */\n private readonly primaryKeyColumns: readonly string[] = [],\n ) {}\n\n /**\n * The table typed as the non-generic `PgTable` base.\n *\n * Drizzle's `.from()` (select) uses a conditional type\n * `TableLikeHasEmptySelection<TTable>` that TypeScript cannot evaluate\n * against a bounded generic `TTable extends PgTable` — it can only\n * resolve when the argument is the concrete base `PgTable`. `.insert()`,\n * `.update()`, and `.delete()` have simpler signatures (`<T extends\n * PgTable>(t: T)`) that DO accept the generic directly, so those call\n * sites keep `this.table` and still get `InferInsertModel<TTable>` /\n * `InferSelectModel<TTable>` inference for `.values()`, `.set()`, and\n * `.returning()`.\n */\n private get _selectTable(): PgTable {\n return this.table\n }\n\n // -------------------------------------------------------------------------\n // Private helpers\n //\n // These collapse patterns that repeated 4-7 times across the public methods\n // (column lookup, ORDER BY emission, where-clause non-emptiness checks,\n // patch-key validation). Keep them at the top of the file so future\n // methods know where to wire in.\n // -------------------------------------------------------------------------\n\n /**\n * Look up a Drizzle column by its JS property name, throwing\n * `TableClientError` with the supplied `context` if no such column exists.\n * Wraps `getTableColumns(this.table)` — uses Drizzle's typed column map\n * instead of an indexed cast on the table object.\n */\n private getColumnOrThrow(name: string, context: string): AnyPgColumn {\n const col = getTableColumns(this.table)[name]\n if (!col) {\n throw new TableClientError(`${context}: unknown column \"${name}\"`)\n }\n return col\n }\n\n /**\n * Build an array of ORDER BY SQL expressions from an `OrderBySpec[]`.\n * Used by `findMany`, `distinct`, `claim`, and `aggregate`.\n */\n private buildOrderBy(specs: readonly OrderBySpec<TCols>[], context: string): SQL[] {\n return specs.map((spec) => {\n const col = this.getColumnOrThrow(spec.column, `${context}: orderBy`)\n return spec.dir === 'desc' ? sql`${col} DESC` : sql`${col} ASC`\n })\n }\n\n /**\n * Throw `TableClientError` if the where clause has no top-level keys.\n * Used by every method that mutates or reads-by-key (`update`, `delete`,\n * `findOne`, `exists`, `claim`, ...) — none of them have a sane\n * \"everything\" path, so empty wheres are always a caller mistake.\n */\n private requireNonEmptyWhere(where: WhereClause<TCols>, method: string): void {\n if (!isNonEmptyWhere(where)) {\n throw new TableClientError(`${method} requires a non-empty where clause`)\n }\n }\n\n /**\n * Verify every key in `patch` names a real column on this table. Used by\n * `update`, `updateMany`, `upsert`, and `claim` so a typo'd or hostile\n * patch key surfaces a clear error instead of being silently dropped by\n * Drizzle's set logic.\n */\n private validateColumnKeys(keys: readonly string[], method: string): void {\n if (keys.length === 0) return\n const cols = getTableColumns(this.table)\n for (const key of keys) {\n if (!Object.hasOwn(cols, key)) {\n throw new TableClientError(`${method}: unknown column \"${key}\"`)\n }\n }\n }\n\n // -------------------------------------------------------------------------\n // Reads\n // -------------------------------------------------------------------------\n\n /**\n * Find a single row matching the given where clause, or `null` if no row\n * matches. Throws if the where is empty.\n *\n * @example\n * ```ts\n * await readState.client.findOne({ ticketId: 't_1', userId: 'u_1' })\n * ```\n */\n async findOne(where: WhereClause<TCols>): Promise<Row<TCols> | null> {\n this.requireNonEmptyWhere(where, 'findOne')\n const condition = this.buildWhereOrThrow(where)\n const rows = await this.db.select().from(this._selectTable).where(condition).limit(1)\n return (rows[0] as Row<TCols> | undefined) ?? null\n }\n\n /**\n * Find multiple rows. Pagination, ordering, and filtering are all\n * optional but `limit` is hard-capped at {@link MAX_LIMIT}.\n *\n * @example\n * ```ts\n * await jobs.client.findMany({\n * where: { status: 'pending', runAt: { lte: new Date() } },\n * orderBy: [{ column: 'priority', dir: 'desc' }, { column: 'runAt', dir: 'asc' }],\n * limit: 25,\n * })\n * ```\n */\n async findMany(options: FindManyOptions<TCols> = {}): Promise<Row<TCols>[]> {\n // Validate all caller-supplied options up front, before touching the\n // database. This guarantees TableClientError is thrown for input\n // mistakes regardless of db state — important for unit testability\n // and for failing fast on misuse.\n const limit = this.validateLimit(options.limit)\n if (options.offset !== undefined) {\n if (!Number.isInteger(options.offset) || options.offset < 0) {\n throw new TableClientError(`offset must be a non-negative integer, got ${options.offset}`)\n }\n }\n const orderExprs =\n options.orderBy && options.orderBy.length > 0\n ? this.buildOrderBy(options.orderBy, 'findMany')\n : undefined\n const condition = options.where ? this.buildWhereOrThrow(options.where) : undefined\n\n let query = this.db.select().from(this._selectTable).$dynamic()\n if (condition) query = query.where(condition)\n if (orderExprs) query = query.orderBy(...orderExprs)\n query = query.limit(limit)\n if (options.offset !== undefined) query = query.offset(options.offset)\n\n const rows = await query\n return rows as Row<TCols>[]\n }\n\n /**\n * Count rows matching the where clause. Returns an exact count via\n * `SELECT COUNT(*)`. For estimated/cached counts on large tables, see\n * `countEstimate()` and `countCached()` (added in a follow-up PR).\n *\n * @example\n * ```ts\n * const open = await jobs.client.count({ status: 'pending' })\n * ```\n */\n async count(where?: WhereClause<TCols>): Promise<number> {\n const condition = where ? this.buildWhereOrThrow(where) : undefined\n let query = this.db.select({ value: countFn() }).from(this._selectTable).$dynamic()\n if (condition) query = query.where(condition)\n const rows = await query\n return Number(rows[0]?.value ?? 0)\n }\n\n /**\n * Returns `true` if at least one row matches the where clause. Always\n * uses `LIMIT 1` and short-circuits — cheaper than `count() > 0` on\n * large tables.\n */\n async exists(where: WhereClause<TCols>): Promise<boolean> {\n this.requireNonEmptyWhere(where, 'exists')\n const condition = this.buildWhereOrThrow(where)\n const rows = await this.db\n .select({ one: sql`1` })\n .from(this._selectTable)\n .where(condition)\n .limit(1)\n return rows.length > 0\n }\n\n /**\n * Return unique non-null values for a single column.\n *\n * Null values are excluded by default — pass `includeNull: true` to\n * include them. Results can be filtered with `where` and ordered with\n * `orderBy` (ascending or descending on the distinct column).\n *\n * @example Get distinct entity types from the audit log\n * ```ts\n * const types = await audit.client.distinct('entityType', { orderBy: 'asc' })\n * ```\n *\n * @example Distinct with a filter\n * ```ts\n * const actions = await audit.client.distinct('action', {\n * where: { userId: 'u_1' },\n * orderBy: 'asc',\n * })\n * ```\n */\n async distinct<K extends keyof TCols & string>(\n column: K,\n options?: {\n where?: WhereClause<TCols>\n orderBy?: 'asc' | 'desc'\n /** Include null values in the result. Defaults to `false`. */\n includeNull?: boolean\n },\n ): Promise<ColumnValue<TCols[K]>[]> {\n const col = this.getColumnOrThrow(column, 'distinct')\n\n const conditions: SQL[] = []\n if (!options?.includeNull) {\n conditions.push(isNotNull(col))\n }\n if (options?.where) {\n conditions.push(this.buildWhereOrThrow(options.where))\n }\n\n let query = this.db.selectDistinct({ value: col }).from(this._selectTable).$dynamic()\n\n if (conditions.length === 1) {\n query = query.where(conditions[0])\n } else if (conditions.length > 1) {\n query = query.where(andFn(...conditions))\n }\n\n if (options?.orderBy) {\n const orderExpr = options.orderBy === 'desc' ? sql`${col} DESC` : sql`${col} ASC`\n query = query.orderBy(orderExpr)\n }\n\n const rows = await query\n return rows.map((r) => (r as { value: unknown }).value) as ColumnValue<TCols[K]>[]\n }\n\n // -------------------------------------------------------------------------\n // Writes\n // -------------------------------------------------------------------------\n\n /**\n * Insert a single row. Returns the inserted row (server-generated\n * defaults populated).\n */\n async insert(values: InferInsertModel<TTable>): Promise<Row<TCols>> {\n const rows = await this.db.insert(this.table).values(values).returning()\n return rows[0] as Row<TCols>\n }\n\n /**\n * Upsert: insert a row, or update an existing row if a unique constraint\n * conflict occurs.\n *\n * `target` names the column(s) whose unique constraint should trigger\n * the conflict path. `set` is the patch applied on conflict — when\n * omitted, the conflict path applies the same `values` (i.e. \"last\n * write wins\").\n *\n * `setWhere` adds a conditional WHERE to the conflict update path. If\n * the condition does not match, the update is skipped and the existing\n * row is returned unchanged (via a fallback SELECT). This enables\n * patterns like \"only update if the new version is higher than the\n * existing one\".\n *\n * @example Idempotent \"mark as read\" tracking\n * ```ts\n * await readState.client.upsert(\n * { ticketId, userId, lastReadAt: new Date() },\n * { target: ['ticketId', 'userId'] },\n * )\n * ```\n *\n * @example Conditional update — only bump if newer\n * ```ts\n * await state.client.upsert(\n * { key: 'sync', version: 5, data: '...' },\n * { target: 'key', setWhere: { version: { lt: 5 } } },\n * )\n * ```\n */\n async upsert(\n values: InferInsertModel<TTable>,\n opts: {\n target: (keyof TCols & string) | (keyof TCols & string)[]\n set?: Partial<InferInsertModel<TTable>>\n /** Conditional WHERE on the conflict update path. Empty objects are ignored. */\n setWhere?: WhereClause<TCols>\n },\n ): Promise<Row<TCols>> {\n const targetKeys = Array.isArray(opts.target) ? opts.target : [opts.target]\n if (targetKeys.length === 0) {\n throw new TableClientError('upsert requires at least one target column')\n }\n const targetCols = targetKeys.map((k) => this.getColumnOrThrow(k, 'upsert target'))\n\n // When `set` is omitted, fall back to applying the same values on\n // conflict — this is the natural \"last write wins\" semantics that\n // covers the common upsert case.\n const setPatch: Partial<InferInsertModel<TTable>> =\n opts.set ?? (values as Partial<InferInsertModel<TTable>>)\n if (opts.set) {\n this.validateColumnKeys(Object.keys(opts.set), 'upsert set')\n }\n\n // Build the optional conditional WHERE for the conflict update path.\n // Empty `setWhere: {}` is treated as \"no condition\" (not an error).\n const setWhereCondition =\n opts.setWhere && isNonEmptyWhere(opts.setWhere)\n ? this.buildWhereOrThrow(opts.setWhere)\n : undefined\n\n const rows = await this.db\n .insert(this.table)\n .values(values)\n .onConflictDoUpdate({\n target: targetCols,\n set: setPatch,\n ...(setWhereCondition ? { setWhere: setWhereCondition } : {}),\n })\n .returning()\n\n if (rows.length > 0) {\n return rows[0] as Row<TCols>\n }\n\n // setWhere blocked the update — Postgres returns 0 rows from RETURNING.\n // Fall back to reading the existing row by the target columns.\n const where: Record<string, unknown> = {}\n for (const k of targetKeys) {\n where[k] = (values as Record<string, unknown>)[k]\n }\n const existing = await this.findOne(where as WhereClause<TCols>)\n if (!existing) {\n throw new TableClientError(\n 'upsert with setWhere: conflict row not found after blocked update — this should not happen',\n )\n }\n return existing as Row<TCols>\n }\n\n /**\n * Insert many rows in a single statement. Capped at {@link MAX_BATCH}.\n * Returns all inserted rows in input order.\n */\n async insertMany(values: InferInsertModel<TTable>[]): Promise<Row<TCols>[]> {\n if (!Array.isArray(values)) {\n throw new TableClientError('insertMany requires an array of rows')\n }\n if (values.length === 0) return []\n if (values.length > MAX_BATCH) {\n throw new TableClientError(`insertMany batch too large: ${values.length} > ${MAX_BATCH}`)\n }\n const rows = await this.db.insert(this.table).values(values).returning()\n return rows as Row<TCols>[]\n }\n\n /**\n * Update **at most one** row matching the where clause. Returns the\n * updated row, or `null` if no row matched.\n *\n * Throws `TableClientError` if:\n * - the where is empty\n * - the where matches more than one row\n *\n * The multi-row guard exists to prevent silent bulk updates from a\n * mistakenly-broad where. If you genuinely want to update multiple\n * rows in one call, use {@link updateMany}, which is explicit about\n * its plurality.\n *\n * Implementation note: this issues a single `UPDATE … RETURNING` and\n * inspects the returned row count *after* the fact. The extra rows\n * over the wire are the cost of the safety check; the alternative\n * (a SELECT round-trip first) would be more rows AND a race window.\n * If the affected count is > 1 the update has *already* happened —\n * the throw is to surface the bug, not to prevent it. Wrap in a\n * transaction if you need atomic rollback.\n */\n async update(\n where: WhereClause<TCols>,\n patch: Partial<InferInsertModel<TTable>>,\n ): Promise<Row<TCols> | null> {\n this.requireNonEmptyWhere(where, 'update')\n this.validateColumnKeys(Object.keys(patch as object), 'update')\n const condition = this.buildWhereOrThrow(where)\n const rows = await this.db.update(this.table).set(patch).where(condition).returning()\n if (rows.length > 1) {\n throw new TableClientError(\n `update matched ${rows.length} rows but is expected to match at most one — use updateMany for bulk updates. ` +\n `WARNING: the update has already been applied; wrap in a transaction if you need rollback.`,\n )\n }\n return (rows[0] as Row<TCols> | undefined) ?? null\n }\n\n /**\n * Update all rows matching the where clause. Returns every updated row.\n * Throws if the where is empty — there is no \"update everything\" path.\n */\n async updateMany(\n where: WhereClause<TCols>,\n patch: Partial<InferInsertModel<TTable>>,\n ): Promise<Row<TCols>[]> {\n this.requireNonEmptyWhere(where, 'updateMany')\n this.validateColumnKeys(Object.keys(patch as object), 'updateMany')\n const condition = this.buildWhereOrThrow(where)\n const rows = await this.db.update(this.table).set(patch).where(condition).returning()\n return rows as Row<TCols>[]\n }\n\n /**\n * Delete **at most one** row matching the where clause. Returns the\n * deleted row, or `null` if no row matched.\n *\n * Throws `TableClientError` if:\n * - the where is empty\n * - the where matches more than one row\n *\n * Same multi-row safety guard as {@link update}. Use {@link deleteMany}\n * for explicit bulk deletes.\n *\n * Implementation note: same as `update` — the delete has already\n * happened by the time the throw fires. Wrap in a transaction if you\n * need atomic rollback.\n */\n async delete(where: WhereClause<TCols>): Promise<Row<TCols> | null> {\n this.requireNonEmptyWhere(where, 'delete')\n const condition = this.buildWhereOrThrow(where)\n const rows = await this.db.delete(this.table).where(condition).returning()\n if (rows.length > 1) {\n throw new TableClientError(\n `delete matched ${rows.length} rows but is expected to match at most one — use deleteMany for bulk deletes. ` +\n `WARNING: the delete has already been applied; wrap in a transaction if you need rollback.`,\n )\n }\n return (rows[0] as Row<TCols> | undefined) ?? null\n }\n\n /**\n * Delete all rows matching the where clause. Returns every deleted row.\n * Throws if the where is empty — there is no \"delete everything\" path.\n */\n async deleteMany(where: WhereClause<TCols>): Promise<Row<TCols>[]> {\n this.requireNonEmptyWhere(where, 'deleteMany')\n const condition = this.buildWhereOrThrow(where)\n const rows = await this.db.delete(this.table).where(condition).returning()\n return rows as Row<TCols>[]\n }\n\n // -------------------------------------------------------------------------\n // Transactions\n // -------------------------------------------------------------------------\n\n /**\n * Run a callback inside a database transaction. The callback receives a\n * transactional `TableClient` instance with the same shape as the\n * top-level client — write code identically inside or outside.\n *\n * Throwing from the callback rolls back; returning a value commits.\n *\n * @example\n * ```ts\n * await files.client.transaction(async (tx) => {\n * const row = await tx.insert(values)\n * await adapter.upload(row.key, buffer) // if this throws, the row is rolled back\n * return row\n * })\n * ```\n */\n async transaction<T>(fn: (tx: TableClient<TCols, TTable>) => Promise<T>): Promise<T> {\n return await this.db.transaction(async (innerDb) => {\n const txClient = new TableClient<TCols, TTable>(\n this.table,\n this.columnKinds,\n innerDb as unknown as PostgresJsDatabase,\n this.primaryKeyColumns,\n )\n return await fn(txClient)\n })\n }\n\n // -------------------------------------------------------------------------\n // Advanced operations\n // -------------------------------------------------------------------------\n\n /**\n * Atomically claim rows using `FOR UPDATE SKIP LOCKED`.\n *\n * Selects rows matching `where`, locks them (skipping any already locked\n * by another worker), updates them with `set`/`setSql`, and returns the\n * claimed rows in a single atomic operation. This is the standard pattern\n * for job queues, task distribution, and any producer-consumer table.\n *\n * Requires the table to have a declared primary key (either via\n * `column.uuid({ primaryKey: true })` or `defineTable({ primaryKey })`.\n *\n * @example Job queue claim\n * ```ts\n * const jobs = await client.claim({\n * where: { status: 'pending', runAt: { lte: new Date() } },\n * set: { status: 'processing', lockedBy: workerId, lockedAt: new Date() },\n * setSql: { attempts: sql`${jobsTable.table.attempts} + 1` },\n * orderBy: [{ column: 'priority', dir: 'desc' }, { column: 'createdAt', dir: 'asc' }],\n * limit: 5,\n * })\n * ```\n */\n async claim(options: ClaimOptions<TCols, TTable>): Promise<Row<TCols>[]> {\n // --- validation ---\n if (this.primaryKeyColumns.length === 0) {\n throw new TableClientError(\n 'claim() requires the table to have a primary key — ' +\n 'declare primaryKey in defineTable() or use column.uuid({ primaryKey: true })',\n )\n }\n if (this.primaryKeyColumns.length > 1) {\n throw new TableClientError(\n 'claim() does not support composite primary keys — ' +\n 'use a single-column primary key or call transaction() with FOR UPDATE directly',\n )\n }\n this.requireNonEmptyWhere(options.where, 'claim')\n if (!Number.isInteger(options.limit) || options.limit < 1) {\n throw new TableClientError(`claim limit must be a positive integer, got ${options.limit}`)\n }\n if (options.limit > MAX_LIMIT) {\n throw new TableClientError(`claim limit ${options.limit} exceeds MAX_LIMIT ${MAX_LIMIT}`)\n }\n\n // Validate set/setSql have at least one field, and that every named\n // column actually exists. Using one helper keeps the literal-set path\n // and the SQL-expression path consistent (same error message shape,\n // same column resolution).\n const setKeys = Object.keys(options.set ?? {})\n const setSqlKeys = Object.keys(options.setSql ?? {})\n if (setKeys.length === 0 && setSqlKeys.length === 0) {\n throw new TableClientError('claim requires at least one field in set or setSql')\n }\n this.validateColumnKeys(setKeys, 'claim set')\n this.validateColumnKeys(setSqlKeys, 'claim setSql')\n\n const whereCondition = this.buildWhereOrThrow(options.where)\n const orderExprs =\n options.orderBy && options.orderBy.length > 0\n ? this.buildOrderBy(options.orderBy, 'claim')\n : undefined\n\n // Resolve PK column for the SELECT. `primaryKeyColumns` is validated\n // non-empty + single-column above, so [0] is always defined.\n const pkName = this.primaryKeyColumns[0] as string\n const pkCol = this.getColumnOrThrow(pkName, 'claim primary key')\n\n // Two-step atomic claim inside a transaction:\n // 1. SELECT ... FOR UPDATE SKIP LOCKED (Drizzle's .for() method)\n // 2. UPDATE ... WHERE id IN (locked ids) RETURNING *\n // Both use Drizzle's typed builder — no raw SQL, no Date serialization issues.\n return await this.db.transaction(async (tx) => {\n // Step 1: Lock rows\n let selectQuery = tx\n .select({ _pk: pkCol })\n .from(this._selectTable)\n .where(whereCondition)\n .limit(options.limit)\n .for('update', { skipLocked: true })\n .$dynamic()\n\n if (orderExprs) {\n selectQuery = selectQuery.orderBy(...orderExprs)\n }\n\n const locked = await selectQuery\n if (locked.length === 0) return []\n\n const lockedIds = locked.map((r) => (r as Record<string, unknown>)._pk)\n\n // Step 2: Update locked rows\n // Merge set (literal values) and setSql (SQL expressions) into one object.\n // `PgUpdateSetSource<TTable>` natively accepts either column values OR\n // `SQL` expressions for each column, which is exactly what we want.\n const setObj: PgUpdateSetSource<TTable> = {\n ...(options.set ?? {}),\n } as PgUpdateSetSource<TTable>\n if (options.setSql) {\n for (const [key, expr] of Object.entries(options.setSql)) {\n ;(setObj as Record<string, SQL>)[key] = expr\n }\n }\n\n const rows = await tx\n .update(this.table)\n .set(setObj)\n .where(\n inArray(pkCol, lockedIds as InferSelectModel<TTable>[keyof InferSelectModel<TTable>][]),\n )\n .returning()\n\n return rows as Row<TCols>[]\n })\n }\n\n /**\n * Run an aggregate query with GROUP BY.\n *\n * Each key in `select` is an output alias mapped to an aggregate\n * specification. The `groupBy` columns are included automatically in\n * the result. Returns typed rows with grouped column values plus the\n * computed aggregates.\n *\n * @example Count jobs by status\n * ```ts\n * const rows = await client.aggregate({\n * select: { count: { fn: 'count' } },\n * groupBy: ['status'],\n * })\n * // → [{ status: 'pending', count: 12 }, { status: 'completed', count: 45 }]\n * ```\n *\n * @example Average priority of open jobs\n * ```ts\n * const [row] = await client.aggregate({\n * select: { avgPriority: { fn: 'avg', column: 'priority' } },\n * where: { status: 'open' },\n * })\n * ```\n */\n async aggregate<TResult extends Record<string, unknown> = Record<string, unknown>>(\n options: AggregateOptions<TCols>,\n ): Promise<TResult[]> {\n const selectEntries = Object.entries(options.select)\n if (selectEntries.length === 0) {\n throw new TableClientError('aggregate requires at least one select field')\n }\n\n // Validate output aliases up front. Drizzle quotes identifier aliases\n // when emitting `... AS \"alias\"`, but accepting only the standard SQL\n // identifier shape removes that implicit reliance and keeps result\n // keys safe to interpolate elsewhere downstream.\n for (const [alias] of selectEntries) {\n if (!SQL_IDENTIFIER_RE.test(alias)) {\n throw new TableClientError(\n `aggregate: invalid output alias \"${alias}\" — must match ${SQL_IDENTIFIER_RE.source}`,\n )\n }\n }\n\n // Build the select object: group-by columns first, then aggregate\n // expressions. `getColumnOrThrow` is the single column-resolution path.\n const selectObj: Record<string, AnyPgColumn | SQL> = {}\n if (options.groupBy) {\n for (const key of options.groupBy) {\n selectObj[key] = this.getColumnOrThrow(key, 'aggregate groupBy')\n }\n }\n for (const [alias, spec] of selectEntries) {\n selectObj[alias] = this.buildAggregateExpr(spec)\n }\n\n let query = this.db.select(selectObj).from(this._selectTable).$dynamic()\n\n if (options.where) {\n query = query.where(this.buildWhereOrThrow(options.where))\n }\n\n if (options.groupBy && options.groupBy.length > 0) {\n const groupCols = options.groupBy.map((key) =>\n this.getColumnOrThrow(key, 'aggregate groupBy'),\n )\n query = query.groupBy(...groupCols)\n }\n\n if (options.orderBy && options.orderBy.length > 0) {\n query = query.orderBy(...this.buildOrderBy(options.orderBy, 'aggregate'))\n }\n\n if (options.limit !== undefined) {\n query = query.limit(this.validateLimit(options.limit))\n }\n\n return (await query) as TResult[]\n }\n\n /**\n * Fast approximate row count using Postgres `reltuples` statistics.\n *\n * Returns the estimated row count from `pg_class` — updated by\n * `ANALYZE` and autovacuum. Much faster than `COUNT(*)` on large\n * tables (no sequential scan), but may be stale by up to a few\n * percent. Returns 0 for tables that have never been analyzed.\n *\n * Use `count()` when you need an exact number. Use `countEstimate()`\n * for UI display, pagination hints, or \"is this table large?\" checks\n * where ±5% accuracy is fine.\n *\n * @example\n * ```ts\n * const approx = await client.countEstimate()\n * if (approx > 100_000) {\n * // Switch to keyset pagination instead of offset\n * }\n * ```\n */\n async countEstimate(): Promise<number> {\n const tableName = getTableName(this.table)\n\n const [row] = await this.db\n .select({ estimate: sql<number>`greatest(${pgClassTable.reltuples}::bigint, 0)` })\n .from(pgClassTable)\n .where(eq(pgClassTable.relname, tableName))\n .limit(1)\n\n return row ? Number(row.estimate) : 0\n }\n\n // -------------------------------------------------------------------------\n // Internals\n // -------------------------------------------------------------------------\n\n /** Build a single aggregate SQL expression from a spec. */\n private buildAggregateExpr(spec: AggregateSpec<TCols>): SQL {\n if ('column' in spec && spec.column) {\n const col = this.getColumnOrThrow(spec.column, 'aggregate')\n switch (spec.fn) {\n case 'count':\n return sql<number>`count(${col})::int`\n case 'sum':\n return sql<number>`sum(${col})`\n case 'avg':\n return sql<number>`avg(${col})`\n case 'min':\n return sql`min(${col})`\n case 'max':\n return sql`max(${col})`\n default:\n throw new TableClientError(`aggregate: unknown function \"${(spec as { fn: string }).fn}\"`)\n }\n }\n // count without column = COUNT(*)\n if (spec.fn === 'count') {\n return sql<number>`count(*)::int`\n }\n throw new TableClientError(`aggregate: ${spec.fn} requires a column`)\n }\n\n private buildWhereOrThrow(where: WhereClause<TCols>) {\n try {\n const condition = buildWhere(this.table, this.columnKinds, where)\n if (!condition) {\n throw new TableClientError('where clause produced no SQL predicate')\n }\n return condition\n } catch (err) {\n if (err instanceof WhereBuilderError) {\n throw new TableClientError(err.message.replace(/^WhereBuilder: /, ''))\n }\n throw err\n }\n }\n\n private validateLimit(limit: number | undefined): number {\n if (limit === undefined) return DEFAULT_LIMIT\n if (!Number.isInteger(limit) || limit < 1) {\n throw new TableClientError(`limit must be a positive integer, got ${limit}`)\n }\n // Strict throw — *not* silent clamping. The previous behaviour\n // (clamp + console.warn) was the worst of both worlds: callers got\n // a quietly different result than they asked for, AND the warn\n // didn't go through the project logger. If a caller passes a limit\n // above MAX_LIMIT it's almost certainly a bug. Make them be\n // explicit: either pass a value within the cap, or refactor to\n // paginate / use claim() (PR 4) for batch processing.\n if (limit > MAX_LIMIT) {\n throw new TableClientError(\n `limit ${limit} exceeds MAX_LIMIT ${MAX_LIMIT} — paginate or use claim() for batch processing`,\n )\n }\n return limit\n }\n}\n","/**\n * Column builders for `defineTable`.\n *\n * Each builder returns a {@link ColumnFactory} — a callable that, given a\n * column name, produces the corresponding Drizzle column builder. Phantom\n * type parameters carry the JS type, \"kind\" tag, nullability, and\n * has-default flag through to the row/insert/where types.\n *\n * Conventions vs. `@murumets-ee/entity` field builders:\n * - `notNull` here is the equivalent of `required` in field builders.\n * Renamed because tables don't have a UI distinction between\n * \"required at the schema level\" and \"required to display\".\n * - No `translatable`, `indexed`, `unique`, `access` — those are entity-only\n * concepts. `unique` and indexes are declared at the table level instead.\n * - `column.jsonb<T>()` accepts a phantom type parameter for typed reads.\n *\n * @example\n * ```ts\n * column.uuid({ primaryKey: true, defaultRandom: true })\n * column.varchar({ length: 255, notNull: true })\n * column.timestamp({ notNull: true, defaultNow: true, withTimezone: true })\n * column.jsonb<{ description: string; tags: string[] }>({ notNull: true })\n * ```\n */\n\nimport {\n type AnyPgColumn,\n bigint,\n boolean,\n doublePrecision,\n integer,\n jsonb,\n type PgColumnBuilderBase,\n text,\n timestamp,\n uuid,\n varchar,\n} from 'drizzle-orm/pg-core'\nimport type { ColumnFactory, ColumnKind } from './types.js'\n\n// ---------------------------------------------------------------------------\n// Internal: factory constructor\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap a Drizzle column-builder factory function with the phantom metadata\n * needed for type inference. The phantom fields are non-enumerable so they\n * don't pollute logging or JSON serialization.\n */\nfunction makeFactory<\n TType,\n TKind extends ColumnKind,\n TNotNull extends boolean,\n THasDefault extends boolean,\n>(\n build: (name: string) => PgColumnBuilderBase,\n meta: {\n kind: TKind\n notNull: TNotNull\n hasDefault: THasDefault\n pgName?: string\n primaryKey?: boolean\n },\n): ColumnFactory<TType, TKind, TNotNull, THasDefault> {\n const factory = build as ColumnFactory<TType, TKind, TNotNull, THasDefault>\n Object.defineProperty(factory, '__kind', { value: meta.kind, enumerable: false })\n Object.defineProperty(factory, '__notNull', { value: meta.notNull, enumerable: false })\n Object.defineProperty(factory, '__hasDefault', { value: meta.hasDefault, enumerable: false })\n // __type is purely a phantom — never read at runtime\n Object.defineProperty(factory, '__type', { value: undefined, enumerable: false })\n if (meta.pgName) {\n Object.defineProperty(factory, '__pgName', { value: meta.pgName, enumerable: false })\n }\n if (meta.primaryKey) {\n Object.defineProperty(factory, '__primaryKey', { value: true, enumerable: false })\n }\n return factory\n}\n\n// ---------------------------------------------------------------------------\n// Overloaded type interface\n// ---------------------------------------------------------------------------\n\n/**\n * Type interface for the `column` builder namespace.\n *\n * Overloads discriminate on whether `default` is present, so\n * `THasDefault` is inferred correctly:\n *\n * - `column.varchar({ length: 20, default: 'x' })` → `hasDefault = true`\n * - `column.varchar({ length: 20 })` → `hasDefault = false`\n *\n * Object-literal methods don't support overloads in TypeScript, so we\n * declare the overloaded signatures on an interface and cast the\n * implementation object.\n */\ninterface ColumnBuilders {\n uuid<\n TPrimaryKey extends boolean = false,\n TNotNull extends boolean = false,\n THasDefault extends boolean = false,\n >(opts?: {\n primaryKey?: TPrimaryKey\n notNull?: TNotNull\n defaultRandom?: THasDefault\n pgName?: string\n }): ColumnFactory<\n string,\n 'uuid',\n TPrimaryKey extends true ? true : TNotNull extends true ? true : false,\n TPrimaryKey extends true ? true : THasDefault extends true ? true : false\n >\n\n // varchar: with `default` → hasDefault = true\n varchar<TNotNull extends boolean = false>(opts: {\n length: number\n notNull?: TNotNull\n default: string\n pgName?: string\n }): ColumnFactory<string, 'varchar', TNotNull extends true ? true : false, true>\n // varchar: without `default` → hasDefault = false\n varchar<TNotNull extends boolean = false>(opts: {\n length: number\n notNull?: TNotNull\n pgName?: string\n }): ColumnFactory<string, 'varchar', TNotNull extends true ? true : false, false>\n\n // text: with `default` → hasDefault = true\n text<TNotNull extends boolean = false>(opts: {\n notNull?: TNotNull\n default: string\n pgName?: string\n }): ColumnFactory<string, 'text', TNotNull extends true ? true : false, true>\n // text: without `default` → hasDefault = false\n text<TNotNull extends boolean = false>(opts?: {\n notNull?: TNotNull\n pgName?: string\n }): ColumnFactory<string, 'text', TNotNull extends true ? true : false, false>\n\n // integer: with `default` → hasDefault = true\n integer<TNotNull extends boolean = false>(opts: {\n notNull?: TNotNull\n default: number\n pgName?: string\n }): ColumnFactory<number, 'integer', TNotNull extends true ? true : false, true>\n // integer: without `default` → hasDefault = false\n integer<TNotNull extends boolean = false>(opts?: {\n notNull?: TNotNull\n pgName?: string\n }): ColumnFactory<number, 'integer', TNotNull extends true ? true : false, false>\n\n // bigint: with `default` → hasDefault = true\n bigint<TMode extends 'number' | 'bigint' = 'number', TNotNull extends boolean = false>(opts: {\n mode?: TMode\n notNull?: TNotNull\n default: TMode extends 'bigint' ? bigint : number\n pgName?: string\n }): ColumnFactory<\n TMode extends 'bigint' ? bigint : number,\n 'bigint',\n TNotNull extends true ? true : false,\n true\n >\n // bigint: without `default` → hasDefault = false\n bigint<TMode extends 'number' | 'bigint' = 'number', TNotNull extends boolean = false>(opts?: {\n mode?: TMode\n notNull?: TNotNull\n pgName?: string\n }): ColumnFactory<\n TMode extends 'bigint' ? bigint : number,\n 'bigint',\n TNotNull extends true ? true : false,\n false\n >\n\n // double: with `default` → hasDefault = true\n double<TNotNull extends boolean = false>(opts: {\n notNull?: TNotNull\n default: number\n pgName?: string\n }): ColumnFactory<number, 'double', TNotNull extends true ? true : false, true>\n // double: without `default` → hasDefault = false\n double<TNotNull extends boolean = false>(opts?: {\n notNull?: TNotNull\n pgName?: string\n }): ColumnFactory<number, 'double', TNotNull extends true ? true : false, false>\n\n // boolean: with `default` → hasDefault = true\n boolean<TNotNull extends boolean = false>(opts: {\n notNull?: TNotNull\n default: boolean\n pgName?: string\n }): ColumnFactory<boolean, 'boolean', TNotNull extends true ? true : false, true>\n // boolean: without `default` → hasDefault = false\n boolean<TNotNull extends boolean = false>(opts?: {\n notNull?: TNotNull\n pgName?: string\n }): ColumnFactory<boolean, 'boolean', TNotNull extends true ? true : false, false>\n\n timestamp<TNotNull extends boolean = false, THasDefault extends boolean = false>(opts?: {\n notNull?: TNotNull\n defaultNow?: THasDefault\n withTimezone?: boolean\n pgName?: string\n }): ColumnFactory<\n Date,\n 'timestamp',\n TNotNull extends true ? true : false,\n THasDefault extends true ? true : false\n >\n\n // jsonb: with `default` → hasDefault = true\n jsonb<T = unknown, TNotNull extends boolean = false>(opts: {\n notNull?: TNotNull\n default: T\n pgName?: string\n }): ColumnFactory<T, 'jsonb', TNotNull extends true ? true : false, true>\n // jsonb: without `default` → hasDefault = false\n jsonb<T = unknown, TNotNull extends boolean = false>(opts?: {\n notNull?: TNotNull\n pgName?: string\n }): ColumnFactory<T, 'jsonb', TNotNull extends true ? true : false, false>\n\n uuidArray<TNotNull extends boolean = false>(opts?: {\n notNull?: TNotNull\n pgName?: string\n }): ColumnFactory<string[], 'uuidArray', TNotNull extends true ? true : false, false>\n}\n\n// ---------------------------------------------------------------------------\n// Public column builders\n// ---------------------------------------------------------------------------\n\n/**\n * Column builder namespace.\n *\n * Each builder produces a {@link ColumnFactory}. Pass the result to a\n * `defineTable` `columns` field. The builders are typed so that the\n * resulting `Row` and `InsertRow` types track each column's value type\n * and nullability.\n *\n * @example\n * ```ts\n * import { column, defineTable } from '@murumets-ee/db'\n *\n * const jobs = defineTable({\n * name: 'toolkit_jobs',\n * columns: {\n * id: column.uuid({ primaryKey: true, defaultRandom: true }),\n * type: column.varchar({ length: 100, notNull: true }),\n * payload: column.jsonb<{ subject: string; body: string }>({ notNull: true }),\n * status: column.varchar({ length: 20, notNull: true, default: 'pending' }),\n * priority: column.integer({ notNull: true, default: 0 }),\n * runAt: column.timestamp({ notNull: true, defaultNow: true, withTimezone: true }),\n * },\n * })\n * ```\n */\n// Implementation object — no type annotation here so each method can\n// return `makeFactory(...)` without per-method casts. The overloaded\n// `ColumnBuilders` interface is applied once at the export below.\nconst _column = {\n /**\n * UUID column.\n *\n * For primary keys, use `{ primaryKey: true, defaultRandom: true }` to\n * get a server-generated UUIDv4.\n */\n uuid(opts?: {\n primaryKey?: boolean\n notNull?: boolean\n defaultRandom?: boolean\n pgName?: string\n }) {\n const notNull = (opts?.notNull ?? false) as boolean\n const hasDefault = (opts?.defaultRandom ?? false) as boolean\n const isPk = (opts?.primaryKey ?? false) as boolean\n return makeFactory(\n (name) => {\n let col = uuid(name)\n if (isPk) col = col.primaryKey()\n if (opts?.defaultRandom) col = col.defaultRandom()\n // Primary keys are implicitly NOT NULL in Postgres; only call notNull for non-PK\n if (notNull && !isPk) col = col.notNull()\n return col\n },\n {\n kind: 'uuid',\n notNull: (notNull || isPk) as boolean,\n hasDefault: (hasDefault || isPk) as boolean,\n pgName: opts?.pgName,\n primaryKey: isPk || undefined,\n },\n )\n },\n\n /**\n * Variable-length string column. `length` is required and must be a\n * positive integer up to Postgres' actual `varchar` ceiling\n * (1073741823 — i.e. ~1GB / 4 bytes per char). For unbounded text use\n * `column.text()` instead — it stores the same way and skips the\n * length check at write time.\n *\n * Conventional sizes for reference:\n * - 50-100: short identifiers, slugs, status codes\n * - 255: legacy \"common max length\" — fits a tweet, an email, etc.\n * - 1024: file paths, URLs\n * - 10000+: prefer `text` instead, varchar with very large lengths\n * buys you nothing\n */\n varchar(opts: { length: number; notNull?: boolean; default?: string; pgName?: string }) {\n // Postgres' real varchar limit is 1073741823 (varchar without length\n // mod uses TEXT internally). We enforce the same upper bound so the\n // error fires at definition time instead of at INSERT time.\n if (!Number.isInteger(opts.length) || opts.length < 1 || opts.length > 1073741823) {\n throw new Error(\n `column.varchar: length must be an integer between 1 and 1073741823, got ${opts.length}`,\n )\n }\n const notNull = (opts.notNull ?? false) as boolean\n const hasDefault = opts.default !== undefined\n return makeFactory(\n (name) => {\n let col = varchar(name, { length: opts.length })\n if (notNull) col = col.notNull()\n if (opts.default !== undefined) col = col.default(opts.default)\n return col\n },\n {\n kind: 'varchar',\n notNull: notNull as boolean,\n hasDefault: hasDefault as boolean,\n pgName: opts.pgName,\n },\n )\n },\n\n /**\n * Unbounded text column.\n */\n text(opts?: { notNull?: boolean; default?: string; pgName?: string }) {\n const notNull = (opts?.notNull ?? false) as boolean\n const hasDefault = opts?.default !== undefined\n return makeFactory(\n (name) => {\n let col = text(name)\n if (notNull) col = col.notNull()\n if (opts?.default !== undefined) col = col.default(opts.default)\n return col\n },\n {\n kind: 'text',\n notNull: notNull as boolean,\n hasDefault: hasDefault as boolean,\n pgName: opts?.pgName,\n },\n )\n },\n\n /**\n * 32-bit integer column.\n */\n integer(opts?: { notNull?: boolean; default?: number; pgName?: string }) {\n const notNull = (opts?.notNull ?? false) as boolean\n const hasDefault = opts?.default !== undefined\n return makeFactory(\n (name) => {\n let col = integer(name)\n if (notNull) col = col.notNull()\n if (opts?.default !== undefined) col = col.default(opts.default)\n return col\n },\n {\n kind: 'integer',\n notNull: notNull as boolean,\n hasDefault: hasDefault as boolean,\n pgName: opts?.pgName,\n },\n )\n },\n\n /**\n * 64-bit integer column. `mode: 'number'` returns a JS number (safe for\n * values up to 2^53), `mode: 'bigint'` returns a JS BigInt.\n */\n bigint(opts?: {\n mode?: 'number' | 'bigint'\n notNull?: boolean\n default?: number | bigint\n pgName?: string\n }) {\n const mode = (opts?.mode ?? 'number') as 'number' | 'bigint'\n const notNull = (opts?.notNull ?? false) as boolean\n const hasDefault = opts?.default !== undefined\n return makeFactory(\n (name) => {\n let col = bigint(name, { mode })\n if (notNull) col = col.notNull()\n if (opts?.default !== undefined) col = col.default(opts.default as never)\n return col\n },\n {\n kind: 'bigint',\n notNull: notNull as boolean,\n hasDefault: hasDefault as boolean,\n pgName: opts?.pgName,\n },\n )\n },\n\n /**\n * Double-precision floating-point column.\n */\n double(opts?: { notNull?: boolean; default?: number; pgName?: string }) {\n const notNull = (opts?.notNull ?? false) as boolean\n const hasDefault = opts?.default !== undefined\n return makeFactory(\n (name) => {\n let col = doublePrecision(name)\n if (notNull) col = col.notNull()\n if (opts?.default !== undefined) col = col.default(opts.default)\n return col\n },\n {\n kind: 'double',\n notNull: notNull as boolean,\n hasDefault: hasDefault as boolean,\n pgName: opts?.pgName,\n },\n )\n },\n\n /**\n * Boolean column. Defaults to `false` if no explicit default is supplied\n * AND `notNull` is true — matches the entity package convention.\n */\n boolean(opts?: { notNull?: boolean; default?: boolean; pgName?: string }) {\n const notNull = (opts?.notNull ?? false) as boolean\n const hasDefault = opts?.default !== undefined\n return makeFactory(\n (name) => {\n let col = boolean(name)\n if (notNull) col = col.notNull()\n if (opts?.default !== undefined) col = col.default(opts.default)\n return col\n },\n {\n kind: 'boolean',\n notNull: notNull as boolean,\n hasDefault: hasDefault as boolean,\n pgName: opts?.pgName,\n },\n )\n },\n\n /**\n * Timestamp column. `withTimezone: true` is strongly recommended for all\n * timestamps in this codebase — entity audit fields use it consistently.\n */\n timestamp(opts?: {\n notNull?: boolean\n defaultNow?: boolean\n withTimezone?: boolean\n pgName?: string\n }) {\n const notNull = (opts?.notNull ?? false) as boolean\n const hasDefault = (opts?.defaultNow ?? false) as boolean\n const withTimezone = opts?.withTimezone ?? true\n return makeFactory(\n (name) => {\n let col = timestamp(name, { withTimezone })\n if (notNull) col = col.notNull()\n if (opts?.defaultNow) col = col.defaultNow()\n return col\n },\n {\n kind: 'timestamp',\n notNull: notNull as boolean,\n hasDefault: hasDefault as boolean,\n pgName: opts?.pgName,\n },\n )\n },\n\n /**\n * JSONB column. The phantom type parameter `T` propagates to row reads\n * and where-clause shorthand for the whole-column case. For dotted-path\n * access via the where-builder, the value type at the path is `unknown`\n * (TypeScript can't follow runtime path navigation).\n */\n jsonb(opts?: { notNull?: boolean; default?: unknown; pgName?: string }) {\n const notNull = (opts?.notNull ?? false) as boolean\n const hasDefault = opts?.default !== undefined\n return makeFactory(\n (name) => {\n let col = jsonb(name)\n if (notNull) col = col.notNull()\n if (opts?.default !== undefined) col = col.default(opts.default)\n return col\n },\n {\n kind: 'jsonb',\n notNull: notNull as boolean,\n hasDefault: hasDefault as boolean,\n pgName: opts?.pgName,\n },\n )\n },\n\n /**\n * UUID array column (`uuid[]`).\n */\n uuidArray(opts?: { notNull?: boolean; pgName?: string }) {\n const notNull = (opts?.notNull ?? false) as boolean\n return makeFactory(\n (name) => {\n // `PgArrayBuilder` (returned by `.array()`) extends `PgColumnBuilder`,\n // which is what `pgTable(...)` ultimately consumes — no cast needed.\n const col = uuid(name).array()\n return notNull ? col.notNull() : col\n },\n {\n kind: 'uuidArray',\n notNull: notNull as boolean,\n hasDefault: false as false,\n pgName: opts?.pgName,\n },\n )\n },\n}\n\n/**\n * Column builder namespace — overloaded interface applied via single\n * assertion. Each builder's implementation returns `makeFactory(...)` with\n * widened boolean params; the `ColumnBuilders` overloads narrow them for\n * callers based on whether `default`/`primaryKey` etc. are present.\n */\nexport const column: ColumnBuilders = _column as ColumnBuilders\n\n// Re-export the Drizzle column type for advanced consumers that need to\n// reach into the underlying pgTable for typed JOINs.\nexport type { AnyPgColumn }\n","/**\n * Process-wide registry of all `defineTable()` calls.\n *\n * Used by:\n * - **Runtime table lookup** — code paths that hold a table name string\n * can resolve it to a `PgTable` without importing the source file.\n * - **Test setup** — `createTestDb().push()` accepts a record of Drizzle\n * tables; pass `tableRegistry.allTables()` to push every defined table\n * into a fresh test schema in one call.\n * - **Audits / introspection** — list all known infra tables for tooling.\n *\n * Note: NOT used for migration discovery. `lumi migrate` discovers tables\n * via `Plugin.tables` fields + `buildEntitySchemaMap()`.\n *\n * The registry is intentionally separate from `schemaRegistry` (which is\n * for entity-generated schemas) so the two layers stay decoupled. They\n * may be merged in a follow-up if it simplifies migration discovery.\n */\n\nimport type { PgTable } from 'drizzle-orm/pg-core'\n\ninterface RegistryEntry {\n name: string\n table: PgTable\n /** Source file (best-effort, captured at definition time for debugging). */\n source?: string\n}\n\nclass TableRegistryImpl {\n private byName: Map<string, RegistryEntry> = new Map()\n\n /**\n * Register a defined table. Throws if a table with the same name is\n * already registered (catches accidental double-registration from a\n * file being imported under two paths).\n */\n register(name: string, table: PgTable, source?: string): void {\n const existing = this.byName.get(name)\n if (existing) {\n // Same table object → idempotent re-import; allow silently.\n if (existing.table === table) return\n // Different object ref but same source location → bundler dual-evaluated\n // the same module (Turbopack chunking, Next.js dev HMR, dual CJS/ESM\n // resolution). The underlying defineTable call is the same code. Treat\n // as idempotent: keep the first registration, silently drop the second.\n if (existing.source && source && existing.source === source) return\n throw new Error(\n `tableRegistry: a different table is already registered under name \"${name}\". ` +\n `Did you call defineTable twice? First registered from ${existing.source ?? '<unknown>'}, ` +\n `now from ${source ?? '<unknown>'}.`,\n )\n }\n this.byName.set(name, { name, table, source })\n }\n\n /**\n * Get a defined table by name.\n */\n get(name: string): PgTable | undefined {\n return this.byName.get(name)?.table\n }\n\n /**\n * Returns true if a table with the given name has been registered.\n */\n has(name: string): boolean {\n return this.byName.has(name)\n }\n\n /**\n * Return every registered table as a `{ [name]: PgTable }` map.\n *\n * Useful for `createTestDb().push(tableRegistry.allTables())` and\n * runtime introspection.\n */\n allTables(): Record<string, PgTable> {\n const out: Record<string, PgTable> = {}\n for (const entry of this.byName.values()) {\n out[entry.name] = entry.table\n }\n return out\n }\n\n /**\n * Clear the registry. **Test-only.**\n *\n * Production code should never call this — clearing the registry\n * silently breaks migration discovery and any code path that looks\n * up a table by name. The runtime guard below refuses to run when\n * `NODE_ENV === 'production'` to catch accidental misuse.\n *\n * Test runners typically set `NODE_ENV=test` (Vitest does), but the\n * guard is permissive about other values — it only blocks the one\n * environment where calling it is definitely wrong.\n */\n clear(): void {\n if (process.env.NODE_ENV === 'production') {\n throw new Error(\n 'tableRegistry.clear() is not allowed when NODE_ENV=production — ' +\n 'this is a test-only utility. If you reached this from production ' +\n 'code, you have a bug.',\n )\n }\n this.byName.clear()\n }\n}\n\n/**\n * Process-wide table registry singleton.\n *\n * Tables register themselves on import via `defineTable()`. The registry\n * is module-level state, which means a single Node process sees one\n * consistent view of all defined tables across packages.\n */\nexport const tableRegistry = new TableRegistryImpl()\n\n/**\n * Create an isolated registry — useful for unit tests that want to\n * verify registration behaviour without polluting the global registry.\n */\nexport function createTableRegistry() {\n return new TableRegistryImpl()\n}\n","/**\n * `defineTable()` — the entry point for the table layer.\n *\n * Given a {@link TableDefinition}, builds:\n *\n * - The underlying Drizzle `pgTable` (exposed for typed JOINs)\n * - A typed {@link TableClient} factory (callers wire in their `db` handle)\n * - The original schema metadata (for introspection / migrations)\n *\n * The table is also registered in the process-wide {@link tableRegistry}\n * so migration tooling and test setup can discover it.\n *\n * @example\n * ```ts\n * import { column, defineTable } from '@murumets-ee/db'\n *\n * export const ticketReadState = defineTable({\n * name: 'ticketing_read_state',\n * columns: {\n * ticketId: column.uuid({ notNull: true }),\n * userId: column.varchar({ length: 255, notNull: true }),\n * lastReadAt: column.timestamp({ notNull: true, defaultNow: true, withTimezone: true }),\n * },\n * primaryKey: ['ticketId', 'userId'],\n * indexes: [{ on: ['userId'] }],\n * })\n *\n * // Use the typed Drizzle table for migrations / typed JOINs:\n * export const ticketReadStateTable = ticketReadState.table\n *\n * // Build a client by wiring in a db handle:\n * const client = ticketReadState.makeClient(db)\n * await client.upsert(...) // (upsert lands in PR 3)\n * ```\n */\n\nimport {\n type AnyPgColumn,\n index as drizzleIndex,\n primaryKey as drizzlePrimaryKey,\n unique as drizzleUnique,\n type PgColumnBuilderBase,\n type PgTable,\n type PgTableExtraConfigValue,\n type PgTableWithColumns,\n pgTable,\n type TableConfig,\n} from 'drizzle-orm/pg-core'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport { TableClient } from './client.js'\nimport { tableRegistry } from './registry.js'\nimport type { ColumnFactory, ColumnKind, TableDefinition } from './types.js'\n\n/** Allowed shape for table names. Snake_case, leading lowercase letter. */\nconst TABLE_NAME_RE = /^[a-z][a-z0-9_]*$/\n\n/**\n * Postgres' default identifier length limit (NAMEDATALEN - 1).\n *\n * Postgres silently truncates identifiers longer than this — which can\n * cause auto-generated constraint/index names to collide. We enforce\n * the same limit at definition time so the failure is loud and\n * caller-controllable.\n *\n * Bumping this requires a custom Postgres build (NAMEDATALEN), which\n * no production setup we ship has.\n */\nconst PG_IDENTIFIER_MAX = 63\n\n/**\n * Check that a Postgres identifier (table/column/constraint/index name)\n * fits in the 63-char limit, throwing with a useful error otherwise.\n *\n * The `kind` argument is for error messages only — e.g. \"table name\",\n * \"auto-generated index name\". The `suggestion` is the auto-generated\n * name's caller-facing alternative (\"supply an explicit `name`\").\n */\nfunction assertIdentifierLength(identifier: string, kind: string, suggestion?: string): void {\n if (identifier.length > PG_IDENTIFIER_MAX) {\n throw new Error(\n `defineTable: ${kind} \"${identifier}\" is ${identifier.length} characters — ` +\n `Postgres truncates identifiers at ${PG_IDENTIFIER_MAX} chars silently, ` +\n `which can cause name collisions${suggestion ? `. ${suggestion}` : ''}`,\n )\n }\n}\n\n/**\n * The result of `defineTable()`.\n *\n * @template TCols The columns record passed to `defineTable`.\n * @template TTable The concrete Drizzle `pgTable` type produced by the\n * definition. Defaulted to the non-generic `PgTable` for callers that\n * pass `DefinedTable` around without propagating inference — inside\n * `defineTable` itself, the actual `typeof table` is captured so\n * `makeClient` returns a fully-typed `TableClient<TCols, typeof table>`.\n */\nexport interface DefinedTable<\n TCols extends Record<string, ColumnFactory>,\n TTable extends PgTable = PgTableWithColumns<TableConfig>,\n> {\n /**\n * The underlying Drizzle `pgTable`. Exposed so callers can use it in\n * typed JOINs (the one sanctioned escape valve), and so it can be\n * included in `Plugin.tables` for migration discovery by `lumi migrate`.\n */\n table: TTable\n /**\n * The original schema metadata. Useful for introspection / tooling\n * that needs to know about indexes, unique constraints, etc.\n */\n schema: TableDefinition<TCols>\n /**\n * Per-column kind metadata (column name → kind tag). Used internally\n * by the where-builder for jsonb path validation.\n */\n columnKinds: Readonly<Record<string, ColumnKind>>\n /**\n * Primary key column names (JS property names). Detected from either\n * `schema.primaryKey` or column-level `{ primaryKey: true }`. Empty\n * array if no PK is declared. Required by `claim()`.\n */\n primaryKeyColumns: readonly string[]\n /**\n * Build a typed CRUD client wired to the given database handle.\n *\n * Callers typically construct one client per logical \"module\" and\n * cache it. The client is stateless aside from the db handle, so it\n * is safe to share.\n */\n makeClient(db: PostgresJsDatabase): TableClient<TCols, TTable>\n}\n\n/**\n * Define a typed table.\n *\n * The function signature uses two const-generic parameters to preserve\n * column literal types end-to-end. This is what enables `Row`,\n * `InsertRow`, and `WhereClause` to know exact column shapes at the call\n * site.\n *\n * @throws if the table name doesn't match the snake_case regex, or if\n * the same name is registered twice with different table objects.\n */\nexport function defineTable<const TCols extends Record<string, ColumnFactory>>(\n def: TableDefinition<TCols>,\n) {\n // Validate the table name shape — fail loudly at definition time rather\n // than letting Postgres catch it at migration time.\n if (!TABLE_NAME_RE.test(def.name)) {\n throw new Error(\n `defineTable: invalid table name \"${def.name}\" — must match /^[a-z][a-z0-9_]*$/`,\n )\n }\n assertIdentifierLength(def.name, 'table name')\n\n // Build the per-column drizzle column builders by invoking each factory\n // with its property name. The factory contract requires the produced\n // column to be a `PgColumnBuilderBase` ready for `pgTable(...)`.\n const drizzleCols: Record<string, PgColumnBuilderBase> = {}\n const columnKinds: Record<string, ColumnKind> = {}\n for (const [name, factory] of Object.entries(def.columns)) {\n if (typeof factory !== 'function') {\n throw new Error(\n `defineTable: column \"${name}\" is not a column factory — did you call e.g. column.uuid() with parens?`,\n )\n }\n assertIdentifierLength(name, `column name in table \"${def.name}\"`)\n // Use explicit pgName when porting existing tables with snake_case columns\n const pgName = factory.__pgName\n if (pgName) {\n assertIdentifierLength(pgName, `pgName for column \"${name}\" in table \"${def.name}\"`)\n }\n drizzleCols[name] = factory(pgName ?? name)\n columnKinds[name] = factory.__kind\n }\n\n // Validate that all constraint references point at known columns BEFORE\n // building the pgTable. The pgTable constraint callback may not surface\n // errors at construction time, so we check eagerly here for fast failure\n // and consistent error messages.\n const knownColumns = new Set(Object.keys(drizzleCols))\n if (def.primaryKey !== undefined) {\n const pkCols = Array.isArray(def.primaryKey) ? def.primaryKey : [def.primaryKey]\n for (const k of pkCols) {\n if (!knownColumns.has(String(k))) {\n throw new Error(`defineTable: primaryKey references unknown column \"${String(k)}\"`)\n }\n }\n }\n if (def.unique) {\n for (const uq of def.unique) {\n for (const k of uq.on) {\n if (!knownColumns.has(String(k))) {\n throw new Error(`defineTable: unique constraint references unknown column \"${String(k)}\"`)\n }\n }\n const name = uq.name ?? `uq_${def.name}_${uq.on.map(String).join('_')}`\n assertIdentifierLength(\n name,\n uq.name ? 'unique constraint name' : 'auto-generated unique constraint name',\n uq.name ? undefined : 'Supply a shorter explicit `name` to the unique definition.',\n )\n }\n }\n if (def.indexes) {\n for (const ix of def.indexes) {\n for (const k of ix.on) {\n if (!knownColumns.has(String(k))) {\n throw new Error(`defineTable: index references unknown column \"${String(k)}\"`)\n }\n }\n const name = ix.name ?? `idx_${def.name}_${ix.on.map(String).join('_')}`\n assertIdentifierLength(\n name,\n ix.name ? 'index name' : 'auto-generated index name',\n ix.name ? undefined : 'Supply a shorter explicit `name` to the index definition.',\n )\n }\n }\n\n // Build the constraint callback for indexes / unique / primary key.\n // pgTable accepts an optional callback that returns a constraint object.\n const hasConstraints =\n def.primaryKey !== undefined ||\n (def.unique && def.unique.length > 0) ||\n (def.indexes && def.indexes.length > 0)\n\n // Helper: look up a column by key from the pgTable callback parameter.\n // `t` has a string index (`{ [x: string]: PgColumn }`) so access is typed.\n function resolveCol(t: Record<string, AnyPgColumn>, k: string, context: string): AnyPgColumn {\n const col = t[k]\n if (!col) throw new Error(`defineTable: ${context} references unknown column \"${k}\"`)\n return col\n }\n\n const table = hasConstraints\n ? pgTable(def.name, drizzleCols, (t) => {\n const tCols = t as unknown as Record<string, AnyPgColumn>\n const constraints: PgTableExtraConfigValue[] = []\n\n // Composite primary key\n if (def.primaryKey !== undefined) {\n const pkCols = Array.isArray(def.primaryKey) ? def.primaryKey : [def.primaryKey]\n const first = resolveCol(tCols, String(pkCols[0]), 'primaryKey')\n const rest = pkCols.slice(1).map((k) => resolveCol(tCols, String(k), 'primaryKey'))\n constraints.push(drizzlePrimaryKey({ columns: [first, ...rest] }))\n }\n\n // Unique constraints\n if (def.unique) {\n for (const uq of def.unique) {\n const first = resolveCol(tCols, String(uq.on[0]), 'unique constraint')\n const rest = uq.on\n .slice(1)\n .map((k) => resolveCol(tCols, String(k), 'unique constraint'))\n constraints.push(drizzleUnique().on(first, ...rest))\n }\n }\n\n // Indexes\n if (def.indexes) {\n for (const ix of def.indexes) {\n const first = resolveCol(tCols, String(ix.on[0]), 'index')\n const rest = ix.on.slice(1).map((k) => resolveCol(tCols, String(k), 'index'))\n constraints.push(\n drizzleIndex(ix.name ?? `idx_${def.name}_${ix.on.map(String).join('_')}`).on(\n first,\n ...rest,\n ),\n )\n }\n }\n\n return constraints\n })\n : pgTable(def.name, drizzleCols)\n\n // Capture caller location for nicer double-registration errors. Best-\n // effort — falls back to undefined if stack parsing fails.\n const source = captureCallerLocation()\n tableRegistry.register(def.name, table, source)\n\n const frozenKinds: Readonly<Record<string, ColumnKind>> = Object.freeze({ ...columnKinds })\n\n // Determine primary key column names from table-level or column-level declarations.\n let pkCols: string[]\n if (def.primaryKey !== undefined) {\n pkCols = Array.isArray(def.primaryKey) ? def.primaryKey.map(String) : [String(def.primaryKey)]\n } else {\n pkCols = []\n for (const [name, factory] of Object.entries(def.columns)) {\n if (factory.__primaryKey) pkCols.push(name)\n }\n }\n const frozenPk: readonly string[] = Object.freeze([...pkCols])\n\n return {\n table,\n schema: def,\n columnKinds: frozenKinds,\n primaryKeyColumns: frozenPk,\n makeClient: (db: PostgresJsDatabase) =>\n new TableClient<TCols, typeof table>(table, frozenKinds, db, frozenPk),\n }\n}\n\n/**\n * Best-effort: walk the stack to find the first non-internal frame so\n * double-registration errors point at the user's call site instead of\n * deep into `defineTable`.\n */\nfunction captureCallerLocation(): string | undefined {\n const stack = new Error().stack\n if (!stack) return undefined\n const lines = stack.split('\\n').slice(1)\n for (const line of lines) {\n if (line.includes('/table/define.') || line.includes('/table/registry.')) continue\n const match = line.match(/\\(([^)]+)\\)/) ?? line.match(/at (.+)$/)\n if (match) return match[1].trim()\n }\n return undefined\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["countFn","andFn","drizzlePrimaryKey","drizzleUnique","drizzleIndex"],"sources":["../src/query-counter.ts","../src/client.ts","../src/migrate.ts","../src/schema-registry.ts","../src/table/where-builder.ts","../src/table/client.ts","../src/table/columns.ts","../src/table/registry.ts","../src/table/define.ts"],"sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks'\nimport type { Logger } from 'drizzle-orm/logger'\n\n/**\n * Per-request DB query counter — opt-in, off by default.\n *\n * Wired into the Drizzle client when `LUMI_DB_QUERY_COUNTER=1`. The harness\n * (apps/perf-harness) wraps each HTTP request in `runWithQueryStats` and emits\n * the count as an `x-db-query-count` response header, so k6 scenarios can\n * assert per-endpoint query budgets like:\n *\n * checks: { 'findById ≤ 2 queries': (r) => r.headers['X-Db-Query-Count'] <= '2' }\n *\n * No-op for any code path that does not enter `runWithQueryStats` — the ALS\n * store is undefined and `logQuery` returns immediately.\n */\n\nexport interface QueryStats {\n count: number\n}\n\nconst queryStatsAls = new AsyncLocalStorage<QueryStats>()\n\nexport function getQueryStats(): QueryStats | undefined {\n return queryStatsAls.getStore()\n}\n\nexport function runWithQueryStats<T>(fn: (stats: QueryStats) => Promise<T>): Promise<T> {\n const stats: QueryStats = { count: 0 }\n return queryStatsAls.run(stats, () => fn(stats))\n}\n\nclass QueryCountingLogger implements Logger {\n logQuery(_query: string, _params: unknown[]): void {\n const stats = queryStatsAls.getStore()\n if (stats) stats.count++\n }\n}\n\nexport const queryCountingLogger: Logger = new QueryCountingLogger()\n\nexport function isQueryCounterEnabled(): boolean {\n return process.env.LUMI_DB_QUERY_COUNTER === '1'\n}\n","import type { Logger } from 'drizzle-orm/logger'\nimport { drizzle, type PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport postgres from 'postgres'\nimport { isQueryCounterEnabled, queryCountingLogger } from './query-counter.js'\n\nexport interface DbConfig {\n url: string\n readOnlyUrl?: string\n poolMin?: number\n poolMax?: number\n}\n\nfunction drizzleConfig(): { logger: Logger } | undefined {\n return isQueryCounterEnabled() ? { logger: queryCountingLogger } : undefined\n}\n\n/**\n * Create a full read-write database client.\n */\nexport function createDbClient(config: DbConfig): PostgresJsDatabase {\n const sql = postgres(config.url, {\n max: config.poolMax || 10,\n idle_timeout: 20,\n max_lifetime: 60 * 30,\n })\n\n const cfg = drizzleConfig()\n return cfg ? drizzle(sql, cfg) : drizzle(sql)\n}\n\n/**\n * Create a read-only database client.\n *\n * Sets `default_transaction_read_only = on` as a startup parameter on every\n * connection in the pool, so any implicit transaction issued through this\n * client is read-only at the PostgreSQL level. This is the defense-in-depth\n * layer beneath the application's own routing of writes through `AdminClient`\n * — even if a write somehow reaches a read-only client, Postgres rejects it.\n *\n * Implementation note: postgres-js's `connection` config object is sent in\n * the PostgreSQL startup packet for every new pool connection, so the GUC is\n * applied uniformly across the whole pool — not just whichever connection\n * happened to run a one-off `SET` statement.\n */\nexport function createReadOnlyClient(config: DbConfig): PostgresJsDatabase {\n const url = config.readOnlyUrl || config.url\n\n const sql = postgres(url, {\n max: config.poolMax || 10,\n idle_timeout: 20,\n max_lifetime: 60 * 30,\n connection: {\n application_name: 'toolkit_readonly',\n default_transaction_read_only: true,\n },\n onnotice: () => {}, // Suppress NOTICE messages\n })\n\n const cfg = drizzleConfig()\n return cfg ? drizzle(sql, cfg) : drizzle(sql)\n}\n","import { readdir } from 'node:fs/promises'\nimport { join } from 'node:path'\nimport { sql } from 'drizzle-orm'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\n\n/**\n * Read a directory's `.ts` filenames, returning `[]` if the directory does\n * not exist. Single round-trip — avoids the `existsSync` + `readdir` race.\n */\nasync function readTsFilesOrEmpty(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir)\n return entries.filter((name) => name.endsWith('.ts'))\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === 'ENOENT') return []\n throw err\n }\n}\n\n/**\n * Arguments passed to migration `up` / `down` functions.\n *\n * Intentionally tiny: we hand over a read-write Drizzle handle bound to\n * the current transaction. If a migration needs anything beyond raw SQL\n * (e.g. calling an AdminClient for a data backfill), it can import the\n * relevant toolkit helpers inside the migration file — the CLI loader\n * runs the migration in the same process as the config, so all package\n * imports resolve normally.\n */\nexport interface MigrateArgs {\n db: PostgresJsDatabase\n}\n\n/**\n * Shape of a migration module loaded from a `.ts` file.\n *\n * At minimum a migration must export `up`. `down` is optional — when\n * absent, `lumi migrate:rollback` refuses to roll that file back.\n */\nexport interface MigrationModule {\n up: (args: MigrateArgs) => Promise<void>\n down?: (args: MigrateArgs) => Promise<void>\n}\n\nexport interface MigrationSource {\n /** Absolute path to the migration file on disk. */\n path: string\n /** `project` for user migrations, `toolkit` for shipped-with-toolkit ones. */\n namespace: 'toolkit' | 'project'\n /** Filename only, used for ordering + tracking. */\n name: string\n}\n\nexport interface MigrationStatus {\n applied: MigrationSource[]\n pending: MigrationSource[]\n}\n\n/**\n * Loader callback — imports a migration `.ts` file and returns its exports.\n *\n * The CLI owns `jiti` (already used to load `lumi.config.ts`), so\n * `@murumets-ee/db` stays free of TS-runtime loader dependencies.\n * Callers who never apply migrations (read-only servers, tests) never\n * pay the loader cost.\n */\nexport type MigrationLoader = (absolutePath: string) => Promise<MigrationModule>\n\n/** Ensure the `_toolkit_migrations` tracking table exists. */\nasync function ensureMigrationsTable(db: PostgresJsDatabase): Promise<void> {\n await db.execute(sql`\n CREATE TABLE IF NOT EXISTS _toolkit_migrations (\n id SERIAL PRIMARY KEY,\n namespace VARCHAR(50) NOT NULL,\n name VARCHAR(255) NOT NULL,\n applied_at TIMESTAMP NOT NULL DEFAULT NOW(),\n UNIQUE(namespace, name)\n )\n `)\n}\n\n/**\n * Discover all migration files under `projectRoot/migrations`.\n *\n * Layout:\n * migrations/\n * 20260411_180000_add_articles.ts ← user migrations (sort chronologically)\n * 20260411_180000_add_articles.snapshot.json\n * .toolkit/\n * 0001_initial_schema.ts ← toolkit-owned migrations (sort numerically)\n *\n * Module-private — callers reach migrations via {@link getMigrationStatus}\n * (returns `applied` + `pending`) or {@link runMigrations} / {@link rollbackMigrations}.\n */\nasync function discoverMigrations(projectRoot: string): Promise<MigrationSource[]> {\n const migrationsDir = join(projectRoot, 'migrations')\n const toolkitDir = join(migrationsDir, '.toolkit')\n\n const [toolkitFiles, projectEntries] = await Promise.all([\n readTsFilesOrEmpty(toolkitDir),\n readTsFilesOrEmpty(migrationsDir),\n ])\n\n const toolkit: MigrationSource[] = toolkitFiles\n .map((name) => ({ path: join(toolkitDir, name), namespace: 'toolkit' as const, name }))\n .sort((a, b) => a.name.localeCompare(b.name))\n\n const project: MigrationSource[] = projectEntries\n // The `.toolkit` directory itself does not end in `.ts`, so it is filtered\n // out by `readTsFilesOrEmpty`. Filtering here is just defence-in-depth.\n .filter((name) => name !== '.toolkit')\n .map((name) => ({ path: join(migrationsDir, name), namespace: 'project' as const, name }))\n .sort((a, b) => a.name.localeCompare(b.name))\n\n // Stable ordering: toolkit first (numeric prefix), then project (timestamp prefix).\n return [...toolkit, ...project]\n}\n\n/** Split discovered migrations into applied vs pending by consulting the tracking table. */\nexport async function getMigrationStatus(\n db: PostgresJsDatabase,\n projectRoot: string,\n): Promise<MigrationStatus> {\n await ensureMigrationsTable(db)\n\n const all = await discoverMigrations(projectRoot)\n\n const appliedRows = await db.execute<{ namespace: string; name: string }>(sql`\n SELECT namespace, name FROM _toolkit_migrations\n ORDER BY applied_at ASC\n `)\n const appliedSet = new Set(appliedRows.map((row) => `${row.namespace}:${row.name}`))\n\n const applied: MigrationSource[] = []\n const pending: MigrationSource[] = []\n for (const m of all) {\n if (appliedSet.has(`${m.namespace}:${m.name}`)) applied.push(m)\n else pending.push(m)\n }\n return { applied, pending }\n}\n\n/**\n * Apply every pending migration in order.\n *\n * Each file runs inside its own transaction: the migration's `up()`\n * executes first, then the `_toolkit_migrations` insert commits. If\n * `up()` throws, the transaction rolls back and the whole command\n * aborts — nothing gets half-applied.\n *\n * The `loader` callback is injected so `@murumets-ee/db` itself doesn't\n * depend on a TS-runtime loader. Callers pass a jiti-backed loader from\n * the CLI or their own equivalent.\n */\nexport async function runMigrations(\n db: PostgresJsDatabase,\n projectRoot: string,\n loader: MigrationLoader,\n): Promise<void> {\n await ensureMigrationsTable(db)\n\n const { pending } = await getMigrationStatus(db, projectRoot)\n\n if (pending.length === 0) {\n console.log('No pending migrations')\n return\n }\n\n console.log(`Running ${pending.length} pending migration${pending.length === 1 ? '' : 's'}...`)\n\n for (const migration of pending) {\n console.log(` Applying ${migration.namespace}/${migration.name}...`)\n\n const mod = await loader(migration.path)\n if (typeof mod.up !== 'function') {\n throw new Error(\n `Migration ${migration.namespace}/${migration.name} has no \\`up\\` export. ` +\n 'Every migration file must export an async `up({ db })` function.',\n )\n }\n\n try {\n await db.transaction(async (tx) => {\n await mod.up({ db: tx as PostgresJsDatabase })\n await tx.execute(sql`\n INSERT INTO _toolkit_migrations (namespace, name)\n VALUES (${migration.namespace}, ${migration.name})\n `)\n })\n console.log(` ✓ Applied ${migration.namespace}/${migration.name}`)\n } catch (error) {\n console.error(` ✗ Failed to apply ${migration.namespace}/${migration.name}:`, error)\n throw error\n }\n }\n\n console.log(`Successfully applied ${pending.length} migration${pending.length === 1 ? '' : 's'}`)\n}\n\n/**\n * Roll back the most recent applied migration (or N most recent).\n *\n * Each file's `down()` runs in its own transaction. Files without a\n * `down` export cause the rollback to abort before running anything —\n * we never roll back some but not all of a requested batch.\n */\nexport async function rollbackMigrations(\n db: PostgresJsDatabase,\n projectRoot: string,\n loader: MigrationLoader,\n count = 1,\n): Promise<void> {\n await ensureMigrationsTable(db)\n const { applied } = await getMigrationStatus(db, projectRoot)\n\n if (applied.length === 0) {\n console.log('No applied migrations to roll back')\n return\n }\n\n // Take the last `count` applied, in reverse application order.\n const toRollback = applied.slice(-count).reverse()\n\n // Load all requested modules first and verify every one has `down`.\n const modules = await Promise.all(\n toRollback.map(async (m) => ({ meta: m, mod: await loader(m.path) })),\n )\n for (const { meta, mod } of modules) {\n if (typeof mod.down !== 'function') {\n throw new Error(\n `Migration ${meta.namespace}/${meta.name} has no \\`down\\` export — cannot roll back. ` +\n 'Add an async `down({ db })` function to the migration file.',\n )\n }\n }\n\n for (const { meta, mod } of modules) {\n console.log(` Rolling back ${meta.namespace}/${meta.name}...`)\n try {\n await db.transaction(async (tx) => {\n // biome-ignore lint/style/noNonNullAssertion: guarded above\n await mod.down!({ db: tx as PostgresJsDatabase })\n await tx.execute(sql`\n DELETE FROM _toolkit_migrations\n WHERE namespace = ${meta.namespace} AND name = ${meta.name}\n `)\n })\n console.log(` ✓ Rolled back ${meta.namespace}/${meta.name}`)\n } catch (error) {\n console.error(` ✗ Failed to roll back ${meta.namespace}/${meta.name}:`, error)\n throw error\n }\n }\n\n console.log(\n `Successfully rolled back ${modules.length} migration${modules.length === 1 ? '' : 's'}`,\n )\n}\n","import type { PgTableWithColumns } from 'drizzle-orm/pg-core'\n\n/**\n * Schema registry for storing Drizzle schemas\n * This will be populated by @org/entity when schemas are generated from entity definitions\n *\n * We use PgTableWithColumns<any> because entity table schemas are dynamic (columns\n * not known at compile time) and we need runtime property access (.id, .status, etc.).\n * AnyPgTable won't work here — it doesn't expose column accessors.\n */\n\n// biome-ignore lint/suspicious/noExplicitAny: dynamic table columns require PgTableWithColumns<any> for property access\ntype AnyTable = PgTableWithColumns<any>\n\nexport interface SchemaRegistry {\n register(name: string, schema: AnyTable): void\n get(name: string): AnyTable | undefined\n all(): Record<string, AnyTable>\n has(name: string): boolean\n}\n\nclass SchemaRegistryImpl implements SchemaRegistry {\n private schemas: Map<string, AnyTable> = new Map()\n\n register(name: string, schema: AnyTable): void {\n this.schemas.set(name, schema)\n }\n\n get(name: string): AnyTable | undefined {\n return this.schemas.get(name)\n }\n\n all(): Record<string, AnyTable> {\n const result: Record<string, AnyTable> = {}\n for (const [name, schema] of this.schemas.entries()) {\n result[name] = schema\n }\n return result\n }\n\n has(name: string): boolean {\n return this.schemas.has(name)\n }\n}\n\n/**\n * Global schema registry instance\n * Entities will register their schemas here when they are defined\n */\nexport const schemaRegistry = new SchemaRegistryImpl()\n","/**\n * WhereClause → Drizzle SQL translator.\n *\n * This module is the *only* place in the table layer that touches Drizzle's\n * query operators. All `findOne`/`findMany`/`update`/`delete`/`count` calls\n * funnel their where-objects through `buildWhere()` here. That makes this\n * file the single security-review surface for query construction.\n *\n * Safety properties:\n *\n * 1. **Values are always parameterized.** Every operator passes its value\n * through Drizzle's typed operators (`eq`, `gt`, etc.) which use\n * parameter binding. Raw `sql` template literals are NEVER constructed\n * from caller-supplied values.\n * 2. **Column identifiers are looked up by key.** The `where` object's keys\n * are matched against the table's known column set; unknown keys throw.\n * No dynamic identifier interpolation.\n * 3. **jsonb path segments are whitelist-validated.** Each segment must\n * match `/^[a-zA-Z_][a-zA-Z0-9_-]*$|^\\d+$/`. Anything else throws.\n * 4. **`ilike`/`startsWith` values are escaped.** `%` and `_` in caller-\n * supplied strings are backslash-escaped before wrapping with wildcards\n * so callers cannot accidentally inject pattern characters.\n *\n * @internal\n */\n\nimport {\n and,\n eq,\n getTableColumns,\n gt,\n gte,\n ilike,\n inArray,\n isNotNull,\n isNull,\n lt,\n lte,\n ne,\n not,\n notInArray,\n or,\n type SQL,\n type SQLWrapper,\n sql,\n} from 'drizzle-orm'\nimport type { AnyPgColumn, PgTable } from 'drizzle-orm/pg-core'\nimport type { ColumnFactory, ColumnKind, WhereClause } from './types.js'\n\n/** Identifier shape allowed for jsonb path segments. */\nconst JSONB_SEGMENT_RE = /^[a-zA-Z_][a-zA-Z0-9_-]*$|^\\d+$/\n\n/** Reserved meta-keys in a where-clause object. */\nconst META_KEYS = new Set(['$and', '$or', '$not'])\n\n/**\n * Maximum nesting depth for `$and` / `$or` / `$not` recursion.\n *\n * Real production where-clauses are 2-3 levels deep at most. The cap\n * exists to prevent stack-overflow DoS attacks if anyone ever passes\n * untrusted user input through the where-builder (which the design\n * docs forbid, but warnings are not code).\n *\n * Bumping this is fine if a real consumer needs it — but think hard\n * about why a query needs more than 16 levels of nesting first.\n */\nconst MAX_WHERE_DEPTH = 16\n\n/** Operator keys recognised in a column-operator object. */\nconst OP_KEYS = new Set([\n 'eq',\n 'ne',\n 'in',\n 'notIn',\n 'gt',\n 'gte',\n 'lt',\n 'lte',\n 'isNull',\n 'isNotNull',\n 'ilike',\n 'startsWith',\n])\n\n/**\n * A custom error thrown by the where-builder for any caller-side mistake.\n *\n * Catching `WhereBuilderError` lets the table client surface a 400-style\n * error to its caller without conflating it with database errors.\n */\nexport class WhereBuilderError extends Error {\n constructor(message: string) {\n super(`WhereBuilder: ${message}`)\n this.name = 'WhereBuilderError'\n }\n}\n\n/**\n * Escape `%` and `_` (and the escape char `\\`) in a caller-supplied LIKE\n * pattern value. The result is safe to wrap with our own wildcards without\n * leaking pattern syntax to the caller.\n */\nfunction escapeLikeValue(value: string): string {\n return value.replace(/[\\\\%_]/g, '\\\\$&')\n}\n\n/**\n * Resolve a where-clause key to a Drizzle column reference or a jsonb path\n * SQL expression.\n *\n * - Plain column key: `'userId'` → `table.userId` (typed Drizzle column)\n * - jsonb dotted path: `'metadata.author.name'` → `metadata->'author'->>'name'`\n *\n * The parent column for any dotted path MUST be of kind `jsonb`. The\n * column kind is read from the column factory metadata stored at table\n * definition time.\n */\nfunction resolveTarget(\n table: PgTable,\n columnKinds: Readonly<Record<string, ColumnKind>>,\n key: string,\n): SQLWrapper {\n const cols = getTableColumns(table)\n if (!key.includes('.')) {\n const col = cols[key]\n if (!col) {\n throw new WhereBuilderError(`unknown column \"${key}\"`)\n }\n return col\n }\n\n const segments = key.split('.')\n const [columnName, ...path] = segments\n if (!columnName || path.length === 0) {\n throw new WhereBuilderError(`malformed dotted key \"${key}\"`)\n }\n const kind = columnKinds[columnName]\n if (!kind) {\n throw new WhereBuilderError(`unknown column \"${columnName}\" in dotted key \"${key}\"`)\n }\n if (kind !== 'jsonb') {\n throw new WhereBuilderError(\n `dotted-path access is only supported on jsonb columns, but \"${columnName}\" is ${kind}`,\n )\n }\n for (const seg of path) {\n if (!JSONB_SEGMENT_RE.test(seg)) {\n throw new WhereBuilderError(\n `invalid jsonb path segment \"${seg}\" — must match [a-zA-Z_][a-zA-Z0-9_-]* or be a non-negative integer`,\n )\n }\n }\n\n const col = cols[columnName]\n if (!col) {\n throw new WhereBuilderError(`unknown column \"${columnName}\"`)\n }\n\n // Build: col->'a'->'b'->>'c' (intermediate -> for nested object access,\n // final ->> for text extraction so comparison/ilike operators work).\n //\n // String segments are parameterized normally — Postgres binds them as\n // text and `->'key'` does object access.\n //\n // Numeric segments are inlined as raw SQL because parameter binding\n // doesn't reliably dispatch to the array-index variant of `->`/`->>`\n // (Postgres often binds bigint, but the array-index operator overload\n // requires a literal int). The segment is already whitelist-validated\n // as `^\\d+$`, so inlining is safe by construction. Re-validate here as\n // belt-and-braces.\n let target: SQL = sql`${col}`\n for (let i = 0; i < path.length; i++) {\n const seg = path[i]\n const isLast = i === path.length - 1\n if (/^\\d+$/.test(seg)) {\n // Defence-in-depth re-check: must be pure digits, no leading zeros\n // beyond a single 0, length capped to avoid runaway integers.\n if (seg.length > 10) {\n throw new WhereBuilderError(`jsonb path index \"${seg}\" is too large`)\n }\n const literal = sql.raw(seg)\n target = isLast ? sql`${target}->>${literal}` : sql`${target}->${literal}`\n } else {\n target = isLast ? sql`${target}->>${seg}` : sql`${target}->${seg}`\n }\n }\n return target\n}\n\n/**\n * Apply a single column-operator object (or shorthand value) to a target\n * (column or jsonb-path expression). Returns the corresponding Drizzle SQL\n * predicate.\n */\nfunction applyOperator(target: SQLWrapper, value: unknown): SQL | undefined {\n // Shorthand: bare value → eq, bare null → isNull\n if (value === null) {\n return isNull(target as AnyPgColumn)\n }\n if (\n typeof value === 'string' ||\n typeof value === 'number' ||\n typeof value === 'boolean' ||\n typeof value === 'bigint' ||\n value instanceof Date\n ) {\n return eq(target as AnyPgColumn, value as never)\n }\n if (Array.isArray(value)) {\n throw new WhereBuilderError(\n 'arrays must be wrapped in an operator (e.g. { in: [...] }) — bare arrays are not allowed',\n )\n }\n if (typeof value !== 'object') {\n throw new WhereBuilderError(`unsupported value type: ${typeof value}`)\n }\n\n const obj = value as Record<string, unknown>\n const opKeys = Object.keys(obj)\n if (opKeys.length === 0) {\n throw new WhereBuilderError('empty operator object')\n }\n\n // Multiple operators in one object are AND-ed together\n const parts: SQL[] = []\n for (const op of opKeys) {\n if (!OP_KEYS.has(op)) {\n throw new WhereBuilderError(`unknown operator \"${op}\"`)\n }\n const v = obj[op]\n switch (op) {\n case 'eq':\n parts.push(eq(target as AnyPgColumn, v as never))\n break\n case 'ne':\n parts.push(ne(target as AnyPgColumn, v as never))\n break\n case 'in':\n if (!Array.isArray(v)) {\n throw new WhereBuilderError('\"in\" operator requires an array')\n }\n if (v.length === 0) {\n // IN () is invalid SQL; an empty IN should match nothing\n parts.push(sql`false`)\n } else {\n parts.push(inArray(target as AnyPgColumn, v as never[]))\n }\n break\n case 'notIn':\n if (!Array.isArray(v)) {\n throw new WhereBuilderError('\"notIn\" operator requires an array')\n }\n if (v.length === 0) {\n // NOT IN () matches everything\n parts.push(sql`true`)\n } else {\n parts.push(notInArray(target as AnyPgColumn, v as never[]))\n }\n break\n case 'gt':\n parts.push(gt(target as AnyPgColumn, v as never))\n break\n case 'gte':\n parts.push(gte(target as AnyPgColumn, v as never))\n break\n case 'lt':\n parts.push(lt(target as AnyPgColumn, v as never))\n break\n case 'lte':\n parts.push(lte(target as AnyPgColumn, v as never))\n break\n case 'isNull':\n if (v !== true) {\n throw new WhereBuilderError('\"isNull\" operator must be `true`')\n }\n parts.push(isNull(target as AnyPgColumn))\n break\n case 'isNotNull':\n if (v !== true) {\n throw new WhereBuilderError('\"isNotNull\" operator must be `true`')\n }\n parts.push(isNotNull(target as AnyPgColumn))\n break\n case 'ilike': {\n if (typeof v !== 'string') {\n throw new WhereBuilderError('\"ilike\" operator requires a string')\n }\n // Caller's value is treated as a literal string — outer wildcards\n // come from the operator semantics, not from caller-supplied %.\n // We escape any wildcard chars in the value to prevent accidental\n // pattern injection.\n parts.push(ilike(target as AnyPgColumn, `%${escapeLikeValue(v)}%`))\n break\n }\n case 'startsWith': {\n if (typeof v !== 'string') {\n throw new WhereBuilderError('\"startsWith\" operator requires a string')\n }\n parts.push(ilike(target as AnyPgColumn, `${escapeLikeValue(v)}%`))\n break\n }\n }\n }\n\n if (parts.length === 0) return undefined\n if (parts.length === 1) return parts[0]\n return and(...parts)\n}\n\n/**\n * Translate a {@link WhereClause} object into a single Drizzle SQL\n * expression suitable for `db.select(...).where(here)`.\n *\n * Returns `undefined` for an empty clause (no filtering). Callers that\n * require a non-empty where (update, delete) should reject `undefined`\n * before invoking the SQL.\n *\n * @param depth - internal recursion depth, used to enforce\n * {@link MAX_WHERE_DEPTH}. External callers should not pass this.\n * @throws {@link WhereBuilderError} for any malformed clause, unknown\n * column, invalid operator, invalid jsonb path segment, or recursion\n * deeper than {@link MAX_WHERE_DEPTH}.\n */\nexport function buildWhere<TCols extends Record<string, ColumnFactory>>(\n table: PgTable,\n columnKinds: Readonly<Record<string, ColumnKind>>,\n clause: WhereClause<TCols> | undefined,\n depth = 0,\n): SQL | undefined {\n if (!clause) return undefined\n if (typeof clause !== 'object' || Array.isArray(clause)) {\n throw new WhereBuilderError('where clause must be a plain object')\n }\n if (depth > MAX_WHERE_DEPTH) {\n throw new WhereBuilderError(\n `where clause exceeds maximum nesting depth of ${MAX_WHERE_DEPTH} — ` +\n 'real queries should not need this much nesting; if yours does, ' +\n 'reconsider whether it should be expressed differently',\n )\n }\n\n const parts: SQL[] = []\n\n for (const key of Object.keys(clause)) {\n const value = (clause as Record<string, unknown>)[key]\n\n if (key === '$and') {\n if (!Array.isArray(value)) {\n throw new WhereBuilderError('$and must be an array')\n }\n if (value.length === 0) {\n // $and: [] is almost certainly a bug — surface it loudly,\n // matching the $or: [] behaviour for consistency.\n throw new WhereBuilderError('$and must contain at least one clause')\n }\n const subParts = (value as WhereClause<TCols>[])\n .map((c) => buildWhere(table, columnKinds, c, depth + 1))\n .filter((p): p is SQL => p !== undefined)\n if (subParts.length > 0) parts.push(and(...subParts) as SQL)\n continue\n }\n\n if (key === '$or') {\n if (!Array.isArray(value)) {\n throw new WhereBuilderError('$or must be an array')\n }\n if (value.length === 0) {\n // $or: [] would match nothing; reject explicitly to surface the bug\n throw new WhereBuilderError('$or must contain at least one clause')\n }\n const subParts = (value as WhereClause<TCols>[])\n .map((c) => buildWhere(table, columnKinds, c, depth + 1))\n .filter((p): p is SQL => p !== undefined)\n if (subParts.length > 0) parts.push(or(...subParts) as SQL)\n continue\n }\n\n if (key === '$not') {\n if (!isNonEmptyWhere(value)) {\n // $not: {} or $not: undefined is almost certainly a bug — the\n // negation of \"match everything\" or \"no filter\" produces\n // surprising semantics.\n throw new WhereBuilderError('$not must contain a non-empty clause')\n }\n const sub = buildWhere(table, columnKinds, value as WhereClause<TCols>, depth + 1)\n if (sub) parts.push(not(sub))\n continue\n }\n\n if (META_KEYS.has(key)) {\n // unreachable — guarded above\n continue\n }\n\n const target = resolveTarget(table, columnKinds, key)\n const predicate = applyOperator(target, value)\n if (predicate) parts.push(predicate)\n }\n\n if (parts.length === 0) return undefined\n if (parts.length === 1) return parts[0]\n return and(...parts) as SQL\n}\n\n/**\n * Returns true if a {@link WhereClause} produces a non-trivial filter (at\n * least one column predicate or sub-clause). Useful for rejecting empty\n * `where` arguments to `update`/`delete`/`updateMany`/`deleteMany`.\n *\n * Note: this does not check whether `buildWhere()` would actually emit SQL\n * for the clause — it just checks that the user supplied something.\n */\nexport function isNonEmptyWhere(clause: unknown): boolean {\n if (clause === undefined || clause === null) return false\n if (typeof clause !== 'object') return false\n if (Array.isArray(clause)) return false\n return Object.keys(clause as object).length > 0\n}\n","/**\n * Generic typed CRUD client for `defineTable` tables.\n *\n * Single-table operations only — no joins, no aggregates beyond `count`,\n * no hooks. For typed multi-table queries, callers reach for the\n * underlying `table` object exposed by `defineTable` and write plain\n * Drizzle. That's the one sanctioned escape valve.\n *\n * Hard rules baked into the client (not just at the SQL level):\n *\n * - `update`, `updateMany`, `delete`, `deleteMany` REQUIRE a non-empty\n * `where` clause. Empty wheres throw `TableClientError` at the client,\n * not just \"0 rows updated\".\n * - `findMany.limit` defaults to {@link DEFAULT_LIMIT} (100) and is hard-\n * capped at {@link MAX_LIMIT} (1000). Larger values get clamped and a\n * warning is logged.\n * - `insertMany` is capped at {@link MAX_BATCH} (1000) per call to keep a\n * single round trip from monopolising the connection.\n * - All write methods return the affected row(s). No silent updates.\n *\n * For more advanced operations (`claim()`, `aggregate()`,\n * `countEstimate()`) see follow-up PRs in the `feat/define-table` series.\n */\n\nimport {\n and as andFn,\n count as countFn,\n eq,\n getTableColumns,\n getTableName,\n type InferInsertModel,\n type InferSelectModel,\n inArray,\n isNotNull,\n type SQL,\n sql,\n} from 'drizzle-orm'\nimport {\n type AnyPgColumn,\n type PgTable,\n type PgUpdateSetSource,\n pgTable,\n real,\n varchar,\n} from 'drizzle-orm/pg-core'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport type { ColumnFactory, ColumnKind, ColumnValue, Row, WhereClause } from './types.js'\nimport { buildWhere, isNonEmptyWhere, WhereBuilderError } from './where-builder.js'\n\n/**\n * Allowed shape for a SQL identifier — used to validate caller-supplied\n * `aggregate()` output aliases before they are emitted as `... AS \"alias\"`.\n * Drizzle quotes identifiers, but accepting only the standard shape keeps\n * the surface predictable and removes implicit reliance on the quoting.\n */\nconst SQL_IDENTIFIER_RE = /^[a-zA-Z_][a-zA-Z0-9_]*$/\n\n/**\n * Minimal Drizzle table definition for Postgres `pg_class` system catalog.\n * Used by `countEstimate()` to query row count statistics without raw SQL.\n */\nconst pgClassTable = pgTable('pg_class', {\n relname: varchar('relname', { length: 63 }).notNull(),\n reltuples: real('reltuples').notNull(),\n})\n\n/**\n * Default page size for `findMany` when no `limit` is supplied.\n *\n * Chosen as a safe upper bound for \"show me a page of stuff\" — anything\n * above this should be paginated explicitly.\n */\nexport const DEFAULT_LIMIT = 100\n\n/**\n * Hard cap on `findMany.limit`. Caller-supplied values above this are\n * clamped and a warning is emitted via `console.warn`.\n */\nexport const MAX_LIMIT = 1000\n\n/**\n * Hard cap on `insertMany.values.length` per single call.\n */\nexport const MAX_BATCH = 1000\n\n/**\n * A custom error thrown by the table client for any caller-side mistake\n * (empty where on update/delete, batch too large, malformed where, …).\n *\n * Database errors are NOT wrapped — they propagate as-is from postgres-js\n * so callers can match on Postgres error codes.\n */\nexport class TableClientError extends Error {\n constructor(message: string) {\n super(`TableClient: ${message}`)\n this.name = 'TableClientError'\n }\n}\n\n/**\n * Order-by spec accepted by `findMany`.\n */\nexport interface OrderBySpec<TCols extends Record<string, ColumnFactory>> {\n column: keyof TCols & string\n dir?: 'asc' | 'desc'\n}\n\n/**\n * `findMany` options.\n */\nexport interface FindManyOptions<TCols extends Record<string, ColumnFactory>> {\n where?: WhereClause<TCols>\n orderBy?: OrderBySpec<TCols>[]\n limit?: number\n offset?: number\n}\n\n/**\n * Options for the `claim()` atomic locking operation.\n *\n * @see {@link TableClient.claim}\n */\nexport interface ClaimOptions<\n TCols extends Record<string, ColumnFactory>,\n TTable extends PgTable = PgTable,\n> {\n /** Filter for rows eligible to be claimed. Must be non-empty. */\n where: WhereClause<TCols>\n /** Literal values to SET on claimed rows. */\n set?: Partial<InferInsertModel<TTable>>\n /** Raw SQL expressions to SET (e.g. `{ attempts: sql\\`attempts + 1\\` }`). */\n setSql?: Record<string, SQL>\n /** ORDER BY for the subselect — controls claim priority. */\n orderBy?: OrderBySpec<TCols>[]\n /** Maximum number of rows to claim. Must be 1..MAX_LIMIT. */\n limit: number\n}\n\n/**\n * Options for the `tryClaim()` atomic insert-or-replace-if operation.\n *\n * @see {@link TableClient.tryClaim}\n */\nexport interface TryClaimOptions<\n TCols extends Record<string, ColumnFactory>,\n TTable extends PgTable = PgTable,\n> {\n /**\n * Unique-constraint column(s) that should trigger the conflict path.\n *\n * Single string for a single-column unique constraint; array for a\n * composite unique. Must reference a real column on this table.\n */\n target: (keyof TCols & string) | (keyof TCols & string)[]\n /**\n * Predicate that decides whether an existing conflicting row may be\n * **replaced**. Required and must be non-empty.\n *\n * - On INSERT (no conflict): always wins; predicate is irrelevant.\n * - On CONFLICT + predicate matches: row is replaced; result is\n * `{ acquired: true, row }`.\n * - On CONFLICT + predicate does NOT match: existing row is preserved;\n * result is `{ acquired: false, row }` (where `row` is the surviving\n * existing row).\n *\n * Use `$or` to express composite re-entrancy rules — e.g.\n * `{ $or: [{ expiresAt: { lt: now } }, { lockedBy: callerId }] }` for\n * \"replace if expired, or if I already hold it\".\n */\n replaceWhen: WhereClause<TCols>\n /**\n * Patch applied on the replace path. Defaults to the full `values`\n * payload (last-write-wins on the columns being inserted).\n */\n set?: Partial<InferInsertModel<TTable>>\n}\n\n/**\n * Specification for a single aggregate function.\n */\nexport type AggregateSpec<TCols extends Record<string, ColumnFactory>> =\n | { fn: 'count' }\n | { fn: 'count'; column: keyof TCols & string }\n | { fn: 'sum' | 'avg' | 'min' | 'max'; column: keyof TCols & string }\n\n/**\n * Options for the `aggregate()` method.\n */\nexport interface AggregateOptions<TCols extends Record<string, ColumnFactory>> {\n /** Named aggregate expressions. Each key becomes an output column. */\n select: Record<string, AggregateSpec<TCols>>\n /** Columns to GROUP BY. Included automatically in the result. */\n groupBy?: (keyof TCols & string)[]\n /** Filter rows before aggregation (WHERE). */\n where?: WhereClause<TCols>\n /** Order results. Can reference both grouped columns and aliases. */\n orderBy?: OrderBySpec<TCols>[]\n /** Limit the number of groups returned. */\n limit?: number\n}\n\n/**\n * Generic CRUD client. Constructed by `defineTable` — never instantiated\n * directly by callers.\n *\n * The same instance shape is exposed both at the top level (using the\n * package's main `db` connection) and inside a `transaction()` callback\n * (using the transactional `tx` handle), so user code is identical\n * regardless of transactional context.\n *\n * @template TCols The columns record passed to `defineTable`. Drives the\n * where-builder DSL and the `Row<TCols>` / `InsertRow<TCols>` shapes.\n * @template TTable The underlying Drizzle pgTable type, inferred from the\n * table value passed to `new TableClient(...)`. Carrying this through\n * lets `.insert().values(...)`, `.update().set(...)`, and `.returning()`\n * use Drizzle's own `InferInsertModel` / `InferSelectModel` instead of\n * falling back to `as never` casts.\n */\nexport class TableClient<\n TCols extends Record<string, ColumnFactory>,\n TTable extends PgTable = PgTable,\n> {\n /**\n * @internal — instances are created by `defineTable()`.\n */\n constructor(\n /** The Drizzle table object. Exposed via `defineTable().table` for typed JOINs. */\n public readonly table: TTable,\n /** Per-column kind metadata. Used by the where-builder for jsonb path validation. */\n private readonly columnKinds: Readonly<Record<string, ColumnKind>>,\n /**\n * The active database handle. May be a top-level `PostgresJsDatabase`\n * or a transaction handle of compatible shape — the client doesn't\n * distinguish.\n */\n private readonly db: PostgresJsDatabase,\n /** Primary key column names. Required by `claim()`. Empty for tables without a declared PK. */\n private readonly primaryKeyColumns: readonly string[] = [],\n ) {}\n\n /**\n * The table typed as the non-generic `PgTable` base.\n *\n * Drizzle's `.from()` (select) uses a conditional type\n * `TableLikeHasEmptySelection<TTable>` that TypeScript cannot evaluate\n * against a bounded generic `TTable extends PgTable` — it can only\n * resolve when the argument is the concrete base `PgTable`. `.insert()`,\n * `.update()`, and `.delete()` have simpler signatures (`<T extends\n * PgTable>(t: T)`) that DO accept the generic directly, so those call\n * sites keep `this.table` and still get `InferInsertModel<TTable>` /\n * `InferSelectModel<TTable>` inference for `.values()`, `.set()`, and\n * `.returning()`.\n */\n private get _selectTable(): PgTable {\n return this.table\n }\n\n // -------------------------------------------------------------------------\n // Private helpers\n //\n // These collapse patterns that repeated 4-7 times across the public methods\n // (column lookup, ORDER BY emission, where-clause non-emptiness checks,\n // patch-key validation). Keep them at the top of the file so future\n // methods know where to wire in.\n // -------------------------------------------------------------------------\n\n /**\n * Look up a Drizzle column by its JS property name, throwing\n * `TableClientError` with the supplied `context` if no such column exists.\n * Wraps `getTableColumns(this.table)` — uses Drizzle's typed column map\n * instead of an indexed cast on the table object.\n */\n private getColumnOrThrow(name: string, context: string): AnyPgColumn {\n const col = getTableColumns(this.table)[name]\n if (!col) {\n throw new TableClientError(`${context}: unknown column \"${name}\"`)\n }\n return col\n }\n\n /**\n * Build an array of ORDER BY SQL expressions from an `OrderBySpec[]`.\n * Used by `findMany`, `distinct`, `claim`, and `aggregate`.\n */\n private buildOrderBy(specs: readonly OrderBySpec<TCols>[], context: string): SQL[] {\n return specs.map((spec) => {\n const col = this.getColumnOrThrow(spec.column, `${context}: orderBy`)\n return spec.dir === 'desc' ? sql`${col} DESC` : sql`${col} ASC`\n })\n }\n\n /**\n * Throw `TableClientError` if the where clause has no top-level keys.\n * Used by every method that mutates or reads-by-key (`update`, `delete`,\n * `findOne`, `exists`, `claim`, ...) — none of them have a sane\n * \"everything\" path, so empty wheres are always a caller mistake.\n */\n private requireNonEmptyWhere(where: WhereClause<TCols>, method: string): void {\n if (!isNonEmptyWhere(where)) {\n throw new TableClientError(`${method} requires a non-empty where clause`)\n }\n }\n\n /**\n * Verify every key in `patch` names a real column on this table. Used by\n * `update`, `updateMany`, `upsert`, and `claim` so a typo'd or hostile\n * patch key surfaces a clear error instead of being silently dropped by\n * Drizzle's set logic.\n */\n private validateColumnKeys(keys: readonly string[], method: string): void {\n if (keys.length === 0) return\n const cols = getTableColumns(this.table)\n for (const key of keys) {\n if (!Object.hasOwn(cols, key)) {\n throw new TableClientError(`${method}: unknown column \"${key}\"`)\n }\n }\n }\n\n // -------------------------------------------------------------------------\n // Reads\n // -------------------------------------------------------------------------\n\n /**\n * Find a single row matching the given where clause, or `null` if no row\n * matches. Throws if the where is empty.\n *\n * @example\n * ```ts\n * await readState.client.findOne({ ticketId: 't_1', userId: 'u_1' })\n * ```\n */\n async findOne(where: WhereClause<TCols>): Promise<Row<TCols> | null> {\n this.requireNonEmptyWhere(where, 'findOne')\n const condition = this.buildWhereOrThrow(where)\n const rows = await this.db.select().from(this._selectTable).where(condition).limit(1)\n return (rows[0] as Row<TCols> | undefined) ?? null\n }\n\n /**\n * Find multiple rows. Pagination, ordering, and filtering are all\n * optional but `limit` is hard-capped at {@link MAX_LIMIT}.\n *\n * @example\n * ```ts\n * await jobs.client.findMany({\n * where: { status: 'pending', runAt: { lte: new Date() } },\n * orderBy: [{ column: 'priority', dir: 'desc' }, { column: 'runAt', dir: 'asc' }],\n * limit: 25,\n * })\n * ```\n */\n async findMany(options: FindManyOptions<TCols> = {}): Promise<Row<TCols>[]> {\n // Validate all caller-supplied options up front, before touching the\n // database. This guarantees TableClientError is thrown for input\n // mistakes regardless of db state — important for unit testability\n // and for failing fast on misuse.\n const limit = this.validateLimit(options.limit)\n if (options.offset !== undefined) {\n if (!Number.isInteger(options.offset) || options.offset < 0) {\n throw new TableClientError(`offset must be a non-negative integer, got ${options.offset}`)\n }\n }\n const orderExprs =\n options.orderBy && options.orderBy.length > 0\n ? this.buildOrderBy(options.orderBy, 'findMany')\n : undefined\n const condition = options.where ? this.buildWhereOrThrow(options.where) : undefined\n\n let query = this.db.select().from(this._selectTable).$dynamic()\n if (condition) query = query.where(condition)\n if (orderExprs) query = query.orderBy(...orderExprs)\n query = query.limit(limit)\n if (options.offset !== undefined) query = query.offset(options.offset)\n\n const rows = await query\n return rows as Row<TCols>[]\n }\n\n /**\n * Count rows matching the where clause. Returns an exact count via\n * `SELECT COUNT(*)`. For estimated/cached counts on large tables, see\n * `countEstimate()` and `countCached()` (added in a follow-up PR).\n *\n * @example\n * ```ts\n * const open = await jobs.client.count({ status: 'pending' })\n * ```\n */\n async count(where?: WhereClause<TCols>): Promise<number> {\n const condition = where ? this.buildWhereOrThrow(where) : undefined\n let query = this.db.select({ value: countFn() }).from(this._selectTable).$dynamic()\n if (condition) query = query.where(condition)\n const rows = await query\n return Number(rows[0]?.value ?? 0)\n }\n\n /**\n * Returns `true` if at least one row matches the where clause. Always\n * uses `LIMIT 1` and short-circuits — cheaper than `count() > 0` on\n * large tables.\n */\n async exists(where: WhereClause<TCols>): Promise<boolean> {\n this.requireNonEmptyWhere(where, 'exists')\n const condition = this.buildWhereOrThrow(where)\n const rows = await this.db\n .select({ one: sql`1` })\n .from(this._selectTable)\n .where(condition)\n .limit(1)\n return rows.length > 0\n }\n\n /**\n * Return unique non-null values for a single column.\n *\n * Null values are excluded by default — pass `includeNull: true` to\n * include them. Results can be filtered with `where` and ordered with\n * `orderBy` (ascending or descending on the distinct column).\n *\n * @example Get distinct entity types from the audit log\n * ```ts\n * const types = await audit.client.distinct('entityType', { orderBy: 'asc' })\n * ```\n *\n * @example Distinct with a filter\n * ```ts\n * const actions = await audit.client.distinct('action', {\n * where: { userId: 'u_1' },\n * orderBy: 'asc',\n * })\n * ```\n */\n async distinct<K extends keyof TCols & string>(\n column: K,\n options?: {\n where?: WhereClause<TCols>\n orderBy?: 'asc' | 'desc'\n /** Include null values in the result. Defaults to `false`. */\n includeNull?: boolean\n },\n ): Promise<ColumnValue<TCols[K]>[]> {\n const col = this.getColumnOrThrow(column, 'distinct')\n\n const conditions: SQL[] = []\n if (!options?.includeNull) {\n conditions.push(isNotNull(col))\n }\n if (options?.where) {\n conditions.push(this.buildWhereOrThrow(options.where))\n }\n\n let query = this.db.selectDistinct({ value: col }).from(this._selectTable).$dynamic()\n\n if (conditions.length === 1) {\n query = query.where(conditions[0])\n } else if (conditions.length > 1) {\n query = query.where(andFn(...conditions))\n }\n\n if (options?.orderBy) {\n const orderExpr = options.orderBy === 'desc' ? sql`${col} DESC` : sql`${col} ASC`\n query = query.orderBy(orderExpr)\n }\n\n const rows = await query\n return rows.map((r) => (r as { value: unknown }).value) as ColumnValue<TCols[K]>[]\n }\n\n // -------------------------------------------------------------------------\n // Writes\n // -------------------------------------------------------------------------\n\n /**\n * Insert a single row. Returns the inserted row (server-generated\n * defaults populated).\n */\n async insert(values: InferInsertModel<TTable>): Promise<Row<TCols>> {\n const rows = await this.db.insert(this.table).values(values).returning()\n return rows[0] as Row<TCols>\n }\n\n /**\n * Upsert: insert a row, or update an existing row if a unique constraint\n * conflict occurs.\n *\n * `target` names the column(s) whose unique constraint should trigger\n * the conflict path. `set` is the patch applied on conflict — when\n * omitted, the conflict path applies the same `values` (i.e. \"last\n * write wins\").\n *\n * `setWhere` adds a conditional WHERE to the conflict update path. If\n * the condition does not match, the update is skipped and the existing\n * row is returned unchanged (via a fallback SELECT). This enables\n * patterns like \"only update if the new version is higher than the\n * existing one\".\n *\n * @example Idempotent \"mark as read\" tracking\n * ```ts\n * await readState.client.upsert(\n * { ticketId, userId, lastReadAt: new Date() },\n * { target: ['ticketId', 'userId'] },\n * )\n * ```\n *\n * @example Conditional update — only bump if newer\n * ```ts\n * await state.client.upsert(\n * { key: 'sync', version: 5, data: '...' },\n * { target: 'key', setWhere: { version: { lt: 5 } } },\n * )\n * ```\n */\n async upsert(\n values: InferInsertModel<TTable>,\n opts: {\n target: (keyof TCols & string) | (keyof TCols & string)[]\n set?: Partial<InferInsertModel<TTable>>\n /** Conditional WHERE on the conflict update path. Empty objects are ignored. */\n setWhere?: WhereClause<TCols>\n },\n ): Promise<Row<TCols>> {\n const targetKeys = Array.isArray(opts.target) ? opts.target : [opts.target]\n if (targetKeys.length === 0) {\n throw new TableClientError('upsert requires at least one target column')\n }\n const targetCols = targetKeys.map((k) => this.getColumnOrThrow(k, 'upsert target'))\n\n // When `set` is omitted, fall back to applying the same values on\n // conflict — this is the natural \"last write wins\" semantics that\n // covers the common upsert case.\n const setPatch: Partial<InferInsertModel<TTable>> =\n opts.set ?? (values as Partial<InferInsertModel<TTable>>)\n if (opts.set) {\n this.validateColumnKeys(Object.keys(opts.set), 'upsert set')\n }\n\n // Build the optional conditional WHERE for the conflict update path.\n // Empty `setWhere: {}` is treated as \"no condition\" (not an error).\n const setWhereCondition =\n opts.setWhere && isNonEmptyWhere(opts.setWhere)\n ? this.buildWhereOrThrow(opts.setWhere)\n : undefined\n\n const rows = await this.db\n .insert(this.table)\n .values(values)\n .onConflictDoUpdate({\n target: targetCols,\n set: setPatch,\n ...(setWhereCondition ? { setWhere: setWhereCondition } : {}),\n })\n .returning()\n\n if (rows.length > 0) {\n return rows[0] as Row<TCols>\n }\n\n // setWhere blocked the update — Postgres returns 0 rows from RETURNING.\n // Fall back to reading the existing row by the target columns.\n const where: Record<string, unknown> = {}\n for (const k of targetKeys) {\n where[k] = (values as Record<string, unknown>)[k]\n }\n const existing = await this.findOne(where as WhereClause<TCols>)\n if (!existing) {\n // Reachable when another connection deletes the conflicting row\n // between the failed conditional UPDATE and this SELECT. Rare in\n // practice, but the message must not lie — callers that care need\n // to distinguish \"race during upsert\" from invariant violations.\n throw new TableClientError(\n 'upsert with setWhere: conflicting row was deleted concurrently — retry the operation',\n )\n }\n return existing as Row<TCols>\n }\n\n /**\n * Atomically claim a row by either inserting it or replacing an existing\n * conflicting row that satisfies `replaceWhen`. The single SQL statement\n * makes this race-free — concurrent callers cannot both observe an empty\n * slot and both INSERT.\n *\n * Distinct from {@link upsert}:\n * - `upsert` always wins on conflict (or hits its `setWhere` block and\n * silently falls back to the existing row). It returns a single\n * `Row<TCols>` and the caller cannot tell which path fired.\n * - `tryClaim` returns a discriminated union — the caller knows whether\n * they took the slot or someone else still holds it.\n *\n * Use case: leasing/locking primitives where the caller must distinguish\n * \"I now hold this lock\" from \"someone else holds it, here is who\".\n *\n * @example Acquire a lock, replacing only if expired\n * ```ts\n * const result = await locks.tryClaim(\n * { entityId, lockedBy: me, expiresAt: in5Min },\n * {\n * target: 'entityId',\n * replaceWhen: { expiresAt: { lt: now } },\n * },\n * )\n * if (result.acquired) {\n * // I have the lock\n * } else {\n * // result.row.lockedBy holds it (and lock is fresh)\n * }\n * ```\n *\n * @example Re-entrant lock (claim if expired, or if I already hold it)\n * ```ts\n * await locks.tryClaim(\n * { entityId, lockedBy: me, expiresAt: in5Min },\n * {\n * target: 'entityId',\n * replaceWhen: { $or: [{ expiresAt: { lt: now } }, { lockedBy: me }] },\n * },\n * )\n * ```\n */\n async tryClaim(\n values: InferInsertModel<TTable>,\n opts: TryClaimOptions<TCols, TTable>,\n ): Promise<{ acquired: true; row: Row<TCols> } | { acquired: false; row: Row<TCols> }> {\n const targetKeys = Array.isArray(opts.target) ? opts.target : [opts.target]\n if (targetKeys.length === 0) {\n throw new TableClientError('tryClaim requires at least one target column')\n }\n const targetCols = targetKeys.map((k) => this.getColumnOrThrow(k, 'tryClaim target'))\n\n const setPatch: Partial<InferInsertModel<TTable>> =\n opts.set ?? (values as Partial<InferInsertModel<TTable>>)\n if (opts.set) {\n this.validateColumnKeys(Object.keys(opts.set), 'tryClaim set')\n }\n\n if (!isNonEmptyWhere(opts.replaceWhen)) {\n throw new TableClientError('tryClaim requires a non-empty replaceWhen clause')\n }\n const setWhereCondition = this.buildWhereOrThrow(opts.replaceWhen)\n\n const rows = await this.db\n .insert(this.table)\n .values(values)\n .onConflictDoUpdate({\n target: targetCols,\n set: setPatch,\n setWhere: setWhereCondition,\n })\n .returning()\n\n if (rows.length > 0) {\n return { acquired: true, row: rows[0] as Row<TCols> }\n }\n\n // Conflict + replaceWhen blocked the update — the existing row is\n // preserved. Read it back so the caller learns who holds the slot.\n const where: Record<string, unknown> = {}\n for (const k of targetKeys) {\n where[k] = (values as Record<string, unknown>)[k]\n }\n const existing = await this.findOne(where as WhereClause<TCols>)\n if (!existing) {\n // Reachable when another caller deletes the conflicting row\n // between the failed conditional UPDATE and this SELECT (e.g.\n // for the lock primitive: a `releaseLock` lands in the gap).\n // Rare but real; surface it honestly so observability tracks\n // races vs invariant breaks.\n throw new TableClientError(\n 'tryClaim: conflicting row was deleted concurrently — retry the operation',\n )\n }\n return { acquired: false, row: existing }\n }\n\n /**\n * Insert many rows in a single statement. Capped at {@link MAX_BATCH}.\n * Returns all inserted rows in input order.\n */\n async insertMany(values: InferInsertModel<TTable>[]): Promise<Row<TCols>[]> {\n if (!Array.isArray(values)) {\n throw new TableClientError('insertMany requires an array of rows')\n }\n if (values.length === 0) return []\n if (values.length > MAX_BATCH) {\n throw new TableClientError(`insertMany batch too large: ${values.length} > ${MAX_BATCH}`)\n }\n const rows = await this.db.insert(this.table).values(values).returning()\n return rows as Row<TCols>[]\n }\n\n /**\n * Update **at most one** row matching the where clause. Returns the\n * updated row, or `null` if no row matched.\n *\n * Throws `TableClientError` if:\n * - the where is empty\n * - the where matches more than one row\n *\n * The multi-row guard exists to prevent silent bulk updates from a\n * mistakenly-broad where. If you genuinely want to update multiple\n * rows in one call, use {@link updateMany}, which is explicit about\n * its plurality.\n *\n * Implementation note: this issues a single `UPDATE … RETURNING` and\n * inspects the returned row count *after* the fact. The extra rows\n * over the wire are the cost of the safety check; the alternative\n * (a SELECT round-trip first) would be more rows AND a race window.\n * If the affected count is > 1 the update has *already* happened —\n * the throw is to surface the bug, not to prevent it. Wrap in a\n * transaction if you need atomic rollback.\n */\n async update(\n where: WhereClause<TCols>,\n patch: Partial<InferInsertModel<TTable>>,\n ): Promise<Row<TCols> | null> {\n this.requireNonEmptyWhere(where, 'update')\n this.validateColumnKeys(Object.keys(patch as object), 'update')\n const condition = this.buildWhereOrThrow(where)\n const rows = await this.db.update(this.table).set(patch).where(condition).returning()\n if (rows.length > 1) {\n throw new TableClientError(\n `update matched ${rows.length} rows but is expected to match at most one — use updateMany for bulk updates. ` +\n `WARNING: the update has already been applied; wrap in a transaction if you need rollback.`,\n )\n }\n return (rows[0] as Row<TCols> | undefined) ?? null\n }\n\n /**\n * Update all rows matching the where clause. Returns every updated row.\n * Throws if the where is empty — there is no \"update everything\" path.\n */\n async updateMany(\n where: WhereClause<TCols>,\n patch: Partial<InferInsertModel<TTable>>,\n ): Promise<Row<TCols>[]> {\n this.requireNonEmptyWhere(where, 'updateMany')\n this.validateColumnKeys(Object.keys(patch as object), 'updateMany')\n const condition = this.buildWhereOrThrow(where)\n const rows = await this.db.update(this.table).set(patch).where(condition).returning()\n return rows as Row<TCols>[]\n }\n\n /**\n * Delete **at most one** row matching the where clause. Returns the\n * deleted row, or `null` if no row matched.\n *\n * Throws `TableClientError` if:\n * - the where is empty\n * - the where matches more than one row\n *\n * Same multi-row safety guard as {@link update}. Use {@link deleteMany}\n * for explicit bulk deletes.\n *\n * Implementation note: same as `update` — the delete has already\n * happened by the time the throw fires. Wrap in a transaction if you\n * need atomic rollback.\n */\n async delete(where: WhereClause<TCols>): Promise<Row<TCols> | null> {\n this.requireNonEmptyWhere(where, 'delete')\n const condition = this.buildWhereOrThrow(where)\n const rows = await this.db.delete(this.table).where(condition).returning()\n if (rows.length > 1) {\n throw new TableClientError(\n `delete matched ${rows.length} rows but is expected to match at most one — use deleteMany for bulk deletes. ` +\n `WARNING: the delete has already been applied; wrap in a transaction if you need rollback.`,\n )\n }\n return (rows[0] as Row<TCols> | undefined) ?? null\n }\n\n /**\n * Delete all rows matching the where clause. Returns every deleted row.\n * Throws if the where is empty — there is no \"delete everything\" path.\n */\n async deleteMany(where: WhereClause<TCols>): Promise<Row<TCols>[]> {\n this.requireNonEmptyWhere(where, 'deleteMany')\n const condition = this.buildWhereOrThrow(where)\n const rows = await this.db.delete(this.table).where(condition).returning()\n return rows as Row<TCols>[]\n }\n\n // -------------------------------------------------------------------------\n // Transactions\n // -------------------------------------------------------------------------\n\n /**\n * Run a callback inside a database transaction. The callback receives a\n * transactional `TableClient` instance with the same shape as the\n * top-level client — write code identically inside or outside.\n *\n * Throwing from the callback rolls back; returning a value commits.\n *\n * @example\n * ```ts\n * await files.client.transaction(async (tx) => {\n * const row = await tx.insert(values)\n * await adapter.upload(row.key, buffer) // if this throws, the row is rolled back\n * return row\n * })\n * ```\n */\n async transaction<T>(fn: (tx: TableClient<TCols, TTable>) => Promise<T>): Promise<T> {\n return await this.db.transaction(async (innerDb) => {\n const txClient = new TableClient<TCols, TTable>(\n this.table,\n this.columnKinds,\n innerDb as unknown as PostgresJsDatabase,\n this.primaryKeyColumns,\n )\n return await fn(txClient)\n })\n }\n\n // -------------------------------------------------------------------------\n // Advanced operations\n // -------------------------------------------------------------------------\n\n /**\n * Atomically claim rows using `FOR UPDATE SKIP LOCKED`.\n *\n * Selects rows matching `where`, locks them (skipping any already locked\n * by another worker), updates them with `set`/`setSql`, and returns the\n * claimed rows in a single atomic operation. This is the standard pattern\n * for job queues, task distribution, and any producer-consumer table.\n *\n * Requires the table to have a declared primary key (either via\n * `column.uuid({ primaryKey: true })` or `defineTable({ primaryKey })`.\n *\n * @example Job queue claim\n * ```ts\n * const jobs = await client.claim({\n * where: { status: 'pending', runAt: { lte: new Date() } },\n * set: { status: 'processing', lockedBy: workerId, lockedAt: new Date() },\n * setSql: { attempts: sql`${jobsTable.table.attempts} + 1` },\n * orderBy: [{ column: 'priority', dir: 'desc' }, { column: 'createdAt', dir: 'asc' }],\n * limit: 5,\n * })\n * ```\n */\n async claim(options: ClaimOptions<TCols, TTable>): Promise<Row<TCols>[]> {\n // --- validation ---\n if (this.primaryKeyColumns.length === 0) {\n throw new TableClientError(\n 'claim() requires the table to have a primary key — ' +\n 'declare primaryKey in defineTable() or use column.uuid({ primaryKey: true })',\n )\n }\n if (this.primaryKeyColumns.length > 1) {\n throw new TableClientError(\n 'claim() does not support composite primary keys — ' +\n 'use a single-column primary key or call transaction() with FOR UPDATE directly',\n )\n }\n this.requireNonEmptyWhere(options.where, 'claim')\n if (!Number.isInteger(options.limit) || options.limit < 1) {\n throw new TableClientError(`claim limit must be a positive integer, got ${options.limit}`)\n }\n if (options.limit > MAX_LIMIT) {\n throw new TableClientError(`claim limit ${options.limit} exceeds MAX_LIMIT ${MAX_LIMIT}`)\n }\n\n // Validate set/setSql have at least one field, and that every named\n // column actually exists. Using one helper keeps the literal-set path\n // and the SQL-expression path consistent (same error message shape,\n // same column resolution).\n const setKeys = Object.keys(options.set ?? {})\n const setSqlKeys = Object.keys(options.setSql ?? {})\n if (setKeys.length === 0 && setSqlKeys.length === 0) {\n throw new TableClientError('claim requires at least one field in set or setSql')\n }\n this.validateColumnKeys(setKeys, 'claim set')\n this.validateColumnKeys(setSqlKeys, 'claim setSql')\n\n const whereCondition = this.buildWhereOrThrow(options.where)\n const orderExprs =\n options.orderBy && options.orderBy.length > 0\n ? this.buildOrderBy(options.orderBy, 'claim')\n : undefined\n\n // Resolve PK column for the SELECT. `primaryKeyColumns` is validated\n // non-empty + single-column above, so [0] is always defined.\n const pkName = this.primaryKeyColumns[0] as string\n const pkCol = this.getColumnOrThrow(pkName, 'claim primary key')\n\n // Two-step atomic claim inside a transaction:\n // 1. SELECT ... FOR UPDATE SKIP LOCKED (Drizzle's .for() method)\n // 2. UPDATE ... WHERE id IN (locked ids) RETURNING *\n // Both use Drizzle's typed builder — no raw SQL, no Date serialization issues.\n return await this.db.transaction(async (tx) => {\n // Step 1: Lock rows\n let selectQuery = tx\n .select({ _pk: pkCol })\n .from(this._selectTable)\n .where(whereCondition)\n .limit(options.limit)\n .for('update', { skipLocked: true })\n .$dynamic()\n\n if (orderExprs) {\n selectQuery = selectQuery.orderBy(...orderExprs)\n }\n\n const locked = await selectQuery\n if (locked.length === 0) return []\n\n const lockedIds = locked.map((r) => (r as Record<string, unknown>)._pk)\n\n // Step 2: Update locked rows\n // Merge set (literal values) and setSql (SQL expressions) into one object.\n // `PgUpdateSetSource<TTable>` natively accepts either column values OR\n // `SQL` expressions for each column, which is exactly what we want.\n const setObj: PgUpdateSetSource<TTable> = {\n ...(options.set ?? {}),\n } as PgUpdateSetSource<TTable>\n if (options.setSql) {\n for (const [key, expr] of Object.entries(options.setSql)) {\n ;(setObj as Record<string, SQL>)[key] = expr\n }\n }\n\n const rows = await tx\n .update(this.table)\n .set(setObj)\n .where(\n inArray(pkCol, lockedIds as InferSelectModel<TTable>[keyof InferSelectModel<TTable>][]),\n )\n .returning()\n\n return rows as Row<TCols>[]\n })\n }\n\n /**\n * Run an aggregate query with GROUP BY.\n *\n * Each key in `select` is an output alias mapped to an aggregate\n * specification. The `groupBy` columns are included automatically in\n * the result. Returns typed rows with grouped column values plus the\n * computed aggregates.\n *\n * @example Count jobs by status\n * ```ts\n * const rows = await client.aggregate({\n * select: { count: { fn: 'count' } },\n * groupBy: ['status'],\n * })\n * // → [{ status: 'pending', count: 12 }, { status: 'completed', count: 45 }]\n * ```\n *\n * @example Average priority of open jobs\n * ```ts\n * const [row] = await client.aggregate({\n * select: { avgPriority: { fn: 'avg', column: 'priority' } },\n * where: { status: 'open' },\n * })\n * ```\n */\n async aggregate<TResult extends Record<string, unknown> = Record<string, unknown>>(\n options: AggregateOptions<TCols>,\n ): Promise<TResult[]> {\n const selectEntries = Object.entries(options.select)\n if (selectEntries.length === 0) {\n throw new TableClientError('aggregate requires at least one select field')\n }\n\n // Validate output aliases up front. Drizzle quotes identifier aliases\n // when emitting `... AS \"alias\"`, but accepting only the standard SQL\n // identifier shape removes that implicit reliance and keeps result\n // keys safe to interpolate elsewhere downstream.\n for (const [alias] of selectEntries) {\n if (!SQL_IDENTIFIER_RE.test(alias)) {\n throw new TableClientError(\n `aggregate: invalid output alias \"${alias}\" — must match ${SQL_IDENTIFIER_RE.source}`,\n )\n }\n }\n\n // Build the select object: group-by columns first, then aggregate\n // expressions. `getColumnOrThrow` is the single column-resolution path.\n const selectObj: Record<string, AnyPgColumn | SQL> = {}\n if (options.groupBy) {\n for (const key of options.groupBy) {\n selectObj[key] = this.getColumnOrThrow(key, 'aggregate groupBy')\n }\n }\n for (const [alias, spec] of selectEntries) {\n selectObj[alias] = this.buildAggregateExpr(spec)\n }\n\n let query = this.db.select(selectObj).from(this._selectTable).$dynamic()\n\n if (options.where) {\n query = query.where(this.buildWhereOrThrow(options.where))\n }\n\n if (options.groupBy && options.groupBy.length > 0) {\n const groupCols = options.groupBy.map((key) =>\n this.getColumnOrThrow(key, 'aggregate groupBy'),\n )\n query = query.groupBy(...groupCols)\n }\n\n if (options.orderBy && options.orderBy.length > 0) {\n query = query.orderBy(...this.buildOrderBy(options.orderBy, 'aggregate'))\n }\n\n if (options.limit !== undefined) {\n query = query.limit(this.validateLimit(options.limit))\n }\n\n return (await query) as TResult[]\n }\n\n /**\n * Fast approximate row count using Postgres `reltuples` statistics.\n *\n * Returns the estimated row count from `pg_class` — updated by\n * `ANALYZE` and autovacuum. Much faster than `COUNT(*)` on large\n * tables (no sequential scan), but may be stale by up to a few\n * percent. Returns 0 for tables that have never been analyzed.\n *\n * Use `count()` when you need an exact number. Use `countEstimate()`\n * for UI display, pagination hints, or \"is this table large?\" checks\n * where ±5% accuracy is fine.\n *\n * @example\n * ```ts\n * const approx = await client.countEstimate()\n * if (approx > 100_000) {\n * // Switch to keyset pagination instead of offset\n * }\n * ```\n */\n async countEstimate(): Promise<number> {\n const tableName = getTableName(this.table)\n\n const [row] = await this.db\n .select({ estimate: sql<number>`greatest(${pgClassTable.reltuples}::bigint, 0)` })\n .from(pgClassTable)\n .where(eq(pgClassTable.relname, tableName))\n .limit(1)\n\n return row ? Number(row.estimate) : 0\n }\n\n // -------------------------------------------------------------------------\n // Internals\n // -------------------------------------------------------------------------\n\n /** Build a single aggregate SQL expression from a spec. */\n private buildAggregateExpr(spec: AggregateSpec<TCols>): SQL {\n if ('column' in spec && spec.column) {\n const col = this.getColumnOrThrow(spec.column, 'aggregate')\n switch (spec.fn) {\n case 'count':\n return sql<number>`count(${col})::int`\n case 'sum':\n return sql<number>`sum(${col})`\n case 'avg':\n return sql<number>`avg(${col})`\n case 'min':\n return sql`min(${col})`\n case 'max':\n return sql`max(${col})`\n default:\n throw new TableClientError(`aggregate: unknown function \"${(spec as { fn: string }).fn}\"`)\n }\n }\n // count without column = COUNT(*)\n if (spec.fn === 'count') {\n return sql<number>`count(*)::int`\n }\n throw new TableClientError(`aggregate: ${spec.fn} requires a column`)\n }\n\n private buildWhereOrThrow(where: WhereClause<TCols>) {\n try {\n const condition = buildWhere(this.table, this.columnKinds, where)\n if (!condition) {\n throw new TableClientError('where clause produced no SQL predicate')\n }\n return condition\n } catch (err) {\n if (err instanceof WhereBuilderError) {\n throw new TableClientError(err.message.replace(/^WhereBuilder: /, ''))\n }\n throw err\n }\n }\n\n private validateLimit(limit: number | undefined): number {\n if (limit === undefined) return DEFAULT_LIMIT\n if (!Number.isInteger(limit) || limit < 1) {\n throw new TableClientError(`limit must be a positive integer, got ${limit}`)\n }\n // Strict throw — *not* silent clamping. The previous behaviour\n // (clamp + console.warn) was the worst of both worlds: callers got\n // a quietly different result than they asked for, AND the warn\n // didn't go through the project logger. If a caller passes a limit\n // above MAX_LIMIT it's almost certainly a bug. Make them be\n // explicit: either pass a value within the cap, or refactor to\n // paginate / use claim() (PR 4) for batch processing.\n if (limit > MAX_LIMIT) {\n throw new TableClientError(\n `limit ${limit} exceeds MAX_LIMIT ${MAX_LIMIT} — paginate or use claim() for batch processing`,\n )\n }\n return limit\n }\n}\n","/**\n * Column builders for `defineTable`.\n *\n * Each builder returns a {@link ColumnFactory} — a callable that, given a\n * column name, produces the corresponding Drizzle column builder. Phantom\n * type parameters carry the JS type, \"kind\" tag, nullability, and\n * has-default flag through to the row/insert/where types.\n *\n * Conventions vs. `@murumets-ee/entity` field builders:\n * - `notNull` here is the equivalent of `required` in field builders.\n * Renamed because tables don't have a UI distinction between\n * \"required at the schema level\" and \"required to display\".\n * - No `translatable`, `indexed`, `unique`, `access` — those are entity-only\n * concepts. `unique` and indexes are declared at the table level instead.\n * - `column.jsonb<T>()` accepts a phantom type parameter for typed reads.\n *\n * @example\n * ```ts\n * column.uuid({ primaryKey: true, defaultRandom: true })\n * column.varchar({ length: 255, notNull: true })\n * column.timestamp({ notNull: true, defaultNow: true, withTimezone: true })\n * column.jsonb<{ description: string; tags: string[] }>({ notNull: true })\n * ```\n */\n\nimport {\n type AnyPgColumn,\n bigint,\n boolean,\n doublePrecision,\n integer,\n jsonb,\n type PgColumnBuilderBase,\n text,\n timestamp,\n uuid,\n varchar,\n} from 'drizzle-orm/pg-core'\nimport type { ColumnFactory, ColumnKind } from './types.js'\n\n// ---------------------------------------------------------------------------\n// Internal: factory constructor\n// ---------------------------------------------------------------------------\n\n/**\n * Wrap a Drizzle column-builder factory function with the phantom metadata\n * needed for type inference. The phantom fields are non-enumerable so they\n * don't pollute logging or JSON serialization.\n */\nfunction makeFactory<\n TType,\n TKind extends ColumnKind,\n TNotNull extends boolean,\n THasDefault extends boolean,\n>(\n build: (name: string) => PgColumnBuilderBase,\n meta: {\n kind: TKind\n notNull: TNotNull\n hasDefault: THasDefault\n pgName?: string\n primaryKey?: boolean\n },\n): ColumnFactory<TType, TKind, TNotNull, THasDefault> {\n const factory = build as ColumnFactory<TType, TKind, TNotNull, THasDefault>\n Object.defineProperty(factory, '__kind', { value: meta.kind, enumerable: false })\n Object.defineProperty(factory, '__notNull', { value: meta.notNull, enumerable: false })\n Object.defineProperty(factory, '__hasDefault', { value: meta.hasDefault, enumerable: false })\n // __type is purely a phantom — never read at runtime\n Object.defineProperty(factory, '__type', { value: undefined, enumerable: false })\n if (meta.pgName) {\n Object.defineProperty(factory, '__pgName', { value: meta.pgName, enumerable: false })\n }\n if (meta.primaryKey) {\n Object.defineProperty(factory, '__primaryKey', { value: true, enumerable: false })\n }\n return factory\n}\n\n// ---------------------------------------------------------------------------\n// Overloaded type interface\n// ---------------------------------------------------------------------------\n\n/**\n * Type interface for the `column` builder namespace.\n *\n * Overloads discriminate on whether `default` is present, so\n * `THasDefault` is inferred correctly:\n *\n * - `column.varchar({ length: 20, default: 'x' })` → `hasDefault = true`\n * - `column.varchar({ length: 20 })` → `hasDefault = false`\n *\n * Object-literal methods don't support overloads in TypeScript, so we\n * declare the overloaded signatures on an interface and cast the\n * implementation object.\n */\ninterface ColumnBuilders {\n uuid<\n TPrimaryKey extends boolean = false,\n TNotNull extends boolean = false,\n THasDefault extends boolean = false,\n >(opts?: {\n primaryKey?: TPrimaryKey\n notNull?: TNotNull\n defaultRandom?: THasDefault\n pgName?: string\n }): ColumnFactory<\n string,\n 'uuid',\n TPrimaryKey extends true ? true : TNotNull extends true ? true : false,\n TPrimaryKey extends true ? true : THasDefault extends true ? true : false\n >\n\n // varchar: with `default` → hasDefault = true\n varchar<TNotNull extends boolean = false>(opts: {\n length: number\n notNull?: TNotNull\n default: string\n pgName?: string\n }): ColumnFactory<string, 'varchar', TNotNull extends true ? true : false, true>\n // varchar: without `default` → hasDefault = false\n varchar<TNotNull extends boolean = false>(opts: {\n length: number\n notNull?: TNotNull\n pgName?: string\n }): ColumnFactory<string, 'varchar', TNotNull extends true ? true : false, false>\n\n // text: with `default` → hasDefault = true\n text<TNotNull extends boolean = false>(opts: {\n notNull?: TNotNull\n default: string\n pgName?: string\n }): ColumnFactory<string, 'text', TNotNull extends true ? true : false, true>\n // text: without `default` → hasDefault = false\n text<TNotNull extends boolean = false>(opts?: {\n notNull?: TNotNull\n pgName?: string\n }): ColumnFactory<string, 'text', TNotNull extends true ? true : false, false>\n\n // integer: with `default` → hasDefault = true\n integer<TNotNull extends boolean = false>(opts: {\n notNull?: TNotNull\n default: number\n pgName?: string\n }): ColumnFactory<number, 'integer', TNotNull extends true ? true : false, true>\n // integer: without `default` → hasDefault = false\n integer<TNotNull extends boolean = false>(opts?: {\n notNull?: TNotNull\n pgName?: string\n }): ColumnFactory<number, 'integer', TNotNull extends true ? true : false, false>\n\n // bigint: with `default` → hasDefault = true\n bigint<TMode extends 'number' | 'bigint' = 'number', TNotNull extends boolean = false>(opts: {\n mode?: TMode\n notNull?: TNotNull\n default: TMode extends 'bigint' ? bigint : number\n pgName?: string\n }): ColumnFactory<\n TMode extends 'bigint' ? bigint : number,\n 'bigint',\n TNotNull extends true ? true : false,\n true\n >\n // bigint: without `default` → hasDefault = false\n bigint<TMode extends 'number' | 'bigint' = 'number', TNotNull extends boolean = false>(opts?: {\n mode?: TMode\n notNull?: TNotNull\n pgName?: string\n }): ColumnFactory<\n TMode extends 'bigint' ? bigint : number,\n 'bigint',\n TNotNull extends true ? true : false,\n false\n >\n\n // double: with `default` → hasDefault = true\n double<TNotNull extends boolean = false>(opts: {\n notNull?: TNotNull\n default: number\n pgName?: string\n }): ColumnFactory<number, 'double', TNotNull extends true ? true : false, true>\n // double: without `default` → hasDefault = false\n double<TNotNull extends boolean = false>(opts?: {\n notNull?: TNotNull\n pgName?: string\n }): ColumnFactory<number, 'double', TNotNull extends true ? true : false, false>\n\n // boolean: with `default` → hasDefault = true\n boolean<TNotNull extends boolean = false>(opts: {\n notNull?: TNotNull\n default: boolean\n pgName?: string\n }): ColumnFactory<boolean, 'boolean', TNotNull extends true ? true : false, true>\n // boolean: without `default` → hasDefault = false\n boolean<TNotNull extends boolean = false>(opts?: {\n notNull?: TNotNull\n pgName?: string\n }): ColumnFactory<boolean, 'boolean', TNotNull extends true ? true : false, false>\n\n timestamp<TNotNull extends boolean = false, THasDefault extends boolean = false>(opts?: {\n notNull?: TNotNull\n defaultNow?: THasDefault\n withTimezone?: boolean\n pgName?: string\n }): ColumnFactory<\n Date,\n 'timestamp',\n TNotNull extends true ? true : false,\n THasDefault extends true ? true : false\n >\n\n // jsonb: with `default` → hasDefault = true\n jsonb<T = unknown, TNotNull extends boolean = false>(opts: {\n notNull?: TNotNull\n default: T\n pgName?: string\n }): ColumnFactory<T, 'jsonb', TNotNull extends true ? true : false, true>\n // jsonb: without `default` → hasDefault = false\n jsonb<T = unknown, TNotNull extends boolean = false>(opts?: {\n notNull?: TNotNull\n pgName?: string\n }): ColumnFactory<T, 'jsonb', TNotNull extends true ? true : false, false>\n\n uuidArray<TNotNull extends boolean = false>(opts?: {\n notNull?: TNotNull\n pgName?: string\n }): ColumnFactory<string[], 'uuidArray', TNotNull extends true ? true : false, false>\n}\n\n// ---------------------------------------------------------------------------\n// Public column builders\n// ---------------------------------------------------------------------------\n\n/**\n * Column builder namespace.\n *\n * Each builder produces a {@link ColumnFactory}. Pass the result to a\n * `defineTable` `columns` field. The builders are typed so that the\n * resulting `Row` and `InsertRow` types track each column's value type\n * and nullability.\n *\n * @example\n * ```ts\n * import { column, defineTable } from '@murumets-ee/db'\n *\n * const jobs = defineTable({\n * name: 'toolkit_jobs',\n * columns: {\n * id: column.uuid({ primaryKey: true, defaultRandom: true }),\n * type: column.varchar({ length: 100, notNull: true }),\n * payload: column.jsonb<{ subject: string; body: string }>({ notNull: true }),\n * status: column.varchar({ length: 20, notNull: true, default: 'pending' }),\n * priority: column.integer({ notNull: true, default: 0 }),\n * runAt: column.timestamp({ notNull: true, defaultNow: true, withTimezone: true }),\n * },\n * })\n * ```\n */\n// Implementation object — no type annotation here so each method can\n// return `makeFactory(...)` without per-method casts. The overloaded\n// `ColumnBuilders` interface is applied once at the export below.\nconst _column = {\n /**\n * UUID column.\n *\n * For primary keys, use `{ primaryKey: true, defaultRandom: true }` to\n * get a server-generated UUIDv4.\n */\n uuid(opts?: {\n primaryKey?: boolean\n notNull?: boolean\n defaultRandom?: boolean\n pgName?: string\n }) {\n const notNull = (opts?.notNull ?? false) as boolean\n const hasDefault = (opts?.defaultRandom ?? false) as boolean\n const isPk = (opts?.primaryKey ?? false) as boolean\n return makeFactory(\n (name) => {\n let col = uuid(name)\n if (isPk) col = col.primaryKey()\n if (opts?.defaultRandom) col = col.defaultRandom()\n // Primary keys are implicitly NOT NULL in Postgres; only call notNull for non-PK\n if (notNull && !isPk) col = col.notNull()\n return col\n },\n {\n kind: 'uuid',\n notNull: (notNull || isPk) as boolean,\n hasDefault: (hasDefault || isPk) as boolean,\n pgName: opts?.pgName,\n primaryKey: isPk || undefined,\n },\n )\n },\n\n /**\n * Variable-length string column. `length` is required and must be a\n * positive integer up to Postgres' actual `varchar` ceiling\n * (1073741823 — i.e. ~1GB / 4 bytes per char). For unbounded text use\n * `column.text()` instead — it stores the same way and skips the\n * length check at write time.\n *\n * Conventional sizes for reference:\n * - 50-100: short identifiers, slugs, status codes\n * - 255: legacy \"common max length\" — fits a tweet, an email, etc.\n * - 1024: file paths, URLs\n * - 10000+: prefer `text` instead, varchar with very large lengths\n * buys you nothing\n */\n varchar(opts: { length: number; notNull?: boolean; default?: string; pgName?: string }) {\n // Postgres' real varchar limit is 1073741823 (varchar without length\n // mod uses TEXT internally). We enforce the same upper bound so the\n // error fires at definition time instead of at INSERT time.\n if (!Number.isInteger(opts.length) || opts.length < 1 || opts.length > 1073741823) {\n throw new Error(\n `column.varchar: length must be an integer between 1 and 1073741823, got ${opts.length}`,\n )\n }\n const notNull = (opts.notNull ?? false) as boolean\n const hasDefault = opts.default !== undefined\n return makeFactory(\n (name) => {\n let col = varchar(name, { length: opts.length })\n if (notNull) col = col.notNull()\n if (opts.default !== undefined) col = col.default(opts.default)\n return col\n },\n {\n kind: 'varchar',\n notNull: notNull as boolean,\n hasDefault: hasDefault as boolean,\n pgName: opts.pgName,\n },\n )\n },\n\n /**\n * Unbounded text column.\n */\n text(opts?: { notNull?: boolean; default?: string; pgName?: string }) {\n const notNull = (opts?.notNull ?? false) as boolean\n const hasDefault = opts?.default !== undefined\n return makeFactory(\n (name) => {\n let col = text(name)\n if (notNull) col = col.notNull()\n if (opts?.default !== undefined) col = col.default(opts.default)\n return col\n },\n {\n kind: 'text',\n notNull: notNull as boolean,\n hasDefault: hasDefault as boolean,\n pgName: opts?.pgName,\n },\n )\n },\n\n /**\n * 32-bit integer column.\n */\n integer(opts?: { notNull?: boolean; default?: number; pgName?: string }) {\n const notNull = (opts?.notNull ?? false) as boolean\n const hasDefault = opts?.default !== undefined\n return makeFactory(\n (name) => {\n let col = integer(name)\n if (notNull) col = col.notNull()\n if (opts?.default !== undefined) col = col.default(opts.default)\n return col\n },\n {\n kind: 'integer',\n notNull: notNull as boolean,\n hasDefault: hasDefault as boolean,\n pgName: opts?.pgName,\n },\n )\n },\n\n /**\n * 64-bit integer column. `mode: 'number'` returns a JS number (safe for\n * values up to 2^53), `mode: 'bigint'` returns a JS BigInt.\n */\n bigint(opts?: {\n mode?: 'number' | 'bigint'\n notNull?: boolean\n default?: number | bigint\n pgName?: string\n }) {\n const mode = (opts?.mode ?? 'number') as 'number' | 'bigint'\n const notNull = (opts?.notNull ?? false) as boolean\n const hasDefault = opts?.default !== undefined\n return makeFactory(\n (name) => {\n let col = bigint(name, { mode })\n if (notNull) col = col.notNull()\n if (opts?.default !== undefined) col = col.default(opts.default as never)\n return col\n },\n {\n kind: 'bigint',\n notNull: notNull as boolean,\n hasDefault: hasDefault as boolean,\n pgName: opts?.pgName,\n },\n )\n },\n\n /**\n * Double-precision floating-point column.\n */\n double(opts?: { notNull?: boolean; default?: number; pgName?: string }) {\n const notNull = (opts?.notNull ?? false) as boolean\n const hasDefault = opts?.default !== undefined\n return makeFactory(\n (name) => {\n let col = doublePrecision(name)\n if (notNull) col = col.notNull()\n if (opts?.default !== undefined) col = col.default(opts.default)\n return col\n },\n {\n kind: 'double',\n notNull: notNull as boolean,\n hasDefault: hasDefault as boolean,\n pgName: opts?.pgName,\n },\n )\n },\n\n /**\n * Boolean column. Defaults to `false` if no explicit default is supplied\n * AND `notNull` is true — matches the entity package convention.\n */\n boolean(opts?: { notNull?: boolean; default?: boolean; pgName?: string }) {\n const notNull = (opts?.notNull ?? false) as boolean\n const hasDefault = opts?.default !== undefined\n return makeFactory(\n (name) => {\n let col = boolean(name)\n if (notNull) col = col.notNull()\n if (opts?.default !== undefined) col = col.default(opts.default)\n return col\n },\n {\n kind: 'boolean',\n notNull: notNull as boolean,\n hasDefault: hasDefault as boolean,\n pgName: opts?.pgName,\n },\n )\n },\n\n /**\n * Timestamp column. `withTimezone: true` is strongly recommended for all\n * timestamps in this codebase — entity audit fields use it consistently.\n */\n timestamp(opts?: {\n notNull?: boolean\n defaultNow?: boolean\n withTimezone?: boolean\n pgName?: string\n }) {\n const notNull = (opts?.notNull ?? false) as boolean\n const hasDefault = (opts?.defaultNow ?? false) as boolean\n const withTimezone = opts?.withTimezone ?? true\n return makeFactory(\n (name) => {\n let col = timestamp(name, { withTimezone })\n if (notNull) col = col.notNull()\n if (opts?.defaultNow) col = col.defaultNow()\n return col\n },\n {\n kind: 'timestamp',\n notNull: notNull as boolean,\n hasDefault: hasDefault as boolean,\n pgName: opts?.pgName,\n },\n )\n },\n\n /**\n * JSONB column. The phantom type parameter `T` propagates to row reads\n * and where-clause shorthand for the whole-column case. For dotted-path\n * access via the where-builder, the value type at the path is `unknown`\n * (TypeScript can't follow runtime path navigation).\n */\n jsonb(opts?: { notNull?: boolean; default?: unknown; pgName?: string }) {\n const notNull = (opts?.notNull ?? false) as boolean\n const hasDefault = opts?.default !== undefined\n return makeFactory(\n (name) => {\n let col = jsonb(name)\n if (notNull) col = col.notNull()\n if (opts?.default !== undefined) col = col.default(opts.default)\n return col\n },\n {\n kind: 'jsonb',\n notNull: notNull as boolean,\n hasDefault: hasDefault as boolean,\n pgName: opts?.pgName,\n },\n )\n },\n\n /**\n * UUID array column (`uuid[]`).\n */\n uuidArray(opts?: { notNull?: boolean; pgName?: string }) {\n const notNull = (opts?.notNull ?? false) as boolean\n return makeFactory(\n (name) => {\n // `PgArrayBuilder` (returned by `.array()`) extends `PgColumnBuilder`,\n // which is what `pgTable(...)` ultimately consumes — no cast needed.\n const col = uuid(name).array()\n return notNull ? col.notNull() : col\n },\n {\n kind: 'uuidArray',\n notNull: notNull as boolean,\n hasDefault: false as false,\n pgName: opts?.pgName,\n },\n )\n },\n}\n\n/**\n * Column builder namespace — overloaded interface applied via single\n * assertion. Each builder's implementation returns `makeFactory(...)` with\n * widened boolean params; the `ColumnBuilders` overloads narrow them for\n * callers based on whether `default`/`primaryKey` etc. are present.\n */\nexport const column: ColumnBuilders = _column as ColumnBuilders\n\n// Re-export the Drizzle column type for advanced consumers that need to\n// reach into the underlying pgTable for typed JOINs.\nexport type { AnyPgColumn }\n","/**\n * Process-wide registry of all `defineTable()` calls.\n *\n * Used by:\n * - **Runtime table lookup** — code paths that hold a table name string\n * can resolve it to a `PgTable` without importing the source file.\n * - **Test setup** — `createTestDb().push()` accepts a record of Drizzle\n * tables; pass `tableRegistry.allTables()` to push every defined table\n * into a fresh test schema in one call.\n * - **Audits / introspection** — list all known infra tables for tooling.\n *\n * Note: NOT used for migration discovery. `lumi migrate` discovers tables\n * via `Plugin.tables` fields + `buildEntitySchemaMap()`.\n *\n * The registry is intentionally separate from `schemaRegistry` (which is\n * for entity-generated schemas) so the two layers stay decoupled. They\n * may be merged in a follow-up if it simplifies migration discovery.\n */\n\nimport type { PgTable } from 'drizzle-orm/pg-core'\n\ninterface RegistryEntry {\n name: string\n table: PgTable\n /** Source file (best-effort, captured at definition time for debugging). */\n source?: string\n}\n\nclass TableRegistryImpl {\n private byName: Map<string, RegistryEntry> = new Map()\n\n /**\n * Register a defined table. Throws if a table with the same name is\n * already registered (catches accidental double-registration from a\n * file being imported under two paths).\n */\n register(name: string, table: PgTable, source?: string): void {\n const existing = this.byName.get(name)\n if (existing) {\n // Same table object → idempotent re-import; allow silently.\n if (existing.table === table) return\n // Different object ref but same source location → bundler dual-evaluated\n // the same module (Turbopack chunking, Next.js dev HMR, dual CJS/ESM\n // resolution). The underlying defineTable call is the same code. Treat\n // as idempotent: keep the first registration, silently drop the second.\n if (existing.source && source && existing.source === source) return\n throw new Error(\n `tableRegistry: a different table is already registered under name \"${name}\". ` +\n `Did you call defineTable twice? First registered from ${existing.source ?? '<unknown>'}, ` +\n `now from ${source ?? '<unknown>'}.`,\n )\n }\n this.byName.set(name, { name, table, source })\n }\n\n /**\n * Get a defined table by name.\n */\n get(name: string): PgTable | undefined {\n return this.byName.get(name)?.table\n }\n\n /**\n * Returns true if a table with the given name has been registered.\n */\n has(name: string): boolean {\n return this.byName.has(name)\n }\n\n /**\n * Return every registered table as a `{ [name]: PgTable }` map.\n *\n * Useful for `createTestDb().push(tableRegistry.allTables())` and\n * runtime introspection.\n */\n allTables(): Record<string, PgTable> {\n const out: Record<string, PgTable> = {}\n for (const entry of this.byName.values()) {\n out[entry.name] = entry.table\n }\n return out\n }\n\n /**\n * Clear the registry. **Test-only.**\n *\n * Production code should never call this — clearing the registry\n * silently breaks migration discovery and any code path that looks\n * up a table by name. The runtime guard below refuses to run when\n * `NODE_ENV === 'production'` to catch accidental misuse.\n *\n * Test runners typically set `NODE_ENV=test` (Vitest does), but the\n * guard is permissive about other values — it only blocks the one\n * environment where calling it is definitely wrong.\n */\n clear(): void {\n if (process.env.NODE_ENV === 'production') {\n throw new Error(\n 'tableRegistry.clear() is not allowed when NODE_ENV=production — ' +\n 'this is a test-only utility. If you reached this from production ' +\n 'code, you have a bug.',\n )\n }\n this.byName.clear()\n }\n}\n\n/**\n * Process-wide table registry singleton.\n *\n * Tables register themselves on import via `defineTable()`. The registry\n * is module-level state, which means a single Node process sees one\n * consistent view of all defined tables across packages.\n */\nexport const tableRegistry = new TableRegistryImpl()\n\n/**\n * Create an isolated registry — useful for unit tests that want to\n * verify registration behaviour without polluting the global registry.\n */\nexport function createTableRegistry() {\n return new TableRegistryImpl()\n}\n","/**\n * `defineTable()` — the entry point for the table layer.\n *\n * Given a {@link TableDefinition}, builds:\n *\n * - The underlying Drizzle `pgTable` (exposed for typed JOINs)\n * - A typed {@link TableClient} factory (callers wire in their `db` handle)\n * - The original schema metadata (for introspection / migrations)\n *\n * The table is also registered in the process-wide {@link tableRegistry}\n * so migration tooling and test setup can discover it.\n *\n * @example\n * ```ts\n * import { column, defineTable } from '@murumets-ee/db'\n *\n * export const ticketReadState = defineTable({\n * name: 'ticketing_read_state',\n * columns: {\n * ticketId: column.uuid({ notNull: true }),\n * userId: column.varchar({ length: 255, notNull: true }),\n * lastReadAt: column.timestamp({ notNull: true, defaultNow: true, withTimezone: true }),\n * },\n * primaryKey: ['ticketId', 'userId'],\n * indexes: [{ on: ['userId'] }],\n * })\n *\n * // Use the typed Drizzle table for migrations / typed JOINs:\n * export const ticketReadStateTable = ticketReadState.table\n *\n * // Build a client by wiring in a db handle:\n * const client = ticketReadState.makeClient(db)\n * await client.upsert(...) // (upsert lands in PR 3)\n * ```\n */\n\nimport {\n type AnyPgColumn,\n index as drizzleIndex,\n primaryKey as drizzlePrimaryKey,\n unique as drizzleUnique,\n type PgColumnBuilderBase,\n type PgTable,\n type PgTableExtraConfigValue,\n type PgTableWithColumns,\n pgTable,\n type TableConfig,\n} from 'drizzle-orm/pg-core'\nimport type { PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport { TableClient } from './client.js'\nimport { tableRegistry } from './registry.js'\nimport type { ColumnFactory, ColumnKind, TableDefinition } from './types.js'\n\n/** Allowed shape for table names. Snake_case, leading lowercase letter. */\nconst TABLE_NAME_RE = /^[a-z][a-z0-9_]*$/\n\n/**\n * Postgres' default identifier length limit (NAMEDATALEN - 1).\n *\n * Postgres silently truncates identifiers longer than this — which can\n * cause auto-generated constraint/index names to collide. We enforce\n * the same limit at definition time so the failure is loud and\n * caller-controllable.\n *\n * Bumping this requires a custom Postgres build (NAMEDATALEN), which\n * no production setup we ship has.\n */\nconst PG_IDENTIFIER_MAX = 63\n\n/**\n * Check that a Postgres identifier (table/column/constraint/index name)\n * fits in the 63-char limit, throwing with a useful error otherwise.\n *\n * The `kind` argument is for error messages only — e.g. \"table name\",\n * \"auto-generated index name\". The `suggestion` is the auto-generated\n * name's caller-facing alternative (\"supply an explicit `name`\").\n */\nfunction assertIdentifierLength(identifier: string, kind: string, suggestion?: string): void {\n if (identifier.length > PG_IDENTIFIER_MAX) {\n throw new Error(\n `defineTable: ${kind} \"${identifier}\" is ${identifier.length} characters — ` +\n `Postgres truncates identifiers at ${PG_IDENTIFIER_MAX} chars silently, ` +\n `which can cause name collisions${suggestion ? `. ${suggestion}` : ''}`,\n )\n }\n}\n\n/**\n * The result of `defineTable()`.\n *\n * @template TCols The columns record passed to `defineTable`.\n * @template TTable The concrete Drizzle `pgTable` type produced by the\n * definition. Defaulted to the non-generic `PgTable` for callers that\n * pass `DefinedTable` around without propagating inference — inside\n * `defineTable` itself, the actual `typeof table` is captured so\n * `makeClient` returns a fully-typed `TableClient<TCols, typeof table>`.\n */\nexport interface DefinedTable<\n TCols extends Record<string, ColumnFactory>,\n TTable extends PgTable = PgTableWithColumns<TableConfig>,\n> {\n /**\n * The underlying Drizzle `pgTable`. Exposed so callers can use it in\n * typed JOINs (the one sanctioned escape valve), and so it can be\n * included in `Plugin.tables` for migration discovery by `lumi migrate`.\n */\n table: TTable\n /**\n * The original schema metadata. Useful for introspection / tooling\n * that needs to know about indexes, unique constraints, etc.\n */\n schema: TableDefinition<TCols>\n /**\n * Per-column kind metadata (column name → kind tag). Used internally\n * by the where-builder for jsonb path validation.\n */\n columnKinds: Readonly<Record<string, ColumnKind>>\n /**\n * Primary key column names (JS property names). Detected from either\n * `schema.primaryKey` or column-level `{ primaryKey: true }`. Empty\n * array if no PK is declared. Required by `claim()`.\n */\n primaryKeyColumns: readonly string[]\n /**\n * Build a typed CRUD client wired to the given database handle.\n *\n * Callers typically construct one client per logical \"module\" and\n * cache it. The client is stateless aside from the db handle, so it\n * is safe to share.\n */\n makeClient(db: PostgresJsDatabase): TableClient<TCols, TTable>\n}\n\n/**\n * Define a typed table.\n *\n * The function signature uses two const-generic parameters to preserve\n * column literal types end-to-end. This is what enables `Row`,\n * `InsertRow`, and `WhereClause` to know exact column shapes at the call\n * site.\n *\n * @throws if the table name doesn't match the snake_case regex, or if\n * the same name is registered twice with different table objects.\n */\nexport function defineTable<const TCols extends Record<string, ColumnFactory>>(\n def: TableDefinition<TCols>,\n) {\n // Validate the table name shape — fail loudly at definition time rather\n // than letting Postgres catch it at migration time.\n if (!TABLE_NAME_RE.test(def.name)) {\n throw new Error(\n `defineTable: invalid table name \"${def.name}\" — must match /^[a-z][a-z0-9_]*$/`,\n )\n }\n assertIdentifierLength(def.name, 'table name')\n\n // Build the per-column drizzle column builders by invoking each factory\n // with its property name. The factory contract requires the produced\n // column to be a `PgColumnBuilderBase` ready for `pgTable(...)`.\n const drizzleCols: Record<string, PgColumnBuilderBase> = {}\n const columnKinds: Record<string, ColumnKind> = {}\n for (const [name, factory] of Object.entries(def.columns)) {\n if (typeof factory !== 'function') {\n throw new Error(\n `defineTable: column \"${name}\" is not a column factory — did you call e.g. column.uuid() with parens?`,\n )\n }\n assertIdentifierLength(name, `column name in table \"${def.name}\"`)\n // Use explicit pgName when porting existing tables with snake_case columns\n const pgName = factory.__pgName\n if (pgName) {\n assertIdentifierLength(pgName, `pgName for column \"${name}\" in table \"${def.name}\"`)\n }\n drizzleCols[name] = factory(pgName ?? name)\n columnKinds[name] = factory.__kind\n }\n\n // Validate that all constraint references point at known columns BEFORE\n // building the pgTable. The pgTable constraint callback may not surface\n // errors at construction time, so we check eagerly here for fast failure\n // and consistent error messages.\n const knownColumns = new Set(Object.keys(drizzleCols))\n if (def.primaryKey !== undefined) {\n const pkCols = Array.isArray(def.primaryKey) ? def.primaryKey : [def.primaryKey]\n for (const k of pkCols) {\n if (!knownColumns.has(String(k))) {\n throw new Error(`defineTable: primaryKey references unknown column \"${String(k)}\"`)\n }\n }\n }\n if (def.unique) {\n for (const uq of def.unique) {\n for (const k of uq.on) {\n if (!knownColumns.has(String(k))) {\n throw new Error(`defineTable: unique constraint references unknown column \"${String(k)}\"`)\n }\n }\n const name = uq.name ?? `uq_${def.name}_${uq.on.map(String).join('_')}`\n assertIdentifierLength(\n name,\n uq.name ? 'unique constraint name' : 'auto-generated unique constraint name',\n uq.name ? undefined : 'Supply a shorter explicit `name` to the unique definition.',\n )\n }\n }\n if (def.indexes) {\n for (const ix of def.indexes) {\n for (const k of ix.on) {\n if (!knownColumns.has(String(k))) {\n throw new Error(`defineTable: index references unknown column \"${String(k)}\"`)\n }\n }\n const name = ix.name ?? `idx_${def.name}_${ix.on.map(String).join('_')}`\n assertIdentifierLength(\n name,\n ix.name ? 'index name' : 'auto-generated index name',\n ix.name ? undefined : 'Supply a shorter explicit `name` to the index definition.',\n )\n }\n }\n\n // Build the constraint callback for indexes / unique / primary key.\n // pgTable accepts an optional callback that returns a constraint object.\n const hasConstraints =\n def.primaryKey !== undefined ||\n (def.unique && def.unique.length > 0) ||\n (def.indexes && def.indexes.length > 0)\n\n // Helper: look up a column by key from the pgTable callback parameter.\n // `t` has a string index (`{ [x: string]: PgColumn }`) so access is typed.\n function resolveCol(t: Record<string, AnyPgColumn>, k: string, context: string): AnyPgColumn {\n const col = t[k]\n if (!col) throw new Error(`defineTable: ${context} references unknown column \"${k}\"`)\n return col\n }\n\n const table = hasConstraints\n ? pgTable(def.name, drizzleCols, (t) => {\n const tCols = t as unknown as Record<string, AnyPgColumn>\n const constraints: PgTableExtraConfigValue[] = []\n\n // Composite primary key\n if (def.primaryKey !== undefined) {\n const pkCols = Array.isArray(def.primaryKey) ? def.primaryKey : [def.primaryKey]\n const first = resolveCol(tCols, String(pkCols[0]), 'primaryKey')\n const rest = pkCols.slice(1).map((k) => resolveCol(tCols, String(k), 'primaryKey'))\n constraints.push(drizzlePrimaryKey({ columns: [first, ...rest] }))\n }\n\n // Unique constraints\n if (def.unique) {\n for (const uq of def.unique) {\n const first = resolveCol(tCols, String(uq.on[0]), 'unique constraint')\n const rest = uq.on\n .slice(1)\n .map((k) => resolveCol(tCols, String(k), 'unique constraint'))\n constraints.push(drizzleUnique().on(first, ...rest))\n }\n }\n\n // Indexes\n if (def.indexes) {\n for (const ix of def.indexes) {\n const first = resolveCol(tCols, String(ix.on[0]), 'index')\n const rest = ix.on.slice(1).map((k) => resolveCol(tCols, String(k), 'index'))\n constraints.push(\n drizzleIndex(ix.name ?? `idx_${def.name}_${ix.on.map(String).join('_')}`).on(\n first,\n ...rest,\n ),\n )\n }\n }\n\n return constraints\n })\n : pgTable(def.name, drizzleCols)\n\n // Capture caller location for nicer double-registration errors. Best-\n // effort — falls back to undefined if stack parsing fails.\n const source = captureCallerLocation()\n tableRegistry.register(def.name, table, source)\n\n const frozenKinds: Readonly<Record<string, ColumnKind>> = Object.freeze({ ...columnKinds })\n\n // Determine primary key column names from table-level or column-level declarations.\n let pkCols: string[]\n if (def.primaryKey !== undefined) {\n pkCols = Array.isArray(def.primaryKey) ? def.primaryKey.map(String) : [String(def.primaryKey)]\n } else {\n pkCols = []\n for (const [name, factory] of Object.entries(def.columns)) {\n if (factory.__primaryKey) pkCols.push(name)\n }\n }\n const frozenPk: readonly string[] = Object.freeze([...pkCols])\n\n return {\n table,\n schema: def,\n columnKinds: frozenKinds,\n primaryKeyColumns: frozenPk,\n makeClient: (db: PostgresJsDatabase) =>\n new TableClient<TCols, typeof table>(table, frozenKinds, db, frozenPk),\n }\n}\n\n/**\n * Best-effort: walk the stack to find the first non-internal frame so\n * double-registration errors point at the user's call site instead of\n * deep into `defineTable`.\n */\nfunction captureCallerLocation(): string | undefined {\n const stack = new Error().stack\n if (!stack) return undefined\n const lines = stack.split('\\n').slice(1)\n for (const line of lines) {\n if (line.includes('/table/define.') || line.includes('/table/registry.')) continue\n const match = line.match(/\\(([^)]+)\\)/) ?? line.match(/at (.+)$/)\n if (match) return match[1].trim()\n }\n return undefined\n}\n"],"mappings":"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"}
@@ -24,6 +24,69 @@ interface TestDb {
24
24
  * ```
25
25
  */
26
26
  declare function createTestDb(): Promise<TestDb>;
27
+ /** OrderBy spec mirrors TableClient's `OrderBySpec`. */
28
+ interface InMemoryOrderBy {
29
+ column: string;
30
+ dir?: 'asc' | 'desc';
31
+ }
32
+ /** Subset of TableClient methods used by tests. */
33
+ interface InMemoryTableClient<Row extends Record<string, unknown>> {
34
+ rows: Row[];
35
+ reset(initial?: Row[]): void;
36
+ findOne(where: Record<string, unknown>): Promise<Row | null>;
37
+ findMany(opts?: {
38
+ where?: Record<string, unknown>;
39
+ orderBy?: InMemoryOrderBy[];
40
+ limit?: number;
41
+ offset?: number;
42
+ }): Promise<Row[]>;
43
+ exists(where: Record<string, unknown>): Promise<boolean>;
44
+ count(where?: Record<string, unknown>): Promise<number>;
45
+ insert(values: Partial<Row>): Promise<Row>;
46
+ update(where: Record<string, unknown>, patch: Partial<Row>): Promise<Row | null>;
47
+ upsert(values: Partial<Row>, opts: {
48
+ target: string[] | string;
49
+ set?: Partial<Row>;
50
+ }): Promise<Row>;
51
+ delete(where: Record<string, unknown>): Promise<Row | null>;
52
+ deleteMany(where: Record<string, unknown>): Promise<Row[]>;
53
+ tryClaim(values: Partial<Row>, opts: {
54
+ target: string | string[];
55
+ replaceWhen: Record<string, unknown>;
56
+ set?: Partial<Row>;
57
+ }): Promise<{
58
+ acquired: true;
59
+ row: Row;
60
+ } | {
61
+ acquired: false;
62
+ row: Row;
63
+ }>;
64
+ aggregate?: (opts: {
65
+ select: Record<string, {
66
+ fn: 'max' | 'min' | 'count' | 'sum' | 'avg';
67
+ column?: string;
68
+ }>;
69
+ where?: Record<string, unknown>;
70
+ }) => Promise<Array<Record<string, unknown>>>;
71
+ }
72
+ /**
73
+ * Build an in-memory TableClient stand-in for unit tests. Pass
74
+ * `idGenerator` if your test wants deterministic synthetic IDs
75
+ * (defaults to a monotonic `row-1`, `row-2`, ... counter).
76
+ *
77
+ * Example:
78
+ * ```ts
79
+ * const drafts = createInMemoryTableClient<DraftRow>({ idField: 'id' })
80
+ * vi.mock('@murumets-ee/content/schema', () => ({
81
+ * getContentTables: () => ({ drafts: { makeClient: () => drafts } })
82
+ * }))
83
+ * ```
84
+ */
85
+ declare function createInMemoryTableClient<Row extends Record<string, unknown>>(options?: {
86
+ idField?: keyof Row & string;
87
+ idGenerator?: () => string;
88
+ initial?: Row[];
89
+ }): InMemoryTableClient<Row>;
27
90
  //#endregion
28
- export { TestDb, createTestDb };
91
+ export { InMemoryOrderBy, InMemoryTableClient, TestDb, createInMemoryTableClient, createTestDb };
29
92
  //# sourceMappingURL=test-utils.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"test-utils.d.mts","names":[],"sources":["../src/test-utils.ts"],"mappings":";;;UAIiB,MAAA;EACf,EAAA,EAAI,kBAAA;EACJ,MAAA;EAFqB;EAIrB,IAAA,GAAO,OAAA,EAAS,MAAA,sBAA4B,OAAA;EAC5C,OAAA,QAAe,OAAA;AAAA;;;;;;;;;;;;;;;;iBAkBK,YAAA,CAAA,GAAgB,OAAA,CAAQ,MAAA"}
1
+ {"version":3,"file":"test-utils.d.mts","names":[],"sources":["../src/test-utils.ts"],"mappings":";;;UAIiB,MAAA;EACf,EAAA,EAAI,kBAAA;EACJ,MAAA;EAFqB;EAIrB,IAAA,GAAO,OAAA,EAAS,MAAA,sBAA4B,OAAA;EAC5C,OAAA,QAAe,OAAA;AAAA;;;;;;;;;;;;;;;;iBAkBK,YAAA,CAAA,GAAgB,OAAA,CAAQ,MAAA;;UA6I7B,eAAA;EACf,MAAA;EACA,GAAA;AAAA;;UAIe,mBAAA,aAAgC,MAAA;EAC/C,IAAA,EAAM,GAAA;EACN,KAAA,CAAM,OAAA,GAAU,GAAA;EAChB,OAAA,CAAQ,KAAA,EAAO,MAAA,oBAA0B,OAAA,CAAQ,GAAA;EACjD,QAAA,CAAS,IAAA;IACP,KAAA,GAAQ,MAAA;IACR,OAAA,GAAU,eAAA;IACV,KAAA;IACA,MAAA;EAAA,IACE,OAAA,CAAQ,GAAA;EACZ,MAAA,CAAO,KAAA,EAAO,MAAA,oBAA0B,OAAA;EACxC,KAAA,CAAM,KAAA,GAAQ,MAAA,oBAA0B,OAAA;EACxC,MAAA,CAAO,MAAA,EAAQ,OAAA,CAAQ,GAAA,IAAO,OAAA,CAAQ,GAAA;EACtC,MAAA,CAAO,KAAA,EAAO,MAAA,mBAAyB,KAAA,EAAO,OAAA,CAAQ,GAAA,IAAO,OAAA,CAAQ,GAAA;EACrE,MAAA,CACE,MAAA,EAAQ,OAAA,CAAQ,GAAA,GAChB,IAAA;IAAQ,MAAA;IAA2B,GAAA,GAAM,OAAA,CAAQ,GAAA;EAAA,IAChD,OAAA,CAAQ,GAAA;EACX,MAAA,CAAO,KAAA,EAAO,MAAA,oBAA0B,OAAA,CAAQ,GAAA;EAChD,UAAA,CAAW,KAAA,EAAO,MAAA,oBAA0B,OAAA,CAAQ,GAAA;EACpD,QAAA,CACE,MAAA,EAAQ,OAAA,CAAQ,GAAA,GAChB,IAAA;IACE,MAAA;IACA,WAAA,EAAa,MAAA;IACb,GAAA,GAAM,OAAA,CAAQ,GAAA;EAAA,IAEf,OAAA;IAAU,QAAA;IAAgB,GAAA,EAAK,GAAA;EAAA;IAAU,QAAA;IAAiB,GAAA,EAAK,GAAA;EAAA;EAClE,SAAA,IAAa,IAAA;IACX,MAAA,EAAQ,MAAA;MAAiB,EAAA;MAA6C,MAAA;IAAA;IACtE,KAAA,GAAQ,MAAA;EAAA,MACJ,OAAA,CAAQ,KAAA,CAAM,MAAA;AAAA;;;;;;;;;;;;;;iBAgBN,yBAAA,aAAsC,MAAA,kBAAA,CACpD,OAAA;EAAW,OAAA,SAAgB,GAAA;EAAc,WAAA;EAA4B,OAAA,GAAU,GAAA;AAAA,IAC9E,mBAAA,CAAoB,GAAA"}
@@ -1,2 +1,2 @@
1
- import{drizzle as e}from"drizzle-orm/postgres-js";import t from"postgres";import{sql as n}from"drizzle-orm";async function r(){if(process.env.NODE_ENV===`production`)throw Error(`createTestDb() is not allowed when NODE_ENV=production — it issues CREATE/DROP SCHEMA and is intended for tests only`);let r=process.env.DATABASE_TEST_URL||process.env.DATABASE_URL;if(!r)throw Error(`DATABASE_TEST_URL (or DATABASE_URL) env var is required for integration tests`);let a=`test_${crypto.randomUUID().replaceAll(`-`,``).slice(0,12)}`,o=t(r,{max:3,idle_timeout:10}),s=e(o);return await s.execute(n.raw(`CREATE SCHEMA "${a}"`)),await s.execute(n.raw(`SET search_path TO "${a}", public`)),{db:s,schema:a,async push(e){let{createRequire:t}=await import(`node:module`),{pushSchema:n}=t(import.meta.url)(`drizzle-kit/api`);await(await n(e,i(s))).apply()},async cleanup(){await s.execute(n.raw(`DROP SCHEMA IF EXISTS "${a}" CASCADE`)),await o.end()}}}function i(e){return new Proxy(e,{get(e,t,n){return t===`execute`?async(...t)=>{let n=await e.execute(...t);return Array.isArray(n)&&!(`rows`in n)&&Object.defineProperty(n,`rows`,{value:[...n],enumerable:!1}),n}:Reflect.get(e,t,n)}})}export{r as createTestDb};
1
+ import{drizzle as e}from"drizzle-orm/postgres-js";import t from"postgres";import{sql as n}from"drizzle-orm";async function r(){if(process.env.NODE_ENV===`production`)throw Error(`createTestDb() is not allowed when NODE_ENV=production — it issues CREATE/DROP SCHEMA and is intended for tests only`);let r=process.env.DATABASE_TEST_URL||process.env.DATABASE_URL;if(!r)throw Error(`DATABASE_TEST_URL (or DATABASE_URL) env var is required for integration tests`);let i=`test_${crypto.randomUUID().replaceAll(`-`,``).slice(0,12)}`,a=t(r,{max:1});try{await a.unsafe(`CREATE SCHEMA "${i}"`)}finally{await a.end()}let o=t(r,{max:3,idle_timeout:10,connection:{search_path:`"${i}",public`}}),c=e(o);return{db:c,schema:i,async push(e){let{createRequire:t}=await import(`node:module`),{pushSchema:n}=t(import.meta.url)(`drizzle-kit/api`);await(await n(e,s(c))).apply()},async cleanup(){await c.execute(n.raw(`DROP SCHEMA IF EXISTS "${i}" CASCADE`)),await o.end()}}}function i(e,t){return`eq`in t?e===t.eq:`ne`in t?e!==t.ne:`in`in t?t.in.includes(e):`notIn`in t?!t.notIn.includes(e):`lt`in t?e<t.lt:`lte`in t?e<=t.lte:`gt`in t?e>t.gt:`gte`in t?e>=t.gte:`isNull`in t?e==null:`isNotNull`in t?e!=null:!1}function a(e,t){for(let[n,r]of Object.entries(t)){if(n===`$or`){if(!r.some(t=>a(e,t)))return!1;continue}if(n===`$and`){if(!r.every(t=>a(e,t)))return!1;continue}let t=e[n];if(r===null){if(t!=null)return!1;continue}if(typeof r==`object`&&r&&!(r instanceof Date)){if(!i(t,r))return!1;continue}if(t!==r)return!1}return!0}function o(e={}){let t=e.idField??`id`,n=1,r=e.idGenerator??(()=>`row-${n++}`),i={rows:e.initial?[...e.initial]:[]};return{get rows(){return i.rows},set rows(e){i.rows=e},reset(e){i.rows=e?[...e]:[],n=1},async findOne(e){return i.rows.find(t=>a(t,e))??null},async findMany(e){let t=e?.where?i.rows.filter(t=>a(t,e.where)):[...i.rows];e?.orderBy&&e.orderBy.length>0&&(t=[...t].sort((t,n)=>{for(let r of e.orderBy??[]){let e=t[r.column],i=n[r.column],a=r.dir===`desc`?-1:1;if(e instanceof Date&&i instanceof Date){let t=(e.getTime()-i.getTime())*a;if(t!==0)return t}else if(typeof e==`number`&&typeof i==`number`){let t=(e-i)*a;if(t!==0)return t}else if(typeof e==`string`&&typeof i==`string`){let t=e.localeCompare(i)*a;if(t!==0)return t}}return 0}));let n=e?.offset??0,r=e?.limit;return n>0&&(t=t.slice(n)),r!==void 0&&r>=0&&(t=t.slice(0,r)),t},async aggregate(e){let t=e?.where?i.rows.filter(t=>a(t,e.where)):i.rows,n={};for(let[r,i]of Object.entries(e.select))if(i.fn===`count`)n[r]=t.length;else if(i.fn===`max`&&i.column){let e=null;for(let n of t){let t=n[i.column];typeof t==`number`&&(e===null||t>e)&&(e=t)}n[r]=e}else if(i.fn===`min`&&i.column){let e=null;for(let n of t){let t=n[i.column];typeof t==`number`&&(e===null||t<e)&&(e=t)}n[r]=e}else if(i.fn===`sum`&&i.column){let e=0;for(let n of t){let t=n[i.column];typeof t==`number`&&(e+=t)}n[r]=e}return[n]},async exists(e){return i.rows.some(t=>a(t,e))},async count(e){return e?i.rows.filter(t=>a(t,e)).length:i.rows.length},async insert(e){let n={[t]:r(),...e};return i.rows.push(n),n},async update(e,t){let n=i.rows.filter(t=>a(t,e));if(n.length===0)return null;let r=n[0];return Object.assign(r,t),r},async upsert(e,n){let o=Array.isArray(n.target)?n.target:[n.target],s={};for(let t of o)s[t]=e[t];let c=i.rows.find(e=>a(e,s));if(c)return Object.assign(c,n.set??e),c;let l={[t]:r(),...e};return i.rows.push(l),l},async delete(e){let t=i.rows.findIndex(t=>a(t,e));if(t===-1)return null;let[n]=i.rows.splice(t,1);return n??null},async deleteMany(e){let t=i.rows.filter(t=>a(t,e));return i.rows=i.rows.filter(e=>!t.includes(e)),t},async tryClaim(e,n){let o=Array.isArray(n.target)?n.target:[n.target],s={};for(let t of o)s[t]=e[t];let c=i.rows.find(e=>a(e,s));if(!c){let n={[t]:r(),...e};return i.rows.push(n),{acquired:!0,row:n}}return a(c,n.replaceWhen)?(Object.assign(c,n.set??e),{acquired:!0,row:c}):{acquired:!1,row:c}}}}function s(e){return new Proxy(e,{get(e,t,n){return t===`execute`?async(...t)=>{let n=await e.execute(...t);return Array.isArray(n)&&!(`rows`in n)&&Object.defineProperty(n,`rows`,{value:[...n],enumerable:!1}),n}:Reflect.get(e,t,n)}})}export{o as createInMemoryTableClient,r as createTestDb};
2
2
  //# sourceMappingURL=test-utils.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"test-utils.mjs","names":[],"sources":["../src/test-utils.ts"],"sourcesContent":["import { sql } from 'drizzle-orm'\nimport { drizzle, type PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport postgres from 'postgres'\n\nexport interface TestDb {\n db: PostgresJsDatabase\n schema: string\n /** Push Drizzle table definitions to the test schema (uses drizzle-kit/api) */\n push: (schemas: Record<string, unknown>) => Promise<void>\n cleanup: () => Promise<void>\n}\n\n/**\n * Create an isolated test database using a unique PostgreSQL schema.\n * Each test suite gets its own schema so tests can run in parallel safely.\n *\n * Requires `DATABASE_TEST_URL` env var pointing to a running PostgreSQL instance.\n * Falls back to `DATABASE_URL` if test URL is not set.\n *\n * Usage:\n * ```ts\n * const testDb = await createTestDb()\n * await testDb.push({ articles: articlesTable, entity_refs: entityRefsTable })\n * // ... run tests with testDb.db ...\n * await testDb.cleanup()\n * ```\n */\nexport async function createTestDb(): Promise<TestDb> {\n // Refuse to ever provision a scratch schema in production. `createTestDb`\n // issues `CREATE SCHEMA` and (via `cleanup()`) `DROP SCHEMA … CASCADE`,\n // which would be catastrophic if pointed at a real database. The whole\n // function is test-only, but the npm subpath export means an app could\n // import it by accident — fail loud instead of running.\n if (process.env.NODE_ENV === 'production') {\n throw new Error(\n 'createTestDb() is not allowed when NODE_ENV=production — it issues CREATE/DROP SCHEMA and is intended for tests only',\n )\n }\n\n const url = process.env.DATABASE_TEST_URL || process.env.DATABASE_URL\n if (!url) {\n throw new Error('DATABASE_TEST_URL (or DATABASE_URL) env var is required for integration tests')\n }\n\n const schemaName = `test_${crypto.randomUUID().replaceAll('-', '').slice(0, 12)}`\n\n const connection = postgres(url, {\n max: 3,\n idle_timeout: 10,\n })\n\n const db = drizzle(connection)\n\n // Create isolated schema\n await db.execute(sql.raw(`CREATE SCHEMA \"${schemaName}\"`))\n await db.execute(sql.raw(`SET search_path TO \"${schemaName}\", public`))\n\n return {\n db,\n schema: schemaName,\n\n async push(schemas: Record<string, unknown>) {\n // Use createRequire to load drizzle-kit/api via Node's CJS resolver.\n // drizzle-kit/api.mjs bundles CJS code with require('fs') — Vite's ESM\n // transform replaces require() with a polyfill that throws. Native CJS bypasses this.\n const { createRequire } = await import('node:module')\n const require = createRequire(import.meta.url)\n const { pushSchema } = require('drizzle-kit/api') as typeof import('drizzle-kit/api')\n\n // Use default schemaFilters ([\"public\"]). Our tables are unqualified (= public),\n // and SET search_path ensures DDL actually creates them in the test schema.\n const result = await pushSchema(schemas, withRowsShim(db))\n await result.apply()\n },\n\n async cleanup() {\n await db.execute(sql.raw(`DROP SCHEMA IF EXISTS \"${schemaName}\" CASCADE`))\n await connection.end()\n },\n }\n}\n\n/**\n * Wrap a Drizzle handle so `.execute()` returns an array that *also* exposes\n * a `.rows` property. `drizzle-kit/api`'s `pushSchema` reads `res.rows`, but\n * postgres-js (the driver Drizzle is built on here) returns plain arrays\n * with no `.rows`. The shim is local to test-utils — production code never\n * needs it.\n */\nfunction withRowsShim(db: PostgresJsDatabase): PostgresJsDatabase {\n return new Proxy(db, {\n get(target, prop, receiver) {\n if (prop === 'execute') {\n return async (...args: unknown[]) => {\n const result = await (target.execute as (...a: unknown[]) => Promise<unknown[]>)(...args)\n if (Array.isArray(result) && !('rows' in result)) {\n Object.defineProperty(result, 'rows', { value: [...result], enumerable: false })\n }\n return result\n }\n }\n return Reflect.get(target, prop, receiver)\n },\n })\n}\n"],"mappings":"4GA2BA,eAAsB,GAAgC,CAMpD,GAAI,QAAQ,IAAI,WAAa,aAC3B,MAAU,MACR,uHACD,CAGH,IAAM,EAAM,QAAQ,IAAI,mBAAqB,QAAQ,IAAI,aACzD,GAAI,CAAC,EACH,MAAU,MAAM,gFAAgF,CAGlG,IAAM,EAAa,QAAQ,OAAO,YAAY,CAAC,WAAW,IAAK,GAAG,CAAC,MAAM,EAAG,GAAG,GAEzE,EAAa,EAAS,EAAK,CAC/B,IAAK,EACL,aAAc,GACf,CAAC,CAEI,EAAK,EAAQ,EAAW,CAM9B,OAHA,MAAM,EAAG,QAAQ,EAAI,IAAI,kBAAkB,EAAW,GAAG,CAAC,CAC1D,MAAM,EAAG,QAAQ,EAAI,IAAI,uBAAuB,EAAW,WAAW,CAAC,CAEhE,CACL,KACA,OAAQ,EAER,MAAM,KAAK,EAAkC,CAI3C,GAAM,CAAE,iBAAkB,MAAM,OAAO,eAEjC,CAAE,cADQ,EAAc,OAAO,KAAK,IAAI,CACf,kBAAkB,CAKjD,MADe,MAAM,EAAW,EAAS,EAAa,EAAG,CAAC,EAC7C,OAAO,EAGtB,MAAM,SAAU,CACd,MAAM,EAAG,QAAQ,EAAI,IAAI,0BAA0B,EAAW,WAAW,CAAC,CAC1E,MAAM,EAAW,KAAK,EAEzB,CAUH,SAAS,EAAa,EAA4C,CAChE,OAAO,IAAI,MAAM,EAAI,CACnB,IAAI,EAAQ,EAAM,EAAU,CAU1B,OATI,IAAS,UACJ,MAAO,GAAG,IAAoB,CACnC,IAAM,EAAS,MAAO,EAAO,QAAoD,GAAG,EAAK,CAIzF,OAHI,MAAM,QAAQ,EAAO,EAAI,EAAE,SAAU,IACvC,OAAO,eAAe,EAAQ,OAAQ,CAAE,MAAO,CAAC,GAAG,EAAO,CAAE,WAAY,GAAO,CAAC,CAE3E,GAGJ,QAAQ,IAAI,EAAQ,EAAM,EAAS,EAE7C,CAAC"}
1
+ {"version":3,"file":"test-utils.mjs","names":[],"sources":["../src/test-utils.ts"],"sourcesContent":["import { sql } from 'drizzle-orm'\nimport { drizzle, type PostgresJsDatabase } from 'drizzle-orm/postgres-js'\nimport postgres from 'postgres'\n\nexport interface TestDb {\n db: PostgresJsDatabase\n schema: string\n /** Push Drizzle table definitions to the test schema (uses drizzle-kit/api) */\n push: (schemas: Record<string, unknown>) => Promise<void>\n cleanup: () => Promise<void>\n}\n\n/**\n * Create an isolated test database using a unique PostgreSQL schema.\n * Each test suite gets its own schema so tests can run in parallel safely.\n *\n * Requires `DATABASE_TEST_URL` env var pointing to a running PostgreSQL instance.\n * Falls back to `DATABASE_URL` if test URL is not set.\n *\n * Usage:\n * ```ts\n * const testDb = await createTestDb()\n * await testDb.push({ articles: articlesTable, entity_refs: entityRefsTable })\n * // ... run tests with testDb.db ...\n * await testDb.cleanup()\n * ```\n */\nexport async function createTestDb(): Promise<TestDb> {\n // Refuse to ever provision a scratch schema in production. `createTestDb`\n // issues `CREATE SCHEMA` and (via `cleanup()`) `DROP SCHEMA … CASCADE`,\n // which would be catastrophic if pointed at a real database. The whole\n // function is test-only, but the npm subpath export means an app could\n // import it by accident — fail loud instead of running.\n if (process.env.NODE_ENV === 'production') {\n throw new Error(\n 'createTestDb() is not allowed when NODE_ENV=production — it issues CREATE/DROP SCHEMA and is intended for tests only',\n )\n }\n\n const url = process.env.DATABASE_TEST_URL || process.env.DATABASE_URL\n if (!url) {\n throw new Error('DATABASE_TEST_URL (or DATABASE_URL) env var is required for integration tests')\n }\n\n const schemaName = `test_${crypto.randomUUID().replaceAll('-', '').slice(0, 12)}`\n\n // First connection: create the schema. We can't pass `search_path` as a\n // connection startup parameter for this client because the schema doesn't\n // exist yet — postgres rejects connections asking to enter a missing\n // schema with `invalid value for parameter \"search_path\"`.\n const setupConnection = postgres(url, { max: 1 })\n try {\n await setupConnection.unsafe(`CREATE SCHEMA \"${schemaName}\"`)\n } finally {\n // Always close the setup connection — even on CREATE SCHEMA failure\n // (rare with a UUID-derived name, but possible if the role lacks\n // permission). Without this, a thrown error would leak a postgres\n // connection until the process exits.\n await setupConnection.end()\n }\n\n // Working connection pool: every connection in the pool starts with\n // `search_path` pointing at the test schema. Without this, a second\n // pooled connection would fall back to the default search_path and miss\n // the test tables — which is exactly the bug that surfaced when concurrent\n // queries (e.g. tryClaim's race test) bypassed connection 1.\n const connection = postgres(url, {\n max: 3,\n idle_timeout: 10,\n connection: {\n search_path: `\"${schemaName}\",public`,\n },\n })\n\n const db = drizzle(connection)\n\n return {\n db,\n schema: schemaName,\n\n async push(schemas: Record<string, unknown>) {\n // Use createRequire to load drizzle-kit/api via Node's CJS resolver.\n // drizzle-kit/api.mjs bundles CJS code with require('fs') — Vite's ESM\n // transform replaces require() with a polyfill that throws. Native CJS bypasses this.\n const { createRequire } = await import('node:module')\n const require = createRequire(import.meta.url)\n const { pushSchema } = require('drizzle-kit/api') as typeof import('drizzle-kit/api')\n\n // Use default schemaFilters ([\"public\"]). Our tables are unqualified (= public),\n // and SET search_path ensures DDL actually creates them in the test schema.\n const result = await pushSchema(schemas, withRowsShim(db))\n await result.apply()\n },\n\n async cleanup() {\n await db.execute(sql.raw(`DROP SCHEMA IF EXISTS \"${schemaName}\" CASCADE`))\n await connection.end()\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// In-memory TableClient shim — for unit tests that exercise consumer code\n// (LockService, ContentClient draft methods, etc.) without spinning up\n// Postgres. Implements the subset of TableClient that consumers actually\n// use; the matcher understands the same WhereClause shape (eq shorthand,\n// lt/gte/in/notIn operators, $or branches, jsonb-path keys are NOT\n// supported — use the real DB for those).\n//\n// Each consumer test previously hand-rolled its own ~100-LoC version of\n// this; collapsed into one place per audit-quality-content L#12.\n// ---------------------------------------------------------------------------\n\n/** Operators recognized by the in-memory matcher. Mirrors WhereClause. */\ntype MemOp =\n | { eq: unknown }\n | { ne: unknown }\n | { in: unknown[] }\n | { notIn: unknown[] }\n | { lt: unknown }\n | { lte: unknown }\n | { gt: unknown }\n | { gte: unknown }\n | { isNull: true }\n | { isNotNull: true }\n\nfunction matchesOp(rowVal: unknown, op: MemOp): boolean {\n if ('eq' in op) return rowVal === op.eq\n if ('ne' in op) return rowVal !== op.ne\n if ('in' in op) return op.in.includes(rowVal)\n if ('notIn' in op) return !op.notIn.includes(rowVal)\n if ('lt' in op) return (rowVal as Date | number) < (op.lt as Date | number)\n if ('lte' in op) return (rowVal as Date | number) <= (op.lte as Date | number)\n if ('gt' in op) return (rowVal as Date | number) > (op.gt as Date | number)\n if ('gte' in op) return (rowVal as Date | number) >= (op.gte as Date | number)\n if ('isNull' in op) return rowVal === null || rowVal === undefined\n if ('isNotNull' in op) return rowVal !== null && rowVal !== undefined\n return false\n}\n\n/** Recursive matcher: handles top-level keys, $or, value shorthand, and operator objects. */\nfunction matchesRow(row: Record<string, unknown>, where: Record<string, unknown>): boolean {\n for (const [key, val] of Object.entries(where)) {\n if (key === '$or') {\n const branches = val as Record<string, unknown>[]\n if (!branches.some((b) => matchesRow(row, b))) return false\n continue\n }\n if (key === '$and') {\n const branches = val as Record<string, unknown>[]\n if (!branches.every((b) => matchesRow(row, b))) return false\n continue\n }\n const rowVal = row[key]\n if (val === null) {\n if (rowVal !== null && rowVal !== undefined) return false\n continue\n }\n if (val !== null && typeof val === 'object' && !(val instanceof Date)) {\n if (!matchesOp(rowVal, val as MemOp)) return false\n continue\n }\n if (rowVal !== val) return false\n }\n return true\n}\n\n/** OrderBy spec mirrors TableClient's `OrderBySpec`. */\nexport interface InMemoryOrderBy {\n column: string\n dir?: 'asc' | 'desc'\n}\n\n/** Subset of TableClient methods used by tests. */\nexport interface InMemoryTableClient<Row extends Record<string, unknown>> {\n rows: Row[] // direct mutation for test seeding / inspection\n reset(initial?: Row[]): void\n findOne(where: Record<string, unknown>): Promise<Row | null>\n findMany(opts?: {\n where?: Record<string, unknown>\n orderBy?: InMemoryOrderBy[]\n limit?: number\n offset?: number\n }): Promise<Row[]>\n exists(where: Record<string, unknown>): Promise<boolean>\n count(where?: Record<string, unknown>): Promise<number>\n insert(values: Partial<Row>): Promise<Row>\n update(where: Record<string, unknown>, patch: Partial<Row>): Promise<Row | null>\n upsert(\n values: Partial<Row>,\n opts: { target: string[] | string; set?: Partial<Row> },\n ): Promise<Row>\n delete(where: Record<string, unknown>): Promise<Row | null>\n deleteMany(where: Record<string, unknown>): Promise<Row[]>\n tryClaim(\n values: Partial<Row>,\n opts: {\n target: string | string[]\n replaceWhen: Record<string, unknown>\n set?: Partial<Row>\n },\n ): Promise<{ acquired: true; row: Row } | { acquired: false; row: Row }>\n aggregate?: (opts: {\n select: Record<string, { fn: 'max' | 'min' | 'count' | 'sum' | 'avg'; column?: string }>\n where?: Record<string, unknown>\n }) => Promise<Array<Record<string, unknown>>>\n}\n\n/**\n * Build an in-memory TableClient stand-in for unit tests. Pass\n * `idGenerator` if your test wants deterministic synthetic IDs\n * (defaults to a monotonic `row-1`, `row-2`, ... counter).\n *\n * Example:\n * ```ts\n * const drafts = createInMemoryTableClient<DraftRow>({ idField: 'id' })\n * vi.mock('@murumets-ee/content/schema', () => ({\n * getContentTables: () => ({ drafts: { makeClient: () => drafts } })\n * }))\n * ```\n */\nexport function createInMemoryTableClient<Row extends Record<string, unknown>>(\n options: { idField?: keyof Row & string; idGenerator?: () => string; initial?: Row[] } = {},\n): InMemoryTableClient<Row> {\n const idField = (options.idField ?? 'id') as keyof Row & string\n let nextId = 1\n const idGen = options.idGenerator ?? (() => `row-${nextId++}`)\n\n const state: { rows: Row[] } = { rows: options.initial ? [...options.initial] : [] }\n\n const client: InMemoryTableClient<Row> = {\n get rows() {\n return state.rows\n },\n set rows(next: Row[]) {\n state.rows = next\n },\n\n reset(initial?: Row[]) {\n state.rows = initial ? [...initial] : []\n nextId = 1\n },\n\n async findOne(where) {\n return state.rows.find((r) => matchesRow(r, where)) ?? null\n },\n\n async findMany(opts) {\n let out = opts?.where\n ? state.rows.filter((r) => matchesRow(r, opts.where as Record<string, unknown>))\n : [...state.rows]\n if (opts?.orderBy && opts.orderBy.length > 0) {\n out = [...out].sort((a, b) => {\n for (const spec of opts.orderBy ?? []) {\n const av = a[spec.column]\n const bv = b[spec.column]\n const dir = spec.dir === 'desc' ? -1 : 1\n // Date and number comparisons via subtraction; string via localeCompare.\n if (av instanceof Date && bv instanceof Date) {\n const d = (av.getTime() - bv.getTime()) * dir\n if (d !== 0) return d\n } else if (typeof av === 'number' && typeof bv === 'number') {\n const d = (av - bv) * dir\n if (d !== 0) return d\n } else if (typeof av === 'string' && typeof bv === 'string') {\n const d = av.localeCompare(bv) * dir\n if (d !== 0) return d\n } else {\n // Mixed / nullable: fall through to next spec.\n }\n }\n return 0\n })\n }\n const offset = opts?.offset ?? 0\n const limit = opts?.limit\n if (offset > 0) out = out.slice(offset)\n if (limit !== undefined && limit >= 0) out = out.slice(0, limit)\n return out\n },\n\n async aggregate(opts) {\n const subset = opts?.where\n ? state.rows.filter((r) => matchesRow(r, opts.where as Record<string, unknown>))\n : state.rows\n const result: Record<string, unknown> = {}\n for (const [alias, spec] of Object.entries(opts.select)) {\n if (spec.fn === 'count') {\n result[alias] = subset.length\n } else if (spec.fn === 'max' && spec.column) {\n let max: number | null = null\n for (const r of subset) {\n const v = r[spec.column]\n if (typeof v === 'number') {\n if (max === null || v > max) max = v\n }\n }\n result[alias] = max\n } else if (spec.fn === 'min' && spec.column) {\n let min: number | null = null\n for (const r of subset) {\n const v = r[spec.column]\n if (typeof v === 'number') {\n if (min === null || v < min) min = v\n }\n }\n result[alias] = min\n } else if (spec.fn === 'sum' && spec.column) {\n let sum = 0\n for (const r of subset) {\n const v = r[spec.column]\n if (typeof v === 'number') sum += v\n }\n result[alias] = sum\n }\n }\n return [result]\n },\n\n async exists(where) {\n return state.rows.some((r) => matchesRow(r, where))\n },\n\n async count(where) {\n if (!where) return state.rows.length\n return state.rows.filter((r) => matchesRow(r, where)).length\n },\n\n async insert(values) {\n const row = { [idField]: idGen(), ...values } as Row\n state.rows.push(row)\n return row\n },\n\n async update(where, patch) {\n const matched = state.rows.filter((r) => matchesRow(r, where))\n if (matched.length === 0) return null\n // TableClient.update expects at-most-one match\n const target = matched[0] as Row\n Object.assign(target, patch)\n return target\n },\n\n async upsert(values, opts) {\n const targetKeys = Array.isArray(opts.target) ? opts.target : [opts.target]\n const targetWhere: Record<string, unknown> = {}\n for (const k of targetKeys) targetWhere[k] = (values as Record<string, unknown>)[k]\n const existing = state.rows.find((r) => matchesRow(r, targetWhere))\n if (existing) {\n Object.assign(existing, opts.set ?? values)\n return existing\n }\n const row = { [idField]: idGen(), ...values } as Row\n state.rows.push(row)\n return row\n },\n\n async delete(where) {\n const idx = state.rows.findIndex((r) => matchesRow(r, where))\n if (idx === -1) return null\n const [removed] = state.rows.splice(idx, 1)\n return (removed ?? null) as Row | null\n },\n\n async deleteMany(where) {\n const matched = state.rows.filter((r) => matchesRow(r, where))\n state.rows = state.rows.filter((r) => !matched.includes(r))\n return matched\n },\n\n async tryClaim(values, opts) {\n const targetKeys = Array.isArray(opts.target) ? opts.target : [opts.target]\n const targetWhere: Record<string, unknown> = {}\n for (const k of targetKeys) targetWhere[k] = (values as Record<string, unknown>)[k]\n const existing = state.rows.find((r) => matchesRow(r, targetWhere))\n\n if (!existing) {\n const row = { [idField]: idGen(), ...values } as Row\n state.rows.push(row)\n return { acquired: true, row }\n }\n\n if (matchesRow(existing, opts.replaceWhen)) {\n Object.assign(existing, opts.set ?? values)\n return { acquired: true, row: existing }\n }\n\n return { acquired: false, row: existing }\n },\n }\n\n return client\n}\n\n/**\n * Wrap a Drizzle handle so `.execute()` returns an array that *also* exposes\n * a `.rows` property. `drizzle-kit/api`'s `pushSchema` reads `res.rows`, but\n * postgres-js (the driver Drizzle is built on here) returns plain arrays\n * with no `.rows`. The shim is local to test-utils — production code never\n * needs it.\n */\nfunction withRowsShim(db: PostgresJsDatabase): PostgresJsDatabase {\n return new Proxy(db, {\n get(target, prop, receiver) {\n if (prop === 'execute') {\n return async (...args: unknown[]) => {\n const result = await (target.execute as (...a: unknown[]) => Promise<unknown[]>)(...args)\n if (Array.isArray(result) && !('rows' in result)) {\n Object.defineProperty(result, 'rows', { value: [...result], enumerable: false })\n }\n return result\n }\n }\n return Reflect.get(target, prop, receiver)\n },\n })\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@murumets-ee/db",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "license": "Elastic-2.0",
5
5
  "type": "module",
6
6
  "exports": {