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.
Files changed (80) hide show
  1. package/dist/decorators.d.ts.map +1 -1
  2. package/dist/decorators.js +44 -24
  3. package/dist/decorators.js.map +1 -1
  4. package/dist/drivers/mysql.d.ts.map +1 -1
  5. package/dist/drivers/mysql.js +6 -0
  6. package/dist/drivers/mysql.js.map +1 -1
  7. package/dist/drivers/postgres.d.ts.map +1 -1
  8. package/dist/drivers/postgres.js +7 -0
  9. package/dist/drivers/postgres.js.map +1 -1
  10. package/dist/drivers/sqlite.d.ts +2 -0
  11. package/dist/drivers/sqlite.d.ts.map +1 -1
  12. package/dist/drivers/sqlite.js +37 -2
  13. package/dist/drivers/sqlite.js.map +1 -1
  14. package/dist/scp/authoring.d.ts +66 -3
  15. package/dist/scp/authoring.d.ts.map +1 -1
  16. package/dist/scp/authoring.js +194 -5
  17. package/dist/scp/authoring.js.map +1 -1
  18. package/dist/scp/catalog.d.ts +2 -1
  19. package/dist/scp/catalog.d.ts.map +1 -1
  20. package/dist/scp/catalog.js +12 -1
  21. package/dist/scp/catalog.js.map +1 -1
  22. package/dist/scp/codegen.d.ts +43 -31
  23. package/dist/scp/codegen.d.ts.map +1 -1
  24. package/dist/scp/codegen.js +279 -95
  25. package/dist/scp/codegen.js.map +1 -1
  26. package/dist/scp/coltype.d.ts +83 -0
  27. package/dist/scp/coltype.d.ts.map +1 -1
  28. package/dist/scp/coltype.js +220 -0
  29. package/dist/scp/coltype.js.map +1 -1
  30. package/dist/scp/guard.d.ts +1 -1
  31. package/dist/scp/guard.d.ts.map +1 -1
  32. package/dist/scp/guard.js +6 -0
  33. package/dist/scp/guard.js.map +1 -1
  34. package/dist/scp/index.cjs +7149 -9379
  35. package/dist/scp/index.d.ts +7 -6
  36. package/dist/scp/index.d.ts.map +1 -1
  37. package/dist/scp/index.js +21 -6
  38. package/dist/scp/index.js.map +1 -1
  39. package/dist/scp/index.mjs +729 -178
  40. package/dist/scp/makesql/authoring-compile.d.ts.map +1 -1
  41. package/dist/scp/makesql/authoring-compile.js +11 -0
  42. package/dist/scp/makesql/authoring-compile.js.map +1 -1
  43. package/dist/scp/makesql/index.d.ts +2 -3
  44. package/dist/scp/makesql/index.d.ts.map +1 -1
  45. package/dist/scp/makesql/index.js +6 -6
  46. package/dist/scp/makesql/index.js.map +1 -1
  47. package/dist/scp/makesql/json-batch.d.ts.map +1 -1
  48. package/dist/scp/makesql/json-batch.js +6 -1
  49. package/dist/scp/makesql/json-batch.js.map +1 -1
  50. package/dist/scp/makesql/outtype.d.ts +61 -6
  51. package/dist/scp/makesql/outtype.d.ts.map +1 -1
  52. package/dist/scp/makesql/outtype.js +164 -33
  53. package/dist/scp/makesql/outtype.js.map +1 -1
  54. package/dist/scp/makesql/pool-executor.d.ts +70 -0
  55. package/dist/scp/makesql/pool-executor.d.ts.map +1 -1
  56. package/dist/scp/makesql/pool-executor.js +73 -5
  57. package/dist/scp/makesql/pool-executor.js.map +1 -1
  58. package/dist/scp/makesql/static-bundle.d.ts +39 -30
  59. package/dist/scp/makesql/static-bundle.d.ts.map +1 -1
  60. package/dist/scp/makesql/static-bundle.js +269 -138
  61. package/dist/scp/makesql/static-bundle.js.map +1 -1
  62. package/dist/scp/makesql/writeouttype.d.ts +5 -6
  63. package/dist/scp/makesql/writeouttype.d.ts.map +1 -1
  64. package/dist/scp/makesql/writeouttype.js +5 -6
  65. package/dist/scp/makesql/writeouttype.js.map +1 -1
  66. package/dist/scp/relation.d.ts +15 -1
  67. package/dist/scp/relation.d.ts.map +1 -1
  68. package/dist/scp/relation.js +61 -2
  69. package/dist/scp/relation.js.map +1 -1
  70. package/dist/scp/runtime.d.ts +6 -5
  71. package/dist/scp/runtime.d.ts.map +1 -1
  72. package/dist/scp/runtime.js +27 -5
  73. package/dist/scp/runtime.js.map +1 -1
  74. package/docs/BENCHMARK.md +58 -56
  75. package/docs/benchmark-chart.svg +166 -166
  76. package/package.json +8 -2
  77. package/dist/scp/makesql/ir.d.ts +0 -42
  78. package/dist/scp/makesql/ir.d.ts.map +0 -1
  79. package/dist/scp/makesql/ir.js +0 -66
  80. package/dist/scp/makesql/ir.js.map +0 -1
