cabloy 5.1.95 → 5.1.97

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 (208) hide show
  1. package/.cabloy-version +1 -0
  2. package/.claude/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +4 -1
  3. package/.claude/skills/cabloy-resource-field-update/SKILL.md +6 -3
  4. package/.github/workflows/vona-test-crud.yml +2 -0
  5. package/.github/workflows/vona-test-mysql.yml +2 -0
  6. package/.github/workflows/vona-test-pg.yml +2 -0
  7. package/.github/workflows/vona-test-sqlite3.yml +2 -0
  8. package/CHANGELOG.md +38 -0
  9. package/CLAUDE.md +1 -0
  10. package/cabloy-docs/ai/playbook-backend-module.md +6 -3
  11. package/cabloy-docs/fullstack/framework-performance.md +3 -3
  12. package/cabloy-docs/fullstack/quickstart.md +1 -1
  13. package/package.json +1 -1
  14. package/scripts/release.ts +27 -1
  15. package/vona/docker-compose-original/docker-compose-dockerfile-app +4 -0
  16. package/vona/docker-compose-original/docker-compose.original.yml +0 -1
  17. package/vona/env/.env +0 -1
  18. package/vona/env/.env.prod +0 -1
  19. package/vona/env/.env.prod.docker +0 -7
  20. package/vona/package.original.json +3 -3
  21. package/vona/packages-cli/cabloy-cli/package.json +2 -2
  22. package/vona/packages-cli/cli/package.json +1 -1
  23. package/vona/packages-cli/cli-set-api/package.json +2 -2
  24. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.build.ts +70 -2
  25. package/vona/packages-utils/babel-plugin-vona-bean-module/package.json +2 -2
  26. package/vona/packages-utils/module-glob/package.json +2 -2
  27. package/vona/packages-utils/module-info-pro/package.json +2 -2
  28. package/vona/packages-utils/utils/package.json +2 -2
  29. package/vona/packages-vona/vona/package.json +1 -1
  30. package/vona/packages-vona/vona-core/package.json +2 -2
  31. package/vona/packages-vona/vona-core/src/lib/addon/addon.ts +77 -0
  32. package/vona/packages-vona/vona-core/src/lib/addon/index.ts +3 -0
  33. package/vona/packages-vona/vona-core/src/lib/addon/sharp.ts +46 -0
  34. package/vona/packages-vona/vona-core/src/lib/addon/sqlite3.ts +23 -0
  35. package/vona/packages-vona/vona-core/src/lib/index.ts +1 -0
  36. package/vona/packages-vona/vona-core/src/lib/utils/index.ts +0 -1
  37. package/vona/packages-vona/vona-core/src/lib/utils/util.ts +5 -0
  38. package/vona/packages-vona/vona-mock/package.json +2 -2
  39. package/vona/pnpm-lock.yaml +441 -62
  40. package/vona/pnpm-workspace.yaml +1 -1
  41. package/vona/src/backend/cli.ts +2 -5
  42. package/vona/src/backend/config/config/config.ts +0 -4
  43. package/vona/src/suite/a-demo/modules/demo-basic/src/bean/ssrMenu.admin.ts +1 -1
  44. package/vona/src/suite/a-training/modules/training-record/src/.metadata/index.ts +26 -0
  45. package/vona/src/suite/a-training/modules/training-record/src/bean/imageScene.sceneImage.ts +11 -0
  46. package/vona/src/suite/a-training/modules/training-record/src/bean/meta.version.ts +1 -0
  47. package/vona/src/suite/a-training/modules/training-record/src/config/locale/en-us.ts +1 -0
  48. package/vona/src/suite/a-training/modules/training-record/src/config/locale/zh-cn.ts +1 -0
  49. package/vona/src/suite/a-training/modules/training-record/src/dto/recordSelectResItem.tsx +12 -0
  50. package/vona/src/suite/a-training/modules/training-record/src/dto/recordView.tsx +12 -0
  51. package/vona/src/suite/a-training/modules/training-record/src/entity/record.tsx +26 -1
  52. package/vona/src/suite/a-training/modules/training-record/src/index.ts +1 -0
  53. package/vona/src/suite/a-training/modules/training-student/src/.metadata/index.ts +27 -1
  54. package/vona/src/suite/a-training/modules/training-student/src/bean/imageScene.studentImage.ts +10 -0
  55. package/vona/src/suite/a-training/modules/training-student/src/bean/meta.version.ts +1 -0
  56. package/vona/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +1 -0
  57. package/vona/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +1 -0
  58. package/vona/src/suite/a-training/modules/training-student/src/dto/detailRecordBase.tsx +16 -2
  59. package/vona/src/suite/a-training/modules/training-student/src/dto/studentCreate.tsx +1 -1
  60. package/vona/src/suite/a-training/modules/training-student/src/dto/studentSelectResItem.tsx +12 -0
  61. package/vona/src/suite/a-training/modules/training-student/src/dto/studentUpdate.tsx +1 -1
  62. package/vona/src/suite/a-training/modules/training-student/src/dto/studentView.tsx +13 -1
  63. package/vona/src/suite/a-training/modules/training-student/src/entity/student.tsx +26 -1
  64. package/vona/src/suite/a-training/modules/training-student/src/index.ts +1 -0
  65. package/vona/src/suite/a-training/modules/training-student/src/model/student.ts +1 -0
  66. package/vona/src/suite-vendor/a-auth/modules/a-auth/package.json +1 -1
  67. package/vona/src/suite-vendor/a-auth/modules/a-auth/src/bean/bean.authProvider.ts +2 -5
  68. package/vona/src/suite-vendor/a-auth/package.json +1 -1
  69. package/vona/src/suite-vendor/a-image/modules/a-image/cli/imageProvider/boilerplate/{{sceneName}}.{{beanName}}.ts_ +23 -0
  70. package/vona/src/suite-vendor/a-image/modules/a-image/cli/imageScene/boilerplate/{{sceneName}}.{{beanName}}.ts_ +5 -0
  71. package/vona/src/suite-vendor/a-image/modules/a-image/package.json +68 -0
  72. package/vona/src/suite-vendor/a-image/modules/a-image/src/.metadata/index.ts +482 -0
  73. package/vona/src/suite-vendor/a-image/modules/a-image/src/.metadata/locales.ts +18 -0
  74. package/vona/src/suite-vendor/a-image/modules/a-image/src/.metadata/this.ts +2 -0
  75. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.image.ts +443 -0
  76. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.imageProvider.ts +55 -0
  77. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/bean.imageUploadPolicy.ts +174 -0
  78. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.redlock.ts +11 -0
  79. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/meta.version.ts +40 -0
  80. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/serializerTransform.resolveView.ts +44 -0
  81. package/vona/src/suite-vendor/a-image/modules/a-image/src/bean/serializerTransform.resolveViews.ts +44 -0
  82. package/vona/src/suite-vendor/a-image/modules/a-image/src/config/config.ts +17 -0
  83. package/vona/src/suite-vendor/a-image/modules/a-image/src/config/locale/en-us.ts +1 -0
  84. package/vona/src/suite-vendor/a-image/modules/a-image/src/config/locale/zh-cn.ts +1 -0
  85. package/vona/src/suite-vendor/a-image/modules/a-image/src/controller/image.ts +165 -0
  86. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDeliveryRequest.ts +20 -0
  87. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadRequest.ts +36 -0
  88. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageDirectUploadResponse.ts +38 -0
  89. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageTransformOptions.tsx +42 -0
  90. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadResponse.ts +62 -0
  91. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadTokenRequest.ts +24 -0
  92. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadTokenResponse.ts +16 -0
  93. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageUploadUrlRequest.ts +33 -0
  94. package/vona/src/suite-vendor/a-image/modules/a-image/src/dto/imageView.ts +46 -0
  95. package/vona/src/suite-vendor/a-image/modules/a-image/src/entity/image.ts +58 -0
  96. package/vona/src/suite-vendor/a-image/modules/a-image/src/entity/imageProvider.ts +24 -0
  97. package/vona/src/suite-vendor/a-image/modules/a-image/src/index.ts +4 -0
  98. package/vona/src/suite-vendor/a-image/modules/a-image/src/lib/imageProvider.ts +9 -0
  99. package/vona/src/suite-vendor/a-image/modules/a-image/src/lib/imageScene.ts +7 -0
  100. package/vona/src/suite-vendor/a-image/modules/a-image/src/lib/imageVariant.ts +46 -0
  101. package/vona/src/suite-vendor/a-image/modules/a-image/src/lib/index.ts +3 -0
  102. package/vona/src/suite-vendor/a-image/modules/a-image/src/model/image.ts +10 -0
  103. package/vona/src/suite-vendor/a-image/modules/a-image/src/model/imageProvider.ts +10 -0
  104. package/vona/src/suite-vendor/a-image/modules/a-image/src/types/image.ts +208 -0
  105. package/vona/src/suite-vendor/a-image/modules/a-image/src/types/imageProvider.ts +110 -0
  106. package/vona/src/suite-vendor/a-image/modules/a-image/src/types/imageScene.ts +52 -0
  107. package/vona/src/suite-vendor/a-image/modules/a-image/src/types/index.ts +3 -0
  108. package/vona/src/suite-vendor/a-image/modules/a-image/tsconfig.build.json +11 -0
  109. package/vona/src/suite-vendor/a-image/modules/a-image/tsconfig.json +7 -0
  110. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/package.json +49 -0
  111. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/.metadata/index.ts +89 -0
  112. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/.metadata/this.ts +2 -0
  113. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/bean/imageProvider.cloudflare.ts +155 -0
  114. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/index.ts +1 -0
  115. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/lib/cloudflareImageUrl.ts +112 -0
  116. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/src/service/imageCloudflare.ts +273 -0
  117. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/tsconfig.build.json +11 -0
  118. package/vona/src/suite-vendor/a-image/modules/image-cloudflare/tsconfig.json +7 -0
  119. package/vona/src/suite-vendor/a-image/modules/image-native/package.json +63 -0
  120. package/vona/src/suite-vendor/a-image/modules/image-native/src/.metadata/index.ts +114 -0
  121. package/vona/src/suite-vendor/a-image/modules/image-native/src/.metadata/this.ts +2 -0
  122. package/vona/src/suite-vendor/a-image/modules/image-native/src/bean/imageProvider.native.ts +131 -0
  123. package/vona/src/suite-vendor/a-image/modules/image-native/src/config/config.ts +11 -0
  124. package/vona/src/suite-vendor/a-image/modules/image-native/src/index.ts +1 -0
  125. package/vona/src/suite-vendor/a-image/modules/image-native/src/monkey.ts +33 -0
  126. package/vona/src/suite-vendor/a-image/modules/image-native/src/service/imageNative.ts +291 -0
  127. package/vona/src/suite-vendor/a-image/modules/image-native/tsconfig.build.json +11 -0
  128. package/vona/src/suite-vendor/a-image/modules/image-native/tsconfig.json +7 -0
  129. package/vona/src/suite-vendor/a-image/package.json +14 -0
  130. package/vona/src/suite-vendor/a-image/tsconfig.base.json +4 -0
  131. package/vona/src/suite-vendor/a-image/tsconfig.json +16 -0
  132. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +2 -2
  133. package/vona/src/suite-vendor/a-vona/modules/a-jwt/package.json +1 -1
  134. package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/service/jwtClient.ts +5 -0
  135. package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/types/jwt.ts +1 -0
  136. package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/package.json +1 -1
  137. package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/src/lib/schema/makeSchemaLikes.ts +6 -3
  138. package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/src/lib/utils.ts +13 -7
  139. package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
  140. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/types/database.ts +0 -1
  141. package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/package.json +9 -1
  142. package/vona/src/suite-vendor/a-vona/modules/a-upload/package.json +1 -1
  143. package/vona/src/suite-vendor/a-vona/modules/a-upload/src/bean/interceptor.upload.ts +39 -7
  144. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  145. package/zova/packages-cli/cli/package.json +3 -3
  146. package/zova/packages-cli/cli-set-front/package.json +9 -9
  147. package/zova/packages-cli/cli-set-front/src/lib/bean/cli.openapi.generate.ts +20 -10
  148. package/zova/packages-utils/babel-plugin-zova-bean-module/package.json +2 -2
  149. package/zova/packages-utils/babel-plugin-zova-bean-use/package.json +2 -2
  150. package/zova/packages-utils/babel-plugin-zova-behavior/package.json +2 -2
  151. package/zova/packages-utils/babel-plugin-zova-component/package.json +2 -2
  152. package/zova/packages-utils/zova-jsx/package.json +3 -3
  153. package/zova/packages-utils/zova-vite/package.json +7 -7
  154. package/zova/packages-zova/zova/package.json +3 -3
  155. package/zova/packages-zova/zova-core/package.json +3 -3
  156. package/zova/pnpm-lock.yaml +126 -74
  157. package/zova/src/suite/a-home/modules/home-api/src/api/captcha.ts +3 -1
  158. package/zova/src/suite/a-home/modules/home-api/src/api/home.ts +3 -1
  159. package/zova/src/suite/a-home/modules/home-api/src/api/homeBaseMenu.ts +3 -1
  160. package/zova/src/suite/a-home/modules/home-api/src/api/homeBasePermission.ts +3 -1
  161. package/zova/src/suite/a-home/modules/home-api/src/api/homeUserPassport.ts +22 -20
  162. package/zova/src/suite/a-home/modules/home-api/src/api/openapi/schemas.ts +163 -53
  163. package/zova/src/suite/a-home/modules/home-api/src/api/openapi/types.ts +2060 -675
  164. package/zova/src/suite/a-home/modules/home-api/src/api/testSsrToolOne.ts +3 -1
  165. package/zova/src/suite/a-home/modules/home-api/src/apiSchema/captcha.ts +3 -1
  166. package/zova/src/suite/a-home/modules/home-api/src/apiSchema/home.ts +3 -1
  167. package/zova/src/suite/a-home/modules/home-api/src/apiSchema/homeBaseMenu.ts +3 -1
  168. package/zova/src/suite/a-home/modules/home-api/src/apiSchema/homeBasePermission.ts +3 -1
  169. package/zova/src/suite/a-home/modules/home-api/src/apiSchema/homeUserPassport.ts +13 -11
  170. package/zova/src/suite/a-home/modules/home-api/src/apiSchema/testSsrToolOne.ts +3 -1
  171. package/zova/src/suite/a-training/modules/training-student/src/api/openapi/schemas.ts +54 -16
  172. package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +468 -8
  173. package/zova/src/suite/a-training/modules/training-student/src/apiSchema/trainingStudent.ts +3 -1
  174. package/zova/src/suite/cabloy-basic/modules/basic-details/src/bean/tableCell.actionUpdate.tsx +1 -1
  175. package/zova/src/suite/cabloy-basic/modules/basic-details/src/component/actionCreate/controller.tsx +1 -1
  176. package/zova/src/suite/cabloy-basic/modules/basic-details/src/service/detail.tsx +53 -1
  177. package/zova/src/suite/cabloy-basic/modules/basic-image/cli/openapi.config.ts +9 -0
  178. package/zova/src/suite/cabloy-basic/modules/basic-image/package.json +60 -0
  179. package/zova/src/suite/cabloy-basic/modules/basic-image/src/.metadata/component/formFieldImage.ts +31 -0
  180. package/zova/src/suite/cabloy-basic/modules/basic-image/src/.metadata/index.ts +191 -0
  181. package/zova/src/suite/cabloy-basic/modules/basic-image/src/.metadata/locales.ts +7 -0
  182. package/zova/src/suite/cabloy-basic/modules/basic-image/src/.metadata/this.ts +2 -0
  183. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/image.ts +104 -0
  184. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/baseURL.ts +5 -0
  185. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/index.ts +3 -0
  186. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/schemas.ts +262 -0
  187. package/zova/src/suite/cabloy-basic/modules/basic-image/src/api/openapi/types.ts +5159 -0
  188. package/zova/src/suite/cabloy-basic/modules/basic-image/src/apiSchema/image.ts +35 -0
  189. package/zova/src/suite/cabloy-basic/modules/basic-image/src/bean/tableCell.image.tsx +200 -0
  190. package/zova/src/suite/cabloy-basic/modules/basic-image/src/component/formFieldImage/controller.tsx +781 -0
  191. package/zova/src/suite/cabloy-basic/modules/basic-image/src/config/locale/en-us.ts +18 -0
  192. package/zova/src/suite/cabloy-basic/modules/basic-image/src/config/locale/zh-cn.ts +18 -0
  193. package/zova/src/suite/cabloy-basic/modules/basic-image/src/index.ts +4 -0
  194. package/zova/src/suite/cabloy-basic/modules/basic-image/src/lib/index.ts +2 -0
  195. package/zova/src/suite/cabloy-basic/modules/basic-image/src/lib/preview.ts +105 -0
  196. package/zova/src/suite/cabloy-basic/modules/basic-image/src/lib/previewDialog.tsx +190 -0
  197. package/zova/src/suite/cabloy-basic/modules/basic-image/src/types/image.ts +5 -0
  198. package/zova/src/suite/cabloy-basic/modules/basic-image/src/types/index.ts +1 -0
  199. package/zova/src/suite/cabloy-basic/modules/basic-image/tsconfig.build.json +13 -0
  200. package/zova/src/suite/cabloy-basic/modules/basic-image/tsconfig.json +5 -0
  201. package/zova/src/suite/cabloy-basic/package.json +1 -0
  202. package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
  203. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/image.ts +1 -0
  204. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/index.ts +1 -0
  205. package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +5 -5
  206. package/zova/src/suite-vendor/a-zova/package.json +3 -3
  207. package/vona/packages-vona/vona-core/src/lib/utils/sqlite3.ts +0 -50
  208. /package/vona/patches/{zova-core@5.1.75.patch → zova-core@5.1.76.patch} +0 -0
