s3mini 0.3.0 → 0.4.0
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 +14 -3
- package/dist/s3mini.d.ts +45 -17
- package/dist/s3mini.js +86 -39
- package/dist/s3mini.js.map +1 -1
- package/dist/s3mini.min.js +1 -1
- package/dist/s3mini.min.js.map +1 -1
- package/package.json +18 -18
- package/src/S3.ts +120 -39
- package/src/consts.ts +3 -3
- package/src/index.ts +3 -3
- package/src/types.ts +14 -0
- package/src/utils.ts +0 -1
package/README.md
CHANGED
|
@@ -60,6 +60,7 @@ The library supports a subset of S3 operations, focusing on essential features,
|
|
|
60
60
|
- ✅ GetObject (getObject, getObjectResponse, getObjectWithETag, getObjectRaw, getObjectArrayBuffer, getObjectJSON)
|
|
61
61
|
- ✅ PutObject (putObject)
|
|
62
62
|
- ✅ DeleteObject (deleteObject)
|
|
63
|
+
- ✅ DeleteObjects (deleteObjects)
|
|
63
64
|
- ✅ HeadObject (objectExists, getEtag, getContentLength)
|
|
64
65
|
- ✅ listMultipartUploads
|
|
65
66
|
- ✅ CreateMultipartUpload (getMultipartUploadId)
|
|
@@ -68,6 +69,8 @@ The library supports a subset of S3 operations, focusing on essential features,
|
|
|
68
69
|
- ✅ uploadPart
|
|
69
70
|
- ❌ CopyObject: Not implemented (tbd)
|
|
70
71
|
|
|
72
|
+
Put/Get objects with SSE-C (server-side encryption with customer-provided keys) is supported, but only tested on Cloudflare R2!
|
|
73
|
+
|
|
71
74
|
## Installation
|
|
72
75
|
|
|
73
76
|
```bash
|
|
@@ -99,10 +102,14 @@ mv example.env .env
|
|
|
99
102
|
|
|
100
103
|
## Usage
|
|
101
104
|
|
|
105
|
+
> [!WARNING]
|
|
106
|
+
> `s3mini` is a deprecated alias retained solely for backward compatibility.
|
|
107
|
+
> It is scheduled for removal in a future release. Please migrate to the new `S3mini` class.
|
|
108
|
+
|
|
102
109
|
```typescript
|
|
103
|
-
import {
|
|
110
|
+
import { S3mini, sanitizeETag } from 's3mini';
|
|
104
111
|
|
|
105
|
-
const s3client = new
|
|
112
|
+
const s3client = new S3mini({
|
|
106
113
|
accessKeyId: config.accessKeyId,
|
|
107
114
|
secretAccessKey: config.secretAccessKey,
|
|
108
115
|
endpoint: config.endpoint,
|
|
@@ -149,7 +156,7 @@ const objectData: string | null = await s3client.getObject(smallObjectKey);
|
|
|
149
156
|
console.log('Object data:', objectData);
|
|
150
157
|
|
|
151
158
|
// get the object with ETag, null if not found
|
|
152
|
-
const response2: Response = await
|
|
159
|
+
const response2: Response = await S3mini.getObject(smallObjectKey, { 'if-none-match': etag });
|
|
153
160
|
if (response2) {
|
|
154
161
|
// ETag changed so we can get the object data and new ETag
|
|
155
162
|
// Note: ETag is not guaranteed to be the same as the MD5 hash of the object
|
|
@@ -175,6 +182,10 @@ if (list) {
|
|
|
175
182
|
|
|
176
183
|
// delete the object
|
|
177
184
|
const wasDeleted: boolean = await s3client.deleteObject(smallObjectKey);
|
|
185
|
+
// to delete multiple objects, use deleteObjects method
|
|
186
|
+
// const keysToDelete: string[] = ['object1.txt', 'object2.txt'];
|
|
187
|
+
// const deletedArray: boolean[] = await s3client.deleteObjects(keysToDelete);
|
|
188
|
+
// Note: deleteObjects returns an array of booleans, one for each key, indicating if the object was deleted or not
|
|
178
189
|
|
|
179
190
|
// Multipart upload
|
|
180
191
|
const multipartKey = 'multipart-object.txt';
|
package/dist/s3mini.d.ts
CHANGED
|
@@ -7,6 +7,11 @@ interface S3Config {
|
|
|
7
7
|
requestAbortTimeout?: number;
|
|
8
8
|
logger?: Logger;
|
|
9
9
|
}
|
|
10
|
+
interface SSECHeaders {
|
|
11
|
+
'x-amz-server-side-encryption-customer-algorithm': string;
|
|
12
|
+
'x-amz-server-side-encryption-customer-key': string;
|
|
13
|
+
'x-amz-server-side-encryption-customer-key-md5': string;
|
|
14
|
+
}
|
|
10
15
|
interface Logger {
|
|
11
16
|
info: (message: string, ...args: unknown[]) => void;
|
|
12
17
|
warn: (message: string, ...args: unknown[]) => void;
|
|
@@ -16,6 +21,13 @@ interface UploadPart {
|
|
|
16
21
|
partNumber: number;
|
|
17
22
|
etag: string;
|
|
18
23
|
}
|
|
24
|
+
interface ListObject {
|
|
25
|
+
Key: string;
|
|
26
|
+
Size: number;
|
|
27
|
+
LastModified: Date;
|
|
28
|
+
ETag: string;
|
|
29
|
+
StorageClass: string;
|
|
30
|
+
}
|
|
19
31
|
interface CompleteMultipartUploadResult {
|
|
20
32
|
location: string;
|
|
21
33
|
bucket: string;
|
|
@@ -92,7 +104,7 @@ type ExistResponseCode = false | true | null;
|
|
|
92
104
|
* // Delete a file
|
|
93
105
|
* await s3.deleteObject('example.txt');
|
|
94
106
|
*/
|
|
95
|
-
declare class
|
|
107
|
+
declare class S3mini {
|
|
96
108
|
/**
|
|
97
109
|
* Creates an instance of the S3 class.
|
|
98
110
|
*
|
|
@@ -192,7 +204,7 @@ declare class s3mini {
|
|
|
192
204
|
* @param {string} [prefix=''] - The prefix to filter objects by.
|
|
193
205
|
* @param {number} [maxKeys] - The maximum number of keys to return. If not provided, all keys will be returned.
|
|
194
206
|
* @param {Record<string, unknown>} [opts={}] - Additional options for the request.
|
|
195
|
-
* @returns {Promise<
|
|
207
|
+
* @returns {Promise<IT.ListObject[] | null>} A promise that resolves to an array of objects or null if the bucket is empty.
|
|
196
208
|
* @example
|
|
197
209
|
* // List all objects
|
|
198
210
|
* const objects = await s3.listObjects();
|
|
@@ -200,7 +212,7 @@ declare class s3mini {
|
|
|
200
212
|
* // List objects with prefix
|
|
201
213
|
* const photos = await s3.listObjects('/', 'photos/', 100);
|
|
202
214
|
*/
|
|
203
|
-
listObjects(delimiter?: string, prefix?: string, maxKeys?: number, opts?: Record<string, unknown>): Promise<
|
|
215
|
+
listObjects(delimiter?: string, prefix?: string, maxKeys?: number, opts?: Record<string, unknown>): Promise<ListObject[] | null>;
|
|
204
216
|
/**
|
|
205
217
|
* Lists multipart uploads in the bucket.
|
|
206
218
|
* This method sends a request to list multipart uploads in the specified bucket.
|
|
@@ -215,42 +227,47 @@ declare class s3mini {
|
|
|
215
227
|
* Get an object from the S3-compatible service.
|
|
216
228
|
* This method sends a request to retrieve the specified object from the S3-compatible service.
|
|
217
229
|
* @param {string} key - The key of the object to retrieve.
|
|
218
|
-
* @param {Record<string, unknown>} [opts
|
|
230
|
+
* @param {Record<string, unknown>} [opts] - Additional options for the request.
|
|
231
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
219
232
|
* @returns A promise that resolves to the object data (string) or null if not found.
|
|
220
233
|
*/
|
|
221
|
-
getObject(key: string, opts?: Record<string, unknown
|
|
234
|
+
getObject(key: string, opts?: Record<string, unknown>, ssecHeaders?: SSECHeaders): Promise<string | null>;
|
|
222
235
|
/**
|
|
223
236
|
* Get an object response from the S3-compatible service.
|
|
224
237
|
* This method sends a request to retrieve the specified object and returns the full response.
|
|
225
238
|
* @param {string} key - The key of the object to retrieve.
|
|
226
239
|
* @param {Record<string, unknown>} [opts={}] - Additional options for the request.
|
|
240
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
227
241
|
* @returns A promise that resolves to the Response object or null if not found.
|
|
228
242
|
*/
|
|
229
|
-
getObjectResponse(key: string, opts?: Record<string, unknown
|
|
243
|
+
getObjectResponse(key: string, opts?: Record<string, unknown>, ssecHeaders?: SSECHeaders): Promise<Response | null>;
|
|
230
244
|
/**
|
|
231
245
|
* Get an object as an ArrayBuffer from the S3-compatible service.
|
|
232
246
|
* This method sends a request to retrieve the specified object and returns it as an ArrayBuffer.
|
|
233
247
|
* @param {string} key - The key of the object to retrieve.
|
|
234
248
|
* @param {Record<string, unknown>} [opts={}] - Additional options for the request.
|
|
249
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
235
250
|
* @returns A promise that resolves to the object data as an ArrayBuffer or null if not found.
|
|
236
251
|
*/
|
|
237
|
-
getObjectArrayBuffer(key: string, opts?: Record<string, unknown
|
|
252
|
+
getObjectArrayBuffer(key: string, opts?: Record<string, unknown>, ssecHeaders?: SSECHeaders): Promise<ArrayBuffer | null>;
|
|
238
253
|
/**
|
|
239
254
|
* Get an object as JSON from the S3-compatible service.
|
|
240
255
|
* This method sends a request to retrieve the specified object and returns it as JSON.
|
|
241
256
|
* @param {string} key - The key of the object to retrieve.
|
|
242
257
|
* @param {Record<string, unknown>} [opts={}] - Additional options for the request.
|
|
258
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
243
259
|
* @returns A promise that resolves to the object data as JSON or null if not found.
|
|
244
260
|
*/
|
|
245
|
-
getObjectJSON<T = unknown>(key: string, opts?: Record<string, unknown
|
|
261
|
+
getObjectJSON<T = unknown>(key: string, opts?: Record<string, unknown>, ssecHeaders?: SSECHeaders): Promise<T | null>;
|
|
246
262
|
/**
|
|
247
263
|
* Get an object with its ETag from the S3-compatible service.
|
|
248
264
|
* This method sends a request to retrieve the specified object and its ETag.
|
|
249
265
|
* @param {string} key - The key of the object to retrieve.
|
|
250
266
|
* @param {Record<string, unknown>} [opts={}] - Additional options for the request.
|
|
267
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
251
268
|
* @returns A promise that resolves to an object containing the ETag and the object data as an ArrayBuffer or null if not found.
|
|
252
269
|
*/
|
|
253
|
-
getObjectWithETag(key: string, opts?: Record<string, unknown
|
|
270
|
+
getObjectWithETag(key: string, opts?: Record<string, unknown>, ssecHeaders?: SSECHeaders): Promise<{
|
|
254
271
|
etag: string | null;
|
|
255
272
|
data: ArrayBuffer | null;
|
|
256
273
|
}>;
|
|
@@ -262,9 +279,10 @@ declare class s3mini {
|
|
|
262
279
|
* @param {number} [rangeFrom=0] - The starting byte for the range (if not whole file).
|
|
263
280
|
* @param {number} [rangeTo=this.requestSizeInBytes] - The ending byte for the range (if not whole file).
|
|
264
281
|
* @param {Record<string, unknown>} [opts={}] - Additional options for the request.
|
|
282
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
265
283
|
* @returns A promise that resolves to the Response object.
|
|
266
284
|
*/
|
|
267
|
-
getObjectRaw(key: string, wholeFile?: boolean, rangeFrom?: number, rangeTo?: number, opts?: Record<string, unknown
|
|
285
|
+
getObjectRaw(key: string, wholeFile?: boolean, rangeFrom?: number, rangeTo?: number, opts?: Record<string, unknown>, ssecHeaders?: SSECHeaders): Promise<Response>;
|
|
268
286
|
/**
|
|
269
287
|
* Get the content length of an object.
|
|
270
288
|
* This method sends a HEAD request to retrieve the content length of the specified object.
|
|
@@ -272,7 +290,7 @@ declare class s3mini {
|
|
|
272
290
|
* @returns A promise that resolves to the content length of the object in bytes, or 0 if not found.
|
|
273
291
|
* @throws {Error} If the content length header is not found in the response.
|
|
274
292
|
*/
|
|
275
|
-
getContentLength(key: string): Promise<number>;
|
|
293
|
+
getContentLength(key: string, ssecHeaders?: SSECHeaders): Promise<number>;
|
|
276
294
|
/**
|
|
277
295
|
* Checks if an object exists in the S3-compatible service.
|
|
278
296
|
* This method sends a HEAD request to check if the specified object exists.
|
|
@@ -285,6 +303,7 @@ declare class s3mini {
|
|
|
285
303
|
* Retrieves the ETag of an object without downloading its content.
|
|
286
304
|
* @param {string} key - The key of the object to retrieve the ETag for.
|
|
287
305
|
* @param {Record<string, unknown>} [opts={}] - Additional options for the request.
|
|
306
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
288
307
|
* @returns {Promise<string | null>} A promise that resolves to the ETag value or null if the object is not found.
|
|
289
308
|
* @throws {Error} If the ETag header is not found in the response.
|
|
290
309
|
* @example
|
|
@@ -293,12 +312,13 @@ declare class s3mini {
|
|
|
293
312
|
* console.log(`File ETag: ${etag}`);
|
|
294
313
|
* }
|
|
295
314
|
*/
|
|
296
|
-
getEtag(key: string, opts?: Record<string, unknown
|
|
315
|
+
getEtag(key: string, opts?: Record<string, unknown>, ssecHeaders?: SSECHeaders): Promise<string | null>;
|
|
297
316
|
/**
|
|
298
317
|
* Uploads an object to the S3-compatible service.
|
|
299
318
|
* @param {string} key - The key/path where the object will be stored.
|
|
300
319
|
* @param {string | Buffer} data - The data to upload (string or Buffer).
|
|
301
320
|
* @param {string} [fileType='application/octet-stream'] - The MIME type of the file.
|
|
321
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
302
322
|
* @returns {Promise<Response>} A promise that resolves to the Response object from the upload request.
|
|
303
323
|
* @throws {TypeError} If data is not a string or Buffer.
|
|
304
324
|
* @example
|
|
@@ -309,11 +329,12 @@ declare class s3mini {
|
|
|
309
329
|
* const buffer = Buffer.from([0x89, 0x50, 0x4e, 0x47]);
|
|
310
330
|
* await s3.putObject('image.png', buffer, 'image/png');
|
|
311
331
|
*/
|
|
312
|
-
putObject(key: string, data: string | Buffer, fileType?: string): Promise<Response>;
|
|
332
|
+
putObject(key: string, data: string | Buffer, fileType?: string, ssecHeaders?: SSECHeaders): Promise<Response>;
|
|
313
333
|
/**
|
|
314
334
|
* Initiates a multipart upload and returns the upload ID.
|
|
315
335
|
* @param {string} key - The key/path where the object will be stored.
|
|
316
336
|
* @param {string} [fileType='application/octet-stream'] - The MIME type of the file.
|
|
337
|
+
* @param {IT.SSECHeaders?} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
317
338
|
* @returns {Promise<string>} A promise that resolves to the upload ID for the multipart upload.
|
|
318
339
|
* @throws {TypeError} If key is invalid or fileType is not a string.
|
|
319
340
|
* @throws {Error} If the multipart upload fails to initialize.
|
|
@@ -321,7 +342,7 @@ declare class s3mini {
|
|
|
321
342
|
* const uploadId = await s3.getMultipartUploadId('large-file.zip', 'application/zip');
|
|
322
343
|
* console.log(`Started multipart upload: ${uploadId}`);
|
|
323
344
|
*/
|
|
324
|
-
getMultipartUploadId(key: string, fileType?: string): Promise<string>;
|
|
345
|
+
getMultipartUploadId(key: string, fileType?: string, ssecHeaders?: SSECHeaders): Promise<string>;
|
|
325
346
|
/**
|
|
326
347
|
* Uploads a part in a multipart upload.
|
|
327
348
|
* @param {string} key - The key of the object being uploaded.
|
|
@@ -329,6 +350,7 @@ declare class s3mini {
|
|
|
329
350
|
* @param {Buffer | string} data - The data for this part.
|
|
330
351
|
* @param {number} partNumber - The part number (must be between 1 and 10,000).
|
|
331
352
|
* @param {Record<string, unknown>} [opts={}] - Additional options for the request.
|
|
353
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
332
354
|
* @returns {Promise<IT.UploadPart>} A promise that resolves to an object containing the partNumber and etag.
|
|
333
355
|
* @throws {TypeError} If any parameter is invalid.
|
|
334
356
|
* @example
|
|
@@ -340,7 +362,7 @@ declare class s3mini {
|
|
|
340
362
|
* );
|
|
341
363
|
* console.log(`Part ${part.partNumber} uploaded with ETag: ${part.etag}`);
|
|
342
364
|
*/
|
|
343
|
-
uploadPart(key: string, uploadId: string, data: Buffer | string, partNumber: number, opts?: Record<string, unknown
|
|
365
|
+
uploadPart(key: string, uploadId: string, data: Buffer | string, partNumber: number, opts?: Record<string, unknown>, ssecHeaders?: SSECHeaders): Promise<UploadPart>;
|
|
344
366
|
/**
|
|
345
367
|
* Completes a multipart upload by combining all uploaded parts.
|
|
346
368
|
* @param {string} key - The key of the object being uploaded.
|
|
@@ -364,6 +386,7 @@ declare class s3mini {
|
|
|
364
386
|
* Aborts a multipart upload and removes all uploaded parts.
|
|
365
387
|
* @param {string} key - The key of the object being uploaded.
|
|
366
388
|
* @param {string} uploadId - The upload ID to abort.
|
|
389
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
367
390
|
* @returns {Promise<object>} A promise that resolves to an object containing the abort status and details.
|
|
368
391
|
* @throws {TypeError} If key or uploadId is invalid.
|
|
369
392
|
* @throws {Error} If the abort operation fails.
|
|
@@ -375,7 +398,7 @@ declare class s3mini {
|
|
|
375
398
|
* console.error('Failed to abort upload:', error);
|
|
376
399
|
* }
|
|
377
400
|
*/
|
|
378
|
-
abortMultipartUpload(key: string, uploadId: string): Promise<object>;
|
|
401
|
+
abortMultipartUpload(key: string, uploadId: string, ssecHeaders?: SSECHeaders): Promise<object>;
|
|
379
402
|
private _buildCompleteMultipartUploadXml;
|
|
380
403
|
/**
|
|
381
404
|
* Deletes an object from the bucket.
|
|
@@ -396,6 +419,10 @@ declare class s3mini {
|
|
|
396
419
|
private _buildCanonicalQueryString;
|
|
397
420
|
private _getSignatureKey;
|
|
398
421
|
}
|
|
422
|
+
/**
|
|
423
|
+
* @deprecated Use `S3mini` instead.
|
|
424
|
+
*/
|
|
425
|
+
declare const s3mini: typeof S3mini;
|
|
399
426
|
|
|
400
427
|
/**
|
|
401
428
|
* Sanitize ETag value by removing quotes and XML entities
|
|
@@ -414,4 +441,5 @@ declare const sanitizeETag: (etag: string) => string;
|
|
|
414
441
|
*/
|
|
415
442
|
declare const runInBatches: <T = unknown>(tasks: Iterable<() => Promise<T>>, batchSize?: number, minIntervalMs?: number) => Promise<Array<PromiseSettledResult<T>>>;
|
|
416
443
|
|
|
417
|
-
export {
|
|
444
|
+
export { S3mini, S3mini as default, runInBatches, s3mini, sanitizeETag };
|
|
445
|
+
export type { CompleteMultipartUploadResult, ErrorWithCode, ExistResponseCode, ListBucketResponse, ListMultipartUploadResponse, Logger, S3Config, UploadPart };
|
package/dist/s3mini.js
CHANGED
|
@@ -13,9 +13,9 @@ const DEFAULT_REQUEST_SIZE_IN_BYTES = 8 * 1024 * 1024;
|
|
|
13
13
|
const HEADER_AMZ_CONTENT_SHA256 = 'x-amz-content-sha256';
|
|
14
14
|
const HEADER_AMZ_DATE = 'x-amz-date';
|
|
15
15
|
const HEADER_HOST = 'host';
|
|
16
|
-
const HEADER_AUTHORIZATION = '
|
|
17
|
-
const HEADER_CONTENT_TYPE = '
|
|
18
|
-
const HEADER_CONTENT_LENGTH = '
|
|
16
|
+
const HEADER_AUTHORIZATION = 'authorization';
|
|
17
|
+
const HEADER_CONTENT_TYPE = 'content-type';
|
|
18
|
+
const HEADER_CONTENT_LENGTH = 'content-length';
|
|
19
19
|
const HEADER_ETAG = 'etag';
|
|
20
20
|
// Error messages
|
|
21
21
|
const ERROR_PREFIX = '[s3mini] ';
|
|
@@ -241,7 +241,7 @@ const runInBatches = async (tasks, batchSize = 30, minIntervalMs = 0) => {
|
|
|
241
241
|
* // Delete a file
|
|
242
242
|
* await s3.deleteObject('example.txt');
|
|
243
243
|
*/
|
|
244
|
-
class
|
|
244
|
+
class S3mini {
|
|
245
245
|
/**
|
|
246
246
|
* Creates an instance of the S3 class.
|
|
247
247
|
*
|
|
@@ -425,8 +425,12 @@ class s3mini {
|
|
|
425
425
|
headers[HEADER_AMZ_CONTENT_SHA256] = UNSIGNED_PAYLOAD; // body ? U.hash(body) : C.UNSIGNED_PAYLOAD;
|
|
426
426
|
headers[HEADER_AMZ_DATE] = fullDatetime;
|
|
427
427
|
headers[HEADER_HOST] = url.host;
|
|
428
|
-
|
|
429
|
-
const
|
|
428
|
+
// sort headers alphabetically by key
|
|
429
|
+
const ignoredHeaders = ['authorization', 'content-length', 'content-type', 'user-agent'];
|
|
430
|
+
let headersForSigning = Object.fromEntries(Object.entries(headers).filter(([key]) => !ignoredHeaders.includes(key.toLowerCase())));
|
|
431
|
+
headersForSigning = Object.fromEntries(Object.entries(headersForSigning).sort(([keyA], [keyB]) => keyA.localeCompare(keyB)));
|
|
432
|
+
const canonicalHeaders = this._buildCanonicalHeaders(headersForSigning);
|
|
433
|
+
const signedHeaders = Object.keys(headersForSigning)
|
|
430
434
|
.map(key => key.toLowerCase())
|
|
431
435
|
.sort()
|
|
432
436
|
.join(';');
|
|
@@ -440,18 +444,18 @@ class s3mini {
|
|
|
440
444
|
_buildCanonicalHeaders(headers) {
|
|
441
445
|
return Object.entries(headers)
|
|
442
446
|
.map(([key, value]) => `${key.toLowerCase()}:${String(value).trim()}`)
|
|
443
|
-
.sort()
|
|
444
447
|
.join('\n');
|
|
445
448
|
}
|
|
446
449
|
_buildCanonicalRequest(method, url, query, canonicalHeaders, signedHeaders) {
|
|
447
|
-
|
|
450
|
+
const parts = [
|
|
448
451
|
method,
|
|
449
452
|
url.pathname,
|
|
450
453
|
this._buildCanonicalQueryString(query),
|
|
451
|
-
|
|
454
|
+
canonicalHeaders + '\n', // Canonical headers end with extra newline
|
|
452
455
|
signedHeaders,
|
|
453
456
|
UNSIGNED_PAYLOAD,
|
|
454
|
-
]
|
|
457
|
+
];
|
|
458
|
+
return parts.join('\n');
|
|
455
459
|
}
|
|
456
460
|
_buildCredentialScope(shortDatetime) {
|
|
457
461
|
return [shortDatetime, this.region, S3_SERVICE, AWS_REQUEST_TYPE].join('/');
|
|
@@ -485,9 +489,6 @@ class s3mini {
|
|
|
485
489
|
if (!['GET', 'HEAD', 'PUT', 'POST', 'DELETE'].includes(method)) {
|
|
486
490
|
throw new Error(`${ERROR_PREFIX}Unsupported HTTP method ${method}`);
|
|
487
491
|
}
|
|
488
|
-
if (key) {
|
|
489
|
-
this._checkKey(key); // allow '' for bucket‑level
|
|
490
|
-
}
|
|
491
492
|
const { filteredOpts, conditionalHeaders } = ['GET', 'HEAD'].includes(method)
|
|
492
493
|
? this._filterIfHeaders(query)
|
|
493
494
|
: { filteredOpts: query, conditionalHeaders: {} };
|
|
@@ -603,7 +604,7 @@ class s3mini {
|
|
|
603
604
|
* @param {string} [prefix=''] - The prefix to filter objects by.
|
|
604
605
|
* @param {number} [maxKeys] - The maximum number of keys to return. If not provided, all keys will be returned.
|
|
605
606
|
* @param {Record<string, unknown>} [opts={}] - Additional options for the request.
|
|
606
|
-
* @returns {Promise<
|
|
607
|
+
* @returns {Promise<IT.ListObject[] | null>} A promise that resolves to an array of objects or null if the bucket is empty.
|
|
607
608
|
* @example
|
|
608
609
|
* // List all objects
|
|
609
610
|
* const objects = await s3.listObjects();
|
|
@@ -709,11 +710,17 @@ class s3mini {
|
|
|
709
710
|
* Get an object from the S3-compatible service.
|
|
710
711
|
* This method sends a request to retrieve the specified object from the S3-compatible service.
|
|
711
712
|
* @param {string} key - The key of the object to retrieve.
|
|
712
|
-
* @param {Record<string, unknown>} [opts
|
|
713
|
+
* @param {Record<string, unknown>} [opts] - Additional options for the request.
|
|
714
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
713
715
|
* @returns A promise that resolves to the object data (string) or null if not found.
|
|
714
716
|
*/
|
|
715
|
-
async getObject(key, opts = {}) {
|
|
716
|
-
|
|
717
|
+
async getObject(key, opts = {}, ssecHeaders) {
|
|
718
|
+
// if ssecHeaders is set, add it to headers
|
|
719
|
+
const res = await this._signedRequest('GET', key, {
|
|
720
|
+
query: opts, // use opts.query if it exists, otherwise use an empty object
|
|
721
|
+
tolerated: [200, 404, 412, 304],
|
|
722
|
+
headers: ssecHeaders ? { ...ssecHeaders } : undefined,
|
|
723
|
+
});
|
|
717
724
|
if ([404, 412, 304].includes(res.status)) {
|
|
718
725
|
return null;
|
|
719
726
|
}
|
|
@@ -724,10 +731,15 @@ class s3mini {
|
|
|
724
731
|
* This method sends a request to retrieve the specified object and returns the full response.
|
|
725
732
|
* @param {string} key - The key of the object to retrieve.
|
|
726
733
|
* @param {Record<string, unknown>} [opts={}] - Additional options for the request.
|
|
734
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
727
735
|
* @returns A promise that resolves to the Response object or null if not found.
|
|
728
736
|
*/
|
|
729
|
-
async getObjectResponse(key, opts = {}) {
|
|
730
|
-
const res = await this._signedRequest('GET', key, {
|
|
737
|
+
async getObjectResponse(key, opts = {}, ssecHeaders) {
|
|
738
|
+
const res = await this._signedRequest('GET', key, {
|
|
739
|
+
query: opts,
|
|
740
|
+
tolerated: [200, 404, 412, 304],
|
|
741
|
+
headers: ssecHeaders ? { ...ssecHeaders } : undefined,
|
|
742
|
+
});
|
|
731
743
|
if ([404, 412, 304].includes(res.status)) {
|
|
732
744
|
return null;
|
|
733
745
|
}
|
|
@@ -738,10 +750,15 @@ class s3mini {
|
|
|
738
750
|
* This method sends a request to retrieve the specified object and returns it as an ArrayBuffer.
|
|
739
751
|
* @param {string} key - The key of the object to retrieve.
|
|
740
752
|
* @param {Record<string, unknown>} [opts={}] - Additional options for the request.
|
|
753
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
741
754
|
* @returns A promise that resolves to the object data as an ArrayBuffer or null if not found.
|
|
742
755
|
*/
|
|
743
|
-
async getObjectArrayBuffer(key, opts = {}) {
|
|
744
|
-
const res = await this._signedRequest('GET', key, {
|
|
756
|
+
async getObjectArrayBuffer(key, opts = {}, ssecHeaders) {
|
|
757
|
+
const res = await this._signedRequest('GET', key, {
|
|
758
|
+
query: opts,
|
|
759
|
+
tolerated: [200, 404, 412, 304],
|
|
760
|
+
headers: ssecHeaders ? { ...ssecHeaders } : undefined,
|
|
761
|
+
});
|
|
745
762
|
if ([404, 412, 304].includes(res.status)) {
|
|
746
763
|
return null;
|
|
747
764
|
}
|
|
@@ -752,10 +769,15 @@ class s3mini {
|
|
|
752
769
|
* This method sends a request to retrieve the specified object and returns it as JSON.
|
|
753
770
|
* @param {string} key - The key of the object to retrieve.
|
|
754
771
|
* @param {Record<string, unknown>} [opts={}] - Additional options for the request.
|
|
772
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
755
773
|
* @returns A promise that resolves to the object data as JSON or null if not found.
|
|
756
774
|
*/
|
|
757
|
-
async getObjectJSON(key, opts = {}) {
|
|
758
|
-
const res = await this._signedRequest('GET', key, {
|
|
775
|
+
async getObjectJSON(key, opts = {}, ssecHeaders) {
|
|
776
|
+
const res = await this._signedRequest('GET', key, {
|
|
777
|
+
query: opts,
|
|
778
|
+
tolerated: [200, 404, 412, 304],
|
|
779
|
+
headers: ssecHeaders ? { ...ssecHeaders } : undefined,
|
|
780
|
+
});
|
|
759
781
|
if ([404, 412, 304].includes(res.status)) {
|
|
760
782
|
return null;
|
|
761
783
|
}
|
|
@@ -766,11 +788,16 @@ class s3mini {
|
|
|
766
788
|
* This method sends a request to retrieve the specified object and its ETag.
|
|
767
789
|
* @param {string} key - The key of the object to retrieve.
|
|
768
790
|
* @param {Record<string, unknown>} [opts={}] - Additional options for the request.
|
|
791
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
769
792
|
* @returns A promise that resolves to an object containing the ETag and the object data as an ArrayBuffer or null if not found.
|
|
770
793
|
*/
|
|
771
|
-
async getObjectWithETag(key, opts = {}) {
|
|
794
|
+
async getObjectWithETag(key, opts = {}, ssecHeaders) {
|
|
772
795
|
try {
|
|
773
|
-
const res = await this._signedRequest('GET', key, {
|
|
796
|
+
const res = await this._signedRequest('GET', key, {
|
|
797
|
+
query: opts,
|
|
798
|
+
tolerated: [200, 404, 412, 304],
|
|
799
|
+
headers: ssecHeaders ? { ...ssecHeaders } : undefined,
|
|
800
|
+
});
|
|
774
801
|
if ([404, 412, 304].includes(res.status)) {
|
|
775
802
|
return { etag: null, data: null };
|
|
776
803
|
}
|
|
@@ -793,13 +820,14 @@ class s3mini {
|
|
|
793
820
|
* @param {number} [rangeFrom=0] - The starting byte for the range (if not whole file).
|
|
794
821
|
* @param {number} [rangeTo=this.requestSizeInBytes] - The ending byte for the range (if not whole file).
|
|
795
822
|
* @param {Record<string, unknown>} [opts={}] - Additional options for the request.
|
|
823
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
796
824
|
* @returns A promise that resolves to the Response object.
|
|
797
825
|
*/
|
|
798
|
-
async getObjectRaw(key, wholeFile = true, rangeFrom = 0, rangeTo = this.requestSizeInBytes, opts = {}) {
|
|
826
|
+
async getObjectRaw(key, wholeFile = true, rangeFrom = 0, rangeTo = this.requestSizeInBytes, opts = {}, ssecHeaders) {
|
|
799
827
|
const rangeHdr = wholeFile ? {} : { range: `bytes=${rangeFrom}-${rangeTo - 1}` };
|
|
800
828
|
return this._signedRequest('GET', key, {
|
|
801
829
|
query: { ...opts },
|
|
802
|
-
headers: rangeHdr,
|
|
830
|
+
headers: { ...rangeHdr, ...ssecHeaders },
|
|
803
831
|
withQuery: true, // keep ?query=string behaviour
|
|
804
832
|
});
|
|
805
833
|
}
|
|
@@ -810,9 +838,11 @@ class s3mini {
|
|
|
810
838
|
* @returns A promise that resolves to the content length of the object in bytes, or 0 if not found.
|
|
811
839
|
* @throws {Error} If the content length header is not found in the response.
|
|
812
840
|
*/
|
|
813
|
-
async getContentLength(key) {
|
|
841
|
+
async getContentLength(key, ssecHeaders) {
|
|
814
842
|
try {
|
|
815
|
-
const res = await this._signedRequest('HEAD', key
|
|
843
|
+
const res = await this._signedRequest('HEAD', key, {
|
|
844
|
+
headers: ssecHeaders ? { ...ssecHeaders } : undefined,
|
|
845
|
+
});
|
|
816
846
|
const len = res.headers.get(HEADER_CONTENT_LENGTH);
|
|
817
847
|
return len ? +len : 0;
|
|
818
848
|
}
|
|
@@ -845,6 +875,7 @@ class s3mini {
|
|
|
845
875
|
* Retrieves the ETag of an object without downloading its content.
|
|
846
876
|
* @param {string} key - The key of the object to retrieve the ETag for.
|
|
847
877
|
* @param {Record<string, unknown>} [opts={}] - Additional options for the request.
|
|
878
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
848
879
|
* @returns {Promise<string | null>} A promise that resolves to the ETag value or null if the object is not found.
|
|
849
880
|
* @throws {Error} If the ETag header is not found in the response.
|
|
850
881
|
* @example
|
|
@@ -853,14 +884,18 @@ class s3mini {
|
|
|
853
884
|
* console.log(`File ETag: ${etag}`);
|
|
854
885
|
* }
|
|
855
886
|
*/
|
|
856
|
-
async getEtag(key, opts = {}) {
|
|
887
|
+
async getEtag(key, opts = {}, ssecHeaders) {
|
|
857
888
|
const res = await this._signedRequest('HEAD', key, {
|
|
858
889
|
query: opts,
|
|
859
|
-
tolerated: [200, 404],
|
|
890
|
+
tolerated: [200, 304, 404, 412],
|
|
891
|
+
headers: ssecHeaders ? { ...ssecHeaders } : undefined,
|
|
860
892
|
});
|
|
861
893
|
if (res.status === 404) {
|
|
862
894
|
return null;
|
|
863
895
|
}
|
|
896
|
+
if (res.status === 412 || res.status === 304) {
|
|
897
|
+
return null; // ETag mismatch
|
|
898
|
+
}
|
|
864
899
|
const etag = res.headers.get(HEADER_ETAG);
|
|
865
900
|
if (!etag) {
|
|
866
901
|
throw new Error(`${ERROR_PREFIX}ETag not found in response headers`);
|
|
@@ -872,6 +907,7 @@ class s3mini {
|
|
|
872
907
|
* @param {string} key - The key/path where the object will be stored.
|
|
873
908
|
* @param {string | Buffer} data - The data to upload (string or Buffer).
|
|
874
909
|
* @param {string} [fileType='application/octet-stream'] - The MIME type of the file.
|
|
910
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
875
911
|
* @returns {Promise<Response>} A promise that resolves to the Response object from the upload request.
|
|
876
912
|
* @throws {TypeError} If data is not a string or Buffer.
|
|
877
913
|
* @example
|
|
@@ -882,7 +918,7 @@ class s3mini {
|
|
|
882
918
|
* const buffer = Buffer.from([0x89, 0x50, 0x4e, 0x47]);
|
|
883
919
|
* await s3.putObject('image.png', buffer, 'image/png');
|
|
884
920
|
*/
|
|
885
|
-
async putObject(key, data, fileType = DEFAULT_STREAM_CONTENT_TYPE) {
|
|
921
|
+
async putObject(key, data, fileType = DEFAULT_STREAM_CONTENT_TYPE, ssecHeaders) {
|
|
886
922
|
if (!(data instanceof Buffer || typeof data === 'string')) {
|
|
887
923
|
throw new TypeError(ERROR_DATA_BUFFER_REQUIRED);
|
|
888
924
|
}
|
|
@@ -891,6 +927,7 @@ class s3mini {
|
|
|
891
927
|
headers: {
|
|
892
928
|
[HEADER_CONTENT_LENGTH]: typeof data === 'string' ? Buffer.byteLength(data) : data.length,
|
|
893
929
|
[HEADER_CONTENT_TYPE]: fileType,
|
|
930
|
+
...ssecHeaders,
|
|
894
931
|
},
|
|
895
932
|
tolerated: [200],
|
|
896
933
|
});
|
|
@@ -899,6 +936,7 @@ class s3mini {
|
|
|
899
936
|
* Initiates a multipart upload and returns the upload ID.
|
|
900
937
|
* @param {string} key - The key/path where the object will be stored.
|
|
901
938
|
* @param {string} [fileType='application/octet-stream'] - The MIME type of the file.
|
|
939
|
+
* @param {IT.SSECHeaders?} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
902
940
|
* @returns {Promise<string>} A promise that resolves to the upload ID for the multipart upload.
|
|
903
941
|
* @throws {TypeError} If key is invalid or fileType is not a string.
|
|
904
942
|
* @throws {Error} If the multipart upload fails to initialize.
|
|
@@ -906,13 +944,13 @@ class s3mini {
|
|
|
906
944
|
* const uploadId = await s3.getMultipartUploadId('large-file.zip', 'application/zip');
|
|
907
945
|
* console.log(`Started multipart upload: ${uploadId}`);
|
|
908
946
|
*/
|
|
909
|
-
async getMultipartUploadId(key, fileType = DEFAULT_STREAM_CONTENT_TYPE) {
|
|
947
|
+
async getMultipartUploadId(key, fileType = DEFAULT_STREAM_CONTENT_TYPE, ssecHeaders) {
|
|
910
948
|
this._checkKey(key);
|
|
911
949
|
if (typeof fileType !== 'string') {
|
|
912
950
|
throw new TypeError(`${ERROR_PREFIX}fileType must be a string`);
|
|
913
951
|
}
|
|
914
952
|
const query = { uploads: '' };
|
|
915
|
-
const headers = { [HEADER_CONTENT_TYPE]: fileType };
|
|
953
|
+
const headers = { [HEADER_CONTENT_TYPE]: fileType, ...ssecHeaders };
|
|
916
954
|
const res = await this._signedRequest('POST', key, {
|
|
917
955
|
query,
|
|
918
956
|
headers,
|
|
@@ -949,6 +987,7 @@ class s3mini {
|
|
|
949
987
|
* @param {Buffer | string} data - The data for this part.
|
|
950
988
|
* @param {number} partNumber - The part number (must be between 1 and 10,000).
|
|
951
989
|
* @param {Record<string, unknown>} [opts={}] - Additional options for the request.
|
|
990
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
952
991
|
* @returns {Promise<IT.UploadPart>} A promise that resolves to an object containing the partNumber and etag.
|
|
953
992
|
* @throws {TypeError} If any parameter is invalid.
|
|
954
993
|
* @example
|
|
@@ -960,13 +999,16 @@ class s3mini {
|
|
|
960
999
|
* );
|
|
961
1000
|
* console.log(`Part ${part.partNumber} uploaded with ETag: ${part.etag}`);
|
|
962
1001
|
*/
|
|
963
|
-
async uploadPart(key, uploadId, data, partNumber, opts = {}) {
|
|
1002
|
+
async uploadPart(key, uploadId, data, partNumber, opts = {}, ssecHeaders) {
|
|
964
1003
|
this._validateUploadPartParams(key, uploadId, data, partNumber, opts);
|
|
965
1004
|
const query = { uploadId, partNumber, ...opts };
|
|
966
1005
|
const res = await this._signedRequest('PUT', key, {
|
|
967
1006
|
query,
|
|
968
1007
|
body: data,
|
|
969
|
-
headers: {
|
|
1008
|
+
headers: {
|
|
1009
|
+
[HEADER_CONTENT_LENGTH]: typeof data === 'string' ? Buffer.byteLength(data) : data.length,
|
|
1010
|
+
...ssecHeaders,
|
|
1011
|
+
},
|
|
970
1012
|
});
|
|
971
1013
|
return { partNumber, etag: sanitizeETag(res.headers.get('etag') || '') };
|
|
972
1014
|
}
|
|
@@ -1024,6 +1066,7 @@ class s3mini {
|
|
|
1024
1066
|
* Aborts a multipart upload and removes all uploaded parts.
|
|
1025
1067
|
* @param {string} key - The key of the object being uploaded.
|
|
1026
1068
|
* @param {string} uploadId - The upload ID to abort.
|
|
1069
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
1027
1070
|
* @returns {Promise<object>} A promise that resolves to an object containing the abort status and details.
|
|
1028
1071
|
* @throws {TypeError} If key or uploadId is invalid.
|
|
1029
1072
|
* @throws {Error} If the abort operation fails.
|
|
@@ -1035,13 +1078,13 @@ class s3mini {
|
|
|
1035
1078
|
* console.error('Failed to abort upload:', error);
|
|
1036
1079
|
* }
|
|
1037
1080
|
*/
|
|
1038
|
-
async abortMultipartUpload(key, uploadId) {
|
|
1081
|
+
async abortMultipartUpload(key, uploadId, ssecHeaders) {
|
|
1039
1082
|
this._checkKey(key);
|
|
1040
1083
|
if (!uploadId) {
|
|
1041
1084
|
throw new TypeError(ERROR_UPLOAD_ID_REQUIRED);
|
|
1042
1085
|
}
|
|
1043
1086
|
const query = { uploadId };
|
|
1044
|
-
const headers = { [HEADER_CONTENT_TYPE]: XML_CONTENT_TYPE };
|
|
1087
|
+
const headers = { [HEADER_CONTENT_TYPE]: XML_CONTENT_TYPE, ...(ssecHeaders ? { ...ssecHeaders } : {}) };
|
|
1045
1088
|
const res = await this._signedRequest('DELETE', key, {
|
|
1046
1089
|
query,
|
|
1047
1090
|
headers,
|
|
@@ -1213,6 +1256,10 @@ class s3mini {
|
|
|
1213
1256
|
return hmac(kService, AWS_REQUEST_TYPE);
|
|
1214
1257
|
}
|
|
1215
1258
|
}
|
|
1259
|
+
/**
|
|
1260
|
+
* @deprecated Use `S3mini` instead.
|
|
1261
|
+
*/
|
|
1262
|
+
const s3mini = S3mini;
|
|
1216
1263
|
|
|
1217
|
-
export {
|
|
1264
|
+
export { S3mini, S3mini as default, runInBatches, s3mini, sanitizeETag };
|
|
1218
1265
|
//# sourceMappingURL=s3mini.js.map
|