@jsenv/server 16.3.6 → 17.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 (32) hide show
  1. package/dist/client/route_inspector/route_inspector.html +4 -4
  2. package/dist/jsenv_server.js +3628 -3376
  3. package/dist/ws/ws.js +37 -31
  4. package/index.js +56 -0
  5. package/package.json +7 -6
  6. package/src/{services/autoreload_on_server_restart/jsenv_service_autoreload_on_server_restart.js → plugins/autoreload_on_server_restart/server_plugin_autoreload_on_server_restart.js} +1 -1
  7. package/src/{services/cors/jsenv_service_cors.js → plugins/cors/server_plugin_cors.js} +1 -1
  8. package/src/{services → plugins}/default_body_4xx_5xx/client/4xx.html +1 -1
  9. package/src/{services/default_body_4xx_5xx/jsenv_service_default_body_4xx_5xx.js → plugins/default_body_4xx_5xx/server_plugin_default_body_4xx_5xx.js} +2 -1
  10. package/src/{services/error_handler/jsenv_service_error_handler.js → plugins/error_handler/server_plugin_error_handler.js} +1 -1
  11. package/src/{filesystem → plugins/filesystem}/fetch_filesystem.js +6 -5
  12. package/src/{filesystem → plugins/filesystem}/serve_directory.js +2 -1
  13. package/src/plugins/filesystem/server_plugin_static_files.js +44 -0
  14. package/src/{services/internal_client_files/jsenv_service_internal_client_files.js → plugins/internal_client_files/server_plugin_internal_client_files.js} +1 -1
  15. package/src/{services/open_file/jsenv_service_open_file.js → plugins/open_file/server_plugin_open_file.js} +1 -1
  16. package/src/{services/request_aliases/jsenv_service_request_aliases.js → plugins/request_aliases/server_plugin_request_aliases.js} +1 -1
  17. package/src/{services/response_acceptance_check/jsenv_service_response_acceptance_check.js → plugins/response_acceptance_check/server_plugin_response_acceptance_check.js} +1 -1
  18. package/src/{services → plugins}/route_inspector/client/route_inspector.html +2 -2
  19. package/src/{services/route_inspector/jsenv_service_route_inspector.js → plugins/route_inspector/server_plugin_route_inspector.js} +1 -1
  20. package/src/plugins/server_plugins_controller.js +21 -0
  21. package/src/plugins_controller.js +341 -0
  22. package/src/router/router.js +8 -5
  23. package/src/start_server.js +34 -38
  24. package/src/main.js +0 -52
  25. package/src/service_controller.js +0 -193
  26. /package/src/{services → plugins}/autoreload_on_server_restart/client/autoreload_on_server_restart.js +0 -0
  27. /package/src/{services → plugins}/error_handler/client/500.html +0 -0
  28. /package/src/{internal → plugins/filesystem}/etag.js +0 -0
  29. /package/src/{internal/convertFileSystemErrorToResponseProperties.js → plugins/filesystem/filesystem_error_to_response.js} +0 -0
  30. /package/src/{internal/filesystem.js → plugins/filesystem/filesystem_path_and_url.js} +0 -0
  31. /package/src/{services → plugins}/route_inspector/client/code_block.js +0 -0
  32. /package/src/{stopReasons.js → stop_reasons.js} +0 -0
package/dist/ws/ws.js CHANGED
@@ -243,11 +243,11 @@ function requireLimiter () {
243
243
  return limiter;
244
244
  }
245
245
 
246
- var permessageDeflate;
246
+ var permessageDeflate$1;
247
247
  var hasRequiredPermessageDeflate;
248
248
 
