feat: add external plugin loader and examples for Brainy

Introduced `pluginLoader.ts` to enable loading and configuring augmentation plugins from external packages. Added `examples/externalPlugins.js` to demonstrate usage. Enhanced storage status reporting in `opfsStorage` and `BrainyData` with detailed storage capacity and usage methods. Updated README with an external plugin usage guide.
This commit is contained in:
David Snelling 2025-05-28 15:39:14 -07:00
parent 767c349f63
commit b031a40b1c
11 changed files with 2220 additions and 1033 deletions

View file

@ -451,4 +451,113 @@ export class FileSystemStorage implements StorageAdapter {
}
return obj
}
/**
* Get information about storage usage and capacity
*/
public async getStorageStatus(): Promise<{
type: string;
used: number;
quota: number | null;
details?: Record<string, any>;
}> {
await this.ensureInitialized()
try {
// Calculate the total size of all files in the storage directories
let totalSize = 0
// Helper function to calculate directory size
const calculateDirSize = async (dirPath: string): Promise<number> => {
let size = 0
try {
const files = await fs.promises.readdir(dirPath)
for (const file of files) {
const filePath = path.join(dirPath, file)
const stats = await fs.promises.stat(filePath)
if (stats.isDirectory()) {
size += await calculateDirSize(filePath)
} else {
size += stats.size
}
}
} catch (error) {
console.warn(`Error calculating size for ${dirPath}:`, error)
}
return size
}
// Calculate size for each directory
const nodesDirSize = await calculateDirSize(this.nodesDir)
const edgesDirSize = await calculateDirSize(this.edgesDir)
const metadataDirSize = await calculateDirSize(this.metadataDir)
totalSize = nodesDirSize + edgesDirSize + metadataDirSize
// Get filesystem information
let quota = null
let details = {}
try {
// Try to get disk space information
const stats = await fs.promises.statfs(this.rootDir)
if (stats) {
const availableSpace = stats.bavail * stats.bsize
const totalSpace = stats.blocks * stats.bsize
quota = totalSpace
details = {
availableSpace,
totalSpace,
freePercentage: (availableSpace / totalSpace) * 100
}
}
} catch (error) {
console.warn('Unable to get filesystem stats:', error)
// If statfs is not available, try to use df command on Unix-like systems
try {
const { exec } = await import('child_process')
const util = await import('util')
const execPromise = util.promisify(exec)
const { stdout } = await execPromise(`df -k "${this.rootDir}"`)
const lines = stdout.trim().split('\n')
if (lines.length > 1) {
const parts = lines[1].split(/\s+/)
if (parts.length >= 4) {
const totalKB = parseInt(parts[1], 10)
const usedKB = parseInt(parts[2], 10)
const availableKB = parseInt(parts[3], 10)
quota = totalKB * 1024
details = {
availableSpace: availableKB * 1024,
totalSpace: totalKB * 1024,
freePercentage: (availableKB / totalKB) * 100
}
}
}
} catch (dfError) {
console.warn('Unable to get disk space using df command:', dfError)
}
}
return {
type: 'filesystem',
used: totalSize,
quota,
details
}
} catch (error) {
console.error('Failed to get storage status:', error)
return {
type: 'filesystem',
used: 0,
quota: null,
details: { error: String(error) }
}
}
}
}

View file

