@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.
- package/README.md +229 -2
- package/index.mjs +23 -0
- package/package.json +71 -4
- package/source/application/Kernel/index.mjs +1137 -0
- package/source/application/Server/index.mjs +1201 -0
- package/source/application/configurations/Http2CleartextServerConfiguration/index.mjs +168 -0
- package/source/application/configurations/Http2SecureServerConfiguration/index.mjs +172 -0
- package/source/application/configurations/HttpServerConfiguration/index.mjs +169 -0
- package/source/application/configurations/HttpsServerConfiguration/index.mjs +172 -0
- package/source/application/configurations/ServerConfiguration/Options/CORSOptions/CORSOptionsValue.mjs +158 -0
- package/source/application/configurations/ServerConfiguration/Options/CORSOptions/index.mjs +87 -0
- package/source/application/configurations/ServerConfiguration/Options/CSRFOptions/CSRFOptionsValue.mjs +178 -0
- package/source/application/configurations/ServerConfiguration/Options/CSRFOptions/index.mjs +80 -0
- package/source/application/configurations/ServerConfiguration/Options/CompressOptions/CompressOptionsValue.mjs +126 -0
- package/source/application/configurations/ServerConfiguration/Options/CompressOptions/index.mjs +81 -0
- package/source/application/configurations/ServerConfiguration/Options/CookieParserOptions/CookieParserOptionsValue.mjs +107 -0
- package/source/application/configurations/ServerConfiguration/Options/CookieParserOptions/index.mjs +82 -0
- package/source/application/configurations/ServerConfiguration/Options/CookieSessionOptions/CookieSessionOptionsValue.mjs +182 -0
- package/source/application/configurations/ServerConfiguration/Options/CookieSessionOptions/index.mjs +86 -0
- package/source/application/configurations/ServerConfiguration/Options/ListenOptions/ListenOptionsValue.mjs +95 -0
- package/source/application/configurations/ServerConfiguration/Options/ListenOptions/index.mjs +47 -0
- package/source/application/configurations/ServerConfiguration/Options/MiddlewareOption.mjs +102 -0
- package/source/application/configurations/ServerConfiguration/Options/Options.mjs +105 -0
- package/source/application/configurations/ServerConfiguration/Options/RateLimitOptions/RateLimitOptionsValue.mjs +164 -0
- package/source/application/configurations/ServerConfiguration/Options/RateLimitOptions/index.mjs +84 -0
- package/source/application/configurations/ServerConfiguration/Options/RequestOptions/RequestOptionsValue.mjs +108 -0
- package/source/application/configurations/ServerConfiguration/Options/RequestOptions/index.mjs +47 -0
- package/source/application/configurations/ServerConfiguration/Options/ResponseOptions/ResponseOptionsValue.mjs +101 -0
- package/source/application/configurations/ServerConfiguration/Options/ResponseOptions/index.mjs +47 -0
- package/source/application/configurations/ServerConfiguration/Options/SecurityHeadersOptions/SecurityHeadersOptionsValue.mjs +272 -0
- package/source/application/configurations/ServerConfiguration/Options/SecurityHeadersOptions/index.mjs +85 -0
- package/source/application/configurations/ServerConfiguration/Options/ServerOption.mjs +50 -0
- package/source/application/configurations/ServerConfiguration/Options/StaticOptions/StaticOptionsValue.mjs +164 -0
- package/source/application/configurations/ServerConfiguration/Options/StaticOptions/index.mjs +89 -0
- package/source/application/configurations/ServerConfiguration/index.mjs +781 -0
- package/source/application/resolvers/TransportResolver/index.mjs +119 -0
- package/source/application/use-cases/CloseServer/index.mjs +85 -0
- package/source/application/use-cases/ListenServer/index.mjs +98 -0
- package/source/core/EventSystem/index.mjs +110 -0
- package/source/core/Exchange/ExchangeContext.mjs +291 -0
- package/source/core/Exchange/Telemetry.mjs +193 -0
- package/source/core/Exchange/index.mjs +604 -0
- package/source/core/ExchangeLayerBuilder/index.mjs +133 -0
- package/source/core/IPCMain/IPCMain.mjs +507 -0
- package/source/core/IPCMain/IPCProtocol.mjs +441 -0
- package/source/core/IPCMain/IPCServerChannel.mjs +331 -0
- package/source/core/IPCMain/WebSocketAdapter.mjs +189 -0
- package/source/core/Request/BodyParser/BodyParser.mjs +197 -0
- package/source/core/Request/BodyParser/JSONParser.mjs +70 -0
- package/source/core/Request/BodyParser/MultipartParser.mjs +612 -0
- package/source/core/Request/BodyParser/URLEncodedParser.mjs +71 -0
- package/source/core/Request/RequestHeaders.mjs +177 -0
- package/source/core/Request/index.mjs +805 -0
- package/source/core/Response/ResponseHeaders.mjs +402 -0
- package/source/core/Response/index.mjs +1370 -0
- package/source/core/middlewares/CORSMiddleware/index.mjs +289 -0
- package/source/core/middlewares/CSRFMiddleware/index.mjs +284 -0
- package/source/core/middlewares/CompressMiddleware/index.mjs +460 -0
- package/source/core/middlewares/CookieParserMiddleware/index.mjs +194 -0
- package/source/core/middlewares/CookieSessionMiddleware/index.mjs +460 -0
- package/source/core/middlewares/RateLimitMiddleware/index.mjs +245 -0
- package/source/core/middlewares/SecurityHeadersMiddleware/index.mjs +151 -0
- package/source/core/middlewares/StaticMiddleware/index.mjs +628 -0
- package/source/core/routing/ExchangeLayer/ExchangeMatcher.mjs +127 -0
- package/source/core/routing/ExchangeLayer/index.mjs +254 -0
- package/source/core/routing/Pipeline/index.mjs +157 -0
- package/source/core/routing/Router/Route.mjs +184 -0
- package/source/core/routing/Router/index.mjs +523 -0
- package/source/core/routing/RouterRegister/OptionsMiddleware.mjs +147 -0
- package/source/core/routing/RouterRegister/index.mjs +150 -0
- package/source/core/routing/RouterSystem/index.mjs +225 -0
- package/source/core/value-objects/Address/index.mjs +281 -0
- package/source/core/value-objects/Endpoint/index.mjs +293 -0
- package/source/index.mjs +62 -0
- package/source/infrastructure/adapters/Http2Adapter/index.mjs +144 -0
- package/source/infrastructure/adapters/Http2CleartextAdapter/index.mjs +147 -0
- package/source/infrastructure/adapters/HttpAdapter/index.mjs +124 -0
- package/source/infrastructure/adapters/HttpsAdapter/index.mjs +124 -0
- package/source/infrastructure/adapters/TransportAdapter/createWebSocketOpenRequest.mjs +843 -0
- package/source/infrastructure/adapters/TransportAdapter/index.mjs +555 -0
- package/source/shared/Identifier/index.mjs +671 -0
- package/source/shared/constants/symbols/brands/index.mjs +45 -0
- package/source/shared/domain/UUIDV7/index.mjs +346 -0
- package/source/shared/errors/BaseError/index.mjs +157 -0
- package/source/shared/errors/IPCError/index.mjs +97 -0
- package/source/shared/errors/MiddlewareError/index.mjs +106 -0
- package/source/shared/errors/PathnamePatternError/index.mjs +103 -0
- package/source/shared/errors/RequestError/index.mjs +116 -0
- package/source/shared/errors/ResponseError/index.mjs +140 -0
- package/source/shared/errors/RouterError/index.mjs +90 -0
- package/source/shared/errors/ServerConfigurationError/index.mjs +551 -0
- package/source/shared/errors/ServerError/index.mjs +127 -0
- package/source/shared/errors/WebSocketError/index.mjs +58 -0
- package/source/shared/outcome/ErrorSchema.mjs +361 -0
- package/source/shared/outcome/Result/index.mjs +239 -0
- package/source/shared/outcome/Validation/index.mjs +208 -0
- package/source/shared/validations/Middleware/index.mjs +160 -0
- package/source/shared/validations/PathnamePattern/index.mjs +256 -0
- package/source/shared/validations/Server/NotFoundHandler/index.mjs +94 -0
- package/source/shared/validations/Server/Router/index.mjs +68 -0
- package/source/shared/validations/Server/ServerConfiguration/index.mjs +68 -0
- package/source/shared/validations/Server/SystemErrorHandler/index.mjs +94 -0
- package/source/shared/validations/ServerConfiguration/CORSOptions/index.mjs +310 -0
- package/source/shared/validations/ServerConfiguration/CSRFOptions/index.mjs +299 -0
- package/source/shared/validations/ServerConfiguration/CompressOptions/index.mjs +204 -0
- package/source/shared/validations/ServerConfiguration/CookieParserOptions/index.mjs +102 -0
- package/source/shared/validations/ServerConfiguration/CookieSessionOptions/index.mjs +332 -0
- package/source/shared/validations/ServerConfiguration/ListenOptions/index.mjs +178 -0
- package/source/shared/validations/ServerConfiguration/RateLimitOptions/index.mjs +229 -0
- package/source/shared/validations/ServerConfiguration/RequestOptions/index.mjs +132 -0
- package/source/shared/validations/ServerConfiguration/ResponseOptions/index.mjs +122 -0
- package/source/shared/validations/ServerConfiguration/SecurityHeadersOptions/index.mjs +137 -0
- package/source/shared/validations/ServerConfiguration/StaticOptions/index.mjs +248 -0
- package/types/index.d.ts +719 -0
|
@@ -0,0 +1,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;
|