@sequenceholdings/orm 2.8.3 → 2.8.5

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 (52) hide show
  1. package/dist/authoring.cjs +154 -45
  2. package/dist/authoring.cjs.map +1 -1
  3. package/dist/authoring.d.cts +12 -2
  4. package/dist/authoring.d.ts +12 -2
  5. package/dist/authoring.js +154 -45
  6. package/dist/authoring.js.map +1 -1
  7. package/dist/canonical.cjs +64 -14
  8. package/dist/canonical.cjs.map +1 -1
  9. package/dist/canonical.d.cts +12 -7
  10. package/dist/canonical.d.ts +12 -7
  11. package/dist/canonical.js +64 -14
  12. package/dist/canonical.js.map +1 -1
  13. package/dist/cli.cjs +78 -20
  14. package/dist/cli.cjs.map +1 -1
  15. package/dist/cli.js +78 -20
  16. package/dist/cli.js.map +1 -1
  17. package/dist/compiler.cjs +77 -20
  18. package/dist/compiler.cjs.map +1 -1
  19. package/dist/compiler.d.cts +4 -4
  20. package/dist/compiler.d.ts +4 -4
  21. package/dist/compiler.js +77 -20
  22. package/dist/compiler.js.map +1 -1
  23. package/dist/index.cjs +161 -47
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +11 -11
  26. package/dist/index.d.ts +11 -11
  27. package/dist/index.js +161 -47
  28. package/dist/index.js.map +1 -1
  29. package/dist/ir.cjs +71 -16
  30. package/dist/ir.cjs.map +1 -1
  31. package/dist/ir.d.cts +9 -9
  32. package/dist/ir.d.ts +9 -9
  33. package/dist/ir.js +71 -16
  34. package/dist/ir.js.map +1 -1
  35. package/dist/{naming-CU9ThP_E.d.cts → naming-BMZKFO0W.d.cts} +1 -2
  36. package/dist/{naming-CPfx7EfF.d.ts → naming-DUk7AcXs.d.ts} +1 -2
  37. package/dist/predicate-v2.cjs +71 -16
  38. package/dist/predicate-v2.cjs.map +1 -1
  39. package/dist/predicate-v2.d.cts +3 -3
  40. package/dist/predicate-v2.d.ts +3 -3
  41. package/dist/predicate-v2.js +71 -16
  42. package/dist/predicate-v2.js.map +1 -1
  43. package/dist/sdl.cjs +71 -16
  44. package/dist/sdl.cjs.map +1 -1
  45. package/dist/sdl.d.cts +2 -2
  46. package/dist/sdl.d.ts +2 -2
  47. package/dist/sdl.js +71 -16
  48. package/dist/sdl.js.map +1 -1
  49. package/package.json +1 -1
  50. package/src/codegen-gql/apollo-hooks.cjs +1 -1
  51. package/src/codegen-gql/server-fns.cjs +1 -1
  52. package/templates/orm-namespace/graphql/example/queries.ts +1 -1
package/dist/cli.cjs CHANGED
@@ -415,6 +415,48 @@ var policyExpressionSchema = zod.z.union([
415
415
  variants: zod.z.array(policyVariantSchema).max(10).default([])
416
416
  })
417
417
  ]);
