Skip to content

Commit

Permalink
Merge pull request #15 from yahoo/changes
Browse files Browse the repository at this point in the history
Fetchr now exports a Class
  • Loading branch information
Vijar committed Sep 11, 2014
2 parents 825fed4 + a4612ce commit db9edec
Show file tree
Hide file tree
Showing 10 changed files with 67 additions and 121 deletions.
55 changes: 26 additions & 29 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,32 +20,34 @@ Fetchr needs delicate set up to work properly.

## 1. Middleware

On the server side, add the fetchr middleware into your express app.
On the server side, add the fetchr middleware into your express app at a custom API endpoint.

```js
//...
var express = require('express'),
fetchr = require('fetchr'),
Fetcher = fetchr({
pathPrefix: '/myCustomAPIEndpoint'
}),
Fetcher = require('fetchr'),
app = express();

app.use(Fetcher.middleware());
app.use('/myCustomAPIEndpoint', Fetcher.middleware());
//...
```

## 2. API pathPrefix
## 2. API xhrPath

`pathPrefix` config option for the middleware is optional. Defaults to `/api`.
`xhrPath` config option when instantiating the Fetchr class is optional. Defaults to `/api`.

On the clientside, the xhrPath will be used for XHR requests.

On the serverside, the xhrPath isn't needed and is ignored.

Note: Even though this config is optional, it is necessary for xhrPath on the clientside fetcher to match the path where the middleware was mounted on in the previous step.

It is necessary to define this prefix on the client side fetcher (`fetchr.client.js`) just as on the server side.
```js
//...
var fetchr = require('fetchr'),
Fetcher = fetchr({
pathPrefix: '/myCustomAPIEndpoint'
});
var Fetcher = require('fetchr'),
fetcher = new Fetcher({
xhrPath: '/myCustomAPIEndpoint'
})
//...
```

Expand All @@ -54,13 +56,10 @@ var fetchr = require('fetchr'),
```js
//app.js
//...
var fetchr = require('fetchr'),
Fetcher = fetchr({
pathPrefix: '/myCustomAPIEndpoint'
}),
var Fetcher = require('fetchr'),
myDataFetcher = require('./dataFetcher');

Fetcher.addFetcher(myDataFetcher);
Fetcher.registerFetcher(myDataFetcher);
//...
```

Expand Down Expand Up @@ -94,20 +93,20 @@ On the clientside, this only needs to happen on page load.
//app.js - server
//...
var express = require('express'),
fetchr = require('fetchr'),
Fetcher = fetchr({
pathPrefix: '/myCustomAPIEndpoint'
}),
Fetcher = require('fetchr'),
app = express(),
myDataFetcher = require('./dataFetcher');

Fetcher.addFetcher(myDataFetcher);
Fetcher.registerFetcher(myDataFetcher);

app.use(Fetcher.middleware());
app.use('/myCustomAPIEndpoint', Fetcher.middleware());

