lean-s3 0.7.2 → 0.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +25 -10
- package/dist/index.js +162 -105
- package/package.json +7 -6
package/dist/index.d.ts
CHANGED
|
@@ -27,10 +27,13 @@ type Brand<B> = {
|
|
|
27
27
|
type Branded<T, B> = T & Brand<B>;
|
|
28
28
|
type BucketName = Branded<string, "BucketName">;
|
|
29
29
|
type ObjectKey = Branded<string, "ObjectKey">;
|
|
30
|
+
type Endpoint = Branded<string, "Endpoint">;
|
|
31
|
+
type Region = Branded<string, "Region">;
|
|
30
32
|
|
|
31
|
-
declare const
|
|
32
|
-
declare const
|
|
33
|
-
declare const
|
|
33
|
+
declare const kWrite: unique symbol;
|
|
34
|
+
declare const kStream: unique symbol;
|
|
35
|
+
declare const kSignedRequest: unique symbol;
|
|
36
|
+
declare const kGetEffectiveParams: unique symbol;
|
|
34
37
|
interface S3ClientOptions {
|
|
35
38
|
bucket: string;
|
|
36
39
|
region: string;
|
|
@@ -57,7 +60,7 @@ type DeleteObjectsError = {
|
|
|
57
60
|
message: string;
|
|
58
61
|
versionId: string;
|
|
59
62
|
};
|
|
60
|
-
interface S3FilePresignOptions {
|
|
63
|
+
interface S3FilePresignOptions extends OverridableS3ClientOptions {
|
|
61
64
|
contentHash?: Buffer;
|
|
62
65
|
/** Seconds. */
|
|
63
66
|
expiresIn?: number;
|
|
@@ -287,6 +290,7 @@ declare class S3Client {
|
|
|
287
290
|
* @param options The default options to use for the S3 client.
|
|
288
291
|
*/
|
|
289
292
|
constructor(options: S3ClientOptions);
|
|
293
|
+
[kGetEffectiveParams](region: string | undefined | null, endpoint: string | undefined | null, bucket: string | undefined | null): [region: Region, endpoint: Endpoint, bucket: BucketName];
|
|
290
294
|
/**
|
|
291
295
|
* Creates an S3File instance for the given path.
|
|
292
296
|
*
|
|
@@ -346,8 +350,7 @@ declare class S3Client {
|
|
|
346
350
|
* });
|
|
347
351
|
* ```
|
|
348
352
|
*/
|
|
349
|
-
presign(path: string,
|
|
350
|
-
storageClass, contentLength, type, acl, region: regionOverride, bucket: bucketOverride, endpoint: endpointOverride, response, }?: S3FilePresignOptions & OverridableS3ClientOptions): string;
|
|
353
|
+
presign(path: string, options?: S3FilePresignOptions): string;
|
|
351
354
|
createMultipartUpload(key: string, options?: CreateMultipartUploadOptions): Promise<CreateMultipartUploadResult>;
|
|
352
355
|
/**
|
|
353
356
|
* @remarks Uses [`ListMultipartUploads`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListMultipartUploads.html).
|
|
@@ -442,16 +445,16 @@ declare class S3Client {
|
|
|
442
445
|
* TODO: Maybe move this into a separate free function?
|
|
443
446
|
* @internal
|
|
444
447
|
*/
|
|
445
|
-
[
|
|
448
|
+
[kSignedRequest](region: Region, endpoint: Endpoint, bucket: BucketName, method: HttpMethod, pathWithoutBucket: ObjectKey, query: string | undefined, body: UndiciBodyInit | undefined, additionalSignedHeaders: Record<string, string> | undefined, additionalUnsignedHeaders: Record<string, string> | undefined, contentHash: Buffer | undefined, signal?: AbortSignal | undefined): Promise<Dispatcher.ResponseData<null>>;
|
|
446
449
|
/**
|
|
447
450
|
* @internal
|
|
448
451
|
* @param {import("./index.d.ts").UndiciBodyInit} data TODO
|
|
449
452
|
*/
|
|
450
|
-
[
|
|
453
|
+
[kWrite](path: ObjectKey, data: UndiciBodyInit, contentType: string, contentLength: number | undefined, contentHash: Buffer | undefined, rageStart: number | undefined, rangeEndExclusive: number | undefined, signal?: AbortSignal | undefined): Promise<void>;
|
|
451
454
|
/**
|
|
452
455
|
* @internal
|
|
453
456
|
*/
|
|
454
|
-
[
|
|
457
|
+
[kStream](path: ObjectKey, contentHash: Buffer | undefined, rageStart: number | undefined, rangeEndExclusive: number | undefined): stream_web.ReadableStream<Uint8Array<ArrayBufferLike>>;
|
|
455
458
|
}
|
|
456
459
|
|
|
457
460
|
declare class S3Stat {
|
|
@@ -471,7 +474,19 @@ declare class S3File {
|
|
|
471
474
|
#private;
|
|
472
475
|
/** @internal */
|
|
473
476
|
constructor(client: S3Client, path: ObjectKey, start: number | undefined, end: number | undefined, contentType: string | undefined);
|
|
474
|
-
|
|
477
|
+
/**
|
|
478
|
+
* Creates and returns a new {@link S3File} containing a subset of this {@link S3File} data.
|
|
479
|
+
* @param start The starting index.
|
|
480
|
+
* @param end The ending index, exclusive.
|
|
481
|
+
*/
|
|
482
|
+
slice(start?: number, end?: number): S3File;
|
|
483
|
+
/**
|
|
484
|
+
* Creates and returns a new {@link S3File} containing a subset of this {@link S3File} data.
|
|
485
|
+
* @param start The starting index.
|
|
486
|
+
* @param end The ending index, exclusive.
|
|
487
|
+
* @param contentType The content-type for the new {@link S3File}.
|
|
488
|
+
*/
|
|
489
|
+
slice(start?: number, end?: number, contentType?: string): S3File;
|
|
475
490
|
/**
|
|
476
491
|
* Get the stat of a file in the bucket. Uses `HEAD` request to check existence.
|
|
477
492
|
*
|
package/dist/index.js
CHANGED
|
@@ -320,6 +320,9 @@ function getAuthorizationHeader(keyCache, method, path, query, date, sortedSigne
|
|
|
320
320
|
|
|
321
321
|
// src/branded.ts
|
|
322
322
|
function ensureValidBucketName(name) {
|
|
323
|
+
if (typeof name !== "string") {
|
|
324
|
+
throw new TypeError("`name` must be a `string`.");
|
|
325
|
+
}
|
|
323
326
|
if (name.length < 3 || name.length > 63) {
|
|
324
327
|
throw new Error("`name` must be between 3 and 63 characters long.");
|
|
325
328
|
}
|
|
@@ -345,6 +348,24 @@ function ensureValidPath(path) {
|
|
|
345
348
|
}
|
|
346
349
|
return path;
|
|
347
350
|
}
|
|
351
|
+
function ensureValidEndpoint(endpoint) {
|
|
352
|
+
if (typeof endpoint !== "string") {
|
|
353
|
+
throw new TypeError("`endpoint` must be a `string`.");
|
|
354
|
+
}
|
|
355
|
+
if (endpoint.length < 1) {
|
|
356
|
+
throw new RangeError("`endpoint` must be at least 1 character long.");
|
|
357
|
+
}
|
|
358
|
+
return endpoint;
|
|
359
|
+
}
|
|
360
|
+
function ensureValidRegion(region) {
|
|
361
|
+
if (typeof region !== "string") {
|
|
362
|
+
throw new TypeError("`region` must be a `string`.");
|
|
363
|
+
}
|
|
364
|
+
if (region.length < 1) {
|
|
365
|
+
throw new RangeError("`region` must be at least 1 character long.");
|
|
366
|
+
}
|
|
367
|
+
return region;
|
|
368
|
+
}
|
|
348
369
|
|
|
349
370
|
// src/assertNever.ts
|
|
350
371
|
function assertNever(v) {
|
|
@@ -373,9 +394,10 @@ function encodeURIComponentExtended(value) {
|
|
|
373
394
|
}
|
|
374
395
|
|
|
375
396
|
// src/S3Client.ts
|
|
376
|
-
var
|
|
377
|
-
var
|
|
378
|
-
var
|
|
397
|
+
var kWrite = Symbol("kWrite");
|
|
398
|
+
var kStream = Symbol("kStream");
|
|
399
|
+
var kSignedRequest = Symbol("kSignedRequest");
|
|
400
|
+
var kGetEffectiveParams = Symbol("kGetEffectiveParams");
|
|
379
401
|
var xmlParser2 = new XMLParser2({
|
|
380
402
|
ignoreAttributes: true,
|
|
381
403
|
isArray: (_, jPath) => jPath === "ListMultipartUploadsResult.Upload" || jPath === "ListBucketResult.Contents" || jPath === "ListPartsResult.Part" || jPath === "DeleteResult.Deleted" || jPath === "DeleteResult.Error"
|
|
@@ -387,7 +409,7 @@ var xmlBuilder = new XMLBuilder({
|
|
|
387
409
|
var S3Client = class {
|
|
388
410
|
#options;
|
|
389
411
|
#keyCache = new KeyCache();
|
|
390
|
-
// TODO: pass options to this in client? Do we want to expose
|
|
412
|
+
// TODO: pass options to this in client? Do we want to expose the internal use of undici?
|
|
391
413
|
#dispatcher = new Agent();
|
|
392
414
|
/**
|
|
393
415
|
* Create a new instance of an S3 bucket so that credentials can be managed from a single instance instead of being passed to every method.
|
|
@@ -424,26 +446,19 @@ var S3Client = class {
|
|
|
424
446
|
this.#options = {
|
|
425
447
|
accessKeyId,
|
|
426
448
|
secretAccessKey,
|
|
427
|
-
endpoint,
|
|
428
|
-
region,
|
|
429
|
-
bucket,
|
|
449
|
+
endpoint: ensureValidEndpoint(options.endpoint),
|
|
450
|
+
region: ensureValidRegion(options.region),
|
|
451
|
+
bucket: ensureValidBucketName(options.bucket),
|
|
430
452
|
sessionToken
|
|
431
453
|
};
|
|
432
454
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
// TODO: PutBucketCors
|
|
441
|
-
},
|
|
442
|
-
delete: () => {
|
|
443
|
-
// TODO: DeleteBucketCors
|
|
444
|
-
},
|
|
445
|
-
};
|
|
446
|
-
*/
|
|
455
|
+
[kGetEffectiveParams](region, endpoint, bucket) {
|
|
456
|
+
return [
|
|
457
|
+
ensureValidRegion(region ?? this.#options.region),
|
|
458
|
+
ensureValidEndpoint(endpoint ?? this.#options.endpoint),
|
|
459
|
+
ensureValidBucketName(bucket ?? this.#options.bucket)
|
|
460
|
+
];
|
|
461
|
+
}
|
|
447
462
|
/**
|
|
448
463
|
* Creates an S3File instance for the given path.
|
|
449
464
|
*
|
|
@@ -511,52 +526,51 @@ var S3Client = class {
|
|
|
511
526
|
* });
|
|
512
527
|
* ```
|
|
513
528
|
*/
|
|
514
|
-
presign(path, {
|
|
515
|
-
|
|
516
|
-
expiresIn = 3600,
|
|
517
|
-
// TODO: Maybe rename this to expiresInSeconds
|
|
518
|
-
storageClass,
|
|
519
|
-
contentLength,
|
|
520
|
-
type,
|
|
521
|
-
acl,
|
|
522
|
-
region: regionOverride,
|
|
523
|
-
bucket: bucketOverride,
|
|
524
|
-
endpoint: endpointOverride,
|
|
525
|
-
response
|
|
526
|
-
} = {}) {
|
|
529
|
+
presign(path, options = {}) {
|
|
530
|
+
const contentLength = options.contentLength ?? void 0;
|
|
527
531
|
if (typeof contentLength === "number") {
|
|
528
532
|
if (contentLength < 0) {
|
|
529
533
|
throw new RangeError("`contentLength` must be >= 0.");
|
|
530
534
|
}
|
|
531
535
|
}
|
|
536
|
+
const method = options.method ?? "GET";
|
|
537
|
+
const contentType = options.type ?? void 0;
|
|
538
|
+
const [region, endpoint, bucket] = this[kGetEffectiveParams](
|
|
539
|
+
options.region,
|
|
540
|
+
options.endpoint,
|
|
541
|
+
options.bucket
|
|
542
|
+
);
|
|
543
|
+
const responseOptions = options.response;
|
|
544
|
+
const contentDisposition = responseOptions?.contentDisposition;
|
|
545
|
+
const responseContentDisposition = contentDisposition ? getContentDispositionHeader(contentDisposition) : void 0;
|
|
546
|
+
const res = buildRequestUrl(
|
|
547
|
+
endpoint,
|
|
548
|
+
bucket,
|
|
549
|
+
region,
|
|
550
|
+
ensureValidPath(path)
|
|
551
|
+
);
|
|
532
552
|
const now2 = /* @__PURE__ */ new Date();
|
|
533
553
|
const date = getAmzDate(now2);
|
|
534
|
-
const options = this.#options;
|
|
535
|
-
const region = regionOverride ?? options.region;
|
|
536
|
-
const bucket = bucketOverride ?? options.bucket;
|
|
537
|
-
const endpoint = endpointOverride ?? options.endpoint;
|
|
538
|
-
const responseContentDisposition = response?.contentDisposition ? getContentDispositionHeader(response?.contentDisposition) : void 0;
|
|
539
|
-
const res = buildRequestUrl(endpoint, bucket, region, path);
|
|
540
554
|
const query = buildSearchParams(
|
|
541
|
-
`${options.accessKeyId}/${date.date}/${region}/s3/aws4_request`,
|
|
555
|
+
`${this.#options.accessKeyId}/${date.date}/${region}/s3/aws4_request`,
|
|
542
556
|
date,
|
|
543
|
-
expiresIn,
|
|
544
|
-
typeof contentLength === "number" || typeof
|
|
557
|
+
options.expiresIn ?? 3600,
|
|
558
|
+
typeof contentLength === "number" || typeof contentType === "string" ? typeof contentLength === "number" && typeof contentType === "string" ? "content-length;content-type;host" : typeof contentLength === "number" ? "content-length;host" : typeof contentType === "string" ? "content-type;host" : "" : "host",
|
|
545
559
|
unsignedPayload,
|
|
546
|
-
storageClass,
|
|
547
|
-
options.sessionToken,
|
|
548
|
-
acl,
|
|
560
|
+
options.storageClass,
|
|
561
|
+
this.#options.sessionToken,
|
|
562
|
+
options.acl,
|
|
549
563
|
responseContentDisposition
|
|
550
564
|
);
|
|
551
|
-
const dataDigest = typeof contentLength === "number" || typeof
|
|
565
|
+
const dataDigest = typeof contentLength === "number" || typeof contentType === "string" ? createCanonicalDataDigest(
|
|
552
566
|
method,
|
|
553
567
|
res.pathname,
|
|
554
568
|
query,
|
|
555
|
-
typeof contentLength === "number" && typeof
|
|
569
|
+
typeof contentLength === "number" && typeof contentType === "string" ? {
|
|
556
570
|
"content-length": String(contentLength),
|
|
557
|
-
"content-type":
|
|
571
|
+
"content-type": contentType,
|
|
558
572
|
host: res.host
|
|
559
|
-
} : typeof contentLength === "number" ? { "content-length": String(contentLength), host: res.host } : typeof
|
|
573
|
+
} : typeof contentLength === "number" ? { "content-length": String(contentLength), host: res.host } : typeof contentType === "string" ? { "content-type": contentType, host: res.host } : {},
|
|
560
574
|
unsignedPayload
|
|
561
575
|
) : createCanonicalDataDigestHostOnly(
|
|
562
576
|
method,
|
|
@@ -567,8 +581,8 @@ var S3Client = class {
|
|
|
567
581
|
const signingKey = this.#keyCache.computeIfAbsent(
|
|
568
582
|
date,
|
|
569
583
|
region,
|
|
570
|
-
options.accessKeyId,
|
|
571
|
-
options.secretAccessKey
|
|
584
|
+
this.#options.accessKeyId,
|
|
585
|
+
this.#options.secretAccessKey
|
|
572
586
|
);
|
|
573
587
|
const signature = signCanonicalDataHash(
|
|
574
588
|
signingKey,
|
|
@@ -583,7 +597,10 @@ var S3Client = class {
|
|
|
583
597
|
async createMultipartUpload(key, options = {}) {
|
|
584
598
|
if (key.length < 1) {
|
|
585
599
|
}
|
|
586
|
-
const response = await this[
|
|
600
|
+
const response = await this[kSignedRequest](
|
|
601
|
+
this.#options.region,
|
|
602
|
+
this.#options.endpoint,
|
|
603
|
+
options.bucket ? ensureValidBucketName(options.bucket) : this.#options.bucket,
|
|
587
604
|
"POST",
|
|
588
605
|
ensureValidPath(key),
|
|
589
606
|
"uploads=",
|
|
@@ -591,7 +608,6 @@ var S3Client = class {
|
|
|
591
608
|
void 0,
|
|
592
609
|
void 0,
|
|
593
610
|
void 0,
|
|
594
|
-
ensureValidBucketName(options.bucket ?? this.#options.bucket),
|
|
595
611
|
options.signal
|
|
596
612
|
);
|
|
597
613
|
if (response.statusCode !== 200) {
|
|
@@ -610,9 +626,6 @@ var S3Client = class {
|
|
|
610
626
|
* @throws {RangeError} If `options.maxKeys` is not between `1` and `1000`.
|
|
611
627
|
*/
|
|
612
628
|
async listMultipartUploads(options = {}) {
|
|
613
|
-
const bucket = ensureValidBucketName(
|
|
614
|
-
options.bucket ?? this.#options.bucket
|
|
615
|
-
);
|
|
616
629
|
let query = "uploads=";
|
|
617
630
|
if (options.delimiter) {
|
|
618
631
|
if (typeof options.delimiter !== "string") {
|
|
@@ -641,7 +654,10 @@ var S3Client = class {
|
|
|
641
654
|
}
|
|
642
655
|
query += `&prefix=${encodeURIComponent(options.prefix)}`;
|
|
643
656
|
}
|
|
644
|
-
const response = await this[
|
|
657
|
+
const response = await this[kSignedRequest](
|
|
658
|
+
this.#options.region,
|
|
659
|
+
this.#options.endpoint,
|
|
660
|
+
options.bucket ? ensureValidBucketName(options.bucket) : this.#options.bucket,
|
|
645
661
|
"GET",
|
|
646
662
|
"",
|
|
647
663
|
query,
|
|
@@ -649,7 +665,6 @@ var S3Client = class {
|
|
|
649
665
|
void 0,
|
|
650
666
|
void 0,
|
|
651
667
|
void 0,
|
|
652
|
-
bucket,
|
|
653
668
|
options.signal
|
|
654
669
|
);
|
|
655
670
|
if (response.statusCode !== 200) {
|
|
@@ -692,7 +707,10 @@ var S3Client = class {
|
|
|
692
707
|
if (!uploadId) {
|
|
693
708
|
throw new Error("`uploadId` is required.");
|
|
694
709
|
}
|
|
695
|
-
const response = await this[
|
|
710
|
+
const response = await this[kSignedRequest](
|
|
711
|
+
this.#options.region,
|
|
712
|
+
this.#options.endpoint,
|
|
713
|
+
options.bucket ? ensureValidBucketName(options.bucket) : this.#options.bucket,
|
|
696
714
|
"DELETE",
|
|
697
715
|
ensureValidPath(path),
|
|
698
716
|
`uploadId=${encodeURIComponent(uploadId)}`,
|
|
@@ -700,10 +718,9 @@ var S3Client = class {
|
|
|
700
718
|
void 0,
|
|
701
719
|
void 0,
|
|
702
720
|
void 0,
|
|
703
|
-
ensureValidBucketName(options.bucket ?? this.#options.bucket),
|
|
704
721
|
options.signal
|
|
705
722
|
);
|
|
706
|
-
if (response.statusCode !== 204) {
|
|
723
|
+
if (response.statusCode !== 204 && response.statusCode !== 200) {
|
|
707
724
|
throw await getResponseError(response, path);
|
|
708
725
|
}
|
|
709
726
|
}
|
|
@@ -724,7 +741,10 @@ var S3Client = class {
|
|
|
724
741
|
}))
|
|
725
742
|
}
|
|
726
743
|
});
|
|
727
|
-
const response = await this[
|
|
744
|
+
const response = await this[kSignedRequest](
|
|
745
|
+
this.#options.region,
|
|
746
|
+
this.#options.endpoint,
|
|
747
|
+
options.bucket ? ensureValidBucketName(options.bucket) : this.#options.bucket,
|
|
728
748
|
"POST",
|
|
729
749
|
ensureValidPath(path),
|
|
730
750
|
`uploadId=${encodeURIComponent(uploadId)}`,
|
|
@@ -732,7 +752,6 @@ var S3Client = class {
|
|
|
732
752
|
void 0,
|
|
733
753
|
void 0,
|
|
734
754
|
void 0,
|
|
735
|
-
ensureValidBucketName(options.bucket ?? this.#options.bucket),
|
|
736
755
|
options.signal
|
|
737
756
|
);
|
|
738
757
|
if (response.statusCode !== 200) {
|
|
@@ -768,7 +787,10 @@ var S3Client = class {
|
|
|
768
787
|
if (typeof partNumber !== "number" || partNumber <= 0) {
|
|
769
788
|
throw new Error("`partNumber` has to be a `number` which is >= 1.");
|
|
770
789
|
}
|
|
771
|
-
const response = await this[
|
|
790
|
+
const response = await this[kSignedRequest](
|
|
791
|
+
this.#options.region,
|
|
792
|
+
this.#options.endpoint,
|
|
793
|
+
options.bucket ? ensureValidBucketName(options.bucket) : this.#options.bucket,
|
|
772
794
|
"PUT",
|
|
773
795
|
ensureValidPath(path),
|
|
774
796
|
`partNumber=${partNumber}&uploadId=${encodeURIComponent(uploadId)}`,
|
|
@@ -776,7 +798,6 @@ var S3Client = class {
|
|
|
776
798
|
void 0,
|
|
777
799
|
void 0,
|
|
778
800
|
void 0,
|
|
779
|
-
ensureValidBucketName(options.bucket ?? this.#options.bucket),
|
|
780
801
|
options.signal
|
|
781
802
|
);
|
|
782
803
|
if (response.statusCode === 200) {
|
|
@@ -820,7 +841,10 @@ var S3Client = class {
|
|
|
820
841
|
query += `&part-number-marker=${encodeURIComponent(options.partNumberMarker)}`;
|
|
821
842
|
}
|
|
822
843
|
query += `&uploadId=${encodeURIComponent(uploadId)}`;
|
|
823
|
-
const response = await this[
|
|
844
|
+
const response = await this[kSignedRequest](
|
|
845
|
+
this.#options.region,
|
|
846
|
+
this.#options.endpoint,
|
|
847
|
+
options.bucket ? ensureValidBucketName(options.bucket) : this.#options.bucket,
|
|
824
848
|
"GET",
|
|
825
849
|
ensureValidPath(path),
|
|
826
850
|
// We always have a leading &, so we can slice the leading & away (this way, we have less conditionals on the hot path); see benchmark-operations.js
|
|
@@ -829,7 +853,6 @@ var S3Client = class {
|
|
|
829
853
|
void 0,
|
|
830
854
|
void 0,
|
|
831
855
|
void 0,
|
|
832
|
-
ensureValidBucketName(options.bucket ?? this.#options.bucket),
|
|
833
856
|
options?.signal
|
|
834
857
|
);
|
|
835
858
|
if (response.statusCode === 200) {
|
|
@@ -893,7 +916,10 @@ var S3Client = class {
|
|
|
893
916
|
}) : void 0;
|
|
894
917
|
}
|
|
895
918
|
const additionalSignedHeaders = body ? { "content-md5": md5Base64(body) } : void 0;
|
|
896
|
-
const response = await this[
|
|
919
|
+
const response = await this[kSignedRequest](
|
|
920
|
+
this.#options.region,
|
|
921
|
+
this.#options.endpoint,
|
|
922
|
+
ensureValidBucketName(name),
|
|
897
923
|
"PUT",
|
|
898
924
|
"",
|
|
899
925
|
void 0,
|
|
@@ -901,7 +927,6 @@ var S3Client = class {
|
|
|
901
927
|
additionalSignedHeaders,
|
|
902
928
|
void 0,
|
|
903
929
|
void 0,
|
|
904
|
-
ensureValidBucketName(name),
|
|
905
930
|
options?.signal
|
|
906
931
|
);
|
|
907
932
|
if (400 <= response.statusCode && response.statusCode < 500) {
|
|
@@ -921,7 +946,10 @@ var S3Client = class {
|
|
|
921
946
|
* @remarks Uses [`DeleteBucket`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucket.html).
|
|
922
947
|
*/
|
|
923
948
|
async deleteBucket(name, options) {
|
|
924
|
-
const response = await this[
|
|
949
|
+
const response = await this[kSignedRequest](
|
|
950
|
+
this.#options.region,
|
|
951
|
+
this.#options.endpoint,
|
|
952
|
+
ensureValidBucketName(name),
|
|
925
953
|
"DELETE",
|
|
926
954
|
"",
|
|
927
955
|
void 0,
|
|
@@ -929,7 +957,6 @@ var S3Client = class {
|
|
|
929
957
|
void 0,
|
|
930
958
|
void 0,
|
|
931
959
|
void 0,
|
|
932
|
-
ensureValidBucketName(name),
|
|
933
960
|
options?.signal
|
|
934
961
|
);
|
|
935
962
|
if (400 <= response.statusCode && response.statusCode < 500) {
|
|
@@ -948,7 +975,10 @@ var S3Client = class {
|
|
|
948
975
|
* @remarks Uses [`HeadBucket`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadBucket.html).
|
|
949
976
|
*/
|
|
950
977
|
async bucketExists(name, options) {
|
|
951
|
-
const response = await this[
|
|
978
|
+
const response = await this[kSignedRequest](
|
|
979
|
+
this.#options.region,
|
|
980
|
+
this.#options.endpoint,
|
|
981
|
+
ensureValidBucketName(name),
|
|
952
982
|
"HEAD",
|
|
953
983
|
"",
|
|
954
984
|
void 0,
|
|
@@ -956,7 +986,6 @@ var S3Client = class {
|
|
|
956
986
|
void 0,
|
|
957
987
|
void 0,
|
|
958
988
|
void 0,
|
|
959
|
-
ensureValidBucketName(name),
|
|
960
989
|
options?.signal
|
|
961
990
|
);
|
|
962
991
|
if (response.statusCode !== 404 && 400 <= response.statusCode && response.statusCode < 500) {
|
|
@@ -987,7 +1016,10 @@ var S3Client = class {
|
|
|
987
1016
|
}))
|
|
988
1017
|
}
|
|
989
1018
|
});
|
|
990
|
-
const response = await this[
|
|
1019
|
+
const response = await this[kSignedRequest](
|
|
1020
|
+
this.#options.region,
|
|
1021
|
+
this.#options.endpoint,
|
|
1022
|
+
options.bucket ? ensureValidBucketName(options.bucket) : this.#options.bucket,
|
|
991
1023
|
"PUT",
|
|
992
1024
|
"",
|
|
993
1025
|
"cors=",
|
|
@@ -998,7 +1030,6 @@ var S3Client = class {
|
|
|
998
1030
|
},
|
|
999
1031
|
void 0,
|
|
1000
1032
|
void 0,
|
|
1001
|
-
ensureValidBucketName(options.bucket ?? this.#options.bucket),
|
|
1002
1033
|
options.signal
|
|
1003
1034
|
);
|
|
1004
1035
|
if (response.statusCode === 200) {
|
|
@@ -1016,7 +1047,10 @@ var S3Client = class {
|
|
|
1016
1047
|
* @remarks Uses [`GetBucketCors`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketCors.html).
|
|
1017
1048
|
*/
|
|
1018
1049
|
async getBucketCors(options = {}) {
|
|
1019
|
-
const response = await this[
|
|
1050
|
+
const response = await this[kSignedRequest](
|
|
1051
|
+
this.#options.region,
|
|
1052
|
+
this.#options.endpoint,
|
|
1053
|
+
options.bucket ? ensureValidBucketName(options.bucket) : this.#options.bucket,
|
|
1020
1054
|
"GET",
|
|
1021
1055
|
"",
|
|
1022
1056
|
"cors=",
|
|
@@ -1025,7 +1059,6 @@ var S3Client = class {
|
|
|
1025
1059
|
void 0,
|
|
1026
1060
|
void 0,
|
|
1027
1061
|
void 0,
|
|
1028
|
-
ensureValidBucketName(options.bucket ?? this.#options.bucket),
|
|
1029
1062
|
options.signal
|
|
1030
1063
|
);
|
|
1031
1064
|
if (response.statusCode !== 200) {
|
|
@@ -1038,7 +1071,10 @@ var S3Client = class {
|
|
|
1038
1071
|
* @remarks Uses [`DeleteBucketCors`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketCors.html).
|
|
1039
1072
|
*/
|
|
1040
1073
|
async deleteBucketCors(options = {}) {
|
|
1041
|
-
const response = await this[
|
|
1074
|
+
const response = await this[kSignedRequest](
|
|
1075
|
+
this.#options.region,
|
|
1076
|
+
this.#options.endpoint,
|
|
1077
|
+
options.bucket ? ensureValidBucketName(options.bucket) : this.#options.bucket,
|
|
1042
1078
|
"DELETE",
|
|
1043
1079
|
"",
|
|
1044
1080
|
"cors=",
|
|
@@ -1047,7 +1083,6 @@ var S3Client = class {
|
|
|
1047
1083
|
void 0,
|
|
1048
1084
|
void 0,
|
|
1049
1085
|
void 0,
|
|
1050
|
-
ensureValidBucketName(options.bucket ?? this.#options.bucket),
|
|
1051
1086
|
options.signal
|
|
1052
1087
|
);
|
|
1053
1088
|
if (response.statusCode !== 204) {
|
|
@@ -1117,7 +1152,10 @@ var S3Client = class {
|
|
|
1117
1152
|
}
|
|
1118
1153
|
query += `&start-after=${encodeURIComponent(options.startAfter)}`;
|
|
1119
1154
|
}
|
|
1120
|
-
const response = await this[
|
|
1155
|
+
const response = await this[kSignedRequest](
|
|
1156
|
+
ensureValidRegion(this.#options.region),
|
|
1157
|
+
ensureValidEndpoint(this.#options.endpoint),
|
|
1158
|
+
options.bucket ? ensureValidBucketName(options.bucket) : this.#options.bucket,
|
|
1121
1159
|
"GET",
|
|
1122
1160
|
"",
|
|
1123
1161
|
query,
|
|
@@ -1125,7 +1163,6 @@ var S3Client = class {
|
|
|
1125
1163
|
void 0,
|
|
1126
1164
|
void 0,
|
|
1127
1165
|
void 0,
|
|
1128
|
-
ensureValidBucketName(options.bucket ?? this.#options.bucket),
|
|
1129
1166
|
options.signal
|
|
1130
1167
|
);
|
|
1131
1168
|
if (response.statusCode !== 200) {
|
|
@@ -1166,7 +1203,10 @@ var S3Client = class {
|
|
|
1166
1203
|
}))
|
|
1167
1204
|
}
|
|
1168
1205
|
});
|
|
1169
|
-
const response = await this[
|
|
1206
|
+
const response = await this[kSignedRequest](
|
|
1207
|
+
this.#options.region,
|
|
1208
|
+
this.#options.endpoint,
|
|
1209
|
+
options.bucket ? ensureValidBucketName(options.bucket) : this.#options.bucket,
|
|
1170
1210
|
"POST",
|
|
1171
1211
|
"",
|
|
1172
1212
|
"delete=",
|
|
@@ -1177,7 +1217,6 @@ var S3Client = class {
|
|
|
1177
1217
|
},
|
|
1178
1218
|
void 0,
|
|
1179
1219
|
void 0,
|
|
1180
|
-
ensureValidBucketName(options.bucket ?? this.#options.bucket),
|
|
1181
1220
|
options.signal
|
|
1182
1221
|
);
|
|
1183
1222
|
if (response.statusCode === 200) {
|
|
@@ -1215,16 +1254,8 @@ var S3Client = class {
|
|
|
1215
1254
|
* TODO: Maybe move this into a separate free function?
|
|
1216
1255
|
* @internal
|
|
1217
1256
|
*/
|
|
1218
|
-
async [
|
|
1219
|
-
const
|
|
1220
|
-
const region = this.#options.region;
|
|
1221
|
-
const effectiveBucket = bucket ?? this.#options.bucket;
|
|
1222
|
-
const url = buildRequestUrl(
|
|
1223
|
-
endpoint,
|
|
1224
|
-
effectiveBucket,
|
|
1225
|
-
region,
|
|
1226
|
-
pathWithoutBucket
|
|
1227
|
-
);
|
|
1257
|
+
async [kSignedRequest](region, endpoint, bucket, method, pathWithoutBucket, query, body, additionalSignedHeaders, additionalUnsignedHeaders, contentHash, signal = void 0) {
|
|
1258
|
+
const url = buildRequestUrl(endpoint, bucket, region, pathWithoutBucket);
|
|
1228
1259
|
if (query) {
|
|
1229
1260
|
url.search = query;
|
|
1230
1261
|
}
|
|
@@ -1272,7 +1303,7 @@ var S3Client = class {
|
|
|
1272
1303
|
* @internal
|
|
1273
1304
|
* @param {import("./index.d.ts").UndiciBodyInit} data TODO
|
|
1274
1305
|
*/
|
|
1275
|
-
async [
|
|
1306
|
+
async [kWrite](path, data, contentType, contentLength, contentHash, rageStart, rangeEndExclusive, signal = void 0) {
|
|
1276
1307
|
const bucket = this.#options.bucket;
|
|
1277
1308
|
const endpoint = this.#options.endpoint;
|
|
1278
1309
|
const region = this.#options.region;
|
|
@@ -1328,7 +1359,7 @@ var S3Client = class {
|
|
|
1328
1359
|
/**
|
|
1329
1360
|
* @internal
|
|
1330
1361
|
*/
|
|
1331
|
-
[
|
|
1362
|
+
[kStream](path, contentHash, rageStart, rangeEndExclusive) {
|
|
1332
1363
|
const bucket = this.#options.bucket;
|
|
1333
1364
|
const endpoint = this.#options.endpoint;
|
|
1334
1365
|
const region = this.#options.region;
|
|
@@ -1513,7 +1544,12 @@ var S3File = class _S3File {
|
|
|
1513
1544
|
this.#end = end;
|
|
1514
1545
|
this.#contentType = contentType ?? "application/octet-stream";
|
|
1515
1546
|
}
|
|
1516
|
-
|
|
1547
|
+
/**
|
|
1548
|
+
* Creates and returns a new {@link S3File} containing a subset of this {@link S3File} data.
|
|
1549
|
+
* @param start The starting index.
|
|
1550
|
+
* @param end The ending index, exclusive.
|
|
1551
|
+
* @param contentType The content-type for the new {@link S3File}.
|
|
1552
|
+
*/
|
|
1517
1553
|
slice(start, end, contentType) {
|
|
1518
1554
|
return new _S3File(
|
|
1519
1555
|
this.#client,
|
|
@@ -1531,7 +1567,15 @@ var S3File = class _S3File {
|
|
|
1531
1567
|
* @throws {Error} If the server returns an invalid response.
|
|
1532
1568
|
*/
|
|
1533
1569
|
async stat(options = {}) {
|
|
1534
|
-
const
|
|
1570
|
+
const [region, endpoint, bucket] = this.#client[kGetEffectiveParams](
|
|
1571
|
+
options.region,
|
|
1572
|
+
options.endpoint,
|
|
1573
|
+
options.bucket
|
|
1574
|
+
);
|
|
1575
|
+
const response = await this.#client[kSignedRequest](
|
|
1576
|
+
region,
|
|
1577
|
+
endpoint,
|
|
1578
|
+
bucket,
|
|
1535
1579
|
"HEAD",
|
|
1536
1580
|
this.#path,
|
|
1537
1581
|
void 0,
|
|
@@ -1539,7 +1583,6 @@ var S3File = class _S3File {
|
|
|
1539
1583
|
void 0,
|
|
1540
1584
|
void 0,
|
|
1541
1585
|
void 0,
|
|
1542
|
-
void 0,
|
|
1543
1586
|
options.signal
|
|
1544
1587
|
);
|
|
1545
1588
|
response.body.dump();
|
|
@@ -1562,7 +1605,15 @@ var S3File = class _S3File {
|
|
|
1562
1605
|
* @remarks Uses [`HeadObject`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html).
|
|
1563
1606
|
*/
|
|
1564
1607
|
async exists(options = {}) {
|
|
1565
|
-
const
|
|
1608
|
+
const [region, endpoint, bucket] = this.#client[kGetEffectiveParams](
|
|
1609
|
+
options.region,
|
|
1610
|
+
options.endpoint,
|
|
1611
|
+
options.bucket
|
|
1612
|
+
);
|
|
1613
|
+
const response = await this.#client[kSignedRequest](
|
|
1614
|
+
region,
|
|
1615
|
+
endpoint,
|
|
1616
|
+
bucket,
|
|
1566
1617
|
"HEAD",
|
|
1567
1618
|
this.#path,
|
|
1568
1619
|
void 0,
|
|
@@ -1570,7 +1621,6 @@ var S3File = class _S3File {
|
|
|
1570
1621
|
void 0,
|
|
1571
1622
|
void 0,
|
|
1572
1623
|
void 0,
|
|
1573
|
-
void 0,
|
|
1574
1624
|
options.signal
|
|
1575
1625
|
);
|
|
1576
1626
|
response.body.dump();
|
|
@@ -1607,7 +1657,15 @@ var S3File = class _S3File {
|
|
|
1607
1657
|
* ```
|
|
1608
1658
|
*/
|
|
1609
1659
|
async delete(options = {}) {
|
|
1610
|
-
const
|
|
1660
|
+
const [region, endpoint, bucket] = this.#client[kGetEffectiveParams](
|
|
1661
|
+
options.region,
|
|
1662
|
+
options.endpoint,
|
|
1663
|
+
options.bucket
|
|
1664
|
+
);
|
|
1665
|
+
const response = await this.#client[kSignedRequest](
|
|
1666
|
+
region,
|
|
1667
|
+
endpoint,
|
|
1668
|
+
bucket,
|
|
1611
1669
|
"DELETE",
|
|
1612
1670
|
this.#path,
|
|
1613
1671
|
void 0,
|
|
@@ -1615,7 +1673,6 @@ var S3File = class _S3File {
|
|
|
1615
1673
|
void 0,
|
|
1616
1674
|
void 0,
|
|
1617
1675
|
void 0,
|
|
1618
|
-
void 0,
|
|
1619
1676
|
options.signal
|
|
1620
1677
|
);
|
|
1621
1678
|
if (response.statusCode === 204) {
|
|
@@ -1646,7 +1703,7 @@ var S3File = class _S3File {
|
|
|
1646
1703
|
}
|
|
1647
1704
|
/** @returns {ReadableStream<Uint8Array>} */
|
|
1648
1705
|
stream() {
|
|
1649
|
-
return this.#client[
|
|
1706
|
+
return this.#client[kStream](this.#path, void 0, this.#start, this.#end);
|
|
1650
1707
|
}
|
|
1651
1708
|
async #transformData(data) {
|
|
1652
1709
|
if (typeof data === "string") {
|
|
@@ -1689,7 +1746,7 @@ var S3File = class _S3File {
|
|
|
1689
1746
|
async write(data, options = {}) {
|
|
1690
1747
|
const signal = void 0;
|
|
1691
1748
|
const [bytes, length, hash] = await this.#transformData(data);
|
|
1692
|
-
return await this.#client[
|
|
1749
|
+
return await this.#client[kWrite](
|
|
1693
1750
|
this.#path,
|
|
1694
1751
|
bytes,
|
|
1695
1752
|
options.type ?? this.#contentType,
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "lean-s3",
|
|
3
3
|
"author": "Niklas Mollenhauer",
|
|
4
4
|
"license": "MIT",
|
|
5
|
-
"version": "0.7.
|
|
5
|
+
"version": "0.7.4",
|
|
6
6
|
"description": "A server-side S3 API for the regular user.",
|
|
7
7
|
"keywords": [
|
|
8
8
|
"s3",
|
|
@@ -47,13 +47,14 @@
|
|
|
47
47
|
"undici": "^7.11.0"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
|
-
"@biomejs/biome": "2.
|
|
51
|
-
"@testcontainers/localstack": "^11.2.
|
|
52
|
-
"@testcontainers/minio": "^11.2.
|
|
50
|
+
"@biomejs/biome": "2.1.1",
|
|
51
|
+
"@testcontainers/localstack": "^11.2.1",
|
|
52
|
+
"@testcontainers/minio": "^11.2.1",
|
|
53
53
|
"@types/node": "^24.0.10",
|
|
54
|
-
"@typescript/native-preview": "^7.0.0-dev.
|
|
54
|
+
"@typescript/native-preview": "^7.0.0-dev.20250708.1",
|
|
55
55
|
"expect": "^30.0.4",
|
|
56
|
-
"lefthook": "^1.
|
|
56
|
+
"lefthook": "^1.12.0",
|
|
57
|
+
"testcontainers": "^11.2.1",
|
|
57
58
|
"tsup": "^8.5.0",
|
|
58
59
|
"tsx": "^4.20.3",
|
|
59
60
|
"typedoc": "^0.28.7"
|