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.
Files changed (193) hide show
  1. package/README.md +7 -0
  2. package/dist/DBConditions.d.ts.map +1 -1
  3. package/dist/DBConditions.js +16 -1
  4. package/dist/DBConditions.js.map +1 -1
  5. package/dist/DBHandler.d.ts.map +1 -1
  6. package/dist/DBHandler.js +6 -0
  7. package/dist/DBHandler.js.map +1 -1
  8. package/dist/DBModel.d.ts +84 -2
  9. package/dist/DBModel.d.ts.map +1 -1
  10. package/dist/DBModel.js +312 -29
  11. package/dist/DBModel.js.map +1 -1
  12. package/dist/decorators.d.ts +52 -0
  13. package/dist/decorators.d.ts.map +1 -1
  14. package/dist/decorators.js +127 -24
  15. package/dist/decorators.js.map +1 -1
  16. package/dist/drivers/mysql.d.ts.map +1 -1
  17. package/dist/drivers/mysql.js +12 -2
  18. package/dist/drivers/mysql.js.map +1 -1
  19. package/dist/drivers/postgres.d.ts.map +1 -1
  20. package/dist/drivers/postgres.js +13 -2
  21. package/dist/drivers/postgres.js.map +1 -1
  22. package/dist/drivers/sqlite.d.ts +2 -0
  23. package/dist/drivers/sqlite.d.ts.map +1 -1
  24. package/dist/drivers/sqlite.js +49 -4
  25. package/dist/drivers/sqlite.js.map +1 -1
  26. package/dist/index.cjs +10741 -0
  27. package/dist/index.d.cts +1 -0
  28. package/dist/scp/coltype.d.ts +108 -0
  29. package/dist/scp/coltype.d.ts.map +1 -1
  30. package/dist/scp/coltype.js +293 -6
  31. package/dist/scp/coltype.js.map +1 -1
  32. package/dist/scp/connection-routing.d.ts +312 -0
  33. package/dist/scp/connection-routing.d.ts.map +1 -0
  34. package/dist/scp/connection-routing.js +394 -0
  35. package/dist/scp/connection-routing.js.map +1 -0
  36. package/dist/scp/dbmodel-runtime.d.ts +101 -0
  37. package/dist/scp/dbmodel-runtime.d.ts.map +1 -0
  38. package/dist/scp/dbmodel-runtime.js +174 -0
  39. package/dist/scp/dbmodel-runtime.js.map +1 -0
  40. package/dist/scp/decorator-adapter.d.ts +196 -0
  41. package/dist/scp/decorator-adapter.d.ts.map +1 -0
  42. package/dist/scp/decorator-adapter.js +286 -0
  43. package/dist/scp/decorator-adapter.js.map +1 -0
  44. package/dist/scp/emit/emitter.d.ts +108 -0
  45. package/dist/scp/emit/emitter.d.ts.map +1 -0
  46. package/dist/scp/emit/emitter.js +782 -0
  47. package/dist/scp/emit/emitter.js.map +1 -0
  48. package/dist/scp/emit/endpoint.d.ts +248 -0
  49. package/dist/scp/emit/endpoint.d.ts.map +1 -0
  50. package/dist/scp/emit/endpoint.js +20 -0
  51. package/dist/scp/emit/endpoint.js.map +1 -0
  52. package/dist/scp/emit/index.d.ts +11 -0
  53. package/dist/scp/emit/index.d.ts.map +1 -0
  54. package/dist/scp/emit/index.js +13 -0
  55. package/dist/scp/emit/index.js.map +1 -0
  56. package/dist/scp/errors.d.ts +45 -1
  57. package/dist/scp/errors.d.ts.map +1 -1
  58. package/dist/scp/errors.js +47 -1
  59. package/dist/scp/errors.js.map +1 -1
  60. package/dist/scp/exec-context.d.ts +390 -0
  61. package/dist/scp/exec-context.d.ts.map +1 -0
  62. package/dist/scp/exec-context.js +540 -0
  63. package/dist/scp/exec-context.js.map +1 -0
  64. package/dist/scp/grouping.d.ts +32 -0
  65. package/dist/scp/grouping.d.ts.map +1 -0
  66. package/dist/scp/grouping.js +0 -0
  67. package/dist/scp/grouping.js.map +1 -0
  68. package/dist/scp/index.cjs +6432 -17579
  69. package/dist/scp/index.d.cts +1 -0
  70. package/dist/scp/index.d.mts +1 -0
  71. package/dist/scp/index.d.ts +51 -52
  72. package/dist/scp/index.d.ts.map +1 -1
  73. package/dist/scp/index.js +189 -152
  74. package/dist/scp/index.js.map +1 -1
  75. package/dist/scp/index.mjs +6067 -3659
  76. package/dist/scp/leaf-transport.d.ts +50 -0
  77. package/dist/scp/leaf-transport.d.ts.map +1 -0
  78. package/dist/scp/leaf-transport.js +92 -0
  79. package/dist/scp/leaf-transport.js.map +1 -0
  80. package/dist/scp/leaves.d.ts +174 -0
  81. package/dist/scp/leaves.d.ts.map +1 -0
  82. package/dist/scp/leaves.js +287 -0
  83. package/dist/scp/leaves.js.map +1 -0
  84. package/dist/scp/limit-config.d.ts +118 -0
  85. package/dist/scp/limit-config.d.ts.map +1 -0
  86. package/dist/scp/limit-config.js +126 -0
  87. package/dist/scp/limit-config.js.map +1 -0
  88. package/dist/scp/makesql/compile-relation.d.ts +49 -23
  89. package/dist/scp/makesql/compile-relation.d.ts.map +1 -1
  90. package/dist/scp/makesql/compile-relation.js +101 -76
  91. package/dist/scp/makesql/compile-relation.js.map +1 -1
  92. package/dist/scp/makesql/compile-select.d.ts +45 -10
  93. package/dist/scp/makesql/compile-select.d.ts.map +1 -1
  94. package/dist/scp/makesql/compile-select.js +50 -10
  95. package/dist/scp/makesql/compile-select.js.map +1 -1
  96. package/dist/scp/makesql/handler.d.ts +1 -1
  97. package/dist/scp/makesql/handler.d.ts.map +1 -1
  98. package/dist/scp/makesql/index.d.ts +7 -10
  99. package/dist/scp/makesql/index.d.ts.map +1 -1
  100. package/dist/scp/makesql/index.js +19 -30
  101. package/dist/scp/makesql/index.js.map +1 -1
  102. package/dist/scp/makesql/json-array.d.ts +65 -0
  103. package/dist/scp/makesql/json-array.d.ts.map +1 -1
  104. package/dist/scp/makesql/json-array.js +99 -10
  105. package/dist/scp/makesql/json-array.js.map +1 -1
  106. package/dist/scp/makesql/json-batch.d.ts.map +1 -1
  107. package/dist/scp/makesql/json-batch.js +21 -19
  108. package/dist/scp/makesql/json-batch.js.map +1 -1
  109. package/dist/scp/makesql/makesql.d.ts +1 -1
  110. package/dist/scp/makesql/makesql.d.ts.map +1 -1
  111. package/dist/scp/makesql/mysql-returning.d.ts +93 -0
  112. package/dist/scp/makesql/mysql-returning.d.ts.map +1 -0
  113. package/dist/scp/makesql/mysql-returning.js +215 -0
  114. package/dist/scp/makesql/mysql-returning.js.map +1 -0
  115. package/dist/scp/makesql/outtype.d.ts +75 -34
  116. package/dist/scp/makesql/outtype.d.ts.map +1 -1
  117. package/dist/scp/makesql/outtype.js +91 -118
  118. package/dist/scp/makesql/outtype.js.map +1 -1
  119. package/dist/scp/makesql/pool-executor.d.ts +135 -13
  120. package/dist/scp/makesql/pool-executor.d.ts.map +1 -1
  121. package/dist/scp/makesql/pool-executor.js +229 -21
  122. package/dist/scp/makesql/pool-executor.js.map +1 -1
  123. package/dist/scp/makesql/tx.d.ts +107 -22
  124. package/dist/scp/makesql/tx.d.ts.map +1 -1
  125. package/dist/scp/makesql/tx.js +451 -97
  126. package/dist/scp/makesql/tx.js.map +1 -1
  127. package/dist/scp/makesql/writeouttype.d.ts +7 -8
  128. package/dist/scp/makesql/writeouttype.d.ts.map +1 -1
  129. package/dist/scp/makesql/writeouttype.js +8 -7
  130. package/dist/scp/makesql/writeouttype.js.map +1 -1
  131. package/dist/scp/middleware.d.ts +287 -0
  132. package/dist/scp/middleware.d.ts.map +1 -0
  133. package/dist/scp/middleware.js +358 -0
  134. package/dist/scp/middleware.js.map +1 -0
  135. package/dist/scp/relation.d.ts +80 -9
  136. package/dist/scp/relation.d.ts.map +1 -1
  137. package/dist/scp/relation.js +198 -78
  138. package/dist/scp/relation.js.map +1 -1
  139. package/dist/scp/tx-options.d.ts +136 -0
  140. package/dist/scp/tx-options.d.ts.map +1 -0
  141. package/dist/scp/tx-options.js +184 -0
  142. package/dist/scp/tx-options.js.map +1 -0
  143. package/dist/scp/write-bundle.d.ts +131 -0
  144. package/dist/scp/write-bundle.d.ts.map +1 -0
  145. package/dist/scp/write-bundle.js +160 -0
  146. package/dist/scp/write-bundle.js.map +1 -0
  147. package/dist/types.d.ts +7 -0
  148. package/dist/types.d.ts.map +1 -1
  149. package/dist/types.js.map +1 -1
  150. package/docs/BENCHMARK.md +58 -56
  151. package/docs/benchmark-chart.svg +166 -166
  152. package/docs/design/phase-a-execution-context.md +139 -0
  153. package/package.json +16 -9
  154. package/dist/scp/authoring-sql.d.ts +0 -134
  155. package/dist/scp/authoring-sql.d.ts.map +0 -1
  156. package/dist/scp/authoring-sql.js +0 -191
  157. package/dist/scp/authoring-sql.js.map +0 -1
  158. package/dist/scp/authoring.d.ts +0 -155
  159. package/dist/scp/authoring.d.ts.map +0 -1
  160. package/dist/scp/authoring.js +0 -162
  161. package/dist/scp/authoring.js.map +0 -1
  162. package/dist/scp/catalog.d.ts +0 -94
  163. package/dist/scp/catalog.d.ts.map +0 -1
  164. package/dist/scp/catalog.js +0 -234
  165. package/dist/scp/catalog.js.map +0 -1
  166. package/dist/scp/codegen.d.ts +0 -189
  167. package/dist/scp/codegen.d.ts.map +0 -1
  168. package/dist/scp/codegen.js +0 -421
  169. package/dist/scp/codegen.js.map +0 -1
  170. package/dist/scp/find-filter-guard.d.ts +0 -68
  171. package/dist/scp/find-filter-guard.d.ts.map +0 -1
  172. package/dist/scp/find-filter-guard.js +0 -88
  173. package/dist/scp/find-filter-guard.js.map +0 -1
  174. package/dist/scp/guard.d.ts +0 -43
  175. package/dist/scp/guard.d.ts.map +0 -1
  176. package/dist/scp/guard.js +0 -134
  177. package/dist/scp/guard.js.map +0 -1
  178. package/dist/scp/makesql/authoring-compile.d.ts +0 -90
  179. package/dist/scp/makesql/authoring-compile.d.ts.map +0 -1
  180. package/dist/scp/makesql/authoring-compile.js +0 -494
  181. package/dist/scp/makesql/authoring-compile.js.map +0 -1
  182. package/dist/scp/makesql/ir.d.ts +0 -42
  183. package/dist/scp/makesql/ir.d.ts.map +0 -1
  184. package/dist/scp/makesql/ir.js +0 -66
  185. package/dist/scp/makesql/ir.js.map +0 -1
  186. package/dist/scp/makesql/static-bundle.d.ts +0 -187
  187. package/dist/scp/makesql/static-bundle.d.ts.map +0 -1
  188. package/dist/scp/makesql/static-bundle.js +0 -1113
  189. package/dist/scp/makesql/static-bundle.js.map +0 -1
  190. package/dist/scp/runtime.d.ts +0 -242
  191. package/dist/scp/runtime.d.ts.map +0 -1
  192. package/dist/scp/runtime.js +0 -400
  193. package/dist/scp/runtime.js.map +0 -1
