@sequenceholdings/orm 2.1.0 → 2.2.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 (77) hide show
  1. package/README.md +40 -1
  2. package/dist/authoring.cjs +1619 -121
  3. package/dist/authoring.cjs.map +1 -1
  4. package/dist/authoring.d.cts +252 -8
  5. package/dist/authoring.d.ts +252 -8
  6. package/dist/authoring.js +1607 -123
  7. package/dist/authoring.js.map +1 -1
  8. package/dist/canonical.cjs +713 -41
  9. package/dist/canonical.cjs.map +1 -1
  10. package/dist/canonical.d.cts +1520 -51
  11. package/dist/canonical.d.ts +1520 -51
  12. package/dist/canonical.js +693 -42
  13. package/dist/canonical.js.map +1 -1
  14. package/dist/cli.cjs +7103 -3901
  15. package/dist/cli.cjs.map +1 -1
  16. package/dist/cli.js +7104 -3902
  17. package/dist/cli.js.map +1 -1
  18. package/dist/client.cjs +122 -40
  19. package/dist/client.cjs.map +1 -1
  20. package/dist/client.d.cts +85 -25
  21. package/dist/client.d.ts +85 -25
  22. package/dist/client.js +119 -41
  23. package/dist/client.js.map +1 -1
  24. package/dist/compiler.cjs +2464 -450
  25. package/dist/compiler.cjs.map +1 -1
  26. package/dist/compiler.d.cts +79 -4
  27. package/dist/compiler.d.ts +79 -4
  28. package/dist/compiler.js +2457 -451
  29. package/dist/compiler.js.map +1 -1
  30. package/dist/constants-C6EG6Wq7.d.cts +99 -0
  31. package/dist/constants-C6EG6Wq7.d.ts +99 -0
  32. package/dist/definition-DjfC7Lll.d.cts +91 -0
  33. package/dist/definition-DjfC7Lll.d.ts +91 -0
  34. package/dist/dynamic-input.cjs +2 -1
  35. package/dist/dynamic-input.cjs.map +1 -1
  36. package/dist/dynamic-input.d.cts +9 -1
  37. package/dist/dynamic-input.d.ts +9 -1
  38. package/dist/dynamic-input.js +2 -1
  39. package/dist/dynamic-input.js.map +1 -1
  40. package/dist/index.cjs +2656 -418
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.d.cts +693 -116
  43. package/dist/index.d.ts +693 -116
  44. package/dist/index.js +2622 -420
  45. package/dist/index.js.map +1 -1
  46. package/dist/ir.cjs +811 -48
  47. package/dist/ir.cjs.map +1 -1
  48. package/dist/ir.d.cts +670 -27
  49. package/dist/ir.d.ts +670 -27
  50. package/dist/ir.js +802 -49
  51. package/dist/ir.js.map +1 -1
  52. package/dist/{naming-DCn94LJ4.d.ts → naming-CPfx7EfF.d.ts} +14 -1
  53. package/dist/{naming-DPglwzkB.d.cts → naming-CU9ThP_E.d.cts} +14 -1
  54. package/dist/policy-1iZbGnZZ.d.cts +24 -0
  55. package/dist/policy-1iZbGnZZ.d.ts +24 -0
  56. package/dist/predicate-v2.cjs +804 -51
  57. package/dist/predicate-v2.cjs.map +1 -1
  58. package/dist/predicate-v2.d.cts +2 -1
  59. package/dist/predicate-v2.d.ts +2 -1
  60. package/dist/predicate-v2.js +804 -51
  61. package/dist/predicate-v2.js.map +1 -1
  62. package/dist/sdl.cjs +1014 -75
  63. package/dist/sdl.cjs.map +1 -1
  64. package/dist/sdl.d.cts +14 -3
  65. package/dist/sdl.d.ts +14 -3
  66. package/dist/sdl.js +1012 -76
  67. package/dist/sdl.js.map +1 -1
  68. package/package.json +13 -7
  69. package/src/codegen-gql/server-fns.cjs +2 -2
  70. package/templates/orm-namespace/AGENTS.md +10 -0
  71. package/templates/orm-namespace/CLAUDE.md +1 -82
  72. package/templates/orm-namespace/codegen.preset.d.cts +7 -0
  73. package/templates/orm-namespace/codegen.ts +2 -1
  74. package/templates/orm-namespace/graphql/example/queries.ts +4 -3
  75. package/templates/orm-namespace/package.json +2 -1
  76. package/templates/orm-namespace/schema/tables.ts +3 -2
  77. package/templates/orm-namespace/sequence.config.ts +7 -11
@@ -3,14 +3,505 @@
3
3
  var zod = require('zod');
4
4
 
5
5
  // src/canonical/v2.ts
