lean-s3 0.9.12 → 0.9.23

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.mts CHANGED
@@ -18,7 +18,7 @@ declare class S3BucketEntry {
18
18
  /**
19
19
  * @internal
20
20
  */
21
- static parse(source: any): S3BucketEntry;
21
+ static parse(this: void, source: any): S3BucketEntry;
22
22
  }
23
23
  //#endregion
24
24
  //#region src/branded.d.ts
@@ -523,11 +523,11 @@ declare class S3Client {
523
523
  */
524
524
  [kSignedRequest](region: Region, endpoint: Endpoint, bucket: BucketName, method: HttpMethod, pathWithoutBucket: ObjectKey, query: string | undefined, body: string | Buffer | Uint8Array | Readable | undefined, additionalSignedHeaders: Record<string, string> | undefined, additionalUnsignedHeaders: Record<string, string> | undefined, contentHash: Buffer | undefined, signal: AbortSignal | undefined): Promise<Dispatcher.ResponseData<null>>;
525
525
  /** @internal */
526
- [kWrite](path: ObjectKey, data: string | Buffer | Uint8Array | Readable, contentType: string, contentLength: number | undefined, contentHash: Buffer | undefined, rageStart: number | undefined, rangeEndExclusive: number | undefined, signal?: AbortSignal | undefined): Promise<void>;
526
+ [kWrite](path: ObjectKey, data: string | Buffer | Uint8Array | Readable, contentType: string, contentLength: number | undefined, contentHash: Buffer | undefined, rangeStart: number | undefined, rangeEndExclusive: number | undefined, signal?: AbortSignal): Promise<void>;
527
527
  /**
528
528
  * @internal
529
529
  */
530
- [kStream](path: ObjectKey, contentHash: Buffer | undefined, rageStart: number | undefined, rangeEndExclusive: number | undefined): ReadableStream<Uint8Array>;
530
+ [kStream](path: ObjectKey, contentHash: Buffer | undefined, rangeStart: number | undefined, rangeEndExclusive: number | undefined, signal?: AbortSignal): ReadableStream<Uint8Array>;
531
531
  [nodeUtil.inspect.custom](_depth?: number, options?: nodeUtil.InspectOptions): string;
532
532
  }
533
533
  //#endregion
package/dist/index.mjs CHANGED
@@ -3,7 +3,6 @@ import { Readable } from "node:stream";
3
3
  import { Agent, request } from "undici";
4
4
  import { XMLBuilder, XMLParser } from "fast-xml-parser";
5
5
  import { createHash, createHmac } from "node:crypto";
6
-
7
6
  //#region src/S3Stat.ts
8
7
  var S3Stat = class S3Stat {
9
8
  etag;
@@ -30,7 +29,6 @@ var S3Stat = class S3Stat {
30
29
  return new S3Stat(etag, new Date(lm), size, ct);
31
30
  }
32
31
  };
33
-
34
32
  //#endregion
35
33
  //#region src/S3Error.ts