@@ -0,0 +1,394 @@
1
+ "use strict";
2
+ /**
3
+ * litedbmodel v2 SCP — the **connection routing + config contract** (Phase C / #87).
4
+ *
5
+ * This module is the **API REFERENCE** for Phase C — the 4 native ports (rust #88 / go #89 / py #90
6
+ * / php #91) mirror THIS contract exactly (config field names + defaults, the connection-registry
7
+ * shape, `withWriter` semantics, and the `connectionFor(intent)` resolution ORDER). It builds ON the
8
+ * Phase A {@link import('./exec-context').ExecutionContext} seam and the Phase A/B owned-connection
9
+ * transaction runtime; it does NOT re-implement the seam — it supplies the pieces
10
+ * {@link import('./exec-context').PooledAsyncContext.connectionFor} uses to complete its resolution.
11
+ *
12
+ * ## The `connectionFor(intent)` resolution order (design §3, v1 `DBModel.ts:313` parity)
13
+ *
14
+ * A statement's connection is resolved in THIS priority (first match wins):
15
+ * 1. **active tx connection** — inside a transaction, always the tx-owned connection (Phase A).
16
+ * 2. **writer scope / writer-sticky** — inside {@link withWriter}, or within `writerStickyDuration`
17
+ * after a transaction (read-your-writes), a READ goes to the WRITER pool (Phase C — here).
18
+ * 3. **read=reader / write=writer** — otherwise a read goes to the reader pool, a write to the
19
+ * writer pool (reader/writer separation; single-pool config ⇒ reader === writer, Phase C).
20
+ * 4. **named-DB routing** — the target pool is selected by `intent.db` (the connection NAME carried
21
+ * by the bundle/model metadata) against the {@link ConnectionRegistry}; absent ⇒ the DEFAULT
22
+ * connection. Named-DB selection happens FIRST (it picks WHICH connection's reader/writer split
23
+ * steps 2-3 then apply to), so the order above is really: pick the named connection, then within
24
+ * it apply tx-pin / writer-sticky / reader-writer. (Phase C, decorator-free — the routing key is
25
+ * config + bundle/model metadata; decorator wiring is Phase F.)
26
+ *
27
+ * ## Backward-compat (the hard constraint)
28
+ *
29
+ * Single DB, `reader === writer` (one pool), empty config, unnamed connection ⇒ BYTE-IDENTICAL to the
30
+ * Phase A/B single-pool behavior. A {@link ConnectionRegistry} built from ONE pool routes every
31
+ * intent to that ONE pool, and the writer-sticky clock only ever diverts to a pool that is the SAME
32
+ * object — so nothing observable changes. The existing `new PooledAsyncContext(pool)` /
33
+ * `new PooledAsyncContext(pool, middleware)` constructors keep working unchanged.
34
+ */
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.WriterStickyClock = exports.ConnectionRegistryBuilder = exports.ConnectionRegistry = exports.DEFAULT_CONNECTION = void 0;
37
+ exports.resolveConnectionConfig = resolveConnectionConfig;
38
+ exports.sessionStatements = sessionStatements;
39
+ exports.singlePoolPair = singlePoolPair;
40
+ exports.readerWriterPair = readerWriterPair;
41
+ exports.inWriterScope = inWriterScope;
42
+ exports.withWriter = withWriter;
43
+ exports.resolvePool = resolvePool;
44
+ exports.sessionResetStatements = sessionResetStatements;
45
+ exports.configuredPool = configuredPool;
46
+ exports.buildRoutingConfig = buildRoutingConfig;
47
+ /** Apply the C3 defaults (queryTimeout=0, keepAlive=false, minPool=0, maxPool=10). */
48
+ function resolveConnectionConfig(config = {}) {
49
+ return {
50
+ driver: config.driver ?? 'postgres',
51
+ ...(config.host !== undefined ? { host: config.host } : {}),
52
+ ...(config.port !== undefined ? { port: config.port } : {}),
53
+ ...(config.database !== undefined ? { database: config.database } : {}),
54
+ ...(config.user !== undefined ? { user: config.user } : {}),
55
+ ...(config.password !== undefined ? { password: config.password } : {}),
56
+ queryTimeout: config.queryTimeout ?? 0,
57
+ keepAlive: config.keepAlive ?? false,
58
+ keepAliveInitialDelayMillis: config.keepAliveInitialDelayMillis ?? 10000,
59
+ minPool: config.minPool ?? 0,
60
+ maxPool: config.maxPool ?? 10,
61
+ ...(config.searchPath !== undefined ? { searchPath: config.searchPath } : {}),
62
+ ...(config.charset !== undefined ? { charset: config.charset } : {}),
63
+ };
64
+ }
65
+ /**
66
+ * The SESSION statements a connection must run at checkout to honor a {@link ResolvedConnectionConfig}
67
+ * (issued once per acquired connection, in order). This is the per-dialect mapping the port agents
68
+ * mirror; it is pure (no connection contact) so it is testable in isolation:
69
+ *
70
+ * - **statement timeout** (`queryTimeout` > 0): PG `SET statement_timeout = <ms>`; MySQL
71
+ * `SET SESSION max_execution_time = <ms>` (both server-side, ms).
72
+ * - **searchPath**: PG `SET search_path TO <path>`; MySQL has no schema search path ⇒ ignored.
73
+ * - **charset**: MySQL `SET NAMES <charset>`; PG `SET client_encoding TO <charset>`.
74
+ *
75
+ * A key with no value emits nothing (⇒ empty array for an all-default config ⇒ the session is
76
+ * untouched, backward-compatible). sqlite has no server session ⇒ empty.
77
+ */
78
+ function sessionStatements(config) {
79
+ const out = [];
80
+ const dialect = config.driver;
81
+ if (dialect === 'sqlite')
82
+ return out;
83
+ if (config.queryTimeout > 0) {
84
+ out.push(dialect === 'postgres'
85
+ ? `SET statement_timeout = ${config.queryTimeout}`
86
+ : `SET SESSION max_execution_time = ${config.queryTimeout}`);
87
+ }
88
+ if (config.searchPath !== undefined && dialect === 'postgres') {
89
+ out.push(`SET search_path TO ${config.searchPath}`);
90
+ }
91
+ if (config.charset !== undefined) {
92
+ out.push(dialect === 'mysql' ? `SET NAMES ${config.charset}` : `SET client_encoding TO ${config.charset}`);
93
+ }
94
+ return out;
95
+ }
96
+ /** Build a {@link ReaderWriterPools} where reader === writer (single-pool, backward-compat). */
97
+ function singlePoolPair(pool) {
98
+ return { reader: pool, writer: pool };
99
+ }
100
+ /** Build a {@link ReaderWriterPools} from a distinct reader + writer pool (reader/writer separation). */
101
+ function readerWriterPair(reader, writer) {
102
+ return { reader, writer };
103
+ }
104
+ // ── The connection registry (C2) — name → reader/writer pools ──────────────────
105
+ /** The reserved name of the DEFAULT (unnamed) connection. An `intent.db` of `undefined` uses this. */
106
+ exports.DEFAULT_CONNECTION = 'default';
107
+ /**
108
+ * The multi-DB connection registry (C2): a map from a connection NAME → its {@link ReaderWriterPools}.
109
+ * `connectionFor(intent)` selects the pair by `intent.db` (the connection name the bundle/model
110
+ * metadata carries — decorator-free, from config + metadata; decorator wiring is Phase F), falling
111
+ * back to {@link DEFAULT_CONNECTION} when unnamed. Selecting a name that was never registered is a
112
+ * LOUD error (a real wiring bug — never a silent default fallback, which would run a query on the
113
+ * wrong DB; mirrors the V0 cross-DB relation registry's loud-fail policy).
114
+ *
115
+ * A single-DB deployment registers exactly one connection under {@link DEFAULT_CONNECTION} with
116
+ * `reader === writer` ⇒ every intent routes to that one pool ⇒ byte-identical to Phase A/B.
117
+ */
118
+ class ConnectionRegistry {
119
+ /** name → reader/writer pools. */
120
+ connections;
121
+ constructor(connections) {
122
+ this.connections = new Map(connections);
123
+ }
124
+ /**
125
+ * Build a registry from ONE pool as the default connection (reader === writer). This is the
126
+ * backward-compat path: {@link import('./exec-context').PooledAsyncContext} built from a single
127
+ * pool wraps it here so its `connectionFor` routes every intent to that one pool.
128
+ */
129
+ static singleDefault(pool) {
130
+ return new ConnectionRegistry(new Map([[exports.DEFAULT_CONNECTION, singlePoolPair(pool)]]));
131
+ }
132
+ /** Fluent builder: start from a default connection's pools, then `.add(name, pools)` more. */
133
+ static fromDefault(pools) {
134
+ return new ConnectionRegistryBuilder().add(exports.DEFAULT_CONNECTION, pools);
135
+ }
136
+ /** The reader/writer pair for `name` (or {@link DEFAULT_CONNECTION} when `undefined`). Loud on a missing name. */
137
+ pairFor(name) {
138
+ const key = name ?? exports.DEFAULT_CONNECTION;
139
+ const pair = this.connections.get(key);
140
+ if (pair === undefined) {
141
+ const known = [...this.connections.keys()].map((k) => `'${k}'`).join(', ');
142
+ throw new Error(`scp connection routing: no connection registered under name '${key}' ` +
143
+ `(known: ${known || '<none>'}). Register it via setConfig/ConnectionRegistry, or drop the ` +
144
+ `connection tag on the bundle/model.`);
145
+ }
146
+ return pair;
147
+ }
148
+ /** The registered connection names (for diagnostics / `closeAllPools`). */
149
+ names() {
150
+ return [...this.connections.keys()];
151
+ }
152
+ /** Every DISTINCT pool object across all connections (a shared reader===writer counts once). */
153
+ distinctPools() {
154
+ const seen = new Set();
155
+ for (const pair of this.connections.values()) {
156
+ seen.add(pair.reader);
157
+ seen.add(pair.writer);
158
+ }
159
+ return [...seen];
160
+ }
161
+ }
162
+ exports.ConnectionRegistry = ConnectionRegistry;
163
+ /** Incremental {@link ConnectionRegistry} builder (name → pools). */
164
+ class ConnectionRegistryBuilder {
165
+ connections = new Map();
166
+ /** Register `name` → its reader/writer pools (chainable). Re-adding a name overwrites it. */
167
+ add(name, pools) {
168
+ this.connections.set(name, pools);
169
+ return this;
170
+ }
171
+ /** Finalize into an immutable {@link ConnectionRegistry}. */
172
+ build() {
173
+ if (this.connections.size === 0) {
174
+ throw new Error('scp connection routing: ConnectionRegistry must have at least the default connection');
175
+ }
176
+ return new ConnectionRegistry(this.connections);
177
+ }
178
+ }
179
+ exports.ConnectionRegistryBuilder = ConnectionRegistryBuilder;
180
+ // ── Writer-sticky + withWriter (C1) ────────────────────────────────────────────
181
+ const node_async_hooks_1 = require("node:async_hooks");
182
+ /** Ambient "route reads to the writer" marker (mirror v1 `withWriter` writer context). */
183
+ const writerScopeStore = new node_async_hooks_1.AsyncLocalStorage();
184
+ /** True if the current async scope is inside a {@link withWriter} scope. */
185
+ function inWriterScope() {
186
+ return writerScopeStore.getStore() === true;
187
+ }
188
+ /**
189
+ * Run `fn` with reads pinned to the WRITER pool (mirror v1 `DBModel.withWriter`): every read `fn`
190
+ * issues resolves the writer pool (read-your-writes without replication lag), and — because this ALSO
191
+ * enters a {@link import('./tx-options').withReadOnly} scope — ANY write inside `fn` throws
192
+ * {@link import('../types').WriteInReadOnlyContextError}. Nested `withWriter` is idempotent (already
193
+ * in a writer scope ⇒ just run `fn`). Inside a transaction the tx-owned connection already wins in
194
+ * `connectionFor`, so a `withWriter` there is a no-op on routing (matches v1 :2941).
195
+ *
196
+ * @param fn the read-your-writes scope body.
197
+ */
198
+ function withWriter(fn) {
199
+ if (inWriterScope())
200
+ return fn();
201
+ // Enter BOTH the writer-routing marker and the read-only (write-reject) marker — v1's single
202
+ // writerContext is both. `withReadOnly` (tx-options) owns the write-reject half.
203
+ return writerScopeStore.run(true, () => withReadOnlyAsync(fn));
204
+ }
205
+ /**
206
+ * A writer-sticky CLOCK (C1, read-your-writes; v1 `_shouldUseWriterSticky` :344 + `_lastTransactionTime`).
207
+ * After a transaction (or a bare write) COMMITs, reads within `stickyDurationMs` route to the WRITER
208
+ * pool so a just-committed row is visible despite reader-replica lag. The ctx owns ONE clock instance;
209
+ * the tx runtime `.mark()`s it on every successful write/commit; `connectionFor` reads `.isSticky()`.
210
+ *
211
+ * `useWriterAfterTransaction=false` disables it entirely (`.isSticky()` always false). A single-pool
212
+ * deployment (reader === writer) is unaffected by stickiness — the diverted pool is the same object.
213
+ */
214
+ class WriterStickyClock {
215
+ lastWriteAt = 0;
216
+ enabled;
217
+ stickyDurationMs;
218
+ /** Injectable clock (tests advance it); defaults to `Date.now`. */
219
+ now;
220
+ constructor(opts = {}) {
221
+ this.enabled = opts.useWriterAfterTransaction ?? true;
222
+ this.stickyDurationMs = opts.writerStickyDuration ?? 5000;
223
+ this.now = opts.now ?? Date.now;
224
+ }
225
+ /** Record that a write/commit just happened (the tx runtime calls this on success). */
226
+ mark() {
227
+ if (this.enabled)
228
+ this.lastWriteAt = this.now();
229
+ }
230
+ /** Is a read currently sticky-to-writer (within `writerStickyDuration` of the last write)? */
231
+ isSticky() {
232
+ if (!this.enabled || this.lastWriteAt === 0)
233
+ return false;
234
+ return this.now() - this.lastWriteAt < this.stickyDurationMs;
235
+ }
236
+ /** Reset the clock (e.g. between tests / on `closeAllPools`). */
237
+ reset() {
238
+ this.lastWriteAt = 0;
239
+ }
240
+ }
241
+ exports.WriterStickyClock = WriterStickyClock;
242
+ // ── The core resolution (steps 2-4) — the ONE routing function the ports mirror ─
243
+ /**
244
+ * Resolve WHICH pool serves a statement given its {@link import('./exec-context').StatementIntent}
245
+ * and the routing config — the completion of `connectionFor`'s steps 2-4 (step 1, the tx-pin, is
246
+ * handled by the ctx BEFORE calling this, since only the ctx holds the ALS pin). The order:
247
+ *
248
+ * 1. **named-DB** (`intent.db`) selects the {@link ReaderWriterPools} pair (loud on unknown name).
249
+ * 2. within that pair: a WRITE ⇒ the writer pool.
250
+ * 3. a READ in a {@link withWriter} scope OR within writer-sticky ⇒ the writer pool (read-your-writes).
251
+ * 4. otherwise a READ ⇒ the reader pool.
252
+ *
253
+ * Single-pool (reader === writer) ⇒ every branch returns the same pool (backward-compat).
254
+ */
255
+ function resolvePool(intent, routing) {
256
+ const pair = routing.registry.pairFor(intent.db);
257
+ if (intent.write)
258
+ return pair.writer; // writes always to the writer
259
+ if (inWriterScope() || routing.sticky.isSticky())
260
+ return pair.writer; // read-your-writes
261
+ return pair.reader; // plain read → reader
262
+ }
263
+ // ── Session-config pool wrapper (C3) — apply queryTimeout/searchPath/charset ───
264
+ /**
265
+ * The RESET statements that undo {@link sessionStatements} on release (per dialect), so a session
266
+ * knob (`statement_timeout` / `search_path` / `client_encoding` / `max_execution_time` / charset)
267
+ * set for THIS configured connection does NOT leak to the next caller that draws the SAME underlying
268
+ * pooled connection — pg/mysql2 do NOT auto-reset session state on release. `RESET`/`SET … DEFAULT`
269
+ * restores the server default. Only the knobs `config` actually set are reset (an all-default config
270
+ * ⇒ nothing to reset).
271
+ */
272
+ function sessionResetStatements(config) {
273
+ const out = [];
274
+ const dialect = config.driver;
275
+ if (dialect === 'sqlite')
276
+ return out;
277
+ if (config.queryTimeout > 0) {
278
+ out.push(dialect === 'postgres' ? 'RESET statement_timeout' : 'SET SESSION max_execution_time = DEFAULT');
279
+ }
280
+ if (config.searchPath !== undefined && dialect === 'postgres')
281
+ out.push('RESET search_path');
282
+ if (config.charset !== undefined)
283
+ out.push(dialect === 'mysql' ? 'SET NAMES DEFAULT' : 'RESET client_encoding');
284
+ return out;
285
+ }
286
+ /**
287
+ * Wrap an {@link AsyncConnectionPool} so every acquired connection first runs the
288
+ * {@link sessionStatements} for `config` (statement timeout / search_path / charset) and, on release,
289
+ * runs {@link sessionResetStatements} to restore the server defaults (so a pooled connection never
290
+ * leaks THIS config's session state to the next caller — pg/mysql2 don't auto-reset on release). A
291
+ * config with no session knobs (all defaults) ⇒ ZERO extra statements ⇒ the wrapper is a transparent
292
+ * passthrough (backward-compat). This is where C3's `queryTimeout`/`searchPath`/`charset` become REAL
293
+ * per-server effects — the acquired connection carries them for the statement(s) that run on it.
294
+ *
295
+ * If a session statement itself fails (e.g. an invalid search_path), the connection is released back
296
+ * as DESTROYED so a mis-configured connection never re-enters the pool. On release, if a statement
297
+ * ERRORED (the connection is possibly aborted — e.g. a fired statement timeout), the reset is SKIPPED
298
+ * and the connection is DESTROYED (a poisoned connection must not re-enter the pool anyway).
299
+ */
300
+ function configuredPool(pool, config) {
301
+ const session = sessionStatements(config);
302
+ if (session.length === 0)
303
+ return pool; // no knobs set ⇒ transparent passthrough (byte-identical)
304
+ const reset = sessionResetStatements(config);
305
+ return {
306
+ async acquire() {
307
+ const conn = await pool.acquire();
308
+ try {
309
+ for (const stmt of session)
310
+ await conn.run(stmt, []);
311
+ }
312
+ catch (e) {
313
+ await pool.release(conn, true); // a failed session setup poisons the connection — drop it
314
+ throw e;
315
+ }
316
+ return conn;
317
+ },
318
+ async release(conn, destroy) {
319
+ // A destroyed (poisoned/aborted) connection is dropped — no point resetting it, and a reset on
320
+ // an aborted-by-timeout connection would itself fail. A CLEAN connection is reset to defaults.
321
+ if (destroy === true) {
322
+ await pool.release(conn, true);
323
+ return;
324
+ }
325
+ try {
326
+ for (const stmt of reset)
327
+ await conn.run(stmt, []);
328
+ }
329
+ catch {
330
+ await pool.release(conn, true); // reset failed ⇒ connection state unknown ⇒ drop it
331
+ return;
332
+ }
333
+ await pool.release(conn, false);
334
+ },
335
+ };
336
+ }
337
+ /**
338
+ * The C3 `setConfig` result: the {@link RoutingConfig} a {@link import('./exec-context').PooledAsyncContext}
339
+ * runs on, plus a `close()` that shuts every constructed pool ({@link closeAllPools}). Build it from
340
+ * one or more {@link ConnectionSetup}s (the one named `default`, or the first unnamed, is the default
341
+ * connection).
342
+ *
343
+ * For each setup: resolve the config, CALL its {@link PoolFactory} to construct the pool(s) — so
344
+ * `minPool`/`maxPool`/`keepAlive`/`keepAliveInitialDelayMillis` are applied at `new Pool()` /
345
+ * `createPool()` time (the config is the SOLE source of the cap) — then wrap each pool with
346
+ * {@link configuredPool} so the SESSION knobs (queryTimeout/searchPath/charset) apply on checkout.
347
+ */
348
+ function buildRoutingConfig(setups, stickyOpts = {}) {
349
+ if (setups.length === 0)
350
+ throw new Error('scp setConfig: at least one connection setup is required');
351
+ const builder = new ConnectionRegistryBuilder();
352
+ const closers = [];
353
+ for (const s of setups) {
354
+ const resolved = resolveConnectionConfig(s.config);
355
+ // CONSTRUCT the reader pool from the resolved config (sizing/keepAlive land at `new Pool()`).
356
+ const readerBuilt = s.poolFactory(resolved, 'reader');
357
+ closers.push(readerBuilt.close);
358
+ const reader = configuredPool(readerBuilt.pool, resolved);
359
+ let pair;
360
+ if (s.separateWriter === true) {
361
+ const writerBuilt = s.poolFactory(resolved, 'writer');
362
+ closers.push(writerBuilt.close);
363
+ pair = readerWriterPair(reader, configuredPool(writerBuilt.pool, resolved));
364
+ }
365
+ else {
366
+ pair = singlePoolPair(reader); // reader === writer (one constructed pool)
367
+ }
368
+ builder.add(s.name ?? exports.DEFAULT_CONNECTION, pair);
369
+ }
370
+ const routing = { registry: builder.build(), sticky: new WriterStickyClock(stickyOpts) };
371
+ return { routing, close: () => closeRouting(closers) };
372
+ }
373
+ /** Close every DISTINCT pool closer (deduped by identity), tolerating individual failures. */
374
+ async function closeRouting(closers) {
375
+ const seen = new Set();
376
+ for (const c of closers) {
377
+ if (seen.has(c))
378
+ continue;
379
+ seen.add(c);
380
+ await c().catch(() => undefined);
381
+ }
382
+ }
383
+ // ── read-only async scope (bridge to tx-options withReadOnly for the async body) ─
384
+ const tx_options_1 = require("./tx-options");
385
+ /**
386
+ * Run an ASYNC `fn` inside the tx-options read-only scope. `withReadOnly` is generic over its body's
387
+ * return, so an async body's Promise is carried through the ALS run correctly (the marker stays set
388
+ * across the awaits inside `fn` because ALS spans the async chain). Kept here so {@link withWriter}
389
+ * composes the write-reject half without exec-context needing to know about it.
390
+ */
391
+ function withReadOnlyAsync(fn) {
392
+ return (0, tx_options_1.withReadOnly)(fn);
393
+ }
394
+ //# sourceMappingURL=connection-routing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connection-routing.js","sourceRoot":"","sources":["../../src/scp/connection-routing.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * litedbmodel v2 SCP — the DBModel ActiveRecord ↔ SCP RUNTIME bridge (Phase F-2, epic #74, issue #105).
3
+ *
4
+ * The runtime glue that puts `DBModel`'s pooled execution on the Phase A-E substrate
5
+ * (`exec-context.ts` / `connection-routing.ts` / `middleware.ts` / `tx-options.ts`). It owns two
6
+ * concerns:
7
+ *
8
+ * 1. **Connection wiring** ({@link buildContextFromConfig}) — a v1 `DBConfig` (+ `writerConfig` /
9
+ * writer-sticky options) → a {@link PooledAsyncContext} over REAL pg / mysql2 pools (via
10
+ * `pgPoolFactory` / `mysqlPoolFactory` + `buildRoutingConfig`) and a `close()`. This is the C3
11
+ * `setConfig → ConnectionRegistry → pool` path. Every statement DBModel issues — a v1-built SELECT
12
+ * as much as a compiled write bundle — goes through this ONE ctx, so routing, middleware and an
13
+ * ambient transaction pin apply uniformly.
14
+ *
15
+ * 2. **Write execution** ({@link executeWriteAsync}) — run a compiled write bundle's transaction plan
16
+ * on the writer through the SCP tx runtime (write=tx guard).
17
+ *
18
+ * There is NO runtime read-compile here. A read whose SHAPE is fixed is declared on the `@behavior` /
19
+ * `@leaf` authoring surface and lowered by `bc generate`; a read whose shape is only known per request
20
+ * (`find({where:{age:{gt:x}}})`) runs the v1 imperative builder — compile-free — and reaches the DB
21
+ * through the SAME ctx seam via `DBModel.execute`.
22
+ *
23
+ * The public API + README code are UNCHANGED: `DBModel`'s method signatures / return shapes are
24
+ * identical; this module is an INTERNAL execution substrate.
25
+ */
26
+ import 'reflect-metadata';
27
+ import { type PoolCloser } from './connection-routing';
28
+ import { PooledAsyncContext } from './exec-context';
29
+ import type { SqlBundle } from './write-bundle';
30
+ import { type TransactionResult } from './makesql/tx';
31
+ import type { TransactionOptions } from './tx-options';
32
+ import { compileCreateBundle, compileUpdateBundle, compileDeleteBundle, type ModelClassLike, type DeriveColumnsOptions } from './decorator-adapter';
33
+ import type { Scope } from 'behavior-contracts/runtime';
34
+ import type { DialectName } from './dialect';
35
+ /** A v1 `DBConfig`-shaped input (host/port/database/user/password + pool sizing + keepAlive). */
36
+ export interface RuntimeDbConfig {
37
+ readonly host?: string;
38
+ readonly port?: number;
39
+ readonly database?: string;
40
+ readonly user?: string;
41
+ readonly password?: string;
42
+ readonly max?: number;
43
+ readonly keepAlive?: boolean;
44
+ readonly keepAliveInitialDelayMillis?: number;
45
+ readonly driver?: 'postgres' | 'mysql' | 'sqlite';
46
+ }
47
+ /** Options for {@link buildContextFromConfig} — reader/writer split + writer-sticky (C3 / v1 parity). */
48
+ export interface RuntimeContextOptions {
49
+ /** A distinct WRITER connection config (reader/writer replica split). Absent ⇒ reader === writer. */
50
+ readonly writerConfig?: RuntimeDbConfig;
51
+ /** Keep routing to the writer for `writerStickyDuration` after a committed tx (read-your-writes). */
52
+ readonly useWriterAfterTransaction?: boolean;
53
+ /** The writer-sticky window in ms (default 5000). */
54
+ readonly writerStickyDuration?: number;
55
+ }
56
+ /** The assembled runtime context: the routed ctx, the dialect, and a closer. */
57
+ export interface RuntimeContext {
58
+ /**
59
+ * The Phase A-E execution context (routing + middleware + tx-pinning) over real pools. This IS the
60
+ * async read/write seam every statement funnels through — a v1-built SELECT via `DBModel.execute`,
61
+ * a compiled write bundle via {@link executeWriteAsync}, and a generated read module via
62
+ * `bindAsync(leafHandlersAsync({ execAsync: ctx, dialect }))`.
63
+ */
64
+ readonly ctx: PooledAsyncContext;
65
+ /** The compiled SQL dialect (`postgres` / `mysql`). */
66
+ readonly dialect: DialectName;
67
+ /** Shut every constructed pool (v1 `closeAllPools`). */
68
+ readonly close: PoolCloser;
69
+ }
70
+ /**
71
+ * Build a Phase A-E {@link PooledAsyncContext} + reader executor from a v1 `DBConfig` (the
72
+ * `setConfig → ConnectionRegistry → pool` wiring). Constructs a REAL pg / mysql2 pool via
73
+ * `buildRoutingConfig` + the driver pool factory (sizing/keepAlive applied at pool construction), wires
74
+ * the default connection, and — when `writerConfig` is present — a distinct writer pool (reader/writer
75
+ * replica split). The writer-sticky clock is armed per `useWriterAfterTransaction` / `writerStickyDuration`.
76
+ *
77
+ * `sqlite` is NOT routed here (it keeps the v1 in-proc path). Throws for an unsupported driver.
78
+ */
79
+ export declare function buildContextFromConfig(config: RuntimeDbConfig, options?: RuntimeContextOptions): RuntimeContext;
80
+ /** The current SCP-runtime generation; a cached runtime built at an older generation is stale (#182). */
81
+ export declare function scpRuntimeGeneration(): number;
82
+ /**
83
+ * Close every pool set {@link buildContextFromConfig} has constructed — including runtimes their owner
84
+ * discarded without closing. Part of the v1 `closeAllPools()` teardown, alongside the per-driver caches.
85
+ * Bumps {@link scpRuntimeGeneration} so `DBModel`'s cached runtimes rebuild instead of reusing a now-closed
86
+ * pool (#182).
87
+ */
88
+ export declare function closeAllScpRuntimes(): Promise<void>;
89
+ /** Run a write bundle's transaction plan on the writer through the SCP tx runtime (write=tx guard). */
90
+ export declare function executeWriteAsync(bundle: SqlBundle, input: Scope, ctx: RuntimeContext, options?: TransactionOptions): Promise<TransactionResult>;
91
+ /**
92
+ * Render a v1-built raw SQL string (with `?` placeholders) to the ctx dialect's placeholder form for
93
+ * the escape-hatch seam path: `postgres` renumbers `?`→`$N` (string-literal-aware, via the SAME
94
+ * `renderPlaceholders` the makesql render uses); `mysql` keeps `?`. v1's DBHandler did this `?`→`$N`
95
+ * conversion on the imperative path — the SCP seam passes SQL verbatim to the driver, so the raw path
96
+ * must renumber here to stay byte-equivalent. Phase F-2 (#105).
97
+ */
98
+ export declare function renderRawSql(sql: string, dialect: DialectName): string;
99
+ export { compileCreateBundle, compileUpdateBundle, compileDeleteBundle };
100
+ export type { ModelClassLike, DeriveColumnsOptions };
101
+ //# sourceMappingURL=dbmodel-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dbmodel-runtime.d.ts","sourceRoot":"","sources":["../../src/scp/dbmodel-runtime.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAEL,KAAK,UAAU,EAIhB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAKpD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAA2B,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAC/E,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AACvD,OAAO,EACL,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,KAAK,cAAc,EACnB,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAC;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAI7C,iGAAiG;AACjG,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,QAAQ,CAAC,2BAA2B,CAAC,EAAE,MAAM,CAAC;IAC9C,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,OAAO,GAAG,QAAQ,CAAC;CACnD;AAED,yGAAyG;AACzG,MAAM,WAAW,qBAAqB;IACpC,qGAAqG;IACrG,QAAQ,CAAC,YAAY,CAAC,EAAE,eAAe,CAAC;IACxC,qGAAqG;IACrG,QAAQ,CAAC,yBAAyB,CAAC,EAAE,OAAO,CAAC;IAC7C,qDAAqD;IACrD,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACxC;AAED,gFAAgF;AAChF,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,kBAAkB,CAAC;IACjC,uDAAuD;IACvD,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC;IAC9B,wDAAwD;IACxD,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;CAC5B;AA0CD;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,GAAE,qBAA0B,GAAG,cAAc,CAsCnH;AAqBD,yGAAyG;AACzG,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,IAAI,CAAC,CAKzD;AAID,uGAAuG;AACvG,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,SAAS,EACjB,KAAK,EAAE,KAAK,EACZ,GAAG,EAAE,cAAc,EACnB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC,CAK5B;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,MAAM,CAEtE;AAGD,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,CAAC;AACzE,YAAY,EAAE,cAAc,EAAE,oBAAoB,EAAE,CAAC"}