2026-06-16 09:41:59 -07:00
/ * *
* Generation threading through the graph - index transaction operations .
*
* The graph time - travel contract ( AddVerb / RemoveVerb gain a ` generation ` ) is
* load - bearing for native providers : ` db.asOf(g) ` graph hops resolve historical
* endpoints from a per - generation edge chain the provider stamps at write time .
* Brainy ' s JS index ignores the value , so a behavioural test against it proves
* nothing — these tests use a spy provider to assert the operation layer :
* 1 . passes the resolved generation to addVerb / removeVerb ,
* 2 . resolves the generation at EXECUTE time , not construction ( the batch
* generation is only assigned once the commit begins executing ) , and
* 3 . reuses one generation across an operation ' s forward + rollback halves
* ( so an add and its undo reference the same watermark in the chain ) .
* /
import { describe , it , expect } from 'vitest'
import {
AddToGraphIndexOperation ,
RemoveFromGraphIndexOperation
} from '../../../src/transaction/operations/IndexOperations.js'
import type { GraphIndexProvider } from '../../../src/plugin.js'
import type { GraphVerb } from '../../../src/coreTypes.js'
import { VerbType } from '../../../src/types/graphTypes.js'
/** A graph verb with just the fields the operations touch. */
function makeVerb ( id : string ) : GraphVerb {
return {
id ,
sourceId : 'src-uuid' ,
targetId : 'tgt-uuid' ,
type : VerbType . RelatedTo ,
sourceInt : 10n ,
targetInt : 20n
} as unknown as GraphVerb
}
/** Records every (method, generation) the operations push to the provider. */
function makeSpyProvider ( ) : {
provider : GraphIndexProvider
calls : Array < { method : 'addVerb' | 'removeVerb' ; generation : bigint ; verbId : string } >
} {
const calls : Array < { method : 'addVerb' | 'removeVerb' ; generation : bigint ; verbId : string } > = [ ]
const provider = {
async addVerb ( verb : GraphVerb , _s : bigint , _t : bigint , generation : bigint ) : Promise < bigint > {
calls . push ( { method : 'addVerb' , generation , verbId : verb.id } )
return 99 n // interned verb int
} ,
async removeVerb ( verbId : string , generation : bigint ) : Promise < void > {
calls . push ( { method : 'removeVerb' , generation , verbId } )
}
} as unknown as GraphIndexProvider
return { provider , calls }
}
describe ( 'Graph index operations — generation threading' , ( ) = > {
it ( 'AddToGraphIndexOperation stamps addVerb (and its rollback removeVerb) at the resolved generation' , async ( ) = > {
const { provider , calls } = makeSpyProvider ( )
const verb = makeVerb ( 'verb-1' )
2026-07-10 18:08:09 -07:00
const op = new AddToGraphIndexOperation ( provider , verb , { sourceInt : 10n , targetInt : 20n } , ( ) = > 7 n )
2026-06-16 09:41:59 -07:00
const rollback = await op . execute ( )
expect ( calls ) . toEqual ( [ { method : 'addVerb' , generation : 7n , verbId : 'verb-1' } ] )
await rollback ( )
expect ( calls [ 1 ] ) . toEqual ( { method : 'removeVerb' , generation : 7n , verbId : 'verb-1' } )
} )
it ( 'RemoveFromGraphIndexOperation stamps removeVerb (and its rollback addVerb) at the resolved generation' , async ( ) = > {
const { provider , calls } = makeSpyProvider ( )
const verb = makeVerb ( 'verb-2' )
2026-07-10 18:08:09 -07:00
const op = new RemoveFromGraphIndexOperation ( provider , verb , { sourceInt : 10n , targetInt : 20n } , ( ) = > 12 n )
2026-06-16 09:41:59 -07:00
const rollback = await op . execute ( )
expect ( calls ) . toEqual ( [ { method : 'removeVerb' , generation : 12n , verbId : 'verb-2' } ] )
await rollback ( )
expect ( calls [ 1 ] ) . toEqual ( { method : 'addVerb' , generation : 12n , verbId : 'verb-2' } )
} )
it ( 'resolves the generation at EXECUTE time, not at construction' , async ( ) = > {
// The generation store assigns the batch generation only once the commit
// begins executing — after the operations are built. The thunk must be read
// then, so a value that changes between construction and execute is observed
// at its execute-time value.
const { provider , calls } = makeSpyProvider ( )
let current = 1 n
2026-07-10 18:08:09 -07:00
const op = new AddToGraphIndexOperation ( provider , makeVerb ( 'verb-3' ) , { sourceInt : 10n , targetInt : 20n } , ( ) = > current )
2026-06-16 09:41:59 -07:00
current = 42 n // store assigns the batch generation after the op is built
await op . execute ( )
expect ( calls [ 0 ] . generation ) . toBe ( 42 n )
} )
it ( 'still surfaces the provider verb int through onVerbInt after the signature change' , async ( ) = > {
const { provider } = makeSpyProvider ( )
let seen : bigint | undefined
const op = new AddToGraphIndexOperation (
provider ,
makeVerb ( 'verb-4' ) ,
2026-07-10 18:08:09 -07:00
{ sourceInt : 10n , targetInt : 20n } ,
2026-06-16 09:41:59 -07:00
( ) = > 5 n ,
( verbInt ) = > {
seen = verbInt
}
)
await op . execute ( )
expect ( seen ) . toBe ( 99 n )
} )
} )