@boo-labs/server 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +229 -2
  2. package/index.mjs +23 -0
  3. package/package.json +71 -4
  4. package/source/application/Kernel/index.mjs +1137 -0
  5. package/source/application/Server/index.mjs +1201 -0
  6. package/source/application/configurations/Http2CleartextServerConfiguration/index.mjs +168 -0
  7. package/source/application/configurations/Http2SecureServerConfiguration/index.mjs +172 -0
  8. package/source/application/configurations/HttpServerConfiguration/index.mjs +169 -0
  9. package/source/application/configurations/HttpsServerConfiguration/index.mjs +172 -0
  10. package/source/application/configurations/ServerConfiguration/Options/CORSOptions/CORSOptionsValue.mjs +158 -0
  11. package/source/application/configurations/ServerConfiguration/Options/CORSOptions/index.mjs +87 -0
  12. package/source/application/configurations/ServerConfiguration/Options/CSRFOptions/CSRFOptionsValue.mjs +178 -0
  13. package/source/application/configurations/ServerConfiguration/Options/CSRFOptions/index.mjs +80 -0
  14. package/source/application/configurations/ServerConfiguration/Options/CompressOptions/CompressOptionsValue.mjs +126 -0
  15. package/source/application/configurations/ServerConfiguration/Options/CompressOptions/index.mjs +81 -0
  16. package/source/application/configurations/ServerConfiguration/Options/CookieParserOptions/CookieParserOptionsValue.mjs +107 -0
  17. package/source/application/configurations/ServerConfiguration/Options/CookieParserOptions/index.mjs +82 -0
  18. package/source/application/configurations/ServerConfiguration/Options/CookieSessionOptions/CookieSessionOptionsValue.mjs +182 -0
  19. package/source/application/configurations/ServerConfiguration/Options/CookieSessionOptions/index.mjs +86 -0
  20. package/source/application/configurations/ServerConfiguration/Options/ListenOptions/ListenOptionsValue.mjs +95 -0
  21. package/source/application/configurations/ServerConfiguration/Options/ListenOptions/index.mjs +47 -0
  22. package/source/application/configurations/ServerConfiguration/Options/MiddlewareOption.mjs +102 -0
  23. package/source/application/configurations/ServerConfiguration/Options/Options.mjs +105 -0
  24. package/source/application/configurations/ServerConfiguration/Options/RateLimitOptions/RateLimitOptionsValue.mjs +164 -0
  25. package/source/application/configurations/ServerConfiguration/Options/RateLimitOptions/index.mjs +84 -0
  26. package/source/application/configurations/ServerConfiguration/Options/RequestOptions/RequestOptionsValue.mjs +108 -0
  27. package/source/application/configurations/ServerConfiguration/Options/RequestOptions/index.mjs +47 -0
  28. package/source/application/configurations/ServerConfiguration/Options/ResponseOptions/ResponseOptionsValue.mjs +101 -0
  29. package/source/application/configurations/ServerConfiguration/Options/ResponseOptions/index.mjs +47 -0
  30. package/source/application/configurations/ServerConfiguration/Options/SecurityHeadersOptions/SecurityHeadersOptionsValue.mjs +272 -0
  31. package/source/application/configurations/ServerConfiguration/Options/SecurityHeadersOptions/index.mjs +85 -0
  32. package/source/application/configurations/ServerConfiguration/Options/ServerOption.mjs +50 -0
  33. package/source/application/configurations/ServerConfiguration/Options/StaticOptions/StaticOptionsValue.mjs +164 -0
  34. package/source/application/configurations/ServerConfiguration/Options/StaticOptions/index.mjs +89 -0
  35. package/source/application/configurations/ServerConfiguration/index.mjs +781 -0
  36. package/source/application/resolvers/TransportResolver/index.mjs +119 -0
  37. package/source/application/use-cases/CloseServer/index.mjs +85 -0
  38. package/source/application/use-cases/ListenServer/index.mjs +98 -0
  39. package/source/core/EventSystem/index.mjs +110 -0
  40. package/source/core/Exchange/ExchangeContext.mjs +291 -0
  41. package/source/core/Exchange/Telemetry.mjs +193 -0
  42. package/source/core/Exchange/index.mjs +604 -0
  43. package/source/core/ExchangeLayerBuilder/index.mjs +133 -0
  44. package/source/core/IPCMain/IPCMain.mjs +507 -0
  45. package/source/core/IPCMain/IPCProtocol.mjs +441 -0
  46. package/source/core/IPCMain/IPCServerChannel.mjs +331 -0
  47. package/source/core/IPCMain/WebSocketAdapter.mjs +189 -0
  48. package/source/core/Request/BodyParser/BodyParser.mjs +197 -0
  49. package/source/core/Request/BodyParser/JSONParser.mjs +70 -0
  50. package/source/core/Request/BodyParser/MultipartParser.mjs +612 -0
  51. package/source/core/Request/BodyParser/URLEncodedParser.mjs +71 -0
  52. package/source/core/Request/RequestHeaders.mjs +177 -0
  53. package/source/core/Request/index.mjs +805 -0
  54. package/source/core/Response/ResponseHeaders.mjs +402 -0
  55. package/source/core/Response/index.mjs +1370 -0
  56. package/source/core/middlewares/CORSMiddleware/index.mjs +289 -0
  57. package/source/core/middlewares/CSRFMiddleware/index.mjs +284 -0
  58. package/source/core/middlewares/CompressMiddleware/index.mjs +460 -0
  59. package/source/core/middlewares/CookieParserMiddleware/index.mjs +194 -0
  60. package/source/core/middlewares/CookieSessionMiddleware/index.mjs +460 -0
  61. package/source/core/middlewares/RateLimitMiddleware/index.mjs +245 -0
  62. package/source/core/middlewares/SecurityHeadersMiddleware/index.mjs +151 -0
  63. package/source/core/middlewares/StaticMiddleware/index.mjs +628 -0
  64. package/source/core/routing/ExchangeLayer/ExchangeMatcher.mjs +127 -0
  65. package/source/core/routing/ExchangeLayer/index.mjs +254 -0
  66. package/source/core/routing/Pipeline/index.mjs +157 -0
  67. package/source/core/routing/Router/Route.mjs +184 -0
  68. package/source/core/routing/Router/index.mjs +523 -0
  69. package/source/core/routing/RouterRegister/OptionsMiddleware.mjs +147 -0
  70. package/source/core/routing/RouterRegister/index.mjs +150 -0
  71. package/source/core/routing/RouterSystem/index.mjs +225 -0
  72. package/source/core/value-objects/Address/index.mjs +281 -0
  73. package/source/core/value-objects/Endpoint/index.mjs +293 -0
  74. package/source/index.mjs +62 -0
  75. package/source/infrastructure/adapters/Http2Adapter/index.mjs +144 -0
  76. package/source/infrastructure/adapters/Http2CleartextAdapter/index.mjs +147 -0
  77. package/source/infrastructure/adapters/HttpAdapter/index.mjs +124 -0
  78. package/source/infrastructure/adapters/HttpsAdapter/index.mjs +124 -0
  79. package/source/infrastructure/adapters/TransportAdapter/createWebSocketOpenRequest.mjs +843 -0
  80. package/source/infrastructure/adapters/TransportAdapter/index.mjs +555 -0
  81. package/source/shared/Identifier/index.mjs +671 -0
  82. package/source/shared/constants/symbols/brands/index.mjs +45 -0
  83. package/source/shared/domain/UUIDV7/index.mjs +346 -0
  84. package/source/shared/errors/BaseError/index.mjs +157 -0
  85. package/source/shared/errors/IPCError/index.mjs +97 -0
  86. package/source/shared/errors/MiddlewareError/index.mjs +106 -0
  87. package/source/shared/errors/PathnamePatternError/index.mjs +103 -0
  88. package/source/shared/errors/RequestError/index.mjs +116 -0
  89. package/source/shared/errors/ResponseError/index.mjs +140 -0
  90. package/source/shared/errors/RouterError/index.mjs +90 -0
  91. package/source/shared/errors/ServerConfigurationError/index.mjs +551 -0
  92. package/source/shared/errors/ServerError/index.mjs +127 -0
  93. package/source/shared/errors/WebSocketError/index.mjs +58 -0
  94. package/source/shared/outcome/ErrorSchema.mjs +361 -0
  95. package/source/shared/outcome/Result/index.mjs +239 -0
  96. package/source/shared/outcome/Validation/index.mjs +208 -0
  97. package/source/shared/validations/Middleware/index.mjs +160 -0
  98. package/source/shared/validations/PathnamePattern/index.mjs +256 -0
  99. package/source/shared/validations/Server/NotFoundHandler/index.mjs +94 -0
  100. package/source/shared/validations/Server/Router/index.mjs +68 -0
  101. package/source/shared/validations/Server/ServerConfiguration/index.mjs +68 -0
  102. package/source/shared/validations/Server/SystemErrorHandler/index.mjs +94 -0
  103. package/source/shared/validations/ServerConfiguration/CORSOptions/index.mjs +310 -0
  104. package/source/shared/validations/ServerConfiguration/CSRFOptions/index.mjs +299 -0
  105. package/source/shared/validations/ServerConfiguration/CompressOptions/index.mjs +204 -0
  106. package/source/shared/validations/ServerConfiguration/CookieParserOptions/index.mjs +102 -0
  107. package/source/shared/validations/ServerConfiguration/CookieSessionOptions/index.mjs +332 -0
  108. package/source/shared/validations/ServerConfiguration/ListenOptions/index.mjs +178 -0
  109. package/source/shared/validations/ServerConfiguration/RateLimitOptions/index.mjs +229 -0
  110. package/source/shared/validations/ServerConfiguration/RequestOptions/index.mjs +132 -0
  111. package/source/shared/validations/ServerConfiguration/ResponseOptions/index.mjs +122 -0
  112. package/source/shared/validations/ServerConfiguration/SecurityHeadersOptions/index.mjs +137 -0
  113. package/source/shared/validations/ServerConfiguration/StaticOptions/index.mjs +248 -0
  114. package/types/index.d.ts +719 -0
