@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,184 @@
1
+ // @ts-check
2
+ /**
3
+ * @file Defines a route grouped by pathname pattern.
4
+ *
5
+ * @description Stores middleware registered for a pathname pattern, grouped by
6
+ * HTTP method. Middleware registered with `all()` applies to every method
7
+ * registered for the route.
8
+ *
9
+ * When `compose()` is called, the route returns its middleware as exchange
10
+ * layer options.
11
+ *
12
+ * @module Route
13
+ */
14
+
15
+ // ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
16
+ /**
17
+ * Represents a single HTTP transaction.
18
+ *
19
+ * @typedef {import('#Exchange').default} Exchange
20
+ */
21
+
22
+ /**
23
+ * Represents an HTTP method supported by the routing layer.
24
+ *
25
+ * @typedef {'GET'|'HEAD'|'POST'|'PUT'|'PATCH'|'DELETE'|'OPTIONS'|'QUERY'|'TRACE'} HTTPMethod
26
+ */
27
+
28
+ /**
29
+ * Represents a function that delegates execution to the next middleware layer.
30
+ *
31
+ * @typedef {() => void | Promise<void>} NextFunction
32
+ */
33
+
34
+ /**
35
+ * Represents an exchange middleware function.
36
+ *
37
+ * @typedef {(exchange: Exchange, next: NextFunction) => void | Promise<void>} MiddlewareFunction
38
+ */
39
+
40
+ /**
41
+ * Represents the options used to create an exchange layer.
42
+ *
43
+ * @typedef {object} ExchangeLayerOptions
44
+ * @property {HTTPMethod} method - HTTP method to match. When undefined, any HTTP method is accepted.
45
+ * @property {string} pathnamePattern - URL pathname pattern to match. When undefined, any pathname is accepted.
46
+ * @property {MiddlewareFunction} middleware - Middleware executed when the exchange matches the layer criteria.
47
+ */
48
+
49
+ // ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
50
+ /**
51
+ * Groups middleware definitions by pathname pattern.
52
+ *
53
+ * A route can contain middleware that applies to all registered methods and
54
+ * middleware specific to individual HTTP methods.
55
+ *
56
+ * @classdesc `Route` stores middleware for a single pathname pattern and
57
+ * composes the registered middleware into exchange layer options.
58
+ * */
59
+ class Route {
60
+ /**
61
+ * Stores the pathname pattern associated with the route.
62
+ *
63
+ * @type {string}
64
+ */
65
+ #pathnamePattern;
66
+
67
+ /**
68
+ * Stores middleware applied to all registered methods.
69
+ *
70
+ * @type {MiddlewareFunction[]}
71
+ */
72
+ #all = [];
73
+
74
+ /**
75
+ * Stores middleware grouped by HTTP method.
76
+ *
77
+ * @type {Map<HTTPMethod, MiddlewareFunction[]>}
78
+ */
79
+ #methods = new Map();
80
+
81
+ /**
82
+ * Creates a route for a pathname pattern.
83
+ *
84
+ * @param {string} pathnamePattern - The pathname pattern associated with the route.
85
+ * @example
86
+ * ```js
87
+ * const route = new Route('/users');
88
+ * ```
89
+ */
90
+ constructor(pathnamePattern) {
91
+ this.#pathnamePattern = pathnamePattern;
92
+ }
93
+
94
+ /**
95
+ * Lists the HTTP methods registered for the route.
96
+ *
97
+ * @type {HTTPMethod[]}
98
+ */
99
+ get methods() {
100
+ return [...this.#methods.keys()];
101
+ }
102
+
103
+ /**
104
+ * Registers middleware applied to all methods in the route.
105
+ *
106
+ * @param {...MiddlewareFunction} middlewares - The middleware functions to execute for all registered methods.
107
+ * @example
108
+ * ```js
109
+ * route.all(authenticate);
110
+ * ```
111
+ */
112
+ all(...middlewares) {
113
+ this.#all.push(...middlewares);
114
+ }
115
+
116
+ /**
117
+ * Registers middleware for a specific HTTP method.
118
+ *
119
+ * @param {HTTPMethod} method - The HTTP method.
120
+ * @param {MiddlewareFunction[]} middlewares - The middleware functions to execute for the method.
121
+ * @example
122
+ * ```js
123
+ * route.add('GET', [getUsers]);
124
+ * ```
125
+ */
126
+ add(method, middlewares) {
127
+ this.#methods.set(method, middlewares);
128
+ }
129
+
130
+ /**
131
+ * Composes the registered middleware into exchange layer options.
132
+ *
133
+ * Middleware registered with `all()` is emitted before middleware specific
134
+ * to each HTTP method.
135
+ *
136
+ * @returns {ExchangeLayerOptions[]} The exchange layer options.
137
+ * @example
138
+ * ```js
139
+ * route.add('GET', [getUsers]);
140
+ * route.add('POST', [createUser]);
141
+ *
142
+ * const definitions = route.compose();
143
+ * ```
144
+ */
145
+ compose() {
146
+ return this.methods.flatMap(method => [
147
+ ...this.#getAll(method),
148
+ ...this.#getMethodMiddlewares(method),
149
+ ]);
150
+ }
151
+
152
+ /**
153
+ * Builds exchange layer options for middleware registered with `all()`.
154
+ *
155
+ * @param {HTTPMethod} method - The HTTP method the options are emitted for.
156
+ * @returns {ExchangeLayerOptions[]} The exchange layer options for all-method middleware.
157
+ */
158
+ #getAll(method) {
159
+ return this.#all.map(middleware => ({
160
+ method,
161
+ pathnamePattern: this.#pathnamePattern,
162
+ middleware,
163
+ }));
164
+ }
165
+
166
+ /**
167
+ * Builds exchange layer options for middleware registered for a specific method.
168
+ *
169
+ * @param {HTTPMethod} method - The HTTP method whose options are emitted.
170
+ * @returns {ExchangeLayerOptions[]} The exchange layer options for the method.
171
+ */
172
+ #getMethodMiddlewares(method) {
173
+ const methodMiddlewares = this.#methods.get(method) ?? [];
174
+
175
+ return methodMiddlewares.map(middleware => ({
176
+ method,
177
+ pathnamePattern: this.#pathnamePattern,
178
+ middleware,
179
+ }));
180
+ }
181
+ }
182
+
183
+ // ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
184
+ export default Route;
@@ -0,0 +1,523 @@
1
+ // @ts-check
2
+ /**
3
+ * @file Defines the HTTP route builder.
4
+ *
5
+ * @description Provides a fluent interface for registering middleware functions
6
+ * for HTTP methods and pathname patterns.
7
+ *
8
+ * Each instance maintains route definitions grouped by pathname pattern. A
9
+ * pathname pattern can have one definition per HTTP method, and each definition
10
+ * can contain multiple middleware functions.
11
+ *
12
+ * When `compose()` is called, the router returns the registered exchange layer
13
+ * options.
14
+ *
15
+ * @module Router
16
+ */
17
+
18
+ // ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
19
+ // » IMPORT LOCAL MODULES
20
+ import { ROUTER_BRAND } from '#shared/constants/symbols/brands';
21
+ import { assertPathnamePattern } from '#shared/validations/PathnamePattern';
22
+ import { assertMiddlewares } from '#shared/validations/Middleware';
23
+ import RouterError from '#shared/errors/RouterError';
24
+ import Route from './Route.mjs';
25
+
26
+ // ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
27
+ /**
28
+ * Represents a single HTTP transaction.
29
+ *
30
+ * @typedef {import('#Exchange').default} Exchange
31
+ */
32
+
33
+ /**
34
+ * This error type provides identifiable failures related to middleware validation.
35
+ *
36
+ * @typedef {import('#shared/errors/MiddlewareError').default} MiddlewareError
37
+ */
38
+
39
+ /**
40
+ * This error type provides identifiable failures related to pathname pattern syntax and validation.
41
+ *
42
+ * @typedef {import('#shared/errors/PathnamePatternError').default} PathnamePatternError
43
+ */
44
+
45
+ /**
46
+ * Represents an HTTP method supported by the routing layer.
47
+ *
48
+ * @typedef {import('#routing/ExchangeLayer').HTTPMethod} HTTPMethod
49
+ */
50
+
51
+ /**
52
+ * Represents an exchange middleware function.
53
+ *
54
+ * @typedef {import('#routing/ExchangeLayer').MiddlewareFunction} MiddlewareFunction
55
+ */
56
+
57
+ /**
58
+ * Represents the options used to create an exchange layer.
59
+ *
60
+ * @typedef {object} ExchangeLayerOptions
61
+ * @property {HTTPMethod} method - HTTP method to match.
62
+ * @property {string} pathnamePattern - URL pathname pattern to match.
63
+ * @property {MiddlewareFunction} middleware - Middleware executed when the exchange matches the layer criteria.
64
+ */
65
+
66
+ /**
67
+ * Represents the result produced by composing a router.
68
+ *
69
+ * @typedef {object} RouterComposition
70
+ * @property {MiddlewareFunction[]} middlewares - Middleware applied to all routes.
71
+ * @property {ExchangeLayerOptions[]} layersOptions - Exchange layer options compiled from the registered routes.
72
+ */
73
+ // ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
74
+ /**
75
+ * Defines HTTP middleware pipelines by pathname pattern and method.
76
+ *
77
+ * @classdesc `Router` provides a fluent API for registering middleware for HTTP
78
+ * methods. Each pathname pattern can have one definition per HTTP method, and
79
+ * multiple middleware functions can be registered for each definition.
80
+ *
81
+ * When `compose()` is called, the router returns the registered exchange layer
82
+ * options.
83
+ */
84
+ class Router {
85
+ /**
86
+ * Stores middleware applied to all routes in the router.
87
+ *
88
+ * @type {MiddlewareFunction[]}
89
+ */
90
+ #middlewares = [];
91
+
92
+ /**
93
+ * Stores routes grouped by pathname pattern.
94
+ *
95
+ * @type {Map<string, Route>}
96
+ */
97
+ #routes;
98
+
99
+ /**
100
+ * Stores registered methods and pathname patterns.
101
+ *
102
+ * @type {Set<string>}
103
+ */
104
+ #registrations;
105
+
106
+ /**
107
+ * Creates an instance of Router.
108
+ *
109
+ * @example
110
+ * ```js
111
+ * const router = new Router();
112
+ *
113
+ * router
114
+ * .get('/users', authenticate, getUsers)
115
+ * .post('/users', authenticate, createUser)
116
+ * .get('/posts/:postId', getPost);
117
+ * ```
118
+ */
119
+ constructor() {
120
+ this.#routes = new Map();
121
+ this.#registrations = new Set();
122
+ }
123
+
124
+ /**
125
+ * Identifies the object as a router.
126
+ *
127
+ * This branded property provides a stable way to recognize objects that
128
+ * implement the router contract without depending on their concrete
129
+ * implementation.
130
+ *
131
+ * @type {boolean}
132
+ */
133
+ get [ROUTER_BRAND]() {
134
+ return true;
135
+ }
136
+
137
+ /**
138
+ * Registers middleware for all routes in the router.
139
+ *
140
+ * @param {...MiddlewareFunction} middlewares - The middleware functions to execute for all routes.
141
+ * @returns {Router} The router instance.
142
+ * @throws {MiddlewareError} If a middleware is not a function.
143
+ * @throws {MiddlewareError} If a middleware does not declare exactly two parameters.
144
+ * @example
145
+ * ```js
146
+ * router
147
+ * .use(authenticate)
148
+ * .get('/users', getUsers)
149
+ * .post('/users', createUser);
150
+ * ```
151
+ */
152
+ use(...middlewares) {
153
+ this.#assertMiddlewares(middlewares);
154
+ this.#middlewares.push(...middlewares);
155
+ return this;
156
+ }
157
+
158
+ /**
159
+ * Registers middleware for all HTTP methods.
160
+ *
161
+ * @param {string} pathnamePattern - The pathname pattern to match.
162
+ * @param {...MiddlewareFunction} middlewares - The middleware functions to execute for all matching HTTP requests.
163
+ * @returns {Router} The router instance.
164
+ * @throws {RouterError} If middleware has already been registered for the pathname pattern with `all()`.
165
+ * @throws {PathnamePatternError} If the pathname pattern is not a string.
166
+ * @throws {PathnamePatternError} If the pathname pattern is empty.
167
+ * @throws {PathnamePatternError} If the pathname pattern exceeds the maximum length.
168
+ * @throws {PathnamePatternError} If the pathname pattern syntax is invalid.
169
+ * @throws {PathnamePatternError} If the pathname pattern has duplicate parameter names.
170
+ * @throws {MiddlewareError} If a middleware is not a function.
171
+ * @throws {MiddlewareError} If a middleware does not declare exactly two parameters.
172
+ * @example
173
+ * ```js
174
+ * router
175
+ * .all('/users', authenticate)
176
+ * .get('/users', getUsers)
177
+ * .post('/users', createUser);
178
+ * ```
179
+ */
180
+ all(pathnamePattern, ...middlewares) {
181
+ this.#addAll(pathnamePattern, middlewares);
182
+ return this;
183
+ }
184
+
185
+ /**
186
+ * Registers middleware for GET requests.
187
+ *
188
+ * @param {string} pathnamePattern - The pathname pattern to match.
189
+ * @param {...MiddlewareFunction} middlewares - The middleware functions to execute for matching GET requests.
190
+ * @returns {Router} The router instance.
191
+ * @throws {RouterError} If GET middleware has already been registered for the pathname pattern.
192
+ * @throws {PathnamePatternError} If the pathname pattern is not a string.
193
+ * @throws {PathnamePatternError} If the pathname pattern is empty.
194
+ * @throws {PathnamePatternError} If the pathname pattern exceeds the maximum length.
195
+ * @throws {PathnamePatternError} If the pathname pattern syntax is invalid.
196
+ * @throws {PathnamePatternError} If the pathname pattern has duplicate parameter names.
197
+ * @throws {MiddlewareError} If a middleware is not a function.
198
+ * @throws {MiddlewareError} If a middleware does not declare exactly two parameters.
199
+ * @example
200
+ * ```js
201
+ * router.get('/users', getUsers);
202
+ * ```
203
+ */
204
+ get(pathnamePattern, ...middlewares) {
205
+ this.#addRoute('GET', pathnamePattern, middlewares);
206
+ return this;
207
+ }
208
+
209
+ /**
210
+ * Registers middleware for QUERY requests.
211
+ *
212
+ * @param {string} pathnamePattern - The pathname pattern to match.
213
+ * @param {...MiddlewareFunction} middlewares - The middleware functions to execute for matching QUERY requests.
214
+ * @returns {Router} The router instance.
215
+ * @throws {RouterError} If QUERY middleware has already been registered for the pathname pattern.
216
+ * @throws {PathnamePatternError} If the pathname pattern is not a string.
217
+ * @throws {PathnamePatternError} If the pathname pattern is empty.
218
+ * @throws {PathnamePatternError} If the pathname pattern exceeds the maximum length.
219
+ * @throws {PathnamePatternError} If the pathname pattern syntax is invalid.
220
+ * @throws {PathnamePatternError} If the pathname pattern has duplicate parameter names.
221
+ * @throws {MiddlewareError} If a middleware is not a function.
222
+ * @throws {MiddlewareError} If a middleware does not declare exactly two parameters.
223
+ * @example
224
+ * ```js
225
+ * router.query('/users', queryUsers);
226
+ * ```
227
+ */
228
+ query(pathnamePattern, ...middlewares) {
229
+ this.#addRoute('QUERY', pathnamePattern, middlewares);
230
+ return this;
231
+ }
232
+
233
+ /**
234
+ * Registers middleware for POST requests.
235
+ *
236
+ * @param {string} pathnamePattern - The pathname pattern to match.
237
+ * @param {...MiddlewareFunction} middlewares - The middleware functions to execute for matching POST requests.
238
+ * @returns {Router} The router instance.
239
+ * @throws {RouterError} If POST middleware has already been registered for the pathname pattern.
240
+ * @throws {PathnamePatternError} If the pathname pattern is not a string.
241
+ * @throws {PathnamePatternError} If the pathname pattern is empty.
242
+ * @throws {PathnamePatternError} If the pathname pattern exceeds the maximum length.
243
+ * @throws {PathnamePatternError} If the pathname pattern syntax is invalid.
244
+ * @throws {PathnamePatternError} If the pathname pattern has duplicate parameter names.
245
+ * @throws {MiddlewareError} If a middleware is not a function.
246
+ * @throws {MiddlewareError} If a middleware does not declare exactly two parameters.
247
+ * @example
248
+ * ```js
249
+ * router.post('/users', createUser);
250
+ * ```
251
+ */
252
+ post(pathnamePattern, ...middlewares) {
253
+ this.#addRoute('POST', pathnamePattern, middlewares);
254
+ return this;
255
+ }
256
+
257
+ /**
258
+ * Registers middleware for PUT requests.
259
+ *
260
+ * @param {string} pathnamePattern - The pathname pattern to match.
261
+ * @param {...MiddlewareFunction} middlewares - The middleware functions to execute for matching PUT requests.
262
+ * @returns {Router} The router instance.
263
+ * @throws {RouterError} If PUT middleware has already been registered for the pathname pattern.
264
+ * @throws {PathnamePatternError} If the pathname pattern is not a string.
265
+ * @throws {PathnamePatternError} If the pathname pattern is empty.
266
+ * @throws {PathnamePatternError} If the pathname pattern exceeds the maximum length.
267
+ * @throws {PathnamePatternError} If the pathname pattern syntax is invalid.
268
+ * @throws {PathnamePatternError} If the pathname pattern has duplicate parameter names.
269
+ * @throws {MiddlewareError} If a middleware is not a function.
270
+ * @throws {MiddlewareError} If a middleware does not declare exactly two parameters.
271
+ * @example
272
+ * ```js
273
+ * router.put('/users/:userId', updateUser);
274
+ * ```
275
+ */
276
+ put(pathnamePattern, ...middlewares) {
277
+ this.#addRoute('PUT', pathnamePattern, middlewares);
278
+ return this;
279
+ }
280
+
281
+ /**
282
+ * Registers middleware for PATCH requests.
283
+ *
284
+ * @param {string} pathnamePattern - The pathname pattern to match.
285
+ * @param {...MiddlewareFunction} middlewares - The middleware functions to execute for matching PATCH requests.
286
+ * @returns {Router} The router instance.
287
+ * @throws {RouterError} If PATCH middleware has already been registered for the pathname pattern.
288
+ * @throws {PathnamePatternError} If the pathname pattern is not a string.
289
+ * @throws {PathnamePatternError} If the pathname pattern is empty.
290
+ * @throws {PathnamePatternError} If the pathname pattern exceeds the maximum length.
291
+ * @throws {PathnamePatternError} If the pathname pattern syntax is invalid.
292
+ * @throws {PathnamePatternError} If the pathname pattern has duplicate parameter names.
293
+ * @throws {MiddlewareError} If a middleware is not a function.
294
+ * @throws {MiddlewareError} If a middleware does not declare exactly two parameters.
295
+ * @example
296
+ * ```js
297
+ * router.patch('/users/:userId', patchUser);
298
+ * ```
299
+ */
300
+ patch(pathnamePattern, ...middlewares) {
301
+ this.#addRoute('PATCH', pathnamePattern, middlewares);
302
+ return this;
303
+ }
304
+
305
+ /**
306
+ * Registers middleware for DELETE requests.
307
+ *
308
+ * @param {string} pathnamePattern - The pathname pattern to match.
309
+ * @param {...MiddlewareFunction} middlewares - The middleware functions to execute for matching DELETE requests.
310
+ * @returns {Router} The router instance.
311
+ * @throws {RouterError} If DELETE middleware has already been registered for the pathname pattern.
312
+ * @throws {PathnamePatternError} If the pathname pattern is not a string.
313
+ * @throws {PathnamePatternError} If the pathname pattern is empty.
314
+ * @throws {PathnamePatternError} If the pathname pattern exceeds the maximum length.
315
+ * @throws {PathnamePatternError} If the pathname pattern syntax is invalid.
316
+ * @throws {PathnamePatternError} If the pathname pattern has duplicate parameter names.
317
+ * @throws {MiddlewareError} If a middleware is not a function.
318
+ * @throws {MiddlewareError} If a middleware does not declare exactly two parameters.
319
+ * @example
320
+ * ```js
321
+ * router.delete('/users/:userId', deleteUser);
322
+ * ```
323
+ */
324
+ delete(pathnamePattern, ...middlewares) {
325
+ this.#addRoute('DELETE', pathnamePattern, middlewares);
326
+ return this;
327
+ }
328
+
329
+ /**
330
+ * Registers middleware for HEAD requests.
331
+ *
332
+ * @param {string} pathnamePattern - The pathname pattern to match.
333
+ * @param {...MiddlewareFunction} middlewares - The middleware functions to execute for matching HEAD requests.
334
+ * @returns {Router} The router instance.
335
+ * @throws {RouterError} If HEAD middleware has already been registered for the pathname pattern.
336
+ * @throws {PathnamePatternError} If the pathname pattern is not a string.
337
+ * @throws {PathnamePatternError} If the pathname pattern is empty.
338
+ * @throws {PathnamePatternError} If the pathname pattern exceeds the maximum length.
339
+ * @throws {PathnamePatternError} If the pathname pattern syntax is invalid.
340
+ * @throws {PathnamePatternError} If the pathname pattern has duplicate parameter names.
341
+ * @throws {MiddlewareError} If a middleware is not a function.
342
+ * @throws {MiddlewareError} If a middleware does not declare exactly two parameters.
343
+ * @example
344
+ * ```js
345
+ * router.head('/users', checkUsers);
346
+ * ```
347
+ */
348
+ head(pathnamePattern, ...middlewares) {
349
+ this.#addRoute('HEAD', pathnamePattern, middlewares);
350
+ return this;
351
+ }
352
+
353
+ /**
354
+ * Registers middleware for TRACE requests.
355
+ *
356
+ * @param {string} pathnamePattern - The pathname pattern to match.
357
+ * @param {...MiddlewareFunction} middlewares - The middleware functions to execute for matching TRACE requests.
358
+ * @returns {Router} The router instance.
359
+ * @throws {RouterError} If TRACE middleware has already been registered for the pathname pattern.
360
+ * @throws {PathnamePatternError} If the pathname pattern is not a string.
361
+ * @throws {PathnamePatternError} If the pathname pattern is empty.
362
+ * @throws {PathnamePatternError} If the pathname pattern exceeds the maximum length.
363
+ * @throws {PathnamePatternError} If the pathname pattern syntax is invalid.
364
+ * @throws {PathnamePatternError} If the pathname pattern has duplicate parameter names.
365
+ * @throws {MiddlewareError} If a middleware is not a function.
366
+ * @throws {MiddlewareError} If a middleware does not declare exactly two parameters.
367
+ * @example
368
+ * ```js
369
+ * router.trace('/debug', traceRequest);
370
+ * ```
371
+ */
372
+ trace(pathnamePattern, ...middlewares) {
373
+ this.#addRoute('TRACE', pathnamePattern, middlewares);
374
+ return this;
375
+ }
376
+
377
+ /**
378
+ * Composes the registered exchange layer options.
379
+ *
380
+ * @returns {RouterComposition} The composed router configuration.
381
+ * @example
382
+ * ```js
383
+ * const router = new Router();
384
+ *
385
+ * router
386
+ * .get('/users', getUsers)
387
+ * .post('/users', createUser);
388
+ *
389
+ * const composition = router.compose();
390
+ *
391
+ * // composition.layerOptions includes:
392
+ * // { method: 'GET', pathnamePattern: '/users', middleware: getUsers }
393
+ * // { method: 'POST', pathnamePattern: '/users', middleware: createUser }
394
+ * ```
395
+ */
396
+ compose() {
397
+ const layersOptions = [...this.#routes.values()].flatMap(route => route.compose());
398
+ const middlewares = [...this.#middlewares];
399
+ return {
400
+ middlewares,
401
+ layersOptions,
402
+ };
403
+ }
404
+
405
+ /**
406
+ * Registers middleware for all HTTP methods on a pathname pattern.
407
+ *
408
+ * @param {string} pathnamePattern - The pathname pattern to match.
409
+ * @param {MiddlewareFunction[]} middlewares - The middleware functions to register.
410
+ * @throws {PathnamePatternError} If the pathname pattern is invalid.
411
+ * @throws {MiddlewareError} If a middleware is invalid.
412
+ * @throws {RouterError} If middleware has already been registered for the pathname pattern with `all()`.
413
+ */
414
+ #addAll(pathnamePattern, middlewares) {
415
+ this.#assertPathnamePattern(pathnamePattern);
416
+ this.#assertMiddlewares(middlewares);
417
+ this.#ensureRegistration(undefined, pathnamePattern);
418
+
419
+ const route = this.#routes.get(pathnamePattern);
420
+
421
+ if (route) {
422
+ route.all(...middlewares);
423
+ } else {
424
+ this.#createRoute(pathnamePattern).all(...middlewares);
425
+ }
426
+ }
427
+
428
+ /**
429
+ * Registers middleware for a specific HTTP method and pathname pattern.
430
+ *
431
+ * Prevents duplicate method and pathname pattern registrations and groups
432
+ * middleware by pathname pattern.
433
+ *
434
+ * @param {HTTPMethod} method - The HTTP method.
435
+ * @param {string} pathnamePattern - The pathname pattern to match.
436
+ * @param {MiddlewareFunction[]} middlewares - The middleware functions to register.
437
+ * @throws {PathnamePatternError} If the pathname pattern is not a string.
438
+ * @throws {PathnamePatternError} If the pathname pattern is empty.
439
+ * @throws {PathnamePatternError} If the pathname pattern exceeds the maximum length.
440
+ * @throws {PathnamePatternError} If the pathname pattern syntax is invalid.
441
+ * @throws {PathnamePatternError} If the pathname pattern has duplicate parameter names.
442
+ * @throws {MiddlewareError} If a middleware is not a function.
443
+ * @throws {MiddlewareError} If a middleware does not declare exactly two parameters.
444
+ * @throws {RouterError} If the method is already registered for the pathname pattern.
445
+ */
446
+ #addRoute(method, pathnamePattern, middlewares) {
447
+ this.#assertPathnamePattern(pathnamePattern);
448
+ this.#assertMiddlewares(middlewares);
449
+ this.#ensureRegistration(method, pathnamePattern);
450
+
451
+ const route = this.#routes.get(pathnamePattern);
452
+
453
+ if (route) {
454
+ route.add(method, middlewares);
455
+ } else {
456
+ this.#createRoute(pathnamePattern).add(method, middlewares);
457
+ }
458
+ }
459
+
460
+ /**
461
+ * Creates and stores a route for a pathname pattern.
462
+ *
463
+ * @param {string} pathnamePattern - The pathname pattern to associate with the route.
464
+ * @returns {Route} The created route.
465
+ */
466
+ #createRoute(pathnamePattern) {
467
+ const route = new Route(pathnamePattern);
468
+ this.#routes.set(pathnamePattern, route);
469
+ return route;
470
+ }
471
+
472
+ /**
473
+ * Ensures that a registration is unique for a pathname pattern.
474
+ *
475
+ * @param {string|undefined} method - The HTTP method, or `undefined` for an `all()` registration.
476
+ * @param {string} pathnamePattern - The pathname pattern to register.
477
+ * @throws {RouterError} If the registration already exists.
478
+ */
479
+ #ensureRegistration(method, pathnamePattern) {
480
+ const key = `${method}:${pathnamePattern}`;
481
+
482
+ if (this.#registrations.has(key)) {
483
+ if (method === undefined) {
484
+ throw new RouterError('DUPLICATE_ALL_METHOD', { values: { pathnamePattern } });
485
+ }
486
+
487
+ throw new RouterError('DUPLICATE_ROUTE', { values: { method, pathnamePattern } });
488
+ }
489
+
490
+ this.#registrations.add(key);
491
+ }
492
+
493
+ /**
494
+ * Validates a pathname pattern.
495
+ *
496
+ * @param {unknown} value - The pathname pattern to validate.
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
+ */
503
+ #assertPathnamePattern(value) {
504
+ assertPathnamePattern(value);
505
+ }
506
+
507
+ /**
508
+ * Validates middleware functions.
509
+ *
510
+ * A middleware must be a function that declares exactly two parameters:
511
+ * `exchange` and `next`.
512
+ *
513
+ * @param {unknown} value - The middleware functions to validate.
514
+ * @throws {MiddlewareError} If a middleware is not a function.
515
+ * @throws {MiddlewareError} If a middleware does not declare exactly two parameters.
516
+ */
517
+ #assertMiddlewares(value) {
518
+ assertMiddlewares(value);
519
+ }
520
+ }
521
+
522
+ // ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
523
+ export default Router;