fdb2 1.0.27 → 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.js +318 -193
- package/dist/public/index.css +59 -0
- package/dist/public/index.js +486 -259
- package/dist/public/layout.js +2 -2
- 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
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CLI 统一错误码与错误类型
|
|
4
|
+
* 退出码:
|
|
5
|
+
* 0 成功
|
|
6
|
+
* 1 通用错误(GENERIC)
|
|
7
|
+
* 2 参数/用法错误(PARAM_ERROR、UNKNOWN_COMMAND、INVALID_FILTER)
|
|
8
|
+
* 3 连接不存在(CONN_NOT_FOUND、NO_DEFAULT_CONN)
|
|
9
|
+
* 4 未就绪(NOT_READY)
|
|
10
|
+
* 5 需确认/预演(REQUIRE_DRYRUN、CONFIRM_REQUIRED、CONFIRM_EXPIRED、FORCE_REQUIRED、WRITE_BLOCKED、MULTI_STATEMENT_BLOCKED)
|
|
11
|
+
* 6 SQL/数据库错误(SQL_ERROR、DB_NOT_FOUND、TABLE_NOT_FOUND、COLUMN_NOT_FOUND、UNSUPPORTED、DRIVER_UNAVAILABLE)
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.AppError = void 0;
|
|
15
|
+
exports.exitCodeOf = exitCodeOf;
|
|
16
|
+
class AppError extends Error {
|
|
17
|
+
code;
|
|
18
|
+
hint;
|
|
19
|
+
details;
|
|
20
|
+
constructor(code, message, opts) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.code = code;
|
|
23
|
+
this.hint = opts?.hint;
|
|
24
|
+
this.details = opts?.details;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
exports.AppError = AppError;
|
|
28
|
+
function exitCodeOf(code) {
|
|
29
|
+
switch (code) {
|
|
30
|
+
case 'PARAM_ERROR':
|
|
31
|
+
case 'UNKNOWN_COMMAND':
|
|
32
|
+
case 'INVALID_FILTER':
|
|
33
|
+
case 'INVALID_COLUMN_SPEC':
|
|
34
|
+
case 'INVALID_OPTION':
|
|
35
|
+
return 2;
|
|
36
|
+
case 'CONN_NOT_FOUND':
|
|
37
|
+
case 'NO_DEFAULT_CONN':
|
|
38
|
+
return 3;
|
|
39
|
+
case 'NOT_READY':
|
|
40
|
+
return 4;
|
|
41
|
+
case 'REQUIRE_DRYRUN':
|
|
42
|
+
case 'CONFIRM_REQUIRED':
|
|
43
|
+
case 'CONFIRM_EXPIRED':
|
|
44
|
+
case 'FORCE_REQUIRED':
|
|
45
|
+
case 'WRITE_BLOCKED':
|
|
46
|
+
case 'MULTI_STATEMENT_BLOCKED':
|
|
47
|
+
case 'WRITE_NOT_ALLOWED':
|
|
48
|
+
return 5;
|
|
49
|
+
case 'SQL_ERROR':
|
|
50
|
+
case 'DB_NOT_FOUND':
|
|
51
|
+
case 'TABLE_NOT_FOUND':
|
|
52
|
+
case 'COLUMN_NOT_FOUND':
|
|
53
|
+
case 'UNSUPPORTED':
|
|
54
|
+
case 'DRIVER_UNAVAILABLE':
|
|
55
|
+
case 'EXEC_ERROR':
|
|
56
|
+
return 6;
|
|
57
|
+
default:
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../server/core/errors.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;GAUG;;;AAeH,gCAgCC;AA7CD,MAAa,QAAS,SAAQ,KAAK;IACjC,IAAI,CAAS;IACb,IAAI,CAAU;IACd,OAAO,CAAO;IAEd,YAAY,IAAY,EAAE,OAAe,EAAE,IAAuC;QAChF,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,EAAE,IAAI,CAAC;QACvB,IAAI,CAAC,OAAO,GAAG,IAAI,EAAE,OAAO,CAAC;IAC/B,CAAC;CACF;AAXD,4BAWC;AAED,SAAgB,UAAU,CAAC,IAAY;IACrC,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,aAAa,CAAC;QACnB,KAAK,iBAAiB,CAAC;QACvB,KAAK,gBAAgB,CAAC;QACtB,KAAK,qBAAqB,CAAC;QAC3B,KAAK,gBAAgB;YACnB,OAAO,CAAC,CAAC;QACX,KAAK,gBAAgB,CAAC;QACtB,KAAK,iBAAiB;YACpB,OAAO,CAAC,CAAC;QACX,KAAK,WAAW;YACd,OAAO,CAAC,CAAC;QACX,KAAK,gBAAgB,CAAC;QACtB,KAAK,kBAAkB,CAAC;QACxB,KAAK,iBAAiB,CAAC;QACvB,KAAK,gBAAgB,CAAC;QACtB,KAAK,eAAe,CAAC;QACrB,KAAK,yBAAyB,CAAC;QAC/B,KAAK,mBAAmB;YACtB,OAAO,CAAC,CAAC;QACX,KAAK,WAAW,CAAC;QACjB,KAAK,cAAc,CAAC;QACpB,KAAK,iBAAiB,CAAC;QACvB,KAAK,kBAAkB,CAAC;QACxB,KAAK,aAAa,CAAC;QACnB,KAAK,oBAAoB,CAAC;QAC1B,KAAK,YAAY;YACf,OAAO,CAAC,CAAC;QACX;YACE,OAAO,CAAC,CAAC;IACb,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI 统一错误码与错误类型
|
|
3
|
+
* 退出码:
|
|
4
|
+
* 0 成功
|
|
5
|
+
* 1 通用错误(GENERIC)
|
|
6
|
+
* 2 参数/用法错误(PARAM_ERROR、UNKNOWN_COMMAND、INVALID_FILTER)
|
|
7
|
+
* 3 连接不存在(CONN_NOT_FOUND、NO_DEFAULT_CONN)
|
|
8
|
+
* 4 未就绪(NOT_READY)
|
|
9
|
+
* 5 需确认/预演(REQUIRE_DRYRUN、CONFIRM_REQUIRED、CONFIRM_EXPIRED、FORCE_REQUIRED、WRITE_BLOCKED、MULTI_STATEMENT_BLOCKED)
|
|
10
|
+
* 6 SQL/数据库错误(SQL_ERROR、DB_NOT_FOUND、TABLE_NOT_FOUND、COLUMN_NOT_FOUND、UNSUPPORTED、DRIVER_UNAVAILABLE)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export class AppError extends Error {
|
|
14
|
+
code: string;
|
|
15
|
+
hint?: string;
|
|
16
|
+
details?: any;
|
|
17
|
+
|
|
18
|
+
constructor(code: string, message: string, opts?: { hint?: string; details?: any }) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.code = code;
|
|
21
|
+
this.hint = opts?.hint;
|
|
22
|
+
this.details = opts?.details;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function exitCodeOf(code: string): number {
|
|
27
|
+
switch (code) {
|
|
28
|
+
case 'PARAM_ERROR':
|
|
29
|
+
case 'UNKNOWN_COMMAND':
|
|
30
|
+
case 'INVALID_FILTER':
|
|
31
|
+
case 'INVALID_COLUMN_SPEC':
|
|
32
|
+
case 'INVALID_OPTION':
|
|
33
|
+
return 2;
|
|
34
|
+
case 'CONN_NOT_FOUND':
|
|
35
|
+
case 'NO_DEFAULT_CONN':
|
|
36
|
+
return 3;
|
|
37
|
+
case 'NOT_READY':
|
|
38
|
+
return 4;
|
|
39
|
+
case 'REQUIRE_DRYRUN':
|
|
40
|
+
case 'CONFIRM_REQUIRED':
|
|
41
|
+
case 'CONFIRM_EXPIRED':
|
|
42
|
+
case 'FORCE_REQUIRED':
|
|
43
|
+
case 'WRITE_BLOCKED':
|
|
44
|
+
case 'MULTI_STATEMENT_BLOCKED':
|
|
45
|
+
case 'WRITE_NOT_ALLOWED':
|
|
46
|
+
return 5;
|
|
47
|
+
case 'SQL_ERROR':
|
|
48
|
+
case 'DB_NOT_FOUND':
|
|
49
|
+
case 'TABLE_NOT_FOUND':
|
|
50
|
+
case 'COLUMN_NOT_FOUND':
|
|
51
|
+
case 'UNSUPPORTED':
|
|
52
|
+
case 'DRIVER_UNAVAILABLE':
|
|
53
|
+
case 'EXEC_ERROR':
|
|
54
|
+
return 6;
|
|
55
|
+
default:
|
|
56
|
+
return 1;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 过滤条件 DSL:把自然语言里的结构化筛选条件解析成条件树,
|
|
3
|
+
* 由 query-builder 编译为带白名单校验与参数绑定的 SQL。
|
|
4
|
+
*
|
|
5
|
+
* 行内语法 --filter "<field><op><value>"
|
|
6
|
+
* = != <> > >= < <=
|
|
7
|
+
* ~ 包含子串(模糊) ^ 前缀 $ 后缀
|
|
8
|
+
* ~c 区分大小写包含
|
|
9
|
+
* in/nin 集合 between 区间 null/notnull 空值
|
|
10
|
+
* 值中逗号用 \, 转义
|
|
11
|
+
*
|
|
12
|
+
* 复杂逻辑 --filter-json '{"or":[...],"and":[...]}'
|
|
13
|
+
*/
|
|
14
|
+
export interface FilterLeaf {
|
|
15
|
+
field: string;
|
|
16
|
+
op: 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'likeCase' | 'starts' | 'ends' | 'in' | 'nin' | 'between' | 'isNull' | 'notNull';
|
|
17
|
+
value?: any;
|
|
18
|
+
}
|
|
19
|
+
export type FilterNode = {
|
|
20
|
+
type: 'and';
|
|
21
|
+
children: FilterNode[];
|
|
22
|
+
} | {
|
|
23
|
+
type: 'or';
|
|
24
|
+
children: FilterNode[];
|
|
25
|
+
} | {
|
|
26
|
+
type: 'not';
|
|
27
|
+
child: FilterNode;
|
|
28
|
+
} | {
|
|
29
|
+
type: 'leaf';
|
|
30
|
+
leaf: FilterLeaf;
|
|
31
|
+
};
|
|
32
|
+
export declare function alwaysTrue(): FilterNode;
|
|
33
|
+
/** 单个条件文本解析 */
|
|
34
|
+
export declare function parseFilterExpr(expr: string): FilterLeaf;
|
|
35
|
+
/** 解析 --filter 重复参数(多个 AND) */
|
|
36
|
+
export declare function parseFilters(exprs: string[]): FilterNode;
|
|
37
|
+
/** 解析 --filter-json 复杂条件 */
|
|
38
|
+
export declare function parseFilterJson(jsonStr: string): FilterNode;
|
|
39
|
+
//# sourceMappingURL=filter-dsl.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-dsl.d.ts","sourceRoot":"","sources":["../../../server/core/filter-dsl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,EAAE,EACE,IAAI,GAAG,KAAK,GACZ,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAC3B,MAAM,GAAG,UAAU,GAAG,QAAQ,GAAG,MAAM,GACvC,IAAI,GAAG,KAAK,GAAG,SAAS,GACxB,QAAQ,GAAG,SAAS,CAAC;IACzB,KAAK,CAAC,EAAE,GAAG,CAAC;CACb;AAED,MAAM,MAAM,UAAU,GAClB;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,QAAQ,EAAE,UAAU,EAAE,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,UAAU,EAAE,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,UAAU,CAAA;CAAE,GAClC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAEvC,wBAAgB,UAAU,IAAI,UAAU,CAEvC;AAED,eAAe;AACf,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,CAqDxD;AA4BD,+BAA+B;AAC/B,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,UAAU,CAKxD;AAED,4BAA4B;AAC5B,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CAQ3D"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 过滤条件 DSL:把自然语言里的结构化筛选条件解析成条件树,
|
|
4
|
+
* 由 query-builder 编译为带白名单校验与参数绑定的 SQL。
|
|
5
|
+
*
|
|
6
|
+
* 行内语法 --filter "<field><op><value>"
|
|
7
|
+
* = != <> > >= < <=
|
|
8
|
+
* ~ 包含子串(模糊) ^ 前缀 $ 后缀
|
|
9
|
+
* ~c 区分大小写包含
|
|
10
|
+
* in/nin 集合 between 区间 null/notnull 空值
|
|
11
|
+
* 值中逗号用 \, 转义
|
|
12
|
+
*
|
|
13
|
+
* 复杂逻辑 --filter-json '{"or":[...],"and":[...]}'
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.alwaysTrue = alwaysTrue;
|
|
17
|
+
exports.parseFilterExpr = parseFilterExpr;
|
|
18
|
+
exports.parseFilters = parseFilters;
|
|
19
|
+
exports.parseFilterJson = parseFilterJson;
|
|
20
|
+
function alwaysTrue() {
|
|
21
|
+
return { type: 'leaf', leaf: { field: '', op: 'eq', value: 1 } };
|
|
22
|
+
}
|
|
23
|
+
/** 单个条件文本解析 */
|
|
24
|
+
function parseFilterExpr(expr) {
|
|
25
|
+
if (expr == null || typeof expr !== 'string') {
|
|
26
|
+
throw new Error('条件为空');
|
|
27
|
+
}
|
|
28
|
+
const text = expr.trim();
|
|
29
|
+
if (!text) {
|
|
30
|
+
throw new Error('条件为空');
|
|
31
|
+
}
|
|
32
|
+
const nullMatch = /^([^\s=<>~^$!]+)\s*(null|notnull|isnull|notnull)$/i.exec(text);
|
|
33
|
+
if (nullMatch) {
|
|
34
|
+
const op = nullMatch[2].toLowerCase();
|
|
35
|
+
return { field: nullMatch[1], op: op === 'isnull' || op === 'null' ? 'isNull' : 'notNull' };
|
|
36
|
+
}
|
|
37
|
+
// 操作符顺序:多字符在前
|
|
38
|
+
const ops = [
|
|
39
|
+
['!=', 'neq'],
|
|
40
|
+
['<>', 'neq'],
|
|
41
|
+
['>=', 'gte'],
|
|
42
|
+
['<=', 'lte'],
|
|
43
|
+
['~c', 'likeCase'],
|
|
44
|
+
['~', 'like'],
|
|
45
|
+
['like', 'like'],
|
|
46
|
+
['starts', 'starts'],
|
|
47
|
+
['ends', 'ends'],
|
|
48
|
+
['in', 'in'],
|
|
49
|
+
['nin', 'nin'],
|
|
50
|
+
['between', 'between'],
|
|
51
|
+
['>', 'gt'],
|
|
52
|
+
['<', 'lt'],
|
|
53
|
+
['=', 'eq'],
|
|
54
|
+
['^', 'starts'],
|
|
55
|
+
['$', 'ends']
|
|
56
|
+
];
|
|
57
|
+
for (const [sym, op] of ops) {
|
|
58
|
+
const idx = text.indexOf(sym);
|
|
59
|
+
if (idx > 0) {
|
|
60
|
+
const field = text.slice(0, idx).trim();
|
|
61
|
+
let rawValue = text.slice(idx + sym.length).trim();
|
|
62
|
+
if (!field || rawValue === '') {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (op === 'in' || op === 'nin' || op === 'between') {
|
|
66
|
+
const parts = splitEscaped(rawValue);
|
|
67
|
+
return { field, op: op, value: parts };
|
|
68
|
+
}
|
|
69
|
+
return { field, op: op, value: unescapeValue(rawValue) };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
throw new Error(`无法解析条件: ${expr}(支持 = != > < >= <= ~ ^ $ in nin between null)`);
|
|
73
|
+
}
|
|
74
|
+
function splitEscaped(value) {
|
|
75
|
+
const parts = [];
|
|
76
|
+
let current = '';
|
|
77
|
+
let escaped = false;
|
|
78
|
+
for (const ch of value) {
|
|
79
|
+
if (escaped) {
|
|
80
|
+
current += ch;
|
|
81
|
+
escaped = false;
|
|
82
|
+
}
|
|
83
|
+
else if (ch === '\\') {
|
|
84
|
+
escaped = true;
|
|
85
|
+
}
|
|
86
|
+
else if (ch === ',') {
|
|
87
|
+
parts.push(current);
|
|
88
|
+
current = '';
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
current += ch;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
if (escaped)
|
|
95
|
+
current += '\\';
|
|
96
|
+
parts.push(current);
|
|
97
|
+
return parts;
|
|
98
|
+
}
|
|
99
|
+
function unescapeValue(value) {
|
|
100
|
+
return value.replace(/\\,/g, ',').trim();
|
|
101
|
+
}
|
|
102
|
+
/** 解析 --filter 重复参数(多个 AND) */
|
|
103
|
+
function parseFilters(exprs) {
|
|
104
|
+
const leaves = (exprs || []).filter(Boolean).map((e) => parseFilterExpr(e));
|
|
105
|
+
if (leaves.length === 0)
|
|
106
|
+
return alwaysTrue();
|
|
107
|
+
if (leaves.length === 1)
|
|
108
|
+
return { type: 'leaf', leaf: leaves[0] };
|
|
109
|
+
return { type: 'and', children: leaves.map((l) => ({ type: 'leaf', leaf: l })) };
|
|
110
|
+
}
|
|
111
|
+
/** 解析 --filter-json 复杂条件 */
|
|
112
|
+
function parseFilterJson(jsonStr) {
|
|
113
|
+
let raw;
|
|
114
|
+
try {
|
|
115
|
+
raw = JSON.parse(jsonStr);
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
throw new Error('--filter-json 不是合法 JSON');
|
|
119
|
+
}
|
|
120
|
+
return parseJsonNode(raw);
|
|
121
|
+
}
|
|
122
|
+
function parseJsonNode(node) {
|
|
123
|
+
if (!node || typeof node !== 'object') {
|
|
124
|
+
throw new Error('filter-json 节点必须是对象');
|
|
125
|
+
}
|
|
126
|
+
if (node.and) {
|
|
127
|
+
return { type: 'and', children: (Array.isArray(node.and) ? node.and : [node.and]).map(parseJsonNode) };
|
|
128
|
+
}
|
|
129
|
+
if (node.or) {
|
|
130
|
+
return { type: 'or', children: (Array.isArray(node.or) ? node.or : [node.or]).map(parseJsonNode) };
|
|
131
|
+
}
|
|
132
|
+
if (node.not) {
|
|
133
|
+
return { type: 'not', child: parseJsonNode(node.not) };
|
|
134
|
+
}
|
|
135
|
+
const field = node.field ?? node.f;
|
|
136
|
+
if (!field)
|
|
137
|
+
throw new Error('filter-json 条件缺少 field');
|
|
138
|
+
const opRaw = (node.op ?? '=');
|
|
139
|
+
const value = node.value !== undefined ? node.value : node.v;
|
|
140
|
+
const leaf = {
|
|
141
|
+
field: String(field),
|
|
142
|
+
op: normalizeOp(opRaw),
|
|
143
|
+
value
|
|
144
|
+
};
|
|
145
|
+
return { type: 'leaf', leaf };
|
|
146
|
+
}
|
|
147
|
+
function normalizeOp(op) {
|
|
148
|
+
const map = {
|
|
149
|
+
'=': 'eq', '==': 'eq', eq: 'eq',
|
|
150
|
+
'!=': 'neq', '<>': 'neq', neq: 'neq',
|
|
151
|
+
'>': 'gt', gt: 'gt', '>=': 'gte', gte: 'gte',
|
|
152
|
+
'<': 'lt', lt: 'lt', '<=': 'lte', lte: 'lte',
|
|
153
|
+
'~': 'like', like: 'like', 'contains': 'like',
|
|
154
|
+
'~c': 'likeCase', likecase: 'likeCase',
|
|
155
|
+
'^': 'starts', starts: 'starts', 'starts_with': 'starts',
|
|
156
|
+
'$': 'ends', ends: 'ends', 'ends_with': 'ends',
|
|
157
|
+
in: 'in', 'not_in': 'nin', nin: 'nin',
|
|
158
|
+
between: 'between',
|
|
159
|
+
'is_null': 'isNull', isnull: 'isNull', null: 'isNull', 'is null': 'isNull',
|
|
160
|
+
'not_null': 'notNull', notnull: 'notNull', 'not null': 'notNull'
|
|
161
|
+
};
|
|
162
|
+
const key = String(op).toLowerCase();
|
|
163
|
+
if (map[key])
|
|
164
|
+
return map[key];
|
|
165
|
+
throw new Error(`不支持的操作符: ${op}`);
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=filter-dsl.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filter-dsl.js","sourceRoot":"","sources":["../../../server/core/filter-dsl.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 过滤条件 DSL:把自然语言里的结构化筛选条件解析成条件树,
|
|
3
|
+
* 由 query-builder 编译为带白名单校验与参数绑定的 SQL。
|
|
4
|
+
*
|
|
5
|
+
* 行内语法 --filter "<field><op><value>"
|
|
6
|
+
* = != <> > >= < <=
|
|
7
|
+
* ~ 包含子串(模糊) ^ 前缀 $ 后缀
|
|
8
|
+
* ~c 区分大小写包含
|
|
9
|
+
* in/nin 集合 between 区间 null/notnull 空值
|
|
10
|
+
* 值中逗号用 \, 转义
|
|
11
|
+
*
|
|
12
|
+
* 复杂逻辑 --filter-json '{"or":[...],"and":[...]}'
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface FilterLeaf {
|
|
16
|
+
field: string;
|
|
17
|
+
op:
|
|
18
|
+
| 'eq' | 'neq'
|
|
19
|
+
| 'gt' | 'gte' | 'lt' | 'lte'
|
|
20
|
+
| 'like' | 'likeCase' | 'starts' | 'ends'
|
|
21
|
+
| 'in' | 'nin' | 'between'
|
|
22
|
+
| 'isNull' | 'notNull';
|
|
23
|
+
value?: any;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type FilterNode =
|
|
27
|
+
| { type: 'and'; children: FilterNode[] }
|
|
28
|
+
| { type: 'or'; children: FilterNode[] }
|
|
29
|
+
| { type: 'not'; child: FilterNode }
|
|
30
|
+
| { type: 'leaf'; leaf: FilterLeaf };
|
|
31
|
+
|
|
32
|
+
export function alwaysTrue(): FilterNode {
|
|
33
|
+
return { type: 'leaf', leaf: { field: '', op: 'eq', value: 1 } };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** 单个条件文本解析 */
|
|
37
|
+
export function parseFilterExpr(expr: string): FilterLeaf {
|
|
38
|
+
if (expr == null || typeof expr !== 'string') {
|
|
39
|
+
throw new Error('条件为空');
|
|
40
|
+
}
|
|
41
|
+
const text = expr.trim();
|
|
42
|
+
if (!text) {
|
|
43
|
+
throw new Error('条件为空');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const nullMatch = /^([^\s=<>~^$!]+)\s*(null|notnull|isnull|notnull)$/i.exec(text);
|
|
47
|
+
if (nullMatch) {
|
|
48
|
+
const op = nullMatch[2].toLowerCase();
|
|
49
|
+
return { field: nullMatch[1], op: op === 'isnull' || op === 'null' ? 'isNull' : 'notNull' };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 操作符顺序:多字符在前
|
|
53
|
+
const ops = [
|
|
54
|
+
['!=', 'neq'],
|
|
55
|
+
['<>', 'neq'],
|
|
56
|
+
['>=', 'gte'],
|
|
57
|
+
['<=', 'lte'],
|
|
58
|
+
['~c', 'likeCase'],
|
|
59
|
+
['~', 'like'],
|
|
60
|
+
['like', 'like'],
|
|
61
|
+
['starts', 'starts'],
|
|
62
|
+
['ends', 'ends'],
|
|
63
|
+
['in', 'in'],
|
|
64
|
+
['nin', 'nin'],
|
|
65
|
+
['between', 'between'],
|
|
66
|
+
['>', 'gt'],
|
|
67
|
+
['<', 'lt'],
|
|
68
|
+
['=', 'eq'],
|
|
69
|
+
['^', 'starts'],
|
|
70
|
+
['$', 'ends']
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
for (const [sym, op] of ops) {
|
|
74
|
+
const idx = text.indexOf(sym);
|
|
75
|
+
if (idx > 0) {
|
|
76
|
+
const field = text.slice(0, idx).trim();
|
|
77
|
+
let rawValue = text.slice(idx + sym.length).trim();
|
|
78
|
+
if (!field || rawValue === '') {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (op === 'in' || op === 'nin' || op === 'between') {
|
|
82
|
+
const parts = splitEscaped(rawValue);
|
|
83
|
+
return { field, op: op as any, value: parts };
|
|
84
|
+
}
|
|
85
|
+
return { field, op: op as any, value: unescapeValue(rawValue) };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
throw new Error(`无法解析条件: ${expr}(支持 = != > < >= <= ~ ^ $ in nin between null)`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function splitEscaped(value: string): string[] {
|
|
93
|
+
const parts: string[] = [];
|
|
94
|
+
let current = '';
|
|
95
|
+
let escaped = false;
|
|
96
|
+
for (const ch of value) {
|
|
97
|
+
if (escaped) {
|
|
98
|
+
current += ch;
|
|
99
|
+
escaped = false;
|
|
100
|
+
} else if (ch === '\\') {
|
|
101
|
+
escaped = true;
|
|
102
|
+
} else if (ch === ',') {
|
|
103
|
+
parts.push(current);
|
|
104
|
+
current = '';
|
|
105
|
+
} else {
|
|
106
|
+
current += ch;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
if (escaped) current += '\\';
|
|
110
|
+
parts.push(current);
|
|
111
|
+
return parts;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function unescapeValue(value: string): string {
|
|
115
|
+
return value.replace(/\\,/g, ',').trim();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** 解析 --filter 重复参数(多个 AND) */
|
|
119
|
+
export function parseFilters(exprs: string[]): FilterNode {
|
|
120
|
+
const leaves = (exprs || []).filter(Boolean).map((e) => parseFilterExpr(e));
|
|
121
|
+
if (leaves.length === 0) return alwaysTrue();
|
|
122
|
+
if (leaves.length === 1) return { type: 'leaf', leaf: leaves[0] };
|
|
123
|
+
return { type: 'and', children: leaves.map((l) => ({ type: 'leaf' as const, leaf: l })) };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** 解析 --filter-json 复杂条件 */
|
|
127
|
+
export function parseFilterJson(jsonStr: string): FilterNode {
|
|
128
|
+
let raw: any;
|
|
129
|
+
try {
|
|
130
|
+
raw = JSON.parse(jsonStr);
|
|
131
|
+
} catch (e) {
|
|
132
|
+
throw new Error('--filter-json 不是合法 JSON');
|
|
133
|
+
}
|
|
134
|
+
return parseJsonNode(raw);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function parseJsonNode(node: any): FilterNode {
|
|
138
|
+
if (!node || typeof node !== 'object') {
|
|
139
|
+
throw new Error('filter-json 节点必须是对象');
|
|
140
|
+
}
|
|
141
|
+
if (node.and) {
|
|
142
|
+
return { type: 'and', children: (Array.isArray(node.and) ? node.and : [node.and]).map(parseJsonNode) };
|
|
143
|
+
}
|
|
144
|
+
if (node.or) {
|
|
145
|
+
return { type: 'or', children: (Array.isArray(node.or) ? node.or : [node.or]).map(parseJsonNode) };
|
|
146
|
+
}
|
|
147
|
+
if (node.not) {
|
|
148
|
+
return { type: 'not', child: parseJsonNode(node.not) };
|
|
149
|
+
}
|
|
150
|
+
const field = node.field ?? node.f;
|
|
151
|
+
if (!field) throw new Error('filter-json 条件缺少 field');
|
|
152
|
+
const opRaw = (node.op ?? '=') as string;
|
|
153
|
+
const value = node.value !== undefined ? node.value : node.v;
|
|
154
|
+
const leaf: FilterLeaf = {
|
|
155
|
+
field: String(field),
|
|
156
|
+
op: normalizeOp(opRaw),
|
|
157
|
+
value
|
|
158
|
+
};
|
|
159
|
+
return { type: 'leaf', leaf };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function normalizeOp(op: string): FilterLeaf['op'] {
|
|
163
|
+
const map: Record<string, FilterLeaf['op']> = {
|
|
164
|
+
'=': 'eq', '==': 'eq', eq: 'eq',
|
|
165
|
+
'!=': 'neq', '<>': 'neq', neq: 'neq',
|
|
166
|
+
'>': 'gt', gt: 'gt', '>=': 'gte', gte: 'gte',
|
|
167
|
+
'<': 'lt', lt: 'lt', '<=': 'lte', lte: 'lte',
|
|
168
|
+
'~': 'like', like: 'like', 'contains': 'like',
|
|
169
|
+
'~c': 'likeCase', likecase: 'likeCase',
|
|
170
|
+
'^': 'starts', starts: 'starts', 'starts_with': 'starts',
|
|
171
|
+
'$': 'ends', ends: 'ends', 'ends_with': 'ends',
|
|
172
|
+
in: 'in', 'not_in': 'nin', nin: 'nin',
|
|
173
|
+
between: 'between',
|
|
174
|
+
'is_null': 'isNull', isnull: 'isNull', null: 'isNull', 'is null': 'isNull',
|
|
175
|
+
'not_null': 'notNull', notnull: 'notNull', 'not null': 'notNull'
|
|
176
|
+
};
|
|
177
|
+
const key = String(op).toLowerCase();
|
|
178
|
+
if (map[key]) return map[key];
|
|
179
|
+
throw new Error(`不支持的操作符: ${op}`);
|
|
180
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export interface WriteOpts {
|
|
2
|
+
dryRun?: boolean;
|
|
3
|
+
confirm?: string | null;
|
|
4
|
+
yes?: boolean;
|
|
5
|
+
force?: boolean;
|
|
6
|
+
write?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function computeToken(op: string, sql: string, params: any[]): string;
|
|
9
|
+
export declare function verifyToken(token: string, op: string, sql: string, params: any[]): boolean;
|
|
10
|
+
/** 令牌是否已使用(一次性,防重放) */
|
|
11
|
+
export declare function isTokenUsed(token: string): boolean;
|
|
12
|
+
/** 标记令牌已使用,保留到时间窗结束以便过期清理 */
|
|
13
|
+
export declare function markTokenUsed(token: string): void;
|
|
14
|
+
/** 检查全局只读模式 */
|
|
15
|
+
export declare function checkReadonly(write: boolean | undefined): void;
|
|
16
|
+
/** 影响行数阈值 */
|
|
17
|
+
export declare function confirmThreshold(): number;
|
|
18
|
+
/** 检查行数是否超阈值(超阈值需 --force) */
|
|
19
|
+
export declare function checkThreshold(rows: number, force: boolean | undefined): void;
|
|
20
|
+
/**
|
|
21
|
+
* 写操作统一入口:
|
|
22
|
+
* - dryRun=true:估算行数,返回 token(不执行)
|
|
23
|
+
* - 有 confirm:校验 token 并放行
|
|
24
|
+
* - 破坏性操作(destructive=true)还需 --yes
|
|
25
|
+
*/
|
|
26
|
+
export declare function gateWrite(opts: {
|
|
27
|
+
op: string;
|
|
28
|
+
sql: string;
|
|
29
|
+
params: any[];
|
|
30
|
+
estimatedRows?: number | null;
|
|
31
|
+
destructive?: boolean;
|
|
32
|
+
write?: boolean;
|
|
33
|
+
dryRun?: boolean;
|
|
34
|
+
confirm?: string | null;
|
|
35
|
+
yes?: boolean;
|
|
36
|
+
force?: boolean;
|
|
37
|
+
}): {
|
|
38
|
+
mode: 'execute' | 'dryrun';
|
|
39
|
+
token?: string;
|
|
40
|
+
};
|
|
41
|
+
/** 审计一个操作 */
|
|
42
|
+
export declare function audit(opts: {
|
|
43
|
+
action: string;
|
|
44
|
+
conn?: string | null;
|
|
45
|
+
database?: string | null;
|
|
46
|
+
table?: string | null;
|
|
47
|
+
kind?: string | null;
|
|
48
|
+
sql?: string | null;
|
|
49
|
+
params?: any;
|
|
50
|
+
rows?: number | null;
|
|
51
|
+
ok: boolean;
|
|
52
|
+
error?: string | null;
|
|
53
|
+
}): void;
|
|
54
|
+
//# sourceMappingURL=guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../../../server/core/guard.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAID,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,MAAM,CAK3E;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,OAAO,CAY1F;AAsCD,uBAAuB;AACvB,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAGlD;AAED,6BAA6B;AAC7B,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAIjD;AAED,eAAe;AACf,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,IAAI,CAO9D;AAED,aAAa;AACb,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED,8BAA8B;AAC9B,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,SAAS,GAAG,IAAI,CAM7E;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE;IACJ,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,GAAG,EAAE,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,GACA;IAAE,IAAI,EAAE,SAAS,GAAG,QAAQ,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAiDhD;AAED,aAAa;AACb,wBAAgB,KAAK,CAAC,IAAI,EAAE;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,CAAC,EAAE,GAAG,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,GAAG,IAAI,CAIP"}
|