@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,102 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file Defines the abstract base for middleware configuration options.
|
|
4
|
+
*
|
|
5
|
+
* @description MiddlewareOption identifies configuration options that
|
|
6
|
+
* represent middlewares (cors, static, securityHeaders, rateLimit).
|
|
7
|
+
*
|
|
8
|
+
* @module MiddlewareOption
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
12
|
+
// » IMPORT LOCAL MODULES
|
|
13
|
+
import Options from './Options.mjs';
|
|
14
|
+
|
|
15
|
+
// ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
16
|
+
/**
|
|
17
|
+
* Represents a router HTTP method supported by the routing layer.
|
|
18
|
+
*
|
|
19
|
+
* @typedef {'GET'|'HEAD'|'POST'|'PUT'|'PATCH'|'DELETE'|'OPTIONS'|'QUERY'|'TRACE'} HTTPMethod
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Represents one middleware configuration consumed by the exchange layer builder.
|
|
23
|
+
*
|
|
24
|
+
* @typedef {object} MiddlewareConfiguration
|
|
25
|
+
* @property {string} name - Middleware name.
|
|
26
|
+
* @property {Record<string, unknown>|undefined} options - Middleware configuration options.
|
|
27
|
+
* @property {string|undefined} pathnamePattern - URL pathname pattern to match.
|
|
28
|
+
* @property {HTTPMethod|undefined} method - HTTP method to match.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Represents a value object wrapping a normalized configuration value.
|
|
33
|
+
*
|
|
34
|
+
* @template {Record<string, unknown>|undefined} TValue
|
|
35
|
+
* @typedef {import('./Options.mjs').ValueObject<TValue>} ValueObject
|
|
36
|
+
*/
|
|
37
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
38
|
+
/**
|
|
39
|
+
* Abstract base class for middleware configuration options.
|
|
40
|
+
*
|
|
41
|
+
* @abstract
|
|
42
|
+
* @template {Record<string, unknown>|undefined} TValue
|
|
43
|
+
* @augments Options<TValue>
|
|
44
|
+
*/
|
|
45
|
+
class MiddlewareOption extends Options {
|
|
46
|
+
/**
|
|
47
|
+
* Creates a server middleware configuration option instance.
|
|
48
|
+
*
|
|
49
|
+
* @abstract
|
|
50
|
+
* @param {string} name - Option name.
|
|
51
|
+
* @param {ValueObject<TValue>} value - Normalized option value.
|
|
52
|
+
* @throws {TypeError} If MiddlewareOption is instantiated directly.
|
|
53
|
+
*/
|
|
54
|
+
constructor(name, value) {
|
|
55
|
+
if (new.target === MiddlewareOption) {
|
|
56
|
+
throw new TypeError('MiddlewareOption is an abstract class and cannot be instantiated');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
super('middleware', name, value.value);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The URL pathname pattern the middleware is scoped to.
|
|
64
|
+
*
|
|
65
|
+
* Defaults to `undefined`, meaning the middleware applies to all paths.
|
|
66
|
+
*
|
|
67
|
+
* @readonly
|
|
68
|
+
* @type {string|undefined}
|
|
69
|
+
*/
|
|
70
|
+
get pathnamePattern() {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The HTTP method the middleware is scoped to.
|
|
76
|
+
*
|
|
77
|
+
* Defaults to `undefined`, meaning the middleware applies to all methods.
|
|
78
|
+
*
|
|
79
|
+
* @readonly
|
|
80
|
+
* @type {HTTPMethod|undefined}
|
|
81
|
+
*/
|
|
82
|
+
get method() {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Composes the middleware configuration consumed by the exchange layer
|
|
88
|
+
* builder.
|
|
89
|
+
*
|
|
90
|
+
* Subclasses must implement this method.
|
|
91
|
+
*
|
|
92
|
+
* @abstract
|
|
93
|
+
* @returns {MiddlewareConfiguration} The composed middleware configuration.
|
|
94
|
+
* @throws {TypeError} If the subclass does not implement `compose`.
|
|
95
|
+
*/
|
|
96
|
+
compose() {
|
|
97
|
+
throw new TypeError('MiddlewareOption.compose must be implemented by subclasses');
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
102
|
+
export default MiddlewareOption;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file Defines the abstract base for configuration options.
|
|
4
|
+
*
|
|
5
|
+
* @description Options provides the common contract shared by configuration
|
|
6
|
+
* option value objects. It cannot be instantiated directly.
|
|
7
|
+
*
|
|
8
|
+
* @module Options
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
12
|
+
/**
|
|
13
|
+
* Represents the kind of configuration option.
|
|
14
|
+
*
|
|
15
|
+
* @typedef {'server'|'middleware'} OptionType
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Represents a value object wrapping a normalized configuration value.
|
|
20
|
+
*
|
|
21
|
+
* @template {Record<string, unknown>|undefined} TValue
|
|
22
|
+
* @typedef {{ value: TValue }} ValueObject
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
26
|
+
/**
|
|
27
|
+
* Abstract base class for configuration options.
|
|
28
|
+
*
|
|
29
|
+
* @template {Record<string, unknown>|undefined} TValue
|
|
30
|
+
* @abstract
|
|
31
|
+
*/
|
|
32
|
+
class Options {
|
|
33
|
+
/**
|
|
34
|
+
* The option kind.
|
|
35
|
+
*
|
|
36
|
+
* @type {OptionType}
|
|
37
|
+
*/
|
|
38
|
+
#type;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The option name.
|
|
42
|
+
*
|
|
43
|
+
* @type {string}
|
|
44
|
+
*/
|
|
45
|
+
#name;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The option value.
|
|
49
|
+
*
|
|
50
|
+
* @type {TValue}
|
|
51
|
+
*/
|
|
52
|
+
#value;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Creates a configuration option instance.
|
|
56
|
+
*
|
|
57
|
+
* @abstract
|
|
58
|
+
* @param {OptionType} type - Option kind.
|
|
59
|
+
* @param {string} name - Option name.
|
|
60
|
+
* @param {TValue} value - Normalized option value.
|
|
61
|
+
* @throws {TypeError} If Options is instantiated directly.
|
|
62
|
+
*/
|
|
63
|
+
constructor(type, name, value) {
|
|
64
|
+
if (new.target === Options) {
|
|
65
|
+
throw new TypeError('Options is an abstract class and cannot be instantiated');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.#type = type;
|
|
69
|
+
this.#name = name;
|
|
70
|
+
this.#value = value;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The option kind.
|
|
75
|
+
*
|
|
76
|
+
* @readonly
|
|
77
|
+
* @type {OptionType}
|
|
78
|
+
*/
|
|
79
|
+
get type() {
|
|
80
|
+
return this.#type;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* The option name.
|
|
85
|
+
*
|
|
86
|
+
* @readonly
|
|
87
|
+
* @type {string}
|
|
88
|
+
*/
|
|
89
|
+
get name() {
|
|
90
|
+
return this.#name;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* The option value.
|
|
95
|
+
*
|
|
96
|
+
* @readonly
|
|
97
|
+
* @type {TValue}
|
|
98
|
+
*/
|
|
99
|
+
get value() {
|
|
100
|
+
return this.#value;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
105
|
+
export default Options;
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file Defines the RateLimitOptionsValue value object.
|
|
4
|
+
*
|
|
5
|
+
* @description Normalizes and validates rate limit middleware configuration
|
|
6
|
+
* used by RateLimitMiddleware. When options are omitted, the value is
|
|
7
|
+
* undefined and the middleware is not registered.
|
|
8
|
+
*
|
|
9
|
+
* @module RateLimitOptionsValue
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
13
|
+
// » IMPORT LOCAL MODULES
|
|
14
|
+
import { assertRateLimitOptions } from '#shared/validations/ServerConfiguration/RateLimitOptions';
|
|
15
|
+
|
|
16
|
+
// ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
17
|
+
/**
|
|
18
|
+
* Domain error thrown by server configuration validation.
|
|
19
|
+
*
|
|
20
|
+
* @typedef {import('#shared/errors/ServerConfigurationError').default} ServerConfigurationError
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Represents the rate limit options accepted by the server configuration.
|
|
25
|
+
*
|
|
26
|
+
* @typedef {object} RateLimitOptionsInput
|
|
27
|
+
* @property {number} [windowMs] - Duration of the rate-limit window in milliseconds.
|
|
28
|
+
* @property {number} [max] - Maximum number of requests allowed per window per client.
|
|
29
|
+
* @property {number} [statusCode] - HTTP status code returned when the limit is exceeded.
|
|
30
|
+
* @property {Record<string, unknown>} [message] - JSON body sent when the limit is exceeded.
|
|
31
|
+
* @property {boolean} [standardHeaders] - Whether to send the modern `RateLimit-*` headers.
|
|
32
|
+
* @property {boolean} [legacyHeaders] - Whether to send the legacy `X-RateLimit-*` headers.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Represents the normalized rate limit options.
|
|
37
|
+
*
|
|
38
|
+
* @typedef {object} RateLimitOptionsData
|
|
39
|
+
* @property {number} windowMs - Duration of the rate-limit window in milliseconds.
|
|
40
|
+
* @property {number} max - Maximum number of requests allowed per window per client.
|
|
41
|
+
* @property {number} statusCode - HTTP status code returned when the limit is exceeded.
|
|
42
|
+
* @property {Readonly<Record<string, unknown>>} message - JSON body sent when the limit is exceeded.
|
|
43
|
+
* @property {boolean} standardHeaders - Whether to send the modern `RateLimit-*` headers.
|
|
44
|
+
* @property {boolean} legacyHeaders - Whether to send the legacy `X-RateLimit-*` headers.
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
// ━━ CONSTANTS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
48
|
+
/**
|
|
49
|
+
* Default duration of the rate-limit window in milliseconds.
|
|
50
|
+
*
|
|
51
|
+
* @type {number}
|
|
52
|
+
*/
|
|
53
|
+
const DEFAULT_WINDOW_MS = 60_000;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Default maximum number of requests allowed per window per client.
|
|
57
|
+
*
|
|
58
|
+
* @type {number}
|
|
59
|
+
*/
|
|
60
|
+
const DEFAULT_MAX = 100;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Default HTTP status code returned when the limit is exceeded.
|
|
64
|
+
*
|
|
65
|
+
* @type {number}
|
|
66
|
+
*/
|
|
67
|
+
const DEFAULT_STATUS_CODE = 429;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Default JSON body sent when the limit is exceeded.
|
|
71
|
+
*
|
|
72
|
+
* @type {Readonly<Record<string, unknown>>}
|
|
73
|
+
*/
|
|
74
|
+
const DEFAULT_MESSAGE = Object.freeze({ error: 'Too Many Requests' });
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Default behavior for modern `RateLimit-*` headers.
|
|
78
|
+
*
|
|
79
|
+
* @type {boolean}
|
|
80
|
+
*/
|
|
81
|
+
const DEFAULT_STANDARD_HEADERS = true;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Default behavior for legacy `X-RateLimit-*` headers.
|
|
85
|
+
*
|
|
86
|
+
* @type {boolean}
|
|
87
|
+
*/
|
|
88
|
+
const DEFAULT_LEGACY_HEADERS = false;
|
|
89
|
+
|
|
90
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
91
|
+
/**
|
|
92
|
+
* Immutable value object representing normalized rate limit middleware
|
|
93
|
+
* options.
|
|
94
|
+
*
|
|
95
|
+
* @classdesc Validates and normalizes rate limit middleware options. When
|
|
96
|
+
* options are provided, defaults are applied for any omitted property. When
|
|
97
|
+
* options are omitted, the value is undefined.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* ```js
|
|
101
|
+
* const options = new RateLimitOptionsValue({ windowMs: 60_000, max: 100 });
|
|
102
|
+
*
|
|
103
|
+
* options.value;
|
|
104
|
+
* // => {
|
|
105
|
+
* // windowMs: 60_000,
|
|
106
|
+
* // max: 100,
|
|
107
|
+
* // statusCode: 429,
|
|
108
|
+
* // message: { error: 'Too Many Requests' },
|
|
109
|
+
* // standardHeaders: true,
|
|
110
|
+
* // legacyHeaders: false,
|
|
111
|
+
* // }
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```js
|
|
116
|
+
* const options = new RateLimitOptionsValue();
|
|
117
|
+
*
|
|
118
|
+
* options.value;
|
|
119
|
+
* // => undefined
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
class RateLimitOptionsValue {
|
|
123
|
+
/**
|
|
124
|
+
* The normalized rate limit options.
|
|
125
|
+
*
|
|
126
|
+
* @type {Readonly<RateLimitOptionsData>|undefined}
|
|
127
|
+
*/
|
|
128
|
+
#value;
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Creates a new {@link RateLimitOptionsValue}.
|
|
132
|
+
*
|
|
133
|
+
* @param {RateLimitOptionsInput} [options] - The rate limit middleware options.
|
|
134
|
+
* @throws {ServerConfigurationError} If `options` is invalid.
|
|
135
|
+
*/
|
|
136
|
+
constructor(options) {
|
|
137
|
+
assertRateLimitOptions(options);
|
|
138
|
+
|
|
139
|
+
this.#value =
|
|
140
|
+
options === undefined
|
|
141
|
+
? options
|
|
142
|
+
: Object.freeze({
|
|
143
|
+
windowMs: options.windowMs ?? DEFAULT_WINDOW_MS,
|
|
144
|
+
max: options.max ?? DEFAULT_MAX,
|
|
145
|
+
statusCode: options.statusCode ?? DEFAULT_STATUS_CODE,
|
|
146
|
+
message: Object.freeze({ ...(options.message ?? DEFAULT_MESSAGE) }),
|
|
147
|
+
standardHeaders: options.standardHeaders ?? DEFAULT_STANDARD_HEADERS,
|
|
148
|
+
legacyHeaders: options.legacyHeaders ?? DEFAULT_LEGACY_HEADERS,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The normalized rate limit options.
|
|
154
|
+
*
|
|
155
|
+
* @readonly
|
|
156
|
+
* @type {Readonly<RateLimitOptionsData>|undefined}
|
|
157
|
+
*/
|
|
158
|
+
get value() {
|
|
159
|
+
return this.#value;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
164
|
+
export default RateLimitOptionsValue;
|
package/source/application/configurations/ServerConfiguration/Options/RateLimitOptions/index.mjs
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file Defines the RateLimitOptions middleware configuration option.
|
|
4
|
+
*
|
|
5
|
+
* @description RateLimitOptions wraps a normalized RateLimitOptionsValue into
|
|
6
|
+
* the middleware configuration option hierarchy.
|
|
7
|
+
*
|
|
8
|
+
* @module RateLimitOptions
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
12
|
+
// » IMPORT LOCAL MODULES
|
|
13
|
+
import MiddlewareOption from '../MiddlewareOption.mjs';
|
|
14
|
+
import RateLimitOptionsValue from './RateLimitOptionsValue.mjs';
|
|
15
|
+
|
|
16
|
+
// ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
17
|
+
/**
|
|
18
|
+
* Represents the rate limit options accepted by the server configuration.
|
|
19
|
+
*
|
|
20
|
+
* @typedef {import('./RateLimitOptionsValue.mjs').RateLimitOptionsInput} RateLimitOptionsInput
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Represents the normalized rate limit options.
|
|
25
|
+
*
|
|
26
|
+
* @typedef {import('./RateLimitOptionsValue.mjs').RateLimitOptionsData} RateLimitOptionsData
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Represents one middleware configuration consumed by the exchange layer builder.
|
|
31
|
+
*
|
|
32
|
+
* @typedef {import('../MiddlewareOption.mjs').MiddlewareConfiguration} MiddlewareConfiguration
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
36
|
+
/**
|
|
37
|
+
* Middleware configuration option wrapping a normalized rate limit
|
|
38
|
+
* configuration.
|
|
39
|
+
*
|
|
40
|
+
* @augments {MiddlewareOption<RateLimitOptionsData|undefined>}
|
|
41
|
+
*/
|
|
42
|
+
class RateLimitOptions extends MiddlewareOption {
|
|
43
|
+
/**
|
|
44
|
+
* Creates a new {@link RateLimitOptions}.
|
|
45
|
+
*
|
|
46
|
+
* @param {RateLimitOptionsInput} [options] - The rate limit middleware options.
|
|
47
|
+
* @example
|
|
48
|
+
* ```js
|
|
49
|
+
* const rateLimitOptions = new RateLimitOptions({
|
|
50
|
+
* windowMs: 60_000,
|
|
51
|
+
* max: 100,
|
|
52
|
+
* });
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
constructor(options) {
|
|
56
|
+
super('rateLimit', new RateLimitOptionsValue(options));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Composes the rate limit middleware configuration consumed by the
|
|
61
|
+
* exchange layer builder.
|
|
62
|
+
*
|
|
63
|
+
* Rate limiting is a cross-cutting concern applied to every request
|
|
64
|
+
* regardless of pathname or method, so both `pathnamePattern` and `method`
|
|
65
|
+
* are `undefined`.
|
|
66
|
+
*
|
|
67
|
+
* @returns {MiddlewareConfiguration} The composed middleware configuration.
|
|
68
|
+
* @example
|
|
69
|
+
* ```js
|
|
70
|
+
* const configuration = rateLimitOptions.compose();
|
|
71
|
+
* ```
|
|
72
|
+
*/
|
|
73
|
+
compose() {
|
|
74
|
+
return {
|
|
75
|
+
name: this.name,
|
|
76
|
+
options: this.value,
|
|
77
|
+
pathnamePattern: undefined,
|
|
78
|
+
method: undefined,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
84
|
+
export default RateLimitOptions;
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file Defines the RequestOptionsValue value object.
|
|
4
|
+
*
|
|
5
|
+
* @description Normalizes and validates request configuration used by
|
|
6
|
+
* ServerConfiguration. When options are omitted, the value is undefined.
|
|
7
|
+
*
|
|
8
|
+
* @module RequestOptionsValue
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
12
|
+
// » IMPORT LOCAL MODULES
|
|
13
|
+
import { assertRequestOptions } from '#shared/validations/ServerConfiguration/RequestOptions';
|
|
14
|
+
|
|
15
|
+
// ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
16
|
+
/**
|
|
17
|
+
* Domain error thrown by server configuration validation.
|
|
18
|
+
*
|
|
19
|
+
* @typedef {import('#shared/errors/ServerConfigurationError').default} ServerConfigurationError
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Whether (and how much) to trust proxy headers.
|
|
24
|
+
*
|
|
25
|
+
* @typedef {boolean|number} TrustProxyOption
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Represents the request options accepted by the server configuration.
|
|
30
|
+
*
|
|
31
|
+
* @typedef {object} RequestOptionsInput
|
|
32
|
+
* @property {number} [bodyLimit] - Maximum number of bytes allowed for the request body.
|
|
33
|
+
* @property {TrustProxyOption} [trustProxy] - Whether (and how much) to trust proxy headers.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Represents the normalized request options.
|
|
38
|
+
*
|
|
39
|
+
* @typedef {object} RequestOptionsData
|
|
40
|
+
* @property {number} [bodyLimit] - Maximum number of bytes allowed for the request body.
|
|
41
|
+
* @property {TrustProxyOption} [trustProxy] - Whether (and how much) to trust proxy headers.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
45
|
+
/**
|
|
46
|
+
* Immutable value object representing normalized request options.
|
|
47
|
+
*
|
|
48
|
+
* @classdesc Validates and normalizes request options for the server.
|
|
49
|
+
* When options are omitted, the value is undefined.
|
|
50
|
+
*/
|
|
51
|
+
class RequestOptionsValue {
|
|
52
|
+
/**
|
|
53
|
+
* Normalized request options.
|
|
54
|
+
*
|
|
55
|
+
* @type {Readonly<RequestOptionsData>|undefined}
|
|
56
|
+
*/
|
|
57
|
+
#value;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Creates a new {@link RequestOptionsValue}.
|
|
61
|
+
*
|
|
62
|
+
* @param {RequestOptionsInput} [options] - The request options.
|
|
63
|
+
* @throws {ServerConfigurationError} If `options` is not a plain object.
|
|
64
|
+
* @throws {ServerConfigurationError} If `bodyLimit` is defined and is not a number.
|
|
65
|
+
* @throws {ServerConfigurationError} If `bodyLimit` is defined and is not a positive safe integer.
|
|
66
|
+
* @throws {ServerConfigurationError} If `trustProxy` is defined and is not a boolean or number.
|
|
67
|
+
* @throws {ServerConfigurationError} If numeric `trustProxy` is defined and is negative or not an integer.
|
|
68
|
+
* @example
|
|
69
|
+
* ```js
|
|
70
|
+
* const options = new RequestOptionsValue({ bodyLimit: 1_000_000, trustProxy: 1 });
|
|
71
|
+
*
|
|
72
|
+
* options.value;
|
|
73
|
+
* // => { bodyLimit: 1_000_000, trustProxy: 1 }
|
|
74
|
+
* ```
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```js
|
|
78
|
+
* const options = new RequestOptionsValue();
|
|
79
|
+
*
|
|
80
|
+
* options.value;
|
|
81
|
+
* // => undefined
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
constructor(options) {
|
|
85
|
+
assertRequestOptions(options);
|
|
86
|
+
|
|
87
|
+
this.#value =
|
|
88
|
+
options === undefined
|
|
89
|
+
? options
|
|
90
|
+
: Object.freeze({
|
|
91
|
+
bodyLimit: options.bodyLimit,
|
|
92
|
+
trustProxy: options.trustProxy,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The request options.
|
|
98
|
+
*
|
|
99
|
+
* @readonly
|
|
100
|
+
* @type {Readonly<RequestOptionsData>|undefined}
|
|
101
|
+
*/
|
|
102
|
+
get value() {
|
|
103
|
+
return this.#value;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
108
|
+
export default RequestOptionsValue;
|
package/source/application/configurations/ServerConfiguration/Options/RequestOptions/index.mjs
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/**
|
|
3
|
+
* @file Defines the RequestOptions server configuration option.
|
|
4
|
+
*
|
|
5
|
+
* @description RequestOptions wraps a normalized RequestOptionsValue into the
|
|
6
|
+
* server configuration option hierarchy.
|
|
7
|
+
*
|
|
8
|
+
* @module RequestOptions
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
12
|
+
// » IMPORT LOCAL MODULES
|
|
13
|
+
import ServerOption from '../ServerOption.mjs';
|
|
14
|
+
import RequestOptionsValue from './RequestOptionsValue.mjs';
|
|
15
|
+
|
|
16
|
+
// ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
17
|
+
/**
|
|
18
|
+
* Represents the request options accepted by the server configuration.
|
|
19
|
+
*
|
|
20
|
+
* @typedef {import('./RequestOptionsValue.mjs').RequestOptionsInput} RequestOptionsInput
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Represents the normalized request options.
|
|
25
|
+
*
|
|
26
|
+
* @typedef {import('./RequestOptionsValue.mjs').RequestOptionsData} RequestOptionsData
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
// ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
30
|
+
/**
|
|
31
|
+
* Server configuration option wrapping a normalized request configuration.
|
|
32
|
+
*
|
|
33
|
+
* @augments {ServerOption<RequestOptionsData|undefined>}
|
|
34
|
+
*/
|
|
35
|
+
class RequestOptions extends ServerOption {
|
|
36
|
+
/**
|
|
37
|
+
* Creates a new {@link RequestOptions}.
|
|
38
|
+
*
|
|
39
|
+
* @param {RequestOptionsInput} [options] - The request options.
|
|
40
|
+
*/
|
|
41
|
+
constructor(options) {
|
|
42
|
+
super('request', new RequestOptionsValue(options));
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
47
|
+
export default RequestOptions;
|