@sequenceholdings/orm 0.1.3 → 2.1.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.
Files changed (59) hide show
  1. package/README.md +108 -38
  2. package/dist/authoring.cjs +128 -16
  3. package/dist/authoring.cjs.map +1 -1
  4. package/dist/authoring.d.cts +21 -4
  5. package/dist/authoring.d.ts +21 -4
  6. package/dist/authoring.js +128 -16
  7. package/dist/authoring.js.map +1 -1
  8. package/dist/canonical.cjs +83 -9
  9. package/dist/canonical.cjs.map +1 -1
  10. package/dist/canonical.d.cts +47 -1
  11. package/dist/canonical.d.ts +47 -1
  12. package/dist/canonical.js +83 -10
  13. package/dist/canonical.js.map +1 -1
  14. package/dist/cli.cjs +816 -134
  15. package/dist/cli.cjs.map +1 -1
  16. package/dist/cli.js +818 -136
  17. package/dist/cli.js.map +1 -1
  18. package/dist/client.cjs.map +1 -1
  19. package/dist/client.js.map +1 -1
  20. package/dist/compiler.cjs +488 -96
  21. package/dist/compiler.cjs.map +1 -1
  22. package/dist/compiler.d.cts +34 -2
  23. package/dist/compiler.d.ts +34 -2
  24. package/dist/compiler.js +489 -97
  25. package/dist/compiler.js.map +1 -1
  26. package/dist/index.cjs +420 -71
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +266 -9
  29. package/dist/index.d.ts +266 -9
  30. package/dist/index.js +410 -73
  31. package/dist/index.js.map +1 -1
  32. package/dist/ir.cjs +146 -12
  33. package/dist/ir.cjs.map +1 -1
  34. package/dist/ir.d.cts +161 -5
  35. package/dist/ir.d.ts +161 -5
  36. package/dist/ir.js +144 -13
  37. package/dist/ir.js.map +1 -1
  38. package/dist/{naming-CTvWSRTf.d.ts → naming-DCn94LJ4.d.ts} +15 -1
  39. package/dist/{naming-DRyinU4u.d.cts → naming-DPglwzkB.d.cts} +15 -1
  40. package/dist/predicate-v2.cjs +136 -11
  41. package/dist/predicate-v2.cjs.map +1 -1
  42. package/dist/predicate-v2.d.cts +3 -0
  43. package/dist/predicate-v2.d.ts +3 -0
  44. package/dist/predicate-v2.js +136 -11
  45. package/dist/predicate-v2.js.map +1 -1
  46. package/dist/sdl.cjs +460 -13
  47. package/dist/sdl.cjs.map +1 -1
  48. package/dist/sdl.d.cts +1 -1
  49. package/dist/sdl.d.ts +1 -1
  50. package/dist/sdl.js +459 -14
  51. package/dist/sdl.js.map +1 -1
  52. package/package.json +6 -4
  53. package/src/codegen-gql/preset.cjs +64 -0
  54. package/src/codegen-gql/server-fns.cjs +11 -6
  55. package/templates/orm-namespace/codegen.preset.cjs +17 -0
  56. package/templates/orm-namespace/codegen.ts +1 -1
  57. package/templates/orm-namespace/package.json +3 -0
  58. package/templates/orm-namespace/schema/tables.ts +1 -1
  59. package/templates/orm-namespace/sequence.config.ts +1 -1
@@ -31,10 +31,12 @@ var PolicyCompileError = class extends Error {
31
31
  };
32
32
 
33
33
  // src/constants.ts
34
+ var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
34
35
  var CAPABILITY_KEY_PATTERN = /^(?:org\/)?[a-z0-9-]{1,64}$/;
35
36
  var CANONICAL_FORMAT = 2;
36
37
  var IDENTIFIER_RE = /^[a-z][a-z0-9_]*$/;
37
38
  var identifier = zod.z.string().min(1).max(63).regex(IDENTIFIER_RE);
