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.
Files changed (149) hide show
  1. package/.claude/hooks/contract-loop-gate.ts +296 -0
  2. package/.claude/settings.json +16 -0
  3. package/.claude/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +1 -0
  4. package/.claude/skills/cabloy-contract-loop/SKILL.md +103 -14
  5. package/.claude/skills/cabloy-contract-loop/references/contract-loop-map.md +126 -12
  6. package/.claude/skills/cabloy-contract-loop/references/resource-custom-state-pattern.md +148 -0
  7. package/.claude/skills/cabloy-contract-loop/references/verification-checklist.md +49 -13
  8. package/.claude/skills/cabloy-frontend-scaffold/SKILL.md +11 -0
  9. package/.claude/skills/cabloy-frontend-scaffold/references/follow-up-checklist.md +2 -0
  10. package/.claude/skills/cabloy-module-removal/SKILL.md +144 -0
  11. package/.claude/skills/cabloy-resource-field-update/SKILL.md +274 -0
  12. package/.claude/skills/cabloy-resource-field-update/evals/evals.json +53 -0
  13. package/.claude/skills/cabloy-resource-field-update/references/custom-renderer-demo-checklist.md +102 -0
  14. package/.claude/skills/cabloy-resource-field-update/references/field-update-decision-tree.md +120 -0
  15. package/.claude/skills/cabloy-resource-field-update/references/follow-up-checklist.md +80 -0
  16. package/.claude/skills/cabloy-resource-field-update/references/verification-checklist.md +97 -0
  17. package/.claude/skills/cabloy-zova-source-reading/SKILL.md +221 -0
  18. package/.claude/skills/cabloy-zova-source-reading/references/analysis-modes.md +91 -0
  19. package/.claude/skills/cabloy-zova-source-reading/references/core-reading-paths.md +117 -0
  20. package/.github/workflows/docs-pages.yml +2 -0
  21. package/.github/workflows/vona-cov-pg.yml +2 -0
  22. package/.github/workflows/vona-test-crud.yml +4 -2
  23. package/.github/workflows/vona-test-mysql.yml +2 -0
  24. package/.github/workflows/vona-test-pg.yml +2 -0
  25. package/.github/workflows/vona-test-sqlite3.yml +2 -0
  26. package/.github/workflows/vona-tsc.yml +2 -0
  27. package/.github/workflows/zova-ui.yml +2 -0
  28. package/.gitignore +0 -4
  29. package/CHANGELOG.md +52 -0
  30. package/CLAUDE.md +12 -0
  31. package/README.md +15 -0
  32. package/cabloy-docs/.vitepress/config.mjs +89 -0
  33. package/cabloy-docs/ai/class-placement-rule.md +2 -0
  34. package/cabloy-docs/ai/cli-to-skill-map.md +14 -0
  35. package/cabloy-docs/ai/docs-skills-rules-mapping.md +14 -0
  36. package/cabloy-docs/ai/future-skill-roadmap.md +27 -9
  37. package/cabloy-docs/ai/introduction.md +1 -0
  38. package/cabloy-docs/ai/playbook-backend-module.md +6 -0
  39. package/cabloy-docs/ai/playbook-module-removal.md +164 -0
  40. package/cabloy-docs/ai/skills.md +11 -0
  41. package/cabloy-docs/backend/bean-scene-authoring.md +350 -0
  42. package/cabloy-docs/backend/cli.md +26 -1
  43. package/cabloy-docs/backend/dto-guide.md +6 -0
  44. package/cabloy-docs/backend/entity-guide.md +18 -0
  45. package/cabloy-docs/backend/foundation.md +28 -3
  46. package/cabloy-docs/backend/introduction.md +10 -0
  47. package/cabloy-docs/backend/serialization-guide.md +10 -0
  48. package/cabloy-docs/backend/service-guide.md +2 -0
  49. package/cabloy-docs/backend/status-guide.md +271 -0
  50. package/cabloy-docs/backend/websocket-call-flow.md +435 -0
  51. package/cabloy-docs/backend/websocket-guide.md +455 -0
  52. package/cabloy-docs/backend/websocket-protocol-guide.md +381 -0
  53. package/cabloy-docs/backend/websocket-usage-guide.md +356 -0
  54. package/cabloy-docs/frontend/api-guide.md +2 -0
  55. package/cabloy-docs/frontend/bean-scene-authoring.md +374 -0
  56. package/cabloy-docs/frontend/behavior-guide.md +449 -0
  57. package/cabloy-docs/frontend/cli.md +24 -0
  58. package/cabloy-docs/frontend/command-scene-authoring.md +495 -0
  59. package/cabloy-docs/frontend/design-principles.md +6 -0
  60. package/cabloy-docs/frontend/fetch-interceptor-guide.md +440 -0
  61. package/cabloy-docs/frontend/form-guide.md +795 -0
  62. package/cabloy-docs/frontend/foundation.md +29 -0
  63. package/cabloy-docs/frontend/introduction.md +17 -1
  64. package/cabloy-docs/frontend/ioc-and-beans.md +16 -9
  65. package/cabloy-docs/frontend/mock-guide.md +1 -0
  66. package/cabloy-docs/frontend/model-architecture.md +252 -39
  67. package/cabloy-docs/frontend/model-resource-best-practices.md +379 -0
  68. package/cabloy-docs/frontend/model-resource-cookbook.md +505 -0
  69. package/cabloy-docs/frontend/model-resource-owner-pattern.md +382 -0
  70. package/cabloy-docs/frontend/model-resource-usage-guide.md +318 -0
  71. package/cabloy-docs/frontend/model-state-guide.md +366 -13
  72. package/cabloy-docs/frontend/openapi-sdk-guide.md +5 -2
  73. package/cabloy-docs/frontend/page-guide.md +6 -0
  74. package/cabloy-docs/frontend/quickstart.md +4 -0
  75. package/cabloy-docs/frontend/reading-zova-for-vue-developers.md +266 -0
  76. package/cabloy-docs/frontend/router-tabs-admin-web-comparison.md +206 -0
  77. package/cabloy-docs/frontend/router-tabs-introduction.md +106 -0
  78. package/cabloy-docs/frontend/router-tabs-mechanism.md +469 -0
  79. package/cabloy-docs/frontend/router-tabs-overview.md +227 -0
  80. package/cabloy-docs/frontend/router-tabs-route-meta-cookbook.md +343 -0
  81. package/cabloy-docs/frontend/server-data.md +2 -0
  82. package/cabloy-docs/frontend/ssr-architecture-overview.md +211 -0
  83. package/cabloy-docs/frontend/ssr-build-deploy-guide.md +308 -0
  84. package/cabloy-docs/frontend/ssr-review-checklist.md +184 -0
  85. package/cabloy-docs/frontend/ssr-troubleshooting-guide.md +301 -0
  86. package/cabloy-docs/frontend/zova-form-source-reading-map.md +295 -0
  87. package/cabloy-docs/frontend/zova-form-under-the-hood.md +556 -0
  88. package/cabloy-docs/frontend/zova-reactivity-under-the-hood.md +320 -0
  89. package/cabloy-docs/frontend/zova-source-reading-map.md +327 -0
  90. package/cabloy-docs/frontend/zova-vs-vue3-comparison.md +308 -0
  91. package/cabloy-docs/fullstack/contract-loop-playbook.md +350 -0
  92. package/cabloy-docs/fullstack/framework-performance.md +3 -3
  93. package/cabloy-docs/fullstack/frontend-metadata-to-backend.md +44 -1
  94. package/cabloy-docs/fullstack/introduction.md +40 -0
  95. package/cabloy-docs/fullstack/openapi-to-sdk.md +19 -9
  96. package/cabloy-docs/fullstack/quickstart.md +7 -1
  97. package/cabloy-docs/fullstack/tutorial-1-first-module.md +111 -0
  98. package/cabloy-docs/fullstack/tutorial-2-first-crud.md +122 -0
  99. package/cabloy-docs/fullstack/tutorial-3-frontend-metadata-sharing.md +131 -0
  100. package/cabloy-docs/fullstack/tutorial-4-custom-level-renderers.md +144 -0
  101. package/cabloy-docs/fullstack/tutorial-5-backend-contract-sharing.md +146 -0
  102. package/cabloy-docs/fullstack/tutorial-6-one-contract-four-uses.md +170 -0
  103. package/cabloy-docs/fullstack/tutorials-overview.md +192 -0
  104. package/cabloy-docs/index.md +4 -3
  105. package/cabloy-docs/reference/bean-scene-boilerplates.md +75 -0
  106. package/cabloy-docs/reference/cli-reference.md +2 -0
  107. package/package.json +7 -2
  108. package/scripts/initTestData.ts +25 -0
  109. package/scripts/upgrade.ts +17 -2
  110. package/vona/packages-cli/cabloy-cli/package.json +2 -2
  111. package/vona/packages-cli/cli/package.json +1 -1
  112. package/vona/packages-cli/cli-set-api/package.json +1 -1
  113. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.create.module.ts +4 -0
  114. package/vona/packages-vona/vona/package.json +1 -1
  115. package/vona/pnpm-lock.yaml +226 -1091
  116. package/vona/pnpm-workspace.yaml +0 -1
  117. package/vona/src/suite-vendor/a-vona/modules/a-core/assets/static/img/vona.svg +1 -1
  118. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  119. package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
  120. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/bean.permission.ts +1 -1
  121. package/vona/src/suite-vendor/a-vona/modules/a-upload/package.json +2 -2
  122. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  123. package/zova/package.original.json +1 -1
  124. package/zova/packages-cli/cli/package.json +3 -3
  125. package/zova/packages-cli/cli-set-front/cli/templates/init/icon/boilerplate/icons/default/zova.svg +1 -1
  126. package/zova/packages-cli/cli-set-front/cli/templates/openapi/config/boilerplate/module/openapi.config.ts +6 -1
  127. package/zova/packages-cli/cli-set-front/package.json +3 -3
  128. package/zova/packages-cli/cli-set-front/src/lib/bean/cli.create.module.ts +4 -0
  129. package/zova/packages-cli/cli-set-front/src/lib/bean/cli.openapi.generate.ts +34 -4
  130. package/zova/packages-cli/cli-set-front/src/lib/command/create.bean.ts +5 -1
  131. package/zova/packages-utils/zova-vite/package.json +2 -2
  132. package/zova/packages-zova/zova/package.json +2 -2
  133. package/zova/pnpm-lock.yaml +282 -1311
  134. package/zova/pnpm-workspace.yaml +0 -1
  135. package/zova/src/suite/a-home/modules/home-icon/icons/social/cabloy.svg +1 -1
  136. package/zova/src/suite/a-home/modules/home-icon/icons/social/vona.svg +1 -1
  137. package/zova/src/suite/a-home/modules/home-icon/icons/social/zova.svg +1 -1
  138. package/zova/src/suite/a-home/modules/home-icon/src/.metadata/icons/groups/social.svg +3 -3
  139. package/zova/src/suite/cabloy-basic/modules/basic-select/src/component/formFieldSelect/controller.tsx +9 -0
  140. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
  141. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +66 -16
  142. package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
  143. package/zova/src/suite-vendor/a-zova/modules/a-routertabs/package.json +1 -1
  144. package/zova/src/suite-vendor/a-zova/modules/a-routertabs/src/model/tabs.ts +60 -18
  145. package/zova/src/suite-vendor/a-zova/modules/a-table/cli/tableActionRow/boilerplate/{{sceneName}}.{{beanName}}.tsx_ +6 -1
  146. package/zova/src/suite-vendor/a-zova/modules/a-table/cli/tableCell/boilerplate/{{sceneName}}.{{beanName}}.tsx_ +6 -1
  147. package/zova/src/suite-vendor/a-zova/modules/a-table/package.json +1 -1
  148. package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
  149. package/zova/src/suite-vendor/a-zova/package.json +4 -4
