@boo-labs/server 0.0.0-stage → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/README.md +229 -2
  2. package/index.mjs +23 -0
  3. package/package.json +71 -4
  4. package/source/application/Kernel/index.mjs +1137 -0
  5. package/source/application/Server/index.mjs +1201 -0
  6. package/source/application/configurations/Http2CleartextServerConfiguration/index.mjs +168 -0
  7. package/source/application/configurations/Http2SecureServerConfiguration/index.mjs +172 -0
  8. package/source/application/configurations/HttpServerConfiguration/index.mjs +169 -0
  9. package/source/application/configurations/HttpsServerConfiguration/index.mjs +172 -0
  10. package/source/application/configurations/ServerConfiguration/Options/CORSOptions/CORSOptionsValue.mjs +158 -0
  11. package/source/application/configurations/ServerConfiguration/Options/CORSOptions/index.mjs +87 -0
  12. package/source/application/configurations/ServerConfiguration/Options/CSRFOptions/CSRFOptionsValue.mjs +178 -0
  13. package/source/application/configurations/ServerConfiguration/Options/CSRFOptions/index.mjs +80 -0
  14. package/source/application/configurations/ServerConfiguration/Options/CompressOptions/CompressOptionsValue.mjs +126 -0
  15. package/source/application/configurations/ServerConfiguration/Options/CompressOptions/index.mjs +81 -0
  16. package/source/application/configurations/ServerConfiguration/Options/CookieParserOptions/CookieParserOptionsValue.mjs +107 -0
  17. package/source/application/configurations/ServerConfiguration/Options/CookieParserOptions/index.mjs +82 -0
  18. package/source/application/configurations/ServerConfiguration/Options/CookieSessionOptions/CookieSessionOptionsValue.mjs +182 -0
  19. package/source/application/configurations/ServerConfiguration/Options/CookieSessionOptions/index.mjs +86 -0
  20. package/source/application/configurations/ServerConfiguration/Options/ListenOptions/ListenOptionsValue.mjs +95 -0
  21. package/source/application/configurations/ServerConfiguration/Options/ListenOptions/index.mjs +47 -0
  22. package/source/application/configurations/ServerConfiguration/Options/MiddlewareOption.mjs +102 -0
  23. package/source/application/configurations/ServerConfiguration/Options/Options.mjs +105 -0
  24. package/source/application/configurations/ServerConfiguration/Options/RateLimitOptions/RateLimitOptionsValue.mjs +164 -0
  25. package/source/application/configurations/ServerConfiguration/Options/RateLimitOptions/index.mjs +84 -0
  26. package/source/application/configurations/ServerConfiguration/Options/RequestOptions/RequestOptionsValue.mjs +108 -0
  27. package/source/application/configurations/ServerConfiguration/Options/RequestOptions/index.mjs +47 -0
  28. package/source/application/configurations/ServerConfiguration/Options/ResponseOptions/ResponseOptionsValue.mjs +101 -0
  29. package/source/application/configurations/ServerConfiguration/Options/ResponseOptions/index.mjs +47 -0
  30. package/source/application/configurations/ServerConfiguration/Options/SecurityHeadersOptions/SecurityHeadersOptionsValue.mjs +272 -0
  31. package/source/application/configurations/ServerConfiguration/Options/SecurityHeadersOptions/index.mjs +85 -0
  32. package/source/application/configurations/ServerConfiguration/Options/ServerOption.mjs +50 -0
  33. package/source/application/configurations/ServerConfiguration/Options/StaticOptions/StaticOptionsValue.mjs +164 -0
  34. package/source/application/configurations/ServerConfiguration/Options/StaticOptions/index.mjs +89 -0
  35. package/source/application/configurations/ServerConfiguration/index.mjs +781 -0
  36. package/source/application/resolvers/TransportResolver/index.mjs +119 -0
  37. package/source/application/use-cases/CloseServer/index.mjs +85 -0
  38. package/source/application/use-cases/ListenServer/index.mjs +98 -0
  39. package/source/core/EventSystem/index.mjs +110 -0
  40. package/source/core/Exchange/ExchangeContext.mjs +291 -0
  41. package/source/core/Exchange/Telemetry.mjs +193 -0
  42. package/source/core/Exchange/index.mjs +604 -0
  43. package/source/core/ExchangeLayerBuilder/index.mjs +133 -0
  44. package/source/core/IPCMain/IPCMain.mjs +507 -0
  45. package/source/core/IPCMain/IPCProtocol.mjs +441 -0
  46. package/source/core/IPCMain/IPCServerChannel.mjs +331 -0
  47. package/source/core/IPCMain/WebSocketAdapter.mjs +189 -0
  48. package/source/core/Request/BodyParser/BodyParser.mjs +197 -0
  49. package/source/core/Request/BodyParser/JSONParser.mjs +70 -0
  50. package/source/core/Request/BodyParser/MultipartParser.mjs +612 -0
  51. package/source/core/Request/BodyParser/URLEncodedParser.mjs +71 -0
  52. package/source/core/Request/RequestHeaders.mjs +177 -0
  53. package/source/core/Request/index.mjs +805 -0
  54. package/source/core/Response/ResponseHeaders.mjs +402 -0
  55. package/source/core/Response/index.mjs +1370 -0
  56. package/source/core/middlewares/CORSMiddleware/index.mjs +289 -0
  57. package/source/core/middlewares/CSRFMiddleware/index.mjs +284 -0
  58. package/source/core/middlewares/CompressMiddleware/index.mjs +460 -0
  59. package/source/core/middlewares/CookieParserMiddleware/index.mjs +194 -0
  60. package/source/core/middlewares/CookieSessionMiddleware/index.mjs +460 -0
  61. package/source/core/middlewares/RateLimitMiddleware/index.mjs +245 -0
  62. package/source/core/middlewares/SecurityHeadersMiddleware/index.mjs +151 -0
  63. package/source/core/middlewares/StaticMiddleware/index.mjs +628 -0
  64. package/source/core/routing/ExchangeLayer/ExchangeMatcher.mjs +127 -0
  65. package/source/core/routing/ExchangeLayer/index.mjs +254 -0
  66. package/source/core/routing/Pipeline/index.mjs +157 -0
  67. package/source/core/routing/Router/Route.mjs +184 -0
  68. package/source/core/routing/Router/index.mjs +523 -0
  69. package/source/core/routing/RouterRegister/OptionsMiddleware.mjs +147 -0
  70. package/source/core/routing/RouterRegister/index.mjs +150 -0
  71. package/source/core/routing/RouterSystem/index.mjs +225 -0
  72. package/source/core/value-objects/Address/index.mjs +281 -0
  73. package/source/core/value-objects/Endpoint/index.mjs +293 -0
  74. package/source/index.mjs +62 -0
  75. package/source/infrastructure/adapters/Http2Adapter/index.mjs +144 -0
  76. package/source/infrastructure/adapters/Http2CleartextAdapter/index.mjs +147 -0
  77. package/source/infrastructure/adapters/HttpAdapter/index.mjs +124 -0
  78. package/source/infrastructure/adapters/HttpsAdapter/index.mjs +124 -0
  79. package/source/infrastructure/adapters/TransportAdapter/createWebSocketOpenRequest.mjs +843 -0
  80. package/source/infrastructure/adapters/TransportAdapter/index.mjs +555 -0
  81. package/source/shared/Identifier/index.mjs +671 -0
  82. package/source/shared/constants/symbols/brands/index.mjs +45 -0
  83. package/source/shared/domain/UUIDV7/index.mjs +346 -0
  84. package/source/shared/errors/BaseError/index.mjs +157 -0
  85. package/source/shared/errors/IPCError/index.mjs +97 -0
  86. package/source/shared/errors/MiddlewareError/index.mjs +106 -0
  87. package/source/shared/errors/PathnamePatternError/index.mjs +103 -0
  88. package/source/shared/errors/RequestError/index.mjs +116 -0
  89. package/source/shared/errors/ResponseError/index.mjs +140 -0
  90. package/source/shared/errors/RouterError/index.mjs +90 -0
  91. package/source/shared/errors/ServerConfigurationError/index.mjs +551 -0
  92. package/source/shared/errors/ServerError/index.mjs +127 -0
  93. package/source/shared/errors/WebSocketError/index.mjs +58 -0
  94. package/source/shared/outcome/ErrorSchema.mjs +361 -0
  95. package/source/shared/outcome/Result/index.mjs +239 -0
  96. package/source/shared/outcome/Validation/index.mjs +208 -0
  97. package/source/shared/validations/Middleware/index.mjs +160 -0
  98. package/source/shared/validations/PathnamePattern/index.mjs +256 -0
  99. package/source/shared/validations/Server/NotFoundHandler/index.mjs +94 -0
  100. package/source/shared/validations/Server/Router/index.mjs +68 -0
  101. package/source/shared/validations/Server/ServerConfiguration/index.mjs +68 -0
  102. package/source/shared/validations/Server/SystemErrorHandler/index.mjs +94 -0
  103. package/source/shared/validations/ServerConfiguration/CORSOptions/index.mjs +310 -0
  104. package/source/shared/validations/ServerConfiguration/CSRFOptions/index.mjs +299 -0
  105. package/source/shared/validations/ServerConfiguration/CompressOptions/index.mjs +204 -0
  106. package/source/shared/validations/ServerConfiguration/CookieParserOptions/index.mjs +102 -0
  107. package/source/shared/validations/ServerConfiguration/CookieSessionOptions/index.mjs +332 -0
  108. package/source/shared/validations/ServerConfiguration/ListenOptions/index.mjs +178 -0
  109. package/source/shared/validations/ServerConfiguration/RateLimitOptions/index.mjs +229 -0
  110. package/source/shared/validations/ServerConfiguration/RequestOptions/index.mjs +132 -0
  111. package/source/shared/validations/ServerConfiguration/ResponseOptions/index.mjs +122 -0
  112. package/source/shared/validations/ServerConfiguration/SecurityHeadersOptions/index.mjs +137 -0
  113. package/source/shared/validations/ServerConfiguration/StaticOptions/index.mjs +248 -0
  114. package/types/index.d.ts +719 -0
