@@ -16,3 +16,3 @@ 'use strict'; | ||
module.exports = { | ||
// conf: { userControllersDirs: [], userFittingsDirs: [], userViewsDirs: [] } | ||
// conf: { connectMiddlewareDirs: [], userFittingsDirs: [], userViewsDirs: [] } | ||
create: function createPipes(pipesDefs, conf) { | ||
@@ -192,19 +192,5 @@ return new Bagpipes(pipesDefs, conf); | ||
debug('starting onError pipe'); | ||
try { | ||
this.play(context._errorHandler, context); | ||
} catch(err) { | ||
unhandledError(context, err); | ||
} | ||
this.play(context._errorHandler, context); | ||
}; | ||
function unhandledError(context, err) { | ||
if (context._finish) { | ||
context.statusCode = 500; | ||
context.output = err.message ? err.message : JSON.stringify(err); | ||
context._finish(); | ||
} else { | ||
throw err; | ||
} | ||
} | ||
function preflight(context, fittingDef) { | ||
@@ -211,0 +197,0 @@ |
{ | ||
"name": "bagpipes", | ||
"version": "0.0.4", | ||
"version": "0.0.5", | ||
"description": "Less code, more flow. Let's dance!", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
119
README.md
@@ -15,4 +15,2 @@ # Bagpipes | ||
* [User Defined Fittings](#user-defined-fittings) | ||
* [Swagger Fittings](#swagger-fittings) | ||
* [Node-Machine Fittings](#node-machine-fittings) | ||
* [Debugging](#debugging) | ||
@@ -23,17 +21,19 @@ * [Change Log](#change-log) | ||
Bagpipes was developed as a way to enable API flows and mashups to be created declaratively in YAML | ||
without writing code. It works a lot like functional programming... there's no global state, data just gets | ||
passed from one function to the next down the line until we're done. | ||
Bagpipes was developed as a way to enable API flows and mashups to be created declaratively in YAML (or JSON) | ||
without writing code. It works a lot like functional programming... there's no global state, data is just | ||
passed from one function to the next down the line until we're done. (Similar to connect middleware.) | ||
For example, to expose an API to get the latitude and longitude of an address using Google's Geocode API, one | ||
could simply define this flow: | ||
could simply define a flow that looks like this: | ||
``` | ||
```yaml | ||
# 1. Define a http callout we'll use in our pipe | ||
google_geocode: | ||
name: http # system fitting (type is optional) | ||
name: http | ||
input: | ||
url: http://maps.googleapis.com/maps/api/geocode/json?sensor=true | ||
url: http://maps.googleapis.com/maps/api/geocode/json?sensor=true | ||
params: | ||
address: .request.parameters.address.value[0] | ||
# 2. Defined the pipe flow we'll play | ||
getAddressLocation: | ||
@@ -53,9 +53,9 @@ - google_geocode # call the fitting defined in this swagger | ||
Here's a very simple "Hello, World" example: | ||
Here's a simple, self-contained "Hello, World" example you can run: | ||
``` | ||
```js | ||
var bagpipes = require('bagpipes'); | ||
var pipesDef = { | ||
MyPipe: [ | ||
var pipesDefs = { | ||
HelloWorld: [ | ||
{ emit: 'Hello, World!' } | ||
@@ -66,4 +66,4 @@ ] | ||
var pipesConfig = {}; | ||
var pipes = bagpipes.create(pipesDef, pipesConfig); | ||
var pipe = pipes.getPipe('MyPipe'); | ||
var pipes = bagpipes.create(pipesDefs, pipesConfig); | ||
var pipe = pipes.getPipe('HelloWorld'); | ||
@@ -76,10 +76,25 @@ var context = {}; | ||
That said, you'll likely load your pipe definitions from a file something like this: | ||
As you can see, the pipe in the hello world above is defined programmatically. This is perfectly ok, but | ||
in general, you'll probably want load your pipe definitions from a YAML file something like this: | ||
```yaml | ||
HelloWorld: | ||
- emit: 'Hello, World!' | ||
``` | ||
```js | ||
var bagpipes = require('bagpipes'); | ||
var yaml = require('js-yaml'); | ||
var pipesDefs = yaml.safeLoad(fs.readFileSync('whatever.yaml')); | ||
var pipesDefs = yaml.safeLoad(fs.readFileSync('HelloWorld.yaml')); | ||
var pipes = bagpipes.create(pipesDefs, pipesConfig); | ||
var pipe = pipes.getPipe('HelloWorld'); | ||
var context = {}; | ||
pipes.play(pipe, context); | ||
console.log(context.output); | ||
``` | ||
Have fun! | ||
Either way, have fun! | ||
@@ -89,7 +104,7 @@ ## Fittings | ||
So what are these things called "fittings"? Well, simply, if a pipe is a list of steps, a fitting describes | ||
what a step actually accomplishes. | ||
what a single step actually accomplishes. | ||
Let's take a very simple example: Say we have some data that looks like this: | ||
``` | ||
```js | ||
[ { "name": "Scott", "city": "Los Angeles" } | ||
@@ -102,3 +117,3 @@ { "name": "Jeff", "city": "San Francisco" } ] | ||
``` | ||
```yaml | ||
getFirstUserName: | ||
@@ -114,3 +129,3 @@ - first | ||
``` | ||
```yaml | ||
getUserNames: | ||
@@ -121,7 +136,7 @@ - pick: name | ||
Obviously, these are trivial examples, but you can create pipes as long and as complex as you wish. In fact, you can | ||
even write your own fittings... but we're getting ahead of ourselves. | ||
even write your own special-purpose fittings. We'll get to that [later](#user-defined-fittings). | ||
### Fitting Definition | ||
When you want to use a fitting, you have 2 options: | ||
When you want to use a fitting in your pipe, you have 2 options: | ||
@@ -136,3 +151,3 @@ 1. A system or user fitting with zero or one input can be defined in-line, as we have shown above. | ||
``` | ||
```yaml | ||
geocode: | ||
@@ -182,3 +197,3 @@ name: http | ||
``` | ||
```yaml | ||
getRestaurantsAndWeather: | ||
@@ -207,3 +222,3 @@ - getAddressLocation | ||
* **_errorHandler**: the pipe played if an error occurs in the pipe | ||
* **_finish**: a final fitting run after the pipe is finished (error or not) | ||
* **_finish**: a final fitting or pipe run once the pipe has finished (error or not) | ||
@@ -221,3 +236,4 @@ Finally, the context object itself will contain any properties that you've assigned to it via the 'output' option on | ||
You may install a custom error handler pipe by specifying them using the system [onError](#onError) fitting. | ||
You may install a custom error handler pipe by specifying them using the system [onError](#onError) fitting. (As | ||
you might guess, this actually sets the _errorHandler property on context.) | ||
@@ -250,3 +266,3 @@ ## Fittings | ||
``` | ||
```yaml | ||
key: # the variable name (key) on context.input to which the value is assigned | ||
@@ -293,3 +309,3 @@ path: '' # the variable to pick from context using [json path syntax](https://www.npmjs.com/package/jspath) | ||
``` | ||
```yaml | ||
emit: | ||
@@ -379,6 +395,8 @@ name: key | ||
context object and a standard javascript asynchronous callback. When executed, this function should perform its | ||
intended function and then call the callback function with (error, response) when complete. Here's an example that | ||
will query Yelp for businesses near a location with an input of { latitude: n, longitude: n }: | ||
intended function and then call the callback function with (error, response) when complete. | ||
``` | ||
Here's an example fitting that will query Yelp for businesses near a location with an input of | ||
{ latitude: n, longitude: n }: | ||
```js | ||
var Yelp = require('yelp'); | ||
@@ -413,2 +431,4 @@ var util = require('util'); | ||
** Experimental ** | ||
You can access Swagger APIs by simply loading that Swagger. A Swagger fitting expects this: | ||
@@ -419,3 +439,3 @@ | ||
``` | ||
```yaml | ||
exampleSwaggerFitting: | ||
@@ -428,2 +448,4 @@ type: swagger | ||
** Experimental ** | ||
A node-machine is a self-documenting component format that we've adapted to the a127 (see [http://node-machine.org]()). | ||
@@ -437,3 +459,3 @@ You can use a node-machine just by using 'npm install' and declaring the fitting. The fitting definition expects a | ||
``` | ||
```yaml | ||
exampleNodeMachineFitting: | ||
@@ -445,18 +467,21 @@ type: node-machine | ||
#### Controller fittings | ||
#### Connect-middleware fittings | ||
#### TODO: CHANGE | ||
** Experimental ** | ||
Controller fittings merely provide a call to one of the controllers you've defined in your /controllers directory | ||
for use with swagger-tools router. However, given that these controllers probably interact directly with the response | ||
and aren't designed for use within the Bagpipes system, proceed with extreme caution. | ||
Connect-middleware fittings are special-purpose fittings provided as a convenience if you want to call out to any | ||
connect middleware that you have. Before calling a connect-middleware fitting, you must set a `request` and `response` | ||
property on context with appropriate values from your request chain. These will be passed to the associated connect | ||
middleware. Also, you must have passed in to the bagpipes configuration a value for connectMiddlewareDirs. | ||
Be aware, however, as these controllers almost certainly interact directly with the response and aren't designed for | ||
use within the Bagpipes system, either appropriately wrap the response object or use this option with caution. | ||
* **type**: 'controller' | ||
* **controller**: the name of the controller file in your controllers directory | ||
* **function**: the exported function to call on the controller | ||
* **type**: 'connect-middleware' | ||
* **module**: the name of the file or module to load from your middleware directory | ||
* **function**: the exported function to call on the middleware | ||
``` | ||
exampleControllerFitting: | ||
type: controller | ||
controller: my_module | ||
```yaml | ||
exampleMiddlewareFitting: | ||
type: connect-middleware | ||
module: my_module | ||
function: someFunction | ||
@@ -463,0 +488,0 @@ ``` |
41037
1.92%494
5.33%655
-1.95%