@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/compiler.cjs CHANGED
@@ -3,6 +3,7 @@
3
3
  var crypto = require('crypto');
4
4
  var zod = require('zod');
5
5
  var graphql = require('graphql');
6
+ var pgParser = require('@supabase/pg-parser');
6
7
 
7
8
  // src/compiler-gql/artifact.ts
8
9
 
@@ -10,6 +11,7 @@ var graphql = require('graphql');
10
11
  var COLUMN_IDENTIFIER_PATTERN = /^_?[a-z][a-z0-9_]*$/;
11
12
  var MAX_IDENTIFIER_LENGTH = 63;
12
13
  var CAPABILITY_KEY_PATTERN = /^(?:org\/|app\/[a-z][a-z0-9-]{0,63}\/)?[a-z0-9-]{1,64}$/;
14
+ var QUALIFIED_TABLE_PATTERN = /^[a-z][a-z0-9_]{0,62}\.[a-z][a-z0-9_]{0,62}$/;
13
15
  var ORM_SCHEMA_PREFIX = "orm_";
14
16
  var ormSchemaName = (namespace) => `${ORM_SCHEMA_PREFIX}${namespace}`;
15
17
  var CELL_PARTS = ["at", "by", "rev", "source", "conflicts"];
@@ -35,8 +37,18 @@ function stableJson(value) {
35
37
  const entries = Object.entries(value).filter(([, entryValue]) => entryValue !== void 0).sort(([a], [b]) => a < b ? -1 : 1).map(([key, entryValue]) => `${JSON.stringify(key)}:${stableJson(entryValue)}`);
36
38
  return `{${entries.join(",")}}`;
37
39
  }
38
- var quoteIdentifier = (name) => `"${name.replaceAll('"', '""')}"`;
40
+
41
+ // src/compiler/sql.ts
42
+ var CLAIMS_SETTING = "request.jwt.claims";
43
+ var CAPABILITIES_CLAIM_KEY = "capabilities";
44
+ var ROLES_CLAIM_KEY = "roles";
45
+ var quoteIdentifier = (name2) => `"${name2.replaceAll('"', '""')}"`;
39
46
  var quoteLiteral = (value) => `'${value.replaceAll("'", "''")}'`;
47
+ var CLAIMS_SOURCE = `nullif(current_setting('${CLAIMS_SETTING}', true), '')::jsonb`;
48
+ var claimExpression = (key) => `(${CLAIMS_SOURCE} ->> ${quoteLiteral(key)})`;
49
+ var scopedClaimsSource = (namespace) => namespace ? `(CASE WHEN ${CLAIMS_SOURCE} ? 'namespaces' THEN ${CLAIMS_SOURCE} -> 'namespaces' -> ${quoteLiteral(namespace)} ELSE ${CLAIMS_SOURCE} END)` : CLAIMS_SOURCE;
50
+ var capabilityExpression = (key, namespace) => `coalesce(${scopedClaimsSource(namespace)} -> '${CAPABILITIES_CLAIM_KEY}' ? ${quoteLiteral(key)}, false)`;
51
+ var roleExpression = (key, namespace) => `coalesce(${scopedClaimsSource(namespace)} -> '${ROLES_CLAIM_KEY}' ? ${quoteLiteral(key)}, false)`;
40
52
 
41
53
  // src/checks/compile.ts
