Comparing version 2.0.1 to 3.0.0
@@ -1,24 +0,20 @@ | ||
export declare type NextFunction<R> = () => Promise<R>; | ||
export declare type Middleware5<T1, T2, T3, T4, T5, R> = (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, next: NextFunction<R>) => R | Promise<R>; | ||
export declare type Middleware4<T1, T2, T3, T4, R> = (arg1: T1, arg2: T2, arg3: T3, arg4: T4, next: NextFunction<R>) => R | Promise<R>; | ||
export declare type Middleware3<T1, T2, T3, R> = (arg1: T1, arg2: T2, arg3: T3, next: NextFunction<R>) => R | Promise<R>; | ||
export declare type Middleware2<T1, T2, R> = (arg1: T1, arg2: T2, next: NextFunction<R>) => R | Promise<R>; | ||
export declare type Middleware1<T1, R> = (arg1: T1, next: NextFunction<R>) => R | Promise<R>; | ||
export declare type Middleware0<R> = (next: NextFunction<R>) => R | Promise<R>; | ||
export declare type Middleware<R> = (...args: any[]) => R | Promise<R>; | ||
export declare type OutFunction5<T1, T2, T3, T4, T5, R> = (arg1: T1, arg2: T2, arg3: T3, arg4: T4, arg5: T5, next: Middleware5<T1, T2, T3, T4, T5, R>) => Promise<R>; | ||
export declare type OutFunction4<T1, T2, T3, T4, R> = (arg1: T1, arg2: T2, arg3: T3, arg4: T4, next: Middleware4<T1, T2, T3, T4, R>) => Promise<R>; | ||
export declare type OutFunction3<T1, T2, T3, R> = (arg1: T1, arg2: T2, arg3: T3, next: Middleware3<T1, T2, T3, R>) => Promise<R>; | ||
export declare type OutFunction2<T1, T2, R> = (arg1: T1, arg2: T2, next: Middleware2<T1, T2, R>) => Promise<R>; | ||
export declare type OutFunction1<T1, R> = (arg1: T1, next: Middleware1<T1, R>) => Promise<R>; | ||
export declare type OutFunction0<R> = (next: Middleware0<R>) => Promise<R>; | ||
export declare type OutFunction<R> = (...args: any[]) => Promise<R>; | ||
/** | ||
* Compose an array of middleware functions into a chain. | ||
* Next function supports optional `ctx` replacement for following middleware. | ||
*/ | ||
export declare function compose<R>(middleware: Array<Middleware0<R>>): OutFunction0<R>; | ||
export declare function compose<T1, R>(middleware: Array<Middleware1<T1, R>>): OutFunction1<T1, R>; | ||
export declare function compose<T1, T2, R>(middleware: Array<Middleware2<T1, T2, R>>): OutFunction2<T1, T2, R>; | ||
export declare function compose<T1, T2, T3, R>(middleware: Array<Middleware3<T1, T2, T3, R>>): OutFunction3<T1, T2, T3, R>; | ||
export declare function compose<T1, T2, T3, T4, R>(middleware: Array<Middleware4<T1, T2, T3, T4, R>>): OutFunction4<T1, T2, T3, T4, R>; | ||
export declare function compose<T1, T2, T3, T4, T5, R>(middleware: Array<Middleware5<T1, T2, T3, T4, T5, R>>): OutFunction5<T1, T2, T3, T4, T5, R>; | ||
export declare type Next<T, U> = (ctx?: T) => Promise<U>; | ||
/** | ||
* Middleware function pattern. | ||
*/ | ||
export declare type Middleware<T, U> = (ctx: T, next: Next<T, U>) => U | Promise<U>; | ||
/** | ||
* Final function has no `next()`. | ||
*/ | ||
export declare type Done<T, U> = (ctx: T) => U | Promise<U>; | ||
/** | ||
* Composed function signature. | ||
*/ | ||
export declare type Composed<T, U> = (ctx: T, done: Done<T, U>) => Promise<U>; | ||
/** | ||
* Compose an array of middleware functions into a single function. | ||
*/ | ||
export declare function compose<T, U>(middleware: Array<Middleware<T, U>>, debug?: boolean): Composed<T, U>; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
function compose(middleware) { | ||
/** | ||
* Default to "debug" mode when in development. | ||
*/ | ||
const debugMode = process.env.NODE_ENV !== 'production'; | ||
/** | ||
* Debug mode wrapper for middleware functions. | ||
*/ | ||
function debugMiddleware(middleware) { | ||
if (!Array.isArray(middleware)) { | ||
throw new TypeError("Expected middleware to be an array, got " + typeof middleware); | ||
throw new TypeError(`Expected middleware to be an array, got ${typeof middleware}`); | ||
} | ||
for (var _i = 0, middleware_1 = middleware; _i < middleware_1.length; _i++) { | ||
var fn = middleware_1[_i]; | ||
if (typeof fn !== 'function') { | ||
throw new TypeError("Expected middleware to contain functions, got " + typeof fn); | ||
for (const fn of middleware) { | ||
if (typeof fn !== 'function') { // tslint:disable-line | ||
throw new TypeError(`Expected middleware to contain functions, but got ${typeof fn}`); | ||
} | ||
} | ||
return function () { | ||
var args = []; | ||
for (var _i = 0; _i < arguments.length; _i++) { | ||
args[_i] = arguments[_i]; | ||
return function debugComposed(ctx, done) { | ||
if (typeof done !== 'function') { // tslint:disable-line | ||
throw new TypeError(`Expected the last argument to be \`done(ctx)\`, but got ${typeof done}`); | ||
} | ||
var index = -1; | ||
var done = args.pop(); | ||
if (typeof done !== 'function') { | ||
throw new TypeError("Expected the last argument to be `next()`, got " + typeof done); | ||
} | ||
function dispatch(pos) { | ||
if (pos <= index) { | ||
throw new TypeError('`next()` called multiple times'); | ||
function dispatcher(startIndex, ctx) { | ||
let index = startIndex; | ||
function dispatch(pos) { | ||
const fn = middleware[pos] || done; | ||
if (pos > middleware.length) { | ||
throw new TypeError('Composed `done(ctx)` function should not call `next()`'); | ||
} | ||
index = pos; | ||
return new Promise(resolve => { | ||
const result = fn(ctx, function next(newCtx) { | ||
if (index > pos) | ||
throw new TypeError('`next()` called multiple times'); | ||
if (newCtx === undefined) | ||
return dispatch(pos + 1); | ||
return dispatcher(pos + 1, newCtx); | ||
}); | ||
if (result === undefined) { // tslint:disable-line | ||
throw new TypeError('Expected middleware to return `next()` or a value'); | ||
} | ||
return resolve(result); | ||
}); | ||
} | ||
index = pos; | ||
var fn = middleware[pos] || done; | ||
return new Promise(function (resolve) { | ||
return resolve(fn.apply(void 0, args.concat([function next() { | ||
return dispatch(pos + 1); | ||
}]))); | ||
}); | ||
return dispatch(startIndex); | ||
} | ||
return dispatch(0); | ||
return dispatcher(0, ctx); | ||
}; | ||
} | ||
/** | ||
* Production-mode middleware composition (no errors thrown). | ||
*/ | ||
function composeMiddleware(middleware) { | ||
function dispatch(pos, ctx, done) { | ||
const fn = middleware[pos]; | ||
if (!fn) | ||
return new Promise(resolve => resolve(done(ctx))); | ||
return new Promise(resolve => { | ||
return resolve(fn(ctx, function next(newCtx) { | ||
return dispatch(pos + 1, newCtx === undefined ? ctx : newCtx, done); | ||
})); | ||
}); | ||
} | ||
return function composed(ctx, done) { | ||
return dispatch(0, ctx, done); | ||
}; | ||
} | ||
/** | ||
* Compose an array of middleware functions into a single function. | ||
*/ | ||
function compose(middleware, debug = debugMode) { | ||
return debug ? debugMiddleware(middleware) : composeMiddleware(middleware); | ||
} | ||
exports.compose = compose; | ||
//# sourceMappingURL=index.js.map |
"use strict"; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var test = require("blue-tape"); | ||
var index_1 = require("./index"); | ||
test('async middleware', function (t) { | ||
t.test('middleware', function (t) { | ||
var arr = []; | ||
var fn = index_1.compose([ | ||
function (req, res, next) { | ||
arr.push(1); | ||
return next().then(function (value) { | ||
arr.push(5); | ||
t.equal(value, 'propagate'); | ||
return 'done'; | ||
}); | ||
}, | ||
function (req, res, next) { | ||
arr.push(2); | ||
return next().then(function (value) { | ||
arr.push(4); | ||
t.equal(value, 'hello'); | ||
return 'propagate'; | ||
}); | ||
} | ||
]); | ||
return fn({}, {}, function () { | ||
arr.push(3); | ||
return 'hello'; | ||
}) | ||
.then(function () { | ||
t.deepEqual(arr, [1, 2, 3, 4, 5]); | ||
const index_1 = require("./index"); | ||
describe('throwback', () => { | ||
// Run tests on each code path. | ||
runTests(false); | ||
runTests(true); | ||
describe('debug mode', () => { | ||
it('should select debug mode based on node env by default', () => { | ||
const fn = index_1.compose([]); | ||
const expectedName = process.env.NODE_ENV !== 'production' ? 'debugComposed' : 'composed'; | ||
expect(fn.name).toEqual(expectedName); | ||
}); | ||
}); | ||
t.test('branch middleware by composing', function (t) { | ||
var arr = []; | ||
var fn = index_1.compose([ | ||
index_1.compose([ | ||
describe('debug errors', () => { | ||
it('throw when input is not an array', () => { | ||
expect(() => index_1.compose('test', true)).toThrow('Expected middleware to be an array, got string'); | ||
}); | ||
it('throw when values are not functions', () => { | ||
expect(() => index_1.compose([1, 2, 3], true)).toThrow('Expected middleware to contain functions, but got number'); | ||
}); | ||
it('throw when next is not a function', () => { | ||
const fn = index_1.compose([], true); | ||
expect(() => fn(true)).toThrow('Expected the last argument to be `done(ctx)`, but got undefined'); | ||
}); | ||
it('throw when calling next() multiple times', () => __awaiter(this, void 0, void 0, function* () { | ||
const fn = index_1.compose([ | ||
function (value, next) { | ||
return next().then(() => next()); | ||
} | ||
], true); | ||
yield expect(fn({}, () => Promise.resolve())).rejects.toEqual(new Error('`next()` called multiple times')); | ||
})); | ||
it('should throw if final function attempts to call `next()`', () => __awaiter(this, void 0, void 0, function* () { | ||
const fn = index_1.compose([], true); | ||
yield expect(fn({}, ((ctx, next) => next()))).rejects.toEqual(new TypeError('Composed `done(ctx)` function should not call `next()`')); | ||
})); | ||
it('should throw if function returns `undefined`', () => __awaiter(this, void 0, void 0, function* () { | ||
const fn = index_1.compose([ | ||
function (ctx) { } | ||
], true); | ||
yield expect(fn(true, () => Promise.resolve())).rejects.toEqual(new TypeError('Expected middleware to return `next()` or a value')); | ||
})); | ||
}); | ||
}); | ||
/** | ||
* Execute tests in each "mode". | ||
*/ | ||
function runTests(debugMode) { | ||
describe(`compose middleware with debug mode: ${debugMode}`, () => { | ||
it('should compose middleware functions', () => __awaiter(this, void 0, void 0, function* () { | ||
const arr = []; | ||
const fn = index_1.compose([ | ||
function (ctx, next) { | ||
arr.push(1); | ||
return next().catch(function () { | ||
arr.push(3); | ||
return next().then(value => { | ||
arr.push(5); | ||
expect(value).toEqual('propagate'); | ||
return 'done'; | ||
}); | ||
@@ -46,41 +72,79 @@ }, | ||
arr.push(2); | ||
return Promise.reject(new Error('Boom!')); | ||
return next().then(value => { | ||
arr.push(4); | ||
expect(value).toEqual('hello'); | ||
return 'propagate'; | ||
}); | ||
} | ||
]), | ||
function (ctx, next) { | ||
arr.push(4); | ||
return next(); | ||
], debugMode); | ||
yield fn({}, () => { | ||
arr.push(3); | ||
return 'hello'; | ||
}); | ||
expect(arr).toEqual([1, 2, 3, 4, 5]); | ||
})); | ||
it('branch middleware by composing', () => __awaiter(this, void 0, void 0, function* () { | ||
const arr = []; | ||
const fn = index_1.compose([ | ||
index_1.compose([ | ||
function (ctx, next) { | ||
arr.push(1); | ||
return next().catch(() => { | ||
arr.push(3); | ||
}); | ||
}, | ||
function (ctx, next) { | ||
arr.push(2); | ||
return Promise.reject(new Error('Boom!')); | ||
} | ||
], debugMode), | ||
function (ctx, next) { | ||
arr.push(4); | ||
return next(); | ||
} | ||
], debugMode); | ||
yield fn({}, () => undefined); | ||
expect(arr).toEqual([1, 2, 3]); | ||
})); | ||
it('should compose multiple layers', () => __awaiter(this, void 0, void 0, function* () { | ||
const arr = []; | ||
function middleware(n, next) { | ||
arr.push(n); | ||
return next(n + 1); | ||
} | ||
]); | ||
return fn({}, function () { return undefined; }) | ||
.then(function () { | ||
t.deepEqual(arr, [1, 2, 3]); | ||
}); | ||
const fn = index_1.compose([ | ||
middleware, | ||
index_1.compose([ | ||
index_1.compose([ | ||
middleware, | ||
middleware | ||
], debugMode), | ||
middleware | ||
], debugMode), | ||
index_1.compose([ | ||
middleware | ||
], debugMode) | ||
], debugMode); | ||
const res = yield fn(0, ctx => ctx); | ||
expect(res).toEqual(5); | ||
expect(arr).toEqual([0, 1, 2, 3, 4]); | ||
})); | ||
it('should replace context object', () => __awaiter(this, void 0, void 0, function* () { | ||
const fn = index_1.compose([ | ||
function (ctx, next) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
expect(ctx.original).toBe(true); | ||
const res = yield next(); | ||
expect(ctx.original).toBe(true); | ||
return res; | ||
}); | ||
}, | ||
function (ctx, next) { | ||
return next({ original: false }); | ||
} | ||
], debugMode); | ||
expect(yield fn({ original: true }, ctx => ctx.original)).toEqual(false); | ||
})); | ||
}); | ||
t.test('throw when input is not an array', function (t) { | ||
t.throws(function () { return index_1.compose('test'); }, 'Expected middleware to be an array, got string'); | ||
t.end(); | ||
}); | ||
t.test('throw when values are not functions', function (t) { | ||
t.throws(function () { return index_1.compose([1, 2, 3]); }, 'Expected middleware to contain functions, got number'); | ||
t.end(); | ||
}); | ||
t.test('throw when next is not a function', function (t) { | ||
var fn = index_1.compose([]); | ||
t.throws(function () { return fn(true); }, 'Expected the last argument to be `next()`, got boolean'); | ||
t.end(); | ||
}); | ||
t.test('throw when calling next() multiple times', function (t) { | ||
var fn = index_1.compose([ | ||
function (value, next) { | ||
return next().then(next); | ||
} | ||
]); | ||
t.plan(1); | ||
return fn({}, function () { return undefined; }) | ||
.catch(function (err) { | ||
t.equal(err.message, '`next()` called multiple times'); | ||
}); | ||
}); | ||
}); | ||
} | ||
//# sourceMappingURL=index.spec.js.map |
{ | ||
"name": "throwback", | ||
"version": "2.0.1", | ||
"description": "An asynchronous middleware pattern", | ||
"version": "3.0.0", | ||
"description": "Simple asynchronous middleware pattern", | ||
"main": "dist/index.js", | ||
"typings": "dist/index.d.ts", | ||
"files": [ | ||
"dist/", | ||
"README.md", | ||
"LICENSE" | ||
"dist/" | ||
], | ||
"scripts": { | ||
"lint": "tslint \"src/**/*.ts\"", | ||
"build": "rm -rf dist && tsc", | ||
"test-spec": "blue-tape 'dist/**/*.spec.js' | tap-spec", | ||
"test-cov": "istanbul cover --print none -x '*.spec.js' node_modules/blue-tape/bin/blue-tape.js -- 'dist/**/*.spec.js' | tap-spec", | ||
"test": "npm run build && npm run lint && npm run test-cov", | ||
"prepublish": "typings install && npm run build" | ||
"lint": "tslint \"src/**/*.ts\" --project tsconfig.json", | ||
"build": "rimraf dist && tsc", | ||
"specs": "jest --coverage", | ||
"test": "npm run build && npm run lint && npm run specs", | ||
"prepublish": "npm run build" | ||
}, | ||
@@ -42,13 +39,29 @@ "repository": { | ||
"homepage": "https://github.com/serviejs/throwback", | ||
"jest": { | ||
"roots": [ | ||
"<rootDir>/src/" | ||
], | ||
"transform": { | ||
"\\.tsx?$": "ts-jest" | ||
}, | ||
"testRegex": "(/__tests__/.*|\\.(test|spec))\\.(tsx?|jsx?)$", | ||
"moduleFileExtensions": [ | ||
"ts", | ||
"tsx", | ||
"js", | ||
"jsx", | ||
"json", | ||
"node" | ||
] | ||
}, | ||
"devDependencies": { | ||
"blue-tape": "^1.0.0", | ||
"bluebird": "^3.3.5", | ||
"chai": "^3.2.0", | ||
"istanbul": "^0.4.4", | ||
"tap-spec": "^4.1.1", | ||
"tslint": "^4.3.1", | ||
"tslint-config-standard": "^2.0.0", | ||
"typescript": "^2.0.3", | ||
"typings": "^2.1.0" | ||
"@types/jest": "^22.2.3", | ||
"@types/node": "^10.1.2", | ||
"jest": "^22.4.4", | ||
"rimraf": "^2.6.2", | ||
"ts-jest": "^22.4.6", | ||
"tslint": "^5.10.0", | ||
"tslint-config-standard": "^7.0.0", | ||
"typescript": "^2.8.3" | ||
} | ||
} |
@@ -8,7 +8,7 @@ # Throwback | ||
> An asynchronous middleware pattern. | ||
> Simple asynchronous middleware pattern. | ||
## Installation | ||
```sh | ||
``` | ||
npm install throwback --save | ||
@@ -19,9 +19,9 @@ ``` | ||
Compose asynchronous functions (promise-based) | ||
Compose asynchronous (promise-returning) functions. | ||
```js | ||
import { compose } from 'throwback' | ||
const { compose } = require('throwback') | ||
const fn = compose([ | ||
async function (req, res, next) { | ||
async function (ctx, next) { | ||
console.log(1) | ||
@@ -37,3 +37,3 @@ | ||
}, | ||
async function (req, res, next) { | ||
async function (ctx, next) { | ||
console.log(2) | ||
@@ -47,12 +47,62 @@ | ||
// the middleware bubbles back to the beginning. | ||
fn({}, {}, function (req, res) { | ||
fn({}, function (ctx) { | ||
console.log(3) | ||
res.status = 404 | ||
ctx.status = 404 | ||
}) | ||
``` | ||
**Tip:** In development mode, `debug` mode will throw errors when you do something unexpected. In production, faster non-error code paths are used. | ||
### Example | ||
Build a micro HTTP server! | ||
```js | ||
const { createServer } = require('http') | ||
const finalhandler = require('finalhandler') // Example only, not compatible with single `ctx` arg. | ||
const { compose } = require('throwback') | ||
const app = compose([ | ||
function ({ req, res }, next) { | ||
res.end('Hello world!') | ||
} | ||
]) | ||
createServer(function (req, res) { | ||
return app({ req, res }, finalhandler()) | ||
}).listen(3000) | ||
``` | ||
### Advanced | ||
Did you know `next(ctx?)` accepts an optional `ctx` argument which will override `ctx` for all following middleware functions? This enables advanced functionality such as `Request` cloning and retries in [`popsicle`](https://github.com/serviejs/popsicle). | ||
```js | ||
async function retryRequest (req, next) { | ||
let retries = 5 | ||
while (retries--) { | ||
try { | ||
const res = await next(req.clone()) | ||
return res | ||
} catch (e) { | ||
continue | ||
} | ||
} | ||
throw new Error('Retry limit exceeded') | ||
} | ||
``` | ||
## Use Cases | ||
* HTTP requests (e.g. [`popsicle`](https://github.com/serviejs/popsicle)) | ||
* HTTP servers (e.g. [`servie`](https://github.com/serviejs/servie)) | ||
* Processing pipelines (e.g. [`scrappy`](https://github.com/blakeembrey/node-scrappy)) | ||
## Inspiration | ||
Built for [`popsicle`](https://github.com/blakeembrey/popsicle) and inspired by [`koa-compose`](https://github.com/koajs/compose). | ||
Built for [`servie`](https://github.com/serviejs) and inspired by [`koa-compose`](https://github.com/koajs/compose). | ||
@@ -59,0 +109,0 @@ ## License |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
31079
8
243
108
1
1