@@ -0,0 +1,89 @@
1
+ // eslint-disable
2
+ /** service: begin */
3
+ export * from '../service/imageCloudflare.ts';
4
+
5
+ import 'vona-module-a-bean';
6
+ declare module 'vona-module-a-bean' {
7
+ export interface IServiceRecord {
8
+ 'image-cloudflare:imageCloudflare': never;
9
+ }
10
+ }
11
+ declare module 'vona-module-image-cloudflare' {
12
+ export interface ServiceImageCloudflare {
13
+ /** @internal */
14
+ get scope(): ScopeModuleImageCloudflare;
15
+ }
16
+
17
+ export interface ServiceImageCloudflare {
18
+ get $beanFullName(): 'image-cloudflare.service.imageCloudflare';
19
+ get $onionName(): 'image-cloudflare:imageCloudflare';
20
+ }
21
+ }
22
+ /** service: end */
23
+ /** service: begin */
24
+ import type { ServiceImageCloudflare } from '../service/imageCloudflare.ts';
25
+ export interface IModuleService {
26
+ imageCloudflare: ServiceImageCloudflare;
27
+ }
28
+ /** service: end */
29
+ /** service: begin */
30
+
31
+ import 'vona';
32
+ declare module 'vona' {
33
+ export interface IBeanRecordGeneral {
34
+ 'image-cloudflare.service.imageCloudflare': ServiceImageCloudflare;
35
+ }
36
+ }
37
+ /** service: end */
38
+ /** imageProvider: begin */
39
+ export * from '../bean/imageProvider.cloudflare.ts';
40
+ import type { IImageProviderOptionsCloudflare } from '../bean/imageProvider.cloudflare.ts';
41
+ import 'vona-module-a-image';
42
+ declare module 'vona-module-a-image' {
43
+ export interface IImageProviderRecord {
44
+ 'image-cloudflare:cloudflare': IImageProviderOptionsCloudflare;
45
+ }
46
+ }
47
+ declare module 'vona-module-image-cloudflare' {
48
+ export interface ImageProviderCloudflare {
49
+ /** @internal */
50
+ get scope(): ScopeModuleImageCloudflare;
51
+ }
52
+
53
+ export interface ImageProviderCloudflare {
54
+ get $beanFullName(): 'image-cloudflare.imageProvider.cloudflare';
55
+ get $onionName(): 'image-cloudflare:cloudflare';
56
+ get $onionOptions(): IImageProviderOptionsCloudflare;
57
+ }
58
+ }
59
+ /** imageProvider: end */
60
+ /** imageProvider: begin */
61
+ import type { ImageProviderCloudflare } from '../bean/imageProvider.cloudflare.ts';
62
+ export interface IModuleImageProvider {
63
+ cloudflare: ImageProviderCloudflare;
64
+ }
65
+ /** imageProvider: end */
66
+ /** scope: begin */
67
+ import { BeanScopeBase, type BeanScopeUtil } from 'vona';
68
+ import { Scope } from 'vona-module-a-bean';
69
+
70
+ @Scope()
71
+ export class ScopeModuleImageCloudflare extends BeanScopeBase {}
72
+
73
+ export interface ScopeModuleImageCloudflare {
74
+ util: BeanScopeUtil;
75
+ service: IModuleService;
76
+ imageProvider: IModuleImageProvider;
77
+ }
78
+
79
+ import 'vona';
80
+ declare module 'vona' {
81
+ export interface IBeanScopeRecord {
82
+ 'image-cloudflare': ScopeModuleImageCloudflare;
83
+ }
84
+
85
+ export interface IBeanScopeContainer {
86
+ imageCloudflare: ScopeModuleImageCloudflare;
87
+ }
88
+ }
89
+ /** scope: end */
@@ -0,0 +1,2 @@
1
+ export const __ThisModule__ = 'image-cloudflare';
2
+ export { ScopeModuleImageCloudflare as ScopeModule } from './index.ts';
@@ -0,0 +1,155 @@
1
+ import type {
2
+ IDecoratorImageProviderOptions,
3
+ IImageDeliveryOptions,
4
+ IImageDirectUploadInput,
5
+ IImageDownloadResult,
6
+ IImageProviderClientOptions,
7
+ IImageProviderClientRecord,
8
+ IImageProviderExecute,
9
+ IImageProviderResource,
10
+ IImageUploadInput,
11
+ IImageUploadUrlInput,
12
+ IImageVariantRequest,
13
+ TypeImageProviderResolvedVariantName,
14
+ } from 'vona-module-a-image';
15
+ import type { EntityImage } from 'vona-module-a-image';
16
+
17
+ import { BeanBase } from 'vona';
18
+ import { resolveImageVariantRequestToTransform } from 'vona-module-a-image';
19
+ import { ImageProvider } from 'vona-module-a-image';
20
+
21
+ export interface IImageProviderCloudflareClientRecord extends IImageProviderClientRecord {}
22
+
23
+ export interface IImageProviderCloudflareClientOptions extends IImageProviderClientOptions {
24
+ accountId?: string;
25
+ apiToken?: string;
26
+ accountHash?: string;
27
+ apiBaseUrl?: string;
28
+ signingKey?: string;
29
+ flexibleVariants?: boolean;
30
+ customDomain?: string;
31
+ }
32
+
33
+ export interface IImageProviderOptionsCloudflare extends IDecoratorImageProviderOptions<
34
+ IImageProviderCloudflareClientRecord,
35
+ IImageProviderCloudflareClientOptions
36
+ > {}
37
+
38
+ @ImageProvider<IImageProviderOptionsCloudflare>({
39
+ base: {
40
+ signedDeliveryKind: 'provider',
41
+ variants: {
42
+ original: {},
43
+ },
44
+ },
45
+ })
46
+ export class ImageProviderCloudflare extends BeanBase implements IImageProviderExecute {
47
+ async upload(
48
+ input: IImageUploadInput,
49
+ clientOptions: IImageProviderCloudflareClientOptions,
50
+ _options: IImageProviderOptionsCloudflare,
51
+ ): Promise<IImageProviderResource> {
52
+ return await this.scope.service.imageCloudflare.upload(input, clientOptions);
53
+ }
54
+
55
+ async uploadUrl(
56
+ input: IImageUploadUrlInput,
57
+ clientOptions: IImageProviderCloudflareClientOptions,
58
+ _options: IImageProviderOptionsCloudflare,
59
+ ) {
60
+ return await this.scope.service.imageCloudflare.uploadUrl(input, clientOptions);
61
+ }
62
+
63
+ async createDirectUpload(
64
+ input: IImageDirectUploadInput,
65
+ clientOptions: IImageProviderCloudflareClientOptions,
66
+ _options: IImageProviderOptionsCloudflare,
67
+ ) {
68
+ return await this.scope.service.imageCloudflare.createDirectUpload(input, clientOptions);
69
+ }
70
+
71
+ async get(
72
+ image: EntityImage,
73
+ clientOptions: IImageProviderCloudflareClientOptions,
74
+ _options: IImageProviderOptionsCloudflare,
75
+ ) {
76
+ return {
77
+ resourceId: image.resourceId,
78
+ filename: image.filename,
79
+ contentType: image.contentType,
80
+ size: image.size,
81
+ width: image.width,
82
+ height: image.height,
83
+ requireSignedURLs: image.requireSignedURLs ?? clientOptions.requireSignedURLs,
84
+ variants: image.variants ?? clientOptions.variants,
85
+ meta: image.meta,
86
+ deliveryBaseUrl: image.deliveryBaseUrl ?? clientOptions.deliveryBaseUrl,
87
+ };
88
+ }
89
+
90
+ async delete(
91
+ image: EntityImage,
92
+ clientOptions: IImageProviderCloudflareClientOptions,
93
+ _options: IImageProviderOptionsCloudflare,
94
+ ) {
95
+ await this.scope.service.imageCloudflare.remove(image, clientOptions);
96
+ }
97
+
98
+ async getVariantUrl(
99
+ image: EntityImage,
100
+ request: IImageVariantRequest,
101
+ clientOptions: IImageProviderCloudflareClientOptions,
102
+ _options: IImageProviderOptionsCloudflare,
103
+ deliveryOptions?: IImageDeliveryOptions,
104
+ ) {
105
+ const variants = image.variants ?? clientOptions.variants;
106
+ const resolved = resolveImageVariantRequestToTransform(request, 'original', variants);
107
+ return this._buildVariantUrl(
108
+ image,
109
+ resolved.variantName,
110
+ resolved.transformOptions,
111
+ {
112
+ ...clientOptions,
113
+ deliveryBaseUrl: image.deliveryBaseUrl ?? clientOptions.deliveryBaseUrl,
114
+ requireSignedURLs: image.requireSignedURLs ?? clientOptions.requireSignedURLs,
115
+ },
116
+ deliveryOptions,
117
+ );
118
+ }
119
+
120
+ async download(
121
+ image: EntityImage,
122
+ request: IImageVariantRequest,
123
+ clientOptions: IImageProviderCloudflareClientOptions,
124
+ options: IImageProviderOptionsCloudflare,
125
+ deliveryOptions?: IImageDeliveryOptions,
126
+ ): Promise<IImageDownloadResult> {
127
+ return {
128
+ kind: 'url',
129
+ url: await this.getVariantUrl(image, request, clientOptions, options, deliveryOptions),
130
+ filename: image.filename,
131
+ contentType: image.contentType,
132
+ signed: !!(
133
+ deliveryOptions?.signed ??
134
+ image.requireSignedURLs ??
135
+ clientOptions.requireSignedURLs
136
+ ),
137
+ };
138
+ }
139
+
140
+ private _buildVariantUrl(
141
+ image: EntityImage,
142
+ variantName: TypeImageProviderResolvedVariantName,
143
+ transformOptions: Record<string, any>,
144
+ clientOptions: IImageProviderCloudflareClientOptions,
145
+ deliveryOptions?: IImageDeliveryOptions,
146
+ ) {
147
+ return this.scope.service.imageCloudflare.buildVariantUrl(
148
+ image,
149
+ variantName,
150
+ transformOptions,
151
+ clientOptions,
152
+ deliveryOptions,
153
+ );
154
+ }
155
+ }
@@ -0,0 +1 @@
1
+ export * from './.metadata/index.ts';
@@ -0,0 +1,112 @@
1
+ import type {
2
+ IImageDeliveryOptions,
3
+ IImageTransformOptions,
4
+ TypeImageVariantName,
5
+ } from 'vona-module-a-image';
6
+
7
+ import { createHmac } from 'node:crypto';
8
+
9
+ export interface ICloudflareImageUrlOptions extends IImageDeliveryOptions {
10
+ accountHash?: string;
11
+ deliveryBaseUrl?: string;
12
+ signingKey?: string;
13
+ }
14
+
15
+ export function buildCloudflareImageUrl(
16
+ resourceId: string,
17
+ variantName: TypeImageVariantName | 'custom',
18
+ transformOptions: IImageTransformOptions,
19
+ options: ICloudflareImageUrlOptions,
20
+ ) {
21
+ const baseUrl = resolveCloudflareDeliveryBaseUrl(options);
22
+ const resourcePath = encodeCloudflareResourceId(resourceId);
23
+ const variantPath =
24
+ variantName === 'custom'
25
+ ? buildCloudflareTransformPath(transformOptions)
26
+ : normalizeCloudflareVariantName(variantName);
27
+ const url = new URL(`${baseUrl}/${resourcePath}/${variantPath}`);
28
+ if (options.signed) {
29
+ signCloudflareImageUrl(url, options);
30
+ }
31
+ return url.toString();
32
+ }
33
+
34
+ export function resolveCloudflareDeliveryBaseUrl(options: {
35
+ accountHash?: string;
36
+ deliveryBaseUrl?: string;
37
+ }) {
38
+ if (options.deliveryBaseUrl) {
39
+ return options.deliveryBaseUrl.replace(/\/$/, '');
40
+ }
41
+ if (!options.accountHash) {
42
+ throw new Error('Cloudflare accountHash or deliveryBaseUrl is required');
43
+ }
44
+ return `https://imagedelivery.net/${options.accountHash}`;
45
+ }
46
+
47
+ export function resolveCloudflareDeliveryBaseUrlFromVariantUrl(url?: string) {
48
+ if (!url) return undefined;
49
+ const parsed = new URL(url);
50
+ const segments = parsed.pathname.split('/').filter(Boolean);
51
+ if (segments.length < 2) return undefined;
52
+ return `${parsed.origin}/${segments[0]}`;
53
+ }
54
+
55
+ export function encodeCloudflareResourceId(resourceId: string) {
56
+ return resourceId
57
+ .split('/')
58
+ .map(segment => encodeURIComponent(segment))
59
+ .join('/');
60
+ }
61
+
62
+ export function buildCloudflareTransformPath(transformOptions: IImageTransformOptions) {
63
+ const entries: Array<[string, string | number]> = [];
64
+ if (transformOptions.width !== undefined) entries.push(['w', transformOptions.width]);
65
+ if (transformOptions.height !== undefined) entries.push(['h', transformOptions.height]);
66
+ if (transformOptions.fit !== undefined) entries.push(['fit', transformOptions.fit]);
67
+ if (transformOptions.gravity !== undefined) entries.push(['gravity', transformOptions.gravity]);
68
+ if (transformOptions.background !== undefined)
69
+ entries.push(['background', transformOptions.background]);
70
+ if (transformOptions.quality !== undefined) entries.push(['quality', transformOptions.quality]);
71
+ if (transformOptions.format !== undefined && transformOptions.format !== 'auto') {
72
+ entries.push(['format', transformOptions.format]);
73
+ }
74
+ if (transformOptions.dpr !== undefined) entries.push(['dpr', transformOptions.dpr]);
75
+ if (transformOptions.rotate !== undefined) entries.push(['rotate', transformOptions.rotate]);
76
+ if (transformOptions.sharpen !== undefined) entries.push(['sharpen', transformOptions.sharpen]);
77
+ if (entries.length === 0) return 'public';
78
+ return entries.map(([key, value]) => `${key}=${encodeURIComponent(String(value))}`).join(',');
79
+ }
80
+
81
+ export function normalizeCloudflareVariantName(variantName: TypeImageVariantName | 'custom') {
82
+ if (variantName === 'original') return 'public';
83
+ return variantName;
84
+ }
85
+
86
+ export function signCloudflareImageUrl(url: URL, options: ICloudflareImageUrlOptions) {
87
+ if (!options.signingKey) {
88
+ throw new Error('Cloudflare signingKey is required for signed delivery');
89
+ }
90
+ const expiry = resolveExpiryTimestamp(options);
91
+ url.searchParams.set('exp', String(expiry));
92
+ const stringToSign = `${url.pathname}?${url.searchParams.toString()}`;
93
+ const sig = createHmac('sha256', options.signingKey).update(stringToSign).digest('hex');
94
+ url.searchParams.set('sig', sig);
95
+ }
96
+
97
+ function resolveExpiryTimestamp(options: IImageDeliveryOptions) {
98
+ if (options.expiresAt !== undefined) {
99
+ const value =
100
+ options.expiresAt instanceof Date
101
+ ? options.expiresAt.getTime()
102
+ : typeof options.expiresAt === 'string'
103
+ ? new Date(options.expiresAt).getTime()
104
+ : Number(options.expiresAt);
105
+ if (!Number.isFinite(value)) {
106
+ throw new TypeError(`Invalid expiresAt for signed delivery: ${String(options.expiresAt)}`);
107
+ }
108
+ return Math.floor(value / 1000);
109
+ }
110
+ const expiresIn = options.expiresIn ?? 60 * 10;
111
+ return Math.floor(Date.now() / 1000) + expiresIn;
112
+ }
@@ -0,0 +1,273 @@
1
+ import type {
2
+ EntityImage,
3
+ IImageDeliveryOptions,
4
+ IImageDirectUploadInput,
5
+ IImageProviderDirectUploadResource,
6
+ IImageProviderResource,
7
+ IImageUploadInput,
8
+ IImageUploadUrlInput,
9
+ } from 'vona-module-a-image';
10
+
11
+ import fse from 'fs-extra';
12
+ import path from 'node:path';
13
+ import { BeanBase } from 'vona';
14
+ import { Service } from 'vona-module-a-bean';
15
+
16
+ import type { IImageProviderCloudflareClientOptions } from '../bean/imageProvider.cloudflare.ts';
17
+
18
+ import {
19
+ buildCloudflareImageUrl,
20
+ resolveCloudflareDeliveryBaseUrl,
21
+ resolveCloudflareDeliveryBaseUrlFromVariantUrl,
22
+ } from '../lib/cloudflareImageUrl.ts';
23
+
24
+ interface ICloudflareApiEnvelope<T> {
25
+ result: T;
26
+ success: boolean;
27
+ errors?: Array<{ message?: string }>;
28
+ messages?: Array<{ message?: string }>;
29
+ }
30
+
31
+ interface ICloudflareImageResult {
32
+ id: string;
33
+ filename?: string;
34
+ uploaded?: string;
35
+ requireSignedURLs?: boolean;
36
+ variants?: string[];
37
+ metadata?: Record<string, unknown>;
38
+ draft?: boolean;
39
+ }
40
+
41
+ interface ICloudflareClientOptionsNormalized extends IImageProviderCloudflareClientOptions {
42
+ accountId: string;
43
+ apiToken: string;
44
+ apiBaseUrl: string;
45
+ deliveryBaseUrl?: string;
46
+ }
47
+
48
+ @Service()
49
+ export class ServiceImageCloudflare extends BeanBase {
50
+ async upload(
51
+ input: IImageUploadInput,
52
+ options: IImageProviderCloudflareClientOptions,
53
+ ): Promise<IImageProviderResource> {
54
+ const normalized = this._normalizeClientOptions(options);
55
+ const form = new FormData();
56
+ const buffer = await fse.readFile(input.file);
57
+ const filename = input.filename ?? path.basename(input.file);
58
+ form.append('file', new Blob([buffer], { type: input.contentType }), filename);
59
+ this._appendUploadFields(form, {
60
+ requireSignedURLs: input.requireSignedURLs,
61
+ metadata: input.meta,
62
+ });
63
+ const result = await this._request<ICloudflareImageResult>(normalized, '/images/v1', {
64
+ method: 'POST',
65
+ body: form,
66
+ });
67
+ const stat = await fse.stat(input.file);
68
+ return this._mapImageResource(result, normalized, {
69
+ filename,
70
+ contentType: input.contentType,
71
+ size: input.size ?? Number(stat.size),
72
+ meta: input.meta,
73
+ requireSignedURLs: input.requireSignedURLs,
74
+ });
75
+ }
76
+
77
+ async uploadUrl(
78
+ input: IImageUploadUrlInput,
79
+ options: IImageProviderCloudflareClientOptions,
80
+ ): Promise<IImageProviderResource> {
81
+ const normalized = this._normalizeClientOptions(options);
82
+ const form = new FormData();
83
+ form.append('url', input.url);
84
+ this._appendUploadFields(form, {
85
+ requireSignedURLs: input.requireSignedURLs,
86
+ metadata: input.meta,
87
+ });
88
+ const result = await this._request<ICloudflareImageResult>(normalized, '/images/v1', {
89
+ method: 'POST',
90
+ body: form,
91
+ });
92
+ return this._mapImageResource(result, normalized, {
93
+ filename: input.filename ?? result.filename,
94
+ contentType: input.contentType,
95
+ meta: input.meta,
96
+ requireSignedURLs: input.requireSignedURLs,
97
+ });
98
+ }
99
+
100
+ async createDirectUpload(
101
+ input: IImageDirectUploadInput,
102
+ options: IImageProviderCloudflareClientOptions,
103
+ ): Promise<IImageProviderDirectUploadResource> {
104
+ const normalized = this._normalizeClientOptions(options);
105
+ const form = new FormData();
106
+ this._appendUploadFields(form, {
107
+ requireSignedURLs: input.requireSignedURLs,
108
+ metadata: input.meta,
109
+ expiry: input.expiry,
110
+ id: input.customId,
111
+ });
112
+ const result = await this._request<ICloudflareImageResult & { uploadURL: string }>(
113
+ normalized,
114
+ '/images/v2/direct_upload',
115
+ {
116
+ method: 'POST',
117
+ body: form,
118
+ },
119
+ );
120
+ const resource = this._mapImageResource(result, normalized, {
121
+ filename: input.filename,
122
+ contentType: input.contentType,
123
+ meta: input.meta,
124
+ requireSignedURLs: input.requireSignedURLs,
125
+ });
126
+ return {
127
+ ...resource,
128
+ uploadUrl: result.uploadURL,
129
+ draft: true,
130
+ };
131
+ }
132
+
133
+ async remove(
134
+ image: Pick<EntityImage, 'resourceId'>,
135
+ options: IImageProviderCloudflareClientOptions,
136
+ ) {
137
+ const normalized = this._normalizeClientOptions(options);
138
+ const response = await fetch(
139
+ `${normalized.apiBaseUrl}/accounts/${normalized.accountId}/images/v1/${encodeURIComponent(image.resourceId)}`,
140
+ {
141
+ method: 'DELETE',
142
+ headers: this._createHeaders(normalized),
143
+ },
144
+ );
145
+ if (response.status === 404) return;
146
+ await this._parseEnvelope<unknown>(response);
147
+ }
148
+
149
+ buildVariantUrl(
150
+ image: Pick<EntityImage, 'resourceId' | 'deliveryBaseUrl' | 'requireSignedURLs'>,
151
+ variantName: string | 'custom',
152
+ transformOptions: Record<string, any>,
153
+ options: IImageProviderCloudflareClientOptions,
154
+ deliveryOptions?: IImageDeliveryOptions,
155
+ ) {
156
+ const normalized = this._normalizeClientOptions(options);
157
+ return buildCloudflareImageUrl(image.resourceId, variantName as any, transformOptions, {
158
+ accountHash: normalized.accountHash,
159
+ deliveryBaseUrl: image.deliveryBaseUrl ?? normalized.deliveryBaseUrl,
160
+ signingKey: normalized.signingKey,
161
+ signed: deliveryOptions?.signed ?? image.requireSignedURLs ?? normalized.requireSignedURLs,
162
+ expiresAt: deliveryOptions?.expiresAt,
163
+ expiresIn: deliveryOptions?.expiresIn,
164
+ });
165
+ }
166
+
167
+ private _appendUploadFields(
168
+ form: FormData,
169
+ data: {
170
+ requireSignedURLs?: boolean;
171
+ metadata?: Record<string, unknown>;
172
+ expiry?: string | number | Date;
173
+ id?: string;
174
+ },
175
+ ) {
176
+ if (data.requireSignedURLs !== undefined) {
177
+ form.append('requireSignedURLs', String(data.requireSignedURLs));
178
+ }
179
+ if (data.metadata !== undefined) {
180
+ form.append('metadata', JSON.stringify(data.metadata));
181
+ }
182
+ if (data.expiry !== undefined) {
183
+ form.append(
184
+ 'expiry',
185
+ data.expiry instanceof Date ? data.expiry.toISOString() : String(data.expiry),
186
+ );
187
+ }
188
+ if (data.id) {
189
+ form.append('id', data.id);
190
+ }
191
+ }
192
+
193
+ private _normalizeClientOptions(options: IImageProviderCloudflareClientOptions) {
194
+ const accountId = options.accountId;
195
+ const apiToken = options.apiToken;
196
+ if (!accountId) {
197
+ throw new Error('Cloudflare Images accountId is required');
198
+ }
199
+ if (!apiToken) {
200
+ throw new Error('Cloudflare Images apiToken is required');
201
+ }
202
+ return {
203
+ ...options,
204
+ accountId,
205
+ apiToken,
206
+ apiBaseUrl: (options.apiBaseUrl ?? 'https://api.cloudflare.com/client/v4').replace(/\/$/, ''),
207
+ deliveryBaseUrl: options.deliveryBaseUrl
208
+ ? resolveCloudflareDeliveryBaseUrl(options)
209
+ : undefined,
210
+ };
211
+ }
212
+
213
+ private _createHeaders(options: { apiToken: string }) {
214
+ return {
215
+ Authorization: `Bearer ${options.apiToken}`,
216
+ };
217
+ }
218
+
219
+ private async _request<T>(
220
+ options: ICloudflareClientOptionsNormalized,
221
+ apiPath: string,
222
+ init: RequestInit,
223
+ ) {
224
+ const response = await fetch(`${options.apiBaseUrl}/accounts/${options.accountId}${apiPath}`, {
225
+ ...init,
226
+ headers: {
227
+ ...this._createHeaders(options),
228
+ ...(init.headers ?? {}),
229
+ },
230
+ });
231
+ return await this._parseEnvelope<T>(response);
232
+ }
233
+
234
+ private async _parseEnvelope<T>(response: Response) {
235
+ const data = (await response.json()) as ICloudflareApiEnvelope<T>;
236
+ if (!response.ok || !data.success) {
237
+ const message = data.errors
238
+ ?.map(item => item.message)
239
+ .filter(Boolean)
240
+ .join('; ');
241
+ throw new Error(message || `Cloudflare Images request failed: ${response.status}`);
242
+ }
243
+ return data.result;
244
+ }
245
+
246
+ private _mapImageResource(
247
+ result: ICloudflareImageResult,
248
+ options: ICloudflareClientOptionsNormalized,
249
+ data: {
250
+ filename?: string;
251
+ contentType?: string;
252
+ size?: number;
253
+ meta?: Record<string, unknown>;
254
+ requireSignedURLs?: boolean;
255
+ },
256
+ ): IImageProviderResource {
257
+ const deliveryBaseUrl =
258
+ options.deliveryBaseUrl ??
259
+ resolveCloudflareDeliveryBaseUrlFromVariantUrl(result.variants?.[0]);
260
+ return {
261
+ resourceId: result.id,
262
+ filename: data.filename ?? result.filename,
263
+ contentType: data.contentType,
264
+ size: data.size,
265
+ requireSignedURLs:
266
+ data.requireSignedURLs ?? result.requireSignedURLs ?? options.requireSignedURLs,
267
+ variants: options.variants,
268
+ meta: data.meta,
269
+ deliveryBaseUrl,
270
+ raw: result,
271
+ };
272
+ }
273
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../tsconfig.base",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "declaration": true,
6
+ "declarationMap": true,
7
+ "emitDeclarationOnly": true,
8
+ "outDir": "dist"
9
+ },
10
+ "include": ["src", "src/.metadata/*.ts", "src/**/*.json"]
11
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../../tsconfig.base",
3
+ "compilerOptions": {
4
+ "noEmit": true
5
+ },
6
+ "include": ["src", "src/.metadata/*.ts", "src/**/*.json", "test"]
7
+ }