@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
@@ -158,7 +158,8 @@ var NAMESPACE_PATTERN = /^[a-z][a-z0-9_]{0,40}$/;
158
158
  var IDENTIFIER_PATTERN = /^[a-z][a-z0-9_]*$/;
159
159
  var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
160
160
  var MAX_IDENTIFIER_LENGTH = 63;
161
- var SYNCED_SOURCE_PATTERN = /^gold\.[a-z][a-z0-9_]*$/;
161
+ var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]{0,62}\.[a-z][a-z0-9_]{0,62}$/;
162
+ var SYNCED_SOURCE_PATTERN = QUALIFIED_TABLE_PATTERN;
162
163
  var HASH_DOMAIN = "sequence-orm-definitions\0";
163
164
  var CELL_PARTS = ["at", "by", "rev", "source", "conflicts"];
164
165
  var CELL_PART_SUFFIX = {
@@ -235,6 +236,9 @@ function registerNamespaceConfig(value) {
235
236
  // src/authoring/table.ts
236
237
  var TABLE_BRAND = "seq-table";
237
238
  var LINK_BRAND = "seq-link";
239
+ function objectRef(namespace, table) {
240
+ return { kind: "seq-external-object", namespace, table };
241
+ }
238
242
  function toOne(target, options) {
239
243
  return {
240
244
  kind: LINK_BRAND,
@@ -288,51 +292,51 @@ function assertIdentifier({ value, what, file, pattern = IDENTIFIER_PATTERN }) {
288
292
  );
289
293
  }
290
294
  }
291
- function defineTable(name, columns, config = {}) {
295
+ function defineTable(name2, columns, config = {}) {
292
296
  const file = activeAuthoringContext()?.currentFile;
293
- assertIdentifier({ value: name, what: "table name", file });
297
+ assertIdentifier({ value: name2, what: "table name", file });
294
298
  const entries = Object.entries(columns);
295
299
  if (entries.length === 0) {
296
- throw new OrmDefinitionError(`table "${name}" declares no columns`, file);
300
+ throw new OrmDefinitionError(`table "${name2}" declares no columns`, file);
297
301
  }
298
302
  const seenColumnNames = /* @__PURE__ */ new Map();
299
303
  for (const [propertyName, builder] of entries) {
300
304
  if (!isColumnBuilder(builder)) {
301
305
  throw new OrmDefinitionError(
302
- `table "${name}": column "${propertyName}" is not a column builder \u2014 use the t.* factories (t.uuid(), t.text(), \u2026)`,
306
+ `table "${name2}": column "${propertyName}" is not a column builder \u2014 use the t.* factories (t.uuid(), t.text(), \u2026)`,
303
307
  file
304
308
  );
305
309
  }
306
310
  const columnName = builder.columnNameFor(propertyName);
307
- assertIdentifier({ value: columnName, what: `table "${name}": column "${propertyName}" column name`, file, pattern: COLUMN_IDENTIFIER_PATTERN });
311
+ assertIdentifier({ value: columnName, what: `table "${name2}": column "${propertyName}" column name`, file, pattern: COLUMN_IDENTIFIER_PATTERN });
308
312
  if (config.materializes === void 0 && (propertyName === CELLS_FIELD || Object.values(CELL_PART_SUFFIX).some((suffix) => columnName.endsWith(suffix) || propertyName.endsWith(suffix)))) {
309
313
  throw new OrmDefinitionError(
310
- `table "${name}": "${propertyName}" collides with the reserved cell-metadata names ('${CELLS_FIELD}', or a ${Object.values(CELL_PART_SUFFIX).join("/")} suffix)`,
314
+ `table "${name2}": "${propertyName}" collides with the reserved cell-metadata names ('${CELLS_FIELD}', or a ${Object.values(CELL_PART_SUFFIX).join("/")} suffix)`,
311
315
  file
312
316
  );
313
317
  }
314
318
  const existing = seenColumnNames.get(columnName);
315
319
  if (existing !== void 0) {
316
320
  throw new OrmDefinitionError(
317
- `table "${name}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
321
+ `table "${name2}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
318
322
  file
319
323
  );
320
324
  }
321
325
  seenColumnNames.set(columnName, propertyName);
322
326
  }
323
- const table = { kind: TABLE_BRAND, tableName: name, columns, config, source: void 0, file };
327
+ const table = { kind: TABLE_BRAND, tableName: name2, columns, config, source: void 0, file };
324
328
  registerTable(table);
325
329
  return table;
326
330
  }
327
- function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
331
+ function defineSyncedTable(name2, columnsOrConfig, maybeConfig) {
328
332
  const file = activeAuthoringContext()?.currentFile;
329
- assertIdentifier({ value: name, what: "table name", file });
333
+ assertIdentifier({ value: name2, what: "table name", file });
330
334
  const isTyped = maybeConfig !== void 0;
331
335
  const columns = isTyped ? columnsOrConfig : {};
332
336
  const config = isTyped ? maybeConfig : columnsOrConfig;
333
337
  if (!SYNCED_SOURCE_PATTERN.test(config.source)) {
334
338
  throw new OrmDefinitionError(
335
- `synced table "${name}": source "${config.source}" must match ${SYNCED_SOURCE_PATTERN} (gold layer only)`,
339
+ `synced table "${name2}": source "${config.source}" must be a qualified lowercase schema.table identifier`,
336
340
  file
337
341
  );
338
342
  }
@@ -342,21 +346,21 @@ function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
342
346
  for (const [propertyName, builder] of entries) {
343
347
  if (!isColumnBuilder(builder)) {
344
348
  throw new OrmDefinitionError(
345
- `synced table "${name}": column "${propertyName}" is not a column builder \u2014 use the t.* factories (t.uuid(), t.text(), \u2026)`,
349
+ `synced table "${name2}": column "${propertyName}" is not a column builder \u2014 use the t.* factories (t.uuid(), t.text(), \u2026)`,
346
350
  file
347
351
  );
348
352
  }
349
353
  const columnName = builder.columnNameFor(propertyName);
350
354
  assertIdentifier({
351
355
  value: columnName,
352
- what: `synced table "${name}": column "${propertyName}" column name`,
356
+ what: `synced table "${name2}": column "${propertyName}" column name`,
353
357
  file,
354
358
  pattern: COLUMN_IDENTIFIER_PATTERN
355
359
  });
356
360
  const existing = seenColumnNames.get(columnName);
357
361
  if (existing !== void 0) {
358
362
  throw new OrmDefinitionError(
359
- `synced table "${name}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
363
+ `synced table "${name2}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
360
364
  file
361
365
  );
362
366
  }
@@ -367,13 +371,13 @@ function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
367
371
  const hasConfigPk = config.primaryKey !== void 0 && config.primaryKey.length > 0;
368
372
  if (columnLevelPk.length === 0 && !hasConfigPk) {
369
373
  throw new OrmDefinitionError(
370
- `synced table "${name}" declares columns but no primary key \u2014 use .primaryKey() or config.primaryKey`,
374
+ `synced table "${name2}" declares columns but no primary key \u2014 use .primaryKey() or config.primaryKey`,
371
375
  file
372
376
  );
373
377
  }
374
378
  if (columnLevelPk.length > 0 && hasConfigPk) {
375
379
  throw new OrmDefinitionError(
376
- `synced table "${name}": declare the primary key either with .primaryKey() on columns or with config.primaryKey \u2014 not both`,
380
+ `synced table "${name2}": declare the primary key either with .primaryKey() on columns or with config.primaryKey \u2014 not both`,
377
381
  file
378
382
  );
379
383
  }
@@ -385,7 +389,7 @@ function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
385
389
  if (config.policies !== void 0) tableConfig.policies = { read: config.policies.read };
386
390
  const table = {
387
391
  kind: TABLE_BRAND,
388
- tableName: name,
392
+ tableName: name2,
389
393
  columns,
390
394
  config: tableConfig,
391
395
  source: config.source,
@@ -394,26 +398,26 @@ function defineSyncedTable(name, columnsOrConfig, maybeConfig) {
394
398
  registerTable(table);
395
399
  return table;
396
400
  }
397
- function defineEnum(name, values) {
401
+ function defineEnum(name2, values) {
398
402
  const file = activeAuthoringContext()?.currentFile;
399
- assertIdentifier({ value: name, what: "enum name", file });
403
+ assertIdentifier({ value: name2, what: "enum name", file });
400
404
  if (values.length === 0 || values.length > 100) {
401
- throw new OrmDefinitionError(`enum "${name}" must declare between 1 and 100 values`, file);
405
+ throw new OrmDefinitionError(`enum "${name2}" must declare between 1 and 100 values`, file);
402
406
  }
403
407
  const seen = /* @__PURE__ */ new Set();
404
408
  for (const value of values) {
405
409
  if (value.length === 0 || value.length > MAX_IDENTIFIER_LENGTH) {
406
410
  throw new OrmDefinitionError(
407
- `enum "${name}": value ${JSON.stringify(value)} must be 1-${MAX_IDENTIFIER_LENGTH} characters`,
411
+ `enum "${name2}": value ${JSON.stringify(value)} must be 1-${MAX_IDENTIFIER_LENGTH} characters`,
408
412
  file
409
413
  );
410
414
  }
411
415
  if (seen.has(value)) {
412
- throw new OrmDefinitionError(`enum "${name}" declares duplicate value "${value}"`, file);
416
+ throw new OrmDefinitionError(`enum "${name2}" declares duplicate value "${value}"`, file);
413
417
  }
414
418
  seen.add(value);
415
419
  }
416
- const ref = { kind: ENUM_BRAND, name, values: [...values] };
420
+ const ref = { kind: ENUM_BRAND, name: name2, values: [...values] };
417
421
  registerEnum(ref);
418
422
  return ref;
419
423
  }
@@ -421,52 +425,52 @@ var VIEW_BRAND = "seq-view";
421
425
  function isSeqView(value) {
422
426
  return typeof value === "object" && value !== null && value.kind === VIEW_BRAND;
423
427
  }
424
- function defineView(name, columns, config) {
428
+ function defineView(name2, columns, config) {
425
429
  const file = activeAuthoringContext()?.currentFile;
426
- if (!IDENTIFIER_PATTERN.test(name) || name.length > MAX_IDENTIFIER_LENGTH) {
430
+ if (!IDENTIFIER_PATTERN.test(name2) || name2.length > MAX_IDENTIFIER_LENGTH) {
427
431
  throw new OrmDefinitionError(
428
- `view name "${name}" must match ${IDENTIFIER_PATTERN} and be at most ${MAX_IDENTIFIER_LENGTH} characters`,
432
+ `view name "${name2}" must match ${IDENTIFIER_PATTERN} and be at most ${MAX_IDENTIFIER_LENGTH} characters`,
429
433
  file
430
434
  );
431
435
  }
432
436
  const entries = Object.entries(columns);
433
- if (entries.length === 0) throw new OrmDefinitionError(`view "${name}" declares no columns`, file);
437
+ if (entries.length === 0) throw new OrmDefinitionError(`view "${name2}" declares no columns`, file);
434
438
  const seenColumnNames = /* @__PURE__ */ new Map();
435
439
  for (const [propertyName, builder] of entries) {
436
440
  if (!isColumnBuilder(builder)) {
437
441
  throw new OrmDefinitionError(
438
- `view "${name}": column "${propertyName}" is not a column builder \u2014 use the t.* factories`,
442
+ `view "${name2}": column "${propertyName}" is not a column builder \u2014 use the t.* factories`,
439
443
  file
440
444
  );
441
445
  }
442
446
  const columnName = builder.columnNameFor(propertyName);
443
447
  if (!COLUMN_IDENTIFIER_PATTERN.test(columnName) || columnName.length > MAX_IDENTIFIER_LENGTH) {
444
- throw new OrmDefinitionError(`view "${name}": column "${propertyName}" has invalid column name "${columnName}"`, file);
448
+ throw new OrmDefinitionError(`view "${name2}": column "${propertyName}" has invalid column name "${columnName}"`, file);
445
449
  }
446
450
  const existing = seenColumnNames.get(columnName);
447
451
  if (existing !== void 0) {
448
452
  throw new OrmDefinitionError(
449
- `view "${name}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
453
+ `view "${name2}": columns "${existing}" and "${propertyName}" both map to Postgres column "${columnName}"`,
450
454
  file
451
455
  );
452
456
  }
453
457
  seenColumnNames.set(columnName, propertyName);
454
458
  }
455
- const view = { kind: VIEW_BRAND, viewName: name, columns, config, file };
459
+ const view = { kind: VIEW_BRAND, viewName: name2, columns, config, file };
456
460
  registerView(view);
457
461
  return view;
458
462
  }
459
463
  var QUERY_COLUMN_FACTORIES = {
460
- uuid: (name) => pgCore.uuid(name),
461
- text: (name) => pgCore.text(name),
462
- integer: (name) => pgCore.integer(name),
463
- bigint: (name) => pgCore.bigint(name, { mode: "bigint" }),
464
- numeric: (name) => pgCore.numeric(name),
465
- double: (name) => pgCore.doublePrecision(name),
466
- boolean: (name) => pgCore.boolean(name),
467
- timestamptz: (name) => pgCore.timestamp(name, { withTimezone: true }),
468
- date: (name) => pgCore.date(name),
469
- jsonb: (name) => pgCore.jsonb(name)
464
+ uuid: (name2) => pgCore.uuid(name2),
465
+ text: (name2) => pgCore.text(name2),
466
+ integer: (name2) => pgCore.integer(name2),
467
+ bigint: (name2) => pgCore.bigint(name2, { mode: "bigint" }),
468
+ numeric: (name2) => pgCore.numeric(name2),
469
+ double: (name2) => pgCore.doublePrecision(name2),
470
+ boolean: (name2) => pgCore.boolean(name2),
471
+ timestamptz: (name2) => pgCore.timestamp(name2, { withTimezone: true }),
472
+ date: (name2) => pgCore.date(name2),
473
+ jsonb: (name2) => pgCore.jsonb(name2)
470
474
  };
471
475
  function compileViewQueries(input) {
472
476
  const schemaName = `orm_${input.namespace}`;
@@ -522,9 +526,9 @@ function compileViewQueries(input) {
522
526
  }
523
527
  const handle = handles.get(resolved);
524
528
  if (!handle) {
525
- const name = isSeqTable(resolved) ? resolved.tableName : resolved.viewName;
529
+ const name2 = isSeqTable(resolved) ? resolved.tableName : resolved.viewName;
526
530
  throw new OrmDefinitionError(
527
- `view "${authored.viewName}" references relation "${name}" which is not part of this namespace`,
531
+ `view "${authored.viewName}" references relation "${name2}" which is not part of this namespace`,
528
532
  authored.file
529
533
  );
530
534
  }
@@ -715,8 +719,8 @@ function overlayWinsUnion(input) {
715
719
  // src/authoring/cow-mutations.ts
716
720
  function overrideRowMutation(input) {
717
721
  const { overlay, synced, pk } = input;
718
- const name = input.name ?? `override_${overlay.tableName}`;
719
- assertMutationName(name, "overrideRowMutation");
722
+ const name2 = input.name ?? `override_${overlay.tableName}`;
723
+ assertMutationName(name2, "overrideRowMutation");
720
724
  assertOverlayPairPrimaryKey({ overlay, synced, pk, helper: "overrideRowMutation" });
721
725
  const sharedProperties = sharedNonPkProperties({ overlay, synced, pk });
722
726
  const setProperties = resolveSetProperties({
@@ -735,7 +739,7 @@ function overrideRowMutation(input) {
735
739
  });
736
740
  return {
737
741
  kind: "override",
738
- name,
742
+ name: name2,
739
743
  synced: synced.tableName,
740
744
  pk,
741
745
  copy: [pk, ...sharedProperties],
@@ -744,23 +748,23 @@ function overrideRowMutation(input) {
744
748
  }
745
749
  function revertOverlayMutation(input) {
746
750
  const { overlay, synced, pk } = input;
747
- const name = input.name ?? `revert_${overlay.tableName}`;
748
- assertMutationName(name, "revertOverlayMutation");
751
+ const name2 = input.name ?? `revert_${overlay.tableName}`;
752
+ assertMutationName(name2, "revertOverlayMutation");
749
753
  assertOverlayPairPrimaryKey({ overlay, synced, pk, helper: "revertOverlayMutation" });
750
754
  assertMatchingColumns({ overlay, synced, properties: [pk], helper: "revertOverlayMutation" });
751
755
  return {
752
756
  kind: "revert",
753
- name,
757
+ name: name2,
754
758
  synced: synced.tableName,
755
759
  pk,
756
760
  copy: [],
757
761
  set: []
758
762
  };
759
763
  }
760
- function assertMutationName(name, helper) {
761
- if (!IDENTIFIER_PATTERN.test(name) || name.length > MAX_IDENTIFIER_LENGTH) {
764
+ function assertMutationName(name2, helper) {
765
+ if (!IDENTIFIER_PATTERN.test(name2) || name2.length > MAX_IDENTIFIER_LENGTH) {
762
766
  throw new OrmDefinitionError(
763
- `${helper}: mutation name "${name}" must match ${IDENTIFIER_PATTERN} and be at most ${MAX_IDENTIFIER_LENGTH} characters`
767
+ `${helper}: mutation name "${name2}" must match ${IDENTIFIER_PATTERN} and be at most ${MAX_IDENTIFIER_LENGTH} characters`
764
768
  );
765
769
  }
766
770
  }
@@ -886,7 +890,7 @@ function scalarColumnType(table, property, helper) {
886
890
  }
887
891
  return type;
888
892
  }
889
- var quoteIdentifier = (name) => `"${name.replaceAll('"', '""')}"`;
893
+ var quoteIdentifier = (name2) => `"${name2.replaceAll('"', '""')}"`;
890
894
  var quoteLiteral = (value) => `'${value.replaceAll("'", "''")}'`;
891
895
 
892
896
  // src/checks/compile.ts
@@ -1220,8 +1224,8 @@ function validateResolvedView({ view, tables, views = [] }) {
1220
1224
  validateRules(scope);
1221
1225
  validateViewCells(scope);
1222
1226
  if (resolved.materialized) validateMaterialized({ scope, tableByName, views });
1223
- for (const name of resolved.deletedBy) {
1224
- if (scope.sourceByName.get(name)?.kind !== "manual") fail(`deletedBy '${name}' must be the manual source`);
1227
+ for (const name2 of resolved.deletedBy) {
1228
+ if (scope.sourceByName.get(name2)?.kind !== "manual") fail(`deletedBy '${name2}' must be the manual source`);
1225
1229
  }
1226
1230
  return errors;
1227
1231
  }
@@ -1318,7 +1322,7 @@ function validateViewCells({ view, dataColumns, pkColumn, fail }) {
1318
1322
  if (column.type !== VIEW_CELL_TYPES[column.cell.part]) fail(`cell column '${column.name}' must be ${VIEW_CELL_TYPES[column.cell.part]}`);
1319
1323
  expected.delete(column.name);
1320
1324
  }
1321
- for (const name of expected.keys()) fail(`missing cell column '${name}'`);
1325
+ for (const name2 of expected.keys()) fail(`missing cell column '${name2}'`);
1322
1326
  }
1323
1327
  var columnSignature = (columns) => JSON.stringify(columns.map((column) => [column.name, column.columnName, column.type]));
1324
1328
  function validateMaterialized({ scope, tableByName, views }) {
@@ -1409,6 +1413,8 @@ var canonicalLinkSchema = zod.z.object({
1409
1413
  kind: zod.z.enum(["toOne", "toMany", "toPlatformUser"]),
1410
1414
  /** Target table name (namespace-local). Absent for toPlatformUser. */
1411
1415
  target: identifier2.optional(),
1416
+ /** Owning namespace of an imported target; omitted for local links. */
1417
+ namespace: identifier2.max(41).optional(),
1412
1418
  /** FK column on the owning side (toOne/toPlatformUser: on this table; toMany: on the target). */
1413
1419
  on: columnIdentifier,
1414
1420
  /** Referenced column on the target (default: target's PK / platform user `sub`). */
@@ -1667,11 +1673,12 @@ var canonicalForeignKeySchema = zod.z.object({
1667
1673
  }).strict();
1668
1674
  var canonicalTableSchema = zod.z.object({
1669
1675
  table: identifier2,
1676
+ displayName: zod.z.string().trim().min(1).max(120).optional(),
1670
1677
  description: zod.z.string().max(2e3).optional(),
1671
1678
  /** Table-level rename hint (new in v2). */
1672
1679
  renamedFrom: identifier2.optional(),
1673
- /** Synced/reflected read-only tables (`source: gold.*`) — v1 semantics. */
1674
- source: zod.z.string().regex(/^gold\.[a-z][a-z0-9_]*$/).optional(),
1680
+ /** Synced/reflected read-only tables (`source: schema.table`) — v1 semantics. */
1681
+ source: zod.z.string().regex(QUALIFIED_TABLE_PATTERN).optional(),
1675
1682
  columns: zod.z.array(canonicalColumnSchema).default([]),
1676
1683
  primaryKey: zod.z.array(columnIdentifier).min(1).max(8).optional(),
1677
1684
  links: zod.z.array(canonicalLinkSchema).default([]),
@@ -1824,7 +1831,9 @@ function validateCellMetadata(table, index, ctx) {
1824
1831
  }
1825
1832
  }
1826
1833
  }
1827
- var canonicalDefinitionSchema = zod.z.object({
1834
+ var exportGateSchema = zod.z.union([zod.z.literal(true), zod.z.object({ capability: zod.z.string().min(1) }).strict(), zod.z.object({ role: zod.z.string().min(1) }).strict()]);
1835
+ var relationExportSchema = zod.z.object({ read: exportGateSchema, write: exportGateSchema.optional() }).strict();
1836
+ var localDefinitionSchema = zod.z.object({
1828
1837
  definition_format: zod.z.literal(CANONICAL_FORMAT),
1829
1838
  namespace: identifier2.max(41),
1830
1839
  description: zod.z.string().max(2e3).optional(),
@@ -1832,6 +1841,8 @@ var canonicalDefinitionSchema = zod.z.object({
1832
1841
  tables: zod.z.array(canonicalTableSchema).min(0).max(500),
1833
1842
  /** Optional to preserve byte-identical hashes for pre-view canonical v2 documents. */
1834
1843
  views: zod.z.array(canonicalViewSchema).max(500).optional(),
1844
+ /** Target-owned cross-namespace access contract, using existing namespace grants. */
1845
+ exports: zod.z.record(identifier2, relationExportSchema).optional(),
1835
1846
  /** Verbatim custom-roles grammar (@sequenceholdings/custom-roles-manifest). */
1836
1847
  capabilities: zod.z.object({
1837
1848
  roles: zod.z.unknown().optional(),
@@ -1858,7 +1869,16 @@ var canonicalDefinitionSchema = zod.z.object({
1858
1869
  }
1859
1870
  }
1860
1871
  const fkTables = new Map(definition.tables.map((table) => [table.table, table]));
1872
+ const relations = /* @__PURE__ */ new Set([...fkTables.keys(), ...(definition.views ?? []).map((view) => view.view)]);
1873
+ for (const name2 of Object.keys(definition.exports ?? {})) {
1874
+ if (!relations.has(name2)) ctx.addIssue({ code: "custom", path: ["exports", name2], message: `export '${name2}' is not a local object` });
1875
+ }
1861
1876
  for (const [tableIndex, table] of definition.tables.entries()) {
1877
+ for (const [linkIndex, link] of table.links.entries()) {
1878
+ if (link.enforce && link.namespace !== void 0 && link.namespace !== definition.namespace) {
1879
+ ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "links", linkIndex], message: "physical foreign keys require a local target; use an ORM link without enforce" });
1880
+ }
1881
+ }
1862
1882
  const names = /* @__PURE__ */ new Set([
1863
1883
  ...table.primaryKey?.length ? [`${table.table}_pkey`] : [],
1864
1884
  ...table.columns.filter((column) => column.unique && !(table.primaryKey?.length === 1 && table.primaryKey[0] === column.columnName)).map((column) => `${table.table}_${column.columnName}_unique`),
@@ -1895,14 +1915,14 @@ var canonicalDefinitionSchema = zod.z.object({
1895
1915
  }
1896
1916
  if (fk.columns.length !== fk.references.length) fail("foreign key columns and references must have the same number of columns");
1897
1917
  if (new Set(fk.columns).size !== fk.columns.length || new Set(fk.references).size !== fk.references.length) fail("foreign key columns must not repeat");
1898
- for (const [position, name] of fk.columns.entries()) {
1899
- const sourceColumn = table.columns.find((column) => column.columnName === name);
1918
+ for (const [position, name2] of fk.columns.entries()) {
1919
+ const sourceColumn = table.columns.find((column) => column.columnName === name2);
1900
1920
  const targetColumn = target.columns.find((column) => column.columnName === fk.references[position]);
1901
1921
  if (!sourceColumn || !targetColumn) fail("foreign key references an unknown column");
1902
1922
  else if (JSON.stringify(sourceColumn.type) !== JSON.stringify(targetColumn.type)) fail("foreign key column types must match");
1903
1923
  }
1904
1924
  if (fk.onDelete === "set null" || fk.onUpdate === "set null") {
1905
- 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");
1925
+ 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");
1906
1926
  }
1907
1927
  const uniqueKeys = [target.primaryKey ?? [], ...target.indexes.filter((index2) => index2.unique).map((index2) => index2.columns), ...target.columns.filter((column) => column.unique).map((column) => [column.columnName])];
1908
1928
  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");
@@ -2007,27 +2027,48 @@ var canonicalDefinitionSchema = zod.z.object({
2007
2027
  const visiting = /* @__PURE__ */ new Set();
2008
2028
  const visited = /* @__PURE__ */ new Set();
2009
2029
  let cycleReported = false;
2010
- const visit = (name, path) => {
2011
- if (visited.has(name) || cycleReported) return;
2012
- if (visiting.has(name)) {
2030
+ const visit = (name2, path) => {
2031
+ if (visited.has(name2) || cycleReported) return;
2032
+ if (visiting.has(name2)) {
2013
2033
  cycleReported = true;
2014
2034
  ctx.addIssue({
2015
2035
  code: "custom",
2016
2036
  path: ["views"],
2017
- message: `view dependencies form a cycle: ${[...path, name].join(" -> ")}`
2037
+ message: `view dependencies form a cycle: ${[...path, name2].join(" -> ")}`
2018
2038
  });
2019
2039
  return;
2020
2040
  }
2021
- visiting.add(name);
2022
- const view = viewByName.get(name);
2041
+ visiting.add(name2);
2042
+ const view = viewByName.get(name2);
2023
2043
  for (const dependency of view?.dependencies ?? []) {
2024
- if (viewByName.has(dependency)) visit(dependency, [...path, name]);
2044
+ if (viewByName.has(dependency)) visit(dependency, [...path, name2]);
2025
2045
  }
2026
- visiting.delete(name);
2027
- visited.add(name);
2046
+ visiting.delete(name2);
2047
+ visited.add(name2);
2028
2048
  };
2029
2049
  for (const view of views) visit(view.view, []);
2030
2050
  });
2051
+ var canonicalDefinitionSchema = localDefinitionSchema.safeExtend({
2052
+ /** Flattened, pinned snapshots. Imported relations never participate in this namespace's DDL. */
2053
+ imports: zod.z.array(localDefinitionSchema).max(32).optional()
2054
+ });
2055
+ function canonicalRelations(definition) {
2056
+ const views = (definition.views ?? []).map((view) => ({
2057
+ table: view.view,
2058
+ ...view.description !== void 0 ? { description: view.description } : {},
2059
+ columns: view.columns,
2060
+ ...view.primaryKey !== void 0 ? { primaryKey: view.primaryKey } : {},
2061
+ links: view.links,
2062
+ indexes: [],
2063
+ // A resolved table's write policies (evaluated against the resolved row)
2064
+ // ride alongside the view's read policy.
2065
+ policies: { read: view.policies.read, ...view.resolved?.policies ?? {} },
2066
+ actions: [],
2067
+ ...view.resolved !== void 0 ? { resolved: view.resolved } : {},
2068
+ ...view.internal ? { internal: true } : {}
2069
+ }));
2070
+ return [...definition.tables, ...views].sort((a, b) => a.table.localeCompare(b.table));
2071
+ }
2031
2072
  function normalizeCanonical(definition) {
2032
2073
  const sortByName = (items) => [...items].sort((a, b) => a.name.localeCompare(b.name));
2033
2074
  const normalized = {
@@ -2054,6 +2095,11 @@ function normalizeCanonical(definition) {
2054
2095
  dependencies: [...view.dependencies].sort()
2055
2096
  }));
2056
2097
  }
2098
+ if (definition.imports?.length) {
2099
+ normalized.imports = definition.imports.map(normalizeCanonical).sort((a, b) => a.namespace.localeCompare(b.namespace));
2100
+ } else {
2101
+ delete normalized.imports;
2102
+ }
2057
2103
  return normalized;
2058
2104
  }
2059
2105
 
@@ -2178,8 +2224,8 @@ var literal = (value) => `'${value.replace(/'/g, "''")}'`;
2178
2224
  var physical = ({ source, property }) => source.table.columns[property].columnNameFor(property);
2179
2225
  function candidateSql({ source, column, pk, pgType }) {
2180
2226
  const a = ident(`s_${source.name}`);
2181
- const name = physical({ source, property: column });
2182
- const value = `CAST(${a}.${ident(name)} AS ${pgType})`;
2227
+ const name2 = physical({ source, property: column });
2228
+ const value = `CAST(${a}.${ident(name2)} AS ${pgType})`;
2183
2229
  if (source.kind === "etl") {
2184
2230
  return {
2185
2231
  source: source.name,
@@ -2189,9 +2235,9 @@ function candidateSql({ source, column, pk, pgType }) {
2189
2235
  by: "CAST(NULL AS text)"
2190
2236
  };
2191
2237
  }
2192
- const at = `${a}.${ident(cellColumnName(name, "at"))}`;
2238
+ const at = `${a}.${ident(cellColumnName(name2, "at"))}`;
2193
2239
  const tombstoned2 = source.kind === "prefetch" ? ` AND NOT ${a}.${ident(RESOLVED_SOURCE_SYSTEM_COLUMNS.tombstoned.columnName)}` : "";
2194
- return { source: source.name, value, has: `(${at} IS NOT NULL${tombstoned2})`, at, by: `${a}.${ident(cellColumnName(name, "by"))}` };
2240
+ return { source: source.name, value, has: `(${at} IS NOT NULL${tombstoned2})`, at, by: `${a}.${ident(cellColumnName(name2, "by"))}` };
2195
2241
  }
2196
2242
  function outranks({ i, j, key, direction, inclusive }) {
2197
2243
  const ki = i[key];
@@ -2284,7 +2330,7 @@ function buildResolvedQuery({ context, plan }) {
2284
2330
  plan.rules.forEach((rule, index) => {
2285
2331
  const canonical = plan.columns[rule.column].toCanonicalColumn(rule.column);
2286
2332
  const pgType = typeof canonical.type === "string" ? PG_TYPE[canonical.type] : `${schema === void 0 ? "" : `${ident(schema)}.`}${ident(canonical.type.enum)}`;
2287
- const candidates = rule.candidates.map((name) => candidateSql({ source: sourceByName.get(name), column: rule.column, pk: plan.pk, pgType }));
2333
+ const candidates = rule.candidates.map((name2) => candidateSql({ source: sourceByName.get(name2), column: rule.column, pk: plan.pk, pgType }));
2288
2334
  cells.set(rule.column, rule.comparator === "custom" ? customCell({ candidates, orderBy: rule.orderBy, index }) : builtinCell({ comparator: rule.comparator, candidates }));
2289
2335
  });
2290
2336
  const laterals = [...cells.values()].flatMap((cell) => cell.lateral ? [drizzleOrm.sql.raw(cell.lateral)] : []);
@@ -2305,7 +2351,7 @@ function buildResolvedQuery({ context, plan }) {
2305
2351
  const system = RESOLVED_VIEW_SYSTEM_COLUMNS;
2306
2352
  selected[system.writebackStatus.name] = drizzleOrm.sql.raw(`${alias(manual.name)}.${ident(RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackStatus.columnName)}`).as(system.writebackStatus.columnName);
2307
2353
  selected[system.writebackError.name] = drizzleOrm.sql.raw(`${alias(manual.name)}.${ident(RESOLVED_SOURCE_SYSTEM_COLUMNS.writebackError.columnName)}`).as(system.writebackError.columnName);
2308
- const hidden = plan.deletedBy.map((name) => `NOT COALESCE(${alias(name)}.${ident(tombstoned2)}, false)`);
2354
+ const hidden = plan.deletedBy.map((name2) => `NOT COALESCE(${alias(name2)}.${ident(tombstoned2)}, false)`);
2309
2355
  const query = qb.select(selected).from(from);
2310
2356
  return hidden.length > 0 ? query.where(drizzleOrm.sql.raw(hidden.join(" AND "))) : query;
2311
2357
  }
@@ -2331,12 +2377,12 @@ var SYSTEM_VIEW_COLUMNS = {
2331
2377
  [RESOLVED_VIEW_SYSTEM_COLUMNS.writebackStatus.name]: () => t.text(RESOLVED_VIEW_SYSTEM_COLUMNS.writebackStatus.columnName),
2332
2378
  [RESOLVED_VIEW_SYSTEM_COLUMNS.writebackError.name]: () => t.text(RESOLVED_VIEW_SYSTEM_COLUMNS.writebackError.columnName)
2333
2379
  };
2334
- function defineResolvedTable(name, columns, config) {
2380
+ function defineResolvedTable(name2, columns, config) {
2335
2381
  const file = activeAuthoringContext()?.currentFile;
2336
2382
  const fail = (message) => {
2337
- throw new OrmDefinitionError(`resolved table "${name}": ${message}`, file);
2383
+ throw new OrmDefinitionError(`resolved table "${name2}": ${message}`, file);
2338
2384
  };
2339
- if (!IDENTIFIER_PATTERN.test(name)) fail(`name must match ${IDENTIFIER_PATTERN}`);
2385
+ if (!IDENTIFIER_PATTERN.test(name2)) fail(`name must match ${IDENTIFIER_PATTERN}`);
2340
2386
  for (const property of Object.keys(columns)) {
2341
2387
  if (RESERVED_RESOLVED_COLUMN_NAMES.has(property) || RESERVED_RESOLVED_COLUMN_NAMES.has(columns[property].columnNameFor(property))) {
2342
2388
  fail(`column "${property}" collides with a reserved system column`);
@@ -2355,7 +2401,7 @@ function defineResolvedTable(name, columns, config) {
2355
2401
  const sourceEntries = Object.entries(config.sources);
2356
2402
  if (sourceEntries.filter(([, source]) => source.kind === "manual").length !== 1) fail("declare exactly one manualSource()");
2357
2403
  const plans = sourceEntries.map(
2358
- ([sourceName, input]) => planSource({ viewName: name, sourceName, input, columns, pk, pkBuilder, dataColumns, fail })
2404
+ ([sourceName, input]) => planSource({ viewName: name2, sourceName, input, columns, pk, pkBuilder, dataColumns, fail })
2359
2405
  );
2360
2406
  const rules = planRules({ config, plans, dataColumns, fail });
2361
2407
  const deletedBy = [...config.rows?.deletedBy ?? []];
@@ -2389,23 +2435,23 @@ function defineResolvedTable(name, columns, config) {
2389
2435
  policies: { read }
2390
2436
  };
2391
2437
  if (!config.materialize) {
2392
- return defineView(name, typedColumns, { ...surface, query: (context) => buildResolvedQuery({ context, plan: queryPlan }), resolved });
2438
+ return defineView(name2, typedColumns, { ...surface, query: (context) => buildResolvedQuery({ context, plan: queryPlan }), resolved });
2393
2439
  }
2394
2440
  const materialize = config.materialize === true ? {} : config.materialize;
2395
- const computeName = `${name}__compute`;
2396
- const tableName = `${name}__materialized`;
2441
+ const computeName = `${name2}__compute`;
2442
+ const tableName = `${name2}__materialized`;
2397
2443
  const compute = defineView(computeName, typedColumns, {
2398
2444
  policies: { read },
2399
2445
  query: (context) => buildResolvedQuery({ context, plan: queryPlan }),
2400
2446
  internal: true
2401
2447
  });
2402
2448
  const table = defineTable(tableName, viewColumns, {
2403
- description: `Materialized rows of resolved table "${name}". System-maintained from "${computeName}".`,
2449
+ description: `Materialized rows of resolved table "${name2}". System-maintained from "${computeName}".`,
2404
2450
  internal: true,
2405
- materializes: name,
2451
+ materializes: name2,
2406
2452
  ...materialize.indexes !== void 0 ? { indexes: materialize.indexes } : {}
2407
2453
  });
2408
- return defineView(name, typedColumns, {
2454
+ return defineView(name2, typedColumns, {
2409
2455
  ...surface,
2410
2456
  query: (context) => {
2411
2457
  const handle = context.relation(table);
@@ -2432,7 +2478,7 @@ function planRules({ config, plans, dataColumns, fail }) {
2432
2478
  return dataColumns.map((column) => {
2433
2479
  const explicit = config.resolve?.[column];
2434
2480
  const comparator = explicit ?? fallback;
2435
- const candidates = explicit ? [...explicit.candidates] : comparator.candidates.filter((name) => plans.find((plan) => plan.name === name)?.columns.includes(column));
2481
+ const candidates = explicit ? [...explicit.candidates] : comparator.candidates.filter((name2) => plans.find((plan) => plan.name === name2)?.columns.includes(column));
2436
2482
  for (const candidate of candidates) {
2437
2483
  const plan = plans.find((entry) => entry.name === candidate);
2438
2484
  if (!plan) fail(`column "${column}" names unknown source "${candidate}"`);
@@ -2511,6 +2557,72 @@ function stableJson(value) {
2511
2557
  return `{${entries.join(",")}}`;
2512
2558
  }
2513
2559
 
2560
+ // src/canonical/composition.ts
2561
+ function localContract(definition) {
2562
+ return normalizeCanonical(localDefinitionSchema.parse(definition));
2563
+ }
2564
+ function flattenNamespaceImports(inputs) {
2565
+ const definitions = /* @__PURE__ */ new Map();
2566
+ const visit = (value, depth) => {
2567
+ if (depth > 16 || definitions.size > 32) throw new OrmDefinitionError("namespace import graph is too large");
2568
+ const definition = localContract(localDefinitionSchema.parse(value));
2569
+ const previous = definitions.get(definition.namespace);
2570
+ if (previous) {
2571
+ if (stableJson(previous) !== stableJson(definition)) throw new OrmDefinitionError(`conflicting contracts for namespace '${definition.namespace}'`);
2572
+ }
2573
+ definitions.set(definition.namespace, definition);
2574
+ if (typeof value === "object" && value !== null && "imports" in value && Array.isArray(value.imports)) {
2575
+ for (const dependency of value.imports) visit(dependency, depth + 1);
2576
+ }
2577
+ };
2578
+ for (const input of inputs) visit(input, 0);
2579
+ return [...definitions.values()].sort((a, b) => a.namespace.localeCompare(b.namespace));
2580
+ }
2581
+ function composeNamespace(definition) {
2582
+ const all = [localContract(definition), ...definition.imports ?? []];
2583
+ const owners = /* @__PURE__ */ new Map();
2584
+ const namespaces = /* @__PURE__ */ new Set();
2585
+ const enums = /* @__PURE__ */ new Map();
2586
+ for (const owner of all) {
2587
+ if (namespaces.has(owner.namespace)) throw new OrmDefinitionError(`duplicate imported namespace '${owner.namespace}'`);
2588
+ namespaces.add(owner.namespace);
2589
+ for (const value of owner.enums) {
2590
+ const previous = enums.get(value.name);
2591
+ if (previous && stableJson(previous) !== stableJson(value)) {
2592
+ throw new OrmDefinitionError(`conflicting imported enum '${value.name}'`);
2593
+ }
2594
+ enums.set(value.name, value);
2595
+ }
2596
+ for (const relation of canonicalRelations(owner)) {
2597
+ if (owners.has(relation.table)) throw new OrmDefinitionError(`ambiguous imported relation '${relation.table}'`);
2598
+ owners.set(relation.table, owner);
2599
+ }
2600
+ }
2601
+ for (const owner of definition.imports?.length ? all : []) {
2602
+ for (const relation of canonicalRelations(owner)) {
2603
+ for (const link of relation.links) {
2604
+ if (link.kind === "toPlatformUser") continue;
2605
+ const target = owners.get(link.target ?? "");
2606
+ if (!target || target.namespace !== (link.namespace ?? owner.namespace)) {
2607
+ throw new OrmDefinitionError(`link '${owner.namespace}/${relation.table}.${link.name}' has no matching imported target`);
2608
+ }
2609
+ if (target.namespace !== owner.namespace && !target.exports?.[link.target ?? ""]) {
2610
+ throw new OrmDefinitionError(`target '${target.namespace}/${link.target}' is not exported`);
2611
+ }
2612
+ }
2613
+ }
2614
+ }
2615
+ return {
2616
+ owners,
2617
+ definition: {
2618
+ ...localContract(definition),
2619
+ tables: all.flatMap((item) => item.tables),
2620
+ views: all.flatMap((item) => item.views ?? []),
2621
+ enums: [...enums.values()]
2622
+ }
2623
+ };
2624
+ }
2625
+
2514
2626
  // src/authoring/namespace.ts
2515
2627
  var NAMESPACE_CONFIG_BRAND = "seq-namespace-config";
2516
2628
  function isSeqNamespaceConfig(value) {
@@ -2526,6 +2638,8 @@ function defineNamespace(input) {
2526
2638
  namespace: input.namespace,
2527
2639
  description: input.description,
2528
2640
  capabilities: input.capabilities ?? {},
2641
+ imports: input.imports,
2642
+ exports: input.exports,
2529
2643
  file
2530
2644
  };
2531
2645
  registerNamespaceConfig(config);
@@ -2570,15 +2684,15 @@ function serializeNamespace(input) {
2570
2684
  }
2571
2685
  const relationsByName = /* @__PURE__ */ new Map();
2572
2686
  for (const relation of [...tables, ...views]) {
2573
- const name = relationName(relation);
2574
- const existing = relationsByName.get(name);
2687
+ const name2 = relationName(relation);
2688
+ const existing = relationsByName.get(name2);
2575
2689
  if (existing) {
2576
2690
  throw new OrmDefinitionError(
2577
- `duplicate relation "${name}" (defined as ${relationKind(existing)} in ${describeFile(existing)} and ${relationKind(relation)} in ${describeFile(relation)})`,
2691
+ `duplicate relation "${name2}" (defined as ${relationKind(existing)} in ${describeFile(existing)} and ${relationKind(relation)} in ${describeFile(relation)})`,
2578
2692
  relation.file
2579
2693
  );
2580
2694
  }
2581
- relationsByName.set(name, relation);
2695
+ relationsByName.set(name2, relation);
2582
2696
  }
2583
2697
  const enumsByName = /* @__PURE__ */ new Map();
2584
2698
  for (const ref of enums) {
@@ -2619,6 +2733,8 @@ function serializeNamespace(input) {
2619
2733
  };
2620
2734
  if (config.description !== void 0) candidate.description = config.description;
2621
2735
  if (canonicalViews.length > 0) candidate.views = canonicalViews;
2736
+ if (config.imports?.length) candidate.imports = flattenNamespaceImports(config.imports);
2737
+ if (config.exports) candidate.exports = config.exports;
2622
2738
  let parsed;
2623
2739
  try {
2624
2740
  parsed = canonicalDefinitionSchema.parse(candidate);
@@ -2628,7 +2744,9 @@ function serializeNamespace(input) {
2628
2744
  }
2629
2745
  throw error;
2630
2746
  }
2631
- return normalizeCanonical(parsed);
2747
+ const normalized = normalizeCanonical(parsed);
2748
+ if (normalized.imports?.length) composeNamespace(normalized);
2749
+ return normalized;
2632
2750
  }
2633
2751
  function computeCanonicalHash(definition) {
2634
2752
  return crypto.createHash("sha256").update(HASH_DOMAIN).update(stableJson(definition)).digest("hex");
@@ -2642,6 +2760,7 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
2642
2760
  policies: {},
2643
2761
  actions: []
2644
2762
  };
2763
+ if (table.config.displayName !== void 0) candidate.displayName = table.config.displayName;
2645
2764
  if (table.config.description !== void 0) candidate.description = table.config.description;
2646
2765
  if (table.config.renamedFrom !== void 0) candidate.renamedFrom = table.config.renamedFrom;
2647
2766
  if (table.source !== void 0) candidate.source = table.source;
@@ -2725,6 +2844,9 @@ function buildCanonicalTable({ table, relationsByName, enumsByName }) {
2725
2844
  if (table.config.foreignKeys?.length) {
2726
2845
  if (table.source) throw new OrmDefinitionError(`synced table "${table.tableName}" cannot enforce foreign keys`, table.file);
2727
2846
  candidate.foreignKeys = table.config.foreignKeys.map((fk) => {
2847
+ if (typeof fk.target === "object" && fk.target.kind === "seq-external-object") {
2848
+ throw new OrmDefinitionError("physical foreign keys require a local target; use a cross-namespace ORM link", table.file);
2849
+ }
2728
2850
  const target = resolveRelationRef({ ref: fk.target, fail: (message) => {
2729
2851
  throw new OrmDefinitionError(message, table.file);
2730
2852
  } });
@@ -2895,6 +3017,19 @@ function buildCanonicalLink({
2895
3017
  ...link.description !== void 0 ? { description: link.description } : {}
2896
3018
  };
2897
3019
  }
3020
+ if (typeof link.target === "object" && link.target?.kind === "seq-external-object") {
3021
+ if (link.enforce) return fail("cannot enforce a cross-namespace physical foreign key; use an ORM link without enforce");
3022
+ return {
3023
+ name: linkName,
3024
+ kind: link.linkKind,
3025
+ namespace: link.target.namespace,
3026
+ target: link.target.table,
3027
+ on: link.linkKind === "toOne" ? resolveColumnRef({ relation, ref: link.on, role: "link on" }) : link.on,
3028
+ ...link.references !== void 0 ? { references: link.linkKind === "toMany" ? resolveColumnRef({ relation, ref: link.references, role: "link references" }) : link.references } : {},
3029
+ enforce: false,
3030
+ ...link.description !== void 0 ? { description: link.description } : {}
3031
+ };
3032
+ }
2898
3033
  const target = resolveRelationRef({ ref: link.target, fail });
2899
3034
  const targetName = relationName(target);
2900
3035
  if (!relationsByName.has(targetName)) {
@@ -2951,11 +3086,11 @@ function assertAcyclicViewDependencies(views) {
2951
3086
  views.map((view) => [view.view, new Set(view.dependencies.filter((dependency) => viewNames.has(dependency)))])
2952
3087
  );
2953
3088
  while (remaining.size > 0) {
2954
- const ready = [...remaining.entries()].filter(([, dependencies]) => [...dependencies].every((dependency) => !remaining.has(dependency))).map(([name]) => name);
3089
+ const ready = [...remaining.entries()].filter(([, dependencies]) => [...dependencies].every((dependency) => !remaining.has(dependency))).map(([name2]) => name2);
2955
3090
  if (ready.length === 0) {
2956
3091
  throw new OrmDefinitionError(`view dependencies form a cycle among: ${[...remaining.keys()].sort().join(", ")}`);
2957
3092
  }
2958
- for (const name of ready) remaining.delete(name);
3093
+ for (const name2 of ready) remaining.delete(name2);
2959
3094
  }
2960
3095
  }
2961
3096
  function toCanonicalPolicy(policy) {
@@ -3001,8 +3136,8 @@ function syncedContractSurface({
3001
3136
  namespace,
3002
3137
  table,
3003
3138
  columns: Object.fromEntries(
3004
- Object.entries(columns).map(([name, column]) => [
3005
- name,
3139
+ Object.entries(columns).map(([name2, column]) => [
3140
+ name2,
3006
3141
  { type: column.type, nullable: column.nullable, key: column.key }
3007
3142
  ])
3008
3143
  ),
@@ -3023,7 +3158,7 @@ function extractOrmSyncedTableContract({
3023
3158
  }
3024
3159
  if (table.source === void 0) {
3025
3160
  throw new OrmDefinitionError(
3026
- `table "${table.table}" is not a synced table \u2014 extractOrmSyncedTableContract requires source: gold.*`
3161
+ `table "${table.table}" is not a synced table \u2014 extractOrmSyncedTableContract requires a qualified source`
3027
3162
  );
3028
3163
  }
3029
3164
  if (table.columns.length === 0) {
@@ -3170,6 +3305,53 @@ function purgeModuleCache(root) {
3170
3305
  if (key.startsWith(prefix)) delete require2.cache[key];
3171
3306
  }
3172
3307
  }
3308
+ var sqlScalarSchema = zod.z.enum(["text", "boolean", "integer", "bigint", "numeric", "double", "uuid", "date", "timestamptz"]);
3309
+ var name = zod.z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).max(63).refine((value) => !["__proto__", "prototype", "constructor"].includes(value), "reserved name");
3310
+ var base = zod.z.object({
3311
+ name,
3312
+ namespace: name,
3313
+ language: zod.z.literal("sql"),
3314
+ kind: zod.z.literal("query"),
3315
+ requires: zod.z.object({ capability: zod.z.string().min(1).max(128) }).strict(),
3316
+ ontology: zod.z.object({ read: zod.z.array(zod.z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*\/[A-Za-z_][A-Za-z0-9_]*$/)).min(1).max(256) }).strict(),
3317
+ maxRows: zod.z.number().int().min(1).max(1e4).default(1e3)
3318
+ });
3319
+ var fixed = base.extend({
3320
+ parameters: zod.z.record(name, zod.z.object({ type: sqlScalarSchema, required: zod.z.boolean().default(true) }).strict()).default({}),
3321
+ sql: zod.z.string().min(1).max(65536)
3322
+ }).strict();
3323
+ var runtime = base.extend({ mode: zod.z.literal("runtime") }).strict();
3324
+ var sqlOperationDefinitionSchema = zod.z.union([fixed, runtime]);
3325
+ function defineOperation(input) {
3326
+ return sqlOperationDefinitionSchema.parse(input);
3327
+ }
3328
+
3329
+ // src/authoring/catalog.ts
3330
+ var SEGMENT = /^[^./\\\s][^/\\]*[^./\\\s]$|^[^./\\\s]$/;
3331
+ function validateNamespaceCatalog(catalog, file) {
3332
+ const paths = /* @__PURE__ */ new Set([""]);
3333
+ function visit(folders, parent) {
3334
+ for (const [name2, children] of Object.entries(folders)) {
3335
+ if (!SEGMENT.test(name2) || ["__proto__", "constructor", "prototype"].includes(name2)) {
3336
+ throw new OrmDefinitionError(`invalid catalog folder name "${name2}"`, file);
3337
+ }
3338
+ const path = parent ? `${parent}/${name2}` : name2;
3339
+ paths.add(path);
3340
+ if (children !== null) {
3341
+ if (typeof children !== "object" || Array.isArray(children)) {
3342
+ throw new OrmDefinitionError(`catalog folder "${path}" must contain folders or null`, file);
3343
+ }
3344
+ visit(children, path);
3345
+ }
3346
+ }
3347
+ }
3348
+ visit(catalog.folders, "");
3349
+ for (const [object, path] of Object.entries(catalog.objects)) {
3350
+ if (!object || !paths.has(path)) {
3351
+ throw new OrmDefinitionError(`catalog object "${object}" references missing folder "${path}"`, file);
3352
+ }
3353
+ }
3354
+ }
3173
3355
 
3174
3356
  exports.COLUMN_BRAND = COLUMN_BRAND;
3175
3357
  exports.COLUMN_DEFAULT_FNS = COLUMN_DEFAULT_FNS;
@@ -3190,6 +3372,7 @@ exports.computeSyncedContractHash = computeSyncedContractHash;
3190
3372
  exports.custom = custom;
3191
3373
  exports.defineEnum = defineEnum;
3192
3374
  exports.defineNamespace = defineNamespace;
3375
+ exports.defineOperation = defineOperation;
3193
3376
  exports.defineResolvedTable = defineResolvedTable;
3194
3377
  exports.defineSyncedTable = defineSyncedTable;
3195
3378
  exports.defineTable = defineTable;
@@ -3211,6 +3394,7 @@ exports.lowest = lowest;
3211
3394
  exports.managedFn = managedFn;
3212
3395
  exports.manualSource = manualSource;
3213
3396
  exports.nonNullFirst = nonNullFirst;
3397
+ exports.objectRef = objectRef;
3214
3398
  exports.overlayWinsUnion = overlayWinsUnion;
3215
3399
  exports.overrideRowMutation = overrideRowMutation;
3216
3400
  exports.prefetchSource = prefetchSource;
@@ -3222,5 +3406,6 @@ exports.t = t;
3222
3406
  exports.toMany = toMany;
3223
3407
  exports.toOne = toOne;
3224
3408
  exports.toPlatformUser = toPlatformUser;
3409
+ exports.validateNamespaceCatalog = validateNamespaceCatalog;
3225
3410
  //# sourceMappingURL=authoring.cjs.map
3226
3411
  //# sourceMappingURL=authoring.cjs.map