@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,441 @@
1
+ // @ts-check
2
+ /**
3
+ * @file Defines the IPC wire protocol.
4
+ *
5
+ * @description
6
+ * Validates incoming IPC requests and serializes outgoing IPC responses.
7
+ *
8
+ * IPCProtocol is the sole owner of wire-format normalization. Higher layers
9
+ * pass raw handler errors to this protocol instead of constructing IPC error
10
+ * payloads themselves.
11
+ *
12
+ * @module IPCProtocol
13
+ */
14
+
15
+ // ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
16
+ // » IMPORT LOCAL MODULES
17
+ import Result from '#shared/outcome/Result';
18
+
19
+ // ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
20
+ /**
21
+ * Represents an IPC invocation request.
22
+ *
23
+ * An invocation expects a matching response carrying the same `id`.
24
+ *
25
+ * @typedef {object} IPCInvokeRequest
26
+ * @property {'invoke'} type - Identifies the request as an invocation.
27
+ * @property {string} channel - IPC channel targeted by the request.
28
+ * @property {string} id - Unique invocation identifier.
29
+ * @property {unknown} [data] - Application-defined data associated with the request.
30
+ */
31
+
32
+ /**
33
+ * Represents an IPC event request.
34
+ *
35
+ * An event does not expect a response.
36
+ *
37
+ * @typedef {object} IPCEventRequest
38
+ * @property {'event'} type - Identifies the request as an event.
39
+ * @property {string} channel - IPC channel targeted by the request.
40
+ * @property {unknown} [data] - Application-defined data associated with the request.
41
+ */
42
+
43
+ /**
44
+ * Represents an IPC request transmitted through the wire protocol.
45
+ *
46
+ * @typedef {IPCInvokeRequest|IPCEventRequest} IPCRequest
47
+ */
48
+
49
+ /**
50
+ * Represents the normalized error payload exposed through the IPC wire format.
51
+ *
52
+ * @typedef {object} IPCError
53
+ * @property {string} code - Machine-readable error code.
54
+ * @property {string} message - Human-readable error message.
55
+ */
56
+
57
+ /**
58
+ * Represents a successful IPC response containing a handler result.
59
+ *
60
+ * @typedef {object} IPCSuccessResponse
61
+ * @property {'response'} type - Identifies the message as an IPC response.
62
+ * @property {string} id - Invocation identifier associated with the request.
63
+ * @property {unknown} result - Handler result returned to the caller.
64
+ */
65
+
66
+ /**
67
+ * Represents a failed IPC response containing a normalized error.
68
+ *
69
+ * @typedef {object} IPCErrorResponse
70
+ * @property {'response'} type - Identifies the message as an IPC response.
71
+ * @property {string} id - Invocation identifier associated with the request.
72
+ * @property {IPCError} error - Normalized error returned to the caller.
73
+ */
74
+
75
+ /**
76
+ * Represents an IPC response transmitted through the wire protocol.
77
+ *
78
+ * @typedef {IPCSuccessResponse|IPCErrorResponse} IPCResponse
79
+ */
80
+
81
+ // ━━ CONSTANTS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
82
+ /**
83
+ * Error code used when an incoming IPC payload cannot be decoded as a valid
84
+ * request.
85
+ *
86
+ * @type {string}
87
+ */
88
+ const IPC_BAD_MESSAGE = 'IPC_BAD_MESSAGE';
89
+
90
+ // ━━ FUNCTIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
91
+ /**
92
+ * Determines whether a value is a non-null plain object.
93
+ *
94
+ * @private
95
+ * @param {unknown} value - Value to inspect.
96
+ * @returns {value is Record<string, unknown>} `true` if the value is a plain object.
97
+ */
98
+ function isPlainObject(value) {
99
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
100
+ }
101
+
102
+ // ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
103
+ /**
104
+ * Implements the IPC wire protocol for decoding requests and serializing responses.
105
+ *
106
+ * @classdesc IPCProtocol validates incoming IPC requests and normalizes outgoing response
107
+ * payloads without exposing wire-format concerns to higher layers.
108
+ *
109
+ */
110
+ class IPCProtocol {
111
+ /**
112
+ * Decodes and validates an incoming IPC request.
113
+ *
114
+ * @param {string} data - JSON-encoded IPC request to decode.
115
+ * @returns {IPCRequest} Decoded and validated IPC request.
116
+ * @throws {TypeError} If the payload is not valid JSON.
117
+ * @throws {Error} If the payload does not satisfy the IPC request contract.
118
+ *
119
+ * @example
120
+ * ```js
121
+ * const request = protocol.decode(
122
+ * '{"type":"invoke","channel":"users:get","id":"42"}',
123
+ * );
124
+ *
125
+ * console.log(request.channel);
126
+ * // 'users:get'
127
+ * ```
128
+ */
129
+ decode(data) {
130
+ const result = this.#ensureRequest(this.#parse(data));
131
+
132
+ if (!result.ok) {
133
+ throw result.error;
134
+ }
135
+
136
+ return result.value;
137
+ }
138
+
139
+ /**
140
+ * Serializes a validated IPC response into its wire representation.
141
+ *
142
+ * @param {IPCResponse} response - Validated IPC response to serialize.
143
+ * @returns {string} JSON-encoded IPC response.
144
+ *
145
+ * @example
146
+ * ```js
147
+ * const data = protocol.encode({
148
+ * type: 'response',
149
+ * id: '42',
150
+ * result: { name: 'Victor' },
151
+ * });
152
+ *
153
+ * console.log(data);
154
+ * // '{"type":"response","id":"42","result":{"name":"Victor"}}'
155
+ * ```
156
+ */
157
+ encode(response) {
158
+ return JSON.stringify(response);
159
+ }
160
+
161
+ /**
162
+ * Creates and serializes a successful IPC response.
163
+ *
164
+ * @param {string} id - Invocation identifier associated with the request.
165
+ * @param {unknown} result - Handler result returned to the caller.
166
+ * @returns {string} JSON-encoded successful IPC response.
167
+ *
168
+ * @example
169
+ * ```js
170
+ * const data = protocol.success('42', { name: 'Victor' });
171
+ *
172
+ * console.log(data);
173
+ * // '{"type":"response","id":"42","result":{"name":"Victor"}}'
174
+ * ```
175
+ */
176
+ success(id, result) {
177
+ return this.encode({
178
+ type: 'response',
179
+ id,
180
+ result,
181
+ });
182
+ }
183
+
184
+ /**
185
+ * Creates and serializes an IPC error response from an arbitrary error value.
186
+ *
187
+ * The supplied error is normalized to the IPC wire error format before the
188
+ * response is serialized.
189
+ *
190
+ * @param {string} id - Invocation identifier associated with the request.
191
+ * @param {unknown} error - Error value produced while handling the request.
192
+ * @returns {string} JSON-encoded IPC error response.
193
+ *
194
+ * @example
195
+ * ```js
196
+ * const data = protocol.error('42', new Error('User not found'));
197
+ *
198
+ * console.log(data);
199
+ * // '{"type":"response","id":"42","error":{"code":"IPC_HANDLER_ERROR","message":"User not found"}}'
200
+ * ```
201
+ */
202
+ error(id, error) {
203
+ return this.encode({
204
+ type: 'response',
205
+ id,
206
+ error: this.#normalizeError(error),
207
+ });
208
+ }
209
+
210
+ /**
211
+ * Creates and serializes an IPC error response for a malformed request.
212
+ *
213
+ * This method is used when a valid invocation identifier can still be
214
+ * recovered from the malformed payload.
215
+ *
216
+ * @param {string} id - Invocation identifier recovered from the request.
217
+ * @param {string} message - Description of the malformed request.
218
+ * @returns {string} JSON-encoded IPC error response.
219
+ *
220
+ * @example
221
+ * ```js
222
+ * const data = protocol.badMessage('42', 'IPC channel must be a non-empty string.');
223
+ *
224
+ * console.log(data);
225
+ * // '{"type":"response","id":"42","error":{"code":"IPC_BAD_MESSAGE","message":"IPC channel must be a non-empty string."}}'
226
+ * ```
227
+ */
228
+ badMessage(id, message) {
229
+ return this.encode({
230
+ type: 'response',
231
+ id,
232
+ error: {
233
+ code: IPC_BAD_MESSAGE,
234
+ message,
235
+ },
236
+ });
237
+ }
238
+
239
+ /**
240
+ * Ensures a parsed IPC payload is a valid request.
241
+ *
242
+ * Returns a successful {@link Result} containing the normalized request, or
243
+ * a failure describing the first structural violation encountered.
244
+ *
245
+ * @param {unknown} message - Parsed IPC payload.
246
+ * @returns {Result<IPCRequest>} Successful result containing the normalized request, or a failure describing the validation error.
247
+ */
248
+ #ensureRequest(message) {
249
+ if (!isPlainObject(message)) {
250
+ return Result.failure({
251
+ error: TypeError,
252
+ message: 'IPC message must be a plain object.',
253
+ code: 'IPC_INVALID_MESSAGE',
254
+ custom: false,
255
+ });
256
+ }
257
+
258
+ if (!('type' in message)) {
259
+ return Result.failure({
260
+ error: ReferenceError,
261
+ message: 'IPC message requires a "type" property.',
262
+ code: 'IPC_MISSING_TYPE',
263
+ custom: false,
264
+ });
265
+ }
266
+
267
+ if (message.type === 'invoke') {
268
+ return this.#ensureInvokeRequest(message);
269
+ }
270
+
271
+ if (message.type === 'event') {
272
+ return this.#ensureEventRequest(message);
273
+ }
274
+
275
+ return Result.failure({
276
+ error: TypeError,
277
+ message: 'IPC message type must be "invoke" or "event".',
278
+ code: 'IPC_INVALID_TYPE',
279
+ custom: false,
280
+ });
281
+ }
282
+
283
+ /**
284
+ * Ensures a parsed IPC payload is a valid invocation request.
285
+ *
286
+ * @param {Record<string, unknown>} message - Parsed IPC payload.
287
+ * @returns {Result<IPCInvokeRequest>} Successful result containing the normalized request, or a failure describing the validation error.
288
+ */
289
+ #ensureInvokeRequest(message) {
290
+ if (!('channel' in message)) {
291
+ return Result.failure({
292
+ error: ReferenceError,
293
+ message: 'IPC message requires a "channel" property.',
294
+ code: 'IPC_MISSING_CHANNEL',
295
+ custom: false,
296
+ });
297
+ }
298
+
299
+ if (typeof message.channel !== 'string') {
300
+ return Result.failure({
301
+ error: TypeError,
302
+ message: 'IPC channel must be a string.',
303
+ code: 'IPC_INVALID_CHANNEL_TYPE',
304
+ custom: false,
305
+ });
306
+ }
307
+
308
+ if (message.channel.trim().length === 0) {
309
+ return Result.failure({
310
+ error: RangeError,
311
+ message: 'IPC channel must be a non-empty string.',
312
+ code: 'IPC_INVALID_CHANNEL_RANGE',
313
+ custom: false,
314
+ });
315
+ }
316
+
317
+ if (!('id' in message)) {
318
+ return Result.failure({
319
+ error: ReferenceError,
320
+ message: 'IPC invocation requires an "id" property.',
321
+ code: 'IPC_MISSING_ID',
322
+ custom: false,
323
+ });
324
+ }
325
+
326
+ if (typeof message.id !== 'string') {
327
+ return Result.failure({
328
+ error: TypeError,
329
+ message: 'IPC invocation id must be a string.',
330
+ code: 'IPC_INVALID_ID_TYPE',
331
+ custom: false,
332
+ });
333
+ }
334
+
335
+ if (message.id.trim().length === 0) {
336
+ return Result.failure({
337
+ error: RangeError,
338
+ message: 'IPC invocation id must be a non-empty string.',
339
+ code: 'IPC_INVALID_ID_RANGE',
340
+ custom: false,
341
+ });
342
+ }
343
+
344
+ return Result.success({
345
+ type: 'invoke',
346
+ channel: message.channel,
347
+ id: message.id,
348
+ data: message.data,
349
+ });
350
+ }
351
+
352
+ /**
353
+ * Ensures a parsed IPC payload is a valid event request.
354
+ *
355
+ * @param {Record<string, unknown>} message - Parsed IPC payload.
356
+ * @returns {Result<IPCEventRequest>} Successful result containing the normalized request, or a failure describing the validation error.
357
+ */
358
+ #ensureEventRequest(message) {
359
+ if (!('channel' in message)) {
360
+ return Result.failure({
361
+ error: ReferenceError,
362
+ message: 'IPC message requires a "channel" property.',
363
+ code: 'IPC_MISSING_CHANNEL',
364
+ custom: false,
365
+ });
366
+ }
367
+
368
+ if (typeof message.channel !== 'string') {
369
+ return Result.failure({
370
+ error: TypeError,
371
+ message: 'IPC channel must be a string.',
372
+ code: 'IPC_INVALID_CHANNEL_TYPE',
373
+ custom: false,
374
+ });
375
+ }
376
+
377
+ if (message.channel.trim().length === 0) {
378
+ return Result.failure({
379
+ error: RangeError,
380
+ message: 'IPC channel must be a non-empty string.',
381
+ code: 'IPC_INVALID_CHANNEL_RANGE',
382
+ custom: false,
383
+ });
384
+ }
385
+
386
+ return Result.success({
387
+ type: 'event',
388
+ channel: message.channel,
389
+ data: message.data,
390
+ });
391
+ }
392
+
393
+ /**
394
+ * Normalizes an arbitrary thrown value into the IPC wire error format.
395
+ *
396
+ * Objects containing `code` and `message` preserve both values after
397
+ * converting them to strings. Other values use the default
398
+ * `IPC_HANDLER_ERROR` code.
399
+ *
400
+ * @param {unknown} error - Thrown value to normalize.
401
+ * @returns {IPCError} Normalized IPC wire error.
402
+ */
403
+ #normalizeError(error) {
404
+ if (error && typeof error === 'object' && 'code' in error && 'message' in error) {
405
+ return {
406
+ code: String(Reflect.get(error, 'code')),
407
+ message: String(Reflect.get(error, 'message')),
408
+ };
409
+ }
410
+
411
+ if (error instanceof Error) {
412
+ return {
413
+ code: 'IPC_HANDLER_ERROR',
414
+ message: error.message,
415
+ };
416
+ }
417
+
418
+ return {
419
+ code: 'IPC_HANDLER_ERROR',
420
+ message: String(error),
421
+ };
422
+ }
423
+
424
+ /**
425
+ * Parses JSON and throws a TypeError when the payload is not valid JSON.
426
+ *
427
+ * @param {string} data - Raw IPC payload.
428
+ * @returns {unknown} The parsed value.
429
+ * @throws {TypeError} If the payload is not valid JSON.
430
+ */
431
+ #parse(data) {
432
+ try {
433
+ return JSON.parse(data);
434
+ } catch (error) {
435
+ throw new TypeError('IPC message must be valid JSON', { cause: error });
436
+ }
437
+ }
438
+ }
439
+
440
+ // ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
441
+ export default IPCProtocol;