@carllee1983/dbcli 3.0.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core.mjs CHANGED
@@ -1,35 +1,5 @@
1
1
  // @bun
2
- var __create = Object.create;
3
- var __getProtoOf = Object.getPrototypeOf;
4
2
  var __defProp = Object.defineProperty;
5
- var __getOwnPropNames = Object.getOwnPropertyNames;
6
- var __hasOwnProp = Object.prototype.hasOwnProperty;
7
- function __accessProp(key) {
8
- return this[key];
9
- }
10
- var __toESMCache_node;
11
- var __toESMCache_esm;
12
- var __toESM = (mod, isNodeMode, target) => {
13
- var canCache = mod != null && typeof mod === "object";
14
- if (canCache) {
15
- var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
- var cached = cache.get(mod);
17
- if (cached)
18
- return cached;
19
- }
20
- target = mod != null ? __create(__getProtoOf(mod)) : {};
21
- const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
22
- for (let key of __getOwnPropNames(mod))
23
- if (!__hasOwnProp.call(to, key))
24
- __defProp(to, key, {
25
- get: __accessProp.bind(mod, key),
26
- enumerable: true
27
- });
28
- if (canCache)
29
- cache.set(mod, to);
30
- return to;
31
- };
32
- var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
33
3
  var __returnValue = (v) => v;
34
4
  function __exportSetter(name, newValue) {
35
5
  this[name] = __returnValue.bind(null, newValue);
@@ -46,76 +16,6 @@ var __export = (target, all) => {
46
16
  var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
47
17
  var __require = import.meta.require;
48
18
 
49
- // node_modules/picocolors/picocolors.js
50
- var require_picocolors = __commonJS((exports, module) => {
51
- var p = process || {};
52
- var argv = p.argv || [];
53
- var env = p.env || {};
54
- var isColorSupported = !(!!env.NO_COLOR || argv.includes("--no-color")) && (!!env.FORCE_COLOR || argv.includes("--color") || p.platform === "win32" || (p.stdout || {}).isTTY && env.TERM !== "dumb" || !!env.CI);
55
- var formatter = (open, close, replace = open) => (input) => {
56
- let string = "" + input, index = string.indexOf(close, open.length);
57
- return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
58
- };
59
- var replaceClose = (string, close, replace, index) => {
60
- let result = "", cursor = 0;
61
- do {
62
- result += string.substring(cursor, index) + replace;
63
- cursor = index + close.length;
64
- index = string.indexOf(close, cursor);
65
- } while (~index);
66
- return result + string.substring(cursor);
67
- };
68
- var createColors = (enabled = isColorSupported) => {
69
- let f = enabled ? formatter : () => String;
70
- return {
71
- isColorSupported: enabled,
72
- reset: f("\x1B[0m", "\x1B[0m"),
73
- bold: f("\x1B[1m", "\x1B[22m", "\x1B[22m\x1B[1m"),
74
- dim: f("\x1B[2m", "\x1B[22m", "\x1B[22m\x1B[2m"),
75
- italic: f("\x1B[3m", "\x1B[23m"),
76
- underline: f("\x1B[4m", "\x1B[24m"),
77
- inverse: f("\x1B[7m", "\x1B[27m"),
78
- hidden: f("\x1B[8m", "\x1B[28m"),
79
- strikethrough: f("\x1B[9m", "\x1B[29m"),
80
- black: f("\x1B[30m", "\x1B[39m"),
81
- red: f("\x1B[31m", "\x1B[39m"),
82
- green: f("\x1B[32m", "\x1B[39m"),
83
- yellow: f("\x1B[33m", "\x1B[39m"),
84
- blue: f("\x1B[34m", "\x1B[39m"),
85
- magenta: f("\x1B[35m", "\x1B[39m"),
86
- cyan: f("\x1B[36m", "\x1B[39m"),
87
- white: f("\x1B[37m", "\x1B[39m"),
88
- gray: f("\x1B[90m", "\x1B[39m"),
89
- bgBlack: f("\x1B[40m", "\x1B[49m"),
90
- bgRed: f("\x1B[41m", "\x1B[49m"),
91
- bgGreen: f("\x1B[42m", "\x1B[49m"),
92
- bgYellow: f("\x1B[43m", "\x1B[49m"),
93
- bgBlue: f("\x1B[44m", "\x1B[49m"),
94
- bgMagenta: f("\x1B[45m", "\x1B[49m"),
95
- bgCyan: f("\x1B[46m", "\x1B[49m"),
96
- bgWhite: f("\x1B[47m", "\x1B[49m"),
97
- blackBright: f("\x1B[90m", "\x1B[39m"),
98
- redBright: f("\x1B[91m", "\x1B[39m"),
99
- greenBright: f("\x1B[92m", "\x1B[39m"),
100
- yellowBright: f("\x1B[93m", "\x1B[39m"),
101
- blueBright: f("\x1B[94m", "\x1B[39m"),
102
- magentaBright: f("\x1B[95m", "\x1B[39m"),
103
- cyanBright: f("\x1B[96m", "\x1B[39m"),
104
- whiteBright: f("\x1B[97m", "\x1B[39m"),
105
- bgBlackBright: f("\x1B[100m", "\x1B[49m"),
106
- bgRedBright: f("\x1B[101m", "\x1B[49m"),
107
- bgGreenBright: f("\x1B[102m", "\x1B[49m"),
108
- bgYellowBright: f("\x1B[103m", "\x1B[49m"),
109
- bgBlueBright: f("\x1B[104m", "\x1B[49m"),
110
- bgMagentaBright: f("\x1B[105m", "\x1B[49m"),
111
- bgCyanBright: f("\x1B[106m", "\x1B[49m"),
112
- bgWhiteBright: f("\x1B[107m", "\x1B[49m")
113
- };
114
- };
115
- module.exports = createColors();
116
- module.exports.createColors = createColors;
117
- });
118
-
119
19
  // node_modules/lru-cache/dist/esm/node/index.min.js
120
20
  import { tracingChannel as j, channel as G } from "diagnostics_channel";
121
21
  var w, W, C, D = () => w.hasSubscribers || W.hasSubscribers, I, L, P = (u, e, t2, i) => {
@@ -1245,281 +1145,6 @@ var ENGINE_CAPABILITIES = Object.freeze({
1245
1145
  shell: cap("limited", "interactive", "Dedicated Kibana Dev Tools-style REPL (method/path + JSON body, blank-line submit); read-focused.")
1246
1146
  })
1247
1147
  });
1248
- // src/adapters/error-mapper.ts
1249
- var TRANSPORT_CODES = {
1250
- ECONNREFUSED: "ECONNREFUSED",
1251
- ETIMEDOUT: "ETIMEDOUT",
1252
- ESOCKETTIMEDOUT: "ETIMEDOUT",
1253
- ENOTFOUND: "ENOTFOUND",
1254
- EAI_AGAIN: "ENOTFOUND",
1255
- PROTOCOL_CONNECTION_LOST: "CONNECTION_LOST",
1256
- PROTOCOL_SEQUENCE_TIMEOUT: "ETIMEDOUT",
1257
- ECONNRESET: "CONNECTION_LOST",
1258
- EPIPE: "CONNECTION_LOST",
1259
- ECONNABORTED: "CONNECTION_LOST",
1260
- EHOSTUNREACH: "EHOSTUNREACH",
1261
- ENETUNREACH: "EHOSTUNREACH",
1262
- ENETDOWN: "EHOSTUNREACH"
1263
- };
1264
- var TLS_CODE_PREFIX = /^(CERT_|SELF_SIGNED_|DEPTH_ZERO_|UNABLE_TO_(?:GET|VERIFY)_|ERR_TLS_|ERR_SSL_)/;
1265
- var MYSQL_CODES = {
1266
- ER_NO_SUCH_TABLE: "TABLE_NOT_FOUND",
1267
- ER_BAD_FIELD_ERROR: "COLUMN_NOT_FOUND",
1268
- ER_PARSE_ERROR: "SQL_SYNTAX_ERROR",
1269
- ER_ACCESS_DENIED_ERROR: "AUTH_FAILED",
1270
- ER_DBACCESS_DENIED_ERROR: "AUTH_FAILED",
1271
- ER_NOT_SUPPORTED_AUTH_MODE: "AUTH_FAILED",
1272
- ER_MUST_CHANGE_PASSWORD_LOGIN: "AUTH_FAILED",
1273
- ER_QUERY_TIMEOUT: "STATEMENT_TIMEOUT",
1274
- ER_STATEMENT_TIMEOUT: "STATEMENT_TIMEOUT",
1275
- ER_CON_COUNT_ERROR: "TOO_MANY_CONNECTIONS",
1276
- ER_SERVER_SHUTDOWN: "CONNECTION_LOST"
1277
- };
1278
- var MYSQL_ERRNOS = {
1279
- 1045: "AUTH_FAILED",
1280
- 1044: "AUTH_FAILED",
1281
- 1054: "COLUMN_NOT_FOUND",
1282
- 1064: "SQL_SYNTAX_ERROR",
1283
- 1146: "TABLE_NOT_FOUND",
1284
- 1251: "AUTH_FAILED",
1285
- 1698: "AUTH_FAILED",
1286
- 1040: "TOO_MANY_CONNECTIONS",
1287
- 1053: "CONNECTION_LOST",
1288
- 2006: "CONNECTION_LOST",
1289
- 2013: "CONNECTION_LOST",
1290
- 1969: "STATEMENT_TIMEOUT",
1291
- 3024: "STATEMENT_TIMEOUT"
1292
- };
1293
- var SQLSTATES = {
1294
- "28000": "AUTH_FAILED",
1295
- "28P01": "AUTH_FAILED",
1296
- "42601": "SQL_SYNTAX_ERROR",
1297
- "42P01": "TABLE_NOT_FOUND",
1298
- "42703": "COLUMN_NOT_FOUND",
1299
- "57014": "STATEMENT_TIMEOUT",
1300
- "08000": "CONNECTION_LOST",
1301
- "08003": "CONNECTION_LOST",
1302
- "08006": "CONNECTION_LOST",
1303
- "08001": "ECONNREFUSED",
1304
- "08004": "CONNECTION_REJECTED",
1305
- "53300": "TOO_MANY_CONNECTIONS",
1306
- "57P01": "CONNECTION_LOST",
1307
- "57P02": "CONNECTION_LOST",
1308
- "57P03": "SERVER_NOT_READY"
1309
- };
1310
- var REDIS_PREFIXES = {
1311
- NOAUTH: "AUTH_FAILED",
1312
- WRONGPASS: "AUTH_FAILED"
1313
- };
1314
- var FALLBACK_PATTERNS = [
1315
- [/connection refused/i, "ECONNREFUSED"],
1316
- [/\bECONNREFUSED\b/, "ECONNREFUSED"],
1317
- [/connect(?:ion)? time(?:d )?out/i, "ETIMEDOUT"],
1318
- [/timeout exceeded/i, "ETIMEDOUT"],
1319
- [/timed out/i, "ETIMEDOUT"],
1320
- [/connection terminated/i, "CONNECTION_LOST"],
1321
- [/server closed the connection/i, "CONNECTION_LOST"],
1322
- [/server has gone away/i, "CONNECTION_LOST"],
1323
- [/lost connection to \S+ server/i, "CONNECTION_LOST"],
1324
- [/getaddrinfo/i, "ENOTFOUND"],
1325
- [/authentication failed/i, "AUTH_FAILED"],
1326
- [/access denied for user/i, "AUTH_FAILED"],
1327
- [/no pg_hba\.conf entry/i, "AUTH_FAILED"],
1328
- [/role\s+".*?"\s+does not exist/i, "AUTH_FAILED"],
1329
- [/password supplied/i, "AUTH_FAILED"]
1330
- ];
1331
- var PG_CANCEL_BY_TIMEOUT = /statement timeout/i;
1332
- function categorize(errCode, errno, errMsg, system) {
1333
- if (errCode) {
1334
- if (TLS_CODE_PREFIX.test(errCode))
1335
- return "TLS_ERROR";
1336
- const known = TRANSPORT_CODES[errCode] ?? MYSQL_CODES[errCode] ?? SQLSTATES[errCode] ?? REDIS_PREFIXES[errCode];
1337
- if (known === "STATEMENT_TIMEOUT" && errCode === "57014" && !PG_CANCEL_BY_TIMEOUT.test(errMsg)) {
1338
- return null;
1339
- }
1340
- if (known)
1341
- return known;
1342
- }
1343
- const isMysqlFamily = system === "mysql" || system === "mariadb";
1344
- if (isMysqlFamily && typeof errno === "number" && MYSQL_ERRNOS[errno]) {
1345
- return MYSQL_ERRNOS[errno];
1346
- }
1347
- const redisPrefix = errMsg.match(/^([A-Z]+)\s/)?.[1];
1348
- if (redisPrefix && REDIS_PREFIXES[redisPrefix])
1349
- return REDIS_PREFIXES[redisPrefix];
1350
- if (errCode || isMysqlFamily && typeof errno === "number")
1351
- return null;
1352
- for (const [pattern, category] of FALLBACK_PATTERNS) {
1353
- if (pattern.test(errMsg))
1354
- return category;
1355
- }
1356
- return null;
1357
- }
1358
- var SYSTEM_FACTS = {
1359
- postgresql: {
1360
- serviceCheck: "systemctl status postgresql",
1361
- defaultPort: "default 5432",
1362
- firewallRule: "allow TCP 5432",
1363
- client: "psql",
1364
- logFile: "postgresql.log"
1365
- },
1366
- mysql: {
1367
- serviceCheck: "systemctl status mysql",
1368
- defaultPort: "default 3306",
1369
- firewallRule: "allow TCP 3306",
1370
- client: "mysql",
1371
- logFile: "mysql.log"
1372
- },
1373
- mariadb: {
1374
- serviceCheck: "systemctl status mariadb",
1375
- defaultPort: "default 3306",
1376
- firewallRule: "allow TCP 3306",
1377
- client: "mariadb",
1378
- logFile: "mariadb.log"
1379
- },
1380
- redis: {
1381
- serviceCheck: "redis-cli ping",
1382
- defaultPort: "default 6379",
1383
- firewallRule: "allow TCP 6379",
1384
- client: "redis-cli",
1385
- logFile: "redis.log"
1386
- }
1387
- };
1388
- function serviceHints(system, options) {
1389
- const facts = SYSTEM_FACTS[system];
1390
- return [
1391
- `Check that the ${system} service is running: ${facts.serviceCheck}`,
1392
- `Verify the port is correct: ${facts.defaultPort}`,
1393
- `Check that ${options.host} is reachable: ping ${options.host} or telnet ${options.host} ${options.port}`
1394
- ];
1395
- }
1396
- function mapError(error, system, options) {
1397
- if (error instanceof ConnectionError)
1398
- return error;
1399
- const err = error;
1400
- const errMsg = String(err?.message || String(error));
1401
- const errCode = String(err?.code || "");
1402
- const errno = err?.errno;
1403
- switch (categorize(errCode, errno, errMsg, system)) {
1404
- case "ECONNREFUSED":
1405
- return new ConnectionError("ECONNREFUSED", `Cannot connect to ${options.host}:${options.port} \u2014 server is not running or not listening on this port`, serviceHints(system, options));
1406
- case "ETIMEDOUT":
1407
- return new ConnectionError("ETIMEDOUT", `Connection timed out (${options.timeout || 5000}ms) \u2014 may be blocked by a firewall or slow network`, [
1408
- `Check firewall rules: ${SYSTEM_FACTS[system].firewallRule}`,
1409
- `Increase timeout: edit .dbcli and add "timeout": 15000`,
1410
- `Verify network connectivity: ping ${options.host} -c 3`
1411
- ]);
1412
- case "CONNECTION_LOST":
1413
- return new ConnectionError("CONNECTION_LOST", `Connection to ${options.host}:${options.port} was lost mid-session \u2014 the server closed it or the network dropped`, [
1414
- "Re-run the command: a dropped connection is often transient",
1415
- `Check whether the server restarted or is cycling: ${SYSTEM_FACTS[system].logFile}`,
1416
- "If it happens on long-running work, look at the server idle/wait timeout"
1417
- ]);
1418
- case "SERVER_NOT_READY":
1419
- return new ConnectionError("SERVER_NOT_READY", `${options.host}:${options.port} is not accepting connections yet \u2014 the server is starting up or recovering`, [
1420
- "Retry shortly: this clears on its own once startup or recovery finishes",
1421
- `Watch progress in the server log: ${SYSTEM_FACTS[system].logFile}`,
1422
- "On a replica, this also appears while it catches up with the primary"
1423
- ]);
1424
- case "CONNECTION_REJECTED":
1425
- return new ConnectionError("CONNECTION_REJECTED", `${options.host}:${options.port} answered but rejected the connection attempt`, [
1426
- "Check the server access rules (pg_hba.conf) for this user, database, and client address",
1427
- "If a pooler (pgbouncer / ProxySQL) sits in front, check its own rules and limits",
1428
- "Check whether the connection limit for this user or database is exhausted"
1429
- ]);
1430
- case "TOO_MANY_CONNECTIONS":
1431
- return new ConnectionError("TOO_MANY_CONNECTIONS", `${options.host}:${options.port} refused the connection \u2014 the server has no connection slots left`, [
1432
- "Wait and retry: the limit is on concurrent connections, not on you",
1433
- system === "postgresql" ? "Inspect usage: SELECT count(*) FROM pg_stat_activity \u2014 compare against max_connections" : 'Inspect usage: SHOW STATUS LIKE "Threads_connected" \u2014 compare against max_connections',
1434
- "Close idle sessions, or raise max_connections if the load is legitimate"
1435
- ]);
1436
- case "EHOSTUNREACH":
1437
- return new ConnectionError("EHOSTUNREACH", `No route to ${options.host} \u2014 the name resolved, but the host or network is unreachable`, [
1438
- "Check that you are on the right network or VPN",
1439
- `Check routing and firewall between here and ${options.host}`,
1440
- `Confirm the address is the one you meant: ${options.host}:${options.port}`
1441
- ]);
1442
- case "TLS_ERROR":
1443
- return new ConnectionError("TLS_ERROR", `TLS handshake failed: ${errMsg}`, [
1444
- 'Point the connection at the right CA bundle: set "caPath" in .dbcli',
1445
- 'For a self-signed certificate in a trusted network, set "rejectUnauthorized": false',
1446
- `Confirm the certificate covers the host you connected to: ${options.host}`
1447
- ]);
1448
- case "ENOTFOUND":
1449
- return new ConnectionError("ENOTFOUND", `Host not found: ${options.host}`, [
1450
- `Check the hostname spelling: ${options.host}`,
1451
- `Verify DNS resolution: nslookup ${options.host}`,
1452
- `If using localhost, try 127.0.0.1 (IPv4 vs IPv6 issue)`
1453
- ]);
1454
- case "AUTH_FAILED":
1455
- return new ConnectionError("AUTH_FAILED", `Authentication failed \u2014 check your username or password`, [
1456
- `Verify credentials: ${SYSTEM_FACTS[system].client} ${system === "postgresql" ? "-U" : "-u"} ${options.user} -h ${options.host}`,
1457
- `Check pg_hba.conf (PostgreSQL) or user privileges (MySQL)`,
1458
- `Re-run dbcli init to update credentials`
1459
- ]);
1460
- case "TABLE_NOT_FOUND": {
1461
- const mysqlMatch = errMsg.match(/Table\s+'(?:[^.']+\.)?([^']+)'/i);
1462
- const pgMatch = errMsg.match(/relation\s+"([^"]+)"/i);
1463
- const tableName = mysqlMatch?.[1] || pgMatch?.[1] || "unknown";
1464
- return new ConnectionError("TABLE_NOT_FOUND", `Table '${tableName}' not found in database '${options.database || "unknown"}'`, [
1465
- "Run `dbcli list` to see available tables",
1466
- "Run `dbcli schema <table>` to confirm the exact name (ORM export names may differ from DB table names)"
1467
- ]);
1468
- }
1469
- case "COLUMN_NOT_FOUND": {
1470
- const mysqlMatch = errMsg.match(/Unknown column\s+'([^']+)'/i);
1471
- const pgMatch = errMsg.match(/column\s+"([^"]+)"\s+does not exist/i);
1472
- const columnName = mysqlMatch?.[1] || pgMatch?.[1] || "unknown";
1473
- return new ConnectionError("COLUMN_NOT_FOUND", `Column '${columnName}' not found`, [
1474
- "Run `dbcli schema <table>` to see the actual column names",
1475
- "Column names are case-sensitive on some databases (PostgreSQL with quoted identifiers)"
1476
- ]);
1477
- }
1478
- case "STATEMENT_TIMEOUT": {
1479
- const limitMs = options.statementTimeout ?? options.timeout;
1480
- return new ConnectionError("STATEMENT_TIMEOUT", `Statement timed out${limitMs ? ` (${limitMs}ms)` : ""} \u2014 the server canceled this query before it finished`, [
1481
- 'Inspect the query plan: dbcli explain "<sql>"',
1482
- "Raise the ceiling for this run: dbcli --statement-timeout <ms> \u2026 (0 removes it)",
1483
- "Narrow the query: add WHERE filters, reduce the LIMIT, or index the scanned columns"
1484
- ], limitMs);
1485
- }
1486
- case "SQL_SYNTAX_ERROR":
1487
- return new ConnectionError("SQL_SYNTAX_ERROR", `SQL syntax error: ${errMsg}`, [
1488
- "Check your SQL syntax near the position reported above",
1489
- "In query-only mode, dbcli auto-appends LIMIT 1000; use --no-limit to disable",
1490
- "For SHOW/DESCRIBE/EXPLAIN statements, LIMIT is not allowed \u2014 these are not auto-limited"
1491
- ]);
1492
- }
1493
- const reportedCode = errCode || (typeof errno === "number" ? String(errno) : "");
1494
- if (reportedCode) {
1495
- return new ConnectionError("UNKNOWN", `Database error (${reportedCode}): ${errMsg}`, [
1496
- "Run `dbcli schema <table>` to confirm the objects referenced by this statement",
1497
- `Look up ${reportedCode} in the ${system} error reference for the exact cause`
1498
- ]);
1499
- }
1500
- return new ConnectionError("UNKNOWN", `Connection failed: ${errMsg}`, [
1501
- `Check connection parameters: host=${options.host}, port=${options.port}, user=${options.user}`,
1502
- `View server logs: ${SYSTEM_FACTS[system].logFile}`,
1503
- `Try connecting directly with the ${SYSTEM_FACTS[system].client} command-line tool`
1504
- ]);
1505
- }
1506
-
1507
- // src/adapters/sql-adapter-utils.ts
1508
- function requireConnected(connection) {
1509
- if (!connection) {
1510
- throw new ConnectionError("UNKNOWN", "Database connection not established", [
1511
- "Call connect() to establish a connection"
1512
- ]);
1513
- }
1514
- return connection;
1515
- }
1516
- async function withMappedConnectionError(system, options, operation) {
1517
- try {
1518
- return await operation();
1519
- } catch (error) {
1520
- throw mapError(error, system, options);
1521
- }
1522
- }
1523
1148
  // resources/lang/en/messages.json
