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
package/.cabloy-version CHANGED
@@ -1 +1 @@
1
- 5.1.97
1
+ 5.1.98
package/CHANGELOG.md CHANGED
@@ -1,5 +1,32 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.1.98
4
+
5
+ ### Features
6
+
7
+ - Add `a-file` support.
8
+ - Add CLI templates for `a-file`.
9
+ - Add draft finalize flow for `a-image` direct uploads.
10
+ - Add native direct-upload finalize flow.
11
+ - Update the package with the latest feature enhancements.
12
+
13
+ ### Bug Fixes
14
+
15
+ - Repair `a-image` type safety.
16
+ - Support image delivery tokens.
17
+ - Support private file download tokens.
18
+
19
+ ### Improvements
20
+
21
+ - Clarify SSR bundle build hints.
22
+ - Tighten SSR bundle error wording.
23
+ - Simplify `a-file` upload and direct-upload contracts.
24
+ - Simplify `a-image` upload flows and native variants.
25
+ - Simplify the `a-image` resolve-view flow.
26
+ - Tighten `a-image` provider option typing.
27
+ - Remove the unused `a-image` failed status.
28
+ - Optimize `a-image` draft pruning and response URL resolution.
29
+
3
30
  ## 5.1.97
4
31
 
5
32
  ### Improvements
@@ -98,6 +98,7 @@ const fullstackGroups = [
98
98
  text: 'Backend Metadata to Frontend Table Actions Source Reading Map',
99
99
  link: '/fullstack/backend-metadata-to-frontend-table-actions-source-reading-map',
100
100
  },
101
+ { text: 'Fullstack Image Workflow', link: '/fullstack/image-workflow' },
101
102
  { text: 'Backend OpenAPI to Frontend SDK', link: '/fullstack/openapi-to-sdk' },
