lean-s3 0.7.10 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -41,6 +41,14 @@ if (exists) {
41
41
  });
42
42
  await otherFile.write(test);
43
43
 
44
+ // Copy the object to a new key in the same bucket
45
+ await test.copyTo("/test-copy.json");
46
+
47
+ // You can also copy to a different bucket
48
+ await test.copyTo("/test.json", {
49
+ destinationBucket: "foo-bucket",
50
+ });
51
+
44
52
  const firstBytesFile = test.slice(0, 100); // lazy-evaluated slicing
45
53
  const firstBytes = await firstBytesFile.bytes(); // evaluated using HTTP range requests
46
54
  console.log("First 100 bytes:", firstBytes);
@@ -135,6 +143,7 @@ See [DESIGN_DECISIONS.md](./DESIGN_DECISIONS.md) to read about why this library
135
143
  - ✅ [`DeleteObjects`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjects.html) via `.deleteObjects`
136
144
  - ✅ [`DeleteObject`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObject.html) via `S3File.delete`
137
145
  - ✅ [`PutObject`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html) via `S3File.write`
146
+ - ✅ [`CopyObject`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObject.html) via `S3File.copyTo` / `.copyObject`
138
147
  - ✅ [`HeadObject`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html) via `S3File.exists`/`S3File.stat`
139
148
  - ✅ [`ListMultipartUploads`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListMultipartUploads.html) via `.listMultipartUploads`
140
149
  - ✅ [`ListParts`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListParts.html) via `.listParts`
package/dist/index.d.ts CHANGED
@@ -124,6 +124,22 @@ interface S3FilePresignOptions extends OverridableS3ClientOptions {
124
124
  contentDisposition?: ContentDisposition;
125
125
  };
126
126
  }
