@boo-labs/server 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +229 -2
- package/index.mjs +23 -0
- package/package.json +71 -4
- package/source/application/Kernel/index.mjs +1137 -0
- package/source/application/Server/index.mjs +1201 -0
- package/source/application/configurations/Http2CleartextServerConfiguration/index.mjs +168 -0
- package/source/application/configurations/Http2SecureServerConfiguration/index.mjs +172 -0
- package/source/application/configurations/HttpServerConfiguration/index.mjs +169 -0
- package/source/application/configurations/HttpsServerConfiguration/index.mjs +172 -0
- package/source/application/configurations/ServerConfiguration/Options/CORSOptions/CORSOptionsValue.mjs +158 -0
- package/source/application/configurations/ServerConfiguration/Options/CORSOptions/index.mjs +87 -0
- package/source/application/configurations/ServerConfiguration/Options/CSRFOptions/CSRFOptionsValue.mjs +178 -0
- package/source/application/configurations/ServerConfiguration/Options/CSRFOptions/index.mjs +80 -0
- package/source/application/configurations/ServerConfiguration/Options/CompressOptions/CompressOptionsValue.mjs +126 -0
- package/source/application/configurations/ServerConfiguration/Options/CompressOptions/index.mjs +81 -0
- package/source/application/configurations/ServerConfiguration/Options/CookieParserOptions/CookieParserOptionsValue.mjs +107 -0
- package/source/application/configurations/ServerConfiguration/Options/CookieParserOptions/index.mjs +82 -0
- package/source/application/configurations/ServerConfiguration/Options/CookieSessionOptions/CookieSessionOptionsValue.mjs +182 -0
- package/source/application/configurations/ServerConfiguration/Options/CookieSessionOptions/index.mjs +86 -0
- package/source/application/configurations/ServerConfiguration/Options/ListenOptions/ListenOptionsValue.mjs +95 -0
- package/source/application/configurations/ServerConfiguration/Options/ListenOptions/index.mjs +47 -0
- package/source/application/configurations/ServerConfiguration/Options/MiddlewareOption.mjs +102 -0
- package/source/application/configurations/ServerConfiguration/Options/Options.mjs +105 -0
- package/source/application/configurations/ServerConfiguration/Options/RateLimitOptions/RateLimitOptionsValue.mjs +164 -0
- package/source/application/configurations/ServerConfiguration/Options/RateLimitOptions/index.mjs +84 -0
- package/source/application/configurations/ServerConfiguration/Options/RequestOptions/RequestOptionsValue.mjs +108 -0
- package/source/application/configurations/ServerConfiguration/Options/RequestOptions/index.mjs +47 -0
- package/source/application/configurations/ServerConfiguration/Options/ResponseOptions/ResponseOptionsValue.mjs +101 -0
- package/source/application/configurations/ServerConfiguration/Options/ResponseOptions/index.mjs +47 -0
- package/source/application/configurations/ServerConfiguration/Options/SecurityHeadersOptions/SecurityHeadersOptionsValue.mjs +272 -0
- package/source/application/configurations/ServerConfiguration/Options/SecurityHeadersOptions/index.mjs +85 -0
- package/source/application/configurations/ServerConfiguration/Options/ServerOption.mjs +50 -0
- package/source/application/configurations/ServerConfiguration/Options/StaticOptions/StaticOptionsValue.mjs +164 -0
- package/source/application/configurations/ServerConfiguration/Options/StaticOptions/index.mjs +89 -0
- package/source/application/configurations/ServerConfiguration/index.mjs +781 -0
- package/source/application/resolvers/TransportResolver/index.mjs +119 -0
- package/source/application/use-cases/CloseServer/index.mjs +85 -0
- package/source/application/use-cases/ListenServer/index.mjs +98 -0
- package/source/core/EventSystem/index.mjs +110 -0
- package/source/core/Exchange/ExchangeContext.mjs +291 -0
- package/source/core/Exchange/Telemetry.mjs +193 -0
- package/source/core/Exchange/index.mjs +604 -0
- package/source/core/ExchangeLayerBuilder/index.mjs +133 -0
- package/source/core/IPCMain/IPCMain.mjs +507 -0
- package/source/core/IPCMain/IPCProtocol.mjs +441 -0
- package/source/core/IPCMain/IPCServerChannel.mjs +331 -0
- package/source/core/IPCMain/WebSocketAdapter.mjs +189 -0
- package/source/core/Request/BodyParser/BodyParser.mjs +197 -0
- package/source/core/Request/BodyParser/JSONParser.mjs +70 -0
- package/source/core/Request/BodyParser/MultipartParser.mjs +612 -0
- package/source/core/Request/BodyParser/URLEncodedParser.mjs +71 -0
- package/source/core/Request/RequestHeaders.mjs +177 -0
- package/source/core/Request/index.mjs +805 -0
- package/source/core/Response/ResponseHeaders.mjs +402 -0
- package/source/core/Response/index.mjs +1370 -0
- package/source/core/middlewares/CORSMiddleware/index.mjs +289 -0
- package/source/core/middlewares/CSRFMiddleware/index.mjs +284 -0
- package/source/core/middlewares/CompressMiddleware/index.mjs +460 -0
- package/source/core/middlewares/CookieParserMiddleware/index.mjs +194 -0
- package/source/core/middlewares/CookieSessionMiddleware/index.mjs +460 -0
- package/source/core/middlewares/RateLimitMiddleware/index.mjs +245 -0
- package/source/core/middlewares/SecurityHeadersMiddleware/index.mjs +151 -0
- package/source/core/middlewares/StaticMiddleware/index.mjs +628 -0
- package/source/core/routing/ExchangeLayer/ExchangeMatcher.mjs +127 -0
- package/source/core/routing/ExchangeLayer/index.mjs +254 -0
- package/source/core/routing/Pipeline/index.mjs +157 -0
- package/source/core/routing/Router/Route.mjs +184 -0
- package/source/core/routing/Router/index.mjs +523 -0
- package/source/core/routing/RouterRegister/OptionsMiddleware.mjs +147 -0
- package/source/core/routing/RouterRegister/index.mjs +150 -0
- package/source/core/routing/RouterSystem/index.mjs +225 -0
- package/source/core/value-objects/Address/index.mjs +281 -0
- package/source/core/value-objects/Endpoint/index.mjs +293 -0
- package/source/index.mjs +62 -0
- package/source/infrastructure/adapters/Http2Adapter/index.mjs +144 -0
- package/source/infrastructure/adapters/Http2CleartextAdapter/index.mjs +147 -0
- package/source/infrastructure/adapters/HttpAdapter/index.mjs +124 -0
- package/source/infrastructure/adapters/HttpsAdapter/index.mjs +124 -0
- package/source/infrastructure/adapters/TransportAdapter/createWebSocketOpenRequest.mjs +843 -0
- package/source/infrastructure/adapters/TransportAdapter/index.mjs +555 -0
- package/source/shared/Identifier/index.mjs +671 -0
- package/source/shared/constants/symbols/brands/index.mjs +45 -0
- package/source/shared/domain/UUIDV7/index.mjs +346 -0
- package/source/shared/errors/BaseError/index.mjs +157 -0
- package/source/shared/errors/IPCError/index.mjs +97 -0
- package/source/shared/errors/MiddlewareError/index.mjs +106 -0
- package/source/shared/errors/PathnamePatternError/index.mjs +103 -0
- package/source/shared/errors/RequestError/index.mjs +116 -0
- package/source/shared/errors/ResponseError/index.mjs +140 -0
- package/source/shared/errors/RouterError/index.mjs +90 -0
- package/source/shared/errors/ServerConfigurationError/index.mjs +551 -0
- package/source/shared/errors/ServerError/index.mjs +127 -0
- package/source/shared/errors/WebSocketError/index.mjs +58 -0
- package/source/shared/outcome/ErrorSchema.mjs +361 -0
- package/source/shared/outcome/Result/index.mjs +239 -0
- package/source/shared/outcome/Validation/index.mjs +208 -0
- package/source/shared/validations/Middleware/index.mjs +160 -0
- package/source/shared/validations/PathnamePattern/index.mjs +256 -0
- package/source/shared/validations/Server/NotFoundHandler/index.mjs +94 -0
- package/source/shared/validations/Server/Router/index.mjs +68 -0
- package/source/shared/validations/Server/ServerConfiguration/index.mjs +68 -0
- package/source/shared/validations/Server/SystemErrorHandler/index.mjs +94 -0
- package/source/shared/validations/ServerConfiguration/CORSOptions/index.mjs +310 -0
- package/source/shared/validations/ServerConfiguration/CSRFOptions/index.mjs +299 -0
- package/source/shared/validations/ServerConfiguration/CompressOptions/index.mjs +204 -0
- package/source/shared/validations/ServerConfiguration/CookieParserOptions/index.mjs +102 -0
- package/source/shared/validations/ServerConfiguration/CookieSessionOptions/index.mjs +332 -0
- package/source/shared/validations/ServerConfiguration/ListenOptions/index.mjs +178 -0
- package/source/shared/validations/ServerConfiguration/RateLimitOptions/index.mjs +229 -0
- package/source/shared/validations/ServerConfiguration/RequestOptions/index.mjs +132 -0
- package/source/shared/validations/ServerConfiguration/ResponseOptions/index.mjs +122 -0
- package/source/shared/validations/ServerConfiguration/SecurityHeadersOptions/index.mjs +137 -0
- package/source/shared/validations/ServerConfiguration/StaticOptions/index.mjs +248 -0
- package/types/index.d.ts +719 -0
|
@@ -0,0 +1,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 };
|