@nmakarov/cli-toolkit 0.79.0 → 0.80.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 +135 -31
- package/dist/cli-runner.cjs.map +1 -1
- package/dist/cli-runner.js +135 -31
- 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 +116 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +116 -30
- package/dist/index.js.map +1 -1
- package/dist/init.cjs +30 -6
- package/dist/init.cjs.map +1 -1
- package/dist/init.js +30 -6
- 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/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -3972,7 +3972,7 @@ var CONNECTION_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
|
3972
3972
|
"57P02",
|
|
3973
3973
|
"57P03"
|
|
3974
3974
|
]);
|
|
3975
|
-
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;
|
|
3975
|
+
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;
|
|
3976
3976
|
var Db = class _Db {
|
|
3977
3977
|
static async init(context, options = {}) {
|
|
3978
3978
|
const buildConfig = async () => {
|
|
@@ -4240,6 +4240,10 @@ var Db = class _Db {
|
|
|
4240
4240
|
this.isConnected = false;
|
|
4241
4241
|
this.queriesLog = [];
|
|
4242
4242
|
this._reconnectPromise = null;
|
|
4243
|
+
this._closed = false;
|
|
4244
|
+
this._liveKnex = /* @__PURE__ */ new Set();
|
|
4245
|
+
this._reconnectCooldownUntil = 0;
|
|
4246
|
+
this._reconnectAcquireTimeoutMs = null;
|
|
4243
4247
|
this.config = {
|
|
4244
4248
|
testConnection: true,
|
|
4245
4249
|
profile: false,
|
|
@@ -4324,6 +4328,9 @@ var Db = class _Db {
|
|
|
4324
4328
|
return null;
|
|
4325
4329
|
}
|
|
4326
4330
|
async connect() {
|
|
4331
|
+
if (this._closed) {
|
|
4332
|
+
throw new ParamError("Db: Connection closed");
|
|
4333
|
+
}
|
|
4327
4334
|
if (this.isConnected && this.knexInstance) {
|
|
4328
4335
|
this.logger.warn?.("[Db] Already connected");
|
|
4329
4336
|
return;
|
|
@@ -4339,22 +4346,46 @@ var Db = class _Db {
|
|
|
4339
4346
|
connectionString: this.config.connectionString,
|
|
4340
4347
|
family: 4
|
|
4341
4348
|
};
|
|
4349
|
+
const acquireTimeout = this._reconnectAcquireTimeoutMs ?? this.config.acquireConnectionTimeout;
|
|
4342
4350
|
this.knexInstance = (0, import_knex.default)({
|
|
4343
4351
|
client,
|
|
4344
4352
|
connection: connectionConfig,
|
|
4345
4353
|
pool: this.config.pool,
|
|
4346
|
-
acquireConnectionTimeout:
|
|
4354
|
+
acquireConnectionTimeout: acquireTimeout,
|
|
4347
4355
|
...this.config.ssl && { ssl: this.config.ssl }
|
|
4348
4356
|
});
|
|
4357
|
+
this._liveKnex.add(this.knexInstance);
|
|
4358
|
+
this.knexInstance.on?.("error", (err) => {
|
|
4359
|
+
if (this._closed) return;
|
|
4360
|
+
this.logger.warn?.(
|
|
4361
|
+
`[Db] Connection error (${this.getErrorMessage(err)})`
|
|
4362
|
+
);
|
|
4363
|
+
});
|
|
4364
|
+
if (this._closed) {
|
|
4365
|
+
await this._destroyKnex(this.knexInstance, "connect aborted (closed)");
|
|
4366
|
+
this.knexInstance = null;
|
|
4367
|
+
throw new ParamError("Db: Connection closed");
|
|
4368
|
+
}
|
|
4349
4369
|
if (this.config.profile) {
|
|
4350
4370
|
this.attachProfiler();
|
|
4351
4371
|
}
|
|
4352
4372
|
if (this.config.testConnection) {
|
|
4353
4373
|
await this.testConnection();
|
|
4354
4374
|
}
|
|
4375
|
+
if (this._closed) {
|
|
4376
|
+
await this._destroyKnex(this.knexInstance, "connect aborted (closed)");
|
|
4377
|
+
this.knexInstance = null;
|
|
4378
|
+
throw new ParamError("Db: Connection closed");
|
|
4379
|
+
}
|
|
4355
4380
|
this.isConnected = true;
|
|
4356
4381
|
this.logger.debug?.(formatDbConnectMessage(this.config.name, this.config.connectionString));
|
|
4357
4382
|
} catch (error) {
|
|
4383
|
+
const failed = this.knexInstance;
|
|
4384
|
+
this.knexInstance = null;
|
|
4385
|
+
this.isConnected = false;
|
|
4386
|
+
if (failed) {
|
|
4387
|
+
await this._destroyKnex(failed, "connect failed");
|
|
4388
|
+
}
|
|
4358
4389
|
if (error instanceof ParamError) {
|
|
4359
4390
|
throw error;
|
|
4360
4391
|
}
|
|
@@ -4362,20 +4393,42 @@ var Db = class _Db {
|
|
|
4362
4393
|
throw new ParamError(`Db: Connection failed - ${errorMsg}`);
|
|
4363
4394
|
}
|
|
4364
4395
|
}
|
|
4365
|
-
|
|
4366
|
-
|
|
4367
|
-
|
|
4368
|
-
|
|
4396
|
+
/**
|
|
4397
|
+
* Destroy a knex pool without hanging exit on stuck TCP sockets (ETIMEDOUT).
|
|
4398
|
+
* @param {import("knex").Knex | null | undefined} knexInst
|
|
4399
|
+
* @param {string} [reason]
|
|
4400
|
+
* @param {number} [timeoutMs]
|
|
4401
|
+
*/
|
|
4402
|
+
async _destroyKnex(knexInst, reason = "destroy", timeoutMs = 3e3) {
|
|
4403
|
+
if (!knexInst || typeof knexInst.destroy !== "function") return;
|
|
4404
|
+
this._liveKnex.delete(knexInst);
|
|
4369
4405
|
try {
|
|
4370
|
-
await
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4406
|
+
await Promise.race([
|
|
4407
|
+
knexInst.destroy(),
|
|
4408
|
+
new Promise((_, reject) => {
|
|
4409
|
+
const t = setTimeout(
|
|
4410
|
+
() => reject(new Error(`Db: ${reason} timed out after ${timeoutMs}ms`)),
|
|
4411
|
+
timeoutMs
|
|
4412
|
+
);
|
|
4413
|
+
t.unref?.();
|
|
4414
|
+
})
|
|
4415
|
+
]);
|
|
4375
4416
|
} catch (error) {
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4417
|
+
this.logger.debug?.(
|
|
4418
|
+
`[Db] ${reason}: ${this.getErrorMessage(error)}`
|
|
4419
|
+
);
|
|
4420
|
+
}
|
|
4421
|
+
}
|
|
4422
|
+
async disconnect() {
|
|
4423
|
+
this._closed = true;
|
|
4424
|
+
this.isConnected = false;
|
|
4425
|
+
this.knexInstance = null;
|
|
4426
|
+
this.queriesLog = [];
|
|
4427
|
+
const all = [...this._liveKnex];
|
|
4428
|
+
this._liveKnex.clear();
|
|
4429
|
+
await Promise.all(all.map((inst) => this._destroyKnex(inst, "disconnect")));
|
|
4430
|
+
if (all.length > 0) {
|
|
4431
|
+
this.logger.debug?.(formatDbDisconnectMessage(this.config.name, this.config.connectionString));
|
|
4379
4432
|
}
|
|
4380
4433
|
}
|
|
4381
4434
|
/**
|
|
@@ -4398,27 +4451,39 @@ var Db = class _Db {
|
|
|
4398
4451
|
}
|
|
4399
4452
|
/**
|
|
4400
4453
|
* Destroy the current knex pool and open a new one. Concurrent callers share one attempt.
|
|
4454
|
+
* No-ops once disconnect() has closed the handle.
|
|
4401
4455
|
*/
|
|
4402
4456
|
async reconnect() {
|
|
4457
|
+
if (this._closed) {
|
|
4458
|
+
return;
|
|
4459
|
+
}
|
|
4460
|
+
if (this._reconnectCooldownUntil && Date.now() < this._reconnectCooldownUntil) {
|
|
4461
|
+
return;
|
|
4462
|
+
}
|
|
4403
4463
|
if (this._reconnectPromise) {
|
|
4404
4464
|
await this._reconnectPromise;
|
|
4405
4465
|
return;
|
|
4406
4466
|
}
|
|
4407
4467
|
this._reconnectPromise = (async () => {
|
|
4468
|
+
if (this._closed) return;
|
|
4408
4469
|
const old = this.knexInstance;
|
|
4409
4470
|
this.isConnected = false;
|
|
4410
4471
|
this.knexInstance = null;
|
|
4411
4472
|
this.queriesLog = [];
|
|
4412
4473
|
if (old) {
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
|
|
4474
|
+
await this._destroyKnex(old, "destroy during reconnect");
|
|
4475
|
+
}
|
|
4476
|
+
if (this._closed) return;
|
|
4477
|
+
this._reconnectAcquireTimeoutMs = 3e3;
|
|
4478
|
+
try {
|
|
4479
|
+
await this.connect();
|
|
4480
|
+
this._reconnectCooldownUntil = 0;
|
|
4481
|
+
} catch (error) {
|
|
4482
|
+
this._reconnectCooldownUntil = Date.now() + 5e3;
|
|
4483
|
+
throw error;
|
|
4484
|
+
} finally {
|
|
4485
|
+
this._reconnectAcquireTimeoutMs = null;
|
|
4420
4486
|
}
|
|
4421
|
-
await this.connect();
|
|
4422
4487
|
})();
|
|
4423
4488
|
try {
|
|
4424
4489
|
await this._reconnectPromise;
|
|
@@ -4427,10 +4492,19 @@ var Db = class _Db {
|
|
|
4427
4492
|
}
|
|
4428
4493
|
}
|
|
4429
4494
|
async reconnectAfterConnectionError(error) {
|
|
4495
|
+
if (this._closed) {
|
|
4496
|
+
return;
|
|
4497
|
+
}
|
|
4498
|
+
if (this._reconnectCooldownUntil && Date.now() < this._reconnectCooldownUntil) {
|
|
4499
|
+
return;
|
|
4500
|
+
}
|
|
4430
4501
|
this.logger.warn?.(
|
|
4431
4502
|
`[Db] Connection lost (${this.getErrorMessage(error)}) \u2014 reconnecting\u2026`
|
|
4432
4503
|
);
|
|
4433
|
-
|
|
4504
|
+
try {
|
|
4505
|
+
await this.reconnect();
|
|
4506
|
+
} catch {
|
|
4507
|
+
}
|
|
4434
4508
|
}
|
|
4435
4509
|
/**
|
|
4436
4510
|
* Run `fn`; on a connection error, reconnect once (by default) and retry `fn`.
|
|
@@ -4443,10 +4517,13 @@ var Db = class _Db {
|
|
|
4443
4517
|
return await fn();
|
|
4444
4518
|
} catch (error) {
|
|
4445
4519
|
lastError = error;
|
|
4446
|
-
if (!this.isConnectionError(error) || attempt >= retries) {
|
|
4520
|
+
if (this._closed || !this.isConnectionError(error) || attempt >= retries) {
|
|
4447
4521
|
throw error;
|
|
4448
4522
|
}
|
|
4449
4523
|
await this.reconnectAfterConnectionError(error);
|
|
4524
|
+
if (this._closed) {
|
|
4525
|
+
throw error;
|
|
4526
|
+
}
|
|
4450
4527
|
}
|
|
4451
4528
|
}
|
|
4452
4529
|
throw lastError;
|
|
@@ -4470,10 +4547,13 @@ var Db = class _Db {
|
|
|
4470
4547
|
try {
|
|
4471
4548
|
return await protoThen.call(builder);
|
|
4472
4549
|
} catch (error) {
|
|
4473
|
-
if (!inst.isConnectionError(error)) {
|
|
4550
|
+
if (inst._closed || !inst.isConnectionError(error)) {
|
|
4474
4551
|
throw error;
|
|
4475
4552
|
}
|
|
4476
4553
|
await inst.reconnectAfterConnectionError(error);
|
|
4554
|
+
if (inst._closed || !inst.knexInstance) {
|
|
4555
|
+
throw error;
|
|
4556
|
+
}
|
|
4477
4557
|
if (typeof builder.clone === "function") {
|
|
4478
4558
|
const retry = builder.clone();
|
|
4479
4559
|
retry.client = inst.knexInstance.client;
|
|
@@ -5495,13 +5575,14 @@ var Logger = class _Logger {
|
|
|
5495
5575
|
*/
|
|
5496
5576
|
progress(message, opts) {
|
|
5497
5577
|
const { prefix, count, total } = opts;
|
|
5498
|
-
const
|
|
5578
|
+
const displayTotal = Math.max(Number(total) || 0, Number(count) || 0);
|
|
5579
|
+
const paddedTotal = String(displayTotal).length;
|
|
5499
5580
|
const paddedCount = String(count).padStart(paddedTotal, " ");
|
|
5500
5581
|
const payload = {
|
|
5501
5582
|
level: "progress",
|
|
5502
5583
|
message,
|
|
5503
5584
|
count: paddedCount,
|
|
5504
|
-
total,
|
|
5585
|
+
total: displayTotal,
|
|
5505
5586
|
prefix
|
|
5506
5587
|
};
|
|
5507
5588
|
const key = prefix ?? "";
|
|
@@ -5518,7 +5599,7 @@ var Logger = class _Logger {
|
|
|
5518
5599
|
if (wantTimes) {
|
|
5519
5600
|
let remaining = -1;
|
|
5520
5601
|
if (itemsPerSec > 0) {
|
|
5521
|
-
remaining = (
|
|
5602
|
+
remaining = Math.max(0, (displayTotal - count) / itemsPerSec);
|
|
5522
5603
|
}
|
|
5523
5604
|
payload.elapsed = this.round(elapsedSeconds, 2);
|
|
5524
5605
|
payload.remaining = remaining >= 0 ? this.round(remaining, 2) : remaining;
|
|
@@ -5527,12 +5608,12 @@ var Logger = class _Logger {
|
|
|
5527
5608
|
payload.rate = itemsPerSec >= 0 ? this.round(itemsPerSec, 2) : itemsPerSec;
|
|
5528
5609
|
}
|
|
5529
5610
|
}
|
|
5530
|
-
if (count
|
|
5611
|
+
if (count === total) {
|
|
5531
5612
|
delete this.startTimes[key];
|
|
5532
5613
|
delete this.startCounts[key];
|
|
5533
5614
|
delete this.lastProgressTimes[key];
|
|
5534
5615
|
}
|
|
5535
|
-
if (this.shouldOutputProgress(prefix ?? "", count,
|
|
5616
|
+
if (this.shouldOutputProgress(prefix ?? "", count, displayTotal)) {
|
|
5536
5617
|
this.out(payload);
|
|
5537
5618
|
if (this.options.progressThrottle && prefix) {
|
|
5538
5619
|
this.lastProgressTimes[prefix] = Date.now();
|
|
@@ -5691,6 +5772,8 @@ function setup(opts = {}) {
|
|
|
5691
5772
|
// a quick "show me the figured params and quit" that skips the flow's
|
|
5692
5773
|
// actual work. Like --stopAfter=init, this is a hard exit(0) (registered
|
|
5693
5774
|
// cleanups are skipped); call it once components/params are resolved.
|
|
5775
|
+
_requestExitCode: null,
|
|
5776
|
+
requestExit: null,
|
|
5694
5777
|
showUsedParamsIfNeeded: () => {
|
|
5695
5778
|
const mode = params.getShowUsedParamsMode?.();
|
|
5696
5779
|
if (mode !== "top" && mode !== "stop") return;
|
|
@@ -5701,6 +5784,9 @@ function setup(opts = {}) {
|
|
|
5701
5784
|
}
|
|
5702
5785
|
}
|
|
5703
5786
|
};
|
|
5787
|
+
context.requestExit = (code = 0) => {
|
|
5788
|
+
context._requestExitCode = code;
|
|
5789
|
+
};
|
|
5704
5790
|
logger.debug("[setup] completed successfully");
|
|
5705
5791
|
return context;
|
|
5706
5792
|
}
|