cabloy 5.1.59 → 5.1.61
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/references/follow-up-checklist.md +1 -0
- package/.claude/skills/cabloy-contract-loop/SKILL.md +103 -14
- package/.claude/skills/cabloy-contract-loop/references/contract-loop-map.md +126 -12
- package/.claude/skills/cabloy-contract-loop/references/resource-custom-state-pattern.md +148 -0
- package/.claude/skills/cabloy-contract-loop/references/verification-checklist.md +49 -13
- package/.claude/skills/cabloy-frontend-scaffold/SKILL.md +11 -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 +274 -0
- package/.claude/skills/cabloy-resource-field-update/evals/evals.json +53 -0
- package/.claude/skills/cabloy-resource-field-update/references/custom-renderer-demo-checklist.md +102 -0
- package/.claude/skills/cabloy-resource-field-update/references/field-update-decision-tree.md +120 -0
- package/.claude/skills/cabloy-resource-field-update/references/follow-up-checklist.md +80 -0
- package/.claude/skills/cabloy-resource-field-update/references/verification-checklist.md +97 -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/.github/workflows/docs-pages.yml +2 -0
- package/.github/workflows/vona-cov-pg.yml +2 -0
- package/.github/workflows/vona-test-crud.yml +4 -2
- package/.github/workflows/vona-test-mysql.yml +2 -0
- package/.github/workflows/vona-test-pg.yml +2 -0
- package/.github/workflows/vona-test-sqlite3.yml +2 -0
- package/.github/workflows/vona-tsc.yml +2 -0
- package/.github/workflows/zova-ui.yml +2 -0
- package/.gitignore +0 -4
- package/CHANGELOG.md +52 -0
- package/CLAUDE.md +12 -0
- package/README.md +15 -0
- package/cabloy-docs/.vitepress/config.mjs +89 -0
- package/cabloy-docs/ai/class-placement-rule.md +2 -0
- package/cabloy-docs/ai/cli-to-skill-map.md +14 -0
- package/cabloy-docs/ai/docs-skills-rules-mapping.md +14 -0
- package/cabloy-docs/ai/future-skill-roadmap.md +27 -9
- 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 +11 -0
- package/cabloy-docs/backend/bean-scene-authoring.md +350 -0
- package/cabloy-docs/backend/cli.md +26 -1
- package/cabloy-docs/backend/dto-guide.md +6 -0
- package/cabloy-docs/backend/entity-guide.md +18 -0
- package/cabloy-docs/backend/foundation.md +28 -3
- package/cabloy-docs/backend/introduction.md +10 -0
- package/cabloy-docs/backend/serialization-guide.md +10 -0
- package/cabloy-docs/backend/service-guide.md +2 -0
- package/cabloy-docs/backend/status-guide.md +271 -0
- package/cabloy-docs/backend/websocket-call-flow.md +435 -0
- package/cabloy-docs/backend/websocket-guide.md +455 -0
- package/cabloy-docs/backend/websocket-protocol-guide.md +381 -0
- package/cabloy-docs/backend/websocket-usage-guide.md +356 -0
- package/cabloy-docs/frontend/api-guide.md +2 -0
- package/cabloy-docs/frontend/bean-scene-authoring.md +374 -0
- package/cabloy-docs/frontend/behavior-guide.md +449 -0
- package/cabloy-docs/frontend/cli.md +24 -0
- package/cabloy-docs/frontend/command-scene-authoring.md +495 -0
- package/cabloy-docs/frontend/design-principles.md +6 -0
- package/cabloy-docs/frontend/fetch-interceptor-guide.md +440 -0
- package/cabloy-docs/frontend/form-guide.md +795 -0
- package/cabloy-docs/frontend/foundation.md +29 -0
- package/cabloy-docs/frontend/introduction.md +17 -1
- package/cabloy-docs/frontend/ioc-and-beans.md +16 -9
- package/cabloy-docs/frontend/mock-guide.md +1 -0
- package/cabloy-docs/frontend/model-architecture.md +252 -39
- package/cabloy-docs/frontend/model-resource-best-practices.md +379 -0
- package/cabloy-docs/frontend/model-resource-cookbook.md +505 -0
- package/cabloy-docs/frontend/model-resource-owner-pattern.md +382 -0
- package/cabloy-docs/frontend/model-resource-usage-guide.md +318 -0
- package/cabloy-docs/frontend/model-state-guide.md +366 -13
- package/cabloy-docs/frontend/openapi-sdk-guide.md +5 -2
- package/cabloy-docs/frontend/page-guide.md +6 -0
- package/cabloy-docs/frontend/quickstart.md +4 -0
- package/cabloy-docs/frontend/reading-zova-for-vue-developers.md +266 -0
- package/cabloy-docs/frontend/router-tabs-admin-web-comparison.md +206 -0
- package/cabloy-docs/frontend/router-tabs-introduction.md +106 -0
- package/cabloy-docs/frontend/router-tabs-mechanism.md +469 -0
- package/cabloy-docs/frontend/router-tabs-overview.md +227 -0
- package/cabloy-docs/frontend/router-tabs-route-meta-cookbook.md +343 -0
- package/cabloy-docs/frontend/server-data.md +2 -0
- package/cabloy-docs/frontend/ssr-architecture-overview.md +211 -0
- package/cabloy-docs/frontend/ssr-build-deploy-guide.md +308 -0
- package/cabloy-docs/frontend/ssr-review-checklist.md +184 -0
- package/cabloy-docs/frontend/ssr-troubleshooting-guide.md +301 -0
- package/cabloy-docs/frontend/zova-form-source-reading-map.md +295 -0
- package/cabloy-docs/frontend/zova-form-under-the-hood.md +556 -0
- package/cabloy-docs/frontend/zova-reactivity-under-the-hood.md +320 -0
- package/cabloy-docs/frontend/zova-source-reading-map.md +327 -0
- package/cabloy-docs/frontend/zova-vs-vue3-comparison.md +308 -0
- package/cabloy-docs/fullstack/contract-loop-playbook.md +350 -0
- package/cabloy-docs/fullstack/framework-performance.md +3 -3
- package/cabloy-docs/fullstack/frontend-metadata-to-backend.md +44 -1
- package/cabloy-docs/fullstack/introduction.md +40 -0
- package/cabloy-docs/fullstack/openapi-to-sdk.md +19 -9
- package/cabloy-docs/fullstack/quickstart.md +7 -1
- package/cabloy-docs/fullstack/tutorial-1-first-module.md +111 -0
- package/cabloy-docs/fullstack/tutorial-2-first-crud.md +122 -0
- package/cabloy-docs/fullstack/tutorial-3-frontend-metadata-sharing.md +131 -0
- package/cabloy-docs/fullstack/tutorial-4-custom-level-renderers.md +144 -0
- package/cabloy-docs/fullstack/tutorial-5-backend-contract-sharing.md +146 -0
- package/cabloy-docs/fullstack/tutorial-6-one-contract-four-uses.md +170 -0
- package/cabloy-docs/fullstack/tutorials-overview.md +192 -0
- package/cabloy-docs/index.md +4 -3
- package/cabloy-docs/reference/bean-scene-boilerplates.md +75 -0
- package/cabloy-docs/reference/cli-reference.md +2 -0
- package/package.json +7 -2
- package/scripts/initTestData.ts +25 -0
- package/scripts/upgrade.ts +17 -2
- package/vona/packages-cli/cabloy-cli/package.json +2 -2
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-cli/cli-set-api/src/lib/bean/cli.create.module.ts +4 -0
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/pnpm-lock.yaml +226 -1091
- package/vona/pnpm-workspace.yaml +0 -1
- package/vona/src/suite-vendor/a-vona/modules/a-core/assets/static/img/vona.svg +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/bean.permission.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-upload/package.json +2 -2
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/package.original.json +1 -1
- package/zova/packages-cli/cli/package.json +3 -3
- package/zova/packages-cli/cli-set-front/cli/templates/init/icon/boilerplate/icons/default/zova.svg +1 -1
- 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 +3 -3
- package/zova/packages-cli/cli-set-front/src/lib/bean/cli.create.module.ts +4 -0
- package/zova/packages-cli/cli-set-front/src/lib/bean/cli.openapi.generate.ts +34 -4
- package/zova/packages-cli/cli-set-front/src/lib/command/create.bean.ts +5 -1
- package/zova/packages-utils/zova-vite/package.json +2 -2
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/pnpm-lock.yaml +282 -1311
- package/zova/pnpm-workspace.yaml +0 -1
- package/zova/src/suite/a-home/modules/home-icon/icons/social/cabloy.svg +1 -1
- package/zova/src/suite/a-home/modules/home-icon/icons/social/vona.svg +1 -1
- package/zova/src/suite/a-home/modules/home-icon/icons/social/zova.svg +1 -1
- package/zova/src/suite/a-home/modules/home-icon/src/.metadata/icons/groups/social.svg +3 -3
- package/zova/src/suite/cabloy-basic/modules/basic-select/src/component/formFieldSelect/controller.tsx +9 -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 +66 -16
- package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/modules/a-routertabs/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-routertabs/src/model/tabs.ts +60 -18
- package/zova/src/suite-vendor/a-zova/modules/a-table/cli/tableActionRow/boilerplate/{{sceneName}}.{{beanName}}.tsx_ +6 -1
- package/zova/src/suite-vendor/a-zova/modules/a-table/cli/tableCell/boilerplate/{{sceneName}}.{{beanName}}.tsx_ +6 -1
- package/zova/src/suite-vendor/a-zova/modules/a-table/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/package.json +4 -4
|
@@ -1,12 +1,28 @@
|
|
|
1
1
|
# Model State Guide
|
|
2
2
|
|
|
3
|
-
This guide explains how model-
|
|
3
|
+
This guide explains how to author and consume model-managed state in Zova within the Cabloy monorepo.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Read [Model Architecture](/frontend/model-architecture) first if you want the broader architectural role of Model.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
If you specifically want the scalable resource-facade pattern, continue with [Model Resource Owner Pattern](/frontend/model-resource-owner-pattern).
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
If you want to apply that pattern in your own module with a more uniform two-usage model, continue with [Using `ModelResource` in Your Module](/frontend/model-resource-usage-guide).
|
|
10
|
+
|
|
11
|
+
## Why the model-state layer exists
|
|
12
|
+
|
|
13
|
+
Zova uses model-based state management so cached remote data and several local state categories can participate in one broader model system.
|
|
14
|
+
|
|
15
|
+
This improves runtime performance and developer experience by building on top of TanStack Query while keeping the developer-facing surface aligned with Zova beans.
|
|
16
|
+
|
|
17
|
+
The key point is that model state is not limited to server data.
|
|
18
|
+
|
|
19
|
+
Current `a-model` source supports a broader state family that includes:
|
|
20
|
+
|
|
21
|
+
- remote/query-style state
|
|
22
|
+
- in-memory state
|
|
23
|
+
- local-storage state
|
|
24
|
+
- cookie-backed state
|
|
25
|
+
- async persisted db state
|
|
10
26
|
|
|
11
27
|
## Create a model
|
|
12
28
|
|
|
@@ -16,13 +32,15 @@ Example: create a model named `menu` in module `demo-student`.
|
|
|
16
32
|
npm run zova :create:bean model menu -- --module=demo-student
|
|
17
33
|
```
|
|
18
34
|
|
|
19
|
-
##
|
|
35
|
+
## Basic model definition
|
|
20
36
|
|
|
21
37
|
Representative pattern:
|
|
22
38
|
|
|
23
39
|
```typescript
|
|
40
|
+
import { BeanModelBase, Model } from 'zova-module-a-model';
|
|
41
|
+
|
|
24
42
|
@Model()
|
|
25
|
-
export class ModelMenu {
|
|
43
|
+
export class ModelMenu extends BeanModelBase {
|
|
26
44
|
retrieveMenus() {
|
|
27
45
|
return this.$useStateData({
|
|
28
46
|
queryKey: ['retrieveMenus'],
|
|
@@ -36,11 +54,11 @@ export class ModelMenu {
|
|
|
36
54
|
}
|
|
37
55
|
```
|
|
38
56
|
|
|
39
|
-
This pattern
|
|
57
|
+
This pattern matters because it shows that model logic is the place where cached remote data becomes a reusable abstraction.
|
|
40
58
|
|
|
41
59
|
## Using a model
|
|
42
60
|
|
|
43
|
-
Representative pattern:
|
|
61
|
+
Representative consumption pattern:
|
|
44
62
|
|
|
45
63
|
```typescript
|
|
46
64
|
@Use()
|
|
@@ -53,18 +71,353 @@ protected render() {
|
|
|
53
71
|
}
|
|
54
72
|
```
|
|
55
73
|
|
|
74
|
+
The important point is architectural:
|
|
75
|
+
|
|
76
|
+
- the page/controller consumes the model
|
|
77
|
+
- the model owns the reusable data access and cache behavior
|
|
78
|
+
|
|
79
|
+
## The main helper families
|
|
80
|
+
|
|
81
|
+
The current source organizes model state around these helper families.
|
|
82
|
+
|
|
83
|
+
### 1. `$useStateData(...)`
|
|
84
|
+
|
|
85
|
+
Use this for cached remote data or other query-style state where the caller wants the full query object.
|
|
86
|
+
|
|
87
|
+
Representative characteristics from current source:
|
|
88
|
+
|
|
89
|
+
- it delegates to `$useQuery(...)`
|
|
90
|
+
- it memoizes the query wrapper by prefixed query key
|
|
91
|
+
- it auto-calls `suspense()` on first creation unless disabled by metadata
|
|
92
|
+
- it is the main bridge from model methods to query-style UI state
|
|
93
|
+
|
|
94
|
+
Representative example:
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
findAll() {
|
|
98
|
+
return this.$useStateData({
|
|
99
|
+
queryKey: ['list'],
|
|
100
|
+
queryFn: async () => {
|
|
101
|
+
return this.scope.api.todo.findAll();
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## 2. `$useMutationData(...)`
|
|
108
|
+
|
|
109
|
+
Use this for model-owned mutation state.
|
|
110
|
+
|
|
111
|
+
Current-source behavior includes:
|
|
112
|
+
|
|
113
|
+
- `mutationKey` is required
|
|
114
|
+
- the key is automatically prefixed with model identity
|
|
115
|
+
- the mutation wrapper is memoized by prefixed key
|
|
116
|
+
- default error handling is attached unless disabled
|
|
117
|
+
|
|
118
|
+
Representative example:
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
create() {
|
|
122
|
+
return this.$useMutationData({
|
|
123
|
+
mutationKey: ['create'],
|
|
124
|
+
mutationFn: async body => {
|
|
125
|
+
return this.scope.api.todo.create(body);
|
|
126
|
+
},
|
|
127
|
+
onSuccess: () => {
|
|
128
|
+
this.$invalidateQueries({ queryKey: ['list'] });
|
|
129
|
+
},
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
The pattern to notice is that the model owns both the mutation and the follow-up cache invalidation policy.
|
|
135
|
+
|
|
136
|
+
## 3. `$useStateMem(...)`
|
|
137
|
+
|
|
138
|
+
Use this when the state should stay only in memory.
|
|
139
|
+
|
|
140
|
+
Current-source characteristics:
|
|
141
|
+
|
|
142
|
+
- no persistence
|
|
143
|
+
- `enabled: false`
|
|
144
|
+
- `staleTime: Infinity`
|
|
145
|
+
- still uses model-owned query cache semantics under the hood
|
|
146
|
+
|
|
147
|
+
This is useful when you want model ownership and query-key identity without local persistence.
|
|
148
|
+
|
|
149
|
+
## 4. `$useStateLocal(...)`
|
|
150
|
+
|
|
151
|
+
Use this when the model state should persist in local storage.
|
|
152
|
+
|
|
153
|
+
Current-source characteristics:
|
|
154
|
+
|
|
155
|
+
- sync local storage persistence
|
|
156
|
+
- simplified storage keys by default
|
|
157
|
+
- `enabled: false`
|
|
158
|
+
- `staleTime: Infinity`
|
|
159
|
+
- state still flows through model-owned query cache first
|
|
160
|
+
|
|
161
|
+
A useful mental model is:
|
|
162
|
+
|
|
163
|
+
> local-storage state in Zova Model is not a separate store system. It is model-owned query state with a local-storage persister.
|
|
164
|
+
|
|
165
|
+
## 5. `$useStateCookie(...)`
|
|
166
|
+
|
|
167
|
+
Use this when the model state should persist in cookies.
|
|
168
|
+
|
|
169
|
+
Current-source characteristics:
|
|
170
|
+
|
|
171
|
+
- sync cookie persistence
|
|
172
|
+
- cookie-type coercion support such as `boolean`, `number`, `date`, or `string`
|
|
173
|
+
- simplified storage keys by default
|
|
174
|
+
- still uses model-owned query cache semantics
|
|
175
|
+
|
|
176
|
+
This is particularly relevant for state that must participate in request-aware or SSR-adjacent flows.
|
|
177
|
+
|
|
178
|
+
## 6. `$useStateDb(...)`
|
|
179
|
+
|
|
180
|
+
Use this when the model state should persist asynchronously in db-style client storage.
|
|
181
|
+
|
|
182
|
+
Current-source characteristics:
|
|
183
|
+
|
|
184
|
+
- async persistence through `localforage`
|
|
185
|
+
- `enabled: false`
|
|
186
|
+
- `staleTime: Infinity`
|
|
187
|
+
- explicit `ssr.dehydrate = false`
|
|
188
|
+
- getters may need async restore flow before data is available
|
|
189
|
+
|
|
190
|
+
This is useful for larger persisted client state that should outlive a page session without being stored in cookies or plain local storage.
|
|
191
|
+
|
|
192
|
+
## 7. `$useStateComputed(...)`
|
|
193
|
+
|
|
194
|
+
Use this when the model should expose derived state that is still model-key-oriented but computed locally.
|
|
195
|
+
|
|
196
|
+
Current-source characteristics:
|
|
197
|
+
|
|
198
|
+
- it prefixes the query key
|
|
199
|
+
- it memoizes the computed value by the hashed prefixed key
|
|
200
|
+
- it uses `$computed(...)` rather than TanStack Query fetching
|
|
201
|
+
|
|
202
|
+
## Query keys are model-owned, not global strings
|
|
203
|
+
|
|
204
|
+
One of the most important model-state behaviors is automatic key prefixing.
|
|
205
|
+
|
|
206
|
+
When a model uses:
|
|
207
|
+
|
|
208
|
+
```typescript
|
|
209
|
+
queryKey: ['list']
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
that logical key is prefixed internally with model identity, and with selector when selector mode is enabled.
|
|
213
|
+
|
|
214
|
+
That means model code can use short business-facing keys while still getting namespace isolation.
|
|
215
|
+
|
|
216
|
+
## Persistence and restore behavior
|
|
217
|
+
|
|
218
|
+
Current source treats persistence as part of the model-state runtime.
|
|
219
|
+
|
|
220
|
+
Representative behaviors include:
|
|
221
|
+
|
|
222
|
+
- persisted data can be restored back into query state
|
|
223
|
+
- expired or busted persisted entries are removed automatically
|
|
224
|
+
- `defaultData` can initialize the cache when no restored value exists
|
|
225
|
+
- setting persisted state to `undefined` removes the persisted record
|
|
226
|
+
|
|
227
|
+
This is why model helpers are better understood as state-runtime helpers, not only convenience wrappers.
|
|
228
|
+
|
|
229
|
+
## SSR-sensitive state choices
|
|
230
|
+
|
|
231
|
+
Model state choices can affect SSR behavior.
|
|
232
|
+
|
|
233
|
+
Current-source behaviors to remember:
|
|
234
|
+
|
|
235
|
+
- query state can be dehydrated on server and hydrated on client
|
|
236
|
+
- mutations are not dehydrated
|
|
237
|
+
- `db` state is explicitly not dehydrated
|
|
238
|
+
- server cannot use local-storage or db persistence backends
|
|
239
|
+
- cookie state is special because cookie access can still exist through the app cookie surface
|
|
240
|
+
|
|
241
|
+
Practical implication:
|
|
242
|
+
|
|
243
|
+
- do not assume every persisted model state behaves the same during SSR
|
|
244
|
+
- choose `mem`, `local`, `cookie`, `db`, or `data` based on ownership and hydration requirements, not only convenience
|
|
245
|
+
|
|
246
|
+
## Real examples to read
|
|
247
|
+
|
|
248
|
+
### Minimal query and mutation model
|
|
249
|
+
|
|
250
|
+
Read:
|
|
251
|
+
|
|
252
|
+
- `zova/src/suite/a-demo/modules/demo-todo/src/model/todo.ts`
|
|
253
|
+
|
|
254
|
+
This file shows:
|
|
255
|
+
|
|
256
|
+
- extending `BeanModelBase`
|
|
257
|
+
- `@Model()` authoring
|
|
258
|
+
- query-style state through `$useStateData(...)`
|
|
259
|
+
- mutation-style state through `$useMutationData(...)`
|
|
260
|
+
- cache invalidation inside the model
|
|
261
|
+
|
|
262
|
+
### Selector-enabled cache-oriented model
|
|
263
|
+
|
|
264
|
+
Read:
|
|
265
|
+
|
|
266
|
+
- `zova/src/suite-vendor/a-zova/modules/a-routertabs/src/model/tabs.ts`
|
|
267
|
+
|
|
268
|
+
This file shows:
|
|
269
|
+
|
|
270
|
+
- `@Model({ enableSelector: true, ... })`
|
|
271
|
+
- richer model initialization in `__init__`
|
|
272
|
+
- mixed use of `$useStateMem(...)` and `$useStateDb(...)`
|
|
273
|
+
- model-owned tab state with cache and persistence decisions
|
|
274
|
+
|
|
275
|
+
### SSR-sensitive auth model
|
|
276
|
+
|
|
277
|
+
Read:
|
|
278
|
+
|
|
279
|
+
- `zova/src/suite/a-home/modules/home-passport/src/model/passport.ts`
|
|
280
|
+
|
|
281
|
+
This file shows:
|
|
282
|
+
|
|
283
|
+
- mixing `mem`, `local`, and `cookie` state in one model
|
|
284
|
+
- SSR-aware state choices
|
|
285
|
+
- model ownership of auth-related cached state and mutation flows
|
|
286
|
+
|
|
287
|
+
### Generic resource-owner model
|
|
288
|
+
|
|
289
|
+
Read:
|
|
290
|
+
|
|
291
|
+
- `zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts`
|
|
292
|
+
|
|
293
|
+
This file is especially important because it shows a higher-level infrastructure pattern rather than a small feature model.
|
|
294
|
+
|
|
295
|
+
It demonstrates that a model can act as a **resource owner facade** for CRUD-oriented business modules.
|
|
296
|
+
|
|
297
|
+
What this example shows:
|
|
298
|
+
|
|
299
|
+
- `@Model({ enableSelector: true })` uses selector to isolate one model instance per resource name
|
|
300
|
+
- `__init__(resource)` bootstraps resource metadata and resolves `resourceApi` before normal query usage
|
|
301
|
+
- `$computed(...)` can expose permissions, form provider, and schema surfaces as model-owned derived state
|
|
302
|
+
- `$useStateData(...)` drives select/view queries
|
|
303
|
+
- `$useMutationData(...)` is wrapped by reusable `create`, `update`, `delete`, and `mutationItem` helpers
|
|
304
|
+
- invalidation policy is centralized so list and item caches stay coherent after mutations
|
|
305
|
+
- model methods can combine `$fetch`, `$sdk`, OpenAPI schema helpers, and form-oriented helpers behind one reusable boundary
|
|
306
|
+
|
|
307
|
+
This is one of the best examples for understanding how Zova Model scales from simple data queries to reusable domain infrastructure.
|
|
308
|
+
|
|
309
|
+
#### Why `enableSelector` matters here
|
|
310
|
+
|
|
311
|
+
This model is not meant to represent only one concrete resource forever.
|
|
312
|
+
|
|
313
|
+
It is a reusable generic model class that can serve many resources.
|
|
314
|
+
|
|
315
|
+
That is why `enableSelector` is essential.
|
|
316
|
+
|
|
317
|
+
The model passes `resource` into `super.__init__(resource)`, so the resource name becomes the selector identity for that model instance.
|
|
318
|
+
|
|
319
|
+
At runtime, model query keys are therefore prefixed not only by the bean full name but also by the selected resource.
|
|
320
|
+
|
|
321
|
+
That means two consumers can both use logical keys such as:
|
|
322
|
+
|
|
323
|
+
```typescript
|
|
324
|
+
['select', '', hashkey(query)]
|
|
325
|
+
['item', id, 'view']
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
without colliding with each other, because the effective cache identity is separated by resource selector.
|
|
329
|
+
|
|
330
|
+
A practical reading takeaway is:
|
|
331
|
+
|
|
332
|
+
> selector turns one generic `ModelResource` implementation into many isolated resource-specific runtime instances.
|
|
333
|
+
|
|
334
|
+
#### Why this is a resource owner, not only a CRUD helper
|
|
335
|
+
|
|
336
|
+
A plain CRUD helper usually forwards requests.
|
|
337
|
+
|
|
338
|
+
`ModelResource` does more than that.
|
|
339
|
+
|
|
340
|
+
It owns several resource-level concerns together:
|
|
341
|
+
|
|
342
|
+
- bootstrap of resource metadata through `$QueryAutoLoad(...)`
|
|
343
|
+
- resolution of the final `resourceApi`
|
|
344
|
+
- permissions lookup
|
|
345
|
+
- OpenAPI schema access for view/create/update/select
|
|
346
|
+
- form integration such as submit mutation choice and default form data
|
|
347
|
+
- query and mutation cache invalidation rules
|
|
348
|
+
|
|
349
|
+
That is why this model is better understood as a **resource owner facade**.
|
|
350
|
+
|
|
351
|
+
In application code, prefer consuming that existing owner directly or adding a thin facade over it, while the lower-level `$fetch` and `$sdk` details stay inside the owner boundary.
|
|
352
|
+
|
|
353
|
+
#### How the cache-key design works
|
|
354
|
+
|
|
355
|
+
The cache-key design in this file is also worth reading carefully.
|
|
356
|
+
|
|
357
|
+
It separates three levels of identity:
|
|
358
|
+
|
|
359
|
+
- `keySelect(actionPath, query)` → list/query-level state
|
|
360
|
+
- `keyItemRoot(id)` → all state owned by one row id
|
|
361
|
+
- `keyItem(id, action)` → one concrete row action such as `view` or `update`
|
|
362
|
+
|
|
363
|
+
Representative shapes are:
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
['select', actionPath ?? '', hashkey(query)]
|
|
367
|
+
['item', id]
|
|
368
|
+
['item', id, action]
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
This makes the invalidation strategy much clearer:
|
|
372
|
+
|
|
373
|
+
- create invalidates the select-level list cache
|
|
374
|
+
- update/delete invalidate the select-level list cache and the item root for that row
|
|
375
|
+
- item-specific queries such as `view(id)` live under the item branch
|
|
376
|
+
|
|
377
|
+
The practical benefit is that the model does not scatter cache policy across pages.
|
|
378
|
+
|
|
379
|
+
Instead, the model itself defines how list-level and row-level state stay coherent after mutations.
|
|
380
|
+
|
|
381
|
+
#### Why this example is a strong source-reading specimen
|
|
382
|
+
|
|
383
|
+
If `demo-todo` shows the minimal pattern for model queries and mutations, `rest-resource` shows the scalable pattern.
|
|
384
|
+
|
|
385
|
+
Use it when you want to understand how Zova Model can support:
|
|
386
|
+
|
|
387
|
+
- generic reusable business infrastructure
|
|
388
|
+
- selector-isolated model instances
|
|
389
|
+
- unified resource schema and permission ownership
|
|
390
|
+
- form-oriented integration on top of query state
|
|
391
|
+
- centralized invalidation semantics for larger UI systems
|
|
392
|
+
|
|
56
393
|
## Relationship to the server-data ladder
|
|
57
394
|
|
|
58
|
-
|
|
395
|
+
Think about the layers like this:
|
|
59
396
|
|
|
60
397
|
- `$fetch` → direct request access
|
|
61
398
|
- `$api` → business-oriented service methods
|
|
62
|
-
- `Model` → cached, reusable,
|
|
399
|
+
- `Model` → cached, reusable, persistence-aware state
|
|
63
400
|
|
|
64
401
|
That makes the model layer one of the most important bridges between backend contracts and frontend rendering.
|
|
65
402
|
|
|
66
|
-
##
|
|
403
|
+
## Practical design checks
|
|
404
|
+
|
|
405
|
+
When adding frontend state, avoid jumping straight to ad hoc request logic or generic store habits.
|
|
406
|
+
|
|
407
|
+
Instead ask:
|
|
408
|
+
|
|
409
|
+
1. should this state live in an existing model?
|
|
410
|
+
2. should it be `data`, `mem`, `local`, `cookie`, or `db` state?
|
|
411
|
+
3. should the model own invalidation or refetch behavior?
|
|
412
|
+
4. does persistence or SSR change the right helper choice?
|
|
413
|
+
5. should the page/controller consume a model instead of owning this state directly?
|
|
414
|
+
|
|
415
|
+
That usually produces cleaner, more Cabloy-native code.
|
|
416
|
+
|
|
417
|
+
## Final takeaway
|
|
418
|
+
|
|
419
|
+
The most important usage insight is simple:
|
|
67
420
|
|
|
68
|
-
|
|
421
|
+
> In Zova, model state is not only “fetch some data”. It is a unified model-owned runtime for query state, local state, persistence, invalidation, and SSR-aware reuse.
|
|
69
422
|
|
|
70
|
-
|
|
423
|
+
Once that is clear, the helper family in `a-model` reads as one coherent system instead of several unrelated APIs.
|
|
@@ -30,12 +30,15 @@ npm run zova :openapi:config demo-student
|
|
|
30
30
|
|
|
31
31
|
## Module-level config
|
|
32
32
|
|
|
33
|
-
Each module
|
|
33
|
+
Each module must explicitly declare which backend operations belong to it.
|
|
34
34
|
|
|
35
35
|
Representative idea:
|
|
36
36
|
|
|
37
|
-
- configure
|
|
37
|
+
- configure `operations.match` or `operations.ignore` in the module OpenAPI config
|
|
38
38
|
- keep API ownership aligned with the module boundary
|
|
39
|
+
- avoid generating large amounts of unrelated SDK output for the current module
|
|
40
|
+
|
|
41
|
+
If both `operations.match` and `operations.ignore` are left empty, `:openapi:generate` should fail fast and ask you to define one of them explicitly. This fail-fast feedback helps both developers and AI agents notice the missing ownership rule immediately and correct it before unrelated SDK output is generated.
|
|
39
42
|
|
|
40
43
|
This modular split matters because Cabloy does not treat the frontend as one flat API client surface.
|
|
41
44
|
|
|
@@ -215,6 +215,12 @@ These refactors are supported by Zova CLI commands rather than requiring a fully
|
|
|
215
215
|
|
|
216
216
|
When generating or editing a Zova page, preserve the page/controller mental model instead of rewriting the code into a generic Vue single-file-component pattern.
|
|
217
217
|
|
|
218
|
+
If you want a stronger reading bridge from Vue habits into page-controller source code, also read:
|
|
219
|
+
|
|
220
|
+
- [Reading Zova for Vue Developers](/frontend/reading-zova-for-vue-developers)
|
|
221
|
+
- [Zova Reactivity Under the Hood](/frontend/zova-reactivity-under-the-hood)
|
|
222
|
+
- [Zova Source Reading Map](/frontend/zova-source-reading-map)
|
|
223
|
+
|
|
218
224
|
A better default is:
|
|
219
225
|
|
|
220
226
|
1. use the Zova page generator
|
|
@@ -200,4 +200,8 @@ Read next:
|
|
|
200
200
|
|
|
201
201
|
- [Frontend (Zova)](/frontend/introduction)
|
|
202
202
|
- [Frontend Foundation](/frontend/foundation)
|
|
203
|
+
- [Reading Zova for Vue Developers](/frontend/reading-zova-for-vue-developers)
|
|
204
|
+
- [Zova vs Vue 3 Comparison](/frontend/zova-vs-vue3-comparison)
|
|
205
|
+
- [Zova Reactivity Under the Hood](/frontend/zova-reactivity-under-the-hood)
|
|
206
|
+
- [Zova Source Reading Map](/frontend/zova-source-reading-map)
|
|
203
207
|
- [Design Principles](/frontend/design-principles)
|