lean-s3 0.6.0 → 0.6.1

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 CHANGED
@@ -20,6 +20,14 @@ declare class S3BucketEntry {
20
20
  static parse(source: any): S3BucketEntry;
21
21
  }
22
22
 
23
+ declare const __brand: unique symbol;
24
+ type Brand<B> = {
25
+ [__brand]: B;
26
+ };
27
+ type Branded<T, B> = T & Brand<B>;
28
+ type BucketName = Branded<string, "BucketName">;
29
+ type ObjectKey = Branded<string, "ObjectKey">;
30
+
23
31
  declare const write: unique symbol;
24
32
  declare const stream: unique symbol;
25
33
  declare const signedRequest: unique symbol;
@@ -34,6 +42,7 @@ interface S3ClientOptions {
34
42
  type OverridableS3ClientOptions = Pick<S3ClientOptions, "region" | "bucket" | "endpoint">;
35
43
  type CreateFileInstanceOptions = {};
36
44
  type DeleteObjectsOptions = {
45
+ bucket?: string;
37
46
  signal?: AbortSignal;
38
47
  };
39
48
  interface S3FilePresignOptions {
@@ -274,19 +283,19 @@ declare class S3Client {
274
283
  * @throws {RangeError} If `key` is not at least 1 character long.
275
284
  * @throws {Error} If `uploadId` is not provided.
276
285
  */
277
- abortMultipartUpload(key: string, uploadId: string, options?: AbortMultipartUploadOptions): Promise<void>;
286
+ abortMultipartUpload(path: string, uploadId: string, options?: AbortMultipartUploadOptions): Promise<void>;
278
287
  /**
279
288
  * @remarks Uses [`CompleteMultipartUpload`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_CompleteMultipartUpload.html).
280
289
  * @throws {RangeError} If `key` is not at least 1 character long.
281
290
  * @throws {Error} If `uploadId` is not provided.
282
291
  */
283
- completeMultipartUpload(key: string, uploadId: string, parts: readonly MultipartUploadPart[], options?: CompleteMultipartUploadOptions): Promise<CompleteMultipartUploadResult>;
292
+ completeMultipartUpload(path: string, uploadId: string, parts: readonly MultipartUploadPart[], options?: CompleteMultipartUploadOptions): Promise<CompleteMultipartUploadResult>;
284
293
  /**
285
294
  * @remarks Uses [`UploadPart`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_UploadPart.html).
286
295
  * @throws {RangeError} If `key` is not at least 1 character long.
287
296
  * @throws {Error} If `uploadId` is not provided.
288
297
  */
289
- uploadPart(key: string, uploadId: string, data: UndiciBodyInit, partNumber: number, options?: UploadPartOptions): Promise<UploadPartResult>;
298
+ uploadPart(path: string, uploadId: string, data: UndiciBodyInit, partNumber: number, options?: UploadPartOptions): Promise<UploadPartResult>;
290
299
  /**
291
300
  * @remarks Uses [`ListParts`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListParts.html).
292
301
  * @throws {RangeError} If `key` is not at least 1 character long.
@@ -295,7 +304,7 @@ declare class S3Client {
295
304
  * @throws {RangeError} If `options.maxParts` is <= 0.
296
305
  * @throws {TypeError} If `options.partNumberMarker` is not a `string`.
297
306
  */
298
- listParts(key: string, uploadId: string, options?: ListPartsOptions): Promise<ListPartsResult>;
307
+ listParts(path: string, uploadId: string, options?: ListPartsOptions): Promise<ListPartsResult>;
299
308
  /**
300
309
  * Creates a new bucket on the S3 server.
301
310
  *
@@ -347,7 +356,7 @@ declare class S3Client {
347
356
  * TODO: Maybe move this into a separate free function?
348
357
  * @internal
349
358
  */
350
- [signedRequest](method: HttpMethod, pathWithoutBucket: string, query: string | undefined, body: UndiciBodyInit | undefined, additionalSignedHeaders: Record<string, string> | undefined, additionalUnsignedHeaders: Record<string, string> | undefined, contentHash: Buffer | undefined, bucket: string | undefined, signal?: AbortSignal | undefined): Promise<Dispatcher.ResponseData<null>>;
359
+ [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>>;
351
360
  /**
352
361
  * @internal
353
362
  * @param {import("./index.d.ts").UndiciBodyInit} data TODO
@@ -373,7 +382,7 @@ declare class S3File {
373
382
  /**
374
383
  * @internal
375
384
  */
376
- constructor(client: S3Client, path: string, start: number | undefined, end: number | undefined, contentType: string | undefined);
385
+ constructor(client: S3Client, path: ObjectKey, start: number | undefined, end: number | undefined, contentType: string | undefined);
377
386
  slice(start?: number | undefined, end?: number | undefined, contentType?: string | undefined): S3File;
378
387
  /**
379
388
  * Get the stat of a file in the bucket. Uses `HEAD` request to check existence.
package/dist/index.js CHANGED
@@ -318,6 +318,34 @@ function getAuthorizationHeader(keyCache, method, path, query, date, sortedSigne
318
318
  return `AWS4-HMAC-SHA256 Credential=${credentialSpec}, SignedHeaders=${signedHeadersSpec}, Signature=${signature}`;
319
319
  }
320
320
 
321
+ // src/branded.ts
322
+ function ensureValidBucketName(name) {
323
+ if (name.length < 3 || name.length > 63) {
324
+ throw new Error("`name` must be between 3 and 63 characters long.");
325
+ }
326
+ if (name.startsWith(".") || name.endsWith(".")) {
327
+ throw new Error("`name` must not start or end with a period (.)");
328
+ }
329
+ if (!/^[a-z0-9.-]+$/.test(name)) {
330
+ throw new Error(
331
+ "`name` can only contain lowercase letters, numbers, periods (.), and hyphens (-)."
332
+ );
333
+ }
334
+ if (name.includes("..")) {
335
+ throw new Error("`name` must not contain two adjacent periods (..)");
336
+ }
337
+ return name;
338
+ }
339
+ function ensureValidPath(path) {
340
+ if (typeof path !== "string") {
341
+ throw new TypeError("`path` must be a `string`.");
342
+ }
343
+ if (path.length < 1) {
344
+ throw new RangeError("`path` must be at least 1 character long.");
345
+ }
346
+ return path;
347
+ }
348
+
321
349
  // src/S3Client.ts
322
350
  var write = Symbol("write");
323
351
  var stream = Symbol("stream");
@@ -412,7 +440,13 @@ var S3Client = class {
412
440
  * ```
413
441
  */
414
442
  file(path, _options) {
415
- return new S3File(this, path, void 0, void 0, void 0);
443
+ return new S3File(
444
+ this,
445
+ ensureValidPath(path),
446
+ void 0,
447
+ void 0,
448
+ void 0
449
+ );
416
450
  }
417
451
  /**
418
452
  * Generate a presigned URL for temporary access to a file.
@@ -489,11 +523,10 @@ var S3Client = class {
489
523
  //#region multipart uploads
490
524
  async createMultipartUpload(key, options = {}) {
491
525
  if (key.length < 1) {
492
- throw new RangeError("`key` must be at least 1 character long.");
493
526
  }
494
527
  const response = await this[signedRequest](
495
528
  "POST",
496
- key,
529
+ ensureValidPath(key),
497
530
  "uploads=",
498
531
  void 0,
499
532
  void 0,
@@ -596,16 +629,13 @@ var S3Client = class {
596
629
  * @throws {RangeError} If `key` is not at least 1 character long.
597
630
  * @throws {Error} If `uploadId` is not provided.
598
631
  */
599
- async abortMultipartUpload(key, uploadId, options = {}) {
600
- if (key.length < 1) {
601
- throw new RangeError("`key` must be at least 1 character long.");
602
- }
632
+ async abortMultipartUpload(path, uploadId, options = {}) {
603
633
  if (!uploadId) {
604
634
  throw new Error("`uploadId` is required.");
605
635
  }
606
636
  const response = await this[signedRequest](
607
637
  "DELETE",
608
- key,
638
+ ensureValidPath(path),
609
639
  `uploadId=${encodeURIComponent(uploadId)}`,
610
640
  void 0,
611
641
  void 0,
@@ -615,7 +645,7 @@ var S3Client = class {
615
645
  options.signal
616
646
  );
617
647
  if (response.statusCode !== 204) {
618
- throw await getResponseError(response, key);
648
+ throw await getResponseError(response, path);
619
649
  }
620
650
  }
621
651
  /**
@@ -623,10 +653,7 @@ var S3Client = class {
623
653
  * @throws {RangeError} If `key` is not at least 1 character long.
624
654
  * @throws {Error} If `uploadId` is not provided.
625
655
  */
626
- async completeMultipartUpload(key, uploadId, parts, options = {}) {
627
- if (key.length < 1) {
628
- throw new RangeError("`key` must be at least 1 character long.");
629
- }
656
+ async completeMultipartUpload(path, uploadId, parts, options = {}) {
630
657
  if (!uploadId) {
631
658
  throw new Error("`uploadId` is required.");
632
659
  }
@@ -640,7 +667,7 @@ var S3Client = class {
640
667
  });
641
668
  const response = await this[signedRequest](
642
669
  "POST",
643
- key,
670
+ ensureValidPath(path),
644
671
  `uploadId=${encodeURIComponent(uploadId)}`,
645
672
  body,
646
673
  void 0,
@@ -650,7 +677,7 @@ var S3Client = class {
650
677
  options.signal
651
678
  );
652
679
  if (response.statusCode !== 200) {
653
- throw await getResponseError(response, key);
680
+ throw await getResponseError(response, path);
654
681
  }
655
682
  const text = await response.body.text();
656
683
  const res = ensureParsedXml(text).CompleteMultipartUploadResult ?? {};
@@ -672,10 +699,7 @@ var S3Client = class {
672
699
  * @throws {RangeError} If `key` is not at least 1 character long.
673
700
  * @throws {Error} If `uploadId` is not provided.
674
701
  */
675
- async uploadPart(key, uploadId, data, partNumber, options = {}) {
676
- if (key.length < 1) {
677
- throw new RangeError("`key` must be at least 1 character long.");
678
- }
702
+ async uploadPart(path, uploadId, data, partNumber, options = {}) {
679
703
  if (!uploadId) {
680
704
  throw new Error("`uploadId` is required.");
681
705
  }
@@ -687,7 +711,7 @@ var S3Client = class {
687
711
  }
688
712
  const response = await this[signedRequest](
689
713
  "PUT",
690
- key,
714
+ ensureValidPath(path),
691
715
  `partNumber=${partNumber}&uploadId=${encodeURIComponent(uploadId)}`,
692
716
  data,
693
717
  void 0,
@@ -719,7 +743,7 @@ var S3Client = class {
719
743
  * @throws {RangeError} If `options.maxParts` is <= 0.
720
744
  * @throws {TypeError} If `options.partNumberMarker` is not a `string`.
721
745
  */
722
- async listParts(key, uploadId, options = {}) {
746
+ async listParts(path, uploadId, options = {}) {
723
747
  let query = "";
724
748
  if (options.maxParts) {
725
749
  if (typeof options.maxParts !== "number") {
@@ -739,7 +763,7 @@ var S3Client = class {
739
763
  query += `&uploadId=${encodeURIComponent(uploadId)}`;
740
764
  const response = await this[signedRequest](
741
765
  "GET",
742
- key,
766
+ ensureValidPath(path),
743
767
  // 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
744
768
  query.substring(1),
745
769
  void 0,
@@ -771,7 +795,7 @@ var S3Client = class {
771
795
  )
772
796
  };
773
797
  }
774
- throw await getResponseError(response, key);
798
+ throw await getResponseError(response, path);
775
799
  }
776
800
  //#endregion
777
801
  //#region bucket operations
@@ -952,7 +976,7 @@ var S3Client = class {
952
976
  void 0,
953
977
  void 0,
954
978
  void 0,
955
- options.bucket ?? this.#options.bucket,
979
+ ensureValidBucketName(options.bucket ?? this.#options.bucket),
956
980
  options.signal
957
981
  );
958
982
  if (response.statusCode !== 200) {
@@ -1004,7 +1028,7 @@ var S3Client = class {
1004
1028
  },
1005
1029
  void 0,
1006
1030
  void 0,
1007
- this.#options.bucket,
1031
+ ensureValidBucketName(options.bucket ?? this.#options.bucket),
1008
1032
  options.signal
1009
1033
  );
1010
1034
  if (response.statusCode === 200) {
@@ -1299,23 +1323,6 @@ function buildSearchParams(amzCredential, date, expiresIn, headerList, contentHa
1299
1323
  }
1300
1324
  return res;
1301
1325
  }
1302
- function ensureValidBucketName(name) {
1303
- if (name.length < 3 || name.length > 63) {
1304
- throw new Error("`name` must be between 3 and 63 characters long.");
1305
- }
1306
- if (name.startsWith(".") || name.endsWith(".")) {
1307
- throw new Error("`name` must not start or end with a period (.)");
1308
- }
1309
- if (!/^[a-z0-9.-]+$/.test(name)) {
1310
- throw new Error(
1311
- "`name` can only contain lowercase letters, numbers, periods (.), and hyphens (-)."
1312
- );
1313
- }
1314
- if (name.includes("..")) {
1315
- throw new Error("`name` must not contain two adjacent periods (..)");
1316
- }
1317
- return name;
1318
- }
1319
1326
  function ensureParsedXml(text) {
1320
1327
  try {
1321
1328
  const r = xmlParser2.parse(text);
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.6.0",
5
+ "version": "0.6.1",
6
6
  "description": "A server-side S3 API for the regular user.",
7
7
  "keywords": [
8
8
  "s3",