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 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 { s3mini, sanitizeETag } from 's3mini';
110
+ import { S3mini, sanitizeETag } from 's3mini';
104
111
 
105
- const s3client = new s3mini({
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 s3mini.getObject(smallObjectKey, { 'if-none-match': etag });
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 s3mini {
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<object[] | null>} A promise that resolves to an array of objects or null if the bucket is empty.
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<object[] | null>;
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={}] - Additional options for the request.
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>): Promise<string | null>;
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>): Promise<Response | null>;
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>): Promise<ArrayBuffer | null>;
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>): Promise<T | null>;
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>): Promise<{
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>): Promise<Response>;
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>): Promise<string | null>;
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>): Promise<UploadPart>;
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 { type CompleteMultipartUploadResult, type ErrorWithCode, type ExistResponseCode, type ListBucketResponse, type ListMultipartUploadResponse, type Logger, type S3Config, type UploadPart, s3mini as default, runInBatches, s3mini, sanitizeETag };
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 = 'Authorization';
17
- const HEADER_CONTENT_TYPE = 'Content-Type';
18
- const HEADER_CONTENT_LENGTH = '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 s3mini {
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
- const canonicalHeaders = this._buildCanonicalHeaders(headers);
429
- const signedHeaders = Object.keys(headers)
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
- return [
450
+ const parts = [
448
451
  method,
449
452
  url.pathname,
450
453
  this._buildCanonicalQueryString(query),
451
- `${canonicalHeaders}\n`,
454
+ canonicalHeaders + '\n', // Canonical headers end with extra newline
452
455
  signedHeaders,
453
456
  UNSIGNED_PAYLOAD,
454
- ].join('\n');
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<object[] | null>} A promise that resolves to an array of objects or null if the bucket is empty.
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={}] - Additional options for the request.
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
- const res = await this._signedRequest('GET', key, { query: opts, tolerated: [200, 404, 412, 304] });
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, { query: opts, tolerated: [200, 404, 412, 304] });
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, { query: opts, tolerated: [200, 404, 412, 304] });
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, { query: opts, tolerated: [200, 404, 412, 304] });
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, { query: opts, tolerated: [200, 404, 412, 304] });
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: { [HEADER_CONTENT_LENGTH]: typeof data === 'string' ? Buffer.byteLength(data) : data.length },
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 { s3mini as default, runInBatches, s3mini, sanitizeETag };
1264
+ export { S3mini, S3mini as default, runInBatches, s3mini, sanitizeETag };
1218
1265
  //# sourceMappingURL=s3mini.js.map