const internalUUID_REGEXP = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i /** * @description 判断输入值是否为合法的 UUID 字符串。 * * @example * ``` * // Expect: true * const example1 = isUuid("550e8400-e29b-41d4-a716-446655440000") * // Expect: false * const example2 = isUuid("not-a-uuid") * ``` */ export const isUuid = (input: string): boolean => { return internalUUID_REGEXP.test(input) } /** * @description 断言输入值是合法的 UUID 字符串。 * * 当输入值不满足 UUID 格式时,该函数会抛出 TypeError,适合在标识边界层做快速失败。 * * @example * ``` * // Expect: no throw * const example1 = assertUuid("550e8400-e29b-41d4-a716-446655440000") * // Expect: throws TypeError * const example2 = () => assertUuid("not-a-uuid") * ``` * * @throws { TypeError } When input is not a valid UUID */ export const assertUuid = (input: string): void => { if (isUuid(input) === false) { throw new TypeError(`Expected a valid UUID string, got: ${input}`) } } /** * @description 读取合法 UUID 字符串中的版本号。 * * 该函数会先校验输入格式;如果输入不是合法 UUID,则会抛出 TypeError。 * * @example * ``` * // Expect: 4 * const example1 = getUuidVersion("550e8400-e29b-41d4-a716-446655440000") * // Expect: 1 * const example2 = getUuidVersion("123e4567-e89b-12d3-a456-426614174000") * ``` * * @throws { TypeError } When input is not a valid UUID */ export const getUuidVersion = (input: string): number => { assertUuid(input) return Number.parseInt(input[14]!, 16) } /** * @description 生成 UUID v4 字符串。 * * 当运行时提供 Web Crypto 能力时,优先使用安全随机源;否则回退到兼容性方案。 * * @example * ``` * const uuid = generateUuidV4() * // Expect: true * const example1 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(uuid) * ``` */ export const generateUuidV4 = (): string => { if (globalThis.crypto !== undefined) { const { crypto } = globalThis if (typeof crypto.randomUUID === "function") { return crypto.randomUUID() } if (typeof crypto.getRandomValues === "function" && typeof Uint8Array === "function") { const buffer = new Uint8Array(16) crypto.getRandomValues(buffer) buffer[6] = (buffer[6]! & 0x0f) | 0x40 buffer[8] = (buffer[8]! & 0x3f) | 0x80 const hex = [...buffer].map((value) => value.toString(16).padStart(2, "0")).join("") return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}` } } const fallbackUUID = (): string => { const random = (value: number): string => { return ((value ^ ((Math.random() * 16) >> (value / 4))) & 15).toString(16) } return "10000000-1000-4000-8000-100000000000".replaceAll(/[018]/g, (char: string) => random(Number(char)), ) } return fallbackUUID() } /** * @description 通过 URL.createObjectURL 侧向取得一个 UUID v4 风格的字符串。 * * 该函数依赖 Blob 与 URL API,适合浏览器等具备对象 URL 能力的环境。 * 如果只需要常规 UUID v4,优先使用 generateUuidV4。 * * @example * ``` * const uuid = generateUuidV4FromUrl() * // Expect: true * const example1 = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(uuid) * // Expect: 36 * const example2 = uuid.length * ``` */ export const generateUuidV4FromUrl = (): string => { const tempURL = URL.createObjectURL(new Blob()) const uuidInUrl = tempURL URL.revokeObjectURL(tempURL) const uuid = uuidInUrl.slice(uuidInUrl.lastIndexOf("/") + 1) return uuid } const internalUUIDv7RandomMask74 = (1n << 74n) - 1n const internalUUIDv7RandomMask48 = (1n << 48n) - 1n let internalUUIDv7LastTimestamp = -1n let internalUUIDv7MonotonicValue = 0n const internalUUIDv7FillRandom = (buffer: Uint8Array): void => { if (globalThis.crypto !== undefined) { const { crypto } = globalThis if (typeof crypto.getRandomValues === "function") { crypto.getRandomValues(buffer) return } } console.warn("generateUuidV7 requires crypto.getRandomValues") for (let i = 0; i < 10; i = i + 1) { buffer[i] = Math.floor(Math.random() * 256) } } const internalUUIDv7NextRandom74 = (timestamp: bigint): bigint => { if (timestamp !== internalUUIDv7LastTimestamp) { const randomValues = new Uint8Array(10) internalUUIDv7FillRandom(randomValues) const randomHex = Array.from(randomValues, (value) => value.toString(16).padStart(2, "0")).join( "", ) internalUUIDv7MonotonicValue = BigInt(`0x${randomHex}`) & internalUUIDv7RandomMask74 internalUUIDv7LastTimestamp = timestamp return internalUUIDv7MonotonicValue } internalUUIDv7MonotonicValue = (internalUUIDv7MonotonicValue + 1n) & internalUUIDv7RandomMask74 return internalUUIDv7MonotonicValue } /** * @description 生成 UUID v7 字符串。 * * 结果以前导时间戳编码排序信息,适合需要近似按生成时间排序的标识场景。 * * @example * ``` * const uuid = generateUuidV7() * // Expect: true * const example1 = /^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(uuid) * ``` */ export const generateUuidV7 = (): string => { const timestamp = BigInt(Date.now()) const random74 = internalUUIDv7NextRandom74(timestamp) const timeHex = timestamp.toString(16).padStart(12, "0") const randAValue = Number((random74 >> 62n) & 0xfffn) const randBValue = Number((random74 >> 48n) & 0x3fffn) const trailingValue = random74 & internalUUIDv7RandomMask48 const randA = ((0x7 << 12) | randAValue) & 0xffff const randB = ((0x2 << 14) | randBValue) & 0xffff const trailingHex = trailingValue.toString(16).padStart(12, "0") return `${timeHex.slice(0, 8)}-${timeHex.slice(8, 12)}-${randA.toString(16).padStart(4, "0")}-${randB.toString(16).padStart(4, "0")}-${trailingHex}` }