litedbmodel 1.2.10 → 2.0.1

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 (143) hide show
  1. package/README.md +1 -1
  2. package/dist/DBModel.d.ts.map +1 -1
  3. package/dist/DBModel.js +6 -0
  4. package/dist/DBModel.js.map +1 -1
  5. package/dist/scp/authoring-sql.d.ts +134 -0
  6. package/dist/scp/authoring-sql.d.ts.map +1 -0
  7. package/dist/scp/authoring-sql.js +191 -0
  8. package/dist/scp/authoring-sql.js.map +1 -0
  9. package/dist/scp/authoring.d.ts +155 -0
  10. package/dist/scp/authoring.d.ts.map +1 -0
  11. package/dist/scp/authoring.js +162 -0
  12. package/dist/scp/authoring.js.map +1 -0
  13. package/dist/scp/catalog.d.ts +91 -0
  14. package/dist/scp/catalog.d.ts.map +1 -0
  15. package/dist/scp/catalog.js +230 -0
  16. package/dist/scp/catalog.js.map +1 -0
  17. package/dist/scp/codegen.d.ts +95 -0
  18. package/dist/scp/codegen.d.ts.map +1 -0
  19. package/dist/scp/codegen.js +94 -0
  20. package/dist/scp/codegen.js.map +1 -0
  21. package/dist/scp/dialect.d.ts +141 -0
  22. package/dist/scp/dialect.d.ts.map +1 -0
  23. package/dist/scp/dialect.js +194 -0
  24. package/dist/scp/dialect.js.map +1 -0
  25. package/dist/scp/errors.d.ts +36 -0
  26. package/dist/scp/errors.d.ts.map +1 -0
  27. package/dist/scp/errors.js +72 -0
  28. package/dist/scp/errors.js.map +1 -0
  29. package/dist/scp/find-filter-guard.d.ts +68 -0
  30. package/dist/scp/find-filter-guard.d.ts.map +1 -0
  31. package/dist/scp/find-filter-guard.js +88 -0
  32. package/dist/scp/find-filter-guard.js.map +1 -0
  33. package/dist/scp/fragment/assemble.d.ts +61 -0
  34. package/dist/scp/fragment/assemble.d.ts.map +1 -0
  35. package/dist/scp/fragment/assemble.js +148 -0
  36. package/dist/scp/fragment/assemble.js.map +1 -0
  37. package/dist/scp/fragment/compile-pg.d.ts +107 -0
  38. package/dist/scp/fragment/compile-pg.d.ts.map +1 -0
  39. package/dist/scp/fragment/compile-pg.js +142 -0
  40. package/dist/scp/fragment/compile-pg.js.map +1 -0
  41. package/dist/scp/fragment/index.d.ts +16 -0
  42. package/dist/scp/fragment/index.d.ts.map +1 -0
  43. package/dist/scp/fragment/index.js +34 -0
  44. package/dist/scp/fragment/index.js.map +1 -0
  45. package/dist/scp/fragment/model.d.ts +119 -0
  46. package/dist/scp/fragment/model.d.ts.map +1 -0
  47. package/dist/scp/fragment/model.js +66 -0
  48. package/dist/scp/fragment/model.js.map +1 -0
  49. package/dist/scp/fragment/pg-array-type.d.ts +13 -0
  50. package/dist/scp/fragment/pg-array-type.d.ts.map +1 -0
  51. package/dist/scp/fragment/pg-array-type.js +34 -0
  52. package/dist/scp/fragment/pg-array-type.js.map +1 -0
  53. package/dist/scp/guard.d.ts +43 -0
  54. package/dist/scp/guard.d.ts.map +1 -0
  55. package/dist/scp/guard.js +134 -0
  56. package/dist/scp/guard.js.map +1 -0
  57. package/dist/scp/index.cjs +12202 -0
  58. package/dist/scp/index.d.ts +64 -0
  59. package/dist/scp/index.d.ts.map +1 -0
  60. package/dist/scp/index.js +238 -0
  61. package/dist/scp/index.js.map +1 -0
  62. package/dist/scp/index.mjs +4819 -0
  63. package/dist/scp/makesql/authoring-compile.d.ts +90 -0
  64. package/dist/scp/makesql/authoring-compile.d.ts.map +1 -0
  65. package/dist/scp/makesql/authoring-compile.js +494 -0
  66. package/dist/scp/makesql/authoring-compile.js.map +1 -0
  67. package/dist/scp/makesql/compile-crud.d.ts +133 -0
  68. package/dist/scp/makesql/compile-crud.d.ts.map +1 -0
  69. package/dist/scp/makesql/compile-crud.js +288 -0
  70. package/dist/scp/makesql/compile-crud.js.map +1 -0
  71. package/dist/scp/makesql/compile-relation.d.ts +157 -0
  72. package/dist/scp/makesql/compile-relation.d.ts.map +1 -0
  73. package/dist/scp/makesql/compile-relation.js +425 -0
  74. package/dist/scp/makesql/compile-relation.js.map +1 -0
  75. package/dist/scp/makesql/compile-select.d.ts +45 -0
  76. package/dist/scp/makesql/compile-select.d.ts.map +1 -0
  77. package/dist/scp/makesql/compile-select.js +58 -0
  78. package/dist/scp/makesql/compile-select.js.map +1 -0
  79. package/dist/scp/makesql/compile.d.ts +63 -0
  80. package/dist/scp/makesql/compile.d.ts.map +1 -0
  81. package/dist/scp/makesql/compile.js +99 -0
  82. package/dist/scp/makesql/compile.js.map +1 -0
  83. package/dist/scp/makesql/handler.d.ts +58 -0
  84. package/dist/scp/makesql/handler.d.ts.map +1 -0
  85. package/dist/scp/makesql/handler.js +113 -0
  86. package/dist/scp/makesql/handler.js.map +1 -0
  87. package/dist/scp/makesql/index.d.ts +33 -0
  88. package/dist/scp/makesql/index.d.ts.map +1 -0
  89. package/dist/scp/makesql/index.js +114 -0
  90. package/dist/scp/makesql/index.js.map +1 -0
  91. package/dist/scp/makesql/ir.d.ts +42 -0
  92. package/dist/scp/makesql/ir.d.ts.map +1 -0
  93. package/dist/scp/makesql/ir.js +66 -0
  94. package/dist/scp/makesql/ir.js.map +1 -0
  95. package/dist/scp/makesql/json-array.d.ts +84 -0
  96. package/dist/scp/makesql/json-array.d.ts.map +1 -0
  97. package/dist/scp/makesql/json-array.js +118 -0
  98. package/dist/scp/makesql/json-array.js.map +1 -0
  99. package/dist/scp/makesql/json-batch.d.ts +102 -0
  100. package/dist/scp/makesql/json-batch.d.ts.map +1 -0
  101. package/dist/scp/makesql/json-batch.js +269 -0
  102. package/dist/scp/makesql/json-batch.js.map +1 -0
  103. package/dist/scp/makesql/makesql.d.ts +99 -0
  104. package/dist/scp/makesql/makesql.d.ts.map +1 -0
  105. package/dist/scp/makesql/makesql.js +122 -0
  106. package/dist/scp/makesql/makesql.js.map +1 -0
  107. package/dist/scp/makesql/pool-executor.d.ts +47 -0
  108. package/dist/scp/makesql/pool-executor.d.ts.map +1 -0
  109. package/dist/scp/makesql/pool-executor.js +50 -0
  110. package/dist/scp/makesql/pool-executor.js.map +1 -0
  111. package/dist/scp/makesql/static-bundle.d.ts +185 -0
  112. package/dist/scp/makesql/static-bundle.d.ts.map +1 -0
  113. package/dist/scp/makesql/static-bundle.js +1024 -0
  114. package/dist/scp/makesql/static-bundle.js.map +1 -0
  115. package/dist/scp/makesql/tx.d.ts +216 -0
  116. package/dist/scp/makesql/tx.d.ts.map +1 -0
  117. package/dist/scp/makesql/tx.js +743 -0
  118. package/dist/scp/makesql/tx.js.map +1 -0
  119. package/dist/scp/relation.d.ts +148 -0
  120. package/dist/scp/relation.d.ts.map +1 -0
  121. package/dist/scp/relation.js +257 -0
  122. package/dist/scp/relation.js.map +1 -0
  123. package/dist/scp/runtime.d.ts +231 -0
  124. package/dist/scp/runtime.d.ts.map +1 -0
  125. package/dist/scp/runtime.js +381 -0
  126. package/dist/scp/runtime.js.map +1 -0
  127. package/dist/scp/typed-object.d.ts +99 -0
  128. package/dist/scp/typed-object.d.ts.map +1 -0
  129. package/dist/scp/typed-object.js +184 -0
  130. package/dist/scp/typed-object.js.map +1 -0
  131. package/dist/scp/writes.d.ts +251 -0
  132. package/dist/scp/writes.d.ts.map +1 -0
  133. package/dist/scp/writes.js +176 -0
  134. package/dist/scp/writes.js.map +1 -0
  135. package/docs/proposal/contracts-architecture-feasibility.md +328 -0
  136. package/docs/proposal/contracts-architecture.md +579 -0
  137. package/docs/proposal/litedbmodel-v2-scp-architecture.md +382 -0
  138. package/docs/proposal/sql-dynamic-expansion-spec.md +175 -0
  139. package/docs/proposal/v0-coverage-matrix.md +157 -0
  140. package/docs/proposal/v2-completeness-plan.md +90 -0
  141. package/docs/proposal/v2-sql-parity-checklist.md +162 -0
  142. package/package.json +27 -5
  143. package/docs/README_REVIEW.md +0 -23
