{"id":26758479,"url":"https://github.com/alessiofrittoli/chain-functions","last_synced_at":"2025-04-15T05:29:29.065Z","repository":{"id":269280620,"uuid":"906745388","full_name":"alessiofrittoli/chain-functions","owner":"alessiofrittoli","description":"Functions chaining made easy","archived":false,"fork":false,"pushed_at":"2025-03-21T11:50:09.000Z","size":312,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-28T16:21:19.182Z","etag":null,"topics":["chaining"],"latest_commit_sha":null,"homepage":"https://npmjs.com/package/@alessiofrittoli/chain-functions","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alessiofrittoli.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"license.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":["alessiofrittoli"]}},"created_at":"2024-12-21T19:25:42.000Z","updated_at":"2025-03-21T11:49:56.000Z","dependencies_parsed_at":"2025-03-28T16:20:38.537Z","dependency_job_id":"54795165-ea90-430d-8cb0-17f983664d49","html_url":"https://github.com/alessiofrittoli/chain-functions","commit_stats":null,"previous_names":["alessiofrittoli/chain-functions"],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alessiofrittoli%2Fchain-functions","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alessiofrittoli%2Fchain-functions/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alessiofrittoli%2Fchain-functions/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alessiofrittoli%2Fchain-functions/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alessiofrittoli","download_url":"https://codeload.github.com/alessiofrittoli/chain-functions/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249013744,"owners_count":21198451,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["chaining"],"created_at":"2025-03-28T16:20:30.179Z","updated_at":"2025-04-15T05:29:29.036Z","avatar_url":"https://github.com/alessiofrittoli.png","language":"TypeScript","funding_links":["https://github.com/sponsors/alessiofrittoli"],"categories":[],"sub_categories":[],"readme":"# Chain Functions ⛓️\n\n[![NPM Latest Version][version-badge]][npm-url] [![Coverage Status][coverage-badge]][coverage-url] [![Socket Status][socket-badge]][socket-url] [![NPM Monthly Downloads][downloads-badge]][npm-url] [![Dependencies][deps-badge]][deps-url]\n\n[![GitHub Sponsor][sponsor-badge]][sponsor-url]\n\n[version-badge]: https://img.shields.io/npm/v/%40alessiofrittoli%2Fchain-functions\n[npm-url]: https://npmjs.org/package/%40alessiofrittoli%2Fchain-functions\n[coverage-badge]: https://coveralls.io/repos/github/alessiofrittoli/chain-functions/badge.svg\n[coverage-url]: https://coveralls.io/github/alessiofrittoli/chain-functions\n[socket-badge]: https://socket.dev/api/badge/npm/package/@alessiofrittoli/chain-functions\n[socket-url]: https://socket.dev/npm/package/@alessiofrittoli/chain-functions/overview\n[downloads-badge]: https://img.shields.io/npm/dm/%40alessiofrittoli%2Fchain-functions.svg\n[deps-badge]: https://img.shields.io/librariesio/release/npm/%40alessiofrittoli%2Fchain-functions\n[deps-url]: https://libraries.io/npm/%40alessiofrittoli%2Fchain-functions\n\n[sponsor-badge]: https://img.shields.io/static/v1?label=Fund%20this%20package\u0026message=%E2%9D%A4\u0026logo=GitHub\u0026color=%23DB61A2\n[sponsor-url]: https://github.com/sponsors/alessiofrittoli\n\n## Functions chaining made easy\n\nThe `Chain` class provides a utility for managing and executing chains of functions. Each function in the chain can optionally invoke the next function, enabling a flexible and composable flow of execution. This is particularly useful for scenarios such as middleware processing, data transformations, or handling asynchronous operations in a structured manner.\n\n### Table of Contents\n\n- [Getting started](#getting-started)\n- [API Reference](#api-reference)\n  - [`Chain` class](#chain-class)\n  - [Types](#types)\n- [Key Features](#key-features)\n- [Examples](#examples)\n- [Development](#development)\n  - [ESLint](#eslint)\n  - [Jest](#jest)\n- [Contributing](#contributing)\n- [Security](#security)\n- [Credits](#made-with-)\n\n---\n\n### Getting started\n\nRun the following command to start using `chain-functions` in your projects:\n\n```bash\nnpm i @alessiofrittoli/chain-functions\n```\n\nor using `pnpm`\n\n```bash\npnpm i @alessiofrittoli/chain-functions\n```\n\n---\n\n### API Reference\n\n#### `Chain` class\n\nA utility class for managing and executing chains of functions.\n\n##### Static Methods\n\n###### `Chain.functions()`\n\nRecursively executes a chain of functions.\n\n\u003cdetails\u003e\n\n\u003csummary\u003eParameters\u003c/summary\u003e\n\n| Parameter | Type                 | Default | Description |\n|-----------|----------------------|---------|-------------|\n| `chain`   | `ChainFactory\u003cT, U\u003e` | -       | The chain of functions to execute. This must be an array of functions (`ChainLink\u003cT\u003e`), where the last function is of type LastChainLink. See [Types](#types) section for further informations about. |\n| `index`   | `number`             | `0`     | (Optional) The starting index for execution. |\n\n\u003c/details\u003e\n\n---\n\n\u003cdetails\u003e\n\n\u003csummary\u003eReturns\u003c/summary\u003e\n\nType: `T | U`\n\nThe result of the chain execution, which matches the type of the chain's functions (`T` or `U`).\n\nSee [Types](#types) section for further informations about.\n\n\u003c/details\u003e\n\n---\n\n\u003cdetails\u003e\n\n\u003csummary\u003eThrows\u003c/summary\u003e\n\n`Error` if no function is found at the specified index.\n\n\u003c/details\u003e\n\n---\n\n\u003cdetails\u003e\n\n\u003csummary\u003eExample\u003c/summary\u003e\n\n```ts\nimport { Chain } from '@alessiofrittoli/chain-functions'\nimport type { ChainLink, LastChainLink, ChainFactory } from '@alessiofrittoli/chain-functions/types'\n\ntype ChainFunction = () =\u003e string\n\nconst function1: ChainLink\u003cChainFunction\u003e = next =\u003e () =\u003e `1-${ next() }`\nconst function2: ChainLink\u003cChainFunction\u003e = next =\u003e () =\u003e `2-${ next() }`\nconst function3: LastChainLink\u003cChainFunction\u003e = () =\u003e () =\u003e 'end'\n\nconst chain: ChainFactory\u003cChainFunction\u003e = [ function1, function2, function3 ]\nconst result = Chain.functions( chain )()\n\nconsole.log( result ) // Output: '1-2-end'\n```\n\n\u003c/details\u003e\n\n---\n\n###### `Chain.isLast()`\n\nDetermines if the given function is the last function in the chain needed to type cast the last function with `LastChainLink\u003cU\u003e`.\n\nThis method is primarily used internally by the `Chain.functions()` method to determine when the chain execution should terminate.\n\n\u003cdetails\u003e\n\n\u003csummary\u003eParameters\u003c/summary\u003e\n\n| Parameter | Type                 | Default | Description |\n|-----------|----------------------|---------|-------------|\n| `chain`   | `ChainFactory\u003cT, U\u003e` | -       | The chain of functions. See [Types](#types) section for further informations about. |\n| `fn`      | `ChainLink\u003cT\u003e \\| LastChainLink\u003cU\u003e` | -       | The function to type cast. This can be either a regular chain link or the last chain link. See [Types](#types) section for further informations about. |\n| `index`   | `number`             | `0`     | (Optional) The current index of the function in the `Chain.functions()` recursion. |\n\n\u003c/details\u003e\n\n---\n\n\u003cdetails\u003e\n\n\u003csummary\u003eReturns\u003c/summary\u003e\n\nType: `boolean`\n\nReturns `true` if the given function is the last function in the chain, `false` otherwise.\n\n\u003c/details\u003e\n\n---\n\n#### Types\n\n##### `ChainFunction`\n\nRepresents any callable function that can be invoked as part of the chain.\n\nThis is used internally to type cast other types `template` parameters.\n\n##### `ChainLink\u003cT extends ChainFunction = ChainFunction\u003e`\n\nRepresents a single link in a chain of functions.\n\n\u003cdetails\u003e\n\n\u003csummary\u003eParameters\u003c/summary\u003e\n\n| Parameter | Type | Description                                                             |\n|-----------|------|-------------------------------------------------------------------------|\n| `next`    | `T`  | The next function in the chain. Its return type must be of type of `T`. |\n\n\u003c/details\u003e\n\n---\n\n\u003cdetails\u003e\n\n\u003csummary\u003eReturns\u003c/summary\u003e\n\nType: `T`\n\nA function that can be invoked as part of the chain.\n\n\u003c/details\u003e\n\n---\n\n##### `LastChainLink\u003cT extends ChainFunction = ChainFunction\u003e`\n\nRepresents the last link in a chain of functions. Unlike `ChainLink`, it does not accept a `next` parameter.\n\n\u003cdetails\u003e\n\n\u003csummary\u003eReturns\u003c/summary\u003e\n\nType: `T`\n\nA function that can be invoked as the final step in the chain.\n\n\u003c/details\u003e\n\n---\n\n##### `ChainFactory\u003cT extends ChainFunction = ChainFunction, U extends ChainFunction = T\u003e`\n\nRepresents the complete chain of functions as an array.\n\n###### Structure\n\n- Can contain any number of `ChainLink\u003cT\u003e` functions.\n- The last element in the array must be a `LastChainLink\u003cU\u003e`.\n\n---\n\n### Key Features\n\n- Chain link functions are highly customizeable.\n- Chain link functions can be `async` functions.\n- The last chain link could return a different type (`U`) other than `T` from a standard `ChainLink`.\n\n---\n\n### Examples\n\n#### Importing the library\n\n```ts\n// importing the main `Chain` class\nimport { Chain } from '@alessiofrittoli/chain-functions'\n// importing types\nimport type { ChainLink, LastChainLink, ChainFactory } from '@alessiofrittoli/chain-functions/types'\n```\n\n\u003cdetails\u003e\n\n\u003csummary\u003eBasic usage\u003c/summary\u003e\n\n```ts\n// define the chain link function type\ntype ChainFunction = () =\u003e string\n\n// declare chain link functions\nconst function1: ChainLink\u003cChainFunction\u003e = next =\u003e () =\u003e `1-${ next() }`\nconst function2: ChainLink\u003cChainFunction\u003e = next =\u003e () =\u003e `2-${ next() }`\n// declare the last chain function\nconst function3: LastChainLink\u003cChainFunction\u003e = () =\u003e () =\u003e 'end'\n\n// declare the chain array\nconst chain: ChainFactory\u003cChainFunction\u003e = [ function1, function2, function3 ]\n// execute the chain array\nconst result = Chain.functions( chain )()\n\nconsole.log( result ) // Output: '1-2-end'\n```\n\n\u003c/details\u003e\n\n---\n\n\u003cdetails\u003e\n\n\u003csummary\u003eAdvance usage\u003c/summary\u003e\n\n```ts\ntype ChainFunctionProps = {\n    someProperty\t: string\n    firstFunction?\t: boolean\n    secondFunction?\t: boolean\n    thirdFunction?\t: boolean\n}\n// define the chain link function type\ntype ChainFunction = ( props: ChainFunctionProps ) =\u003e ChainFunctionProps\n\n// declare chain link functions\nconst function1: ChainLink\u003cChainFunction\u003e = next =\u003e props =\u003e {\n    // edit properties\n    props.someProperty\t= 'Edited by 1st function'\n    props.firstFunction\t= true\n    // call the next function in the chain\n    return next( props )\n}\n\n\nconst function2: ChainLink\u003cChainFunction\u003e = next =\u003e props =\u003e {\n    props.secondFunction = true\n\n    if ( props.someProperty === 'Edited by 1st function' ) {\n        // stop chain execution if some condition is met.\n        return props\n    }\n    \n    // call the next function in the chain\n    return next( props )\n}\n\n\n// declare the last chain function\nconst function3: LastChainLink\u003cChainFunction\u003e = () =\u003e props =\u003e {\n    props.thirdFunction = true\n    return props\n}\n\n// declare the chain array\nconst chain: ChainFactory\u003cChainFunction\u003e = [ function1, function2, function3 ]\n// declare the initial state\nconst initialState: ChainFunctionProps = {\n    someProperty\t: 'Initial value',\n    firstFunction\t: false,\n    secondFunction\t: false,\n    thirdFunction\t: false,\n}\n// execute the chain array with initial state\nconst result = Chain.functions( chain )( initialState )\n\nconsole.log( result )\n// Output: {\n// \tsomeProperty\t: 'Edited by 1st function',\n// \tfirstFunction\t: true,\n// \tsecondFunction\t: true,\n// \tthirdFunction\t: false,\n// }\n```\n\n\u003c/details\u003e\n\n---\n\n\u003cdetails\u003e\n\n\u003csummary\u003e`LastChainLink` with custom return type\u003c/summary\u003e\n\n```ts\ntype ChainFunction = () =\u003e string\ntype LastChainFunction = () =\u003e boolean\n\nconst function1: ChainLink\u003cChainFunction\u003e = next =\u003e () =\u003e `1-${ next() }`\nconst function2: ChainLink\u003cChainFunction\u003e = next =\u003e () =\u003e `2-${ next() }`\nconst function3: LastChainLink\u003cLastChainFunction\u003e = () =\u003e () =\u003e true\n\nconst chain: ChainFactory\u003cChainFunction, LastChainFunction\u003e = [ function1, function2, function3 ]\nconst result = Chain.functions( chain )()\n\nconsole.log( result ) // Outputs: '1-2-true'\n```\n\n\u003c/details\u003e\n\n---\n\n\u003cdetails\u003e\n\n\u003csummary\u003e`ChainLink` functions with promises\u003c/summary\u003e\n\n```ts\ntype ChainFunction = () =\u003e string | Promise\u003cstring\u003e\n\nconst function1: ChainLink\u003cChainFunction\u003e = next =\u003e async () =\u003e {\n    // simulate a long task running\n    await new Promise\u003cvoid\u003e( resolve =\u003e setTimeout( resolve, 5000 ) )\n    return `1-${ next() }`\n}\nconst function2: ChainLink\u003cChainFunction\u003e = next =\u003e (\n    // this function is executed once `function1` Promise get resolved.\n    () =\u003e `2-${ next() }`\n)\nconst function3: LastChainLink\u003cChainFunction\u003e = () =\u003e () =\u003e 'end'\n\nconst chain: ChainFactory\u003cChainFunction\u003e = [ function1, function2, function3 ]\nconst result = Chain.functions( chain )() // `result` is now a promise\n\nconsole.log( await result ) // Outputs: '1-2-end'\n```\n\n\u003c/details\u003e\n\n---\n\n\u003cdetails\u003e\n\n\u003csummary\u003eNext.js middleware chain\u003c/summary\u003e\n\n```ts\n// src/middleware.ts\n\nimport { NextMiddleware, NextResponse } from 'next/server'\nimport { Chain } from '@alessiofrittoli/chain-functions'\nimport type { ChainFactory, ChainLink, LastChainLink } from '@alessiofrittoli/chain-functions/types'\n\ntype Middleware\t\t\t= ChainLink\u003cNextMiddleware\u003e\ntype LastMiddleware\t\t= () =\u003e NextResponse\u003cunknown\u003e\ntype MiddlewareFactory\t= ChainFactory\u003cNextMiddleware, LastMiddleware\u003e\n\nconst middleware1: Middleware = next =\u003e (\n    async ( request, event ) =\u003e {\n        \n        const { nextUrl } = request\n\n        if ( nextUrl === '...' ) {\n            const rewriteUrl = '...'\n            return (\n                NextResponse\n                    .rewrite( rewriteUrl )\n            )\n        }\n\n        return next( request, event )\n\n    }\n)\n\n\nconst middleware2: Middleware = next =\u003e (\n    async ( request, event ) =\u003e {\n        \n        const response = await next( request, event )\n\n        // do something with `response` returned by the next middleware.\n        // ...\n\n        return response\n    }\n)\n\n// ensures `NextResponse.next()` is called if no one stops the chain.\nconst lastMiddleware: LastChainLink\u003cLastMiddleware\u003e = () =\u003e () =\u003e NextResponse.next()\n\nconst middlewares: MiddlewareFactory = [ middleware1, middleware2, lastMiddleware ]\n\nexport const config = {\n    matcher: [\n        /**\n         * Match all request paths except for the ones starting with:\n         * - api (API routes)\n         * - _next/static (static files)\n         * - _next/image (image optimization files)\n         * - favicon.ico (favicon file)\n         */\n        '/((?!api/|_next|.*\\\\..*).*)',\n    ]\n}\n\n// note that we do not execute the chain like in the previous examples since Next.js is responsible for the execution, providing `request` and `event` parameters to the `middleware` functions.\nexport default Chain.functions( middlewares )\n```\n\n\u003c/details\u003e\n\n---\n\n### Development\n\n#### Install depenendencies\n\n```bash\nnpm install\n```\n\nor using `pnpm`\n\n```bash\npnpm i\n```\n\n#### Build the source code\n\nRun the following command to test and build code for distribution.\n\n```bash\npnpm build\n```\n\n#### [ESLint](https://www.npmjs.com/package/eslint)\n\nwarnings / errors check.\n\n```bash\npnpm lint\n```\n\n#### [Jest](https://npmjs.com/package/jest)\n\nRun all the defined test suites by running the following:\n\n```bash\n# Run tests and watch file changes.\npnpm test:watch\n\n# Run tests in a CI environment.\npnpm test:ci\n```\n\n- See [`package.json`](./package.json) file scripts for more info.\n\nRun tests with coverage.\n\nAn HTTP server is then started to serve coverage files from `./coverage` folder.\n\n⚠️ You may see a blank page the first time you run this command. Simply refresh the browser to see the updates.\n\n```bash\ntest:coverage:serve\n```\n\n---\n\n### Contributing\n\nContributions are truly welcome!\n\nPlease refer to the [Contributing Doc](./CONTRIBUTING.md) for more information on how to start contributing to this project.\n\nHelp keep this project up to date with [GitHub Sponsor][sponsor-url].\n\n[![GitHub Sponsor][sponsor-badge]][sponsor-url]\n\n---\n\n### Security\n\nIf you believe you have found a security vulnerability, we encourage you to **_responsibly disclose this and NOT open a public issue_**. We will investigate all legitimate reports. Email `security@alessiofrittoli.it` to disclose any security vulnerabilities.\n\n### Made with ☕\n\n\u003ctable style='display:flex;gap:20px;'\u003e\n  \u003ctbody\u003e\n    \u003ctr\u003e\n      \u003ctd\u003e\n        \u003cimg alt=\"avatar\" src='https://avatars.githubusercontent.com/u/35973186' style='width:60px;border-radius:50%;object-fit:contain;'\u003e\n      \u003c/td\u003e\n      \u003ctd\u003e\n        \u003ctable style='display:flex;gap:2px;flex-direction:column;'\u003e\n          \u003ctbody\u003e\n              \u003ctr\u003e\n                \u003ctd\u003e\n                  \u003ca href='https://github.com/alessiofrittoli' target='_blank' rel='noopener'\u003eAlessio Frittoli\u003c/a\u003e\n                \u003c/td\u003e\n              \u003c/tr\u003e\n              \u003ctr\u003e\n                \u003ctd\u003e\n                  \u003csmall\u003e\n                    \u003ca href='https://alessiofrittoli.it' target='_blank' rel='noopener'\u003ehttps://alessiofrittoli.it\u003c/a\u003e |\n                    \u003ca href='mailto:info@alessiofrittoli.it' target='_blank' rel='noopener'\u003einfo@alessiofrittoli.it\u003c/a\u003e\n                  \u003c/small\u003e\n                \u003c/td\u003e\n              \u003c/tr\u003e\n          \u003c/tbody\u003e\n        \u003c/table\u003e\n      \u003c/td\u003e\n    \u003c/tr\u003e\n  \u003c/tbody\u003e\n\u003c/table\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falessiofrittoli%2Fchain-functions","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falessiofrittoli%2Fchain-functions","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falessiofrittoli%2Fchain-functions/lists"}