@jsenv/server 15.0.2 → 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.
Files changed (53) hide show
  1. package/README.md +23 -23
  2. package/package.json +3 -3
  3. package/src/content_negotiation/pick_accepted_content.js +13 -13
  4. package/src/content_negotiation/pick_content_encoding.js +25 -25
  5. package/src/content_negotiation/pick_content_language.js +35 -34
  6. package/src/content_negotiation/pick_content_type.js +29 -29
  7. package/src/fetch_filesystem.js +110 -107
  8. package/src/from_fetch_response.js +6 -6
  9. package/src/interfacing_with_node/body.js +22 -22
  10. package/src/interfacing_with_node/observable.js +80 -80
  11. package/src/interfacing_with_node/observable_from_node_stream.js +35 -35
  12. package/src/interfacing_with_node/request_factory.js +26 -26
  13. package/src/interfacing_with_node/write_node_response.js +82 -82
  14. package/src/internal/colorizeResponseStatus.js +26 -26
  15. package/src/internal/convertFileSystemErrorToResponseProperties.js +10 -10
  16. package/src/internal/dns_resolution.js +7 -7
  17. package/src/internal/etag.js +11 -11
  18. package/src/internal/filesystem.js +19 -19
  19. package/src/internal/headersFromObject.js +8 -8
  20. package/src/internal/headersFromString.js +11 -11
  21. package/src/internal/headersToObject.js +5 -5
  22. package/src/internal/headersToString.js +7 -7
  23. package/src/internal/headers_composition.js +9 -9
  24. package/src/internal/hostname_parser.js +9 -9
  25. package/src/internal/listen.js +47 -47
  26. package/src/internal/listenClientError.js +10 -10
  27. package/src/internal/listenEvent.js +5 -5
  28. package/src/internal/listenRequest.js +8 -8
  29. package/src/internal/listenServerConnectionError.js +19 -19
  30. package/src/internal/multiple-header.js +50 -50
  31. package/src/internal/normalizeHeaderName.js +4 -4
  32. package/src/internal/normalizeHeaderValue.js +2 -2
  33. package/src/internal/object_composition.js +41 -39
  34. package/src/internal/response_composition.js +6 -6
  35. package/src/internal/server-polyglot.js +42 -42
  36. package/src/internal/server_ips.js +20 -20
  37. package/src/internal/trackServerPendingConnections.js +23 -23
  38. package/src/internal/trackServerPendingRequests.js +28 -28
  39. package/src/main.js +18 -18
  40. package/src/readRequestBody.js +15 -15
  41. package/src/serve_directory.js +16 -16
  42. package/src/server_timing/timing_header.js +24 -24
  43. package/src/server_timing/timing_measure.js +14 -14
  44. package/src/service_composition/path_to_regexp.js +173 -168
  45. package/src/service_composition/routing.js +12 -12
  46. package/src/service_controller.js +77 -77
  47. package/src/services/cors/jsenv_service_cors.js +29 -29
  48. package/src/services/error_handler/jsenv_service_error_handler.js +18 -18
  49. package/src/services/request_aliases/jsenv_service_request_aliases.js +20 -20
  50. package/src/services/response_acceptance_check/jsenv_service_response_acceptance_check.js +16 -16
  51. package/src/sse/sse_room.js +117 -115
  52. package/src/start_server.js +328 -328
  53. package/src/stopReasons.js +9 -9
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  `@jsenv/server` helps to write flexible server code with a declarative API.
4
4
 
5
5
  ```js
6
- import { startServer } from "@jsenv/server"
6
+ import { startServer } from "@jsenv/server";
7
7
 
8
8
  await startServer({
9
9
  port: 8080,
@@ -16,11 +16,11 @@ await startServer({
16
16
  "content-type": "text/plain",
17
17
  },
18
18
  body: "Hello world",
19
- }
19
+ };
20
20
  },
21
21
  },
22
22
  ],
23
- })
23
+ });
24
24
  ```
25
25
 
26
26
  # Examples
@@ -35,7 +35,7 @@ _Code starting a server with 2 request handlers:_
35
35
  * - otherwise
