@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.
Files changed (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +287 -2
  3. package/index.mjs +23 -0
  4. package/package.json +71 -4
  5. package/source/application/Kernel/index.mjs +1137 -0
  6. package/source/application/Server/index.mjs +1201 -0
  7. package/source/application/configurations/Http2CleartextServerConfiguration/index.mjs +168 -0
  8. package/source/application/configurations/Http2SecureServerConfiguration/index.mjs +172 -0
  9. package/source/application/configurations/HttpServerConfiguration/index.mjs +169 -0
  10. package/source/application/configurations/HttpsServerConfiguration/index.mjs +172 -0
  11. package/source/application/configurations/ServerConfiguration/Options/CORSOptions/CORSOptionsValue.mjs +158 -0
  12. package/source/application/configurations/ServerConfiguration/Options/CORSOptions/index.mjs +87 -0
  13. package/source/application/configurations/ServerConfiguration/Options/CSRFOptions/CSRFOptionsValue.mjs +178 -0
  14. package/source/application/configurations/ServerConfiguration/Options/CSRFOptions/index.mjs +80 -0
  15. package/source/application/configurations/ServerConfiguration/Options/CompressOptions/CompressOptionsValue.mjs +126 -0
  16. package/source/application/configurations/ServerConfiguration/Options/CompressOptions/index.mjs +81 -0
  17. package/source/application/configurations/ServerConfiguration/Options/CookieParserOptions/CookieParserOptionsValue.mjs +107 -0
  18. package/source/application/configurations/ServerConfiguration/Options/CookieParserOptions/index.mjs +82 -0
  19. package/source/application/configurations/ServerConfiguration/Options/CookieSessionOptions/CookieSessionOptionsValue.mjs +182 -0
  20. package/source/application/configurations/ServerConfiguration/Options/CookieSessionOptions/index.mjs +86 -0
  21. package/source/application/configurations/ServerConfiguration/Options/ListenOptions/ListenOptionsValue.mjs +95 -0
  22. package/source/application/configurations/ServerConfiguration/Options/ListenOptions/index.mjs +47 -0
  23. package/source/application/configurations/ServerConfiguration/Options/MiddlewareOption.mjs +102 -0
  24. package/source/application/configurations/ServerConfiguration/Options/Options.mjs +105 -0
  25. package/source/application/configurations/ServerConfiguration/Options/RateLimitOptions/RateLimitOptionsValue.mjs +164 -0
  26. package/source/application/configurations/ServerConfiguration/Options/RateLimitOptions/index.mjs +84 -0
  27. package/source/application/configurations/ServerConfiguration/Options/RequestOptions/RequestOptionsValue.mjs +108 -0
  28. package/source/application/configurations/ServerConfiguration/Options/RequestOptions/index.mjs +47 -0
  29. package/source/application/configurations/ServerConfiguration/Options/ResponseOptions/ResponseOptionsValue.mjs +101 -0
  30. package/source/application/configurations/ServerConfiguration/Options/ResponseOptions/index.mjs +47 -0
  31. package/source/application/configurations/ServerConfiguration/Options/SecurityHeadersOptions/SecurityHeadersOptionsValue.mjs +272 -0
  32. package/source/application/configurations/ServerConfiguration/Options/SecurityHeadersOptions/index.mjs +85 -0
  33. package/source/application/configurations/ServerConfiguration/Options/ServerOption.mjs +50 -0
  34. package/source/application/configurations/ServerConfiguration/Options/StaticOptions/StaticOptionsValue.mjs +164 -0
  35. package/source/application/configurations/ServerConfiguration/Options/StaticOptions/index.mjs +89 -0
  36. package/source/application/configurations/ServerConfiguration/index.mjs +781 -0
  37. package/source/application/resolvers/TransportResolver/index.mjs +119 -0
  38. package/source/application/use-cases/CloseServer/index.mjs +85 -0
  39. package/source/application/use-cases/ListenServer/index.mjs +98 -0
  40. package/source/core/EventSystem/index.mjs +110 -0
  41. package/source/core/Exchange/ExchangeContext.mjs +291 -0
  42. package/source/core/Exchange/Telemetry.mjs +193 -0
  43. package/source/core/Exchange/index.mjs +604 -0
  44. package/source/core/ExchangeLayerBuilder/index.mjs +133 -0
  45. package/source/core/IPCMain/IPCMain.mjs +507 -0
  46. package/source/core/IPCMain/IPCProtocol.mjs +441 -0
  47. package/source/core/IPCMain/IPCServerChannel.mjs +331 -0
  48. package/source/core/IPCMain/WebSocketAdapter.mjs +189 -0
  49. package/source/core/Request/BodyParser/BodyParser.mjs +197 -0
  50. package/source/core/Request/BodyParser/JSONParser.mjs +70 -0
  51. package/source/core/Request/BodyParser/MultipartParser.mjs +612 -0
  52. package/source/core/Request/BodyParser/URLEncodedParser.mjs +71 -0
  53. package/source/core/Request/RequestHeaders.mjs +177 -0
  54. package/source/core/Request/index.mjs +805 -0
  55. package/source/core/Response/ResponseHeaders.mjs +402 -0
  56. package/source/core/Response/index.mjs +1370 -0
  57. package/source/core/middlewares/CORSMiddleware/index.mjs +289 -0
  58. package/source/core/middlewares/CSRFMiddleware/index.mjs +284 -0
  59. package/source/core/middlewares/CompressMiddleware/index.mjs +460 -0
  60. package/source/core/middlewares/CookieParserMiddleware/index.mjs +194 -0
  61. package/source/core/middlewares/CookieSessionMiddleware/index.mjs +460 -0
  62. package/source/core/middlewares/RateLimitMiddleware/index.mjs +245 -0
  63. package/source/core/middlewares/SecurityHeadersMiddleware/index.mjs +151 -0
  64. package/source/core/middlewares/StaticMiddleware/index.mjs +628 -0
  65. package/source/core/routing/ExchangeLayer/ExchangeMatcher.mjs +127 -0
  66. package/source/core/routing/ExchangeLayer/index.mjs +254 -0
  67. package/source/core/routing/Pipeline/index.mjs +157 -0
  68. package/source/core/routing/Router/Route.mjs +184 -0
  69. package/source/core/routing/Router/index.mjs +523 -0
  70. package/source/core/routing/RouterRegister/OptionsMiddleware.mjs +147 -0
  71. package/source/core/routing/RouterRegister/index.mjs +150 -0
  72. package/source/core/routing/RouterSystem/index.mjs +225 -0
  73. package/source/core/value-objects/Address/index.mjs +281 -0
  74. package/source/core/value-objects/Endpoint/index.mjs +293 -0
  75. package/source/index.mjs +62 -0
  76. package/source/infrastructure/adapters/Http2Adapter/index.mjs +144 -0
  77. package/source/infrastructure/adapters/Http2CleartextAdapter/index.mjs +147 -0
  78. package/source/infrastructure/adapters/HttpAdapter/index.mjs +124 -0
  79. package/source/infrastructure/adapters/HttpsAdapter/index.mjs +124 -0
  80. package/source/infrastructure/adapters/TransportAdapter/createWebSocketOpenRequest.mjs +843 -0
  81. package/source/infrastructure/adapters/TransportAdapter/index.mjs +555 -0
  82. package/source/shared/Identifier/index.mjs +671 -0
  83. package/source/shared/constants/symbols/brands/index.mjs +45 -0
  84. package/source/shared/domain/UUIDV7/index.mjs +346 -0
  85. package/source/shared/errors/BaseError/index.mjs +157 -0
  86. package/source/shared/errors/IPCError/index.mjs +97 -0
  87. package/source/shared/errors/MiddlewareError/index.mjs +106 -0
  88. package/source/shared/errors/PathnamePatternError/index.mjs +103 -0
  89. package/source/shared/errors/RequestError/index.mjs +116 -0
  90. package/source/shared/errors/ResponseError/index.mjs +140 -0
  91. package/source/shared/errors/RouterError/index.mjs +90 -0
  92. package/source/shared/errors/ServerConfigurationError/index.mjs +551 -0
  93. package/source/shared/errors/ServerError/index.mjs +127 -0
  94. package/source/shared/errors/WebSocketError/index.mjs +58 -0
  95. package/source/shared/outcome/ErrorSchema.mjs +361 -0
  96. package/source/shared/outcome/Result/index.mjs +239 -0
  97. package/source/shared/outcome/Validation/index.mjs +208 -0
  98. package/source/shared/validations/Middleware/index.mjs +160 -0
  99. package/source/shared/validations/PathnamePattern/index.mjs +256 -0
  100. package/source/shared/validations/Server/NotFoundHandler/index.mjs +94 -0
  101. package/source/shared/validations/Server/Router/index.mjs +68 -0
  102. package/source/shared/validations/Server/ServerConfiguration/index.mjs +68 -0
  103. package/source/shared/validations/Server/SystemErrorHandler/index.mjs +94 -0
  104. package/source/shared/validations/ServerConfiguration/CORSOptions/index.mjs +310 -0
  105. package/source/shared/validations/ServerConfiguration/CSRFOptions/index.mjs +299 -0
  106. package/source/shared/validations/ServerConfiguration/CompressOptions/index.mjs +204 -0
  107. package/source/shared/validations/ServerConfiguration/CookieParserOptions/index.mjs +102 -0
  108. package/source/shared/validations/ServerConfiguration/CookieSessionOptions/index.mjs +332 -0
  109. package/source/shared/validations/ServerConfiguration/ListenOptions/index.mjs +178 -0
  110. package/source/shared/validations/ServerConfiguration/RateLimitOptions/index.mjs +229 -0
  111. package/source/shared/validations/ServerConfiguration/RequestOptions/index.mjs +132 -0
  112. package/source/shared/validations/ServerConfiguration/ResponseOptions/index.mjs +122 -0
  113. package/source/shared/validations/ServerConfiguration/SecurityHeadersOptions/index.mjs +137 -0
  114. package/source/shared/validations/ServerConfiguration/StaticOptions/index.mjs +248 -0
  115. package/types/index.d.ts +719 -0
@@ -0,0 +1,1201 @@
1
+ /**
2
+ * @file Defines the Server API.
3
+ *
4
+ * @description Provides the public server interface for configuring,
5
+ * starting, monitoring, and stopping an application server.
6
+ *
7
+ * The Server manages the server lifecycle, middleware and route
8
+ * registration, request handling, and server events.
9
+ *
10
+ * @module Server
11
+ */
12
+
13
+ // ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
14
+ // » IMPORT NATIVE NODEJS MODULES
15
+
16
+ // » IMPORT THIRD PARTY MODULES
17
+
18
+ // » IMPORT LOCAL MODULES
19
+ import Kernel from '#Kernel';
20
+ import ServerError from '#shared/errors/ServerError';
21
+ import { assertServerConfiguration } from '#shared/validations/Server/ServerConfiguration';
22
+ import { assertRouter } from '#shared/validations/Server/Router';
23
+ import { assertNotFoundHandler } from '#shared/validations/Server/NotFoundHandler';
24
+ import { assertSystemErrorHandler } from '#shared/validations/Server/SystemErrorHandler';
25
+ import { assertPathnamePattern } from '#shared/validations/PathnamePattern';
26
+ import { assertMiddlewares } from '#shared/validations/Middleware';
27
+
28
+ // ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
29
+ /**
30
+ * Represents a single HTTP transaction.
31
+ *
32
+ * @typedef {import('#Exchange').default} Exchange
33
+ */
34
+
35
+ /**
36
+ * This error type provides identifiable failures related to middleware validation.
37
+ *
38
+ * @typedef {import('#shared/errors/MiddlewareError').default} MiddlewareError
39
+ */
40
+
41
+ /**
42
+ * This error type provides identifiable failures related to pathname pattern syntax and validation.
43
+ *
44
+ * @typedef {import('#shared/errors/PathnamePatternError').default} PathnamePatternError
45
+ */
46
+
47
+ /**
48
+ * Represents a permitted server configuration identity.
49
+ *
50
+ * @typedef {import('#configurations/ServerConfiguration').ServerConfigurationIdentity} ServerConfigurationIdentity
51
+ */
52
+
53
+ /**
54
+ * Represents a server configuration constrained to a permitted identity.
55
+ *
56
+ * @typedef {import('#configurations/ServerConfiguration').default<ServerConfigurationIdentity>} ServerConfiguration
57
+ */
58
+
59
+ /**
60
+ * Represents a complete network endpoint.
61
+ *
62
+ * @typedef {import('#value-objects/Endpoint').default} Endpoint
63
+ */
64
+
65
+ /**
66
+ * Represents the network address of an HTTP server.
67
+ *
68
+ * @typedef {import('#value-objects/Address').default} Address
69
+ */
70
+
71
+ /**
72
+ * Represents a router instance.
73
+ *
74
+ * @typedef {import('#routing/Router').default} Router
75
+ */
76
+
77
+ /**
78
+ * Owns the IPC subsystem and its application-facing API.
79
+ *
80
+ * @typedef {import('#IPCMain').default} IPCMain
81
+ */
82
+
83
+ /**
84
+ * Provides information about an exchange when processing starts.
85
+ *
86
+ * @typedef {import('#Exchange').ExchangeStartInfo} ExchangeStartInfo
87
+ */
88
+
89
+ /**
90
+ * Provides information about an exchange when processing fails.
91
+ *
92
+ * @typedef {import('#Exchange').ExchangeErrorInfo} ExchangeErrorInfo
93
+ */
94
+
95
+ /**
96
+ * Provides information about an exchange when processing completes.
97
+ *
98
+ * @typedef {import('#Exchange').ExchangeEndInfo} ExchangeEndInfo
99
+ */
100
+
101
+ /**
102
+ * Represents the lifecycle states of the server.
103
+ *
104
+ * @typedef { 'idle' | 'starting' | 'listening' | 'stopping' | 'closed' | 'error' } ServerState
105
+ */
106
+
107
+ /**
108
+ * Represents a pathname pattern used to match incoming requests.
109
+ *
110
+ * @typedef {string} PathnamePattern
111
+ */
112
+
113
+ /**
114
+ * Represents a function that delegates execution to the next
115
+ * middleware layer.
116
+ *
117
+ * @callback NextFunction
118
+ * @returns {void|Promise<void>}
119
+ */
120
+
121
+ /**
122
+ * Represents an exchange middleware function.
123
+ *
124
+ * @callback MiddlewareFunction
125
+ * @param {Exchange} exchange - Current HTTP exchange.
126
+ * @param {NextFunction} next - Delegates execution to the next layer.
127
+ * @returns {void|Promise<void>}
128
+ */
129
+
130
+ /**
131
+ * Represents an event name supported by the server.
132
+ *
133
+ * @typedef {'listen'|'close'|'error'|'exchange:start'|'exchange:end'|'exchange:error'|symbol} EventName
134
+ */
135
+
136
+ /**
137
+ * Data emitted with the `listen` event.
138
+ *
139
+ * @typedef {object} ListenEventData
140
+ * @property {Endpoint} endpoint - Endpoint where the server is listening.
141
+ * @property {Address} address - Network address where the server is listening.
142
+ * @property {number} elapsed - Elapsed time in milliseconds.
143
+ */
144
+
145
+ /**
146
+ * Data emitted with the `close` event.
147
+ *
148
+ * @typedef {object} CloseEventData
149
+ * @property {number} elapsed - Elapsed time in milliseconds.
150
+ */
151
+
152
+ /**
153
+ * Listener registered for the `listen` event.
154
+ *
155
+ * @typedef {(data: ListenEventData) => void} ListenEventListener
156
+ */
157
+
158
+ /**
159
+ * Listener registered for the `close` event.
160
+ *
161
+ * @typedef {(data: CloseEventData) => void} CloseEventListener
162
+ */
163
+
164
+ /**
165
+ * Listener registered for the `error` event.
166
+ *
167
+ * @typedef {(error: Error) => void} ErrorEventListener
168
+ */
169
+
170
+ /**
171
+ * Listener registered for the `exchange:start` event.
172
+ *
173
+ * @typedef {(data: ExchangeStartInfo) => void} ExchangeStartEventListener
174
+ */
175
+
176
+ /**
177
+ * Listener registered for the `exchange:end` event.
178
+ *
179
+ * @typedef {(data: ExchangeEndInfo) => void} ExchangeEndEventListener
180
+ */
181
+
182
+ /**
183
+ * Listener registered for the `exchange:error` event.
184
+ *
185
+ * @typedef {(data: ExchangeErrorInfo) => void} ExchangeErrorEventListener
186
+ */
187
+
188
+ /**
189
+ * Represents a listener registered for a server event.
190
+ *
191
+ * @typedef {(data?: unknown) => void} EventListener
192
+ */
193
+
194
+ /**
195
+ * Represents a handler invoked when a request does not match any registered route.
196
+ *
197
+ * @typedef {(exchange: Exchange) => void|Promise<void>} NotFoundHandler
198
+ */
199
+
200
+ /**
201
+ * Represents a handler invoked when an error propagates during request processing.
202
+ *
203
+ * @typedef {(error: Error, exchange: Exchange) => void|Promise<void>} SystemErrorHandler
204
+ */
205
+
206
+ // ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
207
+ /**
208
+ * Provides the public interface for managing an application server.
209
+ *
210
+ * @classdesc Server is the central entry point for configuring and controlling
211
+ * the application server. It provides middleware and route registration,
212
+ * lifecycle management, request handling, error handling, and server
213
+ * event notifications.
214
+ *
215
+ * The server remains inactive until `listen()` is called and can be
216
+ * gracefully stopped with `close()`. Configuration and application
217
+ * handlers are registered before startup and remain fixed throughout
218
+ * the server lifecycle.
219
+ */
220
+ class Server {
221
+ /**
222
+ * Application kernel instance.
223
+ *
224
+ * @type {Kernel}
225
+ */
226
+ #kernel;
227
+
228
+ /**
229
+ * Current server lifecycle state.
230
+ *
231
+ * @type {ServerState}
232
+ */
233
+ #state = Server.#STATES.IDLE;
234
+
235
+ /**
236
+ * Creates a server instance from the provided configuration.
237
+ *
238
+ * The configuration defines the transport protocol, listening options, and
239
+ * server behavior. The server is initialized in the `idle` state and remains
240
+ * inactive until `listen()` is called.
241
+ *
242
+ * @param {ServerConfiguration} configuration - Server configuration used to initialize the server.
243
+ * @throws {ServerError} If `configuration` does not implement the ServerConfiguration contract.
244
+ * @example
245
+ * ```js
246
+ * const configuration = new HttpServerConfiguration({
247
+ * connection: {
248
+ * requestTimeout: 300_000,
249
+ * headersTimeout: 60_000,
250
+ * keepAliveTimeout: 5_000,
251
+ * maxHeaderSize: 16_384,
252
+ * noDelay: true,
253
+ * },
254
+ * listen: {
255
+ * host: '127.0.0.1',
256
+ * port: 3000,
257
+ * backlog: 511,
258
+ * },
259
+ * request: {
260
+ * bodyLimit: 1_048_576,
261
+ * trustProxy: false,
262
+ * },
263
+ * response: {
264
+ * etag: true,
265
+ * defaultCharset: 'utf-8',
266
+ * },
267
+ * });
268
+ *
269
+ * const server = new Server(configuration);
270
+ * ```
271
+ *
272
+ * @example
273
+ * ```js
274
+ * const configuration = new HttpsServerConfiguration({
275
+ * connection: {
276
+ * requestTimeout: 300_000,
277
+ * headersTimeout: 60_000,
278
+ * keepAliveTimeout: 5_000,
279
+ * maxHeaderSize: 16_384,
280
+ * noDelay: true,
281
+ * key: await readFile('./certs/server.key'),
282
+ * cert: await readFile('./certs/server.crt'),
283
+ * ca: await readFile('./certs/ca.crt'),
284
+ * minVersion: 'TLSv1.2',
285
+ * maxVersion: 'TLSv1.3',
286
+ * requestCert: false,
287
+ * rejectUnauthorized: true,
288
+ * ALPNProtocols: ['h2', 'http/1.1'],
289
+ * },
290
+ * listen: {
291
+ * host: '127.0.0.1',
292
+ * port: 3000,
293
+ * backlog: 511,
294
+ * },
295
+ * request: {
296
+ * bodyLimit: 1_048_576,
297
+ * trustProxy: false,
298
+ * },
299
+ * response: {
300
+ * etag: true,
301
+ * defaultCharset: 'utf-8',
302
+ * },
303
+ * });
304
+ *
305
+ * const server = new Server(configuration);
306
+ * ```
307
+ */
308
+ constructor(configuration) {
309
+ assertServerConfiguration(configuration);
310
+
311
+ this.#kernel = new Kernel(configuration);
312
+ }
313
+
314
+ /**
315
+ * Returns the current server lifecycle state.
316
+ *
317
+ * @readonly
318
+ * @type {ServerState}
319
+ */
320
+ get state() {
321
+ return this.#state;
322
+ }
323
+
324
+ /**
325
+ * Returns whether the server is currently listening for requests.
326
+ *
327
+ * @readonly
328
+ * @type {boolean}
329
+ */
330
+ get listening() {
331
+ return this.#kernel.listening;
332
+ }
333
+
334
+ /**
335
+ * The IPC subsystem owned by the server.
336
+ *
337
+ * Available only while the server is listening.
338
+ *
339
+ * @readonly
340
+ * @type {IPCMain}
341
+ * @throws {ServerError} If the server is not currently listening.
342
+ */
343
+ get ipc() {
344
+ if (!this.#kernel.listening) {
345
+ throw new ServerError('SERVER_NOT_LISTENING');
346
+ }
347
+
348
+ return this.#kernel.ipc;
349
+ }
350
+
351
+ /**
352
+ * Registers middleware for all HTTP methods.
353
+ *
354
+ * @param {PathnamePattern | MiddlewareFunction} pathnamePattern - The pathname pattern to match, or the first middleware function.
355
+ * @param {...MiddlewareFunction} middlewares - The middleware functions to execute for matching requests.
356
+ * @returns {Server} The server instance.
357
+ * @throws {ServerError} If the server is not in the idle state.
358
+ * @throws {PathnamePatternError} If the pathname pattern is not a string.
359
+ * @throws {PathnamePatternError} If the pathname pattern is empty.
360
+ * @throws {PathnamePatternError} If the pathname pattern exceeds the maximum length.
361
+ * @throws {PathnamePatternError} If the pathname pattern syntax is invalid.
362
+ * @throws {PathnamePatternError} If the pathname pattern has duplicate parameter names.
363
+ * @throws {MiddlewareError} If a middleware is not a function.
364
+ * @throws {MiddlewareError} If a middleware does not declare exactly two parameters.
365
+ * @example
366
+ * ```js
367
+ * server.use('/api', authenticate);
368
+ * server.use(logger);
369
+ * ```
370
+ */
371
+ use(pathnamePattern, ...middlewares) {
372
+ this.#assertCurrentState(Server.#IDLE_STATE_ERRORS);
373
+ const routeEntry = this.#ensureRouteEntry(pathnamePattern, middlewares);
374
+ this.#kernel.registerRouteEntry(undefined, routeEntry.pathnamePattern, routeEntry.middlewares);
375
+ return this;
376
+ }
377
+
378
+ /**
379
+ * Registers middleware for GET requests.
380
+ *
381
+ * @param {PathnamePattern | MiddlewareFunction} pathnamePattern - The pathname pattern to match, or the first middleware function.
382
+ * @param {...MiddlewareFunction} middlewares - The middleware functions to execute for matching GET requests.
383
+ * @returns {Server} The server instance.
384
+ * @throws {ServerError} If the server is not in the idle state.
385
+ * @throws {PathnamePatternError} If the pathname pattern is not a string.
386
+ * @throws {PathnamePatternError} If the pathname pattern is empty.
387
+ * @throws {PathnamePatternError} If the pathname pattern exceeds the maximum length.
388
+ * @throws {PathnamePatternError} If the pathname pattern syntax is invalid.
389
+ * @throws {PathnamePatternError} If the pathname pattern has duplicate parameter names.
390
+ * @throws {MiddlewareError} If a middleware is not a function.
391
+ * @throws {MiddlewareError} If a middleware does not declare exactly two parameters.
392
+ * @example
393
+ * ```js
394
+ * server.get('/users', getUsers);
395
+ * ```
396
+ */
397
+ get(pathnamePattern, ...middlewares) {
398
+ this.#assertCurrentState(Server.#IDLE_STATE_ERRORS);
399
+ const routeEntry = this.#ensureRouteEntry(pathnamePattern, middlewares);
400
+
401
+ this.#kernel.registerRouteEntry('GET', routeEntry.pathnamePattern, routeEntry.middlewares);
402
+
403
+ return this;
404
+ }
405
+
406
+ /**
407
+ * Registers middleware for QUERY requests.
408
+ *
409
+ * @param {PathnamePattern | MiddlewareFunction} pathnamePattern - The pathname pattern to match, or the first middleware function.
410
+ * @param {...MiddlewareFunction} middlewares - The middleware functions to execute for matching QUERY requests.
411
+ * @returns {Server} The server instance.
412
+ * @throws {ServerError} If the server is not in the idle state.
413
+ * @throws {PathnamePatternError} If the pathname pattern is not a string.
414
+ * @throws {PathnamePatternError} If the pathname pattern is empty.
415
+ * @throws {PathnamePatternError} If the pathname pattern exceeds the maximum length.
416
+ * @throws {PathnamePatternError} If the pathname pattern syntax is invalid.
417
+ * @throws {PathnamePatternError} If the pathname pattern has duplicate parameter names.
418
+ * @throws {MiddlewareError} If a middleware is not a function.
419
+ * @throws {MiddlewareError} If a middleware does not declare exactly two parameters.
420
+ * @example
421
+ * ```js
422
+ * server.query('/users', queryUsers);
423
+ * ```
424
+ */
425
+ query(pathnamePattern, ...middlewares) {
426
+ this.#assertCurrentState(Server.#IDLE_STATE_ERRORS);
427
+ const routeEntry = this.#ensureRouteEntry(pathnamePattern, middlewares);
428
+
429
+ this.#kernel.registerRouteEntry('QUERY', routeEntry.pathnamePattern, routeEntry.middlewares);
430
+
431
+ return this;
432
+ }
433
+
434
+ /**
435
+ * Registers middleware for POST requests.
436
+ *
437
+ * @param {PathnamePattern | MiddlewareFunction} pathnamePattern - The pathname pattern to match, or the first middleware function.
438
+ * @param {...MiddlewareFunction} middlewares - The middleware functions to execute for matching POST requests.
439
+ * @returns {Server} The server instance.
440
+ * @throws {ServerError} If the server is not in the idle state.
441
+ * @throws {PathnamePatternError} If the pathname pattern is not a string.
442
+ * @throws {PathnamePatternError} If the pathname pattern is empty.
443
+ * @throws {PathnamePatternError} If the pathname pattern exceeds the maximum length.
444
+ * @throws {PathnamePatternError} If the pathname pattern syntax is invalid.
445
+ * @throws {PathnamePatternError} If the pathname pattern has duplicate parameter names.
446
+ * @throws {MiddlewareError} If a middleware is not a function.
447
+ * @throws {MiddlewareError} If a middleware does not declare exactly two parameters.
448
+ * @example
449
+ * ```js
450
+ * server.post('/users', createUser);
451
+ * ```
452
+ */
453
+ post(pathnamePattern, ...middlewares) {
454
+ this.#assertCurrentState(Server.#IDLE_STATE_ERRORS);
455
+ const routeEntry = this.#ensureRouteEntry(pathnamePattern, middlewares);
456
+
457
+ this.#kernel.registerRouteEntry('POST', routeEntry.pathnamePattern, routeEntry.middlewares);
458
+
459
+ return this;
460
+ }
461
+
462
+ /**
463
+ * Registers middleware for PUT requests.
464
+ *
465
+ * @param {PathnamePattern | MiddlewareFunction} pathnamePattern - The pathname pattern to match, or the first middleware function.
466
+ * @param {...MiddlewareFunction} middlewares - The middleware functions to execute for matching PUT requests.
467
+ * @returns {Server} The server instance.
468
+ * @throws {ServerError} If the server is not in the idle state.
469
+ * @throws {PathnamePatternError} If the pathname pattern is not a string.
470
+ * @throws {PathnamePatternError} If the pathname pattern is empty.
471
+ * @throws {PathnamePatternError} If the pathname pattern exceeds the maximum length.
472
+ * @throws {PathnamePatternError} If the pathname pattern syntax is invalid.
473
+ * @throws {PathnamePatternError} If the pathname pattern has duplicate parameter names.
474
+ * @throws {MiddlewareError} If a middleware is not a function.
475
+ * @throws {MiddlewareError} If a middleware does not declare exactly two parameters.
476
+ * @example
477
+ * ```js
478
+ * server.put('/users/:userId', updateUser);
479
+ * ```
480
+ */
481
+ put(pathnamePattern, ...middlewares) {
482
+ this.#assertCurrentState(Server.#IDLE_STATE_ERRORS);
483
+ const routeEntry = this.#ensureRouteEntry(pathnamePattern, middlewares);
484
+
485
+ this.#kernel.registerRouteEntry('PUT', routeEntry.pathnamePattern, routeEntry.middlewares);
486
+
487
+ return this;
488
+ }
489
+
490
+ /**
491
+ * Registers middleware for PATCH requests.
492
+ *
493
+ * @param {PathnamePattern | MiddlewareFunction} pathnamePattern - The pathname pattern to match, or the first middleware function.
494
+ * @param {...MiddlewareFunction} middlewares - The middleware functions to execute for matching PATCH requests.
495
+ * @returns {Server} The server instance.
496
+ * @throws {ServerError} If the server is not in the idle state.
497
+ * @throws {PathnamePatternError} If the pathname pattern is not a string.
498
+ * @throws {PathnamePatternError} If the pathname pattern is empty.
499
+ * @throws {PathnamePatternError} If the pathname pattern exceeds the maximum length.
500
+ * @throws {PathnamePatternError} If the pathname pattern syntax is invalid.
501
+ * @throws {PathnamePatternError} If the pathname pattern has duplicate parameter names.
502
+ * @throws {MiddlewareError} If a middleware is not a function.
503
+ * @throws {MiddlewareError} If a middleware does not declare exactly two parameters.
504
+ * @example
505
+ * ```js
506
+ * server.patch('/users/:userId', patchUser);
507
+ * ```
508
+ */
509
+ patch(pathnamePattern, ...middlewares) {
510
+ this.#assertCurrentState(Server.#IDLE_STATE_ERRORS);
511
+ const routeEntry = this.#ensureRouteEntry(pathnamePattern, middlewares);
512
+
513
+ this.#kernel.registerRouteEntry('PATCH', routeEntry.pathnamePattern, routeEntry.middlewares);
514
+
515
+ return this;
516
+ }
517
+
518
+ /**
519
+ * Registers middleware for DELETE requests.
520
+ *
521
+ * @param {PathnamePattern | MiddlewareFunction} pathnamePattern - The pathname pattern to match, or the first middleware function.
522
+ * @param {...MiddlewareFunction} middlewares - The middleware functions to execute for matching DELETE requests.
523
+ * @returns {Server} The server instance.
524
+ * @throws {ServerError} If the server is not in the idle state.
525
+ * @throws {PathnamePatternError} If the pathname pattern is not a string.
526
+ * @throws {PathnamePatternError} If the pathname pattern is empty.
527
+ * @throws {PathnamePatternError} If the pathname pattern exceeds the maximum length.
528
+ * @throws {PathnamePatternError} If the pathname pattern syntax is invalid.
529
+ * @throws {PathnamePatternError} If the pathname pattern has duplicate parameter names.
530
+ * @throws {MiddlewareError} If a middleware is not a function.
531
+ * @throws {MiddlewareError} If a middleware does not declare exactly two parameters.
532
+ * @example
533
+ * ```js
534
+ * server.delete('/users/:userId', deleteUser);
535
+ * ```
536
+ */
537
+ delete(pathnamePattern, ...middlewares) {
538
+ this.#assertCurrentState(Server.#IDLE_STATE_ERRORS);
539
+ const routeEntry = this.#ensureRouteEntry(pathnamePattern, middlewares);
540
+
541
+ this.#kernel.registerRouteEntry('DELETE', routeEntry.pathnamePattern, routeEntry.middlewares);
542
+
543
+ return this;
544
+ }
545
+
546
+ /**
547
+ * Registers middleware for HEAD requests.
548
+ *
549
+ * @param {PathnamePattern | MiddlewareFunction} pathnamePattern - The pathname pattern to match, or the first middleware function.
550
+ * @param {...MiddlewareFunction} middlewares - The middleware functions to execute for matching HEAD requests.
551
+ * @returns {Server} The server instance.
552
+ * @throws {ServerError} If the server is not in the idle state.
553
+ * @throws {PathnamePatternError} If the pathname pattern is not a string.
554
+ * @throws {PathnamePatternError} If the pathname pattern is empty.
555
+ * @throws {PathnamePatternError} If the pathname pattern exceeds the maximum length.
556
+ * @throws {PathnamePatternError} If the pathname pattern syntax is invalid.
557
+ * @throws {PathnamePatternError} If the pathname pattern has duplicate parameter names.
558
+ * @throws {MiddlewareError} If a middleware is not a function.
559
+ * @throws {MiddlewareError} If a middleware does not declare exactly two parameters.
560
+ * @example
561
+ * ```js
562
+ * server.head('/users', checkUsers);
563
+ * ```
564
+ */
565
+ head(pathnamePattern, ...middlewares) {
566
+ this.#assertCurrentState(Server.#IDLE_STATE_ERRORS);
567
+ const routeEntry = this.#ensureRouteEntry(pathnamePattern, middlewares);
568
+
569
+ this.#kernel.registerRouteEntry('HEAD', routeEntry.pathnamePattern, routeEntry.middlewares);
570
+
571
+ return this;
572
+ }
573
+
574
+ /**
575
+ * Registers middleware for TRACE requests.
576
+ *
577
+ * @param {PathnamePattern | MiddlewareFunction} pathnamePattern - The pathname pattern to match, or the first middleware function.
578
+ * @param {...MiddlewareFunction} middlewares - The middleware functions to execute for matching TRACE requests.
579
+ * @returns {Server} The server instance.
580
+ * @throws {ServerError} If the server is not in the idle state.
581
+ * @throws {PathnamePatternError} If the pathname pattern is not a string.
582
+ * @throws {PathnamePatternError} If the pathname pattern is empty.
583
+ * @throws {PathnamePatternError} If the pathname pattern exceeds the maximum length.
584
+ * @throws {PathnamePatternError} If the pathname pattern syntax is invalid.
585
+ * @throws {PathnamePatternError} If the pathname pattern has duplicate parameter names.
586
+ * @throws {MiddlewareError} If a middleware is not a function.
587
+ * @throws {MiddlewareError} If a middleware does not declare exactly two parameters.
588
+ * @example
589
+ * ```js
590
+ * server.trace('/debug', traceRequest);
591
+ * ```
592
+ */
593
+ trace(pathnamePattern, ...middlewares) {
594
+ this.#assertCurrentState(Server.#IDLE_STATE_ERRORS);
595
+ const routeEntry = this.#ensureRouteEntry(pathnamePattern, middlewares);
596
+
597
+ this.#kernel.registerRouteEntry('TRACE', routeEntry.pathnamePattern, routeEntry.middlewares);
598
+
599
+ return this;
600
+ }
601
+
602
+ /**
603
+ * Mounts a router instance at a specific pathname prefix.
604
+ *
605
+ * @param {PathnamePattern} pathnamePattern - Pathname prefix where the router is mounted.
606
+ * @param {Router} router - Router instance to mount.
607
+ * @returns {Server} The server instance.
608
+ * @throws {ServerError} If the server is not in the idle state.
609
+ * @throws {PathnamePatternError} If the pathname pattern is not a string.
610
+ * @throws {PathnamePatternError} If the pathname pattern is empty.
611
+ * @throws {PathnamePatternError} If the pathname pattern exceeds the maximum length.
612
+ * @throws {PathnamePatternError} If the pathname pattern syntax is invalid.
613
+ * @throws {PathnamePatternError} If the pathname pattern has duplicate parameter names.
614
+ * @example
615
+ * ```js
616
+ *
617
+ * const api = new Router();
618
+ * api.get('/users', getUsers);
619
+ * api.post('/users', createUser);
620
+ *
621
+ * server.mount('/api', api);
622
+ * ```
623
+ */
624
+ mount(pathnamePattern, router) {
625
+ this.#assertCurrentState(Server.#IDLE_STATE_ERRORS);
626
+ assertPathnamePattern(pathnamePattern);
627
+ assertRouter(router);
628
+ this.#kernel.registerMountEntry(pathnamePattern, router);
629
+ return this;
630
+ }
631
+
632
+ /**
633
+ * Registers a custom handler for requests that do not match any route (404 Not Found).
634
+ *
635
+ * @param {NotFoundHandler} notFoundHandler - The handler function executed on unmatched routes.
636
+ * @returns {Server} The server instance.
637
+ * @throws {ServerError} If called in an invalid state.
638
+ * @throws {ServerError} If the handler is not a function.
639
+ * @throws {ServerError} If the handler does not declare exactly one parameter.
640
+ * @example
641
+ * ```js
642
+ * server.notFound(exchange => {
643
+ * exchange.response.status(404).json({ error: 'Not Found' });
644
+ * });
645
+ * ```
646
+ */
647
+ notFound(notFoundHandler) {
648
+ this.#assertCurrentState(Server.#IDLE_STATE_ERRORS);
649
+ assertNotFoundHandler(notFoundHandler);
650
+ this.#kernel.notFound(notFoundHandler);
651
+ return this;
652
+ }
653
+
654
+ /**
655
+ * Registers a handler for errors raised during request processing.
656
+ *
657
+ * This handler is invoked when an error propagates through the exchange
658
+ * processing pipeline.
659
+ *
660
+ * @param {SystemErrorHandler} systemErrorHandler - The error handling function.
661
+ * @returns {Server} The server instance.
662
+ * @throws {ServerError} If called in an invalid state.
663
+ * @throws {ServerError} If the handler is not a function.
664
+ * @throws {ServerError} If the handler does not declare exactly two parameters.
665
+ * @example
666
+ * ```js
667
+ * server.systemError((error, exchange) => {
668
+ * console.error(error);
669
+ * exchange.response.status(500).json({ error: 'Internal Server Error' });
670
+ * });
671
+ * ```
672
+ */
673
+ systemError(systemErrorHandler) {
674
+ this.#assertCurrentState(Server.#IDLE_STATE_ERRORS);
675
+ assertSystemErrorHandler(systemErrorHandler);
676
+ this.#kernel.systemError(systemErrorHandler);
677
+ return this;
678
+ }
679
+
680
+ /**
681
+ * Starts listening for incoming requests.
682
+ *
683
+ * Transitions the server state from 'idle' to 'starting', and upon success, to 'listening'.
684
+ *
685
+ * @returns {Promise<void>}
686
+ * @throws {ServerError} If the server is not in the idle state, or if startup fails.
687
+ * @example
688
+ * ```js
689
+ * await server.listen();
690
+ * // Server is now listening.
691
+ * ```
692
+ *
693
+ * @example
694
+ * ```js
695
+ * server.onListen(({ endpoint, address, elapsed }) => {
696
+ * console.log(`Server listening at ${endpoint.displayUrl} in ${elapsed}ms`);
697
+ * console.log(`Bound to ${address} (${address.family})`);
698
+ * // Bound to [::1]:3000 (IPv6)
699
+ * });
700
+ *
701
+ * await server.listen();
702
+ * ```
703
+ */
704
+ async listen() {
705
+ this.#assertCurrentState(Server.#LISTEN_STATE_ERRORS);
706
+ this.#transitionTo(Server.#STATES.STARTING);
707
+
708
+ try {
709
+ await this.#kernel.listen();
710
+ this.#transitionTo(Server.#STATES.LISTENING);
711
+ } catch (error) {
712
+ this.#transitionTo(Server.#STATES.ERROR);
713
+ throw error;
714
+ }
715
+ }
716
+
717
+ /**
718
+ * Stops the server and releases associated network resources.
719
+ *
720
+ * Transitions the server state to 'stopping' and then to 'closed'.
721
+ *
722
+ * @returns {Promise<void>}
723
+ * @throws {ServerError} If the server is not listening or shutdown fails.
724
+ * @example
725
+ * ```js
726
+ * // Graceful shutdown
727
+ * process.on('SIGTERM', async () => {
728
+ * await server.close();
729
+ * process.exit(0);
730
+ * });
731
+ * ```
732
+ */
733
+ async close() {
734
+ this.#assertCurrentState(Server.#CLOSE_STATE_ERRORS);
735
+ this.#transitionTo(Server.#STATES.STOPPING);
736
+
737
+ try {
738
+ await this.#kernel.close();
739
+ this.#transitionTo(Server.#STATES.CLOSED);
740
+ } catch (error) {
741
+ this.#transitionTo(Server.#STATES.ERROR);
742
+ throw error;
743
+ }
744
+ }
745
+
746
+ /**
747
+ * @overload
748
+ * @param {'listen'} event - Event name.
749
+ * @param {ListenEventListener} listener - Function invoked when the server starts listening.
750
+ * @returns {Server} The server instance.
751
+ */
752
+ /**
753
+ * @overload
754
+ * @param {'close'} event - Event name.
755
+ * @param {CloseEventListener} listener - Function invoked when the server closes.
756
+ * @returns {Server} The server instance.
757
+ */
758
+ /**
759
+ * @overload
760
+ * @param {'error'} event - Event name.
761
+ * @param {ErrorEventListener} listener - Function invoked when a server-level error occurs.
762
+ * @returns {Server} The server instance.
763
+ */
764
+ /**
765
+ * @overload
766
+ * @param {'exchange:start'} event - Event name.
767
+ * @param {ExchangeStartEventListener} listener - Function invoked during the exchange starts.
768
+ * @returns {Server} The server instance.
769
+ */
770
+ /**
771
+ * @overload
772
+ * @param {'exchange:end'} event - Event name.
773
+ * @param {ExchangeEndEventListener} listener - Function invoked during the exchange ends.
774
+ * @returns {Server} The server instance.
775
+ */
776
+ /**
777
+ * @overload
778
+ * @param {'exchange:error'} event - Event name.
779
+ * @param {ExchangeErrorEventListener} listener - Function invoked when an exchange fails.
780
+ * @returns {Server} The server instance.
781
+ */
782
+ /**
783
+ * Registers a listener for a server event.
784
+ *
785
+ * @param {EventName} event - Event name.
786
+ * @param {EventListener} listener - Function invoked when the event is emitted.
787
+ * @returns {Server} The server instance.
788
+ *
789
+ * @example
790
+ * ```js
791
+ * server.on('listen', ({ endpoint, address, elapsed }) => {
792
+ * console.log(`Server listening at ${endpoint.displayUrl} in ${elapsed}ms`);
793
+ * console.log(`Bound to ${address} (${address.family})`);
794
+ * // Bound to [::1]:3000 (IPv6)
795
+ * });
796
+ * ```
797
+ */
798
+ on(event, listener) {
799
+ this.#kernel.on(event, listener);
800
+ return this;
801
+ }
802
+
803
+ /**
804
+ * @overload
805
+ * @param {'listen'} event - Event name.
806
+ * @param {ListenEventListener} listener - Function invoked when the server starts listening.
807
+ * @returns {Server} The server instance.
808
+ */
809
+ /**
810
+ * @overload
811
+ * @param {'close'} event - Event name.
812
+ * @param {CloseEventListener} listener - Function invoked when the server closes.
813
+ * @returns {Server} The server instance.
814
+ */
815
+ /**
816
+ * @overload
817
+ * @param {'error'} event - Event name.
818
+ * @param {ErrorEventListener} listener - Function invoked when a server-level error occurs.
819
+ * @returns {Server} The server instance.
820
+ */
821
+ /**
822
+ * @overload
823
+ * @param {'exchange:start'} event - Event name.
824
+ * @param {ExchangeStartEventListener} listener - Function invoked during the exchange starts.
825
+ * @returns {Server} The server instance.
826
+ */
827
+ /**
828
+ * @overload
829
+ * @param {'exchange:end'} event - Event name.
830
+ * @param {ExchangeEndEventListener} listener - Function invoked during the exchange ends.
831
+ * @returns {Server} The server instance.
832
+ */
833
+ /**
834
+ * @overload
835
+ * @param {'exchange:error'} event - Event name.
836
+ * @param {ExchangeErrorEventListener} listener - Function invoked when an exchange fails.
837
+ * @returns {Server} The server instance.
838
+ */
839
+ /**
840
+ * Registers a listener that is invoked only once for a server event.
841
+ *
842
+ * @param {EventName} event - Event name.
843
+ * @param {EventListener} listener - Function invoked when the event is emitted.
844
+ * @returns {Server} The server instance.
845
+ *
846
+ * @example
847
+ * ```js
848
+ * server.once('listen', ({ endpoint }) => {
849
+ * console.log(`Server started at ${endpoint.displayUrl}`);
850
+ * });
851
+ * ```
852
+ */
853
+ once(event, listener) {
854
+ this.#kernel.once(event, listener);
855
+ return this;
856
+ }
857
+
858
+ /**
859
+ * @overload
860
+ * @param {'listen'} event - Event name.
861
+ * @param {ListenEventListener} listener - Function invoked when the server starts listening.
862
+ * @returns {Server} The server instance.
863
+ */
864
+ /**
865
+ * @overload
866
+ * @param {'close'} event - Event name.
867
+ * @param {CloseEventListener} listener - Function invoked when the server closes.
868
+ * @returns {Server} The server instance.
869
+ */
870
+ /**
871
+ * @overload
872
+ * @param {'error'} event - Event name.
873
+ * @param {ErrorEventListener} listener - Function invoked when a server-level error occurs.
874
+ * @returns {Server} The server instance.
875
+ */
876
+ /**
877
+ * @overload
878
+ * @param {'exchange:start'} event - Event name.
879
+ * @param {ExchangeStartEventListener} listener - Function invoked during the exchange starts.
880
+ * @returns {Server} The server instance.
881
+ */
882
+ /**
883
+ * @overload
884
+ * @param {'exchange:end'} event - Event name.
885
+ * @param {ExchangeEndEventListener} listener - Function invoked during the exchange ends.
886
+ * @returns {Server} The server instance.
887
+ */
888
+ /**
889
+ * @overload
890
+ * @param {'exchange:error'} event - Event name.
891
+ * @param {ExchangeErrorEventListener} listener - Function invoked when an exchange fails.
892
+ * @returns {Server} The server instance.
893
+ */
894
+ /**
895
+ * Removes a listener from a server event.
896
+ *
897
+ * @param {EventName} event - Event name.
898
+ * @param {EventListener} listener - Listener to remove.
899
+ * @returns {Server} The server instance.
900
+ *
901
+ * @example
902
+ * ```js
903
+ * const listener = () => {
904
+ * console.log('Server started');
905
+ * };
906
+ *
907
+ * server.on('listen', listener);
908
+ * server.off('listen', listener);
909
+ * ```
910
+ */
911
+ off(event, listener) {
912
+ this.#kernel.off(event, listener);
913
+ return this;
914
+ }
915
+
916
+ /**
917
+ * Registers a listener for the `listen` event.
918
+ *
919
+ * @param {(data: ListenEventData) => void} listener - Invoked when the server starts listening.
920
+ * @returns {Server} The server instance.
921
+ *
922
+ * @example
923
+ * ```js
924
+ * server.onListen(({ endpoint, address, elapsed }) => {
925
+ * console.log(`Server listening at ${endpoint.displayUrl} in ${elapsed}ms`);
926
+ * console.log(`Bound to ${address} (${address.family})`);
927
+ * // Bound to [::1]:3000 (IPv6)
928
+ * });
929
+ * ```
930
+ */
931
+ onListen(listener) {
932
+ this.#kernel.on('listen', listener);
933
+ return this;
934
+ }
935
+
936
+ /**
937
+ * Registers a listener for the `close` event.
938
+ *
939
+ * @param {(data: CloseEventData) => void} listener - Invoked when the server closes.
940
+ * @returns {Server} The server instance.
941
+ *
942
+ * @example
943
+ * ```js
944
+ * server.onClose(({ elapsed }) => {
945
+ * console.log(`Server closed in ${elapsed}ms`);
946
+ * });
947
+ * ```
948
+ */
949
+ onClose(listener) {
950
+ this.#kernel.on('close', listener);
951
+ return this;
952
+ }
953
+
954
+ /**
955
+ * Registers a listener for the `error` event.
956
+ *
957
+ * @param {(error: Error) => void} listener - Invoked when a server-level error occurs.
958
+ * @returns {Server} The server instance.
959
+ *
960
+ * @example
961
+ * ```js
962
+ * server.onError(error => {
963
+ * console.error('Server error:', error);
964
+ * });
965
+ * ```
966
+ */
967
+ onError(listener) {
968
+ this.#kernel.on('error', listener);
969
+ return this;
970
+ }
971
+
972
+ /**
973
+ * Registers a listener for the `exchange:start` event.
974
+ *
975
+ * @param {ExchangeStartEventListener} listener - Function invoked when an exchange starts.
976
+ * @returns {Server} The server instance.
977
+ *
978
+ * @example
979
+ * ```js
980
+ * server.onExchangeStart(information => {
981
+ * console.log(
982
+ * information.id,
983
+ * information.request.method,
984
+ * information.request.pathname,
985
+ * );
986
+ * });
987
+ * ```
988
+ */
989
+ onExchangeStart(listener) {
990
+ this.#kernel.on('exchange:start', listener);
991
+ return this;
992
+ }
993
+
994
+ /**
995
+ * Registers a listener for the `exchange:end` event.
996
+ *
997
+ * @param {ExchangeEndEventListener} listener - Function invoked when exchange processing ends.
998
+ * @returns {Server} The server instance.
999
+ *
1000
+ * @example
1001
+ * ```js
1002
+ * server.onExchangeEnd(information => {
1003
+ * console.log(
1004
+ * information.outcome, // 'success' | 'error'
1005
+ * information.timing.duration, // number
1006
+ * );
1007
+ * });
1008
+ * ```
1009
+ */
1010
+ onExchangeEnd(listener) {
1011
+ this.#kernel.on('exchange:end', listener);
1012
+ return this;
1013
+ }
1014
+
1015
+ /**
1016
+ * Registers a listener for the `exchange:error` event.
1017
+ *
1018
+ * @param {ExchangeErrorEventListener} listener - Function invoked when an exchange fails.
1019
+ * @returns {Server} The server instance.
1020
+ *
1021
+ * @example
1022
+ * ```js
1023
+ * server.onExchangeError(information => {
1024
+ * console.error(
1025
+ * information.id,
1026
+ * information.error,
1027
+ * information.request.pathname,
1028
+ * );
1029
+ * });
1030
+ * ```
1031
+ */
1032
+ onExchangeError(listener) {
1033
+ this.#kernel.on('exchange:error', listener);
1034
+ return this;
1035
+ }
1036
+
1037
+ /**
1038
+ * Asynchronously disposes of the server resources.
1039
+ *
1040
+ * Enables the use of the `await using` statement to automatically
1041
+ * close the server when it goes out of scope.
1042
+ *
1043
+ * @returns {Promise<void>}
1044
+ * @async
1045
+ * @example
1046
+ * ```js
1047
+ * async function main() {
1048
+ * await using server = new Server(configuration);
1049
+ * await server.listen();
1050
+ * // ... use server ...
1051
+ * } // ← `Symbol.asyncDispose` runs here.
1052
+ * ```
1053
+ */
1054
+ async [Symbol.asyncDispose]() {
1055
+ if (this.#state === Server.#STATES.LISTENING || this.#state === Server.#STATES.STARTING) {
1056
+ await this.close();
1057
+ }
1058
+ }
1059
+
1060
+ /**
1061
+ * Validates that the current state allows an operation.
1062
+ *
1063
+ * @private
1064
+ * @param {Map<ServerState, string>} errorStates - Map containing invalid states and associated error codes.
1065
+ * @throws {ServerError} If the current state is restricted.
1066
+ */
1067
+ #assertCurrentState(errorStates) {
1068
+ const identifier = errorStates.get(this.#state);
1069
+ if (identifier !== undefined) {
1070
+ throw new ServerError(identifier);
1071
+ }
1072
+ }
1073
+
1074
+ /**
1075
+ * Performs a safe state transition.
1076
+ *
1077
+ * @private
1078
+ * @param {ServerState} nextState - Target lifecycle state.
1079
+ */
1080
+ #transitionTo(nextState) {
1081
+ this.#assertTransition(nextState);
1082
+ this.#state = nextState;
1083
+ }
1084
+
1085
+ /**
1086
+ * Validates whether a state transition is permitted.
1087
+ *
1088
+ * @private
1089
+ * @param {ServerState} nextState - Target lifecycle state.
1090
+ * @throws {ServerError} If the transition is disallowed by the state machine.
1091
+ */
1092
+ #assertTransition(nextState) {
1093
+ const transitions = Server.#TRANSITIONS.get(this.#state);
1094
+
1095
+ if (!transitions.has(nextState)) {
1096
+ throw new ServerError('SERVER_INVALID_STATE_TRANSITION', {
1097
+ values: {
1098
+ from: this.#state,
1099
+ to: nextState,
1100
+ },
1101
+ });
1102
+ }
1103
+ }
1104
+
1105
+ #ensureRouteEntry(pathnamePattern, middlewares) {
1106
+ if (typeof pathnamePattern === 'string') {
1107
+ assertPathnamePattern(pathnamePattern);
1108
+ assertMiddlewares(middlewares);
1109
+
1110
+ return {
1111
+ pathnamePattern,
1112
+ middlewares,
1113
+ };
1114
+ }
1115
+
1116
+ const normalizedMiddlewares = [pathnamePattern, ...middlewares];
1117
+
1118
+ assertMiddlewares(normalizedMiddlewares);
1119
+
1120
+ return {
1121
+ pathnamePattern: undefined,
1122
+ middlewares: normalizedMiddlewares,
1123
+ };
1124
+ }
1125
+
1126
+ /**
1127
+ * Enumeration of server lifecycle states.
1128
+ *
1129
+ * @private
1130
+ * @readonly
1131
+ * @enum {ServerState}
1132
+ */
1133
+ static #STATES = Object.freeze({
1134
+ IDLE: 'idle',
1135
+ STARTING: 'starting',
1136
+ LISTENING: 'listening',
1137
+ STOPPING: 'stopping',
1138
+ CLOSED: 'closed',
1139
+ ERROR: 'error',
1140
+ });
1141
+
1142
+ /**
1143
+ * Allowed state transition rules map.
1144
+ *
1145
+ * @private
1146
+ * @type {Map<ServerState, Set<ServerState>>}
1147
+ */
1148
+ static #TRANSITIONS = new Map([
1149
+ [Server.#STATES.IDLE, new Set([Server.#STATES.STARTING])],
1150
+ [Server.#STATES.STARTING, new Set([Server.#STATES.LISTENING, Server.#STATES.ERROR])],
1151
+ [Server.#STATES.LISTENING, new Set([Server.#STATES.STOPPING, Server.#STATES.ERROR])],
1152
+ [Server.#STATES.STOPPING, new Set([Server.#STATES.CLOSED, Server.#STATES.ERROR])],
1153
+ [Server.#STATES.CLOSED, new Set()],
1154
+ [Server.#STATES.ERROR, new Set()],
1155
+ ]);
1156
+
1157
+ /**
1158
+ * Error mappings for configuration methods when server is busy or unready.
1159
+ *
1160
+ * @private
1161
+ * @type {Map<ServerState, string>}
1162
+ */
1163
+ static #IDLE_STATE_ERRORS = new Map([
1164
+ [Server.#STATES.STARTING, 'SERVER_ALREADY_STARTING'],
1165
+ [Server.#STATES.LISTENING, 'SERVER_ALREADY_LISTENING'],
1166
+ [Server.#STATES.STOPPING, 'SERVER_STOPPING'],
1167
+ [Server.#STATES.CLOSED, 'SERVER_CLOSED'],
1168
+ [Server.#STATES.ERROR, 'SERVER_ERROR'],
1169
+ ]);
1170
+
1171
+ /**
1172
+ * Error mappings when attempting to call listen() in an invalid state.
1173
+ *
1174
+ * @private
1175
+ * @type {Map<ServerState, string>}
1176
+ */
1177
+ static #LISTEN_STATE_ERRORS = new Map([
1178
+ [Server.#STATES.STARTING, 'SERVER_ALREADY_STARTING'],
1179
+ [Server.#STATES.LISTENING, 'SERVER_ALREADY_LISTENING'],
1180
+ [Server.#STATES.STOPPING, 'SERVER_STOPPING'],
1181
+ [Server.#STATES.CLOSED, 'SERVER_CLOSED'],
1182
+ [Server.#STATES.ERROR, 'SERVER_ERROR'],
1183
+ ]);
1184
+
1185
+ /**
1186
+ * Error mappings when attempting to call close() in an invalid state.
1187
+ *
1188
+ * @private
1189
+ * @type {Map<ServerState, string>}
1190
+ */
1191
+ static #CLOSE_STATE_ERRORS = new Map([
1192
+ [Server.#STATES.IDLE, 'SERVER_NOT_LISTENING'],
1193
+ [Server.#STATES.STARTING, 'SERVER_NOT_LISTENING'],
1194
+ [Server.#STATES.STOPPING, 'SERVER_ALREADY_STOPPING'],
1195
+ [Server.#STATES.CLOSED, 'SERVER_CLOSED'],
1196
+ [Server.#STATES.ERROR, 'SERVER_ERROR'],
1197
+ ]);
1198
+ }
1199
+
1200
+ // ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1201
+ export default Server;