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/src/S3.ts
CHANGED
|
@@ -27,7 +27,7 @@ import * as U from './utils.js';
|
|
|
27
27
|
* // Delete a file
|
|
28
28
|
* await s3.deleteObject('example.txt');
|
|
29
29
|
*/
|
|
30
|
-
class
|
|
30
|
+
class S3mini {
|
|
31
31
|
/**
|
|
32
32
|
* Creates an instance of the S3 class.
|
|
33
33
|
*
|
|
@@ -260,8 +260,17 @@ class s3mini {
|
|
|
260
260
|
headers[C.HEADER_AMZ_CONTENT_SHA256] = C.UNSIGNED_PAYLOAD; // body ? U.hash(body) : C.UNSIGNED_PAYLOAD;
|
|
261
261
|
headers[C.HEADER_AMZ_DATE] = fullDatetime;
|
|
262
262
|
headers[C.HEADER_HOST] = url.host;
|
|
263
|
-
|
|
264
|
-
const
|
|
263
|
+
// sort headers alphabetically by key
|
|
264
|
+
const ignoredHeaders = ['authorization', 'content-length', 'content-type', 'user-agent'];
|
|
265
|
+
let headersForSigning = Object.fromEntries(
|
|
266
|
+
Object.entries(headers).filter(([key]) => !ignoredHeaders.includes(key.toLowerCase())),
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
headersForSigning = Object.fromEntries(
|
|
270
|
+
Object.entries(headersForSigning).sort(([keyA], [keyB]) => keyA.localeCompare(keyB)),
|
|
271
|
+
);
|
|
272
|
+
const canonicalHeaders = this._buildCanonicalHeaders(headersForSigning);
|
|
273
|
+
const signedHeaders = Object.keys(headersForSigning)
|
|
265
274
|
.map(key => key.toLowerCase())
|
|
266
275
|
.sort()
|
|
267
276
|
.join(';');
|
|
@@ -277,7 +286,6 @@ class s3mini {
|
|
|
277
286
|
private _buildCanonicalHeaders(headers: Record<string, string | number>): string {
|
|
278
287
|
return Object.entries(headers)
|
|
279
288
|
.map(([key, value]) => `${key.toLowerCase()}:${String(value).trim()}`)
|
|
280
|
-
.sort()
|
|
281
289
|
.join('\n');
|
|
282
290
|
}
|
|
283
291
|
|
|
@@ -288,14 +296,15 @@ class s3mini {
|
|
|
288
296
|
canonicalHeaders: string,
|
|
289
297
|
signedHeaders: string,
|
|
290
298
|
): string {
|
|
291
|
-
|
|
299
|
+
const parts = [
|
|
292
300
|
method,
|
|
293
301
|
url.pathname,
|
|
294
302
|
this._buildCanonicalQueryString(query),
|
|
295
|
-
|
|
303
|
+
canonicalHeaders + '\n', // Canonical headers end with extra newline
|
|
296
304
|
signedHeaders,
|
|
297
305
|
C.UNSIGNED_PAYLOAD,
|
|
298
|
-
]
|
|
306
|
+
];
|
|
307
|
+
return parts.join('\n');
|
|
299
308
|
}
|
|
300
309
|
|
|
301
310
|
private _buildCredentialScope(shortDatetime: string): string {
|
|
@@ -332,9 +341,9 @@ class s3mini {
|
|
|
332
341
|
tolerated = [], // [200, 404] etc.
|
|
333
342
|
withQuery = false, // append query string to signed URL
|
|
334
343
|
}: {
|
|
335
|
-
query?: Record<string, unknown
|
|
344
|
+
query?: Record<string, unknown> | undefined;
|
|
336
345
|
body?: string | Buffer | undefined;
|
|
337
|
-
headers?: Record<string, string | number | undefined
|
|
346
|
+
headers?: Record<string, string | number | undefined> | IT.SSECHeaders | undefined;
|
|
338
347
|
tolerated?: number[] | undefined;
|
|
339
348
|
withQuery?: boolean | undefined;
|
|
340
349
|
} = {},
|
|
@@ -343,14 +352,10 @@ class s3mini {
|
|
|
343
352
|
if (!['GET', 'HEAD', 'PUT', 'POST', 'DELETE'].includes(method)) {
|
|
344
353
|
throw new Error(`${C.ERROR_PREFIX}Unsupported HTTP method ${method as string}`);
|
|
345
354
|
}
|
|
346
|
-
if (key) {
|
|
347
|
-
this._checkKey(key); // allow '' for bucket‑level
|
|
348
|
-
}
|
|
349
355
|
|
|
350
356
|
const { filteredOpts, conditionalHeaders } = ['GET', 'HEAD'].includes(method)
|
|
351
357
|
? this._filterIfHeaders(query)
|
|
352
358
|
: { filteredOpts: query, conditionalHeaders: {} };
|
|
353
|
-
|
|
354
359
|
const baseHeaders: Record<string, string | number> = {
|
|
355
360
|
[C.HEADER_AMZ_CONTENT_SHA256]: C.UNSIGNED_PAYLOAD,
|
|
356
361
|
// ...(['GET', 'HEAD'].includes(method) ? { [C.HEADER_CONTENT_TYPE]: C.JSON_CONTENT_TYPE } : {}),
|
|
@@ -475,7 +480,7 @@ class s3mini {
|
|
|
475
480
|
* @param {string} [prefix=''] - The prefix to filter objects by.
|
|
476
481
|
* @param {number} [maxKeys] - The maximum number of keys to return. If not provided, all keys will be returned.
|
|
477
482
|
* @param {Record<string, unknown>} [opts={}] - Additional options for the request.
|
|
478
|
-
* @returns {Promise<
|
|
483
|
+
* @returns {Promise<IT.ListObject[] | null>} A promise that resolves to an array of objects or null if the bucket is empty.
|
|
479
484
|
* @example
|
|
480
485
|
* // List all objects
|
|
481
486
|
* const objects = await s3.listObjects();
|
|
@@ -489,7 +494,7 @@ class s3mini {
|
|
|
489
494
|
maxKeys?: number,
|
|
490
495
|
// method: IT.HttpMethod = 'GET', // 'GET' or 'HEAD'
|
|
491
496
|
opts: Record<string, unknown> = {},
|
|
492
|
-
): Promise<
|
|
497
|
+
): Promise<IT.ListObject[] | null> {
|
|
493
498
|
this._checkDelimiter(delimiter);
|
|
494
499
|
this._checkPrefix(prefix);
|
|
495
500
|
this._checkOpts(opts);
|
|
@@ -499,7 +504,7 @@ class s3mini {
|
|
|
499
504
|
const unlimited = !(maxKeys && maxKeys > 0);
|
|
500
505
|
let remaining = unlimited ? Infinity : maxKeys;
|
|
501
506
|
let token: string | undefined;
|
|
502
|
-
const all:
|
|
507
|
+
const all: IT.ListObject[] = [];
|
|
503
508
|
|
|
504
509
|
do {
|
|
505
510
|
const batchSize = Math.min(remaining, 1000); // S3 ceiling
|
|
@@ -542,7 +547,7 @@ class s3mini {
|
|
|
542
547
|
const contents = out.Contents || out.contents; // S3 v2 vs v1
|
|
543
548
|
if (contents) {
|
|
544
549
|
const batch = Array.isArray(contents) ? contents : [contents];
|
|
545
|
-
all.push(...(batch as
|
|
550
|
+
all.push(...(batch as IT.ListObject[]));
|
|
546
551
|
if (!unlimited) {
|
|
547
552
|
remaining -= batch.length;
|
|
548
553
|
}
|
|
@@ -607,11 +612,21 @@ class s3mini {
|
|
|
607
612
|
* Get an object from the S3-compatible service.
|
|
608
613
|
* This method sends a request to retrieve the specified object from the S3-compatible service.
|
|
609
614
|
* @param {string} key - The key of the object to retrieve.
|
|
610
|
-
* @param {Record<string, unknown>} [opts
|
|
615
|
+
* @param {Record<string, unknown>} [opts] - Additional options for the request.
|
|
616
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
611
617
|
* @returns A promise that resolves to the object data (string) or null if not found.
|
|
612
618
|
*/
|
|
613
|
-
public async getObject(
|
|
614
|
-
|
|
619
|
+
public async getObject(
|
|
620
|
+
key: string,
|
|
621
|
+
opts: Record<string, unknown> = {},
|
|
622
|
+
ssecHeaders?: IT.SSECHeaders,
|
|
623
|
+
): Promise<string | null> {
|
|
624
|
+
// if ssecHeaders is set, add it to headers
|
|
625
|
+
const res = await this._signedRequest('GET', key, {
|
|
626
|
+
query: opts, // use opts.query if it exists, otherwise use an empty object
|
|
627
|
+
tolerated: [200, 404, 412, 304],
|
|
628
|
+
headers: ssecHeaders ? { ...ssecHeaders } : undefined,
|
|
629
|
+
});
|
|
615
630
|
if ([404, 412, 304].includes(res.status)) {
|
|
616
631
|
return null;
|
|
617
632
|
}
|
|
@@ -623,10 +638,19 @@ class s3mini {
|
|
|
623
638
|
* This method sends a request to retrieve the specified object and returns the full response.
|
|
624
639
|
* @param {string} key - The key of the object to retrieve.
|
|
625
640
|
* @param {Record<string, unknown>} [opts={}] - Additional options for the request.
|
|
641
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
626
642
|
* @returns A promise that resolves to the Response object or null if not found.
|
|
627
643
|
*/
|
|
628
|
-
public async getObjectResponse(
|
|
629
|
-
|
|
644
|
+
public async getObjectResponse(
|
|
645
|
+
key: string,
|
|
646
|
+
opts: Record<string, unknown> = {},
|
|
647
|
+
ssecHeaders?: IT.SSECHeaders,
|
|
648
|
+
): Promise<Response | null> {
|
|
649
|
+
const res = await this._signedRequest('GET', key, {
|
|
650
|
+
query: opts,
|
|
651
|
+
tolerated: [200, 404, 412, 304],
|
|
652
|
+
headers: ssecHeaders ? { ...ssecHeaders } : undefined,
|
|
653
|
+
});
|
|
630
654
|
if ([404, 412, 304].includes(res.status)) {
|
|
631
655
|
return null;
|
|
632
656
|
}
|
|
@@ -638,10 +662,19 @@ class s3mini {
|
|
|
638
662
|
* This method sends a request to retrieve the specified object and returns it as an ArrayBuffer.
|
|
639
663
|
* @param {string} key - The key of the object to retrieve.
|
|
640
664
|
* @param {Record<string, unknown>} [opts={}] - Additional options for the request.
|
|
665
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
641
666
|
* @returns A promise that resolves to the object data as an ArrayBuffer or null if not found.
|
|
642
667
|
*/
|
|
643
|
-
public async getObjectArrayBuffer(
|
|
644
|
-
|
|
668
|
+
public async getObjectArrayBuffer(
|
|
669
|
+
key: string,
|
|
670
|
+
opts: Record<string, unknown> = {},
|
|
671
|
+
ssecHeaders?: IT.SSECHeaders,
|
|
672
|
+
): Promise<ArrayBuffer | null> {
|
|
673
|
+
const res = await this._signedRequest('GET', key, {
|
|
674
|
+
query: opts,
|
|
675
|
+
tolerated: [200, 404, 412, 304],
|
|
676
|
+
headers: ssecHeaders ? { ...ssecHeaders } : undefined,
|
|
677
|
+
});
|
|
645
678
|
if ([404, 412, 304].includes(res.status)) {
|
|
646
679
|
return null;
|
|
647
680
|
}
|
|
@@ -653,10 +686,19 @@ class s3mini {
|
|
|
653
686
|
* This method sends a request to retrieve the specified object and returns it as JSON.
|
|
654
687
|
* @param {string} key - The key of the object to retrieve.
|
|
655
688
|
* @param {Record<string, unknown>} [opts={}] - Additional options for the request.
|
|
689
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
656
690
|
* @returns A promise that resolves to the object data as JSON or null if not found.
|
|
657
691
|
*/
|
|
658
|
-
public async getObjectJSON<T = unknown>(
|
|
659
|
-
|
|
692
|
+
public async getObjectJSON<T = unknown>(
|
|
693
|
+
key: string,
|
|
694
|
+
opts: Record<string, unknown> = {},
|
|
695
|
+
ssecHeaders?: IT.SSECHeaders,
|
|
696
|
+
): Promise<T | null> {
|
|
697
|
+
const res = await this._signedRequest('GET', key, {
|
|
698
|
+
query: opts,
|
|
699
|
+
tolerated: [200, 404, 412, 304],
|
|
700
|
+
headers: ssecHeaders ? { ...ssecHeaders } : undefined,
|
|
701
|
+
});
|
|
660
702
|
if ([404, 412, 304].includes(res.status)) {
|
|
661
703
|
return null;
|
|
662
704
|
}
|
|
@@ -668,14 +710,20 @@ class s3mini {
|
|
|
668
710
|
* This method sends a request to retrieve the specified object and its ETag.
|
|
669
711
|
* @param {string} key - The key of the object to retrieve.
|
|
670
712
|
* @param {Record<string, unknown>} [opts={}] - Additional options for the request.
|
|
713
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
671
714
|
* @returns A promise that resolves to an object containing the ETag and the object data as an ArrayBuffer or null if not found.
|
|
672
715
|
*/
|
|
673
716
|
public async getObjectWithETag(
|
|
674
717
|
key: string,
|
|
675
718
|
opts: Record<string, unknown> = {},
|
|
719
|
+
ssecHeaders?: IT.SSECHeaders,
|
|
676
720
|
): Promise<{ etag: string | null; data: ArrayBuffer | null }> {
|
|
677
721
|
try {
|
|
678
|
-
const res = await this._signedRequest('GET', key, {
|
|
722
|
+
const res = await this._signedRequest('GET', key, {
|
|
723
|
+
query: opts,
|
|
724
|
+
tolerated: [200, 404, 412, 304],
|
|
725
|
+
headers: ssecHeaders ? { ...ssecHeaders } : undefined,
|
|
726
|
+
});
|
|
679
727
|
|
|
680
728
|
if ([404, 412, 304].includes(res.status)) {
|
|
681
729
|
return { etag: null, data: null };
|
|
@@ -700,6 +748,7 @@ class s3mini {
|
|
|
700
748
|
* @param {number} [rangeFrom=0] - The starting byte for the range (if not whole file).
|
|
701
749
|
* @param {number} [rangeTo=this.requestSizeInBytes] - The ending byte for the range (if not whole file).
|
|
702
750
|
* @param {Record<string, unknown>} [opts={}] - Additional options for the request.
|
|
751
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
703
752
|
* @returns A promise that resolves to the Response object.
|
|
704
753
|
*/
|
|
705
754
|
public async getObjectRaw(
|
|
@@ -708,12 +757,13 @@ class s3mini {
|
|
|
708
757
|
rangeFrom = 0,
|
|
709
758
|
rangeTo = this.requestSizeInBytes,
|
|
710
759
|
opts: Record<string, unknown> = {},
|
|
760
|
+
ssecHeaders?: IT.SSECHeaders,
|
|
711
761
|
): Promise<Response> {
|
|
712
762
|
const rangeHdr: Record<string, string | number> = wholeFile ? {} : { range: `bytes=${rangeFrom}-${rangeTo - 1}` };
|
|
713
763
|
|
|
714
764
|
return this._signedRequest('GET', key, {
|
|
715
765
|
query: { ...opts },
|
|
716
|
-
headers: rangeHdr,
|
|
766
|
+
headers: { ...rangeHdr, ...ssecHeaders },
|
|
717
767
|
withQuery: true, // keep ?query=string behaviour
|
|
718
768
|
});
|
|
719
769
|
}
|
|
@@ -725,9 +775,11 @@ class s3mini {
|
|
|
725
775
|
* @returns A promise that resolves to the content length of the object in bytes, or 0 if not found.
|
|
726
776
|
* @throws {Error} If the content length header is not found in the response.
|
|
727
777
|
*/
|
|
728
|
-
public async getContentLength(key: string): Promise<number> {
|
|
778
|
+
public async getContentLength(key: string, ssecHeaders?: IT.SSECHeaders): Promise<number> {
|
|
729
779
|
try {
|
|
730
|
-
const res = await this._signedRequest('HEAD', key
|
|
780
|
+
const res = await this._signedRequest('HEAD', key, {
|
|
781
|
+
headers: ssecHeaders ? { ...ssecHeaders } : undefined,
|
|
782
|
+
});
|
|
731
783
|
const len = res.headers.get(C.HEADER_CONTENT_LENGTH);
|
|
732
784
|
return len ? +len : 0;
|
|
733
785
|
} catch (err) {
|
|
@@ -762,6 +814,7 @@ class s3mini {
|
|
|
762
814
|
* Retrieves the ETag of an object without downloading its content.
|
|
763
815
|
* @param {string} key - The key of the object to retrieve the ETag for.
|
|
764
816
|
* @param {Record<string, unknown>} [opts={}] - Additional options for the request.
|
|
817
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
765
818
|
* @returns {Promise<string | null>} A promise that resolves to the ETag value or null if the object is not found.
|
|
766
819
|
* @throws {Error} If the ETag header is not found in the response.
|
|
767
820
|
* @example
|
|
@@ -770,16 +823,25 @@ class s3mini {
|
|
|
770
823
|
* console.log(`File ETag: ${etag}`);
|
|
771
824
|
* }
|
|
772
825
|
*/
|
|
773
|
-
public async getEtag(
|
|
826
|
+
public async getEtag(
|
|
827
|
+
key: string,
|
|
828
|
+
opts: Record<string, unknown> = {},
|
|
829
|
+
ssecHeaders?: IT.SSECHeaders,
|
|
830
|
+
): Promise<string | null> {
|
|
774
831
|
const res = await this._signedRequest('HEAD', key, {
|
|
775
832
|
query: opts,
|
|
776
|
-
tolerated: [200, 404],
|
|
833
|
+
tolerated: [200, 304, 404, 412],
|
|
834
|
+
headers: ssecHeaders ? { ...ssecHeaders } : undefined,
|
|
777
835
|
});
|
|
778
836
|
|
|
779
837
|
if (res.status === 404) {
|
|
780
838
|
return null;
|
|
781
839
|
}
|
|
782
840
|
|
|
841
|
+
if (res.status === 412 || res.status === 304) {
|
|
842
|
+
return null; // ETag mismatch
|
|
843
|
+
}
|
|
844
|
+
|
|
783
845
|
const etag = res.headers.get(C.HEADER_ETAG);
|
|
784
846
|
if (!etag) {
|
|
785
847
|
throw new Error(`${C.ERROR_PREFIX}ETag not found in response headers`);
|
|
@@ -793,6 +855,7 @@ class s3mini {
|
|
|
793
855
|
* @param {string} key - The key/path where the object will be stored.
|
|
794
856
|
* @param {string | Buffer} data - The data to upload (string or Buffer).
|
|
795
857
|
* @param {string} [fileType='application/octet-stream'] - The MIME type of the file.
|
|
858
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
796
859
|
* @returns {Promise<Response>} A promise that resolves to the Response object from the upload request.
|
|
797
860
|
* @throws {TypeError} If data is not a string or Buffer.
|
|
798
861
|
* @example
|
|
@@ -807,6 +870,7 @@ class s3mini {
|
|
|
807
870
|
key: string,
|
|
808
871
|
data: string | Buffer,
|
|
809
872
|
fileType: string = C.DEFAULT_STREAM_CONTENT_TYPE,
|
|
873
|
+
ssecHeaders?: IT.SSECHeaders,
|
|
810
874
|
): Promise<Response> {
|
|
811
875
|
if (!(data instanceof Buffer || typeof data === 'string')) {
|
|
812
876
|
throw new TypeError(C.ERROR_DATA_BUFFER_REQUIRED);
|
|
@@ -816,6 +880,7 @@ class s3mini {
|
|
|
816
880
|
headers: {
|
|
817
881
|
[C.HEADER_CONTENT_LENGTH]: typeof data === 'string' ? Buffer.byteLength(data) : data.length,
|
|
818
882
|
[C.HEADER_CONTENT_TYPE]: fileType,
|
|
883
|
+
...ssecHeaders,
|
|
819
884
|
},
|
|
820
885
|
tolerated: [200],
|
|
821
886
|
});
|
|
@@ -825,6 +890,7 @@ class s3mini {
|
|
|
825
890
|
* Initiates a multipart upload and returns the upload ID.
|
|
826
891
|
* @param {string} key - The key/path where the object will be stored.
|
|
827
892
|
* @param {string} [fileType='application/octet-stream'] - The MIME type of the file.
|
|
893
|
+
* @param {IT.SSECHeaders?} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
828
894
|
* @returns {Promise<string>} A promise that resolves to the upload ID for the multipart upload.
|
|
829
895
|
* @throws {TypeError} If key is invalid or fileType is not a string.
|
|
830
896
|
* @throws {Error} If the multipart upload fails to initialize.
|
|
@@ -832,13 +898,17 @@ class s3mini {
|
|
|
832
898
|
* const uploadId = await s3.getMultipartUploadId('large-file.zip', 'application/zip');
|
|
833
899
|
* console.log(`Started multipart upload: ${uploadId}`);
|
|
834
900
|
*/
|
|
835
|
-
public async getMultipartUploadId(
|
|
901
|
+
public async getMultipartUploadId(
|
|
902
|
+
key: string,
|
|
903
|
+
fileType: string = C.DEFAULT_STREAM_CONTENT_TYPE,
|
|
904
|
+
ssecHeaders?: IT.SSECHeaders,
|
|
905
|
+
): Promise<string> {
|
|
836
906
|
this._checkKey(key);
|
|
837
907
|
if (typeof fileType !== 'string') {
|
|
838
908
|
throw new TypeError(`${C.ERROR_PREFIX}fileType must be a string`);
|
|
839
909
|
}
|
|
840
910
|
const query = { uploads: '' };
|
|
841
|
-
const headers = { [C.HEADER_CONTENT_TYPE]: fileType };
|
|
911
|
+
const headers = { [C.HEADER_CONTENT_TYPE]: fileType, ...ssecHeaders };
|
|
842
912
|
|
|
843
913
|
const res = await this._signedRequest('POST', key, {
|
|
844
914
|
query,
|
|
@@ -883,6 +953,7 @@ class s3mini {
|
|
|
883
953
|
* @param {Buffer | string} data - The data for this part.
|
|
884
954
|
* @param {number} partNumber - The part number (must be between 1 and 10,000).
|
|
885
955
|
* @param {Record<string, unknown>} [opts={}] - Additional options for the request.
|
|
956
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
886
957
|
* @returns {Promise<IT.UploadPart>} A promise that resolves to an object containing the partNumber and etag.
|
|
887
958
|
* @throws {TypeError} If any parameter is invalid.
|
|
888
959
|
* @example
|
|
@@ -900,6 +971,7 @@ class s3mini {
|
|
|
900
971
|
data: Buffer | string,
|
|
901
972
|
partNumber: number,
|
|
902
973
|
opts: Record<string, unknown> = {},
|
|
974
|
+
ssecHeaders?: IT.SSECHeaders,
|
|
903
975
|
): Promise<IT.UploadPart> {
|
|
904
976
|
this._validateUploadPartParams(key, uploadId, data, partNumber, opts);
|
|
905
977
|
|
|
@@ -907,7 +979,10 @@ class s3mini {
|
|
|
907
979
|
const res = await this._signedRequest('PUT', key, {
|
|
908
980
|
query,
|
|
909
981
|
body: data,
|
|
910
|
-
headers: {
|
|
982
|
+
headers: {
|
|
983
|
+
[C.HEADER_CONTENT_LENGTH]: typeof data === 'string' ? Buffer.byteLength(data) : data.length,
|
|
984
|
+
...ssecHeaders,
|
|
985
|
+
},
|
|
911
986
|
});
|
|
912
987
|
|
|
913
988
|
return { partNumber, etag: U.sanitizeETag(res.headers.get('etag') || '') };
|
|
@@ -978,6 +1053,7 @@ class s3mini {
|
|
|
978
1053
|
* Aborts a multipart upload and removes all uploaded parts.
|
|
979
1054
|
* @param {string} key - The key of the object being uploaded.
|
|
980
1055
|
* @param {string} uploadId - The upload ID to abort.
|
|
1056
|
+
* @param {IT.SSECHeaders} [ssecHeaders] - Server-Side Encryption headers, if any.
|
|
981
1057
|
* @returns {Promise<object>} A promise that resolves to an object containing the abort status and details.
|
|
982
1058
|
* @throws {TypeError} If key or uploadId is invalid.
|
|
983
1059
|
* @throws {Error} If the abort operation fails.
|
|
@@ -989,14 +1065,14 @@ class s3mini {
|
|
|
989
1065
|
* console.error('Failed to abort upload:', error);
|
|
990
1066
|
* }
|
|
991
1067
|
*/
|
|
992
|
-
public async abortMultipartUpload(key: string, uploadId: string): Promise<object> {
|
|
1068
|
+
public async abortMultipartUpload(key: string, uploadId: string, ssecHeaders?: IT.SSECHeaders): Promise<object> {
|
|
993
1069
|
this._checkKey(key);
|
|
994
1070
|
if (!uploadId) {
|
|
995
1071
|
throw new TypeError(C.ERROR_UPLOAD_ID_REQUIRED);
|
|
996
1072
|
}
|
|
997
1073
|
|
|
998
1074
|
const query = { uploadId };
|
|
999
|
-
const headers = { [C.HEADER_CONTENT_TYPE]: C.XML_CONTENT_TYPE };
|
|
1075
|
+
const headers = { [C.HEADER_CONTENT_TYPE]: C.XML_CONTENT_TYPE, ...(ssecHeaders ? { ...ssecHeaders } : {}) };
|
|
1000
1076
|
|
|
1001
1077
|
const res = await this._signedRequest('DELETE', key, {
|
|
1002
1078
|
query,
|
|
@@ -1192,5 +1268,10 @@ class s3mini {
|
|
|
1192
1268
|
}
|
|
1193
1269
|
}
|
|
1194
1270
|
|
|
1195
|
-
|
|
1196
|
-
|
|
1271
|
+
/**
|
|
1272
|
+
* @deprecated Use `S3mini` instead.
|
|
1273
|
+
*/
|
|
1274
|
+
const s3mini = S3mini;
|
|
1275
|
+
|
|
1276
|
+
export { S3mini, s3mini };
|
|
1277
|
+
export default S3mini;
|
package/src/consts.ts
CHANGED
|
@@ -15,9 +15,9 @@ export const DEFAULT_REQUEST_SIZE_IN_BYTES = 8 * 1024 * 1024;
|
|
|
15
15
|
export const HEADER_AMZ_CONTENT_SHA256 = 'x-amz-content-sha256';
|
|
16
16
|
export const HEADER_AMZ_DATE = 'x-amz-date';
|
|
17
17
|
export const HEADER_HOST = 'host';
|
|
18
|
-
export const HEADER_AUTHORIZATION = '
|
|
19
|
-
export const HEADER_CONTENT_TYPE = '
|
|
20
|
-
export const HEADER_CONTENT_LENGTH = '
|
|
18
|
+
export const HEADER_AUTHORIZATION = 'authorization';
|
|
19
|
+
export const HEADER_CONTENT_TYPE = 'content-type';
|
|
20
|
+
export const HEADER_CONTENT_LENGTH = 'content-length';
|
|
21
21
|
export const HEADER_ETAG = 'etag';
|
|
22
22
|
export const HEADER_LAST_MODIFIED = 'last-modified';
|
|
23
23
|
|
package/src/index.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
import { s3mini } from './S3.js';
|
|
3
|
+
import { S3mini, s3mini } from './S3.js';
|
|
4
4
|
import { sanitizeETag, runInBatches } from './utils.js';
|
|
5
5
|
|
|
6
6
|
// Export the S3 class as default export and named export
|
|
7
|
-
export { s3mini, sanitizeETag, runInBatches };
|
|
8
|
-
export default
|
|
7
|
+
export { S3mini, s3mini, sanitizeETag, runInBatches };
|
|
8
|
+
export default S3mini;
|
|
9
9
|
|
|
10
10
|
// Re-export types
|
|
11
11
|
export type {
|
package/src/types.ts
CHANGED
|
@@ -8,6 +8,12 @@ export interface S3Config {
|
|
|
8
8
|
logger?: Logger;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
+
export interface SSECHeaders {
|
|
12
|
+
'x-amz-server-side-encryption-customer-algorithm': string;
|
|
13
|
+
'x-amz-server-side-encryption-customer-key': string;
|
|
14
|
+
'x-amz-server-side-encryption-customer-key-md5': string;
|
|
15
|
+
}
|
|
16
|
+
|
|
11
17
|
export interface Crypto {
|
|
12
18
|
createHmac: (
|
|
13
19
|
algorithm: string,
|
|
@@ -33,6 +39,14 @@ export interface UploadPart {
|
|
|
33
39
|
etag: string;
|
|
34
40
|
}
|
|
35
41
|
|
|
42
|
+
export interface ListObject {
|
|
43
|
+
Key: string;
|
|
44
|
+
Size: number;
|
|
45
|
+
LastModified: Date;
|
|
46
|
+
ETag: string;
|
|
47
|
+
StorageClass: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
36
50
|
export interface CompleteMultipartUploadResult {
|
|
37
51
|
location: string;
|
|
38
52
|
bucket: string;
|