/** * @module utils/idNormalization * @description The 8.0 entity-id normalization layer. Every id that enters * Brainy is coerced to a valid UUID, because a native engine maps ids to compact * integers and requires real UUIDs (`encode()` rejects arbitrary strings). * * The rules (decided as AR.1 = "normalize"): * - **No id supplied** → a fresh time-ordered {@link v7} UUID (sortable by * creation time; friendly to the id↔int mapper and range scans). * - **A valid UUID** → used as-is. * - **Any other string** (a natural key like `'user-123'`) → a STABLE * {@link v5} UUID derived from it, so the same key always maps to the same id * and the caller can keep using their natural key on every read/relate while * the engine only ever sees a UUID. The original string is preserved so reads * can surface it. * * Two entry points: {@link coerceNewEntityId} at creation (add/relate), and * {@link resolveEntityId} at lookup (get/update/remove/relate endpoints). */ import { v5, v7, isUUID } from '../universal/uuid.js' /** Metadata key under which a caller's original (non-UUID) id is preserved. */ export const ORIGINAL_ID_KEY = '_originalId' /** * @description The result of normalizing an id at creation time. */ export interface CoercedId { /** The canonical UUID to store and hand to the engine. */ id: string /** The caller's original string, present only when it was normalized (non-UUID). */ originalId?: string } /** * @description Normalize an id supplied at CREATION (add/relate). Generates a * time-ordered UUID when none is given, passes a real UUID through, and maps any * other string to a stable namespaced UUID (recording the original). * @param id - The caller-supplied id, or undefined to auto-generate. * @returns The canonical UUID and, when normalized, the original string. * @example * coerceNewEntityId() // → { id: } * coerceNewEntityId('user-123') // → { id: , originalId: 'user-123' } */ export function coerceNewEntityId(id?: string | null): CoercedId { if (id === undefined || id === null || id === '') { return { id: v7() } } if (isUUID(id)) { return { id } } return { id: v5(id), originalId: id } } /** * @description Normalize an id used for LOOKUP (get/update/remove/relate * endpoints) to the same canonical UUID `coerceNewEntityId` produced — so a * caller can read/relate by their natural key transparently. A real UUID passes * through; any other string maps to its stable namespaced UUID. * @param id - The id to resolve. * @returns The canonical UUID. */ export function resolveEntityId(id: string): string { return isUUID(id) ? id : v5(id) }