@@ -0,0 +1,68 @@
1
+ // @ts-check
2
+ /**
3
+ * @file Router validation utilities.
4
+ *
5
+ * @description Validates objects that implement the Router contract.
6
+ *
7
+ * @module Validation/Router
8
+ */
9
+
10
+ // ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
11
+ // » IMPORT LOCAL MODULES
12
+ import { ROUTER_BRAND } from '#shared/constants/symbols/brands';
13
+ import Validation from '#shared/outcome/Validation';
14
+ import ServerError from '#shared/errors/ServerError';
15
+
16
+ // ━━ FUNCTIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
17
+ /**
18
+ * Validates that a value implements the Router contract.
19
+ *
20
+ * @param {unknown} value - Value to validate.
21
+ * @returns {Validation} Validation result.
22
+ * @example
23
+ * ```js
24
+ * const validation = validateRouter(router);
25
+ *
26
+ * console.log(validation.ok);
27
+ * // => true
28
+ * ```
29
+ */
30
+ export function validateRouter(value) {
31
+ if (value === null || typeof value !== 'object') {
32
+ return Validation.failure({
33
+ error: ServerError,
34
+ message: `Expected a Router instance, received: ${value === null ? 'null' : typeof value}.`,
35
+ code: 'INVALID_ROUTER_TYPE',
36
+ custom: true,
37
+ });
38
+ }
39
+
40
+ if (Reflect.get(value, ROUTER_BRAND) !== true) {
41
+ return Validation.failure({
42
+ error: ServerError,
43
+ message: 'Expected a Router instance.',
44
+ code: 'INVALID_ROUTER_INSTANCE',
45
+ custom: true,
46
+ });
47
+ }
48
+
49
+ return Validation.success();
50
+ }
51
+
52
+ /**
53
+ * Asserts that a value implements the Router contract.
54
+ *
55
+ * @param {unknown} value - Value to assert.
56
+ * @throws {ServerError} If the value does not implement the Router contract.
57
+ * @example
58
+ * ```js
59
+ * assertRouter(router);
60
+ * ```
61
+ */
62
+ export function assertRouter(value) {
63
+ const validation = validateRouter(value);
64
+
65
+ if (!validation.ok) {
66
+ throw validation.error;
67
+ }
68
+ }
@@ -0,0 +1,68 @@
1
+ // @ts-check
2
+ /**
3
+ * @file ServerConfiguration validation utilities.
4
+ *
5
+ * @description Validates objects that implement the ServerConfiguration contract.
6
+ *
7
+ * @module Validation/Server/ServerConfiguration
8
+ */
9
+
10
+ // ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
11
+ // » IMPORT LOCAL MODULES
12
+ import { SERVER_CONFIGURATION_BRAND } from '#shared/constants/symbols/brands';
13
+ import Validation from '#shared/outcome/Validation';
14
+ import ServerError from '#shared/errors/ServerError';
15
+
16
+ // ━━ FUNCTIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
17
+ /**
18
+ * Validates that a value implements the ServerConfiguration contract.
19
+ *
20
+ * @param {unknown} value - Value to validate.
21
+ * @returns {Validation} Validation result.
22
+ * @example
23
+ * ```js
24
+ * const validation = validateServerConfiguration(configuration);
25
+ *
26
+ * console.log(validation.ok);
27
+ * // => true
28
+ * ```
29
+ */
30
+ export function validateServerConfiguration(value) {
31
+ if (value === null || typeof value !== 'object') {
32
+ return Validation.failure({
33
+ error: ServerError,
34
+ message: `Expected a ServerConfiguration instance, received: ${value === null ? 'null' : typeof value}.`,
35
+ code: 'INVALID_SERVER_CONFIGURATION_TYPE',
36
+ custom: true,
37
+ });
38
+ }
39
+
40
+ if (Reflect.get(value, SERVER_CONFIGURATION_BRAND) !== true) {
41
+ return Validation.failure({
42
+ error: ServerError,
43
+ message: 'Expected a ServerConfiguration instance.',
44
+ code: 'INVALID_SERVER_CONFIGURATION_INSTANCE',
45
+ custom: true,
46
+ });
47
+ }
48
+
49
+ return Validation.success();
50
+ }
51
+
52
+ /**
53
+ * Asserts that a value implements the ServerConfiguration contract.
54
+ *
55
+ * @param {unknown} value - Value to assert.
56
+ * @throws {ServerError} If the value does not implement the ServerConfiguration contract.
57
+ * @example
58
+ * ```js
59
+ * assertServerConfiguration(configuration);
60
+ * ```
61
+ */
62
+ export function assertServerConfiguration(value) {
63
+ const validation = validateServerConfiguration(value);
64
+
65
+ if (!validation.ok) {
66
+ throw validation.error;
67
+ }
68
+ }
@@ -0,0 +1,94 @@
1
+ // @ts-check
2
+ /**
3
+ * @file SystemErrorHandler validation utilities.
4
+ *
5
+ * @description Validates handlers used to resolve errors that propagate during
6
+ * request processing.
7
+ *
8
+ * @module Validation/Server/SystemErrorHandler
9
+ */
10
+
11
+ // ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
12
+ // » IMPORT LOCAL MODULES
13
+ import Validation from '#shared/outcome/Validation';
14
+ import ServerError from '#shared/errors/ServerError';
15
+
16
+ // ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
17
+ /**
18
+ * Validates a system error handler.
19
+ *
20
+ * A system error handler must be a function that declares exactly two
21
+ * parameters: `error` and `exchange`.
22
+ *
23
+ * @param {unknown} handler - Handler function to validate.
24
+ * @returns {Validation} Validation result.
25
+ * @example
26
+ * ```js
27
+ * const validation = validateSystemErrorHandler((error, exchange) => {
28
+ * exchange.response.status(500).end();
29
+ * });
30
+ *
31
+ * console.log(validation.ok);
32
+ * // => true
33
+ * ```
34
+ *
35
+ * @example
36
+ * ```js
37
+ * const validation = validateSystemErrorHandler((error) => {});
38
+ *
39
+ * console.log(validation.ok);
40
+ * // => false
41
+ * ```
42
+ */
43
+ export function validateSystemErrorHandler(handler) {
44
+ if (typeof handler !== 'function') {
45
+ return Validation.failure({
46
+ error: ServerError,
47
+ code: 'INVALID_SYSTEM_ERROR_HANDLER_TYPE',
48
+ custom: true,
49
+ options: {
50
+ values: {
51
+ expected: 'function',
52
+ received: typeof handler,
53
+ },
54
+ },
55
+ });
56
+ }
57
+
58
+ if (handler.length !== 2) {
59
+ return Validation.failure({
60
+ error: ServerError,
61
+ code: 'INVALID_SYSTEM_ERROR_HANDLER_ARITY',
62
+ custom: true,
63
+ options: {
64
+ values: {
65
+ expected: 2,
66
+ received: handler.length,
67
+ },
68
+ },
69
+ });
70
+ }
71
+
72
+ return Validation.success();
73
+ }
74
+
75
+ /**
76
+ * Asserts that a system error handler is valid.
77
+ *
78
+ * @param {unknown} handler - Handler function to validate.
79
+ * @throws {ServerError} If the handler is not a function.
80
+ * @throws {ServerError} If the handler does not declare exactly two parameters.
81
+ * @example
82
+ * ```js
83
+ * assertSystemErrorHandler((error, exchange) => {
84
+ * exchange.response.status(500).end();
85
+ * });
86
+ * ```
87
+ */
88
+ export function assertSystemErrorHandler(handler) {
89
+ const validation = validateSystemErrorHandler(handler);
90
+
91
+ if (!validation.ok) {
92
+ throw validation.error;
93
+ }
94
+ }
@@ -0,0 +1,310 @@
1
+ // @ts-check
2
+ /**
3
+ * @file CORS options validation utilities.
4
+ *
5
+ * @description Validates CORS middleware options used by
6
+ * ServerConfiguration, including origin, methods, allowed/exposed headers,
7
+ * credentials, preflight caching, and preflight response behavior.
8
+ *
9
+ * @module Validation/ServerConfiguration/CORSOptions
10
+ */
11
+
12
+ // ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
13
+ // » IMPORT LOCAL MODULES
14
+ import Validation from '#shared/outcome/Validation';
15
+ import ServerConfigurationError from '#shared/errors/ServerConfigurationError';
16
+
17
+ // ━━ CONSTANTS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
18
+ /**
19
+ * Minimum valid HTTP status code for a successful preflight response.
20
+ *
21
+ * @private
22
+ * @type {number}
23
+ */
24
+ const MIN_OPTIONS_SUCCESS_STATUS = 100;
25
+
26
+ /**
27
+ * Maximum valid HTTP status code for a successful preflight response.
28
+ *
29
+ * @private
30
+ * @type {number}
31
+ */
32
+ const MAX_OPTIONS_SUCCESS_STATUS = 599;
33
+
34
+ // ━━ FUNCTIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
35
+ /**
36
+ * Determines whether a value is a non-null plain object.
37
+ *
38
+ * @private
39
+ * @param {unknown} value - Value to inspect.
40
+ * @returns {value is Record<string, unknown>} `true` if the value is a plain object.
41
+ */
42
+ function isPlainObject(value) {
43
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
44
+ }
45
+
46
+ /**
47
+ * Determines whether a value is a non-empty string.
48
+ *
49
+ * @private
50
+ * @param {unknown} value - Value to inspect.
51
+ * @returns {value is string} `true` if the value is a non-empty string.
52
+ */
53
+ function isNonEmptyString(value) {
54
+ return typeof value === 'string' && value.length > 0;
55
+ }
56
+
57
+ /**
58
+ * Determines whether every item in an array is a non-empty string.
59
+ *
60
+ * @private
61
+ * @param {unknown[]} values - Array of values to inspect.
62
+ * @returns {boolean} `true` if every item is a non-empty string.
63
+ */
64
+ function everyNonEmptyString(values) {
65
+ return values.every(isNonEmptyString);
66
+ }
67
+
68
+ /**
69
+ * Validates CORS options.
70
+ *
71
+ * Validation is performed in the following order:
72
+ *
73
+ * 1. `options` may be `undefined`, in which case validation succeeds.
74
+ * 2. `options` must be a plain object.
75
+ * 3. `origin`, if present, must be `true`, a string, or an array of non-empty strings.
76
+ * 4. `methods`, if present, must be an array of non-empty strings.
77
+ * 5. `allowedHeaders`, if present, must be an array of non-empty strings.
78
+ * 6. `exposedHeaders`, if present, must be an array of non-empty strings.
79
+ * 7. `credentials`, if present, must be a boolean.
80
+ * 8. `maxAge`, if present, must be a non-negative integer.
81
+ * 9. `preflightContinue`, if present, must be a boolean.
82
+ * 10. `optionsSuccessStatus`, if present, must be an integer between 100 and 599.
83
+ *
84
+ * @param {unknown} options - CORS options to validate.
85
+ * @returns {Validation} Validation result.
86
+ * @example
87
+ * ```js
88
+ * const validation = validateCORSOptions({ origin: true });
89
+ *
90
+ * console.log(validation.ok);
91
+ * // => true
92
+ * ```
93
+ *
94
+ * @example
95
+ * ```js
96
+ * const validation = validateCORSOptions({ origin: 123 });
97
+ *
98
+ * console.log(validation.ok);
99
+ * // => false
100
+ * ```
101
+ */
102
+ export function validateCORSOptions(options) {
103
+ if (options === undefined) {
104
+ return Validation.success();
105
+ }
106
+
107
+ if (!isPlainObject(options)) {
108
+ return Validation.failure({
109
+ error: ServerConfigurationError,
110
+ code: 'INVALID_CORS_OPTIONS',
111
+ custom: true,
112
+ options: { values: { received: typeof options } },
113
+ });
114
+ }
115
+
116
+ if ('origin' in options) {
117
+ const { origin } = options;
118
+
119
+ const isValidOriginType =
120
+ origin === true ||
121
+ typeof origin === 'string' ||
122
+ (Array.isArray(origin) && everyNonEmptyString(origin));
123
+
124
+ if (!isValidOriginType) {
125
+ if (Array.isArray(origin)) {
126
+ return Validation.failure({
127
+ error: ServerConfigurationError,
128
+ code: 'INVALID_CORS_ORIGIN_ITEM',
129
+ custom: true,
130
+ options: { details: { origin } },
131
+ });
132
+ }
133
+
134
+ return Validation.failure({
135
+ error: ServerConfigurationError,
136
+ code: 'INVALID_CORS_ORIGIN_TYPE',
137
+ custom: true,
138
+ options: { values: { received: typeof origin } },
139
+ });
140
+ }
141
+ }
142
+
143
+ if ('methods' in options) {
144
+ const { methods } = options;
145
+
146
+ if (!Array.isArray(methods)) {
147
+ return Validation.failure({
148
+ error: ServerConfigurationError,
149
+ code: 'INVALID_CORS_METHODS_TYPE',
150
+ custom: true,
151
+ options: { values: { received: typeof methods } },
152
+ });
153
+ }
154
+
155
+ if (!everyNonEmptyString(methods)) {
156
+ return Validation.failure({
157
+ error: ServerConfigurationError,
158
+ code: 'INVALID_CORS_METHODS_ITEM',
159
+ custom: true,
160
+ options: { details: { methods } },
161
+ });
162
+ }
163
+ }
164
+
165
+ if ('allowedHeaders' in options) {
166
+ const { allowedHeaders } = options;
167
+
168
+ if (!Array.isArray(allowedHeaders)) {
169
+ return Validation.failure({
170
+ error: ServerConfigurationError,
171
+ code: 'INVALID_CORS_ALLOWED_HEADERS_TYPE',
172
+ custom: true,
173
+ options: { values: { received: typeof allowedHeaders } },
174
+ });
175
+ }
176
+
177
+ if (!everyNonEmptyString(allowedHeaders)) {
178
+ return Validation.failure({
179
+ error: ServerConfigurationError,
180
+ code: 'INVALID_CORS_ALLOWED_HEADERS_ITEM',
181
+ custom: true,
182
+ options: { details: { allowedHeaders } },
183
+ });
184
+ }
185
+ }
186
+
187
+ if ('exposedHeaders' in options) {
188
+ const { exposedHeaders } = options;
189
+
190
+ if (!Array.isArray(exposedHeaders)) {
191
+ return Validation.failure({
192
+ error: ServerConfigurationError,
193
+ code: 'INVALID_CORS_EXPOSED_HEADERS_TYPE',
194
+ custom: true,
195
+ options: { values: { received: typeof exposedHeaders } },
196
+ });
197
+ }
198
+
199
+ if (!everyNonEmptyString(exposedHeaders)) {
200
+ return Validation.failure({
201
+ error: ServerConfigurationError,
202
+ code: 'INVALID_CORS_EXPOSED_HEADERS_ITEM',
203
+ custom: true,
204
+ options: { details: { exposedHeaders } },
205
+ });
206
+ }
207
+ }
208
+
209
+ if ('credentials' in options) {
210
+ const { credentials } = options;
211
+
212
+ if (typeof credentials !== 'boolean') {
213
+ return Validation.failure({
214
+ error: ServerConfigurationError,
215
+ code: 'INVALID_CORS_CREDENTIALS_TYPE',
216
+ custom: true,
217
+ options: { values: { received: typeof credentials } },
218
+ });
219
+ }
220
+ }
221
+
222
+ if ('maxAge' in options) {
223
+ const { maxAge } = options;
224
+
225
+ if (typeof maxAge !== 'number') {
226
+ return Validation.failure({
227
+ error: ServerConfigurationError,
228
+ code: 'INVALID_CORS_MAX_AGE_TYPE',
229
+ custom: true,
230
+ options: { values: { received: typeof maxAge } },
231
+ });
232
+ }
233
+
234
+ if (!Number.isInteger(maxAge) || maxAge < 0) {
235
+ return Validation.failure({
236
+ error: ServerConfigurationError,
237
+ code: 'INVALID_CORS_MAX_AGE_RANGE',
238
+ custom: true,
239
+ options: { details: { maxAge } },
240
+ });
241
+ }
242
+ }
243
+
244
+ if ('preflightContinue' in options) {
245
+ const { preflightContinue } = options;
246
+
247
+ if (typeof preflightContinue !== 'boolean') {
248
+ return Validation.failure({
249
+ error: ServerConfigurationError,
250
+ code: 'INVALID_CORS_PREFLIGHT_CONTINUE_TYPE',
251
+ custom: true,
252
+ options: { values: { received: typeof preflightContinue } },
253
+ });
254
+ }
255
+ }
256
+
257
+ if ('optionsSuccessStatus' in options) {
258
+ const { optionsSuccessStatus } = options;
259
+
260
+ if (typeof optionsSuccessStatus !== 'number') {
261
+ return Validation.failure({
262
+ error: ServerConfigurationError,
263
+ code: 'INVALID_CORS_OPTIONS_SUCCESS_STATUS_TYPE',
264
+ custom: true,
265
+ options: { values: { received: typeof optionsSuccessStatus } },
266
+ });
267
+ }
268
+
269
+ if (
270
+ !Number.isInteger(optionsSuccessStatus) ||
271
+ optionsSuccessStatus < MIN_OPTIONS_SUCCESS_STATUS ||
272
+ optionsSuccessStatus > MAX_OPTIONS_SUCCESS_STATUS
273
+ ) {
274
+ return Validation.failure({
275
+ error: ServerConfigurationError,
276
+ code: 'INVALID_CORS_OPTIONS_SUCCESS_STATUS_RANGE',
277
+ custom: true,
278
+ options: { details: { optionsSuccessStatus } },
279
+ });
280
+ }
281
+ }
282
+
283
+ return Validation.success();
284
+ }
285
+
286
+ /**
287
+ * Asserts that CORS options are valid.
288
+ *
289
+ * @param {unknown} options - CORS options to validate.
290
+ * @throws {ServerConfigurationError} If `options` is not a plain object.
291
+ * @throws {ServerConfigurationError} If `origin` is not `true`, a string, or an array of non-empty strings.
292
+ * @throws {ServerConfigurationError} If `methods` is not an array of non-empty strings.
293
+ * @throws {ServerConfigurationError} If `allowedHeaders` is not an array of non-empty strings.
294
+ * @throws {ServerConfigurationError} If `exposedHeaders` is not an array of non-empty strings.
295
+ * @throws {ServerConfigurationError} If `credentials` is not a boolean.
296
+ * @throws {ServerConfigurationError} If `maxAge` is not a non-negative integer.
297
+ * @throws {ServerConfigurationError} If `preflightContinue` is not a boolean.
298
+ * @throws {ServerConfigurationError} If `optionsSuccessStatus` is not an integer between 100 and 599.
299
+ * @example
300
+ * ```js
301
+ * assertCORSOptions({ origin: true, credentials: true });
302
+ * ```
303
+ */
304
+ export function assertCORSOptions(options) {
305
+ const validation = validateCORSOptions(options);
306
+
307
+ if (!validation.ok) {
308
+ throw validation.error;
309
+ }
310
+ }