brainy/tests/unit/brainy/find-include-vectors.test.ts
David Snelling 4cc2088aed feat(8.0): upsert + FindParams.includeVectors + removeMany adaptive chunking
Three additive ergonomics from the API-simplification audit (no behavior change
to existing call sites):

- AddParams.upsert: create-or-update in one call. With a custom id, an existing
  entity is MERGED via the update path (merges metadata, re-embeds changed data,
  bumps _rev, PRESERVES createdAt) instead of the destructive full overwrite a
  plain add() does. Mutually exclusive with ifAbsent (throws if both set);
  ignored when no id is supplied. Wired into add(), addMany (per-item flag
  propagation), and the transact add op (routes to planTxUpdate). Kills the
  get()-then-add() round-trip for idempotent writes.
- FindParams.includeVectors: mirror of GetOptions.includeVectors — find() returns
  stored vectors when set; default stays empty (the perf contract is preserved).
  Honored on both the query and metadata-only where paths, and in db.find().
- removeMany adaptive chunking: replaced the hardcoded chunkSize=10 with
  params.chunkSize ?? storageConfig.maxBatchSize, matching addMany/relateMany —
  one storage-adaptive batch policy across all *Many methods (no thrash on
  high-latency backends).

Tests: tests/unit/brainy/upsert.test.ts (insert/merge/createdAt-preserved/
re-embed/ifAbsent-conflict/no-id/addMany/transact), find-include-vectors.test.ts
(true/default/where-path), batch-operations.test.ts (removeMany >10 items).
2026-06-20 16:34:20 -07:00

94 lines
3 KiB
TypeScript

/**
* Unit tests for FindParams.includeVectors — opt-in vector hydration on find().
*
* Verifies that find({ includeVectors: true }) returns each result's stored
* embedding under entity.vector (matching get({ includeVectors: true })), that
* the default keeps the perf contract of an empty vector, and that the
* metadata-only find({ where }) path honors the flag too.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
import { Brainy } from '../../../src/brainy'
import { createTestConfig } from '../../helpers/test-factory'
describe('FindParams.includeVectors', () => {
let brain: Brainy
beforeEach(async () => {
brain = new Brainy(createTestConfig())
await brain.init()
})
afterEach(async () => {
await brain.close()
})
it('returns the stored vector for query-based find when includeVectors is true', async () => {
const id = await brain.add({
type: 'document',
data: 'machine learning and neural networks',
metadata: { topic: 'ai' }
})
// Stored vector, as get() surfaces it — the reference for the find() result.
const stored = await brain.get(id, { includeVectors: true })
expect(stored!.vector.length).toBeGreaterThan(0)
const results = await brain.find({
query: 'machine learning and neural networks',
includeVectors: true,
limit: 10
})
const hit = results.find((r) => r.entity.id === id)
expect(hit).toBeDefined()
expect(hit!.entity.vector.length).toBeGreaterThan(0)
expect(hit!.entity.vector).toEqual(stored!.vector)
})
it('returns an empty vector by default (perf contract preserved)', async () => {
const id = await brain.add({
type: 'document',
data: 'default path should not hydrate vectors',
metadata: { topic: 'perf' }
})
const results = await brain.find({
query: 'default path should not hydrate vectors',
limit: 10
})
const hit = results.find((r) => r.entity.id === id)
expect(hit).toBeDefined()
expect(hit!.entity.vector.length).toBe(0)
})
it('honors includeVectors on the metadata-only where path', async () => {
const id = await brain.add({
type: 'thing',
data: 'metadata only filter target',
metadata: { category: 'filtered' }
})
const stored = await brain.get(id, { includeVectors: true })
const withVectors = await brain.find({
where: { category: 'filtered' },
includeVectors: true,
limit: 10
})
const hit = withVectors.find((r) => r.entity.id === id)
expect(hit).toBeDefined()
expect(hit!.entity.vector.length).toBeGreaterThan(0)
expect(hit!.entity.vector).toEqual(stored!.vector)
// And the same filter without the flag stays empty.
const withoutVectors = await brain.find({
where: { category: 'filtered' },
limit: 10
})
const plainHit = withoutVectors.find((r) => r.entity.id === id)
expect(plainHit).toBeDefined()
expect(plainHit!.entity.vector.length).toBe(0)
})
})