@@ -0,0 +1,99 @@
1
+ /**
2
+ * litedbmodel v2 SCP — typed-object result + hydrate factory + lazy relations (WS4, #24).
3
+ *
4
+ * v2's read result is a plain **typed-object** (spec §4 / §12): its OWN enumerable props are
5
+ * DATA ONLY (the projected columns + any declaratively-selected relations). It is NOT a
6
+ * DBModel instance — `JSON.stringify` / `Object.keys` / spread stay clean (feasibility §9).
7
+ *
8
+ * The v1 method-like UX is recovered via a graphddb-shaped {@link hydrate} factory
9
+ * (`(raw) => HostObject`): host-object construction is a CONSUMER concern that stays in the
10
+ * litedbmodel runtime and NEVER leaks into the portable IR/bundle (per the runtime boundary —
11
+ * the bundle carries the relation op + assembly shape only). `hydrate` is applied AFTER
12
+ * relation resolution and is NOT applied to `null` (a null single-relation), matching
13
+ * graphddb's `options.hydrate`.
14
+ *
15
+ * ## Lazy relations: prototype getter + non-enumerable Symbol batch context
16
+ *
17
+ * A typed-object gets a per-result-set prototype carrying a lazy getter per relation
18
+ * (feasibility §9 "prototype で良い"). The getter, on access, reads a NON-ENUMERABLE Symbol
19
+ * batch context (graphddb's `GRAPHDDB_KEY` technique — invisible to spread/JSON) shared by
20
+ * every sibling in the result set, and fires the SAME pre-compiled relation op ONCE over the
21
+ * whole sibling set (batch — so no N+1 even though it can't prefetch). A declaratively-selected
22
+ * relation is attached as an OWN property (`Post[]`/`Author`) and naturally SHADOWS the
23
+ * prototype getter (own prop wins) — the exact "宣言済み → データ / 未宣言 → lazy" unification
24
+ * from feasibility §9.
25
+ *
26
+ * Both surfaces resolve through {@link import('./relation').runRelationOp} — the identical
27
+ * compiled op — so there is one relation-execution code path, not two.
28
+ */
29
+ import { type RelationOp, type RelationDriver } from './relation';
30
+ /** The non-enumerable Symbol under which the shared lazy batch context is stashed. */
31
+ export declare const RELATION_CONTEXT: unique symbol;
32
+ /** A hydrate factory (graphddb-shaped): raw typed-object → host object. */
33
+ export type HydrateFactory<R> = (raw: Record<string, unknown>) => R;
34
+ /**
35
+ * The shared lazy batch context for one result set. Holds the sibling rows, the driver, the
36
+ * compiled relation ops (keyed by name), the (optional) hydrate factory, and a per-relation
37
+ * memo so a relation is batched at most ONCE across all siblings (structural no-N+1 for lazy).
38
+ * Stored as a non-enumerable Symbol prop so spread/JSON/`Object.keys` never see it (a spread
39
+ * or clone drops it — "designed degradation", feasibility §9).
40
+ */
41
+ export declare class RelationContext {
42
+ private readonly siblings;
43
+ private readonly ops;
44
+ private readonly db;
45
+ private readonly hydrate?;
46
+ /** CROSS-DB (V0 R1): name → driver registry; a tagged relation routes to `connections[tag]`. */
47
+ private readonly connections?;
48
+ /** Per-relation resolved batch (name → grouping), memoized: one batch query per relation. */
49
+ private readonly resolved;
50
+ constructor(siblings: Record<string, unknown>[], ops: Readonly<Record<string, RelationOp>>, db: RelationDriver, hydrate?: HydrateFactory<unknown>, connections?: Readonly<Record<string, RelationDriver>>);
51
+ /** The driver a relation runs against: its tagged cross-DB connection, else the primary `db`. */
52
+ private driverFor;
53
+ /**
54
+ * Resolve `relationName` for `parent` (lazy access). The relation is batch-loaded across
55
+ * ALL siblings on first access and memoized, so N sibling accesses issue ONE query. Returns
56
+ * the hydrated child(ren) per cardinality (`hasMany` → array, else single-or-null).
57
+ */
58
+ resolve(relationName: string, parent: Record<string, unknown>): unknown;
59
+ }
60
+ /** Read the non-enumerable batch context off a typed-object (undefined if spread/cloned away). */
61
+ export declare function readRelationContext(o: Record<string, unknown>): RelationContext | undefined;
62
+ /**
63
+ * Read options bag (graphddb-shaped): declaratively-select relations to prefetch and/or a
64
+ * `hydrate` factory to recover host objects.
65
+ */
66
+ export interface ReadOptions<R = Record<string, unknown>> {
67
+ /**
68
+ * Declarative select: relation name → `true`. Selected relations are batch-prefetched
69
+ * (staged) and attached as OWN props (shadowing the lazy getter). Unselected relations stay
70
+ * lazy on the prototype.
71
+ */
72
+ readonly with?: Readonly<Record<string, true>>;
73
+ /** Host-object factory applied AFTER relation resolution (not applied to `null`). */
74
+ readonly hydrate?: HydrateFactory<R>;
75
+ /**
76
+ * CROSS-DB relations (V0 R1): connection-name → driver registry. A relation whose compiled op
77
+ * carries a `connection` tag (its target model lives in a DIFFERENT DB — v1
78
+ * `TargetClass.getDriverType()`) is batch-loaded against `connections[tag]` instead of the
79
+ * primary `db`. Untagged (same-DB) relations ignore this. Omit for a single-DB read.
80
+ */
81
+ readonly connections?: Readonly<Record<string, RelationDriver>>;
82
+ }
83
+ /**
84
+ * Build the typed-object result set for a page of raw child rows plus the relation ops
85
+ * declared for this model. Every raw row becomes a typed-object whose:
86
+ * - OWN enumerable props = the raw data columns (+ any declaratively-selected relation);
87
+ * - PROTOTYPE carries a lazy getter per UNSELECTED relation (fires the same op at access);
88
+ * - non-enumerable {@link RELATION_CONTEXT} Symbol = the shared lazy batch context.
89
+ *
90
+ * Declaratively-selected relations (`options.with`) are batch-prefetched HERE (one query per
91
+ * relation over the whole page — staged, no N+1) via the SAME {@link runRelationOp}, and
92
+ * attached as own props (so they shadow the prototype getter). `hydrate`, when supplied, is
93
+ * applied to each parent row AND to resolved relations.
94
+ *
95
+ * Returns the typed-objects (hydrated if `options.hydrate` given). This is the litedbmodel
96
+ * runtime concern; the bundle carries only the relation ops + assembly shape (no hydrate).
97
+ */
98
+ export declare function buildResultSet<R = Record<string, unknown>>(rawRows: readonly Record<string, unknown>[], ops: Readonly<Record<string, RelationOp>>, db: RelationDriver, options?: ReadOptions<R>): R[];
99
+ //# sourceMappingURL=typed-object.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typed-object.d.ts","sourceRoot":"","sources":["../../src/scp/typed-object.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,EAGL,KAAK,UAAU,EAEf,KAAK,cAAc,EACpB,MAAM,YAAY,CAAC;AAEpB,sFAAsF;AACtF,eAAO,MAAM,gBAAgB,EAAE,OAAO,MAA+C,CAAC;AAEtF,2EAA2E;AAC3E,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;AAEpE;;;;;;GAMG;AACH,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA4B;IACrD,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAuC;IAC3D,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAiB;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAA0B;IACnD,gGAAgG;IAChG,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAA2C;IACxE,6FAA6F;IAC7F,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoC;gBAG3D,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EACnC,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,EACzC,EAAE,EAAE,cAAc,EAClB,OAAO,CAAC,EAAE,cAAc,CAAC,OAAO,CAAC,EACjC,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IASxD,iGAAiG;IACjG,OAAO,CAAC,SAAS;IAIjB;;;;OAIG;IACH,OAAO,CAAC,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO;CAYxE;AAeD,kGAAkG;AAClG,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,eAAe,GAAG,SAAS,CAG3F;AAUD;;;GAGG;AACH,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACtD;;;;OAIG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC;IAC/C,qFAAqF;IACrF,QAAQ,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC,CAAC;CACjE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACxD,OAAO,EAAE,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC3C,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,EACzC,EAAE,EAAE,cAAc,EAClB,OAAO,GAAE,WAAW,CAAC,CAAC,CAAM,GAC3B,CAAC,EAAE,CA+DL"}
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ /**
3
+ * litedbmodel v2 SCP — typed-object result + hydrate factory + lazy relations (WS4, #24).
4
+ *
5
+ * v2's read result is a plain **typed-object** (spec §4 / §12): its OWN enumerable props are
6
+ * DATA ONLY (the projected columns + any declaratively-selected relations). It is NOT a
7
+ * DBModel instance — `JSON.stringify` / `Object.keys` / spread stay clean (feasibility §9).
8
+ *
9
+ * The v1 method-like UX is recovered via a graphddb-shaped {@link hydrate} factory
10
+ * (`(raw) => HostObject`): host-object construction is a CONSUMER concern that stays in the
11
+ * litedbmodel runtime and NEVER leaks into the portable IR/bundle (per the runtime boundary —
12
+ * the bundle carries the relation op + assembly shape only). `hydrate` is applied AFTER
13
+ * relation resolution and is NOT applied to `null` (a null single-relation), matching
14
+ * graphddb's `options.hydrate`.
15
+ *
16
+ * ## Lazy relations: prototype getter + non-enumerable Symbol batch context
17
+ *
18
+ * A typed-object gets a per-result-set prototype carrying a lazy getter per relation
19
+ * (feasibility §9 "prototype で良い"). The getter, on access, reads a NON-ENUMERABLE Symbol
20
+ * batch context (graphddb's `GRAPHDDB_KEY` technique — invisible to spread/JSON) shared by
21
+ * every sibling in the result set, and fires the SAME pre-compiled relation op ONCE over the
22
+ * whole sibling set (batch — so no N+1 even though it can't prefetch). A declaratively-selected
23
+ * relation is attached as an OWN property (`Post[]`/`Author`) and naturally SHADOWS the
24
+ * prototype getter (own prop wins) — the exact "宣言済み → データ / 未宣言 → lazy" unification
25
+ * from feasibility §9.
26
+ *
27
+ * Both surfaces resolve through {@link import('./relation').runRelationOp} — the identical
28
+ * compiled op — so there is one relation-execution code path, not two.
29
+ */
30
+ Object.defineProperty(exports, "__esModule", { value: true });
31
+ exports.RelationContext = exports.RELATION_CONTEXT = void 0;
32
+ exports.readRelationContext = readRelationContext;
33
+ exports.buildResultSet = buildResultSet;
34
+ const relation_1 = require("./relation");
35
+ /** The non-enumerable Symbol under which the shared lazy batch context is stashed. */
36
+ exports.RELATION_CONTEXT = Symbol('litedbmodel:relation-context');
37
+ /**
38
+ * The shared lazy batch context for one result set. Holds the sibling rows, the driver, the
39
+ * compiled relation ops (keyed by name), the (optional) hydrate factory, and a per-relation
40
+ * memo so a relation is batched at most ONCE across all siblings (structural no-N+1 for lazy).
41
+ * Stored as a non-enumerable Symbol prop so spread/JSON/`Object.keys` never see it (a spread
42
+ * or clone drops it — "designed degradation", feasibility §9).
43
+ */
44
+ class RelationContext {
45
+ siblings;
46
+ ops;
47
+ db;
48
+ hydrate;
49
+ /** CROSS-DB (V0 R1): name → driver registry; a tagged relation routes to `connections[tag]`. */
50
+ connections;
51
+ /** Per-relation resolved batch (name → grouping), memoized: one batch query per relation. */
52
+ resolved = new Map();
53
+ constructor(siblings, ops, db, hydrate, connections) {
54
+ this.siblings = siblings;
55
+ this.ops = ops;
56
+ this.db = db;
57
+ this.hydrate = hydrate;
58
+ this.connections = connections;
59
+ }
60
+ /** The driver a relation runs against: its tagged cross-DB connection, else the primary `db`. */
61
+ driverFor(op) {
62
+ return op.connection === undefined ? this.db : driverForOp(op, this.connections);
63
+ }
64
+ /**
65
+ * Resolve `relationName` for `parent` (lazy access). The relation is batch-loaded across
66
+ * ALL siblings on first access and memoized, so N sibling accesses issue ONE query. Returns
67
+ * the hydrated child(ren) per cardinality (`hasMany` → array, else single-or-null).
68
+ */
69
+ resolve(relationName, parent) {
70
+ const op = this.ops[relationName];
71
+ if (op === undefined) {
72
+ throw new Error(`relation '${relationName}' is not declared on this result set`);
73
+ }
74
+ let batch = this.resolved.get(relationName);
75
+ if (batch === undefined) {
76
+ batch = (0, relation_1.runRelationOp)(op, this.siblings, this.driverFor(op)).batch;
77
+ this.resolved.set(relationName, batch);
78
+ }
79
+ return applyHydrate((0, relation_1.distributeToParent)(op, parent, batch), this.hydrate);
80
+ }
81
+ }
82
+ exports.RelationContext = RelationContext;
83
+ /**
84
+ * The driver a CROSS-DB relation op (V0 R1) runs against: the registered `connections[tag]`. Loud
85
+ * failure when the tag has no registered driver (a real wiring bug — never a silent same-DB
86
+ * fallback, which would run the target's query on the wrong DB). Only called for a tagged op.
87
+ */
88
+ function driverForOp(op, connections) {
89
+ const d = connections?.[op.connection];
90
+ if (d === undefined) {
91
+ throw new Error(`cross-DB relation '${op.name}': no driver registered for connection '${op.connection}' (pass it in ReadOptions.connections)`);
92
+ }
93
+ return d;
94
+ }
95
+ /** Read the non-enumerable batch context off a typed-object (undefined if spread/cloned away). */
96
+ function readRelationContext(o) {
97
+ const v = o[exports.RELATION_CONTEXT];
98
+ return v instanceof RelationContext ? v : undefined;
99
+ }
100
+ /** Apply the hydrate factory to a resolved relation value (skip `null`; map arrays). */
101
+ function applyHydrate(value, hydrate) {
102
+ if (hydrate === undefined)
103
+ return value;
104
+ if (value === null)
105
+ return null; // graphddb: hydrate is NOT applied to null
106
+ if (Array.isArray(value))
107
+ return value.map((v) => (v === null ? null : hydrate(v)));
108
+ return hydrate(value);
109
+ }
110
+ /**
111
+ * Build the typed-object result set for a page of raw child rows plus the relation ops
112
+ * declared for this model. Every raw row becomes a typed-object whose:
113
+ * - OWN enumerable props = the raw data columns (+ any declaratively-selected relation);
114
+ * - PROTOTYPE carries a lazy getter per UNSELECTED relation (fires the same op at access);
115
+ * - non-enumerable {@link RELATION_CONTEXT} Symbol = the shared lazy batch context.
116
+ *
117
+ * Declaratively-selected relations (`options.with`) are batch-prefetched HERE (one query per
118
+ * relation over the whole page — staged, no N+1) via the SAME {@link runRelationOp}, and
119
+ * attached as own props (so they shadow the prototype getter). `hydrate`, when supplied, is
120
+ * applied to each parent row AND to resolved relations.
121
+ *
122
+ * Returns the typed-objects (hydrated if `options.hydrate` given). This is the litedbmodel
123
+ * runtime concern; the bundle carries only the relation ops + assembly shape (no hydrate).
124
+ */
125
+ function buildResultSet(rawRows, ops, db, options = {}) {
126
+ const selected = options.with ?? {};
127
+ const hydrate = options.hydrate;
128
+ // The prototype carries a lazy getter for every relation NOT declaratively selected. A
129
+ // selected relation is an own prop, so it shadows the getter (feasibility §9 unification).
130
+ const proto = {};
131
+ for (const name of Object.keys(ops)) {
132
+ if (selected[name] === true)
133
+ continue;
134
+ Object.defineProperty(proto, name, {
135
+ enumerable: false,
136
+ configurable: true,
137
+ get() {
138
+ const ctx = readRelationContext(this);
139
+ if (ctx === undefined) {
140
+ return Promise.reject(new Error(`lazy relation '${name}': the batch context was dropped (spread/clone/JSON); ` +
141
+ `re-read through the query API or declaratively select it via { with: { ${name}: true } }`));
142
+ }
143
+ // v1 parity: `await post.author` yields a Promise (getter → relation-op launch,
144
+ // feasibility §9). The batch itself is synchronous (better-sqlite3) but memoized
145
+ // across siblings, so N accesses still issue ONE query (no N+1).
146
+ return Promise.resolve(ctx.resolve(name, this));
147
+ },
148
+ });
149
+ }
150
+ // The raw typed-objects (own props = data only), sharing `proto`.
151
+ const rows = rawRows.map((raw) => {
152
+ const o = Object.create(proto);
153
+ for (const k of Object.keys(raw))
154
+ o[k] = raw[k];
155
+ return o;
156
+ });
157
+ // The shared lazy batch context (non-enumerable Symbol) — the whole page is the sibling set.
158
+ const ctx = new RelationContext(rows, ops, db, hydrate, options.connections);
159
+ for (const o of rows) {
160
+ Object.defineProperty(o, exports.RELATION_CONTEXT, {
161
+ value: ctx,
162
+ enumerable: false,
163
+ writable: false,
164
+ configurable: false,
165
+ });
166
+ }
167
+ // Declarative select: batch-prefetch each selected relation ONCE (staged) and attach as an
168
+ // own prop (shadowing the lazy getter). SAME runRelationOp — identical compiled op + SQL.
169
+ for (const name of Object.keys(selected)) {
170
+ const op = ops[name];
171
+ if (op === undefined)
172
+ throw new Error(`declarative select: relation '${name}' is not declared on this model`);
173
+ // CROSS-DB (V0 R1): a tagged relation batches against its own connection; else the primary db.
174
+ const relDb = op.connection === undefined ? db : driverForOp(op, options.connections);
175
+ const { batch } = (0, relation_1.runRelationOp)(op, rows, relDb);
176
+ for (const o of rows) {
177
+ o[name] = applyHydrate((0, relation_1.distributeToParent)(op, o, batch), hydrate);
178
+ }
179
+ }
180
+ if (hydrate === undefined)
181
+ return rows;
182
+ return rows.map((o) => hydrate(o));
183
+ }
184
+ //# sourceMappingURL=typed-object.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"typed-object.js","sourceRoot":"","sources":["../../src/scp/typed-object.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;;;AAqFH,kDAGC;AA+CD,wCAoEC;AAzMD,yCAMoB;AAEpB,sFAAsF;AACzE,QAAA,gBAAgB,GAAkB,MAAM,CAAC,8BAA8B,CAAC,CAAC;AAKtF;;;;;;GAMG;AACH,MAAa,eAAe;IACT,QAAQ,CAA4B;IACpC,GAAG,CAAuC;IAC1C,EAAE,CAAiB;IACnB,OAAO,CAA2B;IACnD,gGAAgG;IAC/E,WAAW,CAA4C;IACxE,6FAA6F;IAC5E,QAAQ,GAAG,IAAI,GAAG,EAAyB,CAAC;IAE7D,YACE,QAAmC,EACnC,GAAyC,EACzC,EAAkB,EAClB,OAAiC,EACjC,WAAsD;QAEtD,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;QACf,IAAI,CAAC,EAAE,GAAG,EAAE,CAAC;QACb,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IACjC,CAAC;IAED,iGAAiG;IACzF,SAAS,CAAC,EAAc;QAC9B,OAAO,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;IACnF,CAAC;IAED;;;;OAIG;IACH,OAAO,CAAC,YAAoB,EAAE,MAA+B;QAC3D,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAClC,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,aAAa,YAAY,sCAAsC,CAAC,CAAC;QACnF,CAAC;QACD,IAAI,KAAK,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAC5C,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YACxB,KAAK,GAAG,IAAA,wBAAa,EAAC,EAAE,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;YACnE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;QACzC,CAAC;QACD,OAAO,YAAY,CAAC,IAAA,6BAAkB,EAAC,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAC3E,CAAC;CACF;AA9CD,0CA8CC;AAED;;;;GAIG;AACH,SAAS,WAAW,CAAC,EAAc,EAAE,WAAiE;IACpG,MAAM,CAAC,GAAG,WAAW,EAAE,CAAC,EAAE,CAAC,UAAoB,CAAC,CAAC;IACjD,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;QACpB,MAAM,IAAI,KAAK,CAAC,sBAAsB,EAAE,CAAC,IAAI,2CAA2C,EAAE,CAAC,UAAU,wCAAwC,CAAC,CAAC;IACjJ,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,kGAAkG;AAClG,SAAgB,mBAAmB,CAAC,CAA0B;IAC5D,MAAM,CAAC,GAAI,CAA6B,CAAC,wBAAgB,CAAC,CAAC;IAC3D,OAAO,CAAC,YAAY,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACtD,CAAC;AAED,wFAAwF;AACxF,SAAS,YAAY,CAAC,KAAc,EAAE,OAAiC;IACrE,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACxC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC,CAAC,2CAA2C;IAC5E,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAA4B,CAAC,CAAC,CAAC,CAAC;IAC/G,OAAO,OAAO,CAAC,KAAgC,CAAC,CAAC;AACnD,CAAC;AAwBD;;;;;;;;;;;;;;GAcG;AACH,SAAgB,cAAc,CAC5B,OAA2C,EAC3C,GAAyC,EACzC,EAAkB,EAClB,UAA0B,EAAE;IAE5B,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC;IACpC,MAAM,OAAO,GAAG,OAAO,CAAC,OAA8C,CAAC;IAEvE,uFAAuF;IACvF,2FAA2F;IAC3F,MAAM,KAAK,GAA4B,EAAE,CAAC;IAC1C,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACpC,IAAI,QAAQ,CAAC,IAAI,CAAC,KAAK,IAAI;YAAE,SAAS;QACtC,MAAM,CAAC,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE;YACjC,UAAU,EAAE,KAAK;YACjB,YAAY,EAAE,IAAI;YAClB,GAAG;gBACD,MAAM,GAAG,GAAG,mBAAmB,CAAC,IAAI,CAAC,CAAC;gBACtC,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;oBACtB,OAAO,OAAO,CAAC,MAAM,CACnB,IAAI,KAAK,CACP,kBAAkB,IAAI,wDAAwD;wBAC5E,0EAA0E,IAAI,YAAY,CAC7F,CACF,CAAC;gBACJ,CAAC;gBACD,gFAAgF;gBAChF,iFAAiF;gBACjF,iEAAiE;gBACjE,OAAO,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;YAClD,CAAC;SACF,CAAC,CAAC;IACL,CAAC;IAED,kEAAkE;IAClE,MAAM,IAAI,GAA8B,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QAC1D,MAAM,CAAC,GAA4B,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACxD,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QAChD,OAAO,CAAC,CAAC;IACX,CAAC,CAAC,CAAC;IAEH,6FAA6F;IAC7F,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,IAAI,EAAE,GAAG,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAC7E,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,CAAC,cAAc,CAAC,CAAC,EAAE,wBAAgB,EAAE;YACzC,KAAK,EAAE,GAAG;YACV,UAAU,EAAE,KAAK;YACjB,QAAQ,EAAE,KAAK;YACf,YAAY,EAAE,KAAK;SACpB,CAAC,CAAC;IACL,CAAC;IAED,2FAA2F;IAC3F,0FAA0F;IAC1F,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzC,MAAM,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC,CAAC;QACrB,IAAI,EAAE,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,IAAI,iCAAiC,CAAC,CAAC;QAC9G,+FAA+F;QAC/F,MAAM,KAAK,GAAG,EAAE,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,EAAE,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;QACtF,MAAM,EAAE,KAAK,EAAE,GAAG,IAAA,wBAAa,EAAC,EAAE,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC;QACjD,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;YACrB,CAAC,CAAC,IAAI,CAAC,GAAG,YAAY,CAAC,IAAA,6BAAkB,EAAC,EAAE,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC;QACpE,CAAC;IACH,CAAC;IAED,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,IAAsB,CAAC;IACzD,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAQ,CAAC;AAC5C,CAAC"}
@@ -0,0 +1,251 @@
1
+ /**
2
+ * litedbmodel v2 SCP — write-time relations declaration vocabulary (WS5, #25; spec §6 / §2.2).
3
+ *
4
+ * `entityWrites` / `edgeWrites` declare the **write-side invariants / effects required for
5
+ * an entity to stay consistent** when it is created / updated / removed — the SQL-backend
6
+ * counterpart of graphddb's `entityWrites` save contract (`graphddb/src/define/entity-writes.ts`,
7
+ * same shape: a per-lifecycle map of `w.lifecycle({requires,unique,derive,edges,emits,idempotency})`).
8
+ * This module produces a PORTABLE declaration (pure data, no closures): every value that
9
+ * references the write is a **path-rooted string** (`$.input.<field>` = the mutation input,
10
+ * `$.entity.<field>` = the written row's RETURNING result). {@link ../write-plan} lowers the
11
+ * declaration + a base write op into an ordered, gate-first SQL transaction plan (§6 table).
12
+ *
13
+ * ## Path roots (spec §6 example) — the SSoT for every derived value
14
+ *
15
+ * A leaf value binds to an explicit root so its source is unambiguous and no engine-code
16
+ * fallback is ever needed (hard rule: defaults live in the declaration):
17
+ * - `$.input.<field>` — the Command's input port value (bc flat scope: `{ref:['<field>']}`).
18
+ * - `$.entity.<field>` — the just-written row (the body write's RETURNING row), exposed to
19
+ * the derive/edges/emits stages under the reserved `{@link ENTITY_ROOT}` binding.
20
+ *
21
+ * The declaration is INITIAL-scope (spec §6 / §13): single-statement Command + fixed-order
22
+ * write-time relations. It does NOT model the complex DAG derivation (WS8).
23
+ */
24
+ /** The reserved scope binding under which the body write's RETURNING row is exposed (`$.entity.*`). */
25
+ export declare const ENTITY_ROOT = "__entity";
26
+ /**
27
+ * The path root a write-relation value binds from:
28
+ * - `input` — the Command input port (bc flat scope).
29
+ * - `entity` — the SOLE body write's RETURNING row (the WS5 single-base-write shorthand),
30
+ * bound under {@link ENTITY_ROOT}.
31
+ * - `ref` — a NAMED upstream write's RETURNING row (`$.ref.<writeName>.<field>`, WS8a
32
+ * composite/DAG scope). `entity` is exactly `ref` targeting the sole base write; keeping
33
+ * it distinct preserves the WS5 vocabulary while composite writes address writes by name.
34
+ */
35
+ export type PathRoot = 'input' | 'entity' | 'ref';
36
+ /** A parsed, path-rooted write-relation value (`$.input.x` / `$.entity.x` / `$.ref.w.x`). */
37
+ export interface EffectPath {
38
+ readonly root: PathRoot;
39
+ /**
40
+ * For `ref`: the upstream write's NAME (the statement whose RETURNING row is referenced).
41
+ * Absent for `input` / `entity` (those have a single implicit source).
42
+ */
43
+ readonly writeName?: string;
44
+ /** The field name after the root (a single physical column; no nested paths in α). */
45
+ readonly field: string;
46
+ }
47
+ /**
48
+ * Parse a path-rooted write-relation value string into an {@link EffectPath}. Fail-closed on
49
+ * a malformed path — a write-relation value MUST be `$.input.<field>`, `$.entity.<field>`, or
50
+ * `$.ref.<writeName>.<field>` (no free-form paths, no implicit root), so a typo is a loud build
51
+ * error, never a silent default (hard rule).
52
+ */
53
+ export declare function parseEffectPath(value: string): EffectPath;
54
+ /**
55
+ * A referential-integrity requirement (§6 `requires`): the named `table` must contain a row
56
+ * whose columns equal the bound keys. Lowered to a gate-first `SELECT 1 FROM <table> WHERE …`
57
+ * existence guard that ROLLBACKs when absent (spec §6 example line 1).
58
+ */
59
+ export interface RequiresEffect {
60
+ readonly kind: 'requires';
61
+ /** The table whose row must exist. */
62
+ readonly table: string;
63
+ /** target column → path-rooted source value (the existence key). */
64
+ readonly keys: Readonly<Record<string, string>>;
65
+ }
66
+ /**
67
+ * A uniqueness guard (§6 `unique`): the combined `fields` must be unique within `scope`.
68
+ * Lowered to a guard-row `INSERT INTO <guardTable> … ON CONFLICT DO NOTHING` whose affected-
69
+ * row count is checked (0 rows ⇒ collision ⇒ ROLLBACK) — the SQL idiom of §6 (`ON CONFLICT
70
+ * DO NOTHING` + affected 検査).
71
+ */
72
+ export interface UniqueEffect {
73
+ readonly kind: 'unique';
74
+ /** The guard-row name (its discriminator column value in the guard table). */
75
+ readonly name: string;
76
+ /** The guard table that materializes the uniqueness rows. */
77
+ readonly guardTable: string;
78
+ /** The scope the uniqueness is partitioned by (path-rooted values). */
79
+ readonly scope: readonly string[];
80
+ /** The fields whose combined value must be unique within the scope (path-rooted values). */
81
+ readonly fields: readonly string[];
82
+ }
83
+ /**
84
+ * A derived / cascading counter update (§6 `derive`): `SET <attribute> = <attribute> ± amount`
85
+ * on the `table` row keyed by `keys`. Lowered to `UPDATE <table> SET c = c + :amount WHERE …`
86
+ * (spec §6 example line "derive"). `amount` is the SSoT for the increment — never a code default.
87
+ */
88
+ export interface DeriveEffect {
89
+ readonly kind: 'derive';
90
+ /** The table whose counter attribute is derived-updated. */
91
+ readonly table: string;
92
+ /** target column → path-rooted source value (the updated row's key). */
93
+ readonly keys: Readonly<Record<string, string>>;
94
+ /** The counter column to increment. */
95
+ readonly attribute: string;
96
+ /** The signed amount added to the counter (the declaration IS the source of truth). */
97
+ readonly amount: number;
98
+ }
99
+ /**
100
+ * An edge write (§6 `edges`). A many-to-many edge materializes as an intermediate-table
101
+ * `INSERT` (create) / `DELETE` (remove); a one-to-many edge as a foreign-key column `UPDATE`
102
+ * on the related table. Lowered per {@link EdgeEffect.relation} (spec §6 table row `edges`).
103
+ */
104
+ export interface EdgeEffect {
105
+ readonly kind: 'edge';
106
+ /** `m2m` → intermediate-table INSERT/DELETE; `fk` → related-table FK column UPDATE. */
107
+ readonly relation: 'm2m' | 'fk';
108
+ /** The write action for this lifecycle: `set` (create/link) or `unset` (remove/unlink). */
109
+ readonly action: 'set' | 'unset';
110
+ /**
111
+ * For `m2m`: the intermediate (join) table. For `fk`: the related table whose FK column is set.
112
+ */
113
+ readonly table: string;
114
+ /**
115
+ * For `m2m`: the join-row columns → path-rooted source values (both endpoint keys).
116
+ * For `fk`: the FK column → path-rooted source value AND the matched-row key columns.
117
+ */
118
+ readonly columns: Readonly<Record<string, string>>;
119
+ /**
120
+ * For `fk` only: the WHERE key columns (matched-row identity) → path-rooted source values.
121
+ * `columns` then holds ONLY the FK column(s) to SET. For `m2m` this is absent.
122
+ */
123
+ readonly where?: Readonly<Record<string, string>>;
124
+ }
125
+ /**
126
+ * A domain event (§6 `emits`): an outbox `INSERT INTO <outboxTable>(type, payload)` in the
127
+ * SAME transaction (spec §6 example line "emits"). The payload is a path-rooted map serialized
128
+ * to a JSON text column at render time.
129
+ */
130
+ export interface EmitEffect {
131
+ readonly kind: 'emit';
132
+ /** The event type discriminator (e.g. `PostCreated`). */
133
+ readonly name: string;
134
+ /** The outbox table the event row is inserted into. */
135
+ readonly outboxTable: string;
136
+ /** The event payload: field → path-rooted source value. */
137
+ readonly payload: Readonly<Record<string, string>>;
138
+ }
139
+ /**
140
+ * An idempotency guard (§6 `idempotency`): a client-token guard `INSERT INTO <table>(token)`
141
+ * whose UNIQUE violation detects a duplicate request (spec §6 example line "idempotency").
142
+ * Runs gate-first: a duplicate token short-circuits the whole transaction (no double write).
143
+ */
144
+ export interface IdempotencyEffect {
145
+ readonly kind: 'idempotency';
146
+ /** The idempotency-key table (a single UNIQUE token column). */
147
+ readonly table: string;
148
+ /** The token column name. */
149
+ readonly column: string;
150
+ /** The path-rooted client-token value (e.g. `$.input.request_id`). */
151
+ readonly token: string;
152
+ }
153
+ /**
154
+ * The §6 effect set a {@link LifecycleContract} carries. Every field is optional; an omitted
155
+ * field is the empty effect set. Fixed derivation order (spec §6 example): gate-first
156
+ * (requires → idempotency → unique), body, then derive → edges → emits.
157
+ */
158
+ export interface LifecycleEffects {
159
+ /** Referential-integrity requirements (→ gate-first existence guard). */
160
+ readonly requires?: readonly RequiresEffect[];
161
+ /** Idempotency guard (→ gate-first token INSERT; duplicate short-circuits). */
162
+ readonly idempotency?: IdempotencyEffect;
163
+ /** Uniqueness guards (→ gate-first guard-row INSERT ON CONFLICT DO NOTHING + affected check). */
164
+ readonly unique?: readonly UniqueEffect[];
165
+ /** Derived / cascading counter updates (→ UPDATE … SET c = c ± n). */
166
+ readonly derive?: readonly DeriveEffect[];
167
+ /** Edge writes (→ M:N intermediate INSERT/DELETE or 1:N FK UPDATE). */
168
+ readonly edges?: readonly EdgeEffect[];
169
+ /** Domain events (→ outbox INSERT, same tx). */
170
+ readonly emits?: readonly EmitEffect[];
171
+ }
172
+ /** One lifecycle's save contract — the §6 effect set for a single phase. */
173
+ export interface LifecycleContract {
174
+ readonly effects: LifecycleEffects;
175
+ }
176
+ /** The lifecycle phase a save-contract entry declares (the Command intent selects it). */
177
+ export type WriteLifecyclePhase = 'create' | 'update' | 'remove';
178
+ /** A model's per-lifecycle save contract (`entityWrites(...)` result). */
179
+ export interface EntityWritesDefinition {
180
+ readonly create?: LifecycleContract;
181
+ readonly update?: LifecycleContract;
182
+ readonly remove?: LifecycleContract;
183
+ }
184
+ /**
185
+ * The recorder handed to the {@link entityWrites} / {@link edgeWrites} builder. Each method
186
+ * builds ONE §6 effect (pure data). `w.lifecycle({...})` gathers the effect arrays into a
187
+ * {@link LifecycleContract}. Every path-rooted value is validated eagerly ({@link parseEffectPath}).
188
+ */
189
+ export interface WriteRecorder {
190
+ /** Build one lifecycle's save contract from its §6 effect arrays (each field optional). */
191
+ lifecycle(effects?: LifecycleEffects): LifecycleContract;
192
+ /** `requires` — a referential-integrity existence guard (§6). */
193
+ exists(table: string, keys: Readonly<Record<string, string>>): RequiresEffect;
194
+ /** `unique` — a composite-scope uniqueness guard (§6). */
195
+ unique(spec: {
196
+ readonly name: string;
197
+ readonly guardTable: string;
198
+ readonly scope: readonly string[];
199
+ readonly fields: readonly string[];
200
+ }): UniqueEffect;
201
+ /** `derive` — a cascade counter increment (§6). `amount` is the SSoT (no code default). */
202
+ increment(table: string, keys: Readonly<Record<string, string>>, attribute: string, amount: number): DeriveEffect;
203
+ /** `edges` — a many-to-many intermediate-table link/unlink (§6). */
204
+ edge(spec: {
205
+ readonly relation: 'm2m';
206
+ readonly action: 'set' | 'unset';
207
+ readonly table: string;
208
+ readonly columns: Readonly<Record<string, string>>;
209
+ }): EdgeEffect;
210
+ /** `edges` — a one-to-many foreign-key column set/unset on a related row (§6). */
211
+ edge(spec: {
212
+ readonly relation: 'fk';
213
+ readonly action: 'set' | 'unset';
214
+ readonly table: string;
215
+ readonly columns: Readonly<Record<string, string>>;
216
+ readonly where: Readonly<Record<string, string>>;
217
+ }): EdgeEffect;
218
+ /** `emits` — a domain event into an outbox table, same tx (§6). */
219
+ event(name: string, outboxTable: string, payload: Readonly<Record<string, string>>): EmitEffect;
220
+ /** `idempotency` — a client-token duplicate guard (§6). */
221
+ idempotentBy(table: string, column: string, token: string): IdempotencyEffect;
222
+ }
223
+ /** The per-lifecycle map an {@link entityWrites} builder returns. */
224
+ export interface EntityWritesShape {
225
+ readonly create?: LifecycleContract;
226
+ readonly update?: LifecycleContract;
227
+ readonly remove?: LifecycleContract;
228
+ }
229
+ /**
230
+ * Declare a model's reusable write-time-relations save contract (spec §2.2 / §6): a
231
+ * per-lifecycle map `{ create?, update?, remove? }`, each value a `w.lifecycle({...})`
232
+ * carrying the §6 effect arrays. Stored on the model as
233
+ * `static readonly writes = entityWrites<Model>((w) => ({ … }))`.
234
+ *
235
+ * @typeParam M documentary model type (the recorder is structural).
236
+ * @throws if no lifecycle is declared, or a lifecycle is not built with `w.lifecycle(...)`.
237
+ */
238
+ export declare function entityWrites<M = unknown>(builder: (w: WriteRecorder) => EntityWritesShape): EntityWritesDefinition;
239
+ /**
240
+ * Declare an adjacency entity's edge-only write contract (spec §2.2 / §6 — the `edgeWrites`
241
+ * counterpart of graphddb's #82). It is a thin specialization of {@link entityWrites} whose
242
+ * lifecycles carry ONLY `edges` effects — the write side of a many-to-many / one-to-many
243
+ * relation entity. Kept distinct so an author signals intent (an edge entity vs a full
244
+ * save contract), matching graphddb's two coexisting `writes` forms.
245
+ *
246
+ * @throws if a lifecycle declares a non-edge effect (an edge entity has only edge writes).
247
+ */
248
+ export declare function edgeWrites<M = unknown>(builder: (w: WriteRecorder) => EntityWritesShape): EntityWritesDefinition;
249
+ /** Resolve the {@link LifecycleContract} for a Command intent, or undefined if not declared. */
250
+ export declare function lifecycleFor(def: EntityWritesDefinition, phase: WriteLifecyclePhase): LifecycleContract | undefined;
251
+ //# sourceMappingURL=writes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writes.d.ts","sourceRoot":"","sources":["../../src/scp/writes.ts"],"names":[],"mappings":"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"}