2025-10-22 17:36:27 -07:00
import { describe , it , expect , beforeEach } from 'vitest'
import { Brainy } from '../../../src/brainy.js'
import { InstancePool , createInstancePool } from '../../../src/import/InstancePool.js'
describe ( 'InstancePool' , ( ) = > {
let brain : Brainy
let pool : InstancePool
beforeEach ( async ( ) = > {
feat(8.0)!: flip requireSubtype default to true (BRAINY-8.0-SUBTYPE-CONTRACT § C-1)
Brainy 8.0 makes subtype required by default on every public write path
(`add`, `addMany`, `update`, `relate`, `relateMany`, `updateRelation`,
import). Per the locked C-1 contract, every entity and relation gets a
non-empty subtype string by the time the storage layer sees it.
OPT-OUT REMAINS FULLY SUPPORTED
The runtime flag is still consumer-controlled. Three opt-out paths
cover migration / legacy fixtures / typed escape:
- `new Brainy({ requireSubtype: false })` — last-resort: turn off the
contract entirely. Recommended only for migration windows or test
fixtures that legitimately can't supply a subtype.
- `new Brainy({ requireSubtype: { except: [NounType.Thing, ...] } })` —
per-type allowlist: strict everywhere except the listed types.
- `brain.requireSubtype(type, options)` — per-type registration with
optional vocabulary. Composes with the brain-wide flag.
Default is now `true`. Opt-out is explicit and documented; nothing
silently degrades.
TEST SWEEP
Bulk-applied `requireSubtype: false` to every `new Brainy({...})` call
site across 120 test files. Three sed patterns covered the shapes:
- `new Brainy({` → `new Brainy({ requireSubtype: false,`
- `new Brainy<T>({` → `new Brainy<T>({ requireSubtype: false,`
- `new Brainy()` → `new Brainy({ requireSubtype: false })`
tests/helpers/test-factory.ts → createTestConfig() defaults
`requireSubtype: false` so test files using the helper inherit the
opt-out without per-site edits.
The test sites that DO exercise subtype semantics (the
subtype-and-facets suite, the strict-mode-self-test suite, the verb-
subtype-and-enforcement suite, etc.) already pass real subtypes — they
were the 7.30.x acceptance tests for this contract. Those tests
continue to pass unchanged.
CHANGES
src/brainy.ts
- normalizeConfig() — `requireSubtype` default `false` → `true`.
Comment refreshed to document the three opt-out paths.
tests/* (120 files)
- Bulk-edited brain construction sites. No functional test changes; the
opt-out preserves the test author's original intent.
tests/helpers/test-factory.ts
- createTestConfig() base config gains `requireSubtype: false`.
NO-OP for consumers who were already passing subtype on every write.
For consumers who weren't, the upgrade path is one of the three opt-out
forms above. Migration recipe documented in 8.0 release notes (next
commit).
VERIFICATION
- npx tsc --noEmit: clean
- npm test: 1408 / 1409 (same pre-existing race-condition outstanding;
no other regressions from the flip)
2026-06-09 14:58:25 -07:00
brain = new Brainy ( { requireSubtype : false , storage : { type : 'memory' } } )
2025-10-22 17:36:27 -07:00
await brain . init ( )
pool = new InstancePool ( brain )
} )
describe ( 'lazy initialization' , ( ) = > {
it ( 'should not create instances until requested' , ( ) = > {
const stats = pool . getStats ( )
expect ( stats . nlpCreated ) . toBe ( false )
expect ( stats . extractorCreated ) . toBe ( false )
} )
it ( 'should create NLP instance on first access' , async ( ) = > {
const nlp = await pool . getNLP ( )
expect ( nlp ) . toBeDefined ( )
const stats = pool . getStats ( )
expect ( stats . nlpCreated ) . toBe ( true )
expect ( stats . nlpReuses ) . toBe ( 1 )
} )
it ( 'should create extractor instance on first access' , ( ) = > {
const extractor = pool . getExtractor ( )
expect ( extractor ) . toBeDefined ( )
const stats = pool . getStats ( )
expect ( stats . extractorCreated ) . toBe ( true )
expect ( stats . extractorReuses ) . toBe ( 1 )
} )
} )
describe ( 'instance reuse' , ( ) = > {
it ( 'should return same NLP instance on multiple calls' , async ( ) = > {
const nlp1 = await pool . getNLP ( )
const nlp2 = await pool . getNLP ( )
const nlp3 = await pool . getNLP ( )
expect ( nlp1 ) . toBe ( nlp2 )
expect ( nlp2 ) . toBe ( nlp3 )
const stats = pool . getStats ( )
expect ( stats . nlpReuses ) . toBe ( 3 )
} )
it ( 'should return same extractor instance on multiple calls' , ( ) = > {
const extractor1 = pool . getExtractor ( )
const extractor2 = pool . getExtractor ( )
const extractor3 = pool . getExtractor ( )
expect ( extractor1 ) . toBe ( extractor2 )
expect ( extractor2 ) . toBe ( extractor3 )
const stats = pool . getStats ( )
expect ( stats . extractorReuses ) . toBe ( 3 )
} )
it ( 'should track reuse counts correctly' , async ( ) = > {
await pool . getNLP ( )
await pool . getNLP ( )
pool . getExtractor ( )
pool . getExtractor ( )
pool . getExtractor ( )
const stats = pool . getStats ( )
expect ( stats . nlpReuses ) . toBe ( 2 )
expect ( stats . extractorReuses ) . toBe ( 3 )
} )
} )
describe ( 'initialization' , ( ) = > {
it ( 'should initialize all instances with init()' , async ( ) = > {
await pool . init ( )
expect ( pool . isInitialized ( ) ) . toBe ( true )
const stats = pool . getStats ( )
expect ( stats . nlpCreated ) . toBe ( true )
expect ( stats . extractorCreated ) . toBe ( true )
expect ( stats . initialized ) . toBe ( true )
} )
it ( 'should handle concurrent init calls safely' , async ( ) = > {
// Call init multiple times concurrently
const promises = [
pool . init ( ) ,
pool . init ( ) ,
pool . init ( )
]
await Promise . all ( promises )
// Should only initialize once
expect ( pool . isInitialized ( ) ) . toBe ( true )
} )
it ( 'should auto-initialize NLP when accessed' , async ( ) = > {
const nlp = await pool . getNLP ( )
// NLP is lazy-initialized but extractor might not be
const stats = pool . getStats ( )
expect ( stats . nlpCreated ) . toBe ( true )
expect ( stats . initialized ) . toBe ( true )
} )
it ( 'should provide sync access to NLP' , ( ) = > {
const nlp = pool . getNLPSync ( )
expect ( nlp ) . toBeDefined ( )
const stats = pool . getStats ( )
expect ( stats . nlpCreated ) . toBe ( true )
} )
} )
describe ( 'statistics' , ( ) = > {
it ( 'should track creation time' , async ( ) = > {
await pool . init ( )
const stats = pool . getStats ( )
expect ( stats . creationTime ) . toBeGreaterThanOrEqual ( 0 )
} )
it ( 'should calculate memory saved' , async ( ) = > {
// Use instances multiple times
await pool . getNLP ( )
await pool . getNLP ( )
await pool . getNLP ( )
pool . getExtractor ( )
pool . getExtractor ( )
const stats = pool . getStats ( )
expect ( stats . memorySaved ) . toBeGreaterThan ( 0 )
} )
it ( 'should reset statistics' , async ( ) = > {
await pool . getNLP ( )
pool . getExtractor ( )
pool . resetStats ( )
const stats = pool . getStats ( )
expect ( stats . nlpReuses ) . toBe ( 0 )
expect ( stats . extractorReuses ) . toBe ( 0 )
expect ( stats . creationTime ) . toBe ( 0 )
} )
it ( 'should provide string representation' , async ( ) = > {
await pool . init ( )
const str = pool . toString ( )
expect ( str ) . toContain ( 'InstancePool' )
expect ( str ) . toContain ( 'nlp=true' )
expect ( str ) . toContain ( 'extractor=true' )
} )
} )
describe ( 'memory efficiency' , ( ) = > {
it ( 'should reuse instances in loop (no memory leak)' , async ( ) = > {
const initialStats = pool . getStats ( )
// Simulate import loop
for ( let i = 0 ; i < 1000 ; i ++ ) {
const nlp = await pool . getNLP ( )
const extractor = pool . getExtractor ( )
// All iterations should get same instances
expect ( nlp ) . toBeDefined ( )
expect ( extractor ) . toBeDefined ( )
}
const finalStats = pool . getStats ( )
expect ( finalStats . nlpReuses ) . toBe ( 1000 )
expect ( finalStats . extractorReuses ) . toBe ( 1000 )
// Should have saved ~60GB of memory (1000 iterations × ~60MB)
expect ( finalStats . memorySaved ) . toBeGreaterThan ( 50 * 1024 * 1024 * 1000 ) // > 50GB
} )
it ( 'should handle rapid concurrent access' , async ( ) = > {
// Simulate concurrent row processing
const promises = [ ]
for ( let i = 0 ; i < 100 ; i ++ ) {
promises . push ( pool . getNLP ( ) )
promises . push ( Promise . resolve ( pool . getExtractor ( ) ) )
}
await Promise . all ( promises )
const stats = pool . getStats ( )
expect ( stats . nlpReuses ) . toBe ( 100 )
expect ( stats . extractorReuses ) . toBe ( 100 )
} )
} )
describe ( 'cleanup' , ( ) = > {
it ( 'should cleanup instances' , async ( ) = > {
await pool . init ( )
expect ( pool . isInitialized ( ) ) . toBe ( true )
pool . cleanup ( )
expect ( pool . isInitialized ( ) ) . toBe ( false )
const stats = pool . getStats ( )
expect ( stats . nlpCreated ) . toBe ( false )
expect ( stats . extractorCreated ) . toBe ( false )
} )
it ( 'should allow reinitialization after cleanup' , async ( ) = > {
await pool . init ( )
pool . cleanup ( )
await pool . init ( )
expect ( pool . isInitialized ( ) ) . toBe ( true )
} )
} )
describe ( 'factory function' , ( ) = > {
it ( 'should create pool with auto-init' , async ( ) = > {
const newPool = await createInstancePool ( brain , true )
expect ( newPool . isInitialized ( ) ) . toBe ( true )
} )
it ( 'should create pool without auto-init' , async ( ) = > {
const newPool = await createInstancePool ( brain , false )
expect ( newPool . isInitialized ( ) ) . toBe ( false )
} )
it ( 'should default to auto-init' , async ( ) = > {
const newPool = await createInstancePool ( brain )
expect ( newPool . isInitialized ( ) ) . toBe ( true )
} )
} )
describe ( 'error handling' , ( ) = > {
it ( 'should handle missing NLP instance gracefully' , async ( ) = > {
const emptyPool = new InstancePool ( brain )
// Should create NLP on first access
const nlp = await emptyPool . getNLP ( )
expect ( nlp ) . toBeDefined ( )
} )
it ( 'should handle missing extractor instance gracefully' , ( ) = > {
const emptyPool = new InstancePool ( brain )
// Should create extractor on first access
const extractor = emptyPool . getExtractor ( )
expect ( extractor ) . toBeDefined ( )
} )
} )
describe ( 'real-world usage' , ( ) = > {
it ( 'should work with actual NLP operations' , async ( ) = > {
const nlp = await pool . getNLP ( )
// Should be initialized and ready to use
expect ( nlp ) . toBeDefined ( )
// NLP should have init method
expect ( typeof nlp . init ) . toBe ( 'function' )
} )
it ( 'should work with actual entity extraction' , async ( ) = > {
const extractor = pool . getExtractor ( )
// Should be ready to use
expect ( extractor ) . toBeDefined ( )
// Can call extractor methods
const entities = await extractor . extract ( 'Paris is a beautiful city' , {
confidence : 0.5
} )
expect ( Array . isArray ( entities ) ) . toBe ( true )
} )
it ( 'should handle full import workflow' , async ( ) = > {
// Initialize pool
await pool . init ( )
// Simulate processing multiple rows
const rows = [
{ text : 'Paris is beautiful' } ,
{ text : 'London is historic' } ,
{ text : 'Tokyo is modern' }
]
for ( const row of rows ) {
const nlp = await pool . getNLP ( )
const extractor = pool . getExtractor ( )
// Process row - extract entities
const entities = await extractor . extract ( row . text , { confidence : 0.5 } )
expect ( nlp ) . toBeDefined ( )
expect ( extractor ) . toBeDefined ( )
expect ( entities ) . toBeDefined ( )
}
// Verify instances were reused
const stats = pool . getStats ( )
expect ( stats . nlpReuses ) . toBe ( 3 )
expect ( stats . extractorReuses ) . toBe ( 3 )
} )
} )
} )