@sequenceholdings/orm 2.8.2 → 2.8.4

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 (53) hide show
  1. package/README.md +5 -0
  2. package/dist/authoring.cjs +31 -8
  3. package/dist/authoring.cjs.map +1 -1
  4. package/dist/authoring.d.cts +8 -2
  5. package/dist/authoring.d.ts +8 -2
  6. package/dist/authoring.js +31 -8
  7. package/dist/authoring.js.map +1 -1
  8. package/dist/canonical.cjs +1 -1
  9. package/dist/canonical.cjs.map +1 -1
  10. package/dist/canonical.d.cts +1 -1
  11. package/dist/canonical.d.ts +1 -1
  12. package/dist/canonical.js +1 -1
  13. package/dist/canonical.js.map +1 -1
  14. package/dist/cli.cjs +27 -24
  15. package/dist/cli.cjs.map +1 -1
  16. package/dist/cli.js +31 -28
  17. package/dist/cli.js.map +1 -1
  18. package/dist/compiler.cjs +1 -1
  19. package/dist/compiler.cjs.map +1 -1
  20. package/dist/compiler.d.cts +4 -4
  21. package/dist/compiler.d.ts +4 -4
  22. package/dist/compiler.js +1 -1
  23. package/dist/compiler.js.map +1 -1
  24. package/dist/index.cjs +31 -8
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +7 -9
  27. package/dist/index.d.ts +7 -9
  28. package/dist/index.js +31 -8
  29. package/dist/index.js.map +1 -1
  30. package/dist/ir.cjs +1 -1
  31. package/dist/ir.cjs.map +1 -1
  32. package/dist/ir.d.cts +2 -2
  33. package/dist/ir.d.ts +2 -2
  34. package/dist/ir.js +1 -1
  35. package/dist/ir.js.map +1 -1
  36. package/dist/{naming-CU9ThP_E.d.cts → naming-BMZKFO0W.d.cts} +1 -2
  37. package/dist/{naming-CPfx7EfF.d.ts → naming-DUk7AcXs.d.ts} +1 -2
  38. package/dist/predicate-v2.cjs +1 -1
  39. package/dist/predicate-v2.cjs.map +1 -1
  40. package/dist/predicate-v2.d.cts +3 -3
  41. package/dist/predicate-v2.d.ts +3 -3
  42. package/dist/predicate-v2.js +1 -1
  43. package/dist/predicate-v2.js.map +1 -1
  44. package/dist/sdl.cjs +1 -1
  45. package/dist/sdl.cjs.map +1 -1
  46. package/dist/sdl.d.cts +2 -2
  47. package/dist/sdl.d.ts +2 -2
  48. package/dist/sdl.js +1 -1
  49. package/dist/sdl.js.map +1 -1
  50. package/package.json +1 -1
  51. package/src/codegen-gql/apollo-hooks.cjs +1 -1
  52. package/src/codegen-gql/server-fns.cjs +1 -1
  53. package/templates/orm-namespace/graphql/example/queries.ts +1 -1
@@ -286,7 +286,7 @@ declare const nonNullFirst: (candidates: readonly string[]) => Comparator;
286
286
  * wins; `ord` is always appended as the final tie-break.
287
287
  *
288
288
  * ```ts
289
- * custom(['edits', 'abrigo'], "CASE WHEN source = 'abrigo' AND value IS NOT NULL THEN 0 ELSE 1 END")
289
+ * custom(['edits', 'vendor'], "CASE WHEN source = 'vendor' AND value IS NOT NULL THEN 0 ELSE 1 END")
290
290
  * ```
291
291
  */
292
292
  declare const custom: (candidates: readonly string[], orderBy: string) => Comparator;
