feat(8.0): id-normalization (#18) + aggregation min/max delete-safety + RC-safe release
- 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.
This commit is contained in:
parent
606445cd61
commit
d02e522a3e
14 changed files with 943 additions and 100 deletions
|
|
@ -9,6 +9,10 @@
|
|||
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest'
|
||||
import { Brainy } from '../../src/brainy'
|
||||
import { NounType, VerbType } from '../../src/types/graphTypes'
|
||||
// 8.0 id normalization: the test factory seeds natural-key ids ('alice', …).
|
||||
// The engine maps each to a stable UUID (v5) and preserves the original under
|
||||
// _originalId, so entity.id is the canonical UUID and lookups round-trip by key.
|
||||
import { v5 } from '../../src/universal/uuid'
|
||||
import {
|
||||
createTestEntity,
|
||||
createTestRelation,
|
||||
|
|
@ -110,8 +114,9 @@ describe('Unified Find() Integration Tests', () => {
|
|||
})
|
||||
|
||||
expect(results).toHaveLength(3)
|
||||
// First result should be Alice herself with high similarity
|
||||
expect(results[0].entity.id).toBe('alice')
|
||||
// First result should be Alice herself with high similarity (her id is
|
||||
// the canonical v5 mapping of the natural key 'alice').
|
||||
expect(results[0].entity.id).toBe(v5('alice'))
|
||||
expect(results[0].score).toBeGreaterThan(0.95)
|
||||
})
|
||||
|
||||
|
|
@ -138,8 +143,8 @@ describe('Unified Find() Integration Tests', () => {
|
|||
|
||||
expect(results.length).toBeGreaterThan(0)
|
||||
// Should find Bob and Charlie (Alice's friends)
|
||||
const bobResult = results.find((r: any) => r.entity.id === 'bob')
|
||||
const charlieResult = results.find((r: any) => r.entity.id === 'charlie')
|
||||
const bobResult = results.find((r: any) => r.entity.id === v5('bob'))
|
||||
const charlieResult = results.find((r: any) => r.entity.id === v5('charlie'))
|
||||
expect(bobResult || charlieResult).toBeDefined()
|
||||
})
|
||||
|
||||
|
|
@ -154,7 +159,7 @@ describe('Unified Find() Integration Tests', () => {
|
|||
|
||||
expect(results.length).toBeGreaterThan(0)
|
||||
// Should find Alice (who is friends with Bob)
|
||||
const aliceResult = results.find((r: any) => r.entity.id === 'alice')
|
||||
const aliceResult = results.find((r: any) => r.entity.id === v5('alice'))
|
||||
expect(aliceResult).toBeDefined()
|
||||
})
|
||||
|
||||
|
|
@ -169,8 +174,8 @@ describe('Unified Find() Integration Tests', () => {
|
|||
|
||||
expect(results.length).toBeGreaterThan(0)
|
||||
// Should find both Bob and Charlie
|
||||
const bobResult = results.find((r: any) => r.entity.id === 'bob')
|
||||
const charlieResult = results.find((r: any) => r.entity.id === 'charlie')
|
||||
const bobResult = results.find((r: any) => r.entity.id === v5('bob'))
|
||||
const charlieResult = results.find((r: any) => r.entity.id === v5('charlie'))
|
||||
expect(bobResult).toBeDefined()
|
||||
expect(charlieResult).toBeDefined()
|
||||
})
|
||||
|
|
@ -204,9 +209,10 @@ describe('Unified Find() Integration Tests', () => {
|
|||
})
|
||||
|
||||
expect(results.length).toBeGreaterThan(0)
|
||||
// Should find direct connections (Bob, Charlie) and second-degree (Diana)
|
||||
const directConnections = ['bob', 'charlie']
|
||||
const secondDegreeConnections = ['diana']
|
||||
// Should find direct connections (Bob, Charlie) and second-degree (Diana).
|
||||
// Compare against the canonical v5 ids the engine stored for each key.
|
||||
const directConnections = [v5('bob'), v5('charlie')]
|
||||
const secondDegreeConnections = [v5('diana')]
|
||||
|
||||
const hasDirect = results.some((r: any) => directConnections.includes(r.entity.id))
|
||||
const hasSecondDegree = results.some((r: any) => secondDegreeConnections.includes(r.entity.id))
|
||||
|
|
@ -326,7 +332,7 @@ describe('Unified Find() Integration Tests', () => {
|
|||
|
||||
expect(results.length).toBeGreaterThan(0)
|
||||
// Charlie should be highly ranked due to direct connection
|
||||
const charlieResult = results.find((r: any) => r.entity.id === 'charlie')
|
||||
const charlieResult = results.find((r: any) => r.entity.id === v5('charlie'))
|
||||
if (charlieResult) {
|
||||
expect(charlieResult.score).toBeGreaterThan(0.5)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue