Skip to content

Commit 26c7bc0

Browse files
chore: finalize collision-safe ID generation follow-ups
Co-authored-by: marcuscastelo <27441558+marcuscastelo@users.noreply.github.com> Agent-Logs-Url: https://github.com/marcuscastelo/macroflows/sessions/e1cd678e-e254-49ee-9b3e-0efbe4cb0a58
1 parent 26a0480 commit 26c7bc0

1 file changed

Lines changed: 27 additions & 3 deletions

File tree

src/shared/utils/uniqueId.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,42 @@
1+
/**
2+
* Reserve 29 bits for the per-runtime counter, which allows more than
3+
* 500 million unique numeric IDs before exhaustion while keeping the final
4+
* value inside JavaScript's safe integer range once combined with the
5+
* runtime prefix.
6+
*/
17
const NUMERIC_ID_COUNTER_LIMIT = 2 ** 29
8+
9+
/**
10+
* Reserve the remaining 24 safe-integer bits for a cryptographically-random
11+
* runtime prefix so separate execution contexts are unlikely to share the same
12+
* numeric ID space. Together with the counter bits above, this keeps
13+
* `prefix * NUMERIC_ID_COUNTER_LIMIT + counter` below `Number.MAX_SAFE_INTEGER`.
14+
*/
215
const NUMERIC_ID_PREFIX_LIMIT = 2 ** 24
316

417
let numericIdCounter = 0
518
let numericIdPrefix: number | undefined
619

720
function getCryptoApi(): Crypto {
821
if (!('crypto' in globalThis)) {
9-
throw new Error('Crypto API is required for ID generation')
22+
throw new Error(
23+
'Crypto API is required for ID generation. Use a modern browser or Node.js runtime with Web Crypto support.',
24+
)
1025
}
1126

1227
return globalThis.crypto
1328
}
1429

1530
function createNumericIdPrefix(): number {
1631
const values = new Uint32Array(1)
17-
getCryptoApi().getRandomValues(values)
32+
const maxUnbiasedValue =
33+
Math.floor(2 ** 32 / NUMERIC_ID_PREFIX_LIMIT) * NUMERIC_ID_PREFIX_LIMIT
34+
35+
do {
36+
// Reject the top incomplete range so the modulo result stays uniform across
37+
// the full prefix space instead of favoring lower values.
38+
getCryptoApi().getRandomValues(values)
39+
} while (values[0]! >= maxUnbiasedValue)
1840

1941
return values[0]! % NUMERIC_ID_PREFIX_LIMIT
2042
}
@@ -55,7 +77,9 @@ export function generateUuid(): string {
5577
*/
5678
export function generateNumericId(): number {
5779
if (numericIdCounter >= NUMERIC_ID_COUNTER_LIMIT) {
58-
throw new Error('Numeric ID counter exhausted')
80+
throw new Error(
81+
'Numeric ID counter exhausted after more than 500 million IDs in one runtime. Restart the application to reset the local counter.',
82+
)
5983
}
6084

6185
if (numericIdPrefix === undefined) {

0 commit comments

Comments
 (0)