6
+ var objectRef = zod.z.string().max(255).regex(/^[a-z][a-z0-9_]{0,40}\/[A-Za-z_][A-Za-z0-9_]*$/, "Expected an explicit namespace/object reference (no wildcards)");
7
+ var functionRef = zod.z.string().max(255).regex(/^[a-z][a-z0-9-]*$/, "Expected a function slug");
8
+ var refs = (schema) => zod.z.array(schema).max(256).refine(
9
+ (values) => new Set(values).size === values.length,
10
+ "Duplicate dependency reference"
11
+ ).default([]);
12
+ var ontologyContractSchema = zod.z.strictObject({
13
+ version: zod.z.literal(1),
14
+ read: refs(objectRef),
15
+ write: refs(objectRef),
16
+ produce: refs(objectRef),
17
+ invoke: refs(functionRef)
18
+ }).refine((contract) => JSON.stringify(contract).length <= 4096, "Ontology contract exceeds the 4 KiB signed-context budget");
19
+ function readOntologyContract(manifest) {
20
+ if (!manifest || typeof manifest !== "object" || !("ontology" in manifest) || manifest.ontology === void 0) return void 0;
21
+ return ontologyContractSchema.parse(manifest.ontology);
22
+ }
23
+ var quoteIdentifier = (name) => `"${name.replaceAll('"', '""')}"`;
24
+ var quoteLiteral = (value) => `'${value.replaceAll("'", "''")}'`;
25
+
26
+ // src/checks/compile.ts
27
+ function tokenize(source) {
28
+ const tokens = [];
29
+ for (let position = 0; position < source.length; ) {
30
+ const char = source[position];
31
+ if (/\s/.test(char)) {
32
+ position++;
33
+ continue;
34
+ }
35
+ if (char === "(" || char === ")" || char === ",") {
36
+ tokens.push({ kind: char === "(" ? "lparen" : char === ")" ? "rparen" : "comma", value: char, position });
37
+ position++;
38
+ continue;
39
+ }
40
+ if (char === "'") {
41
+ const start = position++;
42
+ let value = "";
43
+ let closed = false;
44
+ while (position < source.length) {
45
+ if (source[position] !== "'") {
46
+ value += source[position++];
47
+ continue;
48
+ }
49
+ if (source[position + 1] === "'") {
50
+ value += "'";
51
+ position += 2;
52
+ continue;
53
+ }
54
+ position++;
55
+ closed = true;
56
+ break;
57
+ }
58
+ if (!closed) throw new Error(`unterminated string at position ${start}`);
59
+ if (/[\x00-\x1f\x7f]/.test(value)) throw new Error(`control character in string at position ${start}`);
60
+ tokens.push({ kind: "string", value, position: start });
61
+ continue;
62
+ }
63
+ const operator = /^(<=|>=|<>|!=|!~|=|<|>|~)/.exec(source.slice(position));
64
+ if (operator) {
65
+ tokens.push({ kind: "operator", value: operator[0] === "<>" ? "!=" : operator[0], position });
66
+ position += operator[0].length;
67
+ continue;
68
+ }
69
+ const number = /^-?\d+(?:\.\d+)?/.exec(source.slice(position));
70
+ if (number) {
71
+ tokens.push({ kind: "number", value: number[0], position });
72
+ position += number[0].length;
73
+ continue;
74
+ }
75
+ const identifier3 = /^[a-zA-Z_][a-zA-Z_0-9]*/.exec(source.slice(position));
76
+ if (identifier3) {
77
+ tokens.push({ kind: "identifier", value: identifier3[0], position });
78
+ position += identifier3[0].length;
79
+ continue;
80
+ }
81
+ throw new Error(`unexpected character '${char}' at position ${position}`);
82
+ }
83
+ tokens.push({ kind: "end", value: "", position: source.length });
84
+ if (tokens.length > 256) throw new Error("check expression exceeds 256 tokens");
85
+ return tokens;
86
+ }
87
+ var Parser = class {
88
+ constructor(tokens, columns) {
89
+ this.tokens = tokens;
90
+ this.columns = /* @__PURE__ */ new Map();
91
+ for (const column of columns) {
92
+ for (const alias of [column.name, column.columnName]) {
93
+ const previous = this.columns.get(alias);
94
+ if (previous && previous.columnName !== column.columnName) {
95
+ throw new Error(`ambiguous check column '${alias}'`);
96
+ }
97
+ this.columns.set(alias, column);
98
+ }
99
+ }
100
+ }
101
+ index = 0;
102
+ depth = 0;
103
+ columns;
104
+ parse() {
105
+ const expression = this.parseOr();
106
+ this.expect("end", "end of expression");
107
+ return expression;
108
+ }
109
+ parseOr() {
110
+ let left = this.parseAnd();
111
+ while (this.matchKeyword("or")) left = `(${left} OR ${this.parseAnd()})`;
112
+ return left;
113
+ }
114
+ parseAnd() {
115
+ let left = this.parseNot();
116
+ while (this.matchKeyword("and")) left = `(${left} AND ${this.parseNot()})`;
117
+ return left;
118
+ }
119
+ parseNot() {
120
+ if (this.matchKeyword("not")) return `(NOT ${this.parseNot()})`;
121
+ return this.parsePrimary();
122
+ }
123
+ parsePrimary() {
124
+ if (this.peek().kind === "lparen") {
125
+ if (++this.depth > 32) throw new Error("check expression nesting exceeds 32 levels");
126
+ this.advance();
127
+ const expression = this.parseOr();
128
+ this.expect("rparen", "')'");
129
+ this.depth--;
130
+ return `(${expression})`;
131
+ }
132
+ return this.parseComparison();
133
+ }
134
+ parseComparison() {
135
+ const left = this.parseValue();
136
+ if (this.matchKeyword("is")) {
137
+ const negate = this.matchKeyword("not");
138
+ this.expectKeyword("null");
139
+ return `${left.sql} IS ${negate ? "NOT " : ""}NULL`;
140
+ }
141
+ if (this.matchKeyword("between")) {
142
+ const lower = this.parseValue();
143
+ this.expectKeyword("and");
144
+ const upper = this.parseValue();
145
+ return `${left.sql} BETWEEN ${lower.sql} AND ${upper.sql}`;
146
+ }
147
+ if (this.matchKeyword("in")) {
148
+ this.expect("lparen", "'(' after IN");
149
+ const values = [this.parseValue()];
150
+ while (this.peek().kind === "comma") {
151
+ this.advance();
152
+ values.push(this.parseValue());
153
+ }
154
+ this.expect("rparen", "')' after IN values");
155
+ if (values.some((value) => !value.literal)) throw new Error("IN values must be literals");
156
+ return `${left.sql} IN (${values.map((value) => value.sql).join(", ")})`;
157
+ }
158
+ const operator = this.expect("operator", "a comparison operator");
159
+ const right = this.parseValue();
160
+ if (operator.value === "~" || operator.value === "!~") {
161
+ if (left.column && left.column.type !== "text") throw new Error("regex checks require a text column");
162
+ if (!right.literal) throw new Error("regex pattern must be a literal");
163
+ }
164
+ return `${left.sql} ${operator.value} ${right.sql}`;
165
+ }
166
+ parseValue() {
167
+ const token = this.advance();
168
+ if (token.kind === "string") return { sql: quoteLiteral(token.value), literal: true };
169
+ if (token.kind === "number") {
170
+ const number = Number(token.value);
171
+ if (!Number.isFinite(number) || Number.isInteger(number) && !Number.isSafeInteger(number)) {
172
+ throw new Error(`unsafe numeric literal at position ${token.position}`);
173
+ }
174
+ return { sql: token.value, literal: true };
175
+ }
176
+ if (token.kind !== "identifier") throw new Error(`expected a column or literal at position ${token.position}`);
177
+ const keyword = token.value.toLowerCase();
178
+ if (keyword === "true" || keyword === "false" || keyword === "null") return { sql: keyword.toUpperCase(), literal: true };
179
+ if (keyword === "btrim") {
180
+ this.expect("lparen", "'(' after btrim");
181
+ const column2 = this.parseColumn();
182
+ this.expect("rparen", "')' after btrim column");
183
+ if (column2.type !== "text") throw new Error("btrim requires a text column");
184
+ return { sql: `btrim(${quoteIdentifier(column2.columnName)})`, column: column2 };
185
+ }
186
+ const column = this.columns.get(token.value);
187
+ if (!column) throw new Error(`unknown check column '${token.value}'`);
188
+ return { sql: quoteIdentifier(column.columnName), column };
189
+ }
190
+ parseColumn() {
191
+ const token = this.expect("identifier", "a column name");
192
+ const column = this.columns.get(token.value);
193
+ if (!column) throw new Error(`unknown check column '${token.value}'`);
194
+ return column;
195
+ }
196
+ peek() {
197
+ return this.tokens[this.index];
198
+ }
199
+ advance() {
200
+ const token = this.peek();
201
+ if (token.kind !== "end") this.index++;
202
+ return token;
203
+ }
204
+ matchKeyword(keyword) {
205
+ if (this.peek().kind !== "identifier" || this.peek().value.toLowerCase() !== keyword) return false;
206
+ this.advance();
207
+ return true;
208
+ }
209
+ expectKeyword(keyword) {
210
+ if (!this.matchKeyword(keyword)) throw new Error(`expected '${keyword}' at position ${this.peek().position}`);
211
+ }
212
+ expect(kind, description) {
213
+ const token = this.peek();
214
+ if (token.kind !== kind) throw new Error(`expected ${description} at position ${token.position}`);
215
+ return this.advance();
216
+ }
217
+ };
218
+ function compileCheckExpression(source, columns) {
219
+ return new Parser(tokenize(source), columns).parse();
220
+ }
6
221
 
7
222
  // src/constants.ts
8
223
  var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
