@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
package/dist/sdl.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- export { C as CUSTOM_SCALARS, N as NUMERICISH_COLUMN_TYPES, O as ORDER_BY_ENUM, a as ORDER_BY_ENUM_VALUES, P as PLATFORM_USER_TYPENAME, T as TableNames, V as VARIABLE_SCALAR_NAMES, c as camelCase, b as columnTypeForScalar, d as comparisonExpName, e as enumTypeName, f as fieldName, g as graphqlScalarFor, p as pascalCase, t as tableNames, h as typeName } from './naming-DRyinU4u.cjs';
1
+ export { B as BUCKETABLE_COLUMN_TYPES, C as CUSTOM_SCALARS, D as DATE_BUCKET_ENUM, N as NUMERICISH_COLUMN_TYPES, O as ORDER_BY_ENUM, a as ORDER_BY_ENUM_VALUES, P as PLATFORM_USER_TYPENAME, T as TableNames, V as VARIABLE_SCALAR_NAMES, c as camelCase, b as columnTypeForScalar, d as comparisonExpName, e as enumTypeName, f as fieldName, g as graphqlScalarFor, p as pascalCase, t as tableNames, h as typeName } from './naming-DPglwzkB.cjs';
2
2
  import { CanonicalDefinition } from './canonical.cjs';
3
3
  import { z } from 'zod';
4
4
 
package/dist/sdl.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { C as CUSTOM_SCALARS, N as NUMERICISH_COLUMN_TYPES, O as ORDER_BY_ENUM, a as ORDER_BY_ENUM_VALUES, P as PLATFORM_USER_TYPENAME, T as TableNames, V as VARIABLE_SCALAR_NAMES, c as camelCase, b as columnTypeForScalar, d as comparisonExpName, e as enumTypeName, f as fieldName, g as graphqlScalarFor, p as pascalCase, t as tableNames, h as typeName } from './naming-CTvWSRTf.js';
1
+ export { B as BUCKETABLE_COLUMN_TYPES, C as CUSTOM_SCALARS, D as DATE_BUCKET_ENUM, N as NUMERICISH_COLUMN_TYPES, O as ORDER_BY_ENUM, a as ORDER_BY_ENUM_VALUES, P as PLATFORM_USER_TYPENAME, T as TableNames, V as VARIABLE_SCALAR_NAMES, c as camelCase, b as columnTypeForScalar, d as comparisonExpName, e as enumTypeName, f as fieldName, g as graphqlScalarFor, p as pascalCase, t as tableNames, h as typeName } from './naming-DCn94LJ4.js';
2
2
  import { CanonicalDefinition } from './canonical.js';
3
3
  import { z } from 'zod';
4
4
 
package/dist/sdl.js CHANGED
@@ -1,10 +1,16 @@
1
1
  import { z } from 'zod';
2
2
  import { createHash } from 'crypto';
3
3
 
4
+ // src/canonical/v2.ts
5
+
6
+ // src/constants.ts
7
+ var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
8
+
4
9
  // src/canonical/v2.ts
5
10
  var CANONICAL_FORMAT = 2;
6
11
  var IDENTIFIER_RE = /^[a-z][a-z0-9_]*$/;
7
12
  var identifier = z.string().min(1).max(63).regex(IDENTIFIER_RE);
