Fork me on GitHub

riak-js

Node.js Riak client with support for HTTP and Protocol Buffers.

db.save('airlines', 'KLM', {fleet: 111, country: 'NL'}, { links:
  [{ bucket: 'flights', key: 'KLM-8098', tag: 'cargo' },
   { bucket: 'flights', key: 'KLM-1196', tag: 'passenger' }]
})

db.getAll('flights', { where: { ready: true }})

Setup

// npm install riak-js@latest
var db = require('riak-js').getClient()

// git clone git://github.com/frank06/riak-js.git  # or cloning the repo
var db = require('/path/to/riak-js/lib').getClient()

There are two APIs: http and protobuf. Just ask for them: getClient({api: 'protobuf'}).

HTTP is the default and what the following API guide is based upon. Expect similar behaviour for the Protocol Buffers interface, but keep in mind there are still some differences. One example is that the HTTP implementation internally queues requests and issues them serially. Reason why there are no race conditions in this guide only for HTTP.

Guide

Note: This guide is only applicable to riak-js 0.3.0
db.get('flights', 'KLM-5034', function(err, flight, meta) {
  if (err) throw err
  flight.status = 'delayed'
  meta.links.push({ bucket: 'airlines', key: 'IBE', tag: 'operated_by' })
  db.save('flights', 'KLM-5034', flight, meta)
})

Meta

Meta is an important concept in riak-js. It is a implementation-agnostic object that carries all metadata associated to a document, such as the bucket, key, vclock, links, and so on. It is meant to be recycled — all properties that make sense to be updated for a subsequent store operation can be modified and sent back. Any given properties that aren't used by Riak are assumed to be custom metadata for Riak values. This will become more clear as we go through the guide.

An example meta object could look like:

{ bucket: 'riakjs_airlines'
, key: 'CPA'
, usermeta: { important: false }
, _type: 'application/json'
, binary: false
, links: 
   [ { tag: 'flight'
     , key: 'CPA-729'
     , bucket: 'riakjs_client_test_flights'
     }
   ]
, raw: 'riak'
, clientId: 'riak-js'
, host: 'localhost'
, vclock: 'a85hYGBgymDKBVIsTO+1QzKYEhnzWBm+rRc6xgcRZmtOYvg6tx4q8QMkkQUA'
, lastMod: 'Sat, 25 Sep 2010 17:40:08 GMT'
, etag: '8I9CsEwo8kScElgvCOC0k'
, statusCode: 200
}

Riak properties such as 'contentType', 'vclock', 'clientId', 'links', 'etag', 'r', 'w', 'dw', 'returnBody' can be set on this object. It also contains handy methods to deal with links, and provides sensible defaults, which can of course be overriden. Examples are 'contentType': 'application/json' and 'clientId': 'riak-js'. Note that you cannot change host or port once the client is instantiated.

fs.readFile("drunk-pilot.png", 'binary', function (err, image) {
  if (err) throw err;
  db.save('evidence', 'pilot-smith-drunk', image, { contentType: 'jpeg', immediateAction: 'fire' })
});
Note that 'jpeg' is a shortcut and immediateAction is custom metadata

Not only these are tunable per-request. If you need certain defaults to apply to the whole session, provide them at initialization time: getClient({clientId: 'lan-27', raw: 'data', debug: false}).

Callbacks

db.save('flights', 'KLM-5034', flight)

riak-js follows the node.js convention: last argument is the callback, whose first argument is the err variable. If you don't provide a callback the result will be logged through console.log.

API

All commands take two optional last arguments: meta (options) and callback, in that order, and so they will not necessarily be shown below.

Get

db.get('airlines', 'KLM')

A typical response would be:

{ name: 'KLM'
, fleet: 111
, alliance: 'SkyTeam'
, european: true
}

If, however, there is a sibling conflict (when allow_mult = true) then a typical response would have a meta.statusCode = 300 and would look like:


[ { meta: 
     { bucket: 'airlines'
     , key: 'KLM'
     , usermeta: {}
     , _type: 'application/json'
     , binary: false
     , links: []
     , raw: 'riak'
     , clientId: 'riak-js'
     , host: 'localhost'
     , lastMod: 'Sun, 26 Sep 2010 16:28:17 GMT'
     , etag: '5QDmB8ezT8hpMNX9Ias8DU'
     , vclock: 'a85hYGBgymDKBVIsTO+1QzKYEhnzWBkWfhA+xgcRZmtOYlvXp4MskQUA'
     }
  , data: { name: 'KLM'
     , fleet: 111
     , alliance: 'SkyTeam'
     , european: true
     }
  }
, { meta: 
     { bucket: 'airlines'
     , key: 'KLM'
     , usermeta: {}
     , _type: 'application/json'
     , binary: false
     , links: []
     , raw: 'riak'
     , clientId: 'riak-js'
     , host: 'localhost'
     , lastMod: 'Sun, 26 Sep 2010 16:28:17 GMT'
     , etag: '4wz9tAlKC49RVqQmhcAvHz'
     , vclock: 'a85hYGBgymDKBVIsTO+1QzKYEhnzWBkWfhA+xgcRZmtOYlvXp4MskQUA'
     }
  , data: { name: 'KLM'
     , fleet: 113
     , alliance: 'SkyTeam'
     , european: true
     }
  }
]

Head

Head will only get the meta object back — no data. (It uses the HTTP HEAD verb under the hood.)

db.head('airlines', 'KLM')

Get all

Just like as with the sibling conflict, getAll will return an Array of Objects with the meta and data properties. withId is no longer necessary: you can grab the key from meta.key.

db.getAll('airlines')
db.getAll('airlines', { where: { country: 'NL', fleet: 111 } })

Keys

db.keys('airlines')

Count

db.count('airlines')

Link-walking

db.walk('airlines', 'KLM', [["_", "flight"]])

Save

db.save('airlines', 'ARG', { name: 'Aerolíneas Argentinas', fleet: 40, european: false })
db.save('flights', 'KLM-5034', flight, { returnBody: true, dw: 'quorum', method: 'POST' })

Remove

db.remove('airlines', 'KLM')

Map/Reduce

db.add('albums').map({name: 'Riak.mapValuesJson'}).run()

You can chain any number of phases or pass arrays, too:

db
  .add('airlines')
  .link({ bucket: 'flights', keep: false })
  .map('Riak.mapValuesJson')
  .reduce(['Riak.filterNotFound', function(value, count) { return value.slice(0, count - 1) }])
  .run(function(err, flights) {
    console.log(flights)
  })

Ping

Note: this command only takes an optional callback
db.ping()

Stats

Note: this command only takes an optional callback
db.stats()

Update bucket properties

db.updateProps('airlines', { n_val: 8, allow_mult: true })

Get bucket properties

Note: this command returns an object with the props property
db.getProps('airlines')

Development

Issues

Please report issues here.

Compilation

Run this in the main directory to compile coffeescript to javascript as you go:

coffee -wc -o lib --no-wrap src/**/*.coffee

Testing Philosophy

In my mind, there are three main types of tests in riak-js. The simplest kind is the simple object tests (see test/meta_test.coffee). These don't hit Riak, and are only sanity checks for various objects.

The second type is the high level integration tests for both client libs. These make sure that common operations between the HTTP and PBC clients behave identically. I've started on this in test/protobuf_client_test.coffee, but I want to move the common tests to a central spot.

The third type is a set of client-specific tests. There are a few methods on the PBC API that aren't really in the HTTP API. I still want to support them, even if there isn't any support in the HTTP API.

Authors and contributors, in order of appearence