srvx 0.6.0 → 0.7.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.
@@ -1,14 +1,10 @@
1
- import { ServerRequest, ServerOptions, Server } from '../types.mjs';
2
- import 'node:http';
3
- import 'node:https';
4
- import 'node:net';
5
- import 'bun';
6
- import '@cloudflare/workers-types';
1
+ import { Server, ServerOptions, ServerRequest } from "./types.mjs";
7
2
 
3
+ //#region src/adapters/service-worker.d.ts
8
4
  declare const FastURL: typeof globalThis.URL;
9
5
  declare const FastResponse: typeof globalThis.Response;
10
6
  type ServiceWorkerHandler = (request: ServerRequest, event: FetchEvent) => Response | Promise<Response>;
11
7
  declare function serve(options: ServerOptions): Server<ServiceWorkerHandler>;
12
8
 
13
- export { FastResponse, FastURL, serve };
14
- export type { ServiceWorkerHandler };
9
+ //#endregion
10
+ export { FastResponse, FastURL, ServiceWorkerHandler, serve };
@@ -0,0 +1,80 @@
1
+ import { wrapFetch } from "./_middleware.mjs";
2
+ import { errorPlugin } from "./_plugins.mjs";
3
+
4
+ //#region src/adapters/service-worker.ts
5
+ const FastURL = URL;
6
+ const FastResponse = Response;
7
+ const isBrowserWindow = typeof window !== "undefined" && typeof navigator !== "undefined";
8
+ const isServiceWorker = typeof self !== "undefined" && "skipWaiting" in self;
9
+ function serve(options) {
10
+ return new ServiceWorkerServer(options);
11
+ }
12
+ var ServiceWorkerServer = class {
13
+ runtime = "service-worker";
14
+ options;
15
+ fetch;
16
+ #fetchListener;
17
+ #listeningPromise;
18
+ constructor(options) {
19
+ this.options = {
20
+ ...options,
21
+ middleware: [...options.middleware || []]
22
+ };
23
+ for (const plugin of options.plugins || []) plugin(this);
24
+ errorPlugin(this);
25
+ const fetchHandler = wrapFetch(this);
26
+ this.fetch = (request, event) => {
27
+ Object.defineProperties(request, { runtime: {
28
+ enumerable: true,
29
+ value: {
30
+ runtime: "service-worker",
31
+ serviceWorker: { event }
32
+ }
33
+ } });
34
+ return Promise.resolve(fetchHandler(request));
35
+ };
36
+ if (!options.manual) this.serve();
37
+ }
38
+ serve() {
39
+ if (isBrowserWindow) {
40
+ if (!navigator.serviceWorker) throw new Error("Service worker is not supported in the current window.");
41
+ const swURL = this.options.serviceWorker?.url;
42
+ if (!swURL) throw new Error("Service worker URL is not provided. Please set the `serviceWorker.url` serve option or manually register.");
43
+ this.#listeningPromise = navigator.serviceWorker.register(swURL, {
44
+ type: "module",
45
+ scope: this.options.serviceWorker?.scope
46
+ }).then((registration) => {
47
+ if (registration.active) location.replace(location.href);
48
+ else registration.addEventListener("updatefound", () => {
49
+ location.replace(location.href);
50
+ });
51
+ });
52
+ } else if (isServiceWorker) {
53
+ this.#fetchListener = async (event) => {
54
+ if (/\/[^/]*\.[a-zA-Z0-9]+$/.test(new URL(event.request.url).pathname)) return;
55
+ const response = await this.fetch(event.request, event);
56
+ if (response.status !== 404) event.respondWith(response);
57
+ };
58
+ addEventListener("fetch", this.#fetchListener);
59
+ self.addEventListener("install", () => {
60
+ self.skipWaiting();
61
+ });
62
+ self.addEventListener("activate", () => {
63
+ self.clients?.claim?.();
64
+ });
65
+ }
66
+ }
67
+ ready() {
68
+ return Promise.resolve(this.#listeningPromise).then(() => this);
69
+ }
70
+ async close() {
71
+ if (this.#fetchListener) removeEventListener("fetch", this.#fetchListener);
72
+ if (isBrowserWindow) {
73
+ const registrations = await navigator.serviceWorker.getRegistrations();
74
+ for (const registration of registrations) if (registration.active) await registration.unregister();
75
+ } else if (isServiceWorker) await self.registration.unregister();
76
+ }
77
+ };
78
+
79
+ //#endregion
80
+ export { FastResponse, FastURL, serve };
package/dist/types.d.mts CHANGED
@@ -1,235 +1,228 @@
1
- import * as NodeHttp from 'node:http';
2
- import * as NodeHttps from 'node:https';
3
- import * as NodeNet from 'node:net';
4
- import * as Bun from 'bun';
5
- import * as CF from '@cloudflare/workers-types';
1
+ import * as NodeHttp$1 from "node:http";
2
+ import * as NodeHttps from "node:https";
3
+ import * as NodeHttp2 from "node:http2";
4
+ import * as NodeNet from "node:net";
5
+ import * as Bun from "bun";
6
+ import * as CF from "@cloudflare/workers-types";
6
7
 
8
+ //#region src/types.d.ts
7
9
  type MaybePromise<T> = T | Promise<T>;
8
10
  /**
9
- * Faster URL constructor with lazy access to pathname and search params (For Node, Deno, and Bun).
10
- */
11
- declare const FastURL: typeof globalThis.URL;
12
- /**
13
- * Faster Response constructor optimized for Node.js (same as Response for other runtimes).
14
- */
15
- declare const FastResponse: typeof globalThis.Response;
16
- /**
17
- * Create a new server instance.
18
- */
19
- declare function serve(options: ServerOptions): Server;
11
+ * Faster URL constructor with lazy access to pathname and search params (For Node, Deno, and Bun).
12
+ */
13
+
20
14
  /**
21
- * Web fetch compatible request handler
22
- */
15
+ * Web fetch compatible request handler
16
+ */
23
17
  type ServerHandler = (request: ServerRequest) => MaybePromise<Response>;
18
+ type ServerMiddleware = (request: ServerRequest, next: () => Response | Promise<Response>) => Response | Promise<Response>;
19
+ type ServerPlugin = (server: Server) => void;
24
20
  /**
25
- * Server options
26
- */
21
+ * Server options
22
+ */
27
23
  interface ServerOptions {
28
- /**
29
- * The fetch handler handles incoming requests.
30
- */
31
- fetch: ServerHandler;
32
- /**
33
- * Handle websocket upgrades.
34
- */
35
- upgrade?: ServerHandler;
36
- /**
37
- * Handle lifecycle errors.
38
- *
39
- * @note This handler will set built-in Bun and Deno error handler.
40
- */
41
- error?: ErrorHandler;
42
- /**
43
- * Server plugins.
44
- */
45
- plugins?: (ServerPlugin | ServerPluginInstance)[];
46
- /**
47
- * If set to `true`, server will not start listening automatically.
48
- */
49
- manual?: boolean;
50
- /**
51
- * The port server should be listening to.
52
- *
53
- * Default is read from `PORT` environment variable or will be `3000`.
54
- *
55
- * **Tip:** You can set the port to `0` to use a random port.
56
- */
57
- port?: string | number;
58
- /**
59
- * The hostname (IP or resolvable host) server listener should bound to.
60
- *
61
- * When not provided, server with listen to all network interfaces by default.
62
- *
63
- * **Important:** If you are running a server that is not expected to be exposed to the network, use `hostname: "localhost"`.
64
- */
65
- hostname?: string;
66
- /**
67
- * Enabling this option allows multiple processes to bind to the same port, which is useful for load balancing.
68
- *
69
- * **Note:** Despite Node.js built-in behavior that has `exclusive` flag (opposite of `reusePort`) enabled by default, srvx uses non-exclusive mode for consistency.
70
- */
71
- reusePort?: boolean;
72
- /**
73
- * The protocol to use for the server.
74
- *
75
- * Possible values are `http` and `https`.
76
- *
77
- * If `protocol` is not set, Server will use `http` as the default protocol or `https` if both `tls.cert` and `tls.key` options are provided.
78
- */
79
- protocol?: "http" | "https";
80
- /**
81
- * If set to `true`, server will not print the listening address.
82
- */
83
- silent?: boolean;
84
- /**
85
- * TLS server options.
86
- */
87
- tls?: {
88
- /**
89
- * File path or inlined TLS certificate in PEM format (required).
90
- */
91
- cert?: string;
92
- /**
93
- * File path or inlined TLS private key in PEM format (required).
94
- */
95
- key?: string;
96
- /**
97
- * Passphrase for the private key (optional).
98
- */
99
- passphrase?: string;
100
- };
101
- /**
102
- * Node.js server options.
103
- */
104
- node?: (NodeHttp.ServerOptions | NodeHttps.ServerOptions) & NodeNet.ListenOptions;
105
- /**
106
- * Bun server options
107
- *
108
- * @docs https://bun.sh/docs/api/http
109
- */
110
- bun?: Omit<Bun.ServeOptions | Bun.TLSServeOptions, "fetch">;
111
- /**
112
- * Deno server options
113
- *
114
- * @docs https://docs.deno.com/api/deno/~/Deno.serve
115
- */
116
- deno?: Deno.ServeOptions;
117
- /**
118
- * Service worker options
119
- */
120
- serviceWorker?: {
121
- /**
122
- * The path to the service worker file to be registered.
123
- */
124
- url?: string;
125
- /**
126
- * The scope of the service worker.
127
- *
128
- */
129
- scope?: string;
130
- };
24
+ /**
25
+ * The fetch handler handles incoming requests.
26
+ */
27
+ fetch: ServerHandler;
28
+ /**
29
+ * Handle lifecycle errors.
30
+ *
31
+ * @note This handler will set built-in Bun and Deno error handler.
32
+ */
33
+ error?: ErrorHandler;
34
+ /**
35
+ * Server middleware handlers to run before the main fetch handler.
36
+ */
37
+ middleware?: ServerMiddleware[];
38
+ /**
39
+ * Server plugins.
40
+ */
41
+ plugins?: ServerPlugin[];
42
+ /**
43
+ * If set to `true`, server will not start listening automatically.
44
+ */
45
+ manual?: boolean;
46
+ /**
47
+ * The port server should be listening to.
48
+ *
49
+ * Default is read from `PORT` environment variable or will be `3000`.
50
+ *
51
+ * **Tip:** You can set the port to `0` to use a random port.
52
+ */
53
+ port?: string | number;
54
+ /**
55
+ * The hostname (IP or resolvable host) server listener should bound to.
56
+ *
57
+ * When not provided, server with listen to all network interfaces by default.
58
+ *
59
+ * **Important:** If you are running a server that is not expected to be exposed to the network, use `hostname: "localhost"`.
60
+ */
61
+ hostname?: string;
62
+ /**
63
+ * Enabling this option allows multiple processes to bind to the same port, which is useful for load balancing.
64
+ *
65
+ * **Note:** Despite Node.js built-in behavior that has `exclusive` flag (opposite of `reusePort`) enabled by default, srvx uses non-exclusive mode for consistency.
66
+ */
67
+ reusePort?: boolean;
68
+ /**
69
+ * The protocol to use for the server.
70
+ *
71
+ * Possible values are `http` and `https`.
72
+ *
73
+ * If `protocol` is not set, Server will use `http` as the default protocol or `https` if both `tls.cert` and `tls.key` options are provided.
74
+ */
75
+ protocol?: "http" | "https";
76
+ /**
77
+ * If set to `true`, server will not print the listening address.
78
+ */
79
+ silent?: boolean;
80
+ /**
81
+ * TLS server options.
82
+ */
83
+ tls?: {
84
+ /**
85
+ * File path or inlined TLS certificate in PEM format (required).
86
+ */
87
+ cert?: string;
88
+ /**
89
+ * File path or inlined TLS private key in PEM format (required).
90
+ */
91
+ key?: string;
92
+ /**
93
+ * Passphrase for the private key (optional).
94
+ */
95
+ passphrase?: string;
96
+ };
97
+ /**
98
+ * Node.js server options.
99
+ */
100
+ node?: (NodeHttp$1.ServerOptions | NodeHttps.ServerOptions | NodeHttp2.ServerOptions) & NodeNet.ListenOptions & {
101
+ http2?: boolean;
102
+ };
103
+ /**
104
+ * Bun server options
105
+ *
106
+ * @docs https://bun.sh/docs/api/http
107
+ */
108
+ bun?: Omit<Bun.ServeOptions | Bun.TLSServeOptions, "fetch">;
109
+ /**
110
+ * Deno server options
111
+ *
112
+ * @docs https://docs.deno.com/api/deno/~/Deno.serve
113
+ */
114
+ deno?: Deno.ServeOptions;
115
+ /**
116
+ * Service worker options
117
+ */
118
+ serviceWorker?: {
119
+ /**
120
+ * The path to the service worker file to be registered.
121
+ */
122
+ url?: string;
123
+ /**
124
+ * The scope of the service worker.
125
+ *
126
+ */
127
+ scope?: string;
128
+ };
131
129
  }
132
130
  interface Server<Handler = ServerHandler> {
133
- /**
134
- * Current runtime name
135
- */
136
- readonly runtime: "node" | "deno" | "bun" | "cloudflare" | "service-worker" | "generic";
137
- /**
138
- * Server options
139
- */
140
- readonly options: ServerOptions;
141
- /**
142
- * Server URL address.
143
- */
144
- readonly url?: string;
145
- /**
146
- * Node.js context.
147
- */
148
- readonly node?: {
149
- server?: NodeHttp.Server;
150
- handler: (nodeReq: NodeHttp.IncomingMessage, nodeRes: NodeHttp.ServerResponse) => void | Promise<void>;
151
- };
152
- /**
153
- * Bun context.
154
- */
155
- readonly bun?: {
156
- server?: Bun.Server;
157
- };
158
- /**
159
- * Deno context.
160
- */
161
- readonly deno?: {
162
- server?: Deno.HttpServer;
163
- };
164
- /**
165
- * Server fetch handler
166
- */
167
- readonly fetch: Handler;
168
- /**
169
- * Returns a promise that resolves when the server is ready.
170
- */
171
- ready(): Promise<Server<Handler>>;
172
- /**
173
- * Stop listening to prevent new connections from being accepted.
174
- *
175
- * By default, it does not cancel in-flight requests or websockets. That means it may take some time before all network activity stops.
176
- *
177
- * @param closeActiveConnections Immediately terminate in-flight requests, websockets, and stop accepting new connections.
178
- * @default false
179
- */
180
- close(closeActiveConnections?: boolean): Promise<void>;
181
- }
182
- type ServerPlugin = (server: Server) => ServerPluginInstance;
183
- type ServerMiddleware = (request: ServerRequest, next: () => Response | Promise<Response>) => Response | Promise<Response>;
184
- interface ServerPluginInstance {
185
- name?: string;
186
- fetch?: ServerMiddleware;
131
+ /**
132
+ * Current runtime name
133
+ */
134
+ readonly runtime: "node" | "deno" | "bun" | "cloudflare" | "service-worker" | "generic";
135
+ /**
136
+ * Server options
137
+ */
138
+ readonly options: ServerOptions & {
139
+ middleware: ServerMiddleware[];
140
+ };
141
+ /**
142
+ * Server URL address.
143
+ */
144
+ readonly url?: string;
145
+ /**
146
+ * Node.js context.
147
+ */
148
+ readonly node?: {
149
+ server?: NodeHttp$1.Server | NodeHttp2.Http2Server;
150
+ handler: (req: NodeServerRequest, res: NodeServerResponse) => void | Promise<void>;
151
+ };
152
+ /**
153
+ * Bun context.
154
+ */
155
+ readonly bun?: {
156
+ server?: Bun.Server;
157
+ };
158
+ /**
159
+ * Deno context.
160
+ */
161
+ readonly deno?: {
162
+ server?: Deno.HttpServer;
163
+ };
164
+ /**
165
+ * Server fetch handler
166
+ */
167
+ readonly fetch: Handler;
168
+ /**
169
+ * Returns a promise that resolves when the server is ready.
170
+ */
171
+ ready(): Promise<Server<Handler>>;
172
+ /**
173
+ * Stop listening to prevent new connections from being accepted.
174
+ *
175
+ * By default, it does not cancel in-flight requests or websockets. That means it may take some time before all network activity stops.
176
+ *
177
+ * @param closeActiveConnections Immediately terminate in-flight requests, websockets, and stop accepting new connections.
178
+ * @default false
179
+ */
180
+ close(closeActiveConnections?: boolean): Promise<void>;
187
181
  }
188
182
  interface ServerRuntimeContext {
189
- name: "node" | "deno" | "bun" | "cloudflare" | (string & {});
190
- /**
191
- * Underlying Node.js server request info.
192
- */
193
- node?: {
194
- req: NodeHttp.IncomingMessage;
195
- res?: NodeHttp.ServerResponse;
196
- };
197
- /**
198
- * Underlying Deno server request info.
199
- */
200
- deno?: {
201
- info: Deno.ServeHandlerInfo<Deno.NetAddr>;
202
- };
203
- /**
204
- * Underlying Bun server request context.
205
- */
206
- bun?: {
207
- server: Bun.Server;
208
- };
209
- /**
210
- * Underlying Cloudflare request context.
211
- */
212
- cloudflare?: {
213
- env: unknown;
214
- context: CF.ExecutionContext;
215
- };
183
+ name: "node" | "deno" | "bun" | "cloudflare" | (string & {});
184
+ /**
185
+ * Underlying Node.js server request info.
186
+ */
187
+ node?: {
188
+ req: NodeServerRequest;
189
+ res?: NodeServerResponse;
190
+ };
191
+ /**
192
+ * Underlying Deno server request info.
193
+ */
194
+ deno?: {
195
+ info: Deno.ServeHandlerInfo<Deno.NetAddr>;
196
+ };
197
+ /**
198
+ * Underlying Bun server request context.
199
+ */
200
+ bun?: {
201
+ server: Bun.Server;
202
+ };
203
+ /**
204
+ * Underlying Cloudflare request context.
205
+ */
206
+ cloudflare?: {
207
+ env: unknown;
208
+ context: CF.ExecutionContext;
209
+ };
216
210
  }
217
211
  interface ServerRequest extends Request {
218
- /**
219
- * Runtime specific request context.
220
- */
221
- runtime?: ServerRuntimeContext;
222
- /**
223
- * IP address of the client.
224
- */
225
- ip?: string | undefined;
212
+ /**
213
+ * Runtime specific request context.
214
+ */
215
+ runtime?: ServerRuntimeContext;
216
+ /**
217
+ * IP address of the client.
218
+ */
219
+ ip?: string | undefined;
226
220
  }
227
221
  type FetchHandler = (request: Request) => Response | Promise<Response>;
228
222
  type ErrorHandler = (error: unknown) => Response | Promise<Response>;
229
223
  type BunFetchHandler = (request: Request, server?: Bun.Server) => Response | Promise<Response>;
230
224
  type DenoFetchHandler = (request: Request, info?: Deno.ServeHandlerInfo<Deno.NetAddr>) => Response | Promise<Response>;
231
- type NodeHttpHandler = (nodeReq: NodeHttp.IncomingMessage, nodeRes: NodeHttp.ServerResponse) => void | Promise<void>;
232
- type CloudflareFetchHandler = CF.ExportedHandlerFetchHandler;
233
-
234
- export { FastResponse, FastURL, serve };
235
- export type { BunFetchHandler, CloudflareFetchHandler, DenoFetchHandler, ErrorHandler, FetchHandler, NodeHttpHandler, Server, ServerHandler, ServerMiddleware, ServerOptions, ServerPlugin, ServerPluginInstance, ServerRequest, ServerRuntimeContext };
225
+ type NodeServerRequest = NodeHttp$1.IncomingMessage | NodeHttp2.Http2ServerRequest;
226
+ type NodeServerResponse = NodeHttp$1.ServerResponse | NodeHttp2.Http2ServerResponse;
227
+ type NodeHttpHandler = (req: NodeServerRequest, res: NodeServerResponse) => void | Promise<void>; //#endregion
228
+ export { BunFetchHandler, DenoFetchHandler, FetchHandler, NodeHttpHandler, NodeServerRequest, NodeServerResponse, Server, ServerOptions, ServerRequest };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "srvx",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Universal Server API based on web platform standards. Works seamlessly with Deno, Bun and Node.js.",
5
5
  "homepage": "https://srvx.h3.dev",
6
6
  "repository": "h3js/srvx",
@@ -9,20 +9,20 @@
9
9
  "type": "module",
10
10
  "exports": {
11
11
  "./types": "./dist/types.d.mts",
12
- "./deno": "./dist/adapters/deno.mjs",
13
- "./bun": "./dist/adapters/bun.mjs",
14
- "./node": "./dist/adapters/node.mjs",
15
- "./cloudflare": "./dist/adapters/cloudflare.mjs",
16
- "./generic": "./dist/adapters/generic.mjs",
17
- "./service-worker": "./dist/adapters/service-worker.mjs",
12
+ "./deno": "./dist/deno.mjs",
13
+ "./bun": "./dist/bun.mjs",
14
+ "./node": "./dist/node.mjs",
15
+ "./cloudflare": "./dist/cloudflare.mjs",
16
+ "./generic": "./dist/generic.mjs",
17
+ "./service-worker": "./dist/service-worker.mjs",
18
18
  ".": {
19
19
  "types": "./dist/types.d.mts",
20
- "deno": "./dist/adapters/deno.mjs",
21
- "bun": "./dist/adapters/bun.mjs",
22
- "workerd": "./dist/adapters/cloudflare.mjs",
23
- "browser": "./dist/adapters/service-worker.mjs",
24
- "node": "./dist/adapters/node.mjs",
25
- "default": "./dist/adapters/generic.mjs"
20
+ "deno": "./dist/deno.mjs",
21
+ "bun": "./dist/bun.mjs",
22
+ "workerd": "./dist/cloudflare.mjs",
23
+ "browser": "./dist/service-worker.mjs",
24
+ "node": "./dist/node.mjs",
25
+ "default": "./dist/generic.mjs"
26
26
  }
27
27
  },
28
28
  "types": "./dist/types.d.mts",
@@ -34,7 +34,7 @@
34
34
  "bench:url:bun": "bun run ./test/url.bench.ts",
35
35
  "bench:url:deno": "deno run -A ./test/url.bench.ts",
36
36
  "bench:url:node": "pnpm node-ts --expose-gc --allow-natives-syntax ./test/url.bench.ts",
37
- "build": "unbuild",
37
+ "build": "obuild",
38
38
  "dev": "vitest dev",
39
39
  "lint": "eslint . && prettier -c .",
40
40
  "lint:fix": "automd && eslint . --fix && prettier -w .",
@@ -57,29 +57,32 @@
57
57
  "cookie-es": "^2.0.0"
58
58
  },
