@rovela-ai/sdk 0.1.25 → 0.1.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/components/CategoryForm.d.ts.map +1 -1
- package/dist/admin/components/CategoryForm.js +2 -2
- package/dist/admin/components/CategoryForm.js.map +1 -1
- package/dist/admin/components/ProductForm.d.ts.map +1 -1
- package/dist/admin/components/ProductForm.js +3 -35
- package/dist/admin/components/ProductForm.js.map +1 -1
- package/dist/admin/components/ProductTable.d.ts.map +1 -1
- package/dist/admin/components/ProductTable.js +6 -1
- package/dist/admin/components/ProductTable.js.map +1 -1
- package/dist/admin/server/admin-service.d.ts +3 -3
- package/dist/admin/server/admin-service.d.ts.map +1 -1
- package/dist/admin/server/admin-service.js +12 -22
- package/dist/admin/server/admin-service.js.map +1 -1
- package/dist/auth/server/customer-service.d.ts +2 -2
- package/dist/auth/server/customer-service.d.ts.map +1 -1
- package/dist/auth/server/customer-service.js +11 -20
- package/dist/auth/server/customer-service.js.map +1 -1
- package/dist/auth/server/password-reset-service.d.ts +1 -0
- package/dist/auth/server/password-reset-service.d.ts.map +1 -1
- package/dist/auth/server/password-reset-service.js +5 -7
- package/dist/auth/server/password-reset-service.js.map +1 -1
- package/dist/auth/server/verification-service.d.ts +1 -0
- package/dist/auth/server/verification-service.d.ts.map +1 -1
- package/dist/auth/server/verification-service.js +6 -9
- package/dist/auth/server/verification-service.js.map +1 -1
- package/dist/core/db/client.d.ts +2 -44
- package/dist/core/db/client.d.ts.map +1 -1
- package/dist/core/db/client.js +2 -106
- package/dist/core/db/client.js.map +1 -1
- package/dist/core/db/index.d.ts +1 -1
- package/dist/core/db/index.d.ts.map +1 -1
- package/dist/core/db/index.js +2 -2
- package/dist/core/db/index.js.map +1 -1
- package/dist/core/db/queries.d.ts +18 -35
- package/dist/core/db/queries.d.ts.map +1 -1
- package/dist/core/db/queries.js +69 -110
- package/dist/core/db/queries.js.map +1 -1
- package/dist/core/db/schema.d.ts +1 -137
- package/dist/core/db/schema.d.ts.map +1 -1
- package/dist/core/db/schema.js +6 -23
- package/dist/core/db/schema.js.map +1 -1
- package/dist/core/server/index.d.ts +1 -1
- package/dist/core/server/index.d.ts.map +1 -1
- package/dist/core/server/index.js +1 -3
- package/dist/core/server/index.js.map +1 -1
- package/dist/core/types.d.ts +0 -5
- package/dist/core/types.d.ts.map +1 -1
- package/dist/emails/config.d.ts.map +1 -1
- package/dist/emails/config.js +11 -17
- package/dist/emails/config.js.map +1 -1
- package/dist/media/api/delete.d.ts +44 -0
- package/dist/media/api/delete.d.ts.map +1 -0
- package/dist/media/api/delete.js +134 -0
- package/dist/media/api/delete.js.map +1 -0
- package/dist/media/api/index.d.ts +17 -0
- package/dist/media/api/index.d.ts.map +1 -0
- package/dist/media/api/index.js +17 -0
- package/dist/media/api/index.js.map +1 -0
- package/dist/media/api/presign.d.ts +39 -0
- package/dist/media/api/presign.d.ts.map +1 -0
- package/dist/media/api/presign.js +138 -0
- package/dist/media/api/presign.js.map +1 -0
- package/dist/media/components/DropZone.d.ts +18 -0
- package/dist/media/components/DropZone.d.ts.map +1 -0
- package/dist/media/components/DropZone.js +112 -0
- package/dist/media/components/DropZone.js.map +1 -0
- package/dist/media/components/ImageGalleryUpload.d.ts +18 -0
- package/dist/media/components/ImageGalleryUpload.d.ts.map +1 -0
- package/dist/media/components/ImageGalleryUpload.js +156 -0
- package/dist/media/components/ImageGalleryUpload.js.map +1 -0
- package/dist/media/components/ImageUpload.d.ts +17 -0
- package/dist/media/components/ImageUpload.d.ts.map +1 -0
- package/dist/media/components/ImageUpload.js +95 -0
- package/dist/media/components/ImageUpload.js.map +1 -0
- package/dist/media/components/index.d.ts +10 -0
- package/dist/media/components/index.d.ts.map +1 -0
- package/dist/media/components/index.js +9 -0
- package/dist/media/components/index.js.map +1 -0
- package/dist/media/config.d.ts +57 -0
- package/dist/media/config.d.ts.map +1 -0
- package/dist/media/config.js +142 -0
- package/dist/media/config.js.map +1 -0
- package/dist/media/hooks/index.d.ts +8 -0
- package/dist/media/hooks/index.d.ts.map +1 -0
- package/dist/media/hooks/index.js +7 -0
- package/dist/media/hooks/index.js.map +1 -0
- package/dist/media/hooks/useUpload.d.ts +32 -0
- package/dist/media/hooks/useUpload.d.ts.map +1 -0
- package/dist/media/hooks/useUpload.js +258 -0
- package/dist/media/hooks/useUpload.js.map +1 -0
- package/dist/media/index.d.ts +57 -0
- package/dist/media/index.d.ts.map +1 -0
- package/dist/media/index.js +68 -0
- package/dist/media/index.js.map +1 -0
- package/dist/media/server/delete.d.ts +59 -0
- package/dist/media/server/delete.d.ts.map +1 -0
- package/dist/media/server/delete.js +176 -0
- package/dist/media/server/delete.js.map +1 -0
- package/dist/media/server/index.d.ts +10 -0
- package/dist/media/server/index.d.ts.map +1 -0
- package/dist/media/server/index.js +13 -0
- package/dist/media/server/index.js.map +1 -0
- package/dist/media/server/presign.d.ts +57 -0
- package/dist/media/server/presign.d.ts.map +1 -0
- package/dist/media/server/presign.js +112 -0
- package/dist/media/server/presign.js.map +1 -0
- package/dist/media/server/r2-client.d.ts +30 -0
- package/dist/media/server/r2-client.d.ts.map +1 -0
- package/dist/media/server/r2-client.js +76 -0
- package/dist/media/server/r2-client.js.map +1 -0
- package/dist/media/types.d.ts +271 -0
- package/dist/media/types.d.ts.map +1 -0
- package/dist/media/types.js +52 -0
- package/dist/media/types.js.map +1 -0
- package/package.json +15 -1
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rovela-ai/sdk/media/config
|
|
3
|
+
*
|
|
4
|
+
* Media storage configuration.
|
|
5
|
+
* Loads R2/S3 credentials from environment variables.
|
|
6
|
+
*/
|
|
7
|
+
// =============================================================================
|
|
8
|
+
// Environment Variable Loading
|
|
9
|
+
// =============================================================================
|
|
10
|
+
/**
|
|
11
|
+
* Get media storage configuration from environment variables.
|
|
12
|
+
* Returns null if required variables are missing.
|
|
13
|
+
*/
|
|
14
|
+
export function getMediaConfig() {
|
|
15
|
+
const accountId = process.env.R2_ACCOUNT_ID;
|
|
16
|
+
const accessKeyId = process.env.R2_ACCESS_KEY_ID;
|
|
17
|
+
const secretAccessKey = process.env.R2_SECRET_ACCESS_KEY;
|
|
18
|
+
const bucketName = process.env.R2_BUCKET_NAME;
|
|
19
|
+
const publicUrl = process.env.R2_PUBLIC_URL;
|
|
20
|
+
const storeId = process.env.STORE_ID;
|
|
21
|
+
// All are required
|
|
22
|
+
if (!accountId || !accessKeyId || !secretAccessKey || !bucketName || !publicUrl || !storeId) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
accountId,
|
|
27
|
+
accessKeyId,
|
|
28
|
+
secretAccessKey,
|
|
29
|
+
bucketName,
|
|
30
|
+
publicUrl,
|
|
31
|
+
storeId,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Check if media storage is configured
|
|
36
|
+
*/
|
|
37
|
+
export function isMediaConfigured() {
|
|
38
|
+
return getMediaConfig() !== null;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Get media configuration or throw an error if not configured.
|
|
42
|
+
* Use this in API routes where configuration is required.
|
|
43
|
+
*/
|
|
44
|
+
export function requireMediaConfig() {
|
|
45
|
+
const config = getMediaConfig();
|
|
46
|
+
if (!config) {
|
|
47
|
+
throw new Error('Media storage not configured. Required environment variables: ' +
|
|
48
|
+
'R2_ACCOUNT_ID, R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET_NAME, R2_PUBLIC_URL, STORE_ID');
|
|
49
|
+
}
|
|
50
|
+
return config;
|
|
51
|
+
}
|
|
52
|
+
// =============================================================================
|
|
53
|
+
// Upload Configuration
|
|
54
|
+
// =============================================================================
|
|
55
|
+
/**
|
|
56
|
+
* Default upload configuration
|
|
57
|
+
*/
|
|
58
|
+
export const defaultUploadConfig = {
|
|
59
|
+
maxSizeBytes: 10 * 1024 * 1024, // 10MB
|
|
60
|
+
allowedTypes: ['image/jpeg', 'image/png', 'image/webp', 'image/gif'],
|
|
61
|
+
maxFiles: 10,
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Get upload configuration with optional overrides
|
|
65
|
+
*/
|
|
66
|
+
export function getUploadConfig(overrides) {
|
|
67
|
+
return {
|
|
68
|
+
...defaultUploadConfig,
|
|
69
|
+
...overrides,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// =============================================================================
|
|
73
|
+
// Path Helpers
|
|
74
|
+
// =============================================================================
|
|
75
|
+
/**
|
|
76
|
+
* Build the storage key (path) for a file.
|
|
77
|
+
*
|
|
78
|
+
* Structure: stores/{storeId}/{folder}/{entityId?}/{filename}
|
|
79
|
+
*
|
|
80
|
+
* Examples:
|
|
81
|
+
* - stores/abc123/products/prod456/1699999999_abc123.jpg
|
|
82
|
+
* - stores/abc123/categories/cat789/cover.jpg
|
|
83
|
+
* - stores/abc123/general/1699999999_abc123.jpg
|
|
84
|
+
*/
|
|
85
|
+
export function buildStorageKey(storeId, folder, filename, entityId) {
|
|
86
|
+
const parts = ['stores', storeId, folder];
|
|
87
|
+
if (entityId) {
|
|
88
|
+
parts.push(entityId);
|
|
89
|
+
}
|
|
90
|
+
parts.push(filename);
|
|
91
|
+
return parts.join('/');
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Build the public URL for a stored file
|
|
95
|
+
*/
|
|
96
|
+
export function buildPublicUrl(publicBaseUrl, key) {
|
|
97
|
+
// Remove trailing slash from base URL if present
|
|
98
|
+
const baseUrl = publicBaseUrl.endsWith('/') ? publicBaseUrl.slice(0, -1) : publicBaseUrl;
|
|
99
|
+
return `${baseUrl}/${key}`;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Extract the storage key from a public URL
|
|
103
|
+
*/
|
|
104
|
+
export function extractKeyFromUrl(publicBaseUrl, url) {
|
|
105
|
+
const baseUrl = publicBaseUrl.endsWith('/') ? publicBaseUrl.slice(0, -1) : publicBaseUrl;
|
|
106
|
+
if (!url.startsWith(baseUrl)) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
return url.slice(baseUrl.length + 1);
|
|
110
|
+
}
|
|
111
|
+
// =============================================================================
|
|
112
|
+
// Content Type Mapping
|
|
113
|
+
// =============================================================================
|
|
114
|
+
/**
|
|
115
|
+
* Map of file extensions to MIME types
|
|
116
|
+
*/
|
|
117
|
+
const EXTENSION_TO_MIME = {
|
|
118
|
+
jpg: 'image/jpeg',
|
|
119
|
+
jpeg: 'image/jpeg',
|
|
120
|
+
png: 'image/png',
|
|
121
|
+
gif: 'image/gif',
|
|
122
|
+
webp: 'image/webp',
|
|
123
|
+
svg: 'image/svg+xml',
|
|
124
|
+
ico: 'image/x-icon',
|
|
125
|
+
bmp: 'image/bmp',
|
|
126
|
+
tiff: 'image/tiff',
|
|
127
|
+
tif: 'image/tiff',
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Get MIME type from filename extension
|
|
131
|
+
*/
|
|
132
|
+
export function getMimeType(filename) {
|
|
133
|
+
const ext = filename.split('.').pop()?.toLowerCase() || '';
|
|
134
|
+
return EXTENSION_TO_MIME[ext] || 'application/octet-stream';
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Check if a MIME type is an allowed image type
|
|
138
|
+
*/
|
|
139
|
+
export function isAllowedImageType(mimeType, config = defaultUploadConfig) {
|
|
140
|
+
return config.allowedTypes.includes(mimeType);
|
|
141
|
+
}
|
|
142
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/media/config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,gFAAgF;AAChF,+BAA+B;AAC/B,gFAAgF;AAEhF;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC5B,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAA;IAC3C,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAA;IAChD,MAAM,eAAe,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAA;IACxD,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAA;IAC7C,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAA;IAC3C,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAA;IAEpC,mBAAmB;IACnB,IAAI,CAAC,SAAS,IAAI,CAAC,WAAW,IAAI,CAAC,eAAe,IAAI,CAAC,UAAU,IAAI,CAAC,SAAS,IAAI,CAAC,OAAO,EAAE,CAAC;QAC5F,OAAO,IAAI,CAAA;IACb,CAAC;IAED,OAAO;QACL,SAAS;QACT,WAAW;QACX,eAAe;QACf,UAAU;QACV,SAAS;QACT,OAAO;KACR,CAAA;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO,cAAc,EAAE,KAAK,IAAI,CAAA;AAClC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,MAAM,GAAG,cAAc,EAAE,CAAA;IAC/B,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,gEAAgE;YAC9D,gGAAgG,CACnG,CAAA;IACH,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED,gFAAgF;AAChF,uBAAuB;AACvB,gFAAgF;AAEhF;;GAEG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAiB;IAC/C,YAAY,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,OAAO;IACvC,YAAY,EAAE,CAAC,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,CAAC;IACpE,QAAQ,EAAE,EAAE;CACb,CAAA;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,SAAiC;IAC/D,OAAO;QACL,GAAG,mBAAmB;QACtB,GAAG,SAAS;KACb,CAAA;AACH,CAAC;AAED,gFAAgF;AAChF,eAAe;AACf,gFAAgF;AAEhF;;;;;;;;;GASG;AACH,MAAM,UAAU,eAAe,CAC7B,OAAe,EACf,MAAc,EACd,QAAgB,EAChB,QAAiB;IAEjB,MAAM,KAAK,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAAA;IAEzC,IAAI,QAAQ,EAAE,CAAC;QACb,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IACtB,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;IAEpB,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AACxB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,aAAqB,EAAE,GAAW;IAC/D,iDAAiD;IACjD,MAAM,OAAO,GAAG,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAA;IACxF,OAAO,GAAG,OAAO,IAAI,GAAG,EAAE,CAAA;AAC5B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAAC,aAAqB,EAAE,GAAW;IAClE,MAAM,OAAO,GAAG,aAAa,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,aAAa,CAAA;IAExF,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAA;IACb,CAAC;IAED,OAAO,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;AACtC,CAAC;AAED,gFAAgF;AAChF,uBAAuB;AACvB,gFAAgF;AAEhF;;GAEG;AACH,MAAM,iBAAiB,GAA2B;IAChD,GAAG,EAAE,YAAY;IACjB,IAAI,EAAE,YAAY;IAClB,GAAG,EAAE,WAAW;IAChB,GAAG,EAAE,WAAW;IAChB,IAAI,EAAE,YAAY;IAClB,GAAG,EAAE,eAAe;IACpB,GAAG,EAAE,cAAc;IACnB,GAAG,EAAE,WAAW;IAChB,IAAI,EAAE,YAAY;IAClB,GAAG,EAAE,YAAY;CAClB,CAAA;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,QAAgB;IAC1C,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAA;IAC1D,OAAO,iBAAiB,CAAC,GAAG,CAAC,IAAI,0BAA0B,CAAA;AAC7D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAChC,QAAgB,EAChB,SAAuB,mBAAmB;IAE1C,OAAO,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;AAC/C,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/media/hooks/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/media/hooks/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { UseUploadOptions, UseUploadReturn } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* React hook for uploading files to R2 storage.
|
|
4
|
+
*
|
|
5
|
+
* @param options - Upload configuration options
|
|
6
|
+
* @returns Upload functions and state
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* function ImageUploader() {
|
|
11
|
+
* const { upload, isUploading, progress, error } = useUpload({
|
|
12
|
+
* folder: 'products',
|
|
13
|
+
* entityId: 'prod_123',
|
|
14
|
+
* onSuccess: (result) => console.log('Uploaded:', result.url),
|
|
15
|
+
* });
|
|
16
|
+
*
|
|
17
|
+
* const handleFile = async (file: File) => {
|
|
18
|
+
* const result = await upload(file);
|
|
19
|
+
* if (result.success) {
|
|
20
|
+
* // Use result.url
|
|
21
|
+
* }
|
|
22
|
+
* };
|
|
23
|
+
*
|
|
24
|
+
* return (
|
|
25
|
+
* <input type="file" onChange={(e) => handleFile(e.target.files[0])} />
|
|
26
|
+
* );
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function useUpload(options: UseUploadOptions): UseUploadReturn;
|
|
31
|
+
export default useUpload;
|
|
32
|
+
//# sourceMappingURL=useUpload.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useUpload.d.ts","sourceRoot":"","sources":["../../../src/media/hooks/useUpload.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EAMhB,MAAM,UAAU,CAAA;AAOjB;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,eAAe,CAqMpE;AAmED,eAAe,SAAS,CAAA"}
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
/**
|
|
3
|
+
* @rovela-ai/sdk/media/hooks/useUpload
|
|
4
|
+
*
|
|
5
|
+
* React hook for uploading files to R2 storage via presigned URLs.
|
|
6
|
+
*/
|
|
7
|
+
import { useState, useCallback, useRef } from 'react';
|
|
8
|
+
import { validateFile, DEFAULT_UPLOAD_CONFIG } from '../types';
|
|
9
|
+
// =============================================================================
|
|
10
|
+
// Hook Implementation
|
|
11
|
+
// =============================================================================
|
|
12
|
+
/**
|
|
13
|
+
* React hook for uploading files to R2 storage.
|
|
14
|
+
*
|
|
15
|
+
* @param options - Upload configuration options
|
|
16
|
+
* @returns Upload functions and state
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* function ImageUploader() {
|
|
21
|
+
* const { upload, isUploading, progress, error } = useUpload({
|
|
22
|
+
* folder: 'products',
|
|
23
|
+
* entityId: 'prod_123',
|
|
24
|
+
* onSuccess: (result) => console.log('Uploaded:', result.url),
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* const handleFile = async (file: File) => {
|
|
28
|
+
* const result = await upload(file);
|
|
29
|
+
* if (result.success) {
|
|
30
|
+
* // Use result.url
|
|
31
|
+
* }
|
|
32
|
+
* };
|
|
33
|
+
*
|
|
34
|
+
* return (
|
|
35
|
+
* <input type="file" onChange={(e) => handleFile(e.target.files[0])} />
|
|
36
|
+
* );
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export function useUpload(options) {
|
|
41
|
+
const { folder, entityId, config, onSuccess, onError, onProgress } = options;
|
|
42
|
+
// Merge config with defaults
|
|
43
|
+
const uploadConfig = {
|
|
44
|
+
...DEFAULT_UPLOAD_CONFIG,
|
|
45
|
+
...config,
|
|
46
|
+
};
|
|
47
|
+
// State
|
|
48
|
+
const [status, setStatus] = useState('idle');
|
|
49
|
+
const [progress, setProgress] = useState(null);
|
|
50
|
+
const [error, setError] = useState(null);
|
|
51
|
+
// Abort controller ref for cancellation
|
|
52
|
+
const abortControllerRef = useRef(null);
|
|
53
|
+
/**
|
|
54
|
+
* Upload a single file
|
|
55
|
+
*/
|
|
56
|
+
const upload = useCallback(async (file) => {
|
|
57
|
+
// Validate file
|
|
58
|
+
const validation = validateFile(file, uploadConfig);
|
|
59
|
+
if (!validation.valid) {
|
|
60
|
+
const errorMsg = validation.error || 'File validation failed';
|
|
61
|
+
setError(errorMsg);
|
|
62
|
+
setStatus('error');
|
|
63
|
+
onError?.(errorMsg);
|
|
64
|
+
return {
|
|
65
|
+
success: false,
|
|
66
|
+
filename: file.name,
|
|
67
|
+
size: file.size,
|
|
68
|
+
contentType: file.type,
|
|
69
|
+
error: errorMsg,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// Reset state
|
|
73
|
+
setError(null);
|
|
74
|
+
setStatus('preparing');
|
|
75
|
+
setProgress(null);
|
|
76
|
+
// Create abort controller
|
|
77
|
+
abortControllerRef.current = new AbortController();
|
|
78
|
+
const signal = abortControllerRef.current.signal;
|
|
79
|
+
try {
|
|
80
|
+
// Step 1: Get presigned URL from API
|
|
81
|
+
const presignResponse = await fetch('/api/media/presign', {
|
|
82
|
+
method: 'POST',
|
|
83
|
+
headers: { 'Content-Type': 'application/json' },
|
|
84
|
+
body: JSON.stringify({
|
|
85
|
+
filename: file.name,
|
|
86
|
+
contentType: file.type,
|
|
87
|
+
folder,
|
|
88
|
+
entityId,
|
|
89
|
+
}),
|
|
90
|
+
signal,
|
|
91
|
+
});
|
|
92
|
+
if (!presignResponse.ok) {
|
|
93
|
+
const errorData = await presignResponse.json().catch(() => ({}));
|
|
94
|
+
throw new Error(errorData.error || 'Failed to get upload URL');
|
|
95
|
+
}
|
|
96
|
+
const presignData = await presignResponse.json();
|
|
97
|
+
if (!presignData.success || !presignData.data) {
|
|
98
|
+
throw new Error(presignData.error || 'Failed to get upload URL');
|
|
99
|
+
}
|
|
100
|
+
// Step 2: Upload file directly to R2
|
|
101
|
+
setStatus('uploading');
|
|
102
|
+
// Use XMLHttpRequest for progress tracking
|
|
103
|
+
const uploadResult = await uploadWithProgress(presignData.data.uploadUrl, file, (prog) => {
|
|
104
|
+
setProgress(prog);
|
|
105
|
+
onProgress?.(prog);
|
|
106
|
+
}, signal);
|
|
107
|
+
if (!uploadResult.success) {
|
|
108
|
+
throw new Error(uploadResult.error || 'Upload failed');
|
|
109
|
+
}
|
|
110
|
+
// Success
|
|
111
|
+
setStatus('success');
|
|
112
|
+
setProgress({ loaded: file.size, total: file.size, percentage: 100 });
|
|
113
|
+
const result = {
|
|
114
|
+
success: true,
|
|
115
|
+
url: presignData.data.publicUrl,
|
|
116
|
+
filename: file.name,
|
|
117
|
+
size: file.size,
|
|
118
|
+
contentType: file.type,
|
|
119
|
+
};
|
|
120
|
+
onSuccess?.(result);
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
// Handle abort
|
|
125
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
126
|
+
setStatus('idle');
|
|
127
|
+
setError(null);
|
|
128
|
+
return {
|
|
129
|
+
success: false,
|
|
130
|
+
filename: file.name,
|
|
131
|
+
size: file.size,
|
|
132
|
+
contentType: file.type,
|
|
133
|
+
error: 'Upload cancelled',
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
// Handle error
|
|
137
|
+
const errorMsg = err instanceof Error ? err.message : 'Upload failed';
|
|
138
|
+
setError(errorMsg);
|
|
139
|
+
setStatus('error');
|
|
140
|
+
onError?.(errorMsg);
|
|
141
|
+
return {
|
|
142
|
+
success: false,
|
|
143
|
+
filename: file.name,
|
|
144
|
+
size: file.size,
|
|
145
|
+
contentType: file.type,
|
|
146
|
+
error: errorMsg,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
finally {
|
|
150
|
+
abortControllerRef.current = null;
|
|
151
|
+
}
|
|
152
|
+
}, [folder, entityId, uploadConfig, onSuccess, onError, onProgress]);
|
|
153
|
+
/**
|
|
154
|
+
* Upload multiple files
|
|
155
|
+
*/
|
|
156
|
+
const uploadMultiple = useCallback(async (files) => {
|
|
157
|
+
// Validate max files
|
|
158
|
+
if (files.length > uploadConfig.maxFiles) {
|
|
159
|
+
const errorMsg = `Maximum ${uploadConfig.maxFiles} files allowed`;
|
|
160
|
+
setError(errorMsg);
|
|
161
|
+
onError?.(errorMsg);
|
|
162
|
+
return files.map((f) => ({
|
|
163
|
+
success: false,
|
|
164
|
+
filename: f.name,
|
|
165
|
+
size: f.size,
|
|
166
|
+
contentType: f.type,
|
|
167
|
+
error: errorMsg,
|
|
168
|
+
}));
|
|
169
|
+
}
|
|
170
|
+
// Upload sequentially to avoid overwhelming the server
|
|
171
|
+
const results = [];
|
|
172
|
+
for (const file of files) {
|
|
173
|
+
const result = await upload(file);
|
|
174
|
+
results.push(result);
|
|
175
|
+
// Stop if there's an error (optional - could continue)
|
|
176
|
+
if (!result.success) {
|
|
177
|
+
break;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return results;
|
|
181
|
+
}, [upload, uploadConfig.maxFiles, onError]);
|
|
182
|
+
/**
|
|
183
|
+
* Reset state to idle
|
|
184
|
+
*/
|
|
185
|
+
const reset = useCallback(() => {
|
|
186
|
+
// Cancel any in-progress upload
|
|
187
|
+
if (abortControllerRef.current) {
|
|
188
|
+
abortControllerRef.current.abort();
|
|
189
|
+
abortControllerRef.current = null;
|
|
190
|
+
}
|
|
191
|
+
setStatus('idle');
|
|
192
|
+
setProgress(null);
|
|
193
|
+
setError(null);
|
|
194
|
+
}, []);
|
|
195
|
+
return {
|
|
196
|
+
upload,
|
|
197
|
+
uploadMultiple,
|
|
198
|
+
status,
|
|
199
|
+
progress,
|
|
200
|
+
isUploading: status === 'preparing' || status === 'uploading',
|
|
201
|
+
error,
|
|
202
|
+
reset,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
// =============================================================================
|
|
206
|
+
// Helper Functions
|
|
207
|
+
// =============================================================================
|
|
208
|
+
/**
|
|
209
|
+
* Upload file with progress tracking using XMLHttpRequest
|
|
210
|
+
*/
|
|
211
|
+
function uploadWithProgress(url, file, onProgress, signal) {
|
|
212
|
+
return new Promise((resolve) => {
|
|
213
|
+
const xhr = new XMLHttpRequest();
|
|
214
|
+
// Handle abort
|
|
215
|
+
if (signal) {
|
|
216
|
+
signal.addEventListener('abort', () => {
|
|
217
|
+
xhr.abort();
|
|
218
|
+
resolve({ success: false, error: 'Upload cancelled' });
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
// Track progress
|
|
222
|
+
xhr.upload.addEventListener('progress', (event) => {
|
|
223
|
+
if (event.lengthComputable) {
|
|
224
|
+
onProgress({
|
|
225
|
+
loaded: event.loaded,
|
|
226
|
+
total: event.total,
|
|
227
|
+
percentage: Math.round((event.loaded / event.total) * 100),
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
// Handle completion
|
|
232
|
+
xhr.addEventListener('load', () => {
|
|
233
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
234
|
+
resolve({ success: true });
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
resolve({ success: false, error: `Upload failed with status ${xhr.status}` });
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
// Handle error
|
|
241
|
+
xhr.addEventListener('error', () => {
|
|
242
|
+
resolve({ success: false, error: 'Network error during upload' });
|
|
243
|
+
});
|
|
244
|
+
// Handle abort
|
|
245
|
+
xhr.addEventListener('abort', () => {
|
|
246
|
+
resolve({ success: false, error: 'Upload cancelled' });
|
|
247
|
+
});
|
|
248
|
+
// Send request
|
|
249
|
+
xhr.open('PUT', url);
|
|
250
|
+
xhr.setRequestHeader('Content-Type', file.type);
|
|
251
|
+
xhr.send(file);
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
// =============================================================================
|
|
255
|
+
// Exports
|
|
256
|
+
// =============================================================================
|
|
257
|
+
export default useUpload;
|
|
258
|
+
//# sourceMappingURL=useUpload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useUpload.js","sourceRoot":"","sources":["../../../src/media/hooks/useUpload.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rovela-ai/sdk/media
|
|
3
|
+
*
|
|
4
|
+
* Media upload module for R2 storage.
|
|
5
|
+
*
|
|
6
|
+
* This module provides:
|
|
7
|
+
* - Server-side presigned URL generation
|
|
8
|
+
* - Client-side upload hooks and components
|
|
9
|
+
* - API route handlers for Next.js
|
|
10
|
+
*
|
|
11
|
+
* ## Quick Start
|
|
12
|
+
*
|
|
13
|
+
* 1. Add environment variables:
|
|
14
|
+
* ```env
|
|
15
|
+
* R2_ACCOUNT_ID=xxx
|
|
16
|
+
* R2_ACCESS_KEY_ID=xxx
|
|
17
|
+
* R2_SECRET_ACCESS_KEY=xxx
|
|
18
|
+
* R2_BUCKET_NAME=xxx
|
|
19
|
+
* R2_PUBLIC_URL=https://pub-xxx.r2.dev
|
|
20
|
+
* STORE_ID=xxx
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* 2. Create API routes:
|
|
24
|
+
* ```typescript
|
|
25
|
+
* // app/api/media/presign/route.ts
|
|
26
|
+
* export { POST } from '@rovela-ai/sdk/media/api'
|
|
27
|
+
*
|
|
28
|
+
* // app/api/media/route.ts
|
|
29
|
+
* export { DELETE } from '@rovela-ai/sdk/media/api'
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* 3. Use components:
|
|
33
|
+
* ```tsx
|
|
34
|
+
* import { ImageGalleryUpload } from '@rovela-ai/sdk/media'
|
|
35
|
+
*
|
|
36
|
+
* <ImageGalleryUpload
|
|
37
|
+
* value={images}
|
|
38
|
+
* onChange={setImages}
|
|
39
|
+
* folder="products"
|
|
40
|
+
* entityId={productId}
|
|
41
|
+
* />
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* ## Module Structure
|
|
45
|
+
*
|
|
46
|
+
* - `@rovela-ai/sdk/media` - Types, config, components, hooks
|
|
47
|
+
* - `@rovela-ai/sdk/media/server` - Server-side utilities (presign, delete)
|
|
48
|
+
* - `@rovela-ai/sdk/media/api` - Next.js API route handlers
|
|
49
|
+
*
|
|
50
|
+
* @module media
|
|
51
|
+
*/
|
|
52
|
+
export type { MediaStorageConfig, UploadConfig, MediaFolder, PresignedUrlRequest, PresignedUrlResponse, PresignApiRequest, PresignApiResponse, DeleteApiRequest, DeleteApiResponse, MediaApiError, UploadProgress, UploadStatus, UploadResult, UploadState, UseUploadOptions, UseUploadReturn, DropZoneProps, ImageUploadProps, ImageGalleryUploadProps, FileValidation, } from './types';
|
|
53
|
+
export { DEFAULT_UPLOAD_CONFIG, validateFile, generateUniqueFilename, getFileExtension } from './types';
|
|
54
|
+
export { getMediaConfig, isMediaConfigured, requireMediaConfig, defaultUploadConfig, getUploadConfig, buildStorageKey, buildPublicUrl, extractKeyFromUrl, getMimeType, isAllowedImageType, } from './config';
|
|
55
|
+
export { useUpload } from './hooks';
|
|
56
|
+
export { DropZone, ImageUpload, ImageGalleryUpload } from './components';
|
|
57
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/media/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAMH,YAAY,EAEV,kBAAkB,EAClB,YAAY,EACZ,WAAW,EAEX,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,aAAa,EAEb,cAAc,EACd,YAAY,EACZ,YAAY,EACZ,WAAW,EAEX,gBAAgB,EAChB,eAAe,EAEf,aAAa,EACb,gBAAgB,EAChB,uBAAuB,EAEvB,cAAc,GACf,MAAM,SAAS,CAAA;AAMhB,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAMvG,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,WAAW,EACX,kBAAkB,GACnB,MAAM,UAAU,CAAA;AAMjB,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAMnC,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA"}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rovela-ai/sdk/media
|
|
3
|
+
*
|
|
4
|
+
* Media upload module for R2 storage.
|
|
5
|
+
*
|
|
6
|
+
* This module provides:
|
|
7
|
+
* - Server-side presigned URL generation
|
|
8
|
+
* - Client-side upload hooks and components
|
|
9
|
+
* - API route handlers for Next.js
|
|
10
|
+
*
|
|
11
|
+
* ## Quick Start
|
|
12
|
+
*
|
|
13
|
+
* 1. Add environment variables:
|
|
14
|
+
* ```env
|
|
15
|
+
* R2_ACCOUNT_ID=xxx
|
|
16
|
+
* R2_ACCESS_KEY_ID=xxx
|
|
17
|
+
* R2_SECRET_ACCESS_KEY=xxx
|
|
18
|
+
* R2_BUCKET_NAME=xxx
|
|
19
|
+
* R2_PUBLIC_URL=https://pub-xxx.r2.dev
|
|
20
|
+
* STORE_ID=xxx
|
|
21
|
+
* ```
|
|
22
|
+
*
|
|
23
|
+
* 2. Create API routes:
|
|
24
|
+
* ```typescript
|
|
25
|
+
* // app/api/media/presign/route.ts
|
|
26
|
+
* export { POST } from '@rovela-ai/sdk/media/api'
|
|
27
|
+
*
|
|
28
|
+
* // app/api/media/route.ts
|
|
29
|
+
* export { DELETE } from '@rovela-ai/sdk/media/api'
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* 3. Use components:
|
|
33
|
+
* ```tsx
|
|
34
|
+
* import { ImageGalleryUpload } from '@rovela-ai/sdk/media'
|
|
35
|
+
*
|
|
36
|
+
* <ImageGalleryUpload
|
|
37
|
+
* value={images}
|
|
38
|
+
* onChange={setImages}
|
|
39
|
+
* folder="products"
|
|
40
|
+
* entityId={productId}
|
|
41
|
+
* />
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* ## Module Structure
|
|
45
|
+
*
|
|
46
|
+
* - `@rovela-ai/sdk/media` - Types, config, components, hooks
|
|
47
|
+
* - `@rovela-ai/sdk/media/server` - Server-side utilities (presign, delete)
|
|
48
|
+
* - `@rovela-ai/sdk/media/api` - Next.js API route handlers
|
|
49
|
+
*
|
|
50
|
+
* @module media
|
|
51
|
+
*/
|
|
52
|
+
// =============================================================================
|
|
53
|
+
// Constants & Utilities
|
|
54
|
+
// =============================================================================
|
|
55
|
+
export { DEFAULT_UPLOAD_CONFIG, validateFile, generateUniqueFilename, getFileExtension } from './types';
|
|
56
|
+
// =============================================================================
|
|
57
|
+
// Configuration
|
|
58
|
+
// =============================================================================
|
|
59
|
+
export { getMediaConfig, isMediaConfigured, requireMediaConfig, defaultUploadConfig, getUploadConfig, buildStorageKey, buildPublicUrl, extractKeyFromUrl, getMimeType, isAllowedImageType, } from './config';
|
|
60
|
+
// =============================================================================
|
|
61
|
+
// Hooks
|
|
62
|
+
// =============================================================================
|
|
63
|
+
export { useUpload } from './hooks';
|
|
64
|
+
// =============================================================================
|
|
65
|
+
// Components
|
|
66
|
+
// =============================================================================
|
|
67
|
+
export { DropZone, ImageUpload, ImageGalleryUpload } from './components';
|
|
68
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/media/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAmCH,gFAAgF;AAChF,wBAAwB;AACxB,gFAAgF;AAEhF,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAEvG,gFAAgF;AAChF,gBAAgB;AAChB,gFAAgF;AAEhF,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,cAAc,EACd,iBAAiB,EACjB,WAAW,EACX,kBAAkB,GACnB,MAAM,UAAU,CAAA;AAEjB,gFAAgF;AAChF,QAAQ;AACR,gFAAgF;AAEhF,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAEnC,gFAAgF;AAChF,aAAa;AACb,gFAAgF;AAEhF,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rovela-ai/sdk/media/server/delete
|
|
3
|
+
*
|
|
4
|
+
* File deletion from R2 storage.
|
|
5
|
+
* Server-side only - do not import in client components.
|
|
6
|
+
*/
|
|
7
|
+
import type { MediaStorageConfig } from '../types';
|
|
8
|
+
/**
|
|
9
|
+
* Delete a file from R2 storage.
|
|
10
|
+
*
|
|
11
|
+
* @param url - Public URL of the file to delete
|
|
12
|
+
* @param config - Optional storage configuration override
|
|
13
|
+
* @returns True if deletion was successful
|
|
14
|
+
* @throws Error if URL is invalid or doesn't belong to this store
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* await deleteFile('https://pub-xxx.r2.dev/stores/abc123/products/img.jpg');
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export declare function deleteFile(url: string, config?: MediaStorageConfig): Promise<boolean>;
|
|
22
|
+
/**
|
|
23
|
+
* Delete a file by its storage key.
|
|
24
|
+
* Use this when you have the key directly instead of the URL.
|
|
25
|
+
*
|
|
26
|
+
* @param key - Storage key of the file
|
|
27
|
+
* @param config - Optional storage configuration override
|
|
28
|
+
* @returns True if deletion was successful
|
|
29
|
+
*/
|
|
30
|
+
export declare function deleteFileByKey(key: string, config?: MediaStorageConfig): Promise<boolean>;
|
|
31
|
+
/**
|
|
32
|
+
* Delete multiple files from R2 storage.
|
|
33
|
+
*
|
|
34
|
+
* @param urls - Array of public URLs to delete
|
|
35
|
+
* @param config - Optional storage configuration override
|
|
36
|
+
* @returns Object with results for each URL
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* const results = await deleteFiles([
|
|
41
|
+
* 'https://pub-xxx.r2.dev/stores/abc123/products/img1.jpg',
|
|
42
|
+
* 'https://pub-xxx.r2.dev/stores/abc123/products/img2.jpg',
|
|
43
|
+
* ]);
|
|
44
|
+
* // { succeeded: ['url1', 'url2'], failed: [] }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function deleteFiles(urls: string[], config?: MediaStorageConfig): Promise<{
|
|
48
|
+
succeeded: string[];
|
|
49
|
+
failed: string[];
|
|
50
|
+
}>;
|
|
51
|
+
/**
|
|
52
|
+
* Safely delete a file - returns false instead of throwing if file doesn't exist.
|
|
53
|
+
*
|
|
54
|
+
* @param url - Public URL of the file to delete
|
|
55
|
+
* @param config - Optional storage configuration override
|
|
56
|
+
* @returns True if deleted, false if not found or invalid
|
|
57
|
+
*/
|
|
58
|
+
export declare function safeDeleteFile(url: string, config?: MediaStorageConfig): Promise<boolean>;
|
|
59
|
+
//# sourceMappingURL=delete.d.ts.map
|