litedbmodel 2.2.2 → 2.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/DBModel.d.ts.map +1 -1
- package/dist/DBModel.js +3 -0
- package/dist/DBModel.js.map +1 -1
- package/dist/Middleware.d.ts +19 -2
- package/dist/Middleware.d.ts.map +1 -1
- package/dist/Middleware.js +1 -0
- package/dist/Middleware.js.map +1 -1
- package/dist/decorators.d.ts.map +1 -1
- package/dist/decorators.js +8 -0
- package/dist/decorators.js.map +1 -1
- package/dist/drivers/MysqlHelper.d.ts +1 -1
- package/dist/drivers/MysqlHelper.d.ts.map +1 -1
- package/dist/drivers/MysqlHelper.js.map +1 -1
- package/dist/drivers/PostgresHelper.d.ts +1 -1
- package/dist/drivers/PostgresHelper.d.ts.map +1 -1
- package/dist/drivers/PostgresHelper.js.map +1 -1
- package/dist/drivers/SqliteHelper.d.ts +1 -1
- package/dist/drivers/SqliteHelper.d.ts.map +1 -1
- package/dist/drivers/SqliteHelper.js.map +1 -1
- package/dist/index.cjs +105 -74
- package/dist/scp/connection-routing.d.ts +38 -1
- package/dist/scp/connection-routing.d.ts.map +1 -1
- package/dist/scp/connection-routing.js +64 -5
- package/dist/scp/connection-routing.js.map +1 -1
- package/dist/scp/dbmodel-runtime.d.ts +2 -1
- package/dist/scp/dbmodel-runtime.d.ts.map +1 -1
- package/dist/scp/dbmodel-runtime.js +2 -1
- package/dist/scp/dbmodel-runtime.js.map +1 -1
- package/dist/scp/decorator-adapter.d.ts +22 -0
- package/dist/scp/decorator-adapter.d.ts.map +1 -1
- package/dist/scp/decorator-adapter.js +26 -0
- package/dist/scp/decorator-adapter.js.map +1 -1
- package/dist/scp/emit/emitter.d.ts +21 -10
- package/dist/scp/emit/emitter.d.ts.map +1 -1
- package/dist/scp/emit/emitter.js +119 -51
- package/dist/scp/emit/emitter.js.map +1 -1
- package/dist/scp/emit/endpoint.d.ts +3 -8
- package/dist/scp/emit/endpoint.d.ts.map +1 -1
- package/dist/scp/exec-context.d.ts +39 -17
- package/dist/scp/exec-context.d.ts.map +1 -1
- package/dist/scp/exec-context.js +69 -35
- package/dist/scp/exec-context.js.map +1 -1
- package/dist/scp/grouping.d.ts.map +1 -1
- package/dist/scp/grouping.js +0 -0
- package/dist/scp/grouping.js.map +1 -1
- package/dist/scp/index.cjs +257 -670
- package/dist/scp/index.d.ts +6 -11
- package/dist/scp/index.d.ts.map +1 -1
- package/dist/scp/index.js +10 -28
- package/dist/scp/index.js.map +1 -1
- package/dist/scp/index.mjs +257 -658
- package/dist/scp/leaf-transport.d.ts +127 -39
- package/dist/scp/leaf-transport.d.ts.map +1 -1
- package/dist/scp/leaf-transport.js +30 -24
- package/dist/scp/leaf-transport.js.map +1 -1
- package/dist/scp/leaves.d.ts +57 -20
- package/dist/scp/leaves.d.ts.map +1 -1
- package/dist/scp/leaves.js +224 -65
- package/dist/scp/leaves.js.map +1 -1
- package/dist/scp/makesql/compile-select.d.ts +28 -1
- package/dist/scp/makesql/compile-select.d.ts.map +1 -1
- package/dist/scp/makesql/compile-select.js +19 -17
- package/dist/scp/makesql/compile-select.js.map +1 -1
- package/dist/scp/makesql/index.d.ts +2 -2
- package/dist/scp/makesql/index.d.ts.map +1 -1
- package/dist/scp/makesql/index.js +4 -6
- package/dist/scp/makesql/index.js.map +1 -1
- package/dist/scp/makesql/tx.d.ts +43 -89
- package/dist/scp/makesql/tx.d.ts.map +1 -1
- package/dist/scp/makesql/tx.js +45 -496
- package/dist/scp/makesql/tx.js.map +1 -1
- package/dist/scp/makesql/writeouttype.d.ts +5 -7
- package/dist/scp/makesql/writeouttype.d.ts.map +1 -1
- package/dist/scp/makesql/writeouttype.js +13 -19
- package/dist/scp/makesql/writeouttype.js.map +1 -1
- package/dist/scp/middleware.d.ts +4 -4
- package/dist/scp/middleware.d.ts.map +1 -1
- package/dist/scp/middleware.js +2 -2
- package/dist/scp/middleware.js.map +1 -1
- package/dist/scp/relation.d.ts +31 -12
- package/dist/scp/relation.d.ts.map +1 -1
- package/dist/scp/relation.js +16 -6
- package/dist/scp/relation.js.map +1 -1
- package/dist/scp/typed-object.d.ts +23 -19
- package/dist/scp/typed-object.d.ts.map +1 -1
- package/dist/scp/typed-object.js +24 -30
- package/dist/scp/typed-object.js.map +1 -1
- package/dist/scp/write-bundle.d.ts +21 -70
- package/dist/scp/write-bundle.d.ts.map +1 -1
- package/dist/scp/write-bundle.js +16 -80
- package/dist/scp/write-bundle.js.map +1 -1
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/docs/BENCHMARK.md +26 -22
- package/docs/COMPARISON.md +8 -4
- package/docs/api/README.md +1 -1
- package/docs/api/classes/Conditions.md +10 -10
- package/docs/api/classes/DBHandler.md +17 -17
- package/docs/api/classes/DBModel.md +111 -104
- package/docs/api/classes/LimitExceededError.md +8 -8
- package/docs/api/classes/Middleware.md +18 -18
- package/docs/api/classes/SqlRaw.md +6 -6
- package/docs/api/classes/SqlRef.md +7 -7
- package/docs/api/classes/Values.md +6 -6
- package/docs/api/classes/WriteInReadOnlyContextError.md +5 -5
- package/docs/api/classes/WriteOutsideTransactionError.md +5 -5
- package/docs/api/functions/belongsTo.md +2 -2
- package/docs/api/functions/closeAllPools.md +2 -2
- package/docs/api/functions/condsToRecord.md +2 -2
- package/docs/api/functions/createMiddleware.md +2 -2
- package/docs/api/functions/createPostgresDriver.md +2 -2
- package/docs/api/functions/createSqliteDriver.md +2 -2
- package/docs/api/functions/dbCast.md +3 -3
- package/docs/api/functions/dbCastIn.md +2 -2
- package/docs/api/functions/dbDynamic.md +3 -3
- package/docs/api/functions/dbFalse.md +2 -2
- package/docs/api/functions/dbImmediate.md +2 -2
- package/docs/api/functions/dbIn.md +2 -2
- package/docs/api/functions/dbNotNull.md +2 -2
- package/docs/api/functions/dbNow.md +2 -2
- package/docs/api/functions/dbNull.md +2 -2
- package/docs/api/functions/dbRaw.md +2 -2
- package/docs/api/functions/dbTrue.md +2 -2
- package/docs/api/functions/dbUuid.md +3 -3
- package/docs/api/functions/dbUuidIn.md +2 -2
- package/docs/api/functions/formatLocalDate.md +25 -0
- package/docs/api/functions/formatUTCDate.md +25 -0
- package/docs/api/functions/getDBConfig.md +2 -2
- package/docs/api/functions/getDBHandler.md +2 -2
- package/docs/api/functions/getTransactionConnection.md +2 -2
- package/docs/api/functions/getTransactionContext.md +2 -2
- package/docs/api/functions/hasMany.md +2 -2
- package/docs/api/functions/hasOne.md +2 -2
- package/docs/api/functions/initDBHandler.md +2 -2
- package/docs/api/functions/isAnySqlFragment.md +2 -2
- package/docs/api/functions/isConnectionError.md +26 -0
- package/docs/api/functions/isSqlCondition.md +2 -2
- package/docs/api/functions/isSqlFragment.md +2 -2
- package/docs/api/functions/isSqlRaw.md +2 -2
- package/docs/api/functions/isSqlRef.md +2 -2
- package/docs/api/functions/isSqlTypedFragment.md +2 -2
- package/docs/api/functions/model.md +6 -42
- package/docs/api/functions/parentRef.md +2 -2
- package/docs/api/functions/sql.md +7 -7
- package/docs/api/globals.md +57 -22
- package/docs/api/interfaces/Column.md +31 -31
- package/docs/api/interfaces/ColumnOptions.md +5 -4
- package/docs/api/interfaces/CreatedMiddlewareClass.md +7 -7
- package/docs/api/interfaces/DBConfig.md +13 -11
- package/docs/api/interfaces/DBConfigOptions.md +8 -8
- package/docs/api/interfaces/DeleteOptions.md +4 -4
- package/docs/api/interfaces/InsertOptions.md +8 -8
- package/docs/api/interfaces/LimitConfig.md +4 -4
- package/docs/api/interfaces/MiddlewareConfig.md +15 -15
- package/docs/api/interfaces/ModelOptions.md +8 -7
- package/docs/api/interfaces/PkeyResult.md +4 -4
- package/docs/api/interfaces/SelectOptions.md +17 -16
- package/docs/api/interfaces/SqlCondition.md +6 -6
- package/docs/api/interfaces/SqlFragment.md +5 -5
- package/docs/api/interfaces/SqlTypedFragment.md +7 -7
- package/docs/api/interfaces/TransactionOptions.md +7 -7
- package/docs/api/interfaces/UpdateManyOptions.md +4 -4
- package/docs/api/interfaces/UpdateOptions.md +4 -4
- package/docs/architecture.md +452 -0
- package/docs/benchmark-chart.svg +225 -249
- package/docs/design/phase-a-execution-context.md +2 -0
- package/package.json +20 -5
- package/dist/scp/writes.d.ts +0 -251
- package/dist/scp/writes.d.ts.map +0 -1
- package/dist/scp/writes.js +0 -176
- package/dist/scp/writes.js.map +0 -1
- package/docs/proposal/contracts-architecture-feasibility.md +0 -328
- package/docs/proposal/contracts-architecture.md +0 -579
- package/docs/proposal/litedbmodel-v2-scp-architecture.md +0 -433
- package/docs/proposal/sql-dynamic-expansion-spec.md +0 -175
- package/docs/proposal/v0-coverage-matrix.md +0 -157
- package/docs/proposal/v2-completeness-plan.md +0 -90
- package/docs/proposal/v2-sql-parity-checklist.md +0 -162
package/dist/scp/index.cjs
CHANGED
|
@@ -1137,7 +1137,6 @@ __export(index_exports, {
|
|
|
1137
1137
|
DEFAULT_CONNECTION: () => DEFAULT_CONNECTION,
|
|
1138
1138
|
DEFAULT_UNCAST_SQL_TYPE: () => DEFAULT_UNCAST_SQL_TYPE,
|
|
1139
1139
|
Db: () => Db,
|
|
1140
|
-
ENTITY_ROOT: () => ENTITY_ROOT,
|
|
1141
1140
|
LEAF_TRANSPORT_SYMBOLS: () => LEAF_TRANSPORT_SYMBOLS,
|
|
1142
1141
|
LITEDBMODEL_MAKESQL_CATALOG: () => LITEDBMODEL_MAKESQL_CATALOG,
|
|
1143
1142
|
LimitExceededError: () => LimitExceededError,
|
|
@@ -1174,7 +1173,6 @@ __export(index_exports, {
|
|
|
1174
1173
|
compileCompositeKeyLimited: () => compileCompositeKeyLimited,
|
|
1175
1174
|
compileCompositeKeyStaticUnlimited: () => compileCompositeKeyStaticUnlimited,
|
|
1176
1175
|
compileCompositeKeyUnlimited: () => compileCompositeKeyUnlimited,
|
|
1177
|
-
compileCompositeWriteBundle: () => compileCompositeWriteBundle,
|
|
1178
1176
|
compileCreateBundle: () => compileCreateBundle,
|
|
1179
1177
|
compileCreateManyBundle: () => compileCreateManyBundle,
|
|
1180
1178
|
compileDeleteBundle: () => compileDeleteBundle,
|
|
@@ -1193,7 +1191,6 @@ __export(index_exports, {
|
|
|
1193
1191
|
compileUpdateManyBundle: () => compileUpdateManyBundle,
|
|
1194
1192
|
compileUpdateSingle: () => compileUpdateSingle,
|
|
1195
1193
|
compileWhere: () => compileWhere,
|
|
1196
|
-
compileWriteBundle: () => compileWriteBundle,
|
|
1197
1194
|
compileWriteNode: () => compileWriteNode,
|
|
1198
1195
|
composeMakeSQL: () => composeMakeSQL,
|
|
1199
1196
|
configurePgDeboxTypeParsers: () => configurePgDeboxTypeParsers,
|
|
@@ -1201,7 +1198,6 @@ __export(index_exports, {
|
|
|
1201
1198
|
connectionForDriver: () => connectionForDriver,
|
|
1202
1199
|
contextForConnection: () => contextForConnection,
|
|
1203
1200
|
contextForDriver: () => contextForDriver,
|
|
1204
|
-
countingDriver: () => countingDriver,
|
|
1205
1201
|
createMiddleware: () => createMiddleware,
|
|
1206
1202
|
currentPinnedAsyncConnection: () => currentPinnedAsyncConnection,
|
|
1207
1203
|
currentRegistry: () => currentRegistry,
|
|
@@ -1209,20 +1205,15 @@ __export(index_exports, {
|
|
|
1209
1205
|
dbCastIn: () => dbCastIn,
|
|
1210
1206
|
deriveModelColumns: () => deriveModelColumns,
|
|
1211
1207
|
deriveRelationDecls: () => deriveRelationDecls,
|
|
1212
|
-
deriveTransactionPlan: () => deriveTransactionPlan,
|
|
1213
1208
|
dialectFor: () => dialectFor,
|
|
1214
1209
|
distributeToParent: () => distributeToParent,
|
|
1215
|
-
edgeWrites: () => edgeWrites,
|
|
1216
1210
|
emitBehaviorModule: () => emitBehaviorModule,
|
|
1217
|
-
entityWrites: () => entityWrites,
|
|
1218
1211
|
execute: () => execute,
|
|
1219
1212
|
executeAsync: () => executeAsync,
|
|
1220
1213
|
executeSQL: () => executeSQL,
|
|
1221
1214
|
executeSQLAsync: () => executeSQLAsync,
|
|
1222
1215
|
executeSafe: () => executeSafe,
|
|
1223
|
-
executeTransaction: () => executeTransaction,
|
|
1224
1216
|
executeTransactionAsync: () => executeTransactionAsync,
|
|
1225
|
-
executeTransactionBundle: () => executeTransactionBundle,
|
|
1226
1217
|
failClosedMaterializeResolverFromColumnMap: () => failClosedMaterializeResolverFromColumnMap,
|
|
1227
1218
|
formatterFor: () => formatterFor,
|
|
1228
1219
|
getLimitConfig: () => getLimitConfig,
|
|
@@ -1237,7 +1228,6 @@ __export(index_exports, {
|
|
|
1237
1228
|
leaf: () => leaf,
|
|
1238
1229
|
leafHandlers: () => leafHandlers,
|
|
1239
1230
|
leafHandlersAsync: () => leafHandlersAsync,
|
|
1240
|
-
lifecycleFor: () => lifecycleFor,
|
|
1241
1231
|
makeSqlCatalogEntry: () => makeSqlCatalogEntry,
|
|
1242
1232
|
makeSqlHandler: () => makeSqlHandler,
|
|
1243
1233
|
makeSqlHandlerSync: () => makeSqlHandlerSync,
|
|
@@ -1252,7 +1242,6 @@ __export(index_exports, {
|
|
|
1252
1242
|
mysqlPoolFactory: () => mysqlPoolFactory,
|
|
1253
1243
|
noCastFormatter: () => noCastFormatter,
|
|
1254
1244
|
opt: () => opt,
|
|
1255
|
-
parseEffectPath: () => parseEffectPath,
|
|
1256
1245
|
parseSchemaColumnTypes: () => parseSchemaColumnTypes,
|
|
1257
1246
|
pgCastFormatter: () => pgCastFormatter,
|
|
1258
1247
|
pgConnectionPool: () => pgConnectionPool,
|
|
@@ -1293,7 +1282,6 @@ __export(index_exports, {
|
|
|
1293
1282
|
setLimitConfig: () => setLimitConfig,
|
|
1294
1283
|
singlePoolPair: () => singlePoolPair,
|
|
1295
1284
|
sleep: () => sleep,
|
|
1296
|
-
spliceWhere: () => spliceWhere,
|
|
1297
1285
|
sqlTypeToBcScalar: () => sqlTypeToBcScalar,
|
|
1298
1286
|
sqlTypeToMaterializeClass: () => sqlTypeToMaterializeClass,
|
|
1299
1287
|
stripMysqlPkHint: () => stripMysqlPkHint,
|
|
@@ -1956,30 +1944,32 @@ function lockTail(opts) {
|
|
|
1956
1944
|
return "";
|
|
1957
1945
|
}
|
|
1958
1946
|
function compileSelect(desc) {
|
|
1959
|
-
const
|
|
1947
|
+
const headParams = [];
|
|
1960
1948
|
const selectCols = desc.select || (desc.selectColumn ?? "*");
|
|
1961
1949
|
const formatter = formatterFor(desc.dialect);
|
|
1962
|
-
if (desc.cte?.params && desc.cte.params.length > 0)
|
|
1963
|
-
if (desc.joinParams && desc.joinParams.length > 0)
|
|
1964
|
-
const whereClause2 = conditionsFor(desc.conditions ?? {}, desc.dialect).compile(
|
|
1965
|
-
let
|
|
1966
|
-
if (desc.cte)
|
|
1967
|
-
|
|
1968
|
-
if (desc.join)
|
|
1969
|
-
if (whereClause2)
|
|
1970
|
-
|
|
1950
|
+
if (desc.cte?.params && desc.cte.params.length > 0) headParams.push(...desc.cte.params);
|
|
1951
|
+
if (desc.joinParams && desc.joinParams.length > 0) headParams.push(...desc.joinParams);
|
|
1952
|
+
const whereClause2 = conditionsFor(desc.conditions ?? {}, desc.dialect).compile(headParams, formatter);
|
|
1953
|
+
let head = "";
|
|
1954
|
+
if (desc.cte) head = `WITH ${desc.cte.name} AS (${desc.cte.sql}) `;
|
|
1955
|
+
head += `SELECT ${selectCols} FROM ${desc.tableName}`;
|
|
1956
|
+
if (desc.join) head += ` ${desc.join}`;
|
|
1957
|
+
if (whereClause2) head += ` WHERE ${whereClause2}`;
|
|
1958
|
+
let tail = "";
|
|
1959
|
+
if (desc.group) tail += ` GROUP BY ${desc.group}`;
|
|
1971
1960
|
const orderClause = typeof desc.order === "string" ? desc.order : orderToString(desc.order);
|
|
1972
|
-
if (orderClause)
|
|
1961
|
+
if (orderClause) tail += ` ORDER BY ${orderClause}`;
|
|
1962
|
+
const tailParams = [];
|
|
1973
1963
|
const count = (c) => {
|
|
1974
1964
|
if (!isBoundCount(c)) return String(c);
|
|
1975
|
-
|
|
1965
|
+
tailParams.push(c.bind);
|
|
1976
1966
|
return "?";
|
|
1977
1967
|
};
|
|
1978
|
-
if (desc.limit !== void 0)
|
|
1979
|
-
if (desc.offset !== void 0)
|
|
1980
|
-
|
|
1981
|
-
if (desc.append)
|
|
1982
|
-
return { sql:
|
|
1968
|
+
if (desc.limit !== void 0) tail += ` LIMIT ${count(desc.limit)}`;
|
|
1969
|
+
if (desc.offset !== void 0) tail += ` OFFSET ${count(desc.offset)}`;
|
|
1970
|
+
tail += lockTail(desc);
|
|
1971
|
+
if (desc.append) tail += ` ${desc.append}`;
|
|
1972
|
+
return { sql: head + tail, params: [...headParams, ...tailParams], head, headParams, tail, tailParams, lead: whereClause2 ? "AND" : "WHERE" };
|
|
1983
1973
|
}
|
|
1984
1974
|
|
|
1985
1975
|
// src/scp/makesql/compile-relation.ts
|
|
@@ -4507,6 +4497,24 @@ function readerWriterPair(reader, writer) {
|
|
|
4507
4497
|
return { reader, writer };
|
|
4508
4498
|
}
|
|
4509
4499
|
var DEFAULT_CONNECTION = "default";
|
|
4500
|
+
function assertRoutableNamedDb(db, contextDescription) {
|
|
4501
|
+
if (effectiveConnection(db) === DEFAULT_CONNECTION) return;
|
|
4502
|
+
throw new Error(
|
|
4503
|
+
`scp connection routing: a statement names connection '${db}', but it is executing on ${contextDescription} \u2014 there is no connection registry to resolve the name against. Build the context from a RoutingConfig (setConfig/ConnectionRegistry), or drop the connection tag on the model.`
|
|
4504
|
+
);
|
|
4505
|
+
}
|
|
4506
|
+
function effectiveConnection(db) {
|
|
4507
|
+
return db === void 0 || db === null ? DEFAULT_CONNECTION : db;
|
|
4508
|
+
}
|
|
4509
|
+
function assertTxDbAgrees(db, txDb) {
|
|
4510
|
+
if (db === void 0 || db === null) return;
|
|
4511
|
+
const want = effectiveConnection(db);
|
|
4512
|
+
const open = effectiveConnection(txDb);
|
|
4513
|
+
if (want === open) return;
|
|
4514
|
+
throw new Error(
|
|
4515
|
+
`scp connection routing: a statement names connection '${want}', but it is executing inside a transaction opened on '${open}' \u2014 a transaction is ONE connection on ONE database, so the two cannot both be honored. Open the transaction on '${want}', or issue the statement outside it.`
|
|
4516
|
+
);
|
|
4517
|
+
}
|
|
4510
4518
|
var ConnectionRegistry = class _ConnectionRegistry {
|
|
4511
4519
|
/** name → reader/writer pools. */
|
|
4512
4520
|
connections;
|
|
@@ -4527,7 +4535,7 @@ var ConnectionRegistry = class _ConnectionRegistry {
|
|
|
4527
4535
|
}
|
|
4528
4536
|
/** The reader/writer pair for `name` (or {@link DEFAULT_CONNECTION} when `undefined`). Loud on a missing name. */
|
|
4529
4537
|
pairFor(name) {
|
|
4530
|
-
const key = name
|
|
4538
|
+
const key = effectiveConnection(name);
|
|
4531
4539
|
const pair = this.connections.get(key);
|
|
4532
4540
|
if (pair === void 0) {
|
|
4533
4541
|
const known = [...this.connections.keys()].map((k) => `'${k}'`).join(", ");
|
|
@@ -4575,7 +4583,13 @@ function withWriter(fn) {
|
|
|
4575
4583
|
return writerScopeStore.run(true, () => withReadOnlyAsync(fn));
|
|
4576
4584
|
}
|
|
4577
4585
|
var WriterStickyClock = class {
|
|
4578
|
-
|
|
4586
|
+
/**
|
|
4587
|
+
* `null` until the first `mark()` — absence, NOT a value sentinel. The injectable clocks legitimately
|
|
4588
|
+
* return 0 (a monotonic clock right after process start; rust's `SystemClock` does), so encoding
|
|
4589
|
+
* "never marked" as the value 0 would mis-classify a `mark()` at clock t=0 as unmarked and leak the
|
|
4590
|
+
* read-your-writes read to the reader replica. Absence must be distinct from the value 0.
|
|
4591
|
+
*/
|
|
4592
|
+
lastWriteAt = null;
|
|
4579
4593
|
enabled;
|
|
4580
4594
|
stickyDurationMs;
|
|
4581
4595
|
/** Injectable clock (tests advance it); defaults to `Date.now`. */
|
|
@@ -4591,12 +4605,12 @@ var WriterStickyClock = class {
|
|
|
4591
4605
|
}
|
|
4592
4606
|
/** Is a read currently sticky-to-writer (within `writerStickyDuration` of the last write)? */
|
|
4593
4607
|
isSticky() {
|
|
4594
|
-
if (!this.enabled || this.lastWriteAt ===
|
|
4608
|
+
if (!this.enabled || this.lastWriteAt === null) return false;
|
|
4595
4609
|
return this.now() - this.lastWriteAt < this.stickyDurationMs;
|
|
4596
4610
|
}
|
|
4597
4611
|
/** Reset the clock (e.g. between tests / on `closeAllPools`). */
|
|
4598
4612
|
reset() {
|
|
4599
|
-
this.lastWriteAt =
|
|
4613
|
+
this.lastWriteAt = null;
|
|
4600
4614
|
}
|
|
4601
4615
|
};
|
|
4602
4616
|
function resolvePool(intent, routing) {
|
|
@@ -4945,15 +4959,20 @@ function connectionForDriver(driver) {
|
|
|
4945
4959
|
var BasicContext = class _BasicContext {
|
|
4946
4960
|
middleware;
|
|
4947
4961
|
base;
|
|
4948
|
-
/** The pinned tx connection (present ⇒ this is a tx-scoped ctx; every statement uses it). */
|
|
4962
|
+
/** The pinned tx connection (present ⇒ this is a tx-scoped ctx; every statement of the tx's own database (an unnamed one, or one naming that database) uses it). */
|
|
4949
4963
|
pinned;
|
|
4950
4964
|
constructor(base, middleware, pinned) {
|
|
4951
4965
|
this.base = base;
|
|
4952
4966
|
this.middleware = middleware;
|
|
4953
4967
|
this.pinned = pinned;
|
|
4954
4968
|
}
|
|
4955
|
-
connectionFor(
|
|
4956
|
-
|
|
4969
|
+
connectionFor(intent) {
|
|
4970
|
+
if (this.pinned !== null) {
|
|
4971
|
+
assertTxDbAgrees(intent.db, null);
|
|
4972
|
+
return this.pinned;
|
|
4973
|
+
}
|
|
4974
|
+
assertRoutableNamedDb(intent.db, "a single-connection (non-routed) execution context");
|
|
4975
|
+
return this.base;
|
|
4957
4976
|
}
|
|
4958
4977
|
withConnection(conn, tx) {
|
|
4959
4978
|
return new _BasicContext(this.base, this.middleware, tx ? conn : null);
|
|
@@ -4991,7 +5010,10 @@ var PooledAsyncContext = class {
|
|
|
4991
5010
|
}
|
|
4992
5011
|
connectionFor(intent) {
|
|
4993
5012
|
const pinned = asyncCtxStore.getStore();
|
|
4994
|
-
if (pinned !== void 0)
|
|
5013
|
+
if (pinned !== void 0) {
|
|
5014
|
+
assertTxDbAgrees(intent.db, pinned.db);
|
|
5015
|
+
return pinned.conn;
|
|
5016
|
+
}
|
|
4995
5017
|
const pool = resolvePool(intent, this.routing);
|
|
4996
5018
|
return {
|
|
4997
5019
|
async execute(sql2, params) {
|
|
@@ -5035,8 +5057,8 @@ var PooledAsyncContext = class {
|
|
|
5035
5057
|
function isRoutingConfig(x) {
|
|
5036
5058
|
return typeof x === "object" && x !== null && "registry" in x && "sticky" in x;
|
|
5037
5059
|
}
|
|
5038
|
-
function runWithPinnedAsyncConnection(conn, fn) {
|
|
5039
|
-
return asyncCtxStore.run(conn, fn);
|
|
5060
|
+
function runWithPinnedAsyncConnection(conn, db, fn) {
|
|
5061
|
+
return asyncCtxStore.run({ conn, db }, fn);
|
|
5040
5062
|
}
|
|
5041
5063
|
function currentPinnedAsyncConnection() {
|
|
5042
5064
|
return asyncCtxStore.getStore();
|
|
@@ -5047,9 +5069,9 @@ function defaultIsConnectionError(err) {
|
|
|
5047
5069
|
return message.includes("Connection terminated") || message.includes("Client has encountered a connection error") || message.includes("ECONNRESET") || message.includes("ECONNREFUSED") || message.includes("Connection lost") || message.includes("This socket has been ended by the other party") || code === "ECONNRESET" || code === "ECONNREFUSED" || code === "EPIPE" || code === "PROTOCOL_CONNECTION_LOST";
|
|
5048
5070
|
}
|
|
5049
5071
|
async function withTransactionAsync(ctx, fn, opts = {}, dialect = "postgres", isConnectionError2 = defaultIsConnectionError, connection) {
|
|
5050
|
-
const
|
|
5051
|
-
if (
|
|
5052
|
-
return fn(ctx.withConnection(
|
|
5072
|
+
const outer = currentPinnedAsyncConnection();
|
|
5073
|
+
if (outer !== void 0) {
|
|
5074
|
+
return fn(ctx.withConnection(outer.conn, true));
|
|
5053
5075
|
}
|
|
5054
5076
|
const resolved = resolveTxOptions(opts);
|
|
5055
5077
|
const begins = beginStatements(dialect, resolved.isolation);
|
|
@@ -5060,7 +5082,7 @@ async function withTransactionAsync(ctx, fn, opts = {}, dialect = "postgres", is
|
|
|
5060
5082
|
const conn = await pool.acquire();
|
|
5061
5083
|
const txCtx = ctx.withConnection(conn, true);
|
|
5062
5084
|
let poisoned = false;
|
|
5063
|
-
const attemptResult = await runWithPinnedAsyncConnection(conn, async () => {
|
|
5085
|
+
const attemptResult = await runWithPinnedAsyncConnection(conn, connection ?? null, async () => {
|
|
5064
5086
|
for (const begin of begins) await runAsync(txCtx, begin, []);
|
|
5065
5087
|
try {
|
|
5066
5088
|
const r = await runInTransactionScope(() => fn(txCtx));
|
|
@@ -5091,21 +5113,6 @@ async function withTransactionAsync(ctx, fn, opts = {}, dialect = "postgres", is
|
|
|
5091
5113
|
function transaction(ctx, fn, options = {}, dialect = "postgres", isConnectionError2 = defaultIsConnectionError, connection) {
|
|
5092
5114
|
return withTransactionAsync(ctx, () => fn(), options, dialect, isConnectionError2, connection);
|
|
5093
5115
|
}
|
|
5094
|
-
function withTransactionSync(outer, fn, dialect = "sqlite", isolation) {
|
|
5095
|
-
const ctx = outer.withConnection(outer.connectionFor({ write: true }), true);
|
|
5096
|
-
for (const begin of beginStatements(dialect, isolation)) run(ctx, begin, []);
|
|
5097
|
-
try {
|
|
5098
|
-
const r = runInTransactionScope(() => fn(ctx));
|
|
5099
|
-
run(ctx, r.commit ? "COMMIT" : "ROLLBACK", []);
|
|
5100
|
-
return r.value;
|
|
5101
|
-
} catch (e) {
|
|
5102
|
-
try {
|
|
5103
|
-
run(ctx, "ROLLBACK", []);
|
|
5104
|
-
} catch {
|
|
5105
|
-
}
|
|
5106
|
-
throw e;
|
|
5107
|
-
}
|
|
5108
|
-
}
|
|
5109
5116
|
|
|
5110
5117
|
// src/scp/limit-config.ts
|
|
5111
5118
|
var globalLimitConfig = {};
|
|
@@ -5185,25 +5192,20 @@ function attachToParent(parent, pkCols, byKey, single) {
|
|
|
5185
5192
|
}
|
|
5186
5193
|
|
|
5187
5194
|
// src/scp/leaves.ts
|
|
5188
|
-
var WHERE_TAIL_RE = /\s+(GROUP BY|ORDER BY|LIMIT|OFFSET|FOR UPDATE|RETURNING)\b/i;
|
|
5189
|
-
function spliceWhere(baseSql, whereSql) {
|
|
5190
|
-
if (whereSql === "") return baseSql;
|
|
5191
|
-
const tail = WHERE_TAIL_RE.exec(baseSql);
|
|
5192
|
-
return tail === null ? baseSql + whereSql : baseSql.slice(0, tail.index) + whereSql + baseSql.slice(tail.index);
|
|
5193
|
-
}
|
|
5194
5195
|
function assembleDynamicWhere(p) {
|
|
5195
|
-
const
|
|
5196
|
-
|
|
5197
|
-
const
|
|
5198
|
-
|
|
5199
|
-
|
|
5200
|
-
|
|
5201
|
-
}
|
|
5202
|
-
return { sql: spliceWhere(p.sql, whereSql), params: [...whereParams, ...p.params] };
|
|
5196
|
+
const plan = dynamicWherePlan(p.whereDynamic);
|
|
5197
|
+
const frags = plan.frags.filter((f) => !f.skipped);
|
|
5198
|
+
const clause = frags.length === 0 ? "" : ` ${plan.lead} ${frags.map((f) => f.sql).join(" AND ")}`;
|
|
5199
|
+
return {
|
|
5200
|
+
sql: p.sql + clause + plan.tail,
|
|
5201
|
+
params: [...p.params, ...frags.flatMap((f) => f.params), ...plan.tailParams]
|
|
5202
|
+
};
|
|
5203
5203
|
}
|
|
5204
5204
|
function effectiveStatement(p) {
|
|
5205
|
-
|
|
5206
|
-
|
|
5205
|
+
const write = p.write !== null;
|
|
5206
|
+
const db = p.db ?? null;
|
|
5207
|
+
if (p.whereDynamic == null) return { sql: p.sql, params: p.params, write, db };
|
|
5208
|
+
return { ...assembleDynamicWhere({ sql: p.sql, params: p.params, whereDynamic: p.whereDynamic }), write, db };
|
|
5207
5209
|
}
|
|
5208
5210
|
function toBcInt(v) {
|
|
5209
5211
|
return typeof v === "bigint" ? v : BigInt(v);
|
|
@@ -5225,7 +5227,7 @@ function prepareSql(p, dialect) {
|
|
|
5225
5227
|
}
|
|
5226
5228
|
sql2 = renderPlaceholders(sql2, dialect);
|
|
5227
5229
|
const bound = encodeParams(p.params, dialect);
|
|
5228
|
-
const intent = { write: p.write
|
|
5230
|
+
const intent = { write: p.write, ...p.db != null ? { db: p.db } : {} };
|
|
5229
5231
|
return { sql: sql2, bound, intent };
|
|
5230
5232
|
}
|
|
5231
5233
|
function writeSummary(info) {
|
|
@@ -5233,14 +5235,14 @@ function writeSummary(info) {
|
|
|
5233
5235
|
}
|
|
5234
5236
|
function executeSQL(p, ctx) {
|
|
5235
5237
|
const prepared = prepareSql(effectiveStatement(p), ctx.dialect);
|
|
5236
|
-
if (p.write
|
|
5238
|
+
if (p.write !== null && !p.write.returning) return writeSummary(run(ctx.exec, prepared.sql, prepared.bound, prepared.intent));
|
|
5237
5239
|
const rows = executeSafe(ctx.exec, prepared.sql, prepared.bound, prepared.intent);
|
|
5238
5240
|
assertRelationHardLimit(rows, p.guard);
|
|
5239
5241
|
return rows;
|
|
5240
5242
|
}
|
|
5241
5243
|
async function executeSQLAsync(p, ctx) {
|
|
5242
5244
|
const prepared = prepareSql(effectiveStatement(p), ctx.dialect);
|
|
5243
|
-
if (p.write
|
|
5245
|
+
if (p.write !== null && !p.write.returning) return writeSummary(await runAsync(ctx.execAsync, prepared.sql, prepared.bound, prepared.intent));
|
|
5244
5246
|
const rows = await executeAsync(ctx.execAsync, prepared.sql, prepared.bound, prepared.intent);
|
|
5245
5247
|
assertRelationHardLimit(rows, p.guard);
|
|
5246
5248
|
return rows;
|
|
@@ -5251,40 +5253,106 @@ function pluck(p) {
|
|
|
5251
5253
|
}
|
|
5252
5254
|
function group(p) {
|
|
5253
5255
|
const byKey = groupByKey(p.children, p.fk);
|
|
5254
|
-
return p.parents.map((par) => ({ ...par, [p.into]: attachToParent(par, p.pk, byKey, p.single
|
|
5256
|
+
return p.parents.map((par) => ({ ...par, [p.into]: attachToParent(par, p.pk, byKey, p.single) }));
|
|
5257
|
+
}
|
|
5258
|
+
var PORT_TYPES = {
|
|
5259
|
+
bool: (v) => typeof v === "boolean",
|
|
5260
|
+
int: (v) => typeof v === "bigint",
|
|
5261
|
+
string: (v) => typeof v === "string",
|
|
5262
|
+
list: (v) => Array.isArray(v),
|
|
5263
|
+
"string[]": (v) => Array.isArray(v) && v.every((e) => typeof e === "string"),
|
|
5264
|
+
record: (v) => typeof v === "object" && v !== null && !Array.isArray(v)
|
|
5265
|
+
};
|
|
5266
|
+
function portTyped(value, what, declared) {
|
|
5267
|
+
const kind = declared.endsWith("|null") ? declared.slice(0, -"|null".length) : declared;
|
|
5268
|
+
if (kind !== declared && value === null) return null;
|
|
5269
|
+
if (!PORT_TYPES[kind](value)) {
|
|
5270
|
+
const got = JSON.stringify(value, (_k, v) => typeof v === "bigint" ? `${v}n` : v);
|
|
5271
|
+
throw new Error(`scp leaf: ${what} must be ${declared}, got ${got}`);
|
|
5272
|
+
}
|
|
5273
|
+
return value;
|
|
5255
5274
|
}
|
|
5256
|
-
function
|
|
5257
|
-
if (
|
|
5258
|
-
const g = port;
|
|
5259
|
-
const limit = typeof g.limit === "bigint" ? Number(g.limit) : g.limit;
|
|
5260
|
-
if (typeof limit !== "number" || !Number.isInteger(limit) || typeof g.relation !== "string") {
|
|
5275
|
+
function requiredField(record, name, at, declared) {
|
|
5276
|
+
if (!(name in record)) {
|
|
5261
5277
|
throw new Error(
|
|
5262
|
-
`scp leaf
|
|
5278
|
+
`scp leaf: ${at} is missing its '${name}' field \u2014 a generated module spells every field of every struct it wires, so an ABSENT key is an ABI break (a null VALUE is how an absent write mode / plan / cap is spelled)`
|
|
5263
5279
|
);
|
|
5264
5280
|
}
|
|
5265
|
-
return
|
|
5281
|
+
return portTyped(record[name], `${at}'s '${name}'`, declared);
|
|
5282
|
+
}
|
|
5283
|
+
var PLAN_AT = `the 'whereDynamic' plan`;
|
|
5284
|
+
function dynamicWherePlan(port) {
|
|
5285
|
+
const plan = port;
|
|
5286
|
+
const at = `a 'whereDynamic' fragment`;
|
|
5287
|
+
const frags = requiredField(plan, "frags", PLAN_AT, "list").map((frag) => {
|
|
5288
|
+
const f = portTyped(frag, at, "record");
|
|
5289
|
+
return {
|
|
5290
|
+
skipped: requiredField(f, "skipped", at, "bool"),
|
|
5291
|
+
sql: requiredField(f, "sql", at, "string"),
|
|
5292
|
+
params: requiredField(f, "params", at, "list")
|
|
5293
|
+
};
|
|
5294
|
+
});
|
|
5295
|
+
return {
|
|
5296
|
+
frags,
|
|
5297
|
+
lead: requiredField(plan, "lead", PLAN_AT, "string"),
|
|
5298
|
+
tail: requiredField(plan, "tail", PLAN_AT, "string"),
|
|
5299
|
+
tailParams: requiredField(plan, "tailParams", PLAN_AT, "list")
|
|
5300
|
+
};
|
|
5301
|
+
}
|
|
5302
|
+
function relationGuardPort(port) {
|
|
5303
|
+
if (port === null) return null;
|
|
5304
|
+
const g = port;
|
|
5305
|
+
const at = `the 'guard' cap`;
|
|
5306
|
+
const raw = requiredField(g, "limit", at, "int");
|
|
5307
|
+
const model = requiredField(g, "model", at, "string|null");
|
|
5308
|
+
return {
|
|
5309
|
+
limit: Number(raw),
|
|
5310
|
+
...model === null ? {} : { model },
|
|
5311
|
+
relation: requiredField(g, "relation", at, "string")
|
|
5312
|
+
};
|
|
5313
|
+
}
|
|
5314
|
+
var OPTS_AT = `the 'opts' control record`;
|
|
5315
|
+
function optsField(opts, name) {
|
|
5316
|
+
return requiredField(opts, name, OPTS_AT, "record|null");
|
|
5317
|
+
}
|
|
5318
|
+
function writeModePort(port) {
|
|
5319
|
+
if (port === null) return null;
|
|
5320
|
+
return { returning: requiredField(port, "returning", `the 'write' mode`, "bool") };
|
|
5266
5321
|
}
|
|
5267
5322
|
function executeSqlPorts(ports) {
|
|
5323
|
+
const at = "the executeSQL payload";
|
|
5324
|
+
const sql2 = requiredField(ports, "sql", at, "string");
|
|
5325
|
+
const params = requiredField(ports, "params", at, "list");
|
|
5326
|
+
const opts = "opts" in ports ? requiredField(ports, "opts", at, "record|null") : null;
|
|
5327
|
+
if (opts === null) return { sql: sql2, params, write: null };
|
|
5268
5328
|
return {
|
|
5269
|
-
sql:
|
|
5270
|
-
params
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
|
|
5274
|
-
|
|
5275
|
-
|
|
5329
|
+
sql: sql2,
|
|
5330
|
+
params,
|
|
5331
|
+
// The NAMED database — the one control field that is a bare `string|null` rather than a struct, so
|
|
5332
|
+
// it is read against its own declared type instead of through `optsField`'s `record|null`.
|
|
5333
|
+
db: requiredField(opts, "db", OPTS_AT, "string|null"),
|
|
5334
|
+
write: writeModePort(optsField(opts, "write")),
|
|
5335
|
+
whereDynamic: optsField(opts, "whereDynamic"),
|
|
5336
|
+
guard: relationGuardPort(optsField(opts, "guard"))
|
|
5276
5337
|
};
|
|
5277
5338
|
}
|
|
5339
|
+
var PLUCK_AT = "the pluck payload";
|
|
5340
|
+
var GROUP_AT = "the group payload";
|
|
5278
5341
|
var shapingHandlers = {
|
|
5279
|
-
pluck: (ports) => ({
|
|
5342
|
+
pluck: (ports) => ({
|
|
5343
|
+
ok: pluck({
|
|
5344
|
+
rows: requiredField(ports, "rows", PLUCK_AT, "list"),
|
|
5345
|
+
col: requiredField(ports, "col", PLUCK_AT, "string[]")
|
|
5346
|
+
})
|
|
5347
|
+
}),
|
|
5280
5348
|
group: (ports) => ({
|
|
5281
5349
|
ok: group({
|
|
5282
|
-
parents: ports
|
|
5283
|
-
children: ports
|
|
5284
|
-
pk: ports
|
|
5285
|
-
fk: ports
|
|
5286
|
-
into: ports
|
|
5287
|
-
single: ports
|
|
5350
|
+
parents: requiredField(ports, "parents", GROUP_AT, "list"),
|
|
5351
|
+
children: requiredField(ports, "children", GROUP_AT, "list"),
|
|
5352
|
+
pk: requiredField(ports, "pk", GROUP_AT, "string[]"),
|
|
5353
|
+
fk: requiredField(ports, "fk", GROUP_AT, "string[]"),
|
|
5354
|
+
into: requiredField(ports, "into", GROUP_AT, "string"),
|
|
5355
|
+
single: requiredField(ports, "single", GROUP_AT, "bool")
|
|
5288
5356
|
})
|
|
5289
5357
|
})
|
|
5290
5358
|
};
|
|
@@ -5313,111 +5381,6 @@ function isConnectionError(error) {
|
|
|
5313
5381
|
return message.includes("Connection terminated unexpectedly") || message.includes("Connection terminated") || message.includes("Client has encountered a connection error") || message.includes("read ECONNRESET") || message.includes("connect ECONNREFUSED") || message.includes("Connection lost") || message.includes("This socket has been ended by the other party") || code === "ECONNRESET" || code === "ECONNREFUSED" || code === "EPIPE" || code === "EAI_AGAIN" || code === "PROTOCOL_CONNECTION_LOST";
|
|
5314
5382
|
}
|
|
5315
5383
|
|
|
5316
|
-
// src/scp/writes.ts
|
|
5317
|
-
var ENTITY_ROOT = "__entity";
|
|
5318
|
-
var PATH_RE = /^\$\.(input|entity)\.([A-Za-z_][A-Za-z0-9_]*)$/;
|
|
5319
|
-
var REF_PATH_RE = /^\$\.ref\.([A-Za-z_][A-Za-z0-9_]*)\.([A-Za-z_][A-Za-z0-9_]*)$/;
|
|
5320
|
-
function parseEffectPath(value) {
|
|
5321
|
-
const m = PATH_RE.exec(value);
|
|
5322
|
-
if (m !== null) return { root: m[1], field: m[2] };
|
|
5323
|
-
const r = REF_PATH_RE.exec(value);
|
|
5324
|
-
if (r !== null) return { root: "ref", writeName: r[1], field: r[2] };
|
|
5325
|
-
throw new Error(
|
|
5326
|
-
`write-time relations: '${value}' is not a valid path-rooted value; use '$.input.<field>' (the Command input), '$.entity.<field>' (the sole written row), or '$.ref.<writeName>.<field>' (a named upstream write's RETURNING row).`
|
|
5327
|
-
);
|
|
5328
|
-
}
|
|
5329
|
-
function assertPaths(map) {
|
|
5330
|
-
for (const v of Object.values(map)) parseEffectPath(v);
|
|
5331
|
-
}
|
|
5332
|
-
var recorder = {
|
|
5333
|
-
lifecycle(effects = {}) {
|
|
5334
|
-
for (const r of effects.requires ?? []) assertPaths(r.keys);
|
|
5335
|
-
if (effects.idempotency !== void 0) parseEffectPath(effects.idempotency.token);
|
|
5336
|
-
for (const u of effects.unique ?? []) {
|
|
5337
|
-
u.scope.forEach((p) => parseEffectPath(p));
|
|
5338
|
-
u.fields.forEach((p) => parseEffectPath(p));
|
|
5339
|
-
}
|
|
5340
|
-
for (const d of effects.derive ?? []) assertPaths(d.keys);
|
|
5341
|
-
for (const e of effects.edges ?? []) {
|
|
5342
|
-
assertPaths(e.columns);
|
|
5343
|
-
if (e.where !== void 0) assertPaths(e.where);
|
|
5344
|
-
}
|
|
5345
|
-
for (const em of effects.emits ?? []) assertPaths(em.payload);
|
|
5346
|
-
return { effects: freezeEffects(effects) };
|
|
5347
|
-
},
|
|
5348
|
-
exists(table, keys) {
|
|
5349
|
-
return { kind: "requires", table, keys };
|
|
5350
|
-
},
|
|
5351
|
-
unique(spec) {
|
|
5352
|
-
return { kind: "unique", name: spec.name, guardTable: spec.guardTable, scope: spec.scope, fields: spec.fields };
|
|
5353
|
-
},
|
|
5354
|
-
increment(table, keys, attribute, amount) {
|
|
5355
|
-
if (!Number.isFinite(amount)) {
|
|
5356
|
-
throw new Error(`entityWrites: increment amount for '${table}.${attribute}' must be a finite number (got ${String(amount)})`);
|
|
5357
|
-
}
|
|
5358
|
-
return { kind: "derive", table, keys, attribute, amount };
|
|
5359
|
-
},
|
|
5360
|
-
edge(spec) {
|
|
5361
|
-
if (spec.relation === "fk" && spec.where === void 0) {
|
|
5362
|
-
throw new Error(`entityWrites: a 'fk' edge on '${spec.table}' requires a 'where' key map (the matched row's identity)`);
|
|
5363
|
-
}
|
|
5364
|
-
return {
|
|
5365
|
-
kind: "edge",
|
|
5366
|
-
relation: spec.relation,
|
|
5367
|
-
action: spec.action,
|
|
5368
|
-
table: spec.table,
|
|
5369
|
-
columns: spec.columns,
|
|
5370
|
-
...spec.where !== void 0 ? { where: spec.where } : {}
|
|
5371
|
-
};
|
|
5372
|
-
},
|
|
5373
|
-
event(name, outboxTable, payload) {
|
|
5374
|
-
return { kind: "emit", name, outboxTable, payload };
|
|
5375
|
-
},
|
|
5376
|
-
idempotentBy(table, column2, token) {
|
|
5377
|
-
return { kind: "idempotency", table, column: column2, token };
|
|
5378
|
-
}
|
|
5379
|
-
};
|
|
5380
|
-
function freezeEffects(effects) {
|
|
5381
|
-
const out = {};
|
|
5382
|
-
if (effects.requires !== void 0) out.requires = effects.requires;
|
|
5383
|
-
if (effects.idempotency !== void 0) out.idempotency = effects.idempotency;
|
|
5384
|
-
if (effects.unique !== void 0) out.unique = effects.unique;
|
|
5385
|
-
if (effects.derive !== void 0) out.derive = effects.derive;
|
|
5386
|
-
if (effects.edges !== void 0) out.edges = effects.edges;
|
|
5387
|
-
if (effects.emits !== void 0) out.emits = effects.emits;
|
|
5388
|
-
return out;
|
|
5389
|
-
}
|
|
5390
|
-
function entityWrites(builder) {
|
|
5391
|
-
const shape = builder(recorder);
|
|
5392
|
-
if (shape.create === void 0 && shape.update === void 0 && shape.remove === void 0) {
|
|
5393
|
-
throw new Error(
|
|
5394
|
-
"entityWrites(...) must declare at least one lifecycle (`create` / `update` / `remove`)."
|
|
5395
|
-
);
|
|
5396
|
-
}
|
|
5397
|
-
return {
|
|
5398
|
-
...shape.create !== void 0 ? { create: shape.create } : {},
|
|
5399
|
-
...shape.update !== void 0 ? { update: shape.update } : {},
|
|
5400
|
-
...shape.remove !== void 0 ? { remove: shape.remove } : {}
|
|
5401
|
-
};
|
|
5402
|
-
}
|
|
5403
|
-
function edgeWrites(builder) {
|
|
5404
|
-
const def = entityWrites(builder);
|
|
5405
|
-
for (const phase of ["create", "update", "remove"]) {
|
|
5406
|
-
const c = def[phase];
|
|
5407
|
-
if (c === void 0) continue;
|
|
5408
|
-
const e = c.effects;
|
|
5409
|
-
if (e.requires !== void 0 || e.unique !== void 0 || e.derive !== void 0 || e.emits !== void 0 || e.idempotency !== void 0) {
|
|
5410
|
-
throw new Error(
|
|
5411
|
-
`edgeWrites: the '${phase}' lifecycle may declare ONLY 'edges' effects (an edge entity carries only edge writes; use entityWrites for a full save contract).`
|
|
5412
|
-
);
|
|
5413
|
-
}
|
|
5414
|
-
}
|
|
5415
|
-
return def;
|
|
5416
|
-
}
|
|
5417
|
-
function lifecycleFor(def, phase) {
|
|
5418
|
-
return def[phase];
|
|
5419
|
-
}
|
|
5420
|
-
|
|
5421
5384
|
// src/scp/makesql/tx.ts
|
|
5422
5385
|
function literalize(value) {
|
|
5423
5386
|
if (value === null || value === void 0) return null;
|
|
@@ -5430,100 +5393,9 @@ function literalize(value) {
|
|
|
5430
5393
|
}
|
|
5431
5394
|
return value;
|
|
5432
5395
|
}
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
if (p.root === "entity") return { ref: [ENTITY_ROOT, p.field] };
|
|
5437
|
-
return { ref: [p.writeName, p.field] };
|
|
5438
|
-
}
|
|
5439
|
-
function guardInsert(dialect, table, columns, placeholders2) {
|
|
5440
|
-
const cols = `(${columns.join(", ")})`;
|
|
5441
|
-
if (dialect === "mysql") return `INSERT IGNORE INTO ${table} ${cols} VALUES (${placeholders2})`;
|
|
5442
|
-
return `INSERT INTO ${table} ${cols} VALUES (${placeholders2}) ON CONFLICT DO NOTHING`;
|
|
5443
|
-
}
|
|
5444
|
-
function makeIdGen() {
|
|
5445
|
-
let n = 0;
|
|
5446
|
-
return (role) => `tx_${role}_${n++}`;
|
|
5447
|
-
}
|
|
5448
|
-
function compileRequires(e, nextId) {
|
|
5449
|
-
const cols = Object.keys(e.keys);
|
|
5450
|
-
if (cols.length === 0) throw new Error(`write-plan: requires on '${e.table}' declares no keys`);
|
|
5451
|
-
const whereSql = v1EqualityWhereText(cols);
|
|
5452
|
-
const op = {
|
|
5453
|
-
sql: `SELECT 1 FROM ${e.table} WHERE ${whereSql}`,
|
|
5454
|
-
params: cols.map((c) => pathToRef(e.keys[c]))
|
|
5455
|
-
};
|
|
5456
|
-
return { id: nextId("requires"), role: "gate:requires", op, gate: "existsElseRollback", label: `requires ${e.table}` };
|
|
5457
|
-
}
|
|
5458
|
-
function compileIdempotency(e, nextId, dialect) {
|
|
5459
|
-
const op = {
|
|
5460
|
-
sql: guardInsert(dialect, e.table, [e.column], "?"),
|
|
5461
|
-
params: [pathToRef(e.token)]
|
|
5462
|
-
};
|
|
5463
|
-
return { id: nextId("idem"), role: "gate:idempotency", op, gate: "insertedElseNoop", label: `idempotency ${e.table}` };
|
|
5464
|
-
}
|
|
5465
|
-
function compileUnique(e, nextId, dialect) {
|
|
5466
|
-
const scopeCols = e.scope.map((_, i) => `s${i}`);
|
|
5467
|
-
const fieldCols = e.fields.map((_, i) => `f${i}`);
|
|
5468
|
-
const cols = ["name", ...scopeCols, ...fieldCols];
|
|
5469
|
-
const placeholders2 = cols.map(() => "?").join(", ");
|
|
5470
|
-
const params = [
|
|
5471
|
-
e.name,
|
|
5472
|
-
// literal discriminator (a closed-set string literal)
|
|
5473
|
-
...e.scope.map(pathToRef),
|
|
5474
|
-
...e.fields.map(pathToRef)
|
|
5475
|
-
];
|
|
5476
|
-
const op = { sql: guardInsert(dialect, e.guardTable, cols, placeholders2), params };
|
|
5477
|
-
return { id: nextId("unique"), role: "gate:unique", op, gate: "insertedElseRollback", label: `unique ${e.name}` };
|
|
5478
|
-
}
|
|
5479
|
-
function compileDerive(e, nextId) {
|
|
5480
|
-
const keyCols = Object.keys(e.keys);
|
|
5481
|
-
if (keyCols.length === 0) throw new Error(`write-plan: derive on '${e.table}' declares no keys`);
|
|
5482
|
-
const whereSql = v1EqualityWhereText(keyCols);
|
|
5483
|
-
const op = {
|
|
5484
|
-
sql: `UPDATE ${e.table} SET ${e.attribute} = ${e.attribute} + ? WHERE ${whereSql}`,
|
|
5485
|
-
// SET amount is the first param (before the WHERE keys), matching the v1 param order.
|
|
5486
|
-
params: [e.amount, ...keyCols.map((c) => pathToRef(e.keys[c]))]
|
|
5487
|
-
};
|
|
5488
|
-
return { id: nextId("derive"), role: "derive", op, label: `derive ${e.table}.${e.attribute}` };
|
|
5489
|
-
}
|
|
5490
|
-
function compileEdge(e, nextId) {
|
|
5491
|
-
if (e.relation === "m2m") {
|
|
5492
|
-
const cols = Object.keys(e.columns);
|
|
5493
|
-
if (e.action === "set") {
|
|
5494
|
-
const placeholders2 = cols.map(() => "?").join(", ");
|
|
5495
|
-
const op3 = {
|
|
5496
|
-
sql: `INSERT INTO ${e.table} (${cols.join(", ")}) VALUES (${placeholders2})`,
|
|
5497
|
-
params: cols.map((c) => pathToRef(e.columns[c]))
|
|
5498
|
-
};
|
|
5499
|
-
return { id: nextId("edge"), role: "edge", op: op3, label: `edge m2m link ${e.table}` };
|
|
5500
|
-
}
|
|
5501
|
-
const whereSql2 = v1EqualityWhereText(cols);
|
|
5502
|
-
const op2 = {
|
|
5503
|
-
sql: `DELETE FROM ${e.table} WHERE ${whereSql2}`,
|
|
5504
|
-
params: cols.map((c) => pathToRef(e.columns[c]))
|
|
5505
|
-
};
|
|
5506
|
-
return { id: nextId("edge"), role: "edge", op: op2, label: `edge m2m unlink ${e.table}` };
|
|
5507
|
-
}
|
|
5508
|
-
const setCols = Object.keys(e.columns);
|
|
5509
|
-
const whereCols = Object.keys(e.where);
|
|
5510
|
-
const setClauses = setCols.map((c) => e.action === "set" ? `${c} = ?` : `${c} = NULL`);
|
|
5511
|
-
const setParams = e.action === "set" ? setCols.map((c) => pathToRef(e.columns[c])) : [];
|
|
5512
|
-
const whereSql = v1EqualityWhereText(whereCols);
|
|
5513
|
-
const op = {
|
|
5514
|
-
sql: `UPDATE ${e.table} SET ${setClauses.join(", ")} WHERE ${whereSql}`,
|
|
5515
|
-
params: [...setParams, ...whereCols.map((c) => pathToRef(e.where[c]))]
|
|
5516
|
-
};
|
|
5517
|
-
return { id: nextId("edge"), role: "edge", op, label: `edge fk ${e.action} ${e.table}` };
|
|
5518
|
-
}
|
|
5519
|
-
function compileEmit(e, nextId) {
|
|
5520
|
-
const payloadObj = {};
|
|
5521
|
-
for (const [k, v] of Object.entries(e.payload)) payloadObj[k] = pathToRef(v);
|
|
5522
|
-
const op = {
|
|
5523
|
-
sql: `INSERT INTO ${e.outboxTable} (type, payload) VALUES (?, ?)`,
|
|
5524
|
-
params: [e.name, { obj: payloadObj }]
|
|
5525
|
-
};
|
|
5526
|
-
return { id: nextId("emit"), role: "emit", op, label: `emit ${e.name}` };
|
|
5396
|
+
var ENTITY_ROOT = "__entity";
|
|
5397
|
+
function isBatchPlan(plan) {
|
|
5398
|
+
return plan.entityFrom === null && plan.statements.every((s) => s.binds === void 0 && s.role === "body");
|
|
5527
5399
|
}
|
|
5528
5400
|
function stringPort(ports, name) {
|
|
5529
5401
|
const v = ports[name];
|
|
@@ -5580,11 +5452,6 @@ function v1ConditionText(conditions) {
|
|
|
5580
5452
|
const probe = [];
|
|
5581
5453
|
return new DBConditions(conditions).compile(probe);
|
|
5582
5454
|
}
|
|
5583
|
-
function v1EqualityWhereText(columns) {
|
|
5584
|
-
const conditions = {};
|
|
5585
|
-
for (const c of columns) conditions[c] = PROBE;
|
|
5586
|
-
return v1ConditionText(conditions);
|
|
5587
|
-
}
|
|
5588
5455
|
function lowerWhereMember(node, at, dialect) {
|
|
5589
5456
|
const op = opKey(node);
|
|
5590
5457
|
if (op === void 0) throw new Error(`compileWriteNode: ${at}: a where member must be a single-operator Expression node`);
|
|
@@ -5722,7 +5589,7 @@ function compileWriteNodeSql(node, dialect, resolveColumnType) {
|
|
|
5722
5589
|
const sorted = [...cols].sort();
|
|
5723
5590
|
if (stringPort(ports, "batch") === "true") {
|
|
5724
5591
|
if (dialect === "postgres") {
|
|
5725
|
-
if (resolveColumnType === void 0) throw new Error(`compileWriteNode: batch insert on postgres needs the column-type resolver (schema SoT) to derive the UNNEST element casts \u2014 pass it through compileCreateManyBundle
|
|
5592
|
+
if (resolveColumnType === void 0) throw new Error(`compileWriteNode: batch insert on postgres needs the column-type resolver (schema SoT) to derive the UNNEST element casts \u2014 pass it through compileCreateManyBundle (the decorator-adapter batch write path).`);
|
|
5726
5593
|
return pgBatchInsert(table, sorted, values, ports, resolveColumnType, dialect);
|
|
5727
5594
|
}
|
|
5728
5595
|
const shapeOpts = { tableName: table, columns: sorted, records: [], ...onConflictJsonOpts(ports, sorted), ...stringPort(ports, "returning") !== void 0 ? { returning: stringPort(ports, "returning") } : {} };
|
|
@@ -5750,7 +5617,7 @@ function compileWriteNodeSql(node, dialect, resolveColumnType) {
|
|
|
5750
5617
|
if (keyCols.length === 0) throw new Error(`compileWriteNode: batch Update requires at least one 'key.<field>' port`);
|
|
5751
5618
|
const updateCols = [...setCols].sort();
|
|
5752
5619
|
if (dialect === "postgres") {
|
|
5753
|
-
if (resolveColumnType === void 0) throw new Error(`compileWriteNode: batch update on postgres needs the column-type resolver (schema SoT) to derive the UNNEST element casts \u2014 pass it through compileCreateManyBundle
|
|
5620
|
+
if (resolveColumnType === void 0) throw new Error(`compileWriteNode: batch update on postgres needs the column-type resolver (schema SoT) to derive the UNNEST element casts \u2014 pass it through compileCreateManyBundle (the decorator-adapter batch write path).`);
|
|
5754
5621
|
return pgBatchUpdate(table, keyCols, updateCols, key, set, ports, resolveColumnType, dialect);
|
|
5755
5622
|
}
|
|
5756
5623
|
const shapeOpts = { tableName: table, keyColumns: keyCols, updateColumns: updateCols, records: [], ...stringPort(ports, "returning") !== void 0 ? { returning: stringPort(ports, "returning") } : {} };
|
|
@@ -5782,133 +5649,9 @@ function compileWriteNodeSql(node, dialect, resolveColumnType) {
|
|
|
5782
5649
|
throw new Error(`compileWriteNode: catalog component '${component}' has no write compile (SQL writes: Insert/Update/Delete)`);
|
|
5783
5650
|
}
|
|
5784
5651
|
}
|
|
5785
|
-
function
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
const mk = (stmt, isGate) => {
|
|
5789
|
-
const consumes = refHeadsOf(stmt).filter((h) => h !== ENTITY_ROOT && !isInputHead(h, stmt));
|
|
5790
|
-
return { stmt, seq: seqRef.n++, consumes, produces: stmt.binds ?? null, isGate };
|
|
5791
|
-
};
|
|
5792
|
-
for (const r of e.requires ?? []) nodes.push(mk(compileRequires(r, nextId), true));
|
|
5793
|
-
if (e.idempotency !== void 0) nodes.push(mk(compileIdempotency(e.idempotency, nextId, dialect), true));
|
|
5794
|
-
for (const u of e.unique ?? []) nodes.push(mk(compileUnique(u, nextId, dialect), true));
|
|
5795
|
-
const bodyId = nextId("body");
|
|
5796
|
-
const bodyStmt = {
|
|
5797
|
-
id: bodyId,
|
|
5798
|
-
role: "body",
|
|
5799
|
-
op: base.op,
|
|
5800
|
-
label: base.label,
|
|
5801
|
-
...base.name !== void 0 ? { binds: base.name } : {}
|
|
5802
|
-
};
|
|
5803
|
-
nodes.push(mk(bodyStmt, false));
|
|
5804
|
-
for (const d of e.derive ?? []) nodes.push(mk(compileDerive(d, nextId), false));
|
|
5805
|
-
for (const ed of e.edges ?? []) nodes.push(mk(compileEdge(ed, nextId), false));
|
|
5806
|
-
for (const em of e.emits ?? []) nodes.push(mk(compileEmit(em, nextId), false));
|
|
5807
|
-
return { nodes, bodyId };
|
|
5808
|
-
}
|
|
5809
|
-
function topoOrder(nodes) {
|
|
5810
|
-
const byName = /* @__PURE__ */ new Map();
|
|
5811
|
-
for (const n of nodes) {
|
|
5812
|
-
if (n.produces !== null) {
|
|
5813
|
-
if (byName.has(n.produces)) {
|
|
5814
|
-
throw new Error(`write-plan: two writes both bind the name '${n.produces}' \u2014 write names must be unique in a composite Command.`);
|
|
5815
|
-
}
|
|
5816
|
-
byName.set(n.produces, n);
|
|
5817
|
-
}
|
|
5818
|
-
}
|
|
5819
|
-
const succ = /* @__PURE__ */ new Map();
|
|
5820
|
-
const indeg = /* @__PURE__ */ new Map();
|
|
5821
|
-
const nodeById = /* @__PURE__ */ new Map();
|
|
5822
|
-
for (const n of nodes) {
|
|
5823
|
-
nodeById.set(n.stmt.id, n);
|
|
5824
|
-
succ.set(n.stmt.id, /* @__PURE__ */ new Set());
|
|
5825
|
-
indeg.set(n.stmt.id, 0);
|
|
5826
|
-
}
|
|
5827
|
-
const addEdge = (fromId, toId) => {
|
|
5828
|
-
if (fromId === toId) return;
|
|
5829
|
-
const s = succ.get(fromId);
|
|
5830
|
-
if (!s.has(toId)) {
|
|
5831
|
-
s.add(toId);
|
|
5832
|
-
indeg.set(toId, indeg.get(toId) + 1);
|
|
5833
|
-
}
|
|
5834
|
-
};
|
|
5835
|
-
for (const n of nodes) {
|
|
5836
|
-
for (const dep of n.consumes) {
|
|
5837
|
-
const producer = byName.get(dep);
|
|
5838
|
-
if (producer === void 0) {
|
|
5839
|
-
throw new Error(
|
|
5840
|
-
`write-plan: statement '${n.stmt.label}' references '$.ref.${dep}.*' but no write in this Command binds the name '${dep}' \u2014 a dangling write reference (fail-closed; no silent skip).`
|
|
5841
|
-
);
|
|
5842
|
-
}
|
|
5843
|
-
addEdge(producer.stmt.id, n.stmt.id);
|
|
5844
|
-
}
|
|
5845
|
-
}
|
|
5846
|
-
const gates = nodes.filter((n) => n.isGate);
|
|
5847
|
-
const nonGates = nodes.filter((n) => !n.isGate);
|
|
5848
|
-
for (const g of gates) for (const b of nonGates) addEdge(g.stmt.id, b.stmt.id);
|
|
5849
|
-
const ordered = [];
|
|
5850
|
-
const ready = nodes.filter((n) => indeg.get(n.stmt.id) === 0).sort((a, b) => a.seq - b.seq);
|
|
5851
|
-
while (ready.length > 0) {
|
|
5852
|
-
const n = ready.shift();
|
|
5853
|
-
ordered.push(n.stmt);
|
|
5854
|
-
for (const depId of succ.get(n.stmt.id)) {
|
|
5855
|
-
indeg.set(depId, indeg.get(depId) - 1);
|
|
5856
|
-
if (indeg.get(depId) === 0) {
|
|
5857
|
-
const dn = nodeById.get(depId);
|
|
5858
|
-
let lo = 0;
|
|
5859
|
-
while (lo < ready.length && ready[lo].seq < dn.seq) lo++;
|
|
5860
|
-
ready.splice(lo, 0, dn);
|
|
5861
|
-
}
|
|
5862
|
-
}
|
|
5863
|
-
}
|
|
5864
|
-
if (ordered.length !== nodes.length) {
|
|
5865
|
-
const stuck = nodes.filter((n) => !ordered.includes(n.stmt)).map((n) => n.stmt.label);
|
|
5866
|
-
throw new Error(
|
|
5867
|
-
`write-plan: the write-time transaction DAG has a CYCLE (unresolvable ordering among [${stuck.join(", ")}]). This is either a mutual '$.ref' data dependency or a gate that depends on a body row (a gate cannot depend on a not-yet-written row). ESCALATE: the Command's write dependencies are contradictory \u2014 no order satisfies both the data dependencies and gate-first. (Fail-closed; the derivation never guesses an order.)`
|
|
5868
|
-
);
|
|
5869
|
-
}
|
|
5870
|
-
return ordered;
|
|
5871
|
-
}
|
|
5872
|
-
function deriveTransactionPlan(phase, bases, lifecycle, dialect = "sqlite") {
|
|
5873
|
-
if (bases.length === 0) {
|
|
5874
|
-
throw new Error("write-plan: a Command must declare at least one base write (Insert/Update/Delete).");
|
|
5875
|
-
}
|
|
5876
|
-
const nextId = makeIdGen();
|
|
5877
|
-
const seqRef = { n: 0 };
|
|
5878
|
-
const composite = bases.length > 1;
|
|
5879
|
-
const allNodes = [];
|
|
5880
|
-
const bodyIds = [];
|
|
5881
|
-
bases.forEach((base, i) => {
|
|
5882
|
-
const lc = base.effects !== void 0 ? { effects: base.effects } : i === 0 ? lifecycle : { effects: {} };
|
|
5883
|
-
const { nodes, bodyId } = compileWriteGroup(base, lc, nextId, dialect, seqRef);
|
|
5884
|
-
allNodes.push(...nodes);
|
|
5885
|
-
bodyIds.push(bodyId);
|
|
5886
|
-
});
|
|
5887
|
-
const statements = topoOrder(allNodes);
|
|
5888
|
-
const soleBody = bases[0];
|
|
5889
|
-
const soleBodyId = bodyIds[0];
|
|
5890
|
-
const usesEntity = referencesHead(statements, ENTITY_ROOT);
|
|
5891
|
-
const soleBodyReturns = /\breturning\b/i.test(soleBody.op.sql);
|
|
5892
|
-
if (usesEntity && composite) {
|
|
5893
|
-
throw new Error(
|
|
5894
|
-
`write-plan: a composite (multi-write) Command uses '$.entity.*' \u2014 ambiguous which write it denotes. Address each write's row by name via '$.ref.<writeName>.<field>' (fail-closed).`
|
|
5895
|
-
);
|
|
5896
|
-
}
|
|
5897
|
-
if (usesEntity && !soleBodyReturns) {
|
|
5898
|
-
throw new Error(
|
|
5899
|
-
`write-plan: a derive/edges/emits stage references '$.entity.*' but the body write ('${soleBody.label}') has no RETURNING clause \u2014 the written row cannot be exposed to later stages. Add a RETURNING to the base write (fail-closed; no absent-entity default).`
|
|
5900
|
-
);
|
|
5901
|
-
}
|
|
5902
|
-
const entityFrom = usesEntity || !composite && soleBodyReturns ? soleBodyId : null;
|
|
5903
|
-
for (const base of bases) {
|
|
5904
|
-
if (base.name === void 0) continue;
|
|
5905
|
-
if (referencesHead(statements, base.name) && !/\breturning\b/i.test(base.op.sql)) {
|
|
5906
|
-
throw new Error(
|
|
5907
|
-
`write-plan: write '${base.name}' is referenced as '$.ref.${base.name}.*' by a later statement but its op has no RETURNING clause \u2014 its row cannot be bound for downstream writes. Add a RETURNING to write '${base.name}' (fail-closed; no absent-row default).`
|
|
5908
|
-
);
|
|
5909
|
-
}
|
|
5910
|
-
}
|
|
5911
|
-
return { phase, entityFrom, statements, onIdempotentHit: "rollback" };
|
|
5652
|
+
function makeIdGen() {
|
|
5653
|
+
let n = 0;
|
|
5654
|
+
return (role) => `tx_${role}_${n++}`;
|
|
5912
5655
|
}
|
|
5913
5656
|
function deriveBatchPlan(phase, ops) {
|
|
5914
5657
|
if (ops.length === 0) {
|
|
@@ -5921,56 +5664,7 @@ function deriveBatchPlan(phase, ops) {
|
|
|
5921
5664
|
op: { sql: op.sql, params: op.params.map((p) => literalize(p)) },
|
|
5922
5665
|
label: op.label ?? `batch[${i}]`
|
|
5923
5666
|
}));
|
|
5924
|
-
return { phase, entityFrom: null, statements
|
|
5925
|
-
}
|
|
5926
|
-
function refHeadsOf(stmt) {
|
|
5927
|
-
const heads = /* @__PURE__ */ new Set();
|
|
5928
|
-
collectRefHeads(stmt.op.params, heads);
|
|
5929
|
-
return [...heads];
|
|
5930
|
-
}
|
|
5931
|
-
function isInputHead(head, stmt) {
|
|
5932
|
-
return refPathLengthFor(stmt.op.params, head) === 1;
|
|
5933
|
-
}
|
|
5934
|
-
function refPathLengthFor(node, head) {
|
|
5935
|
-
let maxLen = 0;
|
|
5936
|
-
const walk = (n) => {
|
|
5937
|
-
if (n === null || typeof n !== "object") return;
|
|
5938
|
-
if (Array.isArray(n)) {
|
|
5939
|
-
for (const el of n) walk(el);
|
|
5940
|
-
return;
|
|
5941
|
-
}
|
|
5942
|
-
const keys = Object.keys(n);
|
|
5943
|
-
if (keys.length === 1 && keys[0] === "ref") {
|
|
5944
|
-
const path = n.ref;
|
|
5945
|
-
if (Array.isArray(path) && path[0] === head) maxLen = Math.max(maxLen, path.length);
|
|
5946
|
-
return;
|
|
5947
|
-
}
|
|
5948
|
-
for (const k of keys) walk(n[k]);
|
|
5949
|
-
};
|
|
5950
|
-
walk(node);
|
|
5951
|
-
return maxLen;
|
|
5952
|
-
}
|
|
5953
|
-
function referencesHead(statements, head) {
|
|
5954
|
-
const heads = /* @__PURE__ */ new Set();
|
|
5955
|
-
for (const s of statements) collectRefHeads(s.op.params, heads);
|
|
5956
|
-
return heads.has(head);
|
|
5957
|
-
}
|
|
5958
|
-
function collectRefHeads(node, heads) {
|
|
5959
|
-
if (node === null || typeof node !== "object") return;
|
|
5960
|
-
if (Array.isArray(node)) {
|
|
5961
|
-
for (const el of node) collectRefHeads(el, heads);
|
|
5962
|
-
return;
|
|
5963
|
-
}
|
|
5964
|
-
const keys = Object.keys(node);
|
|
5965
|
-
if (keys.length === 1 && keys[0] === "ref") {
|
|
5966
|
-
const path = node.ref;
|
|
5967
|
-
if (Array.isArray(path) && typeof path[0] === "string") heads.add(path[0]);
|
|
5968
|
-
return;
|
|
5969
|
-
}
|
|
5970
|
-
for (const k of keys) collectRefHeads(node[k], heads);
|
|
5971
|
-
}
|
|
5972
|
-
function asContext(dbOrCtx) {
|
|
5973
|
-
return "connectionFor" in dbOrCtx ? dbOrCtx : contextForDriver(dbOrCtx);
|
|
5667
|
+
return { phase, entityFrom: null, statements };
|
|
5974
5668
|
}
|
|
5975
5669
|
function toDriverParam(v, dialect) {
|
|
5976
5670
|
if (typeof v === "bigint") {
|
|
@@ -5989,78 +5683,13 @@ function renderStatement(op, scope, dialect) {
|
|
|
5989
5683
|
const { sql: sql2, params } = evalAssemble(op, scope, dialect);
|
|
5990
5684
|
return { sql: renderPlaceholders(sql2, dialect), params };
|
|
5991
5685
|
}
|
|
5992
|
-
function execStatement(ctx, op, scope, dialect) {
|
|
5993
|
-
const { sql: sql2, params } = evalAssemble(op, scope, dialect);
|
|
5994
|
-
const hasReturn = /\bselect\b/i.test(sql2.slice(0, 8)) || /\breturning\b/i.test(sql2);
|
|
5995
|
-
const out = executeSQL({ sql: sql2, params, write: true, returning: hasReturn, bigint: false }, { exec: ctx, dialect });
|
|
5996
|
-
return hasReturn ? { rows: out, changes: out.length } : { rows: [], changes: Number(out[0]?.changes ?? 0) };
|
|
5997
|
-
}
|
|
5998
|
-
function gateShortCircuit(gate, result) {
|
|
5999
|
-
switch (gate) {
|
|
6000
|
-
case "existsElseRollback":
|
|
6001
|
-
return result.rows.length === 0 ? "requires_absent" : null;
|
|
6002
|
-
case "insertedElseRollback":
|
|
6003
|
-
return result.changes === 0 ? "unique_collision" : null;
|
|
6004
|
-
case "insertedElseNoop":
|
|
6005
|
-
return result.changes === 0 ? "idempotent_duplicate" : null;
|
|
6006
|
-
default:
|
|
6007
|
-
throw new Error(`scp write: unknown gate rule '${String(gate)}'`);
|
|
6008
|
-
}
|
|
6009
|
-
}
|
|
6010
|
-
function executeTransaction(db, plan, input, dialect = "sqlite") {
|
|
6011
|
-
const outer = asContext(db);
|
|
6012
|
-
const executed = [];
|
|
6013
|
-
const scope = { ...input };
|
|
6014
|
-
let entity = null;
|
|
6015
|
-
const isBatch = plan.entityFrom === null && plan.statements.every((s) => s.gate === void 0 && s.binds === void 0 && s.role === "body");
|
|
6016
|
-
const returnedRows = [];
|
|
6017
|
-
try {
|
|
6018
|
-
return withTransactionSync(
|
|
6019
|
-
outer,
|
|
6020
|
-
(ctx) => {
|
|
6021
|
-
for (const stmt of plan.statements) {
|
|
6022
|
-
const result = execStatement(ctx, stmt.op, scope, dialect);
|
|
6023
|
-
executed.push(stmt.id);
|
|
6024
|
-
if (stmt.gate !== void 0) {
|
|
6025
|
-
const reason = gateShortCircuit(stmt.gate, result);
|
|
6026
|
-
if (reason !== null) {
|
|
6027
|
-
return { commit: false, value: { committed: false, shortCircuit: { statementId: stmt.id, reason }, entity: null, executed } };
|
|
6028
|
-
}
|
|
6029
|
-
}
|
|
6030
|
-
if (stmt.id === plan.entityFrom) {
|
|
6031
|
-
entity = result.rows.length > 0 ? result.rows[0] : null;
|
|
6032
|
-
if (entity !== null) scope[ENTITY_ROOT] = entity;
|
|
6033
|
-
}
|
|
6034
|
-
if (stmt.binds !== void 0 && result.rows.length > 0) {
|
|
6035
|
-
scope[stmt.binds] = result.rows[0];
|
|
6036
|
-
}
|
|
6037
|
-
if (isBatch && stmt.role === "body" && result.rows.length > 0) returnedRows.push(result.rows);
|
|
6038
|
-
}
|
|
6039
|
-
return { commit: true, value: { committed: true, entity, executed, ...returnedRows.length > 0 ? { returnedRows } : {} } };
|
|
6040
|
-
},
|
|
6041
|
-
dialect
|
|
6042
|
-
);
|
|
6043
|
-
} catch (e) {
|
|
6044
|
-
throw mapSqliteError(e);
|
|
6045
|
-
}
|
|
6046
|
-
}
|
|
6047
|
-
function countingDriver(db) {
|
|
6048
|
-
const prepared = [];
|
|
6049
|
-
const wrapped = {
|
|
6050
|
-
prepare(sql2) {
|
|
6051
|
-
prepared.push(sql2);
|
|
6052
|
-
return db.prepare(sql2);
|
|
6053
|
-
}
|
|
6054
|
-
};
|
|
6055
|
-
return { db: wrapped, prepared };
|
|
6056
|
-
}
|
|
6057
5686
|
function renderTxStatement(op, scope, dialect = "sqlite") {
|
|
6058
5687
|
return renderStatement(op, scope, dialect);
|
|
6059
5688
|
}
|
|
6060
5689
|
async function execStatementAsync(ctx, op, scope, dialect) {
|
|
6061
5690
|
const { sql: sql2, params } = evalAssemble(op, scope, dialect);
|
|
6062
5691
|
const hasReturn = /\bselect\b/i.test(sql2.slice(0, 8)) || /\breturning\b/i.test(sql2);
|
|
6063
|
-
const out = await executeSQLAsync({ sql: sql2, params, write:
|
|
5692
|
+
const out = await executeSQLAsync({ sql: sql2, params, write: { returning: hasReturn } }, { execAsync: ctx, dialect });
|
|
6064
5693
|
return hasReturn ? { rows: out, changes: out.length } : { rows: [], changes: Number(out[0]?.changes ?? 0) };
|
|
6065
5694
|
}
|
|
6066
5695
|
function executeTransactionAsync(ctx, plan, input, dialect = "sqlite", options = {}) {
|
|
@@ -6073,7 +5702,7 @@ function executeTransactionAsync(ctx, plan, input, dialect = "sqlite", options =
|
|
|
6073
5702
|
return runTransactionPlanAsync(ctx, plan, input, dialect, options);
|
|
6074
5703
|
}
|
|
6075
5704
|
function runTransactionPlanAsync(ctx, plan, input, dialect, options) {
|
|
6076
|
-
const
|
|
5705
|
+
const batch = isBatchPlan(plan);
|
|
6077
5706
|
return withTransactionAsync(ctx, async (txCtx) => {
|
|
6078
5707
|
const executed = [];
|
|
6079
5708
|
const scope = { ...input };
|
|
@@ -6082,12 +5711,6 @@ function runTransactionPlanAsync(ctx, plan, input, dialect, options) {
|
|
|
6082
5711
|
for (const stmt of plan.statements) {
|
|
6083
5712
|
const result = await execStatementAsync(txCtx, stmt.op, scope, dialect);
|
|
6084
5713
|
executed.push(stmt.id);
|
|
6085
|
-
if (stmt.gate !== void 0) {
|
|
6086
|
-
const reason = gateShortCircuit(stmt.gate, result);
|
|
6087
|
-
if (reason !== null) {
|
|
6088
|
-
throw new GateShortCircuit(stmt.id, reason, executed);
|
|
6089
|
-
}
|
|
6090
|
-
}
|
|
6091
5714
|
if (stmt.id === plan.entityFrom) {
|
|
6092
5715
|
entity = result.rows.length > 0 ? result.rows[0] : null;
|
|
6093
5716
|
if (entity !== null) scope[ENTITY_ROOT] = entity;
|
|
@@ -6095,28 +5718,13 @@ function runTransactionPlanAsync(ctx, plan, input, dialect, options) {
|
|
|
6095
5718
|
if (stmt.binds !== void 0 && result.rows.length > 0) {
|
|
6096
5719
|
scope[stmt.binds] = result.rows[0];
|
|
6097
5720
|
}
|
|
6098
|
-
if (
|
|
5721
|
+
if (batch && stmt.role === "body" && result.rows.length > 0) returnedRows.push(result.rows);
|
|
6099
5722
|
}
|
|
6100
5723
|
return { committed: true, entity, executed, ...returnedRows.length > 0 ? { returnedRows } : {} };
|
|
6101
5724
|
}, options, dialect === "sqlite" ? "postgres" : dialect, isConnectionError).catch((e) => {
|
|
6102
|
-
if (e instanceof GateShortCircuit) {
|
|
6103
|
-
return { committed: false, shortCircuit: { statementId: e.statementId, reason: e.reason }, entity: null, executed: e.executed };
|
|
6104
|
-
}
|
|
6105
5725
|
throw mapSqliteError(e);
|
|
6106
5726
|
});
|
|
6107
5727
|
}
|
|
6108
|
-
var GateShortCircuit = class extends Error {
|
|
6109
|
-
constructor(statementId, reason, executed) {
|
|
6110
|
-
super(`scp write: gate short-circuit at '${statementId}' (${reason})`);
|
|
6111
|
-
this.statementId = statementId;
|
|
6112
|
-
this.reason = reason;
|
|
6113
|
-
this.executed = executed;
|
|
6114
|
-
this.name = "GateShortCircuit";
|
|
6115
|
-
}
|
|
6116
|
-
statementId;
|
|
6117
|
-
reason;
|
|
6118
|
-
executed;
|
|
6119
|
-
};
|
|
6120
5728
|
|
|
6121
5729
|
// src/scp/makesql/pool-executor.ts
|
|
6122
5730
|
var PG_OID = { INT2: 21, INT4: 23, INT8: 20, DATE: 1082, TIMESTAMP: 1114, TIMESTAMPTZ: 1184, TIME: 1083, TIMETZ: 1266 };
|
|
@@ -6310,8 +5918,8 @@ function declarationStub(leafName, args) {
|
|
|
6310
5918
|
);
|
|
6311
5919
|
}
|
|
6312
5920
|
var Db = class {
|
|
6313
|
-
static executeSQL(sql2, params,
|
|
6314
|
-
return declarationStub("executeSQL", [sql2, params,
|
|
5921
|
+
static executeSQL(sql2, params, opts) {
|
|
5922
|
+
return declarationStub("executeSQL", [sql2, params, opts]);
|
|
6315
5923
|
}
|
|
6316
5924
|
static pluck(rows, col) {
|
|
6317
5925
|
return declarationStub("pluck", [rows, col]);
|
|
@@ -6514,11 +6122,7 @@ function targetKeyCols(op) {
|
|
|
6514
6122
|
}
|
|
6515
6123
|
function relationGuard(op) {
|
|
6516
6124
|
if (op.hardLimit === void 0) return null;
|
|
6517
|
-
return {
|
|
6518
|
-
limit: op.hardLimit,
|
|
6519
|
-
...op.targetTable !== void 0 ? { model: op.targetTable } : {},
|
|
6520
|
-
relation: op.name
|
|
6521
|
-
};
|
|
6125
|
+
return { limit: op.hardLimit, model: op.targetTable, relation: op.name };
|
|
6522
6126
|
}
|
|
6523
6127
|
function bindKeys(op, tuples) {
|
|
6524
6128
|
if (op.parentKeys !== void 0) return [encodeJsonParam(op.dialect, tuples.map((t) => [...t]))];
|
|
@@ -6536,7 +6140,10 @@ function runRelationOp(op, parents, db) {
|
|
|
6536
6140
|
const tCols = targetKeyCols(op);
|
|
6537
6141
|
const childCols = op.materializers;
|
|
6538
6142
|
const boundParams = bindKeys(op, keys);
|
|
6539
|
-
const rawRows = executeSafe(ctx, sql2, boundParams
|
|
6143
|
+
const rawRows = executeSafe(ctx, sql2, boundParams, {
|
|
6144
|
+
write: false,
|
|
6145
|
+
...op.connection !== void 0 ? { db: op.connection } : {}
|
|
6146
|
+
});
|
|
6540
6147
|
assertRelationHardLimit(rawRows, relationGuard(op));
|
|
6541
6148
|
const rows = materializeChildRows(rawRows, childCols);
|
|
6542
6149
|
return { sql: sql2, keys, batch: groupByKey(rows, tCols) };
|
|
@@ -6565,7 +6172,6 @@ function distributeToParent(op, parent, batch) {
|
|
|
6565
6172
|
function toPortableScalar(scalar) {
|
|
6566
6173
|
return scalar;
|
|
6567
6174
|
}
|
|
6568
|
-
var SHORT_CIRCUIT_TYPE = { obj: { statementId: "string", reason: "string" } };
|
|
6569
6175
|
function targetAndReturning(sql2) {
|
|
6570
6176
|
const s = sql2.trim();
|
|
6571
6177
|
const ins = /^INSERT\s+(?:IGNORE\s+|OR\s+IGNORE\s+)?INTO\s+([A-Za-z_][A-Za-z0-9_]*)/i.exec(s);
|
|
@@ -6600,9 +6206,6 @@ function rowObjType(table, returning, resolve) {
|
|
|
6600
6206
|
function bodyStatements(plan) {
|
|
6601
6207
|
return plan.statements.filter((s) => s.role === "body");
|
|
6602
6208
|
}
|
|
6603
|
-
function isBatchPlan(plan) {
|
|
6604
|
-
return plan.entityFrom === null && plan.statements.length > 0 && plan.statements.every((s) => s.gate === void 0 && s.binds === void 0 && s.role === "body");
|
|
6605
|
-
}
|
|
6606
6209
|
function writeTargetReturning(plan) {
|
|
6607
6210
|
const bodies = bodyStatements(plan);
|
|
6608
6211
|
if (bodies.length === 0) {
|
|
@@ -6639,7 +6242,6 @@ function deriveWriteOutputType(plan, resolve) {
|
|
|
6639
6242
|
const fields = {
|
|
6640
6243
|
committed: "bool",
|
|
6641
6244
|
executed: { arr: "string" },
|
|
6642
|
-
shortCircuit: { opt: SHORT_CIRCUIT_TYPE },
|
|
6643
6245
|
entity: { opt: row }
|
|
6644
6246
|
};
|
|
6645
6247
|
if (isBatchPlan(plan) && hasReturning) fields.returnedRows = { opt: { arr: { arr: row } } };
|
|
@@ -6656,27 +6258,6 @@ function annotateWriteBundleOutType(bundle, resolve) {
|
|
|
6656
6258
|
}
|
|
6657
6259
|
|
|
6658
6260
|
// src/scp/write-bundle.ts
|
|
6659
|
-
function writeLabelOf(sql2) {
|
|
6660
|
-
const m = /^\s*(INSERT|UPDATE|DELETE)/i.exec(sql2);
|
|
6661
|
-
return m === null ? "Write" : m[1][0].toUpperCase() + m[1].slice(1).toLowerCase();
|
|
6662
|
-
}
|
|
6663
|
-
function compileWriteBundle(name, base, writes, phase, dialectName = "sqlite", resolveColumnType) {
|
|
6664
|
-
const lifecycle = lifecycleFor(writes, phase);
|
|
6665
|
-
if (lifecycle === void 0) {
|
|
6666
|
-
throw new Error(`scp write: the '${phase}' lifecycle is not declared in the entityWrites save contract`);
|
|
6667
|
-
}
|
|
6668
|
-
const plan = deriveTransactionPlan(phase, [{ op: base, label: writeLabelOf(base.sql) }], lifecycle, dialectName);
|
|
6669
|
-
const bundle = { dialect: dialectName, name, statement: base, transaction: plan };
|
|
6670
|
-
return resolveColumnType === void 0 ? bundle : annotateWriteBundleOutType(bundle, resolveColumnType);
|
|
6671
|
-
}
|
|
6672
|
-
function compileCompositeWriteBundle(entries, phase, dialectName = "sqlite") {
|
|
6673
|
-
if (entries.length < 2) {
|
|
6674
|
-
throw new Error("scp write: a composite write bundle needs at least 2 named write members (use compileWriteBundle for a single write).");
|
|
6675
|
-
}
|
|
6676
|
-
const bases = entries.map((e) => ({ op: e.base, label: `${writeLabelOf(e.base.sql)} ${e.name}`, name: e.name, effects: e.lifecycle.effects }));
|
|
6677
|
-
const plan = deriveTransactionPlan(phase, bases, { effects: {} }, dialectName);
|
|
6678
|
-
return { dialect: dialectName, name: entries[0].name, statement: entries[0].base, transaction: plan };
|
|
6679
|
-
}
|
|
6680
6261
|
function flattenBatchOp(node, label) {
|
|
6681
6262
|
const assembled = assembleMakeSQL(node);
|
|
6682
6263
|
return { sql: assembled.sql, params: assembled.params, label };
|
|
@@ -6708,14 +6289,11 @@ function compileDeleteManyBundle(name, options, dialectName = "sqlite") {
|
|
|
6708
6289
|
const components = compileDeleteMany({ dialect: dialectName, ...options });
|
|
6709
6290
|
const ops = components.map((c, i) => flattenBatchOp(c, `deleteMany group ${i}`));
|
|
6710
6291
|
if (ops.length === 0) {
|
|
6711
|
-
return { dialect: dialectName, name, statement: { sql: "", params: [] }, transaction: { phase: "remove", entityFrom: null, statements: []
|
|
6292
|
+
return { dialect: dialectName, name, statement: { sql: "", params: [] }, transaction: { phase: "remove", entityFrom: null, statements: [] } };
|
|
6712
6293
|
}
|
|
6713
6294
|
const plan = deriveBatchPlan("remove", ops);
|
|
6714
6295
|
return { dialect: dialectName, name, statement: { sql: ops[0].sql, params: ops[0].params }, transaction: plan };
|
|
6715
6296
|
}
|
|
6716
|
-
function executeTransactionBundle(bundle, input, options) {
|
|
6717
|
-
return executeTransaction(options.db, bundle.transaction, input, bundle.dialect);
|
|
6718
|
-
}
|
|
6719
6297
|
|
|
6720
6298
|
// src/decorators.ts
|
|
6721
6299
|
var import_reflect_metadata = __toESM(require_Reflect());
|
|
@@ -7236,6 +6814,9 @@ function deriveModelColumns(modelClass, options = {}) {
|
|
|
7236
6814
|
function tableNameOf(modelClass) {
|
|
7237
6815
|
return modelClass.TABLE_NAME ?? modelClass.name.toLowerCase();
|
|
7238
6816
|
}
|
|
6817
|
+
function connectionOf(modelClass) {
|
|
6818
|
+
return modelClass.CONNECTION;
|
|
6819
|
+
}
|
|
7239
6820
|
function compileCreateBundle(modelClass, name, options, dialect = "sqlite", columnsOptions) {
|
|
7240
6821
|
const resolveColumnType = modelColumnResolver(modelClass, columnsOptions);
|
|
7241
6822
|
return compileCreateManyBundle(name, options, dialect, resolveColumnType);
|
|
@@ -7276,12 +6857,14 @@ function relationDeclOf(rel, resolveTargetModel, dialect = "sqlite") {
|
|
|
7276
6857
|
const targetTable = tableNameOf(targetModel);
|
|
7277
6858
|
const select = targetProjection(targetModel);
|
|
7278
6859
|
const order = rel.options?.order ? orderToString(rel.options.order()) : void 0;
|
|
6860
|
+
const connection = connectionOf(targetModel);
|
|
7279
6861
|
const base = {
|
|
7280
6862
|
name: rel.propertyKey,
|
|
7281
6863
|
kind: rel.type,
|
|
7282
6864
|
targetTable,
|
|
7283
6865
|
select,
|
|
7284
6866
|
dialect,
|
|
6867
|
+
...connection !== void 0 ? { connection } : {},
|
|
7285
6868
|
...order !== void 0 ? { order } : {},
|
|
7286
6869
|
...rel.options?.limit !== void 0 ? { limit: rel.options.limit } : {},
|
|
7287
6870
|
...rel.options?.hardLimit !== void 0 ? { hardLimit: rel.options.hardLimit } : {}
|
|
@@ -7373,6 +6956,20 @@ var EmitContext = class {
|
|
|
7373
6956
|
bindTypeOf(model, column2) {
|
|
7374
6957
|
return tsScalar(sqlTypeToBcScalar(this.sqlTypeOf(model, column2)));
|
|
7375
6958
|
}
|
|
6959
|
+
/**
|
|
6960
|
+
* The NAMED connection every statement of an endpoint declared over `model` runs on — read from the
|
|
6961
|
+
* model, the ONE authority ({@link connectionOf}), and handed to {@link execOptions} as its `db`
|
|
6962
|
+
* field. `{}` ⇒ the DEFAULT connection, which leaves a bounded read's option record unspelled
|
|
6963
|
+
* entirely (native-clean).
|
|
6964
|
+
*
|
|
6965
|
+
* A RELATION's child fetch does NOT come through here: its connection is the TARGET model's, which
|
|
6966
|
+
* `relationDeclOf` already resolved onto the compiled op (`RelationOp.connection`) — the emitter
|
|
6967
|
+
* consumes that rather than re-deriving it from a second model handle.
|
|
6968
|
+
*/
|
|
6969
|
+
dbOf(model) {
|
|
6970
|
+
const db = connectionOf(model);
|
|
6971
|
+
return db !== void 0 ? { db } : {};
|
|
6972
|
+
}
|
|
7376
6973
|
// ── methods ────────────────────────────────────────────────────────────────────────────────
|
|
7377
6974
|
method(name, endpoint) {
|
|
7378
6975
|
assertIdentifier(name, `endpoint name '${name}'`);
|
|
@@ -7436,12 +7033,7 @@ var EmitContext = class {
|
|
|
7436
7033
|
const readResolve = endpoint.view !== void 0 ? (t, c) => resolve(t === alias ? baseTable : t, c) : resolve;
|
|
7437
7034
|
const cap = this.bakedFindCap(endpoint);
|
|
7438
7035
|
const where = endpoint.where ?? [];
|
|
7439
|
-
const
|
|
7440
|
-
if (dynamic && endpoint.view !== void 0) {
|
|
7441
|
-
throw new Error(
|
|
7442
|
-
`emit: endpoint '${name}': a QUERY view-model binds its own CTE params BEFORE the WHERE, and a dynamic (SKIP) WHERE is assembled at execution time \u2014 the two cannot share one param order. Declare the view's own predicate inside the view query, or drop the optional predicate.`
|
|
7443
|
-
);
|
|
7444
|
-
}
|
|
7036
|
+
const optional = where.filter(isOptional);
|
|
7445
7037
|
const params = [];
|
|
7446
7038
|
for (const p of where) this.declareWhereParams(model, p, params);
|
|
7447
7039
|
const limit = this.page(endpoint.limit ?? (cap !== null ? cap + 1 : void 0), params);
|
|
@@ -7450,15 +7042,21 @@ var EmitContext = class {
|
|
|
7450
7042
|
dialect: this.spec.dialect,
|
|
7451
7043
|
tableName: table,
|
|
7452
7044
|
select: projection.join(", "),
|
|
7453
|
-
|
|
7045
|
+
// The BOUNDED predicates — this read's static WHERE, whatever else it declares. (An all-optional
|
|
7046
|
+
// read compiles an EMPTY condition set, which is the same no-WHERE statement it always was.)
|
|
7047
|
+
conditions: this.conditions(model, where.filter((p) => !isOptional(p))),
|
|
7454
7048
|
...endpoint.view !== void 0 ? { cte: cteOf(endpoint.view) } : {},
|
|
7455
7049
|
...endpoint.order !== void 0 ? { order: endpoint.order } : {},
|
|
7456
7050
|
...limit !== void 0 ? { limit } : {},
|
|
7457
7051
|
...offset !== void 0 ? { offset } : {},
|
|
7458
7052
|
...endpoint.lock === "update" ? { forUpdate: true } : endpoint.lock === "share" ? { forShare: true } : {}
|
|
7459
7053
|
});
|
|
7460
|
-
const
|
|
7461
|
-
const
|
|
7054
|
+
const plan = optional.length > 0 ? this.dynamicWherePlan(model, optional, compiled) : void 0;
|
|
7055
|
+
const [sql2, slots] = plan === void 0 ? [compiled.sql, compiled.params] : [compiled.head, compiled.headParams];
|
|
7056
|
+
const call = `${quote(sql2)}, [${slots.map(renderSlot).join(", ")}]${execOptions({
|
|
7057
|
+
...this.dbOf(model),
|
|
7058
|
+
...plan !== void 0 ? { whereDynamic: plan } : {}
|
|
7059
|
+
})}`;
|
|
7462
7060
|
const rowObj = objOf(deriveReadRow(table, projection, readResolve, `endpoint '${name}'`).outType, name);
|
|
7463
7061
|
const selections = normalizeSelections(endpoint.with ?? []);
|
|
7464
7062
|
const fresh = freshNames(params.map((p) => p.name));
|
|
@@ -7475,28 +7073,35 @@ var EmitContext = class {
|
|
|
7475
7073
|
const lines = [];
|
|
7476
7074
|
const parentVar = fresh("rows");
|
|
7477
7075
|
lines.push(`const ${parentVar}: WireValue[] = Db.executeSQL(${call});`);
|
|
7478
|
-
const graph = this.relations(model, parentVar, rowObj, selections, lines, fresh,
|
|
7076
|
+
const graph = this.relations(model, parentVar, rowObj, selections, lines, fresh, `endpoint '${name}'`);
|
|
7479
7077
|
const rowType = this.declareInterface(`${cap1(name)}Row`, graph.obj);
|
|
7480
7078
|
lines[lines.length - 1] = `const ${graph.terminalVar}: ${rowType}[] = ${graph.terminalCall} as ${rowType}[];`;
|
|
7481
7079
|
lines.push(`return ${graph.terminalVar};`);
|
|
7482
7080
|
return { name, params, returnType: `${rowType}[]`, lines };
|
|
7483
7081
|
}
|
|
7484
7082
|
/**
|
|
7485
|
-
* The DYNAMIC WHERE plan expression: ONE fragment per
|
|
7486
|
-
*
|
|
7487
|
-
*
|
|
7488
|
-
* `
|
|
7489
|
-
* SQL + params + a SKIP flag). The array is HOMOGENEOUS — never a `cond`-to-null
|
|
7490
|
-
* go/rust native-codegen emitters resolve it (a variant/nullable array element is the
|
|
7491
|
-
* reject); the `executeSQL` leaf drops the `skipped` fragments at run
|
|
7083
|
+
* The DYNAMIC WHERE plan expression: ONE fragment per OPTIONAL predicate — never a bounded one, which
|
|
7084
|
+
* is already in the static statement (CLAUDE.md §2) — every fragment the SAME struct
|
|
7085
|
+
* `{skipped, sql, params}`, with `sql`/`params` from the SAME `compileWhere` that produces a bounded
|
|
7086
|
+
* WHERE and `skipped` the per-call SKIP decision carried as DATA (`param === null`). That is the whole
|
|
7087
|
+
* fragment vocabulary (SQL + params + a SKIP flag). The array is HOMOGENEOUS — never a `cond`-to-null
|
|
7088
|
+
* element — so the go/rust native-codegen emitters resolve it (a variant/nullable array element is the
|
|
7089
|
+
* one shape they reject); the `executeSQL` leaf drops the `skipped` fragments at run and continues the
|
|
7090
|
+
* static WHERE with the survivors.
|
|
7091
|
+
*
|
|
7092
|
+
* The plan also carries what FINISHES the statement, taken straight off the bundle this same read
|
|
7093
|
+
* compiled ({@link import('../makesql/compile-select').SelectBundle}): `lead` — the connector the first
|
|
7094
|
+
* survivor joins with, which is `AND` exactly when the builder emitted a static WHERE — and `tail` /
|
|
7095
|
+
* `tailParams`, the text and bound values that follow the WHERE region. The leaf then concatenates
|
|
7096
|
+
* `head` + clause + `tail`; nothing on the run side has to find the boundary again, which is what a
|
|
7097
|
+
* lexical scan got wrong on a nested statement (#198) and on a quoted `?` (#202).
|
|
7492
7098
|
*/
|
|
7493
|
-
dynamicWherePlan(model,
|
|
7494
|
-
const frags =
|
|
7099
|
+
dynamicWherePlan(model, optional, compiled) {
|
|
7100
|
+
const frags = optional.map((p) => {
|
|
7495
7101
|
const w = compileWhere(this.conditions(model, [p]), this.spec.dialect);
|
|
7496
|
-
|
|
7497
|
-
return `{ skipped: ${skipped}, sql: ${quote(w.sql)}, params: [${w.params.map(renderSlot).join(", ")}] }`;
|
|
7102
|
+
return `{ skipped: ${p.param} === null, sql: ${quote(w.sql)}, params: [${w.params.map(renderSlot).join(", ")}] }`;
|
|
7498
7103
|
});
|
|
7499
|
-
return `{ frags: [${frags.join(", ")}] }`;
|
|
7104
|
+
return `{ frags: [${frags.join(", ")}], lead: ${quote(compiled.lead)}, tail: ${quote(compiled.tail)}, tailParams: [${compiled.tailParams.map(renderSlot).join(", ")}] }`;
|
|
7500
7105
|
}
|
|
7501
7106
|
/**
|
|
7502
7107
|
* Emit ONE relation LEVEL: for each selected relation, pluck the parent key set, run the ONE batched
|
|
@@ -7504,7 +7109,7 @@ var EmitContext = class {
|
|
|
7504
7109
|
* selection recurses FIRST, so a grandchild level is grouped into its own parent before that parent
|
|
7505
7110
|
* is grouped into ours (one query per depth — never one per parent).
|
|
7506
7111
|
*/
|
|
7507
|
-
relations(model, parentVar, parentObj, selections, lines, fresh,
|
|
7112
|
+
relations(model, parentVar, parentObj, selections, lines, fresh, at) {
|
|
7508
7113
|
let acc = parentVar;
|
|
7509
7114
|
const obj = { ...parentObj };
|
|
7510
7115
|
let terminalCall = "";
|
|
@@ -7516,16 +7121,20 @@ var EmitContext = class {
|
|
|
7516
7121
|
lines.push(`const ${keysVar}: WireValue[] = Db.pluck(${acc}, ${jsonArray(parentKeyCols(op))});`);
|
|
7517
7122
|
const childVar = fresh(sel.name);
|
|
7518
7123
|
const guard = relationGuard(op);
|
|
7519
|
-
const guardExpr = guard !== null ? `{ limit: ${guard.limit} as Int,
|
|
7520
|
-
|
|
7521
|
-
|
|
7124
|
+
const guardExpr = guard !== null ? `{ limit: ${guard.limit} as Int, model: ${quote(guard.model)}, relation: ${quote(guard.relation)} }` : void 0;
|
|
7125
|
+
lines.push(
|
|
7126
|
+
`const ${childVar}: WireValue[] = Db.executeSQL(${quote(op.sql)}, [${keysVar}]${execOptions({
|
|
7127
|
+
...op.connection !== void 0 ? { db: op.connection } : {},
|
|
7128
|
+
...guardExpr !== void 0 ? { guard: guardExpr } : {}
|
|
7129
|
+
})});`
|
|
7130
|
+
);
|
|
7522
7131
|
let childRows = childVar;
|
|
7523
7132
|
let childObj = objOf(
|
|
7524
7133
|
deriveReadRow(decl.targetTable, decl.select, this.resolver(targetModel), `${at} relation '${sel.name}'`).outType,
|
|
7525
7134
|
`${at} relation '${sel.name}'`
|
|
7526
7135
|
);
|
|
7527
7136
|
if (sel.with.length > 0) {
|
|
7528
|
-
const nested = this.relations(targetModel, childVar, childObj, normalizeSelections(sel.with), lines, fresh,
|
|
7137
|
+
const nested = this.relations(targetModel, childVar, childObj, normalizeSelections(sel.with), lines, fresh, `${at}.${sel.name}`);
|
|
7529
7138
|
childObj = nested.obj;
|
|
7530
7139
|
childRows = nested.terminalVar;
|
|
7531
7140
|
}
|
|
@@ -7582,7 +7191,7 @@ var EmitContext = class {
|
|
|
7582
7191
|
params,
|
|
7583
7192
|
returnType: `${rowType}[]`,
|
|
7584
7193
|
lines: [
|
|
7585
|
-
`const ${out}: ${rowType}[] = Db.executeSQL(${quote(op.sql)}, [${op.params.map(renderSlot).join(", ")}]
|
|
7194
|
+
`const ${out}: ${rowType}[] = Db.executeSQL(${quote(op.sql)}, [${op.params.map(renderSlot).join(", ")}]${execOptions({ ...this.dbOf(model), write: { returning: returning !== void 0 } })}) as ${rowType}[];`,
|
|
7586
7195
|
`return ${out};`
|
|
7587
7196
|
]
|
|
7588
7197
|
};
|
|
@@ -7659,7 +7268,7 @@ var EmitContext = class {
|
|
|
7659
7268
|
params,
|
|
7660
7269
|
returnType: `${rowType}[]`,
|
|
7661
7270
|
lines: [
|
|
7662
|
-
`const ${out}: ${rowType}[] = Db.executeSQL(${quote(op.sql)}, [${slots.join(", ")}]
|
|
7271
|
+
`const ${out}: ${rowType}[] = Db.executeSQL(${quote(op.sql)}, [${slots.join(", ")}]${execOptions({ ...this.dbOf(model), write: { returning: endpoint.returning !== void 0 } })}) as ${rowType}[];`,
|
|
7663
7272
|
`return ${out};`
|
|
7664
7273
|
]
|
|
7665
7274
|
};
|
|
@@ -7687,7 +7296,7 @@ var EmitContext = class {
|
|
|
7687
7296
|
params,
|
|
7688
7297
|
returnType: `${rowType}[]`,
|
|
7689
7298
|
lines: [
|
|
7690
|
-
`const ${out}: ${rowType}[] = Db.executeSQL(${quote(op.sql)}, [${op.params.map(renderSlot).join(", ")}]
|
|
7299
|
+
`const ${out}: ${rowType}[] = Db.executeSQL(${quote(op.sql)}, [${op.params.map(renderSlot).join(", ")}]${execOptions({ ...this.dbOf(model), write: { returning: endpoint.returning !== void 0 } })}) as ${rowType}[];`,
|
|
7691
7300
|
`return ${out};`
|
|
7692
7301
|
]
|
|
7693
7302
|
};
|
|
@@ -7905,6 +7514,11 @@ function isParameterised(p) {
|
|
|
7905
7514
|
function isOptional(p) {
|
|
7906
7515
|
return isParameterised(p) && p.optional === true;
|
|
7907
7516
|
}
|
|
7517
|
+
function execOptions(o) {
|
|
7518
|
+
if (o.db === void 0 && o.write === void 0 && o.whereDynamic === void 0 && o.guard === void 0) return "";
|
|
7519
|
+
const write = o.write === void 0 ? "null" : `{ returning: ${o.write.returning} }`;
|
|
7520
|
+
return `, { db: ${o.db === void 0 ? "null" : quote(o.db)}, write: ${write}, whereDynamic: ${o.whereDynamic ?? "null"}, guard: ${o.guard ?? "null"} }`;
|
|
7521
|
+
}
|
|
7908
7522
|
function renderSlot(slot) {
|
|
7909
7523
|
if (slot instanceof ParamRef) return slot.expr;
|
|
7910
7524
|
const ref = slot?.__batchArray?.ref;
|
|
@@ -8079,20 +7693,13 @@ var RelationContext = class {
|
|
|
8079
7693
|
ops;
|
|
8080
7694
|
db;
|
|
8081
7695
|
hydrate;
|
|
8082
|
-
/** CROSS-DB (V0 R1): name → driver registry; a tagged relation routes to `connections[tag]`. */
|
|
8083
|
-
connections;
|
|
8084
7696
|
/** Per-relation resolved batch (name → grouping), memoized: one batch query per relation. */
|
|
8085
7697
|
resolved = /* @__PURE__ */ new Map();
|
|
8086
|
-
constructor(siblings, ops, db, hydrate
|
|
7698
|
+
constructor(siblings, ops, db, hydrate) {
|
|
8087
7699
|
this.siblings = siblings;
|
|
8088
7700
|
this.ops = ops;
|
|
8089
7701
|
this.db = db;
|
|
8090
7702
|
this.hydrate = hydrate;
|
|
8091
|
-
this.connections = connections;
|
|
8092
|
-
}
|
|
8093
|
-
/** The driver a relation runs against: its tagged cross-DB connection, else the primary `db`. */
|
|
8094
|
-
driverFor(op) {
|
|
8095
|
-
return op.connection === void 0 ? this.db : driverForOp(op, this.connections);
|
|
8096
7703
|
}
|
|
8097
7704
|
/**
|
|
8098
7705
|
* Resolve `relationName` for `parent` (lazy access). The relation is batch-loaded across
|
|
@@ -8106,19 +7713,12 @@ var RelationContext = class {
|
|
|
8106
7713
|
}
|
|
8107
7714
|
let batch = this.resolved.get(relationName);
|
|
8108
7715
|
if (batch === void 0) {
|
|
8109
|
-
batch = runRelationOp(op, this.siblings, this.
|
|
7716
|
+
batch = runRelationOp(op, this.siblings, this.db).batch;
|
|
8110
7717
|
this.resolved.set(relationName, batch);
|
|
8111
7718
|
}
|
|
8112
7719
|
return applyHydrate(distributeToParent(op, parent, batch), this.hydrate);
|
|
8113
7720
|
}
|
|
8114
7721
|
};
|
|
8115
|
-
function driverForOp(op, connections) {
|
|
8116
|
-
const d = connections?.[op.connection];
|
|
8117
|
-
if (d === void 0) {
|
|
8118
|
-
throw new Error(`cross-DB relation '${op.name}': no driver registered for connection '${op.connection}' (pass it in ReadOptions.connections)`);
|
|
8119
|
-
}
|
|
8120
|
-
return d;
|
|
8121
|
-
}
|
|
8122
7722
|
function readRelationContext(o) {
|
|
8123
7723
|
const v = o[RELATION_CONTEXT];
|
|
8124
7724
|
return v instanceof RelationContext ? v : void 0;
|
|
@@ -8156,7 +7756,7 @@ function buildResultSet(rawRows, ops, db, options = {}) {
|
|
|
8156
7756
|
for (const k of Object.keys(raw)) o[k] = raw[k];
|
|
8157
7757
|
return o;
|
|
8158
7758
|
});
|
|
8159
|
-
const ctx = new RelationContext(rows, ops, db, hydrate
|
|
7759
|
+
const ctx = new RelationContext(rows, ops, db, hydrate);
|
|
8160
7760
|
for (const o of rows) {
|
|
8161
7761
|
Object.defineProperty(o, RELATION_CONTEXT, {
|
|
8162
7762
|
value: ctx,
|
|
@@ -8168,8 +7768,7 @@ function buildResultSet(rawRows, ops, db, options = {}) {
|
|
|
8168
7768
|
for (const name of Object.keys(selected)) {
|
|
8169
7769
|
const op = ops[name];
|
|
8170
7770
|
if (op === void 0) throw new Error(`declarative select: relation '${name}' is not declared on this model`);
|
|
8171
|
-
const
|
|
8172
|
-
const { batch } = runRelationOp(op, rows, relDb);
|
|
7771
|
+
const { batch } = runRelationOp(op, rows, db);
|
|
8173
7772
|
for (const o of rows) {
|
|
8174
7773
|
o[name] = applyHydrate(distributeToParent(op, o, batch), hydrate);
|
|
8175
7774
|
}
|
|
@@ -8186,7 +7785,6 @@ function buildResultSet(rawRows, ops, db, options = {}) {
|
|
|
8186
7785
|
DEFAULT_CONNECTION,
|
|
8187
7786
|
DEFAULT_UNCAST_SQL_TYPE,
|
|
8188
7787
|
Db,
|
|
8189
|
-
ENTITY_ROOT,
|
|
8190
7788
|
LEAF_TRANSPORT_SYMBOLS,
|
|
8191
7789
|
LITEDBMODEL_MAKESQL_CATALOG,
|
|
8192
7790
|
LimitExceededError,
|
|
@@ -8223,7 +7821,6 @@ function buildResultSet(rawRows, ops, db, options = {}) {
|
|
|
8223
7821
|
compileCompositeKeyLimited,
|
|
8224
7822
|
compileCompositeKeyStaticUnlimited,
|
|
8225
7823
|
compileCompositeKeyUnlimited,
|
|
8226
|
-
compileCompositeWriteBundle,
|
|
8227
7824
|
compileCreateBundle,
|
|
8228
7825
|
compileCreateManyBundle,
|
|
8229
7826
|
compileDeleteBundle,
|
|
@@ -8242,7 +7839,6 @@ function buildResultSet(rawRows, ops, db, options = {}) {
|
|
|
8242
7839
|
compileUpdateManyBundle,
|
|
8243
7840
|
compileUpdateSingle,
|
|
8244
7841
|
compileWhere,
|
|
8245
|
-
compileWriteBundle,
|
|
8246
7842
|
compileWriteNode,
|
|
8247
7843
|
composeMakeSQL,
|
|
8248
7844
|
configurePgDeboxTypeParsers,
|
|
@@ -8250,7 +7846,6 @@ function buildResultSet(rawRows, ops, db, options = {}) {
|
|
|
8250
7846
|
connectionForDriver,
|
|
8251
7847
|
contextForConnection,
|
|
8252
7848
|
contextForDriver,
|
|
8253
|
-
countingDriver,
|
|
8254
7849
|
createMiddleware,
|
|
8255
7850
|
currentPinnedAsyncConnection,
|
|
8256
7851
|
currentRegistry,
|
|
@@ -8258,20 +7853,15 @@ function buildResultSet(rawRows, ops, db, options = {}) {
|
|
|
8258
7853
|
dbCastIn,
|
|
8259
7854
|
deriveModelColumns,
|
|
8260
7855
|
deriveRelationDecls,
|
|
8261
|
-
deriveTransactionPlan,
|
|
8262
7856
|
dialectFor,
|
|
8263
7857
|
distributeToParent,
|
|
8264
|
-
edgeWrites,
|
|
8265
7858
|
emitBehaviorModule,
|
|
8266
|
-
entityWrites,
|
|
8267
7859
|
execute,
|
|
8268
7860
|
executeAsync,
|
|
8269
7861
|
executeSQL,
|
|
8270
7862
|
executeSQLAsync,
|
|
8271
7863
|
executeSafe,
|
|
8272
|
-
executeTransaction,
|
|
8273
7864
|
executeTransactionAsync,
|
|
8274
|
-
executeTransactionBundle,
|
|
8275
7865
|
failClosedMaterializeResolverFromColumnMap,
|
|
8276
7866
|
formatterFor,
|
|
8277
7867
|
getLimitConfig,
|
|
@@ -8286,7 +7876,6 @@ function buildResultSet(rawRows, ops, db, options = {}) {
|
|
|
8286
7876
|
leaf,
|
|
8287
7877
|
leafHandlers,
|
|
8288
7878
|
leafHandlersAsync,
|
|
8289
|
-
lifecycleFor,
|
|
8290
7879
|
makeSqlCatalogEntry,
|
|
8291
7880
|
makeSqlHandler,
|
|
8292
7881
|
makeSqlHandlerSync,
|
|
@@ -8301,7 +7890,6 @@ function buildResultSet(rawRows, ops, db, options = {}) {
|
|
|
8301
7890
|
mysqlPoolFactory,
|
|
8302
7891
|
noCastFormatter,
|
|
8303
7892
|
opt,
|
|
8304
|
-
parseEffectPath,
|
|
8305
7893
|
parseSchemaColumnTypes,
|
|
8306
7894
|
pgCastFormatter,
|
|
8307
7895
|
pgConnectionPool,
|
|
@@ -8342,7 +7930,6 @@ function buildResultSet(rawRows, ops, db, options = {}) {
|
|
|
8342
7930
|
setLimitConfig,
|
|
8343
7931
|
singlePoolPair,
|
|
8344
7932
|
sleep,
|
|
8345
|
-
spliceWhere,
|
|
8346
7933
|
sqlTypeToBcScalar,
|
|
8347
7934
|
sqlTypeToMaterializeClass,
|
|
8348
7935
|
stripMysqlPkHint,
|