Skip to content

Commit 031b3d3

Browse files
authoredJul 20, 2024··
fix!: refactor v1 internal state and options logic (#780)
1 parent 9dbd1cd commit 031b3d3

File tree

9 files changed

+346
-348
lines changed

9 files changed

+346
-348
lines changed
 

‎examples/benchmark/benchmark.js

-3
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,6 @@ export default function benchmark(uuid, Benchmark) {
104104
.add('uuid.v1ToV6()', function () {
105105
uuid.v1ToV6(V1_ID);
106106
})
107-
.add('uuid.v1ToV6() w/ randomization', function () {
108-
uuid.v1ToV6(V1_ID, true);
109-
})
110107
.add('uuid.v6ToV1()', function () {
111108
uuid.v6ToV1(V6_ID);
112109
})

‎examples/benchmark/package-lock.json

+24-34
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/test/v1-random.test.ts

-35
This file was deleted.

‎src/test/v1-rng.test.ts

-35
This file was deleted.

‎src/test/v1.test.ts

+128-94
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,56 @@
11
import * as assert from 'assert';
22
import test, { describe } from 'node:test';
3-
import v1 from '../v1.js';
3+
import parse from '../parse.js';
4+
import v1, { updateV1State } from '../v1.js';
45

56
// Verify ordering of v1 ids created with explicit times
67
const TIME = 1321644961388; // 2011-11-18 11:36:01.388-08:00
78

9+
// Fixture values for testing with the rfc v1 UUID example:
10+
// https://www.rfc-editor.org/rfc/rfc9562.html#name-example-of-a-uuidv1-value
11+
const RFC_V1 = 'c232ab00-9414-11ec-b3c8-9f68deced846';
12+
const RFC_V1_BYTES = parse(RFC_V1);
13+
14+
// `options` for producing the above RFC UUID
15+
const RFC_OPTIONS = {
16+
msecs: 0x17f22e279b0,
17+
nsecs: 0,
18+
clockseq: 0x33c8,
19+
node: Uint8Array.of(0x9f, 0x68, 0xde, 0xce, 0xd8, 0x46),
20+
};
21+
22+
// random bytes for producing the above RFC UUID
23+
const RFC_RANDOM = Uint8Array.of(
24+
// unused
25+
0,
26+
0,
27+
0,
28+
0,
29+
0,
30+
0,
31+
0,
32+
0,
33+
34+
// clock seq
35+
RFC_OPTIONS.clockseq >> 8,
36+
RFC_OPTIONS.clockseq & 0xff,
37+
38+
// node
39+
...RFC_OPTIONS.node
40+
);
41+
42+
// Compare v1 timestamp fields chronologically
43+
function compareV1TimeField(a: string, b: string) {
44+
a = a.split('-').slice(0, 3).reverse().join('');
45+
b = b.split('-').slice(0, 3).reverse().join('');
46+
return a < b ? -1 : a > b ? 1 : 0;
47+
}
48+
849
describe('v1', () => {
950
test('v1 sort order (default)', () => {
1051
const ids = [v1(), v1(), v1(), v1(), v1()];
1152

12-
const sorted = [...ids].sort((a, b) => {
13-
a = a.split('-').reverse().join('-');
14-
b = b.split('-').reverse().join('-');
15-
return a < b ? -1 : a > b ? 1 : 0;
16-
});
17-
53+
const sorted = [...ids].sort(compareV1TimeField);
1854
assert.deepEqual(ids, sorted);
1955
});
2056

@@ -28,110 +64,108 @@ describe('v1', () => {
2864
v1({ msecs: TIME + 28 * 24 * 3600 * 1000 }),
2965
];
3066

31-
const sorted = [...ids].sort((a, b) => {
32-
a = a.split('-').reverse().join('-');
33-
b = b.split('-').reverse().join('-');
34-
return a < b ? -1 : a > b ? 1 : 0;
35-
});
36-
67+
const sorted = [...ids].sort(compareV1TimeField);
3768
assert.deepEqual(ids, sorted);
3869
});
3970

40-
test('msec', () => {
41-
assert.ok(
42-
v1({ msecs: TIME }) !== v1({ msecs: TIME }),
43-
'IDs created at same msec are different'
44-
);
45-
});
46-
47-
test('exception thrown when > 10k ids created in 1ms', () => {
48-
assert.throws(function () {
49-
v1({ msecs: TIME, nsecs: 10000 });
50-
}, 'throws when > 10K ids created in 1 ms');
51-
});
52-
53-
test('clock regression by msec', () => {
54-
// Verify clock regression bumps clockseq
55-
const uidt = v1({ msecs: TIME });
56-
const uidtb = v1({ msecs: TIME - 1 });
57-
assert.ok(
58-
parseInt(uidtb.split('-')[3], 16) - parseInt(uidt.split('-')[3], 16) === 1,
59-
'Clock regression by msec increments the clockseq'
60-
);
61-
});
62-
63-
test('clock regression by nsec', () => {
64-
// Verify clock regression bumps clockseq
65-
const uidtn = v1({ msecs: TIME, nsecs: 10 });
66-
const uidtnb = v1({ msecs: TIME, nsecs: 9 });
67-
assert.ok(
68-
parseInt(uidtnb.split('-')[3], 16) - parseInt(uidtn.split('-')[3], 16) === 1,
69-
'Clock regression by nsec increments the clockseq'
70-
);
71-
});
72-
73-
const fullOptions = {
74-
msecs: 1321651533573,
75-
nsecs: 5432,
76-
clockseq: 0x385c,
77-
node: Uint8Array.of(0x61, 0xcd, 0x3c, 0xbb, 0x32, 0x10),
78-
};
79-
80-
test('explicit options produce expected id', () => {
81-
// Verify explicit options produce expected id
82-
const id = v1(fullOptions);
83-
assert.ok(
84-
id === 'd9428888-122b-11e1-b85c-61cd3cbb3210',
85-
'Explicit options produce expected id'
86-
);
71+
test('v1(options)', () => {
72+
assert.equal(v1({ msecs: RFC_OPTIONS.msecs, random: RFC_RANDOM }), RFC_V1, 'minimal options');
73+
assert.equal(v1(RFC_OPTIONS), RFC_V1, 'full options');
8774
});
8875

89-
test('ids spanning 1ms boundary are 100ns apart', () => {
90-
// Verify adjacent ids across a msec boundary are 1 time unit apart
91-
const u0 = v1({ msecs: TIME, nsecs: 9999 });
92-
const u1 = v1({ msecs: TIME + 1, nsecs: 0 });
93-
94-
const before = u0.split('-')[0];
95-
const after = u1.split('-')[0];
96-
const dt = parseInt(after, 16) - parseInt(before, 16);
97-
assert.ok(dt === 1, 'Ids spanning 1ms boundary are 100ns apart');
76+
test('v1(options) equality', () => {
77+
assert.notEqual(v1({ msecs: TIME }), v1({ msecs: TIME }), 'UUIDs with minimal options differ');
78+
assert.equal(v1(RFC_OPTIONS), v1(RFC_OPTIONS), 'UUIDs with full options are identical');
9879
});
9980

100-
const expectedBytes = Uint8Array.of(
101-
217,
102-
66,
103-
136,
104-
136,
105-
18,
106-
43,
107-
17,
108-
225,
109-
184,
110-
92,
111-
97,
112-
205,
113-
60,
114-
187,
115-
50,
116-
16
117-
);
118-
11981
test('fills one UUID into a buffer as expected', () => {
12082
const buffer = new Uint8Array(16);
121-
const result = v1(fullOptions, buffer);
122-
assert.deepEqual(buffer, expectedBytes);
83+
const result = v1(RFC_OPTIONS, buffer);
84+
assert.deepEqual(buffer, RFC_V1_BYTES);
12385
assert.strictEqual(buffer, result);
12486
});
12587

12688
test('fills two UUIDs into a buffer as expected', () => {
12789
const buffer = new Uint8Array(32);
128-
v1(fullOptions, buffer, 0);
129-
v1(fullOptions, buffer, 16);
90+
v1(RFC_OPTIONS, buffer, 0);
91+
v1(RFC_OPTIONS, buffer, 16);
13092

13193
const expectedBuf = new Uint8Array(32);
132-
expectedBuf.set(expectedBytes);
133-
expectedBuf.set(expectedBytes, 16);
94+
expectedBuf.set(RFC_V1_BYTES);
95+
expectedBuf.set(RFC_V1_BYTES, 16);
13496

13597
assert.deepEqual(buffer, expectedBuf);
13698
});
99+
100+
test('v1() state transitions', () => {
101+
// Test fixture for internal state passed into updateV1State function
102+
const PRE_STATE = {
103+
msecs: 10,
104+
nsecs: 20,
105+
clockseq: 0x1234,
106+
node: Uint8Array.of(0x12, 0x34, 0x56, 0x78, 0x9a, 0xbc),
107+
};
108+
109+
// Note: The test code, below, passes RFC_RANDOM as the `rnds` argument for
110+
// convenience. This allows us to test that fields have been initialized from
111+
// the rnds argument by testing for RFC_OPTIONS values in the output state.
112+
113+
const tests = [
114+
{
115+
title: 'initial state',
116+
state: {},
117+
now: 10,
118+
expected: {
119+
msecs: 10, // -> now
120+
nsecs: 0, // -> init
121+
clockseq: RFC_OPTIONS.clockseq, // -> random
122+
node: RFC_OPTIONS.node, // -> random
123+
},
124+
},
125+
{
126+
title: 'same time interval',
127+
state: { ...PRE_STATE },
128+
now: PRE_STATE.msecs,
129+
expected: {
130+
...PRE_STATE,
131+
nsecs: 21, // -> +1
132+
},
133+
},
134+
{
135+
title: 'new time interval',
136+
state: { ...PRE_STATE },
137+
now: PRE_STATE.msecs + 1,
138+
expected: {
139+
...PRE_STATE,
140+
msecs: PRE_STATE.msecs + 1, // -> +1
141+
nsecs: 0, // -> init
142+
},
143+
},
144+
{
145+
title: 'same time interval (nsecs overflow)',
146+
state: { ...PRE_STATE, nsecs: 9999 },
147+
now: PRE_STATE.msecs,
148+
expected: {
149+
...PRE_STATE,
150+
nsecs: 0, // -> init
151+
clockseq: RFC_OPTIONS.clockseq, // -> init
152+
node: RFC_OPTIONS.node, // -> init
153+
},
154+
},
155+
{
156+
title: 'time regression',
157+
state: { ...PRE_STATE },
158+
now: PRE_STATE.msecs - 1,
159+
expected: {
160+
...PRE_STATE,
161+
msecs: PRE_STATE.msecs - 1, // -> now
162+
clockseq: RFC_OPTIONS.clockseq, // -> init
163+
node: RFC_OPTIONS.node, // -> init
164+
},
165+
},
166+
];
167+
for (const { title, state, now, expected } of tests) {
168+
assert.deepStrictEqual(updateV1State(state, now, RFC_RANDOM), expected, `Failed: ${title}`);
169+
}
170+
});
137171
});

‎src/test/v6.test.ts

+18-18
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,29 @@ describe('v6', () => {
99
const V6_ID = '1ef21d2f-1207-6660-8c4f-419efbd44d48';
1010

1111
const fullOptions = {
12-
msecs: 1321651533573,
13-
nsecs: 5432,
12+
msecs: 0x133b891f705,
13+
nsecs: 0x1538,
1414
clockseq: 0x385c,
1515
node: Uint8Array.of(0x61, 0xcd, 0x3c, 0xbb, 0x32, 0x10),
1616
};
1717

1818
const EXPECTED_BYTES = Uint8Array.of(
19-
30,
20-
17,
21-
34,
22-
189,
23-
148,
24-
40,
25-
104,
26-
136,
27-
184,
28-
92,
29-
97,
30-
205,
31-
60,
32-
187,
33-
50,
34-
16
19+
0x1e,
20+
0x11,
21+
0x22,
22+
0xbd,
23+
0x94,
24+
0x28,
25+
0x68,
26+
0x88,
27+
0xb8,
28+
0x5c,
29+
0x61,
30+
0xcd,
31+
0x3c,
32+
0xbb,
33+
0x32,
34+
0x10
3535
);
3636

3737
test('default behavior', () => {

‎src/test/v7.test.ts

+32-32
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,38 @@ describe('v7', () => {
134134
}
135135
});
136136

137-
test('internal state updates properly', () => {
137+
test('can supply seq', () => {
138+
let seq = 0x12345;
139+
let uuid = v7({
140+
msecs: RFC_MSECS,
141+
seq,
142+
});
143+
144+
assert.strictEqual(uuid.substr(0, 25), '017f22e2-79b0-7000-848d-1');
145+
146+
seq = 0x6fffffff;
147+
uuid = v7({
148+
msecs: RFC_MSECS,
149+
seq,
150+
});
151+
152+
assert.strictEqual(uuid.substring(0, 25), '017f22e2-79b0-76ff-bfff-f');
153+
});
154+
155+
test('internal seq is reset upon timestamp change', () => {
156+
v7({
157+
msecs: RFC_MSECS,
158+
seq: 0x6fffffff,
159+
});
160+
161+
const uuid = v7({
162+
msecs: RFC_MSECS + 1,
163+
});
164+
165+
assert.ok(uuid.indexOf('fff') !== 15);
166+
});
167+
168+
test('v7() state transitions', () => {
138169
const tests = [
139170
{
140171
title: 'new time interval',
@@ -191,37 +222,6 @@ describe('v7', () => {
191222
}
192223
});
193224

194-
test('can supply seq', () => {
195-
let seq = 0x12345;
196-
let uuid = v7({
197-
msecs: RFC_MSECS,
198-
seq,
199-
});
200-
201-
assert.strictEqual(uuid.substr(0, 25), '017f22e2-79b0-7000-848d-1');
202-
203-
seq = 0x6fffffff;
204-
uuid = v7({
205-
msecs: RFC_MSECS,
206-
seq,
207-
});
208-
209-
assert.strictEqual(uuid.substring(0, 25), '017f22e2-79b0-76ff-bfff-f');
210-
});
211-
212-
test('internal seq is reset upon timestamp change', () => {
213-
v7({
214-
msecs: RFC_MSECS,
215-
seq: 0x6fffffff,
216-
});
217-
218-
const uuid = v7({
219-
msecs: RFC_MSECS + 1,
220-
});
221-
222-
assert.ok(uuid.indexOf('fff') !== 15);
223-
});
224-
225225
test('flipping bits changes the result', () => {
226226
// convert uint8array to BigInt (BE)
227227
const asBigInt = (buf: Uint8Array) => buf.reduce((acc, v) => (acc << 8n) | BigInt(v), 0n);

‎src/v1.ts

+138-91
Original file line numberDiff line numberDiff line change
@@ -7,133 +7,180 @@ import { unsafeStringify } from './stringify.js';
77
// Inspired by https://github.com/LiosK/UUID.js
88
// and http://docs.python.org/library/uuid.html
99

10-
let _nodeId: Uint8Array;
11-
let _clockseq: number;
10+
type V1State = {
11+
node?: Uint8Array; // node id (47-bit random)
12+
clockseq?: number; // sequence number (14-bit)
1213

13-
// Previous uuid creation time
14-
let _lastMSecs = 0;
15-
let _lastNSecs = 0;
14+
// v1 & v6 timestamps are a pain to deal with. They specify time from the
15+
// Gregorian epoch in 100ns intervals, which requires values with 57+ bits of
16+
// precision. But that's outside the precision of IEEE754 floats (i.e. JS
17+
// numbers). To work around this, we represent them internally using 'msecs'
18+
// (milliseconds since unix epoch) and 'nsecs' (100-nanoseconds offset from
19+
// `msecs`).
20+
21+
msecs?: number; // timestamp (milliseconds, unix epoch)
22+
nsecs?: number; // timestamp (100-nanoseconds offset from 'msecs')
23+
};
24+
25+
const _state: V1State = {};
1626

1727
function v1(options?: Version1Options, buf?: undefined, offset?: number): string;
1828
function v1(options?: Version1Options, buf?: Uint8Array, offset?: number): Uint8Array;
1929
function v1(options?: Version1Options, buf?: Uint8Array, offset?: number): UUIDTypes {
20-
options ??= {};
21-
22-
let i = (buf && offset) || 0;
23-
const b = buf || new Uint8Array(16);
24-
25-
let node = options.node;
26-
let clockseq = options.clockseq;
27-
28-
// v1 only: Use cached `node` and `clockseq` values
29-
if (!options._v6) {
30-
if (!node) {
31-
node = _nodeId;
32-
}
33-
if (clockseq == null) {
34-
clockseq = _clockseq;
30+
let bytes: Uint8Array;
31+
32+
// Extract _v6 flag from options, clearing options if appropriate
33+
const isV6 = options?._v6 ?? false;
34+
if (options) {
35+
const optionsKeys = Object.keys(options);
36+
if (optionsKeys.length === 1 && optionsKeys[0] === '_v6') {
37+
options = undefined;
3538
}
3639
}
3740

38-
// Handle cases where we need entropy. We do this lazily to minimize issues
39-
// related to insufficient system entropy. See #189
40-
if (node == null || clockseq == null) {
41-
const seedBytes = options.random || (options.rng || rng)();
42-
43-
// Randomize node
44-
if (node == null) {
45-
node = Uint8Array.of(
46-
seedBytes[0],
47-
seedBytes[1],
48-
seedBytes[2],
49-
seedBytes[3],
50-
seedBytes[4],
51-
seedBytes[5]
52-
);
53-
54-
// v1 only: cache node value for reuse
55-
if (!_nodeId && !options._v6) {
56-
// per RFC4122 4.5: Set MAC multicast bit (v1 only)
57-
node[0] |= 0x01; // Set multicast bit
58-
59-
_nodeId = node;
60-
}
61-
}
41+
if (options) {
42+
// With options: Make UUID independent of internal state
43+
bytes = v1Bytes(
44+
options.random ?? options.rng?.() ?? rng(),
45+
options.msecs,
46+
options.nsecs,
47+
options.clockseq,
48+
options.node,
49+
buf,
50+
offset
51+
);
52+
} else {
53+
// Without options: Make UUID from internal state
54+
const now = Date.now();
55+
const rnds = rng();
56+
57+
updateV1State(_state, now, rnds);
58+
59+
// Geenerate UUID. Note that v6 uses random values for `clockseq` and
60+
// `node`.
61+
//
62+
// https://www.rfc-editor.org/rfc/rfc9562.html#section-5.6-4
63+
bytes = v1Bytes(
64+
rnds,
65+
_state.msecs,
66+
_state.nsecs,
67+
// v6 UUIDs get random `clockseq` and `node` for every UUID
68+
// https://www.rfc-editor.org/rfc/rfc9562.html#section-5.6-4
69+
isV6 ? undefined : _state.clockseq,
70+
isV6 ? undefined : _state.node,
71+
buf,
72+
offset
73+
);
74+
}
6275

63-
// Randomize clockseq
64-
if (clockseq == null) {
65-
// Per 4.2.2, randomize (14 bit) clockseq
66-
clockseq = ((seedBytes[6] << 8) | seedBytes[7]) & 0x3fff;
67-
if (_clockseq === undefined && !options._v6) {
68-
_clockseq = clockseq;
69-
}
76+
return buf ? bytes : unsafeStringify(bytes);
77+
}
78+
79+
// (Private!) Do not use. This method is only exported for testing purposes
80+
// and may change without notice.
81+
export function updateV1State(state: V1State, now: number, rnds: Uint8Array) {
82+
state.msecs ??= -Infinity;
83+
state.nsecs ??= 0;
84+
85+
// Update timestamp
86+
if (now === state.msecs) {
87+
// Same msec-interval = simulate higher clock resolution by bumping `nsecs`
88+
// https://www.rfc-editor.org/rfc/rfc9562.html#section-6.1-2.6
89+
state.nsecs++;
90+
91+
// Check for `nsecs` overflow (nsecs is capped at 10K intervals / msec)
92+
if (state.nsecs >= 10000) {
93+
// Prior to uuid@11 this would throw an error, however the RFCs allow for
94+
// changing the node in this case. This slightly breaks monotonicity at
95+
// msec granularity, but that's not a significant concern.
96+
// https://www.rfc-editor.org/rfc/rfc9562.html#section-6.1-2.16
97+
state.node = undefined;
98+
state.nsecs = 0;
7099
}
100+
} else if (now > state.msecs) {
101+
// Reset nsec counter when clock advances to a new msec interval
102+
state.nsecs = 0;
103+
} else if (now < state.msecs) {
104+
// Handle clock regression
105+
// https://www.rfc-editor.org/rfc/rfc9562.html#section-6.1-2.7
106+
//
107+
// Note: Unsetting node here causes both it and clockseq to be randomized,
108+
// below.
109+
state.node = undefined;
71110
}
72111

73-
// v1 & v6 timestamps are 100 nano-second units since the Gregorian epoch,
74-
// (1582-10-15 00:00). JSNumbers aren't precise enough for this, so time is
75-
// handled internally as 'msecs' (integer milliseconds) and 'nsecs'
76-
// (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 00:00.
77-
let msecs = options.msecs !== undefined ? options.msecs : Date.now();
112+
// Init node and clock sequence (do this after timestamp update which may
113+
// reset the node) https://www.rfc-editor.org/rfc/rfc9562.html#section-5.1-7
114+
//
115+
// Note:
116+
if (!state.node) {
117+
state.node = rnds.slice(10, 16);
78118

79-
// Per 4.2.1.2, use count of uuid's generated during the current clock
80-
// cycle to simulate higher resolution clock
81-
let nsecs = options.nsecs !== undefined ? options.nsecs : _lastNSecs + 1;
119+
// Set multicast bit
120+
// https://www.rfc-editor.org/rfc/rfc9562.html#section-6.10-3
121+
state.node[0] |= 0x01; // Set multicast bit
82122

83-
// Time since last uuid creation (in msecs)
84-
const dt = msecs - _lastMSecs + (nsecs - _lastNSecs) / 10000;
85-
86-
// Per 4.2.1.2, Bump clockseq on clock regression
87-
if (dt < 0 && options.clockseq === undefined) {
88-
clockseq = (clockseq + 1) & 0x3fff;
123+
// Clock sequence must be randomized
124+
// https://www.rfc-editor.org/rfc/rfc9562.html#section-5.1-8
125+
state.clockseq = ((rnds[8] << 8) | rnds[9]) & 0x3fff;
89126
}
90127

91-
// Reset nsecs if clock regresses (new clockseq) or we've moved onto a new
92-
// time interval
93-
if ((dt < 0 || msecs > _lastMSecs) && options.nsecs === undefined) {
94-
nsecs = 0;
95-
}
128+
state.msecs = now;
96129

97-
// Per 4.2.1.2 Throw error if too many uuids are requested
98-
if (nsecs >= 10000) {
99-
throw new Error("uuid.v1(): Can't create more than 10M uuids/sec");
100-
}
130+
return state;
131+
}
101132

102-
_lastMSecs = msecs;
103-
_lastNSecs = nsecs;
104-
_clockseq = clockseq;
133+
function v1Bytes(
134+
rnds: Uint8Array,
135+
msecs?: number,
136+
nsecs?: number,
137+
clockseq?: number,
138+
node?: Uint8Array,
139+
buf?: Uint8Array,
140+
offset = 0
141+
) {
142+
// Defaults
143+
if (!buf) {
144+
buf = new Uint8Array(16);
145+
offset = 0;
146+
}
147+
msecs ??= Date.now();
148+
nsecs ??= 0;
149+
clockseq ??= ((rnds[8] << 8) | rnds[9]) & 0x3fff;
150+
node ??= rnds.slice(10, 16);
105151

106-
// Per 4.1.4 - Convert from unix epoch to Gregorian epoch
152+
// Offset to Gregorian epoch
153+
// https://www.rfc-editor.org/rfc/rfc9562.html#section-5.1-1
107154
msecs += 12219292800000;
108155

109156
// `time_low`
110157
const tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000;
111-
b[i++] = (tl >>> 24) & 0xff;
112-
b[i++] = (tl >>> 16) & 0xff;
113-
b[i++] = (tl >>> 8) & 0xff;
114-
b[i++] = tl & 0xff;
158+
buf[offset++] = (tl >>> 24) & 0xff;
159+
buf[offset++] = (tl >>> 16) & 0xff;
160+
buf[offset++] = (tl >>> 8) & 0xff;
161+
buf[offset++] = tl & 0xff;
115162

116163
// `time_mid`
117164
const tmh = ((msecs / 0x100000000) * 10000) & 0xfffffff;
118-
b[i++] = (tmh >>> 8) & 0xff;
119-
b[i++] = tmh & 0xff;
165+
buf[offset++] = (tmh >>> 8) & 0xff;
166+
buf[offset++] = tmh & 0xff;
120167

121168
// `time_high_and_version`
122-
b[i++] = ((tmh >>> 24) & 0xf) | 0x10; // include version
123-
b[i++] = (tmh >>> 16) & 0xff;
169+
buf[offset++] = ((tmh >>> 24) & 0xf) | 0x10; // include version
170+
buf[offset++] = (tmh >>> 16) & 0xff;
124171

125-
// `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant)
126-
b[i++] = (clockseq >>> 8) | 0x80;
172+
// `clock_seq_hi_and_reserved` | variant
173+
buf[offset++] = (clockseq >>> 8) | 0x80;
127174

128175
// `clock_seq_low`
129-
b[i++] = clockseq & 0xff;
176+
buf[offset++] = clockseq & 0xff;
130177

131178
// `node`
132179
for (let n = 0; n < 6; ++n) {
133-
b[i + n] = node[n];
180+
buf[offset++] = node[n];
134181
}
135182

136-
return buf || unsafeStringify(b);
183+
return buf;
137184
}
138185

139186
export default v1;

‎src/v7.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,11 @@ import rng from './rng.js';
33
import { unsafeStringify } from './stringify.js';
44

55
type V7State = {
6-
msecs: number; // time, milliseconds
7-
seq: number; // sequence number (32-bits)
6+
msecs?: number; // time, milliseconds
7+
seq?: number; // sequence number (32-bits)
88
};
99

10-
const _state: V7State = {
11-
msecs: -Infinity,
12-
seq: 0,
13-
};
10+
const _state: V7State = {};
1411

1512
function v7(options?: Version7Options, buf?: undefined, offset?: number): string;
1613
function v7(options?: Version7Options, buf?: Uint8Array, offset?: number): Uint8Array;
@@ -42,6 +39,9 @@ function v7(options?: Version7Options, buf?: Uint8Array, offset?: number): UUIDT
4239
// (Private!) Do not use. This method is only exported for testing purposes
4340
// and may change without notice.
4441
export function updateV7State(state: V7State, now: number, rnds: Uint8Array) {
42+
state.msecs ??= -Infinity;
43+
state.seq ??= 0;
44+
4545
if (now > state.msecs) {
4646
// Time has moved on! Pick a new random sequence number
4747
state.seq = (rnds[6] << 23) | (rnds[7] << 16) | (rnds[8] << 8) | rnds[9];

0 commit comments

Comments
 (0)
Please sign in to comment.