@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.js CHANGED
@@ -412,6 +412,48 @@ var policyExpressionSchema = z.union([
412
412
  variants: z.array(policyVariantSchema).max(10).default([])
413
413
  })
414
414
  ]);
415
+
416
+ // src/canonical/resolved-rows.ts
417
+ function logicalKey(table) {
418
+ return (table.primaryKey ?? []).map((columnName) => table.columns.find((c) => c.columnName === columnName)?.name ?? columnName);
419
+ }
420
+ function sameKey(key, expected) {
421
+ return key.length === expected.length && key.every((name2) => expected.includes(name2));
422
+ }
423
+ function validateRowIdentity(input) {
424
+ validateExistsIn(input);
425
+ if (input.keys.length > 1) validateCompositeKey(input);
426
+ }
427
+ function validateExistsIn({ resolved, sourceByName, fail: fail4 }) {
428
+ const name2 = resolved.existsIn?.[0];
429
+ if (name2 === void 0) return;
430
+ if (sourceByName.get(name2)?.kind !== "etl") fail4(`existsIn '${name2}' must be an etl source`);
431
+ if (resolved.policies.insert !== void 0 || resolved.policies.write !== void 0) {
432
+ fail4("existsIn allows only an update policy: rows come from the etl source");
433
+ }
434
+ }
435
+ function validateCompositeKey({ resolved, keys, tableByName, fail: fail4 }) {
436
+ const keyList = `(${keys.join(", ")})`;
437
+ if (resolved.existsIn === void 0) {
438
+ fail4(`requires exactly one primary-key column: composite key ${keyList} requires existsIn`);
439
+ return;
440
+ }
441
+ const writes = ["insert", "update", "write"].filter((kind) => resolved.policies[kind] !== void 0);
442
+ if (writes.length > 0) fail4(`composite key ${keyList} requires a read-only table: remove the ${writes.join("/")} policy`);
443
+ if (resolved.materialized) fail4(`composite key ${keyList} cannot be materialized`);
444
+ for (const source of resolved.sources) {
445
+ if (source.kind === "prefetch") {
446
+ fail4(`composite key ${keyList} cannot have prefetch source '${source.name}'`);
447
+ continue;
448
+ }
449
+ const table = tableByName.get(source.table);
450
+ if (table && !sameKey(logicalKey(table), keys)) {
451
+ fail4(`composite key ${keyList} requires ${source.kind} source '${source.name}' to be keyed by it; table '${source.table}' is keyed by (${logicalKey(table).join(", ")})`);
452
+ }
453
+ }
454
+ }
455
+
456
+ // src/canonical/resolved.ts
415
457
  function parseDurationMs(input) {
416
458
  if (typeof input === "number") return Number.isInteger(input) && input > 0 ? input : void 0;
417
459
  const match = /^(\d+(?:\.\d+)?)(ms|s|m)$/.exec(input);
@@ -488,6 +530,13 @@ var canonicalResolvedSchema = z.object({
488
530
  resolve: z.array(canonicalResolveRuleSchema).max(500),
489
531
  /** Sources whose tombstone hides the resolved row (v1: the manual source only). */
490
532
  deletedBy: z.array(identifier).max(1).default([]),
533
+ /**
534
+ * `rows.existsIn`: the one etl source whose rows are the table's rows (read
535
+ * directly as the row source). Only an update policy may write. It is also
536
+ * what admits a composite primary key: a read-only table keyed exactly like
537
+ * that source.
538
+ */
539
+ existsIn: z.array(identifier).length(1).optional(),
491
540
  /**
492
541
  * Materialized: the view reads an indexed internal table (`table`) that the
493
542
  * data plane keeps in step with `compute`, the internal view holding the
@@ -506,8 +555,8 @@ function validateResolvedView({ view, tables, views = [] }) {
506
555
  if (!resolved) return [];
507
556
  const errors = [];
508
557
  const fail4 = (message) => errors.push(`resolved table '${view.view}': ${message}`);
509
- const pkColumn = view.primaryKey?.length === 1 ? view.columns.find((c) => c.columnName === view.primaryKey?.[0]) : void 0;
510
- if (!pkColumn) {
558
+ const keyColumns = (view.primaryKey ?? []).map((columnName) => view.columns.find((c) => c.columnName === columnName)).filter((column) => column !== void 0);
559
+ if (keyColumns.length === 0 || keyColumns.length !== view.primaryKey?.length) {
511
560
  fail4("requires exactly one primary-key column");
512
561
  return errors;
513
562
  }
@@ -515,7 +564,7 @@ function validateResolvedView({ view, tables, views = [] }) {
515
564
  const scope = {
516
565
  view,
517
566
  resolved,
518
- pkColumn,
567
+ keys: new Map(keyColumns.map((column) => [column.name, column])),
519
568
  dataColumns: new Map(view.columns.filter((c) => !systemNames.has(c.name) && c.cell === void 0).map((c) => [c.name, c])),
520
569
  sourceByName: /* @__PURE__ */ new Map(),
521
570
  fail: fail4
@@ -529,6 +578,7 @@ function validateResolvedView({ view, tables, views = [] }) {
529
578
  if (!table) fail4(`source '${source.name}' references unknown table '${source.table}'`);
530
579
  else validateSource({ scope, source, table });
531
580
  }
581
+ validateRowIdentity({ resolved, keys: [...scope.keys.keys()], sourceByName: scope.sourceByName, tableByName, fail: fail4 });
532
582
  validateRules(scope);
533
583
  validateViewCells(scope);
534
584
  if (resolved.materialized) validateMaterialized({ scope, tableByName, views });
@@ -558,7 +608,7 @@ function validateSource({ scope, source, table }) {
558
608
  validatePrefetchFields({ scope, source });
559
609
  }
560
610
  function validatePrefetchFields({ scope, source }) {
561
- const { fail: fail4, pkColumn } = scope;
611
+ const { fail: fail4, keys } = scope;
562
612
  if (source.kind !== "prefetch") {
563
613
  if (source.key || source.fetch || source.function || source.prefetchDefaults || source.writeback) {
564
614
  fail4(`only prefetch sources may declare key/fetch/function/prefetchDefaults/writeback ('${source.name}')`);
@@ -567,7 +617,7 @@ function validatePrefetchFields({ scope, source }) {
567
617
  }
568
618
  if (!source.key || !source.fetch || !source.function || !source.prefetchDefaults) {
569
619
  fail4(`prefetch source '${source.name}' requires key, fetch, function and prefetchDefaults`);
570
- } else if (source.key !== pkColumn.name && !source.columns.includes(source.key)) {
620
+ } else if (!keys.has(source.key) && !source.columns.includes(source.key)) {
571
621
  fail4(`prefetch source '${source.name}' key '${source.key}' must be the primary key or one of its columns`);
572
622
  }
573
623
  for (const column of source.writeback?.columns ?? []) {
@@ -575,19 +625,19 @@ function validatePrefetchFields({ scope, source }) {
575
625
  }
576
626
  }
577
627
  function validateSourceColumns({ scope, source, byName }) {
578
- const { fail: fail4, pkColumn, dataColumns } = scope;
579
- for (const column of [pkColumn.name, ...source.columns]) {
580
- const own = column === pkColumn.name ? pkColumn : dataColumns.get(column);
628
+ const { fail: fail4, keys, dataColumns } = scope;
629
+ for (const column of [...keys.keys(), ...source.columns]) {
630
+ const own = keys.get(column) ?? dataColumns.get(column);
581
631
  const theirs = byName.get(column);
582
632
  if (!own) fail4(`source '${source.name}' supplies unknown column '${column}'`);
583
633
  else if (!theirs) fail4(`source '${source.name}' table '${source.table}' has no column '${column}'`);
584
634
  else if (JSON.stringify(theirs.type) !== JSON.stringify(own.type)) fail4(`column '${column}' has a different type on source '${source.name}'`);
585
635
  }
586
636
  }
587
- function validateRules({ resolved, pkColumn, dataColumns, sourceByName, fail: fail4 }) {
637
+ function validateRules({ resolved, keys, dataColumns, sourceByName, fail: fail4 }) {
588
638
  const ruled = /* @__PURE__ */ new Set();
589
639
  for (const rule of resolved.resolve) {
590
- if (!dataColumns.has(rule.column) || rule.column === pkColumn.name) {
640
+ if (!dataColumns.has(rule.column) || keys.has(rule.column)) {
591
641
  fail4(`resolve rule targets unknown column '${rule.column}'`);
592
642
  continue;
593
643
  }
@@ -604,7 +654,7 @@ function validateRules({ resolved, pkColumn, dataColumns, sourceByName, fail: fa
604
654
  }
605
655
  }
606
656
  for (const column of dataColumns.keys()) {
607
- if (column !== pkColumn.name && !ruled.has(column)) fail4(`column '${column}' has no resolve rule`);
657
+ if (!keys.has(column) && !ruled.has(column)) fail4(`column '${column}' has no resolve rule`);
608
658
  }
609
659
  }
610
660
  var VIEW_CELL_TYPES = {
@@ -613,10 +663,10 @@ var VIEW_CELL_TYPES = {
613
663
  by: "text",
614
664
  conflicts: "jsonb"
615
665
  };
616
- function validateViewCells({ view, dataColumns, pkColumn, fail: fail4 }) {
666
+ function validateViewCells({ view, dataColumns, keys, fail: fail4 }) {
617
667
  const expected = /* @__PURE__ */ new Map();
618
668
  for (const column of dataColumns.values()) {
619
- if (column.name === pkColumn.name) continue;
669
+ if (keys.has(column.name)) continue;
620
670
  for (const part of RESOLVED_VIEW_CELL_PARTS) {
621
671
  expected.set(cellColumnName(column.name, part), { part, columnName: cellColumnName(column.columnName, part) });
622
672
  }
@@ -984,7 +1034,7 @@ var canonicalTableSchema = z.object({
984
1034
  table: identifier2,
985
1035
  displayName: z.string().trim().min(1).max(120).optional(),
986
1036
  description: z.string().max(2e3).optional(),
987
- /** Table-level rename hint (new in v2). */
1037
+ /** Table-level rename hint. */
988
1038
  renamedFrom: identifier2.optional(),
989
1039
  /** Synced/reflected read-only tables (`source: schema.table`) — v1 semantics. */
990
1040
  source: z.string().regex(QUALIFIED_TABLE_PATTERN).optional(),
@@ -1842,8 +1892,13 @@ var resolvedSourceIrSchema = z.object({
1842
1892
  }).optional()
1843
1893
  });
1844
1894
  var resolvedMetaIrSchema = z.object({
1845
- /** Logical pk column of the resolved view (single-column). */
1846
- pk: z.string(),
1895
+ /**
1896
+ * Logical pk column of the resolved view. Absent for a composite key, which
1897
+ * only a read-only `existsIn` table may declare: it has no writes, prefetch
1898
+ * or materialization (the paths that address one key column), and its reads
1899
+ * use the view's `primaryKey` like any keyed view.
1900
+ */
1901
+ pk: z.string().optional(),
1847
1902
  /** Source name of the manual source. */
1848
1903
  manual: z.string(),
1849
1904
  sources: z.array(resolvedSourceIrSchema),
@@ -3268,7 +3323,7 @@ function buildCompileContext(rawCanonical) {
3268
3323
  appendable: readOnly ? [] : active.filter((c) => c.type === "jsonb").map((c) => c.name),
3269
3324
  ...touchOnUpdate.length > 0 ? { touchOnUpdate } : {},
3270
3325
  ...cells ? { cells } : {},
3271
- ...resolved ? { resolved: resolvedMeta({ resolved, pk: primaryKey2[0] }) } : {},
3326
+ ...resolved ? { resolved: resolvedMeta({ resolved, primaryKey: primaryKey2 }) } : {},
3272
3327
  constraints: isView ? [] : constraints,
3273
3328
  dynamicFilterMaxDepth: 6,
3274
3329
  dynamicFilterMaxNodes: 64
@@ -3550,10 +3605,10 @@ function resolvedWritable(entries) {
3550
3605
  const system = new Set(Object.values(RESOLVED_VIEW_SYSTEM_COLUMNS).map((column) => column.name));
3551
3606
  return entries.filter((entry) => !entry.deprecated && !system.has(entry.name)).map((entry) => entry.name);
3552
3607
  }
3553
- function resolvedMeta({ resolved, pk }) {
3608
+ function resolvedMeta({ resolved, primaryKey: primaryKey2 }) {
3554
3609
  const manual = resolved.sources.find((source) => source.kind === "manual");
3555
3610
  return {
3556
- pk,
3611
+ ...primaryKey2.length === 1 ? { pk: primaryKey2[0] } : {},
3557
3612
  manual: manual?.name ?? "",
3558
3613
  deletedBy: [...resolved.deletedBy],
3559
3614
  ...resolved.materialized ? { materialized: { ...resolved.materialized } } : {},
@@ -3679,7 +3734,9 @@ function applyPrefetchDirectives({ node, root, meta }) {
3679
3734
  if (directive.maxStaleness !== void 0 && maxStalenessMs === void 0) {
3680
3735
  fail(`@prefetch(maxStaleness: "${directive.maxStaleness}") must look like '500ms', '30s' or '5m'`, "invalid-source-directive");
3681
3736
  }
3682
- const key = root.kind === "byPk" ? { one: root.pk[0].value } : selectKey({ where: root.where, pk: meta.resolved.pk, source, alias: root.alias });
3737
+ const pk = meta.resolved?.pk;
3738
+ if (pk === void 0) return fail(`@prefetch on '${root.alias}' needs a single-column primary key`, "invalid-source-directive");
3739
+ const key = root.kind === "byPk" ? { one: root.pk[0].value } : selectKey({ where: root.where, pk, source, alias: root.alias });
3683
3740
  return { source: source.name, ...maxStalenessMs !== void 0 ? { maxStalenessMs } : {}, key };
3684
3741
  });
3685
3742
  return { ...root, prefetch: plans };
@@ -6575,6 +6632,7 @@ function buildCanonicalResolved(resolved) {
6575
6632
  })),
6576
6633
  resolve: resolved.rules.map((rule) => ({ ...rule, candidates: [...rule.candidates] })),
6577
6634
  deletedBy: [...resolved.deletedBy],
6635
+ ...resolved.existsIn !== void 0 ? { existsIn: [...resolved.existsIn] } : {},
6578
6636
  ...resolved.materialized ? { materialized: { ...resolved.materialized } } : {},
6579
6637
  policies
6580
6638
  };