diff --git a/docs/api-returns.md b/docs/api-returns.md new file mode 100644 index 00000000..a9f5e76a --- /dev/null +++ b/docs/api-returns.md @@ -0,0 +1,152 @@ +# Brainy API Return Values + +## Core Operations + +### `brain.add()` - Adding Entities + +**Returns:** `Promise` - The ID of the created entity + +```typescript +// ✅ Correct usage - add() returns the ID string directly +const id = await brain.add({ + type: 'document', + data: 'My document content' +}) + +console.log('Created entity with ID:', id) + +// Use the ID to create relationships +await brain.relate({ + from: id, + to: anotherId, + type: 'references' +}) +``` + +```typescript +// ❌ Incorrect - trying to access .id property +const result = await brain.add({ + type: 'document', + data: 'My content' +}) + +console.log(result.id) // ❌ undefined - result IS the ID, not an object! +``` + +### Getting the Full Entity + +If you need the full entity object after creation, use `brain.get()`: + +```typescript +// Add entity and get its ID +const id = await brain.add({ + type: 'document', + data: 'My content', + metadata: { label: 'Important Doc' } +}) + +// Get the full entity +const entity = await brain.get(id) +console.log(entity.id) // The ID +console.log(entity.type) // 'document' +console.log(entity.data) // 'My content' +console.log(entity.metadata) // { label: 'Important Doc', ... } +console.log(entity.vector) // The embedding vector +console.log(entity.createdAt) // Timestamp +``` + +### `brain.find()` - Finding Entities + +**Returns:** `Promise` - Array of full entity objects + +```typescript +const entities = await brain.find({ + query: 'machine learning', + limit: 10 +}) + +// Each entity has full information +for (const entity of entities) { + console.log(entity.id) + console.log(entity.type) + console.log(entity.data) + console.log(entity.metadata) +} +``` + +### `brain.relate()` - Creating Relationships + +**Returns:** `Promise` - The ID of the created relationship + +```typescript +const relationId = await brain.relate({ + from: entityId1, + to: entityId2, + type: 'references' +}) + +console.log('Created relationship with ID:', relationId) +``` + +## Data Field Behavior + +### String Data - Used for Embeddings + +When `data` is a string, it's used to generate embeddings for semantic search: + +```typescript +await brain.add({ + type: 'document', + data: 'This text will be converted to an embedding vector', + metadata: { + title: 'My Document', + year: 2024 + } +}) +``` + +### Object Data - Structured Information + +When `data` is an object, it's treated as structured data: + +```typescript +await brain.add({ + type: 'product', + data: { + name: 'Widget', + price: 29.99, + category: 'Tools' + }, + vector: precomputedVector // Must provide vector when using object data +}) +``` + +**Important:** If you provide object data without a `vector`, you must include a string somewhere for embedding generation, or the operation will fail. + +### Metadata vs Data + +- **`data`**: Primary content - used for embeddings (if string) or stored as structured data (if object) +- **`metadata`**: Auxiliary information - always stored as structured data, used for filtering + +**Best practice for labels:** +```typescript +await brain.add({ + type: 'document', + data: 'The full text content of the document...', // For semantic search + metadata: { + label: 'Quick Reference Label', // For display + author: 'John Doe', + category: 'Technical' + } +}) +``` + +## Summary + +| Method | Returns | Contains | +|--------|---------|----------| +| `brain.add()` | `string` | The ID of the created entity | +| `brain.get()` | `Entity \| null` | Full entity object with all fields | +| `brain.find()` | `Entity[]` | Array of full entity objects | +| `brain.relate()` | `string` | The ID of the created relationship | +| `brain.getRelations()` | `Relation[]` | Array of relationship objects | diff --git a/src/coreTypes.ts b/src/coreTypes.ts index 128662ad..edd59177 100644 --- a/src/coreTypes.ts +++ b/src/coreTypes.ts @@ -476,6 +476,13 @@ export interface StorageAdapter { */ getMetadataBatch?(ids: string[]): Promise> + /** + * Get noun metadata from storage + * @param id The ID of the noun + * @returns Promise that resolves to the metadata or null if not found + */ + getNounMetadata(id: string): Promise + /** * Save verb metadata to storage * @param id The ID of the verb diff --git a/src/storage/adapters/baseStorageAdapter.ts b/src/storage/adapters/baseStorageAdapter.ts index 987fd8a5..77e5de7f 100644 --- a/src/storage/adapters/baseStorageAdapter.ts +++ b/src/storage/adapters/baseStorageAdapter.ts @@ -38,6 +38,8 @@ export abstract class BaseStorageAdapter implements StorageAdapter { abstract getMetadata(id: string): Promise + abstract getNounMetadata(id: string): Promise + abstract saveVerbMetadata(id: string, metadata: any): Promise abstract getVerbMetadata(id: string): Promise diff --git a/src/storage/adapters/fileSystemStorage.ts b/src/storage/adapters/fileSystemStorage.ts index 006a52bd..c98c60a1 100644 --- a/src/storage/adapters/fileSystemStorage.ts +++ b/src/storage/adapters/fileSystemStorage.ts @@ -568,33 +568,33 @@ export class FileSystemStorage extends BaseStorage { const results = new Map() const batchSize = 10 // Process 10 files at a time - + // Process in batches to avoid overwhelming the filesystem for (let i = 0; i < ids.length; i += batchSize) { const batch = ids.slice(i, i + batchSize) - + const batchPromises = batch.map(async (id) => { try { - const metadata = await this.getMetadata(id) + const metadata = await this.getNounMetadata(id) return { id, metadata } } catch (error) { console.debug(`Failed to read metadata for ${id}:`, error) return { id, metadata: null } } }) - + const batchResults = await Promise.all(batchPromises) - + for (const { id, metadata } of batchResults) { if (metadata !== null) { results.set(id, metadata) } } - + // Small yield between batches await new Promise(resolve => setImmediate(resolve)) } - + return results } diff --git a/src/storage/adapters/opfsStorage.ts b/src/storage/adapters/opfsStorage.ts index 98ce71e5..9a73047e 100644 --- a/src/storage/adapters/opfsStorage.ts +++ b/src/storage/adapters/opfsStorage.ts @@ -633,33 +633,33 @@ export class OPFSStorage extends BaseStorage { const results = new Map() const batchSize = 10 // Process 10 files at a time - + // Process in batches to avoid overwhelming OPFS for (let i = 0; i < ids.length; i += batchSize) { const batch = ids.slice(i, i + batchSize) - + const batchPromises = batch.map(async (id) => { try { - const metadata = await this.getMetadata(id) + const metadata = await this.getNounMetadata(id) return { id, metadata } } catch (error) { console.debug(`Failed to read metadata for ${id}:`, error) return { id, metadata: null } } }) - + const batchResults = await Promise.all(batchPromises) - + for (const { id, metadata } of batchResults) { if (metadata !== null) { results.set(id, metadata) } } - + // Small yield between batches await new Promise(resolve => setImmediate(resolve)) } - + return results } diff --git a/src/utils/metadataIndex.ts b/src/utils/metadataIndex.ts index 87f6753a..eecb4827 100644 --- a/src/utils/metadataIndex.ts +++ b/src/utils/metadataIndex.ts @@ -1618,26 +1618,26 @@ export class MetadataIndexManager { prodLog.warn(`⚠️ FALLBACK: Storage adapter missing getMetadataBatch - using individual calls with concurrency limit`) metadataBatch = new Map() const CONCURRENCY_LIMIT = 3 // Very conservative limit - + for (let i = 0; i < nounIds.length; i += CONCURRENCY_LIMIT) { const batch = nounIds.slice(i, i + CONCURRENCY_LIMIT) const batchPromises = batch.map(async (id) => { try { - const metadata = await this.storage.getMetadata(id) + const metadata = await this.storage.getNounMetadata(id) return { id, metadata } } catch (error) { prodLog.debug(`Failed to read metadata for ${id}:`, error) return { id, metadata: null } } }) - + const batchResults = await Promise.all(batchPromises) for (const { id, metadata } of batchResults) { if (metadata) { metadataBatch.set(id, metadata) } } - + // Yield between batches to prevent socket exhaustion await this.yieldToEventLoop() }