@sequenceholdings/orm 2.8.4 → 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.
@@ -265,6 +265,48 @@ var policyExpressionSchema = zod.z.union([
265
265
  variants: zod.z.array(policyVariantSchema).max(10).default([])
266
266
  })
267
267
  ]);
268
+
269
+ // src/canonical/resolved-rows.ts
270
+ function logicalKey(table) {
271
+ return (table.primaryKey ?? []).map((columnName) => table.columns.find((c) => c.columnName === columnName)?.name ?? columnName);
272
+ }
273
+ function sameKey(key, expected) {
274
+ return key.length === expected.length && key.every((name) => expected.includes(name));
275
+ }
276
+ function validateRowIdentity(input) {
277
+ validateExistsIn(input);
278
+ if (input.keys.length > 1) validateCompositeKey(input);
279
+ }
280
+ function validateExistsIn({ resolved, sourceByName, fail }) {
281
+ const name = resolved.existsIn?.[0];
282
+ if (name === void 0) return;
283
+ if (sourceByName.get(name)?.kind !== "etl") fail(`existsIn '${name}' must be an etl source`);
284
+ if (resolved.policies.insert !== void 0 || resolved.policies.write !== void 0) {
285
+ fail("existsIn allows only an update policy: rows come from the etl source");
286
+ }
287
+ }
288
+ function validateCompositeKey({ resolved, keys, tableByName, fail }) {
289
+ const keyList = `(${keys.join(", ")})`;
290
+ if (resolved.existsIn === void 0) {
291
+ fail(`requires exactly one primary-key column: composite key ${keyList} requires existsIn`);
292
+ return;
293
+ }
294
+ const writes = ["insert", "update", "write"].filter((kind) => resolved.policies[kind] !== void 0);
295
+ if (writes.length > 0) fail(`composite key ${keyList} requires a read-only table: remove the ${writes.join("/")} policy`);
296
+ if (resolved.materialized) fail(`composite key ${keyList} cannot be materialized`);
297
+ for (const source of resolved.sources) {
298
+ if (source.kind === "prefetch") {
299
+ fail(`composite key ${keyList} cannot have prefetch source '${source.name}'`);
300
+ continue;
301
+ }
302
+ const table = tableByName.get(source.table);
303
+ if (table && !sameKey(logicalKey(table), keys)) {
304
+ fail(`composite key ${keyList} requires ${source.kind} source '${source.name}' to be keyed by it; table '${source.table}' is keyed by (${logicalKey(table).join(", ")})`);
305
+ }
306
+ }
307
+ }
308
+
309
+ // src/canonical/resolved.ts
268
310
  var identifier = zod.z.string().min(1).max(63).regex(/^[a-z][a-z0-9_]*$/);
269
311
  var durationMs = zod.z.number().int().positive().max(24 * 60 * 60 * 1e3);
