@nextlyhq/storage-vercel-blob 0.0.2-alpha.7 → 0.0.2-alpha.71

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/index.d.ts CHANGED
@@ -9,24 +9,24 @@
9
9
  * - Vercel Blob (via @nextlyhq/storage-vercel-blob)
10
10
  */
11
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";
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
24
  }
25
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;
26
+ /** Public URL to access the file */
27
+ url: string;
28
+ /** Storage path/key (for deletion and metadata retrieval) */
29
+ path: string;
30
30
  }
31
31
  /**
32
32
  * Extended file metadata returned by getMetadata()
@@ -35,28 +35,28 @@ interface UploadResult {
35
35
  * including dimensions for images and creation timestamps.
36
36
  */
37
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;
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
60
  }
61
61
  /**
62
62
  * Storage type identifier.
@@ -70,29 +70,49 @@ type StorageType = "s3" | "vercel-blob" | "local" | "uploadthing";
70
70
  * Information about a storage adapter's capabilities.
71
71
  * Returned by adapter.getInfo() method.
72
72
  */
73
+ /**
74
+ * Bounds for a read that pulls an object into memory.
75
+ *
76
+ * Reading a stored object means BUFFERING it, so its size is the caller's
77
+ * problem whether or not the caller says so. Left unbounded, a large object
78
+ * consumes as much process memory as it happens to occupy, and a stalled
79
+ * backend holds the request until something further out gives up.
80
+ *
81
+ * OPTIONAL, with the defaults living in the fetch layer rather than being
82
+ * restated per adapter — a second set of numbers agrees today and drifts. What
83
+ * this type exists for is the caller that already knows its own limit: the
84
+ * email attachment path caps a read at the configured attachment size, so an
85
+ * oversized object fails as a size error rather than after it has been read.
86
+ */
87
+ interface StorageReadOptions {
88
+ /** Refuse once this many bytes have arrived. Defaults to the fetch cap. */
89
+ maxBytes?: number;
90
+ /** Give up after this long. Defaults to the fetch deadline. */
91
+ timeoutMs?: number;
92
+ }
73
93
  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;
94
+ /** Storage type identifier */
95
+ type: StorageType;
96
+ /** Human-readable adapter name */
97
+ name: string;
98
+ /** Whether this adapter supports signed URLs for private access */
99
+ supportsSignedUrls: boolean;
100
+ /** Whether this adapter supports client-side (direct) uploads */
101
+ supportsClientUploads: boolean;
82
102
  }
83
103
  /**
84
104
  * Per-collection storage configuration.
85
105
  * Allows customizing storage behavior for specific upload collections.
86
106
  */
87
107
  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;
108
+ /** Prefix/folder for this collection's uploads */
109
+ prefix?: string;
110
+ /** Enable client-side uploads (for serverless platforms with body size limits) */
111
+ clientUploads?: boolean;
112
+ /** Generate signed URLs for downloads (for private buckets) */
113
+ signedDownloads?: boolean;
114
+ /** Signed URL expiry time in seconds (default: 3600) */
115
+ signedUrlExpiresIn?: number;
96
116
  }
97
117
  /**
98
118
  * Collection storage map - maps collection slugs to their config.
@@ -116,10 +136,10 @@ type CollectionStorageMap = Record<string, boolean | CollectionStorageConfig>;
116
136
  * Extended by specific adapter configs (S3StorageConfig, etc.)
117
137
  */
118
138
  interface StoragePluginConfig {
119
- /** Enable/disable the plugin (default: true) */
120
- enabled?: boolean;
121
- /** Collections to apply this storage adapter to */
122
- collections: CollectionStorageMap;
139
+ /** Enable/disable the plugin (default: true) */
140
+ enabled?: boolean;
141
+ /** Collections to apply this storage adapter to */
142
+ collections: CollectionStorageMap;
123
143
  }
124
144
  /**
125
145
  * Storage plugin returned by adapter plugin functions.
@@ -137,24 +157,24 @@ interface StoragePluginConfig {
137
157
  * ```
138
158
  */
139
159
  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>;
