146 lines
4.6 KiB
JavaScript
146 lines
4.6 KiB
JavaScript
|
import rng from './rng.js';
|
||
|
import { unsafeStringify } from './stringify.js';
|
||
|
|
||
|
/**
|
||
|
* UUID V7 - Unix Epoch time-based UUID
|
||
|
*
|
||
|
* The IETF has published RFC9562, introducing 3 new UUID versions (6,7,8). This
|
||
|
* implementation of V7 is based on the accepted, though not yet approved,
|
||
|
* revisions.
|
||
|
*
|
||
|
* RFC 9562:https://www.rfc-editor.org/rfc/rfc9562.html Universally Unique
|
||
|
* IDentifiers (UUIDs)
|
||
|
|
||
|
*
|
||
|
* Sample V7 value:
|
||
|
* https://www.rfc-editor.org/rfc/rfc9562.html#name-example-of-a-uuidv7-value
|
||
|
*
|
||
|
* Monotonic Bit Layout: RFC rfc9562.6.2 Method 1, Dedicated Counter Bits ref:
|
||
|
* https://www.rfc-editor.org/rfc/rfc9562.html#section-6.2-5.1
|
||
|
*
|
||
|
* 0 1 2 3 0 1 2 3 4 5 6
|
||
|
* 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
||
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
|
* | unix_ts_ms |
|
||
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
|
* | unix_ts_ms | ver | seq_hi |
|
||
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
|
* |var| seq_low | rand |
|
||
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
|
* | rand |
|
||
|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|
||
|
*
|
||
|
* seq is a 31 bit serialized counter; comprised of 12 bit seq_hi and 19 bit
|
||
|
* seq_low, and randomly initialized upon timestamp change. 31 bit counter size
|
||
|
* was selected as any bitwise operations in node are done as _signed_ 32 bit
|
||
|
* ints. we exclude the sign bit.
|
||
|
*/
|
||
|
|
||
|
let _seqLow = null;
|
||
|
let _seqHigh = null;
|
||
|
let _msecs = 0;
|
||
|
function v7(options, buf, offset) {
|
||
|
options = options || {};
|
||
|
|
||
|
// initialize buffer and pointer
|
||
|
let i = buf && offset || 0;
|
||
|
const b = buf || new Uint8Array(16);
|
||
|
|
||
|
// rnds is Uint8Array(16) filled with random bytes
|
||
|
const rnds = options.random || (options.rng || rng)();
|
||
|
|
||
|
// milliseconds since unix epoch, 1970-01-01 00:00
|
||
|
const msecs = options.msecs !== undefined ? options.msecs : Date.now();
|
||
|
|
||
|
// seq is user provided 31 bit counter
|
||
|
let seq = options.seq !== undefined ? options.seq : null;
|
||
|
|
||
|
// initialize local seq high/low parts
|
||
|
let seqHigh = _seqHigh;
|
||
|
let seqLow = _seqLow;
|
||
|
|
||
|
// check if clock has advanced and user has not provided msecs
|
||
|
if (msecs > _msecs && options.msecs === undefined) {
|
||
|
_msecs = msecs;
|
||
|
|
||
|
// unless user provided seq, reset seq parts
|
||
|
if (seq !== null) {
|
||
|
seqHigh = null;
|
||
|
seqLow = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// if we have a user provided seq
|
||
|
if (seq !== null) {
|
||
|
// trim provided seq to 31 bits of value, avoiding overflow
|
||
|
if (seq > 0x7fffffff) {
|
||
|
seq = 0x7fffffff;
|
||
|
}
|
||
|
|
||
|
// split provided seq into high/low parts
|
||
|
seqHigh = seq >>> 19 & 0xfff;
|
||
|
seqLow = seq & 0x7ffff;
|
||
|
}
|
||
|
|
||
|
// randomly initialize seq
|
||
|
if (seqHigh === null || seqLow === null) {
|
||
|
seqHigh = rnds[6] & 0x7f;
|
||
|
seqHigh = seqHigh << 8 | rnds[7];
|
||
|
seqLow = rnds[8] & 0x3f; // pad for var
|
||
|
seqLow = seqLow << 8 | rnds[9];
|
||
|
seqLow = seqLow << 5 | rnds[10] >>> 3;
|
||
|
}
|
||
|
|
||
|
// increment seq if within msecs window
|
||
|
if (msecs + 10000 > _msecs && seq === null) {
|
||
|
if (++seqLow > 0x7ffff) {
|
||
|
seqLow = 0;
|
||
|
if (++seqHigh > 0xfff) {
|
||
|
seqHigh = 0;
|
||
|
|
||
|
// increment internal _msecs. this allows us to continue incrementing
|
||
|
// while staying monotonic. Note, once we hit 10k milliseconds beyond system
|
||
|
// clock, we will reset breaking monotonicity (after (2^31)*10000 generations)
|
||
|
_msecs++;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
// resetting; we have advanced more than
|
||
|
// 10k milliseconds beyond system clock
|
||
|
_msecs = msecs;
|
||
|
}
|
||
|
_seqHigh = seqHigh;
|
||
|
_seqLow = seqLow;
|
||
|
|
||
|
// [bytes 0-5] 48 bits of local timestamp
|
||
|
b[i++] = _msecs / 0x10000000000 & 0xff;
|
||
|
b[i++] = _msecs / 0x100000000 & 0xff;
|
||
|
b[i++] = _msecs / 0x1000000 & 0xff;
|
||
|
b[i++] = _msecs / 0x10000 & 0xff;
|
||
|
b[i++] = _msecs / 0x100 & 0xff;
|
||
|
b[i++] = _msecs & 0xff;
|
||
|
|
||
|
// [byte 6] - set 4 bits of version (7) with first 4 bits seq_hi
|
||
|
b[i++] = seqHigh >>> 4 & 0x0f | 0x70;
|
||
|
|
||
|
// [byte 7] remaining 8 bits of seq_hi
|
||
|
b[i++] = seqHigh & 0xff;
|
||
|
|
||
|
// [byte 8] - variant (2 bits), first 6 bits seq_low
|
||
|
b[i++] = seqLow >>> 13 & 0x3f | 0x80;
|
||
|
|
||
|
// [byte 9] 8 bits seq_low
|
||
|
b[i++] = seqLow >>> 5 & 0xff;
|
||
|
|
||
|
// [byte 10] remaining 5 bits seq_low, 3 bits random
|
||
|
b[i++] = seqLow << 3 & 0xff | rnds[10] & 0x07;
|
||
|
|
||
|
// [bytes 11-15] always random
|
||
|
b[i++] = rnds[11];
|
||
|
b[i++] = rnds[12];
|
||
|
b[i++] = rnds[13];
|
||
|
b[i++] = rnds[14];
|
||
|
b[i++] = rnds[15];
|
||
|
return buf || unsafeStringify(b);
|
||
|
}
|
||
|
export default v7;
|