litedbmodel 2.0.1 → 2.1.0

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