This commit is contained in:
Tutur33
2023-11-24 22:35:41 +01:00
parent 3c0b507a93
commit 7644b2a0f7
45165 changed files with 4803356 additions and 3 deletions
+9
View File
@@ -0,0 +1,9 @@
# The MIT License
Copyright 2021 Harminder Virk, contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+164
View File
@@ -0,0 +1,164 @@
<div align="center">
<img src="https://res.cloudinary.com/adonisjs/image/upload/q_100/v1557762307/poppinss_iftxlt.jpg" width="600px">
</div>
# Co Compose
> Compose an array of functions to be executed one after the other. Similar to Koa and AdonisJS middlewares.
[![gh-workflow-image]][gh-workflow-url] [![typescript-image]][typescript-url] [![npm-image]][npm-url] [![license-image]][license-url] [![synk-image]][synk-url]
Co compose composes an array of middleware to be executed in sequence. The library is framework independent and can be used in any Javascript/Typescript project.
<details>
<summary> <strong>Benchmarks (v16.5.0)</strong> </summary>
Co Compose x 2,145,829 ops/sec ±0.16% (90 runs sampled)
fastseries x 166,990 ops/sec ±2.04% (64 runs sampled)
middie x 122,162 ops/sec ±7.84% (28 runs sampled)
<p> <strong> Fastest is Co Compose </strong> </p>
</details>
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
## Table of contents
- [Installation](#installation)
- [Usage](#usage)
- [Passing values](#passing-values)
- [Custom executors](#custom-executors)
- [Final Handler](#final-handler)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Installation
```sh
npm i co-compose
# yarn
yarn add co-compose
```
## Usage
Checkout the following example to run an array of middleware functions.
```ts
import { Middleware } from 'co-compose'
async function fn1(next) {
console.log('executing fn1')
await next()
}
async function fn2(next) {
console.log('executing fn2')
await next()
}
const middleware = new Middleware()
middleware.register([fn1, fn2])
await middleware.runner().run([])
```
### Passing values
You can also pass values to all middleware functions. An `array` of values passed to `runner.run()` will be passed to middleware functions as multiple arguments.
```js
async function fn1(ctx, next) {
ctx.stack.push('fn1')
await next()
}
async function fn2(ctx, next) {
ctx.stack.push('fn2')
await next()
}
const ctx = {
stack: [],
}
await middleware.runner().run([ctx])
assert.deepEqual(ctx.stack, ['fn1', 'fn2'])
```
### Custom executors
The default behavior is to define middleware as functions. However, you can define them in any shape and then stick a custom executor to execute them.
Check the following example where `ES6 classes` are used.
```js
class Middleware1 {
async handle(ctx, next) {
ctx.stack.push('fn1')
await next()
}
}
class Middleware2 {
async handle(ctx, next) {
ctx.stack.push('fn2')
await next()
}
}
const middleware = new Middleware()
const ctx = {
stack: [],
}
middleware.register([Middleware1, Middleware2])
await middleware
.runner()
.executor(async function (MiddlewareClass, params) {
const instance = new MiddlewareClass() // 👈
await instance.handle(...params) // 👈
})
.run([ctx])
```
### Final Handler
The final handler is a executed when the entire middleware chain ends by calling `next`. This makes it easier to execute custom functions, which are not part of the chain, however must be executed when chain ends.
> Also, the arguments for the final handler can be different from the middleware arguments
```js
async function fn1(ctx, next) {
ctx.stack.push('fn1')
await next()
}
async function finalHandler() {
ctx.stack.push('final handler')
}
const ctx = {
stack: [],
}
await middleware.runner().finalHandler(finalHandler, [ctx]).run([ctx])
assert.deepEqual(ctx.stack, ['fn1', 'final handler'])
```
[gh-workflow-image]: https://img.shields.io/github/workflow/status/poppinss/co-compose/test?style=for-the-badge
[gh-workflow-url]: https://github.com/poppinss/co-compose/actions/workflows/test.yml "Github action"
[typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript
[typescript-url]: "typescript"
[npm-image]: https://img.shields.io/npm/v/co-compose.svg?style=for-the-badge&logo=npm
[npm-url]: https://npmjs.org/package/co-compose 'npm'
[license-image]: https://img.shields.io/npm/l/co-compose?color=blueviolet&style=for-the-badge
[license-url]: LICENSE.md 'license'
[synk-image]: https://img.shields.io/snyk/vulnerabilities/github/poppinss/co-compose?label=Synk%20Vulnerabilities&style=for-the-badge
[synk-url]: https://snyk.io/test/github/poppinss/co-compose?targetFile=package.json 'synk'
+1
View File
@@ -0,0 +1 @@
export { Middleware } from './src/Middleware';
+13
View File
@@ -0,0 +1,13 @@
"use strict";
/*
* co-compose
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Middleware = void 0;
var Middleware_1 = require("./src/Middleware");
Object.defineProperty(exports, "Middleware", { enumerable: true, get: function () { return Middleware_1.Middleware; } });
+22
View File
@@ -0,0 +1,22 @@
/**
* An array of arguments + the next function
*/
export declare type MiddlewareArgs = any[];
/**
* The middleware actual function
*/
export declare type MiddlewareFn = (...params: MiddlewareArgs) => Promise<void>;
/**
* Executor job is to execute one middleware function at a
* time and pass arguments to it
*/
export declare type Executor = (fn: any, params: MiddlewareArgs) => Promise<void>;
/**
* Args received by the final handler
*/
export declare type FinalHandlerArgs = any[];
/**
* Final handler is called when the entire chain executes
* completely
*/
export declare type FinalHandler = (...params: FinalHandlerArgs) => Promise<void>;
+10
View File
@@ -0,0 +1,10 @@
"use strict";
/*
* co-compose
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
+18
View File
@@ -0,0 +1,18 @@
import { Runnable } from './Runnable';
/**
* Exposes the API to register middleware and later execute
* them using the runner.
*/
export declare class Middleware {
private list;
/**
* Register an array of middleware to executed
* later.
*/
register(list: any[]): this;
/**
* Returns an instance of runner to execute
* the middleware
*/
runner(): Runnable;
}
+40
View File
@@ -0,0 +1,40 @@
"use strict";
/*
* co-componse
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Middleware = void 0;
const Runnable_1 = require("./Runnable");
/**
* Exposes the API to register middleware and later execute
* them using the runner.
*/
class Middleware {
constructor() {
this.list = [];
}
/**
* Register an array of middleware to executed
* later.
*/
register(list) {
if (!Array.isArray(list)) {
throw new Error('middleware.register expects an array of middleware');
}
this.list = this.list.concat(list);
return this;
}
/**
* Returns an instance of runner to execute
* the middleware
*/
runner() {
return new Runnable_1.Runnable(this.list);
}
}
exports.Middleware = Middleware;
+43
View File
@@ -0,0 +1,43 @@
import { Executor, FinalHandler, FinalHandlerArgs } from './Contracts';
/**
* Runnable to execute an array of functions in sequence. The queue is
* advanced only when the current function calls `next`.
*
* @example
* ```js
* const runner = new Runnable([async function fn1 (params, next) {
* }])
* ```
*/
export declare class Runnable {
private list;
private index;
private params;
private executorFn;
private registeredFinalHandler;
constructor(list: any[]);
/**
* Invoke one middleware at a time. Middleware fns will be executed
* recursively until `next` is invoked.
*
* If one method doesn't call `next`, then the chain will be finished
* automatically.
*/
private invoke;
/**
* Final handler to be executed, when chain ends successfully
*/
finalHandler(fn: FinalHandler, args: FinalHandlerArgs): this;
/**
* Define custom resolver, which is invoked for all the middleware.
* If this method is defined, then default executor is not called
* and it's the responsibility of this method to call the
* middleware and pass params to it
*/
executor(fn: Executor): this;
/**
* Start the middleware queue and pass params to it. The `params`
* array will be passed as spread arguments.
*/
run(params: any[]): Promise<void>;
}
+85
View File
@@ -0,0 +1,85 @@
"use strict";
/*
* co-compose
*
* (c) Harminder Virk <virk@adonisjs.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Runnable = void 0;
/**
* Default final handler resolves the middleware chain right away
*/
const DEFAULT_FINAL_HANDLER = {
fn: () => Promise.resolve(),
args: [],
};
/**
* The default executor to execute middlewares. This method assumes middleware
* as functions and calls them right away
*/
const DEFAULT_EXECUTOR = (fn, params) => fn(...params);
/**
* Runnable to execute an array of functions in sequence. The queue is
* advanced only when the current function calls `next`.
*
* @example
* ```js
* const runner = new Runnable([async function fn1 (params, next) {
* }])
* ```
*/
class Runnable {
constructor(list) {
this.list = list;
this.index = 0;
this.params = [];
this.executorFn = DEFAULT_EXECUTOR;
this.registeredFinalHandler = DEFAULT_FINAL_HANDLER;
}
/**
* Invoke one middleware at a time. Middleware fns will be executed
* recursively until `next` is invoked.
*
* If one method doesn't call `next`, then the chain will be finished
* automatically.
*/
invoke() {
const fn = this.list[this.index++];
/**
* Empty stack
*/
if (!fn) {
return this.registeredFinalHandler.fn(...this.registeredFinalHandler.args);
}
return this.executorFn(fn, this.params);
}
/**
* Final handler to be executed, when chain ends successfully
*/
finalHandler(fn, args) {
this.registeredFinalHandler = { fn, args };
return this;
}
/**
* Define custom resolver, which is invoked for all the middleware.
* If this method is defined, then default executor is not called
* and it's the responsibility of this method to call the
* middleware and pass params to it
*/
executor(fn) {
this.executorFn = fn;
return this;
}
/**
* Start the middleware queue and pass params to it. The `params`
* array will be passed as spread arguments.
*/
async run(params) {
this.params = params.concat(this.invoke.bind(this));
return this.invoke();
}
}
exports.Runnable = Runnable;
+123
View File
@@ -0,0 +1,123 @@
{
"name": "co-compose",
"version": "7.0.3",
"description": "AdonisJS and Koa style middleware layer with ability to run parallel middleware",
"main": "build/index.js",
"files": [
"build/src",
"build/index.d.ts",
"build/index.js"
],
"scripts": {
"mrm": "mrm --preset=@adonisjs/mrm-preset",
"pretest": "npm run lint",
"test": "node .bin/test.js",
"prepublishOnly": "npm run build",
"clean": "del build",
"compile": "npm run lint && npm run clean && tsc",
"build": "npm run compile",
"commit": "git-cz",
"release": "np --message=\"chore(release): %s\"",
"version": "npm run build",
"benchmark": "node -r @adonisjs/require-ts/build/register benchmarks/index.ts",
"format": "prettier --write .",
"lint": "eslint . --ext=.ts",
"sync-labels": "github-label-sync --labels ./node_modules/@adonisjs/mrm-preset/gh-labels.json poppinss/co-compose"
},
"keywords": [
"adonisjs",
"koa",
"middleware",
"co",
"co-middleware"
],
"author": "virk,adonisjs",
"license": "MIT",
"devDependencies": {
"@adonisjs/mrm-preset": "^5.0.3",
"@adonisjs/require-ts": "^2.0.13",
"@types/node": "^18.11.9",
"benchmark": "^2.1.4",
"commitizen": "^4.2.5",
"cz-conventional-changelog": "^3.3.0",
"del-cli": "^5.0.0",
"doctoc": "^2.2.1",
"eslint": "^8.26.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-adonis": "^2.1.1",
"eslint-plugin-prettier": "^4.2.1",
"fastseries": "^2.0.0",
"github-label-sync": "^2.2.0",
"husky": "^8.0.1",
"japa": "^4.0.0",
"middie": "^7.1.0",
"mrm": "^4.1.13",
"np": "^7.6.2",
"prettier": "^2.7.1",
"typescript": "^4.8.4"
},
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
},
"directories": {
"example": "examples",
"test": "test"
},
"repository": {
"type": "git",
"url": "git+https://github.com/poppinss/co-compose.git"
},
"bugs": {
"url": "https://github.com/poppinss/co-compose/issues"
},
"homepage": "https://github.com/poppinss/co-compose#readme",
"np": {
"contents": ".",
"anyBranch": false
},
"mrmConfig": {
"core": false,
"license": "MIT",
"services": [
"github-actions"
],
"minNodeVersion": "16.13.1",
"probotApps": [
"stale",
"lock"
],
"runGhActionsOnWindows": true
},
"eslintConfig": {
"extends": [
"plugin:adonis/typescriptPackage",
"prettier"
],
"plugins": [
"prettier"
],
"rules": {
"prettier/prettier": [
"error",
{
"endOfLine": "auto"
}
]
}
},
"eslintIgnore": [
"build"
],
"prettier": {
"trailingComma": "es5",
"semi": false,
"singleQuote": true,
"useTabs": false,
"quoteProps": "consistent",
"bracketSpacing": true,
"arrowParens": "always",
"printWidth": 100
}
}