ohmit
v0.3.1
Published
Object-Hypermedia Mapper
Downloads
6
Readme
ohmit
The Object-Hypermedia Mapper (OHM)
Install
npm install ohmit
Size (gzipped)
- unminified 3.75kb
- minified 1.80kb
What ohmit Will Do For You
Execute a plan to traverse an api and output an arbitrary object called a 'memento'.
She does this with GET
http methods and returns an object.
The spec defined by your execution plan is found at the results
attribute on the resulting object.
She will support params
at each level of traversal.
What ohmit Will Not Do For You
- She will not support other methods. If your api sucks and you do
POST
to get resources or rely on bodies in methods other thanGET
, then your code is responsible to tolerate that. - She will not transform data for you. If you want a bloated toolbelt library, see underscore.
- She will not magically extend your object. She has limited psychic powers so will fail to know what to do when Things Go Wrong. That is the concern for the caller.
Why?
Hypermedia clients are subject to complex traversal plans that really complicate testing and couple
the application code to a particular hypermedia specification. This problem is identical to application
code coupling itself to various persistence implementations. This coupling also severely complicates testing.
ohmit
aims to solve the coupling and testing complexity caused by connecting to a hypermedia (HATEOAS) api
in the same way an Object-Relational Mapper does for relational data stores . Instead of forcing application
code to speak the dialect of a specific api specification, ohmit
abstracts away the construction of objects
using relationships for paths. Link relationships are at the heart of a hypermedia api so it makes sense to
only couple application code directly to those relationships and let a driver take care of the interpretation to
a specific implementation (eg HAL, JSON API, etc).
The Spec Object
The following attributes are supported for the spec object being executed by ohmit.
var spec = {
//start traversal from this url
//you may also pass in a string for the value
_root: { _url : 'http://example.org/api' }
, a: {
//the relationship to follow
_path: '/a'
//the parameters (if any) to pass in for the relationship by key
, _params: {
a: { foo: 'bar'}
}
//specify whether to only construct a link, or to actuall GET the resources
//here, we are specifying to GET the resources at relationship 'a'
, _link: false
}
//same as 'a', but with only relationship passed (no params)
, shortA: '/a'
//specify paths (relationships) to follow
, b: '/a/b'
, c: {
_path: '/a/b/c'
//specify that the relationships at 'c' should NOT be GETted but instead
//return the unininitialized (unsynced) resources for each link
, _link: true
}
}
_root
required {String | Resource | Object} Either passing a url for the root, a resource, or an object having :_url
{String} The root url to begin traversal_resource
{Resource} The root resource instance to begin traversal from. This instance must expose.get
and.follow
operations, as well as have aself
url. Passing this in negates any other_root
config_params
{Object} Parameters to include in theGET
for the root node
any... optional The key/value pairs you want to map to resources at their relationship paths where each node can either be a
string
representing the relationship (link) or an object having:_path
required {String} The relationship (rel
) to follow from the root. You can travel as deep as you like._params
optional {Object} Map of relationship:parameter object to pass into the GET request for that relationship_link
optional {Boolean} Default : false Specifies the final relationship should not receive a GET request , but instead provide the uninitialized resources as their 'results'.
Examples
Given these resources:
var api = {
_links: {
self: { href: 'http://example.com/api'}
, a: { href: 'http://example.com/a'}
}
}
var a = {
_links: {
self: { href: 'http://example.com/a'}
}
, name: 'a'
}
#
Given the following query plan execution:
var q = {
_root: {
_url: 'http://example.com/api'
}
, a: '/a'
}
var result = ohmit.execute(q)
/** result
{
spec: /* your spec object */
//here is the results
, results: {
a: [{
_links: {
self: { href: 'http://example.com/a'}
}
, name: 'a'
}]
}
}
**/
ohmit
will traverse the api starting at http://example.com/api/
and
follow the _link
relationship of 'a' (http://example.com/a
).
The result will be found at results
attribute keyed identical to the spec object:
Super Duper complex path
var api = {
_links: {
self: { href: '/api'}
, a: { href: '/a'}
}
}
var a = {
_links: {
self: { href: '/a'}
, b: { href: '/b'}
}
}
var b = {
_links: {
self: { href: '/b'}
, c: { href: '/c' }
, d: { href: '/d'}
}
}
var c = {
_links: {
self: { href: '/c'}
}
}
var d = {
_links: {
self: { href: '/d'}
, items: [ {
href: '/items'
} ]
}
}
var items = {
_links: {
self: { href: '/items'}
, item1: { href: '/item1'}
, item2: { href: '/item2'}
}
}
var item1 = {
_links: {
self: { href: '/item1'}
}
}
var item2 = {
_links: {
self: { href: '/item2'}
}
}
var q = {
_root: { _url: '/api'}
, c: {
_path: '/a/b/c'
, _params: {
a: {
foo: 'bar'
}
, b: {
baz: 'biz'
}
}
}
, item1: {
_path: '/a/b/d/items/item1'
, _params: {
a: {
foo: 'bar'
}
, b: {
baz: 'biz'
}
}
}
,item2: {
_path: '/a/b/d/items/item2'
, _params: {
a: {
foo: 'bar'
}
, b: {
baz: 'biz'
}
}
}
}
//expects
var result = {
spec: /* yore spec object */
, results: {
c: [cResource]
, item1: [item1Resource]
, item2: [item2Resource]
}
}
Writing your hypermedia adapter
ohmit
expects a resourceFactory
that conforms to this interface:
{
// @return a Promise that resolves to a single resourceAdapter at `self` URI
createResource: function({self}) { /** returns a resource instance **/ }
}
ohmit
interacts with a resource adapter for traversal through its
related links. The resource adapter interface must meet this contract:
{
self : function() { /** return a url string identifying the location of the resource **/ }
, get : function({params}) { /** return a Promise resolving an resource adapter. should be hydrated ** / }
, hasRelation : function(rel) { /** return a Boolean answering if this resource is related by `rel` **/ }
, follow : function() { /** return a Promise resolving an Array of resource adapters which are related by `rel`. Should not perform GET operations ** / }
, resource : function() { /** return a Promise or the underlying resource the resource adapter is wrapping; could lazily load the resource (eg Proxy) **/ }
}
Running tests
NodeJS
npm test
Browser
npm run serve
In your browser, visit http://localhost:3000/test-runner.html
and look in the console.