@nextlyhq/storage-vercel-blob 0.0.2-alpha.68 → 0.0.2-alpha.69
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/README.md +5 -5
- package/dist/index.cjs +1644 -1457
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +408 -520
- package/dist/index.d.ts +408 -520
- package/dist/index.mjs +1643 -1455
- package/dist/index.mjs.map +1 -1
- package/package.json +13 -17
package/dist/index.d.cts
CHANGED
|
@@ -9,24 +9,24 @@
|
|
|
9
9
|
* - Vercel Blob (via @nextlyhq/storage-vercel-blob)
|
|
10
10
|
*/
|
|
11
11
|
interface UploadOptions {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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.
|
|
@@ -85,34 +85,34 @@ type StorageType = "s3" | "vercel-blob" | "local" | "uploadthing";
|
|
|
85
85
|
* oversized object fails as a size error rather than after it has been read.
|
|
86
86
|
*/
|
|
87
87
|
interface StorageReadOptions {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
92
|
}
|
|
93
93
|
interface StorageAdapterInfo {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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;
|
|
102
102
|
}
|
|
103
103
|
/**
|
|
104
104
|
* Per-collection storage configuration.
|
|
105
105
|
* Allows customizing storage behavior for specific upload collections.
|
|
106
106
|
*/
|
|
107
107
|
interface CollectionStorageConfig {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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;
|
|
116
116
|
}
|
|
117
117
|
/**
|
|
118
118
|
* Collection storage map - maps collection slugs to their config.
|
|
@@ -136,10 +136,10 @@ type CollectionStorageMap = Record<string, boolean | CollectionStorageConfig>;
|
|
|
136
136
|
* Extended by specific adapter configs (S3StorageConfig, etc.)
|
|
137
137
|
*/
|
|
138
138
|
interface StoragePluginConfig {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
139
|
+
/** Enable/disable the plugin (default: true) */
|
|
140
|
+
enabled?: boolean;
|
|
141
|
+
/** Collections to apply this storage adapter to */
|
|
142
|
+
collections: CollectionStorageMap;
|
|
143
143
|
}
|
|
144
144
|
/**
|
|
145
145
|
* Storage plugin returned by adapter plugin functions.
|
|
@@ -157,24 +157,24 @@ interface StoragePluginConfig {
|
|
|
157
157
|
* ```
|
|
158
158
|
*/
|
|
159
159
|
interface StoragePlugin {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
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>;
|
|
178
178
|
}
|
|
179
179
|
/**
|
|
180
180
|
* Data returned for client-side (direct) uploads.
|
|
@@ -197,18 +197,18 @@ interface StoragePlugin {
|
|
|
197
197
|
* ```
|
|
198
198
|
*/
|
|
199
199
|
interface ClientUploadData {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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;
|
|
212
212
|
}
|
|
213
213
|
/**
|
|
214
214
|
* Base storage adapter interface.
|
|
@@ -227,47 +227,56 @@ interface ClientUploadData {
|
|
|
227
227
|
* - getSignedUrl: Generate temporary signed URLs for private access
|
|
228
228
|
* - getPresignedUploadUrl: Generate pre-signed URL for client uploads
|
|
229
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
|
+
}
|
|
230
243
|
interface BulkDeleteResult {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
244
|
+
successful: string[];
|
|
245
|
+
failed: Array<{
|
|
246
|
+
filePath: string;
|
|
247
|
+
error: string;
|
|
248
|
+
}>;
|
|
236
249
|
}
|
|
237
250
|
interface IStorageAdapter {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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>;
|
|
260
279
|
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Vercel Blob Storage Types
|
|
264
|
-
*
|
|
265
|
-
* Type definitions for the @nextly/storage-vercel-blob package.
|
|
266
|
-
* Optimized for Vercel deployments with client-side upload support.
|
|
267
|
-
*
|
|
268
|
-
* @packageDocumentation
|
|
269
|
-
*/
|
|
270
|
-
|
|
271
280
|
/**
|
|
272
281
|
* Vercel Blob storage adapter configuration.
|
|
273
282
|
*
|
|
@@ -311,70 +320,70 @@ interface IStorageAdapter {
|
|
|
311
320
|
* ```
|
|
312
321
|
*/
|
|
313
322
|
interface VercelBlobStorageConfig extends StoragePluginConfig {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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;
|
|
378
387
|
}
|
|
379
388
|
/**
|
|
380
389
|
* Vercel Blob-specific collection storage configuration.
|
|
@@ -399,16 +408,16 @@ interface VercelBlobStorageConfig extends StoragePluginConfig {
|
|
|
399
408
|
* ```
|
|
400
409
|
*/
|
|
401
410
|
interface VercelBlobCollectionConfig extends CollectionStorageConfig {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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;
|
|
412
421
|
}
|
|
413
422
|
/**
|
|
414
423
|
* Type-safe collection storage map for Vercel Blob.
|
|
@@ -422,71 +431,14 @@ type VercelBlobCollectionStorageMap = Record<string, boolean | VercelBlobCollect
|
|
|
422
431
|
* @internal
|
|
423
432
|
*/
|
|
424
433
|
interface ResolvedVercelBlobConfig {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
434
|
+
token: string;
|
|
435
|
+
addRandomSuffix: boolean;
|
|
436
|
+
cacheControlMaxAge: number;
|
|
437
|
+
access: "public";
|
|
438
|
+
storeId?: string;
|
|
439
|
+
allowOverwrite: boolean;
|
|
440
|
+
multipartThreshold: number;
|
|
432
441
|
}
|
|
433
|
-
|
|
434
|
-
/**
|
|
435
|
-
* Vercel Blob Storage Plugin
|
|
436
|
-
*
|
|
437
|
-
* Factory function that creates a storage plugin for Vercel Blob Storage.
|
|
438
|
-
* Returns a StoragePlugin that can be registered with MediaStorage.
|
|
439
|
-
*
|
|
440
|
-
* @example Basic usage
|
|
441
|
-
* ```typescript
|
|
442
|
-
* import { vercelBlobStorage } from '@nextly/storage-vercel-blob'
|
|
443
|
-
* import { defineConfig } from 'nextly/config'
|
|
444
|
-
*
|
|
445
|
-
* export default defineConfig({
|
|
446
|
-
* storage: [
|
|
447
|
-
* vercelBlobStorage({
|
|
448
|
-
* token: process.env.BLOB_READ_WRITE_TOKEN,
|
|
449
|
-
* collections: {
|
|
450
|
-
* media: true
|
|
451
|
-
* }
|
|
452
|
-
* })
|
|
453
|
-
* ]
|
|
454
|
-
* })
|
|
455
|
-
* ```
|
|
456
|
-
*
|
|
457
|
-
* @example With client uploads (recommended for Vercel)
|
|
458
|
-
* ```typescript
|
|
459
|
-
* vercelBlobStorage({
|
|
460
|
-
* collections: {
|
|
461
|
-
* media: {
|
|
462
|
-
* clientUploads: true // Bypass 4.5MB serverless limit
|
|
463
|
-
* }
|
|
464
|
-
* }
|
|
465
|
-
* })
|
|
466
|
-
* ```
|
|
467
|
-
*
|
|
468
|
-
* @example With collection-specific configuration
|
|
469
|
-
* ```typescript
|
|
470
|
-
* vercelBlobStorage({
|
|
471
|
-
* addRandomSuffix: true,
|
|
472
|
-
* cacheControlMaxAge: 86400, // 1 day
|
|
473
|
-
* collections: {
|
|
474
|
-
* // Simple enable with defaults
|
|
475
|
-
* media: true,
|
|
476
|
-
*
|
|
477
|
-
* // Full configuration
|
|
478
|
-
* documents: {
|
|
479
|
-
* prefix: 'docs/',
|
|
480
|
-
* addRandomSuffix: false,
|
|
481
|
-
* allowOverwrite: true
|
|
482
|
-
* }
|
|
483
|
-
* }
|
|
484
|
-
* })
|
|
485
|
-
* ```
|
|
486
|
-
*
|
|
487
|
-
* @packageDocumentation
|
|
488
|
-
*/
|
|
489
|
-
|
|
490
442
|
/**
|
|
491
443
|
* Create a Vercel Blob storage plugin for Nextly.
|
|
492
444
|
*
|
|
@@ -504,264 +456,200 @@ interface ResolvedVercelBlobConfig {
|
|
|
504
456
|
*
|
|
505
457
|
* @throws Error if token is not provided (via adapter)
|
|
506
458
|
*/
|
|
507
|
-
declare function vercelBlobStorage(config: VercelBlobStorageConfig): StoragePlugin;
|
|
508
|
-
|
|
509
|
-
/**
|
|
510
|
-
* Vercel Blob Storage Adapter
|
|
511
|
-
*
|
|
512
|
-
* Stores files on Vercel Blob Storage, a globally distributed object storage
|
|
513
|
-
* service powered by Cloudflare R2. Optimized for Vercel deployments with
|
|
514
|
-
* built-in CDN distribution.
|
|
515
|
-
*
|
|
516
|
-
* Features:
|
|
517
|
-
* - Globally distributed CDN-backed storage
|
|
518
|
-
* - Automatic file management with unique filenames
|
|
519
|
-
* - Token-based authentication
|
|
520
|
-
* - Client-side upload support (via handleUpload API)
|
|
521
|
-
* - Serverless-friendly (no filesystem access required)
|
|
522
|
-
*
|
|
523
|
-
* @example Basic usage
|
|
524
|
-
* ```typescript
|
|
525
|
-
* const adapter = new VercelBlobStorageAdapter({
|
|
526
|
-
* token: process.env.BLOB_READ_WRITE_TOKEN,
|
|
527
|
-
* collections: { media: true }
|
|
528
|
-
* });
|
|
529
|
-
*
|
|
530
|
-
* const result = await adapter.upload(buffer, {
|
|
531
|
-
* filename: 'photo.jpg',
|
|
532
|
-
* mimeType: 'image/jpeg'
|
|
533
|
-
* });
|
|
534
|
-
* // result.url: "https://abc123.public.blob.vercel-storage.com/photo-xyz.jpg"
|
|
535
|
-
* ```
|
|
536
|
-
*/
|
|
537
|
-
|
|
459
|
+
export declare function vercelBlobStorage(config: VercelBlobStorageConfig): StoragePlugin;
|
|
538
460
|
/**
|
|
539
461
|
* Vercel Blob Storage Adapter
|
|
540
462
|
*
|
|
541
463
|
* Implements the IStorageAdapter interface for Vercel Blob Storage.
|
|
542
464
|
* Provides file upload, deletion, existence checks, and metadata retrieval.
|
|
543
465
|
*/
|
|
544
|
-
declare class VercelBlobStorageAdapter implements IStorageAdapter {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
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;
|
|
673
597
|
}>;
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
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;
|
|
724
651
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
* Vercel Blob storage adapter for Nextly CMS.
|
|
730
|
-
* Optimized for Vercel deployments with client-side upload support
|
|
731
|
-
* to bypass serverless function size limits (4.5MB).
|
|
732
|
-
*
|
|
733
|
-
* @example Basic usage
|
|
734
|
-
* ```typescript
|
|
735
|
-
* import { vercelBlobStorage } from '@nextly/storage-vercel-blob'
|
|
736
|
-
* import { defineConfig } from 'nextly/config'
|
|
737
|
-
*
|
|
738
|
-
* export default defineConfig({
|
|
739
|
-
* storage: [
|
|
740
|
-
* vercelBlobStorage({
|
|
741
|
-
* token: process.env.BLOB_READ_WRITE_TOKEN,
|
|
742
|
-
* collections: {
|
|
743
|
-
* media: true
|
|
744
|
-
* }
|
|
745
|
-
* })
|
|
746
|
-
* ]
|
|
747
|
-
* })
|
|
748
|
-
* ```
|
|
749
|
-
*
|
|
750
|
-
* @example With client uploads (recommended for Vercel)
|
|
751
|
-
* ```typescript
|
|
752
|
-
* vercelBlobStorage({
|
|
753
|
-
* collections: {
|
|
754
|
-
* media: {
|
|
755
|
-
* clientUploads: true // Bypass 4.5MB serverless limit
|
|
756
|
-
* }
|
|
757
|
-
* }
|
|
758
|
-
* })
|
|
759
|
-
* ```
|
|
760
|
-
*
|
|
761
|
-
* @packageDocumentation
|
|
762
|
-
*/
|
|
763
|
-
|
|
764
|
-
declare const PACKAGE_NAME = "@nextly/storage-vercel-blob";
|
|
765
|
-
declare const PACKAGE_VERSION = "0.1.0";
|
|
766
|
-
|
|
767
|
-
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.cts.map
|