36
34
  var S3Error = class extends Error {
@@ -40,7 +38,7 @@ var S3Error = class extends Error {
40
38
  message;
41
39
  /** The HTTP status code. */
42
40
  status;
43
- constructor(code, path, { message = void 0, cause = void 0, status = void 0 } = {}) {
41
+ constructor(code, path, { message, cause, status } = {}) {
44
42
  super(message, { cause });
45
43
  this.code = code;
46
44
  this.path = path;
@@ -48,7 +46,6 @@ var S3Error = class extends Error {
48
46
  this.status = status;
49
47
  }
50
48
  };
51
-
52
49
  //#endregion
53
50
  //#region src/S3BucketEntry.ts
54
51
  /**
@@ -78,7 +75,6 @@ var S3BucketEntry = class S3BucketEntry {
78
75
  return new S3BucketEntry(source.Key, source.Size, new Date(source.LastModified), source.ETag, source.StorageClass, source.ChecksumAlgorithm, source.ChecksumType);
79
76
  }
80
77
  };
81
-
82
78
  //#endregion
83
79
  //#region src/sign.ts
84
80
  function deriveSigningKey(date, region, secretAccessKey) {
@@ -116,7 +112,6 @@ function sha256(data) {
116
112
  function md5Base64(data) {
117
113
  return createHash("md5").update(data).digest("base64");
118
114
  }
119
-
120
115
  //#endregion
121
116
  //#region src/KeyCache.ts
122
117
  var KeyCache = class {
@@ -135,7 +130,6 @@ var KeyCache = class {
135
130
  return newKey;
136
131
  }
137
132
  };
138
-
139
133
  //#endregion
140
134
  //#region src/AmzDate.ts
141
135
  const ONE_DAY = 1e3 * 60 * 60 * 24;
@@ -157,7 +151,6 @@ function pad4(v) {
157
151
  function pad2(v) {
158
152
  return v < 10 ? `0${v}` : v.toString();
159
153
  }
160
-
161
154
  //#endregion
162
155
  //#region src/url.ts
163
156
  function buildRequestUrl(endpoint, bucket, region, path) {
@@ -196,7 +189,6 @@ function prepareHeadersForSigning(unfilteredHeadersUnsorted) {
196
189
  function getRangeHeader(start, endExclusive) {
197
190
  return typeof start === "number" || typeof endExclusive === "number" ? `bytes=${start ?? 0}-${typeof endExclusive === "number" ? endExclusive - 1 : ""}` : void 0;
198
191
  }
199
-
200
192
  //#endregion
201
193
  //#region src/error.ts
202
194
  const xmlParser$1 = new XMLParser();
@@ -215,8 +207,14 @@ async function getResponseError(response, path) {
215
207
  }
216
208
  function fromStatusCode(code, path) {
217
209
  switch (code) {
218
- case 404: return new S3Error("NoSuchKey", path, { message: "The specified key does not exist." });
219
- case 403: return new S3Error("AccessDenied", path, { message: "Access denied to the key." });
210
+ case 404: return new S3Error("NoSuchKey", path, {
211
+ message: "The specified key does not exist.",
212
+ status: code
213
+ });
214
+ case 403: return new S3Error("AccessDenied", path, {
215
+ message: "Access denied to the key.",
216
+ status: code
217
+ });
220
218
  default: return;
221
219
  }
222
220
  }
@@ -236,17 +234,14 @@ function parseAndGetXmlError(body, path) {
236
234
  }
237
235
  return new S3Error(error.Code || "Unknown", path, { message: error.Message || void 0 });
238
236
  }
239
-
240
237
  //#endregion
241
238
  //#region src/request.ts
242
239
  function getAuthorizationHeader(keyCache, method, path, query, date, sortedSignedHeaders, region, contentHashStr, accessKeyId, secretAccessKey) {
243
240
  const dataDigest = createCanonicalDataDigest(method, path, query, sortedSignedHeaders, contentHashStr);
244
- const signingKey = keyCache.computeIfAbsent(date, region, accessKeyId, secretAccessKey);
245
- const signature = signCanonicalDataHash(signingKey, dataDigest, date, region);
241
+ const signature = signCanonicalDataHash(keyCache.computeIfAbsent(date, region, accessKeyId, secretAccessKey), dataDigest, date, region);
246
242
  const signedHeadersSpec = Object.keys(sortedSignedHeaders).join(";");
247
243
  return `AWS4-HMAC-SHA256 Credential=${`${accessKeyId}/${date.date}/${region}/s3/aws4_request`}, SignedHeaders=${signedHeadersSpec}, Signature=${signature}`;
248
244
  }
249
-
250
245
  //#endregion
251
246
  //#region src/branded.ts
252
247
  function ensureValidBucketName(bucket) {
@@ -282,13 +277,11 @@ function ensureValidRegion(region) {
282
277
  if (region.length < 1) throw new RangeError("`region` must be at least 1 character long.");
283
278
  return region;
284
279
  }
285
-
286
280
  //#endregion
287
281
  //#region src/assertNever.ts
288
282
  function assertNever(v) {
289
283
  throw new TypeError(`Expected value not to have type ${typeof v}`);
290
284
  }
291
-
292
285
  //#endregion
293
286
  //#region src/encode.ts
294
287
  /**
@@ -311,7 +304,6 @@ function getContentDispositionHeader(value) {
311
304
  function encodeURIComponentExtended(value) {
312
305
  return encodeURIComponent(value).replaceAll(":", "%3A").replaceAll("+", "%2B").replaceAll("(", "%28").replaceAll(")", "%29").replaceAll(",", "%2C").replaceAll("'", "%27").replaceAll("*", "%2A");
313
306
  }
314
-
315
307
  //#endregion
316
308
  //#region src/S3Client.ts
317
309
  const kWrite = Symbol("kWrite");
@@ -442,8 +434,7 @@ var S3Client = class {
442
434
  const contentDisposition = options.response?.contentDisposition;
443
435
  const responseContentDisposition = contentDisposition ? getContentDispositionHeader(contentDisposition) : void 0;
444
436
  const res = buildRequestUrl(endpoint, bucket, region, ensureValidPath(path));
445
- const now = /* @__PURE__ */ new Date();
446
- const date = getAmzDate(now);
437
+ const date = getAmzDate(/* @__PURE__ */ new Date());
447
438
  const query = buildSearchParams(`${this.#options.accessKeyId}/${date.date}/${region}/s3/aws4_request`, date, options.expiresIn ?? 3600, 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", unsignedPayload, options.storageClass, this.#options.sessionToken, options.acl, responseContentDisposition);
448
439
  const dataDigest = typeof contentLength === "number" || typeof contentType === "string" ? createCanonicalDataDigest(method, res.pathname, query, typeof contentLength === "number" && typeof contentType === "string" ? {
449
440
  "content-length": String(contentLength),
@@ -456,8 +447,7 @@ var S3Client = class {
456
447
  "content-type": contentType,
457
448
  host: res.host
458
449
  } : {}, unsignedPayload) : createCanonicalDataDigestHostOnly(method, res.pathname, query, res.host);
459
- const signingKey = this.#keyCache.computeIfAbsent(date, region, this.#options.accessKeyId, this.#options.secretAccessKey);
460
- res.search = `${query}&X-Amz-Signature=${signCanonicalDataHash(signingKey, dataDigest, date, region)}`;
450
+ res.search = `${query}&X-Amz-Signature=${signCanonicalDataHash(this.#keyCache.computeIfAbsent(date, region, this.#options.accessKeyId, this.#options.secretAccessKey), dataDigest, date, region)}`;
461
451
  return res.toString();
462
452
  }
463
453
  presignPost(options) {
@@ -925,11 +915,11 @@ var S3Client = class {
925
915
  async [kSignedRequest](region, endpoint, bucket, method, pathWithoutBucket, query, body, additionalSignedHeaders, additionalUnsignedHeaders, contentHash, signal) {
926
916
  const url = buildRequestUrl(endpoint, bucket, region, pathWithoutBucket);
927
917
  if (query) url.search = query;
928
- const now$3 = now();
929
- const contentHashStr = contentHash?.toString("hex") ?? unsignedPayload;
918
+ const now$2 = now();
919
+ const contentHashStr = contentHash?.toString("hex") ?? "UNSIGNED-PAYLOAD";
930
920
  const headersToBeSigned = prepareHeadersForSigning({
931
921
  host: url.host,
932
- "x-amz-date": now$3.dateTime,
922
+ "x-amz-date": now$2.dateTime,
933
923
  "x-amz-content-sha256": contentHashStr,
934
924
  ...additionalSignedHeaders
935
925
  });
@@ -940,7 +930,7 @@ var S3Client = class {
940
930
  dispatcher: this.#dispatcher,
941
931
  headers: {
942
932
  ...headersToBeSigned,
943
- authorization: getAuthorizationHeader(this.#keyCache, method, url.pathname, query ?? "", now$3, headersToBeSigned, region, contentHashStr, this.#options.accessKeyId, this.#options.secretAccessKey),
933
+ authorization: getAuthorizationHeader(this.#keyCache, method, url.pathname, query ?? "", now$2, headersToBeSigned, region, contentHashStr, this.#options.accessKeyId, this.#options.secretAccessKey),
944
934
  ...additionalUnsignedHeaders,
945
935
  "user-agent": "lean-s3"
946
936
  },
@@ -955,20 +945,20 @@ var S3Client = class {
955
945
  }
956
946
  }
957
947
  /** @internal */
958
- async [kWrite](path, data, contentType, contentLength, contentHash, rageStart, rangeEndExclusive, signal = void 0) {
948
+ async [kWrite](path, data, contentType, contentLength, contentHash, rangeStart, rangeEndExclusive, signal) {
959
949
  const bucket = this.#options.bucket;
960
950
  const endpoint = this.#options.endpoint;
961
951
  const region = this.#options.region;
962
952
  const url = buildRequestUrl(endpoint, bucket, region, path);
963
- const now$2 = now();
964
- const contentHashStr = contentHash?.toString("hex") ?? unsignedPayload;
953
+ const now$3 = now();
954
+ const contentHashStr = contentHash?.toString("hex") ?? "UNSIGNED-PAYLOAD";
965
955
  const headersToBeSigned = prepareHeadersForSigning({
966
956
  "content-length": contentLength?.toString() ?? void 0,
967
957
  "content-type": contentType,
968
958
  host: url.host,
969
- range: getRangeHeader(rageStart, rangeEndExclusive),
959
+ range: getRangeHeader(rangeStart, rangeEndExclusive),
970
960
  "x-amz-content-sha256": contentHashStr,
971
- "x-amz-date": now$2.dateTime
961
+ "x-amz-date": now$3.dateTime
972
962
  });
973
963
  let response;
974
964
  try {
@@ -978,7 +968,7 @@ var S3Client = class {
978
968
  dispatcher: this.#dispatcher,
979
969
  headers: {
980
970
  ...headersToBeSigned,
981
- authorization: getAuthorizationHeader(this.#keyCache, "PUT", url.pathname, url.search, now$2, headersToBeSigned, region, contentHashStr, this.#options.accessKeyId, this.#options.secretAccessKey),
971
+ authorization: getAuthorizationHeader(this.#keyCache, "PUT", url.pathname, url.search, now$3, headersToBeSigned, region, contentHashStr, this.#options.accessKeyId, this.#options.secretAccessKey),
982
972
  "user-agent": "lean-s3"
983
973
  },
984
974
  body: data
@@ -991,20 +981,23 @@ var S3Client = class {
991
981
  });
992
982
  }
993
983
  const status = response.statusCode;
994
- if (200 <= status && status < 300) return;
984
+ if (200 <= status && status < 300) {
985
+ response.body.dump();
986
+ return;
987
+ }
995
988
  throw await getResponseError(response, path);
996
989
  }
997
990
  /**
998
991
  * @internal
999
992
  */
1000
- [kStream](path, contentHash, rageStart, rangeEndExclusive) {
993
+ [kStream](path, contentHash, rangeStart, rangeEndExclusive, signal) {
1001
994
  const bucket = this.#options.bucket;
1002
995
  const endpoint = this.#options.endpoint;
1003
996
  const region = this.#options.region;
1004
997
  const now$1 = now();
1005
998
  const url = buildRequestUrl(endpoint, bucket, region, path);
1006
- const range = getRangeHeader(rageStart, rangeEndExclusive);
1007
- const contentHashStr = contentHash?.toString("hex") ?? unsignedPayload;
999
+ const range = getRangeHeader(rangeStart, rangeEndExclusive);
1000
+ const contentHashStr = contentHash?.toString("hex") ?? "UNSIGNED-PAYLOAD";
1008
1001
  const headersToBeSigned = prepareHeadersForSigning({
1009
1002
  "amz-sdk-invocation-id": crypto.randomUUID(),
1010
1003
  host: url.host,
@@ -1024,7 +1017,7 @@ var S3Client = class {
1024
1017
  };
1025
1018
  request(url, {
1026
1019
  method: "GET",
1027
- signal: ac.signal,
1020
+ signal: signal ? AbortSignal.any([signal, ac.signal]) : ac.signal,
1028
1021
  dispatcher: this.#dispatcher,
1029
1022
  headers: {
1030
1023
  ...headersToBeSigned,
@@ -1120,7 +1113,6 @@ function ensureParsedXml(text) {
1120
1113
  });
1121
1114
  }
1122
1115
  }
1123
-
1124
1116
  //#endregion
1125
1117
  //#region src/S3File.ts
1126
1118
  var S3File = class S3File {
@@ -1289,6 +1281,5 @@ var S3File = class S3File {
1289
1281
  return `S3File ${nodeUtil.formatWithOptions(options, properties)}`;
1290
1282
  }
1291
1283
  };
1292
-
1293
1284
  //#endregion
1294
- export { S3BucketEntry, S3Client, S3Error, S3File, S3Stat };
1285
+ export { S3BucketEntry, S3Client, S3Error, S3File, S3Stat };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lean-s3",
3
- "version": "0.9.12",
3
+ "version": "0.9.23",
4
4
  "description": "A server-side S3 API for the regular user.",
5
5
  "keywords": [
6
6
  "AWS S3",
@@ -34,35 +34,35 @@
34
34
  "types": "./dist/index.d.mts",
35
35
  "scripts": {
36
36
  "build": "tsdown",
37
- "test": "tsgo && tsx --test src/*.test.ts src/test/*.test.ts",
38
- "test:integration": "tsgo && tsx --test src/test/test.integration.ts",
39
- "ci": "oxlint --deny-warnings -f github",
37
+ "test": "tsgo && node --test src/*.test.ts src/test/*.test.ts",
38
+ "test:integration": "tsgo && node --test src/test/test.integration.ts",
39
+ "ci": "oxlint --type-aware --deny-warnings -f github",
40
40
  "docs": "typedoc",
41
41
  "format": "oxfmt",
42
- "lint": "oxlint",
43
- "lint:fix": "oxlint --fix",
42
+ "lint": "oxlint --type-aware",
43
+ "lint:fix": "oxlint --type-aware --fix",
44
44
  "prepublishOnly": "npm run build"
45
45
  },
46
46
  "dependencies": {
47
- "fast-xml-parser": "^5.3.7",
48
- "undici": "^7.22.0"
47
+ "fast-xml-parser": "^5.8.0",
48
+ "undici": "^8.2.0"
49
49
  },
50
50
  "devDependencies": {
51
- "@testcontainers/localstack": "^11.12.0",
52
- "@testcontainers/minio": "^11.12.0",
53
- "@testcontainers/s3mock": "^11.12.0",
54
- "@types/node": "^25.3.0",
55
- "@typescript/native-preview": "^7.0.0-dev.20260220.1",
56
- "expect": "^30.2.0",
57
- "lefthook": "^2.1.1",
58
- "oxfmt": "^0.34.0",
59
- "oxlint": "^1.49.0",
60
- "testcontainers": "^11.12.0",
61
- "tsdown": "^0.20.3",
62
- "tsx": "^4.21.0",
63
- "typedoc": "^0.28.17"
51
+ "@testcontainers/localstack": "^11.14.0",
52
+ "@testcontainers/minio": "^11.14.0",
53
+ "@testcontainers/s3mock": "^11.14.0",
54
+ "@types/node": "^25.7.0",
55
+ "@typescript/native-preview": "^7.0.0-dev.20260511.1",
56
+ "expect": "^30.4.1",
57
+ "lefthook": "^2.1.6",
58
+ "oxfmt": "^0.49.0",
59
+ "oxlint": "^1.64.0",
60
+ "oxlint-tsgolint": "^0.22.1",
61
+ "testcontainers": "^11.14.0",
62
+ "tsdown": "^0.22.0",
63
+ "typedoc": "^0.28.19"
64
64
  },
65
65
  "engines": {
66
- "node": "^20.19.5 || ^22.21.1 || ^24.11.0 || ^25.1.0"
66
+ "node": "^22.22.2 || ^24.15.0 || ^26.1.0"
67
67
  }
68
68
  }