@jsenv/server 15.0.1 → 15.0.3
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 +23 -23
- package/package.json +4 -4
- package/src/content_negotiation/pick_accepted_content.js +13 -13
- package/src/content_negotiation/pick_content_encoding.js +25 -25
- package/src/content_negotiation/pick_content_language.js +35 -34
- package/src/content_negotiation/pick_content_type.js +29 -29
- package/src/fetch_filesystem.js +110 -107
- package/src/from_fetch_response.js +6 -6
- package/src/interfacing_with_node/body.js +22 -22
- package/src/interfacing_with_node/observable.js +80 -80
- package/src/interfacing_with_node/observable_from_node_stream.js +35 -35
- package/src/interfacing_with_node/request_factory.js +26 -26
- package/src/interfacing_with_node/write_node_response.js +82 -82
- package/src/internal/colorizeResponseStatus.js +26 -26
- package/src/internal/convertFileSystemErrorToResponseProperties.js +10 -10
- package/src/internal/dns_resolution.js +7 -7
- package/src/internal/etag.js +11 -11
- package/src/internal/filesystem.js +19 -19
- package/src/internal/headersFromObject.js +8 -8
- package/src/internal/headersFromString.js +11 -11
- package/src/internal/headersToObject.js +5 -5
- package/src/internal/headersToString.js +7 -7
- package/src/internal/headers_composition.js +9 -9
- package/src/internal/hostname_parser.js +9 -9
- package/src/internal/listen.js +47 -47
- package/src/internal/listenClientError.js +10 -10
- package/src/internal/listenEvent.js +5 -5
- package/src/internal/listenRequest.js +8 -8
- package/src/internal/listenServerConnectionError.js +19 -19
- package/src/internal/multiple-header.js +50 -50
- package/src/internal/normalizeHeaderName.js +4 -4
- package/src/internal/normalizeHeaderValue.js +2 -2
- package/src/internal/object_composition.js +41 -39
- package/src/internal/response_composition.js +6 -6
- package/src/internal/server-polyglot.js +42 -42
- package/src/internal/server_ips.js +20 -20
- package/src/internal/trackServerPendingConnections.js +23 -23
- package/src/internal/trackServerPendingRequests.js +28 -28
- package/src/main.js +18 -18
- package/src/readRequestBody.js +15 -15
- package/src/serve_directory.js +16 -16
- package/src/server_timing/timing_header.js +24 -24
- package/src/server_timing/timing_measure.js +14 -14
- package/src/service_composition/path_to_regexp.js +173 -168
- package/src/service_composition/routing.js +12 -12
- package/src/service_controller.js +77 -77
- package/src/services/cors/jsenv_service_cors.js +29 -29
- package/src/services/error_handler/jsenv_service_error_handler.js +18 -18
- package/src/services/request_aliases/jsenv_service_request_aliases.js +20 -20
- package/src/services/response_acceptance_check/jsenv_service_response_acceptance_check.js +16 -16
- package/src/sse/sse_room.js +117 -115
- package/src/start_server.js +328 -328
- package/src/stopReasons.js +9 -9
package/src/fetch_filesystem.js
CHANGED
|
@@ -4,16 +4,19 @@
|
|
|
4
4
|
* It is meant to be used inside "requestToResponse"
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { createReadStream, statSync, readFile } from "node:fs"
|
|
8
|
-
import { CONTENT_TYPE } from "@jsenv/utils/src/content_type/content_type.js"
|
|
9
|
-
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
7
|
+
import { createReadStream, statSync, readFile } from "node:fs";
|
|
8
|
+
import { CONTENT_TYPE } from "@jsenv/utils/src/content_type/content_type.js";
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
isFileSystemPath,
|
|
12
|
+
fileSystemPathToUrl,
|
|
13
|
+
} from "./internal/filesystem.js";
|
|
14
|
+
import { bufferToEtag } from "./internal/etag.js";
|
|
15
|
+
import { composeTwoResponses } from "./internal/response_composition.js";
|
|
16
|
+
import { convertFileSystemErrorToResponseProperties } from "./internal/convertFileSystemErrorToResponseProperties.js";
|
|
17
|
+
import { timeFunction } from "./server_timing/timing_measure.js";
|
|
18
|
+
import { pickContentEncoding } from "./content_negotiation/pick_content_encoding.js";
|
|
19
|
+
import { serveDirectory } from "./serve_directory.js";
|
|
17
20
|
|
|
18
21
|
export const fetchFileSystem = async (
|
|
19
22
|
filesystemUrl,
|
|
@@ -34,33 +37,33 @@ export const fetchFileSystem = async (
|
|
|
34
37
|
rootDirectoryUrl, // = `${pathToFileURL(process.cwd())}/`,
|
|
35
38
|
} = {},
|
|
36
39
|
) => {
|
|
37
|
-
const urlString = asUrlString(filesystemUrl)
|
|
40
|
+
const urlString = asUrlString(filesystemUrl);
|
|
38
41
|
if (!urlString) {
|
|
39
42
|
return create500Response(
|
|
40
43
|
`fetchFileSystem first parameter must be a file url, got ${filesystemUrl}`,
|
|
41
|
-
)
|
|
44
|
+
);
|
|
42
45
|
}
|
|
43
46
|
if (!urlString.startsWith("file://")) {
|
|
44
47
|
return create500Response(
|
|
45
48
|
`fetchFileSystem url must use "file://" scheme, got ${filesystemUrl}`,
|
|
46
|
-
)
|
|
49
|
+
);
|
|
47
50
|
}
|
|
48
51
|
if (rootDirectoryUrl) {
|
|
49
|
-
let rootDirectoryUrlString = asUrlString(rootDirectoryUrl)
|
|
52
|
+
let rootDirectoryUrlString = asUrlString(rootDirectoryUrl);
|
|
50
53
|
if (!rootDirectoryUrlString) {
|
|
51
54
|
return create500Response(
|
|
52
55
|
`rootDirectoryUrl must be a string or an url, got ${rootDirectoryUrl}`,
|
|
53
|
-
)
|
|
56
|
+
);
|
|
54
57
|
}
|
|
55
58
|
if (!rootDirectoryUrlString.endsWith("/")) {
|
|
56
|
-
rootDirectoryUrlString = `${rootDirectoryUrlString}
|
|
59
|
+
rootDirectoryUrlString = `${rootDirectoryUrlString}/`;
|
|
57
60
|
}
|
|
58
61
|
if (!urlString.startsWith(rootDirectoryUrlString)) {
|
|
59
62
|
return create500Response(
|
|
60
63
|
`fetchFileSystem url must be inside root directory, got ${urlString}`,
|
|
61
|
-
)
|
|
64
|
+
);
|
|
62
65
|
}
|
|
63
|
-
rootDirectoryUrl = rootDirectoryUrlString
|
|
66
|
+
rootDirectoryUrl = rootDirectoryUrlString;
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
// here you might be tempted to add || cacheControl === 'no-cache'
|
|
@@ -68,52 +71,52 @@ export const fetchFileSystem = async (
|
|
|
68
71
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#Cacheability
|
|
69
72
|
if (cacheControl === "no-store") {
|
|
70
73
|
if (etagEnabled) {
|
|
71
|
-
console.warn(`cannot enable etag when cache-control is ${cacheControl}`)
|
|
72
|
-
etagEnabled = false
|
|
74
|
+
console.warn(`cannot enable etag when cache-control is ${cacheControl}`);
|
|
75
|
+
etagEnabled = false;
|
|
73
76
|
}
|
|
74
77
|
if (mtimeEnabled) {
|
|
75
|
-
console.warn(`cannot enable mtime when cache-control is ${cacheControl}`)
|
|
76
|
-
mtimeEnabled = false
|
|
78
|
+
console.warn(`cannot enable mtime when cache-control is ${cacheControl}`);
|
|
79
|
+
mtimeEnabled = false;
|
|
77
80
|
}
|
|
78
81
|
}
|
|
79
82
|
if (etagEnabled && mtimeEnabled) {
|
|
80
83
|
console.warn(
|
|
81
84
|
`cannot enable both etag and mtime, mtime disabled in favor of etag.`,
|
|
82
|
-
)
|
|
83
|
-
mtimeEnabled = false
|
|
85
|
+
);
|
|
86
|
+
mtimeEnabled = false;
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
if (method !== "GET" && method !== "HEAD") {
|
|
87
90
|
return {
|
|
88
91
|
status: 501,
|
|
89
|
-
}
|
|
92
|
+
};
|
|
90
93
|
}
|
|
91
94
|
|
|
92
|
-
const sourceUrl = `file://${new URL(urlString).pathname}
|
|
95
|
+
const sourceUrl = `file://${new URL(urlString).pathname}`;
|
|
93
96
|
try {
|
|
94
97
|
const [readStatTiming, sourceStat] = await timeFunction(
|
|
95
98
|
"file service>read file stat",
|
|
96
99
|
() => statSync(new URL(sourceUrl)),
|
|
97
|
-
)
|
|
100
|
+
);
|
|
98
101
|
if (sourceStat.isDirectory()) {
|
|
99
102
|
if (canReadDirectory) {
|
|
100
103
|
return serveDirectory(urlString, {
|
|
101
104
|
headers,
|
|
102
105
|
canReadDirectory,
|
|
103
106
|
rootDirectoryUrl,
|
|
104
|
-
})
|
|
107
|
+
});
|
|
105
108
|
}
|
|
106
109
|
return {
|
|
107
110
|
status: 403,
|
|
108
111
|
statusText: "not allowed to read directory",
|
|
109
|
-
}
|
|
112
|
+
};
|
|
110
113
|
}
|
|
111
114
|
// not a file, give up
|
|
112
115
|
if (!sourceStat.isFile()) {
|
|
113
116
|
return {
|
|
114
117
|
status: 404,
|
|
115
118
|
timing: readStatTiming,
|
|
116
|
-
}
|
|
119
|
+
};
|
|
117
120
|
}
|
|
118
121
|
|
|
119
122
|
const clientCacheResponse = await getClientCacheResponse({
|
|
@@ -124,7 +127,7 @@ export const fetchFileSystem = async (
|
|
|
124
127
|
mtimeEnabled,
|
|
125
128
|
sourceStat,
|
|
126
129
|
sourceUrl,
|
|
127
|
-
})
|
|
130
|
+
});
|
|
128
131
|
|
|
129
132
|
// send 304 (redirect response to client cache)
|
|
130
133
|
// because the response body does not have to be transmitted
|
|
@@ -137,24 +140,24 @@ export const fetchFileSystem = async (
|
|
|
137
140
|
},
|
|
138
141
|
},
|
|
139
142
|
clientCacheResponse,
|
|
140
|
-
)
|
|
143
|
+
);
|
|
141
144
|
}
|
|
142
145
|
|
|
143
|
-
let response
|
|
146
|
+
let response;
|
|
144
147
|
if (compressionEnabled && sourceStat.size >= compressionSizeThreshold) {
|
|
145
148
|
const compressedResponse = await getCompressedResponse({
|
|
146
149
|
headers,
|
|
147
150
|
sourceUrl,
|
|
148
|
-
})
|
|
151
|
+
});
|
|
149
152
|
if (compressedResponse) {
|
|
150
|
-
response = compressedResponse
|
|
153
|
+
response = compressedResponse;
|
|
151
154
|
}
|
|
152
155
|
}
|
|
153
156
|
if (!response) {
|
|
154
157
|
response = await getRawResponse({
|
|
155
158
|
sourceStat,
|
|
156
159
|
sourceUrl,
|
|
157
|
-
})
|
|
160
|
+
});
|
|
158
161
|
}
|
|
159
162
|
|
|
160
163
|
const intermediateResponse = composeTwoResponses(
|
|
@@ -171,8 +174,8 @@ export const fetchFileSystem = async (
|
|
|
171
174
|
},
|
|
172
175
|
},
|
|
173
176
|
response,
|
|
174
|
-
)
|
|
175
|
-
return composeTwoResponses(intermediateResponse, clientCacheResponse)
|
|
177
|
+
);
|
|
178
|
+
return composeTwoResponses(intermediateResponse, clientCacheResponse);
|
|
176
179
|
} catch (e) {
|
|
177
180
|
return composeTwoResponses(
|
|
178
181
|
{
|
|
@@ -181,9 +184,9 @@ export const fetchFileSystem = async (
|
|
|
181
184
|
},
|
|
182
185
|
},
|
|
183
186
|
convertFileSystemErrorToResponseProperties(e) || {},
|
|
184
|
-
)
|
|
187
|
+
);
|
|
185
188
|
}
|
|
186
|
-
}
|
|
189
|
+
};
|
|
187
190
|
|
|
188
191
|
const create500Response = (message) => {
|
|
189
192
|
return {
|
|
@@ -193,8 +196,8 @@ const create500Response = (message) => {
|
|
|
193
196
|
"content-length": Buffer.byteLength(message),
|
|
194
197
|
},
|
|
195
198
|
body: message,
|
|
196
|
-
}
|
|
197
|
-
}
|
|
199
|
+
};
|
|
200
|
+
};
|
|
198
201
|
|
|
199
202
|
const getClientCacheResponse = async ({
|
|
200
203
|
headers,
|
|
@@ -214,7 +217,7 @@ const getClientCacheResponse = async ({
|
|
|
214
217
|
// let's disable it on no-cache too
|
|
215
218
|
headers["cache-control"] === "no-cache"
|
|
216
219
|
) {
|
|
217
|
-
return { status: 200 }
|
|
220
|
+
return { status: 200 };
|
|
218
221
|
}
|
|
219
222
|
|
|
220
223
|
if (etagEnabled) {
|
|
@@ -224,18 +227,18 @@ const getClientCacheResponse = async ({
|
|
|
224
227
|
etagMemoryMaxSize,
|
|
225
228
|
sourceStat,
|
|
226
229
|
sourceUrl,
|
|
227
|
-
})
|
|
230
|
+
});
|
|
228
231
|
}
|
|
229
232
|
|
|
230
233
|
if (mtimeEnabled) {
|
|
231
234
|
return getMtimeResponse({
|
|
232
235
|
headers,
|
|
233
236
|
sourceStat,
|
|
234
|
-
})
|
|
237
|
+
});
|
|
235
238
|
}
|
|
236
239
|
|
|
237
|
-
return { status: 200 }
|
|
238
|
-
}
|
|
240
|
+
return { status: 200 };
|
|
241
|
+
};
|
|
239
242
|
|
|
240
243
|
const getEtagResponse = async ({
|
|
241
244
|
headers,
|
|
@@ -253,9 +256,9 @@ const getEtagResponse = async ({
|
|
|
253
256
|
sourceUrl,
|
|
254
257
|
sourceStat,
|
|
255
258
|
}),
|
|
256
|
-
)
|
|
259
|
+
);
|
|
257
260
|
|
|
258
|
-
const requestHasIfNoneMatchHeader = "if-none-match" in headers
|
|
261
|
+
const requestHasIfNoneMatchHeader = "if-none-match" in headers;
|
|
259
262
|
if (
|
|
260
263
|
requestHasIfNoneMatchHeader &&
|
|
261
264
|
headers["if-none-match"] === fileContentEtag
|
|
@@ -263,7 +266,7 @@ const getEtagResponse = async ({
|
|
|
263
266
|
return {
|
|
264
267
|
status: 304,
|
|
265
268
|
timing: computeEtagTiming,
|
|
266
|
-
}
|
|
269
|
+
};
|
|
267
270
|
}
|
|
268
271
|
|
|
269
272
|
return {
|
|
@@ -272,10 +275,10 @@ const getEtagResponse = async ({
|
|
|
272
275
|
etag: fileContentEtag,
|
|
273
276
|
},
|
|
274
277
|
timing: computeEtagTiming,
|
|
275
|
-
}
|
|
276
|
-
}
|
|
278
|
+
};
|
|
279
|
+
};
|
|
277
280
|
|
|
278
|
-
const ETAG_MEMORY_MAP = new Map()
|
|
281
|
+
const ETAG_MEMORY_MAP = new Map();
|
|
279
282
|
const computeEtag = async ({
|
|
280
283
|
etagMemory,
|
|
281
284
|
etagMemoryMaxSize,
|
|
@@ -283,42 +286,42 @@ const computeEtag = async ({
|
|
|
283
286
|
sourceStat,
|
|
284
287
|
}) => {
|
|
285
288
|
if (etagMemory) {
|
|
286
|
-
const etagMemoryEntry = ETAG_MEMORY_MAP.get(sourceUrl)
|
|
289
|
+
const etagMemoryEntry = ETAG_MEMORY_MAP.get(sourceUrl);
|
|
287
290
|
if (
|
|
288
291
|
etagMemoryEntry &&
|
|
289
292
|
fileStatAreTheSame(etagMemoryEntry.sourceStat, sourceStat)
|
|
290
293
|
) {
|
|
291
|
-
return etagMemoryEntry.eTag
|
|
294
|
+
return etagMemoryEntry.eTag;
|
|
292
295
|
}
|
|
293
296
|
}
|
|
294
297
|
const fileContentAsBuffer = await new Promise((resolve, reject) => {
|
|
295
298
|
readFile(new URL(sourceUrl), (error, buffer) => {
|
|
296
299
|
if (error) {
|
|
297
|
-
reject(error)
|
|
300
|
+
reject(error);
|
|
298
301
|
} else {
|
|
299
|
-
resolve(buffer)
|
|
302
|
+
resolve(buffer);
|
|
300
303
|
}
|
|
301
|
-
})
|
|
302
|
-
})
|
|
303
|
-
const eTag = bufferToEtag(fileContentAsBuffer)
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
const eTag = bufferToEtag(fileContentAsBuffer);
|
|
304
307
|
if (etagMemory) {
|
|
305
308
|
if (ETAG_MEMORY_MAP.size >= etagMemoryMaxSize) {
|
|
306
|
-
const firstKey = Array.from(ETAG_MEMORY_MAP.keys())[0]
|
|
307
|
-
ETAG_MEMORY_MAP.delete(firstKey)
|
|
309
|
+
const firstKey = Array.from(ETAG_MEMORY_MAP.keys())[0];
|
|
310
|
+
ETAG_MEMORY_MAP.delete(firstKey);
|
|
308
311
|
}
|
|
309
|
-
ETAG_MEMORY_MAP.set(sourceUrl, { sourceStat, eTag })
|
|
312
|
+
ETAG_MEMORY_MAP.set(sourceUrl, { sourceStat, eTag });
|
|
310
313
|
}
|
|
311
|
-
return eTag
|
|
312
|
-
}
|
|
314
|
+
return eTag;
|
|
315
|
+
};
|
|
313
316
|
|
|
314
317
|
// https://nodejs.org/api/fs.html#fs_class_fs_stats
|
|
315
318
|
const fileStatAreTheSame = (leftFileStat, rightFileStat) => {
|
|
316
319
|
return fileStatKeysToCompare.every((keyToCompare) => {
|
|
317
|
-
const leftValue = leftFileStat[keyToCompare]
|
|
318
|
-
const rightValue = rightFileStat[keyToCompare]
|
|
319
|
-
return leftValue === rightValue
|
|
320
|
-
})
|
|
321
|
-
}
|
|
320
|
+
const leftValue = leftFileStat[keyToCompare];
|
|
321
|
+
const rightValue = rightFileStat[keyToCompare];
|
|
322
|
+
return leftValue === rightValue;
|
|
323
|
+
});
|
|
324
|
+
};
|
|
322
325
|
const fileStatKeysToCompare = [
|
|
323
326
|
// mtime the the most likely to change, check it first
|
|
324
327
|
"mtimeMs",
|
|
@@ -329,25 +332,25 @@ const fileStatKeysToCompare = [
|
|
|
329
332
|
"uid",
|
|
330
333
|
"gid",
|
|
331
334
|
"blksize",
|
|
332
|
-
]
|
|
335
|
+
];
|
|
333
336
|
|
|
334
337
|
const getMtimeResponse = async ({ headers, sourceStat }) => {
|
|
335
338
|
if ("if-modified-since" in headers) {
|
|
336
|
-
let cachedModificationDate
|
|
339
|
+
let cachedModificationDate;
|
|
337
340
|
try {
|
|
338
|
-
cachedModificationDate = new Date(headers["if-modified-since"])
|
|
341
|
+
cachedModificationDate = new Date(headers["if-modified-since"]);
|
|
339
342
|
} catch (e) {
|
|
340
343
|
return {
|
|
341
344
|
status: 400,
|
|
342
345
|
statusText: "if-modified-since header is not a valid date",
|
|
343
|
-
}
|
|
346
|
+
};
|
|
344
347
|
}
|
|
345
348
|
|
|
346
|
-
const actualModificationDate = dateToSecondsPrecision(sourceStat.mtime)
|
|
349
|
+
const actualModificationDate = dateToSecondsPrecision(sourceStat.mtime);
|
|
347
350
|
if (Number(cachedModificationDate) >= Number(actualModificationDate)) {
|
|
348
351
|
return {
|
|
349
352
|
status: 304,
|
|
350
|
-
}
|
|
353
|
+
};
|
|
351
354
|
}
|
|
352
355
|
}
|
|
353
356
|
|
|
@@ -356,22 +359,22 @@ const getMtimeResponse = async ({ headers, sourceStat }) => {
|
|
|
356
359
|
headers: {
|
|
357
360
|
"last-modified": dateToUTCString(sourceStat.mtime),
|
|
358
361
|
},
|
|
359
|
-
}
|
|
360
|
-
}
|
|
362
|
+
};
|
|
363
|
+
};
|
|
361
364
|
|
|
362
365
|
const getCompressedResponse = async ({ sourceUrl, headers }) => {
|
|
363
366
|
const acceptedCompressionFormat = pickContentEncoding(
|
|
364
367
|
{ headers },
|
|
365
368
|
Object.keys(availableCompressionFormats),
|
|
366
|
-
)
|
|
369
|
+
);
|
|
367
370
|
if (!acceptedCompressionFormat) {
|
|
368
|
-
return null
|
|
371
|
+
return null;
|
|
369
372
|
}
|
|
370
373
|
|
|
371
|
-
const fileReadableStream = fileUrlToReadableStream(sourceUrl)
|
|
374
|
+
const fileReadableStream = fileUrlToReadableStream(sourceUrl);
|
|
372
375
|
const body = await availableCompressionFormats[acceptedCompressionFormat](
|
|
373
376
|
fileReadableStream,
|
|
374
|
-
)
|
|
377
|
+
);
|
|
375
378
|
|
|
376
379
|
return {
|
|
377
380
|
status: 200,
|
|
@@ -381,30 +384,30 @@ const getCompressedResponse = async ({ sourceUrl, headers }) => {
|
|
|
381
384
|
"vary": "accept-encoding",
|
|
382
385
|
},
|
|
383
386
|
body,
|
|
384
|
-
}
|
|
385
|
-
}
|
|
387
|
+
};
|
|
388
|
+
};
|
|
386
389
|
|
|
387
390
|
const fileUrlToReadableStream = (fileUrl) => {
|
|
388
391
|
return createReadStream(new URL(fileUrl), {
|
|
389
392
|
emitClose: true,
|
|
390
393
|
autoClose: true,
|
|
391
|
-
})
|
|
392
|
-
}
|
|
394
|
+
});
|
|
395
|
+
};
|
|
393
396
|
|
|
394
397
|
const availableCompressionFormats = {
|
|
395
398
|
br: async (fileReadableStream) => {
|
|
396
|
-
const { createBrotliCompress } = await import("node:zlib")
|
|
397
|
-
return fileReadableStream.pipe(createBrotliCompress())
|
|
399
|
+
const { createBrotliCompress } = await import("node:zlib");
|
|
400
|
+
return fileReadableStream.pipe(createBrotliCompress());
|
|
398
401
|
},
|
|
399
402
|
deflate: async (fileReadableStream) => {
|
|
400
|
-
const { createDeflate } = await import("node:zlib")
|
|
401
|
-
return fileReadableStream.pipe(createDeflate())
|
|
403
|
+
const { createDeflate } = await import("node:zlib");
|
|
404
|
+
return fileReadableStream.pipe(createDeflate());
|
|
402
405
|
},
|
|
403
406
|
gzip: async (fileReadableStream) => {
|
|
404
|
-
const { createGzip } = await import("node:zlib")
|
|
405
|
-
return fileReadableStream.pipe(createGzip())
|
|
407
|
+
const { createGzip } = await import("node:zlib");
|
|
408
|
+
return fileReadableStream.pipe(createGzip());
|
|
406
409
|
},
|
|
407
|
-
}
|
|
410
|
+
};
|
|
408
411
|
|
|
409
412
|
const getRawResponse = async ({ sourceUrl, sourceStat }) => {
|
|
410
413
|
return {
|
|
@@ -414,32 +417,32 @@ const getRawResponse = async ({ sourceUrl, sourceStat }) => {
|
|
|
414
417
|
"content-length": sourceStat.size,
|
|
415
418
|
},
|
|
416
419
|
body: fileUrlToReadableStream(sourceUrl),
|
|
417
|
-
}
|
|
418
|
-
}
|
|
420
|
+
};
|
|
421
|
+
};
|
|
419
422
|
|
|
420
423
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toUTCString
|
|
421
|
-
const dateToUTCString = (date) => date.toUTCString()
|
|
424
|
+
const dateToUTCString = (date) => date.toUTCString();
|
|
422
425
|
|
|
423
426
|
const dateToSecondsPrecision = (date) => {
|
|
424
|
-
const dateWithSecondsPrecision = new Date(date)
|
|
425
|
-
dateWithSecondsPrecision.setMilliseconds(0)
|
|
426
|
-
return dateWithSecondsPrecision
|
|
427
|
-
}
|
|
427
|
+
const dateWithSecondsPrecision = new Date(date);
|
|
428
|
+
dateWithSecondsPrecision.setMilliseconds(0);
|
|
429
|
+
return dateWithSecondsPrecision;
|
|
430
|
+
};
|
|
428
431
|
|
|
429
432
|
const asUrlString = (value) => {
|
|
430
433
|
if (value instanceof URL) {
|
|
431
|
-
return value.href
|
|
434
|
+
return value.href;
|
|
432
435
|
}
|
|
433
436
|
if (typeof value === "string") {
|
|
434
437
|
if (isFileSystemPath(value)) {
|
|
435
|
-
return fileSystemPathToUrl(value)
|
|
438
|
+
return fileSystemPathToUrl(value);
|
|
436
439
|
}
|
|
437
440
|
try {
|
|
438
|
-
const urlObject = new URL(value)
|
|
439
|
-
return String(urlObject)
|
|
441
|
+
const urlObject = new URL(value);
|
|
442
|
+
return String(urlObject);
|
|
440
443
|
} catch (e) {
|
|
441
|
-
return null
|
|
444
|
+
return null;
|
|
442
445
|
}
|
|
443
446
|
}
|
|
444
|
-
return null
|
|
445
|
-
}
|
|
447
|
+
return null;
|
|
448
|
+
};
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
export const fromFetchResponse = (fetchResponse) => {
|
|
2
|
-
const responseHeaders = {}
|
|
3
|
-
const headersToIgnore = ["connection"]
|
|
2
|
+
const responseHeaders = {};
|
|
3
|
+
const headersToIgnore = ["connection"];
|
|
4
4
|
fetchResponse.headers.forEach((value, name) => {
|
|
5
5
|
if (!headersToIgnore.includes(name)) {
|
|
6
|
-
responseHeaders[name] = value
|
|
6
|
+
responseHeaders[name] = value;
|
|
7
7
|
}
|
|
8
|
-
})
|
|
8
|
+
});
|
|
9
9
|
return {
|
|
10
10
|
status: fetchResponse.status,
|
|
11
11
|
statusText: fetchResponse.statusText,
|
|
12
12
|
headers: responseHeaders,
|
|
13
13
|
body: fetchResponse.body, // node-fetch assumed
|
|
14
|
-
}
|
|
15
|
-
}
|
|
14
|
+
};
|
|
15
|
+
};
|
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
import { Stream, Writable, Readable } from "node:stream"
|
|
2
|
-
import { createReadStream } from "node:fs"
|
|
1
|
+
import { Stream, Writable, Readable } from "node:stream";
|
|
2
|
+
import { createReadStream } from "node:fs";
|
|
3
3
|
|
|
4
|
-
import { isObservable, observableFromValue } from "./observable.js"
|
|
5
|
-
import { observableFromNodeStream } from "./observable_from_node_stream.js"
|
|
4
|
+
import { isObservable, observableFromValue } from "./observable.js";
|
|
5
|
+
import { observableFromNodeStream } from "./observable_from_node_stream.js";
|
|
6
6
|
|
|
7
7
|
export const normalizeBodyMethods = (body) => {
|
|
8
8
|
if (isObservable(body)) {
|
|
9
9
|
return {
|
|
10
10
|
asObservable: () => body,
|
|
11
11
|
destroy: () => {},
|
|
12
|
-
}
|
|
12
|
+
};
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
if (isFileHandle(body)) {
|
|
16
16
|
return {
|
|
17
17
|
asObservable: () => fileHandleToObservable(body),
|
|
18
18
|
destroy: () => {
|
|
19
|
-
body.close()
|
|
19
|
+
body.close();
|
|
20
20
|
},
|
|
21
|
-
}
|
|
21
|
+
};
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
if (isNodeStream(body)) {
|
|
25
25
|
return {
|
|
26
26
|
asObservable: () => observableFromNodeStream(body),
|
|
27
27
|
destroy: () => {
|
|
28
|
-
body.destroy()
|
|
28
|
+
body.destroy();
|
|
29
29
|
},
|
|
30
|
-
}
|
|
30
|
+
};
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
return {
|
|
34
34
|
asObservable: () => observableFromValue(body),
|
|
35
35
|
destroy: () => {},
|
|
36
|
-
}
|
|
37
|
-
}
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
38
|
|
|
39
39
|
export const isFileHandle = (value) => {
|
|
40
|
-
return value && value.constructor && value.constructor.name === "FileHandle"
|
|
41
|
-
}
|
|
40
|
+
return value && value.constructor && value.constructor.name === "FileHandle";
|
|
41
|
+
};
|
|
42
42
|
|
|
43
43
|
export const fileHandleToReadableStream = (fileHandle) => {
|
|
44
44
|
const fileReadableStream =
|
|
@@ -51,22 +51,22 @@ export const fileHandleToReadableStream = (fileHandle) => {
|
|
|
51
51
|
emitClose: true,
|
|
52
52
|
// autoClose: true
|
|
53
53
|
},
|
|
54
|
-
)
|
|
54
|
+
);
|
|
55
55
|
// I suppose it's required only when doing fs.createReadStream()
|
|
56
56
|
// and not fileHandle.createReadStream()
|
|
57
57
|
// fileReadableStream.on("end", () => {
|
|
58
58
|
// fileHandle.close()
|
|
59
59
|
// })
|
|
60
|
-
return fileReadableStream
|
|
61
|
-
}
|
|
60
|
+
return fileReadableStream;
|
|
61
|
+
};
|
|
62
62
|
|
|
63
63
|
const fileHandleToObservable = (fileHandle) => {
|
|
64
|
-
return observableFromNodeStream(fileHandleToReadableStream(fileHandle))
|
|
65
|
-
}
|
|
64
|
+
return observableFromNodeStream(fileHandleToReadableStream(fileHandle));
|
|
65
|
+
};
|
|
66
66
|
|
|
67
67
|
const isNodeStream = (value) => {
|
|
68
68
|
if (value === undefined) {
|
|
69
|
-
return false
|
|
69
|
+
return false;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
if (
|
|
@@ -74,8 +74,8 @@ const isNodeStream = (value) => {
|
|
|
74
74
|
value instanceof Writable ||
|
|
75
75
|
value instanceof Readable
|
|
76
76
|
) {
|
|
77
|
-
return true
|
|
77
|
+
return true;
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
return false
|
|
81
|
-
}
|
|
80
|
+
return false;
|
|
81
|
+
};
|