@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,58 @@
1
+ /**
2
+ * @file Defines the internal error type used for WebSocket failures.
3
+ *
4
+ * @description WebSocketError provides structured errors for WebSocket protocol
5
+ * and connection failures, including errors that map to a WebSocket close code.
6
+ *
7
+ * @private
8
+ * @module WebSocketError
9
+ */
10
+
11
+ import BaseError from '../BaseError/index.mjs';
12
+
13
+ /**
14
+ * @typedef {import('../BaseError/index.mjs').ErrorOptions} ErrorOptions
15
+ */
16
+
17
+ /**
18
+ * Internal error type used for WebSocket protocol and connection failures.
19
+ *
20
+ * @augments BaseError
21
+ * @private
22
+ */
23
+ class WebSocketError extends BaseError {
24
+ /**
25
+ * Creates a new {@link WebSocketError}.
26
+ *
27
+ * @param {string} identifier - A predefined WebSocket error identifier or a custom error message.
28
+ * @param {ErrorOptions} [options] - Additional error options.
29
+ * @example
30
+ * ```js
31
+ * throw new WebSocketError('WS_MESSAGE_TOO_BIG', {
32
+ * values: {
33
+ * size: payloadLength,
34
+ * maxSize: maxMessageSize,
35
+ * },
36
+ * });
37
+ * ```
38
+ */
39
+ constructor(identifier, options = {}) {
40
+ super(identifier, options);
41
+ }
42
+
43
+ /**
44
+ * Message template catalog indexed by WebSocket error identifier.
45
+ *
46
+ * @type {Map<string, string>}
47
+ * @private
48
+ * @static
49
+ */
50
+ static MESSAGES = new Map([
51
+ [
52
+ 'WS_MESSAGE_TOO_BIG',
53
+ 'WebSocket payload size ({size} bytes) exceeds maximum allowed message size ({maxSize} bytes).',
54
+ ],
55
+ ]);
56
+ }
57
+
58
+ export default WebSocketError;
@@ -0,0 +1,361 @@
1
+ /**
2
+ * @file Defines the structured schema used to describe operation errors.
3
+ *
4
+ * @description ErrorSchema normalizes literal-message and code-based error
5
+ * definitions into a consistent schema used to instantiate Error objects.
6
+ *
7
+ * @module ErrorSchema
8
+ */
9
+
10
+ // ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
11
+ // » IMPORT NATIVE NODEJS MODULES
12
+ import { inspect } from 'node:util';
13
+
14
+ // » IMPORT THIRD PARTY MODULES
15
+
16
+ // » IMPORT LOCAL MODULES
17
+
18
+ // ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
19
+ /**
20
+ * Represents an Error constructor.
21
+ *
22
+ * @typedef {new (identifier: string, options?: Record<string, unknown>) => Error} ErrorConstructor
23
+ */
24
+
25
+ /**
26
+ * Represents options passed to a native Error constructor.
27
+ *
28
+ * @typedef {object} NativeErrorOptions
29
+ * @property {unknown} [cause] - Cause associated with the error.
30
+ */
31
+
32
+ /**
33
+ * Represents a native Error schema definition.
34
+ *
35
+ * @typedef {object} NativeErrorSchema
36
+ * @property {ErrorConstructor} error - Error constructor used to create the error.
37
+ * @property {string} message - Literal error message.
38
+ * @property {NativeErrorOptions} [options] - Options passed to the Error constructor.
39
+ * @property {false} custom - Indicates that the constructor represents a native Error.
40
+ * @property {string} code - Error identifier.
41
+ */
42
+
43
+ /**
44
+ * Represents options passed to a custom Error constructor.
45
+ *
46
+ * @typedef {object} CustomErrorOptions
47
+ * @property {Record<string, unknown>} [values] - Values used to resolve message placeholders.
48
+ * @property {Record<string, unknown>} [details] - Additional contextual information about the error.
49
+ * @property {unknown} [cause] - Cause associated with the error.
50
+ */
51
+
52
+ /**
53
+ * Represents a custom Error schema definition.
54
+ *
55
+ * @typedef {object} CustomErrorSchema
56
+ * @property {ErrorConstructor} error - Error constructor used to create the error.
57
+ * @property {string} code - Error code used to resolve the error message and error identifier.
58
+ * @property {string} [message] - Literal error message.
59
+ * @property {CustomErrorOptions} [options] - Options passed to the Error constructor.
60
+ * @property {true} custom - Indicates that the constructor represents a custom Error.
61
+ */
62
+
63
+ /**
64
+ * Represents a supported Error schema definition.
65
+ *
66
+ * @typedef {NativeErrorSchema|CustomErrorSchema} ErrorSchemaDefinition
67
+ * @exports ErrorSchemaDefinition
68
+ */
69
+
70
+ /**
71
+ * Represents the normalized state of an ErrorSchema.
72
+ *
73
+ * @typedef {object} ErrorSchemaState
74
+ * @property {string} [message] - Literal error message.
75
+ * @property {string} type - Error constructor name.
76
+ * @property {Record<string, unknown>} options - Options passed to the Error constructor.
77
+ * @property {string} code - Error identifier.
78
+ */
79
+
80
+ // ━━ FUNCTIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
81
+ /**
82
+ * Determines whether a value is a plain object.
83
+ *
84
+ * @private
85
+ * @param {unknown} value - Value to check.
86
+ * @returns {boolean} Whether the value is a plain object.
87
+ */
88
+ const isObject = value => Object.prototype.toString.call(value) === '[object Object]';
89
+
90
+ // ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
91
+ /**
92
+ * Defines a structured error schema and creates its corresponding Error.
93
+ *
94
+ * @classdesc ErrorSchema accepts either a literal error message or a domain
95
+ * error code. Message-based schemas create errors directly from the message,
96
+ * while code-based schemas preserve the code, values, and details required by
97
+ * the consuming error system.
98
+ */
99
+ class ErrorSchema {
100
+ /**
101
+ * Normalized error schema definition.
102
+ *
103
+ * @type {ErrorSchemaState}
104
+ */
105
+ #schema;
106
+
107
+ /**
108
+ * Error constructor used to reconstruct the error.
109
+ *
110
+ * @type {typeof Error}
111
+ */
112
+ #error;
113
+
114
+ /**
115
+ * Indicates whether the error uses the custom Error flow.
116
+ *
117
+ * @type {boolean}
118
+ */
119
+ #custom;
120
+
121
+ /**
122
+ * Creates an error schema from an Error definition.
123
+ *
124
+ * The schema must provide an Error constructor. Native Errors require a
125
+ * message and code, while custom Errors require a code and may provide a message.
126
+ *
127
+ * @param {ErrorSchemaDefinition} schema - Error schema configuration.
128
+ * @throws {TypeError} If `error` is not an Error constructor.
129
+ * @throws {TypeError} If a native Error does not provide a message.
130
+ * @throws {TypeError} If message is not a string.
131
+ * @throws {TypeError} If a custom Error provides neither message nor code.
132
+ * @example
133
+ * ```js
134
+ * const schema = new ErrorSchema({
135
+ * error: TypeError,
136
+ * message: 'Invalid request.'
137
+ * });
138
+ *
139
+ * schema.error;
140
+ * ```
141
+ *
142
+ * @example
143
+ * ```js
144
+ * const schema = new ErrorSchema({
145
+ * error: RequestError,
146
+ * code: 'INVALID_REQUEST',
147
+ * options: {
148
+ * values: { field: 'email' },
149
+ * details: { source: 'RequestValidator' }
150
+ * },
151
+ * custom: true
152
+ * });
153
+ *
154
+ * schema.error;
155
+ * ```
156
+ */
157
+ constructor(schema) {
158
+ if (!isObject(schema)) {
159
+ throw new TypeError('Schema options must be an object.');
160
+ }
161
+
162
+ if (!('error' in schema)) {
163
+ throw new TypeError('ErrorSchema error constructor is required.');
164
+ }
165
+
166
+ if (!ErrorSchema.isErrorConstructor(schema.error)) {
167
+ throw new TypeError('Expected an Error constructor');
168
+ }
169
+
170
+ const custom = schema.custom ?? false;
171
+
172
+ if (!custom && !('message' in schema)) {
173
+ throw new TypeError('Message is required for a native Error');
174
+ }
175
+
176
+ if (!custom && !('code' in schema)) {
177
+ throw new TypeError('Code is required for Error Schema');
178
+ }
179
+
180
+ if ('message' in schema && typeof schema.message !== 'string') {
181
+ throw new TypeError('Message must be a string');
182
+ }
183
+
184
+ if (custom && !('message' in schema) && !('code' in schema)) {
185
+ throw new TypeError('Message or code is required for a Custom Error');
186
+ }
187
+
188
+ this.#error = schema.error;
189
+ this.#custom = custom;
190
+
191
+ this.#schema = Object.freeze({
192
+ message: schema.message,
193
+ type: schema.error.name,
194
+ options: schema.options ?? {},
195
+ code: schema.code,
196
+ });
197
+ }
198
+
199
+ /**
200
+ * Normalized error schema.
201
+ *
202
+ * @readonly
203
+ * @type {object}
204
+ */
205
+ get schema() {
206
+ return this.#schema;
207
+ }
208
+
209
+ /**
210
+ * Creates a new Error instance from the configured schema.
211
+ *
212
+ * @readonly
213
+ * @type {Error}
214
+ */
215
+ get error() {
216
+ const { message, options, code } = this.#schema;
217
+
218
+ if (this.#custom) {
219
+ return new this.#error(message ?? code, options);
220
+ }
221
+
222
+ return new this.#error(message, options);
223
+ }
224
+
225
+ /**
226
+ * Custom string tag for type identification.
227
+ *
228
+ * @readonly
229
+ * @type {string}
230
+ */
231
+ get [Symbol.toStringTag]() {
232
+ return `${this.constructor.name}`;
233
+ }
234
+
235
+ /**
236
+ * Creates a plain object representation of the error schema.
237
+ *
238
+ * @returns {ErrorSchemaState} Normalized error schema.
239
+ * @example
240
+ * ```js
241
+ * const schema = new ErrorSchema({
242
+ * error: TypeError,
243
+ * message: 'Invalid middleware.',
244
+ * code: 'INVALID_MIDDLEWARE',
245
+ * custom: false
246
+ * });
247
+ *
248
+ * console.log(schema.toObject());
249
+ * // {
250
+ * // message: 'Invalid middleware.',
251
+ * // type: 'TypeError',
252
+ * // options: {},
253
+ * // code: 'INVALID_MIDDLEWARE'
254
+ * // }
255
+ * ```
256
+ */
257
+ toObject() {
258
+ return { ...this.#schema };
259
+ }
260
+
261
+ /**
262
+ * Creates a JSON-compatible representation of the error schema.
263
+ *
264
+ * @returns {ErrorSchemaState} Normalized error schema.
265
+ * @example
266
+ * ```js
267
+ * const schema = new ErrorSchema({
268
+ * error: RequestError,
269
+ * code: 'INVALID_REQUEST',
270
+ * options: {
271
+ * values: { field: 'email' },
272
+ * details: { source: 'RequestValidator' }
273
+ * },
274
+ * custom: true
275
+ * });
276
+ *
277
+ * console.log(JSON.stringify(schema));
278
+ * // {"message":undefined,"type":"RequestError",
279
+ * // "options":{"values":{"field":"email"},
280
+ * // "details":{"source":"RequestValidator"}},
281
+ * // "code":"INVALID_REQUEST"}
282
+ * ```
283
+ */
284
+ toJSON() {
285
+ const schema = this.toObject();
286
+ const options = schema.options;
287
+
288
+ const base = {
289
+ message: schema.message,
290
+ type: schema.type,
291
+ code: schema.code,
292
+ };
293
+
294
+ if ('cause' in options && options.cause instanceof Error) {
295
+ const { cause, ...rest } = options;
296
+
297
+ return {
298
+ ...base,
299
+ options: {
300
+ ...rest,
301
+ cause: {
302
+ name: cause.name,
303
+ message: cause.message,
304
+ stack: cause.stack,
305
+ },
306
+ },
307
+ };
308
+ }
309
+
310
+ return {
311
+ ...base,
312
+ options,
313
+ };
314
+ }
315
+
316
+ /**
317
+ * Custom inspect implementation for Node.js `util.inspect()`.
318
+ *
319
+ * @returns {string} Custom string representation for debugging.
320
+ * @example
321
+ * ```js
322
+ * const schema = new ErrorSchema({
323
+ * error: TypeError,
324
+ * message: 'Invalid middleware.',
325
+ * code: 'INVALID_MIDDLEWARE',
326
+ * custom: false
327
+ * });
328
+ *
329
+ * console.log(inspect(schema));
330
+ * // ErrorSchema({
331
+ * // message: 'Invalid middleware.',
332
+ * // type: 'TypeError',
333
+ * // options: {},
334
+ * // code: 'INVALID_MIDDLEWARE'
335
+ * // })
336
+ * ```
337
+ */
338
+ [inspect.custom]() {
339
+ return `${this.constructor.name}(${inspect(this.toObject(), { colors: true, depth: 2 })})`;
340
+ }
341
+
342
+ /**
343
+ * Determines whether a value is an Error constructor.
344
+ *
345
+ * @param {unknown} error - Value to check.
346
+ * @returns {boolean} Whether `error` is a constructor that inherits from `Error`.
347
+ * @example
348
+ * ```js
349
+ * ErrorSchema.isErrorConstructor(TypeError); // true
350
+ * ErrorSchema.isErrorConstructor(RangeError); // true
351
+ * ErrorSchema.isErrorConstructor(Math); // false
352
+ * ```
353
+ */
354
+ static isErrorConstructor(error) {
355
+ if (error === Error) return true;
356
+ if (typeof error !== 'function') return false;
357
+ return Object.prototype.isPrototypeOf.call(Error.prototype, error.prototype);
358
+ }
359
+ }
360
+ // ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
361
+ export default ErrorSchema;
@@ -0,0 +1,239 @@
1
+ // @ts-check
2
+ /**
3
+ * @file Core result module for representing strict operation outcomes.
4
+ *
5
+ * @description Provides the {@link Result} class for explicitly representing
6
+ * successful and failed operations without relying on ambiguous `undefined`
7
+ * states.
8
+ *
9
+ * @module Result
10
+ */
11
+
12
+ // ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
13
+ // » IMPORT LOCAL MODULES
14
+ import ErrorSchema from '../ErrorSchema.mjs';
15
+
16
+ // ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
17
+ /**
18
+ * Represents options passed to a native Error constructor.
19
+ *
20
+ * @typedef {object} NativeErrorOptions
21
+ * @property {unknown} [cause] - Cause associated with the error.
22
+ */
23
+
24
+ /**
25
+ * Represents a native Error schema definition.
26
+ *
27
+ * @typedef {object} NativeErrorSchema
28
+ * @property {typeof Error} error - Error constructor used to create the error.
29
+ * @property {string} message - Literal error message.
30
+ * @property {NativeErrorOptions} [options] - Options passed to the Error constructor.
31
+ * @property {false} custom - Indicates that the constructor represents a native Error.
32
+ * @property {string} code - Error identifier.
33
+ */
34
+
35
+ /**
36
+ * Represents options passed to a custom Error constructor.
37
+ *
38
+ * @typedef {object} CustomErrorOptions
39
+ * @property {Record<string, unknown>} [values] - Values used to resolve message placeholders.
40
+ * @property {Record<string, unknown>} [details] - Additional contextual information about the error.
41
+ * @property {unknown} [cause] - Cause associated with the error.
42
+ */
43
+
44
+ /**
45
+ * Represents a custom Error schema definition.
46
+ *
47
+ * @typedef {object} CustomErrorSchema
48
+ * @property {typeof Error} error - Error constructor used to create the error.
49
+ * @property {string} code - Error code used to resolve the error message and error identifier.
50
+ * @property {string} [message] - Literal error message.
51
+ * @property {CustomErrorOptions} [options] - Options passed to the Error constructor.
52
+ * @property {true} custom - Indicates that the constructor represents a custom Error.
53
+ */
54
+
55
+ /**
56
+ * Represents a supported Error schema definition.
57
+ *
58
+ * @typedef {NativeErrorSchema|CustomErrorSchema} ErrorSchemaDefinition
59
+ */
60
+
61
+ /**
62
+ * Represents the successful state of a {@link Result} instance.
63
+ *
64
+ * @template T
65
+ * @typedef {object} ResultSuccess
66
+ * @property {true} ok - Indicates that the operation succeeded.
67
+ * @property {T} value - Successful value produced by the operation.
68
+ * @property {undefined} [error] - Always undefined for a successful result.
69
+ */
70
+
71
+ /**
72
+ * Represents the failed state of a {@link Result} instance.
73
+ *
74
+ * @typedef {object} ResultFailure
75
+ * @property {false} ok - Indicates that the operation failed.
76
+ * @property {undefined} [value] - Always undefined for a failed result.
77
+ * @property {ErrorSchema} error - Error schema describing the operation failure.
78
+ */
79
+
80
+ /**
81
+ * Represents the internal state of a {@link Result} instance.
82
+ *
83
+ * @template T
84
+ * @typedef {ResultSuccess<T>|ResultFailure} ResultState
85
+ */
86
+
87
+ // ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
88
+ /**
89
+ * Represents the strict outcome of an operation that may yield a value or an
90
+ * error.
91
+ *
92
+ * @template T
93
+ */
94
+ class Result {
95
+ /**
96
+ * Stores the internal result state.
97
+ *
98
+ * @type {ResultState<T>}
99
+ */
100
+ #result;
101
+
102
+ /**
103
+ * Creates a Result instance.
104
+ *
105
+ * @param {boolean} ok - Whether the operation succeeded.
106
+ * @param {T} [value] - The successful value.
107
+ * @param {ErrorSchemaDefinition} [error] - Required when ok is false.
108
+ * @throws {TypeError} If the combination of ok, value, and error is invalid.
109
+ * @example
110
+ * ```js
111
+ * const result = new Result(true, { id: '123' });
112
+ * ```
113
+ *
114
+ * @example
115
+ * ```js
116
+ * const result = new Result(false, undefined, {
117
+ * error: RangeError,
118
+ * message: 'Value is out of range.',
119
+ * custom: false,
120
+ * code: 'ERR_RANGE',
121
+ * });
122
+ * ```
123
+ */
124
+ constructor(ok, value, error) {
125
+ if (typeof ok !== 'boolean') {
126
+ throw new TypeError('Result status (ok) must be a boolean.');
127
+ }
128
+
129
+ if (ok === true) {
130
+ if (value === undefined) {
131
+ throw new TypeError('A successful Result must contain a value.');
132
+ }
133
+ if (error !== undefined) {
134
+ throw new TypeError('A successful Result cannot contain an error.');
135
+ }
136
+
137
+ this.#result = { ok, value, error };
138
+ } else {
139
+ if (error === undefined) {
140
+ throw new TypeError('A failed Result must contain an error.');
141
+ }
142
+
143
+ if (value !== undefined) {
144
+ throw new TypeError('A failed Result cannot contain a value.');
145
+ }
146
+
147
+ this.#result = {
148
+ ok,
149
+ value: undefined,
150
+ error: new ErrorSchema(error),
151
+ };
152
+ }
153
+ }
154
+
155
+ /**
156
+ * Indicates whether the operation succeeded.
157
+ *
158
+ * @readonly
159
+ * @type {boolean}
160
+ */
161
+ get ok() {
162
+ return this.#result.ok;
163
+ }
164
+
165
+ /**
166
+ * The successful value of the operation.
167
+ *
168
+ * @readonly
169
+ * @type {T}
170
+ * @throws {Error} If the result is not ok.
171
+ */
172
+ get value() {
173
+ if (this.#result.ok === false) {
174
+ throw new Error('Cannot access value of a failed Result.');
175
+ }
176
+
177
+ return this.#result.value;
178
+ }
179
+
180
+ /**
181
+ * The instantiated error of the failed operation.
182
+ *
183
+ * @readonly
184
+ * @type {Error}
185
+ * @throws {Error} If the result is ok.
186
+ */
187
+ get error() {
188
+ if (this.#result.ok === true) {
189
+ throw new Error('Cannot access error of a successful Result.');
190
+ }
191
+
192
+ return this.#result.error.error;
193
+ }
194
+
195
+ /**
196
+ * Creates a successful {@link Result}.
197
+ *
198
+ * @template U
199
+ * @param {U} [value] - The successful value.
200
+ * @returns {Result<U>} A successful Result instance.
201
+ * @example
202
+ * ```js
203
+ * const result = Result.success({ id: '123' });
204
+ *
205
+ * if (result.ok) {
206
+ * console.log(result.value.id);
207
+ * }
208
+ * ```
209
+ */
210
+ static success(value) {
211
+ return new Result(true, value);
212
+ }
213
+
214
+ /**
215
+ * Creates a failed {@link Result}.
216
+ *
217
+ * @param {ErrorSchemaDefinition} error - The error details describing the failure.
218
+ * @returns {Result<never>} A failed Result instance.
219
+ * @example
220
+ * ```js
221
+ * const result = Result.failure({
222
+ * error: RangeError,
223
+ * message: 'Value is out of range.',
224
+ * custom: false,
225
+ * code: 'ERR_RANGE',
226
+ * });
227
+ *
228
+ * if (!result.ok) {
229
+ * console.log(result.error);
230
+ * }
231
+ * ```
232
+ */
233
+ static failure(error) {
234
+ return /** @type {Result<never>} */ (new Result(false, undefined, error));
235
+ }
236
+ }
237
+
238
+ // ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
239
+ export default Result;