diff --git a/CHANGELOG.md b/CHANGELOG.md index 2dcafeca..61b5229d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ All notable changes to this project will be documented in this file. See [standa - Fixed critical bug where `brain.getRelations()` returned `[]` instead of all relationships - Added support for retrieving all relationships with pagination (default limit: 100) - Added string ID shorthand syntax: `brain.getRelations(entityId)` as alias for `brain.getRelations({ from: entityId })` + - **Performance**: Made pagination consistent - now ALL query patterns paginate at storage layer + - **Efficiency**: `getRelations({ from: id, limit: 10 })` now fetches only 10 instead of fetching ALL then slicing + - Fixed storage.getVerbs() offset handling - now properly converts offset to cursor for adapters - Production safety: Warns when fetching >10k relationships without filters - Fixed broken method calls in improvedNeuralAPI.ts (replaced non-existent `getVerbsForNoun` with `getRelations`) - Fixed property access bugs: `verb.target` → `verb.to`, `verb.verb` → `verb.type` diff --git a/src/brainy.ts b/src/brainy.ts index 547d7c92..698ca27a 100644 --- a/src/brainy.ts +++ b/src/brainy.ts @@ -911,58 +911,45 @@ export class Brainy implements BrainyInterface { const limit = params.limit || 100 const offset = params.offset || 0 - let relations: Relation[] = [] + // Production safety: warn for large unfiltered queries + if (!params.from && !params.to && !params.type && limit > 10000) { + console.warn( + `[Brainy] getRelations(): Fetching ${limit} relationships without filters. ` + + `Consider adding 'from', 'to', or 'type' filter for better performance.` + ) + } + + // Build filter for storage query + const filter: any = {} - // Case 1: Filter by source if (params.from) { - const verbs = await this.storage.getVerbsBySource(params.from) - relations.push(...this.verbsToRelations(verbs as any)) - } - // Case 2: Filter by target - else if (params.to) { - const verbs = await this.storage.getVerbsByTarget(params.to) - relations.push(...this.verbsToRelations(verbs as any)) - } - // Case 3: Get ALL relationships (NEW - fixes v4.1.2 bug) - else { - // Production safety: warn for large unfiltered queries - if (!params.type && limit > 10000) { - console.warn( - `[Brainy] getRelations(): Fetching ${limit} relationships without filters. ` + - `Consider adding 'type' filter or reducing 'limit' for better performance.` - ) - } - - // Fetch from storage using pagination - const result = await this.storage.getVerbs({ - pagination: { - limit: limit + offset, // Fetch enough for offset + limit - offset: 0, - cursor: params.cursor - }, - filter: params.type - ? { verbType: Array.isArray(params.type) ? params.type : [params.type] as any } - : undefined - }) - - relations = this.verbsToRelations(result.items as any) + filter.sourceId = params.from } - // Filter by type (only if not already filtered at storage level) - let filtered = relations - if (params.type && (params.from || params.to)) { - // Type filter only needed for from/to queries - const types = Array.isArray(params.type) ? params.type : [params.type] - filtered = relations.filter((r) => types.includes(r.type)) + if (params.to) { + filter.targetId = params.to + } + + if (params.type) { + filter.verbType = Array.isArray(params.type) ? params.type : [params.type] } - // Filter by service if (params.service) { - filtered = filtered.filter((r) => r.service === params.service) + filter.service = params.service } - // Apply pagination (for from/to queries, or trim excess from storage query) - return filtered.slice(offset, offset + limit) + // Fetch from storage with pagination at storage layer (efficient!) + const result = await this.storage.getVerbs({ + pagination: { + limit, + offset, + cursor: params.cursor + }, + filter: Object.keys(filter).length > 0 ? filter : undefined + }) + + // Convert to Relation format + return this.verbsToRelations(result.items as any) } // ============= SEARCH & DISCOVERY ============= diff --git a/src/storage/baseStorage.ts b/src/storage/baseStorage.ts index 16adef74..533fa8a8 100644 --- a/src/storage/baseStorage.ts +++ b/src/storage/baseStorage.ts @@ -752,14 +752,17 @@ export abstract class BaseStorage extends BaseStorageAdapter { // Check if the adapter has a paginated method for getting verbs if (typeof (this as any).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({ limit, - cursor, + cursor: effectiveCursor, filter: options?.filter }) - // Apply offset if needed (some adapters might not support offset) - const items = result.items.slice(offset) + // Items are already offset by the adapter via cursor, no need to slice + const items = result.items // CRITICAL SAFETY CHECK: Prevent infinite loops // If we have no items but hasMore is true, force hasMore to false