mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-07 16:09:43 +02:00
feat: add resource management types and utilities for memory optimization
- Add Disposable, BufferConfig, MemoryMetrics, CleanupRegistration types - Create src/config/buffer-limits.ts with consolidated buffer size constants - Create src/config/map-limits.ts with Map size limits to prevent unbounded growth - Implement BufferAccumulator utility with configurable trim and onTrim callback - Implement LRUMap with automatic eviction and O(1) operations - Implement CleanupManager for unified resource cleanup with isStopped guard - Add comprehensive tests for all new utilities This lays the foundation for memory leak prevention and performance improvements. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* @fileoverview High-performance buffer accumulator for string data.
|
||||
*
|
||||
* This utility reduces GC pressure by avoiding repeated string concatenation (`+=`).
|
||||
* Instead, chunks are pushed to an array and joined only when needed.
|
||||
* Automatically trims when size limits are exceeded.
|
||||
*
|
||||
* @module utils/buffer-accumulator
|
||||
*/
|
||||
|
||||
import type { BufferConfig } from '../types.js';
|
||||
|
||||
/**
|
||||
* High-performance buffer accumulator using array-based collection.
|
||||
*
|
||||
* Features:
|
||||
* - Efficient append via array push (avoids O(n) string concat)
|
||||
* - Lazy join on read (consolidates only when accessed)
|
||||
* - Automatic trimming when max size exceeded
|
||||
* - Optional callback on trim for logging/metrics
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const buffer = new BufferAccumulator({
|
||||
* maxSize: 2 * 1024 * 1024, // 2MB
|
||||
* trimSize: 1.5 * 1024 * 1024, // Trim to 1.5MB
|
||||
* onTrim: (bytes) => console.log(`Trimmed ${bytes} bytes`)
|
||||
* });
|
||||
*
|
||||
* buffer.append('chunk1');
|
||||
* buffer.append('chunk2');
|
||||
* console.log(buffer.value); // Joins and returns full content
|
||||
* ```
|
||||
*/
|
||||
export class BufferAccumulator {
|
||||
private chunks: string[] = [];
|
||||
private totalLength: number = 0;
|
||||
private readonly maxSize: number;
|
||||
private readonly trimSize: number;
|
||||
private readonly onTrim?: (trimmedBytes: number) => void;
|
||||
|
||||
/**
|
||||
* Creates a new BufferAccumulator.
|
||||
*
|
||||
* @param config - Buffer configuration with max/trim sizes
|
||||
*/
|
||||
constructor(config: BufferConfig);
|
||||
/**
|
||||
* Creates a new BufferAccumulator with simple size parameters.
|
||||
*
|
||||
* @param maxSize - Maximum buffer size before trimming
|
||||
* @param trimSize - Size to trim to when max is exceeded
|
||||
*/
|
||||
constructor(maxSize: number, trimSize: number);
|
||||
constructor(configOrMaxSize: BufferConfig | number, trimSize?: number) {
|
||||
if (typeof configOrMaxSize === 'number') {
|
||||
this.maxSize = configOrMaxSize;
|
||||
this.trimSize = trimSize!;
|
||||
this.onTrim = undefined;
|
||||
} else {
|
||||
this.maxSize = configOrMaxSize.maxSize;
|
||||
this.trimSize = configOrMaxSize.trimSize;
|
||||
this.onTrim = configOrMaxSize.onTrim;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Append data to the buffer.
|
||||
* Automatically trims if maxSize is exceeded.
|
||||
*
|
||||
* @param data - String data to append
|
||||
*/
|
||||
append(data: string): void {
|
||||
if (!data) return;
|
||||
this.chunks.push(data);
|
||||
this.totalLength += data.length;
|
||||
|
||||
// Trim if exceeded max size
|
||||
if (this.totalLength > this.maxSize) {
|
||||
this.trim();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full buffer content.
|
||||
* Consolidates chunks on access for efficient subsequent reads.
|
||||
*/
|
||||
get value(): string {
|
||||
if (this.chunks.length === 0) return '';
|
||||
if (this.chunks.length === 1) return this.chunks[0];
|
||||
|
||||
// Consolidate chunks on access
|
||||
const result = this.chunks.join('');
|
||||
this.chunks = [result];
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current buffer length without joining chunks.
|
||||
*/
|
||||
get length(): number {
|
||||
return this.totalLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if buffer is empty.
|
||||
*/
|
||||
get isEmpty(): boolean {
|
||||
return this.totalLength === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the buffer completely.
|
||||
*/
|
||||
clear(): void {
|
||||
this.chunks = [];
|
||||
this.totalLength = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set buffer to a specific value, replacing all content.
|
||||
*
|
||||
* @param value - New buffer content
|
||||
*/
|
||||
set(value: string): void {
|
||||
this.chunks = value ? [value] : [];
|
||||
this.totalLength = value?.length || 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the last N characters from the buffer.
|
||||
* Useful for checking recent content without reading the entire buffer.
|
||||
*
|
||||
* @param n - Number of characters to get from the end
|
||||
* @returns The last N characters (or entire buffer if smaller)
|
||||
*/
|
||||
tail(n: number): string {
|
||||
if (n >= this.totalLength) {
|
||||
return this.value;
|
||||
}
|
||||
const full = this.value;
|
||||
return full.slice(-n);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if buffer ends with a specific string.
|
||||
*
|
||||
* @param suffix - String to check for
|
||||
* @returns True if buffer ends with the suffix
|
||||
*/
|
||||
endsWith(suffix: string): boolean {
|
||||
if (!suffix) return true; // All strings end with empty string
|
||||
if (suffix.length > this.totalLength) return false;
|
||||
return this.tail(suffix.length) === suffix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for a pattern in the buffer (from the end).
|
||||
*
|
||||
* @param pattern - String or RegExp to search for
|
||||
* @param fromEnd - Number of characters from end to search within (default: entire buffer)
|
||||
* @returns True if pattern is found
|
||||
*/
|
||||
contains(pattern: string | RegExp, fromEnd?: number): boolean {
|
||||
const searchIn = fromEnd ? this.tail(fromEnd) : this.value;
|
||||
if (typeof pattern === 'string') {
|
||||
return searchIn.includes(pattern);
|
||||
}
|
||||
return pattern.test(searchIn);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim buffer to keep only the most recent data.
|
||||
* Called automatically when maxSize is exceeded.
|
||||
*/
|
||||
private trim(): void {
|
||||
const full = this.chunks.join('');
|
||||
const trimmedBytes = full.length - this.trimSize;
|
||||
const trimmed = full.slice(-this.trimSize);
|
||||
this.chunks = [trimmed];
|
||||
this.totalLength = trimmed.length;
|
||||
|
||||
// Notify callback if configured
|
||||
if (this.onTrim && trimmedBytes > 0) {
|
||||
this.onTrim(trimmedBytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default BufferAccumulator;
|
||||
Reference in New Issue
Block a user