270
312
  var RESOLVED_VIEW_SYSTEM_COLUMNS = {
@@ -333,6 +375,13 @@ var canonicalResolvedSchema = zod.z.object({
333
375
  resolve: zod.z.array(canonicalResolveRuleSchema).max(500),
334
376
  /** Sources whose tombstone hides the resolved row (v1: the manual source only). */
335
377
  deletedBy: zod.z.array(identifier).max(1).default([]),
378
+ /**
379
+ * `rows.existsIn`: the one etl source whose rows are the table's rows (read
380
+ * directly as the row source). Only an update policy may write. It is also
381
+ * what admits a composite primary key: a read-only table keyed exactly like
382
+ * that source.
383
+ */
384
+ existsIn: zod.z.array(identifier).length(1).optional(),
336
385
  /**
337
386
  * Materialized: the view reads an indexed internal table (`table`) that the
338
387
  * data plane keeps in step with `compute`, the internal view holding the
@@ -351,8 +400,8 @@ function validateResolvedView({ view, tables, views = [] }) {
351
400
  if (!resolved) return [];
352
401
  const errors = [];
353
402
  const fail = (message) => errors.push(`resolved table '${view.view}': ${message}`);
354
- const pkColumn = view.primaryKey?.length === 1 ? view.columns.find((c) => c.columnName === view.primaryKey?.[0]) : void 0;
355
- if (!pkColumn) {
403
+ const keyColumns = (view.primaryKey ?? []).map((columnName) => view.columns.find((c) => c.columnName === columnName)).filter((column) => column !== void 0);
404
+ if (keyColumns.length === 0 || keyColumns.length !== view.primaryKey?.length) {
356
405
  fail("requires exactly one primary-key column");
357
406
  return errors;
358
407
  }
@@ -360,7 +409,7 @@ function validateResolvedView({ view, tables, views = [] }) {
360
409
  const scope = {
361
410
  view,
362
411
  resolved,
363
- pkColumn,
412
+ keys: new Map(keyColumns.map((column) => [column.name, column])),
364
413
  dataColumns: new Map(view.columns.filter((c) => !systemNames.has(c.name) && c.cell === void 0).map((c) => [c.name, c])),
365
414
  sourceByName: /* @__PURE__ */ new Map(),
366
415
  fail
@@ -374,6 +423,7 @@ function validateResolvedView({ view, tables, views = [] }) {
374
423
  if (!table) fail(`source '${source.name}' references unknown table '${source.table}'`);
375
424
  else validateSource({ scope, source, table });
376
425
  }
426
+ validateRowIdentity({ resolved, keys: [...scope.keys.keys()], sourceByName: scope.sourceByName, tableByName, fail });
377
427
  validateRules(scope);
378
428
  validateViewCells(scope);
379
429
  if (resolved.materialized) validateMaterialized({ scope, tableByName, views });
@@ -403,7 +453,7 @@ function validateSource({ scope, source, table }) {
403
453
  validatePrefetchFields({ scope, source });
404
454
  }
405
455
  function validatePrefetchFields({ scope, source }) {
406
- const { fail, pkColumn } = scope;
456
+ const { fail, keys } = scope;
407
457
  if (source.kind !== "prefetch") {
408
458
  if (source.key || source.fetch || source.function || source.prefetchDefaults || source.writeback) {
409
459
  fail(`only prefetch sources may declare key/fetch/function/prefetchDefaults/writeback ('${source.name}')`);
@@ -412,7 +462,7 @@ function validatePrefetchFields({ scope, source }) {
412
462
  }
413
463
  if (!source.key || !source.fetch || !source.function || !source.prefetchDefaults) {
414
464
  fail(`prefetch source '${source.name}' requires key, fetch, function and prefetchDefaults`);
415
- } else if (source.key !== pkColumn.name && !source.columns.includes(source.key)) {
465
+ } else if (!keys.has(source.key) && !source.columns.includes(source.key)) {
416
466
  fail(`prefetch source '${source.name}' key '${source.key}' must be the primary key or one of its columns`);
417
467
  }
418
468
  for (const column of source.writeback?.columns ?? []) {
@@ -420,19 +470,19 @@ function validatePrefetchFields({ scope, source }) {
420
470
  }
421
471
  }
422
472
  function validateSourceColumns({ scope, source, byName }) {
423
- const { fail, pkColumn, dataColumns } = scope;
424
- for (const column of [pkColumn.name, ...source.columns]) {
425
- const own = column === pkColumn.name ? pkColumn : dataColumns.get(column);
473
+ const { fail, keys, dataColumns } = scope;
474
+ for (const column of [...keys.keys(), ...source.columns]) {
475
+ const own = keys.get(column) ?? dataColumns.get(column);
426
476
  const theirs = byName.get(column);
427
477
  if (!own) fail(`source '${source.name}' supplies unknown column '${column}'`);
428
478
  else if (!theirs) fail(`source '${source.name}' table '${source.table}' has no column '${column}'`);
429
479
  else if (JSON.stringify(theirs.type) !== JSON.stringify(own.type)) fail(`column '${column}' has a different type on source '${source.name}'`);
430
480
  }
431
481
  }
432
- function validateRules({ resolved, pkColumn, dataColumns, sourceByName, fail }) {
482
+ function validateRules({ resolved, keys, dataColumns, sourceByName, fail }) {
433
483
  const ruled = /* @__PURE__ */ new Set();
434
484
  for (const rule of resolved.resolve) {
435
- if (!dataColumns.has(rule.column) || rule.column === pkColumn.name) {
485
+ if (!dataColumns.has(rule.column) || keys.has(rule.column)) {
436
486
  fail(`resolve rule targets unknown column '${rule.column}'`);
437
487
  continue;
438
488
  }
@@ -449,7 +499,7 @@ function validateRules({ resolved, pkColumn, dataColumns, sourceByName, fail })
449
499
  }
450
500
  }
451
501
  for (const column of dataColumns.keys()) {
452
- if (column !== pkColumn.name && !ruled.has(column)) fail(`column '${column}' has no resolve rule`);
502
+ if (!keys.has(column) && !ruled.has(column)) fail(`column '${column}' has no resolve rule`);
453
503
  }
454
504
  }
455
505
  var VIEW_CELL_TYPES = {
@@ -458,10 +508,10 @@ var VIEW_CELL_TYPES = {
458
508
  by: "text",
459
509
  conflicts: "jsonb"
460
510
  };
461
- function validateViewCells({ view, dataColumns, pkColumn, fail }) {
511
+ function validateViewCells({ view, dataColumns, keys, fail }) {
462
512
  const expected = /* @__PURE__ */ new Map();
463
513
  for (const column of dataColumns.values()) {
464
- if (column.name === pkColumn.name) continue;
514
+ if (keys.has(column.name)) continue;
465
515
  for (const part of RESOLVED_VIEW_CELL_PARTS) {
466
516
  expected.set(cellColumnName(column.name, part), { part, columnName: cellColumnName(column.columnName, part) });
467
517
  }
@@ -1471,8 +1521,13 @@ var resolvedSourceIrSchema = zod.z.object({
1471
1521
  }).optional()
1472
1522
  });
1473
1523
  var resolvedMetaIrSchema = zod.z.object({
1474
- /** Logical pk column of the resolved view (single-column). */
1475
- pk: zod.z.string(),
1524
+ /**
1525
+ * Logical pk column of the resolved view. Absent for a composite key, which
1526
+ * only a read-only `existsIn` table may declare: it has no writes, prefetch
1527
+ * or materialization (the paths that address one key column), and its reads
1528
+ * use the view's `primaryKey` like any keyed view.
1529
+ */
1530
+ pk: zod.z.string().optional(),
1476
1531
  /** Source name of the manual source. */
1477
1532
  manual: zod.z.string(),
1478
1533
  sources: zod.z.array(resolvedSourceIrSchema),