@aiao/rxdb-model 0.0.18 → 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.
- 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 +31 -0
- package/dist/entity-capabilities.d.ts.map +1 -0
- package/dist/entity-detail/detail-tabs.d.ts +13 -2
- 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 +29 -1
- package/dist/entity-detail/interfaces.d.ts.map +1 -1
- package/dist/entity-field.utils.d.ts +60 -2
- package/dist/entity-field.utils.d.ts.map +1 -1
- package/dist/entity-form/form-data.d.ts +22 -1
- package/dist/entity-form/form-data.d.ts.map +1 -1
- package/dist/entity-form/form-fields.d.ts +25 -2
- package/dist/entity-form/form-fields.d.ts.map +1 -1
- package/dist/entity-form/form-validation.d.ts +20 -2
- 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 +65 -1
- package/dist/entity-form/interfaces.d.ts.map +1 -1
- package/dist/entity-list/manual-order-list.d.ts +99 -0
- package/dist/entity-list/manual-order-list.d.ts.map +1 -0
- package/dist/entity-table/columns/build-editable-columns.d.ts +26 -4
- package/dist/entity-table/columns/build-editable-columns.d.ts.map +1 -1
- package/dist/entity-table/columns/column-utils.d.ts +78 -12
- 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 +6 -1
- package/dist/entity-table/editors/date-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/enum-editor.d.ts +11 -1
- package/dist/entity-table/editors/enum-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/global-overlay-editor.d.ts +27 -4
- package/dist/entity-table/editors/global-overlay-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/icon-list-editor.d.ts +13 -1
- package/dist/entity-table/editors/icon-list-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/json-editor.d.ts +6 -1
- package/dist/entity-table/editors/json-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/key-value-editor.d.ts +12 -2
- 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 +33 -3
- package/dist/entity-table/editors/number-editor.d.ts.map +1 -1
- package/dist/entity-table/editors/relation-editor.d.ts +14 -2
- 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 +12 -1
- 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 +14 -1
- 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 +13 -0
- package/dist/entity-table/interfaces.d.ts.map +1 -1
- package/dist/entity-table/vtable/table-clipboard.d.ts +31 -2
- package/dist/entity-table/vtable/table-clipboard.d.ts.map +1 -1
- package/dist/entity-table/vtable/table-factory.d.ts +25 -5
- 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 +44 -3
- 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 +50 -2
- package/dist/entity-value.utils.d.ts.map +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4392 -64471
- package/dist/query-builder/models/config.interface.d.ts +2 -2
- package/dist/query-builder/models/operator.interface.d.ts +6 -2
- 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 +2 -2
- package/dist/query-builder/services/operator-registry.service.d.ts +13 -3
- package/dist/query-builder/services/operator-registry.service.d.ts.map +1 -1
- package/dist/query-builder/services/query-builder.service.d.ts +16 -6
- package/dist/query-builder/services/query-builder.service.d.ts.map +1 -1
- package/dist/query-builder/services/query-converter.service.d.ts +10 -2
- package/dist/query-builder/services/query-converter.service.d.ts.map +1 -1
- package/dist/query-builder/services/schema-parser.service.d.ts +10 -2
- package/dist/query-builder/services/schema-parser.service.d.ts.map +1 -1
- package/dist/query-builder/services/validation.service.d.ts +34 -4
- 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 +7 -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 +15 -3
- 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 +7 -3
- 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 +21 -1
- 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-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 +72 -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 +57 -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 +309 -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 +59 -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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"value-input.d.ts","sourceRoot":"","sources":["../../../src/query-builder/utils/value-input.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAGtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAGjF,OAAO,EAAE,OAAO,EAAE,CAAC;AAEnB,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,OAAO,EAAE,EACvB,gBAAgB,GAAE,gBAA+C,GAChE,MAAM,CAiCR;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAqB5D;AAED
|
|
1
|
+
{"version":3,"file":"value-input.d.ts","sourceRoot":"","sources":["../../../src/query-builder/utils/value-input.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAGtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AAGjF,oDAAoD;AACpD,OAAO,EAAE,OAAO,EAAE,CAAC;AAEnB;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,WAAW,CAAC,EAAE,OAAO,EAAE,EACvB,gBAAgB,GAAE,gBAA+C,GAChE,MAAM,CAiCR;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAqB5D;AAED;;;;;;;GAOG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,EAAE,CASnF"}
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
* 结构化深比较,替代 JSON.stringify 比较。
|
|
3
3
|
* 递归比较对象和数组,不依赖 key 序列化顺序。
|
|
4
4
|
* 支持循环引用检测,避免无限递归。
|
|
5
|
+
* @param a - 参与比较的第一个值
|
|
6
|
+
* @param b - 参与比较的第二个值
|
|
7
|
+
* @param seen - 已访问的对象集合,用于循环引用检测
|
|
8
|
+
* @returns 两者结构相等时返回 true
|
|
5
9
|
*/
|
|
6
|
-
export declare function structuralEqual(a: unknown, b: unknown
|
|
10
|
+
export declare function structuralEqual(a: unknown, b: unknown): boolean;
|
|
7
11
|
//# sourceMappingURL=structural-equal.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"structural-equal.d.ts","sourceRoot":"","sources":["../src/structural-equal.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"structural-equal.d.ts","sourceRoot":"","sources":["../src/structural-equal.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,OAAO,GAAG,OAAO,CAE/D"}
|
package/package.json
CHANGED
|
@@ -1,14 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiao/rxdb-model",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.26",
|
|
4
|
+
"description": "Framework-agnostic entity model core for Aiao RxDB: metadata-driven forms, details, editable tables and query builder.",
|
|
5
|
+
"homepage": "https://rxdb.netlify.app/docs",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/aiao-io/rxdb/issues"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"aiao",
|
|
11
|
+
"rxdb",
|
|
12
|
+
"entity",
|
|
13
|
+
"form",
|
|
14
|
+
"table",
|
|
15
|
+
"query-builder"
|
|
16
|
+
],
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/aiao-io/rxdb.git",
|
|
21
|
+
"directory": "packages/rxdb-model"
|
|
22
|
+
},
|
|
4
23
|
"type": "module",
|
|
24
|
+
"sideEffects": false,
|
|
5
25
|
"main": "./dist/index.js",
|
|
6
26
|
"module": "./dist/index.js",
|
|
7
27
|
"types": "./dist/index.d.ts",
|
|
8
28
|
"exports": {
|
|
9
29
|
"./package.json": "./package.json",
|
|
10
30
|
".": {
|
|
11
|
-
"@aiao/source": "./src/index.ts",
|
|
12
31
|
"types": "./dist/index.d.ts",
|
|
13
32
|
"import": "./dist/index.js",
|
|
14
33
|
"default": "./dist/index.js"
|
|
@@ -16,18 +35,23 @@
|
|
|
16
35
|
},
|
|
17
36
|
"files": [
|
|
18
37
|
"dist",
|
|
38
|
+
"src",
|
|
39
|
+
"!src/**/*.spec.*",
|
|
40
|
+
"!src/**/*.test.*",
|
|
41
|
+
"!src/**/__tests__/**",
|
|
19
42
|
"!**/*.tsbuildinfo"
|
|
20
43
|
],
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
21
47
|
"dependencies": {
|
|
22
|
-
"
|
|
23
|
-
"
|
|
48
|
+
"clsx": "^2.1.1",
|
|
49
|
+
"rxjs": "^7.8.2",
|
|
50
|
+
"uuid": "^14.0.2",
|
|
51
|
+
"@aiao/rxdb": "0.0.26"
|
|
24
52
|
},
|
|
25
53
|
"peerDependencies": {
|
|
26
|
-
"
|
|
27
|
-
"@visactor/vtable": "
|
|
28
|
-
"@visactor/vtable-editors": ">=1.0.0"
|
|
29
|
-
},
|
|
30
|
-
"publishConfig": {
|
|
31
|
-
"access": "public"
|
|
54
|
+
"@visactor/vtable": "~1.26.5",
|
|
55
|
+
"@visactor/vtable-editors": "~1.26.5"
|
|
32
56
|
}
|
|
33
57
|
}
|
package/src/cn.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { clsx, type ClassValue } from 'clsx';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 拼接 CSS 类名(clsx 的薄封装)。
|
|
5
|
+
*
|
|
6
|
+
* 过滤 falsy 值并支持对象 / 数组形式的条件类名,用于替代模板字符串拼接:
|
|
7
|
+
* 模板字符串中条件类名前的分隔空格若位于行尾,会被格式化工具的尾随空白
|
|
8
|
+
* 裁剪吃掉,产生 `flex-coloverflow-auto` 这类拼接错误;本函数不存在该问题。
|
|
9
|
+
*
|
|
10
|
+
* @param inputs - 字符串类名、条件对象或数组,falsy 值会被自动忽略
|
|
11
|
+
* @returns 以单个空格连接后的类名字符串
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* cn('tab-content flex-col', isForm ? 'overflow-auto p-4' : 'overflow-hidden')
|
|
16
|
+
* // 'tab-content flex-col overflow-auto p-4'
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export function cn(...inputs: ClassValue[]): string {
|
|
20
|
+
return clsx(inputs);
|
|
21
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview 实体界面能力派生(US-027)
|
|
3
|
+
*
|
|
4
|
+
* 实体管理界面的「新增 / 编辑 / 删除」入口只读这一份结论,结论只来自实体的 `permissions` 声明:
|
|
5
|
+
* 是不是系统表、在哪个命名空间都不参与——系统表已经显式声明三项都为 `'system'`。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { type EntityMetadata, getEntityPermission } from '@aiao/rxdb';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 实体在公开写入口上允许的界面操作
|
|
12
|
+
*/
|
|
13
|
+
export interface EntityCapabilities {
|
|
14
|
+
/** 可新增:`create` 为 `'both'` */
|
|
15
|
+
readonly canCreate: boolean;
|
|
16
|
+
/** 已有行可编辑:`update` 为 `'both'`;否则行只读、详情以查看模式打开 */
|
|
17
|
+
readonly canEdit: boolean;
|
|
18
|
+
/** 已有行可删除:`delete` 为 `'both'` */
|
|
19
|
+
readonly canDelete: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 由实体元数据派生界面能力
|
|
24
|
+
*
|
|
25
|
+
* @param metadata - 实体元数据(只读其中的 `permissions`)
|
|
26
|
+
* @returns 三项能力;未声明的操作按 `'both'` 计,即可用
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* const { canCreate, canEdit, canDelete } = deriveEntityCapabilities(getEntityMetadata(Invoice));
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export const deriveEntityCapabilities = (metadata: Pick<EntityMetadata, 'permissions'>): EntityCapabilities => ({
|
|
34
|
+
canCreate: getEntityPermission(metadata, 'create') === 'both',
|
|
35
|
+
canEdit: getEntityPermission(metadata, 'update') === 'both',
|
|
36
|
+
canDelete: getEntityPermission(metadata, 'delete') === 'both'
|
|
37
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity Detail 标签构建工具
|
|
3
|
+
* 根据实体元数据生成详情页标签配置
|
|
4
|
+
* @module entity-detail/detail-tabs
|
|
5
|
+
*/
|
|
6
|
+
import { RelationKind, type EntityMetadata } from '@aiao/rxdb';
|
|
7
|
+
|
|
8
|
+
import type { DetailFormTab, DetailTab, DetailTableTab } from './interfaces.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 根据实体元数据构建详情页标签列表
|
|
12
|
+
* @remarks 基本信息表单标签固定排在首位,其后为每个一对多 / 多对多关系生成一个表格标签
|
|
13
|
+
* @param metadata - 实体元数据
|
|
14
|
+
* @returns 详情页标签配置列表
|
|
15
|
+
*/
|
|
16
|
+
export function buildDetailTabs(metadata: EntityMetadata): DetailTab[] {
|
|
17
|
+
const formTab: DetailFormTab = {
|
|
18
|
+
key: 'basic',
|
|
19
|
+
label: '基本信息',
|
|
20
|
+
type: 'form'
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const relationTabs: DetailTableTab[] = [];
|
|
24
|
+
|
|
25
|
+
metadata.relationMap.forEach((relation, name) => {
|
|
26
|
+
if (relation.kind !== RelationKind.ONE_TO_MANY && relation.kind !== RelationKind.MANY_TO_MANY) return;
|
|
27
|
+
|
|
28
|
+
const tab: DetailTableTab = {
|
|
29
|
+
key: name,
|
|
30
|
+
label: relation.displayName ?? name,
|
|
31
|
+
type: 'table',
|
|
32
|
+
relationName: name,
|
|
33
|
+
relatedEntityName: relation.mappedEntity,
|
|
34
|
+
relatedNamespace: relation.mappedNamespace ?? 'public',
|
|
35
|
+
relationKind: relation.kind
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
if (relation.kind === RelationKind.ONE_TO_MANY) {
|
|
39
|
+
tab.foreignKeyField = `${relation.mappedProperty}Id`;
|
|
40
|
+
} else if (relation.kind === RelationKind.MANY_TO_MANY) {
|
|
41
|
+
tab.mappedProperty = relation.mappedProperty;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
relationTabs.push(tab);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return [formTab, ...relationTabs];
|
|
48
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity Detail 类型定义
|
|
3
|
+
* @module entity-detail/interfaces
|
|
4
|
+
*/
|
|
5
|
+
import type { RelationKind } from '@aiao/rxdb';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 详情页标签类型
|
|
9
|
+
*/
|
|
10
|
+
export type DetailTabType = 'form' | 'table';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 详情页标签基础配置
|
|
14
|
+
*/
|
|
15
|
+
export interface DetailTabBase {
|
|
16
|
+
/** 标签唯一标识 */
|
|
17
|
+
key: string;
|
|
18
|
+
/** 标签显示名称 */
|
|
19
|
+
label: string;
|
|
20
|
+
/** 标签类型 */
|
|
21
|
+
type: DetailTabType;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 表单标签配置(展示实体的基本信息表单)
|
|
26
|
+
*/
|
|
27
|
+
export interface DetailFormTab extends DetailTabBase {
|
|
28
|
+
/** 标签类型:表单 */
|
|
29
|
+
type: 'form';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 表格标签配置(展示关联实体的关系数据)
|
|
34
|
+
*/
|
|
35
|
+
export interface DetailTableTab extends DetailTabBase {
|
|
36
|
+
/** 标签类型:表格 */
|
|
37
|
+
type: 'table';
|
|
38
|
+
/** 关系名 */
|
|
39
|
+
relationName: string;
|
|
40
|
+
/** 关联实体名 */
|
|
41
|
+
relatedEntityName: string;
|
|
42
|
+
/** 关联实体命名空间 */
|
|
43
|
+
relatedNamespace: string;
|
|
44
|
+
/** 关系类型 */
|
|
45
|
+
relationKind: RelationKind;
|
|
46
|
+
/** ONE_TO_MANY: 关联实体中指向当前实体的外键字段,如 'ownerId' */
|
|
47
|
+
foreignKeyField?: string;
|
|
48
|
+
/** MANY_TO_MANY: 关联实体中指向当前实体的关系属性名,如 'orderItems' */
|
|
49
|
+
mappedProperty?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 详情页标签配置
|
|
54
|
+
*/
|
|
55
|
+
export type DetailTab = DetailFormTab | DetailTableTab;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RxDB Model 实体字段工具模块
|
|
3
|
+
* 提供实体字段值的解析、格式化、验证等功能
|
|
4
|
+
* @module entity-field.utils
|
|
5
|
+
*/
|
|
6
|
+
import type { EntityMetadata } from '@aiao/rxdb';
|
|
7
|
+
import {
|
|
8
|
+
PropertyType,
|
|
9
|
+
RelationKind,
|
|
10
|
+
type EntityPropertyMetadata,
|
|
11
|
+
type EntityRelationMetadata,
|
|
12
|
+
type FieldFormat,
|
|
13
|
+
type FieldOptions,
|
|
14
|
+
type KeyValuePropertyMetadata
|
|
15
|
+
} from '@aiao/rxdb';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 实体字段类型
|
|
19
|
+
* @remarks 兼容 PropertyType 枚举值及其字符串字面量形式,并包含关系字段(oneToOne / manyToOne)与计算字段(computed)的标识
|
|
20
|
+
*/
|
|
21
|
+
export type EntityFieldType =
|
|
22
|
+
| PropertyType
|
|
23
|
+
| `${PropertyType.bigint}`
|
|
24
|
+
| `${PropertyType.binary}`
|
|
25
|
+
| `${PropertyType.boolean}`
|
|
26
|
+
| `${PropertyType.date}`
|
|
27
|
+
| `${PropertyType.enum}`
|
|
28
|
+
| `${PropertyType.integer}`
|
|
29
|
+
| `${PropertyType.number}`
|
|
30
|
+
| `${PropertyType.string}`
|
|
31
|
+
| `${PropertyType.uuid}`
|
|
32
|
+
| `${PropertyType.numberArray}`
|
|
33
|
+
| `${PropertyType.stringArray}`
|
|
34
|
+
| `${PropertyType.keyValue}`
|
|
35
|
+
| `${PropertyType.json}`
|
|
36
|
+
| 'oneToOne'
|
|
37
|
+
| 'manyToOne'
|
|
38
|
+
| 'computed';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 键值类型字段中单个键的 Schema 配置
|
|
42
|
+
*/
|
|
43
|
+
export interface KeyValueSchemaEntry {
|
|
44
|
+
/** 键的显示名称 */
|
|
45
|
+
label?: string;
|
|
46
|
+
/** 值的类型 */
|
|
47
|
+
type?: 'string' | 'number' | 'integer' | 'boolean' | 'date';
|
|
48
|
+
/** 是否必填 */
|
|
49
|
+
required?: boolean;
|
|
50
|
+
/** 是否可为空 */
|
|
51
|
+
nullable?: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 实体字段配置
|
|
56
|
+
*/
|
|
57
|
+
export interface EntityFieldConfig {
|
|
58
|
+
/** 字段名 */
|
|
59
|
+
field: string;
|
|
60
|
+
/** 显示名称 */
|
|
61
|
+
displayName: string;
|
|
62
|
+
/** 字段类型 */
|
|
63
|
+
type: EntityFieldType;
|
|
64
|
+
/** 是否只读 */
|
|
65
|
+
readonly?: boolean;
|
|
66
|
+
/** 是否可为空 */
|
|
67
|
+
nullable?: boolean;
|
|
68
|
+
/** 是否必填 */
|
|
69
|
+
required?: boolean;
|
|
70
|
+
/** 是否唯一 */
|
|
71
|
+
unique?: boolean;
|
|
72
|
+
/** 枚举类型的可选值 */
|
|
73
|
+
enumValues?: readonly string[];
|
|
74
|
+
/** 键值类型字段的 Schema */
|
|
75
|
+
keyValueSchema?: Record<string, KeyValueSchemaEntry>;
|
|
76
|
+
/** 关系字段关联的实体名 */
|
|
77
|
+
relatedEntityName?: string;
|
|
78
|
+
/** 关系字段关联实体的命名空间 */
|
|
79
|
+
relatedNamespace?: string;
|
|
80
|
+
/**
|
|
81
|
+
* 字段语义标注(只影响展示与控件选择,不改变运行时值类型)
|
|
82
|
+
* 仅在元数据声明 `format` 时输出该键
|
|
83
|
+
*/
|
|
84
|
+
format?: FieldFormat;
|
|
85
|
+
/**
|
|
86
|
+
* 枚举/多选值的展示元数据(label / color / disabled),键是 `enum` 的子集
|
|
87
|
+
* 仅在元数据声明 `options` 时输出该键
|
|
88
|
+
*/
|
|
89
|
+
options?: FieldOptions;
|
|
90
|
+
/**
|
|
91
|
+
* 是否为加密列
|
|
92
|
+
* 仅在元数据声明 `encrypted: true` 时输出该键
|
|
93
|
+
*/
|
|
94
|
+
encrypted?: boolean;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const SYSTEM_FIELDS = new Set(['id', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy']);
|
|
98
|
+
|
|
99
|
+
/** 只在显式声明 `true` 时才把布尔标志读成 true。 */
|
|
100
|
+
const readFlag = (source: object, key: string): boolean => (source as Record<string, unknown>)[key] === true;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* 将属性元数据转换为字段配置
|
|
104
|
+
*/
|
|
105
|
+
function propertyToField(key: string, prop: EntityPropertyMetadata): EntityFieldConfig {
|
|
106
|
+
const raw = prop as Record<string, unknown>;
|
|
107
|
+
const field: EntityFieldConfig = {
|
|
108
|
+
field: key,
|
|
109
|
+
displayName: prop.displayName ?? key,
|
|
110
|
+
type: prop.type as PropertyType,
|
|
111
|
+
readonly: readFlag(prop, 'readonly'),
|
|
112
|
+
nullable: prop.nullable,
|
|
113
|
+
required: prop.required,
|
|
114
|
+
unique: prop.unique,
|
|
115
|
+
// 只在元数据声明过时才输出这些键,保证既有「全等断言」的字段形状不漂移
|
|
116
|
+
...(raw['format'] === undefined ? {} : { format: raw['format'] as FieldFormat }),
|
|
117
|
+
...(raw['options'] === undefined ? {} : { options: raw['options'] as FieldOptions }),
|
|
118
|
+
...(raw['encrypted'] === undefined ? {} : { encrypted: raw['encrypted'] as boolean })
|
|
119
|
+
};
|
|
120
|
+
if (prop.type === PropertyType.enum || prop.type === PropertyType.stringArray) {
|
|
121
|
+
field.enumValues = (prop as { enum: readonly string[] }).enum;
|
|
122
|
+
}
|
|
123
|
+
if (prop.type === PropertyType.keyValue) {
|
|
124
|
+
const kvProp = prop as { properties: KeyValuePropertyMetadata[] };
|
|
125
|
+
field.keyValueSchema = Object.fromEntries(
|
|
126
|
+
kvProp.properties.map(p => [
|
|
127
|
+
p.name,
|
|
128
|
+
{
|
|
129
|
+
label: p.displayName,
|
|
130
|
+
type: p.type as KeyValueSchemaEntry['type'],
|
|
131
|
+
required: p.required,
|
|
132
|
+
nullable: p.nullable
|
|
133
|
+
}
|
|
134
|
+
])
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
return field;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 将关系元数据转换为字段配置
|
|
142
|
+
*/
|
|
143
|
+
function relationToField(fkField: string, relation: EntityRelationMetadata): EntityFieldConfig {
|
|
144
|
+
return {
|
|
145
|
+
field: fkField,
|
|
146
|
+
displayName: relation.displayName ?? fkField,
|
|
147
|
+
type: relation.kind === RelationKind.ONE_TO_ONE ? 'oneToOne' : 'manyToOne',
|
|
148
|
+
nullable: (relation as Record<string, unknown>)['nullable'] === true,
|
|
149
|
+
relatedEntityName: relation.mappedEntity,
|
|
150
|
+
relatedNamespace: relation.mappedNamespace
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 从实体元数据中提取全部业务字段配置
|
|
156
|
+
* @remarks 跳过系统字段,依次收集普通属性、计算属性与关系字段
|
|
157
|
+
* @param metadata - 实体元数据
|
|
158
|
+
* @returns 字段配置列表
|
|
159
|
+
*/
|
|
160
|
+
export function extractEntityFields(metadata: EntityMetadata): EntityFieldConfig[] {
|
|
161
|
+
const fields: EntityFieldConfig[] = [];
|
|
162
|
+
|
|
163
|
+
metadata.propertyMap.forEach((prop, key) => {
|
|
164
|
+
if (SYSTEM_FIELDS.has(key)) return;
|
|
165
|
+
fields.push(propertyToField(key, prop));
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
metadata.computedPropertyMap.forEach((prop, key) => {
|
|
169
|
+
fields.push({
|
|
170
|
+
field: key,
|
|
171
|
+
displayName: prop.displayName ?? key,
|
|
172
|
+
type: 'computed',
|
|
173
|
+
readonly: true
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
metadata.foreignKeyRelationMap.forEach((relation, fkField) => {
|
|
178
|
+
fields.push(relationToField(fkField, relation));
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
return fields;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const SYSTEM_FIELD_CONFIGS: ReadonlyArray<Omit<EntityFieldConfig, 'readonly'>> = [
|
|
185
|
+
{ field: 'createdAt', displayName: '创建时间', type: PropertyType.date },
|
|
186
|
+
{ field: 'updatedAt', displayName: '更新时间', type: PropertyType.date },
|
|
187
|
+
{ field: 'createdBy', displayName: '创建者', type: PropertyType.string },
|
|
188
|
+
{ field: 'updatedBy', displayName: '更新者', type: PropertyType.string }
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* 从实体元数据中提取系统字段配置
|
|
193
|
+
* @remarks 始终包含只读的 id 字段,并按元数据中实际存在的属性补充时间戳与审计字段
|
|
194
|
+
* @param metadata - 实体元数据
|
|
195
|
+
* @returns 系统字段配置列表
|
|
196
|
+
*/
|
|
197
|
+
export function extractSystemFields(metadata: EntityMetadata): EntityFieldConfig[] {
|
|
198
|
+
const fields: EntityFieldConfig[] = [{ field: 'id', displayName: 'ID', type: PropertyType.uuid, readonly: true }];
|
|
199
|
+
for (const cfg of SYSTEM_FIELD_CONFIGS) {
|
|
200
|
+
if (metadata.propertyMap.has(cfg.field)) {
|
|
201
|
+
fields.push({ ...cfg, readonly: true });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return fields;
|
|
205
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 实体表单数据转换工具
|
|
3
|
+
* 提供实体对象与表单数据之间的转换及默认表单数据创建
|
|
4
|
+
* @module entity-form/form-data
|
|
5
|
+
*/
|
|
6
|
+
import { parseEntityFieldValue } from '../entity-value.utils.js';
|
|
7
|
+
import { structuralEqual } from '../structural-equal.js';
|
|
8
|
+
import type { EntityFormData, FormFieldConfig } from './interfaces.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 将实体对象转换为表单数据
|
|
12
|
+
* @remarks 实体上不存在的字段以 null 填充
|
|
13
|
+
* @param entity - 源实体对象
|
|
14
|
+
* @param fields - 表单字段配置
|
|
15
|
+
* @returns 仅包含字段配置中声明字段的表单数据
|
|
16
|
+
*/
|
|
17
|
+
export function entityToFormData(entity: Record<string, unknown>, fields: FormFieldConfig[]): EntityFormData {
|
|
18
|
+
const data: EntityFormData = {};
|
|
19
|
+
for (const field of fields) {
|
|
20
|
+
data[field.field] = entity[field.field] ?? null;
|
|
21
|
+
}
|
|
22
|
+
return data;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 计算表单数据相对原实体对象的变更集
|
|
27
|
+
* @remarks 只读字段跳过;值相同(引用相等或结构化相等)的字段不进入变更集
|
|
28
|
+
* @param original - 原实体对象
|
|
29
|
+
* @param formData - 表单数据
|
|
30
|
+
* @param fields - 表单字段配置
|
|
31
|
+
* @returns 发生变化的字段及其解析后的新值
|
|
32
|
+
*/
|
|
33
|
+
export function formDataToEntityChanges(
|
|
34
|
+
original: Record<string, unknown>,
|
|
35
|
+
formData: EntityFormData,
|
|
36
|
+
fields: FormFieldConfig[]
|
|
37
|
+
): Record<string, unknown> {
|
|
38
|
+
const changes: Record<string, unknown> = {};
|
|
39
|
+
for (const field of fields) {
|
|
40
|
+
if (field.readonly) continue;
|
|
41
|
+
const parsed = parseEntityFieldValue(field.type, formData[field.field]);
|
|
42
|
+
const orig = original[field.field] ?? null;
|
|
43
|
+
// 快速路径:引用相等或同为原始类型时跳过深比较
|
|
44
|
+
if (parsed === orig) continue;
|
|
45
|
+
if (parsed === null || orig === null || (typeof parsed !== 'object' && typeof orig !== 'object')) {
|
|
46
|
+
changes[field.field] = parsed;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (!structuralEqual(parsed, orig)) {
|
|
50
|
+
changes[field.field] = parsed;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return changes;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 根据字段配置创建默认表单数据
|
|
58
|
+
* @remarks 只读字段不参与;布尔字段默认为 false,字符串 / 数字数组默认为空数组,其余字段默认为 null
|
|
59
|
+
* @param fields - 表单字段配置
|
|
60
|
+
* @returns 默认表单数据
|
|
61
|
+
*/
|
|
62
|
+
export function createDefaultFormData(fields: FormFieldConfig[]): EntityFormData {
|
|
63
|
+
const data: EntityFormData = {};
|
|
64
|
+
for (const field of fields) {
|
|
65
|
+
if (field.readonly) continue;
|
|
66
|
+
switch (field.type) {
|
|
67
|
+
case 'boolean':
|
|
68
|
+
data[field.field] = false;
|
|
69
|
+
break;
|
|
70
|
+
case 'stringArray':
|
|
71
|
+
case 'numberArray':
|
|
72
|
+
data[field.field] = [];
|
|
73
|
+
break;
|
|
74
|
+
case 'keyValue':
|
|
75
|
+
case 'json':
|
|
76
|
+
data[field.field] = null;
|
|
77
|
+
break;
|
|
78
|
+
default:
|
|
79
|
+
data[field.field] = null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return data;
|
|
83
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 表单字段构建工具
|
|
3
|
+
* 根据实体元数据与表单模式生成、排序与过滤表单字段
|
|
4
|
+
* @module entity-form/form-fields
|
|
5
|
+
*/
|
|
6
|
+
import type { EntityMetadata } from '@aiao/rxdb';
|
|
7
|
+
import { extractEntityFields, extractSystemFields } from '../entity-field.utils.js';
|
|
8
|
+
import type { FormFieldConfig, FormMode } from './interfaces.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 根据实体元数据和表单模式构建表单字段列表
|
|
12
|
+
* @remarks 创建模式排除计算字段且不含系统字段;编辑模式追加只读的系统字段;查看模式全部字段设为只读
|
|
13
|
+
* @param metadata - 实体元数据
|
|
14
|
+
* @param mode - 表单模式
|
|
15
|
+
* @returns 表单字段配置列表
|
|
16
|
+
*/
|
|
17
|
+
export function buildFormFields(metadata: EntityMetadata, mode: FormMode): FormFieldConfig[] {
|
|
18
|
+
const entityFields = extractEntityFields(metadata).map(f => f as FormFieldConfig);
|
|
19
|
+
const systemFields = extractSystemFields(metadata).map(f => ({ ...f, readonly: true }) as FormFieldConfig);
|
|
20
|
+
|
|
21
|
+
switch (mode) {
|
|
22
|
+
case 'create':
|
|
23
|
+
return entityFields.filter(f => f.type !== 'computed');
|
|
24
|
+
case 'edit':
|
|
25
|
+
return [...entityFields, ...systemFields];
|
|
26
|
+
case 'view':
|
|
27
|
+
return [...entityFields, ...systemFields].map(f => ({ ...f, readonly: true }));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 按字段的 order 升序排序
|
|
33
|
+
* @remarks 未设置 order 的字段按 0 处理,返回新数组而不修改原数组
|
|
34
|
+
* @param fields - 表单字段配置
|
|
35
|
+
* @returns 排序后的字段列表
|
|
36
|
+
*/
|
|
37
|
+
export function sortFormFields(fields: FormFieldConfig[]): FormFieldConfig[] {
|
|
38
|
+
return [...fields].sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 过滤掉标记为隐藏的字段
|
|
43
|
+
* @param fields - 表单字段配置
|
|
44
|
+
* @returns 可见字段列表
|
|
45
|
+
*/
|
|
46
|
+
export function filterVisibleFields(fields: FormFieldConfig[]): FormFieldConfig[] {
|
|
47
|
+
return fields.filter(f => !f.hidden);
|
|
48
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 表单校验工具
|
|
3
|
+
* 基于字段配置校验单个字段值与整份表单数据
|
|
4
|
+
* @module entity-form/form-validation
|
|
5
|
+
*/
|
|
6
|
+
import { validateEntityFieldValue, type FieldValidationError } from '../entity-value.utils.js';
|
|
7
|
+
import type { EntityFormData, FormFieldConfig, FormValidationResult } from './interfaces.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 校验单个表单字段值
|
|
11
|
+
* @param field - 表单字段配置
|
|
12
|
+
* @param value - 待校验的字段值
|
|
13
|
+
* @returns 校验错误;校验通过时返回 null
|
|
14
|
+
*/
|
|
15
|
+
export function validateField(field: FormFieldConfig, value: unknown): FieldValidationError | null {
|
|
16
|
+
return validateEntityFieldValue(field, value);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* 校验整份表单数据
|
|
21
|
+
* @remarks 只读与隐藏字段不参与校验
|
|
22
|
+
* @param fields - 表单字段配置
|
|
23
|
+
* @param data - 表单数据
|
|
24
|
+
* @returns 校验结果(是否通过及错误列表)
|
|
25
|
+
*/
|
|
26
|
+
export function validateForm(fields: FormFieldConfig[], data: EntityFormData): FormValidationResult {
|
|
27
|
+
const errors: FieldValidationError[] = [];
|
|
28
|
+
for (const field of fields) {
|
|
29
|
+
if (field.readonly || field.hidden) continue;
|
|
30
|
+
const error = validateEntityFieldValue(field, data[field.field]);
|
|
31
|
+
if (error) errors.push(error);
|
|
32
|
+
}
|
|
33
|
+
return { valid: errors.length === 0, errors };
|
|
34
|
+
}
|