chore(8.0): final pre-RC1 sweep — API consistency, named errors, orphans, zero-cast codebase
This commit is contained in:
parent
970e08c466
commit
1f7e365a4e
237 changed files with 1951 additions and 49413 deletions
|
|
@ -76,7 +76,7 @@ describe('Brainy 3.0 Core (Unit Tests)', () => {
|
|||
expect(await brain.get(id)).toBeTruthy()
|
||||
|
||||
// Delete it
|
||||
await brain.delete(id)
|
||||
await brain.remove(id)
|
||||
|
||||
// Verify it's gone
|
||||
expect(await brain.get(id)).toBeNull()
|
||||
|
|
@ -96,7 +96,7 @@ describe('Brainy 3.0 Core (Unit Tests)', () => {
|
|||
})).rejects.toThrow()
|
||||
|
||||
// delete should not throw for non-existent ID
|
||||
await expect(brain.delete(fakeId)).resolves.not.toThrow()
|
||||
await expect(brain.remove(fakeId)).resolves.not.toThrow()
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -199,9 +199,11 @@ describe('Brainy 3.0 Core (Unit Tests)', () => {
|
|||
data: { name: 'Test2' },
|
||||
type: NounType.Concept
|
||||
})
|
||||
|
||||
// Statistics would be available through augmentation system
|
||||
// The exact API depends on augmentation configuration
|
||||
|
||||
const stats = await brain.stats()
|
||||
expect(stats.mode).toBe('writer')
|
||||
expect(stats.entityCount).toBeGreaterThanOrEqual(2)
|
||||
expect(stats.entitiesByType[NounType.Concept]).toBeGreaterThanOrEqual(2)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -155,7 +155,7 @@ describe('Brainy Batch Operations', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('deleteMany - Batch Deletion', () => {
|
||||
describe('removeMany - Batch Deletion', () => {
|
||||
let testIds: string[]
|
||||
|
||||
beforeEach(async () => {
|
||||
|
|
@ -171,7 +171,7 @@ describe('Brainy Batch Operations', () => {
|
|||
})
|
||||
|
||||
it('should delete multiple entities at once', async () => {
|
||||
await brain.deleteMany({ ids: testIds })
|
||||
await brain.removeMany({ ids: testIds })
|
||||
|
||||
// All should be gone
|
||||
for (const id of testIds) {
|
||||
|
|
@ -185,7 +185,7 @@ describe('Brainy Batch Operations', () => {
|
|||
const toDelete = [testIds[0], testIds[2], testIds[4]]
|
||||
const toKeep = [testIds[1], testIds[3]]
|
||||
|
||||
await brain.deleteMany({ ids: toDelete })
|
||||
await brain.removeMany({ ids: toDelete })
|
||||
|
||||
// Deleted ones should be gone
|
||||
for (const id of toDelete) {
|
||||
|
|
@ -212,7 +212,7 @@ describe('Brainy Batch Operations', () => {
|
|||
await brain.relate({ from: person2, to: org, type: VerbType.MemberOf as any })
|
||||
|
||||
// Delete the organization
|
||||
await brain.deleteMany({ ids: [org] })
|
||||
await brain.removeMany({ ids: [org] })
|
||||
|
||||
// Organization should be gone
|
||||
const deletedOrg = await brain.get(org)
|
||||
|
|
@ -237,7 +237,7 @@ describe('Brainy Batch Operations', () => {
|
|||
const manyIds = manyResult.successful
|
||||
|
||||
const startTime = Date.now()
|
||||
await brain.deleteMany({ ids: manyIds })
|
||||
await brain.removeMany({ ids: manyIds })
|
||||
const duration = Date.now() - startTime
|
||||
|
||||
// v5.4.0: Increased to 14500ms for type-first storage + system load variance
|
||||
|
|
@ -281,7 +281,7 @@ describe('Brainy Batch Operations', () => {
|
|||
expect(relationIds).toHaveLength(3)
|
||||
|
||||
// Verify relationships exist
|
||||
const person1Relations = await brain.getRelations({ from: entities[0] })
|
||||
const person1Relations = await brain.related({ from: entities[0] })
|
||||
expect(person1Relations.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
|
|
@ -297,13 +297,13 @@ describe('Brainy Batch Operations', () => {
|
|||
expect(relationIds).toHaveLength(3)
|
||||
|
||||
// Verify different types
|
||||
const friendRelations = await brain.getRelations({
|
||||
const friendRelations = await brain.related({
|
||||
from: entities[0],
|
||||
type: VerbType.FriendOf
|
||||
})
|
||||
expect(friendRelations.length).toBeGreaterThan(0)
|
||||
|
||||
const workRelations = await brain.getRelations({
|
||||
const workRelations = await brain.related({
|
||||
from: entities[0],
|
||||
type: VerbType.WorksWith
|
||||
})
|
||||
|
|
@ -321,10 +321,10 @@ describe('Brainy Batch Operations', () => {
|
|||
expect(relationIds).toHaveLength(2)
|
||||
|
||||
// Both should have the relationship
|
||||
const person1Friends = await brain.getRelations({ from: entities[0] })
|
||||
const person1Friends = await brain.related({ from: entities[0] })
|
||||
expect(person1Friends.length).toBeGreaterThan(0)
|
||||
|
||||
const person2Friends = await brain.getRelations({ from: entities[1] })
|
||||
const person2Friends = await brain.related({ from: entities[1] })
|
||||
expect(person2Friends.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
|
|
@ -358,7 +358,7 @@ describe('Brainy Batch Operations', () => {
|
|||
expect(duration).toBeLessThan(1000) // Should be fast
|
||||
|
||||
// Verify company has all relationships
|
||||
const companyRelations = await brain.getRelations({ to: company })
|
||||
const companyRelations = await brain.related({ to: company })
|
||||
expect(companyRelations.length).toBeGreaterThanOrEqual(50)
|
||||
})
|
||||
|
||||
|
|
@ -460,7 +460,7 @@ describe('Brainy Batch Operations', () => {
|
|||
await brain.relateMany({ items: relationships })
|
||||
|
||||
// 4. Delete some entities
|
||||
await brain.deleteMany({ ids: initialIds.slice(15) })
|
||||
await brain.removeMany({ ids: initialIds.slice(15) })
|
||||
|
||||
const totalTime = Date.now() - startTime
|
||||
|
||||
|
|
@ -473,7 +473,7 @@ describe('Brainy Batch Operations', () => {
|
|||
const deleted = await brain.get(initialIds[19])
|
||||
expect(deleted).toBeNull()
|
||||
|
||||
const relations = await brain.getRelations({ from: initialIds[0] })
|
||||
const relations = await brain.related({ from: initialIds[0] })
|
||||
expect(relations.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
|
@ -487,7 +487,7 @@ describe('Brainy Batch Operations', () => {
|
|||
expect(result.successful).toHaveLength(0)
|
||||
|
||||
await brain.updateMany({ items: [] })
|
||||
await brain.deleteMany({ ids: [] })
|
||||
await brain.removeMany({ ids: [] })
|
||||
// Should not throw
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -169,9 +169,9 @@ describe('Brainy.fillSubtypes()', () => {
|
|||
expect(report.filled).toBe(1)
|
||||
expect(report.byType).toEqual({ relatedTo: 1 })
|
||||
|
||||
const relations = await brain.getRelations({ from: a })
|
||||
const relations = await brain.related({ from: a })
|
||||
expect(relations.find((r) => r.id === relId)!.subtype).toBe('unspecified')
|
||||
const reverse = await brain.getRelations({ from: b })
|
||||
const reverse = await brain.related({ from: b })
|
||||
expect(reverse.find((r) => r.id === labeled)!.subtype).toBe('colleague') // untouched
|
||||
})
|
||||
|
||||
|
|
@ -195,7 +195,7 @@ describe('Brainy.fillSubtypes()', () => {
|
|||
})
|
||||
|
||||
expect(report.filled).toBe(1)
|
||||
const relations = await brain.getRelations({ from: a })
|
||||
const relations = await brain.related({ from: a })
|
||||
expect(relations[0].subtype).toBe('dotted-line')
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ describe('Brainy.get()', () => {
|
|||
data: 'To be deleted',
|
||||
type: 'thing'
|
||||
}))
|
||||
await brain.delete(id)
|
||||
await brain.remove(id)
|
||||
|
||||
// Act
|
||||
const entity = await brain.get(id)
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ describe('Duplicate Check Optimization', () => {
|
|||
expect(relationId2).toBe(relationId1)
|
||||
|
||||
// Verify only one relationship exists
|
||||
const relations = await brain.getRelations({ from: personId })
|
||||
const relations = await brain.related({ from: personId })
|
||||
expect(relations).toHaveLength(1)
|
||||
expect(relations[0].id).toBe(relationId1)
|
||||
})
|
||||
|
|
@ -85,7 +85,7 @@ describe('Duplicate Check Optimization', () => {
|
|||
expect(relationId2).not.toBe(relationId1)
|
||||
|
||||
// Verify both relationships exist
|
||||
const relations = await brain.getRelations({ from: personId })
|
||||
const relations = await brain.related({ from: personId })
|
||||
expect(relations).toHaveLength(2)
|
||||
// Both relations should exist with different IDs
|
||||
const relationIds = relations.map(r => r.id)
|
||||
|
|
@ -129,7 +129,7 @@ describe('Duplicate Check Optimization', () => {
|
|||
expect(elapsed).toBeLessThan(10)
|
||||
|
||||
// Verify duplicate was detected
|
||||
const relations = await brain.getRelations({ from: sourceId })
|
||||
const relations = await brain.related({ from: sourceId })
|
||||
expect(relations).toHaveLength(50) // No duplicate created
|
||||
})
|
||||
|
||||
|
|
@ -198,7 +198,7 @@ describe('Duplicate Check Optimization', () => {
|
|||
})
|
||||
|
||||
// Verify both relationships exist
|
||||
let relations = await brain.getRelations({ from: person })
|
||||
let relations = await brain.related({ from: person })
|
||||
expect(relations).toHaveLength(2)
|
||||
const relationIds = relations.map(r => r.id)
|
||||
expect(relationIds).toContain(rel1)
|
||||
|
|
@ -218,7 +218,7 @@ describe('Duplicate Check Optimization', () => {
|
|||
expect(duplicate).toBe(rel1)
|
||||
|
||||
// Verify still same number of relationships (no duplicate added)
|
||||
const finalRelations = await brain.getRelations({ from: person })
|
||||
const finalRelations = await brain.related({ from: person })
|
||||
expect(finalRelations.length).toBe(relations.length)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ describe('Brainy.relate()', () => {
|
|||
expect(typeof relationId).toBe('string')
|
||||
|
||||
// Verify relationship exists
|
||||
const relations = await brain.getRelations({ from: entity1Id })
|
||||
const relations = await brain.related({ from: entity1Id })
|
||||
expect(relations.length).toBeGreaterThan(0)
|
||||
expect(relations.some(r => r.to === entity2Id)).toBe(true)
|
||||
})
|
||||
|
|
@ -76,7 +76,7 @@ describe('Brainy.relate()', () => {
|
|||
})
|
||||
|
||||
// Assert
|
||||
const relations = await brain.getRelations({ from: entity1Id })
|
||||
const relations = await brain.related({ from: entity1Id })
|
||||
const relation = relations.find(r => r.to === entity2Id)
|
||||
expect(relation).toBeDefined()
|
||||
expect(relation!.weight).toBe(0.8)
|
||||
|
|
@ -99,7 +99,7 @@ describe('Brainy.relate()', () => {
|
|||
})
|
||||
|
||||
// Assert
|
||||
const relations = await brain.getRelations({ from: entity1Id })
|
||||
const relations = await brain.related({ from: entity1Id })
|
||||
const relation = relations.find(r => r.to === entity2Id)
|
||||
expect(relation).toBeDefined()
|
||||
expect(relation!.metadata || {}).toMatchObject(metadata)
|
||||
|
|
@ -115,8 +115,8 @@ describe('Brainy.relate()', () => {
|
|||
})
|
||||
|
||||
// Assert - Check both directions
|
||||
const forwardRelations = await brain.getRelations({ from: entity1Id })
|
||||
const reverseRelations = await brain.getRelations({ from: entity2Id })
|
||||
const forwardRelations = await brain.related({ from: entity1Id })
|
||||
const reverseRelations = await brain.related({ from: entity2Id })
|
||||
|
||||
expect(forwardRelations.some(r => r.to === entity2Id)).toBe(true)
|
||||
expect(reverseRelations.some(r => r.to === entity1Id)).toBe(true)
|
||||
|
|
@ -137,7 +137,7 @@ describe('Brainy.relate()', () => {
|
|||
})
|
||||
|
||||
// Assert
|
||||
const relations = await brain.getRelations({ from: entity1Id })
|
||||
const relations = await brain.related({ from: entity1Id })
|
||||
expect(relations.length).toBe(2)
|
||||
expect(relations.some(r => r.to === entity2Id)).toBe(true)
|
||||
expect(relations.some(r => r.to === entity3Id)).toBe(true)
|
||||
|
|
@ -158,7 +158,7 @@ describe('Brainy.relate()', () => {
|
|||
})
|
||||
|
||||
// Assert
|
||||
const relations = await brain.getRelations({ from: entity1Id })
|
||||
const relations = await brain.related({ from: entity1Id })
|
||||
const toEntity2 = relations.filter(r => r.to === entity2Id)
|
||||
expect(toEntity2.length).toBe(2)
|
||||
expect(toEntity2.some(r => r.type === 'worksWith')).toBe(true)
|
||||
|
|
@ -175,7 +175,7 @@ describe('Brainy.relate()', () => {
|
|||
})
|
||||
|
||||
// Assert
|
||||
const relations = await brain.getRelations({ from: entity1Id })
|
||||
const relations = await brain.related({ from: entity1Id })
|
||||
const selfRelation = relations.find(r => r.to === entity1Id)
|
||||
expect(selfRelation).toBeDefined()
|
||||
expect(selfRelation!.metadata?.type).toBe('self-reference')
|
||||
|
|
@ -241,7 +241,7 @@ describe('Brainy.relate()', () => {
|
|||
// Second call should return existing relationship ID instead of creating duplicate
|
||||
expect(id1).toBe(id2)
|
||||
|
||||
const relations = await brain.getRelations({ from: entity1Id })
|
||||
const relations = await brain.related({ from: entity1Id })
|
||||
const matches = relations.filter(r =>
|
||||
r.to === entity2Id && r.type === 'worksWith'
|
||||
)
|
||||
|
|
@ -267,7 +267,7 @@ describe('Brainy.relate()', () => {
|
|||
})
|
||||
|
||||
// Assert
|
||||
const relations = await brain.getRelations({ from: entity1Id })
|
||||
const relations = await brain.related({ from: entity1Id })
|
||||
const relation = relations.find(r => r.to === entity2Id)
|
||||
expect(relation).toBeDefined()
|
||||
expect(relation!.metadata?.bigArray).toHaveLength(100)
|
||||
|
|
@ -291,7 +291,7 @@ describe('Brainy.relate()', () => {
|
|||
})
|
||||
|
||||
// Assert
|
||||
const relations = await brain.getRelations({ from: entity1Id })
|
||||
const relations = await brain.related({ from: entity1Id })
|
||||
const relation = relations.find(r => r.to === entity2Id)
|
||||
expect(relation!.metadata || {}).toMatchObject(specialMetadata)
|
||||
})
|
||||
|
|
@ -356,8 +356,8 @@ describe('Brainy.relate()', () => {
|
|||
})
|
||||
|
||||
// Assert - Multiple queries should return same data
|
||||
const relations1 = await brain.getRelations({ from: entity1Id })
|
||||
const relations2 = await brain.getRelations({ from: entity1Id })
|
||||
const relations1 = await brain.related({ from: entity1Id })
|
||||
const relations2 = await brain.related({ from: entity1Id })
|
||||
|
||||
expect(relations1.length).toBe(relations2.length)
|
||||
const rel1 = relations1.find(r => r.to === entity2Id)
|
||||
|
|
@ -386,7 +386,7 @@ describe('Brainy.relate()', () => {
|
|||
})
|
||||
|
||||
// Assert - Relationship should still exist
|
||||
const relations = await brain.getRelations({ from: entity1Id })
|
||||
const relations = await brain.related({ from: entity1Id })
|
||||
expect(relations.some(r => r.to === entity2Id)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
|
@ -407,8 +407,8 @@ describe('Brainy.relate()', () => {
|
|||
})
|
||||
|
||||
// Act - Get relationships step by step
|
||||
const step1 = await brain.getRelations({ from: entity1Id })
|
||||
const entity2Relations = await brain.getRelations({ from: entity2Id })
|
||||
const step1 = await brain.related({ from: entity1Id })
|
||||
const entity2Relations = await brain.related({ from: entity2Id })
|
||||
|
||||
// Assert
|
||||
expect(step1.some(r => r.to === entity2Id)).toBe(true)
|
||||
|
|
@ -436,9 +436,9 @@ describe('Brainy.relate()', () => {
|
|||
})
|
||||
|
||||
// Assert - All relationships should exist
|
||||
const rel1 = await brain.getRelations({ from: entity1Id })
|
||||
const rel2 = await brain.getRelations({ from: entity2Id })
|
||||
const rel3 = await brain.getRelations({ from: entity3Id })
|
||||
const rel1 = await brain.related({ from: entity1Id })
|
||||
const rel2 = await brain.related({ from: entity2Id })
|
||||
const rel3 = await brain.related({ from: entity3Id })
|
||||
|
||||
expect(rel1.some(r => r.to === entity2Id)).toBe(true)
|
||||
expect(rel2.some(r => r.to === entity3Id)).toBe(true)
|
||||
|
|
|
|||
|
|
@ -284,7 +284,7 @@ describe('reserved-field metadata remap (8.0 contract)', () => {
|
|||
expect(result?.entity._rev).toBe(1)
|
||||
})
|
||||
|
||||
it('getRelations() by target surfaces reserved fields top-level, custom-only metadata', async () => {
|
||||
it('related() by target surfaces reserved fields top-level, custom-only metadata', async () => {
|
||||
const a = await brain.add({ type: NounType.Person, subtype: 'employee', data: 'src' })
|
||||
const b = await brain.add({ type: NounType.Person, subtype: 'employee', data: 'tgt' })
|
||||
const relId = await brain.relate({
|
||||
|
|
@ -298,7 +298,7 @@ describe('reserved-field metadata remap (8.0 contract)', () => {
|
|||
metadata: { note: 'target path' }
|
||||
})
|
||||
|
||||
const relations = await brain.getRelations({ to: b })
|
||||
const relations = await brain.related({ to: b })
|
||||
const rel = relations.find((r) => r.id === relId)
|
||||
expect(rel).toBeDefined()
|
||||
expect(rel?.metadata).toEqual({ note: 'target path' })
|
||||
|
|
@ -329,7 +329,7 @@ describe('reserved-field metadata remap (8.0 contract)', () => {
|
|||
service: 'orders'
|
||||
})
|
||||
|
||||
const relations = await brain.getRelations({ from: a })
|
||||
const relations = await brain.related({ from: a })
|
||||
const rel = relations.find((r) => r.id === relId)
|
||||
expect(rel?.confidence).toBe(0.77)
|
||||
expect(rel?.service).toBe('orders')
|
||||
|
|
@ -344,7 +344,7 @@ describe('reserved-field metadata remap (8.0 contract)', () => {
|
|||
metadata: { confidence: 0.4, weight: 0.3, role: 'peer' } as object
|
||||
})
|
||||
|
||||
const relations = await brain.getRelations({ from: a })
|
||||
const relations = await brain.related({ from: a })
|
||||
const rel = relations.find((r) => r.id === relId)
|
||||
expect(rel?.confidence).toBe(0.4)
|
||||
expect(rel?.weight).toBe(0.3)
|
||||
|
|
@ -360,7 +360,7 @@ describe('reserved-field metadata remap (8.0 contract)', () => {
|
|||
metadata: { note: 'no echo' }
|
||||
})
|
||||
|
||||
const relations = await brain.getRelations({ from: a })
|
||||
const relations = await brain.related({ from: a })
|
||||
const rel = relations.find((r) => r.id === relId)
|
||||
expect(rel?.type).toBe(VerbType.RelatedTo)
|
||||
expect((rel?.metadata as Record<string, unknown>)?.verb).toBeUndefined()
|
||||
|
|
@ -382,7 +382,7 @@ describe('reserved-field metadata remap (8.0 contract)', () => {
|
|||
metadata: { confidence: 0.55, subtype: 'dotted-line', extra: 'applied' } as object
|
||||
})
|
||||
|
||||
const relations = await brain.getRelations({ from: a })
|
||||
const relations = await brain.related({ from: a })
|
||||
const rel = relations.find((r) => r.id === relId)
|
||||
expect(rel?.confidence).toBe(0.55)
|
||||
expect(rel?.subtype).toBe('dotted-line')
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
* - `getNeighbors` speaks entity ints both ways via the shared idMapper
|
||||
* - unmapped entity ints / UUIDs produce empty reads, never errors
|
||||
* - a missing idMapper fails loudly (wiring bug, not a silent fallback)
|
||||
* - coordinator-level behavior: relate → getRelations/neighbors → unrelate
|
||||
* - coordinator-level behavior: relate → related/neighbors → unrelate
|
||||
* works end-to-end over the BigInt boundary (public API unchanged)
|
||||
*/
|
||||
|
||||
|
|
@ -156,7 +156,7 @@ describe('Coordinator — relate/related end-to-end over the BigInt boundary', (
|
|||
await brain.close()
|
||||
})
|
||||
|
||||
it('relate → getRelations → neighbors round-trips (public API unchanged)', async () => {
|
||||
it('relate → related → neighbors round-trips (public API unchanged)', async () => {
|
||||
const personId = await brain.add({
|
||||
data: { name: 'Ada' },
|
||||
type: NounType.Person
|
||||
|
|
@ -172,8 +172,8 @@ describe('Coordinator — relate/related end-to-end over the BigInt boundary', (
|
|||
type: VerbType.WorksWith
|
||||
})
|
||||
|
||||
// getRelations resolves verb ints back to verb-id strings internally.
|
||||
const relations = await brain.getRelations({ from: personId })
|
||||
// related resolves verb ints back to verb-id strings internally.
|
||||
const relations = await brain.related({ from: personId })
|
||||
expect(relations).toHaveLength(1)
|
||||
expect(relations[0].id).toBe(relId)
|
||||
expect(relations[0].to).toBe(projectId)
|
||||
|
|
@ -208,7 +208,7 @@ describe('Coordinator — relate/related end-to-end over the BigInt boundary', (
|
|||
const relId = await brain.relate({ from: a, to: b, type: VerbType.RelatedTo })
|
||||
await brain.unrelate(relId)
|
||||
|
||||
const relations = await brain.getRelations({ from: a })
|
||||
const relations = await brain.related({ from: a })
|
||||
expect(relations).toHaveLength(0)
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,339 +0,0 @@
|
|||
/**
|
||||
* GraphAdjacencyIndex Pagination Tests (v5.8.0, updated for the 8.0 u64 contract)
|
||||
*
|
||||
* Tests for pagination support in GraphIndex methods:
|
||||
* - getNeighbors() with limit/offset
|
||||
* - getVerbIdsBySource() with limit/offset
|
||||
* - getVerbIdsByTarget() with limit/offset
|
||||
*
|
||||
* 8.0 BigInt boundary: entity ints in (resolved via the metadata index's
|
||||
* idMapper), entity/verb ints out (`bigint[]`). Entity ints map back to UUIDs
|
||||
* via `idMapper.getUuid(Number(int))`; verb ints via `verbIntsToIds()`.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach } from 'vitest'
|
||||
import { Brainy } from '../../../src/brainy.js'
|
||||
import { NounType, VerbType } from '../../../src/types/graphTypes.js'
|
||||
|
||||
describe('GraphAdjacencyIndex Pagination', () => {
|
||||
let brain: Brainy
|
||||
let centralId: string
|
||||
let neighborIds: string[]
|
||||
|
||||
/** The brain's graph index (BigInt provider contract). */
|
||||
const graphIndex = () => (brain as any).graphIndex
|
||||
|
||||
/** The shared UUID ↔ int resolver. */
|
||||
const idMapper = () => (brain as any).metadataIndex.getIdMapper()
|
||||
|
||||
/** Resolve a UUID to its entity int (must already be mapped). */
|
||||
const entityInt = (uuid: string): bigint => {
|
||||
const intId = idMapper().getInt(uuid)
|
||||
expect(intId).toBeDefined()
|
||||
return BigInt(intId!)
|
||||
}
|
||||
|
||||
/** Map returned entity ints back to UUIDs. */
|
||||
const intsToUuids = (ints: bigint[]): string[] =>
|
||||
ints
|
||||
.map((i) => idMapper().getUuid(Number(i)))
|
||||
.filter((u: string | undefined): u is string => u !== undefined)
|
||||
|
||||
beforeEach(async () => {
|
||||
brain = new Brainy({ requireSubtype: false })
|
||||
await brain.init()
|
||||
|
||||
// Create central entity
|
||||
centralId = await brain.add({
|
||||
data: { name: 'Central Hub' },
|
||||
type: NounType.Thing
|
||||
})
|
||||
|
||||
// Create 50 neighbor entities with relationships
|
||||
neighborIds = []
|
||||
for (let i = 0; i < 50; i++) {
|
||||
const neighborId = await brain.add({
|
||||
data: { name: `Neighbor ${i}`, index: i },
|
||||
type: NounType.Thing
|
||||
})
|
||||
neighborIds.push(neighborId)
|
||||
|
||||
// Create outgoing relationship from central to neighbor
|
||||
await brain.relate({
|
||||
from: centralId,
|
||||
to: neighborId,
|
||||
type: VerbType.RelatesTo
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
describe('getNeighbors() Pagination', () => {
|
||||
it('should return all neighbors without pagination', async () => {
|
||||
const neighborInts = await graphIndex().getNeighbors(entityInt(centralId))
|
||||
const neighbors = intsToUuids(neighborInts)
|
||||
|
||||
// Should return all 50 neighbors
|
||||
expect(neighbors).toHaveLength(50)
|
||||
expect(neighbors.every((id: string) => neighborIds.includes(id))).toBe(true)
|
||||
})
|
||||
|
||||
it('should return all outgoing neighbors with direction option', async () => {
|
||||
const neighbors = await graphIndex().getNeighbors(entityInt(centralId), { direction: 'out' })
|
||||
|
||||
expect(neighbors).toHaveLength(50)
|
||||
})
|
||||
|
||||
it('should paginate with limit only', async () => {
|
||||
const page1Ints = await graphIndex().getNeighbors(entityInt(centralId), { limit: 10 })
|
||||
const page1 = intsToUuids(page1Ints)
|
||||
|
||||
expect(page1).toHaveLength(10)
|
||||
expect(page1.every((id: string) => neighborIds.includes(id))).toBe(true)
|
||||
})
|
||||
|
||||
it('should paginate with offset only', async () => {
|
||||
const all = await graphIndex().getNeighbors(entityInt(centralId))
|
||||
const offsetResults = await graphIndex().getNeighbors(entityInt(centralId), { offset: 10 })
|
||||
|
||||
// Offset 10 should return all after first 10
|
||||
expect(offsetResults).toHaveLength(all.length - 10)
|
||||
})
|
||||
|
||||
it('should paginate with both limit and offset', async () => {
|
||||
const central = entityInt(centralId)
|
||||
const page1: bigint[] = await graphIndex().getNeighbors(central, { limit: 10, offset: 0 })
|
||||
const page2: bigint[] = await graphIndex().getNeighbors(central, { limit: 10, offset: 10 })
|
||||
const page3: bigint[] = await graphIndex().getNeighbors(central, { limit: 10, offset: 20 })
|
||||
|
||||
// Each page should have 10 results
|
||||
expect(page1).toHaveLength(10)
|
||||
expect(page2).toHaveLength(10)
|
||||
expect(page3).toHaveLength(10)
|
||||
|
||||
// Pages should not overlap
|
||||
const page2Set = new Set(page2)
|
||||
const page3Set = new Set(page3)
|
||||
|
||||
const overlap12 = page1.filter((id) => page2Set.has(id))
|
||||
const overlap23 = page2.filter((id) => page3Set.has(id))
|
||||
|
||||
expect(overlap12).toHaveLength(0)
|
||||
expect(overlap23).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('should handle offset beyond total count', async () => {
|
||||
const results = await graphIndex().getNeighbors(entityInt(centralId), { offset: 100 })
|
||||
|
||||
// Offset 100 > 50 total → empty array
|
||||
expect(results).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('should handle limit = 0', async () => {
|
||||
const results = await graphIndex().getNeighbors(entityInt(centralId), { limit: 0 })
|
||||
|
||||
expect(results).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('should paginate with direction filter', async () => {
|
||||
// Get first 10 outgoing neighbors
|
||||
const page1 = await graphIndex().getNeighbors(entityInt(centralId), {
|
||||
direction: 'out',
|
||||
limit: 10,
|
||||
offset: 0
|
||||
})
|
||||
|
||||
expect(page1).toHaveLength(10)
|
||||
})
|
||||
|
||||
it('should handle pagination with incoming direction', async () => {
|
||||
// Create some incoming relationships
|
||||
const sourceId = await brain.add({
|
||||
data: { name: 'Source' },
|
||||
type: NounType.Thing
|
||||
})
|
||||
|
||||
await brain.relate({
|
||||
from: sourceId,
|
||||
to: centralId,
|
||||
type: VerbType.RelatesTo
|
||||
})
|
||||
|
||||
// Get incoming neighbors with pagination
|
||||
const incoming = await graphIndex().getNeighbors(entityInt(centralId), {
|
||||
direction: 'in',
|
||||
limit: 10
|
||||
})
|
||||
|
||||
expect(incoming.length).toBeGreaterThan(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getVerbIdsBySource() Pagination', () => {
|
||||
it('should return all verb ints without pagination and resolve them back to ids', async () => {
|
||||
const verbInts: bigint[] = await graphIndex().getVerbIdsBySource(entityInt(centralId))
|
||||
|
||||
// Should return all 50 verb ints
|
||||
expect(verbInts).toHaveLength(50)
|
||||
|
||||
// Every int must resolve back to a verb-id string
|
||||
const verbIds: (string | null)[] = await graphIndex().verbIntsToIds(verbInts)
|
||||
expect(verbIds).toHaveLength(50)
|
||||
expect(verbIds.every((id) => typeof id === 'string')).toBe(true)
|
||||
})
|
||||
|
||||
it('should paginate verb ints with limit', async () => {
|
||||
const page1 = await graphIndex().getVerbIdsBySource(entityInt(centralId), { limit: 10 })
|
||||
|
||||
expect(page1).toHaveLength(10)
|
||||
})
|
||||
|
||||
it('should paginate verb ints with offset', async () => {
|
||||
const all = await graphIndex().getVerbIdsBySource(entityInt(centralId))
|
||||
const offsetResults = await graphIndex().getVerbIdsBySource(entityInt(centralId), { offset: 10 })
|
||||
|
||||
expect(offsetResults).toHaveLength(all.length - 10)
|
||||
})
|
||||
|
||||
it('should paginate verb ints with limit and offset', async () => {
|
||||
const central = entityInt(centralId)
|
||||
const page1: bigint[] = await graphIndex().getVerbIdsBySource(central, { limit: 10, offset: 0 })
|
||||
const page2: bigint[] = await graphIndex().getVerbIdsBySource(central, { limit: 10, offset: 10 })
|
||||
const page3: bigint[] = await graphIndex().getVerbIdsBySource(central, { limit: 10, offset: 20 })
|
||||
|
||||
// Each page should have 10 results
|
||||
expect(page1).toHaveLength(10)
|
||||
expect(page2).toHaveLength(10)
|
||||
expect(page3).toHaveLength(10)
|
||||
|
||||
// Pages should not overlap
|
||||
const combined = [...page1, ...page2, ...page3]
|
||||
const unique = new Set(combined)
|
||||
expect(unique.size).toBe(30) // All unique
|
||||
})
|
||||
|
||||
it('should handle pagination edge cases for verb ints', async () => {
|
||||
// Offset beyond total
|
||||
const beyondTotal = await graphIndex().getVerbIdsBySource(entityInt(centralId), { offset: 100 })
|
||||
expect(beyondTotal).toHaveLength(0)
|
||||
|
||||
// Limit = 0
|
||||
const zeroLimit = await graphIndex().getVerbIdsBySource(entityInt(centralId), { limit: 0 })
|
||||
expect(zeroLimit).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getVerbIdsByTarget() Pagination', () => {
|
||||
it('should return all verb ints targeting an entity', async () => {
|
||||
// Pick a neighbor that's a target of relationships
|
||||
const targetId = neighborIds[0]
|
||||
const verbInts = await graphIndex().getVerbIdsByTarget(entityInt(targetId))
|
||||
|
||||
// Should have at least 1 (the relationship from central)
|
||||
expect(verbInts.length).toBeGreaterThanOrEqual(1)
|
||||
})
|
||||
|
||||
it('should paginate verb ints by target with limit', async () => {
|
||||
// Create entity with many incoming relationships
|
||||
const popularTarget = await brain.add({
|
||||
data: { name: 'Popular Target' },
|
||||
type: NounType.Thing
|
||||
})
|
||||
|
||||
// Create 30 relationships pointing to it
|
||||
for (let i = 0; i < 30; i++) {
|
||||
const sourceId = await brain.add({
|
||||
data: { name: `Source ${i}` },
|
||||
type: NounType.Thing
|
||||
})
|
||||
await brain.relate({
|
||||
from: sourceId,
|
||||
to: popularTarget,
|
||||
type: VerbType.RelatesTo
|
||||
})
|
||||
}
|
||||
|
||||
// Paginate
|
||||
const target = entityInt(popularTarget)
|
||||
const page1: bigint[] = await graphIndex().getVerbIdsByTarget(target, { limit: 10 })
|
||||
const page2: bigint[] = await graphIndex().getVerbIdsByTarget(target, { limit: 10, offset: 10 })
|
||||
|
||||
expect(page1).toHaveLength(10)
|
||||
expect(page2).toHaveLength(10)
|
||||
|
||||
// No overlap
|
||||
const overlap = page1.filter((id) => page2.includes(id))
|
||||
expect(overlap).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Performance with Pagination', () => {
|
||||
it('should maintain sub-5ms performance with pagination', async () => {
|
||||
const central = entityInt(centralId)
|
||||
|
||||
// Multiple paginated queries should be fast
|
||||
const startTime = performance.now()
|
||||
|
||||
await graphIndex().getNeighbors(central, { limit: 10, offset: 0 })
|
||||
await graphIndex().getNeighbors(central, { limit: 10, offset: 10 })
|
||||
await graphIndex().getNeighbors(central, { limit: 10, offset: 20 })
|
||||
|
||||
const elapsed = performance.now() - startTime
|
||||
|
||||
// 3 paginated queries should complete in < 15ms (< 5ms each)
|
||||
expect(elapsed).toBeLessThan(15)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Real-World Use Cases', () => {
|
||||
it('should efficiently paginate through high-degree node', async () => {
|
||||
// Simulate popular entity with 100+ relationships
|
||||
const hub = await brain.add({
|
||||
data: { name: 'Popular Hub' },
|
||||
type: NounType.Thing
|
||||
})
|
||||
|
||||
// Create 100 relationships
|
||||
const targetIds: string[] = []
|
||||
for (let i = 0; i < 100; i++) {
|
||||
const targetId = await brain.add({
|
||||
data: { name: `Target ${i}` },
|
||||
type: NounType.Thing
|
||||
})
|
||||
targetIds.push(targetId)
|
||||
await brain.relate({
|
||||
from: hub,
|
||||
to: targetId,
|
||||
type: VerbType.RelatesTo
|
||||
})
|
||||
}
|
||||
|
||||
// Paginate in chunks of 25
|
||||
const hubInt = entityInt(hub)
|
||||
const chunks: bigint[][] = []
|
||||
for (let offset = 0; offset < 100; offset += 25) {
|
||||
const chunk = await graphIndex().getNeighbors(hubInt, {
|
||||
direction: 'out',
|
||||
limit: 25,
|
||||
offset
|
||||
})
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
// Should have 4 chunks
|
||||
expect(chunks).toHaveLength(4)
|
||||
|
||||
// Each chunk should have 25 items
|
||||
chunks.forEach(chunk => {
|
||||
expect(chunk).toHaveLength(25)
|
||||
})
|
||||
|
||||
// All chunks combined should equal total
|
||||
const allNeighbors = chunks.flat()
|
||||
expect(allNeighbors).toHaveLength(100)
|
||||
|
||||
// No duplicates
|
||||
const uniqueNeighbors = new Set(allNeighbors)
|
||||
expect(uniqueNeighbors.size).toBe(100)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -611,8 +611,10 @@ describe('SmartExtractor', () => {
|
|||
|
||||
extractor.addToHistory('Test Entity', NounType.Person, vector)
|
||||
|
||||
// History is internal, just ensure no errors
|
||||
expect(true).toBe(true)
|
||||
// History lives on the embedding signal the extractor delegates to.
|
||||
const history = (extractor as any).embeddingSignal.historicalEntities
|
||||
expect(history).toHaveLength(1)
|
||||
expect(history[0].text).toBe('Test Entity')
|
||||
})
|
||||
|
||||
it('should clear history', () => {
|
||||
|
|
@ -621,8 +623,8 @@ describe('SmartExtractor', () => {
|
|||
|
||||
extractor.clearHistory()
|
||||
|
||||
// History cleared, no errors
|
||||
expect(true).toBe(true)
|
||||
const history = (extractor as any).embeddingSignal.historicalEntities
|
||||
expect(history).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
* 1. Option A: Never skip a verb type that's explicitly requested in the filter
|
||||
* 2. Option B: Added fast path for sourceId + verbType combo (common VFS pattern)
|
||||
*
|
||||
* Bug Report: /home/dpsifr/Projects/workshop/docs/BRAINY_VFS_BUG_REPORT.md
|
||||
* Originally reported by a downstream application using the VFS.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
||||
|
|
@ -84,7 +84,7 @@ describe('VFS mkdir() Bug Fix (v6.2.9)', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('getRelations should query Contains relationships correctly', () => {
|
||||
describe('related should query Contains relationships correctly', () => {
|
||||
it('should find Contains relationships via sourceId + verbType filter', async () => {
|
||||
// Create some entities and relationships
|
||||
const parent = await brain.add({
|
||||
|
|
@ -110,7 +110,7 @@ describe('VFS mkdir() Bug Fix (v6.2.9)', () => {
|
|||
await brain.relate({ from: parent, to: child2, type: VerbType.Contains })
|
||||
|
||||
// Query relationships (this is what getChildren() does)
|
||||
const relations = await brain.getRelations({
|
||||
const relations = await brain.related({
|
||||
from: parent,
|
||||
type: VerbType.Contains
|
||||
})
|
||||
|
|
@ -132,7 +132,7 @@ describe('VFS mkdir() Bug Fix (v6.2.9)', () => {
|
|||
await brain.relate({ from: entityA, to: entityC, type: VerbType.RelatedTo })
|
||||
|
||||
// Query only Contains relationships
|
||||
const containsRelations = await brain.getRelations({
|
||||
const containsRelations = await brain.related({
|
||||
from: entityA,
|
||||
type: VerbType.Contains
|
||||
})
|
||||
|
|
@ -141,7 +141,7 @@ describe('VFS mkdir() Bug Fix (v6.2.9)', () => {
|
|||
expect(containsRelations[0].to).toBe(entityB)
|
||||
|
||||
// Query only RelatedTo relationships
|
||||
const relatedRelations = await brain.getRelations({
|
||||
const relatedRelations = await brain.related({
|
||||
from: entityA,
|
||||
type: VerbType.RelatedTo
|
||||
})
|
||||
|
|
@ -174,7 +174,7 @@ describe('VFS mkdir() Bug Fix (v6.2.9)', () => {
|
|||
|
||||
// Query should use the new fast path
|
||||
const startTime = performance.now()
|
||||
const relations = await brain.getRelations({
|
||||
const relations = await brain.related({
|
||||
from: parent,
|
||||
type: VerbType.Contains
|
||||
})
|
||||
|
|
|
|||
|
|
@ -120,7 +120,7 @@ describe('EntityIdMapper stability (foundation for 2.4.0)', () => {
|
|||
const deletedInt = beforeInts[2]
|
||||
const maxBefore = Math.max(...beforeInts)
|
||||
|
||||
await brain.delete(deletedId)
|
||||
await brain.remove(deletedId)
|
||||
expect(getInt(deletedId)).toBeUndefined()
|
||||
|
||||
const newId = await addEntity('f', 6)
|
||||
|
|
|
|||
|
|
@ -255,14 +255,9 @@ describe('MetadataIndexManager - Phase 1b: Type-Aware Features', { timeout: 120_
|
|||
})
|
||||
|
||||
it('should preload sparse indices for top fields of top types', async () => {
|
||||
const managerAny = manager as any
|
||||
|
||||
// Warm cache
|
||||
await manager.warmCacheForTopTypes(2)
|
||||
|
||||
// Check that sparse indices were loaded (by checking UnifiedCache)
|
||||
// This is implementation-dependent, so we just verify no errors occurred
|
||||
expect(true).toBe(true) // If we got here, warming succeeded
|
||||
// Warming must complete cleanly against populated storage; the cache
|
||||
// contents are an implementation detail, the contract is clean resolution.
|
||||
await expect(manager.warmCacheForTopTypes(2)).resolves.toBeUndefined()
|
||||
})
|
||||
|
||||
it('should handle empty database gracefully', async () => {
|
||||
|
|
|
|||
|
|
@ -71,12 +71,14 @@ describe('Mutex Safety Tests', () => {
|
|||
})
|
||||
|
||||
it('should not deadlock with nested different keys', async () => {
|
||||
let innerRan = false
|
||||
await mutex.runExclusive('key1', async () => {
|
||||
await mutex.runExclusive('key2', async () => {
|
||||
// Should not deadlock
|
||||
expect(true).toBe(true)
|
||||
innerRan = true
|
||||
})
|
||||
})
|
||||
// Reaching here at all proves no deadlock; assert the inner body ran.
|
||||
expect(innerRan).toBe(true)
|
||||
})
|
||||
|
||||
it('should handle errors in exclusive function', async () => {
|
||||
|
|
@ -89,9 +91,11 @@ describe('Mutex Safety Tests', () => {
|
|||
).rejects.toThrow('Test error')
|
||||
|
||||
// Lock should be released, so we can acquire it again
|
||||
let reacquired = false
|
||||
await mutex.runExclusive('error-test', async () => {
|
||||
expect(true).toBe(true)
|
||||
reacquired = true
|
||||
})
|
||||
expect(reacquired).toBe(true)
|
||||
})
|
||||
|
||||
it('should handle high concurrency without resource exhaustion', async () => {
|
||||
|
|
|
|||
|
|
@ -40,8 +40,8 @@ describe('VFS restart persistence', () => {
|
|||
const entries1 = await brain.vfs.readdir('/')
|
||||
expect(entries1.length).toBeGreaterThan(0)
|
||||
|
||||
// In-session getRelations: root should have Contains relationship to the file
|
||||
const relations1 = await brain.getRelations({ from: rootId, type: VerbType.Contains })
|
||||
// In-session related: root should have Contains relationship to the file
|
||||
const relations1 = await brain.related({ from: rootId, type: VerbType.Contains })
|
||||
expect(relations1.length).toBeGreaterThan(0)
|
||||
|
||||
await brain.close()
|
||||
|
|
@ -68,8 +68,8 @@ describe('VFS restart persistence', () => {
|
|||
expect(entries2.length).toBeGreaterThan(0)
|
||||
expect(entries2).toContain('chapter-1.txt')
|
||||
|
||||
// getRelations should find the Contains relationship
|
||||
const relations2 = await brain.getRelations({ from: rootId, type: VerbType.Contains })
|
||||
// related should find the Contains relationship
|
||||
const relations2 = await brain.related({ from: rootId, type: VerbType.Contains })
|
||||
expect(relations2.length).toBeGreaterThan(0)
|
||||
|
||||
await brain.close()
|
||||
|
|
@ -104,8 +104,8 @@ describe('VFS restart persistence', () => {
|
|||
const docEntries1 = await brain.vfs.readdir('/docs')
|
||||
expect(docEntries1.length).toBe(2) // guide.txt + api.txt
|
||||
|
||||
// In-session getRelations: root should have Contains relationships
|
||||
const rootRelations1 = await brain.getRelations({ from: rootId, type: VerbType.Contains })
|
||||
// In-session related: root should have Contains relationships
|
||||
const rootRelations1 = await brain.related({ from: rootId, type: VerbType.Contains })
|
||||
expect(rootRelations1.length).toBeGreaterThan(0)
|
||||
|
||||
await brain.close()
|
||||
|
|
@ -130,7 +130,7 @@ describe('VFS restart persistence', () => {
|
|||
expect(docEntries2.sort()).toEqual(['api.txt', 'guide.txt'])
|
||||
|
||||
// Root relations
|
||||
const rootRelations = await brain.getRelations({ from: rootId, type: VerbType.Contains })
|
||||
const rootRelations = await brain.related({ from: rootId, type: VerbType.Contains })
|
||||
expect(rootRelations.length).toBeGreaterThan(0)
|
||||
|
||||
await brain.close()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue