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