Skip to content
MarsDB is a Promise based lightweight database with MongoDB query syntax, written on ES6
Branch: master
Clone or download
Latest commit 33856a1 Sep 7, 2016
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
build
dist
gulp
lib
test
.babelrc
.editorconfig
.eslintignore
.eslintrc
.gitignore
.istanbul.yml
.tern-project
.travis.yml
LICENSE
README.md
babelhook.js
bower.json
browser_tests.html
browser_tests.js
gulpfile.js
index.js
package.json
polyfills.js

README.md

MarsDB

Build Status npm version Coverage Status Dependency Status bitHound Overall Score Join the chat at https://gitter.im/c58/marsdb GitHub stars

MarsDB is a lightweight client-side database. It's based on a Meteor's minimongo matching/modifying implementation. It's carefully written on ES6, have a Promise based interface and may be backed with any storage implementation (see plugins). It's also supports observable cursors.

MarsDB supports any kind of find/update/remove operations that Meteor's minimongo does. So, go to the Meteor docs for supported query/modifier operations.

You can use it in any JS environment (Browser, Electron, NW.js, Node.js).

Features

  • Promise based API
  • Carefully written on ES6
  • Very very flexible – just take a look to the plugins section
  • Supports many of MongoDB query/modify operations – thanks to a Meteor's minimongo
  • Flexible pipeline – map, reduce, custom sorting function, filtering. All with a sexy JS interface (no ugly mongo's aggregation language)
  • Persistence API – all collections can be stored (and restored) with any kind of storage (in-memory, LocalStorage, LevelUP, etc)
  • Observable queries - live queries just like in Meteor, but with simplier interface
  • Reactive joins – out of the box

Bindings

Plugins

Meteor compatible client/server

Sometimes you can't use Meteor infrastructure. Maybe you need to build a custom client. Maybe you need to build a custom server with express and other modules. In meteor it can be done with a ton of hack. But the only reason why it's so ugly to do a simple things is because Meteor forces you to use their infrastructure. I'm trying to solve this issue with DDP client/server modules, based on MarsDB.

Examples

Using within non-ES6 environment

The ./dist folder contains already compiled to a ES5 code, but some polyfills needed. For using in a browser you must to include marsdb.polyfills.js before marsdb.min.js. In node.js you need to require('marsdb/polyfills'). It sets in a window/global: Promise, Set and Symbol.

Create a collection

import Collection from 'marsdb';
import LocalForageManager from 'marsdb-localforage';

// Default storage is in-memory
// Setup different storage managers
// (all documents will be save in a browser cache)
Collection.defaultStorageManager(LocalForageManager);

// Create collection wit new default storage
const users = new Collection('users');

Create an in-memory collection

import Collection from 'marsdb';
import LocalStorageManager from 'marsdb-localstorage';

// Set some defaults and create collection
Collection.defaultStorageManager(LocalStorageManager);
const users = new Collection('users');

// But it may be useful to create in-memory
// collection without defined defaults
// (for example to save some session state)
const session = new Collection('session', {inMemory: true});

Find documents

const posts = new Collection('posts');
posts.find({author: 'Bob'})
  .project({author: 1})
  .sort(['createdAt'])
  .then(docs => {
    // do something with docs
  });

Find with pipeline (map, reduce, filter)

An order of pipeline methods invokation is important. Next pipeline operation gives as argument a result of a previous operation.

const posts = new Collection('posts');

// Get number of all comments in the DB
posts.find()
  .limit(10)
  .sortFunc((a, b) => a - b + 10)
  .filter(doc => Matsh.sqrt(doc.comment.length) > 1.5)
  .map(doc => doc.comments.length)
  .reduce((acum, val) => acum + val)
  .then(result => {
    // result is a number of all comments
    // in all found posts
  });

// Result is `undefined` because posts
// is not exists and additional processing
// is not ran (thanks to `.ifNotEmpty()`)
posts.find({author: 'not_existing_name'})
  .aggregate(docs => docs[0])
  .ifNotEmpty()
  .aggregate(user => user.name)

Find with observing changes

Observable cursor returned by a find and findOne methods of a collection. Updates of the cursor is batched and debounced (default batch size is 20 and debounce time is 1000 / 15 ms). You can change the paramters by batchSize and debounce methods of an observable cursor (methods is chained).

const posts = new Collection('posts');
const stopper = posts.find({tags: {$in: ['marsdb', 'is', 'awesome']}})
  .observe(docs => {
    // invoked on every result change
    // (on initial result too)
    stopper.stop(); // stops observing
  }).then(docs => {
    // invoked once on initial result
    // (after `observer` callback)
  });

Find with joins

const users = new Collection('users');
const posts = new Collection('posts');
posts.find()
  .join(doc => {
    // Return a Promise for waiting of the result.
    return users.findOne(doc.authorId).then(user => {
      doc.authorObj = user;
      // any return is ignored
    });
  })
  .join(doc => {
    // For reactive join you must invoke `observe` instead `then`
    // That's it!
    return users.findOne(doc.authorId).observe(user => {
      doc.authorObj = user;
    });
  })
  .join((doc, updated) => {
    // Also any other “join” mutations supported
    doc.another = _cached_data_by_post[doc._id];

    // Manually update a joined parameter and propagate
    // update event from current cursor to a root
    // (`observe` callback invoked)
    setTimeout(() => {
      doc.another = 'some another user';
      updated();
    }, 10);
  })
  // Or just pass join spec object for fast joining
  // (only one `find` will be produced for all posts)
  .join({ authorId: users }) // posts[i].authorId will be user object
  .observe((posts) => {
    // do something with posts with authors
    // invoked any time when posts changed
    // (and when observed joins changed too)
  })

Inserting

const posts = new Collection('posts');
posts.insert({text: 'MarsDB is awesome'}).then(docId => {
  // Invoked after persisting document
})
posts.insertAll(
  {text: 'MarsDB'},
  {text: 'is'},
  {text: 'awesome'}
).then(docsIds => {
  // invoked when all documents inserted
});

Updating

const posts = new Collection('posts');
posts.update(
  {authorId: {$in: [1, 2, 3]}},
  {$set: {text: 'noop'}}
).then(result => {
  console.log(result.modified) // count of modified docs
  console.log(result.updated) // array of updated docs
  console.log(result.original) // array of original docs
});

// Upsert (insert when nothing found)
posts.update(
  {authorId: "123"},
  {$set: {text: 'noop'}},
  {upsert: true}
).then(result => {
  // { authorId: "123", text: 'noop', _id: '...' }
});

Removing

const posts = new Collection('posts');
posts.remove({authorId: {$in: [1,2,3]}})
  .then(removedDocs => {
    // do something with removed documents array
  });

Roadmap

  • Indexes support for some kind of simple requests {a: '^b'}, {a: {$lt: 9}}
  • Documentation

Contributing

I'm waiting for your pull requests and issues. Don't forget to execute gulp lint before requesting. Accepted only requests without errors.

License

See License

You can’t perform that action at this time.