Skip to content
 
 

Latest commit

 

History

166 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Build Status Coverage Status Dependency Status Join the chat at https://gitter.im/mojo-js/mesh.js

Mesh is a universal interface for communicating with data sources whether it's your API, mongodb, pubnub, webrtc, socket.io, redis, or local storage. Easily build sophisticated features such as offline-mode, realtime data, rollbacks, and more with little effort.

Mesh is entirely customizable, and doesn't make assumptions about how a data source works. You can easily build your own API adapter that's interoperable with all the other mesh plugins.

Here's a basic example of how you might implement an API that caches temporarily to local storage:

var mesh = require("mesh");
var http = require("mesh-http");
var localStorage = require("mesh-local-storage");

// local storage cache - keep stuff for one minute max
var cache = localStorage({
	ttl: 1000 * 60
});
var api = http({
	prefix: "/api"
});

// pipe all persistence operations to the cache
api(mesh.op("tail")).pipe(mesh.open(cache));

// the DB we'll use, return the first result returned, and
// only pass 'load' operations to the cache
var db = mesh.first(mesh.accept("load", cache), api);


db(mesh.op("insert", {
	collection: "people"

	// path is automatically resolved from the collection param,
	// but you can easily override it.
	path: "/people",

	// POST is resolved from the operation name, but it's
	// also overridable
	method: "POST",

	data: {
		name: "john"
	}
})).on("data", function(personData) {

	// load the person saved. This should result in a cache
	// hit for local storage. Also note that the HTTP path & method
	// will automatically get resolved.
	db(mesh.op("load", {
		collection: "people",
		query: {
			name: person.name
		}
	})).
	on("data", function(personData) {
		// do stuff with data
	});
});

Highlights

  • Streamable interface.
  • Works with any library, or framework.
  • Works on any platform.
  • Tiny (11kb).
  • Works nicely with other stream-based libraries such as highland.
  • Isomorphic. Easily use different databases for different platforms.
  • Easily testable. Stub out any database for a fake one.
  • Simple design. Use it for many other things such as an event bus, message-queue service, etc.

Roadmap

  • Examples
    • MeteorJS clone with ReactJS
      • latency compensation
      • same views & models on the front-end & backend
      • offline-mode (fully operational with CRUD)
      • realtime data
      • mongodb queries server-side/client-side with socket.io
  • Adapters
    • RPC adapter
    • ReactJS
    • AngularJS Adapter
    • EmberJS

Installation

npm install mesh

Or via bower:

bower install mesh

Adapters

Examples

Realtime Example

Below is an example of a realtime DB that uses pubnub, and local storage.

var mesh = require("mesh");
var pubnub = require("mesh-pubnub");
var localStorage = require("mesh-local-storage");

// store data locally on the users machine
var localdb = localStorage();

// pubnub adapter for sending operations to other connected clients
var pubdb = pubnub({
	publishKey: "publish key",
	subscribeKey: "subscribe key",
	channel: "chatroom"
});

// the actual DB we're going to use. Pass
// all operations to localstorage, and pubnub
var db = mesh.parallel(localdb, pubdb);

// tail all operations from pubnub to the local DB. Note
// that remote operations don't get re-sent to pubnub.
pubdb(mesh.op("tail")).pipe(mesh.open(db));

// create a child database - collection will get passed to each operation
var peopleDb = mesh.child(db, {
	collection: "people"
});

// insert some people
peopleDb(mesh.op("insert", {
	data: [
    {	name: "Gordon Ramsay" },
    {	name: "Ben Stiller" }
  ]
})).on("data", function() {
	// handle data here
});

stream.Readable db(operationName, options)

Runs a new operation.

Note that the supported operations & required options may change depending on the data store you're using.

var localStorage = require("mesh-local-storage");

var localdb = localStorage();
localdb(mesh.operation("insert", {
	collection: "people",
	data: { name: "Arnold Schwarzenegger" }
})).on("data", function() {
	// handle data here
});

stream.Stream mesh.open(db)

Creates a new operation stream.

var operationStream = mesh.open(db);

// emitted when the operation is performed
operationStream.on("data", function() {

});

operationStream.write(mesh.operation("insert", {
	collection: "people",
	data: { name: "Sandra Bullock" }
}));

operationStream.write(mesh.operation("remove", {
	collection: "people",
	query: { name: "Jeff Goldbloom" }
}));

operation db.operation(name, option)

creates a new operation which can be written to a database stream. See mesh.open(db).

mesh.open(db).write(mesh.operation("insert", {
	collection: "friends",
	data: { name: "Blakers" }
}));

operation mesh.op(name, options)

shorthand for mesh.operation(name options).

db mesh.top(db)

to operation - Makes it so that you can simply call db(operationName, options) instead of passing in the operation each time.

var db = mesh.top(localStorage());

// enables this
db("insert", {
	collection: "people",
	data: { name: "Jorge" }
});

// also accepts this
db(mesh.operation("insert"));

db mesh.child(db, options)

Creates a new child database. options is essentially just added to each operation performed.

var peopleDb = mesh.top(mesh.child(db, { collection: "people" }));

// insert a new person into the people collection
peopleDb("insert", {
  data: { name: "Shrek" }
});

db mesh.tailable(db, reject)

Makes the db tailable. This simply allows you to listen for any operations invoked on a db such as create, update, remove, and load.

reject is an array of operations to ignore. Default is [load].

var db = mesh.tailable(localdb);
db("tail", function() {

});

var peopleDb = mesh.top(mesh.child(db, { collection: "people" }));

peopleDb("insert", { data: { name: "Donkey" }}); // trigger tail
peopleDb("remove", { query: { name: "Donkey" }}); // trigger tail
peopleDb("update", { query: { name: "Donkey" }, data: { name: "Donkay" }}); // trigger tail
peopleDb("load", { query: { name: "Donkey" }}); // ignored by tail

db mesh.parallel(...dbs)

Combines databases and executes operations in parallel.

var db = mesh.parallel(localdb, httpdb);

// execute "load" on localdb at the same time
db(mesh.op("load")).on("data", function() {
  // Note that his will get called TWICE
}).on("end", function() {
  // called when operation is executed on all dbs
});

db mesh.sequence(...dbs)

Combines databases and executes operations in sequence.

var db = mesh.top(mesh.sequence(localdb, httpdb));

// load data from localdb first, then move to httpdb
db("load").on("data", function() {
  // Note that his will get called TWICE
});

db mesh.first(...dbs)

Runs dbs in sequence, but stops when a result is emitted from a database.

var db = mesh.top(mesh.first(localStorage(), http()));

// load data from local storage if it exists, or continue
// to http storage
db("load", { collection: "people" }).on("data", function() {

});

db mesh.accept([...operationNames, ]db)

Accepts only the provided operations.

// main DB - api server
var httpdb = mesh.tailable(http());

// temporary cache
var localdb = localStorage();

// main DB - get cached data from local storage before
// checking the server
var db = mesh.first(mesh.accept("load", localdb), httpdb);

// pipe all persistence operations back to local storage
httpdb(mesh.op("tail")).pipe(mesh.open(localdb));

db mesh.reject([...operationNames, ]db)

Runs all operations except the ones provided.

mesh.run(db, operationName, options, onRun)

Runs a database operation

mesh.run(peopleDb, "insert", { data: { name: "blarg"}}, function(err, insertedItem) {
	// handle result here
});

mesh.wrapCallback(callback)

wraps a callback as a db handler

var dispatch = mesh.parallel(
	mesh.accept("load", mesh.wrapCallback(function(operation, next) {
		// do stuff
	})),
	mesh.accept("showPopup", mesh.wrapCallback(function(operation, next) {
		document.body.appendChild(operation.element);
		next();
	}))
);

dispatch(crud.op("showPopup", { element: document.createTextNode("Hello!") }));

Building a custom database

Building a custom database is pretty easy. All you need to do is return a stream when db(opName, options) is called.

Here's some scaffolding for a custom db:

// slimmed down version of node streams.
var stream = require("obj-stream");

function createDatabase(options) {

	// create database here

	// return fn that executes operations
	return function(operation) {
		var writable = stream.writable();

		// this is important so that data can be piped to other things
		process.nextTick(function() {

			// collection MUST exist
			if (!operation.collection) return writable.reader.emit("error", new Error("missing collection"));

			// perform task here

			// write data from insert/load
			writable.write(data);

			// must call end operation when complete
			writable.end();
		});

		return writable.reader;
	};
}

Keep in mind that there are a few conventions you should follow when writing custom database adapters. These conventions are here to ensure that databases are interoperable with each other.

db(insert, options)

Insert a new item in the database. Note that data is emitted for each item inserted in the database.

  • options - db options
    • data - data to insert. Accepts 1 or many items
    • collection - collection to insert (optional for dbs that don't have it)
var _ = require("highland");
var peopleDb = mesh.top(mesh.child(db, { collection: "people" }));

// insert one item
peopleDb("insert", {
  data: { name: "jeff" }
});

// insert many items & collect the results in 1
// array
peopleDb("insert", {
  data: [
    { name: "Joe" },
    { name: "Rogan" }
  ]
}).pipe(_.pipeline(_.collect)).on("data", function(people) {

});

db(update, options)

Updates an item in the database. Doesn't return any values.

  • options
    • query - search query for items to update
    • data - data to merge with
    • collection - db collection
    • multi - true to update multiple items. false is default.
var peopleDb = mesh.top(mesh.child(db, {
	collection: "people"
}));

peopleDb("update", {
	query: { name: "jake"	},
	data: { age: 17 }
});

// update multiple items
peopleDb("update", {
	multi: true,
	query: { name: "joe" },
	data: {	age: 17 }
});

db(upsert, options)

Updates an item if it exists. Inserts an item if it doesn't.

  • options
    • query - search query for items to update
    • data - data to merge or insert
    • collection - db collection
var peopleDb = mesh.top(mesh.child(db, {
	collection: "people"
}));

// insert
peopleDb("upsert", {
	query: { name: "jake" },
	data: {
		name: "jake",
		age: 17
	}
}).on("end", function() {

	// update
	peopleDb("upsert", {
		query: { name: "jake" },
		data: {
			name: "jake",
			age: 18
		}
	})
});

db(load, options)

Loads one or many items from the database.

  • options
    • query - search query for items
    • collection - db collection
    • multi - true if loading multiple. false is default.
var peopleDb = mesh.top(mesh.child(db, { collection: "people" }));

// load one item
peopleDb("load", {
  query: { name: "tina" }
}).on("data", function() {
  // handle
});


// load many items
peopleDb("load", {
  multi: true,
  query: { name: "tina" }
}).pipe(_.pipeline(_.collect)).on("data", function(people) {
  // handle
});

db(remove, options)

Removes an item from the database.

  • options
    • query - query to search
    • collection - collection to search
    • multi - true to remove multiple items
// remove one item
db("remove", {
  collection: "people",
  query: { name: "batman" }
});


// remove all instances where age = 54
db("remove", {
  collection: "people",
  query: { age: 54 },
  multi: true
});

About

common, streamable interface for synchronizing data

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages