@ahmedhegazee/nestjs-telescope
Version:
Advanced observability and monitoring solution for NestJS applications with ML-powered analytics, enterprise features, and production-ready scaling
302 lines • 11.9 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var FileStorageDriver_1;
Object.defineProperty(exports, "__esModule", { value: true });
exports.FileStorageDriver = void 0;
const common_1 = require("@nestjs/common");
const fs_1 = require("fs");
const path = __importStar(require("path"));
let FileStorageDriver = FileStorageDriver_1 = class FileStorageDriver {
constructor(config) {
this.logger = new common_1.Logger(FileStorageDriver_1.name);
this.index = new Map();
this.initialized = false;
this.storageDir = config?.file?.directory || './telescope-storage';
this.indexFile = path.join(this.storageDir, 'index.json');
this.initializeStorage();
}
async initializeStorage() {
if (this.initialized)
return;
try {
await fs_1.promises.mkdir(this.storageDir, { recursive: true });
await this.loadIndex();
this.initialized = true;
this.logger.log(`File storage initialized at: ${this.storageDir}`);
}
catch (error) {
this.logger.error('Failed to initialize file storage:', error);
throw error;
}
}
async store(entry) {
await this.ensureInitialized();
const filename = `${entry.id}.json`;
const filepath = path.join(this.storageDir, filename);
try {
await fs_1.promises.writeFile(filepath, JSON.stringify(entry, null, 2));
this.index.set(entry.id, {
filename,
type: entry.type,
timestamp: entry.timestamp,
tags: entry.tags,
familyHash: entry.familyHash,
sequence: entry.sequence
});
await this.saveIndex();
this.logger.debug(`Stored entry: ${entry.id}`);
}
catch (error) {
this.logger.error(`Failed to store entry ${entry.id}:`, error);
throw error;
}
}
async storeBatch(entries) {
await this.ensureInitialized();
try {
const concurrency = 5;
const chunks = this.chunkArray(entries, concurrency);
for (const chunk of chunks) {
await Promise.all(chunk.map(entry => this.store(entry)));
}
this.logger.debug(`Stored batch of ${entries.length} entries`);
}
catch (error) {
this.logger.error('Failed to store batch:', error);
throw error;
}
}
async find(filter) {
await this.ensureInitialized();
let filteredEntries = Array.from(this.index.values());
if (filter?.type) {
filteredEntries = filteredEntries.filter(entry => entry.type === filter.type);
}
if (filter?.tags && filter.tags.length > 0) {
filteredEntries = filteredEntries.filter(entry => filter.tags.some(tag => entry.tags.includes(tag)));
}
if (filter?.dateFrom) {
filteredEntries = filteredEntries.filter(entry => entry.timestamp >= filter.dateFrom);
}
if (filter?.dateTo) {
filteredEntries = filteredEntries.filter(entry => entry.timestamp <= filter.dateTo);
}
filteredEntries.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
const offset = filter?.offset || 0;
const limit = filter?.limit || 100;
const total = filteredEntries.length;
const paginatedEntries = filteredEntries.slice(offset, offset + limit);
const entries = await Promise.all(paginatedEntries.map(async (indexEntry) => {
try {
const filepath = path.join(this.storageDir, indexEntry.filename);
const content = await fs_1.promises.readFile(filepath, 'utf-8');
return JSON.parse(content);
}
catch (error) {
this.logger.error(`Failed to load entry ${indexEntry.filename}:`, error);
this.index.delete(indexEntry.filename.replace('.json', ''));
return null;
}
}));
return {
entries: entries.filter(entry => entry !== null),
total,
hasMore: offset + limit < total
};
}
async findById(id) {
await this.ensureInitialized();
const indexEntry = this.index.get(id);
if (!indexEntry)
return null;
try {
const filepath = path.join(this.storageDir, indexEntry.filename);
const content = await fs_1.promises.readFile(filepath, 'utf-8');
return JSON.parse(content);
}
catch (error) {
this.logger.error(`Failed to load entry ${id}:`, error);
this.index.delete(id);
await this.saveIndex();
return null;
}
}
async delete(id) {
await this.ensureInitialized();
const indexEntry = this.index.get(id);
if (!indexEntry)
return false;
try {
const filepath = path.join(this.storageDir, indexEntry.filename);
await fs_1.promises.unlink(filepath);
this.index.delete(id);
await this.saveIndex();
return true;
}
catch (error) {
this.logger.error(`Failed to delete entry ${id}:`, error);
return false;
}
}
async clear() {
await this.ensureInitialized();
try {
const files = await fs_1.promises.readdir(this.storageDir);
const deletePromises = files
.filter(file => file.endsWith('.json') && file !== 'index.json')
.map(file => fs_1.promises.unlink(path.join(this.storageDir, file)));
await Promise.all(deletePromises);
this.index.clear();
await this.saveIndex();
this.logger.log('File storage cleared');
}
catch (error) {
this.logger.error('Failed to clear file storage:', error);
throw error;
}
}
async prune(olderThan) {
await this.ensureInitialized();
const entriesToDelete = Array.from(this.index.entries())
.filter(([_, entry]) => entry.timestamp < olderThan);
let deletedCount = 0;
for (const [id, entry] of entriesToDelete) {
try {
const filepath = path.join(this.storageDir, entry.filename);
await fs_1.promises.unlink(filepath);
this.index.delete(id);
deletedCount++;
}
catch (error) {
this.logger.error(`Failed to delete entry ${id} during pruning:`, error);
}
}
if (deletedCount > 0) {
await this.saveIndex();
this.logger.log(`Pruned ${deletedCount} entries`);
}
return deletedCount;
}
async getStats() {
await this.ensureInitialized();
const entries = Array.from(this.index.values());
const entriesByType = {};
let oldestEntry;
let newestEntry;
for (const entry of entries) {
entriesByType[entry.type] = (entriesByType[entry.type] || 0) + 1;
if (!oldestEntry || entry.timestamp < oldestEntry) {
oldestEntry = entry.timestamp;
}
if (!newestEntry || entry.timestamp > newestEntry) {
newestEntry = entry.timestamp;
}
}
let sizeInBytes = 0;
try {
const files = await fs_1.promises.readdir(this.storageDir);
for (const file of files) {
const filepath = path.join(this.storageDir, file);
const stats = await fs_1.promises.stat(filepath);
sizeInBytes += stats.size;
}
}
catch (error) {
this.logger.error('Failed to calculate storage size:', error);
}
return {
totalEntries: entries.length,
entriesByType,
oldestEntry,
newestEntry,
sizeInBytes
};
}
async healthCheck() {
try {
await this.ensureInitialized();
const testFile = path.join(this.storageDir, 'health-check.json');
await fs_1.promises.writeFile(testFile, JSON.stringify({ test: true }));
await fs_1.promises.readFile(testFile);
await fs_1.promises.unlink(testFile);
return true;
}
catch (error) {
return false;
}
}
async loadIndex() {
try {
const content = await fs_1.promises.readFile(this.indexFile, 'utf-8');
const indexData = JSON.parse(content);
for (const [id, entry] of Object.entries(indexData)) {
entry.timestamp = new Date(entry.timestamp);
}
this.index = new Map(Object.entries(indexData));
this.logger.debug(`Loaded index with ${this.index.size} entries`);
}
catch (error) {
this.index = new Map();
this.logger.debug('Started with empty index');
}
}
async saveIndex() {
try {
const indexData = Object.fromEntries(this.index);
await fs_1.promises.writeFile(this.indexFile, JSON.stringify(indexData, null, 2));
}
catch (error) {
this.logger.error('Failed to save index:', error);
throw error;
}
}
async ensureInitialized() {
if (!this.initialized) {
await this.initializeStorage();
}
}
chunkArray(array, chunkSize) {
const chunks = [];
for (let i = 0; i < array.length; i += chunkSize) {
chunks.push(array.slice(i, i + chunkSize));
}
return chunks;
}
};
exports.FileStorageDriver = FileStorageDriver;
exports.FileStorageDriver = FileStorageDriver = FileStorageDriver_1 = __decorate([
(0, common_1.Injectable)(),
__metadata("design:paramtypes", [Object])
], FileStorageDriver);
//# sourceMappingURL=file-storage.driver.js.map