app.use(function(req, res, next) {
//instantiated fetcher with access to req object
var fetcher = new Fetcher({req: req});
var fetcher = new Fetcher({
xhrPath: '/myCustomAPIEndpoint', //xhrPath will be ignored on the serverside fetcher instantiation
req: req
});

fetcher.read('data_api_fetcher', {id: ###}, {}, function (err, data) {
//handle err and/or data returned from data fetcher in this callback
Expand All @@ -121,11 +120,9 @@ app.use(function(req, res, next) {
```js
//app.js - client
//...
var fetchr = require('fetchr'),
Fetcher = fetchr({
pathPrefix: '/myCustomAPIEndpoint'
}),
var Fetcher = require('fetchr'),
fetcher = new Fetcher({
xhrPath: '/myCustomAPIEndpoint', //xhrPath is REQUIRED on the clientside fetcher instantiation
requireCrumb: false, // if crumbs should be required for each request, default: false
context: {
crumb: 'Ax89D94j', //optional crumb string to send back to server with each request. Validation should happen on server.
Expand Down
4 changes: 3 additions & 1 deletion examples/simple/client/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
*/
var readFlickr = require('../shared/getFlickrPhotos'),
readFlickrClient,
Fetcher = require('../shared/fetcherClass'),
config = require('../shared/config'),
Fetcher = require('../../../libs/fetcher.client.js'),
fetcher = new Fetcher({
xhrPath: config.xhrPath,
requireCrumb: false
});

Expand Down
7 changes: 4 additions & 3 deletions examples/simple/server/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,18 @@ var http = require('http'),
path = require('path'),
fs = require('fs'),
express = require('express'),
Fetcher = require('../shared/fetcherClass'),
config = require('../shared/config'),
Fetcher = require('../../../libs/fetcher.js'),
readFlickr = require('../shared/getFlickrPhotos'),
flickrFetcher = require('./fetchers/flickr'),
readFlickrServer,
templatePath = path.join(__dirname, '..', 'shared', 'index.html');

Fetcher.addFetcher(flickrFetcher);
Fetcher.registerFetcher(flickrFetcher);

var app = express();

app.use(Fetcher.middleware());
app.use(config.xhrPath, Fetcher.middleware());


app.use('/server', function (req, res, next) {
Expand Down
3 changes: 3 additions & 0 deletions examples/simple/shared/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
xhrPath: '/myapi'
};
6 changes: 0 additions & 6 deletions examples/simple/shared/fetcherClass.js

This file was deleted.

49 changes: 19 additions & 30 deletions libs/fetcher.client.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
* @module Fetcher
*/
var REST = require('./util/http.client'),
debug = require('debug'),
clientFDebug = debug('FetchrClient'),
debug = require('debug')('FetchrClient'),
_ = {
forEach : require('lodash.foreach'),
values : require('lodash.values'),
Expand All @@ -27,7 +26,7 @@ var REST = require('./util/http.client'),
},
CORE_REQUEST_FIELDS = ['resource', 'operation', 'params', 'body'],
DEFAULT_GUID = 'g0',
DEFAULT_PATH_PREFIX = '/api',
DEFAULT_XHR_PATH = '/api',
// By default, wait for 20ms to trigger sweep of the queue, after an item is added to the queue.
DEFAULT_BATCH_WINDOW = 20,
MAX_URI_LEN = 2048,
Expand All @@ -39,7 +38,7 @@ function parseResponse(response) {
try {
return JSON.parse(response.responseText);
} catch (e) {
clientFDebug('json parse failed:' + e, 'error', NAME);
debug('json parse failed:' + e, 'error', NAME);
return null;
}
}
Expand Down Expand Up @@ -129,16 +128,6 @@ Queue.prototype = {
}
};

/**
* @module createFetcherClient
* @param {object} options
* @param {string} [options.pathPrefix="/api"] The path for XHR requests
* @param {integer} [options.batchWindow=20] Number of milliseconds to wait to batch requests
*/

module.exports = function createFetcherClient (options) {
options = options || {};

/**
* Requests that are initiated within a time window are batched and sent to xhr endpoint.
* The received responses are split and routed back to the callback function assigned by initiator
Expand All @@ -162,19 +151,20 @@ module.exports = function createFetcherClient (options) {
*
* @class FetcherClient
* @param {object} options congiguration options for Fetcher
* @param {string} [options.xhrPath="/api"] The path for XHR requests
* @param {integer} [options.batchWindow=20] Number of milliseconds to wait to batch requests
* @param {Object} [options.context] The context object. It can contain current-session/context data.
* @param {String} [options.context.crumb] The crumb for current session
* @param {Boolean} [options.requireCrumb = false] require crumb for current session?
*/

function Fetcher (options) {
this.options = options || {};
this.xhrPath = options.xhrPath || DEFAULT_XHR_PATH;
this.batchWindow = options.batchWindow || DEFAULT_BATCH_WINDOW;
this.context = this.options.context || {};
}

Fetcher.pathPrefix = options.pathPrefix || DEFAULT_PATH_PREFIX;
Fetcher.batchWindow = options.batchWindow || DEFAULT_BATCH_WINDOW;

Fetcher.prototype = {
// ------------------------------------------------------------------
// Data Access Wrapper Methods
Expand Down Expand Up @@ -259,7 +249,7 @@ module.exports = function createFetcherClient (options) {
}

config = config || {};
config.xhr = Fetcher.pathPrefix;
config.xhr = this.xhrPrefix;

var self = this,
request = {
Expand Down Expand Up @@ -311,7 +301,7 @@ module.exports = function createFetcherClient (options) {
try {
matrix.push(k + '=' + encodeURIComponent(jsonifyComplexType(v)));
} catch (err) {
clientFDebug('jsonifyComplexType failed: ' + err, 'info', NAME);
debug('jsonifyComplexType failed: ' + err, 'info', NAME);
}
}
});
Expand Down Expand Up @@ -384,14 +374,14 @@ module.exports = function createFetcherClient (options) {
callback = request.callback || _.noop,
use_post,
allow_retry_post,
uri = config.uri || config.xhr || Fetcher.pathPrefix,
uri = config.uri || config.xhr || this.xhrPath,
get_uri,
requests,
data;

if (OP_READ !== request.operation && (this._isCrumbRequired() && !this.context.crumb)) {
//Crumb is required but not provided
clientFDebug('missing crumb');
debug('missing crumb');
return callback({statusCode: 400, statusText: 'missing crumb'});
}

Expand All @@ -408,7 +398,7 @@ module.exports = function createFetcherClient (options) {
if (!use_post) {
REST.get(uri, {}, config, function (err, response) {
if (err) {
clientFDebug('Syncing ' + request.resource + ' failed: statusCode=' + err.statusCode, 'info', NAME);
debug('Syncing ' + request.resource + ' failed: statusCode=' + err.statusCode, 'info', NAME);
return callback(err);
}
callback(null, parseResponse(response));
Expand All @@ -430,7 +420,7 @@ module.exports = function createFetcherClient (options) {
allow_retry_post = (request.operation === OP_READ);
REST.post(uri, {}, data, _.merge({unsafeAllowRetry: allow_retry_post}, config), function (err, response) {
if (err) {
clientFDebug('Syncing ' + request.resource + ' failed: statusCode=' + err.statusCode, 'info', NAME);
debug('Syncing ' + request.resource + ' failed: statusCode=' + err.statusCode, 'info', NAME);
return callback(err);
}
var result = parseResponse(response);
Expand All @@ -452,7 +442,7 @@ module.exports = function createFetcherClient (options) {
* @protected
* @static
*/
batch : function (requests) {
batch : /* istanbul ignore next */ function (requests) {
if (!_.isArray(requests) || requests.length <= 1) {
return requests;
}
Expand All @@ -478,7 +468,7 @@ module.exports = function createFetcherClient (options) {
batched = _.values(groups);

if (batched.length < requests.length) {
clientFDebug(requests.length + ' requests batched into ' + batched.length, 'info', NAME);
debug(requests.length + ' requests batched into ' + batched.length, 'info', NAME);
}
return batched;
},
Expand All @@ -491,7 +481,7 @@ module.exports = function createFetcherClient (options) {
* @protected
* @static
*/
multi : function (requests) {
multi : /* istanbul ignore next */ function (requests) {
var uri,
data,
count = 0,
Expand All @@ -510,13 +500,13 @@ module.exports = function createFetcherClient (options) {
if (this._isCrumbRequired() && !this.context.crumb) {
//Crumb is required but not provided
_.forEach(requests, function (request) {
clientFDebug('missing crumb');
debug('missing crumb');
request.callback({statusCode: 400, statusText: 'missing crumb'});
});
return;
}

uri = config.uri || config.xhr || Fetcher.pathPrefix;
uri = config.uri || config.xhr || this.xhrPath;

data = {
requests: {},
Expand Down Expand Up @@ -552,5 +542,4 @@ module.exports = function createFetcherClient (options) {
}
};

return Fetcher;
};
module.exports = Fetcher;
25 changes: 6 additions & 19 deletions libs/fetcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,34 @@ var OP_READ = 'read',
OP_CREATE = 'create',
OP_UPDATE = 'update',
GET = 'GET',
qs = require('querystring'),
DEFAULT_PATH_PREFIX = '/api';
qs = require('querystring');

/**
* @module createFetcherClass
* @param {object} options
* @param {string} [options.pathPrefix="/api"] The path for XHR requests
*/

module.exports = function createFetcherClass (options) {
options = options || {};

var debug = require('debug')('Fetchr');

/**
* @class Fetcher
* @param {Object} options congiguration options for Fetcher
* @param {Object} [options.req] The request object. It can contain per-request/context data.
* @param {string} [options.xhrPath="/api"] The path for XHR requests. Will be ignored serverside.
* @constructor
*/
function Fetcher(options) {
this.options = options || {};
this.req = this.options.req || {};
}

Fetcher.pathPrefix = options.pathPrefix || DEFAULT_PATH_PREFIX;
Fetcher.fetchers = {};

/**
* @method addFetcher
* @method registerFetcher
* @param {Function} fetcher
*/
Fetcher.addFetcher = function (fetcher) {
Fetcher.registerFetcher = function (fetcher) {
var name = fetcher.name || null;
//Store fetcher by name
if (!(fetcher && name)) {
Expand Down Expand Up @@ -77,15 +72,8 @@ module.exports = function createFetcherClass (options) {
return function (req, res, next) {
var request;

if (req.path.indexOf(Fetcher.pathPrefix) !== 0) {
//Skip non fetchr requests
next();
return;
}

if (req.method === GET) {
var defaultPath = Fetcher.pathPrefix + '/resource/',
path = req.path.substr(defaultPath.length).split(';');
var path = req.path.substr('/resource/'.length).split(';');
request = {
req: req,
resource: path.shift(),
Expand Down Expand Up @@ -272,5 +260,4 @@ module.exports = function createFetcherClass (options) {
Fetcher.single(request);
};

return Fetcher;
};
module.exports = Fetcher;
Loading

0 comments on commit db9edec

Please sign in to comment.