litedbmodel 2.2.2 → 2.2.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 (180) hide show
  1. package/README.md +2 -1
  2. package/dist/DBModel.d.ts.map +1 -1
  3. package/dist/DBModel.js +3 -0
  4. package/dist/DBModel.js.map +1 -1
  5. package/dist/Middleware.d.ts +19 -2
  6. package/dist/Middleware.d.ts.map +1 -1
  7. package/dist/Middleware.js +1 -0
  8. package/dist/Middleware.js.map +1 -1
  9. package/dist/decorators.d.ts.map +1 -1
  10. package/dist/decorators.js +8 -0
  11. package/dist/decorators.js.map +1 -1
  12. package/dist/drivers/MysqlHelper.d.ts +1 -1
  13. package/dist/drivers/MysqlHelper.d.ts.map +1 -1
  14. package/dist/drivers/MysqlHelper.js.map +1 -1
  15. package/dist/drivers/PostgresHelper.d.ts +1 -1
  16. package/dist/drivers/PostgresHelper.d.ts.map +1 -1
  17. package/dist/drivers/PostgresHelper.js.map +1 -1
  18. package/dist/drivers/SqliteHelper.d.ts +1 -1
  19. package/dist/drivers/SqliteHelper.d.ts.map +1 -1
  20. package/dist/drivers/SqliteHelper.js.map +1 -1
  21. package/dist/index.cjs +105 -74
  22. package/dist/scp/connection-routing.d.ts +38 -1
  23. package/dist/scp/connection-routing.d.ts.map +1 -1
  24. package/dist/scp/connection-routing.js +64 -5
  25. package/dist/scp/connection-routing.js.map +1 -1
  26. package/dist/scp/dbmodel-runtime.d.ts +2 -1
  27. package/dist/scp/dbmodel-runtime.d.ts.map +1 -1
  28. package/dist/scp/dbmodel-runtime.js +2 -1
  29. package/dist/scp/dbmodel-runtime.js.map +1 -1
  30. package/dist/scp/decorator-adapter.d.ts +22 -0
  31. package/dist/scp/decorator-adapter.d.ts.map +1 -1
  32. package/dist/scp/decorator-adapter.js +26 -0
  33. package/dist/scp/decorator-adapter.js.map +1 -1
  34. package/dist/scp/emit/emitter.d.ts +21 -10
  35. package/dist/scp/emit/emitter.d.ts.map +1 -1
  36. package/dist/scp/emit/emitter.js +119 -51
  37. package/dist/scp/emit/emitter.js.map +1 -1
  38. package/dist/scp/emit/endpoint.d.ts +3 -8
  39. package/dist/scp/emit/endpoint.d.ts.map +1 -1
  40. package/dist/scp/exec-context.d.ts +39 -17
  41. package/dist/scp/exec-context.d.ts.map +1 -1
  42. package/dist/scp/exec-context.js +69 -35
  43. package/dist/scp/exec-context.js.map +1 -1
  44. package/dist/scp/grouping.d.ts.map +1 -1
  45. package/dist/scp/grouping.js +0 -0
  46. package/dist/scp/grouping.js.map +1 -1
  47. package/dist/scp/index.cjs +257 -670
  48. package/dist/scp/index.d.ts +6 -11
  49. package/dist/scp/index.d.ts.map +1 -1
  50. package/dist/scp/index.js +10 -28
  51. package/dist/scp/index.js.map +1 -1
  52. package/dist/scp/index.mjs +257 -658
  53. package/dist/scp/leaf-transport.d.ts +127 -39
  54. package/dist/scp/leaf-transport.d.ts.map +1 -1
  55. package/dist/scp/leaf-transport.js +30 -24
  56. package/dist/scp/leaf-transport.js.map +1 -1
  57. package/dist/scp/leaves.d.ts +57 -20
  58. package/dist/scp/leaves.d.ts.map +1 -1
  59. package/dist/scp/leaves.js +224 -65
  60. package/dist/scp/leaves.js.map +1 -1
  61. package/dist/scp/makesql/compile-select.d.ts +28 -1
  62. package/dist/scp/makesql/compile-select.d.ts.map +1 -1
  63. package/dist/scp/makesql/compile-select.js +19 -17
  64. package/dist/scp/makesql/compile-select.js.map +1 -1
  65. package/dist/scp/makesql/index.d.ts +2 -2
  66. package/dist/scp/makesql/index.d.ts.map +1 -1
  67. package/dist/scp/makesql/index.js +4 -6
  68. package/dist/scp/makesql/index.js.map +1 -1
  69. package/dist/scp/makesql/tx.d.ts +43 -89
  70. package/dist/scp/makesql/tx.d.ts.map +1 -1
  71. package/dist/scp/makesql/tx.js +45 -496
  72. package/dist/scp/makesql/tx.js.map +1 -1
  73. package/dist/scp/makesql/writeouttype.d.ts +5 -7
  74. package/dist/scp/makesql/writeouttype.d.ts.map +1 -1
  75. package/dist/scp/makesql/writeouttype.js +13 -19
  76. package/dist/scp/makesql/writeouttype.js.map +1 -1
  77. package/dist/scp/middleware.d.ts +4 -4
  78. package/dist/scp/middleware.d.ts.map +1 -1
  79. package/dist/scp/middleware.js +2 -2
  80. package/dist/scp/middleware.js.map +1 -1
  81. package/dist/scp/relation.d.ts +31 -12
  82. package/dist/scp/relation.d.ts.map +1 -1
  83. package/dist/scp/relation.js +16 -6
  84. package/dist/scp/relation.js.map +1 -1
  85. package/dist/scp/typed-object.d.ts +23 -19
  86. package/dist/scp/typed-object.d.ts.map +1 -1
  87. package/dist/scp/typed-object.js +24 -30
  88. package/dist/scp/typed-object.js.map +1 -1
  89. package/dist/scp/write-bundle.d.ts +21 -70
  90. package/dist/scp/write-bundle.d.ts.map +1 -1
  91. package/dist/scp/write-bundle.js +16 -80
  92. package/dist/scp/write-bundle.js.map +1 -1
  93. package/dist/types.d.ts +12 -0
  94. package/dist/types.d.ts.map +1 -1
  95. package/dist/types.js.map +1 -1
  96. package/docs/BENCHMARK.md +26 -22
  97. package/docs/COMPARISON.md +8 -4
  98. package/docs/api/README.md +1 -1
  99. package/docs/api/classes/Conditions.md +10 -10
  100. package/docs/api/classes/DBHandler.md +17 -17
  101. package/docs/api/classes/DBModel.md +111 -104
  102. package/docs/api/classes/LimitExceededError.md +8 -8
  103. package/docs/api/classes/Middleware.md +18 -18
  104. package/docs/api/classes/SqlRaw.md +6 -6
  105. package/docs/api/classes/SqlRef.md +7 -7
  106. package/docs/api/classes/Values.md +6 -6
  107. package/docs/api/classes/WriteInReadOnlyContextError.md +5 -5
  108. package/docs/api/classes/WriteOutsideTransactionError.md +5 -5
  109. package/docs/api/functions/belongsTo.md +2 -2
  110. package/docs/api/functions/closeAllPools.md +2 -2
  111. package/docs/api/functions/condsToRecord.md +2 -2
  112. package/docs/api/functions/createMiddleware.md +2 -2
  113. package/docs/api/functions/createPostgresDriver.md +2 -2
  114. package/docs/api/functions/createSqliteDriver.md +2 -2
  115. package/docs/api/functions/dbCast.md +3 -3
  116. package/docs/api/functions/dbCastIn.md +2 -2
  117. package/docs/api/functions/dbDynamic.md +3 -3
  118. package/docs/api/functions/dbFalse.md +2 -2
  119. package/docs/api/functions/dbImmediate.md +2 -2
  120. package/docs/api/functions/dbIn.md +2 -2
  121. package/docs/api/functions/dbNotNull.md +2 -2
  122. package/docs/api/functions/dbNow.md +2 -2
  123. package/docs/api/functions/dbNull.md +2 -2
  124. package/docs/api/functions/dbRaw.md +2 -2
  125. package/docs/api/functions/dbTrue.md +2 -2
  126. package/docs/api/functions/dbUuid.md +3 -3
  127. package/docs/api/functions/dbUuidIn.md +2 -2
  128. package/docs/api/functions/formatLocalDate.md +25 -0
  129. package/docs/api/functions/formatUTCDate.md +25 -0
  130. package/docs/api/functions/getDBConfig.md +2 -2
  131. package/docs/api/functions/getDBHandler.md +2 -2
  132. package/docs/api/functions/getTransactionConnection.md +2 -2
  133. package/docs/api/functions/getTransactionContext.md +2 -2
  134. package/docs/api/functions/hasMany.md +2 -2
  135. package/docs/api/functions/hasOne.md +2 -2
  136. package/docs/api/functions/initDBHandler.md +2 -2
  137. package/docs/api/functions/isAnySqlFragment.md +2 -2
  138. package/docs/api/functions/isConnectionError.md +26 -0
  139. package/docs/api/functions/isSqlCondition.md +2 -2
  140. package/docs/api/functions/isSqlFragment.md +2 -2
  141. package/docs/api/functions/isSqlRaw.md +2 -2
  142. package/docs/api/functions/isSqlRef.md +2 -2
  143. package/docs/api/functions/isSqlTypedFragment.md +2 -2
  144. package/docs/api/functions/model.md +6 -42
  145. package/docs/api/functions/parentRef.md +2 -2
  146. package/docs/api/functions/sql.md +7 -7
  147. package/docs/api/globals.md +57 -22
  148. package/docs/api/interfaces/Column.md +31 -31
  149. package/docs/api/interfaces/ColumnOptions.md +5 -4
  150. package/docs/api/interfaces/CreatedMiddlewareClass.md +7 -7
  151. package/docs/api/interfaces/DBConfig.md +13 -11
  152. package/docs/api/interfaces/DBConfigOptions.md +8 -8
  153. package/docs/api/interfaces/DeleteOptions.md +4 -4
  154. package/docs/api/interfaces/InsertOptions.md +8 -8
  155. package/docs/api/interfaces/LimitConfig.md +4 -4
  156. package/docs/api/interfaces/MiddlewareConfig.md +15 -15
  157. package/docs/api/interfaces/ModelOptions.md +8 -7
  158. package/docs/api/interfaces/PkeyResult.md +4 -4
  159. package/docs/api/interfaces/SelectOptions.md +17 -16
  160. package/docs/api/interfaces/SqlCondition.md +6 -6
  161. package/docs/api/interfaces/SqlFragment.md +5 -5
  162. package/docs/api/interfaces/SqlTypedFragment.md +7 -7
  163. package/docs/api/interfaces/TransactionOptions.md +7 -7
  164. package/docs/api/interfaces/UpdateManyOptions.md +4 -4
  165. package/docs/api/interfaces/UpdateOptions.md +4 -4
  166. package/docs/architecture.md +452 -0
  167. package/docs/benchmark-chart.svg +225 -249
  168. package/docs/design/phase-a-execution-context.md +2 -0
  169. package/package.json +20 -5
  170. package/dist/scp/writes.d.ts +0 -251
  171. package/dist/scp/writes.d.ts.map +0 -1
  172. package/dist/scp/writes.js +0 -176
  173. package/dist/scp/writes.js.map +0 -1
  174. package/docs/proposal/contracts-architecture-feasibility.md +0 -328
  175. package/docs/proposal/contracts-architecture.md +0 -579
  176. package/docs/proposal/litedbmodel-v2-scp-architecture.md +0 -433
  177. package/docs/proposal/sql-dynamic-expansion-spec.md +0 -175
  178. package/docs/proposal/v0-coverage-matrix.md +0 -157
  179. package/docs/proposal/v2-completeness-plan.md +0 -90
  180. package/docs/proposal/v2-sql-parity-checklist.md +0 -162
