@logistically/events-nestjs
Version:
NestJS integration for @logistically/events v3 - Event-driven architecture with Redis Streams
188 lines (187 loc) • 6.57 kB
JavaScript
"use strict";
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;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EventMetadataExplorer = void 0;
const common_1 = require("@nestjs/common");
const core_1 = require("@nestjs/core");
const auto_event_handler_decorator_1 = require("../decorators/auto-event-handler.decorator");
let EventMetadataExplorer = class EventMetadataExplorer {
constructor() {
this.metadataScanner = new core_1.MetadataScanner();
// Cache for metadata scanning results to avoid repeated scans
this.metadataCache = new WeakMap();
// Cache TTL in milliseconds (5 minutes)
this.cacheTTL = 5 * 60 * 1000;
// Track cache statistics
this.cacheHits = 0;
this.cacheMisses = 0;
}
/**
* Explore an instance for event handler methods with caching
* Based on NestJS microservices ListenerMetadataExplorer pattern
*/
explore(instance) {
if (!instance) {
return [];
}
// Check cache first
const cached = this.metadataCache.get(instance);
if (cached && this.isCacheValid(cached.timestamp)) {
this.cacheHits++;
return cached.handlers;
}
this.cacheMisses++;
// Perform fresh scan
const instancePrototype = Object.getPrototypeOf(instance);
if (!instancePrototype) {
return [];
}
const handlers = this.metadataScanner
.getAllMethodNames(instancePrototype)
.map(method => this.exploreMethodMetadata(instancePrototype, method))
.filter(metadata => metadata !== null);
// Cache the results
this.metadataCache.set(instance, {
handlers,
hasHandlers: handlers.length > 0,
eventTypes: handlers.map(h => h.eventType),
timestamp: Date.now()
});
return handlers;
}
/**
* Explore a single method for event handler metadata
*/
exploreMethodMetadata(instancePrototype, methodKey) {
const targetCallback = instancePrototype[methodKey];
// Check if method has @AutoEventHandler decorator
const metadata = Reflect.getMetadata(auto_event_handler_decorator_1.AUTO_EVENT_HANDLER_METADATA, targetCallback);
if (!metadata) {
return null;
}
return {
methodKey,
targetCallback,
eventType: metadata.eventType,
options: metadata
};
}
/**
* Check if an instance has any event handlers with caching
*/
hasEventHandlers(instance) {
if (!instance) {
return false;
}
const cached = this.metadataCache.get(instance);
if (cached && this.isCacheValid(cached.timestamp)) {
this.cacheHits++;
return cached.hasHandlers;
}
this.cacheMisses++;
const result = this.explore(instance).length > 0;
// Update cache if not already cached
if (!cached) {
const handlers = this.explore(instance);
this.metadataCache.set(instance, {
handlers,
hasHandlers: result,
eventTypes: handlers.map(h => h.eventType),
timestamp: Date.now()
});
}
return result;
}
/**
* Get all event types that an instance handles with caching
*/
getEventTypes(instance) {
if (!instance) {
return [];
}
const cached = this.metadataCache.get(instance);
if (cached && this.isCacheValid(cached.timestamp)) {
this.cacheHits++;
return cached.eventTypes;
}
this.cacheMisses++;
const handlers = this.explore(instance);
// Update cache if not already cached
if (!cached) {
this.metadataCache.set(instance, {
handlers,
hasHandlers: handlers.length > 0,
eventTypes: handlers.map(h => h.eventType),
timestamp: Date.now()
});
}
return handlers.map(h => h.eventType);
}
/**
* Check if cached data is still valid
*/
isCacheValid(timestamp) {
return Date.now() - timestamp < this.cacheTTL;
}
/**
* Invalidate cache for a specific instance
* Useful when instance methods are modified at runtime
*/
invalidateCache(instance) {
if (instance) {
this.metadataCache.delete(instance);
}
}
/**
* Clear all cached data
* Useful for testing or when memory usage is a concern
*/
clearCache() {
this.metadataCache = new WeakMap();
this.cacheHits = 0;
this.cacheMisses = 0;
}
/**
* Get cache statistics for monitoring
*/
getCacheStats() {
const total = this.cacheHits + this.cacheMisses;
const hitRate = total > 0 ? (this.cacheHits / total) * 100 : 0;
// Estimate cache size (WeakMap doesn't provide size, so we estimate)
let cacheSize = 0;
// Note: WeakMap size cannot be determined, this is an approximation
// In practice, the WeakMap will automatically clean up when instances are garbage collected
return {
hits: this.cacheHits,
misses: this.cacheMisses,
hitRate: Math.round(hitRate * 100) / 100,
cacheSize
};
}
/**
* Cleanup on module destroy
*/
onModuleDestroy() {
this.clearCache();
}
/**
* Pre-warm cache for known instances
* Useful for frequently accessed services
*/
preWarmCache(instances) {
for (const instance of instances) {
if (instance) {
this.explore(instance);
}
}
}
};
exports.EventMetadataExplorer = EventMetadataExplorer;
exports.EventMetadataExplorer = EventMetadataExplorer = __decorate([
(0, common_1.Injectable)()
], EventMetadataExplorer);