@carllee1983/dbcli 1.57.0 → 1.58.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/.cursor/rules/dbcli.mdc +7 -2
- package/.cursor/skills/dbcli/reference.md +12 -3
- package/.github/skills/dbcli/SKILL.md +7 -2
- package/.github/skills/dbcli/reference.md +12 -3
- package/CHANGELOG.md +40 -0
- package/assets/SKILL.md +7 -2
- package/assets/SKILL.zh-TW.md +4 -1
- package/assets/reference.md +12 -3
- package/dist/cli-runtime.mjs +817 -575
- package/dist/cli.mjs +4 -2
- package/dist/core.d.ts +79 -24
- package/dist/core.mjs +315 -343
- package/package.json +4 -2
- package/plugins/dbcli-agent/skills/dbcli/SKILL.md +7 -2
- package/plugins/dbcli-agent/skills/dbcli/reference.md +12 -3
- package/skills/dbcli/SKILL.md +7 -2
- package/skills/dbcli/reference.md +12 -3
package/dist/cli-runtime.mjs
CHANGED
|
@@ -51,7 +51,7 @@ var package_default;
|
|
|
51
51
|
var init_package = __esm(() => {
|
|
52
52
|
package_default = {
|
|
53
53
|
name: "@carllee1983/dbcli",
|
|
54
|
-
version: "1.
|
|
54
|
+
version: "1.58.0",
|
|
55
55
|
description: "Database CLI for AI agents",
|
|
56
56
|
type: "module",
|
|
57
57
|
publishConfig: {
|
|
@@ -125,12 +125,14 @@ var init_package = __esm(() => {
|
|
|
125
125
|
"test:unit": "bun test tests/unit tests/core",
|
|
126
126
|
"test:integration": "bun test tests/integration",
|
|
127
127
|
"test:gherkin": "bun test tests/gherkin",
|
|
128
|
-
"test:docker": "docker compose -f docker-compose.test.yml up -d --wait && bun test tests/integration
|
|
128
|
+
"test:docker": "docker compose -f docker-compose.test.yml up -d --wait && bun test tests/integration; docker compose -f docker-compose.test.yml down",
|
|
129
|
+
"services:check": "bun run scripts/check-test-services.ts",
|
|
129
130
|
"docs:check": "bun run scripts/check-user-docs.ts",
|
|
130
131
|
"contract:check": "bun run scripts/check-cli-contract.ts",
|
|
131
132
|
"skill:check": "bun run scripts/check-skill-parity.ts",
|
|
132
133
|
"platform:check": "bun run scripts/check-platform-parity.ts",
|
|
133
134
|
"agent-core:check": "bun run scripts/check-agent-core-purity.ts",
|
|
135
|
+
"core-stdout:check": "bun run scripts/check-core-no-stdout.ts",
|
|
134
136
|
typecheck: "tsc --noEmit --pretty false",
|
|
135
137
|
"test:perf": "bun test ./tests/perf/*.bench.ts",
|
|
136
138
|
lint: "eslint src tests scripts --ext .ts --max-warnings=0",
|
|
@@ -8943,6 +8945,12 @@ var init_messages = __esm(() => {
|
|
|
8943
8945
|
invalid_config: "Invalid configuration: {field}",
|
|
8944
8946
|
connection_failed: "Failed to connect to database: {message}",
|
|
8945
8947
|
permission_denied: "Permission denied (required: {required})",
|
|
8948
|
+
permission_requires_level: "{type} operation requires {minimum} permission or higher (current level: {permission})",
|
|
8949
|
+
elasticsearch_requires_level: "Elasticsearch {type} operation requires {minimum} permission or higher (current level: {permission})",
|
|
8950
|
+
escalated_write_requires_admin: "This statement opens as a read but contains an executable {keyword}. A write hidden inside a read statement requires admin permission (current level: {permission}).",
|
|
8951
|
+
multiple_statements_refused: "SQL containing multiple statements is refused below admin permission, because only the first statement determines the permission check. Run each statement separately.",
|
|
8952
|
+
unknown_statement_query_only: "Unrecognised SQL statement (current level: query-only). Security policy requires read-write+ for unknown statements. If this is a legitimate read-only statement, please open an issue at https://github.com/CarlLee1983/dbcli/issues.",
|
|
8953
|
+
permission_denied_reason: "Permission denied: {reason}",
|
|
8946
8954
|
table_not_found: "Table not found: {table}",
|
|
8947
8955
|
unsupported_lang: "Language '{lang}' not supported, using English",
|
|
8948
8956
|
invalid_json: "Invalid JSON: {message}",
|
|
@@ -8964,7 +8972,8 @@ Provide options: --env-host, --env-port, --env-user, --env-password, --env-datab
|
|
|
8964
8972
|
Or use interactive mode: run "dbcli init --use-env-refs" without --no-interactive`,
|
|
8965
8973
|
env_var_not_defined: `Cannot test connection: environment variable {envKey} is not defined.
|
|
8966
8974
|
Set {envKey} in .env or environment variables.
|
|
8967
|
-
Hint: run 'export {envKey}=<value>' and retry
|
|
8975
|
+
Hint: run 'export {envKey}=<value>' and retry`,
|
|
8976
|
+
invalid_output_format: "--format {format} is not supported here \u2014 use text or json"
|
|
8968
8977
|
},
|
|
8969
8978
|
success: {
|
|
8970
8979
|
inserted: "Successfully inserted {count} row(s)",
|
|
@@ -8975,19 +8984,22 @@ Hint: run 'export {envKey}=<value>' and retry`
|
|
|
8975
8984
|
insert: {
|
|
8976
8985
|
description: "Insert data into a table",
|
|
8977
8986
|
prompt_data: "Enter JSON data to insert: ",
|
|
8978
|
-
confirm: "Insert {count} row(s) into {table}? (y/n): "
|
|
8987
|
+
confirm: "Insert {count} row(s) into {table}? (y/n): ",
|
|
8988
|
+
elasticsearch_unsupported: "Elasticsearch does not support the insert command; for now, use an external tool (e.g. curl or Kibana DevTools) to write documents"
|
|
8979
8989
|
},
|
|
8980
8990
|
update: {
|
|
8981
8991
|
description: "Update data in a table",
|
|
8982
8992
|
prompt_where: "Enter WHERE clause (e.g., id=1): ",
|
|
8983
8993
|
prompt_set: 'Enter JSON updates (e.g., {"status": "active"}): ',
|
|
8984
|
-
confirm: "Update rows in {table}? (y/n): "
|
|
8994
|
+
confirm: "Update rows in {table}? (y/n): ",
|
|
8995
|
+
elasticsearch_unsupported: "Elasticsearch does not support the update command; for now, use an external tool (e.g. curl or Kibana DevTools) to update documents"
|
|
8985
8996
|
},
|
|
8986
8997
|
delete: {
|
|
8987
8998
|
description: "Delete data from a table",
|
|
8988
8999
|
prompt_where: "Enter WHERE clause (e.g., id=1): ",
|
|
8989
9000
|
confirm: "Delete rows from {table}? (y/n): ",
|
|
8990
|
-
admin_only: "Delete command requires data-admin or admin permission"
|
|
9001
|
+
admin_only: "Delete command requires data-admin or admin permission",
|
|
9002
|
+
elasticsearch_unsupported: "Elasticsearch does not support the delete command; for now, use an external tool (e.g. curl or Kibana DevTools) to delete documents"
|
|
8991
9003
|
},
|
|
8992
9004
|
export: {
|
|
8993
9005
|
description: "Export query results",
|
|
@@ -9241,6 +9253,12 @@ var init_messages2 = __esm(() => {
|
|
|
9241
9253
|
invalid_config: "\u914D\u7F6E\u7121\u6548\uFF1A{field}",
|
|
9242
9254
|
connection_failed: "\u7121\u6CD5\u9023\u63A5\u5230\u8CC7\u6599\u5EAB\uFF1A{message}",
|
|
9243
9255
|
permission_denied: "\u6B0A\u9650\u88AB\u62D2\uFF08\u9700\u8981\uFF1A{required}\uFF09",
|
|
9256
|
+
permission_requires_level: "{type} \u64CD\u4F5C\u9700\u8981 {minimum} \u4EE5\u4E0A\u7684\u6B0A\u9650\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1A{permission}\uFF09",
|
|
9257
|
+
elasticsearch_requires_level: "Elasticsearch {type} \u64CD\u4F5C\u9700\u8981 {minimum} \u4EE5\u4E0A\u7684\u6B0A\u9650\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1A{permission}\uFF09",
|
|
9258
|
+
escalated_write_requires_admin: "\u6B64\u8A9E\u53E5\u8868\u9762\u4E0A\u662F\u8B80\u53D6\uFF0C\u4F46\u5BE6\u969B\u5305\u542B\u53EF\u57F7\u884C\u7684 {keyword}\u3002\u8B80\u53D6\u8A9E\u53E5\u4E2D\u96B1\u85CF\u5BEB\u5165\u64CD\u4F5C\u9700\u8981 admin \u6B0A\u9650\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1A{permission}\uFF09\u3002",
|
|
9259
|
+
multiple_statements_refused: "\u4F4E\u65BC admin \u6B0A\u9650\u6642\u62D2\u7D55\u57F7\u884C\u5305\u542B\u591A\u500B\u8A9E\u53E5\u7684 SQL\uFF0C\u56E0\u70BA\u6B0A\u9650\u6AA2\u67E5\u53EA\u6703\u4F9D\u64DA\u7B2C\u4E00\u500B\u8A9E\u53E5\u5224\u65B7\u3002\u8ACB\u5C07\u5404\u8A9E\u53E5\u5206\u958B\u57F7\u884C\u3002",
|
|
9260
|
+
unknown_statement_query_only: "\u7121\u6CD5\u8FA8\u8B58\u7684 SQL \u8A9E\u53E5\uFF08\u76EE\u524D\u5C64\u7D1A\uFF1Aquery-only\uFF09\u3002\u5B89\u5168\u653F\u7B56\u8981\u6C42\u672A\u77E5\u8A9E\u53E5\u81F3\u5C11\u9700\u8981 read-write \u4EE5\u4E0A\u6B0A\u9650\u3002\u82E5\u9019\u5176\u5BE6\u662F\u5408\u6CD5\u7684\u552F\u8B80\u8A9E\u53E5\uFF0C\u8ACB\u81F3 https://github.com/CarlLee1983/dbcli/issues \u63D0\u51FA\u8B70\u984C\u3002",
|
|
9261
|
+
permission_denied_reason: "\u6B0A\u9650\u88AB\u62D2\uFF1A{reason}",
|
|
9244
9262
|
table_not_found: "\u627E\u4E0D\u5230\u8868\u683C\uFF1A{table}",
|
|
9245
9263
|
unsupported_lang: "\u4E0D\u652F\u63F4\u7684\u8A9E\u8A00 '{lang}'\uFF0C\u4F7F\u7528\u82F1\u6587",
|
|
9246
9264
|
invalid_json: "\u7121\u6548\u7684 JSON\uFF1A{message}",
|
|
@@ -9262,7 +9280,8 @@ var init_messages2 = __esm(() => {
|
|
|
9262
9280
|
\u6216\u4F7F\u7528\u4E92\u52D5\u6A21\u5F0F\uFF1A\u57F7\u884C "dbcli init --use-env-refs"\uFF08\u4E0D\u52A0 --no-interactive\uFF09`,
|
|
9263
9281
|
env_var_not_defined: `\u7121\u6CD5\u6E2C\u8A66\u9023\u7DDA\uFF1A\u74B0\u5883\u8B8A\u6578 {envKey} \u672A\u5B9A\u7FA9\u3002
|
|
9264
9282
|
\u8ACB\u5728 .env \u6216\u74B0\u5883\u8B8A\u6578\u4E2D\u8A2D\u5B9A {envKey}\u3002
|
|
9265
|
-
\u63D0\u793A\uFF1A\u57F7\u884C 'export {envKey}=<value>' \u5F8C\u91CD\u8A66
|
|
9283
|
+
\u63D0\u793A\uFF1A\u57F7\u884C 'export {envKey}=<value>' \u5F8C\u91CD\u8A66`,
|
|
9284
|
+
invalid_output_format: "\u7121\u6CD5\u4F7F\u7528 --format {format}\uFF0C\u8ACB\u4F7F\u7528 text \u6216 json"
|
|
9266
9285
|
},
|
|
9267
9286
|
success: {
|
|
9268
9287
|
inserted: "\u6210\u529F\u63D2\u5165 {count} \u5217",
|
|
@@ -9273,19 +9292,22 @@ var init_messages2 = __esm(() => {
|
|
|
9273
9292
|
insert: {
|
|
9274
9293
|
description: "\u63D2\u5165\u8CC7\u6599\u5230\u8868\u683C",
|
|
9275
9294
|
prompt_data: "\u8F38\u5165\u8981\u63D2\u5165\u7684 JSON \u8CC7\u6599\uFF1A",
|
|
9276
|
-
confirm: "\u5C07 {count} \u5217\u63D2\u5165\u5230 {table}\uFF1F (y/n)\uFF1A"
|
|
9295
|
+
confirm: "\u5C07 {count} \u5217\u63D2\u5165\u5230 {table}\uFF1F (y/n)\uFF1A",
|
|
9296
|
+
elasticsearch_unsupported: "Elasticsearch \u4E0D\u652F\u63F4 insert \u6307\u4EE4\uFF1B\u76EE\u524D\u8ACB\u4F7F\u7528\u5916\u90E8\u5DE5\u5177\uFF08\u5982 curl \u6216 Kibana DevTools\uFF09\u9032\u884C\u6587\u4EF6\u5BEB\u5165"
|
|
9277
9297
|
},
|
|
9278
9298
|
update: {
|
|
9279
9299
|
description: "\u66F4\u65B0\u8868\u683C\u4E2D\u7684\u8CC7\u6599",
|
|
9280
9300
|
prompt_where: "\u8F38\u5165 WHERE \u5B50\u53E5 (\u4F8B\u5982\uFF1Aid=1)\uFF1A",
|
|
9281
9301
|
prompt_set: '\u8F38\u5165 JSON \u66F4\u65B0 (\u4F8B\u5982\uFF1A{"status": "active"})\uFF1A',
|
|
9282
|
-
confirm: "\u66F4\u65B0 {table} \u4E2D\u7684\u5217\uFF1F (y/n)\uFF1A"
|
|
9302
|
+
confirm: "\u66F4\u65B0 {table} \u4E2D\u7684\u5217\uFF1F (y/n)\uFF1A",
|
|
9303
|
+
elasticsearch_unsupported: "Elasticsearch \u4E0D\u652F\u63F4 update \u6307\u4EE4\uFF1B\u76EE\u524D\u8ACB\u4F7F\u7528\u5916\u90E8\u5DE5\u5177\uFF08\u5982 curl \u6216 Kibana DevTools\uFF09\u9032\u884C\u6587\u4EF6\u66F4\u65B0"
|
|
9283
9304
|
},
|
|
9284
9305
|
delete: {
|
|
9285
9306
|
description: "\u522A\u9664\u8868\u683C\u4E2D\u7684\u8CC7\u6599",
|
|
9286
9307
|
prompt_where: "\u8F38\u5165 WHERE \u5B50\u53E5 (\u4F8B\u5982\uFF1Aid=1)\uFF1A",
|
|
9287
9308
|
confirm: "\u522A\u9664 {table} \u4E2D\u7684\u5217\uFF1F (y/n)\uFF1A",
|
|
9288
|
-
admin_only: "\u522A\u9664\u547D\u4EE4\u9700\u8981 data-admin \u6216 admin \u6B0A\u9650"
|
|
9309
|
+
admin_only: "\u522A\u9664\u547D\u4EE4\u9700\u8981 data-admin \u6216 admin \u6B0A\u9650",
|
|
9310
|
+
elasticsearch_unsupported: "Elasticsearch \u4E0D\u652F\u63F4 delete \u6307\u4EE4\uFF1B\u76EE\u524D\u8ACB\u4F7F\u7528\u5916\u90E8\u5DE5\u5177\uFF08\u5982 curl \u6216 Kibana DevTools\uFF09\u9032\u884C\u6587\u4EF6\u522A\u9664"
|
|
9289
9311
|
},
|
|
9290
9312
|
export: {
|
|
9291
9313
|
description: "\u532F\u51FA\u67E5\u8A62\u7D50\u679C",
|
|
@@ -9430,6 +9452,60 @@ var init_shell2 = __esm(() => {
|
|
|
9430
9452
|
};
|
|
9431
9453
|
});
|
|
9432
9454
|
|
|
9455
|
+
// resources/lang/en/ceremony.json
|
|
9456
|
+
var ceremony_default;
|
|
9457
|
+
var init_ceremony = __esm(() => {
|
|
9458
|
+
ceremony_default = {
|
|
9459
|
+
updated: "Updated {count} row(s) in {table}",
|
|
9460
|
+
deleted: "Deleted {count} row(s) from {table}",
|
|
9461
|
+
inserted: "Inserted {count} row(s) into {table}",
|
|
9462
|
+
matched_nothing: "No rows matched in {table} \u2014 nothing changed",
|
|
9463
|
+
cancelled: "Cancelled. {table} was not changed",
|
|
9464
|
+
dry_run: "Preview only. {table} was not changed",
|
|
9465
|
+
elapsed: "Took {seconds}s",
|
|
9466
|
+
failed: "Failed: {reason}",
|
|
9467
|
+
unknown_error: "unknown error",
|
|
9468
|
+
confirm_sql: "Generated SQL:",
|
|
9469
|
+
confirm_command: "Command to run:",
|
|
9470
|
+
confirm_params: "Parameters:",
|
|
9471
|
+
confirm_destructive_warning: "\u26A0\uFE0F Warning: DELETE operation is destructive and cannot be undone!",
|
|
9472
|
+
confirm_destructive_prompt: "Are you sure you want to execute this DELETE operation? This cannot be undone.",
|
|
9473
|
+
confirm_destructive_warning_ddl: "\u26A0\uFE0F Warning: this schema change is destructive and cannot be undone!",
|
|
9474
|
+
confirm_ddl_prompt: "Are you sure you want to run this {operation}? This cannot be undone.",
|
|
9475
|
+
confirm_prompt: "Proceed with this operation?",
|
|
9476
|
+
recovery_none: "This cannot be undone automatically \u2014 restore from a backup to get the previous values back.",
|
|
9477
|
+
recovery_retry: "Re-run with --recovery to write a recovery plan, then read it with: dbcli recover",
|
|
9478
|
+
blacklist_hint: "Run `dbcli blacklist list` to see what is protected"
|
|
9479
|
+
};
|
|
9480
|
+
});
|
|
9481
|
+
|
|
9482
|
+
// resources/lang/zh-TW/ceremony.json
|
|
9483
|
+
var ceremony_default2;
|
|
9484
|
+
var init_ceremony2 = __esm(() => {
|
|
9485
|
+
ceremony_default2 = {
|
|
9486
|
+
updated: "\u5DF2\u66F4\u65B0 {table} \u7684 {count} \u5217",
|
|
9487
|
+
deleted: "\u5DF2\u5F9E {table} \u522A\u9664 {count} \u5217",
|
|
9488
|
+
inserted: "\u5DF2\u5BEB\u5165 {count} \u5217\u5230 {table}",
|
|
9489
|
+
matched_nothing: "{table} \u6C92\u6709\u4EFB\u4F55\u5217\u7B26\u5408\u689D\u4EF6\uFF0C\u8CC7\u6599\u672A\u8B8A\u52D5",
|
|
9490
|
+
cancelled: "\u5DF2\u53D6\u6D88\uFF0C{table} \u672A\u8B8A\u52D5",
|
|
9491
|
+
dry_run: "\u50C5\u9810\u89BD\uFF0C{table} \u672A\u8B8A\u52D5",
|
|
9492
|
+
elapsed: "\u8017\u6642 {seconds} \u79D2",
|
|
9493
|
+
failed: "\u5931\u6557\uFF1A{reason}",
|
|
9494
|
+
unknown_error: "\u672A\u77E5\u7684\u932F\u8AA4",
|
|
9495
|
+
confirm_sql: "\u7522\u751F\u7684 SQL\uFF1A",
|
|
9496
|
+
confirm_command: "\u5373\u5C07\u57F7\u884C\u7684\u6307\u4EE4\uFF1A",
|
|
9497
|
+
confirm_params: "\u53C3\u6578\uFF1A",
|
|
9498
|
+
confirm_destructive_warning: "\u26A0\uFE0F \u8B66\u544A\uFF1ADELETE \u5177\u7834\u58DE\u6027\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\uFF01",
|
|
9499
|
+
confirm_destructive_prompt: "\u78BA\u5B9A\u8981\u57F7\u884C\u9019\u500B DELETE \u55CE\uFF1F\u6B64\u64CD\u4F5C\u7121\u6CD5\u5FA9\u539F\u3002",
|
|
9500
|
+
confirm_destructive_warning_ddl: "\u26A0\uFE0F \u8B66\u544A\uFF1A\u9019\u500B schema \u8B8A\u66F4\u5177\u7834\u58DE\u6027\uFF0C\u57F7\u884C\u5F8C\u7121\u6CD5\u5FA9\u539F\uFF01",
|
|
9501
|
+
confirm_ddl_prompt: "\u78BA\u5B9A\u8981\u57F7\u884C\u9019\u500B {operation} \u55CE\uFF1F\u6B64\u64CD\u4F5C\u7121\u6CD5\u5FA9\u539F\u3002",
|
|
9502
|
+
confirm_prompt: "\u8981\u7E7C\u7E8C\u57F7\u884C\u9019\u500B\u64CD\u4F5C\u55CE\uFF1F",
|
|
9503
|
+
recovery_none: "\u6B64\u64CD\u4F5C\u7121\u6CD5\u81EA\u52D5\u9084\u539F\uFF0C\u8981\u53D6\u56DE\u5148\u524D\u7684\u8CC7\u6599\u8ACB\u5F9E\u5099\u4EFD\u56DE\u5FA9\u3002",
|
|
9504
|
+
recovery_retry: "\u52A0\u4E0A --recovery \u91CD\u8DD1\u53EF\u7522\u751F\u56DE\u5FA9\u8A08\u756B\uFF0C\u518D\u7528 dbcli recover \u8B80\u53D6",
|
|
9505
|
+
blacklist_hint: "\u57F7\u884C `dbcli blacklist list` \u67E5\u770B\u53D7\u4FDD\u8B77\u7684\u9805\u76EE"
|
|
9506
|
+
};
|
|
9507
|
+
});
|
|
9508
|
+
|
|
9433
9509
|
// src/i18n/message-loader.ts
|
|
9434
9510
|
class MessageLoader {
|
|
9435
9511
|
static instance = null;
|
|
@@ -9496,9 +9572,15 @@ var init_message_loader = __esm(() => {
|
|
|
9496
9572
|
init_messages2();
|
|
9497
9573
|
init_shell();
|
|
9498
9574
|
init_shell2();
|
|
9575
|
+
init_ceremony();
|
|
9576
|
+
init_ceremony2();
|
|
9499
9577
|
BUNDLED_MESSAGES = {
|
|
9500
|
-
en: { ...messages_default, shell: shell_default },
|
|
9501
|
-
"zh-TW": {
|
|
9578
|
+
en: { ...messages_default, shell: shell_default, ceremony: ceremony_default },
|
|
9579
|
+
"zh-TW": {
|
|
9580
|
+
...messages_default2,
|
|
9581
|
+
shell: shell_default2,
|
|
9582
|
+
ceremony: ceremony_default2
|
|
9583
|
+
}
|
|
9502
9584
|
};
|
|
9503
9585
|
messageLoader = MessageLoader.getInstance();
|
|
9504
9586
|
});
|
|
@@ -9664,7 +9746,8 @@ async function selectFallback(message, choices) {
|
|
|
9664
9746
|
return choices[0] ?? "";
|
|
9665
9747
|
}
|
|
9666
9748
|
async function confirmFallback(message) {
|
|
9667
|
-
|
|
9749
|
+
process.stderr.write(`${message} (y/n): `);
|
|
9750
|
+
const answer = await readLineFromStdin();
|
|
9668
9751
|
return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
|
|
9669
9752
|
}
|
|
9670
9753
|
async function text(message, defaultValue) {
|
|
@@ -9696,7 +9779,7 @@ async function confirm(message) {
|
|
|
9696
9779
|
const inquirer = await loadInquirer();
|
|
9697
9780
|
if (!inquirer)
|
|
9698
9781
|
return await confirmFallback(message);
|
|
9699
|
-
return await inquirer.confirm({ message });
|
|
9782
|
+
return await inquirer.confirm({ message }, { output: process.stderr });
|
|
9700
9783
|
}
|
|
9701
9784
|
var promptsUnavailableReported = false, promptUser;
|
|
9702
9785
|
var init_prompts = __esm(() => {
|
|
@@ -18555,33 +18638,7 @@ var init_sql_lexical = __esm(() => {
|
|
|
18555
18638
|
DOLLAR_QUOTE_DELIMITER = /^\$(?:(?:[A-Za-z_]|[\u0080-\uFFFF])(?:[A-Za-z0-9_]|[\u0080-\uFFFF])*)?\$/;
|
|
18556
18639
|
});
|
|
18557
18640
|
|
|
18558
|
-
// src/core/permission-
|
|
18559
|
-
var exports_permission_guard = {};
|
|
18560
|
-
__export(exports_permission_guard, {
|
|
18561
|
-
toSqlDialect: () => toSqlDialect,
|
|
18562
|
-
stripCommentsAndStrings: () => stripCommentsAndStrings,
|
|
18563
|
-
removeParameterMarkers: () => removeParameterMarkers,
|
|
18564
|
-
permissionAtLeast: () => permissionAtLeast,
|
|
18565
|
-
normalizeSQL: () => normalizeSQL,
|
|
18566
|
-
mapKeywordToType: () => mapKeywordToType,
|
|
18567
|
-
isDestructiveOperation: () => isDestructiveOperation,
|
|
18568
|
-
findWriteKeyword: () => findWriteKeyword,
|
|
18569
|
-
extractFirstKeyword: () => extractFirstKeyword,
|
|
18570
|
-
extractAllKeywords: () => extractAllKeywords,
|
|
18571
|
-
enforceRedisPermission: () => enforceRedisPermission,
|
|
18572
|
-
enforcePermission: () => enforcePermission,
|
|
18573
|
-
enforceElasticsearchPermission: () => enforceElasticsearchPermission,
|
|
18574
|
-
determineConfidence: () => determineConfidence,
|
|
18575
|
-
detectCompositePatterns: () => detectCompositePatterns,
|
|
18576
|
-
containsMultipleStatements: () => containsMultipleStatements,
|
|
18577
|
-
classifyStatement: () => classifyStatement,
|
|
18578
|
-
classifyRedisCommand: () => classifyRedisCommand,
|
|
18579
|
-
classifyElasticsearchRequest: () => classifyElasticsearchRequest,
|
|
18580
|
-
checkPermission: () => checkPermission,
|
|
18581
|
-
SQL_WRITE_OR_DDL_KEYWORDS: () => SQL_WRITE_OR_DDL_KEYWORDS,
|
|
18582
|
-
SQL_DIALECTS: () => SQL_DIALECTS,
|
|
18583
|
-
PermissionError: () => PermissionError
|
|
18584
|
-
});
|
|
18641
|
+
// src/core/permission/sql-analysis.ts
|
|
18585
18642
|
function normalizeSQL(sql) {
|
|
18586
18643
|
return sql.replace(/--[^\n]*\n/g, `
|
|
18587
18644
|
`).replace(/\/\*[\s\S]*?\*\//g, " ").trim().replace(/\s+/g, " ");
|
|
@@ -18828,6 +18885,29 @@ function determineConfidence(type, _keyword, _sql) {
|
|
|
18828
18885
|
function removeParameterMarkers(sql) {
|
|
18829
18886
|
return sql.replace(/\$\d+/g, "").replace(/\?/g, "");
|
|
18830
18887
|
}
|
|
18888
|
+
var init_sql_analysis = __esm(() => {
|
|
18889
|
+
init_sql_lexical();
|
|
18890
|
+
});
|
|
18891
|
+
|
|
18892
|
+
// src/core/permission-guard.ts
|
|
18893
|
+
var exports_permission_guard = {};
|
|
18894
|
+
__export(exports_permission_guard, {
|
|
18895
|
+
toSqlDialect: () => toSqlDialect,
|
|
18896
|
+
permitsOperation: () => permitsOperation,
|
|
18897
|
+
permissionAtLeast: () => permissionAtLeast,
|
|
18898
|
+
minimumPermissionFor: () => minimumPermissionFor,
|
|
18899
|
+
findWriteKeyword: () => findWriteKeyword,
|
|
18900
|
+
enforcePermissionForType: () => enforcePermissionForType,
|
|
18901
|
+
enforcePermission: () => enforcePermission,
|
|
18902
|
+
containsMultipleStatements: () => containsMultipleStatements,
|
|
18903
|
+
classifyStatement: () => classifyStatement,
|
|
18904
|
+
classificationForType: () => classificationForType,
|
|
18905
|
+
checkPermissionForClassification: () => checkPermissionForClassification,
|
|
18906
|
+
checkPermission: () => checkPermission,
|
|
18907
|
+
SQL_WRITE_OR_DDL_KEYWORDS: () => SQL_WRITE_OR_DDL_KEYWORDS,
|
|
18908
|
+
SQL_DIALECTS: () => SQL_DIALECTS,
|
|
18909
|
+
PermissionError: () => PermissionError
|
|
18910
|
+
});
|
|
18831
18911
|
function classifyStatement(sql) {
|
|
18832
18912
|
const normalized = normalizeSQL(sql);
|
|
18833
18913
|
const stripped = stripCommentsAndStrings(normalized);
|
|
@@ -18893,15 +18973,33 @@ function checkPermission(sql, permission, dialect) {
|
|
|
18893
18973
|
if (permission !== "admin" && containsMultipleStatements(sql, dialect)) {
|
|
18894
18974
|
return {
|
|
18895
18975
|
allowed: false,
|
|
18896
|
-
reason: "
|
|
18897
|
-
classification
|
|
18976
|
+
reason: t("errors.multiple_statements_refused"),
|
|
18977
|
+
classification,
|
|
18978
|
+
requiredPermission: "admin"
|
|
18898
18979
|
};
|
|
18899
18980
|
}
|
|
18981
|
+
return checkPermissionForClassification(classification, permission);
|
|
18982
|
+
}
|
|
18983
|
+
function minimumPermissionFor(type) {
|
|
18984
|
+
return TIER_GRANTS.find((tier) => tier.types.includes(type))?.permission ?? "admin";
|
|
18985
|
+
}
|
|
18986
|
+
function refusalReason(type, permission) {
|
|
18987
|
+
return t_vars("errors.permission_requires_level", {
|
|
18988
|
+
type,
|
|
18989
|
+
minimum: minimumPermissionFor(type),
|
|
18990
|
+
permission
|
|
18991
|
+
});
|
|
18992
|
+
}
|
|
18993
|
+
function checkPermissionForClassification(classification, permission) {
|
|
18900
18994
|
if (permission !== "admin" && classification.escalatedFrom) {
|
|
18901
18995
|
return {
|
|
18902
18996
|
allowed: false,
|
|
18903
|
-
reason:
|
|
18904
|
-
|
|
18997
|
+
reason: t_vars("errors.escalated_write_requires_admin", {
|
|
18998
|
+
keyword: classification.escalatedFrom,
|
|
18999
|
+
permission
|
|
19000
|
+
}),
|
|
19001
|
+
classification,
|
|
19002
|
+
requiredPermission: "admin"
|
|
18905
19003
|
};
|
|
18906
19004
|
}
|
|
18907
19005
|
if (permission === "admin") {
|
|
@@ -18911,220 +19009,64 @@ function checkPermission(sql, permission, dialect) {
|
|
|
18911
19009
|
classification
|
|
18912
19010
|
};
|
|
18913
19011
|
}
|
|
18914
|
-
|
|
18915
|
-
|
|
19012
|
+
const tierIndex = TIER_GRANTS.findIndex((tier) => tier.permission === permission);
|
|
19013
|
+
if (tierIndex !== -1) {
|
|
19014
|
+
const allowedTypes = TIER_GRANTS.slice(0, tierIndex + 1).flatMap((tier) => tier.types);
|
|
18916
19015
|
if (allowedTypes.includes(classification.type)) {
|
|
18917
19016
|
return {
|
|
18918
19017
|
allowed: true,
|
|
18919
|
-
reason: `${classification.type} operation allowed in
|
|
19018
|
+
reason: `${classification.type} operation allowed in ${permission} mode`,
|
|
18920
19019
|
classification
|
|
18921
19020
|
};
|
|
18922
19021
|
}
|
|
19022
|
+
const isUnknown = permission === "query-only" && classification.type === "UNKNOWN";
|
|
18923
19023
|
return {
|
|
18924
19024
|
allowed: false,
|
|
18925
|
-
reason:
|
|
18926
|
-
classification
|
|
18927
|
-
|
|
18928
|
-
}
|
|
18929
|
-
if (permission === "read-write") {
|
|
18930
|
-
const allowedTypes = ["SELECT", "INSERT", "UPDATE", "SHOW", "DESCRIBE", "EXPLAIN"];
|
|
18931
|
-
if (allowedTypes.includes(classification.type)) {
|
|
18932
|
-
return {
|
|
18933
|
-
allowed: true,
|
|
18934
|
-
reason: `${classification.type} operation allowed in read-write mode`,
|
|
18935
|
-
classification
|
|
18936
|
-
};
|
|
18937
|
-
}
|
|
18938
|
-
return {
|
|
18939
|
-
allowed: false,
|
|
18940
|
-
reason: `${classification.type} operation requires data-admin or admin permission`,
|
|
18941
|
-
classification
|
|
18942
|
-
};
|
|
18943
|
-
}
|
|
18944
|
-
if (permission === "query-only") {
|
|
18945
|
-
const allowedTypes = ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN"];
|
|
18946
|
-
if (allowedTypes.includes(classification.type)) {
|
|
18947
|
-
return {
|
|
18948
|
-
allowed: true,
|
|
18949
|
-
reason: `${classification.type} operation allowed in query-only mode`,
|
|
18950
|
-
classification
|
|
18951
|
-
};
|
|
18952
|
-
}
|
|
18953
|
-
const isUnknown = classification.type === "UNKNOWN";
|
|
18954
|
-
return {
|
|
18955
|
-
allowed: false,
|
|
18956
|
-
reason: isUnknown ? `Unrecognised SQL statement (current level: query-only). Security policy requires read-write+ for unknown statements. If this is a legitimate read-only statement, please open an issue at https://github.com/CarlLee1983/dbcli/issues.` : `${classification.type} operation requires read-write or admin permission`,
|
|
18957
|
-
classification
|
|
19025
|
+
reason: isUnknown ? t("errors.unknown_statement_query_only") : refusalReason(classification.type, permission),
|
|
19026
|
+
classification,
|
|
19027
|
+
requiredPermission: isUnknown ? "read-write" : minimumPermissionFor(classification.type)
|
|
18958
19028
|
};
|
|
18959
19029
|
}
|
|
18960
19030
|
return {
|
|
18961
19031
|
allowed: false,
|
|
18962
19032
|
reason: `Unknown permission level: ${permission}`,
|
|
18963
|
-
classification
|
|
19033
|
+
classification,
|
|
19034
|
+
requiredPermission: "admin"
|
|
18964
19035
|
};
|
|
18965
19036
|
}
|
|
18966
|
-
function
|
|
18967
|
-
const result = checkPermission(sql, permission, dialect);
|
|
18968
|
-
if (!result.allowed) {
|
|
18969
|
-
throw new PermissionError(result.reason, result.classification, permission);
|
|
18970
|
-
}
|
|
18971
|
-
return result.classification;
|
|
18972
|
-
}
|
|
18973
|
-
function classifyRedisCommand(command) {
|
|
18974
|
-
const head = command.trim().split(/\s+/)[0]?.toUpperCase() ?? "";
|
|
18975
|
-
const required = REDIS_COMMAND_PERMISSION[head];
|
|
18976
|
-
let type;
|
|
18977
|
-
if (!required)
|
|
18978
|
-
type = "UNKNOWN";
|
|
18979
|
-
else if (required === "query-only")
|
|
18980
|
-
type = "SELECT";
|
|
18981
|
-
else if (required === "read-write")
|
|
18982
|
-
type = "UPDATE";
|
|
18983
|
-
else if (required === "data-admin")
|
|
18984
|
-
type = "DELETE";
|
|
18985
|
-
else
|
|
18986
|
-
type = "DROP";
|
|
19037
|
+
function classificationForType(type) {
|
|
18987
19038
|
return {
|
|
18988
|
-
command: head,
|
|
18989
|
-
requiredPermission: required ?? "unknown",
|
|
18990
19039
|
type,
|
|
18991
|
-
isDangerous:
|
|
18992
|
-
|
|
18993
|
-
}
|
|
18994
|
-
function permissionAtLeast(actual, required) {
|
|
18995
|
-
return PERMISSION_RANK[actual] >= PERMISSION_RANK[required];
|
|
18996
|
-
}
|
|
18997
|
-
function enforceRedisPermission(command, permission) {
|
|
18998
|
-
const classification = classifyRedisCommand(command);
|
|
18999
|
-
const required = classification.requiredPermission;
|
|
19000
|
-
const stmt = {
|
|
19001
|
-
type: classification.type,
|
|
19002
|
-
isDangerous: classification.isDangerous,
|
|
19003
|
-
keywords: [classification.command],
|
|
19040
|
+
isDangerous: isDestructiveOperation(type),
|
|
19041
|
+
keywords: [type],
|
|
19004
19042
|
isComposite: false,
|
|
19005
|
-
confidence:
|
|
19043
|
+
confidence: "HIGH"
|
|
19006
19044
|
};
|
|
19007
|
-
if (required === "unknown") {
|
|
19008
|
-
throw new PermissionError(`Redis command "${classification.command}" is not whitelisted; refusing to execute`, stmt, "admin");
|
|
19009
|
-
}
|
|
19010
|
-
if (!permissionAtLeast(permission, required)) {
|
|
19011
|
-
throw new PermissionError(`Redis command "${classification.command}" requires ${required} permission`, stmt, required);
|
|
19012
|
-
}
|
|
19013
|
-
return classification;
|
|
19014
19045
|
}
|
|
19015
|
-
function
|
|
19016
|
-
|
|
19017
|
-
const path = request.apiPath.toLowerCase();
|
|
19018
|
-
if (path.includes("_bulk")) {
|
|
19019
|
-
return classifyElasticsearchBulk(request.body ?? "");
|
|
19020
|
-
}
|
|
19021
|
-
if (path.includes("_search") || path.includes("_count") || path.includes("_mapping") || path.includes("_settings") || path.includes("_alias") || method === "GET" && (path.includes("_doc") || path.includes("_source"))) {
|
|
19022
|
-
return {
|
|
19023
|
-
type: "SELECT",
|
|
19024
|
-
isDangerous: false,
|
|
19025
|
-
keywords: [method, path],
|
|
19026
|
-
isComposite: false,
|
|
19027
|
-
confidence: "HIGH"
|
|
19028
|
-
};
|
|
19029
|
-
}
|
|
19030
|
-
if (path.includes("_update") || method === "POST" && path.includes("_doc")) {
|
|
19031
|
-
return {
|
|
19032
|
-
type: "UPDATE",
|
|
19033
|
-
isDangerous: false,
|
|
19034
|
-
keywords: [method, path],
|
|
19035
|
-
isComposite: false,
|
|
19036
|
-
confidence: "HIGH"
|
|
19037
|
-
};
|
|
19038
|
-
}
|
|
19039
|
-
if (method === "PUT" && (path.includes("_doc") || path.includes("_create"))) {
|
|
19040
|
-
return {
|
|
19041
|
-
type: "INSERT",
|
|
19042
|
-
isDangerous: false,
|
|
19043
|
-
keywords: [method, path],
|
|
19044
|
-
isComposite: false,
|
|
19045
|
-
confidence: "HIGH"
|
|
19046
|
-
};
|
|
19047
|
-
}
|
|
19048
|
-
if (method === "DELETE") {
|
|
19049
|
-
return {
|
|
19050
|
-
type: "DELETE",
|
|
19051
|
-
isDangerous: true,
|
|
19052
|
-
keywords: [method, path],
|
|
19053
|
-
isComposite: false,
|
|
19054
|
-
confidence: "HIGH"
|
|
19055
|
-
};
|
|
19056
|
-
}
|
|
19057
|
-
return {
|
|
19058
|
-
type: "DROP",
|
|
19059
|
-
isDangerous: true,
|
|
19060
|
-
keywords: [method, path],
|
|
19061
|
-
isComposite: false,
|
|
19062
|
-
confidence: "LOW"
|
|
19063
|
-
};
|
|
19046
|
+
function permitsOperation(type, permission) {
|
|
19047
|
+
return checkPermissionForClassification(classificationForType(type), permission).allowed;
|
|
19064
19048
|
}
|
|
19065
|
-
function
|
|
19066
|
-
const
|
|
19067
|
-
|
|
19068
|
-
|
|
19069
|
-
|
|
19070
|
-
for (const line of lines) {
|
|
19071
|
-
try {
|
|
19072
|
-
const action = JSON.parse(line);
|
|
19073
|
-
const op = Object.keys(action)[0];
|
|
19074
|
-
if (op === "delete") {
|
|
19075
|
-
highestType = "DELETE";
|
|
19076
|
-
isDangerous = true;
|
|
19077
|
-
break;
|
|
19078
|
-
}
|
|
19079
|
-
if (op === "update" && highestType !== "DELETE") {
|
|
19080
|
-
highestType = "UPDATE";
|
|
19081
|
-
}
|
|
19082
|
-
if ((op === "index" || op === "create") && !["DELETE", "UPDATE"].includes(highestType)) {
|
|
19083
|
-
highestType = "INSERT";
|
|
19084
|
-
}
|
|
19085
|
-
} catch {}
|
|
19049
|
+
function enforcePermissionForType(type, permission) {
|
|
19050
|
+
const classification = classificationForType(type);
|
|
19051
|
+
const result = checkPermissionForClassification(classification, permission);
|
|
19052
|
+
if (!result.allowed) {
|
|
19053
|
+
throw new PermissionError(result.reason, classification, minimumPermissionFor(type));
|
|
19086
19054
|
}
|
|
19087
|
-
return {
|
|
19088
|
-
type: highestType,
|
|
19089
|
-
isDangerous,
|
|
19090
|
-
keywords: ["BULK"],
|
|
19091
|
-
isComposite: true,
|
|
19092
|
-
confidence: "HIGH"
|
|
19093
|
-
};
|
|
19094
19055
|
}
|
|
19095
|
-
function
|
|
19096
|
-
const
|
|
19097
|
-
const result = checkElasticsearchPermission(classification, permission);
|
|
19056
|
+
function enforcePermission(sql, permission, dialect) {
|
|
19057
|
+
const result = checkPermission(sql, permission, dialect);
|
|
19098
19058
|
if (!result.allowed) {
|
|
19099
|
-
throw new PermissionError(result.reason, classification,
|
|
19059
|
+
throw new PermissionError(result.reason, result.classification, result.requiredPermission ?? "admin");
|
|
19100
19060
|
}
|
|
19101
|
-
return classification;
|
|
19061
|
+
return result.classification;
|
|
19102
19062
|
}
|
|
19103
|
-
function
|
|
19104
|
-
|
|
19105
|
-
return { allowed: true, reason: "Admin" };
|
|
19106
|
-
if (permission === "data-admin") {
|
|
19107
|
-
const allowed = ["SELECT", "INSERT", "UPDATE", "DELETE"];
|
|
19108
|
-
if (allowed.includes(classification.type))
|
|
19109
|
-
return { allowed: true, reason: "Data-Admin" };
|
|
19110
|
-
}
|
|
19111
|
-
if (permission === "read-write") {
|
|
19112
|
-
const allowed = ["SELECT", "INSERT", "UPDATE"];
|
|
19113
|
-
if (allowed.includes(classification.type))
|
|
19114
|
-
return { allowed: true, reason: "Read-Write" };
|
|
19115
|
-
}
|
|
19116
|
-
if (permission === "query-only") {
|
|
19117
|
-
if (classification.type === "SELECT")
|
|
19118
|
-
return { allowed: true, reason: "Query-Only" };
|
|
19119
|
-
}
|
|
19120
|
-
return {
|
|
19121
|
-
allowed: false,
|
|
19122
|
-
reason: `Elasticsearch ${classification.type} operation requires higher permission tier`
|
|
19123
|
-
};
|
|
19063
|
+
function permissionAtLeast(actual, required) {
|
|
19064
|
+
return PERMISSION_RANK[actual] >= PERMISSION_RANK[required];
|
|
19124
19065
|
}
|
|
19125
|
-
var PermissionError, SQL_DIALECTS, SQL_WRITE_OR_DDL_KEYWORDS, SQL_LOCK_CLAUSE, ESCALATABLE_READ_TYPES,
|
|
19066
|
+
var PermissionError, SQL_DIALECTS, SQL_WRITE_OR_DDL_KEYWORDS, SQL_LOCK_CLAUSE, ESCALATABLE_READ_TYPES, TIER_GRANTS, PERMISSION_RANK;
|
|
19126
19067
|
var init_permission_guard = __esm(() => {
|
|
19127
|
-
|
|
19068
|
+
init_message_loader();
|
|
19069
|
+
init_sql_analysis();
|
|
19128
19070
|
PermissionError = class PermissionError extends Error {
|
|
19129
19071
|
classification;
|
|
19130
19072
|
requiredPermission;
|
|
@@ -19140,90 +19082,11 @@ var init_permission_guard = __esm(() => {
|
|
|
19140
19082
|
SQL_WRITE_OR_DDL_KEYWORDS = /(?<![.\w])(INSERT|UPDATE|DELETE|MERGE|UPSERT|REPLACE|TRUNCATE|DROP|ALTER|CREATE|GRANT|REVOKE|RENAME|INTO)\b(?!\s*\()/i;
|
|
19141
19083
|
SQL_LOCK_CLAUSE = /\bFOR\s+(?:NO\s+KEY\s+)?UPDATE\b|\bFOR\s+(?:KEY\s+)?SHARE\b/gi;
|
|
19142
19084
|
ESCALATABLE_READ_TYPES = new Set(["SELECT", "EXPLAIN", "DESCRIBE"]);
|
|
19143
|
-
|
|
19144
|
-
|
|
19145
|
-
|
|
19146
|
-
|
|
19147
|
-
|
|
19148
|
-
TTL: "query-only",
|
|
19149
|
-
PTTL: "query-only",
|
|
19150
|
-
TYPE: "query-only",
|
|
19151
|
-
SCAN: "query-only",
|
|
19152
|
-
HGET: "query-only",
|
|
19153
|
-
HGETALL: "query-only",
|
|
19154
|
-
HKEYS: "query-only",
|
|
19155
|
-
HVALS: "query-only",
|
|
19156
|
-
HLEN: "query-only",
|
|
19157
|
-
HEXISTS: "query-only",
|
|
19158
|
-
HMGET: "query-only",
|
|
19159
|
-
LRANGE: "query-only",
|
|
19160
|
-
LLEN: "query-only",
|
|
19161
|
-
LINDEX: "query-only",
|
|
19162
|
-
SMEMBERS: "query-only",
|
|
19163
|
-
SCARD: "query-only",
|
|
19164
|
-
SISMEMBER: "query-only",
|
|
19165
|
-
ZRANGE: "query-only",
|
|
19166
|
-
ZREVRANGE: "query-only",
|
|
19167
|
-
ZRANGEBYSCORE: "query-only",
|
|
19168
|
-
ZCARD: "query-only",
|
|
19169
|
-
ZSCORE: "query-only",
|
|
19170
|
-
PING: "query-only",
|
|
19171
|
-
ECHO: "query-only",
|
|
19172
|
-
SET: "read-write",
|
|
19173
|
-
SETEX: "read-write",
|
|
19174
|
-
SETNX: "read-write",
|
|
19175
|
-
PSETEX: "read-write",
|
|
19176
|
-
MSET: "read-write",
|
|
19177
|
-
MSETNX: "read-write",
|
|
19178
|
-
APPEND: "read-write",
|
|
19179
|
-
INCR: "read-write",
|
|
19180
|
-
INCRBY: "read-write",
|
|
19181
|
-
DECR: "read-write",
|
|
19182
|
-
DECRBY: "read-write",
|
|
19183
|
-
HSET: "read-write",
|
|
19184
|
-
HSETNX: "read-write",
|
|
19185
|
-
HMSET: "read-write",
|
|
19186
|
-
HINCRBY: "read-write",
|
|
19187
|
-
LPUSH: "read-write",
|
|
19188
|
-
RPUSH: "read-write",
|
|
19189
|
-
LPOP: "read-write",
|
|
19190
|
-
RPOP: "read-write",
|
|
19191
|
-
LSET: "read-write",
|
|
19192
|
-
LREM: "read-write",
|
|
19193
|
-
SADD: "read-write",
|
|
19194
|
-
SREM: "read-write",
|
|
19195
|
-
ZADD: "read-write",
|
|
19196
|
-
ZREM: "read-write",
|
|
19197
|
-
XADD: "read-write",
|
|
19198
|
-
XDEL: "data-admin",
|
|
19199
|
-
XLEN: "query-only",
|
|
19200
|
-
XREAD: "query-only",
|
|
19201
|
-
XRANGE: "query-only",
|
|
19202
|
-
XREVRANGE: "query-only",
|
|
19203
|
-
EXPIRE: "read-write",
|
|
19204
|
-
EXPIREAT: "read-write",
|
|
19205
|
-
PEXPIRE: "read-write",
|
|
19206
|
-
PERSIST: "read-write",
|
|
19207
|
-
RENAME: "read-write",
|
|
19208
|
-
DEL: "data-admin",
|
|
19209
|
-
UNLINK: "data-admin",
|
|
19210
|
-
HDEL: "data-admin",
|
|
19211
|
-
FLUSHDB: "admin",
|
|
19212
|
-
FLUSHALL: "admin",
|
|
19213
|
-
CONFIG: "admin",
|
|
19214
|
-
INFO: "admin",
|
|
19215
|
-
CLIENT: "admin",
|
|
19216
|
-
DEBUG: "admin",
|
|
19217
|
-
SHUTDOWN: "admin",
|
|
19218
|
-
KEYS: "admin",
|
|
19219
|
-
MONITOR: "admin",
|
|
19220
|
-
SAVE: "admin",
|
|
19221
|
-
BGSAVE: "admin",
|
|
19222
|
-
BGREWRITEAOF: "admin",
|
|
19223
|
-
REPLICAOF: "admin",
|
|
19224
|
-
SLAVEOF: "admin",
|
|
19225
|
-
ACL: "admin"
|
|
19226
|
-
};
|
|
19085
|
+
TIER_GRANTS = [
|
|
19086
|
+
{ permission: "query-only", types: ["SELECT", "SHOW", "DESCRIBE", "EXPLAIN"] },
|
|
19087
|
+
{ permission: "read-write", types: ["INSERT", "UPDATE"] },
|
|
19088
|
+
{ permission: "data-admin", types: ["DELETE"] }
|
|
19089
|
+
];
|
|
19227
19090
|
PERMISSION_RANK = {
|
|
19228
19091
|
"query-only": 1,
|
|
19229
19092
|
"read-write": 2,
|
|
@@ -21914,6 +21777,7 @@ function inferColumnType(value) {
|
|
|
21914
21777
|
}
|
|
21915
21778
|
var init_query_executor = __esm(() => {
|
|
21916
21779
|
init_permission_guard();
|
|
21780
|
+
init_sql_analysis();
|
|
21917
21781
|
init_error_suggester();
|
|
21918
21782
|
init_sql_tables();
|
|
21919
21783
|
init_applied_limit();
|
|
@@ -22213,6 +22077,263 @@ function assertFanOutReadOnlySql(sql, dialect) {
|
|
|
22213
22077
|
var init_query_fanout = __esm(() => {
|
|
22214
22078
|
init_cli_error();
|
|
22215
22079
|
init_permission_guard();
|
|
22080
|
+
init_sql_analysis();
|
|
22081
|
+
});
|
|
22082
|
+
|
|
22083
|
+
// src/core/permission/elasticsearch.ts
|
|
22084
|
+
function classifyElasticsearchRequest(request) {
|
|
22085
|
+
const method = request.method.toUpperCase();
|
|
22086
|
+
const path2 = request.apiPath.toLowerCase();
|
|
22087
|
+
if (path2.includes("_bulk")) {
|
|
22088
|
+
return classifyElasticsearchBulk(request.body ?? "");
|
|
22089
|
+
}
|
|
22090
|
+
const readMethod = method === "GET" || method === "HEAD";
|
|
22091
|
+
const searchPath = path2.includes("_search") || path2.includes("_count");
|
|
22092
|
+
if (searchPath && (readMethod || method === "POST") || readMethod && (path2.includes("_mapping") || path2.includes("_settings") || path2.includes("_alias") || path2.includes("_doc") || path2.includes("_source"))) {
|
|
22093
|
+
return {
|
|
22094
|
+
type: "SELECT",
|
|
22095
|
+
isDangerous: false,
|
|
22096
|
+
keywords: [method, path2],
|
|
22097
|
+
isComposite: false,
|
|
22098
|
+
confidence: "HIGH"
|
|
22099
|
+
};
|
|
22100
|
+
}
|
|
22101
|
+
if (path2.includes("_update") || method === "POST" && path2.includes("_doc")) {
|
|
22102
|
+
return {
|
|
22103
|
+
type: "UPDATE",
|
|
22104
|
+
isDangerous: false,
|
|
22105
|
+
keywords: [method, path2],
|
|
22106
|
+
isComposite: false,
|
|
22107
|
+
confidence: "HIGH"
|
|
22108
|
+
};
|
|
22109
|
+
}
|
|
22110
|
+
if (method === "PUT" && (path2.includes("_doc") || path2.includes("_create"))) {
|
|
22111
|
+
return {
|
|
22112
|
+
type: "INSERT",
|
|
22113
|
+
isDangerous: false,
|
|
22114
|
+
keywords: [method, path2],
|
|
22115
|
+
isComposite: false,
|
|
22116
|
+
confidence: "HIGH"
|
|
22117
|
+
};
|
|
22118
|
+
}
|
|
22119
|
+
if (method === "DELETE") {
|
|
22120
|
+
if (ES_DOCUMENT_PATH.test(path2)) {
|
|
22121
|
+
return {
|
|
22122
|
+
type: "DELETE",
|
|
22123
|
+
isDangerous: true,
|
|
22124
|
+
keywords: [method, path2],
|
|
22125
|
+
isComposite: false,
|
|
22126
|
+
confidence: "HIGH"
|
|
22127
|
+
};
|
|
22128
|
+
}
|
|
22129
|
+
return {
|
|
22130
|
+
type: "DROP",
|
|
22131
|
+
isDangerous: true,
|
|
22132
|
+
keywords: [method, path2],
|
|
22133
|
+
isComposite: false,
|
|
22134
|
+
confidence: "HIGH"
|
|
22135
|
+
};
|
|
22136
|
+
}
|
|
22137
|
+
return {
|
|
22138
|
+
type: "DROP",
|
|
22139
|
+
isDangerous: true,
|
|
22140
|
+
keywords: [method, path2],
|
|
22141
|
+
isComposite: false,
|
|
22142
|
+
confidence: "LOW"
|
|
22143
|
+
};
|
|
22144
|
+
}
|
|
22145
|
+
function classifyElasticsearchBulk(body) {
|
|
22146
|
+
const lines = body.split(`
|
|
22147
|
+
`).filter((l) => l.trim().length > 0);
|
|
22148
|
+
let highestType = "SELECT";
|
|
22149
|
+
let isDangerous = false;
|
|
22150
|
+
for (const line of lines) {
|
|
22151
|
+
try {
|
|
22152
|
+
const action = JSON.parse(line);
|
|
22153
|
+
const op = Object.keys(action)[0];
|
|
22154
|
+
if (op === "delete") {
|
|
22155
|
+
highestType = "DELETE";
|
|
22156
|
+
isDangerous = true;
|
|
22157
|
+
break;
|
|
22158
|
+
}
|
|
22159
|
+
if (op === "update" && highestType !== "DELETE") {
|
|
22160
|
+
highestType = "UPDATE";
|
|
22161
|
+
}
|
|
22162
|
+
if ((op === "index" || op === "create") && !["DELETE", "UPDATE"].includes(highestType)) {
|
|
22163
|
+
highestType = "INSERT";
|
|
22164
|
+
}
|
|
22165
|
+
} catch {}
|
|
22166
|
+
}
|
|
22167
|
+
return {
|
|
22168
|
+
type: highestType,
|
|
22169
|
+
isDangerous,
|
|
22170
|
+
keywords: ["BULK"],
|
|
22171
|
+
isComposite: true,
|
|
22172
|
+
confidence: "HIGH"
|
|
22173
|
+
};
|
|
22174
|
+
}
|
|
22175
|
+
function enforceElasticsearchPermission(request, permission) {
|
|
22176
|
+
const classification = classifyElasticsearchRequest(request);
|
|
22177
|
+
const result = checkElasticsearchPermission(classification, permission);
|
|
22178
|
+
if (!result.allowed) {
|
|
22179
|
+
throw new PermissionError(result.reason, classification, result.requiredPermission ?? "admin");
|
|
22180
|
+
}
|
|
22181
|
+
return classification;
|
|
22182
|
+
}
|
|
22183
|
+
function checkElasticsearchPermission(classification, permission) {
|
|
22184
|
+
if (permitsOperation(classification.type, permission)) {
|
|
22185
|
+
return { allowed: true, reason: `${classification.type} allowed in ${permission} mode` };
|
|
22186
|
+
}
|
|
22187
|
+
const minimum = minimumPermissionFor(classification.type);
|
|
22188
|
+
return {
|
|
22189
|
+
allowed: false,
|
|
22190
|
+
reason: t_vars("errors.elasticsearch_requires_level", {
|
|
22191
|
+
type: classification.type,
|
|
22192
|
+
minimum,
|
|
22193
|
+
permission
|
|
22194
|
+
}),
|
|
22195
|
+
requiredPermission: minimum
|
|
22196
|
+
};
|
|
22197
|
+
}
|
|
22198
|
+
var ES_DOCUMENT_PATH;
|
|
22199
|
+
var init_elasticsearch = __esm(() => {
|
|
22200
|
+
init_permission_guard();
|
|
22201
|
+
init_message_loader();
|
|
22202
|
+
ES_DOCUMENT_PATH = /\/(?:_doc|_source)\/[^/?]+/;
|
|
22203
|
+
});
|
|
22204
|
+
|
|
22205
|
+
// src/core/permission/redis.ts
|
|
22206
|
+
var exports_redis = {};
|
|
22207
|
+
__export(exports_redis, {
|
|
22208
|
+
enforceRedisPermission: () => enforceRedisPermission,
|
|
22209
|
+
classifyRedisCommand: () => classifyRedisCommand
|
|
22210
|
+
});
|
|
22211
|
+
function classifyRedisCommand(command) {
|
|
22212
|
+
const head = command.trim().split(/\s+/)[0]?.toUpperCase() ?? "";
|
|
22213
|
+
const required = REDIS_COMMAND_PERMISSION[head];
|
|
22214
|
+
let type;
|
|
22215
|
+
if (!required)
|
|
22216
|
+
type = "UNKNOWN";
|
|
22217
|
+
else if (required === "query-only")
|
|
22218
|
+
type = "SELECT";
|
|
22219
|
+
else if (required === "read-write")
|
|
22220
|
+
type = "UPDATE";
|
|
22221
|
+
else if (required === "data-admin")
|
|
22222
|
+
type = "DELETE";
|
|
22223
|
+
else
|
|
22224
|
+
type = "DROP";
|
|
22225
|
+
return {
|
|
22226
|
+
command: head,
|
|
22227
|
+
requiredPermission: required ?? "unknown",
|
|
22228
|
+
type,
|
|
22229
|
+
isDangerous: required === "admin" || required === "data-admin"
|
|
22230
|
+
};
|
|
22231
|
+
}
|
|
22232
|
+
function enforceRedisPermission(command, permission) {
|
|
22233
|
+
const classification = classifyRedisCommand(command);
|
|
22234
|
+
const required = classification.requiredPermission;
|
|
22235
|
+
const stmt = {
|
|
22236
|
+
type: classification.type,
|
|
22237
|
+
isDangerous: classification.isDangerous,
|
|
22238
|
+
keywords: [classification.command],
|
|
22239
|
+
isComposite: false,
|
|
22240
|
+
confidence: required === "unknown" ? "LOW" : "HIGH"
|
|
22241
|
+
};
|
|
22242
|
+
if (required === "unknown") {
|
|
22243
|
+
throw new PermissionError(`Redis command "${classification.command}" is not whitelisted; refusing to execute`, stmt, "admin");
|
|
22244
|
+
}
|
|
22245
|
+
if (!permissionAtLeast(permission, required)) {
|
|
22246
|
+
throw new PermissionError(`Redis command "${classification.command}" requires ${required} permission`, stmt, required);
|
|
22247
|
+
}
|
|
22248
|
+
return classification;
|
|
22249
|
+
}
|
|
22250
|
+
var REDIS_COMMAND_PERMISSION;
|
|
22251
|
+
var init_redis = __esm(() => {
|
|
22252
|
+
init_permission_guard();
|
|
22253
|
+
REDIS_COMMAND_PERMISSION = {
|
|
22254
|
+
GET: "query-only",
|
|
22255
|
+
MGET: "query-only",
|
|
22256
|
+
STRLEN: "query-only",
|
|
22257
|
+
EXISTS: "query-only",
|
|
22258
|
+
TTL: "query-only",
|
|
22259
|
+
PTTL: "query-only",
|
|
22260
|
+
TYPE: "query-only",
|
|
22261
|
+
SCAN: "query-only",
|
|
22262
|
+
HGET: "query-only",
|
|
22263
|
+
HGETALL: "query-only",
|
|
22264
|
+
HKEYS: "query-only",
|
|
22265
|
+
HVALS: "query-only",
|
|
22266
|
+
HLEN: "query-only",
|
|
22267
|
+
HEXISTS: "query-only",
|
|
22268
|
+
HMGET: "query-only",
|
|
22269
|
+
LRANGE: "query-only",
|
|
22270
|
+
LLEN: "query-only",
|
|
22271
|
+
LINDEX: "query-only",
|
|
22272
|
+
SMEMBERS: "query-only",
|
|
22273
|
+
SCARD: "query-only",
|
|
22274
|
+
SISMEMBER: "query-only",
|
|
22275
|
+
ZRANGE: "query-only",
|
|
22276
|
+
ZREVRANGE: "query-only",
|
|
22277
|
+
ZRANGEBYSCORE: "query-only",
|
|
22278
|
+
ZCARD: "query-only",
|
|
22279
|
+
ZSCORE: "query-only",
|
|
22280
|
+
PING: "query-only",
|
|
22281
|
+
ECHO: "query-only",
|
|
22282
|
+
SET: "read-write",
|
|
22283
|
+
SETEX: "read-write",
|
|
22284
|
+
SETNX: "read-write",
|
|
22285
|
+
PSETEX: "read-write",
|
|
22286
|
+
MSET: "read-write",
|
|
22287
|
+
MSETNX: "read-write",
|
|
22288
|
+
APPEND: "read-write",
|
|
22289
|
+
INCR: "read-write",
|
|
22290
|
+
INCRBY: "read-write",
|
|
22291
|
+
DECR: "read-write",
|
|
22292
|
+
DECRBY: "read-write",
|
|
22293
|
+
HSET: "read-write",
|
|
22294
|
+
HSETNX: "read-write",
|
|
22295
|
+
HMSET: "read-write",
|
|
22296
|
+
HINCRBY: "read-write",
|
|
22297
|
+
LPUSH: "read-write",
|
|
22298
|
+
RPUSH: "read-write",
|
|
22299
|
+
LPOP: "read-write",
|
|
22300
|
+
RPOP: "read-write",
|
|
22301
|
+
LSET: "read-write",
|
|
22302
|
+
LREM: "read-write",
|
|
22303
|
+
SADD: "read-write",
|
|
22304
|
+
SREM: "read-write",
|
|
22305
|
+
ZADD: "read-write",
|
|
22306
|
+
ZREM: "read-write",
|
|
22307
|
+
XADD: "read-write",
|
|
22308
|
+
XDEL: "data-admin",
|
|
22309
|
+
XLEN: "query-only",
|
|
22310
|
+
XREAD: "query-only",
|
|
22311
|
+
XRANGE: "query-only",
|
|
22312
|
+
XREVRANGE: "query-only",
|
|
22313
|
+
EXPIRE: "read-write",
|
|
22314
|
+
EXPIREAT: "read-write",
|
|
22315
|
+
PEXPIRE: "read-write",
|
|
22316
|
+
PERSIST: "read-write",
|
|
22317
|
+
RENAME: "read-write",
|
|
22318
|
+
DEL: "data-admin",
|
|
22319
|
+
UNLINK: "data-admin",
|
|
22320
|
+
HDEL: "data-admin",
|
|
22321
|
+
FLUSHDB: "admin",
|
|
22322
|
+
FLUSHALL: "admin",
|
|
22323
|
+
CONFIG: "admin",
|
|
22324
|
+
INFO: "admin",
|
|
22325
|
+
CLIENT: "admin",
|
|
22326
|
+
DEBUG: "admin",
|
|
22327
|
+
SHUTDOWN: "admin",
|
|
22328
|
+
KEYS: "admin",
|
|
22329
|
+
MONITOR: "admin",
|
|
22330
|
+
SAVE: "admin",
|
|
22331
|
+
BGSAVE: "admin",
|
|
22332
|
+
BGREWRITEAOF: "admin",
|
|
22333
|
+
REPLICAOF: "admin",
|
|
22334
|
+
SLAVEOF: "admin",
|
|
22335
|
+
ACL: "admin"
|
|
22336
|
+
};
|
|
22216
22337
|
});
|
|
22217
22338
|
|
|
22218
22339
|
// src/commands/query-table-schema.ts
|
|
@@ -22509,7 +22630,7 @@ async function preflightQuery(query, options, context, fieldSelection, multiConn
|
|
|
22509
22630
|
if (multiConnection) {
|
|
22510
22631
|
throw new Error("Redis queries do not support multiple connections");
|
|
22511
22632
|
}
|
|
22512
|
-
const { enforceRedisPermission: enforceRedisPermission2 } = await Promise.resolve().then(() => (
|
|
22633
|
+
const { enforceRedisPermission: enforceRedisPermission2 } = await Promise.resolve().then(() => (init_redis(), exports_redis));
|
|
22513
22634
|
enforceRedisPermission2(query, config.permission);
|
|
22514
22635
|
return;
|
|
22515
22636
|
}
|
|
@@ -22930,7 +23051,7 @@ var init_query = __esm(() => {
|
|
|
22930
23051
|
init_field_projection();
|
|
22931
23052
|
init_connection_selector();
|
|
22932
23053
|
init_query_fanout();
|
|
22933
|
-
|
|
23054
|
+
init_elasticsearch();
|
|
22934
23055
|
init_slow_query_advisory();
|
|
22935
23056
|
ALLOWED_FORMATS3 = ["table", "json", "csv"];
|
|
22936
23057
|
defaultQueryCommandRuntime = {
|
|
@@ -22989,7 +23110,7 @@ function registerQCommand(program2, run) {
|
|
|
22989
23110
|
});
|
|
22990
23111
|
}
|
|
22991
23112
|
function registerInsertCommand(program2, run) {
|
|
22992
|
-
program2.command("insert <table>").description(t("insert.description")).option("--data <json>", "JSON object to insert").option("--dry-run", "Show generated SQL without executing").option("--force", "Skip confirmation prompt").option("--plan", "Analyze risk without connecting or executing").option("--format <type>", "Output format
|
|
23113
|
+
program2.command("insert <table>").description(t("insert.description")).option("--data <json>", "JSON object to insert").option("--dry-run", "Show generated SQL without executing").option("--force", "Skip confirmation prompt").option("--plan", "Analyze risk without connecting or executing").option("--format <type>", "Output format: text or json", "text").option("--recovery", "On failure, emit a structured recovery envelope to stdout (suppresses human stderr message)", false).action(async (table, options, command) => {
|
|
22993
23114
|
try {
|
|
22994
23115
|
await run(table, options, command);
|
|
22995
23116
|
} catch (error) {
|
|
@@ -23003,7 +23124,7 @@ function registerInsertCommand(program2, run) {
|
|
|
23003
23124
|
});
|
|
23004
23125
|
}
|
|
23005
23126
|
function registerUpdateCommand(program2, run) {
|
|
23006
|
-
program2.command("update <table>").description(t("update.description")).option("--where <condition>", 'WHERE clause (required, e.g. "id=1")').option("--set <json>", `JSON with fields to update (required, e.g. '{"name":"Bob"}')`).option("--dry-run", "Show generated SQL without executing").option("--force", "Skip confirmation prompt").option("--plan", "Analyze risk without connecting or executing").option("--format <type>", "Output format
|
|
23127
|
+
program2.command("update <table>").description(t("update.description")).option("--where <condition>", 'WHERE clause (required, e.g. "id=1")').option("--set <json>", `JSON with fields to update (required, e.g. '{"name":"Bob"}')`).option("--dry-run", "Show generated SQL without executing").option("--force", "Skip confirmation prompt").option("--plan", "Analyze risk without connecting or executing").option("--format <type>", "Output format: text or json", "text").option("--recovery", "On failure, emit a structured recovery envelope to stdout (suppresses human stderr message)", false).action(async (table, options, command) => {
|
|
23007
23128
|
try {
|
|
23008
23129
|
await run(table, options, command);
|
|
23009
23130
|
} catch (error) {
|
|
@@ -23017,7 +23138,7 @@ function registerUpdateCommand(program2, run) {
|
|
|
23017
23138
|
});
|
|
23018
23139
|
}
|
|
23019
23140
|
function registerDeleteCommand(program2, run) {
|
|
23020
|
-
program2.command("delete <table>").description(t("delete.description")).option("--where <condition>", 'WHERE clause (required, e.g. "id=1")').option("--dry-run", "Show generated SQL without executing").option("--force", "Skip confirmation prompt").option("--plan", "Analyze risk without connecting or executing").option("--format <type>", "Output format
|
|
23141
|
+
program2.command("delete <table>").description(t("delete.description")).option("--where <condition>", 'WHERE clause (required, e.g. "id=1")').option("--dry-run", "Show generated SQL without executing").option("--force", "Skip confirmation prompt").option("--plan", "Analyze risk without connecting or executing").option("--format <type>", "Output format: text or json", "text").option("--recovery", "On failure, emit a structured recovery envelope to stdout (suppresses human stderr message)", false).action(async (table, options, command) => {
|
|
23021
23142
|
try {
|
|
23022
23143
|
await run(table, options, command);
|
|
23023
23144
|
} catch (error) {
|
|
@@ -23738,7 +23859,7 @@ function deepHasScript(value) {
|
|
|
23738
23859
|
return Object.values(obj).some(deepHasScript);
|
|
23739
23860
|
}
|
|
23740
23861
|
var NAME_RE2, ES_SIZE_CAP = 1000, ES_MAX_RESULT_WINDOW = 1e4, esStrategy;
|
|
23741
|
-
var
|
|
23862
|
+
var init_elasticsearch2 = __esm(() => {
|
|
23742
23863
|
init_types5();
|
|
23743
23864
|
NAME_RE2 = /:([a-zA-Z_][a-zA-Z0-9_]*)/g;
|
|
23744
23865
|
esStrategy = {
|
|
@@ -23836,7 +23957,7 @@ function applyRedisSizeGuard(command, noLimit) {
|
|
|
23836
23957
|
return { command, warnings };
|
|
23837
23958
|
}
|
|
23838
23959
|
var REDIS_NAME_RE, REDIS_RANGE_CAP = 1000, RANGE_VERBS, SCAN_VERBS, REDIS_READONLY_VERBS, REDIS_HARD_REJECT, redisStrategy;
|
|
23839
|
-
var
|
|
23960
|
+
var init_redis2 = __esm(() => {
|
|
23840
23961
|
init_types5();
|
|
23841
23962
|
REDIS_NAME_RE = /:([a-zA-Z_][a-zA-Z0-9_]*)/g;
|
|
23842
23963
|
RANGE_VERBS = new Set(["LRANGE", "ZRANGE", "ZRANGEBYSCORE", "ZRANGEBYLEX"]);
|
|
@@ -24035,8 +24156,8 @@ function getStrategy(family) {
|
|
|
24035
24156
|
}
|
|
24036
24157
|
var init_strategies = __esm(() => {
|
|
24037
24158
|
init_sql();
|
|
24038
|
-
|
|
24039
|
-
|
|
24159
|
+
init_elasticsearch2();
|
|
24160
|
+
init_redis2();
|
|
24040
24161
|
init_mongodb();
|
|
24041
24162
|
});
|
|
24042
24163
|
|
|
@@ -25268,8 +25389,8 @@ class DataExecutor {
|
|
|
25268
25389
|
const timestamp = new Date().toISOString();
|
|
25269
25390
|
try {
|
|
25270
25391
|
this.checkBlacklist("INSERT", tableName);
|
|
25271
|
-
|
|
25272
|
-
return await this.executeMutation("insert", this.buildInsertSql(tableName, data, schema), timestamp, options, {
|
|
25392
|
+
enforcePermissionForType("INSERT", this.permission);
|
|
25393
|
+
return await this.executeMutation("insert", this.buildInsertSql(tableName, data, schema), timestamp, options, { destructive: false });
|
|
25273
25394
|
} catch (error) {
|
|
25274
25395
|
return this.handleMutationError("insert", timestamp, error);
|
|
25275
25396
|
}
|
|
@@ -25278,8 +25399,8 @@ class DataExecutor {
|
|
|
25278
25399
|
const timestamp = new Date().toISOString();
|
|
25279
25400
|
try {
|
|
25280
25401
|
this.checkBlacklist("UPDATE", tableName);
|
|
25281
|
-
|
|
25282
|
-
return await this.executeMutation("update", this.buildUpdateSql(tableName, data, where, schema), timestamp, options, {
|
|
25402
|
+
enforcePermissionForType("UPDATE", this.permission);
|
|
25403
|
+
return await this.executeMutation("update", this.buildUpdateSql(tableName, data, where, schema), timestamp, options, { destructive: false });
|
|
25283
25404
|
} catch (error) {
|
|
25284
25405
|
return this.handleMutationError("update", timestamp, error);
|
|
25285
25406
|
}
|
|
@@ -25288,19 +25409,8 @@ class DataExecutor {
|
|
|
25288
25409
|
const timestamp = new Date().toISOString();
|
|
25289
25410
|
try {
|
|
25290
25411
|
this.checkBlacklist("DELETE", tableName);
|
|
25291
|
-
|
|
25292
|
-
|
|
25293
|
-
status: "error",
|
|
25294
|
-
operation: "delete",
|
|
25295
|
-
rows_affected: 0,
|
|
25296
|
-
timestamp,
|
|
25297
|
-
error: "Permission denied: DELETE operation requires Data-Admin or Admin permission."
|
|
25298
|
-
};
|
|
25299
|
-
}
|
|
25300
|
-
return await this.executeMutation("delete", this.buildDeleteSql(tableName, where, schema), timestamp, options, {
|
|
25301
|
-
warning: "\u26A0\uFE0F Warning: DELETE operation is destructive and cannot be undone!",
|
|
25302
|
-
prompt: "Are you sure you want to execute this DELETE operation? This cannot be undone."
|
|
25303
|
-
});
|
|
25412
|
+
enforcePermissionForType("DELETE", this.permission);
|
|
25413
|
+
return await this.executeMutation("delete", this.buildDeleteSql(tableName, where, schema), timestamp, options, { destructive: true });
|
|
25304
25414
|
} catch (error) {
|
|
25305
25415
|
return this.handleMutationError("delete", timestamp, error);
|
|
25306
25416
|
}
|
|
@@ -25314,31 +25424,31 @@ class DataExecutor {
|
|
|
25314
25424
|
checkBlacklist(operation, tableName) {
|
|
25315
25425
|
this.blacklistValidator?.checkTableBlacklist(operation, tableName, []);
|
|
25316
25426
|
}
|
|
25317
|
-
async executeMutation(operation, statement, timestamp, options,
|
|
25427
|
+
async executeMutation(operation, statement, timestamp, options, { destructive }) {
|
|
25318
25428
|
if (options?.dryRun) {
|
|
25319
|
-
return this.
|
|
25429
|
+
return this.outcomeResult("dry_run", operation, 0, timestamp, statement.sql);
|
|
25320
25430
|
}
|
|
25321
25431
|
if (!options?.force) {
|
|
25322
|
-
if (
|
|
25323
|
-
|
|
25324
|
-
${confirmation.warning}`);
|
|
25432
|
+
if (!options?.confirm) {
|
|
25433
|
+
throw new Error(`Refusing to ${operation} without confirmation: no confirmation handler was supplied. ` + "Pass options.confirm to ask the user, or options.force to proceed unattended.");
|
|
25325
25434
|
}
|
|
25326
|
-
|
|
25327
|
-
|
|
25328
|
-
|
|
25329
|
-
|
|
25330
|
-
|
|
25331
|
-
|
|
25332
|
-
|
|
25333
|
-
|
|
25435
|
+
const proceed = await options.confirm({
|
|
25436
|
+
operation,
|
|
25437
|
+
engine: "sql",
|
|
25438
|
+
sql: statement.sql,
|
|
25439
|
+
params: statement.params,
|
|
25440
|
+
destructive
|
|
25441
|
+
});
|
|
25442
|
+
if (!proceed) {
|
|
25443
|
+
return this.outcomeResult("cancelled", operation, 0, timestamp, statement.sql);
|
|
25334
25444
|
}
|
|
25335
25445
|
}
|
|
25336
25446
|
const result = await this.adapter.execute(statement.sql, statement.params);
|
|
25337
|
-
return this.
|
|
25447
|
+
return this.outcomeResult("success", operation, result.affectedRows, timestamp, statement.sql);
|
|
25338
25448
|
}
|
|
25339
|
-
|
|
25449
|
+
outcomeResult(status2, operation, rowsAffected, timestamp, sql) {
|
|
25340
25450
|
return {
|
|
25341
|
-
status:
|
|
25451
|
+
status: status2,
|
|
25342
25452
|
operation,
|
|
25343
25453
|
rows_affected: rowsAffected,
|
|
25344
25454
|
timestamp,
|
|
@@ -25349,13 +25459,13 @@ Parameters:`);
|
|
|
25349
25459
|
if (error instanceof BlacklistError) {
|
|
25350
25460
|
throw error;
|
|
25351
25461
|
}
|
|
25352
|
-
if (error instanceof PermissionError
|
|
25462
|
+
if (error instanceof PermissionError) {
|
|
25353
25463
|
return {
|
|
25354
25464
|
status: "error",
|
|
25355
25465
|
operation,
|
|
25356
25466
|
rows_affected: 0,
|
|
25357
25467
|
timestamp,
|
|
25358
|
-
error:
|
|
25468
|
+
error: t_vars("errors.permission_denied_reason", { reason: error.message })
|
|
25359
25469
|
};
|
|
25360
25470
|
}
|
|
25361
25471
|
const errorMessage2 = error instanceof Error ? error.message : String(error);
|
|
@@ -25413,8 +25523,152 @@ Parameters:`);
|
|
|
25413
25523
|
}
|
|
25414
25524
|
var init_data_executor = __esm(() => {
|
|
25415
25525
|
init_permission_guard();
|
|
25526
|
+
init_message_loader();
|
|
25527
|
+
init_blacklist();
|
|
25528
|
+
});
|
|
25529
|
+
|
|
25530
|
+
// src/commands/mutation-confirm.ts
|
|
25531
|
+
async function confirmDirectMutation(request) {
|
|
25532
|
+
if (request.force === true)
|
|
25533
|
+
return true;
|
|
25534
|
+
return confirmMutationInteractively({
|
|
25535
|
+
operation: request.operation,
|
|
25536
|
+
engine: request.engine,
|
|
25537
|
+
sql: request.preview,
|
|
25538
|
+
params: [],
|
|
25539
|
+
destructive: request.destructive
|
|
25540
|
+
});
|
|
25541
|
+
}
|
|
25542
|
+
var confirmMutationInteractively = async (request) => {
|
|
25543
|
+
if (request.destructive) {
|
|
25544
|
+
process.stderr.write(`
|
|
25545
|
+
${t("ceremony.confirm_destructive_warning")}
|
|
25546
|
+
`);
|
|
25547
|
+
}
|
|
25548
|
+
const label = request.engine === "sql" ? "ceremony.confirm_sql" : "ceremony.confirm_command";
|
|
25549
|
+
process.stderr.write(`
|
|
25550
|
+
${t(label)}
|
|
25551
|
+
${request.sql}
|
|
25552
|
+
`);
|
|
25553
|
+
if (request.params.length > 0) {
|
|
25554
|
+
process.stderr.write(`
|
|
25555
|
+
${t("ceremony.confirm_params")}
|
|
25556
|
+
${JSON.stringify(request.params, null, 2)}
|
|
25557
|
+
`);
|
|
25558
|
+
}
|
|
25559
|
+
return promptUser.confirm(t(request.destructive ? "ceremony.confirm_destructive_prompt" : "ceremony.confirm_prompt"));
|
|
25560
|
+
}, confirmDdlInteractively = async (request) => {
|
|
25561
|
+
process.stderr.write(`
|
|
25562
|
+
${t("ceremony.confirm_destructive_warning_ddl")}
|
|
25563
|
+
`);
|
|
25564
|
+
process.stderr.write(`
|
|
25565
|
+
${t("ceremony.confirm_sql")}
|
|
25566
|
+
${request.sql}
|
|
25567
|
+
`);
|
|
25568
|
+
return promptUser.confirm(t_vars("ceremony.confirm_ddl_prompt", { operation: request.operation }));
|
|
25569
|
+
};
|
|
25570
|
+
var init_mutation_confirm = __esm(() => {
|
|
25571
|
+
init_message_loader();
|
|
25416
25572
|
init_prompts();
|
|
25573
|
+
});
|
|
25574
|
+
|
|
25575
|
+
// src/commands/mutation-audit.ts
|
|
25576
|
+
function auditOutcomeForMutation(result, table, recoveryRef) {
|
|
25577
|
+
return {
|
|
25578
|
+
success: result.status === "success" || result.status === "dry_run",
|
|
25579
|
+
target: table,
|
|
25580
|
+
...recoveryRef && { recovery_ref: recoveryRef },
|
|
25581
|
+
...result.sql && { sql: result.sql },
|
|
25582
|
+
metadata: {
|
|
25583
|
+
rows_affected: result.rows_affected,
|
|
25584
|
+
outcome: result.status,
|
|
25585
|
+
...result.status === "dry_run" && { dry_run: true }
|
|
25586
|
+
},
|
|
25587
|
+
...result.status === "error" && {
|
|
25588
|
+
error: new Error(result.error ?? `${result.operation} failed`)
|
|
25589
|
+
}
|
|
25590
|
+
};
|
|
25591
|
+
}
|
|
25592
|
+
|
|
25593
|
+
// src/commands/mutation-outcome.ts
|
|
25594
|
+
function shouldRenderForHuman(context) {
|
|
25595
|
+
if (context.format === "json")
|
|
25596
|
+
return false;
|
|
25597
|
+
return context.isTTY;
|
|
25598
|
+
}
|
|
25599
|
+
function humanOutputContext(options) {
|
|
25600
|
+
return { isTTY: process.stdout.isTTY === true, ...options.format && { format: options.format } };
|
|
25601
|
+
}
|
|
25602
|
+
function renderMutationOutcome(result, table, elapsedMs) {
|
|
25603
|
+
if (result.status === "error") {
|
|
25604
|
+
return [
|
|
25605
|
+
t_vars("ceremony.failed", { reason: result.error ?? t("ceremony.unknown_error") }),
|
|
25606
|
+
t("ceremony.recovery_retry")
|
|
25607
|
+
];
|
|
25608
|
+
}
|
|
25609
|
+
if (result.status === "cancelled") {
|
|
25610
|
+
return [t_vars("ceremony.cancelled", { table })];
|
|
25611
|
+
}
|
|
25612
|
+
if (result.status === "dry_run") {
|
|
25613
|
+
return [t_vars("ceremony.dry_run", { table })];
|
|
25614
|
+
}
|
|
25615
|
+
const elapsed = t_vars("ceremony.elapsed", { seconds: (elapsedMs / 1000).toFixed(2) });
|
|
25616
|
+
if (result.rows_affected === 0) {
|
|
25617
|
+
return [t_vars("ceremony.matched_nothing", { table }), elapsed];
|
|
25618
|
+
}
|
|
25619
|
+
return [
|
|
25620
|
+
t_vars(VERB_KEY[result.operation], { count: result.rows_affected, table }),
|
|
25621
|
+
elapsed,
|
|
25622
|
+
t("ceremony.recovery_none")
|
|
25623
|
+
];
|
|
25624
|
+
}
|
|
25625
|
+
function printMutationOutcome(result, table, elapsedMs, context, envelopeExtras = {}) {
|
|
25626
|
+
if (shouldRenderForHuman(context)) {
|
|
25627
|
+
const writeLine = result.status === "error" ? console.error : console.log;
|
|
25628
|
+
for (const line of renderMutationOutcome(result, table, elapsedMs))
|
|
25629
|
+
writeLine(line);
|
|
25630
|
+
return;
|
|
25631
|
+
}
|
|
25632
|
+
console.log(JSON.stringify({ ...mutationEnvelope(result), ...envelopeExtras }, null, 2));
|
|
25633
|
+
}
|
|
25634
|
+
function printMutationFailure(error, operation, context) {
|
|
25635
|
+
if (shouldRenderForHuman(context)) {
|
|
25636
|
+
console.error(t_vars("ceremony.failed", { reason: error.message }));
|
|
25637
|
+
if (error instanceof BlacklistError) {
|
|
25638
|
+
console.error(t("ceremony.blacklist_hint"));
|
|
25639
|
+
}
|
|
25640
|
+
return;
|
|
25641
|
+
}
|
|
25642
|
+
console.log(JSON.stringify({ status: "error", operation, rows_affected: 0, error: error.message }, null, 2));
|
|
25643
|
+
}
|
|
25644
|
+
function cancelledOutcome(operation, preview) {
|
|
25645
|
+
return {
|
|
25646
|
+
status: "cancelled",
|
|
25647
|
+
operation,
|
|
25648
|
+
rows_affected: 0,
|
|
25649
|
+
sql: preview,
|
|
25650
|
+
timestamp: new Date().toISOString()
|
|
25651
|
+
};
|
|
25652
|
+
}
|
|
25653
|
+
function mutationEnvelope(result) {
|
|
25654
|
+
return {
|
|
25655
|
+
status: result.status,
|
|
25656
|
+
operation: result.operation,
|
|
25657
|
+
rows_affected: result.rows_affected,
|
|
25658
|
+
...result.timestamp && { timestamp: result.timestamp },
|
|
25659
|
+
...result.sql && { sql: result.sql },
|
|
25660
|
+
...result.error && { error: result.error }
|
|
25661
|
+
};
|
|
25662
|
+
}
|
|
25663
|
+
var VERB_KEY;
|
|
25664
|
+
var init_mutation_outcome = __esm(() => {
|
|
25665
|
+
init_message_loader();
|
|
25417
25666
|
init_blacklist();
|
|
25667
|
+
VERB_KEY = {
|
|
25668
|
+
insert: "ceremony.inserted",
|
|
25669
|
+
update: "ceremony.updated",
|
|
25670
|
+
delete: "ceremony.deleted"
|
|
25671
|
+
};
|
|
25418
25672
|
});
|
|
25419
25673
|
|
|
25420
25674
|
// src/core/mongo/dry-run-formatter.ts
|
|
@@ -26258,6 +26512,9 @@ async function insertCommand(table, options, command) {
|
|
|
26258
26512
|
throw new Error("Table name required");
|
|
26259
26513
|
}
|
|
26260
26514
|
table = table.trim();
|
|
26515
|
+
if (options.format !== undefined && options.format !== "text" && options.format !== "json") {
|
|
26516
|
+
throw new Error(t_vars("errors.invalid_output_format", { format: options.format }));
|
|
26517
|
+
}
|
|
26261
26518
|
let jsonInput = "";
|
|
26262
26519
|
if (options.data) {
|
|
26263
26520
|
jsonInput = options.data;
|
|
@@ -26298,30 +26555,40 @@ async function insertCommand(table, options, command) {
|
|
|
26298
26555
|
return;
|
|
26299
26556
|
}
|
|
26300
26557
|
if (config.connection.system === "redis") {
|
|
26301
|
-
|
|
26558
|
+
enforcePermissionForType("INSERT", config.permission);
|
|
26302
26559
|
const blacklistManager = new BlacklistManager(config);
|
|
26303
26560
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
26304
26561
|
blacklistValidator.checkTableBlacklist("INSERT", table, []);
|
|
26305
26562
|
blacklistValidator.checkColumnBlacklistOnWrite(table, Object.keys(data), "INSERT");
|
|
26563
|
+
const preview = `SET ${table} ... (Redis Insert)`;
|
|
26306
26564
|
if (options.dryRun) {
|
|
26307
26565
|
const output = {
|
|
26308
|
-
status: "
|
|
26566
|
+
status: "dry_run",
|
|
26309
26567
|
operation: "insert",
|
|
26310
26568
|
rows_affected: 0,
|
|
26311
|
-
sql:
|
|
26569
|
+
sql: preview,
|
|
26312
26570
|
timestamp: new Date().toISOString()
|
|
26313
26571
|
};
|
|
26314
|
-
|
|
26315
|
-
await writeAuditEntry(config, "insert", options,
|
|
26316
|
-
|
|
26317
|
-
|
|
26318
|
-
|
|
26319
|
-
|
|
26572
|
+
printMutationOutcome(output, table, 0, humanOutputContext(options));
|
|
26573
|
+
await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
|
|
26574
|
+
return;
|
|
26575
|
+
}
|
|
26576
|
+
if (!await confirmDirectMutation({
|
|
26577
|
+
operation: "insert",
|
|
26578
|
+
engine: "redis",
|
|
26579
|
+
preview,
|
|
26580
|
+
destructive: false,
|
|
26581
|
+
force: options.force
|
|
26582
|
+
})) {
|
|
26583
|
+
const output = cancelledOutcome("insert", preview);
|
|
26584
|
+
printMutationOutcome(output, table, 0, humanOutputContext(options));
|
|
26585
|
+
await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
|
|
26320
26586
|
return;
|
|
26321
26587
|
}
|
|
26322
26588
|
const adapter2 = AdapterFactory.createRedisAdapter(config.connection, config.blacklist?.tables ?? []);
|
|
26323
26589
|
await adapter2.connect();
|
|
26324
26590
|
try {
|
|
26591
|
+
const startedAt = performance.now();
|
|
26325
26592
|
const result = await adapter2.insert(table, data);
|
|
26326
26593
|
const output = {
|
|
26327
26594
|
status: "success",
|
|
@@ -26329,12 +26596,8 @@ async function insertCommand(table, options, command) {
|
|
|
26329
26596
|
rows_affected: result.affectedRows,
|
|
26330
26597
|
timestamp: new Date().toISOString()
|
|
26331
26598
|
};
|
|
26332
|
-
|
|
26333
|
-
await writeAuditEntry(config, "insert", options,
|
|
26334
|
-
success: true,
|
|
26335
|
-
target: table,
|
|
26336
|
-
metadata: { rows_affected: result.affectedRows }
|
|
26337
|
-
});
|
|
26599
|
+
printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options));
|
|
26600
|
+
await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
|
|
26338
26601
|
return;
|
|
26339
26602
|
} finally {
|
|
26340
26603
|
await adapter2.disconnect();
|
|
@@ -26346,7 +26609,7 @@ async function insertCommand(table, options, command) {
|
|
|
26346
26609
|
operation: "insert",
|
|
26347
26610
|
rows_affected: 0,
|
|
26348
26611
|
timestamp: new Date().toISOString(),
|
|
26349
|
-
error: "
|
|
26612
|
+
error: t("insert.elasticsearch_unsupported")
|
|
26350
26613
|
};
|
|
26351
26614
|
await writeAuditEntry(config, "insert", options, {
|
|
26352
26615
|
success: false,
|
|
@@ -26357,44 +26620,49 @@ async function insertCommand(table, options, command) {
|
|
|
26357
26620
|
process.exit(1);
|
|
26358
26621
|
}
|
|
26359
26622
|
if (config.connection.system === "mongodb") {
|
|
26360
|
-
|
|
26623
|
+
enforcePermissionForType("INSERT", config.permission);
|
|
26361
26624
|
const blacklistManager = new BlacklistManager(config);
|
|
26362
26625
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
26363
26626
|
blacklistValidator.checkTableBlacklist("INSERT", table, []);
|
|
26364
26627
|
blacklistValidator.checkColumnBlacklistOnWrite(table, Object.keys(data), "INSERT");
|
|
26628
|
+
const preview = previewInsert(table, data);
|
|
26365
26629
|
if (options.dryRun) {
|
|
26366
26630
|
const output = {
|
|
26367
|
-
status: "
|
|
26631
|
+
status: "dry_run",
|
|
26368
26632
|
operation: "insert",
|
|
26369
26633
|
rows_affected: 0,
|
|
26370
|
-
sql:
|
|
26634
|
+
sql: preview,
|
|
26371
26635
|
timestamp: new Date().toISOString()
|
|
26372
26636
|
};
|
|
26373
|
-
|
|
26374
|
-
await writeAuditEntry(config, "insert", options,
|
|
26375
|
-
|
|
26376
|
-
|
|
26377
|
-
|
|
26378
|
-
|
|
26637
|
+
printMutationOutcome(output, table, 0, humanOutputContext(options));
|
|
26638
|
+
await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
|
|
26639
|
+
return;
|
|
26640
|
+
}
|
|
26641
|
+
if (!await confirmDirectMutation({
|
|
26642
|
+
operation: "insert",
|
|
26643
|
+
engine: "mongodb",
|
|
26644
|
+
preview,
|
|
26645
|
+
destructive: false,
|
|
26646
|
+
force: options.force
|
|
26647
|
+
})) {
|
|
26648
|
+
const output = cancelledOutcome("insert", preview);
|
|
26649
|
+
printMutationOutcome(output, table, 0, humanOutputContext(options));
|
|
26650
|
+
await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
|
|
26379
26651
|
return;
|
|
26380
26652
|
}
|
|
26381
26653
|
const adapter2 = AdapterFactory.createMongoDBAdapter(config.connection);
|
|
26382
26654
|
await adapter2.connect();
|
|
26383
26655
|
try {
|
|
26656
|
+
const startedAt = performance.now();
|
|
26384
26657
|
const result = await adapter2.insert(table, data);
|
|
26385
26658
|
const output = {
|
|
26386
26659
|
status: "success",
|
|
26387
26660
|
operation: "insert",
|
|
26388
26661
|
rows_affected: result.affectedRows,
|
|
26389
|
-
timestamp: new Date().toISOString()
|
|
26390
|
-
lastInsertId: result.lastInsertId
|
|
26662
|
+
timestamp: new Date().toISOString()
|
|
26391
26663
|
};
|
|
26392
|
-
|
|
26393
|
-
await writeAuditEntry(config, "insert", options,
|
|
26394
|
-
success: true,
|
|
26395
|
-
target: table,
|
|
26396
|
-
metadata: { rows_affected: result.affectedRows }
|
|
26397
|
-
});
|
|
26664
|
+
printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options), { lastInsertId: result.lastInsertId });
|
|
26665
|
+
await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(output, table));
|
|
26398
26666
|
return;
|
|
26399
26667
|
} finally {
|
|
26400
26668
|
await adapter2.disconnect();
|
|
@@ -26408,31 +26676,20 @@ async function insertCommand(table, options, command) {
|
|
|
26408
26676
|
const blacklistManager = new BlacklistManager(config);
|
|
26409
26677
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
26410
26678
|
const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
|
|
26679
|
+
const startedAt = performance.now();
|
|
26411
26680
|
const result = await executor3.executeInsert(table, data, schema, {
|
|
26412
26681
|
dryRun: options.dryRun,
|
|
26413
|
-
force: options.force
|
|
26414
|
-
|
|
26415
|
-
const output = {
|
|
26416
|
-
status: result.status,
|
|
26417
|
-
operation: result.operation,
|
|
26418
|
-
rows_affected: result.rows_affected,
|
|
26419
|
-
timestamp: result.timestamp,
|
|
26420
|
-
...result.sql && { sql: result.sql },
|
|
26421
|
-
...result.error && { error: result.error }
|
|
26422
|
-
};
|
|
26423
|
-
console.log(JSON.stringify(output, null, 2));
|
|
26424
|
-
await writeAuditEntry(config, "insert", options, {
|
|
26425
|
-
success: result.status === "success",
|
|
26426
|
-
target: table,
|
|
26427
|
-
...result.sql && { sql: result.sql },
|
|
26428
|
-
metadata: {
|
|
26429
|
-
rows_affected: result.rows_affected,
|
|
26430
|
-
...options.dryRun && { dry_run: true }
|
|
26431
|
-
},
|
|
26432
|
-
...result.status === "error" && {
|
|
26433
|
-
error: new Error(result.error ?? "insert failed")
|
|
26434
|
-
}
|
|
26682
|
+
force: options.force,
|
|
26683
|
+
confirm: confirmMutationInteractively
|
|
26435
26684
|
});
|
|
26685
|
+
const elapsedMs = performance.now() - startedAt;
|
|
26686
|
+
const recoveryEnvelopeId = result.status === "error" && options.recovery === true ? crypto5.randomUUID() : undefined;
|
|
26687
|
+
const auditId = await writeAuditEntry(config, "insert", options, auditOutcomeForMutation(result, table, recoveryEnvelopeId));
|
|
26688
|
+
if (recoveryEnvelopeId !== undefined) {
|
|
26689
|
+
const { emitRecoveryEnvelope: emitRecoveryEnvelope2 } = await Promise.resolve().then(() => (init_recovery(), exports_recovery));
|
|
26690
|
+
emitRecoveryEnvelope2(new Error(result.error ?? "INSERT failed"), { operation: "insert", table, writeOperation: "INSERT" }, { envelopeId: recoveryEnvelopeId, auditRef: auditId ?? undefined });
|
|
26691
|
+
}
|
|
26692
|
+
printMutationOutcome(result, table, elapsedMs, humanOutputContext(options));
|
|
26436
26693
|
if (result.status === "error") {
|
|
26437
26694
|
process.exit(1);
|
|
26438
26695
|
}
|
|
@@ -26458,13 +26715,7 @@ async function insertCommand(table, options, command) {
|
|
|
26458
26715
|
emitRecoveryEnvelope2(error, { operation: "insert", table, writeOperation: "INSERT" }, { envelopeId, auditRef: auditId ?? undefined });
|
|
26459
26716
|
}
|
|
26460
26717
|
if (error instanceof BlacklistError) {
|
|
26461
|
-
|
|
26462
|
-
status: "error",
|
|
26463
|
-
operation: "insert",
|
|
26464
|
-
rows_affected: 0,
|
|
26465
|
-
error: error.message
|
|
26466
|
-
};
|
|
26467
|
-
console.log(JSON.stringify(output2, null, 2));
|
|
26718
|
+
printMutationFailure(error, "insert", humanOutputContext(options));
|
|
26468
26719
|
process.exit(1);
|
|
26469
26720
|
}
|
|
26470
26721
|
if (error instanceof PermissionError) {
|
|
@@ -26477,13 +26728,7 @@ async function insertCommand(table, options, command) {
|
|
|
26477
26728
|
printLocalizedCliError(formatCliError(presentConnectionError(error)), error);
|
|
26478
26729
|
process.exit(1);
|
|
26479
26730
|
}
|
|
26480
|
-
|
|
26481
|
-
status: "error",
|
|
26482
|
-
operation: "insert",
|
|
26483
|
-
rows_affected: 0,
|
|
26484
|
-
error: error.message
|
|
26485
|
-
};
|
|
26486
|
-
console.log(JSON.stringify(output, null, 2));
|
|
26731
|
+
printMutationFailure(error, "insert", humanOutputContext(options));
|
|
26487
26732
|
process.exit(1);
|
|
26488
26733
|
}
|
|
26489
26734
|
}
|
|
@@ -26493,6 +26738,8 @@ var init_insert = __esm(() => {
|
|
|
26493
26738
|
init_connection_error_message();
|
|
26494
26739
|
init_adapters();
|
|
26495
26740
|
init_data_executor();
|
|
26741
|
+
init_mutation_confirm();
|
|
26742
|
+
init_mutation_outcome();
|
|
26496
26743
|
init_config();
|
|
26497
26744
|
init_permission_guard();
|
|
26498
26745
|
init_blacklist_validator();
|
|
@@ -26521,6 +26768,9 @@ async function updateCommand(table, options, command) {
|
|
|
26521
26768
|
throw new Error("Table name required");
|
|
26522
26769
|
}
|
|
26523
26770
|
table = table.trim();
|
|
26771
|
+
if (options.format !== undefined && options.format !== "text" && options.format !== "json") {
|
|
26772
|
+
throw new Error(t_vars("errors.invalid_output_format", { format: options.format }));
|
|
26773
|
+
}
|
|
26524
26774
|
if (!options.plan && (!options.where || options.where.trim() === "")) {
|
|
26525
26775
|
throw new Error('UPDATE requires --where clause (e.g. --where "id=1")');
|
|
26526
26776
|
}
|
|
@@ -26581,29 +26831,39 @@ async function updateCommand(table, options, command) {
|
|
|
26581
26831
|
return;
|
|
26582
26832
|
}
|
|
26583
26833
|
if (config.connection?.system === "redis") {
|
|
26584
|
-
|
|
26834
|
+
enforcePermissionForType("UPDATE", config.permission);
|
|
26585
26835
|
const blacklistManager = new BlacklistManager(config);
|
|
26586
26836
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
26587
26837
|
blacklistValidator.checkTableBlacklist("UPDATE", table, []);
|
|
26838
|
+
const preview = `HSET ${table} ... (Redis Update)`;
|
|
26588
26839
|
if (options.dryRun) {
|
|
26589
26840
|
const output = {
|
|
26590
|
-
status: "
|
|
26841
|
+
status: "dry_run",
|
|
26591
26842
|
operation: "update",
|
|
26592
26843
|
rows_affected: 0,
|
|
26593
|
-
sql:
|
|
26844
|
+
sql: preview,
|
|
26594
26845
|
timestamp: new Date().toISOString()
|
|
26595
26846
|
};
|
|
26596
|
-
|
|
26597
|
-
await writeAuditEntry(config, "update", options,
|
|
26598
|
-
|
|
26599
|
-
|
|
26600
|
-
|
|
26601
|
-
|
|
26847
|
+
printMutationOutcome(output, table, 0, humanOutputContext(options));
|
|
26848
|
+
await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
|
|
26849
|
+
return;
|
|
26850
|
+
}
|
|
26851
|
+
if (!await confirmDirectMutation({
|
|
26852
|
+
operation: "update",
|
|
26853
|
+
engine: "redis",
|
|
26854
|
+
preview,
|
|
26855
|
+
destructive: false,
|
|
26856
|
+
force: options.force
|
|
26857
|
+
})) {
|
|
26858
|
+
const output = cancelledOutcome("update", preview);
|
|
26859
|
+
printMutationOutcome(output, table, 0, humanOutputContext(options));
|
|
26860
|
+
await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
|
|
26602
26861
|
return;
|
|
26603
26862
|
}
|
|
26604
26863
|
const adapter2 = AdapterFactory.createRedisAdapter(config.connection, config.blacklist?.tables ?? []);
|
|
26605
26864
|
await adapter2.connect();
|
|
26606
26865
|
try {
|
|
26866
|
+
const startedAt = performance.now();
|
|
26607
26867
|
const result = await adapter2.update(table, {}, setData);
|
|
26608
26868
|
const output = {
|
|
26609
26869
|
status: "success",
|
|
@@ -26611,12 +26871,8 @@ async function updateCommand(table, options, command) {
|
|
|
26611
26871
|
rows_affected: result.affectedRows,
|
|
26612
26872
|
timestamp: new Date().toISOString()
|
|
26613
26873
|
};
|
|
26614
|
-
|
|
26615
|
-
await writeAuditEntry(config, "update", options,
|
|
26616
|
-
success: true,
|
|
26617
|
-
target: table,
|
|
26618
|
-
metadata: { rows_affected: result.affectedRows }
|
|
26619
|
-
});
|
|
26874
|
+
printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options));
|
|
26875
|
+
await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
|
|
26620
26876
|
return;
|
|
26621
26877
|
} finally {
|
|
26622
26878
|
await adapter2.disconnect();
|
|
@@ -26628,7 +26884,7 @@ async function updateCommand(table, options, command) {
|
|
|
26628
26884
|
operation: "update",
|
|
26629
26885
|
rows_affected: 0,
|
|
26630
26886
|
timestamp: new Date().toISOString(),
|
|
26631
|
-
error: "
|
|
26887
|
+
error: t("update.elasticsearch_unsupported")
|
|
26632
26888
|
};
|
|
26633
26889
|
await writeAuditEntry(config, "update", options, {
|
|
26634
26890
|
success: false,
|
|
@@ -26639,7 +26895,7 @@ async function updateCommand(table, options, command) {
|
|
|
26639
26895
|
process.exit(1);
|
|
26640
26896
|
}
|
|
26641
26897
|
if (config.connection?.system === "mongodb") {
|
|
26642
|
-
|
|
26898
|
+
enforcePermissionForType("UPDATE", config.permission);
|
|
26643
26899
|
const hasOperator = Object.keys(setData).some((key) => key.startsWith("$"));
|
|
26644
26900
|
const updateDoc = hasOperator ? setData : { $set: setData };
|
|
26645
26901
|
const writtenFields = new Set;
|
|
@@ -26665,25 +26921,35 @@ async function updateCommand(table, options, command) {
|
|
|
26665
26921
|
} catch {
|
|
26666
26922
|
filter = parseWhereClause(options.where);
|
|
26667
26923
|
}
|
|
26924
|
+
const preview = previewUpdate(table, filter, updateDoc);
|
|
26668
26925
|
if (options.dryRun) {
|
|
26669
26926
|
const output = {
|
|
26670
|
-
status: "
|
|
26927
|
+
status: "dry_run",
|
|
26671
26928
|
operation: "update",
|
|
26672
26929
|
rows_affected: 0,
|
|
26673
|
-
sql:
|
|
26930
|
+
sql: preview,
|
|
26674
26931
|
timestamp: new Date().toISOString()
|
|
26675
26932
|
};
|
|
26676
|
-
|
|
26677
|
-
await writeAuditEntry(config, "update", options,
|
|
26678
|
-
|
|
26679
|
-
|
|
26680
|
-
|
|
26681
|
-
|
|
26933
|
+
printMutationOutcome(output, table, 0, humanOutputContext(options));
|
|
26934
|
+
await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
|
|
26935
|
+
return;
|
|
26936
|
+
}
|
|
26937
|
+
if (!await confirmDirectMutation({
|
|
26938
|
+
operation: "update",
|
|
26939
|
+
engine: "mongodb",
|
|
26940
|
+
preview,
|
|
26941
|
+
destructive: false,
|
|
26942
|
+
force: options.force
|
|
26943
|
+
})) {
|
|
26944
|
+
const output = cancelledOutcome("update", preview);
|
|
26945
|
+
printMutationOutcome(output, table, 0, humanOutputContext(options));
|
|
26946
|
+
await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
|
|
26682
26947
|
return;
|
|
26683
26948
|
}
|
|
26684
26949
|
const adapter2 = AdapterFactory.createMongoDBAdapter(config.connection);
|
|
26685
26950
|
await adapter2.connect();
|
|
26686
26951
|
try {
|
|
26952
|
+
const startedAt = performance.now();
|
|
26687
26953
|
const result = await adapter2.update(table, filter, updateDoc);
|
|
26688
26954
|
const output = {
|
|
26689
26955
|
status: "success",
|
|
@@ -26691,12 +26957,8 @@ async function updateCommand(table, options, command) {
|
|
|
26691
26957
|
rows_affected: result.affectedRows,
|
|
26692
26958
|
timestamp: new Date().toISOString()
|
|
26693
26959
|
};
|
|
26694
|
-
|
|
26695
|
-
await writeAuditEntry(config, "update", options,
|
|
26696
|
-
success: true,
|
|
26697
|
-
target: table,
|
|
26698
|
-
metadata: { rows_affected: result.affectedRows }
|
|
26699
|
-
});
|
|
26960
|
+
printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options));
|
|
26961
|
+
await writeAuditEntry(config, "update", options, auditOutcomeForMutation(output, table));
|
|
26700
26962
|
return;
|
|
26701
26963
|
} finally {
|
|
26702
26964
|
await adapter2.disconnect();
|
|
@@ -26716,31 +26978,20 @@ async function updateCommand(table, options, command) {
|
|
|
26716
26978
|
const blacklistManager = new BlacklistManager(config);
|
|
26717
26979
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
26718
26980
|
const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
|
|
26981
|
+
const startedAt = performance.now();
|
|
26719
26982
|
const result = await executor3.executeUpdate(table, setData, whereConditions, schema, {
|
|
26720
26983
|
dryRun: options.dryRun,
|
|
26721
|
-
force: options.force
|
|
26722
|
-
|
|
26723
|
-
const output = {
|
|
26724
|
-
status: result.status,
|
|
26725
|
-
operation: result.operation,
|
|
26726
|
-
rows_affected: result.rows_affected,
|
|
26727
|
-
timestamp: result.timestamp,
|
|
26728
|
-
...result.sql && { sql: result.sql },
|
|
26729
|
-
...result.error && { error: result.error }
|
|
26730
|
-
};
|
|
26731
|
-
console.log(JSON.stringify(output, null, 2));
|
|
26732
|
-
await writeAuditEntry(config, "update", options, {
|
|
26733
|
-
success: result.status === "success",
|
|
26734
|
-
target: table,
|
|
26735
|
-
...result.sql && { sql: result.sql },
|
|
26736
|
-
metadata: {
|
|
26737
|
-
rows_affected: result.rows_affected,
|
|
26738
|
-
...options.dryRun && { dry_run: true }
|
|
26739
|
-
},
|
|
26740
|
-
...result.status === "error" && {
|
|
26741
|
-
error: new Error(result.error ?? "update failed")
|
|
26742
|
-
}
|
|
26984
|
+
force: options.force,
|
|
26985
|
+
confirm: confirmMutationInteractively
|
|
26743
26986
|
});
|
|
26987
|
+
const elapsedMs = performance.now() - startedAt;
|
|
26988
|
+
const recoveryEnvelopeId = result.status === "error" && options.recovery === true ? crypto6.randomUUID() : undefined;
|
|
26989
|
+
const auditId = await writeAuditEntry(config, "update", options, auditOutcomeForMutation(result, table, recoveryEnvelopeId));
|
|
26990
|
+
if (recoveryEnvelopeId !== undefined) {
|
|
26991
|
+
const { emitRecoveryEnvelope: emitRecoveryEnvelope2 } = await Promise.resolve().then(() => (init_recovery(), exports_recovery));
|
|
26992
|
+
emitRecoveryEnvelope2(new Error(result.error ?? "UPDATE failed"), { operation: "update", table, writeOperation: "UPDATE" }, { envelopeId: recoveryEnvelopeId, auditRef: auditId ?? undefined });
|
|
26993
|
+
}
|
|
26994
|
+
printMutationOutcome(result, table, elapsedMs, humanOutputContext(options));
|
|
26744
26995
|
if (result.status === "error") {
|
|
26745
26996
|
process.exit(1);
|
|
26746
26997
|
}
|
|
@@ -26766,13 +27017,7 @@ async function updateCommand(table, options, command) {
|
|
|
26766
27017
|
emitRecoveryEnvelope2(error, { operation: "update", table, writeOperation: "UPDATE" }, { envelopeId, auditRef: auditId ?? undefined });
|
|
26767
27018
|
}
|
|
26768
27019
|
if (error instanceof BlacklistError) {
|
|
26769
|
-
|
|
26770
|
-
status: "error",
|
|
26771
|
-
operation: "update",
|
|
26772
|
-
rows_affected: 0,
|
|
26773
|
-
error: error.message
|
|
26774
|
-
};
|
|
26775
|
-
console.log(JSON.stringify(output2, null, 2));
|
|
27020
|
+
printMutationFailure(error, "update", humanOutputContext(options));
|
|
26776
27021
|
process.exit(1);
|
|
26777
27022
|
}
|
|
26778
27023
|
if (error instanceof PermissionError) {
|
|
@@ -26785,13 +27030,7 @@ async function updateCommand(table, options, command) {
|
|
|
26785
27030
|
printLocalizedCliError(formatCliError(presentConnectionError(error)), error);
|
|
26786
27031
|
process.exit(1);
|
|
26787
27032
|
}
|
|
26788
|
-
|
|
26789
|
-
status: "error",
|
|
26790
|
-
operation: "update",
|
|
26791
|
-
rows_affected: 0,
|
|
26792
|
-
error: error.message
|
|
26793
|
-
};
|
|
26794
|
-
console.log(JSON.stringify(output, null, 2));
|
|
27033
|
+
printMutationFailure(error, "update", humanOutputContext(options));
|
|
26795
27034
|
process.exit(1);
|
|
26796
27035
|
}
|
|
26797
27036
|
}
|
|
@@ -26801,6 +27040,8 @@ var init_update = __esm(() => {
|
|
|
26801
27040
|
init_connection_error_message();
|
|
26802
27041
|
init_adapters();
|
|
26803
27042
|
init_data_executor();
|
|
27043
|
+
init_mutation_confirm();
|
|
27044
|
+
init_mutation_outcome();
|
|
26804
27045
|
init_config();
|
|
26805
27046
|
init_permission_guard();
|
|
26806
27047
|
init_blacklist_validator();
|
|
@@ -26829,6 +27070,9 @@ async function deleteCommand(table, options, command) {
|
|
|
26829
27070
|
throw new Error("Table name required");
|
|
26830
27071
|
}
|
|
26831
27072
|
table = table.trim();
|
|
27073
|
+
if (options.format !== undefined && options.format !== "text" && options.format !== "json") {
|
|
27074
|
+
throw new Error(t_vars("errors.invalid_output_format", { format: options.format }));
|
|
27075
|
+
}
|
|
26832
27076
|
if (!options.plan && (!options.where || options.where.trim() === "")) {
|
|
26833
27077
|
throw new Error('DELETE requires --where clause (e.g. --where "id=1")');
|
|
26834
27078
|
}
|
|
@@ -26875,14 +27119,8 @@ async function deleteCommand(table, options, command) {
|
|
|
26875
27119
|
});
|
|
26876
27120
|
return;
|
|
26877
27121
|
}
|
|
26878
|
-
if (
|
|
26879
|
-
throw new PermissionError(t("delete.admin_only"),
|
|
26880
|
-
type: "DELETE",
|
|
26881
|
-
isDangerous: true,
|
|
26882
|
-
keywords: ["DELETE"],
|
|
26883
|
-
isComposite: false,
|
|
26884
|
-
confidence: "HIGH"
|
|
26885
|
-
}, config.permission);
|
|
27122
|
+
if (!permitsOperation("DELETE", config.permission)) {
|
|
27123
|
+
throw new PermissionError(t("delete.admin_only"), classificationForType("DELETE"), minimumPermissionFor("DELETE"));
|
|
26886
27124
|
}
|
|
26887
27125
|
if (config.connection?.system === "redis") {
|
|
26888
27126
|
const blacklistManager = new BlacklistManager(config);
|
|
@@ -26894,25 +27132,35 @@ async function deleteCommand(table, options, command) {
|
|
|
26894
27132
|
} catch {
|
|
26895
27133
|
filter = parseWhereClause(options.where);
|
|
26896
27134
|
}
|
|
27135
|
+
const preview = `DEL ${table} (Redis Delete)`;
|
|
26897
27136
|
if (options.dryRun) {
|
|
26898
27137
|
const output = {
|
|
26899
|
-
status: "
|
|
27138
|
+
status: "dry_run",
|
|
26900
27139
|
operation: "delete",
|
|
26901
27140
|
rows_affected: 0,
|
|
26902
|
-
sql:
|
|
27141
|
+
sql: preview,
|
|
26903
27142
|
timestamp: new Date().toISOString()
|
|
26904
27143
|
};
|
|
26905
|
-
|
|
26906
|
-
await writeAuditEntry(config, "delete", options,
|
|
26907
|
-
|
|
26908
|
-
|
|
26909
|
-
|
|
26910
|
-
|
|
27144
|
+
printMutationOutcome(output, table, 0, humanOutputContext(options));
|
|
27145
|
+
await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
|
|
27146
|
+
return;
|
|
27147
|
+
}
|
|
27148
|
+
if (!await confirmDirectMutation({
|
|
27149
|
+
operation: "delete",
|
|
27150
|
+
engine: "redis",
|
|
27151
|
+
preview,
|
|
27152
|
+
destructive: true,
|
|
27153
|
+
force: options.force
|
|
27154
|
+
})) {
|
|
27155
|
+
const output = cancelledOutcome("delete", preview);
|
|
27156
|
+
printMutationOutcome(output, table, 0, humanOutputContext(options));
|
|
27157
|
+
await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
|
|
26911
27158
|
return;
|
|
26912
27159
|
}
|
|
26913
27160
|
const adapter2 = AdapterFactory.createRedisAdapter(config.connection, config.blacklist?.tables ?? []);
|
|
26914
27161
|
await adapter2.connect();
|
|
26915
27162
|
try {
|
|
27163
|
+
const startedAt = performance.now();
|
|
26916
27164
|
const result = await adapter2.delete(table, filter);
|
|
26917
27165
|
const output = {
|
|
26918
27166
|
status: "success",
|
|
@@ -26920,12 +27168,8 @@ async function deleteCommand(table, options, command) {
|
|
|
26920
27168
|
rows_affected: result.affectedRows,
|
|
26921
27169
|
timestamp: new Date().toISOString()
|
|
26922
27170
|
};
|
|
26923
|
-
|
|
26924
|
-
await writeAuditEntry(config, "delete", options,
|
|
26925
|
-
success: true,
|
|
26926
|
-
target: table,
|
|
26927
|
-
metadata: { rows_affected: result.affectedRows }
|
|
26928
|
-
});
|
|
27171
|
+
printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options));
|
|
27172
|
+
await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
|
|
26929
27173
|
return;
|
|
26930
27174
|
} finally {
|
|
26931
27175
|
await adapter2.disconnect();
|
|
@@ -26937,7 +27181,7 @@ async function deleteCommand(table, options, command) {
|
|
|
26937
27181
|
operation: "delete",
|
|
26938
27182
|
rows_affected: 0,
|
|
26939
27183
|
timestamp: new Date().toISOString(),
|
|
26940
|
-
error: "
|
|
27184
|
+
error: t("delete.elasticsearch_unsupported")
|
|
26941
27185
|
};
|
|
26942
27186
|
await writeAuditEntry(config, "delete", options, {
|
|
26943
27187
|
success: false,
|
|
@@ -26957,25 +27201,35 @@ async function deleteCommand(table, options, command) {
|
|
|
26957
27201
|
} catch {
|
|
26958
27202
|
filter = parseWhereClause(options.where);
|
|
26959
27203
|
}
|
|
27204
|
+
const preview = previewDelete(table, filter);
|
|
26960
27205
|
if (options.dryRun) {
|
|
26961
27206
|
const output = {
|
|
26962
|
-
status: "
|
|
27207
|
+
status: "dry_run",
|
|
26963
27208
|
operation: "delete",
|
|
26964
27209
|
rows_affected: 0,
|
|
26965
|
-
sql:
|
|
27210
|
+
sql: preview,
|
|
26966
27211
|
timestamp: new Date().toISOString()
|
|
26967
27212
|
};
|
|
26968
|
-
|
|
26969
|
-
await writeAuditEntry(config, "delete", options,
|
|
26970
|
-
|
|
26971
|
-
|
|
26972
|
-
|
|
26973
|
-
|
|
27213
|
+
printMutationOutcome(output, table, 0, humanOutputContext(options));
|
|
27214
|
+
await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
|
|
27215
|
+
return;
|
|
27216
|
+
}
|
|
27217
|
+
if (!await confirmDirectMutation({
|
|
27218
|
+
operation: "delete",
|
|
27219
|
+
engine: "mongodb",
|
|
27220
|
+
preview,
|
|
27221
|
+
destructive: true,
|
|
27222
|
+
force: options.force
|
|
27223
|
+
})) {
|
|
27224
|
+
const output = cancelledOutcome("delete", preview);
|
|
27225
|
+
printMutationOutcome(output, table, 0, humanOutputContext(options));
|
|
27226
|
+
await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
|
|
26974
27227
|
return;
|
|
26975
27228
|
}
|
|
26976
27229
|
const adapter2 = AdapterFactory.createMongoDBAdapter(config.connection);
|
|
26977
27230
|
await adapter2.connect();
|
|
26978
27231
|
try {
|
|
27232
|
+
const startedAt = performance.now();
|
|
26979
27233
|
const result = await adapter2.delete(table, filter);
|
|
26980
27234
|
const output = {
|
|
26981
27235
|
status: "success",
|
|
@@ -26983,12 +27237,8 @@ async function deleteCommand(table, options, command) {
|
|
|
26983
27237
|
rows_affected: result.affectedRows,
|
|
26984
27238
|
timestamp: new Date().toISOString()
|
|
26985
27239
|
};
|
|
26986
|
-
|
|
26987
|
-
await writeAuditEntry(config, "delete", options,
|
|
26988
|
-
success: true,
|
|
26989
|
-
target: table,
|
|
26990
|
-
metadata: { rows_affected: result.affectedRows }
|
|
26991
|
-
});
|
|
27240
|
+
printMutationOutcome(output, table, performance.now() - startedAt, humanOutputContext(options));
|
|
27241
|
+
await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(output, table));
|
|
26992
27242
|
return;
|
|
26993
27243
|
} finally {
|
|
26994
27244
|
await adapter2.disconnect();
|
|
@@ -27008,31 +27258,20 @@ async function deleteCommand(table, options, command) {
|
|
|
27008
27258
|
const blacklistManager = new BlacklistManager(config);
|
|
27009
27259
|
const blacklistValidator = new BlacklistValidator(blacklistManager);
|
|
27010
27260
|
const executor3 = new DataExecutor(adapter, config.permission, dbSystem, blacklistValidator);
|
|
27261
|
+
const startedAt = performance.now();
|
|
27011
27262
|
const result = await executor3.executeDelete(table, whereConditions, schema, {
|
|
27012
27263
|
dryRun: options.dryRun,
|
|
27013
|
-
force: options.force
|
|
27014
|
-
|
|
27015
|
-
const output = {
|
|
27016
|
-
status: result.status,
|
|
27017
|
-
operation: result.operation,
|
|
27018
|
-
rows_affected: result.rows_affected,
|
|
27019
|
-
timestamp: result.timestamp,
|
|
27020
|
-
...result.sql && { sql: result.sql },
|
|
27021
|
-
...result.error && { error: result.error }
|
|
27022
|
-
};
|
|
27023
|
-
console.log(JSON.stringify(output, null, 2));
|
|
27024
|
-
await writeAuditEntry(config, "delete", options, {
|
|
27025
|
-
success: result.status === "success",
|
|
27026
|
-
target: table,
|
|
27027
|
-
...result.sql && { sql: result.sql },
|
|
27028
|
-
metadata: {
|
|
27029
|
-
rows_affected: result.rows_affected,
|
|
27030
|
-
...options.dryRun && { dry_run: true }
|
|
27031
|
-
},
|
|
27032
|
-
...result.status === "error" && {
|
|
27033
|
-
error: new Error(result.error ?? "delete failed")
|
|
27034
|
-
}
|
|
27264
|
+
force: options.force,
|
|
27265
|
+
confirm: confirmMutationInteractively
|
|
27035
27266
|
});
|
|
27267
|
+
const elapsedMs = performance.now() - startedAt;
|
|
27268
|
+
const recoveryEnvelopeId = result.status === "error" && options.recovery === true ? crypto7.randomUUID() : undefined;
|
|
27269
|
+
const auditId = await writeAuditEntry(config, "delete", options, auditOutcomeForMutation(result, table, recoveryEnvelopeId));
|
|
27270
|
+
if (recoveryEnvelopeId !== undefined) {
|
|
27271
|
+
const { emitRecoveryEnvelope: emitRecoveryEnvelope2 } = await Promise.resolve().then(() => (init_recovery(), exports_recovery));
|
|
27272
|
+
emitRecoveryEnvelope2(new Error(result.error ?? "DELETE failed"), { operation: "delete", table, writeOperation: "DELETE" }, { envelopeId: recoveryEnvelopeId, auditRef: auditId ?? undefined });
|
|
27273
|
+
}
|
|
27274
|
+
printMutationOutcome(result, table, elapsedMs, humanOutputContext(options));
|
|
27036
27275
|
if (result.status === "error") {
|
|
27037
27276
|
process.exit(1);
|
|
27038
27277
|
}
|
|
@@ -27058,17 +27297,11 @@ async function deleteCommand(table, options, command) {
|
|
|
27058
27297
|
emitRecoveryEnvelope2(error, { operation: "delete", table, writeOperation: "DELETE" }, { envelopeId, auditRef: auditId ?? undefined });
|
|
27059
27298
|
}
|
|
27060
27299
|
if (error instanceof BlacklistError) {
|
|
27061
|
-
|
|
27062
|
-
status: "error",
|
|
27063
|
-
operation: "delete",
|
|
27064
|
-
rows_affected: 0,
|
|
27065
|
-
error: error.message
|
|
27066
|
-
};
|
|
27067
|
-
console.log(JSON.stringify(output2, null, 2));
|
|
27300
|
+
printMutationFailure(error, "delete", humanOutputContext(options));
|
|
27068
27301
|
process.exit(1);
|
|
27069
27302
|
}
|
|
27070
27303
|
if (error instanceof PermissionError) {
|
|
27071
|
-
console.error(t_vars("errors.permission_denied", { required:
|
|
27304
|
+
console.error(t_vars("errors.permission_denied", { required: error.requiredPermission }));
|
|
27072
27305
|
console.error(` Operation: ${error.classification.type}`);
|
|
27073
27306
|
console.error(` Message: ${error.message}`);
|
|
27074
27307
|
process.exit(1);
|
|
@@ -27077,13 +27310,7 @@ async function deleteCommand(table, options, command) {
|
|
|
27077
27310
|
printLocalizedCliError(formatCliError(presentConnectionError(error)), error);
|
|
27078
27311
|
process.exit(1);
|
|
27079
27312
|
}
|
|
27080
|
-
|
|
27081
|
-
status: "error",
|
|
27082
|
-
operation: "delete",
|
|
27083
|
-
rows_affected: 0,
|
|
27084
|
-
error: error.message
|
|
27085
|
-
};
|
|
27086
|
-
console.log(JSON.stringify(output, null, 2));
|
|
27313
|
+
printMutationFailure(error, "delete", humanOutputContext(options));
|
|
27087
27314
|
process.exit(1);
|
|
27088
27315
|
}
|
|
27089
27316
|
}
|
|
@@ -27093,6 +27320,8 @@ var init_delete = __esm(() => {
|
|
|
27093
27320
|
init_connection_error_message();
|
|
27094
27321
|
init_adapters();
|
|
27095
27322
|
init_data_executor();
|
|
27323
|
+
init_mutation_confirm();
|
|
27324
|
+
init_mutation_outcome();
|
|
27096
27325
|
init_config();
|
|
27097
27326
|
init_permission_guard();
|
|
27098
27327
|
init_blacklist_validator();
|
|
@@ -27256,7 +27485,7 @@ async function exportCommand(sql, options, command) {
|
|
|
27256
27485
|
}
|
|
27257
27486
|
}
|
|
27258
27487
|
async function redisExportBranch(command, options, config) {
|
|
27259
|
-
const { enforceRedisPermission: enforceRedisPermission2 } = await Promise.resolve().then(() => (
|
|
27488
|
+
const { enforceRedisPermission: enforceRedisPermission2 } = await Promise.resolve().then(() => (init_redis(), exports_redis));
|
|
27260
27489
|
enforceRedisPermission2(command, config.permission);
|
|
27261
27490
|
const redisAdapter = AdapterFactory.createRedisAdapter(config.connection, config.blacklist?.tables ?? [], config.redis?.mask ?? []);
|
|
27262
27491
|
await redisAdapter.connect();
|
|
@@ -44132,6 +44361,7 @@ var init_repl_engine = __esm(() => {
|
|
|
44132
44361
|
init_meta_commands();
|
|
44133
44362
|
init_history_manager();
|
|
44134
44363
|
init_permission_guard();
|
|
44364
|
+
init_redis();
|
|
44135
44365
|
init_query_result_formatter();
|
|
44136
44366
|
init_message_loader();
|
|
44137
44367
|
init_sql_tables();
|
|
@@ -45260,13 +45490,16 @@ class DDLExecutor {
|
|
|
45260
45490
|
};
|
|
45261
45491
|
}
|
|
45262
45492
|
if (isDestructiveOperation2(operation) && !options.force) {
|
|
45263
|
-
|
|
45493
|
+
if (!options.confirm) {
|
|
45494
|
+
throw new DDLExecutionError(`Refusing to run ${operation.kind} without confirmation: no confirmation handler was ` + "supplied. Pass options.confirm to ask the user, or options.force to proceed unattended.");
|
|
45495
|
+
}
|
|
45496
|
+
const confirmed = await options.confirm({ operation: operation.kind, sql });
|
|
45264
45497
|
if (!confirmed) {
|
|
45265
45498
|
return {
|
|
45266
|
-
status: "
|
|
45499
|
+
status: "cancelled",
|
|
45267
45500
|
operation: operation.kind,
|
|
45268
45501
|
sql,
|
|
45269
|
-
warnings
|
|
45502
|
+
warnings,
|
|
45270
45503
|
timestamp: timestamp2,
|
|
45271
45504
|
dryRun: false
|
|
45272
45505
|
};
|
|
@@ -45341,8 +45574,15 @@ class DDLExecutor {
|
|
|
45341
45574
|
} catch {}
|
|
45342
45575
|
}
|
|
45343
45576
|
}
|
|
45577
|
+
var DDLExecutionError;
|
|
45344
45578
|
var init_ddl_executor = __esm(() => {
|
|
45345
|
-
|
|
45579
|
+
DDLExecutionError = class DDLExecutionError extends Error {
|
|
45580
|
+
constructor(message) {
|
|
45581
|
+
super(message);
|
|
45582
|
+
this.name = "DDLExecutionError";
|
|
45583
|
+
Object.setPrototypeOf(this, DDLExecutionError.prototype);
|
|
45584
|
+
}
|
|
45585
|
+
};
|
|
45346
45586
|
});
|
|
45347
45587
|
|
|
45348
45588
|
// src/commands/migrate.ts
|
|
@@ -45386,7 +45626,8 @@ async function runDDL(operation, opts, command) {
|
|
|
45386
45626
|
const executor3 = new DDLExecutor(adapter, generator, config.permission, blacklistManager);
|
|
45387
45627
|
const result = await executor3.execute(operation, {
|
|
45388
45628
|
execute: opts.execute,
|
|
45389
|
-
force: opts.force
|
|
45629
|
+
force: opts.force,
|
|
45630
|
+
confirm: confirmDdlInteractively
|
|
45390
45631
|
});
|
|
45391
45632
|
console.log(JSON.stringify({
|
|
45392
45633
|
status: result.status,
|
|
@@ -45428,6 +45669,7 @@ var init_migrate = __esm(() => {
|
|
|
45428
45669
|
init_adapters();
|
|
45429
45670
|
init_ddl2();
|
|
45430
45671
|
init_ddl_executor();
|
|
45672
|
+
init_mutation_confirm();
|
|
45431
45673
|
init_config_path();
|
|
45432
45674
|
migrateCommand = new Command("migrate").description(t("migrate.description"));
|
|
45433
45675
|
addExecOpts(migrateCommand.command("create <table>").description(t("migrate.create_description")).option("--column <spec...>", 'Column definitions (e.g., "id:serial:pk" "name:varchar(50):not-null")')).action(async (table, opts, command) => {
|