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,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"}
|