@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.cjs
CHANGED
|
@@ -1018,6 +1018,30 @@ var init_ui_elements = __esm({
|
|
|
1018
1018
|
}
|
|
1019
1019
|
});
|
|
1020
1020
|
|
|
1021
|
+
// src/screen/follow-scroll.js
|
|
1022
|
+
function clampScroll(scrollTop, maxScroll) {
|
|
1023
|
+
const max = Math.max(0, Number(maxScroll) || 0);
|
|
1024
|
+
const top = Number(scrollTop) || 0;
|
|
1025
|
+
return Math.min(Math.max(0, top), max);
|
|
1026
|
+
}
|
|
1027
|
+
function isScrolledToBottom(scrollTop, maxScroll) {
|
|
1028
|
+
return clampScroll(scrollTop, maxScroll) >= Math.max(0, Number(maxScroll) || 0);
|
|
1029
|
+
}
|
|
1030
|
+
function nextScrollAfterUserMove(scrollTop, maxScroll, delta) {
|
|
1031
|
+
const next = clampScroll((Number(scrollTop) || 0) + (Number(delta) || 0), maxScroll);
|
|
1032
|
+
return { scrollTop: next, following: isScrolledToBottom(next, maxScroll) };
|
|
1033
|
+
}
|
|
1034
|
+
function nextScrollAfterContentChange({ following, scrollTop, maxScroll }) {
|
|
1035
|
+
const max = Math.max(0, Number(maxScroll) || 0);
|
|
1036
|
+
if (following) return { scrollTop: max, following: true };
|
|
1037
|
+
const next = clampScroll(scrollTop, max);
|
|
1038
|
+
return { scrollTop: next, following: isScrolledToBottom(next, max) };
|
|
1039
|
+
}
|
|
1040
|
+
var init_follow_scroll = __esm({
|
|
1041
|
+
"src/screen/follow-scroll.js"() {
|
|
1042
|
+
}
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1021
1045
|
// src/screen/scrollable-text.js
|
|
1022
1046
|
function wrapTextLines(text, cols) {
|
|
1023
1047
|
const w = Math.max(1, Math.floor(Number(cols) || 1));
|
|
@@ -1050,9 +1074,11 @@ function ScrollableText({
|
|
|
1050
1074
|
showScrollbar = true,
|
|
1051
1075
|
showStatus = true,
|
|
1052
1076
|
bindKeys = true,
|
|
1053
|
-
header = null
|
|
1077
|
+
header = null,
|
|
1078
|
+
followBottom = false
|
|
1054
1079
|
}) {
|
|
1055
1080
|
const [scrollTop, setScrollTop] = (0, import_react5.useState)(0);
|
|
1081
|
+
const [following, setFollowing] = (0, import_react5.useState)(() => !!followBottom);
|
|
1056
1082
|
const [, bump] = (0, import_react5.useState)(0);
|
|
1057
1083
|
const termRows = process.stdout.rows || 24;
|
|
1058
1084
|
const viewportRows = Math.max(
|
|
@@ -1071,39 +1097,40 @@ function ScrollableText({
|
|
|
1071
1097
|
const clamped = Math.min(Math.max(0, scrollTop), maxScroll);
|
|
1072
1098
|
const visible = allLines.slice(clamped, clamped + viewportRows);
|
|
1073
1099
|
const bar = needsBar ? scrollbarGlyphs(viewportRows, allLines.length, clamped) : null;
|
|
1074
|
-
(0, import_react5.useEffect)(() => {
|
|
1075
|
-
setScrollTop((s) => Math.min(s, maxScroll));
|
|
1076
|
-
}, [maxScroll]);
|
|
1077
1100
|
const maxScrollRef = (0, import_react5.useRef)(maxScroll);
|
|
1078
1101
|
const pageSizeRef = (0, import_react5.useRef)(viewportRows);
|
|
1102
|
+
const scrollTopRef = (0, import_react5.useRef)(scrollTop);
|
|
1103
|
+
const followingRef = (0, import_react5.useRef)(following);
|
|
1079
1104
|
maxScrollRef.current = maxScroll;
|
|
1080
1105
|
pageSizeRef.current = viewportRows;
|
|
1106
|
+
scrollTopRef.current = scrollTop;
|
|
1107
|
+
followingRef.current = following;
|
|
1108
|
+
(0, import_react5.useEffect)(() => {
|
|
1109
|
+
const next = nextScrollAfterContentChange({
|
|
1110
|
+
following: followBottom && followingRef.current,
|
|
1111
|
+
scrollTop: scrollTopRef.current,
|
|
1112
|
+
maxScroll
|
|
1113
|
+
});
|
|
1114
|
+
if (next.scrollTop !== scrollTopRef.current) setScrollTop(next.scrollTop);
|
|
1115
|
+
if (followBottom && next.following !== followingRef.current) setFollowing(next.following);
|
|
1116
|
+
}, [maxScroll, followBottom]);
|
|
1117
|
+
const applyUserScroll = (delta) => {
|
|
1118
|
+
const next = nextScrollAfterUserMove(scrollTopRef.current, maxScrollRef.current, delta);
|
|
1119
|
+
setScrollTop(next.scrollTop);
|
|
1120
|
+
if (followBottom) setFollowing(next.following);
|
|
1121
|
+
bump((n) => n + 1);
|
|
1122
|
+
ctx?.update?.();
|
|
1123
|
+
};
|
|
1081
1124
|
(0, import_react5.useEffect)(() => {
|
|
1082
1125
|
if (!ctx || !bindKeys) return void 0;
|
|
1083
1126
|
ctx.setKeyBinding(SCROLL_KEYS);
|
|
1084
|
-
ctx.setAction("scrollUp", () =>
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
});
|
|
1089
|
-
ctx.setAction("scrollDown", () => {
|
|
1090
|
-
setScrollTop((s) => Math.min(maxScrollRef.current, s + 1));
|
|
1091
|
-
bump((n) => n + 1);
|
|
1092
|
-
ctx.update?.();
|
|
1093
|
-
});
|
|
1094
|
-
ctx.setAction("pageUp", () => {
|
|
1095
|
-
setScrollTop((s) => Math.max(0, s - pageSizeRef.current));
|
|
1096
|
-
bump((n) => n + 1);
|
|
1097
|
-
ctx.update?.();
|
|
1098
|
-
});
|
|
1099
|
-
ctx.setAction("pageDown", () => {
|
|
1100
|
-
setScrollTop((s) => Math.min(maxScrollRef.current, s + pageSizeRef.current));
|
|
1101
|
-
bump((n) => n + 1);
|
|
1102
|
-
ctx.update?.();
|
|
1103
|
-
});
|
|
1127
|
+
ctx.setAction("scrollUp", () => applyUserScroll(-1));
|
|
1128
|
+
ctx.setAction("scrollDown", () => applyUserScroll(1));
|
|
1129
|
+
ctx.setAction("pageUp", () => applyUserScroll(-pageSizeRef.current));
|
|
1130
|
+
ctx.setAction("pageDown", () => applyUserScroll(pageSizeRef.current));
|
|
1104
1131
|
return void 0;
|
|
1105
|
-
}, [ctx, bindKeys]);
|
|
1106
|
-
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" : "");
|
|
1132
|
+
}, [ctx, bindKeys, followBottom]);
|
|
1133
|
+
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" : "");
|
|
1107
1134
|
const rowNodes = visible.map((line, i) => {
|
|
1108
1135
|
const body = padEndVisible(line, textWidth);
|
|
1109
1136
|
const glyph = bar ? bar[i] ?? BAR_TRACK : showScrollbar ? " " : "";
|
|
@@ -1143,6 +1170,7 @@ var init_scrollable_text = __esm({
|
|
|
1143
1170
|
import_ink5 = require("ink");
|
|
1144
1171
|
init_components();
|
|
1145
1172
|
init_scrollbar();
|
|
1173
|
+
init_follow_scroll();
|
|
1146
1174
|
init_scrollbar();
|
|
1147
1175
|
h5 = import_react5.createElement;
|
|
1148
1176
|
SCROLL_KEYS = [
|
|
@@ -1311,10 +1339,14 @@ __export(screen_exports, {
|
|
|
1311
1339
|
buildBreadcrumb: () => buildBreadcrumb,
|
|
1312
1340
|
buildDetailBreadcrumb: () => buildDetailBreadcrumb,
|
|
1313
1341
|
buildFooter: () => buildFooter,
|
|
1342
|
+
clampScroll: () => clampScroll,
|
|
1314
1343
|
formatBindingKey: () => formatBindingKey,
|
|
1315
1344
|
h: () => import_react6.createElement,
|
|
1345
|
+
isScrolledToBottom: () => isScrolledToBottom,
|
|
1316
1346
|
load: () => load,
|
|
1317
1347
|
memo: () => import_react6.memo,
|
|
1348
|
+
nextScrollAfterContentChange: () => nextScrollAfterContentChange,
|
|
1349
|
+
nextScrollAfterUserMove: () => nextScrollAfterUserMove,
|
|
1318
1350
|
organizeFooterMessages: () => organizeFooterMessages,
|
|
1319
1351
|
scrollbarGlyphs: () => scrollbarGlyphs,
|
|
1320
1352
|
showListScreen: () => showListScreen,
|
|
@@ -1351,6 +1383,7 @@ var init_screen = __esm({
|
|
|
1351
1383
|
init_components();
|
|
1352
1384
|
init_ui_elements();
|
|
1353
1385
|
init_scrollable_text();
|
|
1386
|
+
init_follow_scroll();
|
|
1354
1387
|
init_scrollbar();
|
|
1355
1388
|
init_key_bindings();
|
|
1356
1389
|
init_utils();
|
|
@@ -2670,13 +2703,14 @@ var Logger = class _Logger {
|
|
|
2670
2703
|
*/
|
|
2671
2704
|
progress(message, opts) {
|
|
2672
2705
|
const { prefix, count, total } = opts;
|
|
2673
|
-
const
|
|
2706
|
+
const displayTotal = Math.max(Number(total) || 0, Number(count) || 0);
|
|
2707
|
+
const paddedTotal = String(displayTotal).length;
|
|
2674
2708
|
const paddedCount = String(count).padStart(paddedTotal, " ");
|
|
2675
2709
|
const payload = {
|
|
2676
2710
|
level: "progress",
|
|
2677
2711
|
message,
|
|
2678
2712
|
count: paddedCount,
|
|
2679
|
-
total,
|
|
2713
|
+
total: displayTotal,
|
|
2680
2714
|
prefix
|
|
2681
2715
|
};
|
|
2682
2716
|
const key = prefix ?? "";
|
|
@@ -2693,7 +2727,7 @@ var Logger = class _Logger {
|
|
|
2693
2727
|
if (wantTimes) {
|
|
2694
2728
|
let remaining = -1;
|
|
2695
2729
|
if (itemsPerSec > 0) {
|
|
2696
|
-
remaining = (
|
|
2730
|
+
remaining = Math.max(0, (displayTotal - count) / itemsPerSec);
|
|
2697
2731
|
}
|
|
2698
2732
|
payload.elapsed = this.round(elapsedSeconds, 2);
|
|
2699
2733
|
payload.remaining = remaining >= 0 ? this.round(remaining, 2) : remaining;
|
|
@@ -2702,12 +2736,12 @@ var Logger = class _Logger {
|
|
|
2702
2736
|
payload.rate = itemsPerSec >= 0 ? this.round(itemsPerSec, 2) : itemsPerSec;
|
|
2703
2737
|
}
|
|
2704
2738
|
}
|
|
2705
|
-
if (count
|
|
2739
|
+
if (count === total) {
|
|
2706
2740
|
delete this.startTimes[key];
|
|
2707
2741
|
delete this.startCounts[key];
|
|
2708
2742
|
delete this.lastProgressTimes[key];
|
|
2709
2743
|
}
|
|
2710
|
-
if (this.shouldOutputProgress(prefix ?? "", count,
|
|
2744
|
+
if (this.shouldOutputProgress(prefix ?? "", count, displayTotal)) {
|
|
2711
2745
|
this.out(payload);
|
|
2712
2746
|
if (this.options.progressThrottle && prefix) {
|
|
2713
2747
|
this.lastProgressTimes[prefix] = Date.now();
|
|
@@ -2866,6 +2900,8 @@ function setup(opts = {}) {
|
|
|
2866
2900
|
// a quick "show me the figured params and quit" that skips the flow's
|
|
2867
2901
|
// actual work. Like --stopAfter=init, this is a hard exit(0) (registered
|
|
2868
2902
|
// cleanups are skipped); call it once components/params are resolved.
|
|
2903
|
+
_requestExitCode: null,
|
|
2904
|
+
requestExit: null,
|
|
2869
2905
|
showUsedParamsIfNeeded: () => {
|
|
2870
2906
|
const mode = params.getShowUsedParamsMode?.();
|
|
2871
2907
|
if (mode !== "top" && mode !== "stop") return;
|
|
@@ -2876,6 +2912,9 @@ function setup(opts = {}) {
|
|
|
2876
2912
|
}
|
|
2877
2913
|
}
|
|
2878
2914
|
};
|
|
2915
|
+
context.requestExit = (code = 0) => {
|
|
2916
|
+
context._requestExitCode = code;
|
|
2917
|
+
};
|
|
2879
2918
|
logger.debug("[setup] completed successfully");
|
|
2880
2919
|
return context;
|
|
2881
2920
|
}
|
|
@@ -2911,9 +2950,25 @@ async function init(flow2, opts = {}) {
|
|
|
2911
2950
|
if (cleanupRan) return;
|
|
2912
2951
|
cleanupRan = true;
|
|
2913
2952
|
const fns = [...ctx.cleanupFunctions].reverse();
|
|
2953
|
+
const budgetMs = 5e3;
|
|
2954
|
+
const started = Date.now();
|
|
2914
2955
|
for (const fn of fns) {
|
|
2956
|
+
const left = budgetMs - (Date.now() - started);
|
|
2957
|
+
if (left <= 0) {
|
|
2958
|
+
ctx.logger.warn("[cleanup] budget exhausted \u2014 skipping remaining cleanup");
|
|
2959
|
+
break;
|
|
2960
|
+
}
|
|
2915
2961
|
try {
|
|
2916
|
-
await
|
|
2962
|
+
await Promise.race([
|
|
2963
|
+
Promise.resolve(fn(ctx)),
|
|
2964
|
+
new Promise((_, reject) => {
|
|
2965
|
+
const t = setTimeout(
|
|
2966
|
+
() => reject(new Error(`cleanup timed out after ${left}ms`)),
|
|
2967
|
+
left
|
|
2968
|
+
);
|
|
2969
|
+
t.unref?.();
|
|
2970
|
+
})
|
|
2971
|
+
]);
|
|
2917
2972
|
} catch (error) {
|
|
2918
2973
|
ctx.logger.warn("[cleanup] error in cleanup function:", error);
|
|
2919
2974
|
}
|
|
@@ -3009,6 +3064,8 @@ async function init(flow2, opts = {}) {
|
|
|
3009
3064
|
process.exit(process.exitCode);
|
|
3010
3065
|
} else if (stop || kill) {
|
|
3011
3066
|
process.exit(0);
|
|
3067
|
+
} else if (context._requestExitCode != null) {
|
|
3068
|
+
process.exit(context._requestExitCode);
|
|
3012
3069
|
}
|
|
3013
3070
|
}
|
|
3014
3071
|
}
|
|
@@ -3132,7 +3189,7 @@ var CONNECTION_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
|
3132
3189
|
"57P02",
|
|
3133
3190
|
"57P03"
|
|
3134
3191
|
]);
|
|
3135
|
-
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;
|
|
3192
|
+
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;
|
|
3136
3193
|
var Db = class _Db {
|
|
3137
3194
|
static async init(context, options = {}) {
|
|
3138
3195
|
const buildConfig = async () => {
|
|
@@ -3400,6 +3457,10 @@ var Db = class _Db {
|
|
|
3400
3457
|
this.isConnected = false;
|
|
3401
3458
|
this.queriesLog = [];
|
|
3402
3459
|
this._reconnectPromise = null;
|
|
3460
|
+
this._closed = false;
|
|
3461
|
+
this._liveKnex = /* @__PURE__ */ new Set();
|
|
3462
|
+
this._reconnectCooldownUntil = 0;
|
|
3463
|
+
this._reconnectAcquireTimeoutMs = null;
|
|
3403
3464
|
this.config = {
|
|
3404
3465
|
testConnection: true,
|
|
3405
3466
|
profile: false,
|
|
@@ -3484,6 +3545,9 @@ var Db = class _Db {
|
|
|
3484
3545
|
return null;
|
|
3485
3546
|
}
|
|
3486
3547
|
async connect() {
|
|
3548
|
+
if (this._closed) {
|
|
3549
|
+
throw new ParamError("Db: Connection closed");
|
|
3550
|
+
}
|
|
3487
3551
|
if (this.isConnected && this.knexInstance) {
|
|
3488
3552
|
this.logger.warn?.("[Db] Already connected");
|
|
3489
3553
|
return;
|
|
@@ -3499,22 +3563,46 @@ var Db = class _Db {
|
|
|
3499
3563
|
connectionString: this.config.connectionString,
|
|
3500
3564
|
family: 4
|
|
3501
3565
|
};
|
|
3566
|
+
const acquireTimeout = this._reconnectAcquireTimeoutMs ?? this.config.acquireConnectionTimeout;
|
|
3502
3567
|
this.knexInstance = (0, import_knex.default)({
|
|
3503
3568
|
client,
|
|
3504
3569
|
connection: connectionConfig,
|
|
3505
3570
|
pool: this.config.pool,
|
|
3506
|
-
acquireConnectionTimeout:
|
|
3571
|
+
acquireConnectionTimeout: acquireTimeout,
|
|
3507
3572
|
...this.config.ssl && { ssl: this.config.ssl }
|
|
3508
3573
|
});
|
|
3574
|
+
this._liveKnex.add(this.knexInstance);
|
|
3575
|
+
this.knexInstance.on?.("error", (err) => {
|
|
3576
|
+
if (this._closed) return;
|
|
3577
|
+
this.logger.warn?.(
|
|
3578
|
+
`[Db] Connection error (${this.getErrorMessage(err)})`
|
|
3579
|
+
);
|
|
3580
|
+
});
|
|
3581
|
+
if (this._closed) {
|
|
3582
|
+
await this._destroyKnex(this.knexInstance, "connect aborted (closed)");
|
|
3583
|
+
this.knexInstance = null;
|
|
3584
|
+
throw new ParamError("Db: Connection closed");
|
|
3585
|
+
}
|
|
3509
3586
|
if (this.config.profile) {
|
|
3510
3587
|
this.attachProfiler();
|
|
3511
3588
|
}
|
|
3512
3589
|
if (this.config.testConnection) {
|
|
3513
3590
|
await this.testConnection();
|
|
3514
3591
|
}
|
|
3592
|
+
if (this._closed) {
|
|
3593
|
+
await this._destroyKnex(this.knexInstance, "connect aborted (closed)");
|
|
3594
|
+
this.knexInstance = null;
|
|
3595
|
+
throw new ParamError("Db: Connection closed");
|
|
3596
|
+
}
|
|
3515
3597
|
this.isConnected = true;
|
|
3516
3598
|
this.logger.debug?.(formatDbConnectMessage(this.config.name, this.config.connectionString));
|
|
3517
3599
|
} catch (error) {
|
|
3600
|
+
const failed = this.knexInstance;
|
|
3601
|
+
this.knexInstance = null;
|
|
3602
|
+
this.isConnected = false;
|
|
3603
|
+
if (failed) {
|
|
3604
|
+
await this._destroyKnex(failed, "connect failed");
|
|
3605
|
+
}
|
|
3518
3606
|
if (error instanceof ParamError) {
|
|
3519
3607
|
throw error;
|
|
3520
3608
|
}
|
|
@@ -3522,20 +3610,42 @@ var Db = class _Db {
|
|
|
3522
3610
|
throw new ParamError(`Db: Connection failed - ${errorMsg}`);
|
|
3523
3611
|
}
|
|
3524
3612
|
}
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3613
|
+
/**
|
|
3614
|
+
* Destroy a knex pool without hanging exit on stuck TCP sockets (ETIMEDOUT).
|
|
3615
|
+
* @param {import("knex").Knex | null | undefined} knexInst
|
|
3616
|
+
* @param {string} [reason]
|
|
3617
|
+
* @param {number} [timeoutMs]
|
|
3618
|
+
*/
|
|
3619
|
+
async _destroyKnex(knexInst, reason = "destroy", timeoutMs = 3e3) {
|
|
3620
|
+
if (!knexInst || typeof knexInst.destroy !== "function") return;
|
|
3621
|
+
this._liveKnex.delete(knexInst);
|
|
3529
3622
|
try {
|
|
3530
|
-
await
|
|
3531
|
-
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3623
|
+
await Promise.race([
|
|
3624
|
+
knexInst.destroy(),
|
|
3625
|
+
new Promise((_, reject) => {
|
|
3626
|
+
const t = setTimeout(
|
|
3627
|
+
() => reject(new Error(`Db: ${reason} timed out after ${timeoutMs}ms`)),
|
|
3628
|
+
timeoutMs
|
|
3629
|
+
);
|
|
3630
|
+
t.unref?.();
|
|
3631
|
+
})
|
|
3632
|
+
]);
|
|
3535
3633
|
} catch (error) {
|
|
3536
|
-
|
|
3537
|
-
|
|
3538
|
-
|
|
3634
|
+
this.logger.debug?.(
|
|
3635
|
+
`[Db] ${reason}: ${this.getErrorMessage(error)}`
|
|
3636
|
+
);
|
|
3637
|
+
}
|
|
3638
|
+
}
|
|
3639
|
+
async disconnect() {
|
|
3640
|
+
this._closed = true;
|
|
3641
|
+
this.isConnected = false;
|
|
3642
|
+
this.knexInstance = null;
|
|
3643
|
+
this.queriesLog = [];
|
|
3644
|
+
const all = [...this._liveKnex];
|
|
3645
|
+
this._liveKnex.clear();
|
|
3646
|
+
await Promise.all(all.map((inst) => this._destroyKnex(inst, "disconnect")));
|
|
3647
|
+
if (all.length > 0) {
|
|
3648
|
+
this.logger.debug?.(formatDbDisconnectMessage(this.config.name, this.config.connectionString));
|
|
3539
3649
|
}
|
|
3540
3650
|
}
|
|
3541
3651
|
/**
|
|
@@ -3558,27 +3668,39 @@ var Db = class _Db {
|
|
|
3558
3668
|
}
|
|
3559
3669
|
/**
|
|
3560
3670
|
* Destroy the current knex pool and open a new one. Concurrent callers share one attempt.
|
|
3671
|
+
* No-ops once disconnect() has closed the handle.
|
|
3561
3672
|
*/
|
|
3562
3673
|
async reconnect() {
|
|
3674
|
+
if (this._closed) {
|
|
3675
|
+
return;
|
|
3676
|
+
}
|
|
3677
|
+
if (this._reconnectCooldownUntil && Date.now() < this._reconnectCooldownUntil) {
|
|
3678
|
+
return;
|
|
3679
|
+
}
|
|
3563
3680
|
if (this._reconnectPromise) {
|
|
3564
3681
|
await this._reconnectPromise;
|
|
3565
3682
|
return;
|
|
3566
3683
|
}
|
|
3567
3684
|
this._reconnectPromise = (async () => {
|
|
3685
|
+
if (this._closed) return;
|
|
3568
3686
|
const old = this.knexInstance;
|
|
3569
3687
|
this.isConnected = false;
|
|
3570
3688
|
this.knexInstance = null;
|
|
3571
3689
|
this.queriesLog = [];
|
|
3572
3690
|
if (old) {
|
|
3573
|
-
|
|
3574
|
-
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
|
|
3579
|
-
|
|
3691
|
+
await this._destroyKnex(old, "destroy during reconnect");
|
|
3692
|
+
}
|
|
3693
|
+
if (this._closed) return;
|
|
3694
|
+
this._reconnectAcquireTimeoutMs = 3e3;
|
|
3695
|
+
try {
|
|
3696
|
+
await this.connect();
|
|
3697
|
+
this._reconnectCooldownUntil = 0;
|
|
3698
|
+
} catch (error) {
|
|
3699
|
+
this._reconnectCooldownUntil = Date.now() + 5e3;
|
|
3700
|
+
throw error;
|
|
3701
|
+
} finally {
|
|
3702
|
+
this._reconnectAcquireTimeoutMs = null;
|
|
3580
3703
|
}
|
|
3581
|
-
await this.connect();
|
|
3582
3704
|
})();
|
|
3583
3705
|
try {
|
|
3584
3706
|
await this._reconnectPromise;
|
|
@@ -3587,10 +3709,19 @@ var Db = class _Db {
|
|
|
3587
3709
|
}
|
|
3588
3710
|
}
|
|
3589
3711
|
async reconnectAfterConnectionError(error) {
|
|
3712
|
+
if (this._closed) {
|
|
3713
|
+
return;
|
|
3714
|
+
}
|
|
3715
|
+
if (this._reconnectCooldownUntil && Date.now() < this._reconnectCooldownUntil) {
|
|
3716
|
+
return;
|
|
3717
|
+
}
|
|
3590
3718
|
this.logger.warn?.(
|
|
3591
3719
|
`[Db] Connection lost (${this.getErrorMessage(error)}) \u2014 reconnecting\u2026`
|
|
3592
3720
|
);
|
|
3593
|
-
|
|
3721
|
+
try {
|
|
3722
|
+
await this.reconnect();
|
|
3723
|
+
} catch {
|
|
3724
|
+
}
|
|
3594
3725
|
}
|
|
3595
3726
|
/**
|
|
3596
3727
|
* Run `fn`; on a connection error, reconnect once (by default) and retry `fn`.
|
|
@@ -3603,10 +3734,13 @@ var Db = class _Db {
|
|
|
3603
3734
|
return await fn();
|
|
3604
3735
|
} catch (error) {
|
|
3605
3736
|
lastError = error;
|
|
3606
|
-
if (!this.isConnectionError(error) || attempt >= retries) {
|
|
3737
|
+
if (this._closed || !this.isConnectionError(error) || attempt >= retries) {
|
|
3607
3738
|
throw error;
|
|
3608
3739
|
}
|
|
3609
3740
|
await this.reconnectAfterConnectionError(error);
|
|
3741
|
+
if (this._closed) {
|
|
3742
|
+
throw error;
|
|
3743
|
+
}
|
|
3610
3744
|
}
|
|
3611
3745
|
}
|
|
3612
3746
|
throw lastError;
|
|
@@ -3630,10 +3764,13 @@ var Db = class _Db {
|
|
|
3630
3764
|
try {
|
|
3631
3765
|
return await protoThen.call(builder);
|
|
3632
3766
|
} catch (error) {
|
|
3633
|
-
if (!inst.isConnectionError(error)) {
|
|
3767
|
+
if (inst._closed || !inst.isConnectionError(error)) {
|
|
3634
3768
|
throw error;
|
|
3635
3769
|
}
|
|
3636
3770
|
await inst.reconnectAfterConnectionError(error);
|
|
3771
|
+
if (inst._closed || !inst.knexInstance) {
|
|
3772
|
+
throw error;
|
|
3773
|
+
}
|
|
3637
3774
|
if (typeof builder.clone === "function") {
|
|
3638
3775
|
const retry = builder.clone();
|
|
3639
3776
|
retry.client = inst.knexInstance.client;
|