2026-05-28 10:24:48 -07:00
|
|
|
/**
|
|
|
|
|
* @module column-store-interchange.test
|
|
|
|
|
* @description 2.4.0 #4 — column-store JS↔native interchange. Pins down that
|
|
|
|
|
* brainy's JS `ColumnStore` writes segments + DELETED bitmaps via the binary-
|
|
|
|
|
* blob primitive at the cortex-shared key convention, AND that indexes
|
|
|
|
|
* persisted under the pre-2.4.0 envelope (`{ _binary, base64 }` at the
|
|
|
|
|
* `.cidx` object-path) still load correctly via the legacy-read fallback.
|
|
|
|
|
*
|
|
|
|
|
* Together these properties make the JS and native engines byte-compatible
|
|
|
|
|
* at the segment payload, so writing through one and reading through the
|
|
|
|
|
* other is a no-op format-wise — closing the gap the cortex 2.3.1 read-side
|
|
|
|
|
* fallback opened on the cortex side.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { describe, it, expect, beforeEach, afterEach } from 'vitest'
|
|
|
|
|
import { ColumnStore } from '../../../../src/indexes/columnStore/ColumnStore.js'
|
|
|
|
|
import { MemoryStorage } from '../../../../src/storage/adapters/memoryStorage.js'
|
|
|
|
|
import { EntityIdMapper } from '../../../../src/utils/entityIdMapper.js'
|
|
|
|
|
import { writeSegmentToBuffer } from '../../../../src/indexes/columnStore/ColumnSegmentFormat.js'
|
|
|
|
|
import { ColumnManifest } from '../../../../src/indexes/columnStore/ColumnManifest.js'
|
|
|
|
|
import { RoaringBitmap32 } from '../../../../src/utils/roaring/index.js'
|
|
|
|
|
|
|
|
|
|
describe('ColumnStore — JS↔native interchange (2.4.0 #4)', () => {
|
|
|
|
|
let storage: MemoryStorage
|
|
|
|
|
let idMapper: EntityIdMapper
|
|
|
|
|
let store: ColumnStore
|
|
|
|
|
|
|
|
|
|
beforeEach(async () => {
|
|
|
|
|
storage = new MemoryStorage()
|
|
|
|
|
await storage.init()
|
|
|
|
|
idMapper = new EntityIdMapper({ storage, storageKey: 'test:idMapper' })
|
|
|
|
|
await idMapper.init()
|
|
|
|
|
store = new ColumnStore({ flushThreshold: 10 })
|
|
|
|
|
await store.init(storage, idMapper)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
afterEach(async () => {
|
|
|
|
|
await store.close()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
|
// WRITE side: raw blob at the cortex-shared key, no legacy envelope
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
it('flushBuffer writes segment bytes as a raw blob at the cortex-shared key', async () => {
|
|
|
|
|
for (let i = 0; i < 5; i++) {
|
feat(8.0): u64 BigInt graph provider contract — punch list a-d,g,h
GraphIndexProvider now speaks BigInt at the boundary (D.2 mirror):
- getNeighbors/getVerbIdsBySource/getVerbIdsByTarget take entity ints and
return entity/verb ints as bigint[]
- new REQUIRED verbIntsToIds(bigint[]) batch reverse resolver (L.7
identity-fingerprint design — verb ids are UUIDs by contract, so the
provider-side interning is losslessly reversible)
- addVerb(verb, sourceInt, targetInt) returns the interned verb int;
removeVerb(verbId) joins the contract
Coordinator (brainy.ts) owns ALL UUID <-> int conversion: getOrAssign on
writes, getInt on reads (unmapped UUID -> empty result without calling the
provider), getUuid / verbIntsToIds on returns, plus a bounded ~100k-entry
insertion-order warm cache for verb-int -> verb-id pairs fed by addVerb
returns and resolver results. GraphVerb gains derived sourceInt/targetInt
(populated at add time, never persisted). findConnectedSubtype gains a
native fast path that routes single-type single-subtype outgoing BFS
through the provider when available.
JS GraphAdjacencyIndex satisfies the contract while staying string/u32-keyed
internally: entity ints resolve through the shared entity-id mapper (threaded
in by the coordinator on init/fork/checkout), verb ints come from an
in-process append-only interning map re-derived from storage on
rebuild/cold-start. ColumnStoreProvider widens the same way: addEntity/
removeEntity take bigint, sortTopK/filteredSortTopK return bigint[].
relate() now rejects a caller-supplied id with a teaching error — verb ids
are brainy-generated UUIDs by contract in 8.0 (previously a passed id was
silently ignored). No Roaring64 provider-boundary decode site exists yet;
the JS-internal column store stays Roaring32 and the Treemap decoder lands
with the first consumer of provider-returned filter buffers.
Public brain API unchanged. 1413 tests green (+10 new BigInt contract tests).
2026-06-10 10:45:45 -07:00
|
|
|
store.addEntity(BigInt(idMapper.getOrAssign(`u-${i}`)), { score: i * 10 })
|
2026-05-28 10:24:48 -07:00
|
|
|
}
|
|
|
|
|
await store.flush()
|
|
|
|
|
|
|
|
|
|
// The raw blob exists at the suffix-free cortex key.
|
|
|
|
|
const key = '_column_index/score/L0-000001'
|
|
|
|
|
const raw = await (storage as any).loadBinaryBlob(key)
|
|
|
|
|
expect(Buffer.isBuffer(raw)).toBe(true)
|
|
|
|
|
expect(raw!.length).toBeGreaterThan(0)
|
|
|
|
|
|
|
|
|
|
// The legacy envelope at the `.cidx` object-path is NOT written (no
|
|
|
|
|
// 33% base64 bloat, no dual-write).
|
|
|
|
|
const legacyPath = '_column_index/score/L0-000001.cidx'
|
|
|
|
|
const legacyEnvelope = await (storage as any).readObjectFromPath(legacyPath)
|
|
|
|
|
expect(legacyEnvelope).toBeFalsy()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('flushBuffer writes the DELETED bitmap as a raw blob too (not an envelope)', async () => {
|
|
|
|
|
for (let i = 0; i < 3; i++) {
|
feat(8.0): u64 BigInt graph provider contract — punch list a-d,g,h
GraphIndexProvider now speaks BigInt at the boundary (D.2 mirror):
- getNeighbors/getVerbIdsBySource/getVerbIdsByTarget take entity ints and
return entity/verb ints as bigint[]
- new REQUIRED verbIntsToIds(bigint[]) batch reverse resolver (L.7
identity-fingerprint design — verb ids are UUIDs by contract, so the
provider-side interning is losslessly reversible)
- addVerb(verb, sourceInt, targetInt) returns the interned verb int;
removeVerb(verbId) joins the contract
Coordinator (brainy.ts) owns ALL UUID <-> int conversion: getOrAssign on
writes, getInt on reads (unmapped UUID -> empty result without calling the
provider), getUuid / verbIntsToIds on returns, plus a bounded ~100k-entry
insertion-order warm cache for verb-int -> verb-id pairs fed by addVerb
returns and resolver results. GraphVerb gains derived sourceInt/targetInt
(populated at add time, never persisted). findConnectedSubtype gains a
native fast path that routes single-type single-subtype outgoing BFS
through the provider when available.
JS GraphAdjacencyIndex satisfies the contract while staying string/u32-keyed
internally: entity ints resolve through the shared entity-id mapper (threaded
in by the coordinator on init/fork/checkout), verb ints come from an
in-process append-only interning map re-derived from storage on
rebuild/cold-start. ColumnStoreProvider widens the same way: addEntity/
removeEntity take bigint, sortTopK/filteredSortTopK return bigint[].
relate() now rejects a caller-supplied id with a teaching error — verb ids
are brainy-generated UUIDs by contract in 8.0 (previously a passed id was
silently ignored). No Roaring64 provider-boundary decode site exists yet;
the JS-internal column store stays Roaring32 and the Treemap decoder lands
with the first consumer of provider-returned filter buffers.
Public brain API unchanged. 1413 tests green (+10 new BigInt contract tests).
2026-06-10 10:45:45 -07:00
|
|
|
store.addEntity(BigInt(idMapper.getOrAssign(`u-${i}`)), { score: i })
|
2026-05-28 10:24:48 -07:00
|
|
|
}
|
feat(8.0): u64 BigInt graph provider contract — punch list a-d,g,h
GraphIndexProvider now speaks BigInt at the boundary (D.2 mirror):
- getNeighbors/getVerbIdsBySource/getVerbIdsByTarget take entity ints and
return entity/verb ints as bigint[]
- new REQUIRED verbIntsToIds(bigint[]) batch reverse resolver (L.7
identity-fingerprint design — verb ids are UUIDs by contract, so the
provider-side interning is losslessly reversible)
- addVerb(verb, sourceInt, targetInt) returns the interned verb int;
removeVerb(verbId) joins the contract
Coordinator (brainy.ts) owns ALL UUID <-> int conversion: getOrAssign on
writes, getInt on reads (unmapped UUID -> empty result without calling the
provider), getUuid / verbIntsToIds on returns, plus a bounded ~100k-entry
insertion-order warm cache for verb-int -> verb-id pairs fed by addVerb
returns and resolver results. GraphVerb gains derived sourceInt/targetInt
(populated at add time, never persisted). findConnectedSubtype gains a
native fast path that routes single-type single-subtype outgoing BFS
through the provider when available.
JS GraphAdjacencyIndex satisfies the contract while staying string/u32-keyed
internally: entity ints resolve through the shared entity-id mapper (threaded
in by the coordinator on init/fork/checkout), verb ints come from an
in-process append-only interning map re-derived from storage on
rebuild/cold-start. ColumnStoreProvider widens the same way: addEntity/
removeEntity take bigint, sortTopK/filteredSortTopK return bigint[].
relate() now rejects a caller-supplied id with a teaching error — verb ids
are brainy-generated UUIDs by contract in 8.0 (previously a passed id was
silently ignored). No Roaring64 provider-boundary decode site exists yet;
the JS-internal column store stays Roaring32 and the Treemap decoder lands
with the first consumer of provider-returned filter buffers.
Public brain API unchanged. 1413 tests green (+10 new BigInt contract tests).
2026-06-10 10:45:45 -07:00
|
|
|
store.removeEntity(BigInt(idMapper.getInt('u-1')!))
|
2026-05-28 10:24:48 -07:00
|
|
|
await store.flush()
|
|
|
|
|
|
|
|
|
|
const rawDel = await (storage as any).loadBinaryBlob('_column_index/score/DELETED')
|
|
|
|
|
expect(Buffer.isBuffer(rawDel)).toBe(true)
|
|
|
|
|
expect(rawDel!.length).toBeGreaterThan(0)
|
|
|
|
|
|
|
|
|
|
const legacyEnvelope = await (storage as any).readObjectFromPath(
|
|
|
|
|
'_column_index/score/DELETED.bin'
|
|
|
|
|
)
|
|
|
|
|
expect(legacyEnvelope).toBeFalsy()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
|
// READ side: pre-2.4.0 legacy envelope still loads via the fallback
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
it('loads a segment persisted as the pre-2.4.0 envelope (legacy fallback)', async () => {
|
|
|
|
|
// Seed a legacy-format index DIRECTLY on storage — the on-disk shape
|
|
|
|
|
// brainy 7.20.0..7.25.0 wrote before this commit.
|
|
|
|
|
const legacyStore = new ColumnStore()
|
|
|
|
|
await legacyStore.init(storage, idMapper)
|
|
|
|
|
|
|
|
|
|
// Build a segment buffer using the shared cidx format.
|
|
|
|
|
const values: number[] = [10, 20, 30]
|
|
|
|
|
const entityIds: number[] = [
|
|
|
|
|
idMapper.getOrAssign('legacy-a'),
|
|
|
|
|
idMapper.getOrAssign('legacy-b'),
|
|
|
|
|
idMapper.getOrAssign('legacy-c')
|
|
|
|
|
]
|
|
|
|
|
const segBuffer = writeSegmentToBuffer(
|
|
|
|
|
{
|
|
|
|
|
fieldName: 'score',
|
|
|
|
|
fieldNameLength: 5,
|
|
|
|
|
valueType: 0,
|
|
|
|
|
level: 0,
|
|
|
|
|
codec: 0,
|
|
|
|
|
flags: 0,
|
|
|
|
|
count: values.length
|
|
|
|
|
},
|
|
|
|
|
values,
|
|
|
|
|
entityIds
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Persist via the LEGACY envelope path (writeObjectToPath, base64 wrap).
|
|
|
|
|
await (storage as any).writeObjectToPath('_column_index/score/L0-000001.cidx', {
|
|
|
|
|
_binary: true,
|
|
|
|
|
data: segBuffer.toString('base64')
|
|
|
|
|
})
|
|
|
|
|
const manifest = new ColumnManifest('score', '_column_index')
|
|
|
|
|
manifest.valueType = 0
|
|
|
|
|
manifest.multiValue = false
|
|
|
|
|
manifest.addSegment({
|
|
|
|
|
id: 1,
|
|
|
|
|
level: 0,
|
|
|
|
|
count: values.length,
|
|
|
|
|
minValue: values[0],
|
|
|
|
|
maxValue: values[values.length - 1],
|
|
|
|
|
file: 'L0-000001.cidx'
|
|
|
|
|
})
|
|
|
|
|
await manifest.save(storage)
|
|
|
|
|
await legacyStore.close()
|
|
|
|
|
|
|
|
|
|
// NEW ColumnStore re-discovers the manifest at init, then the legacy
|
|
|
|
|
// segment loads via the readObjectFromPath fallback (no raw blob exists
|
|
|
|
|
// at the cortex key, so it falls through). sortTopK returns the entities
|
|
|
|
|
// in the order the segment recorded — proves the bytes round-tripped.
|
|
|
|
|
const readStore = new ColumnStore()
|
|
|
|
|
await readStore.init(storage, idMapper)
|
|
|
|
|
const sortedInts = await readStore.sortTopK('score', 'asc', 10)
|
feat(8.0): u64 BigInt graph provider contract — punch list a-d,g,h
GraphIndexProvider now speaks BigInt at the boundary (D.2 mirror):
- getNeighbors/getVerbIdsBySource/getVerbIdsByTarget take entity ints and
return entity/verb ints as bigint[]
- new REQUIRED verbIntsToIds(bigint[]) batch reverse resolver (L.7
identity-fingerprint design — verb ids are UUIDs by contract, so the
provider-side interning is losslessly reversible)
- addVerb(verb, sourceInt, targetInt) returns the interned verb int;
removeVerb(verbId) joins the contract
Coordinator (brainy.ts) owns ALL UUID <-> int conversion: getOrAssign on
writes, getInt on reads (unmapped UUID -> empty result without calling the
provider), getUuid / verbIntsToIds on returns, plus a bounded ~100k-entry
insertion-order warm cache for verb-int -> verb-id pairs fed by addVerb
returns and resolver results. GraphVerb gains derived sourceInt/targetInt
(populated at add time, never persisted). findConnectedSubtype gains a
native fast path that routes single-type single-subtype outgoing BFS
through the provider when available.
JS GraphAdjacencyIndex satisfies the contract while staying string/u32-keyed
internally: entity ints resolve through the shared entity-id mapper (threaded
in by the coordinator on init/fork/checkout), verb ints come from an
in-process append-only interning map re-derived from storage on
rebuild/cold-start. ColumnStoreProvider widens the same way: addEntity/
removeEntity take bigint, sortTopK/filteredSortTopK return bigint[].
relate() now rejects a caller-supplied id with a teaching error — verb ids
are brainy-generated UUIDs by contract in 8.0 (previously a passed id was
silently ignored). No Roaring64 provider-boundary decode site exists yet;
the JS-internal column store stays Roaring32 and the Treemap decoder lands
with the first consumer of provider-returned filter buffers.
Public brain API unchanged. 1413 tests green (+10 new BigInt contract tests).
2026-06-10 10:45:45 -07:00
|
|
|
const sortedUuids = sortedInts.map(i => idMapper.getUuid(Number(i)))
|
2026-05-28 10:24:48 -07:00
|
|
|
expect(sortedUuids).toEqual(['legacy-a', 'legacy-b', 'legacy-c'])
|
|
|
|
|
await readStore.close()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('loads a DELETED bitmap persisted as the pre-2.4.0 envelope (legacy fallback)', async () => {
|
|
|
|
|
// Manually seed a legacy DELETED.bin envelope, then re-init and confirm
|
|
|
|
|
// the entity it marks gone is excluded from filter/sort results.
|
|
|
|
|
const bm = new RoaringBitmap32()
|
|
|
|
|
bm.add(idMapper.getOrAssign('dead'))
|
|
|
|
|
const serialized = Buffer.from(bm.serialize(true))
|
|
|
|
|
await (storage as any).writeObjectToPath('_column_index/score/DELETED.bin', {
|
|
|
|
|
_binary: true,
|
|
|
|
|
data: serialized.toString('base64')
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// Add a couple of live entities so a manifest exists (init only loads
|
|
|
|
|
// DELETED for fields with a manifest on disk).
|
feat(8.0): u64 BigInt graph provider contract — punch list a-d,g,h
GraphIndexProvider now speaks BigInt at the boundary (D.2 mirror):
- getNeighbors/getVerbIdsBySource/getVerbIdsByTarget take entity ints and
return entity/verb ints as bigint[]
- new REQUIRED verbIntsToIds(bigint[]) batch reverse resolver (L.7
identity-fingerprint design — verb ids are UUIDs by contract, so the
provider-side interning is losslessly reversible)
- addVerb(verb, sourceInt, targetInt) returns the interned verb int;
removeVerb(verbId) joins the contract
Coordinator (brainy.ts) owns ALL UUID <-> int conversion: getOrAssign on
writes, getInt on reads (unmapped UUID -> empty result without calling the
provider), getUuid / verbIntsToIds on returns, plus a bounded ~100k-entry
insertion-order warm cache for verb-int -> verb-id pairs fed by addVerb
returns and resolver results. GraphVerb gains derived sourceInt/targetInt
(populated at add time, never persisted). findConnectedSubtype gains a
native fast path that routes single-type single-subtype outgoing BFS
through the provider when available.
JS GraphAdjacencyIndex satisfies the contract while staying string/u32-keyed
internally: entity ints resolve through the shared entity-id mapper (threaded
in by the coordinator on init/fork/checkout), verb ints come from an
in-process append-only interning map re-derived from storage on
rebuild/cold-start. ColumnStoreProvider widens the same way: addEntity/
removeEntity take bigint, sortTopK/filteredSortTopK return bigint[].
relate() now rejects a caller-supplied id with a teaching error — verb ids
are brainy-generated UUIDs by contract in 8.0 (previously a passed id was
silently ignored). No Roaring64 provider-boundary decode site exists yet;
the JS-internal column store stays Roaring32 and the Treemap decoder lands
with the first consumer of provider-returned filter buffers.
Public brain API unchanged. 1413 tests green (+10 new BigInt contract tests).
2026-06-10 10:45:45 -07:00
|
|
|
store.addEntity(BigInt(idMapper.getOrAssign('alive-1')), { score: 100 })
|
|
|
|
|
store.addEntity(BigInt(idMapper.getOrAssign('alive-2')), { score: 200 })
|
2026-05-28 10:24:48 -07:00
|
|
|
await store.flush()
|
|
|
|
|
await store.close()
|
|
|
|
|
|
|
|
|
|
const readStore = new ColumnStore()
|
|
|
|
|
await readStore.init(storage, idMapper)
|
|
|
|
|
const sortedInts = await readStore.sortTopK('score', 'asc', 10)
|
feat(8.0): u64 BigInt graph provider contract — punch list a-d,g,h
GraphIndexProvider now speaks BigInt at the boundary (D.2 mirror):
- getNeighbors/getVerbIdsBySource/getVerbIdsByTarget take entity ints and
return entity/verb ints as bigint[]
- new REQUIRED verbIntsToIds(bigint[]) batch reverse resolver (L.7
identity-fingerprint design — verb ids are UUIDs by contract, so the
provider-side interning is losslessly reversible)
- addVerb(verb, sourceInt, targetInt) returns the interned verb int;
removeVerb(verbId) joins the contract
Coordinator (brainy.ts) owns ALL UUID <-> int conversion: getOrAssign on
writes, getInt on reads (unmapped UUID -> empty result without calling the
provider), getUuid / verbIntsToIds on returns, plus a bounded ~100k-entry
insertion-order warm cache for verb-int -> verb-id pairs fed by addVerb
returns and resolver results. GraphVerb gains derived sourceInt/targetInt
(populated at add time, never persisted). findConnectedSubtype gains a
native fast path that routes single-type single-subtype outgoing BFS
through the provider when available.
JS GraphAdjacencyIndex satisfies the contract while staying string/u32-keyed
internally: entity ints resolve through the shared entity-id mapper (threaded
in by the coordinator on init/fork/checkout), verb ints come from an
in-process append-only interning map re-derived from storage on
rebuild/cold-start. ColumnStoreProvider widens the same way: addEntity/
removeEntity take bigint, sortTopK/filteredSortTopK return bigint[].
relate() now rejects a caller-supplied id with a teaching error — verb ids
are brainy-generated UUIDs by contract in 8.0 (previously a passed id was
silently ignored). No Roaring64 provider-boundary decode site exists yet;
the JS-internal column store stays Roaring32 and the Treemap decoder lands
with the first consumer of provider-returned filter buffers.
Public brain API unchanged. 1413 tests green (+10 new BigInt contract tests).
2026-06-10 10:45:45 -07:00
|
|
|
const sortedUuids = sortedInts.map(i => idMapper.getUuid(Number(i)))
|
2026-05-28 10:24:48 -07:00
|
|
|
|
|
|
|
|
// The dead entity must NOT appear; the alive ones must.
|
|
|
|
|
expect(sortedUuids).not.toContain('dead')
|
|
|
|
|
expect(sortedUuids).toContain('alive-1')
|
|
|
|
|
expect(sortedUuids).toContain('alive-2')
|
|
|
|
|
await readStore.close()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
|
// ROUND-TRIP: write-then-read in the new format
|
|
|
|
|
// -----------------------------------------------------------------------
|
|
|
|
|
|
|
|
|
|
it('write (new format) → re-init → read returns the same data', async () => {
|
|
|
|
|
for (let i = 0; i < 5; i++) {
|
feat(8.0): u64 BigInt graph provider contract — punch list a-d,g,h
GraphIndexProvider now speaks BigInt at the boundary (D.2 mirror):
- getNeighbors/getVerbIdsBySource/getVerbIdsByTarget take entity ints and
return entity/verb ints as bigint[]
- new REQUIRED verbIntsToIds(bigint[]) batch reverse resolver (L.7
identity-fingerprint design — verb ids are UUIDs by contract, so the
provider-side interning is losslessly reversible)
- addVerb(verb, sourceInt, targetInt) returns the interned verb int;
removeVerb(verbId) joins the contract
Coordinator (brainy.ts) owns ALL UUID <-> int conversion: getOrAssign on
writes, getInt on reads (unmapped UUID -> empty result without calling the
provider), getUuid / verbIntsToIds on returns, plus a bounded ~100k-entry
insertion-order warm cache for verb-int -> verb-id pairs fed by addVerb
returns and resolver results. GraphVerb gains derived sourceInt/targetInt
(populated at add time, never persisted). findConnectedSubtype gains a
native fast path that routes single-type single-subtype outgoing BFS
through the provider when available.
JS GraphAdjacencyIndex satisfies the contract while staying string/u32-keyed
internally: entity ints resolve through the shared entity-id mapper (threaded
in by the coordinator on init/fork/checkout), verb ints come from an
in-process append-only interning map re-derived from storage on
rebuild/cold-start. ColumnStoreProvider widens the same way: addEntity/
removeEntity take bigint, sortTopK/filteredSortTopK return bigint[].
relate() now rejects a caller-supplied id with a teaching error — verb ids
are brainy-generated UUIDs by contract in 8.0 (previously a passed id was
silently ignored). No Roaring64 provider-boundary decode site exists yet;
the JS-internal column store stays Roaring32 and the Treemap decoder lands
with the first consumer of provider-returned filter buffers.
Public brain API unchanged. 1413 tests green (+10 new BigInt contract tests).
2026-06-10 10:45:45 -07:00
|
|
|
store.addEntity(BigInt(idMapper.getOrAssign(`u-${i}`)), { score: i * 10 })
|
2026-05-28 10:24:48 -07:00
|
|
|
}
|
|
|
|
|
await store.flush()
|
|
|
|
|
await store.close()
|
|
|
|
|
|
|
|
|
|
const readStore = new ColumnStore()
|
|
|
|
|
await readStore.init(storage, idMapper)
|
|
|
|
|
const sortedInts = await readStore.sortTopK('score', 'asc', 10)
|
feat(8.0): u64 BigInt graph provider contract — punch list a-d,g,h
GraphIndexProvider now speaks BigInt at the boundary (D.2 mirror):
- getNeighbors/getVerbIdsBySource/getVerbIdsByTarget take entity ints and
return entity/verb ints as bigint[]
- new REQUIRED verbIntsToIds(bigint[]) batch reverse resolver (L.7
identity-fingerprint design — verb ids are UUIDs by contract, so the
provider-side interning is losslessly reversible)
- addVerb(verb, sourceInt, targetInt) returns the interned verb int;
removeVerb(verbId) joins the contract
Coordinator (brainy.ts) owns ALL UUID <-> int conversion: getOrAssign on
writes, getInt on reads (unmapped UUID -> empty result without calling the
provider), getUuid / verbIntsToIds on returns, plus a bounded ~100k-entry
insertion-order warm cache for verb-int -> verb-id pairs fed by addVerb
returns and resolver results. GraphVerb gains derived sourceInt/targetInt
(populated at add time, never persisted). findConnectedSubtype gains a
native fast path that routes single-type single-subtype outgoing BFS
through the provider when available.
JS GraphAdjacencyIndex satisfies the contract while staying string/u32-keyed
internally: entity ints resolve through the shared entity-id mapper (threaded
in by the coordinator on init/fork/checkout), verb ints come from an
in-process append-only interning map re-derived from storage on
rebuild/cold-start. ColumnStoreProvider widens the same way: addEntity/
removeEntity take bigint, sortTopK/filteredSortTopK return bigint[].
relate() now rejects a caller-supplied id with a teaching error — verb ids
are brainy-generated UUIDs by contract in 8.0 (previously a passed id was
silently ignored). No Roaring64 provider-boundary decode site exists yet;
the JS-internal column store stays Roaring32 and the Treemap decoder lands
with the first consumer of provider-returned filter buffers.
Public brain API unchanged. 1413 tests green (+10 new BigInt contract tests).
2026-06-10 10:45:45 -07:00
|
|
|
const sortedUuids = sortedInts.map(i => idMapper.getUuid(Number(i)))
|
2026-05-28 10:24:48 -07:00
|
|
|
expect(sortedUuids).toEqual(['u-0', 'u-1', 'u-2', 'u-3', 'u-4'])
|
|
|
|
|
await readStore.close()
|
|
|
|
|
})
|
|
|
|
|
})
|