s3mini 0.9.2 β†’ 0.9.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/README.md CHANGED
@@ -2,13 +2,17 @@
2
2
 
3
3
  `s3mini` is an ultra-lightweight Typescript client (~20 KB minified, β‰ˆ15 % more ops/s) for S3-compatible object storage. It runs on Node, Bun, Cloudflare Workers, and other edge platforms. It has been tested on Cloudflare R2, Backblaze B2, DigitalOcean Spaces, Ceph, Oracle, Garage and MinIO. (No Browser support!)
4
4
 
5
+ ![Node.js](https://img.shields.io/badge/node.js-6DA55F?style=for-the-badge&logo=node.js&logoColor=white)
6
+ ![Bun](https://img.shields.io/badge/Bun-%23000000.svg?style=for-the-badge&logo=bun&logoColor=white)
7
+ ![Cloudflare](https://img.shields.io/badge/Cloudflare-F38020?style=for-the-badge&logo=Cloudflare&logoColor=white)
8
+
5
9
  [[github](https://github.com/good-lly/s3mini)]
6
10
  [[issues](https://github.com/good-lly/s3mini/issues)]
7
11
  [[npm](https://www.npmjs.com/package/s3mini)]
8
12
 
9
13
  ## Features
10
14
 
11
- - πŸš€ Light and fast: averages β‰ˆ15 % more ops/s and only ~20 KB (minified, not gzipped).
15
+ - πŸš€ Light and fast: ~20 KB (minified, not gzipped), up to 1.37x faster on Bun vs Node.
12
16
  - πŸ”§ Zero dependencies; supports AWS SigV4, pre-signed URLs, and SSE-C headers (tested on Cloudflare)
13
17
  - 🟠 Works on Cloudflare Workers; ideal for edge computing, Node, and Bun (no browser support).
14
18
  - πŸ”‘ Only the essential S3 APIsβ€”improved list, put, get, delete, and a few more.
@@ -44,6 +48,10 @@ Dev:
44
48
 
45
49
  <a href="https://github.com/good-lly/s3mini/issues/"> <img src="https://img.shields.io/badge/contributions-welcome-brightgreen.svg" alt="Contributions welcome" /></a>
46
50
 
51
+ ### Bun vs Node
52
+
53
+ s3mini is tested on both Node and Bun. In our benchmarks against MinIO, Bun is roughly **~1.4x faster** on most operations (median across ~40 tests). Blob multipart uploads see the largest gain (~20x) thanks to Bun's native `Blob.slice()`. Results are approximate and will vary by environment.
54
+
47
55
  ## Table of Contents
48
56
 
49
57
  - [Installation](#installation)
@@ -515,6 +523,21 @@ const url = await s3.getPresignedUrl('GET', 'report.pdf', 3600, {
515
523
  });
516
524
  ```
517
525
 
526
+ **Signed headers (enforce headers on the client request):**
527
+
528
+ ```typescript
529
+ // Upload URL that requires Content-Type β€” client MUST send this exact header
530
+ const url = await s3.getPresignedUrl('PUT', 'uploads/data.json', 300, {}, {
531
+ 'Content-Type': 'application/json',
532
+ });
533
+
534
+ await fetch(url, {
535
+ method: 'PUT',
536
+ body: JSON.stringify({ ok: true }),
537
+ headers: { 'Content-Type': 'application/json' },
538
+ });
539
+ ```
540
+
518
541
  **Method signature:**
519
542
 
520
543
  ```typescript
@@ -523,6 +546,7 @@ getPresignedUrl(
523
546
  key: string,
524
547
  expiresIn?: number, // Default: 3600 (1 hour), max: 604800 (7 days)
525
548
  queryParams?: Record<string, string>,
549
+ headers?: Record<string, string>, // HTTP headers to sign (e.g. Content-Type)
526
550
  ): Promise<string>
527
551
  ```
528
552
 
@@ -532,6 +556,7 @@ getPresignedUrl(
532
556
  - Works with both virtual-hosted-style and path-style endpoints.
533
557
  - Special characters and unicode in keys are handled automatically.
534
558
  - Throws `TypeError` for empty keys or out-of-range `expiresIn`.
559
+ - When `headers` are provided, they are included in `X-Amz-SignedHeaders` and the signature. The client consuming the URL must send those exact headers with matching values. The `host` header is always signed automatically.
535
560
 
536
561
  ---
537
562
 
@@ -603,7 +628,7 @@ await s3.copyObject('secret.dat', 'backup/secret.dat', {
603
628
  | `getContentLength(key, ssec?)` | `Promise<number>` | Get size in bytes |
604
629
  | `copyObject(source, dest, opts?)` | `Promise<CopyObjectResult>` | Server-side copy |
605
630
  | `moveObject(source, dest, opts?)` | `Promise<CopyObjectResult>` | Copy + delete |
606
- | `getPresignedUrl(method, key, expiresIn?, queryParams?)` | `Promise<string>` | Generate pre-signed URL |
631
+ | `getPresignedUrl(method, key, expiresIn?, queryParams?, headers?)` | `Promise<string>` | Generate pre-signed URL |
607
632
  | `getMultipartUploadId(key, type?, ssec?, headers?)` | `Promise<string>` | Init multipart |
608
633
  | `uploadPart(key, uploadId, data, partNum, opts?, ssec?, headers?)` | `Promise<UploadPart>` | Upload part |
609
634
  | `completeMultipartUpload(key, uploadId, parts)` | `Promise<CompleteResult>` | Complete multipart |
package/dist/s3mini.d.ts CHANGED
@@ -164,6 +164,7 @@ declare class S3mini {
164
164
  readonly logger?: Logger;
165
165
  readonly _fetch: typeof fetch;
166
166
  readonly minPartSize: number;
167
+ private readonly _bun?;
167
168
  private signingKeyDate?;
168
169
  private signingKey?;
169
170
  constructor({ accessKeyId, secretAccessKey, endpoint, region, requestSizeInBytes, requestAbortTimeout, logger, fetch, minPartSize, }: S3Config);
@@ -175,6 +176,8 @@ declare class S3mini {
175
176
  * @returns true if both accessKeyId and secretAccessKey are non-empty.
176
177
  */
177
178
  private _hasCredentials;
179
+ /** Run a read op via Bun-native S3, returning null on NoSuchKey. */
180
+ private _bunRead;
178
181
  private _ensureValidUrl;
179
182
  private _validateMethodIsGetOrHead;
180
183
  private _checkKey;
@@ -249,6 +252,10 @@ declare class S3mini {
249
252
  private _parseListObjectsResponse;
250
253
  private _extractObjectsFromResponse;
251
254
  private _extractContinuationToken;
255
+ private _bunListAll;
256
+ private _bunFetchPage;
257
+ private _bunNextCursor;
258
+ private _bunMapListResult;
252
259
  /**
253
260
  * Lists multipart uploads in the bucket.
254
261
  * This method sends a request to list multipart uploads in the specified bucket.
@@ -579,6 +586,9 @@ declare class S3mini {
579
586
  */
580
587
  deleteObject(key: string): Promise<boolean>;
581
588
  private _deleteObjectsProcess;
589
+ private _sendDeleteRequest;
590
+ private _markDeletedKeys;
591
+ private _logDeleteErrors;
582
592
  /**
583
593
  * Deletes multiple objects from the bucket.
584
594
  * @param {string[]} keys - An array of object keys to delete.
@@ -597,20 +607,24 @@ declare class S3mini {
597
607
  * @param {'GET' | 'PUT'} method - HTTP method ('GET' for download, 'PUT' for upload)
598
608
  * @param {string} key - The object key/path
599
609
  * @param {number} [expiresIn=3600] - URL expiration time in seconds (1–604800)
600
- * @param {Record<string, string>} [queryParams={}] - Additional query parameters to sign
610
+ * @param {Record<string, string>} [queryParams={}] - Additional query parameters to include in the URL
611
+ * @param {Record<string, string>} [headers={}] - HTTP headers to sign. The consumer of the URL
612
+ * MUST send these exact headers with matching values. The `host` header is always signed automatically.
601
613
  * @returns {Promise<string>} Pre-signed URL string
602
614
  * @throws {TypeError} If key is empty or expiresIn is out of range
603
615
  * @example
604
616
  * // Download URL valid for 1 hour
605
617
  * const url = await s3.getPresignedUrl('GET', 'photos/vacation.jpg');
606
618
  *
607
- * // Upload URL valid for 5 minutes
608
- * const url = await s3.getPresignedUrl('PUT', 'uploads/file.bin', 300);
619
+ * // Upload URL valid for 5 minutes with signed Content-Type
620
+ * const url = await s3.getPresignedUrl('PUT', 'uploads/file.bin', 300, {}, {
621
+ * 'Content-Type': 'application/octet-stream',
622
+ * });
609
623
  *
610
- * // Client-side usage (no credentials needed)
611
- * await fetch(url, { method: 'PUT', body: data });
624
+ * // Client-side usage (must include signed headers)
625
+ * await fetch(url, { method: 'PUT', body: data, headers: { 'Content-Type': 'application/octet-stream' } });
612
626
  */
613
- getPresignedUrl(method: 'GET' | 'PUT', key: string, expiresIn?: number, queryParams?: Record<string, string>): Promise<string>;
627
+ getPresignedUrl(method: 'GET' | 'PUT', key: string, expiresIn?: number, queryParams?: Record<string, string>, headers?: Record<string, string>): Promise<string>;
614
628
  private _presign;
615
629
  private _getSignatureKey;
616
630
  }