litedbmodel 2.0.2 → 2.1.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/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +44 -24
- package/dist/decorators.js.map +1 -1
- package/dist/drivers/mysql.d.ts.map +1 -1
- package/dist/drivers/mysql.js +6 -0
- package/dist/drivers/mysql.js.map +1 -1
- package/dist/drivers/postgres.d.ts.map +1 -1
- package/dist/drivers/postgres.js +7 -0
- 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 +37 -2
- package/dist/drivers/sqlite.js.map +1 -1
- package/dist/scp/authoring.d.ts +66 -3
- package/dist/scp/authoring.d.ts.map +1 -1
- package/dist/scp/authoring.js +194 -5
- package/dist/scp/authoring.js.map +1 -1
- package/dist/scp/catalog.d.ts +2 -1
- package/dist/scp/catalog.d.ts.map +1 -1
- package/dist/scp/catalog.js +12 -1
- package/dist/scp/catalog.js.map +1 -1
- package/dist/scp/codegen.d.ts +43 -31
- package/dist/scp/codegen.d.ts.map +1 -1
- package/dist/scp/codegen.js +279 -95
- package/dist/scp/codegen.js.map +1 -1
- package/dist/scp/coltype.d.ts +83 -0
- package/dist/scp/coltype.d.ts.map +1 -1
- package/dist/scp/coltype.js +220 -0
- package/dist/scp/coltype.js.map +1 -1
- package/dist/scp/guard.d.ts +1 -1
- package/dist/scp/guard.d.ts.map +1 -1
- package/dist/scp/guard.js +6 -0
- package/dist/scp/guard.js.map +1 -1
- package/dist/scp/index.cjs +7149 -9379
- package/dist/scp/index.d.ts +7 -6
- package/dist/scp/index.d.ts.map +1 -1
- package/dist/scp/index.js +21 -6
- package/dist/scp/index.js.map +1 -1
- package/dist/scp/index.mjs +729 -178
- package/dist/scp/makesql/authoring-compile.d.ts.map +1 -1
- package/dist/scp/makesql/authoring-compile.js +11 -0
- package/dist/scp/makesql/authoring-compile.js.map +1 -1
- package/dist/scp/makesql/index.d.ts +2 -3
- package/dist/scp/makesql/index.d.ts.map +1 -1
- package/dist/scp/makesql/index.js +6 -6
- package/dist/scp/makesql/index.js.map +1 -1
- package/dist/scp/makesql/json-batch.d.ts.map +1 -1
- package/dist/scp/makesql/json-batch.js +6 -1
- package/dist/scp/makesql/json-batch.js.map +1 -1
- package/dist/scp/makesql/outtype.d.ts +61 -6
- package/dist/scp/makesql/outtype.d.ts.map +1 -1
- package/dist/scp/makesql/outtype.js +164 -33
- package/dist/scp/makesql/outtype.js.map +1 -1
- package/dist/scp/makesql/pool-executor.d.ts +70 -0
- package/dist/scp/makesql/pool-executor.d.ts.map +1 -1
- package/dist/scp/makesql/pool-executor.js +73 -5
- package/dist/scp/makesql/pool-executor.js.map +1 -1
- package/dist/scp/makesql/static-bundle.d.ts +39 -30
- package/dist/scp/makesql/static-bundle.d.ts.map +1 -1
- package/dist/scp/makesql/static-bundle.js +269 -138
- package/dist/scp/makesql/static-bundle.js.map +1 -1
- package/dist/scp/makesql/writeouttype.d.ts +5 -6
- package/dist/scp/makesql/writeouttype.d.ts.map +1 -1
- package/dist/scp/makesql/writeouttype.js +5 -6
- package/dist/scp/makesql/writeouttype.js.map +1 -1
- package/dist/scp/relation.d.ts +15 -1
- package/dist/scp/relation.d.ts.map +1 -1
- package/dist/scp/relation.js +61 -2
- package/dist/scp/relation.js.map +1 -1
- package/dist/scp/runtime.d.ts +6 -5
- package/dist/scp/runtime.d.ts.map +1 -1
- package/dist/scp/runtime.js +27 -5
- package/dist/scp/runtime.js.map +1 -1
- package/docs/BENCHMARK.md +58 -56
- package/docs/benchmark-chart.svg +166 -166
- package/package.json +8 -2
- 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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"static-bundle.d.ts","sourceRoot":"","sources":["../../../src/scp/makesql/static-bundle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"static-bundle.d.ts","sourceRoot":"","sources":["../../../src/scp/makesql/static-bundle.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAEL,KAAK,KAAK,EACV,KAAK,KAAK,EACX,MAAM,oBAAoB,CAAC;AAM5B,OAAO,KAAK,EAAuC,gBAAgB,EAAE,gBAAgB,EAAE,OAAO,EAAc,qBAAqB,EAAE,MAAM,cAAc,CAAC;AAGxJ,OAAO,EAAsB,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AAE7D,OAAO,EAA0B,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAErF,OAAO,EAAmB,KAAK,kBAAkB,EAAE,KAAK,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAoB7F,6FAA6F;AAC7F,MAAM,MAAM,SAAS,GAAG,OAAO,CAAC;AAEhC;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,kDAAkD;IAClD,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,sFAAsF;IACtF,QAAQ,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,CAAC;IACtC,2EAA2E;IAC3E,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,aAAa,CAAC,EAAE,IAAI,CAAC;CAC/B;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,qCAAqC;IACrC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,QAAQ,CAAC,UAAU,EAAE,SAAS,eAAe,EAAE,CAAC;IAChD,gFAAgF;IAChF,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;CAC3C;AAID,KAAK,OAAO,GAAG,gBAAgB,GAAG,OAAO,CAAC;AAuc1C;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,eAAe,EAAE,CA6DpF;AAsBD;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,qBAAqB,EAC/B,OAAO,EAAE,OAAO,EAChB,KAAK,CAAC,EAAE,MAAM,GACb,YAAY,CAwBd;AA+LD,iFAAiF;AACjF,MAAM,WAAW,QAAQ;IACvB,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG;QACpB,GAAG,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC;QACrC,GAAG,CAAC,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG;YAAE,OAAO,EAAE,MAAM,CAAC;YAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAAA;SAAE,CAAC;KAClF,CAAC;CACH;AAWD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAU/G;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAY9G;AAoBD;;;;;;;;GAQG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,wGAAwG;IACxG,QAAQ,CAAC,EAAE,EAAE,gBAAgB,CAAC;IAC9B,uGAAuG;IACvG,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,eAAe,EAAE,CAAC,CAAC;IACpE,gFAAgF;IAChF,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C;;;;;;;OAOG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC,CAAC;CACjF;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,qBAAqB,EAC/B,OAAO,EAAE,OAAO,EAChB,KAAK,CAAC,EAAE,MAAM,EACd,eAAe,CAAC,EAAE,gBAAgB,EAClC,iBAAiB,CAAC,EAAE,kBAAkB,GACrC,SAAS,CA8EX;AAwID;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,QAAQ,GAAG,KAAK,CAwBpF;AAED,+FAA+F;AAC/F,MAAM,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;AAiCtG;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,KAAK,EAAE,SAAS,EAChB,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,gBAAgB,GACrB,OAAO,CAAC,KAAK,CAAC,CAwBhB;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,qBAAqB,EAC/B,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE,OAAO,EAChB,EAAE,EAAE,QAAQ,EACZ,KAAK,CAAC,EAAE,MAAM,GACb,KAAK,CAEP;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,EAAE,CAAA;CAAE,CASpG"}
|
|
@@ -51,6 +51,7 @@ const handler_1 = require("./handler");
|
|
|
51
51
|
const tx_2 = require("./tx");
|
|
52
52
|
const find_filter_guard_1 = require("../find-filter-guard");
|
|
53
53
|
const outtype_1 = require("./outtype");
|
|
54
|
+
const coltype_1 = require("../coltype");
|
|
54
55
|
const errors_1 = require("../errors");
|
|
55
56
|
const DBConditions_1 = require("../../DBConditions");
|
|
56
57
|
const DBValues_1 = require("../../DBValues");
|
|
@@ -61,6 +62,10 @@ const authoring_sql_1 = require("../authoring-sql");
|
|
|
61
62
|
function isMap(n) {
|
|
62
63
|
return 'map' in n;
|
|
63
64
|
}
|
|
65
|
+
/** A bc 0.7.3+ `FanoutNode` (connection fan-out). litedbmodel never emits these. */
|
|
66
|
+
function isFanout(n) {
|
|
67
|
+
return 'fanout' in n;
|
|
68
|
+
}
|
|
64
69
|
function nodeRef(n) {
|
|
65
70
|
return isMap(n) ? { component: n.map.component, ports: n.map.ports } : { component: n.component, ports: n.ports };
|
|
66
71
|
}
|
|
@@ -578,6 +583,11 @@ function compileStaticBundle(contract, dialect, entry) {
|
|
|
578
583
|
for (const n of component.body) {
|
|
579
584
|
if ('cond' in n)
|
|
580
585
|
continue;
|
|
586
|
+
if (isFanout(n)) {
|
|
587
|
+
// bc 0.7.3+ `FanoutNode`. litedbmodel never emits fanout — reject fail-closed rather
|
|
588
|
+
// than mistake it for the primary catalog `componentRef`.
|
|
589
|
+
throw new Error(`static-bundle: behavior '${component.name}' node '${n.id}' is a fanout node, not supported by litedbmodel (bc FanoutNode)`);
|
|
590
|
+
}
|
|
581
591
|
if (isMap(n))
|
|
582
592
|
continue;
|
|
583
593
|
if (primary === undefined)
|
|
@@ -642,6 +652,8 @@ function collectRefOptHeads(node, into) {
|
|
|
642
652
|
function skipGuardHeads(n, into) {
|
|
643
653
|
if ('cond' in n)
|
|
644
654
|
return;
|
|
655
|
+
if (isFanout(n))
|
|
656
|
+
return; // bc FanoutNode carries no where-ports SKIP guard (litedbmodel never emits it)
|
|
645
657
|
const ports = isMap(n) ? n.map.ports : n.ports;
|
|
646
658
|
const where = ports.where;
|
|
647
659
|
if (typeof where !== 'object' || where === null || !('arr' in where))
|
|
@@ -845,62 +857,14 @@ function executeStaticWrite(bundle, input, db) {
|
|
|
845
857
|
}
|
|
846
858
|
}
|
|
847
859
|
// ============================================================================
|
|
848
|
-
//
|
|
849
|
-
//
|
|
850
|
-
// (`
|
|
851
|
-
//
|
|
852
|
-
//
|
|
853
|
-
// This
|
|
860
|
+
// #12 — the NATIVE read-graph walker (interpreter-free): litedbmodel composes map /
|
|
861
|
+
// Φ-merge / wiring ITSELF over `compileBehaviors`' REAL `Select`/`Count`/map node IR
|
|
862
|
+
// (no `__makeSqlNode`/`__scope` surrogate, no bc `runBehavior`). Each body node's SQL
|
|
863
|
+
// comes from its `statementsById[id]` fragment templates (rendered against the walk
|
|
864
|
+
// scope); topology (`map.over`/`as`, `plan.groups`, `output` Φ) is read off the real
|
|
865
|
+
// nodes. This mirrors the rust/go/py/php native walkers — one shared orchestration model
|
|
866
|
+
// across all 5 runtimes, `runBehavior` invoked on NONE of them.
|
|
854
867
|
// ============================================================================
|
|
855
|
-
/** The synthetic port that carries a SQL node's render scope (bc evaluates it in-scope). */
|
|
856
|
-
const SCOPE_PORT = '__scope';
|
|
857
|
-
/** The makeSQL catalog leaf name every rewritten SQL node references. */
|
|
858
|
-
const NODE_COMPONENT = '__makeSqlNode';
|
|
859
|
-
/** Collect every ref/refOpt path HEAD used inside an Expression IR node. */
|
|
860
|
-
function collectAllHeads(node, heads) {
|
|
861
|
-
if (node === null || typeof node !== 'object')
|
|
862
|
-
return;
|
|
863
|
-
if (Array.isArray(node)) {
|
|
864
|
-
for (const el of node)
|
|
865
|
-
collectAllHeads(el, heads);
|
|
866
|
-
return;
|
|
867
|
-
}
|
|
868
|
-
const keys = Object.keys(node);
|
|
869
|
-
if (keys.length === 1 && (keys[0] === 'ref' || keys[0] === 'refOpt')) {
|
|
870
|
-
const path = node[keys[0]];
|
|
871
|
-
if (Array.isArray(path) && typeof path[0] === 'string')
|
|
872
|
-
heads.add(path[0]);
|
|
873
|
-
return;
|
|
874
|
-
}
|
|
875
|
-
for (const k of keys)
|
|
876
|
-
collectAllHeads(node[k], heads);
|
|
877
|
-
}
|
|
878
|
-
/** Every binding head a node's compiled statements reference (params + skip). */
|
|
879
|
-
function statementHeads(statements) {
|
|
880
|
-
const heads = new Set();
|
|
881
|
-
for (const stmt of statements) {
|
|
882
|
-
for (const p of stmt.params)
|
|
883
|
-
collectAllHeads(p, heads);
|
|
884
|
-
if (stmt.skip !== undefined)
|
|
885
|
-
collectAllHeads(stmt.skip, heads);
|
|
886
|
-
}
|
|
887
|
-
return heads;
|
|
888
|
-
}
|
|
889
|
-
/**
|
|
890
|
-
* Build the surrogate `__scope` port for a node: a bc `{obj:{…}}` re-exporting each referenced
|
|
891
|
-
* head as `{ref:[head]}` (excluding the IN-sentinel column marker). bc evaluates this in ITS
|
|
892
|
-
* scope (so `input`, sibling wire results and the `map` element `as` all resolve), then the
|
|
893
|
-
* handler renders the node's static statements against the resulting plain scope.
|
|
894
|
-
*/
|
|
895
|
-
function scopePort(statements) {
|
|
896
|
-
const obj = {};
|
|
897
|
-
for (const head of statementHeads(statements)) {
|
|
898
|
-
if (head === tx_1.IN_SENTINEL)
|
|
899
|
-
continue;
|
|
900
|
-
obj[head] = { ref: [head] };
|
|
901
|
-
}
|
|
902
|
-
return { obj };
|
|
903
|
-
}
|
|
904
868
|
/** Compile ONE authored SQL node (Select or CRUD write) into its static statements. */
|
|
905
869
|
function compileNodeStatements(node, dialect) {
|
|
906
870
|
const component = 'map' in node ? node.map.component : node.component;
|
|
@@ -910,10 +874,12 @@ function compileNodeStatements(node, dialect) {
|
|
|
910
874
|
return [{ sql: op.sql, params: op.params }];
|
|
911
875
|
}
|
|
912
876
|
/**
|
|
913
|
-
* Compile a READ behavior method into a portable {@link ReadGraph}
|
|
914
|
-
*
|
|
915
|
-
* and compile each node's static statements ONCE
|
|
916
|
-
*
|
|
877
|
+
* Compile a READ behavior method into a portable {@link ReadGraph} (#12 de-surrogated): keep
|
|
878
|
+
* `compileBehaviors`' REAL `Select`/`Count`/map body nodes (only additively annotate each with its
|
|
879
|
+
* read `outType`), and compile each node's static `makeSQL` statements ONCE into the `statementsById`
|
|
880
|
+
* sidecar (keyed by the real body-node id). SYMBOLIC — no concrete input. The native walker
|
|
881
|
+
* ({@link executeReadGraph}) owns map iteration / wire binding / Φ output at execute time, rendering
|
|
882
|
+
* each node's statements against the walk scope — never bc `runBehavior`, never a `__scope` surrogate.
|
|
917
883
|
*/
|
|
918
884
|
function compileReadGraph(contract, dialect, entry, findFilterModel, resolveColumnType) {
|
|
919
885
|
const component = entry ? contract.components.find((c) => c.name === entry) : contract.components[0];
|
|
@@ -929,6 +895,11 @@ function compileReadGraph(contract, dialect, entry, findFilterModel, resolveColu
|
|
|
929
895
|
for (const n of component.body) {
|
|
930
896
|
if ('cond' in n)
|
|
931
897
|
continue;
|
|
898
|
+
if (isFanout(n)) {
|
|
899
|
+
// bc 0.7.3+ `FanoutNode`. litedbmodel never emits fanout — reject fail-closed rather
|
|
900
|
+
// than mis-read it as a Select/Count ref and skip the FIND_FILTER fold check.
|
|
901
|
+
throw new Error(`static-bundle: read component '${component.name}' node '${n.id}' is a fanout node, not supported by litedbmodel (bc FanoutNode)`);
|
|
902
|
+
}
|
|
932
903
|
const ref = 'map' in n ? n.map : n;
|
|
933
904
|
if (ref.component !== 'Select' && ref.component !== 'Count')
|
|
934
905
|
continue;
|
|
@@ -939,115 +910,273 @@ function compileReadGraph(contract, dialect, entry, findFilterModel, resolveColu
|
|
|
939
910
|
for (const n of component.body) {
|
|
940
911
|
if ('cond' in n)
|
|
941
912
|
continue;
|
|
913
|
+
if (isFanout(n)) {
|
|
914
|
+
// bc 0.7.3+ `FanoutNode`. litedbmodel never emits fanout — reject fail-closed rather
|
|
915
|
+
// than feed it to `compileNodeStatements` as a catalog ref.
|
|
916
|
+
throw new Error(`static-bundle: read component '${component.name}' node '${n.id}' is a fanout node, not supported by litedbmodel (bc FanoutNode)`);
|
|
917
|
+
}
|
|
942
918
|
statementsById[n.id] = compileNodeStatements(n, dialect);
|
|
943
919
|
}
|
|
944
|
-
//
|
|
945
|
-
//
|
|
946
|
-
//
|
|
947
|
-
//
|
|
948
|
-
//
|
|
949
|
-
|
|
950
|
-
|
|
920
|
+
// SINGLE column-type resolution (issue #59 audit — unified read+codegen): `deriveReadOutTypes`
|
|
921
|
+
// resolves every projected column of the read ONCE (via the SHARED projection parser, all shapes:
|
|
922
|
+
// bare / qualified / aliased; `*` / computed / undeclared → HARD ERROR, spec §4.1) and returns BOTH
|
|
923
|
+
// the codegen `outType`/`outputType` annotations AND the TS read-path `materializersByNode` — two
|
|
924
|
+
// projections of the ONE resolution, so they cannot diverge (there is no second, weaker read-path
|
|
925
|
+
// pass). The column types come from the model's inline `static columns` (carried on the contract as
|
|
926
|
+
// `resolveColumnType`, always present for a read model), so this runs for EVERY read: codegen writes
|
|
927
|
+
// the outType IR annotations, the runtime read path consumes the materializers. Any ambiguity /
|
|
928
|
+
// undeclared column / `*` / computed projection throws here.
|
|
929
|
+
const readTypes = resolveColumnType !== undefined ? (0, outtype_1.deriveReadOutTypes)(component, resolveColumnType) : undefined;
|
|
930
|
+
const materializers = readTypes?.materializersByNode;
|
|
931
|
+
// #12 (de-surrogation): the read-graph IR is `compileBehaviors`' REAL component — the authored
|
|
932
|
+
// `Select`/`Count`/map nodes with their own `table`/`select`/`where`/… ports, NOT a hand-built
|
|
933
|
+
// `__makeSqlNode` + `__scope` surrogate. litedbmodel constructs NO `ComponentGraphIR`/`BodyNode`
|
|
934
|
+
// literal here: it only ADDITIVELY annotates each node with its read `outType` (issue #59) and the
|
|
935
|
+
// component with its `outputType` — the node SHAPE/ports are `compileBehaviors`' output untouched.
|
|
936
|
+
// The compiled SQL fragment templates ride the sidecar `statementsById` (opaque, v1-`DBConditions`-
|
|
937
|
+
// sourced TEXT), keyed by the SAME real body-node id; every language runtime walks this real-node
|
|
938
|
+
// IR natively (topology from the real nodes; SQL from `statementsById[id]`), never bc `runBehavior`.
|
|
939
|
+
const annotatedBody = component.body.map((n) => {
|
|
951
940
|
if ('cond' in n)
|
|
952
941
|
return n;
|
|
953
|
-
const
|
|
954
|
-
|
|
955
|
-
const outAnn = outType !== undefined ? { outType } : {};
|
|
956
|
-
if ('map' in n) {
|
|
957
|
-
return { ...n, ...outAnn, map: { ...n.map, component: NODE_COMPONENT, ports: { [SCOPE_PORT]: scopePort(stmts) } } };
|
|
958
|
-
}
|
|
959
|
-
return { ...n, ...outAnn, component: NODE_COMPONENT, ports: { [SCOPE_PORT]: scopePort(stmts) } };
|
|
942
|
+
const outType = readTypes?.byNode.get(n.id);
|
|
943
|
+
return outType !== undefined ? { ...n, outType } : n;
|
|
960
944
|
});
|
|
961
|
-
const
|
|
945
|
+
const irComponent = {
|
|
962
946
|
...component,
|
|
963
|
-
body:
|
|
964
|
-
...(
|
|
947
|
+
body: annotatedBody,
|
|
948
|
+
...(readTypes !== undefined ? { outputType: readTypes.outputType } : {}),
|
|
965
949
|
};
|
|
966
|
-
|
|
950
|
+
// Preserve `compileBehaviors`' OWN IR envelope (irVersion/exprVersion + any other metadata) — the
|
|
951
|
+
// read graph IR is the compiler's output with ONLY the single entry component swapped for its
|
|
952
|
+
// outType-annotated twin (additive). litedbmodel fabricates no `ComponentGraphIR`/`BodyNode`.
|
|
953
|
+
const ir = { ...contract.ir, components: [irComponent] };
|
|
967
954
|
return {
|
|
968
955
|
dialect,
|
|
969
956
|
name: component.name,
|
|
970
957
|
ir,
|
|
971
958
|
statementsById,
|
|
972
959
|
optionalHeads: [...optionalHeadsOfComponent(component, dialect)],
|
|
960
|
+
...(materializers !== undefined && materializers.size > 0 ? { materializersByNode: Object.fromEntries(materializers) } : {}),
|
|
973
961
|
};
|
|
974
962
|
}
|
|
975
963
|
/**
|
|
976
|
-
*
|
|
977
|
-
*
|
|
978
|
-
*
|
|
979
|
-
*
|
|
964
|
+
* Apply a node's per-column materializers (issue #59) to its raw driver rows: coerce each cell to
|
|
965
|
+
* the JS form its SQL column type declares (BIGINT→bigint, INT→number, DATE→string, BOOLEAN→
|
|
966
|
+
* boolean). A no-op when the node has no materializer map (read compiled without a column-type
|
|
967
|
+
* resolver) or a column has no entry (defensive — only projected columns are typed). Mutates + returns
|
|
968
|
+
* the same row objects (they are freshly produced by the driver, so mutation is safe).
|
|
980
969
|
*/
|
|
981
|
-
function
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
970
|
+
function materializeRows(rows, cols, safeIntegersOn = false) {
|
|
971
|
+
if (cols === undefined && !safeIntegersOn)
|
|
972
|
+
return rows;
|
|
973
|
+
for (const row of rows) {
|
|
974
|
+
for (const key of Object.keys(row)) {
|
|
975
|
+
const klass = cols?.[key];
|
|
976
|
+
if (klass !== undefined) {
|
|
977
|
+
row[key] = (0, coltype_1.materializeCell)(row[key], klass);
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
980
|
+
// Defensive (SQLite safeIntegers mode): when the statement was put in safe-integer mode for a
|
|
981
|
+
// BIGINT column, EVERY int column arrives as a bigint — including columns the resolver didn't
|
|
982
|
+
// type. Narrow a stray bigint back to a number when it fits safely, else to an exact string
|
|
983
|
+
// (so a value never leaks out as a raw bigint, which is not JSON-safe).
|
|
984
|
+
if (safeIntegersOn && typeof row[key] === 'bigint') {
|
|
985
|
+
const v = row[key];
|
|
986
|
+
row[key] = v >= BigInt(Number.MIN_SAFE_INTEGER) && v <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(v) : v.toString();
|
|
987
|
+
}
|
|
989
988
|
}
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
989
|
+
}
|
|
990
|
+
return rows;
|
|
991
|
+
}
|
|
992
|
+
/** True iff any projected column of this node is a 64-bit int (needs the driver in exact mode). */
|
|
993
|
+
function hasInt64(cols) {
|
|
994
|
+
for (const k of Object.keys(cols))
|
|
995
|
+
if (cols[k] === 'int64')
|
|
996
|
+
return true;
|
|
997
|
+
return false;
|
|
998
|
+
}
|
|
999
|
+
/** The plan stages (`groups`) as body-index lists, or one-node-per-stage in declaration order. */
|
|
1000
|
+
function planStages(component) {
|
|
1001
|
+
const plan = component.plan;
|
|
1002
|
+
const groups = plan?.groups;
|
|
1003
|
+
if (Array.isArray(groups)) {
|
|
1004
|
+
return groups.map((st) => (Array.isArray(st) ? st.filter((i) => typeof i === 'number') : []));
|
|
1005
|
+
}
|
|
1006
|
+
return component.body.map((_n, i) => [i]);
|
|
1007
|
+
}
|
|
1008
|
+
/** The plan's `concurrency` bound (default 1 — serial when absent/≤1), for the async fan-out. */
|
|
1009
|
+
function planConcurrency(component) {
|
|
1010
|
+
const plan = component.plan;
|
|
1011
|
+
const c = plan?.concurrency;
|
|
1012
|
+
return typeof c === 'number' && c >= 1 ? c : 1;
|
|
1013
|
+
}
|
|
1014
|
+
/**
|
|
1015
|
+
* Bounded-parallel map preserving INPUT ORDER in the result. At most `limit` tasks run at once
|
|
1016
|
+
* (`limit <= 1` ⇒ strictly serial). Deterministic: the output array is indexed by input position,
|
|
1017
|
+
* regardless of completion order — mirrors bc `runPlanAsync`'s in-order commit, so the async walker's
|
|
1018
|
+
* assembled result equals the serial walker's byte-for-byte.
|
|
1019
|
+
*/
|
|
1020
|
+
async function boundedMap(items, limit, fn) {
|
|
1021
|
+
const out = new Array(items.length);
|
|
1022
|
+
const cap = Math.max(1, Math.floor(limit));
|
|
1023
|
+
let next = 0;
|
|
1024
|
+
async function worker() {
|
|
1025
|
+
for (;;) {
|
|
1026
|
+
const i = next++;
|
|
1027
|
+
if (i >= items.length)
|
|
1028
|
+
return;
|
|
1029
|
+
out[i] = await fn(items[i], i);
|
|
994
1030
|
}
|
|
995
|
-
|
|
996
|
-
|
|
1031
|
+
}
|
|
1032
|
+
await Promise.all(Array.from({ length: Math.min(cap, items.length) }, () => worker()));
|
|
1033
|
+
return out;
|
|
1034
|
+
}
|
|
1035
|
+
/** Render + execute a node's `statementsById` fragments against `scope`, sync driver, with #59 de-box. */
|
|
1036
|
+
function execReadNodeSync(graph, nodeId, scope, db) {
|
|
1037
|
+
const stmts = graph.statementsById[nodeId];
|
|
1038
|
+
if (stmts === undefined)
|
|
1039
|
+
throw new Error(`static-bundle: no statements for node '${nodeId}'`);
|
|
1040
|
+
const { sql, params } = renderStatements(stmts, graph.dialect, scope);
|
|
1041
|
+
const cols = graph.materializersByNode?.[nodeId];
|
|
1042
|
+
try {
|
|
1043
|
+
const stmt = db.prepare(sql);
|
|
1044
|
+
// A BIGINT-projecting node runs in SQLite safe-integer mode so int8 comes back as an EXACT
|
|
1045
|
+
// bigint (a JS number would round it — the #59 hole); `materializeRows` then narrows/stringifies.
|
|
1046
|
+
let safeIntegersOn = false;
|
|
1047
|
+
if (cols !== undefined && hasInt64(cols) && typeof stmt.safeIntegers === 'function') {
|
|
1048
|
+
stmt.safeIntegers(true);
|
|
1049
|
+
safeIntegersOn = true;
|
|
997
1050
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1051
|
+
const rows = stmt.all(...params);
|
|
1052
|
+
return materializeRows(rows, cols, safeIntegersOn);
|
|
1053
|
+
}
|
|
1054
|
+
catch (e) {
|
|
1055
|
+
throw (0, errors_1.mapSqliteError)(e);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
/** Compute ONE read-graph body node's value against `base`: a `cond` join, a `map` (per-element
|
|
1059
|
+
* render+execute under the `as` binding), or a componentRef (render+execute). Sync driver. */
|
|
1060
|
+
function computeReadNodeSync(graph, node, base, db) {
|
|
1061
|
+
if (node.cond !== undefined)
|
|
1062
|
+
return (0, behavior_contracts_1.evaluateExpression)(node.cond, base);
|
|
1063
|
+
if (node.map !== undefined) {
|
|
1064
|
+
const over = (0, behavior_contracts_1.evaluateExpression)(node.map.over, base);
|
|
1065
|
+
if (!Array.isArray(over))
|
|
1066
|
+
throw new Error(`static-bundle: map '${node.id}': 'over' is not an array`);
|
|
1067
|
+
const asName = node.map.as ?? '$';
|
|
1068
|
+
const out = [];
|
|
1069
|
+
for (const el of over) {
|
|
1070
|
+
out.push(execReadNodeSync(graph, node.id, { ...base, [asName]: el }, db));
|
|
1071
|
+
}
|
|
1072
|
+
return out;
|
|
1073
|
+
}
|
|
1074
|
+
return execReadNodeSync(graph, node.id, base, db);
|
|
1075
|
+
}
|
|
1076
|
+
/** Normalize omitted OPTIONAL heads to present-as-null (absent-key SKIP; SSoT-driven). */
|
|
1077
|
+
function normalizeReadInput(graph, input) {
|
|
1078
|
+
const out = { ...input };
|
|
1001
1079
|
for (const head of graph.optionalHeads)
|
|
1002
|
-
if (!(head in
|
|
1003
|
-
|
|
1080
|
+
if (!(head in out))
|
|
1081
|
+
out[head] = null;
|
|
1082
|
+
return out;
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Execute a compiled {@link ReadGraph} via the NATIVE walker (#12): walk `compileBehaviors`' REAL
|
|
1086
|
+
* `Select`/`Count`/map node IR in `plan.groups` stage order — computing each node's rows from its
|
|
1087
|
+
* `statementsById` fragments (rendered against the walk scope) and committing `(nodeId → value)` —
|
|
1088
|
+
* then evaluate the component's `output` Φ expression. NO bc `runBehavior`, NO `__makeSqlNode`
|
|
1089
|
+
* surrogate; litedbmodel owns map iteration / wire binding / Φ output. This is the SAME native
|
|
1090
|
+
* orchestration model the rust/go/py/php runtimes follow — one shape, all 5 languages, interpreter-free.
|
|
1091
|
+
*/
|
|
1092
|
+
function executeReadGraph(graph, input, db) {
|
|
1093
|
+
const component = graph.ir.components[0];
|
|
1094
|
+
const body = component.body;
|
|
1095
|
+
const output = component.output ?? null;
|
|
1096
|
+
const normalized = normalizeReadInput(graph, input);
|
|
1004
1097
|
try {
|
|
1005
|
-
|
|
1098
|
+
const results = [];
|
|
1099
|
+
for (const stage of planStages(component)) {
|
|
1100
|
+
// Commit stage members in ascending body index (deterministic failure precedence).
|
|
1101
|
+
const ordered = [...stage].sort((a, b) => a - b);
|
|
1102
|
+
const base = { ...normalized };
|
|
1103
|
+
for (const [id, v] of results)
|
|
1104
|
+
base[id] = v;
|
|
1105
|
+
for (const idx of ordered) {
|
|
1106
|
+
const node = body[idx];
|
|
1107
|
+
base[node.id] = results[results.push([node.id, computeReadNodeSync(graph, node, base, db)]) - 1][1];
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
const scope = { ...normalized };
|
|
1111
|
+
for (const [id, v] of results)
|
|
1112
|
+
scope[id] = v;
|
|
1113
|
+
return (0, behavior_contracts_1.evaluateExpression)(output, scope);
|
|
1006
1114
|
}
|
|
1007
1115
|
catch (e) {
|
|
1008
1116
|
throw reErrorToSqlFailure(e);
|
|
1009
1117
|
}
|
|
1010
1118
|
}
|
|
1119
|
+
/** Render + execute a node's `statementsById` fragments against `scope`, async driver, with #59 de-box. */
|
|
1120
|
+
async function execReadNodeAsync(graph, nodeId, scope, exec) {
|
|
1121
|
+
const stmts = graph.statementsById[nodeId];
|
|
1122
|
+
if (stmts === undefined)
|
|
1123
|
+
throw new Error(`static-bundle: no statements for node '${nodeId}'`);
|
|
1124
|
+
const { sql, params } = renderStatements(stmts, graph.dialect, scope);
|
|
1125
|
+
try {
|
|
1126
|
+
const rows = await exec(sql, params);
|
|
1127
|
+
// #59 materializers: the pooled PG/MySQL drivers are configured so int8/BIGINT arrive as a string
|
|
1128
|
+
// (→ bigint) and BOOLEAN as a JS boolean; `materializeCell` coerces each to the SQL-type JS form.
|
|
1129
|
+
return materializeRows(rows, graph.materializersByNode?.[nodeId]);
|
|
1130
|
+
}
|
|
1131
|
+
catch (e) {
|
|
1132
|
+
throw (0, errors_1.mapSqliteError)(e);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
/** Async twin of {@link computeReadNodeSync}: dispatches a map stage's per-element queries in bounded
|
|
1136
|
+
* parallel (the live PG/MySQL fan-out, capped at `concurrency`); a componentRef runs one query; a
|
|
1137
|
+
* `cond` is pure. */
|
|
1138
|
+
async function computeReadNodeAsync(graph, node, base, exec, concurrency) {
|
|
1139
|
+
if (node.cond !== undefined)
|
|
1140
|
+
return (0, behavior_contracts_1.evaluateExpression)(node.cond, base);
|
|
1141
|
+
if (node.map !== undefined) {
|
|
1142
|
+
const over = (0, behavior_contracts_1.evaluateExpression)(node.map.over, base);
|
|
1143
|
+
if (!Array.isArray(over))
|
|
1144
|
+
throw new Error(`static-bundle: map '${node.id}': 'over' is not an array`);
|
|
1145
|
+
const asName = node.map.as ?? '$';
|
|
1146
|
+
return (await boundedMap(over, concurrency, (el) => execReadNodeAsync(graph, node.id, { ...base, [asName]: el }, exec)));
|
|
1147
|
+
}
|
|
1148
|
+
return execReadNodeAsync(graph, node.id, base, exec);
|
|
1149
|
+
}
|
|
1011
1150
|
/**
|
|
1012
|
-
* Async twin of {@link executeReadGraph} — the PG / MySQL execution model (#40).
|
|
1013
|
-
*
|
|
1014
|
-
*
|
|
1015
|
-
* `
|
|
1016
|
-
*
|
|
1017
|
-
*
|
|
1018
|
-
* each `exec` call resolving on its own pooled connection), that becomes REAL parallel read-relation
|
|
1019
|
-
* DB I/O — the plan's declared concurrency finally cashes out as concurrent dispatch. The result is
|
|
1020
|
-
* deterministic (bc commits stage outcomes in declaration order), so it equals the serial
|
|
1021
|
-
* `executeReadGraph` output exactly; only the wall-clock changes.
|
|
1022
|
-
*
|
|
1023
|
-
* The conformance bar stays on the SYNC in-proc better-sqlite3 path (`executeReadGraph`); this async
|
|
1024
|
-
* path is the live PG/MySQL execution model and is proven by the latency-injecting concurrency test.
|
|
1151
|
+
* Async twin of {@link executeReadGraph} — the live PG / MySQL execution model (#40). Byte-identical
|
|
1152
|
+
* composition (SAME real-node IR, SAME per-node SQL text + params, SAME Φ output); within a plan stage
|
|
1153
|
+
* the INDEPENDENT sibling nodes are dispatched concurrently against the pooled async executor (each
|
|
1154
|
+
* `exec` resolving on its own pooled connection), and a map node's per-element queries fan out in
|
|
1155
|
+
* bounded parallel. The result is deterministic (stage outcomes commit in declaration order), so it
|
|
1156
|
+
* equals the serial `executeReadGraph` output exactly; only the wall-clock changes.
|
|
1025
1157
|
*/
|
|
1026
1158
|
async function executeReadGraphAsync(graph, input, exec) {
|
|
1027
|
-
const
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
if (scope === null || typeof scope !== 'object' || Array.isArray(scope)) {
|
|
1033
|
-
return { error: `static-bundle: node '${ctx.nodeId}' surrogate scope did not evaluate to an object` };
|
|
1034
|
-
}
|
|
1035
|
-
const { sql, params } = renderStatements(stmts, graph.dialect, scope);
|
|
1036
|
-
try {
|
|
1037
|
-
const rows = await exec(sql, params);
|
|
1038
|
-
return { ok: rows };
|
|
1039
|
-
}
|
|
1040
|
-
catch (e) {
|
|
1041
|
-
return { error: (0, errors_1.mapSqliteError)(e).message };
|
|
1042
|
-
}
|
|
1043
|
-
};
|
|
1044
|
-
const handlers = { [NODE_COMPONENT]: handle };
|
|
1045
|
-
const normalized = { ...input };
|
|
1046
|
-
for (const head of graph.optionalHeads)
|
|
1047
|
-
if (!(head in normalized))
|
|
1048
|
-
normalized[head] = null;
|
|
1159
|
+
const component = graph.ir.components[0];
|
|
1160
|
+
const body = component.body;
|
|
1161
|
+
const output = component.output ?? null;
|
|
1162
|
+
const concurrency = planConcurrency(component);
|
|
1163
|
+
const normalized = normalizeReadInput(graph, input);
|
|
1049
1164
|
try {
|
|
1050
|
-
|
|
1165
|
+
const results = [];
|
|
1166
|
+
for (const stage of planStages(component)) {
|
|
1167
|
+
const ordered = [...stage].sort((a, b) => a - b);
|
|
1168
|
+
const base = { ...normalized };
|
|
1169
|
+
for (const [id, v] of results)
|
|
1170
|
+
base[id] = v;
|
|
1171
|
+
// Independent siblings of a stage run in bounded parallel (capped at the plan concurrency;
|
|
1172
|
+
// concurrency ≤ 1 ⇒ serial); committed in ascending body index order for determinism.
|
|
1173
|
+
const vals = await boundedMap(ordered, concurrency, (idx) => computeReadNodeAsync(graph, body[idx], base, exec, concurrency));
|
|
1174
|
+
ordered.forEach((idx, i) => results.push([body[idx].id, vals[i]]));
|
|
1175
|
+
}
|
|
1176
|
+
const scope = { ...normalized };
|
|
1177
|
+
for (const [id, v] of results)
|
|
1178
|
+
scope[id] = v;
|
|
1179
|
+
return (0, behavior_contracts_1.evaluateExpression)(output, scope);
|
|
1051
1180
|
}
|
|
1052
1181
|
catch (e) {
|
|
1053
1182
|
throw reErrorToSqlFailure(e);
|
|
@@ -1069,7 +1198,7 @@ function executeReadBehavior(contract, input, dialect, db, entry) {
|
|
|
1069
1198
|
*/
|
|
1070
1199
|
function renderReadPrimary(graph, input) {
|
|
1071
1200
|
const ids = Object.keys(graph.statementsById);
|
|
1072
|
-
// The primary node is the first body node in the
|
|
1201
|
+
// The primary node is the first body node in the real-node IR order (map nodes reference it).
|
|
1073
1202
|
const bodyIds = graph.ir.components[0].body.map((n) => n.id).filter((id) => ids.includes(id));
|
|
1074
1203
|
const primaryId = bodyIds[0];
|
|
1075
1204
|
if (primaryId === undefined)
|
|
@@ -1092,6 +1221,8 @@ function optionalHeadsOfComponent(component, dialect) {
|
|
|
1092
1221
|
for (const n of component.body) {
|
|
1093
1222
|
if ('cond' in n)
|
|
1094
1223
|
continue;
|
|
1224
|
+
if (isFanout(n))
|
|
1225
|
+
continue; // bc FanoutNode: litedbmodel never emits it; carries no ref-opt heads.
|
|
1095
1226
|
const stmts = compileNodeStatements(n, dialect);
|
|
1096
1227
|
for (const stmt of stmts) {
|
|
1097
1228
|
for (const p of stmt.params)
|