36
36
  * -> respond with 404
37
37
  */
38
- import { startServer, composeServices } from "@jsenv/server"
38
+ import { startServer, composeServices } from "@jsenv/server";
39
39
 
40
40
  const server = await startServer({
41
41
  services: [
@@ -43,33 +43,33 @@ const server = await startServer({
43
43
  name: "index",
44
44
  handleRequest: (request) => {
45
45
  if (request.resource === "/") {
46
- return { status: 200 }
46
+ return { status: 200 };
47
47
  }
48
- return null
48
+ return null;
49
49
  },
50
50
  },
51
51
  {
52
52
  name: "otherwise",
53
53
  handleRequest: () => {
54
- return { status: 404 }
54
+ return { status: 404 };
55
55
  },
56
56
  },
57
57
  ],
58
- })
58
+ });
59
59
 
60
- const fetch = await import("node-fetch")
61
- const responseForOrigin = await fetch(server.origin)
62
- responseForOrigin.status // 200
60
+ const fetch = await import("node-fetch");
61
+ const responseForOrigin = await fetch(server.origin);
62
+ responseForOrigin.status; // 200
63
63
 
64
- const responseForFoo = await fetch(`${server.origin}/foo`)
65
- responseForFoo.status // 404
64
+ const responseForFoo = await fetch(`${server.origin}/foo`);
65
+ responseForFoo.status; // 404
66
66
  ```
67
67
 
68
68
  _Code starting a server in https:_
69
69
 
70
70
  ```js
71
- import { readFileSync } from "node:fs"
72
- import { startServer } from "@jsenv/server"
71
+ import { readFileSync } from "node:fs";
72
+ import { startServer } from "@jsenv/server";
73
73
 
