@jsenv/server 15.5.2 → 16.0.0

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.
Files changed (55) hide show
  1. package/README.md +79 -86
  2. package/dist/html/4xx.html +196 -0
  3. package/dist/html/500.html +17 -0
  4. package/dist/html/route_inspector.html +2435 -0
  5. package/dist/js/formidable.js +17 -0
  6. package/dist/js/vendors.js +2001 -0
  7. package/dist/js/ws.js +6863 -0
  8. package/dist/jsenv_server.js +8829 -0
  9. package/package.json +20 -9
  10. package/src/content_negotiation/pick_content_version.js +63 -0
  11. package/src/{fetch_filesystem.js → filesystem/fetch_filesystem.js} +57 -72
  12. package/src/{serve_directory.js → filesystem/serve_directory.js} +1 -2
  13. package/src/interfacing_with_node/from_node_request.js +455 -0
  14. package/src/interfacing_with_node/get_observable_value_type.js +43 -0
  15. package/src/interfacing_with_node/observable.js +91 -75
  16. package/src/interfacing_with_node/observable_from.js +34 -0
  17. package/src/interfacing_with_node/observable_from_file_handle.js +30 -0
  18. package/src/interfacing_with_node/observable_from_node_stream.js +44 -46
  19. package/src/interfacing_with_node/observable_from_node_web_readable_stream.js +32 -0
  20. package/src/interfacing_with_node/observable_from_promise.js +13 -0
  21. package/src/interfacing_with_node/write_node_response.js +80 -35
  22. package/src/internal/colorizeResponseStatus.js +2 -0
  23. package/src/internal/headers_composition.js +42 -15
  24. package/src/internal/multiple-header.js +27 -12
  25. package/src/internal/object_composition.js +2 -2
  26. package/src/internal/response_composition.js +16 -3
  27. package/src/main.js +15 -11
  28. package/src/progressive_response.js +38 -0
  29. package/src/replace_placeholder_in_html.js +12 -0
  30. package/src/request_body_handling.js +22 -0
  31. package/src/router/router.js +878 -0
  32. package/src/server_events.js +431 -0
  33. package/src/service_controller.js +79 -73
  34. package/src/services/autoreload_on_server_restart/client/autoreload_on_server_restart.js +125 -0
  35. package/src/services/autoreload_on_server_restart/jsenv_service_autoreload_on_server_restart.js +45 -0
  36. package/src/services/cors/jsenv_service_cors.js +10 -18
  37. package/src/services/default_body_4xx_5xx/client/4xx.html +65 -0
  38. package/src/services/default_body_4xx_5xx/jsenv_service_default_body_4xx_5xx.js +82 -0
  39. package/src/services/error_handler/client/500.html +17 -0
  40. package/src/services/error_handler/jsenv_service_error_handler.js +47 -28
  41. package/src/services/internal_client_files/jsenv_service_internal_client_files.js +34 -0
  42. package/src/services/open_file/jsenv_service_open_file.js +37 -0
  43. package/src/services/route_inspector/client/code_block.js +215 -0
  44. package/src/services/route_inspector/client/route_inspector.html +1363 -0
  45. package/src/services/route_inspector/jsenv_service_route_inspector.js +39 -0
  46. package/src/start_server.js +575 -607
  47. package/src/web_socket_response.js +53 -0
  48. package/src/from_fetch_response.js +0 -15
  49. package/src/interfacing_with_node/body.js +0 -81
  50. package/src/interfacing_with_node/request_factory.js +0 -125
  51. package/src/readRequestBody.js +0 -32
  52. package/src/server_timing/timing_measure.js +0 -26
  53. package/src/service_composition/path_to_regexp.js +0 -436
  54. package/src/service_composition/routing.js +0 -31
  55. package/src/sse/sse_room.js +0 -313
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The standard ways to create a Response
3
+ * - new Response(body, init)
4
+ * - Response.json(data, init)
5
+ * Here we need a way to tell: I want to handle websocket
6
+ * to align with the style of new Response and Response.json to make it look as follow:
7
+ * ```js
8
+ * import { WebSocketResponse } from "@jsenv/server"
9
+ * new WebSocketResponse((websocket) => {
10
+ * // do stuff with the websocket
11
+ * })
12
+ * ```
13
+ *
14
+ * But we don't really need a class so we are just returning a regular object under the hood
15
+ */
16
+
17
+ export class WebSocketResponse {
18
+ constructor(
19
+ webSocketHandler,
20
+ {
21
+ status = 101,
22
+ statusText = status === 101 ? "Switching Protocols" : undefined,
23
+ headers,
24
+ } = {},
25
+ ) {
26
+ const webSocketResponse = {
27
+ status,
28
+ statusText,
29
+ headers,
30
+ body: {
31
+ websocket: webSocketHandler,
32
+ },
33
+ };
34
+ // eslint-disable-next-line no-constructor-return
35
+ return webSocketResponse;
36
+ }
37
+ }
38
+
39
+ export const isWebSocketResponse = (responseProperties) => {
40
+ return (
41
+ responseProperties.body &&
42
+ typeof responseProperties.body.websocket === "function"
43
+ );
44
+ };
45
+
46
+ export const getWebSocketHandler = (responseProperties) => {
47
+ const responseBody = responseProperties.body;
48
+ if (!responseBody) {
49
+ return undefined;
50
+ }
51
+ const webSocketHandler = responseBody.websocket;
52
+ return webSocketHandler;
53
+ };
@@ -1,15 +0,0 @@
1
- export const fromFetchResponse = (fetchResponse) => {
2
- const responseHeaders = {};
3
- const headersToIgnore = ["connection"];
4
- fetchResponse.headers.forEach((value, name) => {
5
- if (!headersToIgnore.includes(name)) {
6
- responseHeaders[name] = value;
7
- }
8
- });
9
- return {
10
- status: fetchResponse.status,
11
- statusText: fetchResponse.statusText,
12
- headers: responseHeaders,
13
- body: fetchResponse.body, // node-fetch assumed
14
- };
15
- };
@@ -1,81 +0,0 @@
1
- import { createReadStream } from "node:fs";
2
- import { Readable, Stream, Writable } from "node:stream";
3
-
4
- import { isObservable, observableFromValue } from "./observable.js";
5
- import { observableFromNodeStream } from "./observable_from_node_stream.js";
6
-
7
- export const normalizeBodyMethods = (body) => {
8
- if (isObservable(body)) {
9
- return {
10
- asObservable: () => body,
11
- destroy: () => {},
12
- };
13
- }
14
-
15
- if (isFileHandle(body)) {
16
- return {
17
- asObservable: () => fileHandleToObservable(body),
18
- destroy: () => {
19
- body.close();
20
- },
21
- };
22
- }
23
-
24
- if (isNodeStream(body)) {
25
- return {
26
- asObservable: () => observableFromNodeStream(body),
27
- destroy: () => {
28
- body.destroy();
29
- },
30
- };
31
- }
32
-
33
- return {
34
- asObservable: () => observableFromValue(body),
35
- destroy: () => {},
36
- };
37
- };
38
-
39
- export const isFileHandle = (value) => {
40
- return value && value.constructor && value.constructor.name === "FileHandle";
41
- };
42
-
43
- export const fileHandleToReadableStream = (fileHandle) => {
44
- const fileReadableStream =
45
- typeof fileHandle.createReadStream === "function"
46
- ? fileHandle.createReadStream()
47
- : createReadStream(
48
- "/toto", // is it ok to pass a fake path like this?
49
- {
50
- fd: fileHandle.fd,
51
- emitClose: true,
52
- // autoClose: true
53
- },
54
- );
55
- // I suppose it's required only when doing fs.createReadStream()
56
- // and not fileHandle.createReadStream()
57
- // fileReadableStream.on("end", () => {
58
- // fileHandle.close()
59
- // })
60
- return fileReadableStream;
61
- };
62
-
63
- const fileHandleToObservable = (fileHandle) => {
64
- return observableFromNodeStream(fileHandleToReadableStream(fileHandle));
65
- };
66
-
67
- const isNodeStream = (value) => {
68
- if (value === undefined) {
69
- return false;
70
- }
71
-
72
- if (
73
- value instanceof Stream ||
74
- value instanceof Writable ||
75
- value instanceof Readable
76
- ) {
77
- return true;
78
- }
79
-
80
- return false;
81
- };
@@ -1,125 +0,0 @@
1
- import { Abort } from "@jsenv/abort";
2
- import { headersFromObject } from "../internal/headersFromObject.js";
3
- import { observableFromNodeStream } from "./observable_from_node_stream.js";
4
-
5
- export const fromNodeRequest = (
6
- nodeRequest,
7
- { serverOrigin, signal, requestBodyLifetime },
8
- ) => {
9
- const handleRequestOperation = Abort.startOperation();
10
- if (signal) {
11
- handleRequestOperation.addAbortSignal(signal);
12
- }
13
- handleRequestOperation.addAbortSource((abort) => {
14
- nodeRequest.once("close", abort);
15
- return () => {
16
- nodeRequest.removeListener("close", abort);
17
- };
18
- });
19
-
20
- const headers = headersFromObject(nodeRequest.headers);
21
- const body = observableFromNodeStream(nodeRequest, {
22
- readableStreamLifetime: requestBodyLifetime,
23
- });
24
-
25
- let requestOrigin;
26
- if (nodeRequest.upgrade) {
27
- requestOrigin = serverOrigin;
28
- } else if (nodeRequest.authority) {
29
- requestOrigin = nodeRequest.connection.encrypted
30
- ? `https://${nodeRequest.authority}`
31
- : `http://${nodeRequest.authority}`;
32
- } else if (nodeRequest.headers.host) {
33
- requestOrigin = nodeRequest.connection.encrypted
34
- ? `https://${nodeRequest.headers.host}`
35
- : `http://${nodeRequest.headers.host}`;
36
- } else {
37
- requestOrigin = serverOrigin;
38
- }
39
-
40
- return Object.freeze({
41
- signal: handleRequestOperation.signal,
42
- http2: Boolean(nodeRequest.stream),
43
- origin: requestOrigin,
44
- ...getPropertiesFromResource({
45
- resource: nodeRequest.url,
46
- baseUrl: requestOrigin,
47
- }),
48
- method: nodeRequest.method,
49
- headers,
50
- body,
51
- });
52
- };
53
-
54
- export const applyRedirectionToRequest = (
55
- request,
56
- { resource, pathname, ...rest },
57
- ) => {
58
- return {
59
- ...request,
60
- ...(resource
61
- ? getPropertiesFromResource({
62
- resource,
63
- baseUrl: request.url,
64
- })
65
- : pathname
66
- ? getPropertiesFromPathname({
67
- pathname,
68
- baseUrl: request.url,
69
- })
70
- : {}),
71
- ...rest,
72
- };
73
- };
74
-
75
- const getPropertiesFromResource = ({ resource, baseUrl }) => {
76
- const urlObject = new URL(resource, baseUrl);
77
- let pathname = urlObject.pathname;
78
-
79
- return {
80
- url: String(urlObject),
81
- searchParams: urlObject.searchParams,
82
- pathname,
83
- resource,
84
- };
85
- };
86
-
87
- const getPropertiesFromPathname = ({ pathname, baseUrl }) => {
88
- return getPropertiesFromResource({
89
- resource: `${pathname}${new URL(baseUrl).search}`,
90
- baseUrl,
91
- });
92
- };
93
-
94
- export const createPushRequest = (request, { signal, pathname, method }) => {
95
- const pushRequest = Object.freeze({
96
- ...request,
97
- parent: request,
98
- signal,
99
- http2: true,
100
- ...(pathname
101
- ? getPropertiesFromPathname({
102
- pathname,
103
- baseUrl: request.url,
104
- })
105
- : {}),
106
- method: method || request.method,
107
- headers: getHeadersInheritedByPushRequest(request),
108
- body: undefined,
109
- });
110
- return pushRequest;
111
- };
112
-
113
- const getHeadersInheritedByPushRequest = (request) => {
114
- const headersInherited = { ...request.headers };
115
- // mtime sent by the client in request headers concerns the main request
116
- // Time remains valid for request to other resources so we keep it
117
- // in child requests
118
- // delete childHeaders["if-modified-since"]
119
-
120
- // eTag sent by the client in request headers concerns the main request
121
- // A request made to an other resource must not inherit the eTag
122
- delete headersInherited["if-none-match"];
123
-
124
- return headersInherited;
125
- };
@@ -1,32 +0,0 @@
1
- export const readRequestBody = (request, { as = "string" } = {}) => {
2
- return new Promise((resolve, reject) => {
3
- const bufferArray = [];
4
- request.body.subscribe({
5
- error: reject,
6
- next: (buffer) => {
7
- bufferArray.push(buffer);
8
- },
9
- complete: () => {
10
- const bodyAsBuffer = Buffer.concat(bufferArray);
11
-
12
- if (as === "buffer") {
13
- resolve(bodyAsBuffer);
14
- return;
15
- }
16
-
17
- if (as === "string") {
18
- const bodyAsString = bodyAsBuffer.toString();
19
- resolve(bodyAsString);
20
- return;
21
- }
22
-
23
- if (as === "json") {
24
- const bodyAsString = bodyAsBuffer.toString();
25
- const bodyAsJSON = JSON.parse(bodyAsString);
26
- resolve(bodyAsJSON);
27
- return;
28
- }
29
- },
30
- });
31
- });
32
- };
@@ -1,26 +0,0 @@
1
- import { performance } from "node:perf_hooks";
2
-
3
- export const timeStart = (name) => {
4
- // as specified in https://w3c.github.io/server-timing/#the-performanceservertiming-interface
5
- // duration is a https://www.w3.org/TR/hr-time-2/#sec-domhighrestimestamp
6
- const startTimestamp = performance.now();
7
- const timeEnd = () => {
8
- const endTimestamp = performance.now();
9
- const timing = {
10
- [name]: endTimestamp - startTimestamp,
11
- };
12
- return timing;
13
- };
14
- return timeEnd;
15
- };
16
-
17
- export const timeFunction = (name, fn) => {
18
- const timeEnd = timeStart(name);
19
- const returnValue = fn();
20
- if (returnValue && typeof returnValue.then === "function") {
21
- return returnValue.then((value) => {
22
- return [timeEnd(), value];
23
- });
24
- }
25
- return [timeEnd(), returnValue];
26
- };