160
+ /** Plugin name for identification */
161
+ name: string;
162
+ /** Storage type */
163
+ type: StorageType;
164
+ /** Collections this plugin handles */
165
+ collections: CollectionStorageMap;
166
+ /** The storage adapter instance */
167
+ adapter: IStorageAdapter;
168
+ /**
169
+ * Handler for generating client-side upload URLs.
170
+ * Called when clientUploads is enabled for a collection.
171
+ */
172
+ getClientUploadUrl?: (filename: string, mimeType: string, collection: string) => Promise<ClientUploadData>;
173
+ /**
174
+ * Handler for generating signed download URLs.
175
+ * Called when signedDownloads is enabled for a collection.
176
+ */
177
+ getSignedDownloadUrl?: (path: string, expiresIn?: number) => Promise<string>;
158
178
  }
159
179
  /**
160
180
  * Data returned for client-side (direct) uploads.
@@ -177,18 +197,18 @@ interface StoragePlugin {
177
197
  * ```
178
198
  */
179
199
  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;
200
+ /** Pre-signed URL for direct upload */
201
+ uploadUrl: string;
202
+ /** Storage path/key that will be used */
203
+ path: string;
204
+ /** HTTP method to use (usually PUT for S3, POST for some services) */
205
+ method: "PUT" | "POST";
206
+ /** Headers to include in upload request */
207
+ headers?: Record<string, string>;
208
+ /** Form fields for multipart uploads (some services require this) */
209
+ fields?: Record<string, string>;
210
+ /** URL expiry timestamp */
211
+ expiresAt: Date;
192
212
  }
193
213
  /**
194
214
  * Base storage adapter interface.
@@ -207,47 +227,56 @@ interface ClientUploadData {
207
227
  * - getSignedUrl: Generate temporary signed URLs for private access
208
228
  * - getPresignedUploadUrl: Generate pre-signed URL for client uploads
209
229
  */
230
+ /** A file a storage holds, as {@link IStorageAdapter.list} names it. */
231
+ interface StoredFileEntry {
232
+ /** The stored path, as `upload` returned it and `delete` takes it. */
233
+ path: string;
234
+ /** When it was stored, where the storage says. */
235
+ lastModified?: Date;
236
+ /**
237
+ * The name it was uploaded under, where the storage keeps that apart from
238
+ * its path: UploadThing's keys carry no name. Elsewhere the name is the
239
+ * path's last segment.
240
+ */
241
+ name?: string;
242
+ }
210
243
  interface BulkDeleteResult {
211
- successful: string[];
212
- failed: Array<{
213
- filePath: string;
214
- error: string;
215
- }>;
244
+ successful: string[];
245
+ failed: Array<{
246
+ filePath: string;
247
+ error: string;
248
+ }>;
216
249
  }
217
250
  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>;
251
+ /** Upload file buffer to storage */
252
+ upload(buffer: Buffer, options: UploadOptions): Promise<UploadResult>;
253
+ /** Delete file from storage */
254
+ delete(filePath: string): Promise<void>;
255
+ /** Bulk delete files from storage. Optional — adapters that support batch operations should implement this. */
256
+ bulkDelete?(filePaths: string[]): Promise<BulkDeleteResult>;
257
+ /** Check if file exists in storage */
258
+ exists(filePath: string): Promise<boolean>;
259
+ /** Get public URL for file */
260
+ getPublicUrl(filePath: string): string;
261
+ /** Get storage type identifier */
262
+ getType(): string;
263
+ /** Read file contents from storage (optional - not all adapters support this) */
264
+ read?(filePath: string, options?: StorageReadOptions): Promise<Buffer | null>;
265
+ /** Get adapter info including capabilities (optional but recommended) */
266
+ getInfo?(): StorageAdapterInfo;
267
+ /** Get file metadata (optional - not all adapters support this) */
268
+ getMetadata?(filePath: string): Promise<FileMetadata | null>;
269
+ /** Generate signed URL for temporary private access (optional) */
270
+ getSignedUrl?(filePath: string, expiresIn?: number): Promise<string>;
271
+ /** Generate pre-signed upload URL for client-side uploads (optional) */
272
+ getPresignedUploadUrl?(key: string, mimeType: string, expiresIn?: number): Promise<ClientUploadData>;
273
+ /**
274
+ * Every file the storage holds, a page at a time (optional). Used to find
275
+ * files no record refers to; a storage without it cannot be searched for
276
+ * them, which is said rather than taken as "none".
277
+ */
278
+ list?(): AsyncIterable<StoredFileEntry>;
240
279
  }
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
280
  /**
252
281
  * Vercel Blob storage adapter configuration.
253
282
  *
@@ -291,70 +320,70 @@ interface IStorageAdapter {
291
320
  * ```
292
321
  */
