@sequenceholdings/orm 2.0.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 +94 -2
  2. package/dist/authoring.cjs +1474 -54
  3. package/dist/authoring.cjs.map +1 -1
  4. package/dist/authoring.d.cts +242 -4
  5. package/dist/authoring.d.ts +242 -4
  6. package/dist/authoring.js +1465 -56
  7. package/dist/authoring.js.map +1 -1
  8. package/dist/canonical.cjs +740 -42
  9. package/dist/canonical.cjs.map +1 -1
  10. package/dist/canonical.d.cts +742 -53
  11. package/dist/canonical.d.ts +742 -53
  12. package/dist/canonical.js +724 -43
  13. package/dist/canonical.js.map +1 -1
  14. package/dist/cli.cjs +2478 -328
  15. package/dist/cli.cjs.map +1 -1
  16. package/dist/cli.js +2480 -330
  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 +1755 -329
  25. package/dist/compiler.cjs.map +1 -1
  26. package/dist/compiler.d.cts +56 -3
  27. package/dist/compiler.d.ts +56 -3
  28. package/dist/compiler.js +1756 -330
  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 +1973 -164
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +474 -100
  37. package/dist/index.d.ts +474 -100
  38. package/dist/index.js +1940 -167
  39. package/dist/index.js.map +1 -1
  40. package/dist/ir.cjs +869 -46
  41. package/dist/ir.cjs.map +1 -1
  42. package/dist/ir.d.cts +699 -25
  43. package/dist/ir.d.ts +699 -25
  44. package/dist/ir.js +861 -47
  45. package/dist/ir.js.map +1 -1
  46. package/dist/{naming-CTvWSRTf.d.ts → naming-CPfx7EfF.d.ts} +28 -1
  47. package/dist/{naming-DRyinU4u.d.cts → naming-CU9ThP_E.d.cts} +28 -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 +852 -49
  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 +852 -49
  55. package/dist/predicate-v2.js.map +1 -1
  56. package/dist/sdl.cjs +1309 -58
  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 +1305 -59
  61. package/dist/sdl.js.map +1 -1
  62. package/package.json +11 -6
  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/index.cjs CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  var drizzleOrm = require('drizzle-orm');
4
4
  var pgCore = require('drizzle-orm/pg-core');
5
- var crypto = require('crypto');
6
5
  var zod = require('zod');
6
+ var crypto = require('crypto');
7
7
  var jiti = require('jiti');
8
8
  var promises = require('fs/promises');
9
9
  var module$1 = require('module');
@@ -36,10 +36,12 @@ var ColumnBuilder = class {
36
36
  hasDefault: false,
37
37
  defaultValue: void 0,
38
38
  defaultFn: void 0,
39
+ onUpdateFn: void 0,
39
40
  description: void 0,
40
41
  renamedFrom: void 0,
41
42
  cast: void 0,
42
- deprecated: false
43
+ deprecated: false,
44
+ cell: void 0
43
45
  };
44
46
  }
45
47
  /** Marks this column as (part of) the primary key. Implies NOT NULL. */
@@ -71,6 +73,15 @@ var ColumnBuilder = class {
71
73
  this.state.defaultFn = void 0;
72
74
  return this;
73
75
  }
76
+ /**
77
+ * Stamp this column with Postgres `now()` on every UPDATE. Only valid on
78
+ * timestamptz/date; the compiler drops it from SetInput so callers cannot
79
+ * supply a value.
80
+ */
81
+ onUpdate(value) {
82
+ this.state.onUpdateFn = value;
83
+ return this;
84
+ }
74
85
  description(text4) {
75
86
  this.state.description = text4;
76
87
  return this;
@@ -90,6 +101,11 @@ var ColumnBuilder = class {
90
101
  this.state.deprecated = true;
91
102
  return this;
92
103
  }
104
+ /** @internal System cell-metadata part of logical column `of` (generated resolved views only). */
105
+ cellPart(of, part) {
106
+ this.state.cell = { of, part };
107
+ return this;
108
+ }
93
109
  /** Whether `.primaryKey()` was called — collected into the table-level PK. */
94
110
  get isPrimaryKey() {
95
111
  return this.state.primaryKey;
@@ -114,9 +130,11 @@ var ColumnBuilder = class {
114
130
  };
115
131
  if (this.state.hasDefault) column.default = this.state.defaultValue ?? null;
116
132
  if (this.state.defaultFn !== void 0) column.defaultFn = this.state.defaultFn;
133
+ if (this.state.onUpdateFn !== void 0) column.onUpdateFn = this.state.onUpdateFn;
117
134
  if (this.state.description !== void 0) column.description = this.state.description;
118
135
  if (this.state.renamedFrom !== void 0) column.renamedFrom = this.state.renamedFrom;
119
136
  if (this.state.cast !== void 0) column.cast = this.state.cast;
137
+ if (this.state.cell !== void 0) column.cell = { ...this.state.cell };
120
138
  return column;
121
139
  }
122
140
  };
@@ -140,11 +158,12 @@ var t = {
140
158
 
141
159
  // src/constants.ts
142
160
  var NAMESPACE_PATTERN = /^[a-z][a-z0-9_]{0,40}$/;
143
- var IDENTIFIER_PATTERN = /^[a-z][a-z0-9_]*$/;
144
- var MAX_IDENTIFIER_LENGTH = 63;
145
161
  var ORM_DATA_TOKEN_HEADER = "x-sequence-data-token";
146
162
  var ORM_SERVICE_DATA_TOKEN_HEADER = "x-sequence-data-token-service";
147
- var CAPABILITY_KEY_PATTERN = /^(?:org\/)?[a-z0-9-]{1,64}$/;
163
+ var IDENTIFIER_PATTERN = /^[a-z][a-z0-9_]*$/;
164
+ var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
165
+ var MAX_IDENTIFIER_LENGTH = 63;
166
+ var CAPABILITY_KEY_PATTERN = /^(?:org\/|app\/[a-z][a-z0-9-]{0,63}\/)?[a-z0-9-]{1,64}$/;
148
167
  var CLAIM_KEY_PATTERN = /^[a-z][a-z0-9_]*$/;
149
168
  var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]*\.[a-z][a-z0-9_]*$/;
150
169
  var SYNCED_SOURCE_PATTERN = /^gold\.[a-z][a-z0-9_]*$/;
@@ -156,8 +175,31 @@ var DEFINITION_FILE_EXTENSIONS = [".yml", ".yaml"];
156
175
  var NAMESPACE_CONFIG_FILENAMES = /* @__PURE__ */ new Set(["namespace.yaml", "namespace.yml"]);
157
176
  var HASH_DOMAIN = "sequence-orm-definitions\0";
158
177
  var MAX_DEFINITION_FILE_BYTES = 1024 * 1024;
178
+ var MAX_GENERATED_SDL_BYTES = 16 * 1024 * 1024;
179
+ var ORM_REGISTRATION_MAX_BYTES = 64 * 1024 * 1024;
159
180
  var RESERVED_TABLE_NAMES = /* @__PURE__ */ new Set(["actions", "query"]);
160
181
  var RESERVED_COLUMN_NAMES = /* @__PURE__ */ new Set(["select", "order", "limit", "offset"]);
182
+ var CELL_PARTS = ["at", "by", "rev", "source", "conflicts"];
183
+ var CELL_PART_SUFFIX = {
184
+ at: "__at",
185
+ by: "__by",
186
+ rev: "__rev",
187
+ source: "__src",
188
+ conflicts: "__lost"
189
+ };
190
+ var MAX_TRACKED_COLUMN_NAME = MAX_IDENTIFIER_LENGTH - 6;
191
+ var CELLS_FIELD = "cells";
192
+ function cellColumnName(column, part) {
193
+ return `${column}${CELL_PART_SUFFIX[part]}`;
194
+ }
195
+ var MATERIALIZE_ACTOR = "orm:materialize";
196
+ var MATERIALIZE_CLAIMS_JSON = JSON.stringify({ sub: MATERIALIZE_ACTOR, org: null, roles: [], capabilities: [] });
197
+ function materializedRefreshFunctionName(table) {
198
+ return `${table}__refresh`;
199
+ }
200
+ function materializeLockKey(schema, table) {
201
+ return `orm:materialize:${schema}.${table}`;
202
+ }
161
203
 
162
204
  // src/errors.ts
163
205
  var OrmDefinitionError = class extends Error {
@@ -233,6 +275,8 @@ function toOne(target, options) {
233
275
  on: options.on,
234
276
  references: options.references,
235
277
  enforce: options.enforce ?? false,
278
+ onDelete: options.onDelete,
279
+ onUpdate: options.onUpdate,
236
280
  description: options.description
237
281
  };
238
282
  }
@@ -244,6 +288,8 @@ function toMany(target, options) {
244
288
  on: options.on,
245
289
  references: options.references,
246
290
  enforce: false,
291
+ onDelete: void 0,
292
+ onUpdate: void 0,
247
293
  description: options.description
248
294
  };
249
295
  }
@@ -255,6 +301,8 @@ function toPlatformUser(options) {
255
301
  on: options.on,
256
302
  references: options.references,
257
303
  enforce: false,
304
+ onDelete: void 0,
305
+ onUpdate: void 0,
258
306
  description: options.description
259
307
  };
260
308
  }