42
54
  function tokenize(source) {
@@ -342,10 +354,10 @@ function validateResolvedView({ view, tables, views = [] }) {
342
354
  const resolved = view.resolved;
343
355
  if (!resolved) return [];
344
356
  const errors = [];
345
- const fail3 = (message) => errors.push(`resolved table '${view.view}': ${message}`);
357
+ const fail4 = (message) => errors.push(`resolved table '${view.view}': ${message}`);
346
358
  const pkColumn = view.primaryKey?.length === 1 ? view.columns.find((c) => c.columnName === view.primaryKey?.[0]) : void 0;
347
359
  if (!pkColumn) {
348
- fail3("requires exactly one primary-key column");
360
+ fail4("requires exactly one primary-key column");
349
361
  return errors;
350
362
  }
351
363
  const systemNames = new Set(Object.values(RESOLVED_VIEW_SYSTEM_COLUMNS).map((c) => c.name));
@@ -355,92 +367,92 @@ function validateResolvedView({ view, tables, views = [] }) {
355
367
  pkColumn,
356
368
  dataColumns: new Map(view.columns.filter((c) => !systemNames.has(c.name) && c.cell === void 0).map((c) => [c.name, c])),
357
369
  sourceByName: /* @__PURE__ */ new Map(),
358
- fail: fail3
370
+ fail: fail4
359
371
  };
360
- if (resolved.sources.filter((source) => source.kind === "manual").length !== 1) fail3("requires exactly one manual source");
372
+ if (resolved.sources.filter((source) => source.kind === "manual").length !== 1) fail4("requires exactly one manual source");
361
373
  const tableByName = new Map(tables.map((table) => [table.table, table]));
362
374
  for (const source of resolved.sources) {
363
- if (scope.sourceByName.has(source.name)) fail3(`duplicate source '${source.name}'`);
375
+ if (scope.sourceByName.has(source.name)) fail4(`duplicate source '${source.name}'`);
364
376
  scope.sourceByName.set(source.name, source);
365
377
  const table = tableByName.get(source.table);
366
- if (!table) fail3(`source '${source.name}' references unknown table '${source.table}'`);
378
+ if (!table) fail4(`source '${source.name}' references unknown table '${source.table}'`);
367
379
  else validateSource({ scope, source, table });
368
380
  }
369
381
  validateRules(scope);
370
382
  validateViewCells(scope);
371
383
  if (resolved.materialized) validateMaterialized({ scope, tableByName, views });
372
- for (const name of resolved.deletedBy) {
373
- if (scope.sourceByName.get(name)?.kind !== "manual") fail3(`deletedBy '${name}' must be the manual source`);
384
+ for (const name2 of resolved.deletedBy) {
385
+ if (scope.sourceByName.get(name2)?.kind !== "manual") fail4(`deletedBy '${name2}' must be the manual source`);
374
386
  }
375
387
  return errors;
376
388
  }
377
389
  function validateSource({ scope, source, table }) {
378
- const { fail: fail3 } = scope;
379
- if (source.kind === "etl" && table.source === void 0) fail3(`etl source '${source.name}' must reference a synced table`);
390
+ const { fail: fail4 } = scope;
391
+ if (source.kind === "etl" && table.source === void 0) fail4(`etl source '${source.name}' must reference a synced table`);
380
392
  if (source.kind !== "etl" && (table.internal !== true || table.cellMetadata !== true)) {
381
- fail3(`${source.kind} source '${source.name}' must reference an internal table with cell metadata`);
393
+ fail4(`${source.kind} source '${source.name}' must reference an internal table with cell metadata`);
382
394
  }
383
395
  const hasRev = table.columns.some((column) => column.cell?.part === "rev");
384
- if (source.kind === "manual" && !hasRev) fail3(`manual source '${source.name}' must carry '__rev' cell columns`);
385
- if (source.kind === "prefetch" && hasRev) fail3(`prefetch source '${source.name}' must not carry '__rev' cell columns`);
396
+ if (source.kind === "manual" && !hasRev) fail4(`manual source '${source.name}' must carry '__rev' cell columns`);
397
+ if (source.kind === "prefetch" && hasRev) fail4(`prefetch source '${source.name}' must not carry '__rev' cell columns`);
386
398
  const byName = new Map(table.columns.map((c) => [c.name, c]));
387
399
  validateSourceColumns({ scope, source, byName });
388
400
  if (source.kind === "etl") {
389
401
  const updatedAt = source.updatedAt === void 0 ? void 0 : byName.get(source.updatedAt);
390
402
  if (source.updatedAt !== void 0 && updatedAt?.type !== "timestamptz" && updatedAt?.type !== "date") {
391
- fail3(`etl source '${source.name}' updatedAt must name a timestamptz/date column on '${source.table}'`);
403
+ fail4(`etl source '${source.name}' updatedAt must name a timestamptz/date column on '${source.table}'`);
392
404
  }
393
405
  }
394
406
  validatePrefetchFields({ scope, source });
395
407
  }
396
408
  function validatePrefetchFields({ scope, source }) {
397
- const { fail: fail3, pkColumn } = scope;
409
+ const { fail: fail4, pkColumn } = scope;
398
410
  if (source.kind !== "prefetch") {
399
411
  if (source.key || source.fetch || source.function || source.prefetchDefaults || source.writeback) {
400
- fail3(`only prefetch sources may declare key/fetch/function/prefetchDefaults/writeback ('${source.name}')`);
412
+ fail4(`only prefetch sources may declare key/fetch/function/prefetchDefaults/writeback ('${source.name}')`);
401
413
  }
402
414
  return;
403
415
  }
404
416
  if (!source.key || !source.fetch || !source.function || !source.prefetchDefaults) {
405
- fail3(`prefetch source '${source.name}' requires key, fetch, function and prefetchDefaults`);
417
+ fail4(`prefetch source '${source.name}' requires key, fetch, function and prefetchDefaults`);
406
418
  } else if (source.key !== pkColumn.name && !source.columns.includes(source.key)) {
407
- fail3(`prefetch source '${source.name}' key '${source.key}' must be the primary key or one of its columns`);
419
+ fail4(`prefetch source '${source.name}' key '${source.key}' must be the primary key or one of its columns`);
408
420
  }
409
421
  for (const column of source.writeback?.columns ?? []) {
410
- if (!source.columns.includes(column)) fail3(`prefetch source '${source.name}' writes back '${column}', which it does not supply`);
422
+ if (!source.columns.includes(column)) fail4(`prefetch source '${source.name}' writes back '${column}', which it does not supply`);
411
423
  }
412
424
  }
413
425
  function validateSourceColumns({ scope, source, byName }) {
414
- const { fail: fail3, pkColumn, dataColumns } = scope;
426
+ const { fail: fail4, pkColumn, dataColumns } = scope;
415
427
  for (const column of [pkColumn.name, ...source.columns]) {
416
428
  const own = column === pkColumn.name ? pkColumn : dataColumns.get(column);
417
429
  const theirs = byName.get(column);
418
- if (!own) fail3(`source '${source.name}' supplies unknown column '${column}'`);
419
- else if (!theirs) fail3(`source '${source.name}' table '${source.table}' has no column '${column}'`);
420
- else if (JSON.stringify(theirs.type) !== JSON.stringify(own.type)) fail3(`column '${column}' has a different type on source '${source.name}'`);
430
+ if (!own) fail4(`source '${source.name}' supplies unknown column '${column}'`);
431
+ else if (!theirs) fail4(`source '${source.name}' table '${source.table}' has no column '${column}'`);
432
+ else if (JSON.stringify(theirs.type) !== JSON.stringify(own.type)) fail4(`column '${column}' has a different type on source '${source.name}'`);
421
433
  }
422
434
  }
423
- function validateRules({ resolved, pkColumn, dataColumns, sourceByName, fail: fail3 }) {
435
+ function validateRules({ resolved, pkColumn, dataColumns, sourceByName, fail: fail4 }) {
424
436
  const ruled = /* @__PURE__ */ new Set();
425
437
  for (const rule of resolved.resolve) {
426
438
  if (!dataColumns.has(rule.column) || rule.column === pkColumn.name) {
427
- fail3(`resolve rule targets unknown column '${rule.column}'`);
439
+ fail4(`resolve rule targets unknown column '${rule.column}'`);
428
440
  continue;
429
441
  }
430
- if (ruled.has(rule.column)) fail3(`column '${rule.column}' has more than one resolve rule`);
442
+ if (ruled.has(rule.column)) fail4(`column '${rule.column}' has more than one resolve rule`);
431
443
  ruled.add(rule.column);
432
444
  if (rule.comparator === "custom" !== (rule.orderBy !== void 0)) {
433
- fail3(`column '${rule.column}': orderBy is required for custom comparators and only allowed there`);
445
+ fail4(`column '${rule.column}': orderBy is required for custom comparators and only allowed there`);
434
446
  }
435
- if (new Set(rule.candidates).size !== rule.candidates.length) fail3(`column '${rule.column}' repeats a candidate`);
447
+ if (new Set(rule.candidates).size !== rule.candidates.length) fail4(`column '${rule.column}' repeats a candidate`);
436
448
  for (const candidate of rule.candidates) {
437
449
  const source = sourceByName.get(candidate);
438
- if (!source) fail3(`column '${rule.column}' names unknown candidate source '${candidate}'`);
439
- else if (!source.columns.includes(rule.column)) fail3(`column '${rule.column}' candidate '${candidate}' does not supply it`);
450
+ if (!source) fail4(`column '${rule.column}' names unknown candidate source '${candidate}'`);
451
+ else if (!source.columns.includes(rule.column)) fail4(`column '${rule.column}' candidate '${candidate}' does not supply it`);
440
452
  }
441
453
  }
442
454
  for (const column of dataColumns.keys()) {
443
- if (column !== pkColumn.name && !ruled.has(column)) fail3(`column '${column}' has no resolve rule`);
455
+ if (column !== pkColumn.name && !ruled.has(column)) fail4(`column '${column}' has no resolve rule`);
444
456
  }
445
457
  }
446
458
  var VIEW_CELL_TYPES = {
@@ -449,7 +461,7 @@ var VIEW_CELL_TYPES = {
449
461
  by: "text",
450
462
  conflicts: "jsonb"
451
463
  };
452
- function validateViewCells({ view, dataColumns, pkColumn, fail: fail3 }) {
464
+ function validateViewCells({ view, dataColumns, pkColumn, fail: fail4 }) {
453
465
  const expected = /* @__PURE__ */ new Map();
454
466
  for (const column of dataColumns.values()) {
455
467
  if (column.name === pkColumn.name) continue;
@@ -461,34 +473,34 @@ function validateViewCells({ view, dataColumns, pkColumn, fail: fail3 }) {
461
473
  if (!column.cell) continue;
462
474
  const want = expected.get(column.name);
463
475
  if (!want || want.part !== column.cell.part || want.columnName !== column.columnName || cellColumnName(column.cell.of, column.cell.part) !== column.name) {
464
- fail3(`unexpected cell column '${column.name}'`);
476
+ fail4(`unexpected cell column '${column.name}'`);
465
477
  continue;
466
478
  }
467
- if (column.type !== VIEW_CELL_TYPES[column.cell.part]) fail3(`cell column '${column.name}' must be ${VIEW_CELL_TYPES[column.cell.part]}`);
479
+ if (column.type !== VIEW_CELL_TYPES[column.cell.part]) fail4(`cell column '${column.name}' must be ${VIEW_CELL_TYPES[column.cell.part]}`);
468
480
  expected.delete(column.name);
469
481
  }
470
- for (const name of expected.keys()) fail3(`missing cell column '${name}'`);
482
+ for (const name2 of expected.keys()) fail4(`missing cell column '${name2}'`);
471
483
  }
472
484
  var columnSignature = (columns) => JSON.stringify(columns.map((column) => [column.name, column.columnName, column.type]));
473
485
  function validateMaterialized({ scope, tableByName, views }) {
474
- const { view, resolved, fail: fail3 } = scope;
486
+ const { view, resolved, fail: fail4 } = scope;
475
487
  const { table: tableName, compute: computeName } = resolved.materialized;
476
488
  const table = tableByName.get(tableName);
477
489
  if (!table || table.internal !== true || table.materializes !== view.view) {
478
- fail3(`materialized table '${tableName}' must be an internal table that materializes '${view.view}'`);
490
+ fail4(`materialized table '${tableName}' must be an internal table that materializes '${view.view}'`);
479
491
  } else {
480
- if (columnSignature(table.columns) !== columnSignature(view.columns)) fail3(`materialized table '${tableName}' must have exactly the view's columns`);
481
- if (JSON.stringify(table.primaryKey ?? []) !== JSON.stringify(view.primaryKey ?? [])) fail3(`materialized table '${tableName}' must share the view's primary key`);
492
+ if (columnSignature(table.columns) !== columnSignature(view.columns)) fail4(`materialized table '${tableName}' must have exactly the view's columns`);
493
+ if (JSON.stringify(table.primaryKey ?? []) !== JSON.stringify(view.primaryKey ?? [])) fail4(`materialized table '${tableName}' must share the view's primary key`);
482
494
  }
483
495
  const compute = views.find((candidate) => candidate.view === computeName);
484
- if (!compute || compute.internal !== true) fail3(`compute view '${computeName}' must be an internal view`);
485
- else if (columnSignature(compute.columns) !== columnSignature(view.columns)) fail3(`compute view '${computeName}' must have exactly the view's columns`);
496
+ if (!compute || compute.internal !== true) fail4(`compute view '${computeName}' must be an internal view`);
497
+ else if (columnSignature(compute.columns) !== columnSignature(view.columns)) fail4(`compute view '${computeName}' must have exactly the view's columns`);
486
498
  for (const source of resolved.sources) {
487
499
  if (source.kind !== "etl") continue;
488
500
  const read = tableByName.get(source.table)?.policies?.read;
489
501
  const unconditional = typeof read === "string" ? read.trim() === "true" : read !== void 0 && read.default.trim() === "true" && read.variants.length === 0;
490
502
  if (!unconditional) {
491
- fail3(`etl source '${source.name}' must have read policy 'true' to be materialized (the copy is shared by every caller)`);
503
+ fail4(`etl source '${source.name}' must have read policy 'true' to be materialized (the copy is shared by every caller)`);
492
504
  }
493
505
  }
494
506
  }
@@ -558,6 +570,8 @@ var canonicalLinkSchema = zod.z.object({
558
570
  kind: zod.z.enum(["toOne", "toMany", "toPlatformUser"]),
559
571
  /** Target table name (namespace-local). Absent for toPlatformUser. */
560
572
  target: identifier2.optional(),
573
+ /** Owning namespace of an imported target; omitted for local links. */
574
+ namespace: identifier2.max(41).optional(),
561
575
  /** FK column on the owning side (toOne/toPlatformUser: on this table; toMany: on the target). */
562
576
  on: columnIdentifier,
563
577
  /** Referenced column on the target (default: target's PK / platform user `sub`). */
@@ -659,11 +673,12 @@ var canonicalForeignKeySchema = zod.z.object({
659
673
  }).strict();
660
674
  var canonicalTableSchema = zod.z.object({
661
675
  table: identifier2,
676
+ displayName: zod.z.string().trim().min(1).max(120).optional(),
662
677
  description: zod.z.string().max(2e3).optional(),
663
678
  /** Table-level rename hint (new in v2). */
664
679
  renamedFrom: identifier2.optional(),
665
- /** Synced/reflected read-only tables (`source: gold.*`) — v1 semantics. */
666
- source: zod.z.string().regex(/^gold\.[a-z][a-z0-9_]*$/).optional(),
680
+ /** Synced/reflected read-only tables (`source: schema.table`) — v1 semantics. */
681
+ source: zod.z.string().regex(QUALIFIED_TABLE_PATTERN).optional(),
667
682
  columns: zod.z.array(canonicalColumnSchema).default([]),
668
683
  primaryKey: zod.z.array(columnIdentifier).min(1).max(8).optional(),
669
684
  links: zod.z.array(canonicalLinkSchema).default([]),
@@ -770,53 +785,55 @@ function trackedCellColumns(table) {
770
785
  }
771
786
  var SUFFIXES = Object.values(CELL_PART_SUFFIX);
772
787
  function validateCellMetadata(table, index, ctx) {
773
- const fail3 = (message, path = ["tables", index]) => ctx.addIssue({ code: "custom", path, message });
788
+ const fail4 = (message, path = ["tables", index]) => ctx.addIssue({ code: "custom", path, message });
774
789
  const cells = table.columns.flatMap((column, position) => column.cell ? [{ column, position, cell: column.cell }] : []);
775
790
  if (table.materializes !== void 0) return;
776
791
  if (table.cellMetadata !== true) {
777
- for (const { position } of cells) fail3(`cell-metadata columns require cellMetadata on table '${table.table}'`, ["tables", index, "columns", position]);
792
+ for (const { position } of cells) fail4(`cell-metadata columns require cellMetadata on table '${table.table}'`, ["tables", index, "columns", position]);
778
793
  return;
779
794
  }
780
795
  if (table.source !== void 0) {
781
- fail3(`synced table '${table.table}' cannot track cell metadata`);
796
+ fail4(`synced table '${table.table}' cannot track cell metadata`);
782
797
  return;
783
798
  }
784
799
  const tracked = trackedCellColumns(table);
785
800
  const byName = new Map(tracked.map((column) => [column.name, column]));
786
801
  const withRev = cells.some(({ cell }) => cell.part === "rev");
787
- if (withRev && table.internal !== true) fail3(`only a resolved table's internal sources carry '__rev' cell columns ('${table.table}')`);
802
+ if (withRev && table.internal !== true) fail4(`only a resolved table's internal sources carry '__rev' cell columns ('${table.table}')`);
788
803
  const seen = /* @__PURE__ */ new Set();
789
804
  for (const { column, position, cell } of cells) {
790
805
  const path = ["tables", index, "columns", position];
791
806
  const owner = byName.get(cell.of);
792
807
  if (!owner) {
793
- fail3(`cell column '${column.name}' belongs to '${cell.of}', which is not a tracked column`, path);
808
+ fail4(`cell column '${column.name}' belongs to '${cell.of}', which is not a tracked column`, path);
794
809
  continue;
795
810
  }
796
- if (cell.part !== "at" && cell.part !== "by" && cell.part !== "rev") fail3(`a table cell column must be part at/by/rev, not '${cell.part}'`, path);
811
+ if (cell.part !== "at" && cell.part !== "by" && cell.part !== "rev") fail4(`a table cell column must be part at/by/rev, not '${cell.part}'`, path);
797
812
  if (column.name !== cellColumnName(owner.name, cell.part) || column.columnName !== cellColumnName(owner.columnName, cell.part)) {
798
- fail3(`cell column for '${owner.name}' ${cell.part} must be named '${cellColumnName(owner.name, cell.part)}' / '${cellColumnName(owner.columnName, cell.part)}'`, path);
813
+ fail4(`cell column for '${owner.name}' ${cell.part} must be named '${cellColumnName(owner.name, cell.part)}' / '${cellColumnName(owner.columnName, cell.part)}'`, path);
799
814
  }
800
815
  if (column.type !== CELL_PART_TYPES[cell.part] || !column.nullable || column.unique || column.default !== void 0 || column.defaultFn !== void 0) {
801
- fail3(`cell column '${column.name}' must be a nullable, default-less ${CELL_PART_TYPES[cell.part]}`, path);
816
+ fail4(`cell column '${column.name}' must be a nullable, default-less ${CELL_PART_TYPES[cell.part]}`, path);
802
817
  }
803
818
  const key = `${cell.of}:${cell.part}`;
804
- if (seen.has(key)) fail3(`duplicate cell column '${column.name}'`, path);
819
+ if (seen.has(key)) fail4(`duplicate cell column '${column.name}'`, path);
805
820
  seen.add(key);
806
821
  }
807
822
  for (const column of tracked) {
808
823
  if (column.columnName.length > MAX_TRACKED_COLUMN_NAME) {
809
- fail3(`column '${column.columnName}' is too long to track cell metadata (max ${MAX_TRACKED_COLUMN_NAME} characters)`);
824
+ fail4(`column '${column.columnName}' is too long to track cell metadata (max ${MAX_TRACKED_COLUMN_NAME} characters)`);
810
825
  }
811
826
  if (SUFFIXES.some((suffix) => column.columnName.endsWith(suffix))) {
812
- fail3(`column '${column.columnName}' ends in a reserved cell-metadata suffix (${SUFFIXES.join(", ")})`);
827
+ fail4(`column '${column.columnName}' ends in a reserved cell-metadata suffix (${SUFFIXES.join(", ")})`);
813
828
  }
814
829
  for (const part of withRev ? ["at", "by", "rev"] : ["at", "by"]) {
815
- if (!seen.has(`${column.name}:${part}`)) fail3(`tracked column '${column.name}' is missing its '${cellColumnName(column.columnName, part)}' cell column`);
830
+ if (!seen.has(`${column.name}:${part}`)) fail4(`tracked column '${column.name}' is missing its '${cellColumnName(column.columnName, part)}' cell column`);
816
831
  }
817
832
  }
818
833
  }
819
- var canonicalDefinitionSchema = zod.z.object({
834
+ 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()]);
835
+ var relationExportSchema = zod.z.object({ read: exportGateSchema, write: exportGateSchema.optional() }).strict();
836
+ var localDefinitionSchema = zod.z.object({
820
837
  definition_format: zod.z.literal(CANONICAL_FORMAT),
821
838
  namespace: identifier2.max(41),
822
839
  description: zod.z.string().max(2e3).optional(),
@@ -824,6 +841,8 @@ var canonicalDefinitionSchema = zod.z.object({
824
841
  tables: zod.z.array(canonicalTableSchema).min(0).max(500),
825
842
  /** Optional to preserve byte-identical hashes for pre-view canonical v2 documents. */
826
843
  views: zod.z.array(canonicalViewSchema).max(500).optional(),
844
+ /** Target-owned cross-namespace access contract, using existing namespace grants. */
845
+ exports: zod.z.record(identifier2, relationExportSchema).optional(),
827
846
  /** Verbatim custom-roles grammar (@sequenceholdings/custom-roles-manifest). */
828
847
  capabilities: zod.z.object({
829
848
  roles: zod.z.unknown().optional(),
@@ -850,7 +869,16 @@ var canonicalDefinitionSchema = zod.z.object({
850
869
  }
851
870
  }
852
871
  const fkTables = new Map(definition.tables.map((table) => [table.table, table]));
872
+ const relations = /* @__PURE__ */ new Set([...fkTables.keys(), ...(definition.views ?? []).map((view) => view.view)]);
873
+ for (const name2 of Object.keys(definition.exports ?? {})) {
874
+ if (!relations.has(name2)) ctx.addIssue({ code: "custom", path: ["exports", name2], message: `export '${name2}' is not a local object` });
875
+ }
853
876
  for (const [tableIndex, table] of definition.tables.entries()) {
877
+ for (const [linkIndex, link] of table.links.entries()) {
878
+ if (link.enforce && link.namespace !== void 0 && link.namespace !== definition.namespace) {
879
+ ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "links", linkIndex], message: "physical foreign keys require a local target; use an ORM link without enforce" });
880
+ }
881
+ }
854
882
  const names = /* @__PURE__ */ new Set([
855
883
  ...table.primaryKey?.length ? [`${table.table}_pkey`] : [],
856
884
  ...table.columns.filter((column) => column.unique && !(table.primaryKey?.length === 1 && table.primaryKey[0] === column.columnName)).map((column) => `${table.table}_${column.columnName}_unique`),
@@ -866,38 +894,38 @@ var canonicalDefinitionSchema = zod.z.object({
866
894
  }
867
895
  }
868
896
  for (const [index, check] of (table.checks ?? []).entries()) {
869
- const fail3 = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "checks", index], message });
870
- if (table.source) fail3("synced tables cannot declare checks");
871
- if (names.has(check.name)) fail3(`duplicate constraint '${check.name}'`);
897
+ const fail4 = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "checks", index], message });
898
+ if (table.source) fail4("synced tables cannot declare checks");
899
+ if (names.has(check.name)) fail4(`duplicate constraint '${check.name}'`);
872
900
  names.add(check.name);
873
901
  try {
874
902
  compileCheckExpression(check.expression, table.columns);
875
903
  } catch (error) {
876
- fail3(error instanceof Error ? error.message : String(error));
904
+ fail4(error instanceof Error ? error.message : String(error));
877
905
  }
878
906
  }
879
907
  for (const [index, fk] of (table.foreignKeys ?? []).entries()) {
880
- const fail3 = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "foreignKeys", index], message });
881
- if (names.has(fk.name)) fail3(`duplicate foreign key '${fk.name}'`);
908
+ const fail4 = (message) => ctx.addIssue({ code: "custom", path: ["tables", tableIndex, "foreignKeys", index], message });
909
+ if (names.has(fk.name)) fail4(`duplicate foreign key '${fk.name}'`);
882
910
  names.add(fk.name);
883
911
  const target = fkTables.get(fk.target);
884
912
  if (table.source || !target || target.source) {
885
- fail3("foreign keys must connect owned tables in the same namespace");
913
+ fail4("foreign keys must connect owned tables in the same namespace");
886
914
  continue;
887
915
  }
888
- if (fk.columns.length !== fk.references.length) fail3("foreign key columns and references must have the same number of columns");
889
- if (new Set(fk.columns).size !== fk.columns.length || new Set(fk.references).size !== fk.references.length) fail3("foreign key columns must not repeat");
890
- for (const [position, name] of fk.columns.entries()) {
891
- const sourceColumn = table.columns.find((column) => column.columnName === name);
916
+ if (fk.columns.length !== fk.references.length) fail4("foreign key columns and references must have the same number of columns");
917
+ if (new Set(fk.columns).size !== fk.columns.length || new Set(fk.references).size !== fk.references.length) fail4("foreign key columns must not repeat");
918
+ for (const [position, name2] of fk.columns.entries()) {
919
+ const sourceColumn = table.columns.find((column) => column.columnName === name2);
892
920
  const targetColumn = target.columns.find((column) => column.columnName === fk.references[position]);
893
- if (!sourceColumn || !targetColumn) fail3("foreign key references an unknown column");
894
- else if (JSON.stringify(sourceColumn.type) !== JSON.stringify(targetColumn.type)) fail3("foreign key column types must match");
921
+ if (!sourceColumn || !targetColumn) fail4("foreign key references an unknown column");
922
+ else if (JSON.stringify(sourceColumn.type) !== JSON.stringify(targetColumn.type)) fail4("foreign key column types must match");
895
923
  }
896
924
  if (fk.onDelete === "set null" || fk.onUpdate === "set null") {
897
- if (fk.columns.some((name) => table.columns.find((column) => column.columnName === name)?.nullable === false || table.primaryKey?.includes(name))) fail3("SET NULL requires nullable source columns");
925
+ if (fk.columns.some((name2) => table.columns.find((column) => column.columnName === name2)?.nullable === false || table.primaryKey?.includes(name2))) fail4("SET NULL requires nullable source columns");
898
926
  }
899
927
  const uniqueKeys = [target.primaryKey ?? [], ...target.indexes.filter((index2) => index2.unique).map((index2) => index2.columns), ...target.columns.filter((column) => column.unique).map((column) => [column.columnName])];
900
- if (!uniqueKeys.some((key) => key.length === fk.references.length && key.every((column) => fk.references.includes(column)))) fail3("foreign key target columns must form a unique key");
928
+ if (!uniqueKeys.some((key) => key.length === fk.references.length && key.every((column) => fk.references.includes(column)))) fail4("foreign key target columns must form a unique key");
901
929
  }
902
930
  }
903
931
  for (const [viewIndex, view] of (definition.views ?? []).entries()) {
@@ -999,27 +1027,31 @@ var canonicalDefinitionSchema = zod.z.object({
999
1027
  const visiting = /* @__PURE__ */ new Set();
1000
1028
  const visited = /* @__PURE__ */ new Set();
1001
1029
  let cycleReported = false;
1002
- const visit2 = (name, path) => {
1003
- if (visited.has(name) || cycleReported) return;
1004
- if (visiting.has(name)) {
1030
+ const visit2 = (name2, path) => {
1031
+ if (visited.has(name2) || cycleReported) return;
1032
+ if (visiting.has(name2)) {
1005
1033
  cycleReported = true;
1006
1034
  ctx.addIssue({
1007
1035
  code: "custom",
1008
1036
  path: ["views"],
1009
- message: `view dependencies form a cycle: ${[...path, name].join(" -> ")}`
1037
+ message: `view dependencies form a cycle: ${[...path, name2].join(" -> ")}`
1010
1038
  });
1011
1039
  return;
1012
1040
  }
1013
- visiting.add(name);
1014
- const view = viewByName.get(name);
1041
+ visiting.add(name2);
1042
+ const view = viewByName.get(name2);
1015
1043
  for (const dependency of view?.dependencies ?? []) {
1016
- if (viewByName.has(dependency)) visit2(dependency, [...path, name]);
1044
+ if (viewByName.has(dependency)) visit2(dependency, [...path, name2]);
1017
1045
  }
1018
- visiting.delete(name);
1019
- visited.add(name);
1046
+ visiting.delete(name2);
1047
+ visited.add(name2);
1020
1048
  };
1021
1049
  for (const view of views) visit2(view.view, []);
1022
1050
  });
1051
+ var canonicalDefinitionSchema = localDefinitionSchema.safeExtend({
1052
+ /** Flattened, pinned snapshots. Imported relations never participate in this namespace's DDL. */
1053
+ imports: zod.z.array(localDefinitionSchema).max(32).optional()
1054
+ });
1023
1055
  function canonicalRelations(definition) {
1024
1056
  const views = (definition.views ?? []).map((view) => ({
1025
1057
  table: view.view,
@@ -1063,13 +1095,22 @@ function normalizeCanonical(definition) {
1063
1095
  dependencies: [...view.dependencies].sort()
1064
1096
  }));
1065
1097
  }
1098
+ if (definition.imports?.length) {
1099
+ normalized.imports = definition.imports.map(normalizeCanonical).sort((a, b) => a.namespace.localeCompare(b.namespace));
1100
+ } else {
1101
+ delete normalized.imports;
1102
+ }
1066
1103
  return normalized;
1067
1104
  }
1068
1105
 
1069
1106
  // src/ir/index.ts
1070
- var IR_VERSION = 2;
1107
+ var IR_VERSION = 5;
1071
1108
  var LEGACY_IR_VERSION = 1;
1072
- var irVersionSchema = zod.z.union([zod.z.literal(LEGACY_IR_VERSION), zod.z.literal(IR_VERSION)]);
1109
+ var GUARDED_IR_VERSION = 2;
1110
+ var COMPOSED_IR_VERSION = 3;
1111
+ var SQL_IR_VERSION = 4;
1112
+ var RUNTIME_SQL_IR_VERSION = 5;
1113
+ var irVersionSchema = zod.z.union([zod.z.literal(LEGACY_IR_VERSION), zod.z.literal(GUARDED_IR_VERSION), zod.z.literal(COMPOSED_IR_VERSION), zod.z.literal(SQL_IR_VERSION), zod.z.literal(RUNTIME_SQL_IR_VERSION)]);
1073
1114
  var operationVersionSchema = zod.z.object({ irVersion: irVersionSchema });
1074
1115
  function requiredArtifactIrVersion(operations) {
1075
1116
  let required = LEGACY_IR_VERSION;
@@ -1121,8 +1162,8 @@ var predIrSchema = zod.z.lazy(
1121
1162
  value: zod.z.number(),
1122
1163
  where: predIrSchema.optional()
1123
1164
  }),
1124
- zod.z.object({ kind: zod.z.literal("capability"), key: zod.z.string() }),
1125
- zod.z.object({ kind: zod.z.literal("role"), key: zod.z.string() }),
1165
+ zod.z.object({ kind: zod.z.literal("capability"), key: zod.z.string(), namespace: zod.z.string().optional() }),
1166
+ zod.z.object({ kind: zod.z.literal("role"), key: zod.z.string(), namespace: zod.z.string().optional() }),
1126
1167
  zod.z.object({ kind: zod.z.literal("dynamicFilter"), slot: zod.z.string() }),
1127
1168
  zod.z.object({ kind: zod.z.literal("const"), value: zod.z.boolean() })
1128
1169
  ])
@@ -1349,7 +1390,31 @@ var variableIrSchema = zod.z.object({
1349
1390
  /** Absent = no default declared; see {@link variableDefaultIrSchema}. */
1350
1391
  defaultValue: variableDefaultIrSchema.optional()
1351
1392
  });
1393
+ var sqlColumnSchema = zod.z.object({ name: zod.z.string(), type: zod.z.enum(["text", "boolean", "integer", "bigint", "numeric", "double", "uuid", "date", "timestamptz"]), nullable: zod.z.boolean() });
1352
1394
  var compiledOperationSchema = zod.z.object({
1395
+ language: zod.z.literal("sql").optional(),
1396
+ /** Server-compiled object catalog and permission ceiling, never caller SQL. */
1397
+ sqlQuery: zod.z.object({
1398
+ catalog: zod.z.object({
1399
+ identity: zod.z.string(),
1400
+ objects: zod.z.array(zod.z.object({
1401
+ ref: zod.z.string(),
1402
+ table: zod.z.string(),
1403
+ columns: zod.z.array(sqlColumnSchema.extend({ type: zod.z.string() })).min(1),
1404
+ scan: zod.z.string().min(1),
1405
+ read: zod.z.array(zod.z.string()).min(1)
1406
+ }).strict()).min(1).max(256)
1407
+ }).strict(),
1408
+ read: zod.z.array(zod.z.string()).min(1).max(256),
1409
+ maxRows: zod.z.number().int().min(1).max(1e4)
1410
+ }).strict().optional(),
1411
+ sql: zod.z.object({
1412
+ text: zod.z.string().min(1).max(1048576),
1413
+ columns: zod.z.array(sqlColumnSchema).min(1).max(256),
1414
+ parameters: zod.z.array(zod.z.string()).max(128),
1415
+ read: zod.z.array(zod.z.string()).min(1).max(256),
1416
+ maxRows: zod.z.number().int().min(1).max(1e4)
1417
+ }).strict().optional(),
1353
1418
  irVersion: irVersionSchema,
1354
1419
  name: zod.z.string(),
1355
1420
  opHash: zod.z.string().regex(/^sha256:[0-9a-f]{64}$/),
@@ -1361,8 +1426,16 @@ var compiledOperationSchema = zod.z.object({
1361
1426
  variables: zod.z.array(variableIrSchema),
1362
1427
  /** Every table the op touches — data-token scoping + audit. */
1363
1428
  tableSet: zod.z.array(zod.z.string()).min(1),
1364
- roots: zod.z.array(rootIrSchema).min(1)
1429
+ roots: zod.z.array(rootIrSchema)
1365
1430
  }).superRefine((operation, ctx) => {
1431
+ if (operation.language === "sql") {
1432
+ const validPlan = operation.sqlQuery ? !operation.sql && operation.irVersion === RUNTIME_SQL_IR_VERSION : !!operation.sql && operation.irVersion === SQL_IR_VERSION;
1433
+ if (!validPlan || operation.kind !== "query" || operation.roots.length !== 0 || !operation.requires?.capability) {
1434
+ ctx.addIssue({ code: "custom", message: "SQL operations require a query-only v4 plan or v5 runtime contract, capability, and no GraphQL roots" });
1435
+ }
1436
+ } else if (operation.sql || operation.sqlQuery || operation.roots.length === 0) {
1437
+ ctx.addIssue({ code: "custom", message: "GraphQL operations require roots and cannot carry SQL" });
1438
+ }
1366
1439
  for (const [index, root] of operation.roots.entries()) {
1367
1440
  if (root.kind !== "mutation" || root.assertAffectedRows === void 0) continue;
1368
1441
  if (operation.irVersion < 2 || operation.kind !== "mutation" || root.includeIf !== void 0) {
@@ -1409,11 +1482,16 @@ var resolvedMetaIrSchema = zod.z.object({
1409
1482
  });
1410
1483
  var tableMetaIrSchema = zod.z.object({
1411
1484
  table: zod.z.string(),
1485
+ namespace: zod.z.string().optional(),
1486
+ dependencies: zod.z.array(zod.z.string()).optional(),
1487
+ foreignReferences: zod.z.array(zod.z.object({ column: zod.z.string(), target: zod.z.string(), targetColumn: zod.z.string() })).optional(),
1488
+ exportRead: zod.z.union([zod.z.literal(true), zod.z.object({ capability: zod.z.string() }), zod.z.object({ role: zod.z.string() })]).optional(),
1489
+ exportWrite: zod.z.union([zod.z.literal(true), zod.z.object({ capability: zod.z.string() }), zod.z.object({ role: zod.z.string() })]).optional(),
1412
1490
  schema: zod.z.string(),
1413
1491
  typename: zod.z.string(),
1414
1492
  /**
1415
1493
  * True when statements over this relation evaluate a claims-gated synced
1416
- * view: synced twins (`source: gold.*`) materialize as views whose baked
1494
+ * view: synced twins (`source: schema.table`) materialize as views whose baked
1417
1495
  * policy reads `current_setting('request.jwt.claims')`, and namespace views
1418
1496
  * inherit the gate transitively through their declared dependencies. The
1419
1497
  * executor must stamp that GUC (transaction-scoped) before any statement
@@ -1472,13 +1550,21 @@ var compiledArtifactSchema = zod.z.object({
1472
1550
  irVersion: irVersionSchema,
1473
1551
  namespace: zod.z.string(),
1474
1552
  schemaHash: zod.z.string(),
1553
+ ownSchemaHash: zod.z.string().optional(),
1554
+ dependencies: zod.z.record(zod.z.string(), zod.z.string()).optional(),
1555
+ readPolicies: zod.z.record(zod.z.string(), predIrSchema).optional(),
1475
1556
  opsHash: zod.z.string(),
1557
+ /** Apply-observed synced objects without a serving backing; not part of the authored schema hash. */
1558
+ unbackedTables: zod.z.array(zod.z.string()).optional(),
1476
1559
  tables: zod.z.record(zod.z.string(), tableMetaIrSchema),
1477
1560
  operations: zod.z.record(zod.z.string(), compiledOperationSchema)
1478
1561
  }).superRefine((artifact, ctx) => {
1479
- for (const [name, operation] of Object.entries(artifact.operations)) {
1562
+ if (Object.keys(artifact.dependencies ?? {}).length > 0 && artifact.irVersion < COMPOSED_IR_VERSION) {
1563
+ ctx.addIssue({ code: "custom", path: ["irVersion"], message: "cross-namespace dependencies require IR v3" });
1564
+ }
1565
+ for (const [name2, operation] of Object.entries(artifact.operations)) {
1480
1566
  if (operation.irVersion > artifact.irVersion) {
1481
- ctx.addIssue({ code: "custom", path: ["operations", name, "irVersion"], message: "operation requires a newer IR version than its artifact" });
1567
+ ctx.addIssue({ code: "custom", path: ["operations", name2, "irVersion"], message: "operation requires a newer IR version than its artifact" });
1482
1568
  }
1483
1569
  }
1484
1570
  });
@@ -1491,20 +1577,20 @@ var IrVersionError = class extends Error {
1491
1577
  function normalizeIr(raw) {
1492
1578
  if (!raw || typeof raw !== "object") throw new IrVersionError(void 0);
1493
1579
  const version = raw.irVersion;
1494
- if (version !== LEGACY_IR_VERSION && version !== IR_VERSION) throw new IrVersionError(version);
1580
+ if (!irVersionSchema.safeParse(version).success) throw new IrVersionError(version);
1495
1581
  return compiledArtifactSchema.parse(raw);
1496
1582
  }
1497
1583
 
1498
1584
  // src/sdl/naming.ts
1499
- function camelCase(name) {
1500
- const segments = name.split("_").filter((segment) => segment.length > 0);
1585
+ function camelCase(name2) {
1586
+ const segments = name2.split("_").filter((segment) => segment.length > 0);
1501
1587
  const first = segments[0];
1502
- if (first === void 0) return name;
1588
+ if (first === void 0) return name2;
1503
1589
  const head = first.charAt(0).toLowerCase() + first.slice(1);
1504
1590
  return head + segments.slice(1).map(capitalizeFirst).join("");
1505
1591
  }
1506
- function pascalCase(name) {
1507
- return capitalizeFirst(camelCase(name));
1592
+ function pascalCase(name2) {
1593
+ return capitalizeFirst(camelCase(name2));
1508
1594
  }
1509
1595
  function capitalizeFirst(segment) {
1510
1596
  return segment.charAt(0).toUpperCase() + segment.slice(1);
@@ -1524,8 +1610,8 @@ function typeName(table) {
1524
1610
  if (last === void 0) return pascalCase(table);
1525
1611
  return [...segments.slice(0, -1), singularizeWord(last)].map(capitalizeFirst).join("");
1526
1612
  }
1527
- function enumTypeName(name) {
1528
- return pascalCase(name);
1613
+ function enumTypeName(name2) {
1614
+ return pascalCase(name2);
1529
1615
  }
1530
1616
  var SCALAR_FOR_COLUMN_TYPE = {
1531
1617
  uuid: "uuid",
@@ -2316,7 +2402,7 @@ var PolicyInjector = class {
2316
2402
  const updatePolicy = this.update(table);
2317
2403
  const where = andPred(updatePolicy, root.where ? this.injectDocPred(root.where, table) : void 0);
2318
2404
  if (root.op === "update" || root.op === "updateByPk") {
2319
- const check = predContainsExists(updatePolicy) ? {} : { insertCheck: updatePolicy };
2405
+ const check = predContainsExists(updatePolicy) && !this.ctx.composed ? {} : { insertCheck: updatePolicy };
2320
2406
  return { ...root, where, ...check, returning };
2321
2407
  }
2322
2408
  if (root.op === "cowOverride") {
@@ -2330,7 +2416,7 @@ var PolicyInjector = class {
2330
2416
  const pins = (this.ctx.pins.get(table) ?? []).map(
2331
2417
  (pin) => ({ column: pin.column, value: { kind: "claim", key: pin.claim } })
2332
2418
  );
2333
- const check = predContainsExists(updatePolicy) ? {} : { insertCheck: updatePolicy };
2419
+ const check = predContainsExists(updatePolicy) && !this.ctx.composed ? {} : { insertCheck: updatePolicy };
2334
2420
  return { ...root, pinnedColumns: pins, cowCopyCheck: insertPolicy, where, ...check, returning };
2335
2421
  }
2336
2422
  return { ...root, where, returning };
@@ -2481,16 +2567,92 @@ var NAME_KEY_BY_KIND = {
2481
2567
  function inputTypeName(table, kind) {
2482
2568
  return tableNames(table)[NAME_KEY_BY_KIND[kind]];
2483
2569
  }
2484
- function classifyInputType(name, tableByTypename) {
2570
+ function classifyInputType(name2, tableByTypename) {
2485
2571
  for (const [suffix, input] of INPUT_TYPE_SUFFIXES) {
2486
- if (!name.endsWith(suffix) || name.length <= suffix.length) continue;
2487
- const table = tableByTypename.get(name.slice(0, -suffix.length));
2572
+ if (!name2.endsWith(suffix) || name2.length <= suffix.length) continue;
2573
+ const table = tableByTypename.get(name2.slice(0, -suffix.length));
2488
2574
  if (table !== void 0) return { input, table };
2489
2575
  }
2490
2576
  return void 0;
2491
2577
  }
2492
2578
 
2579
+ // src/errors.ts
2580
+ var OrmDefinitionError = class extends Error {
2581
+ constructor(message, file) {
2582
+ super(file ? `${file}: ${message}` : message);
2583
+ this.file = file;
2584
+ }
2585
+ name = "OrmDefinitionError";
2586
+ };
2587
+
2588
+ // src/canonical/composition.ts
2589
+ function localContract(definition) {
2590
+ return normalizeCanonical(localDefinitionSchema.parse(definition));
2591
+ }
2592
+ function composeNamespace(definition) {
2593
+ const all = [localContract(definition), ...definition.imports ?? []];
2594
+ const owners = /* @__PURE__ */ new Map();
2595
+ const namespaces = /* @__PURE__ */ new Set();
2596
+ const enums = /* @__PURE__ */ new Map();
2597
+ for (const owner of all) {
2598
+ if (namespaces.has(owner.namespace)) throw new OrmDefinitionError(`duplicate imported namespace '${owner.namespace}'`);
2599
+ namespaces.add(owner.namespace);
2600
+ for (const value of owner.enums) {
2601
+ const previous = enums.get(value.name);
2602
+ if (previous && stableJson(previous) !== stableJson(value)) {
2603
+ throw new OrmDefinitionError(`conflicting imported enum '${value.name}'`);
2604
+ }
2605
+ enums.set(value.name, value);
2606
+ }
2607
+ for (const relation of canonicalRelations(owner)) {
2608
+ if (owners.has(relation.table)) throw new OrmDefinitionError(`ambiguous imported relation '${relation.table}'`);
2609
+ owners.set(relation.table, owner);
2610
+ }
2611
+ }
2612
+ for (const owner of definition.imports?.length ? all : []) {
2613
+ for (const relation of canonicalRelations(owner)) {
2614
+ for (const link of relation.links) {
2615
+ if (link.kind === "toPlatformUser") continue;
2616
+ const target = owners.get(link.target ?? "");
2617
+ if (!target || target.namespace !== (link.namespace ?? owner.namespace)) {
2618
+ throw new OrmDefinitionError(`link '${owner.namespace}/${relation.table}.${link.name}' has no matching imported target`);
2619
+ }
2620
+ if (target.namespace !== owner.namespace && !target.exports?.[link.target ?? ""]) {
2621
+ throw new OrmDefinitionError(`target '${target.namespace}/${link.target}' is not exported`);
2622
+ }
2623
+ }
2624
+ }
2625
+ }
2626
+ return {
2627
+ owners,
2628
+ definition: {
2629
+ ...localContract(definition),
2630
+ tables: all.flatMap((item) => item.tables),
2631
+ views: all.flatMap((item) => item.views ?? []),
2632
+ enums: [...enums.values()]
2633
+ }
2634
+ };
2635
+ }
2636
+
2493
2637
  // src/compiler-gql/context.ts
2638
+ function scopePolicy(pred, namespace) {
2639
+ switch (pred.kind) {
2640
+ case "capability":
2641
+ case "role":
2642
+ return { ...pred, namespace };
2643
+ case "and":
2644
+ case "or":
2645
+ return { ...pred, operands: pred.operands.map((p) => scopePolicy(p, namespace)) };
2646
+ case "not":
2647
+ return { ...pred, operand: scopePolicy(pred.operand, namespace) };
2648
+ case "exists":
2649
+ return { ...pred, where: scopePolicy(pred.where, namespace) };
2650
+ case "aggPredicate":
2651
+ return pred.where ? { ...pred, where: scopePolicy(pred.where, namespace) } : pred;
2652
+ default:
2653
+ return pred;
2654
+ }
2655
+ }
2494
2656
  function platformUserMeta() {
2495
2657
  const columns = [
2496
2658
  // `sub` is the view's key (auth0_id, filtered NOT NULL in the view).
@@ -2555,7 +2717,8 @@ function logicalColumn(entries, ref) {
2555
2717
  return entries.find((c) => c.name === ref || c.columnName === ref)?.name;
2556
2718
  }
2557
2719
  function buildCompileContext(rawCanonical) {
2558
- const canonical = normalizeCanonical(canonicalDefinitionSchema.parse(rawCanonical));
2720
+ const normalized = normalizeCanonical(canonicalDefinitionSchema.parse(rawCanonical));
2721
+ const { definition: canonical, owners } = composeNamespace(normalized);
2559
2722
  const diagnostics = [];
2560
2723
  const enums = new Map(canonical.enums.map((e) => [e.name, e]));
2561
2724
  const relations = canonicalRelations(canonical);
@@ -2568,17 +2731,18 @@ function buildCompileContext(rawCanonical) {
2568
2731
  const syncedTableNames = new Set(canonical.tables.filter((t) => t.source !== void 0).map((t) => t.table));
2569
2732
  const viewDependencies = new Map((canonical.views ?? []).map((view) => [view.view, view.dependencies]));
2570
2733
  const claimsGatedMemo = /* @__PURE__ */ new Map();
2571
- const relationIsClaimsGated = (name) => {
2572
- if (syncedTableNames.has(name)) return true;
2573
- const memo = claimsGatedMemo.get(name);
2734
+ const relationIsClaimsGated = (name2) => {
2735
+ if (syncedTableNames.has(name2)) return true;
2736
+ const memo = claimsGatedMemo.get(name2);
2574
2737
  if (memo !== void 0) return memo;
2575
- claimsGatedMemo.set(name, false);
2576
- const gated = (viewDependencies.get(name) ?? []).some(relationIsClaimsGated);
2577
- claimsGatedMemo.set(name, gated);
2738
+ claimsGatedMemo.set(name2, false);
2739
+ const gated = (viewDependencies.get(name2) ?? []).some(relationIsClaimsGated);
2740
+ claimsGatedMemo.set(name2, gated);
2578
2741
  return gated;
2579
2742
  };
2580
2743
  for (const table of relations) {
2581
2744
  const isView = viewNames.has(table.table);
2745
+ const owner = owners.get(table.table);
2582
2746
  const entries = resolveColumnEntries(table, enums, diagnostics);
2583
2747
  columnEntries.set(table.table, entries);
2584
2748
  const pkRefs = table.primaryKey ?? (isView ? [] : ["id"]);
@@ -2616,7 +2780,10 @@ function buildCompileContext(rawCanonical) {
2616
2780
  }
2617
2781
  tables[table.table] = {
2618
2782
  table: table.table,
2619
- schema: ormSchemaName(canonical.namespace),
2783
+ schema: ormSchemaName(owner.namespace),
2784
+ namespace: owner.namespace,
2785
+ ...viewDependencies.has(table.table) ? { dependencies: viewDependencies.get(table.table) } : {},
2786
+ ...owner.exports?.[table.table] ? { exportRead: owner.exports[table.table].read, exportWrite: owner.exports[table.table].write } : {},
2620
2787
  typename: typeNameForTable(table.table),
2621
2788
  // Synced twins (and views reading them, resolved transitively below)
2622
2789
  // are claims-gated — see tableMetaIrSchema.claimsGated.
@@ -2627,7 +2794,7 @@ function buildCompileContext(rawCanonical) {
2627
2794
  filterable: data.map((c) => c.name),
2628
2795
  orderable: data.filter((c) => c.type !== "jsonb").map((c) => c.name),
2629
2796
  insertable: resolved ? resolvedWritable(data) : isView ? [] : active.map((c) => c.name),
2630
- settable: resolved ? resolvedWritable(data).filter((name) => !primaryKey.includes(name)) : isView ? [] : active.filter((c) => !c.onUpdateFn).map((c) => c.name),
2797
+ settable: resolved ? resolvedWritable(data).filter((name2) => !primaryKey.includes(name2)) : isView ? [] : active.filter((c) => !c.onUpdateFn).map((c) => c.name),
2631
2798
  appendable: isView ? [] : active.filter((c) => c.type === "jsonb").map((c) => c.name),
2632
2799
  ...touchOnUpdate.length > 0 ? { touchOnUpdate } : {},
2633
2800
  ...cells ? { cells } : {},
@@ -2643,7 +2810,7 @@ function buildCompileContext(rawCanonical) {
2643
2810
  for (const link of table.links) {
2644
2811
  const targetName = link.kind === "toPlatformUser" ? PLATFORM_USER_TABLE : link.target;
2645
2812
  const target = targetName === void 0 ? void 0 : tables[targetName];
2646
- if (!targetName || !target) {
2813
+ if (!targetName || !target || link.namespace !== void 0 && link.namespace !== target.namespace) {
2647
2814
  diagnostics.push({
2648
2815
  severity: "error",
2649
2816
  code: "unknown-link-target",
@@ -2668,6 +2835,11 @@ function buildCompileContext(rawCanonical) {
2668
2835
  continue;
2669
2836
  }
2670
2837
  meta.links.push({ name: link.name, kind: link.kind, target: targetName, on, references });
2838
+ if (link.kind !== "toPlatformUser" && meta.namespace !== target.namespace) {
2839
+ const fkOwner = link.kind === "toMany" ? target : meta;
2840
+ const referenced = link.kind === "toMany" ? table.table : targetName;
2841
+ (fkOwner.foreignReferences ??= []).push({ column: on, target: referenced, targetColumn: references });
2842
+ }
2671
2843
  }
2672
2844
  }
2673
2845
  const readPolicies = /* @__PURE__ */ new Map();
@@ -2685,7 +2857,8 @@ function buildCompileContext(rawCanonical) {
2685
2857
  return;
2686
2858
  }
2687
2859
  try {
2688
- store.set(table.table, compilePolicy({ expression, table: table.table, resolver }));
2860
+ const pred = compilePolicy({ expression, table: table.table, resolver });
2861
+ store.set(table.table, normalized.imports?.length ? scopePolicy(pred, owners.get(table.table).namespace) : pred);
2689
2862
  } catch (error) {
2690
2863
  if (error instanceof PolicyCompileError || error instanceof PolicyParseError) {
2691
2864
  diagnostics.push({
@@ -2739,6 +2912,42 @@ function buildCompileContext(rawCanonical) {
2739
2912
  updatePolicies.set(PLATFORM_USER_TABLE, { kind: "const", value: false });
2740
2913
  pins.set(PLATFORM_USER_TABLE, []);
2741
2914
  }
2915
+ if (normalized.imports?.length) {
2916
+ const originalReads = new Map(readPolicies);
2917
+ const protect = (pred, table, path) => {
2918
+ switch (pred.kind) {
2919
+ case "and":
2920
+ case "or":
2921
+ return { ...pred, operands: pred.operands.map((p) => protect(p, table, path)) };
2922
+ case "not":
2923
+ return { ...pred, operand: protect(pred.operand, table, path) };
2924
+ case "exists":
2925
+ case "aggPredicate": {
2926
+ const target = tables[table]?.links.find((link) => link.name === pred.link)?.target;
2927
+ if (!target) return pred;
2928
+ let where = pred.where ? protect(pred.where, target, path) : CONST_TRUE;
2929
+ if (tables[table]?.namespace !== tables[target]?.namespace) {
2930
+ if (path.includes(target)) {
2931
+ diagnostics.push({
2932
+ severity: "error",
2933
+ code: "cyclic-cross-namespace-policy",
2934
+ table,
2935
+ message: `cross-namespace read policies form a cycle through '${target}'`
2936
+ });
2937
+ return CONST_FALSE;
2938
+ }
2939
+ where = andPred(where, protect(originalReads.get(target) ?? CONST_TRUE, target, [...path, target]));
2940
+ }
2941
+ return { ...pred, where };
2942
+ }
2943
+ default:
2944
+ return pred;
2945
+ }
2946
+ };
2947
+ for (const policies of [readPolicies, writePolicies, insertPolicies, updatePolicies]) {
2948
+ for (const [table, policy] of policies) policies.set(table, protect(policy, table, [table]));
2949
+ }
2950
+ }
2742
2951
  const tableByTypename = new Map(Object.entries(tables).map(([key, meta]) => [meta.typename, key]));
2743
2952
  const queryRoots = /* @__PURE__ */ new Map();
2744
2953
  const mutationRoots = /* @__PURE__ */ new Map();
@@ -2815,6 +3024,7 @@ function buildCompileContext(rawCanonical) {
2815
3024
  return {
2816
3025
  ctx: {
2817
3026
  namespace: canonical.namespace,
3027
+ composed: !!normalized.imports?.length,
2818
3028
  tables,
2819
3029
  tableByTypename,
2820
3030
  queryRoots,
@@ -2883,18 +3093,18 @@ function flattenSelectionSet(selectionSet, fragments, stack) {
2883
3093
  continue;
2884
3094
  }
2885
3095
  if (selection.kind === graphql.Kind.FRAGMENT_SPREAD) {
2886
- const name = selection.name.value;
3096
+ const name2 = selection.name.value;
2887
3097
  if (selection.directives && selection.directives.length > 0) {
2888
- throw new DocumentCompileError(`directives on fragment spread '...${name}' are not supported`, "fragment-directive");
3098
+ throw new DocumentCompileError(`directives on fragment spread '...${name2}' are not supported`, "fragment-directive");
2889
3099
  }
2890
- if (stack.includes(name)) {
2891
- throw new DocumentCompileError(`fragment cycle detected through '${name}'`, "fragment-cycle");
3100
+ if (stack.includes(name2)) {
3101
+ throw new DocumentCompileError(`fragment cycle detected through '${name2}'`, "fragment-cycle");
2892
3102
  }
2893
- const fragment = fragments.get(name);
3103
+ const fragment = fragments.get(name2);
2894
3104
  if (!fragment) {
2895
- throw new DocumentCompileError(`unknown fragment '${name}'`, "unknown-fragment");
3105
+ throw new DocumentCompileError(`unknown fragment '${name2}'`, "unknown-fragment");
2896
3106
  }
2897
- const inlined2 = flattenSelectionSet(fragment.selectionSet, fragments, [...stack, name]);
3107
+ const inlined2 = flattenSelectionSet(fragment.selectionSet, fragments, [...stack, name2]);
2898
3108
  selections.push(...inlined2.selections);
2899
3109
  continue;
2900
3110
  }
@@ -2934,40 +3144,40 @@ var SOURCE_DIRECTIVES = /* @__PURE__ */ new Set(["prefetch", "writeback", "local
2934
3144
  var fail = (message, code) => {
2935
3145
  throw new DocumentCompileError(message, code);
2936
3146
  };
2937
- function readArgs(node, name) {
2938
- return (node.directives ?? []).filter((directive) => directive.name.value === name).map((directive) => {
3147
+ function readArgs(node, name2) {
3148
+ return (node.directives ?? []).filter((directive) => directive.name.value === name2).map((directive) => {
2939
3149
  const args = {};
2940
3150
  for (const arg of directive.arguments ?? []) {
2941
3151
  const key = arg.name.value;
2942
3152
  if (key === "source" || key === "maxStaleness") {
2943
- if (arg.value.kind !== graphql.Kind.STRING) fail(`@${name}(${key}:) must be a string literal`, "invalid-source-directive");
3153
+ if (arg.value.kind !== graphql.Kind.STRING) fail(`@${name2}(${key}:) must be a string literal`, "invalid-source-directive");
2944
3154
  args[key] = arg.value.value;
2945
- } else if (key === "mode" && name === "writeback") {
3155
+ } else if (key === "mode" && name2 === "writeback") {
2946
3156
  if (arg.value.kind !== graphql.Kind.ENUM || !["SYNC", "ASYNC"].includes(arg.value.value)) {
2947
3157
  fail("@writeback(mode:) must be SYNC or ASYNC", "invalid-source-directive");
2948
3158
  }
2949
3159
  args.mode = arg.value.value === "SYNC" ? "sync" : "async";
2950
3160
  } else {
2951
- fail(`@${name} does not take argument '${key}'`, "invalid-source-directive");
3161
+ fail(`@${name2} does not take argument '${key}'`, "invalid-source-directive");
2952
3162
  }
2953
3163
  }
2954
- if (!args.source) fail(`@${name} needs a source: argument`, "invalid-source-directive");
3164
+ if (!args.source) fail(`@${name2} needs a source: argument`, "invalid-source-directive");
2955
3165
  return args;
2956
3166
  });
2957
3167
  }
2958
- function prefetchSourceFor({ resolved, name, directive, table }) {
3168
+ function prefetchSourceFor({ resolved, name: name2, directive, table }) {
2959
3169
  if (!resolved) return fail(`@${directive} is only valid on resolved tables \u2014 '${table}' is not one`, "invalid-source-directive");
2960
- const source = resolved.sources.find((entry) => entry.name === name);
3170
+ const source = resolved.sources.find((entry) => entry.name === name2);
2961
3171
  if (!source || source.kind !== "prefetch") {
2962
3172
  const options = resolved.sources.filter((entry) => entry.kind === "prefetch").map((entry) => entry.name);
2963
- return fail(`@${directive}(source: "${name}") on '${table}' must name a prefetch source (${options.join(", ") || "none declared"})`, "invalid-source-directive");
3173
+ return fail(`@${directive}(source: "${name2}") on '${table}' must name a prefetch source (${options.join(", ") || "none declared"})`, "invalid-source-directive");
2964
3174
  }
2965
3175
  return source;
2966
3176
  }
2967
3177
  function applyPrefetchDirectives({ node, root, meta }) {
2968
3178
  const directives = readArgs(node, "prefetch");
2969
- for (const name of ["writeback", "localOnly"]) {
2970
- if (readArgs(node, name).length > 0) fail(`@${name} is only valid on resolved-table mutation roots`, "invalid-source-directive");
3179
+ for (const name2 of ["writeback", "localOnly"]) {
3180
+ if (readArgs(node, name2).length > 0) fail(`@${name2} is only valid on resolved-table mutation roots`, "invalid-source-directive");
2971
3181
  }
2972
3182
  if (directives.length === 0) return root;
2973
3183
  if (root.kind !== "select" && root.kind !== "byPk") {
@@ -3128,13 +3338,13 @@ var OperationLowerer = class {
3128
3338
  return this.ctx.tables[table];
3129
3339
  }
3130
3340
  /** A data column — cell-metadata columns are reachable only through `cells { … }`. */
3131
- column(table, name) {
3341
+ column(table, name2) {
3132
3342
  const meta = this.meta(table);
3133
- const column = meta.columns.find((c) => c.name === name);
3134
- return column && !isCellColumn(meta, name) ? column : void 0;
3343
+ const column = meta.columns.find((c) => c.name === name2);
3344
+ return column && !isCellColumn(meta, name2) ? column : void 0;
3135
3345
  }
3136
- link(table, name) {
3137
- return this.meta(table).links.find((l) => l.name === name);
3346
+ link(table, name2) {
3347
+ return this.meta(table).links.find((l) => l.name === name2);
3138
3348
  }
3139
3349
  fieldCandidates(table) {
3140
3350
  const meta = this.meta(table);
@@ -3146,12 +3356,12 @@ var OperationLowerer = class {
3146
3356
  ];
3147
3357
  }
3148
3358
  /** Validate that a whole-variable argument is the declared §4f slot for this table. */
3149
- slot(name, input, table) {
3150
- const variable = this.variables.get(name);
3359
+ slot(name2, input, table) {
3360
+ const variable = this.variables.get(name2);
3151
3361
  if (!variable || variable.type.kind !== "inputRef" || variable.type.input !== input || variable.type.table !== table) {
3152
- fail2(`variable $${name} must be declared as the ${input} input type for table '${table}'`, "slot-mismatch");
3362
+ fail2(`variable $${name2} must be declared as the ${input} input type for table '${table}'`, "slot-mismatch");
3153
3363
  }
3154
- return name;
3364
+ return name2;
3155
3365
  }
3156
3366
  // -------------------------------------------------------------------------
3157
3367
  // Values
@@ -3327,13 +3537,13 @@ var OperationLowerer = class {
3327
3537
  });
3328
3538
  }
3329
3539
  lowerLimit(node) {
3330
- const base = { default: DEFAULT_LIMIT, max: MAX_LIMIT };
3331
- if (!node) return base;
3332
- if (node.kind === graphql.Kind.VARIABLE) return { ...base, variable: node.name.value };
3540
+ const base2 = { default: DEFAULT_LIMIT, max: MAX_LIMIT };
3541
+ if (!node) return base2;
3542
+ if (node.kind === graphql.Kind.VARIABLE) return { ...base2, variable: node.name.value };
3333
3543
  if (node.kind === graphql.Kind.INT) {
3334
3544
  const value = Number(node.value);
3335
3545
  if (value < 1) fail2("limit must be a positive integer", "invalid-limit");
3336
- return { ...base, value: Math.min(value, MAX_LIMIT) };
3546
+ return { ...base2, value: Math.min(value, MAX_LIMIT) };
3337
3547
  }
3338
3548
  fail2("limit must be an integer literal or a variable", "invalid-limit");
3339
3549
  }
@@ -3354,28 +3564,28 @@ var OperationLowerer = class {
3354
3564
  if (node.directives && node.directives.length > 0) {
3355
3565
  fail2(`@include/@skip are supported on root fields only ('${node.name.value}' is nested)`, "nested-directive");
3356
3566
  }
3357
- const name = node.name.value;
3358
- if (name === "__typename") continue;
3359
- const alias = node.alias?.value ?? name;
3360
- const column = this.column(table, name);
3567
+ const name2 = node.name.value;
3568
+ if (name2 === "__typename") continue;
3569
+ const alias = node.alias?.value ?? name2;
3570
+ const column = this.column(table, name2);
3361
3571
  if (column) {
3362
3572
  if (node.arguments !== void 0 && node.arguments.length > 0 || node.selectionSet !== void 0) {
3363
- fail2(`column '${table}.${name}' takes no arguments or sub-selection`, "invalid-selection");
3573
+ fail2(`column '${table}.${name2}' takes no arguments or sub-selection`, "invalid-selection");
3364
3574
  }
3365
- selection.columns.push({ alias, column: name });
3575
+ selection.columns.push({ alias, column: name2 });
3366
3576
  continue;
3367
3577
  }
3368
- if (name === CELLS_FIELD && meta.cells) {
3578
+ if (name2 === CELLS_FIELD && meta.cells) {
3369
3579
  if (selection.cells) fail2(`'${CELLS_FIELD}' may be selected once per object`, "invalid-selection");
3370
3580
  selection.cells = this.lowerCells(node, table, alias);
3371
3581
  continue;
3372
3582
  }
3373
- const link = this.link(table, name);
3583
+ const link = this.link(table, name2);
3374
3584
  if (link) {
3375
- if (!node.selectionSet) fail2(`link '${table}.${name}' needs a selection set`, "invalid-selection");
3585
+ if (!node.selectionSet) fail2(`link '${table}.${name2}' needs a selection set`, "invalid-selection");
3376
3586
  const relation = {
3377
3587
  alias,
3378
- link: name,
3588
+ link: name2,
3379
3589
  selection: this.lowerSelection(node.selectionSet, link.target)
3380
3590
  };
3381
3591
  if (link.kind === "toMany") {
@@ -3391,15 +3601,15 @@ var OperationLowerer = class {
3391
3601
  if (offset) relation.offset = offset;
3392
3602
  }
3393
3603
  } else if (node.arguments && node.arguments.length > 0) {
3394
- fail2(`to-one link '${table}.${name}' takes no arguments`, "invalid-selection");
3604
+ fail2(`to-one link '${table}.${name2}' takes no arguments`, "invalid-selection");
3395
3605
  }
3396
3606
  selection.relations.push(relation);
3397
3607
  continue;
3398
3608
  }
3399
- if (name.endsWith("Aggregate")) {
3400
- const aggLink = this.link(table, name.slice(0, -"Aggregate".length));
3609
+ if (name2.endsWith("Aggregate")) {
3610
+ const aggLink = this.link(table, name2.slice(0, -"Aggregate".length));
3401
3611
  if (aggLink && aggLink.kind === "toMany") {
3402
- if (!node.selectionSet) fail2(`'${name}' needs a selection set`, "invalid-selection");
3612
+ if (!node.selectionSet) fail2(`'${name2}' needs a selection set`, "invalid-selection");
3403
3613
  assertKnownArgs(node, ["where"]);
3404
3614
  const whereArg = argsByName(node).get("where");
3405
3615
  const aggregate = this.lowerAggregateSelection(node.selectionSet, aggLink.target);
@@ -3412,7 +3622,7 @@ var OperationLowerer = class {
3412
3622
  continue;
3413
3623
  }
3414
3624
  }
3415
- fail2(`unknown field '${name}' on '${table}'`, "unknown-field", this.fieldCandidates(table));
3625
+ fail2(`unknown field '${name2}' on '${table}'`, "unknown-field", this.fieldCandidates(table));
3416
3626
  }
3417
3627
  return selection;
3418
3628
  }
@@ -3468,13 +3678,13 @@ var OperationLowerer = class {
3468
3678
  const result = {};
3469
3679
  let sawAggregate = false;
3470
3680
  for (const node of aggregateBlockFields(selectionSet)) {
3471
- const name = node.name.value;
3472
- if (name === "nodes") {
3681
+ const name2 = node.name.value;
3682
+ if (name2 === "nodes") {
3473
3683
  if (!node.selectionSet) fail2("'nodes' needs a selection set", "invalid-aggregate");
3474
3684
  result.nodes = this.lowerSelection(node.selectionSet, table);
3475
3685
  continue;
3476
3686
  }
3477
- if (name !== "aggregate") fail2(`unknown aggregate field '${name}'`, "invalid-aggregate", ["aggregate", "nodes"]);
3687
+ if (name2 !== "aggregate") fail2(`unknown aggregate field '${name2}'`, "invalid-aggregate", ["aggregate", "nodes"]);
3478
3688
  if (sawAggregate) fail2("one aggregate block per field \u2014 alias the root instead", "invalid-aggregate");
3479
3689
  sawAggregate = true;
3480
3690
  if (!node.selectionSet) fail2("'aggregate' needs a selection set", "invalid-aggregate");
@@ -3495,18 +3705,18 @@ var OperationLowerer = class {
3495
3705
  let sawAggregate = false;
3496
3706
  let sawKey = false;
3497
3707
  for (const node of aggregateBlockFields(selectionSet)) {
3498
- const name = node.name.value;
3499
- if (name === "key") {
3708
+ const name2 = node.name.value;
3709
+ if (name2 === "key") {
3500
3710
  if (sawKey) fail2("one key block per grouped aggregate", "invalid-aggregate");
3501
3711
  sawKey = true;
3502
3712
  if (!node.selectionSet) fail2("'key' needs a selection set", "invalid-aggregate");
3503
3713
  result.key = this.lowerGroupKeySelection(node.selectionSet, table, groupColumns);
3504
3714
  continue;
3505
3715
  }
3506
- if (name === "nodes") {
3716
+ if (name2 === "nodes") {
3507
3717
  fail2("'nodes' is not available on a grouped aggregate \u2014 query the rows separately", "invalid-aggregate", ["key", "aggregate"]);
3508
3718
  }
3509
- if (name !== "aggregate") fail2(`unknown grouped-aggregate field '${name}'`, "invalid-aggregate", ["key", "aggregate"]);
3719
+ if (name2 !== "aggregate") fail2(`unknown grouped-aggregate field '${name2}'`, "invalid-aggregate", ["key", "aggregate"]);
3510
3720
  if (sawAggregate) fail2("one aggregate block per field \u2014 alias the root instead", "invalid-aggregate");
3511
3721
  sawAggregate = true;
3512
3722
  if (!node.selectionSet) fail2("'aggregate' needs a selection set", "invalid-aggregate");
@@ -3532,19 +3742,19 @@ var OperationLowerer = class {
3532
3742
  const key = [];
3533
3743
  for (const node of selectionSet.selections) {
3534
3744
  if (node.kind !== graphql.Kind.FIELD) fail2("fragments must be flattened before lowering", "internal");
3535
- const name = node.name.value;
3536
- if (name === "__typename") continue;
3537
- if (!this.column(table, name)) {
3538
- fail2(`unknown key column '${name}' on '${table}'`, "unknown-column", [...groupColumns]);
3745
+ const name2 = node.name.value;
3746
+ if (name2 === "__typename") continue;
3747
+ if (!this.column(table, name2)) {
3748
+ fail2(`unknown key column '${name2}' on '${table}'`, "unknown-column", [...groupColumns]);
3539
3749
  }
3540
- if (!groupColumns.has(name)) {
3750
+ if (!groupColumns.has(name2)) {
3541
3751
  fail2(
3542
- `key column '${name}' is not one of this query's groupBy keys \u2014 it has no single value within a group`,
3752
+ `key column '${name2}' is not one of this query's groupBy keys \u2014 it has no single value within a group`,
3543
3753
  "invalid-group-key",
3544
3754
  [...groupColumns]
3545
3755
  );
3546
3756
  }
3547
- key.push({ alias: node.alias?.value ?? name, column: name });
3757
+ key.push({ alias: node.alias?.value ?? name2, column: name2 });
3548
3758
  }
3549
3759
  return key;
3550
3760
  }
@@ -3640,35 +3850,35 @@ var OperationLowerer = class {
3640
3850
  let includeIf;
3641
3851
  let omit = false;
3642
3852
  for (const directive of node.directives ?? []) {
3643
- const name = directive.name.value;
3644
- if (name === "assertAffectedRows") continue;
3645
- if (SOURCE_DIRECTIVES.has(name)) continue;
3646
- if (name !== "include" && name !== "skip") {
3647
- fail2(`directive @${name} is not supported on fields`, "unknown-directive", ["include", "skip"]);
3853
+ const name2 = directive.name.value;
3854
+ if (name2 === "assertAffectedRows") continue;
3855
+ if (SOURCE_DIRECTIVES.has(name2)) continue;
3856
+ if (name2 !== "include" && name2 !== "skip") {
3857
+ fail2(`directive @${name2} is not supported on fields`, "unknown-directive", ["include", "skip"]);
3648
3858
  }
3649
3859
  const ifArg = directive.arguments?.find((a) => a.name.value === "if");
3650
- if (!ifArg) fail2(`@${name} needs an if: argument`, "invalid-directive");
3860
+ if (!ifArg) fail2(`@${name2} needs an if: argument`, "invalid-directive");
3651
3861
  if (ifArg.value.kind === graphql.Kind.BOOLEAN) {
3652
- const keep = name === "include" ? ifArg.value.value : !ifArg.value.value;
3862
+ const keep = name2 === "include" ? ifArg.value.value : !ifArg.value.value;
3653
3863
  if (!keep) omit = true;
3654
3864
  continue;
3655
3865
  }
3656
- if (ifArg.value.kind !== graphql.Kind.VARIABLE) fail2(`@${name}(if:) must be a boolean literal or variable`, "invalid-directive");
3866
+ if (ifArg.value.kind !== graphql.Kind.VARIABLE) fail2(`@${name2}(if:) must be a boolean literal or variable`, "invalid-directive");
3657
3867
  if (includeIf) fail2("at most one of @include/@skip per root field", "invalid-directive");
3658
- includeIf = { variable: ifArg.value.name.value, negated: name === "skip" };
3868
+ includeIf = { variable: ifArg.value.name.value, negated: name2 === "skip" };
3659
3869
  }
3660
3870
  return omit ? { omit: true } : { includeIf, omit: false };
3661
3871
  }
3662
3872
  lowerQueryRoot(node) {
3663
- const name = node.name.value;
3664
- const alias = node.alias?.value ?? name;
3665
- const rootInfo = this.ctx.queryRoots.get(name);
3666
- if (!rootInfo) fail2(`unknown query root '${name}'`, "unknown-root", [...this.ctx.queryRoots.keys()]);
3873
+ const name2 = node.name.value;
3874
+ const alias = node.alias?.value ?? name2;
3875
+ const rootInfo = this.ctx.queryRoots.get(name2);
3876
+ if (!rootInfo) fail2(`unknown query root '${name2}'`, "unknown-root", [...this.ctx.queryRoots.keys()]);
3667
3877
  const { table } = rootInfo;
3668
3878
  const args = argsByName(node);
3669
3879
  if (rootInfo.kind === "select") {
3670
3880
  assertKnownArgs(node, ["where", "orderBy", "distinctOn", "limit", "offset"]);
3671
- if (!node.selectionSet) fail2(`'${name}' needs a selection set`, "invalid-selection");
3881
+ if (!node.selectionSet) fail2(`'${name2}' needs a selection set`, "invalid-selection");
3672
3882
  const where = args.get("where");
3673
3883
  const orderBy = args.get("orderBy");
3674
3884
  const distinctOn = args.get("distinctOn");
@@ -3687,7 +3897,7 @@ var OperationLowerer = class {
3687
3897
  };
3688
3898
  }
3689
3899
  if (rootInfo.kind === "byPk") {
3690
- if (!node.selectionSet) fail2(`'${name}' needs a selection set`, "invalid-selection");
3900
+ if (!node.selectionSet) fail2(`'${name2}' needs a selection set`, "invalid-selection");
3691
3901
  const pk = this.lowerPkArgs(node, table);
3692
3902
  return { kind: "byPk", alias, table, selection: this.lowerSelection(node.selectionSet, table), pk, where: CONST_TRUE };
3693
3903
  }
@@ -3695,7 +3905,7 @@ var OperationLowerer = class {
3695
3905
  return this.lowerAggregateGroupsRoot(node, { alias, table });
3696
3906
  }
3697
3907
  assertKnownArgs(node, ["where"]);
3698
- if (!node.selectionSet) fail2(`'${name}' needs a selection set`, "invalid-selection");
3908
+ if (!node.selectionSet) fail2(`'${name2}' needs a selection set`, "invalid-selection");
3699
3909
  const whereArg = args.get("where");
3700
3910
  const aggregate = this.lowerAggregateSelection(node.selectionSet, table);
3701
3911
  return {
@@ -3798,13 +4008,13 @@ var OperationLowerer = class {
3798
4008
  }
3799
4009
  /** Like lowerLimit, but bounded by MAX_GROUPS and defaulting to it. */
3800
4010
  lowerGroupLimit(node) {
3801
- const base = { default: MAX_GROUPS, max: MAX_GROUPS };
3802
- if (!node) return base;
3803
- if (node.kind === graphql.Kind.VARIABLE) return { ...base, variable: node.name.value };
4011
+ const base2 = { default: MAX_GROUPS, max: MAX_GROUPS };
4012
+ if (!node) return base2;
4013
+ if (node.kind === graphql.Kind.VARIABLE) return { ...base2, variable: node.name.value };
3804
4014
  if (node.kind === graphql.Kind.INT) {
3805
4015
  const value = Number(node.value);
3806
4016
  if (value < 1) fail2("limit must be a positive integer", "invalid-limit");
3807
- return { ...base, value: Math.min(value, MAX_GROUPS) };
4017
+ return { ...base2, value: Math.min(value, MAX_GROUPS) };
3808
4018
  }
3809
4019
  fail2("limit must be an integer literal or a variable", "invalid-limit");
3810
4020
  }
@@ -3823,10 +4033,10 @@ var OperationLowerer = class {
3823
4033
  // Mutations
3824
4034
  // -------------------------------------------------------------------------
3825
4035
  lowerMutationRoot(node) {
3826
- const name = node.name.value;
3827
- const alias = node.alias?.value ?? name;
3828
- const rootInfo = this.ctx.mutationRoots.get(name);
3829
- if (!rootInfo) fail2(`unknown mutation root '${name}'`, "unknown-root", [...this.ctx.mutationRoots.keys()]);
4036
+ const name2 = node.name.value;
4037
+ const alias = node.alias?.value ?? name2;
4038
+ const rootInfo = this.ctx.mutationRoots.get(name2);
4039
+ if (!rootInfo) fail2(`unknown mutation root '${name2}'`, "unknown-root", [...this.ctx.mutationRoots.keys()]);
3830
4040
  if (rootInfo.op === "cowOverride" || rootInfo.op === "cowRevert") {
3831
4041
  return this.lowerCowRoot(node, rootInfo);
3832
4042
  }
@@ -3835,16 +4045,16 @@ var OperationLowerer = class {
3835
4045
  }
3836
4046
  const { op, table } = rootInfo;
3837
4047
  const args = argsByName(node);
3838
- if (!node.selectionSet) fail2(`'${name}' needs a selection set`, "invalid-selection");
3839
- const base = { kind: "mutation", op, alias, table, pinnedColumns: [], affectedRows: false };
4048
+ if (!node.selectionSet) fail2(`'${name2}' needs a selection set`, "invalid-selection");
4049
+ const base2 = { kind: "mutation", op, alias, table, pinnedColumns: [], affectedRows: false };
3840
4050
  switch (op) {
3841
4051
  case "insert": {
3842
4052
  assertKnownArgs(node, ["objects", "onConflict"]);
3843
4053
  const objects = args.get("objects");
3844
- if (!objects) fail2(`'${name}' needs objects:`, "missing-argument");
4054
+ if (!objects) fail2(`'${name2}' needs objects:`, "missing-argument");
3845
4055
  const response = this.lowerMutationResponse(node.selectionSet, table);
3846
4056
  return {
3847
- ...base,
4057
+ ...base2,
3848
4058
  ...response,
3849
4059
  objects: this.lowerInsertObjects(objects.value, table, { list: true }),
3850
4060
  ...this.lowerOnConflictArg(args.get("onConflict"), table)
@@ -3853,9 +4063,9 @@ var OperationLowerer = class {
3853
4063
  case "insertOne": {
3854
4064
  assertKnownArgs(node, ["object", "onConflict"]);
3855
4065
  const object = args.get("object");
3856
- if (!object) fail2(`'${name}' needs object:`, "missing-argument");
4066
+ if (!object) fail2(`'${name2}' needs object:`, "missing-argument");
3857
4067
  return {
3858
- ...base,
4068
+ ...base2,
3859
4069
  objects: this.lowerInsertObjects(object.value, table, { list: false }),
3860
4070
  ...this.lowerOnConflictArg(args.get("onConflict"), table),
3861
4071
  returning: this.lowerSelection(node.selectionSet, table)
@@ -3864,9 +4074,9 @@ var OperationLowerer = class {
3864
4074
  case "update": {
3865
4075
  assertKnownArgs(node, ["where", "_set", "_append", "_inc"]);
3866
4076
  const where = args.get("where");
3867
- if (!where) fail2(`'${name}' needs where: (bulk updates without a filter are not allowed)`, "missing-argument");
4077
+ if (!where) fail2(`'${name2}' needs where: (bulk updates without a filter are not allowed)`, "missing-argument");
3868
4078
  return {
3869
- ...base,
4079
+ ...base2,
3870
4080
  ...this.lowerMutationResponse(node.selectionSet, table),
3871
4081
  where: this.lowerBoolExp(where.value, table),
3872
4082
  ...this.lowerWriteValues(args, table)
@@ -3875,9 +4085,9 @@ var OperationLowerer = class {
3875
4085
  case "updateByPk": {
3876
4086
  assertKnownArgs(node, ["pkColumns", "_set", "_append", "_inc"]);
3877
4087
  const pkColumns = args.get("pkColumns");
3878
- if (!pkColumns) fail2(`'${name}' needs pkColumns:`, "missing-argument");
4088
+ if (!pkColumns) fail2(`'${name2}' needs pkColumns:`, "missing-argument");
3879
4089
  return {
3880
- ...base,
4090
+ ...base2,
3881
4091
  pk: this.lowerPkColumnsArg(pkColumns.value, table),
3882
4092
  ...this.lowerWriteValues(args, table),
3883
4093
  returning: this.lowerSelection(node.selectionSet, table)
@@ -3886,16 +4096,16 @@ var OperationLowerer = class {
3886
4096
  case "delete": {
3887
4097
  assertKnownArgs(node, ["where"]);
3888
4098
  const where = args.get("where");
3889
- if (!where) fail2(`'${name}' needs where: (bulk deletes without a filter are not allowed)`, "missing-argument");
4099
+ if (!where) fail2(`'${name2}' needs where: (bulk deletes without a filter are not allowed)`, "missing-argument");
3890
4100
  return {
3891
- ...base,
4101
+ ...base2,
3892
4102
  ...this.lowerMutationResponse(node.selectionSet, table),
3893
4103
  where: this.lowerBoolExp(where.value, table)
3894
4104
  };
3895
4105
  }
3896
4106
  case "deleteByPk": {
3897
4107
  return {
3898
- ...base,
4108
+ ...base2,
3899
4109
  pk: this.lowerPkArgs(node, table),
3900
4110
  returning: this.lowerSelection(node.selectionSet, table)
3901
4111
  };
@@ -3910,16 +4120,16 @@ var OperationLowerer = class {
3910
4120
  * the structured fields, never from authored statements.
3911
4121
  */
3912
4122
  lowerCowRoot(node, rootInfo) {
3913
- const name = node.name.value;
3914
- const alias = node.alias?.value ?? name;
4123
+ const name2 = node.name.value;
4124
+ const alias = node.alias?.value ?? name2;
3915
4125
  const { table } = rootInfo;
3916
- if (!node.selectionSet) fail2(`'${name}' needs a selection set`, "invalid-selection");
4126
+ if (!node.selectionSet) fail2(`'${name2}' needs a selection set`, "invalid-selection");
3917
4127
  const meta = this.meta(table);
3918
4128
  const args = argsByName(node);
3919
4129
  const pkArg = args.get(rootInfo.pk);
3920
- if (!pkArg) fail2(`'${name}' is missing primary-key argument '${rootInfo.pk}'`, "missing-pk-argument", [rootInfo.pk]);
4130
+ if (!pkArg) fail2(`'${name2}' is missing primary-key argument '${rootInfo.pk}'`, "missing-pk-argument", [rootInfo.pk]);
3921
4131
  const pk = [{ column: rootInfo.pk, value: this.lowerValue(pkArg.value) }];
3922
- const base = {
4132
+ const base2 = {
3923
4133
  kind: "mutation",
3924
4134
  op: rootInfo.op,
3925
4135
  alias,
@@ -3932,14 +4142,14 @@ var OperationLowerer = class {
3932
4142
  };
3933
4143
  if (rootInfo.op === "cowRevert") {
3934
4144
  assertKnownArgs(node, [rootInfo.pk]);
3935
- return base;
4145
+ return base2;
3936
4146
  }
3937
4147
  assertKnownArgs(node, [rootInfo.pk, ...rootInfo.set]);
3938
4148
  const entries = rootInfo.set.map((column) => {
3939
4149
  const arg = args.get(column);
3940
4150
  if (!arg) {
3941
4151
  fail2(
3942
- `'${name}' is missing set argument '${column}' \u2014 a cow override writes every declared set column (pass null to clear a nullable one)`,
4152
+ `'${name2}' is missing set argument '${column}' \u2014 a cow override writes every declared set column (pass null to clear a nullable one)`,
3943
4153
  "missing-argument",
3944
4154
  [...rootInfo.set]
3945
4155
  );
@@ -3949,7 +4159,7 @@ var OperationLowerer = class {
3949
4159
  }
3950
4160
  return { column, value: this.lowerValue(arg.value) };
3951
4161
  });
3952
- return { ...base, set: { entries }, cowCopyColumns: [...rootInfo.copyColumns] };
4162
+ return { ...base2, set: { entries }, cowCopyColumns: [...rootInfo.copyColumns] };
3953
4163
  }
3954
4164
  /**
3955
4165
  * Resolved-table roots: insertOne(object), updateByPk(pkColumns, _set),
@@ -3957,12 +4167,12 @@ var OperationLowerer = class {
3957
4167
  * the touched cells into the manual source; `returning` reads the view.
3958
4168
  */
3959
4169
  lowerResolvedRoot(node, rootInfo) {
3960
- const name = node.name.value;
3961
- const alias = node.alias?.value ?? name;
4170
+ const name2 = node.name.value;
4171
+ const alias = node.alias?.value ?? name2;
3962
4172
  const { table, action } = rootInfo;
3963
- if (!node.selectionSet) fail2(`'${name}' needs a selection set`, "invalid-selection");
4173
+ if (!node.selectionSet) fail2(`'${name2}' needs a selection set`, "invalid-selection");
3964
4174
  const args = argsByName(node);
3965
- const base = {
4175
+ const base2 = {
3966
4176
  kind: "mutation",
3967
4177
  op: "resolvedWrite",
3968
4178
  resolvedAction: action,
@@ -3976,32 +4186,32 @@ var OperationLowerer = class {
3976
4186
  case "insert": {
3977
4187
  assertKnownArgs(node, ["object"]);
3978
4188
  const object = args.get("object");
3979
- if (!object) fail2(`'${name}' needs object:`, "missing-argument");
3980
- return { ...base, objects: this.lowerInsertObjects(object.value, table, { list: false }) };
4189
+ if (!object) fail2(`'${name2}' needs object:`, "missing-argument");
4190
+ return { ...base2, objects: this.lowerInsertObjects(object.value, table, { list: false }) };
3981
4191
  }
3982
4192
  case "update": {
3983
4193
  assertKnownArgs(node, ["pkColumns", "_set"]);
3984
4194
  const pkColumns = args.get("pkColumns");
3985
- if (!pkColumns || !args.get("_set")) fail2(`'${name}' needs pkColumns: and _set:`, "missing-argument");
3986
- return { ...base, pk: this.lowerPkColumnsArg(pkColumns.value, table), ...this.lowerWriteValues(args, table) };
4195
+ if (!pkColumns || !args.get("_set")) fail2(`'${name2}' needs pkColumns: and _set:`, "missing-argument");
4196
+ return { ...base2, pk: this.lowerPkColumnsArg(pkColumns.value, table), ...this.lowerWriteValues(args, table) };
3987
4197
  }
3988
4198
  case "delete":
3989
- return { ...base, pk: this.lowerPkArgs(node, table) };
4199
+ return { ...base2, pk: this.lowerPkArgs(node, table) };
3990
4200
  case "revert": {
3991
4201
  assertKnownArgs(node, ["pkColumns", "columns"]);
3992
4202
  const pkColumns = args.get("pkColumns");
3993
4203
  const columns = args.get("columns");
3994
- if (!pkColumns || !columns) fail2(`'${name}' needs pkColumns: and columns:`, "missing-argument");
4204
+ if (!pkColumns || !columns) fail2(`'${name2}' needs pkColumns: and columns:`, "missing-argument");
3995
4205
  const items = columns.value.kind === graphql.Kind.LIST ? columns.value.values : [columns.value];
3996
4206
  const meta = this.meta(table);
3997
4207
  const revertColumns = items.map((item) => {
3998
4208
  if (item.kind !== graphql.Kind.ENUM || !meta.settable.includes(item.value)) {
3999
- fail2(`'${name}' columns: must list update-column enum values written in the document`, "invalid-revert-columns", meta.settable);
4209
+ fail2(`'${name2}' columns: must list update-column enum values written in the document`, "invalid-revert-columns", meta.settable);
4000
4210
  }
4001
4211
  return item.value;
4002
4212
  });
4003
- if (revertColumns.length === 0) fail2(`'${name}' columns: must name at least one column`, "invalid-revert-columns");
4004
- return { ...base, pk: this.lowerPkColumnsArg(pkColumns.value, table), revertColumns: [...new Set(revertColumns)] };
4213
+ if (revertColumns.length === 0) fail2(`'${name2}' columns: must name at least one column`, "invalid-revert-columns");
4214
+ return { ...base2, pk: this.lowerPkColumnsArg(pkColumns.value, table), revertColumns: [...new Set(revertColumns)] };
4005
4215
  }
4006
4216
  }
4007
4217
  }
@@ -4010,24 +4220,24 @@ var OperationLowerer = class {
4010
4220
  let returning = null;
4011
4221
  for (const node of selectionSet.selections) {
4012
4222
  if (node.kind !== graphql.Kind.FIELD) fail2("fragments must be flattened before lowering", "internal");
4013
- const name = node.name.value;
4014
- if (name === "__typename") continue;
4015
- if (name === "affectedRows") {
4223
+ const name2 = node.name.value;
4224
+ if (name2 === "__typename") continue;
4225
+ if (name2 === "affectedRows") {
4016
4226
  affectedRows = true;
4017
4227
  continue;
4018
4228
  }
4019
- if (name === "returning") {
4229
+ if (name2 === "returning") {
4020
4230
  if (!node.selectionSet) fail2("'returning' needs a selection set", "invalid-selection");
4021
4231
  returning = this.lowerSelection(node.selectionSet, table);
4022
4232
  continue;
4023
4233
  }
4024
- fail2(`unknown mutation-response field '${name}'`, "invalid-selection", ["affectedRows", "returning"]);
4234
+ fail2(`unknown mutation-response field '${name2}'`, "invalid-selection", ["affectedRows", "returning"]);
4025
4235
  }
4026
4236
  return { affectedRows, returning };
4027
4237
  }
4028
- lowerInsertObjects(node, table, { list }) {
4238
+ lowerInsertObjects(node, table, { list: list2 }) {
4029
4239
  if (node.kind === graphql.Kind.VARIABLE) return { slot: this.slot(node.name.value, "insertInput", table) };
4030
- if (list) {
4240
+ if (list2) {
4031
4241
  if (node.kind !== graphql.Kind.LIST) fail2(`objects: must be a list literal or a variable`, "invalid-insert");
4032
4242
  return { rows: node.values.map((item) => this.lowerInsertRow(item, table)) };
4033
4243
  }
@@ -4134,9 +4344,9 @@ function lowerOperation({ restricted, ctx }) {
4134
4344
  const lowerer = new OperationLowerer(ctx, restricted);
4135
4345
  return lowerer.lowerRoots(restricted.definition, restricted.kind);
4136
4346
  }
4137
- function isCellColumn(meta, name) {
4347
+ function isCellColumn(meta, name2) {
4138
4348
  if (!meta.cells) return false;
4139
- return Object.values(meta.cells).some((parts) => Object.values(parts).includes(name));
4349
+ return Object.values(meta.cells).some((parts) => Object.values(parts).includes(name2));
4140
4350
  }
4141
4351
  function fieldNodes(selectionSet) {
4142
4352
  const out = [];
@@ -4193,7 +4403,7 @@ function parseDocuments({
4193
4403
  }
4194
4404
  function unwrapType(node, variable) {
4195
4405
  let nullable = true;
4196
- let list = false;
4406
+ let list2 = false;
4197
4407
  let current = node;
4198
4408
  for (; ; ) {
4199
4409
  if (current.kind === graphql.Kind.NON_NULL_TYPE) {
@@ -4202,17 +4412,17 @@ function unwrapType(node, variable) {
4202
4412
  continue;
4203
4413
  }
4204
4414
  if (current.kind === graphql.Kind.LIST_TYPE) {
4205
- if (list) {
4415
+ if (list2) {
4206
4416
  throw new DocumentCompileError(`variable $${variable}: nested list types are not supported`, "unsupported-variable-type");
4207
4417
  }
4208
- list = true;
4418
+ list2 = true;
4209
4419
  current = current.type;
4210
4420
  continue;
4211
4421
  }
4212
- return { named: current.name.value, list, nullable };
4422
+ return { named: current.name.value, list: list2, nullable };
4213
4423
  }
4214
4424
  }
4215
- function classifyVariableType({ named, list }, variable, ctx) {
4425
+ function classifyVariableType({ named, list: list2 }, variable, ctx) {
4216
4426
  const inputRef = classifyInputType(named, ctx.tableByTypename);
4217
4427
  if (inputRef) {
4218
4428
  const listable = {
@@ -4225,13 +4435,13 @@ function classifyVariableType({ named, list }, variable, ctx) {
4225
4435
  insertInput: "either"
4226
4436
  };
4227
4437
  const rule = listable[inputRef.input];
4228
- if (rule === "must" && !list) {
4438
+ if (rule === "must" && !list2) {
4229
4439
  throw new DocumentCompileError(
4230
4440
  `variable $${variable}: ${named} must be used as a list (e.g. [${named}!])`,
4231
4441
  "unsupported-variable-type"
4232
4442
  );
4233
4443
  }
4234
- if (rule === "never" && list) {
4444
+ if (rule === "never" && list2) {
4235
4445
  throw new DocumentCompileError(
4236
4446
  `variable $${variable}: ${named} cannot be used as a list`,
4237
4447
  "unsupported-variable-type"
@@ -4268,7 +4478,7 @@ function restrictDefault({
4268
4478
  node,
4269
4479
  variable,
4270
4480
  type,
4271
- list,
4481
+ list: list2,
4272
4482
  nullable
4273
4483
  }) {
4274
4484
  if (type.kind === "inputRef") {
@@ -4288,7 +4498,7 @@ function restrictDefault({
4288
4498
  return { value: null };
4289
4499
  }
4290
4500
  if (Array.isArray(value)) {
4291
- if (!list) {
4501
+ if (!list2) {
4292
4502
  throw new DocumentCompileError(
4293
4503
  `variable $${variable}: default for a non-list variable must not be a list`,
4294
4504
  "unsupported-variable-default"
@@ -4296,7 +4506,7 @@ function restrictDefault({
4296
4506
  }
4297
4507
  return { value: value.map((item) => restrictDefaultLiteral(item, variable, type)) };
4298
4508
  }
4299
- if (list) {
4509
+ if (list2) {
4300
4510
  throw new DocumentCompileError(
4301
4511
  `variable $${variable}: default for a list variable must be a list`,
4302
4512
  "unsupported-variable-default"
@@ -4331,8 +4541,8 @@ function restrictOperation({
4331
4541
  if (operation.operation === "subscription") {
4332
4542
  throw new DocumentCompileError("subscriptions are not supported in v2.0 \u2014 poll a query instead", "subscription-unsupported");
4333
4543
  }
4334
- const name = operation.name?.value;
4335
- if (!name) {
4544
+ const name2 = operation.name?.value;
4545
+ if (!name2) {
4336
4546
  throw new DocumentCompileError(
4337
4547
  "anonymous operations are not supported \u2014 every operation must be named (the name keys the registry)",
4338
4548
  "anonymous-operation"
@@ -4341,14 +4551,14 @@ function restrictOperation({
4341
4551
  const variables = (operation.variableDefinitions ?? []).map((definition) => {
4342
4552
  const variable = definition.variable.name.value;
4343
4553
  const unwrapped = unwrapType(definition.type, variable);
4344
- const { list, nullable } = unwrapped;
4554
+ const { list: list2, nullable } = unwrapped;
4345
4555
  const type = classifyVariableType(unwrapped, variable, ctx);
4346
- const ir = { name: variable, type, nullable, list };
4556
+ const ir = { name: variable, type, nullable, list: list2 };
4347
4557
  if (definition.defaultValue === void 0) return ir;
4348
- return { ...ir, defaultValue: restrictDefault({ node: definition.defaultValue, variable, type, list, nullable }) };
4558
+ return { ...ir, defaultValue: restrictDefault({ node: definition.defaultValue, variable, type, list: list2, nullable }) };
4349
4559
  });
4350
4560
  return {
4351
- name,
4561
+ name: name2,
4352
4562
  kind: operation.operation === "mutation" ? "mutation" : "query",
4353
4563
  requires: parseRequires(operation),
4354
4564
  variables,
@@ -4386,8 +4596,20 @@ function compileNamespace({ canonical, sdl, documents }) {
4386
4596
  }
4387
4597
  const loweredRoots = lowerOperation({ restricted: { ...restricted, definition: flattened }, ctx });
4388
4598
  const { roots, tableSet } = injectPolicies({ roots: loweredRoots, ctx });
4599
+ for (const table of tableSet) {
4600
+ const meta = ctx.tables[table];
4601
+ if (meta.namespace && meta.namespace !== ctx.namespace && !meta.exportRead) {
4602
+ throw new DocumentCompileError(`object '${meta.namespace}/${table}' is not exported for reads`, "unexported-object");
4603
+ }
4604
+ }
4605
+ for (const root of roots) {
4606
+ const meta = ctx.tables[root.table];
4607
+ if (root.kind === "mutation" && meta.namespace && meta.namespace !== ctx.namespace && !meta.exportWrite) {
4608
+ throw new DocumentCompileError(`object '${meta.namespace}/${root.table}' is not exported for writes`, "unexported-object");
4609
+ }
4610
+ }
4389
4611
  operations[restricted.name] = {
4390
- irVersion: roots.some((root) => root.kind === "mutation" && root.assertAffectedRows !== void 0) ? IR_VERSION : LEGACY_IR_VERSION,
4612
+ irVersion: canonical.imports?.length ? COMPOSED_IR_VERSION : roots.some((root) => root.kind === "mutation" && root.assertAffectedRows !== void 0) ? GUARDED_IR_VERSION : LEGACY_IR_VERSION,
4391
4613
  name: restricted.name,
4392
4614
  opHash: computeOpHash({ definition: flattened, schemaHash }),
4393
4615
  kind: restricted.kind,
@@ -4414,9 +4636,14 @@ function compileNamespace({ canonical, sdl, documents }) {
4414
4636
  }
4415
4637
  if (diagnostics.some((d) => d.severity === "error")) return { ok: false, diagnostics };
4416
4638
  const artifact = {
4417
- irVersion: requiredArtifactIrVersion(Object.values(operations)),
4639
+ irVersion: canonical.imports?.length ? COMPOSED_IR_VERSION : requiredArtifactIrVersion(Object.values(operations)),
4418
4640
  namespace: ctx.namespace,
4419
4641
  schemaHash,
4642
+ ownSchemaHash: computeSchemaHash(localContract(canonical)),
4643
+ ...canonical.imports?.length ? {
4644
+ dependencies: Object.fromEntries(canonical.imports.map((owner) => [owner.namespace, computeSchemaHash(localContract(owner))]))
4645
+ } : {},
4646
+ readPolicies: Object.fromEntries(ctx.readPolicies),
4420
4647
  opsHash: computeOpsHash(Object.values(operations).map((op) => op.opHash)),
4421
4648
  tables: ctx.tables,
4422
4649
  operations
@@ -4479,9 +4706,10 @@ function allowedOps(type) {
4479
4706
  case "text":
4480
4707
  return ["eq", "neq", "gt", "gte", "lt", "lte", "in", "nin", "like", "ilike", "isNull"];
4481
4708
  case "uuid":
4482
- return ["eq", "neq", "in", "nin", "isNull"];
4709
+ return ["eq", "neq", "in", "nin", "like", "ilike", "isNull"];
4483
4710
  case "integer":
4484
4711
  case "bigint":
4712
+ return ["eq", "neq", "gt", "gte", "lt", "lte", "in", "nin", "like", "ilike", "isNull"];
4485
4713
  case "numeric":
4486
4714
  case "double":
4487
4715
  case "timestamptz":
@@ -4699,12 +4927,12 @@ function validateSelectColumns({ table, value }) {
4699
4927
  function validateInsertInput({
4700
4928
  table,
4701
4929
  value,
4702
- list,
4930
+ list: list2,
4703
4931
  maxRows
4704
4932
  }) {
4705
4933
  const collector = new IssueCollector();
4706
4934
  const rowCap = maxRows ?? 1e3;
4707
- const rows = list ? value : [value];
4935
+ const rows = list2 ? value : [value];
4708
4936
  if (!Array.isArray(rows)) {
4709
4937
  collector.add("invalid-shape", "$", "objects must be an array of rows");
4710
4938
  return { ok: false, issues: collector.issues };
@@ -4715,7 +4943,7 @@ function validateInsertInput({
4715
4943
  }
4716
4944
  const normalized = [];
4717
4945
  rows.forEach((row, index) => {
4718
- const path = list ? `$[${index}]` : "$";
4946
+ const path = list2 ? `$[${index}]` : "$";
4719
4947
  if (!isPlainObject(row)) {
4720
4948
  collector.add("invalid-shape", path, "insert rows must be objects");
4721
4949
  return;
@@ -4825,8 +5053,747 @@ function validateOnConflictInput({
4825
5053
  return finish(collector, { constraint, updateColumns });
4826
5054
  }
4827
5055
 
5056
+ // src/engine/types.ts
5057
+ var SQL_TYPE_NAMES = {
5058
+ double: "double precision",
5059
+ timestamptz: "timestamp with time zone"
5060
+ };
5061
+ var sqlTypeName = (type) => SQL_TYPE_NAMES[type] ?? type;
5062
+
5063
+ // src/compiler/pred-ir-sql.ts
5064
+ function compilePredIrToSql(pred, options = {}) {
5065
+ switch (pred.kind) {
5066
+ case "const":
5067
+ return pred.value ? "TRUE" : "FALSE";
5068
+ case "and":
5069
+ return `(${pred.operands.map((operand) => compilePredIrToSql(operand, options)).join(" AND ")})`;
5070
+ case "or":
5071
+ return `(${pred.operands.map((operand) => compilePredIrToSql(operand, options)).join(" OR ")})`;
5072
+ case "not":
5073
+ return `(NOT ${compilePredIrToSql(pred.operand, options)})`;
5074
+ case "cmp":
5075
+ return compileCmp(pred.op, pred.column, pred.value, options);
5076
+ case "capability":
5077
+ return `(${capabilityExpression(pred.key, pred.namespace ?? options.namespace)})`;
5078
+ case "role":
5079
+ return `(${roleExpression(pred.key, pred.namespace ?? options.namespace)})`;
5080
+ case "exists":
5081
+ if (options.exists) return options.exists(pred);
5082
+ throw new OrmDefinitionError("synced view read policy cannot use 'exists' without a relationship resolver");
5083
+ case "aggPredicate":
5084
+ case "dynamicFilter":
5085
+ throw new OrmDefinitionError(
5086
+ `synced view read policy cannot use '${pred.kind}' \u2014 use row filters and claim/capability/role checks only`
5087
+ );
5088
+ default: {
5089
+ const exhausted = pred;
5090
+ throw new OrmDefinitionError(`unsupported synced view predicate kind '${String(exhausted)}'`);
5091
+ }
5092
+ }
5093
+ }
5094
+ function physicalColumn(logical, options) {
5095
+ return options.columnNames?.[logical] ?? logical;
5096
+ }
5097
+ function compileCmp(op, column, value, options) {
5098
+ const physical = physicalColumn(column, options);
5099
+ const declared = options.columnTypes?.[physical] ?? options.columnTypes?.[column];
5100
+ const isEnum = typeof declared === "object" && declared !== null;
5101
+ const qualified = options.alias ? `${quoteIdentifier(options.alias)}.${quoteIdentifier(physical)}` : quoteIdentifier(physical);
5102
+ const col = isEnum ? `${qualified}::text` : qualified;
5103
+ const columnType = isEnum ? "text" : declared;
5104
+ switch (op) {
5105
+ case "eq":
5106
+ return compileEquality(col, value, columnType, false);
5107
+ case "neq":
5108
+ return compileEquality(col, value, columnType, true);
5109
+ case "gt":
5110
+ return `${col} > ${compileValue(value, columnType)}`;
5111
+ case "gte":
5112
+ return `${col} >= ${compileValue(value, columnType)}`;
5113
+ case "lt":
5114
+ return `${col} < ${compileValue(value, columnType)}`;
5115
+ case "lte":
5116
+ return `${col} <= ${compileValue(value, columnType)}`;
5117
+ case "in":
5118
+ return `${col} = ANY(${compileArrayValue(value, columnType)})`;
5119
+ case "nin":
5120
+ return `${col} <> ALL(${compileArrayValue(value, columnType)})`;
5121
+ case "like":
5122
+ return `${col} LIKE ${compileValue(value, columnType)}`;
5123
+ case "ilike":
5124
+ return `${col} ILIKE ${compileValue(value, columnType)}`;
5125
+ case "isNull":
5126
+ return value.kind === "literal" && value.value === true ? `${col} IS NULL` : `${col} IS NOT NULL`;
5127
+ case "contains":
5128
+ case "hasKey":
5129
+ throw new OrmDefinitionError(`synced view read policy cannot use json operator '${op}'`);
5130
+ default: {
5131
+ const exhausted = op;
5132
+ throw new OrmDefinitionError(`unsupported synced view comparison '${String(exhausted)}'`);
5133
+ }
5134
+ }
5135
+ }
5136
+ function compileEquality(col, value, columnType, negated, options) {
5137
+ if (value.kind === "literal" && value.value === null) {
5138
+ return negated ? `${col} IS NOT NULL` : `${col} IS NULL`;
5139
+ }
5140
+ const rhs = compileValue(value, columnType);
5141
+ return negated ? `${col} <> ${rhs}` : `${col} = ${rhs}`;
5142
+ }
5143
+ function compileValue(value, columnType, _options) {
5144
+ switch (value.kind) {
5145
+ case "literal": {
5146
+ const literal = value.value;
5147
+ if (literal === null) return "NULL";
5148
+ if (typeof literal === "string") return quoteLiteral(literal);
5149
+ if (typeof literal === "boolean") return literal ? "TRUE" : "FALSE";
5150
+ return String(literal);
5151
+ }
5152
+ case "claim": {
5153
+ const expression = claimExpression(value.key);
5154
+ return columnType && columnType !== "text" ? `(${expression})::${sqlTypeName(columnType)}` : expression;
5155
+ }
5156
+ case "variable":
5157
+ throw new OrmDefinitionError("synced view read policy cannot reference operation variables");
5158
+ default: {
5159
+ const exhausted = value;
5160
+ throw new OrmDefinitionError(`unsupported synced view value kind '${String(exhausted)}'`);
5161
+ }
5162
+ }
5163
+ }
5164
+ function compileArrayValue(value, columnType, _options) {
5165
+ if (value.kind !== "literal" || !Array.isArray(value.value)) {
5166
+ throw new OrmDefinitionError("synced view IN/NIN comparisons require literal arrays");
5167
+ }
5168
+ const entries = value.value.map((entry) => {
5169
+ if (entry === null) return "NULL";
5170
+ if (typeof entry === "string") return quoteLiteral(entry);
5171
+ if (typeof entry === "boolean") return entry ? "TRUE" : "FALSE";
5172
+ return String(entry);
5173
+ });
5174
+ const array = `ARRAY[${entries.join(", ")}]`;
5175
+ return columnType ? `${array}::${sqlTypeName(columnType)}[]` : array;
5176
+ }
5177
+
5178
+ // src/sql/catalog.ts
5179
+ function sqlHash(value) {
5180
+ return `sha256:${crypto.createHash("sha256").update(value).digest("hex")}`;
5181
+ }
5182
+ function createSqlCatalog({ canonical, publishedObjects, executionTarget }) {
5183
+ if (!publishedObjects.length || new Set(publishedObjects).size !== publishedObjects.length) throw new Error("SQL objects must be explicitly declared and unique");
5184
+ const { ctx, diagnostics } = buildCompileContext(canonical);
5185
+ if (diagnostics.some((d) => d.severity === "error")) throw new Error(diagnostics.map((d) => d.message).join("; "));
5186
+ const identity = sqlHash(`ontology-sql-v1\0${computeSchemaHash(canonical)}\0${executionTarget}`);
5187
+ const refOf = (meta) => `${meta.namespace ?? canonical.namespace}/${meta.table}`;
5188
+ const definitions = [canonical, ...canonical.imports ?? []];
5189
+ const internal = new Set(definitions.flatMap((owner) => [
5190
+ ...owner.tables.filter((t) => t.internal).map((t) => `${owner.namespace}/${t.table}`),
5191
+ ...(owner.views ?? []).filter((v) => v.internal).map((v) => `${owner.namespace}/${v.view}`)
5192
+ ]));
5193
+ const objects = publishedObjects.map((ref) => {
5194
+ const meta = Object.values(ctx.tables).find((table) => refOf(table) === ref);
5195
+ if (!meta || internal.has(ref) || meta.namespace !== canonical.namespace && !meta.exportRead) throw new Error(`SQL object not published: ${ref}`);
5196
+ const reads = /* @__PURE__ */ new Set([ref]);
5197
+ let sequence = 0;
5198
+ const predicate = (table, pred, alias) => compilePredIrToSql(pred, {
5199
+ alias,
5200
+ namespace: table.namespace ?? canonical.namespace,
5201
+ columnNames: Object.fromEntries(table.columns.map((c) => [c.name, c.columnName])),
5202
+ columnTypes: Object.fromEntries(table.columns.map((c) => [c.name, c.type])),
5203
+ exists: (node) => {
5204
+ const link = table.links.find((candidate) => candidate.name === node.link);
5205
+ const target = link?.target ? ctx.tables[link.target] : void 0;
5206
+ if (!link || !target) throw new Error("SQL policy link is unavailable");
5207
+ reads.add(refOf(target));
5208
+ const child = `p${++sequence}`;
5209
+ const col = (t, a, name2) => {
5210
+ const c = t.columns.find((c2) => c2.name === name2 || c2.columnName === name2);
5211
+ if (!c) throw new Error("SQL policy column is unavailable");
5212
+ return `${quoteIdentifier(a)}.${quoteIdentifier(c.columnName)}`;
5213
+ };
5214
+ const join = link.kind === "toMany" ? `${col(target, child, link.on)} = ${col(table, alias, link.references)}` : `${col(table, alias, link.on)} = ${col(target, child, link.references)}`;
5215
+ return `EXISTS (SELECT 1 FROM ${quoteIdentifier(target.schema)}.${quoteIdentifier(target.table)} AS ${quoteIdentifier(child)} WHERE ${join} AND (${predicate(target, node.where, child)}))`;
5216
+ }
5217
+ });
5218
+ const columns = meta.columns.filter((c) => meta.filterable.includes(c.name)).map((c) => ({
5219
+ name: c.name,
5220
+ type: typeof c.type === "string" ? c.type : "text",
5221
+ nullable: c.nullable
5222
+ }));
5223
+ const projection = columns.map((c) => {
5224
+ const physical = meta.columns.find((m) => m.name === c.name);
5225
+ return `${quoteIdentifier("p0")}.${quoteIdentifier(physical.columnName)}${typeof physical.type === "object" ? "::text" : ""} AS ${quoteIdentifier(c.name)}`;
5226
+ }).join(", ");
5227
+ const policy = ctx.readPolicies.get(meta.table) ?? { kind: "const", value: true };
5228
+ const scan = `SELECT ${projection} FROM ${quoteIdentifier(meta.schema)}.${quoteIdentifier(meta.table)} AS ${quoteIdentifier("p0")} WHERE ${predicate(meta, policy, "p0")}`;
5229
+ return { ref, table: meta.table, columns, scan, read: [...reads].sort() };
5230
+ });
5231
+ return { identity, objects };
5232
+ }
5233
+
5234
+ // src/sql/ast.ts
5235
+ function obj(value) {
5236
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("unsupported SQL node");
5237
+ return value;
5238
+ }
5239
+ function list(value) {
5240
+ return value === void 0 ? [] : Array.isArray(value) ? value : fail3("expected SQL list");
5241
+ }
5242
+ function str(value) {
5243
+ return typeof value === "string" ? value : fail3("expected SQL name");
5244
+ }
5245
+ function fail3(message) {
5246
+ throw new Error(message);
5247
+ }
5248
+ function strings(value) {
5249
+ return list(value).map((v) => str(obj(obj(v).String).sval));
5250
+ }
5251
+ function only(node, keys) {
5252
+ for (const [key, value] of Object.entries(node)) {
5253
+ if (!keys.includes(key) && value !== void 0 && value !== null && value !== false && value !== "" && value !== 0 && !(Array.isArray(value) && value.length === 0)) {
5254
+ fail3(`unsupported SQL clause: ${key}`);
5255
+ }
5256
+ }
5257
+ }
5258
+
5259
+ // src/sql/compiler.ts
5260
+ var parser;
5261
+ var SQL_TYPES = ["text", "boolean", "integer", "bigint", "numeric", "double", "uuid", "date", "timestamptz"];
5262
+ var pgType = (type) => type === "double" ? "double precision" : SQL_TYPES.some((t) => t === type) ? type : fail3("unsupported SQL type");
5263
+ async function compileOntologySql({ catalog, sql, parameterTypes = [], maxRows = 1e3 }) {
5264
+ if (!Number.isInteger(maxRows) || maxRows < 1 || maxRows > 1e4 || Buffer.byteLength(sql) > 65536) fail3("SQL limit exceeded");
5265
+ parser ??= new pgParser.PgParser({ version: 17 });
5266
+ const parsed = await parser.parse(sql);
5267
+ if (parsed.error || !parsed.tree) fail3("invalid SQL");
5268
+ const statements = list(obj(parsed.tree).stmts);
5269
+ if (statements.length !== 1) fail3("one SQL SELECT is required");
5270
+ const emitter = new SqlEmitter({ catalog, parameterTypes });
5271
+ const result = emitter.select(obj(obj(statements[0]).stmt), { relations: [], ctes: /* @__PURE__ */ new Map() });
5272
+ if (emitter.used.size !== parameterTypes.length) fail3("SQL parameters must be used exactly as declared");
5273
+ if (!emitter.read.size) fail3("SQL operation must read an ontology object");
5274
+ return {
5275
+ ...result,
5276
+ read: [...emitter.read].sort(),
5277
+ maxRows,
5278
+ queryHash: sqlHash(JSON.stringify(["ontology-sql-v1", catalog.identity, result.text, parameterTypes, maxRows]))
5279
+ };
5280
+ }
5281
+ var SqlEmitter = class {
5282
+ constructor(input) {
5283
+ this.input = input;
5284
+ }
5285
+ read = /* @__PURE__ */ new Set();
5286
+ used = /* @__PURE__ */ new Set();
5287
+ nodes = 0;
5288
+ depth = 0;
5289
+ budget() {
5290
+ if (++this.nodes > 5e3 || this.depth > 48) fail3("SQL complexity limit exceeded");
5291
+ }
5292
+ select(wrapper, parent, resolveUnknown = true) {
5293
+ this.budget();
5294
+ this.depth++;
5295
+ const node = obj(wrapper.SelectStmt);
5296
+ only(node, ["targetList", "fromClause", "whereClause", "groupClause", "havingClause", "sortClause", "limitCount", "limitOffset", "limitOption", "op", "withClause", "all", "larg", "rarg"]);
5297
+ if (!["SETOP_NONE", "SETOP_UNION"].includes(str(node.op))) fail3("unsupported SQL set operation");
5298
+ if (node.op === "SETOP_NONE" && (node.all || node.larg || node.rarg)) fail3("invalid SQL set operation");
5299
+ if (node.limitOption && !["LIMIT_OPTION_DEFAULT", "LIMIT_OPTION_COUNT"].includes(str(node.limitOption))) fail3("unsupported SQL limit");
5300
+ const scope = { relations: [], parent, ctes: new Map(parent.ctes) };
5301
+ const ctes = [];
5302
+ if (node.withClause) {
5303
+ const withNode = obj(node.withClause);
5304
+ only(withNode, ["ctes", "location"]);
5305
+ for (const entry of list(withNode.ctes)) {
5306
+ const cte = obj(obj(entry).CommonTableExpr);
5307
+ only(cte, ["ctename", "ctequery", "ctematerialized", "location"]);
5308
+ const name2 = str(cte.ctename);
5309
+ if (scope.ctes.has(name2)) fail3("duplicate CTE");
5310
+ const result2 = this.select(obj(cte.ctequery), { ...parent, ctes: scope.ctes });
5311
+ scope.ctes.set(name2, result2);
5312
+ ctes.push(`${quoteIdentifier(name2)} AS (${result2.text})`);
5313
+ }
5314
+ }
5315
+ const result = node.op === "SETOP_UNION" ? this.union(node, scope) : this.projection(node, scope);
5316
+ const { columns } = result;
5317
+ let text = `${ctes.length ? `WITH ${ctes.join(", ")} ` : ""}${result.text}`;
5318
+ if (list(node.sortClause).length) text += ` ORDER BY ${list(node.sortClause).map((n) => {
5319
+ const sort = obj(obj(n).SortBy);
5320
+ only(sort, ["node", "sortby_dir", "sortby_nulls", "location"]);
5321
+ const direction = sort.sortby_dir === "SORTBY_DESC" ? "DESC" : sort.sortby_dir === "SORTBY_ASC" || sort.sortby_dir === "SORTBY_DEFAULT" ? "ASC" : fail3("unsupported SQL sort");
5322
+ const nulls = sort.sortby_nulls === "SORTBY_NULLS_FIRST" ? " NULLS FIRST" : sort.sortby_nulls === "SORTBY_NULLS_LAST" ? " NULLS LAST" : "";
5323
+ const value = obj(sort.node);
5324
+ const reference = value.ColumnRef ? strings(obj(value.ColumnRef).fields) : [];
5325
+ const projected = reference.length === 1 ? columns.filter((c) => c.name === reference[0]) : [];
5326
+ return `${projected.length === 1 ? quoteIdentifier(projected[0].name) : this.expr(value, scope).text} ${direction}${nulls}`;
5327
+ }).join(", ")}`;
5328
+ for (const [key, keyword] of [["limitCount", "LIMIT"], ["limitOffset", "OFFSET"]]) {
5329
+ if (node[key]) text += ` ${keyword} ${this.expr(obj(node[key]), scope).text}`;
5330
+ }
5331
+ this.depth--;
5332
+ return { text, columns: resolveUnknown ? columns.map((c) => c.type === "unknown" ? { ...c, type: "text" } : c) : columns };
5333
+ }
5334
+ union(node, scope) {
5335
+ for (const key of ["targetList", "fromClause", "whereClause", "groupClause", "havingClause"]) {
5336
+ if (node[key]) fail3("unsupported UNION clause");
5337
+ }
5338
+ const left = this.select({ SelectStmt: obj(node.larg) }, scope, false);
5339
+ const right = this.select({ SelectStmt: obj(node.rarg) }, scope, false);
5340
+ if (left.columns.length !== right.columns.length) fail3("UNION branches must have the same column count");
5341
+ const numeric = ["integer", "bigint", "numeric", "double"];
5342
+ const columns = left.columns.map((column, index) => {
5343
+ const other = right.columns[index];
5344
+ const type = column.type === "unknown" && other.type === "unknown" ? "text" : column.type === "unknown" ? other.type : other.type === "unknown" ? column.type : column.type === other.type ? column.type : numeric.includes(column.type) && numeric.includes(other.type) ? numeric[Math.max(numeric.indexOf(column.type), numeric.indexOf(other.type))] : fail3("UNION columns require compatible types; use explicit casts");
5345
+ return { name: column.name, type, nullable: column.nullable || other.nullable };
5346
+ });
5347
+ return { text: `(${left.text}) UNION${node.all ? " ALL" : ""} (${right.text})`, columns };
5348
+ }
5349
+ projection(node, scope) {
5350
+ const from = list(node.fromClause).map((n) => this.from(obj(n), scope)).join(", ");
5351
+ const columns = [];
5352
+ const targets = [];
5353
+ for (const item of list(node.targetList)) {
5354
+ const target = obj(obj(item).ResTarget);
5355
+ only(target, ["name", "val", "location"]);
5356
+ const value = obj(target.val);
5357
+ const fields = value.ColumnRef ? list(obj(value.ColumnRef).fields) : [];
5358
+ if (fields.length && obj(fields[fields.length - 1]).A_Star) {
5359
+ const matches = fields.length === 1 ? scope.relations : scope.relations.filter((r) => r.alias === strings(fields.slice(0, -1))[0]);
5360
+ if (!matches.length || fields.length > 2 || target.name) fail3("invalid wildcard");
5361
+ for (const relation of matches) for (const column of relation.columns) {
5362
+ columns.push(column);
5363
+ targets.push(`${quoteIdentifier(relation.alias)}.${quoteIdentifier(column.name)}`);
5364
+ }
5365
+ } else {
5366
+ const expression = this.expr(value, scope);
5367
+ const name2 = typeof target.name === "string" && target.name ? target.name : expression.name ?? `column${columns.length + 1}`;
5368
+ columns.push({ name: name2, type: expression.type, nullable: true });
5369
+ targets.push(`${expression.text} AS ${quoteIdentifier(name2)}`);
5370
+ }
5371
+ }
5372
+ if (!targets.length || targets.length > 256) fail3("invalid SQL projection");
5373
+ let text = `SELECT ${targets.join(", ")}${from ? ` FROM ${from}` : ""}`;
5374
+ if (node.whereClause) text += ` WHERE ${this.expr(obj(node.whereClause), scope).text}`;
5375
+ if (list(node.groupClause).length) text += ` GROUP BY ${list(node.groupClause).map((n) => this.expr(obj(n), scope).text).join(", ")}`;
5376
+ if (node.havingClause) text += ` HAVING ${this.expr(obj(node.havingClause), scope).text}`;
5377
+ return { text, columns };
5378
+ }
5379
+ from(node, scope) {
5380
+ this.budget();
5381
+ this.depth++;
5382
+ try {
5383
+ return this.relation(node, scope);
5384
+ } finally {
5385
+ this.depth--;
5386
+ }
5387
+ }
5388
+ relation(node, scope) {
5389
+ if (node.RangeVar) {
5390
+ const range = obj(node.RangeVar);
5391
+ only(range, ["schemaname", "relname", "inh", "relpersistence", "alias", "location"]);
5392
+ if (range.inh !== true) fail3("ONLY is not supported for ontology objects");
5393
+ const name2 = str(range.relname);
5394
+ const alias = range.alias ? str(obj(range.alias).aliasname) : name2;
5395
+ if (range.alias) only(obj(range.alias), ["aliasname"]);
5396
+ if (scope.relations.some((r) => r.alias === alias)) fail3("duplicate SQL alias");
5397
+ const cte = !range.schemaname ? scope.ctes.get(name2) : void 0;
5398
+ if (cte) {
5399
+ scope.relations.push({ alias, columns: cte.columns });
5400
+ return `${quoteIdentifier(name2)} AS ${quoteIdentifier(alias)}`;
5401
+ }
5402
+ const object = this.input.catalog.objects.find((o) => o.ref === `${range.schemaname}/${name2}`);
5403
+ if (!object) fail3("SQL object is not declared or available");
5404
+ for (const ref of object.read) this.read.add(ref);
5405
+ scope.relations.push({ alias, columns: object.columns });
5406
+ const source = `(WITH ${quoteIdentifier("__orm_scan")} AS MATERIALIZED (${object.scan}) SELECT * FROM ${quoteIdentifier("__orm_scan")})`;
5407
+ return `${source} AS ${quoteIdentifier(alias)}`;
5408
+ }
5409
+ if (node.JoinExpr) {
5410
+ const join = obj(node.JoinExpr);
5411
+ only(join, ["jointype", "larg", "rarg", "quals"]);
5412
+ if (!["JOIN_INNER", "JOIN_LEFT", "JOIN_RIGHT", "JOIN_FULL"].includes(str(join.jointype))) fail3("unsupported SQL join");
5413
+ const start = scope.relations.length;
5414
+ const left = this.from(obj(join.larg), scope);
5415
+ const middle = scope.relations.length;
5416
+ const right = this.from(obj(join.rarg), scope);
5417
+ const nullable = (relations) => {
5418
+ for (const relation of relations) relation.columns = relation.columns.map((c) => ({ ...c, nullable: true }));
5419
+ };
5420
+ if (["JOIN_RIGHT", "JOIN_FULL"].includes(str(join.jointype))) nullable(scope.relations.slice(start, middle));
5421
+ if (["JOIN_LEFT", "JOIN_FULL"].includes(str(join.jointype))) nullable(scope.relations.slice(middle));
5422
+ const kind = str(join.jointype).slice("JOIN_".length);
5423
+ return `(${left} ${kind} JOIN ${right} ON ${join.quals ? this.expr(obj(join.quals), scope).text : "TRUE"})`;
5424
+ }
5425
+ if (node.RangeSubselect) {
5426
+ const range = obj(node.RangeSubselect);
5427
+ only(range, ["subquery", "alias"]);
5428
+ const alias = str(obj(range.alias).aliasname);
5429
+ only(obj(range.alias), ["aliasname"]);
5430
+ if (scope.relations.some((r) => r.alias === alias)) fail3("duplicate SQL alias");
5431
+ const result = this.select(obj(range.subquery), { relations: [], ctes: scope.ctes });
5432
+ scope.relations.push({ alias, columns: result.columns });
5433
+ return `(${result.text}) AS ${quoteIdentifier(alias)}`;
5434
+ }
5435
+ return fail3("unsupported SQL relation");
5436
+ }
5437
+ column(fields, scope) {
5438
+ if (fields.length !== 1 && fields.length !== 2) fail3("unsupported column reference");
5439
+ const name2 = fields[fields.length - 1];
5440
+ const matches = scope.relations.flatMap((r) => fields.length === 2 && r.alias !== fields[0] ? [] : r.columns.filter((c) => c.name === name2).map((c) => ({ r, c })));
5441
+ if (matches.length > 1) fail3("ambiguous SQL column");
5442
+ if (matches.length === 1) return { text: `${quoteIdentifier(matches[0].r.alias)}.${quoteIdentifier(name2)}`, type: matches[0].c.type, name: name2 };
5443
+ if (scope.parent && !(fields.length === 2 && scope.relations.some((r) => r.alias === fields[0]))) return this.column(fields, scope.parent);
5444
+ return fail3("unknown ontology property");
5445
+ }
5446
+ expr(node, scope) {
5447
+ this.budget();
5448
+ this.depth++;
5449
+ try {
5450
+ return this.expression(node, scope);
5451
+ } finally {
5452
+ this.depth--;
5453
+ }
5454
+ }
5455
+ expression(node, scope) {
5456
+ if (node.ColumnRef) return this.column(strings(obj(node.ColumnRef).fields), scope);
5457
+ if (node.ParamRef) {
5458
+ const number = obj(node.ParamRef).number;
5459
+ if (typeof number !== "number" || !Number.isInteger(number) || number < 1 || number > this.input.parameterTypes.length) fail3("undeclared SQL parameter");
5460
+ this.used.add(number);
5461
+ const type = this.input.parameterTypes[number - 1];
5462
+ return { text: `$${number}::${pgType(type)}`, type };
5463
+ }
5464
+ if (node.A_Const) {
5465
+ const literal = obj(node.A_Const);
5466
+ if (literal.isnull) return { text: "NULL", type: "unknown" };
5467
+ if (literal.sval) return { text: quoteLiteral(str(obj(literal.sval).sval)), type: "unknown" };
5468
+ if (literal.boolval) return { text: obj(literal.boolval).boolval ? "TRUE" : "FALSE", type: "boolean" };
5469
+ const value = literal.ival ? obj(literal.ival).ival : obj(literal.fval).fval;
5470
+ if (!/^-?\d+(\.\d+)?([eE][+-]?\d+)?$/.test(String(value))) fail3("unsupported SQL constant");
5471
+ return { text: String(value), type: literal.ival ? "integer" : "numeric" };
5472
+ }
5473
+ if (node.A_Expr) {
5474
+ const value = obj(node.A_Expr);
5475
+ only(value, ["kind", "name", "lexpr", "rexpr", "location"]);
5476
+ const names = strings(value.name);
5477
+ if (names.length !== 1 || value.kind !== "AEXPR_OP") fail3("unsupported SQL operator");
5478
+ const operator = names[0];
5479
+ if (!["=", "<>", "!=", "<", ">", "<=", ">=", "+", "-", "*", "/"].includes(operator)) fail3("unsupported SQL operator");
5480
+ const left = this.expr(obj(value.lexpr), scope), right = this.expr(obj(value.rexpr), scope);
5481
+ const numericTypes = ["integer", "bigint", "numeric", "double"];
5482
+ const arithmetic = ["+", "-", "*", "/"].includes(operator);
5483
+ if (arithmetic && (!numericTypes.includes(left.type) || !numericTypes.includes(right.type))) fail3("arithmetic requires numeric operands");
5484
+ const type = arithmetic ? numericTypes[Math.max(numericTypes.indexOf(left.type), numericTypes.indexOf(right.type))] : "boolean";
5485
+ return { text: `(${left.text} OPERATOR(pg_catalog.${operator}) ${right.text})`, type };
5486
+ }
5487
+ if (node.BoolExpr) {
5488
+ const value = obj(node.BoolExpr);
5489
+ const args = list(value.args).map((a) => this.expr(obj(a), scope).text);
5490
+ if (value.boolop === "NOT_EXPR" && args.length === 1) return { text: `(NOT ${args[0]})`, type: "boolean" };
5491
+ if (!["AND_EXPR", "OR_EXPR"].includes(str(value.boolop))) fail3("unsupported boolean expression");
5492
+ return { text: `(${args.join(value.boolop === "AND_EXPR" ? " AND " : " OR ")})`, type: "boolean" };
5493
+ }
5494
+ if (node.NullTest) {
5495
+ const value = obj(node.NullTest);
5496
+ only(value, ["arg", "nulltesttype", "location"]);
5497
+ if (!["IS_NULL", "IS_NOT_NULL"].includes(str(value.nulltesttype))) fail3("invalid null comparison");
5498
+ return { text: `(${this.expr(obj(value.arg), scope).text} IS ${value.nulltesttype === "IS_NOT_NULL" ? "NOT " : ""}NULL)`, type: "boolean" };
5499
+ }
5500
+ if (node.SubLink) {
5501
+ const value = obj(node.SubLink);
5502
+ only(value, ["subLinkType", "subselect", "location"]);
5503
+ if (value.subLinkType !== "EXISTS_SUBLINK") fail3("only EXISTS subqueries are supported");
5504
+ return { text: `EXISTS (${this.select(obj(value.subselect), scope).text})`, type: "boolean" };
5505
+ }
5506
+ if (node.TypeCast) {
5507
+ const value = obj(node.TypeCast), typeName2 = obj(value.typeName);
5508
+ only(typeName2, ["names", "typemod", "location"]);
5509
+ const names = strings(typeName2.names);
5510
+ if (names.length > 2 || names.length === 2 && names[0] !== "pg_catalog") fail3("unsupported SQL cast");
5511
+ const aliases = { int4: "integer", int8: "bigint", float8: "double", bool: "boolean" };
5512
+ const name2 = names[names.length - 1], type = aliases[name2] ?? name2;
5513
+ return { text: `(${this.expr(obj(value.arg), scope).text})::${pgType(type)}`, type };
5514
+ }
5515
+ if (node.FuncCall) {
5516
+ const value = obj(node.FuncCall);
5517
+ only(value, ["funcname", "args", "agg_star", "agg_distinct", "funcformat", "location"]);
5518
+ const names = strings(value.funcname);
5519
+ if (names.length !== 1) fail3("SQL function qualification is not supported");
5520
+ const name2 = names[0];
5521
+ if (!["count", "sum", "avg", "min", "max", "lower", "upper", "length"].includes(name2)) fail3("SQL function is not allowed");
5522
+ const args = list(value.args).map((a) => this.expr(obj(a), scope));
5523
+ if (value.agg_star && (name2 !== "count" || args.length) || !value.agg_star && args.length !== 1) fail3("unsupported SQL function arguments");
5524
+ const argType = args[0]?.type ?? "integer";
5525
+ if (["lower", "upper", "length"].includes(name2) && !["text", "unknown"].includes(argType)) fail3("text function requires text");
5526
+ if (["sum", "avg"].includes(name2) && !["integer", "bigint", "numeric", "double"].includes(argType)) fail3("aggregate requires numeric input");
5527
+ const type = name2 === "count" ? "bigint" : name2 === "length" ? "integer" : ["lower", "upper"].includes(name2) ? "text" : name2 === "avg" ? argType === "double" ? "double" : "numeric" : name2 === "sum" ? argType === "integer" ? "bigint" : argType === "bigint" ? "numeric" : argType : argType;
5528
+ return { text: `pg_catalog.${name2}(${value.agg_distinct ? "DISTINCT " : ""}${value.agg_star ? "*" : args.map((a) => a.text).join(", ")})`, type, name: name2 };
5529
+ }
5530
+ return fail3(`unsupported SQL expression: ${Object.keys(node).join(",")}`);
5531
+ }
5532
+ };
5533
+ var sqlScalarSchema = zod.z.enum(["text", "boolean", "integer", "bigint", "numeric", "double", "uuid", "date", "timestamptz"]);
5534
+ var name = zod.z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).max(63).refine((value) => !["__proto__", "prototype", "constructor"].includes(value), "reserved name");
5535
+ var base = zod.z.object({
5536
+ name,
5537
+ namespace: name,
5538
+ language: zod.z.literal("sql"),
5539
+ kind: zod.z.literal("query"),
5540
+ requires: zod.z.object({ capability: zod.z.string().min(1).max(128) }).strict(),
5541
+ 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(),
5542
+ maxRows: zod.z.number().int().min(1).max(1e4).default(1e3)
5543
+ });
5544
+ var fixed = base.extend({
5545
+ parameters: zod.z.record(name, zod.z.object({ type: sqlScalarSchema, required: zod.z.boolean().default(true) }).strict()).default({}),
5546
+ sql: zod.z.string().min(1).max(65536)
5547
+ }).strict();
5548
+ var runtime = base.extend({ mode: zod.z.literal("runtime") }).strict();
5549
+ var sqlOperationDefinitionSchema = zod.z.union([fixed, runtime]);
5550
+ function defineOperation(input) {
5551
+ return sqlOperationDefinitionSchema.parse(input);
5552
+ }
5553
+
5554
+ // src/sql/parameters.ts
5555
+ function bindNamedParameters({ sql, names }) {
5556
+ let result = "", i = 0;
5557
+ while (i < sql.length) {
5558
+ const start = i;
5559
+ if (sql.startsWith("--", i)) {
5560
+ i = sql.indexOf("\n", i);
5561
+ if (i < 0) i = sql.length;
5562
+ } else if (sql.startsWith("/*", i)) {
5563
+ i += 2;
5564
+ let depth = 1;
5565
+ while (i < sql.length && depth) {
5566
+ if (sql.startsWith("/*", i)) {
5567
+ depth++;
5568
+ i += 2;
5569
+ } else if (sql.startsWith("*/", i)) {
5570
+ depth--;
5571
+ i += 2;
5572
+ } else i++;
5573
+ }
5574
+ if (depth) throw new Error("unterminated SQL comment");
5575
+ } else if (sql[i] === "'" || sql[i] === '"') {
5576
+ const escapes = sql[i] === "'" && /[eE]/.test(sql[i - 1] ?? "") && !/[A-Za-z0-9_$]/.test(sql[i - 2] ?? "");
5577
+ const quote = sql[i++];
5578
+ while (i < sql.length) {
5579
+ if (sql[i] === quote) {
5580
+ if (sql[i + 1] === quote) i += 2;
5581
+ else {
5582
+ i++;
5583
+ break;
5584
+ }
5585
+ } else if (escapes && sql[i] === "\\") i += 2;
5586
+ else i++;
5587
+ }
5588
+ } else if (sql[i] === "$" && /^\$(?:[A-Za-z_][A-Za-z0-9_]*)?\$/.test(sql.slice(i))) {
5589
+ const marker = /^\$(?:[A-Za-z_][A-Za-z0-9_]*)?\$/.exec(sql.slice(i))[0];
5590
+ const end = sql.indexOf(marker, i + marker.length);
5591
+ if (end < 0) throw new Error("unterminated SQL string");
5592
+ i = end + marker.length;
5593
+ } else if (sql[i] === ":" && sql[i - 1] !== ":" && sql[i + 1] !== ":") {
5594
+ const name2 = /^[A-Za-z_][A-Za-z0-9_]*/.exec(sql.slice(i + 1))?.[0];
5595
+ if (!name2 || !names.includes(name2)) throw new Error("undeclared named SQL parameter");
5596
+ result += `$${names.indexOf(name2) + 1}`;
5597
+ i += name2.length + 1;
5598
+ continue;
5599
+ } else i++;
5600
+ result += sql.slice(start, i);
5601
+ }
5602
+ return result;
5603
+ }
5604
+
5605
+ // src/sql/operations.ts
5606
+ function isSqlOperationDocument(path, contents) {
5607
+ if (path.endsWith(".operation.json")) return true;
5608
+ if (!contents?.trimStart().startsWith("{")) return false;
5609
+ try {
5610
+ const value = JSON.parse(contents);
5611
+ return !!value && typeof value === "object" && "language" in value && value.language === "sql";
5612
+ } catch {
5613
+ return false;
5614
+ }
5615
+ }
5616
+ async function compileOperationDocuments(input) {
5617
+ const sqlDocuments = input.documents.filter((d) => isSqlOperationDocument(d.path, d.contents));
5618
+ const result = compileNamespace({ ...input, documents: input.documents.filter((d) => !isSqlOperationDocument(d.path, d.contents)) });
5619
+ if (!result.ok || !sqlDocuments.length) return result;
5620
+ for (const document of sqlDocuments) {
5621
+ try {
5622
+ const definition = defineOperation(JSON.parse(document.contents));
5623
+ if (definition.namespace !== input.canonical.namespace) throw new Error("SQL operation namespace does not match its owner");
5624
+ if (result.artifact.operations[definition.name]) throw new Error("duplicate operation name");
5625
+ const roles = input.canonical.capabilities.roles;
5626
+ const localCapabilities = roles && typeof roles === "object" && "capabilities" in roles ? roles.capabilities : void 0;
5627
+ const declaredLocally = localCapabilities && typeof localCapabilities === "object" && Object.hasOwn(localCapabilities, definition.requires.capability);
5628
+ if (!declaredLocally && !input.canonical.capabilities.uses.includes(definition.requires.capability)) throw new Error("SQL operation capability is not declared by its namespace");
5629
+ const read = [...definition.ontology.read].sort();
5630
+ const catalog = createSqlCatalog({ canonical: input.canonical, publishedObjects: read, executionTarget: "registered-operation" });
5631
+ if ("mode" in definition) {
5632
+ if (catalog.objects.some((object) => object.read.some((ref) => !read.includes(ref)))) throw new Error("SQL policy accesses an undeclared ontology object");
5633
+ const opHash2 = sqlHash(JSON.stringify([
5634
+ "sequence-runtime-sql-operation-v1",
5635
+ definition.name,
5636
+ definition.namespace,
5637
+ definition.requires.capability,
5638
+ read,
5639
+ catalog.identity,
5640
+ definition.maxRows
5641
+ ]));
5642
+ result.artifact.operations[definition.name] = compiledOperationSchema.parse({
5643
+ irVersion: RUNTIME_SQL_IR_VERSION,
5644
+ language: "sql",
5645
+ name: definition.name,
5646
+ opHash: opHash2,
5647
+ kind: "query",
5648
+ requires: definition.requires,
5649
+ variables: [
5650
+ { name: "sql", type: { kind: "scalar", scalar: "text" }, nullable: false, list: false },
5651
+ { name: "parameters", type: { kind: "scalar", scalar: "jsonb" }, nullable: true, list: false }
5652
+ ],
5653
+ roots: [],
5654
+ tableSet: read.map((ref) => ref.split("/")[1]).sort(),
5655
+ sqlQuery: { catalog, read, maxRows: definition.maxRows }
5656
+ });
5657
+ result.manifest.operations[definition.name] = { opHash: opHash2, kind: "query", requires: definition.requires };
5658
+ continue;
5659
+ }
5660
+ const names = Object.keys(definition.parameters).sort();
5661
+ if (names.length > 128) throw new Error("too many SQL parameters");
5662
+ const plan = await compileOntologySql({
5663
+ catalog,
5664
+ sql: bindNamedParameters({ sql: definition.sql, names }),
5665
+ parameterTypes: names.map((name2) => definition.parameters[name2].type),
5666
+ maxRows: definition.maxRows
5667
+ });
5668
+ if (plan.read.some((ref) => !read.includes(ref))) throw new Error("SQL policy/query accesses an undeclared ontology object");
5669
+ const variables = names.map((name2) => ({
5670
+ name: name2,
5671
+ type: { kind: "scalar", scalar: definition.parameters[name2].type },
5672
+ nullable: !definition.parameters[name2].required,
5673
+ list: false
5674
+ }));
5675
+ const opHash = sqlHash(JSON.stringify([
5676
+ "sequence-sql-operation-v1",
5677
+ definition.name,
5678
+ definition.namespace,
5679
+ definition.requires.capability,
5680
+ read,
5681
+ variables,
5682
+ plan.queryHash
5683
+ ]));
5684
+ const operation = compiledOperationSchema.parse({
5685
+ irVersion: SQL_IR_VERSION,
5686
+ language: "sql",
5687
+ name: definition.name,
5688
+ opHash,
5689
+ kind: "query",
5690
+ requires: definition.requires,
5691
+ variables,
5692
+ roots: [],
5693
+ tableSet: plan.read.map((ref) => ref.split("/")[1]).sort(),
5694
+ sql: { text: plan.text, columns: plan.columns, parameters: names, read, maxRows: plan.maxRows }
5695
+ });
5696
+ result.artifact.operations[definition.name] = operation;
5697
+ result.manifest.operations[definition.name] = { opHash, kind: "query", requires: definition.requires };
5698
+ } catch (error) {
5699
+ return { ok: false, diagnostics: [...result.diagnostics, {
5700
+ severity: "error",
5701
+ code: "invalid-sql-operation",
5702
+ document: document.path,
5703
+ message: error instanceof Error ? error.message : "invalid SQL operation"
5704
+ }] };
5705
+ }
5706
+ }
5707
+ const required = requiredArtifactIrVersion(Object.values(result.artifact.operations));
5708
+ if (required > result.artifact.irVersion) result.artifact.irVersion = required;
5709
+ result.artifact.opsHash = computeOpsHash(Object.values(result.artifact.operations).map((op) => op.opHash));
5710
+ result.manifest.opsHash = result.artifact.opsHash;
5711
+ return { ...result, artifact: normalizeIr(result.artifact) };
5712
+ }
5713
+ var parameterName = zod.z.string().regex(/^[A-Za-z_][A-Za-z0-9_]*$/).max(63).refine((value) => !["__proto__", "prototype", "constructor"].includes(value), "reserved parameter name");
5714
+ var parameter = zod.z.discriminatedUnion("type", [
5715
+ zod.z.object({ type: zod.z.literal("text"), value: zod.z.string().nullable() }).strict(),
5716
+ zod.z.object({ type: zod.z.literal("uuid"), value: zod.z.guid().nullable() }).strict(),
5717
+ zod.z.object({ type: zod.z.literal("date"), value: zod.z.string().nullable() }).strict(),
5718
+ zod.z.object({ type: zod.z.literal("timestamptz"), value: zod.z.string().nullable() }).strict(),
5719
+ zod.z.object({ type: zod.z.literal("numeric"), value: zod.z.string().regex(/^-?\d+(\.\d+)?$/).nullable() }).strict(),
5720
+ zod.z.object({ type: zod.z.literal("bigint"), value: zod.z.string().regex(/^-?\d+$/).nullable() }).strict(),
5721
+ zod.z.object({ type: zod.z.literal("integer"), value: zod.z.number().int().min(-2147483648).max(2147483647).nullable() }).strict(),
5722
+ zod.z.object({ type: zod.z.literal("double"), value: zod.z.number().nullable() }).strict(),
5723
+ zod.z.object({ type: zod.z.literal("boolean"), value: zod.z.boolean().nullable() }).strict()
5724
+ ]);
5725
+ var sqlQueryInputSchema = zod.z.object({
5726
+ sql: zod.z.string().min(1).max(65536).refine((value) => Buffer.byteLength(value) <= 65536, "SQL exceeds 64 KiB"),
5727
+ parameters: zod.z.record(parameterName, parameter).refine((value) => Object.keys(value).length <= 128, "too many SQL parameters").default({})
5728
+ }).strict();
5729
+ async function compileRuntimeSql({ operation, input }) {
5730
+ const contract = operation.sqlQuery;
5731
+ if (!contract) throw new Error("missing runtime SQL contract");
5732
+ const names = Object.keys(input.parameters).sort();
5733
+ const plan = await compileOntologySql({
5734
+ catalog: contract.catalog,
5735
+ sql: bindNamedParameters({ sql: input.sql, names }),
5736
+ parameterTypes: names.map((name2) => input.parameters[name2].type),
5737
+ maxRows: contract.maxRows
5738
+ });
5739
+ if (plan.read.some((ref) => !contract.read.includes(ref))) throw new Error("SQL accesses an undeclared ontology object");
5740
+ return compiledOperationSchema.parse({
5741
+ ...operation,
5742
+ sqlQuery: void 0,
5743
+ irVersion: SQL_IR_VERSION,
5744
+ variables: names.map((name2) => ({ name: name2, type: { kind: "scalar", scalar: input.parameters[name2].type }, nullable: true, list: false })),
5745
+ tableSet: plan.read.map((ref) => ref.split("/")[1]).sort(),
5746
+ sql: { text: plan.text, columns: plan.columns, parameters: names, read: plan.read, maxRows: plan.maxRows }
5747
+ });
5748
+ }
5749
+ var DATASET_PREVIEW_LIMIT = 100;
5750
+ function compileDatasetPreviews(input) {
5751
+ const canonical = normalizeCanonical(input);
5752
+ const { ctx, diagnostics } = buildCompileContext(canonical);
5753
+ if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) throw new NamespaceCompileError(diagnostics);
5754
+ const schemaHash = computeSchemaHash(canonical);
5755
+ const operations = {};
5756
+ for (const relation of canonicalRelations(canonical)) {
5757
+ if (relation.internal || relation.source !== void 0) continue;
5758
+ const columns = relation.columns.filter((column) => column.cell === void 0);
5759
+ const { roots, tableSet } = injectPolicies({ ctx, roots: [{
5760
+ kind: "select",
5761
+ table: relation.table,
5762
+ alias: "rows",
5763
+ where: { kind: "const", value: true },
5764
+ orderBy: [],
5765
+ limit: { value: DATASET_PREVIEW_LIMIT, default: DATASET_PREVIEW_LIMIT, max: DATASET_PREVIEW_LIMIT },
5766
+ selection: {
5767
+ typename: ctx.tables[relation.table].typename,
5768
+ columns: columns.map((column) => ({ column: column.name, alias: column.name })),
5769
+ relations: [],
5770
+ aggregates: []
5771
+ }
5772
+ }] });
5773
+ for (const table of tableSet) {
5774
+ const meta = ctx.tables[table];
5775
+ if (meta.namespace && meta.namespace !== ctx.namespace && !meta.exportRead) {
5776
+ throw new DocumentCompileError(`object '${meta.namespace}/${table}' is not exported for reads`, "unexported-object");
5777
+ }
5778
+ }
5779
+ const hash = crypto.createHash("sha256").update("sequence-orm-dataset-preview-v1\0").update(stableJson({ schemaHash, roots, tableSet })).digest("hex");
5780
+ operations[relation.table] = compiledOperationSchema.parse({
5781
+ irVersion: canonical.imports?.length ? COMPOSED_IR_VERSION : LEGACY_IR_VERSION,
5782
+ name: `DatasetPreview_${relation.table}`,
5783
+ opHash: `sha256:${hash}`,
5784
+ kind: "query",
5785
+ requires: null,
5786
+ variables: [],
5787
+ tableSet,
5788
+ roots
5789
+ });
5790
+ }
5791
+ return operations;
5792
+ }
5793
+
4828
5794
  exports.COMPILER_MAJOR = COMPILER_MAJOR;
4829
5795
  exports.CONST_TRUE = CONST_TRUE;
5796
+ exports.DATASET_PREVIEW_LIMIT = DATASET_PREVIEW_LIMIT;
4830
5797
  exports.DEFAULT_LIMIT = DEFAULT_LIMIT;
4831
5798
  exports.DocumentCompileError = DocumentCompileError;
4832
5799
  exports.MAX_LIMIT = MAX_LIMIT;
@@ -4848,18 +5815,23 @@ exports.camelCase = camelCase;
4848
5815
  exports.canonicalOperationText = canonicalOperationText;
4849
5816
  exports.classifyInputType = classifyInputType;
4850
5817
  exports.collectPinnedColumns = collectPinnedColumns;
5818
+ exports.compileDatasetPreviews = compileDatasetPreviews;
4851
5819
  exports.compileNamespace = compileNamespace;
4852
5820
  exports.compileNamespaceOrThrow = compileNamespaceOrThrow;
5821
+ exports.compileOperationDocuments = compileOperationDocuments;
4853
5822
  exports.compilePolicy = compilePolicy;
4854
5823
  exports.compilePredicateV2 = compilePredicateV2;
5824
+ exports.compileRuntimeSql = compileRuntimeSql;
4855
5825
  exports.computeOpHash = computeOpHash;
4856
5826
  exports.computeOpsHash = computeOpsHash;
4857
5827
  exports.computePinnedColumnsByTable = computePinnedColumnsByTable;
4858
5828
  exports.computeSchemaHash = computeSchemaHash;
5829
+ exports.defineOperation = defineOperation;
4859
5830
  exports.enumTypeName = enumTypeName;
4860
5831
  exports.flattenOperation = flattenOperation;
4861
5832
  exports.injectPolicies = injectPolicies;
4862
5833
  exports.inputTypeName = inputTypeName;
5834
+ exports.isSqlOperationDocument = isSqlOperationDocument;
4863
5835
  exports.lowerOperation = lowerOperation;
4864
5836
  exports.mutationRootNames = mutationRootNames;
4865
5837
  exports.parseDocuments = parseDocuments;
@@ -4871,6 +5843,8 @@ exports.predContains = predContains;
4871
5843
  exports.predContainsExists = predContainsExists;
4872
5844
  exports.queryRootNames = queryRootNames;
4873
5845
  exports.restrictOperation = restrictOperation;
5846
+ exports.sqlOperationDefinitionSchema = sqlOperationDefinitionSchema;
5847
+ exports.sqlQueryInputSchema = sqlQueryInputSchema;
4874
5848
  exports.typeNameForTable = typeNameForTable;
4875
5849
  exports.validateAppendInput = validateAppendInput;
4876
5850
  exports.validateBoolExpInput = validateBoolExpInput;