├── .npmignore ├── .gitignore ├── test ├── mocha.opts └── index_test.js ├── .travis.yml ├── index.js ├── package.json ├── LICENSE └── Readme.md /.npmignore: -------------------------------------------------------------------------------- 1 | .git* -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --reporter spec 2 | --bail 3 | --check-leaks 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.11" 4 | - "0.12" 5 | - "4" 6 | - "5" 7 | - "node" 8 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | try { 2 | var defaultCo = require('co'); 3 | } catch (err) {} 4 | 5 | module.exports = function wrap(gen, co) { 6 | if (!co) co = defaultCo.wrap; 7 | 8 | var fn = co(gen); 9 | 10 | if (gen.length === 4) { 11 | return function(err, req, res, next) { 12 | var isParam = !(err instanceof Error); 13 | var callNextRoute = next; 14 | if (isParam) { 15 | callNextRoute = res; 16 | } 17 | return fn(err, req, res, next).catch(callNextRoute); 18 | } 19 | } 20 | 21 | return function(req, res, next) { 22 | return fn(req, res, next).catch(next); 23 | }; 24 | }; 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "co-express", 3 | "description": "An express wrapper that enables generators to be used as middlewares", 4 | "version": "2.0.0", 5 | "author": { 6 | "name": "Martín Ciparelli", 7 | "email": "mciparelli@gmail.com" 8 | }, 9 | "contributors": [ 10 | { 11 | "name": "Martín Ciparelli", 12 | "email": "mciparelli@gmail.com" 13 | }, 14 | { 15 | "name": "Alexey Simonenko", 16 | "email": "alexey@simonenko.su" 17 | } 18 | ], 19 | "keywords": [ 20 | "web", 21 | "express", 22 | "route", 23 | "middleware", 24 | "generator", 25 | "harmony", 26 | "continuable" 27 | ], 28 | "repository": { 29 | "type": "git", 30 | "url": "git://github.com/mciparelli/co-express" 31 | }, 32 | "main": "index", 33 | "scripts": { 34 | "test": "mocha --harmony" 35 | }, 36 | "engines": { 37 | "node": ">=v0.11.4" 38 | }, 39 | "license": "MIT", 40 | "optionalDependencies": { 41 | "co": "^4.6.0" 42 | }, 43 | "devDependencies": { 44 | "express": "^4.14.0", 45 | "mocha": "^3.0.2", 46 | "should": "^10.0.0", 47 | "supertest": "^2.0.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2013 Martín Ciparelli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | **co-express** is an express wrapper that enables generators to be used as middlewares. 2 | 3 | [![NPM version](https://badge.fury.io/js/co-express.png)](http://badge.fury.io/js/co-express) [![Build Status](https://travis-ci.org/mciparelli/co-express.png?branch=master)](https://travis-ci.org/mciparelli/co-express) [![Dependency Status](https://david-dm.org/mciparelli/co-express.png)](https://david-dm.org/mciparelli/co-express) [![devDependency Status](https://david-dm.org/mciparelli/co-express/dev-status.png)](https://david-dm.org/mciparelli/co-express#info=devDependencies) 4 | 5 | ## Usage 6 | 7 | **co-express** lets you write your express routes by using generator functions as middlewares. 8 | Like this: 9 | 10 | ```js 11 | var fs = require('mz/fs'); 12 | var express = require('express'); 13 | var wrap = require('co-express'); 14 | 15 | var app = express(); 16 | 17 | app.get('/', wrap(function* (req, res) { 18 | var packageContents = yield fs.readFile('./package.json', 'utf8'); 19 | res.send(packageContents); 20 | })); 21 | 22 | app.listen(8000); 23 | ``` 24 | 25 | You can also define multiple generator functions just the [express](https://github.com/visionmedia/express) way, as long as you wrap them and call `next()`: 26 | 27 | ```js 28 | app.get('/users', wrap(function* (req, res, next) { 29 | req.users = yield db.getUsers(); 30 | next(); 31 | }), wrap(function* (req, res) { 32 | res.send(req.users); 33 | })); 34 | ``` 35 | 36 | ### Usage with other libraries 37 | 38 | Starting from v2, you can pass your own wrapper function as a second argument. 39 | 40 | This argument will be used instead of `co.wrap`. 41 | 42 | The support of this wrapper library when used with this extra argument is untested 43 | and may vary from library to library. 44 | 45 | 46 | ## Installation 47 | 48 | ```bash 49 | $ npm install co-express 50 | ``` 51 | 52 | 53 | ## License 54 | 55 | (The MIT License) 56 | 57 | Copyright (c) 2013 Martín Ciparelli <mciparelli@gmail.com> 58 | 59 | Permission is hereby granted, free of charge, to any person obtaining 60 | a copy of this software and associated documentation files (the 61 | 'Software'), to deal in the Software without restriction, including 62 | without limitation the rights to use, copy, modify, merge, publish, 63 | distribute, sublicense, and/or sell copies of the Software, and to 64 | permit persons to whom the Software is furnished to do so, subject to 65 | the following conditions: 66 | 67 | The above copyright notice and this permission notice shall be 68 | included in all copies or substantial portions of the Software. 69 | 70 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 71 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 72 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 73 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 74 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 75 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 76 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 77 | -------------------------------------------------------------------------------- /test/index_test.js: -------------------------------------------------------------------------------- 1 | var express = require('express'), 2 | wrap = require('../index'), 3 | should = require('should'), 4 | request = require('supertest'); 5 | 6 | function asyncText(err, text, cb) { 7 | setImmediate(function() { 8 | cb(err, text); 9 | }); 10 | } 11 | 12 | function thunk(err, text) { 13 | return function(cb) { 14 | asyncText(err, text, cb); 15 | }; 16 | } 17 | 18 | describe('co-express', function() { 19 | it('supports a single generator route', function(done) { 20 | var app = express(); 21 | 22 | app.get('/', wrap(function* (req, res, next) { 23 | res.send('it works!'); 24 | })); 25 | 26 | request(app) 27 | .get('/') 28 | .end(function(err, res) { 29 | should.not.exist(err); 30 | res.text.should.equal('it works!'); 31 | done(); 32 | }); 33 | }); 34 | 35 | it('supports multiple generator routes', function(done) { 36 | var app = express(); 37 | 38 | app.get('/', wrap(function* (req, res, next) { 39 | req.val = yield thunk(null, 'thunk'); 40 | next(); 41 | }), wrap(function* (req, res, next) { 42 | req.val += yield thunk(null, 'thunk'); 43 | next(); 44 | }), wrap(function* (req, res) { 45 | res.send(req.val + 'func'); 46 | })); 47 | 48 | request(app) 49 | .get('/') 50 | .end(function(err, res) { 51 | should.not.exist(err); 52 | res.text.should.equal('thunkthunkfunc'); 53 | done(); 54 | }); 55 | }); 56 | 57 | it('doesn\'t alter application object', function(done) { 58 | var app = express(); 59 | 60 | app.get('/', wrap(function* (req, res, next) { 61 | res.send('it works!'); 62 | })); 63 | 64 | app.set('it', 'works!'); 65 | 66 | request(app) 67 | .get('/') 68 | .end(function(err, res) { 69 | should.not.exist(err); 70 | res.text.should.equal('it works!'); 71 | app.get('it').should.equal('works!'); 72 | done(); 73 | }); 74 | }); 75 | 76 | 77 | it('supports param', function(done) { 78 | var app = express(); 79 | const map = { 80 | 33: 'user number 33' 81 | }; 82 | app.param('id', wrap(function* (req, res, next, id) { 83 | id.should.equal('33'); 84 | req.user = map[id]; 85 | next(); 86 | })); 87 | app.get('/:id', wrap(function* (req, res, next) { 88 | res.send(req.user); 89 | })); 90 | 91 | request(app) 92 | .get('/33') 93 | .end(function(err, res) { 94 | should.not.exist(err); 95 | res.text.should.equal('user number 33'); 96 | done(); 97 | }); 98 | }); 99 | 100 | it('supports param and catches errors thrown in generator', function(done) { 101 | var app = express(); 102 | const map = { 103 | 33: 'user number 33' 104 | }; 105 | app.param('id', wrap(function* (req, res, next, id) { 106 | id.should.equal('33'); 107 | req.user = map[id]; 108 | throw new Error('param test error'); 109 | })); 110 | app.get('/:id', wrap(function* (req, res, next) { 111 | res.send(req.user); 112 | })); 113 | app.use(wrap(function* (err, req, res, next) { 114 | err.message.should.equal('param test error'); 115 | if (err) { 116 | res.status(500).send(err.message); 117 | } 118 | })); 119 | 120 | request(app) 121 | .get('/33') 122 | .end(function(err, res) { 123 | should.not.exist(err); 124 | res.text.should.equal('param test error'); 125 | done(); 126 | }); 127 | }); 128 | 129 | it('supports param and catches errors called with next', function(done) { 130 | var app = express(); 131 | const map = { 132 | 33: 'user number 33' 133 | }; 134 | app.param('id', wrap(function* (req, res, next, id) { 135 | id.should.equal('33'); 136 | req.user = map[id]; 137 | next(new Error('param test error')); 138 | })); 139 | app.get('/:id', wrap(function* (req, res, next) { 140 | res.send(req.user); 141 | })); 142 | app.use(wrap(function* (err, req, res, next) { 143 | err.message.should.equal('param test error'); 144 | if (err) { 145 | res.status(500).send(err.message); 146 | } 147 | })); 148 | 149 | request(app) 150 | .get('/33') 151 | .end(function(err, res) { 152 | should.not.exist(err); 153 | res.text.should.equal('param test error'); 154 | done(); 155 | }); 156 | }); 157 | 158 | it('passes uncaught exceptions', function(done) { 159 | var app = express(); 160 | 161 | app.get('/', wrap(function* (req, res, next) { 162 | var val = yield thunk(new Error('thunk error')); 163 | res.send(val); 164 | })); 165 | 166 | app.use(function(err, req, res, next) { 167 | if (err && err.message === 'thunk error') { 168 | res.send('caught'); 169 | } else { 170 | next(err); 171 | } 172 | }); 173 | 174 | request(app) 175 | .get('/') 176 | .end(function(err, res) { 177 | should.not.exist(err); 178 | res.text.should.equal('caught'); 179 | done(); 180 | }); 181 | }); 182 | 183 | it('supports error routes', function(done) { 184 | var app = express(); 185 | 186 | app.get('/', wrap(function* (req, res, next) { 187 | var val = yield thunk(new Error('thunk error')); 188 | res.send(val); 189 | })); 190 | 191 | app.use(wrap(function* (err, req, res, next) { 192 | if (err && err.message === 'thunk error') { 193 | res.send('caught'); 194 | } else { 195 | next(err); 196 | } 197 | })); 198 | 199 | request(app) 200 | .get('/') 201 | .end(function(err, res) { 202 | should.not.exist(err); 203 | res.text.should.equal('caught'); 204 | done(); 205 | }); 206 | }); 207 | 208 | it('supports app.route()', function(done) { 209 | var app = express(); 210 | 211 | var books = app.route('/books'); 212 | 213 | books.get(wrap(function* (req, res, next) { 214 | req.val = yield thunk(null, 'thunk'); 215 | next(); 216 | }), wrap(function* (req, res, next) { 217 | req.val += yield thunk(null, 'thunk'); 218 | next(); 219 | }), wrap(function* (req, res) { 220 | res.send(req.val + 'func'); 221 | })); 222 | 223 | 224 | request(app) 225 | .get('/books') 226 | .end(function(err, res) { 227 | should.not.exist(err); 228 | res.text.should.equal('thunkthunkfunc'); 229 | done(); 230 | }); 231 | }); 232 | }); 233 | --------------------------------------------------------------------------------