/** * @module metadata-field-typing.unit.test * @description Regression: a metadata field that holds more than one value * KIND stays fully filterable on every kind it holds. * * The defect this pins, reproduced on the released engine: the metadata index * fixed a field's value type from the FIRST value it saw, and every later value * of a different type was coerced to that type or, when coercion failed, * dropped from the index in silence. Writing `category: 'electronics'` rows and * then `category: 5` rows left `find({ where: { category: 5 } })` returning * nothing — while the same rows in a numbers-only corpus answered correctly. * The rows themselves were never lost: they stayed readable by id and by vector * search, and only ever went missing from equality filters on that one field, * which is what made it so quiet. * * Order is the whole point of these cases. Neither writer owns the field, so * strings-then-numbers and numbers-then-strings must give the same answers. */ import { describe, it, expect } from 'vitest' import { Brainy } from '../../src/brainy.js' import { NounType } from '../../src/types/graphTypes.js' /** A brain over memory storage, with a corpus written in the given order. */ async function brainWith( rows: Array<{ label: string; category: unknown }> ): Promise { const brainy = new Brainy({ requireSubtype: false, storage: { type: 'memory' } }) await brainy.init() for (const row of rows) { await brainy.add({ data: `item ${row.label}`, type: NounType.Thing, metadata: { label: row.label, category: row.category } }) } return brainy } const labelsOf = (results: Array<{ metadata?: Record }>): string[] => results.map((r) => String(r.metadata?.label)).sort() describe('regression: a mixed-kind metadata field filters on every kind', { timeout: 180_000 }, () => { it('finds number rows written after string rows', async () => { const brainy = await brainWith([ { label: 'e1', category: 'electronics' }, { label: 'f1', category: 'furniture' }, { label: 'n1', category: 5 }, { label: 'n2', category: 5 }, { label: 'n3', category: 7 } ]) try { expect(labelsOf(await brainy.find({ where: { category: 5 }, limit: 100 }))).toEqual(['n1', 'n2']) expect(labelsOf(await brainy.find({ where: { category: 7 }, limit: 100 }))).toEqual(['n3']) expect(labelsOf(await brainy.find({ where: { category: 'electronics' }, limit: 100 }))).toEqual(['e1']) expect(labelsOf(await brainy.find({ where: { category: 'furniture' }, limit: 100 }))).toEqual(['f1']) } finally { await brainy.close() } }) it('finds string rows written after number rows', async () => { const brainy = await brainWith([ { label: 'n1', category: 5 }, { label: 'n2', category: 5 }, { label: 'e1', category: 'electronics' }, { label: 'e2', category: 'electronics' } ]) try { expect(labelsOf(await brainy.find({ where: { category: 'electronics' }, limit: 100 }))).toEqual(['e1', 'e2']) expect(labelsOf(await brainy.find({ where: { category: 5 }, limit: 100 }))).toEqual(['n1', 'n2']) } finally { await brainy.close() } }) it('keeps `5` and `\'5\'` apart — a kind is part of the value, not a formatting detail', async () => { const brainy = await brainWith([ { label: 'num', category: 5 }, { label: 'str', category: '5' } ]) try { expect(labelsOf(await brainy.find({ where: { category: 5 }, limit: 100 }))).toEqual(['num']) expect(labelsOf(await brainy.find({ where: { category: '5' }, limit: 100 }))).toEqual(['str']) } finally { await brainy.close() } }) it('serves booleans mixed into a field that already holds strings', async () => { const brainy = await brainWith([ { label: 's1', category: 'yes' }, { label: 'b1', category: true }, { label: 'b2', category: false } ]) try { expect(labelsOf(await brainy.find({ where: { category: true }, limit: 100 }))).toEqual(['b1']) expect(labelsOf(await brainy.find({ where: { category: false }, limit: 100 }))).toEqual(['b2']) expect(labelsOf(await brainy.find({ where: { category: 'yes' }, limit: 100 }))).toEqual(['s1']) } finally { await brainy.close() } }) it('ranges over the numeric part of a mixed field', async () => { const brainy = await brainWith([ { label: 'unpriced', category: 'on request' }, { label: 'cheap', category: 100 }, { label: 'mid', category: 500 }, { label: 'dear', category: 900 } ]) try { const found = await brainy.find({ where: { category: { greaterThan: 200 } }, limit: 100 }) expect(labelsOf(found)).toEqual(['dear', 'mid']) } finally { await brainy.close() } }) })