@ -498,6 +498,89 @@ export class OPFSStorage implements StorageAdapter {
}
return obj
}
/**
* Get information about storage usage and capacity
*/
public async getStorageStatus(): Promise<{
type: string;
used: number;
quota: number | null;
details?: Record<string, any>;
}> {
await this.ensureInitialized()
try {
// Calculate the total size of all files in the storage directories
let totalSize = 0
// Helper function to calculate directory size
const calculateDirSize = async (dirHandle: FileSystemDirectoryHandle): Promise<number> => {
let size = 0
try {
// @ts-ignore - TypeScript doesn't recognize FileSystemDirectoryHandle.entries() properly
for await (const [name, handle] of dirHandle.entries()) {
if (handle.kind === 'file') {
const file = await handle.getFile()
size += file.size
} else if (handle.kind === 'directory') {
size += await calculateDirSize(handle)
}
}
} catch (error) {
console.warn(`Error calculating size for directory:`, error)
}
return size
}
// Calculate size for each directory
if (this.nodesDir) {
totalSize += await calculateDirSize(this.nodesDir)
}
if (this.edgesDir) {
totalSize += await calculateDirSize(this.edgesDir)
}
if (this.metadataDir) {
totalSize += await calculateDirSize(this.metadataDir)
}
// Get storage quota information using the Storage API
let quota = null
let details: Record<string, any> = {
isPersistent: await this.isPersistent()
}
try {
if (navigator.storage && navigator.storage.estimate) {
const estimate = await navigator.storage.estimate()
quota = estimate.quota || null
details = {
...details,
usage: estimate.usage,
quota: estimate.quota,
freePercentage: estimate.quota ? ((estimate.quota - (estimate.usage || 0)) / estimate.quota) * 100 : null
}
}
} catch (error) {
console.warn('Unable to get storage estimate:', error)
}
return {
type: 'opfs',
used: totalSize,
quota,
details
}
} catch (error) {
console.error('Failed to get storage status:', error)
return {
type: 'opfs',
used: 0,
quota: null,
details: { error: String(error) }
}
}
}
}
/**
@ -693,6 +776,114 @@ export class MemoryStorage implements StorageAdapter {
this.edges.clear()
this.metadata.clear()
}
/**
* Get information about storage usage and capacity
*/
public async getStorageStatus(): Promise<{
type: string;
used: number;
quota: number | null;
details?: Record<string, any>;
}> {
try {
// Estimate the size of data in memory
let totalSize = 0
// Helper function to estimate object size in bytes
const estimateSize = (obj: any): number => {
if (obj === null || obj === undefined) return 0
const type = typeof obj
// Handle primitive types
if (type === 'number') return 8
if (type === 'string') return obj.length * 2
if (type === 'boolean') return 4
// Handle arrays and objects
if (Array.isArray(obj)) {
return obj.reduce((size, item) => size + estimateSize(item), 0)
}
if (type === 'object') {
if (obj instanceof Map) {
let mapSize = 0
for (const [key, value] of obj.entries()) {
mapSize += estimateSize(key) + estimateSize(value)
}
return mapSize
}
if (obj instanceof Set) {
let setSize = 0
for (const item of obj) {
setSize += estimateSize(item)
}
return setSize
}
// Regular object
return Object.entries(obj).reduce(
(size, [key, value]) => size + key.length * 2 + estimateSize(value),
0
)
}
return 0
}
// Estimate size of nodes
for (const node of this.nodes.values()) {
totalSize += estimateSize(node)
}
// Estimate size of edges
for (const edge of this.edges.values()) {
totalSize += estimateSize(edge)
}
// Estimate size of metadata
for (const meta of this.metadata.values()) {
totalSize += estimateSize(meta)
}
// Get memory information if available
let quota = null
let details: Record<string, any> = {
nodesCount: this.nodes.size,
edgesCount: this.edges.size,
metadataCount: this.metadata.size
}
// Try to get memory information if in a browser environment
if (typeof window !== 'undefined' && (window as any).performance && (window as any).performance.memory) {
const memory = (window as any).performance.memory
quota = memory.jsHeapSizeLimit
details = {
...details,
totalJSHeapSize: memory.totalJSHeapSize,
usedJSHeapSize: memory.usedJSHeapSize,
jsHeapSizeLimit: memory.jsHeapSizeLimit
}
}
return {
type: 'memory',
used: totalSize,
quota,
details
}
} catch (error) {
console.error('Failed to get memory storage status:', error)
return {
type: 'memory',
used: 0,
quota: null,
details: { error: String(error) }
}
}
}
}
/**