litedbmodel 2.0.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +44 -24
- package/dist/decorators.js.map +1 -1
- package/dist/drivers/mysql.d.ts.map +1 -1
- package/dist/drivers/mysql.js +6 -0
- package/dist/drivers/mysql.js.map +1 -1
- package/dist/drivers/postgres.d.ts.map +1 -1
- package/dist/drivers/postgres.js +7 -0
- package/dist/drivers/postgres.js.map +1 -1
- package/dist/drivers/sqlite.d.ts +2 -0
- package/dist/drivers/sqlite.d.ts.map +1 -1
- package/dist/drivers/sqlite.js +37 -2
- package/dist/drivers/sqlite.js.map +1 -1
- package/dist/scp/authoring.d.ts +66 -3
- package/dist/scp/authoring.d.ts.map +1 -1
- package/dist/scp/authoring.js +194 -5
- package/dist/scp/authoring.js.map +1 -1
- package/dist/scp/catalog.d.ts +2 -1
- package/dist/scp/catalog.d.ts.map +1 -1
- package/dist/scp/catalog.js +12 -1
- package/dist/scp/catalog.js.map +1 -1
- package/dist/scp/codegen.d.ts +43 -31
- package/dist/scp/codegen.d.ts.map +1 -1
- package/dist/scp/codegen.js +279 -95
- package/dist/scp/codegen.js.map +1 -1
- package/dist/scp/coltype.d.ts +83 -0
- package/dist/scp/coltype.d.ts.map +1 -1
- package/dist/scp/coltype.js +220 -0
- package/dist/scp/coltype.js.map +1 -1
- package/dist/scp/guard.d.ts +1 -1
- package/dist/scp/guard.d.ts.map +1 -1
- package/dist/scp/guard.js +6 -0
- package/dist/scp/guard.js.map +1 -1
- package/dist/scp/index.cjs +7149 -9379
- package/dist/scp/index.d.ts +7 -6
- package/dist/scp/index.d.ts.map +1 -1
- package/dist/scp/index.js +21 -6
- package/dist/scp/index.js.map +1 -1
- package/dist/scp/index.mjs +729 -178
- package/dist/scp/makesql/authoring-compile.d.ts.map +1 -1
- package/dist/scp/makesql/authoring-compile.js +11 -0
- package/dist/scp/makesql/authoring-compile.js.map +1 -1
- package/dist/scp/makesql/index.d.ts +2 -3
- package/dist/scp/makesql/index.d.ts.map +1 -1
- package/dist/scp/makesql/index.js +6 -6
- package/dist/scp/makesql/index.js.map +1 -1
- package/dist/scp/makesql/json-batch.d.ts.map +1 -1
- package/dist/scp/makesql/json-batch.js +6 -1
- package/dist/scp/makesql/json-batch.js.map +1 -1
- package/dist/scp/makesql/outtype.d.ts +61 -6
- package/dist/scp/makesql/outtype.d.ts.map +1 -1
- package/dist/scp/makesql/outtype.js +164 -33
- package/dist/scp/makesql/outtype.js.map +1 -1
- package/dist/scp/makesql/pool-executor.d.ts +70 -0
- package/dist/scp/makesql/pool-executor.d.ts.map +1 -1
- package/dist/scp/makesql/pool-executor.js +73 -5
- package/dist/scp/makesql/pool-executor.js.map +1 -1
- package/dist/scp/makesql/static-bundle.d.ts +39 -30
- package/dist/scp/makesql/static-bundle.d.ts.map +1 -1
- package/dist/scp/makesql/static-bundle.js +269 -138
- package/dist/scp/makesql/static-bundle.js.map +1 -1
- package/dist/scp/makesql/writeouttype.d.ts +5 -6
- package/dist/scp/makesql/writeouttype.d.ts.map +1 -1
- package/dist/scp/makesql/writeouttype.js +5 -6
- package/dist/scp/makesql/writeouttype.js.map +1 -1
- package/dist/scp/relation.d.ts +15 -1
- package/dist/scp/relation.d.ts.map +1 -1
- package/dist/scp/relation.js +61 -2
- package/dist/scp/relation.js.map +1 -1
- package/dist/scp/runtime.d.ts +6 -5
- package/dist/scp/runtime.d.ts.map +1 -1
- package/dist/scp/runtime.js +27 -5
- package/dist/scp/runtime.js.map +1 -1
- package/docs/BENCHMARK.md +58 -56
- package/docs/benchmark-chart.svg +166 -166
- package/package.json +8 -2
- package/dist/scp/makesql/ir.d.ts +0 -42
- package/dist/scp/makesql/ir.d.ts.map +0 -1
- package/dist/scp/makesql/ir.js +0 -66
- package/dist/scp/makesql/ir.js.map +0 -1
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
|
}
|
|
@@ -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
|
|
|
@@ -2802,6 +2773,9 @@ import { evaluateExpression as evaluateExpression2 } from "behavior-contracts";
|
|
|
2802
2773
|
function isMap(n) {
|
|
2803
2774
|
return "map" in n;
|
|
2804
2775
|
}
|
|
2776
|
+
function isFanout(n) {
|
|
2777
|
+
return "fanout" in n;
|
|
2778
|
+
}
|
|
2805
2779
|
function nodeRef(n) {
|
|
2806
2780
|
return isMap(n) ? { component: n.map.component, ports: n.map.ports } : { component: n.component, ports: n.ports };
|
|
2807
2781
|
}
|
|
@@ -3040,6 +3014,9 @@ function compileAuthoredBehavior(contract, scope, dialect, entry2) {
|
|
|
3040
3014
|
let primaryNode;
|
|
3041
3015
|
for (const n of component.body) {
|
|
3042
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
|
+
}
|
|
3043
3020
|
if (isMap(n)) {
|
|
3044
3021
|
relations.push(n);
|
|
3045
3022
|
continue;
|
|
@@ -3070,6 +3047,7 @@ function normalizeInput(component, input) {
|
|
|
3070
3047
|
}
|
|
3071
3048
|
function skipGuardHeads(n, into) {
|
|
3072
3049
|
if ("cond" in n) return;
|
|
3050
|
+
if (isFanout(n)) return;
|
|
3073
3051
|
const ports = isMap(n) ? n.map.ports : n.ports;
|
|
3074
3052
|
const where = ports.where;
|
|
3075
3053
|
if (typeof where !== "object" || where === null || !("arr" in where)) return;
|
|
@@ -3099,9 +3077,7 @@ function collectRefHeads2(node, into) {
|
|
|
3099
3077
|
|
|
3100
3078
|
// src/scp/makesql/static-bundle.ts
|
|
3101
3079
|
import {
|
|
3102
|
-
evaluateExpression as evaluateExpression3
|
|
3103
|
-
runBehavior,
|
|
3104
|
-
runBehaviorAsync
|
|
3080
|
+
evaluateExpression as evaluateExpression3
|
|
3105
3081
|
} from "behavior-contracts";
|
|
3106
3082
|
|
|
3107
3083
|
// src/scp/find-filter-guard.ts
|
|
@@ -3187,6 +3163,142 @@ function sqlTypeToBcScalar(sqlType) {
|
|
|
3187
3163
|
);
|
|
3188
3164
|
}
|
|
3189
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
|
+
}
|
|
3190
3302
|
function parseSchemaColumnTypes(ddl) {
|
|
3191
3303
|
const tables = /* @__PURE__ */ new Map();
|
|
3192
3304
|
const CREATE = /^\s*CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?([A-Za-z0-9_."`[\]]+)\s*\(([\s\S]*)\)\s*;?\s*$/i;
|
|
@@ -3293,7 +3405,29 @@ function stringArrayPort2(ports, name) {
|
|
|
3293
3405
|
}
|
|
3294
3406
|
throw new Error(`outtype: port '${name}' must be an {arr:[\u2026]} literal in the IR (got ${JSON.stringify(v)})`);
|
|
3295
3407
|
}
|
|
3296
|
-
function
|
|
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) {
|
|
3297
3431
|
if (projection === void 0 || projection.length === 0) {
|
|
3298
3432
|
throw new Error(
|
|
3299
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).`
|
|
@@ -3301,30 +3435,37 @@ function rowObjType(table, projection, resolveColumnType, at) {
|
|
|
3301
3435
|
}
|
|
3302
3436
|
const obj = {};
|
|
3303
3437
|
for (const col of projection) {
|
|
3304
|
-
|
|
3438
|
+
const entry2 = parseProjectionColumn(col, table, at);
|
|
3439
|
+
if (entry2.kind === "computed") {
|
|
3305
3440
|
throw new Error(
|
|
3306
|
-
`outtype: ${at}: projected
|
|
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.`
|
|
3307
3442
|
);
|
|
3308
3443
|
}
|
|
3309
|
-
|
|
3310
|
-
obj
|
|
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;
|
|
3311
3451
|
}
|
|
3312
3452
|
return { obj };
|
|
3313
3453
|
}
|
|
3314
|
-
function
|
|
3454
|
+
function nodeTypes(node, resolveColumnType) {
|
|
3315
3455
|
const { component, ports } = nodeRef2(node);
|
|
3316
3456
|
const at = `node '${node.id}'`;
|
|
3317
|
-
if (component === "Count") return "int";
|
|
3457
|
+
if (component === "Count") return { outType: "int", materializers: {} };
|
|
3318
3458
|
if (component === "Select") {
|
|
3319
3459
|
const table = stringPort3(ports, "table");
|
|
3320
3460
|
if (table === void 0) throw new Error(`outtype: ${at}: Select node requires a literal 'table' port`);
|
|
3321
3461
|
const projection = stringArrayPort2(ports, "select");
|
|
3322
|
-
const
|
|
3323
|
-
|
|
3324
|
-
|
|
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 };
|
|
3325
3466
|
}
|
|
3326
3467
|
throw new Error(
|
|
3327
|
-
`outtype: ${at}: component '${component}' has no typed outType (typed read
|
|
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.`
|
|
3328
3469
|
);
|
|
3329
3470
|
}
|
|
3330
3471
|
function outputType(output, byNode, at) {
|
|
@@ -3366,13 +3507,51 @@ function outputType(output, byNode, at) {
|
|
|
3366
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.`
|
|
3367
3508
|
);
|
|
3368
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
|
+
}
|
|
3369
3545
|
function deriveReadOutTypes(component, resolveColumnType) {
|
|
3370
3546
|
const byNode = /* @__PURE__ */ new Map();
|
|
3547
|
+
const materializersByNode = /* @__PURE__ */ new Map();
|
|
3371
3548
|
for (const n of component.body) {
|
|
3372
3549
|
if ("cond" in n) continue;
|
|
3373
|
-
|
|
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);
|
|
3374
3553
|
}
|
|
3375
|
-
return { byNode, outputType: outputType(component.output, byNode, `component '${component.name}' output`) };
|
|
3554
|
+
return { byNode, outputType: outputType(component.output, byNode, `component '${component.name}' output`), materializersByNode };
|
|
3376
3555
|
}
|
|
3377
3556
|
|
|
3378
3557
|
// src/scp/authoring-sql.ts
|
|
@@ -3449,6 +3628,9 @@ function whereExists($, sub2, not2 = false) {
|
|
|
3449
3628
|
function isMap3(n) {
|
|
3450
3629
|
return "map" in n;
|
|
3451
3630
|
}
|
|
3631
|
+
function isFanout2(n) {
|
|
3632
|
+
return "fanout" in n;
|
|
3633
|
+
}
|
|
3452
3634
|
function nodeRef3(n) {
|
|
3453
3635
|
return isMap3(n) ? { component: n.map.component, ports: n.map.ports } : { component: n.component, ports: n.ports };
|
|
3454
3636
|
}
|
|
@@ -3755,6 +3937,9 @@ function compileStaticBundle(contract, dialect, entry2) {
|
|
|
3755
3937
|
let primary;
|
|
3756
3938
|
for (const n of component.body) {
|
|
3757
3939
|
if ("cond" in n) continue;
|
|
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
|
+
}
|
|
3758
3943
|
if (isMap3(n)) continue;
|
|
3759
3944
|
if (primary === void 0) primary = n;
|
|
3760
3945
|
}
|
|
@@ -3795,6 +3980,7 @@ function collectRefOptHeads(node, into) {
|
|
|
3795
3980
|
}
|
|
3796
3981
|
function skipGuardHeads2(n, into) {
|
|
3797
3982
|
if ("cond" in n) return;
|
|
3983
|
+
if (isFanout2(n)) return;
|
|
3798
3984
|
const ports = isMap3(n) ? n.map.ports : n.ports;
|
|
3799
3985
|
const where = ports.where;
|
|
3800
3986
|
if (typeof where !== "object" || where === null || !("arr" in where)) return;
|
|
@@ -3929,38 +4115,6 @@ function executeStaticWrite(bundle, input, db) {
|
|
|
3929
4115
|
throw mapSqliteError(e);
|
|
3930
4116
|
}
|
|
3931
4117
|
}
|
|
3932
|
-
var SCOPE_PORT = "__scope";
|
|
3933
|
-
var NODE_COMPONENT = "__makeSqlNode";
|
|
3934
|
-
function collectAllHeads(node, heads) {
|
|
3935
|
-
if (node === null || typeof node !== "object") return;
|
|
3936
|
-
if (Array.isArray(node)) {
|
|
3937
|
-
for (const el of node) collectAllHeads(el, heads);
|
|
3938
|
-
return;
|
|
3939
|
-
}
|
|
3940
|
-
const keys = Object.keys(node);
|
|
3941
|
-
if (keys.length === 1 && (keys[0] === "ref" || keys[0] === "refOpt")) {
|
|
3942
|
-
const path = node[keys[0]];
|
|
3943
|
-
if (Array.isArray(path) && typeof path[0] === "string") heads.add(path[0]);
|
|
3944
|
-
return;
|
|
3945
|
-
}
|
|
3946
|
-
for (const k of keys) collectAllHeads(node[k], heads);
|
|
3947
|
-
}
|
|
3948
|
-
function statementHeads(statements) {
|
|
3949
|
-
const heads = /* @__PURE__ */ new Set();
|
|
3950
|
-
for (const stmt of statements) {
|
|
3951
|
-
for (const p of stmt.params) collectAllHeads(p, heads);
|
|
3952
|
-
if (stmt.skip !== void 0) collectAllHeads(stmt.skip, heads);
|
|
3953
|
-
}
|
|
3954
|
-
return heads;
|
|
3955
|
-
}
|
|
3956
|
-
function scopePort(statements) {
|
|
3957
|
-
const obj = {};
|
|
3958
|
-
for (const head of statementHeads(statements)) {
|
|
3959
|
-
if (head === IN_SENTINEL) continue;
|
|
3960
|
-
obj[head] = { ref: [head] };
|
|
3961
|
-
}
|
|
3962
|
-
return { obj };
|
|
3963
|
-
}
|
|
3964
4118
|
function compileNodeStatements(node, dialect) {
|
|
3965
4119
|
const component = "map" in node ? node.map.component : node.component;
|
|
3966
4120
|
if (component === "Select" || component === "Count") return compileSelectNode(node, dialect);
|
|
@@ -3973,6 +4127,9 @@ function compileReadGraph(contract, dialect, entry2, findFilterModel, resolveCol
|
|
|
3973
4127
|
if (findFilterModel !== void 0) {
|
|
3974
4128
|
for (const n of component.body) {
|
|
3975
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
|
+
}
|
|
3976
4133
|
const ref = "map" in n ? n.map : n;
|
|
3977
4134
|
if (ref.component !== "Select" && ref.component !== "Count") continue;
|
|
3978
4135
|
assertFindFilterFolded(findFilterModel, authoredWhereKeysOf(ref.ports));
|
|
@@ -3981,79 +4138,184 @@ function compileReadGraph(contract, dialect, entry2, findFilterModel, resolveCol
|
|
|
3981
4138
|
const statementsById = {};
|
|
3982
4139
|
for (const n of component.body) {
|
|
3983
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
|
+
}
|
|
3984
4144
|
statementsById[n.id] = compileNodeStatements(n, dialect);
|
|
3985
4145
|
}
|
|
3986
|
-
const
|
|
3987
|
-
const
|
|
4146
|
+
const readTypes = resolveColumnType !== void 0 ? deriveReadOutTypes(component, resolveColumnType) : void 0;
|
|
4147
|
+
const materializers = readTypes?.materializersByNode;
|
|
4148
|
+
const annotatedBody = component.body.map((n) => {
|
|
3988
4149
|
if ("cond" in n) return n;
|
|
3989
|
-
const
|
|
3990
|
-
|
|
3991
|
-
const outAnn = outType !== void 0 ? { outType } : {};
|
|
3992
|
-
if ("map" in n) {
|
|
3993
|
-
return { ...n, ...outAnn, map: { ...n.map, component: NODE_COMPONENT, ports: { [SCOPE_PORT]: scopePort(stmts) } } };
|
|
3994
|
-
}
|
|
3995
|
-
return { ...n, ...outAnn, component: NODE_COMPONENT, ports: { [SCOPE_PORT]: scopePort(stmts) } };
|
|
4150
|
+
const outType = readTypes?.byNode.get(n.id);
|
|
4151
|
+
return outType !== void 0 ? { ...n, outType } : n;
|
|
3996
4152
|
});
|
|
3997
|
-
const
|
|
4153
|
+
const irComponent = {
|
|
3998
4154
|
...component,
|
|
3999
|
-
body:
|
|
4000
|
-
...
|
|
4155
|
+
body: annotatedBody,
|
|
4156
|
+
...readTypes !== void 0 ? { outputType: readTypes.outputType } : {}
|
|
4001
4157
|
};
|
|
4002
|
-
const ir = {
|
|
4158
|
+
const ir = { ...contract.ir, components: [irComponent] };
|
|
4003
4159
|
return {
|
|
4004
4160
|
dialect,
|
|
4005
4161
|
name: component.name,
|
|
4006
4162
|
ir,
|
|
4007
4163
|
statementsById,
|
|
4008
|
-
optionalHeads: [...optionalHeadsOfComponent(component, dialect)]
|
|
4164
|
+
optionalHeads: [...optionalHeadsOfComponent(component, dialect)],
|
|
4165
|
+
...materializers !== void 0 && materializers.size > 0 ? { materializersByNode: Object.fromEntries(materializers) } : {}
|
|
4009
4166
|
};
|
|
4010
4167
|
}
|
|
4011
|
-
function
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
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
|
+
}
|
|
4025
4181
|
}
|
|
4026
|
-
}
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
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);
|
|
4030
4258
|
try {
|
|
4031
|
-
|
|
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);
|
|
4032
4272
|
} catch (e) {
|
|
4033
4273
|
throw reErrorToSqlFailure(e);
|
|
4034
4274
|
}
|
|
4035
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
|
+
}
|
|
4036
4301
|
async function executeReadGraphAsync(graph, input, exec) {
|
|
4037
|
-
const
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
return { error: `static-bundle: node '${ctx.nodeId}' surrogate scope did not evaluate to an object` };
|
|
4043
|
-
}
|
|
4044
|
-
const { sql: sql2, params } = renderStatements(stmts, graph.dialect, scope);
|
|
4045
|
-
try {
|
|
4046
|
-
const rows = await exec(sql2, params);
|
|
4047
|
-
return { ok: rows };
|
|
4048
|
-
} catch (e) {
|
|
4049
|
-
return { error: mapSqliteError(e).message };
|
|
4050
|
-
}
|
|
4051
|
-
};
|
|
4052
|
-
const handlers = { [NODE_COMPONENT]: handle };
|
|
4053
|
-
const normalized = { ...input };
|
|
4054
|
-
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);
|
|
4055
4307
|
try {
|
|
4056
|
-
|
|
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);
|
|
4057
4319
|
} catch (e) {
|
|
4058
4320
|
throw reErrorToSqlFailure(e);
|
|
4059
4321
|
}
|
|
@@ -4078,6 +4340,7 @@ function optionalHeadsOfComponent(component, dialect) {
|
|
|
4078
4340
|
for (const n of component.body) skipGuardHeads2(n, optional);
|
|
4079
4341
|
for (const n of component.body) {
|
|
4080
4342
|
if ("cond" in n) continue;
|
|
4343
|
+
if (isFanout2(n)) continue;
|
|
4081
4344
|
const stmts = compileNodeStatements(n, dialect);
|
|
4082
4345
|
for (const stmt of stmts) {
|
|
4083
4346
|
for (const p of stmt.params) collectRefOptHeads(p, optional);
|
|
@@ -4094,12 +4357,35 @@ function reErrorToSqlFailure(e) {
|
|
|
4094
4357
|
}
|
|
4095
4358
|
|
|
4096
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
|
+
};
|
|
4097
4374
|
function pgPoolExecutor(pool) {
|
|
4098
4375
|
return async (sql2, params) => {
|
|
4099
4376
|
const result = await pool.query(sql2, params);
|
|
4100
4377
|
return result.rows;
|
|
4101
4378
|
};
|
|
4102
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
|
+
}
|
|
4103
4389
|
function mysqlPoolExecutor(pool) {
|
|
4104
4390
|
return async (sql2, params) => {
|
|
4105
4391
|
const [rows] = await pool.query(sql2, params);
|
|
@@ -4165,7 +4451,10 @@ var LITEDBMODEL_CATALOG = {
|
|
|
4165
4451
|
// Only `table` (required) + the optional `where` fragment tree — v1's count carries no
|
|
4166
4452
|
// projection/order/limit/offset (it counts the filtered rows). Output is a one-row `[{count}]`
|
|
4167
4453
|
// list (the `items` shape, like every read); the consumer reads `count` — v1's `parseInt(count)`.
|
|
4168
|
-
|
|
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" },
|
|
4169
4458
|
// Writes yield the RETURNING row list (`items`); a single-row write collapses at the
|
|
4170
4459
|
// consumer boundary, same as reads.
|
|
4171
4460
|
Insert: entry("Insert", WRITE_PORTS, "items", true),
|
|
@@ -4211,6 +4500,10 @@ function assertComponentsInCatalog(components2) {
|
|
|
4211
4500
|
for (const c of components2) {
|
|
4212
4501
|
for (const n of c.body) {
|
|
4213
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
|
+
}
|
|
4214
4507
|
const ref = "map" in n ? n.map : n;
|
|
4215
4508
|
const entryDef = LITEDBMODEL_CATALOG[ref.component];
|
|
4216
4509
|
if (entryDef === void 0) {
|
|
@@ -4388,6 +4681,8 @@ function assertComponentPortable(component) {
|
|
|
4388
4681
|
assertExprPortable(n.cond.if, `${at}/${n.id}.cond.if`);
|
|
4389
4682
|
assertExprPortable(n.cond.then, `${at}/${n.id}.cond.then`);
|
|
4390
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)`);
|
|
4391
4686
|
} else {
|
|
4392
4687
|
assertPortsPortable(n.ports, `${at}/${n.id}.ports`);
|
|
4393
4688
|
}
|
|
@@ -4413,18 +4708,63 @@ import {
|
|
|
4413
4708
|
compileBehaviors,
|
|
4414
4709
|
SemanticBehavior
|
|
4415
4710
|
} from "behavior-contracts";
|
|
4711
|
+
var boundRaw;
|
|
4416
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
|
+
}
|
|
4417
4746
|
function components() {
|
|
4418
4747
|
if (boundComponents === void 0) {
|
|
4419
|
-
|
|
4748
|
+
const raw = rawComponents();
|
|
4749
|
+
const wrapped = {};
|
|
4750
|
+
for (const name of Object.keys(raw)) wrapped[name] = wrapLeaf(name, raw[name]);
|
|
4751
|
+
boundComponents = wrapped;
|
|
4420
4752
|
}
|
|
4421
4753
|
return boundComponents;
|
|
4422
4754
|
}
|
|
4423
4755
|
function lowerBehaviorClass(cls, options) {
|
|
4424
|
-
const
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
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
|
+
);
|
|
4428
4768
|
assertComponentsInCatalog(ir.components);
|
|
4429
4769
|
assertComponentGraphPortable(ir);
|
|
4430
4770
|
const methods = {};
|
|
@@ -4435,13 +4775,70 @@ function lowerBehaviorClass(cls, options) {
|
|
|
4435
4775
|
component
|
|
4436
4776
|
};
|
|
4437
4777
|
}
|
|
4778
|
+
assertReadColumnsDeclared(className, ir.components, methods, resolveColumnType);
|
|
4438
4779
|
return {
|
|
4439
|
-
className
|
|
4780
|
+
className,
|
|
4440
4781
|
ir,
|
|
4441
4782
|
components: ir.components,
|
|
4442
|
-
methods
|
|
4783
|
+
methods,
|
|
4784
|
+
...materializeResolver !== void 0 ? { materializeResolver } : {},
|
|
4785
|
+
...resolveColumnType !== void 0 ? { resolveColumnType } : {}
|
|
4443
4786
|
};
|
|
4444
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
|
+
}
|
|
4445
4842
|
function publishBehaviors(cls, options = {}) {
|
|
4446
4843
|
return lowerBehaviorClass(cls, options);
|
|
4447
4844
|
}
|
|
@@ -4469,7 +4866,7 @@ function makeEagerClass(name, fn) {
|
|
|
4469
4866
|
|
|
4470
4867
|
// src/scp/relation.ts
|
|
4471
4868
|
var RELATION_KEYS_HEAD = "__keys";
|
|
4472
|
-
function compileRelationOp(decl) {
|
|
4869
|
+
function compileRelationOp(decl, resolveColumnType) {
|
|
4473
4870
|
if (decl.kind !== "hasMany" && decl.limit !== void 0) {
|
|
4474
4871
|
throw new Error(`relation '${decl.name}': a per-parent 'limit' is only valid for hasMany (got ${decl.kind})`);
|
|
4475
4872
|
}
|
|
@@ -4485,6 +4882,22 @@ function compileRelationOp(decl) {
|
|
|
4485
4882
|
const composite = isCompositeDecl(decl);
|
|
4486
4883
|
const sql2 = compiledBatchSql(decl, dialect);
|
|
4487
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
|
+
};
|
|
4488
4901
|
if (composite) {
|
|
4489
4902
|
return {
|
|
4490
4903
|
name: decl.name,
|
|
@@ -4493,7 +4906,8 @@ function compileRelationOp(decl) {
|
|
|
4493
4906
|
targetKeys: [...decl.targetKeys],
|
|
4494
4907
|
dialect,
|
|
4495
4908
|
...conn,
|
|
4496
|
-
sql: sql2
|
|
4909
|
+
sql: sql2,
|
|
4910
|
+
...target
|
|
4497
4911
|
};
|
|
4498
4912
|
}
|
|
4499
4913
|
return {
|
|
@@ -4503,7 +4917,8 @@ function compileRelationOp(decl) {
|
|
|
4503
4917
|
targetKey: decl.targetKey,
|
|
4504
4918
|
dialect,
|
|
4505
4919
|
...conn,
|
|
4506
|
-
sql: sql2
|
|
4920
|
+
sql: sql2,
|
|
4921
|
+
...target
|
|
4507
4922
|
};
|
|
4508
4923
|
}
|
|
4509
4924
|
function isCompositeDecl(decl) {
|
|
@@ -4584,7 +4999,14 @@ function runRelationOp(op, parents, db) {
|
|
|
4584
4999
|
const sql2 = renderPlaceholders(cast, op.dialect);
|
|
4585
5000
|
if (keys.length === 0) return { sql: sql2, keys, batch };
|
|
4586
5001
|
const tCols = targetKeyCols(op);
|
|
4587
|
-
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);
|
|
4588
5010
|
for (const row of rows) {
|
|
4589
5011
|
const k = keyIdentity(tCols.map((c) => row[c]));
|
|
4590
5012
|
const list = batch.get(k);
|
|
@@ -4593,6 +5015,23 @@ function runRelationOp(op, parents, db) {
|
|
|
4593
5015
|
}
|
|
4594
5016
|
return { sql: sql2, keys, batch };
|
|
4595
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
|
+
}
|
|
4596
5035
|
function dedupeKeys(parents, keyCols) {
|
|
4597
5036
|
const seen = /* @__PURE__ */ new Set();
|
|
4598
5037
|
const out = [];
|
|
@@ -4829,7 +5268,11 @@ function isWriteComponent(component) {
|
|
|
4829
5268
|
return c === "Insert" || c === "Update" || c === "Delete";
|
|
4830
5269
|
}
|
|
4831
5270
|
function executeBehavior(contract, input, options) {
|
|
4832
|
-
return executeBundle(
|
|
5271
|
+
return executeBundle(
|
|
5272
|
+
compileBundle(contract, options.entry, options.relations, options.dialect, void 0, contract.resolveColumnType),
|
|
5273
|
+
input,
|
|
5274
|
+
options
|
|
5275
|
+
);
|
|
4833
5276
|
}
|
|
4834
5277
|
function compileBundle(contract, entry2, relations = [], dialectName = "sqlite", findFilterModel, resolveColumnType) {
|
|
4835
5278
|
const component = entry2 ? contract.components.find((c) => c.name === entry2) : contract.components[0];
|
|
@@ -4839,7 +5282,7 @@ function compileBundle(contract, entry2, relations = [], dialectName = "sqlite",
|
|
|
4839
5282
|
if (relationOps[decl.name] !== void 0) {
|
|
4840
5283
|
throw new Error(`scp runtime: duplicate relation declaration '${decl.name}'`);
|
|
4841
5284
|
}
|
|
4842
|
-
relationOps[decl.name] = compileRelationOp({ ...decl, dialect: decl.dialect ?? dialectName });
|
|
5285
|
+
relationOps[decl.name] = compileRelationOp({ ...decl, dialect: decl.dialect ?? dialectName }, resolveColumnType);
|
|
4843
5286
|
}
|
|
4844
5287
|
if (isWriteComponent(component)) {
|
|
4845
5288
|
const writeNode = primaryNodeOf(component);
|
|
@@ -4885,11 +5328,17 @@ async function executeBundleAsync(bundle, input, options) {
|
|
|
4885
5328
|
return executeReadGraphAsync(bundle.readGraph, input, options.exec);
|
|
4886
5329
|
}
|
|
4887
5330
|
async function executeBehaviorAsync(contract, input, options) {
|
|
4888
|
-
return executeBundleAsync(
|
|
5331
|
+
return executeBundleAsync(
|
|
5332
|
+
compileBundle(contract, options.entry, options.relations, options.dialect, void 0, contract.resolveColumnType),
|
|
5333
|
+
input,
|
|
5334
|
+
options
|
|
5335
|
+
);
|
|
4889
5336
|
}
|
|
4890
5337
|
function baseWriteNodeOf(component) {
|
|
4891
5338
|
const writes = component.body.filter(
|
|
4892
|
-
|
|
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")
|
|
4893
5342
|
);
|
|
4894
5343
|
if (writes.length === 0) {
|
|
4895
5344
|
throw new Error(`scp write: Command '${component.name}' has no base write (Insert/Update/Delete) node`);
|
|
@@ -4998,7 +5447,11 @@ function executeTransactionBundle(bundle, input, options) {
|
|
|
4998
5447
|
return executeTransaction(options.db, bundle.transaction, input, bundle.dialect);
|
|
4999
5448
|
}
|
|
5000
5449
|
function read(contract, input, options) {
|
|
5001
|
-
return readBundle(
|
|
5450
|
+
return readBundle(
|
|
5451
|
+
compileBundle(contract, options.entry, options.relations, options.dialect, void 0, contract.resolveColumnType),
|
|
5452
|
+
input,
|
|
5453
|
+
options
|
|
5454
|
+
);
|
|
5002
5455
|
}
|
|
5003
5456
|
function readBundle(bundle, input, options) {
|
|
5004
5457
|
const out = executeBundle(bundle, input, options);
|
|
@@ -5019,7 +5472,7 @@ function readBundle(bundle, input, options) {
|
|
|
5019
5472
|
}
|
|
5020
5473
|
|
|
5021
5474
|
// src/scp/codegen.ts
|
|
5022
|
-
import { generateModule } from "behavior-contracts";
|
|
5475
|
+
import { generateModule, loadCompiledIR } from "behavior-contracts";
|
|
5023
5476
|
var CODEGEN_LANGUAGES = ["typescript", "python", "go", "rust", "php"];
|
|
5024
5477
|
var CODEGEN_EMITTER = {
|
|
5025
5478
|
typescript: "typescript-typed",
|
|
@@ -5028,12 +5481,9 @@ var CODEGEN_EMITTER = {
|
|
|
5028
5481
|
};
|
|
5029
5482
|
function bundleToPortableIR(bundle) {
|
|
5030
5483
|
if (bundle.readGraph !== void 0) return bundle.readGraph.ir;
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
|
|
5034
|
-
const body = c.body.map((n, i) => i === 0 ? { ...n, outType: bundle.outputType } : n);
|
|
5035
|
-
const component = { ...c, body, outputType: bundle.outputType };
|
|
5036
|
-
return { ...ir, components: [component] };
|
|
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
|
+
);
|
|
5037
5487
|
}
|
|
5038
5488
|
function companionOf(bundle) {
|
|
5039
5489
|
const base = {
|
|
@@ -5046,7 +5496,6 @@ function companionOf(bundle) {
|
|
|
5046
5496
|
};
|
|
5047
5497
|
return bundle.transaction === void 0 ? base : { ...base, transaction: bundle.transaction };
|
|
5048
5498
|
}
|
|
5049
|
-
var SCOPE_PORT2 = "__scope";
|
|
5050
5499
|
var TypedNativeCoverageError = class extends Error {
|
|
5051
5500
|
constructor(component, reasons) {
|
|
5052
5501
|
super(
|
|
@@ -5064,17 +5513,56 @@ bc's typed-native endpoint (bc#77/#90) fails closed on an uncovered shape \u2014
|
|
|
5064
5513
|
};
|
|
5065
5514
|
var WHERE_FRAGMENT_COLUMN = /^([A-Za-z_][A-Za-z0-9_]*)\s+(?:=|<>|<=|>=|<|>|LIKE|ILIKE)\s*\?$/;
|
|
5066
5515
|
var SELECT_FROM_TABLE = /\bFROM\s+([A-Za-z_][A-Za-z0-9_]*)\b/i;
|
|
5067
|
-
|
|
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) {
|
|
5068
5518
|
const byHead = /* @__PURE__ */ new Map();
|
|
5519
|
+
const arrayHeads = /* @__PURE__ */ new Map();
|
|
5069
5520
|
const unresolved = /* @__PURE__ */ new Map();
|
|
5070
5521
|
const head = statements[0];
|
|
5071
5522
|
const tableMatch = head === void 0 ? null : SELECT_FROM_TABLE.exec(head.sql);
|
|
5072
5523
|
const table = tableMatch?.[1];
|
|
5073
5524
|
for (const stmt of statements) {
|
|
5074
|
-
if (stmt.whereFragment !== true)
|
|
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
|
+
}
|
|
5075
5539
|
for (const param of stmt.params) {
|
|
5076
5540
|
const path = refPathOf(param);
|
|
5541
|
+
if (path !== void 0 && elementVar !== void 0 && path[0] === elementVar) continue;
|
|
5077
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
|
+
}
|
|
5078
5566
|
const named = headNameOf(param);
|
|
5079
5567
|
if (named !== void 0) {
|
|
5080
5568
|
unresolved.set(
|
|
@@ -5108,7 +5596,12 @@ function deriveHeadTypesFromStatements(statements, resolveColumnType) {
|
|
|
5108
5596
|
byHead.set(headName, scalar);
|
|
5109
5597
|
}
|
|
5110
5598
|
}
|
|
5111
|
-
|
|
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 };
|
|
5112
5605
|
}
|
|
5113
5606
|
function refPathOf(param) {
|
|
5114
5607
|
if (param === null || typeof param !== "object" || Array.isArray(param)) return void 0;
|
|
@@ -5127,22 +5620,56 @@ function headNameOf(param) {
|
|
|
5127
5620
|
}
|
|
5128
5621
|
return void 0;
|
|
5129
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
|
+
}
|
|
5130
5661
|
function lowerReadGraphForTypedNative(readGraph, resolveColumnType) {
|
|
5131
5662
|
const ir = readGraph.ir;
|
|
5132
5663
|
const reasons = [];
|
|
5133
5664
|
const components2 = ir.components.map((c) => {
|
|
5134
5665
|
const inputPortTypes = /* @__PURE__ */ new Map();
|
|
5666
|
+
const inputPortElemTypes = /* @__PURE__ */ new Map();
|
|
5135
5667
|
const body = c.body.map((n) => {
|
|
5136
5668
|
if ("cond" in n) return n;
|
|
5137
5669
|
const isMapNode = "map" in n;
|
|
5138
|
-
const ref = isMapNode ? n.map : n;
|
|
5139
|
-
const scopeVal = ref.ports[SCOPE_PORT2];
|
|
5140
|
-
if (scopeVal === void 0 || typeof scopeVal !== "object" || scopeVal === null || !("obj" in scopeVal)) {
|
|
5141
|
-
return n;
|
|
5142
|
-
}
|
|
5143
|
-
const obj = scopeVal.obj;
|
|
5144
5670
|
const stmts = readGraph.statementsById[n.id] ?? [];
|
|
5145
|
-
const
|
|
5671
|
+
const elementVar = isMapNode ? n.map.as ?? void 0 : void 0;
|
|
5672
|
+
const { byHead, arrayHeads, unresolved } = deriveHeadTypesFromStatements(stmts, resolveColumnType, elementVar);
|
|
5146
5673
|
for (const [, reason] of unresolved) reasons.push(`node '${n.id}': ${reason}`);
|
|
5147
5674
|
for (const [head, scalar] of byHead) {
|
|
5148
5675
|
const prior = inputPortTypes.get(head);
|
|
@@ -5152,12 +5679,20 @@ function lowerReadGraphForTypedNative(readGraph, resolveColumnType) {
|
|
|
5152
5679
|
}
|
|
5153
5680
|
inputPortTypes.set(head, scalar);
|
|
5154
5681
|
}
|
|
5155
|
-
const
|
|
5156
|
-
|
|
5157
|
-
if (
|
|
5158
|
-
reasons.push(`
|
|
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`);
|
|
5159
5686
|
continue;
|
|
5160
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()]) {
|
|
5161
5696
|
newPorts[head] = { ref: [head] };
|
|
5162
5697
|
}
|
|
5163
5698
|
return isMapNode ? { ...n, map: { ...n.map, ports: newPorts } } : { ...n, ports: newPorts };
|
|
@@ -5168,6 +5703,10 @@ function lowerReadGraphForTypedNative(readGraph, resolveColumnType) {
|
|
|
5168
5703
|
const schema = (c.inputPorts ?? {})[name];
|
|
5169
5704
|
inputPorts[name] = schema === void 0 ? { required: true, type: scalar } : { ...schema, type: scalar };
|
|
5170
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
|
+
}
|
|
5171
5710
|
return { ...c, body, inputPorts };
|
|
5172
5711
|
});
|
|
5173
5712
|
return { ...ir, components: components2 };
|
|
@@ -5194,8 +5733,8 @@ function typedEmitterFor(language, registered) {
|
|
|
5194
5733
|
function codegenEmitterFor(language, registered) {
|
|
5195
5734
|
return typedEmitterFor(language, registered);
|
|
5196
5735
|
}
|
|
5197
|
-
function
|
|
5198
|
-
return
|
|
5736
|
+
function needsHeadLowering(_emitter) {
|
|
5737
|
+
return true;
|
|
5199
5738
|
}
|
|
5200
5739
|
function generateCodegenArtifact(bundle, language, registeredLanguages, resolveColumnType, runtimeImport) {
|
|
5201
5740
|
if (bundle.readGraph === void 0) {
|
|
@@ -5204,8 +5743,9 @@ function generateCodegenArtifact(bundle, language, registeredLanguages, resolveC
|
|
|
5204
5743
|
);
|
|
5205
5744
|
}
|
|
5206
5745
|
const emitter = typedEmitterFor(language, registeredLanguages);
|
|
5207
|
-
const ir =
|
|
5208
|
-
const
|
|
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 });
|
|
5209
5749
|
return { language, module, companion: companionOf(bundle), bundle };
|
|
5210
5750
|
}
|
|
5211
5751
|
function codegenExecuteBundleForTest(artifact, input, db) {
|
|
@@ -5275,6 +5815,7 @@ export {
|
|
|
5275
5815
|
coalesce,
|
|
5276
5816
|
codegenEmitterFor,
|
|
5277
5817
|
codegenExecuteBundleForTest,
|
|
5818
|
+
columnTypeResolverFromColumnMap,
|
|
5278
5819
|
compileAuthoredBehavior,
|
|
5279
5820
|
compileAuthoredNode,
|
|
5280
5821
|
compileBundle,
|
|
@@ -5288,6 +5829,7 @@ export {
|
|
|
5288
5829
|
compileDeleteManyBundle,
|
|
5289
5830
|
compileEager,
|
|
5290
5831
|
compileInsert as compileInsertMakeSQL,
|
|
5832
|
+
compileInsertMany,
|
|
5291
5833
|
compileReadGraph,
|
|
5292
5834
|
compileRelationMap,
|
|
5293
5835
|
compileRelationOp,
|
|
@@ -5305,7 +5847,10 @@ export {
|
|
|
5305
5847
|
components,
|
|
5306
5848
|
composeMakeSQL,
|
|
5307
5849
|
concat,
|
|
5850
|
+
configurePgDeboxTypeParsers,
|
|
5308
5851
|
countingDriver,
|
|
5852
|
+
dbCast,
|
|
5853
|
+
dbCastIn,
|
|
5309
5854
|
deriveContractEffect,
|
|
5310
5855
|
deriveTransactionPlan,
|
|
5311
5856
|
dialectFor,
|
|
@@ -5327,6 +5872,7 @@ export {
|
|
|
5327
5872
|
executeStaticWrite,
|
|
5328
5873
|
executeTransaction,
|
|
5329
5874
|
executeTransactionBundle,
|
|
5875
|
+
failClosedMaterializeResolverFromColumnMap,
|
|
5330
5876
|
findFilterKeys,
|
|
5331
5877
|
formatterFor,
|
|
5332
5878
|
ge2 as ge,
|
|
@@ -5341,13 +5887,16 @@ export {
|
|
|
5341
5887
|
lowerReadGraphForTypedNative,
|
|
5342
5888
|
lt2 as lt,
|
|
5343
5889
|
makeSqlCatalogEntry,
|
|
5344
|
-
makeSqlComponentIR,
|
|
5345
5890
|
makeSqlHandler,
|
|
5346
5891
|
makeSqlHandlerSync,
|
|
5347
|
-
makeSqlInput,
|
|
5348
5892
|
mapSqliteError,
|
|
5893
|
+
materializeCell,
|
|
5894
|
+
materializeClassOrUndefined,
|
|
5895
|
+
materializeResolverFromColumnMap,
|
|
5349
5896
|
mod,
|
|
5350
5897
|
mul,
|
|
5898
|
+
mysqlDeboxExecutor,
|
|
5899
|
+
mysqlDeboxPoolOptions,
|
|
5351
5900
|
mysqlPkHint,
|
|
5352
5901
|
mysqlPoolExecutor,
|
|
5353
5902
|
ne2 as ne,
|
|
@@ -5359,6 +5908,7 @@ export {
|
|
|
5359
5908
|
parseEffectPath,
|
|
5360
5909
|
parseSchemaColumnTypes,
|
|
5361
5910
|
pgCastFormatter,
|
|
5911
|
+
pgDeboxExecutor,
|
|
5362
5912
|
pgPoolExecutor,
|
|
5363
5913
|
publishBehaviors,
|
|
5364
5914
|
read,
|
|
@@ -5372,6 +5922,7 @@ export {
|
|
|
5372
5922
|
runRelationOp,
|
|
5373
5923
|
schemaColumnTypeResolver,
|
|
5374
5924
|
sqlTypeToBcScalar,
|
|
5925
|
+
sqlTypeToMaterializeClass,
|
|
5375
5926
|
stripMysqlPkHint,
|
|
5376
5927
|
sub,
|
|
5377
5928
|
toDollarPlaceholders,
|