An open API service indexing awesome lists of open source software.

https://github.com/alessiofrittoli/chain-functions

Functions chaining made easy
https://github.com/alessiofrittoli/chain-functions

chaining

Last synced: about 2 months ago
JSON representation

Functions chaining made easy

Awesome Lists containing this project

README

        

# Chain Functions ⛓️

[![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]

[![GitHub Sponsor][sponsor-badge]][sponsor-url]

[version-badge]: https://img.shields.io/npm/v/%40alessiofrittoli%2Fchain-functions
[npm-url]: https://npmjs.org/package/%40alessiofrittoli%2Fchain-functions
[coverage-badge]: https://coveralls.io/repos/github/alessiofrittoli/chain-functions/badge.svg
[coverage-url]: https://coveralls.io/github/alessiofrittoli/chain-functions
[socket-badge]: https://socket.dev/api/badge/npm/package/@alessiofrittoli/chain-functions
[socket-url]: https://socket.dev/npm/package/@alessiofrittoli/chain-functions/overview
[downloads-badge]: https://img.shields.io/npm/dm/%40alessiofrittoli%2Fchain-functions.svg
[deps-badge]: https://img.shields.io/librariesio/release/npm/%40alessiofrittoli%2Fchain-functions
[deps-url]: https://libraries.io/npm/%40alessiofrittoli%2Fchain-functions

[sponsor-badge]: https://img.shields.io/static/v1?label=Fund%20this%20package&message=%E2%9D%A4&logo=GitHub&color=%23DB61A2
[sponsor-url]: https://github.com/sponsors/alessiofrittoli

## Functions chaining made easy

The `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.

### Table of Contents

- [Getting started](#getting-started)
- [API Reference](#api-reference)
- [`Chain` class](#chain-class)
- [Types](#types)
- [Key Features](#key-features)
- [Examples](#examples)
- [Development](#development)
- [ESLint](#eslint)
- [Jest](#jest)
- [Contributing](#contributing)
- [Security](#security)
- [Credits](#made-with-)

---

### Getting started

Run the following command to start using `chain-functions` in your projects:

```bash
npm i @alessiofrittoli/chain-functions
```

or using `pnpm`

```bash
pnpm i @alessiofrittoli/chain-functions
```

---

### API Reference

#### `Chain` class

A utility class for managing and executing chains of functions.

##### Static Methods

###### `Chain.functions()`

Recursively executes a chain of functions.

Parameters

| Parameter | Type | Default | Description |
|-----------|----------------------|---------|-------------|
| `chain` | `ChainFactory` | - | The chain of functions to execute. This must be an array of functions (`ChainLink`), where the last function is of type LastChainLink. See [Types](#types) section for further informations about. |
| `index` | `number` | `0` | (Optional) The starting index for execution. |

---

Returns

Type: `T | U`

The result of the chain execution, which matches the type of the chain's functions (`T` or `U`).

See [Types](#types) section for further informations about.

---

Throws

`Error` if no function is found at the specified index.

---

Example

```ts
import { Chain } from '@alessiofrittoli/chain-functions'
import type { ChainLink, LastChainLink, ChainFactory } from '@alessiofrittoli/chain-functions/types'

type ChainFunction = () => string

const function1: ChainLink = next => () => `1-${ next() }`
const function2: ChainLink = next => () => `2-${ next() }`
const function3: LastChainLink = () => () => 'end'

const chain: ChainFactory = [ function1, function2, function3 ]
const result = Chain.functions( chain )()

console.log( result ) // Output: '1-2-end'
```

---

###### `Chain.isLast()`

Determines if the given function is the last function in the chain needed to type cast the last function with `LastChainLink`.

This method is primarily used internally by the `Chain.functions()` method to determine when the chain execution should terminate.

Parameters

| Parameter | Type | Default | Description |
|-----------|----------------------|---------|-------------|
| `chain` | `ChainFactory` | - | The chain of functions. See [Types](#types) section for further informations about. |
| `fn` | `ChainLink \| LastChainLink` | - | 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. |
| `index` | `number` | `0` | (Optional) The current index of the function in the `Chain.functions()` recursion. |

---

Returns

Type: `boolean`

Returns `true` if the given function is the last function in the chain, `false` otherwise.

---

#### Types

##### `ChainFunction`

Represents any callable function that can be invoked as part of the chain.

This is used internally to type cast other types `template` parameters.

##### `ChainLink`

Represents a single link in a chain of functions.

Parameters

| Parameter | Type | Description |
|-----------|------|-------------------------------------------------------------------------|
| `next` | `T` | The next function in the chain. Its return type must be of type of `T`. |

---

Returns

Type: `T`

A function that can be invoked as part of the chain.

---

##### `LastChainLink`

Represents the last link in a chain of functions. Unlike `ChainLink`, it does not accept a `next` parameter.

Returns

Type: `T`

A function that can be invoked as the final step in the chain.

---

##### `ChainFactory`

Represents the complete chain of functions as an array.

###### Structure

- Can contain any number of `ChainLink` functions.
- The last element in the array must be a `LastChainLink`.

---

### Key Features

- Chain link functions are highly customizeable.
- Chain link functions can be `async` functions.
- The last chain link could return a different type (`U`) other than `T` from a standard `ChainLink`.

---

### Examples

#### Importing the library

```ts
// importing the main `Chain` class
import { Chain } from '@alessiofrittoli/chain-functions'
// importing types
import type { ChainLink, LastChainLink, ChainFactory } from '@alessiofrittoli/chain-functions/types'
```

Basic usage

```ts
// define the chain link function type
type ChainFunction = () => string

// declare chain link functions
const function1: ChainLink = next => () => `1-${ next() }`
const function2: ChainLink = next => () => `2-${ next() }`
// declare the last chain function
const function3: LastChainLink = () => () => 'end'

// declare the chain array
const chain: ChainFactory = [ function1, function2, function3 ]
// execute the chain array
const result = Chain.functions( chain )()

console.log( result ) // Output: '1-2-end'
```

---

Advance usage

```ts
type ChainFunctionProps = {
someProperty : string
firstFunction? : boolean
secondFunction? : boolean
thirdFunction? : boolean
}
// define the chain link function type
type ChainFunction = ( props: ChainFunctionProps ) => ChainFunctionProps

// declare chain link functions
const function1: ChainLink = next => props => {
// edit properties
props.someProperty = 'Edited by 1st function'
props.firstFunction = true
// call the next function in the chain
return next( props )
}

const function2: ChainLink = next => props => {
props.secondFunction = true

if ( props.someProperty === 'Edited by 1st function' ) {
// stop chain execution if some condition is met.
return props
}

// call the next function in the chain
return next( props )
}

// declare the last chain function
const function3: LastChainLink = () => props => {
props.thirdFunction = true
return props
}

// declare the chain array
const chain: ChainFactory = [ function1, function2, function3 ]
// declare the initial state
const initialState: ChainFunctionProps = {
someProperty : 'Initial value',
firstFunction : false,
secondFunction : false,
thirdFunction : false,
}
// execute the chain array with initial state
const result = Chain.functions( chain )( initialState )

console.log( result )
// Output: {
// someProperty : 'Edited by 1st function',
// firstFunction : true,
// secondFunction : true,
// thirdFunction : false,
// }
```

---

`LastChainLink` with custom return type

```ts
type ChainFunction = () => string
type LastChainFunction = () => boolean

const function1: ChainLink = next => () => `1-${ next() }`
const function2: ChainLink = next => () => `2-${ next() }`
const function3: LastChainLink = () => () => true

const chain: ChainFactory = [ function1, function2, function3 ]
const result = Chain.functions( chain )()

console.log( result ) // Outputs: '1-2-true'
```

---

`ChainLink` functions with promises

```ts
type ChainFunction = () => string | Promise

const function1: ChainLink = next => async () => {
// simulate a long task running
await new Promise( resolve => setTimeout( resolve, 5000 ) )
return `1-${ next() }`
}
const function2: ChainLink = next => (
// this function is executed once `function1` Promise get resolved.
() => `2-${ next() }`
)
const function3: LastChainLink = () => () => 'end'

const chain: ChainFactory = [ function1, function2, function3 ]
const result = Chain.functions( chain )() // `result` is now a promise

console.log( await result ) // Outputs: '1-2-end'
```

---

Next.js middleware chain

```ts
// src/middleware.ts

import { NextMiddleware, NextResponse } from 'next/server'
import { Chain } from '@alessiofrittoli/chain-functions'
import type { ChainFactory, ChainLink, LastChainLink } from '@alessiofrittoli/chain-functions/types'

type Middleware = ChainLink
type LastMiddleware = () => NextResponse
type MiddlewareFactory = ChainFactory

const middleware1: Middleware = next => (
async ( request, event ) => {

const { nextUrl } = request

if ( nextUrl === '...' ) {
const rewriteUrl = '...'
return (
NextResponse
.rewrite( rewriteUrl )
)
}

return next( request, event )

}
)

const middleware2: Middleware = next => (
async ( request, event ) => {

const response = await next( request, event )

// do something with `response` returned by the next middleware.
// ...

return response
}
)

// ensures `NextResponse.next()` is called if no one stops the chain.
const lastMiddleware: LastChainLink = () => () => NextResponse.next()

const middlewares: MiddlewareFactory = [ middleware1, middleware2, lastMiddleware ]

export const config = {
matcher: [
/**
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
'/((?!api/|_next|.*\\..*).*)',
]
}

// 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.
export default Chain.functions( middlewares )
```

---

### Development

#### Install depenendencies

```bash
npm install
```

or using `pnpm`

```bash
pnpm i
```

#### Build the source code

Run the following command to test and build code for distribution.

```bash
pnpm build
```

#### [ESLint](https://www.npmjs.com/package/eslint)

warnings / errors check.

```bash
pnpm lint
```

#### [Jest](https://npmjs.com/package/jest)

Run all the defined test suites by running the following:

```bash
# Run tests and watch file changes.
pnpm test:watch

# Run tests in a CI environment.
pnpm test:ci
```

- See [`package.json`](./package.json) file scripts for more info.

Run tests with coverage.

An HTTP server is then started to serve coverage files from `./coverage` folder.

⚠️ You may see a blank page the first time you run this command. Simply refresh the browser to see the updates.

```bash
test:coverage:serve
```

---

### Contributing

Contributions are truly welcome!

Please refer to the [Contributing Doc](./CONTRIBUTING.md) for more information on how to start contributing to this project.

Help keep this project up to date with [GitHub Sponsor][sponsor-url].

[![GitHub Sponsor][sponsor-badge]][sponsor-url]

---

### Security

If 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 `[email protected]` to disclose any security vulnerabilities.

### Made with ☕




avatar






Alessio Frittoli





https://alessiofrittoli.it |
[email protected]