@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.
@@ -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 };