Skip to content

Instantly share code, notes, and snippets.

@robinpokorny
Last active October 24, 2025 10:07
Show Gist options
  • Save robinpokorny/dec8623c91422292a6a8978d60450d80 to your computer and use it in GitHub Desktop.
Save robinpokorny/dec8623c91422292a6a8978d60450d80 to your computer and use it in GitHub Desktop.
const generator = new UUIDv7Generator();
// Single UUID
console.log(generator.generate());
// Batch generation
const batch = generator.generateBatch(1000);
console.log(`Generated ${batch.length} UUIDs`);
console.log('First:', batch[0]);
console.log('Last:', batch[batch.length - 1]);
// Error handling
try {
generator.generateBatch(200000); // exceeds limit
} catch (e) {
console.error(e.message);
}
class UUIDv7Generator {
// constants
#MAX_COUNTER = 0xFFFF_FFFF_FFFF;
#BATCH_LIMIT = 10_000;
// timestamp state
#lastTimestamp = -1;
#timestampHex1 = ``; // 8 chars
#timestampHex2 = ``; // 4 chars
// random hex state
#randomHex1 = ``; // 4 chars
#randomHex2 = ``; // 4 chars
// counter state
#counter = 0;
// random pool state
#randomPool = new Uint8Array(1024);
#poolView;
#poolIndex = 0;
constructor() {
this.#poolView = new DataView(this.#randomPool.buffer);
crypto.getRandomValues(this.#randomPool);
}
#ensurePoolBytes(count) {
if (this.#poolIndex + count > this.#randomPool.length) {
crypto.getRandomValues(this.#randomPool);
this.#poolIndex = 0;
}
}
#readUint32() {
this.#ensurePoolBytes(4);
const value = this.#poolView.getUint32(this.#poolIndex);
this.#poolIndex += 4;
return value;
}
#updateTimestamp(timestamp) {
this.#lastTimestamp = timestamp;
const hex = timestamp.toString(16).padStart(12, `0`);
this.#timestampHex1 = hex.slice(0, 8);
this.#timestampHex2 = hex.slice(8, 12);
}
#resetRandomState() {
this.#ensurePoolBytes(10);
// read uint32 and apply version/variant
let randomValue = this.#poolView.getUint32(this.#poolIndex);
randomValue = (randomValue & 0x0fffffff) | 0x70000000; // version 7
randomValue = (randomValue & 0xffff3fff) | 0x00008000; // variant
const hex = randomValue.toString(16).padStart(8, `0`);
this.#randomHex1 = hex.slice(0, 4);
this.#randomHex2 = hex.slice(4, 8);
this.#poolIndex += 4;
// read counter
this.#counter = this.#poolView.getUint16(this.#poolIndex) * 0x100000000 +
this.#poolView.getUint32(this.#poolIndex + 2);
this.#poolIndex += 6;
}
generate() {
const timestamp = Date.now();
// new millisecond - reset random state
if (timestamp > this.#lastTimestamp) {
this.#updateTimestamp(timestamp);
this.#resetRandomState();
}
// same millisecond or clock regression - increment counter
else {
const randomIncrement = this.#readUint32();
this.#counter += Math.max(randomIncrement, 2);
if (this.#counter >= this.#MAX_COUNTER) {
this.#updateTimestamp(timestamp + 1);
this.#resetRandomState();
}
}
// counter hex (only thing that changes within a millisecond)
const counterHex = this.#counter.toString(16).padStart(12, `0`);
// format without any slices
return `${this.#timestampHex1}-${this.#timestampHex2}-${this.#randomHex1}-${this.#randomHex2}-${counterHex}`;
}
generateBatch(count) {
if (count <= 0) {
throw new Error(`Batch count must be positive`);
}
if (count > this.#BATCH_LIMIT) {
throw new Error(`Requested batch count ${count} exceeds limit of ${this.#BATCH_LIMIT}`);
}
const uuids = new Array(count);
for (let i = 0; i < count; i++) {
uuids[i] = this.generate();
}
return uuids;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment