@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.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { sql, eq, notExists, is, SQL } from 'drizzle-orm';
2
- import { pgSchema, uniqueIndex, index, foreignKey, primaryKey, PgDialect, QueryBuilder, getViewConfig, extractUsedTable, jsonb, date, timestamp, boolean, doublePrecision, numeric, bigint, integer, text, uuid } from 'drizzle-orm/pg-core';
3
- import { createHash } from 'crypto';
2
+ import { pgSchema, uniqueIndex, index, foreignKey, check, primaryKey, getTableConfig, PgDialect, QueryBuilder, getViewConfig, extractUsedTable, jsonb, date, timestamp, boolean, doublePrecision, numeric, bigint, integer, text, uuid } from 'drizzle-orm/pg-core';
4
3
  import { z } from 'zod';
4
+ import { createHash } from 'crypto';
5
5
  import { createJiti } from 'jiti';
6
6
  import { realpath, stat, readdir, readFile, mkdir, writeFile } from 'fs/promises';
7
7
  import { createRequire } from 'module';
@@ -38,7 +38,8 @@ var ColumnBuilder = class {
38
38
  description: void 0,
39
39
  renamedFrom: void 0,
40
40
  cast: void 0,
41
- deprecated: false
41
+ deprecated: false,
42
+ cell: void 0
42
43
  };
43
44
  }
44
45
  /** Marks this column as (part of) the primary key. Implies NOT NULL. */
@@ -98,6 +99,11 @@ var ColumnBuilder = class {
98
99
  this.state.deprecated = true;
99
100
  return this;
100
101
  }
102
+ /** @internal System cell-metadata part of logical column `of` (generated resolved views only). */
103
+ cellPart(of, part) {
104
+ this.state.cell = { of, part };
105
+ return this;
106
+ }
101
107
  /** Whether `.primaryKey()` was called — collected into the table-level PK. */
102
108
  get isPrimaryKey() {
103
109
  return this.state.primaryKey;
@@ -126,6 +132,7 @@ var ColumnBuilder = class {
126
132
  if (this.state.description !== void 0) column.description = this.state.description;
127
133
  if (this.state.renamedFrom !== void 0) column.renamedFrom = this.state.renamedFrom;
128
134
  if (this.state.cast !== void 0) column.cast = this.state.cast;
135
+ if (this.state.cell !== void 0) column.cell = { ...this.state.cell };
129
136
  return column;
130
137
  }
131
138
  };
@@ -149,12 +156,12 @@ var t = {
149
156
 
150
157
  // src/constants.ts
151
158
  var NAMESPACE_PATTERN = /^[a-z][a-z0-9_]{0,40}$/;
159
+ var ORM_DATA_TOKEN_HEADER = "x-sequence-data-token";
160
+ var ORM_SERVICE_DATA_TOKEN_HEADER = "x-sequence-data-token-service";
152
161
  var IDENTIFIER_PATTERN = /^[a-z][a-z0-9_]*$/;
153
162
  var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
154
163
  var MAX_IDENTIFIER_LENGTH = 63;
155
- var ORM_DATA_TOKEN_HEADER = "x-sequence-data-token";
156
- var ORM_SERVICE_DATA_TOKEN_HEADER = "x-sequence-data-token-service";
157
- var CAPABILITY_KEY_PATTERN = /^(?:org\/)?[a-z0-9-]{1,64}$/;
164
+ var CAPABILITY_KEY_PATTERN = /^(?:org\/|app\/[a-z][a-z0-9-]{0,63}\/)?[a-z0-9-]{1,64}$/;
158
165
  var CLAIM_KEY_PATTERN = /^[a-z][a-z0-9_]*$/;
159
166
  var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]*\.[a-z][a-z0-9_]*$/;
160
167
  var SYNCED_SOURCE_PATTERN = /^gold\.[a-z][a-z0-9_]*$/;
@@ -170,6 +177,27 @@ var MAX_GENERATED_SDL_BYTES = 16 * 1024 * 1024;
170
177
  var ORM_REGISTRATION_MAX_BYTES = 64 * 1024 * 1024;
171
178
  var RESERVED_TABLE_NAMES = /* @__PURE__ */ new Set(["actions", "query"]);
172
179
  var RESERVED_COLUMN_NAMES = /* @__PURE__ */ new Set(["select", "order", "limit", "offset"]);
180
+ var CELL_PARTS = ["at", "by", "rev", "source", "conflicts"];
181
+ var CELL_PART_SUFFIX = {
182
+ at: "__at",
183
+ by: "__by",
184
+ rev: "__rev",
185
+ source: "__src",
186
+ conflicts: "__lost"
187
+ };
188
+ var MAX_TRACKED_COLUMN_NAME = MAX_IDENTIFIER_LENGTH - 6;
189
+ var CELLS_FIELD = "cells";
190
+ function cellColumnName(column, part) {
191
+ return `${column}${CELL_PART_SUFFIX[part]}`;
192
+ }
193
+ var MATERIALIZE_ACTOR = "orm:materialize";
194
+ var MATERIALIZE_CLAIMS_JSON = JSON.stringify({ sub: MATERIALIZE_ACTOR, org: null, roles: [], capabilities: [] });
195
+ function materializedRefreshFunctionName(table) {
196
+ return `${table}__refresh`;
197
+ }
198
+ function materializeLockKey(schema, table) {
199
+ return `orm:materialize:${schema}.${table}`;
200
+ }
173
201
 
174
202
  // src/errors.ts
175
203
  var OrmDefinitionError = class extends Error {
@@ -245,6 +273,8 @@ function toOne(target, options) {
245
273
  on: options.on,
246
274
  references: options.references,
247
275
  enforce: options.enforce ?? false,
276
+ onDelete: options.onDelete,
277
+ onUpdate: options.onUpdate,
248
278
  description: options.description
249
279
  };
250
280
  }
@@ -256,6 +286,8 @@ function toMany(target, options) {
256
286
  on: options.on,
257
287
  references: options.references,
258
288
  enforce: false,
289
+ onDelete: void 0,
290
+ onUpdate: void 0,
259
291
  description: options.description
260
292
  };
261
293
  }
@@ -267,6 +299,8 @@ function toPlatformUser(options) {
267
299
  on: options.on,
268
300
  references: options.references,
269
301
  enforce: false,
302
+ onDelete: void 0,
303
+ onUpdate: void 0,
270
304
  description: options.description
271
305
  };
272
306
  }
@@ -301,6 +335,12 @@ function defineTable(name, columns, config = {}) {
301
335
  }
302
336
  const columnName = builder.columnNameFor(propertyName);
303
337
  assertIdentifier({ value: columnName, what: `table "${name}": column "${propertyName}" column name`, file, pattern: COLUMN_IDENTIFIER_PATTERN });
338
+ if (config.materializes === void 0 && (propertyName === CELLS_FIELD || Object.values(CELL_PART_SUFFIX).some((suffix) => columnName.endsWith(suffix) || propertyName.endsWith(suffix)))) {
339
+ throw new OrmDefinitionError(
340
+ `table "${name}": "${propertyName}" collides with the reserved cell-metadata names ('${CELLS_FIELD}', or a ${Object.values(CELL_PART_SUFFIX).join("/")} suffix)`,
341
+ file
342
+ );
343
+ }
304
344
  const existing = seenColumnNames.get(columnName);