224
+ var MAX_IDENTIFIER_LENGTH = 63;
225
+ var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]{0,62}\.[a-z][a-z0-9_]{0,62}$/;
226
+ var CELL_PARTS = ["at", "by", "rev", "source", "conflicts"];
227
+ var CELL_PART_SUFFIX = {
228
+ at: "__at",
229
+ by: "__by",
230
+ rev: "__rev",
231
+ source: "__src",
232
+ conflicts: "__lost"
233
+ };
234
+ var MAX_TRACKED_COLUMN_NAME = MAX_IDENTIFIER_LENGTH - 6;
235
+ function cellColumnName(column, part) {
236
+ return `${column}${CELL_PART_SUFFIX[part]}`;
237
+ }
238
+ var MATERIALIZE_ACTOR = "orm:materialize";
239
+ JSON.stringify({ sub: MATERIALIZE_ACTOR, org: null, roles: [], capabilities: [] });
240
+ var policyVariantSchema = zod.z.object({
241
+ /** Claims-only guard (capability()/role()/claim() comparisons — no columns). */
242
+ when: zod.z.string().min(1).max(1e3),
243
+ /** Exactly one of: allow-all bypass, or a row predicate. */
244
+ allow: zod.z.literal(true).optional(),
245
+ policy: zod.z.string().min(1).max(2e3).optional()
246
+ }).refine((v) => v.allow === true !== (typeof v.policy === "string"), {
247
+ message: "A variant declares exactly one of allow: true or policy"
248
+ });
249
+ var policyExpressionSchema = zod.z.union([
250
+ zod.z.string().min(1).max(2e3),
251
+ zod.z.object({
252
+ default: zod.z.string().min(1).max(2e3),
253
+ variants: zod.z.array(policyVariantSchema).max(10).default([])
254
+ })
255
+ ]);
256
+ function parseDurationMs(input) {
257
+ if (typeof input === "number") return Number.isInteger(input) && input > 0 ? input : void 0;
258
+ const match = /^(\d+(?:\.\d+)?)(ms|s|m)$/.exec(input);
259
+ if (!match) return void 0;
260
+ const factor = match[2] === "ms" ? 1 : match[2] === "s" ? 1e3 : 6e4;
261
+ const value = Math.round(Number(match[1]) * factor);
262
+ return value > 0 ? value : void 0;
263
+ }
264
+ var identifier = zod.z.string().min(1).max(63).regex(/^[a-z][a-z0-9_]*$/);
265
+ var durationMs = zod.z.number().int().positive().max(24 * 60 * 60 * 1e3);
266
+ var RESOLVED_VIEW_SYSTEM_COLUMNS = {
267
+ writebackStatus: { name: "writebackStatus", columnName: "_writeback_status", type: "text" },
268
+ writebackError: { name: "writebackError", columnName: "_writeback_error", type: "text" }
269
+ };
270
+ var RESOLVED_SOURCE_SYSTEM_COLUMNS = {
271
+ tombstoned: { name: "sysTombstoned", columnName: "_tombstoned" },
272
+ fetchedAt: { name: "sysFetchedAt", columnName: "_fetched_at" },
273
+ writebackStatus: { name: "sysWritebackStatus", columnName: "_writeback_status" },
274
+ writebackError: { name: "sysWritebackError", columnName: "_writeback_error" },
275
+ writebackAt: { name: "sysWritebackAt", columnName: "_writeback_at" }
276
+ };
277
+ var RESOLVED_VIEW_CELL_PARTS = ["source", "at", "by", "conflicts"];
278
+ var RESOLVED_SOURCE_CELL_PARTS = { manual: ["at", "by", "rev"], prefetch: ["at", "by"] };
279
+ var RESERVED_RESOLVED_COLUMN_NAMES = /* @__PURE__ */ new Set([
280
+ ...Object.values(RESOLVED_VIEW_SYSTEM_COLUMNS).flatMap((c) => [c.name, c.columnName]),
281
+ ...Object.values(RESOLVED_SOURCE_SYSTEM_COLUMNS).flatMap((c) => [c.name, c.columnName])
282
+ ]);
283
+ var COMPARATORS = ["latest", "highest", "lowest", "priority", "nonNullFirst", "custom"];
284
+ var canonicalFunctionRefSchema = zod.z.object({
285
+ /** `managed` = a Managed Function id/slug; `native` = an in-process Atlas function (reserved). */
286
+ kind: zod.z.enum(["managed", "native"]),
287
+ id: zod.z.string().min(1).max(200)
288
+ }).strict();
289
+ var canonicalFetchShapeSchema = zod.z.union([
290
+ zod.z.object({ shape: zod.z.literal("single") }).strict(),
291
+ zod.z.object({ shape: zod.z.literal("batch"), maxKeys: zod.z.number().int().min(2).max(500) }).strict()
292
+ ]);
293
+ var canonicalResolvedSourceSchema = zod.z.object({
294
+ name: identifier,
295
+ kind: zod.z.enum(["manual", "etl", "prefetch"]),
296
+ /** Backing relation in this namespace: an internal table (manual/prefetch) or a synced table (etl). */
297
+ table: identifier,
298
+ /** Resolved columns (logical names) this source supplies — the primary key is implicit. */
299
+ columns: zod.z.array(zod.z.string().min(1).max(63)).min(1).max(500),
300
+ /** etl: logical timestamptz/date column on the synced table used as every cell's updated_at. */
301
+ updatedAt: zod.z.string().min(1).max(63).optional(),
302
+ /** prefetch: resolved column holding the source system's key (may be the primary key). */
303
+ key: zod.z.string().min(1).max(63).optional(),
304
+ fetch: canonicalFetchShapeSchema.optional(),
305
+ function: canonicalFunctionRefSchema.optional(),
306
+ prefetchDefaults: zod.z.object({
307
+ maxStalenessMs: durationMs,
308
+ timeoutMs: durationMs,
309
+ onError: zod.z.enum(["fallback", "fail"])
310
+ }).strict().optional(),
311
+ writeback: zod.z.object({
312
+ columns: zod.z.array(zod.z.string().min(1).max(63)).min(1).max(500),
313
+ defaultMode: zod.z.enum(["sync", "async"]),
314
+ timeoutMs: durationMs,
315
+ /** Remove confirmed cells from the manual source so a stale edit cannot shadow the source. */
316
+ clearManualOnConfirm: zod.z.boolean()
317
+ }).strict().optional()
318
+ }).strict();
319
+ var canonicalResolveRuleSchema = zod.z.object({
320
+ column: zod.z.string().min(1).max(63),
321
+ comparator: zod.z.enum(COMPARATORS),
322
+ /** Source names, in tie-break order. */
323
+ candidates: zod.z.array(identifier).min(1).max(16),
324
+ /** custom only: SQL ORDER BY key over value / updated_at / source / actor / ord. */
325
+ orderBy: zod.z.string().min(1).max(2e3).optional()
326
+ }).strict();
327
+ var canonicalResolvedSchema = zod.z.object({
328
+ sources: zod.z.array(canonicalResolvedSourceSchema).min(1).max(16),
329
+ /** One rule per resolved (non-pk) column; documents the generated view for tooling. */
330
+ resolve: zod.z.array(canonicalResolveRuleSchema).max(500),
331
+ /** Sources whose tombstone hides the resolved row (v1: the manual source only). */
332
+ deletedBy: zod.z.array(identifier).max(1).default([]),
333
+ /**
334
+ * Materialized: the view reads an indexed internal table (`table`) that the
335
+ * data plane keeps in step with `compute`, the internal view holding the
336
+ * resolution query. Omitted when the view computes on read.
337
+ */
338
+ materialized: zod.z.object({ table: identifier, compute: identifier }).strict().optional(),
339
+ /** Write policies evaluated against the RESOLVED row (reads use the view's read policy). */
340
+ policies: zod.z.object({
341
+ write: policyExpressionSchema.optional(),
342
+ insert: policyExpressionSchema.optional(),
343
+ update: policyExpressionSchema.optional()
344
+ }).strict().default({})
345
+ }).strict();
346
+ function validateResolvedView({ view, tables, views = [] }) {
347
+ const resolved = view.resolved;
348
+ if (!resolved) return [];
349
+ const errors = [];
350
+ const fail = (message) => errors.push(`resolved table '${view.view}': ${message}`);
351
+ const pkColumn = view.primaryKey?.length === 1 ? view.columns.find((c) => c.columnName === view.primaryKey?.[0]) : void 0;
352
+ if (!pkColumn) {
353
+ fail("requires exactly one primary-key column");
354
+ return errors;
355
+ }
356
+ const systemNames = new Set(Object.values(RESOLVED_VIEW_SYSTEM_COLUMNS).map((c) => c.name));
357
+ const scope = {
358
+ view,
359
+ resolved,
360
+ pkColumn,
361
+ dataColumns: new Map(view.columns.filter((c) => !systemNames.has(c.name) && c.cell === void 0).map((c) => [c.name, c])),
362
+ sourceByName: /* @__PURE__ */ new Map(),
363
+ fail
364
+ };
365
+ if (resolved.sources.filter((source) => source.kind === "manual").length !== 1) fail("requires exactly one manual source");
366
+ const tableByName = new Map(tables.map((table) => [table.table, table]));
367
+ for (const source of resolved.sources) {
368
+ if (scope.sourceByName.has(source.name)) fail(`duplicate source '${source.name}'`);
369
+ scope.sourceByName.set(source.name, source);
370
+ const table = tableByName.get(source.table);
371
+ if (!table) fail(`source '${source.name}' references unknown table '${source.table}'`);
372
+ else validateSource({ scope, source, table });
373
+ }
374
+ validateRules(scope);
375
+ validateViewCells(scope);
376
+ if (resolved.materialized) validateMaterialized({ scope, tableByName, views });
377
+ for (const name of resolved.deletedBy) {
378
+ if (scope.sourceByName.get(name)?.kind !== "manual") fail(`deletedBy '${name}' must be the manual source`);
379
+ }
380
+ return errors;
381
+ }
382
+ function validateSource({ scope, source, table }) {
383
+ const { fail } = scope;
384
+ if (source.kind === "etl" && table.source === void 0) fail(`etl source '${source.name}' must reference a synced table`);
385
+ if (source.kind !== "etl" && (table.internal !== true || table.cellMetadata !== true)) {
386
+ fail(`${source.kind} source '${source.name}' must reference an internal table with cell metadata`);
387
+ }
388
+ const hasRev = table.columns.some((column) => column.cell?.part === "rev");
389
+ if (source.kind === "manual" && !hasRev) fail(`manual source '${source.name}' must carry '__rev' cell columns`);
390
+ if (source.kind === "prefetch" && hasRev) fail(`prefetch source '${source.name}' must not carry '__rev' cell columns`);
391
+ const byName = new Map(table.columns.map((c) => [c.name, c]));
392
+ validateSourceColumns({ scope, source, byName });
393
+ if (source.kind === "etl") {
394
+ const updatedAt = source.updatedAt === void 0 ? void 0 : byName.get(source.updatedAt);
395
+ if (source.updatedAt !== void 0 && updatedAt?.type !== "timestamptz" && updatedAt?.type !== "date") {
396
+ fail(`etl source '${source.name}' updatedAt must name a timestamptz/date column on '${source.table}'`);
397
+ }
398
+ }
399
+ validatePrefetchFields({ scope, source });
400
+ }
401
+ function validatePrefetchFields({ scope, source }) {
402
+ const { fail, pkColumn } = scope;
403
+ if (source.kind !== "prefetch") {
404
+ if (source.key || source.fetch || source.function || source.prefetchDefaults || source.writeback) {
405
+ fail(`only prefetch sources may declare key/fetch/function/prefetchDefaults/writeback ('${source.name}')`);
406
+ }
407
+ return;
408
+ }
409
+ if (!source.key || !source.fetch || !source.function || !source.prefetchDefaults) {
410
+ fail(`prefetch source '${source.name}' requires key, fetch, function and prefetchDefaults`);
411
+ } else if (source.key !== pkColumn.name && !source.columns.includes(source.key)) {
412
+ fail(`prefetch source '${source.name}' key '${source.key}' must be the primary key or one of its columns`);
413
+ }
414
+ for (const column of source.writeback?.columns ?? []) {
415
+ if (!source.columns.includes(column)) fail(`prefetch source '${source.name}' writes back '${column}', which it does not supply`);
416
+ }
417
+ }
418
+ function validateSourceColumns({ scope, source, byName }) {
419
+ const { fail, pkColumn, dataColumns } = scope;
420
+ for (const column of [pkColumn.name, ...source.columns]) {
421
+ const own = column === pkColumn.name ? pkColumn : dataColumns.get(column);
422
+ const theirs = byName.get(column);
423
+ if (!own) fail(`source '${source.name}' supplies unknown column '${column}'`);
424
+ else if (!theirs) fail(`source '${source.name}' table '${source.table}' has no column '${column}'`);
425
+ else if (JSON.stringify(theirs.type) !== JSON.stringify(own.type)) fail(`column '${column}' has a different type on source '${source.name}'`);
426
+ }
427
+ }
428
+ function validateRules({ resolved, pkColumn, dataColumns, sourceByName, fail }) {
429
+ const ruled = /* @__PURE__ */ new Set();
430
+ for (const rule of resolved.resolve) {
431
+ if (!dataColumns.has(rule.column) || rule.column === pkColumn.name) {
432
+ fail(`resolve rule targets unknown column '${rule.column}'`);
433
+ continue;
434
+ }
435
+ if (ruled.has(rule.column)) fail(`column '${rule.column}' has more than one resolve rule`);
436
+ ruled.add(rule.column);
437
+ if (rule.comparator === "custom" !== (rule.orderBy !== void 0)) {
438
+ fail(`column '${rule.column}': orderBy is required for custom comparators and only allowed there`);
439
+ }
440
+ if (new Set(rule.candidates).size !== rule.candidates.length) fail(`column '${rule.column}' repeats a candidate`);
441
+ for (const candidate of rule.candidates) {
442
+ const source = sourceByName.get(candidate);
443
+ if (!source) fail(`column '${rule.column}' names unknown candidate source '${candidate}'`);
444
+ else if (!source.columns.includes(rule.column)) fail(`column '${rule.column}' candidate '${candidate}' does not supply it`);
445
+ }
446
+ }
447
+ for (const column of dataColumns.keys()) {
448
+ if (column !== pkColumn.name && !ruled.has(column)) fail(`column '${column}' has no resolve rule`);
449
+ }
450
+ }
451
+ var VIEW_CELL_TYPES = {
452
+ source: "text",
453
+ at: "timestamptz",
454
+ by: "text",
455
+ conflicts: "jsonb"
456
+ };
457
+ function validateViewCells({ view, dataColumns, pkColumn, fail }) {
458
+ const expected = /* @__PURE__ */ new Map();
459
+ for (const column of dataColumns.values()) {
460
+ if (column.name === pkColumn.name) continue;
461
+ for (const part of RESOLVED_VIEW_CELL_PARTS) {
462
+ expected.set(cellColumnName(column.name, part), { part, columnName: cellColumnName(column.columnName, part) });
463
+ }
464
+ }
465
+ for (const column of view.columns) {
466
+ if (!column.cell) continue;
467
+ const want = expected.get(column.name);
468
+ if (!want || want.part !== column.cell.part || want.columnName !== column.columnName || cellColumnName(column.cell.of, column.cell.part) !== column.name) {
469
+ fail(`unexpected cell column '${column.name}'`);
470
+ continue;
471
+ }
472
+ if (column.type !== VIEW_CELL_TYPES[column.cell.part]) fail(`cell column '${column.name}' must be ${VIEW_CELL_TYPES[column.cell.part]}`);
473
+ expected.delete(column.name);
474
+ }
475
+ for (const name of expected.keys()) fail(`missing cell column '${name}'`);
476
+ }
477
+ var columnSignature = (columns) => JSON.stringify(columns.map((column) => [column.name, column.columnName, column.type]));
478
+ function validateMaterialized({ scope, tableByName, views }) {
479
+ const { view, resolved, fail } = scope;
480
+ const { table: tableName, compute: computeName } = resolved.materialized;
481
+ const table = tableByName.get(tableName);
482
+ if (!table || table.internal !== true || table.materializes !== view.view) {
483
+ fail(`materialized table '${tableName}' must be an internal table that materializes '${view.view}'`);
484
+ } else {
485
+ if (columnSignature(table.columns) !== columnSignature(view.columns)) fail(`materialized table '${tableName}' must have exactly the view's columns`);
486
+ if (JSON.stringify(table.primaryKey ?? []) !== JSON.stringify(view.primaryKey ?? [])) fail(`materialized table '${tableName}' must share the view's primary key`);
487
+ }
488
+ const compute = views.find((candidate) => candidate.view === computeName);
489
+ if (!compute || compute.internal !== true) fail(`compute view '${computeName}' must be an internal view`);
490
+ else if (columnSignature(compute.columns) !== columnSignature(view.columns)) fail(`compute view '${computeName}' must have exactly the view's columns`);
491
+ for (const source of resolved.sources) {
492
+ if (source.kind !== "etl") continue;
493
+ const read = tableByName.get(source.table)?.policies?.read;
494
+ const unconditional = typeof read === "string" ? read.trim() === "true" : read !== void 0 && read.default.trim() === "true" && read.variants.length === 0;
495
+ if (!unconditional) {
496
+ fail(`etl source '${source.name}' must have read policy 'true' to be materialized (the copy is shared by every caller)`);
497
+ }
498
+ }
499
+ }
9
500
 
