2025-09-11 16:23:32 -07:00
/ * *
* Data Management API for Brainy 3.0
* Provides backup , restore , import , export , and data management
* /
import { StorageAdapter , HNSWNoun , GraphVerb } from '../coreTypes.js'
import { Entity , Relation } from '../types/brainy.types.js'
import { NounType , VerbType } from '../types/graphTypes.js'
export interface BackupOptions {
includeVectors? : boolean
compress? : boolean
format ? : 'json' | 'binary'
}
export interface RestoreOptions {
merge? : boolean
overwrite? : boolean
validate? : boolean
}
export interface ImportOptions {
format : 'json' | 'csv'
mapping? : Record < string , string >
batchSize? : number
validate? : boolean
}
export interface ExportOptions {
format ? : 'json' | 'csv'
filter ? : {
type ? : NounType | NounType [ ]
where? : Record < string , any >
service? : string
}
includeVectors? : boolean
}
export interface BackupData {
version : string
timestamp : number
entities : Array < {
id : string
vector? : number [ ]
type : string
metadata : any
service? : string
} >
relations : Array < {
id : string
from : string
to : string
type : string
weight : number
metadata? : any
} >
config? : Record < string , any >
stats : {
entityCount : number
relationCount : number
vectorDimensions? : number
}
}
export interface ImportResult {
successful : number
failed : number
errors : Array < { item : any ; error : string } >
duration : number
}
export class DataAPI {
private brain : any // Reference to Brainy instance for neural import
constructor (
private storage : StorageAdapter ,
private getEntity : ( id : string ) = > Promise < Entity | null > ,
private getRelation ? : ( id : string ) = > Promise < Relation | null > ,
brain? : any
) {
this . brain = brain
}
/ * *
* Create a backup of all data
* /
async backup ( options : BackupOptions = { } ) : Promise < BackupData | { compressed : boolean ; data : string ; originalSize : number ; compressedSize : number } > {
const {
includeVectors = true ,
compress = false ,
format = 'json'
} = options
const startTime = Date . now ( )
// Get all entities
const nounsResult = await this . storage . getNouns ( {
pagination : { limit : 1000000 }
} )
const entities : BackupData [ 'entities' ] = [ ]
for ( const noun of nounsResult . items ) {
const entity = {
id : noun.id ,
vector : includeVectors ? noun.vector : undefined ,
fix(storage): v4.8.0 metadata architecture refactoring - FIXES VFS bug
CRITICAL FIX: VFS bug that persisted through v4.5.1-v4.7.4 is NOW FIXED.
Root Cause:
- Storage adapters were not properly extracting standard fields from metadata
- This caused getVerbsBySource_internal() to return 0 relationships despite relationships existing
- VFS PathResolver couldn't navigate directory structure
Solution - Metadata Architecture Refactoring:
1. Move standard fields to top-level of HNSWNounWithMetadata and HNSWVerbWithMetadata
- type, createdAt, updatedAt, confidence, weight, service, data, createdBy
2. Update all 9 storage adapters to extract standard fields from metadata on load
3. Maintain backward compatibility at storage layer (metadata files unchanged)
Changes:
- src/coreTypes.ts: Update HNSWNounWithMetadata and HNSWVerbWithMetadata interfaces
- Add top-level standard fields
- Change data type from unknown to Record<string, any>
- Add confidence field to GraphVerb
- src/storage/baseStorage.ts: Add type cast pattern for standard field extraction
- src/storage/adapters/*.ts: Fix all 9 adapters (memoryStorage, fileSystemStorage, gcsStorage,
s3CompatibleStorage, r2Storage, opfsStorage, azureBlobStorage, typeAwareStorageAdapter)
- Extract standard fields from metadata on load
- Place at top-level of returned entities
- src/api/DataAPI.ts: Read fields from top-level instead of metadata
- src/graph/graphAdjacencyIndex.ts: Convert HNSWVerbWithMetadata to GraphVerb format
- src/utils/metadataIndex.ts: Fix typo (metadata → entityOrMetadata)
- src/types/brainy.types.ts: Add createdBy field to AddParams
- src/types/graphTypes.ts: Add service field to GraphVerb
Test Results:
✅ VFS bug FIXED - vfs.readdir('/') now returns files (was returning empty array)
✅ getVerbsBySource_internal() now returns relationships correctly
✅ Build succeeds with ZERO compilation errors
✅ 95.7% of tests pass (954/997)
Breaking Changes:
- None - backward compatibility maintained at storage layer
Version: 4.8.0
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 15:43:49 -07:00
type : noun . type || NounType . Thing , // v4.8.0: type at top-level
2025-09-11 16:23:32 -07:00
metadata : noun.metadata ,
fix(storage): v4.8.0 metadata architecture refactoring - FIXES VFS bug
CRITICAL FIX: VFS bug that persisted through v4.5.1-v4.7.4 is NOW FIXED.
Root Cause:
- Storage adapters were not properly extracting standard fields from metadata
- This caused getVerbsBySource_internal() to return 0 relationships despite relationships existing
- VFS PathResolver couldn't navigate directory structure
Solution - Metadata Architecture Refactoring:
1. Move standard fields to top-level of HNSWNounWithMetadata and HNSWVerbWithMetadata
- type, createdAt, updatedAt, confidence, weight, service, data, createdBy
2. Update all 9 storage adapters to extract standard fields from metadata on load
3. Maintain backward compatibility at storage layer (metadata files unchanged)
Changes:
- src/coreTypes.ts: Update HNSWNounWithMetadata and HNSWVerbWithMetadata interfaces
- Add top-level standard fields
- Change data type from unknown to Record<string, any>
- Add confidence field to GraphVerb
- src/storage/baseStorage.ts: Add type cast pattern for standard field extraction
- src/storage/adapters/*.ts: Fix all 9 adapters (memoryStorage, fileSystemStorage, gcsStorage,
s3CompatibleStorage, r2Storage, opfsStorage, azureBlobStorage, typeAwareStorageAdapter)
- Extract standard fields from metadata on load
- Place at top-level of returned entities
- src/api/DataAPI.ts: Read fields from top-level instead of metadata
- src/graph/graphAdjacencyIndex.ts: Convert HNSWVerbWithMetadata to GraphVerb format
- src/utils/metadataIndex.ts: Fix typo (metadata → entityOrMetadata)
- src/types/brainy.types.ts: Add createdBy field to AddParams
- src/types/graphTypes.ts: Add service field to GraphVerb
Test Results:
✅ VFS bug FIXED - vfs.readdir('/') now returns files (was returning empty array)
✅ getVerbsBySource_internal() now returns relationships correctly
✅ Build succeeds with ZERO compilation errors
✅ 95.7% of tests pass (954/997)
Breaking Changes:
- None - backward compatibility maintained at storage layer
Version: 4.8.0
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 15:43:49 -07:00
service : noun.service // v4.8.0: service at top-level
2025-09-11 16:23:32 -07:00
}
entities . push ( entity )
}
// Get all relations
const verbsResult = await this . storage . getVerbs ( {
pagination : { limit : 1000000 }
} )
const relations : BackupData [ 'relations' ] = [ ]
for ( const verb of verbsResult . items ) {
relations . push ( {
id : verb.id ,
from : verb . sourceId ,
to : verb.targetId ,
2025-10-17 12:29:27 -07:00
type : verb . verb as string ,
fix(storage): v4.8.0 metadata architecture refactoring - FIXES VFS bug
CRITICAL FIX: VFS bug that persisted through v4.5.1-v4.7.4 is NOW FIXED.
Root Cause:
- Storage adapters were not properly extracting standard fields from metadata
- This caused getVerbsBySource_internal() to return 0 relationships despite relationships existing
- VFS PathResolver couldn't navigate directory structure
Solution - Metadata Architecture Refactoring:
1. Move standard fields to top-level of HNSWNounWithMetadata and HNSWVerbWithMetadata
- type, createdAt, updatedAt, confidence, weight, service, data, createdBy
2. Update all 9 storage adapters to extract standard fields from metadata on load
3. Maintain backward compatibility at storage layer (metadata files unchanged)
Changes:
- src/coreTypes.ts: Update HNSWNounWithMetadata and HNSWVerbWithMetadata interfaces
- Add top-level standard fields
- Change data type from unknown to Record<string, any>
- Add confidence field to GraphVerb
- src/storage/baseStorage.ts: Add type cast pattern for standard field extraction
- src/storage/adapters/*.ts: Fix all 9 adapters (memoryStorage, fileSystemStorage, gcsStorage,
s3CompatibleStorage, r2Storage, opfsStorage, azureBlobStorage, typeAwareStorageAdapter)
- Extract standard fields from metadata on load
- Place at top-level of returned entities
- src/api/DataAPI.ts: Read fields from top-level instead of metadata
- src/graph/graphAdjacencyIndex.ts: Convert HNSWVerbWithMetadata to GraphVerb format
- src/utils/metadataIndex.ts: Fix typo (metadata → entityOrMetadata)
- src/types/brainy.types.ts: Add createdBy field to AddParams
- src/types/graphTypes.ts: Add service field to GraphVerb
Test Results:
✅ VFS bug FIXED - vfs.readdir('/') now returns files (was returning empty array)
✅ getVerbsBySource_internal() now returns relationships correctly
✅ Build succeeds with ZERO compilation errors
✅ 95.7% of tests pass (954/997)
Breaking Changes:
- None - backward compatibility maintained at storage layer
Version: 4.8.0
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-27 15:43:49 -07:00
weight : verb.weight || 1.0 , // v4.8.0: weight at top-level
2025-09-11 16:23:32 -07:00
metadata : verb.metadata
} )
}
// Create backup data
const backupData : BackupData = {
version : '3.0.0' ,
timestamp : Date.now ( ) ,
entities ,
relations ,
stats : {
entityCount : entities.length ,
relationCount : relations.length ,
vectorDimensions : entities [ 0 ] ? . vector ? . length
}
}
// Compress if requested
if ( compress ) {
// Import zlib for compression
feat: add node: protocol to all Node.js built-in imports for bundler compatibility
- Updated all fs, path, crypto, os, url, util, events, http, https, net, child_process, stream, and zlib imports
- Changed both static imports and dynamic imports to use node: protocol
- This makes Brainy more bundler-friendly by explicitly marking Node.js built-ins
- Prevents bundlers from attempting to polyfill or bundle these modules
- Reduces bundle size for web applications using Brainy
- Improves tree-shaking and dead code elimination
Benefits for external bundlers:
- Clear distinction between Node.js built-ins and external dependencies
- No ambiguity about what needs polyfilling
- Smaller bundles for browser builds
- Better compatibility with modern bundlers (Webpack 5, Vite, Rollup, esbuild)
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-17 14:20:21 -07:00
const { gzipSync } = await import ( 'node:zlib' )
2025-09-11 16:23:32 -07:00
const jsonString = JSON . stringify ( backupData )
const compressed = gzipSync ( Buffer . from ( jsonString ) )
return {
compressed : true ,
data : compressed.toString ( 'base64' ) ,
originalSize : jsonString.length ,
compressedSize : compressed.length
}
}
return backupData
}
/ * *
* Restore data from a backup
2025-10-30 09:08:14 -07:00
*
* v4.11.1 : CRITICAL FIX - Now uses brain . addMany ( ) and brain . relateMany ( )
* Previous implementation only wrote to storage cache without updating indexes ,
* causing complete data loss on restart . This fix ensures :
* - All 5 indexes updated ( HNSW , metadata , adjacency , sparse , type - aware )
* - Proper persistence to disk / cloud storage
* - Storage - aware batching for optimal performance
* - Atomic writes to prevent corruption
* - Data survives instance restart
2025-09-11 16:23:32 -07:00
* /
async restore ( params : {
backup : BackupData
merge? : boolean
overwrite? : boolean
validate? : boolean
2025-10-30 09:08:14 -07:00
onProgress ? : ( completed : number , total : number ) = > void
} ) : Promise < {
entitiesRestored : number
relationshipsRestored : number
2025-10-30 13:19:08 -07:00
relationshipsSkipped : number
2025-10-30 09:08:14 -07:00
errors : Array < { type : 'entity' | 'relation' ; id : string ; error : string } >
} > {
const { backup , merge = false , overwrite = false , validate = true , onProgress } = params
const result = {
entitiesRestored : 0 ,
relationshipsRestored : 0 ,
2025-10-30 13:19:08 -07:00
relationshipsSkipped : 0 ,
2025-10-30 09:08:14 -07:00
errors : [ ] as Array < { type : 'entity' | 'relation' ; id : string ; error : string } >
}
2025-09-11 16:23:32 -07:00
// Validate backup format
if ( validate ) {
if ( ! backup . version || ! backup . entities || ! backup . relations ) {
2025-10-30 09:08:14 -07:00
throw new Error ( 'Invalid backup format: missing version, entities, or relations' )
2025-09-11 16:23:32 -07:00
}
}
2025-10-30 09:08:14 -07:00
// Validate brain instance is available (required for v4.11.1+ restore)
if ( ! this . brain ) {
throw new Error (
'Restore requires brain instance. DataAPI must be initialized with brain reference. ' +
'Use: await brain.data() instead of constructing DataAPI directly.'
)
}
2025-09-11 16:23:32 -07:00
// Clear existing data if not merging
if ( ! merge && overwrite ) {
await this . clear ( { entities : true , relations : true } )
}
2025-10-30 09:08:14 -07:00
// ============================================
// Phase 1: Restore entities using addMany()
// v4.11.1: Uses proper persistence path through brain.addMany()
// ============================================
// Prepare entity parameters for addMany()
const entityParams = backup . entities
. filter ( entity = > {
// Skip existing entities when merging without overwrite
if ( merge && ! overwrite ) {
// Note: We'll rely on addMany's internal duplicate handling
// rather than checking each entity individually (performance)
return true
2025-10-17 12:29:27 -07:00
}
2025-10-30 09:08:14 -07:00
return true
} )
. map ( entity = > {
// Extract data field from metadata (backup format compatibility)
// Backup stores the original data in metadata.data
const data = entity . metadata ? . data || entity . id
2025-10-17 12:29:27 -07:00
2025-10-30 09:08:14 -07:00
return {
id : entity.id ,
data , // Required field for brainy.add()
type : entity . type ,
metadata : entity.metadata || { } ,
vector : entity.vector , // Preserve original vectors from backup
2025-10-17 12:29:27 -07:00
service : entity.service ,
2025-10-30 09:08:14 -07:00
// Preserve confidence and weight if available
confidence : entity.metadata?.confidence ,
weight : entity.metadata?.weight
2025-09-11 16:23:32 -07:00
}
2025-10-30 09:08:14 -07:00
} )
2025-09-11 16:23:32 -07:00
2025-10-30 09:08:14 -07:00
// Restore entities in batches using storage-aware batching (v4.11.0)
2025-10-30 13:19:08 -07:00
// v4.11.1: Track successful entity IDs to prevent orphaned relationships
const successfulEntityIds = new Set < string > ( )
2025-10-30 09:08:14 -07:00
if ( entityParams . length > 0 ) {
try {
const addResult = await this . brain . addMany ( {
items : entityParams ,
continueOnError : true ,
onProgress : ( done : number , total : number ) = > {
onProgress ? . ( done , backup . entities . length + backup . relations . length )
2025-09-11 16:23:32 -07:00
}
2025-10-30 09:08:14 -07:00
} )
2025-09-11 16:23:32 -07:00
2025-10-30 09:08:14 -07:00
result . entitiesRestored = addResult . successful . length
2025-10-30 13:19:08 -07:00
// Build Set of successfully restored entity IDs (v4.11.1 Bug Fix)
addResult . successful . forEach ( ( entityId : string ) = > {
successfulEntityIds . add ( entityId )
} )
2025-10-30 09:08:14 -07:00
// Track errors
addResult . failed . forEach ( ( failure : any ) = > {
result . errors . push ( {
type : 'entity' ,
id : failure.item?.id || 'unknown' ,
error : failure.error || 'Unknown error'
} )
} )
2025-09-11 16:23:32 -07:00
} catch ( error ) {
2025-10-30 09:08:14 -07:00
throw new Error ( ` Failed to restore entities: ${ ( error as Error ) . message } ` )
2025-09-11 16:23:32 -07:00
}
}
2025-10-30 09:08:14 -07:00
// ============================================
// Phase 2: Restore relationships using relateMany()
2025-10-30 13:19:08 -07:00
// v4.11.1: CRITICAL FIX - Filter orphaned relationships
2025-10-30 09:08:14 -07:00
// ============================================
2025-10-17 12:29:27 -07:00
2025-10-30 13:19:08 -07:00
// Prepare relationship parameters - filter out orphaned references
2025-10-30 09:08:14 -07:00
const relationParams = backup . relations
. filter ( relation = > {
// Skip existing relations when merging without overwrite
if ( merge && ! overwrite ) {
// Note: We'll rely on relateMany's internal duplicate handling
return true
2025-10-17 12:29:27 -07:00
}
2025-10-30 13:19:08 -07:00
// v4.11.1 CRITICAL BUG FIX: Skip relationships where source or target entity failed to restore
// This prevents creating orphaned references that cause "Entity not found" errors
const sourceExists = successfulEntityIds . has ( relation . from )
const targetExists = successfulEntityIds . has ( relation . to )
if ( ! sourceExists || ! targetExists ) {
// Track skipped relationship
result . relationshipsSkipped ++
// Optionally log for debugging (can be removed in production)
if ( process . env . NODE_ENV !== 'production' ) {
console . debug (
` Skipping orphaned relationship: ${ relation . from } -> ${ relation . to } ` +
` ( ${ ! sourceExists ? 'missing source' : 'missing target' } ) `
)
}
return false
}
2025-10-30 09:08:14 -07:00
return true
} )
. map ( relation = > ( {
from : relation . from ,
to : relation.to ,
type : relation . type as VerbType ,
metadata : relation.metadata || { } ,
weight : relation.weight || 1.0
// Note: relation.id is ignored - brain.relate() generates new IDs
// This is intentional to avoid ID conflicts
} ) )
2025-09-11 16:23:32 -07:00
2025-10-30 09:08:14 -07:00
// Restore relationships in batches using storage-aware batching (v4.11.0)
if ( relationParams . length > 0 ) {
try {
const relateResult = await this . brain . relateMany ( {
items : relationParams ,
continueOnError : true
} )
result . relationshipsRestored = relateResult . successful . length
2025-09-11 16:23:32 -07:00
2025-10-30 09:08:14 -07:00
// Track errors
relateResult . failed . forEach ( ( failure : any ) = > {
result . errors . push ( {
type : 'relation' ,
id : failure.item?.from + '->' + failure . item ? . to || 'unknown' ,
error : failure.error || 'Unknown error'
} )
} )
2025-09-11 16:23:32 -07:00
} catch ( error ) {
2025-10-30 09:08:14 -07:00
throw new Error ( ` Failed to restore relationships: ${ ( error as Error ) . message } ` )
2025-09-11 16:23:32 -07:00
}
}
2025-10-30 09:08:14 -07:00
// ============================================
// Phase 3: Verify restoration succeeded
// ============================================
// Sample verification: Check that first entity is actually retrievable
if ( backup . entities . length > 0 && result . entitiesRestored > 0 ) {
const firstEntityId = backup . entities [ 0 ] . id
const verified = await this . brain . get ( firstEntityId )
if ( ! verified ) {
console . warn (
` ⚠️ Restore completed but verification failed - entity ${ firstEntityId } not retrievable. ` +
` This may indicate a persistence issue with the storage adapter. `
)
}
}
return result
2025-09-11 16:23:32 -07:00
}
/ * *
* Clear data
* /
async clear ( params : {
entities? : boolean
relations? : boolean
config? : boolean
} = { } ) : Promise < void > {
const { entities = true , relations = true , config = false } = params
if ( entities ) {
// Clear all entities
const nounsResult = await this . storage . getNouns ( {
pagination : { limit : 1000000 }
} )
for ( const noun of nounsResult . items ) {
await this . storage . deleteNoun ( noun . id )
}
// Also clear the HNSW index if available
if ( this . brain ? . index ? . clear ) {
this . brain . index . clear ( )
}
// Clear metadata index if available
if ( this . brain ? . metadataIndex ) {
await this . brain . metadataIndex . rebuild ( ) // Rebuild empty index
}
}
if ( relations ) {
// Clear all relations
const verbsResult = await this . storage . getVerbs ( {
pagination : { limit : 1000000 }
} )
for ( const verb of verbsResult . items ) {
await this . storage . deleteVerb ( verb . id )
}
}
if ( config ) {
// Clear configuration would be handled by ConfigAPI
// For now, skip this
}
}
/ * *
* Import data from various formats
* /
async import ( params : ImportOptions & { data : any } ) : Promise < ImportResult > {
const {
data ,
format ,
mapping = { } ,
batchSize = 100 ,
validate = true
} = params
const result : ImportResult = {
successful : 0 ,
failed : 0 ,
errors : [ ] ,
duration : 0
}
const startTime = Date . now ( )
try {
// ALWAYS use neural import for proper type matching
const { UniversalImportAPI } = await import ( './UniversalImportAPI.js' )
const universalImport = new UniversalImportAPI ( this . brain )
await universalImport . init ( )
// Convert to ImportSource format
const neuralResult = await universalImport . import ( {
type : 'object' ,
data ,
format : format || 'json' ,
metadata : { mapping , batchSize , validate }
} )
// Convert neural result to ImportResult format
result . successful = neuralResult . stats . entitiesCreated
result . failed = 0 // Neural import always succeeds with best match
result . duration = neuralResult . stats . processingTimeMs
// Log relationships created
if ( neuralResult . stats . relationshipsCreated > 0 ) {
console . log ( ` Neural import also created ${ neuralResult . stats . relationshipsCreated } relationships ` )
}
return result
} catch ( error ) {
// Fallback to legacy import ONLY if neural import fails to load
console . warn ( 'Neural import failed, using legacy import:' , error )
let items : any [ ] = [ ]
// Parse data based on format
switch ( format ) {
case 'json' :
items = Array . isArray ( data ) ? data : [ data ]
break
case 'csv' :
// CSV parsing would go here
// For now, assume data is already parsed
items = data
break
// Parquet format removed - not implemented
default :
throw new Error ( ` Unsupported format: ${ format } ` )
}
// Process items in batches
for ( let i = 0 ; i < items . length ; i += batchSize ) {
const batch = items . slice ( i , i + batchSize )
for ( const item of batch ) {
try {
// Apply field mapping
const mapped = this . applyMapping ( item , mapping )
// Validate if requested
if ( validate ) {
this . validateImportItem ( mapped )
}
2025-10-17 12:29:27 -07:00
// v4.0.0: Save entity - separate vector and metadata
const id = mapped . id || this . generateId ( )
2025-09-11 16:23:32 -07:00
const noun : HNSWNoun = {
2025-10-17 12:29:27 -07:00
id ,
2025-09-11 16:23:32 -07:00
vector : mapped.vector || new Array ( 384 ) . fill ( 0 ) ,
connections : new Map ( ) ,
2025-10-17 12:29:27 -07:00
level : 0
2025-09-11 16:23:32 -07:00
}
await this . storage . saveNoun ( noun )
2025-10-17 12:29:27 -07:00
await this . storage . saveNounMetadata ( id , { . . . mapped , createdAt : Date.now ( ) } )
2025-09-11 16:23:32 -07:00
result . successful ++
} catch ( error ) {
result . failed ++
result . errors . push ( {
item ,
error : ( error as Error ) . message
} )
}
}
}
result . duration = Date . now ( ) - startTime
return result
}
}
/ * *
* Export data to various formats
* /
async export ( params : ExportOptions = { } ) : Promise < any > {
const {
format = 'json' ,
filter = { } ,
includeVectors = false
} = params
// Get filtered entities
const nounsResult = await this . storage . getNouns ( {
pagination : { limit : 1000000 }
} )
let entities = nounsResult . items
// Apply filters
if ( filter . type ) {
const types = Array . isArray ( filter . type ) ? filter . type : [ filter . type ]
entities = entities . filter ( e = >
types . includes ( e . metadata ? . noun as NounType )
)
}
if ( filter . service ) {
entities = entities . filter ( e = >
e . metadata ? . service === filter . service
)
}
if ( filter . where ) {
entities = entities . filter ( e = >
this . matchesFilter ( e . metadata , filter . where ! )
)
}
// Format data based on export format
switch ( format ) {
case 'json' :
return entities . map ( e = > ( {
id : e.id ,
vector : includeVectors ? e.vector : undefined ,
. . . e . metadata
} ) )
case 'csv' :
// Convert to CSV format
// For now, return simplified format
return this . convertToCSV ( entities )
// Parquet format removed - not implemented
default :
throw new Error ( ` Unsupported export format: ${ format } ` )
}
}
/ * *
* Get storage statistics
* /
async getStats ( ) : Promise < {
entities : number
relations : number
storageSize? : number
vectorDimensions? : number
} > {
const nounsResult = await this . storage . getNouns ( {
pagination : { limit : 1 }
} )
const verbsResult = await this . storage . getVerbs ( {
pagination : { limit : 1 }
} )
const firstNoun = nounsResult . items [ 0 ]
return {
entities : nounsResult.totalCount || nounsResult . items . length ,
relations : verbsResult.totalCount || verbsResult . items . length ,
vectorDimensions : firstNoun?.vector?.length
}
}
// Helper methods
private applyMapping ( item : any , mapping : Record < string , string > ) : any {
const mapped : any = { }
for ( const [ key , value ] of Object . entries ( item ) ) {
const mappedKey = mapping [ key ] || key
mapped [ mappedKey ] = value
}
return mapped
}
private validateImportItem ( item : any ) : void {
// Basic validation
if ( ! item || typeof item !== 'object' ) {
throw new Error ( 'Invalid item: must be an object' )
}
// Could add more validation here
}
private matchesFilter ( metadata : any , filter : Record < string , any > ) : boolean {
for ( const [ key , value ] of Object . entries ( filter ) ) {
if ( metadata [ key ] !== value ) {
return false
}
}
return true
}
2025-10-17 12:29:27 -07:00
private convertToCSV ( entities : Array < { id : string ; metadata? : any } > ) : string {
2025-09-11 16:23:32 -07:00
if ( entities . length === 0 ) return ''
2025-10-17 12:29:27 -07:00
2025-09-11 16:23:32 -07:00
// Get all unique keys from metadata
const keys = new Set < string > ( )
for ( const entity of entities ) {
if ( entity . metadata ) {
Object . keys ( entity . metadata ) . forEach ( k = > keys . add ( k ) )
}
}
2025-10-17 12:29:27 -07:00
2025-09-11 16:23:32 -07:00
// Create CSV header
const headers = [ 'id' , . . . Array . from ( keys ) ]
const rows = [ headers . join ( ',' ) ]
2025-10-17 12:29:27 -07:00
2025-09-11 16:23:32 -07:00
// Add data rows
for ( const entity of entities ) {
const row = [ entity . id ]
for ( const key of keys ) {
const value = entity . metadata ? . [ key ] || ''
// Escape values that contain commas
2025-10-17 12:29:27 -07:00
const escaped = String ( value ) . includes ( ',' )
? ` " ${ String ( value ) . replace ( /"/g , '""' ) } " `
2025-09-11 16:23:32 -07:00
: String ( value )
row . push ( escaped )
}
rows . push ( row . join ( ',' ) )
}
2025-10-17 12:29:27 -07:00
2025-09-11 16:23:32 -07:00
return rows . join ( '\n' )
}
private generateId ( ) : string {
return ` import_ ${ Date . now ( ) } _ ${ Math . random ( ) . toString ( 36 ) . substr ( 2 , 9 ) } `
}
}