@tehreet/conduit
Version:
LLM API gateway with intelligent routing, robust process management, and health monitoring
264 lines • 8.76 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UsageStorage = void 0;
const events_1 = require("events");
const promises_1 = require("fs/promises");
const path_1 = require("path");
const os_1 = require("os");
const log_1 = require("../utils/log");
/**
* Storage layer for usage data
*/
class UsageStorage extends events_1.EventEmitter {
constructor(config = {}) {
super();
this.memoryStore = [];
this.isInitialized = false;
this.config = {
type: 'memory',
retention: 30,
maxSize: 10000,
...config
};
this.filePath = this.config.path || (0, path_1.join)((0, os_1.homedir)(), '.conduit', 'usage.json');
}
/**
* Initialize storage
*/
async initialize() {
if (this.isInitialized) {
return;
}
(0, log_1.log)('Initializing usage storage...');
if (this.config.type === 'file') {
await this.initializeFileStorage();
}
else if (this.config.type === 'database') {
await this.initializeDatabaseStorage();
}
this.isInitialized = true;
this.emit('initialized');
(0, log_1.log)(`Usage storage initialized (type: ${this.config.type})`);
}
/**
* Store usage data
*/
async store(usage) {
if (!this.isInitialized) {
throw new Error('Storage not initialized');
}
try {
switch (this.config.type) {
case 'memory':
await this.storeInMemory(usage);
break;
case 'file':
await this.storeInFile(usage);
break;
case 'database':
await this.storeInDatabase(usage);
break;
}
this.emit('stored', usage);
}
catch (error) {
(0, log_1.log)('Error storing usage data:', error);
this.emit('error', error);
throw error;
}
}
/**
* Query usage data
*/
async query(query = {}) {
if (!this.isInitialized) {
throw new Error('Storage not initialized');
}
try {
let data = [];
switch (this.config.type) {
case 'memory':
data = await this.queryMemory(query);
break;
case 'file':
data = await this.queryFile(query);
break;
case 'database':
data = await this.queryDatabase(query);
break;
}
return this.applyFilters(data, query);
}
catch (error) {
(0, log_1.log)('Error querying usage data:', error);
this.emit('error', error);
throw error;
}
}
/**
* Cleanup old data
*/
async cleanup(cutoffDate) {
if (!this.isInitialized) {
return;
}
const cutoff = cutoffDate || new Date(Date.now() - (this.config.retention * 24 * 60 * 60 * 1000));
try {
switch (this.config.type) {
case 'memory':
await this.cleanupMemory(cutoff);
break;
case 'file':
await this.cleanupFile(cutoff);
break;
case 'database':
await this.cleanupDatabase(cutoff);
break;
}
this.emit('cleanup', { cutoffDate: cutoff });
}
catch (error) {
(0, log_1.log)('Error cleaning up usage data:', error);
this.emit('error', error);
}
}
/**
* Update configuration
*/
async updateConfig(config) {
this.config = { ...this.config, ...config };
if (this.config.path) {
this.filePath = this.config.path;
}
this.emit('config-updated', this.config);
}
/**
* Get storage status
*/
getStatus() {
return {
initialized: this.isInitialized,
type: this.config.type,
entryCount: this.memoryStore.length,
filePath: this.config.type === 'file' ? this.filePath : undefined
};
}
// Memory storage implementation
async storeInMemory(usage) {
this.memoryStore.push(usage);
// Auto-cleanup if max size exceeded
if (this.memoryStore.length > this.config.maxSize) {
const excess = this.memoryStore.length - this.config.maxSize;
this.memoryStore.splice(0, excess);
}
}
async queryMemory(_query) {
return [...this.memoryStore];
}
async cleanupMemory(cutoffDate) {
const initialCount = this.memoryStore.length;
this.memoryStore = this.memoryStore.filter(usage => usage.timestamp > cutoffDate);
const removedCount = initialCount - this.memoryStore.length;
if (removedCount > 0) {
(0, log_1.log)(`Cleaned up ${removedCount} usage entries from memory`);
}
}
// File storage implementation
async initializeFileStorage() {
const dir = this.filePath.substring(0, this.filePath.lastIndexOf('/'));
try {
await (0, promises_1.stat)(dir);
}
catch {
await (0, promises_1.mkdir)(dir, { recursive: true });
}
// Load existing data
try {
const data = await (0, promises_1.readFile)(this.filePath, 'utf8');
this.memoryStore = JSON.parse(data).map((item) => ({
...item,
timestamp: new Date(item.timestamp)
}));
}
catch {
// File doesn't exist or is invalid, start fresh
this.memoryStore = [];
}
}
async storeInFile(usage) {
this.memoryStore.push(usage);
// Auto-cleanup if max size exceeded
if (this.memoryStore.length > this.config.maxSize) {
const excess = this.memoryStore.length - this.config.maxSize;
this.memoryStore.splice(0, excess);
}
await this.saveToFile();
}
async queryFile(_query) {
return [...this.memoryStore];
}
async cleanupFile(cutoffDate) {
const initialCount = this.memoryStore.length;
this.memoryStore = this.memoryStore.filter(usage => usage.timestamp > cutoffDate);
const removedCount = initialCount - this.memoryStore.length;
if (removedCount > 0) {
await this.saveToFile();
(0, log_1.log)(`Cleaned up ${removedCount} usage entries from file`);
}
}
async saveToFile() {
const data = JSON.stringify(this.memoryStore, null, 2);
await (0, promises_1.writeFile)(this.filePath, data, 'utf8');
}
// Database storage implementation (placeholder)
async initializeDatabaseStorage() {
// TODO: Implement database storage
(0, log_1.log)('Database storage not implemented, falling back to memory');
this.config.type = 'memory';
}
async storeInDatabase(usage) {
// TODO: Implement database storage
await this.storeInMemory(usage);
}
async queryDatabase(query) {
// TODO: Implement database storage
return this.queryMemory(query);
}
async cleanupDatabase(cutoffDate) {
// TODO: Implement database storage
await this.cleanupMemory(cutoffDate);
}
// Helper methods
applyFilters(data, query) {
let filtered = data;
// Filter by project ID
if (query.projectId) {
filtered = filtered.filter(item => item.projectId === query.projectId);
}
// Filter by agent ID
if (query.agentId) {
filtered = filtered.filter(item => item.agentId === query.agentId);
}
// Filter by model
if (query.model) {
filtered = filtered.filter(item => item.model === query.model);
}
// Filter by time range
if (query.timeRange) {
filtered = filtered.filter(item => item.timestamp >= query.timeRange.start &&
item.timestamp <= query.timeRange.end);
}
// Sort by timestamp (newest first)
filtered.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
// Apply pagination
if (query.offset) {
filtered = filtered.slice(query.offset);
}
if (query.limit) {
filtered = filtered.slice(0, query.limit);
}
return filtered;
}
}
exports.UsageStorage = UsageStorage;
//# sourceMappingURL=UsageStorage.js.map