/** * Copyright 2026 Angus.Fenying * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import * as eL from './Errors.js'; import { uuidToString } from './Utils.js'; import { ETimeReversedStrategy } from './Constants.js'; const MAX_TIMESTAMP = 0xFFFF_FFFF_FFFF; // Maximum timestamp for UUIDv7 (48 bits) const RAND_A_CAP = 4096; export interface IUuid7GeneratorOptions { /** * The strategy for handling time reversal events. * * @default ETimeReversedStrategy.THROW_ERROR */ onTimeReversed?: ETimeReversedStrategy; } /** * The default strategy for the time reversal event, * which throws an `E_TIME_REVERSED` error. */ export const UUID7_DEFAULT_TIME_REVERSAL_STRATEGY = ETimeReversedStrategy.THROW_ERROR; /** * The class for generating UUID in version 7. */ export class Uuid7Generator { private _prevTime: number = 0; private readonly _onTimeReversedStrategy: ETimeReversedStrategy; public constructor(opts: IUuid7GeneratorOptions = {}) { this._onTimeReversedStrategy = opts.onTimeReversed ?? UUID7_DEFAULT_TIME_REVERSAL_STRATEGY; } /** * Generate a UUIDv7 string, which is based on the current timestamp and random values. * * @returns A UUIDv7 string. * * @example * ```ts * const generator = new Uuid7Generator(); * const uuid = generator.generate(); * console.log(uuid); * ``` */ public generate(): string { return uuidToString(this.generateAsBuffer()); } /** * Generate a binary UUIDv7 as a 16-byte buffer, which is based on the current timestamp and random values. * * @returns A Buffer containing the UUIDv7. * * @example * ```ts * const generator = new Uuid7Generator(); * const uuidBuffer = generator.generateAsBuffer(); * console.log(uuidBuffer); * ``` */ public generateAsBuffer(): Buffer { let now = Date.now(); if (this._prevTime > now) { switch (this._onTimeReversedStrategy) { case ETimeReversedStrategy.THROW_ERROR: throw new eL.E_TIME_REVERSED({ previous: this._prevTime, current: now }); case ETimeReversedStrategy.USE_REVERSED_TIME: // do nothing, because `timeOffset` is the reversed time. break; case ETimeReversedStrategy.USE_PREVIOUS_TIME: now = this._prevTime; break; } } return Uuid7Generator.generateBuffer(this._prevTime = now); } /** * Generate a UUIDv7 string based on the provided timestamp and random value. * * > This method is implemented in pure JavaScript. * * @param timestamp The 48-bit timestamp in milliseconds since the Unix epoch. If not provided, the current time will be used. * @param randA A 12-bit random value to ensure uniqueness. If not provided, a random value will be generated. * * @returns A UUIDv7 string based on the provided timestamp and random value. * * @throws {RangeError} If the timestamp is not a non-negative integer or exceeds the maximum allowed value. * * @example * ```ts * const uuid = Uuid7Generator.generate(Date.parse('2024-01-01T00:00:00Z'), 123456); * console.log(uuid); * ``` */ public static generate( timestamp: number = Date.now(), randA: number = Math.floor(Math.random() * RAND_A_CAP), ): string { return uuidToString(Uuid7Generator.generateBuffer(timestamp, randA)); } /** * Generate a binary UUIDv7 based on the provided timestamp and random value, and return it as a Buffer. * * > This method is implemented in pure JavaScript. * * @param timestamp The 48-bit timestamp in milliseconds since the Unix epoch. If not provided, the current time will be used. * @param randA A 12-bit random value to ensure uniqueness. If not provided, a random value will be generated. * * @returns A Buffer containing the UUIDv7 based on the provided timestamp and random value. * * @throws {RangeError} If the timestamp is not a non-negative integer or exceeds the maximum allowed value. * * @example * ```ts * const uuid = Uuid7Generator.generateBuffer(Date.parse('2024-01-01T00:00:00Z'), 123456); * console.log(uuid); * ``` */ public static generateBuffer( timestamp: number = Date.now(), randA: number = Math.floor(Math.random() * RAND_A_CAP), ): Buffer { if (!Number.isSafeInteger(timestamp) || timestamp < 0 || timestamp > MAX_TIMESTAMP) { throw new RangeError(`Timestamp must be an integer within the range of 0 to ${MAX_TIMESTAMP}.`); } if (!Number.isSafeInteger(randA) || randA < 0 || randA >= RAND_A_CAP) { throw new RangeError(`randA must be an integer within the range of 0 to ${RAND_A_CAP - 1}.`); } const buffer = Buffer.allocUnsafe(16); // Write the timestamp (48 bits) buffer.writeUIntBE(timestamp, 0, 6); // Write the version (4 bits) and the randA (12 bits) const versionAndRandA = (7 << 12) | (randA & 0x0FFF); buffer.writeUInt16BE(versionAndRandA, 6); // Write the variant (2 bits, 0b10) and random bits (62 bits) buffer.writeUInt32BE(((2 << 30) | Math.floor(Math.random() * 0x4000_0000)) >>> 0, 8); buffer.writeUInt32BE(Math.floor(Math.random() * 0x1_0000_0000), 12); return buffer; } }