defuss-express 0.1.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.
- package/LICENSE +21 -0
- package/README.md +244 -0
- package/dist/index.cjs +610 -0
- package/dist/index.d.cts +289 -0
- package/dist/index.d.mts +289 -0
- package/dist/index.mjs +596 -0
- package/package.json +62 -0
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import ultimateExpress from 'ultimate-express';
|
|
2
|
+
import { Socket } from 'node:net';
|
|
3
|
+
import { TelemetrySink } from 'defuss-open-telemetry';
|
|
4
|
+
|
|
5
|
+
declare const express: typeof ultimateExpress;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Duck-typed Express-compatible application.
|
|
9
|
+
*
|
|
10
|
+
* Accepts any object whose `.listen()` method binds to a port.
|
|
11
|
+
* HTTP verb helpers (`get`, `post`, …) are optional so that
|
|
12
|
+
* lightweight frameworks like `ultimate-express` satisfy the contract.
|
|
13
|
+
*/
|
|
14
|
+
type ExpressLike = {
|
|
15
|
+
listen: (...args: any[]) => any;
|
|
16
|
+
disable?: (name: string) => void;
|
|
17
|
+
use?: (...args: any[]) => any;
|
|
18
|
+
get?: (...args: any[]) => any;
|
|
19
|
+
post?: (...args: any[]) => any;
|
|
20
|
+
put?: (...args: any[]) => any;
|
|
21
|
+
patch?: (...args: any[]) => any;
|
|
22
|
+
delete?: (...args: any[]) => any;
|
|
23
|
+
options?: (...args: any[]) => any;
|
|
24
|
+
head?: (...args: any[]) => any;
|
|
25
|
+
all?: (...args: any[]) => any;
|
|
26
|
+
} & Record<string, unknown>;
|
|
27
|
+
/** Minimal structured logger accepted by defuss-express. */
|
|
28
|
+
type LoggerLike = {
|
|
29
|
+
debug: (...args: unknown[]) => void;
|
|
30
|
+
info: (...args: unknown[]) => void;
|
|
31
|
+
warn: (...args: unknown[]) => void;
|
|
32
|
+
error: (...args: unknown[]) => void;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Result of parsing the HTTP/1.x request-line and headers from a
|
|
36
|
+
* raw TCP buffer. Produced by {@link parseRequestHead} and fed to
|
|
37
|
+
* load-balancer functions for request-aware routing decisions.
|
|
38
|
+
*/
|
|
39
|
+
type ParsedRequest = {
|
|
40
|
+
/** HTTP method (`GET`, `POST`, …) – `undefined` if the buffer was unparseable. */
|
|
41
|
+
method?: string;
|
|
42
|
+
/** Request path including query string. */
|
|
43
|
+
path?: string;
|
|
44
|
+
/** HTTP version string, e.g. `"1.1"`. */
|
|
45
|
+
httpVersion?: string;
|
|
46
|
+
/** Value of the `Host` header, if present. */
|
|
47
|
+
host?: string;
|
|
48
|
+
/** Lowercase header name → trimmed value map. */
|
|
49
|
+
headers: Record<string, string>;
|
|
50
|
+
/** Remote IP of the connecting client. */
|
|
51
|
+
remoteAddress?: string;
|
|
52
|
+
/** Remote port of the connecting client. */
|
|
53
|
+
remotePort?: number;
|
|
54
|
+
/** `"http1"` when the request-line was successfully parsed, otherwise `"unknown"`. */
|
|
55
|
+
protocol: "http1" | "unknown";
|
|
56
|
+
/** The raw header bytes decoded as `latin1`. */
|
|
57
|
+
rawHead: string;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Point-in-time resource snapshot reported by a worker process via
|
|
61
|
+
* IPC heartbeat messages. Used by the resource-aware load balancer
|
|
62
|
+
* to steer traffic away from saturated workers.
|
|
63
|
+
*/
|
|
64
|
+
type WorkerRuntimeStats = {
|
|
65
|
+
/** OS-level process id. */
|
|
66
|
+
pid: number;
|
|
67
|
+
/** Sampled CPU usage as a percentage of one core (0–100+). */
|
|
68
|
+
cpuPercent: number;
|
|
69
|
+
/** Resident set size in bytes. */
|
|
70
|
+
rssBytes: number;
|
|
71
|
+
/** V8 heap used in bytes. */
|
|
72
|
+
heapUsedBytes: number;
|
|
73
|
+
/** V8 total heap in bytes. */
|
|
74
|
+
heapTotalBytes: number;
|
|
75
|
+
/** V8 external memory in bytes. */
|
|
76
|
+
externalBytes: number;
|
|
77
|
+
/** V8 ArrayBuffer memory in bytes. */
|
|
78
|
+
arrayBuffersBytes: number;
|
|
79
|
+
/** Process uptime in milliseconds. */
|
|
80
|
+
uptimeMs: number;
|
|
81
|
+
/** Number of currently tracked application-level connections. */
|
|
82
|
+
activeConnections: number;
|
|
83
|
+
/** `Date.now()` when the snapshot was taken. */
|
|
84
|
+
sampledAt: number;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Describes a single worker backend as seen by the primary-process
|
|
88
|
+
* load balancer.
|
|
89
|
+
*/
|
|
90
|
+
type BackendCandidate = {
|
|
91
|
+
/** Stable identifier, e.g. `"worker-0"`. */
|
|
92
|
+
id: string;
|
|
93
|
+
/** Zero-based index in the worker pool. */
|
|
94
|
+
workerIndex: number;
|
|
95
|
+
/** Hostname/IP the worker listens on. */
|
|
96
|
+
host: string;
|
|
97
|
+
/** TCP port the worker listens on. */
|
|
98
|
+
port: number;
|
|
99
|
+
/** OS pid of the worker process, set once the worker reports in. */
|
|
100
|
+
pid?: number;
|
|
101
|
+
/** `true` when the worker is considered able to serve traffic. */
|
|
102
|
+
healthy: boolean;
|
|
103
|
+
/** `true` after the worker has sent its `"defuss:worker-ready"` message. */
|
|
104
|
+
ready: boolean;
|
|
105
|
+
/** `Date.now()` of the last heartbeat received from this worker. */
|
|
106
|
+
lastHeartbeatAt?: number;
|
|
107
|
+
/** Number of TCP proxy connections the primary is currently forwarding to this worker. */
|
|
108
|
+
activeProxyConnections: number;
|
|
109
|
+
/** Most recent resource snapshot from the worker. */
|
|
110
|
+
stats?: WorkerRuntimeStats;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Context object passed to a {@link LoadBalancerFunction} on every
|
|
114
|
+
* inbound connection so it can make a routing decision.
|
|
115
|
+
*/
|
|
116
|
+
type LoadBalancerContext = {
|
|
117
|
+
/** Healthy, ready backend candidates to choose from. */
|
|
118
|
+
candidates: BackendCandidate[];
|
|
119
|
+
/** Parsed HTTP request head (method, path, host, headers). */
|
|
120
|
+
request: ParsedRequest;
|
|
121
|
+
/** Raw client TCP socket (for advanced inspection). */
|
|
122
|
+
socket: Socket;
|
|
123
|
+
/** Monotonically incrementing counter for round-robin tracking. */
|
|
124
|
+
previousIndex: number;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Signature for a pluggable load-balancer strategy.
|
|
128
|
+
*
|
|
129
|
+
* Receives a {@link LoadBalancerContext} and must return (or resolve)
|
|
130
|
+
* the {@link BackendCandidate} that should receive the connection.
|
|
131
|
+
*/
|
|
132
|
+
type LoadBalancerFunction = (context: LoadBalancerContext) => BackendCandidate | Promise<BackendCandidate>;
|
|
133
|
+
/**
|
|
134
|
+
* User-facing configuration object passed to {@link startServer} or
|
|
135
|
+
* {@link setServerConfig}. Every field is optional; omitted values
|
|
136
|
+
* fall back to sensible defaults (see {@link ResolvedServerConfig}).
|
|
137
|
+
*/
|
|
138
|
+
type ServerConfigInput = {
|
|
139
|
+
/** Bind address for the public-facing load balancer (default `"0.0.0.0"`). */
|
|
140
|
+
host?: string;
|
|
141
|
+
/** Public-facing TCP port (default `3000`). */
|
|
142
|
+
port?: number;
|
|
143
|
+
/** Bind address for per-worker listeners (default `"127.0.0.1"`). */
|
|
144
|
+
workerHost?: string;
|
|
145
|
+
/** First port in the worker port range (default `3001`). */
|
|
146
|
+
baseWorkerPort?: number;
|
|
147
|
+
/** Number of worker processes, or `"auto"` for one per available core. */
|
|
148
|
+
workers?: number | "auto";
|
|
149
|
+
/** Max time (ms) the load balancer waits for a request head before blind-forwarding (default `10`). */
|
|
150
|
+
requestInspectionTimeoutMs?: number;
|
|
151
|
+
/** Max bytes buffered while sniffing the request head (default `16384`). */
|
|
152
|
+
maxHeaderBytes?: number;
|
|
153
|
+
/** Enable TCP half-open on proxy sockets (default `true`). */
|
|
154
|
+
allowHalfOpen?: boolean;
|
|
155
|
+
/** Interval (ms) at which workers send heartbeat stats (default `60000`). */
|
|
156
|
+
workerHeartbeatIntervalMs?: number;
|
|
157
|
+
/** A worker is considered stale after this many ms without a heartbeat (default `150000`). */
|
|
158
|
+
workerHeartbeatStaleAfterMs?: number;
|
|
159
|
+
/** Max time (ms) to wait for graceful shutdown before force-killing (default `10000`). */
|
|
160
|
+
gracefulShutdownTimeoutMs?: number;
|
|
161
|
+
/** Re-fork workers that exit unexpectedly (default `true`). */
|
|
162
|
+
respawnWorkers?: boolean;
|
|
163
|
+
/** Custom load-balancer function. */
|
|
164
|
+
loadBalancer?: LoadBalancerFunction;
|
|
165
|
+
/** Structured logger override (defaults to `console`). */
|
|
166
|
+
logger?: Partial<LoggerLike>;
|
|
167
|
+
/** Extra environment variables forwarded to forked workers. */
|
|
168
|
+
workerEnv?: Record<string, string>;
|
|
169
|
+
/** Install `SIGINT`/`SIGTERM` handlers automatically (default `true`). */
|
|
170
|
+
installSignalHandlers?: boolean;
|
|
171
|
+
/**
|
|
172
|
+
* Optional telemetry sink for emitting counters, histograms, and
|
|
173
|
+
* gauges. Defaults to a silent no-op when omitted.
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```ts
|
|
177
|
+
* import { createOpenTelemetrySink, OtelMeterAdapter } from "defuss-open-telemetry";
|
|
178
|
+
* import { metrics } from "@opentelemetry/api";
|
|
179
|
+
*
|
|
180
|
+
* setServerConfig({
|
|
181
|
+
* telemetry: createOpenTelemetrySink({
|
|
182
|
+
* meter: new OtelMeterAdapter(metrics.getMeter("my-app")),
|
|
183
|
+
* prefix: "defuss.express.",
|
|
184
|
+
* }),
|
|
185
|
+
* });
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
telemetry?: TelemetrySink;
|
|
189
|
+
};
|
|
190
|
+
/**
|
|
191
|
+
* Fully resolved server configuration with all defaults applied.
|
|
192
|
+
* Produced by {@link resolveServerConfig}.
|
|
193
|
+
*/
|
|
194
|
+
type ResolvedServerConfig = {
|
|
195
|
+
host: string;
|
|
196
|
+
port: number;
|
|
197
|
+
workerHost: string;
|
|
198
|
+
baseWorkerPort: number;
|
|
199
|
+
workers: number;
|
|
200
|
+
requestInspectionTimeoutMs: number;
|
|
201
|
+
maxHeaderBytes: number;
|
|
202
|
+
allowHalfOpen: boolean;
|
|
203
|
+
workerHeartbeatIntervalMs: number;
|
|
204
|
+
workerHeartbeatStaleAfterMs: number;
|
|
205
|
+
gracefulShutdownTimeoutMs: number;
|
|
206
|
+
respawnWorkers: boolean;
|
|
207
|
+
loadBalancer?: LoadBalancerFunction;
|
|
208
|
+
logger: LoggerLike;
|
|
209
|
+
workerEnv: Record<string, string>;
|
|
210
|
+
installSignalHandlers: boolean;
|
|
211
|
+
/** Active telemetry sink (defaults to {@link noopTelemetrySink}). */
|
|
212
|
+
telemetry: TelemetrySink;
|
|
213
|
+
};
|
|
214
|
+
/** Value returned by {@link startServer} describing the running process. */
|
|
215
|
+
type StartServerResult = {
|
|
216
|
+
/** Whether this process is the `"primary"` load balancer or a `"worker"`. */
|
|
217
|
+
mode: "primary" | "worker";
|
|
218
|
+
/** OS-level process id. */
|
|
219
|
+
pid: number;
|
|
220
|
+
/** Host the process is listening on. */
|
|
221
|
+
host: string;
|
|
222
|
+
/** Port the process is listening on. */
|
|
223
|
+
port: number;
|
|
224
|
+
/** Worker index (only set in worker mode). */
|
|
225
|
+
workerIndex?: number;
|
|
226
|
+
/** Worker port (only set in worker mode). */
|
|
227
|
+
workerPort?: number;
|
|
228
|
+
/** Total number of workers (only set in primary mode). */
|
|
229
|
+
workers?: number;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Apply partial configuration and store it as the current config.
|
|
234
|
+
* Returns the fully resolved result.
|
|
235
|
+
*/
|
|
236
|
+
declare const setServerConfig: (next: ServerConfigInput) => ResolvedServerConfig;
|
|
237
|
+
/** Return the current resolved server configuration. */
|
|
238
|
+
declare const getServerConfig: () => ResolvedServerConfig;
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Cycle through candidates sequentially. `previousIndex` is
|
|
242
|
+
* incremented per connection so each worker gets an equal share.
|
|
243
|
+
*/
|
|
244
|
+
declare const roundRobinLoadBalancer: LoadBalancerFunction;
|
|
245
|
+
/**
|
|
246
|
+
* Pick the candidate with the fewest active proxy connections.
|
|
247
|
+
* Useful under heterogeneous request durations.
|
|
248
|
+
*/
|
|
249
|
+
declare const leastConnectionsLoadBalancer: LoadBalancerFunction;
|
|
250
|
+
/**
|
|
251
|
+
* Composite scoring balancer that accounts for active connections,
|
|
252
|
+
* CPU pressure, and heap utilisation. Formula:
|
|
253
|
+
*
|
|
254
|
+
* ```
|
|
255
|
+
* score = connections × 10 + cpuPercent × 2 + (heapUsed / heapTotal) × 100
|
|
256
|
+
* ```
|
|
257
|
+
*
|
|
258
|
+
* The candidate with the **lowest** score wins.
|
|
259
|
+
*/
|
|
260
|
+
declare const resourceAwareLoadBalancer: LoadBalancerFunction;
|
|
261
|
+
/** Default balancer — currently {@link roundRobinLoadBalancer}. */
|
|
262
|
+
declare const defaultLoadBalancer: LoadBalancerFunction;
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Start the defuss-express server.
|
|
266
|
+
*
|
|
267
|
+
* In the **primary** process this forks worker children, waits for at
|
|
268
|
+
* least one to become ready, and opens a TCP load-balancer on the
|
|
269
|
+
* configured public port.
|
|
270
|
+
*
|
|
271
|
+
* In a **worker** process this binds the Express-like app to its
|
|
272
|
+
* assigned worker port and begins IPC heartbeat reporting.
|
|
273
|
+
*
|
|
274
|
+
* @param app - An Express-compatible application instance.
|
|
275
|
+
* @param nextConfig - Optional partial config (merged with defaults).
|
|
276
|
+
* @returns Metadata about the started process.
|
|
277
|
+
*/
|
|
278
|
+
declare const startServer: (app: ExpressLike, nextConfig?: ServerConfigInput) => Promise<StartServerResult>;
|
|
279
|
+
/**
|
|
280
|
+
* Stop the defuss-express server gracefully.
|
|
281
|
+
*
|
|
282
|
+
* In the primary process this closes the listening socket, sends
|
|
283
|
+
* `SIGTERM` to all workers, and waits for shutdown. In a worker it
|
|
284
|
+
* destroys active connections and closes the app server.
|
|
285
|
+
*/
|
|
286
|
+
declare const stopServer: () => Promise<void>;
|
|
287
|
+
|
|
288
|
+
export { express as default, defaultLoadBalancer, express, express as expressDefault, getServerConfig, leastConnectionsLoadBalancer, resourceAwareLoadBalancer, roundRobinLoadBalancer, setServerConfig, startServer, stopServer };
|
|
289
|
+
export type { BackendCandidate, ExpressLike, LoadBalancerContext, LoadBalancerFunction, ParsedRequest, ResolvedServerConfig, ServerConfigInput, StartServerResult, WorkerRuntimeStats };
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import ultimateExpress from 'ultimate-express';
|
|
2
|
+
import { Socket } from 'node:net';
|
|
3
|
+
import { TelemetrySink } from 'defuss-open-telemetry';
|
|
4
|
+
|
|
5
|
+
declare const express: typeof ultimateExpress;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Duck-typed Express-compatible application.
|
|
9
|
+
*
|
|
10
|
+
* Accepts any object whose `.listen()` method binds to a port.
|
|
11
|
+
* HTTP verb helpers (`get`, `post`, …) are optional so that
|
|
12
|
+
* lightweight frameworks like `ultimate-express` satisfy the contract.
|
|
13
|
+
*/
|
|
14
|
+
type ExpressLike = {
|
|
15
|
+
listen: (...args: any[]) => any;
|
|
16
|
+
disable?: (name: string) => void;
|
|
17
|
+
use?: (...args: any[]) => any;
|
|
18
|
+
get?: (...args: any[]) => any;
|
|
19
|
+
post?: (...args: any[]) => any;
|
|
20
|
+
put?: (...args: any[]) => any;
|
|
21
|
+
patch?: (...args: any[]) => any;
|
|
22
|
+
delete?: (...args: any[]) => any;
|
|
23
|
+
options?: (...args: any[]) => any;
|
|
24
|
+
head?: (...args: any[]) => any;
|
|
25
|
+
all?: (...args: any[]) => any;
|
|
26
|
+
} & Record<string, unknown>;
|
|
27
|
+
/** Minimal structured logger accepted by defuss-express. */
|
|
28
|
+
type LoggerLike = {
|
|
29
|
+
debug: (...args: unknown[]) => void;
|
|
30
|
+
info: (...args: unknown[]) => void;
|
|
31
|
+
warn: (...args: unknown[]) => void;
|
|
32
|
+
error: (...args: unknown[]) => void;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Result of parsing the HTTP/1.x request-line and headers from a
|
|
36
|
+
* raw TCP buffer. Produced by {@link parseRequestHead} and fed to
|
|
37
|
+
* load-balancer functions for request-aware routing decisions.
|
|
38
|
+
*/
|
|
39
|
+
type ParsedRequest = {
|
|
40
|
+
/** HTTP method (`GET`, `POST`, …) – `undefined` if the buffer was unparseable. */
|
|
41
|
+
method?: string;
|
|
42
|
+
/** Request path including query string. */
|
|
43
|
+
path?: string;
|
|
44
|
+
/** HTTP version string, e.g. `"1.1"`. */
|
|
45
|
+
httpVersion?: string;
|
|
46
|
+
/** Value of the `Host` header, if present. */
|
|
47
|
+
host?: string;
|
|
48
|
+
/** Lowercase header name → trimmed value map. */
|
|
49
|
+
headers: Record<string, string>;
|
|
50
|
+
/** Remote IP of the connecting client. */
|
|
51
|
+
remoteAddress?: string;
|
|
52
|
+
/** Remote port of the connecting client. */
|
|
53
|
+
remotePort?: number;
|
|
54
|
+
/** `"http1"` when the request-line was successfully parsed, otherwise `"unknown"`. */
|
|
55
|
+
protocol: "http1" | "unknown";
|
|
56
|
+
/** The raw header bytes decoded as `latin1`. */
|
|
57
|
+
rawHead: string;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Point-in-time resource snapshot reported by a worker process via
|
|
61
|
+
* IPC heartbeat messages. Used by the resource-aware load balancer
|
|
62
|
+
* to steer traffic away from saturated workers.
|
|
63
|
+
*/
|
|
64
|
+
type WorkerRuntimeStats = {
|
|
65
|
+
/** OS-level process id. */
|
|
66
|
+
pid: number;
|
|
67
|
+
/** Sampled CPU usage as a percentage of one core (0–100+). */
|
|
68
|
+
cpuPercent: number;
|
|
69
|
+
/** Resident set size in bytes. */
|
|
70
|
+
rssBytes: number;
|
|
71
|
+
/** V8 heap used in bytes. */
|
|
72
|
+
heapUsedBytes: number;
|
|
73
|
+
/** V8 total heap in bytes. */
|
|
74
|
+
heapTotalBytes: number;
|
|
75
|
+
/** V8 external memory in bytes. */
|
|
76
|
+
externalBytes: number;
|
|
77
|
+
/** V8 ArrayBuffer memory in bytes. */
|
|
78
|
+
arrayBuffersBytes: number;
|
|
79
|
+
/** Process uptime in milliseconds. */
|
|
80
|
+
uptimeMs: number;
|
|
81
|
+
/** Number of currently tracked application-level connections. */
|
|
82
|
+
activeConnections: number;
|
|
83
|
+
/** `Date.now()` when the snapshot was taken. */
|
|
84
|
+
sampledAt: number;
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Describes a single worker backend as seen by the primary-process
|
|
88
|
+
* load balancer.
|
|
89
|
+
*/
|
|
90
|
+
type BackendCandidate = {
|
|
91
|
+
/** Stable identifier, e.g. `"worker-0"`. */
|
|
92
|
+
id: string;
|
|
93
|
+
/** Zero-based index in the worker pool. */
|
|
94
|
+
workerIndex: number;
|
|
95
|
+
/** Hostname/IP the worker listens on. */
|
|
96
|
+
host: string;
|
|
97
|
+
/** TCP port the worker listens on. */
|
|
98
|
+
port: number;
|
|
99
|
+
/** OS pid of the worker process, set once the worker reports in. */
|
|
100
|
+
pid?: number;
|
|
101
|
+
/** `true` when the worker is considered able to serve traffic. */
|
|
102
|
+
healthy: boolean;
|
|
103
|
+
/** `true` after the worker has sent its `"defuss:worker-ready"` message. */
|
|
104
|
+
ready: boolean;
|
|
105
|
+
/** `Date.now()` of the last heartbeat received from this worker. */
|
|
106
|
+
lastHeartbeatAt?: number;
|
|
107
|
+
/** Number of TCP proxy connections the primary is currently forwarding to this worker. */
|
|
108
|
+
activeProxyConnections: number;
|
|
109
|
+
/** Most recent resource snapshot from the worker. */
|
|
110
|
+
stats?: WorkerRuntimeStats;
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Context object passed to a {@link LoadBalancerFunction} on every
|
|
114
|
+
* inbound connection so it can make a routing decision.
|
|
115
|
+
*/
|
|
116
|
+
type LoadBalancerContext = {
|
|
117
|
+
/** Healthy, ready backend candidates to choose from. */
|
|
118
|
+
candidates: BackendCandidate[];
|
|
119
|
+
/** Parsed HTTP request head (method, path, host, headers). */
|
|
120
|
+
request: ParsedRequest;
|
|
121
|
+
/** Raw client TCP socket (for advanced inspection). */
|
|
122
|
+
socket: Socket;
|
|
123
|
+
/** Monotonically incrementing counter for round-robin tracking. */
|
|
124
|
+
previousIndex: number;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Signature for a pluggable load-balancer strategy.
|
|
128
|
+
*
|
|
129
|
+
* Receives a {@link LoadBalancerContext} and must return (or resolve)
|
|
130
|
+
* the {@link BackendCandidate} that should receive the connection.
|
|
131
|
+
*/
|
|
132
|
+
type LoadBalancerFunction = (context: LoadBalancerContext) => BackendCandidate | Promise<BackendCandidate>;
|
|
133
|
+
/**
|
|
134
|
+
* User-facing configuration object passed to {@link startServer} or
|
|
135
|
+
* {@link setServerConfig}. Every field is optional; omitted values
|
|
136
|
+
* fall back to sensible defaults (see {@link ResolvedServerConfig}).
|
|
137
|
+
*/
|
|
138
|
+
type ServerConfigInput = {
|
|
139
|
+
/** Bind address for the public-facing load balancer (default `"0.0.0.0"`). */
|
|
140
|
+
host?: string;
|
|
141
|
+
/** Public-facing TCP port (default `3000`). */
|
|
142
|
+
port?: number;
|
|
143
|
+
/** Bind address for per-worker listeners (default `"127.0.0.1"`). */
|
|
144
|
+
workerHost?: string;
|
|
145
|
+
/** First port in the worker port range (default `3001`). */
|
|
146
|
+
baseWorkerPort?: number;
|
|
147
|
+
/** Number of worker processes, or `"auto"` for one per available core. */
|
|
148
|
+
workers?: number | "auto";
|
|
149
|
+
/** Max time (ms) the load balancer waits for a request head before blind-forwarding (default `10`). */
|
|
150
|
+
requestInspectionTimeoutMs?: number;
|
|
151
|
+
/** Max bytes buffered while sniffing the request head (default `16384`). */
|
|
152
|
+
maxHeaderBytes?: number;
|
|
153
|
+
/** Enable TCP half-open on proxy sockets (default `true`). */
|
|
154
|
+
allowHalfOpen?: boolean;
|
|
155
|
+
/** Interval (ms) at which workers send heartbeat stats (default `60000`). */
|
|
156
|
+
workerHeartbeatIntervalMs?: number;
|
|
157
|
+
/** A worker is considered stale after this many ms without a heartbeat (default `150000`). */
|
|
158
|
+
workerHeartbeatStaleAfterMs?: number;
|
|
159
|
+
/** Max time (ms) to wait for graceful shutdown before force-killing (default `10000`). */
|
|
160
|
+
gracefulShutdownTimeoutMs?: number;
|
|
161
|
+
/** Re-fork workers that exit unexpectedly (default `true`). */
|
|
162
|
+
respawnWorkers?: boolean;
|
|
163
|
+
/** Custom load-balancer function. */
|
|
164
|
+
loadBalancer?: LoadBalancerFunction;
|
|
165
|
+
/** Structured logger override (defaults to `console`). */
|
|
166
|
+
logger?: Partial<LoggerLike>;
|
|
167
|
+
/** Extra environment variables forwarded to forked workers. */
|
|
168
|
+
workerEnv?: Record<string, string>;
|
|
169
|
+
/** Install `SIGINT`/`SIGTERM` handlers automatically (default `true`). */
|
|
170
|
+
installSignalHandlers?: boolean;
|
|
171
|
+
/**
|
|
172
|
+
* Optional telemetry sink for emitting counters, histograms, and
|
|
173
|
+
* gauges. Defaults to a silent no-op when omitted.
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* ```ts
|
|
177
|
+
* import { createOpenTelemetrySink, OtelMeterAdapter } from "defuss-open-telemetry";
|
|
178
|
+
* import { metrics } from "@opentelemetry/api";
|
|
179
|
+
*
|
|
180
|
+
* setServerConfig({
|
|
181
|
+
* telemetry: createOpenTelemetrySink({
|
|
182
|
+
* meter: new OtelMeterAdapter(metrics.getMeter("my-app")),
|
|
183
|
+
* prefix: "defuss.express.",
|
|
184
|
+
* }),
|
|
185
|
+
* });
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
telemetry?: TelemetrySink;
|
|
189
|
+
};
|
|
190
|
+
/**
|
|
191
|
+
* Fully resolved server configuration with all defaults applied.
|
|
192
|
+
* Produced by {@link resolveServerConfig}.
|
|
193
|
+
*/
|
|
194
|
+
type ResolvedServerConfig = {
|
|
195
|
+
host: string;
|
|
196
|
+
port: number;
|
|
197
|
+
workerHost: string;
|
|
198
|
+
baseWorkerPort: number;
|
|
199
|
+
workers: number;
|
|
200
|
+
requestInspectionTimeoutMs: number;
|
|
201
|
+
maxHeaderBytes: number;
|
|
202
|
+
allowHalfOpen: boolean;
|
|
203
|
+
workerHeartbeatIntervalMs: number;
|
|
204
|
+
workerHeartbeatStaleAfterMs: number;
|
|
205
|
+
gracefulShutdownTimeoutMs: number;
|
|
206
|
+
respawnWorkers: boolean;
|
|
207
|
+
loadBalancer?: LoadBalancerFunction;
|
|
208
|
+
logger: LoggerLike;
|
|
209
|
+
workerEnv: Record<string, string>;
|
|
210
|
+
installSignalHandlers: boolean;
|
|
211
|
+
/** Active telemetry sink (defaults to {@link noopTelemetrySink}). */
|
|
212
|
+
telemetry: TelemetrySink;
|
|
213
|
+
};
|
|
214
|
+
/** Value returned by {@link startServer} describing the running process. */
|
|
215
|
+
type StartServerResult = {
|
|
216
|
+
/** Whether this process is the `"primary"` load balancer or a `"worker"`. */
|
|
217
|
+
mode: "primary" | "worker";
|
|
218
|
+
/** OS-level process id. */
|
|
219
|
+
pid: number;
|
|
220
|
+
/** Host the process is listening on. */
|
|
221
|
+
host: string;
|
|
222
|
+
/** Port the process is listening on. */
|
|
223
|
+
port: number;
|
|
224
|
+
/** Worker index (only set in worker mode). */
|
|
225
|
+
workerIndex?: number;
|
|
226
|
+
/** Worker port (only set in worker mode). */
|
|
227
|
+
workerPort?: number;
|
|
228
|
+
/** Total number of workers (only set in primary mode). */
|
|
229
|
+
workers?: number;
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Apply partial configuration and store it as the current config.
|
|
234
|
+
* Returns the fully resolved result.
|
|
235
|
+
*/
|
|
236
|
+
declare const setServerConfig: (next: ServerConfigInput) => ResolvedServerConfig;
|
|
237
|
+
/** Return the current resolved server configuration. */
|
|
238
|
+
declare const getServerConfig: () => ResolvedServerConfig;
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Cycle through candidates sequentially. `previousIndex` is
|
|
242
|
+
* incremented per connection so each worker gets an equal share.
|
|
243
|
+
*/
|
|
244
|
+
declare const roundRobinLoadBalancer: LoadBalancerFunction;
|
|
245
|
+
/**
|
|
246
|
+
* Pick the candidate with the fewest active proxy connections.
|
|
247
|
+
* Useful under heterogeneous request durations.
|
|
248
|
+
*/
|
|
249
|
+
declare const leastConnectionsLoadBalancer: LoadBalancerFunction;
|
|
250
|
+
/**
|
|
251
|
+
* Composite scoring balancer that accounts for active connections,
|
|
252
|
+
* CPU pressure, and heap utilisation. Formula:
|
|
253
|
+
*
|
|
254
|
+
* ```
|
|
255
|
+
* score = connections × 10 + cpuPercent × 2 + (heapUsed / heapTotal) × 100
|
|
256
|
+
* ```
|
|
257
|
+
*
|
|
258
|
+
* The candidate with the **lowest** score wins.
|
|
259
|
+
*/
|
|
260
|
+
declare const resourceAwareLoadBalancer: LoadBalancerFunction;
|
|
261
|
+
/** Default balancer — currently {@link roundRobinLoadBalancer}. */
|
|
262
|
+
declare const defaultLoadBalancer: LoadBalancerFunction;
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Start the defuss-express server.
|
|
266
|
+
*
|
|
267
|
+
* In the **primary** process this forks worker children, waits for at
|
|
268
|
+
* least one to become ready, and opens a TCP load-balancer on the
|
|
269
|
+
* configured public port.
|
|
270
|
+
*
|
|
271
|
+
* In a **worker** process this binds the Express-like app to its
|
|
272
|
+
* assigned worker port and begins IPC heartbeat reporting.
|
|
273
|
+
*
|
|
274
|
+
* @param app - An Express-compatible application instance.
|
|
275
|
+
* @param nextConfig - Optional partial config (merged with defaults).
|
|
276
|
+
* @returns Metadata about the started process.
|
|
277
|
+
*/
|
|
278
|
+
declare const startServer: (app: ExpressLike, nextConfig?: ServerConfigInput) => Promise<StartServerResult>;
|
|
279
|
+
/**
|
|
280
|
+
* Stop the defuss-express server gracefully.
|
|
281
|
+
*
|
|
282
|
+
* In the primary process this closes the listening socket, sends
|
|
283
|
+
* `SIGTERM` to all workers, and waits for shutdown. In a worker it
|
|
284
|
+
* destroys active connections and closes the app server.
|
|
285
|
+
*/
|
|
286
|
+
declare const stopServer: () => Promise<void>;
|
|
287
|
+
|
|
288
|
+
export { express as default, defaultLoadBalancer, express, express as expressDefault, getServerConfig, leastConnectionsLoadBalancer, resourceAwareLoadBalancer, roundRobinLoadBalancer, setServerConfig, startServer, stopServer };
|
|
289
|
+
export type { BackendCandidate, ExpressLike, LoadBalancerContext, LoadBalancerFunction, ParsedRequest, ResolvedServerConfig, ServerConfigInput, StartServerResult, WorkerRuntimeStats };
|