@boo-labs/server 0.0.0-stage → 1.0.1

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 (115) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +287 -2
  3. package/index.mjs +23 -0
  4. package/package.json +71 -4
  5. package/source/application/Kernel/index.mjs +1137 -0
  6. package/source/application/Server/index.mjs +1201 -0
  7. package/source/application/configurations/Http2CleartextServerConfiguration/index.mjs +168 -0
  8. package/source/application/configurations/Http2SecureServerConfiguration/index.mjs +172 -0
  9. package/source/application/configurations/HttpServerConfiguration/index.mjs +169 -0
  10. package/source/application/configurations/HttpsServerConfiguration/index.mjs +172 -0
  11. package/source/application/configurations/ServerConfiguration/Options/CORSOptions/CORSOptionsValue.mjs +158 -0
  12. package/source/application/configurations/ServerConfiguration/Options/CORSOptions/index.mjs +87 -0
  13. package/source/application/configurations/ServerConfiguration/Options/CSRFOptions/CSRFOptionsValue.mjs +178 -0
  14. package/source/application/configurations/ServerConfiguration/Options/CSRFOptions/index.mjs +80 -0
  15. package/source/application/configurations/ServerConfiguration/Options/CompressOptions/CompressOptionsValue.mjs +126 -0
  16. package/source/application/configurations/ServerConfiguration/Options/CompressOptions/index.mjs +81 -0
  17. package/source/application/configurations/ServerConfiguration/Options/CookieParserOptions/CookieParserOptionsValue.mjs +107 -0
  18. package/source/application/configurations/ServerConfiguration/Options/CookieParserOptions/index.mjs +82 -0
  19. package/source/application/configurations/ServerConfiguration/Options/CookieSessionOptions/CookieSessionOptionsValue.mjs +182 -0
  20. package/source/application/configurations/ServerConfiguration/Options/CookieSessionOptions/index.mjs +86 -0
  21. package/source/application/configurations/ServerConfiguration/Options/ListenOptions/ListenOptionsValue.mjs +95 -0
  22. package/source/application/configurations/ServerConfiguration/Options/ListenOptions/index.mjs +47 -0
  23. package/source/application/configurations/ServerConfiguration/Options/MiddlewareOption.mjs +102 -0
  24. package/source/application/configurations/ServerConfiguration/Options/Options.mjs +105 -0
  25. package/source/application/configurations/ServerConfiguration/Options/RateLimitOptions/RateLimitOptionsValue.mjs +164 -0
  26. package/source/application/configurations/ServerConfiguration/Options/RateLimitOptions/index.mjs +84 -0
  27. package/source/application/configurations/ServerConfiguration/Options/RequestOptions/RequestOptionsValue.mjs +108 -0
  28. package/source/application/configurations/ServerConfiguration/Options/RequestOptions/index.mjs +47 -0
  29. package/source/application/configurations/ServerConfiguration/Options/ResponseOptions/ResponseOptionsValue.mjs +101 -0
  30. package/source/application/configurations/ServerConfiguration/Options/ResponseOptions/index.mjs +47 -0
  31. package/source/application/configurations/ServerConfiguration/Options/SecurityHeadersOptions/SecurityHeadersOptionsValue.mjs +272 -0
  32. package/source/application/configurations/ServerConfiguration/Options/SecurityHeadersOptions/index.mjs +85 -0
  33. package/source/application/configurations/ServerConfiguration/Options/ServerOption.mjs +50 -0
  34. package/source/application/configurations/ServerConfiguration/Options/StaticOptions/StaticOptionsValue.mjs +164 -0
  35. package/source/application/configurations/ServerConfiguration/Options/StaticOptions/index.mjs +89 -0
  36. package/source/application/configurations/ServerConfiguration/index.mjs +781 -0
  37. package/source/application/resolvers/TransportResolver/index.mjs +119 -0
  38. package/source/application/use-cases/CloseServer/index.mjs +85 -0
  39. package/source/application/use-cases/ListenServer/index.mjs +98 -0
  40. package/source/core/EventSystem/index.mjs +110 -0
  41. package/source/core/Exchange/ExchangeContext.mjs +291 -0
  42. package/source/core/Exchange/Telemetry.mjs +193 -0
  43. package/source/core/Exchange/index.mjs +604 -0
  44. package/source/core/ExchangeLayerBuilder/index.mjs +133 -0
  45. package/source/core/IPCMain/IPCMain.mjs +507 -0
  46. package/source/core/IPCMain/IPCProtocol.mjs +441 -0
  47. package/source/core/IPCMain/IPCServerChannel.mjs +331 -0
  48. package/source/core/IPCMain/WebSocketAdapter.mjs +189 -0
  49. package/source/core/Request/BodyParser/BodyParser.mjs +197 -0
  50. package/source/core/Request/BodyParser/JSONParser.mjs +70 -0
  51. package/source/core/Request/BodyParser/MultipartParser.mjs +612 -0
  52. package/source/core/Request/BodyParser/URLEncodedParser.mjs +71 -0
  53. package/source/core/Request/RequestHeaders.mjs +177 -0
  54. package/source/core/Request/index.mjs +805 -0
  55. package/source/core/Response/ResponseHeaders.mjs +402 -0
  56. package/source/core/Response/index.mjs +1370 -0
  57. package/source/core/middlewares/CORSMiddleware/index.mjs +289 -0
  58. package/source/core/middlewares/CSRFMiddleware/index.mjs +284 -0
  59. package/source/core/middlewares/CompressMiddleware/index.mjs +460 -0
  60. package/source/core/middlewares/CookieParserMiddleware/index.mjs +194 -0
  61. package/source/core/middlewares/CookieSessionMiddleware/index.mjs +460 -0
  62. package/source/core/middlewares/RateLimitMiddleware/index.mjs +245 -0
  63. package/source/core/middlewares/SecurityHeadersMiddleware/index.mjs +151 -0
  64. package/source/core/middlewares/StaticMiddleware/index.mjs +628 -0
  65. package/source/core/routing/ExchangeLayer/ExchangeMatcher.mjs +127 -0
  66. package/source/core/routing/ExchangeLayer/index.mjs +254 -0
  67. package/source/core/routing/Pipeline/index.mjs +157 -0
  68. package/source/core/routing/Router/Route.mjs +184 -0
  69. package/source/core/routing/Router/index.mjs +523 -0
  70. package/source/core/routing/RouterRegister/OptionsMiddleware.mjs +147 -0
  71. package/source/core/routing/RouterRegister/index.mjs +150 -0
  72. package/source/core/routing/RouterSystem/index.mjs +225 -0
  73. package/source/core/value-objects/Address/index.mjs +281 -0
  74. package/source/core/value-objects/Endpoint/index.mjs +293 -0
  75. package/source/index.mjs +62 -0
  76. package/source/infrastructure/adapters/Http2Adapter/index.mjs +144 -0
  77. package/source/infrastructure/adapters/Http2CleartextAdapter/index.mjs +147 -0
  78. package/source/infrastructure/adapters/HttpAdapter/index.mjs +124 -0
  79. package/source/infrastructure/adapters/HttpsAdapter/index.mjs +124 -0
  80. package/source/infrastructure/adapters/TransportAdapter/createWebSocketOpenRequest.mjs +843 -0
  81. package/source/infrastructure/adapters/TransportAdapter/index.mjs +555 -0
  82. package/source/shared/Identifier/index.mjs +671 -0
  83. package/source/shared/constants/symbols/brands/index.mjs +45 -0
  84. package/source/shared/domain/UUIDV7/index.mjs +346 -0
  85. package/source/shared/errors/BaseError/index.mjs +157 -0
  86. package/source/shared/errors/IPCError/index.mjs +97 -0
  87. package/source/shared/errors/MiddlewareError/index.mjs +106 -0
  88. package/source/shared/errors/PathnamePatternError/index.mjs +103 -0
  89. package/source/shared/errors/RequestError/index.mjs +116 -0
  90. package/source/shared/errors/ResponseError/index.mjs +140 -0
  91. package/source/shared/errors/RouterError/index.mjs +90 -0
  92. package/source/shared/errors/ServerConfigurationError/index.mjs +551 -0
  93. package/source/shared/errors/ServerError/index.mjs +127 -0
  94. package/source/shared/errors/WebSocketError/index.mjs +58 -0
  95. package/source/shared/outcome/ErrorSchema.mjs +361 -0
  96. package/source/shared/outcome/Result/index.mjs +239 -0
  97. package/source/shared/outcome/Validation/index.mjs +208 -0
  98. package/source/shared/validations/Middleware/index.mjs +160 -0
  99. package/source/shared/validations/PathnamePattern/index.mjs +256 -0
  100. package/source/shared/validations/Server/NotFoundHandler/index.mjs +94 -0
  101. package/source/shared/validations/Server/Router/index.mjs +68 -0
  102. package/source/shared/validations/Server/ServerConfiguration/index.mjs +68 -0
  103. package/source/shared/validations/Server/SystemErrorHandler/index.mjs +94 -0
  104. package/source/shared/validations/ServerConfiguration/CORSOptions/index.mjs +310 -0
  105. package/source/shared/validations/ServerConfiguration/CSRFOptions/index.mjs +299 -0
  106. package/source/shared/validations/ServerConfiguration/CompressOptions/index.mjs +204 -0
  107. package/source/shared/validations/ServerConfiguration/CookieParserOptions/index.mjs +102 -0
  108. package/source/shared/validations/ServerConfiguration/CookieSessionOptions/index.mjs +332 -0
  109. package/source/shared/validations/ServerConfiguration/ListenOptions/index.mjs +178 -0
  110. package/source/shared/validations/ServerConfiguration/RateLimitOptions/index.mjs +229 -0
  111. package/source/shared/validations/ServerConfiguration/RequestOptions/index.mjs +132 -0
  112. package/source/shared/validations/ServerConfiguration/ResponseOptions/index.mjs +122 -0
  113. package/source/shared/validations/ServerConfiguration/SecurityHeadersOptions/index.mjs +137 -0
  114. package/source/shared/validations/ServerConfiguration/StaticOptions/index.mjs +248 -0
  115. package/types/index.d.ts +719 -0