@@ -1,47 +1,35 @@
1
1
  "use strict";
2
2
  /**
3
- * litedbmodel v2 SCP — write-time relations → ordered SQL transaction plan, re-expressed on
4
- * the `makeSQL` model (epic #43/#45 Phase B; spec §6 / §14). This REPLACES the reduced-spine
5
- * `../write-plan.ts` + `../write-runtime.ts` (`CompiledOperation` + `renderOperation` +
6
- * FragmentTree). No reduced IR is emitted anywhere: every statement's SQL is COMPLETE tuned
7
- * text (byte-identical to what the v1 write path sends — `SELECT 1 …`, `INSERT … ON CONFLICT
8
- * DO NOTHING`, `UPDATE … SET c = c + ?`, the outbox INSERT), and its `params` are closed-set
9
- * bc Expression IR refs resolved at execute time against the accumulated transaction scope.
3
+ * litedbmodel v2 SCP — the write descriptor → makeSQL statement compiler ({@link compileWriteNode}),
4
+ * the BATCH transaction-plan derivation ({@link deriveBatchPlan}), and the live async 1-tx runtime
5
+ * ({@link executeTransactionAsync}). No reduced IR is emitted anywhere: every statement's SQL is
6
+ * COMPLETE tuned text (byte-identical to what the v1 write path sends — `INSERT … ON CONFLICT DO
7
+ * NOTHING`, `UPDATE … SET c = ?`, `DELETE FROM … WHERE …`), and its `params` are closed-set bc
8
+ * Expression IR refs resolved at execute time against the transaction scope.
10
9
  *
11
10
  * A statement op is exactly a `makeSQL` template: `{ sql, params }` where `sql` carries `?`
12
11
  * placeholders and `params` are Expression IR (`{ref:[…]}` / a literal / a `{obj:{…}}` payload)
13
12
  * that bc evaluates per statement — then the concrete values assemble + render + bind through
14
13
  * the SAME `assembleMakeSQL` / `renderPlaceholders` the read path uses.
15
14
  *
16
- * ## Gate-first is a real execution behavior (spec §6 "Gate First")
15
+ * A BATCH write (createMany / updateMany / deleteMany) is ONE logical op that produces N grouped
16
+ * statements; {@link deriveBatchPlan} lowers those into a gate-free {@link TransactionPlan}
17
+ * (`entityFrom` null, every statement `role:'body'`), run in the declared order as ONE transaction.
18
+ * The single-row and read paths are lowered by `bc generate` into a native module per language and
19
+ * never reach this runtime; there is no runtime read-compile and no runtime single-write compile.
17
20
  *
18
- * Each gate statement carries a {@link GateRule} the runtime evaluates AFTER executing it: a
19
- * failing gate short-circuits — the remaining statements never execute and the tx ROLLBACKs.
20
- *
21
- * ## Path lowering (spec §6) — `$.input.*` / `$.entity.*` / `$.ref.<w>.*` → closed-set refs
22
- *
23
- * `$.input.<f>` → `{ref:['<f>']}` (bc flat input scope)
24
- * `$.entity.<f>` → `{ref:['__entity','<f>']}` (the sole body write's RETURNING row)
25
- * `$.ref.<w>.<f>` → `{ref:['<w>','<f>']}` (a named upstream write's RETURNING row)
26
- *
27
- * ## tx-DAG derivation (spec §6 / §14) — gate-first + data-dependency topo order
28
- *
29
- * Statements form a data-dependency DAG (`$.ref.<w>.*` consumes an earlier write's RETURNING
30
- * row via `TxStatement.binds`) with a gate-first constraint. A deterministic Kahn topo sort
31
- * (stable ascending declaration `seq` tie-break) orders them into a single-transaction plan.
32
- * Underivable shapes (cycle, dangling `$.ref`, referenced write without RETURNING, duplicate
33
- * bind, composite `$.entity`) are LOUD rejects — never a silently mis-ordered plan.
21
+ * Every statement executes: a plan either commits or raises. There is no gate that inspects a
22
+ * statement's result and short-circuits the rest — no plan in this repository ever carried one, so
23
+ * the interpretation was removed rather than kept as a path nothing reaches (#256).
34
24
  */
