@sequenceholdings/orm 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +40 -1
  2. package/dist/authoring.cjs +1619 -121
  3. package/dist/authoring.cjs.map +1 -1
  4. package/dist/authoring.d.cts +252 -8
  5. package/dist/authoring.d.ts +252 -8
  6. package/dist/authoring.js +1607 -123
  7. package/dist/authoring.js.map +1 -1
  8. package/dist/canonical.cjs +713 -41
  9. package/dist/canonical.cjs.map +1 -1
  10. package/dist/canonical.d.cts +1520 -51
  11. package/dist/canonical.d.ts +1520 -51
  12. package/dist/canonical.js +693 -42
  13. package/dist/canonical.js.map +1 -1
  14. package/dist/cli.cjs +7103 -3901
  15. package/dist/cli.cjs.map +1 -1
  16. package/dist/cli.js +7104 -3902
  17. package/dist/cli.js.map +1 -1
  18. package/dist/client.cjs +122 -40
  19. package/dist/client.cjs.map +1 -1
  20. package/dist/client.d.cts +85 -25
  21. package/dist/client.d.ts +85 -25
  22. package/dist/client.js +119 -41
  23. package/dist/client.js.map +1 -1
  24. package/dist/compiler.cjs +2464 -450
  25. package/dist/compiler.cjs.map +1 -1
  26. package/dist/compiler.d.cts +79 -4
  27. package/dist/compiler.d.ts +79 -4
  28. package/dist/compiler.js +2457 -451
  29. package/dist/compiler.js.map +1 -1
  30. package/dist/constants-C6EG6Wq7.d.cts +99 -0
  31. package/dist/constants-C6EG6Wq7.d.ts +99 -0
  32. package/dist/definition-DjfC7Lll.d.cts +91 -0
  33. package/dist/definition-DjfC7Lll.d.ts +91 -0
  34. package/dist/dynamic-input.cjs +2 -1
  35. package/dist/dynamic-input.cjs.map +1 -1
  36. package/dist/dynamic-input.d.cts +9 -1
  37. package/dist/dynamic-input.d.ts +9 -1
  38. package/dist/dynamic-input.js +2 -1
  39. package/dist/dynamic-input.js.map +1 -1
  40. package/dist/index.cjs +2656 -418
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.d.cts +693 -116
  43. package/dist/index.d.ts +693 -116
  44. package/dist/index.js +2622 -420
  45. package/dist/index.js.map +1 -1
  46. package/dist/ir.cjs +811 -48
  47. package/dist/ir.cjs.map +1 -1
  48. package/dist/ir.d.cts +670 -27
  49. package/dist/ir.d.ts +670 -27
  50. package/dist/ir.js +802 -49
  51. package/dist/ir.js.map +1 -1
  52. package/dist/{naming-DCn94LJ4.d.ts → naming-CPfx7EfF.d.ts} +14 -1
  53. package/dist/{naming-DPglwzkB.d.cts → naming-CU9ThP_E.d.cts} +14 -1
  54. package/dist/policy-1iZbGnZZ.d.cts +24 -0
  55. package/dist/policy-1iZbGnZZ.d.ts +24 -0
  56. package/dist/predicate-v2.cjs +804 -51
  57. package/dist/predicate-v2.cjs.map +1 -1
  58. package/dist/predicate-v2.d.cts +2 -1
  59. package/dist/predicate-v2.d.ts +2 -1
  60. package/dist/predicate-v2.js +804 -51
  61. package/dist/predicate-v2.js.map +1 -1
  62. package/dist/sdl.cjs +1014 -75
  63. package/dist/sdl.cjs.map +1 -1
  64. package/dist/sdl.d.cts +14 -3
  65. package/dist/sdl.d.ts +14 -3
  66. package/dist/sdl.js +1012 -76
  67. package/dist/sdl.js.map +1 -1
  68. package/package.json +13 -7
  69. package/src/codegen-gql/server-fns.cjs +2 -2
  70. package/templates/orm-namespace/AGENTS.md +10 -0
  71. package/templates/orm-namespace/CLAUDE.md +1 -82
  72. package/templates/orm-namespace/codegen.preset.d.cts +7 -0
  73. package/templates/orm-namespace/codegen.ts +2 -1
  74. package/templates/orm-namespace/graphql/example/queries.ts +4 -3
  75. package/templates/orm-namespace/package.json +2 -1
  76. package/templates/orm-namespace/schema/tables.ts +3 -2
  77. package/templates/orm-namespace/sequence.config.ts +7 -11
package/dist/index.cjs CHANGED
@@ -2,8 +2,8 @@
2
2
 
3
3
  var drizzleOrm = require('drizzle-orm');
4
4
  var pgCore = require('drizzle-orm/pg-core');
5
- var crypto = require('crypto');
6
5
  var zod = require('zod');
6
+ var crypto = require('crypto');
7
7
  var jiti = require('jiti');
8
8
  var promises = require('fs/promises');
9
9
  var module$1 = require('module');
@@ -40,7 +40,8 @@ var ColumnBuilder = class {
40
40
  description: void 0,
41
41
  renamedFrom: void 0,
42
42
  cast: void 0,
43
- deprecated: false
43
+ deprecated: false,
44
+ cell: void 0
44
45
  };
45
46
  }
46
47
  /** Marks this column as (part of) the primary key. Implies NOT NULL. */
@@ -100,6 +101,11 @@ var ColumnBuilder = class {
100
101
  this.state.deprecated = true;
101
102
  return this;
102
103
  }
104
+ /** @internal System cell-metadata part of logical column `of` (generated resolved views only). */
105
+ cellPart(of, part) {
106
+ this.state.cell = { of, part };
107
+ return this;
108
+ }
103
109
  /** Whether `.primaryKey()` was called — collected into the table-level PK. */
104
110
  get isPrimaryKey() {
105
111
  return this.state.primaryKey;
@@ -128,6 +134,7 @@ var ColumnBuilder = class {
128
134
  if (this.state.description !== void 0) column.description = this.state.description;
129
135
  if (this.state.renamedFrom !== void 0) column.renamedFrom = this.state.renamedFrom;
130
136
  if (this.state.cast !== void 0) column.cast = this.state.cast;
137
+ if (this.state.cell !== void 0) column.cell = { ...this.state.cell };
131
138
  return column;
132
139
  }
133
140
  };
@@ -151,15 +158,15 @@ var t = {
151
158
 
152
159
  // src/constants.ts
153
160
  var NAMESPACE_PATTERN = /^[a-z][a-z0-9_]{0,40}$/;
161
+ var ORM_DATA_TOKEN_HEADER = "x-sequence-data-token";
162
+ var ORM_SERVICE_DATA_TOKEN_HEADER = "x-sequence-data-token-service";
154
163
  var IDENTIFIER_PATTERN = /^[a-z][a-z0-9_]*$/;
155
164
  var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
156
165
  var MAX_IDENTIFIER_LENGTH = 63;
157
- var ORM_DATA_TOKEN_HEADER = "x-sequence-data-token";
158
- var ORM_SERVICE_DATA_TOKEN_HEADER = "x-sequence-data-token-service";
159
- var CAPABILITY_KEY_PATTERN = /^(?:org\/)?[a-z0-9-]{1,64}$/;
166
+ var CAPABILITY_KEY_PATTERN = /^(?:org\/|app\/[a-z][a-z0-9-]{0,63}\/)?[a-z0-9-]{1,64}$/;
160
167
  var CLAIM_KEY_PATTERN = /^[a-z][a-z0-9_]*$/;
161
- var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]*\.[a-z][a-z0-9_]*$/;
162
- var SYNCED_SOURCE_PATTERN = /^gold\.[a-z][a-z0-9_]*$/;
168
+ var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]{0,62}\.[a-z][a-z0-9_]{0,62}$/;
169
+ var SYNCED_SOURCE_PATTERN = QUALIFIED_TABLE_PATTERN;
163
170
  var ORM_SCHEMA_PREFIX = "orm_";
164
171
  var CONSUMER_ROLE_SUFFIX = "_consumer";
165
172
  var ormSchemaName = (namespace) => `${ORM_SCHEMA_PREFIX}${namespace}`;
@@ -172,6 +179,27 @@ var MAX_GENERATED_SDL_BYTES = 16 * 1024 * 1024;
172
179
  var ORM_REGISTRATION_MAX_BYTES = 64 * 1024 * 1024;
173
180
  var RESERVED_TABLE_NAMES = /* @__PURE__ */ new Set(["actions", "query"]);
174
181
  var RESERVED_COLUMN_NAMES = /* @__PURE__ */ new Set(["select", "order", "limit", "offset"]);
182
+ var CELL_PARTS = ["at", "by", "rev", "source", "conflicts"];
183
+ var CELL_PART_SUFFIX = {
184
+ at: "__at",
185
+ by: "__by",
186
+ rev: "__rev",
187
+ source: "__src",
188
+ conflicts: "__lost"
189
+ };
190
+ var MAX_TRACKED_COLUMN_NAME = MAX_IDENTIFIER_LENGTH - 6;
191
+ var CELLS_FIELD = "cells";
192
+ function cellColumnName(column, part) {
193
+ return `${column}${CELL_PART_SUFFIX[part]}`;
194
+ }
195
+ var MATERIALIZE_ACTOR = "orm:materialize";
196
+ var MATERIALIZE_CLAIMS_JSON = JSON.stringify({ sub: MATERIALIZE_ACTOR, org: null, roles: [], capabilities: [] });
197
+ function materializedRefreshFunctionName(table) {
198
+ return `${table}__refresh`;
199
+ }
200
+ function materializeLockKey(schema, table) {
201
+ return `orm:materialize:${schema}.${table}`;
202
+ }
175
203
 
176
204
  // src/errors.ts
177
205
  var OrmDefinitionError = class extends Error {
@@ -239,6 +267,9 @@ function registerNamespaceConfig(value) {
239
267
  // src/authoring/table.ts
240
268
  var TABLE_BRAND = "seq-table";
241
269
  var LINK_BRAND = "seq-link";
270
+ function objectRef(namespace, table) {
271
+ return { kind: "seq-external-object", namespace, table };
272
+ }
242
273
  function toOne(target, options) {
243
274
  return {
244
275
  kind: LINK_BRAND,
@@ -247,6 +278,8 @@ function toOne(target, options) {
247
278
  on: options.on,
248
279
  references: options.references,
249
280
  enforce: options.enforce ?? false,
281
+ onDelete: options.onDelete,
282
+ onUpdate: options.onUpdate,
250
283
  description: options.description
251
284
  };
252
285
  }
@@ -258,6 +291,8 @@ function toMany(target, options) {
258
291
  on: options.on,
259
292
  references: options.references,
260
293
  enforce: false,
294
+ onDelete: void 0,
295
+ onUpdate: void 0,
261
296
  description: options.description
262
297
  };
263
298
  }
@@ -269,6 +304,8 @@ function toPlatformUser(options) {
269
304
  on: options.on,
270
305
  references: options.references,
271
306
  enforce: false,
307
+ onDelete: void 0,
308
+ onUpdate: void 0,
272
309
  description: options.description
273
310
  };
274
311
  }
@@ -286,45 +323,51 @@ function assertIdentifier({ value, what, file, pattern = IDENTIFIER_PATTERN }) {
286
323
  );
287
324
  }
288
325
  }
