/** * @module tests/integration/find-connected-order * @description The graph-first law for `find({ connected })` (10.4.8). * * With `connected` present the neighbour set is the candidate universe: it is * resolved from the adjacency first, the metadata filter is evaluated over * those ids only, and the page is cut last. The earlier order materialized the * whole-store filtered id list, paged it, hydrated the page, and only then * intersected with the neighbours — so a neighbour outside the first page of * the filtered STORE was silently dropped, and every call paid O(store). * * These pins hold both halves. The answer: every matching neighbour is * reachable by paging, a non-neighbour never appears, a negation (`missing`) * is evaluated over the neighbours, `orderBy` sorts the whole neighbour set * before the page is cut, and the vector leg walks the neighbours only. The * cost shape: the metadata index is asked about the neighbour ids only, and * hydration is one page — never the store. */ import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest' import { Brainy } from '../../src/brainy' import { NounType, VerbType } from '../../src/types/graphTypes' import { v5 } from '../../src/universal/uuid' import { generateTestVector } from '../helpers/test-factory' /** Matching rows that are NOT neighbours — added FIRST, so the whole-store filtered list leads with them. */ const NOISE = 120 /** Matching rows that ARE neighbours of the anchor. */ const NEIGHBOURS = 30 /** Neighbours carrying `retracted: true` — excluded by the `missing` negation. */ const RETRACTED = 4 describe('find({ connected }) is graph-first: neighbours → filter → page', () => { let brain: Brainy const anchor = 'anchor' const sharedVector = generateTestVector() const neighbourIds = new Set(Array.from({ length: NEIGHBOURS }, (_, i) => v5(`nb-${i}`))) beforeAll(async () => { brain = new Brainy({ requireSubtype: false, storage: { type: 'memory' } }) await brain.init() await brain.add({ id: anchor, data: 'the anchor', type: NounType.Person, metadata: { kind: 'anchor' }, vector: generateTestVector() }) for (let i = 0; i < NOISE; i++) { await brain.add({ id: `noise-${i}`, data: `noise ${i}`, type: NounType.Person, metadata: { kind: 'note', rank: 1000 + i }, vector: sharedVector }) } for (let i = 0; i < NEIGHBOURS; i++) { await brain.add({ id: `nb-${i}`, data: `neighbour ${i}`, type: NounType.Person, metadata: { kind: 'note', rank: i + 1, ...(i < RETRACTED ? { retracted: true } : {}) }, vector: sharedVector }) await brain.relate({ from: anchor, to: `nb-${i}`, type: VerbType.Knows }) } }) afterAll(async () => { brain = null as any }) it('returns the matching neighbours page by page — none dropped, never a non-neighbour', async () => { const seen = new Set() for (let offset = 0; offset <= NEIGHBOURS; offset += 10) { const page = await brain.find({ connected: { from: anchor, direction: 'out' }, where: { kind: 'note' }, limit: 10, offset }) expect(page).toHaveLength(offset < NEIGHBOURS ? 10 : 0) for (const r of page) { expect(neighbourIds.has(r.entity.id)).toBe(true) expect(seen.has(r.entity.id)).toBe(false) seen.add(r.entity.id) } } expect(seen.size).toBe(NEIGHBOURS) }) it('evaluates a negation (`missing`) over the neighbour set, not the store', async () => { const results = await brain.find({ connected: { from: anchor, direction: 'out' }, where: { kind: 'note', retracted: { missing: true } }, limit: 100 }) expect(results).toHaveLength(NEIGHBOURS - RETRACTED) for (const r of results) { expect(neighbourIds.has(r.entity.id)).toBe(true) expect(r.entity.metadata.retracted).toBeUndefined() } }) it('asks the metadata index about the neighbour ids only, and hydrates one page', async () => { const index = (brain as any).metadataIndex const within = vi.spyOn(index, 'filterIdsWithin') const hydrate = vi.spyOn(brain as any, 'batchGet') try { const results = await brain.find({ connected: { from: anchor, direction: 'out' }, where: { kind: 'note' }, limit: 10 }) expect(results).toHaveLength(10) expect(within).toHaveBeenCalledTimes(1) const askedIds = within.mock.calls[0][1] as string[] expect(askedIds).toHaveLength(NEIGHBOURS) for (const id of askedIds) expect(neighbourIds.has(id)).toBe(true) expect(hydrate).toHaveBeenCalledTimes(1) expect(hydrate.mock.calls[0][0]).toHaveLength(10) } finally { within.mockRestore() hydrate.mockRestore() } }) it('orders the WHOLE neighbour set before cutting the page', async () => { const results = await brain.find({ connected: { from: anchor, direction: 'out' }, where: { kind: 'note' }, orderBy: 'rank', order: 'desc', limit: 5 }) expect(results.map((r) => r.entity.metadata.rank)).toEqual([30, 29, 28, 27, 26]) }) it('walks the vector leg over the neighbours only', async () => { const results = await brain.find({ vector: sharedVector, connected: { from: anchor, direction: 'out' }, where: { kind: 'note' }, limit: 5 }) expect(results).toHaveLength(5) for (const r of results) expect(neighbourIds.has(r.entity.id)).toBe(true) }) it('an anchor without neighbours answers [] before the filter is asked', async () => { const index = (brain as any).metadataIndex const within = vi.spyOn(index, 'filterIdsWithin') try { const results = await brain.find({ connected: { from: 'noise-0', direction: 'out' }, where: { kind: 'note' }, limit: 10 }) expect(results).toEqual([]) expect(within).not.toHaveBeenCalled() } finally { within.mockRestore() } }) })