@nextlyhq/storage-s3 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +129 -0
- package/dist/index.cjs +475 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +873 -0
- package/dist/index.d.ts +873 -0
- package/dist/index.mjs +470 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +79 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,873 @@
|
|
|
1
|
+
import { S3ClientConfig, S3Client } from '@aws-sdk/client-s3';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Media Storage Types
|
|
5
|
+
*
|
|
6
|
+
* Defines interfaces and types for the unified media storage system.
|
|
7
|
+
* Supports cloud storage adapters via plugins.
|
|
8
|
+
*
|
|
9
|
+
* Storage Backends:
|
|
10
|
+
* - AWS S3 / Cloudflare R2 / MinIO (via @nextlyhq/storage-s3)
|
|
11
|
+
* - Vercel Blob (via @nextlyhq/storage-vercel-blob)
|
|
12
|
+
*/
|
|
13
|
+
interface UploadOptions {
|
|
14
|
+
/** Original filename from user */
|
|
15
|
+
filename: string;
|
|
16
|
+
/** MIME type (e.g., 'image/png', 'video/mp4') */
|
|
17
|
+
mimeType: string;
|
|
18
|
+
/** Optional content type override */
|
|
19
|
+
contentType?: string;
|
|
20
|
+
/** Optional folder/prefix for organizing uploads */
|
|
21
|
+
folder?: string;
|
|
22
|
+
/** Collection slug this upload belongs to (for collection-specific storage) */
|
|
23
|
+
collection?: string;
|
|
24
|
+
/** Optional Content-Disposition header value (e.g., 'attachment' for SVG security) */
|
|
25
|
+
contentDisposition?: "inline" | "attachment";
|
|
26
|
+
}
|
|
27
|
+
interface UploadResult {
|
|
28
|
+
/** Public URL to access the file */
|
|
29
|
+
url: string;
|
|
30
|
+
/** Storage path/key (for deletion and metadata retrieval) */
|
|
31
|
+
path: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Extended file metadata returned by getMetadata()
|
|
35
|
+
*
|
|
36
|
+
* Contains comprehensive information about an uploaded file,
|
|
37
|
+
* including dimensions for images and creation timestamps.
|
|
38
|
+
*/
|
|
39
|
+
interface FileMetadata {
|
|
40
|
+
/** Unique identifier (typically the storage path/key) */
|
|
41
|
+
id: string;
|
|
42
|
+
/** Storage filename (may differ from original) */
|
|
43
|
+
filename: string;
|
|
44
|
+
/** Original filename as uploaded by user */
|
|
45
|
+
originalFilename: string;
|
|
46
|
+
/** MIME type (e.g., 'image/jpeg', 'application/pdf') */
|
|
47
|
+
mimeType: string;
|
|
48
|
+
/** File size in bytes */
|
|
49
|
+
size: number;
|
|
50
|
+
/** Public URL to access the file */
|
|
51
|
+
url: string;
|
|
52
|
+
/** Thumbnail URL for images (if generated) */
|
|
53
|
+
thumbnailUrl?: string;
|
|
54
|
+
/** Image width in pixels (for images only) */
|
|
55
|
+
width?: number;
|
|
56
|
+
/** Image height in pixels (for images only) */
|
|
57
|
+
height?: number;
|
|
58
|
+
/** ISO timestamp when file was uploaded */
|
|
59
|
+
createdAt: string;
|
|
60
|
+
/** ISO timestamp when file was last modified */
|
|
61
|
+
updatedAt?: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Storage type identifier.
|
|
65
|
+
* - "s3": AWS S3 or S3-compatible services (R2, MinIO, DigitalOcean Spaces)
|
|
66
|
+
* - "vercel-blob": Vercel Blob Storage
|
|
67
|
+
* - "local": Local disk storage (default for development)
|
|
68
|
+
* - "uploadthing": Uploadthing cloud storage
|
|
69
|
+
*/
|
|
70
|
+
type StorageType = "s3" | "vercel-blob" | "local" | "uploadthing";
|
|
71
|
+
/**
|
|
72
|
+
* Information about a storage adapter's capabilities.
|
|
73
|
+
* Returned by adapter.getInfo() method.
|
|
74
|
+
*/
|
|
75
|
+
interface StorageAdapterInfo {
|
|
76
|
+
/** Storage type identifier */
|
|
77
|
+
type: StorageType;
|
|
78
|
+
/** Human-readable adapter name */
|
|
79
|
+
name: string;
|
|
80
|
+
/** Whether this adapter supports signed URLs for private access */
|
|
81
|
+
supportsSignedUrls: boolean;
|
|
82
|
+
/** Whether this adapter supports client-side (direct) uploads */
|
|
83
|
+
supportsClientUploads: boolean;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Per-collection storage configuration.
|
|
87
|
+
* Allows customizing storage behavior for specific upload collections.
|
|
88
|
+
*/
|
|
89
|
+
interface CollectionStorageConfig {
|
|
90
|
+
/** Prefix/folder for this collection's uploads */
|
|
91
|
+
prefix?: string;
|
|
92
|
+
/** Enable client-side uploads (for serverless platforms with body size limits) */
|
|
93
|
+
clientUploads?: boolean;
|
|
94
|
+
/** Generate signed URLs for downloads (for private buckets) */
|
|
95
|
+
signedDownloads?: boolean;
|
|
96
|
+
/** Signed URL expiry time in seconds (default: 3600) */
|
|
97
|
+
signedUrlExpiresIn?: number;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Collection storage map - maps collection slugs to their config.
|
|
101
|
+
* Used in storage plugin configuration.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* {
|
|
106
|
+
* media: true, // Use default config
|
|
107
|
+
* 'private-docs': {
|
|
108
|
+
* prefix: 'private/',
|
|
109
|
+
* signedDownloads: true,
|
|
110
|
+
* signedUrlExpiresIn: 900
|
|
111
|
+
* }
|
|
112
|
+
* }
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
type CollectionStorageMap = Record<string, boolean | CollectionStorageConfig>;
|
|
116
|
+
/**
|
|
117
|
+
* Base configuration for storage plugins.
|
|
118
|
+
* Extended by specific adapter configs (S3StorageConfig, etc.)
|
|
119
|
+
*/
|
|
120
|
+
interface StoragePluginConfig {
|
|
121
|
+
/** Enable/disable the plugin (default: true) */
|
|
122
|
+
enabled?: boolean;
|
|
123
|
+
/** Collections to apply this storage adapter to */
|
|
124
|
+
collections: CollectionStorageMap;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Storage plugin returned by adapter plugin functions.
|
|
128
|
+
* These are processed during Nextly initialization.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```typescript
|
|
132
|
+
* // From @nextlyhq/storage-s3
|
|
133
|
+
* const plugin = s3Storage({
|
|
134
|
+
* bucket: 'my-bucket',
|
|
135
|
+
* region: 'us-east-1',
|
|
136
|
+
* collections: { media: true }
|
|
137
|
+
* });
|
|
138
|
+
* // plugin implements StoragePlugin
|
|
139
|
+
* ```
|
|
140
|
+
*/
|
|
141
|
+
interface StoragePlugin {
|
|
142
|
+
/** Plugin name for identification */
|
|
143
|
+
name: string;
|
|
144
|
+
/** Storage type */
|
|
145
|
+
type: StorageType;
|
|
146
|
+
/** Collections this plugin handles */
|
|
147
|
+
collections: CollectionStorageMap;
|
|
148
|
+
/** The storage adapter instance */
|
|
149
|
+
adapter: IStorageAdapter;
|
|
150
|
+
/**
|
|
151
|
+
* Handler for generating client-side upload URLs.
|
|
152
|
+
* Called when clientUploads is enabled for a collection.
|
|
153
|
+
*/
|
|
154
|
+
getClientUploadUrl?: (filename: string, mimeType: string, collection: string) => Promise<ClientUploadData>;
|
|
155
|
+
/**
|
|
156
|
+
* Handler for generating signed download URLs.
|
|
157
|
+
* Called when signedDownloads is enabled for a collection.
|
|
158
|
+
*/
|
|
159
|
+
getSignedDownloadUrl?: (path: string, expiresIn?: number) => Promise<string>;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Data returned for client-side (direct) uploads.
|
|
163
|
+
* Contains pre-signed URL and headers for direct-to-storage uploads.
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```typescript
|
|
167
|
+
* // Usage in frontend
|
|
168
|
+
* const uploadData = await fetch('/api/nextly/storage/upload-url', {
|
|
169
|
+
* method: 'POST',
|
|
170
|
+
* body: JSON.stringify({ filename: 'photo.jpg', mimeType: 'image/jpeg', collection: 'media' })
|
|
171
|
+
* }).then(r => r.json());
|
|
172
|
+
*
|
|
173
|
+
* // Direct upload to storage
|
|
174
|
+
* await fetch(uploadData.uploadUrl, {
|
|
175
|
+
* method: uploadData.method,
|
|
176
|
+
* headers: uploadData.headers,
|
|
177
|
+
* body: file
|
|
178
|
+
* });
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
interface ClientUploadData {
|
|
182
|
+
/** Pre-signed URL for direct upload */
|
|
183
|
+
uploadUrl: string;
|
|
184
|
+
/** Storage path/key that will be used */
|
|
185
|
+
path: string;
|
|
186
|
+
/** HTTP method to use (usually PUT for S3, POST for some services) */
|
|
187
|
+
method: "PUT" | "POST";
|
|
188
|
+
/** Headers to include in upload request */
|
|
189
|
+
headers?: Record<string, string>;
|
|
190
|
+
/** Form fields for multipart uploads (some services require this) */
|
|
191
|
+
fields?: Record<string, string>;
|
|
192
|
+
/** URL expiry timestamp */
|
|
193
|
+
expiresAt: Date;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Base storage adapter interface.
|
|
197
|
+
* All storage adapters must implement this interface.
|
|
198
|
+
*
|
|
199
|
+
* Core methods (required):
|
|
200
|
+
* - upload: Store file buffer
|
|
201
|
+
* - delete: Remove file from storage
|
|
202
|
+
* - exists: Check if file exists
|
|
203
|
+
* - getPublicUrl: Get public URL for file access
|
|
204
|
+
* - getType: Get storage type identifier
|
|
205
|
+
*
|
|
206
|
+
* Optional methods:
|
|
207
|
+
* - getInfo: Get adapter capabilities (recommended)
|
|
208
|
+
* - getMetadata: Retrieve file metadata
|
|
209
|
+
* - getSignedUrl: Generate temporary signed URLs for private access
|
|
210
|
+
* - getPresignedUploadUrl: Generate pre-signed URL for client uploads
|
|
211
|
+
*/
|
|
212
|
+
interface BulkDeleteResult {
|
|
213
|
+
successful: string[];
|
|
214
|
+
failed: Array<{
|
|
215
|
+
filePath: string;
|
|
216
|
+
error: string;
|
|
217
|
+
}>;
|
|
218
|
+
}
|
|
219
|
+
interface IStorageAdapter {
|
|
220
|
+
/** Upload file buffer to storage */
|
|
221
|
+
upload(buffer: Buffer, options: UploadOptions): Promise<UploadResult>;
|
|
222
|
+
/** Delete file from storage */
|
|
223
|
+
delete(filePath: string): Promise<void>;
|
|
224
|
+
/** Bulk delete files from storage. Optional — adapters that support batch operations should implement this. */
|
|
225
|
+
bulkDelete?(filePaths: string[]): Promise<BulkDeleteResult>;
|
|
226
|
+
/** Check if file exists in storage */
|
|
227
|
+
exists(filePath: string): Promise<boolean>;
|
|
228
|
+
/** Get public URL for file */
|
|
229
|
+
getPublicUrl(filePath: string): string;
|
|
230
|
+
/** Get storage type identifier */
|
|
231
|
+
getType(): string;
|
|
232
|
+
/** Read file contents from storage (optional - not all adapters support this) */
|
|
233
|
+
read?(filePath: string): Promise<Buffer | null>;
|
|
234
|
+
/** Get adapter info including capabilities (optional but recommended) */
|
|
235
|
+
getInfo?(): StorageAdapterInfo;
|
|
236
|
+
/** Get file metadata (optional - not all adapters support this) */
|
|
237
|
+
getMetadata?(filePath: string): Promise<FileMetadata | null>;
|
|
238
|
+
/** Generate signed URL for temporary private access (optional) */
|
|
239
|
+
getSignedUrl?(filePath: string, expiresIn?: number): Promise<string>;
|
|
240
|
+
/** Generate pre-signed upload URL for client-side uploads (optional) */
|
|
241
|
+
getPresignedUploadUrl?(key: string, mimeType: string, expiresIn?: number): Promise<ClientUploadData>;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* S3 Storage Types
|
|
246
|
+
*
|
|
247
|
+
* Type definitions for the @nextly/storage-s3 package.
|
|
248
|
+
* Supports AWS S3 and S3-compatible services (Cloudflare R2, MinIO, DigitalOcean Spaces).
|
|
249
|
+
*
|
|
250
|
+
* @packageDocumentation
|
|
251
|
+
*/
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* S3 storage adapter configuration.
|
|
255
|
+
*
|
|
256
|
+
* Extends the base storage plugin config with S3-specific options.
|
|
257
|
+
* Supports AWS S3 and S3-compatible services like Cloudflare R2, MinIO, and DigitalOcean Spaces.
|
|
258
|
+
*
|
|
259
|
+
* @example AWS S3
|
|
260
|
+
* ```typescript
|
|
261
|
+
* s3Storage({
|
|
262
|
+
* bucket: process.env.S3_BUCKET!,
|
|
263
|
+
* region: process.env.AWS_REGION!,
|
|
264
|
+
* accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
265
|
+
* secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
266
|
+
* collections: {
|
|
267
|
+
* media: true
|
|
268
|
+
* }
|
|
269
|
+
* })
|
|
270
|
+
* ```
|
|
271
|
+
*
|
|
272
|
+
* @example Cloudflare R2
|
|
273
|
+
* ```typescript
|
|
274
|
+
* s3Storage({
|
|
275
|
+
* bucket: process.env.R2_BUCKET!,
|
|
276
|
+
* region: 'auto',
|
|
277
|
+
* accessKeyId: process.env.R2_ACCESS_KEY_ID!,
|
|
278
|
+
* secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
|
|
279
|
+
* endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
|
|
280
|
+
* publicUrl: process.env.R2_PUBLIC_URL,
|
|
281
|
+
* collections: { media: true }
|
|
282
|
+
* })
|
|
283
|
+
* ```
|
|
284
|
+
*
|
|
285
|
+
* @example MinIO (self-hosted)
|
|
286
|
+
* ```typescript
|
|
287
|
+
* s3Storage({
|
|
288
|
+
* bucket: 'my-bucket',
|
|
289
|
+
* region: 'us-east-1',
|
|
290
|
+
* accessKeyId: process.env.MINIO_ACCESS_KEY!,
|
|
291
|
+
* secretAccessKey: process.env.MINIO_SECRET_KEY!,
|
|
292
|
+
* endpoint: 'http://localhost:9000',
|
|
293
|
+
* forcePathStyle: true,
|
|
294
|
+
* collections: { media: true }
|
|
295
|
+
* })
|
|
296
|
+
* ```
|
|
297
|
+
*/
|
|
298
|
+
interface S3StorageConfig extends StoragePluginConfig {
|
|
299
|
+
/** Enable/disable this storage plugin (default: true). */
|
|
300
|
+
enabled?: boolean;
|
|
301
|
+
/** Collections this plugin handles. */
|
|
302
|
+
collections: Record<string, boolean | CollectionStorageConfig>;
|
|
303
|
+
/**
|
|
304
|
+
* S3 bucket name.
|
|
305
|
+
* Must be an existing bucket that the credentials have access to.
|
|
306
|
+
*/
|
|
307
|
+
bucket: string;
|
|
308
|
+
/**
|
|
309
|
+
* AWS region (e.g., 'us-east-1', 'eu-west-1').
|
|
310
|
+
* Use 'auto' for Cloudflare R2.
|
|
311
|
+
*/
|
|
312
|
+
region: string;
|
|
313
|
+
/**
|
|
314
|
+
* AWS SDK S3 client configuration.
|
|
315
|
+
* Use this for advanced configuration like custom retry strategies,
|
|
316
|
+
* request handlers, or middleware.
|
|
317
|
+
*
|
|
318
|
+
* Note: `region`, `endpoint`, `credentials`, and `forcePathStyle` can be
|
|
319
|
+
* specified either here or via the dedicated config properties. The dedicated
|
|
320
|
+
* properties take precedence if both are specified.
|
|
321
|
+
*/
|
|
322
|
+
config?: Omit<S3ClientConfig, "region">;
|
|
323
|
+
/**
|
|
324
|
+
* AWS Access Key ID.
|
|
325
|
+
* Can also be provided via `config.credentials` or environment variables
|
|
326
|
+
* (AWS_ACCESS_KEY_ID).
|
|
327
|
+
*/
|
|
328
|
+
accessKeyId?: string;
|
|
329
|
+
/**
|
|
330
|
+
* AWS Secret Access Key.
|
|
331
|
+
* Can also be provided via `config.credentials` or environment variables
|
|
332
|
+
* (AWS_SECRET_ACCESS_KEY).
|
|
333
|
+
*/
|
|
334
|
+
secretAccessKey?: string;
|
|
335
|
+
/**
|
|
336
|
+
* Custom endpoint for S3-compatible services.
|
|
337
|
+
* Required for Cloudflare R2, MinIO, DigitalOcean Spaces, etc.
|
|
338
|
+
*
|
|
339
|
+
* @example Cloudflare R2
|
|
340
|
+
* ```typescript
|
|
341
|
+
* endpoint: `https://${accountId}.r2.cloudflarestorage.com`
|
|
342
|
+
* ```
|
|
343
|
+
*
|
|
344
|
+
* @example MinIO
|
|
345
|
+
* ```typescript
|
|
346
|
+
* endpoint: 'http://localhost:9000'
|
|
347
|
+
* ```
|
|
348
|
+
*
|
|
349
|
+
* @example DigitalOcean Spaces
|
|
350
|
+
* ```typescript
|
|
351
|
+
* endpoint: 'https://nyc3.digitaloceanspaces.com'
|
|
352
|
+
* ```
|
|
353
|
+
*/
|
|
354
|
+
endpoint?: string;
|
|
355
|
+
/**
|
|
356
|
+
* Force path-style URLs instead of virtual-hosted-style.
|
|
357
|
+
*
|
|
358
|
+
* When true: `https://endpoint/bucket/key`
|
|
359
|
+
* When false: `https://bucket.endpoint/key`
|
|
360
|
+
*
|
|
361
|
+
* Required for MinIO and some S3-compatible services.
|
|
362
|
+
*
|
|
363
|
+
* @default false
|
|
364
|
+
*/
|
|
365
|
+
forcePathStyle?: boolean;
|
|
366
|
+
/**
|
|
367
|
+
* Access Control List (ACL) for uploaded objects.
|
|
368
|
+
* Determines who can access the uploaded files.
|
|
369
|
+
*
|
|
370
|
+
* Common values:
|
|
371
|
+
* - `'private'` (default): Only authenticated users; serve via signed URLs.
|
|
372
|
+
* - `'public-read'`: Anyone can read (opt in only for buckets that
|
|
373
|
+
* genuinely host public media).
|
|
374
|
+
* - `'bucket-owner-full-control'`: Bucket owner has full control.
|
|
375
|
+
*
|
|
376
|
+
* Defaults to `'private'` so uploads are not world-readable unless
|
|
377
|
+
* explicitly opted in. Pair with `getSignedUrl()` for read access to
|
|
378
|
+
* private objects.
|
|
379
|
+
*
|
|
380
|
+
* Note: Cloudflare R2 ignores ACL settings. Configure public access
|
|
381
|
+
* in the R2 dashboard instead.
|
|
382
|
+
*
|
|
383
|
+
* @default 'private'
|
|
384
|
+
*/
|
|
385
|
+
acl?: S3ObjectACL;
|
|
386
|
+
/**
|
|
387
|
+
* Public URL override for accessing uploaded files.
|
|
388
|
+
* Use this when serving files through a CDN or custom domain.
|
|
389
|
+
*
|
|
390
|
+
* If not set, uses the standard S3 URL format:
|
|
391
|
+
* `https://{bucket}.s3.{region}.amazonaws.com/{key}`
|
|
392
|
+
*
|
|
393
|
+
* Required for Cloudflare R2 (which doesn't have default public URLs).
|
|
394
|
+
*
|
|
395
|
+
* @example CDN URL
|
|
396
|
+
* ```typescript
|
|
397
|
+
* publicUrl: 'https://cdn.example.com'
|
|
398
|
+
* ```
|
|
399
|
+
*
|
|
400
|
+
* @example R2 Public Bucket
|
|
401
|
+
* ```typescript
|
|
402
|
+
* publicUrl: 'https://pub-abc123.r2.dev'
|
|
403
|
+
* ```
|
|
404
|
+
*/
|
|
405
|
+
publicUrl?: string;
|
|
406
|
+
/**
|
|
407
|
+
* Enable signed download URLs for private file access.
|
|
408
|
+
* When enabled, `getSignedUrl()` can generate temporary access URLs.
|
|
409
|
+
*
|
|
410
|
+
* Can be overridden per-collection in the collections map.
|
|
411
|
+
*
|
|
412
|
+
* @default false
|
|
413
|
+
*/
|
|
414
|
+
signedDownloads?: boolean;
|
|
415
|
+
/**
|
|
416
|
+
* Default expiry time for signed URLs in seconds.
|
|
417
|
+
* Applies to both download and upload URLs.
|
|
418
|
+
*
|
|
419
|
+
* Can be overridden per-collection in the collections map.
|
|
420
|
+
*
|
|
421
|
+
* @default 3600 (1 hour)
|
|
422
|
+
*/
|
|
423
|
+
signedUrlExpiresIn?: number;
|
|
424
|
+
/**
|
|
425
|
+
* Cache-Control header for uploaded files.
|
|
426
|
+
* Controls browser and CDN caching behavior.
|
|
427
|
+
*
|
|
428
|
+
* @default 'public, max-age=31536000' (1 year)
|
|
429
|
+
*/
|
|
430
|
+
cacheControl?: string;
|
|
431
|
+
/**
|
|
432
|
+
* Content-Disposition header mode for uploaded files.
|
|
433
|
+
* Controls how browsers handle file downloads.
|
|
434
|
+
*
|
|
435
|
+
* - `'inline'`: Display in browser if possible
|
|
436
|
+
* - `'attachment'`: Always prompt for download
|
|
437
|
+
* - `undefined`: Don't set header (use S3/browser defaults)
|
|
438
|
+
*/
|
|
439
|
+
contentDisposition?: "inline" | "attachment";
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* S3 Object Access Control List (ACL) values.
|
|
443
|
+
* Defines who can access uploaded objects.
|
|
444
|
+
*
|
|
445
|
+
* @see https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html#canned-acl
|
|
446
|
+
*/
|
|
447
|
+
type S3ObjectACL = "private" | "public-read" | "public-read-write" | "authenticated-read" | "aws-exec-read" | "bucket-owner-read" | "bucket-owner-full-control";
|
|
448
|
+
/**
|
|
449
|
+
* S3-specific collection storage configuration.
|
|
450
|
+
* Extends base collection config with S3-specific options.
|
|
451
|
+
*
|
|
452
|
+
* @example
|
|
453
|
+
* ```typescript
|
|
454
|
+
* s3Storage({
|
|
455
|
+
* bucket: 'my-bucket',
|
|
456
|
+
* region: 'us-east-1',
|
|
457
|
+
* collections: {
|
|
458
|
+
* // Simple enable with defaults
|
|
459
|
+
* media: true,
|
|
460
|
+
*
|
|
461
|
+
* // Full configuration
|
|
462
|
+
* 'private-documents': {
|
|
463
|
+
* prefix: 'docs/',
|
|
464
|
+
* acl: 'private',
|
|
465
|
+
* signedDownloads: true,
|
|
466
|
+
* signedUrlExpiresIn: 900, // 15 minutes
|
|
467
|
+
* clientUploads: true,
|
|
468
|
+
* cacheControl: 'private, max-age=0'
|
|
469
|
+
* }
|
|
470
|
+
* }
|
|
471
|
+
* })
|
|
472
|
+
* ```
|
|
473
|
+
*/
|
|
474
|
+
interface S3CollectionConfig extends CollectionStorageConfig {
|
|
475
|
+
/**
|
|
476
|
+
* Override ACL for this collection.
|
|
477
|
+
* If not set, uses the adapter-level ACL setting.
|
|
478
|
+
*/
|
|
479
|
+
acl?: S3ObjectACL;
|
|
480
|
+
/**
|
|
481
|
+
* Override Cache-Control header for this collection.
|
|
482
|
+
* If not set, uses the adapter-level cacheControl setting.
|
|
483
|
+
*/
|
|
484
|
+
cacheControl?: string;
|
|
485
|
+
/**
|
|
486
|
+
* Override Content-Disposition for this collection.
|
|
487
|
+
* If not set, uses the adapter-level contentDisposition setting.
|
|
488
|
+
*/
|
|
489
|
+
contentDisposition?: "inline" | "attachment";
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* Type-safe collection storage map for S3.
|
|
493
|
+
* Maps collection slugs to S3-specific configurations.
|
|
494
|
+
*/
|
|
495
|
+
type S3CollectionStorageMap = Record<string, boolean | S3CollectionConfig>;
|
|
496
|
+
/**
|
|
497
|
+
* Resolved S3 configuration after applying defaults.
|
|
498
|
+
* Used internally by the adapter.
|
|
499
|
+
*
|
|
500
|
+
* @internal
|
|
501
|
+
*/
|
|
502
|
+
interface ResolvedS3Config {
|
|
503
|
+
bucket: string;
|
|
504
|
+
region: string;
|
|
505
|
+
endpoint?: string;
|
|
506
|
+
forcePathStyle: boolean;
|
|
507
|
+
acl: S3ObjectACL;
|
|
508
|
+
publicUrl?: string;
|
|
509
|
+
cacheControl: string;
|
|
510
|
+
contentDisposition?: "inline" | "attachment";
|
|
511
|
+
signedUrlExpiresIn: number;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* S3 Storage Plugin
|
|
516
|
+
*
|
|
517
|
+
* Factory function that creates a storage plugin for AWS S3 and S3-compatible services.
|
|
518
|
+
* Returns a StoragePlugin that can be registered with MediaStorage.
|
|
519
|
+
*
|
|
520
|
+
* @example Basic usage with AWS S3
|
|
521
|
+
* ```typescript
|
|
522
|
+
* import { s3Storage } from '@nextly/storage-s3'
|
|
523
|
+
* import { defineConfig } from 'nextly/config'
|
|
524
|
+
*
|
|
525
|
+
* export default defineConfig({
|
|
526
|
+
* storage: [
|
|
527
|
+
* s3Storage({
|
|
528
|
+
* bucket: process.env.S3_BUCKET!,
|
|
529
|
+
* region: process.env.AWS_REGION!,
|
|
530
|
+
* accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
531
|
+
* secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
532
|
+
* collections: {
|
|
533
|
+
* media: true
|
|
534
|
+
* }
|
|
535
|
+
* })
|
|
536
|
+
* ]
|
|
537
|
+
* })
|
|
538
|
+
* ```
|
|
539
|
+
*
|
|
540
|
+
* @example With Cloudflare R2
|
|
541
|
+
* ```typescript
|
|
542
|
+
* s3Storage({
|
|
543
|
+
* bucket: process.env.R2_BUCKET!,
|
|
544
|
+
* region: 'auto',
|
|
545
|
+
* accessKeyId: process.env.R2_ACCESS_KEY_ID!,
|
|
546
|
+
* secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
|
|
547
|
+
* endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
|
|
548
|
+
* publicUrl: process.env.R2_PUBLIC_URL,
|
|
549
|
+
* collections: { media: true }
|
|
550
|
+
* })
|
|
551
|
+
* ```
|
|
552
|
+
*
|
|
553
|
+
* @example With collection-specific configuration
|
|
554
|
+
* ```typescript
|
|
555
|
+
* s3Storage({
|
|
556
|
+
* bucket: 'my-bucket',
|
|
557
|
+
* region: 'us-east-1',
|
|
558
|
+
* collections: {
|
|
559
|
+
* // Simple enable with defaults
|
|
560
|
+
* media: true,
|
|
561
|
+
*
|
|
562
|
+
* // Full configuration for private documents
|
|
563
|
+
* 'private-docs': {
|
|
564
|
+
* prefix: 'private/',
|
|
565
|
+
* clientUploads: true,
|
|
566
|
+
* signedDownloads: true,
|
|
567
|
+
* signedUrlExpiresIn: 3600
|
|
568
|
+
* }
|
|
569
|
+
* }
|
|
570
|
+
* })
|
|
571
|
+
* ```
|
|
572
|
+
*
|
|
573
|
+
* @packageDocumentation
|
|
574
|
+
*/
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Create an S3 storage plugin for Nextly.
|
|
578
|
+
*
|
|
579
|
+
* This factory function creates a StoragePlugin that can be added to
|
|
580
|
+
* the `storage` array in `nextly.config.ts`. It supports:
|
|
581
|
+
*
|
|
582
|
+
* - AWS S3 (standard)
|
|
583
|
+
* - Cloudflare R2 (S3-compatible)
|
|
584
|
+
* - MinIO (S3-compatible, self-hosted)
|
|
585
|
+
* - DigitalOcean Spaces (S3-compatible)
|
|
586
|
+
* - Any other S3-compatible service
|
|
587
|
+
*
|
|
588
|
+
* @param config - S3 storage configuration
|
|
589
|
+
* @returns A StoragePlugin that MediaStorage can register
|
|
590
|
+
*
|
|
591
|
+
* @throws Error if bucket is not provided (via adapter)
|
|
592
|
+
* @throws Error if region is not provided (via adapter)
|
|
593
|
+
*/
|
|
594
|
+
declare function s3Storage(config: S3StorageConfig): StoragePlugin;
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* S3 Storage Adapter
|
|
598
|
+
*
|
|
599
|
+
* Stores files on AWS S3 or S3-compatible services (Cloudflare R2, MinIO, DigitalOcean Spaces).
|
|
600
|
+
* Uses AWS SDK v3 with automatic multipart upload for large files (>5MB).
|
|
601
|
+
*
|
|
602
|
+
* Features:
|
|
603
|
+
* - Automatic multipart upload via @aws-sdk/lib-storage
|
|
604
|
+
* - Pre-signed URL generation for client-side uploads
|
|
605
|
+
* - Signed URL generation for private file access
|
|
606
|
+
* - Cloudflare R2 support (S3-compatible API)
|
|
607
|
+
* - Custom endpoint support (MinIO, DigitalOcean Spaces)
|
|
608
|
+
* - CDN URL override support
|
|
609
|
+
* - File metadata retrieval
|
|
610
|
+
*
|
|
611
|
+
* @example AWS S3
|
|
612
|
+
* ```typescript
|
|
613
|
+
* const adapter = new S3StorageAdapter({
|
|
614
|
+
* bucket: 'my-media-bucket',
|
|
615
|
+
* region: 'us-east-1',
|
|
616
|
+
* accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
617
|
+
* secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
618
|
+
* collections: { media: true }
|
|
619
|
+
* });
|
|
620
|
+
* ```
|
|
621
|
+
*
|
|
622
|
+
* @example Cloudflare R2
|
|
623
|
+
* ```typescript
|
|
624
|
+
* const adapter = new S3StorageAdapter({
|
|
625
|
+
* bucket: 'my-media',
|
|
626
|
+
* region: 'auto',
|
|
627
|
+
* accessKeyId: process.env.R2_ACCESS_KEY_ID!,
|
|
628
|
+
* secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
|
|
629
|
+
* endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
|
|
630
|
+
* publicUrl: 'https://pub-abc.r2.dev',
|
|
631
|
+
* collections: { media: true }
|
|
632
|
+
* });
|
|
633
|
+
* ```
|
|
634
|
+
*
|
|
635
|
+
* @example MinIO (self-hosted)
|
|
636
|
+
* ```typescript
|
|
637
|
+
* const adapter = new S3StorageAdapter({
|
|
638
|
+
* bucket: 'my-bucket',
|
|
639
|
+
* region: 'us-east-1',
|
|
640
|
+
* accessKeyId: process.env.MINIO_ACCESS_KEY!,
|
|
641
|
+
* secretAccessKey: process.env.MINIO_SECRET_KEY!,
|
|
642
|
+
* endpoint: 'http://localhost:9000',
|
|
643
|
+
* forcePathStyle: true,
|
|
644
|
+
* collections: { media: true }
|
|
645
|
+
* });
|
|
646
|
+
* ```
|
|
647
|
+
*/
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* S3 Storage Adapter
|
|
651
|
+
*
|
|
652
|
+
* Implements the IStorageAdapter interface for AWS S3 and S3-compatible services.
|
|
653
|
+
* Provides full support for uploads, downloads, signed URLs, and client-side uploads.
|
|
654
|
+
*/
|
|
655
|
+
declare class S3StorageAdapter implements IStorageAdapter {
|
|
656
|
+
private config;
|
|
657
|
+
private client;
|
|
658
|
+
private resolvedConfig;
|
|
659
|
+
private isR2;
|
|
660
|
+
/**
|
|
661
|
+
* Create a new S3 storage adapter.
|
|
662
|
+
*
|
|
663
|
+
* @param config - S3 storage configuration
|
|
664
|
+
* @throws Error if bucket or region is not provided
|
|
665
|
+
*/
|
|
666
|
+
constructor(config: S3StorageConfig);
|
|
667
|
+
/**
|
|
668
|
+
* Build AWS credentials from config.
|
|
669
|
+
* Supports explicit credentials or falls back to SDK default chain.
|
|
670
|
+
*/
|
|
671
|
+
private buildCredentials;
|
|
672
|
+
/**
|
|
673
|
+
* Upload file to S3.
|
|
674
|
+
*
|
|
675
|
+
* Uses AWS SDK v3's Upload class from @aws-sdk/lib-storage which:
|
|
676
|
+
* - Automatically handles multipart upload for large files (>5MB)
|
|
677
|
+
* - Provides progress tracking capability
|
|
678
|
+
* - Includes retry logic
|
|
679
|
+
* - Optimizes upload performance
|
|
680
|
+
*
|
|
681
|
+
* @param buffer - File content as Buffer
|
|
682
|
+
* @param options - Upload options (filename, mimeType, folder, collection)
|
|
683
|
+
* @returns Upload result with URL and storage path
|
|
684
|
+
*/
|
|
685
|
+
upload(buffer: Buffer, options: UploadOptions): Promise<UploadResult>;
|
|
686
|
+
/**
|
|
687
|
+
* Delete file from S3.
|
|
688
|
+
*
|
|
689
|
+
* @param filePath - Storage path/key to delete
|
|
690
|
+
*/
|
|
691
|
+
delete(filePath: string): Promise<void>;
|
|
692
|
+
/**
|
|
693
|
+
* Bulk delete files from S3 using a single API call per 1000 keys.
|
|
694
|
+
*
|
|
695
|
+
* Uses AWS SDK v3's DeleteObjectsCommand which supports up to 1000 keys per
|
|
696
|
+
* request. Automatically batches larger arrays and collects per-key results.
|
|
697
|
+
*
|
|
698
|
+
* @param filePaths - Storage paths/keys to delete
|
|
699
|
+
* @returns Object with arrays of successful and failed deletions
|
|
700
|
+
*/
|
|
701
|
+
bulkDelete(filePaths: string[]): Promise<BulkDeleteResult>;
|
|
702
|
+
/**
|
|
703
|
+
* Check if file exists in S3.
|
|
704
|
+
*
|
|
705
|
+
* Uses HeadObject command which is more efficient than GetObject
|
|
706
|
+
* for existence checks (doesn't download the file).
|
|
707
|
+
*
|
|
708
|
+
* @param filePath - Storage path/key to check
|
|
709
|
+
* @returns true if file exists, false otherwise
|
|
710
|
+
*/
|
|
711
|
+
exists(filePath: string): Promise<boolean>;
|
|
712
|
+
/**
|
|
713
|
+
* Get public URL for S3 file.
|
|
714
|
+
*
|
|
715
|
+
* Priority order:
|
|
716
|
+
* 1. Custom publicUrl (CDN or custom domain) if configured
|
|
717
|
+
* 2. Standard S3 URL based on region and bucket
|
|
718
|
+
*
|
|
719
|
+
* For R2: Requires publicUrl configuration (R2 has no default public URLs).
|
|
720
|
+
*
|
|
721
|
+
* @param filePath - Storage path/key
|
|
722
|
+
* @returns Public URL to access the file
|
|
723
|
+
* @throws Error if R2 is used without publicUrl configuration
|
|
724
|
+
*/
|
|
725
|
+
getPublicUrl(filePath: string): string;
|
|
726
|
+
/**
|
|
727
|
+
* Get storage type identifier.
|
|
728
|
+
*
|
|
729
|
+
* Returns "s3" for all S3-compatible services (AWS S3, R2, MinIO, etc.)
|
|
730
|
+
* as they all use the S3 API.
|
|
731
|
+
*/
|
|
732
|
+
getType(): "s3";
|
|
733
|
+
/**
|
|
734
|
+
* Get adapter info including capabilities.
|
|
735
|
+
*
|
|
736
|
+
* @returns Adapter info with type, name, and capability flags
|
|
737
|
+
*/
|
|
738
|
+
getInfo(): StorageAdapterInfo;
|
|
739
|
+
/**
|
|
740
|
+
* Get file metadata from S3.
|
|
741
|
+
*
|
|
742
|
+
* Retrieves file information including size, content type, and timestamps
|
|
743
|
+
* using the HeadObject command.
|
|
744
|
+
*
|
|
745
|
+
* @param filePath - Storage path/key
|
|
746
|
+
* @returns File metadata or null if file not found
|
|
747
|
+
*/
|
|
748
|
+
getMetadata(filePath: string): Promise<FileMetadata | null>;
|
|
749
|
+
/**
|
|
750
|
+
* Generate signed URL for temporary private file access.
|
|
751
|
+
*
|
|
752
|
+
* Creates a pre-signed GetObject URL that grants temporary read access
|
|
753
|
+
* to private files. Useful for serving files from private buckets.
|
|
754
|
+
*
|
|
755
|
+
* @param filePath - Storage path/key
|
|
756
|
+
* @param expiresIn - URL validity duration in seconds (default: 3600)
|
|
757
|
+
* @returns Pre-signed URL for downloading the file
|
|
758
|
+
*/
|
|
759
|
+
getSignedUrl(filePath: string, expiresIn?: number): Promise<string>;
|
|
760
|
+
/**
|
|
761
|
+
* Generate pre-signed URL for client-side uploads.
|
|
762
|
+
*
|
|
763
|
+
* Creates a pre-signed PutObject URL that allows direct uploads from
|
|
764
|
+
* the browser to S3, bypassing server-side upload limits (e.g., Vercel's 4.5MB).
|
|
765
|
+
*
|
|
766
|
+
* @param key - Storage path/key for the upload
|
|
767
|
+
* @param mimeType - MIME type of the file being uploaded
|
|
768
|
+
* @param expiresIn - URL validity duration in seconds (default: 3600)
|
|
769
|
+
* @returns Client upload data with URL, method, and headers
|
|
770
|
+
*/
|
|
771
|
+
getPresignedUploadUrl(key: string, mimeType: string, expiresIn?: number): Promise<ClientUploadData>;
|
|
772
|
+
/**
|
|
773
|
+
* Generate a unique storage key with date-based prefix.
|
|
774
|
+
*
|
|
775
|
+
* Creates keys in format: {folder}/{year}/{month}/{uuid}-{sanitized-filename}
|
|
776
|
+
*
|
|
777
|
+
* @param filename - Original filename (will be sanitized)
|
|
778
|
+
* @param folder - Optional folder/prefix for organizing uploads
|
|
779
|
+
* @returns Generated storage key
|
|
780
|
+
*/
|
|
781
|
+
private generateKey;
|
|
782
|
+
/**
|
|
783
|
+
* Sanitize filename to prevent directory traversal and S3 key issues.
|
|
784
|
+
*
|
|
785
|
+
* @param filename - Original filename
|
|
786
|
+
* @returns Sanitized filename safe for S3 keys
|
|
787
|
+
*/
|
|
788
|
+
private sanitizeFilename;
|
|
789
|
+
/**
|
|
790
|
+
* Check if an error is a "not found" error from S3.
|
|
791
|
+
*
|
|
792
|
+
* @param error - Error to check
|
|
793
|
+
* @returns true if error indicates file not found
|
|
794
|
+
*/
|
|
795
|
+
private isNotFoundError;
|
|
796
|
+
/**
|
|
797
|
+
* Get the S3 client instance.
|
|
798
|
+
* Useful for advanced operations not covered by the adapter interface.
|
|
799
|
+
*/
|
|
800
|
+
getClient(): S3Client;
|
|
801
|
+
/**
|
|
802
|
+
* Get the bucket name.
|
|
803
|
+
*/
|
|
804
|
+
getBucket(): string;
|
|
805
|
+
/**
|
|
806
|
+
* Get the AWS region.
|
|
807
|
+
*/
|
|
808
|
+
getRegion(): string;
|
|
809
|
+
/**
|
|
810
|
+
* Check if this adapter is configured for Cloudflare R2.
|
|
811
|
+
*/
|
|
812
|
+
isCloudflareR2(): boolean;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/**
|
|
816
|
+
* @nextly/storage-s3
|
|
817
|
+
*
|
|
818
|
+
* AWS S3 storage adapter for Nextly CMS.
|
|
819
|
+
* Also works with S3-compatible services like Cloudflare R2, MinIO, and DigitalOcean Spaces.
|
|
820
|
+
*
|
|
821
|
+
* @example Basic usage with AWS S3
|
|
822
|
+
* ```typescript
|
|
823
|
+
* import { s3Storage } from '@nextly/storage-s3'
|
|
824
|
+
* import { defineConfig } from 'nextly/config'
|
|
825
|
+
*
|
|
826
|
+
* export default defineConfig({
|
|
827
|
+
* storage: [
|
|
828
|
+
* s3Storage({
|
|
829
|
+
* bucket: process.env.S3_BUCKET!,
|
|
830
|
+
* region: process.env.AWS_REGION!,
|
|
831
|
+
* accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
832
|
+
* secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
|
|
833
|
+
* collections: {
|
|
834
|
+
* media: true
|
|
835
|
+
* }
|
|
836
|
+
* })
|
|
837
|
+
* ]
|
|
838
|
+
* })
|
|
839
|
+
* ```
|
|
840
|
+
*
|
|
841
|
+
* @example With Cloudflare R2
|
|
842
|
+
* ```typescript
|
|
843
|
+
* s3Storage({
|
|
844
|
+
* bucket: process.env.R2_BUCKET!,
|
|
845
|
+
* region: 'auto',
|
|
846
|
+
* accessKeyId: process.env.R2_ACCESS_KEY_ID!,
|
|
847
|
+
* secretAccessKey: process.env.R2_SECRET_ACCESS_KEY!,
|
|
848
|
+
* endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
|
|
849
|
+
* publicUrl: process.env.R2_PUBLIC_URL,
|
|
850
|
+
* collections: { media: true }
|
|
851
|
+
* })
|
|
852
|
+
* ```
|
|
853
|
+
*
|
|
854
|
+
* @example With MinIO
|
|
855
|
+
* ```typescript
|
|
856
|
+
* s3Storage({
|
|
857
|
+
* bucket: 'my-bucket',
|
|
858
|
+
* region: 'us-east-1',
|
|
859
|
+
* accessKeyId: process.env.MINIO_ACCESS_KEY!,
|
|
860
|
+
* secretAccessKey: process.env.MINIO_SECRET_KEY!,
|
|
861
|
+
* endpoint: 'http://localhost:9000',
|
|
862
|
+
* forcePathStyle: true,
|
|
863
|
+
* collections: { media: true }
|
|
864
|
+
* })
|
|
865
|
+
* ```
|
|
866
|
+
*
|
|
867
|
+
* @packageDocumentation
|
|
868
|
+
*/
|
|
869
|
+
|
|
870
|
+
declare const PACKAGE_NAME = "@nextly/storage-s3";
|
|
871
|
+
declare const PACKAGE_VERSION = "0.1.0";
|
|
872
|
+
|
|
873
|
+
export { PACKAGE_NAME, PACKAGE_VERSION, type ResolvedS3Config, type S3CollectionConfig, type S3CollectionStorageMap, type S3ObjectACL, S3StorageAdapter, type S3StorageConfig, s3Storage };
|