UNPKG

@wroud/flow-middleware

Version:

A lightweight middleware management library for JavaScript and TypeScript, facilitating middleware chains with re-runs, error handling, and disposability.

169 lines 6 kB
export class MiddlewareRequest { middlewares; errorMiddlewares; data; logger; middlewareStates; isDisposed; scheduledReRun; isRunning; constructor(middlewares, errorMiddlewares, data, logger) { this.middlewares = middlewares; this.errorMiddlewares = errorMiddlewares; this.data = data; this.logger = logger; this.middlewares = middlewares; this.middlewareStates = new Map(); this.isDisposed = false; this.scheduledReRun = false; this.isRunning = false; } use(...middleware) { this.middlewares.push(...middleware); return this; } error(...errorMiddleware) { this.errorMiddlewares.push(...errorMiddleware); return this; } async execute(error) { if (this.isDisposed) { throw new Error("Cannot execute a disposed request."); } const executedMiddlewares = new Set(); try { this.isRunning = true; if (error !== undefined) { throw error; } await this.executeNext(executedMiddlewares, 0); } catch (error) { await this.handleNextError(executedMiddlewares, 0, error); } finally { await this.cleanupSubscriptions(executedMiddlewares); this.isRunning = false; if (this.scheduledReRun) { this.scheduledReRun = false; await this.execute(); } } } async executeNext(executedMiddlewares, nextIndex) { if (nextIndex >= this.middlewares.length) { return; } const middleware = this.middlewares[nextIndex]; let currentState = this.middlewareStates.get(middleware); if (!currentState) { currentState = new Map(); this.middlewareStates.set(middleware, currentState); } this.markMiddlewareSubscriptionsInactive(currentState); await middleware(this.data, () => this.executeNext(executedMiddlewares, nextIndex + 1), this.triggerReRun.bind(this), subscribe.bind(null, currentState)); executedMiddlewares.add(middleware); } async triggerReRun(error) { this.logger?.info("Re-run triggered."); if (this.isRunning) { this.scheduledReRun = true; } else { await this.execute(error); } } async handleNextError(executedMiddlewares, nextIndex, error) { if (nextIndex >= this.errorMiddlewares.length) { this.logger?.error("Unhandled error:", error); throw error; } try { const errorMiddleware = this.errorMiddlewares[nextIndex]; let currentState = this.middlewareStates.get(errorMiddleware); if (!currentState) { currentState = new Map(); this.middlewareStates.set(errorMiddleware, currentState); } await errorMiddleware(error, this.data, () => this.handleNextError(executedMiddlewares, nextIndex + 1, error), this.triggerReRun.bind(this), subscribe.bind(null, currentState)); executedMiddlewares.add(errorMiddleware); } catch (error) { await this.handleNextError(executedMiddlewares, nextIndex + 1, error); } } markMiddlewareSubscriptionsInactive(state) { for (const middlewareState of state.values()) { middlewareState.active = false; } } async cleanupSubscriptions(executedMiddlewares) { for (const [middleware, state] of this.middlewareStates.entries()) { if (executedMiddlewares.has(middleware)) { for (const [key, subscription] of state) { if (!subscription.active) { await subscription.unsubscribe(); state.delete(key); } } } else { this.disposeMiddlewareSubscriptions(middleware, state); } } } disposeMiddlewareSubscriptions(middleware, state) { this.logger?.info(`Disposing subscriptions for middleware: ${middleware.name || "anonymous"}`); for (const [key, { unsubscribe }] of state) { try { unsubscribe(); this.logger?.info(`Unsubscribed from ${key}.`); } catch (error) { this.logger?.error(`Error unsubscribing from ${key}:`, error); } } state.clear(); } dispose() { if (this.isDisposed) { return; } this.isDisposed = true; this.logger?.info("Disposing all subscriptions and request."); for (const [middleware, state] of this.middlewareStates) { this.disposeMiddlewareSubscriptions(middleware, state); } this.middlewareStates.clear(); } } function areDependenciesEqual(depsA, depsB) { if (depsA === undefined && depsB === undefined) return true; if (depsA === undefined || depsB === undefined) return false; if (depsA.length === 0 && depsB.length === 0) return true; if (depsA.length !== depsB.length) return false; for (let i = 0; i < depsA.length; i++) { if (depsA[i] !== depsB[i]) return false; } return true; } async function subscribe(state, key, subscribeFn, dependencies) { const existingSubscription = state.get(key); if (existingSubscription && areDependenciesEqual(existingSubscription.dependencies, dependencies)) { existingSubscription.active = true; return; } if (existingSubscription) { await existingSubscription.unsubscribe(); state.delete(key); } const unsubscribe = await subscribeFn(); state.set(key, { dependencies, unsubscribe, active: true }); } //# sourceMappingURL=MiddlewareRequest.js.map