@carllee1983/dbcli 2.1.0 → 4.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,1105 +2011,22 @@ 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;
2487
- }
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");
2515
- }
2516
- return encodeURIComponent(value);
2517
- }
2518
- function encodeEsIndexExpression(expression) {
2519
- if (expression.length === 0) {
2520
- throw new Error("Elasticsearch index expression must not be empty");
2521
- }
2522
- return expression.split(",").map((part) => encodeEsPathSegment(part)).join(",");
2523
- }
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
2531
- };
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
- }
2744
- }
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
- });
2830
- }
2831
- }
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
- });
2893
- }
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
- });
2911
- }
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
- });
2944
- }
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
- }
2985
- }
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
- }
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;
3078
- }
3079
- return;
3080
- }
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}`);
3174
- }
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}`);
3178
- }
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
- });
3185
- }
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}`);
3199
- }
3200
- const payload = await response.json();
3201
- if (payload.Status !== 0 || !payload.Answer?.length) {
3202
- return {};
3203
- }
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));
3278
- }
3279
- }
3280
- async disconnect() {
3281
- if (this.client) {
3282
- await this.client.close();
3283
- this.client = null;
3284
- }
3285
- }
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();
3305
- } 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();
3309
- }
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();
3348
- }
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
- }
3444
- }
3445
- }
2027
+ // src/core/mongo/request-fields.ts
2028
+ var WHOLE_DOCUMENT_VARIABLES = new Set(["ROOT", "CURRENT"]);
2029
+ var RESHAPES_DOCUMENT = new Set(["$objectToArray", "$replaceRoot", "$replaceWith"]);
3446
2030
 
3447
2031
  // src/adapters/redis/command-metadata.ts
3448
2032
  var tbl = (s) => s;
@@ -3450,465 +2034,439 @@ var REDIS_COMMAND_TABLE = Object.freeze({
3450
2034
  PING: tbl({
3451
2035
  readOnly: true,
3452
2036
  sizeGuard: { kind: "unbounded" },
3453
- keyArity: { kind: "no-key" },
3454
- permissionTier: "query-only"
2037
+ keyArity: { kind: "no-key" }
3455
2038
  }),
3456
2039
  INFO: tbl({
3457
2040
  readOnly: true,
3458
2041
  sizeGuard: { kind: "unbounded" },
3459
- keyArity: { kind: "no-key" },
3460
- permissionTier: "query-only"
2042
+ keyArity: { kind: "no-key" }
3461
2043
  }),
3462
2044
  DBSIZE: tbl({
3463
2045
  readOnly: true,
3464
2046
  sizeGuard: { kind: "unbounded" },
3465
- keyArity: { kind: "no-key" },
3466
- permissionTier: "query-only"
2047
+ keyArity: { kind: "no-key" }
3467
2048
  }),
3468
2049
  TYPE: tbl({
3469
2050
  readOnly: true,
3470
2051
  sizeGuard: { kind: "unbounded" },
3471
- keyArity: { kind: "single", argIndex: 0 },
3472
- permissionTier: "query-only"
2052
+ keyArity: { kind: "single", argIndex: 0 }
3473
2053
  }),
3474
2054
  EXISTS: tbl({
3475
2055
  readOnly: true,
3476
2056
  sizeGuard: { kind: "unbounded" },
3477
- keyArity: { kind: "multi-variable", startIndex: 0, step: 1 },
3478
- permissionTier: "query-only"
2057
+ keyArity: { kind: "multi-variable", startIndex: 0, step: 1 }
3479
2058
  }),
3480
2059
  TTL: tbl({
3481
2060
  readOnly: true,
3482
2061
  sizeGuard: { kind: "unbounded" },
3483
- keyArity: { kind: "single", argIndex: 0 },
3484
- permissionTier: "query-only"
2062
+ keyArity: { kind: "single", argIndex: 0 }
3485
2063
  }),
3486
2064
  GET: tbl({
3487
2065
  readOnly: true,
3488
2066
  sizeGuard: { kind: "unbounded" },
3489
- keyArity: { kind: "single", argIndex: 0 },
3490
- permissionTier: "query-only"
2067
+ keyArity: { kind: "single", argIndex: 0 }
3491
2068
  }),
3492
2069
  MGET: tbl({
3493
2070
  readOnly: true,
3494
2071
  sizeGuard: { kind: "unbounded" },
3495
- keyArity: { kind: "multi-variable", startIndex: 0, step: 1 },
3496
- permissionTier: "query-only"
2072
+ keyArity: { kind: "multi-variable", startIndex: 0, step: 1 }
3497
2073
  }),
3498
2074
  STRLEN: tbl({
3499
2075
  readOnly: true,
3500
2076
  sizeGuard: { kind: "unbounded" },
3501
- keyArity: { kind: "single", argIndex: 0 },
3502
- permissionTier: "query-only"
2077
+ keyArity: { kind: "single", argIndex: 0 }
3503
2078
  }),
3504
2079
  HGET: tbl({
3505
2080
  readOnly: true,
3506
2081
  sizeGuard: { kind: "unbounded" },
3507
- keyArity: { kind: "single", argIndex: 0 },
3508
- permissionTier: "query-only"
2082
+ keyArity: { kind: "single", argIndex: 0 }
3509
2083
  }),
3510
2084
  HEXISTS: tbl({
3511
2085
  readOnly: true,
3512
2086
  sizeGuard: { kind: "unbounded" },
3513
- keyArity: { kind: "single", argIndex: 0 },
3514
- permissionTier: "query-only"
2087
+ keyArity: { kind: "single", argIndex: 0 }
3515
2088
  }),
3516
2089
  HLEN: tbl({
3517
2090
  readOnly: true,
3518
2091
  sizeGuard: { kind: "unbounded" },
3519
- keyArity: { kind: "single", argIndex: 0 },
3520
- permissionTier: "query-only"
2092
+ keyArity: { kind: "single", argIndex: 0 }
3521
2093
  }),
3522
2094
  HMGET: tbl({
3523
2095
  readOnly: true,
3524
2096
  sizeGuard: { kind: "unbounded" },
3525
- keyArity: { kind: "single", argIndex: 0 },
3526
- permissionTier: "query-only"
2097
+ keyArity: { kind: "single", argIndex: 0 }
3527
2098
  }),
3528
2099
  LLEN: tbl({
3529
2100
  readOnly: true,
3530
2101
  sizeGuard: { kind: "unbounded" },
3531
- keyArity: { kind: "single", argIndex: 0 },
3532
- permissionTier: "query-only"
2102
+ keyArity: { kind: "single", argIndex: 0 }
3533
2103
  }),
3534
2104
  LINDEX: tbl({
3535
2105
  readOnly: true,
3536
2106
  sizeGuard: { kind: "unbounded" },
3537
- keyArity: { kind: "single", argIndex: 0 },
3538
- permissionTier: "query-only"
2107
+ keyArity: { kind: "single", argIndex: 0 }
3539
2108
  }),
3540
2109
  SCARD: tbl({
3541
2110
  readOnly: true,
3542
2111
  sizeGuard: { kind: "unbounded" },
3543
- keyArity: { kind: "single", argIndex: 0 },
3544
- permissionTier: "query-only"
2112
+ keyArity: { kind: "single", argIndex: 0 }
3545
2113
  }),
3546
2114
  SISMEMBER: tbl({
3547
2115
  readOnly: true,
3548
2116
  sizeGuard: { kind: "unbounded" },
3549
- keyArity: { kind: "single", argIndex: 0 },
3550
- permissionTier: "query-only"
2117
+ keyArity: { kind: "single", argIndex: 0 }
3551
2118
  }),
3552
2119
  ZCARD: tbl({
3553
2120
  readOnly: true,
3554
2121
  sizeGuard: { kind: "unbounded" },
3555
- keyArity: { kind: "single", argIndex: 0 },
3556
- permissionTier: "query-only"
2122
+ keyArity: { kind: "single", argIndex: 0 }
3557
2123
  }),
3558
2124
  ZSCORE: tbl({
3559
2125
  readOnly: true,
3560
2126
  sizeGuard: { kind: "unbounded" },
3561
- keyArity: { kind: "single", argIndex: 0 },
3562
- permissionTier: "query-only"
2127
+ keyArity: { kind: "single", argIndex: 0 }
3563
2128
  }),
3564
2129
  XLEN: tbl({
3565
2130
  readOnly: true,
3566
2131
  sizeGuard: { kind: "unbounded" },
3567
- keyArity: { kind: "single", argIndex: 0 },
3568
- permissionTier: "query-only"
2132
+ keyArity: { kind: "single", argIndex: 0 }
3569
2133
  }),
3570
2134
  SCAN: tbl({
3571
2135
  readOnly: true,
3572
2136
  sizeGuard: { kind: "rewrite-count", argIndex: 0 },
3573
- keyArity: { kind: "no-key" },
3574
- permissionTier: "query-only"
2137
+ keyArity: { kind: "pattern-after-token", token: "MATCH" }
3575
2138
  }),
3576
2139
  HSCAN: tbl({
3577
2140
  readOnly: true,
3578
2141
  sizeGuard: { kind: "rewrite-count", argIndex: 1 },
3579
- keyArity: { kind: "single", argIndex: 0 },
3580
- permissionTier: "query-only"
2142
+ keyArity: { kind: "single", argIndex: 0 }
3581
2143
  }),
3582
2144
  SSCAN: tbl({
3583
2145
  readOnly: true,
3584
2146
  sizeGuard: { kind: "rewrite-count", argIndex: 1 },
3585
- keyArity: { kind: "single", argIndex: 0 },
3586
- permissionTier: "query-only"
2147
+ keyArity: { kind: "single", argIndex: 0 }
3587
2148
  }),
3588
2149
  ZSCAN: tbl({
3589
2150
  readOnly: true,
3590
2151
  sizeGuard: { kind: "rewrite-count", argIndex: 1 },
3591
- keyArity: { kind: "single", argIndex: 0 },
3592
- permissionTier: "query-only"
2152
+ keyArity: { kind: "single", argIndex: 0 }
3593
2153
  }),
3594
2154
  LRANGE: tbl({
3595
2155
  readOnly: true,
3596
2156
  sizeGuard: { kind: "rewrite-stop", argIndex: 2 },
3597
- keyArity: { kind: "single", argIndex: 0 },
3598
- permissionTier: "query-only"
2157
+ keyArity: { kind: "single", argIndex: 0 }
3599
2158
  }),
3600
2159
  ZRANGE: tbl({
3601
2160
  readOnly: true,
3602
2161
  sizeGuard: { kind: "rewrite-stop", argIndex: 2 },
3603
- keyArity: { kind: "single", argIndex: 0 },
3604
- permissionTier: "query-only"
2162
+ keyArity: { kind: "single", argIndex: 0 }
3605
2163
  }),
3606
2164
  ZREVRANGE: tbl({
3607
2165
  readOnly: true,
3608
2166
  sizeGuard: { kind: "rewrite-stop", argIndex: 2 },
3609
- keyArity: { kind: "single", argIndex: 0 },
3610
- permissionTier: "query-only"
2167
+ keyArity: { kind: "single", argIndex: 0 }
3611
2168
  }),
3612
2169
  ZRANGEBYSCORE: tbl({
3613
2170
  readOnly: true,
3614
2171
  sizeGuard: { kind: "rewrite-limit", argIndex: 3 },
3615
- keyArity: { kind: "single", argIndex: 0 },
3616
- permissionTier: "query-only"
2172
+ keyArity: { kind: "single", argIndex: 0 }
3617
2173
  }),
3618
2174
  HGETALL: tbl({
3619
2175
  readOnly: true,
3620
2176
  sizeGuard: { kind: "truncate" },
3621
- keyArity: { kind: "single", argIndex: 0 },
3622
- permissionTier: "query-only"
2177
+ keyArity: { kind: "single", argIndex: 0 }
3623
2178
  }),
3624
2179
  HKEYS: tbl({
3625
2180
  readOnly: true,
3626
2181
  sizeGuard: { kind: "truncate" },
3627
- keyArity: { kind: "single", argIndex: 0 },
3628
- permissionTier: "query-only"
2182
+ keyArity: { kind: "single", argIndex: 0 }
3629
2183
  }),
3630
2184
  HVALS: tbl({
3631
2185
  readOnly: true,
3632
2186
  sizeGuard: { kind: "truncate" },
3633
- keyArity: { kind: "single", argIndex: 0 },
3634
- permissionTier: "query-only"
2187
+ keyArity: { kind: "single", argIndex: 0 }
3635
2188
  }),
3636
2189
  SMEMBERS: tbl({
3637
2190
  readOnly: true,
3638
2191
  sizeGuard: { kind: "truncate" },
3639
- keyArity: { kind: "single", argIndex: 0 },
3640
- permissionTier: "query-only"
2192
+ keyArity: { kind: "single", argIndex: 0 }
3641
2193
  }),
3642
2194
  KEYS: tbl({
3643
2195
  readOnly: true,
3644
2196
  sizeGuard: { kind: "truncate" },
3645
- keyArity: { kind: "pattern", argIndex: 0 },
3646
- permissionTier: "query-only"
2197
+ keyArity: { kind: "pattern", argIndex: 0 }
3647
2198
  }),
3648
2199
  SET: tbl({
3649
2200
  readOnly: false,
3650
2201
  sizeGuard: { kind: "unbounded" },
3651
- keyArity: { kind: "single", argIndex: 0 },
3652
- permissionTier: "read-write"
2202
+ keyArity: { kind: "single", argIndex: 0 }
3653
2203
  }),
3654
2204
  SETEX: tbl({
3655
2205
  readOnly: false,
3656
2206
  sizeGuard: { kind: "unbounded" },
3657
- keyArity: { kind: "single", argIndex: 0 },
3658
- permissionTier: "read-write"
2207
+ keyArity: { kind: "single", argIndex: 0 }
3659
2208
  }),
3660
2209
  MSET: tbl({
3661
2210
  readOnly: false,
3662
2211
  sizeGuard: { kind: "unbounded" },
3663
- keyArity: { kind: "multi-variable", startIndex: 0, step: 2 },
3664
- permissionTier: "read-write"
2212
+ keyArity: { kind: "multi-variable", startIndex: 0, step: 2 }
3665
2213
  }),
3666
2214
  HSET: tbl({
3667
2215
  readOnly: false,
3668
2216
  sizeGuard: { kind: "unbounded" },
3669
- keyArity: { kind: "single", argIndex: 0 },
3670
- permissionTier: "read-write"
2217
+ keyArity: { kind: "single", argIndex: 0 }
3671
2218
  }),
3672
2219
  HMSET: tbl({
3673
2220
  readOnly: false,
3674
2221
  sizeGuard: { kind: "unbounded" },
3675
- keyArity: { kind: "single", argIndex: 0 },
3676
- permissionTier: "read-write"
2222
+ keyArity: { kind: "single", argIndex: 0 }
3677
2223
  }),
3678
2224
  HDEL: tbl({
3679
2225
  readOnly: false,
3680
2226
  sizeGuard: { kind: "unbounded" },
3681
- keyArity: { kind: "single", argIndex: 0 },
3682
- permissionTier: "read-write"
2227
+ keyArity: { kind: "single", argIndex: 0 }
3683
2228
  }),
3684
2229
  RPUSH: tbl({
3685
2230
  readOnly: false,
3686
2231
  sizeGuard: { kind: "unbounded" },
3687
- keyArity: { kind: "single", argIndex: 0 },
3688
- permissionTier: "read-write"
2232
+ keyArity: { kind: "single", argIndex: 0 }
3689
2233
  }),
3690
2234
  LPUSH: tbl({
3691
2235
  readOnly: false,
3692
2236
  sizeGuard: { kind: "unbounded" },
3693
- keyArity: { kind: "single", argIndex: 0 },
3694
- permissionTier: "read-write"
2237
+ keyArity: { kind: "single", argIndex: 0 }
3695
2238
  }),
3696
2239
  LREM: tbl({
3697
2240
  readOnly: false,
3698
2241
  sizeGuard: { kind: "unbounded" },
3699
- keyArity: { kind: "single", argIndex: 0 },
3700
- permissionTier: "read-write"
2242
+ keyArity: { kind: "single", argIndex: 0 }
3701
2243
  }),
3702
2244
  SADD: tbl({
3703
2245
  readOnly: false,
3704
2246
  sizeGuard: { kind: "unbounded" },
3705
- keyArity: { kind: "single", argIndex: 0 },
3706
- permissionTier: "read-write"
2247
+ keyArity: { kind: "single", argIndex: 0 }
3707
2248
  }),
3708
2249
  SREM: tbl({
3709
2250
  readOnly: false,
3710
2251
  sizeGuard: { kind: "unbounded" },
3711
- keyArity: { kind: "single", argIndex: 0 },
3712
- permissionTier: "read-write"
2252
+ keyArity: { kind: "single", argIndex: 0 }
2253
+ }),
2254
+ ZADD: tbl({
2255
+ readOnly: false,
2256
+ sizeGuard: { kind: "unbounded" },
2257
+ keyArity: { kind: "single", argIndex: 0 }
2258
+ }),
2259
+ ZREM: tbl({
2260
+ readOnly: false,
2261
+ sizeGuard: { kind: "unbounded" },
2262
+ keyArity: { kind: "single", argIndex: 0 }
2263
+ }),
2264
+ DEL: tbl({
2265
+ readOnly: false,
2266
+ sizeGuard: { kind: "unbounded" },
2267
+ keyArity: { kind: "multi-variable", startIndex: 0, step: 1 }
2268
+ }),
2269
+ UNLINK: tbl({
2270
+ readOnly: false,
2271
+ sizeGuard: { kind: "unbounded" },
2272
+ keyArity: { kind: "multi-variable", startIndex: 0, step: 1 }
2273
+ }),
2274
+ EXPIRE: tbl({
2275
+ readOnly: false,
2276
+ sizeGuard: { kind: "unbounded" },
2277
+ keyArity: { kind: "single", argIndex: 0 }
2278
+ }),
2279
+ PERSIST: tbl({
2280
+ readOnly: false,
2281
+ sizeGuard: { kind: "unbounded" },
2282
+ keyArity: { kind: "single", argIndex: 0 }
2283
+ }),
2284
+ RENAME: tbl({
2285
+ readOnly: false,
2286
+ sizeGuard: { kind: "unbounded" },
2287
+ keyArity: { kind: "multi-fixed", argIndices: [0, 1] }
2288
+ }),
2289
+ FLUSHDB: tbl({
2290
+ readOnly: false,
2291
+ sizeGuard: { kind: "reject", reason: "FLUSHDB destroys data" },
2292
+ keyArity: { kind: "no-key" }
2293
+ }),
2294
+ FLUSHALL: tbl({
2295
+ readOnly: false,
2296
+ sizeGuard: { kind: "reject", reason: "FLUSHALL destroys data" },
2297
+ keyArity: { kind: "no-key" }
2298
+ }),
2299
+ SHUTDOWN: tbl({
2300
+ readOnly: false,
2301
+ sizeGuard: { kind: "reject", reason: "SHUTDOWN stops the server" },
2302
+ keyArity: { kind: "no-key" }
2303
+ }),
2304
+ DEBUG: tbl({
2305
+ readOnly: false,
2306
+ sizeGuard: { kind: "reject", reason: "DEBUG is unsafe" },
2307
+ keyArity: { kind: "no-key" }
2308
+ }),
2309
+ PTTL: tbl({
2310
+ readOnly: true,
2311
+ sizeGuard: { kind: "unbounded" },
2312
+ keyArity: { kind: "single", argIndex: 0 }
2313
+ }),
2314
+ SETNX: tbl({
2315
+ readOnly: false,
2316
+ sizeGuard: { kind: "unbounded" },
2317
+ keyArity: { kind: "single", argIndex: 0 }
2318
+ }),
2319
+ PSETEX: tbl({
2320
+ readOnly: false,
2321
+ sizeGuard: { kind: "unbounded" },
2322
+ keyArity: { kind: "single", argIndex: 0 }
2323
+ }),
2324
+ APPEND: tbl({
2325
+ readOnly: false,
2326
+ sizeGuard: { kind: "unbounded" },
2327
+ keyArity: { kind: "single", argIndex: 0 }
2328
+ }),
2329
+ INCR: tbl({
2330
+ readOnly: false,
2331
+ sizeGuard: { kind: "unbounded" },
2332
+ keyArity: { kind: "single", argIndex: 0 }
2333
+ }),
2334
+ INCRBY: tbl({
2335
+ readOnly: false,
2336
+ sizeGuard: { kind: "unbounded" },
2337
+ keyArity: { kind: "single", argIndex: 0 }
2338
+ }),
2339
+ DECR: tbl({
2340
+ readOnly: false,
2341
+ sizeGuard: { kind: "unbounded" },
2342
+ keyArity: { kind: "single", argIndex: 0 }
2343
+ }),
2344
+ DECRBY: tbl({
2345
+ readOnly: false,
2346
+ sizeGuard: { kind: "unbounded" },
2347
+ keyArity: { kind: "single", argIndex: 0 }
2348
+ }),
2349
+ HSETNX: tbl({
2350
+ readOnly: false,
2351
+ sizeGuard: { kind: "unbounded" },
2352
+ keyArity: { kind: "single", argIndex: 0 }
2353
+ }),
2354
+ HINCRBY: tbl({
2355
+ readOnly: false,
2356
+ sizeGuard: { kind: "unbounded" },
2357
+ keyArity: { kind: "single", argIndex: 0 }
2358
+ }),
2359
+ LPOP: tbl({
2360
+ readOnly: false,
2361
+ sizeGuard: { kind: "unbounded" },
2362
+ keyArity: { kind: "single", argIndex: 0 }
2363
+ }),
2364
+ RPOP: tbl({
2365
+ readOnly: false,
2366
+ sizeGuard: { kind: "unbounded" },
2367
+ keyArity: { kind: "single", argIndex: 0 }
2368
+ }),
2369
+ LSET: tbl({
2370
+ readOnly: false,
2371
+ sizeGuard: { kind: "unbounded" },
2372
+ keyArity: { kind: "single", argIndex: 0 }
2373
+ }),
2374
+ XADD: tbl({
2375
+ readOnly: false,
2376
+ sizeGuard: { kind: "unbounded" },
2377
+ keyArity: { kind: "single", argIndex: 0 }
2378
+ }),
2379
+ XDEL: tbl({
2380
+ readOnly: false,
2381
+ sizeGuard: { kind: "unbounded" },
2382
+ keyArity: { kind: "single", argIndex: 0 }
2383
+ }),
2384
+ XRANGE: tbl({
2385
+ readOnly: true,
2386
+ sizeGuard: { kind: "unbounded" },
2387
+ keyArity: { kind: "single", argIndex: 0 }
2388
+ }),
2389
+ XREVRANGE: tbl({
2390
+ readOnly: true,
2391
+ sizeGuard: { kind: "unbounded" },
2392
+ keyArity: { kind: "single", argIndex: 0 }
2393
+ }),
2394
+ EXPIREAT: tbl({
2395
+ readOnly: false,
2396
+ sizeGuard: { kind: "unbounded" },
2397
+ keyArity: { kind: "single", argIndex: 0 }
2398
+ }),
2399
+ PEXPIRE: tbl({
2400
+ readOnly: false,
2401
+ sizeGuard: { kind: "unbounded" },
2402
+ keyArity: { kind: "single", argIndex: 0 }
2403
+ }),
2404
+ MSETNX: tbl({
2405
+ readOnly: false,
2406
+ sizeGuard: { kind: "unbounded" },
2407
+ keyArity: { kind: "multi-variable", startIndex: 0, step: 2 }
2408
+ }),
2409
+ XREAD: tbl({
2410
+ readOnly: true,
2411
+ sizeGuard: { kind: "unbounded" },
2412
+ keyArity: { kind: "after-token", token: "STREAMS" }
3713
2413
  }),
3714
- ZADD: tbl({
3715
- readOnly: false,
2414
+ ECHO: tbl({
2415
+ readOnly: true,
3716
2416
  sizeGuard: { kind: "unbounded" },
3717
- keyArity: { kind: "single", argIndex: 0 },
3718
- permissionTier: "read-write"
2417
+ keyArity: { kind: "no-key" }
3719
2418
  }),
3720
- ZREM: tbl({
2419
+ CONFIG: tbl({
3721
2420
  readOnly: false,
3722
2421
  sizeGuard: { kind: "unbounded" },
3723
- keyArity: { kind: "single", argIndex: 0 },
3724
- permissionTier: "read-write"
2422
+ keyArity: { kind: "no-key" }
3725
2423
  }),
3726
- DEL: tbl({
2424
+ CLIENT: tbl({
3727
2425
  readOnly: false,
3728
2426
  sizeGuard: { kind: "unbounded" },
3729
- keyArity: { kind: "multi-variable", startIndex: 0, step: 1 },
3730
- permissionTier: "read-write"
2427
+ keyArity: { kind: "no-key" }
3731
2428
  }),
3732
- UNLINK: tbl({
2429
+ MONITOR: tbl({
3733
2430
  readOnly: false,
3734
2431
  sizeGuard: { kind: "unbounded" },
3735
- keyArity: { kind: "multi-variable", startIndex: 0, step: 1 },
3736
- permissionTier: "read-write"
2432
+ keyArity: { kind: "no-key" }
3737
2433
  }),
3738
- EXPIRE: tbl({
2434
+ SAVE: tbl({
3739
2435
  readOnly: false,
3740
2436
  sizeGuard: { kind: "unbounded" },
3741
- keyArity: { kind: "single", argIndex: 0 },
3742
- permissionTier: "read-write"
2437
+ keyArity: { kind: "no-key" }
3743
2438
  }),
3744
- PERSIST: tbl({
2439
+ BGSAVE: tbl({
3745
2440
  readOnly: false,
3746
2441
  sizeGuard: { kind: "unbounded" },
3747
- keyArity: { kind: "single", argIndex: 0 },
3748
- permissionTier: "read-write"
2442
+ keyArity: { kind: "no-key" }
3749
2443
  }),
3750
- RENAME: tbl({
2444
+ BGREWRITEAOF: tbl({
3751
2445
  readOnly: false,
3752
2446
  sizeGuard: { kind: "unbounded" },
3753
- keyArity: { kind: "multi-fixed", argIndices: [0, 1] },
3754
- permissionTier: "read-write"
3755
- }),
3756
- FLUSHDB: tbl({
3757
- readOnly: false,
3758
- sizeGuard: { kind: "reject", reason: "FLUSHDB destroys data" },
3759
- keyArity: { kind: "no-key" },
3760
- permissionTier: "admin"
2447
+ keyArity: { kind: "no-key" }
3761
2448
  }),
3762
- FLUSHALL: tbl({
2449
+ REPLICAOF: tbl({
3763
2450
  readOnly: false,
3764
- sizeGuard: { kind: "reject", reason: "FLUSHALL destroys data" },
3765
- keyArity: { kind: "no-key" },
3766
- permissionTier: "admin"
2451
+ sizeGuard: { kind: "unbounded" },
2452
+ keyArity: { kind: "no-key" }
3767
2453
  }),
3768
- SHUTDOWN: tbl({
2454
+ SLAVEOF: tbl({
3769
2455
  readOnly: false,
3770
- sizeGuard: { kind: "reject", reason: "SHUTDOWN stops the server" },
3771
- keyArity: { kind: "no-key" },
3772
- permissionTier: "admin"
2456
+ sizeGuard: { kind: "unbounded" },
2457
+ keyArity: { kind: "no-key" }
3773
2458
  }),
3774
- DEBUG: tbl({
2459
+ ACL: tbl({
3775
2460
  readOnly: false,
3776
- sizeGuard: { kind: "reject", reason: "DEBUG is unsafe" },
3777
- keyArity: { kind: "no-key" },
3778
- permissionTier: "admin"
2461
+ sizeGuard: { kind: "unbounded" },
2462
+ keyArity: { kind: "no-key" }
3779
2463
  }),
3780
2464
  "CONFIG SET": tbl({
3781
2465
  readOnly: false,
3782
2466
  sizeGuard: { kind: "reject", reason: "CONFIG SET mutates server state" },
3783
- keyArity: { kind: "no-key" },
3784
- permissionTier: "admin"
2467
+ keyArity: { kind: "no-key" }
3785
2468
  })
3786
2469
  });
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
2470
 
3913
2471
  // src/utils/glob.ts
3914
2472
  function globToRegex(glob) {
@@ -3961,968 +2519,11 @@ function isValidCharacterClass(body) {
3961
2519
  }
3962
2520
  }
3963
2521
 
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
- }
2522
+ // src/adapters/redis/returned-key-names.ts
2523
+ var RETURNS_KEY_NAMES = new Set(["SCAN", "KEYS"]);
4044
2524
 
4045
2525
  // src/adapters/redis/value-masker.ts
4046
- var REDACTED = "[REDACTED]";
4047
2526
  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
2527
  // src/utils/sql-lexical.ts
4927
2528
  var IDENTIFIER_CONTINUATION = /[A-Za-z0-9_$]|[\u0080-\uFFFF]/;
4928
2529
  var IDENTIFIER_START = /[A-Za-z_]|[\u0080-\uFFFF]/;
@@ -10349,10 +7950,26 @@ var MetadataSchema = exports_external.object({
10349
7950
  schemaLastUpdated: exports_external.string().datetime().optional(),
10350
7951
  schemaTableCount: exports_external.number().int().nonnegative().optional()
10351
7952
  }).optional().default({});
7953
+ var BLACKLIST_KEY_ALIASES = {
7954
+ indices: "tables",
7955
+ index: "tables",
7956
+ fields: "columns",
7957
+ keys: "tables"
7958
+ };
10352
7959
  var BlacklistConfigSchema = exports_external.object({
10353
7960
  tables: exports_external.array(exports_external.string()).default([]),
10354
7961
  columns: exports_external.record(exports_external.array(exports_external.string())).default({})
10355
- }).optional().default({ tables: [], columns: {} });
7962
+ }).passthrough().superRefine((value, ctx) => {
7963
+ for (const [wrong, right] of Object.entries(BLACKLIST_KEY_ALIASES)) {
7964
+ if (wrong in value) {
7965
+ ctx.addIssue({
7966
+ code: exports_external.ZodIssueCode.custom,
7967
+ path: [wrong],
7968
+ message: `blacklist.${wrong} is not a setting and would be silently ignored \u2014 did you mean blacklist.${right}?`
7969
+ });
7970
+ }
7971
+ }
7972
+ }).transform((value) => ({ tables: value.tables, columns: value.columns })).optional().default({ tables: [], columns: {} });
10356
7973
  var RedisMaskRuleSchema = exports_external.object({
10357
7974
  keyPattern: exports_external.string().min(1),
10358
7975
  fields: exports_external.array(exports_external.string()).optional()
@@ -10366,9 +7983,14 @@ var AuditRotationConfigSchema = exports_external.object({
10366
7983
  }).optional().default({ max_bytes: 10485760, max_entries: 1000 });
10367
7984
  var AuditConfigSchema = exports_external.object({
10368
7985
  enabled: exports_external.boolean().default(true),
7986
+ strict: exports_external.boolean().default(false),
10369
7987
  rotation: AuditRotationConfigSchema
7988
+ }).refine((audit) => !(audit.strict && !audit.enabled), {
7989
+ 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.",
7990
+ path: ["strict"]
10370
7991
  }).optional().default({
10371
7992
  enabled: true,
7993
+ strict: false,
10372
7994
  rotation: { max_bytes: 10485760, max_entries: 1000 }
10373
7995
  });
10374
7996
  var DbcliConfigSchema = exports_external.object({
@@ -10400,12 +8022,24 @@ var ElasticsearchNamedConnectionSchema = ElasticsearchConnectionConfigSchema.ext
10400
8022
  envFile: exports_external.string().optional(),
10401
8023
  environment: EnvironmentLabelSchema
10402
8024
  });
10403
- var NamedConnectionSchema = exports_external.union([
8025
+ var NamedConnectionUnion = exports_external.union([
10404
8026
  SqlNamedConnectionSchema,
10405
8027
  MongoDBNamedConnectionSchema,
10406
8028
  RedisNamedConnectionSchema,
10407
8029
  ElasticsearchNamedConnectionSchema
10408
8030
  ]);
8031
+ var NamedConnectionSchema = exports_external.preprocess((raw, ctx) => {
8032
+ if (raw !== null && typeof raw === "object" && !Array.isArray(raw)) {
8033
+ if ("blacklist" in raw) {
8034
+ ctx.addIssue({
8035
+ code: exports_external.ZodIssueCode.custom,
8036
+ path: ["blacklist"],
8037
+ 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."
8038
+ });
8039
+ }
8040
+ }
8041
+ return raw;
8042
+ }, NamedConnectionUnion);
10409
8043
  var DbcliConfigV2Schema = exports_external.object({
10410
8044
  version: exports_external.literal(2),
10411
8045
  default: exports_external.string().min(1),
@@ -11146,6 +8780,7 @@ var DEFAULT_CONFIG = {
11146
8780
  blacklist: { tables: [], columns: {} },
11147
8781
  audit: {
11148
8782
  enabled: true,
8783
+ strict: false,
11149
8784
  rotation: { max_bytes: 10485760, max_entries: 1000 }
11150
8785
  }
11151
8786
  };
@@ -11535,6 +9170,32 @@ function matchesIndexGlob(pattern, name) {
11535
9170
  return true;
11536
9171
  }
11537
9172
  }
9173
+ function reachesByConvention(name, entry) {
9174
+ const lower = name.toLowerCase();
9175
+ const target = entry.toLowerCase();
9176
+ return new RegExp(`^\\.ds-${escapeRegExp(target)}-`).test(lower) || new RegExp(`^${escapeRegExp(target)}-\\d+$`).test(lower);
9177
+ }
9178
+ function escapeRegExp(text) {
9179
+ return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9180
+ }
9181
+ function expandBlacklistEntries(blacklisted) {
9182
+ const names = [];
9183
+ const patterns = [];
9184
+ for (const entry of blacklisted) {
9185
+ const { concrete, wildcards } = expandIndexTargets(entry);
9186
+ names.push(...concrete);
9187
+ patterns.push(...wildcards);
9188
+ }
9189
+ return { names, patterns };
9190
+ }
9191
+ function indexExpressionReaches(expression, blacklisted) {
9192
+ if (blacklisted.length === 0)
9193
+ return false;
9194
+ const { concrete, wildcards } = expandIndexTargets(expression);
9195
+ const entries = expandBlacklistEntries(blacklisted);
9196
+ const reachesName = (name) => entries.names.some((entry) => entry.toLowerCase() === name.toLowerCase() || reachesByConvention(name, entry)) || entries.patterns.some((pattern) => matchesIndexGlob(pattern, name));
9197
+ return concrete.some(reachesName) || wildcards.some((pattern) => entries.names.some((entry) => matchesIndexGlob(pattern, entry)) || entries.patterns.length > 0);
9198
+ }
11538
9199
 
11539
9200
  // src/core/blacklist-validator.ts
11540
9201
  function flattenArrayRow(row) {
@@ -11592,26 +9253,23 @@ class BlacklistValidator {
11592
9253
  throw new BlacklistError(message, blocked[0], operation);
11593
9254
  }
11594
9255
  checkIndexBlacklist(operation, target) {
11595
- const { concrete, wildcards } = expandIndexTargets(target);
11596
- this.checkTablesBlacklist(operation, concrete);
11597
- if (wildcards.length === 0 || this.manager.canOverrideBlacklist())
9256
+ if (this.manager.canOverrideBlacklist())
11598
9257
  return;
11599
9258
  const blacklisted = Array.from(this.manager.getState().tables);
11600
9259
  if (blacklisted.length === 0)
11601
9260
  return;
11602
- const reachable = wildcards.filter((pattern) => blacklisted.some((entry) => matchesIndexGlob(pattern, entry)));
11603
- if (reachable.length === 0)
9261
+ if (!indexExpressionReaches(target, blacklisted))
11604
9262
  return;
11605
9263
  const message = t_vars("errors.table_blacklisted", {
11606
- table: reachable.join(", "),
9264
+ table: target,
11607
9265
  operation
11608
9266
  });
11609
- throw new BlacklistError(message, reachable[0], operation);
9267
+ throw new BlacklistError(message, target, operation);
11610
9268
  }
11611
9269
  filterColumnsForIndexExpression(target, rows, columnList) {
11612
9270
  const { concrete, wildcards } = expandIndexTargets(target);
11613
9271
  const ruleKeys = Array.from(this.manager.getState().columns.keys());
11614
- const reachable = ruleKeys.filter((key) => wildcards.some((pattern) => matchesIndexGlob(pattern, key)));
9272
+ const reachable = ruleKeys.filter((key) => wildcards.some((pattern) => matchesIndexGlob(pattern, key)) || concrete.some((name) => indexExpressionReaches(name, [key])));
11615
9273
  return this.filterColumnsForTables([...concrete, ...reachable], rows, columnList);
11616
9274
  }
11617
9275
  checkColumnBlacklistOnWrite(tableName, fields, operation = "WRITE") {
@@ -11742,6 +9400,5 @@ export {
11742
9400
  ConnectionError,
11743
9401
  BlacklistValidator,
11744
9402
  BlacklistManager,
11745
- BlacklistError,
11746
- AdapterFactory
9403
+ BlacklistError
11747
9404
  };