@sourceregistry/node-webserver 1.3.0 → 1.4.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.
@@ -0,0 +1,16 @@
1
+ import { Middleware } from '../../types';
2
+ export interface Options {
3
+ /**
4
+ * Header name used for the request ID
5
+ * @default "x-request-id"
6
+ */
7
+ headerName?: string;
8
+ /**
9
+ * Custom request ID generator
10
+ * @default crypto.randomUUID
11
+ */
12
+ generate?: () => string;
13
+ }
14
+ export declare function assign(options?: Options): Middleware<string, {
15
+ requestId: string;
16
+ }>;
@@ -0,0 +1,39 @@
1
+ import { Middleware } from '../../types';
2
+ export interface Options {
3
+ /**
4
+ * Content Security Policy header value
5
+ * @default "default-src 'self'; base-uri 'self'; frame-ancestors 'none'; object-src 'none'"
6
+ */
7
+ contentSecurityPolicy?: string | false;
8
+ /**
9
+ * X-Frame-Options header value
10
+ * @default "DENY"
11
+ */
12
+ frameOptions?: "DENY" | "SAMEORIGIN" | false;
13
+ /**
14
+ * Referrer-Policy header value
15
+ * @default "no-referrer"
16
+ */
17
+ referrerPolicy?: string | false;
18
+ /**
19
+ * Permissions-Policy header value
20
+ * @default "geolocation=(), microphone=(), camera=()"
21
+ */
22
+ permissionsPolicy?: string | false;
23
+ /**
24
+ * Cross-Origin-Opener-Policy header value
25
+ * @default "same-origin"
26
+ */
27
+ crossOriginOpenerPolicy?: string | false;
28
+ /**
29
+ * Cross-Origin-Resource-Policy header value
30
+ * @default "same-origin"
31
+ */
32
+ crossOriginResourcePolicy?: string | false;
33
+ /**
34
+ * Strict-Transport-Security header value
35
+ * @default false
36
+ */
37
+ strictTransportSecurity?: string | false;
38
+ }
39
+ export declare function headers(options?: Options): Middleware;
@@ -0,0 +1,22 @@
1
+ import { Middleware } from '../../types';
2
+ export interface Options {
3
+ /**
4
+ * Deadline in milliseconds
5
+ */
6
+ ms: number;
7
+ /**
8
+ * Status code to return on timeout
9
+ * @default 504
10
+ */
11
+ status?: number;
12
+ /**
13
+ * Response body to return on timeout
14
+ * @default "Gateway Timeout"
15
+ */
16
+ body?: BodyInit | null;
17
+ /**
18
+ * Optional callback invoked when the deadline is exceeded
19
+ */
20
+ onTimeout?: () => void;
21
+ }
22
+ export declare function deadline(options: Options): Middleware;
@@ -98,7 +98,6 @@ export declare class Router<Locals extends App.Locals = App.Locals> {
98
98
  private sortWsRoutes;
99
99
  private formatActionResult;
100
100
  private handleActionError;
101
- static New(): Router;
102
101
  }
