@aiao/rxdb-model 0.0.19 → 0.0.28
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/LICENSE +1 -1
- package/README.md +43 -6
- package/dist/cn.d.ts +19 -0
- package/dist/cn.d.ts.map +1 -0
- package/dist/entity-capabilities.d.ts +25 -0
- package/dist/entity-capabilities.d.ts.map +1 -0
- package/dist/entity-detail/detail-tabs.d.ts +7 -1
- package/dist/entity-detail/detail-tabs.d.ts.map +1 -1
- package/dist/entity-detail/index.d.ts +5 -0
- package/dist/entity-detail/index.d.ts.map +1 -1
- package/dist/entity-detail/interfaces.d.ts +25 -1
- package/dist/entity-detail/interfaces.d.ts.map +1 -1
- package/dist/entity-field.utils.d.ts +54 -2
- package/dist/entity-field.utils.d.ts.map +1 -1
- package/dist/entity-form/form-data.d.ts +21 -0
- package/dist/entity-form/form-data.d.ts.map +1 -1
- package/dist/entity-form/form-fields.d.ts +19 -1
- package/dist/entity-form/form-fields.d.ts.map +1 -1
- package/dist/entity-form/form-validation.d.ts +13 -0
- package/dist/entity-form/form-validation.d.ts.map +1 -1
- package/dist/entity-form/index.d.ts +5 -0
- package/dist/entity-form/index.d.ts.map +1 -1
- package/dist/entity-form/interfaces.d.ts +60 -0
- package/dist/entity-form/interfaces.d.ts.map +1 -1
- package/dist/entity-list/manual-order-list.d.ts +92 -0
- package/dist/entity-list/manual-order-list.d.ts.map +1 -0
- package/dist/entity-list/types.d.ts +25 -0
- package/dist/entity-list/types.d.ts.map +1 -0
- package/dist/entity-table/columns/build-editable-columns.d.ts +24 -2
- package/dist/entity-table/columns/build-editable-columns.d.ts.map +1 -1
- package/dist/entity-table/columns/column-utils.d.ts +75 -9
- package/dist/entity-table/columns/column-utils.d.ts.map +1 -1
- package/dist/entity-table/editors/color-editor.d.ts +23 -0
- package/dist/entity-table/editors/color-editor.d.ts.map +1 -0
- package/dist/entity-table/editors/date-editor.d.ts +5 -0
- package/dist/entity-table/editors/date-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/enum-editor.d.ts +10 -0
- package/dist/entity-table/editors/enum-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/global-overlay-editor.d.ts +26 -3
- package/dist/entity-table/editors/global-overlay-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/icon-list-editor.d.ts +12 -0
- package/dist/entity-table/editors/icon-list-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/json-editor.d.ts +5 -0
- package/dist/entity-table/editors/json-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/key-value-editor.d.ts +11 -1
- package/dist/entity-table/editors/key-value-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/lucide-svg.d.ts +3 -2
- package/dist/entity-table/editors/lucide-svg.d.ts.map +1 -1
- package/dist/entity-table/editors/multiselect-editor.d.ts +24 -6
- package/dist/entity-table/editors/multiselect-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/number-editor.d.ts +32 -2
- package/dist/entity-table/editors/number-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/relation-editor.d.ts +12 -0
- package/dist/entity-table/editors/relation-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/safe-list-editor.d.ts +1 -0
- package/dist/entity-table/editors/safe-list-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/tags-editor.d.ts +11 -0
- package/dist/entity-table/editors/tags-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/text-format-editor.d.ts +32 -0
- package/dist/entity-table/editors/text-format-editor.d.ts.map +1 -0
- package/dist/entity-table/editors/uuid-editor.d.ts +13 -0
- package/dist/entity-table/editors/uuid-editor.d.ts.map +1 -1
- package/dist/entity-table/index.d.ts +13 -3
- package/dist/entity-table/index.d.ts.map +1 -1
- package/dist/entity-table/interfaces.d.ts +38 -0
- package/dist/entity-table/interfaces.d.ts.map +1 -1
- package/dist/entity-table/vtable/table-clipboard.d.ts +30 -1
- package/dist/entity-table/vtable/table-clipboard.d.ts.map +1 -1
- package/dist/entity-table/vtable/table-factory.d.ts +23 -3
- package/dist/entity-table/vtable/table-factory.d.ts.map +1 -1
- package/dist/entity-table/vtable/table-keyboard.d.ts +18 -1
- package/dist/entity-table/vtable/table-keyboard.d.ts.map +1 -1
- package/dist/entity-table/vtable/table-operations.d.ts +42 -1
- package/dist/entity-table/vtable/table-operations.d.ts.map +1 -1
- package/dist/entity-table/vtable/table-row-move.d.ts +88 -0
- package/dist/entity-table/vtable/table-row-move.d.ts.map +1 -0
- package/dist/entity-table/vtable/table-theme.d.ts +47 -2
- package/dist/entity-table/vtable/table-theme.d.ts.map +1 -1
- package/dist/entity-table/vtable/table-tooltip.d.ts +7 -1
- package/dist/entity-table/vtable/table-tooltip.d.ts.map +1 -1
- package/dist/entity-table/vtable/vtable-compat.d.ts +33 -5
- package/dist/entity-table/vtable/vtable-compat.d.ts.map +1 -1
- package/dist/entity-value.utils.d.ts +49 -1
- package/dist/entity-value.utils.d.ts.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4394 -64471
- package/dist/query-builder/models/operator.interface.d.ts +5 -1
- package/dist/query-builder/models/operator.interface.d.ts.map +1 -1
- package/dist/query-builder/models/query-builder-state.d.ts +31 -2
- package/dist/query-builder/models/query-builder-state.d.ts.map +1 -1
- package/dist/query-builder/models/validation.interface.d.ts +1 -1
- package/dist/query-builder/services/operator-registry.service.d.ts +7 -1
- package/dist/query-builder/services/operator-registry.service.d.ts.map +1 -1
- package/dist/query-builder/services/query-builder.service.d.ts +10 -4
- package/dist/query-builder/services/query-builder.service.d.ts.map +1 -1
- package/dist/query-builder/services/query-converter.service.d.ts +5 -1
- package/dist/query-builder/services/query-converter.service.d.ts.map +1 -1
- package/dist/query-builder/services/schema-parser.service.d.ts +5 -1
- package/dist/query-builder/services/schema-parser.service.d.ts.map +1 -1
- package/dist/query-builder/services/validation.service.d.ts +29 -3
- package/dist/query-builder/services/validation.service.d.ts.map +1 -1
- package/dist/query-builder/utils/date.d.ts +12 -0
- package/dist/query-builder/utils/date.d.ts.map +1 -1
- package/dist/query-builder/utils/field-tree.d.ts.map +1 -1
- package/dist/query-builder/utils/id.d.ts +2 -0
- package/dist/query-builder/utils/id.d.ts.map +1 -1
- package/dist/query-builder/utils/metadata-field-extractor.d.ts +13 -2
- package/dist/query-builder/utils/metadata-field-extractor.d.ts.map +1 -1
- package/dist/query-builder/utils/popover-position.d.ts +11 -0
- package/dist/query-builder/utils/popover-position.d.ts.map +1 -1
- package/dist/query-builder/utils/schema-factory.d.ts +1 -1
- package/dist/query-builder/utils/structural-equal.d.ts +4 -0
- package/dist/query-builder/utils/structural-equal.d.ts.map +1 -1
- package/dist/query-builder/utils/value-input.d.ts +20 -0
- package/dist/query-builder/utils/value-input.d.ts.map +1 -1
- package/dist/structural-equal.d.ts +5 -1
- package/dist/structural-equal.d.ts.map +1 -1
- package/package.json +34 -10
- package/src/cn.ts +21 -0
- package/src/entity-capabilities.ts +37 -0
- package/src/entity-detail/detail-tabs.ts +48 -0
- package/src/entity-detail/index.ts +7 -0
- package/src/entity-detail/interfaces.ts +55 -0
- package/src/entity-field.utils.ts +205 -0
- package/src/entity-form/form-data.ts +83 -0
- package/src/entity-form/form-fields.ts +48 -0
- package/src/entity-form/form-validation.ts +34 -0
- package/src/entity-form/index.ts +9 -0
- package/src/entity-form/interfaces.ts +116 -0
- package/src/entity-list/manual-order-list.ts +159 -0
- package/src/entity-list/types.ts +22 -0
- package/src/entity-table/columns/build-editable-columns.ts +184 -0
- package/src/entity-table/columns/column-utils.ts +657 -0
- package/src/entity-table/editors/color-editor.ts +239 -0
- package/src/entity-table/editors/date-editor.ts +169 -0
- package/src/entity-table/editors/enum-editor.ts +167 -0
- package/src/entity-table/editors/global-overlay-editor.ts +277 -0
- package/src/entity-table/editors/icon-list-editor.ts +130 -0
- package/src/entity-table/editors/json-editor.ts +212 -0
- package/src/entity-table/editors/key-value-editor.ts +595 -0
- package/src/entity-table/editors/lucide-svg.ts +78 -0
- package/src/entity-table/editors/multiselect-editor.ts +171 -0
- package/src/entity-table/editors/number-editor.ts +322 -0
- package/src/entity-table/editors/relation-editor.ts +316 -0
- package/src/entity-table/editors/safe-list-editor.ts +17 -0
- package/src/entity-table/editors/tags-editor.ts +239 -0
- package/src/entity-table/editors/text-format-editor.ts +220 -0
- package/src/entity-table/editors/uuid-editor.ts +198 -0
- package/src/entity-table/index.ts +96 -0
- package/src/entity-table/interfaces.ts +100 -0
- package/src/entity-table/vtable/table-clipboard.ts +419 -0
- package/src/entity-table/vtable/table-factory.ts +117 -0
- package/src/entity-table/vtable/table-keyboard.ts +89 -0
- package/src/entity-table/vtable/table-operations.ts +123 -0
- package/src/entity-table/vtable/table-row-move.ts +122 -0
- package/src/entity-table/vtable/table-theme.ts +181 -0
- package/src/entity-table/vtable/table-tooltip.ts +70 -0
- package/src/entity-table/vtable/vtable-compat.ts +84 -0
- package/src/entity-value.utils.ts +456 -0
- package/src/index.ts +58 -0
- package/src/query-builder/models/config.interface.ts +31 -0
- package/src/query-builder/models/index.ts +8 -0
- package/src/query-builder/models/operator.interface.ts +191 -0
- package/src/query-builder/models/query-builder-state.ts +222 -0
- package/src/query-builder/models/validation.interface.ts +184 -0
- package/src/query-builder/services/index.ts +9 -0
- package/src/query-builder/services/operator-registry.service.ts +243 -0
- package/src/query-builder/services/query-builder.service.ts +568 -0
- package/src/query-builder/services/query-converter.service.ts +153 -0
- package/src/query-builder/services/schema-parser.service.ts +312 -0
- package/src/query-builder/services/validation.service.ts +390 -0
- package/src/query-builder/utils/date.ts +35 -0
- package/src/query-builder/utils/field-tree.ts +400 -0
- package/src/query-builder/utils/id.ts +15 -0
- package/src/query-builder/utils/index.ts +12 -0
- package/src/query-builder/utils/metadata-field-extractor.ts +460 -0
- package/src/query-builder/utils/popover-position.ts +63 -0
- package/src/query-builder/utils/schema-factory.ts +75 -0
- package/src/query-builder/utils/structural-equal.ts +5 -0
- package/src/query-builder/utils/value-input.ts +105 -0
- package/src/structural-equal.ts +49 -0
|
@@ -0,0 +1,568 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview QueryBuilder 核心服务
|
|
3
|
+
* 提供 RxJS 响应式状态管理,支持规则增删改和双向转换
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { OperatorName, RuleGroup } from '@aiao/rxdb';
|
|
7
|
+
import { BehaviorSubject, Observable, distinctUntilChanged, map } from 'rxjs';
|
|
8
|
+
import type { QueryBuilderConfig } from '../models/config.interface.js';
|
|
9
|
+
import type {
|
|
10
|
+
FieldMetadata,
|
|
11
|
+
QueryBuilderRule,
|
|
12
|
+
QueryBuilderRuleGroup,
|
|
13
|
+
QueryBuilderState,
|
|
14
|
+
UIRule
|
|
15
|
+
} from '../models/query-builder-state.js';
|
|
16
|
+
import { isRule, isRuleGroup } from '../models/query-builder-state.js';
|
|
17
|
+
import type { ValidationResult } from '../models/validation.interface.js';
|
|
18
|
+
import { generateId } from '../utils/id.js';
|
|
19
|
+
import { structuralEqual } from '../utils/structural-equal.js';
|
|
20
|
+
import { OperatorRegistry, createOperatorRegistry } from './operator-registry.service.js';
|
|
21
|
+
import { QueryConverter, createQueryConverter } from './query-converter.service.js';
|
|
22
|
+
import { ValidationService, createValidationService, type ValidationGroupInput } from './validation.service.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* QueryBuilderService 选项
|
|
26
|
+
*/
|
|
27
|
+
export interface QueryBuilderServiceOptions<T = Record<string, unknown>> {
|
|
28
|
+
/**
|
|
29
|
+
* 初始查询状态
|
|
30
|
+
*/
|
|
31
|
+
initialState?: QueryBuilderState<T>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 初始 RxDB Query
|
|
35
|
+
*/
|
|
36
|
+
initialQuery?: RuleGroup<T>;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 字段元数据
|
|
40
|
+
*/
|
|
41
|
+
fields?: FieldMetadata[];
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 配置选项
|
|
45
|
+
*/
|
|
46
|
+
config?: QueryBuilderConfig<T>;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 自定义操作符注册表
|
|
50
|
+
*/
|
|
51
|
+
operatorRegistry?: OperatorRegistry;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 自定义校验服务
|
|
55
|
+
*/
|
|
56
|
+
validationService?: ValidationService;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 自定义转换器
|
|
60
|
+
*/
|
|
61
|
+
queryConverter?: QueryConverter<T>;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 默认配置
|
|
66
|
+
*/
|
|
67
|
+
const DEFAULT_MAX_NESTING_LEVEL = 5;
|
|
68
|
+
const DEFAULT_COMBINATOR: 'and' | 'or' = 'and';
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 创建空的根规则组(根节点)。
|
|
72
|
+
*
|
|
73
|
+
* 该方法用于初始化或重置查询构建器的根状态:
|
|
74
|
+
* - 生成一个带有唯一 `id` 的根规则组
|
|
75
|
+
* - 使用默认组合器 {@link DEFAULT_COMBINATOR}(通常为 `and`)
|
|
76
|
+
* - 不包含任何子规则(`rules` 为空数组)
|
|
77
|
+
*
|
|
78
|
+
* @typeParam T 文档/记录的数据类型,用于约束字段和规则的值类型。
|
|
79
|
+
* @returns 一个可作为查询构建器根节点使用的空规则组。
|
|
80
|
+
*/
|
|
81
|
+
function createEmptyRootGroup<T>(): QueryBuilderRuleGroup<T> {
|
|
82
|
+
return {
|
|
83
|
+
id: generateId(),
|
|
84
|
+
combinator: DEFAULT_COMBINATOR,
|
|
85
|
+
rules: []
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* QueryBuilder 核心服务
|
|
91
|
+
* 基于 RxJS 的响应式状态管理
|
|
92
|
+
*/
|
|
93
|
+
export class QueryBuilderService<T = Record<string, unknown>> {
|
|
94
|
+
private readonly stateSubject: BehaviorSubject<QueryBuilderState<T>>;
|
|
95
|
+
private readonly fieldsMap: Map<string, FieldMetadata>;
|
|
96
|
+
private readonly config: QueryBuilderConfig<T>;
|
|
97
|
+
private readonly operatorRegistry: OperatorRegistry;
|
|
98
|
+
private readonly validationService: ValidationService;
|
|
99
|
+
private readonly queryConverter: QueryConverter<T>;
|
|
100
|
+
private currentMaxNestingLevel: number;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 状态变更的 Observable 流
|
|
104
|
+
*/
|
|
105
|
+
readonly state$: Observable<QueryBuilderState<T>>;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 根规则组的 Observable
|
|
109
|
+
*/
|
|
110
|
+
readonly rootGroup$: Observable<QueryBuilderRuleGroup<T>>;
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 校验结果的 Observable
|
|
114
|
+
*/
|
|
115
|
+
readonly validation$: Observable<ValidationResult>;
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* RxDB Query 格式的 Observable
|
|
119
|
+
*/
|
|
120
|
+
readonly query$: Observable<RuleGroup<T>>;
|
|
121
|
+
|
|
122
|
+
/** @param options - 查询构建器配置(schema / initialQuery / 嵌套上限等) */
|
|
123
|
+
constructor(options: QueryBuilderServiceOptions<T> = {}) {
|
|
124
|
+
// 初始化依赖
|
|
125
|
+
this.operatorRegistry = options.operatorRegistry ?? createOperatorRegistry();
|
|
126
|
+
this.validationService =
|
|
127
|
+
options.validationService ??
|
|
128
|
+
createValidationService({
|
|
129
|
+
operatorRegistry: this.operatorRegistry
|
|
130
|
+
});
|
|
131
|
+
this.queryConverter = options.queryConverter ?? createQueryConverter<T>();
|
|
132
|
+
|
|
133
|
+
// 初始化字段映射
|
|
134
|
+
this.fieldsMap = new Map();
|
|
135
|
+
if (options.fields) {
|
|
136
|
+
for (const field of options.fields) {
|
|
137
|
+
this.fieldsMap.set(field.name, field);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// 保存配置
|
|
142
|
+
this.config = options.config ?? {};
|
|
143
|
+
this.currentMaxNestingLevel = this.config.maxNestingLevel ?? DEFAULT_MAX_NESTING_LEVEL;
|
|
144
|
+
|
|
145
|
+
// 初始化状态
|
|
146
|
+
let initialState: QueryBuilderState<T>;
|
|
147
|
+
if (options.initialState) {
|
|
148
|
+
initialState = options.initialState;
|
|
149
|
+
} else if (options.initialQuery) {
|
|
150
|
+
initialState = this.queryConverter.fromRxDBQuery(options.initialQuery);
|
|
151
|
+
} else if (options.config?.initialQuery) {
|
|
152
|
+
initialState = { rootGroup: options.config.initialQuery };
|
|
153
|
+
} else {
|
|
154
|
+
initialState = { rootGroup: createEmptyRootGroup<T>() };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
this.stateSubject = new BehaviorSubject(initialState);
|
|
158
|
+
|
|
159
|
+
// 设置 Observable 流
|
|
160
|
+
this.state$ = this.stateSubject.asObservable();
|
|
161
|
+
|
|
162
|
+
this.rootGroup$ = this.state$.pipe(
|
|
163
|
+
map(state => state.rootGroup),
|
|
164
|
+
distinctUntilChanged()
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
this.validation$ = this.state$.pipe(
|
|
168
|
+
map(state =>
|
|
169
|
+
this.validationService.validateGroup(state.rootGroup as unknown as ValidationGroupInput, this.fieldsMap)
|
|
170
|
+
),
|
|
171
|
+
distinctUntilChanged((a, b) => a.valid === b.valid && structuralEqual(a.errors, b.errors))
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
this.query$ = this.state$.pipe(
|
|
175
|
+
map(state => this.queryConverter.toRxDBQuery(state)),
|
|
176
|
+
distinctUntilChanged((a, b) => structuralEqual(a, b))
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* 获取当前状态快照
|
|
182
|
+
*/
|
|
183
|
+
getState(): QueryBuilderState<T> {
|
|
184
|
+
return this.stateSubject.getValue();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* 获取当前配置
|
|
189
|
+
*/
|
|
190
|
+
getConfig(): QueryBuilderConfig<T> {
|
|
191
|
+
return this.config;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* 获取最大嵌套层级
|
|
196
|
+
*/
|
|
197
|
+
getMaxNestingLevel(): number {
|
|
198
|
+
return this.currentMaxNestingLevel;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* 动态更新最大嵌套层级
|
|
203
|
+
*/
|
|
204
|
+
setMaxNestingLevel(level: number): void {
|
|
205
|
+
this.currentMaxNestingLevel = level;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* 获取字段列表
|
|
210
|
+
*/
|
|
211
|
+
getFields(): FieldMetadata[] {
|
|
212
|
+
return Array.from(this.fieldsMap.values());
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* 设置字段列表
|
|
217
|
+
*/
|
|
218
|
+
setFields(fields: FieldMetadata[]): void {
|
|
219
|
+
this.fieldsMap.clear();
|
|
220
|
+
for (const field of fields) {
|
|
221
|
+
this.fieldsMap.set(field.name, field);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* 获取操作符注册表
|
|
227
|
+
*/
|
|
228
|
+
getOperatorRegistry(): OperatorRegistry {
|
|
229
|
+
return this.operatorRegistry;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ==================== 规则操作 ====================
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* 添加规则
|
|
236
|
+
*/
|
|
237
|
+
addRule(parentGroupId: string | null = null, rule?: Partial<Omit<UIRule, 'id'>>): string {
|
|
238
|
+
const newRule = {
|
|
239
|
+
id: generateId(),
|
|
240
|
+
field: rule?.field ?? ('' as keyof T & string),
|
|
241
|
+
operator: rule?.operator ?? ('=' as OperatorName),
|
|
242
|
+
...(rule && 'value' in rule ? { value: rule.value } : { value: '' })
|
|
243
|
+
} as QueryBuilderRule<T>;
|
|
244
|
+
|
|
245
|
+
const state = this.getState();
|
|
246
|
+
const targetId = parentGroupId ?? state.rootGroup.id;
|
|
247
|
+
const targetDepth = this.getGroupDepth(state.rootGroup, targetId);
|
|
248
|
+
if (targetDepth === -1) {
|
|
249
|
+
throw new Error(`目标分组不存在: ${targetId}`);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const newRootGroup = this.addItemToGroup(state.rootGroup, targetId, newRule);
|
|
253
|
+
|
|
254
|
+
this.stateSubject.next({ rootGroup: newRootGroup });
|
|
255
|
+
return newRule.id;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* 添加规则组
|
|
260
|
+
*/
|
|
261
|
+
addGroup(parentGroupId: string | null = null, combinator: 'and' | 'or' = DEFAULT_COMBINATOR): string {
|
|
262
|
+
const state = this.getState();
|
|
263
|
+
const targetId = parentGroupId ?? state.rootGroup.id;
|
|
264
|
+
|
|
265
|
+
// 检查嵌套深度
|
|
266
|
+
const currentDepth = this.getGroupDepth(state.rootGroup, targetId);
|
|
267
|
+
if (currentDepth === -1) {
|
|
268
|
+
throw new Error(`目标分组不存在: ${targetId}`);
|
|
269
|
+
}
|
|
270
|
+
const maxDepth = this.getMaxNestingLevel();
|
|
271
|
+
if (currentDepth >= maxDepth) {
|
|
272
|
+
throw new Error(`已达到最大嵌套深度 ${maxDepth}`);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const newGroup: QueryBuilderRuleGroup<T> = {
|
|
276
|
+
id: generateId(),
|
|
277
|
+
combinator,
|
|
278
|
+
rules: []
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const newRootGroup = this.addItemToGroup(state.rootGroup, targetId, newGroup);
|
|
282
|
+
this.stateSubject.next({ rootGroup: newRootGroup });
|
|
283
|
+
return newGroup.id;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* 更新规则
|
|
288
|
+
*/
|
|
289
|
+
updateRule(ruleId: string, updates: Partial<Omit<UIRule, 'id'>>): void {
|
|
290
|
+
const state = this.getState();
|
|
291
|
+
const newRootGroup = this.updateItemInGroup(
|
|
292
|
+
state.rootGroup,
|
|
293
|
+
ruleId,
|
|
294
|
+
updates as Partial<QueryBuilderRule<T> | QueryBuilderRuleGroup<T>>
|
|
295
|
+
);
|
|
296
|
+
this.stateSubject.next({ rootGroup: newRootGroup });
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* 更新组的组合器
|
|
301
|
+
*/
|
|
302
|
+
updateGroupCombinator(groupId: string, combinator: 'and' | 'or'): void {
|
|
303
|
+
const state = this.getState();
|
|
304
|
+
const newRootGroup = this.updateItemInGroup(state.rootGroup, groupId, { combinator });
|
|
305
|
+
this.stateSubject.next({ rootGroup: newRootGroup });
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* 删除规则或组
|
|
310
|
+
*/
|
|
311
|
+
remove(itemId: string): void {
|
|
312
|
+
const state = this.getState();
|
|
313
|
+
|
|
314
|
+
// 不允许删除根组
|
|
315
|
+
if (itemId === state.rootGroup.id) {
|
|
316
|
+
throw new Error('不能删除根组');
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const newRootGroup = this.removeItemFromGroup(state.rootGroup, itemId);
|
|
320
|
+
this.stateSubject.next({ rootGroup: newRootGroup });
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* 移动规则或组到目标分组的指定位置
|
|
325
|
+
*/
|
|
326
|
+
moveItem(itemId: string, targetGroupId: string, targetIndex: number): void {
|
|
327
|
+
const state = this.getState();
|
|
328
|
+
|
|
329
|
+
// 不允许移动根组
|
|
330
|
+
if (itemId === state.rootGroup.id) {
|
|
331
|
+
throw new Error('不能移动根组');
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// 查找要移动的项
|
|
335
|
+
const item = this.findItem(state.rootGroup, itemId);
|
|
336
|
+
if (!item) {
|
|
337
|
+
throw new Error(`项目不存在: ${itemId}`);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (!this.findGroup(state.rootGroup, targetGroupId)) {
|
|
341
|
+
throw new Error(`目标分组不存在: ${targetGroupId}`);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// 如果是规则组,不能移动到自身内部
|
|
345
|
+
if (isRuleGroup<T>(item) && this.isDescendant(item, targetGroupId)) {
|
|
346
|
+
throw new Error('不能将分组移动到其自身内部');
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (isRuleGroup<T>(item)) {
|
|
350
|
+
const targetDepth = this.getGroupDepth(state.rootGroup, targetGroupId);
|
|
351
|
+
const subtreeHeight = this.getGroupHeight(item);
|
|
352
|
+
if (targetDepth + subtreeHeight > this.getMaxNestingLevel()) {
|
|
353
|
+
throw new Error(`移动后超过最大嵌套深度 ${this.getMaxNestingLevel()}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// 先移除,再插入
|
|
358
|
+
const afterRemove = this.removeItemFromGroup(state.rootGroup, itemId);
|
|
359
|
+
const newRootGroup = this.insertItemInGroup(afterRemove, targetGroupId, item, targetIndex);
|
|
360
|
+
this.stateSubject.next({ rootGroup: newRootGroup });
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* 清空所有规则
|
|
365
|
+
*/
|
|
366
|
+
clear(): void {
|
|
367
|
+
this.stateSubject.next({ rootGroup: createEmptyRootGroup<T>() });
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* 销毁服务
|
|
372
|
+
*/
|
|
373
|
+
destroy(): void {
|
|
374
|
+
this.stateSubject.complete();
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
// ==================== 转换方法 ====================
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* 获取 RxDB Query 格式
|
|
381
|
+
*/
|
|
382
|
+
toRxDBQuery(): RuleGroup<T> {
|
|
383
|
+
return this.queryConverter.toRxDBQuery(this.getState());
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* 从 RxDB Query 加载
|
|
388
|
+
*
|
|
389
|
+
* 同时接受 RuleGroup(无 id)和 QueryBuilderRuleGroup(有 id)。
|
|
390
|
+
* 内部会重新分配 id,原始 id 会被忽略。
|
|
391
|
+
*/
|
|
392
|
+
fromRxDBQuery(query: RuleGroup<T> | QueryBuilderRuleGroup<T>): void {
|
|
393
|
+
const state = this.queryConverter.fromRxDBQuery(query as RuleGroup<T>);
|
|
394
|
+
this.stateSubject.next(state);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// ==================== 校验方法 ====================
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* 执行校验并返回结果
|
|
401
|
+
*/
|
|
402
|
+
validate(): ValidationResult {
|
|
403
|
+
return this.validationService.validateGroup(
|
|
404
|
+
this.getState().rootGroup as unknown as ValidationGroupInput,
|
|
405
|
+
this.fieldsMap
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* 检查当前状态是否有效
|
|
411
|
+
*/
|
|
412
|
+
isValid(): boolean {
|
|
413
|
+
return this.validate().valid;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ==================== 私有辅助方法 ====================
|
|
417
|
+
|
|
418
|
+
private addItemToGroup(
|
|
419
|
+
group: QueryBuilderRuleGroup<T>,
|
|
420
|
+
targetGroupId: string,
|
|
421
|
+
item: QueryBuilderRule<T> | QueryBuilderRuleGroup<T>
|
|
422
|
+
): QueryBuilderRuleGroup<T> {
|
|
423
|
+
if (group.id === targetGroupId) {
|
|
424
|
+
return {
|
|
425
|
+
...group,
|
|
426
|
+
rules: [...group.rules, item]
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return {
|
|
431
|
+
...group,
|
|
432
|
+
rules: group.rules.map(child => {
|
|
433
|
+
if (isRuleGroup<T>(child)) {
|
|
434
|
+
return this.addItemToGroup(child, targetGroupId, item);
|
|
435
|
+
}
|
|
436
|
+
return child;
|
|
437
|
+
})
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
private updateItemInGroup(
|
|
442
|
+
group: QueryBuilderRuleGroup<T>,
|
|
443
|
+
itemId: string,
|
|
444
|
+
updates: Partial<QueryBuilderRule<T> | QueryBuilderRuleGroup<T>>
|
|
445
|
+
): QueryBuilderRuleGroup<T> {
|
|
446
|
+
if (group.id === itemId) {
|
|
447
|
+
return { ...group, ...updates } as QueryBuilderRuleGroup<T>;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
return {
|
|
451
|
+
...group,
|
|
452
|
+
rules: group.rules.map(child => {
|
|
453
|
+
if (isRuleGroup<T>(child)) {
|
|
454
|
+
if (child.id === itemId) {
|
|
455
|
+
return { ...child, ...updates } as QueryBuilderRuleGroup<T>;
|
|
456
|
+
}
|
|
457
|
+
return this.updateItemInGroup(child, itemId, updates);
|
|
458
|
+
} else if (isRule<T>(child) && child.id === itemId) {
|
|
459
|
+
return { ...child, ...updates } as QueryBuilderRule<T>;
|
|
460
|
+
}
|
|
461
|
+
return child;
|
|
462
|
+
})
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
private removeItemFromGroup(group: QueryBuilderRuleGroup<T>, itemId: string): QueryBuilderRuleGroup<T> {
|
|
467
|
+
const rules: Array<QueryBuilderRule<T> | QueryBuilderRuleGroup<T>> = [];
|
|
468
|
+
for (const child of group.rules) {
|
|
469
|
+
if (child.id === itemId) continue;
|
|
470
|
+
rules.push(isRuleGroup<T>(child) ? this.removeItemFromGroup(child, itemId) : child);
|
|
471
|
+
}
|
|
472
|
+
return { ...group, rules };
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
private getGroupDepth(group: QueryBuilderRuleGroup<T>, targetId: string, currentDepth = 1): number {
|
|
476
|
+
if (group.id === targetId) {
|
|
477
|
+
return currentDepth;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
for (const child of group.rules) {
|
|
481
|
+
if (isRuleGroup<T>(child)) {
|
|
482
|
+
const depth = this.getGroupDepth(child, targetId, currentDepth + 1);
|
|
483
|
+
if (depth !== -1) {
|
|
484
|
+
return depth;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
return -1;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
private findItem(
|
|
493
|
+
group: QueryBuilderRuleGroup<T>,
|
|
494
|
+
itemId: string
|
|
495
|
+
): QueryBuilderRule<T> | QueryBuilderRuleGroup<T> | undefined {
|
|
496
|
+
for (const child of group.rules) {
|
|
497
|
+
if (child.id === itemId) return child;
|
|
498
|
+
if (isRuleGroup<T>(child)) {
|
|
499
|
+
const found = this.findItem(child, itemId);
|
|
500
|
+
if (found) return found;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
return undefined;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
private findGroup(group: QueryBuilderRuleGroup<T>, groupId: string): QueryBuilderRuleGroup<T> | undefined {
|
|
507
|
+
if (group.id === groupId) return group;
|
|
508
|
+
for (const child of group.rules) {
|
|
509
|
+
if (isRuleGroup<T>(child)) {
|
|
510
|
+
const found = this.findGroup(child, groupId);
|
|
511
|
+
if (found) return found;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return undefined;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
private getGroupHeight(group: QueryBuilderRuleGroup<T>): number {
|
|
518
|
+
let height = 1;
|
|
519
|
+
for (const child of group.rules) {
|
|
520
|
+
if (isRuleGroup<T>(child)) height = Math.max(height, 1 + this.getGroupHeight(child));
|
|
521
|
+
}
|
|
522
|
+
return height;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
private isDescendant(group: QueryBuilderRuleGroup<T>, targetId: string): boolean {
|
|
526
|
+
if (group.id === targetId) return true;
|
|
527
|
+
for (const child of group.rules) {
|
|
528
|
+
if (isRuleGroup<T>(child) && this.isDescendant(child, targetId)) return true;
|
|
529
|
+
}
|
|
530
|
+
return false;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
private insertItemInGroup(
|
|
534
|
+
group: QueryBuilderRuleGroup<T>,
|
|
535
|
+
targetGroupId: string,
|
|
536
|
+
item: QueryBuilderRule<T> | QueryBuilderRuleGroup<T>,
|
|
537
|
+
index: number
|
|
538
|
+
): QueryBuilderRuleGroup<T> {
|
|
539
|
+
if (group.id === targetGroupId) {
|
|
540
|
+
const rules = [...group.rules];
|
|
541
|
+
const clampedIndex = Math.max(0, Math.min(index, rules.length));
|
|
542
|
+
rules.splice(clampedIndex, 0, item);
|
|
543
|
+
return { ...group, rules };
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
return {
|
|
547
|
+
...group,
|
|
548
|
+
rules: group.rules.map(child => {
|
|
549
|
+
if (isRuleGroup<T>(child)) {
|
|
550
|
+
return this.insertItemInGroup(child, targetGroupId, item, index);
|
|
551
|
+
}
|
|
552
|
+
return child;
|
|
553
|
+
})
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* 创建 QueryBuilderService 实例的工厂函数
|
|
560
|
+
*
|
|
561
|
+
* @param options - 服务选项
|
|
562
|
+
* @returns 新的 QueryBuilderService 实例
|
|
563
|
+
*/
|
|
564
|
+
export function createQueryBuilderService<T = Record<string, unknown>>(
|
|
565
|
+
options?: QueryBuilderServiceOptions<T>
|
|
566
|
+
): QueryBuilderService<T> {
|
|
567
|
+
return new QueryBuilderService<T>(options);
|
|
568
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Query 转换服务
|
|
3
|
+
* 在 QueryBuilder 状态和 RxDB Query 之间进行双向转换
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Rule, RuleGroup } from '@aiao/rxdb';
|
|
7
|
+
import type { QueryBuilderRule, QueryBuilderRuleGroup, QueryBuilderState } from '../models/query-builder-state.js';
|
|
8
|
+
import { isRuleGroup } from '../models/query-builder-state.js';
|
|
9
|
+
import { generateId } from '../utils/id.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 转换选项
|
|
13
|
+
*/
|
|
14
|
+
export interface QueryConverterOptions {
|
|
15
|
+
/**
|
|
16
|
+
* ID 生成器
|
|
17
|
+
* @default generateId()
|
|
18
|
+
*/
|
|
19
|
+
idGenerator?: () => string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Query 转换服务
|
|
24
|
+
* 负责 QueryBuilder 状态与 RxDB Query 之间的双向转换
|
|
25
|
+
*/
|
|
26
|
+
export class QueryConverter<T = Record<string, unknown>> {
|
|
27
|
+
private readonly idGenerator: () => string;
|
|
28
|
+
|
|
29
|
+
/** 不需要 value 字段的操作符 */
|
|
30
|
+
private readonly noValueOperators = new Set(['null', 'notNull', 'exists', 'notExists']);
|
|
31
|
+
|
|
32
|
+
/** @param options - 转换器配置 */
|
|
33
|
+
constructor(options: QueryConverterOptions = {}) {
|
|
34
|
+
this.idGenerator = options.idGenerator ?? generateId;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 将 QueryBuilder 状态转换为 RxDB Query
|
|
39
|
+
*
|
|
40
|
+
* @param state - QueryBuilder 状态
|
|
41
|
+
* @returns RxDB RuleGroup 查询对象
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```typescript
|
|
45
|
+
* const converter = new QueryConverter<User>();
|
|
46
|
+
* const query = converter.toRxDBQuery(queryBuilderState);
|
|
47
|
+
* // { combinator: 'and', rules: [...] }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
toRxDBQuery(state: QueryBuilderState<T>): RuleGroup<T> {
|
|
51
|
+
return this.convertGroupToRxDB(state.rootGroup);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 将 RxDB Query 转换为 QueryBuilder 状态
|
|
56
|
+
*
|
|
57
|
+
* @param query - RxDB RuleGroup 查询对象
|
|
58
|
+
* @returns QueryBuilder 状态
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* const converter = new QueryConverter<User>();
|
|
63
|
+
* const state = converter.fromRxDBQuery(rxdbQuery);
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
fromRxDBQuery(query: RuleGroup<T>): QueryBuilderState<T> {
|
|
67
|
+
if (!query) {
|
|
68
|
+
return { rootGroup: { id: this.idGenerator(), combinator: 'and', rules: [] } };
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
rootGroup: this.convertGroupFromRxDB(query)
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* 转换单个规则组到 RxDB 格式
|
|
77
|
+
*/
|
|
78
|
+
private convertGroupToRxDB(group: QueryBuilderRuleGroup<T>): RuleGroup<T> {
|
|
79
|
+
const rules: Array<RuleGroup<T> | Rule<T>> = [];
|
|
80
|
+
|
|
81
|
+
for (const item of group.rules) {
|
|
82
|
+
if (isRuleGroup<T>(item)) {
|
|
83
|
+
rules.push(this.convertGroupToRxDB(item));
|
|
84
|
+
} else {
|
|
85
|
+
rules.push(this.convertRuleToRxDB(item));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
combinator: group.combinator,
|
|
91
|
+
rules
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 转换单条规则到 RxDB 格式
|
|
97
|
+
* 移除 QueryBuilder 特有的 id 字段,以及无值操作符的 value 字段
|
|
98
|
+
*/
|
|
99
|
+
private convertRuleToRxDB(rule: QueryBuilderRule<T>): Rule<T> {
|
|
100
|
+
const source = rule as unknown as Record<string, unknown>;
|
|
101
|
+
const operator = source['operator'] as string;
|
|
102
|
+
const omitValue = this.noValueOperators.has(operator);
|
|
103
|
+
const result: Record<string, unknown> = {};
|
|
104
|
+
for (const key in source) {
|
|
105
|
+
if (key === 'id') continue;
|
|
106
|
+
if (omitValue && key === 'value') continue;
|
|
107
|
+
result[key] = source[key];
|
|
108
|
+
}
|
|
109
|
+
return result as unknown as Rule<T>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 从 RxDB 格式转换规则组
|
|
114
|
+
*/
|
|
115
|
+
private convertGroupFromRxDB(group: RuleGroup<T>): QueryBuilderRuleGroup<T> {
|
|
116
|
+
const rules: Array<QueryBuilderRuleGroup<T> | QueryBuilderRule<T>> = [];
|
|
117
|
+
|
|
118
|
+
for (const item of group.rules ?? []) {
|
|
119
|
+
if ('combinator' in item && 'rules' in item) {
|
|
120
|
+
rules.push(this.convertGroupFromRxDB(item as RuleGroup<T>));
|
|
121
|
+
} else {
|
|
122
|
+
rules.push(this.convertRuleFromRxDB(item as Rule<T>));
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
id: this.idGenerator(),
|
|
128
|
+
combinator: group.combinator,
|
|
129
|
+
rules
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 从 RxDB 格式转换单条规则
|
|
135
|
+
* 添加 QueryBuilder 需要的 id 字段
|
|
136
|
+
*/
|
|
137
|
+
private convertRuleFromRxDB(rule: Rule<T>): QueryBuilderRule<T> {
|
|
138
|
+
return {
|
|
139
|
+
id: this.idGenerator(),
|
|
140
|
+
...rule
|
|
141
|
+
} as QueryBuilderRule<T>;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 创建 QueryConverter 实例的工厂函数
|
|
147
|
+
*
|
|
148
|
+
* @param options - 转换选项
|
|
149
|
+
* @returns 新的 QueryConverter 实例
|
|
150
|
+
*/
|
|
151
|
+
export function createQueryConverter<T = Record<string, unknown>>(options?: QueryConverterOptions): QueryConverter<T> {
|
|
152
|
+
return new QueryConverter<T>(options);
|
|
153
|
+
}
|