litedbmodel 1.2.9 → 2.0.0

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 (88) 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 +56 -0
  6. package/dist/scp/authoring-sql.d.ts.map +1 -0
  7. package/dist/scp/authoring-sql.js +83 -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/bridge.d.ts +64 -0
  14. package/dist/scp/bridge.d.ts.map +1 -0
  15. package/dist/scp/bridge.js +329 -0
  16. package/dist/scp/bridge.js.map +1 -0
  17. package/dist/scp/catalog.d.ts +91 -0
  18. package/dist/scp/catalog.d.ts.map +1 -0
  19. package/dist/scp/catalog.js +203 -0
  20. package/dist/scp/catalog.js.map +1 -0
  21. package/dist/scp/codegen.d.ts +131 -0
  22. package/dist/scp/codegen.d.ts.map +1 -0
  23. package/dist/scp/codegen.js +137 -0
  24. package/dist/scp/codegen.js.map +1 -0
  25. package/dist/scp/compile-dialect.d.ts +39 -0
  26. package/dist/scp/compile-dialect.d.ts.map +1 -0
  27. package/dist/scp/compile-dialect.js +62 -0
  28. package/dist/scp/compile-dialect.js.map +1 -0
  29. package/dist/scp/compile-sqlite.d.ts +114 -0
  30. package/dist/scp/compile-sqlite.d.ts.map +1 -0
  31. package/dist/scp/compile-sqlite.js +149 -0
  32. package/dist/scp/compile-sqlite.js.map +1 -0
  33. package/dist/scp/dialect.d.ts +141 -0
  34. package/dist/scp/dialect.d.ts.map +1 -0
  35. package/dist/scp/dialect.js +194 -0
  36. package/dist/scp/dialect.js.map +1 -0
  37. package/dist/scp/errors.d.ts +36 -0
  38. package/dist/scp/errors.d.ts.map +1 -0
  39. package/dist/scp/errors.js +72 -0
  40. package/dist/scp/errors.js.map +1 -0
  41. package/dist/scp/guard.d.ts +47 -0
  42. package/dist/scp/guard.d.ts.map +1 -0
  43. package/dist/scp/guard.js +137 -0
  44. package/dist/scp/guard.js.map +1 -0
  45. package/dist/scp/index.cjs +4744 -0
  46. package/dist/scp/index.d.ts +49 -0
  47. package/dist/scp/index.d.ts.map +1 -0
  48. package/dist/scp/index.js +164 -0
  49. package/dist/scp/index.js.map +1 -0
  50. package/dist/scp/index.mjs +2000 -0
  51. package/dist/scp/ir.d.ts +92 -0
  52. package/dist/scp/ir.d.ts.map +1 -0
  53. package/dist/scp/ir.js +28 -0
  54. package/dist/scp/ir.js.map +1 -0
  55. package/dist/scp/relation.d.ts +136 -0
  56. package/dist/scp/relation.d.ts.map +1 -0
  57. package/dist/scp/relation.js +191 -0
  58. package/dist/scp/relation.js.map +1 -0
  59. package/dist/scp/render.d.ts +40 -0
  60. package/dist/scp/render.d.ts.map +1 -0
  61. package/dist/scp/render.js +170 -0
  62. package/dist/scp/render.js.map +1 -0
  63. package/dist/scp/runtime.d.ts +295 -0
  64. package/dist/scp/runtime.d.ts.map +1 -0
  65. package/dist/scp/runtime.js +665 -0
  66. package/dist/scp/runtime.js.map +1 -0
  67. package/dist/scp/typed-object.d.ts +88 -0
  68. package/dist/scp/typed-object.d.ts.map +1 -0
  69. package/dist/scp/typed-object.js +163 -0
  70. package/dist/scp/typed-object.js.map +1 -0
  71. package/dist/scp/write-plan.d.ts +161 -0
  72. package/dist/scp/write-plan.d.ts.map +1 -0
  73. package/dist/scp/write-plan.js +528 -0
  74. package/dist/scp/write-plan.js.map +1 -0
  75. package/dist/scp/write-runtime.d.ts +80 -0
  76. package/dist/scp/write-runtime.d.ts.map +1 -0
  77. package/dist/scp/write-runtime.js +167 -0
  78. package/dist/scp/write-runtime.js.map +1 -0
  79. package/dist/scp/writes.d.ts +251 -0
  80. package/dist/scp/writes.d.ts.map +1 -0
  81. package/dist/scp/writes.js +176 -0
  82. package/dist/scp/writes.js.map +1 -0
  83. package/docs/proposal/contracts-architecture-feasibility.md +328 -0
  84. package/docs/proposal/contracts-architecture.md +579 -0
  85. package/docs/proposal/litedbmodel-v2-scp-architecture.md +382 -0
  86. package/docs/proposal/sql-dynamic-expansion-spec.md +175 -0
  87. package/package.json +27 -5
  88. package/docs/README_REVIEW.md +0 -23
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ /**
3
+ * litedbmodel v2 SCP — write-time transaction runtime (WS5, #25; spec §6 / §3 / §11).
4
+ *
5
+ * Executes a derived {@link TransactionPlan} against REAL SQLite as ONE transaction with
6
+ * gate-first short-circuit (spec §6). It renders each ordered statement with the SAME normative
7
+ * {@link renderOperation} the read path uses (fragment tree + Expression-IR param slots), and
8
+ * drives an explicit `BEGIN` / `COMMIT` / `ROLLBACK` envelope through the SAME synchronous
9
+ * driver seam ({@link SqliteDb}) the WS3 runtime uses — no mock, no separate interpreter.
10
+ *
11
+ * ## The transaction envelope (spec §6) — Tx is derived, not authored
12
+ *
13
+ * BEGIN;
14
+ * for each ordered statement:
15
+ * render (input + accumulated $.entity row) → execute REAL SQL
16
+ * if it is a GATE and its rule FAILS → ROLLBACK and stop (remaining SQL never runs)
17
+ * COMMIT; (or ROLLBACK on a driver failure / gate fail)
18
+ *
19
+ * The body write's RETURNING row is captured and exposed to the derive/edges/emits stages under
20
+ * `$.entity.*` ({@link ENTITY_ROOT}) — the §6 example's `$.entity.id`.
21
+ *
22
+ * ## Gate-first is a REAL behavior (spec §6 "Gate First"), proven, not textual
23
+ *
24
+ * A gate statement's {@link GateRule} is evaluated on its actual driver result:
25
+ * - `existsElseRollback` — `SELECT 1 …` returned ZERO rows ⇒ the required row is absent ⇒
26
+ * ROLLBACK; the body + all later statements are NEVER prepared/executed (a real query-count
27
+ * drop, observable via a counting driver, and the DB is unchanged).
28
+ * - `insertedElseRollback` — a `unique` guard `INSERT … ON CONFLICT DO NOTHING` affected ZERO
29
+ * rows ⇒ a collision ⇒ ROLLBACK.
30
+ * - `insertedElseNoop` — an `idempotency` token INSERT affected ZERO rows ⇒ a DUPLICATE request
31
+ * ⇒ short-circuit as a no-op: ROLLBACK the (empty-so-far) transaction, so a duplicate
32
+ * `request_id` performs NO double write (idempotent).
33
+ *
34
+ * A short-circuit returns a structured {@link TransactionResult} (`committed:false`) rather than
35
+ * throwing — the caller distinguishes a legitimate gate outcome (absent requires / unique
36
+ * collision / duplicate) from a driver Failure (which IS mapped + thrown, spec §11 item 5).
37
+ */
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.executeTransaction = executeTransaction;
40
+ exports.countingDriver = countingDriver;
41
+ const render_1 = require("./render");
42
+ const errors_1 = require("./errors");
43
+ const writes_1 = require("./writes");
44
+ const dialect_1 = require("./dialect");
45
+ /** bc evaluates ints to bigint; convert a rendered param to a driver-bindable value. */
46
+ function toDriverParam(v) {
47
+ if (typeof v === 'bigint') {
48
+ if (v >= BigInt(Number.MIN_SAFE_INTEGER) && v <= BigInt(Number.MAX_SAFE_INTEGER))
49
+ return Number(v);
50
+ return v;
51
+ }
52
+ // An emit payload evaluates to a plain object (`{obj:{…}}`); serialize it to the outbox JSON
53
+ // text column. Arrays are IN-list elements (not used in the write path); pass through.
54
+ if (v !== null && typeof v === 'object' && !Array.isArray(v))
55
+ return JSON.stringify(v);
56
+ return v;
57
+ }
58
+ /**
59
+ * Execute one statement's compiled op against the driver in the given scope, returning the
60
+ * rows (SELECT / RETURNING) plus the affected-row count (non-returning writes). A driver error
61
+ * is mapped and thrown (the caller ROLLBACKs). This is the SAME render→bind→execute pipeline as
62
+ * the WS3 read/write handlers; only the transaction envelope + gate interpretation is new.
63
+ */
64
+ function execStatement(db, op, scope, dialect) {
65
+ const rendered = (0, render_1.renderOperation)(op, scope, dialect);
66
+ const params = rendered.params.map(toDriverParam);
67
+ const stmt = db.prepare(rendered.sql);
68
+ const hasReturn = op.component === 'Select' || /\breturning\b/i.test(rendered.sql);
69
+ if (hasReturn) {
70
+ const rows = stmt.all(...params);
71
+ return { rows, changes: rows.length };
72
+ }
73
+ const info = stmt.run(...params);
74
+ return { rows: [], changes: info.changes };
75
+ }
76
+ /** Evaluate a gate rule on a statement result → the short-circuit reason, or null to continue. */
77
+ function gateShortCircuit(gate, result) {
78
+ switch (gate) {
79
+ case 'existsElseRollback':
80
+ return result.rows.length === 0 ? 'requires_absent' : null;
81
+ case 'insertedElseRollback':
82
+ return result.changes === 0 ? 'unique_collision' : null;
83
+ case 'insertedElseNoop':
84
+ return result.changes === 0 ? 'idempotent_duplicate' : null;
85
+ }
86
+ }
87
+ /**
88
+ * Execute a derived {@link TransactionPlan} as ONE real SQLite transaction with gate-first
89
+ * short-circuit (spec §6). Statements run in the plan's fixed order (requires → idempotency →
90
+ * unique → body → derive → edges → emits); a failing gate ROLLBACKs and the remaining statements
91
+ * are never executed. On success COMMITs and returns the `$.entity` RETURNING row.
92
+ *
93
+ * @param db the synchronous SQLite driver (better-sqlite3 `Database`), supporting `BEGIN` /
94
+ * `COMMIT` / `ROLLBACK` via `prepare(...).run()`.
95
+ * @param plan the derived transaction plan (pure JSON; from `deriveTransactionPlan` or the bundle).
96
+ * @param input the Command input scope (`$.input.*` = bc flat scope).
97
+ * @throws {SqlFailure} a mapped driver failure (the transaction is ROLLBACKed first, spec §11).
98
+ */
99
+ function executeTransaction(db, plan, input, dialect = dialect_1.SQLITE) {
100
+ db.prepare('BEGIN').run();
101
+ const executed = [];
102
+ // The evolving scope: input names at the top level (bc flat scope) + the body RETURNING row
103
+ // exposed under `__entity` once the body runs. Defaults live in the declaration/schema, so no
104
+ // ad-hoc code default is injected here.
105
+ const scope = { ...input };
106
+ let entity = null;
107
+ try {
108
+ for (const stmt of plan.statements) {
109
+ const result = runOne(db, stmt, scope, executed, dialect);
110
+ // Gate-first: a failing gate short-circuits — ROLLBACK and STOP (tail never executes).
111
+ if (stmt.gate !== undefined) {
112
+ const reason = gateShortCircuit(stmt.gate, result);
113
+ if (reason !== null) {
114
+ db.prepare('ROLLBACK').run();
115
+ return { committed: false, shortCircuit: { statementId: stmt.id, reason }, entity: null, executed };
116
+ }
117
+ }
118
+ // Capture the SOLE body RETURNING row as `$.entity` (WS5 single-write back-compat).
119
+ if (stmt.id === plan.entityFrom) {
120
+ entity = result.rows.length > 0 ? result.rows[0] : null;
121
+ if (entity !== null)
122
+ scope[writes_1.ENTITY_ROOT] = entity;
123
+ }
124
+ // Composite (WS8a): bind THIS statement's RETURNING row under its `binds` name so a later
125
+ // `$.ref.<binds>.<field>` resolves against it (the DAG's data-dependency edge). Self-
126
+ // describing: the runtime just binds the row the plan told it to — no re-derivation.
127
+ if (stmt.binds !== undefined && result.rows.length > 0) {
128
+ scope[stmt.binds] = result.rows[0];
129
+ }
130
+ }
131
+ db.prepare('COMMIT').run();
132
+ return { committed: true, entity, executed };
133
+ }
134
+ catch (e) {
135
+ // A driver failure: ROLLBACK, then map + re-throw the structured SqlFailure (spec §11).
136
+ try {
137
+ db.prepare('ROLLBACK').run();
138
+ }
139
+ catch {
140
+ /* ROLLBACK best-effort; surface the original failure below */
141
+ }
142
+ throw (0, errors_1.mapSqliteError)(e);
143
+ }
144
+ }
145
+ /** Render + execute one statement, recording it as executed. */
146
+ function runOne(db, stmt, scope, executed, dialect) {
147
+ const result = execStatement(db, stmt.op, scope, dialect);
148
+ executed.push(stmt.id);
149
+ return result;
150
+ }
151
+ /**
152
+ * A counting {@link SqliteDb} wrapper: forwards every call to the wrapped driver but records
153
+ * each PREPARED SQL string. Tests use it to PROVE gate-first short-circuit — after an absent
154
+ * `requires`, the body/derive/edges/emits SQL is never prepared (a real, observable query-count
155
+ * drop), not merely absent from a text list. This is a test aid living beside the runtime.
156
+ */
157
+ function countingDriver(db) {
158
+ const prepared = [];
159
+ const wrapped = {
160
+ prepare(sql) {
161
+ prepared.push(sql);
162
+ return db.prepare(sql);
163
+ },
164
+ };
165
+ return { db: wrapped, prepared };
166
+ }
167
+ //# sourceMappingURL=write-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write-runtime.js","sourceRoot":"","sources":["../../src/scp/write-runtime.ts"],"names":[],"mappings":"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"}
@@ -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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"}
@@ -0,0 +1,176 @@
1
+ "use strict";
2
+ /**
3
+ * litedbmodel v2 SCP — write-time relations declaration vocabulary (WS5, #25; spec §6 / §2.2).
4
+ *
5
+ * `entityWrites` / `edgeWrites` declare the **write-side invariants / effects required for
6
+ * an entity to stay consistent** when it is created / updated / removed — the SQL-backend
7
+ * counterpart of graphddb's `entityWrites` save contract (`graphddb/src/define/entity-writes.ts`,
8
+ * same shape: a per-lifecycle map of `w.lifecycle({requires,unique,derive,edges,emits,idempotency})`).
9
+ * This module produces a PORTABLE declaration (pure data, no closures): every value that
10
+ * references the write is a **path-rooted string** (`$.input.<field>` = the mutation input,
11
+ * `$.entity.<field>` = the written row's RETURNING result). {@link ../write-plan} lowers the
12
+ * declaration + a base write op into an ordered, gate-first SQL transaction plan (§6 table).
13
+ *
14
+ * ## Path roots (spec §6 example) — the SSoT for every derived value
15
+ *
16
+ * A leaf value binds to an explicit root so its source is unambiguous and no engine-code
17
+ * fallback is ever needed (hard rule: defaults live in the declaration):
18
+ * - `$.input.<field>` — the Command's input port value (bc flat scope: `{ref:['<field>']}`).
19
+ * - `$.entity.<field>` — the just-written row (the body write's RETURNING row), exposed to
20
+ * the derive/edges/emits stages under the reserved `{@link ENTITY_ROOT}` binding.
21
+ *
22
+ * The declaration is INITIAL-scope (spec §6 / §13): single-statement Command + fixed-order
23
+ * write-time relations. It does NOT model the complex DAG derivation (WS8).
24
+ */
25
+ Object.defineProperty(exports, "__esModule", { value: true });
26
+ exports.ENTITY_ROOT = void 0;
27
+ exports.parseEffectPath = parseEffectPath;
28
+ exports.entityWrites = entityWrites;
29
+ exports.edgeWrites = edgeWrites;
30
+ exports.lifecycleFor = lifecycleFor;
31
+ /** The reserved scope binding under which the body write's RETURNING row is exposed (`$.entity.*`). */
32
+ exports.ENTITY_ROOT = '__entity';
33
+ const PATH_RE = /^\$\.(input|entity)\.([A-Za-z_][A-Za-z0-9_]*)$/;
34
+ const REF_PATH_RE = /^\$\.ref\.([A-Za-z_][A-Za-z0-9_]*)\.([A-Za-z_][A-Za-z0-9_]*)$/;
35
+ /**
36
+ * Parse a path-rooted write-relation value string into an {@link EffectPath}. Fail-closed on
37
+ * a malformed path — a write-relation value MUST be `$.input.<field>`, `$.entity.<field>`, or
38
+ * `$.ref.<writeName>.<field>` (no free-form paths, no implicit root), so a typo is a loud build
39
+ * error, never a silent default (hard rule).
40
+ */
41
+ function parseEffectPath(value) {
42
+ const m = PATH_RE.exec(value);
43
+ if (m !== null)
44
+ return { root: m[1], field: m[2] };
45
+ const r = REF_PATH_RE.exec(value);
46
+ if (r !== null)
47
+ return { root: 'ref', writeName: r[1], field: r[2] };
48
+ throw new Error(`write-time relations: '${value}' is not a valid path-rooted value; use ` +
49
+ `'$.input.<field>' (the Command input), '$.entity.<field>' (the sole written row), or ` +
50
+ `'$.ref.<writeName>.<field>' (a named upstream write's RETURNING row).`);
51
+ }
52
+ /** Assert every value in a path-rooted map parses (fail-closed on a malformed path). */
53
+ function assertPaths(map) {
54
+ for (const v of Object.values(map))
55
+ parseEffectPath(v); // throws on malformed
56
+ }
57
+ const recorder = {
58
+ lifecycle(effects = {}) {
59
+ // Validate every declared effect's paths eagerly (build-time fail-closed).
60
+ for (const r of effects.requires ?? [])
61
+ assertPaths(r.keys);
62
+ if (effects.idempotency !== undefined)
63
+ parseEffectPath(effects.idempotency.token);
64
+ for (const u of effects.unique ?? []) {
65
+ u.scope.forEach((p) => parseEffectPath(p));
66
+ u.fields.forEach((p) => parseEffectPath(p));
67
+ }
68
+ for (const d of effects.derive ?? [])
69
+ assertPaths(d.keys);
70
+ for (const e of effects.edges ?? []) {
71
+ assertPaths(e.columns);
72
+ if (e.where !== undefined)
73
+ assertPaths(e.where);
74
+ }
75
+ for (const em of effects.emits ?? [])
76
+ assertPaths(em.payload);
77
+ return { effects: freezeEffects(effects) };
78
+ },
79
+ exists(table, keys) {
80
+ return { kind: 'requires', table, keys };
81
+ },
82
+ unique(spec) {
83
+ return { kind: 'unique', name: spec.name, guardTable: spec.guardTable, scope: spec.scope, fields: spec.fields };
84
+ },
85
+ increment(table, keys, attribute, amount) {
86
+ if (!Number.isFinite(amount)) {
87
+ throw new Error(`entityWrites: increment amount for '${table}.${attribute}' must be a finite number (got ${String(amount)})`);
88
+ }
89
+ return { kind: 'derive', table, keys, attribute, amount };
90
+ },
91
+ edge(spec) {
92
+ if (spec.relation === 'fk' && spec.where === undefined) {
93
+ throw new Error(`entityWrites: a 'fk' edge on '${spec.table}' requires a 'where' key map (the matched row's identity)`);
94
+ }
95
+ return {
96
+ kind: 'edge',
97
+ relation: spec.relation,
98
+ action: spec.action,
99
+ table: spec.table,
100
+ columns: spec.columns,
101
+ ...(spec.where !== undefined ? { where: spec.where } : {}),
102
+ };
103
+ },
104
+ event(name, outboxTable, payload) {
105
+ return { kind: 'emit', name, outboxTable, payload };
106
+ },
107
+ idempotentBy(table, column, token) {
108
+ return { kind: 'idempotency', table, column, token };
109
+ },
110
+ };
111
+ /** Keep only the present effect fields (an empty `w.lifecycle()` carries an empty set). */
112
+ function freezeEffects(effects) {
113
+ const out = {};
114
+ if (effects.requires !== undefined)
115
+ out.requires = effects.requires;
116
+ if (effects.idempotency !== undefined)
117
+ out.idempotency = effects.idempotency;
118
+ if (effects.unique !== undefined)
119
+ out.unique = effects.unique;
120
+ if (effects.derive !== undefined)
121
+ out.derive = effects.derive;
122
+ if (effects.edges !== undefined)
123
+ out.edges = effects.edges;
124
+ if (effects.emits !== undefined)
125
+ out.emits = effects.emits;
126
+ return out;
127
+ }
128
+ /**
129
+ * Declare a model's reusable write-time-relations save contract (spec §2.2 / §6): a
130
+ * per-lifecycle map `{ create?, update?, remove? }`, each value a `w.lifecycle({...})`
131
+ * carrying the §6 effect arrays. Stored on the model as
132
+ * `static readonly writes = entityWrites<Model>((w) => ({ … }))`.
133
+ *
134
+ * @typeParam M documentary model type (the recorder is structural).
135
+ * @throws if no lifecycle is declared, or a lifecycle is not built with `w.lifecycle(...)`.
136
+ */
137
+ function entityWrites(builder) {
138
+ void undefined;
139
+ const shape = builder(recorder);
140
+ if (shape.create === undefined && shape.update === undefined && shape.remove === undefined) {
141
+ throw new Error('entityWrites(...) must declare at least one lifecycle (`create` / `update` / `remove`).');
142
+ }
143
+ return {
144
+ ...(shape.create !== undefined ? { create: shape.create } : {}),
145
+ ...(shape.update !== undefined ? { update: shape.update } : {}),
146
+ ...(shape.remove !== undefined ? { remove: shape.remove } : {}),
147
+ };
148
+ }
149
+ /**
150
+ * Declare an adjacency entity's edge-only write contract (spec §2.2 / §6 — the `edgeWrites`
151
+ * counterpart of graphddb's #82). It is a thin specialization of {@link entityWrites} whose
152
+ * lifecycles carry ONLY `edges` effects — the write side of a many-to-many / one-to-many
153
+ * relation entity. Kept distinct so an author signals intent (an edge entity vs a full
154
+ * save contract), matching graphddb's two coexisting `writes` forms.
155
+ *
156
+ * @throws if a lifecycle declares a non-edge effect (an edge entity has only edge writes).
157
+ */
158
+ function edgeWrites(builder) {
159
+ const def = entityWrites(builder);
160
+ for (const phase of ['create', 'update', 'remove']) {
161
+ const c = def[phase];
162
+ if (c === undefined)
163
+ continue;
164
+ const e = c.effects;
165
+ if (e.requires !== undefined || e.unique !== undefined || e.derive !== undefined || e.emits !== undefined || e.idempotency !== undefined) {
166
+ throw new Error(`edgeWrites: the '${phase}' lifecycle may declare ONLY 'edges' effects ` +
167
+ `(an edge entity carries only edge writes; use entityWrites for a full save contract).`);
168
+ }
169
+ }
170
+ return def;
171
+ }
172
+ /** Resolve the {@link LifecycleContract} for a Command intent, or undefined if not declared. */
173
+ function lifecycleFor(def, phase) {
174
+ return def[phase];
175
+ }
176
+ //# sourceMappingURL=writes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"writes.js","sourceRoot":"","sources":["../../src/scp/writes.ts"],"names":[],"mappings":"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"}