@btst/stack 2.8.1 → 2.9.1

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 (196) hide show
  1. package/README.md +3 -2
  2. package/dist/components/markdown/index.d.cts +15 -2
  3. package/dist/components/markdown/index.d.mts +15 -2
  4. package/dist/components/markdown/index.d.ts +15 -2
  5. package/dist/packages/stack/src/plugins/blog/client/components/forms/image-field.cjs +30 -1
  6. package/dist/packages/stack/src/plugins/blog/client/components/forms/image-field.mjs +30 -1
  7. package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.cjs +49 -9
  8. package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.mjs +50 -10
  9. package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor.cjs +77 -9
  10. package/dist/packages/stack/src/plugins/blog/client/components/forms/markdown-editor.mjs +77 -9
  11. package/dist/packages/stack/src/plugins/cms/client/components/forms/content-form.cjs +24 -5
  12. package/dist/packages/stack/src/plugins/cms/client/components/forms/content-form.mjs +24 -5
  13. package/dist/packages/stack/src/plugins/cms/client/components/forms/file-upload.cjs +47 -13
  14. package/dist/packages/stack/src/plugins/cms/client/components/forms/file-upload.mjs +47 -13
  15. package/dist/packages/stack/src/plugins/kanban/client/components/forms/board-form.cjs +1 -1
  16. package/dist/packages/stack/src/plugins/kanban/client/components/forms/board-form.mjs +1 -1
  17. package/dist/packages/stack/src/plugins/kanban/client/components/forms/task-form.cjs +6 -2
  18. package/dist/packages/stack/src/plugins/kanban/client/components/forms/task-form.mjs +6 -2
  19. package/dist/packages/stack/src/plugins/media/api/adapters/local.cjs +55 -0
  20. package/dist/packages/stack/src/plugins/media/api/adapters/local.mjs +37 -0
  21. package/dist/packages/stack/src/plugins/media/api/getters.cjs +83 -0
  22. package/dist/packages/stack/src/plugins/media/api/getters.mjs +78 -0
  23. package/dist/packages/stack/src/plugins/media/api/mutations.cjs +88 -0
  24. package/dist/packages/stack/src/plugins/media/api/mutations.mjs +82 -0
  25. package/dist/packages/stack/src/plugins/media/api/plugin.cjs +525 -0
  26. package/dist/packages/stack/src/plugins/media/api/plugin.mjs +523 -0
  27. package/dist/packages/stack/src/plugins/media/api/query-key-defs.cjs +19 -0
  28. package/dist/packages/stack/src/plugins/media/api/query-key-defs.mjs +16 -0
  29. package/dist/packages/stack/src/plugins/media/api/serializers.cjs +17 -0
  30. package/dist/packages/stack/src/plugins/media/api/serializers.mjs +14 -0
  31. package/dist/packages/stack/src/plugins/media/api/storage-adapter.cjs +15 -0
  32. package/dist/packages/stack/src/plugins/media/api/storage-adapter.mjs +11 -0
  33. package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-card.cjs +129 -0
  34. package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-card.mjs +127 -0
  35. package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-preview-button.cjs +58 -0
  36. package/dist/packages/stack/src/plugins/media/client/components/media-picker/asset-preview-button.mjs +56 -0
  37. package/dist/packages/stack/src/plugins/media/client/components/media-picker/browse-tab.cjs +94 -0
  38. package/dist/packages/stack/src/plugins/media/client/components/media-picker/browse-tab.mjs +92 -0
  39. package/dist/packages/stack/src/plugins/media/client/components/media-picker/folder-tree.cjs +171 -0
  40. package/dist/packages/stack/src/plugins/media/client/components/media-picker/folder-tree.mjs +168 -0
  41. package/dist/packages/stack/src/plugins/media/client/components/media-picker/index.cjs +308 -0
  42. package/dist/packages/stack/src/plugins/media/client/components/media-picker/index.mjs +305 -0
  43. package/dist/packages/stack/src/plugins/media/client/components/media-picker/upload-tab.cjs +104 -0
  44. package/dist/packages/stack/src/plugins/media/client/components/media-picker/upload-tab.mjs +102 -0
  45. package/dist/packages/stack/src/plugins/media/client/components/media-picker/url-tab.cjs +70 -0
  46. package/dist/packages/stack/src/plugins/media/client/components/media-picker/url-tab.mjs +68 -0
  47. package/dist/packages/stack/src/plugins/media/client/components/media-picker/utils.cjs +21 -0
  48. package/dist/packages/stack/src/plugins/media/client/components/media-picker/utils.mjs +17 -0
  49. package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.cjs +35 -0
  50. package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.internal.cjs +125 -0
  51. package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.internal.mjs +123 -0
  52. package/dist/packages/stack/src/plugins/media/client/components/pages/library-page.mjs +33 -0
  53. package/dist/packages/stack/src/plugins/media/client/hooks/use-media.cjs +222 -0
  54. package/dist/packages/stack/src/plugins/media/client/hooks/use-media.mjs +214 -0
  55. package/dist/packages/stack/src/plugins/media/client/plugin.cjs +94 -0
  56. package/dist/packages/stack/src/plugins/media/client/plugin.mjs +92 -0
  57. package/dist/packages/stack/src/plugins/media/client/upload.cjs +121 -0
  58. package/dist/packages/stack/src/plugins/media/client/upload.mjs +119 -0
  59. package/dist/packages/stack/src/plugins/media/client/utils/image-compression.cjs +67 -0
  60. package/dist/packages/stack/src/plugins/media/client/utils/image-compression.mjs +65 -0
  61. package/dist/packages/stack/src/plugins/media/db.cjs +62 -0
  62. package/dist/packages/stack/src/plugins/media/db.mjs +60 -0
  63. package/dist/packages/stack/src/plugins/media/schemas.cjs +41 -0
  64. package/dist/packages/stack/src/plugins/media/schemas.mjs +35 -0
  65. package/dist/packages/ui/src/components/minimal-tiptap/components/image/image-edit-block.cjs +18 -1
  66. package/dist/packages/ui/src/components/minimal-tiptap/components/image/image-edit-block.mjs +19 -2
  67. package/dist/packages/ui/src/components/minimal-tiptap/components/image/image-edit-dialog.cjs +2 -2
  68. package/dist/packages/ui/src/components/minimal-tiptap/components/image/image-edit-dialog.mjs +2 -2
  69. package/dist/packages/ui/src/components/minimal-tiptap/components/section/five.cjs +3 -2
  70. package/dist/packages/ui/src/components/minimal-tiptap/components/section/five.mjs +3 -2
  71. package/dist/packages/ui/src/components/minimal-tiptap/minimal-tiptap.cjs +12 -5
  72. package/dist/packages/ui/src/components/minimal-tiptap/minimal-tiptap.mjs +12 -5
  73. package/dist/plugins/blog/client/index.d.cts +58 -1
  74. package/dist/plugins/blog/client/index.d.mts +58 -1
  75. package/dist/plugins/blog/client/index.d.ts +58 -1
  76. package/dist/plugins/cms/client/index.d.cts +73 -3
  77. package/dist/plugins/cms/client/index.d.mts +73 -3
  78. package/dist/plugins/cms/client/index.d.ts +73 -3
  79. package/dist/plugins/kanban/api/index.d.cts +1 -1
  80. package/dist/plugins/kanban/api/index.d.mts +1 -1
  81. package/dist/plugins/kanban/api/index.d.ts +1 -1
  82. package/dist/plugins/kanban/client/hooks/index.d.cts +1 -1
  83. package/dist/plugins/kanban/client/hooks/index.d.mts +1 -1
  84. package/dist/plugins/kanban/client/hooks/index.d.ts +1 -1
  85. package/dist/plugins/kanban/client/index.d.cts +1 -1
  86. package/dist/plugins/kanban/client/index.d.mts +1 -1
  87. package/dist/plugins/kanban/client/index.d.ts +1 -1
  88. package/dist/plugins/kanban/query-keys.d.cts +1 -1
  89. package/dist/plugins/kanban/query-keys.d.mts +1 -1
  90. package/dist/plugins/kanban/query-keys.d.ts +1 -1
  91. package/dist/plugins/media/api/adapters/s3.cjs +106 -0
  92. package/dist/plugins/media/api/adapters/s3.d.cts +60 -0
  93. package/dist/plugins/media/api/adapters/s3.d.mts +60 -0
  94. package/dist/plugins/media/api/adapters/s3.d.ts +60 -0
  95. package/dist/plugins/media/api/adapters/s3.mjs +104 -0
  96. package/dist/plugins/media/api/adapters/vercel-blob.cjs +53 -0
  97. package/dist/plugins/media/api/adapters/vercel-blob.d.cts +41 -0
  98. package/dist/plugins/media/api/adapters/vercel-blob.d.mts +41 -0
  99. package/dist/plugins/media/api/adapters/vercel-blob.d.ts +41 -0
  100. package/dist/plugins/media/api/adapters/vercel-blob.mjs +51 -0
  101. package/dist/plugins/media/api/index.cjs +26 -0
  102. package/dist/plugins/media/api/index.d.cts +116 -0
  103. package/dist/plugins/media/api/index.d.mts +116 -0
  104. package/dist/plugins/media/api/index.d.ts +116 -0
  105. package/dist/plugins/media/api/index.mjs +6 -0
  106. package/dist/plugins/media/client/components/index.cjs +10 -0
  107. package/dist/plugins/media/client/components/index.d.cts +55 -0
  108. package/dist/plugins/media/client/components/index.d.mts +55 -0
  109. package/dist/plugins/media/client/components/index.d.ts +55 -0
  110. package/dist/plugins/media/client/components/index.mjs +2 -0
  111. package/dist/plugins/media/client/hooks/index.cjs +13 -0
  112. package/dist/plugins/media/client/hooks/index.d.cts +53 -0
  113. package/dist/plugins/media/client/hooks/index.d.mts +53 -0
  114. package/dist/plugins/media/client/hooks/index.d.ts +53 -0
  115. package/dist/plugins/media/client/hooks/index.mjs +1 -0
  116. package/dist/plugins/media/client/index.cjs +9 -0
  117. package/dist/plugins/media/client/index.d.cts +242 -0
  118. package/dist/plugins/media/client/index.d.mts +242 -0
  119. package/dist/plugins/media/client/index.d.ts +242 -0
  120. package/dist/plugins/media/client/index.mjs +2 -0
  121. package/dist/plugins/media/client.css +1 -0
  122. package/dist/plugins/media/query-keys.cjs +72 -0
  123. package/dist/plugins/media/query-keys.d.cts +49 -0
  124. package/dist/plugins/media/query-keys.d.mts +49 -0
  125. package/dist/plugins/media/query-keys.d.ts +49 -0
  126. package/dist/plugins/media/query-keys.mjs +70 -0
  127. package/dist/plugins/media/style.css +1 -0
  128. package/dist/shared/{stack.DRpeDS6X.d.ts → stack.BMx2QYOK.d.ts} +25 -0
  129. package/dist/shared/stack.BttDsJJn.d.cts +109 -0
  130. package/dist/shared/stack.BttDsJJn.d.mts +109 -0
  131. package/dist/shared/stack.BttDsJJn.d.ts +109 -0
  132. package/dist/shared/stack.C7vfOBmO.d.mts +63 -0
  133. package/dist/shared/stack.CAni8dnD.d.cts +63 -0
  134. package/dist/shared/stack.CI8iRKKi.d.cts +286 -0
  135. package/dist/shared/stack.CLcnSF_b.d.cts +25 -0
  136. package/dist/shared/stack.CLcnSF_b.d.mts +25 -0
  137. package/dist/shared/stack.CLcnSF_b.d.ts +25 -0
  138. package/dist/shared/stack.CYSwntXC.d.ts +63 -0
  139. package/dist/shared/{stack.Jb0kQDJC.d.mts → stack.Cd6McBu1.d.mts} +25 -0
  140. package/dist/shared/stack.DJDjdG64.d.ts +286 -0
  141. package/dist/shared/{stack.BxFl46lB.d.cts → stack.DxQl8Wa1.d.cts} +25 -0
  142. package/dist/shared/stack.FgBVDSPi.d.mts +286 -0
  143. package/package.json +113 -4
  144. package/src/plugins/blog/client/components/forms/image-field.tsx +35 -4
  145. package/src/plugins/blog/client/components/forms/markdown-editor-with-overrides.tsx +67 -12
  146. package/src/plugins/blog/client/components/forms/markdown-editor.tsx +106 -10
  147. package/src/plugins/blog/client/overrides.ts +58 -1
  148. package/src/plugins/cms/client/components/forms/content-form.tsx +26 -7
  149. package/src/plugins/cms/client/components/forms/file-upload.tsx +73 -15
  150. package/src/plugins/cms/client/overrides.ts +57 -2
  151. package/src/plugins/kanban/client/components/forms/board-form.tsx +1 -1
  152. package/src/plugins/kanban/client/components/forms/task-form.tsx +7 -1
  153. package/src/plugins/kanban/client/overrides.ts +25 -0
  154. package/src/plugins/media/__tests__/__stubs__/vercel-blob-server.ts +9 -0
  155. package/src/plugins/media/__tests__/getters.test.ts +274 -0
  156. package/src/plugins/media/__tests__/mutations.test.ts +299 -0
  157. package/src/plugins/media/__tests__/plugin.test.ts +752 -0
  158. package/src/plugins/media/__tests__/query-key-defs.test.ts +54 -0
  159. package/src/plugins/media/__tests__/storage-adapters.test.ts +351 -0
  160. package/src/plugins/media/api/adapters/local.ts +79 -0
  161. package/src/plugins/media/api/adapters/s3.ts +198 -0
  162. package/src/plugins/media/api/adapters/vercel-blob.ts +131 -0
  163. package/src/plugins/media/api/getters.ts +174 -0
  164. package/src/plugins/media/api/index.ts +41 -0
  165. package/src/plugins/media/api/mutations.ts +179 -0
  166. package/src/plugins/media/api/plugin.ts +855 -0
  167. package/src/plugins/media/api/query-key-defs.ts +41 -0
  168. package/src/plugins/media/api/serializers.ts +28 -0
  169. package/src/plugins/media/api/storage-adapter.ts +139 -0
  170. package/src/plugins/media/client/components/index.tsx +6 -0
  171. package/src/plugins/media/client/components/media-picker/asset-card.tsx +150 -0
  172. package/src/plugins/media/client/components/media-picker/asset-preview-button.tsx +67 -0
  173. package/src/plugins/media/client/components/media-picker/browse-tab.tsx +116 -0
  174. package/src/plugins/media/client/components/media-picker/folder-tree.tsx +188 -0
  175. package/src/plugins/media/client/components/media-picker/index.tsx +347 -0
  176. package/src/plugins/media/client/components/media-picker/upload-tab.tsx +108 -0
  177. package/src/plugins/media/client/components/media-picker/url-tab.tsx +72 -0
  178. package/src/plugins/media/client/components/media-picker/utils.ts +17 -0
  179. package/src/plugins/media/client/components/pages/library-page.internal.tsx +134 -0
  180. package/src/plugins/media/client/components/pages/library-page.tsx +42 -0
  181. package/src/plugins/media/client/hooks/index.tsx +9 -0
  182. package/src/plugins/media/client/hooks/use-media.tsx +289 -0
  183. package/src/plugins/media/client/index.ts +4 -0
  184. package/src/plugins/media/client/overrides.ts +127 -0
  185. package/src/plugins/media/client/plugin.tsx +184 -0
  186. package/src/plugins/media/client/upload.ts +171 -0
  187. package/src/plugins/media/client/utils/image-compression.ts +131 -0
  188. package/src/plugins/media/client.css +1 -0
  189. package/src/plugins/media/db.ts +62 -0
  190. package/src/plugins/media/query-keys.ts +96 -0
  191. package/src/plugins/media/schemas.ts +37 -0
  192. package/src/plugins/media/style.css +1 -0
  193. package/src/plugins/media/types.ts +26 -0
  194. package/dist/shared/{stack.BOokfhZD.d.cts → stack.B6S3cgwN.d.cts} +16 -16
  195. package/dist/shared/{stack.CWxAl9K3.d.mts → stack.Bzfx-_lq.d.mts} +16 -16
  196. package/dist/shared/{stack.BvCR4-9H.d.ts → stack.j5SFLC1d.d.ts} +16 -16
