import { generateUuidV7 } from "./uuid.ts" const INTERNAL_ID: unique symbol = Symbol("INTERNAL_ID") type IdentifierSeed = string | number | symbol /** * @description Represent a logical identity value. * * @example * ;``` * const userId = generateId("user:1") * const example1 = isId(userId) * // Expect: true * * const example2 = String(userId) * // Expect: starts with "Symbol-ID-" * ``` */ export interface Id { [INTERNAL_ID]: string [Symbol.toPrimitive]: (hint: string) => string } const internalIdPacket: Record = {} /** * @description 根据种子生成 `Id`,或在未提供种子时生成一个新的 `Id`。 * 当传入种子时,同一种子会复用同一个 `Id` 实例;未传入种子时,每次都会得到新的 `Id`。 * * @example * ;``` * const stableA = generateId("user:1") * const stableB = generateId("user:1") * const example1 = Object.is(stableA, stableB) * // Expect: true * * const freshA = generateId() * const freshB = generateId() * const example2 = Object.is(freshA, freshB) * // Expect: false * ``` */ export const generateId = (seed?: IdentifierSeed | undefined): Id => { if (seed === undefined) { const id = { [INTERNAL_ID]: generateUuidV7(), [Symbol.toPrimitive]: (): string => { return idToString(id) }, } return id } const id = internalIdPacket[seed] ?? { [INTERNAL_ID]: generateUuidV7(), [Symbol.toPrimitive]: (): string => { return idToString(id) }, } internalIdPacket[seed] = id return id } /** * @description 判断一个值是否为 `Id`。 */ export const isId = (target: unknown): target is Id => { return typeof target === "object" && target !== null && INTERNAL_ID in target } type AssertId = (target: unknown) => asserts target is Id /** * @description 断言一个值是 `Id`。 * * @throws { TypeError } 当 target 不是 `Id` 时抛出 */ export const assertId: AssertId = (target: unknown): void => { if (isId(target) === false) { throw new TypeError(`Expected Id, got: ${String(target)}`) } } /** * @description 按引用身份比较两个 `Id` 值是否为同一个实例。 */ export const isSameId = (a: Id, b: Id): boolean => { return Object.is(a, b) } /** * @description 按内部标识值比较两个 `Id` 是否相等。 */ export const isEqualId = (a: Id, b: Id): boolean => { return a[INTERNAL_ID] === b[INTERNAL_ID] } /** * @description 将 `Id` 转换为可读字符串。 * 返回值格式为 `Symbol-ID-`。 * * @example * ;``` * const userId = generateId("user:1") * const example1 = idToString(userId) * // Expect: starts with "Symbol-ID-" * * const anonymousId = generateId() * const example2 = idToString(anonymousId) * // Expect: matches the format "Symbol-ID-" * ``` */ export const idToString = (id: Id): string => { assertId(id) return `Symbol-ID-${id[INTERNAL_ID]}` }