cabloy 5.1.97 → 5.1.98

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 (122) hide show
  1. package/.cabloy-version +1 -1
  2. package/CHANGELOG.md +27 -0
  3. package/cabloy-docs/.vitepress/config.mjs +7 -0
  4. package/cabloy-docs/backend/image-guide.md +544 -0
  5. package/cabloy-docs/frontend/image-guide.md +512 -0
  6. package/cabloy-docs/frontend/introduction.md +2 -0
  7. package/cabloy-docs/frontend/ioc-and-beans.md +34 -3
  8. package/cabloy-docs/frontend/model-architecture.md +2 -0
  9. package/cabloy-docs/frontend/model-resource-owner-pattern.md +2 -0
  10. package/cabloy-docs/frontend/model-state-guide.md +2 -0
  11. package/cabloy-docs/frontend/reading-zova-for-vue-developers.md +10 -8
  12. package/cabloy-docs/frontend/server-data.md +2 -0
  13. package/cabloy-docs/frontend/state-architecture-for-vue-developers.md +218 -0
  14. package/cabloy-docs/frontend/system-startup-guide.md +2 -0
  15. package/cabloy-docs/frontend/zova-vs-vue3-comparison.md +11 -10
  16. package/cabloy-docs/fullstack/image-workflow.md +403 -0
  17. package/cabloy-docs/fullstack/introduction.md +1 -0
  18. package/package.json +1 -1
  19. package/vona/package.original.json +0 -1
  20. package/vona/packages-vona/vona/package.json +1 -1
  21. package/vona/pnpm-lock.yaml +424 -47
  22. package/vona/src/suite/cabloy-basic/modules/basic-siteadmin/src/bean/ssrSite.admin.ts +4 -0
  23. package/vona/src/suite/cabloy-basic/modules/basic-siteweb/src/bean/ssrSite.web.ts +4 -0
  24. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/cli/ssrSite/boilerplate/{{sceneName}}.{{beanName}}.ts_ +3 -0
  25. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/package.json +1 -1
  26. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/service/ssrHandler.ts +11 -2
  27. package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/types/ssrSite.ts +6 -0
  28. package/vona/src/suite-vendor/a-cabloy/package.json +1 -1
  29. package/vona/src/suite-vendor/a-file/modules/a-file/cli/fileProvider/boilerplate/{{sceneName}}.{{beanName}}.ts_ +23 -0
  30. package/vona/src/suite-vendor/a-file/modules/a-file/cli/fileScene/boilerplate/{{sceneName}}.{{beanName}}.ts_ +5 -0
  31. package/vona/src/suite-vendor/a-file/modules/a-file/package.json +66 -0
  32. package/vona/src/suite-vendor/a-file/modules/a-file/src/.metadata/index.ts +425 -0
  33. package/vona/src/suite-vendor/a-file/modules/a-file/src/.metadata/locales.ts +18 -0
  34. package/vona/src/suite-vendor/a-file/modules/a-file/src/.metadata/this.ts +2 -0
  35. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.file.ts +322 -0
  36. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.fileProvider.ts +62 -0
  37. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.fileUploadPolicy.ts +196 -0
  38. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/meta.redlock.ts +11 -0
  39. package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/meta.version.ts +38 -0
  40. package/vona/src/suite-vendor/a-file/modules/a-file/src/config/config.ts +16 -0
  41. package/vona/src/suite-vendor/a-file/modules/a-file/src/config/locale/en-us.ts +1 -0
  42. package/vona/src/suite-vendor/a-file/modules/a-file/src/config/locale/zh-cn.ts +1 -0
  43. package/vona/src/suite-vendor/a-file/modules/a-file/src/controller/file.ts +167 -0
  44. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadRequest.ts +33 -0
  45. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadResponse.ts +41 -0
  46. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDownloadRequest.ts +12 -0
  47. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadResponse.ts +59 -0
  48. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadTokenRequest.ts +24 -0
  49. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadTokenResponse.ts +16 -0
  50. package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadUrlRequest.ts +33 -0
  51. package/vona/src/suite-vendor/a-file/modules/a-file/src/entity/file.ts +55 -0
  52. package/vona/src/suite-vendor/a-file/modules/a-file/src/entity/fileProvider.ts +24 -0
  53. package/vona/src/suite-vendor/a-file/modules/a-file/src/index.ts +4 -0
  54. package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/fileProvider.ts +7 -0
  55. package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/fileScene.ts +7 -0
  56. package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/fileUploadValidation.ts +15 -0
  57. package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/index.ts +3 -0
  58. package/vona/src/suite-vendor/a-file/modules/a-file/src/model/file.ts +10 -0
  59. package/vona/src/suite-vendor/a-file/modules/a-file/src/model/fileProvider.ts +10 -0
  60. package/vona/src/suite-vendor/a-file/modules/a-file/src/types/file.ts +144 -0
  61. package/vona/src/suite-vendor/a-file/modules/a-file/src/types/fileProvider.ts +111 -0
  62. package/vona/src/suite-vendor/a-file/modules/a-file/src/types/fileScene.ts +53 -0
  63. package/vona/src/suite-vendor/a-file/modules/a-file/src/types/index.ts +3 -0
  64. package/vona/src/suite-vendor/a-file/modules/a-file/tsconfig.build.json +11 -0
  65. package/vona/src/suite-vendor/a-file/modules/a-file/tsconfig.json +7 -0
  66. package/vona/src/suite-vendor/a-file/modules/file-cloudflare/package.json +53 -0
  67. package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/.metadata/index.ts +104 -0
  68. package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/.metadata/this.ts +2 -0
  69. package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/bean/fileProvider.cloudflare.ts +123 -0
  70. package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/index.ts +1 -0
  71. package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/service/fileCloudflare.ts +245 -0
  72. package/vona/src/suite-vendor/a-file/modules/file-cloudflare/tsconfig.build.json +11 -0
  73. package/vona/src/suite-vendor/a-file/modules/file-cloudflare/tsconfig.json +7 -0
  74. package/vona/src/suite-vendor/a-file/modules/file-native/package.json +50 -0
  75. package/vona/src/suite-vendor/a-file/modules/file-native/src/.metadata/index.ts +111 -0
  76. package/vona/src/suite-vendor/a-file/modules/file-native/src/.metadata/this.ts +2 -0
  77. package/vona/src/suite-vendor/a-file/modules/file-native/src/bean/fileProvider.native.ts +125 -0
  78. package/vona/src/suite-vendor/a-file/modules/file-native/src/config/config.ts +7 -0
  79. package/vona/src/suite-vendor/a-file/modules/file-native/src/index.ts +1 -0
  80. package/vona/src/suite-vendor/a-file/modules/file-native/src/service/fileNative.ts +125 -0
  81. package/vona/src/suite-vendor/a-file/modules/file-native/tsconfig.build.json +11 -0
  82. package/vona/src/suite-vendor/a-file/modules/file-native/tsconfig.json +7 -0
  83. package/vona/src/suite-vendor/a-file/package.json +14 -0
  84. package/vona/src/suite-vendor/a-file/tsconfig.base.json +4 -0
  85. package/vona/src/suite-vendor/a-file/tsconfig.json +16 -0
  86. package/vona/src/suite-vendor/a-image/modules/a-image/package.json +4 -1
  87. package/vona/src/suite-vendor/a-image/modules/a-image/src/.metadata/index.ts +61 -3
  88. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.image.ts +336 -152
  89. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.imageProvider.ts +21 -14
  90. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.imageUploadPolicy.ts +59 -11
  91. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.index.ts +12 -0
  92. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.redlock.ts +1 -1
  93. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.version.ts +3 -0
  94. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/schedule.imageDraftPrune.ts +29 -0
  95. package/vona/src/suite-vendor/a-image/modules/a-image/src/config/config.ts +3 -0
  96. package/vona/src/suite-vendor/a-image/modules/a-image/src/controller/image.ts +24 -54
  97. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadFinalizeRequest.ts +13 -0
  98. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadFinalizeResponse.ts +10 -0
  99. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadResponse.ts +10 -0
  100. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadResponse.ts +10 -1
  101. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageView.ts +10 -1
  102. package/vona/src/suite-vendor/a-image/modules/a-image/src/entity/image.ts +10 -1
  103. package/vona/src/suite-vendor/a-image/modules/a-image/src/lib/imageUploadValidation.ts +15 -0
  104. package/vona/src/suite-vendor/a-image/modules/a-image/src/lib/index.ts +1 -0
  105. package/vona/src/suite-vendor/a-image/modules/a-image/src/types/image.ts +18 -0
  106. package/vona/src/suite-vendor/a-image/modules/a-image/src/types/imageProvider.ts +28 -2
  107. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/package.json +1 -1
  108. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/bean/imageProvider.cloudflare.ts +13 -1
  109. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/service/imageCloudflare.ts +26 -0
  110. package/vona/src/suite-vendor/a-image/modules/image-native/package.json +6 -2
  111. package/vona/src/suite-vendor/a-image/modules/image-native/src/.metadata/index.ts +37 -0
  112. package/vona/src/suite-vendor/a-image/modules/image-native/src/bean/imageProvider.native.ts +21 -1
  113. package/vona/src/suite-vendor/a-image/modules/image-native/src/controller/image.ts +57 -0
  114. package/vona/src/suite-vendor/a-image/modules/image-native/src/service/imageNative.ts +142 -34
  115. package/vona/src/suite-vendor/a-image/package.json +1 -1
  116. package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
  117. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/entity.ts +2 -4
  118. package/vona/src/suite-vendor/a-vona/modules/a-ormutils/package.json +1 -1
  119. package/vona/src/suite-vendor/a-vona/modules/a-ormutils/src/lib/columns.ts +7 -3
  120. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  121. package/zova/pnpm-lock.yaml +389 -397
  122. package/zova/src/suite/cabloy-basic/modules/basic-image/src/component/formFieldImage/controller.tsx +0 -9