289
- function defineTable(name, columns, config = {}) {
326
+ function defineTable(name2, columns, config = {}) {
290
327
  const file = activeAuthoringContext()?.currentFile;
291
- assertIdentifier({ value: name, what: "table name", file });
328
+ assertIdentifier({ value: name2, what: "table name", file });
292
329
  const entries = Object.entries(columns);
293
330
  if (entries.length === 0) {
294
- throw new OrmDefinitionError(`table "${name}" declares no columns`, file);
331
+ throw new OrmDefinitionError(`table "${name2}" declares no columns`, file);
295
332
  }
296
333
  const seenColumnNames = /* @__PURE__ */ new Map();
297
334
  for (const [propertyName, builder] of entries) {
298
335
  if (!isColumnBuilder(builder)) {
299
336
  throw new OrmDefinitionError(
300
- `table "${name}": column "${propertyName}" is not a column builder \u2014 use the t.* factories (t.uuid(), t.text(), \u2026)`,
337
+ `table "${name2}": column "${propertyName}" is not a column builder \u2014 use the t.* factories (t.uuid(), t.text(), \u2026)`,
301
338
  file
302
339
  );
303
340
  }
304
341
  const columnName = builder.columnNameFor(propertyName);
305
- assertIdentifier({ value: columnName, what: `table "${name}": column "${propertyName}" column name`, file, pattern: COLUMN_IDENTIFIER_PATTERN });
342
+ assertIdentifier({ value: columnName, what: `table "${name2}": column "${propertyName}" column name`, file, pattern: COLUMN_IDENTIFIER_PATTERN });
343
+ if (config.materializes === void 0 && (propertyName === CELLS_FIELD || Object.values(CELL_PART_SUFFIX).some((suffix) => columnName.endsWith(suffix) || propertyName.endsWith(suffix)))) {
344
+ throw new OrmDefinitionError(
345
+ `table "${name2}": "${propertyName}" collides with the reserved cell-metadata names ('${CELLS_FIELD}', or a ${Object.values(CELL_PART_SUFFIX).join("/")} suffix)`,
346
+ file
347
+ );
348
+ }
306
349
  const existing = seenColumnNames.get(columnName);
307
350
  if (existing !== void 0) {
308
351
  throw new OrmDefinitionError(
309
- `table "${name}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
352
+ `table "${name2}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
310
353
  file
311
354
  );
312
355
  }
313
356
  seenColumnNames.set(columnName, propertyName);
314
357
  }
315
- const table = { kind: TABLE_BRAND, tableName: name, columns, config, source: void 0, file };
358
+ const table = { kind: TABLE_BRAND, tableName: name2, columns, config, source: void 0, file };
316
359
  registerTable(table);
317
360
  return table;
318
361
  }
319
- function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
362
+ function defineSyncedTable(name2, columnsOrConfig, maybeConfig) {
320
363
  const file = activeAuthoringContext()?.currentFile;
321
- assertIdentifier({ value: name, what: "table name", file });
364
+ assertIdentifier({ value: name2, what: "table name", file });
322
365
  const isTyped = maybeConfig !== void 0;
323
366
  const columns = isTyped ? columnsOrConfig : {};
324
367
  const config = isTyped ? maybeConfig : columnsOrConfig;
325
368
  if (!SYNCED_SOURCE_PATTERN.test(config.source)) {
326
369
  throw new OrmDefinitionError(
327
- `synced table "${name}": source "${config.source}" must match ${SYNCED_SOURCE_PATTERN} (gold layer only)`,
370
+ `synced table "${name2}": source "${config.source}" must be a qualified lowercase schema.table identifier`,
328
371
  file
329
372
  );
330
373
  }
@@ -334,21 +377,21 @@ function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
334
377
  for (const [propertyName, builder] of entries) {
335
378
  if (!isColumnBuilder(builder)) {
336
379
  throw new OrmDefinitionError(
337
- `synced table "${name}": column "${propertyName}" is not a column builder \u2014 use the t.* factories (t.uuid(), t.text(), \u2026)`,
380
+ `synced table "${name2}": column "${propertyName}" is not a column builder \u2014 use the t.* factories (t.uuid(), t.text(), \u2026)`,
338
381
  file
339
382
  );
340
383
  }
341
384
  const columnName = builder.columnNameFor(propertyName);
342
385
  assertIdentifier({
343
386
  value: columnName,
344
- what: `synced table "${name}": column "${propertyName}" column name`,
387
+ what: `synced table "${name2}": column "${propertyName}" column name`,
345
388
  file,
346
389
  pattern: COLUMN_IDENTIFIER_PATTERN
347
390
  });
348
391
  const existing = seenColumnNames.get(columnName);
349
392
  if (existing !== void 0) {
350
393
  throw new OrmDefinitionError(
351
- `synced table "${name}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
394
+ `synced table "${name2}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
352
395
  file
353
396
  );
354
397
  }
@@ -359,13 +402,13 @@ function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
359
402
  const hasConfigPk = config.primaryKey !== void 0 && config.primaryKey.length > 0;
360
403
  if (columnLevelPk.length === 0 && !hasConfigPk) {
361
404
  throw new OrmDefinitionError(
362
- `synced table "${name}" declares columns but no primary key \u2014 use .primaryKey() or config.primaryKey`,
405
+ `synced table "${name2}" declares columns but no primary key \u2014 use .primaryKey() or config.primaryKey`,
363
406
  file
364
407
  );
365
408
  }
366
409
  if (columnLevelPk.length > 0 && hasConfigPk) {
367
410
  throw new OrmDefinitionError(
368
- `synced table "${name}": declare the primary key either with .primaryKey() on columns or with config.primaryKey \u2014 not both`,
411
+ `synced table "${name2}": declare the primary key either with .primaryKey() on columns or with config.primaryKey \u2014 not both`,
369
412
  file
370
413
  );
371
414
  }
@@ -377,7 +420,7 @@ function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
377
420
  if (config.policies !== void 0) tableConfig.policies = { read: config.policies.read };
378
421
  const table = {
379
422
  kind: TABLE_BRAND,
380
- tableName: name,
423
+ tableName: name2,
381
424
  columns,
382
425
  config: tableConfig,
383
426
  source: config.source,
@@ -386,26 +429,26 @@ function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
386
429
  registerTable(table);
387
430
  return table;
388
431
  }
389
- function defineEnum(name, values) {
432
+ function defineEnum(name2, values) {
390
433
  const file = activeAuthoringContext()?.currentFile;
391
- assertIdentifier({ value: name, what: "enum name", file });
434
+ assertIdentifier({ value: name2, what: "enum name", file });
392
435
  if (values.length === 0 || values.length > 100) {
393
- throw new OrmDefinitionError(`enum "${name}" must declare between 1 and 100 values`, file);
436
+ throw new OrmDefinitionError(`enum "${name2}" must declare between 1 and 100 values`, file);
394
437
  }
395
438
  const seen = /* @__PURE__ */ new Set();
396
439
  for (const value of values) {
397
440
  if (value.length === 0 || value.length > MAX_IDENTIFIER_LENGTH) {
398
441
  throw new OrmDefinitionError(
399
- `enum "${name}": value ${JSON.stringify(value)} must be 1-${MAX_IDENTIFIER_LENGTH} characters`,
442
+ `enum "${name2}": value ${JSON.stringify(value)} must be 1-${MAX_IDENTIFIER_LENGTH} characters`,
400
443
  file
401
444
  );
402
445
  }
403
446
  if (seen.has(value)) {
404
- throw new OrmDefinitionError(`enum "${name}" declares duplicate value "${value}"`, file);
447
+ throw new OrmDefinitionError(`enum "${name2}" declares duplicate value "${value}"`, file);
405
448
  }
406
449
  seen.add(value);
407
450
  }
408
- const ref = { kind: ENUM_BRAND, name, values: [...values] };
451
+ const ref = { kind: ENUM_BRAND, name: name2, values: [...values] };
409
452
  registerEnum(ref);
410
453
  return ref;
411
454
  }
@@ -413,52 +456,52 @@ var VIEW_BRAND = "seq-view";
413
456
  function isSeqView(value) {
414
457
  return typeof value === "object" && value !== null && value.kind === VIEW_BRAND;
415
458
  }
416
- function defineView(name, columns, config) {
459
+ function defineView(name2, columns, config) {
417
460
  const file = activeAuthoringContext()?.currentFile;
418
- if (!IDENTIFIER_PATTERN.test(name) || name.length > MAX_IDENTIFIER_LENGTH) {
461
+ if (!IDENTIFIER_PATTERN.test(name2) || name2.length > MAX_IDENTIFIER_LENGTH) {
419
462
  throw new OrmDefinitionError(
420
- `view name "${name}" must match ${IDENTIFIER_PATTERN} and be at most ${MAX_IDENTIFIER_LENGTH} characters`,
463
+ `view name "${name2}" must match ${IDENTIFIER_PATTERN} and be at most ${MAX_IDENTIFIER_LENGTH} characters`,
421
464
  file
422
465
  );
423
466
  }
424
467
  const entries = Object.entries(columns);
425
- if (entries.length === 0) throw new OrmDefinitionError(`view "${name}" declares no columns`, file);
468
+ if (entries.length === 0) throw new OrmDefinitionError(`view "${name2}" declares no columns`, file);
426
469
  const seenColumnNames = /* @__PURE__ */ new Map();
427
470
  for (const [propertyName, builder] of entries) {
428
471
  if (!isColumnBuilder(builder)) {
429
472
  throw new OrmDefinitionError(
430
- `view "${name}": column "${propertyName}" is not a column builder \u2014 use the t.* factories`,
473
+ `view "${name2}": column "${propertyName}" is not a column builder \u2014 use the t.* factories`,
431
474
  file
432
475
  );
433
476
  }
434
477
  const columnName = builder.columnNameFor(propertyName);
435
478
  if (!COLUMN_IDENTIFIER_PATTERN.test(columnName) || columnName.length > MAX_IDENTIFIER_LENGTH) {
436
- throw new OrmDefinitionError(`view "${name}": column "${propertyName}" has invalid column name "${columnName}"`, file);
479
+ throw new OrmDefinitionError(`view "${name2}": column "${propertyName}" has invalid column name "${columnName}"`, file);
437
480
  }
438
481
  const existing = seenColumnNames.get(columnName);
439
482
  if (existing !== void 0) {
440
483
  throw new OrmDefinitionError(
441
- `view "${name}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
484
+ `view "${name2}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
442
485
  file
443
486
  );
444
487
  }
445
488
  seenColumnNames.set(columnName, propertyName);
446
489
  }
447
- const view = { kind: VIEW_BRAND, viewName: name, columns, config, file };
490
+ const view = { kind: VIEW_BRAND, viewName: name2, columns, config, file };
448
491
  registerView(view);
449
492
  return view;
450
493
  }
451
494
  var QUERY_COLUMN_FACTORIES = {
452
- uuid: (name) => pgCore.uuid(name),
453
- text: (name) => pgCore.text(name),
454
- integer: (name) => pgCore.integer(name),
455
- bigint: (name) => pgCore.bigint(name, { mode: "bigint" }),
456
- numeric: (name) => pgCore.numeric(name),
457
- double: (name) => pgCore.doublePrecision(name),
458
- boolean: (name) => pgCore.boolean(name),
459
- timestamptz: (name) => pgCore.timestamp(name, { withTimezone: true }),
460
- date: (name) => pgCore.date(name),
461
- jsonb: (name) => pgCore.jsonb(name)
495
+ uuid: (name2) => pgCore.uuid(name2),
496
+ text: (name2) => pgCore.text(name2),
497
+ integer: (name2) => pgCore.integer(name2),
498
+ bigint: (name2) => pgCore.bigint(name2, { mode: "bigint" }),
499
+ numeric: (name2) => pgCore.numeric(name2),
500
+ double: (name2) => pgCore.doublePrecision(name2),
501
+ boolean: (name2) => pgCore.boolean(name2),
502
+ timestamptz: (name2) => pgCore.timestamp(name2, { withTimezone: true }),
503
+ date: (name2) => pgCore.date(name2),
504
+ jsonb: (name2) => pgCore.jsonb(name2)
462
505
  };
463
506
  function compileViewQueries(input) {
464
507
  const schemaName = `orm_${input.namespace}`;
@@ -514,9 +557,9 @@ function compileViewQueries(input) {
514
557
  }
515
558
  const handle = handles.get(resolved);
516
559
  if (!handle) {
517
- const name = isSeqTable(resolved) ? resolved.tableName : resolved.viewName;
560
+ const name2 = isSeqTable(resolved) ? resolved.tableName : resolved.viewName;
518
561
  throw new OrmDefinitionError(
519
- `view "${authored.viewName}" references relation "${name}" which is not part of this namespace`,
562
+ `view "${authored.viewName}" references relation "${name2}" which is not part of this namespace`,
520
563
  authored.file
521
564
  );
522
565
  }
@@ -550,11 +593,11 @@ function compileViewQueries(input) {
550
593
  );
551
594
  }
552
595
  if (!config.query) throw new OrmDefinitionError(`view "${authored.viewName}" produced no query`, authored.file);
553
- const tableDependencies = pgCore.extractUsedTable(config.query).map((physical) => {
554
- const logical = physicalToLogical.get(physical);
596
+ const tableDependencies = pgCore.extractUsedTable(config.query).map((physical2) => {
597
+ const logical = physicalToLogical.get(physical2);
555
598
  if (!logical) {
556
599
  throw new OrmDefinitionError(
557
- `view "${authored.viewName}" query references unmanaged relation "${physical}" \u2014 use relation(...) with a namespace table or view`,
600
+ `view "${authored.viewName}" query references unmanaged relation "${physical2}" \u2014 use relation(...) with a namespace table or view`,
558
601
  authored.file
559
602
  );
560
603
  }
@@ -707,8 +750,8 @@ function overlayWinsUnion(input) {
707
750
  // src/authoring/cow-mutations.ts
708
751
  function overrideRowMutation(input) {
709
752
  const { overlay, synced, pk } = input;
710
- const name = input.name ?? `override_${overlay.tableName}`;
711
- assertMutationName(name, "overrideRowMutation");
753
+ const name2 = input.name ?? `override_${overlay.tableName}`;
754
+ assertMutationName(name2, "overrideRowMutation");
712
755
  assertOverlayPairPrimaryKey({ overlay, synced, pk, helper: "overrideRowMutation" });
713
756
  const sharedProperties = sharedNonPkProperties({ overlay, synced, pk });
714
757
  const setProperties = resolveSetProperties({
@@ -727,7 +770,7 @@ function overrideRowMutation(input) {
727
770
  });
728
771
  return {
729
772
  kind: "override",
730
- name,
773
+ name: name2,
731
774
  synced: synced.tableName,
732
775
  pk,
733
776
  copy: [pk, ...sharedProperties],
@@ -736,23 +779,23 @@ function overrideRowMutation(input) {
736
779
  }
737
780
  function revertOverlayMutation(input) {
738
781
  const { overlay, synced, pk } = input;
739
- const name = input.name ?? `revert_${overlay.tableName}`;
740
- assertMutationName(name, "revertOverlayMutation");
782
+ const name2 = input.name ?? `revert_${overlay.tableName}`;
783
+ assertMutationName(name2, "revertOverlayMutation");
741
784
  assertOverlayPairPrimaryKey({ overlay, synced, pk, helper: "revertOverlayMutation" });
742
785
  assertMatchingColumns({ overlay, synced, properties: [pk], helper: "revertOverlayMutation" });
743
786
  return {
744
787
  kind: "revert",
745
- name,
788
+ name: name2,
746
789
  synced: synced.tableName,
747
790
  pk,
748
791
  copy: [],
749
792
  set: []
750
793
  };
751
794
  }
752
- function assertMutationName(name, helper) {
753
- if (!IDENTIFIER_PATTERN.test(name) || name.length > MAX_IDENTIFIER_LENGTH) {
795
+ function assertMutationName(name2, helper) {
796
+ if (!IDENTIFIER_PATTERN.test(name2) || name2.length > MAX_IDENTIFIER_LENGTH) {
754
797
  throw new OrmDefinitionError(
755
- `${helper}: mutation name "${name}" must match ${IDENTIFIER_PATTERN} and be at most ${MAX_IDENTIFIER_LENGTH} characters`
798
+ `${helper}: mutation name "${name2}" must match ${IDENTIFIER_PATTERN} and be at most ${MAX_IDENTIFIER_LENGTH} characters`
756
799
  );
757
800
  }
758
801
  }
@@ -878,9 +921,495 @@ function scalarColumnType(table, property, helper) {
878
921
  }
879
922
  return type;
880
923
  }
924
+ var objectRef2 = zod.z.string().max(255).regex(/^[a-z][a-z0-9_]{0,40}\/[A-Za-z_][A-Za-z0-9_]*$/, "Expected an explicit namespace/object reference (no wildcards)");
925
+ var functionRef = zod.z.string().max(255).regex(/^[a-z][a-z0-9-]*$/, "Expected a function slug");
926
+ var refs = (schema) => zod.z.array(schema).max(256).refine(
927
+ (values) => new Set(values).size === values.length,
928
+ "Duplicate dependency reference"
929
+ ).default([]);
930
+ var ontologyContractSchema = zod.z.strictObject({
931
+ version: zod.z.literal(1),
932
+ read: refs(objectRef2),
933
+ write: refs(objectRef2),
934
+ produce: refs(objectRef2),
935
+ invoke: refs(functionRef)
936
+ }).refine((contract) => JSON.stringify(contract).length <= 4096, "Ontology contract exceeds the 4 KiB signed-context budget");
937
+ function readOntologyContract(manifest) {
938
+ if (!manifest || typeof manifest !== "object" || !("ontology" in manifest) || manifest.ontology === void 0) return void 0;
939
+ return ontologyContractSchema.parse(manifest.ontology);
940
+ }
941
+
942
+ // src/compiler/sql.ts
943
+ var CLAIMS_SETTING = "request.jwt.claims";
944
+ var CAPABILITIES_CLAIM_KEY = "capabilities";
945
+ var ROLES_CLAIM_KEY = "roles";
946
+ var quoteIdentifier = (name2) => `"${name2.replaceAll('"', '""')}"`;
947
+ var quoteLiteral = (value) => `'${value.replaceAll("'", "''")}'`;
948
+ var CLAIMS_SOURCE = `nullif(current_setting('${CLAIMS_SETTING}', true), '')::jsonb`;
949
+ var claimExpression = (key) => `(${CLAIMS_SOURCE} ->> ${quoteLiteral(key)})`;
950
+ var scopedClaimsSource = (namespace) => namespace ? `(CASE WHEN ${CLAIMS_SOURCE} ? 'namespaces' THEN ${CLAIMS_SOURCE} -> 'namespaces' -> ${quoteLiteral(namespace)} ELSE ${CLAIMS_SOURCE} END)` : CLAIMS_SOURCE;
951
+ var capabilityExpression = (key, namespace) => `coalesce(${scopedClaimsSource(namespace)} -> '${CAPABILITIES_CLAIM_KEY}' ? ${quoteLiteral(key)}, false)`;
952
+ var roleExpression = (key, namespace) => `coalesce(${scopedClaimsSource(namespace)} -> '${ROLES_CLAIM_KEY}' ? ${quoteLiteral(key)}, false)`;
953
+
954
+ // src/checks/compile.ts
955
+ function tokenize(source) {
956
+ const tokens = [];
957
+ for (let position = 0; position < source.length; ) {
958
+ const char = source[position];
959
+ if (/\s/.test(char)) {
960
+ position++;
961
+ continue;
962
+ }
963
+ if (char === "(" || char === ")" || char === ",") {
964
+ tokens.push({ kind: char === "(" ? "lparen" : char === ")" ? "rparen" : "comma", value: char, position });
965
+ position++;
966
+ continue;
967
+ }
968
+ if (char === "'") {
969
+ const start = position++;
970
+ let value = "";
971
+ let closed = false;
972
+ while (position < source.length) {
973
+ if (source[position] !== "'") {
974
+ value += source[position++];
975
+ continue;
976
+ }
977
+ if (source[position + 1] === "'") {
978
+ value += "'";
979
+ position += 2;
980
+ continue;
981
+ }
982
+ position++;
983
+ closed = true;
984
+ break;
985
+ }
986
+ if (!closed) throw new Error(`unterminated string at position ${start}`);
987
+ if (/[\x00-\x1f\x7f]/.test(value)) throw new Error(`control character in string at position ${start}`);
988
+ tokens.push({ kind: "string", value, position: start });
989
+ continue;
990
+ }
991
+ const operator = /^(<=|>=|<>|!=|!~|=|<|>|~)/.exec(source.slice(position));
992
+ if (operator) {
993
+ tokens.push({ kind: "operator", value: operator[0] === "<>" ? "!=" : operator[0], position });
994
+ position += operator[0].length;
995
+ continue;
996
+ }
997
+ const number = /^-?\d+(?:\.\d+)?/.exec(source.slice(position));
998
+ if (number) {
999
+ tokens.push({ kind: "number", value: number[0], position });
1000
+ position += number[0].length;
1001
+ continue;
1002
+ }
1003
+ const identifier3 = /^[a-zA-Z_][a-zA-Z_0-9]*/.exec(source.slice(position));
1004
+ if (identifier3) {
1005
+ tokens.push({ kind: "identifier", value: identifier3[0], position });
1006
+ position += identifier3[0].length;
1007
+ continue;
1008
+ }
1009
+ throw new Error(`unexpected character '${char}' at position ${position}`);
1010
+ }
1011
+ tokens.push({ kind: "end", value: "", position: source.length });
1012
+ if (tokens.length > 256) throw new Error("check expression exceeds 256 tokens");
1013
+ return tokens;
1014
+ }
1015
+ var Parser = class {
1016
+ constructor(tokens, columns) {
1017
+ this.tokens = tokens;
1018
+ this.columns = /* @__PURE__ */ new Map();
1019
+ for (const column of columns) {
1020
+ for (const alias of [column.name, column.columnName]) {
1021
+ const previous = this.columns.get(alias);
1022
+ if (previous && previous.columnName !== column.columnName) {
1023
+ throw new Error(`ambiguous check column '${alias}'`);
1024
+ }
1025
+ this.columns.set(alias, column);
1026
+ }
1027
+ }
1028
+ }
1029
+ index = 0;
1030
+ depth = 0;
1031
+ columns;
1032
+ parse() {
1033
+ const expression = this.parseOr();
1034
+ this.expect("end", "end of expression");
1035
+ return expression;
1036
+ }
1037
+ parseOr() {
1038
+ let left = this.parseAnd();
1039
+ while (this.matchKeyword("or")) left = `(${left} OR ${this.parseAnd()})`;
1040
+ return left;
1041
+ }
1042
+ parseAnd() {
1043
+ let left = this.parseNot();
1044
+ while (this.matchKeyword("and")) left = `(${left} AND ${this.parseNot()})`;
1045
+ return left;
1046
+ }
1047
+ parseNot() {
1048
+ if (this.matchKeyword("not")) return `(NOT ${this.parseNot()})`;
1049
+ return this.parsePrimary();
1050
+ }
1051
+ parsePrimary() {
1052
+ if (this.peek().kind === "lparen") {
1053
+ if (++this.depth > 32) throw new Error("check expression nesting exceeds 32 levels");
1054
+ this.advance();
1055
+ const expression = this.parseOr();
1056
+ this.expect("rparen", "')'");
1057
+ this.depth--;
1058
+ return `(${expression})`;
1059
+ }
1060
+ return this.parseComparison();
1061
+ }
1062
+ parseComparison() {
1063
+ const left = this.parseValue();
1064
+ if (this.matchKeyword("is")) {
1065
+ const negate = this.matchKeyword("not");
1066
+ this.expectKeyword("null");
1067
+ return `${left.sql} IS ${negate ? "NOT " : ""}NULL`;
1068
+ }
1069
+ if (this.matchKeyword("between")) {
1070
+ const lower = this.parseValue();
1071
+ this.expectKeyword("and");
1072
+ const upper = this.parseValue();
1073
+ return `${left.sql} BETWEEN ${lower.sql} AND ${upper.sql}`;
1074
+ }
1075
+ if (this.matchKeyword("in")) {
1076
+ this.expect("lparen", "'(' after IN");
1077
+ const values = [this.parseValue()];
1078
+ while (this.peek().kind === "comma") {
1079
+ this.advance();
1080
+ values.push(this.parseValue());
1081
+ }
1082
+ this.expect("rparen", "')' after IN values");
1083
+ if (values.some((value) => !value.literal)) throw new Error("IN values must be literals");
1084
+ return `${left.sql} IN (${values.map((value) => value.sql).join(", ")})`;
1085
+ }
1086
+ const operator = this.expect("operator", "a comparison operator");
1087
+ const right = this.parseValue();
1088
+ if (operator.value === "~" || operator.value === "!~") {
1089
+ if (left.column && left.column.type !== "text") throw new Error("regex checks require a text column");
1090
+ if (!right.literal) throw new Error("regex pattern must be a literal");
1091
+ }
1092
+ return `${left.sql} ${operator.value} ${right.sql}`;
1093
+ }
1094
+ parseValue() {
1095
+ const token = this.advance();
1096
+ if (token.kind === "string") return { sql: quoteLiteral(token.value), literal: true };
1097
+ if (token.kind === "number") {
1098
+ const number = Number(token.value);
1099
+ if (!Number.isFinite(number) || Number.isInteger(number) && !Number.isSafeInteger(number)) {
1100
+ throw new Error(`unsafe numeric literal at position ${token.position}`);
1101
+ }
1102
+ return { sql: token.value, literal: true };
1103
+ }
1104
+ if (token.kind !== "identifier") throw new Error(`expected a column or literal at position ${token.position}`);
1105
+ const keyword = token.value.toLowerCase();
1106
+ if (keyword === "true" || keyword === "false" || keyword === "null") return { sql: keyword.toUpperCase(), literal: true };
1107
+ if (keyword === "btrim") {
1108
+ this.expect("lparen", "'(' after btrim");
1109
+ const column2 = this.parseColumn();
1110
+ this.expect("rparen", "')' after btrim column");
1111
+ if (column2.type !== "text") throw new Error("btrim requires a text column");
1112
+ return { sql: `btrim(${quoteIdentifier(column2.columnName)})`, column: column2 };
1113
+ }
1114
+ const column = this.columns.get(token.value);
1115
+ if (!column) throw new Error(`unknown check column '${token.value}'`);
1116
+ return { sql: quoteIdentifier(column.columnName), column };
1117
+ }
1118
+ parseColumn() {
1119
+ const token = this.expect("identifier", "a column name");
1120
+ const column = this.columns.get(token.value);
1121
+ if (!column) throw new Error(`unknown check column '${token.value}'`);
1122
+ return column;
1123
+ }
1124
+ peek() {
1125
+ return this.tokens[this.index];
1126
+ }
1127
+ advance() {
1128
+ const token = this.peek();
1129
+ if (token.kind !== "end") this.index++;
1130
+ return token;
1131
+ }
1132
+ matchKeyword(keyword) {
1133
+ if (this.peek().kind !== "identifier" || this.peek().value.toLowerCase() !== keyword) return false;
1134
+ this.advance();
1135
+ return true;
1136
+ }
1137
+ expectKeyword(keyword) {
1138
+ if (!this.matchKeyword(keyword)) throw new Error(`expected '${keyword}' at position ${this.peek().position}`);
1139
+ }
1140
+ expect(kind, description) {
1141
+ const token = this.peek();
1142
+ if (token.kind !== kind) throw new Error(`expected ${description} at position ${token.position}`);
1143
+ return this.advance();
1144
+ }
1145
+ };
1146
+ function compileCheckExpression(source, columns) {
1147
+ return new Parser(tokenize(source), columns).parse();
1148
+ }
1149
+ var policyVariantSchema = zod.z.object({
1150
+ /** Claims-only guard (capability()/role()/claim() comparisons — no columns). */
1151
+ when: zod.z.string().min(1).max(1e3),
1152
+ /** Exactly one of: allow-all bypass, or a row predicate. */
1153
+ allow: zod.z.literal(true).optional(),
1154
+ policy: zod.z.string().min(1).max(2e3).optional()
1155
+ }).refine((v) => v.allow === true !== (typeof v.policy === "string"), {
1156
+ message: "A variant declares exactly one of allow: true or policy"
1157
+ });
1158
+ var policyExpressionSchema = zod.z.union([
1159
+ zod.z.string().min(1).max(2e3),
1160
+ zod.z.object({
1161
+ default: zod.z.string().min(1).max(2e3),
1162
+ variants: zod.z.array(policyVariantSchema).max(10).default([])
1163
+ })
1164
+ ]);
1165
+ function parseDurationMs(input) {
1166
+ if (typeof input === "number") return Number.isInteger(input) && input > 0 ? input : void 0;
1167
+ const match = /^(\d+(?:\.\d+)?)(ms|s|m)$/.exec(input);
1168
+ if (!match) return void 0;
1169
+ const factor = match[2] === "ms" ? 1 : match[2] === "s" ? 1e3 : 6e4;
1170
+ const value = Math.round(Number(match[1]) * factor);
1171
+ return value > 0 ? value : void 0;
1172
+ }
1173
+ var identifier = zod.z.string().min(1).max(63).regex(/^[a-z][a-z0-9_]*$/);
1174
+ var durationMs = zod.z.number().int().positive().max(24 * 60 * 60 * 1e3);
1175
+ var RESOLVED_VIEW_SYSTEM_COLUMNS = {
1176
+ writebackStatus: { name: "writebackStatus", columnName: "_writeback_status", type: "text" },
1177
+ writebackError: { name: "writebackError", columnName: "_writeback_error", type: "text" }
1178
+ };
1179
+ var RESOLVED_SOURCE_SYSTEM_COLUMNS = {
1180
+ tombstoned: { name: "sysTombstoned", columnName: "_tombstoned" },
1181
+ fetchedAt: { name: "sysFetchedAt", columnName: "_fetched_at" },
1182
+ writebackStatus: { name: "sysWritebackStatus", columnName: "_writeback_status" },
1183
+ writebackError: { name: "sysWritebackError", columnName: "_writeback_error" },
1184
+ writebackAt: { name: "sysWritebackAt", columnName: "_writeback_at" }
1185
+ };
1186
+ var RESOLVED_VIEW_CELL_PARTS = ["source", "at", "by", "conflicts"];
1187
+ var RESERVED_RESOLVED_COLUMN_NAMES = /* @__PURE__ */ new Set([
1188
+ ...Object.values(RESOLVED_VIEW_SYSTEM_COLUMNS).flatMap((c) => [c.name, c.columnName]),
1189
+ ...Object.values(RESOLVED_SOURCE_SYSTEM_COLUMNS).flatMap((c) => [c.name, c.columnName])
1190
+ ]);
1191
+ var COMPARATORS = ["latest", "highest", "lowest", "priority", "nonNullFirst", "custom"];
1192
+ var canonicalFunctionRefSchema = zod.z.object({
1193
+ /** `managed` = a Managed Function id/slug; `native` = an in-process Atlas function (reserved). */
1194
+ kind: zod.z.enum(["managed", "native"]),
1195
+ id: zod.z.string().min(1).max(200)
1196
+ }).strict();
1197
+ var canonicalFetchShapeSchema = zod.z.union([
1198
+ zod.z.object({ shape: zod.z.literal("single") }).strict(),
1199
+ zod.z.object({ shape: zod.z.literal("batch"), maxKeys: zod.z.number().int().min(2).max(500) }).strict()
1200
+ ]);
1201
+ var canonicalResolvedSourceSchema = zod.z.object({
1202
+ name: identifier,
1203
+ kind: zod.z.enum(["manual", "etl", "prefetch"]),
1204
+ /** Backing relation in this namespace: an internal table (manual/prefetch) or a synced table (etl). */
1205
+ table: identifier,
1206
+ /** Resolved columns (logical names) this source supplies — the primary key is implicit. */
1207
+ columns: zod.z.array(zod.z.string().min(1).max(63)).min(1).max(500),
1208
+ /** etl: logical timestamptz/date column on the synced table used as every cell's updated_at. */
1209
+ updatedAt: zod.z.string().min(1).max(63).optional(),
1210
+ /** prefetch: resolved column holding the source system's key (may be the primary key). */
1211
+ key: zod.z.string().min(1).max(63).optional(),
1212
+ fetch: canonicalFetchShapeSchema.optional(),
1213
+ function: canonicalFunctionRefSchema.optional(),
1214
+ prefetchDefaults: zod.z.object({
1215
+ maxStalenessMs: durationMs,
1216
+ timeoutMs: durationMs,
1217
+ onError: zod.z.enum(["fallback", "fail"])
1218
+ }).strict().optional(),
1219
+ writeback: zod.z.object({
1220
+ columns: zod.z.array(zod.z.string().min(1).max(63)).min(1).max(500),
1221
+ defaultMode: zod.z.enum(["sync", "async"]),
1222
+ timeoutMs: durationMs,
1223
+ /** Remove confirmed cells from the manual source so a stale edit cannot shadow the source. */
1224
+ clearManualOnConfirm: zod.z.boolean()
1225
+ }).strict().optional()
1226
+ }).strict();
1227
+ var canonicalResolveRuleSchema = zod.z.object({
1228
+ column: zod.z.string().min(1).max(63),
1229
+ comparator: zod.z.enum(COMPARATORS),
1230
+ /** Source names, in tie-break order. */
1231
+ candidates: zod.z.array(identifier).min(1).max(16),
1232
+ /** custom only: SQL ORDER BY key over value / updated_at / source / actor / ord. */
1233
+ orderBy: zod.z.string().min(1).max(2e3).optional()
1234
+ }).strict();
1235
+ var canonicalResolvedSchema = zod.z.object({
1236
+ sources: zod.z.array(canonicalResolvedSourceSchema).min(1).max(16),
1237
+ /** One rule per resolved (non-pk) column; documents the generated view for tooling. */
1238
+ resolve: zod.z.array(canonicalResolveRuleSchema).max(500),
1239
+ /** Sources whose tombstone hides the resolved row (v1: the manual source only). */
1240
+ deletedBy: zod.z.array(identifier).max(1).default([]),
1241
+ /**
1242
+ * Materialized: the view reads an indexed internal table (`table`) that the
1243
+ * data plane keeps in step with `compute`, the internal view holding the
1244
+ * resolution query. Omitted when the view computes on read.
1245
+ */
1246
+ materialized: zod.z.object({ table: identifier, compute: identifier }).strict().optional(),
1247
+ /** Write policies evaluated against the RESOLVED row (reads use the view's read policy). */
1248
+ policies: zod.z.object({
1249
+ write: policyExpressionSchema.optional(),
1250
+ insert: policyExpressionSchema.optional(),
1251
+ update: policyExpressionSchema.optional()
1252
+ }).strict().default({})
1253
+ }).strict();
1254
+ function validateResolvedView({ view, tables, views = [] }) {
1255
+ const resolved = view.resolved;
1256
+ if (!resolved) return [];
1257
+ const errors = [];
1258
+ const fail = (message) => errors.push(`resolved table '${view.view}': ${message}`);
1259
+ const pkColumn = view.primaryKey?.length === 1 ? view.columns.find((c) => c.columnName === view.primaryKey?.[0]) : void 0;
1260
+ if (!pkColumn) {
1261
+ fail("requires exactly one primary-key column");
1262
+ return errors;
1263
+ }
1264
+ const systemNames = new Set(Object.values(RESOLVED_VIEW_SYSTEM_COLUMNS).map((c) => c.name));
1265
+ const scope = {
1266
+ view,
1267
+ resolved,
1268
+ pkColumn,
1269
+ dataColumns: new Map(view.columns.filter((c) => !systemNames.has(c.name) && c.cell === void 0).map((c) => [c.name, c])),
1270
+ sourceByName: /* @__PURE__ */ new Map(),
1271
+ fail
1272
+ };
1273
+ if (resolved.sources.filter((source) => source.kind === "manual").length !== 1) fail("requires exactly one manual source");
1274
+ const tableByName = new Map(tables.map((table) => [table.table, table]));
1275
+ for (const source of resolved.sources) {
1276
+ if (scope.sourceByName.has(source.name)) fail(`duplicate source '${source.name}'`);
1277
+ scope.sourceByName.set(source.name, source);
1278
+ const table = tableByName.get(source.table);
1279
+ if (!table) fail(`source '${source.name}' references unknown table '${source.table}'`);
1280
+ else validateSource({ scope, source, table });
1281
+ }
1282
+ validateRules(scope);
1283
+ validateViewCells(scope);
1284
+ if (resolved.materialized) validateMaterialized({ scope, tableByName, views });
1285
+ for (const name2 of resolved.deletedBy) {
1286
+ if (scope.sourceByName.get(name2)?.kind !== "manual") fail(`deletedBy '${name2}' must be the manual source`);
1287
+ }
1288
+ return errors;
1289
+ }
1290
+ function validateSource({ scope, source, table }) {
1291
+ const { fail } = scope;
1292
+ if (source.kind === "etl" && table.source === void 0) fail(`etl source '${source.name}' must reference a synced table`);
1293
+ if (source.kind !== "etl" && (table.internal !== true || table.cellMetadata !== true)) {
1294
+ fail(`${source.kind} source '${source.name}' must reference an internal table with cell metadata`);
1295
+ }
1296
+ const hasRev = table.columns.some((column) => column.cell?.part === "rev");
1297
+ if (source.kind === "manual" && !hasRev) fail(`manual source '${source.name}' must carry '__rev' cell columns`);
1298
+ if (source.kind === "prefetch" && hasRev) fail(`prefetch source '${source.name}' must not carry '__rev' cell columns`);
1299
+ const byName = new Map(table.columns.map((c) => [c.name, c]));
1300
+ validateSourceColumns({ scope, source, byName });
1301
+ if (source.kind === "etl") {
1302
+ const updatedAt = source.updatedAt === void 0 ? void 0 : byName.get(source.updatedAt);
1303
+ if (source.updatedAt !== void 0 && updatedAt?.type !== "timestamptz" && updatedAt?.type !== "date") {
1304
+ fail(`etl source '${source.name}' updatedAt must name a timestamptz/date column on '${source.table}'`);
1305
+ }
1306
+ }
1307
+ validatePrefetchFields({ scope, source });
1308
+ }
1309
+ function validatePrefetchFields({ scope, source }) {
1310
+ const { fail, pkColumn } = scope;
1311
+ if (source.kind !== "prefetch") {
1312
+ if (source.key || source.fetch || source.function || source.prefetchDefaults || source.writeback) {
1313
+ fail(`only prefetch sources may declare key/fetch/function/prefetchDefaults/writeback ('${source.name}')`);
1314
+ }
1315
+ return;
1316
+ }
1317
+ if (!source.key || !source.fetch || !source.function || !source.prefetchDefaults) {
1318
+ fail(`prefetch source '${source.name}' requires key, fetch, function and prefetchDefaults`);
1319
+ } else if (source.key !== pkColumn.name && !source.columns.includes(source.key)) {
1320
+ fail(`prefetch source '${source.name}' key '${source.key}' must be the primary key or one of its columns`);
1321
+ }
1322
+ for (const column of source.writeback?.columns ?? []) {
1323
+ if (!source.columns.includes(column)) fail(`prefetch source '${source.name}' writes back '${column}', which it does not supply`);
1324
+ }
1325
+ }
1326
+ function validateSourceColumns({ scope, source, byName }) {
1327
+ const { fail, pkColumn, dataColumns } = scope;
1328
+ for (const column of [pkColumn.name, ...source.columns]) {
1329
+ const own = column === pkColumn.name ? pkColumn : dataColumns.get(column);
1330
+ const theirs = byName.get(column);
1331
+ if (!own) fail(`source '${source.name}' supplies unknown column '${column}'`);
1332
+ else if (!theirs) fail(`source '${source.name}' table '${source.table}' has no column '${column}'`);
1333
+ else if (JSON.stringify(theirs.type) !== JSON.stringify(own.type)) fail(`column '${column}' has a different type on source '${source.name}'`);
1334
+ }
1335
+ }
1336
+ function validateRules({ resolved, pkColumn, dataColumns, sourceByName, fail }) {
1337
+ const ruled = /* @__PURE__ */ new Set();
1338
+ for (const rule of resolved.resolve) {
1339
+ if (!dataColumns.has(rule.column) || rule.column === pkColumn.name) {
1340
+ fail(`resolve rule targets unknown column '${rule.column}'`);
1341
+ continue;
1342
+ }
1343
+ if (ruled.has(rule.column)) fail(`column '${rule.column}' has more than one resolve rule`);
1344
+ ruled.add(rule.column);
1345
+ if (rule.comparator === "custom" !== (rule.orderBy !== void 0)) {
1346
+ fail(`column '${rule.column}': orderBy is required for custom comparators and only allowed there`);
1347
+ }
1348
+ if (new Set(rule.candidates).size !== rule.candidates.length) fail(`column '${rule.column}' repeats a candidate`);
1349
+ for (const candidate of rule.candidates) {
1350
+ const source = sourceByName.get(candidate);
1351
+ if (!source) fail(`column '${rule.column}' names unknown candidate source '${candidate}'`);
1352
+ else if (!source.columns.includes(rule.column)) fail(`column '${rule.column}' candidate '${candidate}' does not supply it`);
1353
+ }
1354
+ }
1355
+ for (const column of dataColumns.keys()) {
1356
+ if (column !== pkColumn.name && !ruled.has(column)) fail(`column '${column}' has no resolve rule`);
1357
+ }
1358
+ }
1359
+ var VIEW_CELL_TYPES = {
1360
+ source: "text",
1361
+ at: "timestamptz",
1362
+ by: "text",
1363
+ conflicts: "jsonb"
1364
+ };
1365
+ function validateViewCells({ view, dataColumns, pkColumn, fail }) {
1366
+ const expected = /* @__PURE__ */ new Map();
1367
+ for (const column of dataColumns.values()) {
1368
+ if (column.name === pkColumn.name) continue;
1369
+ for (const part of RESOLVED_VIEW_CELL_PARTS) {
1370
+ expected.set(cellColumnName(column.name, part), { part, columnName: cellColumnName(column.columnName, part) });
1371
+ }
1372
+ }
1373
+ for (const column of view.columns) {
1374
+ if (!column.cell) continue;
1375
+ const want = expected.get(column.name);
1376
+ if (!want || want.part !== column.cell.part || want.columnName !== column.columnName || cellColumnName(column.cell.of, column.cell.part) !== column.name) {
1377
+ fail(`unexpected cell column '${column.name}'`);
1378
+ continue;
1379
+ }
1380
+ if (column.type !== VIEW_CELL_TYPES[column.cell.part]) fail(`cell column '${column.name}' must be ${VIEW_CELL_TYPES[column.cell.part]}`);
1381
+ expected.delete(column.name);
1382
+ }
1383
+ for (const name2 of expected.keys()) fail(`missing cell column '${name2}'`);
1384
+ }
1385
+ var columnSignature = (columns) => JSON.stringify(columns.map((column) => [column.name, column.columnName, column.type]));
1386
+ function validateMaterialized({ scope, tableByName, views }) {
1387
+ const { view, resolved, fail } = scope;
1388
+ const { table: tableName, compute: computeName } = resolved.materialized;
1389
+ const table = tableByName.get(tableName);
1390
+ if (!table || table.internal !== true || table.materializes !== view.view) {
1391
+ fail(`materialized table '${tableName}' must be an internal table that materializes '${view.view}'`);
1392
+ } else {
1393
+ if (columnSignature(table.columns) !== columnSignature(view.columns)) fail(`materialized table '${tableName}' must have exactly the view's columns`);
1394
+ if (JSON.stringify(table.primaryKey ?? []) !== JSON.stringify(view.primaryKey ?? [])) fail(`materialized table '${tableName}' must share the view's primary key`);
1395
+ }
1396
+ const compute = views.find((candidate) => candidate.view === computeName);
1397
+ if (!compute || compute.internal !== true) fail(`compute view '${computeName}' must be an internal view`);
1398
+ else if (columnSignature(compute.columns) !== columnSignature(view.columns)) fail(`compute view '${computeName}' must have exactly the view's columns`);
1399
+ for (const source of resolved.sources) {
1400
+ if (source.kind !== "etl") continue;
1401
+ const read = tableByName.get(source.table)?.policies?.read;
1402
+ const unconditional = typeof read === "string" ? read.trim() === "true" : read !== void 0 && read.default.trim() === "true" && read.variants.length === 0;
1403
+ if (!unconditional) {
1404
+ fail(`etl source '${source.name}' must have read policy 'true' to be materialized (the copy is shared by every caller)`);
1405
+ }
1406
+ }
1407
+ }
1408
+
1409
+ // src/canonical/v2.ts
881
1410
  var CANONICAL_FORMAT = 2;
882
1411
  var IDENTIFIER_RE = /^[a-z][a-z0-9_]*$/;
883
- var identifier = zod.z.string().min(1).max(63).regex(IDENTIFIER_RE);
1412
+ var identifier2 = zod.z.string().min(1).max(63).regex(IDENTIFIER_RE);
884
1413
  var columnIdentifier = zod.z.string().min(1).max(63).regex(COLUMN_IDENTIFIER_PATTERN);
885
1414
  var COLUMN_TYPES = [
886
1415
  "uuid",
@@ -896,14 +1425,14 @@ var COLUMN_TYPES = [
896
1425
  ];
897
1426
  var columnTypeSchema = zod.z.enum(COLUMN_TYPES);
898
1427
  var canonicalEnumSchema = zod.z.object({
899
- name: identifier,
1428
+ name: identifier2,
900
1429
  values: zod.z.array(zod.z.string().min(1).max(63)).min(1).max(100)
901
1430
  });
902
1431
  var canonicalColumnSchema = zod.z.object({
903
1432
  /** Property name in TS (camelCase); columnName is the Postgres name. */
904
1433
  name: zod.z.string().min(1).max(63),
905
1434
  columnName: columnIdentifier,
906
- type: zod.z.union([columnTypeSchema, zod.z.object({ enum: identifier })]),
1435
+ type: zod.z.union([columnTypeSchema, zod.z.object({ enum: identifier2 })]),
907
1436
  nullable: zod.z.boolean().default(true),
908
1437
  unique: zod.z.boolean().default(false),
909
1438
  /** Literal default, or one of the allowlisted SQL functions. */
@@ -919,7 +1448,14 @@ var canonicalColumnSchema = zod.z.object({
919
1448
  /** Migration hints — same semantics as v1. */
920
1449
  renamedFrom: columnIdentifier.optional(),
921
1450
  cast: zod.z.string().max(200).optional(),
922
- deprecated: zod.z.boolean().default(false)
1451
+ deprecated: zod.z.boolean().default(false),
1452
+ /**
1453
+ * Marks a system cell-metadata column (see CELL_PARTS in constants): part
1454
+ * `part` of the cell of logical column `of`. Synthesized by authoring,
1455
+ * never client-writable, absent from GraphQL scalar fields. Omitted on
1456
+ * ordinary columns so their hashes are unchanged.
1457
+ */
1458
+ cell: zod.z.object({ of: zod.z.string().min(1).max(63), part: zod.z.enum(CELL_PARTS) }).strict().optional()
923
1459
  }).superRefine((column, ctx) => {
924
1460
  if (column.onUpdateFn === void 0) return;
925
1461
  if (typeof column.type !== "string" || column.type !== "timestamptz" && column.type !== "date") {
@@ -934,36 +1470,28 @@ var canonicalLinkSchema = zod.z.object({
934
1470
  name: zod.z.string().min(1).max(63),
935
1471
  kind: zod.z.enum(["toOne", "toMany", "toPlatformUser"]),
936
1472
  /** Target table name (namespace-local). Absent for toPlatformUser. */
937
- target: identifier.optional(),
1473
+ target: identifier2.optional(),
1474
+ /** Owning namespace of an imported target; omitted for local links. */
1475
+ namespace: identifier2.max(41).optional(),
938
1476
  /** FK column on the owning side (toOne/toPlatformUser: on this table; toMany: on the target). */
939
1477
  on: columnIdentifier,
940
1478
  /** Referenced column on the target (default: target's PK / platform user `sub`). */
941
1479
  references: columnIdentifier.optional(),
942
1480
  /** enforce: true emits a real FK constraint (same-namespace toOne only). */
943
1481
  enforce: zod.z.boolean().default(false),
1482
+ onDelete: zod.z.enum(["cascade", "restrict", "no action", "set null", "set default"]).optional(),
1483
+ onUpdate: zod.z.enum(["cascade", "restrict", "no action", "set null", "set default"]).optional(),
944
1484
  description: zod.z.string().max(500).optional()
945
1485
  });
946
1486
  var canonicalIndexSchema = zod.z.object({
947
1487
  columns: zod.z.array(columnIdentifier).min(1),
948
1488
  unique: zod.z.boolean().default(false),
949
- name: identifier.optional()
950
- });
951
- var policyVariantSchema = zod.z.object({
952
- /** Claims-only guard (capability()/role()/claim() comparisons — no columns). */
953
- when: zod.z.string().min(1).max(1e3),
954
- /** Exactly one of: allow-all bypass, or a row predicate. */
955
- allow: zod.z.literal(true).optional(),
956
- policy: zod.z.string().min(1).max(2e3).optional()
957
- }).refine((v) => v.allow === true !== (typeof v.policy === "string"), {
958
- message: "A variant declares exactly one of allow: true or policy"
1489
+ name: identifier2.optional()
959
1490
  });
960
- var policyExpressionSchema = zod.z.union([
961
- zod.z.string().min(1).max(2e3),
962
- zod.z.object({
963
- default: zod.z.string().min(1).max(2e3),
964
- variants: zod.z.array(policyVariantSchema).max(10).default([])
965
- })
966
- ]);
1491
+ var canonicalCheckSchema = zod.z.object({
1492
+ name: identifier2,
1493
+ expression: zod.z.string().min(1).max(2e3)
1494
+ }).strict();
967
1495
  var canonicalPoliciesSchema = zod.z.object({
968
1496
  read: policyExpressionSchema.optional(),
969
1497
  write: policyExpressionSchema.optional(),
@@ -1151,7 +1679,7 @@ var VIEW_QUERY_FUNCTION_ALLOWLIST = [
1151
1679
  "width_bucket"
1152
1680
  ];
1153
1681
  var canonicalActionSchema = zod.z.object({
1154
- name: identifier,
1682
+ name: identifier2,
1155
1683
  requires: zod.z.string().max(1e3).optional(),
1156
1684
  params: zod.z.record(zod.z.string(), zod.z.object({
1157
1685
  type: columnTypeSchema,
@@ -1163,9 +1691,9 @@ var canonicalActionSchema = zod.z.object({
1163
1691
  });
1164
1692
  var canonicalCowMutationSchema = zod.z.object({
1165
1693
  kind: zod.z.enum(["override", "revert"]),
1166
- name: identifier,
1694
+ name: identifier2,
1167
1695
  /** Synced table (same namespace) the overlay copies from / falls back to. */
1168
- synced: identifier,
1696
+ synced: identifier2,
1169
1697
  /** Single-column primary key PROPERTY (TS camelCase, like column `name`) shared by overlay and synced. */
1170
1698
  pk: zod.z.string().min(1).max(63),
1171
1699
  /** Override: contract properties copied INSERT…SELECT from synced (pk included). Empty for revert. */
@@ -1193,23 +1721,28 @@ var canonicalCowMutationSchema = zod.z.object({
1193
1721
  }
1194
1722
  });
1195
1723
  var canonicalForeignKeySchema = zod.z.object({
1196
- name: identifier,
1724
+ name: identifier2,
1197
1725
  columns: zod.z.array(columnIdentifier).min(1).max(8),
1198
- target: identifier,
1726
+ target: identifier2,
1199
1727
  references: zod.z.array(columnIdentifier).min(1).max(8),
1200
- deferrable: zod.z.enum(["initially-immediate", "initially-deferred"]).optional()
1728
+ deferrable: zod.z.enum(["initially-immediate", "initially-deferred"]).optional(),
1729
+ onDelete: zod.z.enum(["cascade", "restrict", "no action", "set null", "set default"]).optional(),
1730
+ onUpdate: zod.z.enum(["cascade", "restrict", "no action", "set null", "set default"]).optional()
1201
1731
  }).strict();
1202
1732
  var canonicalTableSchema = zod.z.object({
1203
- table: identifier,
1733
+ table: identifier2,
1734
+ displayName: zod.z.string().trim().min(1).max(120).optional(),
1204
1735
  description: zod.z.string().max(2e3).optional(),
1205
1736
  /** Table-level rename hint (new in v2). */
1206
- renamedFrom: identifier.optional(),
1207
- /** Synced/reflected read-only tables (`source: gold.*`) — v1 semantics. */
1208
- source: zod.z.string().regex(/^gold\.[a-z][a-z0-9_]*$/).optional(),
1737
+ renamedFrom: identifier2.optional(),
1738
+ /** Synced/reflected read-only tables (`source: schema.table`) — v1 semantics. */
1739
+ source: zod.z.string().regex(QUALIFIED_TABLE_PATTERN).optional(),
1209
1740
  columns: zod.z.array(canonicalColumnSchema).default([]),
1210
1741
  primaryKey: zod.z.array(columnIdentifier).min(1).max(8).optional(),
1211
1742
  links: zod.z.array(canonicalLinkSchema).default([]),
1212
1743
  indexes: zod.z.array(canonicalIndexSchema).default([]),
1744
+ /** Omitted by old definitions, preserving their existing content hashes. */
1745
+ checks: zod.z.array(canonicalCheckSchema).max(100).optional(),
1213
1746
  policies: canonicalPoliciesSchema.default({}),
1214
1747
  /**
1215
1748
  * v1-conversion carry-over only. v2 has no action surface — the runtime
@@ -1220,22 +1753,55 @@ var canonicalTableSchema = zod.z.object({
1220
1753
  /** Optional (not defaulted) so pre-CoW canonical documents keep byte-identical hashes. */
1221
1754
  cowMutations: zod.z.array(canonicalCowMutationSchema).optional(),
1222
1755
  /** Omitted by old definitions, preserving their existing content hashes. */
1223
- foreignKeys: zod.z.array(canonicalForeignKeySchema).max(100).optional()
1756
+ foreignKeys: zod.z.array(canonicalForeignKeySchema).max(100).optional(),
1757
+ /**
1758
+ * Per-cell edit metadata (`cellMetadata: true` in authoring): every tracked
1759
+ * column `c` carries typed `c__at` / `c__by` system columns (plus `c__rev`
1760
+ * on a resolved table's internal sources), listed explicitly in `columns`
1761
+ * with a `cell` marker so DDL and migrations treat them as ordinary columns.
1762
+ * Omitted when off so existing definitions hash unchanged.
1763
+ */
1764
+ cellMetadata: zod.z.literal(true).optional(),
1765
+ /**
1766
+ * System-owned backing table of a resolved table (its manual or prefetch
1767
+ * source). Internal tables get DDL and IR metadata but no GraphQL surface;
1768
+ * only the data plane writes them. Omitted when false.
1769
+ */
1770
+ internal: zod.z.literal(true).optional(),
1771
+ /**
1772
+ * The materialized copy of resolved table `materializes` (internal): the
1773
+ * same columns as that view, including its cell parts, maintained by the
1774
+ * data plane. Omitted otherwise.
1775
+ */
1776
+ materializes: identifier2.optional()
1224
1777
  });
1778
+ var MAX_VIEW_QUERY = 1e5;
1779
+ var MAX_RESOLVED_VIEW_QUERY = 5e6;
1225
1780
  var canonicalViewSchema = zod.z.object({
1226
- view: identifier,
1781
+ view: identifier2,
1227
1782
  description: zod.z.string().max(2e3).optional(),
1228
1783
  columns: zod.z.array(canonicalColumnSchema).min(1),
1229
1784
  /** Logical identity for GraphQL only; PostgreSQL views have no PK constraint. */
1230
1785
  primaryKey: zod.z.array(columnIdentifier).min(1).max(8).optional(),
1231
1786
  links: zod.z.array(canonicalLinkSchema).default([]),
1232
1787
  policies: zod.z.object({ read: policyExpressionSchema }).strict(),
1233
- query: zod.z.string().min(1).max(1e5).refine(isSingleViewQuerySql, {
1788
+ query: zod.z.string().min(1).max(MAX_RESOLVED_VIEW_QUERY).refine(isSingleViewQuerySql, {
1234
1789
  message: "view query must be one SELECT/WITH statement without comments or dollar-quoted bodies"
1235
1790
  }),
1236
1791
  /** Namespace-local logical relation names, sorted for deterministic hashing. */
1237
- dependencies: zod.z.array(identifier).max(500)
1792
+ dependencies: zod.z.array(identifier2).max(500),
1793
+ /**
1794
+ * Present when this view is a resolved table (defineResolvedTable): the
1795
+ * multi-source metadata behind its generated query. Omitted otherwise so
1796
+ * existing views hash unchanged.
1797
+ */
1798
+ resolved: canonicalResolvedSchema.optional(),
1799
+ /** System-owned view (a materialized resolved table's compute view): no GraphQL surface. */
1800
+ internal: zod.z.literal(true).optional()
1238
1801
  }).superRefine((view, ctx) => {
1802
+ if (view.resolved === void 0 && view.query.length > MAX_VIEW_QUERY) {
1803
+ ctx.addIssue({ code: "custom", path: ["query"], message: `view query must be at most ${MAX_VIEW_QUERY} characters` });
1804
+ }
1239
1805
  for (const [index3, column] of view.columns.entries()) {
1240
1806
  if (column.default !== void 0 || column.defaultFn !== void 0) {
1241
1807
  ctx.addIssue({ code: "custom", path: ["columns", index3], message: `view column '${column.name}' cannot declare a default` });
@@ -1250,20 +1816,91 @@ var canonicalViewSchema = zod.z.object({
1250
1816
  ctx.addIssue({ code: "custom", path: ["columns", index3], message: `view column '${column.name}' cannot declare migration hints` });
1251
1817
  }
1252
1818
  }
1819
+ if (view.resolved === void 0 && view.internal !== true) {
1820
+ for (const [index3, column] of view.columns.entries()) {
1821
+ if (column.cell) ctx.addIssue({ code: "custom", path: ["columns", index3], message: `only resolved tables carry cell columns ('${column.name}')` });
1822
+ }
1823
+ }
1253
1824
  for (const [index3, link] of view.links.entries()) {
1254
1825
  if (link.enforce) {
1255
1826
  ctx.addIssue({ code: "custom", path: ["links", index3, "enforce"], message: `view link '${link.name}' cannot enforce a foreign key` });
1256
1827
  }
1828
+ if (link.onDelete !== void 0 || link.onUpdate !== void 0) {
1829
+ ctx.addIssue({ code: "custom", path: ["links", index3], message: `view link '${link.name}' cannot declare referential actions` });
1830
+ }
1257
1831
  }
1258
1832
  });
1259
- var canonicalDefinitionSchema = zod.z.object({
1833
+ var CELL_PART_TYPES = {
1834
+ at: "timestamptz",
1835
+ by: "text",
1836
+ rev: "uuid",
1837
+ source: "text",
1838
+ conflicts: "jsonb"
1839
+ };
1840
+ function trackedCellColumns(table) {
1841
+ const pk = new Set(table.primaryKey ?? []);
1842
+ return table.columns.filter((column) => column.cell === void 0 && !pk.has(column.columnName) && column.onUpdateFn === void 0);
1843
+ }
1844
+ var SUFFIXES = Object.values(CELL_PART_SUFFIX);
1845
+ function validateCellMetadata(table, index3, ctx) {
1846
+ const fail = (message, path = ["tables", index3]) => ctx.addIssue({ code: "custom", path, message });
1847
+ const cells = table.columns.flatMap((column, position) => column.cell ? [{ column, position, cell: column.cell }] : []);
1848
+ if (table.materializes !== void 0) return;
1849
+ if (table.cellMetadata !== true) {
1850
+ for (const { position } of cells) fail(`cell-metadata columns require cellMetadata on table '${table.table}'`, ["tables", index3, "columns", position]);
1851
+ return;
1852
+ }
1853
+ if (table.source !== void 0) {
1854
+ fail(`synced table '${table.table}' cannot track cell metadata`);
1855
+ return;
1856
+ }
1857
+ const tracked = trackedCellColumns(table);
1858
+ const byName = new Map(tracked.map((column) => [column.name, column]));
1859
+ const withRev = cells.some(({ cell }) => cell.part === "rev");
1860
+ if (withRev && table.internal !== true) fail(`only a resolved table's internal sources carry '__rev' cell columns ('${table.table}')`);
1861
+ const seen = /* @__PURE__ */ new Set();
1862
+ for (const { column, position, cell } of cells) {
1863
+ const path = ["tables", index3, "columns", position];
1864
+ const owner = byName.get(cell.of);
1865
+ if (!owner) {
1866
+ fail(`cell column '${column.name}' belongs to '${cell.of}', which is not a tracked column`, path);
1867
+ continue;
1868
+ }
1869
+ 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);
1870
+ if (column.name !== cellColumnName(owner.name, cell.part) || column.columnName !== cellColumnName(owner.columnName, cell.part)) {
1871
+ fail(`cell column for '${owner.name}' ${cell.part} must be named '${cellColumnName(owner.name, cell.part)}' / '${cellColumnName(owner.columnName, cell.part)}'`, path);
1872
+ }
1873
+ if (column.type !== CELL_PART_TYPES[cell.part] || !column.nullable || column.unique || column.default !== void 0 || column.defaultFn !== void 0) {
1874
+ fail(`cell column '${column.name}' must be a nullable, default-less ${CELL_PART_TYPES[cell.part]}`, path);
1875
+ }
1876
+ const key = `${cell.of}:${cell.part}`;
1877
+ if (seen.has(key)) fail(`duplicate cell column '${column.name}'`, path);
1878
+ seen.add(key);
1879
+ }
1880
+ for (const column of tracked) {
1881
+ if (column.columnName.length > MAX_TRACKED_COLUMN_NAME) {
1882
+ fail(`column '${column.columnName}' is too long to track cell metadata (max ${MAX_TRACKED_COLUMN_NAME} characters)`);
1883
+ }
1884
+ if (SUFFIXES.some((suffix) => column.columnName.endsWith(suffix))) {
1885
+ fail(`column '${column.columnName}' ends in a reserved cell-metadata suffix (${SUFFIXES.join(", ")})`);
1886
+ }
1887
+ for (const part of withRev ? ["at", "by", "rev"] : ["at", "by"]) {
1888
+ if (!seen.has(`${column.name}:${part}`)) fail(`tracked column '${column.name}' is missing its '${cellColumnName(column.columnName, part)}' cell column`);
1889
+ }
1890
+ }
1891
+ }
1892
+ var exportGateSchema = zod.z.union([zod.z.literal(true), zod.z.object({ capability: zod.z.string().min(1) }).strict(), zod.z.object({ role: zod.z.string().min(1) }).strict()]);
1893
+ var relationExportSchema = zod.z.object({ read: exportGateSchema, write: exportGateSchema.optional() }).strict();
1894
+ var localDefinitionSchema = zod.z.object({
1260
1895
  definition_format: zod.z.literal(CANONICAL_FORMAT),
1261
- namespace: identifier.max(41),
1896
+ namespace: identifier2.max(41),
1262
1897
  description: zod.z.string().max(2e3).optional(),
1263
1898
  enums: zod.z.array(canonicalEnumSchema).default([]),
1264
1899
  tables: zod.z.array(canonicalTableSchema).min(0).max(500),
1265
1900
  /** Optional to preserve byte-identical hashes for pre-view canonical v2 documents. */
1266
1901
  views: zod.z.array(canonicalViewSchema).max(500).optional(),
1902
+ /** Target-owned cross-namespace access contract, using existing namespace grants. */
1903
+ exports: zod.z.record(identifier2, relationExportSchema).optional(),
1267
1904
  /** Verbatim custom-roles grammar (@sequenceholdings/custom-roles-manifest). */
1268
1905
  capabilities: zod.z.object({
1269
1906
  roles: zod.z.unknown().optional(),
@@ -1276,6 +1913,7 @@ var canonicalDefinitionSchema = zod.z.object({
1276
1913
  ctx.addIssue({ code: "custom", path: ["tables", index3, "table"], message: `duplicate relation '${table.table}'` });
1277
1914
  }
1278
1915
  seen.add(table.table);
1916
+ validateCellMetadata(table, index3, ctx);
1279
1917
  if (table.source !== void 0) {
1280
1918
  for (const [columnIndex, column] of table.columns.entries()) {
1281
1919
  if (column.onUpdateFn !== void 0) {
@@ -1289,12 +1927,41 @@ var canonicalDefinitionSchema = zod.z.object({
1289
1927
  }
1290
1928
  }
1291
1929
  const fkTables = new Map(definition.tables.map((table) => [table.table, table]));
1930
+ const relations = /* @__PURE__ */ new Set([...fkTables.keys(), ...(definition.views ?? []).map((view) => view.view)]);
1931
+ for (const name2 of Object.keys(definition.exports ?? {})) {
1932
+ if (!relations.has(name2)) ctx.addIssue({ code: "custom", path: ["exports", name2], message: `export '${name2}' is not a local object` });
1933
+ }
1292
1934
  for (const [tableIndex, table] of definition.tables.entries()) {
1935
+ for (const [linkIndex, link] of table.links.entries()) {
1936
+ if (link.enforce && link.namespace !== void 0 && link.namespace !== definition.namespace) {
1937
+ ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "links", linkIndex], message: "physical foreign keys require a local target; use an ORM link without enforce" });
1938
+ }
1939
+ }
1293
1940
  const names = /* @__PURE__ */ new Set([
1294
1941
  ...table.primaryKey?.length ? [`${table.table}_pkey`] : [],
1295
1942
  ...table.columns.filter((column) => column.unique && !(table.primaryKey?.length === 1 && table.primaryKey[0] === column.columnName)).map((column) => `${table.table}_${column.columnName}_unique`),
1296
1943
  ...table.links.filter((link) => link.enforce).map((link) => `fk_${table.table}_${link.name}`)
1297
1944
  ]);
1945
+ for (const [index3, link] of table.links.entries()) {
1946
+ if ((link.onDelete !== void 0 || link.onUpdate !== void 0) && (!link.enforce || link.kind !== "toOne")) {
1947
+ ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "links", index3], message: "referential actions require an enforced toOne link" });
1948
+ }
1949
+ if (link.onDelete === "set null" || link.onUpdate === "set null") {
1950
+ const source = table.columns.find((column) => column.columnName === link.on);
1951
+ 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" });
1952
+ }
1953
+ }
1954
+ for (const [index3, check] of (table.checks ?? []).entries()) {
1955
+ const fail = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "checks", index3], message });
1956
+ if (table.source) fail("synced tables cannot declare checks");
1957
+ if (names.has(check.name)) fail(`duplicate constraint '${check.name}'`);
1958
+ names.add(check.name);
1959
+ try {
1960
+ compileCheckExpression(check.expression, table.columns);
1961
+ } catch (error) {
1962
+ fail(error instanceof Error ? error.message : String(error));
1963
+ }
1964
+ }
1298
1965
  for (const [index3, fk] of (table.foreignKeys ?? []).entries()) {
1299
1966
  const fail = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "foreignKeys", index3], message });
1300
1967
  if (names.has(fk.name)) fail(`duplicate foreign key '${fk.name}'`);
@@ -1306,16 +1973,24 @@ var canonicalDefinitionSchema = zod.z.object({
1306
1973
  }
1307
1974
  if (fk.columns.length !== fk.references.length) fail("foreign key columns and references must have the same number of columns");
1308
1975
  if (new Set(fk.columns).size !== fk.columns.length || new Set(fk.references).size !== fk.references.length) fail("foreign key columns must not repeat");
1309
- for (const [position, name] of fk.columns.entries()) {
1310
- const sourceColumn = table.columns.find((column) => column.columnName === name);
1976
+ for (const [position, name2] of fk.columns.entries()) {
1977
+ const sourceColumn = table.columns.find((column) => column.columnName === name2);
1311
1978
  const targetColumn = target.columns.find((column) => column.columnName === fk.references[position]);
1312
1979
  if (!sourceColumn || !targetColumn) fail("foreign key references an unknown column");
1313
1980
  else if (JSON.stringify(sourceColumn.type) !== JSON.stringify(targetColumn.type)) fail("foreign key column types must match");
1314
1981
  }
1982
+ if (fk.onDelete === "set null" || fk.onUpdate === "set null") {
1983
+ if (fk.columns.some((name2) => table.columns.find((column) => column.columnName === name2)?.nullable === false || table.primaryKey?.includes(name2))) fail("SET NULL requires nullable source columns");
1984
+ }
1315
1985
  const uniqueKeys = [target.primaryKey ?? [], ...target.indexes.filter((index4) => index4.unique).map((index4) => index4.columns), ...target.columns.filter((column) => column.unique).map((column) => [column.columnName])];
1316
1986
  if (!uniqueKeys.some((key) => key.length === fk.references.length && key.every((column) => fk.references.includes(column)))) fail("foreign key target columns must form a unique key");
1317
1987
  }
1318
1988
  }
1989
+ for (const [viewIndex, view] of (definition.views ?? []).entries()) {
1990
+ for (const message of validateResolvedView({ view, tables: definition.tables, views: definition.views ?? [] })) {
1991
+ ctx.addIssue({ code: "custom", path: ["views", viewIndex, "resolved"], message });
1992
+ }
1993
+ }
1319
1994
  const tableByName = new Map(definition.tables.map((table) => [table.table, table]));
1320
1995
  for (const [tableIndex, table] of definition.tables.entries()) {
1321
1996
  const cowMutations = table.cowMutations ?? [];
@@ -1410,27 +2085,31 @@ var canonicalDefinitionSchema = zod.z.object({
1410
2085
  const visiting = /* @__PURE__ */ new Set();
1411
2086
  const visited = /* @__PURE__ */ new Set();
1412
2087
  let cycleReported = false;
1413
- const visit = (name, path) => {
1414
- if (visited.has(name) || cycleReported) return;
1415
- if (visiting.has(name)) {
2088
+ const visit = (name2, path) => {
2089
+ if (visited.has(name2) || cycleReported) return;
2090
+ if (visiting.has(name2)) {
1416
2091
  cycleReported = true;
1417
2092
  ctx.addIssue({
1418
2093
  code: "custom",
1419
2094
  path: ["views"],
1420
- message: `view dependencies form a cycle: ${[...path, name].join(" -> ")}`
2095
+ message: `view dependencies form a cycle: ${[...path, name2].join(" -> ")}`
1421
2096
  });
1422
2097
  return;
1423
2098
  }
1424
- visiting.add(name);
1425
- const view = viewByName.get(name);
2099
+ visiting.add(name2);
2100
+ const view = viewByName.get(name2);
1426
2101
  for (const dependency of view?.dependencies ?? []) {
1427
- if (viewByName.has(dependency)) visit(dependency, [...path, name]);
2102
+ if (viewByName.has(dependency)) visit(dependency, [...path, name2]);
1428
2103
  }
1429
- visiting.delete(name);
1430
- visited.add(name);
2104
+ visiting.delete(name2);
2105
+ visited.add(name2);
1431
2106
  };
1432
2107
  for (const view of views) visit(view.view, []);
1433
2108
  });
2109
+ var canonicalDefinitionSchema = localDefinitionSchema.safeExtend({
2110
+ /** Flattened, pinned snapshots. Imported relations never participate in this namespace's DDL. */
2111
+ imports: zod.z.array(localDefinitionSchema).max(32).optional()
2112
+ });
1434
2113
  function canonicalRelations(definition) {
1435
2114
  const views = (definition.views ?? []).map((view) => ({
1436
2115
  table: view.view,
@@ -1439,8 +2118,12 @@ function canonicalRelations(definition) {
1439
2118
  ...view.primaryKey !== void 0 ? { primaryKey: view.primaryKey } : {},
1440
2119
  links: view.links,
1441
2120
  indexes: [],
1442
- policies: { read: view.policies.read },
1443
- actions: []
2121
+ // A resolved table's write policies (evaluated against the resolved row)
2122
+ // ride alongside the view's read policy.
2123
+ policies: { read: view.policies.read, ...view.resolved?.policies ?? {} },
2124
+ actions: [],
2125
+ ...view.resolved !== void 0 ? { resolved: view.resolved } : {},
2126
+ ...view.internal ? { internal: true } : {}
1444
2127
  }));
1445
2128
  return [...definition.tables, ...views].sort((a, b) => a.table.localeCompare(b.table));
1446
2129
  }
@@ -1450,13 +2133,14 @@ function normalizeCanonical(definition) {
1450
2133
  ...definition,
1451
2134
  enums: sortByName(definition.enums),
1452
2135
  tables: [...definition.tables].sort((a, b) => a.table.localeCompare(b.table)).map((t2) => {
1453
- const { cowMutations, foreignKeys, ...rest } = t2;
2136
+ const { cowMutations, foreignKeys, checks, ...rest } = t2;
1454
2137
  return {
1455
2138
  ...rest,
1456
2139
  links: sortByName(t2.links),
1457
2140
  indexes: [...t2.indexes].sort((a, b) => (a.name ?? a.columns.join("_")).localeCompare(b.name ?? b.columns.join("_"))),
1458
2141
  actions: sortByName(t2.actions),
1459
2142
  ...foreignKeys?.length ? { foreignKeys: sortByName(foreignKeys) } : {},
2143
+ ...checks?.length ? { checks: sortByName(checks) } : {},
1460
2144
  // Omitted entirely when absent/empty so pre-CoW documents hash unchanged.
1461
2145
  ...cowMutations !== void 0 && cowMutations.length > 0 ? { cowMutations: sortByName(cowMutations) } : {}
1462
2146
  };
@@ -1469,7 +2153,460 @@ function normalizeCanonical(definition) {
1469
2153
  dependencies: [...view.dependencies].sort()
1470
2154
  }));
1471
2155
  }
1472
- return normalized;
2156
+ if (definition.imports?.length) {
2157
+ normalized.imports = definition.imports.map(normalizeCanonical).sort((a, b) => a.namespace.localeCompare(b.namespace));
2158
+ } else {
2159
+ delete normalized.imports;
2160
+ }
2161
+ return normalized;
2162
+ }
2163
+
2164
+ // src/authoring/resolved-sources.ts
2165
+ function cloneColumn({ builder, property, notNull, primaryKey: primaryKey2 }) {
2166
+ const canonical = builder.toCanonicalColumn(property);
2167
+ let clone = new ColumnBuilder(canonical.type, canonical.columnName);
2168
+ if (primaryKey2) clone = clone.primaryKey();
2169
+ if (notNull) clone = clone.notNull();
2170
+ if (canonical.description !== void 0) clone = clone.description(canonical.description);
2171
+ return clone;
2172
+ }
2173
+ function planSource(input) {
2174
+ const { sourceName, input: source, fail } = input;
2175
+ if (!IDENTIFIER_PATTERN.test(sourceName)) fail(`source name "${sourceName}" must match ${IDENTIFIER_PATTERN}`);
2176
+ switch (source.kind) {
2177
+ case "manual":
2178
+ return {
2179
+ name: sourceName,
2180
+ kind: "manual",
2181
+ columns: input.dataColumns,
2182
+ table: internalTable(input, input.dataColumns, {
2183
+ [RESOLVED_SOURCE_SYSTEM_COLUMNS.tombstoned.name]: tombstoned(),
2184
+ [RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackStatus.name]: t.text(RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackStatus.columnName),
2185
+ [RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackError.name]: t.text(RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackError.columnName),
2186
+ [RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackAt.name]: t.timestamptz(RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackAt.columnName)
2187
+ }, { revisions: true })
2188
+ };
2189
+ case "etl":
2190
+ return planEtl(input, source);
2191
+ case "prefetch":
2192
+ return planPrefetch(input, source);
2193
+ }
2194
+ }
2195
+ var tombstoned = () => t.boolean(RESOLVED_SOURCE_SYSTEM_COLUMNS.tombstoned.columnName).notNull().default(false);
2196
+ function internalTable(input, supplied, system, options = {}) {
2197
+ const { viewName, sourceName, columns, pk, pkBuilder } = input;
2198
+ const tableColumns = { [pk]: cloneColumn({ builder: pkBuilder, property: pk, notNull: true, primaryKey: true }) };
2199
+ for (const property of supplied) {
2200
+ tableColumns[property] = cloneColumn({ builder: columns[property], property, notNull: false, primaryKey: false });
2201
+ }
2202
+ return defineTable(`${viewName}__${sourceName}`, { ...tableColumns, ...system }, {
2203
+ description: `Internal ${input.input.kind} source "${sourceName}" of resolved table "${viewName}". System-maintained.`,
2204
+ cellMetadata: true,
2205
+ // The manual source identifies each edit (`c__rev`) so an async
2206
+ // confirmation clears only the exact edit it delivered.
2207
+ ...options.revisions ? { cellRevisions: true } : {},
2208
+ internal: true
2209
+ });
2210
+ }
2211
+ function assertColumns(input, list, what) {
2212
+ for (const column of list) if (!input.dataColumns.includes(column)) input.fail(`${what} names unknown column "${column}"`);
2213
+ }
2214
+ function planEtl(input, source) {
2215
+ const { sourceName, pk, dataColumns, fail } = input;
2216
+ const table = typeof source.table === "function" ? source.table() : source.table;
2217
+ if (!isSeqTable(table) || table.source === void 0) fail(`etl source "${sourceName}" must reference a defineSyncedTable`);
2218
+ const synced = table;
2219
+ if (!(pk in synced.columns)) fail(`etl source "${sourceName}" table "${synced.tableName}" has no "${pk}" column`);
2220
+ const supplied = source.columns ? [...source.columns] : dataColumns.filter((column) => column in synced.columns);
2221
+ assertColumns(input, supplied, `etl source "${sourceName}"`);
2222
+ if (source.updatedAt !== void 0 && !(source.updatedAt in synced.columns)) {
2223
+ fail(`etl source "${sourceName}" updatedAt "${source.updatedAt}" is not a column of "${synced.tableName}"`);
2224
+ }
2225
+ return { name: sourceName, kind: "etl", table: synced, columns: supplied, ...source.updatedAt ? { updatedAt: source.updatedAt } : {} };
2226
+ }
2227
+ function planPrefetch(input, source) {
2228
+ const { sourceName, pk, fail } = input;
2229
+ assertColumns(input, source.columns, `prefetch source "${sourceName}"`);
2230
+ if (source.key !== pk && !source.columns.includes(source.key)) {
2231
+ fail(`prefetch source "${sourceName}" key "${source.key}" must be the primary key or one of its columns`);
2232
+ }
2233
+ for (const column of source.writeback?.columns ?? []) {
2234
+ if (!source.columns.includes(column)) fail(`prefetch source "${sourceName}" writes back "${column}", which it does not supply`);
2235
+ }
2236
+ const writeback = source.writeback;
2237
+ return {
2238
+ name: sourceName,
2239
+ kind: "prefetch",
2240
+ columns: [...source.columns],
2241
+ key: source.key,
2242
+ function: source.function,
2243
+ fetch: source.fetch === "single" ? { shape: "single" } : { shape: "batch", maxKeys: source.fetch.batch.maxKeys },
2244
+ prefetchDefaults: {
2245
+ maxStalenessMs: parseDuration(source.prefetchDefaults?.maxStaleness ?? "30s"),
2246
+ timeoutMs: parseDuration(source.prefetchDefaults?.timeout ?? "1500ms"),
2247
+ onError: source.prefetchDefaults?.onError ?? "fallback"
2248
+ },
2249
+ ...writeback ? {
2250
+ writeback: {
2251
+ columns: [...writeback.columns],
2252
+ defaultMode: writeback.defaultMode ?? "sync",
2253
+ timeoutMs: parseDuration(writeback.timeout ?? "5s"),
2254
+ clearManualOnConfirm: writeback.clearManualOnConfirm ?? true
2255
+ }
2256
+ } : {},
2257
+ table: internalTable(input, source.columns, {
2258
+ [RESOLVED_SOURCE_SYSTEM_COLUMNS.fetchedAt.name]: t.timestamptz(RESOLVED_SOURCE_SYSTEM_COLUMNS.fetchedAt.columnName).notNull().default("now()"),
2259
+ [RESOLVED_SOURCE_SYSTEM_COLUMNS.tombstoned.name]: tombstoned()
2260
+ })
2261
+ };
2262
+ }
2263
+ function parseDuration(input) {
2264
+ const value = parseDurationMs(input);
2265
+ if (value === void 0) throw new OrmDefinitionError(`invalid duration "${input}" \u2014 use e.g. '1500ms', '30s', '5m'`);
2266
+ return value;
2267
+ }
2268
+ var PG_TYPE = {
2269
+ uuid: "uuid",
2270
+ text: "text",
2271
+ integer: "integer",
2272
+ bigint: "bigint",
2273
+ numeric: "numeric",
2274
+ double: "double precision",
2275
+ boolean: "boolean",
2276
+ timestamptz: "timestamptz",
2277
+ date: "date",
2278
+ jsonb: "jsonb"
2279
+ };
2280
+ var ident = (value) => `"${value.replace(/"/g, '""')}"`;
2281
+ var literal = (value) => `'${value.replace(/'/g, "''")}'`;
2282
+ var physical = ({ source, property }) => source.table.columns[property].columnNameFor(property);
2283
+ function candidateSql({ source, column, pk, pgType }) {
2284
+ const a = ident(`s_${source.name}`);
2285
+ const name2 = physical({ source, property: column });
2286
+ const value = `CAST(${a}.${ident(name2)} AS ${pgType})`;
2287
+ if (source.kind === "etl") {
2288
+ return {
2289
+ source: source.name,
2290
+ value,
2291
+ has: `(${a}.${ident(physical({ source, property: pk }))} IS NOT NULL)`,
2292
+ at: source.updatedAt ? `CAST(${a}.${ident(physical({ source, property: source.updatedAt }))} AS timestamptz)` : "CAST(NULL AS timestamptz)",
2293
+ by: "CAST(NULL AS text)"
2294
+ };
2295
+ }
2296
+ const at = `${a}.${ident(cellColumnName(name2, "at"))}`;
2297
+ const tombstoned2 = source.kind === "prefetch" ? ` AND NOT ${a}.${ident(RESOLVED_SOURCE_SYSTEM_COLUMNS.tombstoned.columnName)}` : "";
2298
+ return { source: source.name, value, has: `(${at} IS NOT NULL${tombstoned2})`, at, by: `${a}.${ident(cellColumnName(name2, "by"))}` };
2299
+ }
2300
+ function outranks({ i, j, key, direction, inclusive }) {
2301
+ const ki = i[key];
2302
+ const kj = j[key];
2303
+ const op = inclusive ? `${direction}=` : direction;
2304
+ 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}))`;
2305
+ }
2306
+ function rankedWinner({ candidates, key, direction }) {
2307
+ const branches = candidates.map((candidate, index3) => {
2308
+ 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);
2309
+ return `WHEN ${[candidate.has, ...beats].join(" AND ")} THEN ${index3 + 1}`;
2310
+ });
2311
+ return `CASE ${branches.join(" ")} END`;
2312
+ }
2313
+ function winnerIndex({ comparator, candidates }) {
2314
+ const firstWith = (test) => candidates.map((candidate, index3) => `WHEN ${test(candidate)} THEN ${index3 + 1}`).join(" ");
2315
+ if (candidates.length === 1 || comparator === "priority") return `CASE ${firstWith((c) => c.has)} END`;
2316
+ switch (comparator) {
2317
+ case "latest":
2318
+ return rankedWinner({ candidates, key: "at", direction: ">" });
2319
+ case "highest":
2320
+ return rankedWinner({ candidates, key: "value", direction: ">" });
2321
+ case "lowest":
2322
+ return rankedWinner({ candidates, key: "value", direction: "<" });
2323
+ case "nonNullFirst":
2324
+ return `CASE ${firstWith((c) => `${c.has} AND ${c.value} IS NOT NULL`)} ${firstWith((c) => c.has)} END`;
2325
+ }
2326
+ }
2327
+ function pick(winner, candidates, field) {
2328
+ return `CASE ${winner} ${candidates.map((candidate, index3) => `WHEN ${index3 + 1} THEN ${field(candidate)}`).join(" ")} END`;
2329
+ }
2330
+ function loserSql({ loser, winner }) {
2331
+ 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`;
2332
+ }
2333
+ function losersAgainst({ candidates, winner, isWinner }) {
2334
+ const losers = candidates.flatMap((loser, index3) => {
2335
+ const excluded = isWinner(loser, index3);
2336
+ if (excluded === true) return [];
2337
+ const clause = loserSql({ loser, winner });
2338
+ return [excluded === false ? clause : `CASE WHEN ${excluded} THEN '[]'::jsonb ELSE ${clause} END`];
2339
+ });
2340
+ return losers.length === 0 ? "CAST(NULL AS jsonb)" : `NULLIF(${losers.join(" || ")}, '[]'::jsonb)`;
2341
+ }
2342
+ function builtinCell({ comparator, candidates }) {
2343
+ const winner = winnerIndex({ comparator, candidates });
2344
+ 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`;
2345
+ return {
2346
+ value: pick(winner, candidates, (c) => c.value),
2347
+ parts: {
2348
+ source: pick(winner, candidates, (c) => literal(c.source)),
2349
+ at: pick(winner, candidates, (c) => c.at),
2350
+ by: pick(winner, candidates, (c) => c.by),
2351
+ conflicts
2352
+ }
2353
+ };
2354
+ }
2355
+ function customCell({ candidates, orderBy, index: index3 }) {
2356
+ const w = ident(`w_${index3}`);
2357
+ const rows = candidates.map((c, ord) => `(${literal(c.source)}, ${c.has}, ${c.value}, ${c.at}, ${c.by}, ${ord})`);
2358
+ 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`;
2359
+ const winner = { value: `${w}.value`, at: `${w}.updated_at` };
2360
+ return {
2361
+ value: winner.value,
2362
+ lateral,
2363
+ parts: {
2364
+ source: `${w}.source`,
2365
+ at: winner.at,
2366
+ by: `${w}.actor`,
2367
+ conflicts: candidates.length < 2 ? "CAST(NULL AS jsonb)" : losersAgainst({ candidates, winner, isWinner: (c) => `${w}.source = ${literal(c.source)}` })
2368
+ }
2369
+ };
2370
+ }
2371
+ function buildResolvedQuery({ context, plan }) {
2372
+ const { qb, relation } = context;
2373
+ const pkColumn = plan.columns[plan.pk].columnNameFor(plan.pk);
2374
+ const alias = (source) => ident(`s_${source}`);
2375
+ const tombstoned2 = RESOLVED_SOURCE_SYSTEM_COLUMNS.tombstoned.columnName;
2376
+ const sourceByName = new Map(plan.plans.map((source) => [source.name, source]));
2377
+ const schema = pgCore.getTableConfig(relation(plan.plans[0].table)).schema;
2378
+ const keyBranches = plan.plans.map((source) => {
2379
+ const where = source.kind === "prefetch" ? drizzleOrm.sql.raw(` WHERE NOT ${ident(tombstoned2)}`) : drizzleOrm.sql.raw("");
2380
+ const sourcePk = ident(physical({ source, property: plan.pk }));
2381
+ const selectedPk = sourcePk === ident(pkColumn) ? sourcePk : `${sourcePk} AS ${ident(pkColumn)}`;
2382
+ return drizzleOrm.sql`SELECT ${drizzleOrm.sql.raw(selectedPk)} FROM ${relation(source.table)}${where}`;
2383
+ });
2384
+ const joins = plan.plans.map(
2385
+ (source) => drizzleOrm.sql` LEFT JOIN ${relation(source.table)} AS ${drizzleOrm.sql.raw(alias(source.name))} ON ${drizzleOrm.sql.raw(`${alias(source.name)}.${ident(physical({ source, property: plan.pk }))} = "k".${ident(pkColumn)}`)}`
2386
+ );
2387
+ const cells = /* @__PURE__ */ new Map();
2388
+ plan.rules.forEach((rule, index3) => {
2389
+ const canonical = plan.columns[rule.column].toCanonicalColumn(rule.column);
2390
+ const pgType = typeof canonical.type === "string" ? PG_TYPE[canonical.type] : `${schema === void 0 ? "" : `${ident(schema)}.`}${ident(canonical.type.enum)}`;
2391
+ const candidates = rule.candidates.map((name2) => candidateSql({ source: sourceByName.get(name2), column: rule.column, pk: plan.pk, pgType }));
2392
+ cells.set(rule.column, rule.comparator === "custom" ? customCell({ candidates, orderBy: rule.orderBy, index: index3 }) : builtinCell({ comparator: rule.comparator, candidates }));
2393
+ });
2394
+ const laterals = [...cells.values()].flatMap((cell) => cell.lateral ? [drizzleOrm.sql.raw(cell.lateral)] : []);
2395
+ const from = drizzleOrm.sql.join([drizzleOrm.sql`(${drizzleOrm.sql.join(keyBranches, drizzleOrm.sql.raw(" UNION "))}) AS "k"`, ...joins, ...laterals], drizzleOrm.sql.raw(""));
2396
+ const selected = {
2397
+ [plan.pk]: drizzleOrm.sql.raw(`"k".${ident(pkColumn)}`).as(pkColumn)
2398
+ };
2399
+ for (const [property, builder] of Object.entries(plan.columns)) {
2400
+ if (property === plan.pk) continue;
2401
+ const cell = cells.get(property);
2402
+ const columnName = builder.columnNameFor(property);
2403
+ selected[property] = drizzleOrm.sql.raw(cell.value).as(columnName);
2404
+ for (const [part, expression] of Object.entries(cell.parts)) {
2405
+ selected[cellColumnName(property, part)] = drizzleOrm.sql.raw(expression).as(cellColumnName(columnName, part));
2406
+ }
2407
+ }
2408
+ const manual = plan.plans.find((source) => source.kind === "manual");
2409
+ const system = RESOLVED_VIEW_SYSTEM_COLUMNS;
2410
+ selected[system.writebackStatus.name] = drizzleOrm.sql.raw(`${alias(manual.name)}.${ident(RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackStatus.columnName)}`).as(system.writebackStatus.columnName);
2411
+ selected[system.writebackError.name] = drizzleOrm.sql.raw(`${alias(manual.name)}.${ident(RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackError.columnName)}`).as(system.writebackError.columnName);
2412
+ const hidden = plan.deletedBy.map((name2) => `NOT COALESCE(${alias(name2)}.${ident(tombstoned2)}, false)`);
2413
+ const query = qb.select(selected).from(from);
2414
+ return hidden.length > 0 ? query.where(drizzleOrm.sql.raw(hidden.join(" AND "))) : query;
2415
+ }
2416
+ function managedFn(id) {
2417
+ return { kind: "managed", id };
2418
+ }
2419
+ function manualSource() {
2420
+ return { kind: "manual" };
2421
+ }
2422
+ function etlSource(input) {
2423
+ return { kind: "etl", ...input };
2424
+ }
2425
+ function prefetchSource(input) {
2426
+ return { kind: "prefetch", ...input };
2427
+ }
2428
+ var latest = (candidates) => ({ kind: "latest", candidates });
2429
+ var highest = (candidates) => ({ kind: "highest", candidates });
2430
+ var lowest = (candidates) => ({ kind: "lowest", candidates });
2431
+ var priority = (candidates) => ({ kind: "priority", candidates });
2432
+ var nonNullFirst = (candidates) => ({ kind: "nonNullFirst", candidates });
2433
+ var custom = (candidates, orderBy) => ({ kind: "custom", candidates, orderBy });
2434
+ var SYSTEM_VIEW_COLUMNS = {
2435
+ [RESOLVED_VIEW_SYSTEM_COLUMNS.writebackStatus.name]: () => t.text(RESOLVED_VIEW_SYSTEM_COLUMNS.writebackStatus.columnName),
2436
+ [RESOLVED_VIEW_SYSTEM_COLUMNS.writebackError.name]: () => t.text(RESOLVED_VIEW_SYSTEM_COLUMNS.writebackError.columnName)
2437
+ };
2438
+ function defineResolvedTable(name2, columns, config) {
2439
+ const file = activeAuthoringContext()?.currentFile;
2440
+ const fail = (message) => {
2441
+ throw new OrmDefinitionError(`resolved table "${name2}": ${message}`, file);
2442
+ };
2443
+ if (!IDENTIFIER_PATTERN.test(name2)) fail(`name must match ${IDENTIFIER_PATTERN}`);
2444
+ for (const property of Object.keys(columns)) {
2445
+ if (RESERVED_RESOLVED_COLUMN_NAMES.has(property) || RESERVED_RESOLVED_COLUMN_NAMES.has(columns[property].columnNameFor(property))) {
2446
+ fail(`column "${property}" collides with a reserved system column`);
2447
+ }
2448
+ }
2449
+ for (const property of Object.keys(columns)) {
2450
+ if (columns[property].columnNameFor(property).length > MAX_TRACKED_COLUMN_NAME) {
2451
+ fail(`column "${property}" is too long for a resolved table (max ${MAX_TRACKED_COLUMN_NAME} characters, room for cell parts)`);
2452
+ }
2453
+ }
2454
+ const pkEntries = Object.entries(columns).filter(([, builder]) => builder.isPrimaryKey);
2455
+ if (pkEntries.length !== 1) fail("declare exactly one .primaryKey() column");
2456
+ const [pk, pkBuilder] = pkEntries[0];
2457
+ const dataColumns = Object.keys(columns).filter((property) => property !== pk);
2458
+ if (dataColumns.length === 0) fail("declare at least one non-key column");
2459
+ const sourceEntries = Object.entries(config.sources);
2460
+ if (sourceEntries.filter(([, source]) => source.kind === "manual").length !== 1) fail("declare exactly one manualSource()");
2461
+ const plans = sourceEntries.map(
2462
+ ([sourceName, input]) => planSource({ viewName: name2, sourceName, input, columns, pk, pkBuilder, dataColumns, fail })
2463
+ );
2464
+ const rules = planRules({ config, plans, dataColumns, fail });
2465
+ const deletedBy = [...config.rows?.deletedBy ?? []];
2466
+ for (const source of deletedBy) {
2467
+ if (plans.find((plan) => plan.name === source)?.kind !== "manual") fail(`rows.deletedBy "${source}" must be the manual source`);
2468
+ }
2469
+ const partType = { source: t.text, at: t.timestamptz, by: t.text, conflicts: t.jsonb };
2470
+ const viewColumns = {};
2471
+ for (const [property, builder] of Object.entries(columns)) {
2472
+ viewColumns[property] = cloneColumn({ builder, property, notNull: property === pk, primaryKey: property === pk });
2473
+ if (property === pk) continue;
2474
+ const columnName = builder.columnNameFor(property);
2475
+ for (const part of RESOLVED_VIEW_CELL_PARTS) {
2476
+ viewColumns[cellColumnName(property, part)] = partType[part](cellColumnName(columnName, part)).cellPart(property, part);
2477
+ }
2478
+ }
2479
+ for (const [property, make] of Object.entries(SYSTEM_VIEW_COLUMNS)) viewColumns[property] = make();
2480
+ const { read, ...writePolicies } = config.policies;
2481
+ const resolved = {
2482
+ internalTables: plans.filter((plan) => plan.kind !== "etl").map((plan) => plan.table),
2483
+ sources: plans,
2484
+ rules,
2485
+ deletedBy,
2486
+ policies: writePolicies
2487
+ };
2488
+ const queryPlan = { columns, pk, plans, rules, deletedBy };
2489
+ const typedColumns = viewColumns;
2490
+ const surface = {
2491
+ ...config.description !== void 0 ? { description: config.description } : {},
2492
+ ...config.links !== void 0 ? { links: config.links } : {},
2493
+ policies: { read }
2494
+ };
2495
+ if (!config.materialize) {
2496
+ return defineView(name2, typedColumns, { ...surface, query: (context) => buildResolvedQuery({ context, plan: queryPlan }), resolved });
2497
+ }
2498
+ const materialize = config.materialize === true ? {} : config.materialize;
2499
+ const computeName = `${name2}__compute`;
2500
+ const tableName = `${name2}__materialized`;
2501
+ const compute = defineView(computeName, typedColumns, {
2502
+ policies: { read },
2503
+ query: (context) => buildResolvedQuery({ context, plan: queryPlan }),
2504
+ internal: true
2505
+ });
2506
+ const table = defineTable(tableName, viewColumns, {
2507
+ description: `Materialized rows of resolved table "${name2}". System-maintained from "${computeName}".`,
2508
+ internal: true,
2509
+ materializes: name2,
2510
+ ...materialize.indexes !== void 0 ? { indexes: materialize.indexes } : {}
2511
+ });
2512
+ return defineView(name2, typedColumns, {
2513
+ ...surface,
2514
+ query: (context) => {
2515
+ const handle = context.relation(table);
2516
+ const selected = Object.fromEntries(Object.entries(viewColumns).map(([property, builder]) => [
2517
+ property,
2518
+ drizzleOrm.sql`${handle[property]}`.as(builder.columnNameFor(property))
2519
+ ]));
2520
+ return context.qb.select(selected).from(context.relation(table));
2521
+ },
2522
+ resolved: {
2523
+ ...resolved,
2524
+ internalTables: [...resolved.internalTables, table],
2525
+ internalViews: [compute],
2526
+ materialized: { table: tableName, compute: computeName }
2527
+ }
2528
+ });
2529
+ }
2530
+ function planRules({ config, plans, dataColumns, fail }) {
2531
+ const kindOrder = { manual: 0, prefetch: 1, etl: 2 };
2532
+ const fallback = config.default ?? priority([...plans].sort((a, b) => kindOrder[a.kind] - kindOrder[b.kind]).map((plan) => plan.name));
2533
+ for (const column of Object.keys(config.resolve ?? {})) {
2534
+ if (!dataColumns.includes(column)) fail(`resolve names unknown column "${column}"`);
2535
+ }
2536
+ return dataColumns.map((column) => {
2537
+ const explicit = config.resolve?.[column];
2538
+ const comparator = explicit ?? fallback;
2539
+ const candidates = explicit ? [...explicit.candidates] : comparator.candidates.filter((name2) => plans.find((plan) => plan.name === name2)?.columns.includes(column));
2540
+ for (const candidate of candidates) {
2541
+ const plan = plans.find((entry) => entry.name === candidate);
2542
+ if (!plan) fail(`column "${column}" names unknown source "${candidate}"`);
2543
+ else if (!plan.columns.includes(column)) fail(`column "${column}" candidate "${candidate}" does not supply it`);
2544
+ }
2545
+ if (candidates.length === 0) fail(`column "${column}" has no candidate source`);
2546
+ if (comparator.kind === "custom") assertSafeOrderBy({ orderBy: comparator.orderBy ?? "", column, fail });
2547
+ return {
2548
+ column,
2549
+ comparator: comparator.kind,
2550
+ candidates,
2551
+ ...comparator.kind === "custom" && comparator.orderBy !== void 0 ? { orderBy: comparator.orderBy } : {}
2552
+ };
2553
+ });
2554
+ }
2555
+ var ORDER_BY_KEYWORDS = /* @__PURE__ */ new Set([
2556
+ "value",
2557
+ "updated_at",
2558
+ "source",
2559
+ "actor",
2560
+ "ord",
2561
+ "asc",
2562
+ "desc",
2563
+ "nulls",
2564
+ "first",
2565
+ "last",
2566
+ "case",
2567
+ "when",
2568
+ "then",
2569
+ "else",
2570
+ "end",
2571
+ "and",
2572
+ "or",
2573
+ "not",
2574
+ "is",
2575
+ "null",
2576
+ "true",
2577
+ "false",
2578
+ "in",
2579
+ "distinct",
2580
+ "from",
2581
+ "coalesce",
2582
+ "nullif",
2583
+ "greatest",
2584
+ "least",
2585
+ "text",
2586
+ "numeric",
2587
+ "integer",
2588
+ "bigint",
2589
+ "boolean",
2590
+ "timestamptz",
2591
+ "date",
2592
+ "jsonb",
2593
+ "interval"
2594
+ ]);
2595
+ var ALLOWED_FUNCTIONS = new Set(VIEW_QUERY_FUNCTION_ALLOWLIST);
2596
+ var ORDER_BY_TOKEN = /\s+|'(?:[^']|'')*'|\d+(?:\.\d+)?|[a-z_][a-z0-9_]*|::|<=|>=|<>|!=|[-+*/%<>=(),|]/giy;
2597
+ function assertSafeOrderBy({ orderBy, column, fail }) {
2598
+ ORDER_BY_TOKEN.lastIndex = 0;
2599
+ let consumed = 0;
2600
+ for (let match = ORDER_BY_TOKEN.exec(orderBy); match; match = ORDER_BY_TOKEN.exec(orderBy)) {
2601
+ consumed = ORDER_BY_TOKEN.lastIndex;
2602
+ const token = match[0].toLowerCase();
2603
+ if (/^[a-z_]/.test(token) && !ORDER_BY_KEYWORDS.has(token) && !ALLOWED_FUNCTIONS.has(token)) {
2604
+ fail(`column "${column}" custom orderBy uses "${match[0]}" \u2014 only value, updated_at, source, actor, ord, SQL keywords and allowlisted functions`);
2605
+ }
2606
+ }
2607
+ if (consumed !== orderBy.length || orderBy.trim() === "") {
2608
+ fail(`column "${column}" custom orderBy contains an unsupported character near "${orderBy.slice(consumed, consumed + 10)}"`);
2609
+ }
1473
2610
  }
1474
2611
  function stableJson(value) {
1475
2612
  if (value === null || typeof value !== "object") return JSON.stringify(value);
@@ -1501,8 +2638,8 @@ function canonicalTable(table) {
1501
2638
  },
1502
2639
  actions: emptyToNull(
1503
2640
  Object.fromEntries(
1504
- Object.entries(table.actions ?? {}).map(([name, action]) => [
1505
- name,
2641
+ Object.entries(table.actions ?? {}).map(([name2, action]) => [
2642
+ name2,
1506
2643
  {
1507
2644
  description: action.description ?? null,
1508
2645
  requires: action.requires?.source ?? null,
@@ -1556,6 +2693,72 @@ function computeDefinitionHash(definition) {
1556
2693
  return crypto.createHash("sha256").update(HASH_DOMAIN).update(stableJson(canonicalizeDefinition(definition))).digest("hex");
1557
2694
  }
1558
2695
 
2696
+ // src/canonical/composition.ts
2697
+ function localContract(definition) {
2698
+ return normalizeCanonical(localDefinitionSchema.parse(definition));
2699
+ }
2700
+ function flattenNamespaceImports(inputs) {
2701
+ const definitions = /* @__PURE__ */ new Map();
2702
+ const visit = (value, depth) => {
2703
+ if (depth > 16 || definitions.size > 32) throw new OrmDefinitionError("namespace import graph is too large");
2704
+ const definition = localContract(localDefinitionSchema.parse(value));
2705
+ const previous = definitions.get(definition.namespace);
2706
+ if (previous) {
2707
+ if (stableJson(previous) !== stableJson(definition)) throw new OrmDefinitionError(`conflicting contracts for namespace '${definition.namespace}'`);
2708
+ }
2709
+ definitions.set(definition.namespace, definition);
2710
+ if (typeof value === "object" && value !== null && "imports" in value && Array.isArray(value.imports)) {
2711
+ for (const dependency of value.imports) visit(dependency, depth + 1);
2712
+ }
2713
+ };
2714
+ for (const input of inputs) visit(input, 0);
2715
+ return [...definitions.values()].sort((a, b) => a.namespace.localeCompare(b.namespace));
2716
+ }
2717
+ function composeNamespace(definition) {
2718
+ const all = [localContract(definition), ...definition.imports ?? []];
2719
+ const owners = /* @__PURE__ */ new Map();
2720
+ const namespaces = /* @__PURE__ */ new Set();
2721
+ const enums = /* @__PURE__ */ new Map();
2722
+ for (const owner of all) {
2723
+ if (namespaces.has(owner.namespace)) throw new OrmDefinitionError(`duplicate imported namespace '${owner.namespace}'`);
2724
+ namespaces.add(owner.namespace);
2725
+ for (const value of owner.enums) {
2726
+ const previous = enums.get(value.name);
2727
+ if (previous && stableJson(previous) !== stableJson(value)) {
2728
+ throw new OrmDefinitionError(`conflicting imported enum '${value.name}'`);
2729
+ }
2730
+ enums.set(value.name, value);
2731
+ }
2732
+ for (const relation of canonicalRelations(owner)) {
2733
+ if (owners.has(relation.table)) throw new OrmDefinitionError(`ambiguous imported relation '${relation.table}'`);
2734
+ owners.set(relation.table, owner);
2735
+ }
2736
+ }
2737
+ for (const owner of definition.imports?.length ? all : []) {
2738
+ for (const relation of canonicalRelations(owner)) {
2739
+ for (const link of relation.links) {
2740
+ if (link.kind === "toPlatformUser") continue;
2741
+ const target = owners.get(link.target ?? "");
2742
+ if (!target || target.namespace !== (link.namespace ?? owner.namespace)) {
2743
+ throw new OrmDefinitionError(`link '${owner.namespace}/${relation.table}.${link.name}' has no matching imported target`);
2744
+ }
2745
+ if (target.namespace !== owner.namespace && !target.exports?.[link.target ?? ""]) {
2746
+ throw new OrmDefinitionError(`target '${target.namespace}/${link.target}' is not exported`);
2747
+ }
2748
+ }
2749
+ }
2750
+ }
2751
+ return {
2752
+ owners,
2753
+ definition: {
2754
+ ...localContract(definition),
2755
+ tables: all.flatMap((item) => item.tables),
2756
+ views: all.flatMap((item) => item.views ?? []),
2757
+ enums: [...enums.values()]
2758
+ }
2759
+ };
2760
+ }
2761
+
1559
2762
  // src/authoring/namespace.ts
1560
2763
  var NAMESPACE_CONFIG_BRAND = "seq-namespace-config";
1561
2764
  function isSeqNamespaceConfig(value) {
@@ -1571,14 +2774,27 @@ function defineNamespace(input) {
1571
2774
  namespace: input.namespace,
1572
2775
  description: input.description,
1573
2776
  capabilities: input.capabilities ?? {},
2777
+ imports: input.imports,
2778
+ exports: input.exports,
1574
2779
  file
1575
2780
  };
1576
2781
  registerNamespaceConfig(config);
1577
2782
  return config;
1578
2783
  }
1579
2784
  function serializeNamespace(input) {
1580
- const { config, tables } = input;
1581
- const views = input.views ?? [];
2785
+ const { config } = input;
2786
+ const views = [...input.views ?? []];
2787
+ const tables = [...input.tables];
2788
+ for (const view of views) {
2789
+ for (const internal of view.config.resolved?.internalTables ?? []) {
2790
+ if (!tables.includes(internal)) tables.push(internal);
2791
+ }
2792
+ }
2793
+ for (const view of [...views]) {
2794
+ for (const internal of view.config.resolved?.internalViews ?? []) {
2795
+ if (!views.includes(internal)) views.push(internal);
2796
+ }
2797
+ }
1582
2798
  const enums = input.enums ?? [];
1583
2799
  const tablesByName = /* @__PURE__ */ new Map();
1584
2800
  for (const table of tables) {
@@ -1604,15 +2820,15 @@ function serializeNamespace(input) {
1604
2820
  }
1605
2821
  const relationsByName = /* @__PURE__ */ new Map();
1606
2822
  for (const relation of [...tables, ...views]) {
1607
- const name = relationName(relation);
1608
- const existing = relationsByName.get(name);
2823
+ const name2 = relationName(relation);
2824
+ const existing = relationsByName.get(name2);
1609
2825
  if (existing) {
1610
2826
  throw new OrmDefinitionError(
1611
- `duplicate relation "${name}" (defined as ${relationKind(existing)} in ${describeFile(existing)} and ${relationKind(relation)} in ${describeFile(relation)})`,
2827
+ `duplicate relation "${name2}" (defined as ${relationKind(existing)} in ${describeFile(existing)} and ${relationKind(relation)} in ${describeFile(relation)})`,
1612
2828
  relation.file
1613
2829
  );
1614
2830
  }
1615
- relationsByName.set(name, relation);
2831
+ relationsByName.set(name2, relation);
1616
2832
  }
1617
2833
  const enumsByName = /* @__PURE__ */ new Map();
1618
2834
  for (const ref of enums) {
@@ -1653,6 +2869,8 @@ function serializeNamespace(input) {
1653
2869
  };
1654
2870
  if (config.description !== void 0) candidate.description = config.description;
1655
2871
  if (canonicalViews.length > 0) candidate.views = canonicalViews;
2872
+ if (config.imports?.length) candidate.imports = flattenNamespaceImports(config.imports);
2873
+ if (config.exports) candidate.exports = config.exports;
1656
2874
  let parsed;
1657
2875
  try {
1658
2876
  parsed = canonicalDefinitionSchema.parse(candidate);
@@ -1662,7 +2880,9 @@ function serializeNamespace(input) {
1662
2880
  }
1663
2881
  throw error;
1664
2882
  }
1665
- return normalizeCanonical(parsed);
2883
+ const normalized = normalizeCanonical(parsed);
2884
+ if (normalized.imports?.length) composeNamespace(normalized);
2885
+ return normalized;
1666
2886
  }
1667
2887
  function computeCanonicalHash(definition) {
1668
2888
  return crypto.createHash("sha256").update(HASH_DOMAIN).update(stableJson(definition)).digest("hex");
@@ -1676,10 +2896,14 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
1676
2896
  policies: {},
1677
2897
  actions: []
1678
2898
  };
2899
+ if (table.config.displayName !== void 0) candidate.displayName = table.config.displayName;
1679
2900
  if (table.config.description !== void 0) candidate.description = table.config.description;
1680
2901
  if (table.config.renamedFrom !== void 0) candidate.renamedFrom = table.config.renamedFrom;
1681
2902
  if (table.source !== void 0) candidate.source = table.source;
1682
2903
  if (table.source !== void 0) {
2904
+ if (table.config.checks?.length) {
2905
+ throw new OrmDefinitionError(`synced table "${table.tableName}" is system-owned; checks are not allowed`, table.file);
2906
+ }
1683
2907
  if (table.config.indexes !== void 0 && table.config.indexes.length > 0) {
1684
2908
  throw new OrmDefinitionError(
1685
2909
  `synced table "${table.tableName}" is system-owned; indexes are not allowed`,
@@ -1717,6 +2941,11 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
1717
2941
  if (builder.isPrimaryKey) columnLevelPk.push(column.columnName);
1718
2942
  candidate.columns?.push(column);
1719
2943
  }
2944
+ if (table.config.internal === true) candidate.internal = true;
2945
+ if (table.config.materializes !== void 0) candidate.materializes = table.config.materializes;
2946
+ if (table.config.cellMetadata === true && table.source !== void 0) {
2947
+ throw new OrmDefinitionError(`synced table "${table.tableName}" cannot track cell metadata \u2014 its DDL is owned by the sync`, table.file);
2948
+ }
1720
2949
  if (table.config.primaryKey !== void 0) {
1721
2950
  if (columnLevelPk.length > 0) {
1722
2951
  throw new OrmDefinitionError(
@@ -1730,9 +2959,30 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
1730
2959
  } else if (columnLevelPk.length > 0) {
1731
2960
  candidate.primaryKey = columnLevelPk;
1732
2961
  }
2962
+ if (table.config.cellMetadata === true) {
2963
+ candidate.cellMetadata = true;
2964
+ const parts = table.config.cellRevisions === true ? ["at", "by", "rev"] : ["at", "by"];
2965
+ const tracked = trackedCellColumns({ columns: candidate.columns ?? [], primaryKey: candidate.primaryKey });
2966
+ for (const column of tracked) {
2967
+ for (const part of parts) {
2968
+ candidate.columns?.push({
2969
+ name: cellColumnName(column.name, part),
2970
+ columnName: cellColumnName(column.columnName, part),
2971
+ type: CELL_PART_TYPES[part],
2972
+ nullable: true,
2973
+ unique: false,
2974
+ deprecated: false,
2975
+ cell: { of: column.name, part }
2976
+ });
2977
+ }
2978
+ }
2979
+ }
1733
2980
  if (table.config.foreignKeys?.length) {
1734
2981
  if (table.source) throw new OrmDefinitionError(`synced table "${table.tableName}" cannot enforce foreign keys`, table.file);
1735
2982
  candidate.foreignKeys = table.config.foreignKeys.map((fk) => {
2983
+ if (typeof fk.target === "object" && fk.target.kind === "seq-external-object") {
2984
+ throw new OrmDefinitionError("physical foreign keys require a local target; use a cross-namespace ORM link", table.file);
2985
+ }
1736
2986
  const target = resolveRelationRef({ ref: fk.target, fail: (message) => {
1737
2987
  throw new OrmDefinitionError(message, table.file);
1738
2988
  } });
@@ -1742,10 +2992,15 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
1742
2992
  columns: fk.columns.map((ref) => resolveColumnRef({ relation: table, ref, role: `foreign key "${fk.name}" column` })),
1743
2993
  target: target.tableName,
1744
2994
  references: fk.references.map((ref) => resolveColumnRef({ relation: target, ref, role: `foreign key "${fk.name}" reference` })),
1745
- ...fk.deferrable ? { deferrable: fk.deferrable } : {}
2995
+ ...fk.deferrable ? { deferrable: fk.deferrable } : {},
2996
+ ...fk.onDelete ? { onDelete: fk.onDelete } : {},
2997
+ ...fk.onUpdate ? { onUpdate: fk.onUpdate } : {}
1746
2998
  };
1747
2999
  });
1748
3000
  }
3001
+ if (table.config.checks?.length) {
3002
+ candidate.checks = table.config.checks.map((check) => ({ name: check.name, expression: check.expression }));
3003
+ }
1749
3004
  for (const index3 of table.config.indexes ?? []) {
1750
3005
  const entry = {
1751
3006
  columns: index3.columns.map((ref) => resolveColumnRef({ relation: table, ref, role: "index" }))
@@ -1841,7 +3096,33 @@ function buildCanonicalView({
1841
3096
  columns,
1842
3097
  ...primaryKey2 !== void 0 ? { primaryKey: primaryKey2 } : {},
1843
3098
  links,
1844
- policies: { read: toCanonicalPolicy(view.config.policies.read) }
3099
+ policies: { read: toCanonicalPolicy(view.config.policies.read) },
3100
+ ...view.config.resolved !== void 0 ? { resolved: buildCanonicalResolved(view.config.resolved) } : {},
3101
+ ...view.config.internal === true ? { internal: true } : {}
3102
+ };
3103
+ }
3104
+ function buildCanonicalResolved(resolved) {
3105
+ const policies = {};
3106
+ if (resolved.policies.write !== void 0) policies.write = toCanonicalPolicy(resolved.policies.write);
3107
+ if (resolved.policies.insert !== void 0) policies.insert = toCanonicalPolicy(resolved.policies.insert);
3108
+ if (resolved.policies.update !== void 0) policies.update = toCanonicalPolicy(resolved.policies.update);
3109
+ return {
3110
+ sources: resolved.sources.map((source) => ({
3111
+ name: source.name,
3112
+ kind: source.kind,
3113
+ table: source.table.tableName,
3114
+ columns: [...source.columns],
3115
+ ...source.updatedAt !== void 0 ? { updatedAt: source.updatedAt } : {},
3116
+ ...source.key !== void 0 ? { key: source.key } : {},
3117
+ ...source.fetch !== void 0 ? { fetch: source.fetch } : {},
3118
+ ...source.function !== void 0 ? { function: { ...source.function } } : {},
3119
+ ...source.prefetchDefaults !== void 0 ? { prefetchDefaults: { ...source.prefetchDefaults } } : {},
3120
+ ...source.writeback !== void 0 ? { writeback: { ...source.writeback, columns: [...source.writeback.columns] } } : {}
3121
+ })),
3122
+ resolve: resolved.rules.map((rule) => ({ ...rule, candidates: [...rule.candidates] })),
3123
+ deletedBy: [...resolved.deletedBy],
3124
+ ...resolved.materialized ? { materialized: { ...resolved.materialized } } : {},
3125
+ policies
1845
3126
  };
1846
3127
  }
1847
3128
  function buildCanonicalLink({
@@ -1858,6 +3139,9 @@ function buildCanonicalLink({
1858
3139
  if (link.enforce && link.linkKind !== "toOne") {
1859
3140
  return fail("sets enforce: true, which is only supported on toOne links");
1860
3141
  }
3142
+ if ((link.onDelete !== void 0 || link.onUpdate !== void 0) && !link.enforce) {
3143
+ return fail("sets a referential action without enforce: true");
3144
+ }
1861
3145
  if (link.linkKind === "toPlatformUser") {
1862
3146
  if (link.target !== void 0) return fail("is toPlatformUser and must not declare a target table");
1863
3147
  return {
@@ -1869,6 +3153,19 @@ function buildCanonicalLink({
1869
3153
  ...link.description !== void 0 ? { description: link.description } : {}
1870
3154
  };
1871
3155
  }
3156
+ if (typeof link.target === "object" && link.target?.kind === "seq-external-object") {
3157
+ if (link.enforce) return fail("cannot enforce a cross-namespace physical foreign key; use an ORM link without enforce");
3158
+ return {
3159
+ name: linkName,
3160
+ kind: link.linkKind,
3161
+ namespace: link.target.namespace,
3162
+ target: link.target.table,
3163
+ on: link.linkKind === "toOne" ? resolveColumnRef({ relation, ref: link.on, role: "link on" }) : link.on,
3164
+ ...link.references !== void 0 ? { references: link.linkKind === "toMany" ? resolveColumnRef({ relation, ref: link.references, role: "link references" }) : link.references } : {},
3165
+ enforce: false,
3166
+ ...link.description !== void 0 ? { description: link.description } : {}
3167
+ };
3168
+ }
1872
3169
  const target = resolveRelationRef({ ref: link.target, fail });
1873
3170
  const targetName = relationName(target);
1874
3171
  if (!relationsByName.has(targetName)) {
@@ -1886,6 +3183,8 @@ function buildCanonicalLink({
1886
3183
  on: resolveColumnRef({ relation: owningSide, ref: link.on, role: `link "${linkName}" on` }),
1887
3184
  ...link.references !== void 0 ? { references: resolveColumnRef({ relation: referencedSide, ref: link.references, role: `link "${linkName}" references` }) } : {},
1888
3185
  enforce: link.enforce,
3186
+ ...link.onDelete !== void 0 ? { onDelete: link.onDelete } : {},
3187
+ ...link.onUpdate !== void 0 ? { onUpdate: link.onUpdate } : {},
1889
3188
  ...link.description !== void 0 ? { description: link.description } : {}
1890
3189
  };
1891
3190
  }
@@ -1923,11 +3222,11 @@ function assertAcyclicViewDependencies(views) {
1923
3222
  views.map((view) => [view.view, new Set(view.dependencies.filter((dependency) => viewNames.has(dependency)))])
1924
3223
  );
1925
3224
  while (remaining.size > 0) {
1926
- const ready = [...remaining.entries()].filter(([, dependencies]) => [...dependencies].every((dependency) => !remaining.has(dependency))).map(([name]) => name);
3225
+ const ready = [...remaining.entries()].filter(([, dependencies]) => [...dependencies].every((dependency) => !remaining.has(dependency))).map(([name2]) => name2);
1927
3226
  if (ready.length === 0) {
1928
3227
  throw new OrmDefinitionError(`view dependencies form a cycle among: ${[...remaining.keys()].sort().join(", ")}`);
1929
3228
  }
1930
- for (const name of ready) remaining.delete(name);
3229
+ for (const name2 of ready) remaining.delete(name2);
1931
3230
  }
1932
3231
  }
1933
3232
  function toCanonicalPolicy(policy) {
@@ -1973,8 +3272,8 @@ function syncedContractSurface({
1973
3272
  namespace,
1974
3273
  table,
1975
3274
  columns: Object.fromEntries(
1976
- Object.entries(columns).map(([name, column]) => [
1977
- name,
3275
+ Object.entries(columns).map(([name2, column]) => [
3276
+ name2,
1978
3277
  { type: column.type, nullable: column.nullable, key: column.key }
1979
3278
  ])
1980
3279
  ),
@@ -1995,7 +3294,7 @@ function extractOrmSyncedTableContract({
1995
3294
  }
1996
3295
  if (table.source === void 0) {
1997
3296
  throw new OrmDefinitionError(
1998
- `table "${table.table}" is not a synced table \u2014 extractOrmSyncedTableContract requires source: gold.*`
3297
+ `table "${table.table}" is not a synced table \u2014 extractOrmSyncedTableContract requires a qualified source`
1999
3298
  );
2000
3299
  }
2001
3300
  if (table.columns.length === 0) {
@@ -2142,6 +3441,53 @@ function purgeModuleCache(root) {
2142
3441
  if (key.startsWith(prefix)) delete require2.cache[key];
2143
3442
  }
2144
3443
  }
3444
+ var sqlScalarSchema = zod.z.enum(["text", "boolean", "integer", "bigint", "numeric", "double", "uuid", "date", "timestamptz"]);
3445
+ var name = zod.z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).max(63).refine((value) => !["__proto__", "prototype", "constructor"].includes(value), "reserved name");
3446
+ var base = zod.z.object({
3447
+ name,
3448
+ namespace: name,
3449
+ language: zod.z.literal("sql"),
3450
+ kind: zod.z.literal("query"),
3451
+ requires: zod.z.object({ capability: zod.z.string().min(1).max(128) }).strict(),
3452
+ ontology: zod.z.object({ read: zod.z.array(zod.z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*\/[A-Za-z_][A-Za-z0-9_]*$/)).min(1).max(256) }).strict(),
3453
+ maxRows: zod.z.number().int().min(1).max(1e4).default(1e3)
3454
+ });
3455
+ var fixed = base.extend({
3456
+ parameters: zod.z.record(name, zod.z.object({ type: sqlScalarSchema, required: zod.z.boolean().default(true) }).strict()).default({}),
3457
+ sql: zod.z.string().min(1).max(65536)
3458
+ }).strict();
3459
+ var runtime = base.extend({ mode: zod.z.literal("runtime") }).strict();
3460
+ var sqlOperationDefinitionSchema = zod.z.union([fixed, runtime]);
3461
+ function defineOperation(input) {
3462
+ return sqlOperationDefinitionSchema.parse(input);
3463
+ }
3464
+
3465
+ // src/authoring/catalog.ts
3466
+ var SEGMENT = /^[^./\\\s][^/\\]*[^./\\\s]$|^[^./\\\s]$/;
3467
+ function validateNamespaceCatalog(catalog, file) {
3468
+ const paths = /* @__PURE__ */ new Set([""]);
3469
+ function visit(folders, parent) {
3470
+ for (const [name2, children] of Object.entries(folders)) {
3471
+ if (!SEGMENT.test(name2) || ["__proto__", "constructor", "prototype"].includes(name2)) {
3472
+ throw new OrmDefinitionError(`invalid catalog folder name "${name2}"`, file);
3473
+ }
3474
+ const path = parent ? `${parent}/${name2}` : name2;
3475
+ paths.add(path);
3476
+ if (children !== null) {
3477
+ if (typeof children !== "object" || Array.isArray(children)) {
3478
+ throw new OrmDefinitionError(`catalog folder "${path}" must contain folders or null`, file);
3479
+ }
3480
+ visit(children, path);
3481
+ }
3482
+ }
3483
+ }
3484
+ visit(catalog.folders, "");
3485
+ for (const [object, path] of Object.entries(catalog.objects)) {
3486
+ if (!object || !paths.has(path)) {
3487
+ throw new OrmDefinitionError(`catalog object "${object}" references missing folder "${path}"`, file);
3488
+ }
3489
+ }
3490
+ }
2145
3491
 
2146
3492
  // src/codegen/types.ts
2147
3493
  var WIRE_TYPES = {
@@ -2173,9 +3519,9 @@ function generateNamespaceTypes(definition) {
2173
3519
  lines.push(` /** Synced from ${table.source} \u2014 read-only typed contract. */`);
2174
3520
  }
2175
3521
  lines.push(` ${table.table}: {`);
2176
- for (const [name, column] of columns) {
3522
+ for (const [name2, column] of columns) {
2177
3523
  if (column.deprecated) continue;
2178
- lines.push(` ${name}: ${columnWireType(column)}`);
3524
+ lines.push(` ${name2}: ${columnWireType(column)}`);
2179
3525
  }
2180
3526
  lines.push(" }");
2181
3527
  }
@@ -2186,8 +3532,8 @@ function generateNamespaceTypes(definition) {
2186
3532
  function actionTypeLines(definition) {
2187
3533
  const actions = definition.tables.flatMap((table) => Object.entries(table.actions ?? {}));
2188
3534
  const lines = [`export type ${actionsInterfaceName(definition.namespace)} = {`];
2189
- for (const [name, action] of actions.sort(([a], [b]) => a.localeCompare(b))) {
2190
- lines.push(` ${name}: { params: ${actionParamsType(action)}; result: ${actionResultType(action)} }`);
3535
+ for (const [name2, action] of actions.sort(([a], [b]) => a.localeCompare(b))) {
3536
+ lines.push(` ${name2}: { params: ${actionParamsType(action)}; result: ${actionResultType(action)} }`);
2191
3537
  }
2192
3538
  lines.push("}", "");
2193
3539
  return lines;
@@ -2195,17 +3541,17 @@ function actionTypeLines(definition) {
2195
3541
  function actionParamsType(action) {
2196
3542
  const entries = Object.entries(action.params ?? {});
2197
3543
  if (entries.length === 0) return "Record<never, never>";
2198
- const fields = entries.map(([name, param]) => {
2199
- const base = WIRE_TYPES[param.type];
2200
- return param.optional ? `${name}?: ${base} | null` : `${name}: ${base}`;
3544
+ const fields = entries.map(([name2, param]) => {
3545
+ const base2 = WIRE_TYPES[param.type];
3546
+ return param.optional ? `${name2}?: ${base2} | null` : `${name2}: ${base2}`;
2201
3547
  });
2202
3548
  return `{ ${fields.join("; ")} }`;
2203
3549
  }
2204
3550
  function actionResultType(action) {
2205
3551
  if (!action.returns) return "{ rowsAffected: number }";
2206
- const columns = Object.entries(action.returns).map(([name, type]) => {
2207
- const base = WIRE_TYPES[type];
2208
- return base === "unknown" ? `${name}: ${base}` : `${name}: ${base} | null`;
3552
+ const columns = Object.entries(action.returns).map(([name2, type]) => {
3553
+ const base2 = WIRE_TYPES[type];
3554
+ return base2 === "unknown" ? `${name2}: ${base2}` : `${name2}: ${base2} | null`;
2209
3555
  });
2210
3556
  return `{ rows: Array<{ ${columns.join("; ")} }> }`;
2211
3557
  }
@@ -2219,9 +3565,9 @@ function pascalCase(namespace) {
2219
3565
  return namespace.split("_").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
2220
3566
  }
2221
3567
  function columnWireType(column) {
2222
- const base = WIRE_TYPES[column.type];
3568
+ const base2 = WIRE_TYPES[column.type];
2223
3569
  const notNull = column.primary_key === true || column.nullable === false;
2224
- return notNull || base === "unknown" ? base : `${base} | null`;
3570
+ return notNull || base2 === "unknown" ? base2 : `${base2} | null`;
2225
3571
  }
2226
3572
 
2227
3573
  // src/predicate/ast.ts
@@ -2339,7 +3685,7 @@ var OPERATOR_PATTERN = /^(!=|<=|>=|<>|=|<|>)/;
2339
3685
  var IDENTIFIER_START = /[a-z_]/;
2340
3686
  var IDENTIFIER_CHAR = /[a-z0-9_.-]/;
2341
3687
  var NUMBER_PATTERN = /^-?\d+(\.\d+)?/;
2342
- function tokenize(source) {
3688
+ function tokenize2(source) {
2343
3689
  const tokens = [];
2344
3690
  let position = 0;
2345
3691
  while (position < source.length) {
@@ -2413,7 +3759,7 @@ function readString(source, start) {
2413
3759
  }
2414
3760
  throw new PredicateParseError("unterminated string literal", start);
2415
3761
  }
2416
- var Parser = class {
3762
+ var Parser2 = class {
2417
3763
  constructor(tokens) {
2418
3764
  this.tokens = tokens;
2419
3765
  }
@@ -2545,7 +3891,7 @@ var Parser = class {
2545
3891
  function parsePredicate(source) {
2546
3892
  const trimmed = source.trim();
2547
3893
  if (trimmed.length === 0) throw new PredicateParseError("empty predicate", 0);
2548
- return { source: trimmed, ast: new Parser(tokenize(trimmed)).parse() };
3894
+ return { source: trimmed, ast: new Parser2(tokenize2(trimmed)).parse() };
2549
3895
  }
2550
3896
 
2551
3897
  // src/schema/predicates.ts
@@ -2630,8 +3976,8 @@ var actionSchema = zod.z.strictObject({
2630
3976
  }
2631
3977
  }
2632
3978
  });
2633
- function extractNamedParams(sql5) {
2634
- const withoutStrings = sql5.replace(QUOTED_STRING_PATTERN, "''");
3979
+ function extractNamedParams(sql7) {
3980
+ const withoutStrings = sql7.replace(QUOTED_STRING_PATTERN, "''");
2635
3981
  const names = [...withoutStrings.matchAll(NAMED_PARAM_PATTERN)].map((match) => match[1]);
2636
3982
  return [...new Set(names)];
2637
3983
  }
@@ -2686,7 +4032,7 @@ function executesIssues(executes, declaredParams, hasReturns) {
2686
4032
  if (hasReturns && last && !RETURNING_PATTERN.test(last.replace(QUOTED_STRING_PATTERN, "''"))) {
2687
4033
  issues.push("the last executes statement must use RETURNING to populate the declared returns columns");
2688
4034
  }
2689
- const undeclared = extractNamedParams(statements.join("\n")).filter((name) => !declaredParams.has(name));
4035
+ const undeclared = extractNamedParams(statements.join("\n")).filter((name2) => !declaredParams.has(name2));
2690
4036
  if (undeclared.length > 0) issues.push(`executes references undeclared params: ${undeclared.join(", ")}`);
2691
4037
  return issues;
2692
4038
  }
@@ -2698,17 +4044,6 @@ var SQL_TYPE_NAMES = {
2698
4044
  };
2699
4045
  var sqlTypeName = (type) => SQL_TYPE_NAMES[type] ?? type;
2700
4046
 
2701
- // src/compiler/sql.ts
2702
- var CLAIMS_SETTING = "request.jwt.claims";
2703
- var CAPABILITIES_CLAIM_KEY = "capabilities";
2704
- var ROLES_CLAIM_KEY = "roles";
2705
- var quoteIdentifier = (name) => `"${name.replaceAll('"', '""')}"`;
2706
- var quoteLiteral = (value) => `'${value.replaceAll("'", "''")}'`;
2707
- var CLAIMS_SOURCE = `nullif(current_setting('${CLAIMS_SETTING}', true), '')::jsonb`;
2708
- var claimExpression = (key) => `(${CLAIMS_SOURCE} ->> ${quoteLiteral(key)})`;
2709
- var capabilityExpression = (key) => `coalesce(${CLAIMS_SOURCE} -> '${CAPABILITIES_CLAIM_KEY}' ? ${quoteLiteral(key)}, false)`;
2710
- var roleExpression = (key) => `coalesce(${CLAIMS_SOURCE} -> '${ROLES_CLAIM_KEY}' ? ${quoteLiteral(key)}, false)`;
2711
-
2712
4047
  // src/compiler/predicates.ts
2713
4048
  var SQL_COMPARE_OPS = {
2714
4049
  "=": "=",
@@ -2772,24 +4107,24 @@ var PARAM_SCHEMAS = {
2772
4107
  function buildActionParamsSchema(action) {
2773
4108
  const shape = {};
2774
4109
  const entries = Object.entries(action.params ?? {});
2775
- for (const [name, param] of entries) {
2776
- shape[name] = param.optional ? PARAM_SCHEMAS[param.type].nullish() : PARAM_SCHEMAS[param.type];
4110
+ for (const [name2, param] of entries) {
4111
+ shape[name2] = param.optional ? PARAM_SCHEMAS[param.type].nullish() : PARAM_SCHEMAS[param.type];
2777
4112
  }
2778
4113
  return zod.z.strictObject(shape).superRefine((obj, ctx) => {
2779
- for (const [name, param] of entries) {
2780
- if (!param.optional && param.type === "jsonb" && obj[name] === void 0) {
2781
- ctx.addIssue({ code: "custom", message: "Required", path: [name] });
4114
+ for (const [name2, param] of entries) {
4115
+ if (!param.optional && param.type === "jsonb" && obj[name2] === void 0) {
4116
+ ctx.addIssue({ code: "custom", message: "Required", path: [name2] });
2782
4117
  }
2783
4118
  }
2784
4119
  });
2785
4120
  }
2786
4121
  var actionParamOrder = (action) => Object.keys(action.params ?? {});
2787
- var actionParamArg = (name) => `p_${name}`;
4122
+ var actionParamArg = (name2) => `p_${name2}`;
2788
4123
  function compileActionStatements(action, renderParam = actionParamArg) {
2789
4124
  return actionStatementSources(action.executes).map((statement) => compileStatement(statement, renderParam));
2790
4125
  }
2791
- function compileStatement(sql5, renderParam) {
2792
- const compiled = sql5.replace(TOKEN_PATTERN, (token, claimKey, capabilityKey, paramName) => {
4126
+ function compileStatement(sql7, renderParam) {
4127
+ const compiled = sql7.replace(TOKEN_PATTERN, (token, claimKey, capabilityKey, paramName) => {
2793
4128
  if (claimKey !== void 0) {
2794
4129
  if (!CLAIM_KEY_PATTERN.test(claimKey)) throw new OrmDefinitionError(`invalid claim key in executes: ${token}`);
2795
4130
  return claimExpression(claimKey);
@@ -2835,10 +4170,12 @@ function compilePredIrToSql(pred, options = {}) {
2835
4170
  case "cmp":
2836
4171
  return compileCmp(pred.op, pred.column, pred.value, options);
2837
4172
  case "capability":
2838
- return `(${capabilityExpression(pred.key)})`;
4173
+ return `(${capabilityExpression(pred.key, pred.namespace ?? options.namespace)})`;
2839
4174
  case "role":
2840
- return `(${roleExpression(pred.key)})`;
4175
+ return `(${roleExpression(pred.key, pred.namespace ?? options.namespace)})`;
2841
4176
  case "exists":
4177
+ if (options.exists) return options.exists(pred);
4178
+ throw new OrmDefinitionError("synced view read policy cannot use 'exists' without a relationship resolver");
2842
4179
  case "aggPredicate":
2843
4180
  case "dynamicFilter":
2844
4181
  throw new OrmDefinitionError(
@@ -2854,10 +4191,11 @@ function physicalColumn(logical, options) {
2854
4191
  return options.columnNames?.[logical] ?? logical;
2855
4192
  }
2856
4193
  function compileCmp(op, column, value, options) {
2857
- const physical = physicalColumn(column, options);
2858
- const declared = options.columnTypes?.[physical] ?? options.columnTypes?.[column];
4194
+ const physical2 = physicalColumn(column, options);
4195
+ const declared = options.columnTypes?.[physical2] ?? options.columnTypes?.[column];
2859
4196
  const isEnum = typeof declared === "object" && declared !== null;
2860
- const col = isEnum ? `${quoteIdentifier(physical)}::text` : quoteIdentifier(physical);
4197
+ const qualified = options.alias ? `${quoteIdentifier(options.alias)}.${quoteIdentifier(physical2)}` : quoteIdentifier(physical2);
4198
+ const col = isEnum ? `${qualified}::text` : qualified;
2861
4199
  const columnType = isEnum ? "text" : declared;
2862
4200
  switch (op) {
2863
4201
  case "eq":
@@ -2901,11 +4239,11 @@ function compileEquality(col, value, columnType, negated, options) {
2901
4239
  function compileValue(value, columnType, _options) {
2902
4240
  switch (value.kind) {
2903
4241
  case "literal": {
2904
- const literal = value.value;
2905
- if (literal === null) return "NULL";
2906
- if (typeof literal === "string") return quoteLiteral(literal);
2907
- if (typeof literal === "boolean") return literal ? "TRUE" : "FALSE";
2908
- return String(literal);
4242
+ const literal2 = value.value;
4243
+ if (literal2 === null) return "NULL";
4244
+ if (typeof literal2 === "string") return quoteLiteral(literal2);
4245
+ if (typeof literal2 === "boolean") return literal2 ? "TRUE" : "FALSE";
4246
+ return String(literal2);
2909
4247
  }
2910
4248
  case "claim": {
2911
4249
  const expression = claimExpression(value.key);
@@ -2941,22 +4279,22 @@ function returnProjection(returns, lastStatement) {
2941
4279
  const projection = Object.keys(returns).map(quoteIdentifier).join(", ");
2942
4280
  return `WITH orm_returning AS (${lastStatement}) SELECT ${projection} FROM orm_returning`;
2943
4281
  }
2944
- var allActions = (definition) => definition.tables.flatMap((table) => Object.entries(table.actions ?? {}).map(([name, action]) => ({ name, action }))).sort((a, b) => a.name.localeCompare(b.name));
4282
+ var allActions = (definition) => definition.tables.flatMap((table) => Object.entries(table.actions ?? {}).map(([name2, action]) => ({ name: name2, action }))).sort((a, b) => a.name.localeCompare(b.name));
2945
4283
  function actionFunctionStatements(definition) {
2946
4284
  const schema = ormSchemaName(definition.namespace);
2947
- return [dropAllActionFunctions(schema), ...allActions(definition).flatMap(({ name, action }) => createActionFunction(definition, name, action))];
4285
+ return [dropAllActionFunctions(schema), ...allActions(definition).flatMap(({ name: name2, action }) => createActionFunction(definition, name2, action))];
2948
4286
  }
2949
4287
  function actionPrepareChecks(definition) {
2950
4288
  const actions = allActions(definition);
2951
4289
  if (actions.length === 0) return [];
2952
4290
  const statements = [`SET LOCAL search_path TO ${quoteIdentifier(ormSchemaName(definition.namespace))}`];
2953
- for (const { name, action } of actions) {
4291
+ for (const { name: name2, action } of actions) {
2954
4292
  const order = actionParamOrder(action);
2955
4293
  const signature = order.length > 0 ? ` (${order.map((param) => sqlTypeName(action.params[param].type)).join(", ")})` : "";
2956
4294
  const compiled = compileActionStatements(action, (param) => `$${order.indexOf(param) + 1}`);
2957
- compiled.forEach((sql5, index3) => {
2958
- const checked = action.returns && index3 === compiled.length - 1 ? returnProjection(action.returns, sql5) : sql5;
2959
- statements.push(`PREPARE ${quoteIdentifier(`orm_check_${name}_${index3}`)}${signature} AS ${checked}`);
4295
+ compiled.forEach((sql7, index3) => {
4296
+ const checked = action.returns && index3 === compiled.length - 1 ? returnProjection(action.returns, sql7) : sql7;
4297
+ statements.push(`PREPARE ${quoteIdentifier(`orm_check_${name2}_${index3}`)}${signature} AS ${checked}`);
2960
4298
  });
2961
4299
  }
2962
4300
  return statements;
@@ -2977,9 +4315,9 @@ BEGIN
2977
4315
  END
2978
4316
  $orm_reconcile_actions$`;
2979
4317
  }
2980
- function createActionFunction(definition, name, action) {
4318
+ function createActionFunction(definition, name2, action) {
2981
4319
  const schema = quoteIdentifier(ormSchemaName(definition.namespace));
2982
- const fn = `${schema}.${quoteIdentifier(actionFunctionName(name))}`;
4320
+ const fn = `${schema}.${quoteIdentifier(actionFunctionName(name2))}`;
2983
4321
  const order = actionParamOrder(action);
2984
4322
  const signature = order.map((paramName) => `${quoteIdentifier(actionParamArg(paramName))} ${sqlTypeName(action.params[paramName].type)}`).join(", ");
2985
4323
  const argTypes = order.map((paramName) => sqlTypeName(action.params[paramName].type)).join(", ");
@@ -2999,7 +4337,7 @@ AS $orm_action$
2999
4337
  -- columns; resolve unqualified names to the column.
3000
4338
  #variable_conflict use_column
3001
4339
  BEGIN
3002
- ${[functionGuards(definition, name, action), ...leading, ` RETURN QUERY ${returnProjection(action.returns, last)};`].filter(Boolean).join("\n")}
4340
+ ${[functionGuards(definition, name2, action), ...leading, ` RETURN QUERY ${returnProjection(action.returns, last)};`].filter(Boolean).join("\n")}
3003
4341
  END
3004
4342
  $orm_action$`
3005
4343
  ) : (
@@ -3013,7 +4351,7 @@ AS $orm_action$
3013
4351
  DECLARE
3014
4352
  v_count bigint;
3015
4353
  BEGIN
3016
- ${[functionGuards(definition, name, action), ...leading, ` ${last};`].filter(Boolean).join("\n")}
4354
+ ${[functionGuards(definition, name2, action), ...leading, ` ${last};`].filter(Boolean).join("\n")}
3017
4355
  GET DIAGNOSTICS v_count = ROW_COUNT;
3018
4356
  RETURN v_count;
3019
4357
  END
@@ -3025,13 +4363,13 @@ $orm_action$`
3025
4363
  `GRANT EXECUTE ON FUNCTION ${fn}(${argTypes}) TO ${role}`
3026
4364
  ];
3027
4365
  }
3028
- function functionGuards(definition, name, action) {
4366
+ function functionGuards(definition, name2, action) {
3029
4367
  const lines = [];
3030
4368
  const requires = compileActionRequires(action);
3031
4369
  if (requires) {
3032
4370
  lines.push(
3033
4371
  ` IF NOT (${requires}) THEN`,
3034
- ` RAISE EXCEPTION 'action ${name} requires a grant the caller does not hold' USING ERRCODE = ${quoteLiteral(ACTION_REQUIRES_DENIED_SQLSTATE)};`,
4372
+ ` RAISE EXCEPTION 'action ${name2} requires a grant the caller does not hold' USING ERRCODE = ${quoteLiteral(ACTION_REQUIRES_DENIED_SQLSTATE)};`,
3035
4373
  ` END IF;`
3036
4374
  );
3037
4375
  }
@@ -3083,7 +4421,7 @@ var tableFileSchema = zod.z.strictObject({
3083
4421
  table: identifierSchema,
3084
4422
  description: zod.z.string().optional(),
3085
4423
  columns: zod.z.record(identifierSchema, columnSchema).optional(),
3086
- source: zod.z.string().regex(SYNCED_SOURCE_PATTERN, "source must reference a gold-layer table").optional(),
4424
+ source: zod.z.string().regex(SYNCED_SOURCE_PATTERN, "source must be a qualified lowercase schema.table identifier").optional(),
3087
4425
  links: zod.z.record(identifierSchema, linkSchema).optional(),
3088
4426
  indexes: zod.z.array(indexSchema).optional(),
3089
4427
  policies: policiesSchema.optional(),
@@ -3092,12 +4430,12 @@ var tableFileSchema = zod.z.strictObject({
3092
4430
  if (RESERVED_TABLE_NAMES.has(table.table)) {
3093
4431
  ctx.addIssue({ code: "custom", message: `'${table.table}' is a reserved table name`, path: ["table"] });
3094
4432
  }
3095
- for (const name of Object.keys(table.columns ?? {})) {
3096
- if (RESERVED_COLUMN_NAMES.has(name)) {
4433
+ for (const name2 of Object.keys(table.columns ?? {})) {
4434
+ if (RESERVED_COLUMN_NAMES.has(name2)) {
3097
4435
  ctx.addIssue({
3098
4436
  code: "custom",
3099
- message: `column '${name}' collides with the Data API's reserved query parameters (select, order, limit, offset) \u2014 it could never be filtered`,
3100
- path: ["columns", name]
4437
+ message: `column '${name2}' collides with the Data API's reserved query parameters (select, order, limit, offset) \u2014 it could never be filtered`,
4438
+ path: ["columns", name2]
3101
4439
  });
3102
4440
  }
3103
4441
  }
@@ -3130,41 +4468,41 @@ function validateSyncedTable(table, ctx) {
3130
4468
  const columns = table.columns ?? {};
3131
4469
  const names = Object.keys(columns);
3132
4470
  if (names.length === 0) return;
3133
- const primaryKeys = names.filter((name) => columns[name]?.primary_key);
4471
+ const primaryKeys = names.filter((name2) => columns[name2]?.primary_key);
3134
4472
  if (primaryKeys.length !== 1) {
3135
4473
  ctx.addIssue({ code: "custom", message: "exactly one column must be primary_key", path: ["columns"] });
3136
4474
  }
3137
4475
  validateRenames(columns, names, ctx);
3138
- for (const name of names) {
3139
- const column = columns[name];
4476
+ for (const name2 of names) {
4477
+ const column = columns[name2];
3140
4478
  if (!column) continue;
3141
4479
  const cast = column.cast ? castParts(column.cast) : null;
3142
- if (column.cast && cast?.column !== name) {
4480
+ if (column.cast && cast?.column !== name2) {
3143
4481
  ctx.addIssue({
3144
4482
  code: "custom",
3145
4483
  message: "cast must reference the column it is declared on",
3146
- path: ["columns", name, "cast"]
4484
+ path: ["columns", name2, "cast"]
3147
4485
  });
3148
4486
  }
3149
4487
  if (cast && cast.type !== column.type) {
3150
4488
  ctx.addIssue({
3151
4489
  code: "custom",
3152
4490
  message: `cast target '${cast.type}' must match the declared type '${column.type}'`,
3153
- path: ["columns", name, "cast"]
4491
+ path: ["columns", name2, "cast"]
3154
4492
  });
3155
4493
  }
3156
4494
  }
3157
4495
  }
3158
4496
  function validateRenames(columns, names, ctx) {
3159
4497
  const renameSources = /* @__PURE__ */ new Map();
3160
- for (const name of names) {
3161
- const renamedFrom = columns[name]?.renamed_from;
4498
+ for (const name2 of names) {
4499
+ const renamedFrom = columns[name2]?.renamed_from;
3162
4500
  if (!renamedFrom) continue;
3163
- if (renamedFrom === name || names.includes(renamedFrom)) {
4501
+ if (renamedFrom === name2 || names.includes(renamedFrom)) {
3164
4502
  ctx.addIssue({
3165
4503
  code: "custom",
3166
4504
  message: "renamed_from must name a column that no longer exists",
3167
- path: ["columns", name, "renamed_from"]
4505
+ path: ["columns", name2, "renamed_from"]
3168
4506
  });
3169
4507
  }
3170
4508
  const claimed = renameSources.get(renamedFrom);
@@ -3172,10 +4510,10 @@ function validateRenames(columns, names, ctx) {
3172
4510
  ctx.addIssue({
3173
4511
  code: "custom",
3174
4512
  message: `renamed_from '${renamedFrom}' is already claimed by '${claimed}'`,
3175
- path: ["columns", name, "renamed_from"]
4513
+ path: ["columns", name2, "renamed_from"]
3176
4514
  });
3177
4515
  }
3178
- renameSources.set(renamedFrom, name);
4516
+ renameSources.set(renamedFrom, name2);
3179
4517
  }
3180
4518
  }
3181
4519
  function validateColumnTable(table, ctx) {
@@ -3185,27 +4523,27 @@ function validateColumnTable(table, ctx) {
3185
4523
  ctx.addIssue({ code: "custom", message: "a table must declare columns or a source", path: ["columns"] });
3186
4524
  return;
3187
4525
  }
3188
- const primaryKeys = names.filter((name) => columns[name]?.primary_key);
4526
+ const primaryKeys = names.filter((name2) => columns[name2]?.primary_key);
3189
4527
  if (primaryKeys.length !== 1) {
3190
4528
  ctx.addIssue({ code: "custom", message: "exactly one column must be primary_key", path: ["columns"] });
3191
4529
  }
3192
4530
  validateRenames(columns, names, ctx);
3193
- for (const name of names) {
3194
- const column = columns[name];
4531
+ for (const name2 of names) {
4532
+ const column = columns[name2];
3195
4533
  if (!column) continue;
3196
4534
  const cast = column.cast ? castParts(column.cast) : null;
3197
- if (column.cast && cast?.column !== name) {
4535
+ if (column.cast && cast?.column !== name2) {
3198
4536
  ctx.addIssue({
3199
4537
  code: "custom",
3200
4538
  message: "cast must reference the column it is declared on",
3201
- path: ["columns", name, "cast"]
4539
+ path: ["columns", name2, "cast"]
3202
4540
  });
3203
4541
  }
3204
4542
  if (cast && cast.type !== column.type) {
3205
4543
  ctx.addIssue({
3206
4544
  code: "custom",
3207
4545
  message: `cast target '${cast.type}' must match the declared type '${column.type}'`,
3208
- path: ["columns", name, "cast"]
4546
+ path: ["columns", name2, "cast"]
3209
4547
  });
3210
4548
  }
3211
4549
  }
@@ -3230,22 +4568,22 @@ function validateColumnTable(table, ctx) {
3230
4568
  }
3231
4569
  function validateLinks(table, ctx) {
3232
4570
  const columnNames = Object.keys(table.columns ?? {});
3233
- for (const [name, link] of Object.entries(table.links ?? {})) {
4571
+ for (const [name2, link] of Object.entries(table.links ?? {})) {
3234
4572
  if (columnNames.length > 0 && !columnNames.includes(link.on)) {
3235
- ctx.addIssue({ code: "custom", message: `link references unknown column '${link.on}'`, path: ["links", name, "on"] });
4573
+ ctx.addIssue({ code: "custom", message: `link references unknown column '${link.on}'`, path: ["links", name2, "on"] });
3236
4574
  }
3237
4575
  if (link.enforce && link.to.includes(".")) {
3238
4576
  ctx.addIssue({
3239
4577
  code: "custom",
3240
4578
  message: "enforce is only valid for links inside the namespace; cross-boundary links stay soft",
3241
- path: ["links", name, "enforce"]
4579
+ path: ["links", name2, "enforce"]
3242
4580
  });
3243
4581
  }
3244
4582
  if (link.enforce && table.source) {
3245
4583
  ctx.addIssue({
3246
4584
  code: "custom",
3247
4585
  message: "a synced table cannot enforce foreign keys \u2014 its DDL is owned by the sync",
3248
- path: ["links", name, "enforce"]
4586
+ path: ["links", name2, "enforce"]
3249
4587
  });
3250
4588
  }
3251
4589
  }
@@ -3260,32 +4598,32 @@ function validateDerivedNameLengths(table, ctx) {
3260
4598
  });
3261
4599
  }
3262
4600
  for (const [position, index3] of (table.indexes ?? []).entries()) {
3263
- const name = indexName(table.table, index3);
3264
- if (name.length > MAX_IDENTIFIER_LENGTH) {
4601
+ const name2 = indexName(table.table, index3);
4602
+ if (name2.length > MAX_IDENTIFIER_LENGTH) {
3265
4603
  ctx.addIssue({
3266
4604
  code: "custom",
3267
- message: `derived index name '${name}' exceeds Postgres's ${MAX_IDENTIFIER_LENGTH}-byte identifier limit \u2014 set a shorter explicit name`,
4605
+ message: `derived index name '${name2}' exceeds Postgres's ${MAX_IDENTIFIER_LENGTH}-byte identifier limit \u2014 set a shorter explicit name`,
3268
4606
  path: ["indexes", position]
3269
4607
  });
3270
4608
  }
3271
4609
  }
3272
- for (const [name, link] of Object.entries(table.links ?? {})) {
3273
- const constraint = foreignKeyName(table.table, name);
4610
+ for (const [name2, link] of Object.entries(table.links ?? {})) {
4611
+ const constraint = foreignKeyName(table.table, name2);
3274
4612
  if (link.enforce && constraint.length > MAX_IDENTIFIER_LENGTH) {
3275
4613
  ctx.addIssue({
3276
4614
  code: "custom",
3277
4615
  message: `derived foreign-key name '${constraint}' exceeds Postgres's ${MAX_IDENTIFIER_LENGTH}-byte identifier limit \u2014 shorten the link or table name`,
3278
- path: ["links", name]
4616
+ path: ["links", name2]
3279
4617
  });
3280
4618
  }
3281
4619
  }
3282
- for (const name of Object.keys(table.actions ?? {})) {
3283
- const fnName = `action_${name}`;
4620
+ for (const name2 of Object.keys(table.actions ?? {})) {
4621
+ const fnName = `action_${name2}`;
3284
4622
  if (fnName.length > MAX_IDENTIFIER_LENGTH) {
3285
4623
  ctx.addIssue({
3286
4624
  code: "custom",
3287
4625
  message: `derived action function name '${fnName}' exceeds Postgres's ${MAX_IDENTIFIER_LENGTH}-byte identifier limit \u2014 shorten the action name`,
3288
- path: ["actions", name]
4626
+ path: ["actions", name2]
3289
4627
  });
3290
4628
  }
3291
4629
  }
@@ -3337,14 +4675,14 @@ function comparisonTypeIssue(left, right, columns) {
3337
4675
  return null;
3338
4676
  }
3339
4677
  const column = left.kind === "column" ? left : right.kind === "column" ? right : null;
3340
- const literal = left.kind === "literal" ? left : right.kind === "literal" ? right : null;
3341
- if (!column || !literal || literal.value === null) return null;
4678
+ const literal2 = left.kind === "literal" ? left : right.kind === "literal" ? right : null;
4679
+ if (!column || !literal2 || literal2.value === null) return null;
3342
4680
  const columnGroup = groupOf(column);
3343
4681
  if (!columnGroup) return null;
3344
- if (typeof literal.value === "string") return null;
3345
- const literalGroup = typeof literal.value === "number" ? "numeric" : "boolean";
4682
+ if (typeof literal2.value === "string") return null;
4683
+ const literalGroup = typeof literal2.value === "number" ? "numeric" : "boolean";
3346
4684
  if (literalGroup !== columnGroup) {
3347
- return `policy compares '${column.name}' (${columns[column.name].type}) with a ${typeof literal.value} literal \u2014 incomparable types`;
4685
+ return `policy compares '${column.name}' (${columns[column.name].type}) with a ${typeof literal2.value} literal \u2014 incomparable types`;
3348
4686
  }
3349
4687
  return null;
3350
4688
  }
@@ -3378,7 +4716,7 @@ function policyStatements(schema, table) {
3378
4716
  const policyName2 = (suffix) => quoteIdentifier(policyName(table.table, suffix));
3379
4717
  const statements = POLICY_SUFFIXES.map((suffix) => `DROP POLICY IF EXISTS ${policyName2(suffix)} ON ${qualified}`);
3380
4718
  const columnTypes = Object.fromEntries(
3381
- Object.entries(table.columns ?? {}).map(([name, column]) => [name, column.type])
4719
+ Object.entries(table.columns ?? {}).map(([name2, column]) => [name2, column.type])
3382
4720
  );
3383
4721
  const read = table.policies?.read;
3384
4722
  if (read) {
@@ -3430,8 +4768,8 @@ function syncedViewStatements({
3430
4768
  const backing = backingMap.get(table.table);
3431
4769
  if (!backing) continue;
3432
4770
  const viewName = `${schema}.${quoteIdentifier(table.table)}`;
3433
- const physical = splitPhysicalRelation(backing.physicalRelation);
3434
- const physicalTarget = `${quoteIdentifier(physical.schema)}.${quoteIdentifier(physical.table)}`;
4771
+ const physical2 = splitPhysicalRelation(backing.physicalRelation);
4772
+ const physicalTarget = `${quoteIdentifier(physical2.schema)}.${quoteIdentifier(physical2.table)}`;
3435
4773
  const columnTypes = mergeSyncedViewColumnTypes({
3436
4774
  table,
3437
4775
  canonicalColumnTypes: columnTypesByTable?.get(table.table),
@@ -3441,12 +4779,19 @@ function syncedViewStatements({
3441
4779
  const readPolicyIr = readPolicyIrByTable?.get(table.table);
3442
4780
  const readPolicy = table.policies?.read;
3443
4781
  const whereClause = readPolicyIr ? ` WHERE ${compilePredIrToSql(readPolicyIr, {
4782
+ namespace: definition.namespace,
3444
4783
  columnTypes,
3445
4784
  columnNames: columnNamesByTable?.get(table.table)
3446
4785
  })}` : readPolicy ? ` WHERE ${compilePredicate(readPolicy.ast, narrowToV1ColumnTypes(columnTypes))}` : " WHERE false";
3447
4786
  if (backingPrivileges === "grant") {
3448
- statements.push(`GRANT USAGE ON SCHEMA ${quoteIdentifier(physical.schema)} TO ${owner}`);
4787
+ statements.push(`GRANT USAGE ON SCHEMA ${quoteIdentifier(physical2.schema)} TO ${owner}`);
3449
4788
  statements.push(`GRANT SELECT ON ${physicalTarget} TO ${owner}`);
4789
+ statements.push(`DO ${quoteLiteral(`BEGIN
4790
+ IF NOT (pg_catalog.has_schema_privilege(${quoteLiteral(executionOwnerRole)}, ${quoteLiteral(physical2.schema)}, 'USAGE')
4791
+ AND pg_catalog.has_table_privilege(${quoteLiteral(executionOwnerRole)}, ${quoteLiteral(physicalTarget)}, 'SELECT')) THEN
4792
+ RAISE EXCEPTION 'Synced backing owner lacks effective USAGE/SELECT; repair managed serving reader access before ORM apply' USING ERRCODE = '42501';
4793
+ END IF;
4794
+ END`)}`);
3450
4795
  }
3451
4796
  statements.push(
3452
4797
  `CREATE OR REPLACE VIEW ${viewName} AS SELECT * FROM ${physicalTarget}${whereClause}`
@@ -3462,14 +4807,14 @@ function mergeSyncedViewColumnTypes({
3462
4807
  columnNames
3463
4808
  }) {
3464
4809
  const fromTable = {};
3465
- for (const [name, def] of Object.entries(table.columns ?? {})) {
3466
- if (def?.type) fromTable[name] = def.type;
4810
+ for (const [name2, def] of Object.entries(table.columns ?? {})) {
4811
+ if (def?.type) fromTable[name2] = def.type;
3467
4812
  }
3468
4813
  if (columnNames) {
3469
- for (const [logical, physical] of Object.entries(columnNames)) {
3470
- const declared = fromTable[physical] ?? fromTable[logical];
4814
+ for (const [logical, physical2] of Object.entries(columnNames)) {
4815
+ const declared = fromTable[physical2] ?? fromTable[logical];
3471
4816
  if (!declared) continue;
3472
- fromTable[physical] = declared;
4817
+ fromTable[physical2] = declared;
3473
4818
  fromTable[logical] = declared;
3474
4819
  }
3475
4820
  }
@@ -3508,8 +4853,8 @@ function syncedBackingRevokeStatements({
3508
4853
  const seen = /* @__PURE__ */ new Set();
3509
4854
  const statements = [];
3510
4855
  for (const backing of backings) {
3511
- const physical = splitPhysicalRelation(backing.physicalRelation);
3512
- const physicalTarget = `${quoteIdentifier(physical.schema)}.${quoteIdentifier(physical.table)}`;
4856
+ const physical2 = splitPhysicalRelation(backing.physicalRelation);
4857
+ const physicalTarget = `${quoteIdentifier(physical2.schema)}.${quoteIdentifier(physical2.table)}`;
3513
4858
  if (seen.has(physicalTarget)) continue;
3514
4859
  seen.add(physicalTarget);
3515
4860
  statements.push(`REVOKE SELECT ON ${physicalTarget} FROM ${owner}`);
@@ -3517,21 +4862,21 @@ function syncedBackingRevokeStatements({
3517
4862
  return statements;
3518
4863
  }
3519
4864
  var FACTORIES = {
3520
- uuid: (name) => pgCore.uuid(name),
3521
- text: (name) => pgCore.text(name),
3522
- integer: (name) => pgCore.integer(name),
4865
+ uuid: (name2) => pgCore.uuid(name2),
4866
+ text: (name2) => pgCore.text(name2),
4867
+ integer: (name2) => pgCore.integer(name2),
3523
4868
  // Mode 'bigint' keeps int8 lossless — mode 'number' silently rounds past
3524
4869
  // 2^53. The read executor serializes BigInt to string for JSON.
3525
- bigint: (name) => pgCore.bigint(name, { mode: "bigint" }),
3526
- numeric: (name) => pgCore.numeric(name),
3527
- double: (name) => pgCore.doublePrecision(name),
3528
- boolean: (name) => pgCore.boolean(name),
3529
- timestamptz: (name) => pgCore.timestamp(name, { withTimezone: true }),
3530
- date: (name) => pgCore.date(name),
3531
- jsonb: (name) => pgCore.jsonb(name)
4870
+ bigint: (name2) => pgCore.bigint(name2, { mode: "bigint" }),
4871
+ numeric: (name2) => pgCore.numeric(name2),
4872
+ double: (name2) => pgCore.doublePrecision(name2),
4873
+ boolean: (name2) => pgCore.boolean(name2),
4874
+ timestamptz: (name2) => pgCore.timestamp(name2, { withTimezone: true }),
4875
+ date: (name2) => pgCore.date(name2),
4876
+ jsonb: (name2) => pgCore.jsonb(name2)
3532
4877
  };
3533
- function buildColumn(name, definition) {
3534
- let column = FACTORIES[definition.type](name);
4878
+ function buildColumn(name2, definition) {
4879
+ let column = FACTORIES[definition.type](name2);
3535
4880
  if (definition.primary_key) column = column.primaryKey();
3536
4881
  if (definition.nullable === false && !definition.primary_key) column = column.notNull();
3537
4882
  if (definition.unique && !definition.primary_key) column = column.unique();
@@ -3562,7 +4907,7 @@ function buildDrizzleEntities(definition) {
3562
4907
  }
3563
4908
  function buildTable(schema, table, built) {
3564
4909
  const columns = Object.fromEntries(
3565
- Object.entries(table.columns ?? {}).map(([name, column]) => [name, buildColumn(name, column)])
4910
+ Object.entries(table.columns ?? {}).map(([name2, column]) => [name2, buildColumn(name2, column)])
3566
4911
  );
3567
4912
  const drizzleTable = schema.table(table.table, columns, (self) => [
3568
4913
  ...buildIndexes(table, self),
@@ -3572,21 +4917,21 @@ function buildTable(schema, table, built) {
3572
4917
  }
3573
4918
  function buildIndexes(table, self) {
3574
4919
  return (table.indexes ?? []).map((definition) => {
3575
- const name = indexName(table.table, definition);
3576
- const factory = definition.unique ? pgCore.uniqueIndex(name) : pgCore.index(name);
4920
+ const name2 = indexName(table.table, definition);
4921
+ const factory = definition.unique ? pgCore.uniqueIndex(name2) : pgCore.index(name2);
3577
4922
  const columns = definition.columns.map((column) => self[column]);
3578
4923
  return factory.on(...columns);
3579
4924
  });
3580
4925
  }
3581
4926
  function buildForeignKeys(table, self, built) {
3582
- return Object.entries(table.links ?? {}).filter(([, link]) => link.enforce).map(([name, link]) => {
4927
+ return Object.entries(table.links ?? {}).filter(([, link]) => link.enforce).map(([name2, link]) => {
3583
4928
  const target = link.to === table.table ? self : built.get(link.to);
3584
4929
  const targetColumn = target && findPrimaryKeyColumn(target);
3585
4930
  if (!target || !targetColumn) {
3586
- throw new OrmDefinitionError(`enforced link '${name}' on '${table.table}' has no built target '${link.to}'`);
4931
+ throw new OrmDefinitionError(`enforced link '${name2}' on '${table.table}' has no built target '${link.to}'`);
3587
4932
  }
3588
4933
  return pgCore.foreignKey({
3589
- name: foreignKeyName(table.table, name),
4934
+ name: foreignKeyName(table.table, name2),
3590
4935
  columns: [self[link.on]],
3591
4936
  foreignColumns: [targetColumn]
3592
4937
  });
@@ -3643,21 +4988,21 @@ function diffStatements(previous, current) {
3643
4988
  function assertUnambiguousDiff(previous, current) {
3644
4989
  const previousTables = tablesOf(previous);
3645
4990
  const currentTables = tablesOf(current);
3646
- const removedTables = [...previousTables.keys()].filter((name) => !currentTables.has(name));
3647
- const addedTables = [...currentTables.keys()].filter((name) => !previousTables.has(name));
4991
+ const removedTables = [...previousTables.keys()].filter((name2) => !currentTables.has(name2));
4992
+ const addedTables = [...currentTables.keys()].filter((name2) => !previousTables.has(name2));
3648
4993
  if (removedTables.length > 0 && addedTables.length > 0) {
3649
4994
  throw new OrmDefinitionError(
3650
4995
  `ambiguous diff: table(s) added (${addedTables.join(", ")}) and removed (${removedTables.join(", ")}) in the same change \u2014 ship the add and the drop as separate changes`
3651
4996
  );
3652
4997
  }
3653
- for (const [name, previousColumns] of previousTables) {
3654
- const currentColumns = currentTables.get(name);
4998
+ for (const [name2, previousColumns] of previousTables) {
4999
+ const currentColumns = currentTables.get(name2);
3655
5000
  if (!currentColumns) continue;
3656
5001
  const removed = [...previousColumns].filter((column) => !currentColumns.has(column));
3657
5002
  const added = [...currentColumns].filter((column) => !previousColumns.has(column));
3658
5003
  if (removed.length > 0 && added.length > 0) {
3659
5004
  throw new OrmDefinitionError(
3660
- `ambiguous diff on '${name}': column(s) added (${added.join(", ")}) and removed (${removed.join(", ")}) in the same change \u2014 declare \`renamed_from\` for a rename, or ship the add and the drop as separate changes`
5005
+ `ambiguous diff on '${name2}': column(s) added (${added.join(", ")}) and removed (${removed.join(", ")}) in the same change \u2014 declare \`renamed_from\` for a rename, or ship the add and the drop as separate changes`
3661
5006
  );
3662
5007
  }
3663
5008
  }
@@ -3676,33 +5021,33 @@ function applyMigrationHints(previous, definition) {
3676
5021
  for (const table of columnTables(definition)) {
3677
5022
  const snapshotTable = findTable(adjusted, schemaName, table.table);
3678
5023
  if (!snapshotTable) continue;
3679
- for (const [name, column] of Object.entries(table.columns ?? {})) {
5024
+ for (const [name2, column] of Object.entries(table.columns ?? {})) {
3680
5025
  if (column.renamed_from) {
3681
5026
  const previousColumn = snapshotTable.columns[column.renamed_from];
3682
- if (previousColumn && snapshotTable.columns[name]) {
5027
+ if (previousColumn && snapshotTable.columns[name2]) {
3683
5028
  throw new OrmDefinitionError(
3684
- `'${table.table}.${name}' declares renamed_from '${column.renamed_from}', but both columns exist in the previous schema \u2014 a rename cannot overwrite a live column`
5029
+ `'${table.table}.${name2}' declares renamed_from '${column.renamed_from}', but both columns exist in the previous schema \u2014 a rename cannot overwrite a live column`
3685
5030
  );
3686
5031
  }
3687
5032
  if (previousColumn) {
3688
5033
  statements.push(
3689
- `ALTER TABLE ${qualified(table.table)} RENAME COLUMN ${quoteIdentifier(column.renamed_from)} TO ${quoteIdentifier(name)}`
5034
+ `ALTER TABLE ${qualified(table.table)} RENAME COLUMN ${quoteIdentifier(column.renamed_from)} TO ${quoteIdentifier(name2)}`
3690
5035
  );
3691
5036
  delete snapshotTable.columns[column.renamed_from];
3692
- snapshotTable.columns[name] = { ...previousColumn, name };
3693
- } else if (!snapshotTable.columns[name]) {
5037
+ snapshotTable.columns[name2] = { ...previousColumn, name: name2 };
5038
+ } else if (!snapshotTable.columns[name2]) {
3694
5039
  throw new OrmDefinitionError(
3695
- `'${table.table}.${name}' declares renamed_from '${column.renamed_from}', but the previous schema has neither column \u2014 check the declared source name`
5040
+ `'${table.table}.${name2}' declares renamed_from '${column.renamed_from}', but the previous schema has neither column \u2014 check the declared source name`
3696
5041
  );
3697
5042
  }
3698
5043
  }
3699
5044
  const cast = column.cast ? castParts(column.cast) : null;
3700
5045
  if (cast) {
3701
- const current = snapshotTable.columns[name];
5046
+ const current = snapshotTable.columns[name2];
3702
5047
  const targetType = sqlTypeName(column.type);
3703
5048
  if (current && current.type !== targetType) {
3704
5049
  statements.push(
3705
- `ALTER TABLE ${qualified(table.table)} ALTER COLUMN ${quoteIdentifier(name)} TYPE ${targetType} USING ${quoteIdentifier(name)}::${targetType}`
5050
+ `ALTER TABLE ${qualified(table.table)} ALTER COLUMN ${quoteIdentifier(name2)} TYPE ${targetType} USING ${quoteIdentifier(name2)}::${targetType}`
3706
5051
  );
3707
5052
  current.type = targetType;
3708
5053
  }
@@ -3719,22 +5064,106 @@ function snapshotTableNames(snapshot, schema) {
3719
5064
  const tables = snapshot.tables ?? {};
3720
5065
  return Object.values(tables).filter((entry) => entry.schema === schema).map((entry) => entry.name);
3721
5066
  }
5067
+
5068
+ // src/engine/materialized-refresh.ts
5069
+ function materializedRefreshFunctions(canonical) {
5070
+ const schema = ormSchemaName(canonical.namespace);
5071
+ const functions = /* @__PURE__ */ new Map();
5072
+ for (const view of canonical.views ?? []) {
5073
+ const materialized = view.resolved?.materialized;
5074
+ if (!materialized) continue;
5075
+ const table = canonical.tables.find((candidate) => candidate.table === materialized.table);
5076
+ const pk = view.primaryKey ?? [];
5077
+ if (!table || pk.length === 0) {
5078
+ throw new OrmDefinitionError(`materialized resolved table '${view.view}' needs its table and primary key`);
5079
+ }
5080
+ functions.set(view.view, refreshFunctionSql({
5081
+ schema,
5082
+ name: view.view,
5083
+ table: materialized.table,
5084
+ compute: materialized.compute,
5085
+ columns: table.columns.map((column) => column.columnName),
5086
+ pk
5087
+ }));
5088
+ }
5089
+ return functions;
5090
+ }
5091
+ function refreshFunctionSql({ schema, name: name2, table, compute, columns, pk }) {
5092
+ const fn = `${quoteIdentifier(schema)}.${quoteIdentifier(materializedRefreshFunctionName(name2))}`;
5093
+ const target = `${quoteIdentifier(schema)}.${quoteIdentifier(table)}`;
5094
+ const source = `${quoteIdentifier(schema)}.${quoteIdentifier(compute)}`;
5095
+ const list = columns.map(quoteIdentifier).join(", ");
5096
+ const others = columns.filter((column) => !pk.includes(column));
5097
+ const keys = pk.map(quoteIdentifier).join(", ");
5098
+ const matches = pk.map((column) => `c.${quoteIdentifier(column)} = m.${quoteIdentifier(column)}`).join(" AND ");
5099
+ const onConflict = others.length === 0 ? "DO NOTHING" : `DO UPDATE SET ${others.map((column) => `${quoteIdentifier(column)} = EXCLUDED.${quoteIdentifier(column)}`).join(", ")}
5100
+ WHERE (${others.map((column) => `m.${quoteIdentifier(column)}`).join(", ")}) IS DISTINCT FROM (${others.map((column) => `EXCLUDED.${quoteIdentifier(column)}`).join(", ")})`;
5101
+ const lockKey = quoteLiteral(materializeLockKey(schema, name2));
5102
+ return `CREATE OR REPLACE FUNCTION ${fn}(wait boolean DEFAULT false)
5103
+ RETURNS TABLE(upserted bigint, deleted bigint, locked boolean)
5104
+ LANGUAGE plpgsql
5105
+ SECURITY INVOKER
5106
+ SET search_path = pg_catalog, pg_temp
5107
+ AS $orm_refresh$
5108
+ #variable_conflict use_column
5109
+ DECLARE
5110
+ caller_claims text := current_setting('request.jwt.claims', true);
5111
+ BEGIN
5112
+ IF wait THEN
5113
+ PERFORM pg_advisory_xact_lock(hashtext(${lockKey}));
5114
+ ELSIF NOT pg_try_advisory_xact_lock(hashtext(${lockKey})) THEN
5115
+ RETURN QUERY SELECT 0::bigint, 0::bigint, false;
5116
+ RETURN;
5117
+ END IF;
5118
+ PERFORM set_config('request.jwt.claims', ${quoteLiteral(MATERIALIZE_CLAIMS_JSON)}, true);
5119
+ RETURN QUERY
5120
+ WITH up AS (
5121
+ INSERT INTO ${target} AS m (${list})
5122
+ SELECT ${list} FROM ${source}
5123
+ ON CONFLICT (${keys}) ${onConflict}
5124
+ RETURNING 1
5125
+ ), gone AS (
5126
+ DELETE FROM ${target} AS m
5127
+ WHERE NOT EXISTS (SELECT 1 FROM ${source} AS c WHERE ${matches})
5128
+ RETURNING 1
5129
+ )
5130
+ SELECT (SELECT count(*) FROM up), (SELECT count(*) FROM gone), true;
5131
+ -- NULL resets to the session value when the caller had none.
5132
+ PERFORM set_config('request.jwt.claims', caller_claims, true);
5133
+ END
5134
+ $orm_refresh$`;
5135
+ }
5136
+ function refreshFunctionStatements({ namespace, previous, current }) {
5137
+ const schema = quoteIdentifier(ormSchemaName(namespace));
5138
+ const statements = [];
5139
+ for (const name2 of Object.keys(previous).sort()) {
5140
+ if (!current.has(name2)) statements.push(`DROP FUNCTION IF EXISTS ${schema}.${quoteIdentifier(materializedRefreshFunctionName(name2))}(boolean)`);
5141
+ }
5142
+ for (const [name2, create] of [...current].sort(([a], [b]) => a.localeCompare(b))) {
5143
+ if (previous[name2] === create) continue;
5144
+ const fn = `${schema}.${quoteIdentifier(materializedRefreshFunctionName(name2))}(boolean)`;
5145
+ statements.push(create, `REVOKE ALL ON FUNCTION ${fn} FROM PUBLIC`);
5146
+ }
5147
+ return statements;
5148
+ }
5149
+
5150
+ // src/engine/canonical2.ts
3722
5151
  var FACTORIES2 = {
3723
- uuid: (name) => pgCore.uuid(name),
3724
- text: (name) => pgCore.text(name),
3725
- integer: (name) => pgCore.integer(name),
5152
+ uuid: (name2) => pgCore.uuid(name2),
5153
+ text: (name2) => pgCore.text(name2),
5154
+ integer: (name2) => pgCore.integer(name2),
3726
5155
  // Mode 'bigint' keeps int8 lossless — mode 'number' silently rounds past
3727
5156
  // 2^53. The read executor serializes BigInt to string for JSON.
3728
- bigint: (name) => pgCore.bigint(name, { mode: "bigint" }),
3729
- numeric: (name) => pgCore.numeric(name),
3730
- double: (name) => pgCore.doublePrecision(name),
3731
- boolean: (name) => pgCore.boolean(name),
3732
- timestamptz: (name) => pgCore.timestamp(name, { withTimezone: true }),
3733
- date: (name) => pgCore.date(name),
3734
- jsonb: (name) => pgCore.jsonb(name)
5157
+ bigint: (name2) => pgCore.bigint(name2, { mode: "bigint" }),
5158
+ numeric: (name2) => pgCore.numeric(name2),
5159
+ double: (name2) => pgCore.doublePrecision(name2),
5160
+ boolean: (name2) => pgCore.boolean(name2),
5161
+ timestamptz: (name2) => pgCore.timestamp(name2, { withTimezone: true }),
5162
+ date: (name2) => pgCore.date(name2),
5163
+ jsonb: (name2) => pgCore.jsonb(name2)
3735
5164
  };
3736
5165
  var SCHEMA_IMPORT_KEY2 = "$schema";
3737
- var enumImportKey = (name) => `$enum_${name}`;
5166
+ var enumImportKey = (name2) => `$enum_${name2}`;
3738
5167
  var columnTablesV2 = (definition) => definition.tables.filter((table) => !table.source);
3739
5168
  function buildDrizzleEntitiesV2(definition, options = {}) {
3740
5169
  const schemaName = ormSchemaName(definition.namespace);
@@ -3831,18 +5260,18 @@ function rewriteSyncedSourceRelationsInViewQuery({
3831
5260
  return rewritten;
3832
5261
  });
3833
5262
  }
3834
- function transformSqlOutsideLiterals(sql5, transform) {
5263
+ function transformSqlOutsideLiterals(sql7, transform) {
3835
5264
  const out = [];
3836
5265
  let state = { kind: "normal" };
3837
5266
  let i = 0;
3838
5267
  let codeStart = 0;
3839
5268
  const emitCode = (end) => {
3840
- if (end > codeStart) out.push(transform(sql5.slice(codeStart, end)));
5269
+ if (end > codeStart) out.push(transform(sql7.slice(codeStart, end)));
3841
5270
  codeStart = end;
3842
5271
  };
3843
- while (i < sql5.length) {
3844
- const c = sql5[i];
3845
- const next = i + 1 < sql5.length ? sql5[i + 1] : "";
5272
+ while (i < sql7.length) {
5273
+ const c = sql7[i];
5274
+ const next = i + 1 < sql7.length ? sql7[i + 1] : "";
3846
5275
  switch (state.kind) {
3847
5276
  case "normal": {
3848
5277
  if (c === "-" && next === "-") {
@@ -3862,7 +5291,7 @@ function transformSqlOutsideLiterals(sql5, transform) {
3862
5291
  break;
3863
5292
  }
3864
5293
  if ((c === "E" || c === "e") && next === "'") {
3865
- const prev = i > 0 ? sql5[i - 1] : "";
5294
+ const prev = i > 0 ? sql7[i - 1] : "";
3866
5295
  if (!/[A-Za-z0-9_]/.test(prev)) {
3867
5296
  emitCode(i);
3868
5297
  state = { kind: "e-single" };
@@ -3881,9 +5310,9 @@ function transformSqlOutsideLiterals(sql5, transform) {
3881
5310
  break;
3882
5311
  }
3883
5312
  if (c === "$") {
3884
- const prev = i > 0 ? sql5[i - 1] : "";
5313
+ const prev = i > 0 ? sql7[i - 1] : "";
3885
5314
  if (!/[A-Za-z0-9_$]/.test(prev)) {
3886
- const tagMatch = /^\$([A-Za-z_][A-Za-z0-9_]*)?\$/.exec(sql5.slice(i));
5315
+ const tagMatch = /^\$([A-Za-z_][A-Za-z0-9_]*)?\$/.exec(sql7.slice(i));
3887
5316
  if (tagMatch) {
3888
5317
  emitCode(i);
3889
5318
  state = { kind: "dollar", tag: tagMatch[1] ?? "" };
@@ -3915,8 +5344,8 @@ function transformSqlOutsideLiterals(sql5, transform) {
3915
5344
  break;
3916
5345
  }
3917
5346
  case "e-single": {
3918
- if (c === "\\" && i + 1 < sql5.length) {
3919
- out.push(c, sql5[i + 1]);
5347
+ if (c === "\\" && i + 1 < sql7.length) {
5348
+ out.push(c, sql7[i + 1]);
3920
5349
  i += 2;
3921
5350
  break;
3922
5351
  }
@@ -3938,7 +5367,7 @@ function transformSqlOutsideLiterals(sql5, transform) {
3938
5367
  }
3939
5368
  case "dollar": {
3940
5369
  const closer = `$${state.tag}$`;
3941
- if (sql5.startsWith(closer, i)) {
5370
+ if (sql7.startsWith(closer, i)) {
3942
5371
  out.push(closer);
3943
5372
  state = { kind: "normal" };
3944
5373
  i += closer.length;
@@ -3982,15 +5411,15 @@ function transformSqlOutsideLiterals(sql5, transform) {
3982
5411
  }
3983
5412
  }
3984
5413
  }
3985
- emitCode(sql5.length);
5414
+ emitCode(sql7.length);
3986
5415
  return out.join("");
3987
5416
  }
3988
5417
  function escapeRegExp(value) {
3989
5418
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3990
5419
  }
3991
- function sqlReferencesQualifiedRelation(sql5, sourceSchema, sourceName) {
5420
+ function sqlReferencesQualifiedRelation(sql7, sourceSchema, sourceName) {
3992
5421
  let referenced = false;
3993
- transformSqlOutsideLiterals(sql5, (segment) => {
5422
+ transformSqlOutsideLiterals(sql7, (segment) => {
3994
5423
  if (qualifiedRelationPattern(sourceSchema, sourceName).test(segment)) referenced = true;
3995
5424
  return segment;
3996
5425
  });
@@ -4001,16 +5430,18 @@ function qualifiedRelationPattern(sourceSchema, sourceName) {
4001
5430
  const namePattern = `(?:"${escapeRegExp(sourceName)}"|${escapeRegExp(sourceName)})`;
4002
5431
  return new RegExp(`(?<![\\w."])${schemaPattern}\\.${namePattern}(?![\\w"])`, "g");
4003
5432
  }
4004
- function replaceQualifiedRelation(sql5, sourceSchema, sourceName, boundSchema, boundTable) {
4005
- return sql5.replace(qualifiedRelationPattern(sourceSchema, sourceName), `"${boundSchema}"."${boundTable}"`);
5433
+ function replaceQualifiedRelation(sql7, sourceSchema, sourceName, boundSchema, boundTable) {
5434
+ return sql7.replace(qualifiedRelationPattern(sourceSchema, sourceName), `"${boundSchema}"."${boundTable}"`);
4006
5435
  }
4007
5436
  function generateSnapshotV2(definition, previousId3) {
4008
5437
  const { imports, schemaName } = buildDrizzleEntitiesV2(definition);
4009
5438
  const snapshot = api.generateDrizzleJson(imports, previousId3, [schemaName]);
4010
5439
  if ((definition.views?.length ?? 0) > 0 || definition.tables.some((table) => table.foreignKeys?.length)) {
4011
5440
  const extended = snapshot;
5441
+ const refreshFunctions = materializedRefreshFunctions(definition);
4012
5442
  extended._sequenceOrm = {
4013
5443
  ...definition.tables.some((table) => table.foreignKeys?.length) ? { foreignKeys: Object.fromEntries(definition.tables.flatMap((table) => (table.foreignKeys ?? []).map((fk) => [`${table.table}.${fk.name}`, structuredClone(fk)]))) } : {},
5444
+ ...refreshFunctions.size > 0 ? { refreshFunctions: Object.fromEntries(refreshFunctions) } : {},
4014
5445
  viewDependencies: Object.fromEntries(
4015
5446
  (definition.views ?? []).map((view) => [view.view, [...view.dependencies].sort()])
4016
5447
  )
@@ -4034,9 +5465,9 @@ function buildTable2(schema, table, built, enums, byName, introspection) {
4034
5465
  throw new OrmDefinitionError(`table '${table.table}' declares no primaryKey`);
4035
5466
  }
4036
5467
  const columnNames = new Set(table.columns.map((column) => column.columnName));
4037
- for (const name of primaryKeyColumns) {
4038
- if (!columnNames.has(name)) {
4039
- throw new OrmDefinitionError(`primaryKey of '${table.table}' references unknown column '${name}'`);
5468
+ for (const name2 of primaryKeyColumns) {
5469
+ if (!columnNames.has(name2)) {
5470
+ throw new OrmDefinitionError(`primaryKey of '${table.table}' references unknown column '${name2}'`);
4040
5471
  }
4041
5472
  }
4042
5473
  const singlePrimaryKey = primaryKeyColumns.length === 1 ? primaryKeyColumns[0] : void 0;
@@ -4050,15 +5481,19 @@ function buildTable2(schema, table, built, enums, byName, introspection) {
4050
5481
  const drizzleTable = schema.table(table.table, columns, (self) => [
4051
5482
  ...buildCompositePrimaryKey(table, primaryKeyColumns, self),
4052
5483
  ...buildIndexes2(table, self),
5484
+ ...(table.checks ?? []).map((check) => pgCore.check(check.name, drizzleOrm.sql.raw(compileCheckExpression(check.expression, table.columns)))),
4053
5485
  ...buildForeignKeys2(table, self, built, byName),
4054
5486
  ...(table.foreignKeys ?? []).map((fk) => {
4055
5487
  const target = fk.target === table.table ? self : built.get(fk.target);
4056
5488
  if (!target) throw new OrmDefinitionError(`foreign key '${fk.name}' has no target '${fk.target}'`);
4057
- return pgCore.foreignKey({
5489
+ let constraint = pgCore.foreignKey({
4058
5490
  name: fk.name,
4059
5491
  columns: fk.columns.map((column) => self[column]),
4060
5492
  foreignColumns: fk.references.map((column) => target[column])
4061
5493
  });
5494
+ if (fk.onDelete !== void 0) constraint = constraint.onDelete(fk.onDelete);
5495
+ if (fk.onUpdate !== void 0) constraint = constraint.onUpdate(fk.onUpdate);
5496
+ return constraint;
4062
5497
  })
4063
5498
  ]);
4064
5499
  return drizzleTable.enableRLS();
@@ -4118,8 +5553,8 @@ function buildIndexes2(table, self) {
4118
5553
  throw new OrmDefinitionError(`index on '${table.table}' references unknown column '${column}'`);
4119
5554
  }
4120
5555
  }
4121
- const name = indexName(table.table, definition);
4122
- const factory = definition.unique ? pgCore.uniqueIndex(name) : pgCore.index(name);
5556
+ const name2 = indexName(table.table, definition);
5557
+ const factory = definition.unique ? pgCore.uniqueIndex(name2) : pgCore.index(name2);
4123
5558
  const columns = definition.columns.map((column) => self[column]);
4124
5559
  return factory.on(...columns);
4125
5560
  });
@@ -4149,11 +5584,14 @@ function buildForeignKeys2(table, self, built, byName) {
4149
5584
  `enforced link '${link.name}' on '${table.table}' references unknown column '${targetColumn ? link.on : referencedColumn}'`
4150
5585
  );
4151
5586
  }
4152
- return pgCore.foreignKey({
5587
+ let constraint = pgCore.foreignKey({
4153
5588
  name: foreignKeyName(table.table, link.name),
4154
5589
  columns: [sourceColumn],
4155
5590
  foreignColumns: [targetColumn]
4156
5591
  });
5592
+ if (link.onDelete !== void 0) constraint = constraint.onDelete(link.onDelete);
5593
+ if (link.onUpdate !== void 0) constraint = constraint.onUpdate(link.onUpdate);
5594
+ return constraint;
4157
5595
  });
4158
5596
  }
4159
5597
  var singlePrimaryKeyOf = (table) => table.primaryKey?.length === 1 ? table.primaryKey[0] : void 0;
@@ -4195,33 +5633,33 @@ function applyMigrationHintsV2(previous, definition) {
4195
5633
  const snapshotTable = findTable2(adjusted, schemaName, table.table);
4196
5634
  if (!snapshotTable) continue;
4197
5635
  for (const column of table.columns) {
4198
- const name = column.columnName;
5636
+ const name2 = column.columnName;
4199
5637
  if (column.renamedFrom) {
4200
5638
  const previousColumn = snapshotTable.columns[column.renamedFrom];
4201
- if (previousColumn && snapshotTable.columns[name]) {
5639
+ if (previousColumn && snapshotTable.columns[name2]) {
4202
5640
  throw new OrmDefinitionError(
4203
- `'${table.table}.${name}' declares renamedFrom '${column.renamedFrom}', but both columns exist in the previous schema \u2014 a rename cannot overwrite a live column`
5641
+ `'${table.table}.${name2}' declares renamedFrom '${column.renamedFrom}', but both columns exist in the previous schema \u2014 a rename cannot overwrite a live column`
4204
5642
  );
4205
5643
  }
4206
5644
  if (previousColumn) {
4207
5645
  statements.push(
4208
- `ALTER TABLE ${qualified(table.table)} RENAME COLUMN ${quoteIdentifier(column.renamedFrom)} TO ${quoteIdentifier(name)}`
5646
+ `ALTER TABLE ${qualified(table.table)} RENAME COLUMN ${quoteIdentifier(column.renamedFrom)} TO ${quoteIdentifier(name2)}`
4209
5647
  );
4210
5648
  delete snapshotTable.columns[column.renamedFrom];
4211
- snapshotTable.columns[name] = { ...previousColumn, name };
4212
- } else if (!snapshotTable.columns[name]) {
5649
+ snapshotTable.columns[name2] = { ...previousColumn, name: name2 };
5650
+ } else if (!snapshotTable.columns[name2]) {
4213
5651
  throw new OrmDefinitionError(
4214
- `'${table.table}.${name}' declares renamedFrom '${column.renamedFrom}', but the previous schema has neither column \u2014 check the declared source name`
5652
+ `'${table.table}.${name2}' declares renamedFrom '${column.renamedFrom}', but the previous schema has neither column \u2014 check the declared source name`
4215
5653
  );
4216
5654
  }
4217
5655
  }
4218
5656
  const cast = column.cast ? castParts(column.cast) : null;
4219
5657
  if (cast) {
4220
- const current = snapshotTable.columns[name];
5658
+ const current = snapshotTable.columns[name2];
4221
5659
  const { snapshotType, sqlType } = castTargetTypes(column, enumsByName, schemaName, table.table);
4222
5660
  if (current && current.type !== snapshotType) {
4223
5661
  statements.push(
4224
- `ALTER TABLE ${qualified(table.table)} ALTER COLUMN ${quoteIdentifier(name)} TYPE ${sqlType} USING ${quoteIdentifier(name)}::${sqlType}`
5662
+ `ALTER TABLE ${qualified(table.table)} ALTER COLUMN ${quoteIdentifier(name2)} TYPE ${sqlType} USING ${quoteIdentifier(name2)}::${sqlType}`
4225
5663
  );
4226
5664
  current.type = snapshotType;
4227
5665
  }
@@ -4232,8 +5670,8 @@ function applyMigrationHintsV2(previous, definition) {
4232
5670
  }
4233
5671
  function castTargetTypes(column, enums, schemaName, table) {
4234
5672
  if (typeof column.type === "string") {
4235
- const name = sqlTypeName(column.type);
4236
- return { snapshotType: name, sqlType: name };
5673
+ const name2 = sqlTypeName(column.type);
5674
+ return { snapshotType: name2, sqlType: name2 };
4237
5675
  }
4238
5676
  const enumName = column.type.enum;
4239
5677
  if (!enums.has(enumName)) {
@@ -4274,9 +5712,23 @@ function findTable2(snapshot, schema, table) {
4274
5712
  const tables = snapshot.tables ?? {};
4275
5713
  return Object.values(tables).find((entry) => entry.schema === schema && entry.name === table);
4276
5714
  }
4277
- async function planMigrationV2({
5715
+ function planMigrationV2(input) {
5716
+ return planWithViewRecreation({ ...input, viewRecreation: "affected" });
5717
+ }
5718
+ async function planCommittedMigrationV2({
4278
5719
  canonical,
4279
- previousSnapshot
5720
+ previousSnapshot,
5721
+ reproduces
5722
+ }) {
5723
+ const current = await planMigrationV2({ canonical, previousSnapshot });
5724
+ if (reproduces(current)) return { plan: current, reproduced: true };
5725
+ const legacy = await planWithViewRecreation({ canonical, previousSnapshot, viewRecreation: "all-on-base-change" });
5726
+ return reproduces(legacy) ? { plan: legacy, reproduced: true } : { plan: current, reproduced: false };
5727
+ }
5728
+ async function planWithViewRecreation({
5729
+ canonical,
5730
+ previousSnapshot,
5731
+ viewRecreation
4280
5732
  }) {
4281
5733
  const schema = ormSchemaName(canonical.namespace);
4282
5734
  const previous = previousSnapshot ? normalizeSnapshot(previousSnapshot) : emptySnapshot(canonical.namespace);
@@ -4294,14 +5746,21 @@ async function planMigrationV2({
4294
5746
  currentViews,
4295
5747
  previousDependencies,
4296
5748
  currentDependencies,
4297
- baseChanged: hints.statements.length > 0 || baseDdl.length > 0
5749
+ reach: viewReach({
5750
+ viewRecreation,
5751
+ hintStatements: hints.statements,
5752
+ baseDdl,
5753
+ previous,
5754
+ adjusted: hints.adjusted,
5755
+ current: snapshot
5756
+ })
4298
5757
  });
4299
5758
  const dropOrder = topologicalViewNames(
4300
- new Set([...affected].filter((name) => previousViews.has(name))),
5759
+ new Set([...affected].filter((name2) => previousViews.has(name2))),
4301
5760
  previousDependencies
4302
5761
  ).reverse();
4303
5762
  const createOrder = topologicalViewNames(
4304
- new Set([...affected].filter((name) => currentViews.has(name))),
5763
+ new Set([...affected].filter((name2) => currentViews.has(name2))),
4305
5764
  currentDependencies
4306
5765
  );
4307
5766
  const dropDdl = await generatedViewStatements({
@@ -4318,16 +5777,21 @@ async function planMigrationV2({
4318
5777
  });
4319
5778
  const currentViewDefinitions = new Map((canonical.views ?? []).map((view) => [view.view, view]));
4320
5779
  const statements = [
4321
- ...dropOrder.map((name) => ({ kind: "ddl", sql: requiredViewStatement(dropDdl, name, schema, "DROP") })),
5780
+ ...dropOrder.map((name2) => ({ kind: "ddl", sql: requiredViewStatement(dropDdl, name2, schema, "DROP") })),
4322
5781
  ...hints.statements.map((statement) => ({ kind: "hint", sql: statement })),
4323
5782
  ...baseDdl.map((statement) => ({ kind: "ddl", sql: statement.replace(/;\s*$/, "") })),
4324
- ...foreignKeyTimingStatements(canonical, previous, baseDdl).map((sql5) => ({ kind: "ddl", sql: sql5 })),
4325
- ...createOrder.map((name) => ({ kind: "ddl", sql: requiredViewStatement(createDdl, name, schema, "CREATE") })),
4326
- ...createOrder.map((name) => {
4327
- const view = currentViewDefinitions.get(name);
4328
- if (!view) throw new OrmDefinitionError(`missing canonical definition for view '${name}'`);
5783
+ ...foreignKeyTimingStatements(canonical, previous, baseDdl).map((sql7) => ({ kind: "ddl", sql: sql7 })),
5784
+ ...createOrder.map((name2) => ({ kind: "ddl", sql: requiredViewStatement(createDdl, name2, schema, "CREATE") })),
5785
+ ...createOrder.map((name2) => {
5786
+ const view = currentViewDefinitions.get(name2);
5787
+ if (!view) throw new OrmDefinitionError(`missing canonical definition for view '${name2}'`);
4329
5788
  return { kind: "ddl", sql: validateViewCatalogStatement({ canonical, view, schema }) };
4330
- })
5789
+ }),
5790
+ ...refreshFunctionStatements({
5791
+ namespace: canonical.namespace,
5792
+ previous: previous._sequenceOrm?.refreshFunctions ?? {},
5793
+ current: materializedRefreshFunctions(canonical)
5794
+ }).map((sql7) => ({ kind: "ddl", sql: sql7 }))
4331
5795
  ];
4332
5796
  return { namespace: canonical.namespace, schema, statements, snapshot };
4333
5797
  }
@@ -4350,9 +5814,9 @@ function foreignKeyTimingStatements(canonical, previous, baseDdl) {
4350
5814
  }
4351
5815
  return result;
4352
5816
  }
4353
- function foreignKeyTimingSql(schema, table, name, timing) {
5817
+ function foreignKeyTimingSql(schema, table, name2, timing) {
4354
5818
  const clause = timing === void 0 ? "NOT DEFERRABLE" : timing === "initially-deferred" ? "DEFERRABLE INITIALLY DEFERRED" : "DEFERRABLE INITIALLY IMMEDIATE";
4355
- return `ALTER TABLE ${quoteIdentifier(schema)}.${quoteIdentifier(table)} ALTER CONSTRAINT ${quoteIdentifier(name)} ${clause}`;
5819
+ return `ALTER TABLE ${quoteIdentifier(schema)}.${quoteIdentifier(table)} ALTER CONSTRAINT ${quoteIdentifier(name2)} ${clause}`;
4356
5820
  }
4357
5821
  function validateViewCatalogStatement({
4358
5822
  canonical,
@@ -4371,9 +5835,9 @@ function validateViewCatalogStatement({
4371
5835
  }
4372
5836
  const expectedDependencies = sqlTextArray(
4373
5837
  view.dependencies.map((dependency) => {
4374
- const physical = relationIdentity.get(dependency);
4375
- if (!physical) throw new OrmDefinitionError(`view '${view.view}' depends on unknown relation '${dependency}'`);
4376
- return physical;
5838
+ const physical2 = relationIdentity.get(dependency);
5839
+ if (!physical2) throw new OrmDefinitionError(`view '${view.view}' depends on unknown relation '${dependency}'`);
5840
+ return physical2;
4377
5841
  }).sort()
4378
5842
  );
4379
5843
  const allowedFunctions = sqlTextArray(VIEW_QUERY_FUNCTION_ALLOWLIST);
@@ -4481,30 +5945,111 @@ function withoutViews(snapshot) {
4481
5945
  delete clone._sequenceOrm;
4482
5946
  return clone;
4483
5947
  }
5948
+ var DROPS_TYPE = /^\s*DROP\s+TYPE\b/i;
5949
+ function viewReach({
5950
+ viewRecreation,
5951
+ hintStatements,
5952
+ baseDdl,
5953
+ previous,
5954
+ adjusted,
5955
+ current
5956
+ }) {
5957
+ if (hintStatements.length === 0 && baseDdl.length === 0) return /* @__PURE__ */ new Set();
5958
+ if (viewRecreation === "all-on-base-change") return "all";
5959
+ if (baseDdl.some((statement) => DROPS_TYPE.test(statement))) return "all";
5960
+ const dependencies = snapshotViewDependencies(adjusted);
5961
+ if ([...snapshotViews(adjusted).keys()].some((name2) => dependencies[name2] === void 0)) return "all";
5962
+ return baseChangesReachingViews({ previous, adjusted, current });
5963
+ }
5964
+ var SNAPSHOT_BOOKKEEPING = /* @__PURE__ */ new Set(["id", "prevId", "version", "dialect", "_meta", "views", "_sequenceOrm", "enums"]);
5965
+ var VIEW_SAFE_TABLE_FIELDS = /* @__PURE__ */ new Set([
5966
+ "indexes",
5967
+ "foreignKeys",
5968
+ "uniqueConstraints",
5969
+ "checkConstraints",
5970
+ "policies",
5971
+ "isRLSEnabled"
5972
+ ]);
5973
+ var VIEW_SAFE_COLUMN_FIELDS = /* @__PURE__ */ new Set(["notNull", "default", "isUnique", "uniqueName", "nullsNotDistinct"]);
5974
+ function baseChangesReachingViews({
5975
+ previous,
5976
+ adjusted,
5977
+ current
5978
+ }) {
5979
+ const before = adjusted;
5980
+ const after = current;
5981
+ for (const section of /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])) {
5982
+ if (SNAPSHOT_BOOKKEEPING.has(section) || section === "tables") continue;
5983
+ if (stableJson(before[section]) !== stableJson(after[section])) return "all";
5984
+ }
5985
+ const relations = /* @__PURE__ */ new Set();
5986
+ const previousTables = snapshotTables(previous);
5987
+ const adjustedTables = snapshotTables(adjusted);
5988
+ for (const key of changedEntryKeys(previousTables, adjustedTables)) {
5989
+ const table = previousTables[key] ?? adjustedTables[key];
5990
+ if (table) relations.add(table.name);
5991
+ }
5992
+ const currentTables = snapshotTables(current);
5993
+ for (const key of /* @__PURE__ */ new Set([...Object.keys(adjustedTables), ...Object.keys(currentTables)])) {
5994
+ const was = adjustedTables[key];
5995
+ const is2 = currentTables[key];
5996
+ if (!was || !is2) {
5997
+ const table = was ?? is2;
5998
+ if (table) relations.add(table.name);
5999
+ } else if (tableChangeReachesViews({ was, is: is2 })) {
6000
+ relations.add(is2.name);
6001
+ }
6002
+ }
6003
+ return relations;
6004
+ }
6005
+ function tableChangeReachesViews({ was, is: is2 }) {
6006
+ for (const field of /* @__PURE__ */ new Set([...Object.keys(was), ...Object.keys(is2)])) {
6007
+ if (field === "columns" || VIEW_SAFE_TABLE_FIELDS.has(field)) continue;
6008
+ if (stableJson(was[field]) !== stableJson(is2[field])) return true;
6009
+ }
6010
+ return Object.entries(was.columns).some(([name2, column]) => {
6011
+ const next = is2.columns[name2];
6012
+ if (!next) return true;
6013
+ return [.../* @__PURE__ */ new Set([...Object.keys(column), ...Object.keys(next)])].some(
6014
+ (field) => !VIEW_SAFE_COLUMN_FIELDS.has(field) && stableJson(column[field]) !== stableJson(next[field])
6015
+ );
6016
+ });
6017
+ }
6018
+ function changedEntryKeys(before, after) {
6019
+ return new Set(
6020
+ [.../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])].filter(
6021
+ (key) => stableJson(before[key]) !== stableJson(after[key])
6022
+ )
6023
+ );
6024
+ }
6025
+ function snapshotTables(snapshot) {
6026
+ return snapshot.tables ?? {};
6027
+ }
4484
6028
  function affectedViewNames({
4485
6029
  previousViews,
4486
6030
  currentViews,
4487
6031
  previousDependencies,
4488
6032
  currentDependencies,
4489
- baseChanged
6033
+ reach
4490
6034
  }) {
4491
6035
  const allNames = /* @__PURE__ */ new Set([...previousViews.keys(), ...currentViews.keys()]);
4492
- if (baseChanged) return allNames;
6036
+ if (reach === "all") return allNames;
6037
+ const dependenciesOf = (name2) => /* @__PURE__ */ new Set([...previousDependencies[name2] ?? [], ...currentDependencies[name2] ?? []]);
4493
6038
  const affected = new Set(
4494
- [...allNames].filter((name) => {
4495
- const before = previousViews.get(name);
4496
- const after = currentViews.get(name);
4497
- return before === void 0 || after === void 0 || stableJson(before) !== stableJson(after);
6039
+ [...allNames].filter((name2) => {
6040
+ const before = previousViews.get(name2);
6041
+ const after = currentViews.get(name2);
6042
+ if (before === void 0 || after === void 0 || stableJson(before) !== stableJson(after)) return true;
6043
+ return [...dependenciesOf(name2)].some((dependency) => reach.has(dependency));
4498
6044
  })
4499
6045
  );
4500
6046
  let grew = true;
4501
6047
  while (grew) {
4502
6048
  grew = false;
4503
- for (const name of allNames) {
4504
- if (affected.has(name)) continue;
4505
- const dependencies = /* @__PURE__ */ new Set([...previousDependencies[name] ?? [], ...currentDependencies[name] ?? []]);
4506
- if ([...dependencies].some((dependency) => affected.has(dependency))) {
4507
- affected.add(name);
6049
+ for (const name2 of allNames) {
6050
+ if (affected.has(name2)) continue;
6051
+ if ([...dependenciesOf(name2)].some((dependency) => affected.has(dependency))) {
6052
+ affected.add(name2);
4508
6053
  grew = true;
4509
6054
  }
4510
6055
  }
@@ -4515,13 +6060,13 @@ function topologicalViewNames(names, dependencies) {
4515
6060
  const remaining = new Set(names);
4516
6061
  const ordered = [];
4517
6062
  while (remaining.size > 0) {
4518
- const ready = [...remaining].filter((name) => (dependencies[name] ?? []).every((dependency) => !remaining.has(dependency))).sort();
6063
+ const ready = [...remaining].filter((name2) => (dependencies[name2] ?? []).every((dependency) => !remaining.has(dependency))).sort();
4519
6064
  if (ready.length === 0) {
4520
6065
  throw new OrmDefinitionError(`view dependencies form a cycle among: ${[...remaining].sort().join(", ")}`);
4521
6066
  }
4522
- for (const name of ready) {
4523
- ordered.push(name);
4524
- remaining.delete(name);
6067
+ for (const name2 of ready) {
6068
+ ordered.push(name2);
6069
+ remaining.delete(name2);
4525
6070
  }
4526
6071
  }
4527
6072
  return ordered;
@@ -4539,18 +6084,18 @@ async function generatedViewStatements({
4539
6084
  holder.views = Object.fromEntries(Object.entries(views).filter(([, view]) => !names.has(view.name)));
4540
6085
  const generated = mode === "drop" ? await diffStatements(snapshot, without) : await diffStatements(without, snapshot);
4541
6086
  const statements = /* @__PURE__ */ new Map();
4542
- for (const name of names) {
4543
- const qualified = `${quoteIdentifier(schema)}.${quoteIdentifier(name)}`;
6087
+ for (const name2 of names) {
6088
+ const qualified = `${quoteIdentifier(schema)}.${quoteIdentifier(name2)}`;
4544
6089
  const prefix = mode === "drop" ? "DROP VIEW" : "CREATE VIEW";
4545
6090
  const statement = generated.find((candidate) => candidate.trimStart().startsWith(`${prefix} ${qualified}`));
4546
- if (statement) statements.set(name, statement.replace(/;\s*$/, ""));
6091
+ if (statement) statements.set(name2, statement.replace(/;\s*$/, ""));
4547
6092
  }
4548
6093
  return statements;
4549
6094
  }
4550
- function requiredViewStatement(statements, name, schema, operation) {
4551
- const statement = statements.get(name);
6095
+ function requiredViewStatement(statements, name2, schema, operation) {
6096
+ const statement = statements.get(name2);
4552
6097
  if (!statement) {
4553
- throw new OrmDefinitionError(`drizzle-kit did not generate ${operation} VIEW for '${schema}.${name}'`);
6098
+ throw new OrmDefinitionError(`drizzle-kit did not generate ${operation} VIEW for '${schema}.${name2}'`);
4554
6099
  }
4555
6100
  return statement;
4556
6101
  }
@@ -4741,15 +6286,15 @@ async function planMigration({
4741
6286
  const ddl = await diffStatements(hints.adjusted, snapshot);
4742
6287
  const policyDrops = policyDropStatements(schema, snapshotTableNames(previous, schema));
4743
6288
  const statements = [
4744
- ...policyDrops.map((sql5) => ({ kind: "security", sql: sql5 })),
4745
- ...hints.statements.map((sql5) => ({ kind: "hint", sql: sql5 })),
4746
- ...ddl.map((sql5) => ({ kind: "ddl", sql: sql5.replace(/;\s*$/, "") })),
4747
- ...securityStatements(definition).map((sql5) => ({ kind: "security", sql: sql5 })),
6289
+ ...policyDrops.map((sql7) => ({ kind: "security", sql: sql7 })),
6290
+ ...hints.statements.map((sql7) => ({ kind: "hint", sql: sql7 })),
6291
+ ...ddl.map((sql7) => ({ kind: "ddl", sql: sql7.replace(/;\s*$/, "") })),
6292
+ ...securityStatements(definition).map((sql7) => ({ kind: "security", sql: sql7 })),
4748
6293
  // PREPARE each action's executes first (plpgsql bodies don't check columns
4749
6294
  // at CREATE), then create the functions. Both reference the tables the DDL
4750
6295
  // above creates, so they close the plan.
4751
- ...actionPrepareChecks(definition).map((sql5) => ({ kind: "function", sql: sql5 })),
4752
- ...actionFunctionStatements(definition).map((sql5) => ({ kind: "function", sql: sql5 }))
6296
+ ...actionPrepareChecks(definition).map((sql7) => ({ kind: "function", sql: sql7 })),
6297
+ ...actionFunctionStatements(definition).map((sql7) => ({ kind: "function", sql: sql7 }))
4753
6298
  ];
4754
6299
  return { namespace: definition.namespace, schema, statements, snapshot };
4755
6300
  }
@@ -4796,9 +6341,13 @@ function renderOrmMigrationExport(exported) {
4796
6341
  if (Buffer.byteLength(text4, "utf8") > ORM_MIGRATION_EXPORT_MAX_BYTES) throw new Error("ORM migration export exceeds the size limit");
4797
6342
  return text4;
4798
6343
  }
4799
- var IR_VERSION = 2;
6344
+ var IR_VERSION = 5;
4800
6345
  var LEGACY_IR_VERSION = 1;
4801
- var irVersionSchema = zod.z.union([zod.z.literal(LEGACY_IR_VERSION), zod.z.literal(IR_VERSION)]);
6346
+ var GUARDED_IR_VERSION = 2;
6347
+ var COMPOSED_IR_VERSION = 3;
6348
+ var SQL_IR_VERSION = 4;
6349
+ var RUNTIME_SQL_IR_VERSION = 5;
6350
+ var irVersionSchema = zod.z.union([zod.z.literal(LEGACY_IR_VERSION), zod.z.literal(GUARDED_IR_VERSION), zod.z.literal(COMPOSED_IR_VERSION), zod.z.literal(SQL_IR_VERSION), zod.z.literal(RUNTIME_SQL_IR_VERSION)]);
4802
6351
  var operationVersionSchema = zod.z.object({ irVersion: irVersionSchema });
4803
6352
  function requiredArtifactIrVersion(operations) {
4804
6353
  let required = LEGACY_IR_VERSION;
@@ -4850,8 +6399,8 @@ var predIrSchema = zod.z.lazy(
4850
6399
  value: zod.z.number(),
4851
6400
  where: predIrSchema.optional()
4852
6401
  }),
4853
- zod.z.object({ kind: zod.z.literal("capability"), key: zod.z.string() }),
4854
- zod.z.object({ kind: zod.z.literal("role"), key: zod.z.string() }),
6402
+ zod.z.object({ kind: zod.z.literal("capability"), key: zod.z.string(), namespace: zod.z.string().optional() }),
6403
+ zod.z.object({ kind: zod.z.literal("role"), key: zod.z.string(), namespace: zod.z.string().optional() }),
4855
6404
  zod.z.object({ kind: zod.z.literal("dynamicFilter"), slot: zod.z.string() }),
4856
6405
  zod.z.object({ kind: zod.z.literal("const"), value: zod.z.boolean() })
4857
6406
  ])
@@ -4879,6 +6428,21 @@ var selectionIrSchema = zod.z.lazy(
4879
6428
  () => zod.z.object({
4880
6429
  typename: zod.z.string(),
4881
6430
  columns: zod.z.array(zod.z.object({ alias: zod.z.string(), column: zod.z.string() })),
6431
+ cells: zod.z.object({
6432
+ alias: zod.z.string(),
6433
+ typename: zod.z.string(),
6434
+ fields: zod.z.array(zod.z.object({
6435
+ alias: zod.z.string(),
6436
+ column: zod.z.string(),
6437
+ typename: zod.z.string(),
6438
+ parts: zod.z.array(zod.z.object({
6439
+ alias: zod.z.string(),
6440
+ part: zod.z.enum(["source", "at", "by", "conflicts"]),
6441
+ fields: zod.z.array(zod.z.object({ alias: zod.z.string(), field: zod.z.enum(["source", "at", "value"]) })).optional(),
6442
+ typename: zod.z.string().optional()
6443
+ }))
6444
+ }))
6445
+ }).optional(),
4882
6446
  relations: zod.z.array(zod.z.object({
4883
6447
  alias: zod.z.string(),
4884
6448
  link: zod.z.string(),
@@ -4900,6 +6464,11 @@ var selectionIrSchema = zod.z.lazy(
4900
6464
  })
4901
6465
  );
4902
6466
  var includeIfSchema = zod.z.object({ variable: zod.z.string(), negated: zod.z.boolean() });
6467
+ var prefetchPlanIrSchema = zod.z.object({
6468
+ source: zod.z.string(),
6469
+ maxStalenessMs: zod.z.number().int().positive().optional(),
6470
+ key: zod.z.union([zod.z.object({ one: valueIrSchema }), zod.z.object({ many: valueIrSchema })])
6471
+ });
4903
6472
  var selectRootSchema = zod.z.object({
4904
6473
  kind: zod.z.literal("select"),
4905
6474
  alias: zod.z.string(),
@@ -4911,7 +6480,8 @@ var selectRootSchema = zod.z.object({
4911
6480
  distinctOn: zod.z.union([zod.z.array(zod.z.string()), zod.z.object({ slot: zod.z.string() })]).optional(),
4912
6481
  limit: limitIrSchema,
4913
6482
  offset: zod.z.object({ variable: zod.z.string().optional(), value: zod.z.number().int().optional() }).optional(),
4914
- includeIf: includeIfSchema.optional()
6483
+ includeIf: includeIfSchema.optional(),
6484
+ prefetch: zod.z.array(prefetchPlanIrSchema).optional()
4915
6485
  });
4916
6486
  var byPkRootSchema = zod.z.object({
4917
6487
  kind: zod.z.literal("byPk"),
@@ -4921,7 +6491,8 @@ var byPkRootSchema = zod.z.object({
4921
6491
  pk: zod.z.array(zod.z.object({ column: zod.z.string(), value: valueIrSchema })).min(1),
4922
6492
  /** Policy predicate — ANDed with the PK match. */
4923
6493
  where: predIrSchema,
4924
- includeIf: includeIfSchema.optional()
6494
+ includeIf: includeIfSchema.optional(),
6495
+ prefetch: zod.z.array(prefetchPlanIrSchema).optional()
4925
6496
  });
4926
6497
  var aggregateRootSchema = zod.z.object({
4927
6498
  kind: zod.z.literal("aggregate"),
@@ -4979,7 +6550,20 @@ var mutationRootSchema = zod.z.object({
4979
6550
  * coalesce view falls back to synced. Both are gated on a matching synced
4980
6551
  * row and carry the write policy in `where` like update/delete.
4981
6552
  */
4982
- op: zod.z.enum(["insert", "insertOne", "update", "updateByPk", "delete", "deleteByPk", "cowOverride", "cowRevert"]),
6553
+ op: zod.z.enum(["insert", "insertOne", "update", "updateByPk", "delete", "deleteByPk", "cowOverride", "cowRevert", "resolvedWrite"]),
6554
+ /**
6555
+ * resolvedWrite: a write to a resolved table (`table` is its view). The
6556
+ * executor gates on the resolved row (write policy in `where`), writes only
6557
+ * the touched cells into the manual source, then re-checks `insertCheck`
6558
+ * against the resolved row. `revert` clears `revertColumns` from the manual
6559
+ * source; `delete` tombstones the row there.
6560
+ */
6561
+ resolvedAction: zod.z.enum(["insert", "update", "delete", "revert"]).optional(),
6562
+ revertColumns: zod.z.array(zod.z.string()).optional(),
6563
+ /** `@writeback(source:, mode:)` directives on this root. */
6564
+ writeback: zod.z.array(zod.z.object({ source: zod.z.string(), mode: zod.z.enum(["sync", "async"]) })).optional(),
6565
+ /** `@localOnly(source:)` — writeback columns deliberately kept local for these sources. */
6566
+ localOnly: zod.z.array(zod.z.string()).optional(),
4983
6567
  alias: zod.z.string(),
4984
6568
  table: zod.z.string(),
4985
6569
  /** CoW ops: logical name of the synced twin (read through its policy-filtered view). */
@@ -5043,7 +6627,31 @@ var variableIrSchema = zod.z.object({
5043
6627
  /** Absent = no default declared; see {@link variableDefaultIrSchema}. */
5044
6628
  defaultValue: variableDefaultIrSchema.optional()
5045
6629
  });
6630
+ var sqlColumnSchema = zod.z.object({ name: zod.z.string(), type: zod.z.enum(["text", "boolean", "integer", "bigint", "numeric", "double", "uuid", "date", "timestamptz"]), nullable: zod.z.boolean() });
5046
6631
  var compiledOperationSchema = zod.z.object({
6632
+ language: zod.z.literal("sql").optional(),
6633
+ /** Server-compiled object catalog and permission ceiling, never caller SQL. */
6634
+ sqlQuery: zod.z.object({
6635
+ catalog: zod.z.object({
6636
+ identity: zod.z.string(),
6637
+ objects: zod.z.array(zod.z.object({
6638
+ ref: zod.z.string(),
6639
+ table: zod.z.string(),
6640
+ columns: zod.z.array(sqlColumnSchema.extend({ type: zod.z.string() })).min(1),
6641
+ scan: zod.z.string().min(1),
6642
+ read: zod.z.array(zod.z.string()).min(1)
6643
+ }).strict()).min(1).max(256)
6644
+ }).strict(),
6645
+ read: zod.z.array(zod.z.string()).min(1).max(256),
6646
+ maxRows: zod.z.number().int().min(1).max(1e4)
6647
+ }).strict().optional(),
6648
+ sql: zod.z.object({
6649
+ text: zod.z.string().min(1).max(1048576),
6650
+ columns: zod.z.array(sqlColumnSchema).min(1).max(256),
6651
+ parameters: zod.z.array(zod.z.string()).max(128),
6652
+ read: zod.z.array(zod.z.string()).min(1).max(256),
6653
+ maxRows: zod.z.number().int().min(1).max(1e4)
6654
+ }).strict().optional(),
5047
6655
  irVersion: irVersionSchema,
5048
6656
  name: zod.z.string(),
5049
6657
  opHash: zod.z.string().regex(/^sha256:[0-9a-f]{64}$/),
@@ -5055,8 +6663,16 @@ var compiledOperationSchema = zod.z.object({
5055
6663
  variables: zod.z.array(variableIrSchema),
5056
6664
  /** Every table the op touches — data-token scoping + audit. */
5057
6665
  tableSet: zod.z.array(zod.z.string()).min(1),
5058
- roots: zod.z.array(rootIrSchema).min(1)
6666
+ roots: zod.z.array(rootIrSchema)
5059
6667
  }).superRefine((operation, ctx) => {
6668
+ if (operation.language === "sql") {
6669
+ const validPlan = operation.sqlQuery ? !operation.sql && operation.irVersion === RUNTIME_SQL_IR_VERSION : !!operation.sql && operation.irVersion === SQL_IR_VERSION;
6670
+ if (!validPlan || operation.kind !== "query" || operation.roots.length !== 0 || !operation.requires?.capability) {
6671
+ ctx.addIssue({ code: "custom", message: "SQL operations require a query-only v4 plan or v5 runtime contract, capability, and no GraphQL roots" });
6672
+ }
6673
+ } else if (operation.sql || operation.sqlQuery || operation.roots.length === 0) {
6674
+ ctx.addIssue({ code: "custom", message: "GraphQL operations require roots and cannot carry SQL" });
6675
+ }
5060
6676
  for (const [index3, root] of operation.roots.entries()) {
5061
6677
  if (root.kind !== "mutation" || root.assertAffectedRows === void 0) continue;
5062
6678
  if (operation.irVersion < 2 || operation.kind !== "mutation" || root.includeIf !== void 0) {
@@ -5064,13 +6680,55 @@ var compiledOperationSchema = zod.z.object({
5064
6680
  }
5065
6681
  }
5066
6682
  });
6683
+ var resolvedSourceIrSchema = zod.z.object({
6684
+ name: zod.z.string(),
6685
+ kind: zod.z.enum(["manual", "etl", "prefetch"]),
6686
+ /** Logical table key of the backing relation. */
6687
+ table: zod.z.string(),
6688
+ columns: zod.z.array(zod.z.string()),
6689
+ key: zod.z.string().optional(),
6690
+ fetch: zod.z.union([
6691
+ zod.z.object({ shape: zod.z.literal("single") }),
6692
+ zod.z.object({ shape: zod.z.literal("batch"), maxKeys: zod.z.number().int().positive() })
6693
+ ]).optional(),
6694
+ function: zod.z.object({ kind: zod.z.enum(["managed", "native"]), id: zod.z.string() }).optional(),
6695
+ prefetchDefaults: zod.z.object({
6696
+ maxStalenessMs: zod.z.number().int().positive(),
6697
+ timeoutMs: zod.z.number().int().positive(),
6698
+ onError: zod.z.enum(["fallback", "fail"])
6699
+ }).optional(),
6700
+ writeback: zod.z.object({
6701
+ columns: zod.z.array(zod.z.string()),
6702
+ defaultMode: zod.z.enum(["sync", "async"]),
6703
+ timeoutMs: zod.z.number().int().positive(),
6704
+ clearManualOnConfirm: zod.z.boolean()
6705
+ }).optional()
6706
+ });
6707
+ var resolvedMetaIrSchema = zod.z.object({
6708
+ /** Logical pk column of the resolved view (single-column). */
6709
+ pk: zod.z.string(),
6710
+ /** Source name of the manual source. */
6711
+ manual: zod.z.string(),
6712
+ sources: zod.z.array(resolvedSourceIrSchema),
6713
+ deletedBy: zod.z.array(zod.z.string()),
6714
+ /**
6715
+ * Materialized: the view reads `table`, which the data plane refreshes from
6716
+ * the `compute` view (claims-gated) whenever it writes a source row.
6717
+ */
6718
+ materialized: zod.z.object({ table: zod.z.string(), compute: zod.z.string() }).optional()
6719
+ });
5067
6720
  var tableMetaIrSchema = zod.z.object({
5068
6721
  table: zod.z.string(),
6722
+ namespace: zod.z.string().optional(),
6723
+ dependencies: zod.z.array(zod.z.string()).optional(),
6724
+ foreignReferences: zod.z.array(zod.z.object({ column: zod.z.string(), target: zod.z.string(), targetColumn: zod.z.string() })).optional(),
6725
+ exportRead: zod.z.union([zod.z.literal(true), zod.z.object({ capability: zod.z.string() }), zod.z.object({ role: zod.z.string() })]).optional(),
6726
+ exportWrite: zod.z.union([zod.z.literal(true), zod.z.object({ capability: zod.z.string() }), zod.z.object({ role: zod.z.string() })]).optional(),
5069
6727
  schema: zod.z.string(),
5070
6728
  typename: zod.z.string(),
5071
6729
  /**
5072
6730
  * True when statements over this relation evaluate a claims-gated synced
5073
- * view: synced twins (`source: gold.*`) materialize as views whose baked
6731
+ * view: synced twins (`source: schema.table`) materialize as views whose baked
5074
6732
  * policy reads `current_setting('request.jwt.claims')`, and namespace views
5075
6733
  * inherit the gate transitively through their declared dependencies. The
5076
6734
  * executor must stamp that GUC (transaction-scoped) before any statement
@@ -5104,6 +6762,22 @@ var tableMetaIrSchema = zod.z.object({
5104
6762
  * `onUpdateFn`). Omitted when empty so pre-onUpdate artifacts still parse.
5105
6763
  */
5106
6764
  touchOnUpdate: zod.z.array(zod.z.string()).optional(),
6765
+ /**
6766
+ * Per-cell metadata: logical column → the logical names of its cell-part
6767
+ * columns (which are listed in `columns`, but never filterable, orderable or
6768
+ * writable). Tables stamp `at`/`by` (and a manual source `rev`) on every
6769
+ * write; resolved views expose `source`/`at`/`by`/`conflicts`. Omitted when
6770
+ * the relation tracks no cells.
6771
+ */
6772
+ cells: zod.z.record(zod.z.string(), zod.z.object({
6773
+ at: zod.z.string().optional(),
6774
+ by: zod.z.string().optional(),
6775
+ rev: zod.z.string().optional(),
6776
+ source: zod.z.string().optional(),
6777
+ conflicts: zod.z.string().optional()
6778
+ }).strict()).optional(),
6779
+ /** Resolved-table view metadata (sources, prefetch/writeback config) for the data plane. */
6780
+ resolved: resolvedMetaIrSchema.optional(),
5107
6781
  constraints: zod.z.array(zod.z.object({ name: zod.z.string(), columns: zod.z.array(zod.z.string()) })),
5108
6782
  /** Depth/node caps for dynamic filter trees on this table. */
5109
6783
  dynamicFilterMaxDepth: zod.z.number().int().positive().default(6),
@@ -5113,13 +6787,21 @@ var compiledArtifactSchema = zod.z.object({
5113
6787
  irVersion: irVersionSchema,
5114
6788
  namespace: zod.z.string(),
5115
6789
  schemaHash: zod.z.string(),
6790
+ ownSchemaHash: zod.z.string().optional(),
6791
+ dependencies: zod.z.record(zod.z.string(), zod.z.string()).optional(),
6792
+ readPolicies: zod.z.record(zod.z.string(), predIrSchema).optional(),
5116
6793
  opsHash: zod.z.string(),
6794
+ /** Apply-observed synced objects without a serving backing; not part of the authored schema hash. */
6795
+ unbackedTables: zod.z.array(zod.z.string()).optional(),
5117
6796
  tables: zod.z.record(zod.z.string(), tableMetaIrSchema),
5118
6797
  operations: zod.z.record(zod.z.string(), compiledOperationSchema)
5119
6798
  }).superRefine((artifact, ctx) => {
5120
- for (const [name, operation] of Object.entries(artifact.operations)) {
6799
+ if (Object.keys(artifact.dependencies ?? {}).length > 0 && artifact.irVersion < COMPOSED_IR_VERSION) {
6800
+ ctx.addIssue({ code: "custom", path: ["irVersion"], message: "cross-namespace dependencies require IR v3" });
6801
+ }
6802
+ for (const [name2, operation] of Object.entries(artifact.operations)) {
5121
6803
  if (operation.irVersion > artifact.irVersion) {
5122
- ctx.addIssue({ code: "custom", path: ["operations", name, "irVersion"], message: "operation requires a newer IR version than its artifact" });
6804
+ ctx.addIssue({ code: "custom", path: ["operations", name2, "irVersion"], message: "operation requires a newer IR version than its artifact" });
5123
6805
  }
5124
6806
  }
5125
6807
  });
@@ -5132,7 +6814,7 @@ var IrVersionError = class extends Error {
5132
6814
  function normalizeIr(raw) {
5133
6815
  if (!raw || typeof raw !== "object") throw new IrVersionError(void 0);
5134
6816
  const version = raw.irVersion;
5135
- if (version !== LEGACY_IR_VERSION && version !== IR_VERSION) throw new IrVersionError(version);
6817
+ if (!irVersionSchema.safeParse(version).success) throw new IrVersionError(version);
5136
6818
  return compiledArtifactSchema.parse(raw);
5137
6819
  }
5138
6820
  function indexByHash(artifact) {
@@ -5165,10 +6847,10 @@ function buildNamespaceDefinition(files, config) {
5165
6847
  }
5166
6848
  const actionsSeen = /* @__PURE__ */ new Map();
5167
6849
  for (const { file, table } of files) {
5168
- for (const name of Object.keys(table.actions ?? {})) {
5169
- const existing = actionsSeen.get(name);
5170
- if (existing) throw new OrmDefinitionError(`action '${name}' is already defined in ${existing}`, file);
5171
- actionsSeen.set(name, file);
6850
+ for (const name2 of Object.keys(table.actions ?? {})) {
6851
+ const existing = actionsSeen.get(name2);
6852
+ if (existing) throw new OrmDefinitionError(`action '${name2}' is already defined in ${existing}`, file);
6853
+ actionsSeen.set(name2, file);
5172
6854
  }
5173
6855
  }
5174
6856
  const capabilities = config?.capabilities ? { roles: config.capabilities.roles ?? EMPTY_CAPABILITIES.roles, uses: config.capabilities.uses ?? [] } : EMPTY_CAPABILITIES;
@@ -5211,12 +6893,12 @@ function validateActionValidates(files, byName) {
5211
6893
  }
5212
6894
  function validateIndexNames(files) {
5213
6895
  const seen = /* @__PURE__ */ new Map();
5214
- const claim = (name, label, file) => {
5215
- const existing = seen.get(name);
6896
+ const claim = (name2, label, file) => {
6897
+ const existing = seen.get(name2);
5216
6898
  if (existing) {
5217
- throw new OrmDefinitionError(`relation name '${name}' (${label}) collides with ${existing}`, file);
6899
+ throw new OrmDefinitionError(`relation name '${name2}' (${label}) collides with ${existing}`, file);
5218
6900
  }
5219
- seen.set(name, `${label} (${file})`);
6901
+ seen.set(name2, `${label} (${file})`);
5220
6902
  };
5221
6903
  for (const { file, table } of files) {
5222
6904
  claim(table.table, `table '${table.table}'`, file);
@@ -5224,9 +6906,9 @@ function validateIndexNames(files) {
5224
6906
  for (const { file, table } of files) {
5225
6907
  if (table.source) continue;
5226
6908
  claim(`${table.table}_pkey`, `implicit primary-key index of '${table.table}'`, file);
5227
- for (const [name, column] of Object.entries(table.columns ?? {})) {
6909
+ for (const [name2, column] of Object.entries(table.columns ?? {})) {
5228
6910
  if (column.unique && !column.primary_key) {
5229
- claim(`${table.table}_${name}_unique`, `implicit unique constraint of '${table.table}.${name}'`, file);
6911
+ claim(`${table.table}_${name2}_unique`, `implicit unique constraint of '${table.table}.${name2}'`, file);
5230
6912
  }
5231
6913
  }
5232
6914
  }
@@ -5238,20 +6920,20 @@ function validateIndexNames(files) {
5238
6920
  }
5239
6921
  function validateLocalLinks(files, byName) {
5240
6922
  for (const { file, table } of files) {
5241
- for (const [name, link] of Object.entries(table.links ?? {})) {
6923
+ for (const [name2, link] of Object.entries(table.links ?? {})) {
5242
6924
  if (isQualifiedReference(link.to)) continue;
5243
6925
  const target = byName.get(link.to);
5244
6926
  if (!target) {
5245
- throw new OrmDefinitionError(`link '${name}' references unknown table '${link.to}'`, file);
6927
+ throw new OrmDefinitionError(`link '${name2}' references unknown table '${link.to}'`, file);
5246
6928
  }
5247
6929
  if (link.enforce && target.source) {
5248
- throw new OrmDefinitionError(`link '${name}' cannot enforce a foreign key onto a synced table`, file);
6930
+ throw new OrmDefinitionError(`link '${name2}' cannot enforce a foreign key onto a synced table`, file);
5249
6931
  }
5250
6932
  const sourceType = table.columns?.[link.on]?.type;
5251
6933
  const targetPrimaryKey = Object.values(target.columns ?? {}).find((column) => column.primary_key);
5252
6934
  if (sourceType && targetPrimaryKey && columnTypeGroup(sourceType) !== columnTypeGroup(targetPrimaryKey.type)) {
5253
6935
  throw new OrmDefinitionError(
5254
- `link '${name}' can never join: '${link.on}' is ${sourceType} but '${link.to}' has a ${targetPrimaryKey.type} primary key`,
6936
+ `link '${name2}' can never join: '${link.on}' is ${sourceType} but '${link.to}' has a ${targetPrimaryKey.type} primary key`,
5255
6937
  file
5256
6938
  );
5257
6939
  }
@@ -5331,7 +7013,7 @@ function parseYaml(file, contents) {
5331
7013
  throw new OrmDefinitionError(error instanceof Error ? error.message : "invalid YAML", file);
5332
7014
  }
5333
7015
  }
5334
- var isDefinitionFile = (name) => DEFINITION_FILE_EXTENSIONS.some((extension) => path.extname(name) === extension);
7016
+ var isDefinitionFile = (name2) => DEFINITION_FILE_EXTENSIONS.some((extension) => path.extname(name2) === extension);
5335
7017
  async function listDefinitionFiles(dir) {
5336
7018
  const entries = await promises.readdir(dir, { withFileTypes: true });
5337
7019
  return entries.filter((entry) => entry.isFile() && isDefinitionFile(entry.name)).map((entry) => entry.name).sort();
@@ -5425,13 +7107,13 @@ async function readMigrationSql(dir, entry) {
5425
7107
  var checksumOf = (text4) => crypto.createHash("sha256").update(text4).digest("hex");
5426
7108
  var migrationName = (ordinal, contentHash) => `${pad(ordinal)}_${contentHash.slice(0, 12)}`;
5427
7109
  function renderMigrationSql({
5428
- name,
7110
+ name: name2,
5429
7111
  namespace,
5430
7112
  contentHash,
5431
7113
  plan
5432
7114
  }) {
5433
7115
  const header = [
5434
- `-- ${name}`,
7116
+ `-- ${name2}`,
5435
7117
  `-- namespace: ${namespace}`,
5436
7118
  `-- content hash: ${contentHash}`,
5437
7119
  "-- generated by `seq-studio orm diff` \u2014 review and commit, do not edit"
@@ -5447,13 +7129,13 @@ async function writeMigration({
5447
7129
  }) {
5448
7130
  const tip = workspace.journal.entries.at(-1);
5449
7131
  const ordinal = (tip?.ordinal ?? 0) + 1;
5450
- const name = migrationName(ordinal, contentHash);
5451
- const sql5 = renderMigrationSql({ name, namespace: workspace.journal.namespace, contentHash, plan });
7132
+ const name2 = migrationName(ordinal, contentHash);
7133
+ const sql7 = renderMigrationSql({ name: name2, namespace: workspace.journal.namespace, contentHash, plan });
5452
7134
  const metaDir = path.join(dir, MIGRATIONS_DIRNAME, META_DIRNAME);
5453
7135
  await promises.mkdir(metaDir, { recursive: true });
5454
7136
  const snapshotText = `${JSON.stringify(plan.snapshot, null, 2)}
5455
7137
  `;
5456
- await promises.writeFile(path.join(dir, MIGRATIONS_DIRNAME, `${name}.sql`), sql5);
7138
+ await promises.writeFile(path.join(dir, MIGRATIONS_DIRNAME, `${name2}.sql`), sql7);
5457
7139
  await promises.writeFile(path.join(metaDir, `${pad(ordinal)}.snapshot.json`), snapshotText);
5458
7140
  const journal = {
5459
7141
  ...workspace.journal,
@@ -5461,10 +7143,10 @@ async function writeMigration({
5461
7143
  ...workspace.journal.entries,
5462
7144
  {
5463
7145
  ordinal,
5464
- name,
7146
+ name: name2,
5465
7147
  content_hash: contentHash,
5466
7148
  previous_content_hash: tip?.content_hash ?? null,
5467
- checksum: checksumOf(sql5),
7149
+ checksum: checksumOf(sql7),
5468
7150
  snapshot_checksum: checksumOf(snapshotText),
5469
7151
  destructive
5470
7152
  }
@@ -5472,7 +7154,7 @@ async function writeMigration({
5472
7154
  };
5473
7155
  await promises.writeFile(path.join(metaDir, JOURNAL_FILENAME), `${JSON.stringify(journal, null, 2)}
5474
7156
  `);
5475
- return { name, ordinal, destructive };
7157
+ return { name: name2, ordinal, destructive };
5476
7158
  }
5477
7159
  var pad = (ordinal) => String(ordinal).padStart(4, "0");
5478
7160
  function parseJson(path, raw) {
@@ -5579,8 +7261,8 @@ async function checkWorkspace({
5579
7261
  let previousId3;
5580
7262
  let previousHash = null;
5581
7263
  for (const entry of workspace.journal.entries) {
5582
- const sql5 = await readMigrationSql(dir, entry);
5583
- if (checksumOf(sql5) !== entry.checksum) {
7264
+ const sql7 = await readMigrationSql(dir, entry);
7265
+ if (checksumOf(sql7) !== entry.checksum) {
5584
7266
  log(`${MIGRATIONS_DIRNAME}/${entry.name}.sql does not match its journal checksum \u2014 committed migrations must not be edited`);
5585
7267
  return 1;
5586
7268
  }
@@ -5620,7 +7302,7 @@ async function checkWorkspace({
5620
7302
  log(`migrations in sync \u2014 ${workspace.journal.entries.length} committed, tip ${tip.name}`);
5621
7303
  return 0;
5622
7304
  }
5623
- var firstLine = (sql5) => sql5.split("\n")[0] ?? sql5;
7305
+ var firstLine = (sql7) => sql7.split("\n")[0] ?? sql7;
5624
7306
 
5625
7307
  // src/migrations/from-yaml.ts
5626
7308
  function convertV1DefinitionToCanonical(definition) {
@@ -5646,15 +7328,15 @@ function convertV1DefinitionToCanonical(definition) {
5646
7328
  }
5647
7329
  function convertTable(table) {
5648
7330
  const columns = Object.entries(table.columns ?? {});
5649
- const primaryKey2 = columns.filter(([, column]) => column.primary_key).map(([name]) => name);
7331
+ const primaryKey2 = columns.filter(([, column]) => column.primary_key).map(([name2]) => name2);
5650
7332
  return {
5651
7333
  table: table.table,
5652
7334
  description: table.description,
5653
7335
  source: table.source,
5654
- columns: columns.map(([name, column]) => convertColumn(name, column)),
7336
+ columns: columns.map(([name2, column]) => convertColumn(name2, column)),
5655
7337
  // Synced tables may declare a typed column/PK contract; untyped remain empty.
5656
7338
  primaryKey: primaryKey2.length > 0 ? primaryKey2 : void 0,
5657
- links: Object.entries(table.links ?? {}).map(([name, link]) => convertLink(table.table, name, link)),
7339
+ links: Object.entries(table.links ?? {}).map(([name2, link]) => convertLink(table.table, name2, link)),
5658
7340
  indexes: (table.indexes ?? []).map((index3) => ({
5659
7341
  columns: [...index3.columns],
5660
7342
  unique: index3.unique ?? false,
@@ -5671,11 +7353,11 @@ function convertTable(table) {
5671
7353
  actions: []
5672
7354
  };
5673
7355
  }
5674
- function convertColumn(name, column) {
7356
+ function convertColumn(name2, column) {
5675
7357
  const isDefaultFn = column.default !== void 0 && isFunctionDefault(column.default, column.type);
5676
7358
  return {
5677
- name,
5678
- columnName: name,
7359
+ name: name2,
7360
+ columnName: name2,
5679
7361
  type: column.type,
5680
7362
  nullable: column.primary_key ? false : column.nullable !== false,
5681
7363
  unique: column.unique === true,
@@ -5689,14 +7371,14 @@ function convertColumn(name, column) {
5689
7371
  deprecated: column.deprecated === true
5690
7372
  };
5691
7373
  }
5692
- function convertLink(table, name, link) {
7374
+ function convertLink(table, name2, link) {
5693
7375
  if (link.to.includes(".")) {
5694
7376
  throw new OrmDefinitionError(
5695
- `link '${name}' on '${table}' targets '${link.to}' \u2014 cross-namespace links have no canonical v2 representation (links are namespace-local in v2)`
7377
+ `link '${name2}' on '${table}' targets '${link.to}' \u2014 cross-namespace links have no canonical v2 representation (links are namespace-local in v2)`
5696
7378
  );
5697
7379
  }
5698
7380
  return {
5699
- name,
7381
+ name: name2,
5700
7382
  kind: "toOne",
5701
7383
  target: link.to,
5702
7384
  on: link.on,
@@ -5725,29 +7407,29 @@ function buildRebaseJournalEntry({ namespace, previousHash, newHash, ordinal, sn
5725
7407
  if (!Number.isInteger(ordinal) || ordinal < 2) {
5726
7408
  throw new OrmDefinitionError(`rebase ordinal must be an integer \u2265 2 (a rebase sits on an existing journal tip); got ${ordinal}`);
5727
7409
  }
5728
- const name = migrationName(ordinal, newHash);
5729
- const sql5 = renderRebaseMigrationSql({ name, namespace, previousHash, newHash });
7410
+ const name2 = migrationName(ordinal, newHash);
7411
+ const sql7 = renderRebaseMigrationSql({ name: name2, namespace, previousHash, newHash });
5730
7412
  const snapshotText = `${JSON.stringify(snapshot, null, 2)}
5731
7413
  `;
5732
7414
  const entry = {
5733
7415
  ordinal,
5734
- name,
7416
+ name: name2,
5735
7417
  content_hash: newHash,
5736
7418
  previous_content_hash: previousHash,
5737
- checksum: checksumOf(sql5),
7419
+ checksum: checksumOf(sql7),
5738
7420
  snapshot_checksum: checksumOf(snapshotText),
5739
7421
  destructive: false
5740
7422
  };
5741
- return { name, entry, sql: sql5, snapshotText };
7423
+ return { name: name2, entry, sql: sql7, snapshotText };
5742
7424
  }
5743
7425
  function renderRebaseMigrationSql({
5744
- name,
7426
+ name: name2,
5745
7427
  namespace,
5746
7428
  previousHash,
5747
7429
  newHash
5748
7430
  }) {
5749
7431
  return [
5750
- `-- ${name}`,
7432
+ `-- ${name2}`,
5751
7433
  `-- namespace: ${namespace}`,
5752
7434
  `-- content hash: ${newHash}`,
5753
7435
  `-- rebase: v1 definitions (${previousHash}) converted to canonical v2 \u2014 verified no-op, zero statements`,
@@ -5821,8 +7503,8 @@ function canonicalActions(value) {
5821
7503
  const entries = Object.entries(value);
5822
7504
  if (entries.length === 0) return void 0;
5823
7505
  return Object.fromEntries(
5824
- entries.map(([name, action]) => [
5825
- name,
7506
+ entries.map(([name2, action]) => [
7507
+ name2,
5826
7508
  {
5827
7509
  description: action["description"] ?? void 0,
5828
7510
  requires: action["requires"] ?? void 0,
@@ -5835,6 +7517,526 @@ function canonicalActions(value) {
5835
7517
  ])
5836
7518
  );
5837
7519
  }
7520
+ var IMPORT_CONFLICT_MODES = ["error", "skip", "update"];
7521
+ var columnMapSchema = zod.z.object({
7522
+ /** ORM property name or Postgres column name. */
7523
+ target: zod.z.string().min(1).max(63),
7524
+ /** Column on the source relation. Identifiers only — never an expression. */
7525
+ source: zod.z.string().min(1).max(63),
7526
+ /**
7527
+ * Used when the source value is NULL. Required columns without a database
7528
+ * default fail the load if the source contains nulls and this is omitted.
7529
+ */
7530
+ nullDefault: zod.z.union([zod.z.string().max(1e4), zod.z.number(), zod.z.boolean()]).optional()
7531
+ }).strict();
7532
+ var importSpecSchema = zod.z.object({
7533
+ table: zod.z.string().min(1).max(63),
7534
+ from: zod.z.string().regex(QUALIFIED_TABLE_PATTERN),
7535
+ columns: zod.z.array(columnMapSchema).min(1).max(500),
7536
+ onConflict: zod.z.enum(IMPORT_CONFLICT_MODES).default("skip"),
7537
+ batchSize: zod.z.number().int().min(1).max(2e4).default(5e3)
7538
+ }).strict();
7539
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
7540
+ var DATE_RE = /^\d{4}-\d{2}-\d{2}$/;
7541
+ var TIMESTAMPTZ_RE = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:?\d{2})$/;
7542
+ var INTEGER_RE = /^-?\d+$/;
7543
+ var NUMERIC_RE = /^-?\d+(\.\d+)?$/;
7544
+ function sourceSchemaAllowed(schema) {
7545
+ return schema !== "information_schema" && !schema.startsWith("pg_");
7546
+ }
7547
+ function isRelationIdentifier(value) {
7548
+ return value.length <= 63 && IDENTIFIER_PATTERN.test(value);
7549
+ }
7550
+ function isSourceColumn(value) {
7551
+ return value.length <= 63 && COLUMN_IDENTIFIER_PATTERN.test(value);
7552
+ }
7553
+ function splitQualifiedRelation(from) {
7554
+ const dot = from.indexOf(".");
7555
+ if (dot <= 0 || dot !== from.lastIndexOf(".")) return null;
7556
+ const schema = from.slice(0, dot);
7557
+ const table = from.slice(dot + 1);
7558
+ if (!isRelationIdentifier(schema) || !isRelationIdentifier(table)) return null;
7559
+ return { schema, table };
7560
+ }
7561
+ function postgresTypeName(type) {
7562
+ if (type === "double") return "double precision";
7563
+ if (type === "timestamptz") return "timestamp with time zone";
7564
+ return type;
7565
+ }
7566
+ function columnPostgresType(column, schemaName, enums) {
7567
+ if (typeof column.type === "string") return postgresTypeName(column.type);
7568
+ if (!enums.has(column.type.enum)) return null;
7569
+ return `${quoteIdentifier(schemaName)}.${quoteIdentifier(column.type.enum)}`;
7570
+ }
7571
+ function quoteQualified(schema, table) {
7572
+ return `${quoteIdentifier(schema)}.${quoteIdentifier(table)}`;
7573
+ }
7574
+ var INT4_MIN = -2147483648;
7575
+ var INT4_MAX = 2147483647;
7576
+ function resumeLiteralSql({
7577
+ column,
7578
+ after,
7579
+ schemaName,
7580
+ enums
7581
+ }) {
7582
+ const coerced = coerceResumeValue(column, after);
7583
+ if (!coerced.ok) return coerced;
7584
+ return literalSql({ column, value: coerced.value, schemaName, enums });
7585
+ }
7586
+ function coerceResumeValue(column, after) {
7587
+ const type = typeof column.type === "string" ? column.type : null;
7588
+ if (type === "integer") {
7589
+ if (!INTEGER_RE.test(after)) return { ok: false, error: `${column.name}: after must be an integer` };
7590
+ const parsed = Number(after);
7591
+ if (!Number.isSafeInteger(parsed) || parsed < INT4_MIN || parsed > INT4_MAX) {
7592
+ return { ok: false, error: `${column.name}: after must be an integer` };
7593
+ }
7594
+ return { ok: true, value: parsed };
7595
+ }
7596
+ if (type === "boolean") {
7597
+ if (after !== "true" && after !== "false") return { ok: false, error: `${column.name}: after must be true or false` };
7598
+ return { ok: true, value: after === "true" };
7599
+ }
7600
+ return { ok: true, value: after };
7601
+ }
7602
+ function literalSql({
7603
+ column,
7604
+ value,
7605
+ schemaName,
7606
+ enums
7607
+ }) {
7608
+ const label = column.name;
7609
+ if (typeof value === "string" && value.includes("\0")) {
7610
+ return { ok: false, error: `${label}: null default contains a NUL character` };
7611
+ }
7612
+ if (typeof column.type !== "string") {
7613
+ const enumDef = enums.get(column.type.enum);
7614
+ if (!enumDef) return { ok: false, error: `${label}: enum '${column.type.enum}' is not declared` };
7615
+ if (typeof value !== "string" || !enumDef.values.includes(value)) {
7616
+ return { ok: false, error: `${label}: null default must be one of ${enumDef.values.join(", ")}` };
7617
+ }
7618
+ const typeName = columnPostgresType(column, schemaName, enums);
7619
+ return { ok: true, sql: `${quoteLiteral(value)}::${typeName}` };
7620
+ }
7621
+ return scalarLiteral2({ column, type: column.type, value, label });
7622
+ }
7623
+ var SCALAR_LITERALS = {
7624
+ text: (value, label) => typeof value === "string" ? { ok: true, sql: quoteLiteral(value) } : { ok: false, error: `${label}: null default must be a string` },
7625
+ uuid: (value, label) => typeof value === "string" && UUID_RE.test(value) ? { ok: true, sql: `${quoteLiteral(value)}::uuid` } : { ok: false, error: `${label}: null default must be a UUID` },
7626
+ boolean: (value, label) => typeof value === "boolean" ? { ok: true, sql: value ? "TRUE" : "FALSE" } : { ok: false, error: `${label}: null default must be true or false` },
7627
+ integer: (value, label) => typeof value === "number" && Number.isSafeInteger(value) ? { ok: true, sql: String(value) } : { ok: false, error: `${label}: null default must be an integer` },
7628
+ bigint: (value, label) => typeof value === "string" && INTEGER_RE.test(value) ? { ok: true, sql: `${value}::bigint` } : { ok: false, error: `${label}: null default must be a base-10 integer string` },
7629
+ numeric: numericLiteral,
7630
+ double: numericLiteral,
7631
+ date: (value, label) => typeof value === "string" && DATE_RE.test(value) ? { ok: true, sql: `${quoteLiteral(value)}::date` } : { ok: false, error: `${label}: null default must be a YYYY-MM-DD date` },
7632
+ timestamptz: (value, label) => typeof value === "string" && TIMESTAMPTZ_RE.test(value) ? { ok: true, sql: `${quoteLiteral(value)}::${postgresTypeName("timestamptz")}` } : { ok: false, error: `${label}: null default must be an ISO-8601 timestamp` },
7633
+ jsonb: jsonbLiteral
7634
+ };
7635
+ function numericLiteral(value, label) {
7636
+ if (typeof value === "number" && Number.isFinite(value)) return { ok: true, sql: String(value) };
7637
+ if (typeof value === "string" && NUMERIC_RE.test(value)) return { ok: true, sql: value };
7638
+ return { ok: false, error: `${label}: null default must be a finite number` };
7639
+ }
7640
+ function scalarLiteral2({
7641
+ column,
7642
+ type,
7643
+ value,
7644
+ label
7645
+ }) {
7646
+ const encode = SCALAR_LITERALS[type];
7647
+ if (!encode) return { ok: false, error: `${column.name}: unsupported type '${type}'` };
7648
+ return encode(value, label);
7649
+ }
7650
+ function jsonbLiteral(value, label) {
7651
+ if (typeof value !== "string") return { ok: false, error: `${label}: null default must be a JSON string` };
7652
+ try {
7653
+ JSON.parse(value);
7654
+ } catch {
7655
+ return { ok: false, error: `${label}: null default must be valid JSON` };
7656
+ }
7657
+ return { ok: true, sql: `${quoteLiteral(value)}::jsonb` };
7658
+ }
7659
+ function dollarQuote(body) {
7660
+ const digest = crypto.createHash("sha256").update(body).digest("hex");
7661
+ const candidates = ["seq_orm_import", `seq_orm_${digest.slice(0, 16)}`, `seq_orm_${digest}`];
7662
+ const tag = candidates.find((candidate) => !body.includes(`$${candidate}$`)) ?? `seq_orm_${digest}_body`;
7663
+ return `$${tag}$${body}$${tag}$`;
7664
+ }
7665
+
7666
+ // src/import/script.ts
7667
+ function renderImportScript({
7668
+ namespace,
7669
+ schemaName,
7670
+ table,
7671
+ source,
7672
+ mapped,
7673
+ primaryKey: primaryKey2,
7674
+ onConflict,
7675
+ batchSize,
7676
+ omitted,
7677
+ resume
7678
+ }) {
7679
+ const sourceSql = `${quoteQualified(source.schema, source.table)} AS src`;
7680
+ const targetSql = quoteQualified(schemaName, table.table);
7681
+ const keys = primaryKey2.map((name2) => mapped.find((column) => column.column.columnName === name2)).filter((column) => column !== void 0);
7682
+ const probeSql = renderProbe({ sourceSql, keys, mapped });
7683
+ const procedure = renderProcedure({ targetSql, sourceSql, mapped, keys, onConflict, batchSize });
7684
+ const call = `CALL pg_temp.seq_orm_import(${resume.join(", ")});`;
7685
+ const omittedLine = omitted.length > 0 ? `-- Omitted columns keep their database default or NULL: ${omitted.join(", ")}` : "-- Every column is mapped.";
7686
+ const text4 = [
7687
+ "-- Sequence ORM data import",
7688
+ `-- namespace: ${namespace}`,
7689
+ `-- target: ${schemaName}.${table.table}`,
7690
+ `-- source: ${source.schema}.${source.table}`,
7691
+ `-- conflict: ${onConflict}`,
7692
+ `-- batch size: ${batchSize}`,
7693
+ "--",
7694
+ "-- Runs as the namespace owner. Does not evaluate insert or update policies,",
7695
+ "-- capability gates, or the operation journal. Sync the warehouse table into",
7696
+ "-- this Postgres relation first, review the probe, then run the load.",
7697
+ "-- skip and update are safe to re-run from the start after a failure. error stops on an existing key.",
7698
+ "-- Each batch commits. Do not resume from the maximum key in the target:",
7699
+ "-- a higher key that was already there would skip source rows this run never copied.",
7700
+ "-- --after is only the last key this procedure committed. This script does not print it.",
7701
+ omittedLine,
7702
+ "-- cast_* columns force every mapped value through the target type. An invalid value aborts before any insert.",
7703
+ "",
7704
+ probeSql,
7705
+ "",
7706
+ procedure,
7707
+ call,
7708
+ ""
7709
+ ].join("\n");
7710
+ return { probeSql, text: text4 };
7711
+ }
7712
+ function renderProbe({
7713
+ sourceSql,
7714
+ keys,
7715
+ mapped
7716
+ }) {
7717
+ const nullKey = keys.map((key) => `${key.expression} IS NULL`).join(" OR ");
7718
+ const distinct = keys.length === 1 ? keys[0].expression : `(${keys.map((key) => key.expression).join(", ")})`;
7719
+ const requiredNulls = nullChecked(mapped, keys).map((column) => `count(*) FILTER (WHERE src.${quoteIdentifier(column.source)} IS NULL) AS ${quoteIdentifier(`null_${column.column.columnName}`)}`);
7720
+ const selects = [
7721
+ "count(*) AS source_rows",
7722
+ `count(*) FILTER (WHERE ${nullKey}) AS null_primary_key`,
7723
+ `count(*) FILTER (WHERE NOT (${nullKey})) - count(DISTINCT ${distinct}) AS duplicate_primary_key`,
7724
+ ...requiredNulls,
7725
+ ...mapped.map((column) => `count(${column.expression}) AS ${quoteIdentifier(`cast_${column.column.columnName}`)}`)
7726
+ ];
7727
+ return `SELECT
7728
+ ${selects.join(",\n ")}
7729
+ FROM ${sourceSql};`;
7730
+ }
7731
+ function renderProcedure({
7732
+ targetSql,
7733
+ sourceSql,
7734
+ mapped,
7735
+ keys,
7736
+ onConflict,
7737
+ batchSize
7738
+ }) {
7739
+ const args = keys.map((key, index3) => `IN start_${index3} ${key.pgType} DEFAULT NULL`).join(", ");
7740
+ const declarations = [
7741
+ "DECLARE",
7742
+ " batch_scanned integer;",
7743
+ " batch_affected integer;",
7744
+ " total_rows bigint;",
7745
+ " null_keys bigint;",
7746
+ " distinct_keys bigint;",
7747
+ ...keys.map((key, index3) => ` watermark_${index3} ${key.pgType};`),
7748
+ ...requiredNullDeclarations(mapped, keys),
7749
+ ...mapped.map((column) => ` cast_${column.column.columnName} bigint;`)
7750
+ ];
7751
+ const body = [
7752
+ "BEGIN",
7753
+ ...keys.map((_, index3) => ` watermark_${index3} := start_${index3};`),
7754
+ ` ${renderGuard({ sourceSql, keys, mapped })}`,
7755
+ " LOOP",
7756
+ ` ${renderPage({ targetSql, sourceSql, mapped, keys, onConflict, batchSize })}`,
7757
+ " EXIT WHEN batch_scanned = 0;",
7758
+ " RAISE NOTICE 'orm import scanned % affected %', batch_scanned, batch_affected;",
7759
+ " COMMIT;",
7760
+ " END LOOP;",
7761
+ "END"
7762
+ ].join("\n");
7763
+ return [
7764
+ `CREATE OR REPLACE PROCEDURE pg_temp.seq_orm_import(${args})`,
7765
+ "LANGUAGE plpgsql",
7766
+ `AS ${dollarQuote(`
7767
+ ${declarations.join("\n")}
7768
+ ${body}
7769
+ `)}`,
7770
+ ";",
7771
+ ""
7772
+ ].join("\n");
7773
+ }
7774
+ function nullChecked(mapped, keys) {
7775
+ return mapped.filter((column) => !column.fillsNull && column.column.nullable === false && !keys.includes(column));
7776
+ }
7777
+ function requiredNullDeclarations(mapped, keys) {
7778
+ return nullChecked(mapped, keys).map((column) => ` null_${column.column.columnName} bigint;`);
7779
+ }
7780
+ function renderGuard({
7781
+ sourceSql,
7782
+ keys,
7783
+ mapped
7784
+ }) {
7785
+ const nullKey = keys.map((key) => `${key.expression} IS NULL`).join(" OR ");
7786
+ const distinct = keys.length === 1 ? keys[0].expression : `(${keys.map((key) => key.expression).join(", ")})`;
7787
+ const nullColumns = nullChecked(mapped, keys);
7788
+ const into = [
7789
+ "total_rows",
7790
+ "null_keys",
7791
+ "distinct_keys",
7792
+ ...nullColumns.map((column) => `null_${column.column.columnName}`),
7793
+ ...mapped.map((column) => `cast_${column.column.columnName}`)
7794
+ ];
7795
+ const selects = [
7796
+ "count(*)::bigint",
7797
+ `count(*) FILTER (WHERE ${nullKey})::bigint`,
7798
+ `count(DISTINCT ${distinct})::bigint`,
7799
+ ...nullColumns.map((column) => `count(*) FILTER (WHERE src.${quoteIdentifier(column.source)} IS NULL)::bigint`),
7800
+ ...mapped.map((column) => `count(${column.expression})::bigint`)
7801
+ ];
7802
+ const checks = [
7803
+ "IF null_keys > 0 THEN RAISE EXCEPTION 'import refused: source primary key has % null values', null_keys; END IF;",
7804
+ "IF total_rows <> distinct_keys THEN RAISE EXCEPTION 'import refused: source primary key has % duplicate values', total_rows - distinct_keys; END IF;",
7805
+ ...nullColumns.map(
7806
+ (column) => `IF null_${column.column.columnName} > 0 THEN RAISE EXCEPTION 'import refused: source column ${column.source} is null on % rows and ${column.column.columnName} is required', null_${column.column.columnName}; END IF;`
7807
+ )
7808
+ ];
7809
+ return [`SELECT ${selects.join(", ")}`, ` INTO ${into.join(", ")}`, ` FROM ${sourceSql};`, ...checks.map((check) => ` ${check}`)].join("\n");
7810
+ }
7811
+ function renderPage({
7812
+ targetSql,
7813
+ sourceSql,
7814
+ mapped,
7815
+ keys,
7816
+ onConflict,
7817
+ batchSize
7818
+ }) {
7819
+ const keyExprs = keys.map((key) => key.expression);
7820
+ const where = `watermark_0 IS NULL OR (${keyExprs.join(", ")}) > (${keys.map((_, index3) => `watermark_${index3}`).join(", ")})`;
7821
+ const order = keyExprs.join(", ");
7822
+ const pageColumns = mapped.map((column) => `${column.expression} AS ${quoteIdentifier(column.column.columnName)}`);
7823
+ const insertColumns = mapped.map((column) => quoteIdentifier(column.column.columnName)).join(", ");
7824
+ const conflict = conflictClause(onConflict, keys, mapped);
7825
+ const keyOrder = keys.map((key) => `${quoteIdentifier(key.column.columnName)} DESC`).join(", ");
7826
+ const lastKeys = keys.map((key) => `(SELECT ${quoteIdentifier(key.column.columnName)} FROM page ORDER BY ${keyOrder} LIMIT 1)`).join(", ");
7827
+ const watermarkTargets = keys.map((_, index3) => `watermark_${index3}`).join(", ");
7828
+ return [
7829
+ "WITH page AS (",
7830
+ ` SELECT ${pageColumns.join(", ")}`,
7831
+ ` FROM ${sourceSql}`,
7832
+ ` WHERE ${where}`,
7833
+ ` ORDER BY ${order}`,
7834
+ ` LIMIT ${batchSize}`,
7835
+ " ),",
7836
+ " ins AS (",
7837
+ ` INSERT INTO ${targetSql} (${insertColumns})`,
7838
+ ` SELECT ${insertColumns} FROM page`,
7839
+ ` ${conflict}`,
7840
+ " RETURNING 1",
7841
+ " )",
7842
+ ` SELECT (SELECT count(*)::int FROM page), (SELECT count(*)::int FROM ins), ${lastKeys}`,
7843
+ ` INTO batch_scanned, batch_affected, ${watermarkTargets};`
7844
+ ].join("\n");
7845
+ }
7846
+ function conflictClause(onConflict, keys, mapped) {
7847
+ if (onConflict === "error") return "";
7848
+ const target = keys.map((key) => quoteIdentifier(key.column.columnName)).join(", ");
7849
+ if (onConflict === "skip") return `ON CONFLICT (${target}) DO NOTHING`;
7850
+ const assignments = mapped.filter((column) => !keys.includes(column)).map((column) => `${quoteIdentifier(column.column.columnName)} = EXCLUDED.${quoteIdentifier(column.column.columnName)}`);
7851
+ return `ON CONFLICT (${target}) DO UPDATE SET ${assignments.join(", ")}`;
7852
+ }
7853
+
7854
+ // src/import/plan.ts
7855
+ function planTableImport({
7856
+ definition,
7857
+ spec,
7858
+ after
7859
+ }) {
7860
+ const parsedDefinition = canonicalDefinitionSchema.safeParse(definition);
7861
+ if (!parsedDefinition.success) return { ok: false, errors: ["namespace definition is not canonical ORM v2"] };
7862
+ const parsedSpec = importSpecSchema.safeParse(spec);
7863
+ if (!parsedSpec.success) {
7864
+ return { ok: false, errors: parsedSpec.error.issues.map((issue) => `${issue.path.join(".") || "spec"}: ${issue.message}`) };
7865
+ }
7866
+ return planParsed({ definition: parsedDefinition.data, spec: parsedSpec.data, after });
7867
+ }
7868
+ function planParsed({
7869
+ definition,
7870
+ spec,
7871
+ after
7872
+ }) {
7873
+ const source = splitQualifiedRelation(spec.from);
7874
+ const target = resolveTarget(definition, spec.table);
7875
+ const errors = [];
7876
+ if (!source) errors.push(`from '${spec.from}' is not a schema.table identifier`);
7877
+ else if (!sourceSchemaAllowed(source.schema)) errors.push(`from schema '${source.schema}' is not a warehouse relation`);
7878
+ if (!target.table) errors.push(...target.errors);
7879
+ if (!source || !target.table) return { ok: false, errors };
7880
+ const schemaName = ormSchemaName(definition.namespace);
7881
+ if (source.schema === schemaName && source.table === target.table.table) {
7882
+ errors.push(`from '${spec.from}' is the import target`);
7883
+ }
7884
+ const enums = new Map(definition.enums.map((entry) => [entry.name, entry]));
7885
+ const mapped = mapColumns({ table: target.table, spec, schemaName, enums, errors });
7886
+ const primaryKey2 = target.table.primaryKey ?? [];
7887
+ if (primaryKey2.length === 0) errors.push(`table '${target.table.table}' needs a primary key`);
7888
+ for (const key of primaryKey2) {
7889
+ if (!mapped.some((column) => column.column.columnName === key)) {
7890
+ errors.push(`primary key column '${key}' must be mapped so existing identities are preserved`);
7891
+ }
7892
+ }
7893
+ collectUnmapped({ table: target.table, mapped, errors });
7894
+ if (spec.onConflict === "update" && mapped.every((column) => primaryKey2.includes(column.column.columnName))) {
7895
+ errors.push('onConflict "update" needs a mapped column that is not part of the primary key');
7896
+ }
7897
+ const resume = resumeArguments({ mapped, primaryKey: primaryKey2, after, schemaName, enums, errors });
7898
+ if (errors.length > 0) return { ok: false, errors };
7899
+ const omitted = omittedColumns(target.table, mapped);
7900
+ const script = renderImportScript({
7901
+ namespace: definition.namespace,
7902
+ schemaName,
7903
+ table: target.table,
7904
+ source,
7905
+ mapped,
7906
+ primaryKey: primaryKey2,
7907
+ onConflict: spec.onConflict,
7908
+ batchSize: spec.batchSize,
7909
+ omitted,
7910
+ resume
7911
+ });
7912
+ const warnings = [
7913
+ "This load runs as the namespace owner. It does not evaluate insert or update policies.",
7914
+ ...spec.onConflict === "error" ? ['onConflict "error" is not safe to re-run: the first existing key aborts the batch.'] : []
7915
+ ];
7916
+ return {
7917
+ ok: true,
7918
+ plan: { probeSql: script.probeSql, script: script.text, batchSize: spec.batchSize, onConflict: spec.onConflict, warnings }
7919
+ };
7920
+ }
7921
+ function resolveTarget(definition, name2) {
7922
+ const table = definition.tables.find((candidate) => candidate.table === name2);
7923
+ if (!table) {
7924
+ const view = definition.views?.find((candidate) => candidate.view === name2);
7925
+ return { table: null, errors: [view ? `'${name2}' is a view, not an owned table` : `table '${name2}' is not in this namespace`] };
7926
+ }
7927
+ if (table.source !== void 0) return { table: null, errors: [`'${name2}' is a synced reflection and cannot be imported into`] };
7928
+ if (table.internal || table.materializes !== void 0) {
7929
+ return { table: null, errors: [`'${name2}' is an internal relation, not an import target`] };
7930
+ }
7931
+ return { table, errors: [] };
7932
+ }
7933
+ function mapColumns({
7934
+ table,
7935
+ spec,
7936
+ schemaName,
7937
+ enums,
7938
+ errors
7939
+ }) {
7940
+ const mapped = [];
7941
+ const seen = /* @__PURE__ */ new Set();
7942
+ for (const entry of spec.columns) {
7943
+ const column = findColumn(table, entry.target);
7944
+ if (column === "ambiguous") {
7945
+ errors.push(`target '${entry.target}' matches more than one column`);
7946
+ continue;
7947
+ }
7948
+ if (!column) {
7949
+ errors.push(`target '${entry.target}' is not a column of '${table.table}'`);
7950
+ continue;
7951
+ }
7952
+ if (column.cell !== void 0) {
7953
+ errors.push(`target '${entry.target}' is cell metadata and cannot be imported`);
7954
+ continue;
7955
+ }
7956
+ if (seen.has(column.columnName)) {
7957
+ errors.push(`column '${column.columnName}' is mapped more than once`);
7958
+ continue;
7959
+ }
7960
+ if (!isSourceColumn(entry.source)) {
7961
+ errors.push(`source '${entry.source}' is not a column identifier`);
7962
+ continue;
7963
+ }
7964
+ seen.add(column.columnName);
7965
+ const pgType = columnPostgresType(column, schemaName, enums);
7966
+ if (!pgType) {
7967
+ errors.push(`column '${column.name}' references undeclared enum '${typeof column.type === "string" ? column.type : column.type.enum}'`);
7968
+ continue;
7969
+ }
7970
+ const expression = valueExpression({ column, source: entry.source, pgType, nullDefault: entry.nullDefault, schemaName, enums, errors });
7971
+ if (expression) mapped.push({ column, source: entry.source, expression, pgType, fillsNull: entry.nullDefault !== void 0 });
7972
+ }
7973
+ return mapped;
7974
+ }
7975
+ function findColumn(table, target) {
7976
+ const matches = table.columns.filter((column) => column.name === target || column.columnName === target);
7977
+ if (matches.length > 1) return "ambiguous";
7978
+ return matches[0];
7979
+ }
7980
+ function valueExpression({
7981
+ column,
7982
+ source,
7983
+ pgType,
7984
+ nullDefault,
7985
+ schemaName,
7986
+ enums,
7987
+ errors
7988
+ }) {
7989
+ const cast = `CAST(src.${quoteIdentifier(source)} AS ${pgType})`;
7990
+ if (nullDefault === void 0) return cast;
7991
+ const literal2 = literalSql({ column, value: nullDefault, schemaName, enums });
7992
+ if (!literal2.ok) {
7993
+ errors.push(literal2.error);
7994
+ return null;
7995
+ }
7996
+ return `COALESCE(${cast}, ${literal2.sql})`;
7997
+ }
7998
+ function collectUnmapped({
7999
+ table,
8000
+ mapped,
8001
+ errors
8002
+ }) {
8003
+ const mappedNames = new Set(mapped.map((column) => column.column.columnName));
8004
+ const primaryKey2 = new Set(table.primaryKey ?? []);
8005
+ for (const column of table.columns) {
8006
+ if (mappedNames.has(column.columnName) || column.cell !== void 0) continue;
8007
+ const hasDefault = column.default !== void 0 && column.default !== null || column.defaultFn !== void 0;
8008
+ const required = column.nullable === false && !hasDefault && !primaryKey2.has(column.columnName);
8009
+ if (required) {
8010
+ errors.push(`column '${column.name}' is required and has no default \u2014 map it or set a database default before importing`);
8011
+ }
8012
+ }
8013
+ }
8014
+ function omittedColumns(table, mapped) {
8015
+ const mappedNames = new Set(mapped.map((column) => column.column.columnName));
8016
+ return table.columns.filter((column) => column.cell === void 0 && !mappedNames.has(column.columnName)).map((column) => column.columnName);
8017
+ }
8018
+ function resumeArguments({
8019
+ mapped,
8020
+ primaryKey: primaryKey2,
8021
+ after,
8022
+ schemaName,
8023
+ enums,
8024
+ errors
8025
+ }) {
8026
+ if (after === void 0) return primaryKey2.map(() => "NULL");
8027
+ if (primaryKey2.length !== 1) {
8028
+ errors.push("after resumes a single-column primary key; re-run from the start for a composite key (skip and update are idempotent)");
8029
+ return [];
8030
+ }
8031
+ const key = mapped.find((column) => column.column.columnName === primaryKey2[0]);
8032
+ if (!key) return [];
8033
+ const literal2 = resumeLiteralSql({ column: key.column, after, schemaName, enums });
8034
+ if (!literal2.ok) {
8035
+ errors.push(`after: ${literal2.error}`);
8036
+ return [];
8037
+ }
8038
+ return [literal2.sql];
8039
+ }
5838
8040
 
5839
8041
  Object.defineProperty(exports, "capabilityPinsSchema", {
5840
8042
  enumerable: true,
@@ -5862,6 +8064,9 @@ exports.AUDIT_ACTIONS = AUDIT_ACTIONS;
5862
8064
  exports.CANONICAL_FORMAT = CANONICAL_FORMAT;
5863
8065
  exports.CAPABILITIES_CLAIM_KEY = CAPABILITIES_CLAIM_KEY;
5864
8066
  exports.CAPABILITY_KEY_PATTERN = CAPABILITY_KEY_PATTERN;
8067
+ exports.CELLS_FIELD = CELLS_FIELD;
8068
+ exports.CELL_PARTS = CELL_PARTS;
8069
+ exports.CELL_PART_SUFFIX = CELL_PART_SUFFIX;
5865
8070
  exports.CLAIMS_CONTRACT = CLAIMS_CONTRACT;
5866
8071
  exports.CLAIMS_SETTING = CLAIMS_SETTING;
5867
8072
  exports.CLAIM_KEY_PATTERN = CLAIM_KEY_PATTERN;
@@ -5871,22 +8076,28 @@ exports.COLUMN_DEFAULT_FNS = COLUMN_DEFAULT_FNS;
5871
8076
  exports.COLUMN_IDENTIFIER_PATTERN = COLUMN_IDENTIFIER_PATTERN;
5872
8077
  exports.COLUMN_TYPES = COLUMN_TYPES2;
5873
8078
  exports.COMPARE_OPS = COMPARE_OPS;
8079
+ exports.COMPOSED_IR_VERSION = COMPOSED_IR_VERSION;
5874
8080
  exports.CONSUMER_ROLE_SUFFIX = CONSUMER_ROLE_SUFFIX;
5875
8081
  exports.ColumnBuilder = ColumnBuilder;
5876
8082
  exports.DATE_BUCKETS = DATE_BUCKETS;
5877
8083
  exports.DEFINITION_FILE_EXTENSIONS = DEFINITION_FILE_EXTENSIONS;
5878
8084
  exports.ENUM_BRAND = ENUM_BRAND;
8085
+ exports.GUARDED_IR_VERSION = GUARDED_IR_VERSION;
5879
8086
  exports.HASH_DOMAIN = HASH_DOMAIN;
5880
8087
  exports.IDENTIFIER_PATTERN = IDENTIFIER_PATTERN;
5881
8088
  exports.IDENTIFIER_RE = IDENTIFIER_RE;
8089
+ exports.IMPORT_CONFLICT_MODES = IMPORT_CONFLICT_MODES;
5882
8090
  exports.INPUT_REF_KINDS = INPUT_REF_KINDS;
5883
8091
  exports.IR_VERSION = IR_VERSION;
5884
8092
  exports.IrVersionError = IrVersionError;
5885
8093
  exports.LEGACY_IR_VERSION = LEGACY_IR_VERSION;
5886
8094
  exports.LINK_BRAND = LINK_BRAND;
8095
+ exports.MATERIALIZE_ACTOR = MATERIALIZE_ACTOR;
8096
+ exports.MATERIALIZE_CLAIMS_JSON = MATERIALIZE_CLAIMS_JSON;
5887
8097
  exports.MAX_DEFINITION_FILE_BYTES = MAX_DEFINITION_FILE_BYTES;
5888
8098
  exports.MAX_GENERATED_SDL_BYTES = MAX_GENERATED_SDL_BYTES;
5889
8099
  exports.MAX_IDENTIFIER_LENGTH = MAX_IDENTIFIER_LENGTH;
8100
+ exports.MAX_TRACKED_COLUMN_NAME = MAX_TRACKED_COLUMN_NAME;
5890
8101
  exports.MIGRATIONS_DIRNAME = MIGRATIONS_DIRNAME;
5891
8102
  exports.NAMESPACE_CONFIG_BRAND = NAMESPACE_CONFIG_BRAND;
5892
8103
  exports.NAMESPACE_CONFIG_FILE = NAMESPACE_CONFIG_FILE;
@@ -5905,7 +8116,9 @@ exports.QUALIFIED_TABLE_PATTERN = QUALIFIED_TABLE_PATTERN;
5905
8116
  exports.RESERVED_COLUMN_NAMES = RESERVED_COLUMN_NAMES;
5906
8117
  exports.RESERVED_TABLE_NAMES = RESERVED_TABLE_NAMES;
5907
8118
  exports.ROLES_CLAIM_KEY = ROLES_CLAIM_KEY;
8119
+ exports.RUNTIME_SQL_IR_VERSION = RUNTIME_SQL_IR_VERSION;
5908
8120
  exports.SCHEMA_DIR = SCHEMA_DIR;
8121
+ exports.SQL_IR_VERSION = SQL_IR_VERSION;
5909
8122
  exports.STATEMENT_KINDS = STATEMENT_KINDS;
5910
8123
  exports.SYNCED_CONTRACT_HASH_DOMAIN = SYNCED_CONTRACT_HASH_DOMAIN;
5911
8124
  exports.SYNCED_SOURCE_PATTERN = SYNCED_SOURCE_PATTERN;
@@ -5943,6 +8156,7 @@ exports.canonicalViewSchema = canonicalViewSchema;
5943
8156
  exports.canonicalizeDefinition = canonicalizeDefinition;
5944
8157
  exports.capabilityExpression = capabilityExpression;
5945
8158
  exports.castParts = castParts;
8159
+ exports.cellColumnName = cellColumnName;
5946
8160
  exports.checksumOf = checksumOf;
5947
8161
  exports.claimExpression = claimExpression;
5948
8162
  exports.classifyDestructive = classifyDestructive;
@@ -5961,9 +8175,12 @@ exports.computeSyncedContractHash = computeSyncedContractHash;
5961
8175
  exports.consumerRoleName = consumerRoleName;
5962
8176
  exports.convertV1DefinitionToCanonical = convertV1DefinitionToCanonical;
5963
8177
  exports.createOrmMigrationExport = createOrmMigrationExport;
8178
+ exports.custom = custom;
5964
8179
  exports.declaredCapabilityKeys = declaredCapabilityKeys;
5965
8180
  exports.defineEnum = defineEnum;
5966
8181
  exports.defineNamespace = defineNamespace;
8182
+ exports.defineOperation = defineOperation;
8183
+ exports.defineResolvedTable = defineResolvedTable;
5967
8184
  exports.defineSyncedTable = defineSyncedTable;
5968
8185
  exports.defineTable = defineTable;
5969
8186
  exports.defineView = defineView;
@@ -5973,12 +8190,15 @@ exports.diffStatements = diffStatements;
5973
8190
  exports.emptyJournal = emptyJournal;
5974
8191
  exports.emptySnapshot = emptySnapshot;
5975
8192
  exports.endAuthoringContext = endAuthoringContext;
8193
+ exports.etlSource = etlSource;
5976
8194
  exports.extractNamedParams = extractNamedParams;
5977
8195
  exports.extractOrmSyncedTableContract = extractOrmSyncedTableContract;
5978
8196
  exports.generateNamespaceTypes = generateNamespaceTypes;
5979
8197
  exports.generateSnapshot = generateSnapshot;
5980
8198
  exports.generateSnapshotV2 = generateSnapshotV2;
8199
+ exports.highest = highest;
5981
8200
  exports.identifierSchema = identifierSchema;
8201
+ exports.importSpecSchema = importSpecSchema;
5982
8202
  exports.indexByHash = indexByHash;
5983
8203
  exports.isColumnBuilder = isColumnBuilder;
5984
8204
  exports.isEnumRef = isEnumRef;
@@ -5990,17 +8210,26 @@ exports.isSeqLink = isSeqLink;
5990
8210
  exports.isSeqNamespaceConfig = isSeqNamespaceConfig;
5991
8211
  exports.isSeqTable = isSeqTable;
5992
8212
  exports.isSeqView = isSeqView;
8213
+ exports.latest = latest;
5993
8214
  exports.limitIrSchema = limitIrSchema;
5994
8215
  exports.loadCommittedMigrations = loadCommittedMigrations;
5995
8216
  exports.loadCommittedTipMigration = loadCommittedTipMigration;
5996
8217
  exports.loadNamespaceDefinition = loadNamespaceDefinition;
5997
8218
  exports.loadNamespaceFromDir = loadNamespaceFromDir;
8219
+ exports.lowest = lowest;
8220
+ exports.managedFn = managedFn;
8221
+ exports.manualSource = manualSource;
8222
+ exports.materializeLockKey = materializeLockKey;
8223
+ exports.materializedRefreshFunctionName = materializedRefreshFunctionName;
5998
8224
  exports.migrationName = migrationName;
5999
8225
  exports.namespaceConfigSchema = namespaceConfigSchema;
6000
8226
  exports.namespaceSchema = namespaceSchema;
8227
+ exports.nonNullFirst = nonNullFirst;
6001
8228
  exports.normalizeCanonical = normalizeCanonical;
6002
8229
  exports.normalizeIr = normalizeIr;
6003
8230
  exports.normalizeSnapshot = normalizeSnapshot;
8231
+ exports.objectRef = objectRef;
8232
+ exports.ontologyContractSchema = ontologyContractSchema;
6004
8233
  exports.orderIrSchema = orderIrSchema;
6005
8234
  exports.ormMigrationExportSchema = ormMigrationExportSchema;
6006
8235
  exports.ormSchemaName = ormSchemaName;
@@ -6014,16 +8243,22 @@ exports.parseTableFile = parseTableFile;
6014
8243
  exports.parsedPredicateSchema = parsedPredicateSchema;
6015
8244
  exports.pendingKitStatements = pendingKitStatements;
6016
8245
  exports.pendingKitStatementsV2 = pendingKitStatementsV2;
8246
+ exports.planCommittedMigrationV2 = planCommittedMigrationV2;
6017
8247
  exports.planMigration = planMigration;
6018
8248
  exports.planMigrationV2 = planMigrationV2;
8249
+ exports.planTableImport = planTableImport;
6019
8250
  exports.policyExpressionSchema = policyExpressionSchema;
6020
8251
  exports.policyVariantSchema = policyVariantSchema;
6021
8252
  exports.predIrSchema = predIrSchema;
6022
8253
  exports.predicateOperands = predicateOperands;
8254
+ exports.prefetchPlanIrSchema = prefetchPlanIrSchema;
8255
+ exports.prefetchSource = prefetchSource;
8256
+ exports.priority = priority;
6023
8257
  exports.quoteIdentifier = quoteIdentifier;
6024
8258
  exports.quoteLiteral = quoteLiteral;
6025
8259
  exports.readMigrationSql = readMigrationSql;
6026
8260
  exports.readMigrationWorkspace = readMigrationWorkspace;
8261
+ exports.readOntologyContract = readOntologyContract;
6027
8262
  exports.readSnapshot = readSnapshot;
6028
8263
  exports.rebaseViolations = rebaseViolations;
6029
8264
  exports.referencedCapabilities = referencedCapabilities;
@@ -6031,6 +8266,8 @@ exports.referencedColumns = referencedColumns;
6031
8266
  exports.renderMigrationSql = renderMigrationSql;
6032
8267
  exports.renderOrmMigrationExport = renderOrmMigrationExport;
6033
8268
  exports.requiredArtifactIrVersion = requiredArtifactIrVersion;
8269
+ exports.resolvedMetaIrSchema = resolvedMetaIrSchema;
8270
+ exports.resolvedSourceIrSchema = resolvedSourceIrSchema;
6034
8271
  exports.revertOverlayMutation = revertOverlayMutation;
6035
8272
  exports.rewriteSyncedSourceRelationsInViewQuery = rewriteSyncedSourceRelationsInViewQuery;
6036
8273
  exports.roleExpression = roleExpression;
@@ -6053,6 +8290,7 @@ exports.toMany = toMany;
6053
8290
  exports.toOne = toOne;
6054
8291
  exports.toPlatformUser = toPlatformUser;
6055
8292
  exports.typesInterfaceName = typesInterfaceName;
8293
+ exports.validateNamespaceCatalog = validateNamespaceCatalog;
6056
8294
  exports.validateViewCatalogStatement = validateViewCatalogStatement;
6057
8295
  exports.valueIrSchema = valueIrSchema;
6058
8296
  exports.variableDefaultIrSchema = variableDefaultIrSchema;