Home Manual Reference Source Test

lib/query-subscription-pool.js

/* eslint global-require: 0 */
import QuerySubscription from './query-subscription';

/**
The QuerySubscriptionPool maintains a list of all of the query
subscriptions in the app. In the future, this class will monitor performance,
merge equivalent subscriptions, etc.

@private
*/
export default class QuerySubscriptionPool {
  constructor(database) {
    this._database = database;
    this._subscriptions = {};
    this._cleanupChecks = [];
    this._setup();
  }

  add(query, callback) {
    // TODO
    // if (NylasEnv.inDevMode()) {
    //   callback._registrationPoint = this._formatRegistrationPoint((new Error).stack);
    // }

    const key = this._keyForQuery(query);
    let subscription = this._subscriptions[key];
    if (!subscription) {
      subscription = new QuerySubscription(query);
      this._subscriptions[key] = subscription;
    }

    subscription.addCallback(callback);
    return () => {
      subscription.removeCallback(callback);
      this._scheduleCleanupCheckForSubscription(key);
    };
  }

  addPrivateSubscription(key, subscription, callback) {
    this._subscriptions[key] = subscription;
    subscription.addCallback(callback);
    return () => {
      subscription.removeCallback(callback);
      this._scheduleCleanupCheckForSubscription(key);
    };
  }

  printSubscriptions() {
    // TODO
    // if (!NylasEnv.inDevMode()) {
    //   console.log("printSubscriptions is only available in developer mode.");
    //   return;
    // }

    for (const key of Object.keys(this._subscriptions)) {
      const subscription = this._subscriptions[key];
      console.log(key);
      console.group();
      for (const callback of subscription._callbacks) {
        console.log(`${callback._registrationPoint}`);
      }
      console.groupEnd();
    }
  }

  _scheduleCleanupCheckForSubscription(key) {
    // We unlisten / relisten to lots of subscriptions and setTimeout is actually
    // /not/ that fast. Create one timeout for all checks, not one for each.
    if (this._cleanupChecks.length === 0) {
      setTimeout(() => this._runCleanupChecks(), 1);
    }
    this._cleanupChecks.push(key);
  }

  _runCleanupChecks() {
    for (const key of this._cleanupChecks) {
      const subscription = this._subscriptions[key];
      if (subscription && (subscription.callbackCount() === 0)) {
        delete this._subscriptions[key];
      }
    }
    this._cleanupChecks = [];
  }

  _formatRegistrationPoint(stackString) {
    const stack = stackString.split('\n');
    let ii = 0;
    let seenRx = false;
    while (ii < stack.length) {
      const hasRx = stack[ii].indexOf('rx.lite') !== -1;
      seenRx = seenRx || hasRx;
      if (seenRx === true && !hasRx) {
        break;
      }
      ii += 1;
    }

    return stack.slice(ii, ii + 4).join('\n');
  }

  _keyForQuery(query) {
    return query.sql();
  }

  _setup() {
    this._database.listen(this._onChange);
  }

  _onChange = (record) => {
    for (const key of Object.keys(this._subscriptions)) {
      const subscription = this._subscriptions[key];
      subscription.applyChangeRecord(record);
    }
  }
}