From 74bb7e234535d79a27b8ba22b63cac30ff41e62e Mon Sep 17 00:00:00 2001 From: Aleksei Potsetsuev Date: Wed, 27 Nov 2024 01:13:11 +0800 Subject: [PATCH] feat: base implementation --- packages/@wroud/api-logger/README.md | 108 ++++++ packages/@wroud/api-logger/package.json | 50 +++ packages/@wroud/api-logger/src/ILogger.ts | 19 ++ packages/@wroud/api-logger/src/index.ts | 1 + packages/@wroud/api-logger/tsconfig.json | 16 + packages/@wroud/flow-middleware/README.md | 108 ++++++ packages/@wroud/flow-middleware/package.json | 55 +++ .../flow-middleware/src/FlowMiddleware.ts | 56 ++++ .../flow-middleware/src/MiddlewareRequest.ts | 317 ++++++++++++++++++ packages/@wroud/flow-middleware/src/index.ts | 1 + .../src/interfaces/IErrorMiddleware.ts | 13 + .../src/interfaces/IFlowMiddleware.ts | 28 ++ .../src/interfaces/IMiddleware.ts | 12 + .../src/interfaces/IMiddlewareRequest.ts | 15 + .../flow-middleware/src/interfaces/index.ts | 4 + packages/@wroud/flow-middleware/tsconfig.json | 21 ++ packages/_aggregate/package.json | 2 + packages/_aggregate/tsconfig.json | 6 + yarn.lock | 25 ++ 19 files changed, 857 insertions(+) create mode 100644 packages/@wroud/api-logger/README.md create mode 100644 packages/@wroud/api-logger/package.json create mode 100644 packages/@wroud/api-logger/src/ILogger.ts create mode 100644 packages/@wroud/api-logger/src/index.ts create mode 100644 packages/@wroud/api-logger/tsconfig.json create mode 100644 packages/@wroud/flow-middleware/README.md create mode 100644 packages/@wroud/flow-middleware/package.json create mode 100644 packages/@wroud/flow-middleware/src/FlowMiddleware.ts create mode 100644 packages/@wroud/flow-middleware/src/MiddlewareRequest.ts create mode 100644 packages/@wroud/flow-middleware/src/index.ts create mode 100644 packages/@wroud/flow-middleware/src/interfaces/IErrorMiddleware.ts create mode 100644 packages/@wroud/flow-middleware/src/interfaces/IFlowMiddleware.ts create mode 100644 packages/@wroud/flow-middleware/src/interfaces/IMiddleware.ts create mode 100644 packages/@wroud/flow-middleware/src/interfaces/IMiddlewareRequest.ts create mode 100644 packages/@wroud/flow-middleware/src/interfaces/index.ts create mode 100644 packages/@wroud/flow-middleware/tsconfig.json diff --git a/packages/@wroud/api-logger/README.md b/packages/@wroud/api-logger/README.md new file mode 100644 index 0000000..87738ab --- /dev/null +++ b/packages/@wroud/api-logger/README.md @@ -0,0 +1,108 @@ +# @wroud/api-logger + +[![ESM-only package][package]][esm-info-url] +[![NPM version][npm]][npm-url] + + + +[package]: https://img.shields.io/badge/package-ESM--only-ffe536.svg +[esm-info-url]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c +[npm]: https://img.shields.io/npm/v/@wroud/api-logger.svg +[npm-url]: https://npmjs.com/package/@wroud/api-logger +[size]: https://packagephobia.com/badge?p=@wroud/api-logger +[size-url]: https://packagephobia.com/result?p=@wroud/api-logger + +@wroud/api-logger is a lightweight and flexible logging interface library for JavaScript and TypeScript applications. It provides a standardized way to implement logging across your projects, ensuring consistency and ease of maintenance. Designed with modern JavaScript features in mind, it seamlessly integrates with various logging implementations. + +## Features + +- **TypeScript Support**: Fully typed interfaces for enhanced developer experience. +- **ESM-only Package**: Utilizes ES modules for optimal performance and compatibility. +- **Flexible Logging Levels**: Supports `info`, `warn`, and `error` levels. +- **Ease of Integration**: Easily implement the `ILogger` interface with your preferred logging libraries. + +## Installation + +Install via npm: + +```sh +npm install @wroud/api-logger +``` + +Install via yarn: + +```sh +yarn add @wroud/api-logger +``` + +## Documentation + +For detailed usage and API reference, visit the [documentation site](https://wroud.dev/). + +## Example + +```ts +// Import the ILogger interface +import { ILogger } from "@wroud/api-logger"; + +// Implement the ILogger interface +class ConsoleLogger implements ILogger { + info(...messages: any[]): void { + console.info(...messages); + } + + warn(...messages: any[]): void { + console.warn(...messages); + } + + error(...messages: any[]): void { + console.error(...messages); + } +} + +// Usage example +const logger: ILogger = new ConsoleLogger(); + +logger.info("This is an info message"); +logger.warn("This is a warning message"); +logger.error("This is an error message"); +``` + +### Integrating with @wroud/di + +If you're using `@wroud/di` for dependency injection, you can easily inject your logger implementation: + +```ts +import { ServiceContainerBuilder, injectable } from "@wroud/di"; +import { ILogger } from "@wroud/api-logger"; + +@injectable() +class ConsoleLogger implements ILogger { + info(...messages: any[]): void { + console.info(...messages); + } + + warn(...messages: any[]): void { + console.warn(...messages); + } + + error(...messages: any[]): void { + console.error(...messages); + } +} + +const builder = new ServiceContainerBuilder(); +builder.addSingleton(ConsoleLogger); +const provider = builder.build(); + +const logger = provider.getService(ConsoleLogger); +logger.info("Hello world with DI!"); +``` + +## Changelog + +All notable changes to this project will be documented in the [CHANGELOG](./CHANGELOG.md) file. + +## License + +This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for details. diff --git a/packages/@wroud/api-logger/package.json b/packages/@wroud/api-logger/package.json new file mode 100644 index 0000000..7d4d1c6 --- /dev/null +++ b/packages/@wroud/api-logger/package.json @@ -0,0 +1,50 @@ +{ + "name": "@wroud/api-logger", + "type": "module", + "version": "0.0.0", + "description": "@wroud/api-logger is a lightweight, TypeScript-compatible logging interface for JavaScript applications. It provides standardized logging methods (`info`, `warn`, `error`) to ensure consistent and maintainable logging across your projects. Designed as an ESM-only package, it seamlessly integrates with modern JavaScript workflows and various logging implementations, making it an ideal choice for developers seeking flexibility and type safety in their logging solutions.", + "sideEffects": [], + "exports": { + ".": "./lib/index.js", + "./*": "./lib/*.js" + }, + "scripts": { + "ci:release": "yarn ci release --prefix api-logger-v", + "ci:git-tag": "yarn ci git-tag --prefix api-logger-v", + "ci:release-github": "yarn ci release-github --prefix api-logger-v", + "build": "tsc -b", + "clear": "rimraf lib" + }, + "files": [ + "package.json", + "LICENSE", + "README.md", + "CHANGELOG.md", + "lib", + "!lib/**/*.d.ts.map", + "!lib/**/*.test.js", + "!lib/**/*.test.d.ts", + "!lib/**/*.test.d.ts.map", + "!lib/**/*.test.js.map", + "!lib/tests", + "!.tsbuildinfo" + ], + "packageManager": "yarn@4.5.3", + "devDependencies": { + "@wroud/ci": "workspace:^", + "@wroud/tsconfig": "workspace:^", + "rimraf": "^6", + "typescript": "^5" + }, + "keywords": [ + "logger", + "logging", + "typescript", + "javascript", + "ESM", + "interface", + "lightweight", + "logging-interface", + "API-logger" + ] +} diff --git a/packages/@wroud/api-logger/src/ILogger.ts b/packages/@wroud/api-logger/src/ILogger.ts new file mode 100644 index 0000000..d65e67e --- /dev/null +++ b/packages/@wroud/api-logger/src/ILogger.ts @@ -0,0 +1,19 @@ +export interface ILogger { + /** + * Logs a message with the "info" level. + * @param {any[]} messages - The messages to log. + */ + info(...messages: any[]): void; + + /** + * Logs a message with the "warn" level. + * @param {any[]} messages - The messages to log. + */ + warn(...messages: any[]): void; + + /** + * Logs a message with the "error" level. + * @param {any[]} messages - The messages to log. + */ + error(...messages: any[]): void; +} diff --git a/packages/@wroud/api-logger/src/index.ts b/packages/@wroud/api-logger/src/index.ts new file mode 100644 index 0000000..d7cb623 --- /dev/null +++ b/packages/@wroud/api-logger/src/index.ts @@ -0,0 +1 @@ +export * from "./ILogger.js"; diff --git a/packages/@wroud/api-logger/tsconfig.json b/packages/@wroud/api-logger/tsconfig.json new file mode 100644 index 0000000..2b67794 --- /dev/null +++ b/packages/@wroud/api-logger/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "@wroud/tsconfig/tsconfig.json", + "compilerOptions": { + "tsBuildInfoFile": "./lib/.tsbuildinfo", + "rootDir": "src", + "rootDirs": [ + "src" + ], + "outDir": "lib", + "incremental": true, + "composite": true + }, + "include": [ + "src" + ] +} diff --git a/packages/@wroud/flow-middleware/README.md b/packages/@wroud/flow-middleware/README.md new file mode 100644 index 0000000..10f00de --- /dev/null +++ b/packages/@wroud/flow-middleware/README.md @@ -0,0 +1,108 @@ +# @wroud/flow-middleware + +[![ESM-only package][package]][esm-info-url] +[![NPM version][npm]][npm-url] + + + +[package]: https://img.shields.io/badge/package-ESM--only-ffe536.svg +[esm-info-url]: https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c +[npm]: https://img.shields.io/npm/v/@wroud/flow-middleware.svg +[npm-url]: https://npmjs.com/package/@wroud/flow-middleware +[size]: https://packagephobia.com/badge?p=@wroud/flow-middleware +[size-url]: https://packagephobia.com/result?p=@wroud/flow-middleware + +@wroud/flow-middleware is a lightweight middleware management library for JavaScript and TypeScript. It facilitates the creation and execution of middleware chains with support for re-runs, error handling, and disposability. Inspired by modern middleware patterns, it leverages TypeScript for type safety and ESM for optimal performance. + +## Features + +- **Modern JavaScript**: Utilizes ES modules and ESNext syntax for advanced performance optimizations. +- **TypeScript**: Written in TypeScript for type safety and enhanced developer experience. +- **Middleware Chains**: Easily create and manage middleware chains with support for asynchronous operations. +- **Re-run Capabilities**: Middlewares can trigger re-execution of the middleware chain based on external events. +- **Error Handling**: Dedicated error-handling middlewares to manage and respond to errors gracefully. +- **Subscription Management**: Efficiently handle subscriptions with automatic cleanup to prevent memory leaks. +- **Disposability**: Cleanly dispose of middleware requests and all associated subscriptions. + +## Installation + +Install via npm: + +```sh +npm install @wroud/flow-middleware +``` + +Install via yarn: + +```sh +yarn add @wroud/flow-middleware +``` + +## Documentation + +For detailed usage and API reference, visit the [documentation site](https://wroud.dev). + +## Example + +```ts +import { FlowMiddleware } from "@wroud/flow-middleware"; +import type { + IMiddleware, + IErrorMiddleware, +} from "@wroud/flow-middleware/interfaces"; + +/** + * Simple Middleware Example + */ +const simpleMiddleware: IMiddleware<{ message: string }> = async ( + req, + next, + triggerReRun, + subscribe, +) => { + console.log("Middleware: Processing message:", req.message); + await next(); +}; + +/** + * Simple Error Middleware Example + */ +const simpleErrorMiddleware: IErrorMiddleware<{ message: string }> = async ( + error, + req, + next, + triggerReRun, + subscribe, +) => { + console.error("ErrorMiddleware: An error occurred:", error.message); + // Handle error, modify request data, or trigger re-run + req.message = "Error handled"; + triggerReRun(); +}; + +const middleware = new FlowMiddleware(); + +// Register middlewares +middleware.register(simpleMiddleware); +middleware.registerErrorMiddleware(simpleErrorMiddleware); + +// Create a new request with initial data +const request = middleware.createRequest({ message: "Hello, FlowMiddleware!" }); + +// Execute the middleware chain +(async () => { + try { + await request.execute(); + } catch (error) { + console.error("Main: Error executing middleware chain:", error); + } +})(); +``` + +## Changelog + +All notable changes to this project will be documented in the [CHANGELOG](./CHANGELOG.md) file. + +## License + +This project is licensed under the MIT License. See the [LICENSE](./LICENSE) file for details. diff --git a/packages/@wroud/flow-middleware/package.json b/packages/@wroud/flow-middleware/package.json new file mode 100644 index 0000000..53d2aa2 --- /dev/null +++ b/packages/@wroud/flow-middleware/package.json @@ -0,0 +1,55 @@ +{ + "name": "@wroud/flow-middleware", + "type": "module", + "version": "0.0.0", + "description": "A lightweight middleware management library for JavaScript and TypeScript, facilitating middleware chains with re-runs, error handling, and disposability.", + "sideEffects": [], + "exports": { + ".": "./lib/index.js", + "./*": "./lib/*.js" + }, + "scripts": { + "ci:release": "yarn ci release --prefix api-logger-v", + "ci:git-tag": "yarn ci git-tag --prefix api-logger-v", + "ci:release-github": "yarn ci release-github --prefix api-logger-v", + "build": "tsc -b", + "clear": "rimraf lib" + }, + "files": [ + "package.json", + "LICENSE", + "README.md", + "CHANGELOG.md", + "lib", + "!lib/**/*.d.ts.map", + "!lib/**/*.test.js", + "!lib/**/*.test.d.ts", + "!lib/**/*.test.d.ts.map", + "!lib/**/*.test.js.map", + "!lib/tests", + "!.tsbuildinfo" + ], + "packageManager": "yarn@4.5.3", + "devDependencies": { + "@wroud/api-logger": "workspace:^", + "@wroud/ci": "workspace:^", + "@wroud/tsconfig": "workspace:^", + "rimraf": "^6", + "typescript": "^5" + }, + "keywords": [ + "middleware", + "typescript", + "javascript", + "flow control", + "error handling", + "ESM", + "asynchronous", + "dependency management", + "middleware chains", + "re-run", + "disposable", + "flow middleware", + "middleware library" + ] +} diff --git a/packages/@wroud/flow-middleware/src/FlowMiddleware.ts b/packages/@wroud/flow-middleware/src/FlowMiddleware.ts new file mode 100644 index 0000000..598c24a --- /dev/null +++ b/packages/@wroud/flow-middleware/src/FlowMiddleware.ts @@ -0,0 +1,56 @@ +import type { IMiddlewareRequest } from "./interfaces/IMiddlewareRequest.js"; +import type { IMiddleware } from "./interfaces/IMiddleware.js"; +import type { IFlowMiddleware } from "./interfaces/IFlowMiddleware.js"; +import { MiddlewareRequest } from "./MiddlewareRequest.js"; +import type { IErrorMiddleware } from "./interfaces/IErrorMiddleware.js"; +import type { ILogger } from "@wroud/api-logger"; + +/** + * Singleton class to register and manage middlewares. + * @template Data - The shape of the request data. + */ +export class FlowMiddleware> + implements IFlowMiddleware +{ + private middlewares: IMiddleware[]; + private errorMiddlewares: IErrorMiddleware[]; + + constructor(private readonly logger?: ILogger) { + this.middlewares = []; + this.errorMiddlewares = []; + } + + /** + * Registers a middleware globally. + * @param {Middleware} middleware - The middleware function to register. + */ + public register(middleware: IMiddleware): void { + this.middlewares.push(middleware); + } + + /** + * Registers an error-handling middleware globally. + * @param {ErrorMiddleware} errorMiddleware - The error middleware function to register. + */ + public registerErrorMiddleware( + errorMiddleware: IErrorMiddleware, + ): void { + this.errorMiddlewares.push(errorMiddleware); + } + + /** + * Creates a new MiddlewareRequest instance. + * @param {Data} initialData - Initial data for the request. + * @returns {MiddlewareRequest} A new MiddlewareRequest instance. + */ + public createRequest( + initialData: Data = {} as Data, + ): IMiddlewareRequest { + return new MiddlewareRequest( + this.middlewares, + this.errorMiddlewares, + initialData, + this.logger, + ); + } +} diff --git a/packages/@wroud/flow-middleware/src/MiddlewareRequest.ts b/packages/@wroud/flow-middleware/src/MiddlewareRequest.ts new file mode 100644 index 0000000..6969327 --- /dev/null +++ b/packages/@wroud/flow-middleware/src/MiddlewareRequest.ts @@ -0,0 +1,317 @@ +import type { ILogger } from "@wroud/api-logger"; +import type { IErrorMiddleware } from "./interfaces/IErrorMiddleware.js"; +import type { IMiddleware } from "./interfaces/IMiddleware.js"; +import type { IMiddlewareRequest } from "./interfaces/IMiddlewareRequest.js"; + +type MiddlewareStates = Map, Map void>>; +type ErrorMiddlewareStates = Map< + IErrorMiddleware, + Map void> +>; + +/** + * Class representing a middleware request. + * @template Data - The shape of the request data. + */ +export class MiddlewareRequest> + implements IMiddlewareRequest +{ + public data: Data; + private middlewareStates: MiddlewareStates; + private errorMiddlewareStates: ErrorMiddlewareStates; + private isDisposed: boolean; + + /** + * Creates an instance of MiddlewareRequest. + * @param {Middleware[]} middlewares - Array of middleware functions. + * @param {Data} initialData - Initial data for the request. + */ + constructor( + private readonly middlewares: IMiddleware[], + private readonly errorMiddlewares: IErrorMiddleware[], + initialData: Data, + private readonly logger?: ILogger, + ) { + this.middlewares = middlewares; + this.data = { ...initialData }; + this.middlewareStates = new Map(); + this.errorMiddlewareStates = new Map(); + this.isDisposed = false; + } + + /** + * Executes the middleware chain. + */ + public async execute(): Promise { + if (this.isDisposed) { + throw new Error("Cannot execute a disposed request."); + } + + try { + const newMiddlewareStates: MiddlewareStates = new Map(); + + for (let i = 0; i < this.middlewares.length; i++) { + const middleware = this.middlewares[i]!; + const currentState = + this.middlewareStates.get(middleware) || + new Map void>(); + + /** + * Subscribes to an external event. + * @param {string} key - Unique key for the subscription. + * @param {() => () => void} subscribeFn - Function that sets up the subscription and returns an unsubscribe function. + */ + const subscribe = ( + key: string, + subscribeFn: () => () => void, + ): void => { + if (currentState.has(key)) { + return; // Subscription already exists. + } + + const unsubscribe = subscribeFn(); + currentState.set(key, unsubscribe); + }; + + await middleware( + this.data, + () => this.executeNext(i + 1), + this.triggerReRun.bind(this), + subscribe, + ); + + newMiddlewareStates.set(middleware, currentState); + } + + this.setMiddlewareStates(newMiddlewareStates); + } catch (error) { + await this.handleError(error as Error); + } + } + + /** + * Executes the next middleware in the chain. + * @param {number} nextIndex - Index of the next middleware to execute. + */ + private async executeNext(nextIndex: number): Promise { + if (nextIndex >= this.middlewares.length) return; + + const middleware = this.middlewares[nextIndex]!; + const currentState = + this.middlewareStates.get(middleware) || new Map void>(); + + /** + * Subscribes to an external event. + * @param {string} key - Unique key for the subscription. + * @param {() => () => void} subscribeFn - Function that sets up the subscription and returns an unsubscribe function. + */ + const subscribe = (key: string, subscribeFn: () => () => void): void => { + if (currentState.has(key)) { + return; // Subscription already exists. + } + + const unsubscribe = subscribeFn(); + currentState.set(key, unsubscribe); + }; + + try { + await middleware( + this.data, + () => this.executeNext(nextIndex + 1), + this.triggerReRun.bind(this), + subscribe, + ); + + this.middlewareStates.set(middleware, currentState); + } catch (error) { + await this.handleError(error as Error); + } + } + + /** + * Triggers a re-execution of the middleware chain. + */ + private async triggerReRun(): Promise { + if (this.isDisposed) return; + this.logger?.info("Re-run triggered."); + await this.execute(); + } + + /** + * Handles errors by executing error-handling middlewares. + * @param {Error} error - The error to handle. + */ + private async handleError(error: Error): Promise { + if (this.isDisposed) { + this.logger?.error("Cannot handle error for a disposed request:", error); + return; + } + + if (this.errorMiddlewares.length === 0) { + this.logger?.error("Unhandled error:", error); + return; + } + + try { + const newErrorMiddlewareStates: ErrorMiddlewareStates = new Map(); + + for (let i = 0; i < this.errorMiddlewares.length; i++) { + const errorMiddleware = this.errorMiddlewares[i]!; + const currentState = + this.errorMiddlewareStates.get(errorMiddleware) || + new Map void>(); + + /** + * Subscribes to an external event. + * @param {string} key - Unique key for the subscription. + * @param {() => () => void} subscribeFn - Function that sets up the subscription and returns an unsubscribe function. + */ + const subscribe = ( + key: string, + subscribeFn: () => () => void, + ): void => { + if (currentState.has(key)) { + return; // Subscription already exists. + } + + const unsubscribe = subscribeFn(); + currentState.set(key, unsubscribe); + }; + + await errorMiddleware( + error, + this.data, + () => this.execute(), + this.triggerReRun.bind(this), + subscribe, + ); + + newErrorMiddlewareStates.set(errorMiddleware, currentState); + } + + this.setErrorMiddlewareStates(newErrorMiddlewareStates); + } catch (err) { + this.logger?.error("Error in error-handling middleware:", err); + } + } + + private setMiddlewareStates( + newMiddlewareStates: MiddlewareStates, + ): void { + this.cleanupSubscriptions(newMiddlewareStates); + this.middlewareStates = newMiddlewareStates; + } + + private setErrorMiddlewareStates( + newErrorMiddlewareStates: ErrorMiddlewareStates, + ): void { + this.cleanupErrorSubscriptions(newErrorMiddlewareStates); + this.errorMiddlewareStates = newErrorMiddlewareStates; + } + + /** + * Cleans up subscriptions that are no longer active in regular middlewares. + * @param {Map, Map void>>} newMiddlewareStates - The updated middleware states after execution. + */ + private cleanupSubscriptions( + newMiddlewareStates: Map, Map void>>, + ): void { + for (const [middleware, state] of this.middlewareStates.entries()) { + if (!newMiddlewareStates.has(middleware)) { + this.disposeMiddlewareSubscriptions(middleware, state); + } + } + } + + /** + * Cleans up subscriptions that are no longer active in error middlewares. + * @param {Map, Map void>>} newErrorMiddlewareStates - The updated error middleware states after execution. + */ + private cleanupErrorSubscriptions( + newErrorMiddlewareStates: Map< + IErrorMiddleware, + Map void> + >, + ): void { + for (const [ + errorMiddleware, + state, + ] of this.errorMiddlewareStates.entries()) { + if (!newErrorMiddlewareStates.has(errorMiddleware)) { + this.disposeErrorMiddlewareSubscriptions(errorMiddleware, state); + } + } + } + + /** + * Disposes all subscriptions for a specific regular middleware. + * @param {Middleware} middleware - The middleware whose subscriptions are to be disposed. + * @param {Map void>} state - The subscription state of the middleware. + */ + private disposeMiddlewareSubscriptions( + middleware: IMiddleware, + state: Map void>, + ): void { + this.logger?.info( + `Disposing subscriptions for middleware: ${middleware.name || "anonymous"}`, + ); + for (const [key, unsubscribe] of state.entries()) { + try { + unsubscribe(); + this.logger?.info(`Unsubscribed from ${key}.`); + } catch (error) { + this.logger?.error(`Error unsubscribing from ${key}:`, error); + } + } + state.clear(); + } + + /** + * Disposes all subscriptions for a specific error middleware. + * @param {ErrorMiddleware} errorMiddleware - The error middleware whose subscriptions are to be disposed. + * @param {Map void>} state - The subscription state of the error middleware. + */ + private disposeErrorMiddlewareSubscriptions( + errorMiddleware: IErrorMiddleware, + state: Map void>, + ): void { + this.logger?.info( + `Disposing subscriptions for error middleware: ${errorMiddleware.name || "anonymous"}`, + ); + for (const [key, unsubscribe] of state.entries()) { + try { + unsubscribe(); + this.logger?.info(`Unsubscribed from ${key}.`); + } catch (error) { + this.logger?.error(`Error unsubscribing from ${key}:`, error); + } + } + state.clear(); + } + + /** + * Disposes the request and all active subscriptions. + */ + public dispose(): void { + if (this.isDisposed) return; + + this.isDisposed = true; + this.logger?.info("Disposing all subscriptions and request."); + + // Dispose regular middlewares + for (const [middleware, state] of this.middlewareStates.entries()) { + this.disposeMiddlewareSubscriptions(middleware, state); + } + + // Dispose error middlewares + for (const [ + errorMiddleware, + state, + ] of this.errorMiddlewareStates.entries()) { + this.disposeErrorMiddlewareSubscriptions(errorMiddleware, state); + } + + this.middlewareStates.clear(); + this.errorMiddlewareStates.clear(); + } +} diff --git a/packages/@wroud/flow-middleware/src/index.ts b/packages/@wroud/flow-middleware/src/index.ts new file mode 100644 index 0000000..4070491 --- /dev/null +++ b/packages/@wroud/flow-middleware/src/index.ts @@ -0,0 +1 @@ +export * from "./FlowMiddleware.js"; diff --git a/packages/@wroud/flow-middleware/src/interfaces/IErrorMiddleware.ts b/packages/@wroud/flow-middleware/src/interfaces/IErrorMiddleware.ts new file mode 100644 index 0000000..b41c512 --- /dev/null +++ b/packages/@wroud/flow-middleware/src/interfaces/IErrorMiddleware.ts @@ -0,0 +1,13 @@ +/** + * Type definition for an Error Middleware function. + * @template Data - The shape of the request data. + */ +export interface IErrorMiddleware> { + ( + error: Error, + data: Data, + next: () => Promise, + triggerReRun: () => Promise, + subscribe: (key: string, subscribeFn: () => () => void) => void, + ): Promise; +} diff --git a/packages/@wroud/flow-middleware/src/interfaces/IFlowMiddleware.ts b/packages/@wroud/flow-middleware/src/interfaces/IFlowMiddleware.ts new file mode 100644 index 0000000..04003a1 --- /dev/null +++ b/packages/@wroud/flow-middleware/src/interfaces/IFlowMiddleware.ts @@ -0,0 +1,28 @@ +import type { IErrorMiddleware } from "./IErrorMiddleware.js"; +import type { IMiddleware } from "./IMiddleware.js"; +import type { IMiddlewareRequest } from "./IMiddlewareRequest.js"; + +/** + * Interface for the FlowMiddleware singleton. + * @template Data - The shape of the request data. + */ +export interface IFlowMiddleware> { + /** + * Registers a middleware globally. + * @param {Middleware} middleware - The middleware function to register. + */ + register(middleware: IMiddleware): void; + + /** + * Registers an error-handling middleware globally. + * @param {ErrorMiddleware} errorMiddleware - The error middleware function to register. + */ + registerErrorMiddleware(errorMiddleware: IErrorMiddleware): void; + + /** + * Creates a new MiddlewareRequest instance. + * @param {Data} initialData - Initial data for the request. + * @returns {IMiddlewareRequest} A new MiddlewareRequest instance. + */ + createRequest(initialData?: Data): IMiddlewareRequest; +} diff --git a/packages/@wroud/flow-middleware/src/interfaces/IMiddleware.ts b/packages/@wroud/flow-middleware/src/interfaces/IMiddleware.ts new file mode 100644 index 0000000..26adbc6 --- /dev/null +++ b/packages/@wroud/flow-middleware/src/interfaces/IMiddleware.ts @@ -0,0 +1,12 @@ +/** + * Type definition for a Middleware function. + * @template Data - The shape of the request data. + */ +export interface IMiddleware> { + ( + data: Data, + next: () => Promise, + triggerReRun: () => Promise, + subscribe: (key: string, subscribeFn: () => () => void) => void, + ): Promise; +} diff --git a/packages/@wroud/flow-middleware/src/interfaces/IMiddlewareRequest.ts b/packages/@wroud/flow-middleware/src/interfaces/IMiddlewareRequest.ts new file mode 100644 index 0000000..bffbd8e --- /dev/null +++ b/packages/@wroud/flow-middleware/src/interfaces/IMiddlewareRequest.ts @@ -0,0 +1,15 @@ +/** + * Interface for the MiddlewareRequest class. + * @template Data - The shape of the request data. + */ +export interface IMiddlewareRequest> { + /** + * Executes the middleware chain. + */ + execute(): Promise; + + /** + * Disposes the request and all active subscriptions. + */ + dispose(): void; +} diff --git a/packages/@wroud/flow-middleware/src/interfaces/index.ts b/packages/@wroud/flow-middleware/src/interfaces/index.ts new file mode 100644 index 0000000..64b76e5 --- /dev/null +++ b/packages/@wroud/flow-middleware/src/interfaces/index.ts @@ -0,0 +1,4 @@ +export * from "./IErrorMiddleware.js"; +export * from "./IMiddleware.js"; +export * from "./IMiddlewareRequest.js"; +export * from "./IFlowMiddleware.js"; diff --git a/packages/@wroud/flow-middleware/tsconfig.json b/packages/@wroud/flow-middleware/tsconfig.json new file mode 100644 index 0000000..e252f6b --- /dev/null +++ b/packages/@wroud/flow-middleware/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "@wroud/tsconfig/tsconfig.json", + "compilerOptions": { + "tsBuildInfoFile": "./lib/.tsbuildinfo", + "rootDir": "src", + "rootDirs": [ + "src" + ], + "outDir": "lib", + "incremental": true, + "composite": true + }, + "include": [ + "src" + ], + "references": [ + { + "path": "../api-logger" + } + ] +} diff --git a/packages/_aggregate/package.json b/packages/_aggregate/package.json index bdf9207..a706708 100644 --- a/packages/_aggregate/package.json +++ b/packages/_aggregate/package.json @@ -19,6 +19,7 @@ "tslib": "^2" }, "devDependencies": { + "@wroud/api-logger": "workspace:^", "@wroud/ci": "workspace:*", "@wroud/conventional-commits-bump": "workspace:^", "@wroud/conventional-commits-changelog": "workspace:*", @@ -28,6 +29,7 @@ "@wroud/di-tools-analyzer": "workspace:*", "@wroud/di-tools-codemod": "workspace:*", "@wroud/docs": "workspace:*", + "@wroud/flow-middleware": "workspace:^", "@wroud/git": "workspace:*", "@wroud/github": "workspace:*", "@wroud/preconditions": "workspace:^", diff --git a/packages/_aggregate/tsconfig.json b/packages/_aggregate/tsconfig.json index 7624282..2b99e29 100644 --- a/packages/_aggregate/tsconfig.json +++ b/packages/_aggregate/tsconfig.json @@ -14,6 +14,9 @@ "src" ], "references": [ + { + "path": "../@wroud/api-logger" + }, { "path": "../@wroud/conventional-commits-bump" }, @@ -38,6 +41,9 @@ { "path": "../@wroud/di-tools-codemod/tsconfig.commonjs.json" }, + { + "path": "../@wroud/flow-middleware" + }, { "path": "../@wroud/git" }, diff --git a/yarn.lock b/yarn.lock index 1101737..c1a2f38 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4005,6 +4005,7 @@ __metadata: version: 0.0.0-use.local resolution: "@wroud/_aggregate@workspace:packages/_aggregate" dependencies: + "@wroud/api-logger": "workspace:^" "@wroud/ci": "workspace:*" "@wroud/conventional-commits-bump": "workspace:^" "@wroud/conventional-commits-changelog": "workspace:*" @@ -4014,6 +4015,7 @@ __metadata: "@wroud/di-tools-analyzer": "workspace:*" "@wroud/di-tools-codemod": "workspace:*" "@wroud/docs": "workspace:*" + "@wroud/flow-middleware": "workspace:^" "@wroud/git": "workspace:*" "@wroud/github": "workspace:*" "@wroud/preconditions": "workspace:^" @@ -4032,6 +4034,17 @@ __metadata: languageName: unknown linkType: soft +"@wroud/api-logger@workspace:^, @wroud/api-logger@workspace:packages/@wroud/api-logger": + version: 0.0.0-use.local + resolution: "@wroud/api-logger@workspace:packages/@wroud/api-logger" + dependencies: + "@wroud/ci": "workspace:^" + "@wroud/tsconfig": "workspace:^" + rimraf: "npm:^6" + typescript: "npm:^5" + languageName: unknown + linkType: soft + "@wroud/ci@workspace:*, @wroud/ci@workspace:^, @wroud/ci@workspace:packages/@wroud/ci": version: 0.0.0-use.local resolution: "@wroud/ci@workspace:packages/@wroud/ci" @@ -4247,6 +4260,18 @@ __metadata: languageName: unknown linkType: soft +"@wroud/flow-middleware@workspace:^, @wroud/flow-middleware@workspace:packages/@wroud/flow-middleware": + version: 0.0.0-use.local + resolution: "@wroud/flow-middleware@workspace:packages/@wroud/flow-middleware" + dependencies: + "@wroud/api-logger": "workspace:^" + "@wroud/ci": "workspace:^" + "@wroud/tsconfig": "workspace:^" + rimraf: "npm:^6" + typescript: "npm:^5" + languageName: unknown + linkType: soft + "@wroud/git@workspace:*, @wroud/git@workspace:^0, @wroud/git@workspace:packages/@wroud/git": version: 0.0.0-use.local resolution: "@wroud/git@workspace:packages/@wroud/git"