BundleQueuesManager.js

import lodash from 'lodash';

/**
 * Method for generate example local storage for `bundles`, `nextBundleIds` and adapters for `BandleQueuesManager` `constructor`.
 * @param {CursorsManager} cursorsManager 
 * @returns {Object}
 * @example
 * var cm = new CursorsManager(Cursor);
 * var memory = generateAdapterForBundleQueuesManager(cm);
 * var bqm = new BundleQueuesManage(...memory.adapters);
 * var c = cm.new('any',{'some':['things','and','others']});
 */
function generateAdapterForBundleQueuesManager(cursorsManager) {
  var memory = {
    bundles: {},
    nextBundleIds: {},
    adapters: [
      function getCursor(cursorId, callback) {
        if (cursorsManager.cursors[cursorId])
          callback(cursorsManager.cursors[cursorId]);
      },
      function getBundle(cursorId, bundleId, callback) {
        var bundle = lodash.get(memory.bundles, [cursorId, bundleId]);
        callback(bundle);
      },
      function setBundle(bundle) {
        lodash.set(memory.bundles, [bundle.cursor, bundle.id], bundle);
      },
      function unsetBundle(bundle) {
        if (memory.bundles[bundle.cursor]) {
          delete memory.bundles[bundle.cursor][bundle.id];
        }
      },
      function getNextBundleId(cursorId, callback) {
        var nextBundleId = lodash.get(memory.nextBundleIds, [cursorId]);
        if (typeof(nextBundleId) == 'number') {
          callback(nextBundleId);
        } else {
          this.setNextBundleId(cursorId, 0);
          callback(0);
        }
      },
      function setNextBundleId(cursorId, nextBundleId) {
        memory.nextBundleIds[cursorId] = nextBundleId;
      }
    ],
  };

  return memory;
}

/**
 * Queue of bundles execution for multiple cursors. You must descrobe adapter-methods for store bundles and queue informatation per each cursor. You can see example of adapters in `generateAdapterForBundleQueuesManager` function.
 * @class
 * @memberof module:ancient-cursor
 */
class BundleQueuesManager {
  
  /**
   * @constructs BundleQueuesManager
   * @param {BundleQueuesManager~getCursor} getCursor
   * @param {BundleQueuesManager~getBundle} getBundle
   * @param {BundleQueuesManager~setBundle} setBundle
   * @param {BundleQueuesManager~unsetBundle} unsetBundle
   * @param {BundleQueuesManager~getNextBundleId} getNextBundleId
   * @param {BundleQueuesManager~setNextBundleId} setNextBundleId
   */
  constructor(
    getCursor,
    getBundle,
    setBundle,
    unsetBundle,
    getNextBundleId,
    setNextBundleId,
  ) {
    this.getCursor = getCursor;
    this.getBundle = getBundle;
    this.setBundle = setBundle;
    this.unsetBundle = unsetBundle;
    this.getNextBundleId = getNextBundleId;
    this.setNextBundleId = setNextBundleId;
  }
  
  /**
   * Receive bundle object. If it bundle is next in queue, then execute it, else just save into storage within adapter methods.
   * @param {Bundle} bundle
   * @param {Function} callback - Calls after all executable bundles did executed, and next bundle not founded.
   */
  useBundle(bundle, callback) {
    this.getNextBundleId(bundle.cursor, (nextBundleId) => {
      if (bundle.id == nextBundleId) {
        this.executeBundle(bundle, callback);
      } else {
        if (bundle.id > nextBundleId) {
          this.setBundle(bundle);
        }
        if (callback) callback();
      }
    });
  }
  
  /**
   * Execute bundle without queue. Not for manual usage. After execution, try to execute next bandle in queue, if it exists.
   * @param {Bundle} bundle
   * @param {Function} callback - Calls after all executable bundles did executed, and next bundle not founded.
   */
  executeBundle(bundle, callback) {
    this.getCursor(bundle.cursor, (cursor) => {
      if (cursor) {
        switch (bundle.type) {
          case 'set':
            cursor.set(bundle.path, bundle.value);
            break;
          case 'unset':
            cursor.set(bundle.path, undefined);
            break;
          case 'splice':
            cursor.splice(bundle.path, bundle.start, bundle.deleteCount, ...bundle.items);
            break;
          default:
            throw new Error(`Bundle type "${bundle.type}" is unexpected.`);
        }
        this.unsetBundle(bundle);
        this.setNextBundleId(bundle.cursor, bundle.id + 1);
        this.getBundle(bundle.cursor, bundle.id + 1, (bundle) => {
          if (bundle) this.executeBundle(bundle, callback);
          else if (callback) callback();
        });
      } else if (callback) callback();
    });
  }
}

export {
  BundleQueuesManager,
  generateAdapterForBundleQueuesManager,
};

/**
 * @callback BundleQueuesManager~getCursor
 * @memberof module:ancient-cursor
 * @param cursorId
 * @param {Function} callback - (cursor) => {}
 */

/**
 * @callback BundleQueuesManager~getBundle
 * @memberof module:ancient-cursor
 * @param cursorId
 * @param bundleId
 * @param {Function} callback - (bundle) => {}
 */

/**
 * @callback BundleQueuesManager~setBundle
 * @memberof module:ancient-cursor
 * @param {Bundle} bundle
 */

/**
 * @callback BundleQueuesManager~unsetBundle
 * @memberof module:ancient-cursor
 * @param {Bundle} bundle
 */

/**
 * @callback BundleQueuesManager~getNextBundleId
 * @memberof module:ancient-cursor
 * @param cursorId
 * @param {Function} callback - (nextBundleId) => {}
 */

/**
 * @callback BundleQueuesManager~setNextBundleId
 * @memberof module:ancient-cursor
 * @param cursorId
 * @param nextBundleId
 */