@@ -0,0 +1,218 @@
1
+ # State Architecture for Vue Developers
2
+
3
+ This guide is for Vue developers who already know common Vue state patterns and want the shortest docs-style explanation of how Zova/Cabloy organizes large-project state differently.
4
+
5
+ It does **not** argue that Vue's usual tools are wrong.
6
+ It explains why large projects often need a stronger architecture for state ownership, sharing boundaries, persistence, cache semantics, and SSR behavior.
7
+
8
+ Use this page together with:
9
+
10
+ - [Reading Zova for Vue Developers](/frontend/reading-zova-for-vue-developers)
11
+ - [Zova vs Vue 3 Comparison](/frontend/zova-vs-vue3-comparison)
12
+ - [IoC and Beans](/frontend/ioc-and-beans)
13
+ - [Model Architecture](/frontend/model-architecture)
14
+ - [Model Resource Owner Pattern](/frontend/model-resource-owner-pattern)
15
+
16
+ ## Why this page exists
17
+
18
+ Many Vue teams do not struggle because they picked one obviously bad tool.
19
+
20
+ They struggle because a large codebase gradually accumulates many valid state mechanisms at the same time:
21
+
22
+ - component-local reactive state
23
+ - composables
24
+ - `provide/inject`
25
+ - store layers
26
+ - query cache
27
+ - local persistence
28
+ - SSR-specific state rules
29
+
30
+ Each mechanism can solve a real local problem.
31
+
32
+ The large-project difficulty usually appears later, when the team can no longer answer these questions cleanly:
33
+
34
+ - who owns this state?
35
+ - how far should it be shared?
36
+ - which lifecycle rules apply to it?
37
+ - who owns cache identity and invalidation?
38
+ - is this only a value, or part of a larger resource boundary?
39
+
40
+ That is the architectural problem this page addresses.
41
+
42
+ ## The shortest accurate summary
43
+
44
+ If you only remember one paragraph, remember this one:
45
+
46
+ > Large Vue projects rarely become hard because they need one more state tool. They usually become hard because ownership, sharing boundaries, persistence, cache semantics, and SSR behavior stop composing into one coherent system.
47
+
48
+ Zova's answer is to push those concerns back under a more explicit architecture:
49
+
50
+ - **Controller / Bean** makes ownership more explicit
51
+ - **IoC scopes** make sharing boundaries more explicit
52
+ - **Model** unifies several state families under one model-owned boundary
53
+ - **Resource Owner** keeps query, schema, permissions, forms, and invalidation closer to one resource-level owner
54
+
55
+ ## Start with boundary questions, not only tool questions
56
+
57
+ In many Vue projects, state discussions begin with questions like these:
58
+
59
+ - should this go into a composable?
60
+ - should this move to a store?
61
+ - should we add a query layer?
62
+ - should we persist this in localStorage?
63
+
64
+ Those are reasonable questions.
65
+
66
+ But in a large system, the more important questions usually come first:
67
+
68
+ 1. **Ownership** — which framework-managed object should own this state?
69
+ 2. **Sharing boundary** — is it local, parent/child shared, app-level, or system-level?
70
+ 3. **State family** — is it query-style remote state, in-memory state, local persistence, cookie-backed state, or async persisted state?
71
+ 4. **Lifecycle** — what should survive page disposal, refresh, route transitions, or SSR request boundaries?
72
+ 5. **Resource boundary** — does this state belong to a larger resource owner that also owns schema, permissions, forms, and invalidation?
73
+
74
+ Zova tries to answer those questions structurally instead of leaving them spread across unrelated mechanisms.
75
+
76
+ ## 1. Ownership: make the state host explicit
77
+
78
+ A common Vue mental model starts with local reactive primitives and composition logic inside `setup()`.
79
+
80
+ Zova keeps Vue's reactive foundation, but changes the visible business-facing host. The most important state often lives on framework-managed Controller or Bean instances instead of starting life as scattered local variables.
81
+
82
+ That changes the reading question from:
83
+
84
+ - which composable or local variable holds this?
85
+
86
+ into:
87
+
88
+ - which Controller, Bean, or Model owns this?
89
+
90
+ That is a better scaling question because ownership drift is often the first step toward cache drift, persistence drift, and SSR drift.
91
+
92
+ For the broader authoring-model shift, read [Reading Zova for Vue Developers](/frontend/reading-zova-for-vue-developers) and [Zova vs Vue 3 Comparison](/frontend/zova-vs-vue3-comparison).
93
+
94
+ ## 2. Sharing: treat scope as a first-class architectural decision
95
+
96
+ Large Vue codebases often express different sharing ranges through different mechanisms:
97
+
98
+ - component-local state
99
+ - parent-child props or `provide/inject`
100
+ - app-level stores
101
+ - long-lived globals or imported singletons
102
+
103
+ Zova tries to keep more of those cases inside one bean-and-container architecture.
104
+
105
+ A compact comparison is:
106
+
107
+ | Sharing range | Common Vue pattern | Zova framing |
108
+ | ------------------ | ------------------------------------------------------- | --------------------------------------------------- |
109
+ | component-internal | local state or component-local composables | `ctx`-scoped bean ownership |
110
+ | between-components | props/emits, parent-owned composables, `provide/inject` | hierarchical injection such as `host` or `skipSelf` |
111
+ | app-global | store or app-level provided state | `app`-scoped bean ownership |
112
+ | system-level | module singleton or long-lived imported state | `sys`-scoped bean ownership |
113
+
114
+ The key idea is simple:
115
+
116
+ > Do not start by switching mechanisms. Start by choosing the sharing boundary.
117
+
118
+ This distinction becomes especially important in SSR, where app/request state and system-level state should not be mixed casually.
119
+
120
+ Read together with [IoC and Beans](/frontend/ioc-and-beans) and [System Startup Guide](/frontend/system-startup-guide).
121
+
122
+ ## 3. Model: unify multiple state families under one model boundary
123
+
124
+ In many stacks, remote cache, in-memory shared state, local persistence, cookie state, and async persistent state are handled by separate tools or utilities.
125
+
126
+ Zova Model tries to organize those families under one broader model-owned runtime.
127
+
128
+ The current model layer exposes five main state families:
129
+
130
+ - `data`
131
+ - `mem`
132
+ - `local`
133
+ - `cookie`
134
+ - `db`
135
+
136
+ This means Model is not only a request wrapper and not only another store.
137
+
138
+ Its architectural role is to give several important state categories a shared home for concerns such as:
139
+
140
+ - cache identity
141
+ - invalidation
142
+ - restore behavior
143
+ - persistence strategy
144
+ - SSR-aware hydration behavior
145
+
146
+ Read together with [Model Architecture](/frontend/model-architecture), [Model State Guide](/frontend/model-state-guide), and [Server Data](/frontend/server-data).
147
+
148
+ ## 4. Resource Owner: keep resource semantics together
149
+
150
+ A large frontend resource usually needs more than a query call.
151
+
152
+ The frontend often also needs:
153
+
154
+ - schema
155
+ - permissions
156
+ - forms
157
+ - list and item queries
158
+ - create/update/delete mutations
159
+ - invalidation rules
160
+
161
+ If those concerns are split across pages, form helpers, store logic, and query callbacks, the resource boundary becomes hard to explain.
162
+
163
+ Zova's resource-owner direction tries to keep more of that semantic bundle under one stable owner.
164
+
165
+ A useful shorthand is:
166
+
167
+ > the page consumes resource semantics, while the model owns query and resource semantics.
168
+
169
+ That is why the Resource Owner pattern matters in large applications: it improves not only reuse, but also boundary stability.
170
+
171
+ Read together with [Model Resource Owner Pattern](/frontend/model-resource-owner-pattern) and [Using ModelResource in Your Module](/frontend/model-resource-usage-guide).
172
+
173
+ ## One comparison table
174
+
175
+ | Dimension | Common large-Vue drift | Zova architectural answer |
176
+ | ------------------ | ----------------------------------------------------------------- | --------------------------------------------------------------- |
177
+ | State host | state starts locally, then moves outward case by case | assign ownership first to Controller / Bean / Model |
178
+ | Sharing | sharing range often changes the mechanism | sharing is framed through IoC scopes |
179
+ | Persistence | local, cookie, and async persistence drift into utilities | persistence lives inside model-state families |
180
+ | Cache identity | keys and refresh rules drift across pages and callbacks | model identity and model policy own more of the cache semantics |
181
+ | SSR | request-level and long-lived state can blur together | `app` and `sys` make the distinction structural |
182
+ | Resource semantics | schema, forms, permissions, and invalidation spread across layers | Resource Owner pulls them back under one owner |
183
+
184
+ ## When this architecture matters most
185
+
186
+ This architecture is most valuable when the frontend is:
187
+
188
+ - long-lived
189
+ - maintained by multiple developers
190
+ - SSR-aware
191
+ - cache-heavy
192
+ - persistence-heavy
193
+ - resource-heavy
194
+
195
+ For small or short-lived projects, a lighter scattered approach may still be good enough.
196
+
197
+ The point is not that every Vue project must adopt the same architecture.
198
+ The point is that as complexity rises, boundary control becomes more important than adding one more tool.
199
+
200
+ ## Recommended reading path
201
+
202
+ If this page matches your question, continue in this order:
203
+
204
+ 1. [Reading Zova for Vue Developers](/frontend/reading-zova-for-vue-developers)
205
+ 2. [Zova vs Vue 3 Comparison](/frontend/zova-vs-vue3-comparison)
206
+ 3. [IoC and Beans](/frontend/ioc-and-beans)
207
+ 4. [Model Architecture](/frontend/model-architecture)
208
+ 5. [Model State Guide](/frontend/model-state-guide)
209
+ 6. [Model Resource Owner Pattern](/frontend/model-resource-owner-pattern)
210
+ 7. [System Startup Guide](/frontend/system-startup-guide)
211
+
212
+ ## Final takeaway
213
+
214
+ The most important practical idea is simple:
215
+
216
+ > Large Vue projects usually do not need more state tools first. They need a state architecture that can keep ownership, scope, persistence, cache semantics, SSR behavior, and resource boundaries coherent as the system grows.
217
+
218
+ That is the main reason to read Zova's frontend architecture through Controller / Bean ownership, IoC scopes, Model, and Resource Owner, instead of mapping every state problem back to one more isolated Vue mechanism.
@@ -167,6 +167,8 @@ The legacy system-start docs used route registration as the clearest example bou
167
167
 
