UNPKG

@fivexlabs/ng-terminus

Version:

A comprehensive Angular library for managing RxJS subscriptions and preventing memory leaks with advanced features

850 lines (837 loc) 26.6 kB
import * as i0 from '@angular/core'; import { DestroyRef, OnDestroy, ModuleWithProviders } from '@angular/core'; import { Observable, Subscription, BehaviorSubject, ReplaySubject } from 'rxjs'; /** * An RxJS operator that automatically completes the source observable * when the associated Angular component, directive, or service is destroyed. * * This operator prevents memory leaks by tying the observable lifecycle * to Angular's component lifecycle through DestroyRef. * * @param destroyRef Optional DestroyRef instance. If not provided, * it will be automatically injected using Angular's inject() function. * @returns An operator function that can be used in a pipe() chain * * @example * ```typescript * // Basic usage with automatic DestroyRef injection * constructor(private dataService: DataService) { * this.dataService.getData() * .pipe(takeUntilDestroyed()) * .subscribe(data => console.log(data)); * } * * // Usage with explicit DestroyRef * constructor(private dataService: DataService) { * const destroyRef = inject(DestroyRef); * this.dataService.getData() * .pipe(takeUntilDestroyed(destroyRef)) * .subscribe(data => console.log(data)); * } * ``` */ declare function takeUntilDestroyed<T>(destroyRef?: DestroyRef): (source: Observable<T>) => Observable<T>; /** * A simplified version of takeUntilDestroyed that always uses automatic injection. * This provides the cleanest API for most use cases. * * @returns An operator function that can be used in a pipe() chain * * @example * ```typescript * constructor(private dataService: DataService) { * this.dataService.getData() * .pipe(untilDestroyed()) * .subscribe(data => console.log(data)); * } * ``` */ declare function untilDestroyed<T>(): (source: Observable<T>) => Observable<T>; /** * Type alias for the takeUntilDestroyed operator function */ type TakeUntilDestroyed = typeof takeUntilDestroyed; /** * A service that manages multiple RxJS subscriptions and automatically * unsubscribes from all of them when the associated component is destroyed. * * This service implements Angular's OnDestroy interface and should be * provided at the component level to ensure proper cleanup. * * @example * ```typescript * @Component({ * selector: 'app-my-component', * providers: [SubscriptionManager] // Provide at component level * }) * export class MyComponent implements OnInit { * constructor( * private dataService: DataService, * private subManager: SubscriptionManager * ) {} * * ngOnInit() { * // Add subscriptions to the manager * this.subManager.add( * this.dataService.getStream1().subscribe(data => console.log(data)), * this.dataService.getStream2().subscribe(data => console.log(data)) * ); * } * // No ngOnDestroy needed - the service handles cleanup automatically * } * ``` */ declare class SubscriptionManager implements OnDestroy { private subscriptions; private isDestroyed; /** * Add one or more subscriptions to be managed by this service. * All added subscriptions will be automatically unsubscribed when * the component is destroyed. * * @param subscriptions The subscriptions to add * @returns The SubscriptionManager instance for method chaining * * @example * ```typescript * // Add single subscription * this.subManager.add(observable$.subscribe()); * * // Add multiple subscriptions * this.subManager.add( * observable1$.subscribe(), * observable2$.subscribe() * ); * * // Method chaining * this.subManager * .add(observable1$.subscribe()) * .add(observable2$.subscribe()); * ``` */ add(...subscriptions: Subscription[]): SubscriptionManager; /** * Remove a specific subscription from management. * The subscription will not be unsubscribed automatically. * * @param subscription The subscription to remove * @returns The SubscriptionManager instance for method chaining */ remove(subscription: Subscription): SubscriptionManager; /** * Manually unsubscribe from all managed subscriptions. * This is automatically called during ngOnDestroy. */ unsubscribeAll(): void; /** * Get the current number of active subscriptions being managed. * * @returns The number of active subscriptions */ get activeCount(): number; /** * Check if the manager has any active subscriptions. * * @returns True if there are active subscriptions, false otherwise */ get hasActiveSubscriptions(): boolean; /** * Angular lifecycle hook that automatically unsubscribes from all * managed subscriptions when the component is destroyed. */ ngOnDestroy(): void; static ɵfac: i0.ɵɵFactoryDeclaration<SubscriptionManager, never>; static ɵprov: i0.ɵɵInjectableDeclaration<SubscriptionManager>; } /** * Type guard to check if a value is a Subscription */ declare function isSubscription(value: any): value is Subscription; /** * Type guard to check if a value is an Observable */ declare function isObservableValue<T>(value: any): value is Observable<T>; /** * Utility function to safely unsubscribe from a subscription * without throwing errors if the subscription is null, undefined, or already closed. * * @param subscription The subscription to unsubscribe from * @returns True if unsubscription was successful, false otherwise * * @example * ```typescript * let sub: Subscription | undefined; * // ... later * safeUnsubscribe(sub); // Won't throw if sub is undefined * ``` */ declare function safeUnsubscribe(subscription: Subscription | null | undefined): boolean; /** * Utility function to create an observable that automatically unsubscribes * when the component is destroyed. This is a functional approach alternative * to using the operator in a pipe. * * @param source$ The source observable * @param destroyRef Optional DestroyRef instance * @returns A new observable that will complete on component destruction * * @example * ```typescript * const managedObservable$ = createManagedObservable( * this.dataService.getData(), * inject(DestroyRef) * ); * * managedObservable$.subscribe(data => console.log(data)); * ``` */ declare function createManagedObservable<T>(source$: Observable<T>, destroyRef?: DestroyRef): Observable<T>; /** * Utility function to handle multiple observables with automatic cleanup. * Returns an array of managed observables. * * @param observables Array of source observables * @param destroyRef Optional DestroyRef instance * @returns Array of managed observables * * @example * ```typescript * const [data1$, data2$, data3$] = manageManyObservables([ * this.service.getData1(), * this.service.getData2(), * this.service.getData3() * ]); * ``` */ declare function manageManyObservables<T extends readonly Observable<any>[]>(observables: T, destroyRef?: DestroyRef): { [K in keyof T]: T[K]; }; /** * Configuration options for subscription debugging */ interface SubscriptionDebugOptions { /** Enable console logging for subscription lifecycle events */ enableLogging?: boolean; /** Custom prefix for log messages */ logPrefix?: string; /** Enable stack trace capture for subscription creation */ captureStackTrace?: boolean; } /** * Utility class for debugging subscription lifecycle in development */ declare class SubscriptionDebugger { private static defaultOptions; private static options; /** * Configure global debugging options */ static configure(options: Partial<SubscriptionDebugOptions>): void; /** * Log subscription creation */ static logSubscription(context: string, subscription?: Subscription): void; /** * Log subscription cleanup */ static logCleanup(context: string, count: number): void; } /** * Types for better TypeScript support */ /** A function that returns an observable */ type ObservableFactory<T> = () => Observable<T>; /** A function that handles subscription cleanup */ type CleanupFunction = () => void; /** Configuration for subscription management */ interface SubscriptionConfig { /** Automatically log subscription lifecycle events */ debug?: boolean; /** Custom cleanup functions to run on destroy */ cleanupFunctions?: CleanupFunction[]; } /** * Configuration options for NgTerminus */ interface NgTerminusConfig { enableDebugger?: boolean; enableMemoryOptimization?: boolean; debugMode?: boolean; } /** * The main Angular module for ng-terminus library. * * This module can be imported into your Angular application to provide * all ng-terminus services and enable comprehensive subscription management. * * @example * ```typescript * import { NgModule } from '@angular/core'; * import { NgTerminusModule } from '@fivexlabs/ng-terminus'; * * @NgModule({ * imports: [NgTerminusModule.forRoot({ enableDebugger: true })], * // ... * }) * export class AppModule { } * ``` */ declare class NgTerminusModule { /** * Use this method to configure the module for the root application. * * @param config Configuration options for ng-terminus * @returns The configured module with providers * * @example * ```typescript * @NgModule({ * imports: [NgTerminusModule.forRoot({ * enableDebugger: true, * enableMemoryOptimization: true, * debugMode: environment.production === false * })], * // ... * }) * export class AppModule { } * ``` */ static forRoot(config?: NgTerminusConfig): ModuleWithProviders<NgTerminusModule>; /** * Use this method to configure the module for feature modules. * This ensures proper service scoping in lazy-loaded modules. * * @param config Optional configuration for feature modules * @returns The configured module * * @example * ```typescript * @NgModule({ * imports: [NgTerminusModule.forFeature()], * // ... * }) * export class FeatureModule { } * ``` */ static forFeature(config?: Partial<NgTerminusConfig>): ModuleWithProviders<NgTerminusModule>; constructor(); static ɵfac: i0.ɵɵFactoryDeclaration<NgTerminusModule, never>; static ɵmod: i0.ɵɵNgModuleDeclaration<NgTerminusModule, never, never, never>; static ɵinj: i0.ɵɵInjectorDeclaration<NgTerminusModule>; } /** * RxJS operator that automatically unsubscribes when navigating away from the current route. * This is useful for subscriptions that should only remain active while on a specific route. * * @param targetRoute Optional specific route to monitor. If not provided, monitors any route change. * @returns MonoTypeOperatorFunction that unsubscribes on route change * * @example * ```typescript * // Unsubscribe on any route change * this.dataService.getData() * .pipe(takeUntilRoute()) * .subscribe(data => console.log(data)); * * // Unsubscribe only when leaving specific route * this.dataService.getData() * .pipe(takeUntilRoute('/dashboard')) * .subscribe(data => console.log(data)); * ``` */ declare function takeUntilRoute(targetRoute?: string): <T>(source: Observable<T>) => Observable<T>; /** * RxJS operator that keeps subscription active only while on a specific route. * When navigating away, it unsubscribes and returns EMPTY. * When navigating back, it resubscribes. * * @param routePattern The route pattern to match (supports wildcards) * @returns OperatorFunction that manages subscription based on route presence * * @example * ```typescript * this.dataService.getLiveData() * .pipe(takeWhileOnRoute('/dashboard/**')) * .subscribe(data => console.log('Dashboard data:', data)); * ``` */ declare function takeWhileOnRoute<T>(routePattern: string): (source: Observable<T>) => Observable<T>; /** * Service to manage HTTP request cancellation */ declare class HttpRequestManager { private pendingRequests; private requestCounter; /** * Create a cancellable HTTP request */ createCancellableRequest<T>(requestFn: () => Observable<T>, requestId?: string): { request$: Observable<T>; cancel: () => void; }; /** * Cancel a specific request by ID */ cancelRequest(requestId: string): void; /** * Cancel all pending requests */ cancelAllRequests(): void; /** * Get the number of pending requests */ getPendingRequestCount(): number; /** * Get all pending request IDs */ getPendingRequestIds(): string[]; } /** * RxJS operator that cancels HTTP requests when component is destroyed */ declare function cancelOnDestroy(): <T>(source: Observable<T>) => Observable<T>; /** * RxJS operator that cancels previous HTTP requests when a new one is made */ declare function cancelPrevious(): <T>(source: Observable<T>) => Observable<T>; /** * RxJS operator that adds retry logic with exponential backoff for HTTP requests */ declare function retryWithBackoff(maxRetries?: number, initialDelay?: number, maxDelay?: number): <T>(source: Observable<T>) => Observable<T>; /** * RxJS operator that logs HTTP request lifecycle events */ declare function logHttpRequests(requestName?: string): <T>(source: Observable<T>) => Observable<T>; /** * RxJS operator that pauses subscription when page becomes hidden * and resumes when page becomes visible again. * * @param emitOnResume Whether to emit the last value when resuming (default: true) * @returns OperatorFunction that manages subscription based on page visibility * * @example * ```typescript * this.dataService.getLiveData() * .pipe(takeWhileVisible()) * .subscribe(data => console.log('Received while visible:', data)); * ``` */ declare function takeWhileVisible<T>(emitOnResume?: boolean): (source: Observable<T>) => Observable<T>; /** * RxJS operator that unsubscribes when page becomes hidden * and doesn't automatically resubscribe when visible again. * * @returns MonoTypeOperatorFunction that unsubscribes on page hide * * @example * ```typescript * this.dataService.getData() * .pipe(takeUntilHidden()) * .subscribe(data => console.log('Data received:', data)); * ``` */ declare function takeUntilHidden<T>(): (source: Observable<T>) => Observable<T>; /** * RxJS operator that emits only when page is visible * and buffers emissions while hidden. * * @param bufferSize Maximum number of emissions to buffer (default: 10) * @returns OperatorFunction that buffers emissions while page is hidden * * @example * ```typescript * this.dataService.getNotifications() * .pipe(bufferWhileHidden(5)) * .subscribe(notifications => { * // Receive up to 5 buffered notifications when page becomes visible * console.log('Notifications:', notifications); * }); * ``` */ declare function bufferWhileHidden<T>(bufferSize?: number): (source: Observable<T>) => Observable<T[]>; /** * RxJS operator that throttles emissions when page is not visible * and resumes normal emission rate when visible. * * @param hiddenThrottleMs Throttle time in milliseconds when hidden (default: 30000) * @returns MonoTypeOperatorFunction that throttles based on visibility * * @example * ```typescript * this.dataService.getHeartbeat() * .pipe(throttleWhileHidden(60000)) // Throttle to 1 minute when hidden * .subscribe(heartbeat => console.log('Heartbeat:', heartbeat)); * ``` */ declare function throttleWhileHidden<T>(hiddenThrottleMs?: number): (source: Observable<T>) => Observable<T>; interface SubscriptionDebugInfo { id: string; componentName?: string; operatorName?: string; createdAt: Date; stackTrace?: string; isActive: boolean; emissionCount: number; errorCount: number; lastEmission?: Date; lastError?: Date; memoryUsage?: number; duration?: number; } interface PerformanceMetrics { totalSubscriptions: number; activeSubscriptions: number; totalEmissions: number; totalErrors: number; averageLifetime: number; memoryUsage: number; leaksDetected: number; } declare class SubscriptionDebuggerService { private subscriptions; private debugCounter; private isEnabled; private performanceObserver?; private memoryInterval?; constructor(); /** * Enable debugging mode */ enable(): void; /** * Disable debugging mode */ disable(): void; /** * Create a debuggable subscription with detailed tracking */ debugSubscription<T>(source: Observable<T>, options?: { name?: string; componentName?: string; captureStackTrace?: boolean; logEmissions?: boolean; logErrors?: boolean; }): Observable<T>; /** * Get detailed information about a specific subscription */ getSubscriptionInfo(id: string): SubscriptionDebugInfo | undefined; /** * Get all active subscriptions */ getActiveSubscriptions(): SubscriptionDebugInfo[]; /** * Get all subscriptions (active and completed) */ getAllSubscriptions(): SubscriptionDebugInfo[]; /** * Get performance metrics */ getPerformanceMetrics(): PerformanceMetrics; /** * Log current subscription status */ logStatus(): void; /** * Detect potential memory leaks */ detectPotentialLeaks(): number; /** * Clear all debug information */ clear(): void; /** * Export debug information as JSON */ exportDebugInfo(): string; private captureStackTrace; private calculateAverageLifetime; private getCurrentMemoryUsage; private setupPerformanceMonitoring; private setupMemoryMonitoring; ngOnDestroy(): void; static ɵfac: i0.ɵɵFactoryDeclaration<SubscriptionDebuggerService, never>; static ɵprov: i0.ɵɵInjectableDeclaration<SubscriptionDebuggerService>; } /** * Mock subscription manager for testing */ declare class MockSubscriptionManager { private subscriptions; private subscriptionCount; add(subscription: Subscription, name?: string): string; remove(id: string): boolean; removeAll(): void; getActiveCount(): number; getAllCount(): number; isActive(id: string): boolean; } /** * Test observable that can be controlled for testing subscription behavior */ declare class TestObservable<T> extends Observable<T> { private subject; private isCompleted; private hasErrored; constructor(); /** * Emit a value to all subscribers */ emit(value: T): void; /** * Emit multiple values with optional delays */ emitSequence(values: T[], delayMs?: number): Promise<void>; /** * Complete the observable */ complete(): void; /** * Emit an error */ error(error: any): void; /** * Get the current state */ getState(): { completed: boolean; errored: boolean; }; } /** * Subscription testing utilities */ declare class SubscriptionTester { private subscriptions; private emissionCounts; private errorCounts; private completionCounts; /** * Subscribe to an observable with tracking */ subscribe<T>(observable: Observable<T>, name: string, options?: { onNext?: (value: T) => void; onError?: (error: any) => void; onComplete?: () => void; }): Subscription; /** * Get emission count for a named subscription */ getEmissionCount(name: string): number; /** * Get error count for a named subscription */ getErrorCount(name: string): number; /** * Get completion count for a named subscription */ getCompletionCount(name: string): number; /** * Get total active subscriptions */ getActiveSubscriptionCount(): number; /** * Unsubscribe from all tracked subscriptions */ unsubscribeAll(): void; /** * Reset all counters */ reset(): void; /** * Wait for a specific number of emissions */ waitForEmissions(name: string, count: number, timeoutMs?: number): Promise<void>; /** * Wait for completion */ waitForCompletion(name: string, timeoutMs?: number): Promise<void>; private incrementCount; } /** * Memory leak detector for testing */ declare class MemoryLeakDetector { private initialMemory?; private subscriptions; /** * Start monitoring memory usage */ startMonitoring(): void; /** * Add subscription to monitor */ track(subscription: Subscription): void; /** * Check for memory leaks */ checkForLeaks(): { hasLeaks: boolean; memoryIncrease: number; activeSubscriptions: number; totalSubscriptions: number; }; /** * Clean up all tracked subscriptions */ cleanup(): void; } /** * Helper functions for creating test scenarios */ declare const TestScenarios: { /** * Create an observable that emits values at intervals */ createIntervalObservable(intervalMs: number, maxValues?: number): Observable<number>; /** * Create an observable that errors after a delay */ createErrorObservable(delayMs: number, error?: any): Observable<never>; /** * Create an observable that never completes or errors */ createInfiniteObservable(): Observable<number>; /** * Create an observable that completes immediately */ createImmediateCompletionObservable(): Observable<never>; /** * Create an observable that emits once then completes */ createSingleValueObservable<T>(value: T, delayMs?: number): Observable<T>; }; /** * RxJS operator that automatically unsubscribes from form value changes when component is destroyed */ declare function takeUntilFormDestroyed(): <T>(source: Observable<T>) => Observable<T>; /** * RxJS operator that emits only when form is valid */ declare function takeWhileFormValid<T>(isValid: () => boolean): (source: Observable<T>) => Observable<T>; /** * Injectable service for managing form subscriptions */ declare class FormSubscriptionManager { private subscriptions; /** * Create a managed subscription with automatic cleanup */ manage<T>(source: Observable<T>, name: string): Observable<T>; /** * Unsubscribe from a specific managed subscription */ unsubscribe(name: string): void; /** * Unsubscribe from all managed subscriptions */ unsubscribeAll(): void; /** * Get count of active subscriptions */ getActiveCount(): number; static ɵfac: i0.ɵɵFactoryDeclaration<FormSubscriptionManager, never>; static ɵprov: i0.ɵɵInjectableDeclaration<FormSubscriptionManager>; } /** * Memory usage statistics */ interface MemoryStats { totalObservables: number; activeSubscriptions: number; sharedObservables: number; memoryUsage: number; potentialLeaks: number; } /** * Memory optimizer for RxJS observables */ declare class MemoryOptimizer { private static instance; private observables; private sharedObservables; private idCounter; private isEnabled; static getInstance(): MemoryOptimizer; /** * Enable memory optimization tracking */ enable(): void; /** * Disable memory optimization tracking */ disable(): void; /** * Create a memory-optimized observable with automatic sharing */ optimize<T>(source: Observable<T>, options?: { share?: boolean; shareReplay?: number; name?: string; }): Observable<T>; /** * Create a shared observable that automatically cleans up when no subscribers */ shareWithCleanup<T>(source: Observable<T>, cleanupDelayMs?: number): Observable<T>; /** * Create a memory-efficient BehaviorSubject */ createEfficientBehaviorSubject<T>(initialValue: T, maxHistorySize?: number): BehaviorSubject<T>; /** * Create a memory-efficient ReplaySubject */ createEfficientReplaySubject<T>(bufferSize?: number, windowTime?: number): ReplaySubject<T>; /** * Get memory usage statistics */ getMemoryStats(): MemoryStats; /** * Clean up stale observables */ cleanup(maxAge?: number): number; /** * Force garbage collection (if available) */ forceGarbageCollection(): void; private estimateMemoryUsage; private getCurrentMemoryUsage; } /** * RxJS operator for memory optimization */ declare function optimizeMemory<T>(options?: { share?: boolean; shareReplay?: number; name?: string; }): (source: Observable<T>) => Observable<T>; /** * RxJS operator that automatically shares observables with cleanup */ declare function shareWithAutoCleanup<T>(cleanupDelayMs?: number): (source: Observable<T>) => Observable<T>; /** * RxJS operator that limits emission frequency to reduce memory pressure */ declare function limitEmissionRate<T>(maxEmissionsPerSecond?: number): (source: Observable<T>) => Observable<T>; /** * Utility functions for memory management */ declare const MemoryUtils: { /** * Get current memory usage (if available) */ getCurrentMemoryUsage(): { used: number; total: number; limit: number; } | null; /** * Check if memory usage is high */ isMemoryUsageHigh(): boolean; /** * Log memory statistics */ logMemoryStats(): void; }; export { FormSubscriptionManager, HttpRequestManager, MemoryLeakDetector, MemoryOptimizer, MemoryUtils, MockSubscriptionManager, NgTerminusModule, SubscriptionDebugger, SubscriptionDebuggerService, SubscriptionManager, SubscriptionTester, TestObservable, TestScenarios, bufferWhileHidden, cancelOnDestroy, cancelPrevious, createManagedObservable, isObservableValue, isSubscription, limitEmissionRate, logHttpRequests, manageManyObservables, optimizeMemory, retryWithBackoff, safeUnsubscribe, shareWithAutoCleanup, takeUntilDestroyed, takeUntilFormDestroyed, takeUntilHidden, takeUntilRoute, takeWhileFormValid, takeWhileOnRoute, takeWhileVisible, throttleWhileHidden, untilDestroyed }; export type { CleanupFunction, MemoryStats, NgTerminusConfig, ObservableFactory, PerformanceMetrics, SubscriptionConfig, SubscriptionDebugInfo, SubscriptionDebugOptions, TakeUntilDestroyed };