59
59
  "devDependencies": {
60
- "@cloudflare/workers-types": "^4.20250423.0",
60
+ "@cloudflare/workers-types": "^4.20250513.0",
61
61
  "@hono/node-server": "^1.14.1",
62
62
  "@mitata/counters": "^0.0.8",
63
63
  "@mjackson/node-fetch-server": "^0.6.1",
64
- "@types/bun": "^1.2.10",
65
- "@types/deno": "^2.2.0",
66
- "@types/node": "^22.14.1",
67
- "@types/serviceworker": "^0.0.132",
68
- "@vitest/coverage-v8": "^3.1.2",
64
+ "@types/bun": "^1.2.13",
65
+ "@types/deno": "^2.3.0",
66
+ "@types/node": "^22.15.17",
67
+ "@types/node-forge": "^1.3.11",
68
+ "@types/serviceworker": "^0.0.134",
69
+ "@vitest/coverage-v8": "^3.1.3",
69
70
  "automd": "^0.4.0",
70
71
  "changelogen": "^0.6.1",
71
- "eslint": "^9.25.1",
72
+ "eslint": "^9.26.0",
72
73
  "eslint-config-unjs": "^0.4.2",
73
- "execa": "^9.5.2",
74
+ "execa": "^9.5.3",
74
75
  "get-port-please": "^3.1.2",
75
76
  "mitata": "^1.0.34",
77
+ "node-forge": "^1.3.1",
78
+ "obuild": "^0.0.6",
76
79
  "prettier": "^3.5.3",
77
80
  "typescript": "^5.8.3",
78
- "unbuild": "^3.5.0",
79
- "vitest": "^3.1.2"
81
+ "undici": "^7.9.0",
82
+ "vitest": "^3.1.3"
80
83
  },
81
- "packageManager": "pnpm@10.9.0",
84
+ "packageManager": "pnpm@10.11.0",
82
85
  "engines": {
83
- "node": ">=20.11.1"
86
+ "node": ">=20.16.0"
84
87
  }
85
88
  }