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,238 @@
|
|
|
1
|
+
# ModelResource Internals Deep Dive
|
|
2
|
+
|
|
3
|
+
This guide explains the internals of `ModelResource` in Zova through the current public Cabloy Basic source.
|
|
4
|
+
|
|
5
|
+
Use this page when you want to understand:
|
|
6
|
+
|
|
7
|
+
- how `ModelResource` bootstraps itself
|
|
8
|
+
- how `resourceApi` is resolved
|
|
9
|
+
- where permissions, schemas, and form-provider surfaces come from
|
|
10
|
+
- how query, mutation, and form helpers are organized
|
|
11
|
+
- how query-key prefixing and invalidation work
|
|
12
|
+
- why `ModelResource` is the stable resource-owner boundary under both list and entry pages
|
|
13
|
+
|
|
14
|
+
## Why this page exists
|
|
15
|
+
|
|
16
|
+
This page is the third layer of a small source-reading chain around same-resource model facades:
|
|
17
|
+
|
|
18
|
+
1. [Model Architecture](/frontend/model-architecture) for the broader role of Zova Model
|
|
19
|
+
2. [Generated Contract Consumption: Entry Branch](/frontend/generated-contract-consumption-entry-branch) for the consumer-side handoff into the owner
|
|
20
|
+
3. this page for the owner internals that make that handoff work
|
|
21
|
+
|
|
22
|
+
Several existing docs already explain the meaning and usage of the resource owner well:
|
|
23
|
+
|
|
24
|
+
- [Model Resource Owner Pattern](/frontend/model-resource-owner-pattern) explains the architecture and why the owner boundary exists
|
|
25
|
+
- [Using `ModelResource` in Your Module](/frontend/model-resource-usage-guide) explains application-level usage patterns
|
|
26
|
+
- [Resource Model Best Practices](/frontend/model-resource-best-practices) and [Resource Model Cookbook](/frontend/model-resource-cookbook) explain review and implementation guidance
|
|
27
|
+
- [Rest Resource Under the Hood](/frontend/rest-resource-under-the-hood) explains the module/runtime bridge around the owner
|
|
28
|
+
|
|
29
|
+
What those pages do not isolate directly is the one source-first path through `ModelResource` itself.
|
|
30
|
+
|
|
31
|
+
That is the gap this page fills.
|
|
32
|
+
|
|
33
|
+
## The shortest accurate mental model
|
|
34
|
+
|
|
35
|
+
A practical mental model is:
|
|
36
|
+
|
|
37
|
+
1. `ModelResource` is a selector-backed owner keyed by `resource`
|
|
38
|
+
2. bootstrap resolves the final `resourceApi`
|
|
39
|
+
3. the owner exposes computed surfaces for permissions, schema, and form provider
|
|
40
|
+
4. query helpers wrap the shared model/query runtime for select/view/item-style fetches
|
|
41
|
+
5. mutation helpers wrap the shared model/query runtime for create/update/delete behavior
|
|
42
|
+
6. form helpers let pages choose schema/data/mutation behavior from `formMeta`
|
|
43
|
+
7. query keys and invalidation are centralized as part of the owner contract
|
|
44
|
+
|
|
45
|
+
That means `ModelResource` is not only a fetch helper. It is the stable resource semantics boundary for the frontend.
|
|
46
|
+
|
|
47
|
+
## Source-confirmed reading path
|
|
48
|
+
|
|
49
|
+
When reading this topic, use this order:
|
|
50
|
+
|
|
51
|
+
1. `zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts`
|
|
52
|
+
2. `zova/src/suite-vendor/a-zova/modules/a-openapi/src/model/sdk.ts`
|
|
53
|
+
3. `zova/packages-zova/zova-core/src/core/sys/util.ts`
|
|
54
|
+
4. `zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useQuery.ts`
|
|
55
|
+
5. `zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.query.ts`
|
|
56
|
+
6. `zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.persister.ts`
|
|
57
|
+
7. `zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/resource/controller.tsx`
|
|
58
|
+
8. `zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/entry/controller.tsx`
|
|
59
|
+
|
|
60
|
+
That order moves from the owner core, to the OpenAPI model it relies on, to path/config helpers, to query/persister behavior, and finally to the clearest current consumers.
|
|
61
|
+
|
|
62
|
+
## Initialization and bootstrap
|
|
63
|
+
|
|
64
|
+
The owner core lives in:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The first source-confirmed facts are:
|
|
71
|
+
|
|
72
|
+
- `@Model({ enableSelector: true })`
|
|
73
|
+
- `__init__(resource)` requires a concrete `resource`
|
|
74
|
+
- bootstrap happens before downstream consumers use the owner surfaces
|
|
75
|
+
|
|
76
|
+
This matters because `ModelResource` is not one global singleton.
|
|
77
|
+
|
|
78
|
+
It is a selector-backed owner whose runtime identity is the current resource name.
|
|
79
|
+
|
|
80
|
+
## `resourceApi` resolution
|
|
81
|
+
|
|
82
|
+
The bootstrap path uses:
|
|
83
|
+
|
|
84
|
+
- `this.$sdk.getBootstrap(this.resource)`
|
|
85
|
+
- `this.sys.util.parseResourceApi(resource, api)`
|
|
86
|
+
|
|
87
|
+
The path/config helper lives in:
|
|
88
|
+
|
|
89
|
+
```text
|
|
90
|
+
zova/packages-zova/zova-core/src/core/sys/util.ts
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
This confirms the owner flow:
|
|
94
|
+
|
|
95
|
+
- bootstrap data may override the final API path
|
|
96
|
+
- otherwise the resource name is translated into a default controller/action API path
|
|
97
|
+
|
|
98
|
+
That is why `resourceApi` should be treated as an owner-level resolved surface, not as one hard-coded string duplicated across pages.
|
|
99
|
+
|
|
100
|
+
## Metadata surfaces exposed by the owner
|
|
101
|
+
|
|
102
|
+
Inside `ModelResource`, the owner computes and exposes:
|
|
103
|
+
|
|
104
|
+
- `permissions`
|
|
105
|
+
- `formProvider`
|
|
106
|
+
- `schemaView`
|
|
107
|
+
- `schemaCreate`
|
|
108
|
+
- `schemaUpdate`
|
|
109
|
+
- `schemaFilter`
|
|
110
|
+
- `schemaRow`
|
|
111
|
+
- `schemaPages`
|
|
112
|
+
|
|
113
|
+
These surfaces are derived from:
|
|
114
|
+
|
|
115
|
+
- the bootstrap/runtime resource context
|
|
116
|
+
- the OpenAPI SDK/schema helpers
|
|
117
|
+
|
|
118
|
+
This is the clearest source-confirmed reason why both list pages and entry pages reuse the same owner: schema, permission, and provider surfaces belong to one resource boundary.
|
|
119
|
+
|
|
120
|
+
## Query helper layer
|
|
121
|
+
|
|
122
|
+
The query helpers are organized around:
|
|
123
|
+
|
|
124
|
+
- `selectGeneral(actionPath?, query?)`
|
|
125
|
+
- `select(query?)`
|
|
126
|
+
- `queryItem(...)`
|
|
127
|
+
- `view(id)`
|
|
128
|
+
|
|
129
|
+
`select(query?)` is only a thin wrapper over `selectGeneral(undefined, query)`.
|
|
130
|
+
|
|
131
|
+
The important source-confirmed detail is that the select key includes a hash of the query object:
|
|
132
|
+
|
|
133
|
+
- `['select', actionPath ?? '', hashkey(query)]`
|
|
134
|
+
|
|
135
|
+
That means query identity is deliberate and owner-controlled.
|
|
136
|
+
|
|
137
|
+
This is the owner-side boundary for list/query fetches, not the page shell.
|
|
138
|
+
|
|
139
|
+
## Mutation helper layer
|
|
140
|
+
|
|
141
|
+
The mutation helpers are organized around:
|
|
142
|
+
|
|
143
|
+
- `create()`
|
|
144
|
+
- `mutationItem(...)`
|
|
145
|
+
- `update(id)`
|
|
146
|
+
- `delete(id)`
|
|
147
|
+
|
|
148
|
+
The most important source-confirmed behavior is:
|
|
149
|
+
|
|
150
|
+
- select queries are invalidated after successful mutations
|
|
151
|
+
- item-root queries are invalidated for the specific row identity
|
|
152
|
+
|
|
153
|
+
That means mutation helpers do not only perform network requests. They also own the default resource-level invalidation policy.
|
|
154
|
+
|
|
155
|
+
## Form helper layer
|
|
156
|
+
|
|
157
|
+
The form-facing helpers are:
|
|
158
|
+
|
|
159
|
+
- `getFormSchema(formMeta)`
|
|
160
|
+
- `getFormApiSchemas(formMeta)`
|
|
161
|
+
- `getFormMutationSubmit(formMeta, id?)`
|
|
162
|
+
- `getFormData(formMeta, id?)`
|
|
163
|
+
- `getQueryDataDefaultValue(schemaName?)`
|
|
164
|
+
|
|
165
|
+
This is the clearest source-confirmed bridge from resource owner to entry-page form runtime.
|
|
166
|
+
|
|
167
|
+
A practical reading rule is:
|
|
168
|
+
|
|
169
|
+
- list pages mainly consume `schemaFilter`, `schemaRow`, and `select(...)`
|
|
170
|
+
- entry pages mainly consume `formMeta`-driven form helpers
|
|
171
|
+
|
|
172
|
+
## Query-key prefixing and invalidation behavior
|
|
173
|
+
|
|
174
|
+
The shared model/query behavior lives in:
|
|
175
|
+
|
|
176
|
+
```text
|
|
177
|
+
zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useQuery.ts
|
|
178
|
+
zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.query.ts
|
|
179
|
+
zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.persister.ts
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The important source-confirmed behavior is:
|
|
183
|
+
|
|
184
|
+
- owner query keys are prefixed with bean full name
|
|
185
|
+
- selector-backed models also prefix by selector
|
|
186
|
+
- persister/query behavior therefore treats one resource owner as a distinct query namespace
|
|
187
|
+
|
|
188
|
+
This is why `ModelResource` query identity is stable even when multiple resources or multiple selectors exist at once.
|
|
189
|
+
|
|
190
|
+
## Current consumers that prove the contract
|
|
191
|
+
|
|
192
|
+
The clearest current consumers are:
|
|
193
|
+
|
|
194
|
+
- `rest-resource/src/page/resource/controller.tsx`
|
|
195
|
+
- `rest-resource/src/page/entry/controller.tsx`
|
|
196
|
+
|
|
197
|
+
These page shells prove that:
|
|
198
|
+
|
|
199
|
+
- list pages consume the owner for top-level list/schema/runtime entry
|
|
200
|
+
- entry pages consume the owner for form/provider/schema/runtime entry
|
|
201
|
+
|
|
202
|
+
That is enough to verify that `ModelResource` is the shared owner beneath both branches.
|
|
203
|
+
|
|
204
|
+
## What this guide does not re-explain
|
|
205
|
+
|
|
206
|
+
This page does **not** fully re-explain:
|
|
207
|
+
|
|
208
|
+
- why the resource owner pattern exists -> see [Model Resource Owner Pattern](/frontend/model-resource-owner-pattern)
|
|
209
|
+
- application-level facade and usage guidance -> see [Using `ModelResource` in Your Module](/frontend/model-resource-usage-guide)
|
|
210
|
+
- the full list-page runtime -> see [Resource List Page Deep Dive](/frontend/resource-list-page-deep-dive)
|
|
211
|
+
- the full entry-page runtime -> see [Resource Entry Page Deep Dive](/frontend/resource-entry-page-deep-dive)
|
|
212
|
+
|
|
213
|
+
Its job is only to explain the owner internals and their current consumer contract.
|
|
214
|
+
|
|
215
|
+
## Where to read next
|
|
216
|
+
|
|
217
|
+
Use these next steps depending on your question:
|
|
218
|
+
|
|
219
|
+
- if you want to step back to the broader model role, return to [Model Architecture](/frontend/model-architecture)
|
|
220
|
+
- if you want to step back to the consumer-side handoff, return to [Generated Contract Consumption: Entry Branch](/frontend/generated-contract-consumption-entry-branch)
|
|
221
|
+
- if you want the list-page runtime branch, read [Resource List Page Deep Dive](/frontend/resource-list-page-deep-dive)
|
|
222
|
+
- if you want the entry-page runtime branch, read [Resource Entry Page Deep Dive](/frontend/resource-entry-page-deep-dive)
|
|
223
|
+
- if you want the owner-pattern explanation, read [Model Resource Owner Pattern](/frontend/model-resource-owner-pattern)
|
|
224
|
+
- if you want application-level usage/facade rules, read [Using `ModelResource` in Your Module](/frontend/model-resource-usage-guide)
|
|
225
|
+
|
|
226
|
+
## Final takeaway
|
|
227
|
+
|
|
228
|
+
The most accurate way to read `ModelResource` is not as one generic CRUD helper.
|
|
229
|
+
|
|
230
|
+
Read it as the selector-backed resource owner that:
|
|
231
|
+
|
|
232
|
+
- bootstraps and resolves `resourceApi`
|
|
233
|
+
- exposes schema, permission, and provider surfaces
|
|
234
|
+
- owns query, mutation, and form helper boundaries
|
|
235
|
+
- defines query-key and invalidation behavior
|
|
236
|
+
- remains the stable resource semantics layer beneath both list pages and entry pages
|
|
237
|
+
|
|
238
|
+
That is the source-confirmed role of `ModelResource` in the current Cabloy Basic frontend architecture.
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
# Model Resource Owner Pattern
|
|
2
|
+
|
|
3
|
+
This guide explains the resource-owner pattern built on top of Zova Model.
|
|
4
|
+
|
|
5
|
+
It uses the current `rest-resource` model as the main source specimen:
|
|
6
|
+
|
|
7
|
+
- `zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts`
|
|
8
|
+
|
|
9
|
+
Read [Model Architecture](/frontend/model-architecture) and [Model State Guide](/frontend/model-state-guide) first if you want the broader Model runtime and helper surface.
|
|
10
|
+
|
|
11
|
+
If your next question is how `ModelResource` itself works internally at the source level, continue with [ModelResource Internals Deep Dive](/frontend/model-resource-internals-deep-dive).
|
|
12
|
+
|
|
13
|
+
If your next question is how the generic lower-level model runtime works beneath `ModelResource`, continue with [Model Runtime Under the Hood](/frontend/a-model-under-the-hood).
|
|
14
|
+
|
|
15
|
+
If your next question is how the whole `rest-resource` module works at runtime beyond the model itself, continue with [Rest Resource Under the Hood](/frontend/rest-resource-under-the-hood).
|
|
16
|
+
|
|
17
|
+
If your next question is which files to read next for that runtime, continue with [Rest Resource Source Reading Map](/frontend/rest-resource-source-reading-map).
|
|
18
|
+
|
|
19
|
+
If your next question is how to apply this pattern in your own module, continue with [Using `ModelResource` in Your Module](/frontend/model-resource-usage-guide).
|
|
20
|
+
|
|
21
|
+
If your next question is how to review or constrain that pattern in a larger codebase, continue with [Resource Model Best Practices and Anti-Patterns](/frontend/model-resource-best-practices).
|
|
22
|
+
|
|
23
|
+
If your next question is which common extension shapes to implement first, continue with [Resource Model Cookbook](/frontend/model-resource-cookbook).
|
|
24
|
+
|
|
25
|
+
> [!TIP]
|
|
26
|
+
> **Resource docs path**
|
|
27
|
+
>
|
|
28
|
+
> 1. **[Model Resource Owner Pattern](/frontend/model-resource-owner-pattern)** — learn why `ModelResource` is a resource owner
|
|
29
|
+
> 2. **[Rest Resource Under the Hood](/frontend/rest-resource-under-the-hood)** — learn how the module runtime pieces cooperate
|
|
30
|
+
> 3. **[Rest Resource Source Reading Map](/frontend/rest-resource-source-reading-map)** — learn which files to read next
|
|
31
|
+
> 4. **[Using `ModelResource` in Your Module](/frontend/model-resource-usage-guide)** — learn how to reuse the owner in application code
|
|
32
|
+
> 5. **[Resource Model Best Practices](/frontend/model-resource-best-practices)** — learn the review guardrails
|
|
33
|
+
> 6. **[Resource Model Cookbook](/frontend/model-resource-cookbook)** — learn the common implementation shapes
|
|
34
|
+
>
|
|
35
|
+
> **You are here:** step 1.
|
|
36
|
+
> **Next recommended page:** [Rest Resource Under the Hood](/frontend/rest-resource-under-the-hood).
|
|
37
|
+
|
|
38
|
+
## Why this page exists
|
|
39
|
+
|
|
40
|
+
Many examples of Zova Model start from a small feature model:
|
|
41
|
+
|
|
42
|
+
- one list query
|
|
43
|
+
- one item query
|
|
44
|
+
- one or two mutations
|
|
45
|
+
|
|
46
|
+
Those examples are useful, but they can accidentally make Model look smaller than it really is.
|
|
47
|
+
|
|
48
|
+
The `rest-resource` specimen shows a larger pattern:
|
|
49
|
+
|
|
50
|
+
> a model can become the stable owner of one whole resource domain boundary.
|
|
51
|
+
|
|
52
|
+
That means the model is no longer only a query wrapper.
|
|
53
|
+
|
|
54
|
+
It becomes the place that owns:
|
|
55
|
+
|
|
56
|
+
- resource bootstrap
|
|
57
|
+
- schema access
|
|
58
|
+
- permissions access
|
|
59
|
+
- form integration
|
|
60
|
+
- query state
|
|
61
|
+
- mutation state
|
|
62
|
+
- cache invalidation policy
|
|
63
|
+
|
|
64
|
+
This page explains that pattern explicitly.
|
|
65
|
+
|
|
66
|
+
## What “resource owner” means in Zova
|
|
67
|
+
|
|
68
|
+
In this context, a resource-owner model is a model bean that becomes the reusable frontend boundary for one backend resource.
|
|
69
|
+
|
|
70
|
+
It is responsible for presenting one coherent surface to the rest of the UI.
|
|
71
|
+
|
|
72
|
+
Instead of asking every page, table, form, and action to individually know:
|
|
73
|
+
|
|
74
|
+
- how to resolve the resource API path
|
|
75
|
+
- how to fetch list data
|
|
76
|
+
- how to fetch row data
|
|
77
|
+
- how to submit create/update/delete requests
|
|
78
|
+
- how to obtain schema metadata
|
|
79
|
+
- how to choose invalidation rules
|
|
80
|
+
|
|
81
|
+
those decisions are centralized inside the model.
|
|
82
|
+
|
|
83
|
+
A practical reading takeaway is:
|
|
84
|
+
|
|
85
|
+
> the resource-owner model is the frontend resource facade.
|
|
86
|
+
|
|
87
|
+
## The source specimen
|
|
88
|
+
|
|
89
|
+
The main example is:
|
|
90
|
+
|
|
91
|
+
- `zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts`
|
|
92
|
+
|
|
93
|
+
This class is generic:
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
export class ModelResource<
|
|
97
|
+
Entity = any,
|
|
98
|
+
EntityCreate = Partial<Entity>,
|
|
99
|
+
EntityUpdate = Partial<Entity>,
|
|
100
|
+
> extends BeanModelBase {}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
That already tells you something important.
|
|
104
|
+
|
|
105
|
+
This model is designed to be reused across many resource types rather than tied to one page.
|
|
106
|
+
|
|
107
|
+
## Why `enableSelector` is essential
|
|
108
|
+
|
|
109
|
+
The class is decorated like this:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
@Model<IModelOptionsResource>({
|
|
113
|
+
enableSelector: true,
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
This matters because one generic `ModelResource` class needs to serve many different resources.
|
|
118
|
+
|
|
119
|
+
The resource name is passed into initialization:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
protected async __init__(resource: string) {
|
|
123
|
+
await super.__init__(resource);
|
|
124
|
+
this.resource = resource;
|
|
125
|
+
...
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
That means the selector becomes the runtime identity for the concrete resource instance.
|
|
130
|
+
|
|
131
|
+
So even when many consumers reuse the same generic class, each resource instance stays isolated.
|
|
132
|
+
|
|
133
|
+
This is not only conceptual isolation.
|
|
134
|
+
|
|
135
|
+
It also affects cache identity.
|
|
136
|
+
|
|
137
|
+
Because Zova Model prefixes query keys by bean identity and selector, the effective cache keys are separated by resource.
|
|
138
|
+
|
|
139
|
+
So logical keys such as:
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
['select', '', hashkey(query)][('item', id, 'view')];
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
can safely exist for many resources without colliding.
|
|
146
|
+
|
|
147
|
+
## Bootstrap before normal state usage
|
|
148
|
+
|
|
149
|
+
A small feature model often starts using queries immediately.
|
|
150
|
+
|
|
151
|
+
`ModelResource` adds a bootstrap step first:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
protected async __init__(resource: string) {
|
|
155
|
+
...
|
|
156
|
+
await this._bootstrap();
|
|
157
|
+
}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
And `_bootstrap()` uses:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
const queryBootstrap = await $QueryAutoLoad(() => this.$sdk.getBootstrap(this.resource));
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Then it resolves:
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
this.resourceApi = this.sys.util.parseResourceApi(this.resource, queryBootstrap.data.apiPath);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
This is an important pattern.
|
|
173
|
+
|
|
174
|
+
The model does not assume that the final operational API path is already known as a hardcoded constant.
|
|
175
|
+
|
|
176
|
+
Instead, it loads resource metadata first, then turns that metadata into the stable runtime resource API boundary.
|
|
177
|
+
|
|
178
|
+
That is one reason this model is better understood as infrastructure rather than only CRUD sugar.
|
|
179
|
+
|
|
180
|
+
## The model owns resource metadata surfaces
|
|
181
|
+
|
|
182
|
+
Inside `__init__`, the model exposes several computed resource-level surfaces:
|
|
183
|
+
|
|
184
|
+
- `permissions`
|
|
185
|
+
- `formProvider`
|
|
186
|
+
- `schemaView`
|
|
187
|
+
- `schemaCreate`
|
|
188
|
+
- `schemaUpdate`
|
|
189
|
+
- `schemaFilter`
|
|
190
|
+
- `schemaRow`
|
|
191
|
+
- `schemaPages`
|
|
192
|
+
|
|
193
|
+
These are not random conveniences.
|
|
194
|
+
|
|
195
|
+
They show that the model owns not only request state, but also the metadata that resource UIs need in order to render and behave correctly.
|
|
196
|
+
|
|
197
|
+
For example:
|
|
198
|
+
|
|
199
|
+
- permissions affect whether UI actions are available
|
|
200
|
+
- schemas affect view/create/edit/filter rendering
|
|
201
|
+
- form provider affects how forms are assembled
|
|
202
|
+
|
|
203
|
+
This is exactly the kind of cross-cutting ownership that fits a resource-owner model.
|
|
204
|
+
|
|
205
|
+
## Query ownership pattern
|
|
206
|
+
|
|
207
|
+
The query side is organized around a reusable internal structure.
|
|
208
|
+
|
|
209
|
+
### List/query state
|
|
210
|
+
|
|
211
|
+
The model exposes list-style querying through:
|
|
212
|
+
|
|
213
|
+
- `selectGeneral(actionPath?, query?)`
|
|
214
|
+
- `select(query?)`
|
|
215
|
+
|
|
216
|
+
These methods use `$useStateData(...)` and place query ownership inside the model.
|
|
217
|
+
|
|
218
|
+
That means the page does not own the fetch details or cache-key policy.
|
|
219
|
+
|
|
220
|
+
### Row-specific query state
|
|
221
|
+
|
|
222
|
+
The model exposes item-style querying through:
|
|
223
|
+
|
|
224
|
+
- `queryItem(...)`
|
|
225
|
+
- `view(id)`
|
|
226
|
+
|
|
227
|
+
Again, the page consumes a stable model API rather than building row fetch rules ad hoc.
|
|
228
|
+
|
|
229
|
+
A practical reading takeaway is:
|
|
230
|
+
|
|
231
|
+
> pages consume resource semantics; the model owns query semantics.
|
|
232
|
+
|
|
233
|
+
## Mutation ownership pattern
|
|
234
|
+
|
|
235
|
+
The mutation side follows the same idea.
|
|
236
|
+
|
|
237
|
+
The model does not expose only one-off raw mutation calls.
|
|
238
|
+
|
|
239
|
+
Instead, it builds a reusable mutation layer:
|
|
240
|
+
|
|
241
|
+
- `create()`
|
|
242
|
+
- `update(id)`
|
|
243
|
+
- `delete(id)`
|
|
244
|
+
- `mutationItem(...)`
|
|
245
|
+
|
|
246
|
+
This gives the model one place to enforce mutation-key conventions and invalidation rules.
|
|
247
|
+
|
|
248
|
+
That is an important architectural advantage.
|
|
249
|
+
|
|
250
|
+
Without this model boundary, every page or dialog could invent slightly different invalidation behavior.
|
|
251
|
+
|
|
252
|
+
## Cache-key design
|
|
253
|
+
|
|
254
|
+
One of the best design lessons in this specimen is the cache-key structure.
|
|
255
|
+
|
|
256
|
+
The class defines three related key helpers:
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
protected keySelect(actionPath?: string, query?: ITableQuery) {
|
|
260
|
+
return ['select', actionPath ?? '', hashkey(query)] as const;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
protected keyItemRoot(id: TableIdentity) {
|
|
264
|
+
return ['item', id] as const;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
protected keyItem(id: TableIdentity, action: string) {
|
|
268
|
+
return ['item', id, action] as const;
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
This separates resource state into three layers:
|
|
273
|
+
|
|
274
|
+
- list/query scope
|
|
275
|
+
- row root scope
|
|
276
|
+
- row action scope
|
|
277
|
+
|
|
278
|
+
That structure gives the model precise invalidation control.
|
|
279
|
+
|
|
280
|
+
## Invalidation policy as model-owned policy
|
|
281
|
+
|
|
282
|
+
The model also centralizes invalidation behavior.
|
|
283
|
+
|
|
284
|
+
### Create
|
|
285
|
+
|
|
286
|
+
`create()` invalidates:
|
|
287
|
+
|
|
288
|
+
- `['select']`
|
|
289
|
+
|
|
290
|
+
That makes sense because a new row affects list-level state first.
|
|
291
|
+
|
|
292
|
+
### Update/Delete
|
|
293
|
+
|
|
294
|
+
`mutationItem(...)` invalidates:
|
|
295
|
+
|
|
296
|
+
- `['select']`
|
|
297
|
+
- `keyItemRoot(id)`
|
|
298
|
+
|
|
299
|
+
That means:
|
|
300
|
+
|
|
301
|
+
- list views refresh when row mutations change aggregate list state
|
|
302
|
+
- row-specific state refreshes when one row changes
|
|
303
|
+
|
|
304
|
+
This is a very important design point.
|
|
305
|
+
|
|
306
|
+
The model itself defines consistency rules for resource state.
|
|
307
|
+
|
|
308
|
+
That keeps cache policy close to resource semantics rather than scattering it across UI code.
|
|
309
|
+
|
|
310
|
+
## Form integration pattern
|
|
311
|
+
|
|
312
|
+
`ModelResource` also owns form-facing behavior.
|
|
313
|
+
|
|
314
|
+
Representative methods:
|
|
315
|
+
|
|
316
|
+
- `getFormSchema(formMeta)`
|
|
317
|
+
- `getFormApiSchemas(formMeta)`
|
|
318
|
+
- `getFormMutationSubmit(formMeta, id?)`
|
|
319
|
+
- `getFormData(formMeta, id?)`
|
|
320
|
+
- `getQueryDataDefaultValue(schemaName?)`
|
|
321
|
+
|
|
322
|
+
This is what makes the model a real resource facade instead of only a transport helper.
|
|
323
|
+
|
|
324
|
+
The model translates resource semantics into form semantics.
|
|
325
|
+
|
|
326
|
+
For example:
|
|
327
|
+
|
|
328
|
+
- create form → use create schema and create mutation
|
|
329
|
+
- update form → use update schema and update mutation
|
|
330
|
+
- view form → use view schema and row data query
|
|
331
|
+
|
|
332
|
+
This keeps form orchestration aligned with the same resource owner that already owns queries and mutations.
|
|
333
|
+
|
|
334
|
+
## Relationship to `$fetch`, `$sdk`, and OpenAPI
|
|
335
|
+
|
|
336
|
+
`ModelResource` also shows how multiple frontend data layers can be composed cleanly.
|
|
337
|
+
|
|
338
|
+
Inside one model boundary it uses:
|
|
339
|
+
|
|
340
|
+
- `$fetch` for direct REST request execution
|
|
341
|
+
- `$sdk` for bootstrap, schema, permissions, and default-value helpers
|
|
342
|
+
- model helpers for cached query/mutation state
|
|
343
|
+
|
|
344
|
+
That composition matters.
|
|
345
|
+
|
|
346
|
+
It shows that the server-data ladder is not a set of isolated choices.
|
|
347
|
+
|
|
348
|
+
A higher-level model can combine those layers into one coherent business-facing API.
|
|
349
|
+
|
|
350
|
+
## When to use this pattern
|
|
351
|
+
|
|
352
|
+
Use a resource-owner model when:
|
|
353
|
+
|
|
354
|
+
- many screens or widgets depend on the same backend resource
|
|
355
|
+
- schema metadata, permissions, and CRUD behavior should stay aligned
|
|
356
|
+
- form behavior should stay aligned with the same resource owner
|
|
357
|
+
- cache invalidation rules should be centralized
|
|
358
|
+
- you want one stable frontend boundary for resource-level business semantics
|
|
359
|
+
|
|
360
|
+
This pattern is especially strong for admin-style, resource-driven UIs.
|
|
361
|
+
|
|
362
|
+
## When not to use this pattern
|
|
363
|
+
|
|
364
|
+
Do not reach for this pattern by default when:
|
|
365
|
+
|
|
366
|
+
- the state is truly page-local and disposable
|
|
367
|
+
- the feature only needs one tiny query with no reusable semantics
|
|
368
|
+
- there is no meaningful resource-level schema/permission/form ownership
|
|
369
|
+
- adding a generic resource facade would be heavier than the real business problem
|
|
370
|
+
|
|
371
|
+
In those cases, a smaller feature model may be the better fit.
|
|
372
|
+
|
|
373
|
+
## A useful comparison
|
|
374
|
+
|
|
375
|
+
A compact comparison is:
|
|
376
|
+
|
|
377
|
+
- `demo-todo` → minimal feature model pattern
|
|
378
|
+
- `home-passport` → SSR-sensitive state ownership pattern
|
|
379
|
+
- `rest-resource` → generic reusable resource-owner pattern
|
|
380
|
+
|
|
381
|
+
Use these three together when reading how far Zova Model can scale.
|
|
382
|
+
|
|
383
|
+
## Source-reading checklist
|
|
384
|
+
|
|
385
|
+
When you read `ModelResource`, focus on these questions:
|
|
386
|
+
|
|
387
|
+
1. where does the selector identity come from?
|
|
388
|
+
2. where does `resourceApi` become available?
|
|
389
|
+
3. which state belongs to list scope vs row scope?
|
|
390
|
+
4. which invalidation rules belong to each mutation?
|
|
391
|
+
5. which responsibilities are query-related and which are schema/form-related?
|
|
392
|
+
6. what would become duplicated across pages if this model did not exist?
|
|
393
|
+
|
|
394
|
+
Those questions will help you see the architectural pattern, not only the method list.
|
|
395
|
+
|
|
396
|
+
## Final takeaway
|
|
397
|
+
|
|
398
|
+
The most important insight is simple:
|
|
399
|
+
|
|
400
|
+
> a Zova model can be more than a data hook wrapper. `ModelResource` can become the stable owner of one whole frontend resource boundary.
|
|
401
|
+
|
|
402
|
+
In application code, prefer using that owner directly or adding a thin facade over it, rather than creating a competing second owner for the same resource.
|