lean-s3 0.1.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/src/url.js ADDED
@@ -0,0 +1,89 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * @param {string} endpoint
5
+ * @param {string} bucket
6
+ * @param {string} region
7
+ * @param {string} path
8
+ * @returns {URL}
9
+ */
10
+ export function buildRequestUrl(endpoint, bucket, region, path) {
11
+ const [endpointWithBucketAndRegion, replacedBucket] =
12
+ replaceDomainPlaceholders(endpoint, bucket, region);
13
+
14
+ const result = new URL(endpointWithBucketAndRegion);
15
+
16
+ const pathPrefix = result.pathname.endsWith("/")
17
+ ? result.pathname
18
+ : `${result.pathname}/`;
19
+
20
+ const pathSuffix = replacedBucket
21
+ ? normalizePath(path)
22
+ : `${normalizePath(bucket)}/${normalizePath(path)}`;
23
+
24
+ result.pathname = pathPrefix + pathSuffix;
25
+
26
+ return result;
27
+ }
28
+
29
+ /**
30
+ * @param {string} endpoint
31
+ * @param {string} bucket
32
+ * @param {string} region
33
+ * @returns {[endpoint: string, replacedBucket: boolean]}
34
+ */
35
+ function replaceDomainPlaceholders(endpoint, bucket, region) {
36
+ const replacedBucket = endpoint.includes("{bucket}");
37
+ return [
38
+ endpoint.replaceAll("{bucket}", bucket).replaceAll("{region}", region),
39
+ replacedBucket,
40
+ ];
41
+ }
42
+
43
+ /**
44
+ * Removes trailing and leading slashes.
45
+ * @param {string} path
46
+ * @returns {string}
47
+ */
48
+ function normalizePath(path) {
49
+ const start = path[0] === "/" ? 1 : 0;
50
+ const end = path[path.length - 1] === "/" ? path.length - 1 : path.length;
51
+ return path.substring(start, end);
52
+ }
53
+
54
+ /**
55
+ * Sorts headers alphabetically. Removes headers that are undefined/null.
56
+ *
57
+ * `http.request` doesn't allow passing `undefined` as header values (despite the types allowing it),
58
+ * so we have to filter afterwards.
59
+ *
60
+ * @param {Record<string, string | undefined>} unfilteredHeadersUnsorted
61
+ * @returns {Record<string, string>}
62
+ */
63
+ export function prepareHeadersForSigning(unfilteredHeadersUnsorted) {
64
+ /** @type {Record<string, string>} */
65
+ const result = {};
66
+
67
+ // TODO: `Object.keys(headersUnsorted).sort()` is constant in our case,
68
+ // maybe we want to move this somewhere else to avoid sorting every time
69
+
70
+ for (const header of Object.keys(unfilteredHeadersUnsorted).sort()) {
71
+ const v = unfilteredHeadersUnsorted[header];
72
+ if (v !== undefined && v !== null) {
73
+ result[header] = v;
74
+ }
75
+ }
76
+ return result;
77
+ }
78
+
79
+ /**
80
+ * @param {number | undefined} start
81
+ * @param {number | undefined} endExclusive
82
+ * @returns {string | undefined}
83
+ */
84
+ export function getRangeHeader(start, endExclusive) {
85
+ return typeof start === "number" || typeof endExclusive === "number"
86
+ ? // Http-ranges are end-inclusive, we are exclusiv ein our slice
87
+ `bytes=${start ?? 0}-${typeof endExclusive === "number" ? endExclusive - 1 : ""}`
88
+ : undefined;
89
+ }