168
168
  That distinction is especially important in SSR-capable systems, where app lifecycles can repeat while system-level route and config wiring should not.
169
169
 
170
+ If your main question is how this `app` versus `sys` lifecycle split participates in the broader state architecture of a large Vue application, read [State Architecture for Vue Developers](/frontend/state-architecture-for-vue-developers) together with [IoC and Beans](/frontend/ioc-and-beans).
171
+
170
172
  A practical reading sequence is:
171
173
 
172
174
  1. [Environment and Config Guide](/frontend/environment-config-guide)
@@ -12,6 +12,7 @@ The goal is to explain two things clearly:
12
12
  Use this page together with:
13
13
 
14
14
  - [Reading Zova for Vue Developers](/frontend/reading-zova-for-vue-developers)
15
+ - [State Architecture for Vue Developers](/frontend/state-architecture-for-vue-developers)
15
16
  - [Zova Reactivity Under the Hood](/frontend/zova-reactivity-under-the-hood)
16
17
  - [Zova Source Reading Map](/frontend/zova-source-reading-map)
17
18
 
@@ -40,16 +41,16 @@ The big difference is the **business-facing programming model**.
40
41
 
41
42
  ## Side-by-side comparison table
42
43
 
43
- | Dimension | Zova | Vue 3 default mental model |
44
- | --- | --- | --- |
45
- | Reactive host | controller or bean instance | local `ref` / `reactive` values inside `setup()` |
46
- | Derived state | `$computed()` often assigned to instance fields | `computed()` usually assigned to local variables |
47
- | Main wiring surface | `__init__` and bean lifecycle | `setup()` and composition hooks |
48
- | Route state access | page-controller surface such as `$route`, `$params`, `$query` | composables such as `useRoute()` |
49
- | Sharing model | IoC containers and bean scopes unify more sharing patterns | composables, props/emits, `provide/inject`, and store layers often coexist |
50
- | Render organization | controller-oriented; render can stay in controller or move to render beans | component-oriented; template/render/setup stay the obvious center |
51
- | Growth path | split into controller, render, style, service, model, or other beans | split into composables, child components, and store or helper layers |
52
- | Architectural emphasis | explicit object roles, scope ownership, and runtime-managed surfaces | flexible local composition with more developer-managed assembly |
44
+ | Dimension | Zova | Vue 3 default mental model |
45
+ | ---------------------- | -------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
46
+ | Reactive host | controller or bean instance | local `ref` / `reactive` values inside `setup()` |
47
+ | Derived state | `$computed()` often assigned to instance fields | `computed()` usually assigned to local variables |
48
+ | Main wiring surface | `__init__` and bean lifecycle | `setup()` and composition hooks |
49
+ | Route state access | page-controller surface such as `$route`, `$params`, `$query` | composables such as `useRoute()` |
50
+ | Sharing model | IoC containers and bean scopes unify more sharing patterns | composables, props/emits, `provide/inject`, and store layers often coexist |
51
+ | Render organization | controller-oriented; render can stay in controller or move to render beans | component-oriented; template/render/setup stay the obvious center |
52
+ | Growth path | split into controller, render, style, service, model, or other beans | split into composables, child components, and store or helper layers |
53
+ | Architectural emphasis | explicit object roles, scope ownership, and runtime-managed surfaces | flexible local composition with more developer-managed assembly |
53
54
 
