@boo-labs/server 0.0.0-stage → 1.0.1
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 +287 -2
- package/index.mjs +23 -0
- package/package.json +71 -4
- package/source/application/Kernel/index.mjs +1137 -0
- package/source/application/Server/index.mjs +1201 -0
- package/source/application/configurations/Http2CleartextServerConfiguration/index.mjs +168 -0
- package/source/application/configurations/Http2SecureServerConfiguration/index.mjs +172 -0
- package/source/application/configurations/HttpServerConfiguration/index.mjs +169 -0
- package/source/application/configurations/HttpsServerConfiguration/index.mjs +172 -0
- package/source/application/configurations/ServerConfiguration/Options/CORSOptions/CORSOptionsValue.mjs +158 -0
- package/source/application/configurations/ServerConfiguration/Options/CORSOptions/index.mjs +87 -0
- package/source/application/configurations/ServerConfiguration/Options/CSRFOptions/CSRFOptionsValue.mjs +178 -0
- package/source/application/configurations/ServerConfiguration/Options/CSRFOptions/index.mjs +80 -0
- package/source/application/configurations/ServerConfiguration/Options/CompressOptions/CompressOptionsValue.mjs +126 -0
- package/source/application/configurations/ServerConfiguration/Options/CompressOptions/index.mjs +81 -0
- package/source/application/configurations/ServerConfiguration/Options/CookieParserOptions/CookieParserOptionsValue.mjs +107 -0
- package/source/application/configurations/ServerConfiguration/Options/CookieParserOptions/index.mjs +82 -0
- package/source/application/configurations/ServerConfiguration/Options/CookieSessionOptions/CookieSessionOptionsValue.mjs +182 -0
- package/source/application/configurations/ServerConfiguration/Options/CookieSessionOptions/index.mjs +86 -0
- package/source/application/configurations/ServerConfiguration/Options/ListenOptions/ListenOptionsValue.mjs +95 -0
- package/source/application/configurations/ServerConfiguration/Options/ListenOptions/index.mjs +47 -0
- package/source/application/configurations/ServerConfiguration/Options/MiddlewareOption.mjs +102 -0
- package/source/application/configurations/ServerConfiguration/Options/Options.mjs +105 -0
- package/source/application/configurations/ServerConfiguration/Options/RateLimitOptions/RateLimitOptionsValue.mjs +164 -0
- package/source/application/configurations/ServerConfiguration/Options/RateLimitOptions/index.mjs +84 -0
- package/source/application/configurations/ServerConfiguration/Options/RequestOptions/RequestOptionsValue.mjs +108 -0
- package/source/application/configurations/ServerConfiguration/Options/RequestOptions/index.mjs +47 -0
- package/source/application/configurations/ServerConfiguration/Options/ResponseOptions/ResponseOptionsValue.mjs +101 -0
- package/source/application/configurations/ServerConfiguration/Options/ResponseOptions/index.mjs +47 -0
- package/source/application/configurations/ServerConfiguration/Options/SecurityHeadersOptions/SecurityHeadersOptionsValue.mjs +272 -0
- package/source/application/configurations/ServerConfiguration/Options/SecurityHeadersOptions/index.mjs +85 -0
- package/source/application/configurations/ServerConfiguration/Options/ServerOption.mjs +50 -0
- package/source/application/configurations/ServerConfiguration/Options/StaticOptions/StaticOptionsValue.mjs +164 -0
- package/source/application/configurations/ServerConfiguration/Options/StaticOptions/index.mjs +89 -0
- package/source/application/configurations/ServerConfiguration/index.mjs +781 -0
- package/source/application/resolvers/TransportResolver/index.mjs +119 -0
- package/source/application/use-cases/CloseServer/index.mjs +85 -0
- package/source/application/use-cases/ListenServer/index.mjs +98 -0
- package/source/core/EventSystem/index.mjs +110 -0
- package/source/core/Exchange/ExchangeContext.mjs +291 -0
- package/source/core/Exchange/Telemetry.mjs +193 -0
- package/source/core/Exchange/index.mjs +604 -0
- package/source/core/ExchangeLayerBuilder/index.mjs +133 -0
- package/source/core/IPCMain/IPCMain.mjs +507 -0
- package/source/core/IPCMain/IPCProtocol.mjs +441 -0
- package/source/core/IPCMain/IPCServerChannel.mjs +331 -0
- package/source/core/IPCMain/WebSocketAdapter.mjs +189 -0
- package/source/core/Request/BodyParser/BodyParser.mjs +197 -0
- package/source/core/Request/BodyParser/JSONParser.mjs +70 -0
- package/source/core/Request/BodyParser/MultipartParser.mjs +612 -0
- package/source/core/Request/BodyParser/URLEncodedParser.mjs +71 -0
- package/source/core/Request/RequestHeaders.mjs +177 -0
- package/source/core/Request/index.mjs +805 -0
- package/source/core/Response/ResponseHeaders.mjs +402 -0
- package/source/core/Response/index.mjs +1370 -0
- package/source/core/middlewares/CORSMiddleware/index.mjs +289 -0
- package/source/core/middlewares/CSRFMiddleware/index.mjs +284 -0
- package/source/core/middlewares/CompressMiddleware/index.mjs +460 -0
- package/source/core/middlewares/CookieParserMiddleware/index.mjs +194 -0
- package/source/core/middlewares/CookieSessionMiddleware/index.mjs +460 -0
- package/source/core/middlewares/RateLimitMiddleware/index.mjs +245 -0
- package/source/core/middlewares/SecurityHeadersMiddleware/index.mjs +151 -0
- package/source/core/middlewares/StaticMiddleware/index.mjs +628 -0
- package/source/core/routing/ExchangeLayer/ExchangeMatcher.mjs +127 -0
- package/source/core/routing/ExchangeLayer/index.mjs +254 -0
- package/source/core/routing/Pipeline/index.mjs +157 -0
- package/source/core/routing/Router/Route.mjs +184 -0
- package/source/core/routing/Router/index.mjs +523 -0
- package/source/core/routing/RouterRegister/OptionsMiddleware.mjs +147 -0
- package/source/core/routing/RouterRegister/index.mjs +150 -0
- package/source/core/routing/RouterSystem/index.mjs +225 -0
- package/source/core/value-objects/Address/index.mjs +281 -0
- package/source/core/value-objects/Endpoint/index.mjs +293 -0
- package/source/index.mjs +62 -0
- package/source/infrastructure/adapters/Http2Adapter/index.mjs +144 -0
- package/source/infrastructure/adapters/Http2CleartextAdapter/index.mjs +147 -0
- package/source/infrastructure/adapters/HttpAdapter/index.mjs +124 -0
- package/source/infrastructure/adapters/HttpsAdapter/index.mjs +124 -0
- package/source/infrastructure/adapters/TransportAdapter/createWebSocketOpenRequest.mjs +843 -0
- package/source/infrastructure/adapters/TransportAdapter/index.mjs +555 -0
- package/source/shared/Identifier/index.mjs +671 -0
- package/source/shared/constants/symbols/brands/index.mjs +45 -0
- package/source/shared/domain/UUIDV7/index.mjs +346 -0
- package/source/shared/errors/BaseError/index.mjs +157 -0
- package/source/shared/errors/IPCError/index.mjs +97 -0
- package/source/shared/errors/MiddlewareError/index.mjs +106 -0
- package/source/shared/errors/PathnamePatternError/index.mjs +103 -0
- package/source/shared/errors/RequestError/index.mjs +116 -0
- package/source/shared/errors/ResponseError/index.mjs +140 -0
- package/source/shared/errors/RouterError/index.mjs +90 -0
- package/source/shared/errors/ServerConfigurationError/index.mjs +551 -0
- package/source/shared/errors/ServerError/index.mjs +127 -0
- package/source/shared/errors/WebSocketError/index.mjs +58 -0
- package/source/shared/outcome/ErrorSchema.mjs +361 -0
- package/source/shared/outcome/Result/index.mjs +239 -0
- package/source/shared/outcome/Validation/index.mjs +208 -0
- package/source/shared/validations/Middleware/index.mjs +160 -0
- package/source/shared/validations/PathnamePattern/index.mjs +256 -0
- package/source/shared/validations/Server/NotFoundHandler/index.mjs +94 -0
- package/source/shared/validations/Server/Router/index.mjs +68 -0
- package/source/shared/validations/Server/ServerConfiguration/index.mjs +68 -0
- package/source/shared/validations/Server/SystemErrorHandler/index.mjs +94 -0
- package/source/shared/validations/ServerConfiguration/CORSOptions/index.mjs +310 -0
- package/source/shared/validations/ServerConfiguration/CSRFOptions/index.mjs +299 -0
- package/source/shared/validations/ServerConfiguration/CompressOptions/index.mjs +204 -0
- package/source/shared/validations/ServerConfiguration/CookieParserOptions/index.mjs +102 -0
- package/source/shared/validations/ServerConfiguration/CookieSessionOptions/index.mjs +332 -0
- package/source/shared/validations/ServerConfiguration/ListenOptions/index.mjs +178 -0
- package/source/shared/validations/ServerConfiguration/RateLimitOptions/index.mjs +229 -0
- package/source/shared/validations/ServerConfiguration/RequestOptions/index.mjs +132 -0
- package/source/shared/validations/ServerConfiguration/ResponseOptions/index.mjs +122 -0
- package/source/shared/validations/ServerConfiguration/SecurityHeadersOptions/index.mjs +137 -0
- package/source/shared/validations/ServerConfiguration/StaticOptions/index.mjs +248 -0
- package/types/index.d.ts +719 -0
|
@@ -0,0 +1,1137 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file Central orchestration component of the HTTP server runtime.
|
|
4
|
+
*
|
|
5
|
+
* @description Central orchestration component of the HTTP server runtime.
|
|
6
|
+
*
|
|
7
|
+
* Kernel coordinates the server lifecycle, request processing pipeline,
|
|
8
|
+
* exchange lifecycle events, and server-level error handling. It delegates
|
|
9
|
+
* transport operations such as listening and closing to their corresponding
|
|
10
|
+
* use cases while retaining control over the HTTP execution flow.
|
|
11
|
+
*
|
|
12
|
+
* The kernel does not expose the underlying HTTP adapter directly. Instead,
|
|
13
|
+
* it composes the adapter-dependent use cases and provides the runtime
|
|
14
|
+
* behavior required by HttpServer.
|
|
15
|
+
*
|
|
16
|
+
* @module Kernel
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
20
|
+
// » IMPORT NATIVE NODEJS MODULES
|
|
21
|
+
|
|
22
|
+
// » IMPORT THIRD PARTY MODULES
|
|
23
|
+
|
|
24
|
+
// » IMPORT LOCAL MODULES
|
|
25
|
+
import RouterSystem from '#routing/RouterSystem';
|
|
26
|
+
import EventSystem from '#EventSystem';
|
|
27
|
+
import IPCMain from '#IPCMain';
|
|
28
|
+
import ListenServer from '#use-cases/ListenServer';
|
|
29
|
+
import CloseServer from '#use-cases/CloseServer';
|
|
30
|
+
import TransportResolver from '#resolvers/TransportResolver';
|
|
31
|
+
import Exchange, { discardResponseState } from '#Exchange';
|
|
32
|
+
import ServerError from '#shared/errors/ServerError';
|
|
33
|
+
import ExchangeLayerBuilder from '#ExchangeLayerBuilder';
|
|
34
|
+
import { composeConfiguration } from '#configurations/ServerConfiguration';
|
|
35
|
+
|
|
36
|
+
// ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
37
|
+
/**
|
|
38
|
+
* Represents a Node.js HTTP/1.1 or HTTPS/1.1 incoming request.
|
|
39
|
+
*
|
|
40
|
+
* @typedef {import('node:http').IncomingMessage} HttpIncomingMessage
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Represents a Node.js HTTP/2 incoming request.
|
|
45
|
+
*
|
|
46
|
+
* @typedef {import('node:http2').Http2ServerRequest} Http2IncomingMessage
|
|
47
|
+
*/
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Represents a Node.js HTTP/1.1 response.
|
|
51
|
+
*
|
|
52
|
+
* @typedef {import('node:http').ServerResponse} HttpServerResponse
|
|
53
|
+
*/
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Represents a Node.js HTTP/2 response.
|
|
57
|
+
*
|
|
58
|
+
* @typedef {import('node:http2').Http2ServerResponse} Http2ServerResponse
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Represents a permitted transport identity.
|
|
63
|
+
*
|
|
64
|
+
* @typedef {import('#adapters/TransportAdapter').TransportIdentity} TransportIdentity
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Represents a transport adapter capable of managing a permitted transport.
|
|
69
|
+
*
|
|
70
|
+
* @typedef {import('#adapters/TransportAdapter').default<TransportIdentity>} TransportAdapter
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Represents a permitted server configuration identity.
|
|
75
|
+
*
|
|
76
|
+
* @typedef {import('#configurations/ServerConfiguration').ServerConfigurationIdentity} ServerConfigurationIdentity
|
|
77
|
+
*/
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Represents a server configuration constrained to a permitted identity.
|
|
81
|
+
*
|
|
82
|
+
* @typedef {import('#configurations/ServerConfiguration').default<ServerConfigurationIdentity>} ServerConfiguration
|
|
83
|
+
*/
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Represents the network options used to start the server.
|
|
87
|
+
*
|
|
88
|
+
* @typedef {import('#ServerOptions/ListenOptions').ListenOptionsData} ListenOptions
|
|
89
|
+
*/
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Represents the options forwarded to the Request constructor.
|
|
93
|
+
*
|
|
94
|
+
* @typedef {import('#ServerOptions/RequestOptions').RequestOptionsData} RequestOptions
|
|
95
|
+
*/
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Represents the options forwarded to the Response constructor.
|
|
99
|
+
*
|
|
100
|
+
* @typedef {import('#ServerOptions/ResponseOptions').ResponseOptionsData} ResponseOptions
|
|
101
|
+
*/
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Represents an HTTP method supported by the routing layer.
|
|
105
|
+
*
|
|
106
|
+
* @typedef {import('#routing/ExchangeLayer').HTTPMethod} HTTPMethod
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Represents an exchange middleware function.
|
|
111
|
+
*
|
|
112
|
+
* @typedef {import('#routing/ExchangeLayer').MiddlewareFunction} MiddlewareFunction
|
|
113
|
+
*/
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Represents a router instance.
|
|
117
|
+
*
|
|
118
|
+
* @typedef {import('#routing/Router').default} Router
|
|
119
|
+
*/
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Represents a complete network endpoint.
|
|
123
|
+
*
|
|
124
|
+
* @typedef {import('#value-objects/Endpoint').default} Endpoint
|
|
125
|
+
*/
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Represents the network address of an HTTP server.
|
|
129
|
+
*
|
|
130
|
+
* @typedef {import('#value-objects/Address').default} Address
|
|
131
|
+
*/
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Represents the processing outcome of an HTTP exchange.
|
|
135
|
+
*
|
|
136
|
+
* @typedef {import('#Exchange').ExchangeOutcome} ExchangeOutcome
|
|
137
|
+
*/
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Provides information about an exchange when processing starts.
|
|
141
|
+
*
|
|
142
|
+
* @typedef {import('#Exchange').ExchangeStartInfo} ExchangeStartInfo
|
|
143
|
+
*/
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Provides information about an exchange when processing fails.
|
|
147
|
+
*
|
|
148
|
+
* @typedef {import('#Exchange').ExchangeErrorInfo} ExchangeErrorInfo
|
|
149
|
+
*/
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Provides information about an exchange when processing completes.
|
|
153
|
+
*
|
|
154
|
+
* @typedef {import('#Exchange').ExchangeEndInfo} ExchangeEndInfo
|
|
155
|
+
*/
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Represents an incoming request supported by the transport abstraction.
|
|
159
|
+
*
|
|
160
|
+
* @typedef {HttpIncomingMessage|Http2IncomingMessage} IncomingRequest
|
|
161
|
+
*/
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Represents a response supported by the transport abstraction.
|
|
165
|
+
*
|
|
166
|
+
* @typedef {HttpServerResponse|Http2ServerResponse} ServerResponse
|
|
167
|
+
*/
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Represents the connection options forwarded to the transport adapter.
|
|
171
|
+
*
|
|
172
|
+
* @typedef {ServerConfiguration['connection']} ServerConnectionOptions
|
|
173
|
+
*/
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Represents the transport identifier used to resolve the transport adapter.
|
|
177
|
+
*
|
|
178
|
+
* @typedef {ServerConfiguration['transport']} ServerTransportOption
|
|
179
|
+
*/
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Callback invoked to resolve a not-found exchange.
|
|
183
|
+
*
|
|
184
|
+
* @typedef {(exchange: Exchange) => void|Promise<void>} NotFoundHandler
|
|
185
|
+
*/
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Callback invoked to resolve a system error exchange.
|
|
189
|
+
*
|
|
190
|
+
* @typedef {(error: Error, exchange: Exchange) => void|Promise<void>} SystemErrorHandler
|
|
191
|
+
*/
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Represents an event name accepted by the kernel event system.
|
|
195
|
+
*
|
|
196
|
+
* @typedef {'listen'|'close'|'error'|'exchange:start'|'exchange:end'|'exchange:error'|symbol} EventName
|
|
197
|
+
*/
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Data emitted with the `listen` event.
|
|
201
|
+
*
|
|
202
|
+
* @typedef {object} ListenEventData
|
|
203
|
+
* @property {Endpoint} endpoint - Endpoint where the server is listening.
|
|
204
|
+
* @property {Address} address - Network address where the server is listening.
|
|
205
|
+
* @property {number} elapsed - Elapsed time in milliseconds.
|
|
206
|
+
*/
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Data emitted with the `close` event.
|
|
210
|
+
*
|
|
211
|
+
* @typedef {object} CloseEventData
|
|
212
|
+
* @property {number} elapsed - Elapsed time in milliseconds.
|
|
213
|
+
*/
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Listener registered for the `listen` event.
|
|
217
|
+
*
|
|
218
|
+
* @typedef {(data: ListenEventData) => void} ListenEventListener
|
|
219
|
+
*/
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Listener registered for the `close` event.
|
|
223
|
+
*
|
|
224
|
+
* @typedef {(data: CloseEventData) => void} CloseEventListener
|
|
225
|
+
*/
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Listener registered for the `error` event.
|
|
229
|
+
*
|
|
230
|
+
* @typedef {(error: Error) => void} ErrorEventListener
|
|
231
|
+
*/
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Listener registered for the `exchange:start` event.
|
|
235
|
+
*
|
|
236
|
+
* @typedef {(data: ExchangeStartInfo) => void} ExchangeStartEventListener
|
|
237
|
+
*/
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Listener registered for the `exchange:end` event.
|
|
241
|
+
*
|
|
242
|
+
* @typedef {(data: ExchangeEndInfo) => void} ExchangeEndEventListener
|
|
243
|
+
*/
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Listener registered for the `exchange:error` event.
|
|
247
|
+
*
|
|
248
|
+
* @typedef {(data: ExchangeErrorInfo) => void} ExchangeErrorEventListener
|
|
249
|
+
*/
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Represents a listener registered for a kernel event.
|
|
253
|
+
*
|
|
254
|
+
* @typedef {(data?: any) => void} EventListener
|
|
255
|
+
*/
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Represents a successful pipeline execution.
|
|
259
|
+
*
|
|
260
|
+
* @typedef {object} PipelineExecutionSuccess
|
|
261
|
+
* @property {true} success - Indicates that the pipeline completed successfully.
|
|
262
|
+
*/
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Represents a failed pipeline execution.
|
|
266
|
+
*
|
|
267
|
+
* @typedef {object} PipelineExecutionFailure
|
|
268
|
+
* @property {false} success - Indicates that the pipeline failed.
|
|
269
|
+
* @property {Error} error - The error raised during pipeline execution.
|
|
270
|
+
*/
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Represents the outcome of pipeline execution.
|
|
274
|
+
*
|
|
275
|
+
* @typedef {PipelineExecutionSuccess|PipelineExecutionFailure} PipelineExecutionResult
|
|
276
|
+
*/
|
|
277
|
+
|
|
278
|
+
// ━━ FUNCTIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
279
|
+
/**
|
|
280
|
+
* Normalizes a thrown value into an `Error` instance.
|
|
281
|
+
*
|
|
282
|
+
* - `Error` instances (including subclasses) are returned as-is.
|
|
283
|
+
* - Any other value is converted to an `Error` whose message is `String(value)`.
|
|
284
|
+
*
|
|
285
|
+
* @private
|
|
286
|
+
* @param {unknown} error - The value thrown or rejected.
|
|
287
|
+
* @returns {Error} The normalized error instance.
|
|
288
|
+
*/
|
|
289
|
+
const normalizeError = error => {
|
|
290
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
294
|
+
/**
|
|
295
|
+
* Coordinates the HTTP server lifecycle and request execution pipeline.
|
|
296
|
+
*
|
|
297
|
+
* @classdesc Kernel acts as the central orchestration component between the
|
|
298
|
+
* public HttpServer API and the application-level HTTP runtime. It manages
|
|
299
|
+
* server lifecycle operations, registers pipeline entries, creates exchange
|
|
300
|
+
* contexts, emits lifecycle events, and handles errors occurring during
|
|
301
|
+
* server or exchange execution.
|
|
302
|
+
*
|
|
303
|
+
* Transport-specific lifecycle operations are delegated to ListenServer
|
|
304
|
+
* and CloseServer. Request delivery is connected directly to the transport
|
|
305
|
+
* adapter and routed into the exchange processing pipeline.
|
|
306
|
+
*/
|
|
307
|
+
class Kernel {
|
|
308
|
+
/**
|
|
309
|
+
* Resolved transport adapter instance.
|
|
310
|
+
*
|
|
311
|
+
* @type {TransportAdapter}
|
|
312
|
+
*/
|
|
313
|
+
#server;
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Network configuration used to start the server.
|
|
317
|
+
*
|
|
318
|
+
* @type {ListenOptions|undefined}
|
|
319
|
+
*/
|
|
320
|
+
#listenOptions;
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Options forwarded to the Request constructor.
|
|
324
|
+
*
|
|
325
|
+
* @type {RequestOptions|undefined}
|
|
326
|
+
*/
|
|
327
|
+
#requestOptions;
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Options forwarded to the Response constructor.
|
|
331
|
+
*
|
|
332
|
+
* @type {ResponseOptions|undefined}
|
|
333
|
+
*/
|
|
334
|
+
#responseOptions;
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Use case responsible for starting the server.
|
|
338
|
+
*
|
|
339
|
+
* @type {ListenServer}
|
|
340
|
+
*/
|
|
341
|
+
#listenServer;
|
|
342
|
+
|
|
343
|
+
/**
|
|
344
|
+
* Use case responsible for stopping the server.
|
|
345
|
+
*
|
|
346
|
+
* @type {CloseServer}
|
|
347
|
+
*/
|
|
348
|
+
#closeServer;
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Routing system responsible for registering routes and executing the pipeline.
|
|
352
|
+
*
|
|
353
|
+
* @type {RouterSystem}
|
|
354
|
+
*/
|
|
355
|
+
#routerSystem = new RouterSystem();
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Event system used to report kernel lifecycle and exchange events.
|
|
359
|
+
*
|
|
360
|
+
* @type {EventSystem}
|
|
361
|
+
*/
|
|
362
|
+
#eventSystem = new EventSystem();
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* IPC subsystem owned by the kernel.
|
|
366
|
+
*
|
|
367
|
+
* @type {IPCMain}
|
|
368
|
+
*/
|
|
369
|
+
#ipc;
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* User-provided handler for not-found exchanges.
|
|
373
|
+
*
|
|
374
|
+
* @type {NotFoundHandler|null}
|
|
375
|
+
*/
|
|
376
|
+
#notFoundHandler = null;
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* User-provided handler for system error exchanges.
|
|
380
|
+
*
|
|
381
|
+
* @type {SystemErrorHandler|null}
|
|
382
|
+
*/
|
|
383
|
+
#systemErrorHandler = null;
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Creates an HTTP kernel.
|
|
387
|
+
*
|
|
388
|
+
* @param {ServerConfiguration} configuration - Configuration options for server startup.
|
|
389
|
+
* @example
|
|
390
|
+
* ```js
|
|
391
|
+
* const configuration = new HttpsServerConfiguration({
|
|
392
|
+
* listen: {
|
|
393
|
+
* host: 'localhost',
|
|
394
|
+
* port: 3443,
|
|
395
|
+
* },
|
|
396
|
+
* connection: {
|
|
397
|
+
* cert: './cert.pem',
|
|
398
|
+
* key: './key.pem',
|
|
399
|
+
* },
|
|
400
|
+
* });
|
|
401
|
+
*
|
|
402
|
+
* const kernel = new Kernel(configuration);
|
|
403
|
+
* ```
|
|
404
|
+
*/
|
|
405
|
+
constructor(configuration) {
|
|
406
|
+
const { serverOptions, middlewareOptions } = composeConfiguration(configuration);
|
|
407
|
+
|
|
408
|
+
this.#server = Kernel.#ensureAdapter(serverOptions.transport, serverOptions.connection);
|
|
409
|
+
this.#listenOptions = serverOptions.listen;
|
|
410
|
+
this.#requestOptions = serverOptions.request;
|
|
411
|
+
this.#responseOptions = serverOptions.response;
|
|
412
|
+
|
|
413
|
+
this.#listenServer = new ListenServer(this.#server);
|
|
414
|
+
this.#closeServer = new CloseServer(this.#server);
|
|
415
|
+
this.#ipc = new IPCMain(this.#server);
|
|
416
|
+
|
|
417
|
+
const layerOptions = ExchangeLayerBuilder.build(middlewareOptions);
|
|
418
|
+
|
|
419
|
+
this.#routerSystem.registerLayerOptionsBatch(layerOptions);
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Handles one incoming HTTP request.
|
|
423
|
+
*
|
|
424
|
+
* @param {IncomingRequest} incomingMessage - Native Node.js incoming HTTP message.
|
|
425
|
+
* @param {ServerResponse} serverResponse - Native Node.js outgoing HTTP message.
|
|
426
|
+
* @returns {void}
|
|
427
|
+
*/
|
|
428
|
+
const onRequestHandler = (incomingMessage, serverResponse) => {
|
|
429
|
+
void this.#handleExchange(incomingMessage, serverResponse);
|
|
430
|
+
};
|
|
431
|
+
|
|
432
|
+
this.#server.onRequest(onRequestHandler);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Returns whether the server is currently listening for requests.
|
|
437
|
+
*
|
|
438
|
+
* @readonly
|
|
439
|
+
* @type {boolean}
|
|
440
|
+
*/
|
|
441
|
+
get listening() {
|
|
442
|
+
return this.#server.listening;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* The IPC subsystem owned by the kernel.
|
|
447
|
+
*
|
|
448
|
+
* @readonly
|
|
449
|
+
* @type {IPCMain}
|
|
450
|
+
*/
|
|
451
|
+
get ipc() {
|
|
452
|
+
return this.#ipc;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* @overload
|
|
457
|
+
* @param {'listen'} event - Event name.
|
|
458
|
+
* @param {ListenEventListener} listener - Function invoked when the server starts listening.
|
|
459
|
+
* @returns {Kernel} The current kernel instance.
|
|
460
|
+
*/
|
|
461
|
+
/**
|
|
462
|
+
* @overload
|
|
463
|
+
* @param {'close'} event - Event name.
|
|
464
|
+
* @param {CloseEventListener} listener - Function invoked when the server closes.
|
|
465
|
+
* @returns {Kernel} The current kernel instance.
|
|
466
|
+
*/
|
|
467
|
+
/**
|
|
468
|
+
* @overload
|
|
469
|
+
* @param {'error'} event - Event name.
|
|
470
|
+
* @param {ErrorEventListener} listener - Function invoked when a server-level error occurs.
|
|
471
|
+
* @returns {Kernel} The current kernel instance.
|
|
472
|
+
*/
|
|
473
|
+
/**
|
|
474
|
+
* @overload
|
|
475
|
+
* @param {'exchange:start'} event - Event name.
|
|
476
|
+
* @param {ExchangeStartEventListener} listener - Function invoked during the exchange starts.
|
|
477
|
+
* @returns {Kernel} The current kernel instance.
|
|
478
|
+
*/
|
|
479
|
+
/**
|
|
480
|
+
* @overload
|
|
481
|
+
* @param {'exchange:end'} event - Event name.
|
|
482
|
+
* @param {ExchangeEndEventListener} listener - Function invoked during the exchange ends.
|
|
483
|
+
* @returns {Kernel} The current kernel instance.
|
|
484
|
+
*/
|
|
485
|
+
/**
|
|
486
|
+
* @overload
|
|
487
|
+
* @param {'exchange:error'} event - Event name.
|
|
488
|
+
* @param {ExchangeErrorEventListener} listener - Function invoked when an exchange fails.
|
|
489
|
+
* @returns {Kernel} The current kernel instance.
|
|
490
|
+
*/
|
|
491
|
+
/**
|
|
492
|
+
* Registers a listener for a kernel event.
|
|
493
|
+
*
|
|
494
|
+
* @param {EventName} event - Event name.
|
|
495
|
+
* @param {EventListener} listener - Function invoked when the event is emitted.
|
|
496
|
+
* @returns {Kernel} The current kernel instance.
|
|
497
|
+
* @example
|
|
498
|
+
* ```js
|
|
499
|
+
* kernel.onListen(({ endpoint, address, elapsed }) => {
|
|
500
|
+
* console.log(`Server listening at ${endpoint.displayUrl} in ${elapsed}ms`);
|
|
501
|
+
* console.log(`Bound to ${address} (${address.family})`);
|
|
502
|
+
* // Bound to [::1]:3000 (IPv6)
|
|
503
|
+
* });
|
|
504
|
+
* ```
|
|
505
|
+
*/
|
|
506
|
+
on(event, listener) {
|
|
507
|
+
this.#eventSystem.on(event, listener);
|
|
508
|
+
return this;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* @overload
|
|
513
|
+
* @param {'listen'} event - Event name.
|
|
514
|
+
* @param {ListenEventListener} listener - Function invoked when the server starts listening.
|
|
515
|
+
* @returns {Kernel} The current kernel instance.
|
|
516
|
+
*/
|
|
517
|
+
/**
|
|
518
|
+
* @overload
|
|
519
|
+
* @param {'close'} event - Event name.
|
|
520
|
+
* @param {CloseEventListener} listener - Function invoked when the server closes.
|
|
521
|
+
* @returns {Kernel} The current kernel instance.
|
|
522
|
+
*/
|
|
523
|
+
/**
|
|
524
|
+
* @overload
|
|
525
|
+
* @param {'error'} event - Event name.
|
|
526
|
+
* @param {ErrorEventListener} listener - Function invoked when a server-level error occurs.
|
|
527
|
+
* @returns {Kernel} The current kernel instance.
|
|
528
|
+
*/
|
|
529
|
+
/**
|
|
530
|
+
* @overload
|
|
531
|
+
* @param {'exchange:start'} event - Event name.
|
|
532
|
+
* @param {ExchangeStartEventListener} listener - Function invoked during the exchange starts.
|
|
533
|
+
* @returns {Kernel} The current kernel instance.
|
|
534
|
+
*/
|
|
535
|
+
/**
|
|
536
|
+
* @overload
|
|
537
|
+
* @param {'exchange:end'} event - Event name.
|
|
538
|
+
* @param {ExchangeEndEventListener} listener - Function invoked during the exchange ends.
|
|
539
|
+
* @returns {Kernel} The current kernel instance.
|
|
540
|
+
*/
|
|
541
|
+
/**
|
|
542
|
+
* @overload
|
|
543
|
+
* @param {'exchange:error'} event - Event name.
|
|
544
|
+
* @param {ExchangeErrorEventListener} listener - Function invoked when an exchange fails.
|
|
545
|
+
* @returns {Kernel} The current kernel instance.
|
|
546
|
+
*/
|
|
547
|
+
/**
|
|
548
|
+
* Registers a listener that is invoked only once for a kernel event.
|
|
549
|
+
*
|
|
550
|
+
* @param {EventName} event - Event name.
|
|
551
|
+
* @param {EventListener} listener - Function invoked when the event is emitted.
|
|
552
|
+
* @returns {Kernel} The current kernel instance.
|
|
553
|
+
* @example
|
|
554
|
+
* ```js
|
|
555
|
+
* kernel.once('listen', ({ endpoint }) => {
|
|
556
|
+
* console.log(`Server started at ${endpoint.displayUrl}`);
|
|
557
|
+
* });
|
|
558
|
+
* ```
|
|
559
|
+
*/
|
|
560
|
+
once(event, listener) {
|
|
561
|
+
this.#eventSystem.once(event, listener);
|
|
562
|
+
return this;
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* @overload
|
|
567
|
+
* @param {'listen'} event - Event name.
|
|
568
|
+
* @param {ListenEventListener} listener - Function invoked when the server starts listening.
|
|
569
|
+
* @returns {Kernel} The current kernel instance.
|
|
570
|
+
*/
|
|
571
|
+
/**
|
|
572
|
+
* @overload
|
|
573
|
+
* @param {'close'} event - Event name.
|
|
574
|
+
* @param {CloseEventListener} listener - Function invoked when the server closes.
|
|
575
|
+
* @returns {Kernel} The current kernel instance.
|
|
576
|
+
*/
|
|
577
|
+
/**
|
|
578
|
+
* @overload
|
|
579
|
+
* @param {'error'} event - Event name.
|
|
580
|
+
* @param {ErrorEventListener} listener - Function invoked when a server-level error occurs.
|
|
581
|
+
* @returns {Kernel} The current kernel instance.
|
|
582
|
+
*/
|
|
583
|
+
/**
|
|
584
|
+
* @overload
|
|
585
|
+
* @param {'exchange:start'} event - Event name.
|
|
586
|
+
* @param {ExchangeStartEventListener} listener - Function invoked during the exchange starts.
|
|
587
|
+
* @returns {Kernel} The current kernel instance.
|
|
588
|
+
*/
|
|
589
|
+
/**
|
|
590
|
+
* @overload
|
|
591
|
+
* @param {'exchange:end'} event - Event name.
|
|
592
|
+
* @param {ExchangeEndEventListener} listener - Function invoked during the exchange ends.
|
|
593
|
+
* @returns {Kernel} The current kernel instance.
|
|
594
|
+
*/
|
|
595
|
+
/**
|
|
596
|
+
* @overload
|
|
597
|
+
* @param {'exchange:error'} event - Event name.
|
|
598
|
+
* @param {ExchangeErrorEventListener} listener - Function invoked when an exchange fails.
|
|
599
|
+
* @returns {Kernel} The current kernel instance.
|
|
600
|
+
*/
|
|
601
|
+
/**
|
|
602
|
+
* Removes a listener from a kernel event.
|
|
603
|
+
*
|
|
604
|
+
* @param {EventName} event - Event name.
|
|
605
|
+
* @param {EventListener} listener - Listener to remove.
|
|
606
|
+
* @returns {Kernel} The current kernel instance.
|
|
607
|
+
* @example
|
|
608
|
+
* ```js
|
|
609
|
+
* const listener = () => {
|
|
610
|
+
* console.log('Server started');
|
|
611
|
+
* };
|
|
612
|
+
*
|
|
613
|
+
* kernel.on('listen', listener);
|
|
614
|
+
* kernel.off('listen', listener);
|
|
615
|
+
* ```
|
|
616
|
+
*/
|
|
617
|
+
off(event, listener) {
|
|
618
|
+
this.#eventSystem.off(event, listener);
|
|
619
|
+
return this;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Registers a listener for the `listen` event.
|
|
624
|
+
*
|
|
625
|
+
* @param {ListenEventListener} listener - Function invoked when the server starts listening.
|
|
626
|
+
* @returns {Kernel} The current kernel instance.
|
|
627
|
+
*
|
|
628
|
+
* @example
|
|
629
|
+
* ```js
|
|
630
|
+
* kernel.onListen(({ endpoint, address, elapsed }) => {
|
|
631
|
+
* console.log(`Server listening at ${endpoint.displayUrl} in ${elapsed}ms`);
|
|
632
|
+
* console.log(`Bound to ${address} (${address.family})`);
|
|
633
|
+
* // Bound to [::1]:3000 (IPv6)
|
|
634
|
+
* });
|
|
635
|
+
* ```
|
|
636
|
+
*/
|
|
637
|
+
onListen(listener) {
|
|
638
|
+
this.#eventSystem.on(Kernel.#EVENTS.LISTEN, listener);
|
|
639
|
+
return this;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
/**
|
|
643
|
+
* Registers a listener for the `close` event.
|
|
644
|
+
*
|
|
645
|
+
* @param {CloseEventListener} listener - Function invoked when the server closes.
|
|
646
|
+
* @returns {Kernel} The current kernel instance.
|
|
647
|
+
*
|
|
648
|
+
* @example
|
|
649
|
+
* ```js
|
|
650
|
+
* kernel.onClose(({ elapsed }) => {
|
|
651
|
+
* console.log(`Server closed in ${elapsed}ms`);
|
|
652
|
+
* });
|
|
653
|
+
* ```
|
|
654
|
+
*/
|
|
655
|
+
onClose(listener) {
|
|
656
|
+
this.#eventSystem.on(Kernel.#EVENTS.CLOSE, listener);
|
|
657
|
+
return this;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Registers a listener for the `error` event.
|
|
662
|
+
*
|
|
663
|
+
* @param {ErrorEventListener} listener - Function invoked when a server-level error occurs.
|
|
664
|
+
* @returns {Kernel} The current kernel instance.
|
|
665
|
+
*
|
|
666
|
+
* @example
|
|
667
|
+
* ```js
|
|
668
|
+
* kernel.onError(error => {
|
|
669
|
+
* console.error('Server error:', error);
|
|
670
|
+
* });
|
|
671
|
+
* ```
|
|
672
|
+
*/
|
|
673
|
+
onError(listener) {
|
|
674
|
+
this.#eventSystem.on(Kernel.#EVENTS.ERROR, listener);
|
|
675
|
+
return this;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Registers a listener for the `exchange:start` event.
|
|
680
|
+
*
|
|
681
|
+
* @param {ExchangeStartEventListener} listener - Function invoked when an exchange starts.
|
|
682
|
+
* @returns {Kernel} The current kernel instance.
|
|
683
|
+
*
|
|
684
|
+
* @example
|
|
685
|
+
* ```js
|
|
686
|
+
* kernel.onExchangeStart(information => {
|
|
687
|
+
* console.log(
|
|
688
|
+
* information.id,
|
|
689
|
+
* information.request.method,
|
|
690
|
+
* information.request.pathname,
|
|
691
|
+
* );
|
|
692
|
+
* });
|
|
693
|
+
* ```
|
|
694
|
+
*/
|
|
695
|
+
onExchangeStart(listener) {
|
|
696
|
+
this.#eventSystem.on(Kernel.#EVENTS.EXCHANGE_START, listener);
|
|
697
|
+
return this;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/**
|
|
701
|
+
* Registers a listener for the `exchange:end` event.
|
|
702
|
+
*
|
|
703
|
+
* @param {ExchangeEndEventListener} listener - Function invoked when exchange processing ends.
|
|
704
|
+
* @returns {Kernel} The current kernel instance.
|
|
705
|
+
*
|
|
706
|
+
* @example
|
|
707
|
+
* ```js
|
|
708
|
+
* kernel.onExchangeEnd(information => {
|
|
709
|
+
* console.log(
|
|
710
|
+
* information.outcome, // 'success' | 'error'
|
|
711
|
+
* information.timing.duration, // number
|
|
712
|
+
* );
|
|
713
|
+
* });
|
|
714
|
+
* ```
|
|
715
|
+
*/
|
|
716
|
+
onExchangeEnd(listener) {
|
|
717
|
+
this.#eventSystem.on(Kernel.#EVENTS.EXCHANGE_END, listener);
|
|
718
|
+
return this;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* Registers a listener for the `exchange:error` event.
|
|
723
|
+
*
|
|
724
|
+
* @param {ExchangeErrorEventListener} listener - Function invoked when an exchange fails.
|
|
725
|
+
* @returns {Kernel} The current kernel instance.
|
|
726
|
+
*
|
|
727
|
+
* @example
|
|
728
|
+
* ```js
|
|
729
|
+
* kernel.onExchangeError(information => {
|
|
730
|
+
* console.error(
|
|
731
|
+
* information.id,
|
|
732
|
+
* information.error,
|
|
733
|
+
* information.request.pathname,
|
|
734
|
+
* );
|
|
735
|
+
* });
|
|
736
|
+
* ```
|
|
737
|
+
*/
|
|
738
|
+
onExchangeError(listener) {
|
|
739
|
+
this.#eventSystem.on(Kernel.#EVENTS.EXCHANGE_ERROR, listener);
|
|
740
|
+
return this;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* Registers a route entry.
|
|
745
|
+
*
|
|
746
|
+
* @param {HTTPMethod|undefined} method - The HTTP method to match.
|
|
747
|
+
* @param {string|undefined} pathnamePattern - The URL pathname pattern to match.
|
|
748
|
+
* @param {MiddlewareFunction[]} middlewares - The middleware functions to execute.
|
|
749
|
+
* @returns {Kernel} The current kernel instance.
|
|
750
|
+
*
|
|
751
|
+
* @example
|
|
752
|
+
* ```js
|
|
753
|
+
* kernel.registerRouteEntry('GET', '/users', [
|
|
754
|
+
* async (exchange, next) => {
|
|
755
|
+
* exchange.response.json({ users: [] });
|
|
756
|
+
* await next();
|
|
757
|
+
* },
|
|
758
|
+
* ]);
|
|
759
|
+
* ```
|
|
760
|
+
*/
|
|
761
|
+
registerRouteEntry(method, pathnamePattern, middlewares) {
|
|
762
|
+
this.#routerSystem.registerRouteEntry(method, pathnamePattern, middlewares);
|
|
763
|
+
return this;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Registers a mounted router by prefixing its routes with a base pathname pattern.
|
|
768
|
+
*
|
|
769
|
+
* @param {string} basePathnamePattern - The base URL pathname pattern to prefix.
|
|
770
|
+
* @param {Router} router - The router instance to mount.
|
|
771
|
+
* @returns {Kernel} The current kernel instance.
|
|
772
|
+
*
|
|
773
|
+
* @example
|
|
774
|
+
* ```js
|
|
775
|
+
* const usersRouter = new Router();
|
|
776
|
+
*
|
|
777
|
+
* usersRouter.get('/users', async exchange => {
|
|
778
|
+
* exchange.response.json({ users: [] });
|
|
779
|
+
* });
|
|
780
|
+
*
|
|
781
|
+
* kernel.registerMountEntry('/api', usersRouter);
|
|
782
|
+
* ```
|
|
783
|
+
*/
|
|
784
|
+
registerMountEntry(basePathnamePattern, router) {
|
|
785
|
+
this.#routerSystem.registerMountEntry(basePathnamePattern, router);
|
|
786
|
+
return this;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* Registers a handler for not-found exchanges.
|
|
791
|
+
*
|
|
792
|
+
* @param {NotFoundHandler} handler - Handler invoked when no route matches the exchange.
|
|
793
|
+
* @returns {Kernel} The current kernel instance.
|
|
794
|
+
*
|
|
795
|
+
* @example
|
|
796
|
+
* ```js
|
|
797
|
+
* kernel.notFound(exchange => {
|
|
798
|
+
* exchange.response.status(404).json({
|
|
799
|
+
* error: 'Resource not found',
|
|
800
|
+
* });
|
|
801
|
+
* });
|
|
802
|
+
* ```
|
|
803
|
+
*/
|
|
804
|
+
notFound(handler) {
|
|
805
|
+
this.#notFoundHandler = handler;
|
|
806
|
+
return this;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* Registers a handler for system errors raised during exchange processing.
|
|
811
|
+
*
|
|
812
|
+
* @param {SystemErrorHandler} handler - Handler invoked when an exchange fails.
|
|
813
|
+
* @returns {Kernel} The current kernel instance.
|
|
814
|
+
*
|
|
815
|
+
* @example
|
|
816
|
+
* ```js
|
|
817
|
+
* kernel.systemError((error, exchange) => {
|
|
818
|
+
* console.error(error);
|
|
819
|
+
*
|
|
820
|
+
* exchange.response.status(500).json({
|
|
821
|
+
* error: 'Request failed',
|
|
822
|
+
* });
|
|
823
|
+
* });
|
|
824
|
+
* ```
|
|
825
|
+
*/
|
|
826
|
+
systemError(handler) {
|
|
827
|
+
this.#systemErrorHandler = handler;
|
|
828
|
+
return this;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* Starts the HTTP server.
|
|
833
|
+
*
|
|
834
|
+
* @returns {Promise<void>} Resolves when the server has successfully started listening.
|
|
835
|
+
* @throws {ServerError} Thrown when the server cannot start listening.
|
|
836
|
+
*
|
|
837
|
+
* @example
|
|
838
|
+
* ```js
|
|
839
|
+
* await kernel.listen();
|
|
840
|
+
*
|
|
841
|
+
* console.log('Server started');
|
|
842
|
+
* ```
|
|
843
|
+
*/
|
|
844
|
+
async listen() {
|
|
845
|
+
const startedAt = performance.now();
|
|
846
|
+
|
|
847
|
+
try {
|
|
848
|
+
await this.#listenServer.execute(this.#listenOptions);
|
|
849
|
+
} catch (error) {
|
|
850
|
+
const serverError = new ServerError('SERVER_LISTEN_FAILED', { cause: normalizeError(error) });
|
|
851
|
+
this.#emit(Kernel.#EVENTS.ERROR, serverError);
|
|
852
|
+
throw serverError;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
this.#emit(Kernel.#EVENTS.LISTEN, {
|
|
856
|
+
endpoint: this.#server.endpoint(),
|
|
857
|
+
address: this.#server.address(),
|
|
858
|
+
elapsed: Math.round(performance.now() - startedAt),
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/**
|
|
863
|
+
* Closes the HTTP server.
|
|
864
|
+
*
|
|
865
|
+
* @returns {Promise<void>} Resolves when the server has successfully closed.
|
|
866
|
+
* @throws {ServerError} Thrown when the server cannot be closed successfully.
|
|
867
|
+
*
|
|
868
|
+
* @example
|
|
869
|
+
* ```js
|
|
870
|
+
* await kernel.close();
|
|
871
|
+
*
|
|
872
|
+
* console.log('Server closed');
|
|
873
|
+
* ```
|
|
874
|
+
*/
|
|
875
|
+
async close() {
|
|
876
|
+
const startedAt = performance.now();
|
|
877
|
+
|
|
878
|
+
if (this.#server.listening) {
|
|
879
|
+
this.#closeIPC();
|
|
880
|
+
|
|
881
|
+
try {
|
|
882
|
+
await this.#closeServer.execute();
|
|
883
|
+
} catch (error) {
|
|
884
|
+
const serverError = new ServerError('SERVER_CLOSE_FAILED', {
|
|
885
|
+
cause: normalizeError(error),
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
this.#emit(Kernel.#EVENTS.ERROR, serverError);
|
|
889
|
+
|
|
890
|
+
throw serverError;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
this.#emit(Kernel.#EVENTS.CLOSE, {
|
|
894
|
+
elapsed: Math.round(performance.now() - startedAt),
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
/**
|
|
900
|
+
* Processes a single HTTP exchange.
|
|
901
|
+
*
|
|
902
|
+
* @param {IncomingRequest} incomingMessage - Native Node.js incoming HTTP message.
|
|
903
|
+
* @param {ServerResponse} serverResponse - Native Node.js outgoing HTTP message.
|
|
904
|
+
* @returns {Promise<void>} Resolves when the exchange lifecycle has completed.
|
|
905
|
+
*/
|
|
906
|
+
async #handleExchange(incomingMessage, serverResponse) {
|
|
907
|
+
const exchange = this.#createExchange(incomingMessage, serverResponse);
|
|
908
|
+
|
|
909
|
+
if (exchange !== null) {
|
|
910
|
+
await this.#processExchange(exchange);
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/**
|
|
915
|
+
* Creates an exchange for an incoming HTTP request.
|
|
916
|
+
*
|
|
917
|
+
* If initialization fails, emits a server-level error and attempts to send
|
|
918
|
+
* a minimal `500` response.
|
|
919
|
+
*
|
|
920
|
+
* @param {IncomingRequest} incomingMessage - Incoming HTTP request.
|
|
921
|
+
* @param {ServerResponse} serverResponse - Outgoing HTTP response.
|
|
922
|
+
* @returns {Exchange|null} The created exchange, or `null` if initialization fails.
|
|
923
|
+
*/
|
|
924
|
+
#createExchange(incomingMessage, serverResponse) {
|
|
925
|
+
try {
|
|
926
|
+
return new Exchange(incomingMessage, serverResponse, {
|
|
927
|
+
request: this.#requestOptions,
|
|
928
|
+
response: this.#responseOptions,
|
|
929
|
+
onCommitError: (error, exchangeSource) => {
|
|
930
|
+
this.#emitExchangeError(error, exchangeSource);
|
|
931
|
+
},
|
|
932
|
+
});
|
|
933
|
+
} catch (error) {
|
|
934
|
+
const serverError = new ServerError('SERVER_EXCHANGE_INIT_FAILED', {
|
|
935
|
+
cause: normalizeError(error),
|
|
936
|
+
});
|
|
937
|
+
this.#emit(Kernel.#EVENTS.ERROR, serverError);
|
|
938
|
+
|
|
939
|
+
if (!serverResponse.headersSent && !serverResponse.writableEnded) {
|
|
940
|
+
serverResponse.statusCode = 500;
|
|
941
|
+
serverResponse.end();
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
return null;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* Handles an exchange processing error and attempts to produce an error response.
|
|
950
|
+
*
|
|
951
|
+
* @param {Error} error - The error raised during exchange processing.
|
|
952
|
+
* @param {Exchange} exchange - The exchange associated with the error.
|
|
953
|
+
* @returns {Promise<void>} Resolves when the error has been handled.
|
|
954
|
+
*/
|
|
955
|
+
async #handleExchangeError(error, exchange) {
|
|
956
|
+
this.#emitExchangeError(error, exchange);
|
|
957
|
+
|
|
958
|
+
if (!exchange.response.isCommitted) {
|
|
959
|
+
discardResponseState(exchange.response);
|
|
960
|
+
|
|
961
|
+
try {
|
|
962
|
+
await this.#handleSystemError(error, exchange);
|
|
963
|
+
} finally {
|
|
964
|
+
exchange.response.end();
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* Processes an exchange and emits its lifecycle events.
|
|
971
|
+
*
|
|
972
|
+
* Lifecycle events are emitted with the snapshot produced by
|
|
973
|
+
* `Exchange.createSnapshot`:
|
|
974
|
+
*
|
|
975
|
+
* - `exchange:start` carries a `pending` snapshot.
|
|
976
|
+
* - `exchange:end` carries a terminal snapshot (success or error).
|
|
977
|
+
* - `exchange:error` carries the snapshot of a failure observed before or during commit.
|
|
978
|
+
*
|
|
979
|
+
* @param {Exchange} exchange - The exchange to process.
|
|
980
|
+
*/
|
|
981
|
+
async #processExchange(exchange) {
|
|
982
|
+
this.#emit(
|
|
983
|
+
Kernel.#EVENTS.EXCHANGE_START,
|
|
984
|
+
Exchange.createSnapshot(exchange, 'pending', undefined, undefined),
|
|
985
|
+
);
|
|
986
|
+
|
|
987
|
+
const result = await this.#runPipeline(exchange);
|
|
988
|
+
|
|
989
|
+
try {
|
|
990
|
+
if (!result.success) {
|
|
991
|
+
await this.#handleExchangeError(result.error, exchange);
|
|
992
|
+
}
|
|
993
|
+
} finally {
|
|
994
|
+
const endedAt = performance.now();
|
|
995
|
+
|
|
996
|
+
this.#emit(
|
|
997
|
+
Kernel.#EVENTS.EXCHANGE_END,
|
|
998
|
+
Exchange.createSnapshot(
|
|
999
|
+
exchange,
|
|
1000
|
+
result.success ? 'success' : 'error',
|
|
1001
|
+
endedAt,
|
|
1002
|
+
result.success ? undefined : result.error,
|
|
1003
|
+
),
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* Executes the exchange pipeline and captures its outcome.
|
|
1010
|
+
*
|
|
1011
|
+
* @param {Exchange} exchange - Exchange to process.
|
|
1012
|
+
* @returns {Promise<PipelineExecutionResult>} The pipeline execution result.
|
|
1013
|
+
*/
|
|
1014
|
+
async #runPipeline(exchange) {
|
|
1015
|
+
try {
|
|
1016
|
+
await this.#routerSystem.executePipeline(exchange, this.#handleNotFound);
|
|
1017
|
+
|
|
1018
|
+
if (!exchange.response.isCommitted) {
|
|
1019
|
+
exchange.response.end();
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
return { success: true };
|
|
1023
|
+
} catch (error) {
|
|
1024
|
+
return {
|
|
1025
|
+
success: false,
|
|
1026
|
+
error: normalizeError(error),
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
/**
|
|
1032
|
+
* Handles an exchange that did not match any route.
|
|
1033
|
+
*
|
|
1034
|
+
* @type {(exchange: Exchange) => Promise<void>}
|
|
1035
|
+
*/
|
|
1036
|
+
#handleNotFound = async exchange => {
|
|
1037
|
+
if (this.#notFoundHandler === null) {
|
|
1038
|
+
exchange.response.status(404).json({ error: 'Not Found' });
|
|
1039
|
+
} else {
|
|
1040
|
+
await this.#notFoundHandler(exchange);
|
|
1041
|
+
}
|
|
1042
|
+
};
|
|
1043
|
+
|
|
1044
|
+
/**
|
|
1045
|
+
* Handles a system error raised during exchange processing.
|
|
1046
|
+
*
|
|
1047
|
+
* @param {Error} error - Error raised during exchange processing.
|
|
1048
|
+
* @param {Exchange} exchange - Exchange associated with the failed request.
|
|
1049
|
+
* @returns {Promise<void>} Resolves when the system error handler has completed.
|
|
1050
|
+
*/
|
|
1051
|
+
async #handleSystemError(error, exchange) {
|
|
1052
|
+
if (this.#systemErrorHandler === null) {
|
|
1053
|
+
exchange.response.status(500).json({ error: 'Internal Server Error' });
|
|
1054
|
+
} else {
|
|
1055
|
+
await this.#systemErrorHandler(error, exchange);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
/**
|
|
1060
|
+
* Emits a kernel event.
|
|
1061
|
+
*
|
|
1062
|
+
* @param {EventName} event - Event name.
|
|
1063
|
+
* @param {unknown} [eventPayload] - Optional event payload.
|
|
1064
|
+
* @returns {void}
|
|
1065
|
+
*/
|
|
1066
|
+
#emit(event, eventPayload) {
|
|
1067
|
+
this.#eventSystem.emit(event, eventPayload);
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
/**
|
|
1071
|
+
* Emits an exchange error event.
|
|
1072
|
+
*
|
|
1073
|
+
* @param {Error} error - The error to report.
|
|
1074
|
+
* @param {Exchange} exchange - The exchange associated with the error.
|
|
1075
|
+
*/
|
|
1076
|
+
#emitExchangeError(error, exchange) {
|
|
1077
|
+
this.#emit(
|
|
1078
|
+
Kernel.#EVENTS.EXCHANGE_ERROR,
|
|
1079
|
+
Exchange.createSnapshot(exchange, 'error', undefined, error),
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
/**
|
|
1084
|
+
* Closes the IPC subsystem without propagating close failures.
|
|
1085
|
+
*
|
|
1086
|
+
* Invoked during kernel shutdown. Failures are intentionally ignored
|
|
1087
|
+
* because the server is already shutting down.
|
|
1088
|
+
*/
|
|
1089
|
+
#closeIPC() {
|
|
1090
|
+
try {
|
|
1091
|
+
this.#ipc.close();
|
|
1092
|
+
} catch {
|
|
1093
|
+
// Ignore IPC close failures — the server is shutting down.
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
/**
|
|
1098
|
+
* Kernel lifecycle event names.
|
|
1099
|
+
*
|
|
1100
|
+
* @type {Readonly<{
|
|
1101
|
+
* EXCHANGE_START: 'exchange:start',
|
|
1102
|
+
* EXCHANGE_END: 'exchange:end',
|
|
1103
|
+
* EXCHANGE_ERROR: 'exchange:error',
|
|
1104
|
+
* LISTEN: 'listen',
|
|
1105
|
+
* CLOSE: 'close',
|
|
1106
|
+
* ERROR: 'error'
|
|
1107
|
+
* }>}
|
|
1108
|
+
*/
|
|
1109
|
+
static #EVENTS = Object.freeze({
|
|
1110
|
+
EXCHANGE_START: 'exchange:start',
|
|
1111
|
+
EXCHANGE_END: 'exchange:end',
|
|
1112
|
+
EXCHANGE_ERROR: 'exchange:error',
|
|
1113
|
+
LISTEN: 'listen',
|
|
1114
|
+
CLOSE: 'close',
|
|
1115
|
+
ERROR: 'error',
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
/**
|
|
1119
|
+
* Resolves and creates the transport adapter for the server.
|
|
1120
|
+
*
|
|
1121
|
+
* @param {ServerTransportOption} transport - Transport identifier used to resolve the adapter.
|
|
1122
|
+
* @param {ServerConnectionOptions} connection - Connection options passed to the adapter.
|
|
1123
|
+
* @returns {TransportAdapter} The initialized transport adapter.
|
|
1124
|
+
* @throws {ServerError} Thrown when the transport adapter cannot be resolved or initialized.
|
|
1125
|
+
*/
|
|
1126
|
+
static #ensureAdapter(transport, connection) {
|
|
1127
|
+
try {
|
|
1128
|
+
const Adapter = TransportResolver.resolve(transport);
|
|
1129
|
+
return new Adapter(connection);
|
|
1130
|
+
} catch (error) {
|
|
1131
|
+
throw new ServerError('SERVER_ADAPTER_INIT_FAILED', { cause: normalizeError(error) });
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1137
|
+
export default Kernel;
|