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.
Files changed (3) hide show
  1. package/dist/index.d.ts +25 -10
  2. package/dist/index.js +162 -105
  3. 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 write: unique symbol;
32
- declare const stream: unique symbol;
33
- declare const signedRequest: unique symbol;
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, { method, expiresIn, // TODO: Maybe rename this to expiresInSeconds
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
- [signedRequest](method: HttpMethod, pathWithoutBucket: ObjectKey, query: string | undefined, body: UndiciBodyInit | undefined, additionalSignedHeaders: Record<string, string> | undefined, additionalUnsignedHeaders: Record<string, string> | undefined, contentHash: Buffer | undefined, bucket: BucketName | undefined, signal?: AbortSignal | undefined): Promise<Dispatcher.ResponseData<null>>;
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
- [write](path: string, data: UndiciBodyInit, contentType: string, contentLength: number | undefined, contentHash: Buffer | undefined, rageStart: number | undefined, rangeEndExclusive: number | undefined, signal?: AbortSignal | undefined): Promise<void>;
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
- [stream](path: string, contentHash: Buffer | undefined, rageStart: number | undefined, rangeEndExclusive: number | undefined): stream_web.ReadableStream<Uint8Array<ArrayBufferLike>>;
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
- slice(start?: number | undefined, end?: number | undefined, contentType?: string | undefined): S3File;
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 write = Symbol("write");
377
- var stream = Symbol("stream");
378
- var signedRequest = Symbol("signedRequest");
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 tjhe internal use of undici?
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
- // Maybe future API
434
- /*
435
- cors = {
436
- get: () => {
437
- // TODO: GetBucketCors
438
- },
439
- set: () => {
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
- method = "GET",
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 type === "string" ? typeof contentLength === "number" && typeof type === "string" ? "content-length;content-type;host" : typeof contentLength === "number" ? "content-length;host" : typeof type === "string" ? "content-type;host" : "" : "host",
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 type === "string" ? createCanonicalDataDigest(
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 type === "string" ? {
569
+ typeof contentLength === "number" && typeof contentType === "string" ? {
556
570
  "content-length": String(contentLength),
557
- "content-type": type,
571
+ "content-type": contentType,
558
572
  host: res.host
559
- } : typeof contentLength === "number" ? { "content-length": String(contentLength), host: res.host } : typeof type === "string" ? { "content-type": type, host: res.host } : {},
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[signedRequest](
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[signedRequest](
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[signedRequest](
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[signedRequest](
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[signedRequest](
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[signedRequest](
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[signedRequest](
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[signedRequest](
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[signedRequest](
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[signedRequest](
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[signedRequest](
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[signedRequest](
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[signedRequest](
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[signedRequest](
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 [signedRequest](method, pathWithoutBucket, query, body, additionalSignedHeaders, additionalUnsignedHeaders, contentHash, bucket, signal = void 0) {
1219
- const endpoint = this.#options.endpoint;
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 [write](path, data, contentType, contentLength, contentHash, rageStart, rangeEndExclusive, signal = void 0) {
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
- [stream](path, contentHash, rageStart, rangeEndExclusive) {
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
- // TODO: slice overloads
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 response = await this.#client[signedRequest](
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 response = await this.#client[signedRequest](
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 response = await this.#client[signedRequest](
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[stream](this.#path, void 0, this.#start, this.#end);
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[write](
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.2",
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.0.6",
51
- "@testcontainers/localstack": "^11.2.0",
52
- "@testcontainers/minio": "^11.2.0",
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.20250705.1",
54
+ "@typescript/native-preview": "^7.0.0-dev.20250708.1",
55
55
  "expect": "^30.0.4",
56
- "lefthook": "^1.11.16",
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"