35
25
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.IN_SENTINEL = void 0;
26
+ exports.IN_SENTINEL = exports.ENTITY_ROOT = void 0;
37
27
  exports.literalize = literalize;
28
+ exports.isBatchPlan = isBatchPlan;
38
29
  exports.pgTypeSpecimen = pgTypeSpecimen;
39
30
  exports.pkPort = pkPort;
40
31
  exports.compileWriteNode = compileWriteNode;
41
- exports.deriveTransactionPlan = deriveTransactionPlan;
42
32
  exports.deriveBatchPlan = deriveBatchPlan;
43
- exports.executeTransaction = executeTransaction;
44
- exports.countingDriver = countingDriver;
45
33
  exports.renderTxStatement = renderTxStatement;
46
34
  exports.executeTransactionAsync = executeTransactionAsync;
47
35
  const runtime_1 = require("behavior-contracts/runtime");
@@ -61,7 +49,6 @@ const leaves_1 = require("../leaves");
61
49
  const tx_options_1 = require("../tx-options");
62
50
  const connection_errors_1 = require("../../connection-errors");
63
51
  const DBConditions_1 = require("../../DBConditions");
64
- const writes_1 = require("../writes");
65
52
  /**
66
53
  * Encode a CONCRETE value into a bc Expression IR node that `evaluateExpression` returns VERBATIM.
67
54
  *
@@ -103,126 +90,24 @@ function literalize(value) {
103
90
  }
104
91
  return value;
105
92
  }
106
- // ── Path → Expression IR ref (closed-set only) ────────────────────────────────
107
- function pathToRef(value) {
108
- const p = (0, writes_1.parseEffectPath)(value);
109
- if (p.root === 'input')
110
- return { ref: [p.field] };
111
- if (p.root === 'entity')
112
- return { ref: [writes_1.ENTITY_ROOT, p.field] };
113
- return { ref: [p.writeName, p.field] };
114
- }
115
- // ── Per-dialect guard INSERT text (the ON CONFLICT DO NOTHING / INSERT IGNORE SSoT) ──
116
93
  /**
117
- * The tuned guard INSERT text for a gate-first `idempotency`/`unique` statement — byte-identical
118
- * to the v1 write path: SQLite/Postgres emit `INSERT INTO … VALUES (…) ON CONFLICT DO NOTHING`;
119
- * MySQL emits `INSERT IGNORE INTO … VALUES (…)`.
94
+ * The reserved scope key the tx runtime binds the plan's `entityFrom` row under, so a later statement
95
+ * that references the written row resolves against it. Reserved: no input field may use this name.
120
96
  */