305
345
  if (existing !== void 0) {
306
346
  throw new OrmDefinitionError(
@@ -548,11 +588,11 @@ function compileViewQueries(input) {
548
588
  );
549
589
  }
550
590
  if (!config.query) throw new OrmDefinitionError(`view "${authored.viewName}" produced no query`, authored.file);
551
- const tableDependencies = extractUsedTable(config.query).map((physical) => {
552
- const logical = physicalToLogical.get(physical);
591
+ const tableDependencies = extractUsedTable(config.query).map((physical2) => {
592
+ const logical = physicalToLogical.get(physical2);
553
593
  if (!logical) {
554
594
  throw new OrmDefinitionError(
555
- `view "${authored.viewName}" query references unmanaged relation "${physical}" \u2014 use relation(...) with a namespace table or view`,
595
+ `view "${authored.viewName}" query references unmanaged relation "${physical2}" \u2014 use relation(...) with a namespace table or view`,
556
596
  authored.file
557
597
  );
558
598
  }
@@ -876,9 +916,477 @@ function scalarColumnType(table, property, helper) {
876
916
  }
877
917
  return type;
878
918
  }
919
+
920
+ // src/compiler/sql.ts
921
+ var CLAIMS_SETTING = "request.jwt.claims";
922
+ var CAPABILITIES_CLAIM_KEY = "capabilities";
923
+ var ROLES_CLAIM_KEY = "roles";
924
+ var quoteIdentifier = (name) => `"${name.replaceAll('"', '""')}"`;
925
+ var quoteLiteral = (value) => `'${value.replaceAll("'", "''")}'`;
926
+ var CLAIMS_SOURCE = `nullif(current_setting('${CLAIMS_SETTING}', true), '')::jsonb`;
927
+ var claimExpression = (key) => `(${CLAIMS_SOURCE} ->> ${quoteLiteral(key)})`;
928
+ var capabilityExpression = (key) => `coalesce(${CLAIMS_SOURCE} -> '${CAPABILITIES_CLAIM_KEY}' ? ${quoteLiteral(key)}, false)`;
929
+ var roleExpression = (key) => `coalesce(${CLAIMS_SOURCE} -> '${ROLES_CLAIM_KEY}' ? ${quoteLiteral(key)}, false)`;
930
+
931
+ // src/checks/compile.ts
932
+ function tokenize(source) {
933
+ const tokens = [];
934
+ for (let position = 0; position < source.length; ) {
935
+ const char = source[position];
936
+ if (/\s/.test(char)) {
937
+ position++;
938
+ continue;
939
+ }
940
+ if (char === "(" || char === ")" || char === ",") {
941
+ tokens.push({ kind: char === "(" ? "lparen" : char === ")" ? "rparen" : "comma", value: char, position });
942
+ position++;
943
+ continue;
944
+ }
945
+ if (char === "'") {
946
+ const start = position++;
947
+ let value = "";
948
+ let closed = false;
949
+ while (position < source.length) {
950
+ if (source[position] !== "'") {
951
+ value += source[position++];
952
+ continue;
953
+ }
954
+ if (source[position + 1] === "'") {
955
+ value += "'";
956
+ position += 2;
957
+ continue;
958
+ }
959
+ position++;
960
+ closed = true;
961
+ break;
962
+ }
963
+ if (!closed) throw new Error(`unterminated string at position ${start}`);
964
+ if (/[\x00-\x1f\x7f]/.test(value)) throw new Error(`control character in string at position ${start}`);
965
+ tokens.push({ kind: "string", value, position: start });
966
+ continue;
967
+ }
968
+ const operator = /^(<=|>=|<>|!=|!~|=|<|>|~)/.exec(source.slice(position));
969
+ if (operator) {
970
+ tokens.push({ kind: "operator", value: operator[0] === "<>" ? "!=" : operator[0], position });
971
+ position += operator[0].length;
972
+ continue;
973
+ }
974
+ const number = /^-?\d+(?:\.\d+)?/.exec(source.slice(position));
975
+ if (number) {
976
+ tokens.push({ kind: "number", value: number[0], position });
977
+ position += number[0].length;
978
+ continue;
979
+ }
980
+ const identifier3 = /^[a-zA-Z_][a-zA-Z_0-9]*/.exec(source.slice(position));
981
+ if (identifier3) {
982
+ tokens.push({ kind: "identifier", value: identifier3[0], position });
983
+ position += identifier3[0].length;
984
+ continue;
985
+ }
986
+ throw new Error(`unexpected character '${char}' at position ${position}`);
987
+ }
988
+ tokens.push({ kind: "end", value: "", position: source.length });
989
+ if (tokens.length > 256) throw new Error("check expression exceeds 256 tokens");
990
+ return tokens;
991
+ }
992
+ var Parser = class {
993
+ constructor(tokens, columns) {
994
+ this.tokens = tokens;
995
+ this.columns = /* @__PURE__ */ new Map();
996
+ for (const column of columns) {
997
+ for (const alias of [column.name, column.columnName]) {
998
+ const previous = this.columns.get(alias);
999
+ if (previous && previous.columnName !== column.columnName) {
1000
+ throw new Error(`ambiguous check column '${alias}'`);
1001
+ }
1002
+ this.columns.set(alias, column);
1003
+ }
1004
+ }
1005
+ }
1006
+ index = 0;
1007
+ depth = 0;
1008
+ columns;
1009
+ parse() {
1010
+ const expression = this.parseOr();
1011
+ this.expect("end", "end of expression");
1012
+ return expression;
1013
+ }
1014
+ parseOr() {
1015
+ let left = this.parseAnd();
1016
+ while (this.matchKeyword("or")) left = `(${left} OR ${this.parseAnd()})`;
1017
+ return left;
1018
+ }
1019
+ parseAnd() {
1020
+ let left = this.parseNot();
1021
+ while (this.matchKeyword("and")) left = `(${left} AND ${this.parseNot()})`;
1022
+ return left;
1023
+ }
1024
+ parseNot() {
1025
+ if (this.matchKeyword("not")) return `(NOT ${this.parseNot()})`;
1026
+ return this.parsePrimary();
1027
+ }
1028
+ parsePrimary() {
1029
+ if (this.peek().kind === "lparen") {
1030
+ if (++this.depth > 32) throw new Error("check expression nesting exceeds 32 levels");
1031
+ this.advance();
1032
+ const expression = this.parseOr();
1033
+ this.expect("rparen", "')'");
1034
+ this.depth--;
1035
+ return `(${expression})`;
1036
+ }
1037
+ return this.parseComparison();
1038
+ }
1039
+ parseComparison() {
1040
+ const left = this.parseValue();
1041
+ if (this.matchKeyword("is")) {
1042
+ const negate = this.matchKeyword("not");
1043
+ this.expectKeyword("null");
1044
+ return `${left.sql} IS ${negate ? "NOT " : ""}NULL`;
1045
+ }
1046
+ if (this.matchKeyword("between")) {
1047
+ const lower = this.parseValue();
1048
+ this.expectKeyword("and");
1049
+ const upper = this.parseValue();
1050
+ return `${left.sql} BETWEEN ${lower.sql} AND ${upper.sql}`;
1051
+ }
1052
+ if (this.matchKeyword("in")) {
1053
+ this.expect("lparen", "'(' after IN");
1054
+ const values = [this.parseValue()];
1055
+ while (this.peek().kind === "comma") {
1056
+ this.advance();
1057
+ values.push(this.parseValue());
1058
+ }
1059
+ this.expect("rparen", "')' after IN values");
1060
+ if (values.some((value) => !value.literal)) throw new Error("IN values must be literals");
1061
+ return `${left.sql} IN (${values.map((value) => value.sql).join(", ")})`;
1062
+ }
1063
+ const operator = this.expect("operator", "a comparison operator");
1064
+ const right = this.parseValue();
1065
+ if (operator.value === "~" || operator.value === "!~") {
1066
+ if (left.column && left.column.type !== "text") throw new Error("regex checks require a text column");
1067
+ if (!right.literal) throw new Error("regex pattern must be a literal");
1068
+ }
1069
+ return `${left.sql} ${operator.value} ${right.sql}`;
1070
+ }
1071
+ parseValue() {
1072
+ const token = this.advance();
1073
+ if (token.kind === "string") return { sql: quoteLiteral(token.value), literal: true };
1074
+ if (token.kind === "number") {
1075
+ const number = Number(token.value);
1076
+ if (!Number.isFinite(number) || Number.isInteger(number) && !Number.isSafeInteger(number)) {
1077
+ throw new Error(`unsafe numeric literal at position ${token.position}`);
1078
+ }
1079
+ return { sql: token.value, literal: true };
1080
+ }
1081
+ if (token.kind !== "identifier") throw new Error(`expected a column or literal at position ${token.position}`);
1082
+ const keyword = token.value.toLowerCase();
1083
+ if (keyword === "true" || keyword === "false" || keyword === "null") return { sql: keyword.toUpperCase(), literal: true };
1084
+ if (keyword === "btrim") {
1085
+ this.expect("lparen", "'(' after btrim");
1086
+ const column2 = this.parseColumn();
1087
+ this.expect("rparen", "')' after btrim column");
1088
+ if (column2.type !== "text") throw new Error("btrim requires a text column");
1089
+ return { sql: `btrim(${quoteIdentifier(column2.columnName)})`, column: column2 };
1090
+ }
1091
+ const column = this.columns.get(token.value);
1092
+ if (!column) throw new Error(`unknown check column '${token.value}'`);
1093
+ return { sql: quoteIdentifier(column.columnName), column };
1094
+ }
1095
+ parseColumn() {
1096
+ const token = this.expect("identifier", "a column name");
1097
+ const column = this.columns.get(token.value);
1098
+ if (!column) throw new Error(`unknown check column '${token.value}'`);
1099
+ return column;
1100
+ }
1101
+ peek() {
1102
+ return this.tokens[this.index];
1103
+ }
1104
+ advance() {
1105
+ const token = this.peek();
1106
+ if (token.kind !== "end") this.index++;
1107
+ return token;
1108
+ }
1109
+ matchKeyword(keyword) {
1110
+ if (this.peek().kind !== "identifier" || this.peek().value.toLowerCase() !== keyword) return false;
1111
+ this.advance();
1112
+ return true;
1113
+ }
1114
+ expectKeyword(keyword) {
1115
+ if (!this.matchKeyword(keyword)) throw new Error(`expected '${keyword}' at position ${this.peek().position}`);
1116
+ }
1117
+ expect(kind, description) {
1118
+ const token = this.peek();
1119
+ if (token.kind !== kind) throw new Error(`expected ${description} at position ${token.position}`);
1120
+ return this.advance();
1121
+ }
1122
+ };
1123
+ function compileCheckExpression(source, columns) {
1124
+ return new Parser(tokenize(source), columns).parse();
1125
+ }
1126
+ var policyVariantSchema = z.object({
1127
+ /** Claims-only guard (capability()/role()/claim() comparisons — no columns). */
1128
+ when: z.string().min(1).max(1e3),
1129
+ /** Exactly one of: allow-all bypass, or a row predicate. */
1130
+ allow: z.literal(true).optional(),
1131
+ policy: z.string().min(1).max(2e3).optional()
1132
+ }).refine((v) => v.allow === true !== (typeof v.policy === "string"), {
1133
+ message: "A variant declares exactly one of allow: true or policy"
1134
+ });
1135
+ var policyExpressionSchema = z.union([
1136
+ z.string().min(1).max(2e3),
1137
+ z.object({
1138
+ default: z.string().min(1).max(2e3),
1139
+ variants: z.array(policyVariantSchema).max(10).default([])
1140
+ })
1141
+ ]);
1142
+ function parseDurationMs(input) {
1143
+ if (typeof input === "number") return Number.isInteger(input) && input > 0 ? input : void 0;
1144
+ const match = /^(\d+(?:\.\d+)?)(ms|s|m)$/.exec(input);
1145
+ if (!match) return void 0;
1146
+ const factor = match[2] === "ms" ? 1 : match[2] === "s" ? 1e3 : 6e4;
1147
+ const value = Math.round(Number(match[1]) * factor);
1148
+ return value > 0 ? value : void 0;
1149
+ }
1150
+ var identifier = z.string().min(1).max(63).regex(/^[a-z][a-z0-9_]*$/);
1151
+ var durationMs = z.number().int().positive().max(24 * 60 * 60 * 1e3);
1152
+ var RESOLVED_VIEW_SYSTEM_COLUMNS = {
1153
+ writebackStatus: { name: "writebackStatus", columnName: "_writeback_status", type: "text" },
1154
+ writebackError: { name: "writebackError", columnName: "_writeback_error", type: "text" }
1155
+ };
1156
+ var RESOLVED_SOURCE_SYSTEM_COLUMNS = {
1157
+ tombstoned: { name: "sysTombstoned", columnName: "_tombstoned" },
1158
+ fetchedAt: { name: "sysFetchedAt", columnName: "_fetched_at" },
1159
+ writebackStatus: { name: "sysWritebackStatus", columnName: "_writeback_status" },
1160
+ writebackError: { name: "sysWritebackError", columnName: "_writeback_error" },
1161
+ writebackAt: { name: "sysWritebackAt", columnName: "_writeback_at" }
1162
+ };
1163
+ var RESOLVED_VIEW_CELL_PARTS = ["source", "at", "by", "conflicts"];
1164
+ var RESERVED_RESOLVED_COLUMN_NAMES = /* @__PURE__ */ new Set([
1165
+ ...Object.values(RESOLVED_VIEW_SYSTEM_COLUMNS).flatMap((c) => [c.name, c.columnName]),
1166
+ ...Object.values(RESOLVED_SOURCE_SYSTEM_COLUMNS).flatMap((c) => [c.name, c.columnName])
1167
+ ]);
1168
+ var COMPARATORS = ["latest", "highest", "lowest", "priority", "nonNullFirst", "custom"];
1169
+ var canonicalFunctionRefSchema = z.object({
1170
+ /** `managed` = a Managed Function id/slug; `native` = an in-process Atlas function (reserved). */
1171
+ kind: z.enum(["managed", "native"]),
1172
+ id: z.string().min(1).max(200)
1173
+ }).strict();
1174
+ var canonicalFetchShapeSchema = z.union([
1175
+ z.object({ shape: z.literal("single") }).strict(),
1176
+ z.object({ shape: z.literal("batch"), maxKeys: z.number().int().min(2).max(500) }).strict()
1177
+ ]);
1178
+ var canonicalResolvedSourceSchema = z.object({
1179
+ name: identifier,
1180
+ kind: z.enum(["manual", "etl", "prefetch"]),
1181
+ /** Backing relation in this namespace: an internal table (manual/prefetch) or a synced table (etl). */
1182
+ table: identifier,
1183
+ /** Resolved columns (logical names) this source supplies — the primary key is implicit. */
1184
+ columns: z.array(z.string().min(1).max(63)).min(1).max(500),
1185
+ /** etl: logical timestamptz/date column on the synced table used as every cell's updated_at. */
1186
+ updatedAt: z.string().min(1).max(63).optional(),
1187
+ /** prefetch: resolved column holding the source system's key (may be the primary key). */
1188
+ key: z.string().min(1).max(63).optional(),
1189
+ fetch: canonicalFetchShapeSchema.optional(),
1190
+ function: canonicalFunctionRefSchema.optional(),
1191
+ prefetchDefaults: z.object({
1192
+ maxStalenessMs: durationMs,
1193
+ timeoutMs: durationMs,
1194
+ onError: z.enum(["fallback", "fail"])
1195
+ }).strict().optional(),
1196
+ writeback: z.object({
1197
+ columns: z.array(z.string().min(1).max(63)).min(1).max(500),
1198
+ defaultMode: z.enum(["sync", "async"]),
1199
+ timeoutMs: durationMs,
1200
+ /** Remove confirmed cells from the manual source so a stale edit cannot shadow the source. */
1201
+ clearManualOnConfirm: z.boolean()
1202
+ }).strict().optional()
1203
+ }).strict();
1204
+ var canonicalResolveRuleSchema = z.object({
1205
+ column: z.string().min(1).max(63),
1206
+ comparator: z.enum(COMPARATORS),
1207
+ /** Source names, in tie-break order. */
1208
+ candidates: z.array(identifier).min(1).max(16),
1209
+ /** custom only: SQL ORDER BY key over value / updated_at / source / actor / ord. */
1210
+ orderBy: z.string().min(1).max(2e3).optional()
1211
+ }).strict();
1212
+ var canonicalResolvedSchema = z.object({
1213
+ sources: z.array(canonicalResolvedSourceSchema).min(1).max(16),
1214
+ /** One rule per resolved (non-pk) column; documents the generated view for tooling. */
1215
+ resolve: z.array(canonicalResolveRuleSchema).max(500),
1216
+ /** Sources whose tombstone hides the resolved row (v1: the manual source only). */
1217
+ deletedBy: z.array(identifier).max(1).default([]),
1218
+ /**
1219
+ * Materialized: the view reads an indexed internal table (`table`) that the
1220
+ * data plane keeps in step with `compute`, the internal view holding the
1221
+ * resolution query. Omitted when the view computes on read.
1222
+ */
1223
+ materialized: z.object({ table: identifier, compute: identifier }).strict().optional(),
1224
+ /** Write policies evaluated against the RESOLVED row (reads use the view's read policy). */
1225
+ policies: z.object({
1226
+ write: policyExpressionSchema.optional(),
1227
+ insert: policyExpressionSchema.optional(),
1228
+ update: policyExpressionSchema.optional()
1229
+ }).strict().default({})
1230
+ }).strict();
1231
+ function validateResolvedView({ view, tables, views = [] }) {
1232
+ const resolved = view.resolved;
1233
+ if (!resolved) return [];
1234
+ const errors = [];
1235
+ const fail = (message) => errors.push(`resolved table '${view.view}': ${message}`);
1236
+ const pkColumn = view.primaryKey?.length === 1 ? view.columns.find((c) => c.columnName === view.primaryKey?.[0]) : void 0;
1237
+ if (!pkColumn) {
1238
+ fail("requires exactly one primary-key column");
1239
+ return errors;
1240
+ }
1241
+ const systemNames = new Set(Object.values(RESOLVED_VIEW_SYSTEM_COLUMNS).map((c) => c.name));
1242
+ const scope = {
1243
+ view,
1244
+ resolved,
1245
+ pkColumn,
1246
+ dataColumns: new Map(view.columns.filter((c) => !systemNames.has(c.name) && c.cell === void 0).map((c) => [c.name, c])),
1247
+ sourceByName: /* @__PURE__ */ new Map(),
1248
+ fail
1249
+ };
1250
+ if (resolved.sources.filter((source) => source.kind === "manual").length !== 1) fail("requires exactly one manual source");
1251
+ const tableByName = new Map(tables.map((table) => [table.table, table]));
1252
+ for (const source of resolved.sources) {
1253
+ if (scope.sourceByName.has(source.name)) fail(`duplicate source '${source.name}'`);
1254
+ scope.sourceByName.set(source.name, source);
1255
+ const table = tableByName.get(source.table);
1256
+ if (!table) fail(`source '${source.name}' references unknown table '${source.table}'`);
1257
+ else validateSource({ scope, source, table });
1258
+ }
1259
+ validateRules(scope);
1260
+ validateViewCells(scope);
1261
+ if (resolved.materialized) validateMaterialized({ scope, tableByName, views });
1262
+ for (const name of resolved.deletedBy) {
1263
+ if (scope.sourceByName.get(name)?.kind !== "manual") fail(`deletedBy '${name}' must be the manual source`);
1264
+ }
1265
+ return errors;
1266
+ }
1267
+ function validateSource({ scope, source, table }) {
1268
+ const { fail } = scope;
1269
+ if (source.kind === "etl" && table.source === void 0) fail(`etl source '${source.name}' must reference a synced table`);
1270
+ if (source.kind !== "etl" && (table.internal !== true || table.cellMetadata !== true)) {
1271
+ fail(`${source.kind} source '${source.name}' must reference an internal table with cell metadata`);
1272
+ }
1273
+ const hasRev = table.columns.some((column) => column.cell?.part === "rev");
1274
+ if (source.kind === "manual" && !hasRev) fail(`manual source '${source.name}' must carry '__rev' cell columns`);
1275
+ if (source.kind === "prefetch" && hasRev) fail(`prefetch source '${source.name}' must not carry '__rev' cell columns`);
1276
+ const byName = new Map(table.columns.map((c) => [c.name, c]));
1277
+ validateSourceColumns({ scope, source, byName });
1278
+ if (source.kind === "etl") {
1279
+ const updatedAt = source.updatedAt === void 0 ? void 0 : byName.get(source.updatedAt);
1280
+ if (source.updatedAt !== void 0 && updatedAt?.type !== "timestamptz" && updatedAt?.type !== "date") {
1281
+ fail(`etl source '${source.name}' updatedAt must name a timestamptz/date column on '${source.table}'`);
1282
+ }
1283
+ }
1284
+ validatePrefetchFields({ scope, source });
1285
+ }
1286
+ function validatePrefetchFields({ scope, source }) {
1287
+ const { fail, pkColumn } = scope;
1288
+ if (source.kind !== "prefetch") {
1289
+ if (source.key || source.fetch || source.function || source.prefetchDefaults || source.writeback) {
1290
+ fail(`only prefetch sources may declare key/fetch/function/prefetchDefaults/writeback ('${source.name}')`);
1291
+ }
1292
+ return;
1293
+ }
1294
+ if (!source.key || !source.fetch || !source.function || !source.prefetchDefaults) {
1295
+ fail(`prefetch source '${source.name}' requires key, fetch, function and prefetchDefaults`);
1296
+ } else if (source.key !== pkColumn.name && !source.columns.includes(source.key)) {
1297
+ fail(`prefetch source '${source.name}' key '${source.key}' must be the primary key or one of its columns`);
1298
+ }
1299
+ for (const column of source.writeback?.columns ?? []) {
1300
+ if (!source.columns.includes(column)) fail(`prefetch source '${source.name}' writes back '${column}', which it does not supply`);
1301
+ }
1302
+ }
1303
+ function validateSourceColumns({ scope, source, byName }) {
1304
+ const { fail, pkColumn, dataColumns } = scope;
1305
+ for (const column of [pkColumn.name, ...source.columns]) {
1306
+ const own = column === pkColumn.name ? pkColumn : dataColumns.get(column);
1307
+ const theirs = byName.get(column);
1308
+ if (!own) fail(`source '${source.name}' supplies unknown column '${column}'`);
1309
+ else if (!theirs) fail(`source '${source.name}' table '${source.table}' has no column '${column}'`);
1310
+ else if (JSON.stringify(theirs.type) !== JSON.stringify(own.type)) fail(`column '${column}' has a different type on source '${source.name}'`);
1311
+ }
1312
+ }
1313
+ function validateRules({ resolved, pkColumn, dataColumns, sourceByName, fail }) {
1314
+ const ruled = /* @__PURE__ */ new Set();
1315
+ for (const rule of resolved.resolve) {
1316
+ if (!dataColumns.has(rule.column) || rule.column === pkColumn.name) {
1317
+ fail(`resolve rule targets unknown column '${rule.column}'`);
1318
+ continue;
1319
+ }
1320
+ if (ruled.has(rule.column)) fail(`column '${rule.column}' has more than one resolve rule`);
1321
+ ruled.add(rule.column);
1322
+ if (rule.comparator === "custom" !== (rule.orderBy !== void 0)) {
1323
+ fail(`column '${rule.column}': orderBy is required for custom comparators and only allowed there`);
1324
+ }
1325
+ if (new Set(rule.candidates).size !== rule.candidates.length) fail(`column '${rule.column}' repeats a candidate`);
1326
+ for (const candidate of rule.candidates) {
1327
+ const source = sourceByName.get(candidate);
1328
+ if (!source) fail(`column '${rule.column}' names unknown candidate source '${candidate}'`);
1329
+ else if (!source.columns.includes(rule.column)) fail(`column '${rule.column}' candidate '${candidate}' does not supply it`);
1330
+ }
1331
+ }
1332
+ for (const column of dataColumns.keys()) {
1333
+ if (column !== pkColumn.name && !ruled.has(column)) fail(`column '${column}' has no resolve rule`);
1334
+ }
1335
+ }
1336
+ var VIEW_CELL_TYPES = {
1337
+ source: "text",
1338
+ at: "timestamptz",
1339
+ by: "text",
1340
+ conflicts: "jsonb"
1341
+ };
1342
+ function validateViewCells({ view, dataColumns, pkColumn, fail }) {
1343
+ const expected = /* @__PURE__ */ new Map();
1344
+ for (const column of dataColumns.values()) {
1345
+ if (column.name === pkColumn.name) continue;
1346
+ for (const part of RESOLVED_VIEW_CELL_PARTS) {
1347
+ expected.set(cellColumnName(column.name, part), { part, columnName: cellColumnName(column.columnName, part) });
1348
+ }
1349
+ }
1350
+ for (const column of view.columns) {
1351
+ if (!column.cell) continue;
1352
+ const want = expected.get(column.name);
1353
+ if (!want || want.part !== column.cell.part || want.columnName !== column.columnName || cellColumnName(column.cell.of, column.cell.part) !== column.name) {
1354
+ fail(`unexpected cell column '${column.name}'`);
1355
+ continue;
1356
+ }
1357
+ if (column.type !== VIEW_CELL_TYPES[column.cell.part]) fail(`cell column '${column.name}' must be ${VIEW_CELL_TYPES[column.cell.part]}`);
1358
+ expected.delete(column.name);
1359
+ }
1360
+ for (const name of expected.keys()) fail(`missing cell column '${name}'`);
1361
+ }
1362
+ var columnSignature = (columns) => JSON.stringify(columns.map((column) => [column.name, column.columnName, column.type]));
1363
+ function validateMaterialized({ scope, tableByName, views }) {
1364
+ const { view, resolved, fail } = scope;
1365
+ const { table: tableName, compute: computeName } = resolved.materialized;
1366
+ const table = tableByName.get(tableName);
1367
+ if (!table || table.internal !== true || table.materializes !== view.view) {
1368
+ fail(`materialized table '${tableName}' must be an internal table that materializes '${view.view}'`);
1369
+ } else {
1370
+ if (columnSignature(table.columns) !== columnSignature(view.columns)) fail(`materialized table '${tableName}' must have exactly the view's columns`);
1371
+ if (JSON.stringify(table.primaryKey ?? []) !== JSON.stringify(view.primaryKey ?? [])) fail(`materialized table '${tableName}' must share the view's primary key`);
1372
+ }
1373
+ const compute = views.find((candidate) => candidate.view === computeName);
1374
+ if (!compute || compute.internal !== true) fail(`compute view '${computeName}' must be an internal view`);
1375
+ else if (columnSignature(compute.columns) !== columnSignature(view.columns)) fail(`compute view '${computeName}' must have exactly the view's columns`);
1376
+ for (const source of resolved.sources) {
1377
+ if (source.kind !== "etl") continue;
1378
+ const read = tableByName.get(source.table)?.policies?.read;
1379
+ const unconditional = typeof read === "string" ? read.trim() === "true" : read !== void 0 && read.default.trim() === "true" && read.variants.length === 0;
1380
+ if (!unconditional) {
1381
+ fail(`etl source '${source.name}' must have read policy 'true' to be materialized (the copy is shared by every caller)`);
1382
+ }
1383
+ }
1384
+ }
1385
+
1386
+ // src/canonical/v2.ts
879
1387
  var CANONICAL_FORMAT = 2;
880
1388
  var IDENTIFIER_RE = /^[a-z][a-z0-9_]*$/;
881
- var identifier = z.string().min(1).max(63).regex(IDENTIFIER_RE);
1389
+ var identifier2 = z.string().min(1).max(63).regex(IDENTIFIER_RE);
882
1390
  var columnIdentifier = z.string().min(1).max(63).regex(COLUMN_IDENTIFIER_PATTERN);
883
1391
  var COLUMN_TYPES = [
884
1392
  "uuid",
@@ -894,14 +1402,14 @@ var COLUMN_TYPES = [
894
1402
  ];
895
1403
  var columnTypeSchema = z.enum(COLUMN_TYPES);
896
1404
  var canonicalEnumSchema = z.object({
897
- name: identifier,
1405
+ name: identifier2,
898
1406
  values: z.array(z.string().min(1).max(63)).min(1).max(100)
899
1407
  });
900
1408
  var canonicalColumnSchema = z.object({
901
1409
  /** Property name in TS (camelCase); columnName is the Postgres name. */
902
1410
  name: z.string().min(1).max(63),
903
1411
  columnName: columnIdentifier,
904
- type: z.union([columnTypeSchema, z.object({ enum: identifier })]),
1412
+ type: z.union([columnTypeSchema, z.object({ enum: identifier2 })]),
905
1413
  nullable: z.boolean().default(true),
906
1414
  unique: z.boolean().default(false),
907
1415
  /** Literal default, or one of the allowlisted SQL functions. */
@@ -917,7 +1425,14 @@ var canonicalColumnSchema = z.object({
917
1425
  /** Migration hints — same semantics as v1. */
918
1426
  renamedFrom: columnIdentifier.optional(),
919
1427
  cast: z.string().max(200).optional(),
920
- deprecated: z.boolean().default(false)
1428
+ deprecated: z.boolean().default(false),
1429
+ /**
1430
+ * Marks a system cell-metadata column (see CELL_PARTS in constants): part
1431
+ * `part` of the cell of logical column `of`. Synthesized by authoring,
1432
+ * never client-writable, absent from GraphQL scalar fields. Omitted on
1433
+ * ordinary columns so their hashes are unchanged.
1434
+ */
1435
+ cell: z.object({ of: z.string().min(1).max(63), part: z.enum(CELL_PARTS) }).strict().optional()
921
1436
  }).superRefine((column, ctx) => {
922
1437
  if (column.onUpdateFn === void 0) return;
923
1438
  if (typeof column.type !== "string" || column.type !== "timestamptz" && column.type !== "date") {
@@ -932,36 +1447,26 @@ var canonicalLinkSchema = z.object({
932
1447
  name: z.string().min(1).max(63),
933
1448
  kind: z.enum(["toOne", "toMany", "toPlatformUser"]),
934
1449
  /** Target table name (namespace-local). Absent for toPlatformUser. */
935
- target: identifier.optional(),
1450
+ target: identifier2.optional(),
936
1451
  /** FK column on the owning side (toOne/toPlatformUser: on this table; toMany: on the target). */
937
1452
  on: columnIdentifier,
938
1453
  /** Referenced column on the target (default: target's PK / platform user `sub`). */
939
1454
  references: columnIdentifier.optional(),
940
1455
  /** enforce: true emits a real FK constraint (same-namespace toOne only). */
941
1456
  enforce: z.boolean().default(false),
1457
+ onDelete: z.enum(["cascade", "restrict", "no action", "set null", "set default"]).optional(),
1458
+ onUpdate: z.enum(["cascade", "restrict", "no action", "set null", "set default"]).optional(),
942
1459
  description: z.string().max(500).optional()
943
1460
  });
944
1461
  var canonicalIndexSchema = z.object({
945
1462
  columns: z.array(columnIdentifier).min(1),
946
1463
  unique: z.boolean().default(false),
947
- name: identifier.optional()
1464
+ name: identifier2.optional()
948
1465
  });
949
- var policyVariantSchema = z.object({
950
- /** Claims-only guard (capability()/role()/claim() comparisons — no columns). */
951
- when: z.string().min(1).max(1e3),
952
- /** Exactly one of: allow-all bypass, or a row predicate. */
953
- allow: z.literal(true).optional(),
954
- policy: z.string().min(1).max(2e3).optional()
955
- }).refine((v) => v.allow === true !== (typeof v.policy === "string"), {
956
- message: "A variant declares exactly one of allow: true or policy"
957
- });
958
- var policyExpressionSchema = z.union([
959
- z.string().min(1).max(2e3),
960
- z.object({
961
- default: z.string().min(1).max(2e3),
962
- variants: z.array(policyVariantSchema).max(10).default([])
963
- })
964
- ]);
1466
+ var canonicalCheckSchema = z.object({
1467
+ name: identifier2,
1468
+ expression: z.string().min(1).max(2e3)
1469
+ }).strict();
965
1470
  var canonicalPoliciesSchema = z.object({
966
1471
  read: policyExpressionSchema.optional(),
967
1472
  write: policyExpressionSchema.optional(),
@@ -1149,7 +1654,7 @@ var VIEW_QUERY_FUNCTION_ALLOWLIST = [
1149
1654
  "width_bucket"
1150
1655
  ];
1151
1656
  var canonicalActionSchema = z.object({
1152
- name: identifier,
1657
+ name: identifier2,
1153
1658
  requires: z.string().max(1e3).optional(),
1154
1659
  params: z.record(z.string(), z.object({
1155
1660
  type: columnTypeSchema,
@@ -1161,9 +1666,9 @@ var canonicalActionSchema = z.object({
1161
1666
  });
1162
1667
  var canonicalCowMutationSchema = z.object({
1163
1668
  kind: z.enum(["override", "revert"]),
1164
- name: identifier,
1669
+ name: identifier2,
1165
1670
  /** Synced table (same namespace) the overlay copies from / falls back to. */
1166
- synced: identifier,
1671
+ synced: identifier2,
1167
1672
  /** Single-column primary key PROPERTY (TS camelCase, like column `name`) shared by overlay and synced. */
1168
1673
  pk: z.string().min(1).max(63),
1169
1674
  /** Override: contract properties copied INSERT…SELECT from synced (pk included). Empty for revert. */
@@ -1191,23 +1696,27 @@ var canonicalCowMutationSchema = z.object({
1191
1696
  }
1192
1697
  });
1193
1698
  var canonicalForeignKeySchema = z.object({
1194
- name: identifier,
1699
+ name: identifier2,
1195
1700
  columns: z.array(columnIdentifier).min(1).max(8),
1196
- target: identifier,
1701
+ target: identifier2,
1197
1702
  references: z.array(columnIdentifier).min(1).max(8),
1198
- deferrable: z.enum(["initially-immediate", "initially-deferred"]).optional()
1703
+ deferrable: z.enum(["initially-immediate", "initially-deferred"]).optional(),
1704
+ onDelete: z.enum(["cascade", "restrict", "no action", "set null", "set default"]).optional(),
1705
+ onUpdate: z.enum(["cascade", "restrict", "no action", "set null", "set default"]).optional()
1199
1706
  }).strict();
1200
1707
  var canonicalTableSchema = z.object({
1201
- table: identifier,
1708
+ table: identifier2,
1202
1709
  description: z.string().max(2e3).optional(),
1203
1710
  /** Table-level rename hint (new in v2). */
1204
- renamedFrom: identifier.optional(),
1711
+ renamedFrom: identifier2.optional(),
1205
1712
  /** Synced/reflected read-only tables (`source: gold.*`) — v1 semantics. */
1206
1713
  source: z.string().regex(/^gold\.[a-z][a-z0-9_]*$/).optional(),
1207
1714
  columns: z.array(canonicalColumnSchema).default([]),
1208
1715
  primaryKey: z.array(columnIdentifier).min(1).max(8).optional(),
1209
1716
  links: z.array(canonicalLinkSchema).default([]),
1210
1717
  indexes: z.array(canonicalIndexSchema).default([]),
1718
+ /** Omitted by old definitions, preserving their existing content hashes. */
1719
+ checks: z.array(canonicalCheckSchema).max(100).optional(),
1211
1720
  policies: canonicalPoliciesSchema.default({}),
1212
1721
  /**
1213
1722
  * v1-conversion carry-over only. v2 has no action surface — the runtime
@@ -1218,22 +1727,55 @@ var canonicalTableSchema = z.object({
1218
1727
  /** Optional (not defaulted) so pre-CoW canonical documents keep byte-identical hashes. */
1219
1728
  cowMutations: z.array(canonicalCowMutationSchema).optional(),
1220
1729
  /** Omitted by old definitions, preserving their existing content hashes. */
1221
- foreignKeys: z.array(canonicalForeignKeySchema).max(100).optional()
1730
+ foreignKeys: z.array(canonicalForeignKeySchema).max(100).optional(),
1731
+ /**
1732
+ * Per-cell edit metadata (`cellMetadata: true` in authoring): every tracked
1733
+ * column `c` carries typed `c__at` / `c__by` system columns (plus `c__rev`
1734
+ * on a resolved table's internal sources), listed explicitly in `columns`
1735
+ * with a `cell` marker so DDL and migrations treat them as ordinary columns.
1736
+ * Omitted when off so existing definitions hash unchanged.
1737
+ */
1738
+ cellMetadata: z.literal(true).optional(),
1739
+ /**
1740
+ * System-owned backing table of a resolved table (its manual or prefetch
1741
+ * source). Internal tables get DDL and IR metadata but no GraphQL surface;
1742
+ * only the data plane writes them. Omitted when false.
1743
+ */
1744
+ internal: z.literal(true).optional(),
1745
+ /**
1746
+ * The materialized copy of resolved table `materializes` (internal): the
1747
+ * same columns as that view, including its cell parts, maintained by the
1748
+ * data plane. Omitted otherwise.
1749
+ */
1750
+ materializes: identifier2.optional()
1222
1751
  });
1752
+ var MAX_VIEW_QUERY = 1e5;
1753
+ var MAX_RESOLVED_VIEW_QUERY = 5e6;
1223
1754
  var canonicalViewSchema = z.object({
1224
- view: identifier,
1755
+ view: identifier2,
1225
1756
  description: z.string().max(2e3).optional(),
1226
1757
  columns: z.array(canonicalColumnSchema).min(1),
1227
1758
  /** Logical identity for GraphQL only; PostgreSQL views have no PK constraint. */
1228
1759
  primaryKey: z.array(columnIdentifier).min(1).max(8).optional(),
1229
1760
  links: z.array(canonicalLinkSchema).default([]),
1230
1761
  policies: z.object({ read: policyExpressionSchema }).strict(),
1231
- query: z.string().min(1).max(1e5).refine(isSingleViewQuerySql, {
1762
+ query: z.string().min(1).max(MAX_RESOLVED_VIEW_QUERY).refine(isSingleViewQuerySql, {
1232
1763
  message: "view query must be one SELECT/WITH statement without comments or dollar-quoted bodies"
1233
1764
  }),
1234
1765
  /** Namespace-local logical relation names, sorted for deterministic hashing. */
1235
- dependencies: z.array(identifier).max(500)
1766
+ dependencies: z.array(identifier2).max(500),
1767
+ /**
1768
+ * Present when this view is a resolved table (defineResolvedTable): the
1769
+ * multi-source metadata behind its generated query. Omitted otherwise so
1770
+ * existing views hash unchanged.
1771
+ */
1772
+ resolved: canonicalResolvedSchema.optional(),
1773
+ /** System-owned view (a materialized resolved table's compute view): no GraphQL surface. */
1774
+ internal: z.literal(true).optional()
1236
1775
  }).superRefine((view, ctx) => {
1776
+ if (view.resolved === void 0 && view.query.length > MAX_VIEW_QUERY) {
1777
+ ctx.addIssue({ code: "custom", path: ["query"], message: `view query must be at most ${MAX_VIEW_QUERY} characters` });
1778
+ }
1237
1779
  for (const [index3, column] of view.columns.entries()) {
1238
1780
  if (column.default !== void 0 || column.defaultFn !== void 0) {
1239
1781
  ctx.addIssue({ code: "custom", path: ["columns", index3], message: `view column '${column.name}' cannot declare a default` });
@@ -1248,15 +1790,82 @@ var canonicalViewSchema = z.object({
1248
1790
  ctx.addIssue({ code: "custom", path: ["columns", index3], message: `view column '${column.name}' cannot declare migration hints` });
1249
1791
  }
1250
1792
  }
1793
+ if (view.resolved === void 0 && view.internal !== true) {
1794
+ for (const [index3, column] of view.columns.entries()) {
1795
+ if (column.cell) ctx.addIssue({ code: "custom", path: ["columns", index3], message: `only resolved tables carry cell columns ('${column.name}')` });
1796
+ }
1797
+ }
1251
1798
  for (const [index3, link] of view.links.entries()) {
1252
1799
  if (link.enforce) {
1253
1800
  ctx.addIssue({ code: "custom", path: ["links", index3, "enforce"], message: `view link '${link.name}' cannot enforce a foreign key` });
1254
1801
  }
1802
+ if (link.onDelete !== void 0 || link.onUpdate !== void 0) {
1803
+ ctx.addIssue({ code: "custom", path: ["links", index3], message: `view link '${link.name}' cannot declare referential actions` });
1804
+ }
1255
1805
  }
1256
1806
  });
1807
+ var CELL_PART_TYPES = {
1808
+ at: "timestamptz",
1809
+ by: "text",
1810
+ rev: "uuid",
1811
+ source: "text",
1812
+ conflicts: "jsonb"
1813
+ };
1814
+ function trackedCellColumns(table) {
1815
+ const pk = new Set(table.primaryKey ?? []);
1816
+ return table.columns.filter((column) => column.cell === void 0 && !pk.has(column.columnName) && column.onUpdateFn === void 0);
1817
+ }
1818
+ var SUFFIXES = Object.values(CELL_PART_SUFFIX);
1819
+ function validateCellMetadata(table, index3, ctx) {
1820
+ const fail = (message, path = ["tables", index3]) => ctx.addIssue({ code: "custom", path, message });
1821
+ const cells = table.columns.flatMap((column, position) => column.cell ? [{ column, position, cell: column.cell }] : []);
1822
+ if (table.materializes !== void 0) return;
1823
+ if (table.cellMetadata !== true) {
1824
+ for (const { position } of cells) fail(`cell-metadata columns require cellMetadata on table '${table.table}'`, ["tables", index3, "columns", position]);
1825
+ return;
1826
+ }
1827
+ if (table.source !== void 0) {
1828
+ fail(`synced table '${table.table}' cannot track cell metadata`);
1829
+ return;
1830
+ }
1831
+ const tracked = trackedCellColumns(table);
1832
+ const byName = new Map(tracked.map((column) => [column.name, column]));
1833
+ const withRev = cells.some(({ cell }) => cell.part === "rev");
1834
+ if (withRev && table.internal !== true) fail(`only a resolved table's internal sources carry '__rev' cell columns ('${table.table}')`);
1835
+ const seen = /* @__PURE__ */ new Set();
1836
+ for (const { column, position, cell } of cells) {
1837
+ const path = ["tables", index3, "columns", position];
1838
+ const owner = byName.get(cell.of);
1839
+ if (!owner) {
1840
+ fail(`cell column '${column.name}' belongs to '${cell.of}', which is not a tracked column`, path);
1841
+ continue;
1842
+ }
1843
+ 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);
1844
+ if (column.name !== cellColumnName(owner.name, cell.part) || column.columnName !== cellColumnName(owner.columnName, cell.part)) {
1845
+ fail(`cell column for '${owner.name}' ${cell.part} must be named '${cellColumnName(owner.name, cell.part)}' / '${cellColumnName(owner.columnName, cell.part)}'`, path);
1846
+ }
1847
+ if (column.type !== CELL_PART_TYPES[cell.part] || !column.nullable || column.unique || column.default !== void 0 || column.defaultFn !== void 0) {
1848
+ fail(`cell column '${column.name}' must be a nullable, default-less ${CELL_PART_TYPES[cell.part]}`, path);
1849
+ }
1850
+ const key = `${cell.of}:${cell.part}`;
1851
+ if (seen.has(key)) fail(`duplicate cell column '${column.name}'`, path);
1852
+ seen.add(key);
1853
+ }
1854
+ for (const column of tracked) {
1855
+ if (column.columnName.length > MAX_TRACKED_COLUMN_NAME) {
1856
+ fail(`column '${column.columnName}' is too long to track cell metadata (max ${MAX_TRACKED_COLUMN_NAME} characters)`);
1857
+ }
1858
+ if (SUFFIXES.some((suffix) => column.columnName.endsWith(suffix))) {
1859
+ fail(`column '${column.columnName}' ends in a reserved cell-metadata suffix (${SUFFIXES.join(", ")})`);
1860
+ }
1861
+ for (const part of withRev ? ["at", "by", "rev"] : ["at", "by"]) {
1862
+ if (!seen.has(`${column.name}:${part}`)) fail(`tracked column '${column.name}' is missing its '${cellColumnName(column.columnName, part)}' cell column`);
1863
+ }
1864
+ }
1865
+ }
1257
1866
  var canonicalDefinitionSchema = z.object({
1258
1867
  definition_format: z.literal(CANONICAL_FORMAT),
1259
- namespace: identifier.max(41),
1868
+ namespace: identifier2.max(41),
1260
1869
  description: z.string().max(2e3).optional(),
1261
1870
  enums: z.array(canonicalEnumSchema).default([]),
1262
1871
  tables: z.array(canonicalTableSchema).min(0).max(500),
@@ -1274,6 +1883,7 @@ var canonicalDefinitionSchema = z.object({
1274
1883
  ctx.addIssue({ code: "custom", path: ["tables", index3, "table"], message: `duplicate relation '${table.table}'` });
1275
1884
  }
1276
1885
  seen.add(table.table);
1886
+ validateCellMetadata(table, index3, ctx);
1277
1887
  if (table.source !== void 0) {
1278
1888
  for (const [columnIndex, column] of table.columns.entries()) {
1279
1889
  if (column.onUpdateFn !== void 0) {
@@ -1293,6 +1903,26 @@ var canonicalDefinitionSchema = z.object({
1293
1903
  ...table.columns.filter((column) => column.unique && !(table.primaryKey?.length === 1 && table.primaryKey[0] === column.columnName)).map((column) => `${table.table}_${column.columnName}_unique`),
1294
1904
  ...table.links.filter((link) => link.enforce).map((link) => `fk_${table.table}_${link.name}`)
1295
1905
  ]);
1906
+ for (const [index3, link] of table.links.entries()) {
1907
+ if ((link.onDelete !== void 0 || link.onUpdate !== void 0) && (!link.enforce || link.kind !== "toOne")) {
1908
+ ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "links", index3], message: "referential actions require an enforced toOne link" });
1909
+ }
1910
+ if (link.onDelete === "set null" || link.onUpdate === "set null") {
1911
+ const source = table.columns.find((column) => column.columnName === link.on);
1912
+ 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" });
1913
+ }
1914
+ }
1915
+ for (const [index3, check] of (table.checks ?? []).entries()) {
1916
+ const fail = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "checks", index3], message });
1917
+ if (table.source) fail("synced tables cannot declare checks");
1918
+ if (names.has(check.name)) fail(`duplicate constraint '${check.name}'`);
1919
+ names.add(check.name);
1920
+ try {
1921
+ compileCheckExpression(check.expression, table.columns);
1922
+ } catch (error) {
1923
+ fail(error instanceof Error ? error.message : String(error));
1924
+ }
1925
+ }
1296
1926
  for (const [index3, fk] of (table.foreignKeys ?? []).entries()) {
1297
1927
  const fail = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "foreignKeys", index3], message });
1298
1928
  if (names.has(fk.name)) fail(`duplicate foreign key '${fk.name}'`);
@@ -1310,10 +1940,18 @@ var canonicalDefinitionSchema = z.object({
1310
1940
  if (!sourceColumn || !targetColumn) fail("foreign key references an unknown column");
1311
1941
  else if (JSON.stringify(sourceColumn.type) !== JSON.stringify(targetColumn.type)) fail("foreign key column types must match");
1312
1942
  }
1943
+ if (fk.onDelete === "set null" || fk.onUpdate === "set null") {
1944
+ 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");
1945
+ }
1313
1946
  const uniqueKeys = [target.primaryKey ?? [], ...target.indexes.filter((index4) => index4.unique).map((index4) => index4.columns), ...target.columns.filter((column) => column.unique).map((column) => [column.columnName])];
1314
1947
  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");
1315
1948
  }
1316
1949
  }
1950
+ for (const [viewIndex, view] of (definition.views ?? []).entries()) {
1951
+ for (const message of validateResolvedView({ view, tables: definition.tables, views: definition.views ?? [] })) {
1952
+ ctx.addIssue({ code: "custom", path: ["views", viewIndex, "resolved"], message });
1953
+ }
1954
+ }
1317
1955
  const tableByName = new Map(definition.tables.map((table) => [table.table, table]));
1318
1956
  for (const [tableIndex, table] of definition.tables.entries()) {
1319
1957
  const cowMutations = table.cowMutations ?? [];
@@ -1437,8 +2075,12 @@ function canonicalRelations(definition) {
1437
2075
  ...view.primaryKey !== void 0 ? { primaryKey: view.primaryKey } : {},
1438
2076
  links: view.links,
1439
2077
  indexes: [],
1440
- policies: { read: view.policies.read },
1441
- actions: []
2078
+ // A resolved table's write policies (evaluated against the resolved row)
2079
+ // ride alongside the view's read policy.
2080
+ policies: { read: view.policies.read, ...view.resolved?.policies ?? {} },
2081
+ actions: [],
2082
+ ...view.resolved !== void 0 ? { resolved: view.resolved } : {},
2083
+ ...view.internal ? { internal: true } : {}
1442
2084
  }));
1443
2085
  return [...definition.tables, ...views].sort((a, b) => a.table.localeCompare(b.table));
1444
2086
  }
@@ -1448,13 +2090,14 @@ function normalizeCanonical(definition) {
1448
2090
  ...definition,
1449
2091
  enums: sortByName(definition.enums),
1450
2092
  tables: [...definition.tables].sort((a, b) => a.table.localeCompare(b.table)).map((t2) => {
1451
- const { cowMutations, foreignKeys, ...rest } = t2;
2093
+ const { cowMutations, foreignKeys, checks, ...rest } = t2;
1452
2094
  return {
1453
2095
  ...rest,
1454
2096
  links: sortByName(t2.links),
1455
2097
  indexes: [...t2.indexes].sort((a, b) => (a.name ?? a.columns.join("_")).localeCompare(b.name ?? b.columns.join("_"))),
1456
2098
  actions: sortByName(t2.actions),
1457
2099
  ...foreignKeys?.length ? { foreignKeys: sortByName(foreignKeys) } : {},
2100
+ ...checks?.length ? { checks: sortByName(checks) } : {},
1458
2101
  // Omitted entirely when absent/empty so pre-CoW documents hash unchanged.
1459
2102
  ...cowMutations !== void 0 && cowMutations.length > 0 ? { cowMutations: sortByName(cowMutations) } : {}
1460
2103
  };
@@ -1469,6 +2112,454 @@ function normalizeCanonical(definition) {
1469
2112
  }
1470
2113
  return normalized;
1471
2114
  }
2115
+
2116
+ // src/authoring/resolved-sources.ts
2117
+ function cloneColumn({ builder, property, notNull, primaryKey: primaryKey2 }) {
2118
+ const canonical = builder.toCanonicalColumn(property);
2119
+ let clone = new ColumnBuilder(canonical.type, canonical.columnName);
2120
+ if (primaryKey2) clone = clone.primaryKey();
2121
+ if (notNull) clone = clone.notNull();
2122
+ if (canonical.description !== void 0) clone = clone.description(canonical.description);
2123
+ return clone;
2124
+ }
2125
+ function planSource(input) {
2126
+ const { sourceName, input: source, fail } = input;
2127
+ if (!IDENTIFIER_PATTERN.test(sourceName)) fail(`source name "${sourceName}" must match ${IDENTIFIER_PATTERN}`);
2128
+ switch (source.kind) {
2129
+ case "manual":
2130
+ return {
2131
+ name: sourceName,
2132
+ kind: "manual",
2133
+ columns: input.dataColumns,
2134
+ table: internalTable(input, input.dataColumns, {
2135
+ [RESOLVED_SOURCE_SYSTEM_COLUMNS.tombstoned.name]: tombstoned(),
2136
+ [RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackStatus.name]: t.text(RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackStatus.columnName),
2137
+ [RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackError.name]: t.text(RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackError.columnName),
2138
+ [RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackAt.name]: t.timestamptz(RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackAt.columnName)
2139
+ }, { revisions: true })
2140
+ };
2141
+ case "etl":
2142
+ return planEtl(input, source);
2143
+ case "prefetch":
2144
+ return planPrefetch(input, source);
2145
+ }
2146
+ }
2147
+ var tombstoned = () => t.boolean(RESOLVED_SOURCE_SYSTEM_COLUMNS.tombstoned.columnName).notNull().default(false);
2148
+ function internalTable(input, supplied, system, options = {}) {
2149
+ const { viewName, sourceName, columns, pk, pkBuilder } = input;
2150
+ const tableColumns = { [pk]: cloneColumn({ builder: pkBuilder, property: pk, notNull: true, primaryKey: true }) };
2151
+ for (const property of supplied) {
2152
+ tableColumns[property] = cloneColumn({ builder: columns[property], property, notNull: false, primaryKey: false });
2153
+ }
2154
+ return defineTable(`${viewName}__${sourceName}`, { ...tableColumns, ...system }, {
2155
+ description: `Internal ${input.input.kind} source "${sourceName}" of resolved table "${viewName}". System-maintained.`,
2156
+ cellMetadata: true,
2157
+ // The manual source identifies each edit (`c__rev`) so an async
2158
+ // confirmation clears only the exact edit it delivered.
2159
+ ...options.revisions ? { cellRevisions: true } : {},
2160
+ internal: true
2161
+ });
2162
+ }
2163
+ function assertColumns(input, list, what) {
2164
+ for (const column of list) if (!input.dataColumns.includes(column)) input.fail(`${what} names unknown column "${column}"`);
2165
+ }
2166
+ function planEtl(input, source) {
2167
+ const { sourceName, pk, dataColumns, fail } = input;
2168
+ const table = typeof source.table === "function" ? source.table() : source.table;
2169
+ if (!isSeqTable(table) || table.source === void 0) fail(`etl source "${sourceName}" must reference a defineSyncedTable`);
2170
+ const synced = table;
2171
+ if (!(pk in synced.columns)) fail(`etl source "${sourceName}" table "${synced.tableName}" has no "${pk}" column`);
2172
+ const supplied = source.columns ? [...source.columns] : dataColumns.filter((column) => column in synced.columns);
2173
+ assertColumns(input, supplied, `etl source "${sourceName}"`);
2174
+ if (source.updatedAt !== void 0 && !(source.updatedAt in synced.columns)) {
2175
+ fail(`etl source "${sourceName}" updatedAt "${source.updatedAt}" is not a column of "${synced.tableName}"`);
2176
+ }
2177
+ return { name: sourceName, kind: "etl", table: synced, columns: supplied, ...source.updatedAt ? { updatedAt: source.updatedAt } : {} };
2178
+ }
2179
+ function planPrefetch(input, source) {
2180
+ const { sourceName, pk, fail } = input;
2181
+ assertColumns(input, source.columns, `prefetch source "${sourceName}"`);
2182
+ if (source.key !== pk && !source.columns.includes(source.key)) {
2183
+ fail(`prefetch source "${sourceName}" key "${source.key}" must be the primary key or one of its columns`);
2184
+ }
2185
+ for (const column of source.writeback?.columns ?? []) {
2186
+ if (!source.columns.includes(column)) fail(`prefetch source "${sourceName}" writes back "${column}", which it does not supply`);
2187
+ }
2188
+ const writeback = source.writeback;
2189
+ return {
2190
+ name: sourceName,
2191
+ kind: "prefetch",
2192
+ columns: [...source.columns],
2193
+ key: source.key,
2194
+ function: source.function,
2195
+ fetch: source.fetch === "single" ? { shape: "single" } : { shape: "batch", maxKeys: source.fetch.batch.maxKeys },
2196
+ prefetchDefaults: {
2197
+ maxStalenessMs: parseDuration(source.prefetchDefaults?.maxStaleness ?? "30s"),
2198
+ timeoutMs: parseDuration(source.prefetchDefaults?.timeout ?? "1500ms"),
2199
+ onError: source.prefetchDefaults?.onError ?? "fallback"
2200
+ },
2201
+ ...writeback ? {
2202
+ writeback: {
2203
+ columns: [...writeback.columns],
2204
+ defaultMode: writeback.defaultMode ?? "sync",
2205
+ timeoutMs: parseDuration(writeback.timeout ?? "5s"),
2206
+ clearManualOnConfirm: writeback.clearManualOnConfirm ?? true
2207
+ }
2208
+ } : {},
2209
+ table: internalTable(input, source.columns, {
2210
+ [RESOLVED_SOURCE_SYSTEM_COLUMNS.fetchedAt.name]: t.timestamptz(RESOLVED_SOURCE_SYSTEM_COLUMNS.fetchedAt.columnName).notNull().default("now()"),
2211
+ [RESOLVED_SOURCE_SYSTEM_COLUMNS.tombstoned.name]: tombstoned()
2212
+ })
2213
+ };
2214
+ }
2215
+ function parseDuration(input) {
2216
+ const value = parseDurationMs(input);
2217
+ if (value === void 0) throw new OrmDefinitionError(`invalid duration "${input}" \u2014 use e.g. '1500ms', '30s', '5m'`);
2218
+ return value;
2219
+ }
2220
+ var PG_TYPE = {
2221
+ uuid: "uuid",
2222
+ text: "text",
2223
+ integer: "integer",
2224
+ bigint: "bigint",
2225
+ numeric: "numeric",
2226
+ double: "double precision",
2227
+ boolean: "boolean",
2228
+ timestamptz: "timestamptz",
2229
+ date: "date",
2230
+ jsonb: "jsonb"
2231
+ };
2232
+ var ident = (value) => `"${value.replace(/"/g, '""')}"`;
2233
+ var literal = (value) => `'${value.replace(/'/g, "''")}'`;
2234
+ var physical = ({ source, property }) => source.table.columns[property].columnNameFor(property);
2235
+ function candidateSql({ source, column, pk, pgType }) {
2236
+ const a = ident(`s_${source.name}`);
2237
+ const name = physical({ source, property: column });
2238
+ const value = `CAST(${a}.${ident(name)} AS ${pgType})`;
2239
+ if (source.kind === "etl") {
2240
+ return {
2241
+ source: source.name,
2242
+ value,
2243
+ has: `(${a}.${ident(physical({ source, property: pk }))} IS NOT NULL)`,
2244
+ at: source.updatedAt ? `CAST(${a}.${ident(physical({ source, property: source.updatedAt }))} AS timestamptz)` : "CAST(NULL AS timestamptz)",
2245
+ by: "CAST(NULL AS text)"
2246
+ };
2247
+ }
2248
+ const at = `${a}.${ident(cellColumnName(name, "at"))}`;
2249
+ const tombstoned2 = source.kind === "prefetch" ? ` AND NOT ${a}.${ident(RESOLVED_SOURCE_SYSTEM_COLUMNS.tombstoned.columnName)}` : "";
2250
+ return { source: source.name, value, has: `(${at} IS NOT NULL${tombstoned2})`, at, by: `${a}.${ident(cellColumnName(name, "by"))}` };
2251
+ }
2252
+ function outranks({ i, j, key, direction, inclusive }) {
2253
+ const ki = i[key];
2254
+ const kj = j[key];
2255
+ const op = inclusive ? `${direction}=` : direction;
2256
+ 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}))`;
2257
+ }
2258
+ function rankedWinner({ candidates, key, direction }) {
2259
+ const branches = candidates.map((candidate, index3) => {
2260
+ 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);
2261
+ return `WHEN ${[candidate.has, ...beats].join(" AND ")} THEN ${index3 + 1}`;
2262
+ });
2263
+ return `CASE ${branches.join(" ")} END`;
2264
+ }
2265
+ function winnerIndex({ comparator, candidates }) {
2266
+ const firstWith = (test) => candidates.map((candidate, index3) => `WHEN ${test(candidate)} THEN ${index3 + 1}`).join(" ");
2267
+ if (candidates.length === 1 || comparator === "priority") return `CASE ${firstWith((c) => c.has)} END`;
2268
+ switch (comparator) {
2269
+ case "latest":
2270
+ return rankedWinner({ candidates, key: "at", direction: ">" });
2271
+ case "highest":
2272
+ return rankedWinner({ candidates, key: "value", direction: ">" });
2273
+ case "lowest":
2274
+ return rankedWinner({ candidates, key: "value", direction: "<" });
2275
+ case "nonNullFirst":
2276
+ return `CASE ${firstWith((c) => `${c.has} AND ${c.value} IS NOT NULL`)} ${firstWith((c) => c.has)} END`;
2277
+ }
2278
+ }
2279
+ function pick(winner, candidates, field) {
2280
+ return `CASE ${winner} ${candidates.map((candidate, index3) => `WHEN ${index3 + 1} THEN ${field(candidate)}`).join(" ")} END`;
2281
+ }
2282
+ function loserSql({ loser, winner }) {
2283
+ 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`;
2284
+ }
2285
+ function losersAgainst({ candidates, winner, isWinner }) {
2286
+ const losers = candidates.flatMap((loser, index3) => {
2287
+ const excluded = isWinner(loser, index3);
2288
+ if (excluded === true) return [];
2289
+ const clause = loserSql({ loser, winner });
2290
+ return [excluded === false ? clause : `CASE WHEN ${excluded} THEN '[]'::jsonb ELSE ${clause} END`];
2291
+ });
2292
+ return losers.length === 0 ? "CAST(NULL AS jsonb)" : `NULLIF(${losers.join(" || ")}, '[]'::jsonb)`;
2293
+ }
2294
+ function builtinCell({ comparator, candidates }) {
2295
+ const winner = winnerIndex({ comparator, candidates });
2296
+ 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`;
2297
+ return {
2298
+ value: pick(winner, candidates, (c) => c.value),
2299
+ parts: {
2300
+ source: pick(winner, candidates, (c) => literal(c.source)),
2301
+ at: pick(winner, candidates, (c) => c.at),
2302
+ by: pick(winner, candidates, (c) => c.by),
2303
+ conflicts
2304
+ }
2305
+ };
2306
+ }
2307
+ function customCell({ candidates, orderBy, index: index3 }) {
2308
+ const w = ident(`w_${index3}`);
2309
+ const rows = candidates.map((c, ord) => `(${literal(c.source)}, ${c.has}, ${c.value}, ${c.at}, ${c.by}, ${ord})`);
2310
+ 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`;
2311
+ const winner = { value: `${w}.value`, at: `${w}.updated_at` };
2312
+ return {
2313
+ value: winner.value,
2314
+ lateral,
2315
+ parts: {
2316
+ source: `${w}.source`,
2317
+ at: winner.at,
2318
+ by: `${w}.actor`,
2319
+ conflicts: candidates.length < 2 ? "CAST(NULL AS jsonb)" : losersAgainst({ candidates, winner, isWinner: (c) => `${w}.source = ${literal(c.source)}` })
2320
+ }
2321
+ };
2322
+ }
2323
+ function buildResolvedQuery({ context, plan }) {
2324
+ const { qb, relation } = context;
2325
+ const pkColumn = plan.columns[plan.pk].columnNameFor(plan.pk);
2326
+ const alias = (source) => ident(`s_${source}`);
2327
+ const tombstoned2 = RESOLVED_SOURCE_SYSTEM_COLUMNS.tombstoned.columnName;
2328
+ const sourceByName = new Map(plan.plans.map((source) => [source.name, source]));
2329
+ const schema = getTableConfig(relation(plan.plans[0].table)).schema;
2330
+ const keyBranches = plan.plans.map((source) => {
2331
+ const where = source.kind === "prefetch" ? sql.raw(` WHERE NOT ${ident(tombstoned2)}`) : sql.raw("");
2332
+ const sourcePk = ident(physical({ source, property: plan.pk }));
2333
+ const selectedPk = sourcePk === ident(pkColumn) ? sourcePk : `${sourcePk} AS ${ident(pkColumn)}`;
2334
+ return sql`SELECT ${sql.raw(selectedPk)} FROM ${relation(source.table)}${where}`;
2335
+ });
2336
+ const joins = plan.plans.map(
2337
+ (source) => sql` LEFT JOIN ${relation(source.table)} AS ${sql.raw(alias(source.name))} ON ${sql.raw(`${alias(source.name)}.${ident(physical({ source, property: plan.pk }))} = "k".${ident(pkColumn)}`)}`
2338
+ );
2339
+ const cells = /* @__PURE__ */ new Map();
2340
+ plan.rules.forEach((rule, index3) => {
2341
+ const canonical = plan.columns[rule.column].toCanonicalColumn(rule.column);
2342
+ const pgType = typeof canonical.type === "string" ? PG_TYPE[canonical.type] : `${schema === void 0 ? "" : `${ident(schema)}.`}${ident(canonical.type.enum)}`;
2343
+ const candidates = rule.candidates.map((name) => candidateSql({ source: sourceByName.get(name), column: rule.column, pk: plan.pk, pgType }));
2344
+ cells.set(rule.column, rule.comparator === "custom" ? customCell({ candidates, orderBy: rule.orderBy, index: index3 }) : builtinCell({ comparator: rule.comparator, candidates }));
2345
+ });
2346
+ const laterals = [...cells.values()].flatMap((cell) => cell.lateral ? [sql.raw(cell.lateral)] : []);
2347
+ const from = sql.join([sql`(${sql.join(keyBranches, sql.raw(" UNION "))}) AS "k"`, ...joins, ...laterals], sql.raw(""));
2348
+ const selected = {
2349
+ [plan.pk]: sql.raw(`"k".${ident(pkColumn)}`).as(pkColumn)
2350
+ };
2351
+ for (const [property, builder] of Object.entries(plan.columns)) {
2352
+ if (property === plan.pk) continue;
2353
+ const cell = cells.get(property);
2354
+ const columnName = builder.columnNameFor(property);
2355
+ selected[property] = sql.raw(cell.value).as(columnName);
2356
+ for (const [part, expression] of Object.entries(cell.parts)) {
2357
+ selected[cellColumnName(property, part)] = sql.raw(expression).as(cellColumnName(columnName, part));
2358
+ }
2359
+ }
2360
+ const manual = plan.plans.find((source) => source.kind === "manual");
2361
+ const system = RESOLVED_VIEW_SYSTEM_COLUMNS;
2362
+ selected[system.writebackStatus.name] = sql.raw(`${alias(manual.name)}.${ident(RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackStatus.columnName)}`).as(system.writebackStatus.columnName);
2363
+ selected[system.writebackError.name] = sql.raw(`${alias(manual.name)}.${ident(RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackError.columnName)}`).as(system.writebackError.columnName);
2364
+ const hidden = plan.deletedBy.map((name) => `NOT COALESCE(${alias(name)}.${ident(tombstoned2)}, false)`);
2365
+ const query = qb.select(selected).from(from);
2366
+ return hidden.length > 0 ? query.where(sql.raw(hidden.join(" AND "))) : query;
2367
+ }
2368
+ function managedFn(id) {
2369
+ return { kind: "managed", id };
2370
+ }
2371
+ function manualSource() {
2372
+ return { kind: "manual" };
2373
+ }
2374
+ function etlSource(input) {
2375
+ return { kind: "etl", ...input };
2376
+ }
2377
+ function prefetchSource(input) {
2378
+ return { kind: "prefetch", ...input };
2379
+ }
2380
+ var latest = (candidates) => ({ kind: "latest", candidates });
2381
+ var highest = (candidates) => ({ kind: "highest", candidates });
2382
+ var lowest = (candidates) => ({ kind: "lowest", candidates });
2383
+ var priority = (candidates) => ({ kind: "priority", candidates });
2384
+ var nonNullFirst = (candidates) => ({ kind: "nonNullFirst", candidates });
2385
+ var custom = (candidates, orderBy) => ({ kind: "custom", candidates, orderBy });
2386
+ var SYSTEM_VIEW_COLUMNS = {
2387
+ [RESOLVED_VIEW_SYSTEM_COLUMNS.writebackStatus.name]: () => t.text(RESOLVED_VIEW_SYSTEM_COLUMNS.writebackStatus.columnName),
2388
+ [RESOLVED_VIEW_SYSTEM_COLUMNS.writebackError.name]: () => t.text(RESOLVED_VIEW_SYSTEM_COLUMNS.writebackError.columnName)
2389
+ };
2390
+ function defineResolvedTable(name, columns, config) {
2391
+ const file = activeAuthoringContext()?.currentFile;
2392
+ const fail = (message) => {
2393
+ throw new OrmDefinitionError(`resolved table "${name}": ${message}`, file);
2394
+ };
2395
+ if (!IDENTIFIER_PATTERN.test(name)) fail(`name must match ${IDENTIFIER_PATTERN}`);
2396
+ for (const property of Object.keys(columns)) {
2397
+ if (RESERVED_RESOLVED_COLUMN_NAMES.has(property) || RESERVED_RESOLVED_COLUMN_NAMES.has(columns[property].columnNameFor(property))) {
2398
+ fail(`column "${property}" collides with a reserved system column`);
2399
+ }
2400
+ }
2401
+ for (const property of Object.keys(columns)) {
2402
+ if (columns[property].columnNameFor(property).length > MAX_TRACKED_COLUMN_NAME) {
2403
+ fail(`column "${property}" is too long for a resolved table (max ${MAX_TRACKED_COLUMN_NAME} characters, room for cell parts)`);
2404
+ }
2405
+ }
2406
+ const pkEntries = Object.entries(columns).filter(([, builder]) => builder.isPrimaryKey);
2407
+ if (pkEntries.length !== 1) fail("declare exactly one .primaryKey() column");
2408
+ const [pk, pkBuilder] = pkEntries[0];
2409
+ const dataColumns = Object.keys(columns).filter((property) => property !== pk);
2410
+ if (dataColumns.length === 0) fail("declare at least one non-key column");
2411
+ const sourceEntries = Object.entries(config.sources);
2412
+ if (sourceEntries.filter(([, source]) => source.kind === "manual").length !== 1) fail("declare exactly one manualSource()");
2413
+ const plans = sourceEntries.map(
2414
+ ([sourceName, input]) => planSource({ viewName: name, sourceName, input, columns, pk, pkBuilder, dataColumns, fail })
2415
+ );
2416
+ const rules = planRules({ config, plans, dataColumns, fail });
2417
+ const deletedBy = [...config.rows?.deletedBy ?? []];
2418
+ for (const source of deletedBy) {
2419
+ if (plans.find((plan) => plan.name === source)?.kind !== "manual") fail(`rows.deletedBy "${source}" must be the manual source`);
2420
+ }
2421
+ const partType = { source: t.text, at: t.timestamptz, by: t.text, conflicts: t.jsonb };
2422
+ const viewColumns = {};
2423
+ for (const [property, builder] of Object.entries(columns)) {
2424
+ viewColumns[property] = cloneColumn({ builder, property, notNull: property === pk, primaryKey: property === pk });
2425
+ if (property === pk) continue;
2426
+ const columnName = builder.columnNameFor(property);
2427
+ for (const part of RESOLVED_VIEW_CELL_PARTS) {
2428
+ viewColumns[cellColumnName(property, part)] = partType[part](cellColumnName(columnName, part)).cellPart(property, part);
2429
+ }
2430
+ }
2431
+ for (const [property, make] of Object.entries(SYSTEM_VIEW_COLUMNS)) viewColumns[property] = make();
2432
+ const { read, ...writePolicies } = config.policies;
2433
+ const resolved = {
2434
+ internalTables: plans.filter((plan) => plan.kind !== "etl").map((plan) => plan.table),
2435
+ sources: plans,
2436
+ rules,
2437
+ deletedBy,
2438
+ policies: writePolicies
2439
+ };
2440
+ const queryPlan = { columns, pk, plans, rules, deletedBy };
2441
+ const typedColumns = viewColumns;
2442
+ const surface = {
2443
+ ...config.description !== void 0 ? { description: config.description } : {},
2444
+ ...config.links !== void 0 ? { links: config.links } : {},
2445
+ policies: { read }
2446
+ };
2447
+ if (!config.materialize) {
2448
+ return defineView(name, typedColumns, { ...surface, query: (context) => buildResolvedQuery({ context, plan: queryPlan }), resolved });
2449
+ }
2450
+ const materialize = config.materialize === true ? {} : config.materialize;
2451
+ const computeName = `${name}__compute`;
2452
+ const tableName = `${name}__materialized`;
2453
+ const compute = defineView(computeName, typedColumns, {
2454
+ policies: { read },
2455
+ query: (context) => buildResolvedQuery({ context, plan: queryPlan }),
2456
+ internal: true
2457
+ });
2458
+ const table = defineTable(tableName, viewColumns, {
2459
+ description: `Materialized rows of resolved table "${name}". System-maintained from "${computeName}".`,
2460
+ internal: true,
2461
+ materializes: name,
2462
+ ...materialize.indexes !== void 0 ? { indexes: materialize.indexes } : {}
2463
+ });
2464
+ return defineView(name, typedColumns, {
2465
+ ...surface,
2466
+ query: (context) => {
2467
+ const handle = context.relation(table);
2468
+ const selected = Object.fromEntries(Object.entries(viewColumns).map(([property, builder]) => [
2469
+ property,
2470
+ sql`${handle[property]}`.as(builder.columnNameFor(property))
2471
+ ]));
2472
+ return context.qb.select(selected).from(context.relation(table));
2473
+ },
2474
+ resolved: {
2475
+ ...resolved,
2476
+ internalTables: [...resolved.internalTables, table],
2477
+ internalViews: [compute],
2478
+ materialized: { table: tableName, compute: computeName }
2479
+ }
2480
+ });
2481
+ }
2482
+ function planRules({ config, plans, dataColumns, fail }) {
2483
+ const kindOrder = { manual: 0, prefetch: 1, etl: 2 };
2484
+ const fallback = config.default ?? priority([...plans].sort((a, b) => kindOrder[a.kind] - kindOrder[b.kind]).map((plan) => plan.name));
2485
+ for (const column of Object.keys(config.resolve ?? {})) {
2486
+ if (!dataColumns.includes(column)) fail(`resolve names unknown column "${column}"`);
2487
+ }
2488
+ return dataColumns.map((column) => {
2489
+ const explicit = config.resolve?.[column];
2490
+ const comparator = explicit ?? fallback;
2491
+ const candidates = explicit ? [...explicit.candidates] : comparator.candidates.filter((name) => plans.find((plan) => plan.name === name)?.columns.includes(column));
2492
+ for (const candidate of candidates) {
2493
+ const plan = plans.find((entry) => entry.name === candidate);
2494
+ if (!plan) fail(`column "${column}" names unknown source "${candidate}"`);
2495
+ else if (!plan.columns.includes(column)) fail(`column "${column}" candidate "${candidate}" does not supply it`);
2496
+ }
2497
+ if (candidates.length === 0) fail(`column "${column}" has no candidate source`);
2498
+ if (comparator.kind === "custom") assertSafeOrderBy({ orderBy: comparator.orderBy ?? "", column, fail });
2499
+ return {
2500
+ column,
2501
+ comparator: comparator.kind,
2502
+ candidates,
2503
+ ...comparator.kind === "custom" && comparator.orderBy !== void 0 ? { orderBy: comparator.orderBy } : {}
2504
+ };
2505
+ });
2506
+ }
2507
+ var ORDER_BY_KEYWORDS = /* @__PURE__ */ new Set([
2508
+ "value",
2509
+ "updated_at",
2510
+ "source",
2511
+ "actor",
2512
+ "ord",
2513
+ "asc",
2514
+ "desc",
2515
+ "nulls",
2516
+ "first",
2517
+ "last",
2518
+ "case",
2519
+ "when",
2520
+ "then",
2521
+ "else",
2522
+ "end",
2523
+ "and",
2524
+ "or",
2525
+ "not",
2526
+ "is",
2527
+ "null",
2528
+ "true",
2529
+ "false",
2530
+ "in",
2531
+ "distinct",
2532
+ "from",
2533
+ "coalesce",
2534
+ "nullif",
2535
+ "greatest",
2536
+ "least",
2537
+ "text",
2538
+ "numeric",
2539
+ "integer",
2540
+ "bigint",
2541
+ "boolean",
2542
+ "timestamptz",
2543
+ "date",
2544
+ "jsonb",
2545
+ "interval"
2546
+ ]);
2547
+ var ALLOWED_FUNCTIONS = new Set(VIEW_QUERY_FUNCTION_ALLOWLIST);
2548
+ var ORDER_BY_TOKEN = /\s+|'(?:[^']|'')*'|\d+(?:\.\d+)?|[a-z_][a-z0-9_]*|::|<=|>=|<>|!=|[-+*/%<>=(),|]/giy;
2549
+ function assertSafeOrderBy({ orderBy, column, fail }) {
2550
+ ORDER_BY_TOKEN.lastIndex = 0;
2551
+ let consumed = 0;
2552
+ for (let match = ORDER_BY_TOKEN.exec(orderBy); match; match = ORDER_BY_TOKEN.exec(orderBy)) {
2553
+ consumed = ORDER_BY_TOKEN.lastIndex;
2554
+ const token = match[0].toLowerCase();
2555
+ if (/^[a-z_]/.test(token) && !ORDER_BY_KEYWORDS.has(token) && !ALLOWED_FUNCTIONS.has(token)) {
2556
+ fail(`column "${column}" custom orderBy uses "${match[0]}" \u2014 only value, updated_at, source, actor, ord, SQL keywords and allowlisted functions`);
2557
+ }
2558
+ }
2559
+ if (consumed !== orderBy.length || orderBy.trim() === "") {
2560
+ fail(`column "${column}" custom orderBy contains an unsupported character near "${orderBy.slice(consumed, consumed + 10)}"`);
2561
+ }
2562
+ }
1472
2563
  function stableJson(value) {
1473
2564
  if (value === null || typeof value !== "object") return JSON.stringify(value);
1474
2565
  if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
@@ -1575,8 +2666,19 @@ function defineNamespace(input) {
1575
2666
  return config;
1576
2667
  }
1577
2668
  function serializeNamespace(input) {
1578
- const { config, tables } = input;
1579
- const views = input.views ?? [];
2669
+ const { config } = input;
2670
+ const views = [...input.views ?? []];
2671
+ const tables = [...input.tables];
2672
+ for (const view of views) {
2673
+ for (const internal of view.config.resolved?.internalTables ?? []) {
2674
+ if (!tables.includes(internal)) tables.push(internal);
2675
+ }
2676
+ }
2677
+ for (const view of [...views]) {
2678
+ for (const internal of view.config.resolved?.internalViews ?? []) {
2679
+ if (!views.includes(internal)) views.push(internal);
2680
+ }
2681
+ }
1580
2682
  const enums = input.enums ?? [];
1581
2683
  const tablesByName = /* @__PURE__ */ new Map();
1582
2684
  for (const table of tables) {
@@ -1678,6 +2780,9 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
1678
2780
  if (table.config.renamedFrom !== void 0) candidate.renamedFrom = table.config.renamedFrom;
1679
2781
  if (table.source !== void 0) candidate.source = table.source;
1680
2782
  if (table.source !== void 0) {
2783
+ if (table.config.checks?.length) {
2784
+ throw new OrmDefinitionError(`synced table "${table.tableName}" is system-owned; checks are not allowed`, table.file);
2785
+ }
1681
2786
  if (table.config.indexes !== void 0 && table.config.indexes.length > 0) {
1682
2787
  throw new OrmDefinitionError(
1683
2788
  `synced table "${table.tableName}" is system-owned; indexes are not allowed`,
@@ -1715,6 +2820,11 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
1715
2820
  if (builder.isPrimaryKey) columnLevelPk.push(column.columnName);
1716
2821
  candidate.columns?.push(column);
1717
2822
  }
2823
+ if (table.config.internal === true) candidate.internal = true;
2824
+ if (table.config.materializes !== void 0) candidate.materializes = table.config.materializes;
2825
+ if (table.config.cellMetadata === true && table.source !== void 0) {
2826
+ throw new OrmDefinitionError(`synced table "${table.tableName}" cannot track cell metadata \u2014 its DDL is owned by the sync`, table.file);
2827
+ }
1718
2828
  if (table.config.primaryKey !== void 0) {
1719
2829
  if (columnLevelPk.length > 0) {
1720
2830
  throw new OrmDefinitionError(
@@ -1728,6 +2838,24 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
1728
2838
  } else if (columnLevelPk.length > 0) {
1729
2839
  candidate.primaryKey = columnLevelPk;
1730
2840
  }
2841
+ if (table.config.cellMetadata === true) {
2842
+ candidate.cellMetadata = true;
2843
+ const parts = table.config.cellRevisions === true ? ["at", "by", "rev"] : ["at", "by"];
2844
+ const tracked = trackedCellColumns({ columns: candidate.columns ?? [], primaryKey: candidate.primaryKey });
2845
+ for (const column of tracked) {
2846
+ for (const part of parts) {
2847
+ candidate.columns?.push({
2848
+ name: cellColumnName(column.name, part),
2849
+ columnName: cellColumnName(column.columnName, part),
2850
+ type: CELL_PART_TYPES[part],
2851
+ nullable: true,
2852
+ unique: false,
2853
+ deprecated: false,
2854
+ cell: { of: column.name, part }
2855
+ });
2856
+ }
2857
+ }
2858
+ }
1731
2859
  if (table.config.foreignKeys?.length) {
1732
2860
  if (table.source) throw new OrmDefinitionError(`synced table "${table.tableName}" cannot enforce foreign keys`, table.file);
1733
2861
  candidate.foreignKeys = table.config.foreignKeys.map((fk) => {
@@ -1740,10 +2868,15 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
1740
2868
  columns: fk.columns.map((ref) => resolveColumnRef({ relation: table, ref, role: `foreign key "${fk.name}" column` })),
1741
2869
  target: target.tableName,
1742
2870
  references: fk.references.map((ref) => resolveColumnRef({ relation: target, ref, role: `foreign key "${fk.name}" reference` })),
1743
- ...fk.deferrable ? { deferrable: fk.deferrable } : {}
2871
+ ...fk.deferrable ? { deferrable: fk.deferrable } : {},
2872
+ ...fk.onDelete ? { onDelete: fk.onDelete } : {},
2873
+ ...fk.onUpdate ? { onUpdate: fk.onUpdate } : {}
1744
2874
  };
1745
2875
  });
1746
2876
  }
2877
+ if (table.config.checks?.length) {
2878
+ candidate.checks = table.config.checks.map((check) => ({ name: check.name, expression: check.expression }));
2879
+ }
1747
2880
  for (const index3 of table.config.indexes ?? []) {
1748
2881
  const entry = {
1749
2882
  columns: index3.columns.map((ref) => resolveColumnRef({ relation: table, ref, role: "index" }))
@@ -1839,7 +2972,33 @@ function buildCanonicalView({
1839
2972
  columns,
1840
2973
  ...primaryKey2 !== void 0 ? { primaryKey: primaryKey2 } : {},
1841
2974
  links,
1842
- policies: { read: toCanonicalPolicy(view.config.policies.read) }
2975
+ policies: { read: toCanonicalPolicy(view.config.policies.read) },
2976
+ ...view.config.resolved !== void 0 ? { resolved: buildCanonicalResolved(view.config.resolved) } : {},
2977
+ ...view.config.internal === true ? { internal: true } : {}
2978
+ };
2979
+ }
2980
+ function buildCanonicalResolved(resolved) {
2981
+ const policies = {};
2982
+ if (resolved.policies.write !== void 0) policies.write = toCanonicalPolicy(resolved.policies.write);
2983
+ if (resolved.policies.insert !== void 0) policies.insert = toCanonicalPolicy(resolved.policies.insert);
2984
+ if (resolved.policies.update !== void 0) policies.update = toCanonicalPolicy(resolved.policies.update);
2985
+ return {
2986
+ sources: resolved.sources.map((source) => ({
2987
+ name: source.name,
2988
+ kind: source.kind,
2989
+ table: source.table.tableName,
2990
+ columns: [...source.columns],
2991
+ ...source.updatedAt !== void 0 ? { updatedAt: source.updatedAt } : {},
2992
+ ...source.key !== void 0 ? { key: source.key } : {},
2993
+ ...source.fetch !== void 0 ? { fetch: source.fetch } : {},
2994
+ ...source.function !== void 0 ? { function: { ...source.function } } : {},
2995
+ ...source.prefetchDefaults !== void 0 ? { prefetchDefaults: { ...source.prefetchDefaults } } : {},
2996
+ ...source.writeback !== void 0 ? { writeback: { ...source.writeback, columns: [...source.writeback.columns] } } : {}
2997
+ })),
2998
+ resolve: resolved.rules.map((rule) => ({ ...rule, candidates: [...rule.candidates] })),
2999
+ deletedBy: [...resolved.deletedBy],
3000
+ ...resolved.materialized ? { materialized: { ...resolved.materialized } } : {},
3001
+ policies
1843
3002
  };
1844
3003
  }
1845
3004
  function buildCanonicalLink({
@@ -1856,6 +3015,9 @@ function buildCanonicalLink({
1856
3015
  if (link.enforce && link.linkKind !== "toOne") {
1857
3016
  return fail("sets enforce: true, which is only supported on toOne links");
1858
3017
  }
3018
+ if ((link.onDelete !== void 0 || link.onUpdate !== void 0) && !link.enforce) {
3019
+ return fail("sets a referential action without enforce: true");
3020
+ }
1859
3021
  if (link.linkKind === "toPlatformUser") {
1860
3022
  if (link.target !== void 0) return fail("is toPlatformUser and must not declare a target table");
1861
3023
  return {
@@ -1884,6 +3046,8 @@ function buildCanonicalLink({
1884
3046
  on: resolveColumnRef({ relation: owningSide, ref: link.on, role: `link "${linkName}" on` }),
1885
3047
  ...link.references !== void 0 ? { references: resolveColumnRef({ relation: referencedSide, ref: link.references, role: `link "${linkName}" references` }) } : {},
1886
3048
  enforce: link.enforce,
3049
+ ...link.onDelete !== void 0 ? { onDelete: link.onDelete } : {},
3050
+ ...link.onUpdate !== void 0 ? { onUpdate: link.onUpdate } : {},
1887
3051
  ...link.description !== void 0 ? { description: link.description } : {}
1888
3052
  };
1889
3053
  }
@@ -2337,7 +3501,7 @@ var OPERATOR_PATTERN = /^(!=|<=|>=|<>|=|<|>)/;
2337
3501
  var IDENTIFIER_START = /[a-z_]/;
2338
3502
  var IDENTIFIER_CHAR = /[a-z0-9_.-]/;
2339
3503
  var NUMBER_PATTERN = /^-?\d+(\.\d+)?/;
2340
- function tokenize(source) {
3504
+ function tokenize2(source) {
2341
3505
  const tokens = [];
2342
3506
  let position = 0;
2343
3507
  while (position < source.length) {
@@ -2411,7 +3575,7 @@ function readString(source, start) {
2411
3575
  }
2412
3576
  throw new PredicateParseError("unterminated string literal", start);
2413
3577
  }
2414
- var Parser = class {
3578
+ var Parser2 = class {
2415
3579
  constructor(tokens) {
2416
3580
  this.tokens = tokens;
2417
3581
  }
@@ -2543,7 +3707,7 @@ var Parser = class {
2543
3707
  function parsePredicate(source) {
2544
3708
  const trimmed = source.trim();
2545
3709
  if (trimmed.length === 0) throw new PredicateParseError("empty predicate", 0);
2546
- return { source: trimmed, ast: new Parser(tokenize(trimmed)).parse() };
3710
+ return { source: trimmed, ast: new Parser2(tokenize2(trimmed)).parse() };
2547
3711
  }
2548
3712
 
2549
3713
  // src/schema/predicates.ts
@@ -2628,8 +3792,8 @@ var actionSchema = z.strictObject({
2628
3792
  }
2629
3793
  }
2630
3794
  });
2631
- function extractNamedParams(sql5) {
2632
- const withoutStrings = sql5.replace(QUOTED_STRING_PATTERN, "''");
3795
+ function extractNamedParams(sql7) {
3796
+ const withoutStrings = sql7.replace(QUOTED_STRING_PATTERN, "''");
2633
3797
  const names = [...withoutStrings.matchAll(NAMED_PARAM_PATTERN)].map((match) => match[1]);
2634
3798
  return [...new Set(names)];
2635
3799
  }
@@ -2696,17 +3860,6 @@ var SQL_TYPE_NAMES = {
2696
3860
  };
2697
3861
  var sqlTypeName = (type) => SQL_TYPE_NAMES[type] ?? type;
2698
3862
 
2699
- // src/compiler/sql.ts
2700
- var CLAIMS_SETTING = "request.jwt.claims";
2701
- var CAPABILITIES_CLAIM_KEY = "capabilities";
2702
- var ROLES_CLAIM_KEY = "roles";
2703
- var quoteIdentifier = (name) => `"${name.replaceAll('"', '""')}"`;
2704
- var quoteLiteral = (value) => `'${value.replaceAll("'", "''")}'`;
2705
- var CLAIMS_SOURCE = `nullif(current_setting('${CLAIMS_SETTING}', true), '')::jsonb`;
2706
- var claimExpression = (key) => `(${CLAIMS_SOURCE} ->> ${quoteLiteral(key)})`;
2707
- var capabilityExpression = (key) => `coalesce(${CLAIMS_SOURCE} -> '${CAPABILITIES_CLAIM_KEY}' ? ${quoteLiteral(key)}, false)`;
2708
- var roleExpression = (key) => `coalesce(${CLAIMS_SOURCE} -> '${ROLES_CLAIM_KEY}' ? ${quoteLiteral(key)}, false)`;
2709
-
2710
3863
  // src/compiler/predicates.ts
2711
3864
  var SQL_COMPARE_OPS = {
2712
3865
  "=": "=",
@@ -2786,8 +3939,8 @@ var actionParamArg = (name) => `p_${name}`;
2786
3939
  function compileActionStatements(action, renderParam = actionParamArg) {
2787
3940
  return actionStatementSources(action.executes).map((statement) => compileStatement(statement, renderParam));
2788
3941
  }
2789
- function compileStatement(sql5, renderParam) {
2790
- const compiled = sql5.replace(TOKEN_PATTERN, (token, claimKey, capabilityKey, paramName) => {
3942
+ function compileStatement(sql7, renderParam) {
3943
+ const compiled = sql7.replace(TOKEN_PATTERN, (token, claimKey, capabilityKey, paramName) => {
2791
3944
  if (claimKey !== void 0) {
2792
3945
  if (!CLAIM_KEY_PATTERN.test(claimKey)) throw new OrmDefinitionError(`invalid claim key in executes: ${token}`);
2793
3946
  return claimExpression(claimKey);
@@ -2852,10 +4005,10 @@ function physicalColumn(logical, options) {
2852
4005
  return options.columnNames?.[logical] ?? logical;
2853
4006
  }
2854
4007
  function compileCmp(op, column, value, options) {
2855
- const physical = physicalColumn(column, options);
2856
- const declared = options.columnTypes?.[physical] ?? options.columnTypes?.[column];
4008
+ const physical2 = physicalColumn(column, options);
4009
+ const declared = options.columnTypes?.[physical2] ?? options.columnTypes?.[column];
2857
4010
  const isEnum = typeof declared === "object" && declared !== null;
2858
- const col = isEnum ? `${quoteIdentifier(physical)}::text` : quoteIdentifier(physical);
4011
+ const col = isEnum ? `${quoteIdentifier(physical2)}::text` : quoteIdentifier(physical2);
2859
4012
  const columnType = isEnum ? "text" : declared;
2860
4013
  switch (op) {
2861
4014
  case "eq":
@@ -2899,11 +4052,11 @@ function compileEquality(col, value, columnType, negated, options) {
2899
4052
  function compileValue(value, columnType, _options) {
2900
4053
  switch (value.kind) {
2901
4054
  case "literal": {
2902
- const literal = value.value;
2903
- if (literal === null) return "NULL";
2904
- if (typeof literal === "string") return quoteLiteral(literal);
2905
- if (typeof literal === "boolean") return literal ? "TRUE" : "FALSE";
2906
- return String(literal);
4055
+ const literal2 = value.value;
4056
+ if (literal2 === null) return "NULL";
4057
+ if (typeof literal2 === "string") return quoteLiteral(literal2);
4058
+ if (typeof literal2 === "boolean") return literal2 ? "TRUE" : "FALSE";
4059
+ return String(literal2);
2907
4060
  }
2908
4061
  case "claim": {
2909
4062
  const expression = claimExpression(value.key);
@@ -2952,8 +4105,8 @@ function actionPrepareChecks(definition) {
2952
4105
  const order = actionParamOrder(action);
2953
4106
  const signature = order.length > 0 ? ` (${order.map((param) => sqlTypeName(action.params[param].type)).join(", ")})` : "";
2954
4107
  const compiled = compileActionStatements(action, (param) => `$${order.indexOf(param) + 1}`);
2955
- compiled.forEach((sql5, index3) => {
2956
- const checked = action.returns && index3 === compiled.length - 1 ? returnProjection(action.returns, sql5) : sql5;
4108
+ compiled.forEach((sql7, index3) => {
4109
+ const checked = action.returns && index3 === compiled.length - 1 ? returnProjection(action.returns, sql7) : sql7;
2957
4110
  statements.push(`PREPARE ${quoteIdentifier(`orm_check_${name}_${index3}`)}${signature} AS ${checked}`);
2958
4111
  });
2959
4112
  }
@@ -3335,14 +4488,14 @@ function comparisonTypeIssue(left, right, columns) {
3335
4488
  return null;
3336
4489
  }
3337
4490
  const column = left.kind === "column" ? left : right.kind === "column" ? right : null;
3338
- const literal = left.kind === "literal" ? left : right.kind === "literal" ? right : null;
3339
- if (!column || !literal || literal.value === null) return null;
4491
+ const literal2 = left.kind === "literal" ? left : right.kind === "literal" ? right : null;
4492
+ if (!column || !literal2 || literal2.value === null) return null;
3340
4493
  const columnGroup = groupOf(column);
3341
4494
  if (!columnGroup) return null;
3342
- if (typeof literal.value === "string") return null;
3343
- const literalGroup = typeof literal.value === "number" ? "numeric" : "boolean";
4495
+ if (typeof literal2.value === "string") return null;
4496
+ const literalGroup = typeof literal2.value === "number" ? "numeric" : "boolean";
3344
4497
  if (literalGroup !== columnGroup) {
3345
- return `policy compares '${column.name}' (${columns[column.name].type}) with a ${typeof literal.value} literal \u2014 incomparable types`;
4498
+ return `policy compares '${column.name}' (${columns[column.name].type}) with a ${typeof literal2.value} literal \u2014 incomparable types`;
3346
4499
  }
3347
4500
  return null;
3348
4501
  }
@@ -3428,8 +4581,8 @@ function syncedViewStatements({
3428
4581
  const backing = backingMap.get(table.table);
3429
4582
  if (!backing) continue;
3430
4583
  const viewName = `${schema}.${quoteIdentifier(table.table)}`;
3431
- const physical = splitPhysicalRelation(backing.physicalRelation);
3432
- const physicalTarget = `${quoteIdentifier(physical.schema)}.${quoteIdentifier(physical.table)}`;
4584
+ const physical2 = splitPhysicalRelation(backing.physicalRelation);
4585
+ const physicalTarget = `${quoteIdentifier(physical2.schema)}.${quoteIdentifier(physical2.table)}`;
3433
4586
  const columnTypes = mergeSyncedViewColumnTypes({
3434
4587
  table,
3435
4588
  canonicalColumnTypes: columnTypesByTable?.get(table.table),
@@ -3443,7 +4596,7 @@ function syncedViewStatements({
3443
4596
  columnNames: columnNamesByTable?.get(table.table)
3444
4597
  })}` : readPolicy ? ` WHERE ${compilePredicate(readPolicy.ast, narrowToV1ColumnTypes(columnTypes))}` : " WHERE false";
3445
4598
  if (backingPrivileges === "grant") {
3446
- statements.push(`GRANT USAGE ON SCHEMA ${quoteIdentifier(physical.schema)} TO ${owner}`);
4599
+ statements.push(`GRANT USAGE ON SCHEMA ${quoteIdentifier(physical2.schema)} TO ${owner}`);
3447
4600
  statements.push(`GRANT SELECT ON ${physicalTarget} TO ${owner}`);
3448
4601
  }
3449
4602
  statements.push(
@@ -3464,10 +4617,10 @@ function mergeSyncedViewColumnTypes({
3464
4617
  if (def?.type) fromTable[name] = def.type;
3465
4618
  }
3466
4619
  if (columnNames) {
3467
- for (const [logical, physical] of Object.entries(columnNames)) {
3468
- const declared = fromTable[physical] ?? fromTable[logical];
4620
+ for (const [logical, physical2] of Object.entries(columnNames)) {
4621
+ const declared = fromTable[physical2] ?? fromTable[logical];
3469
4622
  if (!declared) continue;
3470
- fromTable[physical] = declared;
4623
+ fromTable[physical2] = declared;
3471
4624
  fromTable[logical] = declared;
3472
4625
  }
3473
4626
  }
@@ -3506,8 +4659,8 @@ function syncedBackingRevokeStatements({
3506
4659
  const seen = /* @__PURE__ */ new Set();
3507
4660
  const statements = [];
3508
4661
  for (const backing of backings) {
3509
- const physical = splitPhysicalRelation(backing.physicalRelation);
3510
- const physicalTarget = `${quoteIdentifier(physical.schema)}.${quoteIdentifier(physical.table)}`;
4662
+ const physical2 = splitPhysicalRelation(backing.physicalRelation);
4663
+ const physicalTarget = `${quoteIdentifier(physical2.schema)}.${quoteIdentifier(physical2.table)}`;
3511
4664
  if (seen.has(physicalTarget)) continue;
3512
4665
  seen.add(physicalTarget);
3513
4666
  statements.push(`REVOKE SELECT ON ${physicalTarget} FROM ${owner}`);
@@ -3717,6 +4870,90 @@ function snapshotTableNames(snapshot, schema) {
3717
4870
  const tables = snapshot.tables ?? {};
3718
4871
  return Object.values(tables).filter((entry) => entry.schema === schema).map((entry) => entry.name);
3719
4872
  }
4873
+
4874
+ // src/engine/materialized-refresh.ts
4875
+ function materializedRefreshFunctions(canonical) {
4876
+ const schema = ormSchemaName(canonical.namespace);
4877
+ const functions = /* @__PURE__ */ new Map();
4878
+ for (const view of canonical.views ?? []) {
4879
+ const materialized = view.resolved?.materialized;
4880
+ if (!materialized) continue;
4881
+ const table = canonical.tables.find((candidate) => candidate.table === materialized.table);
4882
+ const pk = view.primaryKey ?? [];
4883
+ if (!table || pk.length === 0) {
4884
+ throw new OrmDefinitionError(`materialized resolved table '${view.view}' needs its table and primary key`);
4885
+ }
4886
+ functions.set(view.view, refreshFunctionSql({
4887
+ schema,
4888
+ name: view.view,
4889
+ table: materialized.table,
4890
+ compute: materialized.compute,
4891
+ columns: table.columns.map((column) => column.columnName),
4892
+ pk
4893
+ }));
4894
+ }
4895
+ return functions;
4896
+ }
4897
+ function refreshFunctionSql({ schema, name, table, compute, columns, pk }) {
4898
+ const fn = `${quoteIdentifier(schema)}.${quoteIdentifier(materializedRefreshFunctionName(name))}`;
4899
+ const target = `${quoteIdentifier(schema)}.${quoteIdentifier(table)}`;
4900
+ const source = `${quoteIdentifier(schema)}.${quoteIdentifier(compute)}`;
4901
+ const list = columns.map(quoteIdentifier).join(", ");
4902
+ const others = columns.filter((column) => !pk.includes(column));
4903
+ const keys = pk.map(quoteIdentifier).join(", ");
4904
+ const matches = pk.map((column) => `c.${quoteIdentifier(column)} = m.${quoteIdentifier(column)}`).join(" AND ");
4905
+ const onConflict = others.length === 0 ? "DO NOTHING" : `DO UPDATE SET ${others.map((column) => `${quoteIdentifier(column)} = EXCLUDED.${quoteIdentifier(column)}`).join(", ")}
4906
+ WHERE (${others.map((column) => `m.${quoteIdentifier(column)}`).join(", ")}) IS DISTINCT FROM (${others.map((column) => `EXCLUDED.${quoteIdentifier(column)}`).join(", ")})`;
4907
+ const lockKey = quoteLiteral(materializeLockKey(schema, name));
4908
+ return `CREATE OR REPLACE FUNCTION ${fn}(wait boolean DEFAULT false)
4909
+ RETURNS TABLE(upserted bigint, deleted bigint, locked boolean)
4910
+ LANGUAGE plpgsql
4911
+ SECURITY INVOKER
4912
+ SET search_path = pg_catalog, pg_temp
4913
+ AS $orm_refresh$
4914
+ #variable_conflict use_column
4915
+ DECLARE
4916
+ caller_claims text := current_setting('request.jwt.claims', true);
4917
+ BEGIN
4918
+ IF wait THEN
4919
+ PERFORM pg_advisory_xact_lock(hashtext(${lockKey}));
4920
+ ELSIF NOT pg_try_advisory_xact_lock(hashtext(${lockKey})) THEN
4921
+ RETURN QUERY SELECT 0::bigint, 0::bigint, false;
4922
+ RETURN;
4923
+ END IF;
4924
+ PERFORM set_config('request.jwt.claims', ${quoteLiteral(MATERIALIZE_CLAIMS_JSON)}, true);
4925
+ RETURN QUERY
4926
+ WITH up AS (
4927
+ INSERT INTO ${target} AS m (${list})
4928
+ SELECT ${list} FROM ${source}
4929
+ ON CONFLICT (${keys}) ${onConflict}
4930
+ RETURNING 1
4931
+ ), gone AS (
4932
+ DELETE FROM ${target} AS m
4933
+ WHERE NOT EXISTS (SELECT 1 FROM ${source} AS c WHERE ${matches})
4934
+ RETURNING 1
4935
+ )
4936
+ SELECT (SELECT count(*) FROM up), (SELECT count(*) FROM gone), true;
4937
+ -- NULL resets to the session value when the caller had none.
4938
+ PERFORM set_config('request.jwt.claims', caller_claims, true);
4939
+ END
4940
+ $orm_refresh$`;
4941
+ }
4942
+ function refreshFunctionStatements({ namespace, previous, current }) {
4943
+ const schema = quoteIdentifier(ormSchemaName(namespace));
4944
+ const statements = [];
4945
+ for (const name of Object.keys(previous).sort()) {
4946
+ if (!current.has(name)) statements.push(`DROP FUNCTION IF EXISTS ${schema}.${quoteIdentifier(materializedRefreshFunctionName(name))}(boolean)`);
4947
+ }
4948
+ for (const [name, create] of [...current].sort(([a], [b]) => a.localeCompare(b))) {
4949
+ if (previous[name] === create) continue;
4950
+ const fn = `${schema}.${quoteIdentifier(materializedRefreshFunctionName(name))}(boolean)`;
4951
+ statements.push(create, `REVOKE ALL ON FUNCTION ${fn} FROM PUBLIC`);
4952
+ }
4953
+ return statements;
4954
+ }
4955
+
4956
+ // src/engine/canonical2.ts
3720
4957
  var FACTORIES2 = {
3721
4958
  uuid: (name) => uuid(name),
3722
4959
  text: (name) => text(name),
@@ -3829,18 +5066,18 @@ function rewriteSyncedSourceRelationsInViewQuery({
3829
5066
  return rewritten;
3830
5067
  });
3831
5068
  }
3832
- function transformSqlOutsideLiterals(sql5, transform) {
5069
+ function transformSqlOutsideLiterals(sql7, transform) {
3833
5070
  const out = [];
3834
5071
  let state = { kind: "normal" };
3835
5072
  let i = 0;
3836
5073
  let codeStart = 0;
3837
5074
  const emitCode = (end) => {
3838
- if (end > codeStart) out.push(transform(sql5.slice(codeStart, end)));
5075
+ if (end > codeStart) out.push(transform(sql7.slice(codeStart, end)));
3839
5076
  codeStart = end;
3840
5077
  };
3841
- while (i < sql5.length) {
3842
- const c = sql5[i];
3843
- const next = i + 1 < sql5.length ? sql5[i + 1] : "";
5078
+ while (i < sql7.length) {
5079
+ const c = sql7[i];
5080
+ const next = i + 1 < sql7.length ? sql7[i + 1] : "";
3844
5081
  switch (state.kind) {
3845
5082
  case "normal": {
3846
5083
  if (c === "-" && next === "-") {
@@ -3860,7 +5097,7 @@ function transformSqlOutsideLiterals(sql5, transform) {
3860
5097
  break;
3861
5098
  }
3862
5099
  if ((c === "E" || c === "e") && next === "'") {
3863
- const prev = i > 0 ? sql5[i - 1] : "";
5100
+ const prev = i > 0 ? sql7[i - 1] : "";
3864
5101
  if (!/[A-Za-z0-9_]/.test(prev)) {
3865
5102
  emitCode(i);
3866
5103
  state = { kind: "e-single" };
@@ -3879,9 +5116,9 @@ function transformSqlOutsideLiterals(sql5, transform) {
3879
5116
  break;
3880
5117
  }
3881
5118
  if (c === "$") {
3882
- const prev = i > 0 ? sql5[i - 1] : "";
5119
+ const prev = i > 0 ? sql7[i - 1] : "";
3883
5120
  if (!/[A-Za-z0-9_$]/.test(prev)) {
3884
- const tagMatch = /^\$([A-Za-z_][A-Za-z0-9_]*)?\$/.exec(sql5.slice(i));
5121
+ const tagMatch = /^\$([A-Za-z_][A-Za-z0-9_]*)?\$/.exec(sql7.slice(i));
3885
5122
  if (tagMatch) {
3886
5123
  emitCode(i);
3887
5124
  state = { kind: "dollar", tag: tagMatch[1] ?? "" };
@@ -3913,8 +5150,8 @@ function transformSqlOutsideLiterals(sql5, transform) {
3913
5150
  break;
3914
5151
  }
3915
5152
  case "e-single": {
3916
- if (c === "\\" && i + 1 < sql5.length) {
3917
- out.push(c, sql5[i + 1]);
5153
+ if (c === "\\" && i + 1 < sql7.length) {
5154
+ out.push(c, sql7[i + 1]);
3918
5155
  i += 2;
3919
5156
  break;
3920
5157
  }
@@ -3936,7 +5173,7 @@ function transformSqlOutsideLiterals(sql5, transform) {
3936
5173
  }
3937
5174
  case "dollar": {
3938
5175
  const closer = `$${state.tag}$`;
3939
- if (sql5.startsWith(closer, i)) {
5176
+ if (sql7.startsWith(closer, i)) {
3940
5177
  out.push(closer);
3941
5178
  state = { kind: "normal" };
3942
5179
  i += closer.length;
@@ -3980,15 +5217,15 @@ function transformSqlOutsideLiterals(sql5, transform) {
3980
5217
  }
3981
5218
  }
3982
5219
  }
3983
- emitCode(sql5.length);
5220
+ emitCode(sql7.length);
3984
5221
  return out.join("");
3985
5222
  }
3986
5223
  function escapeRegExp(value) {
3987
5224
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3988
5225
  }
3989
- function sqlReferencesQualifiedRelation(sql5, sourceSchema, sourceName) {
5226
+ function sqlReferencesQualifiedRelation(sql7, sourceSchema, sourceName) {
3990
5227
  let referenced = false;
3991
- transformSqlOutsideLiterals(sql5, (segment) => {
5228
+ transformSqlOutsideLiterals(sql7, (segment) => {
3992
5229
  if (qualifiedRelationPattern(sourceSchema, sourceName).test(segment)) referenced = true;
3993
5230
  return segment;
3994
5231
  });
@@ -3999,16 +5236,18 @@ function qualifiedRelationPattern(sourceSchema, sourceName) {
3999
5236
  const namePattern = `(?:"${escapeRegExp(sourceName)}"|${escapeRegExp(sourceName)})`;
4000
5237
  return new RegExp(`(?<![\\w."])${schemaPattern}\\.${namePattern}(?![\\w"])`, "g");
4001
5238
  }
4002
- function replaceQualifiedRelation(sql5, sourceSchema, sourceName, boundSchema, boundTable) {
4003
- return sql5.replace(qualifiedRelationPattern(sourceSchema, sourceName), `"${boundSchema}"."${boundTable}"`);
5239
+ function replaceQualifiedRelation(sql7, sourceSchema, sourceName, boundSchema, boundTable) {
5240
+ return sql7.replace(qualifiedRelationPattern(sourceSchema, sourceName), `"${boundSchema}"."${boundTable}"`);
4004
5241
  }
4005
5242
  function generateSnapshotV2(definition, previousId3) {
4006
5243
  const { imports, schemaName } = buildDrizzleEntitiesV2(definition);
4007
5244
  const snapshot = generateDrizzleJson(imports, previousId3, [schemaName]);
4008
5245
  if ((definition.views?.length ?? 0) > 0 || definition.tables.some((table) => table.foreignKeys?.length)) {
4009
5246
  const extended = snapshot;
5247
+ const refreshFunctions = materializedRefreshFunctions(definition);
4010
5248
  extended._sequenceOrm = {
4011
5249
  ...definition.tables.some((table) => table.foreignKeys?.length) ? { foreignKeys: Object.fromEntries(definition.tables.flatMap((table) => (table.foreignKeys ?? []).map((fk) => [`${table.table}.${fk.name}`, structuredClone(fk)]))) } : {},
5250
+ ...refreshFunctions.size > 0 ? { refreshFunctions: Object.fromEntries(refreshFunctions) } : {},
4012
5251
  viewDependencies: Object.fromEntries(
4013
5252
  (definition.views ?? []).map((view) => [view.view, [...view.dependencies].sort()])
4014
5253
  )
@@ -4048,15 +5287,19 @@ function buildTable2(schema, table, built, enums, byName, introspection) {
4048
5287
  const drizzleTable = schema.table(table.table, columns, (self) => [
4049
5288
  ...buildCompositePrimaryKey(table, primaryKeyColumns, self),
4050
5289
  ...buildIndexes2(table, self),
5290
+ ...(table.checks ?? []).map((check$1) => check(check$1.name, sql.raw(compileCheckExpression(check$1.expression, table.columns)))),
4051
5291
  ...buildForeignKeys2(table, self, built, byName),
4052
5292
  ...(table.foreignKeys ?? []).map((fk) => {
4053
5293
  const target = fk.target === table.table ? self : built.get(fk.target);
4054
5294
  if (!target) throw new OrmDefinitionError(`foreign key '${fk.name}' has no target '${fk.target}'`);
4055
- return foreignKey({
5295
+ let constraint = foreignKey({
4056
5296
  name: fk.name,
4057
5297
  columns: fk.columns.map((column) => self[column]),
4058
5298
  foreignColumns: fk.references.map((column) => target[column])
4059
5299
  });
5300
+ if (fk.onDelete !== void 0) constraint = constraint.onDelete(fk.onDelete);
5301
+ if (fk.onUpdate !== void 0) constraint = constraint.onUpdate(fk.onUpdate);
5302
+ return constraint;
4060
5303
  })
4061
5304
  ]);
4062
5305
  return drizzleTable.enableRLS();
@@ -4147,11 +5390,14 @@ function buildForeignKeys2(table, self, built, byName) {
4147
5390
  `enforced link '${link.name}' on '${table.table}' references unknown column '${targetColumn ? link.on : referencedColumn}'`
4148
5391
  );
4149
5392
  }
4150
- return foreignKey({
5393
+ let constraint = foreignKey({
4151
5394
  name: foreignKeyName(table.table, link.name),
4152
5395
  columns: [sourceColumn],
4153
5396
  foreignColumns: [targetColumn]
4154
5397
  });
5398
+ if (link.onDelete !== void 0) constraint = constraint.onDelete(link.onDelete);
5399
+ if (link.onUpdate !== void 0) constraint = constraint.onUpdate(link.onUpdate);
5400
+ return constraint;
4155
5401
  });
4156
5402
  }
4157
5403
  var singlePrimaryKeyOf = (table) => table.primaryKey?.length === 1 ? table.primaryKey[0] : void 0;
@@ -4272,9 +5518,23 @@ function findTable2(snapshot, schema, table) {
4272
5518
  const tables = snapshot.tables ?? {};
4273
5519
  return Object.values(tables).find((entry) => entry.schema === schema && entry.name === table);
4274
5520
  }
4275
- async function planMigrationV2({
5521
+ function planMigrationV2(input) {
5522
+ return planWithViewRecreation({ ...input, viewRecreation: "affected" });
5523
+ }
5524
+ async function planCommittedMigrationV2({
4276
5525
  canonical,
4277
- previousSnapshot
5526
+ previousSnapshot,
5527
+ reproduces
5528
+ }) {
5529
+ const current = await planMigrationV2({ canonical, previousSnapshot });
5530
+ if (reproduces(current)) return { plan: current, reproduced: true };
5531
+ const legacy = await planWithViewRecreation({ canonical, previousSnapshot, viewRecreation: "all-on-base-change" });
5532
+ return reproduces(legacy) ? { plan: legacy, reproduced: true } : { plan: current, reproduced: false };
5533
+ }
5534
+ async function planWithViewRecreation({
5535
+ canonical,
5536
+ previousSnapshot,
5537
+ viewRecreation
4278
5538
  }) {
4279
5539
  const schema = ormSchemaName(canonical.namespace);
4280
5540
  const previous = previousSnapshot ? normalizeSnapshot(previousSnapshot) : emptySnapshot(canonical.namespace);
@@ -4292,7 +5552,14 @@ async function planMigrationV2({
4292
5552
  currentViews,
4293
5553
  previousDependencies,
4294
5554
  currentDependencies,
4295
- baseChanged: hints.statements.length > 0 || baseDdl.length > 0
5555
+ reach: viewReach({
5556
+ viewRecreation,
5557
+ hintStatements: hints.statements,
5558
+ baseDdl,
5559
+ previous,
5560
+ adjusted: hints.adjusted,
5561
+ current: snapshot
5562
+ })
4296
5563
  });
4297
5564
  const dropOrder = topologicalViewNames(
4298
5565
  new Set([...affected].filter((name) => previousViews.has(name))),
@@ -4319,13 +5586,18 @@ async function planMigrationV2({
4319
5586
  ...dropOrder.map((name) => ({ kind: "ddl", sql: requiredViewStatement(dropDdl, name, schema, "DROP") })),
4320
5587
  ...hints.statements.map((statement) => ({ kind: "hint", sql: statement })),
4321
5588
  ...baseDdl.map((statement) => ({ kind: "ddl", sql: statement.replace(/;\s*$/, "") })),
4322
- ...foreignKeyTimingStatements(canonical, previous, baseDdl).map((sql5) => ({ kind: "ddl", sql: sql5 })),
5589
+ ...foreignKeyTimingStatements(canonical, previous, baseDdl).map((sql7) => ({ kind: "ddl", sql: sql7 })),
4323
5590
  ...createOrder.map((name) => ({ kind: "ddl", sql: requiredViewStatement(createDdl, name, schema, "CREATE") })),
4324
5591
  ...createOrder.map((name) => {
4325
5592
  const view = currentViewDefinitions.get(name);
4326
5593
  if (!view) throw new OrmDefinitionError(`missing canonical definition for view '${name}'`);
4327
5594
  return { kind: "ddl", sql: validateViewCatalogStatement({ canonical, view, schema }) };
4328
- })
5595
+ }),
5596
+ ...refreshFunctionStatements({
5597
+ namespace: canonical.namespace,
5598
+ previous: previous._sequenceOrm?.refreshFunctions ?? {},
5599
+ current: materializedRefreshFunctions(canonical)
5600
+ }).map((sql7) => ({ kind: "ddl", sql: sql7 }))
4329
5601
  ];
4330
5602
  return { namespace: canonical.namespace, schema, statements, snapshot };
4331
5603
  }
@@ -4369,9 +5641,9 @@ function validateViewCatalogStatement({
4369
5641
  }
4370
5642
  const expectedDependencies = sqlTextArray(
4371
5643
  view.dependencies.map((dependency) => {
4372
- const physical = relationIdentity.get(dependency);
4373
- if (!physical) throw new OrmDefinitionError(`view '${view.view}' depends on unknown relation '${dependency}'`);
4374
- return physical;
5644
+ const physical2 = relationIdentity.get(dependency);
5645
+ if (!physical2) throw new OrmDefinitionError(`view '${view.view}' depends on unknown relation '${dependency}'`);
5646
+ return physical2;
4375
5647
  }).sort()
4376
5648
  );
4377
5649
  const allowedFunctions = sqlTextArray(VIEW_QUERY_FUNCTION_ALLOWLIST);
@@ -4479,20 +5751,102 @@ function withoutViews(snapshot) {
4479
5751
  delete clone._sequenceOrm;
4480
5752
  return clone;
4481
5753
  }
5754
+ var DROPS_TYPE = /^\s*DROP\s+TYPE\b/i;
5755
+ function viewReach({
5756
+ viewRecreation,
5757
+ hintStatements,
5758
+ baseDdl,
5759
+ previous,
5760
+ adjusted,
5761
+ current
5762
+ }) {
5763
+ if (hintStatements.length === 0 && baseDdl.length === 0) return /* @__PURE__ */ new Set();
5764
+ if (viewRecreation === "all-on-base-change") return "all";
5765
+ if (baseDdl.some((statement) => DROPS_TYPE.test(statement))) return "all";
5766
+ const dependencies = snapshotViewDependencies(adjusted);
5767
+ if ([...snapshotViews(adjusted).keys()].some((name) => dependencies[name] === void 0)) return "all";
5768
+ return baseChangesReachingViews({ previous, adjusted, current });
5769
+ }
5770
+ var SNAPSHOT_BOOKKEEPING = /* @__PURE__ */ new Set(["id", "prevId", "version", "dialect", "_meta", "views", "_sequenceOrm", "enums"]);
5771
+ var VIEW_SAFE_TABLE_FIELDS = /* @__PURE__ */ new Set([
5772
+ "indexes",
5773
+ "foreignKeys",
5774
+ "uniqueConstraints",
5775
+ "checkConstraints",
5776
+ "policies",
5777
+ "isRLSEnabled"
5778
+ ]);
5779
+ var VIEW_SAFE_COLUMN_FIELDS = /* @__PURE__ */ new Set(["notNull", "default", "isUnique", "uniqueName", "nullsNotDistinct"]);
5780
+ function baseChangesReachingViews({
5781
+ previous,
5782
+ adjusted,
5783
+ current
5784
+ }) {
5785
+ const before = adjusted;
5786
+ const after = current;
5787
+ for (const section of /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])) {
5788
+ if (SNAPSHOT_BOOKKEEPING.has(section) || section === "tables") continue;
5789
+ if (stableJson(before[section]) !== stableJson(after[section])) return "all";
5790
+ }
5791
+ const relations = /* @__PURE__ */ new Set();
5792
+ const previousTables = snapshotTables(previous);
5793
+ const adjustedTables = snapshotTables(adjusted);
5794
+ for (const key of changedEntryKeys(previousTables, adjustedTables)) {
5795
+ const table = previousTables[key] ?? adjustedTables[key];
5796
+ if (table) relations.add(table.name);
5797
+ }
5798
+ const currentTables = snapshotTables(current);
5799
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(adjustedTables), ...Object.keys(currentTables)])) {
5800
+ const was = adjustedTables[key];
5801
+ const is2 = currentTables[key];
5802
+ if (!was || !is2) {
5803
+ const table = was ?? is2;
5804
+ if (table) relations.add(table.name);
5805
+ } else if (tableChangeReachesViews({ was, is: is2 })) {
5806
+ relations.add(is2.name);
5807
+ }
5808
+ }
5809
+ return relations;
5810
+ }
5811
+ function tableChangeReachesViews({ was, is: is2 }) {
5812
+ for (const field of /* @__PURE__ */ new Set([...Object.keys(was), ...Object.keys(is2)])) {
5813
+ if (field === "columns" || VIEW_SAFE_TABLE_FIELDS.has(field)) continue;
5814
+ if (stableJson(was[field]) !== stableJson(is2[field])) return true;
5815
+ }
5816
+ return Object.entries(was.columns).some(([name, column]) => {
5817
+ const next = is2.columns[name];
5818
+ if (!next) return true;
5819
+ return [.../* @__PURE__ */ new Set([...Object.keys(column), ...Object.keys(next)])].some(
5820
+ (field) => !VIEW_SAFE_COLUMN_FIELDS.has(field) && stableJson(column[field]) !== stableJson(next[field])
5821
+ );
5822
+ });
5823
+ }
5824
+ function changedEntryKeys(before, after) {
5825
+ return new Set(
5826
+ [.../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])].filter(
5827
+ (key) => stableJson(before[key]) !== stableJson(after[key])
5828
+ )
5829
+ );
5830
+ }
5831
+ function snapshotTables(snapshot) {
5832
+ return snapshot.tables ?? {};
5833
+ }
4482
5834
  function affectedViewNames({
4483
5835
  previousViews,
4484
5836
  currentViews,
4485
5837
  previousDependencies,
4486
5838
  currentDependencies,
4487
- baseChanged
5839
+ reach
4488
5840
  }) {
4489
5841
  const allNames = /* @__PURE__ */ new Set([...previousViews.keys(), ...currentViews.keys()]);
4490
- if (baseChanged) return allNames;
5842
+ if (reach === "all") return allNames;
5843
+ const dependenciesOf = (name) => /* @__PURE__ */ new Set([...previousDependencies[name] ?? [], ...currentDependencies[name] ?? []]);
4491
5844
  const affected = new Set(
4492
5845
  [...allNames].filter((name) => {
4493
5846
  const before = previousViews.get(name);
4494
5847
  const after = currentViews.get(name);
4495
- return before === void 0 || after === void 0 || stableJson(before) !== stableJson(after);
5848
+ if (before === void 0 || after === void 0 || stableJson(before) !== stableJson(after)) return true;
5849
+ return [...dependenciesOf(name)].some((dependency) => reach.has(dependency));
4496
5850
  })
4497
5851
  );
4498
5852
  let grew = true;
@@ -4500,8 +5854,7 @@ function affectedViewNames({
4500
5854
  grew = false;
4501
5855
  for (const name of allNames) {
4502
5856
  if (affected.has(name)) continue;
4503
- const dependencies = /* @__PURE__ */ new Set([...previousDependencies[name] ?? [], ...currentDependencies[name] ?? []]);
4504
- if ([...dependencies].some((dependency) => affected.has(dependency))) {
5857
+ if ([...dependenciesOf(name)].some((dependency) => affected.has(dependency))) {
4505
5858
  affected.add(name);
4506
5859
  grew = true;
4507
5860
  }
@@ -4739,15 +6092,15 @@ async function planMigration({
4739
6092
  const ddl = await diffStatements(hints.adjusted, snapshot);
4740
6093
  const policyDrops = policyDropStatements(schema, snapshotTableNames(previous, schema));
4741
6094
  const statements = [
4742
- ...policyDrops.map((sql5) => ({ kind: "security", sql: sql5 })),
4743
- ...hints.statements.map((sql5) => ({ kind: "hint", sql: sql5 })),
4744
- ...ddl.map((sql5) => ({ kind: "ddl", sql: sql5.replace(/;\s*$/, "") })),
4745
- ...securityStatements(definition).map((sql5) => ({ kind: "security", sql: sql5 })),
6095
+ ...policyDrops.map((sql7) => ({ kind: "security", sql: sql7 })),
6096
+ ...hints.statements.map((sql7) => ({ kind: "hint", sql: sql7 })),
6097
+ ...ddl.map((sql7) => ({ kind: "ddl", sql: sql7.replace(/;\s*$/, "") })),
6098
+ ...securityStatements(definition).map((sql7) => ({ kind: "security", sql: sql7 })),
4746
6099
  // PREPARE each action's executes first (plpgsql bodies don't check columns
4747
6100
  // at CREATE), then create the functions. Both reference the tables the DDL
4748
6101
  // above creates, so they close the plan.
4749
- ...actionPrepareChecks(definition).map((sql5) => ({ kind: "function", sql: sql5 })),
4750
- ...actionFunctionStatements(definition).map((sql5) => ({ kind: "function", sql: sql5 }))
6102
+ ...actionPrepareChecks(definition).map((sql7) => ({ kind: "function", sql: sql7 })),
6103
+ ...actionFunctionStatements(definition).map((sql7) => ({ kind: "function", sql: sql7 }))
4751
6104
  ];
4752
6105
  return { namespace: definition.namespace, schema, statements, snapshot };
4753
6106
  }
@@ -4877,6 +6230,21 @@ var selectionIrSchema = z.lazy(
4877
6230
  () => z.object({
4878
6231
  typename: z.string(),
4879
6232
  columns: z.array(z.object({ alias: z.string(), column: z.string() })),
6233
+ cells: z.object({
6234
+ alias: z.string(),
6235
+ typename: z.string(),
6236
+ fields: z.array(z.object({
6237
+ alias: z.string(),
6238
+ column: z.string(),
6239
+ typename: z.string(),
6240
+ parts: z.array(z.object({
6241
+ alias: z.string(),
6242
+ part: z.enum(["source", "at", "by", "conflicts"]),
6243
+ fields: z.array(z.object({ alias: z.string(), field: z.enum(["source", "at", "value"]) })).optional(),
6244
+ typename: z.string().optional()
6245
+ }))
6246
+ }))
6247
+ }).optional(),
4880
6248
  relations: z.array(z.object({
4881
6249
  alias: z.string(),
4882
6250
  link: z.string(),
@@ -4898,6 +6266,11 @@ var selectionIrSchema = z.lazy(
4898
6266
  })
4899
6267
  );
4900
6268
  var includeIfSchema = z.object({ variable: z.string(), negated: z.boolean() });
6269
+ var prefetchPlanIrSchema = z.object({
6270
+ source: z.string(),
6271
+ maxStalenessMs: z.number().int().positive().optional(),
6272
+ key: z.union([z.object({ one: valueIrSchema }), z.object({ many: valueIrSchema })])
6273
+ });
4901
6274
  var selectRootSchema = z.object({
4902
6275
  kind: z.literal("select"),
4903
6276
  alias: z.string(),
@@ -4909,7 +6282,8 @@ var selectRootSchema = z.object({
4909
6282
  distinctOn: z.union([z.array(z.string()), z.object({ slot: z.string() })]).optional(),
4910
6283
  limit: limitIrSchema,
4911
6284
  offset: z.object({ variable: z.string().optional(), value: z.number().int().optional() }).optional(),
4912
- includeIf: includeIfSchema.optional()
6285
+ includeIf: includeIfSchema.optional(),
6286
+ prefetch: z.array(prefetchPlanIrSchema).optional()
4913
6287
  });
4914
6288
  var byPkRootSchema = z.object({
4915
6289
  kind: z.literal("byPk"),
@@ -4919,7 +6293,8 @@ var byPkRootSchema = z.object({
4919
6293
  pk: z.array(z.object({ column: z.string(), value: valueIrSchema })).min(1),
4920
6294
  /** Policy predicate — ANDed with the PK match. */
4921
6295
  where: predIrSchema,
4922
- includeIf: includeIfSchema.optional()
6296
+ includeIf: includeIfSchema.optional(),
6297
+ prefetch: z.array(prefetchPlanIrSchema).optional()
4923
6298
  });
4924
6299
  var aggregateRootSchema = z.object({
4925
6300
  kind: z.literal("aggregate"),
@@ -4977,7 +6352,20 @@ var mutationRootSchema = z.object({
4977
6352
  * coalesce view falls back to synced. Both are gated on a matching synced
4978
6353
  * row and carry the write policy in `where` like update/delete.
4979
6354
  */
4980
- op: z.enum(["insert", "insertOne", "update", "updateByPk", "delete", "deleteByPk", "cowOverride", "cowRevert"]),
6355
+ op: z.enum(["insert", "insertOne", "update", "updateByPk", "delete", "deleteByPk", "cowOverride", "cowRevert", "resolvedWrite"]),
6356
+ /**
6357
+ * resolvedWrite: a write to a resolved table (`table` is its view). The
6358
+ * executor gates on the resolved row (write policy in `where`), writes only
6359
+ * the touched cells into the manual source, then re-checks `insertCheck`
6360
+ * against the resolved row. `revert` clears `revertColumns` from the manual
6361
+ * source; `delete` tombstones the row there.
6362
+ */
6363
+ resolvedAction: z.enum(["insert", "update", "delete", "revert"]).optional(),
6364
+ revertColumns: z.array(z.string()).optional(),
6365
+ /** `@writeback(source:, mode:)` directives on this root. */
6366
+ writeback: z.array(z.object({ source: z.string(), mode: z.enum(["sync", "async"]) })).optional(),
6367
+ /** `@localOnly(source:)` — writeback columns deliberately kept local for these sources. */
6368
+ localOnly: z.array(z.string()).optional(),
4981
6369
  alias: z.string(),
4982
6370
  table: z.string(),
4983
6371
  /** CoW ops: logical name of the synced twin (read through its policy-filtered view). */
@@ -5062,6 +6450,43 @@ var compiledOperationSchema = z.object({
5062
6450
  }
5063
6451
  }
5064
6452
  });
6453
+ var resolvedSourceIrSchema = z.object({
6454
+ name: z.string(),
6455
+ kind: z.enum(["manual", "etl", "prefetch"]),
6456
+ /** Logical table key of the backing relation. */
6457
+ table: z.string(),
6458
+ columns: z.array(z.string()),
6459
+ key: z.string().optional(),
6460
+ fetch: z.union([
6461
+ z.object({ shape: z.literal("single") }),
6462
+ z.object({ shape: z.literal("batch"), maxKeys: z.number().int().positive() })
6463
+ ]).optional(),
6464
+ function: z.object({ kind: z.enum(["managed", "native"]), id: z.string() }).optional(),
6465
+ prefetchDefaults: z.object({
6466
+ maxStalenessMs: z.number().int().positive(),
6467
+ timeoutMs: z.number().int().positive(),
6468
+ onError: z.enum(["fallback", "fail"])
6469
+ }).optional(),
6470
+ writeback: z.object({
6471
+ columns: z.array(z.string()),
6472
+ defaultMode: z.enum(["sync", "async"]),
6473
+ timeoutMs: z.number().int().positive(),
6474
+ clearManualOnConfirm: z.boolean()
6475
+ }).optional()
6476
+ });
6477
+ var resolvedMetaIrSchema = z.object({
6478
+ /** Logical pk column of the resolved view (single-column). */
6479
+ pk: z.string(),
6480
+ /** Source name of the manual source. */
6481
+ manual: z.string(),
6482
+ sources: z.array(resolvedSourceIrSchema),
6483
+ deletedBy: z.array(z.string()),
6484
+ /**
6485
+ * Materialized: the view reads `table`, which the data plane refreshes from
6486
+ * the `compute` view (claims-gated) whenever it writes a source row.
6487
+ */
6488
+ materialized: z.object({ table: z.string(), compute: z.string() }).optional()
6489
+ });
5065
6490
  var tableMetaIrSchema = z.object({
5066
6491
  table: z.string(),
5067
6492
  schema: z.string(),
@@ -5102,6 +6527,22 @@ var tableMetaIrSchema = z.object({
5102
6527
  * `onUpdateFn`). Omitted when empty so pre-onUpdate artifacts still parse.
5103
6528
  */
5104
6529
  touchOnUpdate: z.array(z.string()).optional(),
6530
+ /**
6531
+ * Per-cell metadata: logical column → the logical names of its cell-part
6532
+ * columns (which are listed in `columns`, but never filterable, orderable or
6533
+ * writable). Tables stamp `at`/`by` (and a manual source `rev`) on every
6534
+ * write; resolved views expose `source`/`at`/`by`/`conflicts`. Omitted when
6535
+ * the relation tracks no cells.
6536
+ */
6537
+ cells: z.record(z.string(), z.object({
6538
+ at: z.string().optional(),
6539
+ by: z.string().optional(),
6540
+ rev: z.string().optional(),
6541
+ source: z.string().optional(),
6542
+ conflicts: z.string().optional()
6543
+ }).strict()).optional(),
6544
+ /** Resolved-table view metadata (sources, prefetch/writeback config) for the data plane. */
6545
+ resolved: resolvedMetaIrSchema.optional(),
5105
6546
  constraints: z.array(z.object({ name: z.string(), columns: z.array(z.string()) })),
5106
6547
  /** Depth/node caps for dynamic filter trees on this table. */
5107
6548
  dynamicFilterMaxDepth: z.number().int().positive().default(6),
@@ -5446,12 +6887,12 @@ async function writeMigration({
5446
6887
  const tip = workspace.journal.entries.at(-1);
5447
6888
  const ordinal = (tip?.ordinal ?? 0) + 1;
5448
6889
  const name = migrationName(ordinal, contentHash);
5449
- const sql5 = renderMigrationSql({ name, namespace: workspace.journal.namespace, contentHash, plan });
6890
+ const sql7 = renderMigrationSql({ name, namespace: workspace.journal.namespace, contentHash, plan });
5450
6891
  const metaDir = join(dir, MIGRATIONS_DIRNAME, META_DIRNAME);
5451
6892
  await mkdir(metaDir, { recursive: true });
5452
6893
  const snapshotText = `${JSON.stringify(plan.snapshot, null, 2)}
5453
6894
  `;
5454
- await writeFile(join(dir, MIGRATIONS_DIRNAME, `${name}.sql`), sql5);
6895
+ await writeFile(join(dir, MIGRATIONS_DIRNAME, `${name}.sql`), sql7);
5455
6896
  await writeFile(join(metaDir, `${pad(ordinal)}.snapshot.json`), snapshotText);
5456
6897
  const journal = {
5457
6898
  ...workspace.journal,
@@ -5462,7 +6903,7 @@ async function writeMigration({
5462
6903
  name,
5463
6904
  content_hash: contentHash,
5464
6905
  previous_content_hash: tip?.content_hash ?? null,
5465
- checksum: checksumOf(sql5),
6906
+ checksum: checksumOf(sql7),
5466
6907
  snapshot_checksum: checksumOf(snapshotText),
5467
6908
  destructive
5468
6909
  }
@@ -5577,8 +7018,8 @@ async function checkWorkspace({
5577
7018
  let previousId3;
5578
7019
  let previousHash = null;
5579
7020
  for (const entry of workspace.journal.entries) {
5580
- const sql5 = await readMigrationSql(dir, entry);
5581
- if (checksumOf(sql5) !== entry.checksum) {
7021
+ const sql7 = await readMigrationSql(dir, entry);
7022
+ if (checksumOf(sql7) !== entry.checksum) {
5582
7023
  log(`${MIGRATIONS_DIRNAME}/${entry.name}.sql does not match its journal checksum \u2014 committed migrations must not be edited`);
5583
7024
  return 1;
5584
7025
  }
@@ -5618,7 +7059,7 @@ async function checkWorkspace({
5618
7059
  log(`migrations in sync \u2014 ${workspace.journal.entries.length} committed, tip ${tip.name}`);
5619
7060
  return 0;
5620
7061
  }
5621
- var firstLine = (sql5) => sql5.split("\n")[0] ?? sql5;
7062
+ var firstLine = (sql7) => sql7.split("\n")[0] ?? sql7;
5622
7063
 
5623
7064
  // src/migrations/from-yaml.ts
5624
7065
  function convertV1DefinitionToCanonical(definition) {
@@ -5724,7 +7165,7 @@ function buildRebaseJournalEntry({ namespace, previousHash, newHash, ordinal, sn
5724
7165
  throw new OrmDefinitionError(`rebase ordinal must be an integer \u2265 2 (a rebase sits on an existing journal tip); got ${ordinal}`);
5725
7166
  }
5726
7167
  const name = migrationName(ordinal, newHash);
5727
- const sql5 = renderRebaseMigrationSql({ name, namespace, previousHash, newHash });
7168
+ const sql7 = renderRebaseMigrationSql({ name, namespace, previousHash, newHash });
5728
7169
  const snapshotText = `${JSON.stringify(snapshot, null, 2)}
5729
7170
  `;
5730
7171
  const entry = {
@@ -5732,11 +7173,11 @@ function buildRebaseJournalEntry({ namespace, previousHash, newHash, ordinal, sn
5732
7173
  name,
5733
7174
  content_hash: newHash,
5734
7175
  previous_content_hash: previousHash,
5735
- checksum: checksumOf(sql5),
7176
+ checksum: checksumOf(sql7),
5736
7177
  snapshot_checksum: checksumOf(snapshotText),
5737
7178
  destructive: false
5738
7179
  };
5739
- return { name, entry, sql: sql5, snapshotText };
7180
+ return { name, entry, sql: sql7, snapshotText };
5740
7181
  }
5741
7182
  function renderRebaseMigrationSql({
5742
7183
  name,
@@ -5834,6 +7275,6 @@ function canonicalActions(value) {
5834
7275
  );
5835
7276
  }
5836
7277
 
5837
- export { ACTION_REQUIRES_DENIED_SQLSTATE, ACTION_VALIDATES_FAILED_SQLSTATE, AUDIT_ACTIONS, CANONICAL_FORMAT, CAPABILITIES_CLAIM_KEY, CAPABILITY_KEY_PATTERN, CLAIMS_CONTRACT, CLAIMS_SETTING, CLAIM_KEY_PATTERN, CMP_OPS, COLUMN_BRAND, COLUMN_DEFAULT_FNS, COLUMN_IDENTIFIER_PATTERN, COLUMN_TYPES2 as COLUMN_TYPES, COMPARE_OPS, CONSUMER_ROLE_SUFFIX, ColumnBuilder, DATE_BUCKETS, DEFINITION_FILE_EXTENSIONS, ENUM_BRAND, HASH_DOMAIN, IDENTIFIER_PATTERN, IDENTIFIER_RE, INPUT_REF_KINDS, IR_VERSION, IrVersionError, LEGACY_IR_VERSION, LINK_BRAND, MAX_DEFINITION_FILE_BYTES, MAX_GENERATED_SDL_BYTES, MAX_IDENTIFIER_LENGTH, MIGRATIONS_DIRNAME, NAMESPACE_CONFIG_BRAND, NAMESPACE_CONFIG_FILE, NAMESPACE_CONFIG_FILENAMES, NAMESPACE_PATTERN, ORM_DATA_TOKEN_HEADER, ORM_MIGRATION_EXPORT_FILE, ORM_MIGRATION_EXPORT_MAX_BYTES, ORM_REGISTRATION_MAX_BYTES, ORM_SCHEMA_PREFIX, ORM_SERVICE_DATA_TOKEN_HEADER, ORM_TO_PIPELINE_TYPE, OrmDefinitionError, PredicateParseError, QUALIFIED_TABLE_PATTERN, RESERVED_COLUMN_NAMES, RESERVED_TABLE_NAMES, ROLES_CLAIM_KEY, SCHEMA_DIR, STATEMENT_KINDS, SYNCED_CONTRACT_HASH_DOMAIN, SYNCED_SOURCE_PATTERN, SYNCED_VIEW_EXECUTION_OWNER_ROLE, TABLE_BRAND, VIEW_BRAND, actionFunctionName, actionFunctionStatements, actionParamArg, actionParamOrder, actionParamSchema, actionPrepareChecks, actionSchema, actionWriteTargets, actionsInterfaceName, activeAuthoringContext, applyMigrationHints, applyMigrationHintsV2, beginAuthoringContext, buildActionParamsSchema, buildDrizzleEntities, buildDrizzleEntitiesV2, buildNamespaceDefinition, buildRebaseJournalEntry, canonicalActionSchema, canonicalColumnSchema, canonicalDefinitionSchema, canonicalEnumSchema, canonicalIndexSchema, canonicalLinkSchema, canonicalPoliciesSchema, canonicalRelations, canonicalTableSchema, canonicalViewSchema, canonicalizeDefinition, capabilityExpression, castParts, checksumOf, claimExpression, classifyDestructive, columnSchema, columnTypeSchema2 as columnTypeSchema, compileActionRequires, compileActionStatements, compileNamespace, compilePredIrToSql, compilePredicate, compiledArtifactSchema, compiledOperationSchema, computeCanonicalHash, computeDefinitionHash, computeSyncedContractHash, consumerRoleName, convertV1DefinitionToCanonical, createOrmMigrationExport, declaredCapabilityKeys, defineEnum, defineNamespace, defineSyncedTable, defineTable, defineView, deriveColumnName, diffCommand, diffStatements, emptyJournal, emptySnapshot, endAuthoringContext, extractNamedParams, extractOrmSyncedTableContract, generateNamespaceTypes, generateSnapshot, generateSnapshotV2, identifierSchema, indexByHash, isColumnBuilder, isEnumRef, isFunctionDefault, isOwnedStatement, isOwnedStatementV2, isQualifiedReference, isSeqLink, isSeqNamespaceConfig, isSeqTable, isSeqView, limitIrSchema, loadCommittedMigrations, loadCommittedTipMigration, loadNamespaceDefinition, loadNamespaceFromDir, migrationName, namespaceConfigSchema, namespaceSchema, normalizeCanonical, normalizeIr, normalizeSnapshot, orderIrSchema, ormMigrationExportSchema, ormSchemaName, overlayWinsUnion, overrideRowMutation, parseCanonicalDefinition, parseDefinitionSources, parseOrmMigrationExport, parsePredicate, parseTableFile, parsedPredicateSchema, pendingKitStatements, pendingKitStatementsV2, planMigration, planMigrationV2, policyExpressionSchema, policyVariantSchema, predIrSchema, predicateOperands, quoteIdentifier, quoteLiteral, readMigrationSql, readMigrationWorkspace, readSnapshot, rebaseViolations, referencedCapabilities, referencedColumns, renderMigrationSql, renderOrmMigrationExport, requiredArtifactIrVersion, revertOverlayMutation, rewriteSyncedSourceRelationsInViewQuery, roleExpression, rootIrSchema, securityStatements, selectionIrSchema, serializeNamespace, sqlTypeName, stableJson, syncedBackingRevokeStatements, syncedContractSurface, syncedTables, syncedViewDropStatements, syncedViewStatements, t, tableFileSchema, tableMetaIrSchema, tableReferenceSchema, toMany, toOne, toPlatformUser, typesInterfaceName, validateViewCatalogStatement, valueIrSchema, variableDefaultIrSchema, variableIrSchema, verifyRebaseIsNoop, walkPredicate, writeMigration };
7278
+ export { ACTION_REQUIRES_DENIED_SQLSTATE, ACTION_VALIDATES_FAILED_SQLSTATE, AUDIT_ACTIONS, CANONICAL_FORMAT, CAPABILITIES_CLAIM_KEY, CAPABILITY_KEY_PATTERN, CELLS_FIELD, CELL_PARTS, CELL_PART_SUFFIX, CLAIMS_CONTRACT, CLAIMS_SETTING, CLAIM_KEY_PATTERN, CMP_OPS, COLUMN_BRAND, COLUMN_DEFAULT_FNS, COLUMN_IDENTIFIER_PATTERN, COLUMN_TYPES2 as COLUMN_TYPES, COMPARE_OPS, CONSUMER_ROLE_SUFFIX, ColumnBuilder, DATE_BUCKETS, DEFINITION_FILE_EXTENSIONS, ENUM_BRAND, HASH_DOMAIN, IDENTIFIER_PATTERN, IDENTIFIER_RE, INPUT_REF_KINDS, IR_VERSION, IrVersionError, LEGACY_IR_VERSION, LINK_BRAND, MATERIALIZE_ACTOR, MATERIALIZE_CLAIMS_JSON, MAX_DEFINITION_FILE_BYTES, MAX_GENERATED_SDL_BYTES, MAX_IDENTIFIER_LENGTH, MAX_TRACKED_COLUMN_NAME, MIGRATIONS_DIRNAME, NAMESPACE_CONFIG_BRAND, NAMESPACE_CONFIG_FILE, NAMESPACE_CONFIG_FILENAMES, NAMESPACE_PATTERN, ORM_DATA_TOKEN_HEADER, ORM_MIGRATION_EXPORT_FILE, ORM_MIGRATION_EXPORT_MAX_BYTES, ORM_REGISTRATION_MAX_BYTES, ORM_SCHEMA_PREFIX, ORM_SERVICE_DATA_TOKEN_HEADER, ORM_TO_PIPELINE_TYPE, OrmDefinitionError, PredicateParseError, QUALIFIED_TABLE_PATTERN, RESERVED_COLUMN_NAMES, RESERVED_TABLE_NAMES, ROLES_CLAIM_KEY, SCHEMA_DIR, STATEMENT_KINDS, SYNCED_CONTRACT_HASH_DOMAIN, SYNCED_SOURCE_PATTERN, SYNCED_VIEW_EXECUTION_OWNER_ROLE, TABLE_BRAND, VIEW_BRAND, actionFunctionName, actionFunctionStatements, actionParamArg, actionParamOrder, actionParamSchema, actionPrepareChecks, actionSchema, actionWriteTargets, actionsInterfaceName, activeAuthoringContext, applyMigrationHints, applyMigrationHintsV2, beginAuthoringContext, buildActionParamsSchema, buildDrizzleEntities, buildDrizzleEntitiesV2, buildNamespaceDefinition, buildRebaseJournalEntry, canonicalActionSchema, canonicalColumnSchema, canonicalDefinitionSchema, canonicalEnumSchema, canonicalIndexSchema, canonicalLinkSchema, canonicalPoliciesSchema, canonicalRelations, canonicalTableSchema, canonicalViewSchema, canonicalizeDefinition, capabilityExpression, castParts, cellColumnName, checksumOf, claimExpression, classifyDestructive, columnSchema, columnTypeSchema2 as columnTypeSchema, compileActionRequires, compileActionStatements, compileNamespace, compilePredIrToSql, compilePredicate, compiledArtifactSchema, compiledOperationSchema, computeCanonicalHash, computeDefinitionHash, computeSyncedContractHash, consumerRoleName, convertV1DefinitionToCanonical, createOrmMigrationExport, custom, declaredCapabilityKeys, defineEnum, defineNamespace, defineResolvedTable, defineSyncedTable, defineTable, defineView, deriveColumnName, diffCommand, diffStatements, emptyJournal, emptySnapshot, endAuthoringContext, etlSource, extractNamedParams, extractOrmSyncedTableContract, generateNamespaceTypes, generateSnapshot, generateSnapshotV2, highest, identifierSchema, indexByHash, isColumnBuilder, isEnumRef, isFunctionDefault, isOwnedStatement, isOwnedStatementV2, isQualifiedReference, isSeqLink, isSeqNamespaceConfig, isSeqTable, isSeqView, latest, limitIrSchema, loadCommittedMigrations, loadCommittedTipMigration, loadNamespaceDefinition, loadNamespaceFromDir, lowest, managedFn, manualSource, materializeLockKey, materializedRefreshFunctionName, migrationName, namespaceConfigSchema, namespaceSchema, nonNullFirst, normalizeCanonical, normalizeIr, normalizeSnapshot, orderIrSchema, ormMigrationExportSchema, ormSchemaName, overlayWinsUnion, overrideRowMutation, parseCanonicalDefinition, parseDefinitionSources, parseOrmMigrationExport, parsePredicate, parseTableFile, parsedPredicateSchema, pendingKitStatements, pendingKitStatementsV2, planCommittedMigrationV2, planMigration, planMigrationV2, policyExpressionSchema, policyVariantSchema, predIrSchema, predicateOperands, prefetchPlanIrSchema, prefetchSource, priority, quoteIdentifier, quoteLiteral, readMigrationSql, readMigrationWorkspace, readSnapshot, rebaseViolations, referencedCapabilities, referencedColumns, renderMigrationSql, renderOrmMigrationExport, requiredArtifactIrVersion, resolvedMetaIrSchema, resolvedSourceIrSchema, revertOverlayMutation, rewriteSyncedSourceRelationsInViewQuery, roleExpression, rootIrSchema, securityStatements, selectionIrSchema, serializeNamespace, sqlTypeName, stableJson, syncedBackingRevokeStatements, syncedContractSurface, syncedTables, syncedViewDropStatements, syncedViewStatements, t, tableFileSchema, tableMetaIrSchema, tableReferenceSchema, toMany, toOne, toPlatformUser, typesInterfaceName, validateViewCatalogStatement, valueIrSchema, variableDefaultIrSchema, variableIrSchema, verifyRebaseIsNoop, walkPredicate, writeMigration };
5838
7279
  //# sourceMappingURL=index.js.map
5839
7280
  //# sourceMappingURL=index.js.map