103
102
  export declare const Action: {
104
103
  readonly success: (code?: number, data?: Record<string, any>) => Response;
@@ -2,7 +2,7 @@ import { ServerOptions } from 'http';
2
2
  import { ServerOptions as HttpsServerOptions } from 'https';
3
3
  import { RequestEvent, Router } from './';
4
4
  import { ListenOptions } from 'net';
5
- type HostMatcher = string | RegExp | ((host: string) => boolean);
5
+ type ValueMatcher = string | RegExp | ((value: string) => boolean);
6
6
  type InferServerLocals<TServerConfig extends ServerConfig> = Extract<TServerConfig['locals'], (event: RequestEvent) => any> extends (event: RequestEvent) => infer TLocals ? TLocals extends App.Locals ? TLocals : App.Locals : App.Locals;
7
7
  export type SecurityConfig = {
8
8
  /**
@@ -13,17 +13,36 @@ export type SecurityConfig = {
13
13
  /**
14
14
  * Restrict trusted Host values when trustHostHeader is enabled.
15
15
  */
16
- allowedHosts?: HostMatcher | HostMatcher[];
16
+ allowedHosts?: ValueMatcher | ValueMatcher[];
17
+ /**
18
+ * Trust forwarded proxy headers only when the direct peer matches one of these values.
19
+ */
20
+ trustedProxies?: ValueMatcher | ValueMatcher[];
17
21
  /**
18
22
  * Restrict accepted WebSocket Origin values.
19
23
  * When omitted, Origin is not enforced by default.
20
24
  */
21
- allowedWebSocketOrigins?: HostMatcher | HostMatcher[];
25
+ allowedWebSocketOrigins?: ValueMatcher | ValueMatcher[];
22
26
  /**
23
27
  * Maximum accepted request body size based on Content-Length.
24
28
  * Requests above the limit are rejected before the body is read.
25
29
  */
26
30
  maxRequestBodySize?: number;
31
+ /**
32
+ * Maximum time allowed to receive the complete request headers.
33
+ * @default 30000
34
+ */
35
+ headersTimeoutMs?: number;
36
+ /**
37
+ * Maximum time allowed for the full request lifecycle.
38
+ * @default 60000
39
+ */
40
+ requestTimeoutMs?: number;
41
+ /**
42
+ * How long to keep idle keep-alive connections open.
43
+ * @default 5000
44
+ */
45
+ keepAliveTimeoutMs?: number;
27
46
  /**
28
47
  * Maximum accepted WebSocket message size in bytes.
29
48
  * Passed to ws as maxPayload.
@@ -69,13 +88,22 @@ export declare class WebServer<TServerConfig extends ServerConfig = ServerConfig
69
88
  private toRequest;
70
89
  private wrapRequestBody;
71
90
  private toURL;
91
+ private resolveProtocol;
72
92
  private resolveAuthority;
93
+ private getClientAddress;
73
94
  private normalizeTrustedHost;
74
95
  private matchesValue;
96
+ private configureServerTimeouts;
97
+ private isDevelopment;
98
+ private getTrustedForwardedHeader;
99
+ private isTrustedProxy;
100
+ private normalizeAddress;
101
+ private parseForwardedHeader;
75
102
  private toHeaders;
76
103
  private isRequestBodyAllowed;
77
104
  private handleError;
78
105
  private sendWebResponse;
106
+ private isPrematureCloseError;
79
107
  private shouldOmitResponseBody;
80
108
  private isAllowedWebSocketOrigin;
81
109
  private createEventFetch;
@@ -0,0 +1,75 @@
1
+ import {
2
+ CORS,
3
+ RequestId,
4
+ RateLimiter,
5
+ Security,
6
+ Timeout,
7
+ WebServer,
8
+ json,
9
+ text
10
+ } from "../src";
11
+
12
+ const app = new WebServer({
13
+ type: "http",
14
+ options: {},
15
+ locals: () => ({
16
+ startedAt: Date.now()
17
+ }),
18
+ security: {
19
+ trustedProxies: ["127.0.0.1"],
20
+ trustHostHeader: true,
21
+ allowedHosts: ["app.example.com"],
22
+ headersTimeoutMs: 30_000,
23
+ requestTimeoutMs: 60_000,
24
+ keepAliveTimeoutMs: 5_000,
25
+ maxRequestBodySize: 1024 * 1024,
26
+ allowedWebSocketOrigins: "https://app.example.com"
27
+ }
28
+ });
29
+
30
+ app.pre(async (event) => {
31
+ if (event.url.pathname.startsWith("/private")) {
32
+ const auth = event.request.headers.get("authorization");
33
+ if (!auth) {
34
+ return new Response("Unauthorized", {status: 401});
35
+ }
36
+ }
37
+ });
38
+
39
+ app.useMiddleware(
40
+ RequestId.assign(),
41
+ Security.headers({
42
+ strictTransportSecurity: "max-age=31536000; includeSubDomains"
43
+ }),
44
+ Timeout.deadline({
45
+ ms: 15_000,
46
+ status: 503,
47
+ body: "Request timed out"
48
+ }),
49
+ CORS.policy({
50
+ origin: "https://app.example.com",
51
+ credentials: true
52
+ }),
53
+ RateLimiter.fixedWindowLimit({
54
+ max: 60,
55
+ windowMs: 60_000
56
+ })
57
+ );
58
+
59
+ app.GET("/", () => text("hello"));
60
+
61
+ app.GET("/users/[id]", (event) => json({
62
+ id: event.params.id,
63
+ requestId: event.locals.requestId,
64
+ startedAt: event.locals.startedAt
65
+ }));
66
+
67
+ app.post(async (_event, response) => {
68
+ const nextResponse = new Response(response.body, response);
69
+ nextResponse.headers.set("x-server", "node-webserver");
70
+ return nextResponse;
71
+ });
72
+
73
+ app.listen(3000, () => {
74
+ console.log("server listening on port 3000");
75
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sourceregistry/node-webserver",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "TypeScript web server for Node.js with web-standard Request and Response APIs",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs.js",
@@ -68,16 +68,16 @@
68
68
  "@semantic-release/git": "^10.0.1",
69
69
  "@semantic-release/npm": "^13.1.3",
70
70
  "@semantic-release/release-notes-generator": "^14.1.0",
71
- "@types/node": "^24.3.0",
71
+ "@types/node": "^25.5.0",
72
72
  "@vitest/coverage-v8": "^4.0.16",
73
73
  "@vitest/ui": "^4.0.16",
74
74
  "tsx": "^4.21.0",
75
75
  "typedoc": "^0.28.15",
76
76
  "typescript": "^5.9.3",
77
- "vite": "^7.3.0",
77
+ "vite": "^8.0.1",
78
78
  "vite-plugin-dts": "^4.5.4",
79
79
  "@types/ws": "^8.18.1",
80
- "vitest": "^4.0.16"
80
+ "vitest": "^4.1.0"
81
81
  },
82
82
  "release": {
83
83
  "branches": [