cabloy 5.1.148 → 5.1.150
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/.cabloy-version +1 -1
- package/.claude/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +2 -1
- package/.claude/skills/cabloy-frontend-scaffold/SKILL.md +1 -0
- package/CHANGELOG.md +33 -0
- package/CLAUDE.md +1 -0
- package/package.json +1 -1
- package/repo-docs/.vitepress/config.mjs +4 -0
- package/repo-docs/backend/dto-guide.md +7 -6
- package/repo-docs/backend/dto-infer-generation.md +25 -5
- package/repo-docs/backend/markdown-guide.md +1 -0
- package/repo-docs/backend/relations-guide.md +1 -0
- package/repo-docs/frontend/a-model-under-the-hood.md +38 -0
- package/repo-docs/frontend/a-openapi-under-the-hood.md +13 -2
- package/repo-docs/frontend/form-layout-guide.md +39 -8
- package/repo-docs/frontend/markdown-guide.md +1 -0
- package/repo-docs/frontend/model-resource-best-practices.md +2 -0
- package/repo-docs/frontend/model-resource-cookbook.md +7 -1
- package/repo-docs/frontend/use-state-data-best-practices.md +44 -0
- package/repo-docs/frontend/zova-form-source-reading-map.md +26 -9
- package/repo-docs/frontend/zova-form-under-the-hood.md +11 -0
- package/repo-docs/fullstack/edition-collaboration-differences.md +1 -0
- package/repo-docs/fullstack/one-to-one-companion-resource-guide.md +447 -0
- package/repo-e2e/specs/a-commerce.spec.ts +40 -0
- package/repo-e2e/specs/cabloy-basic.spec.ts +176 -1
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/cli/templates/tools/crudStart/boilerplate/src/controller/{{resourceName}}.ts_ +2 -2
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/pnpm-lock.yaml +83 -75
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/.metadata/index.ts +108 -108
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/dto/skuSelectResItem.tsx +12 -0
- package/vona/src/suite/a-commerce/modules/commerce-catalog/src/service/sku.ts +19 -1
- package/vona/src/suite/a-commerce/modules/commerce-catalog/test/skuPresentation.test.ts +53 -4
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +148 -148
- package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/stockAdjust.tsx +37 -3
- package/vona/src/suite/a-training/modules/training-student/package.json +1 -0
- package/vona/src/suite/a-training/modules/training-student/src/.metadata/index.ts +177 -103
- package/vona/src/suite/a-training/modules/training-student/src/bean/meta.index.ts +1 -0
- package/vona/src/suite/a-training/modules/training-student/src/bean/meta.version.ts +19 -1
- package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +3 -0
- package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +3 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx +22 -2
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentSummary.tsx +7 -1
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentUpdate.tsx +22 -2
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentView.tsx +25 -3
- package/vona/src/suite/a-training/modules/training-student/src/entity/student.tsx +0 -3
- package/vona/src/suite/a-training/modules/training-student/src/entity/studentContent.tsx +36 -0
- package/vona/src/suite/a-training/modules/training-student/src/model/student.ts +6 -0
- package/vona/src/suite/a-training/modules/training-student/src/model/studentContent.ts +22 -0
- package/vona/src/suite/a-training/modules/training-student/src/service/student.ts +43 -14
- package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +137 -8
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/pnpm-lock.yaml +6 -2
- package/zova/src/suite/a-commerce/modules/commerce-trade/cli/openapi.config.ts +1 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/package.json +6 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/component/tableCellActionAdjustStock.ts +34 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +81 -6
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/commerceTradeStockBalance.ts +32 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/schemas.ts +44 -44
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/types.ts +356 -338
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/apiSchema/commerceTradeStockBalance.ts +17 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/bean/tableCell.actionAdjustStock.tsx +35 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/component/tableCellActionAdjustStock/controller.tsx +138 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/en-us.ts +3 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/zh-cn.ts +3 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/stockBalance.ts +36 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx +4 -4
- package/zova/src/suite/a-training/modules/training-student/package.json +3 -1
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/schemas.ts +44 -44
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +360 -339
- package/zova/src/suite/a-training/modules/training-student/src/bean/tableCell.actionSummary.tsx +19 -8
- package/zova/src/suite/cabloy-basic/modules/basic-app/src/bean/behavior.appModal.tsx +2 -0
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +2 -1
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/test/model/resource.types.ts +11 -0
- package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/formLayout.ts +69 -12
- package/zova/src/suite-vendor/a-zova/modules/a-form/test/lib/formLayout.test.ts +157 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.persister.ts +3 -1
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useQuery.ts +11 -13
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useState.ts +9 -7
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useStateGeneral.ts +5 -12
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/queryPersister.ts +17 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/queryRefetch.ts +34 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/types/query.ts +27 -3
- package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/queryPersister.test.ts +115 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/queryRefetch.test.ts +84 -0
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/lib/schema.ts +27 -5
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/model/sdk.ts +2 -2
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/rest.ts +4 -0
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/schema.ts +2 -5
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/test/lib/schema.test.ts +179 -0
- package/zova/src/suite-vendor/a-zova/package.json +4 -4
package/zova/src/suite/a-training/modules/training-student/src/bean/tableCell.actionSummary.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import type { ModelStudent } from 'zova-module-training-student';
|
|
|
9
9
|
|
|
10
10
|
import { BeanBase } from 'zova';
|
|
11
11
|
import { TableCell } from 'zova-module-a-table';
|
|
12
|
+
import { ZMarkdownHtml } from 'zova-module-basic-markdown';
|
|
12
13
|
|
|
13
14
|
declare module 'zova-module-a-openapi' {
|
|
14
15
|
export interface IResourceTableActionRowRecord {
|
|
@@ -39,14 +40,24 @@ export class TableCellActionSummary extends BeanBase implements ITableCellRender
|
|
|
39
40
|
true,
|
|
40
41
|
)) as ModelStudent;
|
|
41
42
|
const querySummary = modelStudent.summary(id);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
43
|
+
await querySummary.refetch({ bypassPersister: true });
|
|
44
|
+
$host.$appModal.dialog(
|
|
45
|
+
{
|
|
46
|
+
title: this.scope.locale.Summary(),
|
|
47
|
+
slotDefault: () => (
|
|
48
|
+
<div class="student-summary-description">
|
|
49
|
+
<ZMarkdownHtml html={querySummary.data?.descriptionHtml ?? ''} />
|
|
50
|
+
</div>
|
|
51
|
+
),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
maxWidth: 720,
|
|
55
|
+
maxHeight: 'calc(100vh - 2rem)',
|
|
56
|
+
closeOnBackdrop: true,
|
|
57
|
+
closeOnEscape: true,
|
|
58
|
+
showCloseButton: true,
|
|
59
|
+
},
|
|
60
|
+
);
|
|
50
61
|
}}
|
|
51
62
|
>
|
|
52
63
|
{this.scope.locale.Summary()}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TableIdentity } from 'table-identity';
|
|
2
|
-
import type { DataMutation, IDecoratorModelOptions } from 'zova-module-a-model';
|
|
2
|
+
import type { DataMutation, IDecoratorModelOptions, QueryMetaPersister } from 'zova-module-a-model';
|
|
3
3
|
import type {
|
|
4
4
|
IFormMeta,
|
|
5
5
|
IFormProvider,
|
|
@@ -27,6 +27,7 @@ interface IModelResourceQueryItemOptions<TData> {
|
|
|
27
27
|
meta?: {
|
|
28
28
|
disableSuspenseOnInit?: boolean;
|
|
29
29
|
disableErrorEffect?: boolean;
|
|
30
|
+
persister?: QueryMetaPersister | boolean;
|
|
30
31
|
};
|
|
31
32
|
}
|
|
32
33
|
|
|
@@ -17,6 +17,17 @@ void requiredBody.delete(id).mutateAsync({ reason: 'duplicate' });
|
|
|
17
17
|
void optionalBody.delete(id).mutateAsync();
|
|
18
18
|
void optionalBody.delete(id).mutateAsync({ reason: 'duplicate' });
|
|
19
19
|
|
|
20
|
+
const queryItem = standard.queryItem({
|
|
21
|
+
id,
|
|
22
|
+
action: 'summary',
|
|
23
|
+
queryFn: async () => ({ id: 1 }),
|
|
24
|
+
});
|
|
25
|
+
void queryItem.refetch({
|
|
26
|
+
bypassPersister: true,
|
|
27
|
+
cancelRefetch: false,
|
|
28
|
+
throwOnError: true,
|
|
29
|
+
});
|
|
30
|
+
|
|
20
31
|
// @ts-expect-error required DELETE bodies cannot be omitted
|
|
21
32
|
void requiredBody.delete(id).mutateAsync();
|
|
22
33
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zova-suite-a-cabloy",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.48",
|
|
4
4
|
"gitHead": "4dece229b6d3f4735930e833afcb4b95ec51bf86",
|
|
5
5
|
"description": "",
|
|
6
6
|
"author": "",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"type": "module",
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"zova-module-a-rbac": "^5.0.1",
|
|
11
|
-
"zova-module-rest-resource": "^5.1.
|
|
11
|
+
"zova-module-rest-resource": "^5.1.47"
|
|
12
12
|
},
|
|
13
13
|
"title": "a-cabloy"
|
|
14
14
|
}
|
|
@@ -24,12 +24,21 @@ export function resolveFormLayout(
|
|
|
24
24
|
layout: IFormLayout,
|
|
25
25
|
properties: ISchemaObjectExtensionField[] | undefined,
|
|
26
26
|
): IResolvedFormLayout {
|
|
27
|
+
const visibleProperties = properties?.filter(item => item.rest?.visible !== false) ?? [];
|
|
27
28
|
const propertyNames = new Set(
|
|
28
|
-
|
|
29
|
-
?.filter(item => item.rest?.visible !== false)
|
|
30
|
-
.map(item => item.key)
|
|
31
|
-
.filter(Boolean) as string[],
|
|
29
|
+
visibleProperties.map(item => item.key).filter(Boolean) as string[],
|
|
32
30
|
);
|
|
31
|
+
const propertyAliases = new Map<string, Set<string>>();
|
|
32
|
+
for (const property of visibleProperties) {
|
|
33
|
+
if (!property.key) continue;
|
|
34
|
+
const schemaKeys = [property.schemaKey, ...(property.schemaKeys ?? [])];
|
|
35
|
+
for (const schemaKey of schemaKeys) {
|
|
36
|
+
if (!schemaKey || schemaKey === property.key) continue;
|
|
37
|
+
const aliases = propertyAliases.get(schemaKey) ?? new Set<string>();
|
|
38
|
+
aliases.add(property.key);
|
|
39
|
+
propertyAliases.set(schemaKey, aliases);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
33
42
|
const fieldNames = new Set<string>();
|
|
34
43
|
const nodeIds = new Set<string>();
|
|
35
44
|
const diagnostics: IFormLayoutDiagnostic[] = [];
|
|
@@ -41,6 +50,7 @@ export function resolveFormLayout(
|
|
|
41
50
|
[index],
|
|
42
51
|
[],
|
|
43
52
|
propertyNames,
|
|
53
|
+
propertyAliases,
|
|
44
54
|
fieldNames,
|
|
45
55
|
nodeIds,
|
|
46
56
|
diagnostics,
|
|
@@ -62,13 +72,22 @@ function resolveNode(
|
|
|
62
72
|
indexPath: number[],
|
|
63
73
|
tabPath: IResolvedFormLayout['fieldTabPaths'][string],
|
|
64
74
|
propertyNames: Set<string>,
|
|
75
|
+
propertyAliases: Map<string, Set<string>>,
|
|
65
76
|
fieldNames: Set<string>,
|
|
66
77
|
nodeIds: Set<string>,
|
|
67
78
|
diagnostics: IFormLayoutDiagnostic[],
|
|
68
79
|
fieldTabPaths: IResolvedFormLayout['fieldTabPaths'],
|
|
69
80
|
): IResolvedFormLayoutNode | undefined {
|
|
70
81
|
if (node.type === 'field') {
|
|
71
|
-
return resolveField(
|
|
82
|
+
return resolveField(
|
|
83
|
+
node,
|
|
84
|
+
tabPath,
|
|
85
|
+
propertyNames,
|
|
86
|
+
propertyAliases,
|
|
87
|
+
fieldNames,
|
|
88
|
+
diagnostics,
|
|
89
|
+
fieldTabPaths,
|
|
90
|
+
);
|
|
72
91
|
}
|
|
73
92
|
if (node.type === 'block') {
|
|
74
93
|
return node;
|
|
@@ -78,6 +97,7 @@ function resolveNode(
|
|
|
78
97
|
node,
|
|
79
98
|
indexPath,
|
|
80
99
|
propertyNames,
|
|
100
|
+
propertyAliases,
|
|
81
101
|
fieldNames,
|
|
82
102
|
nodeIds,
|
|
83
103
|
diagnostics,
|
|
@@ -89,7 +109,15 @@ function resolveNode(
|
|
|
89
109
|
if (node.type === 'section') {
|
|
90
110
|
const children = node.children
|
|
91
111
|
.map(item =>
|
|
92
|
-
resolveLeaf(
|
|
112
|
+
resolveLeaf(
|
|
113
|
+
item,
|
|
114
|
+
tabPath,
|
|
115
|
+
propertyNames,
|
|
116
|
+
propertyAliases,
|
|
117
|
+
fieldNames,
|
|
118
|
+
diagnostics,
|
|
119
|
+
fieldTabPaths,
|
|
120
|
+
),
|
|
93
121
|
)
|
|
94
122
|
.filter(Boolean) as IResolvedFormLayoutLeaf[];
|
|
95
123
|
return children.length ? { ...node, id, children } : undefined;
|
|
@@ -101,6 +129,7 @@ function resolveNode(
|
|
|
101
129
|
[...indexPath, index],
|
|
102
130
|
tabPath,
|
|
103
131
|
propertyNames,
|
|
132
|
+
propertyAliases,
|
|
104
133
|
fieldNames,
|
|
105
134
|
nodeIds,
|
|
106
135
|
diagnostics,
|
|
@@ -117,18 +146,28 @@ function resolveLeaf(
|
|
|
117
146
|
node: IFormLayoutLeaf,
|
|
118
147
|
tabPath: IResolvedFormLayout['fieldTabPaths'][string],
|
|
119
148
|
propertyNames: Set<string>,
|
|
149
|
+
propertyAliases: Map<string, Set<string>>,
|
|
120
150
|
fieldNames: Set<string>,
|
|
121
151
|
diagnostics: IFormLayoutDiagnostic[],
|
|
122
152
|
fieldTabPaths: IResolvedFormLayout['fieldTabPaths'],
|
|
123
153
|
): IResolvedFormLayoutLeaf | undefined {
|
|
124
154
|
if (node.type === 'block') return node;
|
|
125
|
-
return resolveField(
|
|
155
|
+
return resolveField(
|
|
156
|
+
node,
|
|
157
|
+
tabPath,
|
|
158
|
+
propertyNames,
|
|
159
|
+
propertyAliases,
|
|
160
|
+
fieldNames,
|
|
161
|
+
diagnostics,
|
|
162
|
+
fieldTabPaths,
|
|
163
|
+
);
|
|
126
164
|
}
|
|
127
165
|
|
|
128
166
|
function resolveTabs(
|
|
129
167
|
node: IFormLayoutTabs,
|
|
130
168
|
indexPath: number[],
|
|
131
169
|
propertyNames: Set<string>,
|
|
170
|
+
propertyAliases: Map<string, Set<string>>,
|
|
132
171
|
fieldNames: Set<string>,
|
|
133
172
|
nodeIds: Set<string>,
|
|
134
173
|
diagnostics: IFormLayoutDiagnostic[],
|
|
@@ -143,6 +182,7 @@ function resolveTabs(
|
|
|
143
182
|
[...indexPath, index],
|
|
144
183
|
id,
|
|
145
184
|
propertyNames,
|
|
185
|
+
propertyAliases,
|
|
146
186
|
fieldNames,
|
|
147
187
|
nodeIds,
|
|
148
188
|
diagnostics,
|
|
@@ -158,6 +198,7 @@ function resolveTab(
|
|
|
158
198
|
indexPath: number[],
|
|
159
199
|
tabsId: string,
|
|
160
200
|
propertyNames: Set<string>,
|
|
201
|
+
propertyAliases: Map<string, Set<string>>,
|
|
161
202
|
fieldNames: Set<string>,
|
|
162
203
|
nodeIds: Set<string>,
|
|
163
204
|
diagnostics: IFormLayoutDiagnostic[],
|
|
@@ -173,6 +214,7 @@ function resolveTab(
|
|
|
173
214
|
[...indexPath, index],
|
|
174
215
|
tabPath,
|
|
175
216
|
propertyNames,
|
|
217
|
+
propertyAliases,
|
|
176
218
|
fieldNames,
|
|
177
219
|
nodeIds,
|
|
178
220
|
diagnostics,
|
|
@@ -189,21 +231,36 @@ function resolveField(
|
|
|
189
231
|
node: IFormLayoutField,
|
|
190
232
|
tabPath: IResolvedFormLayout['fieldTabPaths'][string],
|
|
191
233
|
propertyNames: Set<string>,
|
|
234
|
+
propertyAliases: Map<string, Set<string>>,
|
|
192
235
|
fieldNames: Set<string>,
|
|
193
236
|
diagnostics: IFormLayoutDiagnostic[],
|
|
194
237
|
fieldTabPaths: IResolvedFormLayout['fieldTabPaths'],
|
|
195
238
|
): IResolvedFormLayoutField | undefined {
|
|
196
|
-
|
|
239
|
+
const name = resolveFieldName(node.name, propertyNames, propertyAliases);
|
|
240
|
+
if (!name) {
|
|
197
241
|
diagnostics.push({ type: 'unknownField', value: node.name });
|
|
198
242
|
return;
|
|
199
243
|
}
|
|
200
|
-
if (fieldNames.has(
|
|
244
|
+
if (fieldNames.has(name)) {
|
|
201
245
|
diagnostics.push({ type: 'duplicateField', value: node.name });
|
|
202
246
|
return;
|
|
203
247
|
}
|
|
204
|
-
fieldNames.add(
|
|
205
|
-
fieldTabPaths[
|
|
206
|
-
return node;
|
|
248
|
+
fieldNames.add(name);
|
|
249
|
+
fieldTabPaths[name] = tabPath;
|
|
250
|
+
return name === node.name ? node : { ...node, name };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function resolveFieldName(
|
|
254
|
+
name: string,
|
|
255
|
+
propertyNames: Set<string>,
|
|
256
|
+
propertyAliases: Map<string, Set<string>>,
|
|
257
|
+
) {
|
|
258
|
+
if (propertyNames.has(name)) return name;
|
|
259
|
+
const aliases = propertyAliases.get(name);
|
|
260
|
+
if (aliases?.size === 1) return aliases.values().next().value;
|
|
261
|
+
const fieldSourcePrefix = `${name}.`;
|
|
262
|
+
const fieldSources = [...propertyNames].filter(item => item.startsWith(fieldSourcePrefix));
|
|
263
|
+
return fieldSources.length === 1 ? fieldSources[0] : undefined;
|
|
207
264
|
}
|
|
208
265
|
|
|
209
266
|
function resolveId(node: { type: string; id?: string }, indexPath: number[]) {
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { IFormLayout, ISchemaObjectExtensionField } from 'zova-module-a-openapi';
|
|
2
|
+
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import test from 'node:test';
|
|
5
|
+
|
|
6
|
+
import { resolveFormLayout } from '../../src/lib/formLayout.js';
|
|
7
|
+
|
|
8
|
+
function createProperties(...properties: ISchemaObjectExtensionField[]) {
|
|
9
|
+
return properties;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function createTabsLayout(name: string): IFormLayout {
|
|
13
|
+
return {
|
|
14
|
+
children: [
|
|
15
|
+
{
|
|
16
|
+
type: 'tabs',
|
|
17
|
+
children: [
|
|
18
|
+
{
|
|
19
|
+
type: 'tab',
|
|
20
|
+
title: 'Content',
|
|
21
|
+
children: [{ type: 'section', children: [{ type: 'field', name }] }],
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
],
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getTabFieldNames(resolved: ReturnType<typeof resolveFormLayout>) {
|
|
30
|
+
const tabs = resolved.children[0] as any;
|
|
31
|
+
return tabs.children[0].children[0].children.map((item: any) => item.name);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
test('Form Layout resolves a real relation name to its unique canonical source', () => {
|
|
35
|
+
const resolved = resolveFormLayout(
|
|
36
|
+
createTabsLayout('content'),
|
|
37
|
+
createProperties({ key: 'content.descriptionMarkdown', type: 'string' }),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
assert.deepEqual(getTabFieldNames(resolved), ['content.descriptionMarkdown']);
|
|
41
|
+
assert.deepEqual(resolved.fieldTabPaths, {
|
|
42
|
+
'content.descriptionMarkdown': [{ tabsId: 'tabs-0', tabId: 'tab-0-0' }],
|
|
43
|
+
});
|
|
44
|
+
assert.deepEqual(resolved.diagnostics, []);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test('Form Layout resolves a virtual schema alias to its canonical source', () => {
|
|
48
|
+
const resolved = resolveFormLayout(
|
|
49
|
+
createTabsLayout('_descriptionMarkdown'),
|
|
50
|
+
createProperties({
|
|
51
|
+
key: 'content.descriptionMarkdown',
|
|
52
|
+
schemaKey: '_descriptionMarkdown',
|
|
53
|
+
type: 'string',
|
|
54
|
+
}),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
assert.deepEqual(getTabFieldNames(resolved), ['content.descriptionMarkdown']);
|
|
58
|
+
assert.deepEqual(resolved.fieldTabPaths, {
|
|
59
|
+
'content.descriptionMarkdown': [{ tabsId: 'tabs-0', tabId: 'tab-0-0' }],
|
|
60
|
+
});
|
|
61
|
+
assert.deepEqual(resolved.diagnostics, []);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('Form Layout accepts every preserved alias for the same canonical source', () => {
|
|
65
|
+
const resolved = resolveFormLayout(
|
|
66
|
+
createTabsLayout('studentContentForm'),
|
|
67
|
+
createProperties({
|
|
68
|
+
key: 'content.descriptionMarkdown',
|
|
69
|
+
schemaKey: '_descriptionMarkdown',
|
|
70
|
+
schemaKeys: ['studentContentForm'],
|
|
71
|
+
type: 'string',
|
|
72
|
+
}),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
assert.deepEqual(getTabFieldNames(resolved), ['content.descriptionMarkdown']);
|
|
76
|
+
assert.deepEqual(resolved.diagnostics, []);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test('Form Layout prefers an exact canonical field over a colliding alias', () => {
|
|
80
|
+
const resolved = resolveFormLayout(
|
|
81
|
+
{ children: [{ type: 'field', name: 'content' }] },
|
|
82
|
+
createProperties(
|
|
83
|
+
{ key: 'content', type: 'object' },
|
|
84
|
+
{
|
|
85
|
+
key: 'content.descriptionMarkdown',
|
|
86
|
+
schemaKey: 'content',
|
|
87
|
+
type: 'string',
|
|
88
|
+
},
|
|
89
|
+
),
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
assert.deepEqual(resolved.children, [
|
|
93
|
+
{ type: 'field', name: 'content' },
|
|
94
|
+
{ type: 'field', name: 'content.descriptionMarkdown' },
|
|
95
|
+
]);
|
|
96
|
+
assert.deepEqual(resolved.diagnostics, []);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('Form Layout detects duplicate alias and canonical declarations by canonical key', () => {
|
|
100
|
+
const resolved = resolveFormLayout(
|
|
101
|
+
{
|
|
102
|
+
children: [
|
|
103
|
+
{ type: 'field', name: '_descriptionMarkdown' },
|
|
104
|
+
{ type: 'field', name: 'content.descriptionMarkdown' },
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
createProperties({
|
|
108
|
+
key: 'content.descriptionMarkdown',
|
|
109
|
+
schemaKey: '_descriptionMarkdown',
|
|
110
|
+
type: 'string',
|
|
111
|
+
}),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
assert.deepEqual(resolved.children, [{ type: 'field', name: 'content.descriptionMarkdown' }]);
|
|
115
|
+
assert.deepEqual(resolved.diagnostics, [
|
|
116
|
+
{ type: 'duplicateField', value: 'content.descriptionMarkdown' },
|
|
117
|
+
]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('Form Layout keeps ambiguous aliases and relation prefixes unknown', () => {
|
|
121
|
+
const resolved = resolveFormLayout(
|
|
122
|
+
{
|
|
123
|
+
children: [
|
|
124
|
+
{ type: 'field', name: '_content' },
|
|
125
|
+
{ type: 'field', name: 'content' },
|
|
126
|
+
],
|
|
127
|
+
},
|
|
128
|
+
createProperties(
|
|
129
|
+
{ key: 'content.descriptionMarkdown', schemaKey: '_content', type: 'string' },
|
|
130
|
+
{ key: 'content.descriptionHtml', schemaKey: '_content', type: 'string' },
|
|
131
|
+
),
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
assert.deepEqual(resolved.children, [
|
|
135
|
+
{ type: 'field', name: 'content.descriptionMarkdown' },
|
|
136
|
+
{ type: 'field', name: 'content.descriptionHtml' },
|
|
137
|
+
]);
|
|
138
|
+
assert.deepEqual(resolved.diagnostics, [
|
|
139
|
+
{ type: 'unknownField', value: '_content' },
|
|
140
|
+
{ type: 'unknownField', value: 'content' },
|
|
141
|
+
]);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('Form Layout does not resolve or append invisible aliases', () => {
|
|
145
|
+
const resolved = resolveFormLayout(
|
|
146
|
+
{ children: [{ type: 'field', name: '_descriptionMarkdown' }] },
|
|
147
|
+
createProperties({
|
|
148
|
+
key: 'content.descriptionMarkdown',
|
|
149
|
+
schemaKey: '_descriptionMarkdown',
|
|
150
|
+
type: 'string',
|
|
151
|
+
rest: { visible: false },
|
|
152
|
+
}),
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
assert.deepEqual(resolved.children, []);
|
|
156
|
+
assert.deepEqual(resolved.diagnostics, [{ type: 'unknownField', value: '_descriptionMarkdown' }]);
|
|
157
|
+
});
|
package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.persister.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { SymbolBeanFullName } from 'zova';
|
|
|
7
7
|
import type { QueryMetaPersister } from '../../types/index.js';
|
|
8
8
|
|
|
9
9
|
import { CookieWrapper } from '../../common/cookieWrapper.js';
|
|
10
|
+
import { createQueryPersister } from '../../lib/queryPersister.js';
|
|
10
11
|
import { resolveMaxAgeTime } from '../../types/index.js';
|
|
11
12
|
import { BeanModelLast } from './bean.model.last.js';
|
|
12
13
|
|
|
@@ -145,13 +146,14 @@ export class BeanModelPersister extends BeanModelLast {
|
|
|
145
146
|
protected _createPersister(options?: QueryMetaPersister | boolean): any {
|
|
146
147
|
options = this._adjustPersisterOptions(options);
|
|
147
148
|
if (!options) return undefined;
|
|
148
|
-
|
|
149
|
+
const persisterFn = experimental_createQueryPersister({
|
|
149
150
|
storage: this._getPersisterStorage(options) as any,
|
|
150
151
|
maxAge: options.maxAge as number,
|
|
151
152
|
refetchOnRestore: options.refetchOnRestore,
|
|
152
153
|
prefix: options.prefix,
|
|
153
154
|
buster: options.buster,
|
|
154
155
|
}).persisterFn;
|
|
156
|
+
return createQueryPersister(persisterFn);
|
|
155
157
|
}
|
|
156
158
|
|
|
157
159
|
protected _adjustPersisterOptions(options?: QueryMetaPersister | boolean) {
|
package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useQuery.ts
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
DefaultError,
|
|
3
|
-
QueryClient,
|
|
4
|
-
QueryKey,
|
|
5
|
-
UseQueryDefinedReturnType,
|
|
6
|
-
UseQueryOptions,
|
|
7
|
-
UseQueryReturnType,
|
|
8
|
-
} from '@tanstack/vue-query';
|
|
9
|
-
import type { UnwrapNestedRefs } from 'vue';
|
|
1
|
+
import type { DefaultError, QueryClient, QueryKey, UseQueryOptions } from '@tanstack/vue-query';
|
|
10
2
|
|
|
11
3
|
import { checkErrorJwtExpired } from '@cabloy/utils';
|
|
12
4
|
import { useQuery } from '@tanstack/vue-query';
|
|
@@ -16,7 +8,9 @@ import type {
|
|
|
16
8
|
DefinedInitialQueryOptions,
|
|
17
9
|
UndefinedInitialQueryOptions,
|
|
18
10
|
} from '../../common/types.js';
|
|
11
|
+
import type { ModelUseQueryDefinedReturnType, ModelUseQueryReturnType } from '../../types/query.js';
|
|
19
12
|
|
|
13
|
+
import { createQueryRefetch } from '../../lib/queryRefetch.js';
|
|
20
14
|
import { resolveStaleTime } from '../../types/index.js';
|
|
21
15
|
import { BeanModelQuery } from './bean.model.query.js';
|
|
22
16
|
|
|
@@ -29,7 +23,7 @@ export class BeanModelUseQuery extends BeanModelQuery {
|
|
|
29
23
|
>(
|
|
30
24
|
options: UndefinedInitialQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
|
|
31
25
|
queryClient?: QueryClient,
|
|
32
|
-
):
|
|
26
|
+
): ModelUseQueryReturnType<TData, TError>;
|
|
33
27
|
$useQuery<
|
|
34
28
|
TQueryFnData = unknown,
|
|
35
29
|
TError = DefaultError,
|
|
@@ -38,7 +32,7 @@ export class BeanModelUseQuery extends BeanModelQuery {
|
|
|
38
32
|
>(
|
|
39
33
|
options: DefinedInitialQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
|
|
40
34
|
queryClient?: QueryClient,
|
|
41
|
-
):
|
|
35
|
+
): ModelUseQueryDefinedReturnType<TData, TError>;
|
|
42
36
|
$useQuery<
|
|
43
37
|
TQueryFnData = unknown,
|
|
44
38
|
TError = DefaultError,
|
|
@@ -47,7 +41,7 @@ export class BeanModelUseQuery extends BeanModelQuery {
|
|
|
47
41
|
>(
|
|
48
42
|
options: UseQueryOptions<TQueryFnData, TError, TData, TQueryFnData, TQueryKey>,
|
|
49
43
|
queryClient?: QueryClient,
|
|
50
|
-
):
|
|
44
|
+
): ModelUseQueryReturnType<TData, TError>;
|
|
51
45
|
$useQuery(options, queryClient) {
|
|
52
46
|
const queryKey = this.self._forceQueryKeyPrefix(options.queryKey);
|
|
53
47
|
const persister = this._createPersister(options.meta?.persister);
|
|
@@ -81,7 +75,11 @@ export class BeanModelUseQuery extends BeanModelQuery {
|
|
|
81
75
|
};
|
|
82
76
|
}
|
|
83
77
|
return this.ctx.util.instanceScope(() => {
|
|
84
|
-
|
|
78
|
+
const query = useQuery(options, queryClient) as unknown as ModelUseQueryReturnType<any, any>;
|
|
79
|
+
query.refetch = createQueryRefetch(query.refetch, () =>
|
|
80
|
+
this.$queryFind({ queryKey }),
|
|
81
|
+
) as typeof query.refetch;
|
|
82
|
+
return query;
|
|
85
83
|
});
|
|
86
84
|
}
|
|
87
85
|
}
|
package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useState.ts
CHANGED
|
@@ -4,11 +4,8 @@ import type {
|
|
|
4
4
|
QueryClient,
|
|
5
5
|
QueryKey,
|
|
6
6
|
QueryMeta,
|
|
7
|
-
UseQueryDefinedReturnType,
|
|
8
7
|
UseQueryOptions,
|
|
9
|
-
UseQueryReturnType,
|
|
10
8
|
} from '@tanstack/vue-query';
|
|
11
|
-
import type { UnwrapNestedRefs } from 'vue';
|
|
12
9
|
|
|
13
10
|
import { hashKey } from '@tanstack/vue-query';
|
|
14
11
|
import { deepExtend } from 'zova';
|
|
@@ -17,7 +14,12 @@ import type {
|
|
|
17
14
|
DefinedInitialQueryOptions,
|
|
18
15
|
UndefinedInitialQueryOptions,
|
|
19
16
|
} from '../../common/types.js';
|
|
20
|
-
import type {
|
|
17
|
+
import type {
|
|
18
|
+
ModelUseQueryDefinedReturnType,
|
|
19
|
+
ModelUseQueryReturnType,
|
|
20
|
+
QueryMetaPersister,
|
|
21
|
+
UseQueryComputedOptions,
|
|
22
|
+
} from '../../types/query.js';
|
|
21
23
|
|
|
22
24
|
import { BeanModelUseQuery } from './bean.model.useQuery.js';
|
|
23
25
|
|
|
@@ -311,7 +313,7 @@ export class BeanModelUseState extends BeanModelUseQuery {
|
|
|
311
313
|
>(
|
|
312
314
|
options: UndefinedInitialQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
|
|
313
315
|
queryClient?: QueryClient,
|
|
314
|
-
):
|
|
316
|
+
): ModelUseQueryReturnType<TData, TError>;
|
|
315
317
|
$useStateData<
|
|
316
318
|
TQueryFnData = unknown,
|
|
317
319
|
TError = DefaultError,
|
|
@@ -320,7 +322,7 @@ export class BeanModelUseState extends BeanModelUseQuery {
|
|
|
320
322
|
>(
|
|
321
323
|
options: DefinedInitialQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
|
|
322
324
|
queryClient?: QueryClient,
|
|
323
|
-
):
|
|
325
|
+
): ModelUseQueryDefinedReturnType<TData, TError>;
|
|
324
326
|
$useStateData<
|
|
325
327
|
TQueryFnData = unknown,
|
|
326
328
|
TError = DefaultError,
|
|
@@ -329,7 +331,7 @@ export class BeanModelUseState extends BeanModelUseQuery {
|
|
|
329
331
|
>(
|
|
330
332
|
options: UseQueryOptions<TQueryFnData, TError, TData, TQueryFnData, TQueryKey>,
|
|
331
333
|
queryClient?: QueryClient,
|
|
332
|
-
):
|
|
334
|
+
): ModelUseQueryReturnType<TData, TError>;
|
|
333
335
|
$useStateData(options, queryClient) {
|
|
334
336
|
const queryKey = this.self._forceQueryKeyPrefix(options.queryKey);
|
|
335
337
|
const queryHash = hashKey(queryKey);
|
|
@@ -1,17 +1,10 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
DefaultError,
|
|
3
|
-
QueryClient,
|
|
4
|
-
QueryKey,
|
|
5
|
-
UseQueryDefinedReturnType,
|
|
6
|
-
UseQueryOptions,
|
|
7
|
-
UseQueryReturnType,
|
|
8
|
-
} from '@tanstack/vue-query';
|
|
9
|
-
import type { UnwrapNestedRefs } from 'vue';
|
|
1
|
+
import type { DefaultError, QueryClient, QueryKey, UseQueryOptions } from '@tanstack/vue-query';
|
|
10
2
|
|
|
11
3
|
import type {
|
|
12
4
|
DefinedInitialQueryOptions,
|
|
13
5
|
UndefinedInitialQueryOptions,
|
|
14
6
|
} from '../../common/types.js';
|
|
7
|
+
import type { ModelUseQueryDefinedReturnType, ModelUseQueryReturnType } from '../../types/query.js';
|
|
15
8
|
import type { StateType } from '../../types/query.js';
|
|
16
9
|
|
|
17
10
|
import { BeanModelUseState } from './bean.model.useState.js';
|
|
@@ -56,7 +49,7 @@ export class BeanModelUseStateGeneral extends BeanModelUseState {
|
|
|
56
49
|
stateType: 'data',
|
|
57
50
|
options: UndefinedInitialQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
|
|
58
51
|
queryClient?: QueryClient,
|
|
59
|
-
):
|
|
52
|
+
): ModelUseQueryReturnType<TData, TError>;
|
|
60
53
|
$useState<
|
|
61
54
|
TQueryFnData = unknown,
|
|
62
55
|
TError = DefaultError,
|
|
@@ -66,7 +59,7 @@ export class BeanModelUseStateGeneral extends BeanModelUseState {
|
|
|
66
59
|
stateType: 'data',
|
|
67
60
|
options: DefinedInitialQueryOptions<TQueryFnData, TError, TData, TQueryKey>,
|
|
68
61
|
queryClient?: QueryClient,
|
|
69
|
-
):
|
|
62
|
+
): ModelUseQueryDefinedReturnType<TData, TError>;
|
|
70
63
|
$useState<
|
|
71
64
|
TQueryFnData = unknown,
|
|
72
65
|
TError = DefaultError,
|
|
@@ -76,7 +69,7 @@ export class BeanModelUseStateGeneral extends BeanModelUseState {
|
|
|
76
69
|
stateType: 'data',
|
|
77
70
|
options: UseQueryOptions<TQueryFnData, TError, TData, TQueryFnData, TQueryKey>,
|
|
78
71
|
queryClient?: QueryClient,
|
|
79
|
-
):
|
|
72
|
+
): ModelUseQueryReturnType<TData, TError>;
|
|
80
73
|
$useState(stateType: StateType, options, queryClient) {
|
|
81
74
|
switch (stateType) {
|
|
82
75
|
case 'localAsync':
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { QueryPersister } from '@tanstack/query-core';
|
|
2
|
+
|
|
3
|
+
// TanStack's FetchMeta is internal to the public QueryPersister call path.
|
|
4
|
+
type QueryFetchMeta = {
|
|
5
|
+
bypassPersister?: boolean;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export function createQueryPersister<T, TQueryKey extends readonly unknown[]>(
|
|
9
|
+
persisterFn: QueryPersister<T, TQueryKey>,
|
|
10
|
+
): QueryPersister<T, TQueryKey> {
|
|
11
|
+
return (queryFn, context, query) => {
|
|
12
|
+
if ((query.state.fetchMeta as QueryFetchMeta | null)?.bypassPersister === true) {
|
|
13
|
+
return queryFn(context);
|
|
14
|
+
}
|
|
15
|
+
return persisterFn(queryFn, context, query);
|
|
16
|
+
};
|
|
17
|
+
}
|