293
322
  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;
323
+ /** Enable/disable this storage plugin (default: true). */
324
+ enabled?: boolean;
325
+ /** Collections this plugin handles. */
326
+ collections: Record<string, boolean | CollectionStorageConfig>;
327
+ /**
328
+ * Vercel Blob read/write token.
329
+ *
330
+ * If not provided, falls back to `BLOB_READ_WRITE_TOKEN` environment variable.
331
+ * Get your token from: Vercel Dashboard > Storage > Blob > Tokens
332
+ *
333
+ * @example
334
+ * ```typescript
335
+ * token: process.env.BLOB_READ_WRITE_TOKEN
336
+ * ```
337
+ */
338
+ token?: string;
339
+ /**
340
+ * Add a random suffix to uploaded filenames.
341
+ * Prevents filename collisions when uploading files with the same name.
342
+ *
343
+ * When true, `photo.jpg` becomes `photo-abc123.jpg`
344
+ *
345
+ * @default true
346
+ */
347
+ addRandomSuffix?: boolean;
348
+ /**
349
+ * Cache-Control max-age in seconds.
350
+ * Controls how long browsers and CDNs cache the files.
351
+ *
352
+ * Note: Vercel Blob has a minimum cache time of 1 minute.
353
+ *
354
+ * @default 31536000 (1 year)
355
+ */
356
+ cacheControlMaxAge?: number;
357
+ /**
358
+ * Access level for uploaded blobs.
359
+ * Vercel Blob only supports 'public' access.
360
+ *
361
+ * @default 'public'
362
+ */
363
+ access?: "public";
364
+ /**
365
+ * Store ID for multi-store setups.
366
+ * Required if you have multiple blob stores in your Vercel project.
367
+ *
368
+ * Get the store ID from: Vercel Dashboard > Storage > Blob > Settings
369
+ */
370
+ storeId?: string;
371
+ /**
372
+ * Whether to allow overwriting existing blobs.
373
+ * When false, uploading a file with the same path will throw an error.
374
+ *
375
+ * Note: Only relevant when `addRandomSuffix` is false.
376
+ *
377
+ * @default false
378
+ */
379
+ allowOverwrite?: boolean;
380
+ /**
381
+ * Multipart upload threshold in bytes.
382
+ * Files larger than this will be uploaded using multipart upload.
383
+ *
384
+ * @default 5242880 (5MB)
385
+ */
386
+ multipartThreshold?: number;
358
387
  }
359
388
  /**
360
389
  * Vercel Blob-specific collection storage configuration.
@@ -379,16 +408,16 @@ interface VercelBlobStorageConfig extends StoragePluginConfig {
379
408
  * ```
380
409
  */
381
410
  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;
411
+ /**
412
+ * Override addRandomSuffix for this collection.
413
+ * If not set, uses the adapter-level setting.
414
+ */
415
+ addRandomSuffix?: boolean;
416
+ /**
417
+ * Override allowOverwrite for this collection.
418
+ * If not set, uses the adapter-level setting.
419
+ */
420
+ allowOverwrite?: boolean;
392
421
  }
393
422
  /**
394
423
  * Type-safe collection storage map for Vercel Blob.
@@ -402,71 +431,14 @@ type VercelBlobCollectionStorageMap = Record<string, boolean | VercelBlobCollect
402
431
  * @internal
403
432
  */