@@ -0,0 +1,382 @@
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 to apply this pattern in your own module, continue with [Using `ModelResource` in Your Module](/frontend/model-resource-usage-guide).
12
+
13
+ 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).
14
+
15
+ If your next question is which common extension shapes to implement first, continue with [Resource Model Cookbook](/frontend/model-resource-cookbook).
16
+
17
+ ## Why this page exists
18
+
19
+ Many examples of Zova Model start from a small feature model:
20
+
21
+ - one list query
22
+ - one item query
23
+ - one or two mutations
24
+
25
+ Those examples are useful, but they can accidentally make Model look smaller than it really is.
26
+
27
+ The `rest-resource` specimen shows a larger pattern:
28
+
29
+ > a model can become the stable owner of one whole resource domain boundary.
30
+
31
+ That means the model is no longer only a query wrapper.
32
+
33
+ It becomes the place that owns:
34
+
35
+ - resource bootstrap
36
+ - schema access
37
+ - permissions access
38
+ - form integration
39
+ - query state
40
+ - mutation state
41
+ - cache invalidation policy
42
+
43
+ This page explains that pattern explicitly.
44
+
45
+ ## What “resource owner” means in Zova
46
+
47
+ In this context, a resource-owner model is a model bean that becomes the reusable frontend boundary for one backend resource.
48
+
49
+ It is responsible for presenting one coherent surface to the rest of the UI.
50
+
51
+ Instead of asking every page, table, form, and action to individually know:
52
+
53
+ - how to resolve the resource API path
54
+ - how to fetch list data
55
+ - how to fetch row data
56
+ - how to submit create/update/delete requests
57
+ - how to obtain schema metadata
58
+ - how to choose invalidation rules
59
+
60
+ those decisions are centralized inside the model.
61
+
62
+ A practical reading takeaway is:
63
+
64
+ > the resource-owner model is the frontend resource facade.
65
+
66
+ ## The source specimen
67
+
68
+ The main example is:
69
+
70
+ - `zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts`
71
+
72
+ This class is generic:
73
+
74
+ ```typescript
75
+ export class ModelResource<
76
+ Entity = any,
77
+ EntityCreate = Partial<Entity>,
78
+ EntityUpdate = Partial<Entity>,
79
+ > extends BeanModelBase {}
80
+ ```
81
+
82
+ That already tells you something important.
83
+
84
+ This model is designed to be reused across many resource types rather than tied to one page.
85
+
86
+ ## Why `enableSelector` is essential
87
+
88
+ The class is decorated like this:
89
+
90
+ ```typescript
91
+ @Model<IModelOptionsResource>({
92
+ enableSelector: true,
93
+ })
94
+ ```
95
+
96
+ This matters because one generic `ModelResource` class needs to serve many different resources.
97
+
98
+ The resource name is passed into initialization:
99
+
100
+ ```typescript
101
+ protected async __init__(resource: string) {
102
+ await super.__init__(resource);
103
+ this.resource = resource;
104
+ ...
105
+ }
106
+ ```
107
+
108
+ That means the selector becomes the runtime identity for the concrete resource instance.
109
+
110
+ So even when many consumers reuse the same generic class, each resource instance stays isolated.
111
+
112
+ This is not only conceptual isolation.
113
+
114
+ It also affects cache identity.
115
+
116
+ Because Zova Model prefixes query keys by bean identity and selector, the effective cache keys are separated by resource.
117
+
118
+ So logical keys such as:
119
+
120
+ ```typescript
121
+ ['select', '', hashkey(query)]
122
+ ['item', id, 'view']
123
+ ```
124
+
125
+ can safely exist for many resources without colliding.
126
+
127
+ ## Bootstrap before normal state usage
128
+
129
+ A small feature model often starts using queries immediately.
130
+
131
+ `ModelResource` adds a bootstrap step first:
132
+
133
+ ```typescript
134
+ protected async __init__(resource: string) {
135
+ ...
136
+ await this._bootstrap();
137
+ }
138
+ ```
139
+
140
+ And `_bootstrap()` uses:
141
+
142
+ ```typescript
143
+ const queryBootstrap = await $QueryAutoLoad(() => this.$sdk.getBootstrap(this.resource));
144
+ ```
145
+
146
+ Then it resolves:
147
+
148
+ ```typescript
149
+ this.resourceApi = this.sys.util.parseResourceApi(this.resource, queryBootstrap.data.apiPath);
150
+ ```
151
+
152
+ This is an important pattern.
153
+
154
+ The model does not assume that the final operational API path is already known as a hardcoded constant.
155
+
156
+ Instead, it loads resource metadata first, then turns that metadata into the stable runtime resource API boundary.
157
+
158
+ That is one reason this model is better understood as infrastructure rather than only CRUD sugar.
159
+
160
+ ## The model owns resource metadata surfaces
161
+
162
+ Inside `__init__`, the model exposes several computed resource-level surfaces:
163
+
164
+ - `permissions`
165
+ - `formProvider`
166
+ - `schemaView`
167
+ - `schemaCreate`
168
+ - `schemaUpdate`
169
+ - `schemaFilter`
170
+ - `schemaRow`
171
+ - `schemaPages`
172
+
173
+ These are not random conveniences.
174
+
175
+ 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.
176
+
177
+ For example:
178
+
179
+ - permissions affect whether UI actions are available
180
+ - schemas affect view/create/edit/filter rendering
181
+ - form provider affects how forms are assembled
182
+
183
+ This is exactly the kind of cross-cutting ownership that fits a resource-owner model.
184
+
185
+ ## Query ownership pattern
186
+
187
+ The query side is organized around a reusable internal structure.
188
+
189
+ ### List/query state
190
+
191
+ The model exposes list-style querying through:
192
+
193
+ - `selectGeneral(actionPath?, query?)`
194
+ - `select(query?)`
195
+
196
+ These methods use `$useStateData(...)` and place query ownership inside the model.
197
+
198
+ That means the page does not own the fetch details or cache-key policy.
199
+
200
+ ### Row-specific query state
201
+
202
+ The model exposes item-style querying through:
203
+
204
+ - `queryItem(...)`
205
+ - `view(id)`
206
+
207
+ Again, the page consumes a stable model API rather than building row fetch rules ad hoc.
208
+
209
+ A practical reading takeaway is:
210
+
211
+ > pages consume resource semantics; the model owns query semantics.
212
+
213
+ ## Mutation ownership pattern
214
+
215
+ The mutation side follows the same idea.
216
+
217
+ The model does not expose only one-off raw mutation calls.
218
+
219
+ Instead, it builds a reusable mutation layer:
220
+
221
+ - `create()`
222
+ - `update(id)`
223
+ - `delete(id)`
224
+ - `mutationItem(...)`
225
+
226
+ This gives the model one place to enforce mutation-key conventions and invalidation rules.
227
+
228
+ That is an important architectural advantage.
229
+
230
+ Without this model boundary, every page or dialog could invent slightly different invalidation behavior.
231
+
232
+ ## Cache-key design
233
+
234
+ One of the best design lessons in this specimen is the cache-key structure.
235
+
236
+ The class defines three related key helpers:
237
+
238
+ ```typescript
239
+ protected keySelect(actionPath?: string, query?: ITableQuery) {
240
+ return ['select', actionPath ?? '', hashkey(query)] as const;
241
+ }
242
+
243
+ protected keyItemRoot(id: TableIdentity) {
244
+ return ['item', id] as const;
245
+ }
246
+
247
+ protected keyItem(id: TableIdentity, action: string) {
248
+ return ['item', id, action] as const;
249
+ }
250
+ ```
251
+
252
+ This separates resource state into three layers:
253
+
254
+ - list/query scope
255
+ - row root scope
256
+ - row action scope
257
+
258
+ That structure gives the model precise invalidation control.
259
+
260
+ ## Invalidation policy as model-owned policy
261
+
262
+ The model also centralizes invalidation behavior.
263
+
264
+ ### Create
265
+
266
+ `create()` invalidates:
267
+
268
+ - `['select']`
269
+
270
+ That makes sense because a new row affects list-level state first.
271
+
272
+ ### Update/Delete
273
+
274
+ `mutationItem(...)` invalidates:
275
+
276
+ - `['select']`
277
+ - `keyItemRoot(id)`
278
+
279
+ That means:
280
+
281
+ - list views refresh when row mutations change aggregate list state
282
+ - row-specific state refreshes when one row changes
283
+
284
+ This is a very important design point.
285
+
286
+ The model itself defines consistency rules for resource state.
287
+
288
+ That keeps cache policy close to resource semantics rather than scattering it across UI code.
289
+
290
+ ## Form integration pattern
291
+
292
+ `ModelResource` also owns form-facing behavior.
293
+
294
+ Representative methods:
295
+
296
+ - `getFormSchema(formMeta)`
297
+ - `getFormApiSchemas(formMeta)`
298
+ - `getFormMutationSubmit(formMeta, id?)`
299
+ - `getFormData(formMeta, id?)`
300
+ - `getQueryDataDefaultValue(schemaName?)`
301
+
302
+ This is what makes the model a real resource facade instead of only a transport helper.
303
+
304
+ The model translates resource semantics into form semantics.
305
+
306
+ For example:
307
+
308
+ - create form → use create schema and create mutation
309
+ - update form → use update schema and update mutation
310
+ - view form → use view schema and row data query
311
+
312
+ This keeps form orchestration aligned with the same resource owner that already owns queries and mutations.
313
+
314
+ ## Relationship to `$fetch`, `$sdk`, and OpenAPI
315
+
316
+ `ModelResource` also shows how multiple frontend data layers can be composed cleanly.
317
+
318
+ Inside one model boundary it uses:
319
+
320
+ - `$fetch` for direct REST request execution
321
+ - `$sdk` for bootstrap, schema, permissions, and default-value helpers
322
+ - model helpers for cached query/mutation state
323
+
324
+ That composition matters.
325
+
326
+ It shows that the server-data ladder is not a set of isolated choices.
327
+
328
+ A higher-level model can combine those layers into one coherent business-facing API.
329
+
330
+ ## When to use this pattern
331
+
332
+ Use a resource-owner model when:
333
+
334
+ - many screens or widgets depend on the same backend resource
335
+ - schema metadata, permissions, and CRUD behavior should stay aligned
336
+ - form behavior should stay aligned with the same resource owner
337
+ - cache invalidation rules should be centralized
338
+ - you want one stable frontend boundary for resource-level business semantics
339
+
340
+ This pattern is especially strong for admin-style, resource-driven UIs.
341
+
342
+ ## When not to use this pattern
343
+
344
+ Do not reach for this pattern by default when:
345
+
346
+ - the state is truly page-local and disposable
347
+ - the feature only needs one tiny query with no reusable semantics
348
+ - there is no meaningful resource-level schema/permission/form ownership
349
+ - adding a generic resource facade would be heavier than the real business problem
350
+
351
+ In those cases, a smaller feature model may be the better fit.
352
+
353
+ ## A useful comparison
354
+
355
+ A compact comparison is:
356
+
357
+ - `demo-todo` → minimal feature model pattern
358
+ - `home-passport` → SSR-sensitive state ownership pattern
359
+ - `rest-resource` → generic reusable resource-owner pattern
360
+
361
+ Use these three together when reading how far Zova Model can scale.
362
+
363
+ ## Source-reading checklist
364
+
365
+ When you read `ModelResource`, focus on these questions:
366
+
367
+ 1. where does the selector identity come from?
368
+ 2. where does `resourceApi` become available?
369
+ 3. which state belongs to list scope vs row scope?
370
+ 4. which invalidation rules belong to each mutation?
371
+ 5. which responsibilities are query-related and which are schema/form-related?
372
+ 6. what would become duplicated across pages if this model did not exist?
373
+
374
+ Those questions will help you see the architectural pattern, not only the method list.
375
+
376
+ ## Final takeaway
377
+
378
+ The most important insight is simple:
379
+
380
+ > a Zova model can be more than a data hook wrapper. `ModelResource` can become the stable owner of one whole frontend resource boundary.
381
+
382
+ 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.
@@ -0,0 +1,318 @@
1
+ # Using `ModelResource` in Your Module
2
+
3
+ This guide explains the two recommended ways to use `ModelResource` in application code.
4
+
5
+ It is the practical follow-up to [Model Resource Owner Pattern](/frontend/model-resource-owner-pattern).
6
+
7
+ For review guardrails and design checks after applying the pattern, continue with [Resource Model Best Practices and Anti-Patterns](/frontend/model-resource-best-practices).
8
+
9
+ For implementation templates covering common extension scenarios, continue with [Resource Model Cookbook](/frontend/model-resource-cookbook).
10
+
11
+ Use this page when the main question is not only “what does `ModelResource` do?”, but also:
12
+
13
+ - when should I use it directly?
14
+ - when should I add a thin business facade?
15
+ - which responsibilities should remain in the existing resource-owner?
16
+ - where should custom business logic live?
17
+
18
+ ## Why this page exists
19
+
20
+ After reading the `rest-resource` source, the next practical step is usually application.
21
+
22
+ A frontend developer often needs one of these outcomes:
23
+
24
+ 1. use `ModelResource` directly
25
+ 2. add a thin business facade over the existing resource-owner
26
+
27
+ This page explains those two choices.
28
+
29
+ ## The base specimen
30
+
31
+ The base resource-owner model is:
32
+
33
+ - `zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts`
34
+
35
+ Representative consumers that use it through selector-based lookup include:
36
+
37
+ - `zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockPage/controller.tsx`
38
+ - `zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockPageEntry/controller.tsx`
39
+
40
+ Those consumers are important because they show that UI blocks can depend on the resource-owner contract without needing to know the low-level resource logic.
41
+
42
+ ## The first decision: use directly or add a thin facade?
43
+
44
+ A practical decision rule is:
45
+
46
+ ### Use `ModelResource` directly when
47
+
48
+ - the resource is close to standard CRUD
49
+ - default `select` / `view` / `create` / `update` / `delete` behavior is already enough
50
+ - form schema and permission ownership can follow the generic resource path
51
+ - generic blocks can consume the resource-owner surface as-is
52
+ - you mainly want the existing resource-owner facade without extra business semantics
53
+
54
+ ### Add a thin facade over the existing resource-owner when
55
+
56
+ - the resource cache owner already exists through `rest-resource`
57
+ - the business module needs a semantic frontend surface such as `summary(...)`, `selectArchived(...)`, or `deleteForce(...)`
58
+ - you want to follow Cabloy’s bidirectional contract loop **forward chain**
59
+ - the right frontend move is to reuse the existing resource-owner instead of creating a competing cache owner
60
+
61
+ In that case, a thin business model facade is often better than creating a second resource-level state owner.
62
+
63
+ The key point is that the business model does **not** become the cache owner itself.
64
+
65
+ Instead, it delegates row/list/mutation ownership to the existing `ModelResource` instance.
66
+
67
+ ## The key architectural rule
68
+
69
+ The goal is usually **not** to create another resource-owner surface for the same resource.
70
+
71
+ The goal is to keep one stable owner boundary and add business-specific semantics on top of it only when needed.
72
+
73
+ A good mental model is:
74
+
75
+ - `ModelResource` owns the generic resource contract
76
+ - your business-facing model owns semantic convenience methods only when the UI needs them
77
+
78
+ ## Pattern 1: use `ModelResource` directly
79
+
80
+ This is the cleanest option when the generic resource owner already matches the real business shape.
81
+
82
+ Typical signs:
83
+
84
+ - the resource mostly needs standard CRUD
85
+ - generic pages or forms already work well
86
+ - no extra semantic query or mutation surface is needed yet
87
+ - the team benefits more from uniformity than from additional business naming
88
+
89
+ In this mode, the best abstraction is often no extra abstraction.
90
+
91
+ ## Pattern 2: add a thin business facade over the existing resource-owner
92
+
93
+ This is the right option when business semantics appear, but ownership should stay centralized in the existing resource-owner.
94
+
95
+ A representative shape is:
96
+
97
+ ```typescript
98
+ const StudentResource = 'demo-student:student';
99
+
100
+ @Model()
101
+ export class ModelStudent extends BeanModelBase {
102
+ @Use({ beanFullName: 'rest-resource.model.resource' })
103
+ protected get $$modelResource(): ModelResource {
104
+ return usePrepareArg(StudentResource, true);
105
+ }
106
+
107
+ summary(id: TableIdentity) {
108
+ return this.$$modelResource.queryItem({
109
+ id,
110
+ action: 'summary',
111
+ queryFn: async () => {
112
+ const res = await this.scope.api.demoStudent.summary({ params: { id } });
113
+ return res ?? null;
114
+ },
115
+ meta: {
116
+ disableSuspenseOnInit: true,
117
+ },
118
+ });
119
+ }
120
+
121
+ deleteForce(id: TableIdentity) {
122
+ return this.$$modelResource.mutationItem<void, void>({
123
+ id,
124
+ action: 'deleteForce',
125
+ mutationFn: async () => {
126
+ await this.scope.api.demoStudent.deleteForce({ params: { id } });
127
+ },
128
+ });
129
+ }
130
+ }
131
+ ```
132
+
133
+ What this pattern preserves:
134
+
135
+ - one cache owner
136
+ - one selector-based resource identity model
137
+ - one list/item invalidation policy
138
+ - one resource-level state boundary
139
+
140
+ What this pattern adds:
141
+
142
+ - semantic business-facing methods
143
+ - better UI readability
144
+ - a cleaner place for forward-chain frontend follow-up
145
+
146
+ ## What should remain owned by `ModelResource`
147
+
148
+ In both usage modes, these parts should remain conceptually owned by the existing resource-owner pattern:
149
+
150
+ - resource bootstrap
151
+ - `resourceApi` resolution
152
+ - select/view/create/update/delete conventions
153
+ - form schema resolution
154
+ - permission/schema/form-provider surfaces
155
+ - generic list/item invalidation structure
156
+
157
+ That does not mean you can never customize behavior.
158
+
159
+ It means the customization should still reuse the same owner boundary instead of competing with it.
160
+
161
+ ## The safest extension points
162
+
163
+ The safest extension points are usually **new business-facing methods** that delegate to the existing resource-owner helper surfaces.
164
+
165
+ Typical examples:
166
+
167
+ - additional item queries through `queryItem(...)`
168
+ - additional list queries through `selectGeneral(...)`
169
+ - business actions through `mutationItem(...)`
170
+ - resource-specific computed helpers
171
+ - schema or permission convenience helpers
172
+
173
+ This keeps the generic resource contract intact while adding local business semantics.
174
+
175
+ ## Pattern 3: add a row-level custom query through the facade
176
+
177
+ Use this when the resource has a domain-specific query that is not just `select` or `view`.
178
+
179
+ Representative shape:
180
+
181
+ ```typescript
182
+ summary(id: TableIdentity) {
183
+ return this.$$modelResource.queryItem({
184
+ id,
185
+ action: 'summary',
186
+ queryFn: async () => {
187
+ const res = await this.scope.api.demoStudent.summary({ params: { id } });
188
+ return res ?? null;
189
+ },
190
+ });
191
+ }
192
+ ```
193
+
194
+ Why this is a good pattern:
195
+
196
+ - it reuses `queryItem(...)`
197
+ - it stays under the existing resource-owner cache-key structure
198
+ - it keeps row-level query ownership centralized
199
+
200
+ ## Pattern 4: add a list-level query variant through the facade
201
+
202
+ Use this when the resource needs a second list-style endpoint beyond the default `select(query)`.
203
+
204
+ Representative shape:
205
+
206
+ ```typescript
207
+ selectArchived(query?: ITableQuery) {
208
+ return this.$$modelResource.selectGeneral('archived', query);
209
+ }
210
+ ```
211
+
212
+ Why this is a good pattern:
213
+
214
+ - it reuses `selectGeneral(...)`
215
+ - it stays under the existing resource-owner list-key structure
216
+ - it keeps list-level semantics inside the same resource boundary
217
+
218
+ ## Pattern 5: add a custom mutation through the facade
219
+
220
+ Use this when the resource needs business actions beyond create/update/delete.
221
+
222
+ Representative shape:
223
+
224
+ ```typescript
225
+ deleteForce(id: TableIdentity) {
226
+ return this.$$modelResource.mutationItem<void, void>({
227
+ id,
228
+ action: 'deleteForce',
229
+ mutationFn: async () => {
230
+ await this.scope.api.demoStudent.deleteForce({ params: { id } });
231
+ },
232
+ });
233
+ }
234
+ ```
235
+
236
+ Why this is a good pattern:
237
+
238
+ - it reuses `mutationItem(...)`
239
+ - it inherits centralized invalidation behavior
240
+ - it keeps mutation ownership under the same resource owner
241
+
242
+ ## Where custom business logic should live
243
+
244
+ A practical placement rule is:
245
+
246
+ ### Put it in the thin business facade when
247
+
248
+ - it is resource semantics
249
+ - it affects query or mutation ownership through the existing resource-owner
250
+ - it affects resource-level invalidation expectations
251
+ - multiple pages or blocks should reuse it
252
+ - it gives the UI a clearer business-facing surface
253
+
254
+ ### Keep it out of the business facade when
255
+
256
+ - it is only local page presentation logic
257
+ - it is one-off UI formatting with no resource ownership meaning
258
+ - it is unrelated to the resource boundary
259
+
260
+ ## A good layering pattern
261
+
262
+ A clean layering often looks like this:
263
+
264
+ - `ModelResource` → generic resource-owner runtime
265
+ - `ModelStudent` / `ModelArticle` / `ModelOrder` → thin business facade over the existing owner
266
+ - page/component controllers → consume the model and focus on page flow
267
+
268
+ That layering keeps the model architecture expressive without turning every page into a mini resource framework.
269
+
270
+ ## Common mistakes to avoid
271
+
272
+ For the broader review-oriented version of these mistakes, see [Resource Model Best Practices and Anti-Patterns](/frontend/model-resource-best-practices).
273
+
274
+ The usage mistakes to remember here are:
275
+
276
+ ### Mistake 1: creating a competing cache owner
277
+
278
+ If the business model starts owning the same list/item resource state independently, the architecture loses its single resource-owner boundary.
279
+
280
+ ### Mistake 2: bypassing the model with ad hoc `$fetch` in pages
281
+
282
+ If a page keeps calling the same resource endpoints directly while a resource-owner model already exists, the ownership boundary becomes inconsistent.
283
+
284
+ ### Mistake 3: adding wrapper classes with no semantic value
285
+
286
+ If a business model does not add real business-facing semantics, the wrapper may create extra maintenance without improving clarity.
287
+
288
+ ## A practical evolution path
289
+
290
+ A healthy evolution path often looks like this:
291
+
292
+ 1. start with direct `ModelResource` usage
293
+ 2. let generic pages/forms consume it directly
294
+ 3. add a thin business facade only when real business semantics appear
295
+ 4. keep generic page and form blocks consuming the stable resource-owner surface
296
+ 5. keep frontend follow-up thin during forward-chain contract evolution
297
+
298
+ This path keeps the abstraction proportional to the real problem and reduces mental overhead.
299
+
300
+ ## Source-reading checklist for application
301
+
302
+ When deciding how to apply `ModelResource`, ask:
303
+
304
+ 1. is the generic resource-owner already enough?
305
+ 2. does the UI need semantic business-facing methods?
306
+ 3. can the new behavior delegate to `queryItem`, `selectGeneral`, or `mutationItem`?
307
+ 4. which invalidation rules should remain centralized?
308
+ 5. which generic consumers should continue to work unchanged?
309
+
310
+ If you can answer those clearly, your application of the pattern is usually on the right track.
311
+
312
+ ## Final takeaway
313
+
314
+ The most important practical insight is simple:
315
+
316
+ > use `ModelResource` directly when the generic owner already fits; add a thin facade only when the UI needs clearer business semantics.
317
+
318
+ That approach keeps the programming model more uniform and reduces mental burden.