@@ -392,6 +392,17 @@ declare const BLOG_LOCALIZATION: {
392
392
  };
393
393
  type BlogLocalization = typeof BLOG_LOCALIZATION;
394
394
 
395
+ /**
396
+ * Props for the overridable blog featured image input component.
397
+ */
398
+ interface BlogImageInputFieldProps {
399
+ /** Current image URL value */
400
+ value: string;
401
+ /** Called when the image URL changes */
402
+ onChange: (value: string) => void;
403
+ /** Whether the field is required */
404
+ isRequired?: boolean;
405
+ }
395
406
  /**
396
407
  * Context passed to lifecycle hooks
397
408
  */
@@ -435,9 +446,55 @@ interface BlogPluginOverrides {
435
446
  */
436
447
  Image?: ComponentType<React.ImgHTMLAttributes<HTMLImageElement> & Record<string, any>>;
437
448
  /**
438
- * Function used to upload an image and return its URL.
449
+ * Function used to upload a new image file and return its URL.
450
+ * This is separate from `imagePicker`, which selects an existing asset URL.
439
451
  */
440
452
  uploadImage: (file: File) => Promise<string>;
453
+ /**
454
+ * Optional custom component for the featured image field.
455
+ *
456
+ * When provided it replaces the default file-upload input entirely.
457
+ * The component receives `value` (current URL string) and `onChange` (setter).
458
+ *
459
+ * Typical use case: render a preview when a value is set, and a media-picker
460
+ * trigger when no value is set.
461
+ *
462
+ * @example
463
+ * ```tsx
464
+ * imageInputField: ({ value, onChange }) =>
465
+ * value ? (
466
+ * <div>
467
+ * <img src={value} alt="Preview" />
468
+ * <MediaPicker trigger={<button>Change</button>} accept={["image/*"]}
469
+ * onSelect={(assets) => onChange(assets[0].url)} />
470
+ * </div>
471
+ * ) : (
472
+ * <MediaPicker trigger={<button>Browse media</button>} accept={["image/*"]}
473
+ * onSelect={(assets) => onChange(assets[0].url)} />
474
+ * )
475
+ * ```
476
+ */
477
+ imageInputField?: ComponentType<BlogImageInputFieldProps>;
478
+ /**
479
+ * Optional trigger component for a media picker.
480
+ * When provided, it is rendered adjacent to the Markdown editor and allows
481
+ * users to browse and select previously uploaded assets.
482
+ * Receives `onSelect(url)` — insert the chosen URL into the editor.
483
+ *
484
+ * @example
485
+ * ```tsx
486
+ * imagePicker: ({ onSelect }) => (
487
+ * <MediaPicker
488
+ * trigger={<Button size="sm" variant="outline">Browse media</Button>}
489
+ * accept={["image/*"]}
490
+ * onSelect={(assets) => onSelect(assets[0].url)}
491
+ * />
492
+ * )
493
+ * ```
494
+ */
495
+ imagePicker?: ComponentType<{
496
+ onSelect: (url: string) => void;
497
+ }>;
441
498
  /**
442
499
  * Localization object for the blog plugin
443
500
  */
@@ -261,6 +261,17 @@ declare const CMS_LOCALIZATION: {
261
261
  };
262
262
  type CMSLocalization = typeof CMS_LOCALIZATION;
263
263
 
264
+ /**
265
+ * Props for the overridable CMS image input field component.
266
+ */
267
+ interface CmsImageInputFieldProps {
268
+ /** Current image URL value */
269
+ value: string;
270
+ /** Called when the image URL changes */
271
+ onChange: (value: string) => void;
272
+ /** Whether the field is required */
273
+ isRequired?: boolean;
274
+ }
264
275
  /**
265
276
  * Context passed to lifecycle hooks
266
277
  */
@@ -298,10 +309,53 @@ interface CMSPluginOverrides {
298
309
  */
299
310
  Image?: ComponentType<React.ImgHTMLAttributes<HTMLImageElement> & Record<string, unknown>>;
300
311
  /**
301
- * Function used to upload an image and return its URL.
302
- * Used by the default "file" field component.
312
+ * Function used to upload a new image file and return its URL.
313
+ * Used by the default "file" field component when not selecting an existing
314
+ * asset via `imagePicker` or `imageInputField`.
303
315
  */
304
316
  uploadImage?: (file: File) => Promise<string>;
317
+ /**
318
+ * Optional custom component for image fields (fieldType: "file").
319
+ *
320
+ * When provided it replaces the default file-upload input entirely.
321
+ * The component receives `value` (current URL string) and `onChange` (setter).
322
+ *
323
+ * @example
324
+ * ```tsx
325
+ * imageInputField: ({ value, onChange }) =>
326
+ * value ? (
327
+ * <div>
328
+ * <img src={value} alt="Preview" />
329
+ * <MediaPicker trigger={<button>Change</button>} accept={["image/*"]}
330
+ * onSelect={(assets) => onChange(assets[0].url)} />
331
+ * </div>
332
+ * ) : (
333
+ * <MediaPicker trigger={<button>Browse media</button>} accept={["image/*"]}
334
+ * onSelect={(assets) => onChange(assets[0].url)} />
335
+ * )
336
+ * ```
337
+ */
338
+ imageInputField?: ComponentType<CmsImageInputFieldProps>;
339
+ /**
340
+ * Optional trigger component for a media picker.
341
+ * When provided, it is rendered inside the default "file" field component as a
342
+ * "Browse media" option, letting users select a previously uploaded asset.
343
+ * Receives `onSelect(url)` — the URL is set as the field value.
344
+ *
345
+ * @example
346
+ * ```tsx
347
+ * imagePicker: ({ onSelect }) => (
348
+ * <MediaPicker
349
+ * trigger={<Button size="sm" variant="outline">Browse media</Button>}
350
+ * accept={["image/*"]}
351
+ * onSelect={(assets) => onSelect(assets[0].url)}
352
+ * />
353
+ * )
354
+ * ```
355
+ */
356
+ imagePicker?: ComponentType<{
357
+ onSelect: (url: string) => void;
358
+ }>;
305
359
  /**
306
360
  * Custom field components for AutoForm fields.
307
361
  *
@@ -402,6 +456,22 @@ interface CMSFileUploadProps extends AutoFormInputComponentProps {
402
456
  * This is required - consumers must provide an upload implementation.
403
457
  */
404
458
  uploadImage: (file: File) => Promise<string>;
459
+ /**
460
+ * Optional custom component for the image field.
461
+ * When provided, it replaces the default file-upload input entirely.
462
+ */
463
+ imageInputField?: ComponentType<{
464
+ value: string;
465
+ onChange: (value: string) => void;
466
+ isRequired?: boolean;
467
+ }>;
468
+ /**
469
+ * Optional trigger component for a media picker.
470
+ * When provided, it is rendered as a "Browse media" option.
471
+ */
472
+ imagePicker?: ComponentType<{
473
+ onSelect: (url: string) => void;
474
+ }>;
405
475
  }
406
476
  /**
407
477
  * Default file upload component for CMS image fields.
@@ -425,7 +495,7 @@ interface CMSFileUploadProps extends AutoFormInputComponentProps {
425
495
  * }
426
496
  * ```
427
497
  */
428
- declare function CMSFileUpload({ label, isRequired, fieldConfigItem, fieldProps, field, uploadImage, }: CMSFileUploadProps): react_jsx_runtime.JSX.Element;
498
+ declare function CMSFileUpload({ label, isRequired, fieldConfigItem, fieldProps, field, uploadImage, imageInputField: ImageInputField, imagePicker: ImagePickerTrigger, }: CMSFileUploadProps): react_jsx_runtime.JSX.Element;
429
499
 
430
500
  export { AutoFormInputComponentProps, CMSFileUpload, cmsClientPlugin };
431
501
  export type { CMSClientConfig, CMSClientHooks, CMSFileUploadProps, CMSLocalization, CMSPluginOverrides, LoaderContext, RouteContext };
@@ -261,6 +261,17 @@ declare const CMS_LOCALIZATION: {
261
261
  };
262
262
  type CMSLocalization = typeof CMS_LOCALIZATION;
263
263
 
264
+ /**
265
+ * Props for the overridable CMS image input field component.
266
+ */
267
+ interface CmsImageInputFieldProps {
268
+ /** Current image URL value */
269
+ value: string;
270
+ /** Called when the image URL changes */
271
+ onChange: (value: string) => void;
272
+ /** Whether the field is required */
273
+ isRequired?: boolean;
274
+ }
264
275
  /**
265
276
  * Context passed to lifecycle hooks
266
277
  */
@@ -298,10 +309,53 @@ interface CMSPluginOverrides {
298
309
  */
299
310
  Image?: ComponentType<React.ImgHTMLAttributes<HTMLImageElement> & Record<string, unknown>>;
300
311
  /**
301
- * Function used to upload an image and return its URL.
302
- * Used by the default "file" field component.
312
+ * Function used to upload a new image file and return its URL.
313
+ * Used by the default "file" field component when not selecting an existing
314
+ * asset via `imagePicker` or `imageInputField`.
303
315
  */
304
316
  uploadImage?: (file: File) => Promise<string>;
317
+ /**
318
+ * Optional custom component for image fields (fieldType: "file").
319
+ *
320
+ * When provided it replaces the default file-upload input entirely.
321
+ * The component receives `value` (current URL string) and `onChange` (setter).
322
+ *
323
+ * @example
324
+ * ```tsx
325
+ * imageInputField: ({ value, onChange }) =>
326
+ * value ? (
327
+ * <div>
328
+ * <img src={value} alt="Preview" />
329
+ * <MediaPicker trigger={<button>Change</button>} accept={["image/*"]}
330
+ * onSelect={(assets) => onChange(assets[0].url)} />
331
+ * </div>
332
+ * ) : (
333
+ * <MediaPicker trigger={<button>Browse media</button>} accept={["image/*"]}
334
+ * onSelect={(assets) => onChange(assets[0].url)} />
335
+ * )
336
+ * ```
337
+ */
338
+ imageInputField?: ComponentType<CmsImageInputFieldProps>;
339
+ /**
340
+ * Optional trigger component for a media picker.
341
+ * When provided, it is rendered inside the default "file" field component as a
342
+ * "Browse media" option, letting users select a previously uploaded asset.
343
+ * Receives `onSelect(url)` — the URL is set as the field value.
344
+ *
345
+ * @example
346
+ * ```tsx
347
+ * imagePicker: ({ onSelect }) => (
348
+ * <MediaPicker
349
+ * trigger={<Button size="sm" variant="outline">Browse media</Button>}
350
+ * accept={["image/*"]}
351
+ * onSelect={(assets) => onSelect(assets[0].url)}
352
+ * />
353
+ * )
354
+ * ```
355
+ */
356
+ imagePicker?: ComponentType<{
357
+ onSelect: (url: string) => void;
358
+ }>;
305
359
  /**
306
360
  * Custom field components for AutoForm fields.
307
361
  *
@@ -402,6 +456,22 @@ interface CMSFileUploadProps extends AutoFormInputComponentProps {
402
456
  * This is required - consumers must provide an upload implementation.
403
457
  */
404
458
  uploadImage: (file: File) => Promise<string>;
459
+ /**
460
+ * Optional custom component for the image field.
461
+ * When provided, it replaces the default file-upload input entirely.
462
+ */
463
+ imageInputField?: ComponentType<{
464
+ value: string;
465
+ onChange: (value: string) => void;
466
+ isRequired?: boolean;
467
+ }>;
468
+ /**
469
+ * Optional trigger component for a media picker.
470
+ * When provided, it is rendered as a "Browse media" option.
471
+ */
472
+ imagePicker?: ComponentType<{
473
+ onSelect: (url: string) => void;
474
+ }>;
405
475
  }
406
476
  /**
407
477
  * Default file upload component for CMS image fields.
@@ -425,7 +495,7 @@ interface CMSFileUploadProps extends AutoFormInputComponentProps {
425
495
  * }
426
496
  * ```
427
497
  */
428
- declare function CMSFileUpload({ label, isRequired, fieldConfigItem, fieldProps, field, uploadImage, }: CMSFileUploadProps): react_jsx_runtime.JSX.Element;
498
+ declare function CMSFileUpload({ label, isRequired, fieldConfigItem, fieldProps, field, uploadImage, imageInputField: ImageInputField, imagePicker: ImagePickerTrigger, }: CMSFileUploadProps): react_jsx_runtime.JSX.Element;
429
499
 
430
500
  export { AutoFormInputComponentProps, CMSFileUpload, cmsClientPlugin };
431
501
  export type { CMSClientConfig, CMSClientHooks, CMSFileUploadProps, CMSLocalization, CMSPluginOverrides, LoaderContext, RouteContext };
@@ -261,6 +261,17 @@ declare const CMS_LOCALIZATION: {
261
261
  };
262
262
  type CMSLocalization = typeof CMS_LOCALIZATION;
263
263
 
264
+ /**
265
+ * Props for the overridable CMS image input field component.
266
+ */
267
+ interface CmsImageInputFieldProps {
268
+ /** Current image URL value */
269
+ value: string;
270
+ /** Called when the image URL changes */
271
+ onChange: (value: string) => void;
272
+ /** Whether the field is required */
273
+ isRequired?: boolean;
274
+ }
264
275
  /**
265
276
  * Context passed to lifecycle hooks
266
277
  */
@@ -298,10 +309,53 @@ interface CMSPluginOverrides {
298
309
  */
299
310
  Image?: ComponentType<React.ImgHTMLAttributes<HTMLImageElement> & Record<string, unknown>>;
300
311
  /**
301
- * Function used to upload an image and return its URL.
302
- * Used by the default "file" field component.
312
+ * Function used to upload a new image file and return its URL.
313
+ * Used by the default "file" field component when not selecting an existing
314
+ * asset via `imagePicker` or `imageInputField`.
303
315
  */
304
316
  uploadImage?: (file: File) => Promise<string>;
317
+ /**
318
+ * Optional custom component for image fields (fieldType: "file").
319
+ *
320
+ * When provided it replaces the default file-upload input entirely.
321
+ * The component receives `value` (current URL string) and `onChange` (setter).
322
+ *
323
+ * @example
324
+ * ```tsx
325
+ * imageInputField: ({ value, onChange }) =>
326
+ * value ? (
327
+ * <div>
328
+ * <img src={value} alt="Preview" />
329
+ * <MediaPicker trigger={<button>Change</button>} accept={["image/*"]}
330
+ * onSelect={(assets) => onChange(assets[0].url)} />
331
+ * </div>
332
+ * ) : (
333
+ * <MediaPicker trigger={<button>Browse media</button>} accept={["image/*"]}
334
+ * onSelect={(assets) => onChange(assets[0].url)} />
335
+ * )
336
+ * ```
337
+ */
338
+ imageInputField?: ComponentType<CmsImageInputFieldProps>;
339
+ /**
340
+ * Optional trigger component for a media picker.
341
+ * When provided, it is rendered inside the default "file" field component as a
342
+ * "Browse media" option, letting users select a previously uploaded asset.
343
+ * Receives `onSelect(url)` — the URL is set as the field value.
344
+ *
345
+ * @example
346
+ * ```tsx
347
+ * imagePicker: ({ onSelect }) => (
348
+ * <MediaPicker
349
+ * trigger={<Button size="sm" variant="outline">Browse media</Button>}
350
+ * accept={["image/*"]}
351
+ * onSelect={(assets) => onSelect(assets[0].url)}
352
+ * />
353
+ * )
354
+ * ```
355
+ */
356
+ imagePicker?: ComponentType<{
357
+ onSelect: (url: string) => void;
358
+ }>;
305
359
  /**
306
360
  * Custom field components for AutoForm fields.
307
361
  *
@@ -402,6 +456,22 @@ interface CMSFileUploadProps extends AutoFormInputComponentProps {
402
456
  * This is required - consumers must provide an upload implementation.
403
457
  */
404
458
  uploadImage: (file: File) => Promise<string>;
459
+ /**
460
+ * Optional custom component for the image field.
461
+ * When provided, it replaces the default file-upload input entirely.
462
+ */
463
+ imageInputField?: ComponentType<{
464
+ value: string;
465
+ onChange: (value: string) => void;
466
+ isRequired?: boolean;
467
+ }>;
468
+ /**
469
+ * Optional trigger component for a media picker.
470
+ * When provided, it is rendered as a "Browse media" option.
471
+ */
472
+ imagePicker?: ComponentType<{
473
+ onSelect: (url: string) => void;
474
+ }>;
405
475
  }
406
476
  /**
407
477
  * Default file upload component for CMS image fields.
@@ -425,7 +495,7 @@ interface CMSFileUploadProps extends AutoFormInputComponentProps {
425
495
  * }
426
496
  * ```
427
497
  */
428
- declare function CMSFileUpload({ label, isRequired, fieldConfigItem, fieldProps, field, uploadImage, }: CMSFileUploadProps): react_jsx_runtime.JSX.Element;
498
+ declare function CMSFileUpload({ label, isRequired, fieldConfigItem, fieldProps, field, uploadImage, imageInputField: ImageInputField, imagePicker: ImagePickerTrigger, }: CMSFileUploadProps): react_jsx_runtime.JSX.Element;
429
499
 
430
500
  export { AutoFormInputComponentProps, CMSFileUpload, cmsClientPlugin };
431
501
  export type { CMSClientConfig, CMSClientHooks, CMSFileUploadProps, CMSLocalization, CMSPluginOverrides, LoaderContext, RouteContext };
@@ -1,4 +1,4 @@
1
- export { B as BoardListResult, C as CreateKanbanTaskInput, i as KANBAN_QUERY_KEYS, b as KanbanApiContext, K as KanbanApiRouter, c as KanbanBackendHooks, a as KanbanRouteKey, e as createKanbanTask, f as findOrCreateKanbanBoard, g as getAllBoards, d as getBoardById, h as getKanbanColumnsByBoardId, k as kanbanBackendPlugin } from '../../../shared/stack.BOokfhZD.cjs';
1
+ export { B as BoardListResult, C as CreateKanbanTaskInput, i as KANBAN_QUERY_KEYS, b as KanbanApiContext, K as KanbanApiRouter, c as KanbanBackendHooks, a as KanbanRouteKey, e as createKanbanTask, f as findOrCreateKanbanBoard, g as getAllBoards, d as getBoardById, h as getKanbanColumnsByBoardId, k as kanbanBackendPlugin } from '../../../shared/stack.B6S3cgwN.cjs';
2
2
  import { B as BoardWithColumns, S as SerializedBoardWithColumns, C as ColumnWithTasks, a as SerializedColumn, T as Task, b as SerializedTask } from '../../../shared/stack.DJaKVY7v.cjs';
3
3
  import '@btst/stack/plugins/api';
4
4
  import '@btst/db';
@@ -1,4 +1,4 @@
1
- export { B as BoardListResult, C as CreateKanbanTaskInput, i as KANBAN_QUERY_KEYS, b as KanbanApiContext, K as KanbanApiRouter, c as KanbanBackendHooks, a as KanbanRouteKey, e as createKanbanTask, f as findOrCreateKanbanBoard, g as getAllBoards, d as getBoardById, h as getKanbanColumnsByBoardId, k as kanbanBackendPlugin } from '../../../shared/stack.CWxAl9K3.mjs';
1
+ export { B as BoardListResult, C as CreateKanbanTaskInput, i as KANBAN_QUERY_KEYS, b as KanbanApiContext, K as KanbanApiRouter, c as KanbanBackendHooks, a as KanbanRouteKey, e as createKanbanTask, f as findOrCreateKanbanBoard, g as getAllBoards, d as getBoardById, h as getKanbanColumnsByBoardId, k as kanbanBackendPlugin } from '../../../shared/stack.Bzfx-_lq.mjs';
2
2
  import { B as BoardWithColumns, S as SerializedBoardWithColumns, C as ColumnWithTasks, a as SerializedColumn, T as Task, b as SerializedTask } from '../../../shared/stack.DJaKVY7v.mjs';
3
3
  import '@btst/stack/plugins/api';
4
4
  import '@btst/db';
@@ -1,4 +1,4 @@
1
- export { B as BoardListResult, C as CreateKanbanTaskInput, i as KANBAN_QUERY_KEYS, b as KanbanApiContext, K as KanbanApiRouter, c as KanbanBackendHooks, a as KanbanRouteKey, e as createKanbanTask, f as findOrCreateKanbanBoard, g as getAllBoards, d as getBoardById, h as getKanbanColumnsByBoardId, k as kanbanBackendPlugin } from '../../../shared/stack.BvCR4-9H.js';
1
+ export { B as BoardListResult, C as CreateKanbanTaskInput, i as KANBAN_QUERY_KEYS, b as KanbanApiContext, K as KanbanApiRouter, c as KanbanBackendHooks, a as KanbanRouteKey, e as createKanbanTask, f as findOrCreateKanbanBoard, g as getAllBoards, d as getBoardById, h as getKanbanColumnsByBoardId, k as kanbanBackendPlugin } from '../../../shared/stack.j5SFLC1d.js';
2
2
  import { B as BoardWithColumns, S as SerializedBoardWithColumns, C as ColumnWithTasks, a as SerializedColumn, T as Task, b as SerializedTask } from '../../../shared/stack.DJaKVY7v.js';
3
3
  import '@btst/stack/plugins/api';
4
4
  import '@btst/db';
@@ -1,5 +1,5 @@
1
1
  import * as _tanstack_react_query from '@tanstack/react-query';
2
- import { a as KanbanUser } from '../../../../shared/stack.BxFl46lB.cjs';
2
+ import { a as KanbanUser } from '../../../../shared/stack.DxQl8Wa1.cjs';
3
3
  import { S as SerializedBoardWithColumns, c as SerializedBoard, a as SerializedColumn, b as SerializedTask, P as Priority } from '../../../../shared/stack.DJaKVY7v.cjs';
4
4
  import 'react';
5
5
 
@@ -1,5 +1,5 @@
1
1
  import * as _tanstack_react_query from '@tanstack/react-query';
2
- import { a as KanbanUser } from '../../../../shared/stack.Jb0kQDJC.mjs';
2
+ import { a as KanbanUser } from '../../../../shared/stack.Cd6McBu1.mjs';
3
3
  import { S as SerializedBoardWithColumns, c as SerializedBoard, a as SerializedColumn, b as SerializedTask, P as Priority } from '../../../../shared/stack.DJaKVY7v.mjs';
4
4
  import 'react';
5
5
 
@@ -1,5 +1,5 @@
1
1
  import * as _tanstack_react_query from '@tanstack/react-query';
2
- import { a as KanbanUser } from '../../../../shared/stack.DRpeDS6X.js';
2
+ import { a as KanbanUser } from '../../../../shared/stack.BMx2QYOK.js';
3
3
  import { S as SerializedBoardWithColumns, c as SerializedBoard, a as SerializedColumn, b as SerializedTask, P as Priority } from '../../../../shared/stack.DJaKVY7v.js';
4
4
  import 'react';
5
5
 
@@ -3,7 +3,7 @@ import * as _btst_yar from '@btst/yar';
3
3
  import { ComponentType } from 'react';
4
4
  import { QueryClient } from '@tanstack/react-query';
5
5
  import { S as SerializedBoardWithColumns } from '../../../shared/stack.DJaKVY7v.cjs';
6
- export { K as KanbanPluginOverrides, a as KanbanUser } from '../../../shared/stack.BxFl46lB.cjs';
6
+ export { K as KanbanPluginOverrides, a as KanbanUser } from '../../../shared/stack.DxQl8Wa1.cjs';
7
7
 
8
8
  /**
9
9
  * Context passed to route hooks
@@ -3,7 +3,7 @@ import * as _btst_yar from '@btst/yar';
3
3
  import { ComponentType } from 'react';
4
4
  import { QueryClient } from '@tanstack/react-query';
5
5
  import { S as SerializedBoardWithColumns } from '../../../shared/stack.DJaKVY7v.mjs';
6
- export { K as KanbanPluginOverrides, a as KanbanUser } from '../../../shared/stack.Jb0kQDJC.mjs';
6
+ export { K as KanbanPluginOverrides, a as KanbanUser } from '../../../shared/stack.Cd6McBu1.mjs';
7
7
 
8
8
  /**
9
9
  * Context passed to route hooks
@@ -3,7 +3,7 @@ import * as _btst_yar from '@btst/yar';
3
3
  import { ComponentType } from 'react';
4
4
  import { QueryClient } from '@tanstack/react-query';
5
5
  import { S as SerializedBoardWithColumns } from '../../../shared/stack.DJaKVY7v.js';
6
- export { K as KanbanPluginOverrides, a as KanbanUser } from '../../../shared/stack.DRpeDS6X.js';
6
+ export { K as KanbanPluginOverrides, a as KanbanUser } from '../../../shared/stack.BMx2QYOK.js';
7
7
 
8
8
  /**
9
9
  * Context passed to route hooks
@@ -1,5 +1,5 @@
1
1
  import * as _tanstack_react_query from '@tanstack/react-query';
2
- import { K as KanbanApiRouter, j as BoardsListDiscriminator } from '../../shared/stack.BOokfhZD.cjs';
2
+ import { K as KanbanApiRouter, j as BoardsListDiscriminator } from '../../shared/stack.B6S3cgwN.cjs';
3
3
  import { createApiClient } from '@btst/stack/plugins/client';
4
4
  import { S as SerializedBoardWithColumns } from '../../shared/stack.DJaKVY7v.cjs';
5
5
  import '@btst/stack/plugins/api';
@@ -1,5 +1,5 @@
1
1
  import * as _tanstack_react_query from '@tanstack/react-query';
2
- import { K as KanbanApiRouter, j as BoardsListDiscriminator } from '../../shared/stack.CWxAl9K3.mjs';
2
+ import { K as KanbanApiRouter, j as BoardsListDiscriminator } from '../../shared/stack.Bzfx-_lq.mjs';
3
3
  import { createApiClient } from '@btst/stack/plugins/client';
4
4
  import { S as SerializedBoardWithColumns } from '../../shared/stack.DJaKVY7v.mjs';
5
5
  import '@btst/stack/plugins/api';
@@ -1,5 +1,5 @@
1
1
  import * as _tanstack_react_query from '@tanstack/react-query';
2
- import { K as KanbanApiRouter, j as BoardsListDiscriminator } from '../../shared/stack.BvCR4-9H.js';
2
+ import { K as KanbanApiRouter, j as BoardsListDiscriminator } from '../../shared/stack.j5SFLC1d.js';
3
3
  import { createApiClient } from '@btst/stack/plugins/client';
4
4
  import { S as SerializedBoardWithColumns } from '../../shared/stack.DJaKVY7v.js';
5
5
  import '@btst/stack/plugins/api';
@@ -0,0 +1,106 @@
1
+ 'use strict';
2
+
3
+ function s3Adapter(options) {
4
+ const {
5
+ bucket,
6
+ region,
7
+ accessKeyId,
8
+ secretAccessKey,
9
+ endpoint,
10
+ publicBaseUrl,
11
+ expiresIn = 300
12
+ } = options;
13
+ let s3ModulePromise = null;
14
+ let clientPromise = null;
15
+ function getS3Module() {
16
+ if (!s3ModulePromise) {
17
+ s3ModulePromise = import('@aws-sdk/client-s3').catch(() => {
18
+ s3ModulePromise = null;
19
+ throw new Error(
20
+ "[@btst/stack] S3 adapter requires '@aws-sdk/client-s3'. Run: npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner"
21
+ );
22
+ });
23
+ }
24
+ return s3ModulePromise;
25
+ }
26
+ function getClient() {
27
+ if (!clientPromise) {
28
+ clientPromise = getS3Module().then(({ S3Client }) => {
29
+ return new S3Client({
30
+ region,
31
+ endpoint,
32
+ credentials: { accessKeyId, secretAccessKey },
33
+ forcePathStyle: !!endpoint
34
+ });
35
+ });
36
+ }
37
+ return clientPromise.catch((error) => {
38
+ if (clientPromise) {
39
+ clientPromise = null;
40
+ }
41
+ if (s3ModulePromise && error instanceof Error) {
42
+ throw error;
43
+ }
44
+ throw new Error(
45
+ "[@btst/stack] S3 adapter requires '@aws-sdk/client-s3'. Run: npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner"
46
+ );
47
+ });
48
+ }
49
+ async function buildSignedUrl(client, command, opts) {
50
+ let getSignedUrl;
51
+ try {
52
+ ({ getSignedUrl } = await import('@aws-sdk/s3-request-presigner'));
53
+ } catch {
54
+ throw new Error(
55
+ "[@btst/stack] S3 adapter requires '@aws-sdk/s3-request-presigner'. Run: npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner"
56
+ );
57
+ }
58
+ return getSignedUrl(
59
+ client,
60
+ command,
61
+ opts
62
+ );
63
+ }
64
+ return {
65
+ type: "s3",
66
+ urlPrefix: publicBaseUrl.replace(/\/$/, ""),
67
+ async generateUploadToken(uploadOptions) {
68
+ const [client, { PutObjectCommand }] = await Promise.all([
69
+ getClient(),
70
+ getS3Module()
71
+ ]);
72
+ const key = uploadOptions.folderId ? `${uploadOptions.folderId}/${uploadOptions.filename}` : uploadOptions.filename;
73
+ const command = new PutObjectCommand({
74
+ Bucket: bucket,
75
+ Key: key,
76
+ ContentType: uploadOptions.mimeType,
77
+ ContentLength: uploadOptions.size
78
+ });
79
+ const uploadUrl = await buildSignedUrl(client, command, { expiresIn });
80
+ const encodedKey = key.split("/").map(encodeURIComponent).join("/");
81
+ const publicUrl = `${publicBaseUrl.replace(/\/$/, "")}/${encodedKey}`;
82
+ return {
83
+ type: "presigned-url",
84
+ payload: {
85
+ uploadUrl,
86
+ publicUrl,
87
+ key,
88
+ method: "PUT",
89
+ headers: { "Content-Type": uploadOptions.mimeType }
90
+ }
91
+ };
92
+ },
93
+ async delete(url) {
94
+ const [client, { DeleteObjectCommand }] = await Promise.all([
95
+ getClient(),
96
+ getS3Module()
97
+ ]);
98
+ const base = publicBaseUrl.replace(/\/$/, "");
99
+ const encodedKey = url.startsWith(base) ? url.slice(base.length + 1) : url.split("/").pop() ?? url;
100
+ const key = decodeURIComponent(encodedKey);
101
+ await client.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));
102
+ }
103
+ };
104
+ }
105
+
106
+ exports.s3Adapter = s3Adapter;
@@ -0,0 +1,60 @@
1
+ import { S as S3StorageAdapter } from '../../../../shared/stack.BttDsJJn.cjs';
2
+
3
+ interface S3StorageAdapterOptions {
4
+ /**
5
+ * The S3 bucket name.
6
+ */
7
+ bucket: string;
8
+ /**
9
+ * AWS region (e.g. `"us-east-1"`).
10
+ */
11
+ region: string;
12
+ /**
13
+ * AWS access key ID.
14
+ */
15
+ accessKeyId: string;
16
+ /**
17
+ * AWS secret access key.
18
+ */
19
+ secretAccessKey: string;
20
+ /**
21
+ * Custom endpoint URL for S3-compatible providers (Cloudflare R2, MinIO, etc.).
22
+ * @example "https://<account-id>.r2.cloudflarestorage.com"
23
+ */
24
+ endpoint?: string;
25
+ /**
26
+ * Base URL used to construct the final public asset URL after upload.
27
+ * @example "https://assets.example.com" or "https://pub-<id>.r2.dev"
28
+ */
29
+ publicBaseUrl: string;
30
+ /**
31
+ * Duration in seconds for which the presigned URL is valid.
32
+ * @default 300 (5 minutes)
33
+ */
34
+ expiresIn?: number;
35
+ }
36
+ /**
37
+ * Create an S3-compatible presigned-URL storage adapter.
38
+ * The server generates a short-lived presigned PUT URL; the browser uploads
39
+ * the file directly to S3 (or R2 / MinIO). The server never receives file bytes.
40
+ *
41
+ * @remarks Requires `@aws-sdk/client-s3` and `@aws-sdk/s3-request-presigner`
42
+ * as optional peer dependencies.
43
+ *
44
+ * @example
45
+ * ```ts
46
+ * mediaBackendPlugin({
47
+ * storageAdapter: s3Adapter({
48
+ * bucket: "my-bucket",
49
+ * region: "us-east-1",
50
+ * accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
51
+ * secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
52
+ * publicBaseUrl: "https://assets.example.com",
53
+ * })
54
+ * })
55
+ * ```
56
+ */
57
+ declare function s3Adapter(options: S3StorageAdapterOptions): S3StorageAdapter;
58
+
59
+ export { s3Adapter };
60
+ export type { S3StorageAdapterOptions };