fdb2 1.0.26 → 1.0.31
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/bin/fdb2.js +18 -2
- package/dist/public/.vite/manifest.json +4 -4
- package/dist/public/bootstrap.js +6 -5
- package/dist/public/explorer.css +513 -503
- package/dist/public/explorer.js +343 -218
- package/dist/public/favicon.ico +0 -0
- package/dist/public/index.css +100 -41
- package/dist/public/index.js +490 -263
- package/dist/public/layout.css +68 -68
- package/dist/public/layout.js +10 -10
- package/dist/public/rolldown-runtime.js +8 -1
- package/dist/public/vconsole.min.js +0 -26
- package/dist/public/vue.js +5238 -5192
- package/dist/server/cli/format.d.ts +6 -0
- package/dist/server/cli/format.d.ts.map +1 -0
- package/dist/server/cli/format.js +116 -0
- package/dist/server/cli/format.js.map +1 -0
- package/dist/server/cli/format.ts +104 -0
- package/dist/server/cli/main.d.ts +3 -0
- package/dist/server/cli/main.d.ts.map +1 -0
- package/dist/server/cli/main.js +710 -0
- package/dist/server/cli/main.js.map +1 -0
- package/dist/server/cli/main.ts +746 -0
- package/dist/server/cli/parse.d.ts +18 -0
- package/dist/server/cli/parse.d.ts.map +1 -0
- package/dist/server/cli/parse.js +150 -0
- package/dist/server/cli/parse.js.map +1 -0
- package/dist/server/cli/parse.ts +148 -0
- package/dist/server/core/column-spec.d.ts +29 -0
- package/dist/server/core/column-spec.d.ts.map +1 -0
- package/dist/server/core/column-spec.js +148 -0
- package/dist/server/core/column-spec.js.map +1 -0
- package/dist/server/core/column-spec.ts +147 -0
- package/dist/server/core/config.d.ts +34 -0
- package/dist/server/core/config.d.ts.map +1 -0
- package/dist/server/core/config.js +187 -0
- package/dist/server/core/config.js.map +1 -0
- package/dist/server/core/config.ts +165 -0
- package/dist/server/core/db-api.d.ts +169 -0
- package/dist/server/core/db-api.d.ts.map +1 -0
- package/dist/server/core/db-api.js +1078 -0
- package/dist/server/core/db-api.js.map +1 -0
- package/dist/server/core/db-api.ts +1181 -0
- package/dist/server/core/dialect.d.ts +44 -0
- package/dist/server/core/dialect.d.ts.map +1 -0
- package/dist/server/core/dialect.js +260 -0
- package/dist/server/core/dialect.js.map +1 -0
- package/dist/server/core/dialect.ts +293 -0
- package/dist/server/core/errors.d.ts +22 -0
- package/dist/server/core/errors.d.ts.map +1 -0
- package/dist/server/core/errors.js +61 -0
- package/dist/server/core/errors.js.map +1 -0
- package/dist/server/core/errors.ts +58 -0
- package/dist/server/core/filter-dsl.d.ts +39 -0
- package/dist/server/core/filter-dsl.d.ts.map +1 -0
- package/dist/server/core/filter-dsl.js +167 -0
- package/dist/server/core/filter-dsl.js.map +1 -0
- package/dist/server/core/filter-dsl.ts +180 -0
- package/dist/server/core/guard.d.ts +54 -0
- package/dist/server/core/guard.d.ts.map +1 -0
- package/dist/server/core/guard.js +207 -0
- package/dist/server/core/guard.js.map +1 -0
- package/dist/server/core/guard.ts +205 -0
- package/dist/server/core/query-builder.d.ts +72 -0
- package/dist/server/core/query-builder.d.ts.map +1 -0
- package/dist/server/core/query-builder.js +478 -0
- package/dist/server/core/query-builder.js.map +1 -0
- package/dist/server/core/query-builder.ts +502 -0
- package/dist/server/service/connection.service.d.ts.map +1 -1
- package/dist/server/service/connection.service.js +7 -3
- package/dist/server/service/connection.service.js.map +1 -1
- package/dist/server/service/connection.service.ts +5 -3
- package/dist/server/utils/data-dir.d.ts.map +1 -1
- package/dist/server/utils/data-dir.js +2 -0
- package/dist/server/utils/data-dir.js.map +1 -1
- package/dist/server/utils/data-dir.ts +2 -0
- package/electron/updater.js +12 -66
- package/package.json +22 -22
- package/public/favicon.ico +0 -0
- package/public/favicon.png +0 -0
|
@@ -0,0 +1,710 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.VERSION = void 0;
|
|
4
|
+
exports.run = run;
|
|
5
|
+
/**
|
|
6
|
+
* fdb2 CLI 入口
|
|
7
|
+
* 结构: fdb2 <group> <command> [args...] [flags...]
|
|
8
|
+
*/
|
|
9
|
+
const db_api_1 = require("../core/db-api");
|
|
10
|
+
const errors_1 = require("../core/errors");
|
|
11
|
+
const parse_1 = require("./parse");
|
|
12
|
+
const format_1 = require("./format");
|
|
13
|
+
exports.VERSION = '2.0.0-cli';
|
|
14
|
+
const api = new db_api_1.DbToolApi();
|
|
15
|
+
const F = {
|
|
16
|
+
filter: { name: 'filter', type: 'list' },
|
|
17
|
+
filterJson: { name: 'filter-json', type: 'string' },
|
|
18
|
+
sort: { name: 'sort', type: 'list' },
|
|
19
|
+
select: { name: 'select', type: 'string' },
|
|
20
|
+
column: { name: 'column', type: 'list' },
|
|
21
|
+
file: { name: 'file', type: 'string' },
|
|
22
|
+
format: { name: 'format', type: 'string' },
|
|
23
|
+
unique: { name: 'unique', type: 'bool' },
|
|
24
|
+
pattern: { name: 'pattern', type: 'string' },
|
|
25
|
+
charset: { name: 'charset', type: 'string' },
|
|
26
|
+
engine: { name: 'engine', type: 'string' },
|
|
27
|
+
comment: { name: 'comment', type: 'string' },
|
|
28
|
+
data: { name: 'data', type: 'json' },
|
|
29
|
+
set: { name: 'set', type: 'list' },
|
|
30
|
+
setJson: { name: 'set-json', type: 'string' },
|
|
31
|
+
addColumn: { name: 'add-column', type: 'list' },
|
|
32
|
+
dropColumn: { name: 'drop-column', type: 'list' },
|
|
33
|
+
modifyColumn: { name: 'modify-column', type: 'list' },
|
|
34
|
+
renameTo: { name: 'rename-to', type: 'string' },
|
|
35
|
+
name: { name: 'name', type: 'string' },
|
|
36
|
+
columns: { name: 'columns', type: 'string' },
|
|
37
|
+
as: { name: 'as', type: 'string' },
|
|
38
|
+
body: { name: 'body', type: 'string' },
|
|
39
|
+
allowMulti: { name: 'allow-multi', type: 'bool' },
|
|
40
|
+
level: { name: 'level', type: 'string' },
|
|
41
|
+
tables: { name: 'tables', type: 'string' },
|
|
42
|
+
mode: { name: 'mode', type: 'string' },
|
|
43
|
+
recreate: { name: 'recreate', type: 'bool' },
|
|
44
|
+
fromConn: { name: 'from-conn', type: 'string' },
|
|
45
|
+
fromDb: { name: 'from-db', type: 'string' },
|
|
46
|
+
fromTable: { name: 'from-table', type: 'string' },
|
|
47
|
+
toConn: { name: 'to-conn', type: 'string' },
|
|
48
|
+
toDb: { name: 'to-db', type: 'string' },
|
|
49
|
+
toTable: { name: 'to-table', type: 'string' },
|
|
50
|
+
dropTarget: { name: 'drop-target', type: 'bool' }
|
|
51
|
+
};
|
|
52
|
+
const g = (v) => ({
|
|
53
|
+
yes: !!v.yes,
|
|
54
|
+
dryRun: !!v['dry-run'],
|
|
55
|
+
confirm: v.confirm || null,
|
|
56
|
+
write: !!v.write,
|
|
57
|
+
force: !!v.force
|
|
58
|
+
});
|
|
59
|
+
async function needDb(v) {
|
|
60
|
+
if (v.db)
|
|
61
|
+
return String(v.db);
|
|
62
|
+
const conn = await api.resolveConnection(v.conn || null);
|
|
63
|
+
if (!conn.database) {
|
|
64
|
+
throw new errors_1.AppError('PARAM_ERROR', '缺少 --db 数据库名', { hint: '该连接未配置默认数据库,请用 --db <库名> 指定' });
|
|
65
|
+
}
|
|
66
|
+
return conn.database;
|
|
67
|
+
}
|
|
68
|
+
async function needConn(v) {
|
|
69
|
+
return v.conn || null;
|
|
70
|
+
}
|
|
71
|
+
async function readStdin() {
|
|
72
|
+
process.stdin.setEncoding('utf8');
|
|
73
|
+
let s = '';
|
|
74
|
+
for await (const chunk of process.stdin)
|
|
75
|
+
s += chunk;
|
|
76
|
+
return s.trim();
|
|
77
|
+
}
|
|
78
|
+
// ================= 命令表 =================
|
|
79
|
+
const connCmds = {
|
|
80
|
+
list: {
|
|
81
|
+
run: async () => api.listConnections()
|
|
82
|
+
},
|
|
83
|
+
get: {
|
|
84
|
+
pos: 1,
|
|
85
|
+
run: async ({ pos }) => api.getConnectionInfo(pos[0])
|
|
86
|
+
},
|
|
87
|
+
add: {
|
|
88
|
+
extra: [F.name],
|
|
89
|
+
run: async ({ v }) => {
|
|
90
|
+
const opts = (0, parse_1.parseKvList)(v.option || []);
|
|
91
|
+
if (v.ssl)
|
|
92
|
+
opts.ssl = true;
|
|
93
|
+
let password = v.password;
|
|
94
|
+
if (v['password-stdin'])
|
|
95
|
+
password = await readStdin();
|
|
96
|
+
return api.addConnection({
|
|
97
|
+
name: v.name,
|
|
98
|
+
type: v.type,
|
|
99
|
+
host: v.host,
|
|
100
|
+
port: v.port,
|
|
101
|
+
database: v.database,
|
|
102
|
+
username: v.username,
|
|
103
|
+
password,
|
|
104
|
+
options: opts
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
update: {
|
|
109
|
+
pos: 1,
|
|
110
|
+
run: async ({ v, pos }) => {
|
|
111
|
+
const updates = {};
|
|
112
|
+
for (const k of ['name', 'host', 'port', 'database', 'username', 'password', 'type', 'enabled']) {
|
|
113
|
+
if (v[k] !== undefined) {
|
|
114
|
+
updates[k] = k === 'enabled' ? !(v[k] === 'false' || v[k] === '0') : v[k];
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (v.option)
|
|
118
|
+
updates.options = (0, parse_1.parseKvList)(v.option || []);
|
|
119
|
+
if (v['password-stdin'])
|
|
120
|
+
updates.password = await readStdin();
|
|
121
|
+
return api.updateConnection(pos[0], updates);
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
remove: {
|
|
125
|
+
pos: 1,
|
|
126
|
+
run: async ({ pos }) => api.deleteConnection(pos[0])
|
|
127
|
+
},
|
|
128
|
+
test: {
|
|
129
|
+
pos: 1,
|
|
130
|
+
run: async ({ pos }) => {
|
|
131
|
+
const r = await api.testConnectionByRef(pos[0]);
|
|
132
|
+
if (!r.ok)
|
|
133
|
+
throw new errors_1.AppError('CONN_NOT_FOUND', `连接 ${pos[0]} 测试失败`, { hint: '请检查主机/端口/账号/密码/网络' });
|
|
134
|
+
return r;
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
use: {
|
|
138
|
+
pos: 1,
|
|
139
|
+
run: async ({ pos }) => api.setDefaultConnection(pos[0])
|
|
140
|
+
},
|
|
141
|
+
current: {
|
|
142
|
+
run: async () => api.currentConnection()
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
const dbCmds = {
|
|
146
|
+
list: {
|
|
147
|
+
run: async ({ v }) => api.listDatabases(await needConn(v))
|
|
148
|
+
},
|
|
149
|
+
info: {
|
|
150
|
+
pos: 1,
|
|
151
|
+
run: async ({ v, pos }) => api.databaseInfo(await needConn(v), await needDb(v))
|
|
152
|
+
},
|
|
153
|
+
create: {
|
|
154
|
+
pos: 1,
|
|
155
|
+
extra: [F.charset],
|
|
156
|
+
run: async ({ v, pos }) => api.createDatabaseCmd(await needConn(v), pos[0], v.charset, g(v))
|
|
157
|
+
},
|
|
158
|
+
drop: {
|
|
159
|
+
pos: 1,
|
|
160
|
+
run: async ({ v, pos }) => api.dropDatabaseCmd(await needConn(v), pos[0], g(v))
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
const tableCmds = {
|
|
164
|
+
list: {
|
|
165
|
+
extra: [F.pattern],
|
|
166
|
+
run: async ({ v }) => api.listTables(await needConn(v), await needDb(v), v.pattern)
|
|
167
|
+
},
|
|
168
|
+
info: {
|
|
169
|
+
pos: 1,
|
|
170
|
+
run: async ({ v, pos }) => api.tableInfo(await needConn(v), await needDb(v), pos[0])
|
|
171
|
+
},
|
|
172
|
+
columns: {
|
|
173
|
+
pos: 1,
|
|
174
|
+
run: async ({ v, pos }) => {
|
|
175
|
+
const r = await api.tableInfo(await needConn(v), await needDb(v), pos[0]);
|
|
176
|
+
return { conn: r.conn, database: r.database, table: r.table.name, columns: r.table.columns || [] };
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
indexes: {
|
|
180
|
+
pos: 1,
|
|
181
|
+
run: async ({ v, pos }) => api.listIndexes(await needConn(v), await needDb(v), pos[0])
|
|
182
|
+
},
|
|
183
|
+
fks: {
|
|
184
|
+
pos: 1,
|
|
185
|
+
run: async ({ v, pos }) => {
|
|
186
|
+
const r = await api.tableInfo(await needConn(v), await needDb(v), pos[0]);
|
|
187
|
+
return { conn: r.conn, database: r.database, table: r.table.name, foreignKeys: r.table.foreignKeys || [] };
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
stats: {
|
|
191
|
+
pos: 1,
|
|
192
|
+
run: async ({ v, pos }) => {
|
|
193
|
+
const r = await api.tableInfo(await needConn(v), await needDb(v), pos[0]);
|
|
194
|
+
return { conn: r.conn, database: r.database, table: r.table.name, rowCount: r.table.rowCount, size: r.table.size, engine: r.table.engine };
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
create: {
|
|
198
|
+
pos: 1,
|
|
199
|
+
extra: [F.column, F.engine, F.charset, F.comment],
|
|
200
|
+
run: async ({ v, pos }) => api.createTableCmd(await needConn(v), await needDb(v), pos[0], v.column || [], {
|
|
201
|
+
engine: v.engine,
|
|
202
|
+
charset: v.charset,
|
|
203
|
+
comment: v.comment,
|
|
204
|
+
...g(v)
|
|
205
|
+
})
|
|
206
|
+
},
|
|
207
|
+
alter: {
|
|
208
|
+
pos: 1,
|
|
209
|
+
extra: [F.addColumn, F.dropColumn, F.modifyColumn, F.renameTo],
|
|
210
|
+
run: async ({ v, pos }) => api.alterTableCmd(await needConn(v), await needDb(v), pos[0], {
|
|
211
|
+
addColumn: v['add-column'] || [],
|
|
212
|
+
dropColumn: v['drop-column'] || [],
|
|
213
|
+
modifyColumn: v['modify-column'] || [],
|
|
214
|
+
renameTo: v['rename-to'],
|
|
215
|
+
...g(v)
|
|
216
|
+
})
|
|
217
|
+
},
|
|
218
|
+
rename: {
|
|
219
|
+
pos: 2,
|
|
220
|
+
run: async ({ v, pos }) => api.alterTableCmd(await needConn(v), await needDb(v), pos[0], {
|
|
221
|
+
renameTo: pos[1],
|
|
222
|
+
...g(v)
|
|
223
|
+
})
|
|
224
|
+
},
|
|
225
|
+
drop: {
|
|
226
|
+
pos: 1,
|
|
227
|
+
run: async ({ v, pos }) => api.dropTableCmd(await needConn(v), await needDb(v), pos[0], { ...g(v), truncate: false })
|
|
228
|
+
},
|
|
229
|
+
truncate: {
|
|
230
|
+
pos: 1,
|
|
231
|
+
run: async ({ v, pos }) => api.dropTableCmd(await needConn(v), await needDb(v), pos[0], { ...g(v), truncate: true })
|
|
232
|
+
}
|
|
233
|
+
};
|
|
234
|
+
const indexCmds = {
|
|
235
|
+
list: {
|
|
236
|
+
pos: 1,
|
|
237
|
+
run: async ({ v, pos }) => api.listIndexes(await needConn(v), await needDb(v), pos[0])
|
|
238
|
+
},
|
|
239
|
+
create: {
|
|
240
|
+
pos: 1,
|
|
241
|
+
extra: [F.name, F.columns, F.unique],
|
|
242
|
+
run: async ({ v, pos }) => api.createIndexCmd(await needConn(v), await needDb(v), pos[0], v.name, (0, parse_1.splitCsv)(v.columns), {
|
|
243
|
+
unique: !!v.unique,
|
|
244
|
+
...g(v)
|
|
245
|
+
})
|
|
246
|
+
},
|
|
247
|
+
drop: {
|
|
248
|
+
pos: 2,
|
|
249
|
+
run: async ({ v, pos }) => api.dropIndexCmd(await needConn(v), await needDb(v), pos[0], pos[1], g(v))
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
const rowsCmds = {
|
|
253
|
+
list: {
|
|
254
|
+
pos: 1,
|
|
255
|
+
extra: [F.select, F.filter, F.filterJson, F.sort],
|
|
256
|
+
run: async ({ v, pos }) => api.rowsList(await needConn(v), await needDb(v), pos[0], {
|
|
257
|
+
select: (0, parse_1.splitCsv)(v.select),
|
|
258
|
+
filters: v.filter || [],
|
|
259
|
+
filterJson: v['filter-json'],
|
|
260
|
+
sorts: v.sort || [],
|
|
261
|
+
limit: v.limit,
|
|
262
|
+
offset: v.offset,
|
|
263
|
+
count: !!v.count
|
|
264
|
+
})
|
|
265
|
+
},
|
|
266
|
+
get: {
|
|
267
|
+
pos: 1,
|
|
268
|
+
extra: [F.filter],
|
|
269
|
+
run: async ({ v, pos }) => {
|
|
270
|
+
if (!v.filter || !v.filter.length) {
|
|
271
|
+
throw new errors_1.AppError('PARAM_ERROR', 'rows get 需要 --filter,如 --filter id=42');
|
|
272
|
+
}
|
|
273
|
+
const r = await api.rowsList(await needConn(v), await needDb(v), pos[0], {
|
|
274
|
+
filters: v.filter,
|
|
275
|
+
filterJson: v['filter-json'],
|
|
276
|
+
limit: 1
|
|
277
|
+
});
|
|
278
|
+
return { conn: r.conn, database: r.database, table: r.table, data: r.data };
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
count: {
|
|
282
|
+
pos: 1,
|
|
283
|
+
extra: [F.filter, F.filterJson],
|
|
284
|
+
run: async ({ v, pos }) => api.rowsCount(await needConn(v), await needDb(v), pos[0], { filters: v.filter || [], filterJson: v['filter-json'] })
|
|
285
|
+
},
|
|
286
|
+
insert: {
|
|
287
|
+
pos: 1,
|
|
288
|
+
extra: [F.data],
|
|
289
|
+
run: async ({ v, pos }) => {
|
|
290
|
+
if (!v.data)
|
|
291
|
+
throw new errors_1.AppError('PARAM_ERROR', 'insert 需要 --data <json>');
|
|
292
|
+
return api.rowsInsert(await needConn(v), await needDb(v), pos[0], v.data, g(v));
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
'insert-many': {
|
|
296
|
+
pos: 1,
|
|
297
|
+
extra: [F.file, F.format],
|
|
298
|
+
run: async ({ v, pos }) => {
|
|
299
|
+
if (!v.file)
|
|
300
|
+
throw new errors_1.AppError('PARAM_ERROR', 'insert-many 需要 --file <csv|json>');
|
|
301
|
+
return api.importFile(await needConn(v), await needDb(v), pos[0], v.file, {
|
|
302
|
+
format: v.format,
|
|
303
|
+
limit: v.limit,
|
|
304
|
+
...g(v)
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
},
|
|
308
|
+
update: {
|
|
309
|
+
pos: 1,
|
|
310
|
+
extra: [F.filter, F.filterJson, F.set, F.setJson],
|
|
311
|
+
run: async ({ v, pos }) => {
|
|
312
|
+
let setData = {};
|
|
313
|
+
if (v.setJson) {
|
|
314
|
+
try {
|
|
315
|
+
setData = typeof v['set-json'] === 'string' ? JSON.parse(v['set-json']) : v['set-json'];
|
|
316
|
+
}
|
|
317
|
+
catch (e) {
|
|
318
|
+
throw new errors_1.AppError('PARAM_ERROR', '--set-json 不是合法 JSON');
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
setData = { ...(0, parse_1.parseKvList)(v.set || []), ...setData };
|
|
322
|
+
return api.rowsUpdate(await needConn(v), await needDb(v), pos[0], setData, {
|
|
323
|
+
filters: v.filter || [],
|
|
324
|
+
filterJson: v['filter-json'],
|
|
325
|
+
...g(v)
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
delete: {
|
|
330
|
+
pos: 1,
|
|
331
|
+
extra: [F.filter, F.filterJson],
|
|
332
|
+
run: async ({ v, pos }) => api.rowsDelete(await needConn(v), await needDb(v), pos[0], {
|
|
333
|
+
filters: v.filter || [],
|
|
334
|
+
filterJson: v['filter-json'],
|
|
335
|
+
...g(v)
|
|
336
|
+
})
|
|
337
|
+
},
|
|
338
|
+
upsert: {
|
|
339
|
+
pos: 1,
|
|
340
|
+
run: async () => {
|
|
341
|
+
throw new errors_1.AppError('UNSUPPORTED', 'upsert 暂未提供,请用 insert 失败后再 update 两步完成', {
|
|
342
|
+
hint: '复杂合并逻辑请使用 fdb2 sql --write(需先 dry-run 确认)'
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
const viewCmds = {
|
|
348
|
+
list: { run: async ({ v }) => api.listViews(await needConn(v), await needDb(v)) },
|
|
349
|
+
show: {
|
|
350
|
+
pos: 1,
|
|
351
|
+
run: async ({ v, pos }) => api.getViewDefinitionCmd(await needConn(v), await needDb(v), pos[0])
|
|
352
|
+
},
|
|
353
|
+
create: {
|
|
354
|
+
pos: 1,
|
|
355
|
+
extra: [F.as],
|
|
356
|
+
run: async ({ v, pos }) => api.createViewCmd(await needConn(v), await needDb(v), pos[0], v.as, g(v))
|
|
357
|
+
},
|
|
358
|
+
drop: {
|
|
359
|
+
pos: 1,
|
|
360
|
+
run: async ({ v, pos }) => api.dropViewCmd(await needConn(v), await needDb(v), pos[0], g(v))
|
|
361
|
+
}
|
|
362
|
+
};
|
|
363
|
+
const procCmds = {
|
|
364
|
+
list: { run: async ({ v }) => api.listProcedures(await needConn(v), await needDb(v)) },
|
|
365
|
+
show: {
|
|
366
|
+
pos: 1,
|
|
367
|
+
run: async ({ v, pos }) => api.getProcedureDefinitionCmd(await needConn(v), await needDb(v), pos[0])
|
|
368
|
+
},
|
|
369
|
+
create: {
|
|
370
|
+
pos: 1,
|
|
371
|
+
extra: [F.body],
|
|
372
|
+
run: async ({ v, pos }) => {
|
|
373
|
+
throw new errors_1.AppError('UNSUPPORTED', '存储过程创建涉及复杂方言语法,请使用桌面端或 fdb2 sql', {
|
|
374
|
+
hint: '如需执行现成脚本可: fdb2 sql --write --allow-multi < 脚本.sql'
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
},
|
|
378
|
+
drop: {
|
|
379
|
+
pos: 1,
|
|
380
|
+
run: async ({ v, pos }) => api.dropProcedureCmd(await needConn(v), await needDb(v), pos[0], g(v))
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
const exportCmds = {
|
|
384
|
+
rows: {
|
|
385
|
+
pos: 1,
|
|
386
|
+
extra: [F.format, F.filter, F.filterJson],
|
|
387
|
+
run: async ({ v, pos }) => api.exportRowsFile(await needConn(v), await needDb(v), pos[0], v.format || 'json', v.out, {
|
|
388
|
+
filters: v.filter || [],
|
|
389
|
+
filterJson: v['filter-json'],
|
|
390
|
+
limit: v.limit
|
|
391
|
+
})
|
|
392
|
+
},
|
|
393
|
+
schema: {
|
|
394
|
+
run: async ({ v }) => api.exportSchemaCmd(await needConn(v), await needDb(v), v.out)
|
|
395
|
+
},
|
|
396
|
+
dump: {
|
|
397
|
+
pos: 1,
|
|
398
|
+
run: async ({ v, pos }) => api.backupCmd(await needConn(v), pos[0], v.out)
|
|
399
|
+
}
|
|
400
|
+
};
|
|
401
|
+
const importCmds = {
|
|
402
|
+
file: {
|
|
403
|
+
pos: 1,
|
|
404
|
+
extra: [F.file, F.format],
|
|
405
|
+
run: async ({ v, pos }) => {
|
|
406
|
+
if (!v.file)
|
|
407
|
+
throw new errors_1.AppError('PARAM_ERROR', 'import file 需要 --file <csv|json>');
|
|
408
|
+
return api.importFile(await needConn(v), await needDb(v), pos[0], v.file, {
|
|
409
|
+
format: v.format,
|
|
410
|
+
limit: v.limit,
|
|
411
|
+
...g(v)
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
};
|
|
416
|
+
const opsCmds = {
|
|
417
|
+
stats: {
|
|
418
|
+
run: async ({ v }) => api.statsCmd(await needConn(v), await needDb(v))
|
|
419
|
+
},
|
|
420
|
+
health: {
|
|
421
|
+
run: async ({ v }) => {
|
|
422
|
+
const conn = await api.resolveConnection(v.conn || null);
|
|
423
|
+
return api.testConnectionByRef(conn.name || conn.id);
|
|
424
|
+
}
|
|
425
|
+
},
|
|
426
|
+
optimize: {
|
|
427
|
+
run: async ({ v }) => api.maintenanceCmd(await needConn(v), await needDb(v), 'optimize', g(v))
|
|
428
|
+
},
|
|
429
|
+
analyze: {
|
|
430
|
+
run: async ({ v }) => api.maintenanceCmd(await needConn(v), await needDb(v), 'analyze', g(v))
|
|
431
|
+
},
|
|
432
|
+
repair: {
|
|
433
|
+
run: async ({ v }) => api.maintenanceCmd(await needConn(v), await needDb(v), 'repair', g(v))
|
|
434
|
+
},
|
|
435
|
+
logs: {
|
|
436
|
+
extra: [F.level],
|
|
437
|
+
run: async ({ v }) => api.logsCmd(await needConn(v), v.db, v.limit || 100)
|
|
438
|
+
}
|
|
439
|
+
};
|
|
440
|
+
const rootCmds = {
|
|
441
|
+
conn: { run: async () => api.listConnections() },
|
|
442
|
+
conns: { run: async () => api.listConnections() },
|
|
443
|
+
types: { run: async () => api.types() },
|
|
444
|
+
current: { run: async () => api.currentConnection() },
|
|
445
|
+
setup: { run: async () => api.setupInfo() },
|
|
446
|
+
audit: {
|
|
447
|
+
run: async ({ v }) => api.auditLog(v.limit || 100)
|
|
448
|
+
},
|
|
449
|
+
sql: {
|
|
450
|
+
extra: [F.allowMulti],
|
|
451
|
+
run: async ({ v, pos }) => {
|
|
452
|
+
const sqlText = v.posSql || pos[0];
|
|
453
|
+
if (!sqlText)
|
|
454
|
+
throw new errors_1.AppError('PARAM_ERROR', 'sql 需要 SQL 文本');
|
|
455
|
+
return api.runSql(await needConn(v), v.db, sqlText, {
|
|
456
|
+
write: !!v.write,
|
|
457
|
+
limit: v.limit,
|
|
458
|
+
allowMulti: !!v['allow-multi'],
|
|
459
|
+
yes: !!v.yes,
|
|
460
|
+
dryRun: !!v['dry-run'],
|
|
461
|
+
confirm: v.confirm || null
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
// 允许在任意子命令尾部追加 SQL(fdb2 sql '...')
|
|
467
|
+
const groups = {
|
|
468
|
+
conn: connCmds,
|
|
469
|
+
db: dbCmds,
|
|
470
|
+
table: tableCmds,
|
|
471
|
+
index: indexCmds,
|
|
472
|
+
rows: rowsCmds,
|
|
473
|
+
view: viewCmds,
|
|
474
|
+
proc: procCmds,
|
|
475
|
+
export: exportCmds,
|
|
476
|
+
import: importCmds,
|
|
477
|
+
ops: opsCmds
|
|
478
|
+
};
|
|
479
|
+
const authCmds = {
|
|
480
|
+
login: {
|
|
481
|
+
run: async () => {
|
|
482
|
+
return {
|
|
483
|
+
message: '本工具通过本地连接配置访问数据库,无第三方账号。请在 WorkBuddy 对话中提供连接信息,或执行 fdb2 conn add 添加连接。'
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
},
|
|
487
|
+
logout: {
|
|
488
|
+
run: async () => {
|
|
489
|
+
// 清空默认连接,保留连接配置(不删除用户数据)
|
|
490
|
+
const { updateConfig } = require('../core/config');
|
|
491
|
+
updateConfig({ defaultConnectionId: null });
|
|
492
|
+
return { message: '已登出:默认连接已清除(连接配置保留)。可通过 fdb2 conn use <name> 重新设置默认连接。' };
|
|
493
|
+
}
|
|
494
|
+
},
|
|
495
|
+
status: {
|
|
496
|
+
run: async () => {
|
|
497
|
+
const list = await api.allConnections();
|
|
498
|
+
const cfg = require('../core/config').getConfig();
|
|
499
|
+
if (list.length === 0) {
|
|
500
|
+
throw new errors_1.AppError('NOT_READY', 'Not connected: no database connection configured', {
|
|
501
|
+
hint: '请先执行 fdb2 conn add 添加数据库连接'
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
return { status: 'Connected', message: `Connected: ${list.length} connections available`, defaultConnection: cfg.defaultConnectionId || null, connections: list.length };
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
// ================= 执行 =================
|
|
509
|
+
function collectDefs(def) {
|
|
510
|
+
return def.extra || [];
|
|
511
|
+
}
|
|
512
|
+
async function runCommand(argv) {
|
|
513
|
+
const first = argv[0] || '';
|
|
514
|
+
// 根命令
|
|
515
|
+
if (!first || first === 'help' || first === '-h' || first === '--help') {
|
|
516
|
+
return { cmdLabel: 'help', payload: { help: helpText() } };
|
|
517
|
+
}
|
|
518
|
+
if (first === 'auth') {
|
|
519
|
+
const sub = argv[1] || 'status';
|
|
520
|
+
const def = authCmds[sub];
|
|
521
|
+
if (!def)
|
|
522
|
+
throw new errors_1.AppError('UNKNOWN_COMMAND', `未知 auth 子命令: ${sub}`);
|
|
523
|
+
const { pos, values } = (0, parse_1.parseArgs)(argv.slice(2), collectDefs(def));
|
|
524
|
+
return { cmdLabel: `auth:${sub}`, payload: await def.run({ v: values, pos, cmd: sub }) };
|
|
525
|
+
}
|
|
526
|
+
if (first === 'config') {
|
|
527
|
+
return runConfig(argv.slice(1));
|
|
528
|
+
}
|
|
529
|
+
if (first === 'backup') {
|
|
530
|
+
const { pos, values } = (0, parse_1.parseArgs)(argv.slice(1), [F.format]);
|
|
531
|
+
if (!pos[0])
|
|
532
|
+
throw new errors_1.AppError('PARAM_ERROR', 'backup <db> [--out <dir>]');
|
|
533
|
+
return { cmdLabel: 'backup', payload: await api.backupCmd(await needConn(values), pos[0], values.out) };
|
|
534
|
+
}
|
|
535
|
+
if (first === 'restore') {
|
|
536
|
+
const { pos, values } = (0, parse_1.parseArgs)(argv.slice(1), [F.file]);
|
|
537
|
+
if (!pos[0] || !values.file)
|
|
538
|
+
throw new errors_1.AppError('PARAM_ERROR', 'restore <db> --file <path>');
|
|
539
|
+
return { cmdLabel: 'restore', payload: await api.restoreCmd(await needConn(values), pos[0], values.file, g(values)) };
|
|
540
|
+
}
|
|
541
|
+
// group 命令
|
|
542
|
+
const group = groups[first];
|
|
543
|
+
if (group) {
|
|
544
|
+
const sub = argv[1] || 'list';
|
|
545
|
+
const def = group[sub];
|
|
546
|
+
if (!def)
|
|
547
|
+
throw new errors_1.AppError('UNKNOWN_COMMAND', `未知 ${first} 子命令: ${sub}`, { hint: helpGroup(first) });
|
|
548
|
+
const { pos, values } = (0, parse_1.parseArgs)(argv.slice(2), collectDefs(def));
|
|
549
|
+
const minPos = def.pos === undefined ? 0 : Array.isArray(def.pos) ? Math.max(...def.pos) + 1 : def.pos;
|
|
550
|
+
if (pos.length < minPos) {
|
|
551
|
+
throw new errors_1.AppError('PARAM_ERROR', `fdb2 ${first} ${sub} 缺少参数`);
|
|
552
|
+
}
|
|
553
|
+
return { cmdLabel: `${first}:${sub}`, payload: await def.run({ v: values, pos, cmd: sub }) };
|
|
554
|
+
}
|
|
555
|
+
const rootDef = rootCmds[first];
|
|
556
|
+
if (rootDef) {
|
|
557
|
+
const { pos, values } = (0, parse_1.parseArgs)(argv.slice(1), collectDefs(rootDef));
|
|
558
|
+
// sql 特殊:允许整段 SQL 作为位置参数(可能含空格,由 shell 引号传入)
|
|
559
|
+
// 无位置参数时若 stdin 被重定向(非 TTY)则从 stdin 读取(支持 fdb2 sql < script.sql)
|
|
560
|
+
if (first === 'sql') {
|
|
561
|
+
if (pos.length > 0) {
|
|
562
|
+
values.posSql = pos.join(' ');
|
|
563
|
+
}
|
|
564
|
+
else if (process.stdin.isTTY === false) {
|
|
565
|
+
values.posSql = await readStdin();
|
|
566
|
+
}
|
|
567
|
+
pos.length = 0;
|
|
568
|
+
}
|
|
569
|
+
return { cmdLabel: first, payload: await rootDef.run({ v: values, pos, cmd: first }) };
|
|
570
|
+
}
|
|
571
|
+
if (first === '-v' || first === '--version' || first === 'version') {
|
|
572
|
+
return { cmdLabel: 'version', payload: { version: exports.VERSION } };
|
|
573
|
+
}
|
|
574
|
+
throw new errors_1.AppError('UNKNOWN_COMMAND', `未知命令: ${first}`, { hint: helpText() });
|
|
575
|
+
}
|
|
576
|
+
async function runConfig(argv) {
|
|
577
|
+
const sub = argv[0] || 'show';
|
|
578
|
+
const cfgApi = require('../core/config');
|
|
579
|
+
if (sub === 'show') {
|
|
580
|
+
return { cmdLabel: 'config:show', payload: await api.configShow() };
|
|
581
|
+
}
|
|
582
|
+
if (sub === 'get') {
|
|
583
|
+
const { pos, values } = (0, parse_1.parseArgs)(argv.slice(1), []);
|
|
584
|
+
if (!pos[0])
|
|
585
|
+
throw new errors_1.AppError('PARAM_ERROR', 'config get <key>');
|
|
586
|
+
const show = await api.configShow();
|
|
587
|
+
return { cmdLabel: 'config:get', payload: { key: pos[0], value: show.config[pos[0]] ?? null } };
|
|
588
|
+
}
|
|
589
|
+
if (sub === 'set') {
|
|
590
|
+
const { pos, values } = (0, parse_1.parseArgs)(argv.slice(1), []);
|
|
591
|
+
if (pos.length < 2)
|
|
592
|
+
throw new errors_1.AppError('PARAM_ERROR', 'config set <key> <value>');
|
|
593
|
+
return { cmdLabel: 'config:set', payload: await api.configSet(pos[0], pos[1]) };
|
|
594
|
+
}
|
|
595
|
+
throw new errors_1.AppError('UNKNOWN_COMMAND', `未知 config 子命令: ${sub}`, { hint: 'config show | get <key> | set <key> <value>' });
|
|
596
|
+
}
|
|
597
|
+
function helpText() {
|
|
598
|
+
return `fdb2 — 数据库命令行工具(支持 8 种数据库)
|
|
599
|
+
|
|
600
|
+
用法: fdb2 <命令> [参数]
|
|
601
|
+
|
|
602
|
+
连接管理:
|
|
603
|
+
conn list 列出全部连接
|
|
604
|
+
conn get <name|id> 查看连接详情
|
|
605
|
+
conn add --name <n> --type <t> --database <d> [--host] [--port] [--username] [--password]
|
|
606
|
+
conn update <name|id> [--host ...] [--password-stdin]
|
|
607
|
+
conn remove <name|id> 删除连接
|
|
608
|
+
conn test <name|id> 连通性测试
|
|
609
|
+
conn use <name|id> 设为默认连接
|
|
610
|
+
current 显示当前默认连接
|
|
611
|
+
types 支持的数据库类型
|
|
612
|
+
|
|
613
|
+
数据库:
|
|
614
|
+
db list | db info <db> | db create <db> [--charset] | db drop <db>
|
|
615
|
+
|
|
616
|
+
表 / 索引 / 视图 / 过程:
|
|
617
|
+
table list [--pattern] | table columns <t> | table info <t>
|
|
618
|
+
table indexes <t> | table fks <t> | table stats <t>
|
|
619
|
+
table create <t> --column 'name:varchar:len=64:not_null' [--column ...]
|
|
620
|
+
table alter <t> --add-column ... | --drop-column <c> | --modify-column ... | --rename-to <n>
|
|
621
|
+
table drop <t> | table truncate <t>
|
|
622
|
+
index list <t> | index create <t> --name idx --columns a,b [--unique] | index drop <t> <name>
|
|
623
|
+
view list | show <n> | create <n> --as '<SELECT...>' | drop <n>
|
|
624
|
+
proc list | show <n> | drop <n>
|
|
625
|
+
|
|
626
|
+
行数据(显式接口,禁止裸拼 SQL):
|
|
627
|
+
rows list <t> [--select a,b] [--filter 'age>=18']... [--sort created_at:desc] [--limit] [--count]
|
|
628
|
+
rows get <t> --filter 'id=42'
|
|
629
|
+
rows count <t> [--filter ...]
|
|
630
|
+
rows insert <t> --data '{"name":"a"}'
|
|
631
|
+
rows insert-many <t> --file rows.csv
|
|
632
|
+
rows update <t> --filter <expr> --set 'status=active' [--dry-run]
|
|
633
|
+
rows delete <t> --filter <expr> [--dry-run]
|
|
634
|
+
|
|
635
|
+
导入导出 / 备份 / 运维:
|
|
636
|
+
export rows <t> --format json|csv|sql [--out <f>]
|
|
637
|
+
export schema [--out <f>] | export dump <db> [--out <f>]
|
|
638
|
+
import file <t> --file <f> [--format csv|json]
|
|
639
|
+
backup <db> [--out <dir>] | restore <db> --file <f>
|
|
640
|
+
ops stats | optimize | analyze | repair | logs | health
|
|
641
|
+
|
|
642
|
+
其它:
|
|
643
|
+
sql '<SQL>' [--write] [--limit] SQL 逃生舱(默认只读、禁多语句)
|
|
644
|
+
config show | get <key> | set <key> <value>
|
|
645
|
+
audit [--limit N] | setup | auth status | help
|
|
646
|
+
|
|
647
|
+
写操作安全协议(必须):
|
|
648
|
+
1. 先执行 --dry-run 预演,获得预计影响行数与 confirmToken
|
|
649
|
+
2. 用户确认后携带 --confirm <token>(破坏性操作另加 --yes)执行
|
|
650
|
+
影响行数超过阈值(默认100)需 --force;全局只读模式下需 --write
|
|
651
|
+
MongoDB 为实验性支持。所有命令可用 --json 输出机器可读结果。`;
|
|
652
|
+
}
|
|
653
|
+
function helpGroup(group) {
|
|
654
|
+
const map = {
|
|
655
|
+
conn: ['list', 'get', 'add', 'update', 'remove', 'test', 'use', 'current'],
|
|
656
|
+
db: ['list', 'info', 'create', 'drop'],
|
|
657
|
+
table: ['list', 'info', 'columns', 'indexes', 'fks', 'stats', 'create', 'alter', 'rename', 'drop', 'truncate'],
|
|
658
|
+
index: ['list', 'create', 'drop'],
|
|
659
|
+
rows: ['list', 'get', 'count', 'insert', 'insert-many', 'update', 'delete'],
|
|
660
|
+
view: ['list', 'show', 'create', 'drop'],
|
|
661
|
+
proc: ['list', 'show', 'drop'],
|
|
662
|
+
export: ['rows', 'schema', 'dump'],
|
|
663
|
+
import: ['file'],
|
|
664
|
+
ops: ['stats', 'health', 'optimize', 'analyze', 'repair', 'logs']
|
|
665
|
+
};
|
|
666
|
+
return `fdb2 ${group} <${(map[group] || []).join('|')}>`;
|
|
667
|
+
}
|
|
668
|
+
async function run(argv) {
|
|
669
|
+
let jsonMode = argv.includes('--json');
|
|
670
|
+
let quiet = argv.includes('--quiet');
|
|
671
|
+
try {
|
|
672
|
+
const { cmdLabel, payload } = await runCommand(argv);
|
|
673
|
+
if (cmdLabel === 'help') {
|
|
674
|
+
process.stdout.write(payload.help + '\n');
|
|
675
|
+
return 0;
|
|
676
|
+
}
|
|
677
|
+
if (cmdLabel === 'version') {
|
|
678
|
+
process.stdout.write(payload.version + '\n');
|
|
679
|
+
return 0;
|
|
680
|
+
}
|
|
681
|
+
if (cmdLabel === 'auth:status') {
|
|
682
|
+
// 状态检查文本(statusMatch 依赖固定前缀)
|
|
683
|
+
process.stdout.write(payload.status === 'Connected' ? payload.message + '\n' : 'Not connected\n');
|
|
684
|
+
return 0;
|
|
685
|
+
}
|
|
686
|
+
if (jsonMode) {
|
|
687
|
+
process.stdout.write(JSON.stringify({ ok: true, ...payload }, null, 2) + '\n');
|
|
688
|
+
}
|
|
689
|
+
else {
|
|
690
|
+
const text = (0, format_1.humanizeResult)(cmdLabel, payload);
|
|
691
|
+
process.stdout.write(text.endsWith('\n') ? text : text + '\n');
|
|
692
|
+
}
|
|
693
|
+
return 0;
|
|
694
|
+
}
|
|
695
|
+
catch (err) {
|
|
696
|
+
const appErr = err instanceof errors_1.AppError ? err : new errors_1.AppError('EXEC_ERROR', err?.message || String(err), { hint: err?.hint });
|
|
697
|
+
const code = appErr.code;
|
|
698
|
+
const exit = (0, errors_1.exitCodeOf)(code);
|
|
699
|
+
if (jsonMode) {
|
|
700
|
+
process.stdout.write(JSON.stringify({ ok: false, error: { code, message: appErr.message, hint: appErr.hint || undefined, details: appErr.details } }, null, 2) + '\n');
|
|
701
|
+
}
|
|
702
|
+
else {
|
|
703
|
+
process.stderr.write(`错误 [${code}]: ${appErr.message}\n`);
|
|
704
|
+
if (appErr.hint)
|
|
705
|
+
process.stderr.write(`提示: ${appErr.hint}\n`);
|
|
706
|
+
}
|
|
707
|
+
return exit;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
//# sourceMappingURL=main.js.map
|