13
+ var columnIdentifier = z.string().min(1).max(63).regex(COLUMN_IDENTIFIER_PATTERN);
8
14
  var COLUMN_TYPES = [
9
15
  "uuid",
10
16
  "text",
@@ -25,18 +31,33 @@ var canonicalEnumSchema = z.object({
25
31
  var canonicalColumnSchema = z.object({
26
32
  /** Property name in TS (camelCase); columnName is the Postgres name. */
27
33
  name: z.string().min(1).max(63),
28
- columnName: identifier,
34
+ columnName: columnIdentifier,
29
35
  type: z.union([columnTypeSchema, z.object({ enum: identifier })]),
30
36
  nullable: z.boolean().default(true),
31
37
  unique: z.boolean().default(false),
32
38
  /** Literal default, or one of the allowlisted SQL functions. */
33
39
  default: z.union([z.string(), z.number(), z.boolean(), z.null()]).optional(),
34
40
  defaultFn: z.enum(["gen_random_uuid()", "now()"]).optional(),
41
+ /**
42
+ * Stamp this column from the database clock on UPDATE (`now()`). The
43
+ * column stays insertable (defaults fill gaps) but is omitted from
44
+ * SetInput / settable so callers cannot backdate it.
45
+ */
46
+ onUpdateFn: z.enum(["now()"]).optional(),
35
47
  description: z.string().max(500).optional(),
36
48
  /** Migration hints — same semantics as v1. */
37
- renamedFrom: identifier.optional(),
49
+ renamedFrom: columnIdentifier.optional(),
38
50
  cast: z.string().max(200).optional(),
39
51
  deprecated: z.boolean().default(false)
52
+ }).superRefine((column, ctx) => {
53
+ if (column.onUpdateFn === void 0) return;
54
+ if (typeof column.type !== "string" || column.type !== "timestamptz" && column.type !== "date") {
55
+ ctx.addIssue({
56
+ code: "custom",
57
+ path: ["onUpdateFn"],
58
+ message: `onUpdateFn '${column.onUpdateFn}' is only valid on timestamptz/date columns`
59
+ });
60
+ }
40
61
  });
41
62
  var canonicalLinkSchema = z.object({
42
63
  name: z.string().min(1).max(63),
@@ -44,15 +65,15 @@ var canonicalLinkSchema = z.object({
44
65
  /** Target table name (namespace-local). Absent for toPlatformUser. */
45
66
  target: identifier.optional(),
46
67
  /** FK column on the owning side (toOne/toPlatformUser: on this table; toMany: on the target). */
47
- on: identifier,
68
+ on: columnIdentifier,
48
69
  /** Referenced column on the target (default: target's PK / platform user `sub`). */
49
- references: identifier.optional(),
70
+ references: columnIdentifier.optional(),
50
71
  /** enforce: true emits a real FK constraint (same-namespace toOne only). */
51
72
  enforce: z.boolean().default(false),
52
73
  description: z.string().max(500).optional()
53
74
  });
54
75
  var canonicalIndexSchema = z.object({
55
- columns: z.array(identifier).min(1),
76
+ columns: z.array(columnIdentifier).min(1),
56
77
  unique: z.boolean().default(false),
57
78
  name: identifier.optional()
58
79
  });
@@ -143,6 +164,13 @@ var canonicalCowMutationSchema = z.object({
143
164
  }
144
165
  }
145
166
  });
167
+ var canonicalForeignKeySchema = z.object({
168
+ name: identifier,
169
+ columns: z.array(columnIdentifier).min(1).max(8),
170
+ target: identifier,
171
+ references: z.array(columnIdentifier).min(1).max(8),
172
+ deferrable: z.enum(["initially-immediate", "initially-deferred"]).optional()
173
+ }).strict();
146
174
  var canonicalTableSchema = z.object({
147
175
  table: identifier,
148
176
  description: z.string().max(2e3).optional(),
@@ -151,7 +179,7 @@ var canonicalTableSchema = z.object({
151
179
  /** Synced/reflected read-only tables (`source: gold.*`) — v1 semantics. */
152
180
  source: z.string().regex(/^gold\.[a-z][a-z0-9_]*$/).optional(),
153
181
  columns: z.array(canonicalColumnSchema).default([]),
154
- primaryKey: z.array(identifier).min(1).max(8).optional(),
182
+ primaryKey: z.array(columnIdentifier).min(1).max(8).optional(),
155
183
  links: z.array(canonicalLinkSchema).default([]),
156
184
  indexes: z.array(canonicalIndexSchema).default([]),
157
185
  policies: canonicalPoliciesSchema.default({}),
@@ -162,14 +190,16 @@ var canonicalTableSchema = z.object({
162
190
  */
163
191
  actions: z.array(canonicalActionSchema).default([]),
164
192
  /** Optional (not defaulted) so pre-CoW canonical documents keep byte-identical hashes. */
165
- cowMutations: z.array(canonicalCowMutationSchema).optional()
193
+ cowMutations: z.array(canonicalCowMutationSchema).optional(),
194
+ /** Omitted by old definitions, preserving their existing content hashes. */
195
+ foreignKeys: z.array(canonicalForeignKeySchema).max(100).optional()
166
196
  });
167
197
  var canonicalViewSchema = z.object({
168
198
  view: identifier,
169
199
  description: z.string().max(2e3).optional(),
170
200
  columns: z.array(canonicalColumnSchema).min(1),
171
201
  /** Logical identity for GraphQL only; PostgreSQL views have no PK constraint. */
172
- primaryKey: z.array(identifier).min(1).max(8).optional(),
202
+ primaryKey: z.array(columnIdentifier).min(1).max(8).optional(),
173
203
  links: z.array(canonicalLinkSchema).default([]),
174
204
  policies: z.object({ read: policyExpressionSchema }).strict(),
175
205
  query: z.string().min(1).max(1e5).refine(isSingleViewQuerySql, {
@@ -182,6 +212,9 @@ var canonicalViewSchema = z.object({
182
212
  if (column.default !== void 0 || column.defaultFn !== void 0) {
183
213
  ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare a default` });
184
214
  }
215
+ if (column.onUpdateFn !== void 0) {
216
+ ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare onUpdateFn` });
217
+ }
185
218
  if (column.unique) {
186
219
  ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare unique` });
187
220
  }
@@ -215,6 +248,45 @@ var canonicalDefinitionSchema = z.object({
215
248
  ctx.addIssue({ code: "custom", path: ["tables", index, "table"], message: `duplicate relation '${table.table}'` });
216
249
  }
217
250
  seen.add(table.table);
251
+ if (table.source !== void 0) {
252
+ for (const [columnIndex, column] of table.columns.entries()) {
253
+ if (column.onUpdateFn !== void 0) {
254
+ ctx.addIssue({
255
+ code: "custom",
256
+ path: ["tables", index, "columns", columnIndex],
257
+ message: `synced table '${table.table}' column '${column.name}' cannot declare onUpdateFn`
258
+ });
259
+ }
260
+ }
261
+ }
262
+ }
263
+ const fkTables = new Map(definition.tables.map((table) => [table.table, table]));
264
+ for (const [tableIndex, table] of definition.tables.entries()) {
265
+ const names = /* @__PURE__ */ new Set([
266
+ ...table.primaryKey?.length ? [`${table.table}_pkey`] : [],
267
+ ...table.columns.filter((column) => column.unique && !(table.primaryKey?.length === 1 && table.primaryKey[0] === column.columnName)).map((column) => `${table.table}_${column.columnName}_unique`),
268
+ ...table.links.filter((link) => link.enforce).map((link) => `fk_${table.table}_${link.name}`)
269
+ ]);
270
+ for (const [index, fk] of (table.foreignKeys ?? []).entries()) {
271
+ const fail = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "foreignKeys", index], message });
272
+ if (names.has(fk.name)) fail(`duplicate foreign key '${fk.name}'`);
273
+ names.add(fk.name);
274
+ const target = fkTables.get(fk.target);
275
+ if (table.source || !target || target.source) {
276
+ fail("foreign keys must connect owned tables in the same namespace");
277
+ continue;
278
+ }
279
+ if (fk.columns.length !== fk.references.length) fail("foreign key columns and references must have the same number of columns");
280
+ if (new Set(fk.columns).size !== fk.columns.length || new Set(fk.references).size !== fk.references.length) fail("foreign key columns must not repeat");
281
+ for (const [position, name] of fk.columns.entries()) {
282
+ const sourceColumn = table.columns.find((column) => column.columnName === name);
283
+ const targetColumn = target.columns.find((column) => column.columnName === fk.references[position]);
284
+ if (!sourceColumn || !targetColumn) fail("foreign key references an unknown column");
285
+ else if (JSON.stringify(sourceColumn.type) !== JSON.stringify(targetColumn.type)) fail("foreign key column types must match");
286
+ }
287
+ const uniqueKeys = [target.primaryKey ?? [], ...target.indexes.filter((index2) => index2.unique).map((index2) => index2.columns), ...target.columns.filter((column) => column.unique).map((column) => [column.columnName])];
288
+ 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");
289
+ }
218
290
  }
219
291
  const tableByName = new Map(definition.tables.map((table) => [table.table, table]));
220
292
  for (const [tableIndex, table] of definition.tables.entries()) {
@@ -350,12 +422,13 @@ function normalizeCanonical(definition) {
350
422
  ...definition,
351
423
  enums: sortByName(definition.enums),
352
424
  tables: [...definition.tables].sort((a, b) => a.table.localeCompare(b.table)).map((t) => {
353
- const { cowMutations, ...rest } = t;
425
+ const { cowMutations, foreignKeys, ...rest } = t;
354
426
  return {
355
427
  ...rest,
356
428
  links: sortByName(t.links),
357
429
  indexes: [...t.indexes].sort((a, b) => (a.name ?? a.columns.join("_")).localeCompare(b.name ?? b.columns.join("_"))),
358
430
  actions: sortByName(t.actions),
431
+ ...foreignKeys?.length ? { foreignKeys: sortByName(foreignKeys) } : {},
359
432
  // Omitted entirely when absent/empty so pre-CoW documents hash unchanged.
360
433
  ...cowMutations !== void 0 && cowMutations.length > 0 ? { cowMutations: sortByName(cowMutations) } : {}
361
434
  };
@@ -444,6 +517,8 @@ var ORDER_BY_ENUM_VALUES = [
444
517
  "DESC_NULLS_LAST"
445
518
  ];
446
519
  var PLATFORM_USER_TYPENAME = "PlatformUser";
520
+ var DATE_BUCKET_ENUM = "DateBucket";
521
+ var BUCKETABLE_COLUMN_TYPES = ["timestamptz", "date"];
447
522
  function tableNames(table) {
448
523
  const root = camelCase(table);
449
524
  const rootPascal = pascalCase(table);
@@ -476,9 +551,327 @@ function tableNames(table) {
476
551
  sumFields: `${type}SumFields`,
477
552
  avgFields: `${type}AvgFields`,
478
553
  aggregateBoolExp: `${type}AggregateBoolExp`,
479
- aggregateBoolExpCount: `${type}AggregateBoolExpCount`
554
+ aggregateBoolExpCount: `${type}AggregateBoolExpCount`,
555
+ aggregateGroupsRoot: `${root}AggregateGroups`,
556
+ groupByColumnEnum: `${type}GroupByColumn`,
557
+ groupByInput: `${type}GroupBy`,
558
+ groupKey: `${type}GroupKey`,
559
+ aggregateGroup: `${type}AggregateGroup`
480
560
  };
481
561
  }
562
+ var IR_VERSION = 2;
563
+ var LEGACY_IR_VERSION = 1;
564
+ var irVersionSchema = z.union([z.literal(LEGACY_IR_VERSION), z.literal(IR_VERSION)]);
565
+ z.object({ irVersion: irVersionSchema });
566
+ var scalarLiteral = z.union([z.string(), z.number(), z.boolean(), z.null()]);
567
+ var valueIrSchema = z.discriminatedUnion("kind", [
568
+ z.object({ kind: z.literal("literal"), value: z.union([scalarLiteral, z.array(scalarLiteral)]) }),
569
+ z.object({ kind: z.literal("variable"), name: z.string() }),
570
+ z.object({ kind: z.literal("claim"), key: z.enum(["sub", "org"]) })
571
+ ]);
572
+ var CMP_OPS = [
573
+ "eq",
574
+ "neq",
575
+ "gt",
576
+ "gte",
577
+ "lt",
578
+ "lte",
579
+ "in",
580
+ "nin",
581
+ "like",
582
+ "ilike",
583
+ "isNull",
584
+ "contains",
585
+ "hasKey"
586
+ ];
587
+ var predIrSchema = z.lazy(
588
+ () => z.discriminatedUnion("kind", [
589
+ z.object({ kind: z.literal("and"), operands: z.array(predIrSchema).min(1) }),
590
+ z.object({ kind: z.literal("or"), operands: z.array(predIrSchema).min(1) }),
591
+ z.object({ kind: z.literal("not"), operand: predIrSchema }),
592
+ z.object({ kind: z.literal("cmp"), op: z.enum(CMP_OPS), column: z.string(), value: valueIrSchema }),
593
+ z.object({ kind: z.literal("exists"), link: z.string(), where: predIrSchema }),
594
+ z.object({
595
+ kind: z.literal("aggPredicate"),
596
+ link: z.string(),
597
+ fn: z.literal("count"),
598
+ op: z.enum(["eq", "neq", "gt", "gte", "lt", "lte"]),
599
+ value: z.number(),
600
+ where: predIrSchema.optional()
601
+ }),
602
+ z.object({ kind: z.literal("capability"), key: z.string() }),
603
+ z.object({ kind: z.literal("role"), key: z.string() }),
604
+ z.object({ kind: z.literal("dynamicFilter"), slot: z.string() }),
605
+ z.object({ kind: z.literal("const"), value: z.boolean() })
606
+ ])
607
+ );
608
+ var orderIrSchema = z.discriminatedUnion("kind", [
609
+ z.object({
610
+ kind: z.literal("column"),
611
+ column: z.string(),
612
+ /** Order through a to-one link: `link` is set and `column` is on the target. */
613
+ link: z.string().optional(),
614
+ direction: z.enum(["asc", "desc"]),
615
+ nulls: z.enum(["first", "last"]).optional()
616
+ }),
617
+ /** §4f: `$orderBy: [FooOrderBy!]` variable — validated + lowered at runtime. */
618
+ z.object({ kind: z.literal("dynamic"), slot: z.string() })
619
+ ]);
620
+ var limitIrSchema = z.object({
621
+ /** Fixed value or a variable, clamped to [1, max]; absent → default. */
622
+ variable: z.string().optional(),
623
+ value: z.number().int().positive().optional(),
624
+ default: z.number().int().positive(),
625
+ max: z.number().int().positive()
626
+ });
627
+ var selectionIrSchema = z.lazy(
628
+ () => z.object({
629
+ typename: z.string(),
630
+ columns: z.array(z.object({ alias: z.string(), column: z.string() })),
631
+ relations: z.array(z.object({
632
+ alias: z.string(),
633
+ link: z.string(),
634
+ selection: selectionIrSchema,
635
+ where: predIrSchema.optional(),
636
+ orderBy: z.array(orderIrSchema).optional(),
637
+ limit: limitIrSchema.optional(),
638
+ offset: z.object({ variable: z.string().optional(), value: z.number().int().optional() }).optional()
639
+ })),
640
+ aggregates: z.array(z.object({
641
+ alias: z.string(),
642
+ link: z.string().optional(),
643
+ where: predIrSchema.optional(),
644
+ count: z.object({ columns: z.array(z.string()).optional(), distinct: z.boolean().optional() }).optional(),
645
+ sum: z.array(z.string()).optional(),
646
+ avg: z.array(z.string()).optional(),
647
+ nodes: selectionIrSchema.optional()
648
+ }))
649
+ })
650
+ );
651
+ var includeIfSchema = z.object({ variable: z.string(), negated: z.boolean() });
652
+ var selectRootSchema = z.object({
653
+ kind: z.literal("select"),
654
+ alias: z.string(),
655
+ table: z.string(),
656
+ selection: selectionIrSchema,
657
+ /** Composed at compile time: AND(policy, documentWhere?, dynamicFilter?). */
658
+ where: predIrSchema,
659
+ orderBy: z.array(orderIrSchema).default([]),
660
+ distinctOn: z.union([z.array(z.string()), z.object({ slot: z.string() })]).optional(),
661
+ limit: limitIrSchema,
662
+ offset: z.object({ variable: z.string().optional(), value: z.number().int().optional() }).optional(),
663
+ includeIf: includeIfSchema.optional()
664
+ });
665
+ var byPkRootSchema = z.object({
666
+ kind: z.literal("byPk"),
667
+ alias: z.string(),
668
+ table: z.string(),
669
+ selection: selectionIrSchema,
670
+ pk: z.array(z.object({ column: z.string(), value: valueIrSchema })).min(1),
671
+ /** Policy predicate — ANDed with the PK match. */
672
+ where: predIrSchema,
673
+ includeIf: includeIfSchema.optional()
674
+ });
675
+ var aggregateRootSchema = z.object({
676
+ kind: z.literal("aggregate"),
677
+ alias: z.string(),
678
+ table: z.string(),
679
+ where: predIrSchema,
680
+ count: z.object({ columns: z.array(z.string()).optional(), distinct: z.boolean().optional() }).optional(),
681
+ sum: z.array(z.string()).optional(),
682
+ avg: z.array(z.string()).optional(),
683
+ nodes: selectionIrSchema.optional(),
684
+ includeIf: includeIfSchema.optional()
685
+ });
686
+ var DATE_BUCKETS = ["year", "quarter", "month", "week", "day", "hour"];
687
+ var groupByKeyIrSchema = z.object({
688
+ column: z.string(),
689
+ bucket: z.enum(DATE_BUCKETS).optional()
690
+ });
691
+ var aggregateGroupsRootSchema = z.object({
692
+ kind: z.literal("aggregateGroups"),
693
+ alias: z.string(),
694
+ table: z.string(),
695
+ /** Composed at compile time: AND(policy, documentWhere?, dynamicFilter?). */
696
+ where: predIrSchema,
697
+ groupBy: z.array(groupByKeyIrSchema).min(1),
698
+ /**
699
+ * The `key { … }` projection: which group columns appear in each entry's key
700
+ * object, in selection order. A subset of `groupBy` (the compiler rejects a
701
+ * key field that is not a group key — it would be indeterminate within the
702
+ * group). Empty when the document selects only `aggregate`.
703
+ */
704
+ key: z.array(z.object({ alias: z.string(), column: z.string() })).default([]),
705
+ count: z.object({ columns: z.array(z.string()).optional(), distinct: z.boolean().optional() }).optional(),
706
+ sum: z.array(z.string()).optional(),
707
+ avg: z.array(z.string()).optional(),
708
+ /** Bounds the GROUP count, not the scanned rows — see MAX_GROUPS. */
709
+ limit: limitIrSchema,
710
+ offset: z.object({ variable: z.string().optional(), value: z.number().int().optional() }).optional(),
711
+ includeIf: includeIfSchema.optional()
712
+ });
713
+ var writeValuesSchema = z.union([
714
+ z.object({ slot: z.string() }),
715
+ z.object({ entries: z.array(z.object({ column: z.string(), value: valueIrSchema })) })
716
+ ]);
717
+ var onConflictIrSchema = z.union([
718
+ z.object({ slot: z.string() }),
719
+ z.object({ constraint: z.string(), updateColumns: z.array(z.string()) })
720
+ ]);
721
+ var mutationRootSchema = z.object({
722
+ kind: z.literal("mutation"),
723
+ /**
724
+ * CRUD verbs plus the structured copy-on-write ops. `cowOverride` copies the
725
+ * synced row into the overlay (INSERT…SELECT, ON CONFLICT DO NOTHING) then
726
+ * applies `set` — both statements generated by the executor from this
727
+ * metadata, never authored SQL. `cowRevert` deletes the override so the
728
+ * coalesce view falls back to synced. Both are gated on a matching synced
729
+ * row and carry the write policy in `where` like update/delete.
730
+ */
731
+ op: z.enum(["insert", "insertOne", "update", "updateByPk", "delete", "deleteByPk", "cowOverride", "cowRevert"]),
732
+ alias: z.string(),
733
+ table: z.string(),
734
+ /** CoW ops: logical name of the synced twin (read through its policy-filtered view). */
735
+ cowSynced: z.string().optional(),
736
+ /** cowOverride: logical properties copied INSERT…SELECT from synced (pk included). */
737
+ cowCopyColumns: z.array(z.string()).optional(),
738
+ /** cowOverride: insert-policy WITH CHECK evaluated over the copied rows. */
739
+ cowCopyCheck: predIrSchema.optional(),
740
+ /** insert/insertOne: rows come from a slot ($object/$objects) or literals. */
741
+ objects: z.union([
742
+ z.object({ slot: z.string() }),
743
+ z.object({ rows: z.array(z.array(z.object({ column: z.string(), value: valueIrSchema }))) })
744
+ ]).optional(),
745
+ /** Server-pinned columns (e.g. author_sub ← claim('sub')) merged into every inserted row. */
746
+ pinnedColumns: z.array(z.object({ column: z.string(), value: valueIrSchema })).default([]),
747
+ onConflict: onConflictIrSchema.optional(),
748
+ set: writeValuesSchema.optional(),
749
+ append: writeValuesSchema.optional(),
750
+ inc: writeValuesSchema.optional(),
751
+ /** update/delete row filter: AND(writePolicy, documentWhere/pk, dynamicFilter?). */
752
+ where: predIrSchema.optional(),
753
+ pk: z.array(z.object({ column: z.string(), value: valueIrSchema })).optional(),
754
+ /** WITH CHECK analogue evaluated against NEW row values (insert/update). */
755
+ insertCheck: predIrSchema.optional(),
756
+ returning: selectionIrSchema.nullable(),
757
+ affectedRows: z.boolean().default(false),
758
+ /** v2: abort the transaction when the actual write count differs, regardless of selection. */
759
+ assertAffectedRows: z.number().int().min(0).max(2147483647).optional(),
760
+ includeIf: includeIfSchema.optional()
761
+ });
762
+ var rootIrSchema = z.discriminatedUnion("kind", [
763
+ selectRootSchema,
764
+ byPkRootSchema,
765
+ aggregateRootSchema,
766
+ aggregateGroupsRootSchema,
767
+ mutationRootSchema
768
+ ]);
769
+ var INPUT_REF_KINDS = [
770
+ "boolExp",
771
+ "orderBy",
772
+ "selectColumn",
773
+ "insertInput",
774
+ "setInput",
775
+ "appendInput",
776
+ "onConflict"
777
+ ];
778
+ var variableDefaultIrSchema = z.object({
779
+ value: z.union([scalarLiteral, z.array(scalarLiteral)])
780
+ });
781
+ var variableIrSchema = z.object({
782
+ name: z.string(),
783
+ type: z.union([
784
+ z.object({ kind: z.literal("scalar"), scalar: z.enum(COLUMN_TYPES) }),
785
+ /** `values` is the declared membership whitelist — the executor validates against it. */
786
+ z.object({ kind: z.literal("enum"), name: z.string(), values: z.array(z.string()).min(1) }),
787
+ /** §4f dynamic slot: validated against `tables[table]` whitelists at runtime. */
788
+ z.object({ kind: z.literal("inputRef"), input: z.enum(INPUT_REF_KINDS), table: z.string() })
789
+ ]),
790
+ nullable: z.boolean(),
791
+ list: z.boolean(),
792
+ /** Absent = no default declared; see {@link variableDefaultIrSchema}. */
793
+ defaultValue: variableDefaultIrSchema.optional()
794
+ });
795
+ var compiledOperationSchema = z.object({
796
+ irVersion: irVersionSchema,
797
+ name: z.string(),
798
+ opHash: z.string().regex(/^sha256:[0-9a-f]{64}$/),
799
+ kind: z.enum(["query", "mutation"]),
800
+ requires: z.object({
801
+ capability: z.string().optional(),
802
+ role: z.string().optional()
803
+ }).nullable(),
804
+ variables: z.array(variableIrSchema),
805
+ /** Every table the op touches — data-token scoping + audit. */
806
+ tableSet: z.array(z.string()).min(1),
807
+ roots: z.array(rootIrSchema).min(1)
808
+ }).superRefine((operation, ctx) => {
809
+ for (const [index, root] of operation.roots.entries()) {
810
+ if (root.kind !== "mutation" || root.assertAffectedRows === void 0) continue;
811
+ if (operation.irVersion < 2 || operation.kind !== "mutation" || root.includeIf !== void 0) {
812
+ ctx.addIssue({ code: "custom", path: ["roots", index], message: "affected-row assertions require unconditional mutation roots and IR v2" });
813
+ }
814
+ }
815
+ });
816
+ var tableMetaIrSchema = z.object({
817
+ table: z.string(),
818
+ schema: z.string(),
819
+ typename: z.string(),
820
+ /**
821
+ * True when statements over this relation evaluate a claims-gated synced
822
+ * view: synced twins (`source: gold.*`) materialize as views whose baked
823
+ * policy reads `current_setting('request.jwt.claims')`, and namespace views
824
+ * inherit the gate transitively through their declared dependencies. The
825
+ * executor must stamp that GUC (transaction-scoped) before any statement
826
+ * touching such a relation, or the synced arm yields zero rows regardless
827
+ * of the compiled predicates.
828
+ */
829
+ claimsGated: z.boolean().default(false),
830
+ columns: z.array(z.object({
831
+ name: z.string(),
832
+ columnName: z.string(),
833
+ type: z.union([z.enum(COLUMN_TYPES), z.object({ enum: z.string(), values: z.array(z.string()) })]),
834
+ nullable: z.boolean()
835
+ })),
836
+ /** Empty for keyless read-only views; keyed tables and views list logical columns. */
837
+ primaryKey: z.array(z.string()),
838
+ links: z.array(z.object({
839
+ name: z.string(),
840
+ kind: z.enum(["toOne", "toMany", "toPlatformUser"]),
841
+ target: z.string(),
842
+ on: z.string(),
843
+ references: z.string()
844
+ })),
845
+ /** §4f whitelists: what dynamic inputs may reference. */
846
+ filterable: z.array(z.string()),
847
+ orderable: z.array(z.string()),
848
+ insertable: z.array(z.string()),
849
+ settable: z.array(z.string()),
850
+ appendable: z.array(z.string()),
851
+ /**
852
+ * Logical columns stamped with Postgres `now()` on UPDATE (canonical
853
+ * `onUpdateFn`). Omitted when empty so pre-onUpdate artifacts still parse.
854
+ */
855
+ touchOnUpdate: z.array(z.string()).optional(),
856
+ constraints: z.array(z.object({ name: z.string(), columns: z.array(z.string()) })),
857
+ /** Depth/node caps for dynamic filter trees on this table. */
858
+ dynamicFilterMaxDepth: z.number().int().positive().default(6),
859
+ dynamicFilterMaxNodes: z.number().int().positive().default(64)
860
+ });
861
+ z.object({
862
+ irVersion: irVersionSchema,
863
+ namespace: z.string(),
864
+ schemaHash: z.string(),
865
+ opsHash: z.string(),
866
+ tables: z.record(z.string(), tableMetaIrSchema),
867
+ operations: z.record(z.string(), compiledOperationSchema)
868
+ }).superRefine((artifact, ctx) => {
869
+ for (const [name, operation] of Object.entries(artifact.operations)) {
870
+ if (operation.irVersion > artifact.irVersion) {
871
+ ctx.addIssue({ code: "custom", path: ["operations", name, "irVersion"], message: "operation requires a newer IR version than its artifact" });
872
+ }
873
+ }
874
+ });
482
875
 
483
876
  // src/sdl/generate.ts
484
877
  var SdlGenerationError = class extends Error {
@@ -557,8 +950,12 @@ function generateSdl(canonical, options = {}) {
557
950
  '"""Gates an operation behind a platform capability or custom role \u2014 checked by the data plane before execution."""\ndirective @requires(capability: String, role: String) on QUERY | MUTATION'
558
951
  );
559
952
  blocks.push("directive @skip(if: Boolean!) on FIELD | FRAGMENT_SPREAD | INLINE_FRAGMENT");
953
+ blocks.push(
954
+ '"""Abort the entire mutation unless this root affects exactly the document-authored number of rows. Mutation roots only; cannot be conditional."""\ndirective @assertAffectedRows(exactly: Int!) on FIELD'
955
+ );
560
956
  for (const scalar of CUSTOM_SCALARS) blocks.push(`scalar ${scalar}`);
561
957
  blocks.push(renderEnum(ORDER_BY_ENUM, [...ORDER_BY_ENUM_VALUES]));
958
+ blocks.push(renderEnum(DATE_BUCKET_ENUM, DATE_BUCKETS.map((bucket) => bucket.toUpperCase())));
562
959
  for (const enumName of [...usedEnums].sort()) {
563
960
  const entry = enumByName.get(enumName);
564
961
  if (!entry) continue;
@@ -588,9 +985,24 @@ function generateSdl(canonical, options = {}) {
588
985
  blocks.push(renderQueryRoot(tables, namesByTable));
589
986
  const mutationRoot = renderMutationRoot(tables, namesByTable);
590
987
  if (mutationRoot !== null) blocks.push(mutationRoot);
988
+ assertNoDuplicateTypeNames(blocks);
591
989
  return `${blocks.join("\n\n")}
592
990
  `;
593
991
  }
992
+ var TYPE_DECLARATION_RE = /^(?:type|input|enum|scalar|interface|union)\s+([_A-Za-z][_0-9A-Za-z]*)/;
993
+ function assertNoDuplicateTypeNames(blocks) {
994
+ const seen = /* @__PURE__ */ new Set();
995
+ for (const block of blocks) {
996
+ const declared = TYPE_DECLARATION_RE.exec(block)?.[1];
997
+ if (declared === void 0) continue;
998
+ if (seen.has(declared)) {
999
+ throw new SdlGenerationError(
1000
+ `Generated schema declares "${declared}" twice. A canonical enum or table name is PascalCased into this GraphQL type name and collides with another declaration (for example an enum named "date_bucket" renders as "DateBucket", the shared bucket enum). Rename the offending enum or table.`
1001
+ );
1002
+ }
1003
+ seen.add(declared);
1004
+ }
1005
+ }
594
1006
  function assertEnumValue(value, context) {
595
1007
  if (!GRAPHQL_NAME_RE.test(value) || RESERVED_ENUM_VALUES.has(value)) {
596
1008
  throw new SdlGenerationError(
@@ -679,7 +1091,8 @@ function renderTableBlocks(ctx) {
679
1091
  renderSelectColumnEnum(ctx),
680
1092
  renderOrderByInput(ctx),
681
1093
  renderBoolExp(ctx),
682
- ...renderAggregateTypes(ctx)
1094
+ ...renderAggregateTypes(ctx),
1095
+ ...renderGroupByTypes(ctx)
683
1096
  ];
684
1097
  if (ctx.emitAggregateBoolExp) blocks.push(...renderAggregateBoolExp(ctx));
685
1098
  const pk = primaryKeyColumns(table);
@@ -816,6 +1229,32 @@ ${sumLines.join("\n")}
816
1229
  }
817
1230
  return blocks;
818
1231
  }
1232
+ function groupableColumns(table) {
1233
+ return table.columns.filter((column) => column.type !== "jsonb");
1234
+ }
1235
+ function renderGroupByTypes(ctx) {
1236
+ const { table, names } = ctx;
1237
+ const groupable = groupableColumns(table);
1238
+ if (groupable.length === 0) return [];
1239
+ const keyLines = groupable.map((column) => ` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}`);
1240
+ return [
1241
+ renderEnum(names.groupByColumnEnum, groupable.map((column) => fieldName(column.name))),
1242
+ `input ${names.groupByInput} {
1243
+ column: ${names.groupByColumnEnum}!
1244
+ bucket: ${DATE_BUCKET_ENUM}
1245
+ }`,
1246
+ `type ${names.groupKey} {
1247
+ ${keyLines.join("\n")}
1248
+ }`,
1249
+ `type ${names.aggregateGroup} {
1250
+ key: ${names.groupKey}!
1251
+ aggregate: ${names.aggregateFields}
1252
+ }`
1253
+ ];
1254
+ }
1255
+ function groupRootArgs(names) {
1256
+ return `where: ${names.boolExp}, groupBy: [${names.groupByInput}!]!, limit: Int, offset: Int`;
1257
+ }
819
1258
  function renderAggregateBoolExp(ctx) {
820
1259
  const { names } = ctx;
821
1260
  return [
@@ -841,6 +1280,9 @@ function constraintValues(table) {
841
1280
  }
842
1281
  return [...values].sort();
843
1282
  }
1283
+ function clientSettableColumns(table) {
1284
+ return table.columns.filter((column) => column.onUpdateFn === void 0);
1285
+ }
844
1286
  function renderWriteBlocks(ctx, access) {
845
1287
  const { table, names, pinned } = ctx;
846
1288
  const blocks = [];
@@ -862,7 +1304,7 @@ ${insertLines.join("\n")}
862
1304
  }`);
863
1305
  }
864
1306
  if (access.update) {
865
- const setLines = table.columns.map(
1307
+ const setLines = clientSettableColumns(table).map(
866
1308
  (column) => ` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}`
867
1309
  );
868
1310
  blocks.push(`input ${names.setInput} {
@@ -889,7 +1331,7 @@ ${incLines.join("\n")}
889
1331
  if (access.insert && allowsOnConflict(table) && constraints.length > 0) {
890
1332
  for (const value of constraints) assertEnumValue(value, `constraint enum for table "${table.table}"`);
891
1333
  blocks.push(renderEnum(names.constraintEnum, constraints));
892
- const updateColumns = table.columns.map((column) => fieldName(column.name));
1334
+ const updateColumns = clientSettableColumns(table).map((column) => fieldName(column.name));
893
1335
  blocks.push(renderEnum(names.updateColumnEnum, updateColumns));
894
1336
  blocks.push(
895
1337
  `input ${names.onConflict} {
@@ -921,6 +1363,9 @@ function renderQueryRoot(tables, namesByTable) {
921
1363
  lines.push(` ${names.byPkRoot}(${args}): ${names.typename}`);
922
1364
  }
923
1365
  lines.push(` ${names.aggregateRoot}(${rootArgs(names)}): ${names.aggregate}!`);
1366
+ if (groupableColumns(table).length > 0) {
1367
+ lines.push(` ${names.aggregateGroupsRoot}(${groupRootArgs(names)}): [${names.aggregateGroup}!]!`);
1368
+ }
924
1369
  }
925
1370
  lines.push("}");
926
1371
  return lines.join("\n");
@@ -1093,6 +1538,6 @@ ${body}
1093
1538
  `;
1094
1539
  }
1095
1540
 
1096
- export { CUSTOM_SCALARS, MANIFEST_VERSION, NUMERICISH_COLUMN_TYPES, ORDER_BY_ENUM, ORDER_BY_ENUM_VALUES, PLATFORM_USER_TYPENAME, SdlGenerationError, VARIABLE_SCALAR_NAMES, buildOperationsManifest, camelCase, columnTypeForScalar, comparisonExpName, enumTypeName, fieldName, generateSdl, generateTypePolicies, graphqlScalarFor, manifestOperationSchema, manifestRequiresSchema, operationsManifestSchema, pascalCase, tableNames, typeName };
1541
+ export { BUCKETABLE_COLUMN_TYPES, CUSTOM_SCALARS, DATE_BUCKET_ENUM, MANIFEST_VERSION, NUMERICISH_COLUMN_TYPES, ORDER_BY_ENUM, ORDER_BY_ENUM_VALUES, PLATFORM_USER_TYPENAME, SdlGenerationError, VARIABLE_SCALAR_NAMES, buildOperationsManifest, camelCase, columnTypeForScalar, comparisonExpName, enumTypeName, fieldName, generateSdl, generateTypePolicies, graphqlScalarFor, manifestOperationSchema, manifestRequiresSchema, operationsManifestSchema, pascalCase, tableNames, typeName };
1097
1542
  //# sourceMappingURL=sdl.js.map
1098
1543
  //# sourceMappingURL=sdl.js.map