2025-11-14 10:26:23 -08:00
/ * *
* Index Operations with Rollback Support
*
* Provides transactional operations for all indexes :
2026-06-09 13:07:56 -07:00
* - JsHnswVectorIndex ( unified vector index )
2025-11-14 10:26:23 -08:00
* - MetadataIndexManager ( roaring bitmap filtering )
* - GraphAdjacencyIndex ( LSM - tree graph storage )
*
* Each operation can be executed and rolled back atomically .
* /
2026-06-09 13:07:56 -07:00
import type { JsHnswVectorIndex } from '../../hnsw/hnswIndex.js'
2025-11-14 10:26:23 -08:00
import type { MetadataIndexManager } from '../../utils/metadataIndex.js'
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
import type { GraphIndexProvider } from '../../plugin.js'
2025-11-14 10:26:23 -08:00
import type { GraphVerb } from '../../coreTypes.js'
import type { Operation , RollbackAction } from '../types.js'
/ * *
* Add to HNSW index with rollback support
*
* Rollback strategy :
* - Remove item from index
feat(8.0)!: delete fork/branch/commit/history/versions — superseded by the Db API
The COW version-control surface (fork, branches, checkout, commit,
getHistory/streamHistory, asOfCommit, brain.versions) is gone, along with
its subsystems: src/versioning/, the COW object store (CommitLog,
CommitObject, RefManager, TreeObject), HistoricalStorageAdapter, and the
TypeAwareHNSWIndex path it kept alive. The Db API (now/transact/asOf/
with/persist/restore) is the one versioning model in 8.0.
Survivors and replacements:
- BlobStorage survives (the VFS stores file content through it), relocated
to src/storage/blobStorage.ts with binaryDataCodec.ts; its adapter
interface is now BlobStoreAdapter, slimmed to the consumed surface
(write/read/has/delete/getMetadata + MIME-aware compression policy).
- brain.migrate() backup branches are replaced by persist-before-migrate:
MigrateOptions.backupTo persists a hard-link snapshot of the current
generation before any transform runs; MigrationResult.backupPath reports
it, and brain.restore(path) brings it back wholesale.
- CLI: cow.ts (fork/branch/checkout/history/migrate) is replaced by
snapshot.ts — snapshot <path>, restore <path>, history (tx-log),
generation.
- New public read API: brain.transactionLog({limit}) exposes the reified
tx-log (generation/timestamp/meta, newest first) that backs the CLI
history command; TxLogEntry is exported.
Tests: superseded suites deleted; fork/commit blocks excised from shared
suites; BlobStorage tests relocated + reworked against the slimmed store;
migration tests now prove the backupTo snapshot/restore round trip; new
transactionLog coverage in db-mvcc.
2026-06-10 15:22:47 -07:00
2025-11-14 10:26:23 -08:00
* /
export class AddToHNSWOperation implements Operation {
readonly name = 'AddToHNSW'
constructor (
2026-06-09 13:07:56 -07:00
private readonly index : JsHnswVectorIndex ,
2025-11-14 10:26:23 -08:00
private readonly id : string ,
private readonly vector : number [ ]
) { }
async execute ( ) : Promise < RollbackAction > {
// Check if item already exists (for rollback decision)
const existed = await this . itemExists ( this . id )
// Add to index
await this . index . addItem ( { id : this.id , vector : this.vector } )
// Return rollback action
return async ( ) = > {
if ( ! existed ) {
// Remove newly added item
await this . index . removeItem ( this . id )
}
// If item existed before, we don't rollback (update is OK)
// This prevents index corruption from removing pre-existing items
}
}
/ * *
2026-06-11 14:51:00 -07:00
* Check if item exists in index .
*
* ` getItem ` is an optional , feature - detected provider capability — see the
* VectorIndexProvider docs ; it is intentionally absent from the required
* contract ( Brainy ' s JS HNSW index omits it ) . When the capability is
* missing the answer must be ` false ` , not ` true ` : treating unknowable
* pre - existence as "existed" made every rollback skip removeItem , leaving
* phantom entries in the index after a failed transaction . The safe default
* is to remove what this operation added — update flows pair this op with a
* RemoveFromHNSWOperation whose own rollback restores the prior vector , so
* reverse - order rollback reconstructs the original state either way .
2025-11-14 10:26:23 -08:00
* /
private async itemExists ( id : string ) : Promise < boolean > {
2026-06-11 14:51:00 -07:00
const index = this . index as JsHnswVectorIndex & {
getItem ? : ( id : string ) = > Promise < unknown >
}
if ( typeof index . getItem !== 'function' ) return false
2025-11-14 10:26:23 -08:00
try {
2026-06-11 14:51:00 -07:00
const item = await index . getItem ( id )
return item !== undefined && item !== null
2025-11-14 10:26:23 -08:00
} catch {
return false
}
}
}
/ * *
* Remove from HNSW index with rollback support
*
* Rollback strategy :
* - Re - add item to index with original vector
*
* Note : Requires storing the vector for rollback
* /
export class RemoveFromHNSWOperation implements Operation {
readonly name = 'RemoveFromHNSW'
constructor (
2026-06-09 13:07:56 -07:00
private readonly index : JsHnswVectorIndex ,
2025-11-14 10:26:23 -08:00
private readonly id : string ,
private readonly vector : number [ ] // Required for rollback
) { }
async execute ( ) : Promise < RollbackAction > {
// Remove from index
await this . index . removeItem ( this . id )
// Return rollback action
return async ( ) = > {
// Re-add item with original vector
await this . index . addItem ( { id : this.id , vector : this.vector } )
}
}
}
/ * *
* Add to metadata index with rollback support
*
* Rollback strategy :
* - Remove item from index
* /
export class AddToMetadataIndexOperation implements Operation {
readonly name = 'AddToMetadataIndex'
constructor (
private readonly index : MetadataIndexManager ,
private readonly id : string ,
private readonly entity : any // Entity or metadata structure
) { }
async execute ( ) : Promise < RollbackAction > {
// Add to metadata index (skipFlush=true for transaction atomicity)
await this . index . addToIndex ( this . id , this . entity , true )
// Return rollback action
return async ( ) = > {
// Remove from metadata index
await this . index . removeFromIndex ( this . id , this . entity )
}
}
}
/ * *
* Remove from metadata index with rollback support
*
* Rollback strategy :
* - Re - add item to index with original metadata
* /
export class RemoveFromMetadataIndexOperation implements Operation {
readonly name = 'RemoveFromMetadataIndex'
constructor (
private readonly index : MetadataIndexManager ,
private readonly id : string ,
private readonly entity : any // Required for rollback
) { }
async execute ( ) : Promise < RollbackAction > {
// Remove from metadata index
await this . index . removeFromIndex ( this . id , this . entity )
// Return rollback action
return async ( ) = > {
// Re-add with original metadata (skipFlush=true)
await this . index . addToIndex ( this . id , this . entity , true )
}
}
}
/ * *
* Add verb to graph index with rollback support
*
* Rollback strategy :
* - Remove verb from graph index
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
*
* 8.0 u64 contract : the coordinator resolves both endpoint ints via the
* shared idMapper ( ` getOrAssign ` ) and passes them alongside the verb ; the
* provider returns the interned verb int , which is surfaced through the
* optional ` onVerbInt ` callback so the coordinator can feed its warm cache .
2025-11-14 10:26:23 -08:00
* /
export class AddToGraphIndexOperation implements Operation {
readonly name = 'AddToGraphIndex'
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
/ * *
* @param index - The graph - index provider ( JS baseline or native ) .
* @param verb - The verb to index ( ` sourceInt ` / ` targetInt ` mirrored on it ) .
* @param sourceInt - The source entity ' s interned int .
* @param targetInt - The target entity ' s interned int .
* @param onVerbInt - Optional hook invoked with the interned verb int
* returned by the provider ( feeds the coordinator ' s verb - int warm cache ) .
* /
2025-11-14 10:26:23 -08:00
constructor (
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
private readonly index : GraphIndexProvider ,
private readonly verb : GraphVerb ,
private readonly sourceInt : bigint ,
private readonly targetInt : bigint ,
private readonly onVerbInt ? : ( verbInt : bigint ) = > void
2025-11-14 10:26:23 -08:00
) { }
async execute ( ) : Promise < RollbackAction > {
// Add verb to graph index
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 verbInt = await this . index . addVerb ( this . verb , this . sourceInt , this . targetInt )
this . onVerbInt ? . ( verbInt )
2025-11-14 10:26:23 -08:00
// Return rollback action
return async ( ) = > {
// Remove verb from graph index
await this . index . removeVerb ( this . verb . id )
}
}
}
/ * *
* Remove verb from graph index with rollback support
*
* Rollback strategy :
* - Re - add verb to graph index
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
*
* 8.0 u64 contract : rollback re - adds through ` addVerb(verb, sourceInt,
* targetInt ) ` , so the coordinator resolves the endpoint ints up front
* ( while the entity → int mappings are guaranteed to still exist ) .
2025-11-14 10:26:23 -08:00
* /
export class RemoveFromGraphIndexOperation implements Operation {
readonly name = 'RemoveFromGraphIndex'
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
/ * *
* @param index - The graph - index provider ( JS baseline or native ) .
* @param verb - The verb being removed ( required for rollback re - add ) .
* @param sourceInt - The source entity ' s interned int ( rollback re - add ) .
* @param targetInt - The target entity ' s interned int ( rollback re - add ) .
* /
2025-11-14 10:26:23 -08:00
constructor (
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
private readonly index : GraphIndexProvider ,
private readonly verb : GraphVerb , // Required for rollback
private readonly sourceInt : bigint ,
private readonly targetInt : bigint
2025-11-14 10:26:23 -08:00
) { }
async execute ( ) : Promise < RollbackAction > {
// Remove verb from graph index
await this . index . removeVerb ( this . verb . id )
// Return rollback action
return async ( ) = > {
// Re-add verb with original data
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
await this . index . addVerb ( this . verb , this . sourceInt , this . targetInt )
2025-11-14 10:26:23 -08:00
}
}
}
/ * *
* Batch operation : Add multiple items to HNSW index
*
* Useful for bulk imports with transaction support .
* Rolls back all items if any fail .
* /
export class BatchAddToHNSWOperation implements Operation {
readonly name = 'BatchAddToHNSW'
private operations : AddToHNSWOperation [ ]
constructor (
2026-06-09 13:07:56 -07:00
index : JsHnswVectorIndex ,
2025-11-14 10:26:23 -08:00
items : Array < { id : string ; vector : number [ ] } >
) {
this . operations = items . map (
item = > new AddToHNSWOperation ( index , item . id , item . vector )
)
}
async execute ( ) : Promise < RollbackAction > {
const rollbackActions : RollbackAction [ ] = [ ]
// Execute all operations
for ( const op of this . operations ) {
const rollback = await op . execute ( )
if ( rollback ) {
rollbackActions . push ( rollback )
}
}
// Return combined rollback action
return async ( ) = > {
// Execute all rollbacks in reverse order
for ( let i = rollbackActions . length - 1 ; i >= 0 ; i -- ) {
await rollbackActions [ i ] ( )
}
}
}
}
/ * *
* Batch operation : Add multiple entities to metadata index
*
* Useful for bulk imports with transaction support .
* /
export class BatchAddToMetadataIndexOperation implements Operation {
readonly name = 'BatchAddToMetadataIndex'
private operations : AddToMetadataIndexOperation [ ]
constructor (
index : MetadataIndexManager ,
items : Array < { id : string ; entity : any } >
) {
this . operations = items . map (
item = > new AddToMetadataIndexOperation ( index , item . id , item . entity )
)
}
async execute ( ) : Promise < RollbackAction > {
const rollbackActions : RollbackAction [ ] = [ ]
// Execute all operations
for ( const op of this . operations ) {
const rollback = await op . execute ( )
if ( rollback ) {
rollbackActions . push ( rollback )
}
}
// Return combined rollback action
return async ( ) = > {
// Execute all rollbacks in reverse order
for ( let i = rollbackActions . length - 1 ; i >= 0 ; i -- ) {
await rollbackActions [ i ] ( )
}
}
}
}