121
- function guardInsert(dialect, table, columns, placeholders) {
122
- const cols = `(${columns.join(', ')})`;
123
- if (dialect === 'mysql')
124
- return `INSERT IGNORE INTO ${table} ${cols} VALUES (${placeholders})`;
125
- return `INSERT INTO ${table} ${cols} VALUES (${placeholders}) ON CONFLICT DO NOTHING`;
126
- }
127
- function makeIdGen() {
128
- let n = 0;
129
- return (role) => `tx_${role}_${n++}`;
130
- }
131
- /** `requires` → a gate-first existence probe: `SELECT 1 FROM <table> WHERE k1 = ? AND …`. */
132
- function compileRequires(e, nextId) {
133
- const cols = Object.keys(e.keys);
134
- if (cols.length === 0)
135
- throw new Error(`write-plan: requires on '${e.table}' declares no keys`);
136
- const whereSql = v1EqualityWhereText(cols);
137
- const op = {
138
- sql: `SELECT 1 FROM ${e.table} WHERE ${whereSql}`,
139
- params: cols.map((c) => pathToRef(e.keys[c])),
140
- };
141
- return { id: nextId('requires'), role: 'gate:requires', op, gate: 'existsElseRollback', label: `requires ${e.table}` };
142
- }
143
- /** `idempotency` → a gate-first token INSERT ON CONFLICT DO NOTHING (duplicate short-circuits). */
144
- function compileIdempotency(e, nextId, dialect) {
145
- const op = {
146
- sql: guardInsert(dialect, e.table, [e.column], '?'),
147
- params: [pathToRef(e.token)],
148
- };
149
- return { id: nextId('idem'), role: 'gate:idempotency', op, gate: 'insertedElseNoop', label: `idempotency ${e.table}` };
150
- }
151
- /** `unique` → a gate-first guard-row INSERT ON CONFLICT DO NOTHING (collision → ROLLBACK). */
152
- function compileUnique(e, nextId, dialect) {
153
- const scopeCols = e.scope.map((_, i) => `s${i}`);
154
- const fieldCols = e.fields.map((_, i) => `f${i}`);
155
- const cols = ['name', ...scopeCols, ...fieldCols];
156
- const placeholders = cols.map(() => '?').join(', ');
157
- const params = [
158
- e.name, // literal discriminator (a closed-set string literal)
159
- ...e.scope.map(pathToRef),
160
- ...e.fields.map(pathToRef),
161
- ];
162
- const op = { sql: guardInsert(dialect, e.guardTable, cols, placeholders), params };
163
- return { id: nextId('unique'), role: 'gate:unique', op, gate: 'insertedElseRollback', label: `unique ${e.name}` };
164
- }
165
- /** `derive` → a cascade counter update: `UPDATE <table> SET <attr> = <attr> + ? WHERE k = ?`. */
166
- function compileDerive(e, nextId) {
167
- const keyCols = Object.keys(e.keys);
168
- if (keyCols.length === 0)
169
- throw new Error(`write-plan: derive on '${e.table}' declares no keys`);
170
- const whereSql = v1EqualityWhereText(keyCols);
171
- const op = {
172
- sql: `UPDATE ${e.table} SET ${e.attribute} = ${e.attribute} + ? WHERE ${whereSql}`,
173
- // SET amount is the first param (before the WHERE keys), matching the v1 param order.
174
- params: [e.amount, ...keyCols.map((c) => pathToRef(e.keys[c]))],
175
- };
176
- return { id: nextId('derive'), role: 'derive', op, label: `derive ${e.table}.${e.attribute}` };
177
- }
178
- /** `edges` → M:N intermediate INSERT/DELETE or 1:N FK UPDATE (spec §6 table row `edges`). */
179
- function compileEdge(e, nextId) {
180
- if (e.relation === 'm2m') {
181
- const cols = Object.keys(e.columns);
182
- if (e.action === 'set') {
183
- const placeholders = cols.map(() => '?').join(', ');
184
- const op = {
185
- sql: `INSERT INTO ${e.table} (${cols.join(', ')}) VALUES (${placeholders})`,
186
- params: cols.map((c) => pathToRef(e.columns[c])),
187
- };
188
- return { id: nextId('edge'), role: 'edge', op, label: `edge m2m link ${e.table}` };
189
- }
190
- const whereSql = v1EqualityWhereText(cols);
191
- const op = {
192
- sql: `DELETE FROM ${e.table} WHERE ${whereSql}`,
193
- params: cols.map((c) => pathToRef(e.columns[c])),
194
- };
195
- return { id: nextId('edge'), role: 'edge', op, label: `edge m2m unlink ${e.table}` };
196
- }
197
- // fk: UPDATE <related> SET <fkCol> = ? (or NULL) WHERE <where keys…>
198
- const setCols = Object.keys(e.columns);
199
- const whereCols = Object.keys(e.where);
200
- const setClauses = setCols.map((c) => (e.action === 'set' ? `${c} = ?` : `${c} = NULL`));
201
- const setParams = e.action === 'set' ? setCols.map((c) => pathToRef(e.columns[c])) : [];
202
- const whereSql = v1EqualityWhereText(whereCols);
203
- const op = {
204
- sql: `UPDATE ${e.table} SET ${setClauses.join(', ')} WHERE ${whereSql}`,
205
- params: [...setParams, ...whereCols.map((c) => pathToRef(e.where[c]))],
206
- };
207
- return { id: nextId('edge'), role: 'edge', op, label: `edge fk ${e.action} ${e.table}` };
208
- }
209
- /** `emits` → an outbox INSERT (same tx): `INSERT INTO <outbox>(type, payload) VALUES(?, ?)`. */
210
- function compileEmit(e, nextId) {
211
- const payloadObj = {};
212
- for (const [k, v] of Object.entries(e.payload))
213
- payloadObj[k] = pathToRef(v);
214
- const op = {
215
- sql: `INSERT INTO ${e.outboxTable} (type, payload) VALUES (?, ?)`,
216
- params: [e.name, { obj: payloadObj }],
217
- };
218
- return { id: nextId('emit'), role: 'emit', op, label: `emit ${e.name}` };
97
+ exports.ENTITY_ROOT = '__entity';
98
+ /**
99
+ * Is this plan a BATCH (createMany / updateMany / deleteMany) rather than a single/composite Command?
100
+ * A batch is ref-free: nothing exposes a row for a later statement to read, so `entityFrom` is null and
101
+ * every statement is a plain `body` with no `binds`.
102
+ *
103
+ * It decides two things that MUST agree, which is why it is ONE function and not a test each side
104
+ * spells for itself: the runtime accumulates `returnedRows` only for a batch, and the codegen out-type
105
+ * ({@link ./writeouttype}) includes the `returnedRows` field only for a batch. Two spellings that drift
106
+ * would type a field the runtime does not fill, or drop one it does (#259).
107
+ */
108
+ function isBatchPlan(plan) {
109
+ return plan.entityFrom === null && plan.statements.every((s) => s.binds === undefined && s.role === 'body');
219
110
  }
220
- // ============================================================================
221
- // Authored write node → makeSQL TxOp (the makeSQL re-expression of the bridge's
222
- // `compileNode` for the write path — reads ports STRUCTURALLY, emits complete SQL
223
- // text + DEFERRED Expression-IR params resolved at tx execute time).
224
- // ============================================================================
225
- /** The reserved column-ref path head that marks an IN-list membership (mirrors the bridge). */
226
111
  exports.IN_SENTINEL = '@in';
227
112
  function stringPort(ports, name) {
228
113
  const v = ports[name];
@@ -313,21 +198,6 @@ function v1ConditionText(conditions) {
313
198
  const probe = [];
314
199
  return new DBConditions_1.DBConditions(conditions).compile(probe);
315
200
  }
316
- /**
317
- * The tuned WHERE-body TEXT for a set of PK-equality columns (`k1 = ? AND k2 = ?`), produced by
318
- * driving the ORIGINAL v1 `DBConditions.compile()` (via {@link v1ConditionText}) — the SAME
319
- * builder the read path uses — so the write-tx predicate text is byte-identical to v1 by
320
- * construction (a v1 predicate regression now moves a tx golden). The throwaway probe params are
321
- * discarded; the real runtime values are the caller's deferred Expression-IR params, bound 1:1
322
- * with the `?` placeholders. `DBConditions.compile` iterates the condition object in insertion
323
- * order and joins with ` AND `, so the `?`/param order matches the caller's column order.
324
- */
325
- function v1EqualityWhereText(columns) {
326
- const conditions = {};
327
- for (const c of columns)
328
- conditions[c] = PROBE;
329
- return v1ConditionText(conditions);
330
- }
331
201
  /**
332
202
  * Lower one where-member Expression node → a `<sql, params, columns>` WHERE fragment (deferred
333
203
  * params). `columns[i]` is the table column the i-th emitted param binds — parallel to `params` — so
@@ -552,7 +422,7 @@ function compileWriteNodeSql(node, dialect, resolveColumnType) {
552
422
  if (stringPort(ports, 'batch') === 'true') {
553
423
  if (dialect === 'postgres') {
554
424
  if (resolveColumnType === undefined)
555
- throw new Error(`compileWriteNode: batch insert on postgres needs the column-type resolver (schema SoT) to derive the UNNEST element casts — pass it through compileCreateManyBundle/compileWriteBundle (the decorator-adapter write path).`);
425
+ throw new Error(`compileWriteNode: batch insert on postgres needs the column-type resolver (schema SoT) to derive the UNNEST element casts — pass it through compileCreateManyBundle (the decorator-adapter batch write path).`);
556
426
  return pgBatchInsert(table, sorted, values, ports, resolveColumnType, dialect);
557
427
  }
558
428
  const shapeOpts = { tableName: table, columns: sorted, records: [], ...onConflictJsonOpts(ports, sorted), ...(stringPort(ports, 'returning') !== undefined ? { returning: stringPort(ports, 'returning') } : {}) };
@@ -599,7 +469,7 @@ function compileWriteNodeSql(node, dialect, resolveColumnType) {
599
469
  const updateCols = [...setCols].sort();
600
470
  if (dialect === 'postgres') {
601
471
  if (resolveColumnType === undefined)
602
- throw new Error(`compileWriteNode: batch update on postgres needs the column-type resolver (schema SoT) to derive the UNNEST element casts — pass it through compileCreateManyBundle/compileWriteBundle (the decorator-adapter write path).`);
472
+ throw new Error(`compileWriteNode: batch update on postgres needs the column-type resolver (schema SoT) to derive the UNNEST element casts — pass it through compileCreateManyBundle (the decorator-adapter batch write path).`);
603
473
  return pgBatchUpdate(table, keyCols, updateCols, key, set, ports, resolveColumnType, dialect);
604
474
  }
605
475
  const shapeOpts = { tableName: table, keyColumns: keyCols, updateColumns: updateCols, records: [], ...(stringPort(ports, 'returning') !== undefined ? { returning: stringPort(ports, 'returning') } : {}) };
@@ -645,149 +515,9 @@ function compileWriteNodeSql(node, dialect, resolveColumnType) {
645
515
  throw new Error(`compileWriteNode: catalog component '${component}' has no write compile (SQL writes: Insert/Update/Delete)`);
646
516
  }
647
517
  }
648
- function compileWriteGroup(base, lifecycle, nextId, dialect, seqRef) {
649
- const e = lifecycle.effects;
650
- const nodes = [];
651
- const mk = (stmt, isGate) => {
652
- const consumes = refHeadsOf(stmt).filter((h) => h !== writes_1.ENTITY_ROOT && !isInputHead(h, stmt));
653
- return { stmt, seq: seqRef.n++, consumes, produces: stmt.binds ?? null, isGate };
654
- };
655
- for (const r of e.requires ?? [])
656
- nodes.push(mk(compileRequires(r, nextId), true));
657
- if (e.idempotency !== undefined)
658
- nodes.push(mk(compileIdempotency(e.idempotency, nextId, dialect), true));
659
- for (const u of e.unique ?? [])
660
- nodes.push(mk(compileUnique(u, nextId, dialect), true));
661
- const bodyId = nextId('body');
662
- const bodyStmt = {
663
- id: bodyId,
664
- role: 'body',
665
- op: base.op,
666
- label: base.label,
667
- ...(base.name !== undefined ? { binds: base.name } : {}),
668
- };
669
- nodes.push(mk(bodyStmt, false));
670
- for (const d of e.derive ?? [])
671
- nodes.push(mk(compileDerive(d, nextId), false));
672
- for (const ed of e.edges ?? [])
673
- nodes.push(mk(compileEdge(ed, nextId), false));
674
- for (const em of e.emits ?? [])
675
- nodes.push(mk(compileEmit(em, nextId), false));
676
- return { nodes, bodyId };
677
- }
678
- function topoOrder(nodes) {
679
- const byName = new Map();
680
- for (const n of nodes) {
681
- if (n.produces !== null) {
682
- if (byName.has(n.produces)) {
683
- throw new Error(`write-plan: two writes both bind the name '${n.produces}' — write names must be unique in a composite Command.`);
684
- }
685
- byName.set(n.produces, n);
686
- }
687
- }
688
- const succ = new Map();
689
- const indeg = new Map();
690
- const nodeById = new Map();
691
- for (const n of nodes) {
692
- nodeById.set(n.stmt.id, n);
693
- succ.set(n.stmt.id, new Set());
694
- indeg.set(n.stmt.id, 0);
695
- }
696
- const addEdge = (fromId, toId) => {
697
- if (fromId === toId)
698
- return;
699
- const s = succ.get(fromId);
700
- if (!s.has(toId)) {
701
- s.add(toId);
702
- indeg.set(toId, indeg.get(toId) + 1);
703
- }
704
- };
705
- for (const n of nodes) {
706
- for (const dep of n.consumes) {
707
- const producer = byName.get(dep);
708
- if (producer === undefined) {
709
- throw new Error(`write-plan: statement '${n.stmt.label}' references '$.ref.${dep}.*' but no write in this ` +
710
- `Command binds the name '${dep}' — a dangling write reference (fail-closed; no silent skip).`);
711
- }
712
- addEdge(producer.stmt.id, n.stmt.id);
713
- }
714
- }
715
- const gates = nodes.filter((n) => n.isGate);
716
- const nonGates = nodes.filter((n) => !n.isGate);
717
- for (const g of gates)
718
- for (const b of nonGates)
719
- addEdge(g.stmt.id, b.stmt.id);
720
- const ordered = [];
721
- const ready = nodes.filter((n) => indeg.get(n.stmt.id) === 0).sort((a, b) => a.seq - b.seq);
722
- while (ready.length > 0) {
723
- const n = ready.shift();
724
- ordered.push(n.stmt);
725
- for (const depId of succ.get(n.stmt.id)) {
726
- indeg.set(depId, indeg.get(depId) - 1);
727
- if (indeg.get(depId) === 0) {
728
- const dn = nodeById.get(depId);
729
- let lo = 0;
730
- while (lo < ready.length && ready[lo].seq < dn.seq)
731
- lo++;
732
- ready.splice(lo, 0, dn);
733
- }
734
- }
735
- }
736
- if (ordered.length !== nodes.length) {
737
- const stuck = nodes.filter((n) => !ordered.includes(n.stmt)).map((n) => n.stmt.label);
738
- throw new Error(`write-plan: the write-time transaction DAG has a CYCLE (unresolvable ordering among ` +
739
- `[${stuck.join(', ')}]). This is either a mutual '$.ref' data dependency or a gate that ` +
740
- `depends on a body row (a gate cannot depend on a not-yet-written row). ESCALATE: the ` +
741
- `Command's write dependencies are contradictory — no order satisfies both the data ` +
742
- `dependencies and gate-first. (Fail-closed; the derivation never guesses an order.)`);
743
- }
744
- return ordered;
745
- }
746
- /**
747
- * Derive the ORDERED, gate-first {@link TransactionPlan} from a Command's base write(s) + its
748
- * lifecycle save contract (spec §6 / §14). Single base write = fixed §6 group; multiple named
749
- * base writes = a topologically-ordered gate-first tx DAG (`$.ref.<name>.*` data dependencies).
750
- */
751
- function deriveTransactionPlan(phase, bases, lifecycle, dialect = 'sqlite') {
752
- if (bases.length === 0) {
753
- throw new Error('write-plan: a Command must declare at least one base write (Insert/Update/Delete).');
754
- }
755
- const nextId = makeIdGen();
756
- const seqRef = { n: 0 };
757
- const composite = bases.length > 1;
758
- const allNodes = [];
759
- const bodyIds = [];
760
- bases.forEach((base, i) => {
761
- const lc = base.effects !== undefined ? { effects: base.effects } : i === 0 ? lifecycle : { effects: {} };
762
- const { nodes, bodyId } = compileWriteGroup(base, lc, nextId, dialect, seqRef);
763
- allNodes.push(...nodes);
764
- bodyIds.push(bodyId);
765
- });
766
- const statements = topoOrder(allNodes);
767
- const soleBody = bases[0];
768
- const soleBodyId = bodyIds[0];
769
- const usesEntity = referencesHead(statements, writes_1.ENTITY_ROOT);
770
- const soleBodyReturns = /\breturning\b/i.test(soleBody.op.sql);
771
- if (usesEntity && composite) {
772
- throw new Error(`write-plan: a composite (multi-write) Command uses '$.entity.*' — ambiguous which write it ` +
773
- `denotes. Address each write's row by name via '$.ref.<writeName>.<field>' (fail-closed).`);
774
- }
775
- if (usesEntity && !soleBodyReturns) {
776
- throw new Error(`write-plan: a derive/edges/emits stage references '$.entity.*' but the body write ` +
777
- `('${soleBody.label}') has no RETURNING clause — the written row cannot be exposed to later ` +
778
- `stages. Add a RETURNING to the base write (fail-closed; no absent-entity default).`);
779
- }
780
- const entityFrom = usesEntity || (!composite && soleBodyReturns) ? soleBodyId : null;
781
- for (const base of bases) {
782
- if (base.name === undefined)
783
- continue;
784
- if (referencesHead(statements, base.name) && !/\breturning\b/i.test(base.op.sql)) {
785
- throw new Error(`write-plan: write '${base.name}' is referenced as '$.ref.${base.name}.*' by a later ` +
786
- `statement but its op has no RETURNING clause — its row cannot be bound for downstream ` +
787
- `writes. Add a RETURNING to write '${base.name}' (fail-closed; no absent-row default).`);
788
- }
789
- }
790
- return { phase, entityFrom, statements, onIdempotentHit: 'rollback' };
518
+ function makeIdGen() {
519
+ let n = 0;
520
+ return (role) => `tx_${role}_${n++}`;
791
521
  }
792
522
  /**
793
523
  * Lower a list of CONCRETE batch-write ops (createMany / updateMany / deleteMany — each already
@@ -817,68 +547,7 @@ function deriveBatchPlan(phase, ops) {
817
547
  // its own rows. `entityFrom` stays null; the runtime accumulates EVERY body statement's RETURNING
818
548
  // rows into `TransactionResult.returnedRows` (ordered by group), reproducing v1 `createMany`'s
819
549
  // "all created rows" result while `expectedDbState` proves the persisted state.
820
- return { phase, entityFrom: null, statements, onIdempotentHit: 'rollback' };
821
- }
822
- /** Collect the ref-heads (`ref[0]`) a statement's params reference. */
823
- function refHeadsOf(stmt) {
824
- const heads = new Set();
825
- collectRefHeads(stmt.op.params, heads);
826
- return [...heads];
827
- }
828
- /** True if a ref-head is an INPUT head (a bare `$.input.<f>` → 1-element `{ref:['<f>']}`). */
829
- function isInputHead(head, stmt) {
830
- return refPathLengthFor(stmt.op.params, head) === 1;
831
- }
832
- function refPathLengthFor(node, head) {
833
- let maxLen = 0;
834
- const walk = (n) => {
835
- if (n === null || typeof n !== 'object')
836
- return;
837
- if (Array.isArray(n)) {
838
- for (const el of n)
839
- walk(el);
840
- return;
841
- }
842
- const keys = Object.keys(n);
843
- if (keys.length === 1 && keys[0] === 'ref') {
844
- const path = n.ref;
845
- if (Array.isArray(path) && path[0] === head)
846
- maxLen = Math.max(maxLen, path.length);
847
- return;
848
- }
849
- for (const k of keys)
850
- walk(n[k]);
851
- };
852
- walk(node);
853
- return maxLen;
854
- }
855
- function referencesHead(statements, head) {
856
- const heads = new Set();
857
- for (const s of statements)
858
- collectRefHeads(s.op.params, heads);
859
- return heads.has(head);
860
- }
861
- function collectRefHeads(node, heads) {
862
- if (node === null || typeof node !== 'object')
863
- return;
864
- if (Array.isArray(node)) {
865
- for (const el of node)
866
- collectRefHeads(el, heads);
867
- return;
868
- }
869
- const keys = Object.keys(node);
870
- if (keys.length === 1 && keys[0] === 'ref') {
871
- const path = node.ref;
872
- if (Array.isArray(path) && typeof path[0] === 'string')
873
- heads.add(path[0]);
874
- return;
875
- }
876
- for (const k of keys)
877
- collectRefHeads(node[k], heads);
878
- }
879
- /** Coerce a `SqliteDb | ExecutionContext` argument to a ctx (raw driver ⇒ backward-compat wrapper). */
880
- function asContext(dbOrCtx) {
881
- return 'connectionFor' in dbOrCtx ? dbOrCtx : (0, exec_context_1.contextForDriver)(dbOrCtx);
550
+ return { phase, entityFrom: null, statements };
882
551
  }
883
552
  /** bc evaluates ints to bigint; convert a rendered param to a driver-bindable value. */
884
553
  function toDriverParam(v, dialect) {
@@ -917,100 +586,6 @@ function renderStatement(op, scope, dialect) {
917
586
  const { sql, params } = evalAssemble(op, scope, dialect);
918
587
  return { sql: (0, handler_1.renderPlaceholders)(sql, dialect), params };
919
588
  }
920
- function execStatement(ctx, op, scope, dialect) {
921
- // A tx body statement rides the SAME op-independent transport as every read/write: the `executeSQL`
922
- // leaf (placeholder render + param encode + central seam). The tx scope-eval/assemble/coerce is done
923
- // first (`evalAssemble`); the leaf receives raw `?` SQL + concrete params. `write:true` ⇒ the tx-owned
924
- // connection resolves via `connectionFor` (§3); a SELECT/RETURNING statement reads rows (`returning`
925
- // ⇒ the row-returning seam), a bare write runs (the `[{changes,…}]` summary).
926
- const { sql, params } = evalAssemble(op, scope, dialect);
927
- const hasReturn = /\bselect\b/i.test(sql.slice(0, 8)) || /\breturning\b/i.test(sql);
928
- const out = (0, leaves_1.executeSQL)({ sql, params, write: true, returning: hasReturn, bigint: false }, { exec: ctx, dialect });
929
- return hasReturn ? { rows: out, changes: out.length } : { rows: [], changes: Number(out[0]?.changes ?? 0) };
930
- }
931
- function gateShortCircuit(gate, result) {
932
- switch (gate) {
933
- case 'existsElseRollback':
934
- return result.rows.length === 0 ? 'requires_absent' : null;
935
- case 'insertedElseRollback':
936
- return result.changes === 0 ? 'unique_collision' : null;
937
- case 'insertedElseNoop':
938
- return result.changes === 0 ? 'idempotent_duplicate' : null;
939
- default:
940
- // Fail-CLOSED on an unknown / forward-incompatible gate rule (aligned with Python + Rust): a
941
- // corrupt or unrecognized gate MUST NOT silently continue (fail-open would let a malformed gate
942
- // be skipped and the write COMMIT). Throwing here aborts the tx (the caller's catch ROLLBACKs).
943
- throw new Error(`scp write: unknown gate rule '${String(gate)}'`);
944
- }
945
- }
946
- /**
947
- * Execute a derived {@link TransactionPlan} as ONE real SQLite transaction with gate-first
948
- * short-circuit. Accepts a raw {@link SqliteDb} (wrapped via {@link contextForDriver}) or a full
949
- * {@link ExecutionContext}. The transaction derives a tx-scoped ctx (`withConnection(conn, true)`)
950
- * that PINS one connection so BEGIN, every body statement, and COMMIT/ROLLBACK all run on the SAME
951
- * connection (§3, per-execution ownership). For the single-DB SQLite driver the pinned connection
952
- * is the sole connection; the ownership shows its teeth on the pooled async path
953
- * ({@link import('../exec-context').withTransactionAsync}), which this mirrors.
954
- */
955
- function executeTransaction(db, plan, input, dialect = 'sqlite') {
956
- const outer = asContext(db);
957
- const executed = [];
958
- const scope = { ...input };
959
- let entity = null;
960
- // Batch mode (createMany/updateMany/deleteMany): a gate-free, ref-free plan (no `$.entity`, no
961
- // gates, no `$.ref` binds) — a pure list of body statements. Only THEN accumulate each body
962
- // statement's RETURNING rows in order (a composite Command also has `entityFrom:null` but carries
963
- // `binds`/gates and is NOT batch — its written rows flow via scope refs, not `returnedRows`).
964
- const isBatch = plan.entityFrom === null &&
965
- plan.statements.every((s) => s.gate === undefined && s.binds === undefined && s.role === 'body');
966
- const returnedRows = [];
967
- // The tx boundary is the sync `ctx.transaction` ({@link withTransactionSync}: pin conn → BEGIN →
968
- // body → COMMIT/ROLLBACK, inside `runInTransactionScope`). The body runs the derived plan's
969
- // statements in order via the `executeSQL` transport leaf ({@link execStatement}); a gate
970
- // short-circuit signals `{commit:false}` so the boundary ROLLBACKs but still returns the reason.
971
- try {
972
- return (0, exec_context_1.withTransactionSync)(outer, (ctx) => {
973
- for (const stmt of plan.statements) {
974
- const result = execStatement(ctx, stmt.op, scope, dialect);
975
- executed.push(stmt.id);
976
- if (stmt.gate !== undefined) {
977
- const reason = gateShortCircuit(stmt.gate, result);
978
- if (reason !== null) {
979
- return { commit: false, value: { committed: false, shortCircuit: { statementId: stmt.id, reason }, entity: null, executed } };
980
- }
981
- }
982
- if (stmt.id === plan.entityFrom) {
983
- entity = result.rows.length > 0 ? result.rows[0] : null;
984
- if (entity !== null)
985
- scope[writes_1.ENTITY_ROOT] = entity;
986
- }
987
- if (stmt.binds !== undefined && result.rows.length > 0) {
988
- scope[stmt.binds] = result.rows[0];
989
- }
990
- if (isBatch && stmt.role === 'body' && result.rows.length > 0)
991
- returnedRows.push(result.rows);
992
- }
993
- return { commit: true, value: { committed: true, entity, executed, ...(returnedRows.length > 0 ? { returnedRows } : {}) } };
994
- }, dialect);
995
- }
996
- catch (e) {
997
- throw (0, errors_1.mapSqliteError)(e);
998
- }
999
- }
1000
- /**
1001
- * A counting {@link SqliteDb} wrapper: forwards every call to the wrapped driver but records
1002
- * each PREPARED SQL string. Tests use it to PROVE gate-first short-circuit.
1003
- */
1004
- function countingDriver(db) {
1005
- const prepared = [];
1006
- const wrapped = {
1007
- prepare(sql) {
1008
- prepared.push(sql);
1009
- return db.prepare(sql);
1010
- },
1011
- };
1012
- return { db: wrapped, prepared };
1013
- }
1014
589
  /** Render one statement op to its dialect SQL text + params (exposed for golden tests). */
1015
590
  function renderTxStatement(op, scope, dialect = 'sqlite') {
1016
591
  return renderStatement(op, scope, dialect);
@@ -1038,12 +613,12 @@ async function execStatementAsync(ctx, op, scope, dialect) {
1038
613
  const { sql, params } = evalAssemble(op, scope, dialect);
1039
614
  // A SELECT/RETURNING reads rows; a bare write returns the `[{changes,…}]` summary.
1040
615
  const hasReturn = /\bselect\b/i.test(sql.slice(0, 8)) || /\breturning\b/i.test(sql);
1041
- const out = await (0, leaves_1.executeSQLAsync)({ sql, params, write: true, returning: hasReturn, bigint: false }, { execAsync: ctx, dialect });
616
+ const out = await (0, leaves_1.executeSQLAsync)({ sql, params, write: { returning: hasReturn } }, { execAsync: ctx, dialect });
1042
617
  return hasReturn ? { rows: out, changes: out.length } : { rows: [], changes: Number(out[0]?.changes ?? 0) };
1043
618
  }
1044
619
  /**
1045
- * Execute a derived {@link TransactionPlan} on a live PG / MySQL connection with gate-first
1046
- * short-circuit and **per-execution connection ownership** (§3). The live-DB WRITE entry (#86).
620
+ * Execute a derived {@link TransactionPlan} on a live PG / MySQL connection with **per-execution
621
+ * connection ownership** (§3). The live-DB WRITE entry (#86).
1047
622
  *
1048
623
  * ## Ambient-tx JOIN vs. its own envelope (the #86 core)
1049
624
  *
@@ -1086,8 +661,7 @@ function executeTransactionAsync(ctx, plan, input, dialect = 'sqlite', options =
1086
661
  }
1087
662
  /** The plan-executor body of {@link executeTransactionAsync}, split so the guard runs at entry. */
1088
663
  function runTransactionPlanAsync(ctx, plan, input, dialect, options) {
1089
- const isBatch = plan.entityFrom === null &&
1090
- plan.statements.every((s) => s.gate === undefined && s.binds === undefined && s.role === 'body');
664
+ const batch = isBatchPlan(plan);
1091
665
  return (0, exec_context_1.withTransactionAsync)(ctx, async (txCtx) => {
1092
666
  const executed = [];
1093
667
  const scope = { ...input };
@@ -1096,47 +670,22 @@ function runTransactionPlanAsync(ctx, plan, input, dialect, options) {
1096
670
  for (const stmt of plan.statements) {
1097
671
  const result = await execStatementAsync(txCtx, stmt.op, scope, dialect);
1098
672
  executed.push(stmt.id);
1099
- if (stmt.gate !== undefined) {
1100
- const reason = gateShortCircuit(stmt.gate, result);
1101
- if (reason !== null) {
1102
- // A gate short-circuit ROLLBACKs the whole tx (atomicity): throw the sentinel so
1103
- // `withTransactionAsync` runs ROLLBACK on the owned connection, then translate to the
1104
- // structured non-committed result at the boundary.
1105
- throw new GateShortCircuit(stmt.id, reason, executed);
1106
- }
1107
- }
1108
673
  if (stmt.id === plan.entityFrom) {
1109
674
  entity = result.rows.length > 0 ? result.rows[0] : null;
1110
675
  if (entity !== null)
1111
- scope[writes_1.ENTITY_ROOT] = entity;
676
+ scope[exports.ENTITY_ROOT] = entity;
1112
677
  }
1113
678
  if (stmt.binds !== undefined && result.rows.length > 0) {
1114
679
  scope[stmt.binds] = result.rows[0];
1115
680
  }
1116
- if (isBatch && stmt.role === 'body' && result.rows.length > 0)
681
+ if (batch && stmt.role === 'body' && result.rows.length > 0)
1117
682
  returnedRows.push(result.rows);
1118
683
  }
1119
684
  return { committed: true, entity, executed, ...(returnedRows.length > 0 ? { returnedRows } : {}) };
1120
685
  }, options, dialect === 'sqlite' ? 'postgres' : dialect, connection_errors_1.isConnectionError).catch((e) => {
1121
- // A gate short-circuit is NOT a failure — it ROLLBACKs and reports `committed:false`. Any other
1122
- // error is a real driver failure (already rolled back by withTransactionAsync) → re-surface.
1123
- if (e instanceof GateShortCircuit) {
1124
- return { committed: false, shortCircuit: { statementId: e.statementId, reason: e.reason }, entity: null, executed: e.executed };
1125
- }
686
+ // Every error here is a real driver failure (already rolled back by withTransactionAsync) — a plan
687
+ // has no non-error way to not commit, so there is nothing to translate before re-surfacing.
1126
688
  throw (0, errors_1.mapSqliteError)(e);
1127
689
  });
1128
690
  }
1129
- /** Internal sentinel: a gate short-circuit inside an async tx (ROLLBACK, then report non-committed). */
1130
- class GateShortCircuit extends Error {
1131
- statementId;
1132
- reason;
1133
- executed;
1134
- constructor(statementId, reason, executed) {
1135
- super(`scp write: gate short-circuit at '${statementId}' (${reason})`);
1136
- this.statementId = statementId;
1137
- this.reason = reason;
1138
- this.executed = executed;
1139
- this.name = 'GateShortCircuit';
1140
- }
1141
- }
1142
691
  //# sourceMappingURL=tx.js.map