@aiao/rxdb-model 0.0.19 → 0.0.26

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.
Files changed (181) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +43 -6
  3. package/dist/cn.d.ts +19 -0
  4. package/dist/cn.d.ts.map +1 -0
  5. package/dist/entity-capabilities.d.ts +31 -0
  6. package/dist/entity-capabilities.d.ts.map +1 -0
  7. package/dist/entity-detail/detail-tabs.d.ts +13 -2
  8. package/dist/entity-detail/detail-tabs.d.ts.map +1 -1
  9. package/dist/entity-detail/index.d.ts +5 -0
  10. package/dist/entity-detail/index.d.ts.map +1 -1
  11. package/dist/entity-detail/interfaces.d.ts +29 -1
  12. package/dist/entity-detail/interfaces.d.ts.map +1 -1
  13. package/dist/entity-field.utils.d.ts +60 -2
  14. package/dist/entity-field.utils.d.ts.map +1 -1
  15. package/dist/entity-form/form-data.d.ts +22 -1
  16. package/dist/entity-form/form-data.d.ts.map +1 -1
  17. package/dist/entity-form/form-fields.d.ts +25 -2
  18. package/dist/entity-form/form-fields.d.ts.map +1 -1
  19. package/dist/entity-form/form-validation.d.ts +20 -2
  20. package/dist/entity-form/form-validation.d.ts.map +1 -1
  21. package/dist/entity-form/index.d.ts +5 -0
  22. package/dist/entity-form/index.d.ts.map +1 -1
  23. package/dist/entity-form/interfaces.d.ts +65 -1
  24. package/dist/entity-form/interfaces.d.ts.map +1 -1
  25. package/dist/entity-list/manual-order-list.d.ts +99 -0
  26. package/dist/entity-list/manual-order-list.d.ts.map +1 -0
  27. package/dist/entity-table/columns/build-editable-columns.d.ts +26 -4
  28. package/dist/entity-table/columns/build-editable-columns.d.ts.map +1 -1
  29. package/dist/entity-table/columns/column-utils.d.ts +78 -12
  30. package/dist/entity-table/columns/column-utils.d.ts.map +1 -1
  31. package/dist/entity-table/editors/color-editor.d.ts +23 -0
  32. package/dist/entity-table/editors/color-editor.d.ts.map +1 -0
  33. package/dist/entity-table/editors/date-editor.d.ts +6 -1
  34. package/dist/entity-table/editors/date-editor.d.ts.map +1 -1
  35. package/dist/entity-table/editors/enum-editor.d.ts +11 -1
  36. package/dist/entity-table/editors/enum-editor.d.ts.map +1 -1
  37. package/dist/entity-table/editors/global-overlay-editor.d.ts +27 -4
  38. package/dist/entity-table/editors/global-overlay-editor.d.ts.map +1 -1
  39. package/dist/entity-table/editors/icon-list-editor.d.ts +13 -1
  40. package/dist/entity-table/editors/icon-list-editor.d.ts.map +1 -1
  41. package/dist/entity-table/editors/json-editor.d.ts +6 -1
  42. package/dist/entity-table/editors/json-editor.d.ts.map +1 -1
  43. package/dist/entity-table/editors/key-value-editor.d.ts +12 -2
  44. package/dist/entity-table/editors/key-value-editor.d.ts.map +1 -1
  45. package/dist/entity-table/editors/lucide-svg.d.ts +3 -2
  46. package/dist/entity-table/editors/lucide-svg.d.ts.map +1 -1
  47. package/dist/entity-table/editors/multiselect-editor.d.ts +24 -6
  48. package/dist/entity-table/editors/multiselect-editor.d.ts.map +1 -1
  49. package/dist/entity-table/editors/number-editor.d.ts +33 -3
  50. package/dist/entity-table/editors/number-editor.d.ts.map +1 -1
  51. package/dist/entity-table/editors/relation-editor.d.ts +14 -2
  52. package/dist/entity-table/editors/relation-editor.d.ts.map +1 -1
  53. package/dist/entity-table/editors/safe-list-editor.d.ts +1 -0
  54. package/dist/entity-table/editors/safe-list-editor.d.ts.map +1 -1
  55. package/dist/entity-table/editors/tags-editor.d.ts +12 -1
  56. package/dist/entity-table/editors/tags-editor.d.ts.map +1 -1
  57. package/dist/entity-table/editors/text-format-editor.d.ts +32 -0
  58. package/dist/entity-table/editors/text-format-editor.d.ts.map +1 -0
  59. package/dist/entity-table/editors/uuid-editor.d.ts +14 -1
  60. package/dist/entity-table/editors/uuid-editor.d.ts.map +1 -1
  61. package/dist/entity-table/index.d.ts +13 -3
  62. package/dist/entity-table/index.d.ts.map +1 -1
  63. package/dist/entity-table/interfaces.d.ts +13 -0
  64. package/dist/entity-table/interfaces.d.ts.map +1 -1
  65. package/dist/entity-table/vtable/table-clipboard.d.ts +31 -2
  66. package/dist/entity-table/vtable/table-clipboard.d.ts.map +1 -1
  67. package/dist/entity-table/vtable/table-factory.d.ts +25 -5
  68. package/dist/entity-table/vtable/table-factory.d.ts.map +1 -1
  69. package/dist/entity-table/vtable/table-keyboard.d.ts +18 -1
  70. package/dist/entity-table/vtable/table-keyboard.d.ts.map +1 -1
  71. package/dist/entity-table/vtable/table-operations.d.ts +44 -3
  72. package/dist/entity-table/vtable/table-operations.d.ts.map +1 -1
  73. package/dist/entity-table/vtable/table-row-move.d.ts +88 -0
  74. package/dist/entity-table/vtable/table-row-move.d.ts.map +1 -0
  75. package/dist/entity-table/vtable/table-theme.d.ts +47 -2
  76. package/dist/entity-table/vtable/table-theme.d.ts.map +1 -1
  77. package/dist/entity-table/vtable/table-tooltip.d.ts +7 -1
  78. package/dist/entity-table/vtable/table-tooltip.d.ts.map +1 -1
  79. package/dist/entity-table/vtable/vtable-compat.d.ts +33 -5
  80. package/dist/entity-table/vtable/vtable-compat.d.ts.map +1 -1
  81. package/dist/entity-value.utils.d.ts +50 -2
  82. package/dist/entity-value.utils.d.ts.map +1 -1
  83. package/dist/index.d.ts +9 -0
  84. package/dist/index.d.ts.map +1 -1
  85. package/dist/index.js +4392 -64471
  86. package/dist/query-builder/models/config.interface.d.ts +2 -2
  87. package/dist/query-builder/models/operator.interface.d.ts +6 -2
  88. package/dist/query-builder/models/operator.interface.d.ts.map +1 -1
  89. package/dist/query-builder/models/query-builder-state.d.ts +31 -2
  90. package/dist/query-builder/models/query-builder-state.d.ts.map +1 -1
  91. package/dist/query-builder/models/validation.interface.d.ts +2 -2
  92. package/dist/query-builder/services/operator-registry.service.d.ts +13 -3
  93. package/dist/query-builder/services/operator-registry.service.d.ts.map +1 -1
  94. package/dist/query-builder/services/query-builder.service.d.ts +16 -6
  95. package/dist/query-builder/services/query-builder.service.d.ts.map +1 -1
  96. package/dist/query-builder/services/query-converter.service.d.ts +10 -2
  97. package/dist/query-builder/services/query-converter.service.d.ts.map +1 -1
  98. package/dist/query-builder/services/schema-parser.service.d.ts +10 -2
  99. package/dist/query-builder/services/schema-parser.service.d.ts.map +1 -1
  100. package/dist/query-builder/services/validation.service.d.ts +34 -4
  101. package/dist/query-builder/services/validation.service.d.ts.map +1 -1
  102. package/dist/query-builder/utils/date.d.ts +12 -0
  103. package/dist/query-builder/utils/date.d.ts.map +1 -1
  104. package/dist/query-builder/utils/field-tree.d.ts +7 -1
  105. package/dist/query-builder/utils/field-tree.d.ts.map +1 -1
  106. package/dist/query-builder/utils/id.d.ts +2 -0
  107. package/dist/query-builder/utils/id.d.ts.map +1 -1
  108. package/dist/query-builder/utils/metadata-field-extractor.d.ts +15 -3
  109. package/dist/query-builder/utils/metadata-field-extractor.d.ts.map +1 -1
  110. package/dist/query-builder/utils/popover-position.d.ts +11 -0
  111. package/dist/query-builder/utils/popover-position.d.ts.map +1 -1
  112. package/dist/query-builder/utils/schema-factory.d.ts +7 -3
  113. package/dist/query-builder/utils/structural-equal.d.ts +4 -0
  114. package/dist/query-builder/utils/structural-equal.d.ts.map +1 -1
  115. package/dist/query-builder/utils/value-input.d.ts +21 -1
  116. package/dist/query-builder/utils/value-input.d.ts.map +1 -1
  117. package/dist/structural-equal.d.ts +5 -1
  118. package/dist/structural-equal.d.ts.map +1 -1
  119. package/package.json +34 -10
  120. package/src/cn.ts +21 -0
  121. package/src/entity-capabilities.ts +37 -0
  122. package/src/entity-detail/detail-tabs.ts +48 -0
  123. package/src/entity-detail/index.ts +7 -0
  124. package/src/entity-detail/interfaces.ts +55 -0
  125. package/src/entity-field.utils.ts +205 -0
  126. package/src/entity-form/form-data.ts +83 -0
  127. package/src/entity-form/form-fields.ts +48 -0
  128. package/src/entity-form/form-validation.ts +34 -0
  129. package/src/entity-form/index.ts +9 -0
  130. package/src/entity-form/interfaces.ts +116 -0
  131. package/src/entity-list/manual-order-list.ts +159 -0
  132. package/src/entity-table/columns/build-editable-columns.ts +184 -0
  133. package/src/entity-table/columns/column-utils.ts +657 -0
  134. package/src/entity-table/editors/color-editor.ts +239 -0
  135. package/src/entity-table/editors/date-editor.ts +169 -0
  136. package/src/entity-table/editors/enum-editor.ts +167 -0
  137. package/src/entity-table/editors/global-overlay-editor.ts +277 -0
  138. package/src/entity-table/editors/icon-list-editor.ts +130 -0
  139. package/src/entity-table/editors/json-editor.ts +212 -0
  140. package/src/entity-table/editors/key-value-editor.ts +595 -0
  141. package/src/entity-table/editors/lucide-svg.ts +78 -0
  142. package/src/entity-table/editors/multiselect-editor.ts +171 -0
  143. package/src/entity-table/editors/number-editor.ts +322 -0
  144. package/src/entity-table/editors/relation-editor.ts +316 -0
  145. package/src/entity-table/editors/safe-list-editor.ts +17 -0
  146. package/src/entity-table/editors/tags-editor.ts +239 -0
  147. package/src/entity-table/editors/text-format-editor.ts +220 -0
  148. package/src/entity-table/editors/uuid-editor.ts +198 -0
  149. package/src/entity-table/index.ts +96 -0
  150. package/src/entity-table/interfaces.ts +72 -0
  151. package/src/entity-table/vtable/table-clipboard.ts +419 -0
  152. package/src/entity-table/vtable/table-factory.ts +117 -0
  153. package/src/entity-table/vtable/table-keyboard.ts +89 -0
  154. package/src/entity-table/vtable/table-operations.ts +123 -0
  155. package/src/entity-table/vtable/table-row-move.ts +122 -0
  156. package/src/entity-table/vtable/table-theme.ts +181 -0
  157. package/src/entity-table/vtable/table-tooltip.ts +70 -0
  158. package/src/entity-table/vtable/vtable-compat.ts +84 -0
  159. package/src/entity-value.utils.ts +456 -0
  160. package/src/index.ts +57 -0
  161. package/src/query-builder/models/config.interface.ts +31 -0
  162. package/src/query-builder/models/index.ts +8 -0
  163. package/src/query-builder/models/operator.interface.ts +191 -0
  164. package/src/query-builder/models/query-builder-state.ts +222 -0
  165. package/src/query-builder/models/validation.interface.ts +184 -0
  166. package/src/query-builder/services/index.ts +9 -0
  167. package/src/query-builder/services/operator-registry.service.ts +243 -0
  168. package/src/query-builder/services/query-builder.service.ts +568 -0
  169. package/src/query-builder/services/query-converter.service.ts +153 -0
  170. package/src/query-builder/services/schema-parser.service.ts +309 -0
  171. package/src/query-builder/services/validation.service.ts +390 -0
  172. package/src/query-builder/utils/date.ts +35 -0
  173. package/src/query-builder/utils/field-tree.ts +400 -0
  174. package/src/query-builder/utils/id.ts +15 -0
  175. package/src/query-builder/utils/index.ts +12 -0
  176. package/src/query-builder/utils/metadata-field-extractor.ts +460 -0
  177. package/src/query-builder/utils/popover-position.ts +59 -0
  178. package/src/query-builder/utils/schema-factory.ts +75 -0
  179. package/src/query-builder/utils/structural-equal.ts +5 -0
  180. package/src/query-builder/utils/value-input.ts +105 -0
  181. package/src/structural-equal.ts +49 -0
