@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,119 @@
1
+ // @ts-check
2
+ /**
3
+ * @file Defines the transport resolver.
4
+ *
5
+ * @description Resolves the transport adapter constructor associated with a
6
+ * supported transport identifier.
7
+ *
8
+ * @module TransportResolver
9
+ */
10
+
11
+ // ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
12
+ // » IMPORT LOCAL MODULES
13
+ import HttpAdapter from '#adapters/HttpAdapter';
14
+ import HttpsAdapter from '#adapters/HttpsAdapter';
15
+ import Http2Adapter from '#adapters/Http2Adapter';
16
+ import Http2CleartextAdapter from '#adapters/Http2CleartextAdapter';
17
+
18
+ // ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
19
+ /**
20
+ * Maps each transport identifier to its concrete adapter type.
21
+ *
22
+ * @typedef {object} TransportRegistry
23
+ * @property {HttpAdapter} h1 - HTTP/1.1 over cleartext TCP.
24
+ * @property {HttpsAdapter} h1s - HTTP/1.1 over TLS.
25
+ * @property {Http2Adapter} h2 - HTTP/2 over TLS.
26
+ * @property {Http2CleartextAdapter} h2c - HTTP/2 over cleartext TCP.
27
+ */
28
+
29
+ /**
30
+ * Identifies the transport implementation handled by an adapter.
31
+ *
32
+ * @typedef {keyof TransportRegistry} Transport
33
+ */
34
+
35
+ /**
36
+ * Generic contract satisfied by every concrete adapter in the registry.
37
+ *
38
+ * @typedef {TransportRegistry[Transport]} TransportAdapterContract
39
+ */
40
+
41
+ /**
42
+ * Represents a transport adapter capable of managing a permitted transport.
43
+ *
44
+ * @typedef {import('#configurations/ServerConfiguration').ServerConfigurationIdentity} ServerConfigurationIdentity
45
+ */
46
+
47
+ /**
48
+ * Represents a transport adapter capable of managing a permitted transport.
49
+ *
50
+ * @typedef {import('#configurations/ServerConfiguration').default<ServerConfigurationIdentity>} ServerConfiguration
51
+ */
52
+
53
+ /**
54
+ * Constructor for a transport adapter that accepts optional transport
55
+ * configuration.
56
+ *
57
+ * @template {Transport} T
58
+ * @typedef {new (options: object|undefined) => TransportRegistry[T]} TransportAdapterConstructor
59
+ */
60
+
61
+ // ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
62
+ /**
63
+ * Resolves transport adapter constructors from transport identifiers.
64
+ *
65
+ * @class
66
+ */
67
+ class TransportResolver {
68
+ /**
69
+ * Prevents direct instantiation of the resolver.
70
+ *
71
+ * @abstract
72
+ * @throws {Error} Always thrown because `TransportResolver` exposes only static behavior.
73
+ */
74
+ constructor() {
75
+ throw new Error('TransportResolver is a static class and cannot be instantiated');
76
+ }
77
+
78
+ /**
79
+ * Resolves the adapter constructor associated with a transport.
80
+ *
81
+ * @param {Transport} transport - Identifier of the transport to resolve.
82
+ * @returns {TransportAdapterConstructor<Transport>} The transport adapter constructor.
83
+ * @throws {Error} When the transport is not supported by this implementation.
84
+ * @example
85
+ * ```js
86
+ * const Adapter = TransportResolver.resolve('h1');
87
+ * const adapter = new Adapter(configuration);
88
+ * ```
89
+ */
90
+ static resolve(transport) {
91
+ const Adapter = TransportResolver.#adapters.get(transport);
92
+
93
+ if (!Adapter) {
94
+ throw new Error(`Unsupported transport: ${transport}`);
95
+ }
96
+
97
+ return Adapter;
98
+ }
99
+
100
+ /**
101
+ * @type {Array<[Transport, TransportAdapterConstructor<Transport>]>}
102
+ */
103
+ static #ADAPTERS = [
104
+ ['h1', HttpAdapter],
105
+ ['h1s', HttpsAdapter],
106
+ ['h2', Http2Adapter],
107
+ ['h2c', Http2CleartextAdapter],
108
+ ];
109
+
110
+ /**
111
+ * Transport adapter constructors indexed by transport identifier.
112
+ *
113
+ * @type {Map<Transport, TransportAdapterConstructor<Transport>>}
114
+ */
115
+ static #adapters = new Map(TransportResolver.#ADAPTERS);
116
+ }
117
+
118
+ // ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
119
+ export default TransportResolver;
@@ -0,0 +1,85 @@
1
+ // @ts-check
2
+ /**
3
+ * @file Defines the CloseServer application use case.
4
+ *
5
+ * @description CloseServer is responsible for executing the server shutdown
6
+ * operation through an injected server.
7
+ *
8
+ * It delegates the underlying shutdown process to the server and remains
9
+ * independent from any concrete transport implementation.
10
+ *
11
+ * @module CloseServer
12
+ */
13
+
14
+ // ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
15
+ /**
16
+ * Represents a permitted transport identity.
17
+ *
18
+ * @typedef {import('#adapters/TransportAdapter').TransportIdentity} TransportIdentity
19
+ */
20
+
21
+ /**
22
+ * Represents a transport adapter capable of managing a permitted transport.
23
+ *
24
+ * @typedef {import('#adapters/TransportAdapter').default<TransportIdentity>} TransportAdapter
25
+ */
26
+
27
+ /**
28
+ * Callback invoked after the server has closed.
29
+ *
30
+ * @callback OnCloseHandler
31
+ * @param {Error} [error] - Error reported if the server could not be closed.
32
+ * @returns {void}
33
+ */
34
+
35
+ // ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
36
+ /**
37
+ * Application use case for stopping the server.
38
+ *
39
+ * @classdesc Executes the server shutdown operation through an injected server.
40
+ *
41
+ * This class does not manage server state transitions or depend on any
42
+ * concrete transport implementation.
43
+ */
44
+ class CloseServer {
45
+ /**
46
+ * The server instance whose shutdown operation this use case executes.
47
+ *
48
+ * @type {TransportAdapter}
49
+ */
50
+ #server;
51
+
52
+ /**
53
+ * Creates a new CloseServer use case.
54
+ *
55
+ * @param {TransportAdapter} server - The server used to execute the shutdown operation.
56
+ * @example
57
+ * ```js
58
+ * const closeServer = new CloseServer(new HttpAdapter({
59
+ * keepAliveTimeout: 65000
60
+ * }));
61
+ * ```
62
+ */
63
+ constructor(server) {
64
+ this.#server = server;
65
+ }
66
+
67
+ /**
68
+ * Executes the server shutdown operation.
69
+ *
70
+ * Delegates the shutdown operation to the configured server.
71
+ *
72
+ * @param {OnCloseHandler} [onCloseHandler] - Callback invoked by Node.js once the server has been successfully closed.
73
+ * @returns {Promise<void>} Resolves once the shutdown operation has completed.
74
+ * @example
75
+ * ```js
76
+ * await closeServer.execute(onCloseHandler);
77
+ * ```
78
+ */
79
+ async execute(onCloseHandler) {
80
+ await this.#server.close(onCloseHandler);
81
+ }
82
+ }
83
+
84
+ // ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
85
+ export default CloseServer;
@@ -0,0 +1,98 @@
1
+ // @ts-check
2
+ /**
3
+ * @file Defines the ListenServer application use case.
4
+ *
5
+ * @description ListenServer is responsible for executing the server listening
6
+ * operation through an injected server.
7
+ *
8
+ * It delegates the underlying network binding process to the server and
9
+ * remains independent from any concrete transport implementation.
10
+ *
11
+ * @module ListenServer
12
+ */
13
+
14
+ // ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
15
+ /**
16
+ * Represents a permitted transport identity.
17
+ *
18
+ * @typedef {import('#adapters/TransportAdapter').TransportIdentity} TransportIdentity
19
+ */
20
+
21
+ /**
22
+ * Represents a transport adapter capable of managing a permitted transport.
23
+ *
24
+ * @typedef {import('#adapters/TransportAdapter').default<TransportIdentity>} TransportAdapter
25
+ */
26
+
27
+ /**
28
+ * Network configuration used to start the server.
29
+ *
30
+ * @typedef {object} ListenOptions
31
+ * @property {number} [port] - The TCP port on which the server listens.
32
+ * @property {string} [host] - The hostname or IP address on which the server listens.
33
+ * @property {number} [backlog] - The maximum number of pending connections that may be queued while the operating system is waiting to accept them.
34
+ */
35
+
36
+ /**
37
+ * Callback invoked when the server has successfully started listening.
38
+ *
39
+ * @callback OnListenHandler
40
+ * @returns {void}
41
+ */
42
+
43
+ // ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
44
+ /**
45
+ * Application use case for starting the server.
46
+ *
47
+ * @classdesc Executes the server listening operation through an injected server.
48
+ *
49
+ * This class does not manage server state transitions or depend on any
50
+ * concrete transport implementation.
51
+ */
52
+ class ListenServer {
53
+ /**
54
+ * The server instance whose listening operation.
55
+ *
56
+ * @type {TransportAdapter}
57
+ */
58
+ #server;
59
+
60
+ /**
61
+ * Creates a new ListenServer use case.
62
+ *
63
+ * @param {TransportAdapter} server - The server responsible for executing the underlying listening operation.
64
+ * @example
65
+ * ```js
66
+ * const listenServer = new ListenServer(new HttpAdapter({
67
+ * keepAliveTimeout: 65000
68
+ * }));
69
+ * ```
70
+ */
71
+ constructor(server) {
72
+ this.#server = server;
73
+ }
74
+
75
+ /**
76
+ * Executes the server listening operation.
77
+ *
78
+ * Delegates the listening operation to the configured server.
79
+ *
80
+ * @param {ListenOptions} [options] - Network configuration used to start the server.
81
+ * @param {OnListenHandler} [onListenHandler] - Callback invoked once the server has successfully started listening.
82
+ * @returns {Promise<void>} Resolves once the listening operation has been initiated.
83
+ * @example
84
+ * ```js
85
+ * await listenServer.execute({
86
+ * port: process.env.PORT || 8080,
87
+ * host: '0.0.0.0',
88
+ * backlog: 511,
89
+ * }, onListenHandler);
90
+ * ```
91
+ */
92
+ async execute(options, onListenHandler) {
93
+ await this.#server.listen(options, onListenHandler);
94
+ }
95
+ }
96
+
97
+ // ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
98
+ export default ListenServer;
@@ -0,0 +1,110 @@
1
+ // @ts-check
2
+ /**
3
+ * @file Defines the server event system.
4
+ *
5
+ * @description Provides the event bus used by the application runtime to
6
+ * register listeners and emit server lifecycle and exchange events.
7
+ *
8
+ * @module EventSystem
9
+ */
10
+
11
+ // ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
12
+ // » IMPORT NATIVE NODEJS MODULES
13
+ import EventEmitter from 'node:events';
14
+
15
+ // ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
16
+ /**
17
+ * Identifies an event by its string or symbol name.
18
+ *
19
+ * @typedef {string|symbol} EventName
20
+ */
21
+
22
+ /**
23
+ * Represents a listener registered for a kernel event.
24
+ *
25
+ * @typedef {(data?: any) => void} EventListener
26
+ */
27
+
28
+ // ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
29
+ /**
30
+ * Provides event management for the server runtime.
31
+ *
32
+ * @classdesc EventSystem encapsulates the underlying event emitter and exposes
33
+ * the event operations required by the application layer.
34
+ */
35
+ class EventSystem {
36
+ #events = new EventEmitter();
37
+
38
+ /**
39
+ * Registers a listener for a server event.
40
+ *
41
+ * @param {EventName} event - Event name.
42
+ * @param {EventListener} listener - Function invoked when the event is emitted.
43
+ * @returns {EventSystem} The current event system instance.
44
+ * @example
45
+ * ```js
46
+ * eventSystem.on('listen', ({ endpoint }) => {
47
+ * console.log(`Server listening at ${endpoint}`);
48
+ * });
49
+ * ```
50
+ */
51
+ on(event, listener) {
52
+ this.#events.on(event, listener);
53
+
54
+ return this;
55
+ }
56
+
57
+ /**
58
+ * Registers a listener that is invoked only once for a server event.
59
+ *
60
+ * @param {EventName} event - Event name.
61
+ * @param {EventListener} listener - Function invoked when the event is emitted.
62
+ * @returns {EventSystem} The current event system instance.
63
+ * @example
64
+ * ```js
65
+ * eventSystem.once('close', () => {
66
+ * console.log('Server closed');
67
+ * });
68
+ * ```
69
+ */
70
+ once(event, listener) {
71
+ this.#events.once(event, listener);
72
+
73
+ return this;
74
+ }
75
+
76
+ /**
77
+ * Removes a listener from a server event.
78
+ *
79
+ * @param {EventName} event - Event name.
80
+ * @param {EventListener} listener - Listener to remove.
81
+ * @returns {EventSystem} The current event system instance.
82
+ * @example
83
+ * ```js
84
+ * eventSystem.off('error', handleError);
85
+ * ```
86
+ */
87
+ off(event, listener) {
88
+ this.#events.off(event, listener);
89
+
90
+ return this;
91
+ }
92
+
93
+ /**
94
+ * Emits a server event.
95
+ *
96
+ * @param {EventName} event - Event name.
97
+ * @param {unknown} [eventData] - Optional data associated with the event.
98
+ * @returns {boolean} True if the event had listeners, false otherwise.
99
+ * @example
100
+ * ```js
101
+ * eventSystem.emit('exchange', exchange);
102
+ * ```
103
+ */
104
+ emit(event, eventData) {
105
+ return this.#events.emit(event, eventData);
106
+ }
107
+ }
108
+
109
+ // ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
110
+ export default EventSystem;
@@ -0,0 +1,291 @@
1
+ // @ts-check
2
+ /**
3
+ * @file Defines the internal execution context of an HTTP exchange.
4
+ *
5
+ * @description The context groups runtime-only data associated with a
6
+ * single transaction: telemetry, cancellation signal, and correlation
7
+ * identifiers. It is private to `Exchange` and is never exposed to the
8
+ * consumer.
9
+ *
10
+ * The context does not access the native HTTP message. Any HTTP-derived
11
+ * information it needs (currently only the W3C `traceparent` header) is
12
+ * extracted by `Exchange` and passed in at construction time.
13
+ *
14
+ * The outgoing `traceparent` represents the server's own span: the incoming
15
+ * `traceId` is preserved when present, a new `spanId` is generated, and the
16
+ * incoming `trace-flags` are preserved to avoid altering the sampling
17
+ * decision taken upstream.
18
+ *
19
+ * @module ExchangeContext
20
+ */
21
+
22
+ // ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
23
+ // » IMPORT NATIVE NODEJS MODULES
24
+ import { randomBytes } from 'node:crypto';
25
+
26
+ // » IMPORT LOCAL MODULES
27
+ import Telemetry from './Telemetry.mjs';
28
+ import UUIDV7 from '#shared/domain/UUIDV7';
29
+
30
+ // ━━ CONSTANTS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
31
+ /**
32
+ * W3C Trace Context version supported by this implementation.
33
+ *
34
+ * @type {string}
35
+ */
36
+ const TRACEPARENT_VERSION = '00';
37
+
38
+ /**
39
+ * Default trace flags used when no incoming `traceparent` is available.
40
+ *
41
+ * @type {string}
42
+ */
43
+ const DEFAULT_TRACE_FLAGS = '01';
44
+
45
+ /**
46
+ * Pattern matching a W3C `traceparent` header value.
47
+ *
48
+ * @type {RegExp}
49
+ */
50
+ const TRACEPARENT_PATTERN = /^([0-9a-f]{2})-([0-9a-f]{32})-([0-9a-f]{16})-([0-9a-f]{2})$/i;
51
+
52
+ /**
53
+ * Represents an all-zero trace identifier, which is invalid per W3C.
54
+ *
55
+ * @type {string}
56
+ */
57
+ const INVALID_TRACE_ID = '0'.repeat(32);
58
+
59
+ // ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
60
+ /**
61
+ * Options used to construct an exchange context.
62
+ *
63
+ * @typedef {object} ExchangeContextOptions
64
+ * @property {string} [traceparent] - W3C `traceparent` header value received with the request.
65
+ */
66
+
67
+ /**
68
+ * Represents the trace information extracted from a W3C `traceparent`.
69
+ *
70
+ * @typedef {object} TraceContext
71
+ * @property {string} traceId - The 32-character hexadecimal trace identifier.
72
+ * @property {string} traceFlags - The 2-character hexadecimal trace flags.
73
+ */
74
+
75
+ // ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
76
+ /**
77
+ * Groups runtime-only data associated with a single HTTP exchange.
78
+ *
79
+ * @classdesc The context holds telemetry, cancellation signal, and
80
+ * correlation identifiers. It is created by `Exchange` and never exposed
81
+ * to the consumer.
82
+ */
83
+ class ExchangeContext {
84
+ /**
85
+ * Cancellation controller for the transaction.
86
+ *
87
+ * @type {AbortController}
88
+ */
89
+ #controller;
90
+
91
+ /**
92
+ * Telemetry tracker for the transaction.
93
+ *
94
+ * @type {Telemetry}
95
+ */
96
+ #telemetry;
97
+
98
+ /**
99
+ * Unique identifier for the transaction.
100
+ *
101
+ * @type {string}
102
+ */
103
+ #id;
104
+
105
+ /**
106
+ * W3C trace identifier.
107
+ *
108
+ * @type {string}
109
+ */
110
+ #traceId;
111
+
112
+ /**
113
+ * W3C span identifier.
114
+ *
115
+ * @type {string}
116
+ */
117
+ #spanId;
118
+
119
+ /**
120
+ * W3C trace flags.
121
+ *
122
+ * @type {string}
123
+ */
124
+ #traceFlags;
125
+
126
+ /**
127
+ * Creates an exchange context.
128
+ *
129
+ * @param {ExchangeContextOptions} [options] - The context options.
130
+ * @example
131
+ * ```js
132
+ * const context = new ExchangeContext({ traceparent: '00-...-...-01' });
133
+ * ```
134
+ */
135
+ constructor(options = {}) {
136
+ this.#controller = new AbortController();
137
+ this.#telemetry = new Telemetry();
138
+ this.#id = UUIDV7.create();
139
+
140
+ const trace = ExchangeContext.#extractTrace(options.traceparent);
141
+
142
+ this.#traceId = trace?.traceId ?? ExchangeContext.#generateTraceId();
143
+ this.#traceFlags = trace?.traceFlags ?? DEFAULT_TRACE_FLAGS;
144
+ this.#spanId = ExchangeContext.#generateSpanId();
145
+ }
146
+
147
+ /**
148
+ * The cancellation signal for the transaction.
149
+ *
150
+ * @type {AbortSignal}
151
+ */
152
+ get signal() {
153
+ return this.#controller.signal;
154
+ }
155
+
156
+ /**
157
+ * The telemetry tracker for the transaction.
158
+ *
159
+ * @type {Telemetry}
160
+ */
161
+ get telemetry() {
162
+ return this.#telemetry;
163
+ }
164
+
165
+ /**
166
+ * The unique identifier for the transaction.
167
+ *
168
+ * @type {string}
169
+ */
170
+ get id() {
171
+ return this.#id;
172
+ }
173
+
174
+ /**
175
+ * The W3C trace identifier for the transaction.
176
+ *
177
+ * @type {string}
178
+ */
179
+ get traceId() {
180
+ return this.#traceId;
181
+ }
182
+
183
+ /**
184
+ * The W3C span identifier for the transaction.
185
+ *
186
+ * @type {string}
187
+ */
188
+ get spanId() {
189
+ return this.#spanId;
190
+ }
191
+
192
+ /**
193
+ * Monotonic timestamp captured when the exchange processing context was created.
194
+ *
195
+ * @type {number}
196
+ */
197
+ get startedAt() {
198
+ return this.#telemetry.startedAt;
199
+ }
200
+
201
+ /**
202
+ * Aborts the transaction.
203
+ *
204
+ * Signals the associated cancellation signal. Callers of `signal` will
205
+ * receive the `abort` event. Subsequent calls have no effect.
206
+ *
207
+ * @returns {void}
208
+ * @example
209
+ * ```js
210
+ * context.abort();
211
+ * ```
212
+ */
213
+ abort() {
214
+ this.#controller.abort();
215
+ }
216
+
217
+ /**
218
+ * Returns the W3C `traceparent` header value for this transaction.
219
+ *
220
+ * The value represents the server's own span: it preserves the incoming
221
+ * `traceId` and `trace-flags` when present, and always carries a new
222
+ * `spanId`.
223
+ *
224
+ * @returns {string} The formatted `traceparent` header value.
225
+ * @example
226
+ * ```js
227
+ * response.setHeader('traceparent', context.traceparent());
228
+ * ```
229
+ */
230
+ traceparent() {
231
+ return `${TRACEPARENT_VERSION}-${this.#traceId}-${this.#spanId}-${this.#traceFlags}`;
232
+ }
233
+
234
+ /**
235
+ * Extracts the trace information from a W3C `traceparent` header value.
236
+ *
237
+ * Returns `null` when the value is missing, malformed, uses a version
238
+ * not supported by this implementation, or carries an all-zero trace
239
+ * identifier.
240
+ *
241
+ * @param {string|undefined} value - The `traceparent` header value.
242
+ * @returns {TraceContext|null} The extracted trace information, or `null`.
243
+ */
244
+ static #extractTrace(value) {
245
+ if (typeof value !== 'string') {
246
+ return null;
247
+ }
248
+
249
+ const match = value.match(TRACEPARENT_PATTERN);
250
+
251
+ if (!match) {
252
+ return null;
253
+ }
254
+
255
+ if (match[1].toLowerCase() !== TRACEPARENT_VERSION) {
256
+ return null;
257
+ }
258
+
259
+ const traceId = match[2].toLowerCase();
260
+
261
+ if (traceId === INVALID_TRACE_ID) {
262
+ return null;
263
+ }
264
+
265
+ return {
266
+ traceId,
267
+ traceFlags: match[4].toLowerCase(),
268
+ };
269
+ }
270
+
271
+ /**
272
+ * Generates a 32-character lowercase hexadecimal trace identifier.
273
+ *
274
+ * @returns {string} The generated trace identifier.
275
+ */
276
+ static #generateTraceId() {
277
+ return randomBytes(16).toString('hex');
278
+ }
279
+
280
+ /**
281
+ * Generates a 16-character lowercase hexadecimal span identifier.
282
+ *
283
+ * @returns {string} The generated span identifier.
284
+ */
285
+ static #generateSpanId() {
286
+ return randomBytes(8).toString('hex');
287
+ }
288
+ }
289
+
290
+ // ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
291
+ export default ExchangeContext;