@sequenceholdings/orm 2.1.1 → 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 (68) hide show
  1. package/README.md +3 -1
  2. package/dist/authoring.cjs +284 -99
  3. package/dist/authoring.cjs.map +1 -1
  4. package/dist/authoring.d.cts +31 -8
  5. package/dist/authoring.d.ts +31 -8
  6. package/dist/authoring.js +282 -100
  7. package/dist/authoring.js.map +1 -1
  8. package/dist/canonical.cjs +51 -3
  9. package/dist/canonical.cjs.map +1 -1
  10. package/dist/canonical.d.cts +838 -12
  11. package/dist/canonical.d.ts +838 -12
  12. package/dist/canonical.js +47 -4
  13. package/dist/canonical.js.map +1 -1
  14. package/dist/cli.cjs +5838 -4197
  15. package/dist/cli.cjs.map +1 -1
  16. package/dist/cli.js +5838 -4197
  17. package/dist/cli.js.map +1 -1
  18. package/dist/client.cjs +49 -32
  19. package/dist/client.cjs.map +1 -1
  20. package/dist/client.d.cts +49 -6
  21. package/dist/client.d.ts +49 -6
  22. package/dist/client.js +49 -32
  23. package/dist/client.js.map +1 -1
  24. package/dist/compiler.cjs +1237 -263
  25. package/dist/compiler.cjs.map +1 -1
  26. package/dist/compiler.d.cts +57 -3
  27. package/dist/compiler.d.ts +57 -3
  28. package/dist/compiler.js +1230 -264
  29. package/dist/compiler.js.map +1 -1
  30. package/dist/{constants-YoFGjIBM.d.cts → constants-C6EG6Wq7.d.cts} +1 -1
  31. package/dist/{constants-YoFGjIBM.d.ts → constants-C6EG6Wq7.d.ts} +1 -1
  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 -2
  37. package/dist/dynamic-input.d.ts +9 -2
  38. package/dist/dynamic-input.js +2 -1
  39. package/dist/dynamic-input.js.map +1 -1
  40. package/dist/index.cjs +1094 -321
  41. package/dist/index.cjs.map +1 -1
  42. package/dist/index.d.cts +479 -19
  43. package/dist/index.d.ts +479 -19
  44. package/dist/index.js +1083 -322
  45. package/dist/index.js.map +1 -1
  46. package/dist/ir.cjs +84 -10
  47. package/dist/ir.cjs.map +1 -1
  48. package/dist/ir.d.cts +138 -13
  49. package/dist/ir.d.ts +138 -13
  50. package/dist/ir.js +81 -11
  51. package/dist/ir.js.map +1 -1
  52. package/dist/predicate-v2.cjs +78 -9
  53. package/dist/predicate-v2.cjs.map +1 -1
  54. package/dist/predicate-v2.d.cts +1 -1
  55. package/dist/predicate-v2.d.ts +1 -1
  56. package/dist/predicate-v2.js +78 -9
  57. package/dist/predicate-v2.js.map +1 -1
  58. package/dist/sdl.cjs +156 -21
  59. package/dist/sdl.cjs.map +1 -1
  60. package/dist/sdl.d.cts +8 -1
  61. package/dist/sdl.d.ts +8 -1
  62. package/dist/sdl.js +156 -21
  63. package/dist/sdl.js.map +1 -1
  64. package/package.json +5 -4
  65. package/src/codegen-gql/server-fns.cjs +2 -2
  66. package/templates/orm-namespace/AGENTS.md +10 -0
  67. package/templates/orm-namespace/CLAUDE.md +1 -84
  68. package/templates/orm-namespace/package.json +1 -1
package/dist/authoring.js CHANGED
@@ -155,7 +155,8 @@ var NAMESPACE_PATTERN = /^[a-z][a-z0-9_]{0,40}$/;
155
155
  var IDENTIFIER_PATTERN = /^[a-z][a-z0-9_]*$/;
156
156
  var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
157
157
  var MAX_IDENTIFIER_LENGTH = 63;
158
- var SYNCED_SOURCE_PATTERN = /^gold\.[a-z][a-z0-9_]*$/;
158
+ var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]{0,62}\.[a-z][a-z0-9_]{0,62}$/;
159
+ var SYNCED_SOURCE_PATTERN = QUALIFIED_TABLE_PATTERN;
159
160
  var HASH_DOMAIN = "sequence-orm-definitions\0";
160
161
  var CELL_PARTS = ["at", "by", "rev", "source", "conflicts"];