102
103
  {
103
104
  text: 'Frontend Metadata Back to Backend',
@@ -231,6 +232,7 @@ export default defineConfig({
231
232
  { text: 'Event Guide', link: '/backend/event-guide' },
232
233
  { text: 'Logger Guide', link: '/backend/logger-guide' },
233
234
  { text: 'Upload Guide', link: '/backend/upload-guide' },
235
+ { text: 'Image Guide', link: '/backend/image-guide' },
234
236
  { text: 'Mail Guide', link: '/backend/mail-guide' },
235
237
  { text: 'Serialization Guide', link: '/backend/serialization-guide' },
236
238
  ],
@@ -330,6 +332,10 @@ export default defineConfig({
330
332
  text: 'Reading Zova for Vue Developers',
331
333
  link: '/frontend/reading-zova-for-vue-developers',
332
334
  },
335
+ {
336
+ text: 'State Architecture for Vue Developers',
337
+ link: '/frontend/state-architecture-for-vue-developers',
338
+ },
333
339
  {
334
340
  text: 'Zova vs Vue 3 Comparison',
335
341
  link: '/frontend/zova-vs-vue3-comparison',
@@ -415,6 +421,7 @@ export default defineConfig({
415
421
  items: [
416
422
  { text: 'Component Guide', link: '/frontend/component-guide' },
417
423
  { text: 'Form Guide', link: '/frontend/form-guide' },
424
+ { text: 'Image Guide', link: '/frontend/image-guide' },
418
425
  {
419
426
  text: 'Zova Form Under the Hood',
420
427
  link: '/frontend/zova-form-under-the-hood',
@@ -0,0 +1,544 @@
1
+ # Image Guide
2
+
3
+ This guide explains how to use **Vona image** in Cabloy Basic with a practical, contract-first path.
4
+
5
+ Vona image is not only a file-upload helper. It is a backend image layer built around:
6
+
7
+ - image scenes for business-specific upload policy
8
+ - image providers for storage and delivery behavior
9
+ - token-based upload and signed delivery flows
10
+ - named variants and ad hoc transforms
11
+ - serializer transforms that turn stored image IDs into `DtoImageView` output
12
+
13
+ Use this page together with:
14
+
15
+ - [Upload Guide](/backend/upload-guide)
16
+ - [Serialization Guide](/backend/serialization-guide)
17
+ - [Bean Scene Authoring](/backend/bean-scene-authoring)
18
+ - [OpenAPI Guide](/backend/openapi-guide)
19
+ - [Frontend Image Guide](/frontend/image-guide)
20
+
21
+ > [!TIP]
22
+ > **Image docs path**
23
+ >
24
+ > - **[Backend Image Guide](/backend/image-guide)** — learn image scenes, providers, serializer transforms, delivery policy, and backend lifecycle behavior
25
+ > - **[Frontend Image Guide](/frontend/image-guide)** — learn `basic-image:formFieldImage`, `basic-image:image`, relation previews, and client-side image-field behavior
26
+ >
27
+ > Use the backend guide when the question is about image scenes, providers, serializer transforms, signed delivery, or upload policy enforcement. Use the frontend guide when the question is about forms, tables, previews, or client-side upload UX.
28
+
29
+ Read this page first when your next question is:
30
+
31
+ - which image scene should own this upload flow
32
+ - which provider or delivery mode should be used
33
+ - how `resolveView` or `resolveViews` fit into the DTO contract
34
+
35
+ ## Why the image suite exists
36
+
37
+ Real image handling usually needs more than accepting a multipart file.
38
+
39
+ A typical business flow also needs to answer questions such as:
40
+
41
+ - which upload use cases are allowed
42
+ - which provider should own the file
43
+ - what size and MIME types are allowed
44
+ - whether the client uploads through the app server, uploads directly, or uploads by URL
45
+ - how the image should be delivered later
46
+ - whether delivery should be signed
47
+ - how APIs should expose image data in DTO responses
48
+
49
+ Vona treats that as one backend capability.
50
+
51
+ ## Smallest correct mental model
52
+
53
+ The smallest practical model is:
54
+
55
+ - `@ImageScene(...)` defines a business upload scene
56
+ - `BeanImageUploadPolicy` resolves the scene into provider, client, upload limits, and temp tokens
57
+ - `ControllerImage` exposes the HTTP endpoints
58
+ - `BeanImage` is the main programmatic API for upload, variant URL resolution, download, delete, and DTO-side image view resolution
59
+ - `@ImageProvider(...)` implementations map the generic image contract to concrete storage or CDN behavior
60
+ - serializer transforms `a-image:resolveView` and `a-image:resolveViews` convert stored image IDs into `DtoImageView` output
61
+
62
+ That division is the key to reading the source correctly.
63
+
64
+ ## Suite boundary
65
+
66
+ The image suite lives under `vona/src/suite-vendor/a-image` and is split into:
67
+
68
+ - `a-image`: the core abstraction, controller, persistence, policy, variants, and serializer transforms
69
+ - `image-native`: local filesystem storage plus Sharp-based transforms
70
+ - `image-cloudflare`: Cloudflare Images integration
71
+
72
+ In other words:
73
+
74
+ - `a-image` defines the contract
75
+ - provider modules implement the contract
76
+
77
+ ## Core concepts
78
+
79
+ ### Image provider
80
+
81
+ An image provider is the storage and delivery backend.
82
+
83
+ Providers are registered with `@ImageProvider(...)` and implement the `IImageProviderExecute` contract.
84
+
85
+ Representative built-in providers are:
86
+
87
+ - `image-native:native`
88
+ - `image-cloudflare:cloudflare`
89
+
90
+ Required provider capabilities are:
91
+
92
+ - `upload`
93
+ - `get`
94
+ - `delete`
95
+ - `getVariantUrl`
96
+
97
+ Optional capabilities are:
98
+
99
+ - `uploadUrl`
100
+ - `createDirectUpload`
101
+ - `download`
102
+
103
+ That means a provider can support the basic lifecycle without supporting every upload style.
104
+
105
+ ### Provider client
106
+
107
+ A provider can expose multiple named clients.
108
+
109
+ `BeanImageProvider.getClientOptions(...)` resolves client options by layering:
110
+
111
+ 1. caller defaults
112
+ 2. provider decorator `base`
113
+ 3. provider decorator `clients[clientName]`
114
+ 4. persisted `aImageProvider.clientOptions`
115
+ 5. caller runtime overrides
116
+
117
+ This lets one provider support multiple delivery or credential configurations without changing business code.
118
+
119
+ ### Image scene
120
+
121
+ An image scene is the business-facing upload policy.
122
+
123
+ A scene is registered with `@ImageScene(...)` and can define:
124
+
125
+ - provider selection
126
+ - client selection
127
+ - upload rules
128
+ - optional per-scene metadata
129
+
130
+ Representative scene shape:
131
+
132
+ ```typescript
133
+ @ImageScene({
134
+ upload: {
135
+ maxSize: 2 * 1024 * 1024,
136
+ mimeTypes: ['image/png', 'image/jpeg', 'image/webp'],
137
+ },
138
+ })
139
+ export class ImageSceneStudentImage extends BeanBase {}
140
+ ```
141
+
142
+ This keeps business upload rules in one named backend capability instead of scattering them across controllers and services.
143
+
144
+ ### Image variant
145
+
146
+ A variant is how an image should be delivered.
147
+
148
+ Vona supports two modes:
149
+
150
+ - a named variant such as `original` or `thumbnail`
151
+ - an ad hoc transform expressed through `transformOptions`
152
+
153
+ The built-in named variants are:
154
+
155
+ - `original`
156
+ - `thumbnail`
157
+ - `small`
158
+ - `medium`
159
+ - `large`
160
+ - `cover`
161
+ - `avatar`
162
+
163
+ The source also makes one important rule explicit:
164
+
165
+ - `variantName` and `transformOptions` are mutually exclusive
166
+
167
+ If neither is provided, Vona falls back to the configured default variant, which is currently `original`.
168
+
169
+ ### Signed delivery
170
+
171
+ Vona supports signed delivery in two forms:
172
+
173
+ - **proxy-signed delivery**: Vona creates a temporary delivery token and redirects through `/image/delivery/:imageId`
174
+ - **provider-signed delivery**: the provider generates the final signed URL directly
175
+
176
+ The built-in defaults differ:
177
+
178
+ - `image-native:native` defaults to `signedDeliveryKind: 'proxy'`
179
+ - `image-cloudflare:cloudflare` defaults to `signedDeliveryKind: 'provider'`
180
+
181
+ ## Main backend API
182
+
183
+ The main backend surface is `app.bean.image`.
184
+
185
+ Representative methods are:
186
+
187
+ - `upload(...)`
188
+ - `uploadUrl(...)`
189
+ - `createDirectUpload(...)`
190
+ - `get(...)`
191
+ - `delete(...)`
192
+ - `getVariantUrl(...)`
193
+ - `download(...)`
194
+ - `resolveView(...)`
195
+ - `resolveViews(...)`
196
+
197
+ A useful practical boundary is:
198
+
199
+ - use `BeanImageUploadPolicy` when you need scene resolution and upload-token policy
200
+ - use `app.bean.image` when you need the actual image lifecycle and delivery behavior
201
+
202
+ ## Persistence model
203
+
204
+ The core module persists two main tables.
205
+
206
+ ### `aImage`
207
+
208
+ Each stored image records backend-facing data such as:
209
+
210
+ - provider and client
211
+ - provider resource ID
212
+ - filename and content type
213
+ - size, width, and height
214
+ - signed-delivery requirement
215
+ - variants
216
+ - custom metadata
217
+ - storage path
218
+ - delivery base URL
219
+ - image scene
220
+
221
+ ### `aImageProvider`
222
+
223
+ Provider-client configuration is stored separately and includes fields such as:
224
+
225
+ - `disabled`
226
+ - `providerName`
227
+ - `clientName`
228
+ - `clientOptions`
229
+
230
+ If a provider-client record does not exist yet, `BeanImageProvider` can lazily register it.
231
+
232
+ ## Operational flows
233
+
234
+ ## Upload-token + multipart upload
235
+
236
+ The tokened upload flow is the most important request path.
237
+
238
+ The flow is:
239
+
240
+ 1. call `POST /image/upload-token`
241
+ 2. Vona resolves the image scene and validates the declared `size` and `mimeType`
242
+ 3. Vona issues a temporary upload token that is bound to the upload path
243
+ 4. submit `POST /image/upload` as multipart form data
244
+ 5. send:
245
+ - `token`
246
+ - `image`
247
+ 6. the controller verifies the token, re-checks file size, MIME type, and extension, then uploads through the resolved provider
248
+
249
+ Representative controller shape:
250
+
251
+ ```typescript
252
+ @Web.post('upload-token')
253
+ async createUploadToken(@Arg.body() data: DtoImageUploadTokenRequest) {
254
+ return await this.bean.imageUploadPolicy.createUploadToken(data);
255
+ }
256
+
257
+ @Web.post('upload')
258
+ @Core.fileUpload()
259
+ @Api.contentType('application/json')
260
+ async upload(@Arg.field('token') token: string, @Arg.file('image') file: IUploadFile) {
261
+ const payload = await this.bean.imageUploadPolicy.verifyUploadToken(
262
+ token,
263
+ this.ctx.route.routePathRaw,
264
+ );
265
+ await this._validateUploadFile(file, payload);
266
+ return await this.bean.image.upload(...);
267
+ }
268
+ ```
269
+
270
+ This flow reuses the normal Vona upload model from [Upload Guide](/backend/upload-guide), but adds image-scene policy and a path-bound temporary token.
271
+
272
+ ## Direct upload
273
+
274
+ `POST /image/direct-upload` is for providers that can create an upload target without proxying the entire lifecycle through the shared `POST /image/upload` endpoint.
275
+
276
+ The shared flow is:
277
+
278
+ 1. resolve the image scene and upload policy
279
+ 2. call `app.bean.image.createDirectUpload(...)`
280
+ 3. let the provider create the direct-upload resource
281
+ 4. persist the draft image record
282
+ 5. return provider information plus `uploadUrl`
283
+ 6. upload bytes to the returned `uploadUrl`
284
+ 7. finalize through `POST /image/direct-upload/finalize`
285
+
286
+ The built-in providers currently support this flow in two different ways:
287
+
288
+ - `image-cloudflare:cloudflare` returns a provider-hosted upload target
289
+ - `image-native:native` returns an app-local upload target under `/image-native/direct-upload/:resourceId`, stores the uploaded bytes as a draft file, and promotes that draft into the final native storage path during finalize
290
+
291
+ That provider difference is exactly why the shared API keeps direct upload split into create-upload-target and finalize steps.
292
+
293
+ ## Upload by URL
294
+
295
+ `POST /image/upload-url` is for providers that can ingest a remote URL directly.
296
+
297
+ The flow is:
298
+
299
+ 1. resolve the image scene and upload policy
300
+ 2. validate the declared `size` and `mimeType`
301
+ 3. call `app.bean.image.uploadUrl(...)`
302
+ 4. let the provider fetch or ingest the remote URL
303
+ 5. persist the image record and return the normal image resource result
304
+
305
+ The built-in Cloudflare provider supports this flow.
306
+
307
+ The native provider does not implement `uploadUrl`.
308
+
309
+ ## Signed delivery redirect
310
+
311
+ When delivery is signed and the provider uses proxy delivery, `BeanImage.getVariantUrl(...)` creates a temporary delivery token and returns a local delivery URL such as:
312
+
313
+ - `/image/delivery/:imageId?token=...`
314
+
315
+ The delivery controller then:
316
+
317
+ 1. verifies the delivery token against the request path
318
+ 2. confirms the token image ID matches the request image ID
319
+ 3. redirects to the final target URL
320
+
321
+ This is why native signed delivery behaves differently from Cloudflare signed delivery.
322
+
323
+ ## Configuration defaults
324
+
325
+ The core image config currently defines:
326
+
327
+ ```typescript
328
+ image: {
329
+ defaultVariant: 'original',
330
+ defaultProvider: 'image-native:native',
331
+ defaultClientName: 'default',
332
+ upload: {
333
+ maxSize: 2 * 1024 * 1024,
334
+ mimeTypes: ['image/png', 'image/jpeg', 'image/webp'],
335
+ },
336
+ }
337
+ ```
338
+
339
+ That gives the suite a working default even before a scene overrides anything.
340
+
341
+ ## Provider behavior differences
342
+
343
+ ### Native provider
344
+
345
+ `image-native:native` stores files locally and uses Sharp-backed processing.
346
+
347
+ Practical behavior to know:
348
+
349
+ - it defaults to proxy-signed delivery
350
+ - it can return local static URLs under `/api/static/...`
351
+ - it supports direct upload through an app-local upload target plus shared finalize
352
+ - named variants produce stable variant files such as `__thumbnail`
353
+ - ad hoc transforms produce stable hashed files such as `__t_<hash>`
354
+ - repeated requests for the same custom transform reuse the same cached transformed output
355
+ - when downloading the unsigned original variant, it can return a buffer instead of a URL
356
+ - it still does not implement `uploadUrl(...)` for provider-side remote URL ingestion
357
+
358
+ These behaviors are demonstrated directly in the native image tests.
359
+
360
+ ### Cloudflare provider
361
+
362
+ `image-cloudflare:cloudflare` delegates storage and delivery to Cloudflare Images.
363
+
364
+ Practical behavior to know:
365
+
366
+ - it defaults to provider-signed delivery
367
+ - it supports direct upload and upload by URL
368
+ - `original` resolves to Cloudflare’s `public` delivery path
369
+ - ad hoc transforms become Cloudflare transform segments such as `w=320,h=180,fit=cover`
370
+ - signed delivery appends `exp` and `sig` query parameters
371
+
372
+ These behaviors are demonstrated in the Cloudflare mapping tests.
373
+
374
+ ## Business integration patterns
375
+
376
+ The best way to understand Vona image is to follow a real business example.
377
+
378
+ ### Single-image pattern: `training-student`
379
+
380
+ The training-student module stores one image field:
381
+
382
+ ```typescript
383
+ @Api.field(
384
+ v.title($locale('StudentImage')),
385
+ v.optional(),
386
+ ZovaRender.field('basic-image:formFieldImage', {
387
+ imageScene: 'training-student:studentImage',
388
+ accept: ['image/png', 'image/jpeg', 'image/webp'],
389
+ maxSize: 2 * 1024 * 1024,
390
+ enableCrop: true,
391
+ cropAspectRatio: 1,
392
+ relationName: 'image',
393
+ resize: {
394
+ width: 512,
395
+ height: 512,
396
+ fit: 'cover',
397
+ format: 'jpeg',
398
+ quality: 90,
399
+ },
400
+ }),
401
+ ZovaRender.cell('basic-image:image', { relationName: 'image' }),
402
+ v.tableIdentity(),
403
+ )
404
+ imageId?: TableIdentity;
405
+ ```
406
+
407
+ The important backend point is not the frontend widget itself.
408
+
409
+ The important point is that the backend contract names a scene:
410
+
411
+ - `training-student:studentImage`
412
+
413
+ That scene centralizes the upload policy while the entity field keeps the persisted value as `imageId`.
414
+
415
+ If you want the frontend-side meaning of that same field contract — how `imageId` pairs with `image`, how form previews refresh, and how the table thumbnail works — continue with [Frontend Image Guide](/frontend/image-guide).
416
+
417
+ ### DTO-side image view resolution
418
+
419
+ The same module exposes the stored image ID through a resolved relation field in DTO output:
420
+
421
+ ```typescript
422
+ @Api.field(
423
+ ZovaRender.visible(false),
424
+ v.optional(),
425
+ v.serializerTransform('a-image:resolveView', {
426
+ fieldName: 'imageId',
427
+ imageScene: 'training-student:studentImage',
428
+ }),
429
+ v.object(DtoImageView),
430
+ )
431
+ image?: DtoImageView;
432
+ ```
433
+
434
+ This means:
435
+
436
+ - the stored field remains `imageId`
437
+ - the DTO adds the resolved relation field `image?: DtoImageView`
438
+ - serialization resolves the image lazily through `app.bean.image.resolveView(...)`
439
+
440
+ Remember the serializer rule from [Serialization Guide](/backend/serialization-guide): field-level serializer metadata only runs when the target API enables serialization.
441
+
442
+ ### Multi-image pattern: `training-record`
443
+
444
+ The training-record module shows the array case.
445
+
446
+ The entity stores `sceneImageIds?: TableIdentity[]` and points to the scene:
447
+
448
+ - `training-record:sceneImage`
449
+
450
+ That scene allows multiple uploads.
451
+
452
+ The DTO then resolves the stored array with:
453
+
454
+ ```typescript
455
+ @Api.field(
456
+ ZovaRender.visible(false),
457
+ v.optional(),
458
+ v.serializerTransform('a-image:resolveViews', {
459
+ fieldName: 'sceneImageIds',
460
+ imageScene: 'training-record:sceneImage',
461
+ }),
462
+ v.array(DtoImageView),
463
+ )
464
+ sceneImages?: DtoImageView[];
465
+ ```
466
+
467
+ This is the cleanest pattern when business storage should keep image IDs, while API output should expose resolved relation fields with view-ready image objects.
468
+
469
+ ## Extending the system
470
+
471
+ ## Add a new image scene
472
+
473
+ When the business needs a new image use case, add a new `@ImageScene(...)` bean.
474
+
475
+ A scene is the right place for:
476
+
477
+ - upload limits
478
+ - provider routing
479
+ - provider client selection
480
+ - scene-specific metadata
481
+ - the decision about whether the use case is single-image or multi-image
482
+
483
+ The core module already provides a boilerplate path for image scenes:
484
+
485
+ - `vona/src/suite-vendor/a-image/modules/a-image/cli/imageScene/boilerplate/`
486
+
487
+ A practical sequence is:
488
+
489
+ 1. create the scene bean
490
+ 2. define `upload` rules and optional provider/meta resolvers
491
+ 3. point entity or DTO metadata at the new scene name
492
+ 4. verify upload-token and serializer behavior end to end
493
+
494
+ ## Add a new image provider
495
+
496
+ When the storage backend changes, add a new `@ImageProvider(...)` bean that implements `IImageProviderExecute`.
497
+
498
+ The provider boilerplate lives under:
499
+
500
+ - `vona/src/suite-vendor/a-image/modules/a-image/cli/imageProvider/boilerplate/`
501
+
502
+ Model new providers after the built-in implementations:
503
+
504
+ - `image-native` for local filesystem plus transform behavior
505
+ - `image-cloudflare` for remote CDN-backed delivery behavior
506
+
507
+ A practical provider checklist is:
508
+
509
+ 1. implement the required methods
510
+ 2. decide whether to support `uploadUrl`, `createDirectUpload`, and `download`
511
+ 3. define sensible base options in the decorator
512
+ 4. verify variant URL behavior, signed delivery behavior, and delete behavior
513
+
514
+ ## Test-backed reading map
515
+
516
+ The current repo already contains strong executable examples under:
517
+
518
+ - `vona/src/suite-vendor/a-test/modules/test-image/test/`
519
+
520
+ The most useful tests are:
521
+
522
+ - `imageUpload.test.ts` for upload-token, upload, direct-upload, and upload-url request flows
523
+ - `imageNative.test.ts` for local upload, native direct-upload/finalize, variant URLs, signed delivery, and delete behavior
524
+ - `imageCloudflareMapping.test.ts` for Cloudflare mapping, custom transforms, signed delivery, direct upload, and upload-by-URL behavior
525
+
526
+ When the source and a prose explanation seem to disagree, prefer the current source and tests.
527
+
528
+ ## Verification checklist
529
+
530
+ When writing or changing image-related docs, verify in this order:
531
+
532
+ 1. check the controller flow in `ControllerImage`
533
+ 2. check scene and token behavior in `BeanImageUploadPolicy`
534
+ 3. check delivery and DTO view behavior in `BeanImage`
535
+ 4. check provider-specific differences in `image-native` and `image-cloudflare`
536
+ 5. check business examples in `training-student` and `training-record`
537
+ 6. check the test-image suite for executable confirmation
538
+ 7. run the docs build and confirm the page appears in the sidebar
539
+
540
+ A good final question is:
541
+
542
+ - does this explanation stay backend-first and source-backed, or has it drifted into a generic image-upload article?
543
+
544
+ If it stays centered on scenes, providers, variants, signed delivery, and serializer transforms, it is aligned with how Vona image actually works.