@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,60 @@
1
+ import { S as S3StorageAdapter } from '../../../../shared/stack.BttDsJJn.mjs';
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 };
@@ -0,0 +1,60 @@
1
+ import { S as S3StorageAdapter } from '../../../../shared/stack.BttDsJJn.js';
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 };
@@ -0,0 +1,104 @@
1
+ function s3Adapter(options) {
2
+ const {
3
+ bucket,
4
+ region,
5
+ accessKeyId,
6
+ secretAccessKey,
7
+ endpoint,
8
+ publicBaseUrl,
9
+ expiresIn = 300
10
+ } = options;
11
+ let s3ModulePromise = null;
12
+ let clientPromise = null;
13
+ function getS3Module() {
14
+ if (!s3ModulePromise) {
15
+ s3ModulePromise = import('@aws-sdk/client-s3').catch(() => {
16
+ s3ModulePromise = null;
17
+ throw new Error(
18
+ "[@btst/stack] S3 adapter requires '@aws-sdk/client-s3'. Run: npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner"
19
+ );
20
+ });
21
+ }
22
+ return s3ModulePromise;
23
+ }
24
+ function getClient() {
25
+ if (!clientPromise) {
26
+ clientPromise = getS3Module().then(({ S3Client }) => {
27
+ return new S3Client({
28
+ region,
29
+ endpoint,
30
+ credentials: { accessKeyId, secretAccessKey },
31
+ forcePathStyle: !!endpoint
32
+ });
33
+ });
34
+ }
35
+ return clientPromise.catch((error) => {
36
+ if (clientPromise) {
37
+ clientPromise = null;
38
+ }
39
+ if (s3ModulePromise && error instanceof Error) {
40
+ throw error;
41
+ }
42
+ throw new Error(
43
+ "[@btst/stack] S3 adapter requires '@aws-sdk/client-s3'. Run: npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner"
44
+ );
45
+ });
46
+ }
47
+ async function buildSignedUrl(client, command, opts) {
48
+ let getSignedUrl;
49
+ try {
50
+ ({ getSignedUrl } = await import('@aws-sdk/s3-request-presigner'));
51
+ } catch {
52
+ throw new Error(
53
+ "[@btst/stack] S3 adapter requires '@aws-sdk/s3-request-presigner'. Run: npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner"
54
+ );
55
+ }
56
+ return getSignedUrl(
57
+ client,
58
+ command,
59
+ opts
60
+ );
61
+ }
62
+ return {
63
+ type: "s3",
64
+ urlPrefix: publicBaseUrl.replace(/\/$/, ""),
65
+ async generateUploadToken(uploadOptions) {
66
+ const [client, { PutObjectCommand }] = await Promise.all([
67
+ getClient(),
68
+ getS3Module()
69
+ ]);
70
+ const key = uploadOptions.folderId ? `${uploadOptions.folderId}/${uploadOptions.filename}` : uploadOptions.filename;
71
+ const command = new PutObjectCommand({
72
+ Bucket: bucket,
73
+ Key: key,
74
+ ContentType: uploadOptions.mimeType,
75
+ ContentLength: uploadOptions.size
76
+ });
77
+ const uploadUrl = await buildSignedUrl(client, command, { expiresIn });
78
+ const encodedKey = key.split("/").map(encodeURIComponent).join("/");
79
+ const publicUrl = `${publicBaseUrl.replace(/\/$/, "")}/${encodedKey}`;
80
+ return {
81
+ type: "presigned-url",
82
+ payload: {
83
+ uploadUrl,
84
+ publicUrl,
85
+ key,
86
+ method: "PUT",
87
+ headers: { "Content-Type": uploadOptions.mimeType }
88
+ }
89
+ };
90
+ },
91
+ async delete(url) {
92
+ const [client, { DeleteObjectCommand }] = await Promise.all([
93
+ getClient(),
94
+ getS3Module()
95
+ ]);
96
+ const base = publicBaseUrl.replace(/\/$/, "");
97
+ const encodedKey = url.startsWith(base) ? url.slice(base.length + 1) : url.split("/").pop() ?? url;
98
+ const key = decodeURIComponent(encodedKey);
99
+ await client.send(new DeleteObjectCommand({ Bucket: bucket, Key: key }));
100
+ }
101
+ };
102
+ }
103
+
104
+ export { s3Adapter };
@@ -0,0 +1,53 @@
1
+ 'use strict';
2
+
3
+ function vercelBlobAdapter(options = {}) {
4
+ return {
5
+ type: "vercel-blob",
6
+ urlHostnameSuffix: ".public.blob.vercel-storage.com",
7
+ async handleRequest(request, callbacks) {
8
+ let handleUpload;
9
+ try {
10
+ const vercelBlobClient = (
11
+ /* @vite-ignore */
12
+ await import('@vercel/blob/client')
13
+ );
14
+ ({ handleUpload } = vercelBlobClient);
15
+ } catch {
16
+ throw new Error(
17
+ "[@btst/stack] Vercel Blob adapter requires '@vercel/blob' with 'handleUpload' exported from '@vercel/blob/client'. Run: npm install @vercel/blob"
18
+ );
19
+ }
20
+ const body = await request.json();
21
+ return handleUpload({
22
+ body,
23
+ request,
24
+ token: options.token,
25
+ onBeforeGenerateToken: async (pathname, clientPayload) => {
26
+ const tokenOptions = await callbacks.onBeforeGenerateToken?.(
27
+ pathname,
28
+ clientPayload ?? null
29
+ ) ?? {};
30
+ return {
31
+ addRandomSuffix: true,
32
+ ...tokenOptions
33
+ };
34
+ },
35
+ onUploadCompleted: async () => {
36
+ }
37
+ });
38
+ },
39
+ async delete(url) {
40
+ let del;
41
+ try {
42
+ ({ del } = await import('@vercel/blob'));
43
+ } catch {
44
+ throw new Error(
45
+ "[@btst/stack] Vercel Blob adapter requires '@vercel/blob'. Run: npm install @vercel/blob"
46
+ );
47
+ }
48
+ await del(url, options.token ? { token: options.token } : void 0);
49
+ }
50
+ };
51
+ }
52
+
53
+ exports.vercelBlobAdapter = vercelBlobAdapter;
@@ -0,0 +1,41 @@
1
+ import { V as VercelBlobStorageAdapter } from '../../../../shared/stack.BttDsJJn.cjs';
2
+
3
+ interface VercelBlobStorageAdapterOptions {
4
+ /**
5
+ * The `BLOB_READ_WRITE_TOKEN` environment variable is read automatically
6
+ * by `@vercel/blob`. You only need to provide this option if you store
7
+ * the token under a different name.
8
+ */
9
+ token?: string;
10
+ }
11
+ /**
12
+ * Create a Vercel Blob storage adapter using the signed direct-upload protocol.
13
+ * The server never receives file bytes — it only issues short-lived client tokens
14
+ * via `@vercel/blob`'s `handleUpload` helper (available via `@vercel/blob/client`
15
+ * in compatible versions).
16
+ *
17
+ * @remarks Requires `@vercel/blob` as an optional peer dependency (version
18
+ * with `handleUpload` exported from `@vercel/blob/client`).
19
+ *
20
+ * Upload flow:
21
+ * 1. Client calls `POST /media/upload/vercel-blob` to obtain a client token.
22
+ * 2. Client uses `@vercel/blob/client`'s `upload()` to upload directly to Vercel.
23
+ * 3. After upload, client calls `POST /media/assets` to save metadata to the DB.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * mediaBackendPlugin({
28
+ * storageAdapter: vercelBlobAdapter(),
29
+ * hooks: {
30
+ * onBeforeUpload: async (_meta, ctx) => {
31
+ * const session = await getSession(ctx.headers);
32
+ * if (!session) throw new Error("Unauthorized");
33
+ * },
34
+ * },
35
+ * })
36
+ * ```
37
+ */
38
+ declare function vercelBlobAdapter(options?: VercelBlobStorageAdapterOptions): VercelBlobStorageAdapter;
39
+
40
+ export { vercelBlobAdapter };
41
+ export type { VercelBlobStorageAdapterOptions };
@@ -0,0 +1,41 @@
1
+ import { V as VercelBlobStorageAdapter } from '../../../../shared/stack.BttDsJJn.mjs';
2
+
3
+ interface VercelBlobStorageAdapterOptions {
4
+ /**
5
+ * The `BLOB_READ_WRITE_TOKEN` environment variable is read automatically
6
+ * by `@vercel/blob`. You only need to provide this option if you store
7
+ * the token under a different name.
8
+ */
9
+ token?: string;
10
+ }
11
+ /**
12
+ * Create a Vercel Blob storage adapter using the signed direct-upload protocol.
13
+ * The server never receives file bytes — it only issues short-lived client tokens
14
+ * via `@vercel/blob`'s `handleUpload` helper (available via `@vercel/blob/client`
15
+ * in compatible versions).
16
+ *
17
+ * @remarks Requires `@vercel/blob` as an optional peer dependency (version
18
+ * with `handleUpload` exported from `@vercel/blob/client`).
19
+ *
20
+ * Upload flow:
21
+ * 1. Client calls `POST /media/upload/vercel-blob` to obtain a client token.
22
+ * 2. Client uses `@vercel/blob/client`'s `upload()` to upload directly to Vercel.
23
+ * 3. After upload, client calls `POST /media/assets` to save metadata to the DB.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * mediaBackendPlugin({
28
+ * storageAdapter: vercelBlobAdapter(),
29
+ * hooks: {
30
+ * onBeforeUpload: async (_meta, ctx) => {
31
+ * const session = await getSession(ctx.headers);
32
+ * if (!session) throw new Error("Unauthorized");
33
+ * },
34
+ * },
35
+ * })
36
+ * ```
37
+ */
38
+ declare function vercelBlobAdapter(options?: VercelBlobStorageAdapterOptions): VercelBlobStorageAdapter;
39
+
40
+ export { vercelBlobAdapter };
41
+ export type { VercelBlobStorageAdapterOptions };
@@ -0,0 +1,41 @@
1
+ import { V as VercelBlobStorageAdapter } from '../../../../shared/stack.BttDsJJn.js';
2
+
3
+ interface VercelBlobStorageAdapterOptions {
4
+ /**
5
+ * The `BLOB_READ_WRITE_TOKEN` environment variable is read automatically
6
+ * by `@vercel/blob`. You only need to provide this option if you store
7
+ * the token under a different name.
8
+ */
9
+ token?: string;
10
+ }
11
+ /**
12
+ * Create a Vercel Blob storage adapter using the signed direct-upload protocol.
13
+ * The server never receives file bytes — it only issues short-lived client tokens
14
+ * via `@vercel/blob`'s `handleUpload` helper (available via `@vercel/blob/client`
15
+ * in compatible versions).
16
+ *
17
+ * @remarks Requires `@vercel/blob` as an optional peer dependency (version
18
+ * with `handleUpload` exported from `@vercel/blob/client`).
19
+ *
20
+ * Upload flow:
21
+ * 1. Client calls `POST /media/upload/vercel-blob` to obtain a client token.
22
+ * 2. Client uses `@vercel/blob/client`'s `upload()` to upload directly to Vercel.
23
+ * 3. After upload, client calls `POST /media/assets` to save metadata to the DB.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * mediaBackendPlugin({
28
+ * storageAdapter: vercelBlobAdapter(),
29
+ * hooks: {
30
+ * onBeforeUpload: async (_meta, ctx) => {
31
+ * const session = await getSession(ctx.headers);
32
+ * if (!session) throw new Error("Unauthorized");
33
+ * },
34
+ * },
35
+ * })
36
+ * ```
37
+ */
38
+ declare function vercelBlobAdapter(options?: VercelBlobStorageAdapterOptions): VercelBlobStorageAdapter;
39
+
40
+ export { vercelBlobAdapter };
41
+ export type { VercelBlobStorageAdapterOptions };
@@ -0,0 +1,51 @@
1
+ function vercelBlobAdapter(options = {}) {
2
+ return {
3
+ type: "vercel-blob",
4
+ urlHostnameSuffix: ".public.blob.vercel-storage.com",
5
+ async handleRequest(request, callbacks) {
6
+ let handleUpload;
7
+ try {
8
+ const vercelBlobClient = (
9
+ /* @vite-ignore */
10
+ await import('@vercel/blob/client')
11
+ );
12
+ ({ handleUpload } = vercelBlobClient);
13
+ } catch {
14
+ throw new Error(
15
+ "[@btst/stack] Vercel Blob adapter requires '@vercel/blob' with 'handleUpload' exported from '@vercel/blob/client'. Run: npm install @vercel/blob"
16
+ );
17
+ }
18
+ const body = await request.json();
19
+ return handleUpload({
20
+ body,
21
+ request,
22
+ token: options.token,
23
+ onBeforeGenerateToken: async (pathname, clientPayload) => {
24
+ const tokenOptions = await callbacks.onBeforeGenerateToken?.(
25
+ pathname,
26
+ clientPayload ?? null
27
+ ) ?? {};
28
+ return {
29
+ addRandomSuffix: true,
30
+ ...tokenOptions
31
+ };
32
+ },
33
+ onUploadCompleted: async () => {
34
+ }
35
+ });
36
+ },
37
+ async delete(url) {
38
+ let del;
39
+ try {
40
+ ({ del } = await import('@vercel/blob'));
41
+ } catch {
42
+ throw new Error(
43
+ "[@btst/stack] Vercel Blob adapter requires '@vercel/blob'. Run: npm install @vercel/blob"
44
+ );
45
+ }
46
+ await del(url, options.token ? { token: options.token } : void 0);
47
+ }
48
+ };
49
+ }
50
+
51
+ export { vercelBlobAdapter };
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ const plugin = require('../../../packages/stack/src/plugins/media/api/plugin.cjs');
4
+ const getters = require('../../../packages/stack/src/plugins/media/api/getters.cjs');
5
+ const mutations = require('../../../packages/stack/src/plugins/media/api/mutations.cjs');
6
+ const serializers = require('../../../packages/stack/src/plugins/media/api/serializers.cjs');
7
+ const queryKeyDefs = require('../../../packages/stack/src/plugins/media/api/query-key-defs.cjs');
8
+ const local = require('../../../packages/stack/src/plugins/media/api/adapters/local.cjs');
9
+
10
+
11
+
12
+ exports.mediaBackendPlugin = plugin.mediaBackendPlugin;
13
+ exports.getAssetById = getters.getAssetById;
14
+ exports.getFolderById = getters.getFolderById;
15
+ exports.listAssets = getters.listAssets;
16
+ exports.listFolders = getters.listFolders;
17
+ exports.createAsset = mutations.createAsset;
18
+ exports.createFolder = mutations.createFolder;
19
+ exports.deleteAsset = mutations.deleteAsset;
20
+ exports.deleteFolder = mutations.deleteFolder;
21
+ exports.updateAsset = mutations.updateAsset;
22
+ exports.serializeAsset = serializers.serializeAsset;
23
+ exports.serializeFolder = serializers.serializeFolder;
24
+ exports.MEDIA_QUERY_KEYS = queryKeyDefs.MEDIA_QUERY_KEYS;
25
+ exports.assetListDiscriminator = queryKeyDefs.assetListDiscriminator;
26
+ exports.localAdapter = local.localAdapter;
@@ -0,0 +1,116 @@
1
+ export { a as MEDIA_QUERY_KEYS, c as MediaApiContext, M as MediaApiRouter, e as MediaBackendConfig, d as MediaBackendHooks, b as assetListDiscriminator, m as mediaBackendPlugin } from '../../../shared/stack.CI8iRKKi.cjs';
2
+ export { A as AssetListParams, c as AssetListResult, F as FolderListParams, g as getAssetById, b as getFolderById, l as listAssets, a as listFolders } from '../../../shared/stack.CAni8dnD.cjs';
3
+ import { DBAdapter } from '@btst/db';
4
+ import { A as Asset, F as Folder, S as SerializedAsset, a as SerializedFolder } from '../../../shared/stack.CLcnSF_b.cjs';
5
+ import { D as DirectStorageAdapter } from '../../../shared/stack.BttDsJJn.cjs';
6
+ export { S as S3StorageAdapter, b as S3UploadToken, a as StorageAdapter, U as UploadOptions, c as VercelBlobHandlerCallbacks, V as VercelBlobStorageAdapter } from '../../../shared/stack.BttDsJJn.cjs';
7
+ import '@btst/stack/plugins/api';
8
+ import 'better-call';
9
+ import 'zod';
10
+
11
+ /**
12
+ * Input for creating a new asset record.
13
+ */
14
+ interface CreateAssetInput {
15
+ filename: string;
16
+ originalName: string;
17
+ mimeType: string;
18
+ size: number;
19
+ url: string;
20
+ folderId?: string;
21
+ alt?: string;
22
+ }
23
+ /**
24
+ * Input for updating an existing asset record.
25
+ */
26
+ interface UpdateAssetInput {
27
+ alt?: string;
28
+ folderId?: string | null;
29
+ }
30
+ /**
31
+ * Input for creating a new folder.
32
+ */
33
+ interface CreateFolderInput {
34
+ name: string;
35
+ parentId?: string;
36
+ }
37
+ /**
38
+ * Create an asset record in the database.
39
+ * Pure DB function — no authorization hooks, no HTTP context.
40
+ *
41
+ * @remarks **Security:** No authorization hooks (e.g. `onBeforeUpload`) are called.
42
+ * The caller is responsible for any access-control checks before invoking this function.
43
+ */
44
+ declare function createAsset(adapter: DBAdapter, input: CreateAssetInput): Promise<Asset>;
45
+ /**
46
+ * Update an asset's `alt` text or `folderId`.
47
+ * Pure DB function — no authorization hooks, no HTTP context.
48
+ *
49
+ * @remarks **Security:** No authorization hooks are called.
50
+ */
51
+ declare function updateAsset(adapter: DBAdapter, id: string, input: UpdateAssetInput): Promise<Asset | null>;
52
+ /**
53
+ * Delete an asset record from the database by its ID.
54
+ * Does NOT delete the underlying file — the caller must do that via the storage adapter.
55
+ * Pure DB function — no authorization hooks, no HTTP context.
56
+ *
57
+ * @remarks **Security:** No authorization hooks are called.
58
+ */
59
+ declare function deleteAsset(adapter: DBAdapter, id: string): Promise<void>;
60
+ /**
61
+ * Create a folder record in the database.
62
+ * Pure DB function — no authorization hooks, no HTTP context.
63
+ *
64
+ * @remarks **Security:** No authorization hooks are called.
65
+ */
66
+ declare function createFolder(adapter: DBAdapter, input: CreateFolderInput): Promise<Folder>;
67
+ /**
68
+ * Delete a folder record from the database by its ID.
69
+ * Child folders are cascade-deleted automatically. Throws if the folder or
70
+ * any of its descendants contain assets (which have associated storage files
71
+ * that must be deleted via the storage adapter first).
72
+ * Pure DB function — no authorization hooks, no HTTP context.
73
+ *
74
+ * @remarks **Security:** No authorization hooks are called.
75
+ */
76
+ declare function deleteFolder(adapter: DBAdapter, id: string): Promise<void>;
77
+
78
+ /**
79
+ * Serialize an Asset for SSR/SSG use (convert dates to strings).
80
+ * Pure function — no DB access, no hooks.
81
+ */
82
+ declare function serializeAsset(asset: Asset): SerializedAsset;
83
+ /**
84
+ * Serialize a Folder for SSR/SSG use (convert dates to strings).
85
+ * Pure function — no DB access, no hooks.
86
+ */
87
+ declare function serializeFolder(folder: Folder): SerializedFolder;
88
+
89
+ interface LocalStorageAdapterOptions {
90
+ /**
91
+ * Absolute path to the directory where uploaded files are stored.
92
+ * @default "./public/uploads"
93
+ */
94
+ uploadDir?: string;
95
+ /**
96
+ * URL prefix used to build the public URL for uploaded files.
97
+ * @default "/uploads"
98
+ */
99
+ publicPath?: string;
100
+ }
101
+ /**
102
+ * Create a local filesystem storage adapter.
103
+ * Files are written to `uploadDir` and served at `publicPath`.
104
+ * Suitable for development and self-hosted deployments.
105
+ *
106
+ * @example
107
+ * ```ts
108
+ * mediaBackendPlugin({
109
+ * storageAdapter: localAdapter({ uploadDir: "./public/uploads", publicPath: "/uploads" })
110
+ * })
111
+ * ```
112
+ */
113
+ declare function localAdapter(options?: LocalStorageAdapterOptions): DirectStorageAdapter;
114
+
115
+ export { DirectStorageAdapter, createAsset, createFolder, deleteAsset, deleteFolder, localAdapter, serializeAsset, serializeFolder, updateAsset };
116
+ export type { CreateAssetInput, CreateFolderInput, LocalStorageAdapterOptions, UpdateAssetInput };