2026-06-20 16:55:02 -07:00
/ * *
* @module tests / unit / storage / verb - cursor - pagination
* @description Graph - perf # 2 ( 8.0 ) : cursor pagination over the verb shard walk .
*
* ` getVerbsWithPagination ` used to be offset - only — it re - scanned from shard 0 on
* every page and early - terminated at ` offset+limit ` , so a full edge walk was O ( N ² )
* ( a consumer measured 19 k edges → 27 s paging at 900 / page ) . It now accepts an
* opaque cursor that resumes immediately after the last returned verb , making a
* full walk O ( N ) at any page size . These tests pin the correctness guarantees a
* cursor MUST provide : every item exactly once , no duplicates , no skips , and the
* same set as an offset walk — plus graceful fallback for a foreign token .
* /
import { describe , it , expect , beforeEach , afterEach } from 'vitest'
import { Brainy } from '../../../src/index.js'
import { NounType , VerbType } from '../../../src/types/graphTypes.js'
import { createTestConfig } from '../../helpers/test-factory.js'
describe ( 'verb cursor pagination (graph-perf #2)' , ( ) = > {
let brain : Brainy
// The storage layer is where the cursor lives; exercise the primitive directly.
let storage : {
getVerbs ( opts : {
pagination ? : { offset? : number ; limit? : number ; cursor? : string }
} ) : Promise < { items : Array < { id : string } > ; hasMore : boolean ; nextCursor? : string } >
}
const EDGE_COUNT = 40
beforeEach ( async ( ) = > {
brain = new Brainy ( createTestConfig ( ) )
await brain . init ( )
const ids : string [ ] = [ ]
for ( let i = 0 ; i <= EDGE_COUNT ; i ++ ) {
ids . push ( await brain . add ( { type : NounType . Person , subtype : 'employee' , data : ` N ${ i } ` } ) )
}
// Hub-and-spoke: EDGE_COUNT edges from one node, spread across id-hash shards.
for ( let i = 1 ; i <= EDGE_COUNT ; i ++ ) {
await brain . relate ( { from : ids [ 0 ] , to : ids [ i ] , type : VerbType . RelatedTo , subtype : 'colleague' } )
}
storage = ( brain as unknown as { storage : typeof storage } ) . storage
} )
afterEach ( async ( ) = > {
await brain . close ( )
} )
it ( 'a full cursored walk visits every edge exactly once (no dup, no skip)' , async ( ) = > {
const all = await storage . getVerbs ( { pagination : { limit : 10000 } } )
const refIds = new Set ( all . items . map ( ( v ) = > v . id ) )
expect ( refIds . size ) . toBe ( EDGE_COUNT )
const seen : string [ ] = [ ]
let cursor : string | undefined
let pages = 0
for ( ; ; ) {
const page = await storage . getVerbs ( {
pagination : cursor ? { limit : 7 , cursor } : { limit : 7 }
} )
pages ++
for ( const v of page . items ) seen . push ( v . id )
if ( ! page . hasMore ) break
expect ( page . nextCursor ) . toBeTruthy ( )
cursor = page . nextCursor
if ( pages > 100 ) throw new Error ( 'cursor walk failed to terminate' )
}
expect ( pages ) . toBeGreaterThan ( 1 ) // genuinely paginated
expect ( new Set ( seen ) . size ) . toBe ( seen . length ) // no duplicates
expect ( seen . length ) . toBe ( EDGE_COUNT ) // no skips
expect ( new Set ( seen ) ) . toEqual ( refIds ) // exactly the full set
} )
it ( 'cursor and offset walks return the same set' , async ( ) = > {
const offsetSeen : string [ ] = [ ]
let offset = 0
for ( ; ; ) {
const page = await storage . getVerbs ( { pagination : { limit : 9 , offset } } )
for ( const v of page . items ) offsetSeen . push ( v . id )
if ( ! page . hasMore ) break
offset += page . items . length
}
const cursorSeen : string [ ] = [ ]
let cursor : string | undefined
for ( ; ; ) {
const page = await storage . getVerbs ( {
pagination : cursor ? { limit : 9 , cursor } : { limit : 9 }
} )
for ( const v of page . items ) cursorSeen . push ( v . id )
if ( ! page . hasMore ) break
cursor = page . nextCursor
}
expect ( cursorSeen . length ) . toBe ( EDGE_COUNT )
expect ( new Set ( cursorSeen ) ) . toEqual ( new Set ( offsetSeen ) )
} )
fix: exception-safe aggregation backfill + generation-verified adoption + loud open-path guards
- Aggregation backfill walks build into a STAGING map and swap in atomically
on completion. A mid-walk failure drops the staging map — the previous live
state keeps serving, the aggregate stays flagged pending, and the storage
error surfaces to the failing query. Previously the walk wiped live state
before a scan that could throw, never cleared the pending flag on failure,
and re-ran a full walk on every subsequent query: a silent wipe/walk/throw
loop at the caller's retry rate.
- Failed walks are latched: retries within a 30s cooldown rethrow the recorded
error instantly instead of re-walking, so a tight caller-side retry loop
costs one loud error per query, never a full store walk per query.
- Persisted aggregation state is stamped with the store's committed generation
at flush; reopen adoption requires stamp equality. Stale state (unclean
shutdown) or over-counting state (a log truncation on a copied store pulled
the watermark back) triggers exactly one loud rescan, never a silent adopt.
- The backfill/adoption path narrates: adoption decisions, walk start/finish
with counts and duration, and failures all log by default.
- getNouns/getVerbs refuse a supplied-but-undecodable pagination cursor with a
loud error instead of silently restarting the walk at offset 0 (which
re-served page 1 forever to any while(hasMore) caller).
- The graph cold-load verb walk aborts loudly on a missing or non-advancing
cursor with hasMore=true.
- A versioned index provider whose generation is AHEAD of the committed
watermark (torn copy / crash-recovery truncation) is now named loudly at
open, alongside the existing behind-direction message.
2026-07-17 16:00:11 -07:00
it ( 'a foreign/malformed cursor FAILS LOUDLY — never a silent restart from page 1' , async ( ) = > {
// The old behavior (decode-null → silent offset-0 fallback) re-served page 1
// forever to any while(hasMore) walker: an unbounded CPU loop with no log
// line. An undecodable resume token now refuses the walk instead.
await expect (
storage . getVerbs ( { pagination : { limit : 5 , cursor : 'not-a-cv1-token' } } )
) . rejects . toThrow ( 'invalid pagination cursor' )
await expect (
storage . getNouns ( { pagination : { limit : 5 , cursor : 'not-a-cv1-token' } } )
) . rejects . toThrow ( 'invalid pagination cursor' )
2026-06-20 16:55:02 -07:00
} )
} )