├── .travis.yml ├── .nycrc ├── .gitignore ├── LICENSE ├── package.json ├── index.js ├── README.md └── index.spec.js /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 10 4 | - 12 5 | - 14 6 | script: 7 | - npm run ci 8 | -------------------------------------------------------------------------------- /.nycrc: -------------------------------------------------------------------------------- 1 | { 2 | "extension": [ 3 | ".js" 4 | ], 5 | "exclude": [ 6 | "index.spec.js" 7 | ], 8 | "reporter": [ 9 | "text-lcov", 10 | "text", 11 | "lcov" 12 | ], 13 | "report-dir": "./coverage", 14 | "temp-dir": "./.nyc_output" 15 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # OS # 3 | ################### 4 | .DS_Store 5 | .idea 6 | Thumbs.db 7 | tmp 8 | temp 9 | 10 | 11 | # Node.js # 12 | ################### 13 | node_modules 14 | package-lock.json 15 | 16 | 17 | # NYC # 18 | ################### 19 | coverage 20 | .nyc_output 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 yunsong 4 | Copyright (c) 2020 Koa.js contributors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "koa-convert", 3 | "version": "2.0.0", 4 | "description": "convert modern Koa legacy generator-based middleware to promise-based middleware", 5 | "main": "index.js", 6 | "files": [ 7 | "index.js" 8 | ], 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/gyson/koa-convert.git" 12 | }, 13 | "standard": { 14 | "ignore": [ 15 | "index.spec.js" 16 | ] 17 | }, 18 | "scripts": { 19 | "lint": "standard", 20 | "pretest": "npm run lint", 21 | "test": "mocha index.spec.js --exit", 22 | "precoverage": "rimraf .nyc_output coverage", 23 | "coverage": "nyc npm run test", 24 | "ci": "npm run coverage" 25 | }, 26 | "keywords": [ 27 | "koa", 28 | "middleware", 29 | "convert", 30 | "back", 31 | "generator", 32 | "promise", 33 | "generator-based-middleware", 34 | "promise-based-middleware", 35 | "support" 36 | ], 37 | "author": "gyson ", 38 | "contributors": [ 39 | "gyson ", 40 | "Lloyd Brookes <75pound@gmail.com>", 41 | "Imed Jaberi (https://www.3imed-jaberi.com)" 42 | ], 43 | "license": "MIT", 44 | "dependencies": { 45 | "co": "^4.6.0", 46 | "koa-compose": "^4.1.0" 47 | }, 48 | "devDependencies": { 49 | "koa": "^2.13.0", 50 | "koa-v1": "npm:koa@1.7.0", 51 | "mocha": "^7.1.1", 52 | "nyc": "^15.1.0", 53 | "rimraf": "^3.0.2", 54 | "standard": "^14.3.4", 55 | "supertest": "^4.0.2" 56 | }, 57 | "engines": { 58 | "node": ">= 10" 59 | }, 60 | "bugs": { 61 | "url": "https://github.com/gyson/koa-convert/issues" 62 | }, 63 | "homepage": "https://github.com/gyson/koa-convert#readme" 64 | } 65 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | const co = require('co') 8 | const compose = require('koa-compose') 9 | 10 | /** 11 | * Expose `convert()`. 12 | */ 13 | 14 | module.exports = convert 15 | 16 | /** 17 | * Convert Koa legacy generator-based middleware 18 | * to modern promise-based middleware. 19 | * 20 | * 21 | * @api public 22 | * */ 23 | 24 | function convert (mw) { 25 | if (typeof mw !== 'function') { 26 | throw new TypeError('middleware must be a function') 27 | } 28 | 29 | // assume it's Promise-based middleware 30 | if ( 31 | mw.constructor.name !== 'GeneratorFunction' && 32 | mw.constructor.name !== 'AsyncGeneratorFunction' 33 | ) { 34 | return mw 35 | } 36 | 37 | const converted = function (ctx, next) { 38 | return co.call( 39 | ctx, 40 | mw.call( 41 | ctx, 42 | (function * (next) { return yield next() })(next) 43 | )) 44 | } 45 | 46 | converted._name = mw._name || mw.name 47 | return converted 48 | } 49 | 50 | /** 51 | * Convert and compose multiple middleware 52 | * (could mix legacy and modern ones) 53 | * and return modern promise middleware. 54 | * 55 | * 56 | * @api public 57 | * */ 58 | 59 | // convert.compose(mw, mw, mw) 60 | // convert.compose([mw, mw, mw]) 61 | convert.compose = function (arr) { 62 | if (!Array.isArray(arr)) { 63 | arr = Array.from(arguments) 64 | } 65 | 66 | return compose(arr.map(convert)) 67 | } 68 | 69 | /** 70 | * Convert Koa modern promise-based middleware 71 | * to legacy generator-based middleware. 72 | * 73 | * 74 | * @api public 75 | * */ 76 | 77 | convert.back = function (mw) { 78 | if (typeof mw !== 'function') { 79 | throw new TypeError('middleware must be a function') 80 | } 81 | 82 | // assume it's generator middleware 83 | if (mw.constructor.name === 'GeneratorFunction' || mw.constructor.name === 'AsyncGeneratorFunction') { 84 | return mw 85 | } 86 | 87 | const converted = function * (next) { 88 | const ctx = this 89 | let called = false 90 | 91 | yield mw(ctx, function () { 92 | if (called) { 93 | // guard against multiple next() calls 94 | // https://github.com/koajs/compose/blob/4e3e96baf58b817d71bd44a8c0d78bb42623aa95/index.js#L36 95 | throw new Error('next() called multiple times') 96 | } 97 | 98 | called = true 99 | return co.call(ctx, next) 100 | }) 101 | } 102 | 103 | converted._name = mw._name || mw.name 104 | return converted 105 | } 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # koa-convert 2 | 3 | [![Build Status][travis-img]][travis-url] 4 | [![NPM version][npm-badge]][npm-url] 5 | [![License][license-badge]][license-url] 6 | ![Code Size][code-size-badge] 7 | 8 | 9 | 10 | 11 | 12 | [travis-img]: https://travis-ci.org/koajs/convert.svg?branch=master 13 | [travis-url]: https://travis-ci.org/koajs/convert 14 | 15 | 20 | 21 | [npm-badge]: https://img.shields.io/npm/v/koa-better-request-id.svg?style=flat 22 | [npm-url]: https://www.npmjs.com/package/koa-better-request-id 23 | [license-badge]: https://img.shields.io/badge/license-MIT-green.svg?style=flat-square 24 | [license-url]: https://github.com/koajs/koa-convert/blob/master/LICENSE 25 | [code-size-badge]: https://img.shields.io/github/languages/code-size/koajs/koa-convert 26 | 27 | 28 | 29 | Convert Koa legacy (0.x & 1.x) generator middleware to modern promise middleware (2.x). 30 | 31 | It could also convert modern promise middleware back to legacy generator middleware (useful to help modern middleware support Koa v0.x or v1.x). 32 | 33 | ## Note 34 | 35 | Router middleware is special case here. Because it reimplements middleware composition internally, we cannot not simply convert it. 36 | 37 | You may use following packages for [routing](https://github.com/koajs/koa/wiki#routing-and-mounting), which are koa 2.x ready now: 38 | 39 | * [koa-route@3.0.0](https://github.com/koajs/route/tree/next) 40 | * [koa-simple-router](https://github.com/gyson/koa-simple-router) 41 | * [koa-router@next](https://github.com/alexmingoia/koa-router/tree/master) 42 | * [koa-66](https://github.com/menems/koa-66) 43 | 44 | ## Installation 45 | 46 | ```bash 47 | # npm .. 48 | $ npm i koa-convert 49 | # yarn .. 50 | $ yarn add koa-convert 51 | ``` 52 | 53 | ## Usage 54 | 55 | ```js 56 | const Koa = require('koa') // koa v2.x 57 | const convert = require('koa-convert') 58 | const app = new Koa() 59 | 60 | app.use(modernMiddleware) 61 | 62 | app.use(convert(legacyMiddleware)) 63 | 64 | app.use(convert.compose(legacyMiddleware, modernMiddleware)) 65 | 66 | function * legacyMiddleware (next) { 67 | // before 68 | yield next 69 | // after 70 | } 71 | 72 | function modernMiddleware (ctx, next) { 73 | // before 74 | return next().then(() => { 75 | // after 76 | }) 77 | } 78 | ``` 79 | 80 | ## Distinguish legacy and modern middleware 81 | 82 | In koa 0.x and 1.x (without experimental flag), `app.use` has an assertion that all (legacy) middleware must be generator function and it's tested with `fn.constructor.name == 'GeneratorFunction'` at [here](https://github.com/koajs/koa/blob/7fe29d92f1e826d9ce36029e1b9263b94cba8a7c/lib/application.js#L105). 83 | 84 | Therefore, we can distinguish legacy and modern middleware with `fn.constructor.name == 'GeneratorFunction'`. 85 | 86 | ## Migration 87 | 88 | `app.use(legacyMiddleware)` is everywhere in 0.x and 1.x and it would be painful to manually change all of them to `app.use(convert(legacyMiddleware))`. 89 | 90 | You can use following snippet to make migration easier. 91 | 92 | ```js 93 | const _use = app.use 94 | app.use = x => _use.call(app, convert(x)) 95 | ``` 96 | 97 | The above snippet will override `app.use` method and implicitly convert all legacy generator middleware to modern promise middleware. 98 | 99 | Therefore, you can have both `app.use(modernMiddleware)` and `app.use(legacyMiddleware)` and your 0.x or 1.x should work without modification. 100 | 101 | Complete example: 102 | 103 | ```js 104 | const Koa = require('koa') // v2.x 105 | const convert = require('koa-convert') 106 | const app = new Koa() 107 | 108 | // ---------- override app.use method ---------- 109 | 110 | const _use = app.use 111 | app.use = x => _use.call(app, convert(x)) 112 | 113 | // ---------- end ---------- 114 | 115 | app.use(modernMiddleware) 116 | 117 | // this will be converted to modern promise middleware implicitly 118 | app.use(legacyMiddleware) 119 | 120 | function * legacyMiddleware (next) { 121 | // before 122 | yield next 123 | // after 124 | } 125 | 126 | function modernMiddleware (ctx, next) { 127 | // before 128 | return next().then(() => { 129 | // after 130 | }) 131 | } 132 | ``` 133 | 134 | ## API 135 | 136 | #### `convert()` 137 | 138 | Convert legacy generator middleware to modern promise middleware. 139 | 140 | ```js 141 | modernMiddleware = convert(legacyMiddleware) 142 | ``` 143 | 144 | #### `convert.compose()` 145 | 146 | Convert and compose multiple middleware (could mix legacy and modern ones) and return modern promise middleware. 147 | 148 | ```js 149 | composedModernMiddleware = convert.compose(legacyMiddleware, modernMiddleware) 150 | // or 151 | composedModernMiddleware = convert.compose([legacyMiddleware, modernMiddleware]) 152 | ``` 153 | 154 | #### `convert.back()` 155 | 156 | Convert modern promise middleware back to legacy generator middleware. 157 | 158 | This is useful to help modern promise middleware support koa 0.x or 1.x. 159 | 160 | ```js 161 | legacyMiddleware = convert.back(modernMiddleware) 162 | ``` 163 | 164 | ## License 165 | 166 | [MIT](LICENSE) 167 | -------------------------------------------------------------------------------- /index.spec.js: -------------------------------------------------------------------------------- 1 | /* global describe, it */ 2 | 3 | 'use strict' 4 | 5 | const co = require('co') 6 | const Koa = require('koa') 7 | const KoaV1 = require('koa-v1') 8 | const assert = require('assert') 9 | const convert = require('./index') 10 | const request = require('supertest') 11 | 12 | describe('convert()', () => { 13 | it('should work', () => { 14 | const call = [] 15 | const ctx = {} 16 | const mw = convert(function * (next) { 17 | assert.ok(ctx === this) 18 | call.push(1) 19 | }) 20 | 21 | return mw(ctx, function () { 22 | call.push(2) 23 | }).then(function () { 24 | assert.deepEqual(call, [1]) 25 | }) 26 | }) 27 | 28 | it('should inherit the original middleware name', () => { 29 | const mw = convert(function * testing (next) {}) 30 | assert.strictEqual(mw._name, 'testing') 31 | }) 32 | 33 | it('should work with `yield next`', () => { 34 | const call = [] 35 | const ctx = {} 36 | const mw = convert(function * (next) { 37 | assert.ok(ctx === this) 38 | call.push(1) 39 | yield next 40 | call.push(3) 41 | }) 42 | 43 | return mw(ctx, function () { 44 | call.push(2) 45 | return Promise.resolve() 46 | }).then(function () { 47 | assert.deepEqual(call, [1, 2, 3]) 48 | }) 49 | }) 50 | 51 | it('should work with `yield* next`', () => { 52 | const call = [] 53 | const ctx = {} 54 | const mw = convert(function * (next) { 55 | assert.ok(ctx === this) 56 | call.push(1) 57 | yield * next 58 | call.push(3) 59 | }) 60 | 61 | return mw(ctx, function () { 62 | call.push(2) 63 | return Promise.resolve() 64 | }).then(function () { 65 | assert.deepEqual(call, [1, 2, 3]) 66 | }) 67 | }) 68 | 69 | it('should throw', () => { 70 | assert.throws(() => convert('foo')) 71 | }) 72 | }) 73 | 74 | describe('convert.compose()', () => { 75 | it('should work', () => { 76 | const call = [] 77 | const context = {} 78 | let _context 79 | const mw = convert.compose([ 80 | function * name (next) { 81 | call.push(1) 82 | yield next 83 | call.push(11) 84 | }, 85 | (ctx, next) => { 86 | call.push(2) 87 | return next().then(() => { 88 | call.push(10) 89 | }) 90 | }, 91 | function * (next) { 92 | call.push(3) 93 | yield * next 94 | call.push(9) 95 | }, 96 | co.wrap(function * (ctx, next) { 97 | call.push(4) 98 | yield next() 99 | call.push(8) 100 | }), 101 | function * (next) { 102 | try { 103 | call.push(5) 104 | yield next 105 | } catch (e) { 106 | call.push(7) 107 | } 108 | }, 109 | (ctx, next) => { 110 | _context = ctx 111 | call.push(6) 112 | throw new Error() 113 | } 114 | ]) 115 | 116 | return mw(context).then(() => { 117 | assert.equal(context, _context) 118 | assert.deepEqual(call, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) 119 | }) 120 | }) 121 | 122 | it('should work too', () => { 123 | const call = [] 124 | const context = {} 125 | let _context 126 | const mw = convert.compose( 127 | (ctx, next) => { 128 | call.push(1) 129 | return next().catch(() => { 130 | call.push(4) 131 | }) 132 | }, 133 | function * (next) { 134 | call.push(2) 135 | yield next 136 | call.push(-1) // should not call this 137 | }, 138 | function * (next) { 139 | call.push(3) 140 | yield * next 141 | call.push(-1) // should not call this 142 | }, 143 | (ctx, next) => { 144 | _context = ctx 145 | return Promise.reject(new Error()) 146 | } 147 | ) 148 | 149 | return mw(context).then(() => { 150 | assert.equal(context, _context) 151 | assert.deepEqual(call, [1, 2, 3, 4]) 152 | }) 153 | }) 154 | }) 155 | 156 | describe('convert.back()', () => { 157 | it('should work with koa 1', done => { 158 | const app = new KoaV1() 159 | 160 | app.use(function * (next) { 161 | this.body = [1] 162 | yield next 163 | this.body.push(6) 164 | }) 165 | 166 | app.use(convert.back((ctx, next) => { 167 | ctx.body.push(2) 168 | return next().then(() => { 169 | ctx.body.push(5) 170 | }) 171 | })) 172 | 173 | app.use(convert.back(co.wrap(function * (ctx, next) { 174 | ctx.body.push(3) 175 | yield next() 176 | ctx.body.push(4) 177 | }))) 178 | 179 | request(app.callback()) 180 | .get('/') 181 | .expect(200, [1, 2, 3, 4, 5, 6]) 182 | .end(done) 183 | }) 184 | 185 | it('should work too', (done) => { 186 | const app = new KoaV1() 187 | 188 | app.use(function * (next) { 189 | this.body = [1] 190 | yield next 191 | this.body.push(3) 192 | }) 193 | 194 | app.use(convert.back(function * (next) { 195 | this.body.push(2) 196 | })) 197 | 198 | request(app.callback()) 199 | .get('/') 200 | .expect(200, [1, 2, 3]) 201 | .end(done) 202 | }) 203 | 204 | it('should guard multiple calls', done => { 205 | const app = new KoaV1() 206 | 207 | app.use(function * (next) { 208 | try { 209 | this.body = [1] 210 | yield next 211 | } catch (e) { 212 | this.body.push(e.message) 213 | } 214 | }) 215 | 216 | app.use(convert.back(co.wrap(function * (ctx, next) { 217 | ctx.body.push(2) 218 | yield next() 219 | yield next() // this should throw new Error('next() called multiple times') 220 | }))) 221 | 222 | request(app.callback()) 223 | .get('/') 224 | .expect(200, [1, 2, 'next() called multiple times']) 225 | .end(done) 226 | }) 227 | 228 | it('should inherit the original middleware name', () => { 229 | const mw = convert.back(function testing (ctx, next) {}) 230 | assert.strictEqual(mw._name, 'testing') 231 | }) 232 | 233 | it('should throw with koa 1', () => { 234 | assert.throws(() => convert.back('foo')) 235 | }) 236 | }) 237 | 238 | describe('migration snippet', () => { 239 | const app = new Koa() 240 | 241 | // snippet 242 | const _use = app.use 243 | app.use = x => _use.call(app, convert(x)) 244 | // end 245 | 246 | app.use((ctx, next) => { 247 | ctx.body = [1] 248 | return next().then(() => { 249 | ctx.body.push(9) 250 | }) 251 | }) 252 | 253 | app.use(function * (next) { 254 | this.body.push(2) 255 | yield next 256 | this.body.push(8) 257 | }) 258 | 259 | app.use(function * (next) { 260 | this.body.push(3) 261 | yield * next 262 | this.body.push(7) 263 | }) 264 | 265 | app.use(co.wrap(function * (ctx, next) { 266 | ctx.body.push(4) 267 | yield next() 268 | ctx.body.push(6) 269 | })) 270 | 271 | app.use(ctx => { 272 | ctx.body.push(5) 273 | }) 274 | 275 | it('should work', done => { 276 | request(app.callback()) 277 | .get('/') 278 | .expect(200, [1, 2, 3, 4, 5, 6, 7, 8, 9]) 279 | .end(done) 280 | }) 281 | }) 282 | --------------------------------------------------------------------------------