@nextlyhq/storage-vercel-blob 0.0.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.
- package/LICENSE +22 -0
- package/README.md +90 -0
- package/dist/index.cjs +399 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +727 -0
- package/dist/index.d.ts +727 -0
- package/dist/index.mjs +394 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +76 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,727 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media Storage Types
|
|
3
|
+
*
|
|
4
|
+
* Defines interfaces and types for the unified media storage system.
|
|
5
|
+
* Supports cloud storage adapters via plugins.
|
|
6
|
+
*
|
|
7
|
+
* Storage Backends:
|
|
8
|
+
* - AWS S3 / Cloudflare R2 / MinIO (via @nextlyhq/storage-s3)
|
|
9
|
+
* - Vercel Blob (via @nextlyhq/storage-vercel-blob)
|
|
10
|
+
*/
|
|
11
|
+
interface UploadOptions {
|
|
12
|
+
/** Original filename from user */
|
|
13
|
+
filename: string;
|
|
14
|
+
/** MIME type (e.g., 'image/png', 'video/mp4') */
|
|
15
|
+
mimeType: string;
|
|
16
|
+
/** Optional content type override */
|
|
17
|
+
contentType?: string;
|
|
18
|
+
/** Optional folder/prefix for organizing uploads */
|
|
19
|
+
folder?: string;
|
|
20
|
+
/** Collection slug this upload belongs to (for collection-specific storage) */
|
|
21
|
+
collection?: string;
|
|
22
|
+
/** Optional Content-Disposition header value (e.g., 'attachment' for SVG security) */
|
|
23
|
+
contentDisposition?: "inline" | "attachment";
|
|
24
|
+
}
|
|
25
|
+
interface UploadResult {
|
|
26
|
+
/** Public URL to access the file */
|
|
27
|
+
url: string;
|
|
28
|
+
/** Storage path/key (for deletion and metadata retrieval) */
|
|
29
|
+
path: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Extended file metadata returned by getMetadata()
|
|
33
|
+
*
|
|
34
|
+
* Contains comprehensive information about an uploaded file,
|
|
35
|
+
* including dimensions for images and creation timestamps.
|
|
36
|
+
*/
|
|
37
|
+
interface FileMetadata {
|
|
38
|
+
/** Unique identifier (typically the storage path/key) */
|
|
39
|
+
id: string;
|
|
40
|
+
/** Storage filename (may differ from original) */
|
|
41
|
+
filename: string;
|
|
42
|
+
/** Original filename as uploaded by user */
|
|
43
|
+
originalFilename: string;
|
|
44
|
+
/** MIME type (e.g., 'image/jpeg', 'application/pdf') */
|
|
45
|
+
mimeType: string;
|
|
46
|
+
/** File size in bytes */
|
|
47
|
+
size: number;
|
|
48
|
+
/** Public URL to access the file */
|
|
49
|
+
url: string;
|
|
50
|
+
/** Thumbnail URL for images (if generated) */
|
|
51
|
+
thumbnailUrl?: string;
|
|
52
|
+
/** Image width in pixels (for images only) */
|
|
53
|
+
width?: number;
|
|
54
|
+
/** Image height in pixels (for images only) */
|
|
55
|
+
height?: number;
|
|
56
|
+
/** ISO timestamp when file was uploaded */
|
|
57
|
+
createdAt: string;
|
|
58
|
+
/** ISO timestamp when file was last modified */
|
|
59
|
+
updatedAt?: string;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Storage type identifier.
|
|
63
|
+
* - "s3": AWS S3 or S3-compatible services (R2, MinIO, DigitalOcean Spaces)
|
|
64
|
+
* - "vercel-blob": Vercel Blob Storage
|
|
65
|
+
* - "local": Local disk storage (default for development)
|
|
66
|
+
* - "uploadthing": Uploadthing cloud storage
|
|
67
|
+
*/
|
|
68
|
+
type StorageType = "s3" | "vercel-blob" | "local" | "uploadthing";
|
|
69
|
+
/**
|
|
70
|
+
* Information about a storage adapter's capabilities.
|
|
71
|
+
* Returned by adapter.getInfo() method.
|
|
72
|
+
*/
|
|
73
|
+
interface StorageAdapterInfo {
|
|
74
|
+
/** Storage type identifier */
|
|
75
|
+
type: StorageType;
|
|
76
|
+
/** Human-readable adapter name */
|
|
77
|
+
name: string;
|
|
78
|
+
/** Whether this adapter supports signed URLs for private access */
|
|
79
|
+
supportsSignedUrls: boolean;
|
|
80
|
+
/** Whether this adapter supports client-side (direct) uploads */
|
|
81
|
+
supportsClientUploads: boolean;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Per-collection storage configuration.
|
|
85
|
+
* Allows customizing storage behavior for specific upload collections.
|
|
86
|
+
*/
|
|
87
|
+
interface CollectionStorageConfig {
|
|
88
|
+
/** Prefix/folder for this collection's uploads */
|
|
89
|
+
prefix?: string;
|
|
90
|
+
/** Enable client-side uploads (for serverless platforms with body size limits) */
|
|
91
|
+
clientUploads?: boolean;
|
|
92
|
+
/** Generate signed URLs for downloads (for private buckets) */
|
|
93
|
+
signedDownloads?: boolean;
|
|
94
|
+
/** Signed URL expiry time in seconds (default: 3600) */
|
|
95
|
+
signedUrlExpiresIn?: number;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Collection storage map - maps collection slugs to their config.
|
|
99
|
+
* Used in storage plugin configuration.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```typescript
|
|
103
|
+
* {
|
|
104
|
+
* media: true, // Use default config
|
|
105
|
+
* 'private-docs': {
|
|
106
|
+
* prefix: 'private/',
|
|
107
|
+
* signedDownloads: true,
|
|
108
|
+
* signedUrlExpiresIn: 900
|
|
109
|
+
* }
|
|
110
|
+
* }
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
type CollectionStorageMap = Record<string, boolean | CollectionStorageConfig>;
|
|
114
|
+
/**
|
|
115
|
+
* Base configuration for storage plugins.
|
|
116
|
+
* Extended by specific adapter configs (S3StorageConfig, etc.)
|
|
117
|
+
*/
|
|
118
|
+
interface StoragePluginConfig {
|
|
119
|
+
/** Enable/disable the plugin (default: true) */
|
|
120
|
+
enabled?: boolean;
|
|
121
|
+
/** Collections to apply this storage adapter to */
|
|
122
|
+
collections: CollectionStorageMap;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Storage plugin returned by adapter plugin functions.
|
|
126
|
+
* These are processed during Nextly initialization.
|
|
127
|
+
*
|
|
128
|
+
* @example
|
|
129
|
+
* ```typescript
|
|
130
|
+
* // From @nextlyhq/storage-s3
|
|
131
|
+
* const plugin = s3Storage({
|
|
132
|
+
* bucket: 'my-bucket',
|
|
133
|
+
* region: 'us-east-1',
|
|
134
|
+
* collections: { media: true }
|
|
135
|
+
* });
|
|
136
|
+
* // plugin implements StoragePlugin
|
|
137
|
+
* ```
|
|
138
|
+
*/
|
|
139
|
+
interface StoragePlugin {
|
|
140
|
+
/** Plugin name for identification */
|
|
141
|
+
name: string;
|
|
142
|
+
/** Storage type */
|
|
143
|
+
type: StorageType;
|
|
144
|
+
/** Collections this plugin handles */
|
|
145
|
+
collections: CollectionStorageMap;
|
|
146
|
+
/** The storage adapter instance */
|
|
147
|
+
adapter: IStorageAdapter;
|
|
148
|
+
/**
|
|
149
|
+
* Handler for generating client-side upload URLs.
|
|
150
|
+
* Called when clientUploads is enabled for a collection.
|
|
151
|
+
*/
|
|
152
|
+
getClientUploadUrl?: (filename: string, mimeType: string, collection: string) => Promise<ClientUploadData>;
|
|
153
|
+
/**
|
|
154
|
+
* Handler for generating signed download URLs.
|
|
155
|
+
* Called when signedDownloads is enabled for a collection.
|
|
156
|
+
*/
|
|
157
|
+
getSignedDownloadUrl?: (path: string, expiresIn?: number) => Promise<string>;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Data returned for client-side (direct) uploads.
|
|
161
|
+
* Contains pre-signed URL and headers for direct-to-storage uploads.
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```typescript
|
|
165
|
+
* // Usage in frontend
|
|
166
|
+
* const uploadData = await fetch('/api/nextly/storage/upload-url', {
|
|
167
|
+
* method: 'POST',
|
|
168
|
+
* body: JSON.stringify({ filename: 'photo.jpg', mimeType: 'image/jpeg', collection: 'media' })
|
|
169
|
+
* }).then(r => r.json());
|
|
170
|
+
*
|
|
171
|
+
* // Direct upload to storage
|
|
172
|
+
* await fetch(uploadData.uploadUrl, {
|
|
173
|
+
* method: uploadData.method,
|
|
174
|
+
* headers: uploadData.headers,
|
|
175
|
+
* body: file
|
|
176
|
+
* });
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
interface ClientUploadData {
|
|
180
|
+
/** Pre-signed URL for direct upload */
|
|
181
|
+
uploadUrl: string;
|
|
182
|
+
/** Storage path/key that will be used */
|
|
183
|
+
path: string;
|
|
184
|
+
/** HTTP method to use (usually PUT for S3, POST for some services) */
|
|
185
|
+
method: "PUT" | "POST";
|
|
186
|
+
/** Headers to include in upload request */
|
|
187
|
+
headers?: Record<string, string>;
|
|
188
|
+
/** Form fields for multipart uploads (some services require this) */
|
|
189
|
+
fields?: Record<string, string>;
|
|
190
|
+
/** URL expiry timestamp */
|
|
191
|
+
expiresAt: Date;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Base storage adapter interface.
|
|
195
|
+
* All storage adapters must implement this interface.
|
|
196
|
+
*
|
|
197
|
+
* Core methods (required):
|
|
198
|
+
* - upload: Store file buffer
|
|
199
|
+
* - delete: Remove file from storage
|
|
200
|
+
* - exists: Check if file exists
|
|
201
|
+
* - getPublicUrl: Get public URL for file access
|
|
202
|
+
* - getType: Get storage type identifier
|
|
203
|
+
*
|
|
204
|
+
* Optional methods:
|
|
205
|
+
* - getInfo: Get adapter capabilities (recommended)
|
|
206
|
+
* - getMetadata: Retrieve file metadata
|
|
207
|
+
* - getSignedUrl: Generate temporary signed URLs for private access
|
|
208
|
+
* - getPresignedUploadUrl: Generate pre-signed URL for client uploads
|
|
209
|
+
*/
|
|
210
|
+
interface BulkDeleteResult {
|
|
211
|
+
successful: string[];
|
|
212
|
+
failed: Array<{
|
|
213
|
+
filePath: string;
|
|
214
|
+
error: string;
|
|
215
|
+
}>;
|
|
216
|
+
}
|
|
217
|
+
interface IStorageAdapter {
|
|
218
|
+
/** Upload file buffer to storage */
|
|
219
|
+
upload(buffer: Buffer, options: UploadOptions): Promise<UploadResult>;
|
|
220
|
+
/** Delete file from storage */
|
|
221
|
+
delete(filePath: string): Promise<void>;
|
|
222
|
+
/** Bulk delete files from storage. Optional — adapters that support batch operations should implement this. */
|
|
223
|
+
bulkDelete?(filePaths: string[]): Promise<BulkDeleteResult>;
|
|
224
|
+
/** Check if file exists in storage */
|
|
225
|
+
exists(filePath: string): Promise<boolean>;
|
|
226
|
+
/** Get public URL for file */
|
|
227
|
+
getPublicUrl(filePath: string): string;
|
|
228
|
+
/** Get storage type identifier */
|
|
229
|
+
getType(): string;
|
|
230
|
+
/** Read file contents from storage (optional - not all adapters support this) */
|
|
231
|
+
read?(filePath: string): Promise<Buffer | null>;
|
|
232
|
+
/** Get adapter info including capabilities (optional but recommended) */
|
|
233
|
+
getInfo?(): StorageAdapterInfo;
|
|
234
|
+
/** Get file metadata (optional - not all adapters support this) */
|
|
235
|
+
getMetadata?(filePath: string): Promise<FileMetadata | null>;
|
|
236
|
+
/** Generate signed URL for temporary private access (optional) */
|
|
237
|
+
getSignedUrl?(filePath: string, expiresIn?: number): Promise<string>;
|
|
238
|
+
/** Generate pre-signed upload URL for client-side uploads (optional) */
|
|
239
|
+
getPresignedUploadUrl?(key: string, mimeType: string, expiresIn?: number): Promise<ClientUploadData>;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Vercel Blob Storage Types
|
|
244
|
+
*
|
|
245
|
+
* Type definitions for the @nextly/storage-vercel-blob package.
|
|
246
|
+
* Optimized for Vercel deployments with client-side upload support.
|
|
247
|
+
*
|
|
248
|
+
* @packageDocumentation
|
|
249
|
+
*/
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Vercel Blob storage adapter configuration.
|
|
253
|
+
*
|
|
254
|
+
* Extends the base storage plugin config with Vercel Blob-specific options.
|
|
255
|
+
* Designed for seamless integration with Vercel's serverless platform.
|
|
256
|
+
*
|
|
257
|
+
* @example Basic usage
|
|
258
|
+
* ```typescript
|
|
259
|
+
* vercelBlobStorage({
|
|
260
|
+
* token: process.env.BLOB_READ_WRITE_TOKEN,
|
|
261
|
+
* collections: {
|
|
262
|
+
* media: true
|
|
263
|
+
* }
|
|
264
|
+
* })
|
|
265
|
+
* ```
|
|
266
|
+
*
|
|
267
|
+
* @example With client uploads (recommended for large files)
|
|
268
|
+
* ```typescript
|
|
269
|
+
* vercelBlobStorage({
|
|
270
|
+
* collections: {
|
|
271
|
+
* media: {
|
|
272
|
+
* clientUploads: true // Bypass 4.5MB serverless limit
|
|
273
|
+
* }
|
|
274
|
+
* }
|
|
275
|
+
* })
|
|
276
|
+
* ```
|
|
277
|
+
*
|
|
278
|
+
* @example With folder prefix
|
|
279
|
+
* ```typescript
|
|
280
|
+
* vercelBlobStorage({
|
|
281
|
+
* collections: {
|
|
282
|
+
* media: {
|
|
283
|
+
* prefix: 'uploads/',
|
|
284
|
+
* },
|
|
285
|
+
* 'private-docs': {
|
|
286
|
+
* prefix: 'documents/',
|
|
287
|
+
* clientUploads: true
|
|
288
|
+
* }
|
|
289
|
+
* }
|
|
290
|
+
* })
|
|
291
|
+
* ```
|
|
292
|
+
*/
|
|
293
|
+
interface VercelBlobStorageConfig extends StoragePluginConfig {
|
|
294
|
+
/** Enable/disable this storage plugin (default: true). */
|
|
295
|
+
enabled?: boolean;
|
|
296
|
+
/** Collections this plugin handles. */
|
|
297
|
+
collections: Record<string, boolean | CollectionStorageConfig>;
|
|
298
|
+
/**
|
|
299
|
+
* Vercel Blob read/write token.
|
|
300
|
+
*
|
|
301
|
+
* If not provided, falls back to `BLOB_READ_WRITE_TOKEN` environment variable.
|
|
302
|
+
* Get your token from: Vercel Dashboard > Storage > Blob > Tokens
|
|
303
|
+
*
|
|
304
|
+
* @example
|
|
305
|
+
* ```typescript
|
|
306
|
+
* token: process.env.BLOB_READ_WRITE_TOKEN
|
|
307
|
+
* ```
|
|
308
|
+
*/
|
|
309
|
+
token?: string;
|
|
310
|
+
/**
|
|
311
|
+
* Add a random suffix to uploaded filenames.
|
|
312
|
+
* Prevents filename collisions when uploading files with the same name.
|
|
313
|
+
*
|
|
314
|
+
* When true, `photo.jpg` becomes `photo-abc123.jpg`
|
|
315
|
+
*
|
|
316
|
+
* @default true
|
|
317
|
+
*/
|
|
318
|
+
addRandomSuffix?: boolean;
|
|
319
|
+
/**
|
|
320
|
+
* Cache-Control max-age in seconds.
|
|
321
|
+
* Controls how long browsers and CDNs cache the files.
|
|
322
|
+
*
|
|
323
|
+
* Note: Vercel Blob has a minimum cache time of 1 minute.
|
|
324
|
+
*
|
|
325
|
+
* @default 31536000 (1 year)
|
|
326
|
+
*/
|
|
327
|
+
cacheControlMaxAge?: number;
|
|
328
|
+
/**
|
|
329
|
+
* Access level for uploaded blobs.
|
|
330
|
+
* Vercel Blob only supports 'public' access.
|
|
331
|
+
*
|
|
332
|
+
* @default 'public'
|
|
333
|
+
*/
|
|
334
|
+
access?: "public";
|
|
335
|
+
/**
|
|
336
|
+
* Store ID for multi-store setups.
|
|
337
|
+
* Required if you have multiple blob stores in your Vercel project.
|
|
338
|
+
*
|
|
339
|
+
* Get the store ID from: Vercel Dashboard > Storage > Blob > Settings
|
|
340
|
+
*/
|
|
341
|
+
storeId?: string;
|
|
342
|
+
/**
|
|
343
|
+
* Whether to allow overwriting existing blobs.
|
|
344
|
+
* When false, uploading a file with the same path will throw an error.
|
|
345
|
+
*
|
|
346
|
+
* Note: Only relevant when `addRandomSuffix` is false.
|
|
347
|
+
*
|
|
348
|
+
* @default false
|
|
349
|
+
*/
|
|
350
|
+
allowOverwrite?: boolean;
|
|
351
|
+
/**
|
|
352
|
+
* Multipart upload threshold in bytes.
|
|
353
|
+
* Files larger than this will be uploaded using multipart upload.
|
|
354
|
+
*
|
|
355
|
+
* @default 5242880 (5MB)
|
|
356
|
+
*/
|
|
357
|
+
multipartThreshold?: number;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Vercel Blob-specific collection storage configuration.
|
|
361
|
+
* Extends base collection config with Vercel Blob-specific options.
|
|
362
|
+
*
|
|
363
|
+
* @example
|
|
364
|
+
* ```typescript
|
|
365
|
+
* vercelBlobStorage({
|
|
366
|
+
* collections: {
|
|
367
|
+
* // Simple enable with defaults
|
|
368
|
+
* media: true,
|
|
369
|
+
*
|
|
370
|
+
* // Full configuration
|
|
371
|
+
* documents: {
|
|
372
|
+
* prefix: 'docs/',
|
|
373
|
+
* addRandomSuffix: false,
|
|
374
|
+
* allowOverwrite: true,
|
|
375
|
+
* clientUploads: true
|
|
376
|
+
* }
|
|
377
|
+
* }
|
|
378
|
+
* })
|
|
379
|
+
* ```
|
|
380
|
+
*/
|
|
381
|
+
interface VercelBlobCollectionConfig extends CollectionStorageConfig {
|
|
382
|
+
/**
|
|
383
|
+
* Override addRandomSuffix for this collection.
|
|
384
|
+
* If not set, uses the adapter-level setting.
|
|
385
|
+
*/
|
|
386
|
+
addRandomSuffix?: boolean;
|
|
387
|
+
/**
|
|
388
|
+
* Override allowOverwrite for this collection.
|
|
389
|
+
* If not set, uses the adapter-level setting.
|
|
390
|
+
*/
|
|
391
|
+
allowOverwrite?: boolean;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Type-safe collection storage map for Vercel Blob.
|
|
395
|
+
* Maps collection slugs to Vercel Blob-specific configurations.
|
|
396
|
+
*/
|
|
397
|
+
type VercelBlobCollectionStorageMap = Record<string, boolean | VercelBlobCollectionConfig>;
|
|
398
|
+
/**
|
|
399
|
+
* Resolved Vercel Blob configuration after applying defaults.
|
|
400
|
+
* Used internally by the adapter.
|
|
401
|
+
*
|
|
402
|
+
* @internal
|
|
403
|
+
*/
|
|
404
|
+
interface ResolvedVercelBlobConfig {
|
|
405
|
+
token: string;
|
|
406
|
+
addRandomSuffix: boolean;
|
|
407
|
+
cacheControlMaxAge: number;
|
|
408
|
+
access: "public";
|
|
409
|
+
storeId?: string;
|
|
410
|
+
allowOverwrite: boolean;
|
|
411
|
+
multipartThreshold: number;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Vercel Blob Storage Plugin
|
|
416
|
+
*
|
|
417
|
+
* Factory function that creates a storage plugin for Vercel Blob Storage.
|
|
418
|
+
* Returns a StoragePlugin that can be registered with MediaStorage.
|
|
419
|
+
*
|
|
420
|
+
* @example Basic usage
|
|
421
|
+
* ```typescript
|
|
422
|
+
* import { vercelBlobStorage } from '@nextly/storage-vercel-blob'
|
|
423
|
+
* import { defineConfig } from 'nextly/config'
|
|
424
|
+
*
|
|
425
|
+
* export default defineConfig({
|
|
426
|
+
* storage: [
|
|
427
|
+
* vercelBlobStorage({
|
|
428
|
+
* token: process.env.BLOB_READ_WRITE_TOKEN,
|
|
429
|
+
* collections: {
|
|
430
|
+
* media: true
|
|
431
|
+
* }
|
|
432
|
+
* })
|
|
433
|
+
* ]
|
|
434
|
+
* })
|
|
435
|
+
* ```
|
|
436
|
+
*
|
|
437
|
+
* @example With client uploads (recommended for Vercel)
|
|
438
|
+
* ```typescript
|
|
439
|
+
* vercelBlobStorage({
|
|
440
|
+
* collections: {
|
|
441
|
+
* media: {
|
|
442
|
+
* clientUploads: true // Bypass 4.5MB serverless limit
|
|
443
|
+
* }
|
|
444
|
+
* }
|
|
445
|
+
* })
|
|
446
|
+
* ```
|
|
447
|
+
*
|
|
448
|
+
* @example With collection-specific configuration
|
|
449
|
+
* ```typescript
|
|
450
|
+
* vercelBlobStorage({
|
|
451
|
+
* addRandomSuffix: true,
|
|
452
|
+
* cacheControlMaxAge: 86400, // 1 day
|
|
453
|
+
* collections: {
|
|
454
|
+
* // Simple enable with defaults
|
|
455
|
+
* media: true,
|
|
456
|
+
*
|
|
457
|
+
* // Full configuration
|
|
458
|
+
* documents: {
|
|
459
|
+
* prefix: 'docs/',
|
|
460
|
+
* addRandomSuffix: false,
|
|
461
|
+
* allowOverwrite: true
|
|
462
|
+
* }
|
|
463
|
+
* }
|
|
464
|
+
* })
|
|
465
|
+
* ```
|
|
466
|
+
*
|
|
467
|
+
* @packageDocumentation
|
|
468
|
+
*/
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Create a Vercel Blob storage plugin for Nextly.
|
|
472
|
+
*
|
|
473
|
+
* This factory function creates a StoragePlugin that can be added to
|
|
474
|
+
* the `storage` array in `nextly.config.ts`. Vercel Blob is optimized
|
|
475
|
+
* for Vercel deployments with:
|
|
476
|
+
*
|
|
477
|
+
* - Global CDN distribution
|
|
478
|
+
* - Simple token-based authentication
|
|
479
|
+
* - Client-side upload support (via handleUpload API)
|
|
480
|
+
* - Automatic file management
|
|
481
|
+
*
|
|
482
|
+
* @param config - Vercel Blob storage configuration
|
|
483
|
+
* @returns A StoragePlugin that MediaStorage can register
|
|
484
|
+
*
|
|
485
|
+
* @throws Error if token is not provided (via adapter)
|
|
486
|
+
*/
|
|
487
|
+
declare function vercelBlobStorage(config: VercelBlobStorageConfig): StoragePlugin;
|
|
488
|
+
|
|
489
|
+
/**
|
|
490
|
+
* Vercel Blob Storage Adapter
|
|
491
|
+
*
|
|
492
|
+
* Stores files on Vercel Blob Storage, a globally distributed object storage
|
|
493
|
+
* service powered by Cloudflare R2. Optimized for Vercel deployments with
|
|
494
|
+
* built-in CDN distribution.
|
|
495
|
+
*
|
|
496
|
+
* Features:
|
|
497
|
+
* - Globally distributed CDN-backed storage
|
|
498
|
+
* - Automatic file management with unique filenames
|
|
499
|
+
* - Token-based authentication
|
|
500
|
+
* - Client-side upload support (via handleUpload API)
|
|
501
|
+
* - Serverless-friendly (no filesystem access required)
|
|
502
|
+
*
|
|
503
|
+
* @example Basic usage
|
|
504
|
+
* ```typescript
|
|
505
|
+
* const adapter = new VercelBlobStorageAdapter({
|
|
506
|
+
* token: process.env.BLOB_READ_WRITE_TOKEN,
|
|
507
|
+
* collections: { media: true }
|
|
508
|
+
* });
|
|
509
|
+
*
|
|
510
|
+
* const result = await adapter.upload(buffer, {
|
|
511
|
+
* filename: 'photo.jpg',
|
|
512
|
+
* mimeType: 'image/jpeg'
|
|
513
|
+
* });
|
|
514
|
+
* // result.url: "https://abc123.public.blob.vercel-storage.com/photo-xyz.jpg"
|
|
515
|
+
* ```
|
|
516
|
+
*/
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Vercel Blob Storage Adapter
|
|
520
|
+
*
|
|
521
|
+
* Implements the IStorageAdapter interface for Vercel Blob Storage.
|
|
522
|
+
* Provides file upload, deletion, existence checks, and metadata retrieval.
|
|
523
|
+
*/
|
|
524
|
+
declare class VercelBlobStorageAdapter implements IStorageAdapter {
|
|
525
|
+
private config;
|
|
526
|
+
private resolvedConfig;
|
|
527
|
+
/**
|
|
528
|
+
* Create a new Vercel Blob storage adapter.
|
|
529
|
+
*
|
|
530
|
+
* @param config - Vercel Blob storage configuration
|
|
531
|
+
* @throws Error if token is not provided
|
|
532
|
+
*/
|
|
533
|
+
constructor(config: VercelBlobStorageConfig);
|
|
534
|
+
/**
|
|
535
|
+
* Upload file to Vercel Blob.
|
|
536
|
+
*
|
|
537
|
+
* Files are stored with globally distributed CDN backing.
|
|
538
|
+
* By default, a random suffix is added to prevent filename collisions.
|
|
539
|
+
*
|
|
540
|
+
* @param buffer - File content as Buffer
|
|
541
|
+
* @param options - Upload options (filename, mimeType, folder, collection)
|
|
542
|
+
* @returns Upload result with URL and storage path
|
|
543
|
+
*/
|
|
544
|
+
upload(buffer: Buffer, options: UploadOptions): Promise<UploadResult>;
|
|
545
|
+
/**
|
|
546
|
+
* Delete file from Vercel Blob.
|
|
547
|
+
*
|
|
548
|
+
* @param filePath - Full blob URL to delete
|
|
549
|
+
*/
|
|
550
|
+
delete(filePath: string): Promise<void>;
|
|
551
|
+
/**
|
|
552
|
+
* Delete multiple files from Vercel Blob in chunked batches.
|
|
553
|
+
*
|
|
554
|
+
* Processes deletions in chunks of 10 concurrent calls using Promise.allSettled,
|
|
555
|
+
* collecting successes and failures without short-circuiting.
|
|
556
|
+
*
|
|
557
|
+
* @param filePaths - Array of full blob URLs to delete
|
|
558
|
+
* @returns BulkDeleteResult with successful and failed arrays
|
|
559
|
+
*/
|
|
560
|
+
bulkDelete(filePaths: string[]): Promise<BulkDeleteResult>;
|
|
561
|
+
/**
|
|
562
|
+
* Check if file exists in Vercel Blob.
|
|
563
|
+
*
|
|
564
|
+
* Uses the head() function which is efficient for existence checks.
|
|
565
|
+
*
|
|
566
|
+
* @param filePath - Full blob URL to check
|
|
567
|
+
* @returns true if file exists, false otherwise
|
|
568
|
+
*/
|
|
569
|
+
exists(filePath: string): Promise<boolean>;
|
|
570
|
+
/**
|
|
571
|
+
* Get public URL for Vercel Blob file.
|
|
572
|
+
*
|
|
573
|
+
* Vercel Blob paths ARE the public URLs - they're already full HTTPS URLs
|
|
574
|
+
* with CDN distribution.
|
|
575
|
+
*
|
|
576
|
+
* @param filePath - Full blob URL
|
|
577
|
+
* @returns The same URL (Vercel Blob paths are already public URLs)
|
|
578
|
+
*/
|
|
579
|
+
getPublicUrl(filePath: string): string;
|
|
580
|
+
/**
|
|
581
|
+
* Get storage type identifier.
|
|
582
|
+
*/
|
|
583
|
+
getType(): "vercel-blob";
|
|
584
|
+
/**
|
|
585
|
+
* Get adapter info including capabilities.
|
|
586
|
+
*
|
|
587
|
+
* @returns Adapter info with type, name, and capability flags
|
|
588
|
+
*/
|
|
589
|
+
getInfo(): StorageAdapterInfo;
|
|
590
|
+
/**
|
|
591
|
+
* Get file metadata from Vercel Blob.
|
|
592
|
+
*
|
|
593
|
+
* Retrieves file information including size, content type, and upload date.
|
|
594
|
+
*
|
|
595
|
+
* @param filePath - Full blob URL
|
|
596
|
+
* @returns File metadata or null if file not found
|
|
597
|
+
*/
|
|
598
|
+
getMetadata(filePath: string): Promise<FileMetadata | null>;
|
|
599
|
+
/**
|
|
600
|
+
* Generate pre-signed URL for client-side uploads.
|
|
601
|
+
*
|
|
602
|
+
* Vercel Blob uses a different pattern for client uploads - they require
|
|
603
|
+
* the handleUpload API which handles token generation server-side.
|
|
604
|
+
*
|
|
605
|
+
* This method is not implemented because Vercel Blob's client upload flow
|
|
606
|
+
* requires server-side route handlers with handleUpload().
|
|
607
|
+
*
|
|
608
|
+
* @see https://vercel.com/docs/storage/vercel-blob/client-upload
|
|
609
|
+
* @throws Error always - client uploads require handleUpload API
|
|
610
|
+
*/
|
|
611
|
+
getPresignedUploadUrl(_key: string, _mimeType: string, _expiresIn?: number): Promise<ClientUploadData>;
|
|
612
|
+
/**
|
|
613
|
+
* List blobs with optional prefix filter.
|
|
614
|
+
*
|
|
615
|
+
* Useful for browsing or batch operations on stored files.
|
|
616
|
+
*
|
|
617
|
+
* @param prefix - Optional prefix to filter results
|
|
618
|
+
* @param options - List options (limit, cursor)
|
|
619
|
+
* @returns List of blob metadata
|
|
620
|
+
*/
|
|
621
|
+
listBlobs(prefix?: string, options?: {
|
|
622
|
+
limit?: number;
|
|
623
|
+
cursor?: string;
|
|
624
|
+
}): Promise<{
|
|
625
|
+
blobs: Array<{
|
|
626
|
+
url: string;
|
|
627
|
+
pathname: string;
|
|
628
|
+
size: number;
|
|
629
|
+
uploadedAt: Date;
|
|
630
|
+
}>;
|
|
631
|
+
hasMore: boolean;
|
|
632
|
+
cursor?: string;
|
|
633
|
+
}>;
|
|
634
|
+
/**
|
|
635
|
+
* Build pathname for Vercel Blob upload.
|
|
636
|
+
*
|
|
637
|
+
* Creates pathname in format: {folder}/{filename}
|
|
638
|
+
* If no folder is provided, uses the filename directly.
|
|
639
|
+
*
|
|
640
|
+
* @param filename - Original filename (will be sanitized)
|
|
641
|
+
* @param folder - Optional folder/prefix for organizing uploads
|
|
642
|
+
* @returns Generated pathname
|
|
643
|
+
* @throws Error if folder fails sanitization
|
|
644
|
+
*/
|
|
645
|
+
private buildPathname;
|
|
646
|
+
/**
|
|
647
|
+
* Sanitize filename to prevent path traversal issues.
|
|
648
|
+
*
|
|
649
|
+
* @param filename - Original filename
|
|
650
|
+
* @returns Sanitized filename
|
|
651
|
+
*/
|
|
652
|
+
private sanitizeFilename;
|
|
653
|
+
/**
|
|
654
|
+
* Reject folder values that would let a caller
|
|
655
|
+
* escape the configured prefix via path traversal or weird
|
|
656
|
+
* separators. Vercel Blob is flat-keyed so `/` is just part of the
|
|
657
|
+
* pathname, but an attacker who controls `folder` could still:
|
|
658
|
+
*
|
|
659
|
+
* - Use `..` to chain a relative-traversal-style key.
|
|
660
|
+
* - Use `\` (Windows separator) to confuse downstream tooling.
|
|
661
|
+
* - Use control chars / null bytes to truncate the key.
|
|
662
|
+
* - Use a leading `/` to look like an absolute path.
|
|
663
|
+
*
|
|
664
|
+
* Internal `/` segments stay allowed because callers legitimately
|
|
665
|
+
* use nested folders (`docs/2026/...`); a leading `/`, empty
|
|
666
|
+
* segments (`docs//x`), or `..` segments are rejected.
|
|
667
|
+
*
|
|
668
|
+
* @throws Error with a stable message so the storage facade can
|
|
669
|
+
* surface a 400 instead of a 500.
|
|
670
|
+
*/
|
|
671
|
+
private sanitizeFolder;
|
|
672
|
+
/**
|
|
673
|
+
* Get the configured token (masked for logging).
|
|
674
|
+
*/
|
|
675
|
+
getTokenMasked(): string;
|
|
676
|
+
/**
|
|
677
|
+
* Check if random suffix is enabled.
|
|
678
|
+
*/
|
|
679
|
+
hasRandomSuffix(): boolean;
|
|
680
|
+
/**
|
|
681
|
+
* Get configured cache control max age.
|
|
682
|
+
*/
|
|
683
|
+
getCacheControlMaxAge(): number;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* @nextly/storage-vercel-blob
|
|
688
|
+
*
|
|
689
|
+
* Vercel Blob storage adapter for Nextly CMS.
|
|
690
|
+
* Optimized for Vercel deployments with client-side upload support
|
|
691
|
+
* to bypass serverless function size limits (4.5MB).
|
|
692
|
+
*
|
|
693
|
+
* @example Basic usage
|
|
694
|
+
* ```typescript
|
|
695
|
+
* import { vercelBlobStorage } from '@nextly/storage-vercel-blob'
|
|
696
|
+
* import { defineConfig } from 'nextly/config'
|
|
697
|
+
*
|
|
698
|
+
* export default defineConfig({
|
|
699
|
+
* storage: [
|
|
700
|
+
* vercelBlobStorage({
|
|
701
|
+
* token: process.env.BLOB_READ_WRITE_TOKEN,
|
|
702
|
+
* collections: {
|
|
703
|
+
* media: true
|
|
704
|
+
* }
|
|
705
|
+
* })
|
|
706
|
+
* ]
|
|
707
|
+
* })
|
|
708
|
+
* ```
|
|
709
|
+
*
|
|
710
|
+
* @example With client uploads (recommended for Vercel)
|
|
711
|
+
* ```typescript
|
|
712
|
+
* vercelBlobStorage({
|
|
713
|
+
* collections: {
|
|
714
|
+
* media: {
|
|
715
|
+
* clientUploads: true // Bypass 4.5MB serverless limit
|
|
716
|
+
* }
|
|
717
|
+
* }
|
|
718
|
+
* })
|
|
719
|
+
* ```
|
|
720
|
+
*
|
|
721
|
+
* @packageDocumentation
|
|
722
|
+
*/
|
|
723
|
+
|
|
724
|
+
declare const PACKAGE_NAME = "@nextly/storage-vercel-blob";
|
|
725
|
+
declare const PACKAGE_VERSION = "0.1.0";
|
|
726
|
+
|
|
727
|
+
export { PACKAGE_NAME, PACKAGE_VERSION, type ResolvedVercelBlobConfig, type VercelBlobCollectionConfig, type VercelBlobCollectionStorageMap, VercelBlobStorageAdapter, type VercelBlobStorageConfig, vercelBlobStorage };
|