127
+ type CopyObjectOptions = {
128
+ /** Set this to override the {@link S3ClientOptions#bucket} that was passed on creation of the {@link S3Client}. */
129
+ sourceBucket?: string;
130
+ /** Set this to override the {@link S3ClientOptions#bucket} that was passed on creation of the {@link S3Client}. */
131
+ destinationBucket?: string;
132
+ /** Signal to abort the request. */
133
+ signal?: AbortSignal;
134
+ };
135
+ type CopyObjectResult = {
136
+ etag?: string;
137
+ lastModified?: Date;
138
+ checksumCRC32?: string;
139
+ checksumCRC32C?: string;
140
+ checksumSHA1?: string;
141
+ checksumSHA256?: string;
142
+ };
127
143
  type ListObjectsOptions = {
128
144
  /** Set this to override the {@link S3ClientOptions#bucket} that was passed on creation of the {@link S3Client}. */
129
145
  bucket?: string;
@@ -432,6 +448,11 @@ declare class S3Client {
432
448
  * ```
433
449
  */
434
450
  presign(path: string, options?: S3FilePresignOptions): string;
451
+ /**
452
+ * Copies an object from a source to a destination.
453
+ * @remarks Uses [`CopyObject`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObject.html).
454
+ */
455
+ copyObject(sourceKey: string, destinationKey: string, options?: CopyObjectOptions): Promise<CopyObjectResult>;
435
456
  createMultipartUpload(key: string, options?: CreateMultipartUploadOptions): Promise<CreateMultipartUploadResult>;
436
457
  /**
437
458
  * @remarks Uses [`ListMultipartUploads`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListMultipartUploads.html).
@@ -611,6 +632,12 @@ declare class S3File {
611
632
  * @returns {Promise<void>}
612
633
  */
613
634
  write(data: ByteSource, options?: S3FileWriteOptions): Promise<void>;
635
+ /**
636
+ * Copies this file to a new destination.
637
+ * @param destination The destination path, can be a string or another S3File instance.
638
+ * @param options Options for the copy operation.
639
+ */
640
+ copyTo(destination: string | S3File, options?: CopyObjectOptions): Promise<void>;
614
641
  }
615
642
  interface S3FileDeleteOptions extends OverridableS3ClientOptions {
616
643
  /** Signal to abort the request. */
package/dist/index.js CHANGED
@@ -589,6 +589,46 @@ var S3Client = class {
589
589
  res.search = `${query}&X-Amz-Signature=${signature}`;
590
590
  return res.toString();
591
591
  }
592
+ /**
593
+ * Copies an object from a source to a destination.
594
+ * @remarks Uses [`CopyObject`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_CopyObject.html).
595
+ */
596
+ async copyObject(sourceKey, destinationKey, options = {}) {
597
+ const sourceBucket = options.sourceBucket ? ensureValidBucketName(options.sourceBucket) : this.#options.bucket;
598
+ const destinationBucket = options.destinationBucket ? ensureValidBucketName(options.destinationBucket) : this.#options.bucket;
599
+ const normalizedSourceKey = normalizePath(ensureValidPath(sourceKey));
600
+ const copySource = encodeURIComponent(
601
+ `/${sourceBucket}/${normalizedSourceKey}`
602
+ );
603
+ const response = await this[kSignedRequest](
604
+ this.#options.region,
605
+ this.#options.endpoint,
606
+ destinationBucket,
607
+ "PUT",
608
+ ensureValidPath(destinationKey),
609
+ void 0,
610
+ void 0,
611
+ {
612
+ "x-amz-copy-source": copySource
613
+ },
614
+ void 0,
615
+ void 0,
616
+ options.signal
617
+ );
618
+ if (response.statusCode !== 200) {
619
+ throw await getResponseError(response, destinationKey);
620
+ }
621
+ const text = await response.body.text();
622
+ const res = ensureParsedXml(text).CopyObjectResult ?? {};
623
+ return {
624
+ etag: res.ETag,
625
+ lastModified: res.LastModified ? new Date(res.LastModified) : void 0,
626
+ checksumCRC32: res.ChecksumCRC32,
627
+ checksumCRC32C: res.ChecksumCRC32C,
628
+ checksumSHA1: res.ChecksumSHA1,
629
+ checksumSHA256: res.ChecksumSHA256
630
+ };
631
+ }
592
632
  //#region multipart uploads
593
633
  async createMultipartUpload(key, options = {}) {
594
634
  const response = await this[kSignedRequest](
@@ -1445,8 +1485,8 @@ var S3Client = class {
1445
1485
  );
1446
1486
  }
1447
1487
  return controller.error(
1448
- new S3Error(error.Code || "Unknown", path, {
1449
- message: error.Message || void 0
1488
+ new S3Error(error.Error.Code || "Unknown", path, {
1489
+ message: error.Error.Message || void 0
1450
1490
  // Message might be "",
1451
1491
  })
1452
1492
  );
@@ -1733,6 +1773,15 @@ var S3File = class _S3File {
1733
1773
  options.signal
1734
1774
  );
1735
1775
  }
1776
+ /**
1777
+ * Copies this file to a new destination.
1778
+ * @param destination The destination path, can be a string or another S3File instance.
1779
+ * @param options Options for the copy operation.
1780
+ */
1781
+ async copyTo(destination, options = {}) {
1782
+ const destinationKey = typeof destination === "string" ? destination : destination.#path;
1783
+ await this.#client.copyObject(this.#path, destinationKey, options);
1784
+ }
1736
1785
  };
1737
1786
  export {
1738
1787
  S3BucketEntry,
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.10",
5
+ "version": "0.8.0",
6
6
  "description": "A server-side S3 API for the regular user.",
7
7
  "keywords": [
8
8
  "s3",
@@ -44,20 +44,20 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "fast-xml-parser": "^5.2.5",
47
- "undici": "^7.13.0"
47
+ "undici": "^7.15.0"
48
48
  },
49
49
  "devDependencies": {
50
- "@biomejs/biome": "2.1.3",
51
- "@testcontainers/localstack": "^11.5.0",
52
- "@testcontainers/minio": "^11.5.0",
53
- "@types/node": "^24.1.0",
54
- "@typescript/native-preview": "^7.0.0-dev.20250802.1",
50
+ "@biomejs/biome": "2.2.2",
51
+ "@testcontainers/localstack": "^11.5.1",
52
+ "@testcontainers/minio": "^11.5.1",
53
+ "@types/node": "^24.3.0",
54
+ "@typescript/native-preview": "^7.0.0-dev.20250825.1",
55
55
  "expect": "^30.0.5",
56
- "lefthook": "^1.12.2",
57
- "testcontainers": "^11.5.0",
56
+ "lefthook": "^1.12.3",
57
+ "testcontainers": "^11.5.1",
58
58
  "tsup": "^8.5.0",
59
- "tsx": "^4.20.3",
60
- "typedoc": "^0.28.9"
59
+ "tsx": "^4.20.5",
60
+ "typedoc": "^0.28.11"
61
61
  },
62
62
  "engines": {
63
63
  "node": "^20.19.3 || ^22.17.0 || ^24.4.0"