@@ -0,0 +1,460 @@
1
+ import type { EntityMetadata, EntityRelationMetadata, KeyValuePropertyMetadata } from '@aiao/rxdb';
2
+ import { PropertyType, RelationKind } from '@aiao/rxdb';
3
+ import type { FieldMetadata, PropertyType as QBPropertyType } from '../models/query-builder-state.js';
4
+
5
+ /**
6
+ * 模型信息接口
7
+ */
8
+ export interface ModelInfo {
9
+ /** 实体名称 */
10
+ name: string;
11
+ /** 实体显示名称 */
12
+ displayName: string;
13
+
14
+ /** 实体类构造函数 */
15
+ entityClass: new (...args: never[]) => unknown;
16
+ /** RxDB 实体元数据 */
17
+ metadata: EntityMetadata;
18
+ }
19
+
20
+ /**
21
+ * 查询示例接口
22
+ */
23
+ export interface QueryExample {
24
+ /** 示例名称 */
25
+ name: string;
26
+ /** 示例描述 */
27
+ description: string;
28
+ /** 示例对应的模型名称 */
29
+ modelName: string;
30
+ /** 示例查询内容(结构由具体模型决定) */
31
+ query: unknown;
32
+ }
33
+
34
+ /**
35
+ * 字段提取器配置
36
+ */
37
+ export interface FieldExtractorConfig {
38
+ /** 关系查询深度,默认 4 */
39
+ relationQueryDeep?: number;
40
+ /** 系统字段列表,默认 ['id', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'] */
41
+ systemFields?: string[];
42
+ }
43
+
44
+ /**
45
+ * 关系深度配置(默认值)
46
+ */
47
+ const DEFAULT_RELATION_QUERY_DEEP = 4;
48
+
49
+ /**
50
+ * 系统字段列表(默认值)
51
+ */
52
+ const DEFAULT_SYSTEM_FIELDS = ['id', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'];
53
+
54
+ /**
55
+ * 将 PropertyType 映射到 FieldMetadata 的 type
56
+ *
57
+ * @param type - RxDB 属性类型
58
+ * @returns QueryBuilder 属性类型
59
+ *
60
+ * @example
61
+ * ```typescript
62
+ * mapPropertyType(PropertyType.string) // 'string'
63
+ * mapPropertyType(PropertyType.number) // 'number'
64
+ * mapPropertyType(PropertyType.boolean) // 'boolean'
65
+ * ```
66
+ */
67
+ export function mapPropertyType(type: PropertyType): QBPropertyType {
68
+ switch (type) {
69
+ case PropertyType.uuid:
70
+ return 'uuid';
71
+ case PropertyType.string:
72
+ return 'string';
73
+ case PropertyType.enum:
74
+ return 'enum';
75
+ case PropertyType.number:
76
+ case PropertyType.integer:
77
+ return 'number';
78
+ case PropertyType.boolean:
79
+ return 'boolean';
80
+ case PropertyType.date:
81
+ return 'date';
82
+ case PropertyType.stringArray:
83
+ case PropertyType.numberArray:
84
+ return 'array';
85
+ case PropertyType.json:
86
+ return 'object';
87
+ case PropertyType.keyValue:
88
+ return 'keyValue';
89
+ default:
90
+ return 'string';
91
+ }
92
+ }
93
+
94
+ /**
95
+ * 获取关系需要忽略的键(避免循环引用)
96
+ *
97
+ * @param relation - 实体关系元数据
98
+ * @returns 需要忽略的键列表
99
+ *
100
+ * @remarks
101
+ * 对于 ONE_TO_MANY 和 MANY_TO_MANY 关系,返回 mappedProperty(如果存在)
102
+ * 以避免无限递归
103
+ *
104
+ * @example
105
+ * ```typescript
106
+ * // ONE_TO_MANY: User.orders -> Order.owner
107
+ * getRelationIgnoreKeys(ordersRelation) // ['owner']
108
+ * ```
109
+ */
110
+ export function getRelationIgnoreKeys(relation: EntityRelationMetadata): string[] {
111
+ if (relation.kind === RelationKind.ONE_TO_MANY || relation.kind === RelationKind.MANY_TO_MANY) {
112
+ return [relation.mappedProperty];
113
+ }
114
+ return [];
115
+ }
116
+
117
+ /**
118
+ * 从 EntityMetadata 提取字段(支持递归关系)
119
+ *
120
+ * @param metadata - 实体元数据
121
+ * @param allModels - 所有模型的映射表
122
+ * @param config - 字段提取器配置
123
+ * @returns 字段元数据列表
124
+ *
125
+ * @remarks
126
+ * 此函数会递归提取实体的所有字段,包括:
127
+ * 1. 系统字段(id, createdAt, updatedAt 等)
128
+ * 2. 实体属性字段
129
+ * 3. 外键字段
130
+ * 4. 关系字段(递归,直到达到深度限制)
131
+ *
132
+ * @example
133
+ * ```typescript
134
+ * const fields = extractFieldsFromMetadata(
135
+ * userMetadata,
136
+ * modelMap,
137
+ * { relationQueryDeep: 3 }
138
+ * );
139
+ * // 返回: [
140
+ * // { name: 'id', displayName: 'ID', type: 'string' },
141
+ * // { name: 'name', displayName: '姓名', type: 'string' },
142
+ * // { name: 'orders', displayName: '订单 (ONE_TO_MANY)', type: 'relation', ... },
143
+ * // { name: 'orders.id', displayName: '订单.ID', type: 'string' },
144
+ * // ...
145
+ * // ]
146
+ * ```
147
+ */
148
+ export function extractFieldsFromMetadata(
149
+ metadata: EntityMetadata,
150
+ allModels: Map<string, ModelInfo>,
151
+ config: FieldExtractorConfig = {}
152
+ ): FieldMetadata[] {
153
+ return extractFieldsRecursive(metadata, allModels, config, metadata, [], '', new Set(), 0);
154
+ }
155
+
156
+ /**
157
+ * 递归提取字段的内部实现
158
+ */
159
+ function extractFieldsRecursive(
160
+ metadata: EntityMetadata,
161
+ allModels: Map<string, ModelInfo>,
162
+ config: FieldExtractorConfig,
163
+ firstMetadata: EntityMetadata,
164
+ ignoreKeys: string[],
165
+ prefix: string,
166
+ visitedEntities: Set<string>,
167
+ depth: number
168
+ ): FieldMetadata[] {
169
+ const fields: FieldMetadata[] = [];
170
+ const relationQueryDeep = config.relationQueryDeep ?? DEFAULT_RELATION_QUERY_DEEP;
171
+ const systemFields = config.systemFields ?? DEFAULT_SYSTEM_FIELDS;
172
+
173
+ // 防止循环引用
174
+ if (visitedEntities.has(metadata.name)) {
175
+ return fields;
176
+ }
177
+
178
+ // 使用 propertyMap 做 O(1) 名称查找
179
+ const propertyByName = metadata.propertyMap;
180
+
181
+ // 1. 添加系统字段(仅顶层)
182
+ if (!prefix) {
183
+ for (const sysField of systemFields) {
184
+ const sysProp = propertyByName.get(sysField);
185
+ if (sysProp) {
186
+ fields.push({
187
+ name: sysField,
188
+ displayName: sysProp.displayName ?? sysField,
189
+ type: mapPropertyType(sysProp.type as PropertyType),
190
+ nullable: sysProp.nullable ?? false
191
+ });
192
+ }
193
+ }
194
+ }
195
+
196
+ // 2. 添加实体属性字段
197
+ for (const [key, property] of metadata.propertyMap) {
198
+ const fieldName = prefix ? `${prefix}.${key}` : key;
199
+ const field: FieldMetadata = {
200
+ name: fieldName,
201
+ displayName: property.displayName ?? key,
202
+ type: mapPropertyType(property.type as PropertyType),
203
+ nullable: property.nullable ?? false
204
+ };
205
+ if (property.type === PropertyType.enum && Array.isArray(property.enum)) {
206
+ field.enum = property.enum;
207
+ }
208
+ if (property.type === PropertyType.keyValue) {
209
+ const kvProperties = (property as unknown as { properties: KeyValuePropertyMetadata[] }).properties;
210
+ if (kvProperties?.length > 0) {
211
+ field.keyValueFields = extractKeyValueFields(kvProperties);
212
+ for (const kvField of field.keyValueFields) {
213
+ fields.push({
214
+ ...kvField,
215
+ name: `${fieldName}.${kvField.name}`,
216
+ displayName: `${field.displayName}.${kvField.displayName}`
217
+ });
218
+ }
219
+ }
220
+ }
221
+ fields.push(field);
222
+ }
223
+
224
+ // 3. 添加外键字段(使用 Set 避免 O(n²) 重复检测)
225
+ const existingNames = new Set<string>();
226
+ for (const f of fields) existingNames.add(f.name);
227
+ for (const fkName of metadata.foreignKeyNames) {
228
+ const fieldName = prefix ? `${prefix}.${fkName}` : fkName;
229
+ if (existingNames.has(fieldName)) continue;
230
+ const relationName = fkName.slice(0, -2);
231
+ const relation = metadata.foreignKeyRelationMap.get(relationName);
232
+ const relationDisplayName = relation?.displayName ?? relationName;
233
+ const nullable = relation && 'nullable' in relation ? (relation.nullable ?? false) : false;
234
+ fields.push({
235
+ name: fieldName,
236
+ displayName: `${relationDisplayName}ID`,
237
+ type: 'string',
238
+ nullable
239
+ });
240
+ existingNames.add(fieldName);
241
+ }
242
+
243
+ // 4. 处理关系(递归)
244
+ if (depth < relationQueryDeep) {
245
+ for (const [key, relation] of metadata.relationMap) {
246
+ if (ignoreKeys.includes(key)) continue;
247
+
248
+ const relatedModel = allModels.get(relation.mappedEntity);
249
+ if (!relatedModel) continue;
250
+
251
+ if (depth > 0 && relatedModel.metadata === firstMetadata) continue;
252
+ if (visitedEntities.has(relatedModel.metadata.name)) continue;
253
+
254
+ const relPrefix = prefix ? `${prefix}.${key}` : key;
255
+ const relDisplayName = relation.displayName || key;
256
+
257
+ // 提取关联实体的字段(用于 EXISTS 子查询)
258
+ const relationFields = extractRelationTargetFields(relatedModel.metadata, config);
259
+
260
+ fields.push({
261
+ name: relPrefix,
262
+ displayName: `${relDisplayName} (${relation.kind})`,
263
+ type: 'relation' as QBPropertyType,
264
+ isRelation: true,
265
+ relationTarget: relation.mappedEntity,
266
+ relationFields
267
+ });
268
+
269
+ const nextIgnoreKeys = getRelationIgnoreKeys(relation);
270
+ const nextVisited = new Set(visitedEntities);
271
+ nextVisited.add(metadata.name);
272
+
273
+ const nestedFields = extractFieldsRecursive(
274
+ relatedModel.metadata,
275
+ allModels,
276
+ config,
277
+ firstMetadata,
278
+ nextIgnoreKeys,
279
+ relPrefix,
280
+ nextVisited,
281
+ depth + 1
282
+ );
283
+
284
+ // 添加嵌套字段的系统字段(使用 propertyMap 做 O(1) 查找)
285
+ const relatedPropertyMap = relatedModel.metadata.propertyMap;
286
+ const existingFieldNames = new Set(fields.map(f => f.name));
287
+ for (const sysField of systemFields) {
288
+ const sysFieldName = `${relPrefix}.${sysField}`;
289
+ const sysProp = relatedPropertyMap.get(sysField);
290
+ if (sysProp && !existingFieldNames.has(sysFieldName)) {
291
+ fields.push({
292
+ name: sysFieldName,
293
+ displayName: `${relDisplayName}.${sysProp.displayName ?? sysField}`,
294
+ type: mapPropertyType(sysProp.type as PropertyType),
295
+ nullable: sysProp.nullable ?? false
296
+ });
297
+ }
298
+ }
299
+
300
+ fields.push(...nestedFields);
301
+ }
302
+ }
303
+
304
+ return fields;
305
+ }
306
+
307
+ /**
308
+ * 提取关系目标实体的字段(用于 EXISTS 子查询)
309
+ *
310
+ * @param metadata - 实体元数据
311
+ * @param config - 字段提取器配置
312
+ * @returns 字段元数据列表(仅一层,不递归)
313
+ *
314
+ * @remarks
315
+ * 此函数只提取一层字段,不递归关系。用于 EXISTS/NOT EXISTS 子查询的字段列表。
316
+ *
317
+ * @example
318
+ * ```typescript
319
+ * const fields = extractRelationTargetFields(orderMetadata);
320
+ * // 返回: [
321
+ * // { name: 'id', displayName: 'ID', type: 'string' },
322
+ * // { name: 'total', displayName: '总金额', type: 'number' },
323
+ * // { name: 'ownerId', displayName: '用户ID', type: 'string' },
324
+ * // ...
325
+ * // ]
326
+ * ```
327
+ */
328
+ export function extractRelationTargetFields(
329
+ metadata: EntityMetadata,
330
+ config: FieldExtractorConfig = {}
331
+ ): FieldMetadata[] {
332
+ const fields: FieldMetadata[] = [];
333
+ const systemFields = config.systemFields ?? DEFAULT_SYSTEM_FIELDS;
334
+ const propertyByName = metadata.propertyMap;
335
+
336
+ // 添加系统字段
337
+ for (const sysField of systemFields) {
338
+ const sysProp = propertyByName.get(sysField);
339
+ if (sysProp) {
340
+ fields.push({
341
+ name: sysField,
342
+ displayName: sysProp.displayName ?? sysField,
343
+ type: mapPropertyType(sysProp.type as PropertyType),
344
+ nullable: sysProp.nullable ?? false
345
+ });
346
+ }
347
+ }
348
+
349
+ // 添加实体属性字段
350
+ for (const [key, property] of metadata.propertyMap) {
351
+ const field: FieldMetadata = {
352
+ name: key,
353
+ displayName: property.displayName ?? key,
354
+ type: mapPropertyType(property.type as PropertyType),
355
+ nullable: property.nullable ?? false
356
+ };
357
+ if (property.type === PropertyType.enum && Array.isArray(property.enum)) {
358
+ field.enum = property.enum;
359
+ }
360
+ fields.push(field);
361
+ }
362
+
363
+ // 添加外键字段
364
+ const existingFkNames = new Set<string>();
365
+ for (const f of fields) existingFkNames.add(f.name);
366
+ for (const fkName of metadata.foreignKeyNames) {
367
+ if (existingFkNames.has(fkName)) continue;
368
+ const relationName = fkName.slice(0, -2);
369
+ const relation = metadata.foreignKeyRelationMap.get(relationName);
370
+ const relationDisplayName = relation?.displayName ?? relationName;
371
+ const nullable = relation && 'nullable' in relation ? (relation.nullable ?? false) : false;
372
+ fields.push({
373
+ name: fkName,
374
+ displayName: `${relationDisplayName}ID`,
375
+ type: 'string',
376
+ nullable
377
+ });
378
+ existingFkNames.add(fkName);
379
+ }
380
+
381
+ return fields;
382
+ }
383
+
384
+ /**
385
+ * 将 KeyValuePropertyMetadata 的 type 映射到 QueryBuilder PropertyType
386
+ */
387
+ function mapKVPropertyType(type?: string): QBPropertyType {
388
+ switch (type) {
389
+ case 'number':
390
+ case 'integer':
391
+ return 'number';
392
+ case 'boolean':
393
+ return 'boolean';
394
+ case 'date':
395
+ return 'date';
396
+ default:
397
+ return 'string';
398
+ }
399
+ }
400
+
401
+ /**
402
+ * 从 keyValue 属性的 properties 提取子字段列表
403
+ *
404
+ * @param properties keyValue 属性的子属性元数据列表
405
+ * @returns 子字段元数据列表
406
+ */
407
+ export function extractKeyValueFields(properties: KeyValuePropertyMetadata[]): FieldMetadata[] {
408
+ return properties.map(p => ({
409
+ name: p.name,
410
+ displayName: p.displayName ?? p.name,
411
+ type: mapKVPropertyType(p.type as string),
412
+ nullable: p.nullable ?? false,
413
+ required: p.required ?? false
414
+ }));
415
+ }
416
+
417
+ /**
418
+ * 对字段进行分组和排序
419
+ *
420
+ * @param fields - 字段元数据列表
421
+ * @returns 排序后的字段列表
422
+ *
423
+ * @remarks
424
+ * 排序规则:
425
+ * 1. 按层级深度排序(字段名中 '.' 的数量)
426
+ * 2. 同一层级按字段名字母顺序排序
427
+ *
428
+ * @example
429
+ * ```typescript
430
+ * const fields = [
431
+ * { name: 'orders.id', ... },
432
+ * { name: 'name', ... },
433
+ * { name: 'id', ... },
434
+ * { name: 'orders.total', ... }
435
+ * ];
436
+ *
437
+ * const sorted = organizeFields(fields);
438
+ * // 返回: [
439
+ * // { name: 'id', ... },
440
+ * // { name: 'name', ... },
441
+ * // { name: 'orders.id', ... },
442
+ * // { name: 'orders.total', ... }
443
+ * // ]
444
+ * ```
445
+ */
446
+ export function organizeFields(fields: FieldMetadata[]): FieldMetadata[] {
447
+ // 预计算深度,避免在排序比较器中重复执行 regex
448
+ const depths = new Map<FieldMetadata, number>();
449
+ for (const f of fields) {
450
+ let depth = 0;
451
+ for (let i = 0; i < f.name.length; i++) {
452
+ if (f.name.charCodeAt(i) === 46) depth++;
453
+ }
454
+ depths.set(f, depth);
455
+ }
456
+ return [...fields].sort((a, b) => {
457
+ const d = (depths.get(a) ?? 0) - (depths.get(b) ?? 0);
458
+ return d !== 0 ? d : a.name.localeCompare(b.name);
459
+ });
460
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * 计算弹出层相对于触发元素的位置,优先下方展示,空间不足时向上。
3
+ *
4
+ * @param triggerRect 触发按钮的 DOMRect
5
+ * @param gap 触发元素与弹出层之间的间距(单位 px,默认 2)
6
+ * @param minSpaceBelow 判断下方空间是否充足的阈值(单位 px,默认 120)
7
+ * @returns 弹出层定位信息:left、minWidth,以及 top(下方展示)或 bottom(上方展示)
8
+ */
9
+ export function calcPopoverPosition(
10
+ triggerRect: DOMRect,
11
+ gap = 2,
12
+ minSpaceBelow = 120
13
+ ): { left: number; top?: number; bottom?: number; minWidth: number } {
14
+ const spaceBelow = window.innerHeight - triggerRect.bottom;
15
+ if (spaceBelow > minSpaceBelow) {
16
+ return { left: triggerRect.left, top: triggerRect.bottom + gap, minWidth: triggerRect.width };
17
+ }
18
+ return { left: triggerRect.left, bottom: window.innerHeight - triggerRect.top + gap, minWidth: triggerRect.width };
19
+ }
20
+
21
+ /**
22
+ * 将 calcPopoverPosition 的结果直接应用到弹出层 DOM 元素。
23
+ *
24
+ * @param el 弹出层 DOM 元素
25
+ * @param triggerRect 触发按钮的 DOMRect
26
+ * @param gap 触发元素与弹出层之间的间距(单位 px,默认 2)
27
+ * @param minSpaceBelow 判断下方空间是否充足的阈值(单位 px,默认 120)
28
+ */
29
+ export function applyPopoverPosition(el: HTMLElement, triggerRect: DOMRect, gap = 2, minSpaceBelow = 120): void {
30
+ const pos = calcPopoverPosition(triggerRect, gap, minSpaceBelow);
31
+ el.style.position = 'fixed';
32
+ el.style.margin = '0';
33
+ el.style.inset = 'unset';
34
+ el.style.minWidth = `${pos.minWidth}px`;
35
+ el.style.left = `${pos.left}px`;
36
+ if (pos.top != null) {
37
+ el.style.top = `${pos.top}px`;
38
+ el.style.bottom = 'unset';
39
+ } else {
40
+ el.style.top = 'unset';
41
+ el.style.bottom = `${pos.bottom}px`;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * 在弹出层选项列表中查找匹配项的 label。
47
+ *
48
+ * @param options 选项列表(value 为选中值、label 为显示文本)
49
+ * @param selected 当前选中的 value
50
+ * @param placeholder 未找到匹配项时返回的占位文本
51
+ * @returns 匹配项的 label;未找到时返回 placeholder
52
+ */
53
+ export function findOptionLabel(
54
+ options: ReadonlyArray<{ value: string; label: string }>,
55
+ selected: string,
56
+ placeholder: string
57
+ ): string {
58
+ return options.find(o => o.value === selected)?.label ?? placeholder;
59
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * @fileoverview Schema 工厂函数
3
+ * 提供便捷的工厂方法从 RxDB 实体创建 SchemaParser 实例并解析字段
4
+ */
5
+
6
+ import type { EntityMetadata } from '@aiao/rxdb';
7
+ import type { FieldMetadata } from '../models/query-builder-state.js';
8
+ import { createSchemaParser, type SchemaParserOptions } from '../services/schema-parser.service.js';
9
+
10
+ /**
11
+ * 从 EntityMetadata 创建字段元数据
12
+ *
13
+ * 这是一个便捷的工厂函数,封装了 SchemaParser 的创建和解析过程
14
+ *
15
+ * @param metadata - RxDB 实体元数据
16
+ * @param options - 解析器选项
17
+ * @returns 字段元数据数组
18
+ *
19
+ * @example
20
+ * ```typescript
21
+ * // 基础用法
22
+ * const fields = createSchemaFromEntity(userMetadata);
23
+ *
24
+ * // 包含外键
25
+ * const fieldsWithFK = createSchemaFromEntity(userMetadata, {
26
+ * includeForeignKeys: true
27
+ * });
28
+ *
29
+ * // 包含计算属性
30
+ * const fieldsWithComputed = createSchemaFromEntity(userMetadata, {
31
+ * includeComputedProperties: true
32
+ * });
33
+ *
34
+ * // 自定义过滤器
35
+ * const onlyStringFields = createSchemaFromEntity(userMetadata, {
36
+ * fieldFilter: (field) => field.type === 'string'
37
+ * });
38
+ * ```
39
+ */
40
+ export function createSchemaFromEntity(metadata: EntityMetadata, options?: SchemaParserOptions): FieldMetadata[] {
41
+ const parser = createSchemaParser(options);
42
+ return parser.parse(metadata);
43
+ }
44
+
45
+ /**
46
+ * 从多个 EntityMetadata 批量创建字段元数据
47
+ *
48
+ * @param metadataMap - 实体名称到元数据的映射
49
+ * @param options - 解析器选项
50
+ * @returns 实体名称到字段数组的映射
51
+ *
52
+ * @example
53
+ * ```typescript
54
+ * const entities = new Map([
55
+ * ['User', userMetadata],
56
+ * ['Post', postMetadata],
57
+ * ['Comment', commentMetadata]
58
+ * ]);
59
+ *
60
+ * const schemas = createSchemaFromEntities(entities, {
61
+ * includeForeignKeys: true
62
+ * });
63
+ *
64
+ * // 使用结果
65
+ * const userFields = schemas.get('User');
66
+ * const postFields = schemas.get('Post');
67
+ * ```
68
+ */
69
+ export function createSchemaFromEntities(
70
+ metadataMap: Map<string, EntityMetadata>,
71
+ options?: SchemaParserOptions
72
+ ): Map<string, FieldMetadata[]> {
73
+ const parser = createSchemaParser(options);
74
+ return parser.parseMany(metadataMap);
75
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * 重新导出 structuralEqual:结构化深比较,替代 JSON.stringify 比较。
3
+ * 递归比较对象和数组,不依赖 key 序列化顺序,支持循环引用检测。
4
+ */
5
+ export { structuralEqual } from '../../structural-equal.js';
@@ -0,0 +1,105 @@
1
+ import { UUID_RE } from '../../entity-value.utils.js';
2
+ import type { OperatorDefinition } from '../models/operator.interface.js';
3
+ import type { PropertyType } from '../models/query-builder-state.js';
4
+ import type { OperatorRegistry } from '../services/operator-registry.service.js';
5
+ import { getDefaultOperatorRegistry } from '../services/operator-registry.service.js';
6
+
7
+ /** 标准 UUID 格式正则(8-4-4-4-12 十六进制,忽略大小写),重新导出供校验使用 */
8
+ export { UUID_RE };
9
+
10
+ /**
11
+ * 根据字段类型和运算符确定查询条件值的输入控件类型。
12
+ *
13
+ * @param fieldType 字段类型(如 'string'、'number'、'date'、'enum' 等)
14
+ * @param operator 运算符 key(如 'between'、'in'、'null' 等)
15
+ * @param enumOptions 字段的枚举选项列表
16
+ * @param operatorRegistry 运算符注册表(默认使用全局默认注册表)
17
+ * @returns 输入控件类型标识(如 'string'、'number'、'boolean'、'date'、'range'、'array'、'enum'、'enum-array'、'subquery'、'uuid'、'none')
18
+ */
19
+ export function getInputType(
20
+ fieldType: string,
21
+ operator: string,
22
+ enumOptions?: unknown[],
23
+ operatorRegistry: OperatorRegistry = getDefaultOperatorRegistry()
24
+ ): string {
25
+ if (operator === 'null' || operator === 'notNull') return 'none';
26
+
27
+ const operators = operatorRegistry.getForType(fieldType as PropertyType) || [];
28
+ const op = operators.find((o: OperatorDefinition) => o.key === operator);
29
+
30
+ if (op?.valueType === 'none') return 'none';
31
+ if (op?.valueType === 'subquery') return 'subquery';
32
+ if (operator === 'between' || operator === 'notBetween') return 'range';
33
+ if (operator === 'in' || operator === 'notIn') {
34
+ if ((enumOptions && enumOptions.length > 0) || fieldType === 'enum') return 'enum-array';
35
+ return 'array';
36
+ }
37
+ if (enumOptions && enumOptions.length > 0) return 'enum';
38
+
39
+ switch (fieldType) {
40
+ case 'boolean':
41
+ return 'boolean';
42
+ case 'number':
43
+ case 'integer':
44
+ return 'number';
45
+ case 'date':
46
+ case 'date-time':
47
+ return 'date';
48
+ case 'array':
49
+ return 'array';
50
+ case 'enum':
51
+ return 'enum';
52
+ case 'uuid':
53
+ return 'uuid';
54
+ default:
55
+ return 'string';
56
+ }
57
+ }
58
+
59
+ /**
60
+ * 返回指定输入控件类型的默认值。
61
+ *
62
+ * @param type 输入控件类型(getInputType 的返回值)
63
+ * @returns 对应类型的默认值:boolean 为 false、range/array/enum-array 为 []、subquery 为 undefined、none/number/date 为 null、其余为 ''
64
+ */
65
+ export function getDefaultValueForType(type: string): unknown {
66
+ switch (type) {
67
+ case 'none':
68
+ return null;
69
+ case 'boolean':
70
+ return false;
71
+ case 'number':
72
+ return null;
73
+ case 'date':
74
+ return null;
75
+ case 'range':
76
+ return [];
77
+ case 'array':
78
+ return [];
79
+ case 'enum-array':
80
+ return [];
81
+ case 'subquery':
82
+ return undefined;
83
+ default:
84
+ return '';
85
+ }
86
+ }
87
+
88
+ /**
89
+ * 将逗号分隔的字符串解析为数组。
90
+ * 当 fieldType 为 'number' 时返回数字数组,否则返回字符串数组。
91
+ *
92
+ * @param text 逗号分隔的字符串
93
+ * @param fieldType 字段类型;为 'number' 时各项解析为数字并过滤无效值
94
+ * @returns 解析后的数组(空白项被过滤)
95
+ */
96
+ export function parseCommaSeparatedInput(text: string, fieldType: string): unknown[] {
97
+ const items = text
98
+ .split(',')
99
+ .map(s => s.trim())
100
+ .filter(s => s.length > 0);
101
+ if (fieldType === 'number') {
102
+ return items.map(s => Number(s)).filter(n => !isNaN(n));
103
+ }
104
+ return items;
105
+ }