Last active
September 14, 2025 17:46
-
-
Save SMJSGaming/766a68aea7435f7fdbe19829de867f00 to your computer and use it in GitHub Desktop.
TypeScript UUIDv7 Implement based on the rfc9562 standard
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| interface OverlapConfig { | |
| bits: number; | |
| overlap: number; | |
| } | |
| interface WriteConfig { | |
| length: number; | |
| value: bigint; | |
| } | |
| export class UUIDv7 { | |
| private static RANDOM_ALGORITHM: () => number = Math.random; | |
| public static setRandomAlgorithm(randomAlgorithm: () => number): void { | |
| UUIDv7.RANDOM_ALGORITHM = randomAlgorithm; | |
| } | |
| public static create(): UUIDv7 { | |
| return new UUIDv7(); | |
| } | |
| /** | |
| * @warning Does not consider reserved UUID variants as valid | |
| */ | |
| public static isValid(uuid: UUIDv7 | string) { | |
| const stringUUID = uuid.toString(); | |
| const hasDashes = stringUUID.includes("-"); | |
| return (!hasDashes || stringUUID.split("-").length == 5) && | |
| stringUUID.length == 32 + 4 * +hasDashes && | |
| UUIDv7.getTimestamp(uuid) <= Date.now() && | |
| UUIDv7.getVersion(uuid) == 7 && | |
| UUIDv7.getVariant(uuid) == 2; | |
| } | |
| public static getTimestamp(uuid: UUIDv7 | string): number { | |
| const stringUUID = uuid.toString(); | |
| return parseInt(stringUUID.slice(0, 12 + +stringUUID.includes("-")).replace("-", ""), 16); | |
| } | |
| public static getVersion(uuid: UUIDv7 | string): number { | |
| const stringUUID = uuid.toString(); | |
| return parseInt(stringUUID.charAt(12 + 2 * +stringUUID.includes("-")), 16); | |
| } | |
| public static getVariant(uuid: UUIDv7 | string): number { | |
| const stringUUID = uuid.toString(); | |
| return parseInt(stringUUID.charAt(16 + 3 * +stringUUID.includes("-")), 16) >> 2; | |
| } | |
| private readonly uuid: Buffer; | |
| private constructor() { | |
| this.uuid = this.generate(); | |
| } | |
| public toString(withDashes: boolean = true): string { | |
| const string = this.uuid.toString("hex"); | |
| if (withDashes) { | |
| return string.replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/, (_, timeHigh, timeLow, version, variant, random) => [ | |
| timeHigh, | |
| timeLow, | |
| version, | |
| variant, | |
| random | |
| ].join("-")); | |
| } else { | |
| return string; | |
| } | |
| } | |
| /** | |
| * @see {@link https://www.rfc-editor.org/rfc/rfc9562.html#section-5.7} | |
| */ | |
| private generate(): Buffer { | |
| const byteSize = 8; | |
| const versionByte = 0x7n; | |
| const variantByte = 0b10n; | |
| const randomAOverlap: OverlapConfig = { | |
| bits: 12, | |
| overlap: 4, | |
| }; | |
| const randomBOverlap: OverlapConfig = { | |
| bits: 62, | |
| overlap: 6 | |
| }; | |
| const randomA = this.createRandom(randomAOverlap.bits); | |
| const randomB = this.createRandom(randomBOverlap.bits); | |
| return this.writer([ | |
| { value: BigInt(Date.now()), length: 6 }, | |
| { value: this.overlap(versionByte, randomA, randomAOverlap), length: 1 }, | |
| { value: this.removeOverlap(randomA, randomAOverlap), length: (randomAOverlap.bits - randomAOverlap.overlap) / byteSize }, | |
| { value: this.overlap(variantByte, randomB, randomBOverlap), length: 1 }, | |
| { value: this.removeOverlap(randomB, randomBOverlap), length: (randomBOverlap.bits - randomBOverlap.overlap) / byteSize } | |
| ]); | |
| } | |
| private writer(configs: WriteConfig[]): Buffer { | |
| const buffer = Buffer.alloc(configs.reduce((acc, { length }) => acc + length, 0)); | |
| configs.reduce((offset, { value, length }) => { | |
| for (let i = BigInt(length - 1); i >= 0; i--) { | |
| buffer.writeUint8(Number(value >> i * 8n & 0xFFn), offset++); | |
| } | |
| return offset; | |
| }, 0); | |
| return buffer; | |
| } | |
| private overlap(high: bigint, low: bigint, overlapConfig: OverlapConfig): bigint { | |
| return high << BigInt(overlapConfig.overlap) | low >> BigInt(overlapConfig.bits - overlapConfig.overlap); | |
| } | |
| private removeOverlap(number: bigint, overlapConfig: OverlapConfig): bigint { | |
| return number & this.createMax(overlapConfig.bits - overlapConfig.overlap); | |
| } | |
| private createRandom(size: number): bigint { | |
| return Array(size).fill(0) | |
| .map(() => BigInt(Math.round(UUIDv7.RANDOM_ALGORITHM()))) | |
| .reduce((acc, bit) => (acc << 1n) | bit, 0n); | |
| } | |
| private createMax(size: number): bigint { | |
| return (1n << BigInt(size)) - 1n; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment