2025-05-23 10:55:20 -07:00
/ * *
* BrainyData
* Main class that provides the vector database functionality
* /
import { v4 as uuidv4 } from 'uuid'
2025-05-29 09:52:30 -07:00
import { HNSWIndex } from './hnsw/hnswIndex.js'
import { createStorage } from './storage/opfsStorage.js'
2025-05-28 15:39:14 -07:00
import {
DistanceFunction ,
2025-06-05 11:18:20 -07:00
GraphVerb ,
2025-05-28 15:39:14 -07:00
EmbeddingFunction ,
2025-06-05 11:18:20 -07:00
HNSWConfig , HNSWNoun ,
2025-05-28 15:39:14 -07:00
SearchResult ,
StorageAdapter ,
Vector ,
VectorDocument
2025-05-29 09:52:30 -07:00
} from './coreTypes.js'
import { cosineDistance , defaultEmbeddingFunction , euclideanDistance } from './utils/index.js'
2025-06-05 11:18:20 -07:00
import { NounType , VerbType , GraphNoun } from './types/graphTypes.js'
2025-05-23 10:55:20 -07:00
export interface BrainyDataConfig {
/ * *
* HNSW index configuration
* /
hnsw? : Partial < HNSWConfig >
/ * *
* Distance function to use for similarity calculations
* /
distanceFunction? : DistanceFunction
/ * *
* Custom storage adapter ( if not provided , will use OPFS or memory storage )
* /
storageAdapter? : StorageAdapter
2025-06-04 10:01:59 -07:00
/ * *
* Storage configuration options
* These will be passed to createStorage if storageAdapter is not provided
* /
storage ? : {
requestPersistentStorage? : boolean ;
r2Storage ? : {
bucketName? : string ;
accountId? : string ;
accessKeyId? : string ;
secretAccessKey? : string ;
} ;
s3Storage ? : {
bucketName? : string ;
accessKeyId? : string ;
secretAccessKey? : string ;
region? : string ;
} ;
gcsStorage ? : {
bucketName? : string ;
accessKeyId? : string ;
secretAccessKey? : string ;
endpoint? : string ;
} ;
customS3Storage ? : {
bucketName? : string ;
accessKeyId? : string ;
secretAccessKey? : string ;
endpoint? : string ;
region? : string ;
} ;
forceFileSystemStorage? : boolean ;
forceMemoryStorage? : boolean ;
}
2025-05-23 10:55:20 -07:00
/ * *
* Embedding function to convert data to vectors
* /
embeddingFunction? : EmbeddingFunction
2025-05-28 11:37:28 -07:00
/ * *
* Request persistent storage when running in a browser
* This will prompt the user for permission to use persistent storage
2025-06-04 10:01:59 -07:00
* @deprecated Use storage . requestPersistentStorage instead
2025-05-28 11:37:28 -07:00
* /
requestPersistentStorage? : boolean
2025-06-04 10:01:59 -07:00
/ * *
* Set the database to read - only mode
* When true , all write operations will throw an error
* /
readOnly? : boolean
2025-05-23 10:55:20 -07:00
}
export class BrainyData < T = any > {
private index : HNSWIndex
private storage : StorageAdapter | null = null
private isInitialized = false
private embeddingFunction : EmbeddingFunction
2025-05-28 11:37:28 -07:00
private requestPersistentStorage : boolean
2025-06-04 10:01:59 -07:00
private readOnly : boolean
private storageConfig : BrainyDataConfig [ 'storage' ] = { }
2025-05-23 10:55:20 -07:00
/ * *
* Create a new vector database
* /
constructor ( config : BrainyDataConfig = { } ) {
// Initialize HNSW index
this . index = new HNSWIndex (
config . hnsw ,
config . distanceFunction || cosineDistance
)
// Set storage if provided, otherwise it will be initialized in init()
this . storage = config . storageAdapter || null
// Set embedding function if provided, otherwise use default
this . embeddingFunction = config . embeddingFunction || defaultEmbeddingFunction
2025-05-28 11:37:28 -07:00
2025-06-04 10:01:59 -07:00
// Set persistent storage request flag (support both new and deprecated options)
this . requestPersistentStorage =
( config . storage ? . requestPersistentStorage !== undefined )
? config . storage . requestPersistentStorage
: ( config . requestPersistentStorage || false )
// Set read-only flag
this . readOnly = config . readOnly || false
// Store storage configuration for later use in init()
this . storageConfig = config . storage || { }
}
/ * *
* Check if the database is in read - only mode and throw an error if it is
* @throws Error if the database is in read - only mode
* /
private checkReadOnly ( ) : void {
if ( this . readOnly ) {
throw new Error ( 'Cannot perform write operation: database is in read-only mode' )
}
2025-05-23 10:55:20 -07:00
}
/ * *
* Initialize the database
* Loads existing data from storage if available
* /
public async init ( ) : Promise < void > {
if ( this . isInitialized ) {
return
}
try {
// Initialize storage if not provided in constructor
if ( ! this . storage ) {
2025-06-04 10:01:59 -07:00
// Combine storage config with requestPersistentStorage for backward compatibility
const storageOptions = {
. . . this . storageConfig ,
2025-05-28 11:37:28 -07:00
requestPersistentStorage : this.requestPersistentStorage
2025-06-04 10:01:59 -07:00
} ;
this . storage = await createStorage ( storageOptions ) ;
2025-05-23 10:55:20 -07:00
}
// Initialize storage
await this . storage ! . init ( )
2025-06-05 11:18:20 -07:00
// Load all nouns from storage
const nouns : HNSWNoun [ ] = await this . storage ! . getAllNouns ( )
2025-05-23 10:55:20 -07:00
2025-06-05 11:18:20 -07:00
// Clear the index and add all nouns
2025-05-23 10:55:20 -07:00
this . index . clear ( )
2025-06-05 11:18:20 -07:00
for ( const noun of nouns ) {
2025-05-23 10:55:20 -07:00
// Add to index
this . index . addItem ( {
2025-06-05 11:18:20 -07:00
id : noun.id ,
vector : noun.vector
2025-05-23 10:55:20 -07:00
} )
}
this . isInitialized = true
} catch ( error ) {
2025-05-28 16:02:20 -07:00
console . error ( 'Failed to initialize BrainyData:' , error )
throw new Error ( ` Failed to initialize BrainyData: ${ error } ` )
2025-05-23 10:55:20 -07:00
}
}
/ * *
* Add a vector or data to the database
* If the input is not a vector , it will be converted using the embedding function
* @param vectorOrData Vector or data to add
* @param metadata Optional metadata to associate with the vector
* @param options Additional options
* @returns The ID of the added vector
* /
public async add (
vectorOrData : Vector | any ,
metadata? : T ,
options : {
forceEmbed? : boolean // Force using the embedding function even if input is a vector
} = { }
) : Promise < string > {
await this . ensureInitialized ( )
2025-06-04 10:01:59 -07:00
// Check if database is in read-only mode
this . checkReadOnly ( )
2025-05-23 10:55:20 -07:00
try {
let vector : Vector
// Check if input is already a vector
if (
Array . isArray ( vectorOrData ) &&
vectorOrData . every ( ( item ) = > typeof item === 'number' ) &&
! options . forceEmbed
) {
// Input is already a vector
vector = vectorOrData
} else {
// Input needs to be vectorized
try {
vector = await this . embeddingFunction ( vectorOrData )
} catch ( embedError ) {
throw new Error ( ` Failed to vectorize data: ${ embedError } ` )
}
}
// Check if vector is defined
if ( ! vector ) {
throw new Error ( 'Vector is undefined or null' )
}
// Generate ID if isn't provided
const id = uuidv4 ( )
// Add to index
this . index . addItem ( { id , vector } )
2025-06-05 11:18:20 -07:00
// Get the noun from the index
const noun = this . index . getNodes ( ) . get ( id )
2025-05-23 10:55:20 -07:00
2025-06-05 11:18:20 -07:00
if ( ! noun ) {
throw new Error ( ` Failed to retrieve newly created noun with ID ${ id } ` )
2025-05-23 10:55:20 -07:00
}
2025-06-05 11:18:20 -07:00
// Save noun to storage
await this . storage ! . saveNoun ( noun )
2025-05-23 10:55:20 -07:00
// Save metadata if provided
if ( metadata !== undefined ) {
2025-06-05 11:18:20 -07:00
// Validate noun type if metadata is for a GraphNoun
if ( metadata && typeof metadata === 'object' && 'noun' in metadata ) {
const nounType = ( metadata as any ) . noun ;
// Check if the noun type is valid
const isValidNounType = Object . values ( NounType ) . includes ( nounType ) ;
if ( ! isValidNounType ) {
console . warn ( ` Invalid noun type: ${ nounType } . Falling back to GraphNoun. ` ) ;
// Set a default noun type
( metadata as any ) . noun = NounType . Concept ;
}
}
2025-05-23 10:55:20 -07:00
await this . storage ! . saveMetadata ( id , metadata )
}
return id
} catch ( error ) {
console . error ( 'Failed to add vector:' , error )
throw new Error ( ` Failed to add vector: ${ error } ` )
}
}
/ * *
* Add multiple vectors or data items to the database
* @param items Array of items to add
* @param options Additional options
* @returns Array of IDs for the added items
* /
public async addBatch (
2025-05-28 15:39:14 -07:00
items : Array < {
vectorOrData : Vector | any ;
metadata? : T
2025-05-23 10:55:20 -07:00
} > ,
options : {
forceEmbed? : boolean // Force using the embedding function even if input is a vector
} = { }
) : Promise < string [ ] > {
await this . ensureInitialized ( )
2025-06-04 10:01:59 -07:00
// Check if database is in read-only mode
this . checkReadOnly ( )
2025-05-23 10:55:20 -07:00
const ids : string [ ] = [ ]
try {
for ( const item of items ) {
const id = await this . add ( item . vectorOrData , item . metadata , options )
ids . push ( id )
}
return ids
} catch ( error ) {
console . error ( 'Failed to add batch of items:' , error )
throw new Error ( ` Failed to add batch of items: ${ error } ` )
}
}
/ * *
2025-06-02 12:23:11 -07:00
* Search for similar vectors within specific noun types
2025-05-23 10:55:20 -07:00
* @param queryVectorOrData Query vector or data to search for
* @param k Number of results to return
2025-06-02 12:23:11 -07:00
* @param nounTypes Array of noun types to search within , or null to search all
2025-05-23 10:55:20 -07:00
* @param options Additional options
* @returns Array of search results
* /
2025-06-02 12:23:11 -07:00
public async searchByNounTypes (
2025-05-23 10:55:20 -07:00
queryVectorOrData : Vector | any ,
k : number = 10 ,
2025-06-02 12:23:11 -07:00
nounTypes : string [ ] | null = null ,
2025-05-23 10:55:20 -07:00
options : {
forceEmbed? : boolean // Force using the embedding function even if input is a vector
} = { }
) : Promise < SearchResult < T > [ ] > {
await this . ensureInitialized ( )
try {
let queryVector : Vector
// Check if input is already a vector
if (
Array . isArray ( queryVectorOrData ) &&
queryVectorOrData . every ( ( item ) = > typeof item === 'number' ) &&
! options . forceEmbed
) {
// Input is already a vector
queryVector = queryVectorOrData
} else {
// Input needs to be vectorized
try {
queryVector = await this . embeddingFunction ( queryVectorOrData )
} catch ( embedError ) {
throw new Error ( ` Failed to vectorize query data: ${ embedError } ` )
}
}
// Check if query vector is defined
if ( ! queryVector ) {
throw new Error ( 'Query vector is undefined or null' )
}
2025-06-05 11:18:20 -07:00
// If no noun types specified, search all nouns
2025-06-02 12:23:11 -07:00
if ( ! nounTypes || nounTypes . length === 0 ) {
// Search in the index
const results = this . index . search ( queryVector , k )
// Get metadata for each result
const searchResults : SearchResult < T > [ ] = [ ]
2025-05-23 10:55:20 -07:00
2025-06-02 12:23:11 -07:00
for ( const [ id , score ] of results ) {
2025-06-05 11:18:20 -07:00
const noun = this . index . getNodes ( ) . get ( id )
if ( ! noun ) {
2025-06-02 12:23:11 -07:00
continue
}
2025-05-23 10:55:20 -07:00
2025-06-02 12:23:11 -07:00
const metadata = await this . storage ! . getMetadata ( id )
searchResults . push ( {
id ,
score ,
2025-06-05 11:18:20 -07:00
vector : noun.vector ,
2025-06-02 12:23:11 -07:00
metadata
} )
2025-05-23 10:55:20 -07:00
}
2025-06-02 12:23:11 -07:00
return searchResults
} else {
2025-06-05 11:18:20 -07:00
// Get nouns for each noun type in parallel
const nounPromises = nounTypes . map ( nounType = > this . storage ! . getNounsByNounType ( nounType ) )
const nounArrays = await Promise . all ( nounPromises )
// Combine all nouns
const nouns : HNSWNoun [ ] = [ ]
for ( const nounArray of nounArrays ) {
nouns . push ( . . . nounArray )
2025-06-02 12:23:11 -07:00
}
2025-05-23 10:55:20 -07:00
2025-06-05 11:18:20 -07:00
// Calculate distances for each noun
2025-06-02 12:23:11 -07:00
const results : Array < [ string , number ] > = [ ]
2025-06-05 11:18:20 -07:00
for ( const noun of nouns ) {
const distance = this . index . getDistanceFunction ( ) ( queryVector , noun . vector )
results . push ( [ noun . id , distance ] )
2025-06-02 12:23:11 -07:00
}
2025-05-23 10:55:20 -07:00
2025-06-02 12:23:11 -07:00
// Sort by distance (ascending)
results . sort ( ( a , b ) = > a [ 1 ] - b [ 1 ] )
// Take top k results
const topResults = results . slice ( 0 , k )
// Get metadata for each result
const searchResults : SearchResult < T > [ ] = [ ]
for ( const [ id , score ] of topResults ) {
2025-06-05 11:18:20 -07:00
const noun = nouns . find ( n = > n . id === id )
if ( ! noun ) {
2025-06-02 12:23:11 -07:00
continue
}
const metadata = await this . storage ! . getMetadata ( id )
searchResults . push ( {
id ,
score ,
2025-06-05 11:18:20 -07:00
vector : noun.vector ,
2025-06-02 12:23:11 -07:00
metadata
} )
}
return searchResults
}
2025-05-23 10:55:20 -07:00
} catch ( error ) {
2025-06-02 12:23:11 -07:00
console . error ( 'Failed to search vectors by noun types:' , error )
throw new Error ( ` Failed to search vectors by noun types: ${ error } ` )
2025-05-23 10:55:20 -07:00
}
}
2025-06-02 12:23:11 -07:00
/ * *
* Search for similar vectors
* @param queryVectorOrData Query vector or data to search for
* @param k Number of results to return
* @param options Additional options
* @returns Array of search results
* /
public async search (
queryVectorOrData : Vector | any ,
k : number = 10 ,
options : {
forceEmbed? : boolean , // Force using the embedding function even if input is a vector
2025-06-05 08:26:40 -07:00
nounTypes? : string [ ] , // Optional array of noun types to search within
includeVerbs? : boolean // Whether to include associated GraphVerbs in the results
2025-06-02 12:23:11 -07:00
} = { }
) : Promise < SearchResult < T > [ ] > {
2025-06-05 08:26:40 -07:00
// If input is a string and not a vector, automatically vectorize it
let queryToUse = queryVectorOrData ;
if ( typeof queryVectorOrData === 'string' && ! options . forceEmbed ) {
queryToUse = await this . embed ( queryVectorOrData ) ;
options . forceEmbed = false ; // Already embedded, don't force again
}
2025-06-02 12:23:11 -07:00
// If noun types are specified, use searchByNounTypes
2025-06-05 08:26:40 -07:00
let searchResults ;
2025-06-02 12:23:11 -07:00
if ( options . nounTypes && options . nounTypes . length > 0 ) {
2025-06-05 08:26:40 -07:00
searchResults = await this . searchByNounTypes ( queryToUse , k , options . nounTypes , {
2025-06-02 12:23:11 -07:00
forceEmbed : options.forceEmbed
2025-06-05 08:26:40 -07:00
} ) ;
} else {
// Otherwise, search all GraphNouns
searchResults = await this . searchByNounTypes ( queryToUse , k , null , {
forceEmbed : options.forceEmbed
} ) ;
2025-06-02 12:23:11 -07:00
}
2025-06-05 08:26:40 -07:00
// If includeVerbs is true, retrieve associated GraphVerbs for each result
if ( options . includeVerbs && this . storage ) {
for ( const result of searchResults ) {
try {
2025-06-05 11:18:20 -07:00
// Get outgoing verbs for this noun
const outgoingVerbs = await this . storage . getVerbsBySource ( result . id ) ;
2025-06-05 08:26:40 -07:00
2025-06-05 11:18:20 -07:00
// Get incoming verbs for this noun
const incomingVerbs = await this . storage . getVerbsByTarget ( result . id ) ;
2025-06-05 08:26:40 -07:00
2025-06-05 11:18:20 -07:00
// Combine all verbs
const allVerbs = [ . . . outgoingVerbs , . . . incomingVerbs ] ;
2025-06-05 08:26:40 -07:00
2025-06-05 11:18:20 -07:00
// Add verbs to the result metadata
2025-06-05 08:26:40 -07:00
if ( ! result . metadata ) {
result . metadata = { } as T ;
}
2025-06-05 11:18:20 -07:00
// Add the verbs to the metadata
( result . metadata as any ) . associatedVerbs = allVerbs ;
2025-06-05 08:26:40 -07:00
} catch ( error ) {
console . warn ( ` Failed to retrieve verbs for noun ${ result . id } : ` , error ) ;
}
}
}
return searchResults ;
2025-06-02 12:23:11 -07:00
}
2025-05-23 10:55:20 -07:00
/ * *
* Get a vector by ID
* /
public async get ( id : string ) : Promise < VectorDocument < T > | null > {
await this . ensureInitialized ( )
try {
2025-06-05 11:18:20 -07:00
// Get noun from index
const noun = this . index . getNodes ( ) . get ( id )
if ( ! noun ) {
2025-05-23 10:55:20 -07:00
return null
}
// Get metadata
const metadata = await this . storage ! . getMetadata ( id )
return {
id ,
2025-06-05 11:18:20 -07:00
vector : noun.vector ,
2025-05-23 10:55:20 -07:00
metadata
}
} catch ( error ) {
console . error ( ` Failed to get vector ${ id } : ` , error )
throw new Error ( ` Failed to get vector ${ id } : ${ error } ` )
}
}
/ * *
* Delete a vector by ID
* /
public async delete ( id : string ) : Promise < boolean > {
await this . ensureInitialized ( )
2025-06-04 10:01:59 -07:00
// Check if database is in read-only mode
this . checkReadOnly ( )
2025-05-23 10:55:20 -07:00
try {
// Remove from index
const removed = this . index . removeItem ( id )
if ( ! removed ) {
return false
}
// Remove from storage
2025-06-05 11:18:20 -07:00
await this . storage ! . deleteNoun ( id )
2025-05-23 10:55:20 -07:00
// Try to remove metadata (ignore errors)
try {
await this . storage ! . saveMetadata ( id , null )
} catch ( error ) {
// Ignore
}
return true
} catch ( error ) {
console . error ( ` Failed to delete vector ${ id } : ` , error )
throw new Error ( ` Failed to delete vector ${ id } : ${ error } ` )
}
}
/ * *
* Update metadata for a vector
* /
public async updateMetadata ( id : string , metadata : T ) : Promise < boolean > {
await this . ensureInitialized ( )
2025-06-04 10:01:59 -07:00
// Check if database is in read-only mode
this . checkReadOnly ( )
2025-05-23 10:55:20 -07:00
try {
// Check if a vector exists
2025-06-05 11:18:20 -07:00
const noun = this . index . getNodes ( ) . get ( id )
if ( ! noun ) {
2025-05-23 10:55:20 -07:00
return false
}
2025-06-05 11:18:20 -07:00
// Validate noun type if metadata is for a GraphNoun
if ( metadata && typeof metadata === 'object' && 'noun' in metadata ) {
const nounType = ( metadata as any ) . noun ;
// Check if the noun type is valid
const isValidNounType = Object . values ( NounType ) . includes ( nounType ) ;
if ( ! isValidNounType ) {
console . warn ( ` Invalid noun type: ${ nounType } . Falling back to GraphNoun. ` ) ;
// Set a default noun type
( metadata as any ) . noun = NounType . Concept ;
}
}
2025-05-23 10:55:20 -07:00
// Update metadata
await this . storage ! . saveMetadata ( id , metadata )
return true
} catch ( error ) {
console . error ( ` Failed to update metadata for vector ${ id } : ` , error )
throw new Error ( ` Failed to update metadata for vector ${ id } : ${ error } ` )
}
}
/ * *
2025-06-05 11:18:20 -07:00
* Add a verb between two nouns
2025-06-05 08:26:40 -07:00
* If metadata is provided and vector is not , the metadata will be vectorized using the embedding function
2025-05-23 10:55:20 -07:00
* /
2025-06-05 11:18:20 -07:00
public async addVerb (
2025-05-23 10:55:20 -07:00
sourceId : string ,
targetId : string ,
vector? : Vector ,
options : {
type ? : string
weight? : number
metadata? : any
2025-06-05 08:26:40 -07:00
forceEmbed? : boolean // Force using the embedding function for metadata even if vector is provided
2025-05-23 10:55:20 -07:00
} = { }
) : Promise < string > {
await this . ensureInitialized ( )
2025-06-04 10:01:59 -07:00
// Check if database is in read-only mode
this . checkReadOnly ( )
2025-05-23 10:55:20 -07:00
try {
2025-06-05 11:18:20 -07:00
// Check if source and target nouns exist
const sourceNoun = this . index . getNodes ( ) . get ( sourceId )
const targetNoun = this . index . getNodes ( ) . get ( targetId )
2025-05-23 10:55:20 -07:00
2025-06-05 11:18:20 -07:00
if ( ! sourceNoun ) {
throw new Error ( ` Source noun with ID ${ sourceId } not found ` )
2025-05-23 10:55:20 -07:00
}
2025-06-05 11:18:20 -07:00
if ( ! targetNoun ) {
throw new Error ( ` Target noun with ID ${ targetId } not found ` )
2025-05-23 10:55:20 -07:00
}
2025-06-05 11:18:20 -07:00
// Generate ID for the verb
2025-05-23 10:55:20 -07:00
const id = uuidv4 ( )
2025-06-05 11:18:20 -07:00
let verbVector : Vector
2025-06-05 08:26:40 -07:00
// If metadata is provided and no vector is provided or forceEmbed is true, vectorize the metadata
if ( options . metadata && ( ! vector || options . forceEmbed ) ) {
try {
2025-06-05 11:18:20 -07:00
verbVector = await this . embeddingFunction ( options . metadata )
2025-06-05 08:26:40 -07:00
} catch ( embedError ) {
2025-06-05 11:18:20 -07:00
throw new Error ( ` Failed to vectorize verb metadata: ${ embedError } ` )
2025-06-05 08:26:40 -07:00
}
} else {
// Use a provided vector or average of source and target vectors
2025-06-05 11:18:20 -07:00
verbVector =
2025-06-05 08:26:40 -07:00
vector ||
2025-06-05 11:18:20 -07:00
sourceNoun . vector . map ( ( val , i ) = > ( val + targetNoun . vector [ i ] ) / 2 )
}
// Validate verb type if provided
let verbType = options . type ;
if ( verbType ) {
// Check if the verb type is valid
const isValidVerbType = Object . values ( VerbType ) . includes ( verbType as any ) ;
if ( ! isValidVerbType ) {
console . warn ( ` Invalid verb type: ${ verbType } . Using RelatedTo as default. ` ) ;
// Set a default verb type
verbType = VerbType . RelatedTo ;
}
2025-06-05 08:26:40 -07:00
}
2025-05-23 10:55:20 -07:00
2025-06-05 11:18:20 -07:00
// Create verb
const verb : GraphVerb = {
2025-05-23 10:55:20 -07:00
id ,
2025-06-05 11:18:20 -07:00
vector : verbVector ,
2025-05-23 10:55:20 -07:00
connections : new Map ( ) ,
sourceId ,
targetId ,
2025-06-05 11:18:20 -07:00
type : verbType ,
2025-05-23 10:55:20 -07:00
weight : options.weight ,
metadata : options.metadata
}
// Add to index
2025-06-05 11:18:20 -07:00
this . index . addItem ( { id , vector : verbVector } )
2025-05-23 10:55:20 -07:00
2025-06-05 11:18:20 -07:00
// Get the noun from the index
const indexNoun = this . index . getNodes ( ) . get ( id )
2025-05-23 10:55:20 -07:00
2025-06-05 11:18:20 -07:00
if ( ! indexNoun ) {
2025-05-23 10:55:20 -07:00
throw new Error (
2025-06-05 11:18:20 -07:00
` Failed to retrieve newly created verb noun with ID ${ id } `
2025-05-23 10:55:20 -07:00
)
}
2025-06-05 11:18:20 -07:00
// Update verb connections from index
verb . connections = indexNoun . connections
2025-05-23 10:55:20 -07:00
2025-06-05 11:18:20 -07:00
// Save verb to storage
await this . storage ! . saveVerb ( verb )
2025-05-23 10:55:20 -07:00
return id
} catch ( error ) {
2025-06-05 11:18:20 -07:00
console . error ( 'Failed to add verb:' , error )
throw new Error ( ` Failed to add verb: ${ error } ` )
2025-05-23 10:55:20 -07:00
}
}
/ * *
2025-06-05 11:18:20 -07:00
* Get a verb by ID
2025-05-23 10:55:20 -07:00
* /
2025-06-05 11:18:20 -07:00
public async getVerb ( id : string ) : Promise < GraphVerb | null > {
2025-05-23 10:55:20 -07:00
await this . ensureInitialized ( )
try {
2025-06-05 11:18:20 -07:00
return await this . storage ! . getVerb ( id )
2025-05-23 10:55:20 -07:00
} catch ( error ) {
2025-06-05 11:18:20 -07:00
console . error ( ` Failed to get verb ${ id } : ` , error )
throw new Error ( ` Failed to get verb ${ id } : ${ error } ` )
2025-05-23 10:55:20 -07:00
}
}
/ * *
2025-06-05 11:18:20 -07:00
* Get all verbs
2025-05-23 10:55:20 -07:00
* /
2025-06-05 11:18:20 -07:00
public async getAllVerbs ( ) : Promise < GraphVerb [ ] > {
2025-05-23 10:55:20 -07:00
await this . ensureInitialized ( )
try {
2025-06-05 11:18:20 -07:00
return await this . storage ! . getAllVerbs ( )
2025-05-23 10:55:20 -07:00
} catch ( error ) {
2025-06-05 11:18:20 -07:00
console . error ( 'Failed to get all verbs:' , error )
throw new Error ( ` Failed to get all verbs: ${ error } ` )
2025-05-23 10:55:20 -07:00
}
}
/ * *
2025-06-05 11:18:20 -07:00
* Get verbs by source noun ID
2025-05-23 10:55:20 -07:00
* /
2025-06-05 11:18:20 -07:00
public async getVerbsBySource ( sourceId : string ) : Promise < GraphVerb [ ] > {
2025-05-23 10:55:20 -07:00
await this . ensureInitialized ( )
try {
2025-06-05 11:18:20 -07:00
return await this . storage ! . getVerbsBySource ( sourceId )
2025-05-23 10:55:20 -07:00
} catch ( error ) {
2025-06-05 11:18:20 -07:00
console . error ( ` Failed to get verbs by source ${ sourceId } : ` , error )
throw new Error ( ` Failed to get verbs by source ${ sourceId } : ${ error } ` )
2025-05-23 10:55:20 -07:00
}
}
/ * *
2025-06-05 11:18:20 -07:00
* Get verbs by target noun ID
2025-05-23 10:55:20 -07:00
* /
2025-06-05 11:18:20 -07:00
public async getVerbsByTarget ( targetId : string ) : Promise < GraphVerb [ ] > {
2025-05-23 10:55:20 -07:00
await this . ensureInitialized ( )
try {
2025-06-05 11:18:20 -07:00
return await this . storage ! . getVerbsByTarget ( targetId )
2025-05-23 10:55:20 -07:00
} catch ( error ) {
2025-06-05 11:18:20 -07:00
console . error ( ` Failed to get verbs by target ${ targetId } : ` , error )
throw new Error ( ` Failed to get verbs by target ${ targetId } : ${ error } ` )
2025-05-23 10:55:20 -07:00
}
}
/ * *
2025-06-05 11:18:20 -07:00
* Get verbs by type
2025-05-23 10:55:20 -07:00
* /
2025-06-05 11:18:20 -07:00
public async getVerbsByType ( type : string ) : Promise < GraphVerb [ ] > {
2025-05-23 10:55:20 -07:00
await this . ensureInitialized ( )
try {
2025-06-05 11:18:20 -07:00
return await this . storage ! . getVerbsByType ( type )
2025-05-23 10:55:20 -07:00
} catch ( error ) {
2025-06-05 11:18:20 -07:00
console . error ( ` Failed to get verbs by type ${ type } : ` , error )
throw new Error ( ` Failed to get verbs by type ${ type } : ${ error } ` )
2025-05-23 10:55:20 -07:00
}
}
/ * *
2025-06-05 11:18:20 -07:00
* Delete a verb
2025-05-23 10:55:20 -07:00
* /
2025-06-05 11:18:20 -07:00
public async deleteVerb ( id : string ) : Promise < boolean > {
2025-05-23 10:55:20 -07:00
await this . ensureInitialized ( )
2025-06-04 10:01:59 -07:00
// Check if database is in read-only mode
this . checkReadOnly ( )
2025-05-23 10:55:20 -07:00
try {
// Remove from index
const removed = this . index . removeItem ( id )
if ( ! removed ) {
return false
}
// Remove from storage
2025-06-05 11:18:20 -07:00
await this . storage ! . deleteVerb ( id )
2025-05-23 10:55:20 -07:00
return true
} catch ( error ) {
2025-06-05 11:18:20 -07:00
console . error ( ` Failed to delete verb ${ id } : ` , error )
throw new Error ( ` Failed to delete verb ${ id } : ${ error } ` )
2025-05-23 10:55:20 -07:00
}
}
/ * *
* Clear the database
* /
public async clear ( ) : Promise < void > {
await this . ensureInitialized ( )
2025-06-04 10:01:59 -07:00
// Check if database is in read-only mode
this . checkReadOnly ( )
2025-05-23 10:55:20 -07:00
try {
// Clear index
this . index . clear ( )
// Clear storage
await this . storage ! . clear ( )
} catch ( error ) {
console . error ( 'Failed to clear vector database:' , error )
throw new Error ( ` Failed to clear vector database: ${ error } ` )
}
}
/ * *
* Get the number of vectors in the database
* /
public size ( ) : number {
return this . index . size ( )
}
2025-06-04 10:01:59 -07:00
/ * *
* Check if the database is in read - only mode
* @returns True if the database is in read - only mode , false otherwise
* /
public isReadOnly ( ) : boolean {
return this . readOnly
}
/ * *
* Set the database to read - only mode
* @param readOnly True to set the database to read - only mode , false to allow writes
* /
public setReadOnly ( readOnly : boolean ) : void {
this . readOnly = readOnly
}
2025-05-27 13:58:49 -07:00
/ * *
* Embed text or data into a vector using the same embedding function used by this instance
* This allows clients to use the same TensorFlow Universal Sentence Encoder throughout their application
2025-05-28 15:39:14 -07:00
*
2025-05-27 13:58:49 -07:00
* @param data Text or data to embed
* @returns A promise that resolves to the embedded vector
* /
public async embed ( data : string | string [ ] ) : Promise < Vector > {
await this . ensureInitialized ( )
try {
return await this . embeddingFunction ( data )
} catch ( error ) {
console . error ( 'Failed to embed data:' , error )
throw new Error ( ` Failed to embed data: ${ error } ` )
}
}
/ * *
* Search for similar documents using a text query
* This is a convenience method that embeds the query text and performs a search
2025-05-28 15:39:14 -07:00
*
2025-05-27 13:58:49 -07:00
* @param query Text query to search for
* @param k Number of results to return
2025-06-05 08:26:40 -07:00
* @param options Additional options
2025-05-27 13:58:49 -07:00
* @returns Array of search results
* /
2025-06-05 08:26:40 -07:00
public async searchText (
query : string ,
k : number = 10 ,
options : {
nounTypes? : string [ ] ,
includeVerbs? : boolean
} = { }
) : Promise < SearchResult < T > [ ] > {
2025-05-27 13:58:49 -07:00
await this . ensureInitialized ( )
try {
// Embed the query text
const queryVector = await this . embed ( query )
// Search using the embedded vector
2025-06-05 08:26:40 -07:00
return await this . search ( queryVector , k , {
nounTypes : options.nounTypes ,
includeVerbs : options.includeVerbs
} )
2025-05-27 13:58:49 -07:00
} catch ( error ) {
console . error ( 'Failed to search with text query:' , error )
throw new Error ( ` Failed to search with text query: ${ error } ` )
}
}
2025-05-23 10:55:20 -07:00
/ * *
* Ensure the database is initialized
* /
private async ensureInitialized ( ) : Promise < void > {
if ( ! this . isInitialized ) {
await this . init ( )
}
}
2025-05-28 15:39:14 -07:00
/ * *
* Get information about the current storage usage and capacity
* @returns Object containing the storage type , used space , quota , and additional details
* /
public async status ( ) : Promise < {
type : string ;
used : number ;
quota : number | null ;
details? : Record < string , any > ;
} > {
await this . ensureInitialized ( )
if ( ! this . storage ) {
return {
type : 'unknown' ,
used : 0 ,
quota : null ,
details : { error : 'Storage not initialized' }
}
}
try {
2025-05-28 16:02:20 -07:00
// Check if the storage adapter has a getStorageStatus method
if ( typeof this . storage . getStorageStatus !== 'function' ) {
// If not, determine the storage type based on the constructor name
2025-05-29 08:27:59 -07:00
const storageType = this . storage . constructor . name . toLowerCase ( ) . replace ( 'storage' , '' )
2025-05-28 16:02:20 -07:00
return {
type : storageType || 'unknown' ,
used : 0 ,
quota : null ,
2025-05-29 08:27:59 -07:00
details : {
2025-05-28 16:02:20 -07:00
error : 'Storage adapter does not implement getStorageStatus method' ,
storageAdapter : this.storage.constructor.name ,
indexSize : this.size ( )
}
}
}
2025-05-28 15:39:14 -07:00
// Get storage status from the storage adapter
const storageStatus = await this . storage . getStorageStatus ( )
// Add index information to the details
const indexInfo = {
indexSize : this.size ( )
}
2025-05-28 16:02:20 -07:00
// Ensure all required fields are present
2025-05-28 15:39:14 -07:00
return {
2025-05-28 16:02:20 -07:00
type : storageStatus . type || 'unknown' ,
used : storageStatus.used || 0 ,
quota : storageStatus.quota || null ,
2025-05-28 15:39:14 -07:00
details : {
2025-05-28 16:02:20 -07:00
. . . ( storageStatus . details || { } ) ,
2025-05-28 15:39:14 -07:00
index : indexInfo
}
}
} catch ( error ) {
console . error ( 'Failed to get storage status:' , error )
2025-05-28 16:02:20 -07:00
// Determine the storage type based on the constructor name
2025-05-29 08:27:59 -07:00
const storageType = this . storage . constructor . name . toLowerCase ( ) . replace ( 'storage' , '' )
2025-05-28 16:02:20 -07:00
2025-05-28 15:39:14 -07:00
return {
2025-05-28 16:02:20 -07:00
type : storageType || 'unknown' ,
2025-05-28 15:39:14 -07:00
used : 0 ,
quota : null ,
2025-05-29 08:27:59 -07:00
details : {
2025-05-28 16:02:20 -07:00
error : String ( error ) ,
storageAdapter : this.storage.constructor.name ,
indexSize : this.size ( )
}
2025-05-28 15:39:14 -07:00
}
}
}
2025-05-23 10:55:20 -07:00
}
// Export distance functions for convenience
export { euclideanDistance , cosineDistance , manhattanDistance , dotProductDistance } from './utils/index.js'