litedbmodel 2.0.0 → 2.0.1
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/dist/scp/authoring-sql.d.ts +78 -0
- package/dist/scp/authoring-sql.d.ts.map +1 -1
- package/dist/scp/authoring-sql.js +110 -2
- package/dist/scp/authoring-sql.js.map +1 -1
- package/dist/scp/catalog.d.ts +1 -1
- package/dist/scp/catalog.d.ts.map +1 -1
- package/dist/scp/catalog.js +27 -0
- package/dist/scp/catalog.js.map +1 -1
- package/dist/scp/codegen.d.ts +46 -82
- package/dist/scp/codegen.d.ts.map +1 -1
- package/dist/scp/codegen.js +37 -80
- package/dist/scp/codegen.js.map +1 -1
- package/dist/scp/find-filter-guard.d.ts +68 -0
- package/dist/scp/find-filter-guard.d.ts.map +1 -0
- package/dist/scp/find-filter-guard.js +88 -0
- package/dist/scp/find-filter-guard.js.map +1 -0
- package/dist/scp/fragment/assemble.d.ts +61 -0
- package/dist/scp/fragment/assemble.d.ts.map +1 -0
- package/dist/scp/fragment/assemble.js +148 -0
- package/dist/scp/fragment/assemble.js.map +1 -0
- package/dist/scp/fragment/compile-pg.d.ts +107 -0
- package/dist/scp/fragment/compile-pg.d.ts.map +1 -0
- package/dist/scp/fragment/compile-pg.js +142 -0
- package/dist/scp/fragment/compile-pg.js.map +1 -0
- package/dist/scp/fragment/index.d.ts +16 -0
- package/dist/scp/fragment/index.d.ts.map +1 -0
- package/dist/scp/fragment/index.js +34 -0
- package/dist/scp/fragment/index.js.map +1 -0
- package/dist/scp/fragment/model.d.ts +119 -0
- package/dist/scp/fragment/model.d.ts.map +1 -0
- package/dist/scp/fragment/model.js +66 -0
- package/dist/scp/fragment/model.js.map +1 -0
- package/dist/scp/fragment/pg-array-type.d.ts +13 -0
- package/dist/scp/fragment/pg-array-type.d.ts.map +1 -0
- package/dist/scp/fragment/pg-array-type.js +34 -0
- package/dist/scp/fragment/pg-array-type.js.map +1 -0
- package/dist/scp/guard.d.ts +2 -6
- package/dist/scp/guard.d.ts.map +1 -1
- package/dist/scp/guard.js +19 -22
- package/dist/scp/guard.js.map +1 -1
- package/dist/scp/index.cjs +11284 -3826
- package/dist/scp/index.d.ts +38 -23
- package/dist/scp/index.d.ts.map +1 -1
- package/dist/scp/index.js +119 -45
- package/dist/scp/index.js.map +1 -1
- package/dist/scp/index.mjs +4231 -1412
- package/dist/scp/makesql/authoring-compile.d.ts +90 -0
- package/dist/scp/makesql/authoring-compile.d.ts.map +1 -0
- package/dist/scp/makesql/authoring-compile.js +494 -0
- package/dist/scp/makesql/authoring-compile.js.map +1 -0
- package/dist/scp/makesql/compile-crud.d.ts +133 -0
- package/dist/scp/makesql/compile-crud.d.ts.map +1 -0
- package/dist/scp/makesql/compile-crud.js +288 -0
- package/dist/scp/makesql/compile-crud.js.map +1 -0
- package/dist/scp/makesql/compile-relation.d.ts +157 -0
- package/dist/scp/makesql/compile-relation.d.ts.map +1 -0
- package/dist/scp/makesql/compile-relation.js +425 -0
- package/dist/scp/makesql/compile-relation.js.map +1 -0
- package/dist/scp/makesql/compile-select.d.ts +45 -0
- package/dist/scp/makesql/compile-select.d.ts.map +1 -0
- package/dist/scp/makesql/compile-select.js +58 -0
- package/dist/scp/makesql/compile-select.js.map +1 -0
- package/dist/scp/makesql/compile.d.ts +63 -0
- package/dist/scp/makesql/compile.d.ts.map +1 -0
- package/dist/scp/makesql/compile.js +99 -0
- package/dist/scp/makesql/compile.js.map +1 -0
- package/dist/scp/makesql/handler.d.ts +58 -0
- package/dist/scp/makesql/handler.d.ts.map +1 -0
- package/dist/scp/makesql/handler.js +113 -0
- package/dist/scp/makesql/handler.js.map +1 -0
- package/dist/scp/makesql/index.d.ts +33 -0
- package/dist/scp/makesql/index.d.ts.map +1 -0
- package/dist/scp/makesql/index.js +114 -0
- package/dist/scp/makesql/index.js.map +1 -0
- package/dist/scp/makesql/ir.d.ts +42 -0
- package/dist/scp/makesql/ir.d.ts.map +1 -0
- package/dist/scp/makesql/ir.js +66 -0
- package/dist/scp/makesql/ir.js.map +1 -0
- package/dist/scp/makesql/json-array.d.ts +84 -0
- package/dist/scp/makesql/json-array.d.ts.map +1 -0
- package/dist/scp/makesql/json-array.js +118 -0
- package/dist/scp/makesql/json-array.js.map +1 -0
- package/dist/scp/makesql/json-batch.d.ts +102 -0
- package/dist/scp/makesql/json-batch.d.ts.map +1 -0
- package/dist/scp/makesql/json-batch.js +269 -0
- package/dist/scp/makesql/json-batch.js.map +1 -0
- package/dist/scp/makesql/makesql.d.ts +99 -0
- package/dist/scp/makesql/makesql.d.ts.map +1 -0
- package/dist/scp/makesql/makesql.js +122 -0
- package/dist/scp/makesql/makesql.js.map +1 -0
- package/dist/scp/makesql/pool-executor.d.ts +47 -0
- package/dist/scp/makesql/pool-executor.d.ts.map +1 -0
- package/dist/scp/makesql/pool-executor.js +50 -0
- package/dist/scp/makesql/pool-executor.js.map +1 -0
- package/dist/scp/makesql/static-bundle.d.ts +185 -0
- package/dist/scp/makesql/static-bundle.d.ts.map +1 -0
- package/dist/scp/makesql/static-bundle.js +1024 -0
- package/dist/scp/makesql/static-bundle.js.map +1 -0
- package/dist/scp/makesql/tx.d.ts +216 -0
- package/dist/scp/makesql/tx.d.ts.map +1 -0
- package/dist/scp/makesql/tx.js +743 -0
- package/dist/scp/makesql/tx.js.map +1 -0
- package/dist/scp/relation.d.ts +86 -74
- package/dist/scp/relation.d.ts.map +1 -1
- package/dist/scp/relation.js +186 -120
- package/dist/scp/relation.js.map +1 -1
- package/dist/scp/runtime.d.ts +146 -210
- package/dist/scp/runtime.d.ts.map +1 -1
- package/dist/scp/runtime.js +228 -512
- package/dist/scp/runtime.js.map +1 -1
- package/dist/scp/typed-object.d.ts +12 -1
- package/dist/scp/typed-object.d.ts.map +1 -1
- package/dist/scp/typed-object.js +25 -4
- package/dist/scp/typed-object.js.map +1 -1
- package/docs/proposal/v0-coverage-matrix.md +157 -0
- package/docs/proposal/v2-completeness-plan.md +90 -0
- package/docs/proposal/v2-sql-parity-checklist.md +162 -0
- package/package.json +3 -3
- package/dist/scp/bridge.d.ts +0 -64
- package/dist/scp/bridge.d.ts.map +0 -1
- package/dist/scp/bridge.js +0 -329
- package/dist/scp/bridge.js.map +0 -1
- package/dist/scp/compile-dialect.d.ts +0 -39
- package/dist/scp/compile-dialect.d.ts.map +0 -1
- package/dist/scp/compile-dialect.js +0 -62
- package/dist/scp/compile-dialect.js.map +0 -1
- package/dist/scp/compile-sqlite.d.ts +0 -114
- package/dist/scp/compile-sqlite.d.ts.map +0 -1
- package/dist/scp/compile-sqlite.js +0 -149
- package/dist/scp/compile-sqlite.js.map +0 -1
- package/dist/scp/ir.d.ts +0 -92
- package/dist/scp/ir.d.ts.map +0 -1
- package/dist/scp/ir.js +0 -28
- package/dist/scp/ir.js.map +0 -1
- package/dist/scp/render.d.ts +0 -40
- package/dist/scp/render.d.ts.map +0 -1
- package/dist/scp/render.js +0 -170
- package/dist/scp/render.js.map +0 -1
- package/dist/scp/write-plan.d.ts +0 -161
- package/dist/scp/write-plan.d.ts.map +0 -1
- package/dist/scp/write-plan.js +0 -528
- package/dist/scp/write-plan.js.map +0 -1
- package/dist/scp/write-runtime.d.ts +0 -80
- package/dist/scp/write-runtime.d.ts.map +0 -1
- package/dist/scp/write-runtime.js +0 -167
- package/dist/scp/write-runtime.js.map +0 -1
package/dist/scp/codegen.js
CHANGED
|
@@ -1,44 +1,26 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* litedbmodel v2 SCP — mode-3 codegen (WS7f, #35; spec §9 exec-mode 3 / §10 / §11
|
|
3
|
+
* litedbmodel v2 SCP — mode-3 codegen (WS7f, #35; spec §9 exec-mode 3 / §10 / §11; makeSQL flip,
|
|
4
|
+
* epic #43/#45 Phase B).
|
|
4
5
|
*
|
|
5
|
-
* The static-codegen execution mode ("Codegen・静的(全言語)— IR → 各言語ソース生成
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* language, emits:
|
|
6
|
+
* The static-codegen execution mode ("Codegen・静的(全言語)— IR → 各言語ソース生成 runtime≈0,
|
|
7
|
+
* 入力は可搬IRのみ, bc#13 共有 generator に SQL catalog を供給" — spec §9). It takes a §8 STATIC
|
|
8
|
+
* makeSQL {@link SqlBundle} and, per target language, emits:
|
|
9
9
|
*
|
|
10
|
-
* 1. **Behavior module** — bc's SHARED generator (`generateModule`, bc#13) run over the
|
|
11
|
-
* bundle's
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* `optionalHeads` / `relations` / `transaction`). This is the "SQL catalog supplied to the
|
|
18
|
-
* shared generator" (§9): a codegen consumer's thin SQL-handler layer reads it to render →
|
|
19
|
-
* execute, exactly as the mode-2 thin-runtime handler does. It is byte-identical to the bundle
|
|
20
|
-
* minus the (now baked-as-literal) `component`/`irVersion`/`exprVersion`.
|
|
10
|
+
* 1. **Behavior module** — bc's SHARED generator (`generateModule`, bc#13) run over the bundle's
|
|
11
|
+
* portable IR (a read bundle's surrogate `ComponentGraphIR`, or the `makeSQL` component IR for
|
|
12
|
+
* a write), baked as a language-native LITERAL with a `bind(handlers)` accessor delegating to
|
|
13
|
+
* `runBehavior`. litedbmodel writes NO execution logic here — codegen is upstream-owned (bc).
|
|
14
|
+
* 2. **SQL catalog companion** — the pure-JSON STATIC makeSQL catalog (per-node statement
|
|
15
|
+
* templates / relations / transaction). A codegen consumer's thin SQL layer reads it to
|
|
16
|
+
* evaluate skip + value-specs → assemble → render → execute (identically to the mode-2 runtime).
|
|
21
17
|
*
|
|
22
|
-
* ##
|
|
18
|
+
* ## Byte-identical to the thin-runtime (mode-2), by construction
|
|
23
19
|
*
|
|
24
|
-
* The
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* calls the SAME `runBehavior(IR, handlers, input, name)`. So when a codegen runtime pairs the
|
|
29
|
-
* generated `bind` with the SAME {@link buildHandlers} + {@link normalizeInput} (from the SQL
|
|
30
|
-
* catalog companion), it drives the identical core over the identical IR and handlers — the SQL
|
|
31
|
-
* text (all dialects) and the results are identical, NOT approximately equal. The TS leg PROVES
|
|
32
|
-
* this by executing the generated module and asserting exact equality vs `executeBundle` AND vs
|
|
33
|
-
* the frozen conformance vector (see `codegenExecuteBundleForTest`).
|
|
34
|
-
*
|
|
35
|
-
* ## Language support (bc#13 capability — the honest scope boundary)
|
|
36
|
-
*
|
|
37
|
-
* bc's shared generator registers emitters for FIVE languages
|
|
38
|
-
* ({@link CODEGEN_LANGUAGES}); Go IS among them (bc#13 SP2 `emit-go.ts`). So litedbmodel mode-3
|
|
39
|
-
* codegen covers all five — TS/Python/Go/Rust/PHP. If a future litedbmodel construct needed an
|
|
40
|
-
* emitter bc does not provide, the honest move is to ESCALATE to bc (bc#22 pattern), never to
|
|
41
|
-
* fork a parallel litedbmodel-local generator (that would defeat the "codegen は上流所有" design).
|
|
20
|
+
* The companion IS the bundle, so a codegen runtime that evaluates it drives the IDENTICAL
|
|
21
|
+
* static-makeSQL render/execute path {@link executeBundle} uses — SQL text (all dialects) and
|
|
22
|
+
* results are identical, not approximately equal. The TS leg PROVES this by executing via the
|
|
23
|
+
* artifact and asserting exact equality vs {@link executeBundle} (see {@link codegenExecuteBundleForTest}).
|
|
42
24
|
*/
|
|
43
25
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
44
26
|
exports.CODEGEN_LANGUAGES = void 0;
|
|
@@ -48,45 +30,37 @@ exports.generateCodegenArtifact = generateCodegenArtifact;
|
|
|
48
30
|
exports.codegenExecuteBundleForTest = codegenExecuteBundleForTest;
|
|
49
31
|
const behavior_contracts_1 = require("behavior-contracts");
|
|
50
32
|
const runtime_1 = require("./runtime");
|
|
51
|
-
const
|
|
52
|
-
const dialect_1 = require("./dialect");
|
|
33
|
+
const ir_1 = require("./makesql/ir");
|
|
53
34
|
/**
|
|
54
35
|
* The languages litedbmodel mode-3 codegen supports — exactly the set bc's shared generator
|
|
55
|
-
* registers (bc#13 SP1 typescript/python + SP2 go/rust/php).
|
|
56
|
-
*
|
|
57
|
-
* requested language against the LIVE registry so a bc capability change is caught loudly rather
|
|
58
|
-
* than silently drifting from this constant.
|
|
36
|
+
* registers (bc#13 SP1 typescript/python + SP2 go/rust/php). {@link assertLanguageSupported}
|
|
37
|
+
* verifies against the LIVE registry so a bc capability drift is caught loudly.
|
|
59
38
|
*/
|
|
60
39
|
exports.CODEGEN_LANGUAGES = ['typescript', 'python', 'go', 'rust', 'php'];
|
|
61
40
|
/**
|
|
62
|
-
* The portable {@link ComponentGraphIR} view of a §8 bundle:
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* {@link executeBundle} feeds `runBehavior`, so baking it is what makes codegen equivalent.
|
|
41
|
+
* The portable {@link ComponentGraphIR} view of a §8 bundle: a read bundle's surrogate IR (each
|
|
42
|
+
* SQL node → a `makeSQL` node), or the single `makeSQL` component IR for a write. Baking THIS is
|
|
43
|
+
* what makes the generated module a genuine bc artifact; the static SQL catalog rides the companion.
|
|
66
44
|
*/
|
|
67
45
|
function bundleToPortableIR(bundle) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
components: [bundle.component],
|
|
72
|
-
};
|
|
46
|
+
if (bundle.readGraph !== undefined)
|
|
47
|
+
return bundle.readGraph.ir;
|
|
48
|
+
return (0, ir_1.makeSqlComponentIR)(bundle.name);
|
|
73
49
|
}
|
|
74
|
-
/** The
|
|
50
|
+
/** The STATIC makeSQL execution catalog carried alongside the (baked) portable IR. */
|
|
75
51
|
function companionOf(bundle) {
|
|
76
52
|
const base = {
|
|
77
|
-
operations: bundle.operations,
|
|
78
53
|
dialect: bundle.dialect,
|
|
79
54
|
optionalHeads: bundle.optionalHeads,
|
|
80
55
|
relations: bundle.relations,
|
|
56
|
+
...(bundle.readGraph !== undefined ? { readGraph: bundle.readGraph } : {}),
|
|
57
|
+
...(bundle.statement !== undefined ? { statement: bundle.statement } : {}),
|
|
81
58
|
};
|
|
82
59
|
return bundle.transaction === undefined ? base : { ...base, transaction: bundle.transaction };
|
|
83
60
|
}
|
|
84
61
|
/**
|
|
85
|
-
* Assert the requested language is one bc's shared generator can
|
|
86
|
-
*
|
|
87
|
-
* drift (an emitter removed/renamed upstream) fails LOUDLY here instead of surfacing as a broken
|
|
88
|
-
* generated module. `generateModule` itself also fail-closes on an unknown language; this is the
|
|
89
|
-
* earlier, litedbmodel-scoped guard with the honest "bc supports X" message.
|
|
62
|
+
* Assert the requested language is one bc's shared generator can emit — checked against the LIVE
|
|
63
|
+
* registry, not just the {@link CODEGEN_LANGUAGES} constant, so a bc capability drift fails LOUDLY.
|
|
90
64
|
*/
|
|
91
65
|
function assertLanguageSupported(language, registered) {
|
|
92
66
|
if (!exports.CODEGEN_LANGUAGES.includes(language)) {
|
|
@@ -100,38 +74,21 @@ function assertLanguageSupported(language, registered) {
|
|
|
100
74
|
}
|
|
101
75
|
/**
|
|
102
76
|
* Generate the mode-3 codegen artifact for ONE §8 bundle in ONE target language: bc's SHARED
|
|
103
|
-
* generator bakes the
|
|
77
|
+
* generator bakes the IR literal + emits `bind(handlers)`, and we attach the STATIC SQL catalog
|
|
104
78
|
* companion. litedbmodel supplies the input (IR + catalog); bc owns the emitter.
|
|
105
|
-
*
|
|
106
|
-
* @param registeredLanguages the live `registeredLanguages()` from bc (injected so the guard sees
|
|
107
|
-
* the actual generator capability; the caller passes `bc.registeredLanguages()`).
|
|
108
|
-
* @param runtimeImport optional runtime-core import specifier override (test/vendored layouts);
|
|
109
|
-
* forwarded verbatim to the generator so the generated code stays deterministic.
|
|
110
79
|
*/
|
|
111
80
|
function generateCodegenArtifact(bundle, language, registeredLanguages, runtimeImport) {
|
|
112
81
|
assertLanguageSupported(language, registeredLanguages);
|
|
113
82
|
const ir = bundleToPortableIR(bundle);
|
|
114
83
|
const module = (0, behavior_contracts_1.generateModule)(ir, runtimeImport === undefined ? { language } : { language, runtimeImport });
|
|
115
|
-
return { language, module, companion: companionOf(bundle), ir };
|
|
84
|
+
return { language, module, companion: companionOf(bundle), ir, bundle };
|
|
116
85
|
}
|
|
117
86
|
/**
|
|
118
|
-
* The TS codegen EXECUTION path used to PROVE byte-identity:
|
|
119
|
-
*
|
|
120
|
-
*
|
|
121
|
-
* catalog companion drives ({@link buildHandlers}). Executing this and comparing to
|
|
122
|
-
* {@link executeBundle} is a REAL byte-identity check on the codegen path (the generated TS module
|
|
123
|
-
* is separately IMPORTED and run in the conformance leg to prove the emitted source itself agrees).
|
|
124
|
-
*
|
|
125
|
-
* This mirrors {@link executeBundle} field-for-field, but drives the components/handlers via the
|
|
126
|
-
* codegen ARTIFACT (baked IR + companion) rather than the raw bundle — so a divergence between the
|
|
127
|
-
* codegen artifact and the bundle would surface as a mismatch.
|
|
87
|
+
* The TS codegen EXECUTION path used to PROVE byte-identity: a codegen runtime reads the SQL
|
|
88
|
+
* catalog companion (the STATIC makeSQL bundle) and evaluates skip + value-specs → assemble →
|
|
89
|
+
* render → execute — which is EXACTLY {@link executeBundle} over the SAME bundle.
|
|
128
90
|
*/
|
|
129
91
|
function codegenExecuteBundleForTest(artifact, input, db) {
|
|
130
|
-
|
|
131
|
-
const component = ir.components[0];
|
|
132
|
-
const { operations, dialect, optionalHeads } = artifact.companion;
|
|
133
|
-
const handlers = (0, runtime_1.buildHandlers)(db, operations, (0, dialect_1.dialectFor)(dialect));
|
|
134
|
-
const normalized = (0, runtime_1.normalizeInput)(component, new Set(optionalHeads), input);
|
|
135
|
-
return (0, behavior_contracts_2.runBehavior)(ir, handlers, normalized, component.name);
|
|
92
|
+
return (0, runtime_1.executeBundle)(artifact.bundle, input, { db });
|
|
136
93
|
}
|
|
137
94
|
//# sourceMappingURL=codegen.js.map
|
package/dist/scp/codegen.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codegen.js","sourceRoot":"","sources":["../../src/scp/codegen.ts"],"names":[],"mappings":";AAAA
|
|
1
|
+
{"version":3,"file":"codegen.js","sourceRoot":"","sources":["../../src/scp/codegen.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;;;AAyDH,gDAGC;AAkBD,0DAaC;AAOD,0DAUC;AAOD,kEAEC;AAnHD,2DAAyH;AAEzH,uCAA0C;AAC1C,qCAAkD;AAMlD;;;;GAIG;AACU,QAAA,iBAAiB,GAAG,CAAC,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAU,CAAC;AAoCxF;;;;GAIG;AACH,SAAgB,kBAAkB,CAAC,MAAiB;IAClD,IAAI,MAAM,CAAC,SAAS,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;IAC/D,OAAO,IAAA,uBAAkB,EAAC,MAAM,CAAC,IAAI,CAAC,CAAC;AACzC,CAAC;AAED,sFAAsF;AACtF,SAAS,WAAW,CAAC,MAAiB;IACpC,MAAM,IAAI,GAAwB;QAChC,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,SAAS,EAAE,MAAM,CAAC,SAAS;QAC3B,GAAG,CAAC,MAAM,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1E,GAAG,CAAC,MAAM,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC3E,CAAC;IACF,OAAO,MAAM,CAAC,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC;AAChG,CAAC;AAED;;;GAGG;AACH,SAAgB,uBAAuB,CAAC,QAAgB,EAAE,UAA6B;IACrF,IAAI,CAAE,yBAAuC,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACjE,MAAM,IAAI,KAAK,CACb,kCAAkC,QAAQ,2CAA2C,yBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CACrH,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CACb,gFAAgF,QAAQ,IAAI;YAC1F,mBAAmB,CAAC,GAAG,UAAU,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,wEAAwE;YAC5H,oEAAoE,CACvE,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAgB,uBAAuB,CACrC,MAAiB,EACjB,QAAgB,EAChB,mBAAsC,EACtC,aAAsB;IAEtB,uBAAuB,CAAC,QAAQ,EAAE,mBAAmB,CAAC,CAAC;IACvD,MAAM,EAAE,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,IAAA,mCAAc,EAAC,EAAE,EAAE,aAAa,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,aAAa,EAAE,CAAC,CAAC;IAC5G,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC;AAC1E,CAAC;AAED;;;;GAIG;AACH,SAAgB,2BAA2B,CAAC,QAAyB,EAAE,KAAY,EAAE,EAAY;IAC/F,OAAO,IAAA,uBAAa,EAAC,QAAQ,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC;AACvD,CAAC"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* litedbmodel v2 SCP — FIND_FILTER fail-closed authoring guard (#47 Finding B / plan R8).
|
|
3
|
+
*
|
|
4
|
+
* `FIND_FILTER` is a v1 PER-MODEL implicit predicate (a soft-delete / tenant scope) that
|
|
5
|
+
* v1 MERGES into the WHERE of every internal `find()`/`count()` before compiling
|
|
6
|
+
* (`DBModel._buildSelectSQL` / `_count`: `normalizedCond.add(condsToRecord(FIND_FILTER))`).
|
|
7
|
+
* It is not a distinct SQL construct — it manifests purely as extra WHERE fragments.
|
|
8
|
+
*
|
|
9
|
+
* The SCP compile path (`L.Select({ where })` → `compileReadGraph` → `renderReadPrimary`)
|
|
10
|
+
* is AUTHORED from an explicit `where`; it has NO `DBModel` class context, so it CANNOT
|
|
11
|
+
* auto-apply a model's `FIND_FILTER` (there is no model reference at compile time, and no
|
|
12
|
+
* production seam routes a `DBModel` into the SCP compile — grep: `FIND_FILTER` appears only
|
|
13
|
+
* in `DBModel`/`decorators`/`types`, never under `src/scp/`). Auto-apply is therefore
|
|
14
|
+
* impossible without model context the SCP compile does not have.
|
|
15
|
+
*
|
|
16
|
+
* Resolution = the plan's variant chosen fail-closed: a model that DECLARES a `FIND_FILTER`
|
|
17
|
+
* MUST have its scope predicates folded into the authored `L.Select({ where })`; if the
|
|
18
|
+
* author routes such a model through SCP WITHOUT the scope keys, this guard throws loudly
|
|
19
|
+
* ({@link FindFilterLeakError}) rather than silently dropping the filter (which would leak
|
|
20
|
+
* soft-deleted / cross-tenant rows — a correctness bug, not a cosmetic one). When the scope
|
|
21
|
+
* keys ARE present the guard is a no-op (byte-identical to v1's merged WHERE — proven in
|
|
22
|
+
* `test/scp/find-filter-noop.test.ts`).
|
|
23
|
+
*/
|
|
24
|
+
import { type Conds } from '../Column';
|
|
25
|
+
/** A `DBModel`-shaped source: the only field this guard reads is the optional `FIND_FILTER`. */
|
|
26
|
+
export interface FindFilterSource {
|
|
27
|
+
/** The per-model implicit scope predicate (v1 `DBModel.FIND_FILTER`), or null/undefined. */
|
|
28
|
+
readonly FIND_FILTER?: Conds | null;
|
|
29
|
+
/** Class name for diagnostics (present on every `DBModel` subclass). */
|
|
30
|
+
readonly name?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* A model declaring a `FIND_FILTER` was compiled via the SCP path without its scope
|
|
34
|
+
* predicates present in the authored WHERE — the filter would silently leak. Thrown
|
|
35
|
+
* fail-closed at compile time; never swallowed.
|
|
36
|
+
*/
|
|
37
|
+
export declare class FindFilterLeakError extends Error {
|
|
38
|
+
/** The declaring model's class name (diagnostics). */
|
|
39
|
+
readonly modelName: string;
|
|
40
|
+
/** The FIND_FILTER scope keys that are MISSING from the authored WHERE. */
|
|
41
|
+
readonly missingKeys: readonly string[];
|
|
42
|
+
constructor(
|
|
43
|
+
/** The declaring model's class name (diagnostics). */
|
|
44
|
+
modelName: string,
|
|
45
|
+
/** The FIND_FILTER scope keys that are MISSING from the authored WHERE. */
|
|
46
|
+
missingKeys: readonly string[]);
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Extract the top-level predicate KEYS a `FIND_FILTER` folds into a WHERE. Uses the SAME
|
|
50
|
+
* `condsToRecord` v1 runs when merging (`DBModel._buildSelectSQL`:597) so the key set is
|
|
51
|
+
* exactly what v1 would add to `normalizedCond`. `__or__` (grouped OR) is kept as a key so a
|
|
52
|
+
* scoped OR-group also demands explicit authoring (fail-closed; never silently satisfied).
|
|
53
|
+
*/
|
|
54
|
+
export declare function findFilterKeys(filter: Conds | null | undefined): string[];
|
|
55
|
+
/**
|
|
56
|
+
* Fail-closed guard: assert a model's `FIND_FILTER` scope keys are all present in the
|
|
57
|
+
* authored WHERE key set BEFORE an SCP compile of that model. No FIND_FILTER → no-op. When
|
|
58
|
+
* present, EVERY scope key must appear in `authoredWhereKeys`; any missing key throws
|
|
59
|
+
* {@link FindFilterLeakError}. The caller passes the top-level keys of the object it hands to
|
|
60
|
+
* `L.Select({ where })` (the same shape `condsToRecord` yields), so the check compares
|
|
61
|
+
* like-for-like against v1's merged predicate.
|
|
62
|
+
*
|
|
63
|
+
* @param model a `DBModel`-shaped source that MAY declare `FIND_FILTER`.
|
|
64
|
+
* @param authoredWhereKeys the top-level WHERE keys the SCP author expressed for this model.
|
|
65
|
+
* @throws FindFilterLeakError when a declared scope key is absent from the authored WHERE.
|
|
66
|
+
*/
|
|
67
|
+
export declare function assertFindFilterFolded(model: FindFilterSource, authoredWhereKeys: Iterable<string>): void;
|
|
68
|
+
//# sourceMappingURL=find-filter-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-filter-guard.d.ts","sourceRoot":"","sources":["../../src/scp/find-filter-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAiB,KAAK,KAAK,EAAE,MAAM,WAAW,CAAC;AAEtD,gGAAgG;AAChG,MAAM,WAAW,gBAAgB;IAC/B,4FAA4F;IAC5F,QAAQ,CAAC,WAAW,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IACpC,wEAAwE;IACxE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;GAIG;AACH,qBAAa,mBAAoB,SAAQ,KAAK;IAE1C,sDAAsD;IACtD,QAAQ,CAAC,SAAS,EAAE,MAAM;IAC1B,2EAA2E;IAC3E,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE;;IAHvC,sDAAsD;IAC7C,SAAS,EAAE,MAAM;IAC1B,2EAA2E;IAClE,WAAW,EAAE,SAAS,MAAM,EAAE;CAY1C;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,KAAK,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,CAGzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,gBAAgB,EACvB,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,GAClC,IAAI,CAQN"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* litedbmodel v2 SCP — FIND_FILTER fail-closed authoring guard (#47 Finding B / plan R8).
|
|
4
|
+
*
|
|
5
|
+
* `FIND_FILTER` is a v1 PER-MODEL implicit predicate (a soft-delete / tenant scope) that
|
|
6
|
+
* v1 MERGES into the WHERE of every internal `find()`/`count()` before compiling
|
|
7
|
+
* (`DBModel._buildSelectSQL` / `_count`: `normalizedCond.add(condsToRecord(FIND_FILTER))`).
|
|
8
|
+
* It is not a distinct SQL construct — it manifests purely as extra WHERE fragments.
|
|
9
|
+
*
|
|
10
|
+
* The SCP compile path (`L.Select({ where })` → `compileReadGraph` → `renderReadPrimary`)
|
|
11
|
+
* is AUTHORED from an explicit `where`; it has NO `DBModel` class context, so it CANNOT
|
|
12
|
+
* auto-apply a model's `FIND_FILTER` (there is no model reference at compile time, and no
|
|
13
|
+
* production seam routes a `DBModel` into the SCP compile — grep: `FIND_FILTER` appears only
|
|
14
|
+
* in `DBModel`/`decorators`/`types`, never under `src/scp/`). Auto-apply is therefore
|
|
15
|
+
* impossible without model context the SCP compile does not have.
|
|
16
|
+
*
|
|
17
|
+
* Resolution = the plan's variant chosen fail-closed: a model that DECLARES a `FIND_FILTER`
|
|
18
|
+
* MUST have its scope predicates folded into the authored `L.Select({ where })`; if the
|
|
19
|
+
* author routes such a model through SCP WITHOUT the scope keys, this guard throws loudly
|
|
20
|
+
* ({@link FindFilterLeakError}) rather than silently dropping the filter (which would leak
|
|
21
|
+
* soft-deleted / cross-tenant rows — a correctness bug, not a cosmetic one). When the scope
|
|
22
|
+
* keys ARE present the guard is a no-op (byte-identical to v1's merged WHERE — proven in
|
|
23
|
+
* `test/scp/find-filter-noop.test.ts`).
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.FindFilterLeakError = void 0;
|
|
27
|
+
exports.findFilterKeys = findFilterKeys;
|
|
28
|
+
exports.assertFindFilterFolded = assertFindFilterFolded;
|
|
29
|
+
const Column_1 = require("../Column");
|
|
30
|
+
/**
|
|
31
|
+
* A model declaring a `FIND_FILTER` was compiled via the SCP path without its scope
|
|
32
|
+
* predicates present in the authored WHERE — the filter would silently leak. Thrown
|
|
33
|
+
* fail-closed at compile time; never swallowed.
|
|
34
|
+
*/
|
|
35
|
+
class FindFilterLeakError extends Error {
|
|
36
|
+
modelName;
|
|
37
|
+
missingKeys;
|
|
38
|
+
constructor(
|
|
39
|
+
/** The declaring model's class name (diagnostics). */
|
|
40
|
+
modelName,
|
|
41
|
+
/** The FIND_FILTER scope keys that are MISSING from the authored WHERE. */
|
|
42
|
+
missingKeys) {
|
|
43
|
+
super(`FIND_FILTER leak: model '${modelName}' declares an implicit scope predicate on ` +
|
|
44
|
+
`[${missingKeys.join(', ')}] that is NOT present in the SCP-authored WHERE. The SCP ` +
|
|
45
|
+
`compile cannot auto-apply FIND_FILTER (no model context at compile time), so the ` +
|
|
46
|
+
`scope predicate would be silently dropped (soft-delete / tenant scope leak). Fold ` +
|
|
47
|
+
`the FIND_FILTER predicates into the authored L.Select({ where }) explicitly ` +
|
|
48
|
+
`(e.g. whereEq/whereIsNull on ${missingKeys.join(', ')}).`);
|
|
49
|
+
this.modelName = modelName;
|
|
50
|
+
this.missingKeys = missingKeys;
|
|
51
|
+
this.name = 'FindFilterLeakError';
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.FindFilterLeakError = FindFilterLeakError;
|
|
55
|
+
/**
|
|
56
|
+
* Extract the top-level predicate KEYS a `FIND_FILTER` folds into a WHERE. Uses the SAME
|
|
57
|
+
* `condsToRecord` v1 runs when merging (`DBModel._buildSelectSQL`:597) so the key set is
|
|
58
|
+
* exactly what v1 would add to `normalizedCond`. `__or__` (grouped OR) is kept as a key so a
|
|
59
|
+
* scoped OR-group also demands explicit authoring (fail-closed; never silently satisfied).
|
|
60
|
+
*/
|
|
61
|
+
function findFilterKeys(filter) {
|
|
62
|
+
if (!filter || filter.length === 0)
|
|
63
|
+
return [];
|
|
64
|
+
return Object.keys((0, Column_1.condsToRecord)(filter));
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Fail-closed guard: assert a model's `FIND_FILTER` scope keys are all present in the
|
|
68
|
+
* authored WHERE key set BEFORE an SCP compile of that model. No FIND_FILTER → no-op. When
|
|
69
|
+
* present, EVERY scope key must appear in `authoredWhereKeys`; any missing key throws
|
|
70
|
+
* {@link FindFilterLeakError}. The caller passes the top-level keys of the object it hands to
|
|
71
|
+
* `L.Select({ where })` (the same shape `condsToRecord` yields), so the check compares
|
|
72
|
+
* like-for-like against v1's merged predicate.
|
|
73
|
+
*
|
|
74
|
+
* @param model a `DBModel`-shaped source that MAY declare `FIND_FILTER`.
|
|
75
|
+
* @param authoredWhereKeys the top-level WHERE keys the SCP author expressed for this model.
|
|
76
|
+
* @throws FindFilterLeakError when a declared scope key is absent from the authored WHERE.
|
|
77
|
+
*/
|
|
78
|
+
function assertFindFilterFolded(model, authoredWhereKeys) {
|
|
79
|
+
const required = findFilterKeys(model.FIND_FILTER);
|
|
80
|
+
if (required.length === 0)
|
|
81
|
+
return; // No implicit scope → nothing to fold → no-op.
|
|
82
|
+
const present = new Set(authoredWhereKeys);
|
|
83
|
+
const missing = required.filter((k) => !present.has(k));
|
|
84
|
+
if (missing.length > 0) {
|
|
85
|
+
throw new FindFilterLeakError(model.name ?? '<anonymous model>', missing);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=find-filter-guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-filter-guard.js","sourceRoot":"","sources":["../../src/scp/find-filter-guard.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;;;AA0CH,wCAGC;AAcD,wDAWC;AApED,sCAAsD;AAUtD;;;;GAIG;AACH,MAAa,mBAAoB,SAAQ,KAAK;IAGjC;IAEA;IAJX;IACE,sDAAsD;IAC7C,SAAiB;IAC1B,2EAA2E;IAClE,WAA8B;QAEvC,KAAK,CACH,4BAA4B,SAAS,4CAA4C;YAC/E,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,2DAA2D;YACrF,mFAAmF;YACnF,oFAAoF;YACpF,8EAA8E;YAC9E,gCAAgC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAC7D,CAAC;QAXO,cAAS,GAAT,SAAS,CAAQ;QAEjB,gBAAW,GAAX,WAAW,CAAmB;QAUvC,IAAI,CAAC,IAAI,GAAG,qBAAqB,CAAC;IACpC,CAAC;CACF;AAjBD,kDAiBC;AAED;;;;;GAKG;AACH,SAAgB,cAAc,CAAC,MAAgC;IAC7D,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAC9C,OAAO,MAAM,CAAC,IAAI,CAAC,IAAA,sBAAa,EAAC,MAAM,CAAC,CAAC,CAAC;AAC5C,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAgB,sBAAsB,CACpC,KAAuB,EACvB,iBAAmC;IAEnC,MAAM,QAAQ,GAAG,cAAc,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IACnD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,+CAA+C;IAClF,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,iBAAiB,CAAC,CAAC;IAC3C,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACxD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,mBAAmB,CAAC,KAAK,CAAC,IAAI,IAAI,mBAAmB,EAAE,OAAO,CAAC,CAAC;IAC5E,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* litedbmodel v2 SCP — fragment RUNTIME (assemble + bind).
|
|
3
|
+
*
|
|
4
|
+
* This is the thin runtime a bundle drives. It does exactly three things:
|
|
5
|
+
*
|
|
6
|
+
* 1. ASSEMBLE — walk the ordered fragment list; drop any fragment whose `skip`
|
|
7
|
+
* is satisfied; concatenate the present fragments' `sql`, and their `params`
|
|
8
|
+
* in order. Nested-fragment params splice their own `sql` at the `?` and
|
|
9
|
+
* contribute their own params in order.
|
|
10
|
+
* 2. EVALUATE — turn each value-spec param into a concrete bound value using the
|
|
11
|
+
* runtime `input` (and upstream `wire` results).
|
|
12
|
+
* 3. RENDER — the produced `{ sql, params }` uses `?`; a dialect step turns `?`
|
|
13
|
+
* into `$N` for PostgreSQL (identical to the original driver's naive replace).
|
|
14
|
+
*
|
|
15
|
+
* NO SQL structure is generated here. The `sql` text is already complete; the
|
|
16
|
+
* runtime only splices nested-fragment text and fills `?`.
|
|
17
|
+
*/
|
|
18
|
+
import { type Fragment, type Node, type WhereGroup, type ValueSpec, type PresenceCondition } from './model';
|
|
19
|
+
/** Runtime binding scope: FLAT — input keys and wire results are top-level roots. */
|
|
20
|
+
export interface Scope {
|
|
21
|
+
input: Record<string, unknown>;
|
|
22
|
+
wire?: Record<string, Record<string, unknown>>;
|
|
23
|
+
}
|
|
24
|
+
/** Is a fragment's `skip` satisfied (⇒ omit the fragment)? */
|
|
25
|
+
export declare function isSkipped(cond: PresenceCondition | undefined, scope: Scope): boolean;
|
|
26
|
+
/** Evaluate a value-spec to its bound value against the runtime scope. */
|
|
27
|
+
export declare function evalValueSpec(spec: ValueSpec, scope: Scope): unknown;
|
|
28
|
+
/**
|
|
29
|
+
* Assemble a single fragment: interleave its literal `sql` (split on `?`) with the
|
|
30
|
+
* rendered text of each param, concatenating params in order.
|
|
31
|
+
*/
|
|
32
|
+
export declare function assembleFragment(frag: Fragment, scope: Scope): {
|
|
33
|
+
sql: string;
|
|
34
|
+
params: unknown[];
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Assemble a WHERE group: join present members with `" AND "`, prefix `" WHERE "`.
|
|
38
|
+
* If no member is present, produce the empty string (identical to the ORIGINAL
|
|
39
|
+
* `DBConditions.compile` → `parts.length === 0 ? '' : parts.join(' AND ')`, with the
|
|
40
|
+
* `" WHERE "` prefix owned by the caller/select in the original — here folded in).
|
|
41
|
+
*/
|
|
42
|
+
export declare function assembleWhereGroup(group: WhereGroup, scope: Scope): {
|
|
43
|
+
sql: string;
|
|
44
|
+
params: unknown[];
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Assemble an ordered fragment/WHERE-group list into a single `{ sql, params }`
|
|
48
|
+
* (with `?`). Fragments whose `skip` is satisfied are omitted entirely (sql AND
|
|
49
|
+
* params). A WHERE group joins its present members with the original's text glue.
|
|
50
|
+
*/
|
|
51
|
+
export declare function assemble(nodes: Node[], scope: Scope): {
|
|
52
|
+
sql: string;
|
|
53
|
+
params: unknown[];
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* PostgreSQL dialect render: `?` → `$1, $2, …`. Byte-identical to the original
|
|
57
|
+
* driver's `convertPlaceholders` (naive left-to-right replace; see
|
|
58
|
+
* `src/drivers/postgres.ts`).
|
|
59
|
+
*/
|
|
60
|
+
export declare function toPostgres(sql: string): string;
|
|
61
|
+
//# sourceMappingURL=assemble.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assemble.d.ts","sourceRoot":"","sources":["../../../src/scp/fragment/assemble.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EACL,KAAK,QAAQ,EACb,KAAK,IAAI,EACT,KAAK,UAAU,EAEf,KAAK,SAAS,EACd,KAAK,iBAAiB,EAOvB,MAAM,SAAS,CAAC;AAEjB,qFAAqF;AACrF,MAAM,WAAW,KAAK;IACpB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAChD;AAWD,8DAA8D;AAC9D,wBAAgB,SAAS,CAAC,IAAI,EAAE,iBAAiB,GAAG,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAIpF;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAqBpE;AAYD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,EAAE,CAAA;CAAE,CAejG;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,EAAE,CAAA;CAAE,CAWtG;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,EAAE,CAAA;CAAE,CAgBxF;AAED;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAG9C"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* litedbmodel v2 SCP — fragment RUNTIME (assemble + bind).
|
|
4
|
+
*
|
|
5
|
+
* This is the thin runtime a bundle drives. It does exactly three things:
|
|
6
|
+
*
|
|
7
|
+
* 1. ASSEMBLE — walk the ordered fragment list; drop any fragment whose `skip`
|
|
8
|
+
* is satisfied; concatenate the present fragments' `sql`, and their `params`
|
|
9
|
+
* in order. Nested-fragment params splice their own `sql` at the `?` and
|
|
10
|
+
* contribute their own params in order.
|
|
11
|
+
* 2. EVALUATE — turn each value-spec param into a concrete bound value using the
|
|
12
|
+
* runtime `input` (and upstream `wire` results).
|
|
13
|
+
* 3. RENDER — the produced `{ sql, params }` uses `?`; a dialect step turns `?`
|
|
14
|
+
* into `$N` for PostgreSQL (identical to the original driver's naive replace).
|
|
15
|
+
*
|
|
16
|
+
* NO SQL structure is generated here. The `sql` text is already complete; the
|
|
17
|
+
* runtime only splices nested-fragment text and fills `?`.
|
|
18
|
+
*/
|
|
19
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
20
|
+
exports.isSkipped = isSkipped;
|
|
21
|
+
exports.evalValueSpec = evalValueSpec;
|
|
22
|
+
exports.assembleFragment = assembleFragment;
|
|
23
|
+
exports.assembleWhereGroup = assembleWhereGroup;
|
|
24
|
+
exports.assemble = assemble;
|
|
25
|
+
exports.toPostgres = toPostgres;
|
|
26
|
+
const model_1 = require("./model");
|
|
27
|
+
function readPath(root, path) {
|
|
28
|
+
let cur = root;
|
|
29
|
+
for (const seg of path) {
|
|
30
|
+
if (cur == null)
|
|
31
|
+
return undefined;
|
|
32
|
+
cur = cur[seg];
|
|
33
|
+
}
|
|
34
|
+
return cur;
|
|
35
|
+
}
|
|
36
|
+
/** Is a fragment's `skip` satisfied (⇒ omit the fragment)? */
|
|
37
|
+
function isSkipped(cond, scope) {
|
|
38
|
+
if (!cond)
|
|
39
|
+
return false;
|
|
40
|
+
const v = readPath(scope.input, cond.absent);
|
|
41
|
+
return v == null;
|
|
42
|
+
}
|
|
43
|
+
/** Evaluate a value-spec to its bound value against the runtime scope. */
|
|
44
|
+
function evalValueSpec(spec, scope) {
|
|
45
|
+
if ((0, model_1.isInputRef)(spec))
|
|
46
|
+
return readPath(scope.input, spec.input);
|
|
47
|
+
if ((0, model_1.isWireRef)(spec)) {
|
|
48
|
+
const [node, ...rest] = spec.wire;
|
|
49
|
+
const nodeResults = scope.wire?.[node] ?? {};
|
|
50
|
+
return readPath(nodeResults, rest);
|
|
51
|
+
}
|
|
52
|
+
if ((0, model_1.isLiteral)(spec))
|
|
53
|
+
return spec.literal;
|
|
54
|
+
if ((0, model_1.isValueExpr)(spec)) {
|
|
55
|
+
if (spec.op === 'coalesce') {
|
|
56
|
+
for (const a of spec.args) {
|
|
57
|
+
const v = evalValueSpec(a, scope);
|
|
58
|
+
if (v != null)
|
|
59
|
+
return v;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
if (spec.op === 'add') {
|
|
64
|
+
return spec.args.reduce((acc, a) => acc + evalValueSpec(a, scope), 0);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
throw new Error(`unknown value-spec: ${JSON.stringify(spec)}`);
|
|
68
|
+
}
|
|
69
|
+
/** Flatten one param (value-spec OR nested fragment) into sql text + bound params. */
|
|
70
|
+
function assembleParam(param, scope) {
|
|
71
|
+
if ((0, model_1.isFragment)(param)) {
|
|
72
|
+
// Nested fragment: splice its own sql at the `?`, contribute its own params.
|
|
73
|
+
return assembleFragment(param, scope);
|
|
74
|
+
}
|
|
75
|
+
// Value-spec: this `?` is filled by a single bound value.
|
|
76
|
+
return { sql: '?', params: [evalValueSpec(param, scope)] };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Assemble a single fragment: interleave its literal `sql` (split on `?`) with the
|
|
80
|
+
* rendered text of each param, concatenating params in order.
|
|
81
|
+
*/
|
|
82
|
+
function assembleFragment(frag, scope) {
|
|
83
|
+
const chunks = frag.sql.split('?');
|
|
84
|
+
if (chunks.length - 1 !== frag.params.length) {
|
|
85
|
+
throw new Error(`fragment placeholder/param mismatch: ${chunks.length - 1} '?' vs ${frag.params.length} params in ${JSON.stringify(frag.sql)}`);
|
|
86
|
+
}
|
|
87
|
+
let sql = chunks[0];
|
|
88
|
+
const params = [];
|
|
89
|
+
for (let i = 0; i < frag.params.length; i++) {
|
|
90
|
+
const rendered = assembleParam(frag.params[i], scope);
|
|
91
|
+
sql += rendered.sql + chunks[i + 1];
|
|
92
|
+
params.push(...rendered.params);
|
|
93
|
+
}
|
|
94
|
+
return { sql, params };
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Assemble a WHERE group: join present members with `" AND "`, prefix `" WHERE "`.
|
|
98
|
+
* If no member is present, produce the empty string (identical to the ORIGINAL
|
|
99
|
+
* `DBConditions.compile` → `parts.length === 0 ? '' : parts.join(' AND ')`, with the
|
|
100
|
+
* `" WHERE "` prefix owned by the caller/select in the original — here folded in).
|
|
101
|
+
*/
|
|
102
|
+
function assembleWhereGroup(group, scope) {
|
|
103
|
+
const parts = [];
|
|
104
|
+
const params = [];
|
|
105
|
+
for (const member of group.where) {
|
|
106
|
+
if (isSkipped(member.skip, scope))
|
|
107
|
+
continue;
|
|
108
|
+
const r = assembleFragment(member, scope);
|
|
109
|
+
parts.push(r.sql);
|
|
110
|
+
params.push(...r.params);
|
|
111
|
+
}
|
|
112
|
+
if (parts.length === 0)
|
|
113
|
+
return { sql: '', params: [] };
|
|
114
|
+
return { sql: ` WHERE ${parts.join(' AND ')}`, params };
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Assemble an ordered fragment/WHERE-group list into a single `{ sql, params }`
|
|
118
|
+
* (with `?`). Fragments whose `skip` is satisfied are omitted entirely (sql AND
|
|
119
|
+
* params). A WHERE group joins its present members with the original's text glue.
|
|
120
|
+
*/
|
|
121
|
+
function assemble(nodes, scope) {
|
|
122
|
+
let sql = '';
|
|
123
|
+
const params = [];
|
|
124
|
+
for (const node of nodes) {
|
|
125
|
+
if ((0, model_1.isWhereGroup)(node)) {
|
|
126
|
+
const r = assembleWhereGroup(node, scope);
|
|
127
|
+
sql += r.sql;
|
|
128
|
+
params.push(...r.params);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (isSkipped(node.skip, scope))
|
|
132
|
+
continue;
|
|
133
|
+
const r = assembleFragment(node, scope);
|
|
134
|
+
sql += r.sql;
|
|
135
|
+
params.push(...r.params);
|
|
136
|
+
}
|
|
137
|
+
return { sql, params };
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* PostgreSQL dialect render: `?` → `$1, $2, …`. Byte-identical to the original
|
|
141
|
+
* driver's `convertPlaceholders` (naive left-to-right replace; see
|
|
142
|
+
* `src/drivers/postgres.ts`).
|
|
143
|
+
*/
|
|
144
|
+
function toPostgres(sql) {
|
|
145
|
+
let index = 0;
|
|
146
|
+
return sql.replace(/\?/g, () => `$${++index}`);
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=assemble.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"assemble.js","sourceRoot":"","sources":["../../../src/scp/fragment/assemble.ts"],"names":[],"mappings":"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"}
|