@sequenceholdings/orm 2.1.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +38 -1
  2. package/dist/authoring.cjs +1358 -45
  3. package/dist/authoring.cjs.map +1 -1
  4. package/dist/authoring.d.cts +223 -2
  5. package/dist/authoring.d.ts +223 -2
  6. package/dist/authoring.js +1349 -47
  7. package/dist/authoring.js.map +1 -1
  8. package/dist/canonical.cjs +662 -38
  9. package/dist/canonical.cjs.map +1 -1
  10. package/dist/canonical.d.cts +696 -53
  11. package/dist/canonical.d.ts +696 -53
  12. package/dist/canonical.js +647 -39
  13. package/dist/canonical.js.map +1 -1
  14. package/dist/cli.cjs +1808 -247
  15. package/dist/cli.cjs.map +1 -1
  16. package/dist/cli.js +1809 -248
  17. package/dist/cli.js.map +1 -1
  18. package/dist/client.cjs +90 -25
  19. package/dist/client.cjs.map +1 -1
  20. package/dist/client.d.cts +39 -22
  21. package/dist/client.d.ts +39 -22
  22. package/dist/client.js +87 -26
  23. package/dist/client.js.map +1 -1
  24. package/dist/compiler.cjs +1325 -285
  25. package/dist/compiler.cjs.map +1 -1
  26. package/dist/compiler.d.cts +23 -2
  27. package/dist/compiler.d.ts +23 -2
  28. package/dist/compiler.js +1325 -285
  29. package/dist/compiler.js.map +1 -1
  30. package/dist/constants-YoFGjIBM.d.cts +99 -0
  31. package/dist/constants-YoFGjIBM.d.ts +99 -0
  32. package/dist/dynamic-input.d.cts +1 -0
  33. package/dist/dynamic-input.d.ts +1 -0
  34. package/dist/index.cjs +1605 -140
  35. package/dist/index.cjs.map +1 -1
  36. package/dist/index.d.cts +230 -113
  37. package/dist/index.d.ts +230 -113
  38. package/dist/index.js +1583 -142
  39. package/dist/index.js.map +1 -1
  40. package/dist/ir.cjs +727 -38
  41. package/dist/ir.cjs.map +1 -1
  42. package/dist/ir.d.cts +539 -21
  43. package/dist/ir.d.ts +539 -21
  44. package/dist/ir.js +722 -39
  45. package/dist/ir.js.map +1 -1
  46. package/dist/{naming-DCn94LJ4.d.ts → naming-CPfx7EfF.d.ts} +14 -1
  47. package/dist/{naming-DPglwzkB.d.cts → naming-CU9ThP_E.d.cts} +14 -1
  48. package/dist/policy-1iZbGnZZ.d.cts +24 -0
  49. package/dist/policy-1iZbGnZZ.d.ts +24 -0
  50. package/dist/predicate-v2.cjs +726 -42
  51. package/dist/predicate-v2.cjs.map +1 -1
  52. package/dist/predicate-v2.d.cts +2 -1
  53. package/dist/predicate-v2.d.ts +2 -1
  54. package/dist/predicate-v2.js +726 -42
  55. package/dist/predicate-v2.js.map +1 -1
  56. package/dist/sdl.cjs +858 -54
  57. package/dist/sdl.cjs.map +1 -1
  58. package/dist/sdl.d.cts +7 -3
  59. package/dist/sdl.d.ts +7 -3
  60. package/dist/sdl.js +856 -55
  61. package/dist/sdl.js.map +1 -1
  62. package/package.json +9 -4
  63. package/templates/orm-namespace/CLAUDE.md +6 -4
  64. package/templates/orm-namespace/codegen.preset.d.cts +7 -0
  65. package/templates/orm-namespace/codegen.ts +2 -1
  66. package/templates/orm-namespace/graphql/example/queries.ts +4 -3
  67. package/templates/orm-namespace/package.json +2 -1
  68. package/templates/orm-namespace/schema/tables.ts +3 -2
  69. package/templates/orm-namespace/sequence.config.ts +7 -11
package/dist/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');
@@ -40,7 +40,8 @@ var ColumnBuilder = class {
40
40
  description: void 0,
41
41
  renamedFrom: void 0,
42
42
  cast: void 0,
43
- deprecated: false
43
+ deprecated: false,
44
+ cell: void 0
44
45
  };
45
46
  }
46
47
  /** Marks this column as (part of) the primary key. Implies NOT NULL. */
@@ -100,6 +101,11 @@ var ColumnBuilder = class {
100
101
  this.state.deprecated = true;
101
102
  return this;
102
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
+ }
103
109
  /** Whether `.primaryKey()` was called — collected into the table-level PK. */
104
110
  get isPrimaryKey() {
105
111
  return this.state.primaryKey;
@@ -128,6 +134,7 @@ var ColumnBuilder = class {
128
134
  if (this.state.description !== void 0) column.description = this.state.description;
129
135
  if (this.state.renamedFrom !== void 0) column.renamedFrom = this.state.renamedFrom;
130
136
  if (this.state.cast !== void 0) column.cast = this.state.cast;
137
+ if (this.state.cell !== void 0) column.cell = { ...this.state.cell };
131
138
  return column;
132
139
  }
133
140
  };
@@ -151,12 +158,12 @@ var t = {
151
158
 
152
159
  // src/constants.ts
153
160
  var NAMESPACE_PATTERN = /^[a-z][a-z0-9_]{0,40}$/;
161
+ var ORM_DATA_TOKEN_HEADER = "x-sequence-data-token";
162
+ var ORM_SERVICE_DATA_TOKEN_HEADER = "x-sequence-data-token-service";
154
163
  var IDENTIFIER_PATTERN = /^[a-z][a-z0-9_]*$/;
155
164
  var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
156
165
  var MAX_IDENTIFIER_LENGTH = 63;
157
- var ORM_DATA_TOKEN_HEADER = "x-sequence-data-token";
158
- var ORM_SERVICE_DATA_TOKEN_HEADER = "x-sequence-data-token-service";
159
- var CAPABILITY_KEY_PATTERN = /^(?:org\/)?[a-z0-9-]{1,64}$/;
166
+ var CAPABILITY_KEY_PATTERN = /^(?:org\/|app\/[a-z][a-z0-9-]{0,63}\/)?[a-z0-9-]{1,64}$/;
160
167
  var CLAIM_KEY_PATTERN = /^[a-z][a-z0-9_]*$/;
161
168
  var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]*\.[a-z][a-z0-9_]*$/;
162
169
  var SYNCED_SOURCE_PATTERN = /^gold\.[a-z][a-z0-9_]*$/;
@@ -172,6 +179,27 @@ var MAX_GENERATED_SDL_BYTES = 16 * 1024 * 1024;
172
179
  var ORM_REGISTRATION_MAX_BYTES = 64 * 1024 * 1024;
173
180
  var RESERVED_TABLE_NAMES = /* @__PURE__ */ new Set(["actions", "query"]);
174
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
+ }
175
203
 
176
204
  // src/errors.ts
177
205
  var OrmDefinitionError = class extends Error {
@@ -247,6 +275,8 @@ function toOne(target, options) {
247
275
  on: options.on,
248
276
  references: options.references,
249
277
  enforce: options.enforce ?? false,
278
+ onDelete: options.onDelete,
279
+ onUpdate: options.onUpdate,
250
280
  description: options.description
251
281
  };
252
282
  }
@@ -258,6 +288,8 @@ function toMany(target, options) {
258
288
  on: options.on,
259
289
  references: options.references,
260
290
  enforce: false,
291
+ onDelete: void 0,
292
+ onUpdate: void 0,
261
293
  description: options.description
262
294
  };
263
295
  }
@@ -269,6 +301,8 @@ function toPlatformUser(options) {
269
301
  on: options.on,
270
302
  references: options.references,
271
303
  enforce: false,
304
+ onDelete: void 0,
305
+ onUpdate: void 0,
272
306
  description: options.description
273
307
  };
274
308
  }
@@ -303,6 +337,12 @@ function defineTable(name, columns, config = {}) {
303
337
  }
304
338
  const columnName = builder.columnNameFor(propertyName);
305
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
+ }
306
346
  const existing = seenColumnNames.get(columnName);