@@ -305,6 +305,12 @@ interface ResolvedTableConfig {
305
305
  default?: Comparator;
306
306
  rows?: {
307
307
  deletedBy?: readonly string[];
308
+ /**
309
+ * A row exists only while this etl source has its key; edits and other
310
+ * sources only change values. A column resolved from this source alone
311
+ * keeps the synced column's NOT NULL. Exactly one etl source.
312
+ */
313
+ existsIn?: readonly string[];
308
314
  };
309
315
  links?: Record<string, SeqLink>;
310
316
  /** Reads use `read`; writes are evaluated against the resolved row. */
@@ -547,7 +553,7 @@ interface ForeignKeyInput {
547
553
  interface TableConfig {
548
554
  displayName?: string;
549
555
  description?: string;
550
- /** Table-level migration rename hint (new in v2). */
556
+ /** Table-level migration rename hint. */
551
557
  renamedFrom?: string;
552
558
  /** Composite primary key: property names (or column names). Mutually exclusive with column-level `.primaryKey()`. */
553
559
  primaryKey?: readonly string[];
@@ -286,7 +286,7 @@ declare const nonNullFirst: (candidates: readonly string[]) => Comparator;
286
286
  * wins; `ord` is always appended as the final tie-break.
287
287
  *
288
288
  * ```ts
289
- * custom(['edits', 'abrigo'], "CASE WHEN source = 'abrigo' AND value IS NOT NULL THEN 0 ELSE 1 END")
289
+ * custom(['edits', 'vendor'], "CASE WHEN source = 'vendor' AND value IS NOT NULL THEN 0 ELSE 1 END")
290
290
  * ```
291
291
  */
292
292
  declare const custom: (candidates: readonly string[], orderBy: string) => Comparator;
@@ -305,6 +305,12 @@ interface ResolvedTableConfig {
305
305
  default?: Comparator;
306
306
  rows?: {
307
307
  deletedBy?: readonly string[];
308
+ /**
309
+ * A row exists only while this etl source has its key; edits and other
310
+ * sources only change values. A column resolved from this source alone
311
+ * keeps the synced column's NOT NULL. Exactly one etl source.
312
+ */
313
+ existsIn?: readonly string[];
308
314
  };
309
315
  links?: Record<string, SeqLink>;
310
316
  /** Reads use `read`; writes are evaluated against the resolved row. */
@@ -547,7 +553,7 @@ interface ForeignKeyInput {
547
553
  interface TableConfig {
548
554
  displayName?: string;
549
555
  description?: string;
550
- /** Table-level migration rename hint (new in v2). */
556
+ /** Table-level migration rename hint. */
551
557
  renamedFrom?: string;
552
558
  /** Composite primary key: property names (or column names). Mutually exclusive with column-level `.primaryKey()`. */
553
559
  primaryKey?: readonly string[];
package/dist/authoring.js CHANGED
@@ -1695,7 +1695,7 @@ var canonicalTableSchema = z.object({
1695
1695
  table: identifier2,
1696
1696
  displayName: z.string().trim().min(1).max(120).optional(),
1697
1697
  description: z.string().max(2e3).optional(),
1698
- /** Table-level rename hint (new in v2). */
1698
+ /** Table-level rename hint. */
1699
1699
  renamedFrom: identifier2.optional(),
1700
1700
  /** Synced/reflected read-only tables (`source: schema.table`) — v1 semantics. */
1701
1701
  source: z.string().regex(QUALIFIED_TABLE_PATTERN).optional(),
@@ -2359,6 +2359,12 @@ function builtinCell({ comparator, candidates }) {
2359
2359
  }
2360
2360
  };
2361
2361
  }
2362
+ function existsInCell(candidate) {
2363
+ return {
2364
+ value: candidate.value,
2365
+ parts: { source: literal(candidate.source), at: candidate.at, by: candidate.by, conflicts: "CAST(NULL AS jsonb)" }
2366
+ };
2367
+ }
2362
2368
  function customCell({ candidates, orderBy, index }) {
2363
2369
  const w = ident(`w_${index}`);
2364
2370
  const rows = candidates.map((c, ord) => `(${literal(c.source)}, ${c.has}, ${c.value}, ${c.at}, ${c.by}, ${ord})`);
@@ -2382,26 +2388,29 @@ function buildResolvedQuery({ context, plan }) {
2382
2388
  const tombstoned2 = RESOLVED_SOURCE_SYSTEM_COLUMNS.tombstoned.columnName;
2383
2389
  const sourceByName = new Map(plan.plans.map((source) => [source.name, source]));
2384
2390
  const schema = getTableConfig(relation(plan.plans[0].table)).schema;
2391
+ const keySource = plan.existsIn === void 0 ? void 0 : sourceByName.get(plan.existsIn);
2392
+ const keyRef = keySource ? `${alias(keySource.name)}.${ident(physical({ source: keySource, property: plan.pk }))}` : `"k".${ident(pkColumn)}`;
2385
2393
  const keyBranches = plan.plans.map((source) => {
2386
2394
  const where = source.kind === "prefetch" ? sql.raw(` WHERE NOT ${ident(tombstoned2)}`) : sql.raw("");
2387
2395
  const sourcePk = ident(physical({ source, property: plan.pk }));
2388
2396
  const selectedPk = sourcePk === ident(pkColumn) ? sourcePk : `${sourcePk} AS ${ident(pkColumn)}`;
2389
2397
  return sql`SELECT ${sql.raw(selectedPk)} FROM ${relation(source.table)}${where}`;
2390
2398
  });
2391
- const joins = plan.plans.map(
2392
- (source) => sql` LEFT JOIN ${relation(source.table)} AS ${sql.raw(alias(source.name))} ON ${sql.raw(`${alias(source.name)}.${ident(physical({ source, property: plan.pk }))} = "k".${ident(pkColumn)}`)}`
2399
+ const base2 = keySource ? sql`${relation(keySource.table)} AS ${sql.raw(alias(keySource.name))}` : sql`(${sql.join(keyBranches, sql.raw(" UNION "))}) AS "k"`;
2400
+ const joins = plan.plans.filter((source) => source !== keySource).map(
2401
+ (source) => sql` LEFT JOIN ${relation(source.table)} AS ${sql.raw(alias(source.name))} ON ${sql.raw(`${alias(source.name)}.${ident(physical({ source, property: plan.pk }))} = ${keyRef}`)}`
2393
2402
  );
2394
2403
  const cells = /* @__PURE__ */ new Map();
2395
2404
  plan.rules.forEach((rule, index) => {
2396
2405
  const canonical = plan.columns[rule.column].toCanonicalColumn(rule.column);
2397
2406
  const pgType = typeof canonical.type === "string" ? PG_TYPE[canonical.type] : `${schema === void 0 ? "" : `${ident(schema)}.`}${ident(canonical.type.enum)}`;
2398
2407
  const candidates = rule.candidates.map((name2) => candidateSql({ source: sourceByName.get(name2), column: rule.column, pk: plan.pk, pgType }));
2399
- cells.set(rule.column, rule.comparator === "custom" ? customCell({ candidates, orderBy: rule.orderBy, index }) : builtinCell({ comparator: rule.comparator, candidates }));
2408
+ cells.set(rule.column, keySource && rule.candidates.length === 1 && rule.candidates[0] === keySource.name ? existsInCell(candidates[0]) : rule.comparator === "custom" ? customCell({ candidates, orderBy: rule.orderBy, index }) : builtinCell({ comparator: rule.comparator, candidates }));
2400
2409
  });
2401
2410
  const laterals = [...cells.values()].flatMap((cell) => cell.lateral ? [sql.raw(cell.lateral)] : []);
2402
- const from = sql.join([sql`(${sql.join(keyBranches, sql.raw(" UNION "))}) AS "k"`, ...joins, ...laterals], sql.raw(""));
2411
+ const from = sql.join([base2, ...joins, ...laterals], sql.raw(""));
2403
2412
  const selected = {
2404
- [plan.pk]: sql.raw(`"k".${ident(pkColumn)}`).as(pkColumn)
2413
+ [plan.pk]: sql.raw(keyRef).as(pkColumn)
2405
2414
  };
2406
2415
  for (const [property, builder] of Object.entries(plan.columns)) {
2407
2416
  if (property === plan.pk) continue;
@@ -2473,10 +2482,12 @@ function defineResolvedTable(name2, columns, config) {
2473
2482
  for (const source of deletedBy) {
2474
2483
  if (plans.find((plan) => plan.name === source)?.kind !== "manual") fail(`rows.deletedBy "${source}" must be the manual source`);
2475
2484
  }
2485
+ const existsIn = planExistsIn({ config, plans, fail });
2486
+ const notNullColumns = existsIn ? inheritedNotNullColumns({ existsIn, rules }) : /* @__PURE__ */ new Set();
2476
2487
  const partType = { source: t.text, at: t.timestamptz, by: t.text, conflicts: t.jsonb };
2477
2488
  const viewColumns = {};
2478
2489
  for (const [property, builder] of Object.entries(columns)) {
2479
- viewColumns[property] = cloneColumn({ builder, property, notNull: property === pk, primaryKey: property === pk });
2490
+ viewColumns[property] = cloneColumn({ builder, property, notNull: property === pk || notNullColumns.has(property), primaryKey: property === pk });
2480
2491
  if (property === pk) continue;
2481
2492
  const columnName = builder.columnNameFor(property);
2482
2493
  for (const part of RESOLVED_VIEW_CELL_PARTS) {
@@ -2492,7 +2503,7 @@ function defineResolvedTable(name2, columns, config) {
2492
2503
  deletedBy,
2493
2504
  policies: writePolicies
2494
2505
  };
2495
- const queryPlan = { columns, pk, plans, rules, deletedBy };
2506
+ const queryPlan = { columns, pk, plans, rules, deletedBy, ...existsIn ? { existsIn: existsIn.name } : {} };
2496
2507
  const typedColumns = viewColumns;
2497
2508
  const surface = {
2498
2509
  ...config.description !== void 0 ? { description: config.description } : {},
@@ -2534,6 +2545,18 @@ function defineResolvedTable(name2, columns, config) {
2534
2545
  }
2535
2546
  });
2536
2547
  }
2548
+ function planExistsIn({ config, plans, fail }) {
2549
+ const names = config.rows?.existsIn;
2550
+ if (names === void 0) return void 0;
2551
+ if (names.length !== 1) fail("rows.existsIn names exactly one etl source");
2552
+ const source = plans.find((plan) => plan.name === names[0]);
2553
+ if (source?.kind !== "etl") fail(`rows.existsIn "${names[0]}" must be an etl source`);
2554
+ if (config.policies.insert !== void 0 || config.policies.write !== void 0) fail("rows.existsIn allows only an update policy: rows come from the etl source");
2555
+ return source;
2556
+ }
2557
+ function inheritedNotNullColumns({ existsIn, rules }) {
2558
+ return new Set(rules.filter((rule) => rule.candidates.length === 1 && rule.candidates[0] === existsIn.name).map((rule) => rule.column).filter((column) => existsIn.table.columns[column]?.toCanonicalColumn(column).nullable === false));
2559
+ }
2537
2560
  function planRules({ config, plans, dataColumns, fail }) {
2538
2561
  const kindOrder = { manual: 0, prefetch: 1, etl: 2 };
2539
2562
  const fallback = config.default ?? priority([...plans].sort((a, b) => kindOrder[a.kind] - kindOrder[b.kind]).map((plan) => plan.name));