10
501
  // src/canonical/v2.ts
11
502
  var CANONICAL_FORMAT = 2;
12
503
  var IDENTIFIER_RE = /^[a-z][a-z0-9_]*$/;
13
- var identifier = zod.z.string().min(1).max(63).regex(IDENTIFIER_RE);
504
+ var identifier2 = zod.z.string().min(1).max(63).regex(IDENTIFIER_RE);
14
505
  var columnIdentifier = zod.z.string().min(1).max(63).regex(COLUMN_IDENTIFIER_PATTERN);
15
506
  var COLUMN_TYPES = [
16
507
  "uuid",
@@ -26,14 +517,14 @@ var COLUMN_TYPES = [
26
517
  ];
27
518
  var columnTypeSchema = zod.z.enum(COLUMN_TYPES);
28
519
  var canonicalEnumSchema = zod.z.object({
29
- name: identifier,
520
+ name: identifier2,
30
521
  values: zod.z.array(zod.z.string().min(1).max(63)).min(1).max(100)
31
522
  });
32
523
  var canonicalColumnSchema = zod.z.object({
33
524
  /** Property name in TS (camelCase); columnName is the Postgres name. */
34
525
  name: zod.z.string().min(1).max(63),
35
526
  columnName: columnIdentifier,
36
- type: zod.z.union([columnTypeSchema, zod.z.object({ enum: identifier })]),
527
+ type: zod.z.union([columnTypeSchema, zod.z.object({ enum: identifier2 })]),
37
528
  nullable: zod.z.boolean().default(true),
38
529
  unique: zod.z.boolean().default(false),
39
530
  /** Literal default, or one of the allowlisted SQL functions. */
@@ -49,7 +540,14 @@ var canonicalColumnSchema = zod.z.object({
49
540
  /** Migration hints — same semantics as v1. */
50
541
  renamedFrom: columnIdentifier.optional(),
51
542
  cast: zod.z.string().max(200).optional(),
52
- deprecated: zod.z.boolean().default(false)
543
+ deprecated: zod.z.boolean().default(false),
544
+ /**
545
+ * Marks a system cell-metadata column (see CELL_PARTS in constants): part
546
+ * `part` of the cell of logical column `of`. Synthesized by authoring,
547
+ * never client-writable, absent from GraphQL scalar fields. Omitted on
548
+ * ordinary columns so their hashes are unchanged.
549
+ */
550
+ cell: zod.z.object({ of: zod.z.string().min(1).max(63), part: zod.z.enum(CELL_PARTS) }).strict().optional()
53
551
  }).superRefine((column, ctx) => {
54
552
  if (column.onUpdateFn === void 0) return;
55
553
  if (typeof column.type !== "string" || column.type !== "timestamptz" && column.type !== "date") {
@@ -64,36 +562,28 @@ var canonicalLinkSchema = zod.z.object({
64
562
  name: zod.z.string().min(1).max(63),
65
563
  kind: zod.z.enum(["toOne", "toMany", "toPlatformUser"]),
66
564
  /** Target table name (namespace-local). Absent for toPlatformUser. */
67
- target: identifier.optional(),
565
+ target: identifier2.optional(),
566
+ /** Owning namespace of an imported target; omitted for local links. */
567
+ namespace: identifier2.max(41).optional(),
68
568
  /** FK column on the owning side (toOne/toPlatformUser: on this table; toMany: on the target). */
69
569
  on: columnIdentifier,
70
570
  /** Referenced column on the target (default: target's PK / platform user `sub`). */
71
571
  references: columnIdentifier.optional(),
72
572
  /** enforce: true emits a real FK constraint (same-namespace toOne only). */
73
573
  enforce: zod.z.boolean().default(false),
574
+ onDelete: zod.z.enum(["cascade", "restrict", "no action", "set null", "set default"]).optional(),
575
+ onUpdate: zod.z.enum(["cascade", "restrict", "no action", "set null", "set default"]).optional(),
74
576
  description: zod.z.string().max(500).optional()
75
577
  });
76
578
  var canonicalIndexSchema = zod.z.object({
77
579
  columns: zod.z.array(columnIdentifier).min(1),
78
580
  unique: zod.z.boolean().default(false),
79
- name: identifier.optional()
80
- });
81
- var policyVariantSchema = zod.z.object({
82
- /** Claims-only guard (capability()/role()/claim() comparisons — no columns). */
83
- when: zod.z.string().min(1).max(1e3),
84
- /** Exactly one of: allow-all bypass, or a row predicate. */
85
- allow: zod.z.literal(true).optional(),
86
- policy: zod.z.string().min(1).max(2e3).optional()
87
- }).refine((v) => v.allow === true !== (typeof v.policy === "string"), {
88
- message: "A variant declares exactly one of allow: true or policy"
581
+ name: identifier2.optional()
89
582
  });
90
- var policyExpressionSchema = zod.z.union([
91
- zod.z.string().min(1).max(2e3),
92
- zod.z.object({
93
- default: zod.z.string().min(1).max(2e3),
94
- variants: zod.z.array(policyVariantSchema).max(10).default([])
95
- })
96
- ]);
583
+ var canonicalCheckSchema = zod.z.object({
584
+ name: identifier2,
585
+ expression: zod.z.string().min(1).max(2e3)
586
+ }).strict();
97
587
  var canonicalPoliciesSchema = zod.z.object({
98
588
  read: policyExpressionSchema.optional(),
99
589
  write: policyExpressionSchema.optional(),
@@ -281,7 +771,7 @@ var VIEW_QUERY_FUNCTION_ALLOWLIST = [
281
771
  "width_bucket"
282
772
  ];
283
773
  var canonicalActionSchema = zod.z.object({
284
- name: identifier,
774
+ name: identifier2,
285
775
  requires: zod.z.string().max(1e3).optional(),
286
776
  params: zod.z.record(zod.z.string(), zod.z.object({
287
777
  type: columnTypeSchema,
@@ -293,9 +783,9 @@ var canonicalActionSchema = zod.z.object({
293
783
  });
294
784
  var canonicalCowMutationSchema = zod.z.object({
295
785
  kind: zod.z.enum(["override", "revert"]),
296
- name: identifier,
786
+ name: identifier2,
297
787
  /** Synced table (same namespace) the overlay copies from / falls back to. */
298
- synced: identifier,
788
+ synced: identifier2,
299
789
  /** Single-column primary key PROPERTY (TS camelCase, like column `name`) shared by overlay and synced. */
300
790
  pk: zod.z.string().min(1).max(63),
301
791
  /** Override: contract properties copied INSERT…SELECT from synced (pk included). Empty for revert. */
@@ -323,23 +813,28 @@ var canonicalCowMutationSchema = zod.z.object({
323
813
  }
324
814
  });
325
815
  var canonicalForeignKeySchema = zod.z.object({
326
- name: identifier,
816
+ name: identifier2,
327
817
  columns: zod.z.array(columnIdentifier).min(1).max(8),
328
- target: identifier,
818
+ target: identifier2,
329
819
  references: zod.z.array(columnIdentifier).min(1).max(8),
330
- deferrable: zod.z.enum(["initially-immediate", "initially-deferred"]).optional()
820
+ deferrable: zod.z.enum(["initially-immediate", "initially-deferred"]).optional(),
821
+ onDelete: zod.z.enum(["cascade", "restrict", "no action", "set null", "set default"]).optional(),
822
+ onUpdate: zod.z.enum(["cascade", "restrict", "no action", "set null", "set default"]).optional()
331
823
  }).strict();
332
824
  var canonicalTableSchema = zod.z.object({
333
- table: identifier,
825
+ table: identifier2,
826
+ displayName: zod.z.string().trim().min(1).max(120).optional(),
334
827
  description: zod.z.string().max(2e3).optional(),
335
828
  /** Table-level rename hint (new in v2). */
336
- renamedFrom: identifier.optional(),
337
- /** Synced/reflected read-only tables (`source: gold.*`) — v1 semantics. */
338
- source: zod.z.string().regex(/^gold\.[a-z][a-z0-9_]*$/).optional(),
829
+ renamedFrom: identifier2.optional(),
830
+ /** Synced/reflected read-only tables (`source: schema.table`) — v1 semantics. */
831
+ source: zod.z.string().regex(QUALIFIED_TABLE_PATTERN).optional(),
339
832
  columns: zod.z.array(canonicalColumnSchema).default([]),
340
833
  primaryKey: zod.z.array(columnIdentifier).min(1).max(8).optional(),
341
834
  links: zod.z.array(canonicalLinkSchema).default([]),
342
835
  indexes: zod.z.array(canonicalIndexSchema).default([]),
836
+ /** Omitted by old definitions, preserving their existing content hashes. */
837
+ checks: zod.z.array(canonicalCheckSchema).max(100).optional(),
343
838
  policies: canonicalPoliciesSchema.default({}),
344
839
  /**
345
840
  * v1-conversion carry-over only. v2 has no action surface — the runtime
@@ -350,22 +845,55 @@ var canonicalTableSchema = zod.z.object({
350
845
  /** Optional (not defaulted) so pre-CoW canonical documents keep byte-identical hashes. */
351
846
  cowMutations: zod.z.array(canonicalCowMutationSchema).optional(),
352
847
  /** Omitted by old definitions, preserving their existing content hashes. */
353
- foreignKeys: zod.z.array(canonicalForeignKeySchema).max(100).optional()
848
+ foreignKeys: zod.z.array(canonicalForeignKeySchema).max(100).optional(),
849
+ /**
850
+ * Per-cell edit metadata (`cellMetadata: true` in authoring): every tracked
851
+ * column `c` carries typed `c__at` / `c__by` system columns (plus `c__rev`
852
+ * on a resolved table's internal sources), listed explicitly in `columns`
853
+ * with a `cell` marker so DDL and migrations treat them as ordinary columns.
854
+ * Omitted when off so existing definitions hash unchanged.
855
+ */
856
+ cellMetadata: zod.z.literal(true).optional(),
857
+ /**
858
+ * System-owned backing table of a resolved table (its manual or prefetch
859
+ * source). Internal tables get DDL and IR metadata but no GraphQL surface;
860
+ * only the data plane writes them. Omitted when false.
861
+ */
862
+ internal: zod.z.literal(true).optional(),
863
+ /**
864
+ * The materialized copy of resolved table `materializes` (internal): the
865
+ * same columns as that view, including its cell parts, maintained by the
866
+ * data plane. Omitted otherwise.
867
+ */
868
+ materializes: identifier2.optional()
354
869
  });
870
+ var MAX_VIEW_QUERY = 1e5;
871
+ var MAX_RESOLVED_VIEW_QUERY = 5e6;
355
872
  var canonicalViewSchema = zod.z.object({
356
- view: identifier,
873
+ view: identifier2,
357
874
  description: zod.z.string().max(2e3).optional(),
358
875
  columns: zod.z.array(canonicalColumnSchema).min(1),
359
876
  /** Logical identity for GraphQL only; PostgreSQL views have no PK constraint. */
360
877
  primaryKey: zod.z.array(columnIdentifier).min(1).max(8).optional(),
361
878
  links: zod.z.array(canonicalLinkSchema).default([]),
362
879
  policies: zod.z.object({ read: policyExpressionSchema }).strict(),
363
- query: zod.z.string().min(1).max(1e5).refine(isSingleViewQuerySql, {
880
+ query: zod.z.string().min(1).max(MAX_RESOLVED_VIEW_QUERY).refine(isSingleViewQuerySql, {
364
881
  message: "view query must be one SELECT/WITH statement without comments or dollar-quoted bodies"
365
882
  }),
366
883
  /** Namespace-local logical relation names, sorted for deterministic hashing. */
367
- dependencies: zod.z.array(identifier).max(500)
884
+ dependencies: zod.z.array(identifier2).max(500),
885
+ /**
886
+ * Present when this view is a resolved table (defineResolvedTable): the
887
+ * multi-source metadata behind its generated query. Omitted otherwise so
888
+ * existing views hash unchanged.
889
+ */
890
+ resolved: canonicalResolvedSchema.optional(),
891
+ /** System-owned view (a materialized resolved table's compute view): no GraphQL surface. */
892
+ internal: zod.z.literal(true).optional()
368
893
  }).superRefine((view, ctx) => {
894
+ if (view.resolved === void 0 && view.query.length > MAX_VIEW_QUERY) {
895
+ ctx.addIssue({ code: "custom", path: ["query"], message: `view query must be at most ${MAX_VIEW_QUERY} characters` });
896
+ }
369
897
  for (const [index, column] of view.columns.entries()) {
370
898
  if (column.default !== void 0 || column.defaultFn !== void 0) {
371
899
  ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare a default` });
@@ -380,20 +908,91 @@ var canonicalViewSchema = zod.z.object({
380
908
  ctx.addIssue({ code: "custom", path: ["columns", index], message: `view column '${column.name}' cannot declare migration hints` });
381
909
  }
382
910
  }
911
+ if (view.resolved === void 0 && view.internal !== true) {
912
+ for (const [index, column] of view.columns.entries()) {
913
+ if (column.cell) ctx.addIssue({ code: "custom", path: ["columns", index], message: `only resolved tables carry cell columns ('${column.name}')` });
914
+ }
915
+ }
383
916
  for (const [index, link] of view.links.entries()) {
384
917
  if (link.enforce) {
385
918
  ctx.addIssue({ code: "custom", path: ["links", index, "enforce"], message: `view link '${link.name}' cannot enforce a foreign key` });
386
919
  }
920
+ if (link.onDelete !== void 0 || link.onUpdate !== void 0) {
921
+ ctx.addIssue({ code: "custom", path: ["links", index], message: `view link '${link.name}' cannot declare referential actions` });
922
+ }
387
923
  }
388
924
  });
389
- var canonicalDefinitionSchema = zod.z.object({
925
+ var CELL_PART_TYPES = {
926
+ at: "timestamptz",
927
+ by: "text",
928
+ rev: "uuid",
929
+ source: "text",
930
+ conflicts: "jsonb"
931
+ };
932
+ function trackedCellColumns(table) {
933
+ const pk = new Set(table.primaryKey ?? []);
934
+ return table.columns.filter((column) => column.cell === void 0 && !pk.has(column.columnName) && column.onUpdateFn === void 0);
935
+ }
936
+ var SUFFIXES = Object.values(CELL_PART_SUFFIX);
937
+ function validateCellMetadata(table, index, ctx) {
938
+ const fail = (message, path = ["tables", index]) => ctx.addIssue({ code: "custom", path, message });
939
+ const cells = table.columns.flatMap((column, position) => column.cell ? [{ column, position, cell: column.cell }] : []);
940
+ if (table.materializes !== void 0) return;
941
+ if (table.cellMetadata !== true) {
942
+ for (const { position } of cells) fail(`cell-metadata columns require cellMetadata on table '${table.table}'`, ["tables", index, "columns", position]);
943
+ return;
944
+ }
945
+ if (table.source !== void 0) {
946
+ fail(`synced table '${table.table}' cannot track cell metadata`);
947
+ return;
948
+ }
949
+ const tracked = trackedCellColumns(table);
950
+ const byName = new Map(tracked.map((column) => [column.name, column]));
951
+ const withRev = cells.some(({ cell }) => cell.part === "rev");
952
+ if (withRev && table.internal !== true) fail(`only a resolved table's internal sources carry '__rev' cell columns ('${table.table}')`);
953
+ const seen = /* @__PURE__ */ new Set();
954
+ for (const { column, position, cell } of cells) {
955
+ const path = ["tables", index, "columns", position];
956
+ const owner = byName.get(cell.of);
957
+ if (!owner) {
958
+ fail(`cell column '${column.name}' belongs to '${cell.of}', which is not a tracked column`, path);
959
+ continue;
960
+ }
961
+ 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);
962
+ if (column.name !== cellColumnName(owner.name, cell.part) || column.columnName !== cellColumnName(owner.columnName, cell.part)) {
963
+ fail(`cell column for '${owner.name}' ${cell.part} must be named '${cellColumnName(owner.name, cell.part)}' / '${cellColumnName(owner.columnName, cell.part)}'`, path);
964
+ }
965
+ if (column.type !== CELL_PART_TYPES[cell.part] || !column.nullable || column.unique || column.default !== void 0 || column.defaultFn !== void 0) {
966
+ fail(`cell column '${column.name}' must be a nullable, default-less ${CELL_PART_TYPES[cell.part]}`, path);
967
+ }
968
+ const key = `${cell.of}:${cell.part}`;
969
+ if (seen.has(key)) fail(`duplicate cell column '${column.name}'`, path);
970
+ seen.add(key);
971
+ }
972
+ for (const column of tracked) {
973
+ if (column.columnName.length > MAX_TRACKED_COLUMN_NAME) {
974
+ fail(`column '${column.columnName}' is too long to track cell metadata (max ${MAX_TRACKED_COLUMN_NAME} characters)`);
975
+ }
976
+ if (SUFFIXES.some((suffix) => column.columnName.endsWith(suffix))) {
977
+ fail(`column '${column.columnName}' ends in a reserved cell-metadata suffix (${SUFFIXES.join(", ")})`);
978
+ }
979
+ for (const part of withRev ? ["at", "by", "rev"] : ["at", "by"]) {
980
+ if (!seen.has(`${column.name}:${part}`)) fail(`tracked column '${column.name}' is missing its '${cellColumnName(column.columnName, part)}' cell column`);
981
+ }
982
+ }
983
+ }
984
+ var exportGateSchema = zod.z.union([zod.z.literal(true), zod.z.object({ capability: zod.z.string().min(1) }).strict(), zod.z.object({ role: zod.z.string().min(1) }).strict()]);
985
+ var relationExportSchema = zod.z.object({ read: exportGateSchema, write: exportGateSchema.optional() }).strict();
986
+ var localDefinitionSchema = zod.z.object({
390
987
  definition_format: zod.z.literal(CANONICAL_FORMAT),
391
- namespace: identifier.max(41),
988
+ namespace: identifier2.max(41),
392
989
  description: zod.z.string().max(2e3).optional(),
393
990
  enums: zod.z.array(canonicalEnumSchema).default([]),
394
991
  tables: zod.z.array(canonicalTableSchema).min(0).max(500),
395
992
  /** Optional to preserve byte-identical hashes for pre-view canonical v2 documents. */
396
993
  views: zod.z.array(canonicalViewSchema).max(500).optional(),
994
+ /** Target-owned cross-namespace access contract, using existing namespace grants. */
995
+ exports: zod.z.record(identifier2, relationExportSchema).optional(),
397
996
  /** Verbatim custom-roles grammar (@sequenceholdings/custom-roles-manifest). */
398
997
  capabilities: zod.z.object({
399
998
  roles: zod.z.unknown().optional(),
@@ -406,6 +1005,7 @@ var canonicalDefinitionSchema = zod.z.object({
406
1005
  ctx.addIssue({ code: "custom", path: ["tables", index, "table"], message: `duplicate relation '${table.table}'` });
407
1006
  }
408
1007
  seen.add(table.table);
1008
+ validateCellMetadata(table, index, ctx);
409
1009
  if (table.source !== void 0) {
410
1010
  for (const [columnIndex, column] of table.columns.entries()) {
411
1011
  if (column.onUpdateFn !== void 0) {
@@ -419,12 +1019,41 @@ var canonicalDefinitionSchema = zod.z.object({
419
1019
  }
420
1020
  }
421
1021
  const fkTables = new Map(definition.tables.map((table) => [table.table, table]));
1022
+ const relations = /* @__PURE__ */ new Set([...fkTables.keys(), ...(definition.views ?? []).map((view) => view.view)]);
1023
+ for (const name of Object.keys(definition.exports ?? {})) {
1024
+ if (!relations.has(name)) ctx.addIssue({ code: "custom", path: ["exports", name], message: `export '${name}' is not a local object` });
1025
+ }
422
1026
  for (const [tableIndex, table] of definition.tables.entries()) {
1027
+ for (const [linkIndex, link] of table.links.entries()) {
1028
+ if (link.enforce && link.namespace !== void 0 && link.namespace !== definition.namespace) {
1029
+ ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "links", linkIndex], message: "physical foreign keys require a local target; use an ORM link without enforce" });
1030
+ }
1031
+ }
423
1032
  const names = /* @__PURE__ */ new Set([
424
1033
  ...table.primaryKey?.length ? [`${table.table}_pkey`] : [],
425
1034
  ...table.columns.filter((column) => column.unique && !(table.primaryKey?.length === 1 && table.primaryKey[0] === column.columnName)).map((column) => `${table.table}_${column.columnName}_unique`),
426
1035
  ...table.links.filter((link) => link.enforce).map((link) => `fk_${table.table}_${link.name}`)
427
1036
  ]);
1037
+ for (const [index, link] of table.links.entries()) {
1038
+ if ((link.onDelete !== void 0 || link.onUpdate !== void 0) && (!link.enforce || link.kind !== "toOne")) {
1039
+ ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "links", index], message: "referential actions require an enforced toOne link" });
1040
+ }
1041
+ if (link.onDelete === "set null" || link.onUpdate === "set null") {
1042
+ const source = table.columns.find((column) => column.columnName === link.on);
1043
+ 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" });
1044
+ }
1045
+ }
1046
+ for (const [index, check] of (table.checks ?? []).entries()) {
1047
+ const fail = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "checks", index], message });
1048
+ if (table.source) fail("synced tables cannot declare checks");
1049
+ if (names.has(check.name)) fail(`duplicate constraint '${check.name}'`);
1050
+ names.add(check.name);
1051
+ try {
1052
+ compileCheckExpression(check.expression, table.columns);
1053
+ } catch (error) {
1054
+ fail(error instanceof Error ? error.message : String(error));
1055
+ }
1056
+ }
428
1057
  for (const [index, fk] of (table.foreignKeys ?? []).entries()) {
429
1058
  const fail = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "foreignKeys", index], message });
430
1059
  if (names.has(fk.name)) fail(`duplicate foreign key '${fk.name}'`);
@@ -442,10 +1071,18 @@ var canonicalDefinitionSchema = zod.z.object({
442
1071
  if (!sourceColumn || !targetColumn) fail("foreign key references an unknown column");
443
1072
  else if (JSON.stringify(sourceColumn.type) !== JSON.stringify(targetColumn.type)) fail("foreign key column types must match");
444
1073
  }
1074
+ if (fk.onDelete === "set null" || fk.onUpdate === "set null") {
1075
+ 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");
1076
+ }
445
1077
  const uniqueKeys = [target.primaryKey ?? [], ...target.indexes.filter((index2) => index2.unique).map((index2) => index2.columns), ...target.columns.filter((column) => column.unique).map((column) => [column.columnName])];
446
1078
  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");
447
1079
  }
448
1080
  }
1081
+ for (const [viewIndex, view] of (definition.views ?? []).entries()) {
1082
+ for (const message of validateResolvedView({ view, tables: definition.tables, views: definition.views ?? [] })) {
1083
+ ctx.addIssue({ code: "custom", path: ["views", viewIndex, "resolved"], message });
1084
+ }
1085
+ }
449
1086
  const tableByName = new Map(definition.tables.map((table) => [table.table, table]));
450
1087
  for (const [tableIndex, table] of definition.tables.entries()) {
451
1088
  const cowMutations = table.cowMutations ?? [];
@@ -561,6 +1198,10 @@ var canonicalDefinitionSchema = zod.z.object({
561
1198
  };
562
1199
  for (const view of views) visit(view.view, []);
563
1200
  });
1201
+ var canonicalDefinitionSchema = localDefinitionSchema.safeExtend({
1202
+ /** Flattened, pinned snapshots. Imported relations never participate in this namespace's DDL. */
1203
+ imports: zod.z.array(localDefinitionSchema).max(32).optional()
1204
+ });
564
1205
  function canonicalRelations(definition) {
565
1206
  const views = (definition.views ?? []).map((view) => ({
566
1207
  table: view.view,
@@ -569,8 +1210,12 @@ function canonicalRelations(definition) {
569
1210
  ...view.primaryKey !== void 0 ? { primaryKey: view.primaryKey } : {},
570
1211
  links: view.links,
571
1212
  indexes: [],
572
- policies: { read: view.policies.read },
573
- actions: []
1213
+ // A resolved table's write policies (evaluated against the resolved row)
1214
+ // ride alongside the view's read policy.
1215
+ policies: { read: view.policies.read, ...view.resolved?.policies ?? {} },
1216
+ actions: [],
1217
+ ...view.resolved !== void 0 ? { resolved: view.resolved } : {},
1218
+ ...view.internal ? { internal: true } : {}
574
1219
  }));
575
1220
  return [...definition.tables, ...views].sort((a, b) => a.table.localeCompare(b.table));
576
1221
  }
@@ -580,13 +1225,14 @@ function normalizeCanonical(definition) {
580
1225
  ...definition,
581
1226
  enums: sortByName(definition.enums),
582
1227
  tables: [...definition.tables].sort((a, b) => a.table.localeCompare(b.table)).map((t) => {
583
- const { cowMutations, foreignKeys, ...rest } = t;
1228
+ const { cowMutations, foreignKeys, checks, ...rest } = t;
584
1229
  return {
585
1230
  ...rest,
586
1231
  links: sortByName(t.links),
587
1232
  indexes: [...t.indexes].sort((a, b) => (a.name ?? a.columns.join("_")).localeCompare(b.name ?? b.columns.join("_"))),
588
1233
  actions: sortByName(t.actions),
589
1234
  ...foreignKeys?.length ? { foreignKeys: sortByName(foreignKeys) } : {},
1235
+ ...checks?.length ? { checks: sortByName(checks) } : {},
590
1236
  // Omitted entirely when absent/empty so pre-CoW documents hash unchanged.
591
1237
  ...cowMutations !== void 0 && cowMutations.length > 0 ? { cowMutations: sortByName(cowMutations) } : {}
592
1238
  };
@@ -599,28 +1245,54 @@ function normalizeCanonical(definition) {
599
1245
  dependencies: [...view.dependencies].sort()
600
1246
  }));
601
1247
  }
1248
+ if (definition.imports?.length) {
1249
+ normalized.imports = definition.imports.map(normalizeCanonical).sort((a, b) => a.namespace.localeCompare(b.namespace));
1250
+ } else {
1251
+ delete normalized.imports;
1252
+ }
602
1253
  return normalized;
603
1254
  }
604
1255
 
605
1256
  exports.CANONICAL_FORMAT = CANONICAL_FORMAT;
1257
+ exports.CELL_PART_TYPES = CELL_PART_TYPES;
606
1258
  exports.COLUMN_TYPES = COLUMN_TYPES;
1259
+ exports.COMPARATORS = COMPARATORS;
607
1260
  exports.IDENTIFIER_RE = IDENTIFIER_RE;
1261
+ exports.RESERVED_RESOLVED_COLUMN_NAMES = RESERVED_RESOLVED_COLUMN_NAMES;
1262
+ exports.RESOLVED_SOURCE_CELL_PARTS = RESOLVED_SOURCE_CELL_PARTS;
1263
+ exports.RESOLVED_SOURCE_SYSTEM_COLUMNS = RESOLVED_SOURCE_SYSTEM_COLUMNS;
1264
+ exports.RESOLVED_VIEW_CELL_PARTS = RESOLVED_VIEW_CELL_PARTS;
1265
+ exports.RESOLVED_VIEW_SYSTEM_COLUMNS = RESOLVED_VIEW_SYSTEM_COLUMNS;
608
1266
  exports.VIEW_QUERY_FUNCTION_ALLOWLIST = VIEW_QUERY_FUNCTION_ALLOWLIST;
609
1267
  exports.canonicalActionSchema = canonicalActionSchema;
1268
+ exports.canonicalCheckSchema = canonicalCheckSchema;
610
1269
  exports.canonicalColumnSchema = canonicalColumnSchema;
611
1270
  exports.canonicalCowMutationSchema = canonicalCowMutationSchema;
612
1271
  exports.canonicalDefinitionSchema = canonicalDefinitionSchema;
613
1272
  exports.canonicalEnumSchema = canonicalEnumSchema;
1273
+ exports.canonicalFetchShapeSchema = canonicalFetchShapeSchema;
614
1274
  exports.canonicalForeignKeySchema = canonicalForeignKeySchema;
1275
+ exports.canonicalFunctionRefSchema = canonicalFunctionRefSchema;
615
1276
  exports.canonicalIndexSchema = canonicalIndexSchema;
616
1277
  exports.canonicalLinkSchema = canonicalLinkSchema;
617
1278
  exports.canonicalPoliciesSchema = canonicalPoliciesSchema;
618
1279
  exports.canonicalRelations = canonicalRelations;
1280
+ exports.canonicalResolveRuleSchema = canonicalResolveRuleSchema;
1281
+ exports.canonicalResolvedSchema = canonicalResolvedSchema;
1282
+ exports.canonicalResolvedSourceSchema = canonicalResolvedSourceSchema;
619
1283
  exports.canonicalTableSchema = canonicalTableSchema;
620
1284
  exports.canonicalViewSchema = canonicalViewSchema;
1285
+ exports.exportGateSchema = exportGateSchema;
621
1286
  exports.isSingleViewQuerySql = isSingleViewQuerySql;
1287
+ exports.localDefinitionSchema = localDefinitionSchema;
622
1288
  exports.normalizeCanonical = normalizeCanonical;
1289
+ exports.ontologyContractSchema = ontologyContractSchema;
1290
+ exports.parseDurationMs = parseDurationMs;
623
1291
  exports.policyExpressionSchema = policyExpressionSchema;
624
1292
  exports.policyVariantSchema = policyVariantSchema;
1293
+ exports.readOntologyContract = readOntologyContract;
1294
+ exports.relationExportSchema = relationExportSchema;
1295
+ exports.trackedCellColumns = trackedCellColumns;
1296
+ exports.validateResolvedView = validateResolvedView;
625
1297
  //# sourceMappingURL=canonical.cjs.map
626
1298
  //# sourceMappingURL=canonical.cjs.map