dsh-api-runner 0.2.0 → 0.3.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/README.md +97 -3
- package/client/client.js +518 -26
- package/package.json +4 -4
- package/src/db.js +958 -0
- package/src/main.js +137 -1
- package/src/ops.js +1327 -1257
- package/src/project-store.js +350 -334
- package/test/client.mjs +102 -1
- package/test/db.mjs +346 -0
- package/test/plugin.mjs +123 -0
- package/test/smoke.mjs +114 -0
package/src/main.js
CHANGED
|
@@ -42,7 +42,7 @@ const PARAMETERS = {
|
|
|
42
42
|
properties: {
|
|
43
43
|
action: {
|
|
44
44
|
type: 'string',
|
|
45
|
-
enum: ['describe', 'call', 'login', 'service', 'log', 'wait_log', 'reset_log', 'verify', 'java'],
|
|
45
|
+
enum: ['describe', 'call', 'login', 'service', 'log', 'wait_log', 'reset_log', 'verify', 'java', 'db'],
|
|
46
46
|
description: '要执行的操作',
|
|
47
47
|
},
|
|
48
48
|
api_id: { type: 'string', description: '接口 id 或 name(call、login、verify 用)' },
|
|
@@ -69,6 +69,34 @@ const PARAMETERS = {
|
|
|
69
69
|
items: { type: 'string' },
|
|
70
70
|
description: 'java_action=start_all 时只启动这些服务(省略则启动全部已保存的)',
|
|
71
71
|
},
|
|
72
|
+
db_action: {
|
|
73
|
+
type: 'string',
|
|
74
|
+
enum: ['list_connections', 'test', 'save', 'delete', 'query', 'list_databases', 'list_tables', 'describe_table'],
|
|
75
|
+
description: '数据库动作(action=db 用):list_connections 列出连接;test 试连;save 保存连接;delete 删除连接;query 执行 SQL(只读模式默认开);list_databases/list_tables/describe_table 看元数据',
|
|
76
|
+
},
|
|
77
|
+
connection_id: { type: 'string', description: '数据库连接 id(db_action=query/list_tables/describe_table/test/delete 用)' },
|
|
78
|
+
sql: { type: 'string', description: '要执行的 SQL(db_action=query 用,只允许单条语句)' },
|
|
79
|
+
params: {
|
|
80
|
+
type: 'object',
|
|
81
|
+
additionalProperties: true,
|
|
82
|
+
description: '接口参数(action=call/login):键会替换 URL/body 里的 {key} 占位。db_action=query 时请改用 db_params。',
|
|
83
|
+
},
|
|
84
|
+
db_params: {
|
|
85
|
+
type: 'array',
|
|
86
|
+
items: {},
|
|
87
|
+
description: 'SQL 参数化占位值(db_action=query 用),按 ?/$1/:1 顺序绑定,不要拼字符串',
|
|
88
|
+
},
|
|
89
|
+
db_readonly: {
|
|
90
|
+
type: 'boolean',
|
|
91
|
+
description: 'db_action=query 时是否只读(默认 true,拒绝 INSERT/UPDATE/DELETE/DDL)',
|
|
92
|
+
},
|
|
93
|
+
connection: {
|
|
94
|
+
type: 'object',
|
|
95
|
+
additionalProperties: true,
|
|
96
|
+
description: '连接配置(db_action=save/test 用):{ id?, name, type(sqlite|mysql|postgres|oracle), host, port, database, username, password, extra }',
|
|
97
|
+
},
|
|
98
|
+
table: { type: 'string', description: '表名(db_action=describe_table 用)' },
|
|
99
|
+
database: { type: 'string', description: '库名/模式名(db_action=list_tables 用;MySQL 必填)' },
|
|
72
100
|
pid: { type: 'number', description: '要接管的 Java 进程 pid(java_action=start 用)' },
|
|
73
101
|
java_id: { type: 'string', description: '插件托管的服务名或 pid(java_action=stop/restart/log/save/unsave/start_saved 用)' },
|
|
74
102
|
port: { type: 'number', description: '服务端口(java_action=start 用;省略则从进程参数推断)' },
|
|
@@ -210,6 +238,64 @@ async function runTool(service, args, projectPath) {
|
|
|
210
238
|
}
|
|
211
239
|
return { ok: false, code: 'BAD_ACTION', message: `不支持的 java_action: ${javaAction}` };
|
|
212
240
|
}
|
|
241
|
+
case 'db': {
|
|
242
|
+
const dbAction = String(args.db_action || 'list_connections');
|
|
243
|
+
if (dbAction === 'list_connections') return service.dbConnections();
|
|
244
|
+
if (dbAction === 'list_databases') {
|
|
245
|
+
if (!args.connection_id) return { ok: false, code: 'MISSING_ARGUMENT', message: 'db_action=list_databases 需要 connection_id' };
|
|
246
|
+
return service.dbDatabases(String(args.connection_id));
|
|
247
|
+
}
|
|
248
|
+
if (dbAction === 'test') {
|
|
249
|
+
const target = args.connection || args.connection_id;
|
|
250
|
+
if (!target) return { ok: false, code: 'MISSING_ARGUMENT', message: 'db_action=test 需要 connection_id 或 connection 配置' };
|
|
251
|
+
return service.dbTest(args.connection ? asObject(args.connection) : String(args.connection_id));
|
|
252
|
+
}
|
|
253
|
+
if (dbAction === 'save') {
|
|
254
|
+
/**
|
|
255
|
+
* connection 可能是对象,也可能被上游序列化成 JSON 字符串
|
|
256
|
+
* (工具参数走 JSON 时常见)。两种都接住,不要只认对象。
|
|
257
|
+
*/
|
|
258
|
+
const raw = args.connection;
|
|
259
|
+
let config;
|
|
260
|
+
if (typeof raw === 'string' && raw.trim() !== '') {
|
|
261
|
+
try {
|
|
262
|
+
config = JSON.parse(raw);
|
|
263
|
+
} catch {
|
|
264
|
+
return { ok: false, code: 'BAD_ARGUMENT', message: 'connection 不是合法 JSON' };
|
|
265
|
+
}
|
|
266
|
+
} else if (raw && typeof raw === 'object') {
|
|
267
|
+
config = raw;
|
|
268
|
+
}
|
|
269
|
+
if (!config || typeof config !== 'object') {
|
|
270
|
+
return { ok: false, code: 'MISSING_ARGUMENT', message: 'db_action=save 需要 connection 配置(对象或 JSON 字符串)' };
|
|
271
|
+
}
|
|
272
|
+
return service.dbSave(asObject(config));
|
|
273
|
+
}
|
|
274
|
+
if (dbAction === 'delete') {
|
|
275
|
+
if (!args.connection_id) return { ok: false, code: 'MISSING_ARGUMENT', message: 'db_action=delete 需要 connection_id' };
|
|
276
|
+
return service.dbDelete(String(args.connection_id));
|
|
277
|
+
}
|
|
278
|
+
if (dbAction === 'query') {
|
|
279
|
+
if (!args.connection_id) return { ok: false, code: 'MISSING_ARGUMENT', message: 'db_action=query 需要 connection_id' };
|
|
280
|
+
if (!args.sql) return { ok: false, code: 'MISSING_ARGUMENT', message: 'db_action=query 需要 sql' };
|
|
281
|
+
return service.dbQuery(String(args.connection_id), String(args.sql), Array.isArray(args.db_params) ? args.db_params : [], {
|
|
282
|
+
// 默认只读:模型直接执行写操作是高风险动作,必须显式关闭
|
|
283
|
+
readonly: args.db_readonly !== false,
|
|
284
|
+
maxRows: args.max_lines ? Number(args.max_lines) : undefined,
|
|
285
|
+
timeoutMs,
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
if (dbAction === 'list_tables') {
|
|
289
|
+
if (!args.connection_id) return { ok: false, code: 'MISSING_ARGUMENT', message: 'db_action=list_tables 需要 connection_id' };
|
|
290
|
+
return service.dbTables(String(args.connection_id), args.database ? String(args.database) : '');
|
|
291
|
+
}
|
|
292
|
+
if (dbAction === 'describe_table') {
|
|
293
|
+
if (!args.connection_id) return { ok: false, code: 'MISSING_ARGUMENT', message: 'db_action=describe_table 需要 connection_id' };
|
|
294
|
+
if (!args.table) return { ok: false, code: 'MISSING_ARGUMENT', message: 'db_action=describe_table 需要 table' };
|
|
295
|
+
return service.dbDescribeTable(String(args.connection_id), String(args.table));
|
|
296
|
+
}
|
|
297
|
+
return { ok: false, code: 'BAD_ACTION', message: `不支持的 db_action: ${dbAction}` };
|
|
298
|
+
}
|
|
213
299
|
case 'service': {
|
|
214
300
|
const serviceAction = String(args.service_action || 'status');
|
|
215
301
|
if (!args.service_id && serviceAction !== 'status') {
|
|
@@ -576,6 +662,56 @@ async function handleHttp(req, res, service, projectOf) {
|
|
|
576
662
|
if (route === 'java/start-all/status' && req.method === 'GET') {
|
|
577
663
|
return send(200, service.startBatchStatus(url.searchParams.get('batch') || ''));
|
|
578
664
|
}
|
|
665
|
+
|
|
666
|
+
// ---- 数据库:连接管理与 SQL 执行(连接是跨项目的,不按 cwd 归档) ----
|
|
667
|
+
if (route === 'db' && req.method === 'GET') {
|
|
668
|
+
return send(200, service.dbConnections());
|
|
669
|
+
}
|
|
670
|
+
if (route === 'db/test' && req.method === 'POST') {
|
|
671
|
+
const body = await readBody();
|
|
672
|
+
const target = body.connection ? asObject(body.connection) : String(body.connection_id || body.id || '');
|
|
673
|
+
if (!target || (typeof target === 'string' && target === '')) {
|
|
674
|
+
return send(400, { ok: false, code: 'MISSING_ARGUMENT', message: '需要 connection_id 或 connection 配置' });
|
|
675
|
+
}
|
|
676
|
+
return send(200, await service.dbTest(target));
|
|
677
|
+
}
|
|
678
|
+
if (route === 'db/save' && req.method === 'POST') {
|
|
679
|
+
const body = await readBody();
|
|
680
|
+
return send(200, service.dbSave(asObject(body.connection || body)));
|
|
681
|
+
}
|
|
682
|
+
if (route === 'db/delete' && req.method === 'POST') {
|
|
683
|
+
const body = await readBody();
|
|
684
|
+
return send(200, service.dbDelete(String(body.connection_id || body.id || '')));
|
|
685
|
+
}
|
|
686
|
+
if (route === 'db/query' && req.method === 'POST') {
|
|
687
|
+
const body = await readBody();
|
|
688
|
+
return send(200, await service.dbQuery(
|
|
689
|
+
String(body.connection_id || ''),
|
|
690
|
+
String(body.sql || ''),
|
|
691
|
+
Array.isArray(body.params) ? body.params : [],
|
|
692
|
+
{
|
|
693
|
+
readonly: body.readonly !== false,
|
|
694
|
+
maxRows: body.max_rows ? Number(body.max_rows) : undefined,
|
|
695
|
+
timeoutMs: body.timeout_ms ? Number(body.timeout_ms) : undefined,
|
|
696
|
+
},
|
|
697
|
+
));
|
|
698
|
+
}
|
|
699
|
+
if (route === 'db/databases' && req.method === 'GET') {
|
|
700
|
+
return send(200, await service.dbDatabases(String(url.searchParams.get('connection_id') || '')));
|
|
701
|
+
}
|
|
702
|
+
if (route === 'db/tables' && req.method === 'GET') {
|
|
703
|
+
return send(200, await service.dbTables(
|
|
704
|
+
String(url.searchParams.get('connection_id') || ''),
|
|
705
|
+
String(url.searchParams.get('database') || ''),
|
|
706
|
+
));
|
|
707
|
+
}
|
|
708
|
+
if (route === 'db/table' && req.method === 'GET') {
|
|
709
|
+
return send(200, await service.dbDescribeTable(
|
|
710
|
+
String(url.searchParams.get('connection_id') || ''),
|
|
711
|
+
String(url.searchParams.get('name') || url.searchParams.get('table') || ''),
|
|
712
|
+
));
|
|
713
|
+
}
|
|
714
|
+
|
|
579
715
|
if (route === 'settings' && req.method === 'GET') {
|
|
580
716
|
return send(200, service.settings());
|
|
581
717
|
}
|