@hkdigital/lib-core 0.5.16 → 0.5.17

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.
@@ -12,7 +12,7 @@ export function expect_notNull(value: any): void;
12
12
  */
13
13
  export function expect_true(value: any): void;
14
14
  /**
15
- * Expect a positive number
15
+ * Expect a positive number (greater than zero)
16
16
  *
17
17
  * @param {any} value
18
18
  */
@@ -23,12 +23,15 @@ export function expect_true(value) {
23
23
  export { expect_true as true };
24
24
 
25
25
  /**
26
- * Expect a positive number
26
+ * Expect a positive number (greater than zero)
27
27
  *
28
28
  * @param {any} value
29
29
  */
30
30
  export function expect_positiveNumber(value) {
31
- const schema = v.pipe(v.number(), v.minValue(0));
31
+ const schema = v.pipe(
32
+ v.number(),
33
+ v.custom((val) => val > 0, 'Invalid value: Expected number > 0')
34
+ );
32
35
  v.parse(schema, value);
33
36
  }
34
37
 
@@ -27,6 +27,7 @@ export function randomBytes(length) {
27
27
  try {
28
28
  const { randomBytes: nodeRandomBytes } = require('crypto');
29
29
  return new Uint8Array(nodeRandomBytes(length));
30
+ // eslint-disable-next-line no-unused-vars
30
31
  } catch (error) {
31
32
  throw new Error('No secure random generator available');
32
33
  }
@@ -66,7 +67,9 @@ export function randomBytesBase64(length) {
66
67
  */
67
68
  export function randomBytesHex(length) {
68
69
  const bytes = randomBytes(length);
69
-
70
+
70
71
  // Convert to hex string
71
- return Array.from(bytes, byte => byte.toString(16).padStart(2, '0')).join('');
72
- }
72
+ return Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join(
73
+ ''
74
+ );
75
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * uuid.js
3
+ *
4
+ * @description
5
+ * Cross-platform UUID generation utilities using Web Crypto API
6
+ */
7
+ /**
8
+ * Generate a cryptographically secure random UUID v4
9
+ * - Uses Web Crypto API (works in Node.js 16+ and all modern browsers)
10
+ * - Returns RFC 4122 compliant UUID v4 string
11
+ * - Format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
12
+ * where x is any hexadecimal digit and y is one of 8, 9, A, or B
13
+ *
14
+ * @returns {string} UUID v4 string (e.g., "a1b2c3d4-e5f6-4890-abcd-ef1234567890")
15
+ *
16
+ * @example
17
+ * import { randomUUID } from './uuid.js';
18
+ *
19
+ * const id = randomUUID();
20
+ * // Returns: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
21
+ */
22
+ export function randomUUID(): string;
@@ -0,0 +1,59 @@
1
+ /**
2
+ * uuid.js
3
+ *
4
+ * @description
5
+ * Cross-platform UUID generation utilities using Web Crypto API
6
+ */
7
+
8
+ /**
9
+ * Generate a cryptographically secure random UUID v4
10
+ * - Uses Web Crypto API (works in Node.js 16+ and all modern browsers)
11
+ * - Returns RFC 4122 compliant UUID v4 string
12
+ * - Format: xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
13
+ * where x is any hexadecimal digit and y is one of 8, 9, A, or B
14
+ *
15
+ * @returns {string} UUID v4 string (e.g., "a1b2c3d4-e5f6-4890-abcd-ef1234567890")
16
+ *
17
+ * @example
18
+ * import { randomUUID } from './uuid.js';
19
+ *
20
+ * const id = randomUUID();
21
+ * // Returns: "f47ac10b-58cc-4372-a567-0e02b2c3d479"
22
+ */
23
+ export function randomUUID() {
24
+ // Try native crypto.randomUUID first (most efficient)
25
+ if (typeof crypto !== 'undefined' && crypto.randomUUID) {
26
+ // Modern browsers + Node.js 16.7.0+
27
+ return crypto.randomUUID();
28
+ }
29
+
30
+ // Fallback: Manual UUID v4 generation using crypto.getRandomValues
31
+ if (typeof crypto !== 'undefined' && crypto.getRandomValues) {
32
+ // Generate 16 random bytes
33
+ const bytes = crypto.getRandomValues(new Uint8Array(16));
34
+
35
+ // Set version (4) and variant bits according to RFC 4122
36
+ bytes[6] = (bytes[6] & 0x0f) | 0x40; // Version 4
37
+ bytes[8] = (bytes[8] & 0x3f) | 0x80; // Variant 10
38
+
39
+ // Convert to UUID string format
40
+ const hex = Array.from(bytes, (byte) =>
41
+ byte.toString(16).padStart(2, '0')
42
+ ).join('');
43
+
44
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
45
+ }
46
+
47
+ // Fallback for older Node.js environments
48
+ if (typeof require !== 'undefined') {
49
+ try {
50
+ const { randomUUID: nodeRandomUUID } = require('crypto');
51
+ return nodeRandomUUID();
52
+ // eslint-disable-next-line no-unused-vars
53
+ } catch (error) {
54
+ // Fall through to error
55
+ }
56
+ }
57
+
58
+ throw new Error('No secure random UUID generator available');
59
+ }
@@ -1 +1,2 @@
1
1
  export * from "./random/bytes.js";
2
+ export * from "./random/uuid.js";
@@ -6,4 +6,5 @@
6
6
  * using the Web Crypto API for maximum compatibility.
7
7
  */
8
8
 
9
- export * from './random/bytes.js';
9
+ export * from './random/bytes.js';
10
+ export * from './random/uuid.js';
@@ -44,16 +44,34 @@ export function randomAccessCode(): string;
44
44
  */
45
45
  export function generateClientSessionId(): string;
46
46
  /**
47
- * Generate an id that can used to identify a server
48
- * - Output format: <base58-time-based> + <random-chars>
49
- * - The ID will be generated only once,
47
+ * Get or generate an id that can be used to identify a client or server instance
48
+ * - Output format: <length-prefix><base58-time-based><random-chars>
49
+ * - Length prefix is the base58 encoded length of the time-based component
50
+ * - Higher entropy than the old serverId for global uniqueness
51
+ * - The ID is generated only once per session/boot and cached
50
52
  *
51
- * @param {number} [randomSize=8] - Number of random base58 characters
53
+ * @param {number} [randomSize=16] - Number of random base58 characters
52
54
  * @param {boolean} [reset=false] - Reset the previously generated id
53
55
  *
54
- * @returns {string} server id
56
+ * @returns {string} instance id
55
57
  */
56
- export function generateServerId(randomSize?: number, reset?: boolean): string;
58
+ export function generateOrGetInstanceId(randomSize?: number, reset?: boolean): string;
59
+ /**
60
+ * Generates and returns a new unique global id
61
+ * - The generated id is guaranteed to be globally unique across different
62
+ * clients/servers/systems
63
+ * - Format: <length-prefix><instance-id><local-id>
64
+ * - Length prefix is the base58 encoded length of the instance ID + local ID
65
+ * - Optimized for high-frequency generation (thousands per second)
66
+ * - Instance ID provides ~94 bits of entropy (16 random base58 chars)
67
+ * - Local ID uses efficient counter mechanism for same-window uniqueness
68
+ *
69
+ * @param {number} [timeMs]
70
+ * Custom time value to be used instead of Date.now()
71
+ *
72
+ * @returns {string} global id
73
+ */
74
+ export function generateGlobalId(timeMs?: number): string;
57
75
  /**
58
76
  * Generates and returns a new unique local id
59
77
  * - The generated id is garanteed to be unique on the currently running
@@ -74,6 +92,23 @@ export function generateLocalId(timeMs?: number): string;
74
92
  * @returns {number} time based numerical that changes every 30 seconds
75
93
  */
76
94
  export function getTimeBasedNumber30s(timeMs?: number): number;
95
+ /**
96
+ * Returns a time based base58 encoded string that changes every 30 seconds
97
+ *
98
+ * - Output length grows over time as the number increases
99
+ * - Starting from TIME_2025_01_01 (January 1, 2025)
100
+ * - After 1 month: ~3 characters
101
+ * - After 1 year: ~4 characters
102
+ * - After 5 years: ~4 characters
103
+ * - After 10 years: ~5 characters
104
+ * - After 100 years: ~6 characters
105
+ *
106
+ * @param {number} [timeMs=sinceMs()]
107
+ * Custom time value to be used instead of sinceMs()
108
+ *
109
+ * @returns {string} base58 encoded time based value
110
+ */
111
+ export function getTimeBasedNumber30sBase58(timeMs?: number): string;
77
112
  /**
78
113
  * Returns two character base58 encoded string that changes every 10
79
114
  * milliseconds
@@ -31,7 +31,7 @@ import { sinceMs } from '../time';
31
31
  /**
32
32
  * @type {{
33
33
  * bootTimePrefix?:string,
34
- * serverId?: string,
34
+ * instanceId?: string,
35
35
  * lastTimeBasedNumber?: number,
36
36
  * lastTimeBasedValue58?: string,
37
37
  * lastCountBasedNumber?: number
@@ -128,23 +128,52 @@ export function generateClientSessionId() {
128
128
  }
129
129
 
130
130
  /**
131
- * Generate an id that can used to identify a server
132
- * - Output format: <base58-time-based> + <random-chars>
133
- * - The ID will be generated only once,
131
+ * Get or generate an id that can be used to identify a client or server instance
132
+ * - Output format: <length-prefix><base58-time-based><random-chars>
133
+ * - Length prefix is the base58 encoded length of the time-based component
134
+ * - Higher entropy than the old serverId for global uniqueness
135
+ * - The ID is generated only once per session/boot and cached
134
136
  *
135
- * @param {number} [randomSize=8] - Number of random base58 characters
137
+ * @param {number} [randomSize=16] - Number of random base58 characters
136
138
  * @param {boolean} [reset=false] - Reset the previously generated id
137
139
  *
138
- * @returns {string} server id
140
+ * @returns {string} instance id
139
141
  */
140
- export function generateServerId(randomSize = 8, reset=false) {
141
- if (!vars.serverId || reset) {
142
- vars.serverId =
143
- base58fromNumber(getTimeBasedNumber30s()) +
142
+ export function generateOrGetInstanceId(randomSize = 16, reset = false) {
143
+ if (!vars.instanceId || reset) {
144
+ const timeBasedPart = getTimeBasedNumber30sBase58();
145
+ const lengthPrefix = base58fromNumber(timeBasedPart.length);
146
+
147
+ vars.instanceId =
148
+ lengthPrefix +
149
+ timeBasedPart +
144
150
  randomStringBase58(randomSize);
145
151
  }
146
152
 
147
- return vars.serverId;
153
+ return vars.instanceId;
154
+ }
155
+
156
+ /**
157
+ * Generates and returns a new unique global id
158
+ * - The generated id is guaranteed to be globally unique across different
159
+ * clients/servers/systems
160
+ * - Format: <length-prefix><instance-id><local-id>
161
+ * - Length prefix is the base58 encoded length of the instance ID + local ID
162
+ * - Optimized for high-frequency generation (thousands per second)
163
+ * - Instance ID provides ~94 bits of entropy (16 random base58 chars)
164
+ * - Local ID uses efficient counter mechanism for same-window uniqueness
165
+ *
166
+ * @param {number} [timeMs]
167
+ * Custom time value to be used instead of Date.now()
168
+ *
169
+ * @returns {string} global id
170
+ */
171
+ export function generateGlobalId(timeMs) {
172
+ const instanceId = generateOrGetInstanceId();
173
+ const localId = generateLocalId(timeMs);
174
+ const lengthPrefix = base58fromNumber(instanceId.length + localId.length);
175
+
176
+ return lengthPrefix + instanceId + localId;
148
177
  }
149
178
 
150
179
  /**
@@ -180,7 +209,7 @@ export function generateLocalId(timeMs) {
180
209
  // -- Same time stamp based number -> increment counter
181
210
 
182
211
  countBasedNumber = vars.lastCountBasedNumber =
183
- vars.lastCountBasedNumber + 1;
212
+ (vars.lastCountBasedNumber ?? 0) + 1;
184
213
 
185
214
  // -- Use cached lastTimeBasedNumber
186
215
 
@@ -197,6 +226,7 @@ export function generateLocalId(timeMs) {
197
226
  const id =
198
227
  // idFormatPrefix
199
228
  bootTimePrefix() +
229
+ // @ts-ignore
200
230
  ALPHABET_BASE_58[timeBasedValue58.length] +
201
231
  timeBasedValue58 +
202
232
  countBasedValue58;
@@ -223,6 +253,26 @@ export function getTimeBasedNumber30s(timeMs) {
223
253
  return Math.floor(timeMs / 30000);
224
254
  }
225
255
 
256
+ /**
257
+ * Returns a time based base58 encoded string that changes every 30 seconds
258
+ *
259
+ * - Output length grows over time as the number increases
260
+ * - Starting from TIME_2025_01_01 (January 1, 2025)
261
+ * - After 1 month: ~3 characters
262
+ * - After 1 year: ~4 characters
263
+ * - After 5 years: ~4 characters
264
+ * - After 10 years: ~5 characters
265
+ * - After 100 years: ~6 characters
266
+ *
267
+ * @param {number} [timeMs=sinceMs()]
268
+ * Custom time value to be used instead of sinceMs()
269
+ *
270
+ * @returns {string} base58 encoded time based value
271
+ */
272
+ export function getTimeBasedNumber30sBase58(timeMs) {
273
+ return base58fromNumber(getTimeBasedNumber30s(timeMs));
274
+ }
275
+
226
276
  /**
227
277
  * Returns two character base58 encoded string that changes every 10
228
278
  * milliseconds
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hkdigital/lib-core",
3
- "version": "0.5.16",
3
+ "version": "0.5.17",
4
4
  "author": {
5
5
  "name": "HKdigital",
6
6
  "url": "https://hkdigital.nl"