404
433
  interface ResolvedVercelBlobConfig {
405
- token: string;
406
- addRandomSuffix: boolean;
407
- cacheControlMaxAge: number;
408
- access: "public";
409
- storeId?: string;
410
- allowOverwrite: boolean;
411
- multipartThreshold: number;
434
+ token: string;
435
+ addRandomSuffix: boolean;
436
+ cacheControlMaxAge: number;
437
+ access: "public";
438
+ storeId?: string;
439
+ allowOverwrite: boolean;
440
+ multipartThreshold: number;
412
441
  }
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
442
  /**
471
443
  * Create a Vercel Blob storage plugin for Nextly.
472
444
  *
@@ -484,244 +456,200 @@ interface ResolvedVercelBlobConfig {
484
456
  *
485
457
  * @throws Error if token is not provided (via adapter)
486
458
  */
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
-
459
+ export declare function vercelBlobStorage(config: VercelBlobStorageConfig): StoragePlugin;
518
460
  /**
519
461
  * Vercel Blob Storage Adapter
520
462
  *
521
463
  * Implements the IStorageAdapter interface for Vercel Blob Storage.
522
464
  * Provides file upload, deletion, existence checks, and metadata retrieval.
523
465
  */
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;
466
+ export declare class VercelBlobStorageAdapter implements IStorageAdapter {
467
+ private config;
468
+ private resolvedConfig;
469
+ /**
470
+ * Create a new Vercel Blob storage adapter.
471
+ *
472
+ * @param config - Vercel Blob storage configuration
473
+ * @throws Error if token is not provided
474
+ */
475
+ constructor(config: VercelBlobStorageConfig);
476
+ /**
477
+ * Upload file to Vercel Blob.
478
+ *
479
+ * Files are stored with globally distributed CDN backing.
480
+ * By default, a random suffix is added to prevent filename collisions.
481
+ *
482
+ * @param buffer - File content as Buffer
483
+ * @param options - Upload options (filename, mimeType, folder, collection)
484
+ * @returns Upload result with URL and storage path
485
+ */
486
+ upload(buffer: Buffer, options: UploadOptions): Promise<UploadResult>;
487
+ /**
488
+ * Delete file from Vercel Blob.
489
+ *
490
+ * @param filePath - Full blob URL to delete
491
+ */
492
+ delete(filePath: string): Promise<void>;
493
+ /**
494
+ * Delete multiple files from Vercel Blob in chunked batches.
495
+ *
496
+ * Processes deletions in chunks of 10 concurrent calls using Promise.allSettled,
497
+ * collecting successes and failures without short-circuiting.
498
+ *
499
+ * @param filePaths - Array of full blob URLs to delete
500
+ * @returns BulkDeleteResult with successful and failed arrays
501
+ */
502
+ bulkDelete(filePaths: string[]): Promise<BulkDeleteResult>;
503
+ /**
504
+ * Check if file exists in Vercel Blob.
505
+ *
506
+ * Uses the head() function which is efficient for existence checks.
507
+ *
508
+ * @param filePath - Full blob URL to check
509
+ * @returns true if file exists, false otherwise
510
+ */
511
+ exists(filePath: string): Promise<boolean>;
512
+ /**
513
+ * Read a stored blob back as bytes, or `null` when it is not there.
514
+ *
515
+ * A NETWORK round trip, unlike the local adapter's disk read, because the
516
+ * bytes live on Vercel's CDN and there is no other way to reach them. That
517
+ * cost is the reason a caller serving these from its own origin has to cache:
518
+ * the privacy rule that forces same-origin serving does not also make the
519
+ * fetch free, and an uncached proxy pays it on every request.
520
+ *
521
+ * The URL is taken from `head` rather than assembled from `filePath`,
522
+ * because a blob's address is issued by the service — it carries a random
523
+ * suffix this adapter never chose — so deriving one here would be guessing at
524
+ * a string another system owns.
525
+ *
526
+ * `null` for a missing blob rather than a throw, matching {@link exists}.
527
+ *
528
+ * @param filePath - Blob path/key
529
+ * @returns The blob's bytes, or `null` when it does not exist
530
+ */
531
+ read(filePath: string, options?: StorageReadOptions): Promise<Buffer | null>;
532
+ /**
533
+ * Get public URL for Vercel Blob file.
534
+ *
535
+ * Vercel Blob paths ARE the public URLs - they're already full HTTPS URLs
536
+ * with CDN distribution.
537
+ *
538
+ * @param filePath - Full blob URL
539
+ * @returns The same URL (Vercel Blob paths are already public URLs)
540
+ */
541
+ /**
542
+ * Every blob in the store, by its URL (which is its stored path here), with
543
+ * when it was uploaded, a page at a time.
544
+ */
545
+ list(): AsyncIterable<StoredFileEntry>;
546
+ getPublicUrl(filePath: string): string;
547
+ /**
548
+ * Get storage type identifier.
549
+ */
550
+ getType(): "vercel-blob";
551
+ /**
552
+ * Get adapter info including capabilities.
553
+ *
554
+ * @returns Adapter info with type, name, and capability flags
555
+ */
556
+ getInfo(): StorageAdapterInfo;
557
+ /**
558
+ * Get file metadata from Vercel Blob.
559
+ *
560
+ * Retrieves file information including size, content type, and upload date.
561
+ *
562
+ * @param filePath - Full blob URL
563
+ * @returns File metadata or null if file not found
564
+ */
565
+ getMetadata(filePath: string): Promise<FileMetadata | null>;
566
+ /**
567
+ * Generate pre-signed URL for client-side uploads.
568
+ *
569
+ * Vercel Blob uses a different pattern for client uploads - they require
570
+ * the handleUpload API which handles token generation server-side.
571
+ *
572
+ * This method is not implemented because Vercel Blob's client upload flow
573
+ * requires server-side route handlers with handleUpload().
574
+ *
575
+ * @see https://vercel.com/docs/storage/vercel-blob/client-upload
576
+ * @throws Error always - client uploads require handleUpload API
577
+ */
578
+ getPresignedUploadUrl(_key: string, _mimeType: string, _expiresIn?: number): Promise<ClientUploadData>;
579
+ /**
580
+ * List blobs with optional prefix filter.
581
+ *
582
+ * Useful for browsing or batch operations on stored files.
583
+ *
584
+ * @param prefix - Optional prefix to filter results
585
+ * @param options - List options (limit, cursor)
586
+ * @returns List of blob metadata
587
+ */
588
+ listBlobs(prefix?: string, options?: {
589
+ limit?: number;
590
+ cursor?: string;
591
+ }): Promise<{
592
+ blobs: Array<{
593
+ url: string;
594
+ pathname: string;
595
+ size: number;
596
+ uploadedAt: Date;
633
597
  }>;
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;
598
+ hasMore: boolean;
599
+ cursor?: string;
600
+ }>;
601
+ /**
602
+ * Build pathname for Vercel Blob upload.
603
+ *
604
+ * Creates pathname in format: {folder}/{filename}
605
+ * If no folder is provided, uses the filename directly.
606
+ *
607
+ * @param filename - Original filename (will be sanitized)
608
+ * @param folder - Optional folder/prefix for organizing uploads
609
+ * @returns Generated pathname
610
+ * @throws Error if folder fails sanitization
611
+ */
612
+ private buildPathname;
613
+ /**
614
+ * Sanitize filename to prevent path traversal issues.
615
+ *
616
+ * @param filename - Original filename
617
+ * @returns Sanitized filename
618
+ */
619
+ private sanitizeFilename;
620
+ /**
621
+ * Reject folder values that would let a caller
622
+ * escape the configured prefix via path traversal or weird
623
+ * separators. Vercel Blob is flat-keyed so `/` is just part of the
624
+ * pathname, but an attacker who controls `folder` could still:
625
+ *
626
+ * - Use `..` to chain a relative-traversal-style key.
627
+ * - Use `\` (Windows separator) to confuse downstream tooling.
628
+ * - Use control chars / null bytes to truncate the key.
629
+ * - Use a leading `/` to look like an absolute path.
630
+ *
631
+ * Internal `/` segments stay allowed because callers legitimately
632
+ * use nested folders (`docs/2026/...`); a leading `/`, empty
633
+ * segments (`docs//x`), or `..` segments are rejected.
634
+ *
635
+ * @throws Error with a stable message so the storage facade can
636
+ * surface a 400 instead of a 500.
637
+ */
638
+ private sanitizeFolder;
639
+ /**
640
+ * Get the configured token (masked for logging).
641
+ */
642
+ getTokenMasked(): string;
643
+ /**
644
+ * Check if random suffix is enabled.
645
+ */
646
+ hasRandomSuffix(): boolean;
647
+ /**
648
+ * Get configured cache control max age.
649
+ */
650
+ getCacheControlMaxAge(): number;
684
651
  }
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 };
652
+ export declare const PACKAGE_NAME = "@nextly/storage-vercel-blob";
653
+ export declare const PACKAGE_VERSION = "0.1.0";
654
+ export type { ResolvedVercelBlobConfig, VercelBlobCollectionConfig, VercelBlobCollectionStorageMap, VercelBlobStorageConfig };
655
+ //# sourceMappingURL=index.d.ts.map