@sequenceholdings/orm 2.1.0 → 2.1.1

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 (69) hide show
  1. package/README.md +38 -1
  2. package/dist/authoring.cjs +1358 -45
  3. package/dist/authoring.cjs.map +1 -1
  4. package/dist/authoring.d.cts +223 -2
  5. package/dist/authoring.d.ts +223 -2
  6. package/dist/authoring.js +1349 -47
  7. package/dist/authoring.js.map +1 -1
  8. package/dist/canonical.cjs +662 -38
  9. package/dist/canonical.cjs.map +1 -1
  10. package/dist/canonical.d.cts +696 -53
  11. package/dist/canonical.d.ts +696 -53
  12. package/dist/canonical.js +647 -39
  13. package/dist/canonical.js.map +1 -1
  14. package/dist/cli.cjs +1808 -247
  15. package/dist/cli.cjs.map +1 -1
  16. package/dist/cli.js +1809 -248
  17. package/dist/cli.js.map +1 -1
  18. package/dist/client.cjs +90 -25
  19. package/dist/client.cjs.map +1 -1
  20. package/dist/client.d.cts +39 -22
  21. package/dist/client.d.ts +39 -22
  22. package/dist/client.js +87 -26
  23. package/dist/client.js.map +1 -1
  24. package/dist/compiler.cjs +1325 -285
  25. package/dist/compiler.cjs.map +1 -1
  26. package/dist/compiler.d.cts +23 -2
  27. package/dist/compiler.d.ts +23 -2
  28. package/dist/compiler.js +1325 -285
  29. package/dist/compiler.js.map +1 -1
  30. package/dist/constants-YoFGjIBM.d.cts +99 -0
  31. package/dist/constants-YoFGjIBM.d.ts +99 -0
  32. package/dist/dynamic-input.d.cts +1 -0
  33. package/dist/dynamic-input.d.ts +1 -0
  34. package/dist/index.cjs +1605 -140
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +230 -113
  37. package/dist/index.d.ts +230 -113
  38. package/dist/index.js +1583 -142
  39. package/dist/index.js.map +1 -1
  40. package/dist/ir.cjs +727 -38
  41. package/dist/ir.cjs.map +1 -1
  42. package/dist/ir.d.cts +539 -21
  43. package/dist/ir.d.ts +539 -21
  44. package/dist/ir.js +722 -39
  45. package/dist/ir.js.map +1 -1
  46. package/dist/{naming-DCn94LJ4.d.ts → naming-CPfx7EfF.d.ts} +14 -1
  47. package/dist/{naming-DPglwzkB.d.cts → naming-CU9ThP_E.d.cts} +14 -1
  48. package/dist/policy-1iZbGnZZ.d.cts +24 -0
  49. package/dist/policy-1iZbGnZZ.d.ts +24 -0
  50. package/dist/predicate-v2.cjs +726 -42
  51. package/dist/predicate-v2.cjs.map +1 -1
  52. package/dist/predicate-v2.d.cts +2 -1
  53. package/dist/predicate-v2.d.ts +2 -1
  54. package/dist/predicate-v2.js +726 -42
  55. package/dist/predicate-v2.js.map +1 -1
  56. package/dist/sdl.cjs +858 -54
  57. package/dist/sdl.cjs.map +1 -1
  58. package/dist/sdl.d.cts +7 -3
  59. package/dist/sdl.d.ts +7 -3
  60. package/dist/sdl.js +856 -55
  61. package/dist/sdl.js.map +1 -1
  62. package/package.json +9 -4
  63. package/templates/orm-namespace/CLAUDE.md +6 -4
  64. package/templates/orm-namespace/codegen.preset.d.cts +7 -0
  65. package/templates/orm-namespace/codegen.ts +2 -1
  66. package/templates/orm-namespace/graphql/example/queries.ts +4 -3
  67. package/templates/orm-namespace/package.json +2 -1
  68. package/templates/orm-namespace/schema/tables.ts +3 -2
  69. package/templates/orm-namespace/sequence.config.ts +7 -11
package/dist/sdl.js CHANGED
@@ -2,14 +2,479 @@ import { z } from 'zod';
2
2
  import { createHash } from 'crypto';
3
3
 
4
4
  // src/canonical/v2.ts
5
+ var quoteIdentifier = (name) => `"${name.replaceAll('"', '""')}"`;
6
+ var quoteLiteral = (value) => `'${value.replaceAll("'", "''")}'`;
7
+
8
+ // src/checks/compile.ts
9
+ function tokenize(source) {
10
+ const tokens = [];
11
+ for (let position = 0; position < source.length; ) {
12
+ const char = source[position];
13
+ if (/\s/.test(char)) {
14
+ position++;
15
+ continue;
16
+ }
17
+ if (char === "(" || char === ")" || char === ",") {
18
+ tokens.push({ kind: char === "(" ? "lparen" : char === ")" ? "rparen" : "comma", value: char, position });
19
+ position++;
20
+ continue;
21
+ }
22
+ if (char === "'") {
23
+ const start = position++;
24
+ let value = "";
25
+ let closed = false;
26
+ while (position < source.length) {
27
+ if (source[position] !== "'") {
28
+ value += source[position++];
29
+ continue;
30
+ }
31
+ if (source[position + 1] === "'") {
32
+ value += "'";
33
+ position += 2;
34
+ continue;
35
+ }
36
+ position++;
37
+ closed = true;
38
+ break;
39
+ }
40
+ if (!closed) throw new Error(`unterminated string at position ${start}`);
41
+ if (/[\x00-\x1f\x7f]/.test(value)) throw new Error(`control character in string at position ${start}`);
42
+ tokens.push({ kind: "string", value, position: start });
43
+ continue;
44
+ }
45
+ const operator = /^(<=|>=|<>|!=|!~|=|<|>|~)/.exec(source.slice(position));
46
+ if (operator) {
47
+ tokens.push({ kind: "operator", value: operator[0] === "<>" ? "!=" : operator[0], position });
48
+ position += operator[0].length;
49
+ continue;
50
+ }
51
+ const number = /^-?\d+(?:\.\d+)?/.exec(source.slice(position));
52
+ if (number) {
53
+ tokens.push({ kind: "number", value: number[0], position });
54
+ position += number[0].length;
55
+ continue;
56
+ }
57
+ const identifier3 = /^[a-zA-Z_][a-zA-Z_0-9]*/.exec(source.slice(position));
58
+ if (identifier3) {
59
+ tokens.push({ kind: "identifier", value: identifier3[0], position });
60
+ position += identifier3[0].length;
61
+ continue;
62
+ }
63
+ throw new Error(`unexpected character '${char}' at position ${position}`);
64
+ }
65
+ tokens.push({ kind: "end", value: "", position: source.length });
66
+ if (tokens.length > 256) throw new Error("check expression exceeds 256 tokens");
67
+ return tokens;
68
+ }
69
+ var Parser = class {
70
+ constructor(tokens, columns) {
71
+ this.tokens = tokens;
72
+ this.columns = /* @__PURE__ */ new Map();
73
+ for (const column of columns) {
74
+ for (const alias of [column.name, column.columnName]) {
75
+ const previous = this.columns.get(alias);
76
+ if (previous && previous.columnName !== column.columnName) {
77
+ throw new Error(`ambiguous check column '${alias}'`);
78
+ }
79
+ this.columns.set(alias, column);
80
+ }
81
+ }
82
+ }
83
+ index = 0;
84
+ depth = 0;
85
+ columns;
86
+ parse() {
87
+ const expression = this.parseOr();
88
+ this.expect("end", "end of expression");
89
+ return expression;
90
+ }
91
+ parseOr() {
92
+ let left = this.parseAnd();
93
+ while (this.matchKeyword("or")) left = `(${left} OR ${this.parseAnd()})`;
94
+ return left;
95
+ }
96
+ parseAnd() {
97
+ let left = this.parseNot();
98
+ while (this.matchKeyword("and")) left = `(${left} AND ${this.parseNot()})`;
99
+ return left;
100
+ }
101
+ parseNot() {
102
+ if (this.matchKeyword("not")) return `(NOT ${this.parseNot()})`;
103
+ return this.parsePrimary();
104
+ }
105
+ parsePrimary() {
106
+ if (this.peek().kind === "lparen") {
107
+ if (++this.depth > 32) throw new Error("check expression nesting exceeds 32 levels");
108
+ this.advance();
109
+ const expression = this.parseOr();
110
+ this.expect("rparen", "')'");
111
+ this.depth--;
112
+ return `(${expression})`;
113
+ }
114
+ return this.parseComparison();
115
+ }
116
+ parseComparison() {
117
+ const left = this.parseValue();
118
+ if (this.matchKeyword("is")) {
119
+ const negate = this.matchKeyword("not");
120
+ this.expectKeyword("null");
121
+ return `${left.sql} IS ${negate ? "NOT " : ""}NULL`;
122
+ }
123
+ if (this.matchKeyword("between")) {
124
+ const lower = this.parseValue();
125
+ this.expectKeyword("and");
126
+ const upper = this.parseValue();
127
+ return `${left.sql} BETWEEN ${lower.sql} AND ${upper.sql}`;
128
+ }
129
+ if (this.matchKeyword("in")) {
130
+ this.expect("lparen", "'(' after IN");
131
+ const values = [this.parseValue()];
132
+ while (this.peek().kind === "comma") {
133
+ this.advance();
134
+ values.push(this.parseValue());
135
+ }
136
+ this.expect("rparen", "')' after IN values");
137
+ if (values.some((value) => !value.literal)) throw new Error("IN values must be literals");
138
+ return `${left.sql} IN (${values.map((value) => value.sql).join(", ")})`;
139
+ }
140
+ const operator = this.expect("operator", "a comparison operator");
141
+ const right = this.parseValue();
142
+ if (operator.value === "~" || operator.value === "!~") {
143
+ if (left.column && left.column.type !== "text") throw new Error("regex checks require a text column");
144
+ if (!right.literal) throw new Error("regex pattern must be a literal");
145
+ }
146
+ return `${left.sql} ${operator.value} ${right.sql}`;
147
+ }
148
+ parseValue() {
149
+ const token = this.advance();
150
+ if (token.kind === "string") return { sql: quoteLiteral(token.value), literal: true };
151
+ if (token.kind === "number") {
152
+ const number = Number(token.value);
153
+ if (!Number.isFinite(number) || Number.isInteger(number) && !Number.isSafeInteger(number)) {
154
+ throw new Error(`unsafe numeric literal at position ${token.position}`);
155
+ }
156
+ return { sql: token.value, literal: true };
157
+ }
158
+ if (token.kind !== "identifier") throw new Error(`expected a column or literal at position ${token.position}`);
159
+ const keyword = token.value.toLowerCase();
160
+ if (keyword === "true" || keyword === "false" || keyword === "null") return { sql: keyword.toUpperCase(), literal: true };
161
+ if (keyword === "btrim") {
162
+ this.expect("lparen", "'(' after btrim");
163
+ const column2 = this.parseColumn();
164
+ this.expect("rparen", "')' after btrim column");
165
+ if (column2.type !== "text") throw new Error("btrim requires a text column");
166
+ return { sql: `btrim(${quoteIdentifier(column2.columnName)})`, column: column2 };
167
+ }
168
+ const column = this.columns.get(token.value);
169
+ if (!column) throw new Error(`unknown check column '${token.value}'`);
170
+ return { sql: quoteIdentifier(column.columnName), column };
171
+ }
172
+ parseColumn() {
173
+ const token = this.expect("identifier", "a column name");
174
+ const column = this.columns.get(token.value);
175
+ if (!column) throw new Error(`unknown check column '${token.value}'`);
176
+ return column;
177
+ }
178
+ peek() {
179
+ return this.tokens[this.index];
180
+ }
181
+ advance() {
182
+ const token = this.peek();
183
+ if (token.kind !== "end") this.index++;
184
+ return token;
185
+ }
186
+ matchKeyword(keyword) {
187
+ if (this.peek().kind !== "identifier" || this.peek().value.toLowerCase() !== keyword) return false;
188
+ this.advance();
189
+ return true;
190
+ }
191
+ expectKeyword(keyword) {
192
+ if (!this.matchKeyword(keyword)) throw new Error(`expected '${keyword}' at position ${this.peek().position}`);
193
+ }
194
+ expect(kind, description) {
195
+ const token = this.peek();
196
+ if (token.kind !== kind) throw new Error(`expected ${description} at position ${token.position}`);
197
+ return this.advance();
198
+ }
199
+ };
200
+ function compileCheckExpression(source, columns) {
201
+ return new Parser(tokenize(source), columns).parse();
202
+ }
5
203
 
