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.
- package/.cabloy-version +1 -1
- package/CHANGELOG.md +27 -0
- package/cabloy-docs/.vitepress/config.mjs +7 -0
- package/cabloy-docs/backend/image-guide.md +544 -0
- package/cabloy-docs/frontend/image-guide.md +512 -0
- package/cabloy-docs/frontend/introduction.md +2 -0
- package/cabloy-docs/frontend/ioc-and-beans.md +34 -3
- package/cabloy-docs/frontend/model-architecture.md +2 -0
- package/cabloy-docs/frontend/model-resource-owner-pattern.md +2 -0
- package/cabloy-docs/frontend/model-state-guide.md +2 -0
- package/cabloy-docs/frontend/reading-zova-for-vue-developers.md +10 -8
- package/cabloy-docs/frontend/server-data.md +2 -0
- package/cabloy-docs/frontend/state-architecture-for-vue-developers.md +218 -0
- package/cabloy-docs/frontend/system-startup-guide.md +2 -0
- package/cabloy-docs/frontend/zova-vs-vue3-comparison.md +11 -10
- package/cabloy-docs/fullstack/image-workflow.md +403 -0
- package/cabloy-docs/fullstack/introduction.md +1 -0
- package/package.json +1 -1
- package/vona/package.original.json +0 -1
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/pnpm-lock.yaml +424 -47
- package/vona/src/suite/cabloy-basic/modules/basic-siteadmin/src/bean/ssrSite.admin.ts +4 -0
- package/vona/src/suite/cabloy-basic/modules/basic-siteweb/src/bean/ssrSite.web.ts +4 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/cli/ssrSite/boilerplate/{{sceneName}}.{{beanName}}.ts_ +3 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/service/ssrHandler.ts +11 -2
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/types/ssrSite.ts +6 -0
- package/vona/src/suite-vendor/a-cabloy/package.json +1 -1
- package/vona/src/suite-vendor/a-file/modules/a-file/cli/fileProvider/boilerplate/{{sceneName}}.{{beanName}}.ts_ +23 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/cli/fileScene/boilerplate/{{sceneName}}.{{beanName}}.ts_ +5 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/package.json +66 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/.metadata/index.ts +425 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/.metadata/locales.ts +18 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/.metadata/this.ts +2 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.file.ts +322 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.fileProvider.ts +62 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/bean.fileUploadPolicy.ts +196 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/meta.redlock.ts +11 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/bean/meta.version.ts +38 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/config/config.ts +16 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/config/locale/en-us.ts +1 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/config/locale/zh-cn.ts +1 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/controller/file.ts +167 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadRequest.ts +33 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDirectUploadResponse.ts +41 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileDownloadRequest.ts +12 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadResponse.ts +59 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadTokenRequest.ts +24 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadTokenResponse.ts +16 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/dto/fileUploadUrlRequest.ts +33 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/entity/file.ts +55 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/entity/fileProvider.ts +24 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/index.ts +4 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/fileProvider.ts +7 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/fileScene.ts +7 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/fileUploadValidation.ts +15 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/lib/index.ts +3 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/model/file.ts +10 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/model/fileProvider.ts +10 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/types/file.ts +144 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/types/fileProvider.ts +111 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/types/fileScene.ts +53 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/src/types/index.ts +3 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/tsconfig.build.json +11 -0
- package/vona/src/suite-vendor/a-file/modules/a-file/tsconfig.json +7 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/package.json +53 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/.metadata/index.ts +104 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/.metadata/this.ts +2 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/bean/fileProvider.cloudflare.ts +123 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/index.ts +1 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/src/service/fileCloudflare.ts +245 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/tsconfig.build.json +11 -0
- package/vona/src/suite-vendor/a-file/modules/file-cloudflare/tsconfig.json +7 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/package.json +50 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/src/.metadata/index.ts +111 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/src/.metadata/this.ts +2 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/src/bean/fileProvider.native.ts +125 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/src/config/config.ts +7 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/src/index.ts +1 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/src/service/fileNative.ts +125 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/tsconfig.build.json +11 -0
- package/vona/src/suite-vendor/a-file/modules/file-native/tsconfig.json +7 -0
- package/vona/src/suite-vendor/a-file/package.json +14 -0
- package/vona/src/suite-vendor/a-file/tsconfig.base.json +4 -0
- package/vona/src/suite-vendor/a-file/tsconfig.json +16 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/package.json +4 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/.metadata/index.ts +61 -3
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.image.ts +336 -152
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.imageProvider.ts +21 -14
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.imageUploadPolicy.ts +59 -11
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.index.ts +12 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.redlock.ts +1 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.version.ts +3 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/schedule.imageDraftPrune.ts +29 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/config/config.ts +3 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/controller/image.ts +24 -54
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadFinalizeRequest.ts +13 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadFinalizeResponse.ts +10 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadResponse.ts +10 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadResponse.ts +10 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageView.ts +10 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/entity/image.ts +10 -1
- package/vona/src/suite-vendor/a-image/modules/a-image/src/lib/imageUploadValidation.ts +15 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/lib/index.ts +1 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/types/image.ts +18 -0
- package/vona/src/suite-vendor/a-image/modules/a-image/src/types/imageProvider.ts +28 -2
- package/vona/src/suite-vendor/a-image/modules/image-cloudflare/package.json +1 -1
- package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/bean/imageProvider.cloudflare.ts +13 -1
- package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/service/imageCloudflare.ts +26 -0
- package/vona/src/suite-vendor/a-image/modules/image-native/package.json +6 -2
- package/vona/src/suite-vendor/a-image/modules/image-native/src/.metadata/index.ts +37 -0
- package/vona/src/suite-vendor/a-image/modules/image-native/src/bean/imageProvider.native.ts +21 -1
- package/vona/src/suite-vendor/a-image/modules/image-native/src/controller/image.ts +57 -0
- package/vona/src/suite-vendor/a-image/modules/image-native/src/service/imageNative.ts +142 -34
- package/vona/src/suite-vendor/a-image/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/entity.ts +2 -4
- package/vona/src/suite-vendor/a-vona/modules/a-ormutils/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-ormutils/src/lib/columns.ts +7 -3
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/pnpm-lock.yaml +389 -397
- 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.
|
|
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.
|