From 501244398ea0bc1710e0dc9640be6260525bedea Mon Sep 17 00:00:00 2001 From: dpsifr Date: Fri, 25 Jul 2025 09:44:10 -0700 Subject: [PATCH] **feat(core, storage, tests): enhance verb construction with timestamps and metadata** - **Core**: Improved verb creation logic by adding `createdAt`, `updatedAt`, and `createdBy` attributes. These fields include timestamped metadata (`seconds`, `nanoseconds`) and source augmentation/service information for better tracking. - **Storage**: Refactored `BaseStorage` methods to utilize internal variants (e.g., `saveVerb_internal`, `getNoun_internal`). Added support for new verb attributes while maintaining backward compatibility with existing data structures. - **Tests**: - Updated `s3-storage.test.ts` and `opfs-storage.test.ts` to validate changes in verb attributes such as timestamps and augmentation metadata. - Added assertions for `createdAt`, `updatedAt`, and `createdBy` fields in test cases. - **Cleanup**: Replaced ambiguous type aliases like `Edge` and `HNSWNode` with clearer equivalents (`Verb` and `HNSWNoun_internal`) for consistency across storage adapters. **Purpose**: Enhance metadata tracking and standardize attribute handling across storage and core modules to ensure accurate and consistent data throughout the system. --- src/brainyData.ts | 30 ++- src/coreTypes.ts | 5 + src/storage/adapters/fileSystemStorage.ts | 195 +++++++------- src/storage/adapters/memoryStorage.ts | 265 +++++++++++--------- src/storage/adapters/opfsStorage.ts | 183 +++++++++++--- src/storage/adapters/s3CompatibleStorage.ts | 150 ++++++++++- src/storage/baseStorage.ts | 72 +++--- src/types/graphTypes.ts | 3 +- tests/opfs-storage.test.ts | 30 ++- tests/s3-storage.test.ts | 18 +- 10 files changed, 641 insertions(+), 310 deletions(-) diff --git a/src/brainyData.ts b/src/brainyData.ts index 2314f93f..902f2ac6 100644 --- a/src/brainyData.ts +++ b/src/brainyData.ts @@ -1656,16 +1656,34 @@ export class BrainyData implements BrainyDataInterface { } } + // Get service name from options or current augmentation + const service = options.service || this.getCurrentAugmentation() + + // Create timestamp for creation/update time + const now = new Date() + const timestamp = { + seconds: Math.floor(now.getTime() / 1000), + nanoseconds: (now.getTime() % 1000) * 1000000 + } + // Create verb const verb: GraphVerb = { id, vector: verbVector, connections: new Map(), - sourceId, - targetId, - type: verbType, + sourceId: sourceId, + targetId: targetId, + source: sourceId, + target: targetId, + verb: verbType as VerbType, weight: options.weight, - metadata: options.metadata + metadata: options.metadata, + createdAt: timestamp, + updatedAt: timestamp, + createdBy: { + augmentation: service, + version: '1.0' // TODO: Get actual version from augmentation + } } // Add to index @@ -1687,8 +1705,8 @@ export class BrainyData implements BrainyDataInterface { await this.storage!.saveVerb(verb) // Track verb statistics - const service = options.service || 'default' - await this.storage!.incrementStatistic('verb', service) + const serviceForStats = options.service || 'default' + await this.storage!.incrementStatistic('verb', serviceForStats) // Update HNSW index size (excluding verbs) await this.storage!.updateHnswIndexSize(await this.getNounCount()) diff --git a/src/coreTypes.ts b/src/coreTypes.ts index ef1ba2b5..b4500cdb 100644 --- a/src/coreTypes.ts +++ b/src/coreTypes.ts @@ -82,6 +82,11 @@ export interface GraphVerb extends HNSWNoun { verb?: string // Alias for type data?: Record // Additional flexible data storage embedding?: Vector // Vector representation of the relationship + + // Timestamp and creator properties + createdAt?: { seconds: number, nanoseconds: number } // When the verb was created + updatedAt?: { seconds: number, nanoseconds: number } // When the verb was last updated + createdBy?: { augmentation: string, version: string } // Information about what created this verb } /** diff --git a/src/storage/adapters/fileSystemStorage.ts b/src/storage/adapters/fileSystemStorage.ts index 50525e33..f7ce621b 100644 --- a/src/storage/adapters/fileSystemStorage.ts +++ b/src/storage/adapters/fileSystemStorage.ts @@ -7,8 +7,8 @@ import { GraphVerb, HNSWNoun, StatisticsData } from '../../coreTypes.js' import { BaseStorage, NOUNS_DIR, VERBS_DIR, METADATA_DIR, INDEX_DIR, STATISTICS_KEY } from '../baseStorage.js' // Type aliases for better readability -type HNSWNode = HNSWNoun -type Edge = GraphVerb +type HNSWNoun_internal = HNSWNoun +type Verb = GraphVerb // Node.js modules - dynamically imported to avoid issues in browser environments let fs: any @@ -110,77 +110,77 @@ export class FileSystemStorage extends BaseStorage { } /** - * Save a node to storage + * Save a noun to storage */ - protected async saveNode(node: HNSWNode): Promise { + protected async saveNoun_internal(noun: HNSWNoun_internal): Promise { await this.ensureInitialized() // Convert connections Map to a serializable format - const serializableNode = { - ...node, - connections: this.mapToObject(node.connections, (set) => + const serializableNoun = { + ...noun, + connections: this.mapToObject(noun.connections, (set) => Array.from(set as Set) ) } - const filePath = path.join(this.nounsDir, `${node.id}.json`) - await fs.promises.writeFile(filePath, JSON.stringify(serializableNode, null, 2)) + const filePath = path.join(this.nounsDir, `${noun.id}.json`) + await fs.promises.writeFile(filePath, JSON.stringify(serializableNoun, null, 2)) } /** - * Get a node from storage + * Get a noun from storage */ - protected async getNode(id: string): Promise { + protected async getNoun_internal(id: string): Promise { await this.ensureInitialized() const filePath = path.join(this.nounsDir, `${id}.json`) try { const data = await fs.promises.readFile(filePath, 'utf-8') - const parsedNode = JSON.parse(data) + const parsedNoun = JSON.parse(data) // Convert serialized connections back to Map> const connections = new Map>() - for (const [level, nodeIds] of Object.entries(parsedNode.connections)) { - connections.set(Number(level), new Set(nodeIds as string[])) + for (const [level, nounIds] of Object.entries(parsedNoun.connections)) { + connections.set(Number(level), new Set(nounIds as string[])) } return { - id: parsedNode.id, - vector: parsedNode.vector, + id: parsedNoun.id, + vector: parsedNoun.vector, connections } } catch (error: any) { if (error.code !== 'ENOENT') { - console.error(`Error reading node ${id}:`, error) + console.error(`Error reading noun ${id}:`, error) } return null } } /** - * Get all nodes from storage + * Get all nouns from storage */ - protected async getAllNodes(): Promise { + protected async getAllNouns_internal(): Promise { await this.ensureInitialized() - const allNodes: HNSWNode[] = [] + const allNouns: HNSWNoun_internal[] = [] try { const files = await fs.promises.readdir(this.nounsDir) for (const file of files) { if (file.endsWith('.json')) { const filePath = path.join(this.nounsDir, file) const data = await fs.promises.readFile(filePath, 'utf-8') - const parsedNode = JSON.parse(data) + const parsedNoun = JSON.parse(data) // Convert serialized connections back to Map> const connections = new Map>() - for (const [level, nodeIds] of Object.entries(parsedNode.connections)) { - connections.set(Number(level), new Set(nodeIds as string[])) + for (const [level, nounIds] of Object.entries(parsedNoun.connections)) { + connections.set(Number(level), new Set(nounIds as string[])) } - allNodes.push({ - id: parsedNode.id, - vector: parsedNode.vector, + allNouns.push({ + id: parsedNoun.id, + vector: parsedNoun.vector, connections }) } @@ -190,39 +190,39 @@ export class FileSystemStorage extends BaseStorage { console.error(`Error reading directory ${this.nounsDir}:`, error) } } - return allNodes + return allNouns } /** - * Get nodes by noun type + * Get nouns by noun type * @param nounType The noun type to filter by - * @returns Promise that resolves to an array of nodes of the specified noun type + * @returns Promise that resolves to an array of nouns of the specified noun type */ - protected async getNodesByNounType(nounType: string): Promise { + protected async getNounsByNounType_internal(nounType: string): Promise { await this.ensureInitialized() - const nouns: HNSWNode[] = [] + const nouns: HNSWNoun_internal[] = [] try { const files = await fs.promises.readdir(this.nounsDir) for (const file of files) { if (file.endsWith('.json')) { const filePath = path.join(this.nounsDir, file) const data = await fs.promises.readFile(filePath, 'utf-8') - const parsedNode = JSON.parse(data) + const parsedNoun = JSON.parse(data) // Filter by noun type using metadata - const nodeId = parsedNode.id - const metadata = await this.getMetadata(nodeId) + const nounId = parsedNoun.id + const metadata = await this.getMetadata(nounId) if (metadata && metadata.noun === nounType) { // Convert serialized connections back to Map> const connections = new Map>() - for (const [level, nodeIds] of Object.entries(parsedNode.connections)) { - connections.set(Number(level), new Set(nodeIds as string[])) + for (const [level, nounIds] of Object.entries(parsedNoun.connections)) { + connections.set(Number(level), new Set(nounIds as string[])) } nouns.push({ - id: parsedNode.id, - vector: parsedNode.vector, + id: parsedNoun.id, + vector: parsedNoun.vector, connections }) } @@ -238,9 +238,9 @@ export class FileSystemStorage extends BaseStorage { } /** - * Delete a node from storage + * Delete a noun from storage */ - protected async deleteNode(id: string): Promise { + protected async deleteNoun_internal(id: string): Promise { await this.ensureInitialized() const filePath = path.join(this.nounsDir, `${id}.json`) @@ -248,95 +248,112 @@ export class FileSystemStorage extends BaseStorage { await fs.promises.unlink(filePath) } catch (error: any) { if (error.code !== 'ENOENT') { - console.error(`Error deleting node file ${filePath}:`, error) + console.error(`Error deleting noun ${id}:`, error) throw error } } } /** - * Save an edge to storage + * Save a verb to storage */ - protected async saveEdge(edge: Edge): Promise { + protected async saveVerb_internal(verb: Verb): Promise { await this.ensureInitialized() // Convert connections Map to a serializable format - const serializableEdge = { - ...edge, - connections: this.mapToObject(edge.connections, (set) => + const serializableVerb = { + ...verb, + connections: this.mapToObject(verb.connections, (set) => Array.from(set as Set) ) } - const filePath = path.join(this.verbsDir, `${edge.id}.json`) - await fs.promises.writeFile(filePath, JSON.stringify(serializableEdge, null, 2)) + const filePath = path.join(this.verbsDir, `${verb.id}.json`) + await fs.promises.writeFile(filePath, JSON.stringify(serializableVerb, null, 2)) } /** - * Get an edge from storage + * Get a verb from storage */ - protected async getEdge(id: string): Promise { + protected async getVerb_internal(id: string): Promise { await this.ensureInitialized() const filePath = path.join(this.verbsDir, `${id}.json`) try { const data = await fs.promises.readFile(filePath, 'utf-8') - const parsedEdge = JSON.parse(data) + const parsedVerb = JSON.parse(data) // Convert serialized connections back to Map> const connections = new Map>() - for (const [level, nodeIds] of Object.entries(parsedEdge.connections)) { + for (const [level, nodeIds] of Object.entries(parsedVerb.connections)) { 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' + } + return { - id: parsedEdge.id, - vector: parsedEdge.vector, + id: parsedVerb.id, + vector: parsedVerb.vector, connections, - sourceId: parsedEdge.sourceId, - targetId: parsedEdge.targetId, - type: parsedEdge.type, - weight: parsedEdge.weight, - metadata: parsedEdge.metadata + sourceId: parsedVerb.sourceId || parsedVerb.source, + targetId: parsedVerb.targetId || parsedVerb.target, + source: parsedVerb.sourceId || parsedVerb.source, + target: parsedVerb.targetId || parsedVerb.target, + verb: parsedVerb.type || parsedVerb.verb, + weight: parsedVerb.weight, + metadata: parsedVerb.metadata, + createdAt: parsedVerb.createdAt || defaultTimestamp, + updatedAt: parsedVerb.updatedAt || defaultTimestamp, + createdBy: parsedVerb.createdBy || defaultCreatedBy } } catch (error: any) { if (error.code !== 'ENOENT') { - console.error(`Error reading edge ${id}:`, error) + console.error(`Error reading verb ${id}:`, error) } return null } } /** - * Get all edges from storage + * Get all verbs from storage */ - protected async getAllEdges(): Promise { + protected async getAllVerbs_internal(): Promise { await this.ensureInitialized() - const allEdges: Edge[] = [] + const allVerbs: Verb[] = [] try { const files = await fs.promises.readdir(this.verbsDir) for (const file of files) { if (file.endsWith('.json')) { const filePath = path.join(this.verbsDir, file) const data = await fs.promises.readFile(filePath, 'utf-8') - const parsedEdge = JSON.parse(data) + const parsedVerb = JSON.parse(data) // Convert serialized connections back to Map> const connections = new Map>() - for (const [level, nodeIds] of Object.entries(parsedEdge.connections)) { + for (const [level, nodeIds] of Object.entries(parsedVerb.connections)) { connections.set(Number(level), new Set(nodeIds as string[])) } - allEdges.push({ - id: parsedEdge.id, - vector: parsedEdge.vector, + allVerbs.push({ + id: parsedVerb.id, + vector: parsedVerb.vector, connections, - sourceId: parsedEdge.sourceId, - targetId: parsedEdge.targetId, - type: parsedEdge.type, - weight: parsedEdge.weight, - metadata: parsedEdge.metadata + sourceId: parsedVerb.sourceId, + targetId: parsedVerb.targetId, + type: parsedVerb.type, + weight: parsedVerb.weight, + metadata: parsedVerb.metadata }) } } @@ -345,37 +362,37 @@ export class FileSystemStorage extends BaseStorage { console.error(`Error reading directory ${this.verbsDir}:`, error) } } - return allEdges + return allVerbs } /** - * Get edges by source + * Get verbs by source */ - protected async getEdgesBySource(sourceId: string): Promise { - const edges = await this.getAllEdges() - return edges.filter((edge) => edge.sourceId === sourceId) + protected async getVerbsBySource_internal(sourceId: string): Promise { + const verbs = await this.getAllVerbs_internal() + return verbs.filter((verb) => (verb.sourceId || verb.source) === sourceId) } /** - * Get edges by target + * Get verbs by target */ - protected async getEdgesByTarget(targetId: string): Promise { - const edges = await this.getAllEdges() - return edges.filter((edge) => edge.targetId === targetId) + protected async getVerbsByTarget_internal(targetId: string): Promise { + const verbs = await this.getAllVerbs_internal() + return verbs.filter((verb) => (verb.targetId || verb.target) === targetId) } /** - * Get edges by type + * Get verbs by type */ - protected async getEdgesByType(type: string): Promise { - const edges = await this.getAllEdges() - return edges.filter((edge) => edge.type === type) + protected async getVerbsByType_internal(type: string): Promise { + const verbs = await this.getAllVerbs_internal() + return verbs.filter((verb) => (verb.type || verb.verb) === type) } /** - * Delete an edge from storage + * Delete a verb from storage */ - protected async deleteEdge(id: string): Promise { + protected async deleteVerb_internal(id: string): Promise { await this.ensureInitialized() const filePath = path.join(this.verbsDir, `${id}.json`) @@ -383,7 +400,7 @@ export class FileSystemStorage extends BaseStorage { await fs.promises.unlink(filePath) } catch (error: any) { if (error.code !== 'ENOENT') { - console.error(`Error deleting edge file ${filePath}:`, error) + console.error(`Error deleting verb file ${filePath}:`, error) throw error } } diff --git a/src/storage/adapters/memoryStorage.ts b/src/storage/adapters/memoryStorage.ts index c44638c0..69a49d95 100644 --- a/src/storage/adapters/memoryStorage.ts +++ b/src/storage/adapters/memoryStorage.ts @@ -9,12 +9,12 @@ import { BaseStorage, STATISTICS_KEY } from '../baseStorage.js' /** * Type alias for HNSWNoun to make the code more readable */ -type HNSWNode = HNSWNoun +type HNSWNoun_internal = HNSWNoun /** * Type alias for GraphVerb to make the code more readable */ -type Edge = GraphVerb +type Verb = GraphVerb /** * In-memory storage adapter @@ -22,8 +22,8 @@ type Edge = GraphVerb */ export class MemoryStorage extends BaseStorage { // Single map of noun ID to noun - private nouns: Map = new Map() - private verbs: Map = new Map() + private nouns: Map = new Map() + private verbs: Map = new Map() private metadata: Map = new Map() private statistics: StatisticsData | null = null @@ -40,237 +40,270 @@ export class MemoryStorage extends BaseStorage { } /** - * Save a node to storage + * Save a noun to storage */ - protected async saveNode(node: HNSWNode): Promise { + protected async saveNoun_internal(noun: HNSWNoun_internal): Promise { // Create a deep copy to avoid reference issues - const nodeCopy: HNSWNode = { - id: node.id, - vector: [...node.vector], + const nounCopy: HNSWNoun_internal = { + id: noun.id, + vector: [...noun.vector], connections: new Map() } // Copy connections - for (const [level, connections] of node.connections.entries()) { - nodeCopy.connections.set(level, new Set(connections)) + for (const [level, connections] of noun.connections.entries()) { + nounCopy.connections.set(level, new Set(connections)) } - // Save the node directly in the nouns map - this.nouns.set(node.id, nodeCopy) + // Save the noun directly in the nouns map + this.nouns.set(noun.id, nounCopy) } /** - * Get a node from storage + * Get a noun from storage */ - protected async getNode(id: string): Promise { - // Get the node directly from the nouns map - const node = this.nouns.get(id) + protected async getNoun_internal(id: string): Promise { + // Get the noun directly from the nouns map + const noun = this.nouns.get(id) // If not found, return null - if (!node) { + if (!noun) { return null } // Return a deep copy to avoid reference issues - const nodeCopy: HNSWNode = { - id: node.id, - vector: [...node.vector], + const nounCopy: HNSWNoun_internal = { + id: noun.id, + vector: [...noun.vector], connections: new Map() } // Copy connections - for (const [level, connections] of node.connections.entries()) { - nodeCopy.connections.set(level, new Set(connections)) + for (const [level, connections] of noun.connections.entries()) { + nounCopy.connections.set(level, new Set(connections)) } - return nodeCopy + return nounCopy } /** - * Get all nodes from storage + * Get all nouns from storage */ - protected async getAllNodes(): Promise { - const allNodes: HNSWNode[] = [] + protected async getAllNouns_internal(): Promise { + const allNouns: HNSWNoun_internal[] = [] - // Iterate through all nodes in the nouns map - for (const [nodeId, node] of this.nouns.entries()) { + // Iterate through all nouns in the nouns map + for (const [nounId, noun] of this.nouns.entries()) { // Return a deep copy to avoid reference issues - const nodeCopy: HNSWNode = { - id: node.id, - vector: [...node.vector], + const nounCopy: HNSWNoun_internal = { + id: noun.id, + vector: [...noun.vector], connections: new Map() } // Copy connections - for (const [level, connections] of node.connections.entries()) { - nodeCopy.connections.set(level, new Set(connections)) + for (const [level, connections] of noun.connections.entries()) { + nounCopy.connections.set(level, new Set(connections)) } - allNodes.push(nodeCopy) + allNouns.push(nounCopy) } - return allNodes + return allNouns } /** - * Get nodes by noun type + * Get nouns by noun type * @param nounType The noun type to filter by - * @returns Promise that resolves to an array of nodes of the specified noun type + * @returns Promise that resolves to an array of nouns of the specified noun type */ - protected async getNodesByNounType(nounType: string): Promise { - const nodes: HNSWNode[] = [] + protected async getNounsByNounType_internal(nounType: string): Promise { + const nouns: HNSWNoun_internal[] = [] - // Iterate through all nodes and filter by noun type using metadata - for (const [nodeId, node] of this.nouns.entries()) { + // Iterate through all nouns and filter by noun type using metadata + for (const [nounId, noun] of this.nouns.entries()) { // Get the metadata to check the noun type - const metadata = await this.getMetadata(nodeId) + const metadata = await this.getMetadata(nounId) - // Include the node if its noun type matches the requested type + // Include the noun if its noun type matches the requested type if (metadata && metadata.noun === nounType) { // Return a deep copy to avoid reference issues - const nodeCopy: HNSWNode = { - id: node.id, - vector: [...node.vector], + const nounCopy: HNSWNoun_internal = { + id: noun.id, + vector: [...noun.vector], connections: new Map() } // Copy connections - for (const [level, connections] of node.connections.entries()) { - nodeCopy.connections.set(level, new Set(connections)) + for (const [level, connections] of noun.connections.entries()) { + nounCopy.connections.set(level, new Set(connections)) } - nodes.push(nodeCopy) + nouns.push(nounCopy) } } - return nodes + return nouns } /** - * Delete a node from storage + * Delete a noun from storage */ - protected async deleteNode(id: string): Promise { - // Delete the node directly from the nouns map + protected async deleteNoun_internal(id: string): Promise { this.nouns.delete(id) } /** - * Save an edge to storage + * Save a verb to storage */ - protected async saveEdge(edge: Edge): Promise { + protected async saveVerb_internal(verb: Verb): Promise { // Create a deep copy to avoid reference issues - const edgeCopy: Edge = { - id: edge.id, - vector: [...edge.vector], + const verbCopy: Verb = { + id: verb.id, + vector: [...verb.vector], connections: new Map(), - sourceId: edge.sourceId, - targetId: edge.targetId, - type: edge.type, - weight: edge.weight, - metadata: edge.metadata + sourceId: verb.sourceId, + targetId: verb.targetId, + type: verb.type, + weight: verb.weight, + metadata: verb.metadata } // Copy connections - for (const [level, connections] of edge.connections.entries()) { - edgeCopy.connections.set(level, new Set(connections)) + for (const [level, connections] of verb.connections.entries()) { + verbCopy.connections.set(level, new Set(connections)) } - // Save the edge directly in the verbs map - this.verbs.set(edge.id, edgeCopy) + // Save the verb directly in the verbs map + this.verbs.set(verb.id, verbCopy) } /** - * Get an edge from storage + * Get a verb from storage */ - protected async getEdge(id: string): Promise { - // Get the edge directly from the verbs map - const edge = this.verbs.get(id) + protected async getVerb_internal(id: string): Promise { + // Get the verb directly from the verbs map + const verb = this.verbs.get(id) // If not found, return null - if (!edge) { + if (!verb) { return null } + // 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' + } + // Return a deep copy to avoid reference issues - const edgeCopy: Edge = { - id: edge.id, - vector: [...edge.vector], + const verbCopy: Verb = { + id: verb.id, + vector: [...verb.vector], connections: new Map(), - sourceId: edge.sourceId, - targetId: edge.targetId, - type: edge.type, - weight: edge.weight, - metadata: edge.metadata + sourceId: (verb.sourceId || verb.source || ""), + targetId: (verb.targetId || verb.target || ""), + source: (verb.sourceId || verb.source || ""), + target: (verb.targetId || verb.target || ""), + verb: verb.type || verb.verb, + weight: verb.weight, + metadata: verb.metadata, + createdAt: verb.createdAt || defaultTimestamp, + updatedAt: verb.updatedAt || defaultTimestamp, + createdBy: verb.createdBy || defaultCreatedBy } // Copy connections - for (const [level, connections] of edge.connections.entries()) { - edgeCopy.connections.set(level, new Set(connections)) + for (const [level, connections] of verb.connections.entries()) { + verbCopy.connections.set(level, new Set(connections)) } - return edgeCopy + return verbCopy } /** - * Get all edges from storage + * Get all verbs from storage */ - protected async getAllEdges(): Promise { - const allEdges: Edge[] = [] + protected async getAllVerbs_internal(): Promise { + const allVerbs: Verb[] = [] + + // Iterate through all verbs in the verbs map + for (const [verbId, verb] of this.verbs.entries()) { + // 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' + } - // Iterate through all edges in the verbs map - for (const [edgeId, edge] of this.verbs.entries()) { // Return a deep copy to avoid reference issues - const edgeCopy: Edge = { - id: edge.id, - vector: [...edge.vector], + const verbCopy: Verb = { + id: verb.id, + vector: [...verb.vector], connections: new Map(), - sourceId: edge.sourceId, - targetId: edge.targetId, - type: edge.type, - weight: edge.weight, - metadata: edge.metadata + sourceId: (verb.sourceId || verb.source || ""), + targetId: (verb.targetId || verb.target || ""), + source: (verb.sourceId || verb.source || ""), + target: (verb.targetId || verb.target || ""), + verb: verb.type || verb.verb, + weight: verb.weight, + metadata: verb.metadata, + createdAt: verb.createdAt || defaultTimestamp, + updatedAt: verb.updatedAt || defaultTimestamp, + createdBy: verb.createdBy || defaultCreatedBy } // Copy connections - for (const [level, connections] of edge.connections.entries()) { - edgeCopy.connections.set(level, new Set(connections)) + for (const [level, connections] of verb.connections.entries()) { + verbCopy.connections.set(level, new Set(connections)) } - allEdges.push(edgeCopy) + allVerbs.push(verbCopy) } - return allEdges + return allVerbs } /** - * Get edges by source + * Get verbs by source */ - protected async getEdgesBySource(sourceId: string): Promise { - const edges = await this.getAllEdges() - return edges.filter((edge) => edge.sourceId === sourceId) + protected async getVerbsBySource_internal(sourceId: string): Promise { + const allVerbs = await this.getAllVerbs_internal() + return allVerbs.filter((verb: Verb) => (verb.sourceId || verb.source) === sourceId) } /** - * Get edges by target + * Get verbs by target */ - protected async getEdgesByTarget(targetId: string): Promise { - const edges = await this.getAllEdges() - return edges.filter((edge) => edge.targetId === targetId) + protected async getVerbsByTarget_internal(targetId: string): Promise { + const allVerbs = await this.getAllVerbs_internal() + return allVerbs.filter((verb: Verb) => (verb.targetId || verb.target) === targetId) } /** - * Get edges by type + * Get verbs by type */ - protected async getEdgesByType(type: string): Promise { - const edges = await this.getAllEdges() - return edges.filter((edge) => edge.type === type) + protected async getVerbsByType_internal(type: string): Promise { + const allVerbs = await this.getAllVerbs_internal() + return allVerbs.filter((verb: Verb) => (verb.type || verb.verb) === type) } /** - * Delete an edge from storage + * Delete a verb from storage */ - protected async deleteEdge(id: string): Promise { - // Delete the edge directly from the verbs map + protected async deleteVerb_internal(id: string): Promise { + // Delete the verb directly from the verbs map this.verbs.delete(id) } diff --git a/src/storage/adapters/opfsStorage.ts b/src/storage/adapters/opfsStorage.ts index 990ed6f6..f7b33807 100644 --- a/src/storage/adapters/opfsStorage.ts +++ b/src/storage/adapters/opfsStorage.ts @@ -7,6 +7,14 @@ import {GraphVerb, HNSWNoun, StatisticsData} from '../../coreTypes.js' import {BaseStorage, NOUNS_DIR, VERBS_DIR, 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 Edge = GraphVerb + /** * Helper function to safely get a file from a FileSystemHandle * This is needed because TypeScript doesn't recognize that a FileSystemHandle @@ -18,8 +26,8 @@ async function safeGetFile(handle: FileSystemHandle): Promise { } // Type aliases for better readability -type HNSWNode = HNSWNoun -type Edge = GraphVerb +type HNSWNoun_internal = HNSWNoun +type Verb = GraphVerb // Root directory name for OPFS storage const ROOT_DIR = 'opfs-vector-db' @@ -149,54 +157,54 @@ export class OPFSStorage extends BaseStorage { } /** - * Save a node to storage + * Save a noun to storage */ - protected async saveNode(node: HNSWNode): Promise { + protected async saveNoun_internal(noun: HNSWNoun_internal): Promise { await this.ensureInitialized() try { // Convert connections Map to a serializable format - const serializableNode = { - ...node, - connections: this.mapToObject(node.connections, (set) => + const serializableNoun = { + ...noun, + connections: this.mapToObject(noun.connections, (set) => Array.from(set as Set) ) } // Create or get the file for this noun - const fileHandle = await this.nounsDir!.getFileHandle(node.id, { + const fileHandle = await this.nounsDir!.getFileHandle(noun.id, { create: true }) // Write the noun data to the file const writable = await fileHandle.createWritable() - await writable.write(JSON.stringify(serializableNode)) + await writable.write(JSON.stringify(serializableNoun)) await writable.close() } catch (error) { - console.error(`Failed to save node ${node.id}:`, error) - throw new Error(`Failed to save node ${node.id}: ${error}`) + console.error(`Failed to save noun ${noun.id}:`, error) + throw new Error(`Failed to save noun ${noun.id}: ${error}`) } } /** - * Get a node from storage + * Get a noun from storage */ - protected async getNode(id: string): Promise { + protected async getNoun_internal(id: string): Promise { await this.ensureInitialized() try { - // Get the file handle for this node + // Get the file handle for this noun const fileHandle = await this.nounsDir!.getFileHandle(id) - // Read the node data from the file + // Read the noun data from the file const file = await fileHandle.getFile() const text = await file.text() const data = JSON.parse(text) // Convert serialized connections back to Map> const connections = new Map>() - for (const [level, nodeIds] of Object.entries(data.connections)) { - connections.set(Number(level), new Set(nodeIds as string[])) + for (const [level, nounIds] of Object.entries(data.connections)) { + connections.set(Number(level), new Set(nounIds as string[])) } return { @@ -205,41 +213,41 @@ export class OPFSStorage extends BaseStorage { connections } } catch (error) { - // Node not found or other error + // Noun not found or other error return null } } /** - * Get all nodes from storage + * Get all nouns from storage */ - protected async getAllNodes(): Promise { + protected async getAllNouns_internal(): Promise { await this.ensureInitialized() - const allNodes: HNSWNode[] = [] + const allNouns: HNSWNoun_internal[] = [] try { // Iterate through all files in the nouns directory for await (const [name, handle] of this.nounsDir!.entries()) { if (handle.kind === 'file') { try { - // Read the node data from the file + // Read the noun data from the file const file = await safeGetFile(handle) const text = await file.text() const data = JSON.parse(text) // Convert serialized connections back to Map> const connections = new Map>() - for (const [level, nodeIds] of Object.entries(data.connections)) { - connections.set(Number(level), new Set(nodeIds as string[])) + for (const [level, nounIds] of Object.entries(data.connections)) { + connections.set(Number(level), new Set(nounIds as string[])) } - allNodes.push({ + allNouns.push({ id: data.id, vector: data.vector, connections }) } catch (error) { - console.error(`Error reading node file ${name}:`, error) + console.error(`Error reading noun file ${name}:`, error) } } } @@ -247,7 +255,16 @@ export class OPFSStorage extends BaseStorage { console.error('Error reading nouns directory:', error) } - return allNodes + return allNouns + } + + /** + * Get nouns by noun type (internal implementation) + * @param nounType The noun type to filter by + * @returns Promise that resolves to an array of nouns of the specified noun type + */ + protected async getNounsByNounType_internal(nounType: string): Promise { + return this.getNodesByNounType(nounType) } /** @@ -299,6 +316,13 @@ export class OPFSStorage extends BaseStorage { return nodes } + /** + * Delete a noun from storage (internal implementation) + */ + protected async deleteNoun_internal(id: string): Promise { + return this.deleteNode(id) + } + /** * Delete a node from storage */ @@ -316,6 +340,13 @@ export class OPFSStorage extends BaseStorage { } } + /** + * Save a verb to storage (internal implementation) + */ + protected async saveVerb_internal(verb: GraphVerb): Promise { + return this.saveEdge(verb) + } + /** * Save an edge to storage */ @@ -346,6 +377,13 @@ export class OPFSStorage extends BaseStorage { } } + /** + * Get a verb from storage (internal implementation) + */ + protected async getVerb_internal(id: string): Promise { + return this.getEdge(id) + } + /** * Get an edge from storage */ @@ -367,15 +405,32 @@ export class OPFSStorage 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' + } + return { id: data.id, vector: data.vector, connections, - sourceId: data.sourceId, - targetId: data.targetId, - type: data.type, + 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 + metadata: data.metadata, + createdAt: data.createdAt || defaultTimestamp, + updatedAt: data.updatedAt || defaultTimestamp, + createdBy: data.createdBy || defaultCreatedBy } } catch (error) { // Edge not found or other error @@ -383,6 +438,13 @@ export class OPFSStorage extends BaseStorage { } } + /** + * Get all verbs from storage (internal implementation) + */ + protected async getAllVerbs_internal(): Promise { + return this.getAllEdges() + } + /** * Get all edges from storage */ @@ -406,15 +468,32 @@ export class OPFSStorage 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' + } + allEdges.push({ id: data.id, vector: data.vector, connections, - sourceId: data.sourceId, - targetId: data.targetId, - type: data.type, + 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 + metadata: data.metadata, + createdAt: data.createdAt || defaultTimestamp, + updatedAt: data.updatedAt || defaultTimestamp, + createdBy: data.createdBy || defaultCreatedBy }) } catch (error) { console.error(`Error reading edge file ${name}:`, error) @@ -428,12 +507,26 @@ export class OPFSStorage extends BaseStorage { return allEdges } + /** + * Get verbs by source (internal implementation) + */ + protected async getVerbsBySource_internal(sourceId: string): Promise { + return this.getEdgesBySource(sourceId) + } + /** * Get edges by source */ protected async getEdgesBySource(sourceId: string): Promise { const edges = await this.getAllEdges() - return edges.filter((edge) => edge.sourceId === sourceId) + return edges.filter((edge) => (edge.sourceId || edge.source) === sourceId) + } + + /** + * Get verbs by target (internal implementation) + */ + protected async getVerbsByTarget_internal(targetId: string): Promise { + return this.getEdgesByTarget(targetId) } /** @@ -441,7 +534,14 @@ export class OPFSStorage extends BaseStorage { */ protected async getEdgesByTarget(targetId: string): Promise { const edges = await this.getAllEdges() - return edges.filter((edge) => edge.targetId === targetId) + return edges.filter((edge) => (edge.targetId || edge.target) === targetId) + } + + /** + * Get verbs by type (internal implementation) + */ + protected async getVerbsByType_internal(type: string): Promise { + return this.getEdgesByType(type) } /** @@ -449,7 +549,14 @@ export class OPFSStorage extends BaseStorage { */ protected async getEdgesByType(type: string): Promise { const edges = await this.getAllEdges() - return edges.filter((edge) => edge.type === type) + return edges.filter((edge) => (edge.type || edge.verb) === type) + } + + /** + * Delete a verb from storage (internal implementation) + */ + protected async deleteVerb_internal(id: string): Promise { + return this.deleteEdge(id) } /** diff --git a/src/storage/adapters/s3CompatibleStorage.ts b/src/storage/adapters/s3CompatibleStorage.ts index 2e78ffac..6fd2f5d5 100644 --- a/src/storage/adapters/s3CompatibleStorage.ts +++ b/src/storage/adapters/s3CompatibleStorage.ts @@ -147,6 +147,13 @@ export class S3CompatibleStorage extends BaseStorage { } } + /** + * Save a noun to storage (internal implementation) + */ + protected async saveNoun_internal(noun: HNSWNoun): Promise { + return this.saveNode(noun) + } + /** * Save a node to storage */ @@ -209,6 +216,13 @@ export class S3CompatibleStorage extends BaseStorage { } } + /** + * Get a noun from storage (internal implementation) + */ + protected async getNoun_internal(id: string): Promise { + return this.getNode(id) + } + /** * Get a node from storage */ @@ -277,6 +291,13 @@ export class S3CompatibleStorage extends BaseStorage { } } + /** + * Get all nouns from storage (internal implementation) + */ + protected async getAllNouns_internal(): Promise { + return this.getAllNodes() + } + /** * Get all nodes from storage */ @@ -401,6 +422,15 @@ export class S3CompatibleStorage extends BaseStorage { } } + /** + * Get nouns by noun type (internal implementation) + * @param nounType The noun type to filter by + * @returns Promise that resolves to an array of nouns of the specified noun type + */ + protected async getNounsByNounType_internal(nounType: string): Promise { + return this.getNodesByNounType(nounType) + } + /** * Get nodes by noun type * @param nounType The noun type to filter by @@ -429,6 +459,13 @@ export class S3CompatibleStorage extends BaseStorage { } } + /** + * Delete a noun from storage (internal implementation) + */ + protected async deleteNoun_internal(id: string): Promise { + return this.deleteNode(id) + } + /** * Delete a node from storage */ @@ -452,6 +489,13 @@ export class S3CompatibleStorage extends BaseStorage { } } + /** + * Save a verb to storage (internal implementation) + */ + protected async saveVerb_internal(verb: GraphVerb): Promise { + return this.saveEdge(verb) + } + /** * Save an edge to storage */ @@ -485,6 +529,13 @@ export class S3CompatibleStorage extends BaseStorage { } } + /** + * Get a verb from storage (internal implementation) + */ + protected async getVerb_internal(id: string): Promise { + return this.getEdge(id) + } + /** * Get an edge from storage */ @@ -524,7 +575,9 @@ export class S3CompatibleStorage extends BaseStorage { // Ensure the parsed edge has the expected properties if (!parsedEdge || !parsedEdge.id || !parsedEdge.vector || !parsedEdge.connections || - !parsedEdge.sourceId || !parsedEdge.targetId || !parsedEdge.type) { + !(parsedEdge.sourceId || parsedEdge.source) || + !(parsedEdge.targetId || parsedEdge.target) || + !(parsedEdge.type || parsedEdge.verb)) { console.error(`Invalid edge data for ${id}:`, parsedEdge) return null } @@ -535,15 +588,33 @@ 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, - targetId: parsedEdge.targetId, - type: parsedEdge.type, + 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 || {} + metadata: parsedEdge.metadata || {}, + createdAt: parsedEdge.createdAt || defaultTimestamp, + updatedAt: parsedEdge.updatedAt || defaultTimestamp, + createdBy: parsedEdge.createdBy || defaultCreatedBy } console.log(`Successfully retrieved edge ${id}:`, edge) @@ -559,6 +630,13 @@ export class S3CompatibleStorage extends BaseStorage { } } + /** + * Get all verbs from storage (internal implementation) + */ + protected async getAllVerbs_internal(): Promise { + return this.getAllEdges() + } + /** * Get all edges from storage */ @@ -611,15 +689,33 @@ 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' + } + return { id: parsedEdge.id, vector: parsedEdge.vector, connections, - sourceId: parsedEdge.sourceId, - targetId: parsedEdge.targetId, - type: parsedEdge.type, - weight: parsedEdge.weight, - metadata: parsedEdge.metadata + 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, + metadata: parsedEdge.metadata || {}, + createdAt: parsedEdge.createdAt || defaultTimestamp, + updatedAt: parsedEdge.updatedAt || defaultTimestamp, + createdBy: parsedEdge.createdBy || defaultCreatedBy } } catch (error) { console.error(`Error getting edge from ${object.Key}:`, error) @@ -637,12 +733,26 @@ export class S3CompatibleStorage extends BaseStorage { } } + /** + * Get verbs by source (internal implementation) + */ + protected async getVerbsBySource_internal(sourceId: string): Promise { + return this.getEdgesBySource(sourceId) + } + /** * Get edges by source */ protected async getEdgesBySource(sourceId: string): Promise { const edges = await this.getAllEdges() - return edges.filter((edge) => edge.sourceId === sourceId) + return edges.filter((edge) => (edge.sourceId || edge.source) === sourceId) + } + + /** + * Get verbs by target (internal implementation) + */ + protected async getVerbsByTarget_internal(targetId: string): Promise { + return this.getEdgesByTarget(targetId) } /** @@ -650,7 +760,14 @@ export class S3CompatibleStorage extends BaseStorage { */ protected async getEdgesByTarget(targetId: string): Promise { const edges = await this.getAllEdges() - return edges.filter((edge) => edge.targetId === targetId) + return edges.filter((edge) => (edge.targetId || edge.target) === targetId) + } + + /** + * Get verbs by type (internal implementation) + */ + protected async getVerbsByType_internal(type: string): Promise { + return this.getEdgesByType(type) } /** @@ -658,7 +775,14 @@ export class S3CompatibleStorage extends BaseStorage { */ protected async getEdgesByType(type: string): Promise { const edges = await this.getAllEdges() - return edges.filter((edge) => edge.type === type) + return edges.filter((edge) => (edge.type || edge.verb) === type) + } + + /** + * Delete a verb from storage (internal implementation) + */ + protected async deleteVerb_internal(id: string): Promise { + return this.deleteEdge(id) } /** diff --git a/src/storage/baseStorage.ts b/src/storage/baseStorage.ts index 46c6eb75..95e3e967 100644 --- a/src/storage/baseStorage.ts +++ b/src/storage/baseStorage.ts @@ -40,7 +40,7 @@ export abstract class BaseStorage extends BaseStorageAdapter { */ public async saveNoun(noun: HNSWNoun): Promise { await this.ensureInitialized() - return this.saveNode(noun) + return this.saveNoun_internal(noun) } /** @@ -48,7 +48,7 @@ export abstract class BaseStorage extends BaseStorageAdapter { */ public async getNoun(id: string): Promise { await this.ensureInitialized() - return this.getNode(id) + return this.getNoun_internal(id) } /** @@ -56,7 +56,7 @@ export abstract class BaseStorage extends BaseStorageAdapter { */ public async getAllNouns(): Promise { await this.ensureInitialized() - return this.getAllNodes() + return this.getAllNouns_internal() } /** @@ -66,7 +66,7 @@ export abstract class BaseStorage extends BaseStorageAdapter { */ public async getNounsByNounType(nounType: string): Promise { await this.ensureInitialized() - return this.getNodesByNounType(nounType) + return this.getNounsByNounType_internal(nounType) } /** @@ -74,7 +74,7 @@ export abstract class BaseStorage extends BaseStorageAdapter { */ public async deleteNoun(id: string): Promise { await this.ensureInitialized() - return this.deleteNode(id) + return this.deleteNoun_internal(id) } /** @@ -82,7 +82,7 @@ export abstract class BaseStorage extends BaseStorageAdapter { */ public async saveVerb(verb: GraphVerb): Promise { await this.ensureInitialized() - return this.saveEdge(verb) + return this.saveVerb_internal(verb) } /** @@ -90,7 +90,7 @@ export abstract class BaseStorage extends BaseStorageAdapter { */ public async getVerb(id: string): Promise { await this.ensureInitialized() - return this.getEdge(id) + return this.getVerb_internal(id) } /** @@ -98,7 +98,7 @@ export abstract class BaseStorage extends BaseStorageAdapter { */ public async getAllVerbs(): Promise { await this.ensureInitialized() - return this.getAllEdges() + return this.getAllVerbs_internal() } /** @@ -106,7 +106,7 @@ export abstract class BaseStorage extends BaseStorageAdapter { */ public async getVerbsBySource(sourceId: string): Promise { await this.ensureInitialized() - return this.getEdgesBySource(sourceId) + return this.getVerbsBySource_internal(sourceId) } /** @@ -114,7 +114,7 @@ export abstract class BaseStorage extends BaseStorageAdapter { */ public async getVerbsByTarget(targetId: string): Promise { await this.ensureInitialized() - return this.getEdgesByTarget(targetId) + return this.getVerbsByTarget_internal(targetId) } /** @@ -122,7 +122,7 @@ export abstract class BaseStorage extends BaseStorageAdapter { */ public async getVerbsByType(type: string): Promise { await this.ensureInitialized() - return this.getEdgesByType(type) + return this.getVerbsByType_internal(type) } /** @@ -130,7 +130,7 @@ export abstract class BaseStorage extends BaseStorageAdapter { */ public async deleteVerb(id: string): Promise { await this.ensureInitialized() - return this.deleteEdge(id) + return this.deleteVerb_internal(id) } /** @@ -163,76 +163,76 @@ export abstract class BaseStorage extends BaseStorageAdapter { public abstract getMetadata(id: string): Promise /** - * Save a node to storage + * Save a noun to storage * This method should be implemented by each specific adapter */ - protected abstract saveNode(node: HNSWNoun): Promise + protected abstract saveNoun_internal(noun: HNSWNoun): Promise /** - * Get a node from storage + * Get a noun from storage * This method should be implemented by each specific adapter */ - protected abstract getNode(id: string): Promise + protected abstract getNoun_internal(id: string): Promise /** - * Get all nodes from storage + * Get all nouns from storage * This method should be implemented by each specific adapter */ - protected abstract getAllNodes(): Promise + protected abstract getAllNouns_internal(): Promise /** - * Get nodes by noun type + * Get nouns by noun type * This method should be implemented by each specific adapter */ - protected abstract getNodesByNounType(nounType: string): Promise + protected abstract getNounsByNounType_internal(nounType: string): Promise /** - * Delete a node from storage + * Delete a noun from storage * This method should be implemented by each specific adapter */ - protected abstract deleteNode(id: string): Promise + protected abstract deleteNoun_internal(id: string): Promise /** - * Save an edge to storage + * Save a verb to storage * This method should be implemented by each specific adapter */ - protected abstract saveEdge(edge: GraphVerb): Promise + protected abstract saveVerb_internal(verb: GraphVerb): Promise /** - * Get an edge from storage + * Get a verb from storage * This method should be implemented by each specific adapter */ - protected abstract getEdge(id: string): Promise + protected abstract getVerb_internal(id: string): Promise /** - * Get all edges from storage + * Get all verbs from storage * This method should be implemented by each specific adapter */ - protected abstract getAllEdges(): Promise + protected abstract getAllVerbs_internal(): Promise /** - * Get edges by source + * Get verbs by source * This method should be implemented by each specific adapter */ - protected abstract getEdgesBySource(sourceId: string): Promise + protected abstract getVerbsBySource_internal(sourceId: string): Promise /** - * Get edges by target + * Get verbs by target * This method should be implemented by each specific adapter */ - protected abstract getEdgesByTarget(targetId: string): Promise + protected abstract getVerbsByTarget_internal(targetId: string): Promise /** - * Get edges by type + * Get verbs by type * This method should be implemented by each specific adapter */ - protected abstract getEdgesByType(type: string): Promise + protected abstract getVerbsByType_internal(type: string): Promise /** - * Delete an edge from storage + * Delete a verb from storage * This method should be implemented by each specific adapter */ - protected abstract deleteEdge(id: string): Promise + protected abstract deleteVerb_internal(id: string): Promise /** * Helper method to convert a Map to a plain object for serialization diff --git a/src/types/graphTypes.ts b/src/types/graphTypes.ts index 7e1d7b79..39fb64a7 100644 --- a/src/types/graphTypes.ts +++ b/src/types/graphTypes.ts @@ -34,7 +34,7 @@ export interface GraphNoun { } /** - * Base interface for edges (verbs) in the graph + * Base interface for verbs in the graph * Represents relationships between nouns */ export interface GraphVerb { @@ -45,6 +45,7 @@ export interface GraphVerb { verb: VerbType // Type of relationship createdAt: Timestamp // When the verb was created updatedAt: Timestamp // When the verb was last updated + createdBy: CreatorMetadata // Information about what created this verb data?: Record // Additional flexible data storage embedding?: number[] // Vector representation of the relationship confidence?: number // Confidence score (0-1) diff --git a/tests/opfs-storage.test.ts b/tests/opfs-storage.test.ts index e54ae72d..e91f3df6 100644 --- a/tests/opfs-storage.test.ts +++ b/tests/opfs-storage.test.ts @@ -120,15 +120,25 @@ describe('OPFSStorage', () => { // Create test verb const testVector: Vector = [0.1, 0.2, 0.3, 0.4, 0.5] + const timestamp = { + seconds: Math.floor(Date.now() / 1000), + nanoseconds: (Date.now() % 1000) * 1000000 + } const testVerb = { id: 'test-verb-1', vector: testVector, connections: new Map(), - sourceId: 'source-noun-1', - targetId: 'target-noun-1', - type: 'test-relation', + source: 'source-noun-1', + target: 'target-noun-1', + verb: 'test-relation', weight: 0.75, - metadata: { description: 'Test relation' } + metadata: { description: 'Test relation' }, + createdAt: timestamp, + updatedAt: timestamp, + createdBy: { + augmentation: 'test-service', + version: '1.0' + } } // Save the verb @@ -141,11 +151,17 @@ describe('OPFSStorage', () => { expect(retrievedVerb).toBeDefined() expect(retrievedVerb?.id).toBe('test-verb-1') expect(retrievedVerb?.vector).toEqual(testVector) - expect(retrievedVerb?.sourceId).toBe('source-noun-1') - expect(retrievedVerb?.targetId).toBe('target-noun-1') - expect(retrievedVerb?.type).toBe('test-relation') + expect(retrievedVerb?.source).toBe('source-noun-1') + expect(retrievedVerb?.target).toBe('target-noun-1') + expect(retrievedVerb?.verb).toBe('test-relation') expect(retrievedVerb?.weight).toBe(0.75) expect(retrievedVerb?.metadata).toEqual({ description: 'Test relation' }) + expect(retrievedVerb?.createdAt).toEqual(timestamp) + expect(retrievedVerb?.updatedAt).toEqual(timestamp) + expect(retrievedVerb?.createdBy).toEqual({ + augmentation: 'test-service', + version: '1.0' + }) // Test getAllVerbs const allVerbs = await opfsStorage.getAllVerbs() diff --git a/tests/s3-storage.test.ts b/tests/s3-storage.test.ts index c055c276..9f3ff86d 100644 --- a/tests/s3-storage.test.ts +++ b/tests/s3-storage.test.ts @@ -222,15 +222,25 @@ describe('S3CompatibleStorage', () => { // Create test verb const testVector: Vector = [0.1, 0.2, 0.3, 0.4, 0.5] + const timestamp = { + seconds: Math.floor(Date.now() / 1000), + nanoseconds: (Date.now() % 1000) * 1000000 + } const testVerb = { id: 'test-verb-1', vector: testVector, connections: new Map(), - sourceId: 'source-noun-1', - targetId: 'target-noun-1', - type: 'test-relation', + source: 'source-noun-1', + target: 'target-noun-1', + verb: 'test-relation', weight: 0.75, - metadata: { description: 'Test relation' } + metadata: { description: 'Test relation' }, + createdAt: timestamp, + updatedAt: timestamp, + createdBy: { + augmentation: 'test-service', + version: '1.0' + } } // Save the verb