@nmakarov/cli-toolkit 0.79.0 → 0.81.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli-runner.cjs +194 -57
- package/dist/cli-runner.cjs.map +1 -1
- package/dist/cli-runner.js +194 -57
- package/dist/cli-runner.js.map +1 -1
- package/dist/db.cjs +105 -25
- package/dist/db.cjs.map +1 -1
- package/dist/db.js +105 -25
- package/dist/db.js.map +1 -1
- package/dist/index.cjs +179 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +175 -56
- package/dist/index.js.map +1 -1
- package/dist/init.cjs +89 -32
- package/dist/init.cjs.map +1 -1
- package/dist/init.js +89 -32
- package/dist/init.js.map +1 -1
- package/dist/logger.cjs +6 -5
- package/dist/logger.cjs.map +1 -1
- package/dist/logger.js +6 -5
- package/dist/logger.js.map +1 -1
- package/dist/screen.cjs +59 -26
- package/dist/screen.cjs.map +1 -1
- package/dist/screen.js +55 -26
- package/dist/screen.js.map +1 -1
- package/package.json +1 -1
package/dist/cli-runner.js
CHANGED
|
@@ -1002,6 +1002,30 @@ var init_ui_elements = __esm({
|
|
|
1002
1002
|
}
|
|
1003
1003
|
});
|
|
1004
1004
|
|
|
1005
|
+
// src/screen/follow-scroll.js
|
|
1006
|
+
function clampScroll(scrollTop, maxScroll) {
|
|
1007
|
+
const max = Math.max(0, Number(maxScroll) || 0);
|
|
1008
|
+
const top = Number(scrollTop) || 0;
|
|
1009
|
+
return Math.min(Math.max(0, top), max);
|
|
1010
|
+
}
|
|
1011
|
+
function isScrolledToBottom(scrollTop, maxScroll) {
|
|
1012
|
+
return clampScroll(scrollTop, maxScroll) >= Math.max(0, Number(maxScroll) || 0);
|
|
1013
|
+
}
|
|
1014
|
+
function nextScrollAfterUserMove(scrollTop, maxScroll, delta) {
|
|
1015
|
+
const next = clampScroll((Number(scrollTop) || 0) + (Number(delta) || 0), maxScroll);
|
|
1016
|
+
return { scrollTop: next, following: isScrolledToBottom(next, maxScroll) };
|
|
1017
|
+
}
|
|
1018
|
+
function nextScrollAfterContentChange({ following, scrollTop, maxScroll }) {
|
|
1019
|
+
const max = Math.max(0, Number(maxScroll) || 0);
|
|
1020
|
+
if (following) return { scrollTop: max, following: true };
|
|
1021
|
+
const next = clampScroll(scrollTop, max);
|
|
1022
|
+
return { scrollTop: next, following: isScrolledToBottom(next, max) };
|
|
1023
|
+
}
|
|
1024
|
+
var init_follow_scroll = __esm({
|
|
1025
|
+
"src/screen/follow-scroll.js"() {
|
|
1026
|
+
}
|
|
1027
|
+
});
|
|
1028
|
+
|
|
1005
1029
|
// src/screen/scrollable-text.js
|
|
1006
1030
|
import { useState as useState3, useEffect as useEffect2, useMemo, useRef as useRef2, createElement as createElement2 } from "react";
|
|
1007
1031
|
import { Box as Box4, Text as Text5 } from "ink";
|
|
@@ -1036,9 +1060,11 @@ function ScrollableText({
|
|
|
1036
1060
|
showScrollbar = true,
|
|
1037
1061
|
showStatus = true,
|
|
1038
1062
|
bindKeys = true,
|
|
1039
|
-
header = null
|
|
1063
|
+
header = null,
|
|
1064
|
+
followBottom = false
|
|
1040
1065
|
}) {
|
|
1041
1066
|
const [scrollTop, setScrollTop] = useState3(0);
|
|
1067
|
+
const [following, setFollowing] = useState3(() => !!followBottom);
|
|
1042
1068
|
const [, bump] = useState3(0);
|
|
1043
1069
|
const termRows = process.stdout.rows || 24;
|
|
1044
1070
|
const viewportRows = Math.max(
|
|
@@ -1057,39 +1083,40 @@ function ScrollableText({
|
|
|
1057
1083
|
const clamped = Math.min(Math.max(0, scrollTop), maxScroll);
|
|
1058
1084
|
const visible = allLines.slice(clamped, clamped + viewportRows);
|
|
1059
1085
|
const bar = needsBar ? scrollbarGlyphs(viewportRows, allLines.length, clamped) : null;
|
|
1060
|
-
useEffect2(() => {
|
|
1061
|
-
setScrollTop((s) => Math.min(s, maxScroll));
|
|
1062
|
-
}, [maxScroll]);
|
|
1063
1086
|
const maxScrollRef = useRef2(maxScroll);
|
|
1064
1087
|
const pageSizeRef = useRef2(viewportRows);
|
|
1088
|
+
const scrollTopRef = useRef2(scrollTop);
|
|
1089
|
+
const followingRef = useRef2(following);
|
|
1065
1090
|
maxScrollRef.current = maxScroll;
|
|
1066
1091
|
pageSizeRef.current = viewportRows;
|
|
1092
|
+
scrollTopRef.current = scrollTop;
|
|
1093
|
+
followingRef.current = following;
|
|
1094
|
+
useEffect2(() => {
|
|
1095
|
+
const next = nextScrollAfterContentChange({
|
|
1096
|
+
following: followBottom && followingRef.current,
|
|
1097
|
+
scrollTop: scrollTopRef.current,
|
|
1098
|
+
maxScroll
|
|
1099
|
+
});
|
|
1100
|
+
if (next.scrollTop !== scrollTopRef.current) setScrollTop(next.scrollTop);
|
|
1101
|
+
if (followBottom && next.following !== followingRef.current) setFollowing(next.following);
|
|
1102
|
+
}, [maxScroll, followBottom]);
|
|
1103
|
+
const applyUserScroll = (delta) => {
|
|
1104
|
+
const next = nextScrollAfterUserMove(scrollTopRef.current, maxScrollRef.current, delta);
|
|
1105
|
+
setScrollTop(next.scrollTop);
|
|
1106
|
+
if (followBottom) setFollowing(next.following);
|
|
1107
|
+
bump((n) => n + 1);
|
|
1108
|
+
ctx?.update?.();
|
|
1109
|
+
};
|
|
1067
1110
|
useEffect2(() => {
|
|
1068
1111
|
if (!ctx || !bindKeys) return void 0;
|
|
1069
1112
|
ctx.setKeyBinding(SCROLL_KEYS);
|
|
1070
|
-
ctx.setAction("scrollUp", () =>
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
});
|
|
1075
|
-
ctx.setAction("scrollDown", () => {
|
|
1076
|
-
setScrollTop((s) => Math.min(maxScrollRef.current, s + 1));
|
|
1077
|
-
bump((n) => n + 1);
|
|
1078
|
-
ctx.update?.();
|
|
1079
|
-
});
|
|
1080
|
-
ctx.setAction("pageUp", () => {
|
|
1081
|
-
setScrollTop((s) => Math.max(0, s - pageSizeRef.current));
|
|
1082
|
-
bump((n) => n + 1);
|
|
1083
|
-
ctx.update?.();
|
|
1084
|
-
});
|
|
1085
|
-
ctx.setAction("pageDown", () => {
|
|
1086
|
-
setScrollTop((s) => Math.min(maxScrollRef.current, s + pageSizeRef.current));
|
|
1087
|
-
bump((n) => n + 1);
|
|
1088
|
-
ctx.update?.();
|
|
1089
|
-
});
|
|
1113
|
+
ctx.setAction("scrollUp", () => applyUserScroll(-1));
|
|
1114
|
+
ctx.setAction("scrollDown", () => applyUserScroll(1));
|
|
1115
|
+
ctx.setAction("pageUp", () => applyUserScroll(-pageSizeRef.current));
|
|
1116
|
+
ctx.setAction("pageDown", () => applyUserScroll(pageSizeRef.current));
|
|
1090
1117
|
return void 0;
|
|
1091
|
-
}, [ctx, bindKeys]);
|
|
1092
|
-
const status = allLines.length === 0 ? "empty" : `lines ${clamped + 1}-${Math.min(clamped + visible.length, allLines.length)} of ${allLines.length}` + (needsBar ? " \xB7 \u2325\u2191/\u2193 or PgUp/Dn page" : "");
|
|
1118
|
+
}, [ctx, bindKeys, followBottom]);
|
|
1119
|
+
const status = allLines.length === 0 ? "empty" : `lines ${clamped + 1}-${Math.min(clamped + visible.length, allLines.length)} of ${allLines.length}` + (needsBar ? " \xB7 \u2325\u2191/\u2193 or PgUp/Dn page" : "") + (followBottom && following ? " \xB7 follow" : followBottom ? " \xB7 follow off" : "");
|
|
1093
1120
|
const rowNodes = visible.map((line, i) => {
|
|
1094
1121
|
const body = padEndVisible(line, textWidth);
|
|
1095
1122
|
const glyph = bar ? bar[i] ?? BAR_TRACK : showScrollbar ? " " : "";
|
|
@@ -1127,6 +1154,7 @@ var init_scrollable_text = __esm({
|
|
|
1127
1154
|
"src/screen/scrollable-text.js"() {
|
|
1128
1155
|
init_components();
|
|
1129
1156
|
init_scrollbar();
|
|
1157
|
+
init_follow_scroll();
|
|
1130
1158
|
init_scrollbar();
|
|
1131
1159
|
h5 = createElement2;
|
|
1132
1160
|
SCROLL_KEYS = [
|
|
@@ -1295,10 +1323,14 @@ __export(screen_exports, {
|
|
|
1295
1323
|
buildBreadcrumb: () => buildBreadcrumb,
|
|
1296
1324
|
buildDetailBreadcrumb: () => buildDetailBreadcrumb,
|
|
1297
1325
|
buildFooter: () => buildFooter,
|
|
1326
|
+
clampScroll: () => clampScroll,
|
|
1298
1327
|
formatBindingKey: () => formatBindingKey,
|
|
1299
1328
|
h: () => createElement3,
|
|
1329
|
+
isScrolledToBottom: () => isScrolledToBottom,
|
|
1300
1330
|
load: () => load,
|
|
1301
1331
|
memo: () => memo,
|
|
1332
|
+
nextScrollAfterContentChange: () => nextScrollAfterContentChange,
|
|
1333
|
+
nextScrollAfterUserMove: () => nextScrollAfterUserMove,
|
|
1302
1334
|
organizeFooterMessages: () => organizeFooterMessages,
|
|
1303
1335
|
scrollbarGlyphs: () => scrollbarGlyphs,
|
|
1304
1336
|
showListScreen: () => showListScreen,
|
|
@@ -1335,6 +1367,7 @@ var init_screen = __esm({
|
|
|
1335
1367
|
init_components();
|
|
1336
1368
|
init_ui_elements();
|
|
1337
1369
|
init_scrollable_text();
|
|
1370
|
+
init_follow_scroll();
|
|
1338
1371
|
init_scrollbar();
|
|
1339
1372
|
init_key_bindings();
|
|
1340
1373
|
init_utils();
|
|
@@ -2654,13 +2687,14 @@ var Logger = class _Logger {
|
|
|
2654
2687
|
*/
|
|
2655
2688
|
progress(message, opts) {
|
|
2656
2689
|
const { prefix, count, total } = opts;
|
|
2657
|
-
const
|
|
2690
|
+
const displayTotal = Math.max(Number(total) || 0, Number(count) || 0);
|
|
2691
|
+
const paddedTotal = String(displayTotal).length;
|
|
2658
2692
|
const paddedCount = String(count).padStart(paddedTotal, " ");
|
|
2659
2693
|
const payload = {
|
|
2660
2694
|
level: "progress",
|
|
2661
2695
|
message,
|
|
2662
2696
|
count: paddedCount,
|
|
2663
|
-
total,
|
|
2697
|
+
total: displayTotal,
|
|
2664
2698
|
prefix
|
|
2665
2699
|
};
|
|
2666
2700
|
const key = prefix ?? "";
|
|
@@ -2677,7 +2711,7 @@ var Logger = class _Logger {
|
|
|
2677
2711
|
if (wantTimes) {
|
|
2678
2712
|
let remaining = -1;
|
|
2679
2713
|
if (itemsPerSec > 0) {
|
|
2680
|
-
remaining = (
|
|
2714
|
+
remaining = Math.max(0, (displayTotal - count) / itemsPerSec);
|
|
2681
2715
|
}
|
|
2682
2716
|
payload.elapsed = this.round(elapsedSeconds, 2);
|
|
2683
2717
|
payload.remaining = remaining >= 0 ? this.round(remaining, 2) : remaining;
|
|
@@ -2686,12 +2720,12 @@ var Logger = class _Logger {
|
|
|
2686
2720
|
payload.rate = itemsPerSec >= 0 ? this.round(itemsPerSec, 2) : itemsPerSec;
|
|
2687
2721
|
}
|
|
2688
2722
|
}
|
|
2689
|
-
if (count
|
|
2723
|
+
if (count === total) {
|
|
2690
2724
|
delete this.startTimes[key];
|
|
2691
2725
|
delete this.startCounts[key];
|
|
2692
2726
|
delete this.lastProgressTimes[key];
|
|
2693
2727
|
}
|
|
2694
|
-
if (this.shouldOutputProgress(prefix ?? "", count,
|
|
2728
|
+
if (this.shouldOutputProgress(prefix ?? "", count, displayTotal)) {
|
|
2695
2729
|
this.out(payload);
|
|
2696
2730
|
if (this.options.progressThrottle && prefix) {
|
|
2697
2731
|
this.lastProgressTimes[prefix] = Date.now();
|
|
@@ -2850,6 +2884,8 @@ function setup(opts = {}) {
|
|
|
2850
2884
|
// a quick "show me the figured params and quit" that skips the flow's
|
|
2851
2885
|
// actual work. Like --stopAfter=init, this is a hard exit(0) (registered
|
|
2852
2886
|
// cleanups are skipped); call it once components/params are resolved.
|
|
2887
|
+
_requestExitCode: null,
|
|
2888
|
+
requestExit: null,
|
|
2853
2889
|
showUsedParamsIfNeeded: () => {
|
|
2854
2890
|
const mode = params.getShowUsedParamsMode?.();
|
|
2855
2891
|
if (mode !== "top" && mode !== "stop") return;
|
|
@@ -2860,6 +2896,9 @@ function setup(opts = {}) {
|
|
|
2860
2896
|
}
|
|
2861
2897
|
}
|
|
2862
2898
|
};
|
|
2899
|
+
context.requestExit = (code = 0) => {
|
|
2900
|
+
context._requestExitCode = code;
|
|
2901
|
+
};
|
|
2863
2902
|
logger.debug("[setup] completed successfully");
|
|
2864
2903
|
return context;
|
|
2865
2904
|
}
|
|
@@ -2895,9 +2934,25 @@ async function init(flow2, opts = {}) {
|
|
|
2895
2934
|
if (cleanupRan) return;
|
|
2896
2935
|
cleanupRan = true;
|
|
2897
2936
|
const fns = [...ctx.cleanupFunctions].reverse();
|
|
2937
|
+
const budgetMs = 5e3;
|
|
2938
|
+
const started = Date.now();
|
|
2898
2939
|
for (const fn of fns) {
|
|
2940
|
+
const left = budgetMs - (Date.now() - started);
|
|
2941
|
+
if (left <= 0) {
|
|
2942
|
+
ctx.logger.warn("[cleanup] budget exhausted \u2014 skipping remaining cleanup");
|
|
2943
|
+
break;
|
|
2944
|
+
}
|
|
2899
2945
|
try {
|
|
2900
|
-
await
|
|
2946
|
+
await Promise.race([
|
|
2947
|
+
Promise.resolve(fn(ctx)),
|
|
2948
|
+
new Promise((_, reject) => {
|
|
2949
|
+
const t = setTimeout(
|
|
2950
|
+
() => reject(new Error(`cleanup timed out after ${left}ms`)),
|
|
2951
|
+
left
|
|
2952
|
+
);
|
|
2953
|
+
t.unref?.();
|
|
2954
|
+
})
|
|
2955
|
+
]);
|
|
2901
2956
|
} catch (error) {
|
|
2902
2957
|
ctx.logger.warn("[cleanup] error in cleanup function:", error);
|
|
2903
2958
|
}
|
|
@@ -2993,6 +3048,8 @@ async function init(flow2, opts = {}) {
|
|
|
2993
3048
|
process.exit(process.exitCode);
|
|
2994
3049
|
} else if (stop || kill) {
|
|
2995
3050
|
process.exit(0);
|
|
3051
|
+
} else if (context._requestExitCode != null) {
|
|
3052
|
+
process.exit(context._requestExitCode);
|
|
2996
3053
|
}
|
|
2997
3054
|
}
|
|
2998
3055
|
}
|
|
@@ -3116,7 +3173,7 @@ var CONNECTION_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
|
3116
3173
|
"57P02",
|
|
3117
3174
|
"57P03"
|
|
3118
3175
|
]);
|
|
3119
|
-
var CONNECTION_ERROR_MESSAGE_RE = /connection (terminated|ended|closed|destroyed|reset|refused|not open)|Connection terminated unexpectedly|Client has encountered a connection error|server closed the connection|Cannot use a pool after calling end|This socket has been ended|connect ECONNRESET/i;
|
|
3176
|
+
var CONNECTION_ERROR_MESSAGE_RE = /connection (terminated|ended|closed|destroyed|reset|refused|not open)|Connection (terminated|ended) unexpectedly|Client has encountered a connection error|server closed the connection|Cannot use a pool after calling end|This socket has been ended|connect ECONNRESET/i;
|
|
3120
3177
|
var Db = class _Db {
|
|
3121
3178
|
static async init(context, options = {}) {
|
|
3122
3179
|
const buildConfig = async () => {
|
|
@@ -3384,6 +3441,10 @@ var Db = class _Db {
|
|
|
3384
3441
|
this.isConnected = false;
|
|
3385
3442
|
this.queriesLog = [];
|
|
3386
3443
|
this._reconnectPromise = null;
|
|
3444
|
+
this._closed = false;
|
|
3445
|
+
this._liveKnex = /* @__PURE__ */ new Set();
|
|
3446
|
+
this._reconnectCooldownUntil = 0;
|
|
3447
|
+
this._reconnectAcquireTimeoutMs = null;
|
|
3387
3448
|
this.config = {
|
|
3388
3449
|
testConnection: true,
|
|
3389
3450
|
profile: false,
|
|
@@ -3468,6 +3529,9 @@ var Db = class _Db {
|
|
|
3468
3529
|
return null;
|
|
3469
3530
|
}
|
|
3470
3531
|
async connect() {
|
|
3532
|
+
if (this._closed) {
|
|
3533
|
+
throw new ParamError("Db: Connection closed");
|
|
3534
|
+
}
|
|
3471
3535
|
if (this.isConnected && this.knexInstance) {
|
|
3472
3536
|
this.logger.warn?.("[Db] Already connected");
|
|
3473
3537
|
return;
|
|
@@ -3483,22 +3547,46 @@ var Db = class _Db {
|
|
|
3483
3547
|
connectionString: this.config.connectionString,
|
|
3484
3548
|
family: 4
|
|
3485
3549
|
};
|
|
3550
|
+
const acquireTimeout = this._reconnectAcquireTimeoutMs ?? this.config.acquireConnectionTimeout;
|
|
3486
3551
|
this.knexInstance = knex({
|
|
3487
3552
|
client,
|
|
3488
3553
|
connection: connectionConfig,
|
|
3489
3554
|
pool: this.config.pool,
|
|
3490
|
-
acquireConnectionTimeout:
|
|
3555
|
+
acquireConnectionTimeout: acquireTimeout,
|
|
3491
3556
|
...this.config.ssl && { ssl: this.config.ssl }
|
|
3492
3557
|
});
|
|
3558
|
+
this._liveKnex.add(this.knexInstance);
|
|
3559
|
+
this.knexInstance.on?.("error", (err) => {
|
|
3560
|
+
if (this._closed) return;
|
|
3561
|
+
this.logger.warn?.(
|
|
3562
|
+
`[Db] Connection error (${this.getErrorMessage(err)})`
|
|
3563
|
+
);
|
|
3564
|
+
});
|
|
3565
|
+
if (this._closed) {
|
|
3566
|
+
await this._destroyKnex(this.knexInstance, "connect aborted (closed)");
|
|
3567
|
+
this.knexInstance = null;
|
|
3568
|
+
throw new ParamError("Db: Connection closed");
|
|
3569
|
+
}
|
|
3493
3570
|
if (this.config.profile) {
|
|
3494
3571
|
this.attachProfiler();
|
|
3495
3572
|
}
|
|
3496
3573
|
if (this.config.testConnection) {
|
|
3497
3574
|
await this.testConnection();
|
|
3498
3575
|
}
|
|
3576
|
+
if (this._closed) {
|
|
3577
|
+
await this._destroyKnex(this.knexInstance, "connect aborted (closed)");
|
|
3578
|
+
this.knexInstance = null;
|
|
3579
|
+
throw new ParamError("Db: Connection closed");
|
|
3580
|
+
}
|
|
3499
3581
|
this.isConnected = true;
|
|
3500
3582
|
this.logger.debug?.(formatDbConnectMessage(this.config.name, this.config.connectionString));
|
|
3501
3583
|
} catch (error) {
|
|
3584
|
+
const failed = this.knexInstance;
|
|
3585
|
+
this.knexInstance = null;
|
|
3586
|
+
this.isConnected = false;
|
|
3587
|
+
if (failed) {
|
|
3588
|
+
await this._destroyKnex(failed, "connect failed");
|
|
3589
|
+
}
|
|
3502
3590
|
if (error instanceof ParamError) {
|
|
3503
3591
|
throw error;
|
|
3504
3592
|
}
|
|
@@ -3506,20 +3594,42 @@ var Db = class _Db {
|
|
|
3506
3594
|
throw new ParamError(`Db: Connection failed - ${errorMsg}`);
|
|
3507
3595
|
}
|
|
3508
3596
|
}
|
|
3509
|
-
|
|
3510
|
-
|
|
3511
|
-
|
|
3512
|
-
|
|
3597
|
+
/**
|
|
3598
|
+
* Destroy a knex pool without hanging exit on stuck TCP sockets (ETIMEDOUT).
|
|
3599
|
+
* @param {import("knex").Knex | null | undefined} knexInst
|
|
3600
|
+
* @param {string} [reason]
|
|
3601
|
+
* @param {number} [timeoutMs]
|
|
3602
|
+
*/
|
|
3603
|
+
async _destroyKnex(knexInst, reason = "destroy", timeoutMs = 3e3) {
|
|
3604
|
+
if (!knexInst || typeof knexInst.destroy !== "function") return;
|
|
3605
|
+
this._liveKnex.delete(knexInst);
|
|
3513
3606
|
try {
|
|
3514
|
-
await
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3607
|
+
await Promise.race([
|
|
3608
|
+
knexInst.destroy(),
|
|
3609
|
+
new Promise((_, reject) => {
|
|
3610
|
+
const t = setTimeout(
|
|
3611
|
+
() => reject(new Error(`Db: ${reason} timed out after ${timeoutMs}ms`)),
|
|
3612
|
+
timeoutMs
|
|
3613
|
+
);
|
|
3614
|
+
t.unref?.();
|
|
3615
|
+
})
|
|
3616
|
+
]);
|
|
3519
3617
|
} catch (error) {
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3618
|
+
this.logger.debug?.(
|
|
3619
|
+
`[Db] ${reason}: ${this.getErrorMessage(error)}`
|
|
3620
|
+
);
|
|
3621
|
+
}
|
|
3622
|
+
}
|
|
3623
|
+
async disconnect() {
|
|
3624
|
+
this._closed = true;
|
|
3625
|
+
this.isConnected = false;
|
|
3626
|
+
this.knexInstance = null;
|
|
3627
|
+
this.queriesLog = [];
|
|
3628
|
+
const all = [...this._liveKnex];
|
|
3629
|
+
this._liveKnex.clear();
|
|
3630
|
+
await Promise.all(all.map((inst) => this._destroyKnex(inst, "disconnect")));
|
|
3631
|
+
if (all.length > 0) {
|
|
3632
|
+
this.logger.debug?.(formatDbDisconnectMessage(this.config.name, this.config.connectionString));
|
|
3523
3633
|
}
|
|
3524
3634
|
}
|
|
3525
3635
|
/**
|
|
@@ -3542,27 +3652,39 @@ var Db = class _Db {
|
|
|
3542
3652
|
}
|
|
3543
3653
|
/**
|
|
3544
3654
|
* Destroy the current knex pool and open a new one. Concurrent callers share one attempt.
|
|
3655
|
+
* No-ops once disconnect() has closed the handle.
|
|
3545
3656
|
*/
|
|
3546
3657
|
async reconnect() {
|
|
3658
|
+
if (this._closed) {
|
|
3659
|
+
return;
|
|
3660
|
+
}
|
|
3661
|
+
if (this._reconnectCooldownUntil && Date.now() < this._reconnectCooldownUntil) {
|
|
3662
|
+
return;
|
|
3663
|
+
}
|
|
3547
3664
|
if (this._reconnectPromise) {
|
|
3548
3665
|
await this._reconnectPromise;
|
|
3549
3666
|
return;
|
|
3550
3667
|
}
|
|
3551
3668
|
this._reconnectPromise = (async () => {
|
|
3669
|
+
if (this._closed) return;
|
|
3552
3670
|
const old = this.knexInstance;
|
|
3553
3671
|
this.isConnected = false;
|
|
3554
3672
|
this.knexInstance = null;
|
|
3555
3673
|
this.queriesLog = [];
|
|
3556
3674
|
if (old) {
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3675
|
+
await this._destroyKnex(old, "destroy during reconnect");
|
|
3676
|
+
}
|
|
3677
|
+
if (this._closed) return;
|
|
3678
|
+
this._reconnectAcquireTimeoutMs = 3e3;
|
|
3679
|
+
try {
|
|
3680
|
+
await this.connect();
|
|
3681
|
+
this._reconnectCooldownUntil = 0;
|
|
3682
|
+
} catch (error) {
|
|
3683
|
+
this._reconnectCooldownUntil = Date.now() + 5e3;
|
|
3684
|
+
throw error;
|
|
3685
|
+
} finally {
|
|
3686
|
+
this._reconnectAcquireTimeoutMs = null;
|
|
3564
3687
|
}
|
|
3565
|
-
await this.connect();
|
|
3566
3688
|
})();
|
|
3567
3689
|
try {
|
|
3568
3690
|
await this._reconnectPromise;
|
|
@@ -3571,10 +3693,19 @@ var Db = class _Db {
|
|
|
3571
3693
|
}
|
|
3572
3694
|
}
|
|
3573
3695
|
async reconnectAfterConnectionError(error) {
|
|
3696
|
+
if (this._closed) {
|
|
3697
|
+
return;
|
|
3698
|
+
}
|
|
3699
|
+
if (this._reconnectCooldownUntil && Date.now() < this._reconnectCooldownUntil) {
|
|
3700
|
+
return;
|
|
3701
|
+
}
|
|
3574
3702
|
this.logger.warn?.(
|
|
3575
3703
|
`[Db] Connection lost (${this.getErrorMessage(error)}) \u2014 reconnecting\u2026`
|
|
3576
3704
|
);
|
|
3577
|
-
|
|
3705
|
+
try {
|
|
3706
|
+
await this.reconnect();
|
|
3707
|
+
} catch {
|
|
3708
|
+
}
|
|
3578
3709
|
}
|
|
3579
3710
|
/**
|
|
3580
3711
|
* Run `fn`; on a connection error, reconnect once (by default) and retry `fn`.
|
|
@@ -3587,10 +3718,13 @@ var Db = class _Db {
|
|
|
3587
3718
|
return await fn();
|
|
3588
3719
|
} catch (error) {
|
|
3589
3720
|
lastError = error;
|
|
3590
|
-
if (!this.isConnectionError(error) || attempt >= retries) {
|
|
3721
|
+
if (this._closed || !this.isConnectionError(error) || attempt >= retries) {
|
|
3591
3722
|
throw error;
|
|
3592
3723
|
}
|
|
3593
3724
|
await this.reconnectAfterConnectionError(error);
|
|
3725
|
+
if (this._closed) {
|
|
3726
|
+
throw error;
|
|
3727
|
+
}
|
|
3594
3728
|
}
|
|
3595
3729
|
}
|
|
3596
3730
|
throw lastError;
|
|
@@ -3614,10 +3748,13 @@ var Db = class _Db {
|
|
|
3614
3748
|
try {
|
|
3615
3749
|
return await protoThen.call(builder);
|
|
3616
3750
|
} catch (error) {
|
|
3617
|
-
if (!inst.isConnectionError(error)) {
|
|
3751
|
+
if (inst._closed || !inst.isConnectionError(error)) {
|
|
3618
3752
|
throw error;
|
|
3619
3753
|
}
|
|
3620
3754
|
await inst.reconnectAfterConnectionError(error);
|
|
3755
|
+
if (inst._closed || !inst.knexInstance) {
|
|
3756
|
+
throw error;
|
|
3757
|
+
}
|
|
3621
3758
|
if (typeof builder.clone === "function") {
|
|
3622
3759
|
const retry = builder.clone();
|
|
3623
3760
|
retry.client = inst.knexInstance.client;
|