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;