- id-normalization (#18): `brain.newId()` (UUID v7) + v7 default ids; non-UUID string ids are transparently normalized to a stable UUID v5 on creation AND every lookup — get/update/remove, relate(from,to), related, find({connected}), getMany, removeMany, the transact op-handler, and db.with() overlays — with the caller's original key preserved under `_originalId`. The engine only ever sees a UUID; real UUIDs pass through untouched. universal/uuid.ts gains v5/v7/isUUID + BRAINY_ID_NAMESPACE; new utils/idNormalization.ts (coerceNewEntityId / resolveEntityId). - aggregation min/max delete-safety: `queryAggregate` no longer leaves a stale min/max after deleting the current extreme — min/max now track a value multiset and recompute in-memory (no entity scan; that scan was the prior delete-then-hang a consumer reported). Other metrics were already exact across deletes. Regression test proves resolve-after-delete + correct new min/max. - release.sh RC-safe: explicit version arg, prerelease detection (npm `--tag rc` + GitHub `--prerelease`), full `test:ci` gate (unit + integration), current-branch push, npm-access verification after publish. - native-engine references point at `@soulcraft/cor` (8.0's partner) in user-facing strings/docs. Unit 1461/0 · integration 599/0 · tsc clean.
65 lines
2.6 KiB
TypeScript
65 lines
2.6 KiB
TypeScript
/**
|
|
* @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: <uuidv7> }
|
|
* coerceNewEntityId('user-123') // → { id: <stable uuidv5>, 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)
|
|
}
|