@@ -0,0 +1,671 @@
1
+ /**
2
+ * @file Represents an immutable binary identifier with a customizable format.
3
+ *
4
+ * @description An Identifier encapsulates a fixed-length binary buffer that can
5
+ * be composed of multiple segments (timestamp, random, counter). Each subclass
6
+ * defines its own format structure with configurable byte allocations for
7
+ * each segment.
8
+ *
9
+ * Identifiers can be created from various input types (hex strings, buffers,
10
+ * arrays, integers) and provide efficient hexadecimal encoding/decoding using
11
+ * precomputed lookup tables for optimal performance.
12
+ *
13
+ * @module Identifier
14
+ */
15
+
16
+ // ━━ IMPORT MODULES ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
17
+ // » IMPORT NATIVE NODEJS MODULES
18
+ import { inspect } from 'node:util';
19
+ import crypto from 'node:crypto';
20
+
21
+ // ━━ TYPE DEFINITIONS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
22
+ /**
23
+ * The allowed input types for creating an Identifier:
24
+ *
25
+ * - string: Hexadecimal representation.
26
+ * - Buffer | Uint8Array | number[]: Raw binary data.
27
+ * - number: A timestamp (milliseconds) to use for generation.
28
+ * - undefined: Generates a new identifier with the current timestamp.
29
+ *
30
+ * @typedef {string | Buffer | Uint8Array | number[] | number | Date} IdentifierInput
31
+ */
32
+
33
+ /**
34
+ * Defines the binary format of an identifier.
35
+ *
36
+ * Specifies how the identifier's fixed-length binary representation is
37
+ * divided into logical segments. The sum of all segment lengths must equal
38
+ * the identifier's.
39
+ *
40
+ * @typedef {object} IdentifierFormat
41
+ * @property {number} timestamp - Number of bytes for the timestamp segment.
42
+ * @property {number} random - Number of bytes for the random data segment.
43
+ * @property {number} counter - Number of bytes for the sequential counter segment.
44
+ */
45
+
46
+ // ━━ CONSTANTS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
47
+ /**
48
+ * Symbol used as a branded property to identify Identifier instances.
49
+ *
50
+ * @private
51
+ * @type {symbol}
52
+ */
53
+ const IDENTIFIER_BRAND = Symbol('Identifier');
54
+
55
+ /**
56
+ * Precomputed hexadecimal encoding lookup table.
57
+ *
58
+ * Maps each byte value (0-255) to its two-character hexadecimal representation.
59
+ * Used for fast encoding without repeated string conversions.
60
+ *
61
+ * @private
62
+ * @type {Array<string>}
63
+ */
64
+ const HEX_ENCODE_TABLE = new Array(256);
65
+
66
+ for (let i = 0; i < 256; i += 1) {
67
+ HEX_ENCODE_TABLE[i] = i.toString(16).padStart(2, '0');
68
+ }
69
+
70
+ /**
71
+ * Precomputed hexadecimal decoding lookup table.
72
+ *
73
+ * Maps ASCII character codes to their hexadecimal values (0-15).
74
+ * Values for non-hex characters default to 255 (invalid).
75
+ * Supports both uppercase and lowercase hex characters.
76
+ *
77
+ * @private
78
+ * @type {Uint8Array}
79
+ */
80
+ const HEX_DECODE_TABLE = new Uint8Array(256);
81
+
82
+ HEX_DECODE_TABLE.fill(255);
83
+
84
+ for (let i = 0; i <= 9; i += 1) {
85
+ HEX_DECODE_TABLE[0x30 + i] = i;
86
+ }
87
+
88
+ for (let i = 10; i <= 15; i += 1) {
89
+ HEX_DECODE_TABLE[0x41 + i - 10] = i;
90
+ HEX_DECODE_TABLE[0x61 + i - 10] = i;
91
+ }
92
+
93
+ // ━━ MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
94
+ /**
95
+ * Represents an immutable binary identifier with a customizable format.
96
+ *
97
+ * The Identifier class serves as a base for creating fixed-length binary
98
+ * identifiers composed of three segments: timestamp, random data, and a
99
+ * sequential counter. The class uses optimized lookup tables for fast
100
+ * hexadecimal encoding/decoding.
101
+ *
102
+ * - {@link Identifier.BYTE_LENGTH}.
103
+ * - {@link Identifier.FORMAT}.
104
+ *
105
+ * @classdesc Identifiers expose an immutable binary value. Instances cannot have their
106
+ * internal identifier data modified after construction. Derived representations
107
+ * may be cached internally without changing the identifier value.
108
+ *
109
+ * **NOTE**: This class is intended to be used as an internal base class.
110
+ * It should not be exposed directly through public APIs. Instead, extend it
111
+ * to create specific types (e.g., SessionId, UserId, ConnectionId).
112
+ *
113
+ * @property {Buffer} buffer - The raw binary data as a Buffer (read-only copy).
114
+ * @property {number} timestamp - The timestamp component extracted from the identifier.
115
+ * @abstract
116
+ * @example
117
+ * ```js
118
+ * // Define a custom identifier (or use default format)
119
+ * class CustomId extends Identifier {
120
+ * static BYTE_LENGTH = 16;
121
+ * static FORMAT = {
122
+ * timestamp: 6,
123
+ * random: 7,
124
+ * counter: 3,
125
+ * };
126
+ * }
127
+ *
128
+ * // Create instances
129
+ * const id1 = new CustomId(); // Auto-generated with current timestamp
130
+ * const id2 = new CustomId('a1b2c3d4e5f6...'); // From hex string
131
+ * const id3 = new CustomId(Buffer.alloc(16)); // From buffer
132
+ * const id4 = new CustomId(1700000000000); // With custom timestamp
133
+ * ```
134
+ */
135
+ class Identifier {
136
+ /**
137
+ * Private buffer storage for the identifier data.
138
+ *
139
+ * @private
140
+ * @type {Buffer}
141
+ */
142
+ #buffer;
143
+
144
+ /**
145
+ * This cached value does not affect the identifier identity and does not
146
+ * modify the underlying binary representation.
147
+ *
148
+ * Lazily computed on first access via {@link Identifier#toHexString}
149
+ * to avoid unnecessary conversions.
150
+ *
151
+ * @private
152
+ * @type {string|undefined}
153
+ */
154
+ #hexString;
155
+
156
+ /**
157
+ * Creates a new Identifier instance.
158
+ *
159
+ * The constructor normalizes the input and stores the branded symbol
160
+ * for type identification.
161
+ *
162
+ * If `undefined`, a random identifier with the current timestamp is generated.
163
+ *
164
+ * @param {IdentifierInput} [identifier] - The input to create the identifier from.
165
+ * @throws {TypeError} If the provided identifier is invalid or has incorrect length.
166
+ * @example
167
+ * ```js
168
+ * // Auto-generate
169
+ * const id = new CustomId();
170
+ *
171
+ * // From hex string
172
+ * const id = new CustomId('a1b2c3d4e5f6...');
173
+ *
174
+ * // From buffer
175
+ * const id = new CustomId(Buffer.alloc(16));
176
+ *
177
+ * // With custom timestamp
178
+ * const id = new CustomId(1700000000000);
179
+ * ```
180
+ */
181
+ constructor(identifier) {
182
+ const Type = this.constructor;
183
+ this.#buffer = Identifier.#normalize(identifier, Type);
184
+ Object.defineProperty(this, IDENTIFIER_BRAND, {
185
+ value: Type,
186
+ writable: false,
187
+ enumerable: false,
188
+ configurable: false,
189
+ });
190
+ }
191
+
192
+ /**
193
+ * Gets the raw binary data of this identifier as a Buffer.
194
+ *
195
+ * @readonly
196
+ * @type {Buffer}
197
+ * @returns {Buffer} A copy of the internal buffer.
198
+ * @example
199
+ * ```js
200
+ * const id = new CustomId();
201
+ * const buffer = id.buffer;
202
+ * console.log(buffer.length); // 16
203
+ * ```
204
+ */
205
+ get buffer() {
206
+ return Buffer.from(this.#buffer);
207
+ }
208
+
209
+ /**
210
+ * Gets the timestamp component of the identifier.
211
+ *
212
+ * Reads the first N bytes of the buffer (where N = FORMAT.timestamp)
213
+ * as a big-endian unsigned integer.
214
+ *
215
+ * @readonly
216
+ * @type {number}
217
+ * @returns {number} The timestamp component encoded in the identifier.
218
+ * @example
219
+ * ```js
220
+ * const id = new CustomId();
221
+ * console.log(id.timestamp); // 1700000000000
222
+ * ```
223
+ */
224
+ get timestamp() {
225
+ return this.#buffer.readUIntBE(0, this.constructor.FORMAT.timestamp);
226
+ }
227
+
228
+ /**
229
+ * Get the hexadecimal representation of this document identifier.
230
+ *
231
+ * @readonly
232
+ * @type {string}
233
+ * @returns {string} The hexadecimal representation of this document identifier.
234
+ * @example
235
+ * ```js
236
+ * const id = new CustomId();
237
+ * console.log(id.hex); // 019f681d8cf21c92ed7768a61d000000
238
+ * ```
239
+ */
240
+ get hex() {
241
+ if (this.#hexString === undefined) {
242
+ this.#hexString = Identifier.#encodeHex(this.#buffer);
243
+ }
244
+ return this.#hexString;
245
+ }
246
+
247
+ /**
248
+ * Custom string tag for better type identification.
249
+ *
250
+ * @readonly
251
+ * @type {string}
252
+ * @returns {string} The string tag of the subclass.
253
+ * @example
254
+ * ```js
255
+ * const id = new CustomId();
256
+ * console.log(Object.prototype.toString.call(id)); // '[object CustomId]'
257
+ * console.log(id[Symbol.toStringTag]); // 'CustomId'
258
+ * ```
259
+ */
260
+ get [Symbol.toStringTag]() {
261
+ return `${this.constructor.name}`;
262
+ }
263
+
264
+ /**
265
+ * Compares this identifier with another identifier by value.
266
+ *
267
+ * Identifiers are considered equal only when they belong to the same
268
+ * identifier type and contain the same binary representation.
269
+ *
270
+ * @param {Identifier} other - Identifier to compare.
271
+ * @returns {boolean} Whether both identifiers represent the same value.
272
+ * @example
273
+ * ```js
274
+ * const id1 = new CustomId();
275
+ * const id2 = new CustomId(id1.buffer);
276
+ *
277
+ * console.log(id1 === id2);
278
+ * // false (different object references)
279
+ *
280
+ * console.log(id1.equals(id2));
281
+ * // true (same identifier value)
282
+ * ```
283
+ */
284
+ equals(other) {
285
+ return other?.[IDENTIFIER_BRAND] === this.constructor && this.#buffer.equals(other.#buffer);
286
+ }
287
+
288
+ /**
289
+ * Returns the string representation of the identifier.
290
+ *
291
+ * This method is automatically invoked in string contexts (e.g., template literals).
292
+ *
293
+ * @returns {string} The hexadecimal string representation.
294
+ * @example
295
+ * ```js
296
+ * const id = new CustomId();
297
+ * console.log(`ID: ${id}`); // 'ID: a1b2c3d4...'
298
+ * ```
299
+ */
300
+ toString() {
301
+ return this.hex;
302
+ }
303
+
304
+ /**
305
+ * Returns the JSON representation of the identifier.
306
+ *
307
+ * This method is automatically invoked by `JSON.stringify()`.
308
+ *
309
+ * @returns {string} The hexadecimal string representation.
310
+ * @example
311
+ * ```js
312
+ * const id = new CustomId();
313
+ * console.log(JSON.stringify({ id })); // '{"id":"a1b2c3d4..."}'
314
+ * ```
315
+ */
316
+ toJSON() {
317
+ return this.hex;
318
+ }
319
+
320
+ /**
321
+ * Custom inspect implementation for Node.js `util.inspect()`.
322
+ *
323
+ * @returns {string} Custom string representation for debugging.
324
+ * @example
325
+ * ```js
326
+ * const id = new CustomId();
327
+ * console.log(require('util').inspect(id));
328
+ * // CustomId 'a1b2c3d4...'
329
+ * ```
330
+ */
331
+ [inspect.custom]() {
332
+ return `${this.constructor.name}(${inspect(this.hex)})`;
333
+ }
334
+
335
+ /**
336
+ * Defines the primitive conversion behavior of the identifier.
337
+ *
338
+ * This method allows the identifier to be automatically cast to a string
339
+ * in contexts like template literals or string concatenation. It explicitly
340
+ * prevents conversion to numbers to avoid unintended mathematical operations.
341
+ *
342
+ * @param {string} hint - The conversion hint ('string', 'number', or 'default').
343
+ * @returns {string} The hexadecimal string representation of the identifier.
344
+ * @throws {TypeError} If `hint` is 'number', as identifiers are not numeric.
345
+ * @example
346
+ * ```js
347
+ * const id = new CustomId();
348
+ *
349
+ * // String conversion (hint: 'string' or 'default')
350
+ * console.log(`ID: ${id}`); // 'ID: a1b2c3d4e5f6...'
351
+ * console.log('ID: ' + id); // 'ID: a1b2c3d4e5f6...'
352
+ * console.log(String(id)); // 'a1b2c3d4e5f6...'
353
+ *
354
+ * // Number conversion (throws TypeError)
355
+ * try {
356
+ * const num = Number(id); // Throws: CustomId cannot be converted to number.
357
+ * } catch (error) {
358
+ * console.error(error.message);
359
+ * }
360
+ *
361
+ * // Comparison operations use string conversion
362
+ * console.log(id == 'a1b2c3d4e5f6...'); // true
363
+ * console.log(id === 'a1b2c3d4e5f6...'); // false (different types)
364
+ * ```
365
+ */
366
+ [Symbol.toPrimitive](hint) {
367
+ if (hint === 'number') {
368
+ throw new TypeError(`${this.constructor.name} cannot be converted to number.`);
369
+ }
370
+ return String(this.hex);
371
+ }
372
+
373
+ /**
374
+ * Total byte length of the identifier.
375
+ *
376
+ * @description Defines the fixed binary size used by the identifier implementation.
377
+ * @static
378
+ * @type {number}
379
+ * @default 16
380
+ * @example
381
+ * ```js
382
+ * class CustomId extends Identifier {
383
+ * static BYTE_LENGTH = 16;
384
+ * }
385
+ * ```
386
+ */
387
+ static BYTE_LENGTH = 16;
388
+
389
+ /**
390
+ * Format definition for the identifier segments.
391
+ *
392
+ * @static
393
+ * @type {IdentifierFormat}
394
+ * @default { timestamp: 6, random: 7, counter: 3 }
395
+ * @description Defines the binary layout used by the identifier implementation.
396
+ * @example
397
+ * ```js
398
+ * class CustomId extends Identifier {
399
+ * static FORMAT = {
400
+ * timestamp: 6, // 48-bit timestamp
401
+ * random: 7, // 56-bit random data
402
+ * counter: 3, // 24-bit sequential counter
403
+ * };
404
+ * }
405
+ * ```
406
+ */
407
+ static FORMAT = {
408
+ timestamp: 6,
409
+ random: 7,
410
+ counter: 3,
411
+ };
412
+
413
+ /**
414
+ * Stores sequence counters for each identifier subclass.
415
+ *
416
+ * Used to generate sequential counter values that increment with each
417
+ * new identifier of the same type.
418
+ *
419
+ * @private
420
+ * @static
421
+ * @type {WeakMap<typeof Identifier, { timestamp: number, counter: number }>}
422
+ */
423
+ static #sequence = new WeakMap();
424
+
425
+ /**
426
+ * Gets or creates the sequence state for an identifier subclass.
427
+ *
428
+ * @private
429
+ * @static
430
+ * @param {typeof Identifier} Type - The identifier subclass.
431
+ * @returns {{ timestamp: number, counter: number }} The sequence state for the identifier type.
432
+ */
433
+ static #getSequence(Type) {
434
+ if (!Identifier.#sequence.has(Type)) {
435
+ Identifier.#sequence.set(Type, {
436
+ timestamp: 0,
437
+ counter: 0,
438
+ });
439
+ }
440
+
441
+ return Identifier.#sequence.get(Type);
442
+ }
443
+
444
+ /**
445
+ * Generates sequential counter values.
446
+ *
447
+ * The first identifier generated for a timestamp starts at 0.
448
+ * Subsequent identifiers increment the counter value.
449
+ *
450
+ * @private
451
+ * @static
452
+ * @param {typeof Identifier} Type - The class to generate for.
453
+ * @param {number} timestamp - Timestamp associated with the identifier being generated.
454
+ * @param {number} counterBytes - Number of bytes allocated for the counter.
455
+ * @returns {number} The counter value for the generated identifier.
456
+ * @throws {RangeError} If the counter exceeds the maximum value representable by the allocated number of bytes for the current timestamp.
457
+ */
458
+ static #nextCounter(Type, timestamp, counterBytes) {
459
+ const max = 2 ** (counterBytes * 8);
460
+ const sequence = Identifier.#getSequence(Type);
461
+
462
+ if (sequence.timestamp !== timestamp) {
463
+ sequence.timestamp = timestamp;
464
+ sequence.counter = 0;
465
+ return 0;
466
+ }
467
+
468
+ if (sequence.counter === max - 1) {
469
+ throw new RangeError('Identifier counter overflow.');
470
+ }
471
+ sequence.counter += 1;
472
+ return sequence.counter;
473
+ }
474
+ /**
475
+ * Parses a hexadecimal string into a buffer.
476
+ *
477
+ * Uses a precomputed lookup table for efficient decoding.
478
+ *
479
+ * @private
480
+ * @static
481
+ * @param {string} hexString - The hexadecimal string to parse.
482
+ * @param {number} byteLength - The expected byte length.
483
+ * @returns {Buffer} The parsed buffer.
484
+ * @throws {TypeError} If the string is not a valid hex string of the correct length.
485
+ */
486
+ static #parseHex(hexString, byteLength) {
487
+ const regex = new RegExp(`^[0-9a-fA-F]{${byteLength * 2}}$`);
488
+
489
+ if (!regex.test(hexString)) {
490
+ throw new TypeError('Invalid hex string format.');
491
+ }
492
+
493
+ const buffer = Buffer.alloc(byteLength);
494
+
495
+ for (let i = 0; i < byteLength; i += 1) {
496
+ const high = HEX_DECODE_TABLE[hexString.charCodeAt(i * 2)];
497
+ const low = HEX_DECODE_TABLE[hexString.charCodeAt(i * 2 + 1)];
498
+
499
+ buffer[i] = (high << 4) | low;
500
+ }
501
+
502
+ return buffer;
503
+ }
504
+
505
+ /**
506
+ * Parses bytes from various input types into a buffer.
507
+ *
508
+ * @private
509
+ * @static
510
+ * @param {Buffer|Uint8Array|number[]} bytes - The bytes to parse.
511
+ * @param {number} byteLength - The expected byte length.
512
+ * @returns {Buffer} The validated buffer.
513
+ * @throws {TypeError} If the byte length doesn't match the expected length.
514
+ */
515
+ static #parseBytes(bytes, byteLength) {
516
+ const buffer = Buffer.from(bytes);
517
+ if (buffer.length !== byteLength)
518
+ throw new TypeError(`Invalid length. Expected ${byteLength} bytes.`);
519
+ return buffer;
520
+ }
521
+
522
+ /**
523
+ * Encodes a buffer to a hexadecimal string.
524
+ *
525
+ * Uses a precomputed lookup table for optimal performance.
526
+ *
527
+ * @private
528
+ * @static
529
+ * @param {Buffer} buffer - The buffer to encode.
530
+ * @returns {string} The hexadecimal string representation.
531
+ */
532
+ static #encodeHex(buffer) {
533
+ let hex = '';
534
+ for (const byte of buffer) {
535
+ hex += HEX_ENCODE_TABLE[byte];
536
+ }
537
+ return hex;
538
+ }
539
+
540
+ /**
541
+ * Writes a big-endian unsigned integer to a buffer at a specific offset.
542
+ *
543
+ * @private
544
+ * @static
545
+ * @param {Buffer} buffer - The buffer to write to.
546
+ * @param {number} value - The value to write.
547
+ * @param {number} offset - The offset in the buffer.
548
+ * @param {number} byteLength - The number of bytes to write.
549
+ */
550
+ static #writeUIntBE(buffer, value, offset, byteLength) {
551
+ buffer.writeUIntBE(value, offset, byteLength);
552
+ }
553
+
554
+ /**
555
+ * Writes the timestamp to a buffer at the beginning.
556
+ *
557
+ * @private
558
+ * @static
559
+ * @param {Buffer} buffer - The buffer to write to.
560
+ * @param {typeof Identifier} Type - The class to generate for.
561
+ * @param {number} timestamp - The timestamp value to write.
562
+ */
563
+ static #addTimestampToBuffer(buffer, Type, timestamp) {
564
+ const { timestamp: timestampBytes } = Type.FORMAT;
565
+ Identifier.#writeUIntBE(buffer, timestamp, 0, timestampBytes);
566
+ }
567
+
568
+ /**
569
+ * Fills the random segment of the buffer using cryptographically secure random bytes.
570
+ *
571
+ * @private
572
+ * @static
573
+ * @param {Buffer} buffer - The buffer to modify.
574
+ * @param {typeof Identifier} Type - The class to generate for.
575
+ */
576
+ static #addRandomToBuffer(buffer, Type) {
577
+ const { timestamp: timestampBytes, random: randomBytes } = Type.FORMAT;
578
+ crypto.randomFillSync(buffer, timestampBytes, randomBytes);
579
+ }
580
+
581
+ /**
582
+ * Writes the counter value to the buffer at the correct position.
583
+ *
584
+ * @private
585
+ * @static
586
+ * @param {Buffer} buffer - The buffer to modify.
587
+ * @param {typeof Identifier} Type - The class to generate for.
588
+ * @param {number} timestamp - Timestamp associated with the identifier being generated.
589
+ */
590
+ static #addCounterToBuffer(buffer, Type, timestamp) {
591
+ const { timestamp: timestampBytes, random: randomBytes, counter: counterBytes } = Type.FORMAT;
592
+ Identifier.#writeUIntBE(
593
+ buffer,
594
+ Identifier.#nextCounter(Type, timestamp, counterBytes),
595
+ timestampBytes + randomBytes,
596
+ counterBytes,
597
+ );
598
+ }
599
+
600
+ /**
601
+ * Generates a new identifier.
602
+ *
603
+ * Creates a buffer composed of timestamp, random data, and counter segments
604
+ * in that order.
605
+ *
606
+ * @private
607
+ * @static
608
+ * @param {typeof Identifier} Type - The class to generate for.
609
+ * @param {number} [timestamp] - Optional timestamp value, default value is `Date.now()`.
610
+ * @returns {Buffer} The generated buffer.
611
+ */
612
+ static #generate(Type, timestamp = Date.now()) {
613
+ const buffer = Buffer.alloc(Type.BYTE_LENGTH);
614
+
615
+ Identifier.#addTimestampToBuffer(buffer, Type, timestamp);
616
+ Identifier.#addRandomToBuffer(buffer, Type);
617
+ Identifier.#addCounterToBuffer(buffer, Type, timestamp);
618
+
619
+ return buffer;
620
+ }
621
+
622
+ /**
623
+ * Normalizes various input types into a buffer.
624
+ *
625
+ * Handles all supported input formats and performs necessary validation.
626
+ *
627
+ * @private
628
+ * @static
629
+ * @param {IdentifierInput} identifier - The input to normalize.
630
+ * @param {typeof Identifier} Type - The class to normalize for.
631
+ * @returns {Buffer} The normalized buffer.
632
+ * @throws {TypeError} If the input is invalid.
633
+ */
634
+ static #normalize(identifier, Type) {
635
+ const byteLength = Type.BYTE_LENGTH;
636
+ if (identifier === undefined) {
637
+ return Identifier.#generate(Type);
638
+ }
639
+
640
+ if (identifier?.[IDENTIFIER_BRAND] === Type) {
641
+ return Buffer.from(identifier.buffer);
642
+ }
643
+
644
+ if (typeof identifier === 'string') {
645
+ return Identifier.#parseHex(identifier, byteLength);
646
+ }
647
+
648
+ if (identifier instanceof Date) {
649
+ return Identifier.#generate(Type, identifier.getTime());
650
+ }
651
+
652
+ if (
653
+ Buffer.isBuffer(identifier) ||
654
+ identifier instanceof Uint8Array ||
655
+ Array.isArray(identifier)
656
+ ) {
657
+ return Identifier.#parseBytes(identifier, byteLength);
658
+ }
659
+
660
+ if (Number.isInteger(identifier)) {
661
+ return Identifier.#generate(Type, identifier);
662
+ }
663
+
664
+ throw new TypeError(
665
+ 'Expected a hexadecimal string, Buffer, Uint8Array, Array<number>, Date, integer timestamp, or an identifier of the same type.',
666
+ );
667
+ }
668
+ }
669
+
670
+ // ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
671
+ export default Identifier;
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @file Defines shared symbols used to brand application objects.
3
+ *
4
+ * @module Brands
5
+ */
6
+
7
+ // ━━ CONSTANTS ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
8
+ /**
9
+ * Symbol used to brand server transport adapter instances.
10
+ *
11
+ * The symbol provides a stable, collision-resistant property that can be
12
+ * used to identify objects implementing the transport adapter contract
13
+ * without relying on their concrete class or constructor.
14
+ *
15
+ * @private
16
+ * @type {symbol}
17
+ */
18
+ const TRANSPORT_ADAPTER_BRAND = Symbol.for('server.transport.adapter');
19
+
20
+ /**
21
+ * Symbol used to brand router instances.
22
+ *
23
+ * The symbol provides a stable, collision-resistant property that can be
24
+ * used to identify objects implementing the router contract without relying
25
+ * on their concrete class or constructor.
26
+ *
27
+ * @private
28
+ * @type {symbol}
29
+ */
30
+ const ROUTER_BRAND = Symbol.for('server.router');
31
+
32
+ /**
33
+ * Symbol used to brand server configuration instances.
34
+ *
35
+ * The symbol provides a stable, collision-resistant property that can be
36
+ * used to identify objects implementing the server configuration contract
37
+ * without relying on their concrete class or constructor.
38
+ *
39
+ * @private
40
+ * @type {symbol}
41
+ */
42
+ const SERVER_CONFIGURATION_BRAND = Symbol.for('server.configuration');
43
+
44
+ // ━━ EXPORT MODULE ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
45
+ export { TRANSPORT_ADAPTER_BRAND, ROUTER_BRAND, SERVER_CONFIGURATION_BRAND };