418
+
419
+ // src/canonical/resolved-rows.ts
420
+ function logicalKey(table) {
421
+ return (table.primaryKey ?? []).map((columnName) => table.columns.find((c) => c.columnName === columnName)?.name ?? columnName);
422
+ }
423
+ function sameKey(key, expected) {
424
+ return key.length === expected.length && key.every((name2) => expected.includes(name2));
425
+ }
426
+ function validateRowIdentity(input) {
427
+ validateExistsIn(input);
428
+ if (input.keys.length > 1) validateCompositeKey(input);
429
+ }
430
+ function validateExistsIn({ resolved, sourceByName, fail: fail4 }) {
431
+ const name2 = resolved.existsIn?.[0];
432
+ if (name2 === void 0) return;
433
+ if (sourceByName.get(name2)?.kind !== "etl") fail4(`existsIn '${name2}' must be an etl source`);
434
+ if (resolved.policies.insert !== void 0 || resolved.policies.write !== void 0) {
435
+ fail4("existsIn allows only an update policy: rows come from the etl source");
436
+ }
437
+ }
438
+ function validateCompositeKey({ resolved, keys, tableByName, fail: fail4 }) {
439
+ const keyList = `(${keys.join(", ")})`;
440
+ if (resolved.existsIn === void 0) {
441
+ fail4(`requires exactly one primary-key column: composite key ${keyList} requires existsIn`);
442
+ return;
443
+ }
444
+ const writes = ["insert", "update", "write"].filter((kind) => resolved.policies[kind] !== void 0);
445
+ if (writes.length > 0) fail4(`composite key ${keyList} requires a read-only table: remove the ${writes.join("/")} policy`);
446
+ if (resolved.materialized) fail4(`composite key ${keyList} cannot be materialized`);
447
+ for (const source of resolved.sources) {
448
+ if (source.kind === "prefetch") {
449
+ fail4(`composite key ${keyList} cannot have prefetch source '${source.name}'`);
450
+ continue;
451
+ }
452
+ const table = tableByName.get(source.table);
453
+ if (table && !sameKey(logicalKey(table), keys)) {
454
+ fail4(`composite key ${keyList} requires ${source.kind} source '${source.name}' to be keyed by it; table '${source.table}' is keyed by (${logicalKey(table).join(", ")})`);
455
+ }
456
+ }
457
+ }
458
+
459
+ // src/canonical/resolved.ts
418
460
  function parseDurationMs(input) {
419
461
  if (typeof input === "number") return Number.isInteger(input) && input > 0 ? input : void 0;
420
462
  const match = /^(\d+(?:\.\d+)?)(ms|s|m)$/.exec(input);
@@ -491,6 +533,13 @@ var canonicalResolvedSchema = zod.z.object({
491
533
  resolve: zod.z.array(canonicalResolveRuleSchema).max(500),
492
534
  /** Sources whose tombstone hides the resolved row (v1: the manual source only). */
493
535
  deletedBy: zod.z.array(identifier).max(1).default([]),
536
+ /**
537
+ * `rows.existsIn`: the one etl source whose rows are the table's rows (read
538
+ * directly as the row source). Only an update policy may write. It is also
539
+ * what admits a composite primary key: a read-only table keyed exactly like
540
+ * that source.
541
+ */
542
+ existsIn: zod.z.array(identifier).length(1).optional(),
494
543
  /**
495
544
  * Materialized: the view reads an indexed internal table (`table`) that the
496
545
  * data plane keeps in step with `compute`, the internal view holding the
@@ -509,8 +558,8 @@ function validateResolvedView({ view, tables, views = [] }) {
509
558
  if (!resolved) return [];
510
559
  const errors = [];
511
560
  const fail4 = (message) => errors.push(`resolved table '${view.view}': ${message}`);
512
- const pkColumn = view.primaryKey?.length === 1 ? view.columns.find((c) => c.columnName === view.primaryKey?.[0]) : void 0;
513
- if (!pkColumn) {
561
+ const keyColumns = (view.primaryKey ?? []).map((columnName) => view.columns.find((c) => c.columnName === columnName)).filter((column) => column !== void 0);
562
+ if (keyColumns.length === 0 || keyColumns.length !== view.primaryKey?.length) {
514
563
  fail4("requires exactly one primary-key column");
515
564
  return errors;
516
565
  }
@@ -518,7 +567,7 @@ function validateResolvedView({ view, tables, views = [] }) {
518
567
  const scope = {
519
568
  view,
520
569
  resolved,
521
- pkColumn,
570
+ keys: new Map(keyColumns.map((column) => [column.name, column])),
522
571
  dataColumns: new Map(view.columns.filter((c) => !systemNames.has(c.name) && c.cell === void 0).map((c) => [c.name, c])),
523
572
  sourceByName: /* @__PURE__ */ new Map(),
524
573
  fail: fail4
@@ -532,6 +581,7 @@ function validateResolvedView({ view, tables, views = [] }) {
532
581
  if (!table) fail4(`source '${source.name}' references unknown table '${source.table}'`);
533
582
  else validateSource({ scope, source, table });
534
583
  }
584
+ validateRowIdentity({ resolved, keys: [...scope.keys.keys()], sourceByName: scope.sourceByName, tableByName, fail: fail4 });
535
585
  validateRules(scope);
536
586
  validateViewCells(scope);
537
587
  if (resolved.materialized) validateMaterialized({ scope, tableByName, views });
@@ -561,7 +611,7 @@ function validateSource({ scope, source, table }) {
561
611
  validatePrefetchFields({ scope, source });
562
612
  }
563
613
  function validatePrefetchFields({ scope, source }) {
564
- const { fail: fail4, pkColumn } = scope;
614
+ const { fail: fail4, keys } = scope;
565
615
  if (source.kind !== "prefetch") {
566
616
  if (source.key || source.fetch || source.function || source.prefetchDefaults || source.writeback) {
567
617
  fail4(`only prefetch sources may declare key/fetch/function/prefetchDefaults/writeback ('${source.name}')`);
@@ -570,7 +620,7 @@ function validatePrefetchFields({ scope, source }) {
570
620
  }
571
621
  if (!source.key || !source.fetch || !source.function || !source.prefetchDefaults) {
572
622
  fail4(`prefetch source '${source.name}' requires key, fetch, function and prefetchDefaults`);
573
- } else if (source.key !== pkColumn.name && !source.columns.includes(source.key)) {
623
+ } else if (!keys.has(source.key) && !source.columns.includes(source.key)) {
574
624
  fail4(`prefetch source '${source.name}' key '${source.key}' must be the primary key or one of its columns`);
575
625
  }
576
626
  for (const column of source.writeback?.columns ?? []) {
@@ -578,19 +628,19 @@ function validatePrefetchFields({ scope, source }) {
578
628
  }
579
629
  }
580
630
  function validateSourceColumns({ scope, source, byName }) {
581
- const { fail: fail4, pkColumn, dataColumns } = scope;
582
- for (const column of [pkColumn.name, ...source.columns]) {
583
- const own = column === pkColumn.name ? pkColumn : dataColumns.get(column);
631
+ const { fail: fail4, keys, dataColumns } = scope;
632
+ for (const column of [...keys.keys(), ...source.columns]) {
633
+ const own = keys.get(column) ?? dataColumns.get(column);
584
634
  const theirs = byName.get(column);
585
635
  if (!own) fail4(`source '${source.name}' supplies unknown column '${column}'`);
586
636
  else if (!theirs) fail4(`source '${source.name}' table '${source.table}' has no column '${column}'`);
587
637
  else if (JSON.stringify(theirs.type) !== JSON.stringify(own.type)) fail4(`column '${column}' has a different type on source '${source.name}'`);
588
638
  }
589
639
  }
590
- function validateRules({ resolved, pkColumn, dataColumns, sourceByName, fail: fail4 }) {
640
+ function validateRules({ resolved, keys, dataColumns, sourceByName, fail: fail4 }) {
591
641
  const ruled = /* @__PURE__ */ new Set();
592
642
  for (const rule of resolved.resolve) {
593
- if (!dataColumns.has(rule.column) || rule.column === pkColumn.name) {
643
+ if (!dataColumns.has(rule.column) || keys.has(rule.column)) {
594
644
  fail4(`resolve rule targets unknown column '${rule.column}'`);
595
645
  continue;
596
646
  }
@@ -607,7 +657,7 @@ function validateRules({ resolved, pkColumn, dataColumns, sourceByName, fail: fa
607
657
  }
608
658
  }
609
659
  for (const column of dataColumns.keys()) {
610
- if (column !== pkColumn.name && !ruled.has(column)) fail4(`column '${column}' has no resolve rule`);
660
+ if (!keys.has(column) && !ruled.has(column)) fail4(`column '${column}' has no resolve rule`);
611
661
  }
612
662
  }
613
663
  var VIEW_CELL_TYPES = {
@@ -616,10 +666,10 @@ var VIEW_CELL_TYPES = {
616
666
  by: "text",
617
667
  conflicts: "jsonb"
618
668
  };
619
- function validateViewCells({ view, dataColumns, pkColumn, fail: fail4 }) {
669
+ function validateViewCells({ view, dataColumns, keys, fail: fail4 }) {
620
670
  const expected = /* @__PURE__ */ new Map();
621
671
  for (const column of dataColumns.values()) {
622
- if (column.name === pkColumn.name) continue;
672
+ if (keys.has(column.name)) continue;
623
673
  for (const part of RESOLVED_VIEW_CELL_PARTS) {
624
674
  expected.set(cellColumnName(column.name, part), { part, columnName: cellColumnName(column.columnName, part) });
625
675
  }
@@ -987,7 +1037,7 @@ var canonicalTableSchema = zod.z.object({
987
1037
  table: identifier2,
988
1038
  displayName: zod.z.string().trim().min(1).max(120).optional(),
989
1039
  description: zod.z.string().max(2e3).optional(),
990
- /** Table-level rename hint (new in v2). */
1040
+ /** Table-level rename hint. */
991
1041
  renamedFrom: identifier2.optional(),
992
1042
  /** Synced/reflected read-only tables (`source: schema.table`) — v1 semantics. */
993
1043
  source: zod.z.string().regex(QUALIFIED_TABLE_PATTERN).optional(),
@@ -1845,8 +1895,13 @@ var resolvedSourceIrSchema = zod.z.object({
1845
1895
  }).optional()
1846
1896
  });
1847
1897
  var resolvedMetaIrSchema = zod.z.object({
1848
- /** Logical pk column of the resolved view (single-column). */
1849
- pk: zod.z.string(),
1898
+ /**
1899
+ * Logical pk column of the resolved view. Absent for a composite key, which
1900
+ * only a read-only `existsIn` table may declare: it has no writes, prefetch
1901
+ * or materialization (the paths that address one key column), and its reads
1902
+ * use the view's `primaryKey` like any keyed view.
1903
+ */
1904
+ pk: zod.z.string().optional(),
1850
1905
  /** Source name of the manual source. */
1851
1906
  manual: zod.z.string(),
1852
1907
  sources: zod.z.array(resolvedSourceIrSchema),
@@ -3271,7 +3326,7 @@ function buildCompileContext(rawCanonical) {
3271
3326
  appendable: readOnly ? [] : active.filter((c) => c.type === "jsonb").map((c) => c.name),
3272
3327
  ...touchOnUpdate.length > 0 ? { touchOnUpdate } : {},
3273
3328
  ...cells ? { cells } : {},
3274
- ...resolved ? { resolved: resolvedMeta({ resolved, pk: primaryKey2[0] }) } : {},
3329
+ ...resolved ? { resolved: resolvedMeta({ resolved, primaryKey: primaryKey2 }) } : {},
3275
3330
  constraints: isView ? [] : constraints,
3276
3331
  dynamicFilterMaxDepth: 6,
3277
3332
  dynamicFilterMaxNodes: 64
@@ -3553,10 +3608,10 @@ function resolvedWritable(entries) {
3553
3608
  const system = new Set(Object.values(RESOLVED_VIEW_SYSTEM_COLUMNS).map((column) => column.name));
3554
3609
  return entries.filter((entry) => !entry.deprecated && !system.has(entry.name)).map((entry) => entry.name);
3555
3610
  }
3556
- function resolvedMeta({ resolved, pk }) {
3611
+ function resolvedMeta({ resolved, primaryKey: primaryKey2 }) {
3557
3612
  const manual = resolved.sources.find((source) => source.kind === "manual");
3558
3613
  return {
3559
- pk,
3614
+ ...primaryKey2.length === 1 ? { pk: primaryKey2[0] } : {},
3560
3615
  manual: manual?.name ?? "",
3561
3616
  deletedBy: [...resolved.deletedBy],
3562
3617
  ...resolved.materialized ? { materialized: { ...resolved.materialized } } : {},
@@ -3682,7 +3737,9 @@ function applyPrefetchDirectives({ node, root, meta }) {
3682
3737
  if (directive.maxStaleness !== void 0 && maxStalenessMs === void 0) {
3683
3738
  fail(`@prefetch(maxStaleness: "${directive.maxStaleness}") must look like '500ms', '30s' or '5m'`, "invalid-source-directive");
3684
3739
  }
3685
- const key = root.kind === "byPk" ? { one: root.pk[0].value } : selectKey({ where: root.where, pk: meta.resolved.pk, source, alias: root.alias });
3740
+ const pk = meta.resolved?.pk;
3741
+ if (pk === void 0) return fail(`@prefetch on '${root.alias}' needs a single-column primary key`, "invalid-source-directive");
3742
+ const key = root.kind === "byPk" ? { one: root.pk[0].value } : selectKey({ where: root.where, pk, source, alias: root.alias });
3686
3743
  return { source: source.name, ...maxStalenessMs !== void 0 ? { maxStalenessMs } : {}, key };
3687
3744
  });
3688
3745
  return { ...root, prefetch: plans };
@@ -6578,6 +6635,7 @@ function buildCanonicalResolved(resolved) {
6578
6635
  })),
6579
6636
  resolve: resolved.rules.map((rule) => ({ ...rule, candidates: [...rule.candidates] })),
6580
6637
  deletedBy: [...resolved.deletedBy],
6638
+ ...resolved.existsIn !== void 0 ? { existsIn: [...resolved.existsIn] } : {},
6581
6639
  ...resolved.materialized ? { materialized: { ...resolved.materialized } } : {},
6582
6640
  policies
6583
6641
  };