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 +3 -3
- package/dist/index.mjs +31 -40
- package/package.json +22 -22
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,
|
|
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,
|
|
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
|
|
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, {
|
|
219
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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$
|
|
929
|
-
const contentHashStr = contentHash?.toString("hex") ??
|
|
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$
|
|
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$
|
|
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,
|
|
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$
|
|
964
|
-
const contentHashStr = contentHash?.toString("hex") ??
|
|
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(
|
|
959
|
+
range: getRangeHeader(rangeStart, rangeEndExclusive),
|
|
970
960
|
"x-amz-content-sha256": contentHashStr,
|
|
971
|
-
"x-amz-date": now$
|
|
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$
|
|
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)
|
|
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,
|
|
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(
|
|
1007
|
-
const contentHashStr = contentHash?.toString("hex") ??
|
|
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.
|
|
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 &&
|
|
38
|
-
"test:integration": "tsgo &&
|
|
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.
|
|
48
|
-
"undici": "^
|
|
47
|
+
"fast-xml-parser": "^5.8.0",
|
|
48
|
+
"undici": "^8.2.0"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@testcontainers/localstack": "^11.
|
|
52
|
-
"@testcontainers/minio": "^11.
|
|
53
|
-
"@testcontainers/s3mock": "^11.
|
|
54
|
-
"@types/node": "^25.
|
|
55
|
-
"@typescript/native-preview": "^7.0.0-dev.
|
|
56
|
-
"expect": "^30.
|
|
57
|
-
"lefthook": "^2.1.
|
|
58
|
-
"oxfmt": "^0.
|
|
59
|
-
"oxlint": "^1.
|
|
60
|
-
"
|
|
61
|
-
"
|
|
62
|
-
"
|
|
63
|
-
"typedoc": "^0.28.
|
|
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": "^
|
|
66
|
+
"node": "^22.22.2 || ^24.15.0 || ^26.1.0"
|
|
67
67
|
}
|
|
68
68
|
}
|