2025-06-24 11:41:30 -07:00
|
|
|
import { GraphVerb, HNSWNoun, StorageAdapter } from '../coreTypes.js'
|
|
|
|
|
|
2025-07-11 11:11:56 -07:00
|
|
|
// Dynamically and asynchronously load Node.js modules at the top level.
|
|
|
|
|
// This ensures they are available as soon as this module is imported,
|
|
|
|
|
// preventing race conditions with dependencies like TensorFlow.js.
|
2025-06-24 11:41:30 -07:00
|
|
|
let fs: any
|
|
|
|
|
let path: any
|
|
|
|
|
|
2025-07-11 11:11:56 -07:00
|
|
|
const nodeModulesPromise = (async () => {
|
|
|
|
|
// A reliable check for a Node.js environment.
|
|
|
|
|
const isNode =
|
|
|
|
|
typeof process !== 'undefined' &&
|
|
|
|
|
process.versions != null &&
|
|
|
|
|
process.versions.node != null
|
|
|
|
|
|
|
|
|
|
if (!isNode) {
|
|
|
|
|
return { fs: null, path: null }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// Use the 'node:' prefix for unambiguous importing of built-in modules.
|
|
|
|
|
const fsModule = await import('node:fs')
|
|
|
|
|
const pathModule = await import('node:path')
|
|
|
|
|
// Return the modules, preferring the default export if it exists.
|
|
|
|
|
return {
|
|
|
|
|
fs: fsModule.default || fsModule,
|
|
|
|
|
path: pathModule.default || pathModule
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
console.error(
|
|
|
|
|
'FileSystemStorage: Failed to load Node.js modules. This adapter is not supported in this environment.',
|
|
|
|
|
error
|
|
|
|
|
)
|
|
|
|
|
return { fs: null, path: null }
|
|
|
|
|
}
|
|
|
|
|
})()
|
|
|
|
|
|
|
|
|
|
// Immediately assign the modules once the promise resolves.
|
|
|
|
|
nodeModulesPromise.then((modules) => {
|
|
|
|
|
fs = modules.fs
|
|
|
|
|
path = modules.path
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
// --- End of Refactored Code ---
|
2025-07-02 16:16:19 -07:00
|
|
|
|
2025-06-24 11:41:30 -07:00
|
|
|
// Constants for directory and file names
|
|
|
|
|
const ROOT_DIR = 'brainy-data'
|
|
|
|
|
const NOUNS_DIR = 'nouns'
|
|
|
|
|
const VERBS_DIR = 'verbs'
|
|
|
|
|
const METADATA_DIR = 'metadata'
|
|
|
|
|
|
2025-07-21 12:46:41 -07:00
|
|
|
// All nouns now use the same directory - no separate directories per noun type
|
2025-06-24 11:41:30 -07:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* File system storage adapter for Node.js environments
|
|
|
|
|
*/
|
|
|
|
|
export class FileSystemStorage implements StorageAdapter {
|
|
|
|
|
private rootDir: string
|
|
|
|
|
private nounsDir: string
|
|
|
|
|
private verbsDir: string
|
|
|
|
|
private metadataDir: string
|
|
|
|
|
private isInitialized = false
|
|
|
|
|
|
|
|
|
|
constructor(rootDirectory?: string) {
|
|
|
|
|
// We'll set the paths in the init method after dynamically importing the modules
|
|
|
|
|
this.rootDir = rootDirectory || ''
|
|
|
|
|
this.nounsDir = ''
|
|
|
|
|
this.verbsDir = ''
|
|
|
|
|
this.metadataDir = ''
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Initialize the storage adapter
|
|
|
|
|
*/
|
|
|
|
|
public async init(): Promise<void> {
|
|
|
|
|
if (this.isInitialized) {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-11 11:11:56 -07:00
|
|
|
// Wait for the top-level module loading to complete.
|
|
|
|
|
await nodeModulesPromise
|
2025-07-02 16:16:19 -07:00
|
|
|
|
2025-07-11 11:11:56 -07:00
|
|
|
// Check if the modules were loaded successfully.
|
|
|
|
|
if (!fs || !path) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
'FileSystemStorage requires a Node.js environment, but `fs` and `path` modules could not be loaded.'
|
|
|
|
|
)
|
|
|
|
|
}
|
2025-06-24 11:41:30 -07:00
|
|
|
|
2025-07-11 11:11:56 -07:00
|
|
|
try {
|
2025-07-02 16:16:19 -07:00
|
|
|
// Now set up the directory paths
|
|
|
|
|
const rootDir = this.rootDir || process.cwd()
|
2025-07-18 10:59:14 -07:00
|
|
|
|
|
|
|
|
// Check if rootDir already ends with ROOT_DIR to prevent duplication
|
|
|
|
|
if (rootDir.endsWith(ROOT_DIR)) {
|
|
|
|
|
this.rootDir = rootDir
|
|
|
|
|
} else {
|
|
|
|
|
this.rootDir = path.resolve(rootDir, ROOT_DIR)
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-02 16:16:19 -07:00
|
|
|
this.nounsDir = path.join(this.rootDir, NOUNS_DIR)
|
|
|
|
|
this.verbsDir = path.join(this.rootDir, VERBS_DIR)
|
|
|
|
|
this.metadataDir = path.join(this.rootDir, METADATA_DIR)
|
|
|
|
|
|
2025-06-24 11:41:30 -07:00
|
|
|
// Create directories if they don't exist
|
|
|
|
|
await this.ensureDirectoryExists(this.rootDir)
|
|
|
|
|
await this.ensureDirectoryExists(this.nounsDir)
|
|
|
|
|
await this.ensureDirectoryExists(this.verbsDir)
|
|
|
|
|
await this.ensureDirectoryExists(this.metadataDir)
|
|
|
|
|
|
|
|
|
|
this.isInitialized = true
|
2025-07-11 11:11:56 -07:00
|
|
|
} catch (error: any) {
|
|
|
|
|
console.error('Error initializing FileSystemStorage:', error)
|
|
|
|
|
throw error
|
2025-06-24 11:41:30 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-11 11:11:56 -07:00
|
|
|
private async ensureDirectoryExists(dirPath: string): Promise<void> {
|
2025-06-24 11:41:30 -07:00
|
|
|
try {
|
2025-07-11 11:11:56 -07:00
|
|
|
await fs.promises.mkdir(dirPath, { recursive: true })
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
// Ignore EEXIST error, which means the directory already exists
|
|
|
|
|
if (error.code !== 'EEXIST') {
|
|
|
|
|
throw error
|
2025-06-24 11:41:30 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-11 11:11:56 -07:00
|
|
|
private getNounPath(id: string, nounType?: string): string {
|
2025-07-21 12:46:41 -07:00
|
|
|
// All nouns now use the same directory regardless of type
|
|
|
|
|
return path.join(this.nounsDir, `${id}.json`)
|
2025-06-24 11:41:30 -07:00
|
|
|
}
|
|
|
|
|
|
2025-07-11 11:11:56 -07:00
|
|
|
public async saveNoun(
|
|
|
|
|
noun: HNSWNoun & { metadata?: { noun?: string } }
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
if (!this.isInitialized) await this.init()
|
|
|
|
|
const nounType = (noun as any).metadata?.noun
|
|
|
|
|
const filePath = this.getNounPath(noun.id, nounType)
|
|
|
|
|
await this.ensureDirectoryExists(path.dirname(filePath))
|
|
|
|
|
await fs.promises.writeFile(filePath, JSON.stringify(noun, null, 2))
|
2025-06-24 11:41:30 -07:00
|
|
|
}
|
|
|
|
|
|
2025-07-11 11:11:56 -07:00
|
|
|
public async getNoun(id: string): Promise<HNSWNoun | null> {
|
|
|
|
|
if (!this.isInitialized) await this.init()
|
2025-07-21 12:46:41 -07:00
|
|
|
const filePath = path.join(this.nounsDir, `${id}.json`)
|
|
|
|
|
try {
|
|
|
|
|
const data = await fs.promises.readFile(filePath, 'utf-8')
|
|
|
|
|
return JSON.parse(data)
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
if (error.code !== 'ENOENT') {
|
|
|
|
|
console.error(`Error reading noun ${id}:`, error)
|
2025-06-24 11:41:30 -07:00
|
|
|
}
|
2025-07-21 12:46:41 -07:00
|
|
|
return null
|
2025-06-24 11:41:30 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public async deleteNoun(id: string): Promise<void> {
|
2025-07-11 11:11:56 -07:00
|
|
|
if (!this.isInitialized) await this.init()
|
|
|
|
|
const noun = await this.getNoun(id)
|
|
|
|
|
if (noun) {
|
|
|
|
|
const nounType = (noun as any).metadata?.noun
|
|
|
|
|
const filePath = this.getNounPath(id, nounType)
|
2025-06-24 11:41:30 -07:00
|
|
|
try {
|
|
|
|
|
await fs.promises.unlink(filePath)
|
2025-07-11 11:11:56 -07:00
|
|
|
} catch (error: any) {
|
|
|
|
|
if (error.code !== 'ENOENT') {
|
|
|
|
|
console.error(`Error deleting noun file ${filePath}:`, error)
|
|
|
|
|
throw error
|
2025-06-24 11:41:30 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-11 11:11:56 -07:00
|
|
|
public async getAllNouns(): Promise<HNSWNoun[]> {
|
|
|
|
|
if (!this.isInitialized) await this.init()
|
|
|
|
|
const allNouns: HNSWNoun[] = []
|
2025-07-21 12:46:41 -07:00
|
|
|
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')
|
|
|
|
|
allNouns.push(JSON.parse(data))
|
2025-07-11 11:11:56 -07:00
|
|
|
}
|
2025-06-24 11:41:30 -07:00
|
|
|
}
|
2025-07-21 12:46:41 -07:00
|
|
|
} catch (error: any) {
|
|
|
|
|
if (error.code !== 'ENOENT') {
|
|
|
|
|
console.error(`Error reading directory ${this.nounsDir}:`, error)
|
|
|
|
|
}
|
2025-06-24 11:41:30 -07:00
|
|
|
}
|
2025-07-11 11:11:56 -07:00
|
|
|
return allNouns
|
2025-06-24 11:41:30 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-11 11:11:56 -07:00
|
|
|
* Get nouns by noun type
|
|
|
|
|
* @param nounType The noun type to filter by
|
|
|
|
|
* @returns Promise that resolves to an array of nouns of the specified noun type
|
2025-06-24 11:41:30 -07:00
|
|
|
*/
|
2025-07-11 11:11:56 -07:00
|
|
|
public async getNounsByNounType(nounType: string): Promise<HNSWNoun[]> {
|
|
|
|
|
if (!this.isInitialized) await this.init()
|
|
|
|
|
|
|
|
|
|
const nouns: HNSWNoun[] = []
|
2025-06-24 11:41:30 -07:00
|
|
|
try {
|
2025-07-21 12:46:41 -07:00
|
|
|
const files = await fs.promises.readdir(this.nounsDir)
|
2025-07-11 11:11:56 -07:00
|
|
|
for (const file of files) {
|
|
|
|
|
if (file.endsWith('.json')) {
|
2025-07-21 12:46:41 -07:00
|
|
|
const filePath = path.join(this.nounsDir, file)
|
2025-07-11 11:11:56 -07:00
|
|
|
const data = await fs.promises.readFile(filePath, 'utf-8')
|
2025-07-21 12:46:41 -07:00
|
|
|
const noun = JSON.parse(data)
|
|
|
|
|
|
|
|
|
|
// Filter by noun type using metadata
|
|
|
|
|
const metadata = await this.getMetadata(noun.id)
|
|
|
|
|
if (metadata && metadata.noun === nounType) {
|
|
|
|
|
nouns.push(noun)
|
|
|
|
|
}
|
2025-07-11 11:11:56 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
if (error.code !== 'ENOENT') {
|
2025-07-21 12:46:41 -07:00
|
|
|
console.error(`Error reading directory ${this.nounsDir}:`, error)
|
2025-06-24 11:41:30 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-11 11:11:56 -07:00
|
|
|
return nouns
|
|
|
|
|
}
|
2025-06-24 11:41:30 -07:00
|
|
|
|
2025-07-11 11:11:56 -07:00
|
|
|
public async saveVerb(verb: GraphVerb): Promise<void> {
|
|
|
|
|
if (!this.isInitialized) await this.init()
|
|
|
|
|
const filePath = path.join(this.verbsDir, `${verb.id}.json`)
|
|
|
|
|
await fs.promises.writeFile(filePath, JSON.stringify(verb, null, 2))
|
2025-06-24 11:41:30 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-11 11:11:56 -07:00
|
|
|
* Get a verb by its ID
|
|
|
|
|
* @param id The ID of the verb to retrieve
|
|
|
|
|
* @returns Promise that resolves to the verb or null if not found
|
2025-06-24 11:41:30 -07:00
|
|
|
*/
|
2025-07-11 11:11:56 -07:00
|
|
|
public async getVerb(id: string): Promise<GraphVerb | null> {
|
|
|
|
|
if (!this.isInitialized) await this.init()
|
|
|
|
|
const filePath = path.join(this.verbsDir, `${id}.json`)
|
2025-06-24 11:41:30 -07:00
|
|
|
try {
|
2025-07-11 11:11:56 -07:00
|
|
|
const data = await fs.promises.readFile(filePath, 'utf-8')
|
2025-06-24 11:41:30 -07:00
|
|
|
return JSON.parse(data)
|
2025-07-11 11:11:56 -07:00
|
|
|
} catch (error: any) {
|
|
|
|
|
if (error.code !== 'ENOENT') {
|
|
|
|
|
console.error(`Error reading verb ${id}:`, error)
|
|
|
|
|
}
|
2025-06-24 11:41:30 -07:00
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-11 11:11:56 -07:00
|
|
|
public async getVerbsBySource(sourceId: string): Promise<GraphVerb[]> {
|
|
|
|
|
if (!this.isInitialized) await this.init()
|
|
|
|
|
const allVerbs = await this.getAllVerbs()
|
|
|
|
|
return allVerbs.filter((verb) => verb.sourceId === sourceId)
|
2025-06-24 11:41:30 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-11 11:11:56 -07:00
|
|
|
* Get verbs by target ID
|
|
|
|
|
* @param targetId The target ID to filter by
|
|
|
|
|
* @returns Promise that resolves to an array of verbs with the specified target ID
|
2025-06-24 11:41:30 -07:00
|
|
|
*/
|
2025-07-11 11:11:56 -07:00
|
|
|
public async getVerbsByTarget(targetId: string): Promise<GraphVerb[]> {
|
|
|
|
|
if (!this.isInitialized) await this.init()
|
|
|
|
|
const allVerbs = await this.getAllVerbs()
|
|
|
|
|
return allVerbs.filter((verb) => verb.targetId === targetId)
|
2025-06-24 11:41:30 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-11 11:11:56 -07:00
|
|
|
* Get verbs by type
|
|
|
|
|
* @param type The verb type to filter by
|
|
|
|
|
* @returns Promise that resolves to an array of verbs of the specified type
|
2025-06-24 11:41:30 -07:00
|
|
|
*/
|
2025-07-11 11:11:56 -07:00
|
|
|
public async getVerbsByType(type: string): Promise<GraphVerb[]> {
|
|
|
|
|
if (!this.isInitialized) await this.init()
|
|
|
|
|
const allVerbs = await this.getAllVerbs()
|
|
|
|
|
return allVerbs.filter((verb) => verb.type === type)
|
2025-06-24 11:41:30 -07:00
|
|
|
}
|
|
|
|
|
|
2025-07-11 11:11:56 -07:00
|
|
|
public async getAllVerbs(): Promise<GraphVerb[]> {
|
|
|
|
|
if (!this.isInitialized) await this.init()
|
|
|
|
|
const allVerbs: GraphVerb[] = []
|
2025-06-24 11:41:30 -07:00
|
|
|
try {
|
2025-07-11 11:11:56 -07:00
|
|
|
const files = await fs.promises.readdir(this.verbsDir)
|
2025-06-24 11:41:30 -07:00
|
|
|
for (const file of files) {
|
2025-07-11 11:11:56 -07:00
|
|
|
if (file.endsWith('.json')) {
|
|
|
|
|
const filePath = path.join(this.verbsDir, file)
|
|
|
|
|
const data = await fs.promises.readFile(filePath, 'utf-8')
|
|
|
|
|
allVerbs.push(JSON.parse(data))
|
2025-06-24 11:41:30 -07:00
|
|
|
}
|
|
|
|
|
}
|
2025-07-11 11:11:56 -07:00
|
|
|
} catch (error: any) {
|
|
|
|
|
if (error.code !== 'ENOENT') {
|
|
|
|
|
console.error(`Error reading verbs directory ${this.verbsDir}:`, error)
|
2025-06-24 11:41:30 -07:00
|
|
|
}
|
|
|
|
|
}
|
2025-07-11 11:11:56 -07:00
|
|
|
return allVerbs
|
2025-06-24 11:41:30 -07:00
|
|
|
}
|
|
|
|
|
|
2025-07-11 11:11:56 -07:00
|
|
|
public async deleteVerb(id: string): Promise<void> {
|
|
|
|
|
if (!this.isInitialized) await this.init()
|
|
|
|
|
const filePath = path.join(this.verbsDir, `${id}.json`)
|
2025-06-24 11:41:30 -07:00
|
|
|
try {
|
2025-07-11 11:11:56 -07:00
|
|
|
await fs.promises.unlink(filePath)
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
if (error.code !== 'ENOENT') {
|
|
|
|
|
console.error(`Error deleting verb file ${filePath}:`, error)
|
|
|
|
|
throw error
|
2025-06-24 11:41:30 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-11 11:11:56 -07:00
|
|
|
* Save metadata for an entity
|
|
|
|
|
* @param id The ID of the entity
|
|
|
|
|
* @param metadata The metadata to save
|
2025-06-24 11:41:30 -07:00
|
|
|
*/
|
2025-07-11 11:11:56 -07:00
|
|
|
public async saveMetadata(id: string, metadata: any): Promise<void> {
|
|
|
|
|
if (!this.isInitialized) await this.init()
|
|
|
|
|
const filePath = path.join(this.metadataDir, `${id}.json`)
|
2025-07-16 13:51:00 -07:00
|
|
|
await this.ensureDirectoryExists(path.dirname(filePath))
|
2025-07-11 11:11:56 -07:00
|
|
|
await fs.promises.writeFile(filePath, JSON.stringify(metadata, null, 2))
|
2025-06-24 11:41:30 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-07-11 11:11:56 -07:00
|
|
|
* Get metadata for an entity
|
|
|
|
|
* @param id The ID of the entity
|
|
|
|
|
* @returns Promise that resolves to the metadata or null if not found
|
2025-06-24 11:41:30 -07:00
|
|
|
*/
|
2025-07-11 11:11:56 -07:00
|
|
|
public async getMetadata(id: string): Promise<any | null> {
|
|
|
|
|
if (!this.isInitialized) await this.init()
|
|
|
|
|
const filePath = path.join(this.metadataDir, `${id}.json`)
|
2025-06-24 11:41:30 -07:00
|
|
|
try {
|
2025-07-11 11:11:56 -07:00
|
|
|
const data = await fs.promises.readFile(filePath, 'utf-8')
|
|
|
|
|
return JSON.parse(data)
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
if (error.code !== 'ENOENT') {
|
|
|
|
|
console.error(`Error reading metadata for ${id}:`, error)
|
2025-06-24 11:41:30 -07:00
|
|
|
}
|
2025-07-11 11:11:56 -07:00
|
|
|
return null
|
2025-06-24 11:41:30 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-11 11:11:56 -07:00
|
|
|
public async clear(): Promise<void> {
|
|
|
|
|
if (!this.isInitialized) await this.init()
|
2025-07-16 13:51:00 -07:00
|
|
|
|
|
|
|
|
// Helper function to recursively remove directory contents
|
|
|
|
|
const removeDirectoryContents = async (dirPath: string): Promise<void> => {
|
|
|
|
|
try {
|
|
|
|
|
const files = await fs.promises.readdir(dirPath, { withFileTypes: true })
|
|
|
|
|
|
|
|
|
|
for (const file of files) {
|
|
|
|
|
const fullPath = path.join(dirPath, file.name)
|
|
|
|
|
|
|
|
|
|
if (file.isDirectory()) {
|
|
|
|
|
await removeDirectoryContents(fullPath)
|
|
|
|
|
// Use fs.promises.rm with recursive option instead of rmdir
|
|
|
|
|
try {
|
|
|
|
|
await fs.promises.rm(fullPath, { recursive: true, force: true })
|
|
|
|
|
} catch (rmError: any) {
|
|
|
|
|
// Fallback to rmdir if rm fails
|
|
|
|
|
await fs.promises.rmdir(fullPath)
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
await fs.promises.unlink(fullPath)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
if (error.code !== 'ENOENT') {
|
|
|
|
|
console.error(`Error removing directory contents ${dirPath}:`, error)
|
|
|
|
|
throw error
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
// First try the modern approach
|
|
|
|
|
await fs.promises.rm(this.rootDir, { recursive: true, force: true })
|
|
|
|
|
} catch (error: any) {
|
|
|
|
|
console.warn('Modern rm failed, falling back to manual cleanup:', error)
|
|
|
|
|
|
|
|
|
|
// Fallback: manually remove contents then directory
|
|
|
|
|
try {
|
|
|
|
|
await removeDirectoryContents(this.rootDir)
|
|
|
|
|
// Use fs.promises.rm with recursive option instead of rmdir
|
|
|
|
|
try {
|
|
|
|
|
await fs.promises.rm(this.rootDir, { recursive: true, force: true })
|
|
|
|
|
} catch (rmError: any) {
|
|
|
|
|
// Final fallback to rmdir if rm fails
|
|
|
|
|
await fs.promises.rmdir(this.rootDir)
|
|
|
|
|
}
|
|
|
|
|
} catch (fallbackError: any) {
|
|
|
|
|
if (fallbackError.code !== 'ENOENT') {
|
|
|
|
|
console.error('Manual cleanup also failed:', fallbackError)
|
|
|
|
|
throw fallbackError
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-11 11:11:56 -07:00
|
|
|
this.isInitialized = false // Reset state
|
|
|
|
|
await this.init() // Re-create directories
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-24 11:41:30 -07:00
|
|
|
public async getStorageStatus(): Promise<{
|
2025-06-26 10:49:07 -07:00
|
|
|
type: string
|
|
|
|
|
used: number
|
|
|
|
|
quota: number | null
|
|
|
|
|
details?: Record<string, any>
|
2025-06-24 11:41:30 -07:00
|
|
|
}> {
|
2025-07-11 11:11:56 -07:00
|
|
|
if (!this.isInitialized) await this.init()
|
2025-06-24 11:41:30 -07:00
|
|
|
|
2025-07-11 11:11:56 -07:00
|
|
|
const calculateSize = async (dirPath: string): Promise<number> => {
|
|
|
|
|
let size = 0
|
2025-06-24 11:41:30 -07:00
|
|
|
try {
|
2025-07-11 11:11:56 -07:00
|
|
|
const files = await fs.promises.readdir(dirPath, {
|
|
|
|
|
withFileTypes: true
|
|
|
|
|
})
|
|
|
|
|
for (const file of files) {
|
|
|
|
|
const fullPath = path.join(dirPath, file.name)
|
|
|
|
|
if (file.isDirectory()) {
|
|
|
|
|
size += await calculateSize(fullPath)
|
|
|
|
|
} else {
|
|
|
|
|
const stats = await fs.promises.stat(fullPath)
|
|
|
|
|
size += stats.size
|
2025-06-24 11:41:30 -07:00
|
|
|
}
|
|
|
|
|
}
|
2025-07-11 11:11:56 -07:00
|
|
|
} catch (error: any) {
|
|
|
|
|
if (error.code !== 'ENOENT') {
|
|
|
|
|
console.error(`Could not calculate size for ${dirPath}:`, error)
|
2025-06-24 11:41:30 -07:00
|
|
|
}
|
|
|
|
|
}
|
2025-07-11 11:11:56 -07:00
|
|
|
return size
|
|
|
|
|
}
|
2025-06-24 11:41:30 -07:00
|
|
|
|
2025-07-11 11:11:56 -07:00
|
|
|
const totalSize = await calculateSize(this.rootDir)
|
|
|
|
|
const nouns = await this.getAllNouns()
|
|
|
|
|
const verbs = await this.getAllVerbs()
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
type: 'FileSystem',
|
|
|
|
|
used: totalSize,
|
|
|
|
|
quota: null, // File system quota is not easily available from Node.js
|
|
|
|
|
details: {
|
|
|
|
|
rootDir: this.rootDir,
|
|
|
|
|
nounCount: nouns.length,
|
|
|
|
|
verbCount: verbs.length
|
2025-06-24 11:41:30 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|