Skip to content

Instantly share code, notes, and snippets.

@kavanagh
Last active August 29, 2015 14:00
Show Gist options
  • Save kavanagh/11257424 to your computer and use it in GitHub Desktop.
Save kavanagh/11257424 to your computer and use it in GitHub Desktop.
Draft read me for o-io.

o-io

Module developers

Modules should rely on services to get or save data. Access to these services SHOULD be via an o-io service to allow the product the ability to override or extend all (or part) of the module's strategy for doing I/O.

A module should NOT:

  • use XMLHttpRequest, localStorage, cookies or IndexDB
  • use a library such as $.ajax or superagent directly
  • care whether something is stored locally or remotely
  • should have no knowledge of how the product intends to provide this service to your module

You should assume all I/O operations are asynchronous.

Product developers

It's your business where modules get and persist data.

You are responsible for defining the plumbing needed to back the services modules need. Where there is no server providing this service you need to create a micro-service in the browser. You do this via o-io's Router.

o-io provides a number of middleware functions to do the most common tasks, greatly reducing the work needed to route service requests to the correct destination. You need only configure the middleware and define specific logic needed for you app.

The provided middleware can do things like:

  • get some JSON from a service endpoint via AJAX
  • put something user data in localStorage
  • ??

Examples of custom middleware:

  • load from the server only if it's not already in IndexDB
  • use cookies for legacy parts of your product, otherwise use localStorage
  • decode/transform custom data formats as data arrives in the browser, centralise this logic and let modules have the decoded data
  • offline images: modules just get the image data, not the specially encoded format we use on webapp.
  • backoff
  • monitoring and error tracking for service requests. do this in one place for the whole product
  • handle negotiating disk space with the user, so modules don't have to.
  • rewrite URLs in dev and test environments.
  • product wants to show the UI in a loading state when waiting for any data from a particular (or all) services.

Usage

Module code

var io = require('o-io');
var service = io('//user.prefs/v1');

service.get('/articles/:id')
       .param('id', 123).end()
       .then(function(data){
         // use the data
       });

var userArticlePrefs = {foo:'bar'};

service.put('/article/:id')
       .param('id', 123).data(userArticlePrefs).end()
       .then(function(){
          // do stuff
       });

Product code

var io = require('o-io');
var middleware = require('o-io/middleware');
var router = io.router('//user.prefs/v1');

// middleware for a whole service or pattern of URIs
router.use(function(req, res, next) {});

// use one of the premade middleware - this one 
// uses localstorage for persistance
router.use(middlware.localStorage({
  // namespace all keys
  namespace: 'userprefs'
});

// constrain a middleware to a specific verb
router.use('/article/*', 'GET', function(req, res, next) {
  // modify the the get response in some way.
  next();
});

Another (really lazy) product. This product is not backed by a server (offline only).

var io = require('o-io');
var middleware = require('o-io/middleware');
var router = io.router(); // when no service name specified then middleware are applied to all requests

// use local storage for everything
router.use(middleware.localstorage);

API

Service

To create a service object...

var io = require('o-io');
var service = io('//user.prefs'); // the service names/rootURL

or

var io = require('o-io');
var options = { secure: true };
var service = io('//user.prefs', options); // see below for options

options:

  • params : to add an API keys to every request, for example.
  • secure : use https. type:boolean. default false
  • ???

Methods

One you have created a service object. You can call it's methods...

var service = io('//myservice.com/');
var request = service.get('/thingy/:id');

service.VERB

Returns a new Request.

  • get(path)
  • get(path, options)
  • put(path)
  • put(path, options)
  • post(path)
  • post(path, options)
  • del(path)
  • del(path, options)

service.use

  • use(verb, middlewareFn) tap into all requests for a given verb. Middleware signature: * req : ??? * res : ??? * next : this param is optional. next is a function. use it to control the chain of middleware.
  • use(middlewareFn) tap into all requests for this service.

service.param

  • param(name, value)

  • param(name, fn) sets para or middleware used for all requests

    var service = io('//myservice.com');
    var getCurrentUser = function(){};
    service.param('userId', getCurrentUser);
    serive.get('/users/:userId').end().then();

Request

A request is not sent until end() is called. All other methods all you to configure the request.

Methods

#####param

Set param tokens in path.

Return the Request for chaining.

  • param(name, value)
  • param(name, fn)
  • param(object)

#####set

Set HTTP headers.

Return the Request for chaining.

  • set(name, value)
  • set(name, fn)
  • set(object)

#####data

For PUT and POST sets the body of the request. For GET set the query params.

Return the Request for chaining.

  • data(name, value)
  • data(obj)
  • data(fn)

this is equivalent to superagent's send method. This method called be called send or data. The former is too similar to end.

#####when

Delay sending the request until some other promises have been resolved/rejected.

Return the Request for chaining.

  • when(promise) or when([promise, promise, promise]): postpones sending the request end() are complete.

#####end

Send the request. If when has been called on the request the send will also be delay until after those.

Returns a CommonJS promise.

  • end()

this method could instead by called send.

Router

To create a Router object...

var io = require('o-io');
var router = io.router(); // no params. a router for all services
router.use(middlewareFn) // this middleware will apply to all services

or

var io = require('o-io');
var router = io.router('//user.*'); // routing for all service names matching this pattern
router.use(middlewareFn) // this middleware will only apply to requests on the matched services

Methods

  • use(middlewareFn)
  • use(pathExpression, middlewareFn)
  • use(pathExpression, verb, middlewareFn)
  • ... more?

Router Middleware

These will help with most tasks.

AJAX

Use Superstore underneath.

options

  • ?
  • ?
  • ?

localStorage

Say what it does.

options

  • useCookieFallback : boolean, default true
  • namespace

cookie

Sat what it does

options

  • ?
  • ?

IndexDB

Sat what it does

options

  • db
  • ?
@wheresrhys
Copy link

I would reposition router, making it service.prototype.router - would make its role more obvious.

Also, the docs above don't make it clear how a product discovers the routes they need to configure. I imagine that io would log descriptive and helpful errors until the product has set up all the routes. But this should probably be on init of the module rather than on failed calls to a given route. So maybe something like the following is needed

//module.init()
var io = require('o-io');
var service = io('//service'); // retrieves service if it exists, or creates it.
service.router('/a/required/endpoint/:id'); // or an array of them. Sets up a requirement for a route handler, or does nothing if that route is already matched by some handler
service.verify() // throws an error if there exist unhandled routes

// product
var io = require('o-io');
var service =  io('//service');  // retrieves service if it exists, or creates it.
service.router('/a/required/endpoint/:id', middleware);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment