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