@@ -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 rowObjType(table, projection, resolveColumnType, at) {
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
- if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(col)) {
3438
+ const entry2 = parseProjectionColumn(col, table, at);
3439
+ if (entry2.kind === "computed") {
3305
3440
  throw new Error(
3306
- `outtype: ${at}: projected column '${col}' on '${table}' is not a bare column name (alias / qualified / computed). Typed de-box (spec \xA74.1) requires bare schema columns so each maps to a SQL type. No-assume, no-fallback.`
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
- if (col in obj) throw new Error(`outtype: ${at}: duplicate projected column '${col}' on '${table}'`);
3310
- obj[col] = toPortableScalar(sqlTypeToBcScalar(resolveColumnType(table, col)), table, col);
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 nodeOutType(node, resolveColumnType) {
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 row = rowObjType(table, projection, resolveColumnType, at);
3323
- if (isMap2(node)) return { arr: { arr: row } };
3324
- return { arr: row };
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 codegen covers Select/Count only). A write / unknown node cannot be de-boxed here \u2014 no-assume, no-fallback.`
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
- byNode.set(n.id, nodeOutType(n, resolveColumnType));
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 outTypes = resolveColumnType !== void 0 ? deriveReadOutTypes(component, resolveColumnType) : void 0;
3987
- const surrogateBody = component.body.map((n) => {
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 stmts = statementsById[n.id];
3990
- const outType = outTypes?.byNode.get(n.id);
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 surrogate = {
4153
+ const irComponent = {
3998
4154
  ...component,
3999
- body: surrogateBody,
4000
- ...outTypes !== void 0 ? { outputType: outTypes.outputType } : {}
4155
+ body: annotatedBody,
4156
+ ...readTypes !== void 0 ? { outputType: readTypes.outputType } : {}
4001
4157
  };
4002
- const ir = { irVersion: 1, exprVersion: 2, components: [surrogate] };
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 executeReadGraph(graph, input, db) {
4012
- const handle = (ports, ctx) => {
4013
- const stmts = graph.statementsById[ctx.nodeId];
4014
- if (stmts === void 0) return { error: `static-bundle: no statements for node '${ctx.nodeId}'` };
4015
- const scope = ports[SCOPE_PORT];
4016
- if (scope === null || typeof scope !== "object" || Array.isArray(scope)) {
4017
- return { error: `static-bundle: node '${ctx.nodeId}' surrogate scope did not evaluate to an object` };
4018
- }
4019
- const { sql: sql2, params } = renderStatements(stmts, graph.dialect, scope);
4020
- try {
4021
- const rows = db.prepare(sql2).all(...params);
4022
- return { ok: rows };
4023
- } catch (e) {
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
- const handlers = { [NODE_COMPONENT]: handle };
4028
- const normalized = { ...input };
4029
- for (const head of graph.optionalHeads) if (!(head in normalized)) normalized[head] = null;
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
- return runBehavior(graph.ir, handlers, normalized, graph.name);
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 handle = async (ports, ctx) => {
4038
- const stmts = graph.statementsById[ctx.nodeId];
4039
- if (stmts === void 0) return { error: `static-bundle: no statements for node '${ctx.nodeId}'` };
4040
- const scope = ports[SCOPE_PORT];
4041
- if (scope === null || typeof scope !== "object" || Array.isArray(scope)) {
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
- return await runBehaviorAsync(graph.ir, handlers, normalized, graph.name);
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
- Count: entry("Count", { table: P("string", true), where: P("fragment") }, "items", true),
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
- boundComponents = catalogComponents(LITEDBMODEL_CATALOG);
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 ir = compileBehaviors(cls, {
4425
- ...options.inputPorts !== void 0 ? { inputPorts: options.inputPorts } : {},
4426
- ...options.concurrency !== void 0 ? { concurrency: options.concurrency } : {}
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: cls.name ?? "<anonymous>",
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 rows = db.prepare(sql2).all(...bindKeys(op, keys));
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(compileBundle(contract, options.entry, options.relations, options.dialect), input, options);
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(compileBundle(contract, options.entry, options.relations, options.dialect), input, options);
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
- (n) => !("cond" in n) && !("map" in n) && (n.component === "Insert" || n.component === "Update" || n.component === "Delete")
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(compileBundle(contract, options.entry, options.relations, options.dialect), input, options);
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
- const ir = makeSqlComponentIR(bundle.name);
5032
- if (bundle.outputType === void 0) return ir;
5033
- const c = ir.components[0];
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
- function deriveHeadTypesFromStatements(statements, resolveColumnType) {
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) continue;
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
- return { byHead, unresolved };
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 { byHead, unresolved } = deriveHeadTypesFromStatements(stmts, resolveColumnType);
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 newPorts = {};
5156
- for (const head of Object.keys(obj)) {
5157
- if (!byHead.has(head) && !unresolved.has(head)) {
5158
- reasons.push(`node '${n.id}': input head '${head}' referenced by the surrogate scope but not resolved by any WHERE fragment \u2014 cannot type it for native codegen`);
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 isTypedNativeEmitter(emitter) {
5198
- return emitter.endsWith("-typed-native");
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 = isTypedNativeEmitter(emitter) ? lowerReadGraphForTypedNative(bundle.readGraph, resolveColumnType) : bundleToPortableIR(bundle);
5208
- const module = generateModule(ir, runtimeImport === void 0 ? { language: emitter } : { language: emitter, runtimeImport });
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,