1524
1149
  var messages_default = {
1525
1150
  init: {
@@ -1782,7 +1407,8 @@ Reference: {referencePath}`,
1782
1407
  table_added: "\u2713 Table '{table}' added to blacklist",
1783
1408
  table_removed: "\u2713 Table '{table}' removed from blacklist",
1784
1409
  column_added: "\u2713 Column '{table}.{column}' added to blacklist",
1785
- column_removed: "\u2713 Column '{table}.{column}' removed from blacklist"
1410
+ column_removed: "\u2713 Column '{table}.{column}' removed from blacklist",
1411
+ refuse_wildcard: "Refused: '{table}' contains wildcard characters, which are matched literally on {system} \u2014 the entry would never match anything. Write the exact name."
1786
1412
  },
1787
1413
  security: {
1788
1414
  columns_omitted: "Security: {count} column(s) were omitted based on your blacklist"
@@ -2095,7 +1721,8 @@ var messages_default2 = {
2095
1721
  table_added: "\u2713 \u8868\u683C '{table}' \u5DF2\u65B0\u589E\u81F3\u9ED1\u540D\u55AE",
2096
1722
  table_removed: "\u2713 \u8868\u683C '{table}' \u5DF2\u5F9E\u9ED1\u540D\u55AE\u79FB\u9664",
2097
1723
  column_added: "\u2713 \u6B04\u4F4D '{table}.{column}' \u5DF2\u65B0\u589E\u81F3\u9ED1\u540D\u55AE",
2098
- column_removed: "\u2713 \u6B04\u4F4D '{table}.{column}' \u5DF2\u5F9E\u9ED1\u540D\u55AE\u79FB\u9664"
1724
+ column_removed: "\u2713 \u6B04\u4F4D '{table}.{column}' \u5DF2\u5F9E\u9ED1\u540D\u55AE\u79FB\u9664",
1725
+ refuse_wildcard: "\u5DF2\u62D2\u7D55\uFF1A'{table}' \u542B\u6709\u842C\u7528\u5B57\u5143\uFF0C\u800C {system} \u4E0A\u7684\u6BD4\u5C0D\u662F\u9010\u5B57\u9032\u884C\u7684\u2014\u2014\u9019\u7B46\u8A2D\u5B9A\u6C38\u9060\u4E0D\u6703\u547D\u4E2D\u4EFB\u4F55\u6771\u897F\u3002\u8ACB\u5BEB\u51FA\u78BA\u5207\u7684\u540D\u7A31\u3002"
2099
1726
  },
2100
1727
  security: {
2101
1728
  columns_omitted: "\u5B89\u5168: {count} \u500B\u6B04\u4F4D\u6839\u64DA\u4F60\u7684\u9ED1\u540D\u55AE\u5DF2\u88AB\u96B1\u85CF"
@@ -2167,7 +1794,27 @@ var shell_default = {
2167
1794
  timing_display: "Time: {ms}ms",
2168
1795
  rows_display: "{count} row(s)",
2169
1796
  unknown_command: "Unknown command: '{command}'. Type '.help' for available commands.",
2170
- subcommand_prefix_hint: "This line is being read as SQL and is waiting for its `;`. If you meant the dbcli subcommand, type {command} instead. Ctrl-C cancels."
1797
+ subcommand_prefix_hint: "This line is being read as SQL and is waiting for its `;`. If you meant the dbcli subcommand, type {command} instead. Ctrl-C cancels.",
1798
+ es: {
1799
+ parse_empty: "Empty request",
1800
+ parse_needs_method_path: 'Request requires a method and a path, e.g. "GET /index/_search"',
1801
+ parse_needs_path: "Request requires a path",
1802
+ refuse_dot_segment: "Refused: '{path}' contains a '..' segment once decoded, and dbcli cannot tell which path Elasticsearch would route it to. Write the path out without '..'.",
1803
+ refuse_unparseable: "Refused: '{path}' is not the request the server would receive. It cannot be parsed as a path.",
1804
+ refuse_not_canonical: "Refused: '{path}' is not the request the server would receive. Write it as '{canonical}'.",
1805
+ refuse_source_param: "Refused: `source` in the query string is a request body by another route. Write the body as JSON.",
1806
+ refuse_search_template: "Refused: '{path}' is a search template, whose body cannot be inspected \u2014 it is a string rendered on the cluster, or stored there. Write the query out in full.",
1807
+ refuse_string_body: "Refused: a quoted string request body is not supported. Write the body as JSON.",
1808
+ refuse_audit_strict: "Refused: the audit entry for this request could not be written, and audit.strict is on. Fix the audit sink (disk space, directory permissions, a stale lockfile in .dbcli/audit) or turn audit.strict off to accept unrecorded requests.",
1809
+ blacklist_index: "index '{index}' is blacklist-protected",
1810
+ blacklist_unscoped: "'{path}' names no index, so it cannot be checked against the blacklist. Scope the request to an index, e.g. GET /<index>/_search.",
1811
+ blacklist_field: "field '{field}' is blacklist-protected and cannot be named in a request \u2014 sorting, aggregating or scripting on it would return its values.",
1812
+ banner_title: "Elasticsearch shell \u2014 Kibana Dev Tools syntax",
1813
+ banner_hint: 'Enter "<METHOD> /<path>" then an optional JSON body; submit with a blank line. Try: GET /_cat/indices',
1814
+ banner_keys: 'Ctrl+C cancels the current block; Ctrl+D or "exit" quits.',
1815
+ block_cancelled: "(block cancelled)",
1816
+ goodbye: "Goodbye"
1817
+ }
2171
1818
  };
2172
1819
  // resources/lang/zh-TW/shell.json
2173
1820
  var shell_default2 = {
@@ -2190,7 +1837,27 @@ var shell_default2 = {
2190
1837
  timing_display: "\u8017\u6642\uFF1A{ms}ms",
2191
1838
  rows_display: "{count} \u7B46\u8CC7\u6599",
2192
1839
  unknown_command: "\u672A\u77E5\u6307\u4EE4\uFF1A'{command}'\u3002\u8F38\u5165 '.help' \u67E5\u770B\u53EF\u7528\u6307\u4EE4\u3002",
2193
- subcommand_prefix_hint: "\u9019\u4E00\u884C\u88AB\u7576\u6210 SQL\uFF0C\u6B63\u5728\u7B49\u5206\u865F\u3002\u5982\u679C\u4F60\u8981\u7684\u662F dbcli \u5B50\u6307\u4EE4\uFF0C\u8ACB\u6539\u6253 {command}\u3002Ctrl-C \u53EF\u53D6\u6D88\u3002"
1840
+ subcommand_prefix_hint: "\u9019\u4E00\u884C\u88AB\u7576\u6210 SQL\uFF0C\u6B63\u5728\u7B49\u5206\u865F\u3002\u5982\u679C\u4F60\u8981\u7684\u662F dbcli \u5B50\u6307\u4EE4\uFF0C\u8ACB\u6539\u6253 {command}\u3002Ctrl-C \u53EF\u53D6\u6D88\u3002",
1841
+ es: {
1842
+ parse_empty: "\u7A7A\u7684\u8ACB\u6C42",
1843
+ parse_needs_method_path: '\u8ACB\u6C42\u9700\u8981\u4E00\u500B method \u8207\u4E00\u500B path\uFF0C\u4F8B\u5982 "GET /index/_search"',
1844
+ parse_needs_path: "\u8ACB\u6C42\u9700\u8981\u4E00\u500B path",
1845
+ refuse_dot_segment: "\u5DF2\u62D2\u7D55\uFF1A'{path}' \u89E3\u78BC\u5F8C\u542B\u6709 '..' \u5340\u6BB5\uFF0Cdbcli \u7121\u6CD5\u5224\u65B7 Elasticsearch \u6703\u628A\u5B83\u8DEF\u7531\u5230\u54EA\u500B\u8DEF\u5F91\u3002\u8ACB\u628A\u8DEF\u5F91\u5BEB\u6210\u4E0D\u542B '..' \u7684\u5F62\u5F0F\u3002",
1846
+ refuse_unparseable: "\u5DF2\u62D2\u7D55\uFF1A'{path}' \u4E0D\u662F\u4F3A\u670D\u5668\u6703\u6536\u5230\u7684\u90A3\u500B\u8ACB\u6C42\u3002\u5B83\u7121\u6CD5\u88AB\u89E3\u6790\u6210\u4E00\u500B\u8DEF\u5F91\u3002",
1847
+ refuse_not_canonical: "\u5DF2\u62D2\u7D55\uFF1A'{path}' \u4E0D\u662F\u4F3A\u670D\u5668\u6703\u6536\u5230\u7684\u90A3\u500B\u8ACB\u6C42\u3002\u8ACB\u5BEB\u6210 '{canonical}'\u3002",
1848
+ refuse_source_param: "\u5DF2\u62D2\u7D55\uFF1Aquery string \u88E1\u7684 `source` \u662F\u53E6\u4E00\u689D\u8DEF\u5F91\u4E0A\u7684 request body\u3002\u8ACB\u628A body \u5BEB\u6210 JSON\u3002",
1849
+ refuse_search_template: "\u5DF2\u62D2\u7D55\uFF1A'{path}' \u662F search template\uFF0C\u5B83\u7684 body \u7121\u6CD5\u6AA2\u67E5\u2014\u2014\u90A3\u662F\u4E00\u500B\u5728\u53E2\u96C6\u4E0A\u7B97\u51FA\u4F86\u7684\u5B57\u4E32\uFF0C\u6216\u8005\u6839\u672C\u5B58\u5728\u53E2\u96C6\u88E1\u3002\u8ACB\u628A\u67E5\u8A62\u5B8C\u6574\u5BEB\u51FA\u4F86\u3002",
1850
+ refuse_string_body: "\u5DF2\u62D2\u7D55\uFF1A\u4E0D\u652F\u63F4\u4EE5\u5F15\u865F\u5B57\u4E32\u5F62\u5F0F\u5BEB\u7684 request body\u3002\u8ACB\u628A body \u5BEB\u6210 JSON\u3002",
1851
+ refuse_audit_strict: "\u5DF2\u62D2\u7D55\uFF1A\u9019\u500B\u8ACB\u6C42\u7684\u7A3D\u6838\u7D00\u9304\u5BEB\u4E0D\u51FA\u53BB\uFF0C\u800C audit.strict \u662F\u958B\u7684\u3002\u8ACB\u4FEE\u597D\u7A3D\u6838\u8F38\u51FA\u7AEF\uFF08\u78C1\u789F\u7A7A\u9593\u3001\u76EE\u9304\u6B0A\u9650\u3001.dbcli/audit \u88E1\u6B98\u7559\u7684 lockfile\uFF09\uFF0C\u6216\u95DC\u6389 audit.strict \u4EE5\u63A5\u53D7\u6C92\u6709\u7D00\u9304\u7684\u8ACB\u6C42\u3002",
1852
+ blacklist_index: "\u7D22\u5F15 '{index}' \u53D7\u9ED1\u540D\u55AE\u4FDD\u8B77",
1853
+ blacklist_unscoped: "'{path}' \u6307\u4E0D\u51FA\u4EFB\u4F55\u7D22\u5F15\uFF0C\u56E0\u6B64\u7121\u6CD5\u5C0D\u7167\u9ED1\u540D\u55AE\u6AA2\u67E5\u3002\u8ACB\u628A\u8ACB\u6C42\u9650\u5B9A\u5230\u67D0\u500B\u7D22\u5F15\uFF0C\u4F8B\u5982 GET /<index>/_search\u3002",
1854
+ blacklist_field: "\u6B04\u4F4D '{field}' \u53D7\u9ED1\u540D\u55AE\u4FDD\u8B77\uFF0C\u4E0D\u80FD\u51FA\u73FE\u5728\u8ACB\u6C42\u88E1\u2014\u2014\u5C0D\u5B83\u6392\u5E8F\u3001\u805A\u5408\u6216\u8DD1 script \u90FD\u6703\u628A\u5B83\u7684\u503C\u53D6\u56DE\u4F86\u3002",
1855
+ banner_title: "Elasticsearch shell \u2014\u2014 Kibana Dev Tools \u8A9E\u6CD5",
1856
+ banner_hint: '\u8F38\u5165 "<METHOD> /<path>"\uFF0C\u63A5\u8457\u5BEB\u53EF\u9078\u7684 JSON body\uFF1B\u7A7A\u4E00\u884C\u9001\u51FA\u3002\u53EF\u4EE5\u5148\u8A66\uFF1AGET /_cat/indices',
1857
+ banner_keys: 'Ctrl+C \u53D6\u6D88\u76EE\u524D\u9019\u500B block\uFF1BCtrl+D \u6216 "exit" \u96E2\u958B\u3002',
1858
+ block_cancelled: "\uFF08block \u5DF2\u53D6\u6D88\uFF09",
1859
+ goodbye: "\u518D\u898B"
1860
+ }
2194
1861
  };
2195
1862
  // resources/lang/en/ceremony.json
2196
1863
  var ceremony_default = {
@@ -2344,2585 +2011,686 @@ class MessageLoader {
2344
2011
  }
2345
2012
  return key;
2346
2013
  }
2014
+ setLanguage(lang) {
2015
+ this.currentLang = lang;
2016
+ this.messages = {};
2017
+ this.loadMessages();
2018
+ }
2347
2019
  interpolate(key, vars) {
2348
- let message = this.t(key);
2349
- for (const [varName, value] of Object.entries(vars)) {
2350
- const escapedVarName = varName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2351
- const regex = new RegExp(`{${escapedVarName}}`, "g");
2352
- message = message.replace(regex, String(value));
2353
- }
2354
- return message;
2020
+ return this.t(key).replace(/\{([^{}]+)\}/g, (match, name) => Object.prototype.hasOwnProperty.call(vars, name) ? String(vars[name]) : match);
2355
2021
  }
2356
2022
  }
2357
2023
  var messageLoader = MessageLoader.getInstance();
2358
2024
  var t = (key) => messageLoader.t(key);
2359
2025
  var t_vars = (key, vars) => messageLoader.interpolate(key, vars);
2360
2026
 
2361
- // src/utils/colors.ts
2362
- var import_picocolors = __toESM(require_picocolors(), 1);
2363
- var colors = {
2364
- success: (text) => import_picocolors.default.green(text),
2365
- error: (text) => import_picocolors.default.red(text),
2366
- warn: (text) => import_picocolors.default.yellow(text),
2367
- info: (text) => import_picocolors.default.blue(text),
2368
- dim: (text) => import_picocolors.default.dim(text),
2369
- bold: (text) => import_picocolors.default.bold(text),
2370
- keyword: (text) => import_picocolors.default.blue(import_picocolors.default.bold(text))
2371
- };
2372
-
2373
- // src/utils/db-version-check.ts
2374
- var MIN_SUPPORTED_VERSIONS = {
2375
- postgresql: "12.0",
2376
- mysql: "8.0",
2377
- mariadb: "10.5",
2378
- elasticsearch: "7.10"
2379
- };
2380
- function parseVersionSegments(version) {
2381
- const match = version.match(/^(\d+(?:\.\d+)*)/);
2382
- if (!match)
2383
- return [];
2384
- return (match[1] ?? "").split(".").map(Number);
2385
- }
2386
- function isMariaDBVersion(versionString) {
2387
- return /mariadb/i.test(versionString);
2388
- }
2389
- function extractMariaDBVersion(versionString) {
2390
- const match = versionString.match(/(\d+\.\d+\.\d+)-MariaDB/i);
2391
- if (match)
2392
- return match[1] ?? versionString;
2393
- const prefixMatch = versionString.match(/^5\.5\.5-(\d+\.\d+\.\d+)/);
2394
- if (prefixMatch)
2395
- return prefixMatch[1] ?? versionString;
2396
- return versionString;
2397
- }
2398
- function compareVersions(a, b) {
2399
- const segA = parseVersionSegments(a);
2400
- const segB = parseVersionSegments(b);
2401
- const len = Math.max(segA.length, segB.length);
2402
- for (let i = 0;i < len; i++) {
2403
- const diff = (segA[i] ?? 0) - (segB[i] ?? 0);
2404
- if (diff !== 0)
2405
- return diff;
2406
- }
2407
- return 0;
2408
- }
2409
- function checkDbVersion(rawVersion, declaredSystem) {
2410
- const isMariaDB = isMariaDBVersion(rawVersion);
2411
- const system = isMariaDB ? "mariadb" : declaredSystem;
2412
- const serverVersion = isMariaDB ? extractMariaDBVersion(rawVersion) : rawVersion;
2413
- const minVersion = MIN_SUPPORTED_VERSIONS[system];
2414
- const supported = compareVersions(serverVersion, minVersion ?? "0") >= 0;
2415
- return {
2416
- serverVersion,
2417
- system,
2418
- supported,
2419
- minVersion: minVersion ?? "0"
2420
- };
2421
- }
2422
- function warnIfUnsupported(result) {
2423
- if (result.supported)
2424
- return;
2425
- const message = t_vars("version.unsupported_warning", {
2426
- system: result.system,
2427
- version: result.serverVersion,
2428
- minVersion: result.minVersion
2429
- });
2430
- process.stderr.write(colors.warn(`\u26A0 ${message}`) + `
2431
- `);
2432
- }
2433
-
2434
- // src/utils/encoding.ts
2435
- var CP1252_REVERSE = {
2436
- 8364: 128,
2437
- 8218: 130,
2438
- 402: 131,
2439
- 8222: 132,
2440
- 8230: 133,
2441
- 8224: 134,
2442
- 8225: 135,
2443
- 710: 136,
2444
- 8240: 137,
2445
- 352: 138,
2446
- 8249: 139,
2447
- 338: 140,
2448
- 381: 142,
2449
- 8216: 145,
2450
- 8217: 146,
2451
- 8220: 147,
2452
- 8221: 148,
2453
- 8226: 149,
2454
- 8211: 150,
2455
- 8212: 151,
2456
- 732: 152,
2457
- 8482: 153,
2458
- 353: 154,
2459
- 8250: 155,
2460
- 339: 156,
2461
- 382: 158,
2462
- 376: 159
2463
- };
2464
- function fixDoubleEncodedUtf8(str) {
2465
- if (!str)
2466
- return str;
2467
- const bytes = [];
2468
- for (const char of str) {
2469
- const code = char.codePointAt(0);
2470
- if (code < 128) {
2471
- bytes.push(code);
2472
- } else if (code <= 255) {
2473
- bytes.push(code);
2474
- } else {
2475
- const cp1252Byte = CP1252_REVERSE[code];
2476
- if (cp1252Byte !== undefined) {
2477
- bytes.push(cp1252Byte);
2478
- } else {
2479
- return str;
2480
- }
2481
- }
2482
- }
2483
- try {
2484
- const decoded = new TextDecoder("utf-8", { fatal: true }).decode(new Uint8Array(bytes));
2485
- if (decoded !== str) {
2486
- return decoded;
2027
+ // src/utils/glob.ts
2028
+ function findClassEnd(glob, open) {
2029
+ for (let i = open + 1;i < glob.length; i++) {
2030
+ if (glob[i] === "\\") {
2031
+ i++;
2032
+ continue;
2487
2033
  }
2488
- } catch {}
2489
- return str;
2490
- }
2491
-
2492
- // src/adapters/identifier-quote.ts
2493
- function quoteCharFor(dialect) {
2494
- return dialect === "postgresql" ? '"' : "`";
2495
- }
2496
- function assertQuotable(name, what) {
2497
- if (name.length === 0) {
2498
- throw new Error(`${what} must not be empty`);
2499
- }
2500
- if (name.includes(String.fromCharCode(0))) {
2501
- throw new Error(`${what} must not contain a NUL character`);
2502
- }
2503
- }
2504
- function quoteIdentifier(name, dialect) {
2505
- assertQuotable(name, "identifier");
2506
- const q = quoteCharFor(dialect);
2507
- return `${q}${name.split(q).join(q + q)}${q}`;
2508
- }
2509
- function encodeEsPathSegment(value) {
2510
- if (value.length === 0) {
2511
- throw new Error("Elasticsearch path segment must not be empty");
2512
- }
2513
- if (value === "." || value === "..") {
2514
- return value.replaceAll(".", "%2E");
2034
+ if (glob[i] === "]")
2035
+ return i;
2515
2036
  }
2516
- return encodeURIComponent(value);
2037
+ return -1;
2517
2038
  }
2518
- function encodeEsIndexExpression(expression) {
2519
- if (expression.length === 0) {
2520
- throw new Error("Elasticsearch index expression must not be empty");
2039
+ function isValidCharacterClass(body) {
2040
+ try {
2041
+ new RegExp(body);
2042
+ return true;
2043
+ } catch {
2044
+ return false;
2521
2045
  }
2522
- return expression.split(",").map((part) => encodeEsPathSegment(part)).join(",");
2523
2046
  }
2524
-
2525
- // src/adapters/timeout-policy.ts
2526
- var DEFAULT_CONNECT_TIMEOUT_MS = 5000;
2527
- function resolveTimeoutPolicy(options) {
2528
- return {
2529
- connectMs: options.timeout ?? DEFAULT_CONNECT_TIMEOUT_MS,
2530
- statementMs: options.statementTimeout ?? options.timeout
2047
+ var parsedGlobs = new Map;
2048
+ function parseGlob(glob) {
2049
+ const memo = parsedGlobs.get(glob);
2050
+ if (memo !== undefined)
2051
+ return memo;
2052
+ const parsed = parseGlobUncached(glob);
2053
+ parsedGlobs.set(glob, parsed);
2054
+ return parsed;
2055
+ }
2056
+ function parseGlobUncached(glob) {
2057
+ const runs = [];
2058
+ let current = [];
2059
+ let leadingStar = false;
2060
+ let trailingStar = false;
2061
+ let sawStar = false;
2062
+ const endRun = () => {
2063
+ if (current.length > 0)
2064
+ runs.push(current);
2065
+ current = [];
2531
2066
  };
2532
- }
2533
- function statementTimeoutSql(system, ms) {
2534
- if (system === "mariadb") {
2535
- return `SET SESSION max_statement_time = ${ms / 1000}`;
2536
- }
2537
- return `SET SESSION max_execution_time = ${Math.round(ms)}`;
2538
- }
2539
-
2540
- // src/adapters/postgresql-adapter.ts
2541
- class PostgreSQLAdapter {
2542
- pool = null;
2543
- client = null;
2544
- options;
2545
- constructor(options) {
2546
- this.options = options;
2547
- if (options.port < 1 || options.port > 65535) {
2548
- throw new Error(`Invalid port number: ${options.port}`);
2549
- }
2550
- }
2551
- async connect() {
2552
- await withMappedConnectionError("postgresql", this.options, async () => {
2553
- const timeouts = resolveTimeoutPolicy(this.options);
2554
- const { Pool } = await import("pg");
2555
- this.pool = new Pool({
2556
- host: this.options.host,
2557
- port: this.options.port,
2558
- user: this.options.user,
2559
- password: this.options.password,
2560
- database: this.options.database,
2561
- connectionTimeoutMillis: timeouts.connectMs,
2562
- ...timeouts.statementMs !== undefined && { statement_timeout: timeouts.statementMs }
2563
- });
2564
- await this.testConnection();
2565
- try {
2566
- const rawVersion = await this.getServerVersion();
2567
- const result = checkDbVersion(rawVersion, "postgresql");
2568
- warnIfUnsupported(result);
2569
- } catch {}
2570
- });
2571
- }
2572
- async disconnect() {
2573
- try {
2574
- if (this.client) {
2575
- await this.client.release();
2576
- this.client = null;
2577
- }
2578
- if (this.pool) {
2579
- await this.pool.end();
2580
- this.pool = null;
2581
- }
2582
- } catch {}
2583
- }
2584
- async testConnection() {
2585
- requireConnected(this.pool);
2586
- return withMappedConnectionError("postgresql", this.options, async () => {
2587
- const result = await this.execute("SELECT 1 as count");
2588
- return result.rows.length > 0;
2589
- });
2590
- }
2591
- async execute(sql, params) {
2592
- const pool = requireConnected(this.pool);
2593
- return withMappedConnectionError("postgresql", this.options, async () => {
2594
- const result = params ? await pool.query(sql, params) : await pool.query(sql);
2595
- return {
2596
- rows: result.rows,
2597
- affectedRows: result.rowCount ?? 0
2598
- };
2599
- });
2600
- }
2601
- async getServerVersion() {
2602
- const result = await this.execute("SHOW server_version");
2603
- return result.rows[0]?.server_version ?? "unknown";
2604
- }
2605
- async listTables() {
2606
- requireConnected(this.pool);
2607
- return withMappedConnectionError("postgresql", this.options, async () => {
2608
- const query = `
2609
- SELECT
2610
- n.nspname as schema_name,
2611
- c.relname as table_name,
2612
- c.reltuples::bigint as estimated_rows,
2613
- CASE c.relkind
2614
- WHEN 'r' THEN 'table'
2615
- WHEN 'v' THEN 'view'
2616
- WHEN 'm' THEN 'view'
2617
- ELSE 'table'
2618
- END as table_type,
2619
- (SELECT COUNT(*) FROM information_schema.columns ic
2620
- WHERE ic.table_schema = 'public' AND ic.table_name = c.relname)::int as column_count
2621
- FROM pg_class c
2622
- JOIN pg_namespace n ON n.oid = c.relnamespace
2623
- WHERE n.nspname = 'public'
2624
- AND c.relkind IN ('r', 'v', 'm')
2625
- ORDER BY c.relname
2626
- `;
2627
- const result = await this.execute(query);
2628
- return result.rows.map((row) => ({
2629
- name: row.table_name,
2630
- schema: row.schema_name,
2631
- columns: [],
2632
- columnCount: row.column_count,
2633
- rowCount: Math.max(0, row.estimated_rows || 0),
2634
- engine: "PostgreSQL",
2635
- estimatedRowCount: Math.max(0, row.estimated_rows || 0),
2636
- tableType: row.table_type
2637
- }));
2638
- });
2639
- }
2640
- async getTableSchema(tableName, options) {
2641
- requireConnected(this.pool);
2642
- return withMappedConnectionError("postgresql", this.options, async () => {
2643
- const columnQuery = `
2644
- SELECT
2645
- c.column_name as name,
2646
- c.data_type as type,
2647
- c.is_nullable = 'YES' as nullable,
2648
- c.column_default as default_value,
2649
- (
2650
- SELECT EXISTS(
2651
- SELECT 1 FROM information_schema.table_constraints tc
2652
- JOIN information_schema.key_column_usage kcu
2653
- ON tc.constraint_catalog = kcu.constraint_catalog
2654
- AND tc.constraint_schema = kcu.constraint_schema
2655
- AND tc.constraint_name = kcu.constraint_name
2656
- WHERE tc.table_catalog = c.table_catalog
2657
- AND tc.table_schema = c.table_schema
2658
- AND tc.table_name = c.table_name
2659
- AND kcu.table_catalog = c.table_catalog
2660
- AND kcu.table_schema = c.table_schema
2661
- AND kcu.table_name = c.table_name
2662
- AND tc.constraint_type = 'PRIMARY KEY'
2663
- AND kcu.column_name = c.column_name
2664
- )
2665
- ) as is_primary_key,
2666
- (c.column_default LIKE 'nextval%') as auto_increment,
2667
- pgd.description as comment
2668
- FROM information_schema.columns c
2669
- LEFT JOIN pg_catalog.pg_statio_all_tables st
2670
- ON st.schemaname = c.table_schema AND st.relname = c.table_name
2671
- LEFT JOIN pg_catalog.pg_description pgd
2672
- ON pgd.objoid = st.relid AND pgd.objsubid = c.ordinal_position
2673
- WHERE c.table_name = $1
2674
- AND c.table_schema = 'public'
2675
- ORDER BY c.ordinal_position
2676
- `;
2677
- const columnResult = await this.execute(columnQuery, [tableName]);
2678
- const columns = columnResult.rows;
2679
- const enumQuery = `
2680
- SELECT
2681
- c.column_name as name,
2682
- array_agg(e.enumlabel ORDER BY e.enumsortorder) as enum_values
2683
- FROM information_schema.columns c
2684
- JOIN pg_catalog.pg_type AS enum_type
2685
- ON enum_type.typname = c.udt_name
2686
- JOIN pg_catalog.pg_namespace AS enum_schema
2687
- ON enum_schema.oid = enum_type.typnamespace
2688
- AND enum_schema.nspname = c.udt_schema
2689
- JOIN pg_catalog.pg_enum AS e ON e.enumtypid = enum_type.oid
2690
- WHERE c.table_name = $1
2691
- AND c.table_schema = 'public'
2692
- GROUP BY c.column_name
2693
- `;
2694
- const enumResult = await this.execute(enumQuery, [tableName]);
2695
- const enumResults = enumResult.rows;
2696
- const enumMap = new Map;
2697
- for (const row of enumResults) {
2698
- enumMap.set(row.name, Array.isArray(row.enum_values) ? row.enum_values : []);
2699
- }
2700
- const fkQuery = `
2701
- SELECT
2702
- constraint_info.conname as name,
2703
- array_agg(source_column.attname ORDER BY source_key.ordinality) as columns,
2704
- referenced_schema.nspname as ref_schema,
2705
- referenced_table.relname as ref_table,
2706
- array_agg(referenced_column.attname ORDER BY source_key.ordinality) as ref_columns
2707
- FROM pg_catalog.pg_constraint AS constraint_info
2708
- JOIN pg_catalog.pg_class AS source_table
2709
- ON source_table.oid = constraint_info.conrelid
2710
- JOIN pg_catalog.pg_namespace AS source_schema
2711
- ON source_schema.oid = source_table.relnamespace
2712
- JOIN pg_catalog.pg_class AS referenced_table
2713
- ON referenced_table.oid = constraint_info.confrelid
2714
- JOIN pg_catalog.pg_namespace AS referenced_schema
2715
- ON referenced_schema.oid = referenced_table.relnamespace
2716
- JOIN LATERAL unnest(constraint_info.conkey) WITH ORDINALITY
2717
- AS source_key(attnum, ordinality) ON TRUE
2718
- JOIN LATERAL unnest(constraint_info.confkey) WITH ORDINALITY
2719
- AS referenced_key(attnum, ordinality)
2720
- ON referenced_key.ordinality = source_key.ordinality
2721
- JOIN pg_catalog.pg_attribute AS source_column
2722
- ON source_column.attrelid = constraint_info.conrelid
2723
- AND source_column.attnum = source_key.attnum
2724
- JOIN pg_catalog.pg_attribute AS referenced_column
2725
- ON referenced_column.attrelid = constraint_info.confrelid
2726
- AND referenced_column.attnum = referenced_key.attnum
2727
- WHERE constraint_info.contype = 'f'
2728
- AND source_table.relname = $1
2729
- AND source_schema.nspname = 'public'
2730
- GROUP BY
2731
- constraint_info.oid,
2732
- constraint_info.conname,
2733
- referenced_schema.nspname,
2734
- referenced_table.relname
2735
- ORDER BY constraint_info.oid
2736
- `;
2737
- const fkResult = await this.execute(fkQuery, [tableName]);
2738
- const fkResults = fkResult.rows;
2739
- const fkMap = new Map;
2740
- for (const fk of fkResults) {
2741
- if (fk.columns.length === 1 && fk.ref_columns.length === 1) {
2742
- fkMap.set(fk.columns[0], { table: fk.ref_table, column: fk.ref_columns[0] });
2743
- }
2067
+ for (let i = 0;i < glob.length; i++) {
2068
+ const c = glob[i];
2069
+ if (c === "\\" && i + 1 < glob.length) {
2070
+ trailingStar = false;
2071
+ current.push({ kind: "literal", char: glob[++i] });
2072
+ continue;
2073
+ }
2074
+ if (c === "*") {
2075
+ if (!sawStar && current.length === 0)
2076
+ leadingStar = true;
2077
+ sawStar = true;
2078
+ trailingStar = true;
2079
+ endRun();
2080
+ continue;
2081
+ }
2082
+ trailingStar = false;
2083
+ if (c === "?") {
2084
+ current.push({ kind: "any" });
2085
+ continue;
2086
+ }
2087
+ if (c === "[") {
2088
+ const end = findClassEnd(glob, i);
2089
+ const body = end === -1 ? "" : glob.slice(i, end + 1);
2090
+ if (body !== "" && isValidCharacterClass(body)) {
2091
+ current.push({ kind: "class", test: new RegExp(`^${body}$`, "s") });
2092
+ i = end;
2093
+ continue;
2744
2094
  }
2745
- const indexQuery = `
2746
- SELECT
2747
- i.relname as name,
2748
- array_agg(a.attname ORDER BY array_position(ix.indkey, a.attnum)) as columns,
2749
- ix.indisunique as is_unique
2750
- FROM pg_index ix
2751
- JOIN pg_class t ON t.oid = ix.indrelid
2752
- JOIN pg_class i ON i.oid = ix.indexrelid
2753
- JOIN pg_namespace n ON n.oid = t.relnamespace
2754
- JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(ix.indkey)
2755
- WHERE t.relname = $1
2756
- AND n.nspname = 'public'
2757
- AND NOT ix.indisprimary
2758
- GROUP BY i.relname, ix.indisunique
2759
- `;
2760
- const indexResult = await this.execute(indexQuery, [tableName]);
2761
- const indexResults = indexResult.rows;
2762
- const estimateQuery = `
2763
- SELECT source_table.reltuples::bigint as estimated_rows
2764
- FROM pg_catalog.pg_class AS source_table
2765
- JOIN pg_catalog.pg_namespace AS source_schema
2766
- ON source_schema.oid = source_table.relnamespace
2767
- WHERE source_table.relname = $1
2768
- AND source_schema.nspname = 'public'
2769
- `;
2770
- const estimateResult = await this.execute(estimateQuery, [
2771
- tableName
2772
- ]);
2773
- const estimateResults = estimateResult.rows;
2774
- const pkQuery = `
2775
- SELECT
2776
- array_agg(primary_key_column.attname ORDER BY primary_key.ordinality) as columns
2777
- FROM pg_catalog.pg_index AS index_info
2778
- JOIN pg_catalog.pg_class AS source_table
2779
- ON source_table.oid = index_info.indrelid
2780
- JOIN pg_catalog.pg_namespace AS source_schema
2781
- ON source_schema.oid = source_table.relnamespace
2782
- JOIN LATERAL unnest(index_info.indkey) WITH ORDINALITY
2783
- AS primary_key(attnum, ordinality) ON TRUE
2784
- JOIN pg_catalog.pg_attribute AS primary_key_column
2785
- ON primary_key_column.attrelid = source_table.oid
2786
- AND primary_key_column.attnum = primary_key.attnum
2787
- WHERE index_info.indisprimary
2788
- AND source_table.relname = $1
2789
- AND source_schema.nspname = 'public'
2790
- `;
2791
- const pkResult = await this.execute(pkQuery, [tableName]);
2792
- const pkResults = pkResult.rows;
2793
- const qualifiedTableName = `${quoteIdentifier("public", "postgresql")}.${quoteIdentifier(tableName, "postgresql")}`;
2794
- const exactRowCount = options?.exactRowCount === false ? undefined : (await this.execute(`SELECT COUNT(*) as count FROM ${qualifiedTableName}`)).rows[0]?.count;
2795
- const primaryKeyArray = Array.isArray(pkResults[0]?.columns) ? pkResults[0].columns : [];
2796
- const safeForeignKeys = fkResults.map((fk) => ({
2797
- name: fk.name,
2798
- columns: Array.isArray(fk.columns) ? fk.columns : [],
2799
- refSchema: fk.ref_schema,
2800
- refTable: fk.ref_table,
2801
- refColumns: Array.isArray(fk.ref_columns) ? fk.ref_columns : []
2802
- }));
2803
- const schema = {
2804
- name: tableName,
2805
- schema: "public",
2806
- columns: columns.map((col) => ({
2807
- name: col.name,
2808
- type: col.type,
2809
- nullable: col.nullable,
2810
- default: col.default_value || undefined,
2811
- primaryKey: col.is_primary_key,
2812
- foreignKey: fkMap.get(col.name),
2813
- autoIncrement: col.auto_increment,
2814
- comment: col.comment ? fixDoubleEncodedUtf8(col.comment) : null,
2815
- enumValues: enumMap.get(col.name)
2816
- })),
2817
- rowCount: exactRowCount ?? Math.max(0, estimateResults[0]?.estimated_rows || 0),
2818
- engine: "PostgreSQL",
2819
- primaryKey: primaryKeyArray,
2820
- foreignKeys: safeForeignKeys,
2821
- indexes: indexResults.map((idx) => ({
2822
- name: idx.name,
2823
- columns: Array.isArray(idx.columns) ? idx.columns : [],
2824
- unique: idx.is_unique
2825
- })),
2826
- estimatedRowCount: Math.max(0, estimateResults[0]?.estimated_rows || 0)
2827
- };
2828
- return schema;
2829
- });
2095
+ current.push({ kind: "literal", char: "[" });
2096
+ continue;
2097
+ }
2098
+ current.push({ kind: "literal", char: c });
2830
2099
  }
2100
+ endRun();
2101
+ return { runs, leadingStar, trailingStar, anchored: !sawStar };
2831
2102
  }
2832
-
2833
- // src/adapters/mysql-adapter.ts
2834
- function parseEnumValues(columnType) {
2835
- const match = columnType.match(/^enum\((.+)\)$/i);
2836
- if (!match)
2837
- return;
2838
- return (match[1] ?? "").split(",").map((v) => v.trim().replace(/^'|'$/g, ""));
2839
- }
2840
-
2841
- class MySQLAdapter {
2842
- db = null;
2843
- options;
2844
- system;
2845
- constructor(options) {
2846
- this.options = options;
2847
- this.system = options.system === "mariadb" ? "mariadb" : "mysql";
2848
- if (options.port < 1 || options.port > 65535) {
2849
- throw new Error(`Invalid port number: ${options.port}`);
2850
- }
2851
- }
2852
- async connect() {
2853
- await withMappedConnectionError(this.system, this.options, async () => {
2854
- const timeouts = resolveTimeoutPolicy(this.options);
2855
- const { default: driver } = await import("mysql2/promise");
2856
- this.db = await driver.createConnection({
2857
- host: this.options.host,
2858
- port: this.options.port,
2859
- user: this.options.user,
2860
- password: this.options.password || undefined,
2861
- database: this.options.database,
2862
- charset: "utf8mb4",
2863
- connectTimeout: timeouts.connectMs
2864
- });
2865
- await this.db.execute("SET NAMES utf8mb4");
2866
- if (timeouts.statementMs !== undefined) {
2867
- try {
2868
- await this.db.query(statementTimeoutSql(this.system, timeouts.statementMs));
2869
- } catch {}
2870
- }
2871
- await this.testConnection();
2872
- try {
2873
- const rawVersion = await this.getServerVersion();
2874
- const result = checkDbVersion(rawVersion, this.system);
2875
- warnIfUnsupported(result);
2876
- } catch {}
2877
- });
2878
- }
2879
- async disconnect() {
2880
- try {
2881
- if (this.db) {
2882
- await this.db.end();
2883
- this.db = null;
2884
- }
2885
- } catch {}
2886
- }
2887
- async testConnection() {
2888
- requireConnected(this.db);
2889
- return withMappedConnectionError(this.system, this.options, async () => {
2890
- const result = await this.execute("SELECT 1 as count");
2891
- return result.rows.length > 0;
2892
- });
2103
+ function runMatchesAt(run, text, at) {
2104
+ if (at + run.length > text.length)
2105
+ return false;
2106
+ for (let i = 0;i < run.length; i++) {
2107
+ const token = run[i];
2108
+ const ch = text[at + i];
2109
+ if (token.kind === "literal") {
2110
+ if (token.char !== ch)
2111
+ return false;
2112
+ } else if (token.kind === "class") {
2113
+ if (!token.test.test(ch))
2114
+ return false;
2115
+ }
2893
2116
  }
2894
- async execute(sql, params) {
2895
- const db = requireConnected(this.db);
2896
- return withMappedConnectionError(this.system, this.options, async () => {
2897
- const [result] = params ? await db.execute(sql, params) : await db.execute(sql);
2898
- if (Array.isArray(result)) {
2899
- return {
2900
- rows: result,
2901
- affectedRows: result.length
2902
- };
2903
- }
2904
- const header = result;
2905
- return {
2906
- rows: [],
2907
- affectedRows: header.affectedRows || 0,
2908
- lastInsertId: header.insertId
2909
- };
2910
- });
2117
+ return true;
2118
+ }
2119
+ function globMatches(glob, text) {
2120
+ const { runs, leadingStar, trailingStar, anchored } = parseGlob(glob);
2121
+ if (anchored) {
2122
+ const run = runs[0] ?? [];
2123
+ return text.length === run.length && runMatchesAt(run, text, 0);
2911
2124
  }
2912
- async getServerVersion() {
2913
- const result = await this.execute("SELECT VERSION() as version");
2914
- return result.rows[0]?.version ?? "unknown";
2915
- }
2916
- async listTables() {
2917
- requireConnected(this.db);
2918
- return withMappedConnectionError(this.system, this.options, async () => {
2919
- const query = `
2920
- SELECT
2921
- t.TABLE_NAME as table_name,
2922
- t.TABLE_ROWS as row_count,
2923
- t.ENGINE as engine,
2924
- t.TABLE_TYPE as table_type,
2925
- COUNT(c.COLUMN_NAME) as column_count
2926
- FROM information_schema.TABLES t
2927
- LEFT JOIN information_schema.COLUMNS c
2928
- ON t.TABLE_SCHEMA = c.TABLE_SCHEMA AND t.TABLE_NAME = c.TABLE_NAME
2929
- WHERE t.TABLE_SCHEMA = DATABASE()
2930
- GROUP BY t.TABLE_NAME, t.TABLE_ROWS, t.ENGINE, t.TABLE_TYPE
2931
- ORDER BY t.TABLE_NAME
2932
- `;
2933
- const result = await this.execute(query);
2934
- return result.rows.map((row) => ({
2935
- name: row.table_name,
2936
- columns: [],
2937
- columnCount: row.column_count,
2938
- rowCount: row.row_count || 0,
2939
- engine: row.engine,
2940
- estimatedRowCount: row.row_count || 0,
2941
- tableType: row.table_type === "VIEW" ? "view" : "table"
2942
- }));
2943
- });
2125
+ if (runs.length === 0)
2126
+ return true;
2127
+ let cursor = 0;
2128
+ let first = 0;
2129
+ let last = runs.length;
2130
+ if (!leadingStar) {
2131
+ const head = runs[0];
2132
+ if (!runMatchesAt(head, text, 0))
2133
+ return false;
2134
+ cursor = head.length;
2135
+ first = 1;
2944
2136
  }
2945
- async getTableSchema(tableName, options) {
2946
- requireConnected(this.db);
2947
- return withMappedConnectionError(this.system, this.options, async () => {
2948
- const columnQuery = `
2949
- SELECT
2950
- COLUMN_NAME as name,
2951
- COLUMN_TYPE as type,
2952
- IS_NULLABLE = 'YES' as nullable,
2953
- COLUMN_DEFAULT as default_value,
2954
- COLUMN_KEY = 'PRI' as is_primary_key,
2955
- EXTRA LIKE '%auto_increment%' as auto_increment,
2956
- COLUMN_COMMENT as comment
2957
- FROM information_schema.COLUMNS
2958
- WHERE TABLE_SCHEMA = DATABASE()
2959
- AND TABLE_NAME = ?
2960
- ORDER BY ORDINAL_POSITION
2961
- `;
2962
- const columnResult = await this.execute(columnQuery, [tableName]);
2963
- const columns = columnResult.rows;
2964
- const fkQuery = `
2965
- SELECT
2966
- rc.CONSTRAINT_NAME as name,
2967
- GROUP_CONCAT(kcu.COLUMN_NAME) as columns,
2968
- rc.REFERENCED_TABLE_NAME as ref_table,
2969
- GROUP_CONCAT(kcu.REFERENCED_COLUMN_NAME) as ref_columns
2970
- FROM information_schema.REFERENTIAL_CONSTRAINTS rc
2971
- JOIN information_schema.KEY_COLUMN_USAGE kcu
2972
- ON rc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME AND rc.TABLE_NAME = kcu.TABLE_NAME
2973
- WHERE kcu.TABLE_NAME = ? AND rc.CONSTRAINT_SCHEMA = DATABASE()
2974
- GROUP BY rc.CONSTRAINT_NAME, rc.REFERENCED_TABLE_NAME
2975
- `;
2976
- const fkResult = await this.execute(fkQuery, [tableName]);
2977
- const fkResults = fkResult.rows;
2978
- const fkMap = new Map;
2979
- for (const fk of fkResults) {
2980
- const cols = fk.columns.split(",").map((c) => c.trim());
2981
- const refCols = fk.ref_columns.split(",").map((c) => c.trim());
2982
- if (cols.length === 1 && refCols.length === 1) {
2983
- fkMap.set(cols[0], { table: fk.ref_table, column: refCols[0] });
2984
- }
2137
+ if (!trailingStar) {
2138
+ const tail = runs[last - 1];
2139
+ const at = text.length - tail.length;
2140
+ if (at < cursor || !runMatchesAt(tail, text, at))
2141
+ return false;
2142
+ last -= 1;
2143
+ }
2144
+ const limit = trailingStar ? text.length : text.length - runs[runs.length - 1].length;
2145
+ for (let r = first;r < last; r++) {
2146
+ const run = runs[r];
2147
+ let found = -1;
2148
+ for (let at = cursor;at + run.length <= limit; at++) {
2149
+ if (runMatchesAt(run, text, at)) {
2150
+ found = at;
2151
+ break;
2985
2152
  }
2986
- const primaryKeyColumns = columns.filter((col) => col.is_primary_key).map((col) => col.name);
2987
- const indexQuery = `
2988
- SELECT
2989
- INDEX_NAME as name,
2990
- GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) as columns,
2991
- NOT NON_UNIQUE as is_unique
2992
- FROM information_schema.STATISTICS
2993
- WHERE TABLE_SCHEMA = DATABASE()
2994
- AND TABLE_NAME = ?
2995
- AND INDEX_NAME != 'PRIMARY'
2996
- GROUP BY INDEX_NAME, NON_UNIQUE
2997
- `;
2998
- const indexResult = await this.execute(indexQuery, [tableName]);
2999
- const indexResults = indexResult.rows;
3000
- const tableMetaQuery = `
3001
- SELECT ENGINE as engine, TABLE_ROWS as estimated_rows
3002
- FROM information_schema.TABLES
3003
- WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?
3004
- `;
3005
- const tableMetaResult = await this.execute(tableMetaQuery, [tableName]);
3006
- const tableMeta = tableMetaResult.rows[0];
3007
- const estimatedRowCount = Math.max(0, tableMeta?.estimated_rows || 0);
3008
- const exactRowCount = options?.exactRowCount === false ? undefined : (await this.execute(`SELECT COUNT(*) as count FROM ${quoteIdentifier(tableName, this.system)}`)).rows[0]?.count;
3009
- const schema = {
3010
- name: tableName,
3011
- columns: columns.map((col) => ({
3012
- name: col.name,
3013
- type: col.type,
3014
- nullable: col.nullable,
3015
- default: col.default_value || undefined,
3016
- primaryKey: col.is_primary_key,
3017
- foreignKey: fkMap.get(col.name),
3018
- autoIncrement: col.auto_increment,
3019
- comment: col.comment ? fixDoubleEncodedUtf8(col.comment) : null,
3020
- enumValues: parseEnumValues(col.type)
3021
- })),
3022
- rowCount: exactRowCount ?? estimatedRowCount,
3023
- engine: tableMeta?.engine || "MySQL",
3024
- primaryKey: primaryKeyColumns,
3025
- foreignKeys: fkResults.map((fk) => ({
3026
- name: fk.name,
3027
- columns: fk.columns.split(",").map((c) => c.trim()),
3028
- refTable: fk.ref_table,
3029
- refColumns: fk.ref_columns.split(",").map((c) => c.trim())
3030
- })),
3031
- indexes: indexResults.map((idx) => ({
3032
- name: idx.name,
3033
- columns: idx.columns.split(",").map((c) => c.trim()),
3034
- unique: idx.is_unique
3035
- })),
3036
- estimatedRowCount
3037
- };
3038
- return schema;
3039
- });
3040
- }
3041
- }
3042
-
3043
- // src/adapters/mongodb-adapter.ts
3044
- import { resolveSrv, resolveTxt } from "dns/promises";
3045
-
3046
- // src/adapters/server-side-script.ts
3047
- class ServerSideScriptRejection extends Error {
3048
- operator;
3049
- constructor(message, operator) {
3050
- super(message);
3051
- this.operator = operator;
3052
- this.name = "ServerSideScriptRejection";
3053
- Object.setPrototypeOf(this, ServerSideScriptRejection.prototype);
3054
- }
3055
- }
3056
- var MONGO_SCRIPT_OPERATORS = ["$where", "$function", "$accumulator"];
3057
- var ES_SCRIPT_KEYS = ["script", "script_fields"];
3058
- function findKey(value, keys) {
3059
- if (value === null || typeof value !== "object")
3060
- return;
3061
- if (Array.isArray(value)) {
3062
- for (const item of value) {
3063
- const found = findKey(item, keys);
3064
- if (found)
3065
- return found;
3066
2153
  }
3067
- return;
3068
- }
3069
- const record = value;
3070
- for (const key of keys) {
3071
- if (key in record)
3072
- return key;
3073
- }
3074
- for (const nested of Object.values(record)) {
3075
- const found = findKey(nested, keys);
3076
- if (found)
3077
- return found;
2154
+ if (found === -1)
2155
+ return false;
2156
+ cursor = found + run.length;
3078
2157
  }
3079
- return;
2158
+ return true;
3080
2159
  }
3081
- function assertNoMongoServerSideScript(query) {
3082
- const operator = findKey(query, MONGO_SCRIPT_OPERATORS);
3083
- if (!operator)
3084
- return;
3085
- throw new ServerSideScriptRejection(`MongoDB server-side script rejected: '${operator}' executes JavaScript on the database server. ` + `Rewrite the query with standard operators, or run it through a tool that is meant to allow it.`, operator);
3086
- }
3087
- function assertNoElasticsearchScript(body) {
3088
- const key = findKey(body, ES_SCRIPT_KEYS);
3089
- if (!key)
3090
- return;
3091
- throw new ServerSideScriptRejection(`Elasticsearch server-side script rejected: '${key}' executes script code on the cluster. ` + `Rewrite the query without it.`, key);
3092
- }
3093
-
3094
- // src/adapters/mongodb-adapter.ts
3095
- class MongoDBAdapter {
3096
- options;
3097
- ClientClass;
3098
- client = null;
3099
- constructor(options, ClientClass = null) {
3100
- this.options = options;
3101
- this.ClientClass = ClientClass;
3102
- }
3103
- async resolveClientClass() {
3104
- if (this.ClientClass)
3105
- return this.ClientClass;
3106
- const { MongoClient } = await import("mongodb");
3107
- return MongoClient;
3108
- }
3109
- buildUri() {
3110
- if (this.options.uri)
3111
- return this.options.uri;
3112
- const { user, password, host, port, database, authSource, replicaSet, tls, srv } = this.options;
3113
- if (!host) {
3114
- throw new ConnectionError("UNKNOWN", "MongoDB host \u672A\u8A2D\u5B9A", [
3115
- "\u8ACB\u586B\u5BEB host\uFF0C\u6216\u6539\u7528 uri \u6B04\u4F4D\u6307\u5B9A\u5B8C\u6574\u9023\u7DDA\u5B57\u4E32"
3116
- ]);
3117
- }
3118
- const isBracketedIpv6 = /^\[[0-9a-f:]+\]$/i.test(host);
3119
- if (!isBracketedIpv6 && /[/@?#:\s\\]/.test(host)) {
3120
- throw new ConnectionError("UNKNOWN", `MongoDB host \u542B\u6709\u975E\u6CD5\u5B57\u5143: ${host}`, [
3121
- "host \u53EA\u61C9\u5305\u542B\u4E3B\u6A5F\u540D\u7A31\u6216 IP\uFF0C\u4E0D\u8981\u542B /\u3001@\u3001?\u3001#\u3001: \u6216\u7A7A\u767D",
3122
- "\u57E0\u865F\u8ACB\u586B\u5728 port \u6B04\u4F4D\uFF0C\u4E0D\u8981\u4F75\u9032 host",
3123
- "IPv6 \u4F4D\u5740\u8ACB\u52A0\u65B9\u62EC\u865F\uFF0C\u4F8B\u5982 [::1]",
3124
- "\u82E5\u8981\u6307\u5B9A\u5B8C\u6574\u9023\u7DDA\u5B57\u4E32\uFF0C\u8ACB\u6539\u7528 uri \u6B04\u4F4D"
3125
- ]);
3126
- }
3127
- if (user && !password) {
3128
- throw new ConnectionError("UNKNOWN", "\u5DF2\u6307\u5B9A user \u4F46\u672A\u63D0\u4F9B password", [
3129
- '\u8ACB\u88DC\u4E0A password\uFF0C\u6216\u6539\u7528\u74B0\u5883\u8B8A\u6578\u53C3\u7167 {"$env": "..."}',
3130
- "\u82E5\u78BA\u5B9A\u8981\u4EE5\u7121\u8A8D\u8B49\u65B9\u5F0F\u9023\u7DDA\uFF0C\u8ACB\u4E00\u4F75\u6E05\u7A7A user"
3131
- ]);
3132
- }
3133
- const userInfo = user ? `${encodeURIComponent(user)}:${encodeURIComponent(password)}@` : "";
3134
- const scheme = srv ? "mongodb+srv://" : "mongodb://";
3135
- const authority = srv ? host : `${host}:${port}`;
3136
- const path = database ? `/${encodeURIComponent(database)}` : "/";
3137
- const query = new URLSearchParams;
3138
- if (user)
3139
- query.set("authSource", authSource || "admin");
3140
- if (replicaSet)
3141
- query.set("replicaSet", replicaSet);
3142
- if (tls !== undefined)
3143
- query.set("tls", String(tls));
3144
- const search = query.toString();
3145
- return `${scheme}${userInfo}${authority}${path}${search ? `?${search}` : ""}`;
3146
- }
3147
- parseTxtRecords(records) {
3148
- const combined = records.flat().map((record) => record.replace(/^"|"$/g, "")).join("&");
3149
- if (!combined)
3150
- return {};
3151
- const params = new URLSearchParams(combined);
3152
- return Object.fromEntries(params.entries());
3153
- }
3154
- async resolveSrvHosts(hostname) {
3155
- const srvName = `_mongodb._tcp.${hostname}`;
3156
- try {
3157
- const records = await resolveSrv(srvName);
3158
- if (!records.length) {
3159
- throw new Error(`No SRV records found for ${srvName}`);
3160
- }
3161
- return records.map((record) => {
3162
- const target = String(record.name || record.target || "").replace(/\.$/, "");
3163
- return `${target}:${record.port}`;
3164
- });
3165
- } catch (error) {
3166
- const code = error?.code;
3167
- if (!["ECONNREFUSED", "ENOTFOUND", "ETIMEOUT", "ETIMEDOUT"].includes(code ?? "")) {
3168
- throw error;
3169
- }
3170
- }
3171
- const response = await fetch(`https://dns.google/resolve?name=${encodeURIComponent(srvName)}&type=SRV`);
3172
- if (!response.ok) {
3173
- throw new Error(`SRV lookup failed with HTTP ${response.status}`);
2160
+ function globNeverMatches(glob) {
2161
+ for (let i = 0;i < glob.length; i++) {
2162
+ const c = glob[i];
2163
+ if (c === "\\") {
2164
+ i++;
2165
+ continue;
3174
2166
  }
3175
- const payload = await response.json();
3176
- if (payload.Status !== 0 || !payload.Answer?.length) {
3177
- throw new Error(payload.Comment || `No SRV records found for ${srvName}`);
2167
+ if (c !== "[")
2168
+ continue;
2169
+ const end = findClassEnd(glob, i);
2170
+ if (end === -1) {
2171
+ return "unclosed character class";
3178
2172
  }
3179
- return payload.Answer.map((answer) => {
3180
- const parts = answer.data.trim().split(/\s+/);
3181
- const port = parts[2];
3182
- const target = parts.slice(3).join(" ").replace(/\.$/, "");
3183
- return `${target}:${port}`;
3184
- });
2173
+ const body = glob.slice(i, end + 1);
2174
+ if (body === "[]" || body === "[^]")
2175
+ return "empty character class matches nothing";
2176
+ if (!isValidCharacterClass(body))
2177
+ return "invalid character class";
2178
+ i = end;
3185
2179
  }
3186
- async resolveTxtOptions(hostname) {
3187
- try {
3188
- const records = await resolveTxt(hostname);
3189
- return this.parseTxtRecords(records);
3190
- } catch (error) {
3191
- const code = error?.code;
3192
- if (!["ECONNREFUSED", "ENOTFOUND", "ETIMEOUT", "ETIMEDOUT"].includes(code ?? "")) {
3193
- throw error;
3194
- }
3195
- }
3196
- const response = await fetch(`https://dns.google/resolve?name=${encodeURIComponent(hostname)}&type=TXT`);
3197
- if (!response.ok) {
3198
- throw new Error(`TXT lookup failed with HTTP ${response.status}`);
2180
+ return null;
2181
+ }
2182
+
2183
+ // src/core/mongo/path-matcher.ts
2184
+ function compilePatterns(raw) {
2185
+ const patterns = [];
2186
+ const rejected = [];
2187
+ for (const entry of raw) {
2188
+ if (typeof entry !== "string" || entry.length === 0) {
2189
+ rejected.push({ raw: String(entry ?? ""), reason: "must be a non-empty string" });
2190
+ continue;
3199
2191
  }
3200
- const payload = await response.json();
3201
- if (payload.Status !== 0 || !payload.Answer?.length) {
3202
- return {};
2192
+ const segments = entry.split(".");
2193
+ if (segments.some((s) => s.length === 0)) {
2194
+ rejected.push({ raw: entry, reason: "empty path segment" });
2195
+ continue;
3203
2196
  }
3204
- return this.parseTxtRecords(payload.Answer.map((answer) => [answer.data]));
3205
- }
3206
- async buildResolvedUri() {
3207
- const canonical = this.buildUri();
3208
- if (!canonical.startsWith("mongodb+srv://")) {
3209
- return canonical;
3210
- }
3211
- const url = new URL(canonical);
3212
- const hosts = await this.resolveSrvHosts(url.hostname);
3213
- const txtOptions = await this.resolveTxtOptions(url.hostname);
3214
- const query = new URLSearchParams(url.searchParams);
3215
- for (const [key, value] of Object.entries(txtOptions)) {
3216
- if (!query.has(key)) {
3217
- query.set(key, value);
3218
- }
3219
- }
3220
- if (!query.has("tls") && !query.has("ssl")) {
3221
- query.set("tls", "true");
3222
- }
3223
- if ((url.username || url.password) && !query.has("authSource")) {
3224
- query.set("authSource", "admin");
3225
- }
3226
- const userInfo = url.username || url.password ? `${encodeURIComponent(decodeURIComponent(url.username))}${url.password ? `:${encodeURIComponent(decodeURIComponent(url.password))}` : ""}@` : "";
3227
- const path = url.pathname && url.pathname !== "/" ? url.pathname : `/${this.options.database || "test"}`;
3228
- const search = query.toString();
3229
- return `mongodb://${userInfo}${hosts.join(",")}${path}${search ? `?${search}` : ""}`;
3230
- }
3231
- connectionHints(error, message) {
3232
- const AUTH_HINTS = [
3233
- "\u8A8D\u8B49\u5931\u6557\uFF1A\u8ACB\u78BA\u8A8D user / password \u6B63\u78BA",
3234
- "\u8ACB\u78BA\u8A8D authSource \u6307\u5411\u5B58\u653E\u8A72\u5E33\u865F\u7684\u8CC7\u6599\u5EAB\uFF08Atlas \u8207\u591A\u6578\u81EA\u67B6\u74B0\u5883\u70BA admin\uFF09"
3235
- ];
3236
- const DNS_HINTS = [
3237
- "DNS/SRV \u89E3\u6790\u5931\u6557\uFF1A\u8ACB\u78BA\u8A8D host \u70BA SRV \u7DB2\u57DF\uFF0C\u4E14 srv \u8A2D\u5B9A\u8207\u5B83\u4E00\u81F4",
3238
- "\u82E5\u8A72\u4E3B\u6A5F\u4E0D\u662F SRV \u7DB2\u57DF\uFF0C\u8ACB\u95DC\u9589 srv \u4E26\u6539\u586B host \u8207 port",
3239
- "\u8ACB\u78BA\u8A8D\u672C\u6A5F DNS \u6216\u7DB2\u8DEF\uFF08VPN\u3001\u516C\u53F8\u7DB2\u8DEF\uFF09\u5141\u8A31 SRV \u67E5\u8A62"
3240
- ];
3241
- const TLS_HINTS = [
3242
- "TLS \u63E1\u624B\u5931\u6557\uFF1A\u8ACB\u78BA\u8A8D tls \u6B04\u4F4D\u8A2D\u5B9A\u8207\u4F3A\u670D\u5668\u4E00\u81F4",
3243
- "\u81EA\u7C3D\u6191\u8B49\u74B0\u5883\u9700\u8981\u5728\u4F3A\u670D\u5668\u7AEF\u6216\u7CFB\u7D71\u4FE1\u4EFB\u93C8\u4E2D\u5B89\u88DD CA \u6191\u8B49"
3244
- ];
3245
- const err = error;
3246
- const causeCode = String(err?.cause?.code ?? err?.code ?? "");
3247
- if (err?.code === 18 || err?.codeName === "AuthenticationFailed")
3248
- return AUTH_HINTS;
3249
- if (["ENOTFOUND", "EAI_AGAIN"].includes(causeCode))
3250
- return DNS_HINTS;
3251
- if (causeCode.startsWith("ERR_TLS") || causeCode.startsWith("SELF_SIGNED"))
3252
- return TLS_HINTS;
3253
- if (/authentication failed|not authorized|bad auth/i.test(message))
3254
- return AUTH_HINTS;
3255
- if (/querySrv|getaddrinfo (ENOTFOUND|EAI_AGAIN)/i.test(message))
3256
- return DNS_HINTS;
3257
- if (/unable to verify the first certificate|self.signed certificate|certificate has expired|ERR_TLS/i.test(message)) {
3258
- return TLS_HINTS;
3259
- }
3260
- return ["\u8ACB\u78BA\u8A8D MongoDB \u670D\u52D9\u6B63\u5728\u57F7\u884C", "\u8ACB\u78BA\u8A8D\u9023\u7DDA\u8A2D\u5B9A\uFF08URI \u6216 host/port\uFF09\u6B63\u78BA"];
3261
- }
3262
- getDatabase() {
3263
- if (!this.client) {
3264
- throw new ConnectionError("UNKNOWN", "\u5C1A\u672A\u9023\u7DDA\uFF0C\u8ACB\u5148\u547C\u53EB connect()", []);
3265
- }
3266
- return this.client.db(this.options.database || undefined);
3267
- }
3268
- async connect() {
3269
- const ClientClass = await this.resolveClientClass();
3270
- const uri = await this.buildResolvedUri();
3271
- try {
3272
- this.client = new ClientClass(uri, { serverSelectionTimeoutMS: this.options.timeout ?? 5000 });
3273
- await this.client.connect();
3274
- } catch (err) {
3275
- const message = err.message ?? "Unknown error";
3276
- const code = message.includes("ECONNREFUSED") ? "ECONNREFUSED" : message.includes("ETIMEDOUT") ? "ETIMEDOUT" : "UNKNOWN";
3277
- throw new ConnectionError(code, `MongoDB \u9023\u7DDA\u5931\u6557: ${message}`, this.connectionHints(err, message));
2197
+ const globstar = segments.find((seg) => seg.includes("**"));
2198
+ if (globstar !== undefined) {
2199
+ rejected.push({
2200
+ raw: entry,
2201
+ reason: "`**` matches one segment, not a subtree; write `*` or a longer path"
2202
+ });
2203
+ continue;
3278
2204
  }
3279
- }
3280
- async disconnect() {
3281
- if (this.client) {
3282
- await this.client.close();
3283
- this.client = null;
2205
+ const dead = segments.map((seg) => globNeverMatches(seg)).find((r) => r !== null);
2206
+ if (dead !== undefined && dead !== null) {
2207
+ rejected.push({ raw: entry, reason: dead });
2208
+ continue;
3284
2209
  }
2210
+ const wildcardTail = segments.length > 1 && segments[segments.length - 1] === "*";
2211
+ const literal = wildcardTail ? segments.slice(0, -1) : segments;
2212
+ patterns.push({
2213
+ raw: entry,
2214
+ segments: literal,
2215
+ wildcardTail
2216
+ });
3285
2217
  }
3286
- async execute(query, params, options) {
3287
- const db = this.getDatabase();
3288
- const collectionName = params?.[0];
3289
- const collection = db.collection(collectionName);
3290
- let parsed;
3291
- try {
3292
- parsed = JSON.parse(query);
3293
- } catch {
3294
- throw new Error("MongoDB \u67E5\u8A62\u5FC5\u9808\u662F\u6709\u6548\u7684 JSON\uFF08object filter \u6216 array pipeline\uFF09");
3295
- }
3296
- assertNoMongoServerSideScript(parsed);
3297
- const limit = options?.limit;
3298
- let docs;
3299
- if (Array.isArray(parsed)) {
3300
- const stages = parsed;
3301
- const alreadyHasLimit = stages.some((stage) => stage !== null && typeof stage === "object" && ("$limit" in stage));
3302
- const limitedStages = typeof limit === "number" && limit > 0 && !alreadyHasLimit ? [...stages, { $limit: limit }] : stages;
3303
- const finalStages = options?.projection ? [...limitedStages, { $project: options.projection }] : limitedStages;
3304
- docs = await collection.aggregate(finalStages).toArray();
2218
+ return { patterns, rejected };
2219
+ }
2220
+ function matchAny(path, patterns) {
2221
+ if (patterns.length === 0)
2222
+ return false;
2223
+ const pathSegments = path.split(".");
2224
+ for (const pat of patterns) {
2225
+ if (pat.wildcardTail) {
2226
+ if (pathSegments.length < pat.segments.length)
2227
+ continue;
3305
2228
  } else {
3306
- const cap2 = typeof limit === "number" && limit > 0 ? limit : 0;
3307
- const cursor = options?.projection ? collection.find(parsed, { projection: options.projection }) : collection.find(parsed);
3308
- docs = await cursor.limit(cap2).toArray();
2229
+ if (pat.segments.length !== pathSegments.length)
2230
+ continue;
3309
2231
  }
3310
- return { rows: docs, affectedRows: docs.length };
3311
- }
3312
- async listCollections() {
3313
- const db = this.getDatabase();
3314
- const colls = await db.listCollections().toArray();
3315
- const results = await Promise.all(colls.map(async (col) => {
3316
- try {
3317
- const count = await db.collection(col.name).estimatedDocumentCount();
3318
- return { name: col.name, documentCount: count };
3319
- } catch {
3320
- return { name: col.name };
3321
- }
3322
- }));
3323
- return results;
3324
- }
3325
- async listTables() {
3326
- const collections = await this.listCollections();
3327
- return collections.map((c) => ({
3328
- name: c.name,
3329
- estimatedRowCount: c.documentCount,
3330
- columns: []
3331
- }));
3332
- }
3333
- async getTableSchema(collectionName, options) {
3334
- const db = this.getDatabase();
3335
- const collection = db.collection(collectionName);
3336
- const estimatedRowCount = await collection.estimatedDocumentCount();
3337
- const requested = options?.sampleSize;
3338
- const desired = typeof requested === "number" && requested >= 1 ? requested : 100;
3339
- const sampleSize = Math.min(desired, 1000);
3340
- const method = options?.sampleMethod ?? "random";
3341
- let samples = [];
3342
- if (method === "random") {
3343
- try {
3344
- samples = await collection.aggregate([{ $sample: { size: sampleSize } }]).toArray();
3345
- } catch (err) {
3346
- console.error(`[mongo] $sample failed (${err.message}); falling back to natural order.`);
3347
- samples = await collection.find().limit(sampleSize).toArray();
2232
+ let ok = true;
2233
+ for (let i = 0;i < pat.segments.length; i++) {
2234
+ if (!globMatches(pat.segments[i], pathSegments[i])) {
2235
+ ok = false;
2236
+ break;
3348
2237
  }
3349
- } else {
3350
- samples = await collection.find().limit(sampleSize).toArray();
3351
- }
3352
- const accumulator = new Map;
3353
- for (const doc of samples)
3354
- accumulateDocPaths(doc, "", accumulator);
3355
- const denom = Math.max(samples.length, 1);
3356
- const columns = Array.from(accumulator.entries()).map(([name, info]) => ({
3357
- name,
3358
- type: Array.from(info.types).sort().join(" | "),
3359
- nullable: info.types.has("null") || info.count < denom,
3360
- presence: info.count / denom
3361
- }));
3362
- return {
3363
- name: collectionName,
3364
- columns,
3365
- estimatedRowCount,
3366
- tableType: "table"
3367
- };
3368
- }
3369
- async testConnection() {
3370
- if (!this.client)
3371
- return false;
3372
- await this.getDatabase().command({ ping: 1 });
3373
- return true;
3374
- }
3375
- async getServerVersion() {
3376
- const info = await this.getDatabase().admin().serverInfo();
3377
- return info.version ?? "unknown";
3378
- }
3379
- async insert(collection, data) {
3380
- const db = this.getDatabase();
3381
- const result = await db.collection(collection).insertOne(data);
3382
- return {
3383
- rows: [],
3384
- affectedRows: result.acknowledged ? 1 : 0,
3385
- lastInsertId: result.insertedId.toString()
3386
- };
3387
- }
3388
- async update(collection, filter, update) {
3389
- const db = this.getDatabase();
3390
- assertNoMongoServerSideScript(filter);
3391
- assertNoMongoServerSideScript(update);
3392
- const result = await db.collection(collection).updateMany(filter, update);
3393
- return {
3394
- rows: [],
3395
- affectedRows: result.modifiedCount
3396
- };
3397
- }
3398
- async delete(collection, filter) {
3399
- const db = this.getDatabase();
3400
- assertNoMongoServerSideScript(filter);
3401
- const result = await db.collection(collection).deleteMany(filter);
3402
- return {
3403
- rows: [],
3404
- affectedRows: result.deletedCount
3405
- };
3406
- }
3407
- }
3408
- function detectMongoType(v) {
3409
- if (v === null || v === undefined)
3410
- return "null";
3411
- if (Array.isArray(v))
3412
- return "array";
3413
- if (v instanceof Date)
3414
- return "Date";
3415
- if (typeof v === "object") {
3416
- const ctor = v.constructor?.name;
3417
- if (ctor === "ObjectId")
3418
- return "ObjectId";
3419
- if (ctor === "Binary")
3420
- return "Binary";
3421
- if (ctor === "Decimal128")
3422
- return "Decimal128";
3423
- return "object";
3424
- }
3425
- if (typeof v === "string")
3426
- return "string";
3427
- if (typeof v === "number")
3428
- return "number";
3429
- if (typeof v === "boolean")
3430
- return "boolean";
3431
- return "string";
3432
- }
3433
- function accumulateDocPaths(doc, prefix, acc) {
3434
- for (const [k, v] of Object.entries(doc)) {
3435
- const path = prefix === "" ? k : `${prefix}.${k}`;
3436
- const t2 = detectMongoType(v);
3437
- const slot = acc.get(path) ?? { types: new Set, count: 0 };
3438
- slot.types.add(t2);
3439
- slot.count += 1;
3440
- acc.set(path, slot);
3441
- if (t2 === "object" && v && typeof v === "object") {
3442
- accumulateDocPaths(v, path, acc);
3443
2238
  }
2239
+ if (ok)
2240
+ return true;
3444
2241
  }
2242
+ return false;
3445
2243
  }
3446
2244
 
2245
+ // src/core/mongo/request-fields.ts
2246
+ var WHOLE_DOCUMENT_VARIABLES = new Set(["ROOT", "CURRENT"]);
2247
+ var RESHAPES_DOCUMENT = new Set(["$objectToArray", "$replaceRoot", "$replaceWith"]);
2248
+
3447
2249
  // src/adapters/redis/command-metadata.ts
3448
2250
  var tbl = (s) => s;
3449
2251
  var REDIS_COMMAND_TABLE = Object.freeze({
3450
2252
  PING: tbl({
3451
2253
  readOnly: true,
3452
2254
  sizeGuard: { kind: "unbounded" },
3453
- keyArity: { kind: "no-key" },
3454
- permissionTier: "query-only"
2255
+ keyArity: { kind: "no-key" }
3455
2256
  }),
3456
2257
  INFO: tbl({
3457
2258
  readOnly: true,
3458
2259
  sizeGuard: { kind: "unbounded" },
3459
- keyArity: { kind: "no-key" },
3460
- permissionTier: "query-only"
2260
+ keyArity: { kind: "no-key" }
3461
2261
  }),
3462
2262
  DBSIZE: tbl({
3463
2263
  readOnly: true,
3464
2264
  sizeGuard: { kind: "unbounded" },
3465
- keyArity: { kind: "no-key" },
3466
- permissionTier: "query-only"
2265
+ keyArity: { kind: "no-key" }
3467
2266
  }),
3468
2267
  TYPE: tbl({
3469
2268
  readOnly: true,
3470
2269
  sizeGuard: { kind: "unbounded" },
3471
- keyArity: { kind: "single", argIndex: 0 },
3472
- permissionTier: "query-only"
2270
+ keyArity: { kind: "single", argIndex: 0 }
3473
2271
  }),
3474
2272
  EXISTS: tbl({
3475
2273
  readOnly: true,
3476
2274
  sizeGuard: { kind: "unbounded" },
3477
- keyArity: { kind: "multi-variable", startIndex: 0, step: 1 },
3478
- permissionTier: "query-only"
2275
+ keyArity: { kind: "multi-variable", startIndex: 0, step: 1 }
3479
2276
  }),
3480
2277
  TTL: tbl({
3481
2278
  readOnly: true,
3482
2279
  sizeGuard: { kind: "unbounded" },
3483
- keyArity: { kind: "single", argIndex: 0 },
3484
- permissionTier: "query-only"
2280
+ keyArity: { kind: "single", argIndex: 0 }
3485
2281
  }),
3486
2282
  GET: tbl({
3487
2283
  readOnly: true,
3488
2284
  sizeGuard: { kind: "unbounded" },
3489
- keyArity: { kind: "single", argIndex: 0 },
3490
- permissionTier: "query-only"
2285
+ keyArity: { kind: "single", argIndex: 0 }
3491
2286
  }),
3492
2287
  MGET: tbl({
3493
2288
  readOnly: true,
3494
2289
  sizeGuard: { kind: "unbounded" },
3495
- keyArity: { kind: "multi-variable", startIndex: 0, step: 1 },
3496
- permissionTier: "query-only"
2290
+ keyArity: { kind: "multi-variable", startIndex: 0, step: 1 }
3497
2291
  }),
3498
2292
  STRLEN: tbl({
3499
2293
  readOnly: true,
3500
2294
  sizeGuard: { kind: "unbounded" },
3501
- keyArity: { kind: "single", argIndex: 0 },
3502
- permissionTier: "query-only"
2295
+ keyArity: { kind: "single", argIndex: 0 }
3503
2296
  }),
3504
2297
  HGET: tbl({
3505
2298
  readOnly: true,
3506
2299
  sizeGuard: { kind: "unbounded" },
3507
- keyArity: { kind: "single", argIndex: 0 },
3508
- permissionTier: "query-only"
2300
+ keyArity: { kind: "single", argIndex: 0 }
3509
2301
  }),
3510
2302
  HEXISTS: tbl({
3511
2303
  readOnly: true,
3512
2304
  sizeGuard: { kind: "unbounded" },
3513
- keyArity: { kind: "single", argIndex: 0 },
3514
- permissionTier: "query-only"
2305
+ keyArity: { kind: "single", argIndex: 0 }
3515
2306
  }),
3516
2307
  HLEN: tbl({
3517
2308
  readOnly: true,
3518
2309
  sizeGuard: { kind: "unbounded" },
3519
- keyArity: { kind: "single", argIndex: 0 },
3520
- permissionTier: "query-only"
2310
+ keyArity: { kind: "single", argIndex: 0 }
3521
2311
  }),
3522
2312
  HMGET: tbl({
3523
2313
  readOnly: true,
3524
2314
  sizeGuard: { kind: "unbounded" },
3525
- keyArity: { kind: "single", argIndex: 0 },
3526
- permissionTier: "query-only"
2315
+ keyArity: { kind: "single", argIndex: 0 }
3527
2316
  }),
3528
2317
  LLEN: tbl({
3529
2318
  readOnly: true,
3530
2319
  sizeGuard: { kind: "unbounded" },
3531
- keyArity: { kind: "single", argIndex: 0 },
3532
- permissionTier: "query-only"
2320
+ keyArity: { kind: "single", argIndex: 0 }
3533
2321
  }),
3534
2322
  LINDEX: tbl({
3535
2323
  readOnly: true,
3536
2324
  sizeGuard: { kind: "unbounded" },
3537
- keyArity: { kind: "single", argIndex: 0 },
3538
- permissionTier: "query-only"
2325
+ keyArity: { kind: "single", argIndex: 0 }
3539
2326
  }),
3540
2327
  SCARD: tbl({
3541
2328
  readOnly: true,
3542
2329
  sizeGuard: { kind: "unbounded" },
3543
- keyArity: { kind: "single", argIndex: 0 },
3544
- permissionTier: "query-only"
2330
+ keyArity: { kind: "single", argIndex: 0 }
3545
2331
  }),
3546
2332
  SISMEMBER: tbl({
3547
2333
  readOnly: true,
3548
2334
  sizeGuard: { kind: "unbounded" },
3549
- keyArity: { kind: "single", argIndex: 0 },
3550
- permissionTier: "query-only"
2335
+ keyArity: { kind: "single", argIndex: 0 }
3551
2336
  }),
3552
2337
  ZCARD: tbl({
3553
2338
  readOnly: true,
3554
2339
  sizeGuard: { kind: "unbounded" },
3555
- keyArity: { kind: "single", argIndex: 0 },
3556
- permissionTier: "query-only"
2340
+ keyArity: { kind: "single", argIndex: 0 }
3557
2341
  }),
3558
2342
  ZSCORE: tbl({
3559
2343
  readOnly: true,
3560
2344
  sizeGuard: { kind: "unbounded" },
3561
- keyArity: { kind: "single", argIndex: 0 },
3562
- permissionTier: "query-only"
2345
+ keyArity: { kind: "single", argIndex: 0 }
3563
2346
  }),
3564
2347
  XLEN: tbl({
3565
2348
  readOnly: true,
3566
2349
  sizeGuard: { kind: "unbounded" },
3567
- keyArity: { kind: "single", argIndex: 0 },
3568
- permissionTier: "query-only"
2350
+ keyArity: { kind: "single", argIndex: 0 }
3569
2351
  }),
3570
2352
  SCAN: tbl({
3571
2353
  readOnly: true,
3572
2354
  sizeGuard: { kind: "rewrite-count", argIndex: 0 },
3573
- keyArity: { kind: "no-key" },
3574
- permissionTier: "query-only"
2355
+ keyArity: { kind: "pattern-after-token", token: "MATCH" }
3575
2356
  }),
3576
2357
  HSCAN: tbl({
3577
2358
  readOnly: true,
3578
2359
  sizeGuard: { kind: "rewrite-count", argIndex: 1 },
3579
- keyArity: { kind: "single", argIndex: 0 },
3580
- permissionTier: "query-only"
2360
+ keyArity: { kind: "single", argIndex: 0 }
3581
2361
  }),
3582
2362
  SSCAN: tbl({
3583
2363
  readOnly: true,
3584
2364
  sizeGuard: { kind: "rewrite-count", argIndex: 1 },
3585
- keyArity: { kind: "single", argIndex: 0 },
3586
- permissionTier: "query-only"
2365
+ keyArity: { kind: "single", argIndex: 0 }
3587
2366
  }),
3588
2367
  ZSCAN: tbl({
3589
2368
  readOnly: true,
3590
2369
  sizeGuard: { kind: "rewrite-count", argIndex: 1 },
3591
- keyArity: { kind: "single", argIndex: 0 },
3592
- permissionTier: "query-only"
2370
+ keyArity: { kind: "single", argIndex: 0 }
3593
2371
  }),
3594
2372
  LRANGE: tbl({
3595
2373
  readOnly: true,
3596
2374
  sizeGuard: { kind: "rewrite-stop", argIndex: 2 },
3597
- keyArity: { kind: "single", argIndex: 0 },
3598
- permissionTier: "query-only"
2375
+ keyArity: { kind: "single", argIndex: 0 }
3599
2376
  }),
3600
2377
  ZRANGE: tbl({
3601
2378
  readOnly: true,
3602
2379
  sizeGuard: { kind: "rewrite-stop", argIndex: 2 },
3603
- keyArity: { kind: "single", argIndex: 0 },
3604
- permissionTier: "query-only"
2380
+ keyArity: { kind: "single", argIndex: 0 }
3605
2381
  }),
3606
2382
  ZREVRANGE: tbl({
3607
2383
  readOnly: true,
3608
2384
  sizeGuard: { kind: "rewrite-stop", argIndex: 2 },
3609
- keyArity: { kind: "single", argIndex: 0 },
3610
- permissionTier: "query-only"
2385
+ keyArity: { kind: "single", argIndex: 0 }
3611
2386
  }),
3612
2387
  ZRANGEBYSCORE: tbl({
3613
2388
  readOnly: true,
3614
2389
  sizeGuard: { kind: "rewrite-limit", argIndex: 3 },
3615
- keyArity: { kind: "single", argIndex: 0 },
3616
- permissionTier: "query-only"
2390
+ keyArity: { kind: "single", argIndex: 0 }
3617
2391
  }),
3618
2392
  HGETALL: tbl({
3619
2393
  readOnly: true,
3620
2394
  sizeGuard: { kind: "truncate" },
3621
- keyArity: { kind: "single", argIndex: 0 },
3622
- permissionTier: "query-only"
2395
+ keyArity: { kind: "single", argIndex: 0 }
3623
2396
  }),
3624
2397
  HKEYS: tbl({
3625
2398
  readOnly: true,
3626
2399
  sizeGuard: { kind: "truncate" },
3627
- keyArity: { kind: "single", argIndex: 0 },
3628
- permissionTier: "query-only"
2400
+ keyArity: { kind: "single", argIndex: 0 }
3629
2401
  }),
3630
2402
  HVALS: tbl({
3631
2403
  readOnly: true,
3632
2404
  sizeGuard: { kind: "truncate" },
3633
- keyArity: { kind: "single", argIndex: 0 },
3634
- permissionTier: "query-only"
2405
+ keyArity: { kind: "single", argIndex: 0 }
3635
2406
  }),
3636
2407
  SMEMBERS: tbl({
3637
2408
  readOnly: true,
3638
2409
  sizeGuard: { kind: "truncate" },
3639
- keyArity: { kind: "single", argIndex: 0 },
3640
- permissionTier: "query-only"
2410
+ keyArity: { kind: "single", argIndex: 0 }
3641
2411
  }),
3642
2412
  KEYS: tbl({
3643
2413
  readOnly: true,
3644
2414
  sizeGuard: { kind: "truncate" },
3645
- keyArity: { kind: "pattern", argIndex: 0 },
3646
- permissionTier: "query-only"
2415
+ keyArity: { kind: "pattern", argIndex: 0 }
3647
2416
  }),
3648
2417
  SET: tbl({
3649
2418
  readOnly: false,
3650
2419
  sizeGuard: { kind: "unbounded" },
3651
- keyArity: { kind: "single", argIndex: 0 },
3652
- permissionTier: "read-write"
2420
+ keyArity: { kind: "single", argIndex: 0 }
3653
2421
  }),
3654
2422
  SETEX: tbl({
3655
2423
  readOnly: false,
3656
2424
  sizeGuard: { kind: "unbounded" },
3657
- keyArity: { kind: "single", argIndex: 0 },
3658
- permissionTier: "read-write"
2425
+ keyArity: { kind: "single", argIndex: 0 }
3659
2426
  }),
3660
2427
  MSET: tbl({
3661
2428
  readOnly: false,
3662
2429
  sizeGuard: { kind: "unbounded" },
3663
- keyArity: { kind: "multi-variable", startIndex: 0, step: 2 },
3664
- permissionTier: "read-write"
2430
+ keyArity: { kind: "multi-variable", startIndex: 0, step: 2 }
3665
2431
  }),
3666
2432
  HSET: tbl({
3667
2433
  readOnly: false,
3668
2434
  sizeGuard: { kind: "unbounded" },
3669
- keyArity: { kind: "single", argIndex: 0 },
3670
- permissionTier: "read-write"
2435
+ keyArity: { kind: "single", argIndex: 0 }
3671
2436
  }),
3672
2437
  HMSET: tbl({
3673
2438
  readOnly: false,
3674
2439
  sizeGuard: { kind: "unbounded" },
3675
- keyArity: { kind: "single", argIndex: 0 },
3676
- permissionTier: "read-write"
2440
+ keyArity: { kind: "single", argIndex: 0 }
3677
2441
  }),
3678
2442
  HDEL: tbl({
3679
2443
  readOnly: false,
3680
2444
  sizeGuard: { kind: "unbounded" },
3681
- keyArity: { kind: "single", argIndex: 0 },
3682
- permissionTier: "read-write"
2445
+ keyArity: { kind: "single", argIndex: 0 }
3683
2446
  }),
3684
2447
  RPUSH: tbl({
3685
2448
  readOnly: false,
3686
2449
  sizeGuard: { kind: "unbounded" },
3687
- keyArity: { kind: "single", argIndex: 0 },
3688
- permissionTier: "read-write"
2450
+ keyArity: { kind: "single", argIndex: 0 }
3689
2451
  }),
3690
2452
  LPUSH: tbl({
3691
2453
  readOnly: false,
3692
2454
  sizeGuard: { kind: "unbounded" },
3693
- keyArity: { kind: "single", argIndex: 0 },
3694
- permissionTier: "read-write"
2455
+ keyArity: { kind: "single", argIndex: 0 }
3695
2456
  }),
3696
2457
  LREM: tbl({
3697
2458
  readOnly: false,
3698
2459
  sizeGuard: { kind: "unbounded" },
3699
- keyArity: { kind: "single", argIndex: 0 },
3700
- permissionTier: "read-write"
2460
+ keyArity: { kind: "single", argIndex: 0 }
3701
2461
  }),
3702
2462
  SADD: tbl({
3703
2463
  readOnly: false,
3704
2464
  sizeGuard: { kind: "unbounded" },
3705
- keyArity: { kind: "single", argIndex: 0 },
3706
- permissionTier: "read-write"
2465
+ keyArity: { kind: "single", argIndex: 0 }
3707
2466
  }),
3708
2467
  SREM: tbl({
3709
2468
  readOnly: false,
3710
2469
  sizeGuard: { kind: "unbounded" },
3711
- keyArity: { kind: "single", argIndex: 0 },
3712
- permissionTier: "read-write"
2470
+ keyArity: { kind: "single", argIndex: 0 }
3713
2471
  }),
3714
2472
  ZADD: tbl({
3715
2473
  readOnly: false,
3716
2474
  sizeGuard: { kind: "unbounded" },
3717
- keyArity: { kind: "single", argIndex: 0 },
3718
- permissionTier: "read-write"
2475
+ keyArity: { kind: "single", argIndex: 0 }
3719
2476
  }),
3720
2477
  ZREM: tbl({
3721
2478
  readOnly: false,
3722
2479
  sizeGuard: { kind: "unbounded" },
3723
- keyArity: { kind: "single", argIndex: 0 },
3724
- permissionTier: "read-write"
2480
+ keyArity: { kind: "single", argIndex: 0 }
3725
2481
  }),
3726
2482
  DEL: tbl({
3727
2483
  readOnly: false,
3728
2484
  sizeGuard: { kind: "unbounded" },
3729
- keyArity: { kind: "multi-variable", startIndex: 0, step: 1 },
3730
- permissionTier: "read-write"
2485
+ keyArity: { kind: "multi-variable", startIndex: 0, step: 1 }
3731
2486
  }),
3732
2487
  UNLINK: tbl({
3733
2488
  readOnly: false,
3734
2489
  sizeGuard: { kind: "unbounded" },
3735
- keyArity: { kind: "multi-variable", startIndex: 0, step: 1 },
3736
- permissionTier: "read-write"
2490
+ keyArity: { kind: "multi-variable", startIndex: 0, step: 1 }
3737
2491
  }),
3738
2492
  EXPIRE: tbl({
3739
2493
  readOnly: false,
3740
2494
  sizeGuard: { kind: "unbounded" },
3741
- keyArity: { kind: "single", argIndex: 0 },
3742
- permissionTier: "read-write"
2495
+ keyArity: { kind: "single", argIndex: 0 }
3743
2496
  }),
3744
2497
  PERSIST: tbl({
3745
2498
  readOnly: false,
3746
2499
  sizeGuard: { kind: "unbounded" },
3747
- keyArity: { kind: "single", argIndex: 0 },
3748
- permissionTier: "read-write"
2500
+ keyArity: { kind: "single", argIndex: 0 }
3749
2501
  }),
3750
2502
  RENAME: tbl({
3751
2503
  readOnly: false,
3752
2504
  sizeGuard: { kind: "unbounded" },
3753
- keyArity: { kind: "multi-fixed", argIndices: [0, 1] },
3754
- permissionTier: "read-write"
2505
+ keyArity: { kind: "multi-fixed", argIndices: [0, 1] }
3755
2506
  }),
3756
2507
  FLUSHDB: tbl({
3757
2508
  readOnly: false,
3758
2509
  sizeGuard: { kind: "reject", reason: "FLUSHDB destroys data" },
3759
- keyArity: { kind: "no-key" },
3760
- permissionTier: "admin"
2510
+ keyArity: { kind: "no-key" }
3761
2511
  }),
3762
2512
  FLUSHALL: tbl({
3763
2513
  readOnly: false,
3764
2514
  sizeGuard: { kind: "reject", reason: "FLUSHALL destroys data" },
3765
- keyArity: { kind: "no-key" },
3766
- permissionTier: "admin"
2515
+ keyArity: { kind: "no-key" }
3767
2516
  }),
3768
2517
  SHUTDOWN: tbl({
3769
2518
  readOnly: false,
3770
2519
  sizeGuard: { kind: "reject", reason: "SHUTDOWN stops the server" },
3771
- keyArity: { kind: "no-key" },
3772
- permissionTier: "admin"
2520
+ keyArity: { kind: "no-key" }
3773
2521
  }),
3774
2522
  DEBUG: tbl({
3775
2523
  readOnly: false,
3776
2524
  sizeGuard: { kind: "reject", reason: "DEBUG is unsafe" },
3777
- keyArity: { kind: "no-key" },
3778
- permissionTier: "admin"
2525
+ keyArity: { kind: "no-key" }
3779
2526
  }),
3780
- "CONFIG SET": tbl({
2527
+ PTTL: tbl({
2528
+ readOnly: true,
2529
+ sizeGuard: { kind: "unbounded" },
2530
+ keyArity: { kind: "single", argIndex: 0 }
2531
+ }),
2532
+ SETNX: tbl({
3781
2533
  readOnly: false,
3782
- sizeGuard: { kind: "reject", reason: "CONFIG SET mutates server state" },
3783
- keyArity: { kind: "no-key" },
3784
- permissionTier: "admin"
3785
- })
3786
- });
3787
- function getCommandSpec(name) {
3788
- return REDIS_COMMAND_TABLE[name.toUpperCase()];
3789
- }
3790
-
3791
- // src/adapters/redis/types.ts
3792
- class BlacklistRejection extends Error {
3793
- command;
3794
- matchedKey;
3795
- matchedPattern;
3796
- constructor(message, command, matchedKey, matchedPattern) {
3797
- super(message);
3798
- this.command = command;
3799
- this.matchedKey = matchedKey;
3800
- this.matchedPattern = matchedPattern;
3801
- this.name = "BlacklistRejection";
3802
- Object.setPrototypeOf(this, BlacklistRejection.prototype);
3803
- }
3804
- }
3805
- var REDIS_LIMIT_DEFAULT = 1000;
3806
-
3807
- // src/adapters/redis/size-guard.ts
3808
- function rewriteArgs(command, args, opts) {
3809
- if (opts.noLimit)
3810
- return { rewritten: args };
3811
- const spec = getCommandSpec(command);
3812
- if (!spec)
3813
- return { rewritten: args };
3814
- const original = args;
3815
- switch (spec.sizeGuard.kind) {
3816
- case "rewrite-count":
3817
- return rewriteCount(command, args, original);
3818
- case "rewrite-stop":
3819
- return rewriteStop(command, args, spec.sizeGuard.argIndex, original);
3820
- case "rewrite-limit":
3821
- return rewriteLimit(command, args, original);
3822
- default:
3823
- return { rewritten: args };
3824
- }
3825
- }
3826
- function rewriteCount(command, args, original) {
3827
- const idx = args.findIndex((a) => a.toUpperCase() === "COUNT");
3828
- if (idx === -1) {
3829
- const rewritten = [...args, "COUNT", String(REDIS_LIMIT_DEFAULT)];
3830
- return { rewritten, warning: { code: "REDIS_SIZE_REWRITE", command, original, rewritten } };
3831
- }
3832
- const n = parseInt(args[idx + 1] ?? "0", 10);
3833
- if (n > REDIS_LIMIT_DEFAULT) {
3834
- const rewritten = [...args];
3835
- rewritten[idx + 1] = String(REDIS_LIMIT_DEFAULT);
3836
- return { rewritten, warning: { code: "REDIS_SIZE_REWRITE", command, original, rewritten } };
3837
- }
3838
- return { rewritten: args };
3839
- }
3840
- function rewriteStop(command, args, stopIdx, original) {
3841
- const startStr = args[stopIdx - 1] ?? "0";
3842
- const stopStr = args[stopIdx];
3843
- if (stopStr === undefined)
3844
- return { rewritten: args };
3845
- const start = parseInt(startStr, 10) || 0;
3846
- const stop = parseInt(stopStr, 10);
3847
- const span = stop === -1 ? Infinity : stop - start + 1;
3848
- if (span > REDIS_LIMIT_DEFAULT) {
3849
- const rewritten = [...args];
3850
- rewritten[stopIdx] = String(start + REDIS_LIMIT_DEFAULT - 1);
3851
- return { rewritten, warning: { code: "REDIS_SIZE_REWRITE", command, original, rewritten } };
3852
- }
3853
- return { rewritten: args };
3854
- }
3855
- function rewriteLimit(command, args, original) {
3856
- const idx = args.findIndex((a) => a.toUpperCase() === "LIMIT");
3857
- if (idx === -1) {
3858
- const rewritten = [...args, "LIMIT", "0", String(REDIS_LIMIT_DEFAULT)];
3859
- return { rewritten, warning: { code: "REDIS_SIZE_REWRITE", command, original, rewritten } };
3860
- }
3861
- const n = parseInt(args[idx + 2] ?? "0", 10);
3862
- if (n > REDIS_LIMIT_DEFAULT) {
3863
- const rewritten = [...args];
3864
- rewritten[idx + 2] = String(REDIS_LIMIT_DEFAULT);
3865
- return { rewritten, warning: { code: "REDIS_SIZE_REWRITE", command, original, rewritten } };
3866
- }
3867
- return { rewritten: args };
3868
- }
3869
- function truncateResult(command, reply, opts) {
3870
- if (opts.noLimit)
3871
- return { value: reply };
3872
- const spec = getCommandSpec(command);
3873
- if (!spec || spec.sizeGuard.kind !== "truncate")
3874
- return { value: reply };
3875
- if (Array.isArray(reply)) {
3876
- if (reply.length > REDIS_LIMIT_DEFAULT) {
3877
- const droppedAtLeast = reply.length - REDIS_LIMIT_DEFAULT;
3878
- const value = reply.slice(0, REDIS_LIMIT_DEFAULT);
3879
- return {
3880
- value,
3881
- warning: {
3882
- code: "REDIS_SIZE_TRUNCATE",
3883
- command,
3884
- kept: REDIS_LIMIT_DEFAULT,
3885
- droppedAtLeast
3886
- }
3887
- };
3888
- }
3889
- return { value: reply };
3890
- }
3891
- if (reply && typeof reply === "object") {
3892
- const obj = reply;
3893
- const keys = Object.keys(obj);
3894
- if (keys.length > REDIS_LIMIT_DEFAULT) {
3895
- const dropped = keys.length - REDIS_LIMIT_DEFAULT;
3896
- const truncated = {};
3897
- for (let i = 0;i < REDIS_LIMIT_DEFAULT; i++)
3898
- truncated[keys[i]] = obj[keys[i]];
3899
- return {
3900
- value: truncated,
3901
- warning: {
3902
- code: "REDIS_SIZE_TRUNCATE",
3903
- command,
3904
- kept: REDIS_LIMIT_DEFAULT,
3905
- droppedAtLeast: dropped
3906
- }
3907
- };
3908
- }
3909
- }
3910
- return { value: reply };
3911
- }
3912
-
3913
- // src/utils/glob.ts
3914
- function globToRegex(glob) {
3915
- let out = "^";
3916
- for (let i = 0;i < glob.length; i++) {
3917
- const c = glob[i];
3918
- if (c === "\\" && i + 1 < glob.length) {
3919
- out += glob[++i].replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3920
- continue;
3921
- }
3922
- if (c === "*")
3923
- out += ".*";
3924
- else if (c === "?")
3925
- out += ".";
3926
- else if (c === "[") {
3927
- const end = findClassEnd(glob, i);
3928
- const body = end === -1 ? "" : glob.slice(i, end + 1);
3929
- if (body !== "" && isValidCharacterClass(body)) {
3930
- out += body;
3931
- i = end;
3932
- } else {
3933
- out += "\\[";
3934
- }
3935
- } else if (".^$+(){}|\\".includes(c)) {
3936
- out += "\\" + c;
3937
- } else {
3938
- out += c;
3939
- }
3940
- }
3941
- out += "$";
3942
- return new RegExp(out);
3943
- }
3944
- function findClassEnd(glob, open) {
3945
- for (let i = open + 1;i < glob.length; i++) {
3946
- if (glob[i] === "\\") {
3947
- i++;
3948
- continue;
3949
- }
3950
- if (glob[i] === "]")
3951
- return i;
3952
- }
3953
- return -1;
3954
- }
3955
- function isValidCharacterClass(body) {
3956
- try {
3957
- new RegExp(body);
3958
- return true;
3959
- } catch {
3960
- return false;
3961
- }
3962
- }
2534
+ sizeGuard: { kind: "unbounded" },
2535
+ keyArity: { kind: "single", argIndex: 0 }
2536
+ }),
2537
+ PSETEX: tbl({
2538
+ readOnly: false,
2539
+ sizeGuard: { kind: "unbounded" },
2540
+ keyArity: { kind: "single", argIndex: 0 }
2541
+ }),
2542
+ APPEND: tbl({
2543
+ readOnly: false,
2544
+ sizeGuard: { kind: "unbounded" },
2545
+ keyArity: { kind: "single", argIndex: 0 }
2546
+ }),
2547
+ INCR: tbl({
2548
+ readOnly: false,
2549
+ sizeGuard: { kind: "unbounded" },
2550
+ keyArity: { kind: "single", argIndex: 0 }
2551
+ }),
2552
+ INCRBY: tbl({
2553
+ readOnly: false,
2554
+ sizeGuard: { kind: "unbounded" },
2555
+ keyArity: { kind: "single", argIndex: 0 }
2556
+ }),
2557
+ DECR: tbl({
2558
+ readOnly: false,
2559
+ sizeGuard: { kind: "unbounded" },
2560
+ keyArity: { kind: "single", argIndex: 0 }
2561
+ }),
2562
+ DECRBY: tbl({
2563
+ readOnly: false,
2564
+ sizeGuard: { kind: "unbounded" },
2565
+ keyArity: { kind: "single", argIndex: 0 }
2566
+ }),
2567
+ HSETNX: tbl({
2568
+ readOnly: false,
2569
+ sizeGuard: { kind: "unbounded" },
2570
+ keyArity: { kind: "single", argIndex: 0 }
2571
+ }),
2572
+ HINCRBY: tbl({
2573
+ readOnly: false,
2574
+ sizeGuard: { kind: "unbounded" },
2575
+ keyArity: { kind: "single", argIndex: 0 }
2576
+ }),
2577
+ LPOP: tbl({
2578
+ readOnly: false,
2579
+ sizeGuard: { kind: "unbounded" },
2580
+ keyArity: { kind: "single", argIndex: 0 }
2581
+ }),
2582
+ RPOP: tbl({
2583
+ readOnly: false,
2584
+ sizeGuard: { kind: "unbounded" },
2585
+ keyArity: { kind: "single", argIndex: 0 }
2586
+ }),
2587
+ LSET: tbl({
2588
+ readOnly: false,
2589
+ sizeGuard: { kind: "unbounded" },
2590
+ keyArity: { kind: "single", argIndex: 0 }
2591
+ }),
2592
+ XADD: tbl({
2593
+ readOnly: false,
2594
+ sizeGuard: { kind: "unbounded" },
2595
+ keyArity: { kind: "single", argIndex: 0 }
2596
+ }),
2597
+ XDEL: tbl({
2598
+ readOnly: false,
2599
+ sizeGuard: { kind: "unbounded" },
2600
+ keyArity: { kind: "single", argIndex: 0 }
2601
+ }),
2602
+ XRANGE: tbl({
2603
+ readOnly: true,
2604
+ sizeGuard: { kind: "unbounded" },
2605
+ keyArity: { kind: "single", argIndex: 0 }
2606
+ }),
2607
+ XREVRANGE: tbl({
2608
+ readOnly: true,
2609
+ sizeGuard: { kind: "unbounded" },
2610
+ keyArity: { kind: "single", argIndex: 0 }
2611
+ }),
2612
+ EXPIREAT: tbl({
2613
+ readOnly: false,
2614
+ sizeGuard: { kind: "unbounded" },
2615
+ keyArity: { kind: "single", argIndex: 0 }
2616
+ }),
2617
+ PEXPIRE: tbl({
2618
+ readOnly: false,
2619
+ sizeGuard: { kind: "unbounded" },
2620
+ keyArity: { kind: "single", argIndex: 0 }
2621
+ }),
2622
+ MSETNX: tbl({
2623
+ readOnly: false,
2624
+ sizeGuard: { kind: "unbounded" },
2625
+ keyArity: { kind: "multi-variable", startIndex: 0, step: 2 }
2626
+ }),
2627
+ XREAD: tbl({
2628
+ readOnly: true,
2629
+ sizeGuard: { kind: "unbounded" },
2630
+ keyArity: { kind: "after-token", token: "STREAMS" }
2631
+ }),
2632
+ ECHO: tbl({
2633
+ readOnly: true,
2634
+ sizeGuard: { kind: "unbounded" },
2635
+ keyArity: { kind: "no-key" }
2636
+ }),
2637
+ CONFIG: tbl({
2638
+ readOnly: false,
2639
+ sizeGuard: { kind: "unbounded" },
2640
+ keyArity: { kind: "no-key" }
2641
+ }),
2642
+ CLIENT: tbl({
2643
+ readOnly: false,
2644
+ sizeGuard: { kind: "unbounded" },
2645
+ keyArity: { kind: "no-key" }
2646
+ }),
2647
+ MONITOR: tbl({
2648
+ readOnly: false,
2649
+ sizeGuard: { kind: "unbounded" },
2650
+ keyArity: { kind: "no-key" }
2651
+ }),
2652
+ SAVE: tbl({
2653
+ readOnly: false,
2654
+ sizeGuard: { kind: "unbounded" },
2655
+ keyArity: { kind: "no-key" }
2656
+ }),
2657
+ BGSAVE: tbl({
2658
+ readOnly: false,
2659
+ sizeGuard: { kind: "unbounded" },
2660
+ keyArity: { kind: "no-key" }
2661
+ }),
2662
+ BGREWRITEAOF: tbl({
2663
+ readOnly: false,
2664
+ sizeGuard: { kind: "unbounded" },
2665
+ keyArity: { kind: "no-key" }
2666
+ }),
2667
+ REPLICAOF: tbl({
2668
+ readOnly: false,
2669
+ sizeGuard: { kind: "unbounded" },
2670
+ keyArity: { kind: "no-key" }
2671
+ }),
2672
+ SLAVEOF: tbl({
2673
+ readOnly: false,
2674
+ sizeGuard: { kind: "unbounded" },
2675
+ keyArity: { kind: "no-key" }
2676
+ }),
2677
+ ACL: tbl({
2678
+ readOnly: false,
2679
+ sizeGuard: { kind: "unbounded" },
2680
+ keyArity: { kind: "no-key" }
2681
+ }),
2682
+ "CONFIG SET": tbl({
2683
+ readOnly: false,
2684
+ sizeGuard: { kind: "reject", reason: "CONFIG SET mutates server state" },
2685
+ keyArity: { kind: "no-key" }
2686
+ })
2687
+ });
3963
2688
 
3964
- // src/adapters/redis/blacklist-enforcer.ts
3965
- function patternsOverlap(a, b) {
3966
- const ra = globToRegex(a);
3967
- const rb = globToRegex(b);
3968
- const probeA = sampleFromGlob(a);
3969
- const probeB = sampleFromGlob(b);
3970
- if (probeA !== null && rb.test(probeA))
3971
- return true;
3972
- if (probeB !== null && ra.test(probeB))
3973
- return true;
3974
- return false;
3975
- }
3976
- function sampleFromGlob(glob) {
3977
- let s = "";
3978
- for (let i = 0;i < glob.length; i++) {
3979
- const c = glob[i];
3980
- if (c === "*")
3981
- s += "__star__";
3982
- else if (c === "?")
3983
- s += "x";
3984
- else if (c === "[") {
3985
- const end = glob.indexOf("]", i);
3986
- if (end === -1)
3987
- return null;
3988
- const first = glob[i + 1];
3989
- if (!first)
3990
- return null;
3991
- s += first;
3992
- i = end;
3993
- } else
3994
- s += c;
3995
- }
3996
- return s;
3997
- }
3998
- function checkKeyArgs(command, args, rules) {
3999
- if (rules.length === 0)
4000
- return { ok: true };
4001
- const spec = getCommandSpec(command);
4002
- if (!spec)
4003
- return { ok: true };
4004
- const keyIndexes = expandKeyArity(spec.keyArity, args.length);
4005
- for (const idx of keyIndexes) {
4006
- const key = args[idx];
4007
- if (key === undefined)
4008
- continue;
4009
- for (const pat of rules) {
4010
- if (globToRegex(pat).test(key)) {
4011
- return { ok: false, matchedKey: key, matchedPattern: pat };
4012
- }
4013
- }
4014
- }
4015
- if (spec.keyArity.kind === "pattern") {
4016
- const userPat = args[spec.keyArity.argIndex];
4017
- if (userPat !== undefined) {
4018
- for (const pat of rules) {
4019
- if (patternsOverlap(userPat, pat)) {
4020
- return { ok: false, matchedKey: null, matchedPattern: pat };
4021
- }
4022
- }
4023
- }
4024
- }
4025
- return { ok: true };
4026
- }
4027
- function expandKeyArity(arity, argCount) {
4028
- switch (arity.kind) {
4029
- case "no-key":
4030
- case "pattern":
4031
- return [];
4032
- case "single":
4033
- return [arity.argIndex];
4034
- case "multi-fixed":
4035
- return arity.argIndices;
4036
- case "multi-variable": {
4037
- const indices = [];
4038
- for (let i = arity.startIndex;i < argCount; i += arity.step)
4039
- indices.push(i);
4040
- return indices;
4041
- }
4042
- }
4043
- }
2689
+ // src/adapters/redis/returned-key-names.ts
2690
+ var RETURNS_KEY_NAMES = new Set(["SCAN", "KEYS"]);
4044
2691
 
4045
2692
  // src/adapters/redis/value-masker.ts
4046
- var REDACTED = "[REDACTED]";
4047
2693
  var MASKABLE = new Set(["GET", "GETRANGE", "HGETALL", "HGET", "HMGET", "HVALS"]);
4048
- function planFor(key, rules) {
4049
- const matched = rules.filter((r) => globToRegex(r.keyPattern).test(key));
4050
- if (matched.length === 0)
4051
- return null;
4052
- const wholeValue = matched.some((r) => !r.fields || r.fields.length === 0);
4053
- const fields = new Set(matched.flatMap((r) => r.fields ?? []));
4054
- return { wholeValue, fields };
4055
- }
4056
- function maskRedisRows(command, args, rows, rules) {
4057
- if (rules.length === 0)
4058
- return rows;
4059
- const head = command.toUpperCase();
4060
- if (!MASKABLE.has(head))
4061
- return rows;
4062
- const key = args[0];
4063
- if (key === undefined)
4064
- return rows;
4065
- const plan = planFor(key, rules);
4066
- if (!plan)
4067
- return rows;
4068
- if (head === "GET" || head === "GETRANGE") {
4069
- if (!plan.wholeValue)
4070
- return rows;
4071
- return rows.map((r) => ({ ...r, value: REDACTED }));
4072
- }
4073
- if (head === "HGETALL") {
4074
- return rows.map((row) => {
4075
- const out = {};
4076
- for (const [field, value] of Object.entries(row)) {
4077
- out[field] = plan.wholeValue || plan.fields.has(field) ? REDACTED : value;
4078
- }
4079
- return out;
4080
- });
4081
- }
4082
- if (head === "HGET") {
4083
- const field = args[1];
4084
- const hit = plan.wholeValue || field !== undefined && plan.fields.has(field);
4085
- return hit ? rows.map((r) => ({ ...r, value: REDACTED })) : rows;
4086
- }
4087
- if (head === "HMGET") {
4088
- return rows.map((row) => {
4089
- const idx = typeof row.index === "number" ? row.index : Number(row.index);
4090
- const field = args[1 + idx];
4091
- const hit = plan.wholeValue || field !== undefined && plan.fields.has(field);
4092
- return hit ? { ...row, value: REDACTED } : row;
4093
- });
4094
- }
4095
- if (head === "HVALS") {
4096
- if (!plan.wholeValue)
4097
- return rows;
4098
- return rows.map((r) => ({ ...r, value: REDACTED }));
4099
- }
4100
- return rows;
4101
- }
4102
-
4103
- // src/adapters/redis-adapter.ts
4104
- var REDIS_LIST_KEY_LIMIT = 1000;
4105
-
4106
- class RedisAdapter {
4107
- options;
4108
- ClientClass;
4109
- client = null;
4110
- blacklistRules = [];
4111
- constructor(options, ClientClass = null) {
4112
- this.options = options;
4113
- this.ClientClass = ClientClass;
4114
- if (options.port < 1 || options.port > 65535) {
4115
- throw new Error(`Invalid port number: ${options.port}`);
4116
- }
4117
- }
4118
- setBlacklistRules(rules) {
4119
- this.blacklistRules = rules;
4120
- }
4121
- maskRules = [];
4122
- setMaskRules(rules) {
4123
- this.maskRules = rules;
4124
- }
4125
- async resolveClientClass() {
4126
- if (this.ClientClass)
4127
- return this.ClientClass;
4128
- const mod = await Promise.resolve(globalThis.Bun);
4129
- return mod.RedisClient;
4130
- }
4131
- requireClient() {
4132
- if (!this.client) {
4133
- throw new ConnectionError("UNKNOWN", "\u5C1A\u672A\u9023\u7DDA\uFF0C\u8ACB\u5148\u547C\u53EB connect()", ["\u8ACB\u5148\u547C\u53EB connect()"]);
4134
- }
4135
- return this.client;
4136
- }
4137
- async connect() {
4138
- const ClientClass = await this.resolveClientClass();
4139
- const url = buildRedisUrl(this.options);
4140
- try {
4141
- this.client = new ClientClass(url, {
4142
- connectionTimeout: this.options.timeout ?? 5000,
4143
- autoReconnect: false,
4144
- maxRetries: 0,
4145
- enableOfflineQueue: false
4146
- });
4147
- try {
4148
- this.client.onclose = () => {};
4149
- } catch {}
4150
- await this.client.connect();
4151
- } catch (err) {
4152
- try {
4153
- this.client?.close?.();
4154
- } catch {}
4155
- this.client = null;
4156
- const message = err.message ?? "Unknown error";
4157
- const code = inferConnectionCode(message, err.code);
4158
- throw new ConnectionError(code, `Redis \u9023\u7DDA\u5931\u6557: ${message}`, [
4159
- "\u8ACB\u78BA\u8A8D Redis \u670D\u52D9\u6B63\u5728\u57F7\u884C\uFF08\u4F8B\u5982\uFF1Aredis-cli ping\uFF09",
4160
- `\u8ACB\u78BA\u8A8D host/port \u6B63\u78BA\uFF1A${this.options.host}:${this.options.port}`,
4161
- "\u82E5\u6709\u8A2D\u5BC6\u78BC\u8ACB\u78BA\u8A8D password \u8207 ACL \u8A2D\u5B9A\u4E00\u81F4"
4162
- ]);
4163
- }
4164
- }
4165
- async disconnect() {
4166
- if (!this.client)
4167
- return;
4168
- try {
4169
- this.client.close();
4170
- } catch {} finally {
4171
- this.client = null;
4172
- }
4173
- }
4174
- async testConnection() {
4175
- const client = this.requireClient();
4176
- const reply = await client.send("PING", []);
4177
- return reply === "PONG";
4178
- }
4179
- async getServerVersion() {
4180
- const client = this.requireClient();
4181
- const info = await client.send("INFO", ["server"]);
4182
- const match = info.match(/^redis_version:(.+)$/m);
4183
- return match?.[1]?.trim() ?? "unknown";
4184
- }
4185
- async listCollections(options) {
4186
- const { names } = await this.sampleKeyNames(options?.limit ?? REDIS_LIST_KEY_LIMIT);
4187
- return names.map((name) => ({ name }));
4188
- }
4189
- async sampleKeyNames(limit) {
4190
- const client = this.requireClient();
4191
- const rules = this.blacklistRules;
4192
- const regexes = rules.map((p) => globToRegex(p));
4193
- const { keys, scanned } = await scanAllKeys(client, "*", limit, limit, (key) => regexes.every((r) => !r.test(key)));
4194
- return { names: keys, truncated: scanned >= limit };
4195
- }
4196
- async getDbSize() {
4197
- const client = this.requireClient();
4198
- const reply = await client.send("DBSIZE", []);
4199
- return reply;
4200
- }
4201
- async listTables() {
4202
- const collections = await this.listCollections();
4203
- return collections.map((c) => ({ name: c.name, columns: [] }));
4204
- }
4205
- async getTableSchema(keyName) {
4206
- const blk = checkKeyArgs("GET", [keyName], this.blacklistRules);
4207
- if (!blk.ok) {
4208
- throw new BlacklistRejection(`BlacklistRejection: schema lookup on key '${keyName}' rejected
4209
- matched blacklist pattern: ${blk.matchedPattern}`, "SCHEMA", keyName, blk.matchedPattern);
4210
- }
4211
- const client = this.requireClient();
4212
- const type = await client.send("TYPE", [keyName]);
4213
- if (type === "none") {
4214
- return { name: keyName, columns: [], tableType: "table" };
4215
- }
4216
- const ttl = await client.ttl(keyName);
4217
- const sizeInfo = await readSizeInfo(client, keyName, type);
4218
- const columns = [
4219
- { name: "type", type, nullable: false },
4220
- {
4221
- name: "ttl",
4222
- type: ttl >= 0 ? `${ttl}s` : ttl === -1 ? "no expiry" : "missing",
4223
- nullable: false
4224
- },
4225
- { name: "size", type: String(sizeInfo.size), nullable: false }
4226
- ];
4227
- if (sizeInfo.sample) {
4228
- columns.push({ name: "sample", type: sizeInfo.sample, nullable: true });
4229
- }
4230
- return {
4231
- name: keyName,
4232
- columns,
4233
- tableType: "table",
4234
- estimatedRowCount: sizeInfo.size
4235
- };
4236
- }
4237
- async dispatchCommand(head, rest) {
4238
- const client = this.requireClient();
4239
- const reply = await client.send(head, rest);
4240
- return wrapReply(head, reply);
4241
- }
4242
- async execute(command, _params, options) {
4243
- const tokens = parseRedisCommand(command);
4244
- if (tokens.length === 0) {
4245
- throw new Error("Redis \u6307\u4EE4\u4E0D\u53EF\u70BA\u7A7A");
4246
- }
4247
- const head = String(tokens[0]).toUpperCase();
4248
- const rest = tokens.slice(1).map(String);
4249
- const blk = checkKeyArgs(head, rest, this.blacklistRules);
4250
- if (!blk.ok) {
4251
- const msg = blk.matchedKey ? `BlacklistRejection: command ${head} on key '${blk.matchedKey}' rejected
4252
- matched blacklist pattern: ${blk.matchedPattern}` : `BlacklistRejection: ${head} pattern overlaps blacklist pattern: ${blk.matchedPattern}`;
4253
- throw new BlacklistRejection(msg, head, blk.matchedKey ?? null, blk.matchedPattern);
4254
- }
4255
- const noLimit = options?.noLimit === true;
4256
- const rw = rewriteArgs(head, rest, { noLimit });
4257
- const warnings = [];
4258
- if (rw.warning)
4259
- warnings.push(rw.warning);
4260
- const spec = getCommandSpec(head);
4261
- if (spec?.sizeGuard.kind === "truncate") {
4262
- const client = this.requireClient();
4263
- const rawReply = await client.send(head, rw.rewritten);
4264
- const tr = truncateResult(head, rawReply, { noLimit });
4265
- if (tr.warning)
4266
- warnings.push(tr.warning);
4267
- const rows2 = wrapReply(head, tr.value);
4268
- const masked2 = maskRedisRows(head, rest, rows2, this.maskRules);
4269
- return finishResult(masked2, warnings);
4270
- }
4271
- const rows = await this.dispatchCommand(head, rw.rewritten);
4272
- const masked = maskRedisRows(head, rest, rows, this.maskRules);
4273
- return finishResult(masked, warnings);
4274
- }
4275
- async insert(keyName, data) {
4276
- const client = this.requireClient();
4277
- const type = data.__type ?? "string";
4278
- if (type === "string") {
4279
- const value = String(data.value ?? "");
4280
- await client.set(keyName, value);
4281
- if (typeof data.ttl === "number" && data.ttl > 0) {
4282
- await client.expire(keyName, data.ttl);
4283
- }
4284
- return { rows: [], affectedRows: 1 };
4285
- }
4286
- if (type === "hash") {
4287
- const fields = data.fields;
4288
- if (!fields)
4289
- throw new Error("hash insert \u9700\u8981 fields \u7269\u4EF6");
4290
- const flat = [];
4291
- for (const [k, v] of Object.entries(fields))
4292
- flat.push(k, String(v));
4293
- await client.hmset(keyName, flat);
4294
- return { rows: [], affectedRows: Object.keys(fields).length };
4295
- }
4296
- if (type === "list") {
4297
- const values = data.values;
4298
- if (!values || !Array.isArray(values))
4299
- throw new Error("list insert \u9700\u8981 values \u9663\u5217");
4300
- await client.send("RPUSH", [keyName, ...values.map(String)]);
4301
- return { rows: [], affectedRows: values.length };
4302
- }
4303
- if (type === "set") {
4304
- const values = data.values;
4305
- if (!values || !Array.isArray(values))
4306
- throw new Error("set insert \u9700\u8981 values \u9663\u5217");
4307
- await client.send("SADD", [keyName, ...values.map(String)]);
4308
- return { rows: [], affectedRows: values.length };
4309
- }
4310
- if (type === "zset") {
4311
- const members = data.members;
4312
- if (!members)
4313
- throw new Error("zset insert \u9700\u8981 members \u7269\u4EF6 (member -> score)");
4314
- const flat = [];
4315
- for (const [m, s] of Object.entries(members))
4316
- flat.push(String(s), m);
4317
- await client.send("ZADD", [keyName, ...flat]);
4318
- return { rows: [], affectedRows: Object.keys(members).length };
4319
- }
4320
- throw new Error(`\u4E0D\u652F\u63F4\u7684 insert \u985E\u578B: ${String(type)}`);
4321
- }
4322
- async update(keyName, _filter, update) {
4323
- const client = this.requireClient();
4324
- const fields = update.fields;
4325
- if (fields) {
4326
- const flat = [];
4327
- for (const [k, v] of Object.entries(fields))
4328
- flat.push(k, String(v));
4329
- await client.hmset(keyName, flat);
4330
- return { rows: [], affectedRows: Object.keys(fields).length };
4331
- }
4332
- const type = update.__type;
4333
- if (type === "list") {
4334
- const values = update.values;
4335
- await client.send("RPUSH", [keyName, ...values.map(String)]);
4336
- return { rows: [], affectedRows: values.length };
4337
- }
4338
- if (type === "set") {
4339
- const values = update.values;
4340
- await client.send("SADD", [keyName, ...values.map(String)]);
4341
- return { rows: [], affectedRows: values.length };
4342
- }
4343
- if (type === "zset") {
4344
- const members = update.members;
4345
- const flat = [];
4346
- for (const [m, s] of Object.entries(members))
4347
- flat.push(String(s), m);
4348
- await client.send("ZADD", [keyName, ...flat]);
4349
- return { rows: [], affectedRows: Object.keys(members).length };
4350
- }
4351
- if ("value" in update) {
4352
- await client.set(keyName, String(update.value));
4353
- return { rows: [], affectedRows: 1 };
4354
- }
4355
- throw new Error("update \u9700\u8981 fields, values, members \u6216 value");
4356
- }
4357
- async delete(keyName, filter) {
4358
- const client = this.requireClient();
4359
- const field = filter.field;
4360
- if (field) {
4361
- const removed2 = await client.send("HDEL", [keyName, field]);
4362
- return { rows: [], affectedRows: removed2 };
4363
- }
4364
- const value = filter.value;
4365
- if (value) {
4366
- const type = await client.send("TYPE", [keyName]);
4367
- let removed2 = 0;
4368
- if (type === "list") {
4369
- removed2 = await client.send("LREM", [keyName, "0", value]);
4370
- } else if (type === "set") {
4371
- removed2 = await client.send("SREM", [keyName, value]);
4372
- } else if (type === "zset") {
4373
- removed2 = await client.send("ZREM", [keyName, value]);
4374
- }
4375
- return { rows: [], affectedRows: removed2 };
4376
- }
4377
- const removed = await client.del(keyName);
4378
- return { rows: [], affectedRows: removed };
4379
- }
4380
- }
4381
- function finishResult(rows, warnings) {
4382
- const columnNames = rows[0] ? Object.keys(rows[0]) : ["value"];
4383
- return {
4384
- rows,
4385
- affectedRows: rows.length,
4386
- rowCount: rows.length,
4387
- columnNames,
4388
- ...warnings.length > 0 ? { warnings } : {}
4389
- };
4390
- }
4391
- function buildRedisUrl(opts) {
4392
- let auth = "";
4393
- if (opts.user || opts.password) {
4394
- const u = encodeURIComponent(opts.user || "");
4395
- const p = encodeURIComponent(opts.password || "");
4396
- auth = `${u}:${p}@`;
4397
- }
4398
- const dbPart = opts.database ? `/${opts.database}` : "";
4399
- return `redis://${auth}${opts.host}:${opts.port}${dbPart}`;
4400
- }
4401
- function inferConnectionCode(message, driverCode) {
4402
- const m = message.toLowerCase();
4403
- if (driverCode === "ERR_REDIS_AUTHENTICATION_FAILED")
4404
- return "AUTH_FAILED";
4405
- if (driverCode === "ERR_REDIS_CONNECTION_CLOSED")
4406
- return "ECONNREFUSED";
4407
- if (driverCode === "ECONNREFUSED" || m.includes("econnrefused") || m.includes("refused")) {
4408
- return "ECONNREFUSED";
4409
- }
4410
- if (driverCode === "ETIMEDOUT" || m.includes("timeout") || m.includes("timed out")) {
4411
- return "ETIMEDOUT";
4412
- }
4413
- if (driverCode === "ENOTFOUND" || m.includes("enotfound") || m.includes("getaddrinfo")) {
4414
- return "ENOTFOUND";
4415
- }
4416
- if (m.includes("noauth") || m.includes("wrongpass") || m.includes("auth")) {
4417
- return "AUTH_FAILED";
4418
- }
4419
- return "UNKNOWN";
4420
- }
4421
- function parseRedisCommand(input) {
4422
- const tokens = [];
4423
- let buf = "";
4424
- let quote = null;
4425
- let i = 0;
4426
- let inToken = false;
4427
- const push = () => {
4428
- tokens.push(buf);
4429
- buf = "";
4430
- inToken = false;
4431
- };
4432
- while (i < input.length) {
4433
- const ch = input[i];
4434
- if (quote) {
4435
- if (ch === "\\" && i + 1 < input.length) {
4436
- buf += input[i + 1];
4437
- i += 2;
4438
- continue;
4439
- }
4440
- if (ch === quote) {
4441
- quote = null;
4442
- i++;
4443
- continue;
4444
- }
4445
- buf += ch;
4446
- i++;
4447
- continue;
4448
- }
4449
- if (ch === '"' || ch === "'") {
4450
- quote = ch;
4451
- inToken = true;
4452
- i++;
4453
- continue;
4454
- }
4455
- if (/\s/.test(ch)) {
4456
- if (inToken)
4457
- push();
4458
- i++;
4459
- continue;
4460
- }
4461
- if (ch === "\\" && i + 1 < input.length) {
4462
- buf += input[i + 1];
4463
- i += 2;
4464
- inToken = true;
4465
- continue;
4466
- }
4467
- buf += ch;
4468
- inToken = true;
4469
- i++;
4470
- }
4471
- if (quote) {
4472
- throw new Error("Redis \u6307\u4EE4\u542B\u6709\u672A\u9589\u5408\u7684\u5F15\u865F");
4473
- }
4474
- if (inToken)
4475
- push();
4476
- return tokens;
4477
- }
4478
- async function scanAllKeys(client, pattern, count, maxKeys, accept = () => true) {
4479
- const seen = new Set;
4480
- const kept = new Set;
4481
- let cursor = "0";
4482
- do {
4483
- const reply = await client.send("SCAN", [
4484
- cursor,
4485
- "MATCH",
4486
- pattern,
4487
- "COUNT",
4488
- String(count)
4489
- ]);
4490
- const [next, batch] = reply;
4491
- for (const k of batch) {
4492
- if (seen.has(k))
4493
- continue;
4494
- seen.add(k);
4495
- if (accept(k))
4496
- kept.add(k);
4497
- }
4498
- cursor = next;
4499
- if (seen.size >= maxKeys)
4500
- break;
4501
- } while (cursor !== "0");
4502
- return { keys: Array.from(kept), scanned: seen.size };
4503
- }
4504
- async function readSizeInfo(client, key, type) {
4505
- switch (type) {
4506
- case "string": {
4507
- const len = await client.send("STRLEN", [key]);
4508
- return { size: len };
4509
- }
4510
- case "hash": {
4511
- const len = await client.send("HLEN", [key]);
4512
- const fields = await client.send("HKEYS", [key]);
4513
- return { size: len, sample: fields.slice(0, 5).join(", ") };
4514
- }
4515
- case "list": {
4516
- const len = await client.send("LLEN", [key]);
4517
- return { size: len };
4518
- }
4519
- case "set": {
4520
- const len = await client.send("SCARD", [key]);
4521
- return { size: len };
4522
- }
4523
- case "zset": {
4524
- const len = await client.send("ZCARD", [key]);
4525
- return { size: len };
4526
- }
4527
- case "stream": {
4528
- const len = await client.send("XLEN", [key]);
4529
- return { size: len };
4530
- }
4531
- default:
4532
- return { size: 0 };
4533
- }
4534
- }
4535
- function wrapReply(command, reply) {
4536
- const upper = command.toUpperCase();
4537
- if (reply === null || reply === undefined)
4538
- return [];
4539
- if (typeof reply === "string" || typeof reply === "number" || typeof reply === "boolean" || Buffer.isBuffer(reply)) {
4540
- return [{ value: reply.toString() }];
4541
- }
4542
- if (Array.isArray(reply)) {
4543
- if (upper === "HGETALL") {
4544
- const obj = {};
4545
- for (let i = 0;i < reply.length; i += 2) {
4546
- obj[String(reply[i])] = reply[i + 1];
4547
- }
4548
- return [obj];
4549
- }
4550
- return reply.map((value, idx) => ({ index: idx, value: String(value) }));
4551
- }
4552
- if (typeof reply === "object") {
4553
- return [reply];
4554
- }
4555
- return [{ value: String(reply) }];
4556
- }
4557
-
4558
- // src/adapters/elasticsearch-adapter.ts
4559
- class ElasticsearchAdapter {
4560
- options;
4561
- baseUrl = "";
4562
- currentNodeIndex = 0;
4563
- constructor(options) {
4564
- this.options = options;
4565
- this.baseUrl = this.resolveBaseUrl();
4566
- }
4567
- async connect() {
4568
- try {
4569
- await this.request("GET", "/");
4570
- } catch (error) {
4571
- if (error instanceof ConnectionError)
4572
- throw error;
4573
- throw new ConnectionError("UNKNOWN", `Could not connect to Elasticsearch: ${error.message}`, ["Check if Elasticsearch is running", "Verify host and port", "Check network connectivity"]);
4574
- }
4575
- }
4576
- async disconnect() {}
4577
- async execute(query, params, options) {
4578
- const indexName = params?.[0];
4579
- if (!indexName) {
4580
- throw new Error("Index name is required as the first parameter to execute()");
4581
- }
4582
- const isDsl = query.trim().startsWith("{");
4583
- const limit = options?.limit ?? 100;
4584
- let path = `/${encodeEsIndexExpression(indexName)}/_search`;
4585
- let body = undefined;
4586
- if (isDsl) {
4587
- body = JSON.parse(query);
4588
- assertNoElasticsearchScript(body);
4589
- if (body.size === undefined) {
4590
- body.size = limit;
4591
- }
4592
- } else {
4593
- path += `?q=${encodeURIComponent(query)}&size=${limit}`;
4594
- }
4595
- const response = await this.request(body ? "POST" : "GET", path, body);
4596
- const hits = response.hits?.hits || [];
4597
- const rows = hits.map((hit) => {
4598
- const row = { _id: hit._id };
4599
- this.flattenSource("", hit._source || {}, row);
4600
- return row;
4601
- });
4602
- return {
4603
- rows,
4604
- affectedRows: rows.length,
4605
- rowCount: rows.length,
4606
- columnNames: rows[0] ? Object.keys(rows[0]) : []
4607
- };
4608
- }
4609
- flattenSource(prefix, source, target) {
4610
- for (const [key, value] of Object.entries(source)) {
4611
- const fullKey = prefix ? `${prefix}.${key}` : key;
4612
- if (value !== null && typeof value === "object" && !Array.isArray(value)) {
4613
- this.flattenSource(fullKey, value, target);
4614
- } else {
4615
- target[fullKey] = value;
4616
- }
4617
- }
4618
- }
4619
- async listCollections(options) {
4620
- const rows = await this.request("GET", "/_cat/indices?format=json&h=index,docs.count&expand_wildcards=all");
4621
- return (Array.isArray(rows) ? rows : []).filter((row) => typeof row.index === "string" && row.index.length > 0).filter((row) => options?.includeSystem || !row.index.startsWith(".")).map((row) => {
4622
- const parsed = Number(row["docs.count"]);
4623
- return { name: row.index, documentCount: Number.isFinite(parsed) ? parsed : 0 };
4624
- });
4625
- }
4626
- async listTables(options) {
4627
- const collections = await this.listCollections(options);
4628
- return collections.map((c) => ({
4629
- name: c.name,
4630
- columns: [],
4631
- estimatedRowCount: c.documentCount,
4632
- tableType: "table"
4633
- }));
4634
- }
4635
- async getTableSchema(tableName, _options) {
4636
- const mapping = await this.request("GET", `/${encodeEsIndexExpression(tableName)}/_mapping`);
4637
- const indexMapping = mapping[tableName] || Object.values(mapping)[0];
4638
- if (!indexMapping) {
4639
- throw new Error(`Index not found: ${tableName}`);
4640
- }
4641
- const columns = [];
4642
- this.flattenProperties("", indexMapping.mappings?.properties || {}, columns);
4643
- return {
4644
- name: tableName,
4645
- columns,
4646
- tableType: "table"
4647
- };
4648
- }
4649
- flattenProperties(prefix, properties, columns) {
4650
- for (const [name, config] of Object.entries(properties)) {
4651
- const fullName = prefix ? `${prefix}.${name}` : name;
4652
- if (config.properties) {
4653
- this.flattenProperties(fullName, config.properties, columns);
4654
- } else {
4655
- columns.push({
4656
- name: fullName,
4657
- type: config.type || "object",
4658
- nullable: true
4659
- });
4660
- if (config.fields) {
4661
- for (const [fieldName, fieldConfig] of Object.entries(config.fields)) {
4662
- columns.push({
4663
- name: `${fullName}.${fieldName}`,
4664
- type: fieldConfig.type,
4665
- nullable: true
4666
- });
4667
- }
4668
- }
4669
- }
4670
- }
4671
- }
4672
- async testConnection() {
4673
- try {
4674
- await this.connect();
4675
- return true;
4676
- } catch {
4677
- return false;
4678
- }
4679
- }
4680
- async getServerVersion() {
4681
- const info = await this.request("GET", "/");
4682
- return info.version?.number ?? "unknown";
4683
- }
4684
- async insert(collection, data) {
4685
- const id = data._id || data.id;
4686
- const body = { ...data };
4687
- delete body._id;
4688
- delete body.id;
4689
- const encodedId = id ? encodeEsPathSegment(id) : "";
4690
- const response = await this.request("PUT", `/${encodeEsIndexExpression(collection)}/_doc/${encodedId}`, body);
4691
- return {
4692
- rows: [],
4693
- affectedRows: response.result === "created" || response.result === "updated" ? 1 : 0,
4694
- lastInsertId: response._id
4695
- };
4696
- }
4697
- async update(collection, filter, update) {
4698
- const id = filter._id || filter.id;
4699
- if (!id) {
4700
- throw new Error("Elasticsearch update requires _id or id in filter");
4701
- }
4702
- const response = await this.request("POST", `/${encodeEsIndexExpression(collection)}/_update/${encodeEsPathSegment(id)}`, {
4703
- doc: update
4704
- });
4705
- return {
4706
- rows: [],
4707
- affectedRows: response.result === "updated" || response.result === "noop" ? 1 : 0
4708
- };
4709
- }
4710
- async delete(collection, filter) {
4711
- const id = filter._id || filter.id;
4712
- if (!id) {
4713
- throw new Error("Elasticsearch delete requires _id or id in filter");
4714
- }
4715
- const response = await this.request("DELETE", `/${encodeEsIndexExpression(collection)}/_doc/${encodeEsPathSegment(id)}`);
4716
- return {
4717
- rows: [],
4718
- affectedRows: response.result === "deleted" ? 1 : 0
4719
- };
4720
- }
4721
- resolveBaseUrl() {
4722
- if (this.options.cloudId) {
4723
- return this.decodeCloudId(this.options.cloudId);
4724
- }
4725
- const nodes = this.options.nodes;
4726
- if (nodes && nodes.length > 0) {
4727
- return (nodes[0] ?? "").replace(/\/$/, "");
4728
- }
4729
- const protocol = this.options.protocol ?? "https";
4730
- const host = this.options.host || "localhost";
4731
- const port = this.options.port || 9200;
4732
- return `${protocol}://${host}:${port}`;
4733
- }
4734
- decodeCloudId(cloudId) {
4735
- try {
4736
- const parts = cloudId.split(":");
4737
- if (parts.length !== 2)
4738
- throw new Error("Invalid format");
4739
- const base64 = parts[1] ?? "";
4740
- const decoded = atob(base64);
4741
- const [hostAndPort = "", ...rest] = decoded.split("$");
4742
- if (hostAndPort.includes(".")) {
4743
- const [host = "", port] = hostAndPort.split(":");
4744
- return `https://${rest[0]}.${host}:${port || 443}`;
4745
- }
4746
- return `https://${rest[0]}.${hostAndPort}:443`;
4747
- } catch (error) {
4748
- throw new Error(`Invalid Cloud ID: ${error.message}`);
4749
- }
4750
- }
4751
- async getHeaders() {
4752
- const headers = {
4753
- "Content-Type": "application/json"
4754
- };
4755
- if (this.options.apiKey) {
4756
- headers["Authorization"] = `ApiKey ${this.options.apiKey}`;
4757
- } else if (this.options.user && this.options.password) {
4758
- const auth = btoa(`${this.options.user}:${this.options.password}`);
4759
- headers["Authorization"] = `Basic ${auth}`;
4760
- }
4761
- return headers;
4762
- }
4763
- getBaseUrl() {
4764
- return this.baseUrl;
4765
- }
4766
- async request(method, path, body, options) {
4767
- const url = `${this.baseUrl}${path.startsWith("/") ? "" : "/"}${path}`;
4768
- const headers = await this.getHeaders();
4769
- const controller = new AbortController;
4770
- const timeout = options?.timeout ?? this.options.timeout ?? 5000;
4771
- const timeoutId = setTimeout(() => controller.abort(), timeout);
4772
- try {
4773
- const response = await fetch(url, {
4774
- method,
4775
- headers,
4776
- body: body ? typeof body === "string" ? body : JSON.stringify(body) : undefined,
4777
- signal: controller.signal,
4778
- tls: {
4779
- rejectUnauthorized: this.options.rejectUnauthorized ?? true,
4780
- ca: this.options.caPath ? await Bun.file(this.options.caPath).text() : undefined
4781
- }
4782
- });
4783
- if (!response.ok) {
4784
- await this.handleErrorResponse(response);
4785
- }
4786
- return await response.json();
4787
- } catch (error) {
4788
- if (error.name === "AbortError") {
4789
- throw new ConnectionError("ETIMEDOUT", `Request timed out after ${timeout}ms`, [
4790
- "Increase timeout in config if the cluster is slow",
4791
- "Check if the network is congested"
4792
- ]);
4793
- }
4794
- if (error instanceof ConnectionError)
4795
- throw error;
4796
- const msg = error.message.toLowerCase();
4797
- if (msg.includes("connection refused") || msg.includes("econnrefused")) {
4798
- throw new ConnectionError("ECONNREFUSED", `Connection refused at ${this.baseUrl}`, [
4799
- "Check if Elasticsearch is running",
4800
- "Verify host and port"
4801
- ]);
4802
- }
4803
- if (msg.includes("not found") || msg.includes("enotfound")) {
4804
- throw new ConnectionError("ENOTFOUND", `Host not found: ${this.baseUrl}`, [
4805
- "Verify the hostname is correct",
4806
- "Check DNS settings"
4807
- ]);
4808
- }
4809
- throw error;
4810
- } finally {
4811
- clearTimeout(timeoutId);
4812
- }
4813
- }
4814
- async handleErrorResponse(response) {
4815
- let message = "";
4816
- try {
4817
- const errorBody = await response.json();
4818
- message = errorBody?.error?.reason || errorBody?.message || response.statusText;
4819
- } catch {
4820
- try {
4821
- message = await response.text();
4822
- } catch {
4823
- message = response.statusText;
4824
- }
4825
- }
4826
- const status = response.status;
4827
- if (status === 401) {
4828
- throw new ConnectionError("AUTH_FAILED", `Authentication failed: ${message}`, [
4829
- "Check your API key or username/password",
4830
- "Verify if the user has required roles"
4831
- ]);
4832
- }
4833
- if (status === 403) {
4834
- throw new ConnectionError("AUTH_FAILED", `Permission denied: ${message}`, [
4835
- "User does not have permission to perform this action"
4836
- ]);
4837
- }
4838
- throw new ConnectionError("UNKNOWN", `Elasticsearch error (${status}): ${message}`, [
4839
- "Check Elasticsearch logs for more details",
4840
- "Verify the request body is valid ES DSL"
4841
- ]);
4842
- }
4843
- }
4844
-
4845
- // src/utils/connection-timeout.ts
4846
- var _globalConnectionTimeout;
4847
- var _globalStatementTimeout;
4848
- function resolveConnectionTimeout(configured) {
4849
- return _globalConnectionTimeout ?? configured;
4850
- }
4851
- function resolveStatementTimeout(configured) {
4852
- return _globalStatementTimeout ?? configured;
4853
- }
4854
- function withResolvedTimeout(options) {
4855
- const timeout = resolveConnectionTimeout(options.timeout);
4856
- const statementTimeout = resolveStatementTimeout(options.statementTimeout);
4857
- if (timeout === options.timeout && statementTimeout === options.statementTimeout)
4858
- return options;
4859
- return { ...options, timeout, statementTimeout };
4860
- }
4861
-
4862
- // src/adapters/factory.ts
4863
- class AdapterFactory {
4864
- static createSqlAdapter(rawOptions) {
4865
- const options = withResolvedTimeout(rawOptions);
4866
- switch (options.system) {
4867
- case "postgresql":
4868
- return new PostgreSQLAdapter(options);
4869
- case "mysql":
4870
- case "mariadb":
4871
- return new MySQLAdapter(options);
4872
- default:
4873
- throw new Error(`createSqlAdapter requires a SQL system, got: ${options.system}`);
4874
- }
4875
- }
4876
- static createQueryableAdapter(rawOptions) {
4877
- const options = withResolvedTimeout(rawOptions);
4878
- switch (options.system) {
4879
- case "mongodb":
4880
- return new MongoDBAdapter(options);
4881
- case "redis":
4882
- return new RedisAdapter(options);
4883
- case "elasticsearch":
4884
- return new ElasticsearchAdapter(options);
4885
- default:
4886
- throw new Error(`createQueryableAdapter requires a non-SQL queryable system, got: ${options.system}`);
4887
- }
4888
- }
4889
- static createAdapter(options) {
4890
- switch (options.system) {
4891
- case "postgresql":
4892
- case "mysql":
4893
- case "mariadb":
4894
- return AdapterFactory.createSqlAdapter(options);
4895
- case "mongodb":
4896
- case "redis":
4897
- case "elasticsearch":
4898
- return AdapterFactory.createQueryableAdapter(options);
4899
- default:
4900
- throw new Error(`Unsupported database system: ${options.system}`);
4901
- }
4902
- }
4903
- static createMongoDBAdapter(options) {
4904
- if (options.system !== "mongodb") {
4905
- throw new Error("createMongoDBAdapter requires system: mongodb");
4906
- }
4907
- return AdapterFactory.createQueryableAdapter(options);
4908
- }
4909
- static createRedisAdapter(rawOptions, blacklistRules = [], maskRules = []) {
4910
- if (rawOptions.system !== "redis") {
4911
- throw new Error("createRedisAdapter requires system: redis");
4912
- }
4913
- const options = withResolvedTimeout(rawOptions);
4914
- const adapter = new RedisAdapter(options);
4915
- adapter.setBlacklistRules(blacklistRules);
4916
- adapter.setMaskRules(maskRules);
4917
- return adapter;
4918
- }
4919
- static createElasticsearchAdapter(options) {
4920
- if (options.system !== "elasticsearch") {
4921
- throw new Error("createElasticsearchAdapter requires system: elasticsearch");
4922
- }
4923
- return AdapterFactory.createQueryableAdapter(options);
4924
- }
4925
- }
4926
2694
  // src/utils/sql-lexical.ts
4927
2695
  var IDENTIFIER_CONTINUATION = /[A-Za-z0-9_$]|[\u0080-\uFFFF]/;
4928
2696
  var IDENTIFIER_START = /[A-Za-z_]|[\u0080-\uFFFF]/;
@@ -10349,10 +8117,26 @@ var MetadataSchema = exports_external.object({
10349
8117
  schemaLastUpdated: exports_external.string().datetime().optional(),
10350
8118
  schemaTableCount: exports_external.number().int().nonnegative().optional()
10351
8119
  }).optional().default({});
8120
+ var BLACKLIST_KEY_ALIASES = {
8121
+ indices: "tables",
8122
+ index: "tables",
8123
+ fields: "columns",
8124
+ keys: "tables"
8125
+ };
10352
8126
  var BlacklistConfigSchema = exports_external.object({
10353
8127
  tables: exports_external.array(exports_external.string()).default([]),
10354
8128
  columns: exports_external.record(exports_external.array(exports_external.string())).default({})
10355
- }).optional().default({ tables: [], columns: {} });
8129
+ }).passthrough().superRefine((value, ctx) => {
8130
+ for (const [wrong, right] of Object.entries(BLACKLIST_KEY_ALIASES)) {
8131
+ if (wrong in value) {
8132
+ ctx.addIssue({
8133
+ code: exports_external.ZodIssueCode.custom,
8134
+ path: [wrong],
8135
+ message: `blacklist.${wrong} is not a setting and would be silently ignored \u2014 did you mean blacklist.${right}?`
8136
+ });
8137
+ }
8138
+ }
8139
+ }).transform((value) => ({ tables: value.tables, columns: value.columns })).optional().default({ tables: [], columns: {} });
10356
8140
  var RedisMaskRuleSchema = exports_external.object({
10357
8141
  keyPattern: exports_external.string().min(1),
10358
8142
  fields: exports_external.array(exports_external.string()).optional()
@@ -10360,16 +8144,22 @@ var RedisMaskRuleSchema = exports_external.object({
10360
8144
  var RedisConfigSchema = exports_external.object({
10361
8145
  mask: exports_external.array(RedisMaskRuleSchema).default([])
10362
8146
  }).optional();
8147
+ var DEFAULT_AUDIT_ROTATION = { max_bytes: 10485760, max_entries: 1e4 };
10363
8148
  var AuditRotationConfigSchema = exports_external.object({
10364
- max_bytes: exports_external.number().int().positive().default(10485760),
10365
- max_entries: exports_external.number().int().positive().default(1000)
10366
- }).optional().default({ max_bytes: 10485760, max_entries: 1000 });
8149
+ max_bytes: exports_external.number().int().positive().default(DEFAULT_AUDIT_ROTATION.max_bytes),
8150
+ max_entries: exports_external.number().int().positive().default(DEFAULT_AUDIT_ROTATION.max_entries)
8151
+ }).optional().default({ ...DEFAULT_AUDIT_ROTATION });
10367
8152
  var AuditConfigSchema = exports_external.object({
10368
8153
  enabled: exports_external.boolean().default(true),
8154
+ strict: exports_external.boolean().default(false),
10369
8155
  rotation: AuditRotationConfigSchema
8156
+ }).refine((audit) => !(audit.strict && !audit.enabled), {
8157
+ message: "audit.strict requires audit.enabled: with audit off there is nothing to write, so " + "strict would refuse nothing. Enable audit, or turn strict off.",
8158
+ path: ["strict"]
10370
8159
  }).optional().default({
10371
8160
  enabled: true,
10372
- rotation: { max_bytes: 10485760, max_entries: 1000 }
8161
+ strict: false,
8162
+ rotation: { ...DEFAULT_AUDIT_ROTATION }
10373
8163
  });
10374
8164
  var DbcliConfigSchema = exports_external.object({
10375
8165
  connection: ConnectionConfigSchema,
@@ -10400,12 +8190,24 @@ var ElasticsearchNamedConnectionSchema = ElasticsearchConnectionConfigSchema.ext
10400
8190
  envFile: exports_external.string().optional(),
10401
8191
  environment: EnvironmentLabelSchema
10402
8192
  });
10403
- var NamedConnectionSchema = exports_external.union([
8193
+ var NamedConnectionUnion = exports_external.union([
10404
8194
  SqlNamedConnectionSchema,
10405
8195
  MongoDBNamedConnectionSchema,
10406
8196
  RedisNamedConnectionSchema,
10407
8197
  ElasticsearchNamedConnectionSchema
10408
8198
  ]);
8199
+ var NamedConnectionSchema = exports_external.preprocess((raw, ctx) => {
8200
+ if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) {
8201
+ if ("blacklist" in raw) {
8202
+ ctx.addIssue({
8203
+ code: exports_external.ZodIssueCode.custom,
8204
+ path: ["blacklist"],
8205
+ message: "blacklist is configured once at the top level, not per connection \u2014 a blacklist here " + "would be ignored. Move it to the top-level blacklist."
8206
+ });
8207
+ }
8208
+ }
8209
+ return raw;
8210
+ }, NamedConnectionUnion);
10409
8211
  var DbcliConfigV2Schema = exports_external.object({
10410
8212
  version: exports_external.literal(2),
10411
8213
  default: exports_external.string().min(1),
@@ -10875,7 +8677,7 @@ function migrateV1ToV2(v1) {
10875
8677
  schemas: {},
10876
8678
  metadata: v1.metadata ?? { version: "2.0" },
10877
8679
  blacklist: v1.blacklist ?? { tables: [], columns: {} },
10878
- audit: v1.audit ?? { enabled: true, rotation: { max_bytes: 10485760, max_entries: 1000 } }
8680
+ audit: v1.audit ?? { enabled: true, rotation: { ...DEFAULT_AUDIT_ROTATION } }
10879
8681
  };
10880
8682
  }
10881
8683
  function upsertConnection(config, input) {
@@ -11146,7 +8948,8 @@ var DEFAULT_CONFIG = {
11146
8948
  blacklist: { tables: [], columns: {} },
11147
8949
  audit: {
11148
8950
  enabled: true,
11149
- rotation: { max_bytes: 10485760, max_entries: 1000 }
8951
+ strict: false,
8952
+ rotation: { ...DEFAULT_AUDIT_ROTATION }
11150
8953
  }
11151
8954
  };
11152
8955
  function isEnvReference(value) {
@@ -11401,6 +9204,23 @@ DBCLI_PASSWORD=${password}
11401
9204
  }
11402
9205
  };
11403
9206
  // src/core/blacklist-manager.ts
9207
+ function normalizeBlacklistEntry(raw) {
9208
+ const trimmed = raw.trim();
9209
+ const quote = trimmed[0];
9210
+ if ((quote === '"' || quote === "`") && trimmed.length > 1 && trimmed.endsWith(quote)) {
9211
+ return trimmed.slice(1, -1).trim();
9212
+ }
9213
+ return trimmed;
9214
+ }
9215
+ function assertNotTableQualified(tableKey, column) {
9216
+ const dot = column.indexOf(".");
9217
+ if (dot <= 0)
9218
+ return;
9219
+ if (column.slice(0, dot).toLowerCase() !== tableKey.toLowerCase())
9220
+ return;
9221
+ throw new Error(`[BlacklistManager] blacklist.columns["${tableKey}"] entry ${JSON.stringify(column)} is ` + `qualified with its own table and would never match. Write it as ` + `${JSON.stringify(column.slice(dot + 1))}.`);
9222
+ }
9223
+
11404
9224
  class BlacklistManager {
11405
9225
  config;
11406
9226
  state;
@@ -11409,6 +9229,7 @@ class BlacklistManager {
11409
9229
  this.config = config;
11410
9230
  this.overrideEnabled = (overrideEnvValue ?? Bun.env.DBCLI_OVERRIDE_BLACKLIST ?? "") === "true";
11411
9231
  this.state = this.loadBlacklist();
9232
+ this.wildcardTables = [...this.state.tables].filter((entry) => /[*?[\\]/.test(entry));
11412
9233
  }
11413
9234
  loadBlacklist() {
11414
9235
  const tables = new Set;
@@ -11420,7 +9241,7 @@ class BlacklistManager {
11420
9241
  if (Array.isArray(blacklistConfig.tables)) {
11421
9242
  for (const tableName of blacklistConfig.tables) {
11422
9243
  if (typeof tableName === "string") {
11423
- tables.add(tableName.toLowerCase());
9244
+ tables.add(normalizeBlacklistEntry(tableName).toLowerCase());
11424
9245
  } else {
11425
9246
  console.warn(`[BlacklistManager] Invalid table name in blacklist config: ${JSON.stringify(tableName)}`);
11426
9247
  }
@@ -11441,13 +9262,15 @@ class BlacklistManager {
11441
9262
  const columnSet = new Set;
11442
9263
  for (const col of cols) {
11443
9264
  if (typeof col === "string") {
11444
- columnSet.add(col);
9265
+ const entry = normalizeBlacklistEntry(col);
9266
+ assertNotTableQualified(normalizeBlacklistEntry(tableName), entry);
9267
+ columnSet.add(entry);
11445
9268
  } else {
11446
9269
  console.warn(`[BlacklistManager] Invalid column name in blacklist.columns["${tableName}"]: ${JSON.stringify(col)}`);
11447
9270
  }
11448
9271
  }
11449
9272
  if (columnSet.size > 0) {
11450
- columns.set(tableName.toLowerCase(), columnSet);
9273
+ columns.set(normalizeBlacklistEntry(tableName).toLowerCase(), columnSet);
11451
9274
  }
11452
9275
  }
11453
9276
  } else if (blacklistConfig.columns !== undefined) {
@@ -11456,17 +9279,47 @@ class BlacklistManager {
11456
9279
  return { tables, columns };
11457
9280
  }
11458
9281
  isTableBlacklisted(tableName) {
11459
- return this.state.tables.has(tableName.toLowerCase());
9282
+ const name = tableName.toLowerCase();
9283
+ if (this.state.tables.has(name))
9284
+ return true;
9285
+ for (const pattern of this.wildcardTables) {
9286
+ if (globMatches(pattern, name))
9287
+ return true;
9288
+ }
9289
+ return false;
9290
+ }
9291
+ wildcardTables;
9292
+ columnRulesFor(tableName) {
9293
+ const key = normalizeBlacklistEntry(tableName).toLowerCase();
9294
+ const direct = this.state.columns.get(key);
9295
+ if (direct)
9296
+ return direct;
9297
+ const dot = key.lastIndexOf(".");
9298
+ if (dot > 0) {
9299
+ const bare = this.state.columns.get(key.slice(dot + 1));
9300
+ if (bare)
9301
+ return bare;
9302
+ }
9303
+ let merged;
9304
+ for (const [ruleKey, columns] of this.state.columns) {
9305
+ const ruleDot = ruleKey.lastIndexOf(".");
9306
+ if (ruleDot <= 0 || ruleKey.slice(ruleDot + 1) !== key)
9307
+ continue;
9308
+ merged ??= new Set;
9309
+ for (const column of columns)
9310
+ merged.add(column);
9311
+ }
9312
+ return merged;
11460
9313
  }
11461
9314
  isColumnBlacklisted(tableName, columnName) {
11462
- const columnSet = this.state.columns.get(tableName.toLowerCase());
9315
+ const columnSet = this.columnRulesFor(tableName);
11463
9316
  if (!columnSet) {
11464
9317
  return false;
11465
9318
  }
11466
- return columnSet.has(columnName);
9319
+ return columnSet.has(normalizeBlacklistEntry(columnName).toLowerCase());
11467
9320
  }
11468
9321
  getBlacklistedColumns(tableName) {
11469
- const columnSet = this.state.columns.get(tableName.toLowerCase());
9322
+ const columnSet = this.columnRulesFor(tableName);
11470
9323
  if (!columnSet) {
11471
9324
  return [];
11472
9325
  }
@@ -11530,11 +9383,37 @@ function expandIndexTargets(target) {
11530
9383
  function matchesIndexGlob(pattern, name) {
11531
9384
  const normalized = pattern.toLowerCase() === "_all" ? "*" : pattern.toLowerCase();
11532
9385
  try {
11533
- return globToRegex(normalized).test(name.toLowerCase());
9386
+ return globMatches(normalized, name.toLowerCase());
11534
9387
  } catch {
11535
9388
  return true;
11536
9389
  }
11537
9390
  }
9391
+ function reachesByConvention(name, entry) {
9392
+ const lower = name.toLowerCase();
9393
+ const target = entry.toLowerCase();
9394
+ return new RegExp(`^\\.ds-${escapeRegExp(target)}-`).test(lower) || new RegExp(`^${escapeRegExp(target)}-\\d+$`).test(lower);
9395
+ }
9396
+ function escapeRegExp(text) {
9397
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9398
+ }
9399
+ function expandBlacklistEntries(blacklisted) {
9400
+ const names = [];
9401
+ const patterns = [];
9402
+ for (const entry of blacklisted) {
9403
+ const { concrete, wildcards } = expandIndexTargets(entry);
9404
+ names.push(...concrete);
9405
+ patterns.push(...wildcards);
9406
+ }
9407
+ return { names, patterns };
9408
+ }
9409
+ function indexExpressionReaches(expression, blacklisted) {
9410
+ if (blacklisted.length === 0)
9411
+ return false;
9412
+ const { concrete, wildcards } = expandIndexTargets(expression);
9413
+ const entries = expandBlacklistEntries(blacklisted);
9414
+ const reachesName = (name) => entries.names.some((entry) => entry.toLowerCase() === name.toLowerCase() || reachesByConvention(name, entry)) || entries.patterns.some((pattern) => matchesIndexGlob(pattern, name));
9415
+ return concrete.some(reachesName) || wildcards.some((pattern) => entries.names.some((entry) => matchesIndexGlob(pattern, entry)) || entries.patterns.length > 0);
9416
+ }
11538
9417
 
11539
9418
  // src/core/blacklist-validator.ts
11540
9419
  function flattenArrayRow(row) {
@@ -11559,6 +9438,17 @@ function dedupe2(values) {
11559
9438
  }
11560
9439
  return result;
11561
9440
  }
9441
+ function compileGlobRules(rules, fold, table, operation) {
9442
+ const globbed = rules.filter((rule) => /[*?[\\]/.test(rule));
9443
+ if (globbed.length === 0)
9444
+ return [];
9445
+ const { patterns, rejected } = compilePatterns(globbed.map(fold));
9446
+ if (rejected.length > 0) {
9447
+ const detail = rejected.map((r) => `'${r.raw}' (${r.reason})`).join(", ");
9448
+ throw new BlacklistError(`blacklist.columns for '${table}' has entries this matcher cannot read: ${detail}`, table, operation);
9449
+ }
9450
+ return patterns;
9451
+ }
11562
9452
 
11563
9453
  class BlacklistValidator {
11564
9454
  manager;
@@ -11592,26 +9482,23 @@ class BlacklistValidator {
11592
9482
  throw new BlacklistError(message, blocked[0], operation);
11593
9483
  }
11594
9484
  checkIndexBlacklist(operation, target) {
11595
- const { concrete, wildcards } = expandIndexTargets(target);
11596
- this.checkTablesBlacklist(operation, concrete);
11597
- if (wildcards.length === 0 || this.manager.canOverrideBlacklist())
9485
+ if (this.manager.canOverrideBlacklist())
11598
9486
  return;
11599
9487
  const blacklisted = Array.from(this.manager.getState().tables);
11600
9488
  if (blacklisted.length === 0)
11601
9489
  return;
11602
- const reachable = wildcards.filter((pattern) => blacklisted.some((entry) => matchesIndexGlob(pattern, entry)));
11603
- if (reachable.length === 0)
9490
+ if (!indexExpressionReaches(target, blacklisted))
11604
9491
  return;
11605
9492
  const message = t_vars("errors.table_blacklisted", {
11606
- table: reachable.join(", "),
9493
+ table: target,
11607
9494
  operation
11608
9495
  });
11609
- throw new BlacklistError(message, reachable[0], operation);
9496
+ throw new BlacklistError(message, target, operation);
11610
9497
  }
11611
9498
  filterColumnsForIndexExpression(target, rows, columnList) {
11612
9499
  const { concrete, wildcards } = expandIndexTargets(target);
11613
9500
  const ruleKeys = Array.from(this.manager.getState().columns.keys());
11614
- const reachable = ruleKeys.filter((key) => wildcards.some((pattern) => matchesIndexGlob(pattern, key)));
9501
+ const reachable = ruleKeys.filter((key) => wildcards.some((pattern) => matchesIndexGlob(pattern, key)) || concrete.some((name) => indexExpressionReaches(name, [key])));
11615
9502
  return this.filterColumnsForTables([...concrete, ...reachable], rows, columnList);
11616
9503
  }
11617
9504
  checkColumnBlacklistOnWrite(tableName, fields, operation = "WRITE") {
@@ -11619,7 +9506,27 @@ class BlacklistValidator {
11619
9506
  if (blacklisted.length === 0 || fields.length === 0) {
11620
9507
  return;
11621
9508
  }
11622
- const conflicts = fields.filter((f) => blacklisted.includes(f));
9509
+ const protectedPaths = new Set(blacklisted.map((name) => name.toLowerCase()));
9510
+ const globs = compileGlobRules(blacklisted, (value) => value.toLowerCase(), tableName, operation);
9511
+ const conflicts = fields.filter((field) => {
9512
+ const name = field.toLowerCase();
9513
+ if (protectedPaths.has(name))
9514
+ return true;
9515
+ if (matchAny(name, globs))
9516
+ return true;
9517
+ let dot = name.indexOf(".");
9518
+ while (dot >= 0) {
9519
+ if (dot > 0) {
9520
+ const ancestor = name.slice(0, dot);
9521
+ if (protectedPaths.has(ancestor))
9522
+ return true;
9523
+ if (matchAny(ancestor, globs))
9524
+ return true;
9525
+ }
9526
+ dot = name.indexOf(".", dot + 1);
9527
+ }
9528
+ return false;
9529
+ });
11623
9530
  if (conflicts.length === 0) {
11624
9531
  return;
11625
9532
  }
@@ -11670,11 +9577,20 @@ class BlacklistValidator {
11670
9577
  }
11671
9578
  collect(row);
11672
9579
  }
11673
- const protectedPaths = new Set(blacklistedColumns);
9580
+ const foldHead = (path) => {
9581
+ const dot = path.indexOf(".");
9582
+ return dot < 0 ? path.toLowerCase() : path.slice(0, dot).toLowerCase() + path.slice(dot);
9583
+ };
9584
+ const protectedPaths = new Set(blacklistedColumns.map(foldHead));
9585
+ const globRules = compileGlobRules(blacklistedColumns, foldHead, tables[0] ?? "unknown", "SELECT");
9586
+ const presentByFolded = new Map;
9587
+ for (const column of presentColumns)
9588
+ presentByFolded.set(foldHead(column), column);
11674
9589
  const omitted = new Set;
11675
9590
  for (const path of blacklistedColumns) {
11676
- if (presentColumns.has(path)) {
11677
- omitted.add(path);
9591
+ const present = presentByFolded.get(foldHead(path));
9592
+ if (present !== undefined) {
9593
+ omitted.add(present);
11678
9594
  continue;
11679
9595
  }
11680
9596
  const dot = path.indexOf(".");
@@ -11689,13 +9605,30 @@ class BlacklistValidator {
11689
9605
  for (const column of presentColumns) {
11690
9606
  let dot = column.indexOf(".");
11691
9607
  while (dot >= 0) {
11692
- if (dot > 0 && protectedPaths.has(column.slice(0, dot))) {
9608
+ if (dot > 0 && protectedPaths.has(column.slice(0, dot).toLowerCase())) {
11693
9609
  omitted.add(column);
11694
9610
  break;
11695
9611
  }
11696
9612
  dot = column.indexOf(".", dot + 1);
11697
9613
  }
11698
9614
  }
9615
+ if (globRules.length > 0) {
9616
+ for (const column of presentColumns) {
9617
+ const folded = foldHead(column);
9618
+ if (matchAny(folded, globRules)) {
9619
+ omitted.add(column);
9620
+ continue;
9621
+ }
9622
+ let dot = folded.indexOf(".");
9623
+ while (dot >= 0) {
9624
+ if (dot > 0 && matchAny(folded.slice(0, dot), globRules)) {
9625
+ omitted.add(column);
9626
+ break;
9627
+ }
9628
+ dot = folded.indexOf(".", dot + 1);
9629
+ }
9630
+ }
9631
+ }
11699
9632
  const omittedColumns = Array.from(omitted);
11700
9633
  if (omittedColumns.length === 0) {
11701
9634
  return { filteredRows: rows, omittedColumns: [] };
@@ -11742,6 +9675,5 @@ export {
11742
9675
  ConnectionError,
11743
9676
  BlacklistValidator,
11744
9677
  BlacklistManager,
11745
- BlacklistError,
11746
- AdapterFactory
9678
+ BlacklistError
11747
9679
  };