feat(engine): a protected factory for the generation store — a subclass may substitute one that keeps the contract
Some checks are pending
CI / Node 22 (push) Waiting to run
CI / Node 24 (push) Waiting to run
CI / Integration + conformance (Node 22) (push) Waiting to run
CI / Bun (latest) (push) Waiting to run

This commit is contained in:
David Snelling 2026-09-02 09:09:30 -07:00
parent a8c5fbf9dc
commit f763317af7
2 changed files with 113 additions and 1 deletions

View file

@ -1040,6 +1040,17 @@ export class Brainy<T = any> implements BrainyInterface<T> {
}
}
/**
* Factory hook for the generation store, so an engine built on top of this
* reference implementation can substitute a `GenerationStore` that keeps
* the same behavioural contract (for example, one backed by a native
* implementation) overriding it never changes this engine's own
* behaviour, since the default implementation is unchanged.
*/
protected createGenerationStore(storage: BaseStorage): GenerationStore {
return new GenerationStore(storage)
}
/**
* Initialize Brainy.
*
@ -1297,7 +1308,7 @@ export class Brainy<T = any> implements BrainyInterface<T> {
// guarantees indexes never observe rolled-back state. Reader-mode
// instances skip recovery (readers never write; the next writer
// repairs).
this.generationStore = new GenerationStore(this.storage)
this.generationStore = this.createGenerationStore(this.storage)
const generationOpenResult = await step(
'generation-store.open',
'reading the generation manifest and committed ranges, opening the fact log and the ' +

View file

@ -0,0 +1,101 @@
/**
* @module tests/integration/generation-store-factory
* @description Pins the `createGenerationStore` protected factory hook on
* `Brainy` ({@link Brainy.createGenerationStore}). The hook exists so an
* engine built on top of this reference implementation can substitute a
* `GenerationStore` that keeps the same behavioural contract; this suite
* proves two things:
*
* 1. A subclass overriding the hook is the ONLY path that constructs the
* generation store it is called exactly once, with the same storage
* instance `performInit` holds and the store the brain actually uses
* is the one the override returned.
* 2. The default (non-overridden) path is unaffected proven here by
* confirming the base class still produces a plain `GenerationStore`
* wired to `brain.storage`, and separately by running the existing
* `db-mvcc` and `brainy-core.integration` suites unmodified against this
* change (they exercise generation-store behaviour end to end).
*/
import { describe, it, expect, afterEach } from 'vitest'
import { Brainy } from '../../src/brainy.js'
import { GenerationStore } from '../../src/db/generationStore.js'
import type { BaseStorage } from '../../src/storage/baseStorage.js'
/** Typed access to the brain's private storage + generation-store fields (test injection point). */
function internalsOf(brain: Brainy): { storage: BaseStorage; generationStore: GenerationStore } {
return brain as unknown as { storage: BaseStorage; generationStore: GenerationStore }
}
/**
* A `GenerationStore` subclass that counts its own construction and
* remembers the storage instance it was built with, so the test can prove
* the hook is the sole construction path without mocking the module.
*/
class SpyGenerationStore extends GenerationStore {
static constructCount = 0
static lastStorage: BaseStorage | undefined
constructor(storage: BaseStorage) {
super(storage)
SpyGenerationStore.constructCount++
SpyGenerationStore.lastStorage = storage
}
}
/** A Brainy subclass overriding the factory hook — stands in for an engine built on the reference. */
class BrainyWithSpyStore extends Brainy {
hookCallCount = 0
hookStorageArg: BaseStorage | undefined
protected override createGenerationStore(storage: BaseStorage): GenerationStore {
this.hookCallCount++
this.hookStorageArg = storage
return new SpyGenerationStore(storage)
}
}
describe('Brainy.createGenerationStore — protected factory hook', () => {
const brains: Brainy[] = []
afterEach(async () => {
SpyGenerationStore.constructCount = 0
SpyGenerationStore.lastStorage = undefined
for (const brain of brains.splice(0)) {
try {
await brain.close()
} catch {
// already closed by the test
}
}
})
it('a subclass override is the sole construction path: called once, same storage instance, its store is the one the brain uses', async () => {
const brain = new BrainyWithSpyStore({ requireSubtype: false, storage: { type: 'memory' } })
await brain.init()
brains.push(brain)
// Called exactly once, through the hook.
expect(brain.hookCallCount).toBe(1)
expect(SpyGenerationStore.constructCount).toBe(1)
// Same storage instance the base class holds — not a copy, not a different adapter.
const { storage, generationStore } = internalsOf(brain)
expect(brain.hookStorageArg).toBe(storage)
expect(SpyGenerationStore.lastStorage).toBe(storage)
// The store the brain actually uses is the one the override returned.
expect(generationStore).toBeInstanceOf(SpyGenerationStore)
})
it('the default (non-overridden) path still produces a plain GenerationStore wired to the same storage', async () => {
const brain = new Brainy({ requireSubtype: false, storage: { type: 'memory' } })
await brain.init()
brains.push(brain)
const { storage, generationStore } = internalsOf(brain)
expect(generationStore).toBeInstanceOf(GenerationStore)
// The default implementation constructs from the same storage the brain holds.
expect((generationStore as unknown as { storage: BaseStorage }).storage).toBe(storage)
})
})