161
162
  var CELL_PART_SUFFIX = {
@@ -232,6 +233,9 @@ function registerNamespaceConfig(value) {
232
233
  // src/authoring/table.ts
233
234
  var TABLE_BRAND = "seq-table";
234
235
  var LINK_BRAND = "seq-link";
236
+ function objectRef(namespace, table) {
237
+ return { kind: "seq-external-object", namespace, table };
238
+ }
235
239
  function toOne(target, options) {
236
240
  return {
237
241
  kind: LINK_BRAND,
@@ -285,51 +289,51 @@ function assertIdentifier({ value, what, file, pattern = IDENTIFIER_PATTERN }) {
285
289
  );
286
290
  }
287
291
  }
288
- function defineTable(name, columns, config = {}) {
292
+ function defineTable(name2, columns, config = {}) {
289
293
  const file = activeAuthoringContext()?.currentFile;
290
- assertIdentifier({ value: name, what: "table name", file });
294
+ assertIdentifier({ value: name2, what: "table name", file });
291
295
  const entries = Object.entries(columns);
292
296
  if (entries.length === 0) {
293
- throw new OrmDefinitionError(`table "${name}" declares no columns`, file);
297
+ throw new OrmDefinitionError(`table "${name2}" declares no columns`, file);
294
298
  }
295
299
  const seenColumnNames = /* @__PURE__ */ new Map();
296
300
  for (const [propertyName, builder] of entries) {
297
301
  if (!isColumnBuilder(builder)) {
298
302
  throw new OrmDefinitionError(
299
- `table "${name}": column "${propertyName}" is not a column builder \u2014 use the t.* factories (t.uuid(), t.text(), \u2026)`,
303
+ `table "${name2}": column "${propertyName}" is not a column builder \u2014 use the t.* factories (t.uuid(), t.text(), \u2026)`,
300
304
  file
301
305
  );
302
306
  }
303
307
  const columnName = builder.columnNameFor(propertyName);
304
- assertIdentifier({ value: columnName, what: `table "${name}": column "${propertyName}" column name`, file, pattern: COLUMN_IDENTIFIER_PATTERN });
308
+ assertIdentifier({ value: columnName, what: `table "${name2}": column "${propertyName}" column name`, file, pattern: COLUMN_IDENTIFIER_PATTERN });
305
309
  if (config.materializes === void 0 && (propertyName === CELLS_FIELD || Object.values(CELL_PART_SUFFIX).some((suffix) => columnName.endsWith(suffix) || propertyName.endsWith(suffix)))) {
306
310
  throw new OrmDefinitionError(
307
- `table "${name}": "${propertyName}" collides with the reserved cell-metadata names ('${CELLS_FIELD}', or a ${Object.values(CELL_PART_SUFFIX).join("/")} suffix)`,
311
+ `table "${name2}": "${propertyName}" collides with the reserved cell-metadata names ('${CELLS_FIELD}', or a ${Object.values(CELL_PART_SUFFIX).join("/")} suffix)`,
308
312
  file
309
313
  );
310
314
  }
311
315
  const existing = seenColumnNames.get(columnName);
312
316
  if (existing !== void 0) {
313
317
  throw new OrmDefinitionError(
314
- `table "${name}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
318
+ `table "${name2}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
315
319
  file
316
320
  );
317
321
  }
318
322
  seenColumnNames.set(columnName, propertyName);
319
323
  }
320
- const table = { kind: TABLE_BRAND, tableName: name, columns, config, source: void 0, file };
324
+ const table = { kind: TABLE_BRAND, tableName: name2, columns, config, source: void 0, file };
321
325
  registerTable(table);
322
326
  return table;
323
327
  }
324
- function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
328
+ function defineSyncedTable(name2, columnsOrConfig, maybeConfig) {
325
329
  const file = activeAuthoringContext()?.currentFile;
326
- assertIdentifier({ value: name, what: "table name", file });
330
+ assertIdentifier({ value: name2, what: "table name", file });
327
331
  const isTyped = maybeConfig !== void 0;
328
332
  const columns = isTyped ? columnsOrConfig : {};
329
333
  const config = isTyped ? maybeConfig : columnsOrConfig;
330
334
  if (!SYNCED_SOURCE_PATTERN.test(config.source)) {
331
335
  throw new OrmDefinitionError(
332
- `synced table "${name}": source "${config.source}" must match ${SYNCED_SOURCE_PATTERN} (gold layer only)`,
336
+ `synced table "${name2}": source "${config.source}" must be a qualified lowercase schema.table identifier`,
333
337
  file
334
338
  );
335
339
  }
@@ -339,21 +343,21 @@ function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
339
343
  for (const [propertyName, builder] of entries) {
340
344
  if (!isColumnBuilder(builder)) {
341
345
  throw new OrmDefinitionError(
342
- `synced table "${name}": column "${propertyName}" is not a column builder \u2014 use the t.* factories (t.uuid(), t.text(), \u2026)`,
346
+ `synced table "${name2}": column "${propertyName}" is not a column builder \u2014 use the t.* factories (t.uuid(), t.text(), \u2026)`,
343
347
  file
344
348
  );
345
349
  }
346
350
  const columnName = builder.columnNameFor(propertyName);
347
351
  assertIdentifier({
348
352
  value: columnName,
349
- what: `synced table "${name}": column "${propertyName}" column name`,
353
+ what: `synced table "${name2}": column "${propertyName}" column name`,
350
354
  file,
351
355
  pattern: COLUMN_IDENTIFIER_PATTERN
352
356
  });
353
357
  const existing = seenColumnNames.get(columnName);
354
358
  if (existing !== void 0) {
355
359
  throw new OrmDefinitionError(
356
- `synced table "${name}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
360
+ `synced table "${name2}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
357
361
  file
358
362
  );
359
363
  }
@@ -364,13 +368,13 @@ function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
364
368
  const hasConfigPk = config.primaryKey !== void 0 && config.primaryKey.length > 0;
365
369
  if (columnLevelPk.length === 0 && !hasConfigPk) {
366
370
  throw new OrmDefinitionError(
367
- `synced table "${name}" declares columns but no primary key \u2014 use .primaryKey() or config.primaryKey`,
371
+ `synced table "${name2}" declares columns but no primary key \u2014 use .primaryKey() or config.primaryKey`,
368
372
  file
369
373
  );
370
374
  }
371
375
  if (columnLevelPk.length > 0 && hasConfigPk) {
372
376
  throw new OrmDefinitionError(
373
- `synced table "${name}": declare the primary key either with .primaryKey() on columns or with config.primaryKey \u2014 not both`,
377
+ `synced table "${name2}": declare the primary key either with .primaryKey() on columns or with config.primaryKey \u2014 not both`,
374
378
  file
375
379
  );
376
380
  }
@@ -382,7 +386,7 @@ function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
382
386
  if (config.policies !== void 0) tableConfig.policies = { read: config.policies.read };
383
387
  const table = {
384
388
  kind: TABLE_BRAND,
385
- tableName: name,
389
+ tableName: name2,
386
390
  columns,
387
391
  config: tableConfig,
388
392
  source: config.source,
@@ -391,26 +395,26 @@ function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
391
395
  registerTable(table);
392
396
  return table;
393
397
  }
394
- function defineEnum(name, values) {
398
+ function defineEnum(name2, values) {
395
399
  const file = activeAuthoringContext()?.currentFile;
396
- assertIdentifier({ value: name, what: "enum name", file });
400
+ assertIdentifier({ value: name2, what: "enum name", file });
397
401
  if (values.length === 0 || values.length > 100) {
398
- throw new OrmDefinitionError(`enum "${name}" must declare between 1 and 100 values`, file);
402
+ throw new OrmDefinitionError(`enum "${name2}" must declare between 1 and 100 values`, file);
399
403
  }
400
404
  const seen = /* @__PURE__ */ new Set();
401
405
  for (const value of values) {
402
406
  if (value.length === 0 || value.length > MAX_IDENTIFIER_LENGTH) {
403
407
  throw new OrmDefinitionError(
404
- `enum "${name}": value ${JSON.stringify(value)} must be 1-${MAX_IDENTIFIER_LENGTH} characters`,
408
+ `enum "${name2}": value ${JSON.stringify(value)} must be 1-${MAX_IDENTIFIER_LENGTH} characters`,
405
409
  file
406
410
  );
407
411
  }
408
412
  if (seen.has(value)) {
409
- throw new OrmDefinitionError(`enum "${name}" declares duplicate value "${value}"`, file);
413
+ throw new OrmDefinitionError(`enum "${name2}" declares duplicate value "${value}"`, file);
410
414
  }
411
415
  seen.add(value);
412
416
  }
413
- const ref = { kind: ENUM_BRAND, name, values: [...values] };
417
+ const ref = { kind: ENUM_BRAND, name: name2, values: [...values] };
414
418
  registerEnum(ref);
415
419
  return ref;
416
420
  }
@@ -418,52 +422,52 @@ var VIEW_BRAND = "seq-view";
418
422
  function isSeqView(value) {
419
423
  return typeof value === "object" && value !== null && value.kind === VIEW_BRAND;
420
424
  }
421
- function defineView(name, columns, config) {
425
+ function defineView(name2, columns, config) {
422
426
  const file = activeAuthoringContext()?.currentFile;
423
- if (!IDENTIFIER_PATTERN.test(name) || name.length > MAX_IDENTIFIER_LENGTH) {
427
+ if (!IDENTIFIER_PATTERN.test(name2) || name2.length > MAX_IDENTIFIER_LENGTH) {
424
428
  throw new OrmDefinitionError(
425
- `view name "${name}" must match ${IDENTIFIER_PATTERN} and be at most ${MAX_IDENTIFIER_LENGTH} characters`,
429
+ `view name "${name2}" must match ${IDENTIFIER_PATTERN} and be at most ${MAX_IDENTIFIER_LENGTH} characters`,
426
430
  file
427
431
  );
428
432
  }
429
433
  const entries = Object.entries(columns);
430
- if (entries.length === 0) throw new OrmDefinitionError(`view "${name}" declares no columns`, file);
434
+ if (entries.length === 0) throw new OrmDefinitionError(`view "${name2}" declares no columns`, file);
431
435
  const seenColumnNames = /* @__PURE__ */ new Map();
432
436
  for (const [propertyName, builder] of entries) {
433
437
  if (!isColumnBuilder(builder)) {
434
438
  throw new OrmDefinitionError(
435
- `view "${name}": column "${propertyName}" is not a column builder \u2014 use the t.* factories`,
439
+ `view "${name2}": column "${propertyName}" is not a column builder \u2014 use the t.* factories`,
436
440
  file
437
441
  );
438
442
  }
439
443
  const columnName = builder.columnNameFor(propertyName);
440
444
  if (!COLUMN_IDENTIFIER_PATTERN.test(columnName) || columnName.length > MAX_IDENTIFIER_LENGTH) {
441
- throw new OrmDefinitionError(`view "${name}": column "${propertyName}" has invalid column name "${columnName}"`, file);
445
+ throw new OrmDefinitionError(`view "${name2}": column "${propertyName}" has invalid column name "${columnName}"`, file);
442
446
  }
443
447
  const existing = seenColumnNames.get(columnName);
444
448
  if (existing !== void 0) {
445
449
  throw new OrmDefinitionError(
446
- `view "${name}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
450
+ `view "${name2}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
447
451
  file
448
452
  );
449
453
  }
450
454
  seenColumnNames.set(columnName, propertyName);
451
455
  }
452
- const view = { kind: VIEW_BRAND, viewName: name, columns, config, file };
456
+ const view = { kind: VIEW_BRAND, viewName: name2, columns, config, file };
453
457
  registerView(view);
454
458
  return view;
455
459
  }
456
460
  var QUERY_COLUMN_FACTORIES = {
457
- uuid: (name) => uuid(name),
458
- text: (name) => text(name),
459
- integer: (name) => integer(name),
460
- bigint: (name) => bigint(name, { mode: "bigint" }),
461
- numeric: (name) => numeric(name),
462
- double: (name) => doublePrecision(name),
463
- boolean: (name) => boolean(name),
464
- timestamptz: (name) => timestamp(name, { withTimezone: true }),
465
- date: (name) => date(name),
466
- jsonb: (name) => jsonb(name)
461
+ uuid: (name2) => uuid(name2),
462
+ text: (name2) => text(name2),
463
+ integer: (name2) => integer(name2),
464
+ bigint: (name2) => bigint(name2, { mode: "bigint" }),
465
+ numeric: (name2) => numeric(name2),
466
+ double: (name2) => doublePrecision(name2),
467
+ boolean: (name2) => boolean(name2),
468
+ timestamptz: (name2) => timestamp(name2, { withTimezone: true }),
469
+ date: (name2) => date(name2),
470
+ jsonb: (name2) => jsonb(name2)
467
471
  };
468
472
  function compileViewQueries(input) {
469
473
  const schemaName = `orm_${input.namespace}`;
@@ -519,9 +523,9 @@ function compileViewQueries(input) {
519
523
  }
520
524
  const handle = handles.get(resolved);
521
525
  if (!handle) {
522
- const name = isSeqTable(resolved) ? resolved.tableName : resolved.viewName;
526
+ const name2 = isSeqTable(resolved) ? resolved.tableName : resolved.viewName;
523
527
  throw new OrmDefinitionError(
524
- `view "${authored.viewName}" references relation "${name}" which is not part of this namespace`,
528
+ `view "${authored.viewName}" references relation "${name2}" which is not part of this namespace`,
525
529
  authored.file
526
530
  );
527
531
  }
@@ -712,8 +716,8 @@ function overlayWinsUnion(input) {
712
716
  // src/authoring/cow-mutations.ts
713
717
  function overrideRowMutation(input) {
714
718
  const { overlay, synced, pk } = input;
715
- const name = input.name ?? `override_${overlay.tableName}`;
716
- assertMutationName(name, "overrideRowMutation");
719
+ const name2 = input.name ?? `override_${overlay.tableName}`;
720
+ assertMutationName(name2, "overrideRowMutation");
717
721
  assertOverlayPairPrimaryKey({ overlay, synced, pk, helper: "overrideRowMutation" });
718
722
  const sharedProperties = sharedNonPkProperties({ overlay, synced, pk });
719
723
  const setProperties = resolveSetProperties({
@@ -732,7 +736,7 @@ function overrideRowMutation(input) {
732
736
  });
733
737
  return {
734
738
  kind: "override",
735
- name,
739
+ name: name2,
736
740
  synced: synced.tableName,
737
741
  pk,
738
742
  copy: [pk, ...sharedProperties],
@@ -741,23 +745,23 @@ function overrideRowMutation(input) {
741
745
  }
742
746
  function revertOverlayMutation(input) {
743
747
  const { overlay, synced, pk } = input;
744
- const name = input.name ?? `revert_${overlay.tableName}`;
745
- assertMutationName(name, "revertOverlayMutation");
748
+ const name2 = input.name ?? `revert_${overlay.tableName}`;
749
+ assertMutationName(name2, "revertOverlayMutation");
746
750
  assertOverlayPairPrimaryKey({ overlay, synced, pk, helper: "revertOverlayMutation" });
747
751
  assertMatchingColumns({ overlay, synced, properties: [pk], helper: "revertOverlayMutation" });
748
752
  return {
749
753
  kind: "revert",
750
- name,
754
+ name: name2,
751
755
  synced: synced.tableName,
752
756
  pk,
753
757
  copy: [],
754
758
  set: []
755
759
  };
756
760
  }
757
- function assertMutationName(name, helper) {
758
- if (!IDENTIFIER_PATTERN.test(name) || name.length > MAX_IDENTIFIER_LENGTH) {
761
+ function assertMutationName(name2, helper) {
762
+ if (!IDENTIFIER_PATTERN.test(name2) || name2.length > MAX_IDENTIFIER_LENGTH) {
759
763
  throw new OrmDefinitionError(
760
- `${helper}: mutation name "${name}" must match ${IDENTIFIER_PATTERN} and be at most ${MAX_IDENTIFIER_LENGTH} characters`
764
+ `${helper}: mutation name "${name2}" must match ${IDENTIFIER_PATTERN} and be at most ${MAX_IDENTIFIER_LENGTH} characters`
761
765
  );
762
766
  }
763
767
  }
@@ -883,7 +887,7 @@ function scalarColumnType(table, property, helper) {
883
887
  }
884
888
  return type;
885
889
  }
886
- var quoteIdentifier = (name) => `"${name.replaceAll('"', '""')}"`;
890
+ var quoteIdentifier = (name2) => `"${name2.replaceAll('"', '""')}"`;
887
891
  var quoteLiteral = (value) => `'${value.replaceAll("'", "''")}'`;
888
892
 
889
893
  // src/checks/compile.ts
@@ -1217,8 +1221,8 @@ function validateResolvedView({ view, tables, views = [] }) {
1217
1221
  validateRules(scope);
1218
1222
  validateViewCells(scope);
1219
1223
  if (resolved.materialized) validateMaterialized({ scope, tableByName, views });
1220
- for (const name of resolved.deletedBy) {
1221
- if (scope.sourceByName.get(name)?.kind !== "manual") fail(`deletedBy '${name}' must be the manual source`);
1224
+ for (const name2 of resolved.deletedBy) {
1225
+ if (scope.sourceByName.get(name2)?.kind !== "manual") fail(`deletedBy '${name2}' must be the manual source`);
1222
1226
  }
1223
1227
  return errors;
1224
1228
  }
@@ -1315,7 +1319,7 @@ function validateViewCells({ view, dataColumns, pkColumn, fail }) {
1315
1319
  if (column.type !== VIEW_CELL_TYPES[column.cell.part]) fail(`cell column '${column.name}' must be ${VIEW_CELL_TYPES[column.cell.part]}`);
1316
1320
  expected.delete(column.name);
1317
1321
  }
1318
- for (const name of expected.keys()) fail(`missing cell column '${name}'`);
1322
+ for (const name2 of expected.keys()) fail(`missing cell column '${name2}'`);
1319
1323
  }
1320
1324
  var columnSignature = (columns) => JSON.stringify(columns.map((column) => [column.name, column.columnName, column.type]));
1321
1325
  function validateMaterialized({ scope, tableByName, views }) {
@@ -1406,6 +1410,8 @@ var canonicalLinkSchema = z.object({
1406
1410
  kind: z.enum(["toOne", "toMany", "toPlatformUser"]),
1407
1411
  /** Target table name (namespace-local). Absent for toPlatformUser. */
1408
1412
  target: identifier2.optional(),
1413
+ /** Owning namespace of an imported target; omitted for local links. */
1414
+ namespace: identifier2.max(41).optional(),
1409
1415
  /** FK column on the owning side (toOne/toPlatformUser: on this table; toMany: on the target). */
1410
1416
  on: columnIdentifier,
1411
1417
  /** Referenced column on the target (default: target's PK / platform user `sub`). */
@@ -1664,11 +1670,12 @@ var canonicalForeignKeySchema = z.object({
1664
1670
  }).strict();
1665
1671
  var canonicalTableSchema = z.object({
1666
1672
  table: identifier2,
1673
+ displayName: z.string().trim().min(1).max(120).optional(),
1667
1674
  description: z.string().max(2e3).optional(),
1668
1675
  /** Table-level rename hint (new in v2). */
1669
1676
  renamedFrom: identifier2.optional(),
1670
- /** Synced/reflected read-only tables (`source: gold.*`) — v1 semantics. */
1671
- source: z.string().regex(/^gold\.[a-z][a-z0-9_]*$/).optional(),
1677
+ /** Synced/reflected read-only tables (`source: schema.table`) — v1 semantics. */
1678
+ source: z.string().regex(QUALIFIED_TABLE_PATTERN).optional(),
1672
1679
  columns: z.array(canonicalColumnSchema).default([]),
1673
1680
  primaryKey: z.array(columnIdentifier).min(1).max(8).optional(),
1674
1681
  links: z.array(canonicalLinkSchema).default([]),
@@ -1821,7 +1828,9 @@ function validateCellMetadata(table, index, ctx) {
1821
1828
  }
1822
1829
  }
1823
1830
  }
1824
- var canonicalDefinitionSchema = z.object({
1831
+ var exportGateSchema = z.union([z.literal(true), z.object({ capability: z.string().min(1) }).strict(), z.object({ role: z.string().min(1) }).strict()]);
1832
+ var relationExportSchema = z.object({ read: exportGateSchema, write: exportGateSchema.optional() }).strict();
1833
+ var localDefinitionSchema = z.object({
1825
1834
  definition_format: z.literal(CANONICAL_FORMAT),
1826
1835
  namespace: identifier2.max(41),
1827
1836
  description: z.string().max(2e3).optional(),
@@ -1829,6 +1838,8 @@ var canonicalDefinitionSchema = z.object({
1829
1838
  tables: z.array(canonicalTableSchema).min(0).max(500),
1830
1839
  /** Optional to preserve byte-identical hashes for pre-view canonical v2 documents. */
1831
1840
  views: z.array(canonicalViewSchema).max(500).optional(),
1841
+ /** Target-owned cross-namespace access contract, using existing namespace grants. */
1842
+ exports: z.record(identifier2, relationExportSchema).optional(),
1832
1843
  /** Verbatim custom-roles grammar (@sequenceholdings/custom-roles-manifest). */
1833
1844
  capabilities: z.object({
1834
1845
  roles: z.unknown().optional(),
@@ -1855,7 +1866,16 @@ var canonicalDefinitionSchema = z.object({
1855
1866
  }
1856
1867
  }
1857
1868
  const fkTables = new Map(definition.tables.map((table) => [table.table, table]));
1869
+ const relations = /* @__PURE__ */ new Set([...fkTables.keys(), ...(definition.views ?? []).map((view) => view.view)]);
1870
+ for (const name2 of Object.keys(definition.exports ?? {})) {
1871
+ if (!relations.has(name2)) ctx.addIssue({ code: "custom", path: ["exports", name2], message: `export '${name2}' is not a local object` });
1872
+ }
1858
1873
  for (const [tableIndex, table] of definition.tables.entries()) {
1874
+ for (const [linkIndex, link] of table.links.entries()) {
1875
+ if (link.enforce && link.namespace !== void 0 && link.namespace !== definition.namespace) {
1876
+ ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "links", linkIndex], message: "physical foreign keys require a local target; use an ORM link without enforce" });
1877
+ }
1878
+ }
1859
1879
  const names = /* @__PURE__ */ new Set([
1860
1880
  ...table.primaryKey?.length ? [`${table.table}_pkey`] : [],
1861
1881
  ...table.columns.filter((column) => column.unique && !(table.primaryKey?.length === 1 && table.primaryKey[0] === column.columnName)).map((column) => `${table.table}_${column.columnName}_unique`),
@@ -1892,14 +1912,14 @@ var canonicalDefinitionSchema = z.object({
1892
1912
  }
1893
1913
  if (fk.columns.length !== fk.references.length) fail("foreign key columns and references must have the same number of columns");
1894
1914
  if (new Set(fk.columns).size !== fk.columns.length || new Set(fk.references).size !== fk.references.length) fail("foreign key columns must not repeat");
1895
- for (const [position, name] of fk.columns.entries()) {
1896
- const sourceColumn = table.columns.find((column) => column.columnName === name);
1915
+ for (const [position, name2] of fk.columns.entries()) {
1916
+ const sourceColumn = table.columns.find((column) => column.columnName === name2);
1897
1917
  const targetColumn = target.columns.find((column) => column.columnName === fk.references[position]);
1898
1918
  if (!sourceColumn || !targetColumn) fail("foreign key references an unknown column");
1899
1919
  else if (JSON.stringify(sourceColumn.type) !== JSON.stringify(targetColumn.type)) fail("foreign key column types must match");
1900
1920
  }
1901
1921
  if (fk.onDelete === "set null" || fk.onUpdate === "set null") {
1902
- if (fk.columns.some((name) => table.columns.find((column) => column.columnName === name)?.nullable === false || table.primaryKey?.includes(name))) fail("SET NULL requires nullable source columns");
1922
+ 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");
1903
1923
  }
1904
1924
  const uniqueKeys = [target.primaryKey ?? [], ...target.indexes.filter((index2) => index2.unique).map((index2) => index2.columns), ...target.columns.filter((column) => column.unique).map((column) => [column.columnName])];
1905
1925
  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");
@@ -2004,27 +2024,48 @@ var canonicalDefinitionSchema = z.object({
2004
2024
  const visiting = /* @__PURE__ */ new Set();
2005
2025
  const visited = /* @__PURE__ */ new Set();
2006
2026
  let cycleReported = false;
2007
- const visit = (name, path) => {
2008
- if (visited.has(name) || cycleReported) return;
2009
- if (visiting.has(name)) {
2027
+ const visit = (name2, path) => {
2028
+ if (visited.has(name2) || cycleReported) return;
2029
+ if (visiting.has(name2)) {
2010
2030
  cycleReported = true;
2011
2031
  ctx.addIssue({
2012
2032
  code: "custom",
2013
2033
  path: ["views"],
2014
- message: `view dependencies form a cycle: ${[...path, name].join(" -> ")}`
2034
+ message: `view dependencies form a cycle: ${[...path, name2].join(" -> ")}`
2015
2035
  });
2016
2036
  return;
2017
2037
  }
2018
- visiting.add(name);
2019
- const view = viewByName.get(name);
2038
+ visiting.add(name2);
2039
+ const view = viewByName.get(name2);
2020
2040
  for (const dependency of view?.dependencies ?? []) {
2021
- if (viewByName.has(dependency)) visit(dependency, [...path, name]);
2041
+ if (viewByName.has(dependency)) visit(dependency, [...path, name2]);
2022
2042
  }
2023
- visiting.delete(name);
2024
- visited.add(name);
2043
+ visiting.delete(name2);
2044
+ visited.add(name2);
2025
2045
  };
2026
2046
  for (const view of views) visit(view.view, []);
2027
2047
  });
2048
+ var canonicalDefinitionSchema = localDefinitionSchema.safeExtend({
2049
+ /** Flattened, pinned snapshots. Imported relations never participate in this namespace's DDL. */
2050
+ imports: z.array(localDefinitionSchema).max(32).optional()
2051
+ });
2052
+ function canonicalRelations(definition) {
2053
+ const views = (definition.views ?? []).map((view) => ({
2054
+ table: view.view,
2055
+ ...view.description !== void 0 ? { description: view.description } : {},
2056
+ columns: view.columns,
2057
+ ...view.primaryKey !== void 0 ? { primaryKey: view.primaryKey } : {},
2058
+ links: view.links,
2059
+ indexes: [],
2060
+ // A resolved table's write policies (evaluated against the resolved row)
2061
+ // ride alongside the view's read policy.
2062
+ policies: { read: view.policies.read, ...view.resolved?.policies ?? {} },
2063
+ actions: [],
2064
+ ...view.resolved !== void 0 ? { resolved: view.resolved } : {},
2065
+ ...view.internal ? { internal: true } : {}
2066
+ }));
2067
+ return [...definition.tables, ...views].sort((a, b) => a.table.localeCompare(b.table));
2068
+ }
2028
2069
  function normalizeCanonical(definition) {
2029
2070
  const sortByName = (items) => [...items].sort((a, b) => a.name.localeCompare(b.name));
2030
2071
  const normalized = {
@@ -2051,6 +2092,11 @@ function normalizeCanonical(definition) {
2051
2092
  dependencies: [...view.dependencies].sort()
2052
2093
  }));
2053
2094
  }
2095
+ if (definition.imports?.length) {
2096
+ normalized.imports = definition.imports.map(normalizeCanonical).sort((a, b) => a.namespace.localeCompare(b.namespace));
2097
+ } else {
2098
+ delete normalized.imports;
2099
+ }
2054
2100
  return normalized;
2055
2101
  }
2056
2102
 
@@ -2175,8 +2221,8 @@ var literal = (value) => `'${value.replace(/'/g, "''")}'`;
2175
2221
  var physical = ({ source, property }) => source.table.columns[property].columnNameFor(property);
2176
2222
  function candidateSql({ source, column, pk, pgType }) {
2177
2223
  const a = ident(`s_${source.name}`);
2178
- const name = physical({ source, property: column });
2179
- const value = `CAST(${a}.${ident(name)} AS ${pgType})`;
2224
+ const name2 = physical({ source, property: column });
2225
+ const value = `CAST(${a}.${ident(name2)} AS ${pgType})`;
2180
2226
  if (source.kind === "etl") {
2181
2227
  return {
2182
2228
  source: source.name,
@@ -2186,9 +2232,9 @@ function candidateSql({ source, column, pk, pgType }) {
2186
2232
  by: "CAST(NULL AS text)"
2187
2233
  };
2188
2234
  }
2189
- const at = `${a}.${ident(cellColumnName(name, "at"))}`;
2235
+ const at = `${a}.${ident(cellColumnName(name2, "at"))}`;
2190
2236
  const tombstoned2 = source.kind === "prefetch" ? ` AND NOT ${a}.${ident(RESOLVED_SOURCE_SYSTEM_COLUMNS.tombstoned.columnName)}` : "";
2191
- return { source: source.name, value, has: `(${at} IS NOT NULL${tombstoned2})`, at, by: `${a}.${ident(cellColumnName(name, "by"))}` };
2237
+ return { source: source.name, value, has: `(${at} IS NOT NULL${tombstoned2})`, at, by: `${a}.${ident(cellColumnName(name2, "by"))}` };
2192
2238
  }
2193
2239
  function outranks({ i, j, key, direction, inclusive }) {
2194
2240
  const ki = i[key];
@@ -2281,7 +2327,7 @@ function buildResolvedQuery({ context, plan }) {
2281
2327
  plan.rules.forEach((rule, index) => {
2282
2328
  const canonical = plan.columns[rule.column].toCanonicalColumn(rule.column);
2283
2329
  const pgType = typeof canonical.type === "string" ? PG_TYPE[canonical.type] : `${schema === void 0 ? "" : `${ident(schema)}.`}${ident(canonical.type.enum)}`;
2284
- const candidates = rule.candidates.map((name) => candidateSql({ source: sourceByName.get(name), column: rule.column, pk: plan.pk, pgType }));
2330
+ const candidates = rule.candidates.map((name2) => candidateSql({ source: sourceByName.get(name2), column: rule.column, pk: plan.pk, pgType }));
2285
2331
  cells.set(rule.column, rule.comparator === "custom" ? customCell({ candidates, orderBy: rule.orderBy, index }) : builtinCell({ comparator: rule.comparator, candidates }));
2286
2332
  });
2287
2333
  const laterals = [...cells.values()].flatMap((cell) => cell.lateral ? [sql.raw(cell.lateral)] : []);
@@ -2302,7 +2348,7 @@ function buildResolvedQuery({ context, plan }) {
2302
2348
  const system = RESOLVED_VIEW_SYSTEM_COLUMNS;
2303
2349
  selected[system.writebackStatus.name] = sql.raw(`${alias(manual.name)}.${ident(RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackStatus.columnName)}`).as(system.writebackStatus.columnName);
2304
2350
  selected[system.writebackError.name] = sql.raw(`${alias(manual.name)}.${ident(RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackError.columnName)}`).as(system.writebackError.columnName);
2305
- const hidden = plan.deletedBy.map((name) => `NOT COALESCE(${alias(name)}.${ident(tombstoned2)}, false)`);
2351
+ const hidden = plan.deletedBy.map((name2) => `NOT COALESCE(${alias(name2)}.${ident(tombstoned2)}, false)`);
2306
2352
  const query = qb.select(selected).from(from);
2307
2353
  return hidden.length > 0 ? query.where(sql.raw(hidden.join(" AND "))) : query;
2308
2354
  }
@@ -2328,12 +2374,12 @@ var SYSTEM_VIEW_COLUMNS = {
2328
2374
  [RESOLVED_VIEW_SYSTEM_COLUMNS.writebackStatus.name]: () => t.text(RESOLVED_VIEW_SYSTEM_COLUMNS.writebackStatus.columnName),
2329
2375
  [RESOLVED_VIEW_SYSTEM_COLUMNS.writebackError.name]: () => t.text(RESOLVED_VIEW_SYSTEM_COLUMNS.writebackError.columnName)
2330
2376
  };
2331
- function defineResolvedTable(name, columns, config) {
2377
+ function defineResolvedTable(name2, columns, config) {
2332
2378
  const file = activeAuthoringContext()?.currentFile;
2333
2379
  const fail = (message) => {
2334
- throw new OrmDefinitionError(`resolved table "${name}": ${message}`, file);
2380
+ throw new OrmDefinitionError(`resolved table "${name2}": ${message}`, file);
2335
2381
  };
2336
- if (!IDENTIFIER_PATTERN.test(name)) fail(`name must match ${IDENTIFIER_PATTERN}`);
2382
+ if (!IDENTIFIER_PATTERN.test(name2)) fail(`name must match ${IDENTIFIER_PATTERN}`);
2337
2383
  for (const property of Object.keys(columns)) {
2338
2384
  if (RESERVED_RESOLVED_COLUMN_NAMES.has(property) || RESERVED_RESOLVED_COLUMN_NAMES.has(columns[property].columnNameFor(property))) {
2339
2385
  fail(`column "${property}" collides with a reserved system column`);
@@ -2352,7 +2398,7 @@ function defineResolvedTable(name, columns, config) {
2352
2398
  const sourceEntries = Object.entries(config.sources);
2353
2399
  if (sourceEntries.filter(([, source]) => source.kind === "manual").length !== 1) fail("declare exactly one manualSource()");
2354
2400
  const plans = sourceEntries.map(
2355
- ([sourceName, input]) => planSource({ viewName: name, sourceName, input, columns, pk, pkBuilder, dataColumns, fail })
2401
+ ([sourceName, input]) => planSource({ viewName: name2, sourceName, input, columns, pk, pkBuilder, dataColumns, fail })
2356
2402
  );
2357
2403
  const rules = planRules({ config, plans, dataColumns, fail });
2358
2404
  const deletedBy = [...config.rows?.deletedBy ?? []];
@@ -2386,23 +2432,23 @@ function defineResolvedTable(name, columns, config) {
2386
2432
  policies: { read }
2387
2433
  };
2388
2434
  if (!config.materialize) {
2389
- return defineView(name, typedColumns, { ...surface, query: (context) => buildResolvedQuery({ context, plan: queryPlan }), resolved });
2435
+ return defineView(name2, typedColumns, { ...surface, query: (context) => buildResolvedQuery({ context, plan: queryPlan }), resolved });
2390
2436
  }
2391
2437
  const materialize = config.materialize === true ? {} : config.materialize;
2392
- const computeName = `${name}__compute`;
2393
- const tableName = `${name}__materialized`;
2438
+ const computeName = `${name2}__compute`;
2439
+ const tableName = `${name2}__materialized`;
2394
2440
  const compute = defineView(computeName, typedColumns, {
2395
2441
  policies: { read },
2396
2442
  query: (context) => buildResolvedQuery({ context, plan: queryPlan }),
2397
2443
  internal: true
2398
2444
  });
2399
2445
  const table = defineTable(tableName, viewColumns, {
2400
- description: `Materialized rows of resolved table "${name}". System-maintained from "${computeName}".`,
2446
+ description: `Materialized rows of resolved table "${name2}". System-maintained from "${computeName}".`,
2401
2447
  internal: true,
2402
- materializes: name,
2448
+ materializes: name2,
2403
2449
  ...materialize.indexes !== void 0 ? { indexes: materialize.indexes } : {}
2404
2450
  });
2405
- return defineView(name, typedColumns, {
2451
+ return defineView(name2, typedColumns, {
2406
2452
  ...surface,
2407
2453
  query: (context) => {
2408
2454
  const handle = context.relation(table);
@@ -2429,7 +2475,7 @@ function planRules({ config, plans, dataColumns, fail }) {
2429
2475
  return dataColumns.map((column) => {
2430
2476
  const explicit = config.resolve?.[column];
2431
2477
  const comparator = explicit ?? fallback;
2432
- const candidates = explicit ? [...explicit.candidates] : comparator.candidates.filter((name) => plans.find((plan) => plan.name === name)?.columns.includes(column));
2478
+ const candidates = explicit ? [...explicit.candidates] : comparator.candidates.filter((name2) => plans.find((plan) => plan.name === name2)?.columns.includes(column));
2433
2479
  for (const candidate of candidates) {
2434
2480
  const plan = plans.find((entry) => entry.name === candidate);
2435
2481
  if (!plan) fail(`column "${column}" names unknown source "${candidate}"`);
@@ -2508,6 +2554,72 @@ function stableJson(value) {
2508
2554
  return `{${entries.join(",")}}`;
2509
2555
  }
2510
2556
 
2557
+ // src/canonical/composition.ts
2558
+ function localContract(definition) {
2559
+ return normalizeCanonical(localDefinitionSchema.parse(definition));
2560
+ }
2561
+ function flattenNamespaceImports(inputs) {
2562
+ const definitions = /* @__PURE__ */ new Map();
2563
+ const visit = (value, depth) => {
2564
+ if (depth > 16 || definitions.size > 32) throw new OrmDefinitionError("namespace import graph is too large");
2565
+ const definition = localContract(localDefinitionSchema.parse(value));
2566
+ const previous = definitions.get(definition.namespace);
2567
+ if (previous) {
2568
+ if (stableJson(previous) !== stableJson(definition)) throw new OrmDefinitionError(`conflicting contracts for namespace '${definition.namespace}'`);
2569
+ }
2570
+ definitions.set(definition.namespace, definition);
2571
+ if (typeof value === "object" && value !== null && "imports" in value && Array.isArray(value.imports)) {
2572
+ for (const dependency of value.imports) visit(dependency, depth + 1);
2573
+ }
2574
+ };
2575
+ for (const input of inputs) visit(input, 0);
2576
+ return [...definitions.values()].sort((a, b) => a.namespace.localeCompare(b.namespace));
2577
+ }
2578
+ function composeNamespace(definition) {
2579
+ const all = [localContract(definition), ...definition.imports ?? []];
2580
+ const owners = /* @__PURE__ */ new Map();
2581
+ const namespaces = /* @__PURE__ */ new Set();
2582
+ const enums = /* @__PURE__ */ new Map();
2583
+ for (const owner of all) {
2584
+ if (namespaces.has(owner.namespace)) throw new OrmDefinitionError(`duplicate imported namespace '${owner.namespace}'`);
2585
+ namespaces.add(owner.namespace);
2586
+ for (const value of owner.enums) {
2587
+ const previous = enums.get(value.name);
2588
+ if (previous && stableJson(previous) !== stableJson(value)) {
2589
+ throw new OrmDefinitionError(`conflicting imported enum '${value.name}'`);
2590
+ }
2591
+ enums.set(value.name, value);
2592
+ }
2593
+ for (const relation of canonicalRelations(owner)) {
2594
+ if (owners.has(relation.table)) throw new OrmDefinitionError(`ambiguous imported relation '${relation.table}'`);
2595
+ owners.set(relation.table, owner);
2596
+ }
2597
+ }
2598
+ for (const owner of definition.imports?.length ? all : []) {
2599
+ for (const relation of canonicalRelations(owner)) {
2600
+ for (const link of relation.links) {
2601
+ if (link.kind === "toPlatformUser") continue;
2602
+ const target = owners.get(link.target ?? "");
2603
+ if (!target || target.namespace !== (link.namespace ?? owner.namespace)) {
2604
+ throw new OrmDefinitionError(`link '${owner.namespace}/${relation.table}.${link.name}' has no matching imported target`);
2605
+ }
2606
+ if (target.namespace !== owner.namespace && !target.exports?.[link.target ?? ""]) {
2607
+ throw new OrmDefinitionError(`target '${target.namespace}/${link.target}' is not exported`);
2608
+ }
2609
+ }
2610
+ }
2611
+ }
2612
+ return {
2613
+ owners,
2614
+ definition: {
2615
+ ...localContract(definition),
2616
+ tables: all.flatMap((item) => item.tables),
2617
+ views: all.flatMap((item) => item.views ?? []),
2618
+ enums: [...enums.values()]
2619
+ }
2620
+ };
2621
+ }
2622
+
2511
2623
  // src/authoring/namespace.ts
2512
2624
  var NAMESPACE_CONFIG_BRAND = "seq-namespace-config";
2513
2625
  function isSeqNamespaceConfig(value) {
@@ -2523,6 +2635,8 @@ function defineNamespace(input) {
2523
2635
  namespace: input.namespace,
2524
2636
  description: input.description,
2525
2637
  capabilities: input.capabilities ?? {},
2638
+ imports: input.imports,
2639
+ exports: input.exports,
2526
2640
  file
2527
2641
  };
2528
2642
  registerNamespaceConfig(config);
@@ -2567,15 +2681,15 @@ function serializeNamespace(input) {
2567
2681
  }
2568
2682
  const relationsByName = /* @__PURE__ */ new Map();
2569
2683
  for (const relation of [...tables, ...views]) {
2570
- const name = relationName(relation);
2571
- const existing = relationsByName.get(name);
2684
+ const name2 = relationName(relation);
2685
+ const existing = relationsByName.get(name2);
2572
2686
  if (existing) {
2573
2687
  throw new OrmDefinitionError(
2574
- `duplicate relation "${name}" (defined as ${relationKind(existing)} in ${describeFile(existing)} and ${relationKind(relation)} in ${describeFile(relation)})`,
2688
+ `duplicate relation "${name2}" (defined as ${relationKind(existing)} in ${describeFile(existing)} and ${relationKind(relation)} in ${describeFile(relation)})`,
2575
2689
  relation.file
2576
2690
  );
2577
2691
  }
2578
- relationsByName.set(name, relation);
2692
+ relationsByName.set(name2, relation);
2579
2693
  }
2580
2694
  const enumsByName = /* @__PURE__ */ new Map();
2581
2695
  for (const ref of enums) {
@@ -2616,6 +2730,8 @@ function serializeNamespace(input) {
2616
2730
  };
2617
2731
  if (config.description !== void 0) candidate.description = config.description;
2618
2732
  if (canonicalViews.length > 0) candidate.views = canonicalViews;
2733
+ if (config.imports?.length) candidate.imports = flattenNamespaceImports(config.imports);
2734
+ if (config.exports) candidate.exports = config.exports;
2619
2735
  let parsed;
2620
2736
  try {
2621
2737
  parsed = canonicalDefinitionSchema.parse(candidate);
@@ -2625,7 +2741,9 @@ function serializeNamespace(input) {
2625
2741
  }
2626
2742
  throw error;
2627
2743
  }
2628
- return normalizeCanonical(parsed);
2744
+ const normalized = normalizeCanonical(parsed);
2745
+ if (normalized.imports?.length) composeNamespace(normalized);
2746
+ return normalized;
2629
2747
  }
2630
2748
  function computeCanonicalHash(definition) {
2631
2749
  return createHash("sha256").update(HASH_DOMAIN).update(stableJson(definition)).digest("hex");
@@ -2639,6 +2757,7 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
2639
2757
  policies: {},
2640
2758
  actions: []
2641
2759
  };
2760
+ if (table.config.displayName !== void 0) candidate.displayName = table.config.displayName;
2642
2761
  if (table.config.description !== void 0) candidate.description = table.config.description;
2643
2762
  if (table.config.renamedFrom !== void 0) candidate.renamedFrom = table.config.renamedFrom;
2644
2763
  if (table.source !== void 0) candidate.source = table.source;
@@ -2722,6 +2841,9 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
2722
2841
  if (table.config.foreignKeys?.length) {
2723
2842
  if (table.source) throw new OrmDefinitionError(`synced table "${table.tableName}" cannot enforce foreign keys`, table.file);
2724
2843
  candidate.foreignKeys = table.config.foreignKeys.map((fk) => {
2844
+ if (typeof fk.target === "object" && fk.target.kind === "seq-external-object") {
2845
+ throw new OrmDefinitionError("physical foreign keys require a local target; use a cross-namespace ORM link", table.file);
2846
+ }
2725
2847
  const target = resolveRelationRef({ ref: fk.target, fail: (message) => {
2726
2848
  throw new OrmDefinitionError(message, table.file);
2727
2849
  } });
@@ -2892,6 +3014,19 @@ function buildCanonicalLink({
2892
3014
  ...link.description !== void 0 ? { description: link.description } : {}
2893
3015
  };
2894
3016
  }
3017
+ if (typeof link.target === "object" && link.target?.kind === "seq-external-object") {
3018
+ if (link.enforce) return fail("cannot enforce a cross-namespace physical foreign key; use an ORM link without enforce");
3019
+ return {
3020
+ name: linkName,
3021
+ kind: link.linkKind,
3022
+ namespace: link.target.namespace,
3023
+ target: link.target.table,
3024
+ on: link.linkKind === "toOne" ? resolveColumnRef({ relation, ref: link.on, role: "link on" }) : link.on,
3025
+ ...link.references !== void 0 ? { references: link.linkKind === "toMany" ? resolveColumnRef({ relation, ref: link.references, role: "link references" }) : link.references } : {},
3026
+ enforce: false,
3027
+ ...link.description !== void 0 ? { description: link.description } : {}
3028
+ };
3029
+ }
2895
3030
  const target = resolveRelationRef({ ref: link.target, fail });
2896
3031
  const targetName = relationName(target);
2897
3032
  if (!relationsByName.has(targetName)) {
@@ -2948,11 +3083,11 @@ function assertAcyclicViewDependencies(views) {
2948
3083
  views.map((view) => [view.view, new Set(view.dependencies.filter((dependency) => viewNames.has(dependency)))])
2949
3084
  );
2950
3085
  while (remaining.size > 0) {
2951
- const ready = [...remaining.entries()].filter(([, dependencies]) => [...dependencies].every((dependency) => !remaining.has(dependency))).map(([name]) => name);
3086
+ const ready = [...remaining.entries()].filter(([, dependencies]) => [...dependencies].every((dependency) => !remaining.has(dependency))).map(([name2]) => name2);
2952
3087
  if (ready.length === 0) {
2953
3088
  throw new OrmDefinitionError(`view dependencies form a cycle among: ${[...remaining.keys()].sort().join(", ")}`);
2954
3089
  }
2955
- for (const name of ready) remaining.delete(name);
3090
+ for (const name2 of ready) remaining.delete(name2);
2956
3091
  }
2957
3092
  }
2958
3093
  function toCanonicalPolicy(policy) {
@@ -2998,8 +3133,8 @@ function syncedContractSurface({
2998
3133
  namespace,
2999
3134
  table,
3000
3135
  columns: Object.fromEntries(
3001
- Object.entries(columns).map(([name, column]) => [
3002
- name,
3136
+ Object.entries(columns).map(([name2, column]) => [
3137
+ name2,
3003
3138
  { type: column.type, nullable: column.nullable, key: column.key }
3004
3139
  ])
3005
3140
  ),
@@ -3020,7 +3155,7 @@ function extractOrmSyncedTableContract({
3020
3155
  }
3021
3156
  if (table.source === void 0) {
3022
3157
  throw new OrmDefinitionError(
3023
- `table "${table.table}" is not a synced table \u2014 extractOrmSyncedTableContract requires source: gold.*`
3158
+ `table "${table.table}" is not a synced table \u2014 extractOrmSyncedTableContract requires a qualified source`
3024
3159
  );
3025
3160
  }
3026
3161
  if (table.columns.length === 0) {
@@ -3167,7 +3302,54 @@ function purgeModuleCache(root) {
3167
3302
  if (key.startsWith(prefix)) delete require2.cache[key];
3168
3303
  }
3169
3304
  }
3305
+ var sqlScalarSchema = z.enum(["text", "boolean", "integer", "bigint", "numeric", "double", "uuid", "date", "timestamptz"]);
3306
+ var name = z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).max(63).refine((value) => !["__proto__", "prototype", "constructor"].includes(value), "reserved name");
3307
+ var base = z.object({
3308
+ name,
3309
+ namespace: name,
3310
+ language: z.literal("sql"),
3311
+ kind: z.literal("query"),
3312
+ requires: z.object({ capability: z.string().min(1).max(128) }).strict(),
3313
+ 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(),
3314
+ maxRows: z.number().int().min(1).max(1e4).default(1e3)
3315
+ });
3316
+ var fixed = base.extend({
3317
+ parameters: z.record(name, z.object({ type: sqlScalarSchema, required: z.boolean().default(true) }).strict()).default({}),
3318
+ sql: z.string().min(1).max(65536)
3319
+ }).strict();
3320
+ var runtime = base.extend({ mode: z.literal("runtime") }).strict();
3321
+ var sqlOperationDefinitionSchema = z.union([fixed, runtime]);
3322
+ function defineOperation(input) {
3323
+ return sqlOperationDefinitionSchema.parse(input);
3324
+ }
3325
+
3326
+ // src/authoring/catalog.ts
3327
+ var SEGMENT = /^[^./\\\s][^/\\]*[^./\\\s]$|^[^./\\\s]$/;
3328
+ function validateNamespaceCatalog(catalog, file) {
3329
+ const paths = /* @__PURE__ */ new Set([""]);
3330
+ function visit(folders, parent) {
3331
+ for (const [name2, children] of Object.entries(folders)) {
3332
+ if (!SEGMENT.test(name2) || ["__proto__", "constructor", "prototype"].includes(name2)) {
3333
+ throw new OrmDefinitionError(`invalid catalog folder name "${name2}"`, file);
3334
+ }
3335
+ const path = parent ? `${parent}/${name2}` : name2;
3336
+ paths.add(path);
3337
+ if (children !== null) {
3338
+ if (typeof children !== "object" || Array.isArray(children)) {
3339
+ throw new OrmDefinitionError(`catalog folder "${path}" must contain folders or null`, file);
3340
+ }
3341
+ visit(children, path);
3342
+ }
3343
+ }
3344
+ }
3345
+ visit(catalog.folders, "");
3346
+ for (const [object, path] of Object.entries(catalog.objects)) {
3347
+ if (!object || !paths.has(path)) {
3348
+ throw new OrmDefinitionError(`catalog object "${object}" references missing folder "${path}"`, file);
3349
+ }
3350
+ }
3351
+ }
3170
3352
 
3171
- export { COLUMN_BRAND, COLUMN_DEFAULT_FNS, ColumnBuilder, ENUM_BRAND, LINK_BRAND, NAMESPACE_CONFIG_BRAND, NAMESPACE_CONFIG_FILE, ORM_TO_PIPELINE_TYPE, SCHEMA_DIR, SYNCED_CONTRACT_HASH_DOMAIN, TABLE_BRAND, VIEW_BRAND, activeAuthoringContext, beginAuthoringContext, computeCanonicalHash, computeSyncedContractHash, custom, defineEnum, defineNamespace, defineResolvedTable, defineSyncedTable, defineTable, defineView, deriveColumnName, endAuthoringContext, etlSource, extractOrmSyncedTableContract, highest, isColumnBuilder, isEnumRef, isSeqLink, isSeqNamespaceConfig, isSeqTable, isSeqView, latest, loadNamespaceFromDir, lowest, managedFn, manualSource, nonNullFirst, overlayWinsUnion, overrideRowMutation, prefetchSource, priority, revertOverlayMutation, serializeNamespace, syncedContractSurface, t, toMany, toOne, toPlatformUser };
3353
+ export { COLUMN_BRAND, COLUMN_DEFAULT_FNS, ColumnBuilder, ENUM_BRAND, LINK_BRAND, NAMESPACE_CONFIG_BRAND, NAMESPACE_CONFIG_FILE, ORM_TO_PIPELINE_TYPE, SCHEMA_DIR, SYNCED_CONTRACT_HASH_DOMAIN, TABLE_BRAND, VIEW_BRAND, activeAuthoringContext, beginAuthoringContext, computeCanonicalHash, computeSyncedContractHash, custom, defineEnum, defineNamespace, defineOperation, defineResolvedTable, defineSyncedTable, defineTable, defineView, deriveColumnName, endAuthoringContext, etlSource, extractOrmSyncedTableContract, highest, isColumnBuilder, isEnumRef, isSeqLink, isSeqNamespaceConfig, isSeqTable, isSeqView, latest, loadNamespaceFromDir, lowest, managedFn, manualSource, nonNullFirst, objectRef, overlayWinsUnion, overrideRowMutation, prefetchSource, priority, revertOverlayMutation, serializeNamespace, syncedContractSurface, t, toMany, toOne, toPlatformUser, validateNamespaceCatalog };
3172
3354
  //# sourceMappingURL=authoring.js.map
3173
3355
  //# sourceMappingURL=authoring.js.map