@allegria/aws-file-manager 1.0.1 → 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/aws-file-manager.d.ts +318 -0
- package/dist/cjs/aws-file-manager.js +426 -0
- package/dist/cjs/aws-file-manager.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/lib/aws-file-manager.d.ts +1 -1
- package/dist/lib/aws-file-manager.js +5 -1
- package/dist/lib/aws-file-manager.js.map +1 -1
- package/package.json +27 -9
- package/dist/examples/01-setup.d.ts +0 -14
- package/dist/examples/01-setup.js +0 -42
- package/dist/examples/01-setup.js.map +0 -1
- package/dist/examples/02-upload-multer.d.ts +0 -13
- package/dist/examples/02-upload-multer.js +0 -63
- package/dist/examples/02-upload-multer.js.map +0 -1
- package/dist/examples/03-upload-web.d.ts +0 -12
- package/dist/examples/03-upload-web.js +0 -38
- package/dist/examples/03-upload-web.js.map +0 -1
- package/dist/examples/04-signed-urls.d.ts +0 -12
- package/dist/examples/04-signed-urls.js +0 -48
- package/dist/examples/04-signed-urls.js.map +0 -1
- package/dist/examples/05-download.d.ts +0 -14
- package/dist/examples/05-download.js +0 -53
- package/dist/examples/05-download.js.map +0 -1
- package/dist/examples/06-delete.d.ts +0 -17
- package/dist/examples/06-delete.js +0 -44
- package/dist/examples/06-delete.js.map +0 -1
- package/dist/examples/07-copy.d.ts +0 -14
- package/dist/examples/07-copy.js +0 -35
- package/dist/examples/07-copy.js.map +0 -1
- package/dist/examples/08-list-paginated.d.ts +0 -16
- package/dist/examples/08-list-paginated.js +0 -60
- package/dist/examples/08-list-paginated.js.map +0 -1
- package/dist/examples/09-exists.d.ts +0 -13
- package/dist/examples/09-exists.js +0 -32
- package/dist/examples/09-exists.js.map +0 -1
- package/dist/tests/aws-file-manager.test.d.ts +0 -1
- package/dist/tests/aws-file-manager.test.js +0 -359
- package/dist/tests/aws-file-manager.test.js.map +0 -1
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { S3Client, StorageClass } from "@aws-sdk/client-s3";
|
|
2
|
+
import type { Readable } from "stream";
|
|
3
|
+
/**
|
|
4
|
+
* Configuration options for AwsFileManager.
|
|
5
|
+
* Credentials are optional — when omitted, the SDK resolves them from the
|
|
6
|
+
* environment (IAM role, AWS_ACCESS_KEY_ID, ~/.aws/credentials, etc.).
|
|
7
|
+
* Prefer environment/IAM resolution in production.
|
|
8
|
+
*/
|
|
9
|
+
export interface AwsFileManagerConfig {
|
|
10
|
+
/** AWS region, e.g. 'us-east-1' */
|
|
11
|
+
region: string;
|
|
12
|
+
/** S3 bucket name */
|
|
13
|
+
bucketName: string;
|
|
14
|
+
/** AWS access key ID — omit to rely on environment/IAM resolution */
|
|
15
|
+
accessKeyId?: string;
|
|
16
|
+
/** AWS secret access key — omit to rely on environment/IAM resolution */
|
|
17
|
+
secretAccessKey?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Base path prefix prepended to every key (no leading/trailing slashes).
|
|
20
|
+
* Useful for namespacing all writes to a sub-folder, e.g. 'uploads'.
|
|
21
|
+
*/
|
|
22
|
+
basePath?: string;
|
|
23
|
+
/** Signed URL TTL in seconds (default: 3600) */
|
|
24
|
+
urlExpirationSeconds?: number;
|
|
25
|
+
/** Default S3 storage class (default: INTELLIGENT_TIERING) */
|
|
26
|
+
storageClass?: StorageClass;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A normalised file input that works for both server-side (multer Buffer) and
|
|
30
|
+
* browser-side (Web API File) callers. Use `fromMullerFile()` or
|
|
31
|
+
* `fromWebFile()` helpers to construct one, or build it directly.
|
|
32
|
+
*/
|
|
33
|
+
export interface FileInput {
|
|
34
|
+
/** Raw file bytes */
|
|
35
|
+
buffer: Uint8Array;
|
|
36
|
+
/** Original filename as provided by the uploader */
|
|
37
|
+
originalName: string;
|
|
38
|
+
/** MIME type, e.g. 'image/jpeg' */
|
|
39
|
+
mimeType: string;
|
|
40
|
+
/** File size in bytes */
|
|
41
|
+
size: number;
|
|
42
|
+
}
|
|
43
|
+
/** Options that control how a single file is stored in S3. */
|
|
44
|
+
export interface UploadOptions {
|
|
45
|
+
/**
|
|
46
|
+
* Full S3 key (path + filename) for this object, relative to basePath.
|
|
47
|
+
* When provided, `folder` and `generateUniqueFileName` are ignored.
|
|
48
|
+
* Prefer this in pipeline usage where the calling code owns key generation.
|
|
49
|
+
*/
|
|
50
|
+
key?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Folder path appended to basePath (no leading/trailing slashes).
|
|
53
|
+
* Used when `key` is not provided.
|
|
54
|
+
*/
|
|
55
|
+
folder?: string;
|
|
56
|
+
/**
|
|
57
|
+
* Custom filename to use instead of originalName.
|
|
58
|
+
* Used when `key` is not provided.
|
|
59
|
+
*/
|
|
60
|
+
fileName?: string;
|
|
61
|
+
/**
|
|
62
|
+
* When true and `key` is not provided, replaces the filename with a UUID
|
|
63
|
+
* while preserving the extension. Prevents collisions under concurrent
|
|
64
|
+
* uploads.
|
|
65
|
+
*/
|
|
66
|
+
generateUniqueFileName?: boolean;
|
|
67
|
+
/** Override the detected MIME type */
|
|
68
|
+
contentType?: string;
|
|
69
|
+
/** Arbitrary string metadata stored alongside the S3 object */
|
|
70
|
+
metadata?: Record<string, string>;
|
|
71
|
+
/** Per-upload storage class override */
|
|
72
|
+
storageClass?: StorageClass;
|
|
73
|
+
}
|
|
74
|
+
/** Options for listing objects in a folder */
|
|
75
|
+
export interface ListOptions {
|
|
76
|
+
/** Folder path appended to basePath */
|
|
77
|
+
folder?: string;
|
|
78
|
+
/** Additional prefix filter within the folder */
|
|
79
|
+
prefix?: string;
|
|
80
|
+
/** Maximum number of keys to return (1–1000, default: 1000) */
|
|
81
|
+
maxResults?: number;
|
|
82
|
+
/** Continuation token from a previous list call for pagination */
|
|
83
|
+
continuationToken?: string;
|
|
84
|
+
}
|
|
85
|
+
/** Options for generating a signed URL for an existing key */
|
|
86
|
+
export interface SignedUrlOptions {
|
|
87
|
+
/** TTL override in seconds. Falls back to instance default. */
|
|
88
|
+
expiresIn?: number;
|
|
89
|
+
/**
|
|
90
|
+
* Sets Content-Disposition on the response.
|
|
91
|
+
* 'inline' — browser renders the file (images, PDFs).
|
|
92
|
+
* 'attachment' — browser downloads the file.
|
|
93
|
+
*/
|
|
94
|
+
disposition?: "inline" | "attachment";
|
|
95
|
+
/** Original filename to suggest in Content-Disposition (for attachments) */
|
|
96
|
+
fileName?: string;
|
|
97
|
+
}
|
|
98
|
+
/** Metadata returned after a successful upload */
|
|
99
|
+
export interface UploadResult {
|
|
100
|
+
/** Full S3 key for this object — store this in your database */
|
|
101
|
+
key: string;
|
|
102
|
+
/** Original filename as provided by the uploader */
|
|
103
|
+
originalName: string;
|
|
104
|
+
/** Filename used in S3 (may differ from originalName if UUID was generated) */
|
|
105
|
+
storedName: string;
|
|
106
|
+
/** Stored file size in bytes */
|
|
107
|
+
size: number;
|
|
108
|
+
/** MIME type */
|
|
109
|
+
contentType: string;
|
|
110
|
+
/** S3 ETag (without surrounding quotes) */
|
|
111
|
+
etag?: string;
|
|
112
|
+
/** Storage class applied */
|
|
113
|
+
storageClass: string;
|
|
114
|
+
}
|
|
115
|
+
/** A single entry returned by list() */
|
|
116
|
+
export interface ListEntry {
|
|
117
|
+
/** Full S3 key */
|
|
118
|
+
key: string;
|
|
119
|
+
/** Filename portion of the key */
|
|
120
|
+
fileName: string;
|
|
121
|
+
/** Object size in bytes */
|
|
122
|
+
size: number;
|
|
123
|
+
/** Last modified timestamp */
|
|
124
|
+
lastModified: Date;
|
|
125
|
+
/** Storage class */
|
|
126
|
+
storageClass?: string;
|
|
127
|
+
/** ETag (without surrounding quotes) */
|
|
128
|
+
etag?: string;
|
|
129
|
+
}
|
|
130
|
+
/** Paginated result from list() */
|
|
131
|
+
export interface ListResult {
|
|
132
|
+
entries: ListEntry[];
|
|
133
|
+
/** Pass this to the next list() call to fetch the next page */
|
|
134
|
+
continuationToken?: string;
|
|
135
|
+
/** True when there are more results beyond this page */
|
|
136
|
+
hasMore: boolean;
|
|
137
|
+
}
|
|
138
|
+
export type DownloadMode = "buffer" | "stream";
|
|
139
|
+
export type DownloadResult<T extends DownloadMode> = T extends "buffer" ? {
|
|
140
|
+
buffer: Buffer;
|
|
141
|
+
metadata: ObjectMetadata;
|
|
142
|
+
} : {
|
|
143
|
+
stream: Readable;
|
|
144
|
+
metadata: ObjectMetadata;
|
|
145
|
+
};
|
|
146
|
+
export interface ObjectMetadata {
|
|
147
|
+
contentType?: string;
|
|
148
|
+
contentLength?: number;
|
|
149
|
+
lastModified?: Date;
|
|
150
|
+
metadata?: Record<string, string>;
|
|
151
|
+
storageClass?: string;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Adapts a multer file (Express.Multer.File) to FileInput.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* // In an Express route with multer:
|
|
158
|
+
* const fileInput = fromMulterFile(req.file);
|
|
159
|
+
*/
|
|
160
|
+
export declare function fromMulterFile(multerFile: {
|
|
161
|
+
buffer: Buffer;
|
|
162
|
+
originalname: string;
|
|
163
|
+
mimetype: string;
|
|
164
|
+
size: number;
|
|
165
|
+
}): FileInput;
|
|
166
|
+
/**
|
|
167
|
+
* Adapts a Web API File (browser / Next.js App Router FormData) to FileInput.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* // In a Next.js App Router route handler:
|
|
171
|
+
* const formData = await request.formData();
|
|
172
|
+
* const fileInput = await fromWebFile(formData.get('file') as File);
|
|
173
|
+
*/
|
|
174
|
+
export declare function fromWebFile(webFile: File): Promise<FileInput>;
|
|
175
|
+
export declare class AwsFileManager {
|
|
176
|
+
private readonly s3;
|
|
177
|
+
private readonly bucketName;
|
|
178
|
+
private readonly basePath;
|
|
179
|
+
private readonly urlExpirationSeconds;
|
|
180
|
+
private readonly storageClass;
|
|
181
|
+
static readonly DEFAULTS: {
|
|
182
|
+
readonly storageClass: "INTELLIGENT_TIERING";
|
|
183
|
+
readonly urlExpirationSeconds: 3600;
|
|
184
|
+
};
|
|
185
|
+
constructor(config: AwsFileManagerConfig);
|
|
186
|
+
/**
|
|
187
|
+
* Uploads a file to S3 and returns storage metadata (no URL).
|
|
188
|
+
*
|
|
189
|
+
* The returned `key` is what you persist to your database. Generate signed
|
|
190
|
+
* URLs on demand via `getSignedUrl()` — never store them, as they expire.
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* // In a pipeline step:
|
|
194
|
+
* const result = await fileManager.upload(ctx.fileInput, { key: ctx.s3Key });
|
|
195
|
+
* ctx.uploadResult = result;
|
|
196
|
+
*/
|
|
197
|
+
upload(file: FileInput, options?: UploadOptions): Promise<UploadResult>;
|
|
198
|
+
/**
|
|
199
|
+
* Generates a short-lived signed URL for a private S3 object.
|
|
200
|
+
*
|
|
201
|
+
* Call this at request time; never store the returned URL. Signed URLs
|
|
202
|
+
* are credentials — treat them accordingly.
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* const url = await fileManager.getSignedUrl(file.s3Key, {
|
|
206
|
+
* disposition: 'inline',
|
|
207
|
+
* fileName: file.originalName,
|
|
208
|
+
* });
|
|
209
|
+
*/
|
|
210
|
+
getSignedUrl(key: string, options?: SignedUrlOptions): Promise<string>;
|
|
211
|
+
/**
|
|
212
|
+
* Downloads an S3 object as a Buffer or a Node.js Readable stream.
|
|
213
|
+
*
|
|
214
|
+
* Use `'stream'` (default) when piping to a response or writing to disk.
|
|
215
|
+
* Use `'buffer'` when you need the full bytes in memory (e.g. for processing).
|
|
216
|
+
*
|
|
217
|
+
* Returns `null` when the object does not exist.
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* const result = await fileManager.download(key, 'buffer');
|
|
221
|
+
* if (result) {
|
|
222
|
+
* const { buffer, metadata } = result;
|
|
223
|
+
* }
|
|
224
|
+
*/
|
|
225
|
+
download<T extends DownloadMode = "stream">(key: string, mode?: T): Promise<DownloadResult<T> | null>;
|
|
226
|
+
/**
|
|
227
|
+
* Permanently deletes an object from S3.
|
|
228
|
+
*
|
|
229
|
+
* Note: this only removes the S3 object. Your database record should be
|
|
230
|
+
* soft-deleted first, and this called as a cleanup step after the DB
|
|
231
|
+
* transaction commits — so a crash mid-way leaves a recoverable orphan
|
|
232
|
+
* rather than a missing file with an intact DB row.
|
|
233
|
+
*
|
|
234
|
+
* @example
|
|
235
|
+
* await fileManager.delete(file.s3Key);
|
|
236
|
+
*/
|
|
237
|
+
delete(key: string): Promise<void>;
|
|
238
|
+
/**
|
|
239
|
+
* Deletes multiple objects in a single call.
|
|
240
|
+
* Silently skips keys that do not exist.
|
|
241
|
+
*
|
|
242
|
+
* @example
|
|
243
|
+
* await fileManager.deleteMany([original.s3Key, thumbnail.s3Key]);
|
|
244
|
+
*/
|
|
245
|
+
deleteMany(keys: string[]): Promise<void>;
|
|
246
|
+
/**
|
|
247
|
+
* Copies an object within the same bucket.
|
|
248
|
+
*
|
|
249
|
+
* Useful when duplicating files across entities (e.g. cloning a note)
|
|
250
|
+
* or reorganising keys without re-uploading bytes.
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* await fileManager.copy(sourceFile.s3Key, newKey);
|
|
254
|
+
*/
|
|
255
|
+
copy(sourceKey: string, destinationKey: string, options?: {
|
|
256
|
+
storageClass?: StorageClass;
|
|
257
|
+
metadata?: Record<string, string>;
|
|
258
|
+
}): Promise<void>;
|
|
259
|
+
/**
|
|
260
|
+
* Lists objects under a folder prefix.
|
|
261
|
+
*
|
|
262
|
+
* Results are paginated. Pass the returned `continuationToken` back
|
|
263
|
+
* into subsequent calls to walk through large result sets.
|
|
264
|
+
*
|
|
265
|
+
* Primarily intended for the server-side storage reconciliation job
|
|
266
|
+
* (nightly scan to reconcile DB byte counts against S3 actuals).
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* let token: string | undefined;
|
|
270
|
+
* do {
|
|
271
|
+
* const result = await fileManager.list({ folder: 'acme-corp', continuationToken: token });
|
|
272
|
+
* processBatch(result.entries);
|
|
273
|
+
* token = result.continuationToken;
|
|
274
|
+
* } while (result.hasMore);
|
|
275
|
+
*/
|
|
276
|
+
list(options?: ListOptions): Promise<ListResult>;
|
|
277
|
+
/**
|
|
278
|
+
* Returns true if an object exists at the given key.
|
|
279
|
+
*
|
|
280
|
+
* @example
|
|
281
|
+
* if (!(await fileManager.exists(key))) {
|
|
282
|
+
* throw new Error('Referenced file is missing from storage');
|
|
283
|
+
* }
|
|
284
|
+
*/
|
|
285
|
+
exists(key: string): Promise<boolean>;
|
|
286
|
+
/**
|
|
287
|
+
* Returns the underlying S3Client for operations not covered by this class.
|
|
288
|
+
* Use sparingly — prefer adding methods here so the abstraction stays coherent.
|
|
289
|
+
*/
|
|
290
|
+
getS3Client(): S3Client;
|
|
291
|
+
/**
|
|
292
|
+
* Resolves the final S3 key and stored filename from upload options.
|
|
293
|
+
*
|
|
294
|
+
* Priority order for the key:
|
|
295
|
+
* 1. options.key (explicit full key — pipeline usage)
|
|
296
|
+
* 2. basePath + folder + fileName (if provided)
|
|
297
|
+
* 3. basePath + folder + UUID-based name (if generateUniqueFileName)
|
|
298
|
+
* 4. basePath + folder + originalName (fallback)
|
|
299
|
+
*/
|
|
300
|
+
private resolveKey;
|
|
301
|
+
/**
|
|
302
|
+
* Builds a full S3 key from basePath, optional folder, and filename.
|
|
303
|
+
*/
|
|
304
|
+
private buildFullKey;
|
|
305
|
+
/**
|
|
306
|
+
* Builds a prefix string for list operations.
|
|
307
|
+
*/
|
|
308
|
+
private buildPrefix;
|
|
309
|
+
/**
|
|
310
|
+
* Generates a UUID-based filename, preserving the original extension.
|
|
311
|
+
* UUIDs are collision-proof under any upload concurrency.
|
|
312
|
+
*/
|
|
313
|
+
private makeUniqueFileName;
|
|
314
|
+
/**
|
|
315
|
+
* Builds a Content-Disposition header value from SignedUrlOptions.
|
|
316
|
+
*/
|
|
317
|
+
private buildContentDisposition;
|
|
318
|
+
}
|