packfs-core
Version:
Semantic filesystem operations for LLM agent frameworks with natural language understanding. See LLM_AGENT_GUIDE.md for copy-paste examples.
254 lines • 8.56 kB
JavaScript
;
/**
* RxDB storage backend for NoSQL document-based storage
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.RxDBBackend = void 0;
const logger_js_1 = require("../core/logger.js");
const rxdb_1 = require("rxdb");
const storage_memory_1 = require("rxdb/plugins/storage-memory");
// RxDB schema definition
const fileSchema = {
version: 0,
primaryKey: 'path',
type: 'object',
properties: {
path: {
type: 'string',
maxLength: 1000
},
data: {
type: 'string'
},
size: {
type: 'number',
minimum: 0
},
mtime: {
type: 'number'
},
isDirectory: {
type: 'boolean'
},
permissions: {
type: 'number'
},
mimeType: {
type: 'string'
}
},
required: ['path', 'data', 'size', 'mtime', 'isDirectory', 'permissions']
};
class RxDBBackend {
constructor(options = {}) {
this.logger = logger_js_1.Logger.getInstance().createChildLogger('RxDBBackend');
this.options = {
name: options.name || 'packfs',
inMemory: options.inMemory ?? true,
password: options.password
};
}
async initialize() {
this.logger.info('Initializing RxDB backend', { options: this.options });
try {
// Create database
const db = await (0, rxdb_1.createRxDatabase)({
name: this.options.name,
storage: (0, storage_memory_1.getRxStorageMemory)(), // Use in-memory storage by default
password: this.options.password,
multiInstance: false,
eventReduce: true
});
// Create collection
await db.addCollections({
files: {
schema: fileSchema
}
});
this.db = db;
this.logger.info('RxDB backend initialized successfully');
}
catch (error) {
this.logger.error('Failed to initialize RxDB backend', error);
throw new Error(`Failed to initialize RxDB backend: ${error}`);
}
}
async read(path) {
this.logger.debug(`Reading file: ${path}`);
if (!this.db) {
throw new Error('Database not initialized');
}
try {
const doc = await this.db.files.findOne({
selector: { path }
}).exec();
if (!doc) {
this.logger.error(`File not found: ${path}`);
throw new Error(`File not found: ${path}`);
}
// Convert base64 back to Buffer
const buffer = Buffer.from(doc.data, 'base64');
this.logger.info(`Successfully read file: ${path}`, { size: buffer.length });
return buffer;
}
catch (error) {
this.logger.error(`Failed to read file: ${path}`, error);
throw error;
}
}
async write(path, data) {
this.logger.debug(`Writing file: ${path}`, { size: data.length });
if (!this.db) {
throw new Error('Database not initialized');
}
try {
const fileData = {
path,
data: data.toString('base64'), // Convert Buffer to base64 for storage
size: data.length,
mtime: Date.now(),
isDirectory: false,
permissions: 0o644
};
// Upsert the document
await this.db.files.upsert(fileData);
this.logger.info(`Successfully wrote file: ${path}`, { size: data.length });
}
catch (error) {
this.logger.error(`Failed to write file: ${path}`, error);
throw new Error(`Failed to write file ${path}: ${error}`);
}
}
async exists(path) {
this.logger.debug(`Checking existence: ${path}`);
if (!this.db) {
throw new Error('Database not initialized');
}
try {
const count = await this.db.files.count({
selector: { path }
}).exec();
const exists = count > 0;
this.logger.debug(`File ${exists ? 'exists' : 'does not exist'}: ${path}`);
return exists;
}
catch (error) {
this.logger.error(`Failed to check existence: ${path}`, error);
throw error;
}
}
async stat(path) {
this.logger.debug(`Getting file stats: ${path}`);
if (!this.db) {
throw new Error('Database not initialized');
}
try {
const doc = await this.db.files.findOne({
selector: { path }
}).exec();
if (!doc) {
this.logger.error(`File not found: ${path}`);
throw new Error(`File not found: ${path}`);
}
const metadata = {
path: doc.path,
size: doc.size,
mtime: new Date(doc.mtime),
isDirectory: doc.isDirectory,
permissions: doc.permissions,
mimeType: doc.mimeType
};
this.logger.debug(`Got file stats: ${path}`, metadata);
return metadata;
}
catch (error) {
this.logger.error(`Failed to get stats: ${path}`, error);
throw error;
}
}
async list(path) {
this.logger.debug(`Listing directory: ${path}`);
if (!this.db) {
throw new Error('Database not initialized');
}
try {
// Normalize path
const normalizedPath = path.endsWith('/') ? path : path + '/';
// Find all documents with paths that start with the directory path
const docs = await this.db.files.find({
selector: {
path: {
$regex: `^${normalizedPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[^/]+$`
}
}
}).exec();
// Extract just the filenames
const entries = docs.map(doc => {
const fullPath = doc.path;
const relativePath = fullPath.substring(normalizedPath.length);
return relativePath;
});
this.logger.info(`Listed directory: ${path}`, { count: entries.length });
return entries;
}
catch (error) {
this.logger.error(`Failed to list directory: ${path}`, error);
throw new Error(`Failed to list directory ${path}: ${error}`);
}
}
async delete(path) {
this.logger.debug(`Deleting: ${path}`);
if (!this.db) {
throw new Error('Database not initialized');
}
try {
const doc = await this.db.files.findOne({
selector: { path }
}).exec();
if (!doc) {
this.logger.error(`File not found: ${path}`);
throw new Error(`File not found: ${path}`);
}
await doc.remove();
this.logger.info(`Deleted file: ${path}`);
}
catch (error) {
this.logger.error(`Failed to delete file: ${path}`, error);
throw error;
}
}
async cleanup() {
this.logger.info('Cleaning up RxDB backend');
if (this.db) {
try {
await this.db.destroy();
this.db = undefined;
this.logger.info('RxDB backend cleaned up successfully');
}
catch (error) {
this.logger.error('Failed to cleanup RxDB backend', error);
throw error;
}
}
}
/**
* Get direct access to the RxDB database (for advanced queries)
*/
getDatabase() {
return this.db;
}
/**
* Create indexes for better query performance
*/
async createIndexes() {
if (!this.db) {
throw new Error('Database not initialized');
}
this.logger.info('Creating RxDB indexes');
// Note: RxDB automatically creates indexes for schema properties marked as indexed
// For more complex indexing, you would modify the schema definition
this.logger.info('Indexes configured in schema');
}
}
exports.RxDBBackend = RxDBBackend;
//# sourceMappingURL=rxdb.js.map