@nmakarov/cli-toolkit 0.44.0 → 0.46.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 +163 -4
- package/dist/cli-runner.cjs.map +1 -1
- package/dist/cli-runner.js +163 -4
- package/dist/cli-runner.js.map +1 -1
- package/dist/db.cjs +163 -4
- package/dist/db.cjs.map +1 -1
- package/dist/db.js +163 -4
- package/dist/db.js.map +1 -1
- package/dist/http-client.cjs +1 -1
- package/dist/http-client.cjs.map +1 -1
- package/dist/http-client.js +1 -1
- package/dist/http-client.js.map +1 -1
- package/dist/index.cjs +163 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +163 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3478,10 +3478,35 @@ async function ensureSchemaEverywhere(dbs, spec, options = {}) {
|
|
|
3478
3478
|
// src/db/index.js
|
|
3479
3479
|
var KNEX_DEFAULTS = {
|
|
3480
3480
|
testConnection: true,
|
|
3481
|
-
|
|
3481
|
+
// min: 0 avoids holding idle sockets that go stale during long-running CLIs
|
|
3482
|
+
pool: { min: 0, max: 10, idleTimeoutMillis: 3e4 },
|
|
3482
3483
|
acquireConnectionTimeout: 1e4,
|
|
3483
3484
|
ssl: { rejectUnauthorized: false }
|
|
3484
3485
|
};
|
|
3486
|
+
var CONNECTION_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
3487
|
+
"ECONNRESET",
|
|
3488
|
+
"ECONNREFUSED",
|
|
3489
|
+
"EPIPE",
|
|
3490
|
+
"ETIMEDOUT",
|
|
3491
|
+
"ENOTFOUND",
|
|
3492
|
+
"EHOSTUNREACH",
|
|
3493
|
+
"ENETUNREACH",
|
|
3494
|
+
"ECONNABORTED",
|
|
3495
|
+
"CONNECTION_ENDED",
|
|
3496
|
+
"CONNECTION_CLOSED",
|
|
3497
|
+
// PostgreSQL SQLSTATE class 08xxx (connection exception) + admin shutdowns
|
|
3498
|
+
"08000",
|
|
3499
|
+
"08001",
|
|
3500
|
+
"08003",
|
|
3501
|
+
"08004",
|
|
3502
|
+
"08006",
|
|
3503
|
+
"08007",
|
|
3504
|
+
"08P01",
|
|
3505
|
+
"57P01",
|
|
3506
|
+
"57P02",
|
|
3507
|
+
"57P03"
|
|
3508
|
+
]);
|
|
3509
|
+
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|Timeout acquiring a connection/i;
|
|
3485
3510
|
var Db = class _Db {
|
|
3486
3511
|
static async init(context, options = {}) {
|
|
3487
3512
|
const buildConfig = async () => {
|
|
@@ -3729,10 +3754,11 @@ var Db = class _Db {
|
|
|
3729
3754
|
this.knexInstance = null;
|
|
3730
3755
|
this.isConnected = false;
|
|
3731
3756
|
this.queriesLog = [];
|
|
3757
|
+
this._reconnectPromise = null;
|
|
3732
3758
|
this.config = {
|
|
3733
3759
|
testConnection: true,
|
|
3734
3760
|
profile: false,
|
|
3735
|
-
pool: { min:
|
|
3761
|
+
pool: { min: 0, max: 10, idleTimeoutMillis: 3e4 },
|
|
3736
3762
|
acquireConnectionTimeout: 1e4,
|
|
3737
3763
|
ssl: { rejectUnauthorized: false },
|
|
3738
3764
|
logger: console,
|
|
@@ -3750,7 +3776,7 @@ var Db = class _Db {
|
|
|
3750
3776
|
if (!inst.knexInstance) {
|
|
3751
3777
|
throw new Error("Db: Not connected. Call connect() first.");
|
|
3752
3778
|
}
|
|
3753
|
-
return inst.knexInstance(...argumentsList);
|
|
3779
|
+
return inst.wrapQueryBuilder(inst.knexInstance(...argumentsList));
|
|
3754
3780
|
},
|
|
3755
3781
|
get: (target, prop) => {
|
|
3756
3782
|
if (prop === "_instance") {
|
|
@@ -3760,8 +3786,12 @@ var Db = class _Db {
|
|
|
3760
3786
|
const ownMethods = [
|
|
3761
3787
|
"connect",
|
|
3762
3788
|
"disconnect",
|
|
3789
|
+
"reconnect",
|
|
3763
3790
|
"testConnection",
|
|
3764
3791
|
"tableExists",
|
|
3792
|
+
"raw",
|
|
3793
|
+
"withConnectionRetry",
|
|
3794
|
+
"isConnectionError",
|
|
3765
3795
|
"getQueryLog",
|
|
3766
3796
|
"getKnex",
|
|
3767
3797
|
"isConnectedToDb",
|
|
@@ -3781,6 +3811,9 @@ var Db = class _Db {
|
|
|
3781
3811
|
if (inst.knexInstance) {
|
|
3782
3812
|
const knexProp = inst.knexInstance[prop];
|
|
3783
3813
|
if (typeof knexProp === "function") {
|
|
3814
|
+
if (prop === "raw") {
|
|
3815
|
+
return (...args) => inst.raw(...args);
|
|
3816
|
+
}
|
|
3784
3817
|
return knexProp.bind(inst.knexInstance);
|
|
3785
3818
|
}
|
|
3786
3819
|
return knexProp;
|
|
@@ -3860,6 +3893,130 @@ var Db = class _Db {
|
|
|
3860
3893
|
throw error;
|
|
3861
3894
|
}
|
|
3862
3895
|
}
|
|
3896
|
+
/**
|
|
3897
|
+
* True when the error indicates a dead socket / pool that a fresh connect may fix.
|
|
3898
|
+
* Safe to call as `Db.prototype.isConnectionError(err)` or via a connected handle.
|
|
3899
|
+
*/
|
|
3900
|
+
isConnectionError(error) {
|
|
3901
|
+
if (!error) {
|
|
3902
|
+
return false;
|
|
3903
|
+
}
|
|
3904
|
+
if (error instanceof AggregateError && Array.isArray(error.errors)) {
|
|
3905
|
+
return error.errors.some((e) => this.isConnectionError(e));
|
|
3906
|
+
}
|
|
3907
|
+
const code = error.code ?? error.errno;
|
|
3908
|
+
if (code != null && CONNECTION_ERROR_CODES.has(String(code))) {
|
|
3909
|
+
return true;
|
|
3910
|
+
}
|
|
3911
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
3912
|
+
return CONNECTION_ERROR_MESSAGE_RE.test(msg);
|
|
3913
|
+
}
|
|
3914
|
+
/**
|
|
3915
|
+
* Destroy the current knex pool and open a new one. Concurrent callers share one attempt.
|
|
3916
|
+
*/
|
|
3917
|
+
async reconnect() {
|
|
3918
|
+
if (this._reconnectPromise) {
|
|
3919
|
+
await this._reconnectPromise;
|
|
3920
|
+
return;
|
|
3921
|
+
}
|
|
3922
|
+
this._reconnectPromise = (async () => {
|
|
3923
|
+
const old = this.knexInstance;
|
|
3924
|
+
this.isConnected = false;
|
|
3925
|
+
this.knexInstance = null;
|
|
3926
|
+
this.queriesLog = [];
|
|
3927
|
+
if (old) {
|
|
3928
|
+
try {
|
|
3929
|
+
await old.destroy();
|
|
3930
|
+
} catch (error) {
|
|
3931
|
+
this.logger.debug?.(
|
|
3932
|
+
`[Db] destroy during reconnect: ${this.getErrorMessage(error)}`
|
|
3933
|
+
);
|
|
3934
|
+
}
|
|
3935
|
+
}
|
|
3936
|
+
await this.connect();
|
|
3937
|
+
})();
|
|
3938
|
+
try {
|
|
3939
|
+
await this._reconnectPromise;
|
|
3940
|
+
} finally {
|
|
3941
|
+
this._reconnectPromise = null;
|
|
3942
|
+
}
|
|
3943
|
+
}
|
|
3944
|
+
async reconnectAfterConnectionError(error) {
|
|
3945
|
+
this.logger.warn?.(
|
|
3946
|
+
`[Db] Connection lost (${this.getErrorMessage(error)}) \u2014 reconnecting\u2026`
|
|
3947
|
+
);
|
|
3948
|
+
await this.reconnect();
|
|
3949
|
+
}
|
|
3950
|
+
/**
|
|
3951
|
+
* Run `fn`; on a connection error, reconnect once (by default) and retry `fn`.
|
|
3952
|
+
* `fn` should look up `this.knexInstance` each call so the retry uses the new client.
|
|
3953
|
+
*/
|
|
3954
|
+
async withConnectionRetry(fn, { retries = 1 } = {}) {
|
|
3955
|
+
let lastError;
|
|
3956
|
+
for (let attempt = 0; attempt <= retries; attempt++) {
|
|
3957
|
+
try {
|
|
3958
|
+
return await fn();
|
|
3959
|
+
} catch (error) {
|
|
3960
|
+
lastError = error;
|
|
3961
|
+
if (!this.isConnectionError(error) || attempt >= retries) {
|
|
3962
|
+
throw error;
|
|
3963
|
+
}
|
|
3964
|
+
await this.reconnectAfterConnectionError(error);
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3967
|
+
throw lastError;
|
|
3968
|
+
}
|
|
3969
|
+
/**
|
|
3970
|
+
* Wrap a knex QueryBuilder / Raw so that awaiting it retries once after reconnect
|
|
3971
|
+
* when the first attempt dies with a connection error.
|
|
3972
|
+
*/
|
|
3973
|
+
wrapQueryBuilder(builder) {
|
|
3974
|
+
if (!builder || typeof builder.then !== "function" || builder.__dbReconnectWrapped) {
|
|
3975
|
+
return builder;
|
|
3976
|
+
}
|
|
3977
|
+
builder.__dbReconnectWrapped = true;
|
|
3978
|
+
const inst = this;
|
|
3979
|
+
const protoThen = Object.getPrototypeOf(builder)?.then;
|
|
3980
|
+
if (typeof protoThen !== "function") {
|
|
3981
|
+
return builder;
|
|
3982
|
+
}
|
|
3983
|
+
builder.then = function(onFulfilled, onRejected) {
|
|
3984
|
+
const run2 = async () => {
|
|
3985
|
+
try {
|
|
3986
|
+
return await protoThen.call(builder);
|
|
3987
|
+
} catch (error) {
|
|
3988
|
+
if (!inst.isConnectionError(error)) {
|
|
3989
|
+
throw error;
|
|
3990
|
+
}
|
|
3991
|
+
await inst.reconnectAfterConnectionError(error);
|
|
3992
|
+
if (typeof builder.clone === "function") {
|
|
3993
|
+
const retry = builder.clone();
|
|
3994
|
+
retry.client = inst.knexInstance.client;
|
|
3995
|
+
return await protoThen.call(retry);
|
|
3996
|
+
}
|
|
3997
|
+
if (typeof builder.toSQL === "function" && inst.knexInstance) {
|
|
3998
|
+
const sql = builder.toSQL();
|
|
3999
|
+
const statements = Array.isArray(sql) ? sql : [sql];
|
|
4000
|
+
let last;
|
|
4001
|
+
for (const stmt of statements) {
|
|
4002
|
+
last = await inst.knexInstance.raw(stmt.sql, stmt.bindings);
|
|
4003
|
+
}
|
|
4004
|
+
return last;
|
|
4005
|
+
}
|
|
4006
|
+
throw error;
|
|
4007
|
+
}
|
|
4008
|
+
};
|
|
4009
|
+
return run2().then(onFulfilled, onRejected);
|
|
4010
|
+
};
|
|
4011
|
+
return builder;
|
|
4012
|
+
}
|
|
4013
|
+
/** knex.raw with auto-reconnect on dead connections. */
|
|
4014
|
+
raw(...args) {
|
|
4015
|
+
if (!this.knexInstance) {
|
|
4016
|
+
throw new Error("Db: Not connected. Call connect() first.");
|
|
4017
|
+
}
|
|
4018
|
+
return this.wrapQueryBuilder(this.knexInstance.raw(...args));
|
|
4019
|
+
}
|
|
3863
4020
|
getErrorMessage(error) {
|
|
3864
4021
|
if (error instanceof AggregateError) {
|
|
3865
4022
|
const errors = error.errors || [];
|
|
@@ -4030,7 +4187,9 @@ var Db = class _Db {
|
|
|
4030
4187
|
throw new Error("Db: Not connected. Call connect() first.");
|
|
4031
4188
|
}
|
|
4032
4189
|
try {
|
|
4033
|
-
return await this.
|
|
4190
|
+
return await this.withConnectionRetry(
|
|
4191
|
+
() => this.knexInstance.schema.hasTable(tableName)
|
|
4192
|
+
);
|
|
4034
4193
|
} catch (error) {
|
|
4035
4194
|
this.logger.error?.(`[Db] Error checking table existence: ${error.message}`);
|
|
4036
4195
|
throw error;
|