litedbmodel 2.2.10 → 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/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 +211 -152
- 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
|
-
break;
|
|
3226
|
-
}
|
|
3227
|
-
}
|
|
3228
|
-
cached = {
|
|
3229
|
-
convertedSql,
|
|
3230
|
-
isMulti,
|
|
3231
|
-
name: `ldb_${++preparedStmtCounter}`
|
|
3232
|
-
};
|
|
3233
|
-
queryCache.set(sql2, cached);
|
|
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;
|
|
3234
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);
|
|
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) {
|
|
@@ -8155,8 +8215,8 @@ var DBModel = class _DBModel {
|
|
|
8155
8215
|
useWriterAfterTransaction: true,
|
|
8156
8216
|
writerStickyDuration: 5e3
|
|
8157
8217
|
};
|
|
8158
|
-
/**
|
|
8159
|
-
static
|
|
8218
|
+
/** Writer-sticky clock of the v1 execution path (the SCP runtime carries its own in its routing). */
|
|
8219
|
+
static _writerSticky = new WriterStickyClock();
|
|
8160
8220
|
/**
|
|
8161
8221
|
* The Phase A-E SCP runtime context (built lazily from `_dbConfig` for the live pg/mysql dialects).
|
|
8162
8222
|
* `null` when not yet built; `false` when the config is sqlite / uninitialized (⇒ v1 in-proc path,
|
|
@@ -8194,6 +8254,8 @@ var DBModel = class _DBModel {
|
|
|
8194
8254
|
...config.user !== void 0 ? { user: config.user } : {},
|
|
8195
8255
|
...config.password !== void 0 ? { password: config.password } : {},
|
|
8196
8256
|
...config.max !== void 0 ? { max: config.max } : {},
|
|
8257
|
+
...config.timeout !== void 0 ? { timeout: config.timeout } : {},
|
|
8258
|
+
...config.idleTimeoutMillis !== void 0 ? { idleTimeoutMillis: config.idleTimeoutMillis } : {},
|
|
8197
8259
|
...config.keepAlive !== void 0 ? { keepAlive: config.keepAlive } : {},
|
|
8198
8260
|
...config.keepAliveInitialDelayMillis !== void 0 ? { keepAliveInitialDelayMillis: config.keepAliveInitialDelayMillis } : {},
|
|
8199
8261
|
driver
|
|
@@ -8308,6 +8370,10 @@ var DBModel = class _DBModel {
|
|
|
8308
8370
|
...this._configOptions,
|
|
8309
8371
|
...options
|
|
8310
8372
|
};
|
|
8373
|
+
this._writerSticky = new WriterStickyClock({
|
|
8374
|
+
useWriterAfterTransaction: this._configOptions.useWriterAfterTransaction,
|
|
8375
|
+
writerStickyDuration: this._configOptions.writerStickyDuration
|
|
8376
|
+
});
|
|
8311
8377
|
this._resetScpRuntime();
|
|
8312
8378
|
}
|
|
8313
8379
|
/**
|
|
@@ -8380,12 +8446,9 @@ var DBModel = class _DBModel {
|
|
|
8380
8446
|
}
|
|
8381
8447
|
}
|
|
8382
8448
|
/**
|
|
8383
|
-
* Get a DBHandler instance for this model
|
|
8384
|
-
*
|
|
8385
|
-
*
|
|
8386
|
-
* 2. Writer context (if in withWriter())
|
|
8387
|
-
* 3. Writer sticky (if within writerStickyDuration after transaction)
|
|
8388
|
-
* 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).
|
|
8389
8452
|
*/
|
|
8390
8453
|
static getHandler() {
|
|
8391
8454
|
const txContext = this._getTransactionContext().getStore();
|
|
@@ -8395,24 +8458,14 @@ var DBModel = class _DBModel {
|
|
|
8395
8458
|
}
|
|
8396
8459
|
return txContext.handler;
|
|
8397
8460
|
}
|
|
8398
|
-
|
|
8399
|
-
if (this._getWriterContext().getStore()) {
|
|
8400
|
-
return handler;
|
|
8401
|
-
}
|
|
8402
|
-
if (this._shouldUseWriterSticky()) {
|
|
8403
|
-
return handler;
|
|
8404
|
-
}
|
|
8405
|
-
return handler;
|
|
8461
|
+
return getDBHandler();
|
|
8406
8462
|
}
|
|
8407
8463
|
/**
|
|
8408
|
-
* 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).
|
|
8409
8466
|
*/
|
|
8410
8467
|
static _shouldUseWriterSticky() {
|
|
8411
|
-
|
|
8412
|
-
return false;
|
|
8413
|
-
}
|
|
8414
|
-
const stickyDuration = this._configOptions.writerStickyDuration ?? 5e3;
|
|
8415
|
-
return Date.now() - this._lastTransactionTime < stickyDuration;
|
|
8468
|
+
return this._writerSticky.isSticky();
|
|
8416
8469
|
}
|
|
8417
8470
|
/**
|
|
8418
8471
|
* Get transaction context storage.
|
|
@@ -8467,8 +8520,11 @@ var DBModel = class _DBModel {
|
|
|
8467
8520
|
*
|
|
8468
8521
|
* DBModel.use(LoggerMiddleware);
|
|
8469
8522
|
*
|
|
8470
|
-
* //
|
|
8471
|
-
*
|
|
8523
|
+
* // One request
|
|
8524
|
+
* await Middleware.run(async () => {
|
|
8525
|
+
* await handleRequest();
|
|
8526
|
+
* console.log(LoggerMiddleware.getCurrentContext().getLogs());
|
|
8527
|
+
* });
|
|
8472
8528
|
* ```
|
|
8473
8529
|
*/
|
|
8474
8530
|
static use(MiddlewareClass) {
|
|
@@ -8517,7 +8573,7 @@ var DBModel = class _DBModel {
|
|
|
8517
8573
|
* // Register
|
|
8518
8574
|
* DBModel.use(TenantMiddleware);
|
|
8519
8575
|
*
|
|
8520
|
-
* // Set tenant for current request (type-safe)
|
|
8576
|
+
* // Set tenant for the current request (inside Middleware.run at the request entry, type-safe)
|
|
8521
8577
|
* TenantMiddleware.getCurrentContext().tenantId = 123;
|
|
8522
8578
|
* ```
|
|
8523
8579
|
*/
|
|
@@ -10318,8 +10374,7 @@ var DBModel = class _DBModel {
|
|
|
10318
10374
|
}
|
|
10319
10375
|
const rt = this._getScpRuntime();
|
|
10320
10376
|
if (rt !== null && this._scpUsable()) {
|
|
10321
|
-
|
|
10322
|
-
const result = await transaction(
|
|
10377
|
+
return transaction(
|
|
10323
10378
|
rt.ctx,
|
|
10324
10379
|
() => {
|
|
10325
10380
|
const sentinel = {
|
|
@@ -10331,8 +10386,6 @@ var DBModel = class _DBModel {
|
|
|
10331
10386
|
options,
|
|
10332
10387
|
rt.dialect
|
|
10333
10388
|
);
|
|
10334
|
-
if (useWriterAfterTx && !(options.rollbackOnly ?? false)) this._lastTransactionTime = Date.now();
|
|
10335
|
-
return result;
|
|
10336
10389
|
}
|
|
10337
10390
|
const handler = this.getHandler();
|
|
10338
10391
|
const retryOnError = options.retryOnError ?? true;
|
|
@@ -10340,38 +10393,41 @@ var DBModel = class _DBModel {
|
|
|
10340
10393
|
const retryDuration = options.retryDuration ?? 200;
|
|
10341
10394
|
const rollbackOnly = options.rollbackOnly ?? false;
|
|
10342
10395
|
const useWriterAfterTransaction = options.useWriterAfterTransaction ?? this._configOptions.useWriterAfterTransaction ?? true;
|
|
10343
|
-
|
|
10344
|
-
|
|
10345
|
-
attempt
|
|
10346
|
-
|
|
10347
|
-
|
|
10348
|
-
|
|
10349
|
-
|
|
10350
|
-
const context = { connection };
|
|
10351
|
-
const result = await txContext.run(context, func);
|
|
10352
|
-
if (rollbackOnly) {
|
|
10353
|
-
await connection.query("ROLLBACK");
|
|
10354
|
-
} else {
|
|
10355
|
-
await connection.query("COMMIT");
|
|
10356
|
-
if (useWriterAfterTransaction) {
|
|
10357
|
-
this._lastTransactionTime = Date.now();
|
|
10358
|
-
}
|
|
10359
|
-
}
|
|
10360
|
-
return result;
|
|
10361
|
-
} catch (error) {
|
|
10362
|
-
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;
|
|
10363
10403
|
try {
|
|
10364
|
-
await connection.query("
|
|
10365
|
-
|
|
10366
|
-
|
|
10367
|
-
|
|
10368
|
-
|
|
10369
|
-
|
|
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);
|
|
10370
10427
|
}
|
|
10371
|
-
throw error;
|
|
10372
|
-
} finally {
|
|
10373
|
-
connection.release(destroyConnection || void 0);
|
|
10374
10428
|
}
|
|
10429
|
+
} finally {
|
|
10430
|
+
this._writerSticky.end(stickyCell);
|
|
10375
10431
|
}
|
|
10376
10432
|
}
|
|
10377
10433
|
/**
|
|
@@ -10512,7 +10568,10 @@ var DBModel = class _DBModel {
|
|
|
10512
10568
|
writerStickyDuration: options?.writerStickyDuration ?? 5e3,
|
|
10513
10569
|
...options
|
|
10514
10570
|
};
|
|
10515
|
-
static
|
|
10571
|
+
static _writerSticky = new WriterStickyClock({
|
|
10572
|
+
useWriterAfterTransaction: options?.useWriterAfterTransaction ?? true,
|
|
10573
|
+
writerStickyDuration: options?.writerStickyDuration ?? 5e3
|
|
10574
|
+
});
|
|
10516
10575
|
// Phase F-2 (#105): this base class gets its OWN SCP runtime slot (built lazily from ITS config),
|
|
10517
10576
|
// isolated from DBModel's default-config runtime and from other base classes — otherwise a
|
|
10518
10577
|
// sqlite/mysql base would inherit DBModel's shared pg runtime and route its SQL to the wrong DB.
|