@@ -0,0 +1,127 @@
1
+ // @ts-check
2
+ /**
3
+ * @file Defines the request matching pattern used by the HTTP routing layer.
4
+ *
5
+ * @description Provides the ExchangeMatcher class for evaluating whether
6
+ * incoming HTTP requests satisfy configured routing criteria using the native
7
+ * URLPattern API.
8
+ *
9
+ * @module ExchangeMatcher
10
+ */
11
+
12
+ // ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
13
+ /**
14
+ * Represents a single HTTP transaction.
15
+ *
16
+ * @typedef {import('#Exchange').default} Exchange
17
+ */
18
+
19
+ /**
20
+ * Represents an HTTP method supported by the routing layer.
21
+ *
22
+ * @typedef {'GET'|'HEAD'|'POST'|'PUT'|'PATCH'|'DELETE'|'OPTIONS'|'QUERY'|'TRACE'} HTTPMethod
23
+ */
24
+
25
+ /**
26
+ * Defines the criteria used to match an HTTP exchange.
27
+ *
28
+ * @typedef {object} ExchangeMatcherOptions
29
+ * @property {HTTPMethod|undefined} method - HTTP method to match. When undefined, any HTTP method is accepted.
30
+ * @property {string|undefined} pathnamePattern - URL pathname pattern to match. When undefined, any pathname is accepted.
31
+ */
32
+
33
+ // ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
34
+ /**
35
+ * Represents a request matching pattern.
36
+ *
37
+ * @classdesc ExchangeMatcher defines matching criteria composed of an
38
+ * optional HTTP method and a pathname pattern.
39
+ *
40
+ * When the pathnamePattern is `undefined`, the pattern is considered global
41
+ * and matches every pathname. If a method is specified, the method must still
42
+ * match.
43
+ */
44
+ class ExchangeMatcher {
45
+ /**
46
+ * HTTP method used as a matching criterion.
47
+ *
48
+ * @type {HTTPMethod|undefined}
49
+ */
50
+ #method;
51
+
52
+ /**
53
+ * Native URLPattern instance used to evaluate the pathname criterion.
54
+ *
55
+ * @type {URLPattern}
56
+ */
57
+ #pattern;
58
+
59
+ /**
60
+ * Indicates whether the matcher accepts every pathname.
61
+ *
62
+ * @type {boolean}
63
+ */
64
+ #isGlobal;
65
+
66
+ /**
67
+ * Creates an instance of ExchangeMatcher.
68
+ *
69
+ * @param {ExchangeMatcherOptions} options - Matching criteria.
70
+ * @example
71
+ * ```js
72
+ * const matcher = new ExchangeMatcher({
73
+ * method: 'GET',
74
+ * pathnamePattern: '/users',
75
+ * });
76
+ * ```
77
+ * @example
78
+ * ```js
79
+ * const matcher = new ExchangeMatcher({
80
+ * method: undefined,
81
+ * pathnamePattern: undefined,
82
+ * });
83
+ * ```
84
+ */
85
+ constructor({ method, pathnamePattern }) {
86
+ this.#method = method;
87
+ this.#isGlobal = pathnamePattern === undefined;
88
+ this.#pattern = new URLPattern({ pathname: this.#isGlobal ? '*' : pathnamePattern });
89
+ }
90
+
91
+ /**
92
+ * Tests whether the specified exchange matches this pattern.
93
+ *
94
+ * Evaluates the HTTP request method and pathname from the provided Exchange object
95
+ * against the configured matching criteria to determine if this layer should handle it.
96
+ *
97
+ * @param {Exchange} exchange - The exchange object to evaluate.
98
+ * @returns {boolean} `true` when the exchange matches the configured criteria; otherwise, `false`.
99
+ * @example
100
+ * ```js
101
+ * const matcher = new ExchangeMatcher({
102
+ * method: 'POST',
103
+ * pathnamePattern: '/api/users',
104
+ * });
105
+ *
106
+ * matcher.test(exchange); // true if exchange is POST /api/users
107
+ * ```
108
+ */
109
+ test(exchange) {
110
+ const method = exchange.request.method;
111
+
112
+ if (this.#method && this.#method !== method.toUpperCase()) {
113
+ return false;
114
+ }
115
+
116
+ if (this.#isGlobal) {
117
+ return true;
118
+ }
119
+
120
+ const pathname = exchange.request.pathname;
121
+
122
+ return this.#pattern.test({ pathname });
123
+ }
124
+ }
125
+
126
+ // ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
127
+ export default ExchangeMatcher;
@@ -0,0 +1,254 @@
1
+ // @ts-check
2
+ /**
3
+ * @file Defines the HTTP exchange execution layer.
4
+ *
5
+ * @description Provides the ExchangeLayer class for matching exchanges against
6
+ * HTTP routing criteria and executing the associated middleware.
7
+ *
8
+ * @module ExchangeLayer
9
+ */
10
+
11
+ // ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
12
+ // » IMPORT NATIVE NODEJS MODULES
13
+ import { inspect } from 'node:util';
14
+
15
+ // » IMPORT LOCAL MODULES
16
+ import ExchangeMatcher from './ExchangeMatcher.mjs';
17
+
18
+ // ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
19
+ /**
20
+ * Represents a single HTTP transaction.
21
+ *
22
+ * @typedef {import('#Exchange').default} Exchange
23
+ */
24
+
25
+ /**
26
+ * Represents an HTTP method supported by the routing layer.
27
+ *
28
+ * @typedef {'GET'|'HEAD'|'POST'|'PUT'|'PATCH'|'DELETE'|'OPTIONS'|'QUERY'|'TRACE'} HTTPMethod
29
+ */
30
+
31
+ /**
32
+ * Represents a function that delegates execution to the next
33
+ * middleware layer.
34
+ *
35
+ * @callback NextFunction
36
+ * @returns {void|Promise<void>}
37
+ */
38
+
39
+ /**
40
+ * Represents an exchange middleware function.
41
+ *
42
+ * @callback MiddlewareFunction
43
+ * @param {Exchange} exchange - Current HTTP exchange.
44
+ * @param {NextFunction} next - The function to call the next layer.
45
+ * @returns {void|Promise<void>}
46
+ */
47
+
48
+ /**
49
+ * Defines the criteria and middleware used by an exchange layer.
50
+ *
51
+ * @typedef {object} ExchangeLayerOptions
52
+ * @property {HTTPMethod|undefined} method - HTTP method to match. When undefined, any HTTP method is accepted.
53
+ * @property {string|undefined} pathnamePattern - URL pathname pattern to match. When undefined, any pathname is accepted.
54
+ * @property {MiddlewareFunction} middleware - Middleware executed when the exchange matches the layer criteria.
55
+ */
56
+
57
+ /**
58
+ * Represents the plain object representation of an exchange layer.
59
+ *
60
+ * @typedef {object} ExchangeLayerObject
61
+ * @property {HTTPMethod|undefined} method - HTTP method used by the layer.
62
+ * @property {string|undefined} pathnamePattern - URL pathname pattern used by the layer.
63
+ * @property {MiddlewareFunction} middleware - Middleware executed by the layer.
64
+ */
65
+
66
+ // ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
67
+ /**
68
+ * Represents an executable layer for HTTP exchange processing.
69
+ *
70
+ * @classdesc Associates middleware with matching criteria based on an HTTP
71
+ * method and pathname pattern.
72
+ *
73
+ * The layer delegates matching to an ExchangeMatcher and execution to
74
+ * the middleware provided during construction.
75
+ */
76
+ class ExchangeLayer {
77
+ /**
78
+ * The HTTP method used by the layer.
79
+ *
80
+ * @type {HTTPMethod|undefined}
81
+ */
82
+ #method;
83
+
84
+ /**
85
+ * The pathname pattern used by the layer.
86
+ *
87
+ * @type {string|undefined}
88
+ */
89
+ #pathnamePattern;
90
+
91
+ /**
92
+ * The matcher used to evaluate incoming exchanges.
93
+ *
94
+ * @type {ExchangeMatcher}
95
+ */
96
+ #matcher;
97
+
98
+ /**
99
+ * The middleware executed when the exchange matches the layer criteria.
100
+ *
101
+ * @type {MiddlewareFunction}
102
+ */
103
+ #middleware;
104
+
105
+ /**
106
+ * Creates an ExchangeLayer.
107
+ *
108
+ * @param {ExchangeLayerOptions} options - Configuration used to define the exchange layer.
109
+ * @example
110
+ * ```js
111
+ * const authLayer = new ExchangeLayer({
112
+ * method: undefined,
113
+ * pathnamePattern: '/api/v1/users',
114
+ * middleware: authMiddleware,
115
+ * });
116
+ * ```
117
+ */
118
+ constructor({ method, pathnamePattern, middleware }) {
119
+ this.#matcher = new ExchangeMatcher({ method, pathnamePattern });
120
+ this.#method = method;
121
+ this.#pathnamePattern = pathnamePattern;
122
+ this.#middleware = middleware;
123
+ }
124
+
125
+ /**
126
+ * The HTTP method used by the layer.
127
+ *
128
+ * @type {HTTPMethod|undefined}
129
+ */
130
+ get method() {
131
+ return this.#method;
132
+ }
133
+
134
+ /**
135
+ * The pathname pattern used by the layer.
136
+ *
137
+ * @type {string|undefined}
138
+ */
139
+ get pathnamePattern() {
140
+ return this.#pathnamePattern;
141
+ }
142
+
143
+ /**
144
+ * Custom string tag for better type identification.
145
+ *
146
+ * @readonly
147
+ * @type {string}
148
+ * @example
149
+ * ```js
150
+ * const exchangeLayer = new ExchangeLayer({...});
151
+ *
152
+ * console.log(Object.prototype.toString.call(exchangeLayer));
153
+ * // '[object ExchangeLayer]'
154
+ * console.log(exchangeLayer[Symbol.toStringTag]);
155
+ * // 'ExchangeLayer'
156
+ * ```
157
+ */
158
+ get [Symbol.toStringTag]() {
159
+ return this.constructor.name;
160
+ }
161
+
162
+ /**
163
+ * Handles the exchange. If constraints match, executes the middleware.
164
+ * Otherwise, delegates to the next layer in the pipeline.
165
+ *
166
+ * @param {Exchange} exchange - The current exchange.
167
+ * @param {NextFunction} next - The function to call the next layer.
168
+ * @returns {Promise<void>} The result of calling the next layer or the completion of the middleware execution.
169
+ * @example
170
+ * ```js
171
+ * await layer.handle(exchange, next);
172
+ * ```
173
+ */
174
+ async handle(exchange, next) {
175
+ if (!this.#matcher.test(exchange)) {
176
+ return next();
177
+ }
178
+ await this.#middleware(exchange, next);
179
+ }
180
+
181
+ /**
182
+ * Converts the `ExchangeLayer` instance to a plain JavaScript object.
183
+ *
184
+ * @returns {ExchangeLayerObject} A plain object representation of the exchange layer.
185
+ * @example
186
+ * ```js
187
+ * const usersLayer = new ExchangeLayer({
188
+ * method: 'GET',
189
+ * pathnamePattern: '/api/users/:id',
190
+ * middleware: usersRepository.find,
191
+ * });
192
+ *
193
+ * console.log(usersLayer.toObject());
194
+ * // {
195
+ * // method: 'GET',
196
+ * // pathnamePattern: '/api/users/:id',
197
+ * // middleware: [AsyncFunction: middleware]
198
+ * // }
199
+ * ```
200
+ */
201
+ toObject() {
202
+ return Object.freeze({
203
+ method: this.method,
204
+ pathnamePattern: this.pathnamePattern,
205
+ middleware: this.#middleware,
206
+ });
207
+ }
208
+
209
+ /**
210
+ * Converts the `ExchangeLayer` instance to a plain JavaScript object.
211
+ *
212
+ * @returns {ExchangeLayerObject} A plain object representation of the exchange layer.
213
+ * @example
214
+ * ```js
215
+ * const usersLayer = new ExchangeLayer({
216
+ * method: 'DELETE',
217
+ * pathnamePattern: '/api/users/:id',
218
+ * middleware: usersRepository.delete,
219
+ * });
220
+ *
221
+ * console.log(JSON.stringify(usersLayer));
222
+ * // '{"method":"DELETE","pathnamePattern":"/api/users/:id"}'
223
+ * ```
224
+ */
225
+ toJSON() {
226
+ return this.toObject();
227
+ }
228
+
229
+ /**
230
+ * Custom inspect implementation for Node.js `util.inspect()`.
231
+ *
232
+ * @returns {string} Custom string representation for debugging.
233
+ * @example
234
+ * ```js
235
+ * const authLayer = new ExchangeLayer({
236
+ * method: undefined,
237
+ * pathnamePattern: '/api/login',
238
+ * middleware: authMiddleware,
239
+ * });
240
+ *
241
+ * console.log(authLayer);
242
+ * // ExchangeLayer {
243
+ * // method: undefined,
244
+ * // pathnamePattern: '/api/login',
245
+ * // middleware: [AsyncFunction: middleware]
246
+ * // }
247
+ * ```
248
+ */
249
+ [inspect.custom]() {
250
+ return `${this.constructor.name} ${inspect(this.toObject(), { colors: true })}`;
251
+ }
252
+ }
253
+ // ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
254
+ export default ExchangeLayer;
@@ -0,0 +1,157 @@
1
+ // @ts-check
2
+ /**
3
+ * @file Defines the Pipeline execution component for HTTP exchanges.
4
+ *
5
+ * @description Manages and executes an ordered chain of middleware layers.
6
+ * It acts as the core dispatch mechanism, handling control flow and safe
7
+ * delegation through `next()`. It is transport-agnostic.
8
+ *
9
+ * @module Pipeline
10
+ */
11
+
12
+ // ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
13
+ /**
14
+ * Represents an executable layer for HTTP exchange processing.
15
+ *
16
+ * @typedef {import('#routing/ExchangeLayer').default} ExchangeLayer
17
+ */
18
+
19
+ /**
20
+ * Represents an exchange middleware function.
21
+ *
22
+ * @typedef {import('#routing/ExchangeLayer').MiddlewareFunction} MiddlewareFunction
23
+ */
24
+
25
+ /**
26
+ * Represents a single HTTP transaction.
27
+ *
28
+ * @typedef {import('#Exchange').default} Exchange
29
+ */
30
+
31
+ /**
32
+ * Represents the fallback handler invoked when no layer resolves
33
+ * the exchange.
34
+ *
35
+ * @typedef {(exchange: Exchange) => void | Promise<void>} NotFoundHandler
36
+ */
37
+
38
+ // ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
39
+ /**
40
+ * Represents an ordered processing pipeline for HTTP exchanges.
41
+ *
42
+ * @classdesc Stores processing layers and executes their handlers in
43
+ * registration order. Each layer is responsible for determining whether
44
+ * it applies to the current exchange.
45
+ */
46
+ class Pipeline {
47
+ /**
48
+ * Stores the middleware layer instances in execution order.
49
+ *
50
+ * @type {ExchangeLayer[]}
51
+ */
52
+ #layers = [];
53
+
54
+ /**
55
+ * Appends a single middleware layer to the end of the execution chain.
56
+ *
57
+ * @param {ExchangeLayer} layer - The middleware layer instance to add.
58
+ * @returns {Pipeline} The current pipeline instance for method chaining.
59
+ * @example
60
+ * ```js
61
+ * const layer = new ExchangeLayer({
62
+ * method: undefined,
63
+ * pathnamePattern: '/api/v1/users',
64
+ * middleware: authMiddleware
65
+ * });
66
+ *
67
+ * pipeline.add(layer);
68
+ * ```
69
+ */
70
+ add(layer) {
71
+ this.#layers.push(layer);
72
+ return this;
73
+ }
74
+
75
+ /**
76
+ * Appends multiple middleware layers to the execution chain at once.
77
+ *
78
+ * This is particularly useful when mounting an entire router or a
79
+ * predefined set of middleware into the main server pipeline.
80
+ *
81
+ * @param {ExchangeLayer[]} layers - The middleware layer instances to add.
82
+ * @returns {Pipeline} The current pipeline instance for method chaining.
83
+ * @example
84
+ * ```js
85
+ * const routerLayers = [
86
+ * new ExchangeLayer({
87
+ * method: undefined,
88
+ * pathnamePattern: '/api/v1/users',
89
+ * middleware: authMiddleware
90
+ * }),
91
+ * new ExchangeLayer({
92
+ * method: 'GET',
93
+ * pathnamePattern: '/api/v1/users',
94
+ * middleware: listUsers
95
+ * }),
96
+ * ];
97
+ * pipeline.addBatch(routerLayers);
98
+ * ```
99
+ */
100
+ addBatch(layers) {
101
+ this.#layers.push(...layers);
102
+ return this;
103
+ }
104
+
105
+ /**
106
+ * Executes the middleware pipeline for a given exchange.
107
+ *
108
+ * Layers are processed sequentially in registration order. Each matching
109
+ * layer receives the exchange and a `next()` function that delegates
110
+ * execution to the following layer. If no layer resolves the exchange,
111
+ * the `notFoundHandler` is invoked.
112
+ *
113
+ * @param {Exchange} exchange - The current exchange.
114
+ * @param {NotFoundHandler} notFoundHandler - The fallback async function to execute, if no layer resolves the exchange.
115
+ * @returns {Promise<void>} Resolves when the processing chain completes.
116
+ * @throws {Error} If a middleware layer calls `next()` more than once.
117
+ * @example
118
+ * ```js
119
+ * await pipeline.execute(exchange, async exchange => {
120
+ * exchange.response.status(404).json({ error: 'Not Found' });
121
+ * });
122
+ * ```
123
+ */
124
+ async execute(exchange, notFoundHandler) {
125
+ const called = new Set();
126
+
127
+ /**
128
+ * Dispatches middleware execution by index.
129
+ *
130
+ * @private
131
+ * @param {number} index - The current layer index in the pipeline.
132
+ * @returns {Promise<void>}
133
+ */
134
+ const dispatch = async index => {
135
+ if (index >= this.#layers.length) {
136
+ await notFoundHandler(exchange);
137
+ } else {
138
+ const layer = this.#layers[index];
139
+
140
+ const next = async () => {
141
+ if (called.has(index)) {
142
+ throw new Error(`next() called multiple times at pipeline index ${index}.`);
143
+ }
144
+ called.add(index);
145
+ await dispatch(index + 1);
146
+ };
147
+
148
+ await layer.handle(exchange, next);
149
+ }
150
+ };
151
+
152
+ await dispatch(0);
153
+ }
154
+ }
155
+
156
+ // ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
157
+ export default Pipeline;