307
347
  if (existing !== void 0) {
308
348
  throw new OrmDefinitionError(
@@ -550,11 +590,11 @@ function compileViewQueries(input) {
550
590
  );
551
591
  }
552
592
  if (!config.query) throw new OrmDefinitionError(`view "${authored.viewName}" produced no query`, authored.file);
553
- const tableDependencies = pgCore.extractUsedTable(config.query).map((physical) => {
554
- const logical = physicalToLogical.get(physical);
593
+ const tableDependencies = pgCore.extractUsedTable(config.query).map((physical2) => {
594
+ const logical = physicalToLogical.get(physical2);
555
595
  if (!logical) {
556
596
  throw new OrmDefinitionError(
557
- `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`,
558
598
  authored.file
559
599
  );
560
600
  }
@@ -878,9 +918,477 @@ function scalarColumnType(table, property, helper) {
878
918
  }
879
919
  return type;
880
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
881
1389
  var CANONICAL_FORMAT = 2;
882
1390
  var IDENTIFIER_RE = /^[a-z][a-z0-9_]*$/;
883
- 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);
884
1392
  var columnIdentifier = zod.z.string().min(1).max(63).regex(COLUMN_IDENTIFIER_PATTERN);
885
1393
  var COLUMN_TYPES = [
886
1394
  "uuid",
@@ -896,14 +1404,14 @@ var COLUMN_TYPES = [
896
1404
  ];
897
1405
  var columnTypeSchema = zod.z.enum(COLUMN_TYPES);
898
1406
  var canonicalEnumSchema = zod.z.object({
899
- name: identifier,
1407
+ name: identifier2,
900
1408
  values: zod.z.array(zod.z.string().min(1).max(63)).min(1).max(100)
901
1409
  });
902
1410
  var canonicalColumnSchema = zod.z.object({
903
1411
  /** Property name in TS (camelCase); columnName is the Postgres name. */
904
1412
  name: zod.z.string().min(1).max(63),
905
1413
  columnName: columnIdentifier,
906
- type: zod.z.union([columnTypeSchema, zod.z.object({ enum: identifier })]),
1414
+ type: zod.z.union([columnTypeSchema, zod.z.object({ enum: identifier2 })]),
907
1415
  nullable: zod.z.boolean().default(true),
908
1416
  unique: zod.z.boolean().default(false),
909
1417
  /** Literal default, or one of the allowlisted SQL functions. */
@@ -919,7 +1427,14 @@ var canonicalColumnSchema = zod.z.object({
919
1427
  /** Migration hints — same semantics as v1. */
920
1428
  renamedFrom: columnIdentifier.optional(),
921
1429
  cast: zod.z.string().max(200).optional(),
922
- 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()
923
1438
  }).superRefine((column, ctx) => {
924
1439
  if (column.onUpdateFn === void 0) return;
925
1440
  if (typeof column.type !== "string" || column.type !== "timestamptz" && column.type !== "date") {
@@ -934,36 +1449,26 @@ var canonicalLinkSchema = zod.z.object({
934
1449
  name: zod.z.string().min(1).max(63),
935
1450
  kind: zod.z.enum(["toOne", "toMany", "toPlatformUser"]),
936
1451
  /** Target table name (namespace-local). Absent for toPlatformUser. */
937
- target: identifier.optional(),
1452
+ target: identifier2.optional(),
938
1453
  /** FK column on the owning side (toOne/toPlatformUser: on this table; toMany: on the target). */
939
1454
  on: columnIdentifier,
940
1455
  /** Referenced column on the target (default: target's PK / platform user `sub`). */
941
1456
  references: columnIdentifier.optional(),
942
1457
  /** enforce: true emits a real FK constraint (same-namespace toOne only). */
943
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(),
944
1461
  description: zod.z.string().max(500).optional()
945
1462
  });
946
1463
  var canonicalIndexSchema = zod.z.object({
947
1464
  columns: zod.z.array(columnIdentifier).min(1),
948
1465
  unique: zod.z.boolean().default(false),
949
- name: identifier.optional()
1466
+ name: identifier2.optional()
950
1467
  });
951
- var policyVariantSchema = zod.z.object({
952
- /** Claims-only guard (capability()/role()/claim() comparisons — no columns). */
953
- when: zod.z.string().min(1).max(1e3),
954
- /** Exactly one of: allow-all bypass, or a row predicate. */
955
- allow: zod.z.literal(true).optional(),
956
- policy: zod.z.string().min(1).max(2e3).optional()
957
- }).refine((v) => v.allow === true !== (typeof v.policy === "string"), {
958
- message: "A variant declares exactly one of allow: true or policy"
959
- });
960
- var policyExpressionSchema = zod.z.union([
961
- zod.z.string().min(1).max(2e3),
962
- zod.z.object({
963
- default: zod.z.string().min(1).max(2e3),
964
- variants: zod.z.array(policyVariantSchema).max(10).default([])
965
- })
966
- ]);
1468
+ var canonicalCheckSchema = zod.z.object({
1469
+ name: identifier2,
1470
+ expression: zod.z.string().min(1).max(2e3)
1471
+ }).strict();
967
1472
  var canonicalPoliciesSchema = zod.z.object({
968
1473
  read: policyExpressionSchema.optional(),
969
1474
  write: policyExpressionSchema.optional(),
@@ -1151,7 +1656,7 @@ var VIEW_QUERY_FUNCTION_ALLOWLIST = [
1151
1656
  "width_bucket"
1152
1657
  ];
1153
1658
  var canonicalActionSchema = zod.z.object({
1154
- name: identifier,
1659
+ name: identifier2,
1155
1660
  requires: zod.z.string().max(1e3).optional(),
1156
1661
  params: zod.z.record(zod.z.string(), zod.z.object({
1157
1662
  type: columnTypeSchema,
@@ -1163,9 +1668,9 @@ var canonicalActionSchema = zod.z.object({
1163
1668
  });
1164
1669
  var canonicalCowMutationSchema = zod.z.object({
1165
1670
  kind: zod.z.enum(["override", "revert"]),
1166
- name: identifier,
1671
+ name: identifier2,
1167
1672
  /** Synced table (same namespace) the overlay copies from / falls back to. */
1168
- synced: identifier,
1673
+ synced: identifier2,
1169
1674
  /** Single-column primary key PROPERTY (TS camelCase, like column `name`) shared by overlay and synced. */
1170
1675
  pk: zod.z.string().min(1).max(63),
1171
1676
  /** Override: contract properties copied INSERT…SELECT from synced (pk included). Empty for revert. */
@@ -1193,23 +1698,27 @@ var canonicalCowMutationSchema = zod.z.object({
1193
1698
  }
1194
1699
  });
1195
1700
  var canonicalForeignKeySchema = zod.z.object({
1196
- name: identifier,
1701
+ name: identifier2,
1197
1702
  columns: zod.z.array(columnIdentifier).min(1).max(8),
1198
- target: identifier,
1703
+ target: identifier2,
1199
1704
  references: zod.z.array(columnIdentifier).min(1).max(8),
1200
- deferrable: zod.z.enum(["initially-immediate", "initially-deferred"]).optional()
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()
1201
1708
  }).strict();
1202
1709
  var canonicalTableSchema = zod.z.object({
1203
- table: identifier,
1710
+ table: identifier2,
1204
1711
  description: zod.z.string().max(2e3).optional(),
1205
1712
  /** Table-level rename hint (new in v2). */
1206
- renamedFrom: identifier.optional(),
1713
+ renamedFrom: identifier2.optional(),
1207
1714
  /** Synced/reflected read-only tables (`source: gold.*`) — v1 semantics. */
1208
1715
  source: zod.z.string().regex(/^gold\.[a-z][a-z0-9_]*$/).optional(),
1209
1716
  columns: zod.z.array(canonicalColumnSchema).default([]),
1210
1717
  primaryKey: zod.z.array(columnIdentifier).min(1).max(8).optional(),
1211
1718
  links: zod.z.array(canonicalLinkSchema).default([]),
1212
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(),
1213
1722
  policies: canonicalPoliciesSchema.default({}),
1214
1723
  /**
1215
1724
  * v1-conversion carry-over only. v2 has no action surface — the runtime
@@ -1220,22 +1729,55 @@ var canonicalTableSchema = zod.z.object({
1220
1729
  /** Optional (not defaulted) so pre-CoW canonical documents keep byte-identical hashes. */
1221
1730
  cowMutations: zod.z.array(canonicalCowMutationSchema).optional(),
1222
1731
  /** Omitted by old definitions, preserving their existing content hashes. */
1223
- foreignKeys: zod.z.array(canonicalForeignKeySchema).max(100).optional()
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()
1224
1753
  });
1754
+ var MAX_VIEW_QUERY = 1e5;
1755
+ var MAX_RESOLVED_VIEW_QUERY = 5e6;
1225
1756
  var canonicalViewSchema = zod.z.object({
1226
- view: identifier,
1757
+ view: identifier2,
1227
1758
  description: zod.z.string().max(2e3).optional(),
1228
1759
  columns: zod.z.array(canonicalColumnSchema).min(1),
1229
1760
  /** Logical identity for GraphQL only; PostgreSQL views have no PK constraint. */
1230
1761
  primaryKey: zod.z.array(columnIdentifier).min(1).max(8).optional(),
1231
1762
  links: zod.z.array(canonicalLinkSchema).default([]),
1232
1763
  policies: zod.z.object({ read: policyExpressionSchema }).strict(),
1233
- query: zod.z.string().min(1).max(1e5).refine(isSingleViewQuerySql, {
1764
+ query: zod.z.string().min(1).max(MAX_RESOLVED_VIEW_QUERY).refine(isSingleViewQuerySql, {
1234
1765
  message: "view query must be one SELECT/WITH statement without comments or dollar-quoted bodies"
1235
1766
  }),
1236
1767
  /** Namespace-local logical relation names, sorted for deterministic hashing. */
1237
- 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()
1238
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
+ }
1239
1781
  for (const [index3, column] of view.columns.entries()) {
1240
1782
  if (column.default !== void 0 || column.defaultFn !== void 0) {
1241
1783
  ctx.addIssue({ code: "custom", path: ["columns", index3], message: `view column '${column.name}' cannot declare a default` });
@@ -1250,15 +1792,82 @@ var canonicalViewSchema = zod.z.object({
1250
1792
  ctx.addIssue({ code: "custom", path: ["columns", index3], message: `view column '${column.name}' cannot declare migration hints` });
1251
1793
  }
1252
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
+ }
1253
1800
  for (const [index3, link] of view.links.entries()) {
1254
1801
  if (link.enforce) {
1255
1802
  ctx.addIssue({ code: "custom", path: ["links", index3, "enforce"], message: `view link '${link.name}' cannot enforce a foreign key` });
1256
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
+ }
1257
1807
  }
1258
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
+ }
1259
1868
  var canonicalDefinitionSchema = zod.z.object({
1260
1869
  definition_format: zod.z.literal(CANONICAL_FORMAT),
1261
- namespace: identifier.max(41),
1870
+ namespace: identifier2.max(41),
1262
1871
  description: zod.z.string().max(2e3).optional(),
1263
1872
  enums: zod.z.array(canonicalEnumSchema).default([]),
1264
1873
  tables: zod.z.array(canonicalTableSchema).min(0).max(500),
@@ -1276,6 +1885,7 @@ var canonicalDefinitionSchema = zod.z.object({
1276
1885
  ctx.addIssue({ code: "custom", path: ["tables", index3, "table"], message: `duplicate relation '${table.table}'` });
1277
1886
  }
1278
1887
  seen.add(table.table);
1888
+ validateCellMetadata(table, index3, ctx);
1279
1889
  if (table.source !== void 0) {
1280
1890
  for (const [columnIndex, column] of table.columns.entries()) {
1281
1891
  if (column.onUpdateFn !== void 0) {
@@ -1295,6 +1905,26 @@ var canonicalDefinitionSchema = zod.z.object({
1295
1905
  ...table.columns.filter((column) => column.unique && !(table.primaryKey?.length === 1 && table.primaryKey[0] === column.columnName)).map((column) => `${table.table}_${column.columnName}_unique`),
1296
1906
  ...table.links.filter((link) => link.enforce).map((link) => `fk_${table.table}_${link.name}`)
1297
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
+ }
1298
1928
  for (const [index3, fk] of (table.foreignKeys ?? []).entries()) {
1299
1929
  const fail = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "foreignKeys", index3], message });
1300
1930
  if (names.has(fk.name)) fail(`duplicate foreign key '${fk.name}'`);
@@ -1312,10 +1942,18 @@ var canonicalDefinitionSchema = zod.z.object({
1312
1942
  if (!sourceColumn || !targetColumn) fail("foreign key references an unknown column");
1313
1943
  else if (JSON.stringify(sourceColumn.type) !== JSON.stringify(targetColumn.type)) fail("foreign key column types must match");
1314
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
+ }
1315
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])];
1316
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");
1317
1950
  }
1318
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
+ }
1956
+ }
1319
1957
  const tableByName = new Map(definition.tables.map((table) => [table.table, table]));
1320
1958
  for (const [tableIndex, table] of definition.tables.entries()) {
1321
1959
  const cowMutations = table.cowMutations ?? [];
@@ -1439,8 +2077,12 @@ function canonicalRelations(definition) {
1439
2077
  ...view.primaryKey !== void 0 ? { primaryKey: view.primaryKey } : {},
1440
2078
  links: view.links,
1441
2079
  indexes: [],
1442
- policies: { read: view.policies.read },
1443
- 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 } : {}
1444
2086
  }));
1445
2087
  return [...definition.tables, ...views].sort((a, b) => a.table.localeCompare(b.table));
1446
2088
  }
@@ -1450,13 +2092,14 @@ function normalizeCanonical(definition) {
1450
2092
  ...definition,
1451
2093
  enums: sortByName(definition.enums),
1452
2094
  tables: [...definition.tables].sort((a, b) => a.table.localeCompare(b.table)).map((t2) => {
1453
- const { cowMutations, foreignKeys, ...rest } = t2;
2095
+ const { cowMutations, foreignKeys, checks, ...rest } = t2;
1454
2096
  return {
1455
2097
  ...rest,
1456
2098
  links: sortByName(t2.links),
1457
2099
  indexes: [...t2.indexes].sort((a, b) => (a.name ?? a.columns.join("_")).localeCompare(b.name ?? b.columns.join("_"))),
1458
2100
  actions: sortByName(t2.actions),
1459
2101
  ...foreignKeys?.length ? { foreignKeys: sortByName(foreignKeys) } : {},
2102
+ ...checks?.length ? { checks: sortByName(checks) } : {},
1460
2103
  // Omitted entirely when absent/empty so pre-CoW documents hash unchanged.
1461
2104
  ...cowMutations !== void 0 && cowMutations.length > 0 ? { cowMutations: sortByName(cowMutations) } : {}
1462
2105
  };
@@ -1471,6 +2114,454 @@ function normalizeCanonical(definition) {
1471
2114
  }
1472
2115
  return normalized;
1473
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
+ }
1474
2565
  function stableJson(value) {
1475
2566
  if (value === null || typeof value !== "object") return JSON.stringify(value);
1476
2567
  if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
@@ -1577,8 +2668,19 @@ function defineNamespace(input) {
1577
2668
  return config;
1578
2669
  }
1579
2670
  function serializeNamespace(input) {
1580
- const { config, tables } = input;
1581
- 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
+ }
1582
2684
  const enums = input.enums ?? [];
1583
2685
  const tablesByName = /* @__PURE__ */ new Map();
1584
2686
  for (const table of tables) {
@@ -1680,6 +2782,9 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
1680
2782
  if (table.config.renamedFrom !== void 0) candidate.renamedFrom = table.config.renamedFrom;
1681
2783
  if (table.source !== void 0) candidate.source = table.source;
1682
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
+ }
1683
2788
  if (table.config.indexes !== void 0 && table.config.indexes.length > 0) {
1684
2789
  throw new OrmDefinitionError(
1685
2790
  `synced table "${table.tableName}" is system-owned; indexes are not allowed`,
@@ -1717,6 +2822,11 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
1717
2822
  if (builder.isPrimaryKey) columnLevelPk.push(column.columnName);
1718
2823
  candidate.columns?.push(column);
1719
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
+ }
1720
2830
  if (table.config.primaryKey !== void 0) {
1721
2831
  if (columnLevelPk.length > 0) {
1722
2832
  throw new OrmDefinitionError(
@@ -1730,6 +2840,24 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
1730
2840
  } else if (columnLevelPk.length > 0) {
1731
2841
  candidate.primaryKey = columnLevelPk;
1732
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
+ }
1733
2861
  if (table.config.foreignKeys?.length) {
1734
2862
  if (table.source) throw new OrmDefinitionError(`synced table "${table.tableName}" cannot enforce foreign keys`, table.file);
1735
2863
  candidate.foreignKeys = table.config.foreignKeys.map((fk) => {
@@ -1742,10 +2870,15 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
1742
2870
  columns: fk.columns.map((ref) => resolveColumnRef({ relation: table, ref, role: `foreign key "${fk.name}" column` })),
1743
2871
  target: target.tableName,
1744
2872
  references: fk.references.map((ref) => resolveColumnRef({ relation: target, ref, role: `foreign key "${fk.name}" reference` })),
1745
- ...fk.deferrable ? { deferrable: fk.deferrable } : {}
2873
+ ...fk.deferrable ? { deferrable: fk.deferrable } : {},
2874
+ ...fk.onDelete ? { onDelete: fk.onDelete } : {},
2875
+ ...fk.onUpdate ? { onUpdate: fk.onUpdate } : {}
1746
2876
  };
1747
2877
  });
1748
2878
  }
2879
+ if (table.config.checks?.length) {
2880
+ candidate.checks = table.config.checks.map((check) => ({ name: check.name, expression: check.expression }));
2881
+ }
1749
2882
  for (const index3 of table.config.indexes ?? []) {
1750
2883
  const entry = {
1751
2884
  columns: index3.columns.map((ref) => resolveColumnRef({ relation: table, ref, role: "index" }))
@@ -1841,7 +2974,33 @@ function buildCanonicalView({
1841
2974
  columns,
1842
2975
  ...primaryKey2 !== void 0 ? { primaryKey: primaryKey2 } : {},
1843
2976
  links,
1844
- 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
1845
3004
  };
1846
3005
  }
1847
3006
  function buildCanonicalLink({
@@ -1858,6 +3017,9 @@ function buildCanonicalLink({
1858
3017
  if (link.enforce && link.linkKind !== "toOne") {
1859
3018
  return fail("sets enforce: true, which is only supported on toOne links");
1860
3019
  }
3020
+ if ((link.onDelete !== void 0 || link.onUpdate !== void 0) && !link.enforce) {
3021
+ return fail("sets a referential action without enforce: true");
3022
+ }
1861
3023
  if (link.linkKind === "toPlatformUser") {
1862
3024
  if (link.target !== void 0) return fail("is toPlatformUser and must not declare a target table");
1863
3025
  return {
@@ -1886,6 +3048,8 @@ function buildCanonicalLink({
1886
3048
  on: resolveColumnRef({ relation: owningSide, ref: link.on, role: `link "${linkName}" on` }),
1887
3049
  ...link.references !== void 0 ? { references: resolveColumnRef({ relation: referencedSide, ref: link.references, role: `link "${linkName}" references` }) } : {},
1888
3050
  enforce: link.enforce,
3051
+ ...link.onDelete !== void 0 ? { onDelete: link.onDelete } : {},
3052
+ ...link.onUpdate !== void 0 ? { onUpdate: link.onUpdate } : {},
1889
3053
  ...link.description !== void 0 ? { description: link.description } : {}
1890
3054
  };
1891
3055
  }
@@ -2339,7 +3503,7 @@ var OPERATOR_PATTERN = /^(!=|<=|>=|<>|=|<|>)/;
2339
3503
  var IDENTIFIER_START = /[a-z_]/;
2340
3504
  var IDENTIFIER_CHAR = /[a-z0-9_.-]/;
2341
3505
  var NUMBER_PATTERN = /^-?\d+(\.\d+)?/;
2342
- function tokenize(source) {
3506
+ function tokenize2(source) {
2343
3507
  const tokens = [];
2344
3508
  let position = 0;
2345
3509
  while (position < source.length) {
@@ -2413,7 +3577,7 @@ function readString(source, start) {
2413
3577
  }
2414
3578
  throw new PredicateParseError("unterminated string literal", start);
2415
3579
  }
2416
- var Parser = class {
3580
+ var Parser2 = class {
2417
3581
  constructor(tokens) {
2418
3582
  this.tokens = tokens;
2419
3583
  }
@@ -2545,7 +3709,7 @@ var Parser = class {
2545
3709
  function parsePredicate(source) {
2546
3710
  const trimmed = source.trim();
2547
3711
  if (trimmed.length === 0) throw new PredicateParseError("empty predicate", 0);
2548
- return { source: trimmed, ast: new Parser(tokenize(trimmed)).parse() };
3712
+ return { source: trimmed, ast: new Parser2(tokenize2(trimmed)).parse() };
2549
3713
  }
2550
3714
 
2551
3715
  // src/schema/predicates.ts
@@ -2630,8 +3794,8 @@ var actionSchema = zod.z.strictObject({
2630
3794
  }
2631
3795
  }
2632
3796
  });
2633
- function extractNamedParams(sql5) {
2634
- const withoutStrings = sql5.replace(QUOTED_STRING_PATTERN, "''");
3797
+ function extractNamedParams(sql7) {
3798
+ const withoutStrings = sql7.replace(QUOTED_STRING_PATTERN, "''");
2635
3799
  const names = [...withoutStrings.matchAll(NAMED_PARAM_PATTERN)].map((match) => match[1]);
2636
3800
  return [...new Set(names)];
2637
3801
  }
@@ -2698,17 +3862,6 @@ var SQL_TYPE_NAMES = {
2698
3862
  };
2699
3863
  var sqlTypeName = (type) => SQL_TYPE_NAMES[type] ?? type;
2700
3864
 
2701
- // src/compiler/sql.ts
2702
- var CLAIMS_SETTING = "request.jwt.claims";
2703
- var CAPABILITIES_CLAIM_KEY = "capabilities";
2704
- var ROLES_CLAIM_KEY = "roles";
2705
- var quoteIdentifier = (name) => `"${name.replaceAll('"', '""')}"`;
2706
- var quoteLiteral = (value) => `'${value.replaceAll("'", "''")}'`;
2707
- var CLAIMS_SOURCE = `nullif(current_setting('${CLAIMS_SETTING}', true), '')::jsonb`;
2708
- var claimExpression = (key) => `(${CLAIMS_SOURCE} ->> ${quoteLiteral(key)})`;
2709
- var capabilityExpression = (key) => `coalesce(${CLAIMS_SOURCE} -> '${CAPABILITIES_CLAIM_KEY}' ? ${quoteLiteral(key)}, false)`;
2710
- var roleExpression = (key) => `coalesce(${CLAIMS_SOURCE} -> '${ROLES_CLAIM_KEY}' ? ${quoteLiteral(key)}, false)`;
2711
-
2712
3865
  // src/compiler/predicates.ts
2713
3866
  var SQL_COMPARE_OPS = {
2714
3867
  "=": "=",
@@ -2788,8 +3941,8 @@ var actionParamArg = (name) => `p_${name}`;
2788
3941
  function compileActionStatements(action, renderParam = actionParamArg) {
2789
3942
  return actionStatementSources(action.executes).map((statement) => compileStatement(statement, renderParam));
2790
3943
  }
2791
- function compileStatement(sql5, renderParam) {
2792
- const compiled = sql5.replace(TOKEN_PATTERN, (token, claimKey, capabilityKey, paramName) => {
3944
+ function compileStatement(sql7, renderParam) {
3945
+ const compiled = sql7.replace(TOKEN_PATTERN, (token, claimKey, capabilityKey, paramName) => {
2793
3946
  if (claimKey !== void 0) {
2794
3947
  if (!CLAIM_KEY_PATTERN.test(claimKey)) throw new OrmDefinitionError(`invalid claim key in executes: ${token}`);
2795
3948
  return claimExpression(claimKey);
@@ -2854,10 +4007,10 @@ function physicalColumn(logical, options) {
2854
4007
  return options.columnNames?.[logical] ?? logical;
2855
4008
  }
2856
4009
  function compileCmp(op, column, value, options) {
2857
- const physical = physicalColumn(column, options);
2858
- const declared = options.columnTypes?.[physical] ?? options.columnTypes?.[column];
4010
+ const physical2 = physicalColumn(column, options);
4011
+ const declared = options.columnTypes?.[physical2] ?? options.columnTypes?.[column];
2859
4012
  const isEnum = typeof declared === "object" && declared !== null;
2860
- const col = isEnum ? `${quoteIdentifier(physical)}::text` : quoteIdentifier(physical);
4013
+ const col = isEnum ? `${quoteIdentifier(physical2)}::text` : quoteIdentifier(physical2);
2861
4014
  const columnType = isEnum ? "text" : declared;
2862
4015
  switch (op) {
2863
4016
  case "eq":
@@ -2901,11 +4054,11 @@ function compileEquality(col, value, columnType, negated, options) {
2901
4054
  function compileValue(value, columnType, _options) {
2902
4055
  switch (value.kind) {
2903
4056
  case "literal": {
2904
- const literal = value.value;
2905
- if (literal === null) return "NULL";
2906
- if (typeof literal === "string") return quoteLiteral(literal);
2907
- if (typeof literal === "boolean") return literal ? "TRUE" : "FALSE";
2908
- 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);
2909
4062
  }
2910
4063
  case "claim": {
2911
4064
  const expression = claimExpression(value.key);
@@ -2954,8 +4107,8 @@ function actionPrepareChecks(definition) {
2954
4107
  const order = actionParamOrder(action);
2955
4108
  const signature = order.length > 0 ? ` (${order.map((param) => sqlTypeName(action.params[param].type)).join(", ")})` : "";
2956
4109
  const compiled = compileActionStatements(action, (param) => `$${order.indexOf(param) + 1}`);
2957
- compiled.forEach((sql5, index3) => {
2958
- const checked = action.returns && index3 === compiled.length - 1 ? returnProjection(action.returns, sql5) : sql5;
4110
+ compiled.forEach((sql7, index3) => {
4111
+ const checked = action.returns && index3 === compiled.length - 1 ? returnProjection(action.returns, sql7) : sql7;
2959
4112
  statements.push(`PREPARE ${quoteIdentifier(`orm_check_${name}_${index3}`)}${signature} AS ${checked}`);
2960
4113
  });
2961
4114
  }
@@ -3337,14 +4490,14 @@ function comparisonTypeIssue(left, right, columns) {
3337
4490
  return null;
3338
4491
  }
3339
4492
  const column = left.kind === "column" ? left : right.kind === "column" ? right : null;
3340
- const literal = left.kind === "literal" ? left : right.kind === "literal" ? right : null;
3341
- 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;
3342
4495
  const columnGroup = groupOf(column);
3343
4496
  if (!columnGroup) return null;
3344
- if (typeof literal.value === "string") return null;
3345
- 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";
3346
4499
  if (literalGroup !== columnGroup) {
3347
- 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`;
3348
4501
  }
3349
4502
  return null;
3350
4503
  }
@@ -3430,8 +4583,8 @@ function syncedViewStatements({
3430
4583
  const backing = backingMap.get(table.table);
3431
4584
  if (!backing) continue;
3432
4585
  const viewName = `${schema}.${quoteIdentifier(table.table)}`;
3433
- const physical = splitPhysicalRelation(backing.physicalRelation);
3434
- const physicalTarget = `${quoteIdentifier(physical.schema)}.${quoteIdentifier(physical.table)}`;
4586
+ const physical2 = splitPhysicalRelation(backing.physicalRelation);
4587
+ const physicalTarget = `${quoteIdentifier(physical2.schema)}.${quoteIdentifier(physical2.table)}`;
3435
4588
  const columnTypes = mergeSyncedViewColumnTypes({
3436
4589
  table,
3437
4590
  canonicalColumnTypes: columnTypesByTable?.get(table.table),
@@ -3445,7 +4598,7 @@ function syncedViewStatements({
3445
4598
  columnNames: columnNamesByTable?.get(table.table)
3446
4599
  })}` : readPolicy ? ` WHERE ${compilePredicate(readPolicy.ast, narrowToV1ColumnTypes(columnTypes))}` : " WHERE false";
3447
4600
  if (backingPrivileges === "grant") {
3448
- statements.push(`GRANT USAGE ON SCHEMA ${quoteIdentifier(physical.schema)} TO ${owner}`);
4601
+ statements.push(`GRANT USAGE ON SCHEMA ${quoteIdentifier(physical2.schema)} TO ${owner}`);
3449
4602
  statements.push(`GRANT SELECT ON ${physicalTarget} TO ${owner}`);
3450
4603
  }
3451
4604
  statements.push(
@@ -3466,10 +4619,10 @@ function mergeSyncedViewColumnTypes({
3466
4619
  if (def?.type) fromTable[name] = def.type;
3467
4620
  }
3468
4621
  if (columnNames) {
3469
- for (const [logical, physical] of Object.entries(columnNames)) {
3470
- const declared = fromTable[physical] ?? fromTable[logical];
4622
+ for (const [logical, physical2] of Object.entries(columnNames)) {
4623
+ const declared = fromTable[physical2] ?? fromTable[logical];
3471
4624
  if (!declared) continue;
3472
- fromTable[physical] = declared;
4625
+ fromTable[physical2] = declared;
3473
4626
  fromTable[logical] = declared;
3474
4627
  }
3475
4628
  }
@@ -3508,8 +4661,8 @@ function syncedBackingRevokeStatements({
3508
4661
  const seen = /* @__PURE__ */ new Set();
3509
4662
  const statements = [];
3510
4663
  for (const backing of backings) {
3511
- const physical = splitPhysicalRelation(backing.physicalRelation);
3512
- const physicalTarget = `${quoteIdentifier(physical.schema)}.${quoteIdentifier(physical.table)}`;
4664
+ const physical2 = splitPhysicalRelation(backing.physicalRelation);
4665
+ const physicalTarget = `${quoteIdentifier(physical2.schema)}.${quoteIdentifier(physical2.table)}`;
3513
4666
  if (seen.has(physicalTarget)) continue;
3514
4667
  seen.add(physicalTarget);
3515
4668
  statements.push(`REVOKE SELECT ON ${physicalTarget} FROM ${owner}`);
@@ -3719,6 +4872,90 @@ function snapshotTableNames(snapshot, schema) {
3719
4872
  const tables = snapshot.tables ?? {};
3720
4873
  return Object.values(tables).filter((entry) => entry.schema === schema).map((entry) => entry.name);
3721
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
3722
4959
  var FACTORIES2 = {
3723
4960
  uuid: (name) => pgCore.uuid(name),
3724
4961
  text: (name) => pgCore.text(name),
@@ -3831,18 +5068,18 @@ function rewriteSyncedSourceRelationsInViewQuery({
3831
5068
  return rewritten;
3832
5069
  });
3833
5070
  }
3834
- function transformSqlOutsideLiterals(sql5, transform) {
5071
+ function transformSqlOutsideLiterals(sql7, transform) {
3835
5072
  const out = [];
3836
5073
  let state = { kind: "normal" };
3837
5074
  let i = 0;
3838
5075
  let codeStart = 0;
3839
5076
  const emitCode = (end) => {
3840
- if (end > codeStart) out.push(transform(sql5.slice(codeStart, end)));
5077
+ if (end > codeStart) out.push(transform(sql7.slice(codeStart, end)));
3841
5078
  codeStart = end;
3842
5079
  };
3843
- while (i < sql5.length) {
3844
- const c = sql5[i];
3845
- const next = i + 1 < sql5.length ? sql5[i + 1] : "";
5080
+ while (i < sql7.length) {
5081
+ const c = sql7[i];
5082
+ const next = i + 1 < sql7.length ? sql7[i + 1] : "";
3846
5083
  switch (state.kind) {
3847
5084
  case "normal": {
3848
5085
  if (c === "-" && next === "-") {
@@ -3862,7 +5099,7 @@ function transformSqlOutsideLiterals(sql5, transform) {
3862
5099
  break;
3863
5100
  }
3864
5101
  if ((c === "E" || c === "e") && next === "'") {
3865
- const prev = i > 0 ? sql5[i - 1] : "";
5102
+ const prev = i > 0 ? sql7[i - 1] : "";
3866
5103
  if (!/[A-Za-z0-9_]/.test(prev)) {
3867
5104
  emitCode(i);
3868
5105
  state = { kind: "e-single" };
@@ -3881,9 +5118,9 @@ function transformSqlOutsideLiterals(sql5, transform) {
3881
5118
  break;
3882
5119
  }
3883
5120
  if (c === "$") {
3884
- const prev = i > 0 ? sql5[i - 1] : "";
5121
+ const prev = i > 0 ? sql7[i - 1] : "";
3885
5122
  if (!/[A-Za-z0-9_$]/.test(prev)) {
3886
- const tagMatch = /^\$([A-Za-z_][A-Za-z0-9_]*)?\$/.exec(sql5.slice(i));
5123
+ const tagMatch = /^\$([A-Za-z_][A-Za-z0-9_]*)?\$/.exec(sql7.slice(i));
3887
5124
  if (tagMatch) {
3888
5125
  emitCode(i);
3889
5126
  state = { kind: "dollar", tag: tagMatch[1] ?? "" };
@@ -3915,8 +5152,8 @@ function transformSqlOutsideLiterals(sql5, transform) {
3915
5152
  break;
3916
5153
  }
3917
5154
  case "e-single": {
3918
- if (c === "\\" && i + 1 < sql5.length) {
3919
- out.push(c, sql5[i + 1]);
5155
+ if (c === "\\" && i + 1 < sql7.length) {
5156
+ out.push(c, sql7[i + 1]);
3920
5157
  i += 2;
3921
5158
  break;
3922
5159
  }
@@ -3938,7 +5175,7 @@ function transformSqlOutsideLiterals(sql5, transform) {
3938
5175
  }
3939
5176
  case "dollar": {
3940
5177
  const closer = `$${state.tag}$`;
3941
- if (sql5.startsWith(closer, i)) {
5178
+ if (sql7.startsWith(closer, i)) {
3942
5179
  out.push(closer);
3943
5180
  state = { kind: "normal" };
3944
5181
  i += closer.length;
@@ -3982,15 +5219,15 @@ function transformSqlOutsideLiterals(sql5, transform) {
3982
5219
  }
3983
5220
  }
3984
5221
  }
3985
- emitCode(sql5.length);
5222
+ emitCode(sql7.length);
3986
5223
  return out.join("");
3987
5224
  }
3988
5225
  function escapeRegExp(value) {
3989
5226
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3990
5227
  }
3991
- function sqlReferencesQualifiedRelation(sql5, sourceSchema, sourceName) {
5228
+ function sqlReferencesQualifiedRelation(sql7, sourceSchema, sourceName) {
3992
5229
  let referenced = false;
3993
- transformSqlOutsideLiterals(sql5, (segment) => {
5230
+ transformSqlOutsideLiterals(sql7, (segment) => {
3994
5231
  if (qualifiedRelationPattern(sourceSchema, sourceName).test(segment)) referenced = true;
3995
5232
  return segment;
3996
5233
  });
@@ -4001,16 +5238,18 @@ function qualifiedRelationPattern(sourceSchema, sourceName) {
4001
5238
  const namePattern = `(?:"${escapeRegExp(sourceName)}"|${escapeRegExp(sourceName)})`;
4002
5239
  return new RegExp(`(?<![\\w."])${schemaPattern}\\.${namePattern}(?![\\w"])`, "g");
4003
5240
  }
4004
- function replaceQualifiedRelation(sql5, sourceSchema, sourceName, boundSchema, boundTable) {
4005
- return sql5.replace(qualifiedRelationPattern(sourceSchema, sourceName), `"${boundSchema}"."${boundTable}"`);
5241
+ function replaceQualifiedRelation(sql7, sourceSchema, sourceName, boundSchema, boundTable) {
5242
+ return sql7.replace(qualifiedRelationPattern(sourceSchema, sourceName), `"${boundSchema}"."${boundTable}"`);
4006
5243
  }
4007
5244
  function generateSnapshotV2(definition, previousId3) {
4008
5245
  const { imports, schemaName } = buildDrizzleEntitiesV2(definition);
4009
5246
  const snapshot = api.generateDrizzleJson(imports, previousId3, [schemaName]);
4010
5247
  if ((definition.views?.length ?? 0) > 0 || definition.tables.some((table) => table.foreignKeys?.length)) {
4011
5248
  const extended = snapshot;
5249
+ const refreshFunctions = materializedRefreshFunctions(definition);
4012
5250
  extended._sequenceOrm = {
4013
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) } : {},
4014
5253
  viewDependencies: Object.fromEntries(
4015
5254
  (definition.views ?? []).map((view) => [view.view, [...view.dependencies].sort()])
4016
5255
  )
@@ -4050,15 +5289,19 @@ function buildTable2(schema, table, built, enums, byName, introspection) {
4050
5289
  const drizzleTable = schema.table(table.table, columns, (self) => [
4051
5290
  ...buildCompositePrimaryKey(table, primaryKeyColumns, self),
4052
5291
  ...buildIndexes2(table, self),
5292
+ ...(table.checks ?? []).map((check) => pgCore.check(check.name, drizzleOrm.sql.raw(compileCheckExpression(check.expression, table.columns)))),
4053
5293
  ...buildForeignKeys2(table, self, built, byName),
4054
5294
  ...(table.foreignKeys ?? []).map((fk) => {
4055
5295
  const target = fk.target === table.table ? self : built.get(fk.target);
4056
5296
  if (!target) throw new OrmDefinitionError(`foreign key '${fk.name}' has no target '${fk.target}'`);
4057
- return pgCore.foreignKey({
5297
+ let constraint = pgCore.foreignKey({
4058
5298
  name: fk.name,
4059
5299
  columns: fk.columns.map((column) => self[column]),
4060
5300
  foreignColumns: fk.references.map((column) => target[column])
4061
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;
4062
5305
  })
4063
5306
  ]);
4064
5307
  return drizzleTable.enableRLS();
@@ -4149,11 +5392,14 @@ function buildForeignKeys2(table, self, built, byName) {
4149
5392
  `enforced link '${link.name}' on '${table.table}' references unknown column '${targetColumn ? link.on : referencedColumn}'`
4150
5393
  );
4151
5394
  }
4152
- return pgCore.foreignKey({
5395
+ let constraint = pgCore.foreignKey({
4153
5396
  name: foreignKeyName(table.table, link.name),
4154
5397
  columns: [sourceColumn],
4155
5398
  foreignColumns: [targetColumn]
4156
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;
4157
5403
  });
4158
5404
  }
4159
5405
  var singlePrimaryKeyOf = (table) => table.primaryKey?.length === 1 ? table.primaryKey[0] : void 0;
@@ -4274,9 +5520,23 @@ function findTable2(snapshot, schema, table) {
4274
5520
  const tables = snapshot.tables ?? {};
4275
5521
  return Object.values(tables).find((entry) => entry.schema === schema && entry.name === table);
4276
5522
  }
4277
- async function planMigrationV2({
5523
+ function planMigrationV2(input) {
5524
+ return planWithViewRecreation({ ...input, viewRecreation: "affected" });
5525
+ }
5526
+ async function planCommittedMigrationV2({
4278
5527
  canonical,
4279
- 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
4280
5540
  }) {
4281
5541
  const schema = ormSchemaName(canonical.namespace);
4282
5542
  const previous = previousSnapshot ? normalizeSnapshot(previousSnapshot) : emptySnapshot(canonical.namespace);
@@ -4294,7 +5554,14 @@ async function planMigrationV2({
4294
5554
  currentViews,
4295
5555
  previousDependencies,
4296
5556
  currentDependencies,
4297
- 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
+ })
4298
5565
  });
4299
5566
  const dropOrder = topologicalViewNames(
4300
5567
  new Set([...affected].filter((name) => previousViews.has(name))),
@@ -4321,13 +5588,18 @@ async function planMigrationV2({
4321
5588
  ...dropOrder.map((name) => ({ kind: "ddl", sql: requiredViewStatement(dropDdl, name, schema, "DROP") })),
4322
5589
  ...hints.statements.map((statement) => ({ kind: "hint", sql: statement })),
4323
5590
  ...baseDdl.map((statement) => ({ kind: "ddl", sql: statement.replace(/;\s*$/, "") })),
4324
- ...foreignKeyTimingStatements(canonical, previous, baseDdl).map((sql5) => ({ kind: "ddl", sql: sql5 })),
5591
+ ...foreignKeyTimingStatements(canonical, previous, baseDdl).map((sql7) => ({ kind: "ddl", sql: sql7 })),
4325
5592
  ...createOrder.map((name) => ({ kind: "ddl", sql: requiredViewStatement(createDdl, name, schema, "CREATE") })),
4326
5593
  ...createOrder.map((name) => {
4327
5594
  const view = currentViewDefinitions.get(name);
4328
5595
  if (!view) throw new OrmDefinitionError(`missing canonical definition for view '${name}'`);
4329
5596
  return { kind: "ddl", sql: validateViewCatalogStatement({ canonical, view, schema }) };
4330
- })
5597
+ }),
5598
+ ...refreshFunctionStatements({
5599
+ namespace: canonical.namespace,
5600
+ previous: previous._sequenceOrm?.refreshFunctions ?? {},
5601
+ current: materializedRefreshFunctions(canonical)
5602
+ }).map((sql7) => ({ kind: "ddl", sql: sql7 }))
4331
5603
  ];
4332
5604
  return { namespace: canonical.namespace, schema, statements, snapshot };
4333
5605
  }
@@ -4371,9 +5643,9 @@ function validateViewCatalogStatement({
4371
5643
  }
4372
5644
  const expectedDependencies = sqlTextArray(
4373
5645
  view.dependencies.map((dependency) => {
4374
- const physical = relationIdentity.get(dependency);
4375
- if (!physical) throw new OrmDefinitionError(`view '${view.view}' depends on unknown relation '${dependency}'`);
4376
- 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;
4377
5649
  }).sort()
4378
5650
  );
4379
5651
  const allowedFunctions = sqlTextArray(VIEW_QUERY_FUNCTION_ALLOWLIST);
@@ -4481,20 +5753,102 @@ function withoutViews(snapshot) {
4481
5753
  delete clone._sequenceOrm;
4482
5754
  return clone;
4483
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
+ }
4484
5836
  function affectedViewNames({
4485
5837
  previousViews,
4486
5838
  currentViews,
4487
5839
  previousDependencies,
4488
5840
  currentDependencies,
4489
- baseChanged
5841
+ reach
4490
5842
  }) {
4491
5843
  const allNames = /* @__PURE__ */ new Set([...previousViews.keys(), ...currentViews.keys()]);
4492
- if (baseChanged) return allNames;
5844
+ if (reach === "all") return allNames;
5845
+ const dependenciesOf = (name) => /* @__PURE__ */ new Set([...previousDependencies[name] ?? [], ...currentDependencies[name] ?? []]);
4493
5846
  const affected = new Set(
4494
5847
  [...allNames].filter((name) => {
4495
5848
  const before = previousViews.get(name);
4496
5849
  const after = currentViews.get(name);
4497
- 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));
4498
5852
  })
4499
5853
  );
4500
5854
  let grew = true;
@@ -4502,8 +5856,7 @@ function affectedViewNames({
4502
5856
  grew = false;
4503
5857
  for (const name of allNames) {
4504
5858
  if (affected.has(name)) continue;
4505
- const dependencies = /* @__PURE__ */ new Set([...previousDependencies[name] ?? [], ...currentDependencies[name] ?? []]);
4506
- if ([...dependencies].some((dependency) => affected.has(dependency))) {
5859
+ if ([...dependenciesOf(name)].some((dependency) => affected.has(dependency))) {
4507
5860
  affected.add(name);
4508
5861
  grew = true;
4509
5862
  }
@@ -4741,15 +6094,15 @@ async function planMigration({
4741
6094
  const ddl = await diffStatements(hints.adjusted, snapshot);
4742
6095
  const policyDrops = policyDropStatements(schema, snapshotTableNames(previous, schema));
4743
6096
  const statements = [
4744
- ...policyDrops.map((sql5) => ({ kind: "security", sql: sql5 })),
4745
- ...hints.statements.map((sql5) => ({ kind: "hint", sql: sql5 })),
4746
- ...ddl.map((sql5) => ({ kind: "ddl", sql: sql5.replace(/;\s*$/, "") })),
4747
- ...securityStatements(definition).map((sql5) => ({ kind: "security", sql: sql5 })),
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 })),
4748
6101
  // PREPARE each action's executes first (plpgsql bodies don't check columns
4749
6102
  // at CREATE), then create the functions. Both reference the tables the DDL
4750
6103
  // above creates, so they close the plan.
4751
- ...actionPrepareChecks(definition).map((sql5) => ({ kind: "function", sql: sql5 })),
4752
- ...actionFunctionStatements(definition).map((sql5) => ({ kind: "function", sql: sql5 }))
6104
+ ...actionPrepareChecks(definition).map((sql7) => ({ kind: "function", sql: sql7 })),
6105
+ ...actionFunctionStatements(definition).map((sql7) => ({ kind: "function", sql: sql7 }))
4753
6106
  ];
4754
6107
  return { namespace: definition.namespace, schema, statements, snapshot };
4755
6108
  }
@@ -4879,6 +6232,21 @@ var selectionIrSchema = zod.z.lazy(
4879
6232
  () => zod.z.object({
4880
6233
  typename: zod.z.string(),
4881
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(),
4882
6250
  relations: zod.z.array(zod.z.object({
4883
6251
  alias: zod.z.string(),
4884
6252
  link: zod.z.string(),
@@ -4900,6 +6268,11 @@ var selectionIrSchema = zod.z.lazy(
4900
6268
  })
4901
6269
  );
4902
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
+ });
4903
6276
  var selectRootSchema = zod.z.object({
4904
6277
  kind: zod.z.literal("select"),
4905
6278
  alias: zod.z.string(),
@@ -4911,7 +6284,8 @@ var selectRootSchema = zod.z.object({
4911
6284
  distinctOn: zod.z.union([zod.z.array(zod.z.string()), zod.z.object({ slot: zod.z.string() })]).optional(),
4912
6285
  limit: limitIrSchema,
4913
6286
  offset: zod.z.object({ variable: zod.z.string().optional(), value: zod.z.number().int().optional() }).optional(),
4914
- includeIf: includeIfSchema.optional()
6287
+ includeIf: includeIfSchema.optional(),
6288
+ prefetch: zod.z.array(prefetchPlanIrSchema).optional()
4915
6289
  });
4916
6290
  var byPkRootSchema = zod.z.object({
4917
6291
  kind: zod.z.literal("byPk"),
@@ -4921,7 +6295,8 @@ var byPkRootSchema = zod.z.object({
4921
6295
  pk: zod.z.array(zod.z.object({ column: zod.z.string(), value: valueIrSchema })).min(1),
4922
6296
  /** Policy predicate — ANDed with the PK match. */
4923
6297
  where: predIrSchema,
4924
- includeIf: includeIfSchema.optional()
6298
+ includeIf: includeIfSchema.optional(),
6299
+ prefetch: zod.z.array(prefetchPlanIrSchema).optional()
4925
6300
  });
4926
6301
  var aggregateRootSchema = zod.z.object({
4927
6302
  kind: zod.z.literal("aggregate"),
@@ -4979,7 +6354,20 @@ var mutationRootSchema = zod.z.object({
4979
6354
  * coalesce view falls back to synced. Both are gated on a matching synced
4980
6355
  * row and carry the write policy in `where` like update/delete.
4981
6356
  */
4982
- 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(),
4983
6371
  alias: zod.z.string(),
4984
6372
  table: zod.z.string(),
4985
6373
  /** CoW ops: logical name of the synced twin (read through its policy-filtered view). */
@@ -5064,6 +6452,43 @@ var compiledOperationSchema = zod.z.object({
5064
6452
  }
5065
6453
  }
5066
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()
6491
+ });
5067
6492
  var tableMetaIrSchema = zod.z.object({
5068
6493
  table: zod.z.string(),
5069
6494
  schema: zod.z.string(),
@@ -5104,6 +6529,22 @@ var tableMetaIrSchema = zod.z.object({
5104
6529
  * `onUpdateFn`). Omitted when empty so pre-onUpdate artifacts still parse.
5105
6530
  */
5106
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(),
5107
6548
  constraints: zod.z.array(zod.z.object({ name: zod.z.string(), columns: zod.z.array(zod.z.string()) })),
5108
6549
  /** Depth/node caps for dynamic filter trees on this table. */
5109
6550
  dynamicFilterMaxDepth: zod.z.number().int().positive().default(6),
@@ -5448,12 +6889,12 @@ async function writeMigration({
5448
6889
  const tip = workspace.journal.entries.at(-1);
5449
6890
  const ordinal = (tip?.ordinal ?? 0) + 1;
5450
6891
  const name = migrationName(ordinal, contentHash);
5451
- const sql5 = renderMigrationSql({ name, namespace: workspace.journal.namespace, contentHash, plan });
6892
+ const sql7 = renderMigrationSql({ name, namespace: workspace.journal.namespace, contentHash, plan });
5452
6893
  const metaDir = path.join(dir, MIGRATIONS_DIRNAME, META_DIRNAME);
5453
6894
  await promises.mkdir(metaDir, { recursive: true });
5454
6895
  const snapshotText = `${JSON.stringify(plan.snapshot, null, 2)}
5455
6896
  `;
5456
- await promises.writeFile(path.join(dir, MIGRATIONS_DIRNAME, `${name}.sql`), sql5);
6897
+ await promises.writeFile(path.join(dir, MIGRATIONS_DIRNAME, `${name}.sql`), sql7);
5457
6898
  await promises.writeFile(path.join(metaDir, `${pad(ordinal)}.snapshot.json`), snapshotText);
5458
6899
  const journal = {
5459
6900
  ...workspace.journal,
@@ -5464,7 +6905,7 @@ async function writeMigration({
5464
6905
  name,
5465
6906
  content_hash: contentHash,
5466
6907
  previous_content_hash: tip?.content_hash ?? null,
5467
- checksum: checksumOf(sql5),
6908
+ checksum: checksumOf(sql7),
5468
6909
  snapshot_checksum: checksumOf(snapshotText),
5469
6910
  destructive
5470
6911
  }
@@ -5579,8 +7020,8 @@ async function checkWorkspace({
5579
7020
  let previousId3;
5580
7021
  let previousHash = null;
5581
7022
  for (const entry of workspace.journal.entries) {
5582
- const sql5 = await readMigrationSql(dir, entry);
5583
- if (checksumOf(sql5) !== entry.checksum) {
7023
+ const sql7 = await readMigrationSql(dir, entry);
7024
+ if (checksumOf(sql7) !== entry.checksum) {
5584
7025
  log(`${MIGRATIONS_DIRNAME}/${entry.name}.sql does not match its journal checksum \u2014 committed migrations must not be edited`);
5585
7026
  return 1;
5586
7027
  }
@@ -5620,7 +7061,7 @@ async function checkWorkspace({
5620
7061
  log(`migrations in sync \u2014 ${workspace.journal.entries.length} committed, tip ${tip.name}`);
5621
7062
  return 0;
5622
7063
  }
5623
- var firstLine = (sql5) => sql5.split("\n")[0] ?? sql5;
7064
+ var firstLine = (sql7) => sql7.split("\n")[0] ?? sql7;
5624
7065
 
5625
7066
  // src/migrations/from-yaml.ts
5626
7067
  function convertV1DefinitionToCanonical(definition) {
@@ -5726,7 +7167,7 @@ function buildRebaseJournalEntry({ namespace, previousHash, newHash, ordinal, sn
5726
7167
  throw new OrmDefinitionError(`rebase ordinal must be an integer \u2265 2 (a rebase sits on an existing journal tip); got ${ordinal}`);
5727
7168
  }
5728
7169
  const name = migrationName(ordinal, newHash);
5729
- const sql5 = renderRebaseMigrationSql({ name, namespace, previousHash, newHash });
7170
+ const sql7 = renderRebaseMigrationSql({ name, namespace, previousHash, newHash });
5730
7171
  const snapshotText = `${JSON.stringify(snapshot, null, 2)}
5731
7172
  `;
5732
7173
  const entry = {
@@ -5734,11 +7175,11 @@ function buildRebaseJournalEntry({ namespace, previousHash, newHash, ordinal, sn
5734
7175
  name,
5735
7176
  content_hash: newHash,
5736
7177
  previous_content_hash: previousHash,
5737
- checksum: checksumOf(sql5),
7178
+ checksum: checksumOf(sql7),
5738
7179
  snapshot_checksum: checksumOf(snapshotText),
5739
7180
  destructive: false
5740
7181
  };
5741
- return { name, entry, sql: sql5, snapshotText };
7182
+ return { name, entry, sql: sql7, snapshotText };
5742
7183
  }
5743
7184
  function renderRebaseMigrationSql({
5744
7185
  name,
@@ -5862,6 +7303,9 @@ exports.AUDIT_ACTIONS = AUDIT_ACTIONS;
5862
7303
  exports.CANONICAL_FORMAT = CANONICAL_FORMAT;
5863
7304
  exports.CAPABILITIES_CLAIM_KEY = CAPABILITIES_CLAIM_KEY;
5864
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;
5865
7309
  exports.CLAIMS_CONTRACT = CLAIMS_CONTRACT;
5866
7310
  exports.CLAIMS_SETTING = CLAIMS_SETTING;
5867
7311
  exports.CLAIM_KEY_PATTERN = CLAIM_KEY_PATTERN;
@@ -5884,9 +7328,12 @@ exports.IR_VERSION = IR_VERSION;
5884
7328
  exports.IrVersionError = IrVersionError;
5885
7329
  exports.LEGACY_IR_VERSION = LEGACY_IR_VERSION;
5886
7330
  exports.LINK_BRAND = LINK_BRAND;
7331
+ exports.MATERIALIZE_ACTOR = MATERIALIZE_ACTOR;
7332
+ exports.MATERIALIZE_CLAIMS_JSON = MATERIALIZE_CLAIMS_JSON;
5887
7333
  exports.MAX_DEFINITION_FILE_BYTES = MAX_DEFINITION_FILE_BYTES;
5888
7334
  exports.MAX_GENERATED_SDL_BYTES = MAX_GENERATED_SDL_BYTES;
5889
7335
  exports.MAX_IDENTIFIER_LENGTH = MAX_IDENTIFIER_LENGTH;
7336
+ exports.MAX_TRACKED_COLUMN_NAME = MAX_TRACKED_COLUMN_NAME;
5890
7337
  exports.MIGRATIONS_DIRNAME = MIGRATIONS_DIRNAME;
5891
7338
  exports.NAMESPACE_CONFIG_BRAND = NAMESPACE_CONFIG_BRAND;
5892
7339
  exports.NAMESPACE_CONFIG_FILE = NAMESPACE_CONFIG_FILE;
@@ -5943,6 +7390,7 @@ exports.canonicalViewSchema = canonicalViewSchema;
5943
7390
  exports.canonicalizeDefinition = canonicalizeDefinition;
5944
7391
  exports.capabilityExpression = capabilityExpression;
5945
7392
  exports.castParts = castParts;
7393
+ exports.cellColumnName = cellColumnName;
5946
7394
  exports.checksumOf = checksumOf;
5947
7395
  exports.claimExpression = claimExpression;
5948
7396
  exports.classifyDestructive = classifyDestructive;
@@ -5961,9 +7409,11 @@ exports.computeSyncedContractHash = computeSyncedContractHash;
5961
7409
  exports.consumerRoleName = consumerRoleName;
5962
7410
  exports.convertV1DefinitionToCanonical = convertV1DefinitionToCanonical;
5963
7411
  exports.createOrmMigrationExport = createOrmMigrationExport;
7412
+ exports.custom = custom;
5964
7413
  exports.declaredCapabilityKeys = declaredCapabilityKeys;
5965
7414
  exports.defineEnum = defineEnum;
5966
7415
  exports.defineNamespace = defineNamespace;
7416
+ exports.defineResolvedTable = defineResolvedTable;
5967
7417
  exports.defineSyncedTable = defineSyncedTable;
5968
7418
  exports.defineTable = defineTable;
5969
7419
  exports.defineView = defineView;
@@ -5973,11 +7423,13 @@ exports.diffStatements = diffStatements;
5973
7423
  exports.emptyJournal = emptyJournal;
5974
7424
  exports.emptySnapshot = emptySnapshot;
5975
7425
  exports.endAuthoringContext = endAuthoringContext;
7426
+ exports.etlSource = etlSource;
5976
7427
  exports.extractNamedParams = extractNamedParams;
5977
7428
  exports.extractOrmSyncedTableContract = extractOrmSyncedTableContract;
5978
7429
  exports.generateNamespaceTypes = generateNamespaceTypes;
5979
7430
  exports.generateSnapshot = generateSnapshot;
5980
7431
  exports.generateSnapshotV2 = generateSnapshotV2;
7432
+ exports.highest = highest;
5981
7433
  exports.identifierSchema = identifierSchema;
5982
7434
  exports.indexByHash = indexByHash;
5983
7435
  exports.isColumnBuilder = isColumnBuilder;
@@ -5990,14 +7442,21 @@ exports.isSeqLink = isSeqLink;
5990
7442
  exports.isSeqNamespaceConfig = isSeqNamespaceConfig;
5991
7443
  exports.isSeqTable = isSeqTable;
5992
7444
  exports.isSeqView = isSeqView;
7445
+ exports.latest = latest;
5993
7446
  exports.limitIrSchema = limitIrSchema;
5994
7447
  exports.loadCommittedMigrations = loadCommittedMigrations;
5995
7448
  exports.loadCommittedTipMigration = loadCommittedTipMigration;
5996
7449
  exports.loadNamespaceDefinition = loadNamespaceDefinition;
5997
7450
  exports.loadNamespaceFromDir = loadNamespaceFromDir;
7451
+ exports.lowest = lowest;
7452
+ exports.managedFn = managedFn;
7453
+ exports.manualSource = manualSource;
7454
+ exports.materializeLockKey = materializeLockKey;
7455
+ exports.materializedRefreshFunctionName = materializedRefreshFunctionName;
5998
7456
  exports.migrationName = migrationName;
5999
7457
  exports.namespaceConfigSchema = namespaceConfigSchema;
6000
7458
  exports.namespaceSchema = namespaceSchema;
7459
+ exports.nonNullFirst = nonNullFirst;
6001
7460
  exports.normalizeCanonical = normalizeCanonical;
6002
7461
  exports.normalizeIr = normalizeIr;
6003
7462
  exports.normalizeSnapshot = normalizeSnapshot;
@@ -6014,12 +7473,16 @@ exports.parseTableFile = parseTableFile;
6014
7473
  exports.parsedPredicateSchema = parsedPredicateSchema;
6015
7474
  exports.pendingKitStatements = pendingKitStatements;
6016
7475
  exports.pendingKitStatementsV2 = pendingKitStatementsV2;
7476
+ exports.planCommittedMigrationV2 = planCommittedMigrationV2;
6017
7477
  exports.planMigration = planMigration;
6018
7478
  exports.planMigrationV2 = planMigrationV2;
6019
7479
  exports.policyExpressionSchema = policyExpressionSchema;
6020
7480
  exports.policyVariantSchema = policyVariantSchema;
6021
7481
  exports.predIrSchema = predIrSchema;
6022
7482
  exports.predicateOperands = predicateOperands;
7483
+ exports.prefetchPlanIrSchema = prefetchPlanIrSchema;
7484
+ exports.prefetchSource = prefetchSource;
7485
+ exports.priority = priority;
6023
7486
  exports.quoteIdentifier = quoteIdentifier;
6024
7487
  exports.quoteLiteral = quoteLiteral;
6025
7488
  exports.readMigrationSql = readMigrationSql;
@@ -6031,6 +7494,8 @@ exports.referencedColumns = referencedColumns;
6031
7494
  exports.renderMigrationSql = renderMigrationSql;
6032
7495
  exports.renderOrmMigrationExport = renderOrmMigrationExport;
6033
7496
  exports.requiredArtifactIrVersion = requiredArtifactIrVersion;
7497
+ exports.resolvedMetaIrSchema = resolvedMetaIrSchema;
7498
+ exports.resolvedSourceIrSchema = resolvedSourceIrSchema;
6034
7499
  exports.revertOverlayMutation = revertOverlayMutation;
6035
7500
  exports.rewriteSyncedSourceRelationsInViewQuery = rewriteSyncedSourceRelationsInViewQuery;
6036
7501
  exports.roleExpression = roleExpression;