@carllee1983/dbcli 1.2.1 → 1.4.1
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/CHANGELOG.md +27 -0
- package/README.md +24 -18
- package/README.zh-TW.md +60 -41
- package/assets/SKILL.md +8 -2
- package/dist/cli.mjs +2355 -392
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -6124,14 +6124,8 @@ var init_validation = __esm(() => {
|
|
|
6124
6124
|
EnvRefSchema = exports_external.object({
|
|
6125
6125
|
$env: exports_external.string()
|
|
6126
6126
|
}).strict();
|
|
6127
|
-
StringOrEnvRef = exports_external.union([
|
|
6128
|
-
|
|
6129
|
-
EnvRefSchema
|
|
6130
|
-
]);
|
|
6131
|
-
NumberOrEnvRef = exports_external.union([
|
|
6132
|
-
exports_external.number().int().min(1).max(65535),
|
|
6133
|
-
EnvRefSchema
|
|
6134
|
-
]);
|
|
6127
|
+
StringOrEnvRef = exports_external.union([exports_external.string().min(1), EnvRefSchema]);
|
|
6128
|
+
NumberOrEnvRef = exports_external.union([exports_external.number().int().min(1).max(65535), EnvRefSchema]);
|
|
6135
6129
|
ConnectionConfigSchema = exports_external.object({
|
|
6136
6130
|
system: exports_external.enum(["postgresql", "mysql", "mariadb"]),
|
|
6137
6131
|
host: StringOrEnvRef,
|
|
@@ -6143,7 +6137,9 @@ var init_validation = __esm(() => {
|
|
|
6143
6137
|
PermissionSchema = exports_external.enum(["query-only", "read-write", "data-admin", "admin"]).default("query-only");
|
|
6144
6138
|
MetadataSchema = exports_external.object({
|
|
6145
6139
|
createdAt: exports_external.string().datetime().optional(),
|
|
6146
|
-
version: exports_external.string().default("1.0")
|
|
6140
|
+
version: exports_external.string().default("1.0"),
|
|
6141
|
+
schemaLastUpdated: exports_external.string().datetime().optional(),
|
|
6142
|
+
schemaTableCount: exports_external.number().int().nonnegative().optional()
|
|
6147
6143
|
}).optional().default({});
|
|
6148
6144
|
BlacklistConfigSchema = exports_external.object({
|
|
6149
6145
|
tables: exports_external.array(exports_external.string()).default([]),
|
|
@@ -6163,11 +6159,1458 @@ var init_validation = __esm(() => {
|
|
|
6163
6159
|
DbcliConfigV2Schema = exports_external.object({
|
|
6164
6160
|
version: exports_external.literal(2),
|
|
6165
6161
|
default: exports_external.string().min(1),
|
|
6166
|
-
connections: exports_external.record(NamedConnectionSchema).refine((conns) => Object.keys(conns).length > 0, {
|
|
6162
|
+
connections: exports_external.record(NamedConnectionSchema).refine((conns) => Object.keys(conns).length > 0, {
|
|
6163
|
+
message: "At least one connection is required"
|
|
6164
|
+
}),
|
|
6167
6165
|
schema: exports_external.record(exports_external.any()).optional().default({}),
|
|
6166
|
+
schemas: exports_external.record(exports_external.record(exports_external.any())).optional().default({}),
|
|
6168
6167
|
metadata: MetadataSchema,
|
|
6169
6168
|
blacklist: BlacklistConfigSchema
|
|
6170
|
-
}).refine((config) => (config.default in config.connections), {
|
|
6169
|
+
}).refine((config) => (config.default in config.connections), {
|
|
6170
|
+
message: "Default connection must exist in connections",
|
|
6171
|
+
path: ["default"]
|
|
6172
|
+
});
|
|
6173
|
+
});
|
|
6174
|
+
|
|
6175
|
+
// node_modules/lru-cache/dist/esm/index.js
|
|
6176
|
+
class Stack {
|
|
6177
|
+
heap;
|
|
6178
|
+
length;
|
|
6179
|
+
static #constructing = false;
|
|
6180
|
+
static create(max) {
|
|
6181
|
+
const HeapCls = getUintArray(max);
|
|
6182
|
+
if (!HeapCls)
|
|
6183
|
+
return [];
|
|
6184
|
+
Stack.#constructing = true;
|
|
6185
|
+
const s = new Stack(max, HeapCls);
|
|
6186
|
+
Stack.#constructing = false;
|
|
6187
|
+
return s;
|
|
6188
|
+
}
|
|
6189
|
+
constructor(max, HeapCls) {
|
|
6190
|
+
if (!Stack.#constructing) {
|
|
6191
|
+
throw new TypeError("instantiate Stack using Stack.create(n)");
|
|
6192
|
+
}
|
|
6193
|
+
this.heap = new HeapCls(max);
|
|
6194
|
+
this.length = 0;
|
|
6195
|
+
}
|
|
6196
|
+
push(n) {
|
|
6197
|
+
this.heap[this.length++] = n;
|
|
6198
|
+
}
|
|
6199
|
+
pop() {
|
|
6200
|
+
return this.heap[--this.length];
|
|
6201
|
+
}
|
|
6202
|
+
}
|
|
6203
|
+
var perf, warned, PROCESS, emitWarning = (msg, type, code, fn) => {
|
|
6204
|
+
typeof PROCESS.emitWarning === "function" ? PROCESS.emitWarning(msg, type, code, fn) : console.error(`[${code}] ${type}: ${msg}`);
|
|
6205
|
+
}, AC, AS, shouldWarn = (code) => !warned.has(code), TYPE, isPosInt = (n) => n && n === Math.floor(n) && n > 0 && isFinite(n), getUintArray = (max) => !isPosInt(max) ? null : max <= Math.pow(2, 8) ? Uint8Array : max <= Math.pow(2, 16) ? Uint16Array : max <= Math.pow(2, 32) ? Uint32Array : max <= Number.MAX_SAFE_INTEGER ? ZeroArray : null, ZeroArray, LRUCache;
|
|
6206
|
+
var init_esm = __esm(() => {
|
|
6207
|
+
perf = typeof performance === "object" && performance && typeof performance.now === "function" ? performance : Date;
|
|
6208
|
+
warned = new Set;
|
|
6209
|
+
PROCESS = typeof process === "object" && !!process ? process : {};
|
|
6210
|
+
AC = globalThis.AbortController;
|
|
6211
|
+
AS = globalThis.AbortSignal;
|
|
6212
|
+
if (typeof AC === "undefined") {
|
|
6213
|
+
AS = class AbortSignal2 {
|
|
6214
|
+
onabort;
|
|
6215
|
+
_onabort = [];
|
|
6216
|
+
reason;
|
|
6217
|
+
aborted = false;
|
|
6218
|
+
addEventListener(_, fn) {
|
|
6219
|
+
this._onabort.push(fn);
|
|
6220
|
+
}
|
|
6221
|
+
};
|
|
6222
|
+
AC = class AbortController2 {
|
|
6223
|
+
constructor() {
|
|
6224
|
+
warnACPolyfill();
|
|
6225
|
+
}
|
|
6226
|
+
signal = new AS;
|
|
6227
|
+
abort(reason) {
|
|
6228
|
+
if (this.signal.aborted)
|
|
6229
|
+
return;
|
|
6230
|
+
this.signal.reason = reason;
|
|
6231
|
+
this.signal.aborted = true;
|
|
6232
|
+
for (const fn of this.signal._onabort) {
|
|
6233
|
+
fn(reason);
|
|
6234
|
+
}
|
|
6235
|
+
this.signal.onabort?.(reason);
|
|
6236
|
+
}
|
|
6237
|
+
};
|
|
6238
|
+
let printACPolyfillWarning = PROCESS.env?.LRU_CACHE_IGNORE_AC_WARNING !== "1";
|
|
6239
|
+
const warnACPolyfill = () => {
|
|
6240
|
+
if (!printACPolyfillWarning)
|
|
6241
|
+
return;
|
|
6242
|
+
printACPolyfillWarning = false;
|
|
6243
|
+
emitWarning("AbortController is not defined. If using lru-cache in " + "node 14, load an AbortController polyfill from the " + "`node-abort-controller` package. A minimal polyfill is " + "provided for use by LRUCache.fetch(), but it should not be " + "relied upon in other contexts (eg, passing it to other APIs that " + "use AbortController/AbortSignal might have undesirable effects). " + "You may disable this with LRU_CACHE_IGNORE_AC_WARNING=1 in the env.", "NO_ABORT_CONTROLLER", "ENOTSUP", warnACPolyfill);
|
|
6244
|
+
};
|
|
6245
|
+
}
|
|
6246
|
+
TYPE = Symbol("type");
|
|
6247
|
+
ZeroArray = class ZeroArray extends Array {
|
|
6248
|
+
constructor(size) {
|
|
6249
|
+
super(size);
|
|
6250
|
+
this.fill(0);
|
|
6251
|
+
}
|
|
6252
|
+
};
|
|
6253
|
+
LRUCache = class LRUCache {
|
|
6254
|
+
#max;
|
|
6255
|
+
#maxSize;
|
|
6256
|
+
#dispose;
|
|
6257
|
+
#disposeAfter;
|
|
6258
|
+
#fetchMethod;
|
|
6259
|
+
#memoMethod;
|
|
6260
|
+
ttl;
|
|
6261
|
+
ttlResolution;
|
|
6262
|
+
ttlAutopurge;
|
|
6263
|
+
updateAgeOnGet;
|
|
6264
|
+
updateAgeOnHas;
|
|
6265
|
+
allowStale;
|
|
6266
|
+
noDisposeOnSet;
|
|
6267
|
+
noUpdateTTL;
|
|
6268
|
+
maxEntrySize;
|
|
6269
|
+
sizeCalculation;
|
|
6270
|
+
noDeleteOnFetchRejection;
|
|
6271
|
+
noDeleteOnStaleGet;
|
|
6272
|
+
allowStaleOnFetchAbort;
|
|
6273
|
+
allowStaleOnFetchRejection;
|
|
6274
|
+
ignoreFetchAbort;
|
|
6275
|
+
#size;
|
|
6276
|
+
#calculatedSize;
|
|
6277
|
+
#keyMap;
|
|
6278
|
+
#keyList;
|
|
6279
|
+
#valList;
|
|
6280
|
+
#next;
|
|
6281
|
+
#prev;
|
|
6282
|
+
#head;
|
|
6283
|
+
#tail;
|
|
6284
|
+
#free;
|
|
6285
|
+
#disposed;
|
|
6286
|
+
#sizes;
|
|
6287
|
+
#starts;
|
|
6288
|
+
#ttls;
|
|
6289
|
+
#hasDispose;
|
|
6290
|
+
#hasFetchMethod;
|
|
6291
|
+
#hasDisposeAfter;
|
|
6292
|
+
static unsafeExposeInternals(c) {
|
|
6293
|
+
return {
|
|
6294
|
+
starts: c.#starts,
|
|
6295
|
+
ttls: c.#ttls,
|
|
6296
|
+
sizes: c.#sizes,
|
|
6297
|
+
keyMap: c.#keyMap,
|
|
6298
|
+
keyList: c.#keyList,
|
|
6299
|
+
valList: c.#valList,
|
|
6300
|
+
next: c.#next,
|
|
6301
|
+
prev: c.#prev,
|
|
6302
|
+
get head() {
|
|
6303
|
+
return c.#head;
|
|
6304
|
+
},
|
|
6305
|
+
get tail() {
|
|
6306
|
+
return c.#tail;
|
|
6307
|
+
},
|
|
6308
|
+
free: c.#free,
|
|
6309
|
+
isBackgroundFetch: (p) => c.#isBackgroundFetch(p),
|
|
6310
|
+
backgroundFetch: (k, index, options, context) => c.#backgroundFetch(k, index, options, context),
|
|
6311
|
+
moveToTail: (index) => c.#moveToTail(index),
|
|
6312
|
+
indexes: (options) => c.#indexes(options),
|
|
6313
|
+
rindexes: (options) => c.#rindexes(options),
|
|
6314
|
+
isStale: (index) => c.#isStale(index)
|
|
6315
|
+
};
|
|
6316
|
+
}
|
|
6317
|
+
get max() {
|
|
6318
|
+
return this.#max;
|
|
6319
|
+
}
|
|
6320
|
+
get maxSize() {
|
|
6321
|
+
return this.#maxSize;
|
|
6322
|
+
}
|
|
6323
|
+
get calculatedSize() {
|
|
6324
|
+
return this.#calculatedSize;
|
|
6325
|
+
}
|
|
6326
|
+
get size() {
|
|
6327
|
+
return this.#size;
|
|
6328
|
+
}
|
|
6329
|
+
get fetchMethod() {
|
|
6330
|
+
return this.#fetchMethod;
|
|
6331
|
+
}
|
|
6332
|
+
get memoMethod() {
|
|
6333
|
+
return this.#memoMethod;
|
|
6334
|
+
}
|
|
6335
|
+
get dispose() {
|
|
6336
|
+
return this.#dispose;
|
|
6337
|
+
}
|
|
6338
|
+
get disposeAfter() {
|
|
6339
|
+
return this.#disposeAfter;
|
|
6340
|
+
}
|
|
6341
|
+
constructor(options) {
|
|
6342
|
+
const { max = 0, ttl, ttlResolution = 1, ttlAutopurge, updateAgeOnGet, updateAgeOnHas, allowStale, dispose, disposeAfter, noDisposeOnSet, noUpdateTTL, maxSize = 0, maxEntrySize = 0, sizeCalculation, fetchMethod, memoMethod, noDeleteOnFetchRejection, noDeleteOnStaleGet, allowStaleOnFetchRejection, allowStaleOnFetchAbort, ignoreFetchAbort } = options;
|
|
6343
|
+
if (max !== 0 && !isPosInt(max)) {
|
|
6344
|
+
throw new TypeError("max option must be a nonnegative integer");
|
|
6345
|
+
}
|
|
6346
|
+
const UintArray = max ? getUintArray(max) : Array;
|
|
6347
|
+
if (!UintArray) {
|
|
6348
|
+
throw new Error("invalid max value: " + max);
|
|
6349
|
+
}
|
|
6350
|
+
this.#max = max;
|
|
6351
|
+
this.#maxSize = maxSize;
|
|
6352
|
+
this.maxEntrySize = maxEntrySize || this.#maxSize;
|
|
6353
|
+
this.sizeCalculation = sizeCalculation;
|
|
6354
|
+
if (this.sizeCalculation) {
|
|
6355
|
+
if (!this.#maxSize && !this.maxEntrySize) {
|
|
6356
|
+
throw new TypeError("cannot set sizeCalculation without setting maxSize or maxEntrySize");
|
|
6357
|
+
}
|
|
6358
|
+
if (typeof this.sizeCalculation !== "function") {
|
|
6359
|
+
throw new TypeError("sizeCalculation set to non-function");
|
|
6360
|
+
}
|
|
6361
|
+
}
|
|
6362
|
+
if (memoMethod !== undefined && typeof memoMethod !== "function") {
|
|
6363
|
+
throw new TypeError("memoMethod must be a function if defined");
|
|
6364
|
+
}
|
|
6365
|
+
this.#memoMethod = memoMethod;
|
|
6366
|
+
if (fetchMethod !== undefined && typeof fetchMethod !== "function") {
|
|
6367
|
+
throw new TypeError("fetchMethod must be a function if specified");
|
|
6368
|
+
}
|
|
6369
|
+
this.#fetchMethod = fetchMethod;
|
|
6370
|
+
this.#hasFetchMethod = !!fetchMethod;
|
|
6371
|
+
this.#keyMap = new Map;
|
|
6372
|
+
this.#keyList = new Array(max).fill(undefined);
|
|
6373
|
+
this.#valList = new Array(max).fill(undefined);
|
|
6374
|
+
this.#next = new UintArray(max);
|
|
6375
|
+
this.#prev = new UintArray(max);
|
|
6376
|
+
this.#head = 0;
|
|
6377
|
+
this.#tail = 0;
|
|
6378
|
+
this.#free = Stack.create(max);
|
|
6379
|
+
this.#size = 0;
|
|
6380
|
+
this.#calculatedSize = 0;
|
|
6381
|
+
if (typeof dispose === "function") {
|
|
6382
|
+
this.#dispose = dispose;
|
|
6383
|
+
}
|
|
6384
|
+
if (typeof disposeAfter === "function") {
|
|
6385
|
+
this.#disposeAfter = disposeAfter;
|
|
6386
|
+
this.#disposed = [];
|
|
6387
|
+
} else {
|
|
6388
|
+
this.#disposeAfter = undefined;
|
|
6389
|
+
this.#disposed = undefined;
|
|
6390
|
+
}
|
|
6391
|
+
this.#hasDispose = !!this.#dispose;
|
|
6392
|
+
this.#hasDisposeAfter = !!this.#disposeAfter;
|
|
6393
|
+
this.noDisposeOnSet = !!noDisposeOnSet;
|
|
6394
|
+
this.noUpdateTTL = !!noUpdateTTL;
|
|
6395
|
+
this.noDeleteOnFetchRejection = !!noDeleteOnFetchRejection;
|
|
6396
|
+
this.allowStaleOnFetchRejection = !!allowStaleOnFetchRejection;
|
|
6397
|
+
this.allowStaleOnFetchAbort = !!allowStaleOnFetchAbort;
|
|
6398
|
+
this.ignoreFetchAbort = !!ignoreFetchAbort;
|
|
6399
|
+
if (this.maxEntrySize !== 0) {
|
|
6400
|
+
if (this.#maxSize !== 0) {
|
|
6401
|
+
if (!isPosInt(this.#maxSize)) {
|
|
6402
|
+
throw new TypeError("maxSize must be a positive integer if specified");
|
|
6403
|
+
}
|
|
6404
|
+
}
|
|
6405
|
+
if (!isPosInt(this.maxEntrySize)) {
|
|
6406
|
+
throw new TypeError("maxEntrySize must be a positive integer if specified");
|
|
6407
|
+
}
|
|
6408
|
+
this.#initializeSizeTracking();
|
|
6409
|
+
}
|
|
6410
|
+
this.allowStale = !!allowStale;
|
|
6411
|
+
this.noDeleteOnStaleGet = !!noDeleteOnStaleGet;
|
|
6412
|
+
this.updateAgeOnGet = !!updateAgeOnGet;
|
|
6413
|
+
this.updateAgeOnHas = !!updateAgeOnHas;
|
|
6414
|
+
this.ttlResolution = isPosInt(ttlResolution) || ttlResolution === 0 ? ttlResolution : 1;
|
|
6415
|
+
this.ttlAutopurge = !!ttlAutopurge;
|
|
6416
|
+
this.ttl = ttl || 0;
|
|
6417
|
+
if (this.ttl) {
|
|
6418
|
+
if (!isPosInt(this.ttl)) {
|
|
6419
|
+
throw new TypeError("ttl must be a positive integer if specified");
|
|
6420
|
+
}
|
|
6421
|
+
this.#initializeTTLTracking();
|
|
6422
|
+
}
|
|
6423
|
+
if (this.#max === 0 && this.ttl === 0 && this.#maxSize === 0) {
|
|
6424
|
+
throw new TypeError("At least one of max, maxSize, or ttl is required");
|
|
6425
|
+
}
|
|
6426
|
+
if (!this.ttlAutopurge && !this.#max && !this.#maxSize) {
|
|
6427
|
+
const code = "LRU_CACHE_UNBOUNDED";
|
|
6428
|
+
if (shouldWarn(code)) {
|
|
6429
|
+
warned.add(code);
|
|
6430
|
+
const msg = "TTL caching without ttlAutopurge, max, or maxSize can " + "result in unbounded memory consumption.";
|
|
6431
|
+
emitWarning(msg, "UnboundedCacheWarning", code, LRUCache);
|
|
6432
|
+
}
|
|
6433
|
+
}
|
|
6434
|
+
}
|
|
6435
|
+
getRemainingTTL(key) {
|
|
6436
|
+
return this.#keyMap.has(key) ? Infinity : 0;
|
|
6437
|
+
}
|
|
6438
|
+
#initializeTTLTracking() {
|
|
6439
|
+
const ttls = new ZeroArray(this.#max);
|
|
6440
|
+
const starts = new ZeroArray(this.#max);
|
|
6441
|
+
this.#ttls = ttls;
|
|
6442
|
+
this.#starts = starts;
|
|
6443
|
+
this.#setItemTTL = (index, ttl, start = perf.now()) => {
|
|
6444
|
+
starts[index] = ttl !== 0 ? start : 0;
|
|
6445
|
+
ttls[index] = ttl;
|
|
6446
|
+
if (ttl !== 0 && this.ttlAutopurge) {
|
|
6447
|
+
const t2 = setTimeout(() => {
|
|
6448
|
+
if (this.#isStale(index)) {
|
|
6449
|
+
this.#delete(this.#keyList[index], "expire");
|
|
6450
|
+
}
|
|
6451
|
+
}, ttl + 1);
|
|
6452
|
+
if (t2.unref) {
|
|
6453
|
+
t2.unref();
|
|
6454
|
+
}
|
|
6455
|
+
}
|
|
6456
|
+
};
|
|
6457
|
+
this.#updateItemAge = (index) => {
|
|
6458
|
+
starts[index] = ttls[index] !== 0 ? perf.now() : 0;
|
|
6459
|
+
};
|
|
6460
|
+
this.#statusTTL = (status, index) => {
|
|
6461
|
+
if (ttls[index]) {
|
|
6462
|
+
const ttl = ttls[index];
|
|
6463
|
+
const start = starts[index];
|
|
6464
|
+
if (!ttl || !start)
|
|
6465
|
+
return;
|
|
6466
|
+
status.ttl = ttl;
|
|
6467
|
+
status.start = start;
|
|
6468
|
+
status.now = cachedNow || getNow();
|
|
6469
|
+
const age = status.now - start;
|
|
6470
|
+
status.remainingTTL = ttl - age;
|
|
6471
|
+
}
|
|
6472
|
+
};
|
|
6473
|
+
let cachedNow = 0;
|
|
6474
|
+
const getNow = () => {
|
|
6475
|
+
const n = perf.now();
|
|
6476
|
+
if (this.ttlResolution > 0) {
|
|
6477
|
+
cachedNow = n;
|
|
6478
|
+
const t2 = setTimeout(() => cachedNow = 0, this.ttlResolution);
|
|
6479
|
+
if (t2.unref) {
|
|
6480
|
+
t2.unref();
|
|
6481
|
+
}
|
|
6482
|
+
}
|
|
6483
|
+
return n;
|
|
6484
|
+
};
|
|
6485
|
+
this.getRemainingTTL = (key) => {
|
|
6486
|
+
const index = this.#keyMap.get(key);
|
|
6487
|
+
if (index === undefined) {
|
|
6488
|
+
return 0;
|
|
6489
|
+
}
|
|
6490
|
+
const ttl = ttls[index];
|
|
6491
|
+
const start = starts[index];
|
|
6492
|
+
if (!ttl || !start) {
|
|
6493
|
+
return Infinity;
|
|
6494
|
+
}
|
|
6495
|
+
const age = (cachedNow || getNow()) - start;
|
|
6496
|
+
return ttl - age;
|
|
6497
|
+
};
|
|
6498
|
+
this.#isStale = (index) => {
|
|
6499
|
+
const s = starts[index];
|
|
6500
|
+
const t2 = ttls[index];
|
|
6501
|
+
return !!t2 && !!s && (cachedNow || getNow()) - s > t2;
|
|
6502
|
+
};
|
|
6503
|
+
}
|
|
6504
|
+
#updateItemAge = () => {};
|
|
6505
|
+
#statusTTL = () => {};
|
|
6506
|
+
#setItemTTL = () => {};
|
|
6507
|
+
#isStale = () => false;
|
|
6508
|
+
#initializeSizeTracking() {
|
|
6509
|
+
const sizes = new ZeroArray(this.#max);
|
|
6510
|
+
this.#calculatedSize = 0;
|
|
6511
|
+
this.#sizes = sizes;
|
|
6512
|
+
this.#removeItemSize = (index) => {
|
|
6513
|
+
this.#calculatedSize -= sizes[index];
|
|
6514
|
+
sizes[index] = 0;
|
|
6515
|
+
};
|
|
6516
|
+
this.#requireSize = (k, v, size, sizeCalculation) => {
|
|
6517
|
+
if (this.#isBackgroundFetch(v)) {
|
|
6518
|
+
return 0;
|
|
6519
|
+
}
|
|
6520
|
+
if (!isPosInt(size)) {
|
|
6521
|
+
if (sizeCalculation) {
|
|
6522
|
+
if (typeof sizeCalculation !== "function") {
|
|
6523
|
+
throw new TypeError("sizeCalculation must be a function");
|
|
6524
|
+
}
|
|
6525
|
+
size = sizeCalculation(v, k);
|
|
6526
|
+
if (!isPosInt(size)) {
|
|
6527
|
+
throw new TypeError("sizeCalculation return invalid (expect positive integer)");
|
|
6528
|
+
}
|
|
6529
|
+
} else {
|
|
6530
|
+
throw new TypeError("invalid size value (must be positive integer). " + "When maxSize or maxEntrySize is used, sizeCalculation " + "or size must be set.");
|
|
6531
|
+
}
|
|
6532
|
+
}
|
|
6533
|
+
return size;
|
|
6534
|
+
};
|
|
6535
|
+
this.#addItemSize = (index, size, status) => {
|
|
6536
|
+
sizes[index] = size;
|
|
6537
|
+
if (this.#maxSize) {
|
|
6538
|
+
const maxSize = this.#maxSize - sizes[index];
|
|
6539
|
+
while (this.#calculatedSize > maxSize) {
|
|
6540
|
+
this.#evict(true);
|
|
6541
|
+
}
|
|
6542
|
+
}
|
|
6543
|
+
this.#calculatedSize += sizes[index];
|
|
6544
|
+
if (status) {
|
|
6545
|
+
status.entrySize = size;
|
|
6546
|
+
status.totalCalculatedSize = this.#calculatedSize;
|
|
6547
|
+
}
|
|
6548
|
+
};
|
|
6549
|
+
}
|
|
6550
|
+
#removeItemSize = (_i) => {};
|
|
6551
|
+
#addItemSize = (_i, _s, _st) => {};
|
|
6552
|
+
#requireSize = (_k, _v, size, sizeCalculation) => {
|
|
6553
|
+
if (size || sizeCalculation) {
|
|
6554
|
+
throw new TypeError("cannot set size without setting maxSize or maxEntrySize on cache");
|
|
6555
|
+
}
|
|
6556
|
+
return 0;
|
|
6557
|
+
};
|
|
6558
|
+
*#indexes({ allowStale = this.allowStale } = {}) {
|
|
6559
|
+
if (this.#size) {
|
|
6560
|
+
for (let i = this.#tail;; ) {
|
|
6561
|
+
if (!this.#isValidIndex(i)) {
|
|
6562
|
+
break;
|
|
6563
|
+
}
|
|
6564
|
+
if (allowStale || !this.#isStale(i)) {
|
|
6565
|
+
yield i;
|
|
6566
|
+
}
|
|
6567
|
+
if (i === this.#head) {
|
|
6568
|
+
break;
|
|
6569
|
+
} else {
|
|
6570
|
+
i = this.#prev[i];
|
|
6571
|
+
}
|
|
6572
|
+
}
|
|
6573
|
+
}
|
|
6574
|
+
}
|
|
6575
|
+
*#rindexes({ allowStale = this.allowStale } = {}) {
|
|
6576
|
+
if (this.#size) {
|
|
6577
|
+
for (let i = this.#head;; ) {
|
|
6578
|
+
if (!this.#isValidIndex(i)) {
|
|
6579
|
+
break;
|
|
6580
|
+
}
|
|
6581
|
+
if (allowStale || !this.#isStale(i)) {
|
|
6582
|
+
yield i;
|
|
6583
|
+
}
|
|
6584
|
+
if (i === this.#tail) {
|
|
6585
|
+
break;
|
|
6586
|
+
} else {
|
|
6587
|
+
i = this.#next[i];
|
|
6588
|
+
}
|
|
6589
|
+
}
|
|
6590
|
+
}
|
|
6591
|
+
}
|
|
6592
|
+
#isValidIndex(index) {
|
|
6593
|
+
return index !== undefined && this.#keyMap.get(this.#keyList[index]) === index;
|
|
6594
|
+
}
|
|
6595
|
+
*entries() {
|
|
6596
|
+
for (const i of this.#indexes()) {
|
|
6597
|
+
if (this.#valList[i] !== undefined && this.#keyList[i] !== undefined && !this.#isBackgroundFetch(this.#valList[i])) {
|
|
6598
|
+
yield [this.#keyList[i], this.#valList[i]];
|
|
6599
|
+
}
|
|
6600
|
+
}
|
|
6601
|
+
}
|
|
6602
|
+
*rentries() {
|
|
6603
|
+
for (const i of this.#rindexes()) {
|
|
6604
|
+
if (this.#valList[i] !== undefined && this.#keyList[i] !== undefined && !this.#isBackgroundFetch(this.#valList[i])) {
|
|
6605
|
+
yield [this.#keyList[i], this.#valList[i]];
|
|
6606
|
+
}
|
|
6607
|
+
}
|
|
6608
|
+
}
|
|
6609
|
+
*keys() {
|
|
6610
|
+
for (const i of this.#indexes()) {
|
|
6611
|
+
const k = this.#keyList[i];
|
|
6612
|
+
if (k !== undefined && !this.#isBackgroundFetch(this.#valList[i])) {
|
|
6613
|
+
yield k;
|
|
6614
|
+
}
|
|
6615
|
+
}
|
|
6616
|
+
}
|
|
6617
|
+
*rkeys() {
|
|
6618
|
+
for (const i of this.#rindexes()) {
|
|
6619
|
+
const k = this.#keyList[i];
|
|
6620
|
+
if (k !== undefined && !this.#isBackgroundFetch(this.#valList[i])) {
|
|
6621
|
+
yield k;
|
|
6622
|
+
}
|
|
6623
|
+
}
|
|
6624
|
+
}
|
|
6625
|
+
*values() {
|
|
6626
|
+
for (const i of this.#indexes()) {
|
|
6627
|
+
const v = this.#valList[i];
|
|
6628
|
+
if (v !== undefined && !this.#isBackgroundFetch(this.#valList[i])) {
|
|
6629
|
+
yield this.#valList[i];
|
|
6630
|
+
}
|
|
6631
|
+
}
|
|
6632
|
+
}
|
|
6633
|
+
*rvalues() {
|
|
6634
|
+
for (const i of this.#rindexes()) {
|
|
6635
|
+
const v = this.#valList[i];
|
|
6636
|
+
if (v !== undefined && !this.#isBackgroundFetch(this.#valList[i])) {
|
|
6637
|
+
yield this.#valList[i];
|
|
6638
|
+
}
|
|
6639
|
+
}
|
|
6640
|
+
}
|
|
6641
|
+
[Symbol.iterator]() {
|
|
6642
|
+
return this.entries();
|
|
6643
|
+
}
|
|
6644
|
+
[Symbol.toStringTag] = "LRUCache";
|
|
6645
|
+
find(fn, getOptions = {}) {
|
|
6646
|
+
for (const i of this.#indexes()) {
|
|
6647
|
+
const v = this.#valList[i];
|
|
6648
|
+
const value = this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
|
|
6649
|
+
if (value === undefined)
|
|
6650
|
+
continue;
|
|
6651
|
+
if (fn(value, this.#keyList[i], this)) {
|
|
6652
|
+
return this.get(this.#keyList[i], getOptions);
|
|
6653
|
+
}
|
|
6654
|
+
}
|
|
6655
|
+
}
|
|
6656
|
+
forEach(fn, thisp = this) {
|
|
6657
|
+
for (const i of this.#indexes()) {
|
|
6658
|
+
const v = this.#valList[i];
|
|
6659
|
+
const value = this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
|
|
6660
|
+
if (value === undefined)
|
|
6661
|
+
continue;
|
|
6662
|
+
fn.call(thisp, value, this.#keyList[i], this);
|
|
6663
|
+
}
|
|
6664
|
+
}
|
|
6665
|
+
rforEach(fn, thisp = this) {
|
|
6666
|
+
for (const i of this.#rindexes()) {
|
|
6667
|
+
const v = this.#valList[i];
|
|
6668
|
+
const value = this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
|
|
6669
|
+
if (value === undefined)
|
|
6670
|
+
continue;
|
|
6671
|
+
fn.call(thisp, value, this.#keyList[i], this);
|
|
6672
|
+
}
|
|
6673
|
+
}
|
|
6674
|
+
purgeStale() {
|
|
6675
|
+
let deleted = false;
|
|
6676
|
+
for (const i of this.#rindexes({ allowStale: true })) {
|
|
6677
|
+
if (this.#isStale(i)) {
|
|
6678
|
+
this.#delete(this.#keyList[i], "expire");
|
|
6679
|
+
deleted = true;
|
|
6680
|
+
}
|
|
6681
|
+
}
|
|
6682
|
+
return deleted;
|
|
6683
|
+
}
|
|
6684
|
+
info(key) {
|
|
6685
|
+
const i = this.#keyMap.get(key);
|
|
6686
|
+
if (i === undefined)
|
|
6687
|
+
return;
|
|
6688
|
+
const v = this.#valList[i];
|
|
6689
|
+
const value = this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
|
|
6690
|
+
if (value === undefined)
|
|
6691
|
+
return;
|
|
6692
|
+
const entry = { value };
|
|
6693
|
+
if (this.#ttls && this.#starts) {
|
|
6694
|
+
const ttl = this.#ttls[i];
|
|
6695
|
+
const start = this.#starts[i];
|
|
6696
|
+
if (ttl && start) {
|
|
6697
|
+
const remain = ttl - (perf.now() - start);
|
|
6698
|
+
entry.ttl = remain;
|
|
6699
|
+
entry.start = Date.now();
|
|
6700
|
+
}
|
|
6701
|
+
}
|
|
6702
|
+
if (this.#sizes) {
|
|
6703
|
+
entry.size = this.#sizes[i];
|
|
6704
|
+
}
|
|
6705
|
+
return entry;
|
|
6706
|
+
}
|
|
6707
|
+
dump() {
|
|
6708
|
+
const arr = [];
|
|
6709
|
+
for (const i of this.#indexes({ allowStale: true })) {
|
|
6710
|
+
const key = this.#keyList[i];
|
|
6711
|
+
const v = this.#valList[i];
|
|
6712
|
+
const value = this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
|
|
6713
|
+
if (value === undefined || key === undefined)
|
|
6714
|
+
continue;
|
|
6715
|
+
const entry = { value };
|
|
6716
|
+
if (this.#ttls && this.#starts) {
|
|
6717
|
+
entry.ttl = this.#ttls[i];
|
|
6718
|
+
const age = perf.now() - this.#starts[i];
|
|
6719
|
+
entry.start = Math.floor(Date.now() - age);
|
|
6720
|
+
}
|
|
6721
|
+
if (this.#sizes) {
|
|
6722
|
+
entry.size = this.#sizes[i];
|
|
6723
|
+
}
|
|
6724
|
+
arr.unshift([key, entry]);
|
|
6725
|
+
}
|
|
6726
|
+
return arr;
|
|
6727
|
+
}
|
|
6728
|
+
load(arr) {
|
|
6729
|
+
this.clear();
|
|
6730
|
+
for (const [key, entry] of arr) {
|
|
6731
|
+
if (entry.start) {
|
|
6732
|
+
const age = Date.now() - entry.start;
|
|
6733
|
+
entry.start = perf.now() - age;
|
|
6734
|
+
}
|
|
6735
|
+
this.set(key, entry.value, entry);
|
|
6736
|
+
}
|
|
6737
|
+
}
|
|
6738
|
+
set(k, v, setOptions = {}) {
|
|
6739
|
+
if (v === undefined) {
|
|
6740
|
+
this.delete(k);
|
|
6741
|
+
return this;
|
|
6742
|
+
}
|
|
6743
|
+
const { ttl = this.ttl, start, noDisposeOnSet = this.noDisposeOnSet, sizeCalculation = this.sizeCalculation, status } = setOptions;
|
|
6744
|
+
let { noUpdateTTL = this.noUpdateTTL } = setOptions;
|
|
6745
|
+
const size = this.#requireSize(k, v, setOptions.size || 0, sizeCalculation);
|
|
6746
|
+
if (this.maxEntrySize && size > this.maxEntrySize) {
|
|
6747
|
+
if (status) {
|
|
6748
|
+
status.set = "miss";
|
|
6749
|
+
status.maxEntrySizeExceeded = true;
|
|
6750
|
+
}
|
|
6751
|
+
this.#delete(k, "set");
|
|
6752
|
+
return this;
|
|
6753
|
+
}
|
|
6754
|
+
let index = this.#size === 0 ? undefined : this.#keyMap.get(k);
|
|
6755
|
+
if (index === undefined) {
|
|
6756
|
+
index = this.#size === 0 ? this.#tail : this.#free.length !== 0 ? this.#free.pop() : this.#size === this.#max ? this.#evict(false) : this.#size;
|
|
6757
|
+
this.#keyList[index] = k;
|
|
6758
|
+
this.#valList[index] = v;
|
|
6759
|
+
this.#keyMap.set(k, index);
|
|
6760
|
+
this.#next[this.#tail] = index;
|
|
6761
|
+
this.#prev[index] = this.#tail;
|
|
6762
|
+
this.#tail = index;
|
|
6763
|
+
this.#size++;
|
|
6764
|
+
this.#addItemSize(index, size, status);
|
|
6765
|
+
if (status)
|
|
6766
|
+
status.set = "add";
|
|
6767
|
+
noUpdateTTL = false;
|
|
6768
|
+
} else {
|
|
6769
|
+
this.#moveToTail(index);
|
|
6770
|
+
const oldVal = this.#valList[index];
|
|
6771
|
+
if (v !== oldVal) {
|
|
6772
|
+
if (this.#hasFetchMethod && this.#isBackgroundFetch(oldVal)) {
|
|
6773
|
+
oldVal.__abortController.abort(new Error("replaced"));
|
|
6774
|
+
const { __staleWhileFetching: s } = oldVal;
|
|
6775
|
+
if (s !== undefined && !noDisposeOnSet) {
|
|
6776
|
+
if (this.#hasDispose) {
|
|
6777
|
+
this.#dispose?.(s, k, "set");
|
|
6778
|
+
}
|
|
6779
|
+
if (this.#hasDisposeAfter) {
|
|
6780
|
+
this.#disposed?.push([s, k, "set"]);
|
|
6781
|
+
}
|
|
6782
|
+
}
|
|
6783
|
+
} else if (!noDisposeOnSet) {
|
|
6784
|
+
if (this.#hasDispose) {
|
|
6785
|
+
this.#dispose?.(oldVal, k, "set");
|
|
6786
|
+
}
|
|
6787
|
+
if (this.#hasDisposeAfter) {
|
|
6788
|
+
this.#disposed?.push([oldVal, k, "set"]);
|
|
6789
|
+
}
|
|
6790
|
+
}
|
|
6791
|
+
this.#removeItemSize(index);
|
|
6792
|
+
this.#addItemSize(index, size, status);
|
|
6793
|
+
this.#valList[index] = v;
|
|
6794
|
+
if (status) {
|
|
6795
|
+
status.set = "replace";
|
|
6796
|
+
const oldValue = oldVal && this.#isBackgroundFetch(oldVal) ? oldVal.__staleWhileFetching : oldVal;
|
|
6797
|
+
if (oldValue !== undefined)
|
|
6798
|
+
status.oldValue = oldValue;
|
|
6799
|
+
}
|
|
6800
|
+
} else if (status) {
|
|
6801
|
+
status.set = "update";
|
|
6802
|
+
}
|
|
6803
|
+
}
|
|
6804
|
+
if (ttl !== 0 && !this.#ttls) {
|
|
6805
|
+
this.#initializeTTLTracking();
|
|
6806
|
+
}
|
|
6807
|
+
if (this.#ttls) {
|
|
6808
|
+
if (!noUpdateTTL) {
|
|
6809
|
+
this.#setItemTTL(index, ttl, start);
|
|
6810
|
+
}
|
|
6811
|
+
if (status)
|
|
6812
|
+
this.#statusTTL(status, index);
|
|
6813
|
+
}
|
|
6814
|
+
if (!noDisposeOnSet && this.#hasDisposeAfter && this.#disposed) {
|
|
6815
|
+
const dt = this.#disposed;
|
|
6816
|
+
let task;
|
|
6817
|
+
while (task = dt?.shift()) {
|
|
6818
|
+
this.#disposeAfter?.(...task);
|
|
6819
|
+
}
|
|
6820
|
+
}
|
|
6821
|
+
return this;
|
|
6822
|
+
}
|
|
6823
|
+
pop() {
|
|
6824
|
+
try {
|
|
6825
|
+
while (this.#size) {
|
|
6826
|
+
const val = this.#valList[this.#head];
|
|
6827
|
+
this.#evict(true);
|
|
6828
|
+
if (this.#isBackgroundFetch(val)) {
|
|
6829
|
+
if (val.__staleWhileFetching) {
|
|
6830
|
+
return val.__staleWhileFetching;
|
|
6831
|
+
}
|
|
6832
|
+
} else if (val !== undefined) {
|
|
6833
|
+
return val;
|
|
6834
|
+
}
|
|
6835
|
+
}
|
|
6836
|
+
} finally {
|
|
6837
|
+
if (this.#hasDisposeAfter && this.#disposed) {
|
|
6838
|
+
const dt = this.#disposed;
|
|
6839
|
+
let task;
|
|
6840
|
+
while (task = dt?.shift()) {
|
|
6841
|
+
this.#disposeAfter?.(...task);
|
|
6842
|
+
}
|
|
6843
|
+
}
|
|
6844
|
+
}
|
|
6845
|
+
}
|
|
6846
|
+
#evict(free) {
|
|
6847
|
+
const head = this.#head;
|
|
6848
|
+
const k = this.#keyList[head];
|
|
6849
|
+
const v = this.#valList[head];
|
|
6850
|
+
if (this.#hasFetchMethod && this.#isBackgroundFetch(v)) {
|
|
6851
|
+
v.__abortController.abort(new Error("evicted"));
|
|
6852
|
+
} else if (this.#hasDispose || this.#hasDisposeAfter) {
|
|
6853
|
+
if (this.#hasDispose) {
|
|
6854
|
+
this.#dispose?.(v, k, "evict");
|
|
6855
|
+
}
|
|
6856
|
+
if (this.#hasDisposeAfter) {
|
|
6857
|
+
this.#disposed?.push([v, k, "evict"]);
|
|
6858
|
+
}
|
|
6859
|
+
}
|
|
6860
|
+
this.#removeItemSize(head);
|
|
6861
|
+
if (free) {
|
|
6862
|
+
this.#keyList[head] = undefined;
|
|
6863
|
+
this.#valList[head] = undefined;
|
|
6864
|
+
this.#free.push(head);
|
|
6865
|
+
}
|
|
6866
|
+
if (this.#size === 1) {
|
|
6867
|
+
this.#head = this.#tail = 0;
|
|
6868
|
+
this.#free.length = 0;
|
|
6869
|
+
} else {
|
|
6870
|
+
this.#head = this.#next[head];
|
|
6871
|
+
}
|
|
6872
|
+
this.#keyMap.delete(k);
|
|
6873
|
+
this.#size--;
|
|
6874
|
+
return head;
|
|
6875
|
+
}
|
|
6876
|
+
has(k, hasOptions = {}) {
|
|
6877
|
+
const { updateAgeOnHas = this.updateAgeOnHas, status } = hasOptions;
|
|
6878
|
+
const index = this.#keyMap.get(k);
|
|
6879
|
+
if (index !== undefined) {
|
|
6880
|
+
const v = this.#valList[index];
|
|
6881
|
+
if (this.#isBackgroundFetch(v) && v.__staleWhileFetching === undefined) {
|
|
6882
|
+
return false;
|
|
6883
|
+
}
|
|
6884
|
+
if (!this.#isStale(index)) {
|
|
6885
|
+
if (updateAgeOnHas) {
|
|
6886
|
+
this.#updateItemAge(index);
|
|
6887
|
+
}
|
|
6888
|
+
if (status) {
|
|
6889
|
+
status.has = "hit";
|
|
6890
|
+
this.#statusTTL(status, index);
|
|
6891
|
+
}
|
|
6892
|
+
return true;
|
|
6893
|
+
} else if (status) {
|
|
6894
|
+
status.has = "stale";
|
|
6895
|
+
this.#statusTTL(status, index);
|
|
6896
|
+
}
|
|
6897
|
+
} else if (status) {
|
|
6898
|
+
status.has = "miss";
|
|
6899
|
+
}
|
|
6900
|
+
return false;
|
|
6901
|
+
}
|
|
6902
|
+
peek(k, peekOptions = {}) {
|
|
6903
|
+
const { allowStale = this.allowStale } = peekOptions;
|
|
6904
|
+
const index = this.#keyMap.get(k);
|
|
6905
|
+
if (index === undefined || !allowStale && this.#isStale(index)) {
|
|
6906
|
+
return;
|
|
6907
|
+
}
|
|
6908
|
+
const v = this.#valList[index];
|
|
6909
|
+
return this.#isBackgroundFetch(v) ? v.__staleWhileFetching : v;
|
|
6910
|
+
}
|
|
6911
|
+
#backgroundFetch(k, index, options, context) {
|
|
6912
|
+
const v = index === undefined ? undefined : this.#valList[index];
|
|
6913
|
+
if (this.#isBackgroundFetch(v)) {
|
|
6914
|
+
return v;
|
|
6915
|
+
}
|
|
6916
|
+
const ac = new AC;
|
|
6917
|
+
const { signal } = options;
|
|
6918
|
+
signal?.addEventListener("abort", () => ac.abort(signal.reason), {
|
|
6919
|
+
signal: ac.signal
|
|
6920
|
+
});
|
|
6921
|
+
const fetchOpts = {
|
|
6922
|
+
signal: ac.signal,
|
|
6923
|
+
options,
|
|
6924
|
+
context
|
|
6925
|
+
};
|
|
6926
|
+
const cb = (v2, updateCache = false) => {
|
|
6927
|
+
const { aborted } = ac.signal;
|
|
6928
|
+
const ignoreAbort = options.ignoreFetchAbort && v2 !== undefined;
|
|
6929
|
+
if (options.status) {
|
|
6930
|
+
if (aborted && !updateCache) {
|
|
6931
|
+
options.status.fetchAborted = true;
|
|
6932
|
+
options.status.fetchError = ac.signal.reason;
|
|
6933
|
+
if (ignoreAbort)
|
|
6934
|
+
options.status.fetchAbortIgnored = true;
|
|
6935
|
+
} else {
|
|
6936
|
+
options.status.fetchResolved = true;
|
|
6937
|
+
}
|
|
6938
|
+
}
|
|
6939
|
+
if (aborted && !ignoreAbort && !updateCache) {
|
|
6940
|
+
return fetchFail(ac.signal.reason);
|
|
6941
|
+
}
|
|
6942
|
+
const bf2 = p;
|
|
6943
|
+
if (this.#valList[index] === p) {
|
|
6944
|
+
if (v2 === undefined) {
|
|
6945
|
+
if (bf2.__staleWhileFetching) {
|
|
6946
|
+
this.#valList[index] = bf2.__staleWhileFetching;
|
|
6947
|
+
} else {
|
|
6948
|
+
this.#delete(k, "fetch");
|
|
6949
|
+
}
|
|
6950
|
+
} else {
|
|
6951
|
+
if (options.status)
|
|
6952
|
+
options.status.fetchUpdated = true;
|
|
6953
|
+
this.set(k, v2, fetchOpts.options);
|
|
6954
|
+
}
|
|
6955
|
+
}
|
|
6956
|
+
return v2;
|
|
6957
|
+
};
|
|
6958
|
+
const eb = (er) => {
|
|
6959
|
+
if (options.status) {
|
|
6960
|
+
options.status.fetchRejected = true;
|
|
6961
|
+
options.status.fetchError = er;
|
|
6962
|
+
}
|
|
6963
|
+
return fetchFail(er);
|
|
6964
|
+
};
|
|
6965
|
+
const fetchFail = (er) => {
|
|
6966
|
+
const { aborted } = ac.signal;
|
|
6967
|
+
const allowStaleAborted = aborted && options.allowStaleOnFetchAbort;
|
|
6968
|
+
const allowStale = allowStaleAborted || options.allowStaleOnFetchRejection;
|
|
6969
|
+
const noDelete = allowStale || options.noDeleteOnFetchRejection;
|
|
6970
|
+
const bf2 = p;
|
|
6971
|
+
if (this.#valList[index] === p) {
|
|
6972
|
+
const del = !noDelete || bf2.__staleWhileFetching === undefined;
|
|
6973
|
+
if (del) {
|
|
6974
|
+
this.#delete(k, "fetch");
|
|
6975
|
+
} else if (!allowStaleAborted) {
|
|
6976
|
+
this.#valList[index] = bf2.__staleWhileFetching;
|
|
6977
|
+
}
|
|
6978
|
+
}
|
|
6979
|
+
if (allowStale) {
|
|
6980
|
+
if (options.status && bf2.__staleWhileFetching !== undefined) {
|
|
6981
|
+
options.status.returnedStale = true;
|
|
6982
|
+
}
|
|
6983
|
+
return bf2.__staleWhileFetching;
|
|
6984
|
+
} else if (bf2.__returned === bf2) {
|
|
6985
|
+
throw er;
|
|
6986
|
+
}
|
|
6987
|
+
};
|
|
6988
|
+
const pcall = (res, rej) => {
|
|
6989
|
+
const fmp = this.#fetchMethod?.(k, v, fetchOpts);
|
|
6990
|
+
if (fmp && fmp instanceof Promise) {
|
|
6991
|
+
fmp.then((v2) => res(v2 === undefined ? undefined : v2), rej);
|
|
6992
|
+
}
|
|
6993
|
+
ac.signal.addEventListener("abort", () => {
|
|
6994
|
+
if (!options.ignoreFetchAbort || options.allowStaleOnFetchAbort) {
|
|
6995
|
+
res(undefined);
|
|
6996
|
+
if (options.allowStaleOnFetchAbort) {
|
|
6997
|
+
res = (v2) => cb(v2, true);
|
|
6998
|
+
}
|
|
6999
|
+
}
|
|
7000
|
+
});
|
|
7001
|
+
};
|
|
7002
|
+
if (options.status)
|
|
7003
|
+
options.status.fetchDispatched = true;
|
|
7004
|
+
const p = new Promise(pcall).then(cb, eb);
|
|
7005
|
+
const bf = Object.assign(p, {
|
|
7006
|
+
__abortController: ac,
|
|
7007
|
+
__staleWhileFetching: v,
|
|
7008
|
+
__returned: undefined
|
|
7009
|
+
});
|
|
7010
|
+
if (index === undefined) {
|
|
7011
|
+
this.set(k, bf, { ...fetchOpts.options, status: undefined });
|
|
7012
|
+
index = this.#keyMap.get(k);
|
|
7013
|
+
} else {
|
|
7014
|
+
this.#valList[index] = bf;
|
|
7015
|
+
}
|
|
7016
|
+
return bf;
|
|
7017
|
+
}
|
|
7018
|
+
#isBackgroundFetch(p) {
|
|
7019
|
+
if (!this.#hasFetchMethod)
|
|
7020
|
+
return false;
|
|
7021
|
+
const b = p;
|
|
7022
|
+
return !!b && b instanceof Promise && b.hasOwnProperty("__staleWhileFetching") && b.__abortController instanceof AC;
|
|
7023
|
+
}
|
|
7024
|
+
async fetch(k, fetchOptions = {}) {
|
|
7025
|
+
const {
|
|
7026
|
+
allowStale = this.allowStale,
|
|
7027
|
+
updateAgeOnGet = this.updateAgeOnGet,
|
|
7028
|
+
noDeleteOnStaleGet = this.noDeleteOnStaleGet,
|
|
7029
|
+
ttl = this.ttl,
|
|
7030
|
+
noDisposeOnSet = this.noDisposeOnSet,
|
|
7031
|
+
size = 0,
|
|
7032
|
+
sizeCalculation = this.sizeCalculation,
|
|
7033
|
+
noUpdateTTL = this.noUpdateTTL,
|
|
7034
|
+
noDeleteOnFetchRejection = this.noDeleteOnFetchRejection,
|
|
7035
|
+
allowStaleOnFetchRejection = this.allowStaleOnFetchRejection,
|
|
7036
|
+
ignoreFetchAbort = this.ignoreFetchAbort,
|
|
7037
|
+
allowStaleOnFetchAbort = this.allowStaleOnFetchAbort,
|
|
7038
|
+
context,
|
|
7039
|
+
forceRefresh = false,
|
|
7040
|
+
status,
|
|
7041
|
+
signal
|
|
7042
|
+
} = fetchOptions;
|
|
7043
|
+
if (!this.#hasFetchMethod) {
|
|
7044
|
+
if (status)
|
|
7045
|
+
status.fetch = "get";
|
|
7046
|
+
return this.get(k, {
|
|
7047
|
+
allowStale,
|
|
7048
|
+
updateAgeOnGet,
|
|
7049
|
+
noDeleteOnStaleGet,
|
|
7050
|
+
status
|
|
7051
|
+
});
|
|
7052
|
+
}
|
|
7053
|
+
const options = {
|
|
7054
|
+
allowStale,
|
|
7055
|
+
updateAgeOnGet,
|
|
7056
|
+
noDeleteOnStaleGet,
|
|
7057
|
+
ttl,
|
|
7058
|
+
noDisposeOnSet,
|
|
7059
|
+
size,
|
|
7060
|
+
sizeCalculation,
|
|
7061
|
+
noUpdateTTL,
|
|
7062
|
+
noDeleteOnFetchRejection,
|
|
7063
|
+
allowStaleOnFetchRejection,
|
|
7064
|
+
allowStaleOnFetchAbort,
|
|
7065
|
+
ignoreFetchAbort,
|
|
7066
|
+
status,
|
|
7067
|
+
signal
|
|
7068
|
+
};
|
|
7069
|
+
let index = this.#keyMap.get(k);
|
|
7070
|
+
if (index === undefined) {
|
|
7071
|
+
if (status)
|
|
7072
|
+
status.fetch = "miss";
|
|
7073
|
+
const p = this.#backgroundFetch(k, index, options, context);
|
|
7074
|
+
return p.__returned = p;
|
|
7075
|
+
} else {
|
|
7076
|
+
const v = this.#valList[index];
|
|
7077
|
+
if (this.#isBackgroundFetch(v)) {
|
|
7078
|
+
const stale = allowStale && v.__staleWhileFetching !== undefined;
|
|
7079
|
+
if (status) {
|
|
7080
|
+
status.fetch = "inflight";
|
|
7081
|
+
if (stale)
|
|
7082
|
+
status.returnedStale = true;
|
|
7083
|
+
}
|
|
7084
|
+
return stale ? v.__staleWhileFetching : v.__returned = v;
|
|
7085
|
+
}
|
|
7086
|
+
const isStale = this.#isStale(index);
|
|
7087
|
+
if (!forceRefresh && !isStale) {
|
|
7088
|
+
if (status)
|
|
7089
|
+
status.fetch = "hit";
|
|
7090
|
+
this.#moveToTail(index);
|
|
7091
|
+
if (updateAgeOnGet) {
|
|
7092
|
+
this.#updateItemAge(index);
|
|
7093
|
+
}
|
|
7094
|
+
if (status)
|
|
7095
|
+
this.#statusTTL(status, index);
|
|
7096
|
+
return v;
|
|
7097
|
+
}
|
|
7098
|
+
const p = this.#backgroundFetch(k, index, options, context);
|
|
7099
|
+
const hasStale = p.__staleWhileFetching !== undefined;
|
|
7100
|
+
const staleVal = hasStale && allowStale;
|
|
7101
|
+
if (status) {
|
|
7102
|
+
status.fetch = isStale ? "stale" : "refresh";
|
|
7103
|
+
if (staleVal && isStale)
|
|
7104
|
+
status.returnedStale = true;
|
|
7105
|
+
}
|
|
7106
|
+
return staleVal ? p.__staleWhileFetching : p.__returned = p;
|
|
7107
|
+
}
|
|
7108
|
+
}
|
|
7109
|
+
async forceFetch(k, fetchOptions = {}) {
|
|
7110
|
+
const v = await this.fetch(k, fetchOptions);
|
|
7111
|
+
if (v === undefined)
|
|
7112
|
+
throw new Error("fetch() returned undefined");
|
|
7113
|
+
return v;
|
|
7114
|
+
}
|
|
7115
|
+
memo(k, memoOptions = {}) {
|
|
7116
|
+
const memoMethod = this.#memoMethod;
|
|
7117
|
+
if (!memoMethod) {
|
|
7118
|
+
throw new Error("no memoMethod provided to constructor");
|
|
7119
|
+
}
|
|
7120
|
+
const { context, forceRefresh, ...options } = memoOptions;
|
|
7121
|
+
const v = this.get(k, options);
|
|
7122
|
+
if (!forceRefresh && v !== undefined)
|
|
7123
|
+
return v;
|
|
7124
|
+
const vv = memoMethod(k, v, {
|
|
7125
|
+
options,
|
|
7126
|
+
context
|
|
7127
|
+
});
|
|
7128
|
+
this.set(k, vv, options);
|
|
7129
|
+
return vv;
|
|
7130
|
+
}
|
|
7131
|
+
get(k, getOptions = {}) {
|
|
7132
|
+
const { allowStale = this.allowStale, updateAgeOnGet = this.updateAgeOnGet, noDeleteOnStaleGet = this.noDeleteOnStaleGet, status } = getOptions;
|
|
7133
|
+
const index = this.#keyMap.get(k);
|
|
7134
|
+
if (index !== undefined) {
|
|
7135
|
+
const value = this.#valList[index];
|
|
7136
|
+
const fetching = this.#isBackgroundFetch(value);
|
|
7137
|
+
if (status)
|
|
7138
|
+
this.#statusTTL(status, index);
|
|
7139
|
+
if (this.#isStale(index)) {
|
|
7140
|
+
if (status)
|
|
7141
|
+
status.get = "stale";
|
|
7142
|
+
if (!fetching) {
|
|
7143
|
+
if (!noDeleteOnStaleGet) {
|
|
7144
|
+
this.#delete(k, "expire");
|
|
7145
|
+
}
|
|
7146
|
+
if (status && allowStale)
|
|
7147
|
+
status.returnedStale = true;
|
|
7148
|
+
return allowStale ? value : undefined;
|
|
7149
|
+
} else {
|
|
7150
|
+
if (status && allowStale && value.__staleWhileFetching !== undefined) {
|
|
7151
|
+
status.returnedStale = true;
|
|
7152
|
+
}
|
|
7153
|
+
return allowStale ? value.__staleWhileFetching : undefined;
|
|
7154
|
+
}
|
|
7155
|
+
} else {
|
|
7156
|
+
if (status)
|
|
7157
|
+
status.get = "hit";
|
|
7158
|
+
if (fetching) {
|
|
7159
|
+
return value.__staleWhileFetching;
|
|
7160
|
+
}
|
|
7161
|
+
this.#moveToTail(index);
|
|
7162
|
+
if (updateAgeOnGet) {
|
|
7163
|
+
this.#updateItemAge(index);
|
|
7164
|
+
}
|
|
7165
|
+
return value;
|
|
7166
|
+
}
|
|
7167
|
+
} else if (status) {
|
|
7168
|
+
status.get = "miss";
|
|
7169
|
+
}
|
|
7170
|
+
}
|
|
7171
|
+
#connect(p, n) {
|
|
7172
|
+
this.#prev[n] = p;
|
|
7173
|
+
this.#next[p] = n;
|
|
7174
|
+
}
|
|
7175
|
+
#moveToTail(index) {
|
|
7176
|
+
if (index !== this.#tail) {
|
|
7177
|
+
if (index === this.#head) {
|
|
7178
|
+
this.#head = this.#next[index];
|
|
7179
|
+
} else {
|
|
7180
|
+
this.#connect(this.#prev[index], this.#next[index]);
|
|
7181
|
+
}
|
|
7182
|
+
this.#connect(this.#tail, index);
|
|
7183
|
+
this.#tail = index;
|
|
7184
|
+
}
|
|
7185
|
+
}
|
|
7186
|
+
delete(k) {
|
|
7187
|
+
return this.#delete(k, "delete");
|
|
7188
|
+
}
|
|
7189
|
+
#delete(k, reason) {
|
|
7190
|
+
let deleted = false;
|
|
7191
|
+
if (this.#size !== 0) {
|
|
7192
|
+
const index = this.#keyMap.get(k);
|
|
7193
|
+
if (index !== undefined) {
|
|
7194
|
+
deleted = true;
|
|
7195
|
+
if (this.#size === 1) {
|
|
7196
|
+
this.#clear(reason);
|
|
7197
|
+
} else {
|
|
7198
|
+
this.#removeItemSize(index);
|
|
7199
|
+
const v = this.#valList[index];
|
|
7200
|
+
if (this.#isBackgroundFetch(v)) {
|
|
7201
|
+
v.__abortController.abort(new Error("deleted"));
|
|
7202
|
+
} else if (this.#hasDispose || this.#hasDisposeAfter) {
|
|
7203
|
+
if (this.#hasDispose) {
|
|
7204
|
+
this.#dispose?.(v, k, reason);
|
|
7205
|
+
}
|
|
7206
|
+
if (this.#hasDisposeAfter) {
|
|
7207
|
+
this.#disposed?.push([v, k, reason]);
|
|
7208
|
+
}
|
|
7209
|
+
}
|
|
7210
|
+
this.#keyMap.delete(k);
|
|
7211
|
+
this.#keyList[index] = undefined;
|
|
7212
|
+
this.#valList[index] = undefined;
|
|
7213
|
+
if (index === this.#tail) {
|
|
7214
|
+
this.#tail = this.#prev[index];
|
|
7215
|
+
} else if (index === this.#head) {
|
|
7216
|
+
this.#head = this.#next[index];
|
|
7217
|
+
} else {
|
|
7218
|
+
const pi = this.#prev[index];
|
|
7219
|
+
this.#next[pi] = this.#next[index];
|
|
7220
|
+
const ni = this.#next[index];
|
|
7221
|
+
this.#prev[ni] = this.#prev[index];
|
|
7222
|
+
}
|
|
7223
|
+
this.#size--;
|
|
7224
|
+
this.#free.push(index);
|
|
7225
|
+
}
|
|
7226
|
+
}
|
|
7227
|
+
}
|
|
7228
|
+
if (this.#hasDisposeAfter && this.#disposed?.length) {
|
|
7229
|
+
const dt = this.#disposed;
|
|
7230
|
+
let task;
|
|
7231
|
+
while (task = dt?.shift()) {
|
|
7232
|
+
this.#disposeAfter?.(...task);
|
|
7233
|
+
}
|
|
7234
|
+
}
|
|
7235
|
+
return deleted;
|
|
7236
|
+
}
|
|
7237
|
+
clear() {
|
|
7238
|
+
return this.#clear("delete");
|
|
7239
|
+
}
|
|
7240
|
+
#clear(reason) {
|
|
7241
|
+
for (const index of this.#rindexes({ allowStale: true })) {
|
|
7242
|
+
const v = this.#valList[index];
|
|
7243
|
+
if (this.#isBackgroundFetch(v)) {
|
|
7244
|
+
v.__abortController.abort(new Error("deleted"));
|
|
7245
|
+
} else {
|
|
7246
|
+
const k = this.#keyList[index];
|
|
7247
|
+
if (this.#hasDispose) {
|
|
7248
|
+
this.#dispose?.(v, k, reason);
|
|
7249
|
+
}
|
|
7250
|
+
if (this.#hasDisposeAfter) {
|
|
7251
|
+
this.#disposed?.push([v, k, reason]);
|
|
7252
|
+
}
|
|
7253
|
+
}
|
|
7254
|
+
}
|
|
7255
|
+
this.#keyMap.clear();
|
|
7256
|
+
this.#valList.fill(undefined);
|
|
7257
|
+
this.#keyList.fill(undefined);
|
|
7258
|
+
if (this.#ttls && this.#starts) {
|
|
7259
|
+
this.#ttls.fill(0);
|
|
7260
|
+
this.#starts.fill(0);
|
|
7261
|
+
}
|
|
7262
|
+
if (this.#sizes) {
|
|
7263
|
+
this.#sizes.fill(0);
|
|
7264
|
+
}
|
|
7265
|
+
this.#head = 0;
|
|
7266
|
+
this.#tail = 0;
|
|
7267
|
+
this.#free.length = 0;
|
|
7268
|
+
this.#calculatedSize = 0;
|
|
7269
|
+
this.#size = 0;
|
|
7270
|
+
if (this.#hasDisposeAfter && this.#disposed) {
|
|
7271
|
+
const dt = this.#disposed;
|
|
7272
|
+
let task;
|
|
7273
|
+
while (task = dt?.shift()) {
|
|
7274
|
+
this.#disposeAfter?.(...task);
|
|
7275
|
+
}
|
|
7276
|
+
}
|
|
7277
|
+
}
|
|
7278
|
+
};
|
|
7279
|
+
});
|
|
7280
|
+
|
|
7281
|
+
// src/utils/schema-path.ts
|
|
7282
|
+
import { join as join2 } from "path";
|
|
7283
|
+
function resolveSchemaPath(dbcliPath, connectionName) {
|
|
7284
|
+
if (!connectionName)
|
|
7285
|
+
return join2(dbcliPath, "schemas");
|
|
7286
|
+
return join2(dbcliPath, "schemas", connectionName);
|
|
7287
|
+
}
|
|
7288
|
+
var init_schema_path = () => {};
|
|
7289
|
+
|
|
7290
|
+
// src/core/schema-cache.ts
|
|
7291
|
+
import { join as join3 } from "path";
|
|
7292
|
+
|
|
7293
|
+
class SchemaCacheManager {
|
|
7294
|
+
cache;
|
|
7295
|
+
index = null;
|
|
7296
|
+
hotSchemas = new Map;
|
|
7297
|
+
dbcliPath;
|
|
7298
|
+
schemaRoot;
|
|
7299
|
+
maxItems;
|
|
7300
|
+
maxSize;
|
|
7301
|
+
constructor(dbcliPath, options) {
|
|
7302
|
+
this.dbcliPath = dbcliPath;
|
|
7303
|
+
this.schemaRoot = resolveSchemaPath(dbcliPath, options?.connectionName);
|
|
7304
|
+
this.maxItems = options?.maxCacheItems || 100;
|
|
7305
|
+
this.maxSize = options?.maxCacheSize || 52428800;
|
|
7306
|
+
this.cache = new LRUCache({
|
|
7307
|
+
max: this.maxItems,
|
|
7308
|
+
maxSize: this.maxSize,
|
|
7309
|
+
sizeCalculation: (schema) => JSON.stringify(schema).length,
|
|
7310
|
+
allowStale: false,
|
|
7311
|
+
updateAgeOnGet: true,
|
|
7312
|
+
updateAgeOnHas: false
|
|
7313
|
+
});
|
|
7314
|
+
}
|
|
7315
|
+
async initialize() {
|
|
7316
|
+
try {
|
|
7317
|
+
const indexPath = join3(this.schemaRoot, "index.json");
|
|
7318
|
+
const indexFile = Bun.file(indexPath);
|
|
7319
|
+
if (await indexFile.exists()) {
|
|
7320
|
+
const indexContent = await indexFile.text();
|
|
7321
|
+
this.index = JSON.parse(indexContent);
|
|
7322
|
+
}
|
|
7323
|
+
const hotPath = join3(this.schemaRoot, "hot-schemas.json");
|
|
7324
|
+
const hotFile = Bun.file(hotPath);
|
|
7325
|
+
if (await hotFile.exists()) {
|
|
7326
|
+
const hotContent = await hotFile.text();
|
|
7327
|
+
const hotData = JSON.parse(hotContent);
|
|
7328
|
+
const schemasObj = hotData.schemas || hotData;
|
|
7329
|
+
for (const [tableName, schema] of Object.entries(schemasObj)) {
|
|
7330
|
+
const tableSchema = schema;
|
|
7331
|
+
this.hotSchemas.set(tableName, tableSchema);
|
|
7332
|
+
this.cache.set(tableName, tableSchema);
|
|
7333
|
+
}
|
|
7334
|
+
}
|
|
7335
|
+
} catch (error) {
|
|
7336
|
+
console.error("Failed to load schema index/hot-schemas:", error);
|
|
7337
|
+
this.index = null;
|
|
7338
|
+
}
|
|
7339
|
+
}
|
|
7340
|
+
async getTableSchema(tableName) {
|
|
7341
|
+
if (this.hotSchemas.has(tableName)) {
|
|
7342
|
+
return this.hotSchemas.get(tableName);
|
|
7343
|
+
}
|
|
7344
|
+
const cached = this.cache.get(tableName);
|
|
7345
|
+
if (cached) {
|
|
7346
|
+
return cached;
|
|
7347
|
+
}
|
|
7348
|
+
if (!this.index) {
|
|
7349
|
+
return null;
|
|
7350
|
+
}
|
|
7351
|
+
const tableInfo = this.index.tables[tableName];
|
|
7352
|
+
if (!tableInfo) {
|
|
7353
|
+
return null;
|
|
7354
|
+
}
|
|
7355
|
+
try {
|
|
7356
|
+
const filePath = join3(this.schemaRoot, tableInfo.file);
|
|
7357
|
+
const file = Bun.file(filePath);
|
|
7358
|
+
if (!await file.exists()) {
|
|
7359
|
+
console.error(`Cold table file not found: ${tableInfo.file} for table ${tableName}`);
|
|
7360
|
+
return null;
|
|
7361
|
+
}
|
|
7362
|
+
const content = await file.text();
|
|
7363
|
+
const data = JSON.parse(content);
|
|
7364
|
+
const schema = data.schemas?.[tableName] || data[tableName];
|
|
7365
|
+
if (schema) {
|
|
7366
|
+
this.cache.set(tableName, schema);
|
|
7367
|
+
}
|
|
7368
|
+
return schema || null;
|
|
7369
|
+
} catch (error) {
|
|
7370
|
+
console.error(`Failed to load cold schema for table ${tableName}:`, error);
|
|
7371
|
+
return null;
|
|
7372
|
+
}
|
|
7373
|
+
}
|
|
7374
|
+
async findFieldsByName(fieldName) {
|
|
7375
|
+
const results = [];
|
|
7376
|
+
for (const [tableName, schema] of this.hotSchemas.entries()) {
|
|
7377
|
+
const column = schema.columns.find((c) => c.name === fieldName);
|
|
7378
|
+
if (column) {
|
|
7379
|
+
results.push({ table: tableName, column });
|
|
7380
|
+
}
|
|
7381
|
+
}
|
|
7382
|
+
return results;
|
|
7383
|
+
}
|
|
7384
|
+
invalidateTable(tableName) {
|
|
7385
|
+
this.hotSchemas.delete(tableName);
|
|
7386
|
+
this.cache.delete(tableName);
|
|
7387
|
+
}
|
|
7388
|
+
refreshTable(tableName, schema) {
|
|
7389
|
+
this.hotSchemas.set(tableName, schema);
|
|
7390
|
+
this.cache.set(tableName, schema);
|
|
7391
|
+
}
|
|
7392
|
+
getStats() {
|
|
7393
|
+
const cacheSize = this.cache.calculatedSize || 0;
|
|
7394
|
+
const cacheHitRate = this.cache.max && this.cache.max > 0 ? Math.round(this.cache.size / this.cache.max * 100) : 0;
|
|
7395
|
+
return {
|
|
7396
|
+
hotTables: this.hotSchemas.size,
|
|
7397
|
+
cachedTables: this.cache.size,
|
|
7398
|
+
cacheSize,
|
|
7399
|
+
cacheHitRate: `${cacheHitRate}%`,
|
|
7400
|
+
maxItems: this.maxItems,
|
|
7401
|
+
maxSize: this.maxSize
|
|
7402
|
+
};
|
|
7403
|
+
}
|
|
7404
|
+
}
|
|
7405
|
+
var init_schema_cache = __esm(() => {
|
|
7406
|
+
init_esm();
|
|
7407
|
+
init_schema_path();
|
|
7408
|
+
});
|
|
7409
|
+
|
|
7410
|
+
// src/core/schema-index.ts
|
|
7411
|
+
import { join as join4 } from "path";
|
|
7412
|
+
|
|
7413
|
+
class SchemaIndexBuilder {
|
|
7414
|
+
static async loadIndex(dbcliPath, connectionName) {
|
|
7415
|
+
try {
|
|
7416
|
+
const indexPath = join4(resolveSchemaPath(dbcliPath, connectionName), "index.json");
|
|
7417
|
+
const file = Bun.file(indexPath);
|
|
7418
|
+
if (!await file.exists()) {
|
|
7419
|
+
return null;
|
|
7420
|
+
}
|
|
7421
|
+
const content = await file.text();
|
|
7422
|
+
return JSON.parse(content);
|
|
7423
|
+
} catch (error) {
|
|
7424
|
+
console.error("Failed to load schema index:", error);
|
|
7425
|
+
return null;
|
|
7426
|
+
}
|
|
7427
|
+
}
|
|
7428
|
+
static async buildIndex(config, options) {
|
|
7429
|
+
const hotTableThreshold = options?.hotTableThreshold || 20;
|
|
7430
|
+
const schema = config.schema || {};
|
|
7431
|
+
const tableEntries = Object.entries(schema).map(([tableName, tableData]) => {
|
|
7432
|
+
const size = JSON.stringify(tableData).length;
|
|
7433
|
+
return { tableName, size, data: tableData };
|
|
7434
|
+
});
|
|
7435
|
+
tableEntries.sort((a, b) => b.size - a.size);
|
|
7436
|
+
const hotCount = Math.max(1, Math.ceil(tableEntries.length * hotTableThreshold / 100));
|
|
7437
|
+
const hotTableNames = tableEntries.slice(0, hotCount).map((e) => e.tableName);
|
|
7438
|
+
const index = {
|
|
7439
|
+
tables: {},
|
|
7440
|
+
hotTables: hotTableNames,
|
|
7441
|
+
metadata: {
|
|
7442
|
+
version: "1.0",
|
|
7443
|
+
lastRefreshed: new Date().toISOString(),
|
|
7444
|
+
totalTables: tableEntries.length
|
|
7445
|
+
}
|
|
7446
|
+
};
|
|
7447
|
+
for (const { tableName, size } of tableEntries) {
|
|
7448
|
+
const location = hotTableNames.includes(tableName) ? "hot" : "cold";
|
|
7449
|
+
const file = location === "hot" ? "hot-schemas.json" : `cold/${this.getFileForTable(tableName)}`;
|
|
7450
|
+
index.tables[tableName] = {
|
|
7451
|
+
location,
|
|
7452
|
+
file,
|
|
7453
|
+
estimatedSize: size,
|
|
7454
|
+
lastModified: new Date().toISOString()
|
|
7455
|
+
};
|
|
7456
|
+
}
|
|
7457
|
+
return index;
|
|
7458
|
+
}
|
|
7459
|
+
static async saveIndex(dbcliPath, index, connectionName) {
|
|
7460
|
+
try {
|
|
7461
|
+
const schemasDir = resolveSchemaPath(dbcliPath, connectionName);
|
|
7462
|
+
await this.ensureDir(schemasDir);
|
|
7463
|
+
const indexPath = join4(schemasDir, "index.json");
|
|
7464
|
+
const indexFile = Bun.file(indexPath);
|
|
7465
|
+
await indexFile.write(JSON.stringify(index, null, 2));
|
|
7466
|
+
} catch (error) {
|
|
7467
|
+
throw new Error(`Failed to save schema index: ${error}`);
|
|
7468
|
+
}
|
|
7469
|
+
}
|
|
7470
|
+
static calculateFileMapping(index) {
|
|
7471
|
+
const mapping = {
|
|
7472
|
+
hot: [],
|
|
7473
|
+
cold: []
|
|
7474
|
+
};
|
|
7475
|
+
for (const [tableName, tableInfo] of Object.entries(index.tables)) {
|
|
7476
|
+
mapping[tableInfo.location].push({
|
|
7477
|
+
table: tableName,
|
|
7478
|
+
file: tableInfo.file
|
|
7479
|
+
});
|
|
7480
|
+
}
|
|
7481
|
+
return mapping;
|
|
7482
|
+
}
|
|
7483
|
+
static async ensureDir(dirPath) {
|
|
7484
|
+
const { mkdirSync } = await import("fs");
|
|
7485
|
+
try {
|
|
7486
|
+
mkdirSync(dirPath, { recursive: true });
|
|
7487
|
+
} catch (error) {
|
|
7488
|
+
if (error.code !== "EEXIST") {
|
|
7489
|
+
throw new Error(`Failed to ensure directory ${dirPath}: ${error}`);
|
|
7490
|
+
}
|
|
7491
|
+
}
|
|
7492
|
+
}
|
|
7493
|
+
static getFileForTable(tableName) {
|
|
7494
|
+
if (/^(old_|legacy_|archive_)/.test(tableName)) {
|
|
7495
|
+
return "legacy.json";
|
|
7496
|
+
}
|
|
7497
|
+
return "infrequent.json";
|
|
7498
|
+
}
|
|
7499
|
+
}
|
|
7500
|
+
var init_schema_index = __esm(() => {
|
|
7501
|
+
init_schema_path();
|
|
7502
|
+
});
|
|
7503
|
+
|
|
7504
|
+
// src/core/schema-loader.ts
|
|
7505
|
+
var exports_schema_loader = {};
|
|
7506
|
+
__export(exports_schema_loader, {
|
|
7507
|
+
SchemaLayeredLoader: () => SchemaLayeredLoader2
|
|
7508
|
+
});
|
|
7509
|
+
import { join as join5 } from "path";
|
|
7510
|
+
|
|
7511
|
+
class SchemaLayeredLoader2 {
|
|
7512
|
+
dbcliPath;
|
|
7513
|
+
connectionName;
|
|
7514
|
+
options;
|
|
7515
|
+
cache = null;
|
|
7516
|
+
index = null;
|
|
7517
|
+
loadTime = 0;
|
|
7518
|
+
constructor(dbcliPath, options) {
|
|
7519
|
+
this.dbcliPath = dbcliPath;
|
|
7520
|
+
this.connectionName = options?.connectionName;
|
|
7521
|
+
this.options = {
|
|
7522
|
+
maxCacheItems: options?.maxCacheItems || 100,
|
|
7523
|
+
maxCacheSize: options?.maxCacheSize || 52428800,
|
|
7524
|
+
hotTableThreshold: options?.hotTableThreshold || 20,
|
|
7525
|
+
enableStreaming: options?.enableStreaming || false,
|
|
7526
|
+
streamingTimeout: options?.streamingTimeout || 30000
|
|
7527
|
+
};
|
|
7528
|
+
}
|
|
7529
|
+
async initialize() {
|
|
7530
|
+
const startTime = performance.now();
|
|
7531
|
+
try {
|
|
7532
|
+
await this.ensureDirectories();
|
|
7533
|
+
this.index = await SchemaIndexBuilder.loadIndex(this.dbcliPath, this.connectionName);
|
|
7534
|
+
this.cache = new SchemaCacheManager(this.dbcliPath, {
|
|
7535
|
+
maxCacheItems: this.options.maxCacheItems,
|
|
7536
|
+
maxCacheSize: this.options.maxCacheSize,
|
|
7537
|
+
connectionName: this.connectionName
|
|
7538
|
+
});
|
|
7539
|
+
await this.cache.initialize();
|
|
7540
|
+
this.loadTime = performance.now() - startTime;
|
|
7541
|
+
if (this.loadTime > 50) {
|
|
7542
|
+
console.warn(`Schema initialization took ${this.loadTime.toFixed(2)}ms (target: < 100ms)`);
|
|
7543
|
+
}
|
|
7544
|
+
return {
|
|
7545
|
+
cache: this.cache,
|
|
7546
|
+
index: this.index,
|
|
7547
|
+
loadTime: this.loadTime
|
|
7548
|
+
};
|
|
7549
|
+
} catch (error) {
|
|
7550
|
+
console.error("Failed to initialize schema loader:", error);
|
|
7551
|
+
this.loadTime = performance.now() - startTime;
|
|
7552
|
+
if (!this.cache) {
|
|
7553
|
+
this.cache = new SchemaCacheManager(this.dbcliPath, {
|
|
7554
|
+
maxCacheItems: this.options.maxCacheItems,
|
|
7555
|
+
maxCacheSize: this.options.maxCacheSize,
|
|
7556
|
+
connectionName: this.connectionName
|
|
7557
|
+
});
|
|
7558
|
+
}
|
|
7559
|
+
return {
|
|
7560
|
+
cache: this.cache,
|
|
7561
|
+
index: this.index,
|
|
7562
|
+
loadTime: this.loadTime
|
|
7563
|
+
};
|
|
7564
|
+
}
|
|
7565
|
+
}
|
|
7566
|
+
async loadColdTable(tableName, cache) {
|
|
7567
|
+
try {
|
|
7568
|
+
return await cache.getTableSchema(tableName);
|
|
7569
|
+
} catch (error) {
|
|
7570
|
+
console.error(`Failed to load cold table ${tableName}:`, error);
|
|
7571
|
+
return null;
|
|
7572
|
+
}
|
|
7573
|
+
}
|
|
7574
|
+
async ensureDirectories() {
|
|
7575
|
+
const base = resolveSchemaPath(this.dbcliPath, this.connectionName);
|
|
7576
|
+
const dirs = [base, join5(base, "cold")];
|
|
7577
|
+
for (const dir of dirs) {
|
|
7578
|
+
try {
|
|
7579
|
+
const dirFile = Bun.file(dir);
|
|
7580
|
+
if (!await dirFile.exists()) {
|
|
7581
|
+
const proc = Bun.spawn(["mkdir", "-p", dir]);
|
|
7582
|
+
const exitCode = await proc.exited;
|
|
7583
|
+
if (exitCode !== 0) {
|
|
7584
|
+
console.error(`Failed to create directory: ${dir}`);
|
|
7585
|
+
}
|
|
7586
|
+
}
|
|
7587
|
+
} catch (error) {
|
|
7588
|
+
console.error(`Error ensuring directory ${dir}:`, error);
|
|
7589
|
+
}
|
|
7590
|
+
}
|
|
7591
|
+
}
|
|
7592
|
+
getBenchmark() {
|
|
7593
|
+
if (!this.cache || !this.index) {
|
|
7594
|
+
return {
|
|
7595
|
+
initTime: this.loadTime,
|
|
7596
|
+
hotTables: 0,
|
|
7597
|
+
totalTables: 0,
|
|
7598
|
+
estimatedSize: 0
|
|
7599
|
+
};
|
|
7600
|
+
}
|
|
7601
|
+
const estimatedSize = Object.values(this.index.tables).reduce((sum, table) => sum + (table.estimatedSize || 0), 0);
|
|
7602
|
+
return {
|
|
7603
|
+
initTime: this.loadTime,
|
|
7604
|
+
hotTables: this.index.hotTables.length,
|
|
7605
|
+
totalTables: this.index.metadata.totalTables,
|
|
7606
|
+
estimatedSize
|
|
7607
|
+
};
|
|
7608
|
+
}
|
|
7609
|
+
}
|
|
7610
|
+
var init_schema_loader = __esm(() => {
|
|
7611
|
+
init_schema_cache();
|
|
7612
|
+
init_schema_index();
|
|
7613
|
+
init_schema_path();
|
|
6171
7614
|
});
|
|
6172
7615
|
|
|
6173
7616
|
// node_modules/@inquirer/core/dist/esm/lib/key.mjs
|
|
@@ -6427,7 +7870,7 @@ function isUnicodeSupported() {
|
|
|
6427
7870
|
return Boolean(process2.env["WT_SESSION"]) || Boolean(process2.env["TERMINUS_SUBLIME"]) || process2.env["ConEmuTask"] === "{cmd::Cmder}" || process2.env["TERM_PROGRAM"] === "Terminus-Sublime" || process2.env["TERM_PROGRAM"] === "vscode" || process2.env["TERM"] === "xterm-256color" || process2.env["TERM"] === "alacritty" || process2.env["TERMINAL_EMULATOR"] === "JetBrains-JediTerm";
|
|
6428
7871
|
}
|
|
6429
7872
|
var common, specialMainSymbols, specialFallbackSymbols, mainSymbols, fallbackSymbols, shouldUseMain, figures, esm_default, replacements;
|
|
6430
|
-
var
|
|
7873
|
+
var init_esm2 = __esm(() => {
|
|
6431
7874
|
common = {
|
|
6432
7875
|
circleQuestionMark: "(?)",
|
|
6433
7876
|
questionMarkPrefix: "(?)",
|
|
@@ -6713,7 +8156,7 @@ var init_esm = __esm(() => {
|
|
|
6713
8156
|
// node_modules/@inquirer/core/dist/esm/lib/theme.mjs
|
|
6714
8157
|
var import_yoctocolors_cjs, defaultTheme;
|
|
6715
8158
|
var init_theme = __esm(() => {
|
|
6716
|
-
|
|
8159
|
+
init_esm2();
|
|
6717
8160
|
import_yoctocolors_cjs = __toESM(require_yoctocolors_cjs(), 1);
|
|
6718
8161
|
defaultTheme = {
|
|
6719
8162
|
prefix: {
|
|
@@ -8925,12 +10368,12 @@ class Separator {
|
|
|
8925
10368
|
}
|
|
8926
10369
|
var import_yoctocolors_cjs2;
|
|
8927
10370
|
var init_Separator = __esm(() => {
|
|
8928
|
-
|
|
10371
|
+
init_esm2();
|
|
8929
10372
|
import_yoctocolors_cjs2 = __toESM(require_yoctocolors_cjs(), 1);
|
|
8930
10373
|
});
|
|
8931
10374
|
|
|
8932
10375
|
// node_modules/@inquirer/core/dist/esm/index.mjs
|
|
8933
|
-
var
|
|
10376
|
+
var init_esm3 = __esm(() => {
|
|
8934
10377
|
init_use_prefix();
|
|
8935
10378
|
init_use_state();
|
|
8936
10379
|
init_use_effect();
|
|
@@ -8984,10 +10427,10 @@ function normalizeChoices(choices) {
|
|
|
8984
10427
|
});
|
|
8985
10428
|
}
|
|
8986
10429
|
var import_yoctocolors_cjs3, import_ansi_escapes2, checkboxTheme, esm_default2;
|
|
8987
|
-
var
|
|
8988
|
-
|
|
8989
|
-
init_esm();
|
|
10430
|
+
var init_esm4 = __esm(() => {
|
|
10431
|
+
init_esm3();
|
|
8990
10432
|
init_esm2();
|
|
10433
|
+
init_esm3();
|
|
8991
10434
|
import_yoctocolors_cjs3 = __toESM(require_yoctocolors_cjs(), 1);
|
|
8992
10435
|
import_ansi_escapes2 = __toESM(require_ansi_escapes(), 1);
|
|
8993
10436
|
checkboxTheme = {
|
|
@@ -18489,8 +19932,8 @@ var require_main = __commonJS((exports) => {
|
|
|
18489
19932
|
// node_modules/@inquirer/editor/dist/esm/index.mjs
|
|
18490
19933
|
import { AsyncResource as AsyncResource4 } from "async_hooks";
|
|
18491
19934
|
var import_external_editor, esm_default3;
|
|
18492
|
-
var
|
|
18493
|
-
|
|
19935
|
+
var init_esm5 = __esm(() => {
|
|
19936
|
+
init_esm3();
|
|
18494
19937
|
import_external_editor = __toESM(require_main(), 1);
|
|
18495
19938
|
esm_default3 = createPrompt((config, done) => {
|
|
18496
19939
|
const { waitForUseInput = true, postfix = ".txt", validate = () => true } = config;
|
|
@@ -18553,8 +19996,8 @@ var init_esm4 = __esm(() => {
|
|
|
18553
19996
|
|
|
18554
19997
|
// node_modules/@inquirer/confirm/dist/esm/index.mjs
|
|
18555
19998
|
var esm_default4;
|
|
18556
|
-
var
|
|
18557
|
-
|
|
19999
|
+
var init_esm6 = __esm(() => {
|
|
20000
|
+
init_esm3();
|
|
18558
20001
|
esm_default4 = createPrompt((config, done) => {
|
|
18559
20002
|
const { transformer = (answer) => answer ? "yes" : "no" } = config;
|
|
18560
20003
|
const [status, setStatus] = useState("pending");
|
|
@@ -18589,8 +20032,8 @@ var init_esm5 = __esm(() => {
|
|
|
18589
20032
|
|
|
18590
20033
|
// node_modules/@inquirer/input/dist/esm/index.mjs
|
|
18591
20034
|
var esm_default5;
|
|
18592
|
-
var
|
|
18593
|
-
|
|
20035
|
+
var init_esm7 = __esm(() => {
|
|
20036
|
+
init_esm3();
|
|
18594
20037
|
esm_default5 = createPrompt((config, done) => {
|
|
18595
20038
|
const { required, validate = () => true } = config;
|
|
18596
20039
|
const theme = makeTheme(config.theme);
|
|
@@ -18669,8 +20112,8 @@ function validateNumber(value, { min, max, step }) {
|
|
|
18669
20112
|
return true;
|
|
18670
20113
|
}
|
|
18671
20114
|
var esm_default6;
|
|
18672
|
-
var
|
|
18673
|
-
|
|
20115
|
+
var init_esm8 = __esm(() => {
|
|
20116
|
+
init_esm3();
|
|
18674
20117
|
esm_default6 = createPrompt((config, done) => {
|
|
18675
20118
|
const { validate = () => true, min = -Infinity, max = Infinity, step = 1, required = false } = config;
|
|
18676
20119
|
const theme = makeTheme(config.theme);
|
|
@@ -18753,8 +20196,8 @@ function normalizeChoices2(choices) {
|
|
|
18753
20196
|
});
|
|
18754
20197
|
}
|
|
18755
20198
|
var import_yoctocolors_cjs4, helpChoice, esm_default7;
|
|
18756
|
-
var
|
|
18757
|
-
|
|
20199
|
+
var init_esm9 = __esm(() => {
|
|
20200
|
+
init_esm3();
|
|
18758
20201
|
import_yoctocolors_cjs4 = __toESM(require_yoctocolors_cjs(), 1);
|
|
18759
20202
|
helpChoice = {
|
|
18760
20203
|
key: "h",
|
|
@@ -18867,8 +20310,8 @@ function normalizeChoices3(choices) {
|
|
|
18867
20310
|
});
|
|
18868
20311
|
}
|
|
18869
20312
|
var import_yoctocolors_cjs5, numberRegex, esm_default8;
|
|
18870
|
-
var
|
|
18871
|
-
|
|
20313
|
+
var init_esm10 = __esm(() => {
|
|
20314
|
+
init_esm3();
|
|
18872
20315
|
import_yoctocolors_cjs5 = __toESM(require_yoctocolors_cjs(), 1);
|
|
18873
20316
|
numberRegex = /\d+/;
|
|
18874
20317
|
esm_default8 = createPrompt((config, done) => {
|
|
@@ -18930,8 +20373,8 @@ var init_esm9 = __esm(() => {
|
|
|
18930
20373
|
|
|
18931
20374
|
// node_modules/@inquirer/password/dist/esm/index.mjs
|
|
18932
20375
|
var import_ansi_escapes3, esm_default9;
|
|
18933
|
-
var
|
|
18934
|
-
|
|
20376
|
+
var init_esm11 = __esm(() => {
|
|
20377
|
+
init_esm3();
|
|
18935
20378
|
import_ansi_escapes3 = __toESM(require_ansi_escapes(), 1);
|
|
18936
20379
|
esm_default9 = createPrompt((config, done) => {
|
|
18937
20380
|
const { validate = () => true } = config;
|
|
@@ -19010,9 +20453,9 @@ function normalizeChoices4(choices) {
|
|
|
19010
20453
|
});
|
|
19011
20454
|
}
|
|
19012
20455
|
var import_yoctocolors_cjs6, searchTheme, esm_default10;
|
|
19013
|
-
var
|
|
20456
|
+
var init_esm12 = __esm(() => {
|
|
20457
|
+
init_esm3();
|
|
19014
20458
|
init_esm2();
|
|
19015
|
-
init_esm();
|
|
19016
20459
|
import_yoctocolors_cjs6 = __toESM(require_yoctocolors_cjs(), 1);
|
|
19017
20460
|
searchTheme = {
|
|
19018
20461
|
icon: { cursor: esm_default.pointer },
|
|
@@ -19179,9 +20622,9 @@ function normalizeChoices5(choices) {
|
|
|
19179
20622
|
});
|
|
19180
20623
|
}
|
|
19181
20624
|
var import_yoctocolors_cjs7, import_ansi_escapes4, selectTheme, esm_default11;
|
|
19182
|
-
var
|
|
20625
|
+
var init_esm13 = __esm(() => {
|
|
20626
|
+
init_esm3();
|
|
19183
20627
|
init_esm2();
|
|
19184
|
-
init_esm();
|
|
19185
20628
|
import_yoctocolors_cjs7 = __toESM(require_yoctocolors_cjs(), 1);
|
|
19186
20629
|
import_ansi_escapes4 = __toESM(require_ansi_escapes(), 1);
|
|
19187
20630
|
selectTheme = {
|
|
@@ -19312,8 +20755,7 @@ __export(exports_esm, {
|
|
|
19312
20755
|
checkbox: () => esm_default2,
|
|
19313
20756
|
Separator: () => Separator
|
|
19314
20757
|
});
|
|
19315
|
-
var
|
|
19316
|
-
init_esm3();
|
|
20758
|
+
var init_esm14 = __esm(() => {
|
|
19317
20759
|
init_esm4();
|
|
19318
20760
|
init_esm5();
|
|
19319
20761
|
init_esm6();
|
|
@@ -19323,6 +20765,7 @@ var init_esm13 = __esm(() => {
|
|
|
19323
20765
|
init_esm10();
|
|
19324
20766
|
init_esm11();
|
|
19325
20767
|
init_esm12();
|
|
20768
|
+
init_esm13();
|
|
19326
20769
|
});
|
|
19327
20770
|
|
|
19328
20771
|
// node_modules/postgres-array/index.js
|
|
@@ -43012,7 +44455,7 @@ var require_named_placeholders = __commonJS((exports, module) => {
|
|
|
43012
44455
|
}
|
|
43013
44456
|
return s;
|
|
43014
44457
|
}
|
|
43015
|
-
function
|
|
44458
|
+
function join7(tree) {
|
|
43016
44459
|
if (tree.length === 1) {
|
|
43017
44460
|
return tree;
|
|
43018
44461
|
}
|
|
@@ -43038,7 +44481,7 @@ var require_named_placeholders = __commonJS((exports, module) => {
|
|
|
43038
44481
|
if (cache && (tree = cache.get(query))) {
|
|
43039
44482
|
return toArrayParams(tree, paramsObj);
|
|
43040
44483
|
}
|
|
43041
|
-
tree =
|
|
44484
|
+
tree = join7(parse(query));
|
|
43042
44485
|
if (cache) {
|
|
43043
44486
|
cache.set(query, tree);
|
|
43044
44487
|
}
|
|
@@ -46864,7 +48307,7 @@ var {
|
|
|
46864
48307
|
// package.json
|
|
46865
48308
|
var package_default = {
|
|
46866
48309
|
name: "@carllee1983/dbcli",
|
|
46867
|
-
version: "1.
|
|
48310
|
+
version: "1.4.1",
|
|
46868
48311
|
description: "Database CLI for AI agents",
|
|
46869
48312
|
type: "module",
|
|
46870
48313
|
publishConfig: {
|
|
@@ -47066,9 +48509,27 @@ Hint: run 'export {envKey}=<value>' and retry`
|
|
|
47066
48509
|
formats: "Supported formats: json, csv",
|
|
47067
48510
|
exported: "Exported {count} rows to {file}"
|
|
47068
48511
|
},
|
|
48512
|
+
upgrade: {
|
|
48513
|
+
description: "Check for updates and upgrade dbcli to the latest version",
|
|
48514
|
+
checking: "Checking for updates...",
|
|
48515
|
+
already_up_to_date: "\u2713 Already up to date (v{version})",
|
|
48516
|
+
new_version_available: "New version available: v{version}",
|
|
48517
|
+
current_version: "Current version : v{version}",
|
|
48518
|
+
latest_version: "Latest version : v{version}",
|
|
48519
|
+
update_hint: '[INFO] dbcli v{version} available. Run "dbcli upgrade" to upgrade.',
|
|
48520
|
+
install_hint: 'Run "dbcli upgrade" to install the update.',
|
|
48521
|
+
upgrading: "Upgrading...",
|
|
48522
|
+
success: "\u2713 Successfully upgraded to v{version}",
|
|
48523
|
+
skill_recheck_hint: 'Hint: If you have installed AI skills, run "dbcli skill --install <platform>" to ensure they are up to date.',
|
|
48524
|
+
failed: "\u2717 Upgrade failed. Try running manually:",
|
|
48525
|
+
manual_hint: "bun add -g @carllee1983/dbcli@latest",
|
|
48526
|
+
network_error: "\u26A0 Could not check for updates (network error or registry unavailable)"
|
|
48527
|
+
},
|
|
47069
48528
|
skill: {
|
|
47070
48529
|
description: "Generate AI skill documentation",
|
|
47071
|
-
installed: "Skill installed to {path}"
|
|
48530
|
+
installed: "Skill installed to {path}",
|
|
48531
|
+
update_available: "Skill updates available for the following platforms:",
|
|
48532
|
+
update_hint: 'Run "dbcli skill --install <platform>" to update.'
|
|
47072
48533
|
},
|
|
47073
48534
|
blacklist: {
|
|
47074
48535
|
description: "Manage sensitive data blacklist to prevent AI access",
|
|
@@ -47238,9 +48699,27 @@ var messages_default2 = {
|
|
|
47238
48699
|
formats: "\u652F\u63F4\u7684\u683C\u5F0F\uFF1Ajson\u3001csv",
|
|
47239
48700
|
exported: "\u5DF2\u5C07 {count} \u5217\u532F\u51FA\u81F3 {file}"
|
|
47240
48701
|
},
|
|
48702
|
+
upgrade: {
|
|
48703
|
+
description: "\u6AA2\u67E5\u66F4\u65B0\u4E26\u5C07 dbcli \u5347\u7D1A\u81F3\u6700\u65B0\u7248\u672C",
|
|
48704
|
+
checking: "\u6B63\u5728\u6AA2\u67E5\u66F4\u65B0...",
|
|
48705
|
+
already_up_to_date: "\u2713 \u5DF2\u7D93\u662F\u6700\u65B0\u7248\u672C (v{version})",
|
|
48706
|
+
new_version_available: "\u767C\u73FE\u65B0\u7248\u672C\uFF1Av{version}",
|
|
48707
|
+
current_version: "\u76EE\u524D\u7248\u672C\uFF1Av{version}",
|
|
48708
|
+
latest_version: "\u6700\u65B0\u7248\u672C\uFF1Av{version}",
|
|
48709
|
+
update_hint: '[\u63D0\u793A] dbcli v{version} \u5DF2\u767C\u5E03\u3002\u57F7\u884C "dbcli upgrade" \u9032\u884C\u5347\u7D1A\u3002',
|
|
48710
|
+
install_hint: '\u57F7\u884C "dbcli upgrade" \u5B89\u88DD\u66F4\u65B0\u3002',
|
|
48711
|
+
upgrading: "\u6B63\u5728\u5347\u7D1A...",
|
|
48712
|
+
success: "\u2713 \u6210\u529F\u5347\u7D1A\u81F3 v{version}",
|
|
48713
|
+
skill_recheck_hint: '\u63D0\u793A\uFF1A\u82E5\u5DF2\u5B89\u88DD AI \u6280\u80FD\uFF0C\u8ACB\u57F7\u884C "dbcli skill --install <platform>" \u78BA\u8A8D\u6280\u80FD\u70BA\u6700\u65B0\u7248\u672C\u3002',
|
|
48714
|
+
failed: "\u2717 \u5347\u7D1A\u5931\u6557\u3002\u8ACB\u5617\u8A66\u624B\u52D5\u57F7\u884C\uFF1A",
|
|
48715
|
+
manual_hint: "bun add -g @carllee1983/dbcli@latest",
|
|
48716
|
+
network_error: "\u26A0 \u7121\u6CD5\u6AA2\u67E5\u66F4\u65B0\uFF08\u7DB2\u8DEF\u932F\u8AA4\u6216\u8A3B\u518A\u8868\u4E0D\u53EF\u7528\uFF09"
|
|
48717
|
+
},
|
|
47241
48718
|
skill: {
|
|
47242
48719
|
description: "\u751F\u6210 AI \u6280\u80FD\u6587\u6A94",
|
|
47243
|
-
installed: "\u6280\u80FD\u5DF2\u5B89\u88DD\u81F3 {path}"
|
|
48720
|
+
installed: "\u6280\u80FD\u5DF2\u5B89\u88DD\u81F3 {path}",
|
|
48721
|
+
update_available: "\u4EE5\u4E0B\u5E73\u53F0\u7684\u6280\u80FD\u9700\u8981\u66F4\u65B0\uFF1A",
|
|
48722
|
+
update_hint: '\u57F7\u884C "dbcli skill --install <platform>" \u9032\u884C\u66F4\u65B0\u3002'
|
|
47244
48723
|
},
|
|
47245
48724
|
blacklist: {
|
|
47246
48725
|
description: "\u7BA1\u7406\u654F\u611F\u8CC7\u6599\u9ED1\u540D\u55AE\u4EE5\u9632\u6B62 AI \u5B58\u53D6",
|
|
@@ -47444,7 +48923,7 @@ function getLogger() {
|
|
|
47444
48923
|
}
|
|
47445
48924
|
|
|
47446
48925
|
// src/commands/init.ts
|
|
47447
|
-
import { join as
|
|
48926
|
+
import { join as join7 } from "path";
|
|
47448
48927
|
|
|
47449
48928
|
// src/utils/errors.ts
|
|
47450
48929
|
class EnvParseError extends Error {
|
|
@@ -47631,13 +49110,51 @@ async function writeV2Config(path, config) {
|
|
|
47631
49110
|
const json = JSON.stringify(config, null, 2);
|
|
47632
49111
|
await Bun.write(configPath, json);
|
|
47633
49112
|
}
|
|
49113
|
+
async function patchConnectionSchema(dbcliPath, connectionName, schema, metadataUpdate) {
|
|
49114
|
+
const v2Config = await readV2Config(dbcliPath);
|
|
49115
|
+
const updated = {
|
|
49116
|
+
...v2Config,
|
|
49117
|
+
schemas: {
|
|
49118
|
+
...v2Config.schemas,
|
|
49119
|
+
[connectionName]: schema
|
|
49120
|
+
},
|
|
49121
|
+
metadata: {
|
|
49122
|
+
...v2Config.metadata,
|
|
49123
|
+
...metadataUpdate ?? {}
|
|
49124
|
+
}
|
|
49125
|
+
};
|
|
49126
|
+
await writeV2Config(dbcliPath, updated);
|
|
49127
|
+
}
|
|
47634
49128
|
|
|
47635
49129
|
// src/core/config.ts
|
|
47636
|
-
import { join as
|
|
49130
|
+
import { join as join6 } from "path";
|
|
47637
49131
|
var _globalConnectionName;
|
|
47638
49132
|
function setGlobalConnectionName(name) {
|
|
47639
49133
|
_globalConnectionName = name;
|
|
47640
49134
|
}
|
|
49135
|
+
function getGlobalConnectionName() {
|
|
49136
|
+
return _globalConnectionName;
|
|
49137
|
+
}
|
|
49138
|
+
async function getSchemaIsolationConnectionName(dbcliPath) {
|
|
49139
|
+
const effectiveName = getGlobalConnectionName();
|
|
49140
|
+
try {
|
|
49141
|
+
const stat = await Bun.file(dbcliPath).stat();
|
|
49142
|
+
const isDirectory = stat?.isDirectory() ?? false;
|
|
49143
|
+
if (!isDirectory)
|
|
49144
|
+
return;
|
|
49145
|
+
const configJsonPath = join6(dbcliPath, "config.json");
|
|
49146
|
+
const configFile = Bun.file(configJsonPath);
|
|
49147
|
+
if (!await configFile.exists())
|
|
49148
|
+
return;
|
|
49149
|
+
const raw = JSON.parse(await configFile.text());
|
|
49150
|
+
if (detectConfigVersion(raw) !== 2)
|
|
49151
|
+
return;
|
|
49152
|
+
const v2Config = DbcliConfigV2Schema.parse(raw);
|
|
49153
|
+
return resolveConnection(v2Config, effectiveName).name;
|
|
49154
|
+
} catch {
|
|
49155
|
+
return;
|
|
49156
|
+
}
|
|
49157
|
+
}
|
|
47641
49158
|
var DEFAULT_CONFIG = {
|
|
47642
49159
|
connection: {
|
|
47643
49160
|
system: "postgresql",
|
|
@@ -47705,7 +49222,7 @@ var configModule = {
|
|
|
47705
49222
|
isDirectory = false;
|
|
47706
49223
|
}
|
|
47707
49224
|
if (isDirectory) {
|
|
47708
|
-
const configPath =
|
|
49225
|
+
const configPath = join6(path, "config.json");
|
|
47709
49226
|
const configFile = Bun.file(configPath);
|
|
47710
49227
|
const configExists = await configFile.exists();
|
|
47711
49228
|
if (configExists) {
|
|
@@ -47715,7 +49232,7 @@ var configModule = {
|
|
|
47715
49232
|
const v2Config = DbcliConfigV2Schema.parse(config);
|
|
47716
49233
|
const resolved = resolveConnection(v2Config, effectiveConnectionName);
|
|
47717
49234
|
await loadConnectionEnv(resolved, path);
|
|
47718
|
-
const envLocalPath =
|
|
49235
|
+
const envLocalPath = join6(path, ".env.local");
|
|
47719
49236
|
const envLocalFile = Bun.file(envLocalPath);
|
|
47720
49237
|
let legacyPassword = null;
|
|
47721
49238
|
if (await envLocalFile.exists()) {
|
|
@@ -47729,16 +49246,35 @@ var configModule = {
|
|
|
47729
49246
|
if (!resolvedConnection.password && legacyPassword) {
|
|
47730
49247
|
resolvedConnection.password = legacyPassword;
|
|
47731
49248
|
}
|
|
49249
|
+
let schema = (v2Config.schemas ?? {})[resolved.name] ?? v2Config.schema;
|
|
49250
|
+
try {
|
|
49251
|
+
const { SchemaLayeredLoader: SchemaLayeredLoader3 } = await Promise.resolve().then(() => (init_schema_loader(), exports_schema_loader));
|
|
49252
|
+
const loader = new SchemaLayeredLoader3(path, { connectionName: resolved.name });
|
|
49253
|
+
const { cache, index } = await loader.initialize();
|
|
49254
|
+
if (index && Object.keys(index.tables).length > 0) {
|
|
49255
|
+
const layeredSchema = {};
|
|
49256
|
+
for (const tableName of Object.keys(index.tables)) {
|
|
49257
|
+
const s = await cache.getTableSchema(tableName);
|
|
49258
|
+
if (s)
|
|
49259
|
+
layeredSchema[tableName] = s;
|
|
49260
|
+
}
|
|
49261
|
+
if (Object.keys(layeredSchema).length > 0) {
|
|
49262
|
+
schema = layeredSchema;
|
|
49263
|
+
}
|
|
49264
|
+
}
|
|
49265
|
+
} catch (error) {
|
|
49266
|
+
console.warn("Warning: Failed to load layered schema cache, falling back to config.json");
|
|
49267
|
+
}
|
|
47732
49268
|
return DbcliConfigSchema.parse({
|
|
47733
49269
|
connection: resolvedConnection,
|
|
47734
49270
|
permission: resolved.permission,
|
|
47735
|
-
schema
|
|
49271
|
+
schema,
|
|
47736
49272
|
metadata: v2Config.metadata,
|
|
47737
49273
|
blacklist: v2Config.blacklist
|
|
47738
49274
|
});
|
|
47739
49275
|
}
|
|
47740
49276
|
const resolvedConfig = resolveEnvReferences(config, process.env, undefined, false);
|
|
47741
|
-
const envPath =
|
|
49277
|
+
const envPath = join6(path, ".env.local");
|
|
47742
49278
|
const envFile = Bun.file(envPath);
|
|
47743
49279
|
if (await envFile.exists()) {
|
|
47744
49280
|
const envContent = await envFile.text();
|
|
@@ -47805,7 +49341,7 @@ var configModule = {
|
|
|
47805
49341
|
if (isDirectory || path.endsWith(".dbcli")) {
|
|
47806
49342
|
const hasEnvReferences = isEnvReference(config.connection.password);
|
|
47807
49343
|
if (hasEnvReferences) {
|
|
47808
|
-
const configPath =
|
|
49344
|
+
const configPath = join6(path, "config.json");
|
|
47809
49345
|
const configJson = JSON.stringify(config, null, 2);
|
|
47810
49346
|
await Bun.file(configPath).write(configJson);
|
|
47811
49347
|
} else {
|
|
@@ -47818,11 +49354,11 @@ var configModule = {
|
|
|
47818
49354
|
}
|
|
47819
49355
|
};
|
|
47820
49356
|
delete configWithoutPassword.connection.password;
|
|
47821
|
-
const configPath =
|
|
49357
|
+
const configPath = join6(path, "config.json");
|
|
47822
49358
|
const configJson = JSON.stringify(configWithoutPassword, null, 2);
|
|
47823
49359
|
await Bun.file(configPath).write(configJson);
|
|
47824
49360
|
if (password) {
|
|
47825
|
-
const envPath =
|
|
49361
|
+
const envPath = join6(path, ".env.local");
|
|
47826
49362
|
const envContent = `# Database Credentials - DO NOT commit to git
|
|
47827
49363
|
|
|
47828
49364
|
DBCLI_PASSWORD=${password}
|
|
@@ -47879,7 +49415,7 @@ async function text(message, defaultValue) {
|
|
|
47879
49415
|
return answer.trim() || defaultValue || "";
|
|
47880
49416
|
}
|
|
47881
49417
|
try {
|
|
47882
|
-
const { text: inquirerText } = await Promise.resolve().then(() => (
|
|
49418
|
+
const { text: inquirerText } = await Promise.resolve().then(() => (init_esm14(), exports_esm));
|
|
47883
49419
|
return await inquirerText({ message, default: defaultValue });
|
|
47884
49420
|
} catch {
|
|
47885
49421
|
const displayMessage = defaultValue ? `${message} [${defaultValue}]: ` : `${message}: `;
|
|
@@ -47901,7 +49437,7 @@ async function select(message, choices) {
|
|
|
47901
49437
|
return choices[0];
|
|
47902
49438
|
}
|
|
47903
49439
|
try {
|
|
47904
|
-
const { select: inquirerSelect } = await Promise.resolve().then(() => (
|
|
49440
|
+
const { select: inquirerSelect } = await Promise.resolve().then(() => (init_esm14(), exports_esm));
|
|
47905
49441
|
return await inquirerSelect({ message, choices });
|
|
47906
49442
|
} catch {
|
|
47907
49443
|
console.log(message);
|
|
@@ -47922,7 +49458,7 @@ async function confirm(message) {
|
|
|
47922
49458
|
return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
|
|
47923
49459
|
}
|
|
47924
49460
|
try {
|
|
47925
|
-
const { confirm: inquirerConfirm } = await Promise.resolve().then(() => (
|
|
49461
|
+
const { confirm: inquirerConfirm } = await Promise.resolve().then(() => (init_esm14(), exports_esm));
|
|
47926
49462
|
return await inquirerConfirm({ message });
|
|
47927
49463
|
} catch {
|
|
47928
49464
|
const answer = await readLineFromStdin(`${message} (y/n): `);
|
|
@@ -48618,7 +50154,7 @@ class AdapterFactory {
|
|
|
48618
50154
|
var VALID_PERMISSIONS = ["query-only", "read-write", "data-admin", "admin"];
|
|
48619
50155
|
async function checkOverwrite(configPath, shouldPrompt, force) {
|
|
48620
50156
|
const fileExists = await Bun.file(configPath).exists();
|
|
48621
|
-
const dirConfigExists = await Bun.file(
|
|
50157
|
+
const dirConfigExists = await Bun.file(join7(configPath, "config.json")).exists();
|
|
48622
50158
|
if (!fileExists && !dirConfigExists || force)
|
|
48623
50159
|
return true;
|
|
48624
50160
|
if (shouldPrompt) {
|
|
@@ -48632,7 +50168,7 @@ async function checkOverwrite(configPath, shouldPrompt, force) {
|
|
|
48632
50168
|
throw new Error(t("init.config_exists_use_force"));
|
|
48633
50169
|
}
|
|
48634
50170
|
async function handleRemove(configPath, name) {
|
|
48635
|
-
const configFile = Bun.file(
|
|
50171
|
+
const configFile = Bun.file(join7(configPath, "config.json"));
|
|
48636
50172
|
if (!await configFile.exists()) {
|
|
48637
50173
|
throw new Error(t("init.config_not_found"));
|
|
48638
50174
|
}
|
|
@@ -48667,7 +50203,7 @@ async function handleRename(configPath, renameArg) {
|
|
|
48667
50203
|
if (!oldName || !newName) {
|
|
48668
50204
|
throw new Error(t("init.rename_invalid_format"));
|
|
48669
50205
|
}
|
|
48670
|
-
const configFile = Bun.file(
|
|
50206
|
+
const configFile = Bun.file(join7(configPath, "config.json"));
|
|
48671
50207
|
if (!await configFile.exists()) {
|
|
48672
50208
|
throw new Error(t("init.config_not_found"));
|
|
48673
50209
|
}
|
|
@@ -48692,7 +50228,7 @@ async function handleRename(configPath, renameArg) {
|
|
|
48692
50228
|
console.log(t_vars("init.connection_renamed", { oldName, newName }));
|
|
48693
50229
|
}
|
|
48694
50230
|
async function writeV2InitConfig(configPath, connectionName, connection, permission, envFile) {
|
|
48695
|
-
const configJsonPath =
|
|
50231
|
+
const configJsonPath = join7(configPath, "config.json");
|
|
48696
50232
|
const configFile = Bun.file(configJsonPath);
|
|
48697
50233
|
let existingV2 = null;
|
|
48698
50234
|
if (await configFile.exists()) {
|
|
@@ -49266,6 +50802,582 @@ class SchemaDiffEngine {
|
|
|
49266
50802
|
}
|
|
49267
50803
|
}
|
|
49268
50804
|
|
|
50805
|
+
// src/core/index.ts
|
|
50806
|
+
init_schema_loader();
|
|
50807
|
+
init_schema_index();
|
|
50808
|
+
init_schema_cache();
|
|
50809
|
+
|
|
50810
|
+
// src/core/schema-writer.ts
|
|
50811
|
+
init_schema_index();
|
|
50812
|
+
import { join as join8 } from "path";
|
|
50813
|
+
|
|
50814
|
+
// src/core/atomic-writer.ts
|
|
50815
|
+
class AtomicFileWriter {
|
|
50816
|
+
async write(filePath, content, options) {
|
|
50817
|
+
const startTime = Date.now();
|
|
50818
|
+
const timeout = options?.timeout || 5000;
|
|
50819
|
+
const createBackup = options?.createBackup ?? true;
|
|
50820
|
+
const timestamp = Date.now();
|
|
50821
|
+
const tempPath = `${filePath}.${timestamp}.tmp`;
|
|
50822
|
+
const backupPath = `${filePath}.backup.${timestamp}`;
|
|
50823
|
+
let backupCreated = false;
|
|
50824
|
+
let tempFileCreated = false;
|
|
50825
|
+
try {
|
|
50826
|
+
this.checkTimeout(startTime, timeout, "write initialization");
|
|
50827
|
+
const buffer = typeof content === "string" ? Buffer.from(content, "utf-8") : content;
|
|
50828
|
+
const tempFile = Bun.file(tempPath);
|
|
50829
|
+
await Bun.write(tempFile, buffer);
|
|
50830
|
+
tempFileCreated = true;
|
|
50831
|
+
this.checkTimeout(startTime, timeout, "temp file write");
|
|
50832
|
+
if (createBackup) {
|
|
50833
|
+
const originalFile = Bun.file(filePath);
|
|
50834
|
+
if (await originalFile.exists()) {
|
|
50835
|
+
const originalContent = await originalFile.arrayBuffer();
|
|
50836
|
+
const backupFile = Bun.file(backupPath);
|
|
50837
|
+
await Bun.write(backupFile, originalContent);
|
|
50838
|
+
backupCreated = true;
|
|
50839
|
+
}
|
|
50840
|
+
}
|
|
50841
|
+
this.checkTimeout(startTime, timeout, "backup creation");
|
|
50842
|
+
const moveResult = await Bun.spawn(["mv", tempPath, filePath]).exited;
|
|
50843
|
+
if (moveResult !== 0) {
|
|
50844
|
+
throw new Error(`Atomic rename failed with exit code ${moveResult}`);
|
|
50845
|
+
}
|
|
50846
|
+
tempFileCreated = false;
|
|
50847
|
+
const writtenFile = Bun.file(filePath);
|
|
50848
|
+
const written = await writtenFile.exists();
|
|
50849
|
+
if (!written) {
|
|
50850
|
+
throw new Error("Verification failed: file not found after write");
|
|
50851
|
+
}
|
|
50852
|
+
const sizeBytes = writtenFile.size || buffer.length;
|
|
50853
|
+
return {
|
|
50854
|
+
filePath,
|
|
50855
|
+
sizeBytes,
|
|
50856
|
+
timestamp: new Date().toISOString(),
|
|
50857
|
+
backupCreated,
|
|
50858
|
+
backupPath: backupCreated ? backupPath : undefined
|
|
50859
|
+
};
|
|
50860
|
+
} catch (error) {
|
|
50861
|
+
await this.cleanup(tempPath, tempFileCreated);
|
|
50862
|
+
throw new Error(`Atomic write failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
50863
|
+
}
|
|
50864
|
+
}
|
|
50865
|
+
async writeJSON(filePath, data, options) {
|
|
50866
|
+
const jsonContent = JSON.stringify(data, null, 2);
|
|
50867
|
+
return this.write(filePath, jsonContent, options);
|
|
50868
|
+
}
|
|
50869
|
+
async read(filePath, timeout = 5000) {
|
|
50870
|
+
const startTime = Date.now();
|
|
50871
|
+
this.checkTimeout(startTime, timeout, "read start");
|
|
50872
|
+
const file = Bun.file(filePath);
|
|
50873
|
+
if (!await file.exists()) {
|
|
50874
|
+
throw new Error(`File not found: ${filePath}`);
|
|
50875
|
+
}
|
|
50876
|
+
this.checkTimeout(startTime, timeout, "file existence check");
|
|
50877
|
+
return await file.text();
|
|
50878
|
+
}
|
|
50879
|
+
async backup(filePath) {
|
|
50880
|
+
const file = Bun.file(filePath);
|
|
50881
|
+
if (!await file.exists()) {
|
|
50882
|
+
return null;
|
|
50883
|
+
}
|
|
50884
|
+
const timestamp = Date.now();
|
|
50885
|
+
const backupPath = `${filePath}.backup.${timestamp}`;
|
|
50886
|
+
const content = await file.arrayBuffer();
|
|
50887
|
+
const backupFile = Bun.file(backupPath);
|
|
50888
|
+
await Bun.write(backupFile, content);
|
|
50889
|
+
return backupPath;
|
|
50890
|
+
}
|
|
50891
|
+
async restore(backupPath, targetPath) {
|
|
50892
|
+
try {
|
|
50893
|
+
const backupFile = Bun.file(backupPath);
|
|
50894
|
+
if (!await backupFile.exists()) {
|
|
50895
|
+
throw new Error(`Backup file not found: ${backupPath}`);
|
|
50896
|
+
}
|
|
50897
|
+
const content = await backupFile.arrayBuffer();
|
|
50898
|
+
const result = await this.write(targetPath, Buffer.from(content), {
|
|
50899
|
+
createBackup: false
|
|
50900
|
+
});
|
|
50901
|
+
return !!result;
|
|
50902
|
+
} catch (error) {
|
|
50903
|
+
throw new Error(`File restore failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
50904
|
+
}
|
|
50905
|
+
}
|
|
50906
|
+
checkTimeout(startTime, timeout, stage) {
|
|
50907
|
+
const elapsed = Date.now() - startTime;
|
|
50908
|
+
if (elapsed > timeout) {
|
|
50909
|
+
throw new Error(`Operation timeout exceeded at ${stage}: ${elapsed}ms > ${timeout}ms`);
|
|
50910
|
+
}
|
|
50911
|
+
}
|
|
50912
|
+
async cleanup(tempPath, tempExists) {
|
|
50913
|
+
if (!tempExists)
|
|
50914
|
+
return;
|
|
50915
|
+
try {
|
|
50916
|
+
const tempFile = Bun.file(tempPath);
|
|
50917
|
+
if (await tempFile.exists()) {
|
|
50918
|
+
await Bun.spawn(["rm", "-f", tempPath]).exited;
|
|
50919
|
+
}
|
|
50920
|
+
} catch (error) {
|
|
50921
|
+
console.error(`Cleanup failed for temp file ${tempPath}:`, error);
|
|
50922
|
+
}
|
|
50923
|
+
}
|
|
50924
|
+
}
|
|
50925
|
+
|
|
50926
|
+
// src/core/schema-writer.ts
|
|
50927
|
+
init_schema_path();
|
|
50928
|
+
|
|
50929
|
+
class SchemaWriter {
|
|
50930
|
+
dbcliPath;
|
|
50931
|
+
writer;
|
|
50932
|
+
constructor(dbcliPath) {
|
|
50933
|
+
this.dbcliPath = dbcliPath;
|
|
50934
|
+
this.writer = new AtomicFileWriter;
|
|
50935
|
+
}
|
|
50936
|
+
async save(schema, connectionName) {
|
|
50937
|
+
const schemaRoot = resolveSchemaPath(this.dbcliPath, connectionName);
|
|
50938
|
+
const index = await SchemaIndexBuilder.buildIndex({ schema });
|
|
50939
|
+
await SchemaIndexBuilder.saveIndex(this.dbcliPath, index, connectionName);
|
|
50940
|
+
const mapping = SchemaIndexBuilder.calculateFileMapping(index);
|
|
50941
|
+
const hotSchemas = {};
|
|
50942
|
+
for (const item of mapping.hot) {
|
|
50943
|
+
hotSchemas[item.table] = schema[item.table];
|
|
50944
|
+
}
|
|
50945
|
+
await this.writer.writeJSON(join8(schemaRoot, "hot-schemas.json"), hotSchemas);
|
|
50946
|
+
const coldGroups = {};
|
|
50947
|
+
for (const item of mapping.cold) {
|
|
50948
|
+
if (!coldGroups[item.file]) {
|
|
50949
|
+
coldGroups[item.file] = {};
|
|
50950
|
+
}
|
|
50951
|
+
coldGroups[item.file][item.table] = schema[item.table];
|
|
50952
|
+
}
|
|
50953
|
+
const coldDir = join8(schemaRoot, "cold");
|
|
50954
|
+
await this.ensureDir(coldDir);
|
|
50955
|
+
for (const [fileName, tables] of Object.entries(coldGroups)) {
|
|
50956
|
+
const filePath = join8(schemaRoot, fileName);
|
|
50957
|
+
await this.writer.writeJSON(filePath, tables);
|
|
50958
|
+
}
|
|
50959
|
+
}
|
|
50960
|
+
async clear(connectionName) {
|
|
50961
|
+
const schemaRoot = resolveSchemaPath(this.dbcliPath, connectionName);
|
|
50962
|
+
try {
|
|
50963
|
+
const dir = Bun.file(schemaRoot);
|
|
50964
|
+
const s = await dir.stat();
|
|
50965
|
+
if (s.isDirectory()) {
|
|
50966
|
+
await Bun.spawn(["rm", "-rf", schemaRoot]).exited;
|
|
50967
|
+
}
|
|
50968
|
+
} catch {}
|
|
50969
|
+
}
|
|
50970
|
+
async ensureDir(path) {
|
|
50971
|
+
const { mkdirSync } = await import("fs");
|
|
50972
|
+
try {
|
|
50973
|
+
mkdirSync(path, { recursive: true });
|
|
50974
|
+
} catch (error) {
|
|
50975
|
+
if (error.code !== "EEXIST") {
|
|
50976
|
+
throw new Error(`Error ensuring directory ${path}: ${error}`);
|
|
50977
|
+
}
|
|
50978
|
+
}
|
|
50979
|
+
}
|
|
50980
|
+
}
|
|
50981
|
+
// src/core/column-index.ts
|
|
50982
|
+
class ColumnIndexBuilder {
|
|
50983
|
+
index = {
|
|
50984
|
+
columns: new Map,
|
|
50985
|
+
totalColumns: 0,
|
|
50986
|
+
totalTables: 0,
|
|
50987
|
+
timestamp: new Date().toISOString()
|
|
50988
|
+
};
|
|
50989
|
+
build(schemas) {
|
|
50990
|
+
this.index = {
|
|
50991
|
+
columns: new Map,
|
|
50992
|
+
totalColumns: 0,
|
|
50993
|
+
totalTables: Object.keys(schemas).length,
|
|
50994
|
+
timestamp: new Date().toISOString()
|
|
50995
|
+
};
|
|
50996
|
+
for (const [tableName, table] of Object.entries(schemas)) {
|
|
50997
|
+
for (const column of table.columns) {
|
|
50998
|
+
this.addColumnEntry(tableName, column);
|
|
50999
|
+
}
|
|
51000
|
+
}
|
|
51001
|
+
return this.index;
|
|
51002
|
+
}
|
|
51003
|
+
addColumnEntry(tableName, column) {
|
|
51004
|
+
const colName = column.name.toLowerCase();
|
|
51005
|
+
if (!this.index.columns.has(colName)) {
|
|
51006
|
+
this.index.columns.set(colName, {
|
|
51007
|
+
name: column.name,
|
|
51008
|
+
tables: []
|
|
51009
|
+
});
|
|
51010
|
+
this.index.totalColumns++;
|
|
51011
|
+
}
|
|
51012
|
+
const entry = this.index.columns.get(colName);
|
|
51013
|
+
entry.tables.push({
|
|
51014
|
+
tableName,
|
|
51015
|
+
column
|
|
51016
|
+
});
|
|
51017
|
+
}
|
|
51018
|
+
findColumn(columnName) {
|
|
51019
|
+
const entry = this.index.columns.get(columnName.toLowerCase());
|
|
51020
|
+
return entry?.tables || [];
|
|
51021
|
+
}
|
|
51022
|
+
findColumnsMatching(pattern) {
|
|
51023
|
+
const regex = typeof pattern === "string" ? new RegExp(pattern, "i") : pattern;
|
|
51024
|
+
const results = [];
|
|
51025
|
+
for (const [colName, entry] of this.index.columns.entries()) {
|
|
51026
|
+
if (regex.test(entry.name)) {
|
|
51027
|
+
results.push({
|
|
51028
|
+
columnName: entry.name,
|
|
51029
|
+
tables: entry.tables
|
|
51030
|
+
});
|
|
51031
|
+
}
|
|
51032
|
+
}
|
|
51033
|
+
return results;
|
|
51034
|
+
}
|
|
51035
|
+
getTableColumns(tableName) {
|
|
51036
|
+
const results = [];
|
|
51037
|
+
for (const entry of this.index.columns.values()) {
|
|
51038
|
+
const tableEntry = entry.tables.find((t2) => t2.tableName === tableName);
|
|
51039
|
+
if (tableEntry) {
|
|
51040
|
+
results.push(tableEntry.column);
|
|
51041
|
+
}
|
|
51042
|
+
}
|
|
51043
|
+
return results;
|
|
51044
|
+
}
|
|
51045
|
+
findColumnsByType(type) {
|
|
51046
|
+
const results = [];
|
|
51047
|
+
const typePattern = new RegExp(type, "i");
|
|
51048
|
+
for (const entry of this.index.columns.values()) {
|
|
51049
|
+
for (const tableEntry of entry.tables) {
|
|
51050
|
+
if (typePattern.test(tableEntry.column.type)) {
|
|
51051
|
+
results.push(tableEntry);
|
|
51052
|
+
}
|
|
51053
|
+
}
|
|
51054
|
+
}
|
|
51055
|
+
return results;
|
|
51056
|
+
}
|
|
51057
|
+
findPrimaryKeys() {
|
|
51058
|
+
const results = [];
|
|
51059
|
+
for (const entry of this.index.columns.values()) {
|
|
51060
|
+
for (const tableEntry of entry.tables) {
|
|
51061
|
+
if (tableEntry.column.primaryKey) {
|
|
51062
|
+
results.push(tableEntry);
|
|
51063
|
+
}
|
|
51064
|
+
}
|
|
51065
|
+
}
|
|
51066
|
+
return results;
|
|
51067
|
+
}
|
|
51068
|
+
findNullableColumns() {
|
|
51069
|
+
const results = [];
|
|
51070
|
+
for (const entry of this.index.columns.values()) {
|
|
51071
|
+
for (const tableEntry of entry.tables) {
|
|
51072
|
+
if (tableEntry.column.nullable) {
|
|
51073
|
+
results.push(tableEntry);
|
|
51074
|
+
}
|
|
51075
|
+
}
|
|
51076
|
+
}
|
|
51077
|
+
return results;
|
|
51078
|
+
}
|
|
51079
|
+
getStats() {
|
|
51080
|
+
return {
|
|
51081
|
+
totalColumns: this.index.totalColumns,
|
|
51082
|
+
totalTables: this.index.totalTables,
|
|
51083
|
+
uniqueColumnNames: this.index.columns.size,
|
|
51084
|
+
averageTablesPerColumn: this.index.columns.size > 0 ? Array.from(this.index.columns.values()).reduce((sum, entry) => sum + entry.tables.length, 0) / this.index.columns.size : 0,
|
|
51085
|
+
timestamp: this.index.timestamp
|
|
51086
|
+
};
|
|
51087
|
+
}
|
|
51088
|
+
export() {
|
|
51089
|
+
const serialized = {
|
|
51090
|
+
columns: Array.from(this.index.columns.entries()).reduce((acc, [key2, value]) => {
|
|
51091
|
+
acc[key2] = value;
|
|
51092
|
+
return acc;
|
|
51093
|
+
}, {}),
|
|
51094
|
+
totalColumns: this.index.totalColumns,
|
|
51095
|
+
totalTables: this.index.totalTables,
|
|
51096
|
+
timestamp: this.index.timestamp
|
|
51097
|
+
};
|
|
51098
|
+
return serialized;
|
|
51099
|
+
}
|
|
51100
|
+
import(exported) {
|
|
51101
|
+
this.index = {
|
|
51102
|
+
columns: new Map(Object.entries(exported.columns)),
|
|
51103
|
+
totalColumns: exported.totalColumns,
|
|
51104
|
+
totalTables: exported.totalTables,
|
|
51105
|
+
timestamp: exported.timestamp
|
|
51106
|
+
};
|
|
51107
|
+
}
|
|
51108
|
+
}
|
|
51109
|
+
// src/core/blacklist-manager.ts
|
|
51110
|
+
class BlacklistManager {
|
|
51111
|
+
config;
|
|
51112
|
+
state;
|
|
51113
|
+
overrideEnabled;
|
|
51114
|
+
constructor(config, overrideEnvValue) {
|
|
51115
|
+
this.config = config;
|
|
51116
|
+
this.overrideEnabled = (overrideEnvValue ?? Bun.env.DBCLI_OVERRIDE_BLACKLIST ?? "") === "true";
|
|
51117
|
+
this.state = this.loadBlacklist();
|
|
51118
|
+
}
|
|
51119
|
+
loadBlacklist() {
|
|
51120
|
+
const tables = new Set;
|
|
51121
|
+
const columns = new Map;
|
|
51122
|
+
const blacklistConfig = this.config.blacklist;
|
|
51123
|
+
if (!blacklistConfig) {
|
|
51124
|
+
return { tables, columns };
|
|
51125
|
+
}
|
|
51126
|
+
if (Array.isArray(blacklistConfig.tables)) {
|
|
51127
|
+
for (const tableName of blacklistConfig.tables) {
|
|
51128
|
+
if (typeof tableName === "string") {
|
|
51129
|
+
tables.add(tableName.toLowerCase());
|
|
51130
|
+
} else {
|
|
51131
|
+
console.warn(`[BlacklistManager] Invalid table name in blacklist config: ${JSON.stringify(tableName)}`);
|
|
51132
|
+
}
|
|
51133
|
+
}
|
|
51134
|
+
} else if (blacklistConfig.tables !== undefined) {
|
|
51135
|
+
console.warn("[BlacklistManager] blacklist.tables must be an array, ignoring");
|
|
51136
|
+
}
|
|
51137
|
+
if (blacklistConfig.columns && typeof blacklistConfig.columns === "object" && !Array.isArray(blacklistConfig.columns)) {
|
|
51138
|
+
for (const [tableName, cols] of Object.entries(blacklistConfig.columns)) {
|
|
51139
|
+
if (typeof tableName !== "string") {
|
|
51140
|
+
console.warn(`[BlacklistManager] Invalid table name key in columns config: ${JSON.stringify(tableName)}`);
|
|
51141
|
+
continue;
|
|
51142
|
+
}
|
|
51143
|
+
if (!Array.isArray(cols)) {
|
|
51144
|
+
console.warn(`[BlacklistManager] blacklist.columns["${tableName}"] must be an array, ignoring`);
|
|
51145
|
+
continue;
|
|
51146
|
+
}
|
|
51147
|
+
const columnSet = new Set;
|
|
51148
|
+
for (const col of cols) {
|
|
51149
|
+
if (typeof col === "string") {
|
|
51150
|
+
columnSet.add(col);
|
|
51151
|
+
} else {
|
|
51152
|
+
console.warn(`[BlacklistManager] Invalid column name in blacklist.columns["${tableName}"]: ${JSON.stringify(col)}`);
|
|
51153
|
+
}
|
|
51154
|
+
}
|
|
51155
|
+
if (columnSet.size > 0) {
|
|
51156
|
+
columns.set(tableName, columnSet);
|
|
51157
|
+
}
|
|
51158
|
+
}
|
|
51159
|
+
} else if (blacklistConfig.columns !== undefined) {
|
|
51160
|
+
console.warn("[BlacklistManager] blacklist.columns must be an object, ignoring");
|
|
51161
|
+
}
|
|
51162
|
+
return { tables, columns };
|
|
51163
|
+
}
|
|
51164
|
+
isTableBlacklisted(tableName) {
|
|
51165
|
+
return this.state.tables.has(tableName.toLowerCase());
|
|
51166
|
+
}
|
|
51167
|
+
isColumnBlacklisted(tableName, columnName) {
|
|
51168
|
+
const columnSet = this.state.columns.get(tableName);
|
|
51169
|
+
if (!columnSet) {
|
|
51170
|
+
return false;
|
|
51171
|
+
}
|
|
51172
|
+
return columnSet.has(columnName);
|
|
51173
|
+
}
|
|
51174
|
+
getBlacklistedColumns(tableName) {
|
|
51175
|
+
const columnSet = this.state.columns.get(tableName);
|
|
51176
|
+
if (!columnSet) {
|
|
51177
|
+
return [];
|
|
51178
|
+
}
|
|
51179
|
+
return Array.from(columnSet);
|
|
51180
|
+
}
|
|
51181
|
+
canOverrideBlacklist() {
|
|
51182
|
+
return this.overrideEnabled;
|
|
51183
|
+
}
|
|
51184
|
+
getState() {
|
|
51185
|
+
return this.state;
|
|
51186
|
+
}
|
|
51187
|
+
}
|
|
51188
|
+
// src/types/blacklist.ts
|
|
51189
|
+
class BlacklistError extends Error {
|
|
51190
|
+
tableName;
|
|
51191
|
+
operation;
|
|
51192
|
+
constructor(message, tableName, operation) {
|
|
51193
|
+
super(message);
|
|
51194
|
+
this.tableName = tableName;
|
|
51195
|
+
this.operation = operation;
|
|
51196
|
+
this.name = "BlacklistError";
|
|
51197
|
+
}
|
|
51198
|
+
}
|
|
51199
|
+
|
|
51200
|
+
// src/core/blacklist-validator.ts
|
|
51201
|
+
class BlacklistValidator {
|
|
51202
|
+
manager;
|
|
51203
|
+
constructor(manager) {
|
|
51204
|
+
this.manager = manager;
|
|
51205
|
+
}
|
|
51206
|
+
checkTableBlacklist(operation, tableName, _tableList = []) {
|
|
51207
|
+
if (this.manager.canOverrideBlacklist()) {
|
|
51208
|
+
const message = t_vars("warnings.blacklist_override_used", {
|
|
51209
|
+
operation,
|
|
51210
|
+
table: tableName
|
|
51211
|
+
});
|
|
51212
|
+
console.error(message);
|
|
51213
|
+
return;
|
|
51214
|
+
}
|
|
51215
|
+
if (this.manager.isTableBlacklisted(tableName)) {
|
|
51216
|
+
const message = t_vars("errors.table_blacklisted", {
|
|
51217
|
+
table: tableName,
|
|
51218
|
+
operation
|
|
51219
|
+
});
|
|
51220
|
+
throw new BlacklistError(message, tableName, operation);
|
|
51221
|
+
}
|
|
51222
|
+
}
|
|
51223
|
+
filterColumns(tableName, rows, columnList) {
|
|
51224
|
+
const blacklistedColumns = this.manager.getBlacklistedColumns(tableName);
|
|
51225
|
+
if (blacklistedColumns.length === 0) {
|
|
51226
|
+
return { filteredRows: rows, omittedColumns: [] };
|
|
51227
|
+
}
|
|
51228
|
+
const omittedColumns = columnList.filter((col) => blacklistedColumns.includes(col));
|
|
51229
|
+
if (omittedColumns.length === 0) {
|
|
51230
|
+
return { filteredRows: rows, omittedColumns: [] };
|
|
51231
|
+
}
|
|
51232
|
+
const filteredRows = rows.map((row) => {
|
|
51233
|
+
const newRow = {};
|
|
51234
|
+
for (const [key2, value] of Object.entries(row)) {
|
|
51235
|
+
if (!omittedColumns.includes(key2)) {
|
|
51236
|
+
newRow[key2] = value;
|
|
51237
|
+
}
|
|
51238
|
+
}
|
|
51239
|
+
return newRow;
|
|
51240
|
+
});
|
|
51241
|
+
return { filteredRows, omittedColumns };
|
|
51242
|
+
}
|
|
51243
|
+
buildSecurityNotification(_tableName, omittedColumns) {
|
|
51244
|
+
if (omittedColumns.length === 0) {
|
|
51245
|
+
return "";
|
|
51246
|
+
}
|
|
51247
|
+
return t_vars("security.columns_omitted", {
|
|
51248
|
+
count: omittedColumns.length
|
|
51249
|
+
});
|
|
51250
|
+
}
|
|
51251
|
+
}
|
|
51252
|
+
|
|
51253
|
+
// src/core/index.ts
|
|
51254
|
+
init_size_category();
|
|
51255
|
+
|
|
51256
|
+
// src/core/health-checker.ts
|
|
51257
|
+
init_size_category();
|
|
51258
|
+
|
|
51259
|
+
class HealthChecker {
|
|
51260
|
+
adapter;
|
|
51261
|
+
constructor(adapter) {
|
|
51262
|
+
this.adapter = adapter;
|
|
51263
|
+
}
|
|
51264
|
+
async check(schema, options = {}) {
|
|
51265
|
+
const checks = options.checks || ["nulls", "duplicates", "orphans", "emptyStrings", "rowCount"];
|
|
51266
|
+
const sample2 = options.sample || 1e4;
|
|
51267
|
+
const blacklisted = options.blacklistedColumns || new Set;
|
|
51268
|
+
const visibleColumns = schema.columns.filter((c) => !blacklisted.has(`${schema.name}.${c.name}`));
|
|
51269
|
+
const countResult = await this.adapter.execute(`SELECT COUNT(*) as count FROM \`${schema.name}\``);
|
|
51270
|
+
const rowCount = countResult[0]?.count || 0;
|
|
51271
|
+
const nulls = checks.includes("nulls") ? await this.checkNulls(schema.name, visibleColumns, rowCount, sample2) : [];
|
|
51272
|
+
const orphans = checks.includes("orphans") ? await this.checkOrphans(schema.name, visibleColumns) : [];
|
|
51273
|
+
const duplicates = checks.includes("duplicates") ? await this.checkDuplicates(schema.name, schema.indexes || []) : [];
|
|
51274
|
+
const emptyStrings = checks.includes("emptyStrings") ? await this.checkEmptyStrings(schema.name, visibleColumns) : [];
|
|
51275
|
+
const issues = orphans.length + duplicates.length;
|
|
51276
|
+
const warnings = nulls.filter((n) => n.nullPercent > 50).length + emptyStrings.length;
|
|
51277
|
+
const clean = Math.max(0, checks.length - (issues > 0 ? 1 : 0) - (warnings > 0 ? 1 : 0));
|
|
51278
|
+
return {
|
|
51279
|
+
table: schema.name,
|
|
51280
|
+
rowCount,
|
|
51281
|
+
sizeCategory: getSizeCategory(schema.estimatedRowCount),
|
|
51282
|
+
checks: { nulls, orphans, duplicates, emptyStrings },
|
|
51283
|
+
summary: { issues, warnings, clean },
|
|
51284
|
+
skippedColumns: blacklisted.size > 0 ? Array.from(blacklisted).filter((c) => c.startsWith(`${schema.name}.`)) : undefined
|
|
51285
|
+
};
|
|
51286
|
+
}
|
|
51287
|
+
async checkNulls(tableName, columns, totalRows, sample2) {
|
|
51288
|
+
if (totalRows === 0)
|
|
51289
|
+
return [];
|
|
51290
|
+
const nullableColumns = columns.filter((c) => c.nullable);
|
|
51291
|
+
const results = [];
|
|
51292
|
+
for (const col of nullableColumns) {
|
|
51293
|
+
try {
|
|
51294
|
+
const useSample = totalRows > sample2;
|
|
51295
|
+
const sql = useSample ? `SELECT COUNT(*) - COUNT(\`${col.name}\`) as null_count FROM (SELECT \`${col.name}\` FROM \`${tableName}\` LIMIT ${sample2}) sub` : `SELECT COUNT(*) - COUNT(\`${col.name}\`) as null_count FROM \`${tableName}\``;
|
|
51296
|
+
const result = await this.adapter.execute(sql);
|
|
51297
|
+
const nullCount = result[0]?.null_count || 0;
|
|
51298
|
+
if (nullCount > 0) {
|
|
51299
|
+
const sampleSize = Math.min(totalRows, sample2);
|
|
51300
|
+
results.push({
|
|
51301
|
+
column: col.name,
|
|
51302
|
+
nullCount,
|
|
51303
|
+
nullPercent: Number((nullCount / sampleSize * 100).toFixed(1))
|
|
51304
|
+
});
|
|
51305
|
+
}
|
|
51306
|
+
} catch {}
|
|
51307
|
+
}
|
|
51308
|
+
return results;
|
|
51309
|
+
}
|
|
51310
|
+
async checkOrphans(tableName, columns) {
|
|
51311
|
+
const fkColumns = columns.filter((c) => c.foreignKey);
|
|
51312
|
+
const results = [];
|
|
51313
|
+
for (const col of fkColumns) {
|
|
51314
|
+
if (!col.foreignKey)
|
|
51315
|
+
continue;
|
|
51316
|
+
try {
|
|
51317
|
+
const sql = `
|
|
51318
|
+
SELECT COUNT(*) as orphan_count
|
|
51319
|
+
FROM \`${tableName}\` child
|
|
51320
|
+
LEFT JOIN \`${col.foreignKey.table}\` parent
|
|
51321
|
+
ON child.\`${col.name}\` = parent.\`${col.foreignKey.column}\`
|
|
51322
|
+
WHERE child.\`${col.name}\` IS NOT NULL
|
|
51323
|
+
AND parent.\`${col.foreignKey.column}\` IS NULL
|
|
51324
|
+
`;
|
|
51325
|
+
const result = await this.adapter.execute(sql);
|
|
51326
|
+
const orphanCount = result[0]?.orphan_count || 0;
|
|
51327
|
+
if (orphanCount > 0) {
|
|
51328
|
+
results.push({
|
|
51329
|
+
column: col.name,
|
|
51330
|
+
references: `${col.foreignKey.table}.${col.foreignKey.column}`,
|
|
51331
|
+
orphanCount
|
|
51332
|
+
});
|
|
51333
|
+
}
|
|
51334
|
+
} catch {}
|
|
51335
|
+
}
|
|
51336
|
+
return results;
|
|
51337
|
+
}
|
|
51338
|
+
async checkDuplicates(tableName, indexes) {
|
|
51339
|
+
const uniqueIndexes = indexes.filter((idx) => idx.unique);
|
|
51340
|
+
const results = [];
|
|
51341
|
+
for (const idx of uniqueIndexes) {
|
|
51342
|
+
try {
|
|
51343
|
+
const colList = idx.columns.map((c) => `\`${c}\``).join(", ");
|
|
51344
|
+
const sql = `
|
|
51345
|
+
SELECT COUNT(*) as dup_count FROM (
|
|
51346
|
+
SELECT ${colList}
|
|
51347
|
+
FROM \`${tableName}\`
|
|
51348
|
+
GROUP BY ${colList}
|
|
51349
|
+
HAVING COUNT(*) > 1
|
|
51350
|
+
) dups
|
|
51351
|
+
`;
|
|
51352
|
+
const result = await this.adapter.execute(sql);
|
|
51353
|
+
const dupCount = result[0]?.dup_count || 0;
|
|
51354
|
+
if (dupCount > 0) {
|
|
51355
|
+
results.push({
|
|
51356
|
+
columns: idx.columns,
|
|
51357
|
+
indexName: idx.name,
|
|
51358
|
+
duplicateCount: dupCount
|
|
51359
|
+
});
|
|
51360
|
+
}
|
|
51361
|
+
} catch {}
|
|
51362
|
+
}
|
|
51363
|
+
return results;
|
|
51364
|
+
}
|
|
51365
|
+
async checkEmptyStrings(tableName, columns) {
|
|
51366
|
+
const stringColumns = columns.filter((c) => /varchar|text|char/i.test(c.type));
|
|
51367
|
+
const results = [];
|
|
51368
|
+
for (const col of stringColumns) {
|
|
51369
|
+
try {
|
|
51370
|
+
const sql = `SELECT COUNT(*) as empty_count FROM \`${tableName}\` WHERE \`${col.name}\` = ''`;
|
|
51371
|
+
const result = await this.adapter.execute(sql);
|
|
51372
|
+
const count = result[0]?.empty_count || 0;
|
|
51373
|
+
if (count > 0) {
|
|
51374
|
+
results.push({ column: col.name, count });
|
|
51375
|
+
}
|
|
51376
|
+
} catch {}
|
|
51377
|
+
}
|
|
51378
|
+
return results;
|
|
51379
|
+
}
|
|
51380
|
+
}
|
|
49269
51381
|
// src/commands/schema.ts
|
|
49270
51382
|
init_validation();
|
|
49271
51383
|
var ALLOWED_FORMATS2 = ["table", "json"];
|
|
@@ -49278,17 +51390,29 @@ async function schemaAction(table, options) {
|
|
|
49278
51390
|
console.error("Database not configured. Run: dbcli init");
|
|
49279
51391
|
process.exit(1);
|
|
49280
51392
|
}
|
|
51393
|
+
const connectionName = await getSchemaIsolationConnectionName(options.config);
|
|
51394
|
+
let existingSchemaCount;
|
|
51395
|
+
if (connectionName !== undefined) {
|
|
51396
|
+
try {
|
|
51397
|
+
const v2Raw = await readV2Config(options.config);
|
|
51398
|
+
existingSchemaCount = Object.keys(v2Raw.schemas?.[connectionName] ?? {}).length;
|
|
51399
|
+
} catch {
|
|
51400
|
+
existingSchemaCount = 0;
|
|
51401
|
+
}
|
|
51402
|
+
} else {
|
|
51403
|
+
existingSchemaCount = Object.keys(config.schema ?? {}).length;
|
|
51404
|
+
}
|
|
49281
51405
|
const adapter = AdapterFactory.createAdapter(config.connection);
|
|
49282
51406
|
await adapter.connect();
|
|
49283
51407
|
try {
|
|
49284
51408
|
if (options.reset) {
|
|
49285
|
-
await handleSchemaReset(adapter, config, options);
|
|
51409
|
+
await handleSchemaReset(adapter, config, options, connectionName, existingSchemaCount);
|
|
49286
51410
|
} else if (options.refresh) {
|
|
49287
|
-
await handleSchemaRefresh(adapter, config, options);
|
|
51411
|
+
await handleSchemaRefresh(adapter, config, options, connectionName);
|
|
49288
51412
|
} else if (table) {
|
|
49289
51413
|
await handleSingleTableSchema(adapter, table, options.format);
|
|
49290
51414
|
} else {
|
|
49291
|
-
await handleFullDatabaseScan(adapter, config, options);
|
|
51415
|
+
await handleFullDatabaseScan(adapter, config, options, connectionName, existingSchemaCount);
|
|
49292
51416
|
}
|
|
49293
51417
|
} finally {
|
|
49294
51418
|
await adapter.disconnect();
|
|
@@ -49348,10 +51472,18 @@ Indexes:`);
|
|
|
49348
51472
|
}
|
|
49349
51473
|
}
|
|
49350
51474
|
}
|
|
49351
|
-
async function handleSchemaRefresh(adapter, config, options) {
|
|
51475
|
+
async function handleSchemaRefresh(adapter, config, options, connectionName) {
|
|
49352
51476
|
const diffEngine = new SchemaDiffEngine(adapter, config);
|
|
49353
51477
|
const report = await diffEngine.diff();
|
|
49354
51478
|
if (report.tablesAdded.length === 0 && report.tablesRemoved.length === 0 && Object.keys(report.tablesModified).length === 0) {
|
|
51479
|
+
const updatedConfig2 = configModule.merge(config, {
|
|
51480
|
+
metadata: {
|
|
51481
|
+
...config.metadata,
|
|
51482
|
+
schemaLastUpdated: new Date().toISOString(),
|
|
51483
|
+
schemaTableCount: Object.keys(config.schema || {}).length
|
|
51484
|
+
}
|
|
51485
|
+
});
|
|
51486
|
+
await writeSchema(options.config, updatedConfig2, connectionName);
|
|
49355
51487
|
console.log("\u2705 Schema is up-to-date (no changes detected)");
|
|
49356
51488
|
return;
|
|
49357
51489
|
}
|
|
@@ -49375,27 +51507,37 @@ async function handleSchemaRefresh(adapter, config, options) {
|
|
|
49375
51507
|
schemaTableCount: Object.keys(newSchema).length
|
|
49376
51508
|
}
|
|
49377
51509
|
});
|
|
49378
|
-
|
|
51510
|
+
const writer = new SchemaWriter(options.config);
|
|
51511
|
+
await writer.save(newSchema, connectionName);
|
|
51512
|
+
console.log(`\u2705 Schema persisted to layered storage (.dbcli/schemas/${connectionName || ""})`);
|
|
51513
|
+
await writeSchema(options.config, updatedConfig, connectionName);
|
|
49379
51514
|
console.log(`\u2705 Schema updated in .dbcli`);
|
|
49380
51515
|
}
|
|
49381
|
-
async function handleSchemaReset(adapter, config, options) {
|
|
49382
|
-
const existingCount = config.schema ? Object.keys(config.schema).length : 0;
|
|
51516
|
+
async function handleSchemaReset(adapter, config, options, connectionName, existingCount) {
|
|
49383
51517
|
if (existingCount > 0 && !options.force) {
|
|
49384
|
-
|
|
49385
|
-
|
|
51518
|
+
const { SchemaLayeredLoader: SchemaLayeredLoader3 } = await Promise.resolve().then(() => (init_schema_loader(), exports_schema_loader));
|
|
51519
|
+
const loader = new SchemaLayeredLoader3(options.config, { connectionName });
|
|
51520
|
+
const { index } = await loader.initialize();
|
|
51521
|
+
if (!index || Object.keys(index.tables).length === 0) {
|
|
51522
|
+
console.log(`\u26A0 This will clear ${existingCount} existing table schemas and re-fetch from database.`);
|
|
51523
|
+
console.log("\uD83D\uDCA1 Hint: Schema found in config.json but layered cache files are missing.");
|
|
51524
|
+
console.log(" Use --force to migrate to optimized layered storage.");
|
|
51525
|
+
} else {
|
|
51526
|
+
console.log(`\u26A0 This will clear ${existingCount} existing table schemas and re-fetch from database.`);
|
|
51527
|
+
console.log(" Use --force to confirm.");
|
|
51528
|
+
}
|
|
49386
51529
|
return;
|
|
49387
51530
|
}
|
|
49388
51531
|
console.log("\uD83D\uDDD1 Clearing existing schema data...");
|
|
51532
|
+
const emptyMeta = { schemaLastUpdated: undefined, schemaTableCount: 0 };
|
|
49389
51533
|
const configWithoutSchema = {
|
|
49390
51534
|
...config,
|
|
49391
51535
|
schema: {},
|
|
49392
|
-
metadata: {
|
|
49393
|
-
...config.metadata,
|
|
49394
|
-
schemaLastUpdated: undefined,
|
|
49395
|
-
schemaTableCount: 0
|
|
49396
|
-
}
|
|
51536
|
+
metadata: { ...config.metadata, ...emptyMeta }
|
|
49397
51537
|
};
|
|
49398
|
-
await
|
|
51538
|
+
await writeSchema(options.config, configWithoutSchema, connectionName);
|
|
51539
|
+
const writer = new SchemaWriter(options.config);
|
|
51540
|
+
await writer.clear(connectionName);
|
|
49399
51541
|
console.log(t("schema.scanning_database"));
|
|
49400
51542
|
const tables = await adapter.listTables();
|
|
49401
51543
|
console.log(t_vars("schema.tables_found", { count: tables.length }));
|
|
@@ -49428,7 +51570,9 @@ async function handleSchemaReset(adapter, config, options) {
|
|
|
49428
51570
|
schemaTableCount: tables.length
|
|
49429
51571
|
}
|
|
49430
51572
|
};
|
|
49431
|
-
await
|
|
51573
|
+
await writer.save(schemaData, connectionName);
|
|
51574
|
+
console.log(`\u2705 Schema persisted to layered storage (.dbcli/schemas/${connectionName || ""})`);
|
|
51575
|
+
await writeSchema(options.config, updatedConfig, connectionName);
|
|
49432
51576
|
if (existingCount > 0) {
|
|
49433
51577
|
console.log(`
|
|
49434
51578
|
\u2705 Schema reset complete \u2014 cleared ${existingCount} old tables, fetched ${tables.length} tables from database`);
|
|
@@ -49437,7 +51581,7 @@ async function handleSchemaReset(adapter, config, options) {
|
|
|
49437
51581
|
\u2705 Schema fetched \u2014 ${tables.length} tables from database`);
|
|
49438
51582
|
}
|
|
49439
51583
|
}
|
|
49440
|
-
async function handleFullDatabaseScan(adapter, config, options) {
|
|
51584
|
+
async function handleFullDatabaseScan(adapter, config, options, connectionName, existingSchemaCount) {
|
|
49441
51585
|
console.log(t("schema.scanning_database"));
|
|
49442
51586
|
const tables = await adapter.listTables();
|
|
49443
51587
|
console.log(t_vars("schema.tables_found", { count: tables.length }));
|
|
@@ -49461,26 +51605,50 @@ async function handleFullDatabaseScan(adapter, config, options) {
|
|
|
49461
51605
|
console.log(t_vars("schema.processing_tables", { processed, total: tables.length }));
|
|
49462
51606
|
}
|
|
49463
51607
|
}
|
|
49464
|
-
if (
|
|
49465
|
-
|
|
51608
|
+
if (existingSchemaCount > 0 && !options.force) {
|
|
51609
|
+
const { SchemaLayeredLoader: SchemaLayeredLoader3 } = await Promise.resolve().then(() => (init_schema_loader(), exports_schema_loader));
|
|
51610
|
+
const loader = new SchemaLayeredLoader3(options.config, { connectionName });
|
|
51611
|
+
const { index } = await loader.initialize();
|
|
51612
|
+
if (!index || Object.keys(index.tables).length === 0) {
|
|
51613
|
+
console.log(`
|
|
51614
|
+
` + t("schema.schema_exists_warning"));
|
|
51615
|
+
console.log("\uD83D\uDCA1 Hint: Schema found in config.json but layered cache files are missing.");
|
|
51616
|
+
console.log(" Run with --force to migrate your schema to optimized layered storage.");
|
|
51617
|
+
} else {
|
|
51618
|
+
console.log(`
|
|
49466
51619
|
` + t("schema.schema_exists_warning"));
|
|
49467
|
-
|
|
51620
|
+
console.log(t("schema.use_force_to_override"));
|
|
51621
|
+
}
|
|
49468
51622
|
process.exit(0);
|
|
49469
51623
|
}
|
|
51624
|
+
const now = new Date().toISOString();
|
|
49470
51625
|
const updatedConfig = {
|
|
49471
51626
|
...config,
|
|
49472
51627
|
schema: schemaData,
|
|
49473
51628
|
metadata: {
|
|
49474
51629
|
...config.metadata,
|
|
49475
|
-
schemaLastUpdated:
|
|
51630
|
+
schemaLastUpdated: now,
|
|
49476
51631
|
schemaTableCount: tables.length
|
|
49477
51632
|
}
|
|
49478
51633
|
};
|
|
49479
|
-
|
|
51634
|
+
const writer = new SchemaWriter(options.config);
|
|
51635
|
+
await writer.save(schemaData, connectionName);
|
|
51636
|
+
console.log(`\u2705 Schema persisted to layered storage (.dbcli/schemas/${connectionName || ""})`);
|
|
51637
|
+
await writeSchema(options.config, updatedConfig, connectionName);
|
|
49480
51638
|
console.log(`
|
|
49481
51639
|
\u2705 Schema updated in .dbcli`);
|
|
49482
51640
|
console.log(` ${tables.length} tables with full column details and relationships`);
|
|
49483
|
-
console.log(` Timestamp: ${
|
|
51641
|
+
console.log(` Timestamp: ${now}`);
|
|
51642
|
+
}
|
|
51643
|
+
async function writeSchema(configPath, config, connectionName) {
|
|
51644
|
+
if (connectionName !== undefined) {
|
|
51645
|
+
await patchConnectionSchema(configPath, connectionName, config.schema ?? {}, {
|
|
51646
|
+
schemaLastUpdated: config.metadata?.schemaLastUpdated,
|
|
51647
|
+
schemaTableCount: config.metadata?.schemaTableCount
|
|
51648
|
+
});
|
|
51649
|
+
} else {
|
|
51650
|
+
await configModule.write(configPath, config);
|
|
51651
|
+
}
|
|
49484
51652
|
}
|
|
49485
51653
|
|
|
49486
51654
|
// src/core/permission-guard.ts
|
|
@@ -50007,151 +52175,6 @@ function inferColumnType(value) {
|
|
|
50007
52175
|
return "unknown";
|
|
50008
52176
|
}
|
|
50009
52177
|
|
|
50010
|
-
// src/core/blacklist-manager.ts
|
|
50011
|
-
class BlacklistManager {
|
|
50012
|
-
config;
|
|
50013
|
-
state;
|
|
50014
|
-
overrideEnabled;
|
|
50015
|
-
constructor(config, overrideEnvValue) {
|
|
50016
|
-
this.config = config;
|
|
50017
|
-
this.overrideEnabled = (overrideEnvValue ?? Bun.env.DBCLI_OVERRIDE_BLACKLIST ?? "") === "true";
|
|
50018
|
-
this.state = this.loadBlacklist();
|
|
50019
|
-
}
|
|
50020
|
-
loadBlacklist() {
|
|
50021
|
-
const tables = new Set;
|
|
50022
|
-
const columns = new Map;
|
|
50023
|
-
const blacklistConfig = this.config.blacklist;
|
|
50024
|
-
if (!blacklistConfig) {
|
|
50025
|
-
return { tables, columns };
|
|
50026
|
-
}
|
|
50027
|
-
if (Array.isArray(blacklistConfig.tables)) {
|
|
50028
|
-
for (const tableName of blacklistConfig.tables) {
|
|
50029
|
-
if (typeof tableName === "string") {
|
|
50030
|
-
tables.add(tableName.toLowerCase());
|
|
50031
|
-
} else {
|
|
50032
|
-
console.warn(`[BlacklistManager] Invalid table name in blacklist config: ${JSON.stringify(tableName)}`);
|
|
50033
|
-
}
|
|
50034
|
-
}
|
|
50035
|
-
} else if (blacklistConfig.tables !== undefined) {
|
|
50036
|
-
console.warn("[BlacklistManager] blacklist.tables must be an array, ignoring");
|
|
50037
|
-
}
|
|
50038
|
-
if (blacklistConfig.columns && typeof blacklistConfig.columns === "object" && !Array.isArray(blacklistConfig.columns)) {
|
|
50039
|
-
for (const [tableName, cols] of Object.entries(blacklistConfig.columns)) {
|
|
50040
|
-
if (typeof tableName !== "string") {
|
|
50041
|
-
console.warn(`[BlacklistManager] Invalid table name key in columns config: ${JSON.stringify(tableName)}`);
|
|
50042
|
-
continue;
|
|
50043
|
-
}
|
|
50044
|
-
if (!Array.isArray(cols)) {
|
|
50045
|
-
console.warn(`[BlacklistManager] blacklist.columns["${tableName}"] must be an array, ignoring`);
|
|
50046
|
-
continue;
|
|
50047
|
-
}
|
|
50048
|
-
const columnSet = new Set;
|
|
50049
|
-
for (const col of cols) {
|
|
50050
|
-
if (typeof col === "string") {
|
|
50051
|
-
columnSet.add(col);
|
|
50052
|
-
} else {
|
|
50053
|
-
console.warn(`[BlacklistManager] Invalid column name in blacklist.columns["${tableName}"]: ${JSON.stringify(col)}`);
|
|
50054
|
-
}
|
|
50055
|
-
}
|
|
50056
|
-
if (columnSet.size > 0) {
|
|
50057
|
-
columns.set(tableName, columnSet);
|
|
50058
|
-
}
|
|
50059
|
-
}
|
|
50060
|
-
} else if (blacklistConfig.columns !== undefined) {
|
|
50061
|
-
console.warn("[BlacklistManager] blacklist.columns must be an object, ignoring");
|
|
50062
|
-
}
|
|
50063
|
-
return { tables, columns };
|
|
50064
|
-
}
|
|
50065
|
-
isTableBlacklisted(tableName) {
|
|
50066
|
-
return this.state.tables.has(tableName.toLowerCase());
|
|
50067
|
-
}
|
|
50068
|
-
isColumnBlacklisted(tableName, columnName) {
|
|
50069
|
-
const columnSet = this.state.columns.get(tableName);
|
|
50070
|
-
if (!columnSet) {
|
|
50071
|
-
return false;
|
|
50072
|
-
}
|
|
50073
|
-
return columnSet.has(columnName);
|
|
50074
|
-
}
|
|
50075
|
-
getBlacklistedColumns(tableName) {
|
|
50076
|
-
const columnSet = this.state.columns.get(tableName);
|
|
50077
|
-
if (!columnSet) {
|
|
50078
|
-
return [];
|
|
50079
|
-
}
|
|
50080
|
-
return Array.from(columnSet);
|
|
50081
|
-
}
|
|
50082
|
-
canOverrideBlacklist() {
|
|
50083
|
-
return this.overrideEnabled;
|
|
50084
|
-
}
|
|
50085
|
-
getState() {
|
|
50086
|
-
return this.state;
|
|
50087
|
-
}
|
|
50088
|
-
}
|
|
50089
|
-
|
|
50090
|
-
// src/types/blacklist.ts
|
|
50091
|
-
class BlacklistError extends Error {
|
|
50092
|
-
tableName;
|
|
50093
|
-
operation;
|
|
50094
|
-
constructor(message, tableName, operation) {
|
|
50095
|
-
super(message);
|
|
50096
|
-
this.tableName = tableName;
|
|
50097
|
-
this.operation = operation;
|
|
50098
|
-
this.name = "BlacklistError";
|
|
50099
|
-
}
|
|
50100
|
-
}
|
|
50101
|
-
|
|
50102
|
-
// src/core/blacklist-validator.ts
|
|
50103
|
-
class BlacklistValidator {
|
|
50104
|
-
manager;
|
|
50105
|
-
constructor(manager) {
|
|
50106
|
-
this.manager = manager;
|
|
50107
|
-
}
|
|
50108
|
-
checkTableBlacklist(operation, tableName, _tableList = []) {
|
|
50109
|
-
if (this.manager.canOverrideBlacklist()) {
|
|
50110
|
-
const message = t_vars("warnings.blacklist_override_used", {
|
|
50111
|
-
operation,
|
|
50112
|
-
table: tableName
|
|
50113
|
-
});
|
|
50114
|
-
console.error(message);
|
|
50115
|
-
return;
|
|
50116
|
-
}
|
|
50117
|
-
if (this.manager.isTableBlacklisted(tableName)) {
|
|
50118
|
-
const message = t_vars("errors.table_blacklisted", {
|
|
50119
|
-
table: tableName,
|
|
50120
|
-
operation
|
|
50121
|
-
});
|
|
50122
|
-
throw new BlacklistError(message, tableName, operation);
|
|
50123
|
-
}
|
|
50124
|
-
}
|
|
50125
|
-
filterColumns(tableName, rows, columnList) {
|
|
50126
|
-
const blacklistedColumns = this.manager.getBlacklistedColumns(tableName);
|
|
50127
|
-
if (blacklistedColumns.length === 0) {
|
|
50128
|
-
return { filteredRows: rows, omittedColumns: [] };
|
|
50129
|
-
}
|
|
50130
|
-
const omittedColumns = columnList.filter((col) => blacklistedColumns.includes(col));
|
|
50131
|
-
if (omittedColumns.length === 0) {
|
|
50132
|
-
return { filteredRows: rows, omittedColumns: [] };
|
|
50133
|
-
}
|
|
50134
|
-
const filteredRows = rows.map((row) => {
|
|
50135
|
-
const newRow = {};
|
|
50136
|
-
for (const [key2, value] of Object.entries(row)) {
|
|
50137
|
-
if (!omittedColumns.includes(key2)) {
|
|
50138
|
-
newRow[key2] = value;
|
|
50139
|
-
}
|
|
50140
|
-
}
|
|
50141
|
-
return newRow;
|
|
50142
|
-
});
|
|
50143
|
-
return { filteredRows, omittedColumns };
|
|
50144
|
-
}
|
|
50145
|
-
buildSecurityNotification(_tableName, omittedColumns) {
|
|
50146
|
-
if (omittedColumns.length === 0) {
|
|
50147
|
-
return "";
|
|
50148
|
-
}
|
|
50149
|
-
return t_vars("security.columns_omitted", {
|
|
50150
|
-
count: omittedColumns.length
|
|
50151
|
-
});
|
|
50152
|
-
}
|
|
50153
|
-
}
|
|
50154
|
-
|
|
50155
52178
|
// src/commands/query.ts
|
|
50156
52179
|
init_validation();
|
|
50157
52180
|
var ALLOWED_FORMATS3 = ["table", "json", "csv"];
|
|
@@ -50904,6 +52927,7 @@ async function exportCommand(sql, options) {
|
|
|
50904
52927
|
}
|
|
50905
52928
|
|
|
50906
52929
|
// src/commands/skill.ts
|
|
52930
|
+
var {$ } = globalThis.Bun;
|
|
50907
52931
|
import * as path from "path";
|
|
50908
52932
|
import { homedir } from "os";
|
|
50909
52933
|
function findPackageRoot() {
|
|
@@ -50917,6 +52941,7 @@ function findPackageRoot() {
|
|
|
50917
52941
|
return path.resolve(import.meta.dir, "../..");
|
|
50918
52942
|
}
|
|
50919
52943
|
var SKILL_SOURCE_PATH = path.join(findPackageRoot(), "assets", "SKILL.md");
|
|
52944
|
+
var SUPPORTED_PLATFORMS = ["claude", "gemini", "copilot", "cursor"];
|
|
50920
52945
|
async function skillCommand(_program, options) {
|
|
50921
52946
|
try {
|
|
50922
52947
|
const skillFile = Bun.file(SKILL_SOURCE_PATH);
|
|
@@ -50943,6 +52968,28 @@ async function skillCommand(_program, options) {
|
|
|
50943
52968
|
process.exit(1);
|
|
50944
52969
|
}
|
|
50945
52970
|
}
|
|
52971
|
+
async function checkSkillUpdates() {
|
|
52972
|
+
const outdated = [];
|
|
52973
|
+
try {
|
|
52974
|
+
const sourceFile = Bun.file(SKILL_SOURCE_PATH);
|
|
52975
|
+
if (!await sourceFile.exists())
|
|
52976
|
+
return [];
|
|
52977
|
+
const sourceContent = await sourceFile.text();
|
|
52978
|
+
for (const platform of SUPPORTED_PLATFORMS) {
|
|
52979
|
+
try {
|
|
52980
|
+
const installPath = getInstallPath(platform);
|
|
52981
|
+
const installedFile = Bun.file(installPath);
|
|
52982
|
+
if (await installedFile.exists()) {
|
|
52983
|
+
const installedContent = await installedFile.text();
|
|
52984
|
+
if (installedContent !== sourceContent) {
|
|
52985
|
+
outdated.push(platform);
|
|
52986
|
+
}
|
|
52987
|
+
}
|
|
52988
|
+
} catch {}
|
|
52989
|
+
}
|
|
52990
|
+
} catch {}
|
|
52991
|
+
return outdated;
|
|
52992
|
+
}
|
|
50946
52993
|
function getInstallPath(platform) {
|
|
50947
52994
|
const home = process.env.HOME || homedir();
|
|
50948
52995
|
const platformLower = platform.toLowerCase();
|
|
@@ -50956,7 +53003,7 @@ function getInstallPath(platform) {
|
|
|
50956
53003
|
case "cursor":
|
|
50957
53004
|
return path.join(process.cwd(), ".cursor", "rules", "dbcli.mdc");
|
|
50958
53005
|
default:
|
|
50959
|
-
throw new Error(`Unknown platform: ${platform}. Supported platforms:
|
|
53006
|
+
throw new Error(`Unknown platform: ${platform}. Supported platforms: ${SUPPORTED_PLATFORMS.join(", ")}`);
|
|
50960
53007
|
}
|
|
50961
53008
|
}
|
|
50962
53009
|
async function ensureDir(dirPath) {
|
|
@@ -51148,132 +53195,6 @@ columnCmd.command("remove <table.column>").description("Remove column from black
|
|
|
51148
53195
|
}
|
|
51149
53196
|
});
|
|
51150
53197
|
|
|
51151
|
-
// src/core/health-checker.ts
|
|
51152
|
-
init_size_category();
|
|
51153
|
-
|
|
51154
|
-
class HealthChecker {
|
|
51155
|
-
adapter;
|
|
51156
|
-
constructor(adapter) {
|
|
51157
|
-
this.adapter = adapter;
|
|
51158
|
-
}
|
|
51159
|
-
async check(schema, options = {}) {
|
|
51160
|
-
const checks = options.checks || ["nulls", "duplicates", "orphans", "emptyStrings", "rowCount"];
|
|
51161
|
-
const sample2 = options.sample || 1e4;
|
|
51162
|
-
const blacklisted = options.blacklistedColumns || new Set;
|
|
51163
|
-
const visibleColumns = schema.columns.filter((c) => !blacklisted.has(`${schema.name}.${c.name}`));
|
|
51164
|
-
const countResult = await this.adapter.execute(`SELECT COUNT(*) as count FROM \`${schema.name}\``);
|
|
51165
|
-
const rowCount = countResult[0]?.count || 0;
|
|
51166
|
-
const nulls = checks.includes("nulls") ? await this.checkNulls(schema.name, visibleColumns, rowCount, sample2) : [];
|
|
51167
|
-
const orphans = checks.includes("orphans") ? await this.checkOrphans(schema.name, visibleColumns) : [];
|
|
51168
|
-
const duplicates = checks.includes("duplicates") ? await this.checkDuplicates(schema.name, schema.indexes || []) : [];
|
|
51169
|
-
const emptyStrings = checks.includes("emptyStrings") ? await this.checkEmptyStrings(schema.name, visibleColumns) : [];
|
|
51170
|
-
const issues = orphans.length + duplicates.length;
|
|
51171
|
-
const warnings = nulls.filter((n) => n.nullPercent > 50).length + emptyStrings.length;
|
|
51172
|
-
const clean = Math.max(0, checks.length - (issues > 0 ? 1 : 0) - (warnings > 0 ? 1 : 0));
|
|
51173
|
-
return {
|
|
51174
|
-
table: schema.name,
|
|
51175
|
-
rowCount,
|
|
51176
|
-
sizeCategory: getSizeCategory(schema.estimatedRowCount),
|
|
51177
|
-
checks: { nulls, orphans, duplicates, emptyStrings },
|
|
51178
|
-
summary: { issues, warnings, clean },
|
|
51179
|
-
skippedColumns: blacklisted.size > 0 ? Array.from(blacklisted).filter((c) => c.startsWith(`${schema.name}.`)) : undefined
|
|
51180
|
-
};
|
|
51181
|
-
}
|
|
51182
|
-
async checkNulls(tableName, columns, totalRows, sample2) {
|
|
51183
|
-
if (totalRows === 0)
|
|
51184
|
-
return [];
|
|
51185
|
-
const nullableColumns = columns.filter((c) => c.nullable);
|
|
51186
|
-
const results = [];
|
|
51187
|
-
for (const col of nullableColumns) {
|
|
51188
|
-
try {
|
|
51189
|
-
const useSample = totalRows > sample2;
|
|
51190
|
-
const sql = useSample ? `SELECT COUNT(*) - COUNT(\`${col.name}\`) as null_count FROM (SELECT \`${col.name}\` FROM \`${tableName}\` LIMIT ${sample2}) sub` : `SELECT COUNT(*) - COUNT(\`${col.name}\`) as null_count FROM \`${tableName}\``;
|
|
51191
|
-
const result = await this.adapter.execute(sql);
|
|
51192
|
-
const nullCount = result[0]?.null_count || 0;
|
|
51193
|
-
if (nullCount > 0) {
|
|
51194
|
-
const sampleSize = Math.min(totalRows, sample2);
|
|
51195
|
-
results.push({
|
|
51196
|
-
column: col.name,
|
|
51197
|
-
nullCount,
|
|
51198
|
-
nullPercent: Number((nullCount / sampleSize * 100).toFixed(1))
|
|
51199
|
-
});
|
|
51200
|
-
}
|
|
51201
|
-
} catch {}
|
|
51202
|
-
}
|
|
51203
|
-
return results;
|
|
51204
|
-
}
|
|
51205
|
-
async checkOrphans(tableName, columns) {
|
|
51206
|
-
const fkColumns = columns.filter((c) => c.foreignKey);
|
|
51207
|
-
const results = [];
|
|
51208
|
-
for (const col of fkColumns) {
|
|
51209
|
-
if (!col.foreignKey)
|
|
51210
|
-
continue;
|
|
51211
|
-
try {
|
|
51212
|
-
const sql = `
|
|
51213
|
-
SELECT COUNT(*) as orphan_count
|
|
51214
|
-
FROM \`${tableName}\` child
|
|
51215
|
-
LEFT JOIN \`${col.foreignKey.table}\` parent
|
|
51216
|
-
ON child.\`${col.name}\` = parent.\`${col.foreignKey.column}\`
|
|
51217
|
-
WHERE child.\`${col.name}\` IS NOT NULL
|
|
51218
|
-
AND parent.\`${col.foreignKey.column}\` IS NULL
|
|
51219
|
-
`;
|
|
51220
|
-
const result = await this.adapter.execute(sql);
|
|
51221
|
-
const orphanCount = result[0]?.orphan_count || 0;
|
|
51222
|
-
if (orphanCount > 0) {
|
|
51223
|
-
results.push({
|
|
51224
|
-
column: col.name,
|
|
51225
|
-
references: `${col.foreignKey.table}.${col.foreignKey.column}`,
|
|
51226
|
-
orphanCount
|
|
51227
|
-
});
|
|
51228
|
-
}
|
|
51229
|
-
} catch {}
|
|
51230
|
-
}
|
|
51231
|
-
return results;
|
|
51232
|
-
}
|
|
51233
|
-
async checkDuplicates(tableName, indexes) {
|
|
51234
|
-
const uniqueIndexes = indexes.filter((idx) => idx.unique);
|
|
51235
|
-
const results = [];
|
|
51236
|
-
for (const idx of uniqueIndexes) {
|
|
51237
|
-
try {
|
|
51238
|
-
const colList = idx.columns.map((c) => `\`${c}\``).join(", ");
|
|
51239
|
-
const sql = `
|
|
51240
|
-
SELECT COUNT(*) as dup_count FROM (
|
|
51241
|
-
SELECT ${colList}
|
|
51242
|
-
FROM \`${tableName}\`
|
|
51243
|
-
GROUP BY ${colList}
|
|
51244
|
-
HAVING COUNT(*) > 1
|
|
51245
|
-
) dups
|
|
51246
|
-
`;
|
|
51247
|
-
const result = await this.adapter.execute(sql);
|
|
51248
|
-
const dupCount = result[0]?.dup_count || 0;
|
|
51249
|
-
if (dupCount > 0) {
|
|
51250
|
-
results.push({
|
|
51251
|
-
columns: idx.columns,
|
|
51252
|
-
indexName: idx.name,
|
|
51253
|
-
duplicateCount: dupCount
|
|
51254
|
-
});
|
|
51255
|
-
}
|
|
51256
|
-
} catch {}
|
|
51257
|
-
}
|
|
51258
|
-
return results;
|
|
51259
|
-
}
|
|
51260
|
-
async checkEmptyStrings(tableName, columns) {
|
|
51261
|
-
const stringColumns = columns.filter((c) => /varchar|text|char/i.test(c.type));
|
|
51262
|
-
const results = [];
|
|
51263
|
-
for (const col of stringColumns) {
|
|
51264
|
-
try {
|
|
51265
|
-
const sql = `SELECT COUNT(*) as empty_count FROM \`${tableName}\` WHERE \`${col.name}\` = ''`;
|
|
51266
|
-
const result = await this.adapter.execute(sql);
|
|
51267
|
-
const count = result[0]?.empty_count || 0;
|
|
51268
|
-
if (count > 0) {
|
|
51269
|
-
results.push({ column: col.name, count });
|
|
51270
|
-
}
|
|
51271
|
-
} catch {}
|
|
51272
|
-
}
|
|
51273
|
-
return results;
|
|
51274
|
-
}
|
|
51275
|
-
}
|
|
51276
|
-
|
|
51277
53198
|
// src/commands/check.ts
|
|
51278
53199
|
init_size_category();
|
|
51279
53200
|
init_validation();
|
|
@@ -51615,7 +53536,8 @@ var statusCommand = new Command("status").description("Show current configuratio
|
|
|
51615
53536
|
|
|
51616
53537
|
// src/commands/doctor.ts
|
|
51617
53538
|
init_validation();
|
|
51618
|
-
|
|
53539
|
+
init_schema_path();
|
|
53540
|
+
import { join as join10 } from "path";
|
|
51619
53541
|
var ALLOWED_FORMATS7 = ["text", "json"];
|
|
51620
53542
|
var SENSITIVE_PATTERNS = [
|
|
51621
53543
|
"password",
|
|
@@ -51633,6 +53555,15 @@ var SENSITIVE_PATTERNS = [
|
|
|
51633
53555
|
"ssn",
|
|
51634
53556
|
"credit_card"
|
|
51635
53557
|
];
|
|
53558
|
+
function resolveSchemaLastUpdated(indexJson, configMetadata) {
|
|
53559
|
+
if (indexJson && typeof indexJson === "object") {
|
|
53560
|
+
const idx = indexJson;
|
|
53561
|
+
const fromIndex = idx.metadata?.lastRefreshed ?? idx.updatedAt;
|
|
53562
|
+
if (fromIndex)
|
|
53563
|
+
return fromIndex;
|
|
53564
|
+
}
|
|
53565
|
+
return configMetadata?.schemaLastUpdated ?? null;
|
|
53566
|
+
}
|
|
51636
53567
|
function compareSemver(a, b) {
|
|
51637
53568
|
const pa = a.split(".").map(Number);
|
|
51638
53569
|
const pb = b.split(".").map(Number);
|
|
@@ -51677,7 +53608,7 @@ var runDoctorChecks = {
|
|
|
51677
53608
|
}
|
|
51678
53609
|
},
|
|
51679
53610
|
async checkConfigExists(configPath, existsFn) {
|
|
51680
|
-
const exists = existsFn ? await existsFn(configPath) : await Bun.file(configPath).exists() || await Bun.file(
|
|
53611
|
+
const exists = existsFn ? await existsFn(configPath) : await Bun.file(configPath).exists() || await Bun.file(join10(configPath, "config.json")).exists();
|
|
51681
53612
|
return {
|
|
51682
53613
|
group: "Configuration",
|
|
51683
53614
|
label: "Config exists",
|
|
@@ -51771,7 +53702,7 @@ var runDoctorChecks = {
|
|
|
51771
53702
|
},
|
|
51772
53703
|
async checkV2Config(configPath) {
|
|
51773
53704
|
const results = [];
|
|
51774
|
-
const configFile = Bun.file(
|
|
53705
|
+
const configFile = Bun.file(join10(configPath, "config.json"));
|
|
51775
53706
|
if (!await configFile.exists())
|
|
51776
53707
|
return results;
|
|
51777
53708
|
let raw;
|
|
@@ -51811,7 +53742,7 @@ var runDoctorChecks = {
|
|
|
51811
53742
|
}
|
|
51812
53743
|
for (const [name, conn] of Object.entries(config.connections)) {
|
|
51813
53744
|
if (conn.envFile) {
|
|
51814
|
-
const envPath =
|
|
53745
|
+
const envPath = join10(configPath, "..", conn.envFile);
|
|
51815
53746
|
const exists = await Bun.file(envPath).exists();
|
|
51816
53747
|
results.push({
|
|
51817
53748
|
group: "Configuration",
|
|
@@ -51907,17 +53838,17 @@ var doctorCommand = new Command("doctor").description("Run diagnostic checks on
|
|
|
51907
53838
|
logger.debug("Could not list tables for blacklist/large table check");
|
|
51908
53839
|
}
|
|
51909
53840
|
try {
|
|
51910
|
-
const
|
|
53841
|
+
const schemaConnName = await getSchemaIsolationConnectionName(configPath);
|
|
53842
|
+
const indexPath = join10(resolveSchemaPath(configPath, schemaConnName), "index.json");
|
|
51911
53843
|
const indexFile = Bun.file(indexPath);
|
|
53844
|
+
let indexParsed = null;
|
|
51912
53845
|
if (await indexFile.exists()) {
|
|
51913
|
-
|
|
51914
|
-
const index = JSON.parse(indexContent);
|
|
51915
|
-
results.push(runDoctorChecks.checkSchemaCacheFreshness(index.updatedAt ?? null));
|
|
51916
|
-
} else {
|
|
51917
|
-
results.push(runDoctorChecks.checkSchemaCacheFreshness(null));
|
|
53846
|
+
indexParsed = JSON.parse(await indexFile.text());
|
|
51918
53847
|
}
|
|
53848
|
+
const lastUpdated = resolveSchemaLastUpdated(indexParsed, config.metadata);
|
|
53849
|
+
results.push(runDoctorChecks.checkSchemaCacheFreshness(lastUpdated));
|
|
51919
53850
|
} catch {
|
|
51920
|
-
results.push(runDoctorChecks.checkSchemaCacheFreshness(null));
|
|
53851
|
+
results.push(runDoctorChecks.checkSchemaCacheFreshness(config.metadata?.schemaLastUpdated ?? null));
|
|
51921
53852
|
}
|
|
51922
53853
|
await adapter.disconnect();
|
|
51923
53854
|
} catch (error) {
|
|
@@ -51949,7 +53880,7 @@ var doctorCommand = new Command("doctor").description("Run diagnostic checks on
|
|
|
51949
53880
|
});
|
|
51950
53881
|
|
|
51951
53882
|
// src/commands/completion.ts
|
|
51952
|
-
import { join as
|
|
53883
|
+
import { join as join11 } from "path";
|
|
51953
53884
|
import { homedir as homedir2 } from "os";
|
|
51954
53885
|
function extractCommands(program2) {
|
|
51955
53886
|
return program2.commands.map((cmd) => ({
|
|
@@ -52054,11 +53985,11 @@ function getInstallPath2(shell) {
|
|
|
52054
53985
|
const home = homedir2();
|
|
52055
53986
|
switch (shell) {
|
|
52056
53987
|
case "bash":
|
|
52057
|
-
return
|
|
53988
|
+
return join11(home, ".bashrc");
|
|
52058
53989
|
case "zsh":
|
|
52059
|
-
return
|
|
53990
|
+
return join11(home, ".zshrc");
|
|
52060
53991
|
case "fish":
|
|
52061
|
-
return
|
|
53992
|
+
return join11(home, ".config", "fish", "completions", "dbcli.fish");
|
|
52062
53993
|
default:
|
|
52063
53994
|
throw new Error(`Unsupported shell: ${shell}. Supported: bash, zsh, fish`);
|
|
52064
53995
|
}
|
|
@@ -52078,7 +54009,7 @@ var MARKER_END = "# <<< dbcli completion <<<";
|
|
|
52078
54009
|
async function installCompletion(shell, script) {
|
|
52079
54010
|
const targetPath = getInstallPath2(shell);
|
|
52080
54011
|
if (shell === "fish") {
|
|
52081
|
-
const dir =
|
|
54012
|
+
const dir = join11(homedir2(), ".config", "fish", "completions");
|
|
52082
54013
|
await Bun.$`mkdir -p ${dir}`.quiet();
|
|
52083
54014
|
await Bun.file(targetPath).write(script);
|
|
52084
54015
|
console.log(colors.success(`\u2713 Fish completion installed to ${targetPath}`));
|
|
@@ -52220,22 +54151,34 @@ async function checkForUpdate(currentVersion, cachePath, existingCache) {
|
|
|
52220
54151
|
|
|
52221
54152
|
// src/commands/upgrade.ts
|
|
52222
54153
|
function formatAlreadyUpToDate(version) {
|
|
52223
|
-
return colors.success(
|
|
54154
|
+
return colors.success(t_vars("upgrade.already_up_to_date", { version }));
|
|
52224
54155
|
}
|
|
52225
54156
|
function formatUpgradeMessage(currentVersion, latestVersion) {
|
|
52226
54157
|
return [
|
|
52227
|
-
colors.info(`
|
|
52228
|
-
colors.success(`
|
|
54158
|
+
colors.info(` ${t_vars("upgrade.current_version", { version: currentVersion })}`),
|
|
54159
|
+
colors.success(` ${t_vars("upgrade.latest_version", { version: latestVersion })}`)
|
|
52229
54160
|
].join(`
|
|
52230
54161
|
`);
|
|
52231
54162
|
}
|
|
52232
54163
|
function formatUpdateHint(latestVersion) {
|
|
52233
|
-
return colors.warn(
|
|
54164
|
+
return colors.warn(t_vars("upgrade.update_hint", { version: latestVersion }));
|
|
54165
|
+
}
|
|
54166
|
+
function formatSkillUpdateReminder(platforms) {
|
|
54167
|
+
if (platforms.length === 0)
|
|
54168
|
+
return "";
|
|
54169
|
+
return [
|
|
54170
|
+
colors.warn(`
|
|
54171
|
+
${t("skill.update_available")}`),
|
|
54172
|
+
...platforms.map((p) => colors.info(` - ${p}`)),
|
|
54173
|
+
colors.dim(`
|
|
54174
|
+
${t("skill.update_hint")}`)
|
|
54175
|
+
].join(`
|
|
54176
|
+
`);
|
|
52234
54177
|
}
|
|
52235
|
-
var upgradeCommand = new Command("upgrade").description("
|
|
54178
|
+
var upgradeCommand = new Command("upgrade").description(t("upgrade.description")).option("--check", "Only check for updates, do not upgrade").action(async (options) => {
|
|
52236
54179
|
const configPath = upgradeCommand.parent?.opts().config ?? ".dbcli";
|
|
52237
54180
|
const currentVersion = package_default.version;
|
|
52238
|
-
console.log(colors.bold("
|
|
54181
|
+
console.log(colors.bold(t("upgrade.checking")));
|
|
52239
54182
|
let cachePath = null;
|
|
52240
54183
|
try {
|
|
52241
54184
|
const file = Bun.file(configPath);
|
|
@@ -52247,41 +54190,53 @@ var upgradeCommand = new Command("upgrade").description("Check for updates and u
|
|
|
52247
54190
|
cachePath = null;
|
|
52248
54191
|
}
|
|
52249
54192
|
const result = await checkForUpdate(currentVersion, cachePath);
|
|
54193
|
+
const outdatedSkills = await checkSkillUpdates();
|
|
52250
54194
|
if (!result) {
|
|
52251
|
-
console.error(colors.warn(
|
|
54195
|
+
console.error(colors.warn(t("upgrade.network_error")));
|
|
54196
|
+
if (outdatedSkills.length > 0) {
|
|
54197
|
+
console.log(formatSkillUpdateReminder(outdatedSkills));
|
|
54198
|
+
}
|
|
52252
54199
|
process.exit(0);
|
|
52253
54200
|
}
|
|
52254
54201
|
if (!result.hasUpdate) {
|
|
52255
54202
|
console.log(formatAlreadyUpToDate(currentVersion));
|
|
54203
|
+
if (outdatedSkills.length > 0) {
|
|
54204
|
+
console.log(formatSkillUpdateReminder(outdatedSkills));
|
|
54205
|
+
}
|
|
52256
54206
|
process.exit(0);
|
|
52257
54207
|
}
|
|
52258
54208
|
console.log(colors.warn(`
|
|
52259
|
-
|
|
54209
|
+
${t_vars("upgrade.new_version_available", { version: result.latestVersion })}`));
|
|
52260
54210
|
console.log(formatUpgradeMessage(currentVersion, result.latestVersion));
|
|
52261
54211
|
console.log();
|
|
52262
54212
|
if (options.check) {
|
|
52263
|
-
console.log(colors.dim(
|
|
54213
|
+
console.log(colors.dim(` ${t("upgrade.install_hint")}`));
|
|
54214
|
+
if (outdatedSkills.length > 0) {
|
|
54215
|
+
console.log(formatSkillUpdateReminder(outdatedSkills));
|
|
54216
|
+
}
|
|
52264
54217
|
process.exit(0);
|
|
52265
54218
|
}
|
|
52266
|
-
console.log(colors.bold("
|
|
52267
|
-
console.log(colors.dim(`
|
|
54219
|
+
console.log(colors.bold(t("upgrade.upgrading")));
|
|
54220
|
+
console.log(colors.dim(` ${t("upgrade.manual_hint")}`));
|
|
52268
54221
|
console.log();
|
|
52269
54222
|
const proc = Bun.$`bun add -g @carllee1983/dbcli@latest`.nothrow();
|
|
52270
54223
|
const result2 = await proc;
|
|
52271
54224
|
if (result2.exitCode === 0) {
|
|
52272
54225
|
console.log(colors.success(`
|
|
52273
|
-
|
|
54226
|
+
${t_vars("upgrade.success", { version: result.latestVersion })}`));
|
|
54227
|
+
console.log(colors.dim(`
|
|
54228
|
+
${t("upgrade.skill_recheck_hint")}`));
|
|
52274
54229
|
} else {
|
|
52275
54230
|
console.error(colors.error(`
|
|
52276
|
-
|
|
52277
|
-
console.error(colors.dim(
|
|
54231
|
+
${t("upgrade.failed")}`));
|
|
54232
|
+
console.error(colors.dim(` ${t("upgrade.manual_hint")}`));
|
|
52278
54233
|
process.exit(1);
|
|
52279
54234
|
}
|
|
52280
54235
|
});
|
|
52281
54236
|
|
|
52282
54237
|
// src/commands/shell.ts
|
|
52283
54238
|
import { createInterface as createInterface2 } from "readline";
|
|
52284
|
-
import { join as
|
|
54239
|
+
import { join as join12 } from "path";
|
|
52285
54240
|
import { homedir as homedir3 } from "os";
|
|
52286
54241
|
|
|
52287
54242
|
// src/core/repl/types.ts
|
|
@@ -52969,7 +54924,7 @@ function matchWithSuffix(candidates, prefix) {
|
|
|
52969
54924
|
|
|
52970
54925
|
// src/commands/shell.ts
|
|
52971
54926
|
var import_picocolors4 = __toESM(require_picocolors(), 1);
|
|
52972
|
-
var HISTORY_PATH =
|
|
54927
|
+
var HISTORY_PATH = join12(homedir3(), ".dbcli_history");
|
|
52973
54928
|
var shellCommand = new Command("shell").description("Interactive database shell with auto-completion and syntax highlighting").option("--sql", "SQL-only mode (skip dbcli command parsing)").action(async (options) => {
|
|
52974
54929
|
const globalOpts = shellCommand.optsWithGlobals?.() ?? {};
|
|
52975
54930
|
const configPath = globalOpts.config ?? ".dbcli";
|
|
@@ -53868,7 +55823,7 @@ addExecOpts(migrateCommand.command("drop-enum <name>").description(t("migrate.dr
|
|
|
53868
55823
|
});
|
|
53869
55824
|
|
|
53870
55825
|
// src/commands/use.ts
|
|
53871
|
-
import { join as
|
|
55826
|
+
import { join as join13 } from "path";
|
|
53872
55827
|
async function switchDefault(configPath, name, config) {
|
|
53873
55828
|
if (!config.connections[name]) {
|
|
53874
55829
|
const available = Object.keys(config.connections).join(", ");
|
|
@@ -53890,7 +55845,7 @@ function listConnectionsForDisplay(config) {
|
|
|
53890
55845
|
});
|
|
53891
55846
|
}
|
|
53892
55847
|
async function ensureV2Config(configPath) {
|
|
53893
|
-
const configFile = Bun.file(
|
|
55848
|
+
const configFile = Bun.file(join13(configPath, "config.json"));
|
|
53894
55849
|
if (!await configFile.exists()) {
|
|
53895
55850
|
throw new ConfigError(t("init.config_not_found"));
|
|
53896
55851
|
}
|
|
@@ -53927,7 +55882,7 @@ var useCommand = new Command("use").description("Switch or display the default d
|
|
|
53927
55882
|
});
|
|
53928
55883
|
|
|
53929
55884
|
// src/cli.ts
|
|
53930
|
-
import { join as
|
|
55885
|
+
import { join as join14 } from "path";
|
|
53931
55886
|
var _bgVersionCheckResult;
|
|
53932
55887
|
var program2 = new Command().name("dbcli").description("Database CLI for AI agents").version(package_default.version).option("--no-color", "Disable colored output").option("-v, --verbose", "Increase verbosity (-v verbose, -vv debug)", (_, prev) => prev + 1, 0).option("-q, --quiet", "Suppress non-essential output").option("--config <path>", "Path to .dbcli config file", ".dbcli").option("--use <connection>", "Use a specific named connection (v2 config)");
|
|
53933
55888
|
program2.hook("preAction", (thisCommand, actionCommand) => {
|
|
@@ -53953,7 +55908,7 @@ program2.hook("preAction", (thisCommand, actionCommand) => {
|
|
|
53953
55908
|
try {
|
|
53954
55909
|
let cache = null;
|
|
53955
55910
|
try {
|
|
53956
|
-
const cacheFile = Bun.file(
|
|
55911
|
+
const cacheFile = Bun.file(join14(configPath, "version-check.json"));
|
|
53957
55912
|
if (await cacheFile.exists()) {
|
|
53958
55913
|
cache = await cacheFile.json();
|
|
53959
55914
|
}
|
|
@@ -53966,16 +55921,24 @@ program2.hook("preAction", (thisCommand, actionCommand) => {
|
|
|
53966
55921
|
})();
|
|
53967
55922
|
}
|
|
53968
55923
|
});
|
|
53969
|
-
program2.hook("postAction", () => {
|
|
55924
|
+
program2.hook("postAction", async (thisCommand, actionCommand) => {
|
|
53970
55925
|
if (_bgVersionCheckResult?.hasUpdate) {
|
|
53971
55926
|
process.stderr.write(formatUpdateHint(_bgVersionCheckResult.latestVersion) + `
|
|
53972
55927
|
`);
|
|
53973
55928
|
}
|
|
55929
|
+
const isUpgradeOrSkill = ["upgrade", "skill"].includes(actionCommand.name());
|
|
55930
|
+
if (!thisCommand.opts().quiet && !isUpgradeOrSkill) {
|
|
55931
|
+
const outdatedSkills = await checkSkillUpdates();
|
|
55932
|
+
if (outdatedSkills.length > 0) {
|
|
55933
|
+
process.stderr.write(formatSkillUpdateReminder(outdatedSkills) + `
|
|
55934
|
+
`);
|
|
55935
|
+
}
|
|
55936
|
+
}
|
|
53974
55937
|
});
|
|
53975
55938
|
program2.addCommand(initCommand);
|
|
53976
55939
|
program2.addCommand(listCommand);
|
|
53977
55940
|
program2.addCommand(schemaCommand);
|
|
53978
|
-
program2.command("query <sql>").description(t("query.description")).option("--format <type>", "Output format: table, json, csv", "table").option("--limit <number>", "Limit result rows (overrides auto-limit)",
|
|
55941
|
+
program2.command("query <sql>").description(t("query.description")).option("--format <type>", "Output format: table, json, csv", "table").option("--limit <number>", "Limit result rows (overrides auto-limit)", (val) => parseInt(val, 10)).option("--no-limit", "Disable auto-limit in query-only mode").action(async (sql, options) => {
|
|
53979
55942
|
try {
|
|
53980
55943
|
await queryCommand(sql, options);
|
|
53981
55944
|
} catch (error) {
|