249
249
  function requirePermessageDeflate () {
250
- if (hasRequiredPermessageDeflate) return permessageDeflate;
250
+ if (hasRequiredPermessageDeflate) return permessageDeflate$1;
251
251
  hasRequiredPermessageDeflate = 1;
252
252
 
253
253
  const zlib = require$$0;
@@ -287,6 +287,9 @@ function requirePermessageDeflate () {
287
287
  * acknowledge disabling of client context takeover
288
288
  * @param {Number} [options.concurrencyLimit=10] The number of concurrent
289
289
  * calls to zlib
290
+ * @param {Boolean} [options.isServer=false] Create the instance in either
291
+ * server or client mode
292
+ * @param {Number} [options.maxPayload=0] The maximum allowed message length
290
293
  * @param {(Boolean|Number)} [options.serverMaxWindowBits] Request/confirm the
291
294
  * use of a custom server window size
292
295
  * @param {Boolean} [options.serverNoContextTakeover=false] Request/accept
@@ -297,16 +300,13 @@ function requirePermessageDeflate () {
297
300
  * deflate
298
301
  * @param {Object} [options.zlibInflateOptions] Options to pass to zlib on
299
302
  * inflate
300
- * @param {Boolean} [isServer=false] Create the instance in either server or
301
- * client mode
302
- * @param {Number} [maxPayload=0] The maximum allowed message length
303
303
  */
304
- constructor(options, isServer, maxPayload) {
305
- this._maxPayload = maxPayload | 0;
304
+ constructor(options) {
306
305
  this._options = options || {};
307
306
  this._threshold =
308
307
  this._options.threshold !== undefined ? this._options.threshold : 1024;
309
- this._isServer = !!isServer;
308
+ this._maxPayload = this._options.maxPayload | 0;
309
+ this._isServer = !!this._options.isServer;
310
310
  this._deflate = null;
311
311
  this._inflate = null;
312
312
 
@@ -710,7 +710,7 @@ function requirePermessageDeflate () {
710
710
  }
711
711
  }
712
712
 
713
- permessageDeflate = PerMessageDeflate;
713
+ permessageDeflate$1 = PerMessageDeflate;
714
714
 
715
715
  /**
716
716
  * The listener of the `zlib.DeflateRaw` stream `'data'` event.
@@ -776,7 +776,7 @@ function requirePermessageDeflate () {
776
776
  err[kStatusCode] = 1007;
777
777
  this[kCallback](err);
778
778
  }
779
- return permessageDeflate;
779
+ return permessageDeflate$1;
780
780
  }
781
781
 
782
782
  var validation = {exports: {}};
@@ -2564,11 +2564,11 @@ function requireEventTarget () {
2564
2564
  return eventTarget;
2565
2565
  }
2566
2566
 
2567
- var extension;
2567
+ var extension$1;
2568
2568
  var hasRequiredExtension;
2569
2569
 
2570
2570
  function requireExtension () {
2571
- if (hasRequiredExtension) return extension;
2571
+ if (hasRequiredExtension) return extension$1;
2572
2572
  hasRequiredExtension = 1;
2573
2573
 
2574
2574
  const { tokenChars } = requireValidation();
@@ -2771,8 +2771,8 @@ function requireExtension () {
2771
2771
  .join(', ');
2772
2772
  }
2773
2773
 
2774
- extension = { format, parse };
2775
- return extension;
2774
+ extension$1 = { format, parse };
2775
+ return extension$1;
2776
2776
  }
2777
2777
 
2778
2778
  /* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex|Readable$", "caughtErrors": "none" }] */
@@ -3475,7 +3475,7 @@ function requireWebsocket () {
3475
3475
  } else {
3476
3476
  try {
3477
3477
  parsedUrl = new URL(address);
3478
- } catch (e) {
3478
+ } catch {
3479
3479
  throw new SyntaxError(`Invalid URL: ${address}`);
3480
3480
  }
3481
3481
  }
@@ -3537,11 +3537,11 @@ function requireWebsocket () {
3537
3537
  opts.timeout = opts.handshakeTimeout;
3538
3538
 
3539
3539
  if (opts.perMessageDeflate) {
3540
- perMessageDeflate = new PerMessageDeflate(
3541
- opts.perMessageDeflate !== true ? opts.perMessageDeflate : {},
3542
- false,
3543
- opts.maxPayload
3544
- );
3540
+ perMessageDeflate = new PerMessageDeflate({
3541
+ ...opts.perMessageDeflate,
3542
+ isServer: false,
3543
+ maxPayload: opts.maxPayload
3544
+ });
3545
3545
  opts.headers['Sec-WebSocket-Extensions'] = format({
3546
3546
  [PerMessageDeflate.extensionName]: perMessageDeflate.offer()
3547
3547
  });
@@ -4348,17 +4348,19 @@ function requireStream () {
4348
4348
 
4349
4349
  requireStream();
4350
4350
 
4351
+ requireExtension();
4352
+
4353
+ requirePermessageDeflate();
4354
+
4351
4355
  requireReceiver();
4352
4356
 
4353
4357
  requireSender();
4354
4358
 
4355
- requireWebsocket();
4356
-
4357
- var subprotocol;
4359
+ var subprotocol$1;
4358
4360
  var hasRequiredSubprotocol;
4359
4361
 
4360
4362
  function requireSubprotocol () {
4361
- if (hasRequiredSubprotocol) return subprotocol;
4363
+ if (hasRequiredSubprotocol) return subprotocol$1;
4362
4364
  hasRequiredSubprotocol = 1;
4363
4365
 
4364
4366
  const { tokenChars } = requireValidation();
@@ -4420,10 +4422,14 @@ function requireSubprotocol () {
4420
4422
  return protocols;
4421
4423
  }
4422
4424
 
4423
- subprotocol = { parse };
4424
- return subprotocol;
4425
+ subprotocol$1 = { parse };
4426
+ return subprotocol$1;
4425
4427
  }
4426
4428
 
4429
+ requireSubprotocol();
4430
+
4431
+ requireWebsocket();
4432
+
4427
4433
  /* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Duplex$", "caughtErrors": "none" }] */
4428
4434
 
4429
4435
  var websocketServer$1;
@@ -4724,11 +4730,11 @@ function requireWebsocketServer () {
4724
4730
  this.options.perMessageDeflate &&
4725
4731
  secWebSocketExtensions !== undefined
4726
4732
  ) {
4727
- const perMessageDeflate = new PerMessageDeflate(
4728
- this.options.perMessageDeflate,
4729
- true,
4730
- this.options.maxPayload
4731
- );
4733
+ const perMessageDeflate = new PerMessageDeflate({
4734
+ ...this.options.perMessageDeflate,
4735
+ isServer: true,
4736
+ maxPayload: this.options.maxPayload
4737
+ });
4732
4738
 
4733
4739
  try {
4734
4740
  const offers = extension.parse(secWebSocketExtensions);
package/index.js ADDED
@@ -0,0 +1,56 @@
1
+ /*
2
+ * This file is the first file executed by code using the package
3
+ * Its responsability is to export what is documented
4
+ * Ideally this file should be kept simple to help discovering codebase progressively.
5
+ */
6
+
7
+ // tslint:disable:ordered-imports
8
+
9
+ export { startServer } from "./src/start_server.js";
10
+
11
+ export {
12
+ STOP_REASON_INTERNAL_ERROR,
13
+ STOP_REASON_PROCESS_SIGHUP,
14
+ STOP_REASON_PROCESS_SIGTERM,
15
+ STOP_REASON_PROCESS_SIGINT,
16
+ STOP_REASON_PROCESS_BEFORE_EXIT,
17
+ STOP_REASON_PROCESS_EXIT,
18
+ STOP_REASON_NOT_SPECIFIED,
19
+ } from "./src/stop_reasons.js";
20
+ export { serverPluginErrorHandler } from "./src/plugins/error_handler/server_plugin_error_handler.js";
21
+
22
+ // webSocket
23
+ export { WebSocketResponse } from "./src/web_socket_response.js";
24
+ // long polling
25
+ export { ProgressiveResponse } from "./src/progressive_response.js";
26
+ // Server event (can be used to push events to clients that can be connected with WebSocket or EventSource)
27
+ export { ServerEvents, LazyServerEvents } from "./src/server_events.js";
28
+
29
+ // CORS
30
+ export {
31
+ serverPluginCORS,
32
+ jsenvAccessControlAllowedHeaders,
33
+ jsenvAccessControlAllowedMethods,
34
+ } from "./src/plugins/cors/server_plugin_cors.js";
35
+
36
+ // filesystem
37
+ export {
38
+ createFileSystemFetch,
39
+ fetchFileSystem,
40
+ } from "./src/plugins/filesystem/fetch_filesystem.js";
41
+ export { serveDirectory } from "./src/plugins/filesystem/serve_directory.js";
42
+ export { serverPluginStaticFiles } from "./src/plugins/filesystem/server_plugin_static_files.js";
43
+
44
+ // content-negotiation
45
+ export { pickContentType } from "./src/content_negotiation/pick_content_type.js";
46
+ export { pickContentEncoding } from "./src/content_negotiation/pick_content_encoding.js";
47
+ export { pickContentLanguage } from "./src/content_negotiation/pick_content_language.js";
48
+ export { serverPluginResponseAcceptanceCheck } from "./src/plugins/response_acceptance_check/server_plugin_response_acceptance_check.js";
49
+
50
+ // others
51
+ export { composeTwoResponses } from "./src/internal/response_composition.js";
52
+ export { serverPluginRequestAliases } from "./src/plugins/request_aliases/server_plugin_request_aliases.js";
53
+ export { findFreePort } from "./src/internal/listen.js";
54
+
55
+ // internal (used by @jsenv/core)
56
+ export { createPluginsController } from "./src/plugins_controller.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/server",
3
- "version": "16.3.6",
3
+ "version": "17.0.0",
4
4
  "description": "A modern Node.js HTTP server with declarative routing, content negotiation, and WebSocket support",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,23 +19,24 @@
19
19
  "exports": {
20
20
  ".": {
21
21
  "import": {
22
- "dev:jsenv": "./src/main.js",
22
+ "dev:jsenv": "./index.js",
23
23
  "default": "./dist/jsenv_server.js"
24
24
  }
25
25
  },
26
26
  "./*": "./*"
27
27
  },
28
- "main": "./src/main.js",
28
+ "main": "./index.js",
29
29
  "files": [
30
30
  "/dist/",
31
- "/src/"
31
+ "/src/",
32
+ "/index.js"
32
33
  ],
33
34
  "scripts": {
34
35
  "build": "node --experimental-import-meta-resolve ./scripts/build.mjs",
35
36
  "prepublishOnly": "npm run build"
36
37
  },
37
38
  "dependencies": {
38
- "launch-editor": "2.13.1"
39
+ "launch-editor": "2.13.2"
39
40
  },
40
41
  "devDependencies": {
41
42
  "@jsenv/abort": "../../tooling/abort",
@@ -57,7 +58,7 @@
57
58
  "formidable": "3.5.4",
58
59
  "playwright": "1.58.1",
59
60
  "undici": "7.24.2",
60
- "ws": "8.19.0"
61
+ "ws": "8.20.0"
61
62
  },
62
63
  "engines": {
63
64
  "node": ">=22.13.1"
@@ -1,6 +1,6 @@
1
1
  import { LazyServerEvents } from "../../server_events.js";
2
2
 
3
- export const jsenvServiceAutoreloadOnRestart = () => {
3
+ export const serverPluginAutoreloadOnRestart = () => {
4
4
  const aliveServerEvents = new LazyServerEvents(() => {});
5
5
 
6
6
  return {
@@ -13,7 +13,7 @@ export const jsenvAccessControlAllowedMethods = [
13
13
  "OPTIONS",
14
14
  ];
15
15
 
16
- export const jsenvServiceCORS = ({
16
+ export const serverPluginCORS = ({
17
17
  accessControlAllowedOrigins = [],
18
18
  accessControlAllowedMethods = jsenvAccessControlAllowedMethods,
19
19
  accessControlAllowedHeaders = jsenvAccessControlAllowedHeaders,
@@ -60,6 +60,6 @@
60
60
  <p class="error_message">${statusMessage}</p>
61
61
  <a href="/" jsenv-ignore class="home_link">Return to Home</a>
62
62
  </div>
63
- <script src="/@jsenv/server/src/services/autoreload_on_server_restart/client/autoreload_on_server_restart.js?inline"></script>
63
+ <script src="/@jsenv/server/src/plugins/autoreload_on_server_restart/client/autoreload_on_server_restart.js?inline"></script>
64
64
  </body>
65
65
  </html>
@@ -1,10 +1,11 @@
1
1
  import { readFileSync } from "node:fs";
2
+
2
3
  import { pickContentType } from "../../content_negotiation/pick_content_type.js";
3
4
  import { replacePlaceholdersInHtml } from "../../replace_placeholder_in_html.js";
4
5
 
5
6
  const clientErrorHtmlTemplateFileUrl = import.meta.resolve("./client/4xx.html");
6
7
 
7
- export const jsenvServiceDefaultBody4xx5xx = () => {
8
+ export const serverPluginDefaultBody4xx5xx = () => {
8
9
  return {
9
10
  name: "jsenv:default_body_4xx_5xx",
10
11
 
@@ -5,7 +5,7 @@ import { replacePlaceholdersInHtml } from "../../replace_placeholder_in_html.js"
5
5
 
6
6
  const internalErrorHtmlFileUrl = import.meta.resolve("./client/500.html");
7
7
 
8
- export const jsenvServiceErrorHandler = ({ sendErrorDetails = false } = {}) => {
8
+ export const serverPluginErrorHandler = ({ sendErrorDetails = false } = {}) => {
9
9
  return {
10
10
  name: "jsenv:error_handler",
11
11
  handleError: (serverInternalError, { request }) => {
@@ -6,14 +6,15 @@
6
6
 
7
7
  import { CONTENT_TYPE } from "@jsenv/utils/src/content_type/content_type.js";
8
8
  import { createReadStream, readFile, statSync } from "node:fs";
9
- import { pickContentEncoding } from "../content_negotiation/pick_content_encoding.js";
10
- import { convertFileSystemErrorToResponseProperties } from "../internal/convertFileSystemErrorToResponseProperties.js";
11
- import { bufferToEtag } from "../internal/etag.js";
9
+
10
+ import { pickContentEncoding } from "../../content_negotiation/pick_content_encoding.js";
11
+ import { composeTwoResponses } from "../../internal/response_composition.js";
12
+ import { bufferToEtag } from "./etag.js";
13
+ import { convertFileSystemErrorToResponseProperties } from "./filesystem_error_to_response.js";
12
14
  import {
13
15
  fileSystemPathToUrl,
14
16
  isFileSystemPath,
15
- } from "../internal/filesystem.js";
16
- import { composeTwoResponses } from "../internal/response_composition.js";
17
+ } from "./filesystem_path_and_url.js";
17
18
  import { serveDirectory } from "./serve_directory.js";
18
19
 
19
20
  export const createFileSystemFetch = (directoryUrl, options) => {
@@ -1,5 +1,6 @@
1
1
  import { lstatSync, readdirSync } from "node:fs";
2
- import { pickContentType } from "../content_negotiation/pick_content_type.js";
2
+
3
+ import { pickContentType } from "../../content_negotiation/pick_content_type.js";
3
4
 
4
5
  export const serveDirectory = (
5
6
  url,
@@ -0,0 +1,44 @@
1
+ import { urlToExtension, urlToPathname } from "@jsenv/urls";
2
+
3
+ import { createFileSystemFetch } from "./fetch_filesystem.js";
4
+
5
+ export const serverPluginStaticFiles = ({ directoryUrl, mainFilePath }) => {
6
+ return {
7
+ name: "jsenv:static_files",
8
+ routes: [
9
+ {
10
+ endpoint: "GET *",
11
+ description: "Serve static files.",
12
+ fetch: (request, helpers) => {
13
+ const urlIsVersioned = new URL(request.url).searchParams.has("v");
14
+ if (mainFilePath && request.resource === "/") {
15
+ request = {
16
+ ...request,
17
+ resource: `/${mainFilePath}`,
18
+ };
19
+ }
20
+ const urlObject = new URL(request.resource.slice(1), directoryUrl);
21
+ return createFileSystemFetch(directoryUrl, {
22
+ cacheControl: urlIsVersioned
23
+ ? `private,max-age=${SECONDS_IN_30_DAYS},immutable`
24
+ : "private,max-age=0,must-revalidate",
25
+ etagEnabled: true,
26
+ compressionEnabled: true,
27
+ rootDirectoryUrl: directoryUrl,
28
+ canReadDirectory: true,
29
+ ENOENTFallback: () => {
30
+ if (
31
+ !urlToExtension(urlObject) &&
32
+ !urlToPathname(urlObject).endsWith("/")
33
+ ) {
34
+ return new URL(mainFilePath, directoryUrl);
35
+ }
36
+ return null;
37
+ },
38
+ })(request, helpers);
39
+ },
40
+ },
41
+ ],
42
+ };
43
+ };
44
+ const SECONDS_IN_30_DAYS = 60 * 60 * 24 * 30;
@@ -3,7 +3,7 @@ import { readFileSync } from "node:fs";
3
3
 
4
4
  const jsenvServerRootDirectoryUrl = import.meta.resolve("../../../");
5
5
 
6
- export const jsenvServiceInternalClientFiles = () => {
6
+ export const serverPluginInternalClientFiles = () => {
7
7
  return {
8
8
  name: "jsenv:internal_client_files",
9
9
 
@@ -1,7 +1,7 @@
1
1
  import { urlToFileSystemPath } from "@jsenv/urls";
2
2
  import { createRequire } from "node:module";
3
3
 
4
- export const jsenvServiceOpenFile = () => {
4
+ export const serverPluginOpenFile = () => {
5
5
  return {
6
6
  name: "jsenv:open_file",
7
7
  routes: [
@@ -1,6 +1,6 @@
1
1
  import { URL_META } from "@jsenv/url-meta";
2
2
 
3
- export const jsenvServiceRequestAliases = (resourceAliases) => {
3
+ export const serverPluginRequestAliases = (resourceAliases) => {
4
4
  const aliases = {};
5
5
  Object.keys(resourceAliases).forEach((key) => {
6
6
  aliases[asFileUrl(key)] = asFileUrl(resourceAliases[key]);
@@ -2,7 +2,7 @@ import { pickContentEncoding } from "@jsenv/server/src/content_negotiation/pick_
2
2
  import { pickContentLanguage } from "@jsenv/server/src/content_negotiation/pick_content_language.js";
3
3
  import { pickContentType } from "@jsenv/server/src/content_negotiation/pick_content_type.js";
4
4
 
5
- export const jsenvServiceResponseAcceptanceCheck = () => {
5
+ export const serverPluginResponseAcceptanceCheck = () => {
6
6
  return {
7
7
  name: "jsenv:response_acceptance_check",
8
8
  inspectResponse: (request, { response, warn }) => {
@@ -14,8 +14,8 @@
14
14
  }
15
15
  }
16
16
  </script>
17
- <script src="/@jsenv/server/src/services/route_inspector/client/code_block.js?inline"></script>
18
- <script src="/@jsenv/server/src/services/autoreload_on_server_restart/client/autoreload_on_server_restart.js?inline"></script>
17
+ <script src="/@jsenv/server/src/plugins/route_inspector/client/code_block.js?inline"></script>
18
+ <script src="/@jsenv/server/src/plugins/autoreload_on_server_restart/client/autoreload_on_server_restart.js?inline"></script>
19
19
  </head>
20
20
  <body>
21
21
  <style>
@@ -3,7 +3,7 @@ import { readFileSync } from "node:fs";
3
3
  const routeInspectorHtmlFileUrl = import.meta
4
4
  .resolve("./client/route_inspector.html");
5
5
 
6
- export const jsenvServiceRouteInspector = () => {
6
+ export const serverPluginRouteInspector = () => {
7
7
  return {
8
8
  name: "jsenv:route_inspector",
9
9
  routes: [
@@ -0,0 +1,21 @@
1
+ import { createPluginsController } from "../plugins_controller.js";
2
+
3
+ export const createServerPluginsController = async (serverPlugins) => {
4
+ const jsenvServerPluginsController = await createPluginsController({
5
+ plugins: serverPlugins,
6
+ pluginDescription: {
7
+ name: "server plugin",
8
+ properties: {
9
+ serverListening: { type: "hook" },
10
+ redirectRequest: { type: "hook" },
11
+ augmentRouteFetchSecondArg: { type: "hook" },
12
+ handleError: { type: "hook" },
13
+ onResponsePush: { type: "hook" },
14
+ injectResponseProperties: { type: "hook" },
15
+ serverStopped: { type: "hook" },
16
+ routes: {}, // routes are handled separately (used to populate the router)
17
+ },
18
+ },
19
+ });
20
+ return jsenvServerPluginsController;
21
+ };