cabloy 5.1.60 → 5.1.62
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/.claude/hooks/contract-loop-gate.ts +296 -0
- package/.claude/settings.json +16 -0
- package/.claude/skills/cabloy-backend-scaffold/SKILL.md +2 -0
- package/.claude/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +1 -0
- package/.claude/skills/cabloy-contract-loop/SKILL.md +89 -16
- package/.claude/skills/cabloy-contract-loop/references/contract-loop-map.md +102 -14
- package/.claude/skills/cabloy-contract-loop/references/resource-custom-state-pattern.md +4 -0
- package/.claude/skills/cabloy-contract-loop/references/verification-checklist.md +32 -14
- package/.claude/skills/cabloy-domain-planning/SKILL.md +212 -0
- package/.claude/skills/cabloy-frontend-scaffold/SKILL.md +13 -0
- package/.claude/skills/cabloy-frontend-scaffold/references/follow-up-checklist.md +2 -0
- package/.claude/skills/cabloy-module-removal/SKILL.md +144 -0
- package/.claude/skills/cabloy-resource-field-update/SKILL.md +7 -0
- package/.claude/skills/cabloy-zova-source-reading/SKILL.md +221 -0
- package/.claude/skills/cabloy-zova-source-reading/references/analysis-modes.md +91 -0
- package/.claude/skills/cabloy-zova-source-reading/references/core-reading-paths.md +117 -0
- package/CHANGELOG.md +64 -0
- package/CLAUDE.md +11 -0
- package/cabloy-docs/.vitepress/config.mjs +197 -5
- package/cabloy-docs/ai/cli-to-skill-map.md +7 -0
- package/cabloy-docs/ai/docs-skills-rules-mapping.md +22 -0
- package/cabloy-docs/ai/future-skill-roadmap.md +12 -7
- package/cabloy-docs/ai/introduction.md +1 -0
- package/cabloy-docs/ai/playbook-backend-module.md +6 -0
- package/cabloy-docs/ai/playbook-module-removal.md +164 -0
- package/cabloy-docs/ai/skills.md +12 -0
- package/cabloy-docs/backend/backend-contract-emission-output-inspection.md +189 -0
- package/cabloy-docs/backend/backend-contract-emission-source-reading-map.md +160 -0
- package/cabloy-docs/backend/backend-contract-emission-specimen.md +170 -0
- package/cabloy-docs/backend/backend-resource-module-contract-chain.md +323 -0
- package/cabloy-docs/backend/backend-source-reading-debug-checklist.md +173 -0
- package/cabloy-docs/backend/backend-source-reading-roadmap.md +129 -0
- package/cabloy-docs/backend/backend-source-reading-verify-playbook.md +166 -0
- package/cabloy-docs/backend/bean-scene-authoring.md +4 -4
- package/cabloy-docs/backend/broadcast-guide.md +3 -3
- package/cabloy-docs/backend/cli.md +20 -11
- package/cabloy-docs/backend/config-guide.md +4 -4
- package/cabloy-docs/backend/controller-aop-guide.md +10 -10
- package/cabloy-docs/backend/controller-guide.md +12 -2
- package/cabloy-docs/backend/crud-workflow.md +7 -3
- package/cabloy-docs/backend/dto-guide.md +18 -2
- package/cabloy-docs/backend/dto-infer-generation.md +201 -25
- package/cabloy-docs/backend/election-guide.md +2 -2
- package/cabloy-docs/backend/entity-guide.md +30 -3
- package/cabloy-docs/backend/error-guide.md +3 -3
- package/cabloy-docs/backend/event-guide.md +4 -4
- package/cabloy-docs/backend/external-aop-guide.md +2 -2
- package/cabloy-docs/backend/field-indexes.md +9 -3
- package/cabloy-docs/backend/foundation.md +8 -8
- package/cabloy-docs/backend/i18n-guide.md +6 -6
- package/cabloy-docs/backend/internal-aop-guide.md +2 -2
- package/cabloy-docs/backend/introduction.md +15 -0
- package/cabloy-docs/backend/migration-and-changes.md +3 -3
- package/cabloy-docs/backend/model-guide.md +16 -6
- package/cabloy-docs/backend/openapi-guide.md +3 -0
- package/cabloy-docs/backend/queue-guide.md +3 -3
- package/cabloy-docs/backend/redlock-guide.md +2 -2
- package/cabloy-docs/backend/schedule-guide.md +2 -2
- package/cabloy-docs/backend/scripts.md +8 -0
- package/cabloy-docs/backend/serialization-guide.md +12 -2
- package/cabloy-docs/backend/service-guide.md +18 -9
- package/cabloy-docs/backend/startup-guide.md +5 -5
- package/cabloy-docs/backend/status-guide.md +271 -0
- package/cabloy-docs/backend/unit-testing.md +3 -3
- package/cabloy-docs/backend/vona-source-reading-map.md +157 -0
- package/cabloy-docs/backend/websocket-protocol-guide.md +5 -5
- package/cabloy-docs/backend/websocket-usage-guide.md +15 -8
- package/cabloy-docs/frontend/a-model-under-the-hood.md +281 -0
- package/cabloy-docs/frontend/a-openapi-under-the-hood.md +248 -0
- package/cabloy-docs/frontend/a-router-guide.md +307 -0
- package/cabloy-docs/frontend/api-guide.md +6 -4
- package/cabloy-docs/frontend/api-schema-guide.md +1 -0
- package/cabloy-docs/frontend/app-startup-guide.md +7 -4
- package/cabloy-docs/frontend/bean-scene-authoring.md +3 -1
- package/cabloy-docs/frontend/behavior-guide.md +16 -16
- package/cabloy-docs/frontend/cli.md +14 -2
- package/cabloy-docs/frontend/command-scene-authoring.md +504 -0
- package/cabloy-docs/frontend/component-guide.md +5 -5
- package/cabloy-docs/frontend/component-props-guide.md +1 -1
- package/cabloy-docs/frontend/component-v-model-guide.md +2 -2
- package/cabloy-docs/frontend/design-principles.md +6 -0
- package/cabloy-docs/frontend/fetch-interceptor-guide.md +440 -0
- package/cabloy-docs/frontend/filter-query-select-data-flow-guide.md +260 -0
- package/cabloy-docs/frontend/form-guide.md +786 -0
- package/cabloy-docs/frontend/form-scene-to-page-meta-guide.md +303 -0
- package/cabloy-docs/frontend/foundation.md +33 -0
- package/cabloy-docs/frontend/frontend-source-reading-roadmap.md +249 -0
- package/cabloy-docs/frontend/generated-contract-consumption-debug-checklist.md +190 -0
- package/cabloy-docs/frontend/generated-contract-consumption-entry-branch.md +205 -0
- package/cabloy-docs/frontend/generated-contract-consumption-list-branch.md +157 -0
- package/cabloy-docs/frontend/generated-contract-consumption-specimen.md +203 -0
- package/cabloy-docs/frontend/generated-contract-consumption-verify-playbook.md +189 -0
- package/cabloy-docs/frontend/generic-component-guide.md +1 -1
- package/cabloy-docs/frontend/introduction.md +38 -5
- package/cabloy-docs/frontend/ioc-and-beans.md +6 -0
- package/cabloy-docs/frontend/mock-guide.md +1 -0
- package/cabloy-docs/frontend/model-architecture.md +288 -39
- package/cabloy-docs/frontend/model-resource-best-practices.md +379 -0
- package/cabloy-docs/frontend/model-resource-cookbook.md +508 -0
- package/cabloy-docs/frontend/model-resource-internals-deep-dive.md +238 -0
- package/cabloy-docs/frontend/model-resource-owner-pattern.md +402 -0
- package/cabloy-docs/frontend/model-resource-usage-guide.md +334 -0
- package/cabloy-docs/frontend/model-state-guide.md +371 -15
- package/cabloy-docs/frontend/module-scope.md +8 -8
- package/cabloy-docs/frontend/modules-and-suites.md +2 -1
- package/cabloy-docs/frontend/navigation-guards-guide.md +7 -0
- package/cabloy-docs/frontend/openapi-sdk-guide.md +17 -6
- package/cabloy-docs/frontend/page-guide.md +15 -9
- package/cabloy-docs/frontend/page-meta-guide.md +466 -0
- package/cabloy-docs/frontend/page-params-guide.md +3 -3
- package/cabloy-docs/frontend/page-query-guide.md +2 -2
- package/cabloy-docs/frontend/page-route-guide.md +6 -0
- package/cabloy-docs/frontend/permission-formscene-action-visibility-guide.md +263 -0
- package/cabloy-docs/frontend/quickstart.md +18 -2
- package/cabloy-docs/frontend/reading-zova-for-vue-developers.md +266 -0
- package/cabloy-docs/frontend/resource-entry-page-deep-dive.md +271 -0
- package/cabloy-docs/frontend/resource-list-page-deep-dive.md +279 -0
- package/cabloy-docs/frontend/rest-resource-source-reading-map.md +522 -0
- package/cabloy-docs/frontend/rest-resource-under-the-hood.md +622 -0
- package/cabloy-docs/frontend/root-behaviors-guide.md +282 -0
- package/cabloy-docs/frontend/route-alias-guide.md +6 -0
- package/cabloy-docs/frontend/router-stack-guide.md +229 -0
- package/cabloy-docs/frontend/router-tabs-introduction.md +26 -3
- package/cabloy-docs/frontend/router-tabs-layout-integration.md +367 -0
- package/cabloy-docs/frontend/router-tabs-mechanism.md +6 -0
- package/cabloy-docs/frontend/router-tabs-route-meta-cookbook.md +7 -0
- package/cabloy-docs/frontend/router-tabs-vs-stack.md +167 -0
- package/cabloy-docs/frontend/router-view-hosts-guide.md +450 -0
- package/cabloy-docs/frontend/server-data.md +4 -1
- package/cabloy-docs/frontend/system-startup-guide.md +2 -2
- package/cabloy-docs/frontend/table-action-visibility-permission-flow-guide.md +263 -0
- package/cabloy-docs/frontend/table-cell-cookbook.md +568 -0
- package/cabloy-docs/frontend/table-guide.md +373 -0
- package/cabloy-docs/frontend/table-resource-crud-cookbook.md +496 -0
- package/cabloy-docs/frontend/zova-app-guide.md +251 -0
- package/cabloy-docs/frontend/zova-form-source-reading-map.md +293 -0
- package/cabloy-docs/frontend/zova-form-under-the-hood.md +561 -0
- package/cabloy-docs/frontend/zova-reactivity-under-the-hood.md +320 -0
- package/cabloy-docs/frontend/zova-router-under-the-hood.md +561 -0
- package/cabloy-docs/frontend/zova-source-reading-map.md +421 -0
- package/cabloy-docs/frontend/zova-table-controller-render-supplement.md +225 -0
- package/cabloy-docs/frontend/zova-table-source-reading-map.md +317 -0
- package/cabloy-docs/frontend/zova-table-under-the-hood.md +532 -0
- package/cabloy-docs/frontend/zova-vs-vue3-comparison.md +308 -0
- package/cabloy-docs/fullstack/backend-metadata-to-frontend-table-actions-debug-checklist.md +245 -0
- package/cabloy-docs/fullstack/backend-metadata-to-frontend-table-actions-source-reading-map.md +139 -0
- package/cabloy-docs/fullstack/backend-metadata-to-frontend-table-actions-verify-playbook.md +248 -0
- package/cabloy-docs/fullstack/backend-metadata-to-frontend-table-actions.md +511 -0
- package/cabloy-docs/fullstack/contract-loop-playbook.md +356 -0
- package/cabloy-docs/fullstack/edition-collaboration-differences.md +6 -0
- package/cabloy-docs/fullstack/frontend-metadata-to-backend.md +199 -23
- package/cabloy-docs/fullstack/introduction.md +15 -1
- package/cabloy-docs/fullstack/openapi-to-sdk.md +135 -11
- package/cabloy-docs/fullstack/suites-and-modules.md +333 -0
- package/cabloy-docs/fullstack/tutorial-1-first-module.md +3 -0
- package/cabloy-docs/fullstack/tutorial-2-first-crud.md +4 -0
- package/cabloy-docs/fullstack/tutorial-3-frontend-metadata-sharing.md +6 -2
- package/cabloy-docs/fullstack/tutorial-4-custom-level-renderers.md +60 -23
- package/cabloy-docs/fullstack/tutorial-5-backend-contract-sharing.md +14 -7
- package/cabloy-docs/fullstack/tutorial-6-one-contract-four-uses.md +6 -0
- package/cabloy-docs/fullstack/tutorials-overview.md +17 -4
- package/cabloy-docs/reference/bean-scene-boilerplates.md +15 -13
- package/cabloy-docs/reference/package-map.md +4 -3
- package/package.json +2 -1
- package/scripts/init.ts +2 -18
- package/scripts/initTestData.ts +25 -0
- package/scripts/upgrade.ts +17 -2
- package/vona/pnpm-lock.yaml +48 -194
- package/vona/src/suite/a-training/modules/training-student/package.json +53 -0
- package/vona/src/suite/a-training/modules/training-student/src/.metadata/index.ts +400 -0
- package/vona/src/suite/a-training/modules/training-student/src/.metadata/locales.ts +18 -0
- package/vona/src/suite/a-training/modules/training-student/src/.metadata/this.ts +2 -0
- package/vona/src/suite/a-training/modules/training-student/src/bean/meta.index.ts +12 -0
- package/vona/src/suite/a-training/modules/training-student/src/bean/meta.version.ts +21 -0
- package/vona/src/suite/a-training/modules/training-student/src/bean/ssrMenu.student.ts +29 -0
- package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +15 -0
- package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +15 -0
- package/vona/src/suite/a-training/modules/training-student/src/controller/student.ts +74 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx +28 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentSelectReq.tsx +44 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentSelectRes.tsx +11 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentSelectResItem.tsx +45 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentSummary.tsx +42 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentUpdate.tsx +28 -0
- package/vona/src/suite/a-training/modules/training-student/src/dto/studentView.tsx +25 -0
- package/vona/src/suite/a-training/modules/training-student/src/entity/student.tsx +84 -0
- package/vona/src/suite/a-training/modules/training-student/src/index.ts +2 -0
- package/vona/src/suite/a-training/modules/training-student/src/model/student.ts +10 -0
- package/vona/src/suite/a-training/modules/training-student/src/service/student.ts +57 -0
- package/vona/src/suite/a-training/modules/training-student/test/student.test.ts +173 -0
- package/vona/src/suite/a-training/modules/training-student/tsconfig.build.json +11 -0
- package/vona/src/suite/a-training/modules/training-student/tsconfig.json +7 -0
- package/vona/src/suite/a-training/package.json +12 -0
- package/vona/src/suite/a-training/tsconfig.base.json +4 -0
- package/vona/src/suite/a-training/tsconfig.json +10 -0
- package/zova/packages-cli/cli/package.json +2 -2
- package/zova/packages-cli/cli-set-front/cli/templates/openapi/config/boilerplate/module/openapi.config.ts +6 -1
- package/zova/packages-cli/cli-set-front/package.json +1 -1
- package/zova/packages-cli/cli-set-front/src/lib/bean/cli.openapi.generate.ts +34 -4
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/pnpm-lock.yaml +416 -690
- package/zova/src/suite/a-training/modules/training-student/cli/openapi.config.ts +9 -0
- package/zova/src/suite/a-training/modules/training-student/package.json +52 -0
- package/zova/src/suite/a-training/modules/training-student/src/.metadata/component/formFieldLevel.ts +31 -0
- package/zova/src/suite/a-training/modules/training-student/src/.metadata/index.ts +258 -0
- package/zova/src/suite/a-training/modules/training-student/src/.metadata/locales.ts +7 -0
- package/zova/src/suite/a-training/modules/training-student/src/.metadata/this.ts +2 -0
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/baseURL.ts +5 -0
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/index.ts +3 -0
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/schemas.ts +196 -0
- package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +4146 -0
- package/zova/src/suite/a-training/modules/training-student/src/api/trainingStudent.ts +151 -0
- package/zova/src/suite/a-training/modules/training-student/src/apiSchema/trainingStudent.ts +43 -0
- package/zova/src/suite/a-training/modules/training-student/src/bean/tableCell.actionDeleteForce.tsx +51 -0
- package/zova/src/suite/a-training/modules/training-student/src/bean/tableCell.actionSummary.tsx +56 -0
- package/zova/src/suite/a-training/modules/training-student/src/bean/tableCell.level.tsx +63 -0
- package/zova/src/suite/a-training/modules/training-student/src/component/formFieldLevel/controller.tsx +117 -0
- package/zova/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +9 -0
- package/zova/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +9 -0
- package/zova/src/suite/a-training/modules/training-student/src/index.ts +2 -0
- package/zova/src/suite/a-training/modules/training-student/src/model/student.ts +42 -0
- package/zova/src/suite/a-training/modules/training-student/tsconfig.build.json +13 -0
- package/zova/src/suite/a-training/modules/training-student/tsconfig.json +5 -0
- package/zova/src/suite/a-training/package.json +12 -0
- package/zova/src/suite/a-training/tsconfig.base.json +4 -0
- package/zova/src/suite/a-training/tsconfig.json +4 -0
- package/zova/src/suite/cabloy-basic/modules/basic-select/src/component/formFieldSelect/controller.tsx +29 -7
- package/zova/src/suite/cabloy-basic/modules/basic-select/src/component/select/controller.tsx +34 -11
- package/zova/src/suite-vendor/a-zova/modules/a-table/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-table/src/component/table/controller.tsx +3 -3
- package/zova/src/suite-vendor/a-zova/modules/a-table/src/lib/tableCell.ts +1 -1
- package/zova/src/suite-vendor/a-zova/package.json +2 -2
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
# Resource Model Cookbook
|
|
2
|
+
|
|
3
|
+
This cookbook collects common extension scenarios for resource-oriented models built on top of `ModelResource`.
|
|
4
|
+
|
|
5
|
+
It is the practical companion to:
|
|
6
|
+
|
|
7
|
+
- [Model Resource Owner Pattern](/frontend/model-resource-owner-pattern)
|
|
8
|
+
- [Using `ModelResource` in Your Module](/frontend/model-resource-usage-guide)
|
|
9
|
+
- [Resource Model Best Practices and Anti-Patterns](/frontend/model-resource-best-practices)
|
|
10
|
+
|
|
11
|
+
Use this page when you want implementation templates instead of only architectural explanation.
|
|
12
|
+
|
|
13
|
+
## Why this page exists
|
|
14
|
+
|
|
15
|
+
The earlier pages explain the architecture, application model, and review rules.
|
|
16
|
+
|
|
17
|
+
The next practical need is usually:
|
|
18
|
+
|
|
19
|
+
> show me the common cases I will actually implement.
|
|
20
|
+
|
|
21
|
+
This page focuses on that need.
|
|
22
|
+
|
|
23
|
+
## How to use this cookbook
|
|
24
|
+
|
|
25
|
+
Unless a recipe says otherwise, the examples below assume the thin-facade pattern from Recipe 1.
|
|
26
|
+
|
|
27
|
+
That means the business-facing model delegates resource ownership to `this.$$modelResource` instead of becoming another `ModelResource` owner itself.
|
|
28
|
+
|
|
29
|
+
For each recipe below:
|
|
30
|
+
|
|
31
|
+
1. identify whether the scenario is query-oriented, mutation-oriented, or invalidation-oriented
|
|
32
|
+
2. keep the resource semantics either on `ModelResource` directly or in a thin facade that delegates to it
|
|
33
|
+
3. reuse `queryItem(...)`, `mutationItem(...)`, `select(...)`, `view(...)`, and the existing key structure when possible
|
|
34
|
+
4. keep generic page or form blocks compatible unless you have a real reason to break that contract
|
|
35
|
+
|
|
36
|
+
## Forward-chain principle: prefer a thin business facade over a competing cache owner
|
|
37
|
+
|
|
38
|
+
In Cabloy’s bidirectional contract loop, this cookbook most often applies to the **forward chain**:
|
|
39
|
+
|
|
40
|
+
- backend contract truth changes first
|
|
41
|
+
- frontend API consumers are regenerated or refreshed from that truth
|
|
42
|
+
- frontend follow-up stays thin and semantic
|
|
43
|
+
|
|
44
|
+
For resource-oriented frontend work, that usually means:
|
|
45
|
+
|
|
46
|
+
> create a thin business model facade and reuse the existing resource-owner instead of creating a competing cache owner.
|
|
47
|
+
|
|
48
|
+
A strong pattern is:
|
|
49
|
+
|
|
50
|
+
- let `ModelResource` continue to own cache identity, list/item invalidation, and resource-level state
|
|
51
|
+
- let the business-facing model add semantic methods such as `summary(...)` or `deleteForce(...)`
|
|
52
|
+
- let custom cells, commands, or page controllers consume the thin business model facade, not invent a second owner for the same resource state
|
|
53
|
+
|
|
54
|
+
That pattern is especially important after forward-chain contract evolution, because it keeps generated or contract-aligned resource ownership centralized while still giving the business module a clean semantic API.
|
|
55
|
+
|
|
56
|
+
## Recipe 1: add a thin business facade over the existing resource-owner
|
|
57
|
+
|
|
58
|
+
### Use this when
|
|
59
|
+
|
|
60
|
+
The business module needs custom semantic methods, but the resource cache owner already exists and should remain authoritative.
|
|
61
|
+
|
|
62
|
+
Typical forward-chain case:
|
|
63
|
+
|
|
64
|
+
- backend adds a custom action such as `summary` or `deleteForce`
|
|
65
|
+
- frontend contract is regenerated or refreshed
|
|
66
|
+
- frontend needs a clean business-facing model method
|
|
67
|
+
- but should **not** create a second cache owner for the same resource
|
|
68
|
+
|
|
69
|
+
### Recommended shape
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
const StudentResource = 'training-student:student';
|
|
73
|
+
|
|
74
|
+
@Model()
|
|
75
|
+
export class ModelStudent extends BeanModelBase {
|
|
76
|
+
@Use({ beanFullName: 'rest-resource.model.resource' })
|
|
77
|
+
protected get $$modelResource(): ModelResource {
|
|
78
|
+
return usePrepareArg(StudentResource, true);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
summary(id: TableIdentity) {
|
|
82
|
+
return this.$$modelResource.queryItem({
|
|
83
|
+
id,
|
|
84
|
+
action: 'summary',
|
|
85
|
+
queryFn: async () => {
|
|
86
|
+
const res = await this.scope.api.trainingStudent.summary({ params: { id } });
|
|
87
|
+
return res ?? null;
|
|
88
|
+
},
|
|
89
|
+
meta: {
|
|
90
|
+
disableSuspenseOnInit: true,
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
deleteForce(id: TableIdentity) {
|
|
96
|
+
return this.$$modelResource.mutationItem<void, void>({
|
|
97
|
+
id,
|
|
98
|
+
action: 'deleteForce',
|
|
99
|
+
mutationFn: async () => {
|
|
100
|
+
await this.scope.api.trainingStudent.deleteForce({ params: { id } });
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Why this works well
|
|
108
|
+
|
|
109
|
+
- the business module gets a semantic model surface
|
|
110
|
+
- the existing `rest-resource` owner remains the single cache owner
|
|
111
|
+
- item keys, invalidation policy, and selector-based resource identity stay centralized
|
|
112
|
+
- custom UI code can call `ModelStudent.summary(...)` without rebuilding resource semantics locally
|
|
113
|
+
|
|
114
|
+
### Typical consumer shape
|
|
115
|
+
|
|
116
|
+
A custom cell or command can consume the thin facade and trigger the semantic method:
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
const modelStudent = (await ctx.bean._getBean(
|
|
120
|
+
'training-student.model.student',
|
|
121
|
+
true,
|
|
122
|
+
)) as ModelStudent;
|
|
123
|
+
const querySummary = modelStudent.summary(id);
|
|
124
|
+
const { data: summary } = await querySummary.refetch();
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Avoid
|
|
128
|
+
|
|
129
|
+
Do not create a second model that independently caches the same student resource list/item state just because a custom action was added.
|
|
130
|
+
|
|
131
|
+
That usually creates competing cache ownership and makes forward-chain maintenance harder.
|
|
132
|
+
|
|
133
|
+
## Recipe 2: inside that facade, add a row-level custom query
|
|
134
|
+
|
|
135
|
+
### Use this when
|
|
136
|
+
|
|
137
|
+
You already chose the thin-facade pattern from Recipe 1, and the business module needs more than the standard `view(id)` query.
|
|
138
|
+
|
|
139
|
+
Examples:
|
|
140
|
+
|
|
141
|
+
- summary detail
|
|
142
|
+
- audit detail
|
|
143
|
+
- timeline detail
|
|
144
|
+
- metrics detail
|
|
145
|
+
|
|
146
|
+
### Recommended shape
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
summary(id: TableIdentity) {
|
|
150
|
+
return this.$$modelResource.queryItem({
|
|
151
|
+
id,
|
|
152
|
+
action: 'summary',
|
|
153
|
+
queryFn: async () => {
|
|
154
|
+
const res = await this.scope.api.trainingStudent.summary({ params: { id } });
|
|
155
|
+
return res ?? null;
|
|
156
|
+
},
|
|
157
|
+
meta: {
|
|
158
|
+
disableSuspenseOnInit: true,
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Why this works well
|
|
165
|
+
|
|
166
|
+
- the thin facade adds business semantics without becoming a second owner
|
|
167
|
+
- row-level cache identity still stays under the existing resource-owner
|
|
168
|
+
- item-level key structure and selector-based isolation remain centralized in `rest-resource`
|
|
169
|
+
|
|
170
|
+
### Avoid
|
|
171
|
+
|
|
172
|
+
Do not rewrite this as an independent `$useStateData(...)` owner in the business model when the same row already belongs to the existing resource-owner.
|
|
173
|
+
|
|
174
|
+
## Recipe 3: inside that facade, add a list-level query variant
|
|
175
|
+
|
|
176
|
+
### Use this when
|
|
177
|
+
|
|
178
|
+
You already chose the thin-facade pattern from Recipe 1, and the business module needs a second list-style endpoint beyond the default `select(query)`.
|
|
179
|
+
|
|
180
|
+
Examples:
|
|
181
|
+
|
|
182
|
+
- archived list
|
|
183
|
+
- pending list
|
|
184
|
+
- dashboard list
|
|
185
|
+
- lightweight picker list
|
|
186
|
+
|
|
187
|
+
### Recommended shape
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
selectArchived(query?: ITableQuery) {
|
|
191
|
+
return this.$$modelResource.selectGeneral('archived', query);
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Why this works well
|
|
196
|
+
|
|
197
|
+
- the business-facing model stays thin
|
|
198
|
+
- list-level cache identity still belongs to the existing resource-owner
|
|
199
|
+
- list variants remain compatible with the same invalidation and selector semantics
|
|
200
|
+
|
|
201
|
+
### Avoid
|
|
202
|
+
|
|
203
|
+
Do not create another list owner with a parallel `keySelect(...)` convention when the list is still part of the same resource boundary.
|
|
204
|
+
|
|
205
|
+
## Recipe 4: inside that facade, add a row-level custom mutation
|
|
206
|
+
|
|
207
|
+
### Use this when
|
|
208
|
+
|
|
209
|
+
You already chose the thin-facade pattern from Recipe 1, and the business module needs a business action beyond create/update/delete.
|
|
210
|
+
|
|
211
|
+
Examples:
|
|
212
|
+
|
|
213
|
+
- deleteForce
|
|
214
|
+
- archive
|
|
215
|
+
- publish
|
|
216
|
+
- approve
|
|
217
|
+
- restore
|
|
218
|
+
|
|
219
|
+
### Recommended shape
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
deleteForce(id: TableIdentity) {
|
|
223
|
+
return this.$$modelResource.mutationItem<void, void>({
|
|
224
|
+
id,
|
|
225
|
+
action: 'deleteForce',
|
|
226
|
+
mutationFn: async () => {
|
|
227
|
+
await this.scope.api.trainingStudent.deleteForce({ params: { id } });
|
|
228
|
+
},
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Why this works well
|
|
234
|
+
|
|
235
|
+
- mutation ownership still stays under the existing resource-owner
|
|
236
|
+
- item/list invalidation remains centralized
|
|
237
|
+
- the business-facing model exposes semantic actions without competing for cache ownership
|
|
238
|
+
|
|
239
|
+
## Recipe 5: customize invalidation for a special mutation
|
|
240
|
+
|
|
241
|
+
### Use this when
|
|
242
|
+
|
|
243
|
+
A custom mutation should not follow the default invalidation strategy, but cache ownership should still remain inside the existing resource-owner.
|
|
244
|
+
|
|
245
|
+
### Recommended shape
|
|
246
|
+
|
|
247
|
+
```typescript
|
|
248
|
+
publish(id: TableIdentity) {
|
|
249
|
+
return this.$$modelResource.mutationItem<void, void>({
|
|
250
|
+
id,
|
|
251
|
+
action: 'publish',
|
|
252
|
+
invalidateSelect: false,
|
|
253
|
+
mutationFn: async () => {
|
|
254
|
+
await this.scope.api.trainingStudent.publish({ params: { id } });
|
|
255
|
+
},
|
|
256
|
+
onSuccess: async () => {
|
|
257
|
+
await this.$$modelResource.$invalidateQueries({ queryKey: ['select'] });
|
|
258
|
+
await this.$$modelResource.$invalidateQueries({ queryKey: ['item', id] });
|
|
259
|
+
await this.$$modelResource.$invalidateQueries({ queryKey: ['select', 'dashboard'] });
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Why this works well
|
|
266
|
+
|
|
267
|
+
- the existing resource-owner remains the source of truth for consistency rules
|
|
268
|
+
- special cache dependencies stay explicit
|
|
269
|
+
- pages do not need to remember hidden follow-up refetch rules
|
|
270
|
+
|
|
271
|
+
### Avoid
|
|
272
|
+
|
|
273
|
+
Do not spread this invalidation policy across page code, modal code, and button handlers.
|
|
274
|
+
|
|
275
|
+
## Recipe 6: expose permission-oriented helpers
|
|
276
|
+
|
|
277
|
+
### Use this when
|
|
278
|
+
|
|
279
|
+
Several screens need the same permission-based business decision.
|
|
280
|
+
|
|
281
|
+
Examples:
|
|
282
|
+
|
|
283
|
+
- can archive this resource?
|
|
284
|
+
- can publish this row?
|
|
285
|
+
- should a bulk action be visible?
|
|
286
|
+
|
|
287
|
+
### Recommended shape
|
|
288
|
+
|
|
289
|
+
```typescript
|
|
290
|
+
canArchive() {
|
|
291
|
+
return this.$computed(() => {
|
|
292
|
+
return !!this.$$modelResource.permissions?.actions?.archive;
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Or row-oriented logic:
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
canPublishRow(row: Entity) {
|
|
301
|
+
return !!this.$$modelResource.permissions?.actions?.publish && row.status === 'draft';
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### Why this works well
|
|
306
|
+
|
|
307
|
+
- permission semantics stay close to the existing resource owner
|
|
308
|
+
- multiple pages reuse the same rule
|
|
309
|
+
- UI code gets simpler and more consistent
|
|
310
|
+
|
|
311
|
+
## Recipe 7: expose schema-oriented convenience helpers
|
|
312
|
+
|
|
313
|
+
### Use this when
|
|
314
|
+
|
|
315
|
+
Multiple forms or blocks need the same schema-based interpretation.
|
|
316
|
+
|
|
317
|
+
Examples:
|
|
318
|
+
|
|
319
|
+
- whether a field should be hidden in a certain scene
|
|
320
|
+
- whether a resource uses a special title field
|
|
321
|
+
- default schema chosen for a custom scene
|
|
322
|
+
|
|
323
|
+
### Recommended shape
|
|
324
|
+
|
|
325
|
+
```typescript
|
|
326
|
+
getTitleField() {
|
|
327
|
+
return this.$computed(() => {
|
|
328
|
+
return this.$$modelResource.schemaRow?.properties?.title ? 'title' : 'name';
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Why this works well
|
|
334
|
+
|
|
335
|
+
- schema interpretation stays reusable
|
|
336
|
+
- pages do not duplicate metadata reading logic
|
|
337
|
+
- metadata ownership still stays under the existing resource-owner
|
|
338
|
+
|
|
339
|
+
## Recipe 8: add custom default form data
|
|
340
|
+
|
|
341
|
+
### Use this when
|
|
342
|
+
|
|
343
|
+
Create forms need richer default values than the generic schema default alone.
|
|
344
|
+
|
|
345
|
+
### Recommended shape
|
|
346
|
+
|
|
347
|
+
```typescript
|
|
348
|
+
getCreateDefaultData() {
|
|
349
|
+
const data = this.$$modelResource.getQueryDataDefaultValue(this.$$modelResource.schemaCreate) ?? {};
|
|
350
|
+
return {
|
|
351
|
+
...data,
|
|
352
|
+
status: 'draft',
|
|
353
|
+
enabled: true,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Then integrate it through the business-facing form helper:
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
getFormData(formMeta: IFormMeta, id?: TableIdentity) {
|
|
362
|
+
if (formMeta.formMode === 'edit' && formMeta.editMode === 'create') {
|
|
363
|
+
return this.getCreateDefaultData();
|
|
364
|
+
}
|
|
365
|
+
return this.$$modelResource.getFormData(formMeta, id);
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### Why this works well
|
|
370
|
+
|
|
371
|
+
- create defaults stay resource-owned
|
|
372
|
+
- form behavior remains consistent across entry points
|
|
373
|
+
- the thin facade only adds semantic customization on top of the existing form flow
|
|
374
|
+
|
|
375
|
+
## Recipe 9: add batch action support
|
|
376
|
+
|
|
377
|
+
### Use this when
|
|
378
|
+
|
|
379
|
+
The resource supports multi-row operations.
|
|
380
|
+
|
|
381
|
+
Examples:
|
|
382
|
+
|
|
383
|
+
- batch delete
|
|
384
|
+
- batch archive
|
|
385
|
+
- batch enable/disable
|
|
386
|
+
|
|
387
|
+
### Recommended shape
|
|
388
|
+
|
|
389
|
+
```typescript
|
|
390
|
+
batchArchive(ids: TableIdentity[]) {
|
|
391
|
+
return this.$$modelResource.$useMutationData<void, TableIdentity[]>({
|
|
392
|
+
mutationKey: ['batchArchive'],
|
|
393
|
+
mutationFn: async ids => {
|
|
394
|
+
await this.scope.api.trainingStudent.batchArchive({ ids });
|
|
395
|
+
},
|
|
396
|
+
onSuccess: async () => {
|
|
397
|
+
await this.$$modelResource.$invalidateQueries({ queryKey: ['select'] });
|
|
398
|
+
},
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### Why this works well
|
|
404
|
+
|
|
405
|
+
- batch behavior stays modeled explicitly
|
|
406
|
+
- list invalidation policy remains visible
|
|
407
|
+
- cache ownership still stays with the existing resource-owner even when row-level helpers are not the right fit
|
|
408
|
+
|
|
409
|
+
## Recipe 10: keep generic blocks working while adding resource semantics
|
|
410
|
+
|
|
411
|
+
### Use this when
|
|
412
|
+
|
|
413
|
+
You want richer resource behavior but still rely on generic blocks such as page or page-entry consumers.
|
|
414
|
+
|
|
415
|
+
### Recommended approach
|
|
416
|
+
|
|
417
|
+
- preserve stable surfaces like `select`, `view`, `schemaFilter`, `schemaRow`, `getFormSchema`, and `getFormData`
|
|
418
|
+
- add new methods around those stable surfaces instead of replacing them
|
|
419
|
+
- keep custom semantics additive where possible
|
|
420
|
+
|
|
421
|
+
### Why this works well
|
|
422
|
+
|
|
423
|
+
You get stronger business behavior without sacrificing reusable infrastructure.
|
|
424
|
+
|
|
425
|
+
## Recipe 11: move repeated page logic back into the model
|
|
426
|
+
|
|
427
|
+
### Use this when
|
|
428
|
+
|
|
429
|
+
You notice the same resource-specific logic appearing across several page controllers.
|
|
430
|
+
|
|
431
|
+
Typical signals:
|
|
432
|
+
|
|
433
|
+
- repeated action visibility checks
|
|
434
|
+
- repeated fetches to the same custom endpoint
|
|
435
|
+
- repeated default-form preparation
|
|
436
|
+
- repeated manual invalidation after the same mutation
|
|
437
|
+
|
|
438
|
+
### Recommended refactor direction
|
|
439
|
+
|
|
440
|
+
1. identify the repeated resource semantic
|
|
441
|
+
2. add one model method or computed helper for it
|
|
442
|
+
3. update pages to consume that stable model surface
|
|
443
|
+
4. keep the invalidation or schema logic centralized in the model
|
|
444
|
+
|
|
445
|
+
### Why this works well
|
|
446
|
+
|
|
447
|
+
It restores the resource-owner boundary and reduces drift.
|
|
448
|
+
|
|
449
|
+
## Recipe 12: start direct, then add a thin facade only when semantics appear
|
|
450
|
+
|
|
451
|
+
### Use this when
|
|
452
|
+
|
|
453
|
+
A resource does not yet need custom business methods, but you expect it may later.
|
|
454
|
+
|
|
455
|
+
### Recommended approach
|
|
456
|
+
|
|
457
|
+
- start with direct `ModelResource` usage
|
|
458
|
+
- let generic pages or forms consume the existing resource-owner surface
|
|
459
|
+
- add a thin business facade only when the UI really needs semantic methods such as `summary(...)` or `deleteForce(...)`
|
|
460
|
+
|
|
461
|
+
### Why this works well
|
|
462
|
+
|
|
463
|
+
- the programming model stays more uniform
|
|
464
|
+
- you avoid premature wrapper growth
|
|
465
|
+
- later business semantics can still be added without changing the single-owner boundary
|
|
466
|
+
|
|
467
|
+
## Recipe 13: decide whether logic belongs in model or page
|
|
468
|
+
|
|
469
|
+
### Put it in the model when
|
|
470
|
+
|
|
471
|
+
- it is resource semantics
|
|
472
|
+
- multiple pages should share it
|
|
473
|
+
- it affects query or mutation ownership
|
|
474
|
+
- it affects invalidation or form/schema decisions
|
|
475
|
+
|
|
476
|
+
### Keep it in the page when
|
|
477
|
+
|
|
478
|
+
- it is only local presentation flow
|
|
479
|
+
- it has no resource reuse value
|
|
480
|
+
- it is temporary UI behavior with no resource meaning
|
|
481
|
+
|
|
482
|
+
### Quick test
|
|
483
|
+
|
|
484
|
+
Ask:
|
|
485
|
+
|
|
486
|
+
> if another screen reused this resource, should it inherit the same rule?
|
|
487
|
+
|
|
488
|
+
If yes, the logic probably belongs in the model.
|
|
489
|
+
|
|
490
|
+
## A compact cookbook workflow
|
|
491
|
+
|
|
492
|
+
When adding a new resource behavior, use this decision order:
|
|
493
|
+
|
|
494
|
+
1. Is this list-level, row-level, batch-level, or form-level?
|
|
495
|
+
2. Can I reuse `selectGeneral`, `queryItem`, or `mutationItem`?
|
|
496
|
+
3. Does the behavior need custom invalidation?
|
|
497
|
+
4. Should generic pages or forms keep working unchanged?
|
|
498
|
+
5. Is this resource semantics or only page presentation?
|
|
499
|
+
|
|
500
|
+
That sequence usually leads to a cleaner implementation.
|
|
501
|
+
|
|
502
|
+
## Final takeaway
|
|
503
|
+
|
|
504
|
+
The most useful cookbook habit is simple:
|
|
505
|
+
|
|
506
|
+
> when a resource behavior repeats, give it a named model-owned surface instead of letting pages rediscover it independently.
|
|
507
|
+
|
|
508
|
+
That is how a resource model grows in a clean and Zova-native way.
|