MarsDB

repository·master·Indexed 42 days ago

https://github.com/c58/marsdb

A lightweight, Promise-based client-side MongoDB-like database for Browsers, Node.js, and Electron. Version 0.6.11 implements a Meteor-compatible (Minimongo) matching and modification engine featuring observable queries, reactive joins, and a JS-based pipeline API for data processing (map, reduce, filter, sort).

Tokens
7.1K
Snippets
27
Records
31
Agent score
52%

What's inside MarsDB

  1. Perform reactive joins

    master

    MarsDB supports joining data from different collections. You can perform joins in three ways:

    1. Manual Promise Join: Use .join(doc => ...) returning a Promise. This is a one-time join.
    2. Reactive Join: Use .join(doc => ...) returning an observable. This makes the join reactive; if the joined document changes, the root cursor will re-emit.
    3. Manual Mutation Join: Use .join((doc, updated) => ...) where you can manually call updated() to propagate changes.
    4. Fast Join Spec: Pass a join object like .join({ authorId: users }). This is highly optimized and produces a single query for all posts.
    const users = new Collection('users');
    const posts = new Collection('posts');
    
    // Reactive join example
    posts.find()
      .join(doc => {
        return users.findOne(doc.authorId).observe(user => {
          doc.authorObj = user;
        });
      })
      .observe(joinedPosts => {
        // invoked when posts OR joined users change
      });
    
    // Fast join spec example
    posts.find()
      .join({ authorId: users })
      .observe(docs => { /* ... */ });
  2. Use MarsDB in non-ES6 environments

    master

    If you are working in an environment that does not support ES6, you can use the pre-compiled ES5 files in the ./dist folder.

    In the Browser: You must include marsdb.polyfills.js before marsdb.min.js. This sets Promise, Set, and Symbol on the window or global object.

    In Node.js: Require the polyfills first:

    require('marsdb/polyfills');
    require('marsdb/polyfills');
  3. Implement a custom StorageManager

    master

    The StorageManager class serves as the interface for backend storage implementations in MarsDB. By default, the provided implementation uses an in-memory object. To use a different storage engine (such as levelup), you must implement a class that follows the same interface.

    Key methods to implement include:

    • _loadStorage(): An internal method (prefixed with _) that should return a Promise resolving once the storage is loaded.
    • persist(key, value): Saves a value associated with a key. Note that the default implementation uses EJSON.clone(value) to ensure data integrity.
    • get(key): Retrieves the value for a specific key.
    • delete(key): Removes a key from storage.
    • destroy(): Cleans up the storage.
    • createReadStream(options): Returns an EventEmitter that emits 'data' events for each stored value and an 'end' event when finished.
  4. Initialize a MarsDB Collection

    master

    To create a new collection, instantiate the Collection class with a name and an optional configuration object.

    If you set options.inMemory: true, the collection will use in-memory defaults for its cursor, delegate, storage manager, index manager, and ID generator.

    Because MarsDB uses a lazy initialization pattern to allow for global configuration, you should wrap your initial setup or logic that depends on the collection in Collection.startup(fn) to ensure the collection is fully initialized before use.

    import Collection from 'marsdb/lib/Collection';
    
    // Create a collection
    const users = new Collection('users', { inMemory: true });
    
    // Use startup to ensure the collection is ready
    Collection.startup(() => {
      console.log('Collection is ready:', users.modelName);
    });
  5. Insert documents

    master

    Use .insert() to add a single document or .insertAll() to add multiple documents. Both methods return a Promise.

    const posts = new Collection('posts');
    
    // Single insert
    posts.insert({text: 'MarsDB is awesome'}).then(docId => {
      // docId is the inserted ID
    });
    
    // Multiple inserts
    posts.insertAll(
      {text: 'MarsDB'},
      {text: 'is'},
      {text: 'awesome'}
    ).then(docsIds => {
      // docsIds is an array of IDs
    });
  6. Use the pipeline API (map, reduce, filter, sort)

    master

    MarsDB provides a flexible JS-based pipeline for data processing. The order of invocation is critical: each subsequent method receives the result of the previous operation.

    Supported pipeline methods include:

    • .limit(n)
    • .sortFunc((a, b) => ...)
    • .filter(doc => ...)
    • .map(doc => ...)
    • .reduce((acum, val) => ...)
    • .aggregate(fn)
    • .ifNotEmpty(): Prevents further processing if the previous step returned no results.
    const posts = new Collection('posts');
    
    posts.find()
      .limit(10)
      .sortFunc((a, b) => a - b + 10)
      .filter(doc => doc.comment.length > 1.5)
      .map(doc => doc.comments.length)
      .reduce((acum, val) => acum + val)
      .then(result => {
        // result is the aggregated number
      });
  7. Update documents

    master

    Use .update(query, modifier, options) to modify documents.

    Parameters:

    • query: The selection criteria (supports MongoDB-style operators like $in).
    • modifier: The update operations (e.g., {$set: {key: value}}).
    • options: An object for configuration, such as {upsert: true} to create a document if no match is found.

    Return Value: A Promise that resolves to an object containing:

    • modified: count of modified docs
    • updated: array of updated docs
    • original: array of original docs
    const posts = new Collection('posts');
    
    // Standard update
    posts.update(
      {authorId: {$in: [1, 2, 3]}},
      {$set: {text: 'noop'}}
    ).then(result => {
      console.log(result.modified);
    });
    
    // Upsert
    posts.update(
      {authorId: "123"},
      {$set: {text: 'noop'}},
      {upsert: true}
    );
  8. Create a MarsDB collection

    master

    To create a collection, import Collection from marsdb. By default, collections use in-memory storage. You can change the default storage for all collections using Collection.defaultStorageManager(Manager).

    To create a collection that is strictly in-memory (e.g., for session state) regardless of the default manager, pass {inMemory: true} in the options object.

    import Collection from 'marsdb';
    import LocalForageManager from 'marsdb-localforage';
    
    // Set a global storage manager (e.g., LocalForage)
    Collection.defaultStorageManager(LocalForageManager);
    
    // Create a persistent collection
    const users = new Collection('users');
    
    // Create an in-memory collection
    const session = new Collection('session', {inMemory: true});
  9. Find documents with projection and sorting

    master

    Use the .find() method to query documents. It returns a Promise-based cursor that supports chaining methods like .project() (to select specific fields) and .sort() (to order results).

    const posts = new Collection('posts');
    posts.find({author: 'Bob'})
      .project({author: 1})
      .sort(['createdAt'])
      .then(docs => {
        // handle docs
      });
  10. Remove documents

    master

    Use .remove(query) to delete documents matching the criteria. It returns a Promise that resolves to an array of the removed documents.

    const posts = new Collection('posts');
    posts.remove({authorId: {$in: [1,2,3]}})
      .then(removedDocs => {
        // removedDocs is an array of deleted documents
      });
  11. Observe changes with reactive queries

    master

    You can create live, observable cursors using the .observe() method. This is useful for reactive UIs. The observer is invoked on the initial result and then whenever the underlying data changes.

    Updates are batched and debounced. You can configure these via .batchSize(n) and .debounce(ms) methods chained to the cursor. To stop observing, call .stop() on the object returned by .observe().

    const posts = new Collection('posts');
    const stopper = posts.find({tags: {$in: ['marsdb']}})
      .observe(docs => {
        // invoked on every result change
        stopper.stop(); // stops observing
      }).then(docs => {
        // invoked once after initial result
      });
  12. Manually trigger a CursorObservable update

    master

    The update(firstRun = false, immediately = false) method allows you to manually trigger a query execution.

    • firstRun: If true, the update is treated as the initial execution.
    • immediately: If true, the update bypasses the debounce logic and executes right away. If false (default), the update is subject to the configured debounce and batchSize settings.

    Returns a Promise that resolves with the result of the execution.

    // Trigger an immediate update bypassing debounce
    await cursor.update(false, true);