From 1040f1ce3425e91dc0111d2aed7196f58867abbd Mon Sep 17 00:00:00 2001 From: David Snelling Date: Sun, 3 Aug 2025 10:47:55 -0700 Subject: [PATCH] feat: add verb and noun metadata handling in storage adapters - Implement saveVerbMetadata and getVerbMetadata methods for managing verb metadata. - Implement saveNounMetadata and getNounMetadata methods for managing noun metadata. - Update storage adapters to use HNSWVerb instead of GraphVerb for improved performance. - Deprecate methods that require loading metadata for edges, returning empty arrays instead. --- src/storage/adapters/baseStorageAdapter.ts | 4 + src/storage/adapters/fileSystemStorage.ts | 57 ++-- src/storage/adapters/opfsStorage.ts | 161 ++++++++--- src/storage/adapters/s3CompatibleStorage.ts | 301 +++++++++++++++----- 4 files changed, 386 insertions(+), 137 deletions(-) diff --git a/src/storage/adapters/baseStorageAdapter.ts b/src/storage/adapters/baseStorageAdapter.ts index 444d3f8e..8a4de667 100644 --- a/src/storage/adapters/baseStorageAdapter.ts +++ b/src/storage/adapters/baseStorageAdapter.ts @@ -41,6 +41,10 @@ export abstract class BaseStorageAdapter implements StorageAdapter { abstract getMetadata(id: string): Promise + abstract saveVerbMetadata(id: string, metadata: any): Promise + + abstract getVerbMetadata(id: string): Promise + abstract clear(): Promise abstract getStorageStatus(): Promise<{ diff --git a/src/storage/adapters/fileSystemStorage.ts b/src/storage/adapters/fileSystemStorage.ts index b1e145ee..1bb1f2c8 100644 --- a/src/storage/adapters/fileSystemStorage.ts +++ b/src/storage/adapters/fileSystemStorage.ts @@ -3,7 +3,7 @@ * File system storage adapter for Node.js environments */ -import { GraphVerb, HNSWNoun, StatisticsData } from '../../coreTypes.js' +import { GraphVerb, HNSWNoun, HNSWVerb, StatisticsData } from '../../coreTypes.js' import { BaseStorage, NOUNS_DIR, @@ -17,7 +17,7 @@ import { // Type aliases for better readability type HNSWNode = HNSWNoun -type Edge = GraphVerb +type Edge = HNSWVerb // Node.js modules - dynamically imported to avoid issues in browser environments let fs: any @@ -345,12 +345,7 @@ export class FileSystemStorage extends BaseStorage { return { id: parsedEdge.id, vector: parsedEdge.vector, - connections, - sourceId: parsedEdge.sourceId, - targetId: parsedEdge.targetId, - type: parsedEdge.type, - weight: parsedEdge.weight, - metadata: parsedEdge.metadata + connections } } catch (error: any) { if (error.code !== 'ENOENT') { @@ -386,12 +381,7 @@ export class FileSystemStorage extends BaseStorage { allEdges.push({ id: parsedEdge.id, vector: parsedEdge.vector, - connections, - sourceId: parsedEdge.sourceId, - targetId: parsedEdge.targetId, - type: parsedEdge.type, - weight: parsedEdge.weight, - metadata: parsedEdge.metadata + connections }) } } @@ -407,24 +397,30 @@ export class FileSystemStorage extends BaseStorage { * Get edges by source */ protected async getEdgesBySource(sourceId: string): Promise { - const edges = await this.getAllEdges() - return edges.filter((edge) => edge.sourceId === sourceId) + // This method is deprecated and would require loading metadata for each edge + // For now, return empty array since this is not efficiently implementable with new storage pattern + console.warn('getEdgesBySource is deprecated and not efficiently supported in new storage pattern') + return [] } /** * Get edges by target */ protected async getEdgesByTarget(targetId: string): Promise { - const edges = await this.getAllEdges() - return edges.filter((edge) => edge.targetId === targetId) + // This method is deprecated and would require loading metadata for each edge + // For now, return empty array since this is not efficiently implementable with new storage pattern + console.warn('getEdgesByTarget is deprecated and not efficiently supported in new storage pattern') + return [] } /** * Get edges by type */ protected async getEdgesByType(type: string): Promise { - const edges = await this.getAllEdges() - return edges.filter((edge) => edge.type === type) + // This method is deprecated and would require loading metadata for each edge + // For now, return empty array since this is not efficiently implementable with new storage pattern + console.warn('getEdgesByType is deprecated and not efficiently supported in new storage pattern') + return [] } /** @@ -751,21 +747,21 @@ export class FileSystemStorage extends BaseStorage { /** * Save a verb to storage */ - protected async saveVerb_internal(verb: GraphVerb): Promise { + protected async saveVerb_internal(verb: HNSWVerb): Promise { return this.saveEdge(verb) } /** * Get a verb from storage */ - protected async getVerb_internal(id: string): Promise { + protected async getVerb_internal(id: string): Promise { return this.getEdge(id) } /** * Get all verbs from storage */ - protected async getAllVerbs_internal(): Promise { + protected async getAllVerbs_internal(): Promise { return this.getAllEdges() } @@ -775,7 +771,10 @@ export class FileSystemStorage extends BaseStorage { protected async getVerbsBySource_internal( sourceId: string ): Promise { - return this.getEdgesBySource(sourceId) + // This method is deprecated and would require loading metadata for each edge + // For now, return empty array since this is not efficiently implementable with new storage pattern + console.warn('getVerbsBySource_internal is deprecated and not efficiently supported in new storage pattern') + return [] } /** @@ -784,14 +783,20 @@ export class FileSystemStorage extends BaseStorage { protected async getVerbsByTarget_internal( targetId: string ): Promise { - return this.getEdgesByTarget(targetId) + // This method is deprecated and would require loading metadata for each edge + // For now, return empty array since this is not efficiently implementable with new storage pattern + console.warn('getVerbsByTarget_internal is deprecated and not efficiently supported in new storage pattern') + return [] } /** * Get verbs by type */ protected async getVerbsByType_internal(type: string): Promise { - return this.getEdgesByType(type) + // This method is deprecated and would require loading metadata for each edge + // For now, return empty array since this is not efficiently implementable with new storage pattern + console.warn('getVerbsByType_internal is deprecated and not efficiently supported in new storage pattern') + return [] } /** diff --git a/src/storage/adapters/opfsStorage.ts b/src/storage/adapters/opfsStorage.ts index b7989f79..5ff0f5e1 100644 --- a/src/storage/adapters/opfsStorage.ts +++ b/src/storage/adapters/opfsStorage.ts @@ -3,17 +3,17 @@ * Provides persistent storage for the vector database using the Origin Private File System API */ -import {GraphVerb, HNSWNoun, StatisticsData} from '../../coreTypes.js' -import {BaseStorage, NOUNS_DIR, VERBS_DIR, METADATA_DIR, INDEX_DIR, STATISTICS_KEY} from '../baseStorage.js' +import {GraphVerb, HNSWNoun, HNSWVerb, StatisticsData} from '../../coreTypes.js' +import {BaseStorage, NOUNS_DIR, VERBS_DIR, METADATA_DIR, NOUN_METADATA_DIR, VERB_METADATA_DIR, INDEX_DIR, STATISTICS_KEY} from '../baseStorage.js' import '../../types/fileSystemTypes.js' // Type alias for HNSWNode type HNSWNode = HNSWNoun /** - * Type alias for GraphVerb to make the code more readable + * Type alias for HNSWVerb to make the code more readable */ -type Edge = GraphVerb +type Edge = HNSWVerb /** * Helper function to safely get a file from a FileSystemHandle @@ -41,6 +41,8 @@ export class OPFSStorage extends BaseStorage { private nounsDir: FileSystemDirectoryHandle | null = null private verbsDir: FileSystemDirectoryHandle | null = null private metadataDir: FileSystemDirectoryHandle | null = null + private nounMetadataDir: FileSystemDirectoryHandle | null = null + private verbMetadataDir: FileSystemDirectoryHandle | null = null private indexDir: FileSystemDirectoryHandle | null = null private isAvailable = false private isPersistentRequested = false @@ -94,6 +96,16 @@ export class OPFSStorage extends BaseStorage { create: true }) + // Create or get noun metadata directory + this.nounMetadataDir = await this.rootDir.getDirectoryHandle(NOUN_METADATA_DIR, { + create: true + }) + + // Create or get verb metadata directory + this.verbMetadataDir = await this.rootDir.getDirectoryHandle(VERB_METADATA_DIR, { + create: true + }) + // Create or get index directory this.indexDir = await this.rootDir.getDirectoryHandle(INDEX_DIR, { create: true @@ -345,7 +357,7 @@ export class OPFSStorage extends BaseStorage { /** * Save a verb to storage (internal implementation) */ - protected async saveVerb_internal(verb: GraphVerb): Promise { + protected async saveVerb_internal(verb: HNSWVerb): Promise { return this.saveEdge(verb) } @@ -382,7 +394,7 @@ export class OPFSStorage extends BaseStorage { /** * Get a verb from storage (internal implementation) */ - protected async getVerb_internal(id: string): Promise { + protected async getVerb_internal(id: string): Promise { return this.getEdge(id) } @@ -422,17 +434,7 @@ export class OPFSStorage extends BaseStorage { return { id: data.id, vector: data.vector, - connections, - sourceId: data.sourceId || data.source, - targetId: data.targetId || data.target, - source: data.sourceId || data.source, - target: data.targetId || data.target, - verb: data.type || data.verb, - weight: data.weight, - metadata: data.metadata, - createdAt: data.createdAt || defaultTimestamp, - updatedAt: data.updatedAt || defaultTimestamp, - createdBy: data.createdBy || defaultCreatedBy + connections } } catch (error) { // Edge not found or other error @@ -443,7 +445,7 @@ export class OPFSStorage extends BaseStorage { /** * Get all verbs from storage (internal implementation) */ - protected async getAllVerbs_internal(): Promise { + protected async getAllVerbs_internal(): Promise { return this.getAllEdges() } @@ -485,17 +487,7 @@ export class OPFSStorage extends BaseStorage { allEdges.push({ id: data.id, vector: data.vector, - connections, - sourceId: data.sourceId || data.source, - targetId: data.targetId || data.target, - source: data.sourceId || data.source, - target: data.targetId || data.target, - verb: data.type || data.verb, - weight: data.weight, - metadata: data.metadata, - createdAt: data.createdAt || defaultTimestamp, - updatedAt: data.updatedAt || defaultTimestamp, - createdBy: data.createdBy || defaultCreatedBy + connections }) } catch (error) { console.error(`Error reading edge file ${name}:`, error) @@ -513,45 +505,60 @@ export class OPFSStorage extends BaseStorage { * Get verbs by source (internal implementation) */ protected async getVerbsBySource_internal(sourceId: string): Promise { - return this.getEdgesBySource(sourceId) + // This method is deprecated and would require loading metadata for each edge + // For now, return empty array since this is not efficiently implementable with new storage pattern + console.warn('getVerbsBySource_internal is deprecated and not efficiently supported in new storage pattern') + return [] } /** * Get edges by source */ - protected async getEdgesBySource(sourceId: string): Promise { - const edges = await this.getAllEdges() - return edges.filter((edge) => (edge.sourceId || edge.source) === sourceId) + protected async getEdgesBySource(sourceId: string): Promise { + // This method is deprecated and would require loading metadata for each edge + // For now, return empty array since this is not efficiently implementable with new storage pattern + console.warn('getEdgesBySource is deprecated and not efficiently supported in new storage pattern') + return [] } /** * Get verbs by target (internal implementation) */ protected async getVerbsByTarget_internal(targetId: string): Promise { - return this.getEdgesByTarget(targetId) + // This method is deprecated and would require loading metadata for each edge + // For now, return empty array since this is not efficiently implementable with new storage pattern + console.warn('getVerbsByTarget_internal is deprecated and not efficiently supported in new storage pattern') + return [] } /** * Get edges by target */ - protected async getEdgesByTarget(targetId: string): Promise { - const edges = await this.getAllEdges() - return edges.filter((edge) => (edge.targetId || edge.target) === targetId) + protected async getEdgesByTarget(targetId: string): Promise { + // This method is deprecated and would require loading metadata for each edge + // For now, return empty array since this is not efficiently implementable with new storage pattern + console.warn('getEdgesByTarget is deprecated and not efficiently supported in new storage pattern') + return [] } /** * Get verbs by type (internal implementation) */ protected async getVerbsByType_internal(type: string): Promise { - return this.getEdgesByType(type) + // This method is deprecated and would require loading metadata for each edge + // For now, return empty array since this is not efficiently implementable with new storage pattern + console.warn('getVerbsByType_internal is deprecated and not efficiently supported in new storage pattern') + return [] } /** * Get edges by type */ - protected async getEdgesByType(type: string): Promise { - const edges = await this.getAllEdges() - return edges.filter((edge) => (edge.type || edge.verb) === type) + protected async getEdgesByType(type: string): Promise { + // This method is deprecated and would require loading metadata for each edge + // For now, return empty array since this is not efficiently implementable with new storage pattern + console.warn('getEdgesByType is deprecated and not efficiently supported in new storage pattern') + return [] } /** @@ -620,6 +627,72 @@ export class OPFSStorage extends BaseStorage { } } + /** + * Save verb metadata to storage + */ + public async saveVerbMetadata(id: string, metadata: any): Promise { + await this.ensureInitialized() + + const fileName = `${id}.json` + const fileHandle = await (this.verbMetadataDir as FileSystemDirectoryHandle).getFileHandle(fileName, { create: true }) + const writable = await (fileHandle as FileSystemFileHandle).createWritable() + await writable.write(JSON.stringify(metadata, null, 2)) + await writable.close() + } + + /** + * Get verb metadata from storage + */ + public async getVerbMetadata(id: string): Promise { + await this.ensureInitialized() + + const fileName = `${id}.json` + try { + const fileHandle = await (this.verbMetadataDir as FileSystemDirectoryHandle).getFileHandle(fileName) + const file = await safeGetFile(fileHandle) + const text = await file.text() + return JSON.parse(text) + } catch (error: any) { + if (error.name !== 'NotFoundError') { + console.error(`Error reading verb metadata ${id}:`, error) + } + return null + } + } + + /** + * Save noun metadata to storage + */ + public async saveNounMetadata(id: string, metadata: any): Promise { + await this.ensureInitialized() + + const fileName = `${id}.json` + const fileHandle = await (this.nounMetadataDir as FileSystemDirectoryHandle).getFileHandle(fileName, { create: true }) + const writable = await fileHandle.createWritable() + await writable.write(JSON.stringify(metadata, null, 2)) + await writable.close() + } + + /** + * Get noun metadata from storage + */ + public async getNounMetadata(id: string): Promise { + await this.ensureInitialized() + + const fileName = `${id}.json` + try { + const fileHandle = await (this.nounMetadataDir as FileSystemDirectoryHandle).getFileHandle(fileName) + const file = await safeGetFile(fileHandle) + const text = await file.text() + return JSON.parse(text) + } catch (error: any) { + if (error.name !== 'NotFoundError') { + console.error(`Error reading noun metadata ${id}:`, error) + } + return null + } + } + /** * Clear all data from storage */ @@ -651,6 +724,12 @@ export class OPFSStorage extends BaseStorage { // Remove all files in the metadata directory await removeDirectoryContents(this.metadataDir!) + // Remove all files in the noun metadata directory + await removeDirectoryContents(this.nounMetadataDir!) + + // Remove all files in the verb metadata directory + await removeDirectoryContents(this.verbMetadataDir!) + // Remove all files in the index directory await removeDirectoryContents(this.indexDir!) } catch (error) { diff --git a/src/storage/adapters/s3CompatibleStorage.ts b/src/storage/adapters/s3CompatibleStorage.ts index d63caae4..d45e4d3a 100644 --- a/src/storage/adapters/s3CompatibleStorage.ts +++ b/src/storage/adapters/s3CompatibleStorage.ts @@ -4,7 +4,7 @@ * including Amazon S3, Cloudflare R2, and Google Cloud Storage */ -import { GraphVerb, HNSWNoun, StatisticsData } from '../../coreTypes.js' +import { GraphVerb, HNSWNoun, HNSWVerb, StatisticsData } from '../../coreTypes.js' import { BaseStorage, NOUNS_DIR, @@ -22,7 +22,7 @@ import { CacheManager } from '../cacheManager.js' // Type aliases for better readability type HNSWNode = HNSWNoun -type Edge = GraphVerb +type Edge = HNSWVerb // Change log entry interface for tracking data modifications interface ChangeLogEntry { @@ -712,7 +712,7 @@ export class S3CompatibleStorage extends BaseStorage { /** * Save a verb to storage (internal implementation) */ - protected async saveVerb_internal(verb: GraphVerb): Promise { + protected async saveVerb_internal(verb: HNSWVerb): Promise { return this.saveEdge(verb) } @@ -751,11 +751,7 @@ export class S3CompatibleStorage extends BaseStorage { entityType: 'verb', entityId: edge.id, data: { - sourceId: edge.sourceId || edge.source, - targetId: edge.targetId || edge.target, - type: edge.type || edge.verb, - vector: edge.vector, - metadata: edge.metadata + vector: edge.vector } }) } catch (error) { @@ -767,7 +763,7 @@ export class S3CompatibleStorage extends BaseStorage { /** * Get a verb from storage (internal implementation) */ - protected async getVerb_internal(id: string): Promise { + protected async getVerb_internal(id: string): Promise { return this.getEdge(id) } @@ -815,10 +811,7 @@ export class S3CompatibleStorage extends BaseStorage { !parsedEdge || !parsedEdge.id || !parsedEdge.vector || - !parsedEdge.connections || - !(parsedEdge.sourceId || parsedEdge.source) || - !(parsedEdge.targetId || parsedEdge.target) || - !(parsedEdge.type || parsedEdge.verb) + !parsedEdge.connections ) { console.error(`Invalid edge data for ${id}:`, parsedEdge) return null @@ -830,33 +823,10 @@ export class S3CompatibleStorage extends BaseStorage { connections.set(Number(level), new Set(nodeIds as string[])) } - // Create default timestamp if not present - const defaultTimestamp = { - seconds: Math.floor(Date.now() / 1000), - nanoseconds: (Date.now() % 1000) * 1000000 - } - - // Create default createdBy if not present - const defaultCreatedBy = { - augmentation: 'unknown', - version: '1.0' - } - const edge = { id: parsedEdge.id, vector: parsedEdge.vector, - connections, - sourceId: parsedEdge.sourceId || parsedEdge.source, - targetId: parsedEdge.targetId || parsedEdge.target, - source: parsedEdge.sourceId || parsedEdge.source, - target: parsedEdge.targetId || parsedEdge.target, - verb: parsedEdge.type || parsedEdge.verb, - type: parsedEdge.type || parsedEdge.verb, - weight: parsedEdge.weight || 1.0, // Default weight if not provided - metadata: parsedEdge.metadata || {}, - createdAt: parsedEdge.createdAt || defaultTimestamp, - updatedAt: parsedEdge.updatedAt || defaultTimestamp, - createdBy: parsedEdge.createdBy || defaultCreatedBy + connections } console.log(`Successfully retrieved edge ${id}:`, edge) @@ -877,7 +847,7 @@ export class S3CompatibleStorage extends BaseStorage { * @deprecated This method is deprecated and will be removed in a future version. * It can cause memory issues with large datasets. Use getVerbsWithPagination() instead. */ - protected async getAllVerbs_internal(): Promise { + protected async getAllVerbs_internal(): Promise { console.warn('WARNING: getAllVerbs_internal() is deprecated and will be removed in a future version. Use getVerbsWithPagination() instead.') return this.getAllEdges() } @@ -1053,27 +1023,10 @@ export class S3CompatibleStorage extends BaseStorage { targetId?: string type?: string }): boolean { - // If no filter, include all edges - if (!filter.sourceId && !filter.targetId && !filter.type) { - return true - } - - // Filter by source ID - if (filter.sourceId && edge.sourceId !== filter.sourceId) { - return false - } - - // Filter by target ID - if (filter.targetId && edge.targetId !== filter.targetId) { - return false - } - - // Filter by type - if (filter.type && edge.type !== filter.type) { - return false - } - - return true + // HNSWVerb filtering is not supported since metadata is stored separately + // This method is deprecated and should not be used with the new storage pattern + console.warn('Edge filtering is deprecated and not supported with the new storage pattern') + return true // Return all edges since filtering requires metadata } /** @@ -1137,9 +1090,17 @@ export class S3CompatibleStorage extends BaseStorage { filter: edgeFilter }) - // Convert edges to verbs (they're the same in this implementation) + // Convert HNSWVerbs to GraphVerbs by combining with metadata + const graphVerbs: GraphVerb[] = [] + for (const hnswVerb of result.edges) { + const graphVerb = await this.convertHNSWVerbToGraphVerb(hnswVerb) + if (graphVerb) { + graphVerbs.push(graphVerb) + } + } + return { - items: result.edges, + items: graphVerbs, hasMore: result.hasMore, nextCursor: result.nextCursor } @@ -1157,9 +1118,11 @@ export class S3CompatibleStorage extends BaseStorage { /** * Get edges by source */ - protected async getEdgesBySource(sourceId: string): Promise { - const edges = await this.getAllEdges() - return edges.filter((edge) => (edge.sourceId || edge.source) === sourceId) + protected async getEdgesBySource(sourceId: string): Promise { + // This method is deprecated and would require loading metadata for each edge + // For now, return empty array since this is not efficiently implementable with new storage pattern + console.warn('getEdgesBySource is deprecated and not efficiently supported in new storage pattern') + return [] } /** @@ -1174,9 +1137,11 @@ export class S3CompatibleStorage extends BaseStorage { /** * Get edges by target */ - protected async getEdgesByTarget(targetId: string): Promise { - const edges = await this.getAllEdges() - return edges.filter((edge) => (edge.targetId || edge.target) === targetId) + protected async getEdgesByTarget(targetId: string): Promise { + // This method is deprecated and would require loading metadata for each edge + // For now, return empty array since this is not efficiently implementable with new storage pattern + console.warn('getEdgesByTarget is deprecated and not efficiently supported in new storage pattern') + return [] } /** @@ -1189,9 +1154,11 @@ export class S3CompatibleStorage extends BaseStorage { /** * Get edges by type */ - protected async getEdgesByType(type: string): Promise { - const edges = await this.getAllEdges() - return edges.filter((edge) => (edge.type || edge.verb) === type) + protected async getEdgesByType(type: string): Promise { + // This method is deprecated and would require loading metadata for each edge + // For now, return empty array since this is not efficiently implementable with new storage pattern + console.warn('getEdgesByType is deprecated and not efficiently supported in new storage pattern') + return [] } /** @@ -1303,6 +1270,200 @@ export class S3CompatibleStorage extends BaseStorage { } } + /** + * Save verb metadata to storage + */ + public async saveVerbMetadata(id: string, metadata: any): Promise { + await this.ensureInitialized() + + try { + console.log(`Saving verb metadata for ${id} to bucket ${this.bucketName}`) + + // Import the PutObjectCommand only when needed + const { PutObjectCommand } = await import('@aws-sdk/client-s3') + + const key = `verb-metadata/${id}.json` + const body = JSON.stringify(metadata, null, 2) + + console.log(`Saving verb metadata to key: ${key}`) + console.log(`Verb Metadata: ${body}`) + + // Save the verb metadata to S3-compatible storage + const result = await this.s3Client!.send( + new PutObjectCommand({ + Bucket: this.bucketName, + Key: key, + Body: body, + ContentType: 'application/json' + }) + ) + + console.log(`Verb metadata for ${id} saved successfully:`, result) + } catch (error) { + console.error(`Failed to save verb metadata for ${id}:`, error) + throw new Error(`Failed to save verb metadata for ${id}: ${error}`) + } + } + + /** + * Get verb metadata from storage + */ + public async getVerbMetadata(id: string): Promise { + await this.ensureInitialized() + + try { + // Import the GetObjectCommand only when needed + const { GetObjectCommand } = await import('@aws-sdk/client-s3') + + console.log(`Getting verb metadata for ${id} from bucket ${this.bucketName}`) + const key = `verb-metadata/${id}.json` + console.log(`Looking for verb metadata at key: ${key}`) + + // Try to get the verb metadata + const response = await this.s3Client!.send( + new GetObjectCommand({ + Bucket: this.bucketName, + Key: key + }) + ) + + // Check if response is null or undefined + if (!response || !response.Body) { + console.log(`No verb metadata found for ${id}`) + return null + } + + // Convert the response body to a string + const bodyContents = await response.Body.transformToString() + console.log(`Retrieved verb metadata body: ${bodyContents}`) + + // Parse the JSON string + try { + const parsedMetadata = JSON.parse(bodyContents) + console.log( + `Successfully retrieved verb metadata for ${id}:`, + parsedMetadata + ) + return parsedMetadata + } catch (parseError) { + console.error(`Failed to parse verb metadata for ${id}:`, parseError) + return null + } + } catch (error: any) { + // Check if this is a "NoSuchKey" error (object doesn't exist) + if ( + error.name === 'NoSuchKey' || + (error.message && + (error.message.includes('NoSuchKey') || + error.message.includes('not found') || + error.message.includes('does not exist'))) + ) { + console.log(`Verb metadata not found for ${id}`) + return null + } + + // For other types of errors, convert to BrainyError for better classification + throw BrainyError.fromError(error, `getVerbMetadata(${id})`) + } + } + + /** + * Save noun metadata to storage + */ + public async saveNounMetadata(id: string, metadata: any): Promise { + await this.ensureInitialized() + + try { + console.log(`Saving noun metadata for ${id} to bucket ${this.bucketName}`) + + // Import the PutObjectCommand only when needed + const { PutObjectCommand } = await import('@aws-sdk/client-s3') + + const key = `noun-metadata/${id}.json` + const body = JSON.stringify(metadata, null, 2) + + console.log(`Saving noun metadata to key: ${key}`) + console.log(`Noun Metadata: ${body}`) + + // Save the noun metadata to S3-compatible storage + const result = await this.s3Client!.send( + new PutObjectCommand({ + Bucket: this.bucketName, + Key: key, + Body: body, + ContentType: 'application/json' + }) + ) + + console.log(`Noun metadata for ${id} saved successfully:`, result) + } catch (error) { + console.error(`Failed to save noun metadata for ${id}:`, error) + throw new Error(`Failed to save noun metadata for ${id}: ${error}`) + } + } + + /** + * Get noun metadata from storage + */ + public async getNounMetadata(id: string): Promise { + await this.ensureInitialized() + + try { + // Import the GetObjectCommand only when needed + const { GetObjectCommand } = await import('@aws-sdk/client-s3') + + console.log(`Getting noun metadata for ${id} from bucket ${this.bucketName}`) + const key = `noun-metadata/${id}.json` + console.log(`Looking for noun metadata at key: ${key}`) + + // Try to get the noun metadata + const response = await this.s3Client!.send( + new GetObjectCommand({ + Bucket: this.bucketName, + Key: key + }) + ) + + // Check if response is null or undefined + if (!response || !response.Body) { + console.log(`No noun metadata found for ${id}`) + return null + } + + // Convert the response body to a string + const bodyContents = await response.Body.transformToString() + console.log(`Retrieved noun metadata body: ${bodyContents}`) + + // Parse the JSON string + try { + const parsedMetadata = JSON.parse(bodyContents) + console.log( + `Successfully retrieved noun metadata for ${id}:`, + parsedMetadata + ) + return parsedMetadata + } catch (parseError) { + console.error(`Failed to parse noun metadata for ${id}:`, parseError) + return null + } + } catch (error: any) { + // Check if this is a "NoSuchKey" error (object doesn't exist) + if ( + error.name === 'NoSuchKey' || + (error.message && + (error.message.includes('NoSuchKey') || + error.message.includes('not found') || + error.message.includes('does not exist'))) + ) { + console.log(`Noun metadata not found for ${id}`) + return null + } + + // For other types of errors, convert to BrainyError for better classification + throw BrainyError.fromError(error, `getNounMetadata(${id})`) + } + } + /** * Get metadata from storage */