litedbmodel 2.0.1 → 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 +6 -2
- package/dist/scp/catalog.d.ts.map +1 -1
- package/dist/scp/catalog.js +17 -2
- package/dist/scp/catalog.js.map +1 -1
- package/dist/scp/codegen.d.ts +132 -26
- package/dist/scp/codegen.d.ts.map +1 -1
- package/dist/scp/codegen.js +546 -35
- package/dist/scp/codegen.js.map +1 -1
- package/dist/scp/coltype.d.ts +124 -0
- package/dist/scp/coltype.d.ts.map +1 -0
- package/dist/scp/coltype.js +408 -0
- package/dist/scp/coltype.js.map +1 -0
- package/dist/scp/dialect.d.ts +12 -3
- package/dist/scp/dialect.d.ts.map +1 -1
- package/dist/scp/dialect.js +12 -3
- package/dist/scp/dialect.js.map +1 -1
- package/dist/scp/fragment/assemble.d.ts.map +1 -1
- package/dist/scp/fragment/assemble.js +10 -2
- package/dist/scp/fragment/assemble.js.map +1 -1
- package/dist/scp/fragment/compile-pg.js +4 -4
- 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 +10003 -4917
- package/dist/scp/index.d.ts +8 -5
- package/dist/scp/index.d.ts.map +1 -1
- package/dist/scp/index.js +35 -9
- package/dist/scp/index.js.map +1 -1
- package/dist/scp/index.mjs +1343 -213
- 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/compile-select.d.ts +8 -1
- package/dist/scp/makesql/compile-select.d.ts.map +1 -1
- package/dist/scp/makesql/compile-select.js +1 -1
- package/dist/scp/makesql/compile-select.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 +99 -0
- package/dist/scp/makesql/outtype.d.ts.map +1 -0
- package/dist/scp/makesql/outtype.js +328 -0
- package/dist/scp/makesql/outtype.js.map +1 -0
- 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 +41 -30
- package/dist/scp/makesql/static-bundle.d.ts.map +1 -1
- package/dist/scp/makesql/static-bundle.js +351 -131
- package/dist/scp/makesql/static-bundle.js.map +1 -1
- package/dist/scp/makesql/tx.d.ts +1 -1
- package/dist/scp/makesql/tx.d.ts.map +1 -1
- package/dist/scp/makesql/tx.js +48 -3
- package/dist/scp/makesql/tx.js.map +1 -1
- package/dist/scp/makesql/writeouttype.d.ts +65 -0
- package/dist/scp/makesql/writeouttype.d.ts.map +1 -0
- package/dist/scp/makesql/writeouttype.js +194 -0
- package/dist/scp/makesql/writeouttype.js.map +1 -0
- 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 +17 -5
- package/dist/scp/runtime.d.ts.map +1 -1
- package/dist/scp/runtime.js +55 -14
- package/dist/scp/runtime.js.map +1 -1
- package/docs/BENCHMARK.md +58 -56
- package/docs/benchmark-chart.svg +166 -166
- package/docs/proposal/litedbmodel-v2-scp-architecture.md +51 -0
- 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
package/dist/scp/index.mjs
CHANGED
|
@@ -238,6 +238,9 @@ var DBCastArray = class extends DBToken {
|
|
|
238
238
|
function dbCast(value, sqlType, operator = "=") {
|
|
239
239
|
return new DBCast(value, sqlType, operator);
|
|
240
240
|
}
|
|
241
|
+
function dbCastIn(values, sqlType) {
|
|
242
|
+
return new DBCastArray(values, sqlType);
|
|
243
|
+
}
|
|
241
244
|
function dbTupleIn(columns, tuples) {
|
|
242
245
|
return new DBTupleIn(columns, tuples);
|
|
243
246
|
}
|
|
@@ -801,7 +804,7 @@ function condsToRecord(conditions) {
|
|
|
801
804
|
// src/scp/makesql/compile-select.ts
|
|
802
805
|
function compileSelect(desc) {
|
|
803
806
|
const params = [];
|
|
804
|
-
const selectCols = desc.select ?? "*";
|
|
807
|
+
const selectCols = desc.select || (desc.selectColumn ?? "*");
|
|
805
808
|
const formatter = formatterFor(desc.dialect);
|
|
806
809
|
if (desc.cte?.params && desc.cte.params.length > 0) params.push(...desc.cte.params);
|
|
807
810
|
if (desc.joinParams && desc.joinParams.length > 0) params.push(...desc.joinParams);
|
|
@@ -1641,7 +1644,7 @@ function sqliteInsertJson(opts) {
|
|
|
1641
1644
|
} else if (onConflict && onConflictUpdate) {
|
|
1642
1645
|
const updateCols = onConflictUpdate === "all" ? columns : onConflictUpdate;
|
|
1643
1646
|
const updateClauses = updateCols.map((c) => `${c} = excluded.${c}`);
|
|
1644
|
-
sql2 = `INSERT INTO ${tableName} (${columns.join(", ")}) ${source} ON CONFLICT (${onConflict.join(", ")}) DO UPDATE SET ${updateClauses.join(", ")}`;
|
|
1647
|
+
sql2 = `INSERT INTO ${tableName} (${columns.join(", ")}) ${source} WHERE true ON CONFLICT (${onConflict.join(", ")}) DO UPDATE SET ${updateClauses.join(", ")}`;
|
|
1645
1648
|
} else {
|
|
1646
1649
|
sql2 = `INSERT INTO ${tableName} (${columns.join(", ")}) ${source}`;
|
|
1647
1650
|
}
|
|
@@ -2089,38 +2092,6 @@ function compileCompositeKeyStaticLimited(opts) {
|
|
|
2089
2092
|
});
|
|
2090
2093
|
}
|
|
2091
2094
|
|
|
2092
|
-
// src/scp/makesql/ir.ts
|
|
2093
|
-
function makeSqlComponentIR(entry2 = "Query") {
|
|
2094
|
-
const component = {
|
|
2095
|
-
name: entry2,
|
|
2096
|
-
inputPorts: {
|
|
2097
|
-
__sql: { type: "string", required: true },
|
|
2098
|
-
__sqlParams: { type: "arr" },
|
|
2099
|
-
__skip: { type: "bool" }
|
|
2100
|
-
},
|
|
2101
|
-
body: [
|
|
2102
|
-
{
|
|
2103
|
-
id: "q",
|
|
2104
|
-
component: MAKESQL,
|
|
2105
|
-
ports: {
|
|
2106
|
-
sql: { ref: ["__sql"] },
|
|
2107
|
-
params: { ref: ["__sqlParams"] },
|
|
2108
|
-
skip: { ref: ["__skip"] }
|
|
2109
|
-
}
|
|
2110
|
-
}
|
|
2111
|
-
],
|
|
2112
|
-
output: { ref: ["q"] }
|
|
2113
|
-
};
|
|
2114
|
-
return { irVersion: 1, exprVersion: 2, components: [component] };
|
|
2115
|
-
}
|
|
2116
|
-
function makeSqlInput(node) {
|
|
2117
|
-
return {
|
|
2118
|
-
__sql: node.sql,
|
|
2119
|
-
__sqlParams: node.params,
|
|
2120
|
-
__skip: node.skip === true
|
|
2121
|
-
};
|
|
2122
|
-
}
|
|
2123
|
-
|
|
2124
2095
|
// src/scp/makesql/tx.ts
|
|
2125
2096
|
import { evaluateExpression } from "behavior-contracts";
|
|
2126
2097
|
|
|
@@ -2384,6 +2355,23 @@ function collectFamily(ports, prefix) {
|
|
|
2384
2355
|
}
|
|
2385
2356
|
return out;
|
|
2386
2357
|
}
|
|
2358
|
+
function collectSqlCast(ports) {
|
|
2359
|
+
const map = /* @__PURE__ */ new Map();
|
|
2360
|
+
for (const k of Object.keys(ports)) {
|
|
2361
|
+
if (!k.startsWith("sqlCast.")) continue;
|
|
2362
|
+
const v = ports[k];
|
|
2363
|
+
if (typeof v !== "string") {
|
|
2364
|
+
throw new Error(`compileWriteNode: port '${k}' (a sqlCast type) must be a literal string in the IR (got ${JSON.stringify(v)})`);
|
|
2365
|
+
}
|
|
2366
|
+
map.set(k.slice("sqlCast.".length), v);
|
|
2367
|
+
}
|
|
2368
|
+
return map;
|
|
2369
|
+
}
|
|
2370
|
+
function castPlaceholder(dialect, sqlCastMap, column) {
|
|
2371
|
+
const sqlCast = sqlCastMap.get(column);
|
|
2372
|
+
if (dialect !== "postgres" || sqlCast === void 0 || sqlCast === "timestamp" || sqlCast === "date") return "?";
|
|
2373
|
+
return formatterFor("postgres")("?", sqlCast);
|
|
2374
|
+
}
|
|
2387
2375
|
function opKey(node) {
|
|
2388
2376
|
if (node === null || typeof node !== "object" || Array.isArray(node)) return void 0;
|
|
2389
2377
|
const keys = Object.keys(node);
|
|
@@ -2469,17 +2457,18 @@ function mysqlPkHint(op) {
|
|
|
2469
2457
|
function stripMysqlPkHint(sql2) {
|
|
2470
2458
|
return sql2.replace(MYSQL_PK_HINT_RE, "");
|
|
2471
2459
|
}
|
|
2472
|
-
function compileWriteNode(node) {
|
|
2460
|
+
function compileWriteNode(node, dialect = "sqlite") {
|
|
2473
2461
|
const { component, ports } = node;
|
|
2474
2462
|
const table = stringPort(ports, "table");
|
|
2475
2463
|
if (table === void 0) throw new Error(`compileWriteNode: ${component} node requires a literal 'table' port`);
|
|
2464
|
+
const sqlCastMap = collectSqlCast(ports);
|
|
2476
2465
|
switch (component) {
|
|
2477
2466
|
case "Insert": {
|
|
2478
2467
|
const values = collectFamily(ports, "values");
|
|
2479
2468
|
const cols = Object.keys(values);
|
|
2480
2469
|
if (cols.length === 0) throw new Error(`compileWriteNode: Insert requires at least one 'values.<field>' port`);
|
|
2481
2470
|
const sorted = [...cols].sort();
|
|
2482
|
-
const placeholders = sorted.map(() =>
|
|
2471
|
+
const placeholders = sorted.map((c) => castPlaceholder(dialect, sqlCastMap, c)).join(", ");
|
|
2483
2472
|
const sql2 = `INSERT INTO ${table} (${sorted.join(", ")}) VALUES (${placeholders})${returningTail(ports)}`;
|
|
2484
2473
|
const pk = pkPort(ports);
|
|
2485
2474
|
return { sql: sql2, params: sorted.map((c) => values[c]), ...pk !== void 0 ? { pk } : {} };
|
|
@@ -2490,7 +2479,7 @@ function compileWriteNode(node) {
|
|
|
2490
2479
|
if (setCols.length === 0) throw new Error(`compileWriteNode: Update requires at least one 'set.<field>' port`);
|
|
2491
2480
|
const where = lowerWherePort(ports, "Update");
|
|
2492
2481
|
if (where.sql === "") throw new Error(`compileWriteNode: Update requires a 'where' port`);
|
|
2493
|
-
const setClauses = setCols.map((c) => `${c} =
|
|
2482
|
+
const setClauses = setCols.map((c) => `${c} = ${castPlaceholder(dialect, sqlCastMap, c)}`).join(", ");
|
|
2494
2483
|
const sql2 = `UPDATE ${table} SET ${setClauses} WHERE ${where.sql}${returningTail(ports)}`;
|
|
2495
2484
|
return { sql: sql2, params: [...setCols.map((c) => set[c]), ...where.params] };
|
|
2496
2485
|
}
|
|
@@ -2724,6 +2713,8 @@ function gateShortCircuit(gate, result) {
|
|
|
2724
2713
|
return result.changes === 0 ? "unique_collision" : null;
|
|
2725
2714
|
case "insertedElseNoop":
|
|
2726
2715
|
return result.changes === 0 ? "idempotent_duplicate" : null;
|
|
2716
|
+
default:
|
|
2717
|
+
throw new Error(`scp write: unknown gate rule '${String(gate)}'`);
|
|
2727
2718
|
}
|
|
2728
2719
|
}
|
|
2729
2720
|
function executeTransaction(db, plan, input, dialect = "sqlite") {
|
|
@@ -2782,6 +2773,9 @@ import { evaluateExpression as evaluateExpression2 } from "behavior-contracts";
|
|
|
2782
2773
|
function isMap(n) {
|
|
2783
2774
|
return "map" in n;
|
|
2784
2775
|
}
|
|
2776
|
+
function isFanout(n) {
|
|
2777
|
+
return "fanout" in n;
|
|
2778
|
+
}
|
|
2785
2779
|
function nodeRef(n) {
|
|
2786
2780
|
return isMap(n) ? { component: n.map.component, ports: n.map.ports } : { component: n.component, ports: n.ports };
|
|
2787
2781
|
}
|
|
@@ -3020,6 +3014,9 @@ function compileAuthoredBehavior(contract, scope, dialect, entry2) {
|
|
|
3020
3014
|
let primaryNode;
|
|
3021
3015
|
for (const n of component.body) {
|
|
3022
3016
|
if ("cond" in n) continue;
|
|
3017
|
+
if (isFanout(n)) {
|
|
3018
|
+
throw new Error(`authoring-compile: behavior '${component.name}' node '${n.id}' is a fanout node, not supported by litedbmodel (bc FanoutNode)`);
|
|
3019
|
+
}
|
|
3023
3020
|
if (isMap(n)) {
|
|
3024
3021
|
relations.push(n);
|
|
3025
3022
|
continue;
|
|
@@ -3050,6 +3047,7 @@ function normalizeInput(component, input) {
|
|
|
3050
3047
|
}
|
|
3051
3048
|
function skipGuardHeads(n, into) {
|
|
3052
3049
|
if ("cond" in n) return;
|
|
3050
|
+
if (isFanout(n)) return;
|
|
3053
3051
|
const ports = isMap(n) ? n.map.ports : n.ports;
|
|
3054
3052
|
const where = ports.where;
|
|
3055
3053
|
if (typeof where !== "object" || where === null || !("arr" in where)) return;
|
|
@@ -3079,11 +3077,483 @@ function collectRefHeads2(node, into) {
|
|
|
3079
3077
|
|
|
3080
3078
|
// src/scp/makesql/static-bundle.ts
|
|
3081
3079
|
import {
|
|
3082
|
-
evaluateExpression as evaluateExpression3
|
|
3083
|
-
runBehavior,
|
|
3084
|
-
runBehaviorAsync
|
|
3080
|
+
evaluateExpression as evaluateExpression3
|
|
3085
3081
|
} from "behavior-contracts";
|
|
3086
3082
|
|
|
3083
|
+
// src/scp/find-filter-guard.ts
|
|
3084
|
+
var FindFilterLeakError = class extends Error {
|
|
3085
|
+
constructor(modelName, missingKeys) {
|
|
3086
|
+
super(
|
|
3087
|
+
`FIND_FILTER leak: model '${modelName}' declares an implicit scope predicate on [${missingKeys.join(", ")}] that is NOT present in the SCP-authored WHERE. The SCP compile cannot auto-apply FIND_FILTER (no model context at compile time), so the scope predicate would be silently dropped (soft-delete / tenant scope leak). Fold the FIND_FILTER predicates into the authored L.Select({ where }) explicitly (e.g. whereEq/whereIsNull on ${missingKeys.join(", ")}).`
|
|
3088
|
+
);
|
|
3089
|
+
this.modelName = modelName;
|
|
3090
|
+
this.missingKeys = missingKeys;
|
|
3091
|
+
this.name = "FindFilterLeakError";
|
|
3092
|
+
}
|
|
3093
|
+
modelName;
|
|
3094
|
+
missingKeys;
|
|
3095
|
+
};
|
|
3096
|
+
function findFilterKeys(filter) {
|
|
3097
|
+
if (!filter || filter.length === 0) return [];
|
|
3098
|
+
return Object.keys(condsToRecord(filter));
|
|
3099
|
+
}
|
|
3100
|
+
function assertFindFilterFolded(model, authoredWhereKeys) {
|
|
3101
|
+
const required = findFilterKeys(model.FIND_FILTER);
|
|
3102
|
+
if (required.length === 0) return;
|
|
3103
|
+
const present = new Set(authoredWhereKeys);
|
|
3104
|
+
const missing = required.filter((k) => !present.has(k));
|
|
3105
|
+
if (missing.length > 0) {
|
|
3106
|
+
throw new FindFilterLeakError(model.name ?? "<anonymous model>", missing);
|
|
3107
|
+
}
|
|
3108
|
+
}
|
|
3109
|
+
|
|
3110
|
+
// src/scp/coltype.ts
|
|
3111
|
+
function sqlTypeToBcScalar(sqlType) {
|
|
3112
|
+
const t = sqlType.trim().toUpperCase().replace(/\(.*\)/, "").replace(/\b(UNSIGNED|ZEROFILL|PRECISION)\b/g, "").replace(/\s+/g, " ").trim();
|
|
3113
|
+
switch (t) {
|
|
3114
|
+
// int は既定 64bit(i64)。INTEGER も BIGINT も int(bigint を別型にしない — サイズ制限は列制約で表現)。
|
|
3115
|
+
case "INTEGER":
|
|
3116
|
+
case "INT":
|
|
3117
|
+
case "BIGINT":
|
|
3118
|
+
case "SMALLINT":
|
|
3119
|
+
case "TINYINT":
|
|
3120
|
+
case "MEDIUMINT":
|
|
3121
|
+
case "INT2":
|
|
3122
|
+
case "INT4":
|
|
3123
|
+
case "INT8":
|
|
3124
|
+
return "int";
|
|
3125
|
+
// int と real は明確に分離。
|
|
3126
|
+
case "REAL":
|
|
3127
|
+
case "FLOAT":
|
|
3128
|
+
case "DOUBLE":
|
|
3129
|
+
case "FLOAT4":
|
|
3130
|
+
case "FLOAT8":
|
|
3131
|
+
return "float";
|
|
3132
|
+
// decimal は精度保持のため文字列表現。
|
|
3133
|
+
case "DECIMAL":
|
|
3134
|
+
case "NUMERIC":
|
|
3135
|
+
case "MONEY":
|
|
3136
|
+
return "string";
|
|
3137
|
+
case "TEXT":
|
|
3138
|
+
case "VARCHAR":
|
|
3139
|
+
case "CHAR":
|
|
3140
|
+
case "CHARACTER":
|
|
3141
|
+
case "CHARACTER VARYING":
|
|
3142
|
+
case "CLOB":
|
|
3143
|
+
case "UUID":
|
|
3144
|
+
return "string";
|
|
3145
|
+
case "BOOLEAN":
|
|
3146
|
+
case "BOOL":
|
|
3147
|
+
return "bool";
|
|
3148
|
+
// date/timestamp は一旦 string(spec §4.1 owner 決定)。bc に date scalar が無く(behavior-contracts#84
|
|
3149
|
+
// は defer)、DB は行ごとに別 TZ を保存しないので、文字列表現で往復させる。将来 bc date 型導入時に最適化。
|
|
3150
|
+
case "DATE":
|
|
3151
|
+
case "TIMESTAMP":
|
|
3152
|
+
case "TIMESTAMPTZ":
|
|
3153
|
+
case "DATETIME":
|
|
3154
|
+
case "TIME":
|
|
3155
|
+
return "string";
|
|
3156
|
+
// JSON 列の表現は JSON テキスト=文字列(TS のみ利便で object へ de/serialize。列を typed obj 化しない)。
|
|
3157
|
+
case "JSON":
|
|
3158
|
+
case "JSONB":
|
|
3159
|
+
return "string";
|
|
3160
|
+
default:
|
|
3161
|
+
throw new Error(
|
|
3162
|
+
`litedbmodel type system (spec \xA74.1): no bc outType mapping for SQL type '${sqlType}' (normalized '${t}'). Add it to the \xA74.1 table or fix the column \u2014 ambiguous/unknown types are a hard error (no-assume, no-fallback), never defaulted.`
|
|
3163
|
+
);
|
|
3164
|
+
}
|
|
3165
|
+
}
|
|
3166
|
+
function sqlTypeToMaterializeClass(sqlType) {
|
|
3167
|
+
const t = sqlType.trim().toUpperCase().replace(/\(.*\)/, "").replace(/\b(UNSIGNED|ZEROFILL|PRECISION)\b/g, "").replace(/\s+/g, " ").trim();
|
|
3168
|
+
switch (t) {
|
|
3169
|
+
// 32-bit int family: JS number holds the full range exactly.
|
|
3170
|
+
case "INTEGER":
|
|
3171
|
+
case "INT":
|
|
3172
|
+
case "SMALLINT":
|
|
3173
|
+
case "TINYINT":
|
|
3174
|
+
case "MEDIUMINT":
|
|
3175
|
+
case "INT2":
|
|
3176
|
+
case "INT4":
|
|
3177
|
+
return "int32";
|
|
3178
|
+
// 64-bit int: needs JS bigint for exactness.
|
|
3179
|
+
case "BIGINT":
|
|
3180
|
+
case "INT8":
|
|
3181
|
+
return "int64";
|
|
3182
|
+
case "BOOLEAN":
|
|
3183
|
+
case "BOOL":
|
|
3184
|
+
return "bool";
|
|
3185
|
+
case "DATE":
|
|
3186
|
+
case "TIMESTAMP":
|
|
3187
|
+
case "TIMESTAMPTZ":
|
|
3188
|
+
case "DATETIME":
|
|
3189
|
+
case "TIME":
|
|
3190
|
+
return "date";
|
|
3191
|
+
// float / decimal(→string) / text / json / uuid — driver value already matches the outType.
|
|
3192
|
+
case "REAL":
|
|
3193
|
+
case "FLOAT":
|
|
3194
|
+
case "DOUBLE":
|
|
3195
|
+
case "FLOAT4":
|
|
3196
|
+
case "FLOAT8":
|
|
3197
|
+
case "DECIMAL":
|
|
3198
|
+
case "NUMERIC":
|
|
3199
|
+
case "MONEY":
|
|
3200
|
+
case "TEXT":
|
|
3201
|
+
case "VARCHAR":
|
|
3202
|
+
case "CHAR":
|
|
3203
|
+
case "CHARACTER":
|
|
3204
|
+
case "CHARACTER VARYING":
|
|
3205
|
+
case "CLOB":
|
|
3206
|
+
case "UUID":
|
|
3207
|
+
case "JSON":
|
|
3208
|
+
case "JSONB":
|
|
3209
|
+
return "passthrough";
|
|
3210
|
+
default:
|
|
3211
|
+
throw new Error(
|
|
3212
|
+
`litedbmodel type system (spec \xA74.1): no materialization class for SQL type '${sqlType}' (normalized '${t}'). Add it to the \xA74.1 table or fix the column \u2014 ambiguous/unknown types are a hard error (no-assume, no-fallback), never defaulted.`
|
|
3213
|
+
);
|
|
3214
|
+
}
|
|
3215
|
+
}
|
|
3216
|
+
function materializeCell(value, klass) {
|
|
3217
|
+
if (value === null || value === void 0) return value;
|
|
3218
|
+
switch (klass) {
|
|
3219
|
+
case "int64": {
|
|
3220
|
+
if (typeof value === "bigint") return value.toString();
|
|
3221
|
+
if (typeof value === "string") {
|
|
3222
|
+
if (!/^-?\d+$/.test(value)) throw new Error(`materialize int64: driver returned a non-integer string '${value}'`);
|
|
3223
|
+
return value;
|
|
3224
|
+
}
|
|
3225
|
+
if (typeof value === "number") {
|
|
3226
|
+
if (!Number.isInteger(value)) throw new Error(`materialize int64: driver returned a non-integer number ${value} for a 64-bit int column`);
|
|
3227
|
+
if (!Number.isSafeInteger(value)) {
|
|
3228
|
+
throw new Error(
|
|
3229
|
+
`materialize int64: driver returned an UNSAFE JS number ${value} for a 64-bit int column \u2014 precision was already lost before materialization (the driver must return int8 as string/bigint, not a rounded double). Configure the driver (better-sqlite3 safeIntegers / mysql2 supportBigNumbers+bigNumberStrings / pg int8-as-string).`
|
|
3230
|
+
);
|
|
3231
|
+
}
|
|
3232
|
+
return value.toString();
|
|
3233
|
+
}
|
|
3234
|
+
throw new Error(`materialize int64: unexpected driver JS type ${typeof value}`);
|
|
3235
|
+
}
|
|
3236
|
+
case "int32": {
|
|
3237
|
+
if (typeof value === "number") return value;
|
|
3238
|
+
if (typeof value === "bigint") return Number(value);
|
|
3239
|
+
if (typeof value === "string" && /^-?\d+$/.test(value)) return Number(value);
|
|
3240
|
+
throw new Error(`materialize int32: unexpected driver JS type ${typeof value} (${String(value)})`);
|
|
3241
|
+
}
|
|
3242
|
+
case "date": {
|
|
3243
|
+
if (typeof value === "string") return value;
|
|
3244
|
+
if (value instanceof Date) return dateToTzString(value);
|
|
3245
|
+
throw new Error(`materialize date: unexpected driver JS type ${typeof value}`);
|
|
3246
|
+
}
|
|
3247
|
+
case "bool": {
|
|
3248
|
+
if (typeof value === "boolean") return value;
|
|
3249
|
+
if (typeof value === "number") return value !== 0;
|
|
3250
|
+
if (typeof value === "bigint") return value !== 0n;
|
|
3251
|
+
if (typeof value === "string") {
|
|
3252
|
+
const s = value.trim().toLowerCase();
|
|
3253
|
+
if (s === "t" || s === "true" || s === "1") return true;
|
|
3254
|
+
if (s === "f" || s === "false" || s === "0") return false;
|
|
3255
|
+
}
|
|
3256
|
+
throw new Error(`materialize bool: unexpected driver JS type ${typeof value} (${String(value)})`);
|
|
3257
|
+
}
|
|
3258
|
+
case "passthrough":
|
|
3259
|
+
return value;
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
function dateToTzString(d) {
|
|
3263
|
+
return d.toISOString();
|
|
3264
|
+
}
|
|
3265
|
+
function materializeResolverFromColumnMap(columnTypes) {
|
|
3266
|
+
return (table, column) => {
|
|
3267
|
+
const sqlType = columnTypes.get(table)?.get(column);
|
|
3268
|
+
return sqlType === void 0 ? void 0 : materializeClassOrUndefined(sqlType);
|
|
3269
|
+
};
|
|
3270
|
+
}
|
|
3271
|
+
function failClosedMaterializeResolverFromColumnMap(columnTypes) {
|
|
3272
|
+
const resolveSqlType = columnTypeResolverFromColumnMap(columnTypes);
|
|
3273
|
+
return (table, column) => {
|
|
3274
|
+
const sqlType = resolveSqlType(table, column);
|
|
3275
|
+
return sqlTypeToMaterializeClass(sqlType);
|
|
3276
|
+
};
|
|
3277
|
+
}
|
|
3278
|
+
function columnTypeResolverFromColumnMap(columnTypes) {
|
|
3279
|
+
return (table, column) => {
|
|
3280
|
+
const cols = columnTypes.get(table);
|
|
3281
|
+
if (cols === void 0) {
|
|
3282
|
+
throw new Error(
|
|
3283
|
+
`litedbmodel type system (spec \xA74.1): table '${table}' has no inline column-type declaration (the model's \`columns\` map). Declare it (\`static columns = { ${table}: { \u2026 } }\`) so its SELECT projection can be typed. Known tables: ${[...columnTypes.keys()].join(", ") || "<none>"}. No-assume, no-fallback.`
|
|
3284
|
+
);
|
|
3285
|
+
}
|
|
3286
|
+
const sqlType = cols.get(column);
|
|
3287
|
+
if (sqlType === void 0) {
|
|
3288
|
+
throw new Error(
|
|
3289
|
+
`litedbmodel type system (spec \xA74.1): column '${column}' not declared on table '${table}' (declared: ${[...cols.keys()].join(", ")}). Add it to the model's inline \`columns\`. No-assume, no-fallback.`
|
|
3290
|
+
);
|
|
3291
|
+
}
|
|
3292
|
+
return sqlType;
|
|
3293
|
+
};
|
|
3294
|
+
}
|
|
3295
|
+
function materializeClassOrUndefined(sqlType) {
|
|
3296
|
+
try {
|
|
3297
|
+
return sqlTypeToMaterializeClass(sqlType);
|
|
3298
|
+
} catch {
|
|
3299
|
+
return void 0;
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
function parseSchemaColumnTypes(ddl) {
|
|
3303
|
+
const tables = /* @__PURE__ */ new Map();
|
|
3304
|
+
const CREATE = /^\s*CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?([A-Za-z0-9_."`[\]]+)\s*\(([\s\S]*)\)\s*;?\s*$/i;
|
|
3305
|
+
const unquote = (s) => s.replace(/^["`[]|["`\]]$/g, "");
|
|
3306
|
+
const TABLE_CONSTRAINT = /^(PRIMARY|FOREIGN|UNIQUE|CHECK|CONSTRAINT|KEY|INDEX)\b/i;
|
|
3307
|
+
for (const stmt of ddl) {
|
|
3308
|
+
const m = CREATE.exec(stmt);
|
|
3309
|
+
if (m === null) continue;
|
|
3310
|
+
const table = unquote(m[1].trim());
|
|
3311
|
+
const cols = /* @__PURE__ */ new Map();
|
|
3312
|
+
for (const entry2 of splitTopLevel(m[2])) {
|
|
3313
|
+
const line = entry2.trim();
|
|
3314
|
+
if (line.length === 0) continue;
|
|
3315
|
+
if (TABLE_CONSTRAINT.test(line)) continue;
|
|
3316
|
+
const parsed = parseColumnDef(line);
|
|
3317
|
+
if (parsed === void 0) continue;
|
|
3318
|
+
cols.set(parsed.name, parsed.sqlType);
|
|
3319
|
+
}
|
|
3320
|
+
tables.set(table, cols);
|
|
3321
|
+
}
|
|
3322
|
+
return tables;
|
|
3323
|
+
}
|
|
3324
|
+
function splitTopLevel(body) {
|
|
3325
|
+
const parts = [];
|
|
3326
|
+
let depth = 0;
|
|
3327
|
+
let cur = "";
|
|
3328
|
+
for (const ch of body) {
|
|
3329
|
+
if (ch === "(") depth++;
|
|
3330
|
+
else if (ch === ")") depth--;
|
|
3331
|
+
if (ch === "," && depth === 0) {
|
|
3332
|
+
parts.push(cur);
|
|
3333
|
+
cur = "";
|
|
3334
|
+
} else cur += ch;
|
|
3335
|
+
}
|
|
3336
|
+
if (cur.trim().length > 0) parts.push(cur);
|
|
3337
|
+
return parts;
|
|
3338
|
+
}
|
|
3339
|
+
var TYPE_CONTINUATION = /^(VARYING|PRECISION)$/i;
|
|
3340
|
+
function parseColumnDef(line) {
|
|
3341
|
+
const unquote = (s) => s.replace(/^["`[]|["`\]]$/g, "");
|
|
3342
|
+
const nameMatch = /^([A-Za-z0-9_."`[\]]+)\s+(.*)$/.exec(line);
|
|
3343
|
+
if (nameMatch === null) return void 0;
|
|
3344
|
+
const name = unquote(nameMatch[1]);
|
|
3345
|
+
const rest = nameMatch[2].trim();
|
|
3346
|
+
const typeMatch = /^([A-Za-z0-9_]+(?:\s*\([^)]*\))?)\s*(.*)$/.exec(rest);
|
|
3347
|
+
if (typeMatch === null) return void 0;
|
|
3348
|
+
let sqlType = typeMatch[1].trim();
|
|
3349
|
+
const after = typeMatch[2].trim();
|
|
3350
|
+
const nextWord = /^([A-Za-z]+)/.exec(after);
|
|
3351
|
+
if (nextWord !== null && TYPE_CONTINUATION.test(nextWord[1])) sqlType = `${sqlType} ${nextWord[1]}`;
|
|
3352
|
+
if (sqlType.length === 0) return void 0;
|
|
3353
|
+
return { name, sqlType };
|
|
3354
|
+
}
|
|
3355
|
+
function schemaColumnTypeResolver(ddl) {
|
|
3356
|
+
const tables = parseSchemaColumnTypes(ddl);
|
|
3357
|
+
return (table, column) => {
|
|
3358
|
+
const cols = tables.get(table);
|
|
3359
|
+
if (cols === void 0) {
|
|
3360
|
+
throw new Error(
|
|
3361
|
+
`litedbmodel type system (spec \xA74.1): no schema for table '${table}' \u2014 cannot type its SELECT projection for typed codegen. The DDL (schema.sql SoT) must declare the table (known: ${[...tables.keys()].join(", ") || "<none>"}). No-assume, no-fallback.`
|
|
3362
|
+
);
|
|
3363
|
+
}
|
|
3364
|
+
const sqlType = cols.get(column);
|
|
3365
|
+
if (sqlType === void 0) {
|
|
3366
|
+
throw new Error(
|
|
3367
|
+
`litedbmodel type system (spec \xA74.1): column '${column}' not declared on table '${table}' (known columns: ${[...cols.keys()].join(", ")}). A projected column with no schema type cannot be typed for de-boxed codegen. No-assume, no-fallback.`
|
|
3368
|
+
);
|
|
3369
|
+
}
|
|
3370
|
+
return sqlType;
|
|
3371
|
+
};
|
|
3372
|
+
}
|
|
3373
|
+
|
|
3374
|
+
// src/scp/makesql/outtype.ts
|
|
3375
|
+
function toPortableScalar(scalar, _table, _column) {
|
|
3376
|
+
return scalar;
|
|
3377
|
+
}
|
|
3378
|
+
function isMap2(n) {
|
|
3379
|
+
return "map" in n;
|
|
3380
|
+
}
|
|
3381
|
+
function nodeRef2(n) {
|
|
3382
|
+
return isMap2(n) ? { component: n.map.component, ports: n.map.ports } : { component: n.component, ports: n.ports };
|
|
3383
|
+
}
|
|
3384
|
+
function opKey3(node) {
|
|
3385
|
+
if (node === null || typeof node !== "object" || Array.isArray(node)) return void 0;
|
|
3386
|
+
const keys = Object.keys(node);
|
|
3387
|
+
return keys.length === 1 ? keys[0] : void 0;
|
|
3388
|
+
}
|
|
3389
|
+
function stringPort3(ports, name) {
|
|
3390
|
+
const v = ports[name];
|
|
3391
|
+
if (v === void 0) return void 0;
|
|
3392
|
+
if (typeof v !== "string") {
|
|
3393
|
+
throw new Error(`outtype: port '${name}' must be a literal string in the IR (got ${JSON.stringify(v)})`);
|
|
3394
|
+
}
|
|
3395
|
+
return v;
|
|
3396
|
+
}
|
|
3397
|
+
function stringArrayPort2(ports, name) {
|
|
3398
|
+
const v = ports[name];
|
|
3399
|
+
if (v === void 0) return void 0;
|
|
3400
|
+
if (typeof v === "object" && v !== null && "arr" in v && Array.isArray(v.arr)) {
|
|
3401
|
+
return v.arr.map((e) => {
|
|
3402
|
+
if (typeof e !== "string") throw new Error(`outtype: '${name}' entries must be literal strings (got ${JSON.stringify(e)})`);
|
|
3403
|
+
return e;
|
|
3404
|
+
});
|
|
3405
|
+
}
|
|
3406
|
+
throw new Error(`outtype: port '${name}' must be an {arr:[\u2026]} literal in the IR (got ${JSON.stringify(v)})`);
|
|
3407
|
+
}
|
|
3408
|
+
function parseProjectionColumn(col, table, at) {
|
|
3409
|
+
const raw = col.trim();
|
|
3410
|
+
if (raw === "*" || raw.endsWith(".*")) {
|
|
3411
|
+
throw new Error(
|
|
3412
|
+
`outtype: ${at}: SELECT on '${table}' projects '${raw}' (a wildcard). A typed de-box needs the concrete column list to build the row struct + type each column \u2014 spec \xA74.1 is column-typed. Project explicit columns (no-assume, no-fallback).`
|
|
3413
|
+
);
|
|
3414
|
+
}
|
|
3415
|
+
let expr = raw;
|
|
3416
|
+
let alias;
|
|
3417
|
+
const asMatch = /^(.*?)\s+AS\s+([A-Za-z_][A-Za-z0-9_]*)$/i.exec(raw);
|
|
3418
|
+
if (asMatch !== null) {
|
|
3419
|
+
expr = asMatch[1].trim();
|
|
3420
|
+
alias = asMatch[2];
|
|
3421
|
+
}
|
|
3422
|
+
const refMatch = /^(?:([A-Za-z_][A-Za-z0-9_]*)\.)?([A-Za-z_][A-Za-z0-9_]*)$/.exec(expr);
|
|
3423
|
+
if (refMatch !== null) {
|
|
3424
|
+
const qualifier = refMatch[1];
|
|
3425
|
+
const underlying = refMatch[2];
|
|
3426
|
+
return { kind: "column", underlying, outputKey: alias ?? underlying, ...qualifier !== void 0 ? { qualifier } : {} };
|
|
3427
|
+
}
|
|
3428
|
+
return { kind: "computed", text: raw };
|
|
3429
|
+
}
|
|
3430
|
+
function rowObjType(table, projection, resolveColumnType, at, materializers) {
|
|
3431
|
+
if (projection === void 0 || projection.length === 0) {
|
|
3432
|
+
throw new Error(
|
|
3433
|
+
`outtype: ${at}: SELECT on '${table}' has no explicit column projection ('*' / empty). A typed de-box needs the concrete column list to build the row struct \u2014 spec \xA74.1 is column-typed. Project explicit columns (no-assume, no-fallback).`
|
|
3434
|
+
);
|
|
3435
|
+
}
|
|
3436
|
+
const obj = {};
|
|
3437
|
+
for (const col of projection) {
|
|
3438
|
+
const entry2 = parseProjectionColumn(col, table, at);
|
|
3439
|
+
if (entry2.kind === "computed") {
|
|
3440
|
+
throw new Error(
|
|
3441
|
+
`outtype: ${at}: projected expression '${entry2.text}' on '${table}' is computed (function / aggregate / cast / literal) and cannot be typed into a concrete row struct field. Typed de-box (spec \xA74.1) requires bare/qualified/aliased schema columns. No-assume, no-fallback.`
|
|
3442
|
+
);
|
|
3443
|
+
}
|
|
3444
|
+
const { underlying, outputKey, qualifier } = entry2;
|
|
3445
|
+
if (outputKey in obj) throw new Error(`outtype: ${at}: duplicate projected column key '${outputKey}' on '${table}'`);
|
|
3446
|
+
const owner = qualifier ?? table;
|
|
3447
|
+
const sqlType = resolveColumnType(owner, underlying);
|
|
3448
|
+
obj[outputKey] = toPortableScalar(sqlTypeToBcScalar(sqlType), owner, underlying);
|
|
3449
|
+
const klass = sqlTypeToMaterializeClass(sqlType);
|
|
3450
|
+
if (klass !== "passthrough") materializers[outputKey] = klass;
|
|
3451
|
+
}
|
|
3452
|
+
return { obj };
|
|
3453
|
+
}
|
|
3454
|
+
function nodeTypes(node, resolveColumnType) {
|
|
3455
|
+
const { component, ports } = nodeRef2(node);
|
|
3456
|
+
const at = `node '${node.id}'`;
|
|
3457
|
+
if (component === "Count") return { outType: "int", materializers: {} };
|
|
3458
|
+
if (component === "Select") {
|
|
3459
|
+
const table = stringPort3(ports, "table");
|
|
3460
|
+
if (table === void 0) throw new Error(`outtype: ${at}: Select node requires a literal 'table' port`);
|
|
3461
|
+
const projection = stringArrayPort2(ports, "select");
|
|
3462
|
+
const materializers = {};
|
|
3463
|
+
const row = rowObjType(table, projection, resolveColumnType, at, materializers);
|
|
3464
|
+
const outType = isMap2(node) ? { arr: { arr: row } } : { arr: row };
|
|
3465
|
+
return { outType, materializers };
|
|
3466
|
+
}
|
|
3467
|
+
throw new Error(
|
|
3468
|
+
`outtype: ${at}: component '${component}' has no typed outType (typed read de-box covers Select/Count only). A write / unknown node cannot be de-boxed here \u2014 no-assume, no-fallback.`
|
|
3469
|
+
);
|
|
3470
|
+
}
|
|
3471
|
+
function outputType(output, byNode, at) {
|
|
3472
|
+
const op = opKey3(output);
|
|
3473
|
+
if (op === "ref" || op === "refOpt") {
|
|
3474
|
+
const path = output[op];
|
|
3475
|
+
if (!Array.isArray(path) || path.length === 0 || typeof path[0] !== "string") {
|
|
3476
|
+
throw new Error(`outtype: ${at}: a '${op}' output must reference a node id`);
|
|
3477
|
+
}
|
|
3478
|
+
if (path.length !== 1 || path[0].startsWith("$") || path[0] === IN_SENTINEL) {
|
|
3479
|
+
throw new Error(
|
|
3480
|
+
`outtype: ${at}: output ref path ${JSON.stringify(path)} is not a bare node-result reference; field access / input refs at the output position are not supported by the typed de-box path. No-assume, no-fallback.`
|
|
3481
|
+
);
|
|
3482
|
+
}
|
|
3483
|
+
const t = byNode.get(path[0]);
|
|
3484
|
+
if (t === void 0) throw new Error(`outtype: ${at}: output references node '${path[0]}' which has no derived outType`);
|
|
3485
|
+
return t;
|
|
3486
|
+
}
|
|
3487
|
+
if (op === "obj") {
|
|
3488
|
+
const obj = output.obj;
|
|
3489
|
+
if (obj === null || typeof obj !== "object" || Array.isArray(obj)) throw new Error(`outtype: ${at}: 'obj' output must be a record`);
|
|
3490
|
+
const out = {};
|
|
3491
|
+
for (const [k, expr] of Object.entries(obj)) {
|
|
3492
|
+
if (k === "__proto__") throw new Error(`outtype: ${at}: '__proto__' key forbidden in output obj`);
|
|
3493
|
+
out[k] = outputType(expr, byNode, `${at}.obj.${k}`);
|
|
3494
|
+
}
|
|
3495
|
+
return { obj: out };
|
|
3496
|
+
}
|
|
3497
|
+
if (op === "arr") {
|
|
3498
|
+
const arr = output.arr;
|
|
3499
|
+
if (Array.isArray(arr)) throw new Error(`outtype: ${at}: an '{arr:[\u2026]}' literal list output cannot be typed as a homogeneous array. No-assume, no-fallback.`);
|
|
3500
|
+
return { arr: outputType(arr, byNode, `${at}.arr`) };
|
|
3501
|
+
}
|
|
3502
|
+
if (op === "opt") {
|
|
3503
|
+
const inner = output.opt;
|
|
3504
|
+
return { opt: outputType(inner, byNode, `${at}.opt`) };
|
|
3505
|
+
}
|
|
3506
|
+
throw new Error(
|
|
3507
|
+
`outtype: ${at}: output expression ${JSON.stringify(output)} is not a typed composition (ref / obj / arr / opt of node results). The typed de-box path does not build a dynamic Value output tree \u2014 no-assume, no-fallback.`
|
|
3508
|
+
);
|
|
3509
|
+
}
|
|
3510
|
+
function crudNodeAsType(component, ports, resolveColumnType, at) {
|
|
3511
|
+
const table = ports["table"];
|
|
3512
|
+
if (typeof table !== "string") {
|
|
3513
|
+
throw new Error(`outtype: ${at}: ${component} node requires a literal 'table' string port to type its output (got ${JSON.stringify(table)})`);
|
|
3514
|
+
}
|
|
3515
|
+
if (component === "Select") {
|
|
3516
|
+
const select = ports["select"];
|
|
3517
|
+
if (!Array.isArray(select) || select.length === 0) {
|
|
3518
|
+
throw new Error(
|
|
3519
|
+
`outtype: ${at}: Select has no explicit column projection ('*' / empty). A typed de-box needs the concrete column list to build the row struct \u2014 spec \xA74.1 is column-typed. Project explicit columns.`
|
|
3520
|
+
);
|
|
3521
|
+
}
|
|
3522
|
+
const obj = {};
|
|
3523
|
+
for (const raw of select) {
|
|
3524
|
+
if (typeof raw !== "string") throw new Error(`outtype: ${at}: Select 'select' entries must be literal strings (got ${JSON.stringify(raw)})`);
|
|
3525
|
+
const parsed = parseProjectionColumn(raw, table, at);
|
|
3526
|
+
if (parsed.kind === "computed") continue;
|
|
3527
|
+
const { underlying, outputKey, qualifier } = parsed;
|
|
3528
|
+
if (outputKey in obj) throw new Error(`outtype: ${at}: duplicate projected column key '${outputKey}' on '${table}'`);
|
|
3529
|
+
obj[outputKey] = toPortableScalar(sqlTypeToBcScalar(resolveColumnType(qualifier ?? table, underlying)), qualifier ?? table, underlying);
|
|
3530
|
+
}
|
|
3531
|
+
return { arr: { obj } };
|
|
3532
|
+
}
|
|
3533
|
+
if (component === "Insert" || component === "Update" || component === "Delete") {
|
|
3534
|
+
const returning = ports["returning"];
|
|
3535
|
+
if (returning === void 0) return { arr: { obj: {} } };
|
|
3536
|
+
if (typeof returning !== "string") {
|
|
3537
|
+
throw new Error(`outtype: ${at}: ${component} 'returning' port must be a literal string (got ${JSON.stringify(returning)})`);
|
|
3538
|
+
}
|
|
3539
|
+
const cols = returning.split(",").map((c) => c.trim()).filter((c) => c.length > 0);
|
|
3540
|
+
if (cols.length === 0) return { arr: { obj: {} } };
|
|
3541
|
+
return { arr: rowObjType(table, cols, resolveColumnType, at, {}) };
|
|
3542
|
+
}
|
|
3543
|
+
throw new Error(`outtype: ${at}: no pre-compile output type for component '${component}' (typed CRUD covers Select/Insert/Update/Delete; Count is static via catalog elemType).`);
|
|
3544
|
+
}
|
|
3545
|
+
function deriveReadOutTypes(component, resolveColumnType) {
|
|
3546
|
+
const byNode = /* @__PURE__ */ new Map();
|
|
3547
|
+
const materializersByNode = /* @__PURE__ */ new Map();
|
|
3548
|
+
for (const n of component.body) {
|
|
3549
|
+
if ("cond" in n) continue;
|
|
3550
|
+
const t = nodeTypes(n, resolveColumnType);
|
|
3551
|
+
byNode.set(n.id, t.outType);
|
|
3552
|
+
if (Object.keys(t.materializers).length > 0) materializersByNode.set(n.id, t.materializers);
|
|
3553
|
+
}
|
|
3554
|
+
return { byNode, outputType: outputType(component.output, byNode, `component '${component.name}' output`), materializersByNode };
|
|
3555
|
+
}
|
|
3556
|
+
|
|
3087
3557
|
// src/scp/authoring-sql.ts
|
|
3088
3558
|
import { eq, ne, lt, le, gt, ge } from "behavior-contracts";
|
|
3089
3559
|
var BETWEEN_SENTINEL = "@between";
|
|
@@ -3155,13 +3625,16 @@ function whereExists($, sub2, not2 = false) {
|
|
|
3155
3625
|
}
|
|
3156
3626
|
|
|
3157
3627
|
// src/scp/makesql/static-bundle.ts
|
|
3158
|
-
function
|
|
3628
|
+
function isMap3(n) {
|
|
3159
3629
|
return "map" in n;
|
|
3160
3630
|
}
|
|
3161
|
-
function
|
|
3162
|
-
return
|
|
3631
|
+
function isFanout2(n) {
|
|
3632
|
+
return "fanout" in n;
|
|
3163
3633
|
}
|
|
3164
|
-
function
|
|
3634
|
+
function nodeRef3(n) {
|
|
3635
|
+
return isMap3(n) ? { component: n.map.component, ports: n.map.ports } : { component: n.component, ports: n.ports };
|
|
3636
|
+
}
|
|
3637
|
+
function stringPort4(ports, name) {
|
|
3165
3638
|
const v = ports[name];
|
|
3166
3639
|
if (v === void 0) return void 0;
|
|
3167
3640
|
if (typeof v !== "string") throw new Error(`static-bundle: port '${name}' must be a literal string in the IR (got ${JSON.stringify(v)})`);
|
|
@@ -3175,7 +3648,7 @@ function arrPort2(ports, name) {
|
|
|
3175
3648
|
}
|
|
3176
3649
|
throw new Error(`static-bundle: port '${name}' must be an {arr:[...]} literal in the IR (got ${JSON.stringify(v)})`);
|
|
3177
3650
|
}
|
|
3178
|
-
function
|
|
3651
|
+
function stringArrayPort3(ports, name) {
|
|
3179
3652
|
const arr = arrPort2(ports, name);
|
|
3180
3653
|
if (arr === void 0) return void 0;
|
|
3181
3654
|
return arr.map((e) => {
|
|
@@ -3200,13 +3673,13 @@ function exprArrayPort(ports, name) {
|
|
|
3200
3673
|
if (arr === void 0) return void 0;
|
|
3201
3674
|
return arr;
|
|
3202
3675
|
}
|
|
3203
|
-
function
|
|
3676
|
+
function opKey4(node) {
|
|
3204
3677
|
if (node === null || typeof node !== "object" || Array.isArray(node)) return void 0;
|
|
3205
3678
|
const keys = Object.keys(node);
|
|
3206
3679
|
return keys.length === 1 ? keys[0] : void 0;
|
|
3207
3680
|
}
|
|
3208
3681
|
function columnOf3(node, ctx) {
|
|
3209
|
-
const op =
|
|
3682
|
+
const op = opKey4(node);
|
|
3210
3683
|
if (op !== "ref" && op !== "refOpt") throw new Error(`static-bundle: ${ctx}: the column operand must be a {ref:[\u2026]} / {refOpt:[\u2026]} path`);
|
|
3211
3684
|
const path = node[op];
|
|
3212
3685
|
if (!Array.isArray(path) || path.length === 0 || typeof path[path.length - 1] !== "string") {
|
|
@@ -3215,7 +3688,7 @@ function columnOf3(node, ctx) {
|
|
|
3215
3688
|
return path[path.length - 1];
|
|
3216
3689
|
}
|
|
3217
3690
|
function inSentinelColumn2(node) {
|
|
3218
|
-
const op =
|
|
3691
|
+
const op = opKey4(node);
|
|
3219
3692
|
if (op !== "ref" && op !== "refOpt") return void 0;
|
|
3220
3693
|
const path = node[op];
|
|
3221
3694
|
if (Array.isArray(path) && path.length >= 2 && path[0] === IN_SENTINEL && typeof path[path.length - 1] === "string") {
|
|
@@ -3229,7 +3702,7 @@ function binOperands3(node, op, at) {
|
|
|
3229
3702
|
return [args[0], args[1]];
|
|
3230
3703
|
}
|
|
3231
3704
|
function sentinelPath(node) {
|
|
3232
|
-
const op =
|
|
3705
|
+
const op = opKey4(node);
|
|
3233
3706
|
if (op !== "ref" && op !== "refOpt") return void 0;
|
|
3234
3707
|
const path = node[op];
|
|
3235
3708
|
if (!Array.isArray(path) || path.length === 0) return void 0;
|
|
@@ -3344,7 +3817,7 @@ function inListFragment(dialect, column, valueSpec) {
|
|
|
3344
3817
|
return { sql: v1ConditionText2(conditions, dialect), params: [jsonArraySpec(dialect, valueSpec)] };
|
|
3345
3818
|
}
|
|
3346
3819
|
function lowerWhereMember2(node, dialect, at) {
|
|
3347
|
-
const op =
|
|
3820
|
+
const op = opKey4(node);
|
|
3348
3821
|
if (op === void 0) throw new Error(`static-bundle: ${at}: a where member must be a single-operator Expression node`);
|
|
3349
3822
|
if (op === "and" || op === "or") {
|
|
3350
3823
|
const args = node[op];
|
|
@@ -3384,7 +3857,7 @@ function lowerWherePort2(ports, dialect, at) {
|
|
|
3384
3857
|
const fragments = [];
|
|
3385
3858
|
arr.forEach((m, i) => {
|
|
3386
3859
|
const memberAt = `${at}.where[${i}]`;
|
|
3387
|
-
if (
|
|
3860
|
+
if (opKey4(m) === "cond") {
|
|
3388
3861
|
const cargs = m.cond;
|
|
3389
3862
|
if (!Array.isArray(cargs) || cargs.length !== 3 || cargs[2] !== null) {
|
|
3390
3863
|
throw new Error(`static-bundle: ${memberAt}: a SKIP-optional condition must be {cond:[c, <member>, null]} (else = null)`);
|
|
@@ -3413,18 +3886,18 @@ function limitOffsetTail(kind, dialect, table) {
|
|
|
3413
3886
|
return tail.replace(String(SENTINEL), "?");
|
|
3414
3887
|
}
|
|
3415
3888
|
function compileSelectNode(node, dialect) {
|
|
3416
|
-
const { component, ports } =
|
|
3889
|
+
const { component, ports } = nodeRef3(node);
|
|
3417
3890
|
if (component !== "Select" && component !== "Count") {
|
|
3418
3891
|
throw new Error(`static-bundle: compileSelectNode only compiles Select/Count nodes (got '${component}')`);
|
|
3419
3892
|
}
|
|
3420
|
-
const table =
|
|
3893
|
+
const table = stringPort4(ports, "table");
|
|
3421
3894
|
if (table === void 0) throw new Error(`static-bundle: ${component} node requires a literal 'table' port`);
|
|
3422
3895
|
const isCount = component === "Count";
|
|
3423
|
-
const select =
|
|
3896
|
+
const select = stringArrayPort3(ports, "select");
|
|
3424
3897
|
const cols = isCount ? "COUNT(*) as count" : select && select.length > 0 ? select.join(", ") : "*";
|
|
3425
3898
|
const statements = [];
|
|
3426
3899
|
const cte = mapPort(ports, "cte");
|
|
3427
|
-
const join =
|
|
3900
|
+
const join = stringPort4(ports, "join");
|
|
3428
3901
|
const cteParams = exprArrayPort(ports, "cteParams");
|
|
3429
3902
|
const joinParams = exprArrayPort(ports, "joinParams");
|
|
3430
3903
|
const headDesc = { dialect, tableName: table, select: cols };
|
|
@@ -3437,15 +3910,15 @@ function compileSelectNode(node, dialect) {
|
|
|
3437
3910
|
statements.push({ sql: frag.sql, params: frag.params, whereFragment: true, ...frag.skip !== void 0 ? { skip: frag.skip } : {} });
|
|
3438
3911
|
}
|
|
3439
3912
|
if (isCount) return statements;
|
|
3440
|
-
const group =
|
|
3913
|
+
const group = stringPort4(ports, "group");
|
|
3441
3914
|
if (group !== void 0) statements.push({ sql: selectTail({ dialect, tableName: table, group }, table), params: [] });
|
|
3442
|
-
const order =
|
|
3915
|
+
const order = stringPort4(ports, "order");
|
|
3443
3916
|
if (order !== void 0) statements.push({ sql: selectTail({ dialect, tableName: table, order }, table), params: [] });
|
|
3444
3917
|
if (ports.limit !== void 0) statements.push({ sql: limitOffsetTail("limit", dialect, table), params: [ports.limit] });
|
|
3445
3918
|
if (ports.offset !== void 0) statements.push({ sql: limitOffsetTail("offset", dialect, table), params: [ports.offset] });
|
|
3446
|
-
const forUpdate =
|
|
3919
|
+
const forUpdate = stringPort4(ports, "forUpdate");
|
|
3447
3920
|
if (forUpdate === "true") statements.push({ sql: selectTail({ dialect, tableName: table, forUpdate: true }, table), params: [] });
|
|
3448
|
-
const append =
|
|
3921
|
+
const append = stringPort4(ports, "append");
|
|
3449
3922
|
if (append !== void 0) statements.push({ sql: selectTail({ dialect, tableName: table, append }, table), params: [] });
|
|
3450
3923
|
return statements;
|
|
3451
3924
|
}
|
|
@@ -3453,7 +3926,7 @@ function compilePrimaryNode(node, dialect) {
|
|
|
3453
3926
|
const component = "map" in node ? node.map.component : node.component;
|
|
3454
3927
|
if (component === "Select" || component === "Count") return compileSelectNode(node, dialect);
|
|
3455
3928
|
if (component === "Insert" || component === "Update" || component === "Delete") {
|
|
3456
|
-
const op = compileWriteNode(node);
|
|
3929
|
+
const op = compileWriteNode(node, dialect);
|
|
3457
3930
|
return [{ sql: op.sql, params: op.params }];
|
|
3458
3931
|
}
|
|
3459
3932
|
throw new Error(`static-bundle: catalog component '${component}' has no makeSQL compile (SQL CRUD only: Select/Count/Insert/Update/Delete)`);
|
|
@@ -3464,7 +3937,10 @@ function compileStaticBundle(contract, dialect, entry2) {
|
|
|
3464
3937
|
let primary;
|
|
3465
3938
|
for (const n of component.body) {
|
|
3466
3939
|
if ("cond" in n) continue;
|
|
3467
|
-
if (
|
|
3940
|
+
if (isFanout2(n)) {
|
|
3941
|
+
throw new Error(`static-bundle: behavior '${component.name}' node '${n.id}' is a fanout node, not supported by litedbmodel (bc FanoutNode)`);
|
|
3942
|
+
}
|
|
3943
|
+
if (isMap3(n)) continue;
|
|
3468
3944
|
if (primary === void 0) primary = n;
|
|
3469
3945
|
}
|
|
3470
3946
|
if (primary === void 0) throw new Error(`static-bundle: behavior '${component.name}' has no primary catalog node`);
|
|
@@ -3504,13 +3980,14 @@ function collectRefOptHeads(node, into) {
|
|
|
3504
3980
|
}
|
|
3505
3981
|
function skipGuardHeads2(n, into) {
|
|
3506
3982
|
if ("cond" in n) return;
|
|
3507
|
-
|
|
3983
|
+
if (isFanout2(n)) return;
|
|
3984
|
+
const ports = isMap3(n) ? n.map.ports : n.ports;
|
|
3508
3985
|
const where = ports.where;
|
|
3509
3986
|
if (typeof where !== "object" || where === null || !("arr" in where)) return;
|
|
3510
3987
|
const arr = where.arr;
|
|
3511
3988
|
if (!Array.isArray(arr)) return;
|
|
3512
3989
|
for (const m of arr) {
|
|
3513
|
-
if (
|
|
3990
|
+
if (opKey4(m) === "cond") {
|
|
3514
3991
|
const cond = m.cond;
|
|
3515
3992
|
if (Array.isArray(cond) && cond.length === 3) collectRefHeads3(cond[0], into);
|
|
3516
3993
|
}
|
|
@@ -3522,7 +3999,7 @@ function collectRefHeads3(node, into) {
|
|
|
3522
3999
|
for (const e of node) collectRefHeads3(e, into);
|
|
3523
4000
|
return;
|
|
3524
4001
|
}
|
|
3525
|
-
const op =
|
|
4002
|
+
const op = opKey4(node);
|
|
3526
4003
|
if (op === "ref" || op === "refOpt") {
|
|
3527
4004
|
const path = node[op];
|
|
3528
4005
|
if (Array.isArray(path) && path.length > 0 && typeof path[0] === "string") into.add(path[0]);
|
|
@@ -3530,6 +4007,43 @@ function collectRefHeads3(node, into) {
|
|
|
3530
4007
|
}
|
|
3531
4008
|
for (const v of Object.values(node)) collectRefHeads3(v, into);
|
|
3532
4009
|
}
|
|
4010
|
+
function whereMemberColumns(node, into) {
|
|
4011
|
+
const op = opKey4(node);
|
|
4012
|
+
if (op === void 0) return;
|
|
4013
|
+
if (op === "and" || op === "or") {
|
|
4014
|
+
const args = node[op];
|
|
4015
|
+
if (Array.isArray(args)) for (const m of args) whereMemberColumns(m, into);
|
|
4016
|
+
return;
|
|
4017
|
+
}
|
|
4018
|
+
if (op === "cond") {
|
|
4019
|
+
const cargs = node.cond;
|
|
4020
|
+
if (Array.isArray(cargs) && cargs.length === 3) whereMemberColumns(cargs[1], into);
|
|
4021
|
+
return;
|
|
4022
|
+
}
|
|
4023
|
+
if (op === "eq" || op in CMP_OPS3) {
|
|
4024
|
+
const args = node[op];
|
|
4025
|
+
if (!Array.isArray(args) || args.length !== 2) return;
|
|
4026
|
+
const inCol = inSentinelColumn2(args[0]);
|
|
4027
|
+
if (inCol !== void 0) {
|
|
4028
|
+
into.add(inCol);
|
|
4029
|
+
return;
|
|
4030
|
+
}
|
|
4031
|
+
const lhsOp = opKey4(args[0]);
|
|
4032
|
+
if (lhsOp === "ref" || lhsOp === "refOpt") {
|
|
4033
|
+
const path = args[0][lhsOp];
|
|
4034
|
+
if (Array.isArray(path) && path.length > 0 && typeof path[path.length - 1] === "string" && path[0] !== IN_SENTINEL) {
|
|
4035
|
+
into.add(path[path.length - 1]);
|
|
4036
|
+
}
|
|
4037
|
+
}
|
|
4038
|
+
}
|
|
4039
|
+
}
|
|
4040
|
+
function authoredWhereKeysOf(ports) {
|
|
4041
|
+
const keys = /* @__PURE__ */ new Set();
|
|
4042
|
+
const arr = arrPort2(ports, "where");
|
|
4043
|
+
if (arr === void 0) return keys;
|
|
4044
|
+
for (const m of arr) whereMemberColumns(m, keys);
|
|
4045
|
+
return keys;
|
|
4046
|
+
}
|
|
3533
4047
|
function toDriverParam2(v) {
|
|
3534
4048
|
if (typeof v === "bigint") {
|
|
3535
4049
|
if (v >= BigInt(Number.MIN_SAFE_INTEGER) && v <= BigInt(Number.MAX_SAFE_INTEGER)) return Number(v);
|
|
@@ -3601,116 +4115,207 @@ function executeStaticWrite(bundle, input, db) {
|
|
|
3601
4115
|
throw mapSqliteError(e);
|
|
3602
4116
|
}
|
|
3603
4117
|
}
|
|
3604
|
-
var SCOPE_PORT = "__scope";
|
|
3605
|
-
var NODE_COMPONENT = "__makeSqlNode";
|
|
3606
|
-
function collectAllHeads(node, heads) {
|
|
3607
|
-
if (node === null || typeof node !== "object") return;
|
|
3608
|
-
if (Array.isArray(node)) {
|
|
3609
|
-
for (const el of node) collectAllHeads(el, heads);
|
|
3610
|
-
return;
|
|
3611
|
-
}
|
|
3612
|
-
const keys = Object.keys(node);
|
|
3613
|
-
if (keys.length === 1 && (keys[0] === "ref" || keys[0] === "refOpt")) {
|
|
3614
|
-
const path = node[keys[0]];
|
|
3615
|
-
if (Array.isArray(path) && typeof path[0] === "string") heads.add(path[0]);
|
|
3616
|
-
return;
|
|
3617
|
-
}
|
|
3618
|
-
for (const k of keys) collectAllHeads(node[k], heads);
|
|
3619
|
-
}
|
|
3620
|
-
function statementHeads(statements) {
|
|
3621
|
-
const heads = /* @__PURE__ */ new Set();
|
|
3622
|
-
for (const stmt of statements) {
|
|
3623
|
-
for (const p of stmt.params) collectAllHeads(p, heads);
|
|
3624
|
-
if (stmt.skip !== void 0) collectAllHeads(stmt.skip, heads);
|
|
3625
|
-
}
|
|
3626
|
-
return heads;
|
|
3627
|
-
}
|
|
3628
|
-
function scopePort(statements) {
|
|
3629
|
-
const obj = {};
|
|
3630
|
-
for (const head of statementHeads(statements)) {
|
|
3631
|
-
if (head === IN_SENTINEL) continue;
|
|
3632
|
-
obj[head] = { ref: [head] };
|
|
3633
|
-
}
|
|
3634
|
-
return { obj };
|
|
3635
|
-
}
|
|
3636
4118
|
function compileNodeStatements(node, dialect) {
|
|
3637
4119
|
const component = "map" in node ? node.map.component : node.component;
|
|
3638
4120
|
if (component === "Select" || component === "Count") return compileSelectNode(node, dialect);
|
|
3639
|
-
const op = compileWriteNode(node);
|
|
4121
|
+
const op = compileWriteNode(node, dialect);
|
|
3640
4122
|
return [{ sql: op.sql, params: op.params }];
|
|
3641
4123
|
}
|
|
3642
|
-
function compileReadGraph(contract, dialect, entry2) {
|
|
4124
|
+
function compileReadGraph(contract, dialect, entry2, findFilterModel, resolveColumnType) {
|
|
3643
4125
|
const component = entry2 ? contract.components.find((c) => c.name === entry2) : contract.components[0];
|
|
3644
4126
|
if (component === void 0) throw new Error(`static-bundle: entry component '${entry2 ?? "<first>"}' not found in contract`);
|
|
4127
|
+
if (findFilterModel !== void 0) {
|
|
4128
|
+
for (const n of component.body) {
|
|
4129
|
+
if ("cond" in n) continue;
|
|
4130
|
+
if (isFanout2(n)) {
|
|
4131
|
+
throw new Error(`static-bundle: read component '${component.name}' node '${n.id}' is a fanout node, not supported by litedbmodel (bc FanoutNode)`);
|
|
4132
|
+
}
|
|
4133
|
+
const ref = "map" in n ? n.map : n;
|
|
4134
|
+
if (ref.component !== "Select" && ref.component !== "Count") continue;
|
|
4135
|
+
assertFindFilterFolded(findFilterModel, authoredWhereKeysOf(ref.ports));
|
|
4136
|
+
}
|
|
4137
|
+
}
|
|
3645
4138
|
const statementsById = {};
|
|
3646
4139
|
for (const n of component.body) {
|
|
3647
4140
|
if ("cond" in n) continue;
|
|
4141
|
+
if (isFanout2(n)) {
|
|
4142
|
+
throw new Error(`static-bundle: read component '${component.name}' node '${n.id}' is a fanout node, not supported by litedbmodel (bc FanoutNode)`);
|
|
4143
|
+
}
|
|
3648
4144
|
statementsById[n.id] = compileNodeStatements(n, dialect);
|
|
3649
4145
|
}
|
|
3650
|
-
const
|
|
4146
|
+
const readTypes = resolveColumnType !== void 0 ? deriveReadOutTypes(component, resolveColumnType) : void 0;
|
|
4147
|
+
const materializers = readTypes?.materializersByNode;
|
|
4148
|
+
const annotatedBody = component.body.map((n) => {
|
|
3651
4149
|
if ("cond" in n) return n;
|
|
3652
|
-
const
|
|
3653
|
-
|
|
3654
|
-
return { ...n, map: { ...n.map, component: NODE_COMPONENT, ports: { [SCOPE_PORT]: scopePort(stmts) } } };
|
|
3655
|
-
}
|
|
3656
|
-
return { ...n, component: NODE_COMPONENT, ports: { [SCOPE_PORT]: scopePort(stmts) } };
|
|
4150
|
+
const outType = readTypes?.byNode.get(n.id);
|
|
4151
|
+
return outType !== void 0 ? { ...n, outType } : n;
|
|
3657
4152
|
});
|
|
3658
|
-
const
|
|
3659
|
-
|
|
4153
|
+
const irComponent = {
|
|
4154
|
+
...component,
|
|
4155
|
+
body: annotatedBody,
|
|
4156
|
+
...readTypes !== void 0 ? { outputType: readTypes.outputType } : {}
|
|
4157
|
+
};
|
|
4158
|
+
const ir = { ...contract.ir, components: [irComponent] };
|
|
3660
4159
|
return {
|
|
3661
4160
|
dialect,
|
|
3662
4161
|
name: component.name,
|
|
3663
4162
|
ir,
|
|
3664
4163
|
statementsById,
|
|
3665
|
-
optionalHeads: [...optionalHeadsOfComponent(component, dialect)]
|
|
4164
|
+
optionalHeads: [...optionalHeadsOfComponent(component, dialect)],
|
|
4165
|
+
...materializers !== void 0 && materializers.size > 0 ? { materializersByNode: Object.fromEntries(materializers) } : {}
|
|
3666
4166
|
};
|
|
3667
4167
|
}
|
|
3668
|
-
function
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
return { error: mapSqliteError(e).message };
|
|
4168
|
+
function materializeRows(rows, cols, safeIntegersOn = false) {
|
|
4169
|
+
if (cols === void 0 && !safeIntegersOn) return rows;
|
|
4170
|
+
for (const row of rows) {
|
|
4171
|
+
for (const key of Object.keys(row)) {
|
|
4172
|
+
const klass = cols?.[key];
|
|
4173
|
+
if (klass !== void 0) {
|
|
4174
|
+
row[key] = materializeCell(row[key], klass);
|
|
4175
|
+
continue;
|
|
4176
|
+
}
|
|
4177
|
+
if (safeIntegersOn && typeof row[key] === "bigint") {
|
|
4178
|
+
const v = row[key];
|
|
4179
|
+
row[key] = v >= BigInt(Number.MIN_SAFE_INTEGER) && v <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(v) : v.toString();
|
|
4180
|
+
}
|
|
3682
4181
|
}
|
|
3683
|
-
}
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
4182
|
+
}
|
|
4183
|
+
return rows;
|
|
4184
|
+
}
|
|
4185
|
+
function hasInt64(cols) {
|
|
4186
|
+
for (const k of Object.keys(cols)) if (cols[k] === "int64") return true;
|
|
4187
|
+
return false;
|
|
4188
|
+
}
|
|
4189
|
+
function planStages(component) {
|
|
4190
|
+
const plan = component.plan;
|
|
4191
|
+
const groups = plan?.groups;
|
|
4192
|
+
if (Array.isArray(groups)) {
|
|
4193
|
+
return groups.map((st) => Array.isArray(st) ? st.filter((i) => typeof i === "number") : []);
|
|
4194
|
+
}
|
|
4195
|
+
return component.body.map((_n, i) => [i]);
|
|
4196
|
+
}
|
|
4197
|
+
function planConcurrency(component) {
|
|
4198
|
+
const plan = component.plan;
|
|
4199
|
+
const c = plan?.concurrency;
|
|
4200
|
+
return typeof c === "number" && c >= 1 ? c : 1;
|
|
4201
|
+
}
|
|
4202
|
+
async function boundedMap(items, limit, fn) {
|
|
4203
|
+
const out = new Array(items.length);
|
|
4204
|
+
const cap = Math.max(1, Math.floor(limit));
|
|
4205
|
+
let next = 0;
|
|
4206
|
+
async function worker() {
|
|
4207
|
+
for (; ; ) {
|
|
4208
|
+
const i = next++;
|
|
4209
|
+
if (i >= items.length) return;
|
|
4210
|
+
out[i] = await fn(items[i], i);
|
|
4211
|
+
}
|
|
4212
|
+
}
|
|
4213
|
+
await Promise.all(Array.from({ length: Math.min(cap, items.length) }, () => worker()));
|
|
4214
|
+
return out;
|
|
4215
|
+
}
|
|
4216
|
+
function execReadNodeSync(graph, nodeId, scope, db) {
|
|
4217
|
+
const stmts = graph.statementsById[nodeId];
|
|
4218
|
+
if (stmts === void 0) throw new Error(`static-bundle: no statements for node '${nodeId}'`);
|
|
4219
|
+
const { sql: sql2, params } = renderStatements(stmts, graph.dialect, scope);
|
|
4220
|
+
const cols = graph.materializersByNode?.[nodeId];
|
|
4221
|
+
try {
|
|
4222
|
+
const stmt = db.prepare(sql2);
|
|
4223
|
+
let safeIntegersOn = false;
|
|
4224
|
+
if (cols !== void 0 && hasInt64(cols) && typeof stmt.safeIntegers === "function") {
|
|
4225
|
+
stmt.safeIntegers(true);
|
|
4226
|
+
safeIntegersOn = true;
|
|
4227
|
+
}
|
|
4228
|
+
const rows = stmt.all(...params);
|
|
4229
|
+
return materializeRows(rows, cols, safeIntegersOn);
|
|
4230
|
+
} catch (e) {
|
|
4231
|
+
throw mapSqliteError(e);
|
|
4232
|
+
}
|
|
4233
|
+
}
|
|
4234
|
+
function computeReadNodeSync(graph, node, base, db) {
|
|
4235
|
+
if (node.cond !== void 0) return evaluateExpression3(node.cond, base);
|
|
4236
|
+
if (node.map !== void 0) {
|
|
4237
|
+
const over = evaluateExpression3(node.map.over, base);
|
|
4238
|
+
if (!Array.isArray(over)) throw new Error(`static-bundle: map '${node.id}': 'over' is not an array`);
|
|
4239
|
+
const asName = node.map.as ?? "$";
|
|
4240
|
+
const out = [];
|
|
4241
|
+
for (const el of over) {
|
|
4242
|
+
out.push(execReadNodeSync(graph, node.id, { ...base, [asName]: el }, db));
|
|
4243
|
+
}
|
|
4244
|
+
return out;
|
|
4245
|
+
}
|
|
4246
|
+
return execReadNodeSync(graph, node.id, base, db);
|
|
4247
|
+
}
|
|
4248
|
+
function normalizeReadInput(graph, input) {
|
|
4249
|
+
const out = { ...input };
|
|
4250
|
+
for (const head of graph.optionalHeads) if (!(head in out)) out[head] = null;
|
|
4251
|
+
return out;
|
|
4252
|
+
}
|
|
4253
|
+
function executeReadGraph(graph, input, db) {
|
|
4254
|
+
const component = graph.ir.components[0];
|
|
4255
|
+
const body = component.body;
|
|
4256
|
+
const output = component.output ?? null;
|
|
4257
|
+
const normalized = normalizeReadInput(graph, input);
|
|
3687
4258
|
try {
|
|
3688
|
-
|
|
4259
|
+
const results = [];
|
|
4260
|
+
for (const stage of planStages(component)) {
|
|
4261
|
+
const ordered = [...stage].sort((a, b) => a - b);
|
|
4262
|
+
const base = { ...normalized };
|
|
4263
|
+
for (const [id, v] of results) base[id] = v;
|
|
4264
|
+
for (const idx of ordered) {
|
|
4265
|
+
const node = body[idx];
|
|
4266
|
+
base[node.id] = results[results.push([node.id, computeReadNodeSync(graph, node, base, db)]) - 1][1];
|
|
4267
|
+
}
|
|
4268
|
+
}
|
|
4269
|
+
const scope = { ...normalized };
|
|
4270
|
+
for (const [id, v] of results) scope[id] = v;
|
|
4271
|
+
return evaluateExpression3(output, scope);
|
|
3689
4272
|
} catch (e) {
|
|
3690
4273
|
throw reErrorToSqlFailure(e);
|
|
3691
4274
|
}
|
|
3692
4275
|
}
|
|
4276
|
+
async function execReadNodeAsync(graph, nodeId, scope, exec) {
|
|
4277
|
+
const stmts = graph.statementsById[nodeId];
|
|
4278
|
+
if (stmts === void 0) throw new Error(`static-bundle: no statements for node '${nodeId}'`);
|
|
4279
|
+
const { sql: sql2, params } = renderStatements(stmts, graph.dialect, scope);
|
|
4280
|
+
try {
|
|
4281
|
+
const rows = await exec(sql2, params);
|
|
4282
|
+
return materializeRows(rows, graph.materializersByNode?.[nodeId]);
|
|
4283
|
+
} catch (e) {
|
|
4284
|
+
throw mapSqliteError(e);
|
|
4285
|
+
}
|
|
4286
|
+
}
|
|
4287
|
+
async function computeReadNodeAsync(graph, node, base, exec, concurrency) {
|
|
4288
|
+
if (node.cond !== void 0) return evaluateExpression3(node.cond, base);
|
|
4289
|
+
if (node.map !== void 0) {
|
|
4290
|
+
const over = evaluateExpression3(node.map.over, base);
|
|
4291
|
+
if (!Array.isArray(over)) throw new Error(`static-bundle: map '${node.id}': 'over' is not an array`);
|
|
4292
|
+
const asName = node.map.as ?? "$";
|
|
4293
|
+
return await boundedMap(
|
|
4294
|
+
over,
|
|
4295
|
+
concurrency,
|
|
4296
|
+
(el) => execReadNodeAsync(graph, node.id, { ...base, [asName]: el }, exec)
|
|
4297
|
+
);
|
|
4298
|
+
}
|
|
4299
|
+
return execReadNodeAsync(graph, node.id, base, exec);
|
|
4300
|
+
}
|
|
3693
4301
|
async function executeReadGraphAsync(graph, input, exec) {
|
|
3694
|
-
const
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
return { error: `static-bundle: node '${ctx.nodeId}' surrogate scope did not evaluate to an object` };
|
|
3700
|
-
}
|
|
3701
|
-
const { sql: sql2, params } = renderStatements(stmts, graph.dialect, scope);
|
|
3702
|
-
try {
|
|
3703
|
-
const rows = await exec(sql2, params);
|
|
3704
|
-
return { ok: rows };
|
|
3705
|
-
} catch (e) {
|
|
3706
|
-
return { error: mapSqliteError(e).message };
|
|
3707
|
-
}
|
|
3708
|
-
};
|
|
3709
|
-
const handlers = { [NODE_COMPONENT]: handle };
|
|
3710
|
-
const normalized = { ...input };
|
|
3711
|
-
for (const head of graph.optionalHeads) if (!(head in normalized)) normalized[head] = null;
|
|
4302
|
+
const component = graph.ir.components[0];
|
|
4303
|
+
const body = component.body;
|
|
4304
|
+
const output = component.output ?? null;
|
|
4305
|
+
const concurrency = planConcurrency(component);
|
|
4306
|
+
const normalized = normalizeReadInput(graph, input);
|
|
3712
4307
|
try {
|
|
3713
|
-
|
|
4308
|
+
const results = [];
|
|
4309
|
+
for (const stage of planStages(component)) {
|
|
4310
|
+
const ordered = [...stage].sort((a, b) => a - b);
|
|
4311
|
+
const base = { ...normalized };
|
|
4312
|
+
for (const [id, v] of results) base[id] = v;
|
|
4313
|
+
const vals = await boundedMap(ordered, concurrency, (idx) => computeReadNodeAsync(graph, body[idx], base, exec, concurrency));
|
|
4314
|
+
ordered.forEach((idx, i) => results.push([body[idx].id, vals[i]]));
|
|
4315
|
+
}
|
|
4316
|
+
const scope = { ...normalized };
|
|
4317
|
+
for (const [id, v] of results) scope[id] = v;
|
|
4318
|
+
return evaluateExpression3(output, scope);
|
|
3714
4319
|
} catch (e) {
|
|
3715
4320
|
throw reErrorToSqlFailure(e);
|
|
3716
4321
|
}
|
|
@@ -3735,6 +4340,7 @@ function optionalHeadsOfComponent(component, dialect) {
|
|
|
3735
4340
|
for (const n of component.body) skipGuardHeads2(n, optional);
|
|
3736
4341
|
for (const n of component.body) {
|
|
3737
4342
|
if ("cond" in n) continue;
|
|
4343
|
+
if (isFanout2(n)) continue;
|
|
3738
4344
|
const stmts = compileNodeStatements(n, dialect);
|
|
3739
4345
|
for (const stmt of stmts) {
|
|
3740
4346
|
for (const p of stmt.params) collectRefOptHeads(p, optional);
|
|
@@ -3751,12 +4357,35 @@ function reErrorToSqlFailure(e) {
|
|
|
3751
4357
|
}
|
|
3752
4358
|
|
|
3753
4359
|
// src/scp/makesql/pool-executor.ts
|
|
4360
|
+
var PG_OID = { INT8: 20, DATE: 1082, TIMESTAMP: 1114, TIMESTAMPTZ: 1184, TIME: 1083, TIMETZ: 1266 };
|
|
4361
|
+
function configurePgDeboxTypeParsers(types) {
|
|
4362
|
+
const asString = (v) => v;
|
|
4363
|
+
types.setTypeParser(PG_OID.DATE, asString);
|
|
4364
|
+
types.setTypeParser(PG_OID.TIMESTAMP, asString);
|
|
4365
|
+
types.setTypeParser(PG_OID.TIMESTAMPTZ, asString);
|
|
4366
|
+
types.setTypeParser(PG_OID.TIME, asString);
|
|
4367
|
+
types.setTypeParser(PG_OID.TIMETZ, asString);
|
|
4368
|
+
}
|
|
4369
|
+
var mysqlDeboxPoolOptions = {
|
|
4370
|
+
supportBigNumbers: true,
|
|
4371
|
+
bigNumberStrings: true,
|
|
4372
|
+
dateStrings: true
|
|
4373
|
+
};
|
|
3754
4374
|
function pgPoolExecutor(pool) {
|
|
3755
4375
|
return async (sql2, params) => {
|
|
3756
4376
|
const result = await pool.query(sql2, params);
|
|
3757
4377
|
return result.rows;
|
|
3758
4378
|
};
|
|
3759
4379
|
}
|
|
4380
|
+
function pgDeboxExecutor(pg, config) {
|
|
4381
|
+
configurePgDeboxTypeParsers(pg.types);
|
|
4382
|
+
const pool = new pg.Pool(config);
|
|
4383
|
+
return { pool, exec: pgPoolExecutor(pool) };
|
|
4384
|
+
}
|
|
4385
|
+
function mysqlDeboxExecutor(mysql2, config) {
|
|
4386
|
+
const pool = mysql2.createPool({ ...config, ...mysqlDeboxPoolOptions });
|
|
4387
|
+
return { pool, exec: mysqlPoolExecutor(pool) };
|
|
4388
|
+
}
|
|
3760
4389
|
function mysqlPoolExecutor(pool) {
|
|
3761
4390
|
return async (sql2, params) => {
|
|
3762
4391
|
const [rows] = await pool.query(sql2, params);
|
|
@@ -3822,7 +4451,10 @@ var LITEDBMODEL_CATALOG = {
|
|
|
3822
4451
|
// Only `table` (required) + the optional `where` fragment tree — v1's count carries no
|
|
3823
4452
|
// projection/order/limit/offset (it counts the filtered rows). Output is a one-row `[{count}]`
|
|
3824
4453
|
// list (the `items` shape, like every read); the consumer reads `count` — v1's `parseInt(count)`.
|
|
3825
|
-
|
|
4454
|
+
// bc 0.8.0 (SA5): Count's output element type is STATICALLY determined (`int` — the counted rows),
|
|
4455
|
+
// so it declares a catalog `elemType` and needs no per-call `.as` (unlike Select, whose row shape is
|
|
4456
|
+
// per-projection). `baseOutTypeFromEntry` bakes node.outType = {arr:'int'} (items shape) at record.
|
|
4457
|
+
Count: { ...entry("Count", { table: P("string", true), where: P("fragment") }, "items", true), elemType: "int" },
|
|
3826
4458
|
// Writes yield the RETURNING row list (`items`); a single-row write collapses at the
|
|
3827
4459
|
// consumer boundary, same as reads.
|
|
3828
4460
|
Insert: entry("Insert", WRITE_PORTS, "items", true),
|
|
@@ -3860,13 +4492,18 @@ var WRITE_CATALOG_NAMES = [
|
|
|
3860
4492
|
var WRITE_SET = new Set(WRITE_CATALOG_NAMES);
|
|
3861
4493
|
var WRITE_PORT_FAMILIES = [
|
|
3862
4494
|
"values.",
|
|
3863
|
-
"set."
|
|
4495
|
+
"set.",
|
|
4496
|
+
"sqlCast."
|
|
3864
4497
|
];
|
|
3865
4498
|
function assertComponentsInCatalog(components2) {
|
|
3866
4499
|
const errs = [];
|
|
3867
4500
|
for (const c of components2) {
|
|
3868
4501
|
for (const n of c.body) {
|
|
3869
4502
|
if ("cond" in n) continue;
|
|
4503
|
+
if ("fanout" in n) {
|
|
4504
|
+
errs.push(`${c.name}/${n.id}: fanout node is not supported by litedbmodel (bc FanoutNode)`);
|
|
4505
|
+
continue;
|
|
4506
|
+
}
|
|
3870
4507
|
const ref = "map" in n ? n.map : n;
|
|
3871
4508
|
const entryDef = LITEDBMODEL_CATALOG[ref.component];
|
|
3872
4509
|
if (entryDef === void 0) {
|
|
@@ -4044,6 +4681,8 @@ function assertComponentPortable(component) {
|
|
|
4044
4681
|
assertExprPortable(n.cond.if, `${at}/${n.id}.cond.if`);
|
|
4045
4682
|
assertExprPortable(n.cond.then, `${at}/${n.id}.cond.then`);
|
|
4046
4683
|
assertExprPortable(n.cond.else, `${at}/${n.id}.cond.else`);
|
|
4684
|
+
} else if ("fanout" in n) {
|
|
4685
|
+
throw new Error(`${at}/${n.id}: fanout node is not supported by litedbmodel (bc FanoutNode)`);
|
|
4047
4686
|
} else {
|
|
4048
4687
|
assertPortsPortable(n.ports, `${at}/${n.id}.ports`);
|
|
4049
4688
|
}
|
|
@@ -4069,18 +4708,63 @@ import {
|
|
|
4069
4708
|
compileBehaviors,
|
|
4070
4709
|
SemanticBehavior
|
|
4071
4710
|
} from "behavior-contracts";
|
|
4711
|
+
var boundRaw;
|
|
4072
4712
|
var boundComponents;
|
|
4713
|
+
var currentResolver;
|
|
4714
|
+
function withColumnResolver(resolve, fn) {
|
|
4715
|
+
const prev = currentResolver;
|
|
4716
|
+
currentResolver = resolve;
|
|
4717
|
+
try {
|
|
4718
|
+
return fn();
|
|
4719
|
+
} finally {
|
|
4720
|
+
currentResolver = prev;
|
|
4721
|
+
}
|
|
4722
|
+
}
|
|
4723
|
+
var READ_TYPED_LEAF = "Select";
|
|
4724
|
+
var WRITE_TYPED_LEAVES = /* @__PURE__ */ new Set(["Insert", "Update", "Delete"]);
|
|
4725
|
+
function wrapLeaf(name, raw) {
|
|
4726
|
+
const isRead = name === READ_TYPED_LEAF;
|
|
4727
|
+
const isWrite = WRITE_TYPED_LEAVES.has(name);
|
|
4728
|
+
if (!isRead && !isWrite) return raw;
|
|
4729
|
+
return (ports) => {
|
|
4730
|
+
const handle = raw(ports);
|
|
4731
|
+
if (isRead && currentResolver === void 0) {
|
|
4732
|
+
throw new Error(
|
|
4733
|
+
`scp authoring: a Select node needs its output row type at compile time (bc 0.8.0 all-nodes-typed gate), but the model declares NO \`columns\` \u2014 a typed read REQUIRES an inline \`static columns\` SoT (or \`options.columns\`). No-assume, no-fallback.`
|
|
4734
|
+
);
|
|
4735
|
+
}
|
|
4736
|
+
const outType = currentResolver !== void 0 ? crudNodeAsType(name, ports, currentResolver, `${name} node`) : { arr: { obj: {} } };
|
|
4737
|
+
return handle.as(outType);
|
|
4738
|
+
};
|
|
4739
|
+
}
|
|
4740
|
+
function rawComponents() {
|
|
4741
|
+
if (boundRaw === void 0) {
|
|
4742
|
+
boundRaw = catalogComponents(LITEDBMODEL_CATALOG);
|
|
4743
|
+
}
|
|
4744
|
+
return boundRaw;
|
|
4745
|
+
}
|
|
4073
4746
|
function components() {
|
|
4074
4747
|
if (boundComponents === void 0) {
|
|
4075
|
-
|
|
4748
|
+
const raw = rawComponents();
|
|
4749
|
+
const wrapped = {};
|
|
4750
|
+
for (const name of Object.keys(raw)) wrapped[name] = wrapLeaf(name, raw[name]);
|
|
4751
|
+
boundComponents = wrapped;
|
|
4076
4752
|
}
|
|
4077
4753
|
return boundComponents;
|
|
4078
4754
|
}
|
|
4079
4755
|
function lowerBehaviorClass(cls, options) {
|
|
4080
|
-
const
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4756
|
+
const className = cls.name ?? "<anonymous>";
|
|
4757
|
+
const declaredColumns = options.columns ?? cls.columns;
|
|
4758
|
+
const columnMap = declaredColumns !== void 0 ? toColumnMap(declaredColumns) : void 0;
|
|
4759
|
+
const materializeResolver = columnMap !== void 0 ? failClosedMaterializeResolverFromColumnMap(columnMap) : void 0;
|
|
4760
|
+
const resolveColumnType = columnMap !== void 0 ? columnTypeResolverFromColumnMap(columnMap) : void 0;
|
|
4761
|
+
const ir = withColumnResolver(
|
|
4762
|
+
resolveColumnType,
|
|
4763
|
+
() => compileBehaviors(cls, {
|
|
4764
|
+
...options.inputPorts !== void 0 ? { inputPorts: options.inputPorts } : {},
|
|
4765
|
+
...options.concurrency !== void 0 ? { concurrency: options.concurrency } : {}
|
|
4766
|
+
})
|
|
4767
|
+
);
|
|
4084
4768
|
assertComponentsInCatalog(ir.components);
|
|
4085
4769
|
assertComponentGraphPortable(ir);
|
|
4086
4770
|
const methods = {};
|
|
@@ -4091,13 +4775,70 @@ function lowerBehaviorClass(cls, options) {
|
|
|
4091
4775
|
component
|
|
4092
4776
|
};
|
|
4093
4777
|
}
|
|
4778
|
+
assertReadColumnsDeclared(className, ir.components, methods, resolveColumnType);
|
|
4094
4779
|
return {
|
|
4095
|
-
className
|
|
4780
|
+
className,
|
|
4096
4781
|
ir,
|
|
4097
4782
|
components: ir.components,
|
|
4098
|
-
methods
|
|
4783
|
+
methods,
|
|
4784
|
+
...materializeResolver !== void 0 ? { materializeResolver } : {},
|
|
4785
|
+
...resolveColumnType !== void 0 ? { resolveColumnType } : {}
|
|
4099
4786
|
};
|
|
4100
4787
|
}
|
|
4788
|
+
function toColumnMap(columns) {
|
|
4789
|
+
const map = /* @__PURE__ */ new Map();
|
|
4790
|
+
for (const [table, cols] of Object.entries(columns)) {
|
|
4791
|
+
const inner = /* @__PURE__ */ new Map();
|
|
4792
|
+
for (const [col, sqlType] of Object.entries(cols)) inner.set(col, sqlType);
|
|
4793
|
+
map.set(table, inner);
|
|
4794
|
+
}
|
|
4795
|
+
return map;
|
|
4796
|
+
}
|
|
4797
|
+
function literalStringPort(ports, name) {
|
|
4798
|
+
const v = ports[name];
|
|
4799
|
+
return typeof v === "string" ? v : void 0;
|
|
4800
|
+
}
|
|
4801
|
+
function literalStringArrayPort(ports, name) {
|
|
4802
|
+
const v = ports[name];
|
|
4803
|
+
if (v !== null && typeof v === "object" && "arr" in v && Array.isArray(v.arr)) {
|
|
4804
|
+
const arr = v.arr;
|
|
4805
|
+
if (arr.every((e) => typeof e === "string")) return arr;
|
|
4806
|
+
}
|
|
4807
|
+
return void 0;
|
|
4808
|
+
}
|
|
4809
|
+
function selectProjectionsOf(component) {
|
|
4810
|
+
const out = [];
|
|
4811
|
+
for (const n of component.body) {
|
|
4812
|
+
if ("cond" in n) continue;
|
|
4813
|
+
if ("fanout" in n) {
|
|
4814
|
+
throw new Error(`selectProjectionsOf: component '${component.name}' node '${n.id}' is a fanout node, not supported by litedbmodel (bc FanoutNode)`);
|
|
4815
|
+
}
|
|
4816
|
+
const ref = "map" in n ? n.map : n;
|
|
4817
|
+
if (ref.component !== "Select") continue;
|
|
4818
|
+
const ports = ref.ports;
|
|
4819
|
+
const table = literalStringPort(ports, "table");
|
|
4820
|
+
const columns = literalStringArrayPort(ports, "select");
|
|
4821
|
+
if (table === void 0 || columns === void 0 || columns.length === 0) continue;
|
|
4822
|
+
out.push({ table, columns });
|
|
4823
|
+
}
|
|
4824
|
+
return out;
|
|
4825
|
+
}
|
|
4826
|
+
function assertReadColumnsDeclared(className, components2, methods, resolveColumnType) {
|
|
4827
|
+
for (const component of components2) {
|
|
4828
|
+
if (methods[component.name]?.effect !== "query") continue;
|
|
4829
|
+
for (const proj of selectProjectionsOf(component)) {
|
|
4830
|
+
const at = `model '${className}' method '${component.name}'`;
|
|
4831
|
+
const schemaCols = proj.columns.map((col) => parseProjectionColumn(col, proj.table, at)).filter((e) => e.kind === "column").map((e) => ({ table: e.qualifier ?? proj.table, column: e.underlying }));
|
|
4832
|
+
if (schemaCols.length === 0) continue;
|
|
4833
|
+
if (resolveColumnType === void 0) {
|
|
4834
|
+
throw new Error(
|
|
4835
|
+
`scp publishBehaviors: ${at} reads projecting typed columns [${schemaCols.map((s) => `${s.table}.${s.column}`).join(", ")}] but the model declares NO \`columns\`. A typed read REQUIRES an inline \`static columns\` declaration so the read-path de-box (INT\u2192number / BIGINT\u2192string / DATE\u2192string / BOOLEAN\u2192boolean) is always-on \u2014 never a silent raw (rounded i64) result. Declare the model's \`static columns\`.`
|
|
4836
|
+
);
|
|
4837
|
+
}
|
|
4838
|
+
for (const s of schemaCols) resolveColumnType(s.table, s.column);
|
|
4839
|
+
}
|
|
4840
|
+
}
|
|
4841
|
+
}
|
|
4101
4842
|
function publishBehaviors(cls, options = {}) {
|
|
4102
4843
|
return lowerBehaviorClass(cls, options);
|
|
4103
4844
|
}
|
|
@@ -4123,36 +4864,9 @@ function makeEagerClass(name, fn) {
|
|
|
4123
4864
|
return cls;
|
|
4124
4865
|
}
|
|
4125
4866
|
|
|
4126
|
-
// src/scp/find-filter-guard.ts
|
|
4127
|
-
var FindFilterLeakError = class extends Error {
|
|
4128
|
-
constructor(modelName, missingKeys) {
|
|
4129
|
-
super(
|
|
4130
|
-
`FIND_FILTER leak: model '${modelName}' declares an implicit scope predicate on [${missingKeys.join(", ")}] that is NOT present in the SCP-authored WHERE. The SCP compile cannot auto-apply FIND_FILTER (no model context at compile time), so the scope predicate would be silently dropped (soft-delete / tenant scope leak). Fold the FIND_FILTER predicates into the authored L.Select({ where }) explicitly (e.g. whereEq/whereIsNull on ${missingKeys.join(", ")}).`
|
|
4131
|
-
);
|
|
4132
|
-
this.modelName = modelName;
|
|
4133
|
-
this.missingKeys = missingKeys;
|
|
4134
|
-
this.name = "FindFilterLeakError";
|
|
4135
|
-
}
|
|
4136
|
-
modelName;
|
|
4137
|
-
missingKeys;
|
|
4138
|
-
};
|
|
4139
|
-
function findFilterKeys(filter) {
|
|
4140
|
-
if (!filter || filter.length === 0) return [];
|
|
4141
|
-
return Object.keys(condsToRecord(filter));
|
|
4142
|
-
}
|
|
4143
|
-
function assertFindFilterFolded(model, authoredWhereKeys) {
|
|
4144
|
-
const required = findFilterKeys(model.FIND_FILTER);
|
|
4145
|
-
if (required.length === 0) return;
|
|
4146
|
-
const present = new Set(authoredWhereKeys);
|
|
4147
|
-
const missing = required.filter((k) => !present.has(k));
|
|
4148
|
-
if (missing.length > 0) {
|
|
4149
|
-
throw new FindFilterLeakError(model.name ?? "<anonymous model>", missing);
|
|
4150
|
-
}
|
|
4151
|
-
}
|
|
4152
|
-
|
|
4153
4867
|
// src/scp/relation.ts
|
|
4154
4868
|
var RELATION_KEYS_HEAD = "__keys";
|
|
4155
|
-
function compileRelationOp(decl) {
|
|
4869
|
+
function compileRelationOp(decl, resolveColumnType) {
|
|
4156
4870
|
if (decl.kind !== "hasMany" && decl.limit !== void 0) {
|
|
4157
4871
|
throw new Error(`relation '${decl.name}': a per-parent 'limit' is only valid for hasMany (got ${decl.kind})`);
|
|
4158
4872
|
}
|
|
@@ -4168,6 +4882,22 @@ function compileRelationOp(decl) {
|
|
|
4168
4882
|
const composite = isCompositeDecl(decl);
|
|
4169
4883
|
const sql2 = compiledBatchSql(decl, dialect);
|
|
4170
4884
|
const conn = decl.connection !== void 0 ? { connection: decl.connection } : {};
|
|
4885
|
+
const materializers = {};
|
|
4886
|
+
if (resolveColumnType !== void 0) {
|
|
4887
|
+
const at = `relation '${decl.name}'`;
|
|
4888
|
+
for (const c of decl.select) {
|
|
4889
|
+
const entry2 = parseProjectionColumn(c, decl.targetTable, at);
|
|
4890
|
+
if (entry2.kind === "computed") continue;
|
|
4891
|
+
const sqlType = resolveColumnType(entry2.qualifier ?? decl.targetTable, entry2.underlying);
|
|
4892
|
+
const klass = sqlTypeToMaterializeClass(sqlType);
|
|
4893
|
+
if (klass !== "passthrough") materializers[entry2.outputKey] = klass;
|
|
4894
|
+
}
|
|
4895
|
+
}
|
|
4896
|
+
const target = {
|
|
4897
|
+
targetTable: decl.targetTable,
|
|
4898
|
+
select: [...decl.select],
|
|
4899
|
+
...Object.keys(materializers).length > 0 ? { materializers } : {}
|
|
4900
|
+
};
|
|
4171
4901
|
if (composite) {
|
|
4172
4902
|
return {
|
|
4173
4903
|
name: decl.name,
|
|
@@ -4176,7 +4906,8 @@ function compileRelationOp(decl) {
|
|
|
4176
4906
|
targetKeys: [...decl.targetKeys],
|
|
4177
4907
|
dialect,
|
|
4178
4908
|
...conn,
|
|
4179
|
-
sql: sql2
|
|
4909
|
+
sql: sql2,
|
|
4910
|
+
...target
|
|
4180
4911
|
};
|
|
4181
4912
|
}
|
|
4182
4913
|
return {
|
|
@@ -4186,7 +4917,8 @@ function compileRelationOp(decl) {
|
|
|
4186
4917
|
targetKey: decl.targetKey,
|
|
4187
4918
|
dialect,
|
|
4188
4919
|
...conn,
|
|
4189
|
-
sql: sql2
|
|
4920
|
+
sql: sql2,
|
|
4921
|
+
...target
|
|
4190
4922
|
};
|
|
4191
4923
|
}
|
|
4192
4924
|
function isCompositeDecl(decl) {
|
|
@@ -4267,7 +4999,14 @@ function runRelationOp(op, parents, db) {
|
|
|
4267
4999
|
const sql2 = renderPlaceholders(cast, op.dialect);
|
|
4268
5000
|
if (keys.length === 0) return { sql: sql2, keys, batch };
|
|
4269
5001
|
const tCols = targetKeyCols(op);
|
|
4270
|
-
const
|
|
5002
|
+
const childCols = op.materializers;
|
|
5003
|
+
const int64Child = childCols !== void 0 && Object.values(childCols).includes("int64");
|
|
5004
|
+
const stmt = db.prepare(sql2);
|
|
5005
|
+
if (int64Child && typeof stmt.safeIntegers === "function") {
|
|
5006
|
+
stmt.safeIntegers(true);
|
|
5007
|
+
}
|
|
5008
|
+
const rawRows = stmt.all(...bindKeys(op, keys));
|
|
5009
|
+
const rows = materializeChildRows(rawRows, childCols, int64Child);
|
|
4271
5010
|
for (const row of rows) {
|
|
4272
5011
|
const k = keyIdentity(tCols.map((c) => row[c]));
|
|
4273
5012
|
const list = batch.get(k);
|
|
@@ -4276,6 +5015,23 @@ function runRelationOp(op, parents, db) {
|
|
|
4276
5015
|
}
|
|
4277
5016
|
return { sql: sql2, keys, batch };
|
|
4278
5017
|
}
|
|
5018
|
+
function materializeChildRows(rows, cols, safeIntegersOn) {
|
|
5019
|
+
if (cols === void 0 && !safeIntegersOn) return rows;
|
|
5020
|
+
for (const row of rows) {
|
|
5021
|
+
for (const key of Object.keys(row)) {
|
|
5022
|
+
const klass = cols?.[key];
|
|
5023
|
+
if (klass !== void 0) {
|
|
5024
|
+
row[key] = materializeCell(row[key], klass);
|
|
5025
|
+
continue;
|
|
5026
|
+
}
|
|
5027
|
+
if (safeIntegersOn && typeof row[key] === "bigint") {
|
|
5028
|
+
const v = row[key];
|
|
5029
|
+
row[key] = v >= BigInt(Number.MIN_SAFE_INTEGER) && v <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(v) : v.toString();
|
|
5030
|
+
}
|
|
5031
|
+
}
|
|
5032
|
+
}
|
|
5033
|
+
return rows;
|
|
5034
|
+
}
|
|
4279
5035
|
function dedupeKeys(parents, keyCols) {
|
|
4280
5036
|
const seen = /* @__PURE__ */ new Set();
|
|
4281
5037
|
const out = [];
|
|
@@ -4402,6 +5158,100 @@ function buildResultSet(rawRows, ops, db, options = {}) {
|
|
|
4402
5158
|
return rows.map((o) => hydrate(o));
|
|
4403
5159
|
}
|
|
4404
5160
|
|
|
5161
|
+
// src/scp/makesql/writeouttype.ts
|
|
5162
|
+
function toPortableScalar2(scalar) {
|
|
5163
|
+
return scalar;
|
|
5164
|
+
}
|
|
5165
|
+
var SHORT_CIRCUIT_TYPE = { obj: { statementId: "string", reason: "string" } };
|
|
5166
|
+
function targetAndReturning(sql2) {
|
|
5167
|
+
const s = sql2.trim();
|
|
5168
|
+
const ins = /^INSERT\s+(?:IGNORE\s+)?INTO\s+([A-Za-z_][A-Za-z0-9_]*)/i.exec(s);
|
|
5169
|
+
const upd = /^UPDATE\s+([A-Za-z_][A-Za-z0-9_]*)/i.exec(s);
|
|
5170
|
+
const del = /^DELETE\s+FROM\s+([A-Za-z_][A-Za-z0-9_]*)/i.exec(s);
|
|
5171
|
+
const m = ins ?? upd ?? del;
|
|
5172
|
+
if (m === null) {
|
|
5173
|
+
throw new Error(
|
|
5174
|
+
`writeouttype: a write body statement's SQL is not a recognized INSERT/UPDATE/DELETE (${JSON.stringify(sql2)}). The typed de-box must read the target table from the base write. No-assume, no-fallback.`
|
|
5175
|
+
);
|
|
5176
|
+
}
|
|
5177
|
+
const table = m[1];
|
|
5178
|
+
const clean = s.replace(/\s*\/\*scp:pk=[^*]*\*\//i, "");
|
|
5179
|
+
const ret = /\bRETURNING\s+(.+?)\s*$/i.exec(clean);
|
|
5180
|
+
if (ret === null) return { table, returning: null };
|
|
5181
|
+
const cols = ret[1].split(",").map((c) => c.trim()).filter((c) => c.length > 0);
|
|
5182
|
+
return { table, returning: cols };
|
|
5183
|
+
}
|
|
5184
|
+
function rowObjType2(table, returning, resolve) {
|
|
5185
|
+
const obj = {};
|
|
5186
|
+
for (const col of returning) {
|
|
5187
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(col)) {
|
|
5188
|
+
throw new Error(
|
|
5189
|
+
`writeouttype: RETURNING column '${col}' on '${table}' is not a bare column name (alias / qualified / computed). The typed de-box needs bare schema columns so each maps to a SQL type. No-assume, no-fallback.`
|
|
5190
|
+
);
|
|
5191
|
+
}
|
|
5192
|
+
if (col in obj) throw new Error(`writeouttype: duplicate RETURNING column '${col}' on '${table}'`);
|
|
5193
|
+
obj[col] = toPortableScalar2(sqlTypeToBcScalar(resolve(table, col)));
|
|
5194
|
+
}
|
|
5195
|
+
return { obj };
|
|
5196
|
+
}
|
|
5197
|
+
function bodyStatements(plan) {
|
|
5198
|
+
return plan.statements.filter((s) => s.role === "body");
|
|
5199
|
+
}
|
|
5200
|
+
function isBatchPlan(plan) {
|
|
5201
|
+
return plan.entityFrom === null && plan.statements.length > 0 && plan.statements.every((s) => s.gate === void 0 && s.binds === void 0 && s.role === "body");
|
|
5202
|
+
}
|
|
5203
|
+
function writeTargetReturning(plan) {
|
|
5204
|
+
const bodies = bodyStatements(plan);
|
|
5205
|
+
if (bodies.length === 0) {
|
|
5206
|
+
throw new Error(
|
|
5207
|
+
`writeouttype: the transaction plan has no 'body' write statement \u2014 a write bundle must carry at least one base write. Cannot derive its output row type. No-assume, no-fallback.`
|
|
5208
|
+
);
|
|
5209
|
+
}
|
|
5210
|
+
let table;
|
|
5211
|
+
let returning;
|
|
5212
|
+
for (const b of bodies) {
|
|
5213
|
+
const { table: t, returning: r } = targetAndReturning(b.op.sql);
|
|
5214
|
+
if (table === void 0) {
|
|
5215
|
+
table = t;
|
|
5216
|
+
returning = r;
|
|
5217
|
+
continue;
|
|
5218
|
+
}
|
|
5219
|
+
if (t !== table) {
|
|
5220
|
+
throw new Error(
|
|
5221
|
+
`writeouttype: batch write body statements target DIFFERENT tables ('${table}' vs '${t}'); the typed de-box requires a homogeneous RETURNING row shape across a batch. No-assume, no-fallback.`
|
|
5222
|
+
);
|
|
5223
|
+
}
|
|
5224
|
+
if (JSON.stringify(returning) !== JSON.stringify(r)) {
|
|
5225
|
+
throw new Error(
|
|
5226
|
+
`writeouttype: batch write body statements on '${table}' have DIFFERENT RETURNING projections (${JSON.stringify(returning)} vs ${JSON.stringify(r)}); the de-boxed row struct must be identical across a batch. No-assume, no-fallback.`
|
|
5227
|
+
);
|
|
5228
|
+
}
|
|
5229
|
+
}
|
|
5230
|
+
return { table, returning: returning ?? null };
|
|
5231
|
+
}
|
|
5232
|
+
function deriveWriteOutputType(plan, resolve) {
|
|
5233
|
+
const { table, returning } = writeTargetReturning(plan);
|
|
5234
|
+
const hasReturning = returning !== null;
|
|
5235
|
+
const row = hasReturning ? rowObjType2(table, returning, resolve) : "null";
|
|
5236
|
+
const fields = {
|
|
5237
|
+
committed: "bool",
|
|
5238
|
+
executed: { arr: "string" },
|
|
5239
|
+
shortCircuit: { opt: SHORT_CIRCUIT_TYPE },
|
|
5240
|
+
entity: { opt: row }
|
|
5241
|
+
};
|
|
5242
|
+
if (isBatchPlan(plan) && hasReturning) fields.returnedRows = { opt: { arr: { arr: row } } };
|
|
5243
|
+
return { obj: fields };
|
|
5244
|
+
}
|
|
5245
|
+
function annotateWriteBundleOutType(bundle, resolve) {
|
|
5246
|
+
if (bundle.transaction === void 0) {
|
|
5247
|
+
throw new Error(
|
|
5248
|
+
`writeouttype: bundle '${bundle.name}' carries no transaction plan \u2014 the write de-box types the TransactionResult of a write-Command / batch bundle. No-assume, no-fallback.`
|
|
5249
|
+
);
|
|
5250
|
+
}
|
|
5251
|
+
const outputType2 = deriveWriteOutputType(bundle.transaction, resolve);
|
|
5252
|
+
return { ...bundle, outputType: outputType2 };
|
|
5253
|
+
}
|
|
5254
|
+
|
|
4405
5255
|
// src/scp/runtime.ts
|
|
4406
5256
|
function primaryNodeOf(component) {
|
|
4407
5257
|
for (const n of component.body) {
|
|
@@ -4418,9 +5268,13 @@ function isWriteComponent(component) {
|
|
|
4418
5268
|
return c === "Insert" || c === "Update" || c === "Delete";
|
|
4419
5269
|
}
|
|
4420
5270
|
function executeBehavior(contract, input, options) {
|
|
4421
|
-
return executeBundle(
|
|
5271
|
+
return executeBundle(
|
|
5272
|
+
compileBundle(contract, options.entry, options.relations, options.dialect, void 0, contract.resolveColumnType),
|
|
5273
|
+
input,
|
|
5274
|
+
options
|
|
5275
|
+
);
|
|
4422
5276
|
}
|
|
4423
|
-
function compileBundle(contract, entry2, relations = [], dialectName = "sqlite") {
|
|
5277
|
+
function compileBundle(contract, entry2, relations = [], dialectName = "sqlite", findFilterModel, resolveColumnType) {
|
|
4424
5278
|
const component = entry2 ? contract.components.find((c) => c.name === entry2) : contract.components[0];
|
|
4425
5279
|
if (component === void 0) throw new Error(`scp runtime: entry component '${entry2 ?? "<first>"}' not found in contract`);
|
|
4426
5280
|
const relationOps = {};
|
|
@@ -4428,11 +5282,11 @@ function compileBundle(contract, entry2, relations = [], dialectName = "sqlite")
|
|
|
4428
5282
|
if (relationOps[decl.name] !== void 0) {
|
|
4429
5283
|
throw new Error(`scp runtime: duplicate relation declaration '${decl.name}'`);
|
|
4430
5284
|
}
|
|
4431
|
-
relationOps[decl.name] = compileRelationOp({ ...decl, dialect: decl.dialect ?? dialectName });
|
|
5285
|
+
relationOps[decl.name] = compileRelationOp({ ...decl, dialect: decl.dialect ?? dialectName }, resolveColumnType);
|
|
4432
5286
|
}
|
|
4433
5287
|
if (isWriteComponent(component)) {
|
|
4434
5288
|
const writeNode = primaryNodeOf(component);
|
|
4435
|
-
const op = compileWriteNode(writeNode);
|
|
5289
|
+
const op = compileWriteNode(writeNode, dialectName);
|
|
4436
5290
|
return {
|
|
4437
5291
|
dialect: dialectName,
|
|
4438
5292
|
name: component.name,
|
|
@@ -4441,7 +5295,7 @@ function compileBundle(contract, entry2, relations = [], dialectName = "sqlite")
|
|
|
4441
5295
|
relations: relationOps
|
|
4442
5296
|
};
|
|
4443
5297
|
}
|
|
4444
|
-
const readGraph = compileReadGraph(contract, dialectName, entry2);
|
|
5298
|
+
const readGraph = compileReadGraph(contract, dialectName, entry2, findFilterModel, resolveColumnType);
|
|
4445
5299
|
return {
|
|
4446
5300
|
dialect: dialectName,
|
|
4447
5301
|
name: readGraph.name,
|
|
@@ -4474,11 +5328,17 @@ async function executeBundleAsync(bundle, input, options) {
|
|
|
4474
5328
|
return executeReadGraphAsync(bundle.readGraph, input, options.exec);
|
|
4475
5329
|
}
|
|
4476
5330
|
async function executeBehaviorAsync(contract, input, options) {
|
|
4477
|
-
return executeBundleAsync(
|
|
5331
|
+
return executeBundleAsync(
|
|
5332
|
+
compileBundle(contract, options.entry, options.relations, options.dialect, void 0, contract.resolveColumnType),
|
|
5333
|
+
input,
|
|
5334
|
+
options
|
|
5335
|
+
);
|
|
4478
5336
|
}
|
|
4479
5337
|
function baseWriteNodeOf(component) {
|
|
4480
5338
|
const writes = component.body.filter(
|
|
4481
|
-
|
|
5339
|
+
// `fanout` (bc 0.7.3+ FanoutNode) is excluded from narrowing here: litedbmodel never emits
|
|
5340
|
+
// it, and it carries no base write op, so it can never be a write node.
|
|
5341
|
+
(n) => !("cond" in n) && !("map" in n) && !("fanout" in n) && (n.component === "Insert" || n.component === "Update" || n.component === "Delete")
|
|
4482
5342
|
);
|
|
4483
5343
|
if (writes.length === 0) {
|
|
4484
5344
|
throw new Error(`scp write: Command '${component.name}' has no base write (Insert/Update/Delete) node`);
|
|
@@ -4490,7 +5350,7 @@ function baseWriteNodeOf(component) {
|
|
|
4490
5350
|
}
|
|
4491
5351
|
return writes[0];
|
|
4492
5352
|
}
|
|
4493
|
-
function compileWriteBundle(contract, entry2, writes, phase, dialectName = "sqlite") {
|
|
5353
|
+
function compileWriteBundle(contract, entry2, writes, phase, dialectName = "sqlite", resolveColumnType) {
|
|
4494
5354
|
const component = contract.components.find((c) => c.name === entry2);
|
|
4495
5355
|
if (component === void 0) throw new Error(`scp write: entry component '${entry2}' not found in contract`);
|
|
4496
5356
|
const lifecycle = lifecycleFor(writes, phase);
|
|
@@ -4498,10 +5358,10 @@ function compileWriteBundle(contract, entry2, writes, phase, dialectName = "sqli
|
|
|
4498
5358
|
throw new Error(`scp write: the '${phase}' lifecycle is not declared in the entityWrites save contract`);
|
|
4499
5359
|
}
|
|
4500
5360
|
const writeNode = baseWriteNodeOf(component);
|
|
4501
|
-
const baseOp = dialectName === "mysql" ? mysqlPkHint(compileWriteNode(writeNode)) : compileWriteNode(writeNode);
|
|
5361
|
+
const baseOp = dialectName === "mysql" ? mysqlPkHint(compileWriteNode(writeNode, dialectName)) : compileWriteNode(writeNode, dialectName);
|
|
4502
5362
|
const base = { op: baseOp, label: `${writeNode.component}` };
|
|
4503
5363
|
const plan = deriveTransactionPlan(phase, [base], lifecycle, dialectName);
|
|
4504
|
-
|
|
5364
|
+
const bundle = {
|
|
4505
5365
|
dialect: dialectName,
|
|
4506
5366
|
name: component.name,
|
|
4507
5367
|
statement: { sql: baseOp.sql, params: baseOp.params },
|
|
@@ -4509,6 +5369,7 @@ function compileWriteBundle(contract, entry2, writes, phase, dialectName = "sqli
|
|
|
4509
5369
|
relations: {},
|
|
4510
5370
|
transaction: plan
|
|
4511
5371
|
};
|
|
5372
|
+
return resolveColumnType === void 0 ? bundle : annotateWriteBundleOutType(bundle, resolveColumnType);
|
|
4512
5373
|
}
|
|
4513
5374
|
function compileCompositeWriteBundle(contract, entries, phase, dialectName = "sqlite") {
|
|
4514
5375
|
if (entries.length < 2) {
|
|
@@ -4522,7 +5383,7 @@ function compileCompositeWriteBundle(contract, entries, phase, dialectName = "sq
|
|
|
4522
5383
|
if (component === void 0) throw new Error(`scp write: entry component '${e.entry}' not found in contract`);
|
|
4523
5384
|
if (firstBaseOp === void 0) firstName = component.name;
|
|
4524
5385
|
const writeNode = baseWriteNodeOf(component);
|
|
4525
|
-
const baseOp = compileWriteNode(writeNode);
|
|
5386
|
+
const baseOp = compileWriteNode(writeNode, dialectName);
|
|
4526
5387
|
if (firstBaseOp === void 0) firstBaseOp = baseOp;
|
|
4527
5388
|
bases.push({ op: baseOp, label: `${writeNode.component} ${e.name}`, name: e.name, effects: e.lifecycle.effects });
|
|
4528
5389
|
}
|
|
@@ -4540,7 +5401,7 @@ function flattenBatchOp(node, label) {
|
|
|
4540
5401
|
const assembled = assembleMakeSQL(node);
|
|
4541
5402
|
return { sql: assembled.sql, params: assembled.params, label };
|
|
4542
5403
|
}
|
|
4543
|
-
function compileCreateManyBundle(name, options, dialectName = "sqlite") {
|
|
5404
|
+
function compileCreateManyBundle(name, options, dialectName = "sqlite", resolveColumnType) {
|
|
4544
5405
|
const components2 = compileInsertMany(dialectName, options);
|
|
4545
5406
|
const ops = components2.map((c, i) => {
|
|
4546
5407
|
const flat = flattenBatchOp(c, `createMany group ${i}`);
|
|
@@ -4550,7 +5411,12 @@ function compileCreateManyBundle(name, options, dialectName = "sqlite") {
|
|
|
4550
5411
|
return flat;
|
|
4551
5412
|
});
|
|
4552
5413
|
const plan = deriveBatchPlan("create", ops);
|
|
4553
|
-
|
|
5414
|
+
const head = ops[0];
|
|
5415
|
+
if (head === void 0) {
|
|
5416
|
+
throw new Error(`scp write: compileCreateManyBundle('${name}') produced no INSERT statements (empty createMany grouping) \u2014 an empty-ops batch bundle is a compile bug, not a silent empty-SQL default.`);
|
|
5417
|
+
}
|
|
5418
|
+
const bundle = { dialect: dialectName, name, statement: { sql: head.sql, params: head.params }, optionalHeads: [], relations: {}, transaction: plan };
|
|
5419
|
+
return resolveColumnType === void 0 ? bundle : annotateWriteBundleOutType(bundle, resolveColumnType);
|
|
4554
5420
|
}
|
|
4555
5421
|
function compileUpdateManyBundle(name, options, dialectName = "sqlite") {
|
|
4556
5422
|
const op = flattenBatchOp(compileUpdateMany(dialectName, options), "updateMany");
|
|
@@ -4581,7 +5447,11 @@ function executeTransactionBundle(bundle, input, options) {
|
|
|
4581
5447
|
return executeTransaction(options.db, bundle.transaction, input, bundle.dialect);
|
|
4582
5448
|
}
|
|
4583
5449
|
function read(contract, input, options) {
|
|
4584
|
-
return readBundle(
|
|
5450
|
+
return readBundle(
|
|
5451
|
+
compileBundle(contract, options.entry, options.relations, options.dialect, void 0, contract.resolveColumnType),
|
|
5452
|
+
input,
|
|
5453
|
+
options
|
|
5454
|
+
);
|
|
4585
5455
|
}
|
|
4586
5456
|
function readBundle(bundle, input, options) {
|
|
4587
5457
|
const out = executeBundle(bundle, input, options);
|
|
@@ -4602,11 +5472,18 @@ function readBundle(bundle, input, options) {
|
|
|
4602
5472
|
}
|
|
4603
5473
|
|
|
4604
5474
|
// src/scp/codegen.ts
|
|
4605
|
-
import { generateModule } from "behavior-contracts";
|
|
5475
|
+
import { generateModule, loadCompiledIR } from "behavior-contracts";
|
|
4606
5476
|
var CODEGEN_LANGUAGES = ["typescript", "python", "go", "rust", "php"];
|
|
5477
|
+
var CODEGEN_EMITTER = {
|
|
5478
|
+
typescript: "typescript-typed",
|
|
5479
|
+
go: "go-typed-native",
|
|
5480
|
+
rust: "rust-typed-native"
|
|
5481
|
+
};
|
|
4607
5482
|
function bundleToPortableIR(bundle) {
|
|
4608
5483
|
if (bundle.readGraph !== void 0) return bundle.readGraph.ir;
|
|
4609
|
-
|
|
5484
|
+
throw new Error(
|
|
5485
|
+
`litedbmodel codegen: bundle '${bundle.name}' has no readGraph \u2014 a WRITE bundle carries a single compiled makeSQL statement, not a portable component-graph IR, and is not a codegen-module case (#12: the hand-built makeSQL write surrogate IR is eliminated; writes ride the write/tx exec path).`
|
|
5486
|
+
);
|
|
4610
5487
|
}
|
|
4611
5488
|
function companionOf(bundle) {
|
|
4612
5489
|
const base = {
|
|
@@ -4614,27 +5491,262 @@ function companionOf(bundle) {
|
|
|
4614
5491
|
optionalHeads: bundle.optionalHeads,
|
|
4615
5492
|
relations: bundle.relations,
|
|
4616
5493
|
...bundle.readGraph !== void 0 ? { readGraph: bundle.readGraph } : {},
|
|
4617
|
-
...bundle.statement !== void 0 ? { statement: bundle.statement } : {}
|
|
5494
|
+
...bundle.statement !== void 0 ? { statement: bundle.statement } : {},
|
|
5495
|
+
...bundle.outputType !== void 0 ? { outputType: bundle.outputType } : {}
|
|
4618
5496
|
};
|
|
4619
5497
|
return bundle.transaction === void 0 ? base : { ...base, transaction: bundle.transaction };
|
|
4620
5498
|
}
|
|
4621
|
-
|
|
5499
|
+
var TypedNativeCoverageError = class extends Error {
|
|
5500
|
+
constructor(component, reasons) {
|
|
5501
|
+
super(
|
|
5502
|
+
`litedbmodel codegen (#60 m1): component '${component}' is not typed-native-coverable:
|
|
5503
|
+
` + reasons.map((r) => ` - ${r}`).join("\n") + `
|
|
5504
|
+
|
|
5505
|
+
bc's typed-native endpoint (bc#77/#90) fails closed on an uncovered shape \u2014 this is NOT regenerated on the boxed '-typed'/'-typed-raw' emitter (retired from litedbmodel's codegen surface, #60 milestone 1). Report this as a bc#86 coverage gap (array-typed / non-scalar input heads have no native port type yet), or exclude this case from native-codegen benching.`
|
|
5506
|
+
);
|
|
5507
|
+
this.component = component;
|
|
5508
|
+
this.reasons = reasons;
|
|
5509
|
+
this.name = "TypedNativeCoverageError";
|
|
5510
|
+
}
|
|
5511
|
+
component;
|
|
5512
|
+
reasons;
|
|
5513
|
+
};
|
|
5514
|
+
var WHERE_FRAGMENT_COLUMN = /^([A-Za-z_][A-Za-z0-9_]*)\s+(?:=|<>|<=|>=|<|>|LIKE|ILIKE)\s*\?$/;
|
|
5515
|
+
var SELECT_FROM_TABLE = /\bFROM\s+([A-Za-z_][A-Za-z0-9_]*)\b/i;
|
|
5516
|
+
var IN_LIST_COLUMN = /^([A-Za-z_][A-Za-z0-9_]*)\s+(?:IN\s*\(|=\s*ANY\s*\()/i;
|
|
5517
|
+
function deriveHeadTypesFromStatements(statements, resolveColumnType, elementVar) {
|
|
5518
|
+
const byHead = /* @__PURE__ */ new Map();
|
|
5519
|
+
const arrayHeads = /* @__PURE__ */ new Map();
|
|
5520
|
+
const unresolved = /* @__PURE__ */ new Map();
|
|
5521
|
+
const head = statements[0];
|
|
5522
|
+
const tableMatch = head === void 0 ? null : SELECT_FROM_TABLE.exec(head.sql);
|
|
5523
|
+
const table = tableMatch?.[1];
|
|
5524
|
+
for (const stmt of statements) {
|
|
5525
|
+
if (stmt.whereFragment !== true) {
|
|
5526
|
+
for (const param of stmt.params) {
|
|
5527
|
+
const lh = limitHeadNameOf(param);
|
|
5528
|
+
if (lh === void 0) continue;
|
|
5529
|
+
if (elementVar !== void 0 && lh === elementVar) continue;
|
|
5530
|
+
const prior = byHead.get(lh);
|
|
5531
|
+
if (prior !== void 0 && prior !== "int") {
|
|
5532
|
+
unresolved.set(lh, `input head '${lh}' resolves to conflicting scalar types ('${prior}' vs 'int' from a LIMIT clause) across statements`);
|
|
5533
|
+
continue;
|
|
5534
|
+
}
|
|
5535
|
+
byHead.set(lh, "int");
|
|
5536
|
+
}
|
|
5537
|
+
continue;
|
|
5538
|
+
}
|
|
5539
|
+
for (const param of stmt.params) {
|
|
5540
|
+
const path = refPathOf(param);
|
|
5541
|
+
if (path !== void 0 && elementVar !== void 0 && path[0] === elementVar) continue;
|
|
5542
|
+
if (path === void 0 || path.length !== 1) {
|
|
5543
|
+
const arrHead = arrayHeadNameOf(param);
|
|
5544
|
+
if (arrHead !== void 0) {
|
|
5545
|
+
if (table === void 0) {
|
|
5546
|
+
unresolved.set(arrHead, `input head '${arrHead}': could not resolve the owning table from the SELECT head SQL ('${head?.sql ?? "<none>"}')`);
|
|
5547
|
+
continue;
|
|
5548
|
+
}
|
|
5549
|
+
const cm = IN_LIST_COLUMN.exec(stmt.sql.trim());
|
|
5550
|
+
if (cm === null) {
|
|
5551
|
+
unresolved.set(
|
|
5552
|
+
arrHead,
|
|
5553
|
+
`input head '${arrHead}': IN-list WHERE fragment SQL '${stmt.sql}' is not the closed '<col> IN (\u2026)'/'<col> = ANY(?)' shape this codegen-only deriver covers`
|
|
5554
|
+
);
|
|
5555
|
+
continue;
|
|
5556
|
+
}
|
|
5557
|
+
const elemType = sqlTypeToBcScalar(resolveColumnType(table, cm[1]));
|
|
5558
|
+
const priorElem = arrayHeads.get(arrHead);
|
|
5559
|
+
if (priorElem !== void 0 && priorElem !== elemType) {
|
|
5560
|
+
unresolved.set(arrHead, `input head '${arrHead}' resolves to conflicting array element scalar types ('${priorElem}' vs '${elemType}') across statements`);
|
|
5561
|
+
continue;
|
|
5562
|
+
}
|
|
5563
|
+
arrayHeads.set(arrHead, elemType);
|
|
5564
|
+
continue;
|
|
5565
|
+
}
|
|
5566
|
+
const named = headNameOf(param);
|
|
5567
|
+
if (named !== void 0) {
|
|
5568
|
+
unresolved.set(
|
|
5569
|
+
named,
|
|
5570
|
+
`input head '${named}' is bound by a non-scalar param shape (${JSON.stringify(param)}) \u2014 bc's typed-native emitter has no native port type for it (string/int/float/bool scalars only).`
|
|
5571
|
+
);
|
|
5572
|
+
}
|
|
5573
|
+
continue;
|
|
5574
|
+
}
|
|
5575
|
+
const headName = path[0];
|
|
5576
|
+
if (table === void 0) {
|
|
5577
|
+
unresolved.set(headName, `input head '${headName}': could not resolve the owning table from the SELECT head SQL ('${head?.sql ?? "<none>"}')`);
|
|
5578
|
+
continue;
|
|
5579
|
+
}
|
|
5580
|
+
const m = WHERE_FRAGMENT_COLUMN.exec(stmt.sql.trim());
|
|
5581
|
+
if (m === null) {
|
|
5582
|
+
unresolved.set(
|
|
5583
|
+
headName,
|
|
5584
|
+
`input head '${headName}': WHERE fragment SQL '${stmt.sql}' is not the closed '<col> <op> ?' shape this codegen-only deriver covers`
|
|
5585
|
+
);
|
|
5586
|
+
continue;
|
|
5587
|
+
}
|
|
5588
|
+
const column = m[1];
|
|
5589
|
+
const sqlType = resolveColumnType(table, column);
|
|
5590
|
+
const scalar = sqlTypeToBcScalar(sqlType);
|
|
5591
|
+
const prior = byHead.get(headName);
|
|
5592
|
+
if (prior !== void 0 && prior !== scalar) {
|
|
5593
|
+
unresolved.set(headName, `input head '${headName}' resolves to conflicting scalar types ('${prior}' vs '${scalar}') across statements`);
|
|
5594
|
+
continue;
|
|
5595
|
+
}
|
|
5596
|
+
byHead.set(headName, scalar);
|
|
5597
|
+
}
|
|
5598
|
+
}
|
|
5599
|
+
for (const head2 of arrayHeads.keys()) {
|
|
5600
|
+
if (byHead.has(head2)) {
|
|
5601
|
+
unresolved.set(head2, `input head '${head2}' is bound both as a scalar and as an array param \u2014 no single native port type`);
|
|
5602
|
+
}
|
|
5603
|
+
}
|
|
5604
|
+
return { byHead, arrayHeads, unresolved };
|
|
5605
|
+
}
|
|
5606
|
+
function refPathOf(param) {
|
|
5607
|
+
if (param === null || typeof param !== "object" || Array.isArray(param)) return void 0;
|
|
5608
|
+
const keys = Object.keys(param);
|
|
5609
|
+
if (keys.length !== 1 || keys[0] !== "ref" && keys[0] !== "refOpt") return void 0;
|
|
5610
|
+
const path = param[keys[0]];
|
|
5611
|
+
if (!Array.isArray(path) || path.length === 0 || !path.every((s) => typeof s === "string")) return void 0;
|
|
5612
|
+
return path;
|
|
5613
|
+
}
|
|
5614
|
+
function headNameOf(param) {
|
|
5615
|
+
if (param === null || typeof param !== "object" || Array.isArray(param)) return void 0;
|
|
5616
|
+
const obj = param;
|
|
5617
|
+
if ("__jsonArray" in obj) {
|
|
5618
|
+
const inner = refPathOf(obj.__jsonArray);
|
|
5619
|
+
return inner?.[0];
|
|
5620
|
+
}
|
|
5621
|
+
return void 0;
|
|
5622
|
+
}
|
|
5623
|
+
function elementFieldRefs(statements, elementVar) {
|
|
5624
|
+
const out = [];
|
|
5625
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5626
|
+
for (const stmt of statements) {
|
|
5627
|
+
for (const param of stmt.params) {
|
|
5628
|
+
const path = refPathOf(param);
|
|
5629
|
+
if (path === void 0 || path.length !== 2 || path[0] !== elementVar) continue;
|
|
5630
|
+
const field = path[1];
|
|
5631
|
+
if (!seen.has(field)) {
|
|
5632
|
+
seen.add(field);
|
|
5633
|
+
out.push(field);
|
|
5634
|
+
}
|
|
5635
|
+
}
|
|
5636
|
+
}
|
|
5637
|
+
return out;
|
|
5638
|
+
}
|
|
5639
|
+
function limitHeadNameOf(param) {
|
|
5640
|
+
if (param === null || typeof param !== "object" || Array.isArray(param)) return void 0;
|
|
5641
|
+
const obj = param;
|
|
5642
|
+
const args = obj.coalesce;
|
|
5643
|
+
if (!Array.isArray(args) || args.length !== 2) return void 0;
|
|
5644
|
+
const [refPart, dflt] = args;
|
|
5645
|
+
if (typeof dflt !== "number" || !Number.isInteger(dflt)) return void 0;
|
|
5646
|
+
if (refPart === null || typeof refPart !== "object" || Array.isArray(refPart)) return void 0;
|
|
5647
|
+
const rp = refPart;
|
|
5648
|
+
const keys = Object.keys(rp);
|
|
5649
|
+
if (keys.length !== 1 || keys[0] !== "ref" && keys[0] !== "refOpt") return void 0;
|
|
5650
|
+
const path = rp[keys[0]];
|
|
5651
|
+
if (!Array.isArray(path) || path.length !== 1 || typeof path[0] !== "string") return void 0;
|
|
5652
|
+
return path[0];
|
|
5653
|
+
}
|
|
5654
|
+
function arrayHeadNameOf(param) {
|
|
5655
|
+
if (param === null || typeof param !== "object" || Array.isArray(param)) return void 0;
|
|
5656
|
+
const obj = param;
|
|
5657
|
+
if (!("__jsonArray" in obj)) return void 0;
|
|
5658
|
+
const inner = refPathOf(obj.__jsonArray);
|
|
5659
|
+
return inner !== void 0 && inner.length === 1 ? inner[0] : void 0;
|
|
5660
|
+
}
|
|
5661
|
+
function lowerReadGraphForTypedNative(readGraph, resolveColumnType) {
|
|
5662
|
+
const ir = readGraph.ir;
|
|
5663
|
+
const reasons = [];
|
|
5664
|
+
const components2 = ir.components.map((c) => {
|
|
5665
|
+
const inputPortTypes = /* @__PURE__ */ new Map();
|
|
5666
|
+
const inputPortElemTypes = /* @__PURE__ */ new Map();
|
|
5667
|
+
const body = c.body.map((n) => {
|
|
5668
|
+
if ("cond" in n) return n;
|
|
5669
|
+
const isMapNode = "map" in n;
|
|
5670
|
+
const stmts = readGraph.statementsById[n.id] ?? [];
|
|
5671
|
+
const elementVar = isMapNode ? n.map.as ?? void 0 : void 0;
|
|
5672
|
+
const { byHead, arrayHeads, unresolved } = deriveHeadTypesFromStatements(stmts, resolveColumnType, elementVar);
|
|
5673
|
+
for (const [, reason] of unresolved) reasons.push(`node '${n.id}': ${reason}`);
|
|
5674
|
+
for (const [head, scalar] of byHead) {
|
|
5675
|
+
const prior = inputPortTypes.get(head);
|
|
5676
|
+
if (prior !== void 0 && prior !== scalar) {
|
|
5677
|
+
reasons.push(`input head '${head}' resolves to conflicting scalar types ('${prior}' vs '${scalar}') across nodes`);
|
|
5678
|
+
continue;
|
|
5679
|
+
}
|
|
5680
|
+
inputPortTypes.set(head, scalar);
|
|
5681
|
+
}
|
|
5682
|
+
for (const [head, elem] of arrayHeads) {
|
|
5683
|
+
const prior = inputPortElemTypes.get(head);
|
|
5684
|
+
if (prior !== void 0 && prior !== elem) {
|
|
5685
|
+
reasons.push(`input head '${head}' resolves to conflicting array element scalar types ('${prior}' vs '${elem}') across nodes`);
|
|
5686
|
+
continue;
|
|
5687
|
+
}
|
|
5688
|
+
inputPortElemTypes.set(head, elem);
|
|
5689
|
+
}
|
|
5690
|
+
const newPorts = {};
|
|
5691
|
+
if (isMapNode && elementVar !== void 0) {
|
|
5692
|
+
const fields = elementFieldRefs(stmts, elementVar);
|
|
5693
|
+
for (const field of fields) newPorts[field] = { ref: [elementVar, field] };
|
|
5694
|
+
}
|
|
5695
|
+
for (const head of [...byHead.keys(), ...arrayHeads.keys()]) {
|
|
5696
|
+
newPorts[head] = { ref: [head] };
|
|
5697
|
+
}
|
|
5698
|
+
return isMapNode ? { ...n, map: { ...n.map, ports: newPorts } } : { ...n, ports: newPorts };
|
|
5699
|
+
});
|
|
5700
|
+
if (reasons.length > 0) throw new TypedNativeCoverageError(c.name, reasons);
|
|
5701
|
+
const inputPorts = {};
|
|
5702
|
+
for (const [name, scalar] of inputPortTypes) {
|
|
5703
|
+
const schema = (c.inputPorts ?? {})[name];
|
|
5704
|
+
inputPorts[name] = schema === void 0 ? { required: true, type: scalar } : { ...schema, type: scalar };
|
|
5705
|
+
}
|
|
5706
|
+
for (const [name, elem] of inputPortElemTypes) {
|
|
5707
|
+
const schema = (c.inputPorts ?? {})[name];
|
|
5708
|
+
inputPorts[name] = { ...schema ?? { required: true }, type: "array", elemType: elem };
|
|
5709
|
+
}
|
|
5710
|
+
return { ...c, body, inputPorts };
|
|
5711
|
+
});
|
|
5712
|
+
return { ...ir, components: components2 };
|
|
5713
|
+
}
|
|
5714
|
+
function typedEmitterFor(language, registered) {
|
|
4622
5715
|
if (!CODEGEN_LANGUAGES.includes(language)) {
|
|
4623
5716
|
throw new Error(
|
|
4624
5717
|
`litedbmodel codegen: language '${language}' is not a supported target (supported: ${CODEGEN_LANGUAGES.join(", ")})`
|
|
4625
5718
|
);
|
|
4626
5719
|
}
|
|
4627
|
-
|
|
5720
|
+
const emitter = CODEGEN_EMITTER[language];
|
|
5721
|
+
if (emitter === void 0) {
|
|
4628
5722
|
throw new Error(
|
|
4629
|
-
`litedbmodel codegen:
|
|
5723
|
+
`litedbmodel codegen: no READ codegen endpoint for '${language}'. litedbmodel codegen is STATIC de-interpreted codegen (spec \xA79) with NO literal/interpreter/boxed fallback (an unspec'd fallback is invalid). bc registers a READ endpoint only for ${Object.keys(CODEGEN_EMITTER).join("/")} \u2014 a '${language}' endpoint is a bc capability gap. ESCALATE to bc (bc#22 pattern); never substitute the literal emitter.`
|
|
4630
5724
|
);
|
|
4631
5725
|
}
|
|
5726
|
+
if (!registered.includes(emitter)) {
|
|
5727
|
+
throw new Error(
|
|
5728
|
+
`litedbmodel codegen: bc's shared generator does not register the '${emitter}' emitter for '${language}' (bc registered: ${[...registered].sort().join(", ")}). ESCALATE to bc (bc#22 pattern), never fork locally.`
|
|
5729
|
+
);
|
|
5730
|
+
}
|
|
5731
|
+
return emitter;
|
|
5732
|
+
}
|
|
5733
|
+
function codegenEmitterFor(language, registered) {
|
|
5734
|
+
return typedEmitterFor(language, registered);
|
|
4632
5735
|
}
|
|
4633
|
-
function
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
5736
|
+
function needsHeadLowering(_emitter) {
|
|
5737
|
+
return true;
|
|
5738
|
+
}
|
|
5739
|
+
function generateCodegenArtifact(bundle, language, registeredLanguages, resolveColumnType, runtimeImport) {
|
|
5740
|
+
if (bundle.readGraph === void 0) {
|
|
5741
|
+
throw new Error(
|
|
5742
|
+
`litedbmodel codegen (#60 m1): bundle '${bundle.name}' has no readGraph \u2014 WRITE bundles are not codegen-module cases (they stay on the existing write/tx execution path). Only READ bundles are generated through generateCodegenArtifact.`
|
|
5743
|
+
);
|
|
5744
|
+
}
|
|
5745
|
+
const emitter = typedEmitterFor(language, registeredLanguages);
|
|
5746
|
+
const ir = needsHeadLowering(emitter) ? lowerReadGraphForTypedNative(bundle.readGraph, resolveColumnType) : bundleToPortableIR(bundle);
|
|
5747
|
+
const compiled = loadCompiledIR(ir);
|
|
5748
|
+
const module = generateModule(compiled, runtimeImport === void 0 ? { language: emitter } : { language: emitter, runtimeImport });
|
|
5749
|
+
return { language, module, companion: companionOf(bundle), bundle };
|
|
4638
5750
|
}
|
|
4639
5751
|
function codegenExecuteBundleForTest(artifact, input, db) {
|
|
4640
5752
|
return executeBundle(artifact.bundle, input, { db });
|
|
@@ -4667,6 +5779,7 @@ import {
|
|
|
4667
5779
|
} from "behavior-contracts";
|
|
4668
5780
|
export {
|
|
4669
5781
|
CATALOG_NAMES,
|
|
5782
|
+
CODEGEN_EMITTER,
|
|
4670
5783
|
CODEGEN_LANGUAGES,
|
|
4671
5784
|
ENTITY_ROOT,
|
|
4672
5785
|
FindFilterLeakError,
|
|
@@ -4682,6 +5795,7 @@ export {
|
|
|
4682
5795
|
SQLITE,
|
|
4683
5796
|
SemanticBehavior2 as SemanticBehavior,
|
|
4684
5797
|
SqlFailure,
|
|
5798
|
+
TypedNativeCoverageError,
|
|
4685
5799
|
WRITE_CATALOG_NAMES,
|
|
4686
5800
|
WRITE_PORT_FAMILIES,
|
|
4687
5801
|
add,
|
|
@@ -4693,14 +5807,15 @@ export {
|
|
|
4693
5807
|
assertComponentsInCatalog,
|
|
4694
5808
|
assertExprPortable,
|
|
4695
5809
|
assertFindFilterFolded,
|
|
4696
|
-
assertLanguageSupported,
|
|
4697
5810
|
behavior,
|
|
4698
5811
|
buildResultSet,
|
|
4699
5812
|
builderFor,
|
|
4700
5813
|
bundleToPortableIR,
|
|
4701
5814
|
catalogEntry,
|
|
4702
5815
|
coalesce,
|
|
5816
|
+
codegenEmitterFor,
|
|
4703
5817
|
codegenExecuteBundleForTest,
|
|
5818
|
+
columnTypeResolverFromColumnMap,
|
|
4704
5819
|
compileAuthoredBehavior,
|
|
4705
5820
|
compileAuthoredNode,
|
|
4706
5821
|
compileBundle,
|
|
@@ -4714,6 +5829,7 @@ export {
|
|
|
4714
5829
|
compileDeleteManyBundle,
|
|
4715
5830
|
compileEager,
|
|
4716
5831
|
compileInsert as compileInsertMakeSQL,
|
|
5832
|
+
compileInsertMany,
|
|
4717
5833
|
compileReadGraph,
|
|
4718
5834
|
compileRelationMap,
|
|
4719
5835
|
compileRelationOp,
|
|
@@ -4731,7 +5847,10 @@ export {
|
|
|
4731
5847
|
components,
|
|
4732
5848
|
composeMakeSQL,
|
|
4733
5849
|
concat,
|
|
5850
|
+
configurePgDeboxTypeParsers,
|
|
4734
5851
|
countingDriver,
|
|
5852
|
+
dbCast,
|
|
5853
|
+
dbCastIn,
|
|
4735
5854
|
deriveContractEffect,
|
|
4736
5855
|
deriveTransactionPlan,
|
|
4737
5856
|
dialectFor,
|
|
@@ -4753,6 +5872,7 @@ export {
|
|
|
4753
5872
|
executeStaticWrite,
|
|
4754
5873
|
executeTransaction,
|
|
4755
5874
|
executeTransactionBundle,
|
|
5875
|
+
failClosedMaterializeResolverFromColumnMap,
|
|
4756
5876
|
findFilterKeys,
|
|
4757
5877
|
formatterFor,
|
|
4758
5878
|
ge2 as ge,
|
|
@@ -4764,15 +5884,19 @@ export {
|
|
|
4764
5884
|
le2 as le,
|
|
4765
5885
|
len,
|
|
4766
5886
|
lifecycleFor,
|
|
5887
|
+
lowerReadGraphForTypedNative,
|
|
4767
5888
|
lt2 as lt,
|
|
4768
5889
|
makeSqlCatalogEntry,
|
|
4769
|
-
makeSqlComponentIR,
|
|
4770
5890
|
makeSqlHandler,
|
|
4771
5891
|
makeSqlHandlerSync,
|
|
4772
|
-
makeSqlInput,
|
|
4773
5892
|
mapSqliteError,
|
|
5893
|
+
materializeCell,
|
|
5894
|
+
materializeClassOrUndefined,
|
|
5895
|
+
materializeResolverFromColumnMap,
|
|
4774
5896
|
mod,
|
|
4775
5897
|
mul,
|
|
5898
|
+
mysqlDeboxExecutor,
|
|
5899
|
+
mysqlDeboxPoolOptions,
|
|
4776
5900
|
mysqlPkHint,
|
|
4777
5901
|
mysqlPoolExecutor,
|
|
4778
5902
|
ne2 as ne,
|
|
@@ -4782,7 +5906,9 @@ export {
|
|
|
4782
5906
|
opt,
|
|
4783
5907
|
or,
|
|
4784
5908
|
parseEffectPath,
|
|
5909
|
+
parseSchemaColumnTypes,
|
|
4785
5910
|
pgCastFormatter,
|
|
5911
|
+
pgDeboxExecutor,
|
|
4786
5912
|
pgPoolExecutor,
|
|
4787
5913
|
publishBehaviors,
|
|
4788
5914
|
read,
|
|
@@ -4794,9 +5920,13 @@ export {
|
|
|
4794
5920
|
renderTxStatement,
|
|
4795
5921
|
resolvePgArrayCast,
|
|
4796
5922
|
runRelationOp,
|
|
5923
|
+
schemaColumnTypeResolver,
|
|
5924
|
+
sqlTypeToBcScalar,
|
|
5925
|
+
sqlTypeToMaterializeClass,
|
|
4797
5926
|
stripMysqlPkHint,
|
|
4798
5927
|
sub,
|
|
4799
5928
|
toDollarPlaceholders,
|
|
5929
|
+
typedEmitterFor,
|
|
4800
5930
|
when,
|
|
4801
5931
|
whereBetween,
|
|
4802
5932
|
whereCast,
|