chore(8.0): final pre-RC1 sweep — API consistency, named errors, orphans, zero-cast codebase

This commit is contained in:
David Snelling 2026-06-11 14:51:00 -07:00
parent 970e08c466
commit 1f7e365a4e
237 changed files with 1951 additions and 49413 deletions

View file

@ -193,6 +193,27 @@ function getVerbMetadataPath(id: string): string {
return `entities/verbs/${shard}/${id}/metadata.json`
}
/**
* Optional count capabilities probed via duck typing by getNouns()/getVerbs().
* Adapters with a native O(1) count API may implement these; they are not part
* of the BaseStorageAdapter contract, so BaseStorage feature-detects them at
* runtime before falling back to scan-based counting.
*/
interface OptionalCountCapabilities {
countNouns?: (filter?: {
nounType?: string | string[]
service?: string | string[]
metadata?: Record<string, unknown>
}) => Promise<number>
countVerbs?: (filter?: {
verbType?: string | string[]
sourceId?: string | string[]
targetId?: string | string[]
service?: string | string[]
metadata?: Record<string, unknown>
}) => Promise<number>
}
/**
* Base storage adapter that implements common functionality
* This is an abstract class that should be extended by specific storage adapters
@ -454,9 +475,14 @@ export abstract class BaseStorage extends BaseStorageAdapter {
public invalidateGraphIndex(): void {
if (this.graphIndex) {
prodLog.info('[BaseStorage] Invalidating GraphAdjacencyIndex for clear()')
// Stop any pending operations
if (typeof (this.graphIndex as any).stopAutoFlush === 'function') {
(this.graphIndex as any).stopAutoFlush()
// Stop any pending operations. stopAutoFlush is an optional capability
// of plugin-provided graph indexes (duck-typed; the built-in
// GraphAdjacencyIndex does not implement it).
const flushable = this.graphIndex as GraphAdjacencyIndex & {
stopAutoFlush?: () => void
}
if (typeof flushable.stopAutoFlush === 'function') {
flushable.stopAutoFlush()
}
this.graphIndex = undefined
this.graphIndexPromise = undefined
@ -738,7 +764,7 @@ export abstract class BaseStorage extends BaseStorageAdapter {
*
* The hook fires once per entity-visible metadata mutation noun/verb
* metadata saves and deletes, the one storage write every logical Brainy
* mutation (`add`, `update`, `delete`, `relate`, `updateRelation`,
* mutation (`add`, `update`, `remove`, `relate`, `updateRelation`,
* `unrelate`) performs exactly once per entity it touches. It does NOT fire
* for derived-index writes (HNSW node data, metadata-index chunks, LSM
* segments, statistics), so the generation counter tracks *data* mutations,
@ -1380,9 +1406,11 @@ export abstract class BaseStorage extends BaseStorageAdapter {
// First, try to get a count of total nouns (if the adapter supports it)
let totalCount: number | undefined = undefined
try {
// This is an optional method that adapters may implement
if (typeof (this as any).countNouns === 'function') {
totalCount = await (this as any).countNouns(options?.filter)
// This is an optional method that adapters may implement (duck-typed —
// see OptionalCountCapabilities)
const adapter = this as BaseStorage & OptionalCountCapabilities
if (typeof adapter.countNouns === 'function') {
totalCount = await adapter.countNouns(options?.filter)
}
} catch (countError) {
// Ignore errors from count method, it's optional
@ -1390,9 +1418,16 @@ export abstract class BaseStorage extends BaseStorageAdapter {
}
// Check if the adapter has a paginated method for getting nouns
if (typeof (this as any).getNounsWithPagination === 'function') {
// Use the adapter's paginated method - pass offset directly to adapter
const result = await (this as any).getNounsWithPagination({
if (typeof this.getNounsWithPagination === 'function') {
// Use the adapter's paginated method - pass offset directly to adapter.
// The annotation widens totalCount to optional: adapter overrides
// follow BaseStorageAdapter's contract, where totalCount may be absent.
const result: {
items: HNSWNounWithMetadata[]
totalCount?: number
hasMore: boolean
nextCursor?: string
} = await this.getNounsWithPagination({
limit,
offset, // Let the adapter handle offset for O(1) operation
cursor,
@ -1556,7 +1591,7 @@ export abstract class BaseStorage extends BaseStorageAdapter {
/**
* Get verbs with pagination (Type-first implementation with billion-scale optimizations)
*
* CRITICAL: This method is required for brain.getRelations() to work!
* CRITICAL: This method is required for brain.related() to work!
* Iterates through verb types with the same optimizations as nouns.
*
* ARCHITECTURE: Reads storage directly (not indexes) to avoid circular dependencies.
@ -1605,11 +1640,17 @@ export abstract class BaseStorage extends BaseStorageAdapter {
const filterTargetIds = filter?.targetId
? new Set(Array.isArray(filter.targetId) ? filter.targetId : [filter.targetId])
: null
const filterSubtypes = (filter as any)?.subtype
// `subtype` rides alongside the declared filter fields (Brainy's
// related() path passes it through); it's applied after metadata
// loads below, since subtype lives in verb metadata, not on the raw verb.
const subtypeFilterValue = (
filter as { verbType?: string | string[]; subtype?: string | string[] } | undefined
)?.subtype
const filterSubtypes = subtypeFilterValue
? new Set(
Array.isArray((filter as any).subtype)
? (filter as any).subtype
: [(filter as any).subtype]
Array.isArray(subtypeFilterValue)
? subtypeFilterValue
: [subtypeFilterValue]
)
: null
@ -1652,7 +1693,7 @@ export abstract class BaseStorage extends BaseStorageAdapter {
// Apply subtype filter (requires metadata — checked AFTER load)
if (filterSubtypes) {
const subtype = (metadata as any)?.subtype as string | undefined
const subtype = metadata?.subtype as string | undefined
if (!subtype || !filterSubtypes.has(subtype)) {
continue
}
@ -1725,9 +1766,12 @@ export abstract class BaseStorage extends BaseStorageAdapter {
// subtype check after the load). Subtype is not stored on the raw HNSWVerb;
// it's a metadata field. The graph-index fast paths return verbs without
// metadata, so they can't apply subtype filtering correctly.
if (options?.filter && !(options.filter as any).subtype) {
if (
options?.filter &&
!(options.filter as { verbType?: string | string[]; subtype?: string | string[] }).subtype
) {
// CRITICAL VFS FIX: If filtering by sourceId + verbType (most common VFS pattern!)
// This is the query PathResolver.getChildren() uses: getRelations({ from: dirId, type: VerbType.Contains })
// This is the query PathResolver.getChildren() uses: related({ from: dirId, type: VerbType.Contains })
if (
options.filter.sourceId &&
options.filter.verbType &&
@ -1921,9 +1965,11 @@ export abstract class BaseStorage extends BaseStorageAdapter {
// First, try to get a count of total verbs (if the adapter supports it)
let totalCount: number | undefined = undefined
try {
// This is an optional method that adapters may implement
if (typeof (this as any).countVerbs === 'function') {
totalCount = await (this as any).countVerbs(options?.filter)
// This is an optional method that adapters may implement (duck-typed —
// see OptionalCountCapabilities)
const adapter = this as BaseStorage & OptionalCountCapabilities
if (typeof adapter.countVerbs === 'function') {
totalCount = await adapter.countVerbs(options?.filter)
}
} catch (countError) {
// Ignore errors from count method, it's optional
@ -1931,13 +1977,24 @@ export abstract class BaseStorage extends BaseStorageAdapter {
}
// Check if the adapter has a paginated method for getting verbs
if (typeof (this as any).getVerbsWithPagination === 'function') {
if (typeof this.getVerbsWithPagination === 'function') {
// Use the adapter's paginated method
// Convert offset to cursor if no cursor provided (adapters use cursor for offset)
const effectiveCursor = cursor || (offset > 0 ? offset.toString() : undefined)
const result = await (this as any).getVerbsWithPagination({
// offset is carried via effectiveCursor for adapters with cursor-based
// pagination; 0 here matches the long-standing destructure default in
// getVerbsWithPagination (this call never passed offset). The
// annotation widens totalCount to optional: adapter overrides follow
// BaseStorageAdapter's contract, where totalCount may be absent.
const result: {
items: HNSWVerbWithMetadata[]
totalCount?: number
hasMore: boolean
nextCursor?: string
} = await this.getVerbsWithPagination({
limit,
offset: 0,
cursor: effectiveCursor,
filter: options?.filter
})
@ -2398,7 +2455,7 @@ export abstract class BaseStorage extends BaseStorageAdapter {
* **Use cases:**
* - VFS tree traversal (fetch all children at once)
* - brain.find() result hydration (batch load entities)
* - brain.getRelations() target entities (eliminate N+1)
* - brain.related() target entities (eliminate N+1)
* - Import operations (batch existence checks)
*
* @param ids Array of entity IDs to fetch
@ -2571,8 +2628,12 @@ export abstract class BaseStorage extends BaseStorageAdapter {
private async readBatchFromAdapter(paths: string[]): Promise<Map<string, any>> {
if (paths.length === 0) return new Map()
// Check if this class implements batch operations (will be added to cloud adapters)
const selfWithBatch = this as any
// Check if this class implements batch operations (will be added to cloud
// adapters). Duck-typed optional capability — readBatch is not part of the
// BaseStorageAdapter contract.
const selfWithBatch = this as BaseStorage & {
readBatch?: (paths: string[]) => Promise<Map<string, unknown>>
}
if (typeof selfWithBatch.readBatch === 'function') {
// Adapter has native batch support - use it
@ -2693,7 +2754,7 @@ export abstract class BaseStorage extends BaseStorageAdapter {
await this.ensureInitialized()
// Extract verb type from metadata for ID-first path
const verbType = (metadata as any).verb as VerbType | undefined
const verbType = metadata.verb as VerbType | undefined
if (!verbType) {
// Backward compatibility: fallback to old path if no verb type
@ -2729,7 +2790,7 @@ export abstract class BaseStorage extends BaseStorageAdapter {
this.decrementVerbSubtypeCount(priorEntry.verb, priorEntry.subtype)
} else if (!priorEntry && priorVerbForSubtype && !isNew) {
// Edge case: cache miss but metadata existed with a subtype (e.g. reader process startup).
const priorSubFromMeta = (existingMetadata as any)?.subtype as string | undefined
const priorSubFromMeta = existingMetadata?.subtype as string | undefined
if (priorSubFromMeta && (priorSubFromMeta !== newSubtype || priorVerbForSubtype !== verbType)) {
this.decrementVerbSubtypeCount(priorVerbForSubtype, priorSubFromMeta)
}
@ -3706,7 +3767,7 @@ export abstract class BaseStorage extends BaseStorageAdapter {
*
* **Use cases:**
* - VFS tree traversal (get Contains edges for multiple directories)
* - brain.getRelations() for multiple entities
* - brain.related() for multiple entities
* - Graph traversal (fetch neighbors of multiple nodes)
*
* @param sourceIds Array of source entity IDs