@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
@@ -0,0 +1,286 @@
1
+ import * as _btst_stack_plugins_api from '@btst/stack/plugins/api';
2
+ import { a as StorageAdapter, b as S3UploadToken } from './stack.BttDsJJn.js';
3
+ import { c as AssetListResult, l as listAssets, a as listFolders, A as AssetListParams } from './stack.CYSwntXC.js';
4
+ import * as better_call from 'better-call';
5
+ import { z } from 'zod';
6
+ import { A as Asset, F as Folder } from './stack.CLcnSF_b.js';
7
+
8
+ declare const AssetListQuerySchema: z.ZodObject<{
9
+ folderId: z.ZodOptional<z.ZodString>;
10
+ mimeType: z.ZodOptional<z.ZodString>;
11
+ query: z.ZodOptional<z.ZodString>;
12
+ offset: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
13
+ limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
14
+ }, z.core.$strip>;
15
+ declare const updateAssetSchema: z.ZodObject<{
16
+ alt: z.ZodOptional<z.ZodString>;
17
+ folderId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
18
+ }, z.core.$strip>;
19
+ declare const createFolderSchema: z.ZodObject<{
20
+ name: z.ZodString;
21
+ parentId: z.ZodOptional<z.ZodString>;
22
+ }, z.core.$strip>;
23
+
24
+ /**
25
+ * Context passed to media API hooks.
26
+ */
27
+ interface MediaApiContext<TBody = unknown, TParams = unknown, TQuery = unknown> {
28
+ body?: TBody;
29
+ params?: TParams;
30
+ query?: TQuery;
31
+ request?: Request;
32
+ headers?: Headers;
33
+ [key: string]: unknown;
34
+ }
35
+ /**
36
+ * Configuration hooks for the media backend plugin.
37
+ * All hooks are optional and allow consumers to customise behaviour.
38
+ */
39
+ interface MediaBackendHooks {
40
+ /**
41
+ * Called before a file upload is allowed (both direct and signed adapters).
42
+ * Throw an Error to reject the upload (e.g. if the user is not authenticated).
43
+ */
44
+ onBeforeUpload?: (meta: {
45
+ filename: string;
46
+ mimeType: string;
47
+ size?: number;
48
+ }, context: MediaApiContext) => Promise<void> | void;
49
+ /**
50
+ * Called after an asset record is created in the database.
51
+ */
52
+ onAfterUpload?: (asset: Asset, context: MediaApiContext) => Promise<void> | void;
53
+ /**
54
+ * Called before an asset is deleted. Throw to prevent deletion.
55
+ */
56
+ onBeforeDelete?: (asset: Asset, context: MediaApiContext) => Promise<void> | void;
57
+ /**
58
+ * Called after an asset has been deleted from the DB and storage.
59
+ */
60
+ onAfterDelete?: (assetId: string, context: MediaApiContext) => Promise<void> | void;
61
+ /**
62
+ * Called before listing assets. Throw to deny access.
63
+ */
64
+ onBeforeListAssets?: (filter: z.infer<typeof AssetListQuerySchema>, context: MediaApiContext) => Promise<void> | void;
65
+ /**
66
+ * Called before updating an asset (PATCH). Throw to deny access.
67
+ */
68
+ onBeforeUpdateAsset?: (asset: Asset, updates: z.infer<typeof updateAssetSchema>, context: MediaApiContext) => Promise<void> | void;
69
+ /**
70
+ * Called before listing folders. Throw to deny access.
71
+ */
72
+ onBeforeListFolders?: (filter: {
73
+ parentId?: string;
74
+ }, context: MediaApiContext) => Promise<void> | void;
75
+ /**
76
+ * Called before creating a folder. Throw to deny access.
77
+ */
78
+ onBeforeCreateFolder?: (input: z.infer<typeof createFolderSchema>, context: MediaApiContext) => Promise<void> | void;
79
+ /**
80
+ * Called before deleting a folder. Throw to deny access.
81
+ */
82
+ onBeforeDeleteFolder?: (folder: Folder, context: MediaApiContext) => Promise<void> | void;
83
+ }
84
+ /**
85
+ * Configuration for the media backend plugin.
86
+ */
87
+ interface MediaBackendConfig {
88
+ /**
89
+ * The storage adapter to use for file uploads.
90
+ * - `localAdapter()` — writes to the local filesystem (dev / self-hosted)
91
+ * - `s3Adapter()` — presigned PUT URL (AWS S3, Cloudflare R2, MinIO)
92
+ * - `vercelBlobAdapter()` — signed direct upload via Vercel Blob
93
+ */
94
+ storageAdapter: StorageAdapter;
95
+ /**
96
+ * Maximum file size in bytes.
97
+ * Enforced server-side for `localAdapter`.
98
+ * Passed into the Vercel Blob token for edge enforcement.
99
+ * Validated against the client-reported size for `s3Adapter`.
100
+ * @default 10485760 (10 MB)
101
+ */
102
+ maxFileSizeBytes?: number;
103
+ /**
104
+ * MIME type allowlist (e.g. `["image/jpeg", "image/png"]`).
105
+ * If omitted, all MIME types are accepted.
106
+ * Enforced server-side for `localAdapter`.
107
+ * Passed to Vercel Blob token for edge enforcement.
108
+ * Validated against the client-reported MIME type for `s3Adapter`.
109
+ */
110
+ allowedMimeTypes?: string[];
111
+ /**
112
+ * URL prefixes that are allowed when creating asset records via `POST /media/assets`.
113
+ * When omitted the plugin automatically derives a safe default from the storage adapter:
114
+ * - `s3Adapter` → the configured `publicBaseUrl`
115
+ * - `vercelBlobAdapter` → any URL whose hostname ends with `.public.blob.vercel-storage.com`
116
+ * - `localAdapter` → rejects client-supplied URLs; use `POST /media/upload` instead
117
+ *
118
+ * Provide this option only when you need to override the automatic default (e.g. to allow
119
+ * assets from a CDN in front of your storage that uses a different domain). When using
120
+ * `localAdapter`, setting `allowedUrlPrefixes` explicitly opts `POST /media/assets` back in.
121
+ */
122
+ allowedUrlPrefixes?: string[];
123
+ /**
124
+ * Optional lifecycle hooks for the media backend plugin.
125
+ */
126
+ hooks?: MediaBackendHooks;
127
+ }
128
+ /**
129
+ * Media backend plugin.
130
+ * Provides API endpoints for managing media assets and folders, and supports
131
+ * local, S3-compatible, and Vercel Blob storage backends.
132
+ *
133
+ * @example
134
+ * ```ts
135
+ * import { mediaBackendPlugin, localAdapter } from "@btst/stack/plugins/media/api";
136
+ *
137
+ * mediaBackendPlugin({
138
+ * storageAdapter: localAdapter(),
139
+ * hooks: {
140
+ * onBeforeUpload: async (_meta, ctx) => {
141
+ * const session = await getSession(ctx.headers as Headers);
142
+ * if (!session) throw new Error("Unauthorized");
143
+ * },
144
+ * },
145
+ * })
146
+ * ```
147
+ */
148
+ declare const mediaBackendPlugin: (config: MediaBackendConfig) => _btst_stack_plugins_api.BackendPlugin<{
149
+ readonly listAssets: better_call.StrictEndpoint<"/media/assets", {} & {
150
+ method: "GET";
151
+ } & {
152
+ query: better_call.StandardSchemaV1<{
153
+ folderId?: string | undefined;
154
+ mimeType?: string | undefined;
155
+ query?: string | undefined;
156
+ offset?: unknown;
157
+ limit?: unknown;
158
+ }, {
159
+ folderId?: string | undefined;
160
+ mimeType?: string | undefined;
161
+ query?: string | undefined;
162
+ offset?: unknown;
163
+ limit?: unknown;
164
+ }>;
165
+ }, AssetListResult>;
166
+ readonly createAsset: better_call.StrictEndpoint<"/media/assets", {} & {
167
+ method: "POST";
168
+ body: better_call.StandardSchemaV1<{
169
+ filename: string;
170
+ originalName: string;
171
+ mimeType: string;
172
+ size: number;
173
+ url: string;
174
+ folderId?: string | undefined;
175
+ alt?: string | undefined;
176
+ }, {
177
+ filename: string;
178
+ originalName: string;
179
+ mimeType: string;
180
+ size: number;
181
+ url: string;
182
+ folderId?: string | undefined;
183
+ alt?: string | undefined;
184
+ }>;
185
+ }, Asset>;
186
+ readonly updateAsset: better_call.StrictEndpoint<"/media/assets/:id", {} & {
187
+ method: "PATCH";
188
+ body: better_call.StandardSchemaV1<{
189
+ alt?: string | undefined;
190
+ folderId?: string | null | undefined;
191
+ }, {
192
+ alt?: string | undefined;
193
+ folderId?: string | null | undefined;
194
+ }>;
195
+ }, Asset>;
196
+ readonly deleteAsset: better_call.StrictEndpoint<"/media/assets/:id", {} & {
197
+ method: "DELETE";
198
+ body: better_call.StandardSchemaV1<unknown, unknown>;
199
+ }, {
200
+ success: boolean;
201
+ }>;
202
+ readonly listFolders: better_call.StrictEndpoint<"/media/folders", {} & {
203
+ method: "GET";
204
+ } & {
205
+ query: better_call.StandardSchemaV1<{
206
+ parentId?: string | undefined;
207
+ }, {
208
+ parentId?: string | undefined;
209
+ }>;
210
+ }, Folder[]>;
211
+ readonly createFolder: better_call.StrictEndpoint<"/media/folders", {} & {
212
+ method: "POST";
213
+ body: better_call.StandardSchemaV1<{
214
+ name: string;
215
+ parentId?: string | undefined;
216
+ }, {
217
+ name: string;
218
+ parentId?: string | undefined;
219
+ }>;
220
+ }, Folder>;
221
+ readonly deleteFolder: better_call.StrictEndpoint<"/media/folders/:id", {} & {
222
+ method: "DELETE";
223
+ body: better_call.StandardSchemaV1<unknown, unknown>;
224
+ }, {
225
+ success: boolean;
226
+ }>;
227
+ readonly uploadDirect: better_call.StrictEndpoint<"/media/upload", {
228
+ metadata: {
229
+ allowedMediaTypes: string[];
230
+ };
231
+ } & {
232
+ method: "POST";
233
+ body: better_call.StandardSchemaV1<unknown, unknown>;
234
+ }, Asset>;
235
+ readonly uploadToken: better_call.StrictEndpoint<"/media/upload/token", {} & {
236
+ method: "POST";
237
+ body: better_call.StandardSchemaV1<{
238
+ filename: string;
239
+ mimeType: string;
240
+ size: number;
241
+ folderId?: string | undefined;
242
+ }, {
243
+ filename: string;
244
+ mimeType: string;
245
+ size: number;
246
+ folderId?: string | undefined;
247
+ }>;
248
+ }, S3UploadToken>;
249
+ readonly uploadVercelBlob: better_call.StrictEndpoint<"/media/upload/vercel-blob", {} & {
250
+ method: "POST";
251
+ body: better_call.StandardSchemaV1<unknown, unknown>;
252
+ }, unknown>;
253
+ }, {
254
+ listAssets: (params?: Parameters<typeof listAssets>[1]) => Promise<AssetListResult>;
255
+ getAssetById: (id: string) => Promise<Asset | null>;
256
+ listFolders: (params?: Parameters<typeof listFolders>[1]) => Promise<Folder[]>;
257
+ getFolderById: (id: string) => Promise<Folder | null>;
258
+ }>;
259
+ type MediaApiRouter = ReturnType<ReturnType<typeof mediaBackendPlugin>["routes"]>;
260
+
261
+ /**
262
+ * Internal query key constants for the media plugin.
263
+ * Shared between query-keys.ts (HTTP path) and any SSR/SSG prefetching
264
+ * to prevent key drift between client and server.
265
+ */
266
+
267
+ /**
268
+ * Discriminator for the asset list cache key.
269
+ */
270
+ interface AssetListDiscriminator {
271
+ folderId: string | undefined;
272
+ mimeType: string | undefined;
273
+ query: string | undefined;
274
+ limit: number | undefined;
275
+ offset: number | undefined;
276
+ }
277
+ declare function assetListDiscriminator(params?: AssetListParams): AssetListDiscriminator;
278
+ /** Full query key builders — use these with `queryClient.setQueryData()`. */
279
+ declare const MEDIA_QUERY_KEYS: {
280
+ assetsList: (params?: AssetListParams) => readonly ["media", "assets", "list", AssetListDiscriminator];
281
+ assetDetail: (id: string) => readonly ["media", "assets", "detail", string];
282
+ foldersList: (parentId?: string | null) => readonly ["media", "folders", "list", string];
283
+ };
284
+
285
+ export { MEDIA_QUERY_KEYS as a, assetListDiscriminator as b, mediaBackendPlugin as m };
286
+ export type { AssetListDiscriminator as A, MediaApiRouter as M, MediaApiContext as c, MediaBackendHooks as d, MediaBackendConfig as e };
@@ -141,6 +141,31 @@ interface KanbanPluginOverrides {
141
141
  * Optional headers to pass with API requests (e.g., for SSR auth)
142
142
  */
143
143
  headers?: HeadersInit;
144
+ /**
145
+ * Function used to upload a new image file from the task description editor
146
+ * and return its URL. This is separate from `imagePicker`, which selects an
147
+ * existing asset URL.
148
+ */
149
+ uploadImage?: (file: File) => Promise<string>;
150
+ /**
151
+ * Optional trigger component for a media picker.
152
+ * When provided, it appears inside the image insertion dialog of the task description editor,
153
+ * letting users browse and select previously uploaded assets.
154
+ *
155
+ * @example
156
+ * ```tsx
157
+ * imagePicker: ({ onSelect }) => (
158
+ * <MediaPicker
159
+ * trigger={<Button size="sm" variant="outline">Browse media</Button>}
160
+ * accept={["image/*"]}
161
+ * onSelect={(assets) => onSelect(assets[0].url)}
162
+ * />
163
+ * )
164
+ * ```
165
+ */
166
+ imagePicker?: ComponentType<{
167
+ onSelect: (url: string) => void;
168
+ }>;
144
169
  /**
145
170
  * Resolve user info from an assigneeId
146
171
  * Called when rendering task cards/forms that have an assignee
@@ -0,0 +1,286 @@
1
+ import * as _btst_stack_plugins_api from '@btst/stack/plugins/api';
2
+ import { a as StorageAdapter, b as S3UploadToken } from './stack.BttDsJJn.mjs';
3
+ import { c as AssetListResult, l as listAssets, a as listFolders, A as AssetListParams } from './stack.C7vfOBmO.mjs';
4
+ import * as better_call from 'better-call';
5
+ import { z } from 'zod';
6
+ import { A as Asset, F as Folder } from './stack.CLcnSF_b.mjs';
7
+
8
+ declare const AssetListQuerySchema: z.ZodObject<{
9
+ folderId: z.ZodOptional<z.ZodString>;
10
+ mimeType: z.ZodOptional<z.ZodString>;
11
+ query: z.ZodOptional<z.ZodString>;
12
+ offset: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
13
+ limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
14
+ }, z.core.$strip>;
15
+ declare const updateAssetSchema: z.ZodObject<{
16
+ alt: z.ZodOptional<z.ZodString>;
17
+ folderId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
18
+ }, z.core.$strip>;
19
+ declare const createFolderSchema: z.ZodObject<{
20
+ name: z.ZodString;
21
+ parentId: z.ZodOptional<z.ZodString>;
22
+ }, z.core.$strip>;
23
+
24
+ /**
25
+ * Context passed to media API hooks.
26
+ */
27
+ interface MediaApiContext<TBody = unknown, TParams = unknown, TQuery = unknown> {
28
+ body?: TBody;
29
+ params?: TParams;
30
+ query?: TQuery;
31
+ request?: Request;
32
+ headers?: Headers;
33
+ [key: string]: unknown;
34
+ }
35
+ /**
36
+ * Configuration hooks for the media backend plugin.
37
+ * All hooks are optional and allow consumers to customise behaviour.
38
+ */
39
+ interface MediaBackendHooks {
40
+ /**
41
+ * Called before a file upload is allowed (both direct and signed adapters).
42
+ * Throw an Error to reject the upload (e.g. if the user is not authenticated).
43
+ */
44
+ onBeforeUpload?: (meta: {
45
+ filename: string;
46
+ mimeType: string;
47
+ size?: number;
48
+ }, context: MediaApiContext) => Promise<void> | void;
49
+ /**
50
+ * Called after an asset record is created in the database.
51
+ */
52
+ onAfterUpload?: (asset: Asset, context: MediaApiContext) => Promise<void> | void;
53
+ /**
54
+ * Called before an asset is deleted. Throw to prevent deletion.
55
+ */
56
+ onBeforeDelete?: (asset: Asset, context: MediaApiContext) => Promise<void> | void;
57
+ /**
58
+ * Called after an asset has been deleted from the DB and storage.
59
+ */
60
+ onAfterDelete?: (assetId: string, context: MediaApiContext) => Promise<void> | void;
61
+ /**
62
+ * Called before listing assets. Throw to deny access.
63
+ */
64
+ onBeforeListAssets?: (filter: z.infer<typeof AssetListQuerySchema>, context: MediaApiContext) => Promise<void> | void;
65
+ /**
66
+ * Called before updating an asset (PATCH). Throw to deny access.
67
+ */
68
+ onBeforeUpdateAsset?: (asset: Asset, updates: z.infer<typeof updateAssetSchema>, context: MediaApiContext) => Promise<void> | void;
69
+ /**
70
+ * Called before listing folders. Throw to deny access.
71
+ */
72
+ onBeforeListFolders?: (filter: {
73
+ parentId?: string;
74
+ }, context: MediaApiContext) => Promise<void> | void;
75
+ /**
76
+ * Called before creating a folder. Throw to deny access.
77
+ */
78
+ onBeforeCreateFolder?: (input: z.infer<typeof createFolderSchema>, context: MediaApiContext) => Promise<void> | void;
79
+ /**
80
+ * Called before deleting a folder. Throw to deny access.
81
+ */
82
+ onBeforeDeleteFolder?: (folder: Folder, context: MediaApiContext) => Promise<void> | void;
83
+ }
84
+ /**
85
+ * Configuration for the media backend plugin.
86
+ */
87
+ interface MediaBackendConfig {
88
+ /**
89
+ * The storage adapter to use for file uploads.
90
+ * - `localAdapter()` — writes to the local filesystem (dev / self-hosted)
91
+ * - `s3Adapter()` — presigned PUT URL (AWS S3, Cloudflare R2, MinIO)
92
+ * - `vercelBlobAdapter()` — signed direct upload via Vercel Blob
93
+ */
94
+ storageAdapter: StorageAdapter;
95
+ /**
96
+ * Maximum file size in bytes.
97
+ * Enforced server-side for `localAdapter`.
98
+ * Passed into the Vercel Blob token for edge enforcement.
99
+ * Validated against the client-reported size for `s3Adapter`.
100
+ * @default 10485760 (10 MB)
101
+ */
102
+ maxFileSizeBytes?: number;
103
+ /**
104
+ * MIME type allowlist (e.g. `["image/jpeg", "image/png"]`).
105
+ * If omitted, all MIME types are accepted.
106
+ * Enforced server-side for `localAdapter`.
107
+ * Passed to Vercel Blob token for edge enforcement.
108
+ * Validated against the client-reported MIME type for `s3Adapter`.
109
+ */
110
+ allowedMimeTypes?: string[];
111
+ /**
112
+ * URL prefixes that are allowed when creating asset records via `POST /media/assets`.
113
+ * When omitted the plugin automatically derives a safe default from the storage adapter:
114
+ * - `s3Adapter` → the configured `publicBaseUrl`
115
+ * - `vercelBlobAdapter` → any URL whose hostname ends with `.public.blob.vercel-storage.com`
116
+ * - `localAdapter` → rejects client-supplied URLs; use `POST /media/upload` instead
117
+ *
118
+ * Provide this option only when you need to override the automatic default (e.g. to allow
119
+ * assets from a CDN in front of your storage that uses a different domain). When using
120
+ * `localAdapter`, setting `allowedUrlPrefixes` explicitly opts `POST /media/assets` back in.
121
+ */
122
+ allowedUrlPrefixes?: string[];
123
+ /**
124
+ * Optional lifecycle hooks for the media backend plugin.
125
+ */
126
+ hooks?: MediaBackendHooks;
127
+ }
128
+ /**
129
+ * Media backend plugin.
130
+ * Provides API endpoints for managing media assets and folders, and supports
131
+ * local, S3-compatible, and Vercel Blob storage backends.
132
+ *
133
+ * @example
134
+ * ```ts
135
+ * import { mediaBackendPlugin, localAdapter } from "@btst/stack/plugins/media/api";
136
+ *
137
+ * mediaBackendPlugin({
138
+ * storageAdapter: localAdapter(),
139
+ * hooks: {
140
+ * onBeforeUpload: async (_meta, ctx) => {
141
+ * const session = await getSession(ctx.headers as Headers);
142
+ * if (!session) throw new Error("Unauthorized");
143
+ * },
144
+ * },
145
+ * })
146
+ * ```
147
+ */
148
+ declare const mediaBackendPlugin: (config: MediaBackendConfig) => _btst_stack_plugins_api.BackendPlugin<{
149
+ readonly listAssets: better_call.StrictEndpoint<"/media/assets", {} & {
150
+ method: "GET";
151
+ } & {
152
+ query: better_call.StandardSchemaV1<{
153
+ folderId?: string | undefined;
154
+ mimeType?: string | undefined;
155
+ query?: string | undefined;
156
+ offset?: unknown;
157
+ limit?: unknown;
158
+ }, {
159
+ folderId?: string | undefined;
160
+ mimeType?: string | undefined;
161
+ query?: string | undefined;
162
+ offset?: unknown;
163
+ limit?: unknown;
164
+ }>;
165
+ }, AssetListResult>;
166
+ readonly createAsset: better_call.StrictEndpoint<"/media/assets", {} & {
167
+ method: "POST";
168
+ body: better_call.StandardSchemaV1<{
169
+ filename: string;
170
+ originalName: string;
171
+ mimeType: string;
172
+ size: number;
173
+ url: string;
174
+ folderId?: string | undefined;
175
+ alt?: string | undefined;
176
+ }, {
177
+ filename: string;
178
+ originalName: string;
179
+ mimeType: string;
180
+ size: number;
181
+ url: string;
182
+ folderId?: string | undefined;
183
+ alt?: string | undefined;
184
+ }>;
185
+ }, Asset>;
186
+ readonly updateAsset: better_call.StrictEndpoint<"/media/assets/:id", {} & {
187
+ method: "PATCH";
188
+ body: better_call.StandardSchemaV1<{
189
+ alt?: string | undefined;
190
+ folderId?: string | null | undefined;
191
+ }, {
192
+ alt?: string | undefined;
193
+ folderId?: string | null | undefined;
194
+ }>;
195
+ }, Asset>;
196
+ readonly deleteAsset: better_call.StrictEndpoint<"/media/assets/:id", {} & {
197
+ method: "DELETE";
198
+ body: better_call.StandardSchemaV1<unknown, unknown>;
199
+ }, {
200
+ success: boolean;
201
+ }>;
202
+ readonly listFolders: better_call.StrictEndpoint<"/media/folders", {} & {
203
+ method: "GET";
204
+ } & {
205
+ query: better_call.StandardSchemaV1<{
206
+ parentId?: string | undefined;
207
+ }, {
208
+ parentId?: string | undefined;
209
+ }>;
210
+ }, Folder[]>;
211
+ readonly createFolder: better_call.StrictEndpoint<"/media/folders", {} & {
212
+ method: "POST";
213
+ body: better_call.StandardSchemaV1<{
214
+ name: string;
215
+ parentId?: string | undefined;
216
+ }, {
217
+ name: string;
218
+ parentId?: string | undefined;
219
+ }>;
220
+ }, Folder>;
221
+ readonly deleteFolder: better_call.StrictEndpoint<"/media/folders/:id", {} & {
222
+ method: "DELETE";
223
+ body: better_call.StandardSchemaV1<unknown, unknown>;
224
+ }, {
225
+ success: boolean;
226
+ }>;
227
+ readonly uploadDirect: better_call.StrictEndpoint<"/media/upload", {
228
+ metadata: {
229
+ allowedMediaTypes: string[];
230
+ };
231
+ } & {
232
+ method: "POST";
233
+ body: better_call.StandardSchemaV1<unknown, unknown>;
234
+ }, Asset>;
235
+ readonly uploadToken: better_call.StrictEndpoint<"/media/upload/token", {} & {
236
+ method: "POST";
237
+ body: better_call.StandardSchemaV1<{
238
+ filename: string;
239
+ mimeType: string;
240
+ size: number;
241
+ folderId?: string | undefined;
242
+ }, {
243
+ filename: string;
244
+ mimeType: string;
245
+ size: number;
246
+ folderId?: string | undefined;
247
+ }>;
248
+ }, S3UploadToken>;
249
+ readonly uploadVercelBlob: better_call.StrictEndpoint<"/media/upload/vercel-blob", {} & {
250
+ method: "POST";
251
+ body: better_call.StandardSchemaV1<unknown, unknown>;
252
+ }, unknown>;
253
+ }, {
254
+ listAssets: (params?: Parameters<typeof listAssets>[1]) => Promise<AssetListResult>;
255
+ getAssetById: (id: string) => Promise<Asset | null>;
256
+ listFolders: (params?: Parameters<typeof listFolders>[1]) => Promise<Folder[]>;
257
+ getFolderById: (id: string) => Promise<Folder | null>;
258
+ }>;
259
+ type MediaApiRouter = ReturnType<ReturnType<typeof mediaBackendPlugin>["routes"]>;
260
+
261
+ /**
262
+ * Internal query key constants for the media plugin.
263
+ * Shared between query-keys.ts (HTTP path) and any SSR/SSG prefetching
264
+ * to prevent key drift between client and server.
265
+ */
266
+
267
+ /**
268
+ * Discriminator for the asset list cache key.
269
+ */
270
+ interface AssetListDiscriminator {
271
+ folderId: string | undefined;
272
+ mimeType: string | undefined;
273
+ query: string | undefined;
274
+ limit: number | undefined;
275
+ offset: number | undefined;
276
+ }
277
+ declare function assetListDiscriminator(params?: AssetListParams): AssetListDiscriminator;
278
+ /** Full query key builders — use these with `queryClient.setQueryData()`. */
279
+ declare const MEDIA_QUERY_KEYS: {
280
+ assetsList: (params?: AssetListParams) => readonly ["media", "assets", "list", AssetListDiscriminator];
281
+ assetDetail: (id: string) => readonly ["media", "assets", "detail", string];
282
+ foldersList: (parentId?: string | null) => readonly ["media", "folders", "list", string];
283
+ };
284
+
285
+ export { MEDIA_QUERY_KEYS as a, assetListDiscriminator as b, mediaBackendPlugin as m };
286
+ export type { AssetListDiscriminator as A, MediaApiRouter as M, MediaApiContext as c, MediaBackendHooks as d, MediaBackendConfig as e };