litedbmodel 2.2.9 → 2.2.11
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 +8 -5
- package/dist/DBHandler.d.ts +2 -28
- package/dist/DBHandler.d.ts.map +1 -1
- package/dist/DBHandler.js.map +1 -1
- package/dist/DBModel.d.ts +14 -12
- package/dist/DBModel.d.ts.map +1 -1
- package/dist/DBModel.js +71 -72
- package/dist/DBModel.js.map +1 -1
- package/dist/LazyRelation.d.ts +1 -0
- package/dist/LazyRelation.d.ts.map +1 -1
- package/dist/LazyRelation.js +17 -14
- package/dist/LazyRelation.js.map +1 -1
- package/dist/Middleware.d.ts +19 -17
- package/dist/Middleware.d.ts.map +1 -1
- package/dist/Middleware.js +30 -33
- package/dist/Middleware.js.map +1 -1
- package/dist/drivers/mysql.d.ts.map +1 -1
- package/dist/drivers/mysql.js +1 -0
- package/dist/drivers/mysql.js.map +1 -1
- package/dist/drivers/postgres.d.ts.map +1 -1
- package/dist/drivers/postgres.js +42 -22
- package/dist/drivers/postgres.js.map +1 -1
- package/dist/drivers/types.d.ts +2 -26
- package/dist/drivers/types.d.ts.map +1 -1
- package/dist/drivers/types.js.map +1 -1
- package/dist/index.cjs +224 -163
- package/dist/scp/connection-routing.d.ts +50 -18
- package/dist/scp/connection-routing.d.ts.map +1 -1
- package/dist/scp/connection-routing.js +55 -24
- package/dist/scp/connection-routing.js.map +1 -1
- package/dist/scp/dbmodel-runtime.d.ts +3 -0
- package/dist/scp/dbmodel-runtime.d.ts.map +1 -1
- package/dist/scp/dbmodel-runtime.js +2 -0
- package/dist/scp/dbmodel-runtime.js.map +1 -1
- package/dist/scp/exec-context.d.ts.map +1 -1
- package/dist/scp/exec-context.js +54 -46
- package/dist/scp/exec-context.js.map +1 -1
- package/dist/scp/index.cjs +99 -48
- package/dist/scp/index.mjs +96 -47
- package/dist/scp/makesql/pool-executor.d.ts +4 -1
- package/dist/scp/makesql/pool-executor.d.ts.map +1 -1
- package/dist/scp/makesql/pool-executor.js +21 -3
- package/dist/scp/makesql/pool-executor.js.map +1 -1
- package/dist/types.d.ts +9 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/docs/BENCHMARK.md +1 -1
- package/docs/COMPARISON.md +2 -2
- package/docs/api/README.md +1 -1
- package/docs/api/classes/Conditions.md +1 -1
- package/docs/api/classes/DBHandler.md +16 -16
- package/docs/api/classes/DBModel.md +94 -93
- package/docs/api/classes/LimitExceededError.md +8 -8
- package/docs/api/classes/Middleware.md +35 -31
- package/docs/api/classes/SqlRaw.md +1 -1
- package/docs/api/classes/SqlRef.md +1 -1
- package/docs/api/classes/Values.md +1 -1
- package/docs/api/classes/WriteInReadOnlyContextError.md +5 -5
- package/docs/api/classes/WriteOutsideTransactionError.md +5 -5
- package/docs/api/functions/belongsTo.md +1 -1
- package/docs/api/functions/closeAllPools.md +2 -2
- package/docs/api/functions/condsToRecord.md +1 -1
- package/docs/api/functions/createMiddleware.md +4 -4
- package/docs/api/functions/createPostgresDriver.md +2 -2
- package/docs/api/functions/createSqliteDriver.md +1 -1
- package/docs/api/functions/dbCast.md +1 -1
- package/docs/api/functions/dbCastIn.md +1 -1
- package/docs/api/functions/dbDynamic.md +1 -1
- package/docs/api/functions/dbFalse.md +1 -1
- package/docs/api/functions/dbImmediate.md +1 -1
- package/docs/api/functions/dbIn.md +1 -1
- package/docs/api/functions/dbNotNull.md +1 -1
- package/docs/api/functions/dbNow.md +1 -1
- package/docs/api/functions/dbNull.md +1 -1
- package/docs/api/functions/dbRaw.md +1 -1
- package/docs/api/functions/dbTrue.md +1 -1
- package/docs/api/functions/dbUuid.md +1 -1
- package/docs/api/functions/dbUuidIn.md +1 -1
- package/docs/api/functions/formatLocalDate.md +1 -1
- package/docs/api/functions/formatUTCDate.md +1 -1
- 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 +1 -1
- package/docs/api/functions/hasOne.md +1 -1
- package/docs/api/functions/initDBHandler.md +2 -2
- package/docs/api/functions/isAnySqlFragment.md +1 -1
- package/docs/api/functions/isConnectionError.md +1 -1
- package/docs/api/functions/isSqlCondition.md +1 -1
- package/docs/api/functions/isSqlFragment.md +1 -1
- package/docs/api/functions/isSqlRaw.md +1 -1
- package/docs/api/functions/isSqlRef.md +1 -1
- package/docs/api/functions/isSqlTypedFragment.md +1 -1
- package/docs/api/functions/model.md +1 -1
- package/docs/api/functions/parentRef.md +1 -1
- package/docs/api/functions/sql.md +1 -1
- package/docs/api/globals.md +5 -5
- package/docs/api/interfaces/Column.md +1 -1
- package/docs/api/interfaces/ColumnDecorator.md +1 -1
- package/docs/api/interfaces/ColumnOptions.md +1 -1
- package/docs/api/interfaces/CreatedMiddlewareClass.md +7 -7
- package/docs/api/interfaces/DBConfig.md +29 -14
- 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/ModelClassDecorator.md +1 -1
- package/docs/api/interfaces/ModelOptions.md +1 -1
- package/docs/api/interfaces/PkeyResult.md +1 -1
- package/docs/api/interfaces/RelationDecorator.md +1 -1
- package/docs/api/interfaces/SelectOptions.md +17 -17
- package/docs/api/interfaces/SqlCondition.md +1 -1
- package/docs/api/interfaces/SqlFragment.md +1 -1
- package/docs/api/interfaces/SqlTypedFragment.md +1 -1
- 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/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -3092,11 +3092,27 @@ function mysqlConnection(conn) {
|
|
|
3092
3092
|
}
|
|
3093
3093
|
};
|
|
3094
3094
|
}
|
|
3095
|
-
function mysqlConnectionPool(pool) {
|
|
3095
|
+
function mysqlConnectionPool(pool, acquireTimeoutMillis) {
|
|
3096
3096
|
const handles = /* @__PURE__ */ new WeakMap();
|
|
3097
3097
|
return {
|
|
3098
3098
|
async acquire() {
|
|
3099
|
-
const
|
|
3099
|
+
const pending = pool.getConnection();
|
|
3100
|
+
const raw = acquireTimeoutMillis === void 0 ? await pending : await new Promise((resolve, reject) => {
|
|
3101
|
+
const timer = setTimeout(() => {
|
|
3102
|
+
pending.then((late) => late.release(), () => void 0);
|
|
3103
|
+
reject(new Error(`timeout exceeded when trying to get a connection (${acquireTimeoutMillis} ms)`));
|
|
3104
|
+
}, acquireTimeoutMillis);
|
|
3105
|
+
pending.then(
|
|
3106
|
+
(c) => {
|
|
3107
|
+
clearTimeout(timer);
|
|
3108
|
+
resolve(c);
|
|
3109
|
+
},
|
|
3110
|
+
(e) => {
|
|
3111
|
+
clearTimeout(timer);
|
|
3112
|
+
reject(e);
|
|
3113
|
+
}
|
|
3114
|
+
);
|
|
3115
|
+
});
|
|
3100
3116
|
const conn = mysqlConnection(raw);
|
|
3101
3117
|
handles.set(conn, raw);
|
|
3102
3118
|
return conn;
|
|
@@ -3121,6 +3137,9 @@ function pgPoolFactory(pg) {
|
|
|
3121
3137
|
// SIZING (construction knob): the config is the sole source of the cap.
|
|
3122
3138
|
max: config.maxPool,
|
|
3123
3139
|
min: config.minPool,
|
|
3140
|
+
// pg-pool bounds BOTH a new connection and the wait for a free one with this one limit.
|
|
3141
|
+
connectionTimeoutMillis: config.connectTimeoutMillis,
|
|
3142
|
+
idleTimeoutMillis: config.idleTimeoutMillis,
|
|
3124
3143
|
// keepAlive (construction knob): TCP keepalive for serverless/long-idle connections.
|
|
3125
3144
|
keepAlive: config.keepAlive,
|
|
3126
3145
|
...config.keepAlive ? { keepAliveInitialDelayMillis: config.keepAliveInitialDelayMillis } : {}
|
|
@@ -3138,12 +3157,15 @@ function mysqlPoolFactory(mysql2) {
|
|
|
3138
3157
|
...config.password !== void 0 ? { password: config.password } : {},
|
|
3139
3158
|
// SIZING (construction knob): connectionLimit = maxPool (the sole source of the cap).
|
|
3140
3159
|
connectionLimit: config.maxPool,
|
|
3160
|
+
connectTimeout: config.connectTimeoutMillis,
|
|
3161
|
+
idleTimeout: config.idleTimeoutMillis,
|
|
3141
3162
|
// keepAlive (construction knob).
|
|
3142
3163
|
enableKeepAlive: config.keepAlive,
|
|
3143
3164
|
...config.keepAlive ? { keepAliveInitialDelay: config.keepAliveInitialDelayMillis } : {},
|
|
3144
3165
|
...mysqlDeboxPoolOptions
|
|
3145
3166
|
});
|
|
3146
|
-
|
|
3167
|
+
const acquireTimeout = config.connectTimeoutMillis > 0 ? config.connectTimeoutMillis : void 0;
|
|
3168
|
+
return { pool: mysqlConnectionPool(pool, acquireTimeout), close: () => pool.end() };
|
|
3147
3169
|
};
|
|
3148
3170
|
}
|
|
3149
3171
|
|
|
@@ -3179,6 +3201,7 @@ function getPool(config) {
|
|
|
3179
3201
|
password: config.password,
|
|
3180
3202
|
max: config.max || 10,
|
|
3181
3203
|
connectionTimeoutMillis: (config.timeout || 30) * 1e3,
|
|
3204
|
+
idleTimeoutMillis: config.idleTimeoutMillis ?? 1e4,
|
|
3182
3205
|
query_timeout: (config.queryTimeout || 30) * 1e3,
|
|
3183
3206
|
keepAlive: config.keepAlive,
|
|
3184
3207
|
keepAliveInitialDelayMillis: config.keepAliveInitialDelayMillis
|
|
@@ -3209,33 +3232,44 @@ function convertPlaceholders(sql2) {
|
|
|
3209
3232
|
let index = 0;
|
|
3210
3233
|
return sql2.replace(/\?/g, () => `$${++index}`);
|
|
3211
3234
|
}
|
|
3235
|
+
var MAX_NAMED_STATEMENTS = 1e3;
|
|
3236
|
+
var MAX_UNNAMED_QUERIES = 1e3;
|
|
3212
3237
|
var preparedStmtCounter = 0;
|
|
3213
3238
|
var queryCache = /* @__PURE__ */ new Map();
|
|
3239
|
+
var unnamedQueries = /* @__PURE__ */ new Map();
|
|
3214
3240
|
function resolveQuery(sql2) {
|
|
3215
3241
|
let cached = queryCache.get(sql2);
|
|
3216
|
-
if (
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3242
|
+
if (cached) return cached;
|
|
3243
|
+
cached = unnamedQueries.get(sql2);
|
|
3244
|
+
if (cached) {
|
|
3245
|
+
if (preparedStmtCounter < MAX_NAMED_STATEMENTS) {
|
|
3246
|
+
unnamedQueries.delete(sql2);
|
|
3247
|
+
cached.name = `ldb_${++preparedStmtCounter}`;
|
|
3248
|
+
queryCache.set(sql2, cached);
|
|
3249
|
+
}
|
|
3250
|
+
return cached;
|
|
3251
|
+
}
|
|
3252
|
+
const convertedSql = convertPlaceholders(sql2);
|
|
3253
|
+
let isMulti = false;
|
|
3254
|
+
let end = convertedSql.length - 1;
|
|
3255
|
+
while (end >= 0 && (convertedSql[end] === " " || convertedSql[end] === "\n" || convertedSql[end] === " " || convertedSql[end] === "\r")) end--;
|
|
3256
|
+
if (end >= 0 && convertedSql[end] === ";") end--;
|
|
3257
|
+
for (let i = 0; i <= end; i++) {
|
|
3258
|
+
if (convertedSql[i] === ";") {
|
|
3259
|
+
isMulti = true;
|
|
3260
|
+
break;
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
cached = { convertedSql, isMulti };
|
|
3264
|
+
if (unnamedQueries.size >= MAX_UNNAMED_QUERIES) {
|
|
3265
|
+
unnamedQueries.delete(unnamedQueries.keys().next().value);
|
|
3234
3266
|
}
|
|
3267
|
+
unnamedQueries.set(sql2, cached);
|
|
3235
3268
|
return cached;
|
|
3236
3269
|
}
|
|
3237
3270
|
function clearQueryCache() {
|
|
3238
3271
|
queryCache.clear();
|
|
3272
|
+
unnamedQueries.clear();
|
|
3239
3273
|
preparedStmtCounter = 0;
|
|
3240
3274
|
}
|
|
3241
3275
|
var PostgresConnection = class {
|
|
@@ -3581,6 +3615,7 @@ function getPool2(config) {
|
|
|
3581
3615
|
waitForConnections: true,
|
|
3582
3616
|
connectionLimit: config.max || 10,
|
|
3583
3617
|
connectTimeout: (config.timeout || 30) * 1e3,
|
|
3618
|
+
idleTimeout: config.idleTimeoutMillis ?? 1e4,
|
|
3584
3619
|
// Read de-box (issue #9): make BIGINT arrive as an exact string (else mysql2 rounds it to a JS
|
|
3585
3620
|
// number past 2^53) and the DATE/TIMESTAMP/DATETIME family arrive as its native textual string
|
|
3586
3621
|
// (else a TZ-shifted JS Date) — the coercible forms `materializeCell` (int64/date) expects. Same
|
|
@@ -4277,6 +4312,8 @@ function resolveConnectionConfig(config = {}) {
|
|
|
4277
4312
|
keepAliveInitialDelayMillis: config.keepAliveInitialDelayMillis ?? 1e4,
|
|
4278
4313
|
minPool: config.minPool ?? 0,
|
|
4279
4314
|
maxPool: config.maxPool ?? 10,
|
|
4315
|
+
connectTimeoutMillis: config.connectTimeoutMillis ?? 3e4,
|
|
4316
|
+
idleTimeoutMillis: config.idleTimeoutMillis ?? 1e4,
|
|
4280
4317
|
...config.searchPath !== void 0 ? { searchPath: config.searchPath } : {},
|
|
4281
4318
|
...config.charset !== void 0 ? { charset: config.charset } : {}
|
|
4282
4319
|
};
|
|
@@ -4380,14 +4417,15 @@ var writerScopeStore = new import_node_async_hooks2.AsyncLocalStorage();
|
|
|
4380
4417
|
function inWriterScope() {
|
|
4381
4418
|
return writerScopeStore.getStore() === true;
|
|
4382
4419
|
}
|
|
4420
|
+
function latestStamp(cell) {
|
|
4421
|
+
let latest = null;
|
|
4422
|
+
for (let c = cell; c; c = c.prev) {
|
|
4423
|
+
if (c.at !== null && (latest === null || c.at > latest)) latest = c.at;
|
|
4424
|
+
}
|
|
4425
|
+
return latest;
|
|
4426
|
+
}
|
|
4383
4427
|
var WriterStickyClock = class {
|
|
4384
|
-
|
|
4385
|
-
* `null` until the first `mark()` — absence, NOT a value sentinel. The injectable clocks legitimately
|
|
4386
|
-
* return 0 (a monotonic clock right after process start; rust's `SystemClock` does), so encoding
|
|
4387
|
-
* "never marked" as the value 0 would mis-classify a `mark()` at clock t=0 as unmarked and leak the
|
|
4388
|
-
* read-your-writes read to the reader replica. Absence must be distinct from the value 0.
|
|
4389
|
-
*/
|
|
4390
|
-
lastWriteAt = null;
|
|
4428
|
+
chain = new import_node_async_hooks2.AsyncLocalStorage();
|
|
4391
4429
|
enabled;
|
|
4392
4430
|
stickyDurationMs;
|
|
4393
4431
|
/** Injectable clock (tests advance it); defaults to `Date.now`. */
|
|
@@ -4397,18 +4435,37 @@ var WriterStickyClock = class {
|
|
|
4397
4435
|
this.stickyDurationMs = opts.writerStickyDuration ?? 5e3;
|
|
4398
4436
|
this.now = opts.now ?? Date.now;
|
|
4399
4437
|
}
|
|
4400
|
-
/**
|
|
4438
|
+
/**
|
|
4439
|
+
* Start tracking a transaction in the current chain: enters a fresh cell that carries the chain's
|
|
4440
|
+
* latest stamp. Must run synchronously at the start of the transaction, before its first `await`.
|
|
4441
|
+
*/
|
|
4442
|
+
begin() {
|
|
4443
|
+
const current = this.chain.getStore();
|
|
4444
|
+
let open = current ?? null;
|
|
4445
|
+
while (open !== null && !open.open) open = open.prev;
|
|
4446
|
+
const cell = { at: latestStamp(current), open: true, prev: open };
|
|
4447
|
+
this.chain.enterWith(cell);
|
|
4448
|
+
return cell;
|
|
4449
|
+
}
|
|
4450
|
+
/** The transaction that began `cell` is over (committed or not). */
|
|
4451
|
+
end(cell) {
|
|
4452
|
+
cell.open = false;
|
|
4453
|
+
}
|
|
4454
|
+
/** Record that the current chain just committed. */
|
|
4401
4455
|
mark() {
|
|
4402
|
-
if (this.enabled)
|
|
4456
|
+
if (!this.enabled) return;
|
|
4457
|
+
let cell = this.chain.getStore();
|
|
4458
|
+
if (cell === void 0) {
|
|
4459
|
+
cell = { at: null, open: false, prev: null };
|
|
4460
|
+
this.chain.enterWith(cell);
|
|
4461
|
+
}
|
|
4462
|
+
cell.at = this.now();
|
|
4403
4463
|
}
|
|
4404
|
-
/** Is a read
|
|
4464
|
+
/** Is a read in the current chain sticky-to-writer (within `writerStickyDuration` of its last commit)? */
|
|
4405
4465
|
isSticky() {
|
|
4406
|
-
if (!this.enabled
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
/** Reset the clock (e.g. between tests / on `closeAllPools`). */
|
|
4410
|
-
reset() {
|
|
4411
|
-
this.lastWriteAt = null;
|
|
4466
|
+
if (!this.enabled) return false;
|
|
4467
|
+
const at = latestStamp(this.chain.getStore());
|
|
4468
|
+
return at !== null && this.now() - at < this.stickyDurationMs;
|
|
4412
4469
|
}
|
|
4413
4470
|
};
|
|
4414
4471
|
function resolvePool(intent, routing) {
|
|
@@ -4693,38 +4750,43 @@ async function withTransactionAsync(ctx, fn, opts = {}, dialect = "postgres", is
|
|
|
4693
4750
|
const resolved = resolveTxOptions(opts);
|
|
4694
4751
|
const begins = beginStatements(dialect, resolved.isolation);
|
|
4695
4752
|
const pool = ctx.connectionPoolFor({ write: true, ...connection !== void 0 ? { db: connection } : {} });
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
attempt
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
const
|
|
4706
|
-
await runAsync(txCtx, resolved.rollbackOnly ? "ROLLBACK" : "COMMIT", []);
|
|
4707
|
-
return { ok: true, value: r };
|
|
4708
|
-
} catch (error2) {
|
|
4753
|
+
const stickyCell = ctx.stickyClock.begin();
|
|
4754
|
+
try {
|
|
4755
|
+
let attempt = 0;
|
|
4756
|
+
for (; ; ) {
|
|
4757
|
+
attempt++;
|
|
4758
|
+
const conn = await pool.acquire();
|
|
4759
|
+
const txCtx = ctx.withConnection(conn, true);
|
|
4760
|
+
let poisoned = false;
|
|
4761
|
+
const attemptResult = await runWithPinnedAsyncConnection(conn, connection ?? null, async () => {
|
|
4762
|
+
for (const begin of begins) await runAsync(txCtx, begin, []);
|
|
4709
4763
|
try {
|
|
4710
|
-
await
|
|
4711
|
-
|
|
4712
|
-
|
|
4764
|
+
const r = await runInTransactionScope(() => fn(txCtx));
|
|
4765
|
+
await runAsync(txCtx, resolved.rollbackOnly ? "ROLLBACK" : "COMMIT", []);
|
|
4766
|
+
return { ok: true, value: r };
|
|
4767
|
+
} catch (error2) {
|
|
4768
|
+
try {
|
|
4769
|
+
await runAsync(txCtx, "ROLLBACK", []);
|
|
4770
|
+
} catch {
|
|
4771
|
+
poisoned = true;
|
|
4772
|
+
}
|
|
4773
|
+
return { ok: false, error: error2 };
|
|
4713
4774
|
}
|
|
4714
|
-
|
|
4775
|
+
});
|
|
4776
|
+
await pool.release(conn, poisoned || !attemptResult.ok);
|
|
4777
|
+
if (attemptResult.ok) {
|
|
4778
|
+
if (!resolved.rollbackOnly && resolved.useWriterAfterTransaction) ctx.stickyClock.mark();
|
|
4779
|
+
return attemptResult.value;
|
|
4715
4780
|
}
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
const { error } = attemptResult;
|
|
4723
|
-
if (resolved.retryOnError && attempt < resolved.retryLimit && isRetryableTxError(error, isConnectionError2)) {
|
|
4724
|
-
await sleep(resolved.retryDuration * Math.pow(2, attempt - 1));
|
|
4725
|
-
continue;
|
|
4781
|
+
const { error } = attemptResult;
|
|
4782
|
+
if (resolved.retryOnError && attempt < resolved.retryLimit && isRetryableTxError(error, isConnectionError2)) {
|
|
4783
|
+
await sleep(resolved.retryDuration * Math.pow(2, attempt - 1));
|
|
4784
|
+
continue;
|
|
4785
|
+
}
|
|
4786
|
+
throw error;
|
|
4726
4787
|
}
|
|
4727
|
-
|
|
4788
|
+
} finally {
|
|
4789
|
+
ctx.stickyClock.end(stickyCell);
|
|
4728
4790
|
}
|
|
4729
4791
|
}
|
|
4730
4792
|
function transaction(ctx, fn, options = {}, dialect = "postgres", isConnectionError2 = defaultIsConnectionError, connection) {
|
|
@@ -4857,11 +4919,13 @@ function compareCore(op, a, b) {
|
|
|
4857
4919
|
if (typeof a === "bigint" && typeof b === "bigint")
|
|
4858
4920
|
c = a < b ? -1 : a > b ? 1 : 0;
|
|
4859
4921
|
else if (typeof a === "number" && typeof b === "number")
|
|
4860
|
-
c = a < b ? -1 : a > b ? 1 : 0;
|
|
4922
|
+
c = a < b ? -1 : a > b ? 1 : a === b ? 0 : null;
|
|
4861
4923
|
else if (typeof a === "string" && typeof b === "string")
|
|
4862
4924
|
c = cmpCodePoints(a, b);
|
|
4863
4925
|
else
|
|
4864
4926
|
fail("TYPE_MISMATCH", `${op}: \u540C\u4E00\u578B\u306E int/float/string \u306E\u307F\uFF08got ${typeName(a)}\xD7${typeName(b)}\uFF09`);
|
|
4927
|
+
if (c === null)
|
|
4928
|
+
return false;
|
|
4865
4929
|
switch (op) {
|
|
4866
4930
|
case "lt":
|
|
4867
4931
|
return c < 0;
|
|
@@ -7203,6 +7267,8 @@ function toConnectionConfig(config) {
|
|
|
7203
7267
|
...config.user !== void 0 ? { user: config.user } : {},
|
|
7204
7268
|
...config.password !== void 0 ? { password: config.password } : {},
|
|
7205
7269
|
...config.max !== void 0 ? { maxPool: config.max } : {},
|
|
7270
|
+
...config.timeout !== void 0 ? { connectTimeoutMillis: config.timeout * 1e3 } : {},
|
|
7271
|
+
...config.idleTimeoutMillis !== void 0 ? { idleTimeoutMillis: config.idleTimeoutMillis } : {},
|
|
7206
7272
|
...config.keepAlive !== void 0 ? { keepAlive: config.keepAlive } : {},
|
|
7207
7273
|
...config.keepAliveInitialDelayMillis !== void 0 ? { keepAliveInitialDelayMillis: config.keepAliveInitialDelayMillis } : {}
|
|
7208
7274
|
};
|
|
@@ -7480,52 +7546,46 @@ async function closeAllPools2() {
|
|
|
7480
7546
|
|
|
7481
7547
|
// src/Middleware.ts
|
|
7482
7548
|
var import_async_hooks = require("async_hooks");
|
|
7483
|
-
var
|
|
7484
|
-
|
|
7485
|
-
static _storage;
|
|
7486
|
-
/** Get or create storage for this class */
|
|
7487
|
-
static getStorage() {
|
|
7488
|
-
if (!this.hasOwnProperty("_storage") || !this._storage) {
|
|
7489
|
-
this._storage = new import_async_hooks.AsyncLocalStorage();
|
|
7490
|
-
}
|
|
7491
|
-
return this._storage;
|
|
7492
|
-
}
|
|
7549
|
+
var requestScope = new import_async_hooks.AsyncLocalStorage();
|
|
7550
|
+
var Middleware = class _Middleware {
|
|
7493
7551
|
/**
|
|
7494
|
-
* Get current request's instance.
|
|
7495
|
-
*
|
|
7552
|
+
* Get the current request's instance of this middleware. Inside a {@link Middleware.run} scope the
|
|
7553
|
+
* instance is created on first access and shared by the rest of the scope; outside any scope each
|
|
7554
|
+
* call gets a new instance that lives only for that call.
|
|
7496
7555
|
*/
|
|
7497
7556
|
static getCurrentContext() {
|
|
7498
|
-
const
|
|
7499
|
-
let instance =
|
|
7557
|
+
const scope = requestScope.getStore();
|
|
7558
|
+
let instance = scope?.get(this);
|
|
7500
7559
|
if (!instance) {
|
|
7501
7560
|
instance = new this();
|
|
7502
|
-
|
|
7561
|
+
instance.init?.();
|
|
7562
|
+
scope?.set(this, instance);
|
|
7503
7563
|
}
|
|
7504
7564
|
return instance;
|
|
7505
7565
|
}
|
|
7506
7566
|
/**
|
|
7507
|
-
* Run
|
|
7508
|
-
*
|
|
7567
|
+
* Run `fn` as one request: the middleware instances `fn` uses (directly or through DBModel
|
|
7568
|
+
* operations) are created inside the scope and are not shared with any other request. Call it at
|
|
7569
|
+
* the request entry. `Middleware.run` gives every middleware a fresh instance; `X.run` gives `X` a
|
|
7570
|
+
* fresh instance and keeps the enclosing scope's instances of the others.
|
|
7509
7571
|
*/
|
|
7510
7572
|
static run(fn) {
|
|
7511
|
-
const
|
|
7512
|
-
const
|
|
7513
|
-
|
|
7573
|
+
const enclosing = requestScope.getStore();
|
|
7574
|
+
const scope = new Map(this === _Middleware || enclosing === void 0 ? [] : enclosing);
|
|
7575
|
+
scope.delete(this);
|
|
7576
|
+
return requestScope.run(scope, fn);
|
|
7514
7577
|
}
|
|
7515
7578
|
/**
|
|
7516
|
-
* Check if
|
|
7579
|
+
* Check if the current request scope holds an instance of this middleware
|
|
7517
7580
|
*/
|
|
7518
7581
|
static hasContext() {
|
|
7519
|
-
return
|
|
7582
|
+
return requestScope.getStore()?.has(this) ?? false;
|
|
7520
7583
|
}
|
|
7521
7584
|
/**
|
|
7522
|
-
*
|
|
7585
|
+
* Drop this middleware's instance from the current request scope (for testing)
|
|
7523
7586
|
*/
|
|
7524
7587
|
static clearContext() {
|
|
7525
|
-
|
|
7526
|
-
if (storage.getStore()) {
|
|
7527
|
-
storage.enterWith(void 0);
|
|
7528
|
-
}
|
|
7588
|
+
requestScope.getStore()?.delete(this);
|
|
7529
7589
|
}
|
|
7530
7590
|
};
|
|
7531
7591
|
function createMiddleware(config) {
|
|
@@ -7586,6 +7646,7 @@ function inferPgArrayType2(values, sqlCast) {
|
|
|
7586
7646
|
var LazyRelationContext = class {
|
|
7587
7647
|
sourceClass;
|
|
7588
7648
|
records;
|
|
7649
|
+
/** Batch loads by cache key — held from the start, so concurrent first accesses share one load. */
|
|
7589
7650
|
batchCache = /* @__PURE__ */ new Map();
|
|
7590
7651
|
constructor(sourceClass, records) {
|
|
7591
7652
|
this.sourceClass = sourceClass;
|
|
@@ -7599,10 +7660,16 @@ var LazyRelationContext = class {
|
|
|
7599
7660
|
*/
|
|
7600
7661
|
async getRelation(record, relationType, config) {
|
|
7601
7662
|
const cacheKey = this.getCacheKey(relationType, config);
|
|
7602
|
-
|
|
7603
|
-
|
|
7663
|
+
let pending = this.batchCache.get(cacheKey);
|
|
7664
|
+
if (!pending) {
|
|
7665
|
+
const load = this.batchLoadRelation(relationType, config);
|
|
7666
|
+
pending = load;
|
|
7667
|
+
this.batchCache.set(cacheKey, load);
|
|
7668
|
+
load.catch(() => {
|
|
7669
|
+
if (this.batchCache.get(cacheKey) === load) this.batchCache.delete(cacheKey);
|
|
7670
|
+
});
|
|
7604
7671
|
}
|
|
7605
|
-
const relationMap =
|
|
7672
|
+
const relationMap = await pending;
|
|
7606
7673
|
const isCompositeKey = config.targetKeys && config.targetKeys.length > 0 || config.sourceKeys && config.sourceKeys.length > 0;
|
|
7607
7674
|
let mapKey;
|
|
7608
7675
|
if (isCompositeKey) {
|
|
@@ -7669,9 +7736,7 @@ var LazyRelationContext = class {
|
|
|
7669
7736
|
}
|
|
7670
7737
|
}
|
|
7671
7738
|
if (sourceKeyValues.length === 0 && compositeKeyValues.length === 0) {
|
|
7672
|
-
|
|
7673
|
-
this.batchCache.set(cacheKey2, /* @__PURE__ */ new Map());
|
|
7674
|
-
return;
|
|
7739
|
+
return /* @__PURE__ */ new Map();
|
|
7675
7740
|
}
|
|
7676
7741
|
const driverType = TargetClass.getDriverType();
|
|
7677
7742
|
let results;
|
|
@@ -7682,9 +7747,7 @@ var LazyRelationContext = class {
|
|
|
7682
7747
|
const sourceKeys = config.sourceKeys || [config.sourceKey || "id"];
|
|
7683
7748
|
const uniqueTuples = this.getUniqueCompositeKeyTuples(compositeKeyValues, sourceKeys);
|
|
7684
7749
|
if (uniqueTuples.length === 0) {
|
|
7685
|
-
|
|
7686
|
-
this.batchCache.set(cacheKey2, /* @__PURE__ */ new Map());
|
|
7687
|
-
return;
|
|
7750
|
+
return /* @__PURE__ */ new Map();
|
|
7688
7751
|
}
|
|
7689
7752
|
if (needsLimitedLoading) {
|
|
7690
7753
|
if (driverType === "postgres") {
|
|
@@ -7733,8 +7796,7 @@ var LazyRelationContext = class {
|
|
|
7733
7796
|
relationMap.set(keyValue, result);
|
|
7734
7797
|
}
|
|
7735
7798
|
}
|
|
7736
|
-
|
|
7737
|
-
this.batchCache.set(cacheKey, relationMap);
|
|
7799
|
+
return relationMap;
|
|
7738
7800
|
}
|
|
7739
7801
|
/**
|
|
7740
7802
|
* PostgreSQL optimized: Single key batch load using = ANY($1::type[])
|
|
@@ -8153,8 +8215,8 @@ var DBModel = class _DBModel {
|
|
|
8153
8215
|
useWriterAfterTransaction: true,
|
|
8154
8216
|
writerStickyDuration: 5e3
|
|
8155
8217
|
};
|
|
8156
|
-
/**
|
|
8157
|
-
static
|
|
8218
|
+
/** Writer-sticky clock of the v1 execution path (the SCP runtime carries its own in its routing). */
|
|
8219
|
+
static _writerSticky = new WriterStickyClock();
|
|
8158
8220
|
/**
|
|
8159
8221
|
* The Phase A-E SCP runtime context (built lazily from `_dbConfig` for the live pg/mysql dialects).
|
|
8160
8222
|
* `null` when not yet built; `false` when the config is sqlite / uninitialized (⇒ v1 in-proc path,
|
|
@@ -8192,6 +8254,8 @@ var DBModel = class _DBModel {
|
|
|
8192
8254
|
...config.user !== void 0 ? { user: config.user } : {},
|
|
8193
8255
|
...config.password !== void 0 ? { password: config.password } : {},
|
|
8194
8256
|
...config.max !== void 0 ? { max: config.max } : {},
|
|
8257
|
+
...config.timeout !== void 0 ? { timeout: config.timeout } : {},
|
|
8258
|
+
...config.idleTimeoutMillis !== void 0 ? { idleTimeoutMillis: config.idleTimeoutMillis } : {},
|
|
8195
8259
|
...config.keepAlive !== void 0 ? { keepAlive: config.keepAlive } : {},
|
|
8196
8260
|
...config.keepAliveInitialDelayMillis !== void 0 ? { keepAliveInitialDelayMillis: config.keepAliveInitialDelayMillis } : {},
|
|
8197
8261
|
driver
|
|
@@ -8306,6 +8370,10 @@ var DBModel = class _DBModel {
|
|
|
8306
8370
|
...this._configOptions,
|
|
8307
8371
|
...options
|
|
8308
8372
|
};
|
|
8373
|
+
this._writerSticky = new WriterStickyClock({
|
|
8374
|
+
useWriterAfterTransaction: this._configOptions.useWriterAfterTransaction,
|
|
8375
|
+
writerStickyDuration: this._configOptions.writerStickyDuration
|
|
8376
|
+
});
|
|
8309
8377
|
this._resetScpRuntime();
|
|
8310
8378
|
}
|
|
8311
8379
|
/**
|
|
@@ -8378,12 +8446,9 @@ var DBModel = class _DBModel {
|
|
|
8378
8446
|
}
|
|
8379
8447
|
}
|
|
8380
8448
|
/**
|
|
8381
|
-
* Get a DBHandler instance for this model
|
|
8382
|
-
*
|
|
8383
|
-
*
|
|
8384
|
-
* 2. Writer context (if in withWriter())
|
|
8385
|
-
* 3. Writer sticky (if within writerStickyDuration after transaction)
|
|
8386
|
-
* 4. Default reader connection
|
|
8449
|
+
* Get a DBHandler instance for this model: the transaction's handler inside a transaction, else the
|
|
8450
|
+
* default handler (whose reads `execute()` sends to the writer inside `withWriter()` or while
|
|
8451
|
+
* writer-sticky).
|
|
8387
8452
|
*/
|
|
8388
8453
|
static getHandler() {
|
|
8389
8454
|
const txContext = this._getTransactionContext().getStore();
|
|
@@ -8393,24 +8458,14 @@ var DBModel = class _DBModel {
|
|
|
8393
8458
|
}
|
|
8394
8459
|
return txContext.handler;
|
|
8395
8460
|
}
|
|
8396
|
-
|
|
8397
|
-
if (this._getWriterContext().getStore()) {
|
|
8398
|
-
return handler;
|
|
8399
|
-
}
|
|
8400
|
-
if (this._shouldUseWriterSticky()) {
|
|
8401
|
-
return handler;
|
|
8402
|
-
}
|
|
8403
|
-
return handler;
|
|
8461
|
+
return getDBHandler();
|
|
8404
8462
|
}
|
|
8405
8463
|
/**
|
|
8406
|
-
* Check if
|
|
8464
|
+
* Check if this async chain should read from the writer because it committed a transaction within
|
|
8465
|
+
* `writerStickyDuration` (read-your-writes for the request that wrote).
|
|
8407
8466
|
*/
|
|
8408
8467
|
static _shouldUseWriterSticky() {
|
|
8409
|
-
|
|
8410
|
-
return false;
|
|
8411
|
-
}
|
|
8412
|
-
const stickyDuration = this._configOptions.writerStickyDuration ?? 5e3;
|
|
8413
|
-
return Date.now() - this._lastTransactionTime < stickyDuration;
|
|
8468
|
+
return this._writerSticky.isSticky();
|
|
8414
8469
|
}
|
|
8415
8470
|
/**
|
|
8416
8471
|
* Get transaction context storage.
|
|
@@ -8465,8 +8520,11 @@ var DBModel = class _DBModel {
|
|
|
8465
8520
|
*
|
|
8466
8521
|
* DBModel.use(LoggerMiddleware);
|
|
8467
8522
|
*
|
|
8468
|
-
* //
|
|
8469
|
-
*
|
|
8523
|
+
* // One request
|
|
8524
|
+
* await Middleware.run(async () => {
|
|
8525
|
+
* await handleRequest();
|
|
8526
|
+
* console.log(LoggerMiddleware.getCurrentContext().getLogs());
|
|
8527
|
+
* });
|
|
8470
8528
|
* ```
|
|
8471
8529
|
*/
|
|
8472
8530
|
static use(MiddlewareClass) {
|
|
@@ -8515,7 +8573,7 @@ var DBModel = class _DBModel {
|
|
|
8515
8573
|
* // Register
|
|
8516
8574
|
* DBModel.use(TenantMiddleware);
|
|
8517
8575
|
*
|
|
8518
|
-
* // Set tenant for current request (type-safe)
|
|
8576
|
+
* // Set tenant for the current request (inside Middleware.run at the request entry, type-safe)
|
|
8519
8577
|
* TenantMiddleware.getCurrentContext().tenantId = 123;
|
|
8520
8578
|
* ```
|
|
8521
8579
|
*/
|
|
@@ -10316,8 +10374,7 @@ var DBModel = class _DBModel {
|
|
|
10316
10374
|
}
|
|
10317
10375
|
const rt = this._getScpRuntime();
|
|
10318
10376
|
if (rt !== null && this._scpUsable()) {
|
|
10319
|
-
|
|
10320
|
-
const result = await transaction(
|
|
10377
|
+
return transaction(
|
|
10321
10378
|
rt.ctx,
|
|
10322
10379
|
() => {
|
|
10323
10380
|
const sentinel = {
|
|
@@ -10329,8 +10386,6 @@ var DBModel = class _DBModel {
|
|
|
10329
10386
|
options,
|
|
10330
10387
|
rt.dialect
|
|
10331
10388
|
);
|
|
10332
|
-
if (useWriterAfterTx && !(options.rollbackOnly ?? false)) this._lastTransactionTime = Date.now();
|
|
10333
|
-
return result;
|
|
10334
10389
|
}
|
|
10335
10390
|
const handler = this.getHandler();
|
|
10336
10391
|
const retryOnError = options.retryOnError ?? true;
|
|
@@ -10338,38 +10393,41 @@ var DBModel = class _DBModel {
|
|
|
10338
10393
|
const retryDuration = options.retryDuration ?? 200;
|
|
10339
10394
|
const rollbackOnly = options.rollbackOnly ?? false;
|
|
10340
10395
|
const useWriterAfterTransaction = options.useWriterAfterTransaction ?? this._configOptions.useWriterAfterTransaction ?? true;
|
|
10341
|
-
|
|
10342
|
-
|
|
10343
|
-
attempt
|
|
10344
|
-
|
|
10345
|
-
|
|
10346
|
-
|
|
10347
|
-
|
|
10348
|
-
const context = { connection };
|
|
10349
|
-
const result = await txContext.run(context, func);
|
|
10350
|
-
if (rollbackOnly) {
|
|
10351
|
-
await connection.query("ROLLBACK");
|
|
10352
|
-
} else {
|
|
10353
|
-
await connection.query("COMMIT");
|
|
10354
|
-
if (useWriterAfterTransaction) {
|
|
10355
|
-
this._lastTransactionTime = Date.now();
|
|
10356
|
-
}
|
|
10357
|
-
}
|
|
10358
|
-
return result;
|
|
10359
|
-
} catch (error) {
|
|
10360
|
-
destroyConnection = true;
|
|
10396
|
+
const stickyCell = this._writerSticky.begin();
|
|
10397
|
+
try {
|
|
10398
|
+
let attempt = 0;
|
|
10399
|
+
while (true) {
|
|
10400
|
+
attempt++;
|
|
10401
|
+
const connection = await handler.getConnection();
|
|
10402
|
+
let destroyConnection = false;
|
|
10361
10403
|
try {
|
|
10362
|
-
await connection.query("
|
|
10363
|
-
|
|
10364
|
-
|
|
10365
|
-
|
|
10366
|
-
|
|
10367
|
-
|
|
10404
|
+
await connection.query("BEGIN");
|
|
10405
|
+
const context = { connection };
|
|
10406
|
+
const result = await txContext.run(context, func);
|
|
10407
|
+
if (rollbackOnly) {
|
|
10408
|
+
await connection.query("ROLLBACK");
|
|
10409
|
+
} else {
|
|
10410
|
+
await connection.query("COMMIT");
|
|
10411
|
+
if (useWriterAfterTransaction) this._writerSticky.mark();
|
|
10412
|
+
}
|
|
10413
|
+
return result;
|
|
10414
|
+
} catch (error) {
|
|
10415
|
+
destroyConnection = true;
|
|
10416
|
+
try {
|
|
10417
|
+
await connection.query("ROLLBACK");
|
|
10418
|
+
} catch {
|
|
10419
|
+
}
|
|
10420
|
+
if (retryOnError && attempt < retryLimit && this.isRetryableError(error)) {
|
|
10421
|
+
await this.sleep(retryDuration * Math.pow(2, attempt - 1));
|
|
10422
|
+
continue;
|
|
10423
|
+
}
|
|
10424
|
+
throw error;
|
|
10425
|
+
} finally {
|
|
10426
|
+
connection.release(destroyConnection || void 0);
|
|
10368
10427
|
}
|
|
10369
|
-
throw error;
|
|
10370
|
-
} finally {
|
|
10371
|
-
connection.release(destroyConnection || void 0);
|
|
10372
10428
|
}
|
|
10429
|
+
} finally {
|
|
10430
|
+
this._writerSticky.end(stickyCell);
|
|
10373
10431
|
}
|
|
10374
10432
|
}
|
|
10375
10433
|
/**
|
|
@@ -10510,7 +10568,10 @@ var DBModel = class _DBModel {
|
|
|
10510
10568
|
writerStickyDuration: options?.writerStickyDuration ?? 5e3,
|
|
10511
10569
|
...options
|
|
10512
10570
|
};
|
|
10513
|
-
static
|
|
10571
|
+
static _writerSticky = new WriterStickyClock({
|
|
10572
|
+
useWriterAfterTransaction: options?.useWriterAfterTransaction ?? true,
|
|
10573
|
+
writerStickyDuration: options?.writerStickyDuration ?? 5e3
|
|
10574
|
+
});
|
|
10514
10575
|
// Phase F-2 (#105): this base class gets its OWN SCP runtime slot (built lazily from ITS config),
|
|
10515
10576
|
// isolated from DBModel's default-config runtime and from other base classes — otherwise a
|
|
10516
10577
|
// sqlite/mysql base would inherit DBModel's shared pg runtime and route its SQL to the wrong DB.
|