54
55
  ## 1. Reactive host
55
56
 
@@ -0,0 +1,403 @@
1
+ # Fullstack Image Workflow
2
+
3
+ This page explains the fullstack image workflow in Cabloy Basic with a practical, contract-chain path.
4
+
5
+ In this page, **resolved relation field** means the DTO-side field such as `image` or `sceneImages` that carries `DtoImageView` data for frontend preview and display.
6
+
7
+ It fills the gap between the layer-specific image guides:
8
+
9
+ - [Backend Image Guide](/backend/image-guide)
10
+ - [Frontend Image Guide](/frontend/image-guide)
11
+
12
+ Use this page when the real question is not only backend image policy and not only frontend image rendering, but the bridge between the two.
13
+
14
+ Use this page together with:
15
+
16
+ - [Contract Loop Playbook](/fullstack/contract-loop-playbook)
17
+ - [Backend OpenAPI to Frontend SDK](/fullstack/openapi-to-sdk)
18
+ - [Backend Metadata to Frontend Table Actions](/fullstack/backend-metadata-to-frontend-table-actions)
19
+ - [Backend Image Guide](/backend/image-guide)
20
+ - [Frontend Image Guide](/frontend/image-guide)
21
+ - [Upload Guide](/backend/upload-guide)
22
+ - [Serialization Guide](/backend/serialization-guide)
23
+ - [Form Guide](/frontend/form-guide)
24
+ - [Table Guide](/frontend/table-guide)
25
+
26
+ > [!TIP]
27
+ > **Image workflow docs path**
28
+ >
29
+ > - **[Fullstack Image Workflow](/fullstack/image-workflow)** — learn the cross-stack chain from backend image field contract and scene policy to frontend `basic-image` consumption
30
+ > - **[Backend Image Guide](/backend/image-guide)** — learn image scenes, providers, serializer transforms, delivery policy, and backend lifecycle behavior
31
+ > - **[Frontend Image Guide](/frontend/image-guide)** — learn `basic-image:formFieldImage`, `basic-image:image`, relation previews, and client-side image-field behavior
32
+ >
33
+ > Use this page when the question is about how one image field travels across the full stack rather than only one layer.
34
+
35
+ ## Why this page exists
36
+
37
+ The repository now has a backend image guide and a frontend image guide.
38
+
39
+ What contributors and AI workflows often still need is one continuous answer to this narrower question:
40
+
41
+ > how does one image field travel from backend entity and DTO metadata to frontend form and table rendering?
42
+
43
+ This page answers that question by treating image handling as one contract chain rather than as disconnected snippets.
44
+
45
+ ## The shortest correct mental model
46
+
47
+ If you only remember one idea, remember this one:
48
+
49
+ > backend code owns the image field contract, image scene policy, and DTO-side image view resolution, while frontend `basic-image` resources consume that resolved contract for upload, preview, and display.
50
+
51
+ That means an image field usually has two connected surfaces:
52
+
53
+ - a stored identity field such as `imageId` or `sceneImageIds`
54
+ - a resolved relation field such as `image` or `sceneImages`
55
+
56
+ The fullstack workflow works best when both surfaces are authored deliberately and kept aligned.
57
+
58
+ ## The fullstack chain in one view
59
+
60
+ A practical image workflow usually looks like this:
61
+
62
+ 1. the backend entity stores image identity data such as `imageId` or `sceneImageIds`
63
+ 2. backend field metadata points to the frontend image resources with `ZovaRender.field(...)` and `ZovaRender.cell(...)`
64
+ 3. the backend image scene defines upload policy such as MIME type, size, and multiple-image rules
65
+ 4. the backend DTO resolves image IDs into `DtoImageView` or `DtoImageView[]` through `a-image:resolveView` or `a-image:resolveViews`
66
+ 5. the shared contract exposes both the stored ID field and the resolved relation field to frontend consumers
67
+ 6. frontend `basic-image:formFieldImage` binds the stored ID field while reusing the resolved relation field for preview cards and dialogs
68
+ 7. frontend `basic-image:image` uses the same resolved relation field to render thumbnails in list pages
69
+
70
+ This is not only a forward chain and not only a reverse chain.
71
+
72
+ Instead, it is one concrete business thread where backend contract truth, shared metadata, and frontend resource consumption cooperate.
73
+
74
+ For the broader decision model, read [Contract Loop Playbook](/fullstack/contract-loop-playbook).
75
+
76
+ ## Specimen A: single image on Student
77
+
78
+ The Student resource is the best single-image specimen because it shows the complete chain with the smallest number of moving parts.
79
+
80
+ ### Backend entity field
81
+
82
+ The Student entity stores one image identity field and points to the built-in image resources:
83
+
84
+ ```typescript
85
+ @Api.field(
86
+ v.title($locale('StudentImage')),
87
+ v.optional(),
88
+ ZovaRender.field('basic-image:formFieldImage', {
89
+ imageScene: 'training-student:studentImage',
90
+ relationName: 'image',
91
+ accept: ['image/png', 'image/jpeg', 'image/webp'],
92
+ maxSize: 2 * 1024 * 1024,
93
+ enableCrop: true,
94
+ cropAspectRatio: 1,
95
+ resize: {
96
+ width: 512,
97
+ height: 512,
98
+ fit: 'cover',
99
+ format: 'jpeg',
100
+ quality: 90,
101
+ },
102
+ }),
103
+ ZovaRender.cell('basic-image:image', { relationName: 'image' }),
104
+ v.tableIdentity(),
105
+ )
106
+ imageId?: TableIdentity;
107
+ ```
108
+
109
+ This already defines two important truths:
110
+
111
+ - the stored field is `imageId`
112
+ - the frontend resource identities are `basic-image:formFieldImage` and `basic-image:image`
113
+
114
+ ### Backend image scene
115
+
116
+ The Student image scene defines the upload policy:
117
+
118
+ ```typescript
119
+ @ImageScene({
120
+ upload: {
121
+ maxSize: 2 * 1024 * 1024,
122
+ mimeTypes: ['image/png', 'image/jpeg', 'image/webp'],
123
+ },
124
+ })
125
+ export class ImageSceneStudentImage extends BeanBase {}
126
+ ```
127
+
128
+ This means the backend owns the true upload policy even when the frontend also performs convenience validation.
129
+
130
+ ### DTO-side image resolution
131
+
132
+ The Student DTO resolves the stored ID into a resolved relation field:
133
+
134
+ ```typescript
135
+ @Api.field(
136
+ ZovaRender.visible(false),
137
+ v.optional(),
138
+ v.serializerTransform('a-image:resolveView', {
139
+ fieldName: 'imageId',
140
+ imageScene: 'training-student:studentImage',
141
+ }),
142
+ v.object(DtoImageView),
143
+ )
144
+ image?: DtoImageView;
145
+ ```
146
+
147
+ This is the bridge that turns:
148
+
149
+ - stored value: `imageId`
150
+ - resolved preview data: `image?: DtoImageView`
151
+
152
+ The same list-page branch is also visible in `DtoStudentSelectResItem`, where the row DTO resolves `image` for table usage.
153
+
154
+ ### Frontend consumption
155
+
156
+ On the frontend side, the built-in resources consume that contract shape:
157
+
158
+ - `basic-image:formFieldImage` binds the ID field and reuses the `image` resolved relation field for preview cards
159
+ - `basic-image:image` uses the same resolved relation field to render thumbnails in tables
160
+
161
+ This is the cleanest single-image chain in the repo.
162
+
163
+ ## Specimen B: multiple images on Record
164
+
165
+ The Record resource shows the plural branch.
166
+
167
+ ### Backend entity field
168
+
169
+ The Record entity stores an array of image IDs and points to the same built-in resources:
170
+
171
+ ```typescript
172
+ @Api.field(
173
+ v.title($locale('ScenePhotos')),
174
+ v.optional(),
175
+ ZovaRender.field('basic-image:formFieldImage', {
176
+ imageScene: 'training-record:sceneImage',
177
+ multiple: true,
178
+ maxCount: 9,
179
+ relationName: 'sceneImages',
180
+ accept: ['image/png', 'image/jpeg', 'image/webp'],
181
+ maxSize: 2 * 1024 * 1024,
182
+ enableCrop: false,
183
+ resize: {
184
+ width: 1280,
185
+ height: 1280,
186
+ fit: 'contain',
187
+ format: 'jpeg',
188
+ quality: 90,
189
+ },
190
+ }),
191
+ ZovaRender.cell('basic-image:image', { relationName: 'sceneImages' }),
192
+ v.array(v.tableIdentity()),
193
+ )
194
+ sceneImageIds?: TableIdentity[];
195
+ ```
196
+
197
+ ### Backend image scene
198
+
199
+ The matching scene enables multi-image upload policy:
200
+
201
+ ```typescript
202
+ @ImageScene({
203
+ upload: {
204
+ maxSize: 2 * 1024 * 1024,
205
+ mimeTypes: ['image/png', 'image/jpeg', 'image/webp'],
206
+ multiple: true,
207
+ },
208
+ })
209
+ export class ImageSceneSceneImage extends BeanBase {}
210
+ ```
211
+
212
+ ### DTO-side image resolution
213
+
214
+ The DTO resolves many IDs into many preview objects:
215
+
216
+ ```typescript
217
+ @Api.field(
218
+ ZovaRender.visible(false),
219
+ v.optional(),
220
+ v.serializerTransform('a-image:resolveViews', {
221
+ fieldName: 'sceneImageIds',
222
+ imageScene: 'training-record:sceneImage',
223
+ }),
224
+ v.array(DtoImageView),
225
+ )
226
+ sceneImages?: DtoImageView[];
227
+ ```
228
+
229
+ The same pattern also appears in `DtoRecordSelectResItem`, which makes the relation data available for list-page thumbnails.
230
+
231
+ ### Why this branch matters
232
+
233
+ This branch proves that the same workflow scales from:
234
+
235
+ - one ID -> one relation object
236
+ - many IDs -> many relation objects
237
+
238
+ without changing the overall contract-chain model.
239
+
240
+ ## Backend authoring responsibilities
241
+
242
+ The backend owns the contract truth.
243
+
244
+ For image workflows, that usually means:
245
+
246
+ - the entity field defines the stored data shape
247
+ - `ZovaRender.field(...)` chooses the form-side frontend resource identity
248
+ - `ZovaRender.cell(...)` chooses the table-side frontend resource identity
249
+ - the image scene bean defines upload policy
250
+ - the DTO serializer transform resolves stored IDs into preview-ready image view objects
251
+
252
+ A practical rule is:
253
+
254
+ - when the business meaning of the image field changes, start by changing backend truth first
255
+
256
+ For the backend-authoring details, read [Backend Image Guide](/backend/image-guide).
257
+
258
+ ## What crosses the contract boundary
259
+
260
+ The most important thing that crosses the fullstack boundary is not only the raw image ID.
261
+
262
+ What really crosses the boundary is the pairing of:
263
+
264
+ - stored identity field
265
+ - resolved preview field
266
+
267
+ In the single-image branch, that pair is usually:
268
+
269
+ - `imageId`
270
+ - `image?: DtoImageView`
271
+
272
+ In the multi-image branch, that pair is usually:
273
+
274
+ - `sceneImageIds`
275
+ - `sceneImages?: DtoImageView[]`
276
+
277
+ This is why the DTO serializer step matters so much.
278
+
279
+ It turns backend-owned identity data into frontend-friendly preview data without changing the persistence model.
280
+
281
+ ## Frontend consumption responsibilities
282
+
283
+ The frontend does not redefine the image field contract.
284
+
285
+ Instead, it consumes the contract through the built-in image resources.
286
+
287
+ ### `basic-image:formFieldImage`
288
+
289
+ The form-side resource is responsible for:
290
+
291
+ - selecting files
292
+ - performing frontend validation
293
+ - optionally cropping and resizing in the browser
294
+ - requesting an upload token
295
+ - uploading multipart data
296
+ - binding the stored image ID field
297
+ - synchronizing the resolved relation field for immediate UI refresh
298
+
299
+ ### `basic-image:image`
300
+
301
+ The table-side resource is responsible for:
302
+
303
+ - resolving relation-backed preview data first
304
+ - falling back to URL-style values when needed
305
+ - rendering thumbnails
306
+ - opening the shared preview dialog
307
+
308
+ ### Why `relationName` matters
309
+
310
+ The built-in frontend image resources work best when the resolved relation field is named predictably.
311
+
312
+ Common pairings are:
313
+
314
+ - `imageId` -> `image`
315
+ - `sceneImageIds` -> `sceneImages`
316
+
317
+ When your field names are nonstandard, set `relationName` explicitly so backend DTO resolution and frontend preview lookup stay aligned.
318
+
319
+ For the frontend runtime details, read [Frontend Image Guide](/frontend/image-guide).
320
+
321
+ ## Single-image vs multi-image differences
322
+
323
+ The contract chain stays the same, but three details change.
324
+
325
+ ### 1. Stored value shape
326
+
327
+ - single image: scalar ID such as `imageId`
328
+ - multiple images: ID array such as `sceneImageIds`
329
+
330
+ ### 2. DTO serializer transform
331
+
332
+ - single image: `a-image:resolveView`
333
+ - multiple images: `a-image:resolveViews`
334
+
335
+ ### 3. Relation preview shape
336
+
337
+ - single image: one `DtoImageView`
338
+ - multiple images: `DtoImageView[]`
339
+
340
+ That is the main branching point the fullstack doc should make explicit.
341
+
342
+ ## Where this page sits in the contract loop
343
+
344
+ This workflow is easiest to understand when you keep the contract-loop vocabulary precise.
345
+
346
+ - backend field metadata and DTO/image-scene changes belong to backend contract truth
347
+ - image DTO resolution is part of the backend-authored handoff
348
+ - frontend `basic-image` resources are consumers of that truth
349
+ - frontend-specific render behavior is not a reason to duplicate the backend contract
350
+
351
+ That is why this page sits between:
352
+
353
+ - [Contract Loop Playbook](/fullstack/contract-loop-playbook)
354
+ - [Backend OpenAPI to Frontend SDK](/fullstack/openapi-to-sdk)
355
+ - [Backend Metadata to Frontend Table Actions](/fullstack/backend-metadata-to-frontend-table-actions)
356
+
357
+ It is a bridge page for one concrete business thread rather than a general direction guide.
358
+
359
+ ## Verification workflow
360
+
361
+ When writing or changing image workflow docs, verify in this order.
362
+
363
+ 1. confirm the entity field still points to:
364
+ - `basic-image:formFieldImage`
365
+ - `basic-image:image`
366
+ 2. confirm the image scene still matches the specimen field
367
+ 3. confirm the DTO still uses:
368
+ - `a-image:resolveView` for single-image fields
369
+ - `a-image:resolveViews` for multi-image fields
370
+ 4. confirm the list-row DTOs still expose the same resolved relation fields for table usage
371
+ 5. confirm the frontend image resources still consume the same relation-name pattern
372
+ 6. confirm the page stays bridge-focused rather than duplicating the backend or frontend guides
373
+ 7. run the docs build and confirm the page appears under `Fullstack / Architecture & Integration`
374
+
375
+ ## Read together with
376
+
377
+ Use the surrounding docs based on which layer now needs deeper attention.
378
+
379
+ ### Backend policy and authoring details
380
+
381
+ - [Backend Image Guide](/backend/image-guide)
382
+ - [Upload Guide](/backend/upload-guide)
383
+ - [Serialization Guide](/backend/serialization-guide)
384
+ - [Controller Guide](/backend/controller-guide)
385
+
386
+ ### Frontend runtime and rendering details
387
+
388
+ - [Frontend Image Guide](/frontend/image-guide)
389
+ - [Form Guide](/frontend/form-guide)
390
+ - [Table Guide](/frontend/table-guide)
391
+ - [TableCell Authoring Cookbook](/frontend/table-cell-cookbook)
392
+
393
+ ### Broader fullstack bridge and contract-loop decisions
394
+
395
+ - [Contract Loop Playbook](/fullstack/contract-loop-playbook)
396
+ - [Backend OpenAPI to Frontend SDK](/fullstack/openapi-to-sdk)
397
+ - [Backend Metadata to Frontend Table Actions](/fullstack/backend-metadata-to-frontend-table-actions)
398
+
399
+ A good final question is:
400
+
401
+ - am I debugging one layer, or am I debugging the handoff between layers?
402
+
403
+ If the answer is the handoff, this page is the right place to start.
@@ -46,6 +46,7 @@ Use this path when the task is about how backend and frontend stay aligned insid
46
46
  - [Vona + Zova Integration](/fullstack/vona-zova-integration)
47
47
  - [Contract Loop Playbook](/fullstack/contract-loop-playbook)
48
48
  - [Backend Metadata to Frontend Table Actions](/fullstack/backend-metadata-to-frontend-table-actions)
49
+ - [Fullstack Image Workflow](/fullstack/image-workflow)
49
50
  - [Backend OpenAPI to Frontend SDK](/fullstack/openapi-to-sdk)
50
51
  - [Frontend Metadata Back to Backend](/fullstack/frontend-metadata-to-backend)
51
52
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cabloy",
3
- "version": "5.1.97",
3
+ "version": "5.1.98",
4
4
  "gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
5
5
  "description": "A Node.js fullstack framework",
6
6
  "keywords": [