litedbmodel 2.0.2 → 2.2.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.
- package/README.md +7 -0
- package/dist/DBConditions.d.ts.map +1 -1
- package/dist/DBConditions.js +16 -1
- package/dist/DBConditions.js.map +1 -1
- package/dist/DBHandler.d.ts.map +1 -1
- package/dist/DBHandler.js +6 -0
- package/dist/DBHandler.js.map +1 -1
- package/dist/DBModel.d.ts +84 -2
- package/dist/DBModel.d.ts.map +1 -1
- package/dist/DBModel.js +312 -29
- package/dist/DBModel.js.map +1 -1
- package/dist/decorators.d.ts +52 -0
- package/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +127 -24
- package/dist/decorators.js.map +1 -1
- package/dist/drivers/mysql.d.ts.map +1 -1
- package/dist/drivers/mysql.js +12 -2
- package/dist/drivers/mysql.js.map +1 -1
- package/dist/drivers/postgres.d.ts.map +1 -1
- package/dist/drivers/postgres.js +13 -2
- package/dist/drivers/postgres.js.map +1 -1
- package/dist/drivers/sqlite.d.ts +2 -0
- package/dist/drivers/sqlite.d.ts.map +1 -1
- package/dist/drivers/sqlite.js +49 -4
- package/dist/drivers/sqlite.js.map +1 -1
- package/dist/index.cjs +10741 -0
- package/dist/index.d.cts +1 -0
- package/dist/scp/coltype.d.ts +108 -0
- package/dist/scp/coltype.d.ts.map +1 -1
- package/dist/scp/coltype.js +293 -6
- package/dist/scp/coltype.js.map +1 -1
- package/dist/scp/connection-routing.d.ts +312 -0
- package/dist/scp/connection-routing.d.ts.map +1 -0
- package/dist/scp/connection-routing.js +394 -0
- package/dist/scp/connection-routing.js.map +1 -0
- package/dist/scp/dbmodel-runtime.d.ts +101 -0
- package/dist/scp/dbmodel-runtime.d.ts.map +1 -0
- package/dist/scp/dbmodel-runtime.js +174 -0
- package/dist/scp/dbmodel-runtime.js.map +1 -0
- package/dist/scp/decorator-adapter.d.ts +196 -0
- package/dist/scp/decorator-adapter.d.ts.map +1 -0
- package/dist/scp/decorator-adapter.js +286 -0
- package/dist/scp/decorator-adapter.js.map +1 -0
- package/dist/scp/emit/emitter.d.ts +108 -0
- package/dist/scp/emit/emitter.d.ts.map +1 -0
- package/dist/scp/emit/emitter.js +782 -0
- package/dist/scp/emit/emitter.js.map +1 -0
- package/dist/scp/emit/endpoint.d.ts +248 -0
- package/dist/scp/emit/endpoint.d.ts.map +1 -0
- package/dist/scp/emit/endpoint.js +20 -0
- package/dist/scp/emit/endpoint.js.map +1 -0
- package/dist/scp/emit/index.d.ts +11 -0
- package/dist/scp/emit/index.d.ts.map +1 -0
- package/dist/scp/emit/index.js +13 -0
- package/dist/scp/emit/index.js.map +1 -0
- package/dist/scp/errors.d.ts +45 -1
- package/dist/scp/errors.d.ts.map +1 -1
- package/dist/scp/errors.js +47 -1
- package/dist/scp/errors.js.map +1 -1
- package/dist/scp/exec-context.d.ts +390 -0
- package/dist/scp/exec-context.d.ts.map +1 -0
- package/dist/scp/exec-context.js +540 -0
- package/dist/scp/exec-context.js.map +1 -0
- package/dist/scp/grouping.d.ts +32 -0
- package/dist/scp/grouping.d.ts.map +1 -0
- package/dist/scp/grouping.js +0 -0
- package/dist/scp/grouping.js.map +1 -0
- package/dist/scp/index.cjs +6432 -17579
- package/dist/scp/index.d.cts +1 -0
- package/dist/scp/index.d.mts +1 -0
- package/dist/scp/index.d.ts +51 -52
- package/dist/scp/index.d.ts.map +1 -1
- package/dist/scp/index.js +189 -152
- package/dist/scp/index.js.map +1 -1
- package/dist/scp/index.mjs +6067 -3659
- package/dist/scp/leaf-transport.d.ts +50 -0
- package/dist/scp/leaf-transport.d.ts.map +1 -0
- package/dist/scp/leaf-transport.js +92 -0
- package/dist/scp/leaf-transport.js.map +1 -0
- package/dist/scp/leaves.d.ts +174 -0
- package/dist/scp/leaves.d.ts.map +1 -0
- package/dist/scp/leaves.js +287 -0
- package/dist/scp/leaves.js.map +1 -0
- package/dist/scp/limit-config.d.ts +118 -0
- package/dist/scp/limit-config.d.ts.map +1 -0
- package/dist/scp/limit-config.js +126 -0
- package/dist/scp/limit-config.js.map +1 -0
- package/dist/scp/makesql/compile-relation.d.ts +49 -23
- package/dist/scp/makesql/compile-relation.d.ts.map +1 -1
- package/dist/scp/makesql/compile-relation.js +101 -76
- package/dist/scp/makesql/compile-relation.js.map +1 -1
- package/dist/scp/makesql/compile-select.d.ts +45 -10
- package/dist/scp/makesql/compile-select.d.ts.map +1 -1
- package/dist/scp/makesql/compile-select.js +50 -10
- package/dist/scp/makesql/compile-select.js.map +1 -1
- package/dist/scp/makesql/handler.d.ts +1 -1
- package/dist/scp/makesql/handler.d.ts.map +1 -1
- package/dist/scp/makesql/index.d.ts +7 -10
- package/dist/scp/makesql/index.d.ts.map +1 -1
- package/dist/scp/makesql/index.js +19 -30
- package/dist/scp/makesql/index.js.map +1 -1
- package/dist/scp/makesql/json-array.d.ts +65 -0
- package/dist/scp/makesql/json-array.d.ts.map +1 -1
- package/dist/scp/makesql/json-array.js +99 -10
- package/dist/scp/makesql/json-array.js.map +1 -1
- package/dist/scp/makesql/json-batch.d.ts.map +1 -1
- package/dist/scp/makesql/json-batch.js +21 -19
- package/dist/scp/makesql/json-batch.js.map +1 -1
- package/dist/scp/makesql/makesql.d.ts +1 -1
- package/dist/scp/makesql/makesql.d.ts.map +1 -1
- package/dist/scp/makesql/mysql-returning.d.ts +93 -0
- package/dist/scp/makesql/mysql-returning.d.ts.map +1 -0
- package/dist/scp/makesql/mysql-returning.js +215 -0
- package/dist/scp/makesql/mysql-returning.js.map +1 -0
- package/dist/scp/makesql/outtype.d.ts +75 -34
- package/dist/scp/makesql/outtype.d.ts.map +1 -1
- package/dist/scp/makesql/outtype.js +91 -118
- package/dist/scp/makesql/outtype.js.map +1 -1
- package/dist/scp/makesql/pool-executor.d.ts +135 -13
- package/dist/scp/makesql/pool-executor.d.ts.map +1 -1
- package/dist/scp/makesql/pool-executor.js +229 -21
- package/dist/scp/makesql/pool-executor.js.map +1 -1
- package/dist/scp/makesql/tx.d.ts +107 -22
- package/dist/scp/makesql/tx.d.ts.map +1 -1
- package/dist/scp/makesql/tx.js +451 -97
- package/dist/scp/makesql/tx.js.map +1 -1
- package/dist/scp/makesql/writeouttype.d.ts +7 -8
- package/dist/scp/makesql/writeouttype.d.ts.map +1 -1
- package/dist/scp/makesql/writeouttype.js +8 -7
- package/dist/scp/makesql/writeouttype.js.map +1 -1
- package/dist/scp/middleware.d.ts +287 -0
- package/dist/scp/middleware.d.ts.map +1 -0
- package/dist/scp/middleware.js +358 -0
- package/dist/scp/middleware.js.map +1 -0
- package/dist/scp/relation.d.ts +80 -9
- package/dist/scp/relation.d.ts.map +1 -1
- package/dist/scp/relation.js +198 -78
- package/dist/scp/relation.js.map +1 -1
- package/dist/scp/tx-options.d.ts +136 -0
- package/dist/scp/tx-options.d.ts.map +1 -0
- package/dist/scp/tx-options.js +184 -0
- package/dist/scp/tx-options.js.map +1 -0
- package/dist/scp/write-bundle.d.ts +131 -0
- package/dist/scp/write-bundle.d.ts.map +1 -0
- package/dist/scp/write-bundle.js +160 -0
- package/dist/scp/write-bundle.js.map +1 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/docs/BENCHMARK.md +58 -56
- package/docs/benchmark-chart.svg +166 -166
- package/docs/design/phase-a-execution-context.md +139 -0
- package/package.json +16 -9
- package/dist/scp/authoring-sql.d.ts +0 -134
- package/dist/scp/authoring-sql.d.ts.map +0 -1
- package/dist/scp/authoring-sql.js +0 -191
- package/dist/scp/authoring-sql.js.map +0 -1
- package/dist/scp/authoring.d.ts +0 -155
- package/dist/scp/authoring.d.ts.map +0 -1
- package/dist/scp/authoring.js +0 -162
- package/dist/scp/authoring.js.map +0 -1
- package/dist/scp/catalog.d.ts +0 -94
- package/dist/scp/catalog.d.ts.map +0 -1
- package/dist/scp/catalog.js +0 -234
- package/dist/scp/catalog.js.map +0 -1
- package/dist/scp/codegen.d.ts +0 -189
- package/dist/scp/codegen.d.ts.map +0 -1
- package/dist/scp/codegen.js +0 -421
- package/dist/scp/codegen.js.map +0 -1
- package/dist/scp/find-filter-guard.d.ts +0 -68
- package/dist/scp/find-filter-guard.d.ts.map +0 -1
- package/dist/scp/find-filter-guard.js +0 -88
- package/dist/scp/find-filter-guard.js.map +0 -1
- package/dist/scp/guard.d.ts +0 -43
- package/dist/scp/guard.d.ts.map +0 -1
- package/dist/scp/guard.js +0 -134
- package/dist/scp/guard.js.map +0 -1
- package/dist/scp/makesql/authoring-compile.d.ts +0 -90
- package/dist/scp/makesql/authoring-compile.d.ts.map +0 -1
- package/dist/scp/makesql/authoring-compile.js +0 -494
- package/dist/scp/makesql/authoring-compile.js.map +0 -1
- package/dist/scp/makesql/ir.d.ts +0 -42
- package/dist/scp/makesql/ir.d.ts.map +0 -1
- package/dist/scp/makesql/ir.js +0 -66
- package/dist/scp/makesql/ir.js.map +0 -1
- package/dist/scp/makesql/static-bundle.d.ts +0 -187
- package/dist/scp/makesql/static-bundle.d.ts.map +0 -1
- package/dist/scp/makesql/static-bundle.js +0 -1113
- package/dist/scp/makesql/static-bundle.js.map +0 -1
- package/dist/scp/runtime.d.ts +0 -242
- package/dist/scp/runtime.d.ts.map +0 -1
- package/dist/scp/runtime.js +0 -400
- package/dist/scp/runtime.js.map +0 -1
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* litedbmodel v2 SCP — the **ExecutionContext + central execute/run seam** (Phase A / #75).
|
|
4
|
+
*
|
|
5
|
+
* This module is the CONTRACT-DEFINING artifact for Phase A: it fixes the shape every runtime
|
|
6
|
+
* (TS here; rust #76 / go #77 / py #78 / php #79 port it) follows. It replaces the raw `driver`
|
|
7
|
+
* parameter threaded through `executeBundle` / `executeReadGraph` / `executeTransaction` with an
|
|
8
|
+
* {@link ExecutionContext} that carries:
|
|
9
|
+
*
|
|
10
|
+
* 1. a **connection provider** — {@link ExecutionContext.connectionFor}`(intent)` resolves WHICH
|
|
11
|
+
* connection a statement runs on (tx-owned / reader / writer / named-DB; Phase A only wires the
|
|
12
|
+
* tx-owned + single-DB cases, the rest are B/C/D on this same seam);
|
|
13
|
+
* 2. a **middleware chain** — {@link ExecutionContext.middleware}, wrapping every SQL (empty in
|
|
14
|
+
* Phase A = passthrough; the registration API is Phase D — this is only the hook point);
|
|
15
|
+
* 3. {@link ExecutionContext.withConnection}`(conn, tx)` — derive a tx-scoped ctx that pins ONE
|
|
16
|
+
* connection so every statement in a transaction body runs on it.
|
|
17
|
+
*
|
|
18
|
+
* ## The central seam (§2 of the design) — ALL SQL funnels through here
|
|
19
|
+
*
|
|
20
|
+
* ```
|
|
21
|
+
* execute(ctx, sql, params) -> Rows // SELECT / RETURNING reads
|
|
22
|
+
* run(ctx, sql, params) -> RunInfo // INSERT/UPDATE/DELETE, BEGIN/COMMIT/ROLLBACK
|
|
23
|
+
* ```
|
|
24
|
+
*
|
|
25
|
+
* Both do the SAME three things, in order:
|
|
26
|
+
* ① run the middleware chain (empty ⇒ passthrough, behavior unchanged);
|
|
27
|
+
* ② resolve the connection via `ctx.connectionFor(intent)`;
|
|
28
|
+
* ③ execute on that connection (the ONLY driver contact point).
|
|
29
|
+
*
|
|
30
|
+
* Every direct `driver.prepare(sql).all()/run()` in the read / tx / relation path is replaced by a
|
|
31
|
+
* call through this seam. A `grep` for `.prepare(` outside this file's connection adapters must come
|
|
32
|
+
* up empty in the runtime SQL path — that is the AC.
|
|
33
|
+
*
|
|
34
|
+
* ## Two flavors — sync (SQLite) and async (live PG / MySQL)
|
|
35
|
+
*
|
|
36
|
+
* The in-process better-sqlite3 path is **synchronous** (and must stay byte-identical to the frozen
|
|
37
|
+
* conformance corpus), so it uses a {@link SyncConnection} and the {@link execute} / {@link run}
|
|
38
|
+
* seam. The live PG / MySQL path is **async + pooled**, where per-execution connection ownership is
|
|
39
|
+
* what actually fixes the concurrent-tx isolation bug; it uses an {@link AsyncConnection} and the
|
|
40
|
+
* {@link executeAsync} / {@link runAsync} seam plus {@link withTransactionAsync}.
|
|
41
|
+
*
|
|
42
|
+
* Both flavors implement the SAME contract shape (`connectionFor` / `middleware` / `withConnection`
|
|
43
|
+
* / a central seam / `withTransaction`); the native ports mirror whichever their runtime needs
|
|
44
|
+
* (rust/go = the owned-connection async model; py = contextvars; php = the single-connection sync
|
|
45
|
+
* model — same contract, no ALS since PHP is 1-request-1-process).
|
|
46
|
+
*
|
|
47
|
+
* ## Per-execution connection ownership (§3) — the concurrent-tx fix
|
|
48
|
+
*
|
|
49
|
+
* A transaction acquires ONE connection, scopes it into an {@link AsyncLocalStorage} ctx, runs its
|
|
50
|
+
* body (every statement resolves that connection via `connectionFor`), COMMITs/ROLLBACKs, and
|
|
51
|
+
* releases it. Concurrent transactions each run in their own ALS ctx with their own connection —
|
|
52
|
+
* **isolated**. There is NO driver-global single-slot writer (the shared-slot model — `pool.query`
|
|
53
|
+
* per statement, or a `Mutex<Option<writer>>` — is what corrupts concurrent transactions; see the
|
|
54
|
+
* `test/integration/tx-isolation` proof).
|
|
55
|
+
*/
|
|
56
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
57
|
+
exports.PooledAsyncContext = exports.MiddlewareChain = void 0;
|
|
58
|
+
exports.execute = execute;
|
|
59
|
+
exports.executeSafe = executeSafe;
|
|
60
|
+
exports.run = run;
|
|
61
|
+
exports.executeAsync = executeAsync;
|
|
62
|
+
exports.runAsync = runAsync;
|
|
63
|
+
exports.runGuarded = runGuarded;
|
|
64
|
+
exports.runGuardedAsync = runGuardedAsync;
|
|
65
|
+
exports.connectionForDriver = connectionForDriver;
|
|
66
|
+
exports.contextForDriver = contextForDriver;
|
|
67
|
+
exports.contextForConnection = contextForConnection;
|
|
68
|
+
exports.runWithPinnedAsyncConnection = runWithPinnedAsyncConnection;
|
|
69
|
+
exports.currentPinnedAsyncConnection = currentPinnedAsyncConnection;
|
|
70
|
+
exports.withTransactionAsync = withTransactionAsync;
|
|
71
|
+
exports.transaction = transaction;
|
|
72
|
+
exports.withTransactionSync = withTransactionSync;
|
|
73
|
+
const node_async_hooks_1 = require("node:async_hooks");
|
|
74
|
+
const tx_options_1 = require("./tx-options");
|
|
75
|
+
const connection_routing_1 = require("./connection-routing");
|
|
76
|
+
const middleware_1 = require("./middleware");
|
|
77
|
+
/**
|
|
78
|
+
* The ordered middleware chain a ctx carries (§4). `wrap` folds the middlewares around `next`
|
|
79
|
+
* (the connection-resolve + execute terminal). An EMPTY chain is a pure passthrough — `wrap`
|
|
80
|
+
* returns `next(sql, params)` verbatim, so an unregistered chain is byte-identical. The chain is
|
|
81
|
+
* generic over the seam result type `T` so ONE shape serves both the sync (`Rows`/`RunInfo`) and
|
|
82
|
+
* async (`Promise<…>`) seams.
|
|
83
|
+
*
|
|
84
|
+
* The stack is resolved by a {@link MiddlewareStackSource} at EACH `wrap` (Phase D) rather than
|
|
85
|
+
* captured at construction, so registration (`DBModel.use`) that happens after the ctx is built is
|
|
86
|
+
* still honored and per-execution-scope registries (concurrent isolation) resolve correctly. A chain
|
|
87
|
+
* constructed with an explicit fixed stack (or none) still works — that stack is its constant source.
|
|
88
|
+
*/
|
|
89
|
+
class MiddlewareChain {
|
|
90
|
+
/** Resolve the CURRENT registered SQL-level middlewares, outermost first, at `wrap` time. */
|
|
91
|
+
source;
|
|
92
|
+
constructor(stackOrSource = []) {
|
|
93
|
+
this.source = typeof stackOrSource === 'function' ? stackOrSource : () => stackOrSource;
|
|
94
|
+
}
|
|
95
|
+
/** Is the chain empty RIGHT NOW (⇒ this `wrap` is a guaranteed passthrough)? */
|
|
96
|
+
get isEmpty() {
|
|
97
|
+
return this.source().length === 0;
|
|
98
|
+
}
|
|
99
|
+
/** Fold the chain around `next`, then invoke it. Empty chain ⇒ `next(sql, params)` verbatim. */
|
|
100
|
+
wrap(sql, params, next) {
|
|
101
|
+
const stack = this.source();
|
|
102
|
+
if (stack.length === 0)
|
|
103
|
+
return next(sql, params);
|
|
104
|
+
let fn = next;
|
|
105
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
106
|
+
const mw = stack[i];
|
|
107
|
+
const inner = fn;
|
|
108
|
+
fn = (s, p) => mw(s, p, inner);
|
|
109
|
+
}
|
|
110
|
+
return fn(sql, params);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
exports.MiddlewareChain = MiddlewareChain;
|
|
114
|
+
// ── The central seam (§2) — the ONLY place SQL meets a connection ─────────────
|
|
115
|
+
/**
|
|
116
|
+
* Central READ seam (sync): ① middleware chain, ② resolve the connection, ③ execute. Every read
|
|
117
|
+
* (primary read node, relation batch) funnels through here.
|
|
118
|
+
*/
|
|
119
|
+
function execute(ctx, sql, params, intent = { write: false }) {
|
|
120
|
+
return ctx.middleware.wrap(sql, params, (s, p) => ctx.connectionFor(intent).execute(s, p));
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Central READ seam (sync) in `safeIntegers` mode (§2 + the #59 BIGINT-exact read). Same
|
|
124
|
+
* middleware → connectionFor → execute path, but the terminal runs the SELECT in exact-integer mode
|
|
125
|
+
* so int8/BIGINT columns materialize without rounding. Used by the read walker for a BIGINT-
|
|
126
|
+
* projecting node; a non-BIGINT node uses the plain {@link execute}.
|
|
127
|
+
*/
|
|
128
|
+
function executeSafe(ctx, sql, params, intent = { write: false }) {
|
|
129
|
+
return ctx.middleware.wrap(sql, params, (s, p) => ctx.connectionFor(intent).executeSafeIntegers(s, p));
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Central WRITE seam (sync): ① middleware chain, ② resolve the connection, ③ run. Every write and
|
|
133
|
+
* every tx-control statement (BEGIN/COMMIT/ROLLBACK) funnels through here (writes ⇒ `intent.write`).
|
|
134
|
+
*/
|
|
135
|
+
function run(ctx, sql, params, intent = { write: true }) {
|
|
136
|
+
return ctx.middleware.wrap(sql, params, (s, p) => ctx.connectionFor(intent).run(s, p));
|
|
137
|
+
}
|
|
138
|
+
/** Central READ seam (async) — the live PG / MySQL twin of {@link execute}. */
|
|
139
|
+
function executeAsync(ctx, sql, params, intent = { write: false }) {
|
|
140
|
+
return ctx.middleware.wrap(sql, params, (s, p) => ctx.connectionFor(intent).execute(s, p));
|
|
141
|
+
}
|
|
142
|
+
/** Central WRITE seam (async) — the live PG / MySQL twin of {@link run}. */
|
|
143
|
+
function runAsync(ctx, sql, params, intent = { write: true }) {
|
|
144
|
+
return ctx.middleware.wrap(sql, params, (s, p) => ctx.connectionFor(intent).run(s, p));
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* GUARDED write seam (sync): enforce the write=tx guard ({@link checkWriteAllowed}) for a
|
|
148
|
+
* DATA-mutating statement, then delegate to {@link run}. A write issued OUTSIDE a transaction throws
|
|
149
|
+
* {@link WriteOutsideTransactionError}; a write in a {@link withReadOnly} scope throws
|
|
150
|
+
* {@link WriteInReadOnlyContextError}. Tx-control statements (BEGIN/COMMIT/ROLLBACK/SET) are NOT
|
|
151
|
+
* guarded — the tx runtime issues them to OPEN the very scope the guard checks. This is the seam a
|
|
152
|
+
* bare model-level write (create/update/delete/upsert/batch) goes through.
|
|
153
|
+
*/
|
|
154
|
+
function runGuarded(ctx, sql, params, operation, modelName) {
|
|
155
|
+
(0, tx_options_1.checkWriteAllowed)(operation, modelName);
|
|
156
|
+
return run(ctx, sql, params, { write: true });
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* GUARDED write seam (async) — the live PG / MySQL twin of {@link runGuarded}. The guard is enforced
|
|
160
|
+
* inside the returned promise (an async caller gets a REJECTED promise, never a synchronous throw).
|
|
161
|
+
*/
|
|
162
|
+
async function runGuardedAsync(ctx, sql, params, operation, modelName) {
|
|
163
|
+
(0, tx_options_1.checkWriteAllowed)(operation, modelName);
|
|
164
|
+
return runAsync(ctx, sql, params, { write: true });
|
|
165
|
+
}
|
|
166
|
+
/** Adapt a better-sqlite3 `Database` to the {@link SyncConnection} seam (the ONE driver contact). */
|
|
167
|
+
function connectionForDriver(driver) {
|
|
168
|
+
return {
|
|
169
|
+
execute(sql, params) {
|
|
170
|
+
return driver.prepare(sql).all(...params);
|
|
171
|
+
},
|
|
172
|
+
executeSafeIntegers(sql, params) {
|
|
173
|
+
const stmt = driver.prepare(sql);
|
|
174
|
+
// better-sqlite3: run in exact-integer mode so int8/BIGINT arrives as a bigint (the #59 hole:
|
|
175
|
+
// a JS number would round it). A driver without the toggle degrades to a plain `.all` (its
|
|
176
|
+
// rows come back in whatever exact form it natively uses — pg/mysql string BIGINT).
|
|
177
|
+
if (typeof stmt.safeIntegers === 'function')
|
|
178
|
+
stmt.safeIntegers(true);
|
|
179
|
+
return stmt.all(...params);
|
|
180
|
+
},
|
|
181
|
+
run(sql, params) {
|
|
182
|
+
return driver.prepare(sql).run(...params);
|
|
183
|
+
},
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* A thin, single-DB, middleware-free {@link ExecutionContext} over ONE connection. A tx-scoped ctx
|
|
188
|
+
* (`withConnection(conn, true)`) pins that connection for every `connectionFor` — this is the
|
|
189
|
+
* per-execution connection ownership (§3). Absent a pinned tx connection, it returns the base
|
|
190
|
+
* connection (the single-DB Phase A case; reader/writer/named-DB routing is B/C/D on this seam).
|
|
191
|
+
*/
|
|
192
|
+
class BasicContext {
|
|
193
|
+
middleware;
|
|
194
|
+
base;
|
|
195
|
+
/** The pinned tx connection (present ⇒ this is a tx-scoped ctx; every statement uses it). */
|
|
196
|
+
pinned;
|
|
197
|
+
constructor(base, middleware, pinned) {
|
|
198
|
+
this.base = base;
|
|
199
|
+
this.middleware = middleware;
|
|
200
|
+
this.pinned = pinned;
|
|
201
|
+
}
|
|
202
|
+
connectionFor(_intent) {
|
|
203
|
+
// Phase A resolution: the tx-owned (pinned) connection wins; else the single base connection.
|
|
204
|
+
// Reader/writer split (§3-2/3) + named-DB routing (§3-4) extend HERE in B/C/D.
|
|
205
|
+
return this.pinned ?? this.base;
|
|
206
|
+
}
|
|
207
|
+
withConnection(conn, tx) {
|
|
208
|
+
return new BasicContext(this.base, this.middleware, tx ? conn : null);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* **Backward-compat wrapper (§6).** Wrap a raw better-sqlite3 `Database` in a thin
|
|
213
|
+
* {@link ExecutionContext}: reader = writer = the same driver, a single DB. Its middleware chain
|
|
214
|
+
* resolves the ambient/global SQL-level {@link import('./middleware').MiddlewareRegistry} at wrap
|
|
215
|
+
* time (Phase D), so a middleware registered via `DBModel.use(...)` intercepts the SQL — and, when
|
|
216
|
+
* NO middleware is registered, the chain is empty and the seam is a pure passthrough to
|
|
217
|
+
* `driver.prepare(...).all()/run()`, i.e. **byte-identical** (the conformance/livedb runners
|
|
218
|
+
* register none, so they are unchanged).
|
|
219
|
+
*/
|
|
220
|
+
function contextForDriver(driver) {
|
|
221
|
+
return new BasicContext(connectionForDriver(driver), new MiddlewareChain(middleware_1.activeSqlMiddlewares), null);
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Build a sync ctx directly over a {@link SyncConnection} (for a caller that already owns a
|
|
225
|
+
* connection adapter — e.g. a non-better-sqlite3 sync backend). Same single-DB / ambient-middleware
|
|
226
|
+
* shape as {@link contextForDriver}.
|
|
227
|
+
*/
|
|
228
|
+
function contextForConnection(conn) {
|
|
229
|
+
return new BasicContext(conn, new MiddlewareChain(middleware_1.activeSqlMiddlewares), null);
|
|
230
|
+
}
|
|
231
|
+
/** Per-async-execution ambient ctx: the ALS slot carrying the tx-scoped connection (§3). */
|
|
232
|
+
const asyncCtxStore = new node_async_hooks_1.AsyncLocalStorage();
|
|
233
|
+
/**
|
|
234
|
+
* A pooled async {@link AsyncExecutionContext}. Outside a transaction, `connectionFor` acquires a
|
|
235
|
+
* fresh pooled connection per statement (the existing read fan-out model — each concurrent sibling
|
|
236
|
+
* on its own connection). Inside a transaction, {@link withTransactionAsync} pins ONE acquired
|
|
237
|
+
* connection into the ALS store; `connectionFor` returns THAT for every statement in the body, so
|
|
238
|
+
* the whole tx runs on one owned connection — isolated from concurrent transactions.
|
|
239
|
+
*
|
|
240
|
+
* NB: outside a tx, `connectionFor` returns a **per-statement** owned connection wrapper that
|
|
241
|
+
* acquires-runs-releases; the read walker issues one statement per `executeAsync`, matching the
|
|
242
|
+
* existing `SqlExecutorAsync` "acquire per exec" semantics byte-for-byte.
|
|
243
|
+
*/
|
|
244
|
+
class PooledAsyncContext {
|
|
245
|
+
middleware;
|
|
246
|
+
/**
|
|
247
|
+
* The full routing config (§3 steps 2-4): the multi-DB {@link ConnectionRegistry} + the
|
|
248
|
+
* {@link WriterStickyClock}. A ctx built from a single `pool` synthesizes a default-only registry
|
|
249
|
+
* (reader === writer) + an always-false sticky clock, so its resolution is byte-identical to the
|
|
250
|
+
* Phase A/B single-pool path. A ctx built with a {@link RoutingConfig} (via {@link setConfig})
|
|
251
|
+
* gets reader/writer separation, writer-sticky, and named-DB routing.
|
|
252
|
+
*/
|
|
253
|
+
routing;
|
|
254
|
+
/**
|
|
255
|
+
* Construct from EITHER a single {@link AsyncConnectionPool} (backward-compat: default connection,
|
|
256
|
+
* reader === writer, sticky disabled — byte-identical to Phase A/B) OR a full {@link RoutingConfig}
|
|
257
|
+
* (Phase C: reader/writer separation + writer-sticky + named-DB routing).
|
|
258
|
+
*/
|
|
259
|
+
constructor(poolOrRouting, middleware = new MiddlewareChain(middleware_1.activeSqlMiddlewares)) {
|
|
260
|
+
this.middleware = middleware;
|
|
261
|
+
this.routing = isRoutingConfig(poolOrRouting)
|
|
262
|
+
? poolOrRouting
|
|
263
|
+
: // Single-pool backward-compat: default-only registry (reader === writer) + a sticky clock
|
|
264
|
+
// that is NEVER marked (so `isSticky()` stays false) ⇒ every intent lands on the one pool.
|
|
265
|
+
{ registry: connection_routing_1.ConnectionRegistry.singleDefault(poolOrRouting), sticky: new connection_routing_1.WriterStickyClock({ useWriterAfterTransaction: false }) };
|
|
266
|
+
}
|
|
267
|
+
connectionFor(intent) {
|
|
268
|
+
// STEP 1 (§3): inside a tx, the ALS-pinned owned connection wins — every statement on the SAME
|
|
269
|
+
// conn (the per-execution ownership the concurrent-tx fix depends on). The tx pin is a per-scope
|
|
270
|
+
// fact only the ctx's ALS holds, so it is resolved HERE, before the pool-selection steps.
|
|
271
|
+
const pinned = asyncCtxStore.getStore();
|
|
272
|
+
if (pinned !== undefined)
|
|
273
|
+
return pinned;
|
|
274
|
+
// STEPS 2-4 (§3): named-DB → reader/writer split → writer-sticky/withWriter. `resolvePool`
|
|
275
|
+
// returns WHICH pool serves this intent; the returned wrapper acquires/runs/releases one owned
|
|
276
|
+
// connection per statement (the read fan-out: each concurrent sibling on its own connection).
|
|
277
|
+
const pool = (0, connection_routing_1.resolvePool)(intent, this.routing);
|
|
278
|
+
return {
|
|
279
|
+
async execute(sql, params) {
|
|
280
|
+
const c = await pool.acquire();
|
|
281
|
+
try {
|
|
282
|
+
return await c.execute(sql, params);
|
|
283
|
+
}
|
|
284
|
+
finally {
|
|
285
|
+
await pool.release(c);
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
async run(sql, params) {
|
|
289
|
+
const c = await pool.acquire();
|
|
290
|
+
try {
|
|
291
|
+
return await c.run(sql, params);
|
|
292
|
+
}
|
|
293
|
+
finally {
|
|
294
|
+
await pool.release(c);
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
withConnection(_conn, _tx) {
|
|
300
|
+
// Deriving a tx-scoped ctx pins the connection via the ALS run in withTransactionAsync (not by
|
|
301
|
+
// mutating this object); the derived ctx shares the routing + middleware and is never used to
|
|
302
|
+
// acquire (the ALS-pinned connection wins in `connectionFor`).
|
|
303
|
+
return this;
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* The WRITER pool of the connection a transaction runs against (so `withTransactionAsync` acquires
|
|
307
|
+
* the tx's owned connection from the right pool). A transaction is a write ⇒ the writer pool of the
|
|
308
|
+
* named connection (`intent.db`). Absent `db` ⇒ the default connection's writer.
|
|
309
|
+
*/
|
|
310
|
+
connectionPoolFor(intent = { write: true }) {
|
|
311
|
+
return this.routing.registry.pairFor(intent.db).writer;
|
|
312
|
+
}
|
|
313
|
+
/** The default connection's writer pool (backward-compat accessor for the single-DB tx path). */
|
|
314
|
+
get connectionPool() {
|
|
315
|
+
return this.routing.registry.pairFor(undefined).writer;
|
|
316
|
+
}
|
|
317
|
+
/** The writer-sticky clock (the tx runtime `.mark()`s it on a successful write/commit). */
|
|
318
|
+
get stickyClock() {
|
|
319
|
+
return this.routing.sticky;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
exports.PooledAsyncContext = PooledAsyncContext;
|
|
323
|
+
/** Narrow the {@link PooledAsyncContext} ctor arg: is it a {@link RoutingConfig} (vs. a bare pool)? */
|
|
324
|
+
function isRoutingConfig(x) {
|
|
325
|
+
return typeof x === 'object' && x !== null && 'registry' in x && 'sticky' in x;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Run `fn` inside the ALS scope with `conn` pinned as the ambient tx connection (§3). Every
|
|
329
|
+
* `connectionFor` inside `fn` (async boundaries included) returns `conn`. This is the TS
|
|
330
|
+
* per-execution ownership mechanism (v1 `DBModel.ts` `txContext.run`); the native ports use
|
|
331
|
+
* task-local (rust) / `context.Context` (go) / contextvars (py) / an explicit arg (php) for the
|
|
332
|
+
* SAME effect.
|
|
333
|
+
*/
|
|
334
|
+
function runWithPinnedAsyncConnection(conn, fn) {
|
|
335
|
+
return asyncCtxStore.run(conn, fn);
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* The tx-owned connection pinned in the CURRENT async scope, or `undefined` if not inside a
|
|
339
|
+
* transaction. {@link withTransactionAsync} reads this to detect a NESTED transaction (an inner
|
|
340
|
+
* `withTransactionAsync` running inside an outer's ALS scope joins the outer instead of opening a
|
|
341
|
+
* new physical transaction). The native ports read their task-local / contextvar / `context.Context`
|
|
342
|
+
* for the SAME nested detection.
|
|
343
|
+
*/
|
|
344
|
+
function currentPinnedAsyncConnection() {
|
|
345
|
+
return asyncCtxStore.getStore();
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Is `err` a broken/stale connection (retryable via reconnect)? Defaulted here to a message/-code
|
|
349
|
+
* heuristic matching `src/connection-errors.ts` so exec-context needs no cross-module import; the
|
|
350
|
+
* runtime passes the shared {@link import('../connection-errors').isConnectionError} in explicitly.
|
|
351
|
+
*/
|
|
352
|
+
function defaultIsConnectionError(err) {
|
|
353
|
+
const message = err.message || '';
|
|
354
|
+
const code = err.code || '';
|
|
355
|
+
return (message.includes('Connection terminated') ||
|
|
356
|
+
message.includes('Client has encountered a connection error') ||
|
|
357
|
+
message.includes('ECONNRESET') ||
|
|
358
|
+
message.includes('ECONNREFUSED') ||
|
|
359
|
+
message.includes('Connection lost') ||
|
|
360
|
+
message.includes('This socket has been ended by the other party') ||
|
|
361
|
+
code === 'ECONNRESET' ||
|
|
362
|
+
code === 'ECONNREFUSED' ||
|
|
363
|
+
code === 'EPIPE' ||
|
|
364
|
+
code === 'PROTOCOL_CONNECTION_LOST');
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Run a transaction with **per-execution connection ownership** (§3, the concurrent-tx fix) plus the
|
|
368
|
+
* full Phase B tx-completeness contract (#81): isolation level, nested-tx join, rollbackOnly, and
|
|
369
|
+
* whole-tx retry with per-attempt connection acquisition.
|
|
370
|
+
*
|
|
371
|
+
* **Per attempt** (up to `retryLimit`):
|
|
372
|
+
* 1. acquire ONE fresh connection from the pool (a retry after a connection error thus RECONNECTS);
|
|
373
|
+
* 2. pin it into the ALS ctx (`runWithPinnedAsyncConnection`) so EVERY statement `fn` issues
|
|
374
|
+
* resolves THAT connection — never a fresh pooled one;
|
|
375
|
+
* 3. issue the isolation-aware BEGIN ({@link beginStatements}: PG `BEGIN ISOLATION LEVEL …`;
|
|
376
|
+
* MySQL a preceding `SET TRANSACTION ISOLATION LEVEL …` then `BEGIN`);
|
|
377
|
+
* 4. run `fn(txCtx)` → `COMMIT` (or `ROLLBACK` if `rollbackOnly`, still returning the body result);
|
|
378
|
+
* 5. on a body/commit error `ROLLBACK` and either RETRY (retryable error + attempts remain, with
|
|
379
|
+
* exponential backoff `retryDuration·2^(k-1)`) or re-raise;
|
|
380
|
+
* 6. release the connection back to the pool (DESTROYED if the ROLLBACK itself failed, or the whole
|
|
381
|
+
* tx errored non-cleanly — a poisoned connection must not re-enter the pool).
|
|
382
|
+
*
|
|
383
|
+
* **Nested**: if a tx-owned connection is ALREADY pinned in the ALS (an outer `withTransactionAsync`
|
|
384
|
+
* on this async chain), the inner call JOINS the outer — it runs `fn` directly on the outer's ctx
|
|
385
|
+
* with NO new BEGIN/COMMIT/acquire, so the whole nested body is one physical transaction (an inner
|
|
386
|
+
* error propagates and rolls back the WHOLE tx). Mirrors v1 `DBModel.transaction` :2794-2797.
|
|
387
|
+
*
|
|
388
|
+
* Concurrent (non-nested) `withTransactionAsync` calls each acquire a DISTINCT connection and pin it
|
|
389
|
+
* in their OWN ALS scope, so their writes never cross-talk — the isolation the shared-slot model
|
|
390
|
+
* violates. This is the reference the native ports (#82-85) mirror.
|
|
391
|
+
*/
|
|
392
|
+
async function withTransactionAsync(ctx, fn, opts = {}, dialect = 'postgres', isConnectionError = defaultIsConnectionError, connection) {
|
|
393
|
+
// NESTED-TX JOIN (§ mirror v1 :2794): already inside a tx on this async chain ⇒ join the outer.
|
|
394
|
+
// No new connection, no BEGIN/COMMIT — the inner body is part of the outer physical transaction.
|
|
395
|
+
const outerConn = currentPinnedAsyncConnection();
|
|
396
|
+
if (outerConn !== undefined) {
|
|
397
|
+
// Reuse the outer's ctx (the pinned conn already wins in `connectionFor`). Isolation/retry/
|
|
398
|
+
// rollbackOnly options on a NESTED call are ignored — the outer transaction owns them.
|
|
399
|
+
return fn(ctx.withConnection(outerConn, true));
|
|
400
|
+
}
|
|
401
|
+
const resolved = (0, tx_options_1.resolveTxOptions)(opts);
|
|
402
|
+
const begins = (0, tx_options_1.beginStatements)(dialect, resolved.isolation);
|
|
403
|
+
// Named-DB routing (§3-4): a transaction is a write ⇒ acquire from the WRITER pool of the target
|
|
404
|
+
// connection (`connection` name, or the default). Single-DB ⇒ the one default writer pool.
|
|
405
|
+
const pool = ctx.connectionPoolFor({ write: true, ...(connection !== undefined ? { db: connection } : {}) });
|
|
406
|
+
let attempt = 0;
|
|
407
|
+
for (;;) {
|
|
408
|
+
attempt++;
|
|
409
|
+
const conn = await pool.acquire();
|
|
410
|
+
const txCtx = ctx.withConnection(conn, true);
|
|
411
|
+
// One attempt: BEGIN…body…COMMIT/ROLLBACK on the pinned conn. Returns `{ ok, value }` on success
|
|
412
|
+
// or `{ ok:false, error }` on failure — so the release + retry decision happens OUTSIDE the ALS
|
|
413
|
+
// run and the connection is released EXACTLY once per attempt.
|
|
414
|
+
let poisoned = false;
|
|
415
|
+
const attemptResult = await runWithPinnedAsyncConnection(conn, async () => {
|
|
416
|
+
for (const begin of begins)
|
|
417
|
+
await runAsync(txCtx, begin, []);
|
|
418
|
+
try {
|
|
419
|
+
// Mark the body as "inside a transaction" so a nested write's guard (`checkWriteAllowed`)
|
|
420
|
+
// and nested-tx detection see an active tx. The connection pin (above) and this marker share
|
|
421
|
+
// the SAME async scope.
|
|
422
|
+
const r = await (0, tx_options_1.runInTransactionScope)(() => fn(txCtx));
|
|
423
|
+
// rollbackOnly (dry-run): ROLLBACK but still return the body result — no committed change.
|
|
424
|
+
await runAsync(txCtx, resolved.rollbackOnly ? 'ROLLBACK' : 'COMMIT', []);
|
|
425
|
+
return { ok: true, value: r };
|
|
426
|
+
}
|
|
427
|
+
catch (error) {
|
|
428
|
+
try {
|
|
429
|
+
await runAsync(txCtx, 'ROLLBACK', []);
|
|
430
|
+
}
|
|
431
|
+
catch {
|
|
432
|
+
poisoned = true; // ROLLBACK failed ⇒ the connection is in an unknown state; drop it.
|
|
433
|
+
}
|
|
434
|
+
return { ok: false, error };
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
// An errored attempt saw a failed statement — destroy the (possibly poisoned) conn so a retry
|
|
438
|
+
// reconnects on a fresh one; a clean success returns the conn to the pool.
|
|
439
|
+
await pool.release(conn, poisoned || !attemptResult.ok);
|
|
440
|
+
if (attemptResult.ok) {
|
|
441
|
+
// WRITER-STICKY (§3-2, read-your-writes): a committed tx marks the sticky clock so subsequent
|
|
442
|
+
// reads within `writerStickyDuration` route to the writer pool (v1 `_lastTransactionTime`). A
|
|
443
|
+
// rollbackOnly (dry-run) tx committed NOTHING ⇒ it does NOT arm stickiness, and a tx that opted
|
|
444
|
+
// OUT per-transaction (`useWriterAfterTransaction: false`) does not arm it either — mirror v1
|
|
445
|
+
// `DBModel.transaction` :3126. The GLOBAL setting still has the last word: `mark()` is a no-op
|
|
446
|
+
// on a clock the deployment disabled, so a per-tx `true` cannot force stickiness on (v1 :487).
|
|
447
|
+
if (!resolved.rollbackOnly && resolved.useWriterAfterTransaction)
|
|
448
|
+
ctx.stickyClock.mark();
|
|
449
|
+
return attemptResult.value;
|
|
450
|
+
}
|
|
451
|
+
const { error } = attemptResult;
|
|
452
|
+
if (resolved.retryOnError && attempt < resolved.retryLimit && (0, tx_options_1.isRetryableTxError)(error, isConnectionError)) {
|
|
453
|
+
await (0, tx_options_1.sleep)(resolved.retryDuration * Math.pow(2, attempt - 1));
|
|
454
|
+
continue; // RETRY the whole transaction on a fresh connection
|
|
455
|
+
}
|
|
456
|
+
throw error;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
// ── The PUBLIC user-controlled transaction boundary (§ Phase B-core / #86) ─────
|
|
460
|
+
/**
|
|
461
|
+
* **The public user-controlled transaction boundary** (#86) — the REAL transaction feature v2 was
|
|
462
|
+
* missing. `transaction(ctx, fn, options?)` opens ONE boundary the caller wraps around MULTIPLE
|
|
463
|
+
* arbitrary operations so they commit or roll back TOGETHER:
|
|
464
|
+
*
|
|
465
|
+
* ```ts
|
|
466
|
+
* await transaction(ctx, async () => {
|
|
467
|
+
* await A.create(...); // ← every op inside joins this ONE boundary:
|
|
468
|
+
* await B.update(...); // one connection, one BEGIN…COMMIT, all-or-nothing.
|
|
469
|
+
* }, { isolation: 'serializable' });
|
|
470
|
+
* ```
|
|
471
|
+
*
|
|
472
|
+
* ## What it does (v1 `DBModel.transaction` :2787 parity, on the SCP seam)
|
|
473
|
+
*
|
|
474
|
+
* It acquires ONE pooled connection, issues the isolation-aware `BEGIN`
|
|
475
|
+
* ({@link import('./tx-options').beginStatements}), PINS that connection into the ALS ctx, runs
|
|
476
|
+
* `fn`, then `COMMIT` (or `ROLLBACK` on a body error / `options.rollbackOnly`), with the #81 retry
|
|
477
|
+
* loop (deadlock / serialization / connection error) wrapped around the WHOLE boundary. It is a thin
|
|
478
|
+
* façade over {@link withTransactionAsync}: the ONE mechanism — the same acquire → pin → BEGIN →
|
|
479
|
+
* body → COMMIT/ROLLBACK → release + retry — powers both.
|
|
480
|
+
*
|
|
481
|
+
* ## The ambient-tx JOIN — how operations participate (the core #86 fix)
|
|
482
|
+
*
|
|
483
|
+
* `fn` takes NO connection argument. Instead the pinned connection lives in the ALS
|
|
484
|
+
* ({@link currentPinnedAsyncConnection}) + the "inside a transaction" marker
|
|
485
|
+
* ({@link import('./tx-options').runInTransactionScope}). Every operation `fn` issues — a live-DB
|
|
486
|
+
* write via {@link import('./makesql/tx').executeTransactionAsync}, a read via the async read seam —
|
|
487
|
+
* detects that ambient pinned connection and runs its statements on THAT connection **without opening
|
|
488
|
+
* its own BEGIN/COMMIT** (`withTransactionAsync`'s nested-join, :495). So N operations inside one
|
|
489
|
+
* `transaction(fn)` produce exactly ONE BEGIN + ONE COMMIT on ONE connection. Outside a
|
|
490
|
+
* `transaction(fn)` the ambient pin is absent, so a bare write's own `executeTransactionAsync` sees
|
|
491
|
+
* no ambient tx and the write=tx guard fires ({@link WriteOutsideTransactionError}).
|
|
492
|
+
*
|
|
493
|
+
* NESTED `transaction()` joins the outer (one physical BEGIN/COMMIT; an inner error rolls back the
|
|
494
|
+
* WHOLE tx) — again via `withTransactionAsync`'s nested-join. Isolation/retry/rollbackOnly options on
|
|
495
|
+
* a nested call are ignored (the outer owns them). This is the API REFERENCE the native ports
|
|
496
|
+
* (#82-85) mirror.
|
|
497
|
+
*/
|
|
498
|
+
function transaction(ctx, fn, options = {}, dialect = 'postgres', isConnectionError = defaultIsConnectionError, connection) {
|
|
499
|
+
// The boundary is `withTransactionAsync` with a body that ignores the tx ctx — ambient operations
|
|
500
|
+
// resolve the pinned connection through the ALS, not an explicit arg (v1 `txContext.run(func)`).
|
|
501
|
+
// `connection` (Phase C-2) routes the tx to a NAMED connection's writer pool (default when absent).
|
|
502
|
+
return withTransactionAsync(ctx, () => fn(), options, dialect, isConnectionError, connection);
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* **The SYNC transaction boundary (#141)** — the sync twin of {@link withTransactionAsync} for the
|
|
506
|
+
* in-process better-sqlite3 runtime. Pins ONE connection for the whole transaction (BEGIN…COMMIT on
|
|
507
|
+
* the same conn — the per-execution ownership the async path gets from the pool), issues the
|
|
508
|
+
* isolation-aware `BEGIN` ({@link beginStatements}), runs `fn(txCtx)` inside the tx scope marker
|
|
509
|
+
* (`runInTransactionScope` — so a nested write's guard/detection sees an active tx), then COMMITs or
|
|
510
|
+
* ROLLBACKs by the body's signal:
|
|
511
|
+
* - `{ commit: true, value }` → `COMMIT`, return `value`;
|
|
512
|
+
* - `{ commit: false, value }` → `ROLLBACK`, return `value` (a gate short-circuit / rollbackOnly —
|
|
513
|
+
* no committed change, but the body result is still returned, e.g. the short-circuit reason);
|
|
514
|
+
* - a THROW from `fn` → best-effort `ROLLBACK`, re-raise (the caller maps the driver error).
|
|
515
|
+
*
|
|
516
|
+
* Every statement `fn` issues resolves the pinned tx connection via `connectionFor` (the tx-owned
|
|
517
|
+
* conn), so the whole body runs atomically on one connection. This is the boundary the write/tx leaf
|
|
518
|
+
* path runs inside; the tx-control statements (BEGIN/COMMIT/ROLLBACK) go through the seam here, while
|
|
519
|
+
* the body's data statements ride the `executeSQL` transport leaf.
|
|
520
|
+
*/
|
|
521
|
+
function withTransactionSync(outer, fn, dialect = 'sqlite', isolation) {
|
|
522
|
+
const ctx = outer.withConnection(outer.connectionFor({ write: true }), true);
|
|
523
|
+
for (const begin of (0, tx_options_1.beginStatements)(dialect, isolation))
|
|
524
|
+
run(ctx, begin, []);
|
|
525
|
+
try {
|
|
526
|
+
const r = (0, tx_options_1.runInTransactionScope)(() => fn(ctx));
|
|
527
|
+
run(ctx, r.commit ? 'COMMIT' : 'ROLLBACK', []);
|
|
528
|
+
return r.value;
|
|
529
|
+
}
|
|
530
|
+
catch (e) {
|
|
531
|
+
try {
|
|
532
|
+
run(ctx, 'ROLLBACK', []);
|
|
533
|
+
}
|
|
534
|
+
catch {
|
|
535
|
+
/* ROLLBACK best-effort; the caller surfaces the original failure */
|
|
536
|
+
}
|
|
537
|
+
throw e;
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
//# sourceMappingURL=exec-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exec-context.js","sourceRoot":"","sources":["../../src/scp/exec-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* litedbmodel v2 SCP — the SHARED relation-grouping core (#141).
|
|
3
|
+
*
|
|
4
|
+
* The ONE implementation of relation key-identity + dedupe + parent grouping, used by BOTH surfaces
|
|
5
|
+
* so there is a single source of truth (no duplicated grouping logic):
|
|
6
|
+
*
|
|
7
|
+
* - the EAGER graph — the op-independent `pluck` / `group` leaves (`./leaves`), run by bc
|
|
8
|
+
* `bindBehaviors` (N+1-free: `parents → pluck → executeSQL(WHERE fk = ANY(keys)) → group`);
|
|
9
|
+
* - the RUNTIME lazy / declarative path — `./relation` `runRelationOp` / `distributeToParent`, the
|
|
10
|
+
* typed-object lazy prototype getters + declarative `with` (a lazy relation can't be a baked
|
|
11
|
+
* graph — it fires on property access — so it keeps this grouping at runtime, over the SAME core).
|
|
12
|
+
*
|
|
13
|
+
* Nothing here touches SQL or a driver: it is pure in-memory grouping over already-fetched rows.
|
|
14
|
+
*/
|
|
15
|
+
/** The stringified key identity for dedupe/grouping. Single scalar → `String(v)`; tuple → joined. */
|
|
16
|
+
export declare function keyIdentity(values: readonly unknown[]): string;
|
|
17
|
+
/**
|
|
18
|
+
* The deduped, non-null key TUPLES of `rows` over `keyCols` (insertion order preserved —
|
|
19
|
+
* deterministic). A tuple is dropped if ANY of its key columns is null/undefined (no partial keys);
|
|
20
|
+
* deduped on the stringified tuple identity (so `1` and `"1"` collapse exactly as `String(v)`).
|
|
21
|
+
*/
|
|
22
|
+
export declare function dedupeKeyTuples(rows: readonly Record<string, unknown>[], keyCols: readonly string[]): unknown[][];
|
|
23
|
+
/** Group `children` by their `fkCols` tuple identity (a null/undefined key drops the child). */
|
|
24
|
+
export declare function groupByKey(children: readonly Record<string, unknown>[], fkCols: readonly string[]): Map<string, Record<string, unknown>[]>;
|
|
25
|
+
/**
|
|
26
|
+
* Distribute grouped children onto ONE parent per cardinality: `single === false` (hasMany) → the
|
|
27
|
+
* child list (`[]` when none); `single === true` (belongsTo/hasOne) → the single child (or `null`).
|
|
28
|
+
* Keyed by the parent's `pkCols` tuple identity. `null`/`[]` is the declared cardinality's empty
|
|
29
|
+
* representation, not an ad-hoc default.
|
|
30
|
+
*/
|
|
31
|
+
export declare function attachToParent(parent: Record<string, unknown>, pkCols: readonly string[], byKey: Map<string, Record<string, unknown>[]>, single: boolean): Record<string, unknown>[] | Record<string, unknown> | null;
|
|
32
|
+
//# sourceMappingURL=grouping.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grouping.d.ts","sourceRoot":"","sources":["../../src/scp/grouping.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,qGAAqG;AACrG,wBAAgB,WAAW,CAAC,MAAM,EAAE,SAAS,OAAO,EAAE,GAAG,MAAM,CAO9D;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,EAAE,EAAE,CAYjH;AAED,gGAAgG;AAChG,wBAAgB,UAAU,CACxB,QAAQ,EAAE,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC5C,MAAM,EAAE,SAAS,MAAM,EAAE,GACxB,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAWxC;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,EAC7C,MAAM,EAAE,OAAO,GACd,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAK5D"}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"grouping.js","sourceRoot":"","sources":["../../src/scp/grouping.ts"],"names":[],"mappings":"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"}
|