74
74
  await startServer({
75
75
  https: {
@@ -80,7 +80,7 @@ await startServer({
80
80
  services: [
81
81
  {
82
82
  handleRequest: (request) => {
83
- const clientUsesHttp = request.origin.startsWith("http:")
83
+ const clientUsesHttp = request.origin.startsWith("http:");
84
84
 
85
85
  return {
86
86
  status: 200,
@@ -88,29 +88,29 @@ await startServer({
88
88
  "content-type": "text/plain",
89
89
  },
90
90
  body: clientUsesHttp ? `Welcome http user` : `Welcome https user`,
91
- }
91
+ };
92
92
  },
93
93
  },
94
94
  ],
95
- })
95
+ });
96
96
  ```
97
97
 
98
98
  _Code starting a server for static files:_
99
99
 
100
100
  ```js
101
- import { startServer, fetchFileSystem } from "@jsenv/server"
101
+ import { startServer, fetchFileSystem } from "@jsenv/server";
102
102
 
103
103
  await startServer({
104
104
  services: [
105
105
  {
106
106
  handleRequest: async (request) => {
107
- const fileUrl = new URL(request.resource.slice(1), import.meta.url)
108
- const response = await fetchFileSystem(fileUrl, request)
109
- return response
107
+ const fileUrl = new URL(request.resource.slice(1), import.meta.url);
108
+ const response = await fetchFileSystem(fileUrl, request);
109
+ return response;
110
110
  },
111
111
  },
112
112
  ],
113
- })
113
+ });
114
114
  ```
115
115
 
116
116
  # Documentation
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/server",
3
- "version": "15.0.2",
3
+ "version": "15.0.3",
4
4
  "description": "Write your Node.js server using pure functions",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "repository": {
12
12
  "type": "git",
13
- "url": "https://github.com/jsenv/jsenv-core",
13
+ "url": "https://github.com/jsenv/core",
14
14
  "directory": "packages/server"
15
15
  },
16
16
  "engines": {
@@ -35,7 +35,7 @@
35
35
  },
36
36
  "dependencies": {
37
37
  "@jsenv/abort": "4.2.4",
38
- "@jsenv/log": "3.3.4",
38
+ "@jsenv/log": "3.3.5",
39
39
  "@jsenv/url-meta": "8.1.0",
40
40
  "@jsenv/utils": "2.0.1",
41
41
  "ws": "8.13.0"
@@ -3,24 +3,24 @@ export const pickAcceptedContent = ({
3
3
  accepteds,
4
4
  getAcceptanceScore,
5
5
  }) => {
6
- let highestScore = -1
7
- let availableWithHighestScore = null
8
- let availableIndex = 0
6
+ let highestScore = -1;
7
+ let availableWithHighestScore = null;
8
+ let availableIndex = 0;
9
9
  while (availableIndex < availables.length) {
10
- const available = availables[availableIndex]
11
- availableIndex++
10
+ const available = availables[availableIndex];
11
+ availableIndex++;
12
12
 
13
- let acceptedIndex = 0
13
+ let acceptedIndex = 0;
14
14
  while (acceptedIndex < accepteds.length) {
15
- const accepted = accepteds[acceptedIndex]
16
- acceptedIndex++
15
+ const accepted = accepteds[acceptedIndex];
16
+ acceptedIndex++;
17
17
 
18
- const score = getAcceptanceScore(accepted, available)
18
+ const score = getAcceptanceScore(accepted, available);
19
19
  if (score > highestScore) {
20
- availableWithHighestScore = available
21
- highestScore = score
20
+ availableWithHighestScore = available;
21
+ highestScore = score;
22
22
  }
23
23
  }
24
24
  }
25
- return availableWithHighestScore
26
- }
25
+ return availableWithHighestScore;
26
+ };
@@ -1,58 +1,58 @@
1
- import { parseMultipleHeader } from "../internal/multiple-header.js"
2
- import { pickAcceptedContent } from "./pick_accepted_content.js"
1
+ import { parseMultipleHeader } from "../internal/multiple-header.js";
2
+ import { pickAcceptedContent } from "./pick_accepted_content.js";
3
3
 
4
4
  export const pickContentEncoding = (request, availableEncodings) => {
5
- const { headers = {} } = request
6
- const requestAcceptEncodingHeader = headers["accept-encoding"]
5
+ const { headers = {} } = request;
6
+ const requestAcceptEncodingHeader = headers["accept-encoding"];
7
7
  if (!requestAcceptEncodingHeader) {
8
- return null
8
+ return null;
9
9
  }
10
10
 
11
11
  const encodingsAccepted = parseAcceptEncodingHeader(
12
12
  requestAcceptEncodingHeader,
13
- )
13
+ );
14
14
  return pickAcceptedContent({
15
15
  accepteds: encodingsAccepted,
16
16
  availables: availableEncodings,
17
17
  getAcceptanceScore: getEncodingAcceptanceScore,
18
- })
19
- }
18
+ });
19
+ };
20
20
 
21
21
  const parseAcceptEncodingHeader = (acceptEncodingHeaderString) => {
22
22
  const acceptEncodingHeader = parseMultipleHeader(acceptEncodingHeaderString, {
23
23
  validateProperty: ({ name }) => {
24
24
  // read only q, anything else is ignored
25
- return name === "q"
25
+ return name === "q";
26
26
  },
27
- })
27
+ });
28
28
 
29
- const encodingsAccepted = []
29
+ const encodingsAccepted = [];
30
30
  Object.keys(acceptEncodingHeader).forEach((key) => {
31
- const { q = 1 } = acceptEncodingHeader[key]
32
- const value = key
31
+ const { q = 1 } = acceptEncodingHeader[key];
32
+ const value = key;
33
33
  encodingsAccepted.push({
34
34
  value,
35
35
  quality: q,
36
- })
37
- })
36
+ });
37
+ });
38
38
  encodingsAccepted.sort((a, b) => {
39
- return b.quality - a.quality
40
- })
41
- return encodingsAccepted
42
- }
39
+ return b.quality - a.quality;
40
+ });
41
+ return encodingsAccepted;
42
+ };
43
43
 
44
44
  const getEncodingAcceptanceScore = ({ value, quality }, availableEncoding) => {
45
45
  if (value === "*") {
46
- return quality
46
+ return quality;
47
47
  }
48
48
 
49
49
  // normalize br to brotli
50
- if (value === "br") value = "brotli"
51
- if (availableEncoding === "br") availableEncoding = "brotli"
50
+ if (value === "br") value = "brotli";
51
+ if (availableEncoding === "br") availableEncoding = "brotli";
52
52
 
53
53
  if (value === availableEncoding) {
54
- return quality
54
+ return quality;
55
55
  }
56
56
 
57
- return -1
58
- }
57
+ return -1;
58
+ };
@@ -1,77 +1,78 @@
1
- import { parseMultipleHeader } from "../internal/multiple-header.js"
2
- import { pickAcceptedContent } from "./pick_accepted_content.js"
1
+ import { parseMultipleHeader } from "../internal/multiple-header.js";
2
+ import { pickAcceptedContent } from "./pick_accepted_content.js";
3
3
 
4
4
  export const pickContentLanguage = (request, availableLanguages) => {
5
- const { headers = {} } = request
6
- const requestAcceptLanguageHeader = headers["accept-language"]
5
+ const { headers = {} } = request;
6
+ const requestAcceptLanguageHeader = headers["accept-language"];
7
7
  if (!requestAcceptLanguageHeader) {
8
- return null
8
+ return null;
9
9
  }
10
10
 
11
11
  const languagesAccepted = parseAcceptLanguageHeader(
12
12
  requestAcceptLanguageHeader,
13
- )
13
+ );
14
14
  return pickAcceptedContent({
15
15
  accepteds: languagesAccepted,
16
16
  availables: availableLanguages,
17
17
  getAcceptanceScore: getLanguageAcceptanceScore,
18
- })
19
- }
18
+ });
19
+ };
20
20
 
21
21
  const parseAcceptLanguageHeader = (acceptLanguageHeaderString) => {
22
22
  const acceptLanguageHeader = parseMultipleHeader(acceptLanguageHeaderString, {
23
23
  validateProperty: ({ name }) => {
24
24
  // read only q, anything else is ignored
25
- return name === "q"
25
+ return name === "q";
26
26
  },
27
- })
27
+ });
28
28
 
29
- const languagesAccepted = []
29
+ const languagesAccepted = [];
30
30
  Object.keys(acceptLanguageHeader).forEach((key) => {
31
- const { q = 1 } = acceptLanguageHeader[key]
32
- const value = key
31
+ const { q = 1 } = acceptLanguageHeader[key];
32
+ const value = key;
33
33
  languagesAccepted.push({
34
34
  value,
35
35
  quality: q,
36
- })
37
- })
36
+ });
37
+ });
38
38
  languagesAccepted.sort((a, b) => {
39
- return b.quality - a.quality
40
- })
41
- return languagesAccepted
42
- }
39
+ return b.quality - a.quality;
40
+ });
41
+ return languagesAccepted;
42
+ };
43
43
 
44
44
  const getLanguageAcceptanceScore = ({ value, quality }, availableLanguage) => {
45
- const [acceptedPrimary, acceptedVariant] = decomposeLanguage(value)
45
+ const [acceptedPrimary, acceptedVariant] = decomposeLanguage(value);
46
46
  const [availablePrimary, availableVariant] =
47
- decomposeLanguage(availableLanguage)
47
+ decomposeLanguage(availableLanguage);
48
48
 
49
49
  const primaryAccepted =
50
50
  acceptedPrimary === "*" ||
51
- acceptedPrimary.toLowerCase() === availablePrimary.toLowerCase()
51
+ acceptedPrimary.toLowerCase() === availablePrimary.toLowerCase();
52
52
  const variantAccepted =
53
- acceptedVariant === "*" || compareVariant(acceptedVariant, availableVariant)
53
+ acceptedVariant === "*" ||
54
+ compareVariant(acceptedVariant, availableVariant);
54
55
 
55
56
  if (primaryAccepted && variantAccepted) {
56
- return quality + 1
57
+ return quality + 1;
57
58
  }
58
59
  if (primaryAccepted) {
59
- return quality
60
+ return quality;
60
61
  }
61
- return -1
62
- }
62
+ return -1;
63
+ };
63
64
 
64
65
  const decomposeLanguage = (fullType) => {
65
- const [primary, variant] = fullType.split("-")
66
- return [primary, variant]
67
- }
66
+ const [primary, variant] = fullType.split("-");
67
+ return [primary, variant];
68
+ };
68
69
 
69
70
  const compareVariant = (left, right) => {
70
71
  if (left === right) {
71
- return true
72
+ return true;
72
73
  }
73
74
  if (left && right && left.toLowerCase() === right.toLowerCase()) {
74
- return true
75
+ return true;
75
76
  }
76
- return false
77
- }
77
+ return false;
78
+ };
@@ -1,63 +1,63 @@
1
- import { parseMultipleHeader } from "../internal/multiple-header.js"
2
- import { pickAcceptedContent } from "./pick_accepted_content.js"
1
+ import { parseMultipleHeader } from "../internal/multiple-header.js";
2
+ import { pickAcceptedContent } from "./pick_accepted_content.js";
3
3
 
4
4
  export const pickContentType = (request, availableContentTypes) => {
5
- const { headers = {} } = request
6
- const requestAcceptHeader = headers.accept
5
+ const { headers = {} } = request;
6
+ const requestAcceptHeader = headers.accept;
7
7
  if (!requestAcceptHeader) {
8
- return null
8
+ return null;
9
9
  }
10
10
 
11
- const contentTypesAccepted = parseAcceptHeader(requestAcceptHeader)
11
+ const contentTypesAccepted = parseAcceptHeader(requestAcceptHeader);
12
12
  return pickAcceptedContent({
13
13
  accepteds: contentTypesAccepted,
14
14
  availables: availableContentTypes,
15
15
  getAcceptanceScore: getContentTypeAcceptanceScore,
16
- })
17
- }
16
+ });
17
+ };
18
18
 
19
19
  const parseAcceptHeader = (acceptHeader) => {
20
20
  const acceptHeaderObject = parseMultipleHeader(acceptHeader, {
21
21
  validateProperty: ({ name }) => {
22
22
  // read only q, anything else is ignored
23
- return name === "q"
23
+ return name === "q";
24
24
  },
25
- })
25
+ });
26
26
 
27
- const accepts = []
27
+ const accepts = [];
28
28
  Object.keys(acceptHeaderObject).forEach((key) => {
29
- const { q = 1 } = acceptHeaderObject[key]
30
- const value = key
29
+ const { q = 1 } = acceptHeaderObject[key];
30
+ const value = key;
31
31
  accepts.push({
32
32
  value,
33
33
  quality: q,
34
- })
35
- })
34
+ });
35
+ });
36
36
  accepts.sort((a, b) => {
37
- return b.quality - a.quality
38
- })
39
- return accepts
40
- }
37
+ return b.quality - a.quality;
38
+ });
39
+ return accepts;
40
+ };
41
41
 
42
42
  const getContentTypeAcceptanceScore = (
43
43
  { value, quality },
44
44
  availableContentType,
45
45
  ) => {
46
- const [acceptedType, acceptedSubtype] = decomposeContentType(value)
46
+ const [acceptedType, acceptedSubtype] = decomposeContentType(value);
47
47
  const [availableType, availableSubtype] =
48
- decomposeContentType(availableContentType)
48
+ decomposeContentType(availableContentType);
49
49
 
50
- const typeAccepted = acceptedType === "*" || acceptedType === availableType
50
+ const typeAccepted = acceptedType === "*" || acceptedType === availableType;
51
51
  const subtypeAccepted =
52
- acceptedSubtype === "*" || acceptedSubtype === availableSubtype
52
+ acceptedSubtype === "*" || acceptedSubtype === availableSubtype;
53
53
 
54
54
  if (typeAccepted && subtypeAccepted) {
55
- return quality
55
+ return quality;
56
56
  }
57
- return -1
58
- }
57
+ return -1;
58
+ };
59
59
 
60
60
  const decomposeContentType = (fullType) => {
61
- const [type, subtype] = fullType.split("/")
62
- return [type, subtype]
63
- }
61
+ const [type, subtype] = fullType.split("/");
62
+ return [type, subtype];
63
+ };