@re-shell/cli
Version:
Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja
461 lines (460 loc) • 15.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EventBatcher = exports.EventDebouncer = void 0;
exports.createEventDebouncer = createEventDebouncer;
exports.createEventBatcher = createEventBatcher;
exports.createWebpackDebouncer = createWebpackDebouncer;
exports.createTestDebouncer = createTestDebouncer;
const events_1 = require("events");
// Advanced file system event debouncer with batching and deduplication
class EventDebouncer extends events_1.EventEmitter {
constructor(options = {}) {
super();
this.pendingEvents = new Map();
this.timers = new Map();
this.batchTimers = new Map();
this.eventBatches = new Map();
this.eventCounter = 0;
this.options = {
delay: 300, // 300ms debounce delay
maxDelay: 2000, // 2s maximum delay
maxBatchSize: 100, // Maximum events per batch
enableDeduplication: true,
enableBatching: true,
groupByType: false,
includeStats: true,
...options
};
this.filters = [];
}
// Add file system event to debouncer
addEvent(type, path, stats) {
const event = {
id: this.generateEventId(),
type: type,
path,
timestamp: Date.now(),
stats: this.options.includeStats ? stats : undefined
};
// Apply filters
if (!this.passesFilters(event)) {
return;
}
const eventKey = this.getEventKey(event);
// Update or create pending event
const existingEvent = this.pendingEvents.get(eventKey);
if (existingEvent && this.options.enableDeduplication) {
// Update existing event with latest info
existingEvent.timestamp = event.timestamp;
existingEvent.stats = event.stats;
}
else {
this.pendingEvents.set(eventKey, event);
}
// Clear existing timer
const existingTimer = this.timers.get(eventKey);
if (existingTimer) {
clearTimeout(existingTimer);
}
// Set new debounce timer
const timer = setTimeout(() => {
this.processEvent(eventKey);
}, this.options.delay);
this.timers.set(eventKey, timer);
// Set maximum delay timer if not exists
if (!this.batchTimers.has(eventKey)) {
const maxTimer = setTimeout(() => {
this.forceProcessEvent(eventKey);
}, this.options.maxDelay);
this.batchTimers.set(eventKey, maxTimer);
}
// Emit immediate event for debugging
this.emit('event-added', event);
}
// Process individual event
processEvent(eventKey) {
const event = this.pendingEvents.get(eventKey);
if (!event)
return;
this.pendingEvents.delete(eventKey);
this.timers.delete(eventKey);
// Clear max delay timer
const maxTimer = this.batchTimers.get(eventKey);
if (maxTimer) {
clearTimeout(maxTimer);
this.batchTimers.delete(eventKey);
}
if (this.options.enableBatching) {
this.addToBatch(event);
}
else {
this.emit('debounced-event', event);
}
}
// Force process event when max delay reached
forceProcessEvent(eventKey) {
const event = this.pendingEvents.get(eventKey);
if (!event)
return;
this.pendingEvents.delete(eventKey);
// Clear regular timer
const timer = this.timers.get(eventKey);
if (timer) {
clearTimeout(timer);
this.timers.delete(eventKey);
}
this.batchTimers.delete(eventKey);
if (this.options.enableBatching) {
this.addToBatch(event);
}
else {
this.emit('debounced-event', event);
}
}
// Add event to batch
addToBatch(event) {
const batchKey = this.getBatchKey(event);
if (!this.eventBatches.has(batchKey)) {
this.eventBatches.set(batchKey, []);
}
const batch = this.eventBatches.get(batchKey);
batch.push(event);
// Process batch if it reaches max size
if (batch.length >= this.options.maxBatchSize) {
this.processBatch(batchKey);
}
else {
// Set timer to process batch after delay
setTimeout(() => {
if (this.eventBatches.has(batchKey)) {
this.processBatch(batchKey);
}
}, this.options.delay);
}
}
// Process event batch
processBatch(batchKey) {
const events = this.eventBatches.get(batchKey);
if (!events || events.length === 0)
return;
this.eventBatches.delete(batchKey);
// Deduplicate events if enabled
const finalEvents = this.options.enableDeduplication
? this.deduplicateEvents(events)
: events;
const batch = {
id: this.generateBatchId(),
events: finalEvents,
startTime: Math.min(...events.map(e => e.timestamp)),
endTime: Math.max(...events.map(e => e.timestamp)),
totalEvents: events.length,
deduplicated: finalEvents.length !== events.length
};
this.emit('batched-events', batch);
}
// Deduplicate events in batch
deduplicateEvents(events) {
const eventMap = new Map();
// Sort events by timestamp
const sortedEvents = events.sort((a, b) => a.timestamp - b.timestamp);
for (const event of sortedEvents) {
const key = this.getDeduplicationKey(event);
// Keep the latest event for each path/type combination
if (!eventMap.has(key) || eventMap.get(key).timestamp < event.timestamp) {
eventMap.set(key, event);
}
}
return Array.from(eventMap.values());
}
// Get event key for debouncing
getEventKey(event) {
return `${event.path}:${event.type}`;
}
// Get batch key for grouping
getBatchKey(event) {
if (this.options.groupByType) {
return event.type;
}
return 'default';
}
// Get deduplication key
getDeduplicationKey(event) {
return event.path; // Deduplicate by path only
}
// Generate unique event ID
generateEventId() {
return `event_${Date.now()}_${++this.eventCounter}`;
}
// Generate unique batch ID
generateBatchId() {
return `batch_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
// Check if event passes filters
passesFilters(event) {
for (const filter of this.filters) {
if (!this.eventMatchesFilter(event, filter)) {
return false;
}
}
return true;
}
// Check if event matches specific filter
eventMatchesFilter(event, filter) {
// Check type filter
if (filter.types.length > 0 && !filter.types.includes(event.type)) {
return false;
}
// Check exclude patterns
if (filter.excludePatterns) {
for (const pattern of filter.excludePatterns) {
if (pattern.test(event.path)) {
return false;
}
}
}
// Check include patterns
if (filter.patterns.length > 0) {
let matches = false;
for (const pattern of filter.patterns) {
if (pattern.test(event.path)) {
matches = true;
break;
}
}
if (!matches) {
return false;
}
}
// Check extensions
if (filter.extensions && filter.extensions.length > 0) {
const extension = this.getFileExtension(event.path);
if (!filter.extensions.includes(extension)) {
return false;
}
}
// Check file size (if stats available)
if (event.stats && (filter.minFileSize || filter.maxFileSize)) {
const fileSize = event.stats.size || 0;
if (filter.minFileSize && fileSize < filter.minFileSize) {
return false;
}
if (filter.maxFileSize && fileSize > filter.maxFileSize) {
return false;
}
}
return true;
}
// Get file extension
getFileExtension(filePath) {
const parts = filePath.split('.');
return parts.length > 1 ? parts[parts.length - 1].toLowerCase() : '';
}
// Add event filter
addFilter(filter) {
this.filters.push(filter);
}
// Remove event filter
removeFilter(index) {
if (index >= 0 && index < this.filters.length) {
this.filters.splice(index, 1);
}
}
// Clear all filters
clearFilters() {
this.filters = [];
}
// Get current filter count
getFilterCount() {
return this.filters.length;
}
// Get pending events count
getPendingEventsCount() {
return this.pendingEvents.size;
}
// Get active timers count
getActiveTimersCount() {
return this.timers.size;
}
// Get batch count
getBatchCount() {
return this.eventBatches.size;
}
// Force flush all pending events
flush() {
// Process all pending events immediately
for (const [eventKey] of this.pendingEvents) {
this.forceProcessEvent(eventKey);
}
// Process all batches immediately
for (const [batchKey] of this.eventBatches) {
this.processBatch(batchKey);
}
}
// Clear all pending events and timers
clear() {
// Clear all timers
for (const timer of this.timers.values()) {
clearTimeout(timer);
}
this.timers.clear();
for (const timer of this.batchTimers.values()) {
clearTimeout(timer);
}
this.batchTimers.clear();
// Clear all pending data
this.pendingEvents.clear();
this.eventBatches.clear();
}
// Get debouncer statistics
getStatistics() {
return {
pendingEvents: this.pendingEvents.size,
activeTimers: this.timers.size,
activeBatches: this.eventBatches.size,
totalFilters: this.filters.length,
options: { ...this.options }
};
}
// Update debounce options
updateOptions(newOptions) {
this.options = { ...this.options, ...newOptions };
}
}
exports.EventDebouncer = EventDebouncer;
// File system event batcher for high-frequency operations
class EventBatcher extends events_1.EventEmitter {
constructor(options = {}) {
super();
this.batches = new Map();
this.batchTimers = new Map();
this.options = {
batchInterval: 500, // 500ms batch interval
maxBatchSize: 50, // Maximum 50 events per batch
groupByDirectory: true, // Group events by directory
...options
};
}
// Add event to batcher
addEvent(event) {
const batchKey = this.getBatchKey(event);
if (!this.batches.has(batchKey)) {
this.batches.set(batchKey, []);
this.startBatchTimer(batchKey);
}
const batch = this.batches.get(batchKey);
batch.push(event);
// Emit batch if it reaches max size
if (batch.length >= this.options.maxBatchSize) {
this.emitBatch(batchKey);
}
}
// Get batch key for grouping
getBatchKey(event) {
if (this.options.groupByDirectory) {
const pathParts = event.path.split('/');
return pathParts.slice(0, -1).join('/'); // Directory path
}
return 'default';
}
// Start batch timer
startBatchTimer(batchKey) {
const timer = setTimeout(() => {
this.emitBatch(batchKey);
}, this.options.batchInterval);
this.batchTimers.set(batchKey, timer);
}
// Emit batch of events
emitBatch(batchKey) {
const events = this.batches.get(batchKey);
if (!events || events.length === 0)
return;
// Clear timer
const timer = this.batchTimers.get(batchKey);
if (timer) {
clearTimeout(timer);
this.batchTimers.delete(batchKey);
}
// Remove batch
this.batches.delete(batchKey);
// Create batch object
const batch = {
id: `batch_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
events,
startTime: Math.min(...events.map(e => e.timestamp)),
endTime: Math.max(...events.map(e => e.timestamp)),
totalEvents: events.length,
deduplicated: false
};
this.emit('batch', batch);
}
// Flush all pending batches
flush() {
for (const batchKey of this.batches.keys()) {
this.emitBatch(batchKey);
}
}
// Clear all batches
clear() {
// Clear all timers
for (const timer of this.batchTimers.values()) {
clearTimeout(timer);
}
this.batchTimers.clear();
this.batches.clear();
}
// Get current batch count
getBatchCount() {
return this.batches.size;
}
// Get total pending events
getPendingEventCount() {
let total = 0;
for (const batch of this.batches.values()) {
total += batch.length;
}
return total;
}
}
exports.EventBatcher = EventBatcher;
// Utility functions
function createEventDebouncer(options) {
return new EventDebouncer(options);
}
function createEventBatcher(options) {
return new EventBatcher(options);
}
// Pre-configured debouncer for common use cases
function createWebpackDebouncer() {
const debouncer = new EventDebouncer({
delay: 300,
maxDelay: 1000,
maxBatchSize: 50,
enableDeduplication: true,
enableBatching: true
});
// Add filter for webpack-relevant files
debouncer.addFilter({
patterns: [/\.(js|jsx|ts|tsx|css|scss|less|vue|svelte)$/],
types: ['add', 'change', 'unlink'],
excludePatterns: [/node_modules/, /\.git/, /dist/, /build/],
extensions: ['js', 'jsx', 'ts', 'tsx', 'css', 'scss', 'less', 'vue', 'svelte']
});
return debouncer;
}
function createTestDebouncer() {
const debouncer = new EventDebouncer({
delay: 100,
maxDelay: 500,
maxBatchSize: 20,
enableDeduplication: true,
enableBatching: false
});
// Add filter for test files
debouncer.addFilter({
patterns: [/\.(test|spec)\.(js|jsx|ts|tsx)$/],
types: ['add', 'change', 'unlink'],
excludePatterns: [/node_modules/, /\.git/],
extensions: ['js', 'jsx', 'ts', 'tsx']
});
return debouncer;
}