1
- function addHyphens ( id : string ) {
2
- return (
3
- id . substring ( 0 , 8 ) +
4
- "-" +
5
- id . substring ( 8 , 12 ) +
6
- "-" +
7
- id . substring ( 12 , 16 ) +
8
- "-" +
9
- id . substring ( 16 , 20 ) +
10
- "-" +
11
- id . substring ( 20 )
12
- ) ;
13
- }
14
-
15
- // Generates the 12 [rand_a] and 62 bits [rand_b] parts in number and bigint formats.
16
- function genRandParts ( ) {
17
- const v4 = crypto . randomUUID ( ) ;
18
-
19
- return {
20
- randA : parseInt ( v4 . slice ( 15 , 18 ) , 16 ) ,
21
- randB : BigInt ( "0x" + v4 . replace ( / - / g, "" ) ) & ( ( 1n << 62n ) - 1n ) ,
22
- } ;
23
- }
1
+ import { TimestampUUIDv7 } from "./timestamp-uuid" ;
2
+ import { addHyphens , genRandParts } from "./utils" ;
24
3
25
4
export class UUIDv7 {
26
5
#lastTimestamp: number = - 1 ;
27
- #lastCustomTimestamp: number = - 1 ;
28
6
#lastRandA: number ;
29
7
#lastRandB: bigint ;
30
8
#lastUUID: bigint = - 1n ;
31
9
#encodeAlphabet: string ;
10
+ #timestampUUID: TimestampUUIDv7 ;
32
11
33
12
/**
34
13
* Generates a new `UUIDv7` instance.
@@ -46,6 +25,7 @@ export class UUIDv7 {
46
25
}
47
26
48
27
this . #encodeAlphabet = opts ?. encodeAlphabet ?? "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" ;
28
+ this . #timestampUUID = new TimestampUUIDv7 ( ) ;
49
29
}
50
30
51
31
/**
@@ -56,29 +36,25 @@ export class UUIDv7 {
56
36
gen ( customTimestamp ?: number ) {
57
37
const hasCustomTimestamp = typeof customTimestamp === "number" ;
58
38
59
- if ( hasCustomTimestamp && ( customTimestamp < 0 || customTimestamp > 2 ** 48 - 1 ) ) {
60
- throw new Error ( "uuidv7 gen error: custom timestamp must be between 0 and 2 ** 48 - 1" ) ;
39
+ if ( hasCustomTimestamp ) {
40
+ return this . #timestampUUID . gen ( customTimestamp ) ;
61
41
}
62
42
63
43
let uuid = this . #lastUUID;
64
44
65
45
while ( this . #lastUUID >= uuid ) {
66
- const timestamp = hasCustomTimestamp ? customTimestamp : Date . now ( ) ;
46
+ const timestamp = Date . now ( ) ;
67
47
68
48
let randA : number ;
69
49
let randB : bigint ;
70
50
71
- // Generate new [rand_a] and [rand_b] parts if (or):
72
- // - custom timestamp is provided and is different from the last custom stored one;
73
- // - custom timestamp is not provided and current one is ahead of the last stored one.
74
- if ( hasCustomTimestamp ? timestamp !== this . #lastCustomTimestamp : timestamp > this . #lastTimestamp) {
51
+ // Generate new [rand_a] and [rand_b] parts if current timestamp one is ahead of the last stored one.
52
+ if ( timestamp > this . #lastTimestamp) {
75
53
const parts = genRandParts ( ) ;
76
54
randA = parts . randA ;
77
55
randB = parts . randB ;
78
- } else if ( ! hasCustomTimestamp && timestamp < this . #lastTimestamp) {
79
- // If custom timestamp is not provided and current timestamp is behind the last stored one,
80
- // it means that the system clock went backwards. So wait until it goes ahead before generating new UUIDs.
81
- // If custom timestamp is provided, this doesn't matter, since timestamp is always fixed.
56
+ } else if ( timestamp < this . #lastTimestamp) {
57
+ // The system clock went backwards. So wait until it goes ahead before generating new UUIDs.
82
58
continue ;
83
59
} else {
84
60
// Otherwise, current timestamp is the same as the previous stored one.
@@ -101,16 +77,10 @@ export class UUIDv7 {
101
77
randA = randA + 1 ;
102
78
103
79
// If the [rand_a] part overflows its 12 bits,
80
+ // Skip this loop iteration, since both [rand_a] and [rand_b] counters have overflowed.
81
+ // This ensures monotonicity per instance.
104
82
if ( randA > 2 ** 12 - 1 ) {
105
- // if custom timestamp is provided, generate new [rand_a] part.
106
- // This breaks monotonicity but keeps custom timestamp the same and generates a new ID.
107
- if ( hasCustomTimestamp ) {
108
- randA = genRandParts ( ) . randA ;
109
- } else {
110
- // if custom timestamp is not provided, skip this loop iteration, since both
111
- // [rand_a] and [rand_b] counters have overflowed. This ensures monotonicity per instance.
112
- continue ;
113
- }
83
+ continue ;
114
84
}
115
85
}
116
86
}
@@ -133,19 +103,10 @@ export class UUIDv7 {
133
103
this . #lastRandA = randA ;
134
104
this . #lastRandB = randB ;
135
105
136
- // If custom timestamp is provided, always break the loop, since a valid UUIDv7 for this timestamp
137
- // was generated.
138
- if ( hasCustomTimestamp ) {
139
- this . #lastCustomTimestamp = timestamp ;
140
- break ;
141
- } else {
142
- this . #lastTimestamp = timestamp ;
143
- }
106
+ this . #lastTimestamp = timestamp ;
144
107
}
145
108
146
- if ( ! hasCustomTimestamp ) {
147
- this . #lastUUID = uuid ;
148
- }
109
+ this . #lastUUID = uuid ;
149
110
150
111
return addHyphens ( uuid . toString ( 16 ) . padStart ( 32 , "0" ) ) ;
151
112
}
0 commit comments