2025-09-11 16:23:32 -07:00
|
|
|
/**
|
|
|
|
|
* Comprehensive Public API Test Suite
|
|
|
|
|
*
|
|
|
|
|
* This test suite validates ALL public API methods exposed by Brainy,
|
|
|
|
|
* ensuring complete coverage of documented functionality.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach, vi } from 'vitest'
|
|
|
|
|
import { Brainy } from '../../src/brainy'
|
|
|
|
|
import { NounType, VerbType } from '../../src/types/graphTypes'
|
|
|
|
|
import type { Entity, Result, SearchQuery, GraphConstraints } from '../../src/types/brainy.types'
|
|
|
|
|
import * as fs from 'fs/promises'
|
|
|
|
|
import * as path from 'path'
|
|
|
|
|
import { tmpdir } from 'os'
|
|
|
|
|
|
|
|
|
|
describe('Brainy Public API - Complete Coverage', () => {
|
|
|
|
|
let brain: Brainy
|
|
|
|
|
let testDir: string
|
|
|
|
|
|
|
|
|
|
beforeAll(async () => {
|
|
|
|
|
// Create test directory for filesystem tests
|
|
|
|
|
testDir = path.join(tmpdir(), `brainy-test-${Date.now()}`)
|
|
|
|
|
await fs.mkdir(testDir, { recursive: true })
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
afterAll(async () => {
|
|
|
|
|
// Cleanup test directory
|
|
|
|
|
try {
|
|
|
|
|
await fs.rm(testDir, { recursive: true, force: true })
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.warn('Failed to cleanup test directory:', error)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Core CRUD Operations', () => {
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
brain = new Brainy({ storage: { type: 'memory' } })
|
|
|
|
|
await brain.init()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
afterEach(async () => {
|
|
|
|
|
await brain.close()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('brain.add()', () => {
|
|
|
|
|
it('should handle all noun types', async () => {
|
|
|
|
|
const nounTypes = Object.values(NounType)
|
|
|
|
|
|
|
|
|
|
for (const nounType of nounTypes) {
|
|
|
|
|
const id = await brain.add({
|
|
|
|
|
data: `Test ${nounType}`,
|
|
|
|
|
type: nounType,
|
|
|
|
|
metadata: { nounType }
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(id).toBeDefined()
|
|
|
|
|
expect(typeof id).toBe('string')
|
|
|
|
|
|
|
|
|
|
const entity = await brain.get(id)
|
|
|
|
|
expect(entity).toBeDefined()
|
|
|
|
|
expect(entity?.type).toBe(nounType)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should validate required fields', async () => {
|
|
|
|
|
// Test missing data
|
|
|
|
|
await expect(brain.add({
|
|
|
|
|
type: NounType.Document
|
|
|
|
|
} as any)).rejects.toThrow()
|
|
|
|
|
|
|
|
|
|
// Test missing type
|
|
|
|
|
await expect(brain.add({
|
|
|
|
|
data: 'test'
|
|
|
|
|
} as any)).rejects.toThrow()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle very large data', async () => {
|
|
|
|
|
const largeText = 'x'.repeat(1_000_000) // 1MB of text
|
|
|
|
|
const id = await brain.add({
|
|
|
|
|
data: largeText,
|
|
|
|
|
type: NounType.Document
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const entity = await brain.get(id)
|
|
|
|
|
expect(entity?.metadata?.content).toBe(largeText)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('brain.addMany()', () => {
|
|
|
|
|
it('should handle batch operations efficiently', async () => {
|
|
|
|
|
const items = Array.from({ length: 1000 }, (_, i) => ({
|
|
|
|
|
data: `Item ${i}`,
|
|
|
|
|
type: NounType.Document,
|
|
|
|
|
metadata: { index: i }
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
const start = Date.now()
|
|
|
|
|
const ids = await brain.addMany(items)
|
|
|
|
|
const duration = Date.now() - start
|
|
|
|
|
|
|
|
|
|
expect(ids).toHaveLength(1000)
|
|
|
|
|
expect(duration).toBeLessThan(5000) // Should complete in < 5 seconds
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle partial failures', async () => {
|
|
|
|
|
const items = [
|
|
|
|
|
{ data: 'Valid 1', type: NounType.Document },
|
|
|
|
|
{ data: null as any, type: NounType.Document }, // Invalid
|
|
|
|
|
{ data: 'Valid 2', type: NounType.Document }
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
// Should either fail completely or handle gracefully
|
|
|
|
|
try {
|
|
|
|
|
const ids = await brain.addMany(items)
|
|
|
|
|
// If it succeeds, check partial results
|
|
|
|
|
expect(ids.length).toBeGreaterThan(0)
|
|
|
|
|
} catch (error) {
|
|
|
|
|
// If it fails, ensure error is meaningful
|
|
|
|
|
expect(error).toBeDefined()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('brain.update()', () => {
|
|
|
|
|
it('should handle concurrent updates', async () => {
|
|
|
|
|
const id = await brain.add({
|
|
|
|
|
data: 'Initial',
|
|
|
|
|
type: NounType.Document,
|
|
|
|
|
metadata: { version: 1 }
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Concurrent updates
|
|
|
|
|
const updates = Array.from({ length: 10 }, (_, i) =>
|
|
|
|
|
brain.update(id, {
|
|
|
|
|
metadata: { version: i + 2, updatedBy: `thread-${i}` }
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
await Promise.all(updates)
|
|
|
|
|
|
|
|
|
|
const final = await brain.get(id)
|
|
|
|
|
expect(final).toBeDefined()
|
|
|
|
|
expect(final?.metadata?.version).toBeGreaterThan(1)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle update of non-existent entity', async () => {
|
|
|
|
|
const result = await brain.update('non-existent-id', {
|
|
|
|
|
metadata: { test: true }
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Should handle gracefully (return false or throw)
|
|
|
|
|
expect(result === false || result === undefined).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should preserve unmodified fields', async () => {
|
|
|
|
|
const id = await brain.add({
|
|
|
|
|
data: 'Test',
|
|
|
|
|
type: NounType.Document,
|
|
|
|
|
metadata: {
|
|
|
|
|
field1: 'value1',
|
|
|
|
|
field2: 'value2',
|
|
|
|
|
nested: { a: 1, b: 2 }
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await brain.update(id, {
|
|
|
|
|
metadata: { field1: 'updated' }
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const updated = await brain.get(id)
|
|
|
|
|
expect(updated?.metadata?.field1).toBe('updated')
|
|
|
|
|
expect(updated?.metadata?.field2).toBe('value2')
|
|
|
|
|
expect(updated?.metadata?.nested).toEqual({ a: 1, b: 2 })
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('brain.updateMany()', () => {
|
|
|
|
|
it('should batch update multiple entities', async () => {
|
|
|
|
|
const ids = await brain.addMany([
|
|
|
|
|
{ data: 'Item 1', type: NounType.Document },
|
|
|
|
|
{ data: 'Item 2', type: NounType.Document },
|
|
|
|
|
{ data: 'Item 3', type: NounType.Document }
|
|
|
|
|
])
|
|
|
|
|
|
|
|
|
|
const updates = ids.map(id => ({
|
|
|
|
|
id,
|
|
|
|
|
data: { metadata: { updated: true } }
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
const results = await brain.updateMany(updates)
|
|
|
|
|
expect(results).toBeDefined()
|
|
|
|
|
|
|
|
|
|
for (const id of ids) {
|
|
|
|
|
const entity = await brain.get(id)
|
|
|
|
|
expect(entity?.metadata?.updated).toBe(true)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('brain.delete()', () => {
|
|
|
|
|
it('should handle cascade deletion of relationships', async () => {
|
|
|
|
|
const id1 = await brain.add({ data: 'Entity 1', type: NounType.Person })
|
|
|
|
|
const id2 = await brain.add({ data: 'Entity 2', type: NounType.Organization })
|
|
|
|
|
|
|
|
|
|
await brain.relate(id1, id2, VerbType.WorksWith)
|
|
|
|
|
|
|
|
|
|
await brain.delete(id1)
|
|
|
|
|
|
|
|
|
|
const relations = await brain.getRelations(id2)
|
|
|
|
|
expect(relations.filter(r => r.source === id1 || r.target === id1)).toHaveLength(0)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should return correct status for non-existent entity', async () => {
|
|
|
|
|
const result = await brain.delete('non-existent-id')
|
|
|
|
|
// Should not throw, just return false/undefined
|
|
|
|
|
expect(result === false || result === undefined).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('brain.deleteMany()', () => {
|
|
|
|
|
it('should efficiently delete large batches', async () => {
|
|
|
|
|
const ids = await brain.addMany(
|
|
|
|
|
Array.from({ length: 100 }, (_, i) => ({
|
|
|
|
|
data: `Item ${i}`,
|
|
|
|
|
type: NounType.Document
|
|
|
|
|
}))
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const start = Date.now()
|
|
|
|
|
await brain.deleteMany(ids)
|
|
|
|
|
const duration = Date.now() - start
|
|
|
|
|
|
|
|
|
|
expect(duration).toBeLessThan(1000) // Should be fast
|
|
|
|
|
|
|
|
|
|
// Verify all deleted
|
|
|
|
|
for (const id of ids) {
|
|
|
|
|
const entity = await brain.get(id)
|
|
|
|
|
expect(entity).toBeNull()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Relationship Operations', () => {
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
brain = new Brainy({ storage: { type: 'memory' } })
|
|
|
|
|
await brain.init()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
afterEach(async () => {
|
|
|
|
|
await brain.close()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('brain.relate()', () => {
|
|
|
|
|
it('should create relationships with all verb types', async () => {
|
|
|
|
|
const id1 = await brain.add({ data: 'Source', type: NounType.Person })
|
|
|
|
|
const id2 = await brain.add({ data: 'Target', type: NounType.Organization })
|
|
|
|
|
|
|
|
|
|
const verbTypes = Object.values(VerbType)
|
|
|
|
|
|
|
|
|
|
for (const verbType of verbTypes) {
|
|
|
|
|
const relationId = await brain.relate(id1, id2, verbType, {
|
|
|
|
|
metadata: { verbType }
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(relationId).toBeDefined()
|
|
|
|
|
expect(typeof relationId).toBe('string')
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle bidirectional relationships', async () => {
|
|
|
|
|
const person1 = await brain.add({ data: 'Alice', type: NounType.Person })
|
|
|
|
|
const person2 = await brain.add({ data: 'Bob', type: NounType.Person })
|
|
|
|
|
|
|
|
|
|
await brain.relate(person1, person2, VerbType.FriendOf, {
|
|
|
|
|
bidirectional: true
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const relations1 = await brain.getRelations(person1)
|
|
|
|
|
const relations2 = await brain.getRelations(person2)
|
|
|
|
|
|
|
|
|
|
expect(relations1.some(r => r.target === person2)).toBe(true)
|
|
|
|
|
expect(relations2.some(r => r.target === person1)).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should prevent duplicate relationships', async () => {
|
|
|
|
|
const id1 = await brain.add({ data: 'A', type: NounType.Document })
|
|
|
|
|
const id2 = await brain.add({ data: 'B', type: NounType.Document })
|
|
|
|
|
|
|
|
|
|
await brain.relate(id1, id2, VerbType.References)
|
|
|
|
|
await brain.relate(id1, id2, VerbType.References) // Duplicate
|
|
|
|
|
|
|
|
|
|
const relations = await brain.getRelations(id1)
|
|
|
|
|
const referenceRelations = relations.filter(
|
|
|
|
|
r => r.verb === VerbType.References && r.target === id2
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Should either prevent duplicate or handle gracefully
|
|
|
|
|
expect(referenceRelations.length).toBeLessThanOrEqual(1)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle relationship metadata and weights', async () => {
|
|
|
|
|
const id1 = await brain.add({ data: 'Source', type: NounType.Document })
|
|
|
|
|
const id2 = await brain.add({ data: 'Target', type: NounType.Document })
|
|
|
|
|
|
|
|
|
|
const relationId = await brain.relate(id1, id2, VerbType.References, {
|
|
|
|
|
weight: 0.8,
|
|
|
|
|
confidence: 0.95,
|
|
|
|
|
metadata: {
|
|
|
|
|
context: 'academic',
|
|
|
|
|
verified: true
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const relations = await brain.getRelations(id1)
|
|
|
|
|
const relation = relations.find(r => r.id === relationId)
|
|
|
|
|
|
|
|
|
|
expect(relation?.weight).toBe(0.8)
|
|
|
|
|
expect(relation?.confidence).toBe(0.95)
|
|
|
|
|
expect(relation?.data?.context).toBe('academic')
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('brain.relateMany()', () => {
|
|
|
|
|
it('should create multiple relationships efficiently', async () => {
|
|
|
|
|
const entities = await brain.addMany(
|
|
|
|
|
Array.from({ length: 10 }, (_, i) => ({
|
|
|
|
|
data: `Entity ${i}`,
|
|
|
|
|
type: NounType.Document
|
|
|
|
|
}))
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const relationships = []
|
|
|
|
|
for (let i = 0; i < entities.length - 1; i++) {
|
|
|
|
|
relationships.push({
|
|
|
|
|
source: entities[i],
|
|
|
|
|
target: entities[i + 1],
|
|
|
|
|
verb: VerbType.Precedes
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const relationIds = await brain.relateMany(relationships)
|
|
|
|
|
expect(relationIds).toHaveLength(relationships.length)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('brain.getRelations()', () => {
|
|
|
|
|
it('should retrieve all relationship types', async () => {
|
|
|
|
|
const center = await brain.add({ data: 'Center', type: NounType.Person })
|
|
|
|
|
const related1 = await brain.add({ data: 'Related1', type: NounType.Organization })
|
|
|
|
|
const related2 = await brain.add({ data: 'Related2', type: NounType.Document })
|
|
|
|
|
const related3 = await brain.add({ data: 'Related3', type: NounType.Task })
|
|
|
|
|
|
|
|
|
|
await brain.relate(center, related1, VerbType.WorksWith)
|
|
|
|
|
await brain.relate(center, related2, VerbType.Creates)
|
|
|
|
|
await brain.relate(related3, center, VerbType.DependsOn)
|
|
|
|
|
|
|
|
|
|
const relations = await brain.getRelations(center)
|
|
|
|
|
|
|
|
|
|
expect(relations).toHaveLength(3)
|
|
|
|
|
expect(relations.some(r => r.verb === VerbType.WorksWith)).toBe(true)
|
|
|
|
|
expect(relations.some(r => r.verb === VerbType.Creates)).toBe(true)
|
|
|
|
|
expect(relations.some(r => r.verb === VerbType.DependsOn)).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should filter by relationship direction', async () => {
|
|
|
|
|
const center = await brain.add({ data: 'Center', type: NounType.Document })
|
|
|
|
|
const source = await brain.add({ data: 'Source', type: NounType.Person })
|
|
|
|
|
const target = await brain.add({ data: 'Target', type: NounType.Task })
|
|
|
|
|
|
|
|
|
|
await brain.relate(source, center, VerbType.Creates)
|
|
|
|
|
await brain.relate(center, target, VerbType.Requires)
|
|
|
|
|
|
|
|
|
|
const outgoing = await brain.getRelations(center, { direction: 'outgoing' })
|
|
|
|
|
const incoming = await brain.getRelations(center, { direction: 'incoming' })
|
|
|
|
|
|
|
|
|
|
expect(outgoing).toHaveLength(1)
|
|
|
|
|
expect(outgoing[0].target).toBe(target)
|
|
|
|
|
|
|
|
|
|
expect(incoming).toHaveLength(1)
|
|
|
|
|
expect(incoming[0].source).toBe(source)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should filter by verb type', async () => {
|
|
|
|
|
const doc = await brain.add({ data: 'Document', type: NounType.Document })
|
|
|
|
|
const ref1 = await brain.add({ data: 'Reference1', type: NounType.Document })
|
|
|
|
|
const ref2 = await brain.add({ data: 'Reference2', type: NounType.Document })
|
|
|
|
|
const author = await brain.add({ data: 'Author', type: NounType.Person })
|
|
|
|
|
|
|
|
|
|
await brain.relate(doc, ref1, VerbType.References)
|
|
|
|
|
await brain.relate(doc, ref2, VerbType.References)
|
|
|
|
|
await brain.relate(author, doc, VerbType.Creates)
|
|
|
|
|
|
|
|
|
|
const references = await brain.getRelations(doc, {
|
|
|
|
|
verbType: VerbType.References
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(references).toHaveLength(2)
|
|
|
|
|
expect(references.every(r => r.verb === VerbType.References)).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Search Operations', () => {
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
brain = new Brainy({ storage: { type: 'memory' } })
|
|
|
|
|
await brain.init()
|
|
|
|
|
|
|
|
|
|
// Add test data
|
|
|
|
|
await brain.addMany([
|
|
|
|
|
{ data: 'The quick brown fox', type: NounType.Document, metadata: { category: 'animals' } },
|
|
|
|
|
{ data: 'jumps over the lazy dog', type: NounType.Document, metadata: { category: 'animals' } },
|
|
|
|
|
{ data: 'Machine learning algorithms', type: NounType.Document, metadata: { category: 'tech' } },
|
|
|
|
|
{ data: 'Deep neural networks', type: NounType.Document, metadata: { category: 'tech' } },
|
|
|
|
|
{ data: 'Natural language processing', type: NounType.Document, metadata: { category: 'tech' } }
|
|
|
|
|
])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
afterEach(async () => {
|
|
|
|
|
await brain.close()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('brain.find()', () => {
|
|
|
|
|
it('should support all search modes', async () => {
|
|
|
|
|
const modes: Array<'auto' | 'vector' | 'metadata' | 'graph' | 'hybrid'> = [
|
|
|
|
|
'auto', 'vector', 'metadata', 'graph', 'hybrid'
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for (const mode of modes) {
|
|
|
|
|
const results = await brain.find({
|
|
|
|
|
query: 'technology',
|
|
|
|
|
mode,
|
|
|
|
|
limit: 5
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(results).toBeDefined()
|
|
|
|
|
expect(Array.isArray(results)).toBe(true)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle complex metadata filters', async () => {
|
|
|
|
|
const results = await brain.find({
|
|
|
|
|
where: {
|
|
|
|
|
category: 'tech',
|
|
|
|
|
$or: [
|
|
|
|
|
{ content: { $contains: 'neural' } },
|
|
|
|
|
{ content: { $contains: 'learning' } }
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
limit: 10
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(results.length).toBeGreaterThan(0)
|
|
|
|
|
expect(results.every(r => r.entity.metadata?.category === 'tech')).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should support graph-connected searches', async () => {
|
|
|
|
|
const doc1 = await brain.add({ data: 'Primary document', type: NounType.Document })
|
|
|
|
|
const doc2 = await brain.add({ data: 'Related document', type: NounType.Document })
|
|
|
|
|
await brain.relate(doc1, doc2, VerbType.References)
|
|
|
|
|
|
|
|
|
|
const results = await brain.find({
|
|
|
|
|
query: 'document',
|
|
|
|
|
connected: { from: doc1 },
|
|
|
|
|
limit: 5
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(results.some(r => r.entity.id === doc2)).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle fusion search with custom weights', async () => {
|
|
|
|
|
const results = await brain.find({
|
|
|
|
|
query: 'machine learning',
|
|
|
|
|
fusion: {
|
|
|
|
|
strategy: 'weighted',
|
|
|
|
|
weights: {
|
|
|
|
|
vector: 0.6,
|
|
|
|
|
field: 0.3,
|
|
|
|
|
graph: 0.1
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
limit: 10
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(results).toBeDefined()
|
|
|
|
|
expect(results.length).toBeGreaterThan(0)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should support pagination', async () => {
|
|
|
|
|
const page1 = await brain.find({
|
|
|
|
|
query: 'document',
|
|
|
|
|
limit: 2,
|
|
|
|
|
offset: 0
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const page2 = await brain.find({
|
|
|
|
|
query: 'document',
|
|
|
|
|
limit: 2,
|
|
|
|
|
offset: 2
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(page1[0]?.entity.id).not.toBe(page2[0]?.entity.id)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('brain.similar()', () => {
|
|
|
|
|
it('should find similar entities', async () => {
|
|
|
|
|
const reference = await brain.add({
|
|
|
|
|
data: 'Artificial intelligence and machine learning',
|
|
|
|
|
type: NounType.Document
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const similar = await brain.similar(reference, {
|
|
|
|
|
limit: 5,
|
|
|
|
|
threshold: 0.5
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(similar).toBeDefined()
|
|
|
|
|
expect(similar.length).toBeGreaterThan(0)
|
|
|
|
|
expect(similar[0].similarity).toBeGreaterThanOrEqual(0.5)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should exclude source entity', async () => {
|
|
|
|
|
const reference = await brain.add({
|
|
|
|
|
data: 'Unique content',
|
|
|
|
|
type: NounType.Document
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const similar = await brain.similar(reference, {
|
|
|
|
|
limit: 10,
|
|
|
|
|
includeSource: false
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(similar.every(r => r.entity.id !== reference)).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Neural API', () => {
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
brain = new Brainy({ storage: { type: 'memory' } })
|
|
|
|
|
await brain.init()
|
|
|
|
|
|
|
|
|
|
// Add diverse test data
|
|
|
|
|
await brain.addMany([
|
|
|
|
|
{ data: 'Cat', type: NounType.Concept, metadata: { category: 'animal' } },
|
|
|
|
|
{ data: 'Dog', type: NounType.Concept, metadata: { category: 'animal' } },
|
|
|
|
|
{ data: 'Tiger', type: NounType.Concept, metadata: { category: 'animal' } },
|
|
|
|
|
{ data: 'Car', type: NounType.Thing, metadata: { category: 'vehicle' } },
|
|
|
|
|
{ data: 'Truck', type: NounType.Thing, metadata: { category: 'vehicle' } },
|
|
|
|
|
{ data: 'Python', type: NounType.Language, metadata: { category: 'programming' } },
|
|
|
|
|
{ data: 'JavaScript', type: NounType.Language, metadata: { category: 'programming' } }
|
|
|
|
|
])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
afterEach(async () => {
|
|
|
|
|
await brain.close()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('brain.neural().clusters()', () => {
|
|
|
|
|
it('should cluster entities by similarity', async () => {
|
|
|
|
|
const clusters = await brain.neural().clusters({
|
|
|
|
|
k: 3,
|
|
|
|
|
maxIterations: 10
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(clusters).toBeDefined()
|
|
|
|
|
expect(clusters.length).toBeLessThanOrEqual(3)
|
|
|
|
|
expect(clusters.every(c => c.members.length > 0)).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should support different clustering algorithms', async () => {
|
|
|
|
|
const algorithms = ['kmeans', 'hierarchical', 'dbscan']
|
|
|
|
|
|
|
|
|
|
for (const algorithm of algorithms) {
|
|
|
|
|
const clusters = await brain.neural().clusters({
|
|
|
|
|
algorithm,
|
|
|
|
|
k: 2
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(clusters).toBeDefined()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('brain.neural().hierarchy()', () => {
|
|
|
|
|
it('should build semantic hierarchy', async () => {
|
|
|
|
|
const hierarchy = await brain.neural().hierarchy()
|
|
|
|
|
|
|
|
|
|
expect(hierarchy).toBeDefined()
|
|
|
|
|
expect(hierarchy.root).toBeDefined()
|
|
|
|
|
expect(hierarchy.levels).toBeGreaterThan(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('brain.neural().outliers()', () => {
|
|
|
|
|
it('should detect anomalous entities', async () => {
|
|
|
|
|
// Add an outlier
|
|
|
|
|
await brain.add({
|
|
|
|
|
data: 'Quantum physics equations',
|
|
|
|
|
type: NounType.Document,
|
|
|
|
|
metadata: { category: 'science' }
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const outliers = await brain.neural().outliers({
|
|
|
|
|
threshold: 0.8
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(outliers).toBeDefined()
|
|
|
|
|
expect(Array.isArray(outliers)).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('brain.neural().visualize()', () => {
|
|
|
|
|
it('should generate visualization data', async () => {
|
|
|
|
|
const vizData = await brain.neural().visualize({
|
|
|
|
|
dimensions: 2,
|
|
|
|
|
algorithm: 'tsne'
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(vizData).toBeDefined()
|
|
|
|
|
expect(vizData.nodes).toBeDefined()
|
|
|
|
|
expect(vizData.nodes.length).toBeGreaterThan(0)
|
|
|
|
|
expect(vizData.nodes[0].x).toBeDefined()
|
|
|
|
|
expect(vizData.nodes[0].y).toBeDefined()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should support 3D visualization', async () => {
|
|
|
|
|
const vizData = await brain.neural().visualize({
|
|
|
|
|
dimensions: 3,
|
|
|
|
|
algorithm: 'umap'
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(vizData.nodes[0].z).toBeDefined()
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('brain.neural().neighbors()', () => {
|
|
|
|
|
it('should find nearest neighbors', async () => {
|
|
|
|
|
const catId = (await brain.find({ query: 'Cat', limit: 1 }))[0]?.entity.id
|
|
|
|
|
|
|
|
|
|
if (catId) {
|
|
|
|
|
const neighbors = await brain.neural().neighbors(catId, {
|
|
|
|
|
k: 3
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
expect(neighbors).toHaveLength(3)
|
|
|
|
|
expect(neighbors[0].distance).toBeLessThanOrEqual(neighbors[1].distance)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Statistics and Monitoring', () => {
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
brain = new Brainy({ storage: { type: 'memory' } })
|
|
|
|
|
await brain.init()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
afterEach(async () => {
|
|
|
|
|
await brain.close()
|
|
|
|
|
})
|
|
|
|
|
|
2025-10-09 11:40:31 -07:00
|
|
|
describe('brain.getStats()', () => {
|
2025-09-11 16:23:32 -07:00
|
|
|
it('should return comprehensive statistics', async () => {
|
|
|
|
|
// Add test data
|
|
|
|
|
await brain.addMany([
|
|
|
|
|
{ data: 'Item 1', type: NounType.Document },
|
|
|
|
|
{ data: 'Item 2', type: NounType.Person },
|
|
|
|
|
{ data: 'Item 3', type: NounType.Task }
|
|
|
|
|
])
|
|
|
|
|
|
2025-10-09 11:40:31 -07:00
|
|
|
const stats = brain.getStats()
|
2025-09-11 16:23:32 -07:00
|
|
|
|
|
|
|
|
expect(stats).toBeDefined()
|
|
|
|
|
expect(stats.totalEntities).toBe(3)
|
|
|
|
|
expect(stats.entitiesByType).toBeDefined()
|
|
|
|
|
expect(stats.entitiesByType[NounType.Document]).toBe(1)
|
|
|
|
|
expect(stats.storageSize).toBeGreaterThan(0)
|
|
|
|
|
expect(stats.indexStats).toBeDefined()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should track operation metrics', async () => {
|
2025-10-09 11:40:31 -07:00
|
|
|
const stats1 = brain.getStats()
|
2025-09-11 16:23:32 -07:00
|
|
|
|
|
|
|
|
await brain.add({ data: 'Test', type: NounType.Document })
|
|
|
|
|
await brain.find({ query: 'Test' })
|
|
|
|
|
|
2025-10-09 11:40:31 -07:00
|
|
|
const stats2 = brain.getStats()
|
2025-09-11 16:23:32 -07:00
|
|
|
|
|
|
|
|
expect(stats2.operations.adds).toBeGreaterThan(stats1.operations.adds)
|
|
|
|
|
expect(stats2.operations.searches).toBeGreaterThan(stats1.operations.searches)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('brain.health()', () => {
|
|
|
|
|
it('should return health status', async () => {
|
|
|
|
|
const health = await brain.health()
|
|
|
|
|
|
|
|
|
|
expect(health).toBeDefined()
|
|
|
|
|
expect(health.status).toBe('healthy')
|
|
|
|
|
expect(health.storage).toBeDefined()
|
|
|
|
|
expect(health.storage.status).toBe('connected')
|
|
|
|
|
expect(health.augmentations).toBeDefined()
|
|
|
|
|
expect(health.memory).toBeDefined()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should detect unhealthy conditions', async () => {
|
|
|
|
|
// Simulate unhealthy condition
|
|
|
|
|
await brain.close()
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const health = await brain.health()
|
|
|
|
|
expect(health.status).not.toBe('healthy')
|
|
|
|
|
} catch (error) {
|
|
|
|
|
// Closed brain might throw
|
|
|
|
|
expect(error).toBeDefined()
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('FileSystem Storage', () => {
|
|
|
|
|
it('should handle basic CRUD with filesystem', async () => {
|
|
|
|
|
const fsBrain = new Brainy({
|
|
|
|
|
storage: {
|
|
|
|
|
type: 'filesystem',
|
|
|
|
|
options: {
|
|
|
|
|
path: testDir
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await fsBrain.init()
|
|
|
|
|
|
|
|
|
|
const id = await fsBrain.add({
|
|
|
|
|
data: 'Filesystem test',
|
|
|
|
|
type: NounType.Document
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const retrieved = await fsBrain.get(id)
|
|
|
|
|
expect(retrieved?.metadata?.content).toBe('Filesystem test')
|
|
|
|
|
|
|
|
|
|
await fsBrain.update(id, {
|
|
|
|
|
metadata: { updated: true }
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const updated = await fsBrain.get(id)
|
|
|
|
|
expect(updated?.metadata?.updated).toBe(true)
|
|
|
|
|
|
|
|
|
|
await fsBrain.delete(id)
|
|
|
|
|
const deleted = await fsBrain.get(id)
|
|
|
|
|
expect(deleted).toBeNull()
|
|
|
|
|
|
|
|
|
|
await fsBrain.close()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle concurrent filesystem operations', async () => {
|
|
|
|
|
const fsBrain = new Brainy({
|
|
|
|
|
storage: {
|
|
|
|
|
type: 'filesystem',
|
|
|
|
|
options: {
|
|
|
|
|
path: testDir
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await fsBrain.init()
|
|
|
|
|
|
|
|
|
|
const operations = Array.from({ length: 10 }, async (_, i) => {
|
|
|
|
|
const id = await fsBrain.add({
|
|
|
|
|
data: `Concurrent ${i}`,
|
|
|
|
|
type: NounType.Document
|
|
|
|
|
})
|
|
|
|
|
return id
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
const ids = await Promise.all(operations)
|
|
|
|
|
expect(ids).toHaveLength(10)
|
|
|
|
|
expect(new Set(ids).size).toBe(10) // All unique
|
|
|
|
|
|
|
|
|
|
await fsBrain.close()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should recover from corrupted files', async () => {
|
|
|
|
|
const fsBrain = new Brainy({
|
|
|
|
|
storage: {
|
|
|
|
|
type: 'filesystem',
|
|
|
|
|
options: {
|
|
|
|
|
path: testDir
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await fsBrain.init()
|
|
|
|
|
|
|
|
|
|
const id = await fsBrain.add({
|
|
|
|
|
data: 'Test',
|
|
|
|
|
type: NounType.Document
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Corrupt the file
|
|
|
|
|
const filePath = path.join(testDir, 'entities', `${id}.json`)
|
|
|
|
|
await fs.writeFile(filePath, 'corrupted{json', 'utf-8')
|
|
|
|
|
|
|
|
|
|
// Should handle gracefully
|
|
|
|
|
const retrieved = await fsBrain.get(id)
|
|
|
|
|
expect(retrieved === null || retrieved === undefined).toBe(true)
|
|
|
|
|
|
|
|
|
|
await fsBrain.close()
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Error Recovery and Resilience', () => {
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
brain = new Brainy({ storage: { type: 'memory' } })
|
|
|
|
|
await brain.init()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
afterEach(async () => {
|
|
|
|
|
await brain.close()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle and recover from storage errors', async () => {
|
|
|
|
|
// Mock storage failure
|
|
|
|
|
const originalAdd = brain.add.bind(brain)
|
|
|
|
|
let failCount = 0
|
|
|
|
|
brain.add = vi.fn(async (...args) => {
|
|
|
|
|
if (failCount++ < 2) {
|
|
|
|
|
throw new Error('Storage temporarily unavailable')
|
|
|
|
|
}
|
|
|
|
|
return originalAdd(...args)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Should retry and eventually succeed
|
|
|
|
|
let succeeded = false
|
|
|
|
|
for (let i = 0; i < 3; i++) {
|
|
|
|
|
try {
|
|
|
|
|
await brain.add({
|
|
|
|
|
data: 'Test',
|
|
|
|
|
type: NounType.Document
|
|
|
|
|
})
|
|
|
|
|
succeeded = true
|
|
|
|
|
break
|
|
|
|
|
} catch (error) {
|
|
|
|
|
// Expected for first attempts
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
expect(succeeded).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should handle malformed input gracefully', async () => {
|
|
|
|
|
const malformedInputs = [
|
|
|
|
|
null,
|
|
|
|
|
undefined,
|
|
|
|
|
{},
|
|
|
|
|
{ data: null },
|
|
|
|
|
{ type: 'invalid' },
|
|
|
|
|
{ data: {}, type: NounType.Document }, // Non-string data
|
|
|
|
|
{ data: '', type: NounType.Document }, // Empty data
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
for (const input of malformedInputs) {
|
|
|
|
|
try {
|
|
|
|
|
await brain.add(input as any)
|
|
|
|
|
} catch (error) {
|
|
|
|
|
// Should throw meaningful error
|
|
|
|
|
expect(error).toBeDefined()
|
|
|
|
|
expect((error as Error).message).toBeDefined()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should maintain consistency during partial failures', async () => {
|
|
|
|
|
const items = Array.from({ length: 5 }, (_, i) => ({
|
|
|
|
|
data: `Item ${i}`,
|
|
|
|
|
type: NounType.Document
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
// Add items
|
|
|
|
|
const ids = await brain.addMany(items)
|
|
|
|
|
|
|
|
|
|
// Simulate partial delete failure
|
|
|
|
|
const originalDelete = brain.delete.bind(brain)
|
|
|
|
|
brain.delete = vi.fn(async (id) => {
|
|
|
|
|
if (id === ids[2]) {
|
|
|
|
|
throw new Error('Delete failed')
|
|
|
|
|
}
|
|
|
|
|
return originalDelete(id)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Try to delete all
|
|
|
|
|
const results = await Promise.allSettled(
|
|
|
|
|
ids.map(id => brain.delete(id))
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Check consistency
|
|
|
|
|
const remaining = await brain.find({ limit: 100 })
|
|
|
|
|
expect(remaining.some(r => r.entity.id === ids[2])).toBe(true)
|
|
|
|
|
expect(remaining.length).toBe(1) // Only the failed one remains
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Performance and Scale', () => {
|
|
|
|
|
it('should handle large-scale operations efficiently', async () => {
|
|
|
|
|
const largeBrain = new Brainy({ storage: { type: 'memory' } })
|
|
|
|
|
await largeBrain.init()
|
|
|
|
|
|
|
|
|
|
// Add 10,000 items
|
|
|
|
|
const batchSize = 100
|
|
|
|
|
const totalItems = 10000
|
|
|
|
|
|
|
|
|
|
const start = Date.now()
|
|
|
|
|
|
|
|
|
|
for (let batch = 0; batch < totalItems / batchSize; batch++) {
|
|
|
|
|
const items = Array.from({ length: batchSize }, (_, i) => ({
|
|
|
|
|
data: `Item ${batch * batchSize + i}`,
|
|
|
|
|
type: NounType.Document,
|
|
|
|
|
metadata: {
|
|
|
|
|
batch,
|
|
|
|
|
index: i,
|
|
|
|
|
category: `cat-${batch % 10}`
|
|
|
|
|
}
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
await largeBrain.addMany(items)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const addDuration = Date.now() - start
|
|
|
|
|
expect(addDuration).toBeLessThan(30000) // Should complete in < 30 seconds
|
|
|
|
|
|
|
|
|
|
// Test search performance
|
|
|
|
|
const searchStart = Date.now()
|
|
|
|
|
const results = await largeBrain.find({
|
|
|
|
|
query: 'Item 5000',
|
|
|
|
|
limit: 10
|
|
|
|
|
})
|
|
|
|
|
const searchDuration = Date.now() - searchStart
|
|
|
|
|
|
|
|
|
|
expect(results.length).toBeGreaterThan(0)
|
|
|
|
|
expect(searchDuration).toBeLessThan(1000) // Search should be < 1 second
|
|
|
|
|
|
|
|
|
|
// Test filtered search
|
|
|
|
|
const filteredStart = Date.now()
|
|
|
|
|
const filtered = await largeBrain.find({
|
|
|
|
|
where: { category: 'cat-5' },
|
|
|
|
|
limit: 100
|
|
|
|
|
})
|
|
|
|
|
const filteredDuration = Date.now() - filteredStart
|
|
|
|
|
|
|
|
|
|
expect(filtered.length).toBeGreaterThan(0)
|
|
|
|
|
expect(filteredDuration).toBeLessThan(2000) // Filtered search < 2 seconds
|
|
|
|
|
|
|
|
|
|
await largeBrain.close()
|
|
|
|
|
}, 60000) // 60 second timeout for this test
|
|
|
|
|
|
|
|
|
|
it('should not leak memory during extended operations', async () => {
|
|
|
|
|
const memBrain = new Brainy({ storage: { type: 'memory' } })
|
|
|
|
|
await memBrain.init()
|
|
|
|
|
|
|
|
|
|
const initialMemory = process.memoryUsage().heapUsed
|
|
|
|
|
|
|
|
|
|
// Perform many operations
|
|
|
|
|
for (let i = 0; i < 100; i++) {
|
|
|
|
|
const id = await memBrain.add({
|
|
|
|
|
data: `Memory test ${i}`,
|
|
|
|
|
type: NounType.Document
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
await memBrain.find({ query: 'test' })
|
|
|
|
|
await memBrain.update(id, { metadata: { updated: i } })
|
|
|
|
|
await memBrain.delete(id)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Force garbage collection if available
|
|
|
|
|
if (global.gc) {
|
|
|
|
|
global.gc()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const finalMemory = process.memoryUsage().heapUsed
|
|
|
|
|
const memoryGrowth = finalMemory - initialMemory
|
|
|
|
|
|
|
|
|
|
// Memory growth should be reasonable (< 50MB)
|
|
|
|
|
expect(memoryGrowth).toBeLessThan(50 * 1024 * 1024)
|
|
|
|
|
|
|
|
|
|
await memBrain.close()
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
describe('Clear Operations', () => {
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
brain = new Brainy({ storage: { type: 'memory' } })
|
|
|
|
|
await brain.init()
|
|
|
|
|
|
|
|
|
|
// Add test data
|
|
|
|
|
await brain.addMany([
|
|
|
|
|
{ data: 'Doc 1', type: NounType.Document, metadata: { category: 'A' } },
|
|
|
|
|
{ data: 'Doc 2', type: NounType.Document, metadata: { category: 'B' } },
|
|
|
|
|
{ data: 'Person 1', type: NounType.Person, metadata: { category: 'A' } },
|
|
|
|
|
{ data: 'Task 1', type: NounType.Task, metadata: { category: 'B' } }
|
|
|
|
|
])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
afterEach(async () => {
|
|
|
|
|
await brain.close()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should clear all data', async () => {
|
|
|
|
|
await brain.clear()
|
|
|
|
|
|
|
|
|
|
const remaining = await brain.find({ limit: 100 })
|
|
|
|
|
expect(remaining).toHaveLength(0)
|
|
|
|
|
|
2025-10-09 11:40:31 -07:00
|
|
|
const stats = brain.getStats()
|
2025-09-11 16:23:32 -07:00
|
|
|
expect(stats.totalEntities).toBe(0)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should clear by type filter', async () => {
|
|
|
|
|
await brain.clear({ type: NounType.Document })
|
|
|
|
|
|
|
|
|
|
const remaining = await brain.find({ limit: 100 })
|
|
|
|
|
expect(remaining).toHaveLength(2)
|
|
|
|
|
expect(remaining.every(r => r.entity.type !== NounType.Document)).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should clear by metadata filter', async () => {
|
|
|
|
|
await brain.clear({ where: { category: 'A' } })
|
|
|
|
|
|
|
|
|
|
const remaining = await brain.find({ limit: 100 })
|
|
|
|
|
expect(remaining).toHaveLength(2)
|
|
|
|
|
expect(remaining.every(r => r.entity.metadata?.category !== 'A')).toBe(true)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('should clear relationships when clearing entities', async () => {
|
|
|
|
|
const docs = await brain.find({ where: { type: NounType.Document } })
|
|
|
|
|
const people = await brain.find({ where: { type: NounType.Person } })
|
|
|
|
|
|
feat: Stage 3 CANONICAL taxonomy with 169 types (v5.5.0)
Expand type system from 71 to 169 types achieving 96-97% coverage of all human knowledge.
NEW FEATURES:
- 42 noun types (was 31): Added organism, substance + 11 others
- 127 verb types (was 40): Added affects, learns, destroys + 84 others
- Stage 3 CANONICAL taxonomy covering all major knowledge domains
NEW TYPES:
Nouns: organism (biological entities), substance (physical matter)
Verbs: destroys (lifecycle), affects (patient role), learns (cognition)
Plus 95 additional types across 24 semantic categories
REMOVED TYPES (migration recommended):
- user → person, topic → concept, content → informationContent
- createdBy, belongsTo, supervises, succeeds → use inverse relationships
PERFORMANCE:
- Memory: 676 bytes for 169 types (99.2% reduction vs Maps)
- Type embeddings: 338KB embedded, zero runtime computation
- Coverage: Natural Sciences (96%), Formal Sciences (98%), Social Sciences (97%), Humanities (96%)
DOCUMENTATION:
- Added docs/STAGE3-CANONICAL-TAXONOMY.md
- Updated README.md with new type counts
- Complete CHANGELOG entry for v5.5.0
BREAKING CHANGES (minor impact):
Removed 6 types (user, topic, content, createdBy, belongsTo, supervises, succeeds).
Migration path provided via type mapping.
Timeless design: Stable for 20+ years without changes.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-06 09:02:23 -08:00
|
|
|
await brain.relate(docs[0].entity.id, people[0].entity.id, VerbType.Creates)
|
2025-09-11 16:23:32 -07:00
|
|
|
|
|
|
|
|
await brain.clear({ type: NounType.Document })
|
|
|
|
|
|
|
|
|
|
const relations = await brain.getRelations(people[0].entity.id)
|
|
|
|
|
expect(relations).toHaveLength(0)
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
})
|