6
204
  // src/constants.ts
7
205
  var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
206
+ var MAX_IDENTIFIER_LENGTH = 63;
207
+ var CELL_PARTS = ["at", "by", "rev", "source", "conflicts"];
208
+ var CELL_PART_SUFFIX = {
209
+ at: "__at",
210
+ by: "__by",
211
+ rev: "__rev",
212
+ source: "__src",
213
+ conflicts: "__lost"
214
+ };
215
+ var MAX_TRACKED_COLUMN_NAME = MAX_IDENTIFIER_LENGTH - 6;
216
+ var CELLS_FIELD = "cells";
217
+ function cellColumnName(column, part) {
218
+ return `${column}${CELL_PART_SUFFIX[part]}`;
219
+ }
220
+ var MATERIALIZE_ACTOR = "orm:materialize";
221
+ JSON.stringify({ sub: MATERIALIZE_ACTOR, org: null, roles: [], capabilities: [] });
222
+ var policyVariantSchema = z.object({
223
+ /** Claims-only guard (capability()/role()/claim() comparisons — no columns). */
224
+ when: z.string().min(1).max(1e3),
225
+ /** Exactly one of: allow-all bypass, or a row predicate. */
226
+ allow: z.literal(true).optional(),
227
+ policy: z.string().min(1).max(2e3).optional()
228
+ }).refine((v) => v.allow === true !== (typeof v.policy === "string"), {
229
+ message: "A variant declares exactly one of allow: true or policy"
230
+ });
231
+ var policyExpressionSchema = z.union([
232
+ z.string().min(1).max(2e3),
233
+ z.object({
234
+ default: z.string().min(1).max(2e3),
235
+ variants: z.array(policyVariantSchema).max(10).default([])
236
+ })
237
+ ]);
238
+ var identifier = z.string().min(1).max(63).regex(/^[a-z][a-z0-9_]*$/);
239
+ var durationMs = z.number().int().positive().max(24 * 60 * 60 * 1e3);
240
+ var RESOLVED_VIEW_SYSTEM_COLUMNS = {
241
+ writebackStatus: { name: "writebackStatus", columnName: "_writeback_status", type: "text" },
242
+ writebackError: { name: "writebackError", columnName: "_writeback_error", type: "text" }
243
+ };
244
+ var RESOLVED_SOURCE_SYSTEM_COLUMNS = {
245
+ tombstoned: { name: "sysTombstoned", columnName: "_tombstoned" },
246
+ fetchedAt: { name: "sysFetchedAt", columnName: "_fetched_at" },
247
+ writebackStatus: { name: "sysWritebackStatus", columnName: "_writeback_status" },
248
+ writebackError: { name: "sysWritebackError", columnName: "_writeback_error" },
249
+ writebackAt: { name: "sysWritebackAt", columnName: "_writeback_at" }
250
+ };
251
+ var RESOLVED_VIEW_CELL_PARTS = ["source", "at", "by", "conflicts"];
252
+ /* @__PURE__ */ new Set([
253
+ ...Object.values(RESOLVED_VIEW_SYSTEM_COLUMNS).flatMap((c) => [c.name, c.columnName]),
254
+ ...Object.values(RESOLVED_SOURCE_SYSTEM_COLUMNS).flatMap((c) => [c.name, c.columnName])
255
+ ]);
256
+ var COMPARATORS = ["latest", "highest", "lowest", "priority", "nonNullFirst", "custom"];
257
+ var canonicalFunctionRefSchema = z.object({
258
+ /** `managed` = a Managed Function id/slug; `native` = an in-process Atlas function (reserved). */
259
+ kind: z.enum(["managed", "native"]),
260
+ id: z.string().min(1).max(200)
261
+ }).strict();
262
+ var canonicalFetchShapeSchema = z.union([
263
+ z.object({ shape: z.literal("single") }).strict(),
264
+ z.object({ shape: z.literal("batch"), maxKeys: z.number().int().min(2).max(500) }).strict()
265
+ ]);
266
+ var canonicalResolvedSourceSchema = z.object({
267
+ name: identifier,
268
+ kind: z.enum(["manual", "etl", "prefetch"]),
269
+ /** Backing relation in this namespace: an internal table (manual/prefetch) or a synced table (etl). */
270
+ table: identifier,
271
+ /** Resolved columns (logical names) this source supplies — the primary key is implicit. */
272
+ columns: z.array(z.string().min(1).max(63)).min(1).max(500),
273
+ /** etl: logical timestamptz/date column on the synced table used as every cell's updated_at. */
274
+ updatedAt: z.string().min(1).max(63).optional(),
275
+ /** prefetch: resolved column holding the source system's key (may be the primary key). */
276
+ key: z.string().min(1).max(63).optional(),
277
+ fetch: canonicalFetchShapeSchema.optional(),
278
+ function: canonicalFunctionRefSchema.optional(),
279
+ prefetchDefaults: z.object({
280
+ maxStalenessMs: durationMs,
281
+ timeoutMs: durationMs,
282
+ onError: z.enum(["fallback", "fail"])
283
+ }).strict().optional(),
284
+ writeback: z.object({
285
+ columns: z.array(z.string().min(1).max(63)).min(1).max(500),
286
+ defaultMode: z.enum(["sync", "async"]),
287
+ timeoutMs: durationMs,
288
+ /** Remove confirmed cells from the manual source so a stale edit cannot shadow the source. */
289
+ clearManualOnConfirm: z.boolean()
290
+ }).strict().optional()
291
+ }).strict();
292
+ var canonicalResolveRuleSchema = z.object({
293
+ column: z.string().min(1).max(63),
294
+ comparator: z.enum(COMPARATORS),
295
+ /** Source names, in tie-break order. */
296
+ candidates: z.array(identifier).min(1).max(16),
297
+ /** custom only: SQL ORDER BY key over value / updated_at / source / actor / ord. */
298
+ orderBy: z.string().min(1).max(2e3).optional()
299
+ }).strict();
300
+ var canonicalResolvedSchema = z.object({
301
+ sources: z.array(canonicalResolvedSourceSchema).min(1).max(16),
302
+ /** One rule per resolved (non-pk) column; documents the generated view for tooling. */
303
+ resolve: z.array(canonicalResolveRuleSchema).max(500),
304
+ /** Sources whose tombstone hides the resolved row (v1: the manual source only). */
305
+ deletedBy: z.array(identifier).max(1).default([]),
306
+ /**
307
+ * Materialized: the view reads an indexed internal table (`table`) that the
308
+ * data plane keeps in step with `compute`, the internal view holding the
309
+ * resolution query. Omitted when the view computes on read.
310
+ */
311
+ materialized: z.object({ table: identifier, compute: identifier }).strict().optional(),
312
+ /** Write policies evaluated against the RESOLVED row (reads use the view's read policy). */
313
+ policies: z.object({
314
+ write: policyExpressionSchema.optional(),
315
+ insert: policyExpressionSchema.optional(),
316
+ update: policyExpressionSchema.optional()
317
+ }).strict().default({})
318
+ }).strict();
319
+ function validateResolvedView({ view, tables, views = [] }) {
320
+ const resolved = view.resolved;
321
+ if (!resolved) return [];
322
+ const errors = [];
323
+ const fail = (message) => errors.push(`resolved table '${view.view}': ${message}`);
324
+ const pkColumn = view.primaryKey?.length === 1 ? view.columns.find((c) => c.columnName === view.primaryKey?.[0]) : void 0;
325
+ if (!pkColumn) {
326
+ fail("requires exactly one primary-key column");
327
+ return errors;
328
+ }
329
+ const systemNames = new Set(Object.values(RESOLVED_VIEW_SYSTEM_COLUMNS).map((c) => c.name));
330
+ const scope = {
331
+ view,
332
+ resolved,
333
+ pkColumn,
334
+ dataColumns: new Map(view.columns.filter((c) => !systemNames.has(c.name) && c.cell === void 0).map((c) => [c.name, c])),
335
+ sourceByName: /* @__PURE__ */ new Map(),
336
+ fail
337
+ };
338
+ if (resolved.sources.filter((source) => source.kind === "manual").length !== 1) fail("requires exactly one manual source");
339
+ const tableByName = new Map(tables.map((table) => [table.table, table]));
340
+ for (const source of resolved.sources) {
341
+ if (scope.sourceByName.has(source.name)) fail(`duplicate source '${source.name}'`);
342
+ scope.sourceByName.set(source.name, source);
343
+ const table = tableByName.get(source.table);
344
+ if (!table) fail(`source '${source.name}' references unknown table '${source.table}'`);
345
+ else validateSource({ scope, source, table });
346
+ }
347
+ validateRules(scope);
348
+ validateViewCells(scope);
349
+ if (resolved.materialized) validateMaterialized({ scope, tableByName, views });
350
+ for (const name of resolved.deletedBy) {
351
+ if (scope.sourceByName.get(name)?.kind !== "manual") fail(`deletedBy '${name}' must be the manual source`);
352
+ }
353
+ return errors;
354
+ }
355
+ function validateSource({ scope, source, table }) {
356
+ const { fail } = scope;
357
+ if (source.kind === "etl" && table.source === void 0) fail(`etl source '${source.name}' must reference a synced table`);
358
+ if (source.kind !== "etl" && (table.internal !== true || table.cellMetadata !== true)) {
359
+ fail(`${source.kind} source '${source.name}' must reference an internal table with cell metadata`);
360
+ }
361
+ const hasRev = table.columns.some((column) => column.cell?.part === "rev");
362
+ if (source.kind === "manual" && !hasRev) fail(`manual source '${source.name}' must carry '__rev' cell columns`);
363
+ if (source.kind === "prefetch" && hasRev) fail(`prefetch source '${source.name}' must not carry '__rev' cell columns`);
364
+ const byName = new Map(table.columns.map((c) => [c.name, c]));
365
+ validateSourceColumns({ scope, source, byName });
366
+ if (source.kind === "etl") {
367
+ const updatedAt = source.updatedAt === void 0 ? void 0 : byName.get(source.updatedAt);
368
+ if (source.updatedAt !== void 0 && updatedAt?.type !== "timestamptz" && updatedAt?.type !== "date") {
369
+ fail(`etl source '${source.name}' updatedAt must name a timestamptz/date column on '${source.table}'`);
370
+ }
371
+ }
372
+ validatePrefetchFields({ scope, source });
373
+ }
374
+ function validatePrefetchFields({ scope, source }) {
375
+ const { fail, pkColumn } = scope;
376
+ if (source.kind !== "prefetch") {
377
+ if (source.key || source.fetch || source.function || source.prefetchDefaults || source.writeback) {
378
+ fail(`only prefetch sources may declare key/fetch/function/prefetchDefaults/writeback ('${source.name}')`);
379
+ }
380
+ return;
381
+ }
382
+ if (!source.key || !source.fetch || !source.function || !source.prefetchDefaults) {
383
+ fail(`prefetch source '${source.name}' requires key, fetch, function and prefetchDefaults`);
384
+ } else if (source.key !== pkColumn.name && !source.columns.includes(source.key)) {
385
+ fail(`prefetch source '${source.name}' key '${source.key}' must be the primary key or one of its columns`);
386
+ }
387
+ for (const column of source.writeback?.columns ?? []) {
388
+ if (!source.columns.includes(column)) fail(`prefetch source '${source.name}' writes back '${column}', which it does not supply`);
389
+ }
390
+ }
391
+ function validateSourceColumns({ scope, source, byName }) {
392
+ const { fail, pkColumn, dataColumns } = scope;
393
+ for (const column of [pkColumn.name, ...source.columns]) {
394
+ const own = column === pkColumn.name ? pkColumn : dataColumns.get(column);
395
+ const theirs = byName.get(column);
396
+ if (!own) fail(`source '${source.name}' supplies unknown column '${column}'`);
397
+ else if (!theirs) fail(`source '${source.name}' table '${source.table}' has no column '${column}'`);
398
+ else if (JSON.stringify(theirs.type) !== JSON.stringify(own.type)) fail(`column '${column}' has a different type on source '${source.name}'`);
399
+ }
400
+ }
401
+ function validateRules({ resolved, pkColumn, dataColumns, sourceByName, fail }) {
402
+ const ruled = /* @__PURE__ */ new Set();
403
+ for (const rule of resolved.resolve) {
404
+ if (!dataColumns.has(rule.column) || rule.column === pkColumn.name) {
405
+ fail(`resolve rule targets unknown column '${rule.column}'`);
406
+ continue;
407
+ }
408
+ if (ruled.has(rule.column)) fail(`column '${rule.column}' has more than one resolve rule`);
409
+ ruled.add(rule.column);
410
+ if (rule.comparator === "custom" !== (rule.orderBy !== void 0)) {
411
+ fail(`column '${rule.column}': orderBy is required for custom comparators and only allowed there`);
412
+ }
413
+ if (new Set(rule.candidates).size !== rule.candidates.length) fail(`column '${rule.column}' repeats a candidate`);
414
+ for (const candidate of rule.candidates) {
415
+ const source = sourceByName.get(candidate);
416
+ if (!source) fail(`column '${rule.column}' names unknown candidate source '${candidate}'`);
417
+ else if (!source.columns.includes(rule.column)) fail(`column '${rule.column}' candidate '${candidate}' does not supply it`);
418
+ }
419
+ }
420
+ for (const column of dataColumns.keys()) {
421
+ if (column !== pkColumn.name && !ruled.has(column)) fail(`column '${column}' has no resolve rule`);
422
+ }
423
+ }
424
+ var VIEW_CELL_TYPES = {
425
+ source: "text",
426
+ at: "timestamptz",
427
+ by: "text",
428
+ conflicts: "jsonb"
429
+ };
430
+ function validateViewCells({ view, dataColumns, pkColumn, fail }) {
431
+ const expected = /* @__PURE__ */ new Map();
432
+ for (const column of dataColumns.values()) {
433
+ if (column.name === pkColumn.name) continue;
434
+ for (const part of RESOLVED_VIEW_CELL_PARTS) {
435
+ expected.set(cellColumnName(column.name, part), { part, columnName: cellColumnName(column.columnName, part) });
436
+ }
437
+ }
438
+ for (const column of view.columns) {
439
+ if (!column.cell) continue;
440
+ const want = expected.get(column.name);
441
+ if (!want || want.part !== column.cell.part || want.columnName !== column.columnName || cellColumnName(column.cell.of, column.cell.part) !== column.name) {
442
+ fail(`unexpected cell column '${column.name}'`);
443
+ continue;
444
+ }
445
+ if (column.type !== VIEW_CELL_TYPES[column.cell.part]) fail(`cell column '${column.name}' must be ${VIEW_CELL_TYPES[column.cell.part]}`);
446
+ expected.delete(column.name);
447
+ }
448
+ for (const name of expected.keys()) fail(`missing cell column '${name}'`);
449
+ }
450
+ var columnSignature = (columns) => JSON.stringify(columns.map((column) => [column.name, column.columnName, column.type]));
451
+ function validateMaterialized({ scope, tableByName, views }) {
452
+ const { view, resolved, fail } = scope;
453
+ const { table: tableName, compute: computeName } = resolved.materialized;
454
+ const table = tableByName.get(tableName);
455
+ if (!table || table.internal !== true || table.materializes !== view.view) {
456
+ fail(`materialized table '${tableName}' must be an internal table that materializes '${view.view}'`);
457
+ } else {
458
+ if (columnSignature(table.columns) !== columnSignature(view.columns)) fail(`materialized table '${tableName}' must have exactly the view's columns`);
459
+ if (JSON.stringify(table.primaryKey ?? []) !== JSON.stringify(view.primaryKey ?? [])) fail(`materialized table '${tableName}' must share the view's primary key`);
460
+ }
461
+ const compute = views.find((candidate) => candidate.view === computeName);
462
+ if (!compute || compute.internal !== true) fail(`compute view '${computeName}' must be an internal view`);
463
+ else if (columnSignature(compute.columns) !== columnSignature(view.columns)) fail(`compute view '${computeName}' must have exactly the view's columns`);
464
+ for (const source of resolved.sources) {
465
+ if (source.kind !== "etl") continue;
466
+ const read = tableByName.get(source.table)?.policies?.read;
467
+ const unconditional = typeof read === "string" ? read.trim() === "true" : read !== void 0 && read.default.trim() === "true" && read.variants.length === 0;
468
+ if (!unconditional) {
469
+ fail(`etl source '${source.name}' must have read policy 'true' to be materialized (the copy is shared by every caller)`);
470
+ }
471
+ }
472
+ }
8
473
 
9
474
  // src/canonical/v2.ts
10
475
  var CANONICAL_FORMAT = 2;
11
476
  var IDENTIFIER_RE = /^[a-z][a-z0-9_]*$/;
12
- var identifier = z.string().min(1).max(63).regex(IDENTIFIER_RE);
477
+ var identifier2 = z.string().min(1).max(63).regex(IDENTIFIER_RE);
13
478
  var columnIdentifier = z.string().min(1).max(63).regex(COLUMN_IDENTIFIER_PATTERN);
14
479
  var COLUMN_TYPES = [
15
480
  "uuid",
@@ -25,14 +490,14 @@ var COLUMN_TYPES = [
25
490
  ];
26
491
  var columnTypeSchema = z.enum(COLUMN_TYPES);
27
492
  var canonicalEnumSchema = z.object({
28
- name: identifier,
493
+ name: identifier2,
29
494
  values: z.array(z.string().min(1).max(63)).min(1).max(100)
30
495
  });
31
496
  var canonicalColumnSchema = z.object({
32
497
  /** Property name in TS (camelCase); columnName is the Postgres name. */
33
498
  name: z.string().min(1).max(63),
34
499
  columnName: columnIdentifier,
35
- type: z.union([columnTypeSchema, z.object({ enum: identifier })]),
500
+ type: z.union([columnTypeSchema, z.object({ enum: identifier2 })]),
36
501
  nullable: z.boolean().default(true),
37
502
  unique: z.boolean().default(false),
38
503
  /** Literal default, or one of the allowlisted SQL functions. */
@@ -48,7 +513,14 @@ var canonicalColumnSchema = z.object({
48
513
  /** Migration hints — same semantics as v1. */
49
514
  renamedFrom: columnIdentifier.optional(),
50
515
  cast: z.string().max(200).optional(),
51
- deprecated: z.boolean().default(false)
516
+ deprecated: z.boolean().default(false),
517
+ /**
518
+ * Marks a system cell-metadata column (see CELL_PARTS in constants): part
519
+ * `part` of the cell of logical column `of`. Synthesized by authoring,
520
+ * never client-writable, absent from GraphQL scalar fields. Omitted on
521
+ * ordinary columns so their hashes are unchanged.
522
+ */
523
+ cell: z.object({ of: z.string().min(1).max(63), part: z.enum(CELL_PARTS) }).strict().optional()
52
524
  }).superRefine((column, ctx) => {
53
525
  if (column.onUpdateFn === void 0) return;
54
526
  if (typeof column.type !== "string" || column.type !== "timestamptz" && column.type !== "date") {
@@ -63,36 +535,26 @@ var canonicalLinkSchema = z.object({
63
535
  name: z.string().min(1).max(63),
64
536
  kind: z.enum(["toOne", "toMany", "toPlatformUser"]),
65
537
  /** Target table name (namespace-local). Absent for toPlatformUser. */
66
- target: identifier.optional(),
538
+ target: identifier2.optional(),
67
539
  /** FK column on the owning side (toOne/toPlatformUser: on this table; toMany: on the target). */
68
540
  on: columnIdentifier,
69
541
  /** Referenced column on the target (default: target's PK / platform user `sub`). */
70
542
  references: columnIdentifier.optional(),
71
543
  /** enforce: true emits a real FK constraint (same-namespace toOne only). */
72
544
  enforce: z.boolean().default(false),
545
+ onDelete: z.enum(["cascade", "restrict", "no action", "set null", "set default"]).optional(),
546
+ onUpdate: z.enum(["cascade", "restrict", "no action", "set null", "set default"]).optional(),
73
547
  description: z.string().max(500).optional()
74
548
  });
75
549
  var canonicalIndexSchema = z.object({
76
550
  columns: z.array(columnIdentifier).min(1),
77
551
  unique: z.boolean().default(false),
78
- name: identifier.optional()
79
- });
80
- var policyVariantSchema = z.object({
81
- /** Claims-only guard (capability()/role()/claim() comparisons — no columns). */
82
- when: z.string().min(1).max(1e3),
83
- /** Exactly one of: allow-all bypass, or a row predicate. */
84
- allow: z.literal(true).optional(),
85
- policy: z.string().min(1).max(2e3).optional()
86
- }).refine((v) => v.allow === true !== (typeof v.policy === "string"), {
87
- message: "A variant declares exactly one of allow: true or policy"
552
+ name: identifier2.optional()
88
553
  });
89
- var policyExpressionSchema = z.union([
90
- z.string().min(1).max(2e3),
91
- z.object({
92
- default: z.string().min(1).max(2e3),
93
- variants: z.array(policyVariantSchema).max(10).default([])
94
- })
95
- ]);
554
+ var canonicalCheckSchema = z.object({
555
+ name: identifier2,
556
+ expression: z.string().min(1).max(2e3)
557
+ }).strict();
96
558
  var canonicalPoliciesSchema = z.object({
97
559
  read: policyExpressionSchema.optional(),
98
560
  write: policyExpressionSchema.optional(),
@@ -123,7 +585,7 @@ function isSingleViewQuerySql(value) {
123
585
  return state === "normal" && !/\$[a-z_0-9]*\$/i.test(query);
124
586
  }
125
587
  var canonicalActionSchema = z.object({
126
- name: identifier,
588
+ name: identifier2,
127
589
  requires: z.string().max(1e3).optional(),
128
590
  params: z.record(z.string(), z.object({
129
591
  type: columnTypeSchema,
@@ -135,9 +597,9 @@ var canonicalActionSchema = z.object({
135
597
  });
136
598
  var canonicalCowMutationSchema = z.object({
137
599
  kind: z.enum(["override", "revert"]),
138
- name: identifier,
600
+ name: identifier2,
139
601
  /** Synced table (same namespace) the overlay copies from / falls back to. */
140
- synced: identifier,
602
+ synced: identifier2,
141
603
  /** Single-column primary key PROPERTY (TS camelCase, like column `name`) shared by overlay and synced. */
142
604
  pk: z.string().min(1).max(63),
143
605
  /** Override: contract properties copied INSERT…SELECT from synced (pk included). Empty for revert. */
@@ -165,23 +627,27 @@ var canonicalCowMutationSchema = z.object({
165
627
  }
166
628
  });
167
629
  var canonicalForeignKeySchema = z.object({
168
- name: identifier,
630
+ name: identifier2,
169
631
  columns: z.array(columnIdentifier).min(1).max(8),
170
- target: identifier,
632
+ target: identifier2,
171
633
  references: z.array(columnIdentifier).min(1).max(8),
172
- deferrable: z.enum(["initially-immediate", "initially-deferred"]).optional()
634
+ deferrable: z.enum(["initially-immediate", "initially-deferred"]).optional(),
635
+ onDelete: z.enum(["cascade", "restrict", "no action", "set null", "set default"]).optional(),
636
+ onUpdate: z.enum(["cascade", "restrict", "no action", "set null", "set default"]).optional()
173
637
  }).strict();
174
638
  var canonicalTableSchema = z.object({
175
- table: identifier,
639
+ table: identifier2,
176
640
  description: z.string().max(2e3).optional(),
177
641
  /** Table-level rename hint (new in v2). */
178
- renamedFrom: identifier.optional(),
642
+ renamedFrom: identifier2.optional(),
179
643
  /** Synced/reflected read-only tables (`source: gold.*`) — v1 semantics. */
180
644
  source: z.string().regex(/^gold\.[a-z][a-z0-9_]*$/).optional(),
181
645
  columns: z.array(canonicalColumnSchema).default([]),
182
646
  primaryKey: z.array(columnIdentifier).min(1).max(8).optional(),
183
647
  links: z.array(canonicalLinkSchema).default([]),
184
648
  indexes: z.array(canonicalIndexSchema).default([]),
649
+ /** Omitted by old definitions, preserving their existing content hashes. */
650
+ checks: z.array(canonicalCheckSchema).max(100).optional(),
185
651
  policies: canonicalPoliciesSchema.default({}),
186
652
  /**
187
653
  * v1-conversion carry-over only. v2 has no action surface — the runtime
@@ -192,22 +658,55 @@ var canonicalTableSchema = z.object({
192
658
  /** Optional (not defaulted) so pre-CoW canonical documents keep byte-identical hashes. */
193
659
  cowMutations: z.array(canonicalCowMutationSchema).optional(),
194
660
  /** Omitted by old definitions, preserving their existing content hashes. */
195
- foreignKeys: z.array(canonicalForeignKeySchema).max(100).optional()
661
+ foreignKeys: z.array(canonicalForeignKeySchema).max(100).optional(),
662
+ /**
663
+ * Per-cell edit metadata (`cellMetadata: true` in authoring): every tracked
664
+ * column `c` carries typed `c__at` / `c__by` system columns (plus `c__rev`
665
+ * on a resolved table's internal sources), listed explicitly in `columns`
666
+ * with a `cell` marker so DDL and migrations treat them as ordinary columns.
667
+ * Omitted when off so existing definitions hash unchanged.
668
+ */
669
+ cellMetadata: z.literal(true).optional(),
670
+ /**
671
+ * System-owned backing table of a resolved table (its manual or prefetch
672
+ * source). Internal tables get DDL and IR metadata but no GraphQL surface;
673
+ * only the data plane writes them. Omitted when false.
674
+ */
675
+ internal: z.literal(true).optional(),
676
+ /**
677
+ * The materialized copy of resolved table `materializes` (internal): the
678
+ * same columns as that view, including its cell parts, maintained by the
679
+ * data plane. Omitted otherwise.
680
+ */
681
+ materializes: identifier2.optional()
196
682
  });
683
+ var MAX_VIEW_QUERY = 1e5;
684
+ var MAX_RESOLVED_VIEW_QUERY = 5e6;
197
685
  var canonicalViewSchema = z.object({
198
- view: identifier,
686
+ view: identifier2,
199
687
  description: z.string().max(2e3).optional(),
200
688
  columns: z.array(canonicalColumnSchema).min(1),
201
689
  /** Logical identity for GraphQL only; PostgreSQL views have no PK constraint. */
202
690
  primaryKey: z.array(columnIdentifier).min(1).max(8).optional(),
203
691
  links: z.array(canonicalLinkSchema).default([]),
204
692
  policies: z.object({ read: policyExpressionSchema }).strict(),
205
- query: z.string().min(1).max(1e5).refine(isSingleViewQuerySql, {
693
+ query: z.string().min(1).max(MAX_RESOLVED_VIEW_QUERY).refine(isSingleViewQuerySql, {
206
694
  message: "view query must be one SELECT/WITH statement without comments or dollar-quoted bodies"
207
695
  }),
208
696
  /** Namespace-local logical relation names, sorted for deterministic hashing. */
209
- dependencies: z.array(identifier).max(500)
697
+ dependencies: z.array(identifier2).max(500),
698
+ /**
699
+ * Present when this view is a resolved table (defineResolvedTable): the
700
+ * multi-source metadata behind its generated query. Omitted otherwise so
701
+ * existing views hash unchanged.
702
+ */
703
+ resolved: canonicalResolvedSchema.optional(),
704
+ /** System-owned view (a materialized resolved table's compute view): no GraphQL surface. */
705
+ internal: z.literal(true).optional()
210
706
  }).superRefine((view, ctx) => {
707
+ if (view.resolved === void 0 && view.query.length > MAX_VIEW_QUERY) {
708
+ ctx.addIssue({ code: "custom", path: ["query"], message: `view query must be at most ${MAX_VIEW_QUERY} characters` });
709
+ }
211
710
  for (const [index, column] of view.columns.entries()) {
212
711
  if (column.default !== void 0 || column.defaultFn !== void 0) {
213
712
  ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare a default` });
@@ -222,15 +721,82 @@ var canonicalViewSchema = z.object({
222
721
  ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare migration hints` });
223
722
  }
224
723
  }
724
+ if (view.resolved === void 0 && view.internal !== true) {
725
+ for (const [index, column] of view.columns.entries()) {
726
+ if (column.cell) ctx.addIssue({ code: "custom", path: ["columns", index], message: `only resolved tables carry cell columns ('${column.name}')` });
727
+ }
728
+ }
225
729
  for (const [index, link] of view.links.entries()) {
226
730
  if (link.enforce) {
227
731
  ctx.addIssue({ code: "custom", path: ["links", index, "enforce"], message: `view link '${link.name}' cannot enforce a foreign key` });
228
732
  }
733
+ if (link.onDelete !== void 0 || link.onUpdate !== void 0) {
734
+ ctx.addIssue({ code: "custom", path: ["links", index], message: `view link '${link.name}' cannot declare referential actions` });
735
+ }
229
736
  }
230
737
  });
738
+ var CELL_PART_TYPES = {
739
+ at: "timestamptz",
740
+ by: "text",
741
+ rev: "uuid",
742
+ source: "text",
743
+ conflicts: "jsonb"
744
+ };
745
+ function trackedCellColumns(table) {
746
+ const pk = new Set(table.primaryKey ?? []);
747
+ return table.columns.filter((column) => column.cell === void 0 && !pk.has(column.columnName) && column.onUpdateFn === void 0);
748
+ }
749
+ var SUFFIXES = Object.values(CELL_PART_SUFFIX);
750
+ function validateCellMetadata(table, index, ctx) {
751
+ const fail = (message, path = ["tables", index]) => ctx.addIssue({ code: "custom", path, message });
752
+ const cells = table.columns.flatMap((column, position) => column.cell ? [{ column, position, cell: column.cell }] : []);
753
+ if (table.materializes !== void 0) return;
754
+ if (table.cellMetadata !== true) {
755
+ for (const { position } of cells) fail(`cell-metadata columns require cellMetadata on table '${table.table}'`, ["tables", index, "columns", position]);
756
+ return;
757
+ }
758
+ if (table.source !== void 0) {
759
+ fail(`synced table '${table.table}' cannot track cell metadata`);
760
+ return;
761
+ }
762
+ const tracked = trackedCellColumns(table);
763
+ const byName = new Map(tracked.map((column) => [column.name, column]));
764
+ const withRev = cells.some(({ cell }) => cell.part === "rev");
765
+ if (withRev && table.internal !== true) fail(`only a resolved table's internal sources carry '__rev' cell columns ('${table.table}')`);
766
+ const seen = /* @__PURE__ */ new Set();
767
+ for (const { column, position, cell } of cells) {
768
+ const path = ["tables", index, "columns", position];
769
+ const owner = byName.get(cell.of);
770
+ if (!owner) {
771
+ fail(`cell column '${column.name}' belongs to '${cell.of}', which is not a tracked column`, path);
772
+ continue;
773
+ }
774
+ if (cell.part !== "at" && cell.part !== "by" && cell.part !== "rev") fail(`a table cell column must be part at/by/rev, not '${cell.part}'`, path);
775
+ if (column.name !== cellColumnName(owner.name, cell.part) || column.columnName !== cellColumnName(owner.columnName, cell.part)) {
776
+ fail(`cell column for '${owner.name}' ${cell.part} must be named '${cellColumnName(owner.name, cell.part)}' / '${cellColumnName(owner.columnName, cell.part)}'`, path);
777
+ }
778
+ if (column.type !== CELL_PART_TYPES[cell.part] || !column.nullable || column.unique || column.default !== void 0 || column.defaultFn !== void 0) {
779
+ fail(`cell column '${column.name}' must be a nullable, default-less ${CELL_PART_TYPES[cell.part]}`, path);
780
+ }
781
+ const key = `${cell.of}:${cell.part}`;
782
+ if (seen.has(key)) fail(`duplicate cell column '${column.name}'`, path);
783
+ seen.add(key);
784
+ }
785
+ for (const column of tracked) {
786
+ if (column.columnName.length > MAX_TRACKED_COLUMN_NAME) {
787
+ fail(`column '${column.columnName}' is too long to track cell metadata (max ${MAX_TRACKED_COLUMN_NAME} characters)`);
788
+ }
789
+ if (SUFFIXES.some((suffix) => column.columnName.endsWith(suffix))) {
790
+ fail(`column '${column.columnName}' ends in a reserved cell-metadata suffix (${SUFFIXES.join(", ")})`);
791
+ }
792
+ for (const part of withRev ? ["at", "by", "rev"] : ["at", "by"]) {
793
+ if (!seen.has(`${column.name}:${part}`)) fail(`tracked column '${column.name}' is missing its '${cellColumnName(column.columnName, part)}' cell column`);
794
+ }
795
+ }
796
+ }
231
797
  var canonicalDefinitionSchema = z.object({
232
798
  definition_format: z.literal(CANONICAL_FORMAT),
233
- namespace: identifier.max(41),
799
+ namespace: identifier2.max(41),
234
800
  description: z.string().max(2e3).optional(),
235
801
  enums: z.array(canonicalEnumSchema).default([]),
236
802
  tables: z.array(canonicalTableSchema).min(0).max(500),
@@ -248,6 +814,7 @@ var canonicalDefinitionSchema = z.object({
248
814
  ctx.addIssue({ code: "custom", path: ["tables", index, "table"], message: `duplicate relation '${table.table}'` });
249
815
  }
250
816
  seen.add(table.table);
817
+ validateCellMetadata(table, index, ctx);
251
818
  if (table.source !== void 0) {
252
819
  for (const [columnIndex, column] of table.columns.entries()) {
253
820
  if (column.onUpdateFn !== void 0) {
@@ -267,6 +834,26 @@ var canonicalDefinitionSchema = z.object({
267
834
  ...table.columns.filter((column) => column.unique && !(table.primaryKey?.length === 1 && table.primaryKey[0] === column.columnName)).map((column) => `${table.table}_${column.columnName}_unique`),
268
835
  ...table.links.filter((link) => link.enforce).map((link) => `fk_${table.table}_${link.name}`)
269
836
  ]);
837
+ for (const [index, link] of table.links.entries()) {
838
+ if ((link.onDelete !== void 0 || link.onUpdate !== void 0) && (!link.enforce || link.kind !== "toOne")) {
839
+ ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "links", index], message: "referential actions require an enforced toOne link" });
840
+ }
841
+ if (link.onDelete === "set null" || link.onUpdate === "set null") {
842
+ const source = table.columns.find((column) => column.columnName === link.on);
843
+ if (source?.nullable === false || table.primaryKey?.includes(link.on)) ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "links", index], message: "SET NULL requires a nullable source column" });
844
+ }
845
+ }
846
+ for (const [index, check] of (table.checks ?? []).entries()) {
847
+ const fail = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "checks", index], message });
848
+ if (table.source) fail("synced tables cannot declare checks");
849
+ if (names.has(check.name)) fail(`duplicate constraint '${check.name}'`);
850
+ names.add(check.name);
851
+ try {
852
+ compileCheckExpression(check.expression, table.columns);
853
+ } catch (error) {
854
+ fail(error instanceof Error ? error.message : String(error));
855
+ }
856
+ }
270
857
  for (const [index, fk] of (table.foreignKeys ?? []).entries()) {
271
858
  const fail = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "foreignKeys", index], message });
272
859
  if (names.has(fk.name)) fail(`duplicate foreign key '${fk.name}'`);
@@ -284,10 +871,18 @@ var canonicalDefinitionSchema = z.object({
284
871
  if (!sourceColumn || !targetColumn) fail("foreign key references an unknown column");
285
872
  else if (JSON.stringify(sourceColumn.type) !== JSON.stringify(targetColumn.type)) fail("foreign key column types must match");
286
873
  }
874
+ if (fk.onDelete === "set null" || fk.onUpdate === "set null") {
875
+ if (fk.columns.some((name) => table.columns.find((column) => column.columnName === name)?.nullable === false || table.primaryKey?.includes(name))) fail("SET NULL requires nullable source columns");
876
+ }
287
877
  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
878
  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
879
  }
290
880
  }
881
+ for (const [viewIndex, view] of (definition.views ?? []).entries()) {
882
+ for (const message of validateResolvedView({ view, tables: definition.tables, views: definition.views ?? [] })) {
883
+ ctx.addIssue({ code: "custom", path: ["views", viewIndex, "resolved"], message });
884
+ }
885
+ }
291
886
  const tableByName = new Map(definition.tables.map((table) => [table.table, table]));
292
887
  for (const [tableIndex, table] of definition.tables.entries()) {
293
888
  const cowMutations = table.cowMutations ?? [];
@@ -411,8 +1006,12 @@ function canonicalRelations(definition) {
411
1006
  ...view.primaryKey !== void 0 ? { primaryKey: view.primaryKey } : {},
412
1007
  links: view.links,
413
1008
  indexes: [],
414
- policies: { read: view.policies.read },
415
- actions: []
1009
+ // A resolved table's write policies (evaluated against the resolved row)
1010
+ // ride alongside the view's read policy.
1011
+ policies: { read: view.policies.read, ...view.resolved?.policies ?? {} },
1012
+ actions: [],
1013
+ ...view.resolved !== void 0 ? { resolved: view.resolved } : {},
1014
+ ...view.internal ? { internal: true } : {}
416
1015
  }));
417
1016
  return [...definition.tables, ...views].sort((a, b) => a.table.localeCompare(b.table));
418
1017
  }
@@ -422,13 +1021,14 @@ function normalizeCanonical(definition) {
422
1021
  ...definition,
423
1022
  enums: sortByName(definition.enums),
424
1023
  tables: [...definition.tables].sort((a, b) => a.table.localeCompare(b.table)).map((t) => {
425
- const { cowMutations, foreignKeys, ...rest } = t;
1024
+ const { cowMutations, foreignKeys, checks, ...rest } = t;
426
1025
  return {
427
1026
  ...rest,
428
1027
  links: sortByName(t.links),
429
1028
  indexes: [...t.indexes].sort((a, b) => (a.name ?? a.columns.join("_")).localeCompare(b.name ?? b.columns.join("_"))),
430
1029
  actions: sortByName(t.actions),
431
1030
  ...foreignKeys?.length ? { foreignKeys: sortByName(foreignKeys) } : {},
1031
+ ...checks?.length ? { checks: sortByName(checks) } : {},
432
1032
  // Omitted entirely when absent/empty so pre-CoW documents hash unchanged.
433
1033
  ...cowMutations !== void 0 && cowMutations.length > 0 ? { cowMutations: sortByName(cowMutations) } : {}
434
1034
  };
@@ -534,6 +1134,7 @@ function tableNames(table) {
534
1134
  updateByPkRoot: `update${rootPascal}ByPk`,
535
1135
  deleteRoot: `delete${rootPascal}`,
536
1136
  deleteByPkRoot: `delete${rootPascal}ByPk`,
1137
+ revertByPkRoot: `revert${rootPascal}ByPk`,
537
1138
  boolExp: `${type}BoolExp`,
538
1139
  orderBy: `${type}OrderBy`,
539
1140
  selectColumn: `${type}SelectColumn`,
@@ -559,6 +1160,17 @@ function tableNames(table) {
559
1160
  aggregateGroup: `${type}AggregateGroup`
560
1161
  };
561
1162
  }
1163
+ var CELL_TYPENAMES = {
1164
+ /** A plain table's cell: when and by whom it was last written. */
1165
+ stamp: "CellStamp",
1166
+ /** A resolved table's cell: which source won, when, by whom, and newer losers. */
1167
+ resolved: "ResolvedCell",
1168
+ /** A newer candidate that lost to the column's rule. */
1169
+ conflict: "CellConflict"
1170
+ };
1171
+ function cellsTypename(typename) {
1172
+ return `${typename}Cells`;
1173
+ }
562
1174
  var IR_VERSION = 2;
563
1175
  var LEGACY_IR_VERSION = 1;
564
1176
  var irVersionSchema = z.union([z.literal(LEGACY_IR_VERSION), z.literal(IR_VERSION)]);
@@ -628,6 +1240,21 @@ var selectionIrSchema = z.lazy(
628
1240
  () => z.object({
629
1241
  typename: z.string(),
630
1242
  columns: z.array(z.object({ alias: z.string(), column: z.string() })),
1243
+ cells: z.object({
1244
+ alias: z.string(),
1245
+ typename: z.string(),
1246
+ fields: z.array(z.object({
1247
+ alias: z.string(),
1248
+ column: z.string(),
1249
+ typename: z.string(),
1250
+ parts: z.array(z.object({
1251
+ alias: z.string(),
1252
+ part: z.enum(["source", "at", "by", "conflicts"]),
1253
+ fields: z.array(z.object({ alias: z.string(), field: z.enum(["source", "at", "value"]) })).optional(),
1254
+ typename: z.string().optional()
1255
+ }))
1256
+ }))
1257
+ }).optional(),
631
1258
  relations: z.array(z.object({
632
1259
  alias: z.string(),
633
1260
  link: z.string(),
@@ -649,6 +1276,11 @@ var selectionIrSchema = z.lazy(
649
1276
  })
650
1277
  );
651
1278
  var includeIfSchema = z.object({ variable: z.string(), negated: z.boolean() });
1279
+ var prefetchPlanIrSchema = z.object({
1280
+ source: z.string(),
1281
+ maxStalenessMs: z.number().int().positive().optional(),
1282
+ key: z.union([z.object({ one: valueIrSchema }), z.object({ many: valueIrSchema })])
1283
+ });
652
1284
  var selectRootSchema = z.object({
653
1285
  kind: z.literal("select"),
654
1286
  alias: z.string(),
@@ -660,7 +1292,8 @@ var selectRootSchema = z.object({
660
1292
  distinctOn: z.union([z.array(z.string()), z.object({ slot: z.string() })]).optional(),
661
1293
  limit: limitIrSchema,
662
1294
  offset: z.object({ variable: z.string().optional(), value: z.number().int().optional() }).optional(),
663
- includeIf: includeIfSchema.optional()
1295
+ includeIf: includeIfSchema.optional(),
1296
+ prefetch: z.array(prefetchPlanIrSchema).optional()
664
1297
  });
665
1298
  var byPkRootSchema = z.object({
666
1299
  kind: z.literal("byPk"),
@@ -670,7 +1303,8 @@ var byPkRootSchema = z.object({
670
1303
  pk: z.array(z.object({ column: z.string(), value: valueIrSchema })).min(1),
671
1304
  /** Policy predicate — ANDed with the PK match. */
672
1305
  where: predIrSchema,
673
- includeIf: includeIfSchema.optional()
1306
+ includeIf: includeIfSchema.optional(),
1307
+ prefetch: z.array(prefetchPlanIrSchema).optional()
674
1308
  });
675
1309
  var aggregateRootSchema = z.object({
676
1310
  kind: z.literal("aggregate"),
@@ -728,7 +1362,20 @@ var mutationRootSchema = z.object({
728
1362
  * coalesce view falls back to synced. Both are gated on a matching synced
729
1363
  * row and carry the write policy in `where` like update/delete.
730
1364
  */
731
- op: z.enum(["insert", "insertOne", "update", "updateByPk", "delete", "deleteByPk", "cowOverride", "cowRevert"]),
1365
+ op: z.enum(["insert", "insertOne", "update", "updateByPk", "delete", "deleteByPk", "cowOverride", "cowRevert", "resolvedWrite"]),
1366
+ /**
1367
+ * resolvedWrite: a write to a resolved table (`table` is its view). The
1368
+ * executor gates on the resolved row (write policy in `where`), writes only
1369
+ * the touched cells into the manual source, then re-checks `insertCheck`
1370
+ * against the resolved row. `revert` clears `revertColumns` from the manual
1371
+ * source; `delete` tombstones the row there.
1372
+ */
1373
+ resolvedAction: z.enum(["insert", "update", "delete", "revert"]).optional(),
1374
+ revertColumns: z.array(z.string()).optional(),
1375
+ /** `@writeback(source:, mode:)` directives on this root. */
1376
+ writeback: z.array(z.object({ source: z.string(), mode: z.enum(["sync", "async"]) })).optional(),
1377
+ /** `@localOnly(source:)` — writeback columns deliberately kept local for these sources. */
1378
+ localOnly: z.array(z.string()).optional(),
732
1379
  alias: z.string(),
733
1380
  table: z.string(),
734
1381
  /** CoW ops: logical name of the synced twin (read through its policy-filtered view). */
@@ -813,6 +1460,43 @@ var compiledOperationSchema = z.object({
813
1460
  }
814
1461
  }
815
1462
  });
1463
+ var resolvedSourceIrSchema = z.object({
1464
+ name: z.string(),
1465
+ kind: z.enum(["manual", "etl", "prefetch"]),
1466
+ /** Logical table key of the backing relation. */
1467
+ table: z.string(),
1468
+ columns: z.array(z.string()),
1469
+ key: z.string().optional(),
1470
+ fetch: z.union([
1471
+ z.object({ shape: z.literal("single") }),
1472
+ z.object({ shape: z.literal("batch"), maxKeys: z.number().int().positive() })
1473
+ ]).optional(),
1474
+ function: z.object({ kind: z.enum(["managed", "native"]), id: z.string() }).optional(),
1475
+ prefetchDefaults: z.object({
1476
+ maxStalenessMs: z.number().int().positive(),
1477
+ timeoutMs: z.number().int().positive(),
1478
+ onError: z.enum(["fallback", "fail"])
1479
+ }).optional(),
1480
+ writeback: z.object({
1481
+ columns: z.array(z.string()),
1482
+ defaultMode: z.enum(["sync", "async"]),
1483
+ timeoutMs: z.number().int().positive(),
1484
+ clearManualOnConfirm: z.boolean()
1485
+ }).optional()
1486
+ });
1487
+ var resolvedMetaIrSchema = z.object({
1488
+ /** Logical pk column of the resolved view (single-column). */
1489
+ pk: z.string(),
1490
+ /** Source name of the manual source. */
1491
+ manual: z.string(),
1492
+ sources: z.array(resolvedSourceIrSchema),
1493
+ deletedBy: z.array(z.string()),
1494
+ /**
1495
+ * Materialized: the view reads `table`, which the data plane refreshes from
1496
+ * the `compute` view (claims-gated) whenever it writes a source row.
1497
+ */
1498
+ materialized: z.object({ table: z.string(), compute: z.string() }).optional()
1499
+ });
816
1500
  var tableMetaIrSchema = z.object({
817
1501
  table: z.string(),
818
1502
  schema: z.string(),
@@ -853,6 +1537,22 @@ var tableMetaIrSchema = z.object({
853
1537
  * `onUpdateFn`). Omitted when empty so pre-onUpdate artifacts still parse.
854
1538
  */
855
1539
  touchOnUpdate: z.array(z.string()).optional(),
1540
+ /**
1541
+ * Per-cell metadata: logical column → the logical names of its cell-part
1542
+ * columns (which are listed in `columns`, but never filterable, orderable or
1543
+ * writable). Tables stamp `at`/`by` (and a manual source `rev`) on every
1544
+ * write; resolved views expose `source`/`at`/`by`/`conflicts`. Omitted when
1545
+ * the relation tracks no cells.
1546
+ */
1547
+ cells: z.record(z.string(), z.object({
1548
+ at: z.string().optional(),
1549
+ by: z.string().optional(),
1550
+ rev: z.string().optional(),
1551
+ source: z.string().optional(),
1552
+ conflicts: z.string().optional()
1553
+ }).strict()).optional(),
1554
+ /** Resolved-table view metadata (sources, prefetch/writeback config) for the data plane. */
1555
+ resolved: resolvedMetaIrSchema.optional(),
856
1556
  constraints: z.array(z.object({ name: z.string(), columns: z.array(z.string()) })),
857
1557
  /** Depth/node caps for dynamic filter trees on this table. */
858
1558
  dynamicFilterMaxDepth: z.number().int().positive().default(6),
@@ -894,7 +1594,9 @@ var PLATFORM_USER_FIELDS = [
894
1594
  ];
895
1595
  function generateSdl(canonical, options = {}) {
896
1596
  const def = normalizeCanonical(canonicalDefinitionSchema.parse(canonical));
897
- const tables = canonicalRelations(def).filter((table) => table.source === void 0);
1597
+ const exposed = canonicalRelations(def).filter((table) => table.source === void 0 && table.internal !== true);
1598
+ const cellsByTable = new Map(exposed.map((table) => [table.table, trackedCells(table)]));
1599
+ const tables = exposed.map((table) => ({ ...table, columns: table.columns.filter((column) => column.cell === void 0) }));
898
1600
  if (tables.length === 0) {
899
1601
  throw new SdlGenerationError(
900
1602
  `Namespace "${def.namespace}" exposes no relations (synced tables are excluded from SDL in v2.0) \u2014 nothing to generate.`
@@ -953,7 +1655,9 @@ function generateSdl(canonical, options = {}) {
953
1655
  blocks.push(
954
1656
  '"""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
1657
  );
1658
+ if (tables.some((table) => table.resolved !== void 0)) blocks.push(...RESOLVED_DIRECTIVE_BLOCKS);
956
1659
  for (const scalar of CUSTOM_SCALARS) blocks.push(`scalar ${scalar}`);
1660
+ blocks.push(...renderSharedCellTypes([...cellsByTable.values()]));
957
1661
  blocks.push(renderEnum(ORDER_BY_ENUM, [...ORDER_BY_ENUM_VALUES]));
958
1662
  blocks.push(renderEnum(DATE_BUCKET_ENUM, DATE_BUCKETS.map((bucket) => bucket.toUpperCase())));
959
1663
  for (const enumName of [...usedEnums].sort()) {
@@ -978,7 +1682,8 @@ function generateSdl(canonical, options = {}) {
978
1682
  names,
979
1683
  namesByTable,
980
1684
  emitAggregateBoolExp: aggregateBoolExpTargets.has(table.table),
981
- pinned: options.pinnedColumns?.[table.table] ?? []
1685
+ pinned: options.pinnedColumns?.[table.table] ?? [],
1686
+ cells: cellsByTable.get(table.table) ?? null
982
1687
  })
983
1688
  );
984
1689
  }
@@ -1068,6 +1773,52 @@ ${orderFields}
1068
1773
  }`
1069
1774
  ];
1070
1775
  }
1776
+ var RESOLVED_DIRECTIVE_BLOCKS = [
1777
+ '"""Refresh this resolved-table root from a prefetch source before reading. Key-addressed roots only; the source must support the root shape."""\ndirective @prefetch(source: String!, maxStaleness: String) repeatable on FIELD',
1778
+ renderEnum("WritebackMode", ["SYNC", "ASYNC"]),
1779
+ '"""Write this resolved-table mutation back to a prefetch source (SYNC rolls back on failure; ASYNC queues)."""\ndirective @writeback(source: String!, mode: WritebackMode) repeatable on FIELD',
1780
+ '"""Declare that this mutation deliberately keeps writeback columns local to the named source."""\ndirective @localOnly(source: String!) repeatable on FIELD'
1781
+ ];
1782
+ function trackedCells(table) {
1783
+ const columns = [...new Set(table.columns.flatMap((column) => column.cell ? [column.cell.of] : []))];
1784
+ if (columns.length === 0) return null;
1785
+ return { resolved: table.columns.some((column) => column.cell?.part === "source"), columns };
1786
+ }
1787
+ function renderSharedCellTypes(all) {
1788
+ const present = all.filter((cells) => cells !== null);
1789
+ const blocks = [];
1790
+ if (present.some((cells) => !cells.resolved)) {
1791
+ blocks.push(`"""When, and by whom, a cell was last written."""
1792
+ type ${CELL_TYPENAMES.stamp} {
1793
+ at: timestamptz!
1794
+ by: String
1795
+ }`);
1796
+ }
1797
+ if (present.some((cells) => cells.resolved)) {
1798
+ blocks.push(`"""A newer candidate value that lost to the column's resolve rule."""
1799
+ type ${CELL_TYPENAMES.conflict} {
1800
+ source: String!
1801
+ at: timestamptz
1802
+ value: jsonb
1803
+ }`);
1804
+ blocks.push(`"""How a resolved cell was decided: the winning source, its time and actor, and newer losers."""
1805
+ type ${CELL_TYPENAMES.resolved} {
1806
+ source: String!
1807
+ at: timestamptz
1808
+ by: String
1809
+ conflicts: [${CELL_TYPENAMES.conflict}!]!
1810
+ }`);
1811
+ }
1812
+ return blocks;
1813
+ }
1814
+ function renderCellsType(ctx) {
1815
+ if (!ctx.cells) return [];
1816
+ const cellType = ctx.cells.resolved ? CELL_TYPENAMES.resolved : CELL_TYPENAMES.stamp;
1817
+ const lines = ctx.cells.columns.map((column) => ` ${fieldName(column)}: ${cellType}`);
1818
+ return [`type ${cellsTypename(ctx.names.typename)} {
1819
+ ${lines.join("\n")}
1820
+ }`];
1821
+ }
1071
1822
  function relationArgs(target) {
1072
1823
  return `where: ${target.boolExp}, orderBy: [${target.orderBy}!], limit: Int, offset: Int`;
1073
1824
  }
@@ -1076,10 +1827,10 @@ function rootArgs(names) {
1076
1827
  }
1077
1828
  function primaryKeyColumns(table) {
1078
1829
  if (!table.primaryKey || table.primaryKey.length === 0) return [];
1079
- return table.primaryKey.map((identifier2) => {
1080
- const column = table.columns.find((c) => c.columnName === identifier2 || c.name === identifier2);
1830
+ return table.primaryKey.map((identifier3) => {
1831
+ const column = table.columns.find((c) => c.columnName === identifier3 || c.name === identifier3);
1081
1832
  if (!column) {
1082
- throw new SdlGenerationError(`Primary key column "${identifier2}" not found on table "${table.table}".`);
1833
+ throw new SdlGenerationError(`Primary key column "${identifier3}" not found on table "${table.table}".`);
1083
1834
  }
1084
1835
  return column;
1085
1836
  });
@@ -1088,6 +1839,7 @@ function renderTableBlocks(ctx) {
1088
1839
  const { table, names } = ctx;
1089
1840
  const blocks = [
1090
1841
  renderObjectType(ctx),
1842
+ ...renderCellsType(ctx),
1091
1843
  renderSelectColumnEnum(ctx),
1092
1844
  renderOrderByInput(ctx),
1093
1845
  renderBoolExp(ctx),
@@ -1102,11 +1854,49 @@ function renderTableBlocks(ctx) {
1102
1854
  ${fields}
1103
1855
  }`);
1104
1856
  }
1105
- if (allowsInsert(table) || allowsUpdate(table)) {
1857
+ if (ctx.table.resolved !== void 0) {
1858
+ blocks.push(...renderResolvedWriteBlocks(ctx));
1859
+ } else if (allowsInsert(table) || allowsUpdate(table)) {
1106
1860
  blocks.push(...renderWriteBlocks(ctx, { insert: allowsInsert(table), update: allowsUpdate(table) }));
1107
1861
  }
1108
1862
  return blocks;
1109
1863
  }
1864
+ function resolvedWritableColumns(table) {
1865
+ const system = new Set(Object.values(RESOLVED_VIEW_SYSTEM_COLUMNS).map((column) => column.name));
1866
+ return table.columns.filter((column) => !system.has(column.name));
1867
+ }
1868
+ function renderResolvedWriteBlocks(ctx) {
1869
+ const { table, names } = ctx;
1870
+ const writable = resolvedWritableColumns(table);
1871
+ const pk = new Set(table.primaryKey ?? []);
1872
+ const settable = writable.filter((column) => !pk.has(column.columnName));
1873
+ const line = (column) => ` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}`;
1874
+ const blocks = [];
1875
+ if (allowsInsert(table)) blocks.push(`input ${names.insertInput} {
1876
+ ${writable.map(line).join("\n")}
1877
+ }`);
1878
+ if (allowsUpdate(table)) {
1879
+ blocks.push(`input ${names.setInput} {
1880
+ ${settable.map(line).join("\n")}
1881
+ }`);
1882
+ blocks.push(renderEnum(names.updateColumnEnum, settable.map((column) => fieldName(column.name))));
1883
+ }
1884
+ return blocks;
1885
+ }
1886
+ function renderResolvedMutationFields(table, names) {
1887
+ const lines = [];
1888
+ const pk = primaryKeyColumns(table);
1889
+ if (allowsInsert(table)) lines.push(` ${names.insertOneRoot}(object: ${names.insertInput}!): ${names.typename}`);
1890
+ if (allowsUpdate(table) && pk.length > 0) {
1891
+ lines.push(` ${names.updateByPkRoot}(pkColumns: ${names.pkColumnsInput}!, _set: ${names.setInput}!): ${names.typename}`);
1892
+ lines.push(` ${names.revertByPkRoot}(pkColumns: ${names.pkColumnsInput}!, columns: [${names.updateColumnEnum}!]!): ${names.typename}`);
1893
+ if ((table.resolved?.deletedBy.length ?? 0) > 0) {
1894
+ const args = pk.map((column) => `${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}!`).join(", ");
1895
+ lines.push(` ${names.deleteByPkRoot}(${args}): ${names.typename}`);
1896
+ }
1897
+ }
1898
+ return lines;
1899
+ }
1110
1900
  function allowsInsert(table) {
1111
1901
  return table.policies.write !== void 0 || table.policies.insert !== void 0;
1112
1902
  }
@@ -1129,6 +1919,10 @@ function renderObjectType(ctx) {
1129
1919
  const deprecated = column.deprecated ? " @deprecated" : "";
1130
1920
  lines.push(` ${fieldName(column.name)}: ${columnGqlTypeOf(table, column)}${bang}${deprecated}`);
1131
1921
  }
1922
+ if (ctx.cells) {
1923
+ lines.push(' """Per-cell metadata: when each field was last written, and by whom."""');
1924
+ lines.push(` ${CELLS_FIELD}: ${cellsTypename(names.typename)}!`);
1925
+ }
1132
1926
  for (const link of table.links) {
1133
1927
  if (link.description !== void 0) lines.push(...descriptionLines(link.description, " "));
1134
1928
  if (link.kind === "toPlatformUser") {
@@ -1197,7 +1991,10 @@ function numericishColumns(table) {
1197
1991
  return table.columns.filter((column) => typeof column.type !== "object" && NUMERICISH.has(column.type));
1198
1992
  }
1199
1993
  function jsonbColumns(table) {
1200
- return table.columns.filter((column) => column.type === "jsonb");
1994
+ return clientWritableColumns(table).filter((column) => column.type === "jsonb");
1995
+ }
1996
+ function clientWritableColumns(table) {
1997
+ return table.columns;
1201
1998
  }
1202
1999
  function renderAggregateTypes(ctx) {
1203
2000
  const { table, names } = ctx;
@@ -1281,14 +2078,14 @@ function constraintValues(table) {
1281
2078
  return [...values].sort();
1282
2079
  }
1283
2080
  function clientSettableColumns(table) {
1284
- return table.columns.filter((column) => column.onUpdateFn === void 0);
2081
+ return clientWritableColumns(table).filter((column) => column.onUpdateFn === void 0);
1285
2082
  }
1286
2083
  function renderWriteBlocks(ctx, access) {
1287
2084
  const { table, names, pinned } = ctx;
1288
2085
  const blocks = [];
1289
2086
  if (access.insert) {
1290
2087
  const pinnedSet = new Set(pinned);
1291
- const insertable = table.columns.filter(
2088
+ const insertable = clientWritableColumns(table).filter(
1292
2089
  (column) => !pinnedSet.has(column.name) && !pinnedSet.has(column.columnName)
1293
2090
  );
1294
2091
  if (insertable.length === 0) {
@@ -1381,6 +2178,10 @@ function renderMutationRoot(tables, namesByTable) {
1381
2178
  if (!canInsert && !canUpdate) continue;
1382
2179
  const names = namesByTable.get(table.table);
1383
2180
  if (!names) continue;
2181
+ if (table.resolved !== void 0) {
2182
+ lines.push(...renderResolvedMutationFields(table, names));
2183
+ continue;
2184
+ }
1384
2185
  const constraints = constraintValues(table);
1385
2186
  const onConflictArg = canInsert && allowsOnConflict(table) && constraints.length > 0 ? `, onConflict: ${names.onConflict}` : "";
1386
2187
  const updateArgs = [`_set: ${names.setInput}`];
@@ -1514,13 +2315,13 @@ function generateTypePolicies(canonical) {
1514
2315
  const entries = [];
1515
2316
  let hasPlatformUser = false;
1516
2317
  for (const table of canonicalRelations(def)) {
1517
- if (table.source !== void 0) continue;
2318
+ if (table.source !== void 0 || table.internal === true) continue;
1518
2319
  if (table.links.some((link) => link.kind === "toPlatformUser")) hasPlatformUser = true;
1519
2320
  if (!table.primaryKey || table.primaryKey.length === 0) continue;
1520
- const keyFields = table.primaryKey.map((identifier2) => {
1521
- const column = table.columns.find((c) => c.columnName === identifier2 || c.name === identifier2);
2321
+ const keyFields = table.primaryKey.map((identifier3) => {
2322
+ const column = table.columns.find((c) => c.columnName === identifier3 || c.name === identifier3);
1522
2323
  if (!column) {
1523
- throw new CodegenGqlError(`Primary key column "${identifier2}" not found on relation "${table.table}".`);
2324
+ throw new CodegenGqlError(`Primary key column "${identifier3}" not found on relation "${table.table}".`);
1524
2325
  }
1525
2326
  return fieldName(column.name);
1526
2327
  });
@@ -1538,6 +2339,6 @@ ${body}
1538
2339
  `;
1539
2340
  }
1540
2341
 
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 };
2342
+ export { BUCKETABLE_COLUMN_TYPES, CELL_TYPENAMES, 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, cellsTypename, columnTypeForScalar, comparisonExpName, enumTypeName, fieldName, generateSdl, generateTypePolicies, graphqlScalarFor, manifestOperationSchema, manifestRequiresSchema, operationsManifestSchema, pascalCase, resolvedWritableColumns, tableNames, typeName };
1542
2343
  //# sourceMappingURL=sdl.js.map
1543
2344
  //# sourceMappingURL=sdl.js.map