@@ -264,10 +312,10 @@ function isSeqLink(value) {
264
312
  function isSeqTable(value) {
265
313
  return typeof value === "object" && value !== null && value.kind === TABLE_BRAND;
266
314
  }
267
- function assertIdentifier({ value, what, file }) {
268
- if (!IDENTIFIER_PATTERN.test(value) || value.length > MAX_IDENTIFIER_LENGTH) {
315
+ function assertIdentifier({ value, what, file, pattern = IDENTIFIER_PATTERN }) {
316
+ if (!pattern.test(value) || value.length > MAX_IDENTIFIER_LENGTH) {
269
317
  throw new OrmDefinitionError(
270
- `${what} "${value}" must match ${IDENTIFIER_PATTERN} and be at most ${MAX_IDENTIFIER_LENGTH} characters`,
318
+ `${what} "${value}" must match ${pattern} and be at most ${MAX_IDENTIFIER_LENGTH} characters`,
271
319
  file
272
320
  );
273
321
  }
@@ -288,7 +336,13 @@ function defineTable(name, columns, config = {}) {
288
336
  );
289
337
  }
290
338
  const columnName = builder.columnNameFor(propertyName);
291
- assertIdentifier({ value: columnName, what: `table "${name}": column "${propertyName}" column name`, file });
339
+ assertIdentifier({ value: columnName, what: `table "${name}": column "${propertyName}" column name`, file, pattern: COLUMN_IDENTIFIER_PATTERN });
340
+ if (config.materializes === void 0 && (propertyName === CELLS_FIELD || Object.values(CELL_PART_SUFFIX).some((suffix) => columnName.endsWith(suffix) || propertyName.endsWith(suffix)))) {
341
+ throw new OrmDefinitionError(
342
+ `table "${name}": "${propertyName}" collides with the reserved cell-metadata names ('${CELLS_FIELD}', or a ${Object.values(CELL_PART_SUFFIX).join("/")} suffix)`,
343
+ file
344
+ );
345
+ }
292
346
  const existing = seenColumnNames.get(columnName);
293
347
  if (existing !== void 0) {
294
348
  throw new OrmDefinitionError(
@@ -328,7 +382,8 @@ function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
328
382
  assertIdentifier({
329
383
  value: columnName,
330
384
  what: `synced table "${name}": column "${propertyName}" column name`,
331
- file
385
+ file,
386
+ pattern: COLUMN_IDENTIFIER_PATTERN
332
387
  });
333
388
  const existing = seenColumnNames.get(columnName);
334
389
  if (existing !== void 0) {
@@ -417,7 +472,7 @@ function defineView(name, columns, config) {
417
472
  );
418
473
  }
419
474
  const columnName = builder.columnNameFor(propertyName);
420
- if (!IDENTIFIER_PATTERN.test(columnName) || columnName.length > MAX_IDENTIFIER_LENGTH) {
475
+ if (!COLUMN_IDENTIFIER_PATTERN.test(columnName) || columnName.length > MAX_IDENTIFIER_LENGTH) {
421
476
  throw new OrmDefinitionError(`view "${name}": column "${propertyName}" has invalid column name "${columnName}"`, file);
422
477
  }
423
478
  const existing = seenColumnNames.get(columnName);
@@ -535,11 +590,11 @@ function compileViewQueries(input) {
535
590
  );
536
591
  }
537
592
  if (!config.query) throw new OrmDefinitionError(`view "${authored.viewName}" produced no query`, authored.file);
538
- const tableDependencies = pgCore.extractUsedTable(config.query).map((physical) => {
539
- const logical = physicalToLogical.get(physical);
593
+ const tableDependencies = pgCore.extractUsedTable(config.query).map((physical2) => {
594
+ const logical = physicalToLogical.get(physical2);
540
595
  if (!logical) {
541
596
  throw new OrmDefinitionError(
542
- `view "${authored.viewName}" query references unmanaged relation "${physical}" \u2014 use relation(...) with a namespace table or view`,
597
+ `view "${authored.viewName}" query references unmanaged relation "${physical2}" \u2014 use relation(...) with a namespace table or view`,
543
598
  authored.file
544
599
  );
545
600
  }
@@ -863,9 +918,478 @@ function scalarColumnType(table, property, helper) {
863
918
  }
864
919
  return type;
865
920
  }
921
+
922
+ // src/compiler/sql.ts
923
+ var CLAIMS_SETTING = "request.jwt.claims";
924
+ var CAPABILITIES_CLAIM_KEY = "capabilities";
925
+ var ROLES_CLAIM_KEY = "roles";
926
+ var quoteIdentifier = (name) => `"${name.replaceAll('"', '""')}"`;
927
+ var quoteLiteral = (value) => `'${value.replaceAll("'", "''")}'`;
928
+ var CLAIMS_SOURCE = `nullif(current_setting('${CLAIMS_SETTING}', true), '')::jsonb`;
929
+ var claimExpression = (key) => `(${CLAIMS_SOURCE} ->> ${quoteLiteral(key)})`;
930
+ var capabilityExpression = (key) => `coalesce(${CLAIMS_SOURCE} -> '${CAPABILITIES_CLAIM_KEY}' ? ${quoteLiteral(key)}, false)`;
931
+ var roleExpression = (key) => `coalesce(${CLAIMS_SOURCE} -> '${ROLES_CLAIM_KEY}' ? ${quoteLiteral(key)}, false)`;
932
+
933
+ // src/checks/compile.ts
934
+ function tokenize(source) {
935
+ const tokens = [];
936
+ for (let position = 0; position < source.length; ) {
937
+ const char = source[position];
938
+ if (/\s/.test(char)) {
939
+ position++;
940
+ continue;
941
+ }
942
+ if (char === "(" || char === ")" || char === ",") {
943
+ tokens.push({ kind: char === "(" ? "lparen" : char === ")" ? "rparen" : "comma", value: char, position });
944
+ position++;
945
+ continue;
946
+ }
947
+ if (char === "'") {
948
+ const start = position++;
949
+ let value = "";
950
+ let closed = false;
951
+ while (position < source.length) {
952
+ if (source[position] !== "'") {
953
+ value += source[position++];
954
+ continue;
955
+ }
956
+ if (source[position + 1] === "'") {
957
+ value += "'";
958
+ position += 2;
959
+ continue;
960
+ }
961
+ position++;
962
+ closed = true;
963
+ break;
964
+ }
965
+ if (!closed) throw new Error(`unterminated string at position ${start}`);
966
+ if (/[\x00-\x1f\x7f]/.test(value)) throw new Error(`control character in string at position ${start}`);
967
+ tokens.push({ kind: "string", value, position: start });
968
+ continue;
969
+ }
970
+ const operator = /^(<=|>=|<>|!=|!~|=|<|>|~)/.exec(source.slice(position));
971
+ if (operator) {
972
+ tokens.push({ kind: "operator", value: operator[0] === "<>" ? "!=" : operator[0], position });
973
+ position += operator[0].length;
974
+ continue;
975
+ }
976
+ const number = /^-?\d+(?:\.\d+)?/.exec(source.slice(position));
977
+ if (number) {
978
+ tokens.push({ kind: "number", value: number[0], position });
979
+ position += number[0].length;
980
+ continue;
981
+ }
982
+ const identifier3 = /^[a-zA-Z_][a-zA-Z_0-9]*/.exec(source.slice(position));
983
+ if (identifier3) {
984
+ tokens.push({ kind: "identifier", value: identifier3[0], position });
985
+ position += identifier3[0].length;
986
+ continue;
987
+ }
988
+ throw new Error(`unexpected character '${char}' at position ${position}`);
989
+ }
990
+ tokens.push({ kind: "end", value: "", position: source.length });
991
+ if (tokens.length > 256) throw new Error("check expression exceeds 256 tokens");
992
+ return tokens;
993
+ }
994
+ var Parser = class {
995
+ constructor(tokens, columns) {
996
+ this.tokens = tokens;
997
+ this.columns = /* @__PURE__ */ new Map();
998
+ for (const column of columns) {
999
+ for (const alias of [column.name, column.columnName]) {
1000
+ const previous = this.columns.get(alias);
1001
+ if (previous && previous.columnName !== column.columnName) {
1002
+ throw new Error(`ambiguous check column '${alias}'`);
1003
+ }
1004
+ this.columns.set(alias, column);
1005
+ }
1006
+ }
1007
+ }
1008
+ index = 0;
1009
+ depth = 0;
1010
+ columns;
1011
+ parse() {
1012
+ const expression = this.parseOr();
1013
+ this.expect("end", "end of expression");
1014
+ return expression;
1015
+ }
1016
+ parseOr() {
1017
+ let left = this.parseAnd();
1018
+ while (this.matchKeyword("or")) left = `(${left} OR ${this.parseAnd()})`;
1019
+ return left;
1020
+ }
1021
+ parseAnd() {
1022
+ let left = this.parseNot();
1023
+ while (this.matchKeyword("and")) left = `(${left} AND ${this.parseNot()})`;
1024
+ return left;
1025
+ }
1026
+ parseNot() {
1027
+ if (this.matchKeyword("not")) return `(NOT ${this.parseNot()})`;
1028
+ return this.parsePrimary();
1029
+ }
1030
+ parsePrimary() {
1031
+ if (this.peek().kind === "lparen") {
1032
+ if (++this.depth > 32) throw new Error("check expression nesting exceeds 32 levels");
1033
+ this.advance();
1034
+ const expression = this.parseOr();
1035
+ this.expect("rparen", "')'");
1036
+ this.depth--;
1037
+ return `(${expression})`;
1038
+ }
1039
+ return this.parseComparison();
1040
+ }
1041
+ parseComparison() {
1042
+ const left = this.parseValue();
1043
+ if (this.matchKeyword("is")) {
1044
+ const negate = this.matchKeyword("not");
1045
+ this.expectKeyword("null");
1046
+ return `${left.sql} IS ${negate ? "NOT " : ""}NULL`;
1047
+ }
1048
+ if (this.matchKeyword("between")) {
1049
+ const lower = this.parseValue();
1050
+ this.expectKeyword("and");
1051
+ const upper = this.parseValue();
1052
+ return `${left.sql} BETWEEN ${lower.sql} AND ${upper.sql}`;
1053
+ }
1054
+ if (this.matchKeyword("in")) {
1055
+ this.expect("lparen", "'(' after IN");
1056
+ const values = [this.parseValue()];
1057
+ while (this.peek().kind === "comma") {
1058
+ this.advance();
1059
+ values.push(this.parseValue());
1060
+ }
1061
+ this.expect("rparen", "')' after IN values");
1062
+ if (values.some((value) => !value.literal)) throw new Error("IN values must be literals");
1063
+ return `${left.sql} IN (${values.map((value) => value.sql).join(", ")})`;
1064
+ }
1065
+ const operator = this.expect("operator", "a comparison operator");
1066
+ const right = this.parseValue();
1067
+ if (operator.value === "~" || operator.value === "!~") {
1068
+ if (left.column && left.column.type !== "text") throw new Error("regex checks require a text column");
1069
+ if (!right.literal) throw new Error("regex pattern must be a literal");
1070
+ }
1071
+ return `${left.sql} ${operator.value} ${right.sql}`;
1072
+ }
1073
+ parseValue() {
1074
+ const token = this.advance();
1075
+ if (token.kind === "string") return { sql: quoteLiteral(token.value), literal: true };
1076
+ if (token.kind === "number") {
1077
+ const number = Number(token.value);
1078
+ if (!Number.isFinite(number) || Number.isInteger(number) && !Number.isSafeInteger(number)) {
1079
+ throw new Error(`unsafe numeric literal at position ${token.position}`);
1080
+ }
1081
+ return { sql: token.value, literal: true };
1082
+ }
1083
+ if (token.kind !== "identifier") throw new Error(`expected a column or literal at position ${token.position}`);
1084
+ const keyword = token.value.toLowerCase();
1085
+ if (keyword === "true" || keyword === "false" || keyword === "null") return { sql: keyword.toUpperCase(), literal: true };
1086
+ if (keyword === "btrim") {
1087
+ this.expect("lparen", "'(' after btrim");
1088
+ const column2 = this.parseColumn();
1089
+ this.expect("rparen", "')' after btrim column");
1090
+ if (column2.type !== "text") throw new Error("btrim requires a text column");
1091
+ return { sql: `btrim(${quoteIdentifier(column2.columnName)})`, column: column2 };
1092
+ }
1093
+ const column = this.columns.get(token.value);
1094
+ if (!column) throw new Error(`unknown check column '${token.value}'`);
1095
+ return { sql: quoteIdentifier(column.columnName), column };
1096
+ }
1097
+ parseColumn() {
1098
+ const token = this.expect("identifier", "a column name");
1099
+ const column = this.columns.get(token.value);
1100
+ if (!column) throw new Error(`unknown check column '${token.value}'`);
1101
+ return column;
1102
+ }
1103
+ peek() {
1104
+ return this.tokens[this.index];
1105
+ }
1106
+ advance() {
1107
+ const token = this.peek();
1108
+ if (token.kind !== "end") this.index++;
1109
+ return token;
1110
+ }
1111
+ matchKeyword(keyword) {
1112
+ if (this.peek().kind !== "identifier" || this.peek().value.toLowerCase() !== keyword) return false;
1113
+ this.advance();
1114
+ return true;
1115
+ }
1116
+ expectKeyword(keyword) {
1117
+ if (!this.matchKeyword(keyword)) throw new Error(`expected '${keyword}' at position ${this.peek().position}`);
1118
+ }
1119
+ expect(kind, description) {
1120
+ const token = this.peek();
1121
+ if (token.kind !== kind) throw new Error(`expected ${description} at position ${token.position}`);
1122
+ return this.advance();
1123
+ }
1124
+ };
1125
+ function compileCheckExpression(source, columns) {
1126
+ return new Parser(tokenize(source), columns).parse();
1127
+ }
1128
+ var policyVariantSchema = zod.z.object({
1129
+ /** Claims-only guard (capability()/role()/claim() comparisons — no columns). */
1130
+ when: zod.z.string().min(1).max(1e3),
1131
+ /** Exactly one of: allow-all bypass, or a row predicate. */
1132
+ allow: zod.z.literal(true).optional(),
1133
+ policy: zod.z.string().min(1).max(2e3).optional()
1134
+ }).refine((v) => v.allow === true !== (typeof v.policy === "string"), {
1135
+ message: "A variant declares exactly one of allow: true or policy"
1136
+ });
1137
+ var policyExpressionSchema = zod.z.union([
1138
+ zod.z.string().min(1).max(2e3),
1139
+ zod.z.object({
1140
+ default: zod.z.string().min(1).max(2e3),
1141
+ variants: zod.z.array(policyVariantSchema).max(10).default([])
1142
+ })
1143
+ ]);
1144
+ function parseDurationMs(input) {
1145
+ if (typeof input === "number") return Number.isInteger(input) && input > 0 ? input : void 0;
1146
+ const match = /^(\d+(?:\.\d+)?)(ms|s|m)$/.exec(input);
1147
+ if (!match) return void 0;
1148
+ const factor = match[2] === "ms" ? 1 : match[2] === "s" ? 1e3 : 6e4;
1149
+ const value = Math.round(Number(match[1]) * factor);
1150
+ return value > 0 ? value : void 0;
1151
+ }
1152
+ var identifier = zod.z.string().min(1).max(63).regex(/^[a-z][a-z0-9_]*$/);
1153
+ var durationMs = zod.z.number().int().positive().max(24 * 60 * 60 * 1e3);
1154
+ var RESOLVED_VIEW_SYSTEM_COLUMNS = {
1155
+ writebackStatus: { name: "writebackStatus", columnName: "_writeback_status", type: "text" },
1156
+ writebackError: { name: "writebackError", columnName: "_writeback_error", type: "text" }
1157
+ };
1158
+ var RESOLVED_SOURCE_SYSTEM_COLUMNS = {
1159
+ tombstoned: { name: "sysTombstoned", columnName: "_tombstoned" },
1160
+ fetchedAt: { name: "sysFetchedAt", columnName: "_fetched_at" },
1161
+ writebackStatus: { name: "sysWritebackStatus", columnName: "_writeback_status" },
1162
+ writebackError: { name: "sysWritebackError", columnName: "_writeback_error" },
1163
+ writebackAt: { name: "sysWritebackAt", columnName: "_writeback_at" }
1164
+ };
1165
+ var RESOLVED_VIEW_CELL_PARTS = ["source", "at", "by", "conflicts"];
1166
+ var RESERVED_RESOLVED_COLUMN_NAMES = /* @__PURE__ */ new Set([
1167
+ ...Object.values(RESOLVED_VIEW_SYSTEM_COLUMNS).flatMap((c) => [c.name, c.columnName]),
1168
+ ...Object.values(RESOLVED_SOURCE_SYSTEM_COLUMNS).flatMap((c) => [c.name, c.columnName])
1169
+ ]);
1170
+ var COMPARATORS = ["latest", "highest", "lowest", "priority", "nonNullFirst", "custom"];
1171
+ var canonicalFunctionRefSchema = zod.z.object({
1172
+ /** `managed` = a Managed Function id/slug; `native` = an in-process Atlas function (reserved). */
1173
+ kind: zod.z.enum(["managed", "native"]),
1174
+ id: zod.z.string().min(1).max(200)
1175
+ }).strict();
1176
+ var canonicalFetchShapeSchema = zod.z.union([
1177
+ zod.z.object({ shape: zod.z.literal("single") }).strict(),
1178
+ zod.z.object({ shape: zod.z.literal("batch"), maxKeys: zod.z.number().int().min(2).max(500) }).strict()
1179
+ ]);
1180
+ var canonicalResolvedSourceSchema = zod.z.object({
1181
+ name: identifier,
1182
+ kind: zod.z.enum(["manual", "etl", "prefetch"]),
1183
+ /** Backing relation in this namespace: an internal table (manual/prefetch) or a synced table (etl). */
1184
+ table: identifier,
1185
+ /** Resolved columns (logical names) this source supplies — the primary key is implicit. */
1186
+ columns: zod.z.array(zod.z.string().min(1).max(63)).min(1).max(500),
1187
+ /** etl: logical timestamptz/date column on the synced table used as every cell's updated_at. */
1188
+ updatedAt: zod.z.string().min(1).max(63).optional(),
1189
+ /** prefetch: resolved column holding the source system's key (may be the primary key). */
1190
+ key: zod.z.string().min(1).max(63).optional(),
1191
+ fetch: canonicalFetchShapeSchema.optional(),
1192
+ function: canonicalFunctionRefSchema.optional(),
1193
+ prefetchDefaults: zod.z.object({
1194
+ maxStalenessMs: durationMs,
1195
+ timeoutMs: durationMs,
1196
+ onError: zod.z.enum(["fallback", "fail"])
1197
+ }).strict().optional(),
1198
+ writeback: zod.z.object({
1199
+ columns: zod.z.array(zod.z.string().min(1).max(63)).min(1).max(500),
1200
+ defaultMode: zod.z.enum(["sync", "async"]),
1201
+ timeoutMs: durationMs,
1202
+ /** Remove confirmed cells from the manual source so a stale edit cannot shadow the source. */
1203
+ clearManualOnConfirm: zod.z.boolean()
1204
+ }).strict().optional()
1205
+ }).strict();
1206
+ var canonicalResolveRuleSchema = zod.z.object({
1207
+ column: zod.z.string().min(1).max(63),
1208
+ comparator: zod.z.enum(COMPARATORS),
1209
+ /** Source names, in tie-break order. */
1210
+ candidates: zod.z.array(identifier).min(1).max(16),
1211
+ /** custom only: SQL ORDER BY key over value / updated_at / source / actor / ord. */
1212
+ orderBy: zod.z.string().min(1).max(2e3).optional()
1213
+ }).strict();
1214
+ var canonicalResolvedSchema = zod.z.object({
1215
+ sources: zod.z.array(canonicalResolvedSourceSchema).min(1).max(16),
1216
+ /** One rule per resolved (non-pk) column; documents the generated view for tooling. */
1217
+ resolve: zod.z.array(canonicalResolveRuleSchema).max(500),
1218
+ /** Sources whose tombstone hides the resolved row (v1: the manual source only). */
1219
+ deletedBy: zod.z.array(identifier).max(1).default([]),
1220
+ /**
1221
+ * Materialized: the view reads an indexed internal table (`table`) that the
1222
+ * data plane keeps in step with `compute`, the internal view holding the
1223
+ * resolution query. Omitted when the view computes on read.
1224
+ */
1225
+ materialized: zod.z.object({ table: identifier, compute: identifier }).strict().optional(),
1226
+ /** Write policies evaluated against the RESOLVED row (reads use the view's read policy). */
1227
+ policies: zod.z.object({
1228
+ write: policyExpressionSchema.optional(),
1229
+ insert: policyExpressionSchema.optional(),
1230
+ update: policyExpressionSchema.optional()
1231
+ }).strict().default({})
1232
+ }).strict();
1233
+ function validateResolvedView({ view, tables, views = [] }) {
1234
+ const resolved = view.resolved;
1235
+ if (!resolved) return [];
1236
+ const errors = [];
1237
+ const fail = (message) => errors.push(`resolved table '${view.view}': ${message}`);
1238
+ const pkColumn = view.primaryKey?.length === 1 ? view.columns.find((c) => c.columnName === view.primaryKey?.[0]) : void 0;
1239
+ if (!pkColumn) {
1240
+ fail("requires exactly one primary-key column");
1241
+ return errors;
1242
+ }
1243
+ const systemNames = new Set(Object.values(RESOLVED_VIEW_SYSTEM_COLUMNS).map((c) => c.name));
1244
+ const scope = {
1245
+ view,
1246
+ resolved,
1247
+ pkColumn,
1248
+ dataColumns: new Map(view.columns.filter((c) => !systemNames.has(c.name) && c.cell === void 0).map((c) => [c.name, c])),
1249
+ sourceByName: /* @__PURE__ */ new Map(),
1250
+ fail
1251
+ };
1252
+ if (resolved.sources.filter((source) => source.kind === "manual").length !== 1) fail("requires exactly one manual source");
1253
+ const tableByName = new Map(tables.map((table) => [table.table, table]));
1254
+ for (const source of resolved.sources) {
1255
+ if (scope.sourceByName.has(source.name)) fail(`duplicate source '${source.name}'`);
1256
+ scope.sourceByName.set(source.name, source);
1257
+ const table = tableByName.get(source.table);
1258
+ if (!table) fail(`source '${source.name}' references unknown table '${source.table}'`);
1259
+ else validateSource({ scope, source, table });
1260
+ }
1261
+ validateRules(scope);
1262
+ validateViewCells(scope);
1263
+ if (resolved.materialized) validateMaterialized({ scope, tableByName, views });
1264
+ for (const name of resolved.deletedBy) {
1265
+ if (scope.sourceByName.get(name)?.kind !== "manual") fail(`deletedBy '${name}' must be the manual source`);
1266
+ }
1267
+ return errors;
1268
+ }
1269
+ function validateSource({ scope, source, table }) {
1270
+ const { fail } = scope;
1271
+ if (source.kind === "etl" && table.source === void 0) fail(`etl source '${source.name}' must reference a synced table`);
1272
+ if (source.kind !== "etl" && (table.internal !== true || table.cellMetadata !== true)) {
1273
+ fail(`${source.kind} source '${source.name}' must reference an internal table with cell metadata`);
1274
+ }
1275
+ const hasRev = table.columns.some((column) => column.cell?.part === "rev");
1276
+ if (source.kind === "manual" && !hasRev) fail(`manual source '${source.name}' must carry '__rev' cell columns`);
1277
+ if (source.kind === "prefetch" && hasRev) fail(`prefetch source '${source.name}' must not carry '__rev' cell columns`);
1278
+ const byName = new Map(table.columns.map((c) => [c.name, c]));
1279
+ validateSourceColumns({ scope, source, byName });
1280
+ if (source.kind === "etl") {
1281
+ const updatedAt = source.updatedAt === void 0 ? void 0 : byName.get(source.updatedAt);
1282
+ if (source.updatedAt !== void 0 && updatedAt?.type !== "timestamptz" && updatedAt?.type !== "date") {
1283
+ fail(`etl source '${source.name}' updatedAt must name a timestamptz/date column on '${source.table}'`);
1284
+ }
1285
+ }
1286
+ validatePrefetchFields({ scope, source });
1287
+ }
1288
+ function validatePrefetchFields({ scope, source }) {
1289
+ const { fail, pkColumn } = scope;
1290
+ if (source.kind !== "prefetch") {
1291
+ if (source.key || source.fetch || source.function || source.prefetchDefaults || source.writeback) {
1292
+ fail(`only prefetch sources may declare key/fetch/function/prefetchDefaults/writeback ('${source.name}')`);
1293
+ }
1294
+ return;
1295
+ }
1296
+ if (!source.key || !source.fetch || !source.function || !source.prefetchDefaults) {
1297
+ fail(`prefetch source '${source.name}' requires key, fetch, function and prefetchDefaults`);
1298
+ } else if (source.key !== pkColumn.name && !source.columns.includes(source.key)) {
1299
+ fail(`prefetch source '${source.name}' key '${source.key}' must be the primary key or one of its columns`);
1300
+ }
1301
+ for (const column of source.writeback?.columns ?? []) {
1302
+ if (!source.columns.includes(column)) fail(`prefetch source '${source.name}' writes back '${column}', which it does not supply`);
1303
+ }
1304
+ }
1305
+ function validateSourceColumns({ scope, source, byName }) {
1306
+ const { fail, pkColumn, dataColumns } = scope;
1307
+ for (const column of [pkColumn.name, ...source.columns]) {
1308
+ const own = column === pkColumn.name ? pkColumn : dataColumns.get(column);
1309
+ const theirs = byName.get(column);
1310
+ if (!own) fail(`source '${source.name}' supplies unknown column '${column}'`);
1311
+ else if (!theirs) fail(`source '${source.name}' table '${source.table}' has no column '${column}'`);
1312
+ else if (JSON.stringify(theirs.type) !== JSON.stringify(own.type)) fail(`column '${column}' has a different type on source '${source.name}'`);
1313
+ }
1314
+ }
1315
+ function validateRules({ resolved, pkColumn, dataColumns, sourceByName, fail }) {
1316
+ const ruled = /* @__PURE__ */ new Set();
1317
+ for (const rule of resolved.resolve) {
1318
+ if (!dataColumns.has(rule.column) || rule.column === pkColumn.name) {
1319
+ fail(`resolve rule targets unknown column '${rule.column}'`);
1320
+ continue;
1321
+ }
1322
+ if (ruled.has(rule.column)) fail(`column '${rule.column}' has more than one resolve rule`);
1323
+ ruled.add(rule.column);
1324
+ if (rule.comparator === "custom" !== (rule.orderBy !== void 0)) {
1325
+ fail(`column '${rule.column}': orderBy is required for custom comparators and only allowed there`);
1326
+ }
1327
+ if (new Set(rule.candidates).size !== rule.candidates.length) fail(`column '${rule.column}' repeats a candidate`);
1328
+ for (const candidate of rule.candidates) {
1329
+ const source = sourceByName.get(candidate);
1330
+ if (!source) fail(`column '${rule.column}' names unknown candidate source '${candidate}'`);
1331
+ else if (!source.columns.includes(rule.column)) fail(`column '${rule.column}' candidate '${candidate}' does not supply it`);
1332
+ }
1333
+ }
1334
+ for (const column of dataColumns.keys()) {
1335
+ if (column !== pkColumn.name && !ruled.has(column)) fail(`column '${column}' has no resolve rule`);
1336
+ }
1337
+ }
1338
+ var VIEW_CELL_TYPES = {
1339
+ source: "text",
1340
+ at: "timestamptz",
1341
+ by: "text",
1342
+ conflicts: "jsonb"
1343
+ };
1344
+ function validateViewCells({ view, dataColumns, pkColumn, fail }) {
1345
+ const expected = /* @__PURE__ */ new Map();
1346
+ for (const column of dataColumns.values()) {
1347
+ if (column.name === pkColumn.name) continue;
1348
+ for (const part of RESOLVED_VIEW_CELL_PARTS) {
1349
+ expected.set(cellColumnName(column.name, part), { part, columnName: cellColumnName(column.columnName, part) });
1350
+ }
1351
+ }
1352
+ for (const column of view.columns) {
1353
+ if (!column.cell) continue;
1354
+ const want = expected.get(column.name);
1355
+ if (!want || want.part !== column.cell.part || want.columnName !== column.columnName || cellColumnName(column.cell.of, column.cell.part) !== column.name) {
1356
+ fail(`unexpected cell column '${column.name}'`);
1357
+ continue;
1358
+ }
1359
+ if (column.type !== VIEW_CELL_TYPES[column.cell.part]) fail(`cell column '${column.name}' must be ${VIEW_CELL_TYPES[column.cell.part]}`);
1360
+ expected.delete(column.name);
1361
+ }
1362
+ for (const name of expected.keys()) fail(`missing cell column '${name}'`);
1363
+ }
1364
+ var columnSignature = (columns) => JSON.stringify(columns.map((column) => [column.name, column.columnName, column.type]));
1365
+ function validateMaterialized({ scope, tableByName, views }) {
1366
+ const { view, resolved, fail } = scope;
1367
+ const { table: tableName, compute: computeName } = resolved.materialized;
1368
+ const table = tableByName.get(tableName);
1369
+ if (!table || table.internal !== true || table.materializes !== view.view) {
1370
+ fail(`materialized table '${tableName}' must be an internal table that materializes '${view.view}'`);
1371
+ } else {
1372
+ if (columnSignature(table.columns) !== columnSignature(view.columns)) fail(`materialized table '${tableName}' must have exactly the view's columns`);
1373
+ if (JSON.stringify(table.primaryKey ?? []) !== JSON.stringify(view.primaryKey ?? [])) fail(`materialized table '${tableName}' must share the view's primary key`);
1374
+ }
1375
+ const compute = views.find((candidate) => candidate.view === computeName);
1376
+ if (!compute || compute.internal !== true) fail(`compute view '${computeName}' must be an internal view`);
1377
+ else if (columnSignature(compute.columns) !== columnSignature(view.columns)) fail(`compute view '${computeName}' must have exactly the view's columns`);
1378
+ for (const source of resolved.sources) {
1379
+ if (source.kind !== "etl") continue;
1380
+ const read = tableByName.get(source.table)?.policies?.read;
1381
+ const unconditional = typeof read === "string" ? read.trim() === "true" : read !== void 0 && read.default.trim() === "true" && read.variants.length === 0;
1382
+ if (!unconditional) {
1383
+ fail(`etl source '${source.name}' must have read policy 'true' to be materialized (the copy is shared by every caller)`);
1384
+ }
1385
+ }
1386
+ }
1387
+
1388
+ // src/canonical/v2.ts
866
1389
  var CANONICAL_FORMAT = 2;
867
1390
  var IDENTIFIER_RE = /^[a-z][a-z0-9_]*$/;
868
- var identifier = zod.z.string().min(1).max(63).regex(IDENTIFIER_RE);
1391
+ var identifier2 = zod.z.string().min(1).max(63).regex(IDENTIFIER_RE);
1392
+ var columnIdentifier = zod.z.string().min(1).max(63).regex(COLUMN_IDENTIFIER_PATTERN);
869
1393
  var COLUMN_TYPES = [
870
1394
  "uuid",
871
1395
  "text",
@@ -880,59 +1404,71 @@ var COLUMN_TYPES = [
880
1404
  ];
881
1405
  var columnTypeSchema = zod.z.enum(COLUMN_TYPES);
882
1406
  var canonicalEnumSchema = zod.z.object({
883
- name: identifier,
1407
+ name: identifier2,
884
1408
  values: zod.z.array(zod.z.string().min(1).max(63)).min(1).max(100)
885
1409
  });
886
1410
  var canonicalColumnSchema = zod.z.object({
887
1411
  /** Property name in TS (camelCase); columnName is the Postgres name. */
888
1412
  name: zod.z.string().min(1).max(63),
889
- columnName: identifier,
890
- type: zod.z.union([columnTypeSchema, zod.z.object({ enum: identifier })]),
1413
+ columnName: columnIdentifier,
1414
+ type: zod.z.union([columnTypeSchema, zod.z.object({ enum: identifier2 })]),
891
1415
  nullable: zod.z.boolean().default(true),
892
1416
  unique: zod.z.boolean().default(false),
893
1417
  /** Literal default, or one of the allowlisted SQL functions. */
894
1418
  default: zod.z.union([zod.z.string(), zod.z.number(), zod.z.boolean(), zod.z.null()]).optional(),
895
1419
  defaultFn: zod.z.enum(["gen_random_uuid()", "now()"]).optional(),
1420
+ /**
1421
+ * Stamp this column from the database clock on UPDATE (`now()`). The
1422
+ * column stays insertable (defaults fill gaps) but is omitted from
1423
+ * SetInput / settable so callers cannot backdate it.
1424
+ */
1425
+ onUpdateFn: zod.z.enum(["now()"]).optional(),
896
1426
  description: zod.z.string().max(500).optional(),
897
1427
  /** Migration hints — same semantics as v1. */
898
- renamedFrom: identifier.optional(),
1428
+ renamedFrom: columnIdentifier.optional(),
899
1429
  cast: zod.z.string().max(200).optional(),
900
- deprecated: zod.z.boolean().default(false)
1430
+ deprecated: zod.z.boolean().default(false),
1431
+ /**
1432
+ * Marks a system cell-metadata column (see CELL_PARTS in constants): part
1433
+ * `part` of the cell of logical column `of`. Synthesized by authoring,
1434
+ * never client-writable, absent from GraphQL scalar fields. Omitted on
1435
+ * ordinary columns so their hashes are unchanged.
1436
+ */
1437
+ cell: zod.z.object({ of: zod.z.string().min(1).max(63), part: zod.z.enum(CELL_PARTS) }).strict().optional()
1438
+ }).superRefine((column, ctx) => {
1439
+ if (column.onUpdateFn === void 0) return;
1440
+ if (typeof column.type !== "string" || column.type !== "timestamptz" && column.type !== "date") {
1441
+ ctx.addIssue({
1442
+ code: "custom",
1443
+ path: ["onUpdateFn"],
1444
+ message: `onUpdateFn '${column.onUpdateFn}' is only valid on timestamptz/date columns`
1445
+ });
1446
+ }
901
1447
  });
902
1448
  var canonicalLinkSchema = zod.z.object({
903
1449
  name: zod.z.string().min(1).max(63),
904
1450
  kind: zod.z.enum(["toOne", "toMany", "toPlatformUser"]),
905
1451
  /** Target table name (namespace-local). Absent for toPlatformUser. */
906
- target: identifier.optional(),
1452
+ target: identifier2.optional(),
907
1453
  /** FK column on the owning side (toOne/toPlatformUser: on this table; toMany: on the target). */
908
- on: identifier,
1454
+ on: columnIdentifier,
909
1455
  /** Referenced column on the target (default: target's PK / platform user `sub`). */
910
- references: identifier.optional(),
1456
+ references: columnIdentifier.optional(),
911
1457
  /** enforce: true emits a real FK constraint (same-namespace toOne only). */
912
1458
  enforce: zod.z.boolean().default(false),
1459
+ onDelete: zod.z.enum(["cascade", "restrict", "no action", "set null", "set default"]).optional(),
1460
+ onUpdate: zod.z.enum(["cascade", "restrict", "no action", "set null", "set default"]).optional(),
913
1461
  description: zod.z.string().max(500).optional()
914
1462
  });
915
1463
  var canonicalIndexSchema = zod.z.object({
916
- columns: zod.z.array(identifier).min(1),
1464
+ columns: zod.z.array(columnIdentifier).min(1),
917
1465
  unique: zod.z.boolean().default(false),
918
- name: identifier.optional()
1466
+ name: identifier2.optional()
919
1467
  });
920
- var policyVariantSchema = zod.z.object({
921
- /** Claims-only guard (capability()/role()/claim() comparisons — no columns). */
922
- when: zod.z.string().min(1).max(1e3),
923
- /** Exactly one of: allow-all bypass, or a row predicate. */
924
- allow: zod.z.literal(true).optional(),
925
- policy: zod.z.string().min(1).max(2e3).optional()
926
- }).refine((v) => v.allow === true !== (typeof v.policy === "string"), {
927
- message: "A variant declares exactly one of allow: true or policy"
928
- });
929
- var policyExpressionSchema = zod.z.union([
930
- zod.z.string().min(1).max(2e3),
931
- zod.z.object({
932
- default: zod.z.string().min(1).max(2e3),
933
- variants: zod.z.array(policyVariantSchema).max(10).default([])
934
- })
935
- ]);
1468
+ var canonicalCheckSchema = zod.z.object({
1469
+ name: identifier2,
1470
+ expression: zod.z.string().min(1).max(2e3)
1471
+ }).strict();
936
1472
  var canonicalPoliciesSchema = zod.z.object({
937
1473
  read: policyExpressionSchema.optional(),
938
1474
  write: policyExpressionSchema.optional(),
@@ -1120,7 +1656,7 @@ var VIEW_QUERY_FUNCTION_ALLOWLIST = [
1120
1656
  "width_bucket"
1121
1657
  ];
1122
1658
  var canonicalActionSchema = zod.z.object({
1123
- name: identifier,
1659
+ name: identifier2,
1124
1660
  requires: zod.z.string().max(1e3).optional(),
1125
1661
  params: zod.z.record(zod.z.string(), zod.z.object({
1126
1662
  type: columnTypeSchema,
@@ -1132,9 +1668,9 @@ var canonicalActionSchema = zod.z.object({
1132
1668
  });
1133
1669
  var canonicalCowMutationSchema = zod.z.object({
1134
1670
  kind: zod.z.enum(["override", "revert"]),
1135
- name: identifier,
1671
+ name: identifier2,
1136
1672
  /** Synced table (same namespace) the overlay copies from / falls back to. */
1137
- synced: identifier,
1673
+ synced: identifier2,
1138
1674
  /** Single-column primary key PROPERTY (TS camelCase, like column `name`) shared by overlay and synced. */
1139
1675
  pk: zod.z.string().min(1).max(63),
1140
1676
  /** Override: contract properties copied INSERT…SELECT from synced (pk included). Empty for revert. */
@@ -1161,17 +1697,28 @@ var canonicalCowMutationSchema = zod.z.object({
1161
1697
  }
1162
1698
  }
1163
1699
  });
1700
+ var canonicalForeignKeySchema = zod.z.object({
1701
+ name: identifier2,
1702
+ columns: zod.z.array(columnIdentifier).min(1).max(8),
1703
+ target: identifier2,
1704
+ references: zod.z.array(columnIdentifier).min(1).max(8),
1705
+ deferrable: zod.z.enum(["initially-immediate", "initially-deferred"]).optional(),
1706
+ onDelete: zod.z.enum(["cascade", "restrict", "no action", "set null", "set default"]).optional(),
1707
+ onUpdate: zod.z.enum(["cascade", "restrict", "no action", "set null", "set default"]).optional()
1708
+ }).strict();
1164
1709
  var canonicalTableSchema = zod.z.object({
1165
- table: identifier,
1710
+ table: identifier2,
1166
1711
  description: zod.z.string().max(2e3).optional(),
1167
1712
  /** Table-level rename hint (new in v2). */
1168
- renamedFrom: identifier.optional(),
1713
+ renamedFrom: identifier2.optional(),
1169
1714
  /** Synced/reflected read-only tables (`source: gold.*`) — v1 semantics. */
1170
1715
  source: zod.z.string().regex(/^gold\.[a-z][a-z0-9_]*$/).optional(),
1171
1716
  columns: zod.z.array(canonicalColumnSchema).default([]),
1172
- primaryKey: zod.z.array(identifier).min(1).max(8).optional(),
1717
+ primaryKey: zod.z.array(columnIdentifier).min(1).max(8).optional(),
1173
1718
  links: zod.z.array(canonicalLinkSchema).default([]),
1174
1719
  indexes: zod.z.array(canonicalIndexSchema).default([]),
1720
+ /** Omitted by old definitions, preserving their existing content hashes. */
1721
+ checks: zod.z.array(canonicalCheckSchema).max(100).optional(),
1175
1722
  policies: canonicalPoliciesSchema.default({}),
1176
1723
  /**
1177
1724
  * v1-conversion carry-over only. v2 has no action surface — the runtime
@@ -1180,26 +1727,64 @@ var canonicalTableSchema = zod.z.object({
1180
1727
  */
1181
1728
  actions: zod.z.array(canonicalActionSchema).default([]),
1182
1729
  /** Optional (not defaulted) so pre-CoW canonical documents keep byte-identical hashes. */
1183
- cowMutations: zod.z.array(canonicalCowMutationSchema).optional()
1730
+ cowMutations: zod.z.array(canonicalCowMutationSchema).optional(),
1731
+ /** Omitted by old definitions, preserving their existing content hashes. */
1732
+ foreignKeys: zod.z.array(canonicalForeignKeySchema).max(100).optional(),
1733
+ /**
1734
+ * Per-cell edit metadata (`cellMetadata: true` in authoring): every tracked
1735
+ * column `c` carries typed `c__at` / `c__by` system columns (plus `c__rev`
1736
+ * on a resolved table's internal sources), listed explicitly in `columns`
1737
+ * with a `cell` marker so DDL and migrations treat them as ordinary columns.
1738
+ * Omitted when off so existing definitions hash unchanged.
1739
+ */
1740
+ cellMetadata: zod.z.literal(true).optional(),
1741
+ /**
1742
+ * System-owned backing table of a resolved table (its manual or prefetch
1743
+ * source). Internal tables get DDL and IR metadata but no GraphQL surface;
1744
+ * only the data plane writes them. Omitted when false.
1745
+ */
1746
+ internal: zod.z.literal(true).optional(),
1747
+ /**
1748
+ * The materialized copy of resolved table `materializes` (internal): the
1749
+ * same columns as that view, including its cell parts, maintained by the
1750
+ * data plane. Omitted otherwise.
1751
+ */
1752
+ materializes: identifier2.optional()
1184
1753
  });
1754
+ var MAX_VIEW_QUERY = 1e5;
1755
+ var MAX_RESOLVED_VIEW_QUERY = 5e6;
1185
1756
  var canonicalViewSchema = zod.z.object({
1186
- view: identifier,
1757
+ view: identifier2,
1187
1758
  description: zod.z.string().max(2e3).optional(),
1188
1759
  columns: zod.z.array(canonicalColumnSchema).min(1),
1189
1760
  /** Logical identity for GraphQL only; PostgreSQL views have no PK constraint. */
1190
- primaryKey: zod.z.array(identifier).min(1).max(8).optional(),
1761
+ primaryKey: zod.z.array(columnIdentifier).min(1).max(8).optional(),
1191
1762
  links: zod.z.array(canonicalLinkSchema).default([]),
1192
1763
  policies: zod.z.object({ read: policyExpressionSchema }).strict(),
1193
- query: zod.z.string().min(1).max(1e5).refine(isSingleViewQuerySql, {
1764
+ query: zod.z.string().min(1).max(MAX_RESOLVED_VIEW_QUERY).refine(isSingleViewQuerySql, {
1194
1765
  message: "view query must be one SELECT/WITH statement without comments or dollar-quoted bodies"
1195
1766
  }),
1196
1767
  /** Namespace-local logical relation names, sorted for deterministic hashing. */
1197
- dependencies: zod.z.array(identifier).max(500)
1768
+ dependencies: zod.z.array(identifier2).max(500),
1769
+ /**
1770
+ * Present when this view is a resolved table (defineResolvedTable): the
1771
+ * multi-source metadata behind its generated query. Omitted otherwise so
1772
+ * existing views hash unchanged.
1773
+ */
1774
+ resolved: canonicalResolvedSchema.optional(),
1775
+ /** System-owned view (a materialized resolved table's compute view): no GraphQL surface. */
1776
+ internal: zod.z.literal(true).optional()
1198
1777
  }).superRefine((view, ctx) => {
1778
+ if (view.resolved === void 0 && view.query.length > MAX_VIEW_QUERY) {
1779
+ ctx.addIssue({ code: "custom", path: ["query"], message: `view query must be at most ${MAX_VIEW_QUERY} characters` });
1780
+ }
1199
1781
  for (const [index3, column] of view.columns.entries()) {
1200
1782
  if (column.default !== void 0 || column.defaultFn !== void 0) {
1201
1783
  ctx.addIssue({ code: "custom", path: ["columns", index3], message: `view column '${column.name}' cannot declare a default` });
1202
1784
  }
1785
+ if (column.onUpdateFn !== void 0) {
1786
+ ctx.addIssue({ code: "custom", path: ["columns", index3], message: `view column '${column.name}' cannot declare onUpdateFn` });
1787
+ }
1203
1788
  if (column.unique) {
1204
1789
  ctx.addIssue({ code: "custom", path: ["columns", index3], message: `view column '${column.name}' cannot declare unique` });
1205
1790
  }
@@ -1207,15 +1792,82 @@ var canonicalViewSchema = zod.z.object({
1207
1792
  ctx.addIssue({ code: "custom", path: ["columns", index3], message: `view column '${column.name}' cannot declare migration hints` });
1208
1793
  }
1209
1794
  }
1795
+ if (view.resolved === void 0 && view.internal !== true) {
1796
+ for (const [index3, column] of view.columns.entries()) {
1797
+ if (column.cell) ctx.addIssue({ code: "custom", path: ["columns", index3], message: `only resolved tables carry cell columns ('${column.name}')` });
1798
+ }
1799
+ }
1210
1800
  for (const [index3, link] of view.links.entries()) {
1211
1801
  if (link.enforce) {
1212
1802
  ctx.addIssue({ code: "custom", path: ["links", index3, "enforce"], message: `view link '${link.name}' cannot enforce a foreign key` });
1213
1803
  }
1804
+ if (link.onDelete !== void 0 || link.onUpdate !== void 0) {
1805
+ ctx.addIssue({ code: "custom", path: ["links", index3], message: `view link '${link.name}' cannot declare referential actions` });
1806
+ }
1214
1807
  }
1215
1808
  });
1809
+ var CELL_PART_TYPES = {
1810
+ at: "timestamptz",
1811
+ by: "text",
1812
+ rev: "uuid",
1813
+ source: "text",
1814
+ conflicts: "jsonb"
1815
+ };
1816
+ function trackedCellColumns(table) {
1817
+ const pk = new Set(table.primaryKey ?? []);
1818
+ return table.columns.filter((column) => column.cell === void 0 && !pk.has(column.columnName) && column.onUpdateFn === void 0);
1819
+ }
1820
+ var SUFFIXES = Object.values(CELL_PART_SUFFIX);
1821
+ function validateCellMetadata(table, index3, ctx) {
1822
+ const fail = (message, path = ["tables", index3]) => ctx.addIssue({ code: "custom", path, message });
1823
+ const cells = table.columns.flatMap((column, position) => column.cell ? [{ column, position, cell: column.cell }] : []);
1824
+ if (table.materializes !== void 0) return;
1825
+ if (table.cellMetadata !== true) {
1826
+ for (const { position } of cells) fail(`cell-metadata columns require cellMetadata on table '${table.table}'`, ["tables", index3, "columns", position]);
1827
+ return;
1828
+ }
1829
+ if (table.source !== void 0) {
1830
+ fail(`synced table '${table.table}' cannot track cell metadata`);
1831
+ return;
1832
+ }
1833
+ const tracked = trackedCellColumns(table);
1834
+ const byName = new Map(tracked.map((column) => [column.name, column]));
1835
+ const withRev = cells.some(({ cell }) => cell.part === "rev");
1836
+ if (withRev && table.internal !== true) fail(`only a resolved table's internal sources carry '__rev' cell columns ('${table.table}')`);
1837
+ const seen = /* @__PURE__ */ new Set();
1838
+ for (const { column, position, cell } of cells) {
1839
+ const path = ["tables", index3, "columns", position];
1840
+ const owner = byName.get(cell.of);
1841
+ if (!owner) {
1842
+ fail(`cell column '${column.name}' belongs to '${cell.of}', which is not a tracked column`, path);
1843
+ continue;
1844
+ }
1845
+ 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);
1846
+ if (column.name !== cellColumnName(owner.name, cell.part) || column.columnName !== cellColumnName(owner.columnName, cell.part)) {
1847
+ fail(`cell column for '${owner.name}' ${cell.part} must be named '${cellColumnName(owner.name, cell.part)}' / '${cellColumnName(owner.columnName, cell.part)}'`, path);
1848
+ }
1849
+ if (column.type !== CELL_PART_TYPES[cell.part] || !column.nullable || column.unique || column.default !== void 0 || column.defaultFn !== void 0) {
1850
+ fail(`cell column '${column.name}' must be a nullable, default-less ${CELL_PART_TYPES[cell.part]}`, path);
1851
+ }
1852
+ const key = `${cell.of}:${cell.part}`;
1853
+ if (seen.has(key)) fail(`duplicate cell column '${column.name}'`, path);
1854
+ seen.add(key);
1855
+ }
1856
+ for (const column of tracked) {
1857
+ if (column.columnName.length > MAX_TRACKED_COLUMN_NAME) {
1858
+ fail(`column '${column.columnName}' is too long to track cell metadata (max ${MAX_TRACKED_COLUMN_NAME} characters)`);
1859
+ }
1860
+ if (SUFFIXES.some((suffix) => column.columnName.endsWith(suffix))) {
1861
+ fail(`column '${column.columnName}' ends in a reserved cell-metadata suffix (${SUFFIXES.join(", ")})`);
1862
+ }
1863
+ for (const part of withRev ? ["at", "by", "rev"] : ["at", "by"]) {
1864
+ if (!seen.has(`${column.name}:${part}`)) fail(`tracked column '${column.name}' is missing its '${cellColumnName(column.columnName, part)}' cell column`);
1865
+ }
1866
+ }
1867
+ }
1216
1868
  var canonicalDefinitionSchema = zod.z.object({
1217
1869
  definition_format: zod.z.literal(CANONICAL_FORMAT),
1218
- namespace: identifier.max(41),
1870
+ namespace: identifier2.max(41),
1219
1871
  description: zod.z.string().max(2e3).optional(),
1220
1872
  enums: zod.z.array(canonicalEnumSchema).default([]),
1221
1873
  tables: zod.z.array(canonicalTableSchema).min(0).max(500),
@@ -1233,6 +1885,74 @@ var canonicalDefinitionSchema = zod.z.object({
1233
1885
  ctx.addIssue({ code: "custom", path: ["tables", index3, "table"], message: `duplicate relation '${table.table}'` });
1234
1886
  }
1235
1887
  seen.add(table.table);
1888
+ validateCellMetadata(table, index3, ctx);
1889
+ if (table.source !== void 0) {
1890
+ for (const [columnIndex, column] of table.columns.entries()) {
1891
+ if (column.onUpdateFn !== void 0) {
1892
+ ctx.addIssue({
1893
+ code: "custom",
1894
+ path: ["tables", index3, "columns", columnIndex],
1895
+ message: `synced table '${table.table}' column '${column.name}' cannot declare onUpdateFn`
1896
+ });
1897
+ }
1898
+ }
1899
+ }
1900
+ }
1901
+ const fkTables = new Map(definition.tables.map((table) => [table.table, table]));
1902
+ for (const [tableIndex, table] of definition.tables.entries()) {
1903
+ const names = /* @__PURE__ */ new Set([
1904
+ ...table.primaryKey?.length ? [`${table.table}_pkey`] : [],
1905
+ ...table.columns.filter((column) => column.unique && !(table.primaryKey?.length === 1 && table.primaryKey[0] === column.columnName)).map((column) => `${table.table}_${column.columnName}_unique`),
1906
+ ...table.links.filter((link) => link.enforce).map((link) => `fk_${table.table}_${link.name}`)
1907
+ ]);
1908
+ for (const [index3, link] of table.links.entries()) {
1909
+ if ((link.onDelete !== void 0 || link.onUpdate !== void 0) && (!link.enforce || link.kind !== "toOne")) {
1910
+ ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "links", index3], message: "referential actions require an enforced toOne link" });
1911
+ }
1912
+ if (link.onDelete === "set null" || link.onUpdate === "set null") {
1913
+ const source = table.columns.find((column) => column.columnName === link.on);
1914
+ if (source?.nullable === false || table.primaryKey?.includes(link.on)) ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "links", index3], message: "SET NULL requires a nullable source column" });
1915
+ }
1916
+ }
1917
+ for (const [index3, check] of (table.checks ?? []).entries()) {
1918
+ const fail = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "checks", index3], message });
1919
+ if (table.source) fail("synced tables cannot declare checks");
1920
+ if (names.has(check.name)) fail(`duplicate constraint '${check.name}'`);
1921
+ names.add(check.name);
1922
+ try {
1923
+ compileCheckExpression(check.expression, table.columns);
1924
+ } catch (error) {
1925
+ fail(error instanceof Error ? error.message : String(error));
1926
+ }
1927
+ }
1928
+ for (const [index3, fk] of (table.foreignKeys ?? []).entries()) {
1929
+ const fail = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "foreignKeys", index3], message });
1930
+ if (names.has(fk.name)) fail(`duplicate foreign key '${fk.name}'`);
1931
+ names.add(fk.name);
1932
+ const target = fkTables.get(fk.target);
1933
+ if (table.source || !target || target.source) {
1934
+ fail("foreign keys must connect owned tables in the same namespace");
1935
+ continue;
1936
+ }
1937
+ if (fk.columns.length !== fk.references.length) fail("foreign key columns and references must have the same number of columns");
1938
+ if (new Set(fk.columns).size !== fk.columns.length || new Set(fk.references).size !== fk.references.length) fail("foreign key columns must not repeat");
1939
+ for (const [position, name] of fk.columns.entries()) {
1940
+ const sourceColumn = table.columns.find((column) => column.columnName === name);
1941
+ const targetColumn = target.columns.find((column) => column.columnName === fk.references[position]);
1942
+ if (!sourceColumn || !targetColumn) fail("foreign key references an unknown column");
1943
+ else if (JSON.stringify(sourceColumn.type) !== JSON.stringify(targetColumn.type)) fail("foreign key column types must match");
1944
+ }
1945
+ if (fk.onDelete === "set null" || fk.onUpdate === "set null") {
1946
+ 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");
1947
+ }
1948
+ const uniqueKeys = [target.primaryKey ?? [], ...target.indexes.filter((index4) => index4.unique).map((index4) => index4.columns), ...target.columns.filter((column) => column.unique).map((column) => [column.columnName])];
1949
+ 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");
1950
+ }
1951
+ }
1952
+ for (const [viewIndex, view] of (definition.views ?? []).entries()) {
1953
+ for (const message of validateResolvedView({ view, tables: definition.tables, views: definition.views ?? [] })) {
1954
+ ctx.addIssue({ code: "custom", path: ["views", viewIndex, "resolved"], message });
1955
+ }
1236
1956
  }
1237
1957
  const tableByName = new Map(definition.tables.map((table) => [table.table, table]));
1238
1958
  for (const [tableIndex, table] of definition.tables.entries()) {
@@ -1357,8 +2077,12 @@ function canonicalRelations(definition) {
1357
2077
  ...view.primaryKey !== void 0 ? { primaryKey: view.primaryKey } : {},
1358
2078
  links: view.links,
1359
2079
  indexes: [],
1360
- policies: { read: view.policies.read },
1361
- actions: []
2080
+ // A resolved table's write policies (evaluated against the resolved row)
2081
+ // ride alongside the view's read policy.
2082
+ policies: { read: view.policies.read, ...view.resolved?.policies ?? {} },
2083
+ actions: [],
2084
+ ...view.resolved !== void 0 ? { resolved: view.resolved } : {},
2085
+ ...view.internal ? { internal: true } : {}
1362
2086
  }));
1363
2087
  return [...definition.tables, ...views].sort((a, b) => a.table.localeCompare(b.table));
1364
2088
  }
@@ -1368,12 +2092,14 @@ function normalizeCanonical(definition) {
1368
2092
  ...definition,
1369
2093
  enums: sortByName(definition.enums),
1370
2094
  tables: [...definition.tables].sort((a, b) => a.table.localeCompare(b.table)).map((t2) => {
1371
- const { cowMutations, ...rest } = t2;
2095
+ const { cowMutations, foreignKeys, checks, ...rest } = t2;
1372
2096
  return {
1373
2097
  ...rest,
1374
2098
  links: sortByName(t2.links),
1375
2099
  indexes: [...t2.indexes].sort((a, b) => (a.name ?? a.columns.join("_")).localeCompare(b.name ?? b.columns.join("_"))),
1376
2100
  actions: sortByName(t2.actions),
2101
+ ...foreignKeys?.length ? { foreignKeys: sortByName(foreignKeys) } : {},
2102
+ ...checks?.length ? { checks: sortByName(checks) } : {},
1377
2103
  // Omitted entirely when absent/empty so pre-CoW documents hash unchanged.
1378
2104
  ...cowMutations !== void 0 && cowMutations.length > 0 ? { cowMutations: sortByName(cowMutations) } : {}
1379
2105
  };
@@ -1388,6 +2114,454 @@ function normalizeCanonical(definition) {
1388
2114
  }
1389
2115
  return normalized;
1390
2116
  }
2117
+
2118
+ // src/authoring/resolved-sources.ts
2119
+ function cloneColumn({ builder, property, notNull, primaryKey: primaryKey2 }) {
2120
+ const canonical = builder.toCanonicalColumn(property);
2121
+ let clone = new ColumnBuilder(canonical.type, canonical.columnName);
2122
+ if (primaryKey2) clone = clone.primaryKey();
2123
+ if (notNull) clone = clone.notNull();
2124
+ if (canonical.description !== void 0) clone = clone.description(canonical.description);
2125
+ return clone;
2126
+ }
2127
+ function planSource(input) {
2128
+ const { sourceName, input: source, fail } = input;
2129
+ if (!IDENTIFIER_PATTERN.test(sourceName)) fail(`source name "${sourceName}" must match ${IDENTIFIER_PATTERN}`);
2130
+ switch (source.kind) {
2131
+ case "manual":
2132
+ return {
2133
+ name: sourceName,
2134
+ kind: "manual",
2135
+ columns: input.dataColumns,
2136
+ table: internalTable(input, input.dataColumns, {
2137
+ [RESOLVED_SOURCE_SYSTEM_COLUMNS.tombstoned.name]: tombstoned(),
2138
+ [RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackStatus.name]: t.text(RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackStatus.columnName),
2139
+ [RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackError.name]: t.text(RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackError.columnName),
2140
+ [RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackAt.name]: t.timestamptz(RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackAt.columnName)
2141
+ }, { revisions: true })
2142
+ };
2143
+ case "etl":
2144
+ return planEtl(input, source);
2145
+ case "prefetch":
2146
+ return planPrefetch(input, source);
2147
+ }
2148
+ }
2149
+ var tombstoned = () => t.boolean(RESOLVED_SOURCE_SYSTEM_COLUMNS.tombstoned.columnName).notNull().default(false);
2150
+ function internalTable(input, supplied, system, options = {}) {
2151
+ const { viewName, sourceName, columns, pk, pkBuilder } = input;
2152
+ const tableColumns = { [pk]: cloneColumn({ builder: pkBuilder, property: pk, notNull: true, primaryKey: true }) };
2153
+ for (const property of supplied) {
2154
+ tableColumns[property] = cloneColumn({ builder: columns[property], property, notNull: false, primaryKey: false });
2155
+ }
2156
+ return defineTable(`${viewName}__${sourceName}`, { ...tableColumns, ...system }, {
2157
+ description: `Internal ${input.input.kind} source "${sourceName}" of resolved table "${viewName}". System-maintained.`,
2158
+ cellMetadata: true,
2159
+ // The manual source identifies each edit (`c__rev`) so an async
2160
+ // confirmation clears only the exact edit it delivered.
2161
+ ...options.revisions ? { cellRevisions: true } : {},
2162
+ internal: true
2163
+ });
2164
+ }
2165
+ function assertColumns(input, list, what) {
2166
+ for (const column of list) if (!input.dataColumns.includes(column)) input.fail(`${what} names unknown column "${column}"`);
2167
+ }
2168
+ function planEtl(input, source) {
2169
+ const { sourceName, pk, dataColumns, fail } = input;
2170
+ const table = typeof source.table === "function" ? source.table() : source.table;
2171
+ if (!isSeqTable(table) || table.source === void 0) fail(`etl source "${sourceName}" must reference a defineSyncedTable`);
2172
+ const synced = table;
2173
+ if (!(pk in synced.columns)) fail(`etl source "${sourceName}" table "${synced.tableName}" has no "${pk}" column`);
2174
+ const supplied = source.columns ? [...source.columns] : dataColumns.filter((column) => column in synced.columns);
2175
+ assertColumns(input, supplied, `etl source "${sourceName}"`);
2176
+ if (source.updatedAt !== void 0 && !(source.updatedAt in synced.columns)) {
2177
+ fail(`etl source "${sourceName}" updatedAt "${source.updatedAt}" is not a column of "${synced.tableName}"`);
2178
+ }
2179
+ return { name: sourceName, kind: "etl", table: synced, columns: supplied, ...source.updatedAt ? { updatedAt: source.updatedAt } : {} };
2180
+ }
2181
+ function planPrefetch(input, source) {
2182
+ const { sourceName, pk, fail } = input;
2183
+ assertColumns(input, source.columns, `prefetch source "${sourceName}"`);
2184
+ if (source.key !== pk && !source.columns.includes(source.key)) {
2185
+ fail(`prefetch source "${sourceName}" key "${source.key}" must be the primary key or one of its columns`);
2186
+ }
2187
+ for (const column of source.writeback?.columns ?? []) {
2188
+ if (!source.columns.includes(column)) fail(`prefetch source "${sourceName}" writes back "${column}", which it does not supply`);
2189
+ }
2190
+ const writeback = source.writeback;
2191
+ return {
2192
+ name: sourceName,
2193
+ kind: "prefetch",
2194
+ columns: [...source.columns],
2195
+ key: source.key,
2196
+ function: source.function,
2197
+ fetch: source.fetch === "single" ? { shape: "single" } : { shape: "batch", maxKeys: source.fetch.batch.maxKeys },
2198
+ prefetchDefaults: {
2199
+ maxStalenessMs: parseDuration(source.prefetchDefaults?.maxStaleness ?? "30s"),
2200
+ timeoutMs: parseDuration(source.prefetchDefaults?.timeout ?? "1500ms"),
2201
+ onError: source.prefetchDefaults?.onError ?? "fallback"
2202
+ },
2203
+ ...writeback ? {
2204
+ writeback: {
2205
+ columns: [...writeback.columns],
2206
+ defaultMode: writeback.defaultMode ?? "sync",
2207
+ timeoutMs: parseDuration(writeback.timeout ?? "5s"),
2208
+ clearManualOnConfirm: writeback.clearManualOnConfirm ?? true
2209
+ }
2210
+ } : {},
2211
+ table: internalTable(input, source.columns, {
2212
+ [RESOLVED_SOURCE_SYSTEM_COLUMNS.fetchedAt.name]: t.timestamptz(RESOLVED_SOURCE_SYSTEM_COLUMNS.fetchedAt.columnName).notNull().default("now()"),
2213
+ [RESOLVED_SOURCE_SYSTEM_COLUMNS.tombstoned.name]: tombstoned()
2214
+ })
2215
+ };
2216
+ }
2217
+ function parseDuration(input) {
2218
+ const value = parseDurationMs(input);
2219
+ if (value === void 0) throw new OrmDefinitionError(`invalid duration "${input}" \u2014 use e.g. '1500ms', '30s', '5m'`);
2220
+ return value;
2221
+ }
2222
+ var PG_TYPE = {
2223
+ uuid: "uuid",
2224
+ text: "text",
2225
+ integer: "integer",
2226
+ bigint: "bigint",
2227
+ numeric: "numeric",
2228
+ double: "double precision",
2229
+ boolean: "boolean",
2230
+ timestamptz: "timestamptz",
2231
+ date: "date",
2232
+ jsonb: "jsonb"
2233
+ };
2234
+ var ident = (value) => `"${value.replace(/"/g, '""')}"`;
2235
+ var literal = (value) => `'${value.replace(/'/g, "''")}'`;
2236
+ var physical = ({ source, property }) => source.table.columns[property].columnNameFor(property);
2237
+ function candidateSql({ source, column, pk, pgType }) {
2238
+ const a = ident(`s_${source.name}`);
2239
+ const name = physical({ source, property: column });
2240
+ const value = `CAST(${a}.${ident(name)} AS ${pgType})`;
2241
+ if (source.kind === "etl") {
2242
+ return {
2243
+ source: source.name,
2244
+ value,
2245
+ has: `(${a}.${ident(physical({ source, property: pk }))} IS NOT NULL)`,
2246
+ at: source.updatedAt ? `CAST(${a}.${ident(physical({ source, property: source.updatedAt }))} AS timestamptz)` : "CAST(NULL AS timestamptz)",
2247
+ by: "CAST(NULL AS text)"
2248
+ };
2249
+ }
2250
+ const at = `${a}.${ident(cellColumnName(name, "at"))}`;
2251
+ const tombstoned2 = source.kind === "prefetch" ? ` AND NOT ${a}.${ident(RESOLVED_SOURCE_SYSTEM_COLUMNS.tombstoned.columnName)}` : "";
2252
+ return { source: source.name, value, has: `(${at} IS NOT NULL${tombstoned2})`, at, by: `${a}.${ident(cellColumnName(name, "by"))}` };
2253
+ }
2254
+ function outranks({ i, j, key, direction, inclusive }) {
2255
+ const ki = i[key];
2256
+ const kj = j[key];
2257
+ const op = inclusive ? `${direction}=` : direction;
2258
+ return inclusive ? `(${kj} IS NULL OR (${ki} IS NOT NULL AND ${ki} ${op} ${kj}))` : `(${ki} IS NOT NULL AND (${kj} IS NULL OR ${ki} ${op} ${kj}))`;
2259
+ }
2260
+ function rankedWinner({ candidates, key, direction }) {
2261
+ const branches = candidates.map((candidate, index3) => {
2262
+ const beats = candidates.map((other, otherIndex) => otherIndex === index3 ? null : `(NOT ${other.has} OR ${outranks({ i: candidate, j: other, key, direction, inclusive: index3 < otherIndex })})`).filter((clause) => clause !== null);
2263
+ return `WHEN ${[candidate.has, ...beats].join(" AND ")} THEN ${index3 + 1}`;
2264
+ });
2265
+ return `CASE ${branches.join(" ")} END`;
2266
+ }
2267
+ function winnerIndex({ comparator, candidates }) {
2268
+ const firstWith = (test) => candidates.map((candidate, index3) => `WHEN ${test(candidate)} THEN ${index3 + 1}`).join(" ");
2269
+ if (candidates.length === 1 || comparator === "priority") return `CASE ${firstWith((c) => c.has)} END`;
2270
+ switch (comparator) {
2271
+ case "latest":
2272
+ return rankedWinner({ candidates, key: "at", direction: ">" });
2273
+ case "highest":
2274
+ return rankedWinner({ candidates, key: "value", direction: ">" });
2275
+ case "lowest":
2276
+ return rankedWinner({ candidates, key: "value", direction: "<" });
2277
+ case "nonNullFirst":
2278
+ return `CASE ${firstWith((c) => `${c.has} AND ${c.value} IS NOT NULL`)} ${firstWith((c) => c.has)} END`;
2279
+ }
2280
+ }
2281
+ function pick(winner, candidates, field) {
2282
+ return `CASE ${winner} ${candidates.map((candidate, index3) => `WHEN ${index3 + 1} THEN ${field(candidate)}`).join(" ")} END`;
2283
+ }
2284
+ function loserSql({ loser, winner }) {
2285
+ return `CASE WHEN ${loser.has} AND ${loser.value} IS DISTINCT FROM ${winner.value} AND ${loser.at} > ${winner.at} THEN jsonb_build_array(jsonb_build_object('source', ${literal(loser.source)}, 'value', to_jsonb(${loser.value}), 'at', ${loser.at})) ELSE '[]'::jsonb END`;
2286
+ }
2287
+ function losersAgainst({ candidates, winner, isWinner }) {
2288
+ const losers = candidates.flatMap((loser, index3) => {
2289
+ const excluded = isWinner(loser, index3);
2290
+ if (excluded === true) return [];
2291
+ const clause = loserSql({ loser, winner });
2292
+ return [excluded === false ? clause : `CASE WHEN ${excluded} THEN '[]'::jsonb ELSE ${clause} END`];
2293
+ });
2294
+ return losers.length === 0 ? "CAST(NULL AS jsonb)" : `NULLIF(${losers.join(" || ")}, '[]'::jsonb)`;
2295
+ }
2296
+ function builtinCell({ comparator, candidates }) {
2297
+ const winner = winnerIndex({ comparator, candidates });
2298
+ const conflicts = comparator === "latest" || candidates.length < 2 ? "CAST(NULL AS jsonb)" : `CASE ${winner} ${candidates.map((known, index3) => `WHEN ${index3 + 1} THEN ${losersAgainst({ candidates, winner: known, isWinner: (_c, other) => other === index3 })}`).join(" ")} END`;
2299
+ return {
2300
+ value: pick(winner, candidates, (c) => c.value),
2301
+ parts: {
2302
+ source: pick(winner, candidates, (c) => literal(c.source)),
2303
+ at: pick(winner, candidates, (c) => c.at),
2304
+ by: pick(winner, candidates, (c) => c.by),
2305
+ conflicts
2306
+ }
2307
+ };
2308
+ }
2309
+ function customCell({ candidates, orderBy, index: index3 }) {
2310
+ const w = ident(`w_${index3}`);
2311
+ const rows = candidates.map((c, ord) => `(${literal(c.source)}, ${c.has}, ${c.value}, ${c.at}, ${c.by}, ${ord})`);
2312
+ const lateral = ` LEFT JOIN LATERAL (SELECT max(winner.source) AS source, (array_agg(winner.value))[1] AS value, max(winner.updated_at) AS updated_at, max(winner.actor) AS actor FROM (WITH cand(source, has, value, updated_at, actor, ord) AS (VALUES ${rows.join(", ")}) SELECT c.source, c.value, c.updated_at, c.actor FROM cand c WHERE c.has ORDER BY ${orderBy}, c.ord LIMIT 1) AS winner) AS ${w} ON true`;
2313
+ const winner = { value: `${w}.value`, at: `${w}.updated_at` };
2314
+ return {
2315
+ value: winner.value,
2316
+ lateral,
2317
+ parts: {
2318
+ source: `${w}.source`,
2319
+ at: winner.at,
2320
+ by: `${w}.actor`,
2321
+ conflicts: candidates.length < 2 ? "CAST(NULL AS jsonb)" : losersAgainst({ candidates, winner, isWinner: (c) => `${w}.source = ${literal(c.source)}` })
2322
+ }
2323
+ };
2324
+ }
2325
+ function buildResolvedQuery({ context, plan }) {
2326
+ const { qb, relation } = context;
2327
+ const pkColumn = plan.columns[plan.pk].columnNameFor(plan.pk);
2328
+ const alias = (source) => ident(`s_${source}`);
2329
+ const tombstoned2 = RESOLVED_SOURCE_SYSTEM_COLUMNS.tombstoned.columnName;
2330
+ const sourceByName = new Map(plan.plans.map((source) => [source.name, source]));
2331
+ const schema = pgCore.getTableConfig(relation(plan.plans[0].table)).schema;
2332
+ const keyBranches = plan.plans.map((source) => {
2333
+ const where = source.kind === "prefetch" ? drizzleOrm.sql.raw(` WHERE NOT ${ident(tombstoned2)}`) : drizzleOrm.sql.raw("");
2334
+ const sourcePk = ident(physical({ source, property: plan.pk }));
2335
+ const selectedPk = sourcePk === ident(pkColumn) ? sourcePk : `${sourcePk} AS ${ident(pkColumn)}`;
2336
+ return drizzleOrm.sql`SELECT ${drizzleOrm.sql.raw(selectedPk)} FROM ${relation(source.table)}${where}`;
2337
+ });
2338
+ const joins = plan.plans.map(
2339
+ (source) => drizzleOrm.sql` LEFT JOIN ${relation(source.table)} AS ${drizzleOrm.sql.raw(alias(source.name))} ON ${drizzleOrm.sql.raw(`${alias(source.name)}.${ident(physical({ source, property: plan.pk }))} = "k".${ident(pkColumn)}`)}`
2340
+ );
2341
+ const cells = /* @__PURE__ */ new Map();
2342
+ plan.rules.forEach((rule, index3) => {
2343
+ const canonical = plan.columns[rule.column].toCanonicalColumn(rule.column);
2344
+ const pgType = typeof canonical.type === "string" ? PG_TYPE[canonical.type] : `${schema === void 0 ? "" : `${ident(schema)}.`}${ident(canonical.type.enum)}`;
2345
+ const candidates = rule.candidates.map((name) => candidateSql({ source: sourceByName.get(name), column: rule.column, pk: plan.pk, pgType }));
2346
+ cells.set(rule.column, rule.comparator === "custom" ? customCell({ candidates, orderBy: rule.orderBy, index: index3 }) : builtinCell({ comparator: rule.comparator, candidates }));
2347
+ });
2348
+ const laterals = [...cells.values()].flatMap((cell) => cell.lateral ? [drizzleOrm.sql.raw(cell.lateral)] : []);
2349
+ const from = drizzleOrm.sql.join([drizzleOrm.sql`(${drizzleOrm.sql.join(keyBranches, drizzleOrm.sql.raw(" UNION "))}) AS "k"`, ...joins, ...laterals], drizzleOrm.sql.raw(""));
2350
+ const selected = {
2351
+ [plan.pk]: drizzleOrm.sql.raw(`"k".${ident(pkColumn)}`).as(pkColumn)
2352
+ };
2353
+ for (const [property, builder] of Object.entries(plan.columns)) {
2354
+ if (property === plan.pk) continue;
2355
+ const cell = cells.get(property);
2356
+ const columnName = builder.columnNameFor(property);
2357
+ selected[property] = drizzleOrm.sql.raw(cell.value).as(columnName);
2358
+ for (const [part, expression] of Object.entries(cell.parts)) {
2359
+ selected[cellColumnName(property, part)] = drizzleOrm.sql.raw(expression).as(cellColumnName(columnName, part));
2360
+ }
2361
+ }
2362
+ const manual = plan.plans.find((source) => source.kind === "manual");
2363
+ const system = RESOLVED_VIEW_SYSTEM_COLUMNS;
2364
+ selected[system.writebackStatus.name] = drizzleOrm.sql.raw(`${alias(manual.name)}.${ident(RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackStatus.columnName)}`).as(system.writebackStatus.columnName);
2365
+ selected[system.writebackError.name] = drizzleOrm.sql.raw(`${alias(manual.name)}.${ident(RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackError.columnName)}`).as(system.writebackError.columnName);
2366
+ const hidden = plan.deletedBy.map((name) => `NOT COALESCE(${alias(name)}.${ident(tombstoned2)}, false)`);
2367
+ const query = qb.select(selected).from(from);
2368
+ return hidden.length > 0 ? query.where(drizzleOrm.sql.raw(hidden.join(" AND "))) : query;
2369
+ }
2370
+ function managedFn(id) {
2371
+ return { kind: "managed", id };
2372
+ }
2373
+ function manualSource() {
2374
+ return { kind: "manual" };
2375
+ }
2376
+ function etlSource(input) {
2377
+ return { kind: "etl", ...input };
2378
+ }
2379
+ function prefetchSource(input) {
2380
+ return { kind: "prefetch", ...input };
2381
+ }
2382
+ var latest = (candidates) => ({ kind: "latest", candidates });
2383
+ var highest = (candidates) => ({ kind: "highest", candidates });
2384
+ var lowest = (candidates) => ({ kind: "lowest", candidates });
2385
+ var priority = (candidates) => ({ kind: "priority", candidates });
2386
+ var nonNullFirst = (candidates) => ({ kind: "nonNullFirst", candidates });
2387
+ var custom = (candidates, orderBy) => ({ kind: "custom", candidates, orderBy });
2388
+ var SYSTEM_VIEW_COLUMNS = {
2389
+ [RESOLVED_VIEW_SYSTEM_COLUMNS.writebackStatus.name]: () => t.text(RESOLVED_VIEW_SYSTEM_COLUMNS.writebackStatus.columnName),
2390
+ [RESOLVED_VIEW_SYSTEM_COLUMNS.writebackError.name]: () => t.text(RESOLVED_VIEW_SYSTEM_COLUMNS.writebackError.columnName)
2391
+ };
2392
+ function defineResolvedTable(name, columns, config) {
2393
+ const file = activeAuthoringContext()?.currentFile;
2394
+ const fail = (message) => {
2395
+ throw new OrmDefinitionError(`resolved table "${name}": ${message}`, file);
2396
+ };
2397
+ if (!IDENTIFIER_PATTERN.test(name)) fail(`name must match ${IDENTIFIER_PATTERN}`);
2398
+ for (const property of Object.keys(columns)) {
2399
+ if (RESERVED_RESOLVED_COLUMN_NAMES.has(property) || RESERVED_RESOLVED_COLUMN_NAMES.has(columns[property].columnNameFor(property))) {
2400
+ fail(`column "${property}" collides with a reserved system column`);
2401
+ }
2402
+ }
2403
+ for (const property of Object.keys(columns)) {
2404
+ if (columns[property].columnNameFor(property).length > MAX_TRACKED_COLUMN_NAME) {
2405
+ fail(`column "${property}" is too long for a resolved table (max ${MAX_TRACKED_COLUMN_NAME} characters, room for cell parts)`);
2406
+ }
2407
+ }
2408
+ const pkEntries = Object.entries(columns).filter(([, builder]) => builder.isPrimaryKey);
2409
+ if (pkEntries.length !== 1) fail("declare exactly one .primaryKey() column");
2410
+ const [pk, pkBuilder] = pkEntries[0];
2411
+ const dataColumns = Object.keys(columns).filter((property) => property !== pk);
2412
+ if (dataColumns.length === 0) fail("declare at least one non-key column");
2413
+ const sourceEntries = Object.entries(config.sources);
2414
+ if (sourceEntries.filter(([, source]) => source.kind === "manual").length !== 1) fail("declare exactly one manualSource()");
2415
+ const plans = sourceEntries.map(
2416
+ ([sourceName, input]) => planSource({ viewName: name, sourceName, input, columns, pk, pkBuilder, dataColumns, fail })
2417
+ );
2418
+ const rules = planRules({ config, plans, dataColumns, fail });
2419
+ const deletedBy = [...config.rows?.deletedBy ?? []];
2420
+ for (const source of deletedBy) {
2421
+ if (plans.find((plan) => plan.name === source)?.kind !== "manual") fail(`rows.deletedBy "${source}" must be the manual source`);
2422
+ }
2423
+ const partType = { source: t.text, at: t.timestamptz, by: t.text, conflicts: t.jsonb };
2424
+ const viewColumns = {};
2425
+ for (const [property, builder] of Object.entries(columns)) {
2426
+ viewColumns[property] = cloneColumn({ builder, property, notNull: property === pk, primaryKey: property === pk });
2427
+ if (property === pk) continue;
2428
+ const columnName = builder.columnNameFor(property);
2429
+ for (const part of RESOLVED_VIEW_CELL_PARTS) {
2430
+ viewColumns[cellColumnName(property, part)] = partType[part](cellColumnName(columnName, part)).cellPart(property, part);
2431
+ }
2432
+ }
2433
+ for (const [property, make] of Object.entries(SYSTEM_VIEW_COLUMNS)) viewColumns[property] = make();
2434
+ const { read, ...writePolicies } = config.policies;
2435
+ const resolved = {
2436
+ internalTables: plans.filter((plan) => plan.kind !== "etl").map((plan) => plan.table),
2437
+ sources: plans,
2438
+ rules,
2439
+ deletedBy,
2440
+ policies: writePolicies
2441
+ };
2442
+ const queryPlan = { columns, pk, plans, rules, deletedBy };
2443
+ const typedColumns = viewColumns;
2444
+ const surface = {
2445
+ ...config.description !== void 0 ? { description: config.description } : {},
2446
+ ...config.links !== void 0 ? { links: config.links } : {},
2447
+ policies: { read }
2448
+ };
2449
+ if (!config.materialize) {
2450
+ return defineView(name, typedColumns, { ...surface, query: (context) => buildResolvedQuery({ context, plan: queryPlan }), resolved });
2451
+ }
2452
+ const materialize = config.materialize === true ? {} : config.materialize;
2453
+ const computeName = `${name}__compute`;
2454
+ const tableName = `${name}__materialized`;
2455
+ const compute = defineView(computeName, typedColumns, {
2456
+ policies: { read },
2457
+ query: (context) => buildResolvedQuery({ context, plan: queryPlan }),
2458
+ internal: true
2459
+ });
2460
+ const table = defineTable(tableName, viewColumns, {
2461
+ description: `Materialized rows of resolved table "${name}". System-maintained from "${computeName}".`,
2462
+ internal: true,
2463
+ materializes: name,
2464
+ ...materialize.indexes !== void 0 ? { indexes: materialize.indexes } : {}
2465
+ });
2466
+ return defineView(name, typedColumns, {
2467
+ ...surface,
2468
+ query: (context) => {
2469
+ const handle = context.relation(table);
2470
+ const selected = Object.fromEntries(Object.entries(viewColumns).map(([property, builder]) => [
2471
+ property,
2472
+ drizzleOrm.sql`${handle[property]}`.as(builder.columnNameFor(property))
2473
+ ]));
2474
+ return context.qb.select(selected).from(context.relation(table));
2475
+ },
2476
+ resolved: {
2477
+ ...resolved,
2478
+ internalTables: [...resolved.internalTables, table],
2479
+ internalViews: [compute],
2480
+ materialized: { table: tableName, compute: computeName }
2481
+ }
2482
+ });
2483
+ }
2484
+ function planRules({ config, plans, dataColumns, fail }) {
2485
+ const kindOrder = { manual: 0, prefetch: 1, etl: 2 };
2486
+ const fallback = config.default ?? priority([...plans].sort((a, b) => kindOrder[a.kind] - kindOrder[b.kind]).map((plan) => plan.name));
2487
+ for (const column of Object.keys(config.resolve ?? {})) {
2488
+ if (!dataColumns.includes(column)) fail(`resolve names unknown column "${column}"`);
2489
+ }
2490
+ return dataColumns.map((column) => {
2491
+ const explicit = config.resolve?.[column];
2492
+ const comparator = explicit ?? fallback;
2493
+ const candidates = explicit ? [...explicit.candidates] : comparator.candidates.filter((name) => plans.find((plan) => plan.name === name)?.columns.includes(column));
2494
+ for (const candidate of candidates) {
2495
+ const plan = plans.find((entry) => entry.name === candidate);
2496
+ if (!plan) fail(`column "${column}" names unknown source "${candidate}"`);
2497
+ else if (!plan.columns.includes(column)) fail(`column "${column}" candidate "${candidate}" does not supply it`);
2498
+ }
2499
+ if (candidates.length === 0) fail(`column "${column}" has no candidate source`);
2500
+ if (comparator.kind === "custom") assertSafeOrderBy({ orderBy: comparator.orderBy ?? "", column, fail });
2501
+ return {
2502
+ column,
2503
+ comparator: comparator.kind,
2504
+ candidates,
2505
+ ...comparator.kind === "custom" && comparator.orderBy !== void 0 ? { orderBy: comparator.orderBy } : {}
2506
+ };
2507
+ });
2508
+ }
2509
+ var ORDER_BY_KEYWORDS = /* @__PURE__ */ new Set([
2510
+ "value",
2511
+ "updated_at",
2512
+ "source",
2513
+ "actor",
2514
+ "ord",
2515
+ "asc",
2516
+ "desc",
2517
+ "nulls",
2518
+ "first",
2519
+ "last",
2520
+ "case",
2521
+ "when",
2522
+ "then",
2523
+ "else",
2524
+ "end",
2525
+ "and",
2526
+ "or",
2527
+ "not",
2528
+ "is",
2529
+ "null",
2530
+ "true",
2531
+ "false",
2532
+ "in",
2533
+ "distinct",
2534
+ "from",
2535
+ "coalesce",
2536
+ "nullif",
2537
+ "greatest",
2538
+ "least",
2539
+ "text",
2540
+ "numeric",
2541
+ "integer",
2542
+ "bigint",
2543
+ "boolean",
2544
+ "timestamptz",
2545
+ "date",
2546
+ "jsonb",
2547
+ "interval"
2548
+ ]);
2549
+ var ALLOWED_FUNCTIONS = new Set(VIEW_QUERY_FUNCTION_ALLOWLIST);
2550
+ var ORDER_BY_TOKEN = /\s+|'(?:[^']|'')*'|\d+(?:\.\d+)?|[a-z_][a-z0-9_]*|::|<=|>=|<>|!=|[-+*/%<>=(),|]/giy;
2551
+ function assertSafeOrderBy({ orderBy, column, fail }) {
2552
+ ORDER_BY_TOKEN.lastIndex = 0;
2553
+ let consumed = 0;
2554
+ for (let match = ORDER_BY_TOKEN.exec(orderBy); match; match = ORDER_BY_TOKEN.exec(orderBy)) {
2555
+ consumed = ORDER_BY_TOKEN.lastIndex;
2556
+ const token = match[0].toLowerCase();
2557
+ if (/^[a-z_]/.test(token) && !ORDER_BY_KEYWORDS.has(token) && !ALLOWED_FUNCTIONS.has(token)) {
2558
+ fail(`column "${column}" custom orderBy uses "${match[0]}" \u2014 only value, updated_at, source, actor, ord, SQL keywords and allowlisted functions`);
2559
+ }
2560
+ }
2561
+ if (consumed !== orderBy.length || orderBy.trim() === "") {
2562
+ fail(`column "${column}" custom orderBy contains an unsupported character near "${orderBy.slice(consumed, consumed + 10)}"`);
2563
+ }
2564
+ }
1391
2565
  function stableJson(value) {
1392
2566
  if (value === null || typeof value !== "object") return JSON.stringify(value);
1393
2567
  if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
@@ -1494,8 +2668,19 @@ function defineNamespace(input) {
1494
2668
  return config;
1495
2669
  }
1496
2670
  function serializeNamespace(input) {
1497
- const { config, tables } = input;
1498
- const views = input.views ?? [];
2671
+ const { config } = input;
2672
+ const views = [...input.views ?? []];
2673
+ const tables = [...input.tables];
2674
+ for (const view of views) {
2675
+ for (const internal of view.config.resolved?.internalTables ?? []) {
2676
+ if (!tables.includes(internal)) tables.push(internal);
2677
+ }
2678
+ }
2679
+ for (const view of [...views]) {
2680
+ for (const internal of view.config.resolved?.internalViews ?? []) {
2681
+ if (!views.includes(internal)) views.push(internal);
2682
+ }
2683
+ }
1499
2684
  const enums = input.enums ?? [];
1500
2685
  const tablesByName = /* @__PURE__ */ new Map();
1501
2686
  for (const table of tables) {
@@ -1597,6 +2782,9 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
1597
2782
  if (table.config.renamedFrom !== void 0) candidate.renamedFrom = table.config.renamedFrom;
1598
2783
  if (table.source !== void 0) candidate.source = table.source;
1599
2784
  if (table.source !== void 0) {
2785
+ if (table.config.checks?.length) {
2786
+ throw new OrmDefinitionError(`synced table "${table.tableName}" is system-owned; checks are not allowed`, table.file);
2787
+ }
1600
2788
  if (table.config.indexes !== void 0 && table.config.indexes.length > 0) {
1601
2789
  throw new OrmDefinitionError(
1602
2790
  `synced table "${table.tableName}" is system-owned; indexes are not allowed`,
@@ -1634,6 +2822,11 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
1634
2822
  if (builder.isPrimaryKey) columnLevelPk.push(column.columnName);
1635
2823
  candidate.columns?.push(column);
1636
2824
  }
2825
+ if (table.config.internal === true) candidate.internal = true;
2826
+ if (table.config.materializes !== void 0) candidate.materializes = table.config.materializes;
2827
+ if (table.config.cellMetadata === true && table.source !== void 0) {
2828
+ throw new OrmDefinitionError(`synced table "${table.tableName}" cannot track cell metadata \u2014 its DDL is owned by the sync`, table.file);
2829
+ }
1637
2830
  if (table.config.primaryKey !== void 0) {
1638
2831
  if (columnLevelPk.length > 0) {
1639
2832
  throw new OrmDefinitionError(
@@ -1647,6 +2840,45 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
1647
2840
  } else if (columnLevelPk.length > 0) {
1648
2841
  candidate.primaryKey = columnLevelPk;
1649
2842
  }
2843
+ if (table.config.cellMetadata === true) {
2844
+ candidate.cellMetadata = true;
2845
+ const parts = table.config.cellRevisions === true ? ["at", "by", "rev"] : ["at", "by"];
2846
+ const tracked = trackedCellColumns({ columns: candidate.columns ?? [], primaryKey: candidate.primaryKey });
2847
+ for (const column of tracked) {
2848
+ for (const part of parts) {
2849
+ candidate.columns?.push({
2850
+ name: cellColumnName(column.name, part),
2851
+ columnName: cellColumnName(column.columnName, part),
2852
+ type: CELL_PART_TYPES[part],
2853
+ nullable: true,
2854
+ unique: false,
2855
+ deprecated: false,
2856
+ cell: { of: column.name, part }
2857
+ });
2858
+ }
2859
+ }
2860
+ }
2861
+ if (table.config.foreignKeys?.length) {
2862
+ if (table.source) throw new OrmDefinitionError(`synced table "${table.tableName}" cannot enforce foreign keys`, table.file);
2863
+ candidate.foreignKeys = table.config.foreignKeys.map((fk) => {
2864
+ const target = resolveRelationRef({ ref: fk.target, fail: (message) => {
2865
+ throw new OrmDefinitionError(message, table.file);
2866
+ } });
2867
+ if (!isSeqTable(target) || !relationsByName.has(target.tableName)) throw new OrmDefinitionError("foreign key target must be a table in this namespace", table.file);
2868
+ return {
2869
+ name: fk.name,
2870
+ columns: fk.columns.map((ref) => resolveColumnRef({ relation: table, ref, role: `foreign key "${fk.name}" column` })),
2871
+ target: target.tableName,
2872
+ references: fk.references.map((ref) => resolveColumnRef({ relation: target, ref, role: `foreign key "${fk.name}" reference` })),
2873
+ ...fk.deferrable ? { deferrable: fk.deferrable } : {},
2874
+ ...fk.onDelete ? { onDelete: fk.onDelete } : {},
2875
+ ...fk.onUpdate ? { onUpdate: fk.onUpdate } : {}
2876
+ };
2877
+ });
2878
+ }
2879
+ if (table.config.checks?.length) {
2880
+ candidate.checks = table.config.checks.map((check) => ({ name: check.name, expression: check.expression }));
2881
+ }
1650
2882
  for (const index3 of table.config.indexes ?? []) {
1651
2883
  const entry = {
1652
2884
  columns: index3.columns.map((ref) => resolveColumnRef({ relation: table, ref, role: "index" }))
@@ -1699,6 +2931,12 @@ function buildCanonicalView({
1699
2931
  if (column.default !== void 0 || column.defaultFn !== void 0) {
1700
2932
  throw new OrmDefinitionError(`view "${view.viewName}": column "${propertyName}" cannot declare a default`, view.file);
1701
2933
  }
2934
+ if (column.onUpdateFn !== void 0) {
2935
+ throw new OrmDefinitionError(
2936
+ `view "${view.viewName}": column "${propertyName}" cannot declare onUpdateFn`,
2937
+ view.file
2938
+ );
2939
+ }
1702
2940
  if (column.unique) {
1703
2941
  throw new OrmDefinitionError(`view "${view.viewName}": column "${propertyName}" cannot declare unique`, view.file);
1704
2942
  }
@@ -1736,7 +2974,33 @@ function buildCanonicalView({
1736
2974
  columns,
1737
2975
  ...primaryKey2 !== void 0 ? { primaryKey: primaryKey2 } : {},
1738
2976
  links,
1739
- policies: { read: toCanonicalPolicy(view.config.policies.read) }
2977
+ policies: { read: toCanonicalPolicy(view.config.policies.read) },
2978
+ ...view.config.resolved !== void 0 ? { resolved: buildCanonicalResolved(view.config.resolved) } : {},
2979
+ ...view.config.internal === true ? { internal: true } : {}
2980
+ };
2981
+ }
2982
+ function buildCanonicalResolved(resolved) {
2983
+ const policies = {};
2984
+ if (resolved.policies.write !== void 0) policies.write = toCanonicalPolicy(resolved.policies.write);
2985
+ if (resolved.policies.insert !== void 0) policies.insert = toCanonicalPolicy(resolved.policies.insert);
2986
+ if (resolved.policies.update !== void 0) policies.update = toCanonicalPolicy(resolved.policies.update);
2987
+ return {
2988
+ sources: resolved.sources.map((source) => ({
2989
+ name: source.name,
2990
+ kind: source.kind,
2991
+ table: source.table.tableName,
2992
+ columns: [...source.columns],
2993
+ ...source.updatedAt !== void 0 ? { updatedAt: source.updatedAt } : {},
2994
+ ...source.key !== void 0 ? { key: source.key } : {},
2995
+ ...source.fetch !== void 0 ? { fetch: source.fetch } : {},
2996
+ ...source.function !== void 0 ? { function: { ...source.function } } : {},
2997
+ ...source.prefetchDefaults !== void 0 ? { prefetchDefaults: { ...source.prefetchDefaults } } : {},
2998
+ ...source.writeback !== void 0 ? { writeback: { ...source.writeback, columns: [...source.writeback.columns] } } : {}
2999
+ })),
3000
+ resolve: resolved.rules.map((rule) => ({ ...rule, candidates: [...rule.candidates] })),
3001
+ deletedBy: [...resolved.deletedBy],
3002
+ ...resolved.materialized ? { materialized: { ...resolved.materialized } } : {},
3003
+ policies
1740
3004
  };
1741
3005
  }
1742
3006
  function buildCanonicalLink({
@@ -1753,6 +3017,9 @@ function buildCanonicalLink({
1753
3017
  if (link.enforce && link.linkKind !== "toOne") {
1754
3018
  return fail("sets enforce: true, which is only supported on toOne links");
1755
3019
  }
3020
+ if ((link.onDelete !== void 0 || link.onUpdate !== void 0) && !link.enforce) {
3021
+ return fail("sets a referential action without enforce: true");
3022
+ }
1756
3023
  if (link.linkKind === "toPlatformUser") {
1757
3024
  if (link.target !== void 0) return fail("is toPlatformUser and must not declare a target table");
1758
3025
  return {
@@ -1781,6 +3048,8 @@ function buildCanonicalLink({
1781
3048
  on: resolveColumnRef({ relation: owningSide, ref: link.on, role: `link "${linkName}" on` }),
1782
3049
  ...link.references !== void 0 ? { references: resolveColumnRef({ relation: referencedSide, ref: link.references, role: `link "${linkName}" references` }) } : {},
1783
3050
  enforce: link.enforce,
3051
+ ...link.onDelete !== void 0 ? { onDelete: link.onDelete } : {},
3052
+ ...link.onUpdate !== void 0 ? { onUpdate: link.onUpdate } : {},
1784
3053
  ...link.description !== void 0 ? { description: link.description } : {}
1785
3054
  };
1786
3055
  }
@@ -1959,6 +3228,10 @@ async function loadNamespaceFromDir(dir, options = {}) {
1959
3228
  fsCache: false,
1960
3229
  ...options.ormModulePath !== void 0 ? {
1961
3230
  alias: {
3231
+ // The public /drizzle barrel re-exports this pinned dependency.
3232
+ // Explicitly map it before the root alias: otherwise jiti appends
3233
+ // "/drizzle" to the overridden module's filename.
3234
+ "@sequenceholdings/orm/drizzle": module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))).resolve("drizzle-orm"),
1962
3235
  "@sequenceholdings/orm": options.ormModulePath,
1963
3236
  "@sequenceholdings/orm/authoring": options.ormModulePath
1964
3237
  }
@@ -2230,7 +3503,7 @@ var OPERATOR_PATTERN = /^(!=|<=|>=|<>|=|<|>)/;
2230
3503
  var IDENTIFIER_START = /[a-z_]/;
2231
3504
  var IDENTIFIER_CHAR = /[a-z0-9_.-]/;
2232
3505
  var NUMBER_PATTERN = /^-?\d+(\.\d+)?/;
2233
- function tokenize(source) {
3506
+ function tokenize2(source) {
2234
3507
  const tokens = [];
2235
3508
  let position = 0;
2236
3509
  while (position < source.length) {
@@ -2304,7 +3577,7 @@ function readString(source, start) {
2304
3577
  }
2305
3578
  throw new PredicateParseError("unterminated string literal", start);
2306
3579
  }
2307
- var Parser = class {
3580
+ var Parser2 = class {
2308
3581
  constructor(tokens) {
2309
3582
  this.tokens = tokens;
2310
3583
  }
@@ -2436,7 +3709,7 @@ var Parser = class {
2436
3709
  function parsePredicate(source) {
2437
3710
  const trimmed = source.trim();
2438
3711
  if (trimmed.length === 0) throw new PredicateParseError("empty predicate", 0);
2439
- return { source: trimmed, ast: new Parser(tokenize(trimmed)).parse() };
3712
+ return { source: trimmed, ast: new Parser2(tokenize2(trimmed)).parse() };
2440
3713
  }
2441
3714
 
2442
3715
  // src/schema/predicates.ts
@@ -2521,8 +3794,8 @@ var actionSchema = zod.z.strictObject({
2521
3794
  }
2522
3795
  }
2523
3796
  });
2524
- function extractNamedParams(sql4) {
2525
- const withoutStrings = sql4.replace(QUOTED_STRING_PATTERN, "''");
3797
+ function extractNamedParams(sql7) {
3798
+ const withoutStrings = sql7.replace(QUOTED_STRING_PATTERN, "''");
2526
3799
  const names = [...withoutStrings.matchAll(NAMED_PARAM_PATTERN)].map((match) => match[1]);
2527
3800
  return [...new Set(names)];
2528
3801
  }
@@ -2589,17 +3862,6 @@ var SQL_TYPE_NAMES = {
2589
3862
  };
2590
3863
  var sqlTypeName = (type) => SQL_TYPE_NAMES[type] ?? type;
2591
3864
 
2592
- // src/compiler/sql.ts
2593
- var CLAIMS_SETTING = "request.jwt.claims";
2594
- var CAPABILITIES_CLAIM_KEY = "capabilities";
2595
- var ROLES_CLAIM_KEY = "roles";
2596
- var quoteIdentifier = (name) => `"${name.replaceAll('"', '""')}"`;
2597
- var quoteLiteral = (value) => `'${value.replaceAll("'", "''")}'`;
2598
- var CLAIMS_SOURCE = `nullif(current_setting('${CLAIMS_SETTING}', true), '')::jsonb`;
2599
- var claimExpression = (key) => `(${CLAIMS_SOURCE} ->> ${quoteLiteral(key)})`;
2600
- var capabilityExpression = (key) => `coalesce(${CLAIMS_SOURCE} -> '${CAPABILITIES_CLAIM_KEY}' ? ${quoteLiteral(key)}, false)`;
2601
- var roleExpression = (key) => `coalesce(${CLAIMS_SOURCE} -> '${ROLES_CLAIM_KEY}' ? ${quoteLiteral(key)}, false)`;
2602
-
2603
3865
  // src/compiler/predicates.ts
2604
3866
  var SQL_COMPARE_OPS = {
2605
3867
  "=": "=",
@@ -2679,8 +3941,8 @@ var actionParamArg = (name) => `p_${name}`;
2679
3941
  function compileActionStatements(action, renderParam = actionParamArg) {
2680
3942
  return actionStatementSources(action.executes).map((statement) => compileStatement(statement, renderParam));
2681
3943
  }
2682
- function compileStatement(sql4, renderParam) {
2683
- const compiled = sql4.replace(TOKEN_PATTERN, (token, claimKey, capabilityKey, paramName) => {
3944
+ function compileStatement(sql7, renderParam) {
3945
+ const compiled = sql7.replace(TOKEN_PATTERN, (token, claimKey, capabilityKey, paramName) => {
2684
3946
  if (claimKey !== void 0) {
2685
3947
  if (!CLAIM_KEY_PATTERN.test(claimKey)) throw new OrmDefinitionError(`invalid claim key in executes: ${token}`);
2686
3948
  return claimExpression(claimKey);
@@ -2745,10 +4007,10 @@ function physicalColumn(logical, options) {
2745
4007
  return options.columnNames?.[logical] ?? logical;
2746
4008
  }
2747
4009
  function compileCmp(op, column, value, options) {
2748
- const physical = physicalColumn(column, options);
2749
- const declared = options.columnTypes?.[physical] ?? options.columnTypes?.[column];
4010
+ const physical2 = physicalColumn(column, options);
4011
+ const declared = options.columnTypes?.[physical2] ?? options.columnTypes?.[column];
2750
4012
  const isEnum = typeof declared === "object" && declared !== null;
2751
- const col = isEnum ? `${quoteIdentifier(physical)}::text` : quoteIdentifier(physical);
4013
+ const col = isEnum ? `${quoteIdentifier(physical2)}::text` : quoteIdentifier(physical2);
2752
4014
  const columnType = isEnum ? "text" : declared;
2753
4015
  switch (op) {
2754
4016
  case "eq":
@@ -2792,11 +4054,11 @@ function compileEquality(col, value, columnType, negated, options) {
2792
4054
  function compileValue(value, columnType, _options) {
2793
4055
  switch (value.kind) {
2794
4056
  case "literal": {
2795
- const literal = value.value;
2796
- if (literal === null) return "NULL";
2797
- if (typeof literal === "string") return quoteLiteral(literal);
2798
- if (typeof literal === "boolean") return literal ? "TRUE" : "FALSE";
2799
- return String(literal);
4057
+ const literal2 = value.value;
4058
+ if (literal2 === null) return "NULL";
4059
+ if (typeof literal2 === "string") return quoteLiteral(literal2);
4060
+ if (typeof literal2 === "boolean") return literal2 ? "TRUE" : "FALSE";
4061
+ return String(literal2);
2800
4062
  }
2801
4063
  case "claim": {
2802
4064
  const expression = claimExpression(value.key);
@@ -2845,8 +4107,8 @@ function actionPrepareChecks(definition) {
2845
4107
  const order = actionParamOrder(action);
2846
4108
  const signature = order.length > 0 ? ` (${order.map((param) => sqlTypeName(action.params[param].type)).join(", ")})` : "";
2847
4109
  const compiled = compileActionStatements(action, (param) => `$${order.indexOf(param) + 1}`);
2848
- compiled.forEach((sql4, index3) => {
2849
- const checked = action.returns && index3 === compiled.length - 1 ? returnProjection(action.returns, sql4) : sql4;
4110
+ compiled.forEach((sql7, index3) => {
4111
+ const checked = action.returns && index3 === compiled.length - 1 ? returnProjection(action.returns, sql7) : sql7;
2850
4112
  statements.push(`PREPARE ${quoteIdentifier(`orm_check_${name}_${index3}`)}${signature} AS ${checked}`);
2851
4113
  });
2852
4114
  }
@@ -3228,14 +4490,14 @@ function comparisonTypeIssue(left, right, columns) {
3228
4490
  return null;
3229
4491
  }
3230
4492
  const column = left.kind === "column" ? left : right.kind === "column" ? right : null;
3231
- const literal = left.kind === "literal" ? left : right.kind === "literal" ? right : null;
3232
- if (!column || !literal || literal.value === null) return null;
4493
+ const literal2 = left.kind === "literal" ? left : right.kind === "literal" ? right : null;
4494
+ if (!column || !literal2 || literal2.value === null) return null;
3233
4495
  const columnGroup = groupOf(column);
3234
4496
  if (!columnGroup) return null;
3235
- if (typeof literal.value === "string") return null;
3236
- const literalGroup = typeof literal.value === "number" ? "numeric" : "boolean";
4497
+ if (typeof literal2.value === "string") return null;
4498
+ const literalGroup = typeof literal2.value === "number" ? "numeric" : "boolean";
3237
4499
  if (literalGroup !== columnGroup) {
3238
- return `policy compares '${column.name}' (${columns[column.name].type}) with a ${typeof literal.value} literal \u2014 incomparable types`;
4500
+ return `policy compares '${column.name}' (${columns[column.name].type}) with a ${typeof literal2.value} literal \u2014 incomparable types`;
3239
4501
  }
3240
4502
  return null;
3241
4503
  }
@@ -3300,7 +4562,8 @@ function syncedViewStatements({
3300
4562
  readPolicyIrByTable,
3301
4563
  columnNamesByTable,
3302
4564
  columnTypesByTable,
3303
- executionOwnerRole = SYNCED_VIEW_EXECUTION_OWNER_ROLE
4565
+ executionOwnerRole = SYNCED_VIEW_EXECUTION_OWNER_ROLE,
4566
+ backingPrivileges = "grant"
3304
4567
  }) {
3305
4568
  const schema = quoteIdentifier(ormSchemaName(definition.namespace));
3306
4569
  const role = quoteIdentifier(consumerRoleName(definition.namespace));
@@ -3320,8 +4583,8 @@ function syncedViewStatements({
3320
4583
  const backing = backingMap.get(table.table);
3321
4584
  if (!backing) continue;
3322
4585
  const viewName = `${schema}.${quoteIdentifier(table.table)}`;
3323
- const physical = splitPhysicalRelation(backing.physicalRelation);
3324
- const physicalTarget = `${quoteIdentifier(physical.schema)}.${quoteIdentifier(physical.table)}`;
4586
+ const physical2 = splitPhysicalRelation(backing.physicalRelation);
4587
+ const physicalTarget = `${quoteIdentifier(physical2.schema)}.${quoteIdentifier(physical2.table)}`;
3325
4588
  const columnTypes = mergeSyncedViewColumnTypes({
3326
4589
  table,
3327
4590
  canonicalColumnTypes: columnTypesByTable?.get(table.table),
@@ -3334,8 +4597,10 @@ function syncedViewStatements({
3334
4597
  columnTypes,
3335
4598
  columnNames: columnNamesByTable?.get(table.table)
3336
4599
  })}` : readPolicy ? ` WHERE ${compilePredicate(readPolicy.ast, narrowToV1ColumnTypes(columnTypes))}` : " WHERE false";
3337
- statements.push(`GRANT USAGE ON SCHEMA ${quoteIdentifier(physical.schema)} TO ${owner}`);
3338
- statements.push(`GRANT SELECT ON ${physicalTarget} TO ${owner}`);
4600
+ if (backingPrivileges === "grant") {
4601
+ statements.push(`GRANT USAGE ON SCHEMA ${quoteIdentifier(physical2.schema)} TO ${owner}`);
4602
+ statements.push(`GRANT SELECT ON ${physicalTarget} TO ${owner}`);
4603
+ }
3339
4604
  statements.push(
3340
4605
  `CREATE OR REPLACE VIEW ${viewName} AS SELECT * FROM ${physicalTarget}${whereClause}`
3341
4606
  );
@@ -3354,10 +4619,10 @@ function mergeSyncedViewColumnTypes({
3354
4619
  if (def?.type) fromTable[name] = def.type;
3355
4620
  }
3356
4621
  if (columnNames) {
3357
- for (const [logical, physical] of Object.entries(columnNames)) {
3358
- const declared = fromTable[physical] ?? fromTable[logical];
4622
+ for (const [logical, physical2] of Object.entries(columnNames)) {
4623
+ const declared = fromTable[physical2] ?? fromTable[logical];
3359
4624
  if (!declared) continue;
3360
- fromTable[physical] = declared;
4625
+ fromTable[physical2] = declared;
3361
4626
  fromTable[logical] = declared;
3362
4627
  }
3363
4628
  }
@@ -3396,8 +4661,8 @@ function syncedBackingRevokeStatements({
3396
4661
  const seen = /* @__PURE__ */ new Set();
3397
4662
  const statements = [];
3398
4663
  for (const backing of backings) {
3399
- const physical = splitPhysicalRelation(backing.physicalRelation);
3400
- const physicalTarget = `${quoteIdentifier(physical.schema)}.${quoteIdentifier(physical.table)}`;
4664
+ const physical2 = splitPhysicalRelation(backing.physicalRelation);
4665
+ const physicalTarget = `${quoteIdentifier(physical2.schema)}.${quoteIdentifier(physical2.table)}`;
3401
4666
  if (seen.has(physicalTarget)) continue;
3402
4667
  seen.add(physicalTarget);
3403
4668
  statements.push(`REVOKE SELECT ON ${physicalTarget} FROM ${owner}`);
@@ -3607,6 +4872,90 @@ function snapshotTableNames(snapshot, schema) {
3607
4872
  const tables = snapshot.tables ?? {};
3608
4873
  return Object.values(tables).filter((entry) => entry.schema === schema).map((entry) => entry.name);
3609
4874
  }
4875
+
4876
+ // src/engine/materialized-refresh.ts
4877
+ function materializedRefreshFunctions(canonical) {
4878
+ const schema = ormSchemaName(canonical.namespace);
4879
+ const functions = /* @__PURE__ */ new Map();
4880
+ for (const view of canonical.views ?? []) {
4881
+ const materialized = view.resolved?.materialized;
4882
+ if (!materialized) continue;
4883
+ const table = canonical.tables.find((candidate) => candidate.table === materialized.table);
4884
+ const pk = view.primaryKey ?? [];
4885
+ if (!table || pk.length === 0) {
4886
+ throw new OrmDefinitionError(`materialized resolved table '${view.view}' needs its table and primary key`);
4887
+ }
4888
+ functions.set(view.view, refreshFunctionSql({
4889
+ schema,
4890
+ name: view.view,
4891
+ table: materialized.table,
4892
+ compute: materialized.compute,
4893
+ columns: table.columns.map((column) => column.columnName),
4894
+ pk
4895
+ }));
4896
+ }
4897
+ return functions;
4898
+ }
4899
+ function refreshFunctionSql({ schema, name, table, compute, columns, pk }) {
4900
+ const fn = `${quoteIdentifier(schema)}.${quoteIdentifier(materializedRefreshFunctionName(name))}`;
4901
+ const target = `${quoteIdentifier(schema)}.${quoteIdentifier(table)}`;
4902
+ const source = `${quoteIdentifier(schema)}.${quoteIdentifier(compute)}`;
4903
+ const list = columns.map(quoteIdentifier).join(", ");
4904
+ const others = columns.filter((column) => !pk.includes(column));
4905
+ const keys = pk.map(quoteIdentifier).join(", ");
4906
+ const matches = pk.map((column) => `c.${quoteIdentifier(column)} = m.${quoteIdentifier(column)}`).join(" AND ");
4907
+ const onConflict = others.length === 0 ? "DO NOTHING" : `DO UPDATE SET ${others.map((column) => `${quoteIdentifier(column)} = EXCLUDED.${quoteIdentifier(column)}`).join(", ")}
4908
+ WHERE (${others.map((column) => `m.${quoteIdentifier(column)}`).join(", ")}) IS DISTINCT FROM (${others.map((column) => `EXCLUDED.${quoteIdentifier(column)}`).join(", ")})`;
4909
+ const lockKey = quoteLiteral(materializeLockKey(schema, name));
4910
+ return `CREATE OR REPLACE FUNCTION ${fn}(wait boolean DEFAULT false)
4911
+ RETURNS TABLE(upserted bigint, deleted bigint, locked boolean)
4912
+ LANGUAGE plpgsql
4913
+ SECURITY INVOKER
4914
+ SET search_path = pg_catalog, pg_temp
4915
+ AS $orm_refresh$
4916
+ #variable_conflict use_column
4917
+ DECLARE
4918
+ caller_claims text := current_setting('request.jwt.claims', true);
4919
+ BEGIN
4920
+ IF wait THEN
4921
+ PERFORM pg_advisory_xact_lock(hashtext(${lockKey}));
4922
+ ELSIF NOT pg_try_advisory_xact_lock(hashtext(${lockKey})) THEN
4923
+ RETURN QUERY SELECT 0::bigint, 0::bigint, false;
4924
+ RETURN;
4925
+ END IF;
4926
+ PERFORM set_config('request.jwt.claims', ${quoteLiteral(MATERIALIZE_CLAIMS_JSON)}, true);
4927
+ RETURN QUERY
4928
+ WITH up AS (
4929
+ INSERT INTO ${target} AS m (${list})
4930
+ SELECT ${list} FROM ${source}
4931
+ ON CONFLICT (${keys}) ${onConflict}
4932
+ RETURNING 1
4933
+ ), gone AS (
4934
+ DELETE FROM ${target} AS m
4935
+ WHERE NOT EXISTS (SELECT 1 FROM ${source} AS c WHERE ${matches})
4936
+ RETURNING 1
4937
+ )
4938
+ SELECT (SELECT count(*) FROM up), (SELECT count(*) FROM gone), true;
4939
+ -- NULL resets to the session value when the caller had none.
4940
+ PERFORM set_config('request.jwt.claims', caller_claims, true);
4941
+ END
4942
+ $orm_refresh$`;
4943
+ }
4944
+ function refreshFunctionStatements({ namespace, previous, current }) {
4945
+ const schema = quoteIdentifier(ormSchemaName(namespace));
4946
+ const statements = [];
4947
+ for (const name of Object.keys(previous).sort()) {
4948
+ if (!current.has(name)) statements.push(`DROP FUNCTION IF EXISTS ${schema}.${quoteIdentifier(materializedRefreshFunctionName(name))}(boolean)`);
4949
+ }
4950
+ for (const [name, create] of [...current].sort(([a], [b]) => a.localeCompare(b))) {
4951
+ if (previous[name] === create) continue;
4952
+ const fn = `${schema}.${quoteIdentifier(materializedRefreshFunctionName(name))}(boolean)`;
4953
+ statements.push(create, `REVOKE ALL ON FUNCTION ${fn} FROM PUBLIC`);
4954
+ }
4955
+ return statements;
4956
+ }
4957
+
4958
+ // src/engine/canonical2.ts
3610
4959
  var FACTORIES2 = {
3611
4960
  uuid: (name) => pgCore.uuid(name),
3612
4961
  text: (name) => pgCore.text(name),
@@ -3624,7 +4973,7 @@ var FACTORIES2 = {
3624
4973
  var SCHEMA_IMPORT_KEY2 = "$schema";
3625
4974
  var enumImportKey = (name) => `$enum_${name}`;
3626
4975
  var columnTablesV2 = (definition) => definition.tables.filter((table) => !table.source);
3627
- function buildDrizzleEntitiesV2(definition) {
4976
+ function buildDrizzleEntitiesV2(definition, options = {}) {
3628
4977
  const schemaName = ormSchemaName(definition.namespace);
3629
4978
  const schema = pgCore.pgSchema(schemaName);
3630
4979
  const imports = { [SCHEMA_IMPORT_KEY2]: schema };
@@ -3639,11 +4988,19 @@ function buildDrizzleEntitiesV2(definition) {
3639
4988
  }
3640
4989
  const byName = new Map(definition.tables.map((table) => [table.table, table]));
3641
4990
  for (const table of sortByEnforcedLinks2(columnTablesV2(definition))) {
3642
- const drizzleTable = buildTable2(schema, table, built, enums, byName);
4991
+ const drizzleTable = buildTable2(schema, table, built, enums, byName, options.introspection ?? false);
3643
4992
  built.set(table.table, drizzleTable);
3644
4993
  imports[table.table] = drizzleTable;
3645
4994
  tables[table.table] = drizzleTable;
3646
4995
  }
4996
+ if (options.introspection) {
4997
+ for (const table of definition.tables.filter((table2) => table2.source)) {
4998
+ const columns = Object.fromEntries(
4999
+ table.columns.map((column) => [column.columnName, buildViewColumnV2({ view: table.table, column, enums })])
5000
+ );
5001
+ imports[table.table] = schema.view(table.table, columns).existing();
5002
+ }
5003
+ }
3647
5004
  for (const view of definition.views ?? []) {
3648
5005
  const columns = Object.fromEntries(
3649
5006
  view.columns.map((column) => [column.columnName, buildViewColumnV2({ view: view.view, column, enums })])
@@ -3711,18 +5068,18 @@ function rewriteSyncedSourceRelationsInViewQuery({
3711
5068
  return rewritten;
3712
5069
  });
3713
5070
  }
3714
- function transformSqlOutsideLiterals(sql4, transform) {
5071
+ function transformSqlOutsideLiterals(sql7, transform) {
3715
5072
  const out = [];
3716
5073
  let state = { kind: "normal" };
3717
5074
  let i = 0;
3718
5075
  let codeStart = 0;
3719
5076
  const emitCode = (end) => {
3720
- if (end > codeStart) out.push(transform(sql4.slice(codeStart, end)));
5077
+ if (end > codeStart) out.push(transform(sql7.slice(codeStart, end)));
3721
5078
  codeStart = end;
3722
5079
  };
3723
- while (i < sql4.length) {
3724
- const c = sql4[i];
3725
- const next = i + 1 < sql4.length ? sql4[i + 1] : "";
5080
+ while (i < sql7.length) {
5081
+ const c = sql7[i];
5082
+ const next = i + 1 < sql7.length ? sql7[i + 1] : "";
3726
5083
  switch (state.kind) {
3727
5084
  case "normal": {
3728
5085
  if (c === "-" && next === "-") {
@@ -3742,7 +5099,7 @@ function transformSqlOutsideLiterals(sql4, transform) {
3742
5099
  break;
3743
5100
  }
3744
5101
  if ((c === "E" || c === "e") && next === "'") {
3745
- const prev = i > 0 ? sql4[i - 1] : "";
5102
+ const prev = i > 0 ? sql7[i - 1] : "";
3746
5103
  if (!/[A-Za-z0-9_]/.test(prev)) {
3747
5104
  emitCode(i);
3748
5105
  state = { kind: "e-single" };
@@ -3761,9 +5118,9 @@ function transformSqlOutsideLiterals(sql4, transform) {
3761
5118
  break;
3762
5119
  }
3763
5120
  if (c === "$") {
3764
- const prev = i > 0 ? sql4[i - 1] : "";
5121
+ const prev = i > 0 ? sql7[i - 1] : "";
3765
5122
  if (!/[A-Za-z0-9_$]/.test(prev)) {
3766
- const tagMatch = /^\$([A-Za-z_][A-Za-z0-9_]*)?\$/.exec(sql4.slice(i));
5123
+ const tagMatch = /^\$([A-Za-z_][A-Za-z0-9_]*)?\$/.exec(sql7.slice(i));
3767
5124
  if (tagMatch) {
3768
5125
  emitCode(i);
3769
5126
  state = { kind: "dollar", tag: tagMatch[1] ?? "" };
@@ -3795,8 +5152,8 @@ function transformSqlOutsideLiterals(sql4, transform) {
3795
5152
  break;
3796
5153
  }
3797
5154
  case "e-single": {
3798
- if (c === "\\" && i + 1 < sql4.length) {
3799
- out.push(c, sql4[i + 1]);
5155
+ if (c === "\\" && i + 1 < sql7.length) {
5156
+ out.push(c, sql7[i + 1]);
3800
5157
  i += 2;
3801
5158
  break;
3802
5159
  }
@@ -3818,7 +5175,7 @@ function transformSqlOutsideLiterals(sql4, transform) {
3818
5175
  }
3819
5176
  case "dollar": {
3820
5177
  const closer = `$${state.tag}$`;
3821
- if (sql4.startsWith(closer, i)) {
5178
+ if (sql7.startsWith(closer, i)) {
3822
5179
  out.push(closer);
3823
5180
  state = { kind: "normal" };
3824
5181
  i += closer.length;
@@ -3862,15 +5219,15 @@ function transformSqlOutsideLiterals(sql4, transform) {
3862
5219
  }
3863
5220
  }
3864
5221
  }
3865
- emitCode(sql4.length);
5222
+ emitCode(sql7.length);
3866
5223
  return out.join("");
3867
5224
  }
3868
5225
  function escapeRegExp(value) {
3869
5226
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3870
5227
  }
3871
- function sqlReferencesQualifiedRelation(sql4, sourceSchema, sourceName) {
5228
+ function sqlReferencesQualifiedRelation(sql7, sourceSchema, sourceName) {
3872
5229
  let referenced = false;
3873
- transformSqlOutsideLiterals(sql4, (segment) => {
5230
+ transformSqlOutsideLiterals(sql7, (segment) => {
3874
5231
  if (qualifiedRelationPattern(sourceSchema, sourceName).test(segment)) referenced = true;
3875
5232
  return segment;
3876
5233
  });
@@ -3881,15 +5238,18 @@ function qualifiedRelationPattern(sourceSchema, sourceName) {
3881
5238
  const namePattern = `(?:"${escapeRegExp(sourceName)}"|${escapeRegExp(sourceName)})`;
3882
5239
  return new RegExp(`(?<![\\w."])${schemaPattern}\\.${namePattern}(?![\\w"])`, "g");
3883
5240
  }
3884
- function replaceQualifiedRelation(sql4, sourceSchema, sourceName, boundSchema, boundTable) {
3885
- return sql4.replace(qualifiedRelationPattern(sourceSchema, sourceName), `"${boundSchema}"."${boundTable}"`);
5241
+ function replaceQualifiedRelation(sql7, sourceSchema, sourceName, boundSchema, boundTable) {
5242
+ return sql7.replace(qualifiedRelationPattern(sourceSchema, sourceName), `"${boundSchema}"."${boundTable}"`);
3886
5243
  }
3887
5244
  function generateSnapshotV2(definition, previousId3) {
3888
5245
  const { imports, schemaName } = buildDrizzleEntitiesV2(definition);
3889
5246
  const snapshot = api.generateDrizzleJson(imports, previousId3, [schemaName]);
3890
- if ((definition.views?.length ?? 0) > 0) {
5247
+ if ((definition.views?.length ?? 0) > 0 || definition.tables.some((table) => table.foreignKeys?.length)) {
3891
5248
  const extended = snapshot;
5249
+ const refreshFunctions = materializedRefreshFunctions(definition);
3892
5250
  extended._sequenceOrm = {
5251
+ ...definition.tables.some((table) => table.foreignKeys?.length) ? { foreignKeys: Object.fromEntries(definition.tables.flatMap((table) => (table.foreignKeys ?? []).map((fk) => [`${table.table}.${fk.name}`, structuredClone(fk)]))) } : {},
5252
+ ...refreshFunctions.size > 0 ? { refreshFunctions: Object.fromEntries(refreshFunctions) } : {},
3893
5253
  viewDependencies: Object.fromEntries(
3894
5254
  (definition.views ?? []).map((view) => [view.view, [...view.dependencies].sort()])
3895
5255
  )
@@ -3907,7 +5267,7 @@ function buildViewColumnV2({
3907
5267
  if (!column.nullable) builder = builder.notNull();
3908
5268
  return builder;
3909
5269
  }
3910
- function buildTable2(schema, table, built, enums, byName) {
5270
+ function buildTable2(schema, table, built, enums, byName, introspection) {
3911
5271
  const primaryKeyColumns = table.primaryKey ?? [];
3912
5272
  if (primaryKeyColumns.length === 0) {
3913
5273
  throw new OrmDefinitionError(`table '${table.table}' declares no primaryKey`);
@@ -3923,13 +5283,26 @@ function buildTable2(schema, table, built, enums, byName) {
3923
5283
  const columns = Object.fromEntries(
3924
5284
  table.columns.map((column) => [
3925
5285
  column.columnName,
3926
- buildColumnV2({ table: table.table, column, singlePrimaryKey, compositeMembers, enums })
5286
+ buildColumnV2({ table: table.table, column, singlePrimaryKey, compositeMembers, enums, introspection })
3927
5287
  ])
3928
5288
  );
3929
5289
  const drizzleTable = schema.table(table.table, columns, (self) => [
3930
5290
  ...buildCompositePrimaryKey(table, primaryKeyColumns, self),
3931
5291
  ...buildIndexes2(table, self),
3932
- ...buildForeignKeys2(table, self, built, byName)
5292
+ ...(table.checks ?? []).map((check) => pgCore.check(check.name, drizzleOrm.sql.raw(compileCheckExpression(check.expression, table.columns)))),
5293
+ ...buildForeignKeys2(table, self, built, byName),
5294
+ ...(table.foreignKeys ?? []).map((fk) => {
5295
+ const target = fk.target === table.table ? self : built.get(fk.target);
5296
+ if (!target) throw new OrmDefinitionError(`foreign key '${fk.name}' has no target '${fk.target}'`);
5297
+ let constraint = pgCore.foreignKey({
5298
+ name: fk.name,
5299
+ columns: fk.columns.map((column) => self[column]),
5300
+ foreignColumns: fk.references.map((column) => target[column])
5301
+ });
5302
+ if (fk.onDelete !== void 0) constraint = constraint.onDelete(fk.onDelete);
5303
+ if (fk.onUpdate !== void 0) constraint = constraint.onUpdate(fk.onUpdate);
5304
+ return constraint;
5305
+ })
3933
5306
  ]);
3934
5307
  return drizzleTable.enableRLS();
3935
5308
  }
@@ -3946,7 +5319,8 @@ function buildColumnV2({
3946
5319
  column,
3947
5320
  singlePrimaryKey,
3948
5321
  compositeMembers,
3949
- enums
5322
+ enums,
5323
+ introspection
3950
5324
  }) {
3951
5325
  const factory = resolveColumnFactory(table, column, enums);
3952
5326
  let builder = factory(column.columnName);
@@ -3967,7 +5341,7 @@ function buildColumnV2({
3967
5341
  }
3968
5342
  builder = builder.default(drizzleOrm.sql.raw(column.defaultFn));
3969
5343
  } else if (column.default !== void 0 && column.default !== null) {
3970
- builder = builder.default(column.default);
5344
+ builder = introspection && column.type === "jsonb" && typeof column.default === "string" ? builder.default(drizzleOrm.sql.raw(`${quoteLiteral(column.default)}::jsonb`)) : builder.default(column.default);
3971
5345
  }
3972
5346
  return builder;
3973
5347
  }
@@ -4018,11 +5392,14 @@ function buildForeignKeys2(table, self, built, byName) {
4018
5392
  `enforced link '${link.name}' on '${table.table}' references unknown column '${targetColumn ? link.on : referencedColumn}'`
4019
5393
  );
4020
5394
  }
4021
- return pgCore.foreignKey({
5395
+ let constraint = pgCore.foreignKey({
4022
5396
  name: foreignKeyName(table.table, link.name),
4023
5397
  columns: [sourceColumn],
4024
5398
  foreignColumns: [targetColumn]
4025
5399
  });
5400
+ if (link.onDelete !== void 0) constraint = constraint.onDelete(link.onDelete);
5401
+ if (link.onUpdate !== void 0) constraint = constraint.onUpdate(link.onUpdate);
5402
+ return constraint;
4026
5403
  });
4027
5404
  }
4028
5405
  var singlePrimaryKeyOf = (table) => table.primaryKey?.length === 1 ? table.primaryKey[0] : void 0;
@@ -4143,16 +5520,31 @@ function findTable2(snapshot, schema, table) {
4143
5520
  const tables = snapshot.tables ?? {};
4144
5521
  return Object.values(tables).find((entry) => entry.schema === schema && entry.name === table);
4145
5522
  }
4146
- async function planMigrationV2({
5523
+ function planMigrationV2(input) {
5524
+ return planWithViewRecreation({ ...input, viewRecreation: "affected" });
5525
+ }
5526
+ async function planCommittedMigrationV2({
4147
5527
  canonical,
4148
- previousSnapshot
5528
+ previousSnapshot,
5529
+ reproduces
5530
+ }) {
5531
+ const current = await planMigrationV2({ canonical, previousSnapshot });
5532
+ if (reproduces(current)) return { plan: current, reproduced: true };
5533
+ const legacy = await planWithViewRecreation({ canonical, previousSnapshot, viewRecreation: "all-on-base-change" });
5534
+ return reproduces(legacy) ? { plan: legacy, reproduced: true } : { plan: current, reproduced: false };
5535
+ }
5536
+ async function planWithViewRecreation({
5537
+ canonical,
5538
+ previousSnapshot,
5539
+ viewRecreation
4149
5540
  }) {
4150
5541
  const schema = ormSchemaName(canonical.namespace);
4151
5542
  const previous = previousSnapshot ? normalizeSnapshot(previousSnapshot) : emptySnapshot(canonical.namespace);
4152
5543
  assertValidViewDependencies(canonical);
4153
5544
  const hints = applyMigrationHintsV2(previous, canonical);
4154
5545
  const snapshot = generateSnapshotV2(canonical, previousId(previous));
4155
- const baseDdl = await diffStatements(withoutViews(hints.adjusted), withoutViews(snapshot));
5546
+ const generatedBaseDdl = await diffStatements(withoutViews(hints.adjusted), withoutViews(snapshot));
5547
+ const baseDdl = orderForeignKeyAdditions(canonical, generatedBaseDdl);
4156
5548
  const previousViews = snapshotViews(hints.adjusted);
4157
5549
  const currentViews = snapshotViews(snapshot);
4158
5550
  const previousDependencies = snapshotViewDependencies(hints.adjusted);
@@ -4162,7 +5554,14 @@ async function planMigrationV2({
4162
5554
  currentViews,
4163
5555
  previousDependencies,
4164
5556
  currentDependencies,
4165
- baseChanged: hints.statements.length > 0 || baseDdl.length > 0
5557
+ reach: viewReach({
5558
+ viewRecreation,
5559
+ hintStatements: hints.statements,
5560
+ baseDdl,
5561
+ previous,
5562
+ adjusted: hints.adjusted,
5563
+ current: snapshot
5564
+ })
4166
5565
  });
4167
5566
  const dropOrder = topologicalViewNames(
4168
5567
  new Set([...affected].filter((name) => previousViews.has(name))),
@@ -4189,15 +5588,44 @@ async function planMigrationV2({
4189
5588
  ...dropOrder.map((name) => ({ kind: "ddl", sql: requiredViewStatement(dropDdl, name, schema, "DROP") })),
4190
5589
  ...hints.statements.map((statement) => ({ kind: "hint", sql: statement })),
4191
5590
  ...baseDdl.map((statement) => ({ kind: "ddl", sql: statement.replace(/;\s*$/, "") })),
5591
+ ...foreignKeyTimingStatements(canonical, previous, baseDdl).map((sql7) => ({ kind: "ddl", sql: sql7 })),
4192
5592
  ...createOrder.map((name) => ({ kind: "ddl", sql: requiredViewStatement(createDdl, name, schema, "CREATE") })),
4193
5593
  ...createOrder.map((name) => {
4194
5594
  const view = currentViewDefinitions.get(name);
4195
5595
  if (!view) throw new OrmDefinitionError(`missing canonical definition for view '${name}'`);
4196
5596
  return { kind: "ddl", sql: validateViewCatalogStatement({ canonical, view, schema }) };
4197
- })
5597
+ }),
5598
+ ...refreshFunctionStatements({
5599
+ namespace: canonical.namespace,
5600
+ previous: previous._sequenceOrm?.refreshFunctions ?? {},
5601
+ current: materializedRefreshFunctions(canonical)
5602
+ }).map((sql7) => ({ kind: "ddl", sql: sql7 }))
4198
5603
  ];
4199
5604
  return { namespace: canonical.namespace, schema, statements, snapshot };
4200
5605
  }
5606
+ function orderForeignKeyAdditions(canonical, statements) {
5607
+ if (!canonical.tables.some((table) => table.foreignKeys?.length)) return [...statements];
5608
+ const addsForeignKey = (statement) => /^ALTER TABLE[\s\S]* ADD CONSTRAINT[\s\S]* FOREIGN KEY\b/.test(statement);
5609
+ return [...statements.filter((statement) => !addsForeignKey(statement)), ...statements.filter(addsForeignKey)];
5610
+ }
5611
+ function foreignKeyTimingStatements(canonical, previous, baseDdl) {
5612
+ const prior = previous._sequenceOrm?.foreignKeys ?? {};
5613
+ const result = [];
5614
+ for (const table of canonical.tables) {
5615
+ for (const fk of table.foreignKeys ?? []) {
5616
+ const before = prior[`${table.table}.${fk.name}`];
5617
+ const recreated = baseDdl.some((statement) => statement.includes(`ALTER TABLE ${quoteIdentifier(ormSchemaName(canonical.namespace))}.${quoteIdentifier(table.table)} ADD CONSTRAINT ${quoteIdentifier(fk.name)}`));
5618
+ if (fk.deferrable ? !before || before.deferrable !== fk.deferrable || recreated : before?.deferrable && !recreated) {
5619
+ result.push(foreignKeyTimingSql(ormSchemaName(canonical.namespace), table.table, fk.name, fk.deferrable));
5620
+ }
5621
+ }
5622
+ }
5623
+ return result;
5624
+ }
5625
+ function foreignKeyTimingSql(schema, table, name, timing) {
5626
+ const clause = timing === void 0 ? "NOT DEFERRABLE" : timing === "initially-deferred" ? "DEFERRABLE INITIALLY DEFERRED" : "DEFERRABLE INITIALLY IMMEDIATE";
5627
+ return `ALTER TABLE ${quoteIdentifier(schema)}.${quoteIdentifier(table)} ALTER CONSTRAINT ${quoteIdentifier(name)} ${clause}`;
5628
+ }
4201
5629
  function validateViewCatalogStatement({
4202
5630
  canonical,
4203
5631
  view,
@@ -4215,9 +5643,9 @@ function validateViewCatalogStatement({
4215
5643
  }
4216
5644
  const expectedDependencies = sqlTextArray(
4217
5645
  view.dependencies.map((dependency) => {
4218
- const physical = relationIdentity.get(dependency);
4219
- if (!physical) throw new OrmDefinitionError(`view '${view.view}' depends on unknown relation '${dependency}'`);
4220
- return physical;
5646
+ const physical2 = relationIdentity.get(dependency);
5647
+ if (!physical2) throw new OrmDefinitionError(`view '${view.view}' depends on unknown relation '${dependency}'`);
5648
+ return physical2;
4221
5649
  }).sort()
4222
5650
  );
4223
5651
  const allowedFunctions = sqlTextArray(VIEW_QUERY_FUNCTION_ALLOWLIST);
@@ -4325,20 +5753,102 @@ function withoutViews(snapshot) {
4325
5753
  delete clone._sequenceOrm;
4326
5754
  return clone;
4327
5755
  }
5756
+ var DROPS_TYPE = /^\s*DROP\s+TYPE\b/i;
5757
+ function viewReach({
5758
+ viewRecreation,
5759
+ hintStatements,
5760
+ baseDdl,
5761
+ previous,
5762
+ adjusted,
5763
+ current
5764
+ }) {
5765
+ if (hintStatements.length === 0 && baseDdl.length === 0) return /* @__PURE__ */ new Set();
5766
+ if (viewRecreation === "all-on-base-change") return "all";
5767
+ if (baseDdl.some((statement) => DROPS_TYPE.test(statement))) return "all";
5768
+ const dependencies = snapshotViewDependencies(adjusted);
5769
+ if ([...snapshotViews(adjusted).keys()].some((name) => dependencies[name] === void 0)) return "all";
5770
+ return baseChangesReachingViews({ previous, adjusted, current });
5771
+ }
5772
+ var SNAPSHOT_BOOKKEEPING = /* @__PURE__ */ new Set(["id", "prevId", "version", "dialect", "_meta", "views", "_sequenceOrm", "enums"]);
5773
+ var VIEW_SAFE_TABLE_FIELDS = /* @__PURE__ */ new Set([
5774
+ "indexes",
5775
+ "foreignKeys",
5776
+ "uniqueConstraints",
5777
+ "checkConstraints",
5778
+ "policies",
5779
+ "isRLSEnabled"
5780
+ ]);
5781
+ var VIEW_SAFE_COLUMN_FIELDS = /* @__PURE__ */ new Set(["notNull", "default", "isUnique", "uniqueName", "nullsNotDistinct"]);
5782
+ function baseChangesReachingViews({
5783
+ previous,
5784
+ adjusted,
5785
+ current
5786
+ }) {
5787
+ const before = adjusted;
5788
+ const after = current;
5789
+ for (const section of /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])) {
5790
+ if (SNAPSHOT_BOOKKEEPING.has(section) || section === "tables") continue;
5791
+ if (stableJson(before[section]) !== stableJson(after[section])) return "all";
5792
+ }
5793
+ const relations = /* @__PURE__ */ new Set();
5794
+ const previousTables = snapshotTables(previous);
5795
+ const adjustedTables = snapshotTables(adjusted);
5796
+ for (const key of changedEntryKeys(previousTables, adjustedTables)) {
5797
+ const table = previousTables[key] ?? adjustedTables[key];
5798
+ if (table) relations.add(table.name);
5799
+ }
5800
+ const currentTables = snapshotTables(current);
5801
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(adjustedTables), ...Object.keys(currentTables)])) {
5802
+ const was = adjustedTables[key];
5803
+ const is2 = currentTables[key];
5804
+ if (!was || !is2) {
5805
+ const table = was ?? is2;
5806
+ if (table) relations.add(table.name);
5807
+ } else if (tableChangeReachesViews({ was, is: is2 })) {
5808
+ relations.add(is2.name);
5809
+ }
5810
+ }
5811
+ return relations;
5812
+ }
5813
+ function tableChangeReachesViews({ was, is: is2 }) {
5814
+ for (const field of /* @__PURE__ */ new Set([...Object.keys(was), ...Object.keys(is2)])) {
5815
+ if (field === "columns" || VIEW_SAFE_TABLE_FIELDS.has(field)) continue;
5816
+ if (stableJson(was[field]) !== stableJson(is2[field])) return true;
5817
+ }
5818
+ return Object.entries(was.columns).some(([name, column]) => {
5819
+ const next = is2.columns[name];
5820
+ if (!next) return true;
5821
+ return [.../* @__PURE__ */ new Set([...Object.keys(column), ...Object.keys(next)])].some(
5822
+ (field) => !VIEW_SAFE_COLUMN_FIELDS.has(field) && stableJson(column[field]) !== stableJson(next[field])
5823
+ );
5824
+ });
5825
+ }
5826
+ function changedEntryKeys(before, after) {
5827
+ return new Set(
5828
+ [.../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])].filter(
5829
+ (key) => stableJson(before[key]) !== stableJson(after[key])
5830
+ )
5831
+ );
5832
+ }
5833
+ function snapshotTables(snapshot) {
5834
+ return snapshot.tables ?? {};
5835
+ }
4328
5836
  function affectedViewNames({
4329
5837
  previousViews,
4330
5838
  currentViews,
4331
5839
  previousDependencies,
4332
5840
  currentDependencies,
4333
- baseChanged
5841
+ reach
4334
5842
  }) {
4335
5843
  const allNames = /* @__PURE__ */ new Set([...previousViews.keys(), ...currentViews.keys()]);
4336
- if (baseChanged) return allNames;
5844
+ if (reach === "all") return allNames;
5845
+ const dependenciesOf = (name) => /* @__PURE__ */ new Set([...previousDependencies[name] ?? [], ...currentDependencies[name] ?? []]);
4337
5846
  const affected = new Set(
4338
5847
  [...allNames].filter((name) => {
4339
5848
  const before = previousViews.get(name);
4340
5849
  const after = currentViews.get(name);
4341
- return before === void 0 || after === void 0 || stableJson(before) !== stableJson(after);
5850
+ if (before === void 0 || after === void 0 || stableJson(before) !== stableJson(after)) return true;
5851
+ return [...dependenciesOf(name)].some((dependency) => reach.has(dependency));
4342
5852
  })
4343
5853
  );
4344
5854
  let grew = true;
@@ -4346,8 +5856,7 @@ function affectedViewNames({
4346
5856
  grew = false;
4347
5857
  for (const name of allNames) {
4348
5858
  if (affected.has(name)) continue;
4349
- const dependencies = /* @__PURE__ */ new Set([...previousDependencies[name] ?? [], ...currentDependencies[name] ?? []]);
4350
- if ([...dependencies].some((dependency) => affected.has(dependency))) {
5859
+ if ([...dependenciesOf(name)].some((dependency) => affected.has(dependency))) {
4351
5860
  affected.add(name);
4352
5861
  grew = true;
4353
5862
  }
@@ -4452,7 +5961,63 @@ async function pendingKitStatementsV2({
4452
5961
  canonical,
4453
5962
  db
4454
5963
  }) {
4455
- return pendingStatements({ entities: buildDrizzleEntitiesV2(canonical), db, owned: V2_OWNED_STATEMENT });
5964
+ const { desired, defaultDrift } = await normalizeJsonDefaultsForIntrospection(canonical, db);
5965
+ const entities = buildDrizzleEntitiesV2(desired, { introspection: true });
5966
+ const pending = await pendingStatements({ entities, db, owned: V2_OWNED_STATEMENT });
5967
+ return [...orderForeignKeyAdditions(canonical, pending), ...defaultDrift, ...await pendingForeignKeyTimingStatements(canonical, db)];
5968
+ }
5969
+ async function normalizeJsonDefaultsForIntrospection(canonical, db) {
5970
+ const defaults = canonical.tables.flatMap((table, tableIndex) => table.source ? [] : table.columns.flatMap((column, columnIndex) => column.type === "jsonb" && typeof column.default === "string" ? [{ tableIndex, columnIndex, table: table.table, column: column.columnName, value: column.default }] : []));
5971
+ if (defaults.length === 0) return { desired: canonical, defaultDrift: [] };
5972
+ const array = (values) => drizzleOrm.sql`ARRAY[${drizzleOrm.sql.join(values.map((value) => drizzleOrm.sql`${value}`), drizzleOrm.sql`, `)}]::text[]`;
5973
+ const schema = ormSchemaName(canonical.namespace);
5974
+ const result = await shimExecute(db).execute(drizzleOrm.sql`
5975
+ SELECT desired.ordinal, desired.value::jsonb::text AS normalized,
5976
+ attribute.attnum IS NOT NULL AS column_exists,
5977
+ coalesce(pg_get_expr(ad.adbin, ad.adrelid) = quote_literal(desired.value::jsonb::text) || '::jsonb', false) AS matches
5978
+ FROM unnest(${array(defaults.map((item) => item.table))}, ${array(defaults.map((item) => item.column))}, ${array(defaults.map((item) => item.value))})
5979
+ WITH ORDINALITY AS desired(table_name, column_name, value, ordinal)
5980
+ LEFT JOIN pg_catalog.pg_namespace namespace ON namespace.nspname = ${schema}
5981
+ LEFT JOIN pg_catalog.pg_class relation ON relation.relnamespace = namespace.oid AND relation.relname = desired.table_name
5982
+ LEFT JOIN pg_catalog.pg_attribute attribute ON attribute.attrelid = relation.oid AND attribute.attname = desired.column_name AND NOT attribute.attisdropped
5983
+ LEFT JOIN pg_catalog.pg_attrdef ad ON ad.adrelid = relation.oid AND ad.adnum = attribute.attnum
5984
+ ORDER BY desired.ordinal
5985
+ `);
5986
+ const rows = result.rows;
5987
+ if (!Array.isArray(rows) || rows.length !== defaults.length || rows.some((row, index3) => Number(row.ordinal) !== index3 + 1 || typeof row.normalized !== "string" || typeof row.column_exists !== "boolean" || typeof row.matches !== "boolean")) {
5988
+ throw new Error("invalid JSON default introspection result");
5989
+ }
5990
+ const desired = structuredClone(canonical);
5991
+ const defaultDrift = [];
5992
+ defaults.forEach((item, index3) => {
5993
+ const row = rows[index3];
5994
+ desired.tables[item.tableIndex].columns[item.columnIndex].default = JSON.stringify(JSON.parse(row.normalized));
5995
+ if (row.column_exists && !row.matches) defaultDrift.push(
5996
+ `ALTER TABLE ${quoteIdentifier(schema)}.${quoteIdentifier(item.table)} ALTER COLUMN ${quoteIdentifier(item.column)} SET DEFAULT ${quoteLiteral(item.value)}::jsonb;`
5997
+ );
5998
+ });
5999
+ return { desired, defaultDrift };
6000
+ }
6001
+ async function pendingForeignKeyTimingStatements(canonical, db) {
6002
+ const keys = canonical.tables.flatMap((table) => (table.foreignKeys ?? []).map((fk) => ({ table: table.table, fk })));
6003
+ if (keys.length === 0) return [];
6004
+ const schema = ormSchemaName(canonical.namespace);
6005
+ const result = await shimExecute(db).execute(drizzleOrm.sql`
6006
+ SELECT relation.relname AS table_name, constraint_row.conname AS name,
6007
+ constraint_row.condeferrable AS deferrable, constraint_row.condeferred AS deferred
6008
+ FROM pg_catalog.pg_constraint AS constraint_row
6009
+ JOIN pg_catalog.pg_class AS relation ON relation.oid = constraint_row.conrelid
6010
+ JOIN pg_catalog.pg_namespace AS namespace ON namespace.oid = relation.relnamespace
6011
+ WHERE namespace.nspname = ${schema} AND constraint_row.contype = 'f'
6012
+ `);
6013
+ const rows = result.rows;
6014
+ return keys.flatMap(({ table, fk }) => {
6015
+ const actual = rows.find((row) => row.table_name === table && row.name === fk.name);
6016
+ const deferrable = fk.deferrable !== void 0;
6017
+ const deferred = fk.deferrable === "initially-deferred";
6018
+ if (actual ? actual.deferrable === deferrable && actual.deferred === deferred : !deferrable) return [];
6019
+ return [foreignKeyTimingSql(schema, table, fk.name, fk.deferrable)];
6020
+ });
4456
6021
  }
4457
6022
  async function pendingStatements({
4458
6023
  entities,
@@ -4465,11 +6030,31 @@ async function pendingStatements({
4465
6030
  );
4466
6031
  return statementsToExecute.filter((statement) => !owned.test(statement));
4467
6032
  }
6033
+ function correctIntrospectionQuery(query) {
6034
+ if (!drizzleOrm.is(query, drizzleOrm.SQL)) return query;
6035
+ const compiled = new pgCore.PgDialect().sqlToQuery(query);
6036
+ let text4 = compiled.sql;
6037
+ const indexJoin = "pg_constraint con ON con.conindid = idx.indexrelid";
6038
+ if (text4.includes("AS generated_by_constraint") && text4.includes(indexJoin)) {
6039
+ text4 = text4.replace(indexJoin, `${indexJoin} AND con.contype IN ('p', 'u', 'x')`);
6040
+ }
6041
+ const sourceJoin = "LEFT JOIN pg_catalog.pg_attribute att ON att.attnum = ANY (con.conkey)";
6042
+ const targetJoin = "LEFT JOIN pg_catalog.pg_attribute fatt ON fatt.attnum = ANY (con.confkey)";
6043
+ if (text4.includes(sourceJoin) && text4.includes(targetJoin) && text4.includes("AND con.contype IN ('f')")) {
6044
+ text4 = text4.replace(
6045
+ sourceJoin,
6046
+ "JOIN LATERAL unnest(con.conkey, con.confkey) WITH ORDINALITY AS fk_key(source_num, target_num, ordinal) ON true LEFT JOIN pg_catalog.pg_attribute att ON att.attnum = fk_key.source_num"
6047
+ ).replace(targetJoin, "LEFT JOIN pg_catalog.pg_attribute fatt ON fatt.attnum = fk_key.target_num").replace(/;\s*$/, "") + " ORDER BY con.conname, fk_key.ordinal";
6048
+ }
6049
+ if (text4 === compiled.sql) return query;
6050
+ if (compiled.params.length !== 0) throw new Error("parameterized Drizzle catalog query cannot be normalized");
6051
+ return drizzleOrm.sql.raw(text4);
6052
+ }
4468
6053
  function shimExecute(db) {
4469
6054
  const execute = db.execute.bind(db);
4470
6055
  return {
4471
6056
  execute: async (query) => {
4472
- const result = await execute(query);
6057
+ const result = await execute(correctIntrospectionQuery(query));
4473
6058
  if (result && typeof result === "object" && "rows" in result) return result;
4474
6059
  return { rows: result };
4475
6060
  }
@@ -4509,21 +6094,73 @@ async function planMigration({
4509
6094
  const ddl = await diffStatements(hints.adjusted, snapshot);
4510
6095
  const policyDrops = policyDropStatements(schema, snapshotTableNames(previous, schema));
4511
6096
  const statements = [
4512
- ...policyDrops.map((sql4) => ({ kind: "security", sql: sql4 })),
4513
- ...hints.statements.map((sql4) => ({ kind: "hint", sql: sql4 })),
4514
- ...ddl.map((sql4) => ({ kind: "ddl", sql: sql4.replace(/;\s*$/, "") })),
4515
- ...securityStatements(definition).map((sql4) => ({ kind: "security", sql: sql4 })),
6097
+ ...policyDrops.map((sql7) => ({ kind: "security", sql: sql7 })),
6098
+ ...hints.statements.map((sql7) => ({ kind: "hint", sql: sql7 })),
6099
+ ...ddl.map((sql7) => ({ kind: "ddl", sql: sql7.replace(/;\s*$/, "") })),
6100
+ ...securityStatements(definition).map((sql7) => ({ kind: "security", sql: sql7 })),
4516
6101
  // PREPARE each action's executes first (plpgsql bodies don't check columns
4517
6102
  // at CREATE), then create the functions. Both reference the tables the DDL
4518
6103
  // above creates, so they close the plan.
4519
- ...actionPrepareChecks(definition).map((sql4) => ({ kind: "function", sql: sql4 })),
4520
- ...actionFunctionStatements(definition).map((sql4) => ({ kind: "function", sql: sql4 }))
6104
+ ...actionPrepareChecks(definition).map((sql7) => ({ kind: "function", sql: sql7 })),
6105
+ ...actionFunctionStatements(definition).map((sql7) => ({ kind: "function", sql: sql7 }))
4521
6106
  ];
4522
6107
  return { namespace: definition.namespace, schema, statements, snapshot };
4523
6108
  }
4524
6109
  var compileNamespace = (definition) => planMigration({ definition });
4525
6110
  var previousId2 = (snapshot) => snapshot.id;
4526
- var IR_VERSION = 1;
6111
+ var ORM_MIGRATION_EXPORT_FILE = "orm.migration.json";
6112
+ var ORM_MIGRATION_EXPORT_MAX_BYTES = ORM_REGISTRATION_MAX_BYTES;
6113
+ var hash = zod.z.string().regex(/^sha256:[0-9a-f]{64}$/);
6114
+ var ormMigrationPayloadSchema = zod.z.object({
6115
+ canonical_definition: canonicalDefinitionSchema,
6116
+ documents: zod.z.array(zod.z.object({ path: zod.z.string().min(1).max(2048), contents: zod.z.string().max(1e6) }).strict()).max(5e3),
6117
+ client_manifest: zod.z.object({ ops_hash: hash, operations: zod.z.record(zod.z.string(), hash) }).strict(),
6118
+ sdl: zod.z.string().refine((value) => Buffer.byteLength(value, "utf8") <= MAX_GENERATED_SDL_BYTES, {
6119
+ message: `sdl exceeds ${MAX_GENERATED_SDL_BYTES} bytes`
6120
+ }),
6121
+ schema_snapshot: zod.z.json(),
6122
+ // The existing ORM committed-migration validator and replay engine remain
6123
+ // authoritative. This envelope hashes the provenance, never authorizes SQL.
6124
+ migration: zod.z.json().optional()
6125
+ }).strict();
6126
+ var ormMigrationExportSchema = zod.z.object({
6127
+ schema_version: zod.z.literal(1),
6128
+ schema_hash: hash,
6129
+ payload: ormMigrationPayloadSchema,
6130
+ checksum: zod.z.string().regex(/^[0-9a-f]{64}$/)
6131
+ }).strict();
6132
+ function checksum(value) {
6133
+ return crypto.createHash("sha256").update(stableJson(value)).digest("hex");
6134
+ }
6135
+ function createOrmMigrationExport({ schemaHash, payload }) {
6136
+ const contents = { schema_version: 1, schema_hash: hash.parse(schemaHash), payload: ormMigrationPayloadSchema.parse(payload) };
6137
+ return { ...contents, checksum: checksum(contents) };
6138
+ }
6139
+ function parseOrmMigrationExport(text4) {
6140
+ if (Buffer.byteLength(text4, "utf8") > ORM_MIGRATION_EXPORT_MAX_BYTES) throw new Error("ORM migration export exceeds the size limit");
6141
+ const exported = ormMigrationExportSchema.parse(JSON.parse(text4));
6142
+ const { checksum: expected, ...contents } = exported;
6143
+ if (checksum(contents) !== expected) throw new Error("ORM migration export checksum mismatch; regenerate and commit it");
6144
+ return exported;
6145
+ }
6146
+ function renderOrmMigrationExport(exported) {
6147
+ const text4 = `${JSON.stringify(exported, null, 2)}
6148
+ `;
6149
+ if (Buffer.byteLength(text4, "utf8") > ORM_MIGRATION_EXPORT_MAX_BYTES) throw new Error("ORM migration export exceeds the size limit");
6150
+ return text4;
6151
+ }
6152
+ var IR_VERSION = 2;
6153
+ var LEGACY_IR_VERSION = 1;
6154
+ var irVersionSchema = zod.z.union([zod.z.literal(LEGACY_IR_VERSION), zod.z.literal(IR_VERSION)]);
6155
+ var operationVersionSchema = zod.z.object({ irVersion: irVersionSchema });
6156
+ function requiredArtifactIrVersion(operations) {
6157
+ let required = LEGACY_IR_VERSION;
6158
+ for (const operation of operations) {
6159
+ const { irVersion } = operationVersionSchema.parse(operation);
6160
+ if (irVersion > required) required = irVersion;
6161
+ }
6162
+ return required;
6163
+ }
4527
6164
  var CLAIMS_CONTRACT = {
4528
6165
  sub: "string",
4529
6166
  org: "string?",
@@ -4595,6 +6232,21 @@ var selectionIrSchema = zod.z.lazy(
4595
6232
  () => zod.z.object({
4596
6233
  typename: zod.z.string(),
4597
6234
  columns: zod.z.array(zod.z.object({ alias: zod.z.string(), column: zod.z.string() })),
6235
+ cells: zod.z.object({
6236
+ alias: zod.z.string(),
6237
+ typename: zod.z.string(),
6238
+ fields: zod.z.array(zod.z.object({
6239
+ alias: zod.z.string(),
6240
+ column: zod.z.string(),
6241
+ typename: zod.z.string(),
6242
+ parts: zod.z.array(zod.z.object({
6243
+ alias: zod.z.string(),
6244
+ part: zod.z.enum(["source", "at", "by", "conflicts"]),
6245
+ fields: zod.z.array(zod.z.object({ alias: zod.z.string(), field: zod.z.enum(["source", "at", "value"]) })).optional(),
6246
+ typename: zod.z.string().optional()
6247
+ }))
6248
+ }))
6249
+ }).optional(),
4598
6250
  relations: zod.z.array(zod.z.object({
4599
6251
  alias: zod.z.string(),
4600
6252
  link: zod.z.string(),
@@ -4616,6 +6268,11 @@ var selectionIrSchema = zod.z.lazy(
4616
6268
  })
4617
6269
  );
4618
6270
  var includeIfSchema = zod.z.object({ variable: zod.z.string(), negated: zod.z.boolean() });
6271
+ var prefetchPlanIrSchema = zod.z.object({
6272
+ source: zod.z.string(),
6273
+ maxStalenessMs: zod.z.number().int().positive().optional(),
6274
+ key: zod.z.union([zod.z.object({ one: valueIrSchema }), zod.z.object({ many: valueIrSchema })])
6275
+ });
4619
6276
  var selectRootSchema = zod.z.object({
4620
6277
  kind: zod.z.literal("select"),
4621
6278
  alias: zod.z.string(),
@@ -4627,7 +6284,8 @@ var selectRootSchema = zod.z.object({
4627
6284
  distinctOn: zod.z.union([zod.z.array(zod.z.string()), zod.z.object({ slot: zod.z.string() })]).optional(),
4628
6285
  limit: limitIrSchema,
4629
6286
  offset: zod.z.object({ variable: zod.z.string().optional(), value: zod.z.number().int().optional() }).optional(),
4630
- includeIf: includeIfSchema.optional()
6287
+ includeIf: includeIfSchema.optional(),
6288
+ prefetch: zod.z.array(prefetchPlanIrSchema).optional()
4631
6289
  });
4632
6290
  var byPkRootSchema = zod.z.object({
4633
6291
  kind: zod.z.literal("byPk"),
@@ -4637,7 +6295,8 @@ var byPkRootSchema = zod.z.object({
4637
6295
  pk: zod.z.array(zod.z.object({ column: zod.z.string(), value: valueIrSchema })).min(1),
4638
6296
  /** Policy predicate — ANDed with the PK match. */
4639
6297
  where: predIrSchema,
4640
- includeIf: includeIfSchema.optional()
6298
+ includeIf: includeIfSchema.optional(),
6299
+ prefetch: zod.z.array(prefetchPlanIrSchema).optional()
4641
6300
  });
4642
6301
  var aggregateRootSchema = zod.z.object({
4643
6302
  kind: zod.z.literal("aggregate"),
@@ -4650,6 +6309,33 @@ var aggregateRootSchema = zod.z.object({
4650
6309
  nodes: selectionIrSchema.optional(),
4651
6310
  includeIf: includeIfSchema.optional()
4652
6311
  });
6312
+ var DATE_BUCKETS = ["year", "quarter", "month", "week", "day", "hour"];
6313
+ var groupByKeyIrSchema = zod.z.object({
6314
+ column: zod.z.string(),
6315
+ bucket: zod.z.enum(DATE_BUCKETS).optional()
6316
+ });
6317
+ var aggregateGroupsRootSchema = zod.z.object({
6318
+ kind: zod.z.literal("aggregateGroups"),
6319
+ alias: zod.z.string(),
6320
+ table: zod.z.string(),
6321
+ /** Composed at compile time: AND(policy, documentWhere?, dynamicFilter?). */
6322
+ where: predIrSchema,
6323
+ groupBy: zod.z.array(groupByKeyIrSchema).min(1),
6324
+ /**
6325
+ * The `key { … }` projection: which group columns appear in each entry's key
6326
+ * object, in selection order. A subset of `groupBy` (the compiler rejects a
6327
+ * key field that is not a group key — it would be indeterminate within the
6328
+ * group). Empty when the document selects only `aggregate`.
6329
+ */
6330
+ key: zod.z.array(zod.z.object({ alias: zod.z.string(), column: zod.z.string() })).default([]),
6331
+ count: zod.z.object({ columns: zod.z.array(zod.z.string()).optional(), distinct: zod.z.boolean().optional() }).optional(),
6332
+ sum: zod.z.array(zod.z.string()).optional(),
6333
+ avg: zod.z.array(zod.z.string()).optional(),
6334
+ /** Bounds the GROUP count, not the scanned rows — see MAX_GROUPS. */
6335
+ limit: limitIrSchema,
6336
+ offset: zod.z.object({ variable: zod.z.string().optional(), value: zod.z.number().int().optional() }).optional(),
6337
+ includeIf: includeIfSchema.optional()
6338
+ });
4653
6339
  var writeValuesSchema = zod.z.union([
4654
6340
  zod.z.object({ slot: zod.z.string() }),
4655
6341
  zod.z.object({ entries: zod.z.array(zod.z.object({ column: zod.z.string(), value: valueIrSchema })) })
@@ -4668,7 +6354,20 @@ var mutationRootSchema = zod.z.object({
4668
6354
  * coalesce view falls back to synced. Both are gated on a matching synced
4669
6355
  * row and carry the write policy in `where` like update/delete.
4670
6356
  */
4671
- op: zod.z.enum(["insert", "insertOne", "update", "updateByPk", "delete", "deleteByPk", "cowOverride", "cowRevert"]),
6357
+ op: zod.z.enum(["insert", "insertOne", "update", "updateByPk", "delete", "deleteByPk", "cowOverride", "cowRevert", "resolvedWrite"]),
6358
+ /**
6359
+ * resolvedWrite: a write to a resolved table (`table` is its view). The
6360
+ * executor gates on the resolved row (write policy in `where`), writes only
6361
+ * the touched cells into the manual source, then re-checks `insertCheck`
6362
+ * against the resolved row. `revert` clears `revertColumns` from the manual
6363
+ * source; `delete` tombstones the row there.
6364
+ */
6365
+ resolvedAction: zod.z.enum(["insert", "update", "delete", "revert"]).optional(),
6366
+ revertColumns: zod.z.array(zod.z.string()).optional(),
6367
+ /** `@writeback(source:, mode:)` directives on this root. */
6368
+ writeback: zod.z.array(zod.z.object({ source: zod.z.string(), mode: zod.z.enum(["sync", "async"]) })).optional(),
6369
+ /** `@localOnly(source:)` — writeback columns deliberately kept local for these sources. */
6370
+ localOnly: zod.z.array(zod.z.string()).optional(),
4672
6371
  alias: zod.z.string(),
4673
6372
  table: zod.z.string(),
4674
6373
  /** CoW ops: logical name of the synced twin (read through its policy-filtered view). */
@@ -4695,12 +6394,15 @@ var mutationRootSchema = zod.z.object({
4695
6394
  insertCheck: predIrSchema.optional(),
4696
6395
  returning: selectionIrSchema.nullable(),
4697
6396
  affectedRows: zod.z.boolean().default(false),
6397
+ /** v2: abort the transaction when the actual write count differs, regardless of selection. */
6398
+ assertAffectedRows: zod.z.number().int().min(0).max(2147483647).optional(),
4698
6399
  includeIf: includeIfSchema.optional()
4699
6400
  });
4700
6401
  var rootIrSchema = zod.z.discriminatedUnion("kind", [
4701
6402
  selectRootSchema,
4702
6403
  byPkRootSchema,
4703
6404
  aggregateRootSchema,
6405
+ aggregateGroupsRootSchema,
4704
6406
  mutationRootSchema
4705
6407
  ]);
4706
6408
  var INPUT_REF_KINDS = [
@@ -4730,7 +6432,7 @@ var variableIrSchema = zod.z.object({
4730
6432
  defaultValue: variableDefaultIrSchema.optional()
4731
6433
  });
4732
6434
  var compiledOperationSchema = zod.z.object({
4733
- irVersion: zod.z.literal(IR_VERSION),
6435
+ irVersion: irVersionSchema,
4734
6436
  name: zod.z.string(),
4735
6437
  opHash: zod.z.string().regex(/^sha256:[0-9a-f]{64}$/),
4736
6438
  kind: zod.z.enum(["query", "mutation"]),
@@ -4742,6 +6444,50 @@ var compiledOperationSchema = zod.z.object({
4742
6444
  /** Every table the op touches — data-token scoping + audit. */
4743
6445
  tableSet: zod.z.array(zod.z.string()).min(1),
4744
6446
  roots: zod.z.array(rootIrSchema).min(1)
6447
+ }).superRefine((operation, ctx) => {
6448
+ for (const [index3, root] of operation.roots.entries()) {
6449
+ if (root.kind !== "mutation" || root.assertAffectedRows === void 0) continue;
6450
+ if (operation.irVersion < 2 || operation.kind !== "mutation" || root.includeIf !== void 0) {
6451
+ ctx.addIssue({ code: "custom", path: ["roots", index3], message: "affected-row assertions require unconditional mutation roots and IR v2" });
6452
+ }
6453
+ }
6454
+ });
6455
+ var resolvedSourceIrSchema = zod.z.object({
6456
+ name: zod.z.string(),
6457
+ kind: zod.z.enum(["manual", "etl", "prefetch"]),
6458
+ /** Logical table key of the backing relation. */
6459
+ table: zod.z.string(),
6460
+ columns: zod.z.array(zod.z.string()),
6461
+ key: zod.z.string().optional(),
6462
+ fetch: zod.z.union([
6463
+ zod.z.object({ shape: zod.z.literal("single") }),
6464
+ zod.z.object({ shape: zod.z.literal("batch"), maxKeys: zod.z.number().int().positive() })
6465
+ ]).optional(),
6466
+ function: zod.z.object({ kind: zod.z.enum(["managed", "native"]), id: zod.z.string() }).optional(),
6467
+ prefetchDefaults: zod.z.object({
6468
+ maxStalenessMs: zod.z.number().int().positive(),
6469
+ timeoutMs: zod.z.number().int().positive(),
6470
+ onError: zod.z.enum(["fallback", "fail"])
6471
+ }).optional(),
6472
+ writeback: zod.z.object({
6473
+ columns: zod.z.array(zod.z.string()),
6474
+ defaultMode: zod.z.enum(["sync", "async"]),
6475
+ timeoutMs: zod.z.number().int().positive(),
6476
+ clearManualOnConfirm: zod.z.boolean()
6477
+ }).optional()
6478
+ });
6479
+ var resolvedMetaIrSchema = zod.z.object({
6480
+ /** Logical pk column of the resolved view (single-column). */
6481
+ pk: zod.z.string(),
6482
+ /** Source name of the manual source. */
6483
+ manual: zod.z.string(),
6484
+ sources: zod.z.array(resolvedSourceIrSchema),
6485
+ deletedBy: zod.z.array(zod.z.string()),
6486
+ /**
6487
+ * Materialized: the view reads `table`, which the data plane refreshes from
6488
+ * the `compute` view (claims-gated) whenever it writes a source row.
6489
+ */
6490
+ materialized: zod.z.object({ table: zod.z.string(), compute: zod.z.string() }).optional()
4745
6491
  });
4746
6492
  var tableMetaIrSchema = zod.z.object({
4747
6493
  table: zod.z.string(),
@@ -4778,18 +6524,45 @@ var tableMetaIrSchema = zod.z.object({
4778
6524
  insertable: zod.z.array(zod.z.string()),
4779
6525
  settable: zod.z.array(zod.z.string()),
4780
6526
  appendable: zod.z.array(zod.z.string()),
6527
+ /**
6528
+ * Logical columns stamped with Postgres `now()` on UPDATE (canonical
6529
+ * `onUpdateFn`). Omitted when empty so pre-onUpdate artifacts still parse.
6530
+ */
6531
+ touchOnUpdate: zod.z.array(zod.z.string()).optional(),
6532
+ /**
6533
+ * Per-cell metadata: logical column → the logical names of its cell-part
6534
+ * columns (which are listed in `columns`, but never filterable, orderable or
6535
+ * writable). Tables stamp `at`/`by` (and a manual source `rev`) on every
6536
+ * write; resolved views expose `source`/`at`/`by`/`conflicts`. Omitted when
6537
+ * the relation tracks no cells.
6538
+ */
6539
+ cells: zod.z.record(zod.z.string(), zod.z.object({
6540
+ at: zod.z.string().optional(),
6541
+ by: zod.z.string().optional(),
6542
+ rev: zod.z.string().optional(),
6543
+ source: zod.z.string().optional(),
6544
+ conflicts: zod.z.string().optional()
6545
+ }).strict()).optional(),
6546
+ /** Resolved-table view metadata (sources, prefetch/writeback config) for the data plane. */
6547
+ resolved: resolvedMetaIrSchema.optional(),
4781
6548
  constraints: zod.z.array(zod.z.object({ name: zod.z.string(), columns: zod.z.array(zod.z.string()) })),
4782
6549
  /** Depth/node caps for dynamic filter trees on this table. */
4783
6550
  dynamicFilterMaxDepth: zod.z.number().int().positive().default(6),
4784
6551
  dynamicFilterMaxNodes: zod.z.number().int().positive().default(64)
4785
6552
  });
4786
6553
  var compiledArtifactSchema = zod.z.object({
4787
- irVersion: zod.z.literal(IR_VERSION),
6554
+ irVersion: irVersionSchema,
4788
6555
  namespace: zod.z.string(),
4789
6556
  schemaHash: zod.z.string(),
4790
6557
  opsHash: zod.z.string(),
4791
6558
  tables: zod.z.record(zod.z.string(), tableMetaIrSchema),
4792
6559
  operations: zod.z.record(zod.z.string(), compiledOperationSchema)
6560
+ }).superRefine((artifact, ctx) => {
6561
+ for (const [name, operation] of Object.entries(artifact.operations)) {
6562
+ if (operation.irVersion > artifact.irVersion) {
6563
+ ctx.addIssue({ code: "custom", path: ["operations", name, "irVersion"], message: "operation requires a newer IR version than its artifact" });
6564
+ }
6565
+ }
4793
6566
  });
4794
6567
  var IrVersionError = class extends Error {
4795
6568
  constructor(found) {
@@ -4800,7 +6573,7 @@ var IrVersionError = class extends Error {
4800
6573
  function normalizeIr(raw) {
4801
6574
  if (!raw || typeof raw !== "object") throw new IrVersionError(void 0);
4802
6575
  const version = raw.irVersion;
4803
- if (version !== IR_VERSION) throw new IrVersionError(version);
6576
+ if (version !== LEGACY_IR_VERSION && version !== IR_VERSION) throw new IrVersionError(version);
4804
6577
  return compiledArtifactSchema.parse(raw);
4805
6578
  }
4806
6579
  function indexByHash(artifact) {
@@ -5116,12 +6889,12 @@ async function writeMigration({
5116
6889
  const tip = workspace.journal.entries.at(-1);
5117
6890
  const ordinal = (tip?.ordinal ?? 0) + 1;
5118
6891
  const name = migrationName(ordinal, contentHash);
5119
- const sql4 = renderMigrationSql({ name, namespace: workspace.journal.namespace, contentHash, plan });
6892
+ const sql7 = renderMigrationSql({ name, namespace: workspace.journal.namespace, contentHash, plan });
5120
6893
  const metaDir = path.join(dir, MIGRATIONS_DIRNAME, META_DIRNAME);
5121
6894
  await promises.mkdir(metaDir, { recursive: true });
5122
6895
  const snapshotText = `${JSON.stringify(plan.snapshot, null, 2)}
5123
6896
  `;
5124
- await promises.writeFile(path.join(dir, MIGRATIONS_DIRNAME, `${name}.sql`), sql4);
6897
+ await promises.writeFile(path.join(dir, MIGRATIONS_DIRNAME, `${name}.sql`), sql7);
5125
6898
  await promises.writeFile(path.join(metaDir, `${pad(ordinal)}.snapshot.json`), snapshotText);
5126
6899
  const journal = {
5127
6900
  ...workspace.journal,
@@ -5132,7 +6905,7 @@ async function writeMigration({
5132
6905
  name,
5133
6906
  content_hash: contentHash,
5134
6907
  previous_content_hash: tip?.content_hash ?? null,
5135
- checksum: checksumOf(sql4),
6908
+ checksum: checksumOf(sql7),
5136
6909
  snapshot_checksum: checksumOf(snapshotText),
5137
6910
  destructive
5138
6911
  }
@@ -5247,8 +7020,8 @@ async function checkWorkspace({
5247
7020
  let previousId3;
5248
7021
  let previousHash = null;
5249
7022
  for (const entry of workspace.journal.entries) {
5250
- const sql4 = await readMigrationSql(dir, entry);
5251
- if (checksumOf(sql4) !== entry.checksum) {
7023
+ const sql7 = await readMigrationSql(dir, entry);
7024
+ if (checksumOf(sql7) !== entry.checksum) {
5252
7025
  log(`${MIGRATIONS_DIRNAME}/${entry.name}.sql does not match its journal checksum \u2014 committed migrations must not be edited`);
5253
7026
  return 1;
5254
7027
  }
@@ -5288,7 +7061,7 @@ async function checkWorkspace({
5288
7061
  log(`migrations in sync \u2014 ${workspace.journal.entries.length} committed, tip ${tip.name}`);
5289
7062
  return 0;
5290
7063
  }
5291
- var firstLine = (sql4) => sql4.split("\n")[0] ?? sql4;
7064
+ var firstLine = (sql7) => sql7.split("\n")[0] ?? sql7;
5292
7065
 
5293
7066
  // src/migrations/from-yaml.ts
5294
7067
  function convertV1DefinitionToCanonical(definition) {
@@ -5394,7 +7167,7 @@ function buildRebaseJournalEntry({ namespace, previousHash, newHash, ordinal, sn
5394
7167
  throw new OrmDefinitionError(`rebase ordinal must be an integer \u2265 2 (a rebase sits on an existing journal tip); got ${ordinal}`);
5395
7168
  }
5396
7169
  const name = migrationName(ordinal, newHash);
5397
- const sql4 = renderRebaseMigrationSql({ name, namespace, previousHash, newHash });
7170
+ const sql7 = renderRebaseMigrationSql({ name, namespace, previousHash, newHash });
5398
7171
  const snapshotText = `${JSON.stringify(snapshot, null, 2)}
5399
7172
  `;
5400
7173
  const entry = {
@@ -5402,11 +7175,11 @@ function buildRebaseJournalEntry({ namespace, previousHash, newHash, ordinal, sn
5402
7175
  name,
5403
7176
  content_hash: newHash,
5404
7177
  previous_content_hash: previousHash,
5405
- checksum: checksumOf(sql4),
7178
+ checksum: checksumOf(sql7),
5406
7179
  snapshot_checksum: checksumOf(snapshotText),
5407
7180
  destructive: false
5408
7181
  };
5409
- return { name, entry, sql: sql4, snapshotText };
7182
+ return { name, entry, sql: sql7, snapshotText };
5410
7183
  }
5411
7184
  function renderRebaseMigrationSql({
5412
7185
  name,
@@ -5530,16 +7303,21 @@ exports.AUDIT_ACTIONS = AUDIT_ACTIONS;
5530
7303
  exports.CANONICAL_FORMAT = CANONICAL_FORMAT;
5531
7304
  exports.CAPABILITIES_CLAIM_KEY = CAPABILITIES_CLAIM_KEY;
5532
7305
  exports.CAPABILITY_KEY_PATTERN = CAPABILITY_KEY_PATTERN;
7306
+ exports.CELLS_FIELD = CELLS_FIELD;
7307
+ exports.CELL_PARTS = CELL_PARTS;
7308
+ exports.CELL_PART_SUFFIX = CELL_PART_SUFFIX;
5533
7309
  exports.CLAIMS_CONTRACT = CLAIMS_CONTRACT;
5534
7310
  exports.CLAIMS_SETTING = CLAIMS_SETTING;
5535
7311
  exports.CLAIM_KEY_PATTERN = CLAIM_KEY_PATTERN;
5536
7312
  exports.CMP_OPS = CMP_OPS;
5537
7313
  exports.COLUMN_BRAND = COLUMN_BRAND;
5538
7314
  exports.COLUMN_DEFAULT_FNS = COLUMN_DEFAULT_FNS;
7315
+ exports.COLUMN_IDENTIFIER_PATTERN = COLUMN_IDENTIFIER_PATTERN;
5539
7316
  exports.COLUMN_TYPES = COLUMN_TYPES2;
5540
7317
  exports.COMPARE_OPS = COMPARE_OPS;
5541
7318
  exports.CONSUMER_ROLE_SUFFIX = CONSUMER_ROLE_SUFFIX;
5542
7319
  exports.ColumnBuilder = ColumnBuilder;
7320
+ exports.DATE_BUCKETS = DATE_BUCKETS;
5543
7321
  exports.DEFINITION_FILE_EXTENSIONS = DEFINITION_FILE_EXTENSIONS;
5544
7322
  exports.ENUM_BRAND = ENUM_BRAND;
5545
7323
  exports.HASH_DOMAIN = HASH_DOMAIN;
@@ -5548,15 +7326,23 @@ exports.IDENTIFIER_RE = IDENTIFIER_RE;
5548
7326
  exports.INPUT_REF_KINDS = INPUT_REF_KINDS;
5549
7327
  exports.IR_VERSION = IR_VERSION;
5550
7328
  exports.IrVersionError = IrVersionError;
7329
+ exports.LEGACY_IR_VERSION = LEGACY_IR_VERSION;
5551
7330
  exports.LINK_BRAND = LINK_BRAND;
7331
+ exports.MATERIALIZE_ACTOR = MATERIALIZE_ACTOR;
7332
+ exports.MATERIALIZE_CLAIMS_JSON = MATERIALIZE_CLAIMS_JSON;
5552
7333
  exports.MAX_DEFINITION_FILE_BYTES = MAX_DEFINITION_FILE_BYTES;
7334
+ exports.MAX_GENERATED_SDL_BYTES = MAX_GENERATED_SDL_BYTES;
5553
7335
  exports.MAX_IDENTIFIER_LENGTH = MAX_IDENTIFIER_LENGTH;
7336
+ exports.MAX_TRACKED_COLUMN_NAME = MAX_TRACKED_COLUMN_NAME;
5554
7337
  exports.MIGRATIONS_DIRNAME = MIGRATIONS_DIRNAME;
5555
7338
  exports.NAMESPACE_CONFIG_BRAND = NAMESPACE_CONFIG_BRAND;
5556
7339
  exports.NAMESPACE_CONFIG_FILE = NAMESPACE_CONFIG_FILE;
5557
7340
  exports.NAMESPACE_CONFIG_FILENAMES = NAMESPACE_CONFIG_FILENAMES;
5558
7341
  exports.NAMESPACE_PATTERN = NAMESPACE_PATTERN;
5559
7342
  exports.ORM_DATA_TOKEN_HEADER = ORM_DATA_TOKEN_HEADER;
7343
+ exports.ORM_MIGRATION_EXPORT_FILE = ORM_MIGRATION_EXPORT_FILE;
7344
+ exports.ORM_MIGRATION_EXPORT_MAX_BYTES = ORM_MIGRATION_EXPORT_MAX_BYTES;
7345
+ exports.ORM_REGISTRATION_MAX_BYTES = ORM_REGISTRATION_MAX_BYTES;
5560
7346
  exports.ORM_SCHEMA_PREFIX = ORM_SCHEMA_PREFIX;
5561
7347
  exports.ORM_SERVICE_DATA_TOKEN_HEADER = ORM_SERVICE_DATA_TOKEN_HEADER;
5562
7348
  exports.ORM_TO_PIPELINE_TYPE = ORM_TO_PIPELINE_TYPE;
@@ -5604,6 +7390,7 @@ exports.canonicalViewSchema = canonicalViewSchema;
5604
7390
  exports.canonicalizeDefinition = canonicalizeDefinition;
5605
7391
  exports.capabilityExpression = capabilityExpression;
5606
7392
  exports.castParts = castParts;
7393
+ exports.cellColumnName = cellColumnName;
5607
7394
  exports.checksumOf = checksumOf;
5608
7395
  exports.claimExpression = claimExpression;
5609
7396
  exports.classifyDestructive = classifyDestructive;
@@ -5621,9 +7408,12 @@ exports.computeDefinitionHash = computeDefinitionHash;
5621
7408
  exports.computeSyncedContractHash = computeSyncedContractHash;
5622
7409
  exports.consumerRoleName = consumerRoleName;
5623
7410
  exports.convertV1DefinitionToCanonical = convertV1DefinitionToCanonical;
7411
+ exports.createOrmMigrationExport = createOrmMigrationExport;
7412
+ exports.custom = custom;
5624
7413
  exports.declaredCapabilityKeys = declaredCapabilityKeys;
5625
7414
  exports.defineEnum = defineEnum;
5626
7415
  exports.defineNamespace = defineNamespace;
7416
+ exports.defineResolvedTable = defineResolvedTable;
5627
7417
  exports.defineSyncedTable = defineSyncedTable;
5628
7418
  exports.defineTable = defineTable;
5629
7419
  exports.defineView = defineView;
@@ -5633,11 +7423,13 @@ exports.diffStatements = diffStatements;
5633
7423
  exports.emptyJournal = emptyJournal;
5634
7424
  exports.emptySnapshot = emptySnapshot;
5635
7425
  exports.endAuthoringContext = endAuthoringContext;
7426
+ exports.etlSource = etlSource;
5636
7427
  exports.extractNamedParams = extractNamedParams;
5637
7428
  exports.extractOrmSyncedTableContract = extractOrmSyncedTableContract;
5638
7429
  exports.generateNamespaceTypes = generateNamespaceTypes;
5639
7430
  exports.generateSnapshot = generateSnapshot;
5640
7431
  exports.generateSnapshotV2 = generateSnapshotV2;
7432
+ exports.highest = highest;
5641
7433
  exports.identifierSchema = identifierSchema;
5642
7434
  exports.indexByHash = indexByHash;
5643
7435
  exports.isColumnBuilder = isColumnBuilder;
@@ -5650,34 +7442,47 @@ exports.isSeqLink = isSeqLink;
5650
7442
  exports.isSeqNamespaceConfig = isSeqNamespaceConfig;
5651
7443
  exports.isSeqTable = isSeqTable;
5652
7444
  exports.isSeqView = isSeqView;
7445
+ exports.latest = latest;
5653
7446
  exports.limitIrSchema = limitIrSchema;
5654
7447
  exports.loadCommittedMigrations = loadCommittedMigrations;
5655
7448
  exports.loadCommittedTipMigration = loadCommittedTipMigration;
5656
7449
  exports.loadNamespaceDefinition = loadNamespaceDefinition;
5657
7450
  exports.loadNamespaceFromDir = loadNamespaceFromDir;
7451
+ exports.lowest = lowest;
7452
+ exports.managedFn = managedFn;
7453
+ exports.manualSource = manualSource;
7454
+ exports.materializeLockKey = materializeLockKey;
7455
+ exports.materializedRefreshFunctionName = materializedRefreshFunctionName;
5658
7456
  exports.migrationName = migrationName;
5659
7457
  exports.namespaceConfigSchema = namespaceConfigSchema;
5660
7458
  exports.namespaceSchema = namespaceSchema;
7459
+ exports.nonNullFirst = nonNullFirst;
5661
7460
  exports.normalizeCanonical = normalizeCanonical;
5662
7461
  exports.normalizeIr = normalizeIr;
5663
7462
  exports.normalizeSnapshot = normalizeSnapshot;
5664
7463
  exports.orderIrSchema = orderIrSchema;
7464
+ exports.ormMigrationExportSchema = ormMigrationExportSchema;
5665
7465
  exports.ormSchemaName = ormSchemaName;
5666
7466
  exports.overlayWinsUnion = overlayWinsUnion;
5667
7467
  exports.overrideRowMutation = overrideRowMutation;
5668
7468
  exports.parseCanonicalDefinition = parseCanonicalDefinition;
5669
7469
  exports.parseDefinitionSources = parseDefinitionSources;
7470
+ exports.parseOrmMigrationExport = parseOrmMigrationExport;
5670
7471
  exports.parsePredicate = parsePredicate;
5671
7472
  exports.parseTableFile = parseTableFile;
5672
7473
  exports.parsedPredicateSchema = parsedPredicateSchema;
5673
7474
  exports.pendingKitStatements = pendingKitStatements;
5674
7475
  exports.pendingKitStatementsV2 = pendingKitStatementsV2;
7476
+ exports.planCommittedMigrationV2 = planCommittedMigrationV2;
5675
7477
  exports.planMigration = planMigration;
5676
7478
  exports.planMigrationV2 = planMigrationV2;
5677
7479
  exports.policyExpressionSchema = policyExpressionSchema;
5678
7480
  exports.policyVariantSchema = policyVariantSchema;
5679
7481
  exports.predIrSchema = predIrSchema;
5680
7482
  exports.predicateOperands = predicateOperands;
7483
+ exports.prefetchPlanIrSchema = prefetchPlanIrSchema;
7484
+ exports.prefetchSource = prefetchSource;
7485
+ exports.priority = priority;
5681
7486
  exports.quoteIdentifier = quoteIdentifier;
5682
7487
  exports.quoteLiteral = quoteLiteral;
5683
7488
  exports.readMigrationSql = readMigrationSql;
@@ -5687,6 +7492,10 @@ exports.rebaseViolations = rebaseViolations;
5687
7492
  exports.referencedCapabilities = referencedCapabilities;
5688
7493
  exports.referencedColumns = referencedColumns;
5689
7494
  exports.renderMigrationSql = renderMigrationSql;
7495
+ exports.renderOrmMigrationExport = renderOrmMigrationExport;
7496
+ exports.requiredArtifactIrVersion = requiredArtifactIrVersion;
7497
+ exports.resolvedMetaIrSchema = resolvedMetaIrSchema;
7498
+ exports.resolvedSourceIrSchema = resolvedSourceIrSchema;
5690
7499
  exports.revertOverlayMutation = revertOverlayMutation;
5691
7500
  exports.rewriteSyncedSourceRelationsInViewQuery = rewriteSyncedSourceRelationsInViewQuery;
5692
7501
  exports.roleExpression = roleExpression;