39
+ var columnIdentifier = zod.z.string().min(1).max(63).regex(COLUMN_IDENTIFIER_PATTERN);
38
40
  var COLUMN_TYPES = [
39
41
  "uuid",
40
42
  "text",
@@ -55,18 +57,33 @@ var canonicalEnumSchema = zod.z.object({
55
57
  var canonicalColumnSchema = zod.z.object({
56
58
  /** Property name in TS (camelCase); columnName is the Postgres name. */
57
59
  name: zod.z.string().min(1).max(63),
58
- columnName: identifier,
60
+ columnName: columnIdentifier,
59
61
  type: zod.z.union([columnTypeSchema, zod.z.object({ enum: identifier })]),
60
62
  nullable: zod.z.boolean().default(true),
61
63
  unique: zod.z.boolean().default(false),
62
64
  /** Literal default, or one of the allowlisted SQL functions. */
63
65
  default: zod.z.union([zod.z.string(), zod.z.number(), zod.z.boolean(), zod.z.null()]).optional(),
64
66
  defaultFn: zod.z.enum(["gen_random_uuid()", "now()"]).optional(),
67
+ /**
68
+ * Stamp this column from the database clock on UPDATE (`now()`). The
69
+ * column stays insertable (defaults fill gaps) but is omitted from
70
+ * SetInput / settable so callers cannot backdate it.
71
+ */
72
+ onUpdateFn: zod.z.enum(["now()"]).optional(),
65
73
  description: zod.z.string().max(500).optional(),
66
74
  /** Migration hints — same semantics as v1. */
67
- renamedFrom: identifier.optional(),
75
+ renamedFrom: columnIdentifier.optional(),
68
76
  cast: zod.z.string().max(200).optional(),
69
77
  deprecated: zod.z.boolean().default(false)
78
+ }).superRefine((column, ctx) => {
79
+ if (column.onUpdateFn === void 0) return;
80
+ if (typeof column.type !== "string" || column.type !== "timestamptz" && column.type !== "date") {
81
+ ctx.addIssue({
82
+ code: "custom",
83
+ path: ["onUpdateFn"],
84
+ message: `onUpdateFn '${column.onUpdateFn}' is only valid on timestamptz/date columns`
85
+ });
86
+ }
70
87
  });
71
88
  var canonicalLinkSchema = zod.z.object({
72
89
  name: zod.z.string().min(1).max(63),
@@ -74,15 +91,15 @@ var canonicalLinkSchema = zod.z.object({
74
91
  /** Target table name (namespace-local). Absent for toPlatformUser. */
75
92
  target: identifier.optional(),
76
93
  /** FK column on the owning side (toOne/toPlatformUser: on this table; toMany: on the target). */
77
- on: identifier,
94
+ on: columnIdentifier,
78
95
  /** Referenced column on the target (default: target's PK / platform user `sub`). */
79
- references: identifier.optional(),
96
+ references: columnIdentifier.optional(),
80
97
  /** enforce: true emits a real FK constraint (same-namespace toOne only). */
81
98
  enforce: zod.z.boolean().default(false),
82
99
  description: zod.z.string().max(500).optional()
83
100
  });
84
101
  var canonicalIndexSchema = zod.z.object({
85
- columns: zod.z.array(identifier).min(1),
102
+ columns: zod.z.array(columnIdentifier).min(1),
86
103
  unique: zod.z.boolean().default(false),
87
104
  name: identifier.optional()
88
105
  });
@@ -173,6 +190,13 @@ var canonicalCowMutationSchema = zod.z.object({
173
190
  }
174
191
  }
175
192
  });
193
+ var canonicalForeignKeySchema = zod.z.object({
194
+ name: identifier,
195
+ columns: zod.z.array(columnIdentifier).min(1).max(8),
196
+ target: identifier,
197
+ references: zod.z.array(columnIdentifier).min(1).max(8),
198
+ deferrable: zod.z.enum(["initially-immediate", "initially-deferred"]).optional()
199
+ }).strict();
176
200
  var canonicalTableSchema = zod.z.object({
177
201
  table: identifier,
178
202
  description: zod.z.string().max(2e3).optional(),
@@ -181,7 +205,7 @@ var canonicalTableSchema = zod.z.object({
181
205
  /** Synced/reflected read-only tables (`source: gold.*`) — v1 semantics. */
182
206
  source: zod.z.string().regex(/^gold\.[a-z][a-z0-9_]*$/).optional(),
183
207
  columns: zod.z.array(canonicalColumnSchema).default([]),
184
- primaryKey: zod.z.array(identifier).min(1).max(8).optional(),
208
+ primaryKey: zod.z.array(columnIdentifier).min(1).max(8).optional(),
185
209
  links: zod.z.array(canonicalLinkSchema).default([]),
186
210
  indexes: zod.z.array(canonicalIndexSchema).default([]),
187
211
  policies: canonicalPoliciesSchema.default({}),
@@ -192,14 +216,16 @@ var canonicalTableSchema = zod.z.object({
192
216
  */
193
217
  actions: zod.z.array(canonicalActionSchema).default([]),
194
218
  /** Optional (not defaulted) so pre-CoW canonical documents keep byte-identical hashes. */
195
- cowMutations: zod.z.array(canonicalCowMutationSchema).optional()
219
+ cowMutations: zod.z.array(canonicalCowMutationSchema).optional(),
220
+ /** Omitted by old definitions, preserving their existing content hashes. */
221
+ foreignKeys: zod.z.array(canonicalForeignKeySchema).max(100).optional()
196
222
  });
197
223
  var canonicalViewSchema = zod.z.object({
198
224
  view: identifier,
199
225
  description: zod.z.string().max(2e3).optional(),
200
226
  columns: zod.z.array(canonicalColumnSchema).min(1),
201
227
  /** Logical identity for GraphQL only; PostgreSQL views have no PK constraint. */
202
- primaryKey: zod.z.array(identifier).min(1).max(8).optional(),
228
+ primaryKey: zod.z.array(columnIdentifier).min(1).max(8).optional(),
203
229
  links: zod.z.array(canonicalLinkSchema).default([]),
204
230
  policies: zod.z.object({ read: policyExpressionSchema }).strict(),
205
231
  query: zod.z.string().min(1).max(1e5).refine(isSingleViewQuerySql, {
@@ -212,6 +238,9 @@ var canonicalViewSchema = zod.z.object({
212
238
  if (column.default !== void 0 || column.defaultFn !== void 0) {
213
239
  ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare a default` });
214
240
  }
241
+ if (column.onUpdateFn !== void 0) {
242
+ ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare onUpdateFn` });
243
+ }
215
244
  if (column.unique) {
216
245
  ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare unique` });
217
246
  }
@@ -245,6 +274,45 @@ zod.z.object({
245
274
  ctx.addIssue({ code: "custom", path: ["tables", index, "table"], message: `duplicate relation '${table.table}'` });
246
275
  }
247
276
  seen.add(table.table);
277
+ if (table.source !== void 0) {
278
+ for (const [columnIndex, column] of table.columns.entries()) {
279
+ if (column.onUpdateFn !== void 0) {
280
+ ctx.addIssue({
281
+ code: "custom",
282
+ path: ["tables", index, "columns", columnIndex],
283
+ message: `synced table '${table.table}' column '${column.name}' cannot declare onUpdateFn`
284
+ });
285
+ }
286
+ }
287
+ }
288
+ }
289
+ const fkTables = new Map(definition.tables.map((table) => [table.table, table]));
290
+ for (const [tableIndex, table] of definition.tables.entries()) {
291
+ const names = /* @__PURE__ */ new Set([
292
+ ...table.primaryKey?.length ? [`${table.table}_pkey`] : [],
293
+ ...table.columns.filter((column) => column.unique && !(table.primaryKey?.length === 1 && table.primaryKey[0] === column.columnName)).map((column) => `${table.table}_${column.columnName}_unique`),
294
+ ...table.links.filter((link) => link.enforce).map((link) => `fk_${table.table}_${link.name}`)
295
+ ]);
296
+ for (const [index, fk] of (table.foreignKeys ?? []).entries()) {
297
+ const fail = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "foreignKeys", index], message });
298
+ if (names.has(fk.name)) fail(`duplicate foreign key '${fk.name}'`);
299
+ names.add(fk.name);
300
+ const target = fkTables.get(fk.target);
301
+ if (table.source || !target || target.source) {
302
+ fail("foreign keys must connect owned tables in the same namespace");
303
+ continue;
304
+ }
305
+ if (fk.columns.length !== fk.references.length) fail("foreign key columns and references must have the same number of columns");
306
+ if (new Set(fk.columns).size !== fk.columns.length || new Set(fk.references).size !== fk.references.length) fail("foreign key columns must not repeat");
307
+ for (const [position, name] of fk.columns.entries()) {
308
+ const sourceColumn = table.columns.find((column) => column.columnName === name);
309
+ const targetColumn = target.columns.find((column) => column.columnName === fk.references[position]);
310
+ if (!sourceColumn || !targetColumn) fail("foreign key references an unknown column");
311
+ else if (JSON.stringify(sourceColumn.type) !== JSON.stringify(targetColumn.type)) fail("foreign key column types must match");
312
+ }
313
+ const uniqueKeys = [target.primaryKey ?? [], ...target.indexes.filter((index2) => index2.unique).map((index2) => index2.columns), ...target.columns.filter((column) => column.unique).map((column) => [column.columnName])];
314
+ if (!uniqueKeys.some((key) => key.length === fk.references.length && key.every((column) => fk.references.includes(column)))) fail("foreign key target columns must form a unique key");
315
+ }
248
316
  }
249
317
  const tableByName = new Map(definition.tables.map((table) => [table.table, table]));
250
318
  for (const [tableIndex, table] of definition.tables.entries()) {
@@ -363,7 +431,10 @@ zod.z.object({
363
431
  });
364
432
 
365
433
  // src/ir/index.ts
366
- var IR_VERSION = 1;
434
+ var IR_VERSION = 2;
435
+ var LEGACY_IR_VERSION = 1;
436
+ var irVersionSchema = zod.z.union([zod.z.literal(LEGACY_IR_VERSION), zod.z.literal(IR_VERSION)]);
437
+ zod.z.object({ irVersion: irVersionSchema });
367
438
  var CLAIMS_CONTRACT = {
368
439
  sub: "string",
369
440
  org: "string?",
@@ -490,6 +561,33 @@ var aggregateRootSchema = zod.z.object({
490
561
  nodes: selectionIrSchema.optional(),
491
562
  includeIf: includeIfSchema.optional()
492
563
  });
564
+ var DATE_BUCKETS = ["year", "quarter", "month", "week", "day", "hour"];
565
+ var groupByKeyIrSchema = zod.z.object({
566
+ column: zod.z.string(),
567
+ bucket: zod.z.enum(DATE_BUCKETS).optional()
568
+ });
569
+ var aggregateGroupsRootSchema = zod.z.object({
570
+ kind: zod.z.literal("aggregateGroups"),
571
+ alias: zod.z.string(),
572
+ table: zod.z.string(),
573
+ /** Composed at compile time: AND(policy, documentWhere?, dynamicFilter?). */
574
+ where: predIrSchema,
575
+ groupBy: zod.z.array(groupByKeyIrSchema).min(1),
576
+ /**
577
+ * The `key { … }` projection: which group columns appear in each entry's key
578
+ * object, in selection order. A subset of `groupBy` (the compiler rejects a
579
+ * key field that is not a group key — it would be indeterminate within the
580
+ * group). Empty when the document selects only `aggregate`.
581
+ */
582
+ key: zod.z.array(zod.z.object({ alias: zod.z.string(), column: zod.z.string() })).default([]),
583
+ count: zod.z.object({ columns: zod.z.array(zod.z.string()).optional(), distinct: zod.z.boolean().optional() }).optional(),
584
+ sum: zod.z.array(zod.z.string()).optional(),
585
+ avg: zod.z.array(zod.z.string()).optional(),
586
+ /** Bounds the GROUP count, not the scanned rows — see MAX_GROUPS. */
587
+ limit: limitIrSchema,
588
+ offset: zod.z.object({ variable: zod.z.string().optional(), value: zod.z.number().int().optional() }).optional(),
589
+ includeIf: includeIfSchema.optional()
590
+ });
493
591
  var writeValuesSchema = zod.z.union([
494
592
  zod.z.object({ slot: zod.z.string() }),
495
593
  zod.z.object({ entries: zod.z.array(zod.z.object({ column: zod.z.string(), value: valueIrSchema })) })
@@ -535,12 +633,15 @@ var mutationRootSchema = zod.z.object({
535
633
  insertCheck: predIrSchema.optional(),
536
634
  returning: selectionIrSchema.nullable(),
537
635
  affectedRows: zod.z.boolean().default(false),
636
+ /** v2: abort the transaction when the actual write count differs, regardless of selection. */
637
+ assertAffectedRows: zod.z.number().int().min(0).max(2147483647).optional(),
538
638
  includeIf: includeIfSchema.optional()
539
639
  });
540
640
  var rootIrSchema = zod.z.discriminatedUnion("kind", [
541
641
  selectRootSchema,
542
642
  byPkRootSchema,
543
643
  aggregateRootSchema,
644
+ aggregateGroupsRootSchema,
544
645
  mutationRootSchema
545
646
  ]);
546
647
  var INPUT_REF_KINDS = [
@@ -570,7 +671,7 @@ var variableIrSchema = zod.z.object({
570
671
  defaultValue: variableDefaultIrSchema.optional()
571
672
  });
572
673
  var compiledOperationSchema = zod.z.object({
573
- irVersion: zod.z.literal(IR_VERSION),
674
+ irVersion: irVersionSchema,
574
675
  name: zod.z.string(),
575
676
  opHash: zod.z.string().regex(/^sha256:[0-9a-f]{64}$/),
576
677
  kind: zod.z.enum(["query", "mutation"]),
@@ -582,6 +683,13 @@ var compiledOperationSchema = zod.z.object({
582
683
  /** Every table the op touches — data-token scoping + audit. */
583
684
  tableSet: zod.z.array(zod.z.string()).min(1),
584
685
  roots: zod.z.array(rootIrSchema).min(1)
686
+ }).superRefine((operation, ctx) => {
687
+ for (const [index, root] of operation.roots.entries()) {
688
+ if (root.kind !== "mutation" || root.assertAffectedRows === void 0) continue;
689
+ if (operation.irVersion < 2 || operation.kind !== "mutation" || root.includeIf !== void 0) {
690
+ ctx.addIssue({ code: "custom", path: ["roots", index], message: "affected-row assertions require unconditional mutation roots and IR v2" });
691
+ }
692
+ }
585
693
  });
586
694
  var tableMetaIrSchema = zod.z.object({
587
695
  table: zod.z.string(),
@@ -618,18 +726,29 @@ var tableMetaIrSchema = zod.z.object({
618
726
  insertable: zod.z.array(zod.z.string()),
619
727
  settable: zod.z.array(zod.z.string()),
620
728
  appendable: zod.z.array(zod.z.string()),
729
+ /**
730
+ * Logical columns stamped with Postgres `now()` on UPDATE (canonical
731
+ * `onUpdateFn`). Omitted when empty so pre-onUpdate artifacts still parse.
732
+ */
733
+ touchOnUpdate: zod.z.array(zod.z.string()).optional(),
621
734
  constraints: zod.z.array(zod.z.object({ name: zod.z.string(), columns: zod.z.array(zod.z.string()) })),
622
735
  /** Depth/node caps for dynamic filter trees on this table. */
623
736
  dynamicFilterMaxDepth: zod.z.number().int().positive().default(6),
624
737
  dynamicFilterMaxNodes: zod.z.number().int().positive().default(64)
625
738
  });
626
739
  zod.z.object({
627
- irVersion: zod.z.literal(IR_VERSION),
740
+ irVersion: irVersionSchema,
628
741
  namespace: zod.z.string(),
629
742
  schemaHash: zod.z.string(),
630
743
  opsHash: zod.z.string(),
631
744
  tables: zod.z.record(zod.z.string(), tableMetaIrSchema),
632
745
  operations: zod.z.record(zod.z.string(), compiledOperationSchema)
746
+ }).superRefine((artifact, ctx) => {
747
+ for (const [name, operation] of Object.entries(artifact.operations)) {
748
+ if (operation.irVersion > artifact.irVersion) {
749
+ ctx.addIssue({ code: "custom", path: ["operations", name, "irVersion"], message: "operation requires a newer IR version than its artifact" });
750
+ }
751
+ }
633
752
  });
634
753
 
635
754
  // src/predicate-v2/parse.ts
@@ -750,6 +869,10 @@ var Parser = class {
750
869
  this.expect("rparen", "')'");
751
870
  return inner;
752
871
  }
872
+ if (this.peekKeyword("true") || this.peekKeyword("false")) {
873
+ const token = this.advance();
874
+ return { kind: "literal", value: token.value === "true" };
875
+ }
753
876
  if (this.peekKeyword("capability") || this.peekKeyword("role")) {
754
877
  const fn = this.peek().value;
755
878
  const node = this.parseBooleanCall(fn);
@@ -933,6 +1056,8 @@ var PolicyCompiler = class {
933
1056
  }
934
1057
  compile(node, table, depth) {
935
1058
  switch (node.kind) {
1059
+ case "literal":
1060
+ return { kind: "const", value: node.value };
936
1061
  case "and":
937
1062
  case "or":
938
1063
  return { kind: node.kind, operands: node.operands.map((operand) => this.compile(operand, table, depth)) };