<commit_before>var EventEmitter = require('events').EventEmitter,
    inherits = require('util').inherits,
    lookup = require('dns').lookup,
    isIP = require('net').isIP,
    ReadableStream = require('stream').Readable;

var LRU = require('lru-cache');

var binding = require('../build/Release/sqlclient').Client;

var RE_PARAM = /(?:\?)|(?::(\d+|(?:[a-zA-Z][a-zA-Z0-9_]*)))/g,
    DQUOTE = 34,
    SQUOTE = 39,
    BSLASH = 92;

function Client(config) {
  if (!(this instanceof Client))
    return new Client(config);

  EventEmitter.call(this);

  this._handle = new binding({
    context: this,
    config: config,
    onconnect: this._onconnect,
    onerror: this._onerror,
    onidle: this._onidle,
    onresultinfo: this._onresultinfo,
    onrow: this._onrow,
    onresultend: this._onresultend,
    onping: this._onping,
    onclose: this._onclose,
  });
  if (typeof config === 'object' && config !== null)
    this._config = config;
  else
    this._config = {};
  this._req = undefined;
  this._queue = [];
  this._queryCache = undefined;
  this._handleClosing = false;
  this.connecting = false;
  this.connected = false;
  this.closing = false;
  this.threadId = undefined;

  // XXX: hack to get thread ID first before any other queries
  var self = this;
  this._firstQuery = {
    str: 'SELECT CONNECTION_ID()',
    cb: function(err, rows) {
      if (err) {
        self.emit('error', err);
        return;
      }
      self.threadId = rows[0][0];
      self.connecting = false;
      self.connected = true;
      self.emit('ready');
    },
    result: undefined,
    results: undefined,
    needMetadata: false,
    needColumns: false
  };
}
inherits(Client, EventEmitter);

Client.prototype._onconnect = function() {
  var queue = this._queue,
      firstQuery = this._firstQuery;
  if (queue.length === 0 || queue[0] !== firstQuery)
    queue.unshift(firstQuery);
  this._processQueue(true);
};
Client.prototype._onerror = function(err) {
  if (isDeadConn(err.code)) {
    this.connecting = false;
    this.connected = false;
    this.emit('error', err);
    this.close();
  } else {
    var req = this._req;
    if (req) {
      if (req.cb !== undefined) {
        var results = req.results;
        if (results === undefined)
          req.results = [err];
        else
          results.push(err);
      } else {
        var stream = req.stream;
        if (stream === undefined) {
          var emitter = req.emitter;
          stream = emitter._createStream();
          emitter.emit('result', stream);
          stream.emit('error', err);
          stream.push(null);
        } else {
          stream.emit('error', err);
          stream.push(null);
          req.stream = undefined;
        }
      }
    } else
      this.emit('error', err);
  }
};
Client.prototype._onidle = function() {
  var req = this._req;
  if (req) {
    // a query finished -- no more result sets
    this._queue.shift();
    this._req = undefined;
    var cb = req.cb;
    if (cb !== undefined) {
      var results = req.results;
      if (results.length === 1) {
        // single result set response

        var r = results[0];
        if (r instanceof Error)
          cb(r);
        else
          cb(undefined, r);
      } else {
        // multi-result set response

        // TODO: "undefined" here can be a bit misleading if 1 of several
        // results ended in error (can this even happen in reality?)
        cb(undefined, results);
      }
    } else
      req.emitter.emit('end');
  }
  this._processQueue(false);
};
Client.prototype._onresultinfo = function(cols, metadata) {
  var req = this._req;
  if (req.cb !== undefined) {
    if (req.needMetadata)
      req.metadata = metadata;
  } else {
    var emitter = req.emitter,
        stream = req.stream = emitter._createStream();
    if (req.needMetadata)
      stream.metadata = metadata;
    emitter.emit('result', stream);
  }
  if (req.needColumns)
    req.rowBuilder = createRowBuilder(cols);
};
Client.prototype._onrow = function(row) {
  var req = this._req,
      builder = req.rowBuilder;
  if (builder)
    row = builder(row);
  if (req.cb !== undefined) {
    var result = req.result;
    if (result === undefined)
      req.result = [row];
    else
      result.push(row);
  } else {
    var stream = req.stream;
    if (stream === undefined) {
      var emitter = req.emitter;
      stream = req.stream = emitter._createStream();
      emitter.emit('result', stream);
    }
    if (stream.push(row) === false) {
      this._handle.pause();
      stream._needResume = true;
    }
  }
};
Client.prototype._onresultend = function(numRows, affectedRows, insertId) {
  var req = this._req;
  if (req.cb !== undefined) {
    var results = req.results,
        result = req.result;
    if (result === undefined) {
      result = {
        numRows: numRows,
        affectedRows: affectedRows,
        insertId: insertId,
        metadata: req.metadata
      };
    } else {
      req.result = undefined;
      result.numRows = numRows;
      result.affectedRows = affectedRows;
      result.insertId = insertId;
      result.metadata = req.metadata;
    }
    req.metadata = undefined;
    if (results !== undefined)
      results.push(result);
    else
      req.results = [result];
  } else {
    var stream = req.stream;
    if (stream === undefined) {
      var emitter = req.emitter;
      stream = emitter._createStream();
      stream.numRows = numRows;
      stream.affectedRows = affectedRows;
      stream.insertId = insertId;
      emitter.emit('result', stream);
      stream.push(null);
    } else {
      stream.numRows = numRows;
      stream.affectedRows = affectedRows;
      stream.insertId = insertId;
      stream.push(null);
      req.stream = undefined;
    }
  }
};
Client.prototype._onping = function() {
  // TODO
};
Client.prototype._onclose = function() {
  this.connecting = false;
  this.connected = false;
  this.closing = false;
  this._handleClosing = false;
  this._req = undefined;
  this.emit('end');
  this.emit('close');
};

Client.prototype._processQueue = function(ignoreConnected) {
  var connected = this.connected;
  if (!ignoreConnected && !connected)
    return;
  var req = this._req;
  if (req)
    return;
  var queue = this._queue;
  if (queue.length) {
    req = this._req = queue[0];
    this._handle.query(req.str, req.needColumns, req.needMetadata);
  } else if (connected && this.closing && !this._handleClosing) {
    this._handleClosing = true;
    this._handle.close();
  }
};

Client.prototype.connect = function(config) {
  if (this.connecting || this.connected)
    return;

  if (typeof config === 'object' && config !== null)
    this._config = config;

  var cfg = this._config,
      self = this;
  if (typeof cfg !== 'object')
    throw new Error('Missing config');

  this.connecting = true;

  process.nextTick(function() {
    // doing a manual resolve prevents libmariadbclient from doing a blocking
    // DNS resolve
    if (!isIP(cfg.host)) {
      lookup(cfg.host, function(err, address, family) {
        if (err) {
          self.connecting = false;
          self.emit('error', err);
          return self.emit('close');
        }
        cfg = clone(cfg);
        cfg.host = address;
        self._handle.connect(cfg);
      });
    } else
      self._handle.connect(cfg);
  });
};

Client.prototype.isMariaDB = function() {
  return this._handle.isMariaDB();
};

Client.prototype.query = function(str, values, config, cb) {
  var req,
      ret;
  if (typeof str !== 'string')
    throw new Error('Missing query string');
  if (typeof values === 'function') {
    // query(str, cb)
    cb = values;
    values = config = undefined;
  } else if (typeof config === 'function') {
    // query(str, ___, cb)
    cb = config;
    if (typeof values === 'boolean') {
      config = values;
      values = undefined;
    } else
      config = undefined;
  } else if (typeof values === 'boolean') {
    // query(str, config)
    config = values;
    values = undefined;
  }

  if (Array.isArray(values) || (typeof values === 'object' && values !== null))
    query = this.prepare(query)(values);

  var needColumns = (!config ||
                     (typeof config === 'object'
                      && config !== null
                      && !config.useArray)),
      needMetadata = ((config && config.metadata === true)
                      || this._config.metadata === true);
  if (typeof cb === 'function') {
    // we are buffering all rows
    req = {
      cb: cb,
      result: undefined,
      results: undefined,
      metadata: undefined,

      str: str,
      needColumns: needColumns,
      needMetadata: needMetadata,
      rowBuilder: undefined
    };
  } else {
    // we are streaming all rows
    var hwm = (config && config.hwm) || this._config.streamHWM;
    req = {
      emitter: new ResultEmitter(this, hwm),
      stream: undefined,

      str: str,
      needColumns: needColumns,
      needMetadata: needMetadata,
      rowBuilder: undefined
    };
    ret = req.emitter;
  }
  this._queue.push(req);
  if (!this.connected)
    this.connect();
  else if (this._req === undefined)
    this._processQueue(false);
  return ret;
};

Client.prototype.close = function() {
  if (this.connected && !this.closing) {
    this.closing = true;
    if (this._req === undefined && this._queue.length) {
      this._handleClosing = true;
      this._handle.close();
    }
  }
};
Client.prototype.end = Client.prototype.close;

Client.prototype.escape = function(str) {
  if (!this.connected)
    throw new Error('Not connected');
  return this._handle.escape(str);
};

Client.prototype.prepare = function(query) {
  var cache = this._queryCache;
  var cqfn;
  if (cache && (cqfn = cache.get(query)))
    return cqfn;

  var ppos = RE_PARAM.exec(query),
      curpos = 0,
      start = 0,
      parts = [],
      inQuote = false,
      escape = false,
      tokens = [],
      qcnt = 0,
      qchr,
      chr,
      end,
      fn,
      i;

  if (ppos) {
    do {
      for (i = curpos, end = ppos.index; i < end; ++i) {
        chr = query.charCodeAt(i);
        if (chr === BSLASH)
          escape = !escape;
        else {
          if (escape) {
            escape = false;
            continue;
          }
          if (inQuote && chr === qchr) {
            if (query.charCodeAt(i + 1) === qchr) {
              // quote escaped via "" or ''
              ++i;
              continue;
            }
            inQuote = false;
          } else if (chr === DQUOTE || chr === SQUOTE) {
            inQuote = true;
            qchr = chr;
          }
        }
      }
      if (!inQuote) {
        parts.push(query.substring(start, end));
        tokens.push(ppos[0].length === 1 ? qcnt++ : ppos[1]);
        start = end + ppos[0].length;
      }
      curpos = end + ppos[0].length;
    } while (ppos = RE_PARAM.exec(query));

    if (tokens.length) {
      if (curpos < query.length)
        parts.push(query.substring(curpos));

      var self = this;
      fn = function(values) {
        var ret = '', j, val;
        for (j = 0; j < tokens.length; ++j) {
          ret += parts[j];
          ret += self._format_value(values[tokens[j]]);
        }
        if (j < parts.length)
          ret += parts[j];
        return ret;
      };
      var cache = this._queryCache;
      cache && cache.set(query, fn);
      return fn;
    }
  }
  return function() { return query; };
};

Client.prototype._format_value = function (v) {
  if (Buffer.isBuffer(v))
    return "'" + Client.escape(v.toString('utf8')) + "'";
  else if (Array.isArray(v)) {
    var r = [];
    for (var i = 0, len = v.length; i < len; ++i)
      r.push(this._format_value(v[i]));
    return r.join(',');
  } else if (v != null)
    return "'" + Client.escape(v + '') + "'";

  return 'NULL';
};

Client.escape = binding.escape;

var QueryStreamDefaultOpts = { objectMode: true };
function ResultEmitter(client, hwm) {
  EventEmitter.call(this);
  if (typeof hwm === 'number')
    this._streamOpts = { objectMode: true, highWaterMark: hwm };
  else
    this._streamOpts = QueryStreamDefaultOpts;
  this._client = client;
}
inherits(ResultEmitter, EventEmitter);

ResultEmitter.prototype._createStream = function() {
  return new QueryStream(this._client, this._streamOpts);
};

function QueryStream(client, opts) {
  ReadableStream.call(this, opts);
  this._client = client;
  this._needResume = false;
  this.metadata = undefined;
  this.affectedRows = undefined;
  this.numRows = undefined;
  this.insertId = undefined;
}
inherits(QueryStream, ReadableStream);
QueryStream.prototype._read = function(n) {
  if (this._needResume) {
    this._needResume = false;
    this._client.resume();
  }
};

function clone(obj) {
  var ret = {};
  var keys = Object.keys(obj);
  var key;
  for (var i = 0; i < keys.length; ++i) {
    key = keys[i];
    ret[key] = obj[key];
  }
  return ret;
}

function isDeadConn(code) {
  return code === 2006 || code === 2013 || code === 2055;
}

function createRowBuilder(cols) {
  var fn = 'return {';
  for (var i = 0; i < cols.length; ++i)
    fn += JSON.stringify(cols[i]) + ': v[' + i + '],';
  return new Function('v', fn + '}');
}

module.exports = Client;
<commit_msg>Client: fix implicit prepare()<commit_after>var EventEmitter = require('events').EventEmitter,
    inherits = require('util').inherits,
    lookup = require('dns').lookup,
    isIP = require('net').isIP,
    ReadableStream = require('stream').Readable;

var LRU = require('lru-cache');

var binding = require('../build/Release/sqlclient').Client;

var RE_PARAM = /(?:\?)|(?::(\d+|(?:[a-zA-Z][a-zA-Z0-9_]*)))/g,
    DQUOTE = 34,
    SQUOTE = 39,
    BSLASH = 92;

function Client(config) {
  if (!(this instanceof Client))
    return new Client(config);

  EventEmitter.call(this);

  this._handle = new binding({
    context: this,
    config: config,
    onconnect: this._onconnect,
    onerror: this._onerror,
    onidle: this._onidle,
    onresultinfo: this._onresultinfo,
    onrow: this._onrow,
    onresultend: this._onresultend,
    onping: this._onping,
    onclose: this._onclose,
  });
  if (typeof config === 'object' && config !== null)
    this._config = config;
  else
    this._config = {};
  this._req = undefined;
  this._queue = [];
  this._queryCache = undefined;
  this._handleClosing = false;
  this.connecting = false;
  this.connected = false;
  this.closing = false;
  this.threadId = undefined;

  // XXX: hack to get thread ID first before any other queries
  var self = this;
  this._firstQuery = {
    str: 'SELECT CONNECTION_ID()',
    cb: function(err, rows) {
      if (err) {
        self.emit('error', err);
        return;
      }
      self.threadId = rows[0][0];
      self.connecting = false;
      self.connected = true;
      self.emit('ready');
    },
    result: undefined,
    results: undefined,
    needMetadata: false,
    needColumns: false
  };
}
inherits(Client, EventEmitter);

Client.prototype._onconnect = function() {
  var queue = this._queue,
      firstQuery = this._firstQuery;
  if (queue.length === 0 || queue[0] !== firstQuery)
    queue.unshift(firstQuery);
  this._processQueue(true);
};
Client.prototype._onerror = function(err) {
  if (isDeadConn(err.code)) {
    this.connecting = false;
    this.connected = false;
    this.emit('error', err);
    this.close();
  } else {
    var req = this._req;
    if (req) {
      if (req.cb !== undefined) {
        var results = req.results;
        if (results === undefined)
          req.results = [err];
        else
          results.push(err);
      } else {
        var stream = req.stream;
        if (stream === undefined) {
          var emitter = req.emitter;
          stream = emitter._createStream();
          emitter.emit('result', stream);
          stream.emit('error', err);
          stream.push(null);
        } else {
          stream.emit('error', err);
          stream.push(null);
          req.stream = undefined;
        }
      }
    } else
      this.emit('error', err);
  }
};
Client.prototype._onidle = function() {
  var req = this._req;
  if (req) {
    // a query finished -- no more result sets
    this._queue.shift();
    this._req = undefined;
    var cb = req.cb;
    if (cb !== undefined) {
      var results = req.results;
      if (results.length === 1) {
        // single result set response

        var r = results[0];
        if (r instanceof Error)
          cb(r);
        else
          cb(undefined, r);
      } else {
        // multi-result set response

        // TODO: "undefined" here can be a bit misleading if 1 of several
        // results ended in error (can this even happen in reality?)
        cb(undefined, results);
      }
    } else
      req.emitter.emit('end');
  }
  this._processQueue(false);
};
Client.prototype._onresultinfo = function(cols, metadata) {
  var req = this._req;
  if (req.cb !== undefined) {
    if (req.needMetadata)
      req.metadata = metadata;
  } else {
    var emitter = req.emitter,
        stream = req.stream = emitter._createStream();
    if (req.needMetadata)
      stream.metadata = metadata;
    emitter.emit('result', stream);
  }
  if (req.needColumns)
    req.rowBuilder = createRowBuilder(cols);
};
Client.prototype._onrow = function(row) {
  var req = this._req,
      builder = req.rowBuilder;
  if (builder)
    row = builder(row);
  if (req.cb !== undefined) {
    var result = req.result;
    if (result === undefined)
      req.result = [row];
    else
      result.push(row);
  } else {
    var stream = req.stream;
    if (stream === undefined) {
      var emitter = req.emitter;
      stream = req.stream = emitter._createStream();
      emitter.emit('result', stream);
    }
    if (stream.push(row) === false) {
      this._handle.pause();
      stream._needResume = true;
    }
  }
};
Client.prototype._onresultend = function(numRows, affectedRows, insertId) {
  var req = this._req;
  if (req.cb !== undefined) {
    var results = req.results,
        result = req.result;
    if (result === undefined) {
      result = {
        numRows: numRows,
        affectedRows: affectedRows,
        insertId: insertId,
        metadata: req.metadata
      };
    } else {
      req.result = undefined;
      result.numRows = numRows;
      result.affectedRows = affectedRows;
      result.insertId = insertId;
      result.metadata = req.metadata;
    }
    req.metadata = undefined;
    if (results !== undefined)
      results.push(result);
    else
      req.results = [result];
  } else {
    var stream = req.stream;
    if (stream === undefined) {
      var emitter = req.emitter;
      stream = emitter._createStream();
      stream.numRows = numRows;
      stream.affectedRows = affectedRows;
      stream.insertId = insertId;
      emitter.emit('result', stream);
      stream.push(null);
    } else {
      stream.numRows = numRows;
      stream.affectedRows = affectedRows;
      stream.insertId = insertId;
      stream.push(null);
      req.stream = undefined;
    }
  }
};
Client.prototype._onping = function() {
  // TODO
};
Client.prototype._onclose = function() {
  this.connecting = false;
  this.connected = false;
  this.closing = false;
  this._handleClosing = false;
  this._req = undefined;
  this.emit('end');
  this.emit('close');
};

Client.prototype._processQueue = function(ignoreConnected) {
  var connected = this.connected;
  if (!ignoreConnected && !connected)
    return;
  var req = this._req;
  if (req)
    return;
  var queue = this._queue;
  if (queue.length) {
    req = this._req = queue[0];
    this._handle.query(req.str, req.needColumns, req.needMetadata);
  } else if (connected && this.closing && !this._handleClosing) {
    this._handleClosing = true;
    this._handle.close();
  }
};

Client.prototype.connect = function(config) {
  if (this.connecting || this.connected)
    return;

  if (typeof config === 'object' && config !== null)
    this._config = config;

  var cfg = this._config,
      self = this;
  if (typeof cfg !== 'object')
    throw new Error('Missing config');

  this.connecting = true;

  process.nextTick(function() {
    // doing a manual resolve prevents libmariadbclient from doing a blocking
    // DNS resolve
    if (!isIP(cfg.host)) {
      lookup(cfg.host, function(err, address, family) {
        if (err) {
          self.connecting = false;
          self.emit('error', err);
          return self.emit('close');
        }
        cfg = clone(cfg);
        cfg.host = address;
        self._handle.connect(cfg);
      });
    } else
      self._handle.connect(cfg);
  });
};

Client.prototype.isMariaDB = function() {
  return this._handle.isMariaDB();
};

Client.prototype.query = function(str, values, config, cb) {
  var req,
      ret;
  if (typeof str !== 'string')
    throw new Error('Missing query string');
  if (typeof values === 'function') {
    // query(str, cb)
    cb = values;
    values = config = undefined;
  } else if (typeof config === 'function') {
    // query(str, ___, cb)
    cb = config;
    if (typeof values === 'boolean') {
      config = values;
      values = undefined;
    } else
      config = undefined;
  } else if (typeof values === 'boolean') {
    // query(str, config)
    config = values;
    values = undefined;
  }

  if (Array.isArray(values) || (typeof values === 'object' && values !== null))
    query = this.prepare(str)(values);

  var needColumns = (!config ||
                     (typeof config === 'object'
                      && config !== null
                      && !config.useArray)),
      needMetadata = ((config && config.metadata === true)
                      || this._config.metadata === true);
  if (typeof cb === 'function') {
    // we are buffering all rows
    req = {
      cb: cb,
      result: undefined,
      results: undefined,
      metadata: undefined,

      str: str,
      needColumns: needColumns,
      needMetadata: needMetadata,
      rowBuilder: undefined
    };
  } else {
    // we are streaming all rows
    var hwm = (config && config.hwm) || this._config.streamHWM;
    req = {
      emitter: new ResultEmitter(this, hwm),
      stream: undefined,

      str: str,
      needColumns: needColumns,
      needMetadata: needMetadata,
      rowBuilder: undefined
    };
    ret = req.emitter;
  }
  this._queue.push(req);
  if (!this.connected)
    this.connect();
  else if (this._req === undefined)
    this._processQueue(false);
  return ret;
};

Client.prototype.close = function() {
  if (this.connected && !this.closing) {
    this.closing = true;
    if (this._req === undefined && this._queue.length) {
      this._handleClosing = true;
      this._handle.close();
    }
  }
};
Client.prototype.end = Client.prototype.close;

Client.prototype.escape = function(str) {
  if (!this.connected)
    throw new Error('Not connected');
  return this._handle.escape(str);
};

Client.prototype.prepare = function(query) {
  var cache = this._queryCache;
  var cqfn;
  if (cache && (cqfn = cache.get(query)))
    return cqfn;

  var ppos = RE_PARAM.exec(query),
      curpos = 0,
      start = 0,
      parts = [],
      inQuote = false,
      escape = false,
      tokens = [],
      qcnt = 0,
      qchr,
      chr,
      end,
      fn,
      i;

  if (ppos) {
    do {
      for (i = curpos, end = ppos.index; i < end; ++i) {
        chr = query.charCodeAt(i);
        if (chr === BSLASH)
          escape = !escape;
        else {
          if (escape) {
            escape = false;
            continue;
          }
          if (inQuote && chr === qchr) {
            if (query.charCodeAt(i + 1) === qchr) {
              // quote escaped via "" or ''
              ++i;
              continue;
            }
            inQuote = false;
          } else if (chr === DQUOTE || chr === SQUOTE) {
            inQuote = true;
            qchr = chr;
          }
        }
      }
      if (!inQuote) {
        parts.push(query.substring(start, end));
        tokens.push(ppos[0].length === 1 ? qcnt++ : ppos[1]);
        start = end + ppos[0].length;
      }
      curpos = end + ppos[0].length;
    } while (ppos = RE_PARAM.exec(query));

    if (tokens.length) {
      if (curpos < query.length)
        parts.push(query.substring(curpos));

      var self = this;
      fn = function(values) {
        var ret = '', j, val;
        for (j = 0; j < tokens.length; ++j) {
          ret += parts[j];
          ret += self._format_value(values[tokens[j]]);
        }
        if (j < parts.length)
          ret += parts[j];
        return ret;
      };
      var cache = this._queryCache;
      cache && cache.set(query, fn);
      return fn;
    }
  }
  return function() { return query; };
};

Client.prototype._format_value = function (v) {
  if (Buffer.isBuffer(v))
    return "'" + Client.escape(v.toString('utf8')) + "'";
  else if (Array.isArray(v)) {
    var r = [];
    for (var i = 0, len = v.length; i < len; ++i)
      r.push(this._format_value(v[i]));
    return r.join(',');
  } else if (v != null)
    return "'" + Client.escape(v + '') + "'";

  return 'NULL';
};

Client.escape = binding.escape;

var QueryStreamDefaultOpts = { objectMode: true };
function ResultEmitter(client, hwm) {
  EventEmitter.call(this);
  if (typeof hwm === 'number')
    this._streamOpts = { objectMode: true, highWaterMark: hwm };
  else
    this._streamOpts = QueryStreamDefaultOpts;
  this._client = client;
}
inherits(ResultEmitter, EventEmitter);

ResultEmitter.prototype._createStream = function() {
  return new QueryStream(this._client, this._streamOpts);
};

function QueryStream(client, opts) {
  ReadableStream.call(this, opts);
  this._client = client;
  this._needResume = false;
  this.metadata = undefined;
  this.affectedRows = undefined;
  this.numRows = undefined;
  this.insertId = undefined;
}
inherits(QueryStream, ReadableStream);
QueryStream.prototype._read = function(n) {
  if (this._needResume) {
    this._needResume = false;
    this._client.resume();
  }
};

function clone(obj) {
  var ret = {};
  var keys = Object.keys(obj);
  var key;
  for (var i = 0; i < keys.length; ++i) {
    key = keys[i];
    ret[key] = obj[key];
  }
  return ret;
}

function isDeadConn(code) {
  return code === 2006 || code === 2013 || code === 2055;
}

function createRowBuilder(cols) {
  var fn = 'return {';
  for (var i = 0; i < cols.length; ++i)
    fn += JSON.stringify(cols[i]) + ': v[' + i + '],';
  return new Function('v', fn + '}');
}

module.exports = Client;
<|endoftext|>


<commit_before>'use strict';

var def = require('./def'),
    Long = require('long'),
    colors = require('colors/safe'),
    expect = require('chai').expect;

function core (cxt) {
    cxt.setBase(10);
    var stash = {};

    def('\\', function () {
        this.io.parse('\\\n');
    }, cxt, { immediate: true });

    def('(', function () {
        this.io.parse('\\)');
    }, cxt, { immediate: true });

    def('.(', function () {
        var str = this.io.parse('\\)');
        this.log(str);
    }, cxt, { immediate: true });

// 6.1.0990 CR  “c-r” (CORE)
// ( -- )
// Cause subsequent output to appear at the beginning of the next line.
    def('cr', function () {
        cxt.log('\n');
    }, cxt);

// 6.1.2220 SPACE
    def('space', function () {
        cxt.log(' ');
    }, cxt);

// 6.1.2230 SPACES
    def('spaces', function () {
        cxt.log(' '.repeat(this.dpop()));
    }, cxt);

    def('bl', function () {
        this.dpush(32);
    }, cxt);

    def('.', function () {
        cxt.log(' ' + this.dpop());
    }, cxt);

    def('.s', function () {
        cxt.DS.forEach(function (e) {
            cxt.log(' ' + e);
        });
    }, cxt);

// 15.6.1.1280 DUMP (TOOLS)
// ( addr u -- )
// Display the contents of u consecutive addresses starting at addr.
// The format of the display is implementation dependent.
    def('dump', function () {
        var ilen = this.dpop();
        var addr = this.dpop();
        var i;
        for (i = 0; i < ilen; i++) {
            cxt.log(' ' + this.MEMi8[addr + i]);
        }
    }, cxt);

// 6.1.0895 CHAR “char” (CORE)
// ( “<spaces>name” -- char )
// Skip leading space delimiters. Parse name delimited by a space.
// Put the value of its first character onto the stack.
    def('char', function () {
        var w = this.io.word('\\s');
        this.dpush(w.charCodeAt(0));
    }, cxt);

// 6.1.0630 ?DUP “question-dupe” (CORE)
// ( x -- 0 | x x )
// Duplicate x if it is non-zero.

    def('?dup', function () {
        var t = this.dpop();
        if (t !== 0) {
            this.dpush(t);
        }
        this.dpush(t);
    }, cxt);

    def('hex', function () { this.setBase(16); }, cxt);

    def('decimal', function () { this.setBase(10); }, cxt);

// 6.1.0490 <# “less-number-sign”
// Initialize the pictured numeric output conversion process.
    def('<#', function () { // ( -- )
        this.picturedNumericOutputAddress = this.here + 128;
        this.picturedNumericOutputLength = 0;
    }, cxt);

// 6.1.0040 #>
    def('#>', function () {
        this.dpop();
        this.dpop();
        this.dpush(this.picturedNumericOutputAddress + 1);
        this.dpush(this.picturedNumericOutputLength);
    }, cxt);

// 6.1.1670 HOLD
    def('hold', function () {
        var char = this.dpop() >>> 0;
        this.MEMi8[this.picturedNumericOutputAddress] = char;
        this.picturedNumericOutputAddress -= 1;
        this.picturedNumericOutputLength += 1;
    }, cxt);

// 6.1.2210 SIGN
    def('sign', function () { // ( n -- )
        var t = this.dpop() | 0;
        if (t < 0) {
            this.MEMi8[this.picturedNumericOutputAddress] = 45;
            this.picturedNumericOutputAddress -= 1;
            this.picturedNumericOutputLength += 1;
        }
    }, cxt);

// 6.1.0030 # “number-sign”
// Divide ud1 by the number in BASE giving the quotient ud2 and the remainder n.
// (n is the least-significant digit of ud1.) Convert n to external form
// and add the resulting character to the beginning of the pictured numeric
// output string.
    def('#', function () { // ( ud1 -- ud2 )
        var t = this.dpop() >>> 0;
        var n = this.dpop() >>> 0;
        var dd = new Long(n, t, true);
        var base = this.getBase();

        var r1 = dd.mod(base);
        var char = r1.toInt();
        if (char < 10) {
            char += 48;
        } else {
            char += 55;
        }

        this.MEMi8[this.picturedNumericOutputAddress] = char;

        this.picturedNumericOutputAddress -= 1;
        this.picturedNumericOutputLength += 1;

        dd = dd.div(base);

        this.dpush(dd.low);
        this.dpush(dd.high);
    }, cxt);

// 6.1.0050 #S
    def('#s', function () {
        var t = this.dpop() >>> 0;
        var n = this.dpop() >>> 0;
        var dd = new Long(n, t, true);
        var base = this.getBase();

        do {
            var r1 = dd.mod(base);
            var char = r1.toInt();
            if (char < 10) {
                char += 48;
            } else {
                char += 55;
            }
            this.MEMi8[this.picturedNumericOutputAddress] = char;

            this.picturedNumericOutputAddress -= 1;
            this.picturedNumericOutputLength += 1;

            dd = dd.div(base, true);
        } while (!dd.eq(0));

        this.dpush(dd.low);
        this.dpush(dd.high);

    }, cxt);

// 6.1.0560 >IN
    def('>in', function () {
        this.dpush(this.toinAddr());
    }, cxt);

// 6.1.0570 >NUMBER
    def('>number', function () { }, cxt);

// 6.1.0670 ABORT
    def('abort', function () { }, cxt);

// 6.1.0680 ABORT"
    def('abort"', function () { }, cxt);

// 6.1.0695 ACCEPT
    def('accept', function () { }, cxt);

// 6.1.0750 BASE
    def('base', function () {
        this.dpush(this.baseAddr());
    }, cxt);

// 6.1.0980 COUNT
    def('count', function () {
        var addr = this.dpop();
        var len = this.MEMi8[addr];
        this.dpush(addr + 1);
        this.dpush(len);
    }, cxt);

// 6.1.1320 EMIT ( x -- )
    def('emit', function () {
        this.log(String.fromCharCode(this.dpop() >>> 0));
    }, cxt);

// 6.1.1360 EVALUATE ( i*x c-addr u -- j*x )
    def('evaluate', function () {
        var str = '';
        var len = this.dpop();
        var addr = this.dpop();

        // Save the current input source specification
        var buf = this.io.buf;
        var ptr = this.io.ptr;
        var last = this.io.last;

        // Make the string described by c-addr and u both the input
        // source and input buffer, set >IN to zero
        var i;
        for (i = 0; i < len; i++) {
            str += String.fromCharCode(this.MEMi8[addr + i]);
        }
        // , and interpret
        this.interpret(str);

        // When the parse area is empty, restore the prior input
        // source specification.
        this.io.buf = buf;
        this.io.ptr = ptr;
        this.io.last = last;
    }, cxt);

// 6.1.1380 EXIT
    def('exit', function () { }, cxt);

// 6.1.1540 FILL
    def('fill', function () { // ( c-addr u char -- )
        var char = this.dpop() >>> 0;
        var len = this.dpop() >>> 0;
        var addr = this.dpop() >>> 0;
        var i;
        for (i = 0; i < len; i++) {
            this.MEMi8[addr + i] = char;
        }
    }, cxt);

// 6.1.1550 FIND
    def('find', function () {
        'count'();
        var len = this.dpop();
        var addr = this.dpop();
        var str = '';
        var i;
        for (i = 0; i < len; i++) {
            str += String.fromCharCode(this.MEMi8[addr + i]);
        }
        str = str.toLowerCase();
        var wordLink = this.O[str];
        if (wordLink) {
            this.dpush(wordLink.index);
            if (wordLink.immediate) {
                this.dpush(1);
            } else {
                this.dpush(-1);
            }
        } else {
            this.dpush(addr);
            this.dpush(0);
        }
    }, cxt);

// 6.1.1750 KEY
    def('key', function () {
        this.dpush(42); // TODO
    }, cxt);

// 6.1.1900 MOVE
    def('move', function () { // ( addr1 addr2 u -- )
        var len = this.dpop() >>> 0;
        var dst = this.dpop() >>> 0;
        var src = this.dpop() >>> 0;
        var i;
        if (src > dst) {
            for (i = 0; i < len; i++) {
                this.MEMi8[dst + i] = this.MEMi8[src + i];
            }
        } else {
            for (i = len - 1; i >= 0; i--) {
                this.MEMi8[dst + i] = this.MEMi8[src + i];
            }
        }
    }, cxt);

// 6.1.2050 QUIT
    def('quit', function () { }, cxt);

// 6.1.2216 SOURCE
    def('source', function () {
        this.dpush(this.here);
        this.dpush(this.io.last - this.io.ptr);
    }, cxt);

// 6.1.2310 TYPE
    def('type', function () { // ( c-addr u -- )
        var len = this.dpop();
        var addr = this.dpop();
        var i, char;
        for (i = 0; i < len; i++) {
            char = this.MEMi8[addr + i];
            cxt.log(String.fromCharCode(char));
        }
    }, cxt);

// 6.1.2320 U.
    def('u.', function () {
        cxt.log(' ' + (this.dpop() >>> 0));
    }, cxt);

// 6.1.2450 WORD
    def('word', function () {
        var char = this.dpop();
        var w = this.io.word(String.fromCharCode(char));
        var i, ilen, here;
        here = this.here;
        for (i = 0, ilen = w.length; i < ilen; i++) {
            this.MEMi8[here + i] = w.charCodeAt(i);
        }
        this.MEMi8[here + ilen] = 0;
        this.here += (ilen + 1);
        this.dpush(here);
    }, cxt);

// tester

    def('testing', function () { this.io.parse('\\\n'); }, cxt);

    def('t{', function () { this.DS = []; }, cxt);

    def('->', function () {
        stash.DS = this.DS;
        this.DS = [];
    }, cxt);

    def('}t', function () {
        var lines, l0, l1;
        try {
            expect(stash.DS).deep.equal(this.DS);
        } catch (err) {
            lines = this.io.buf.toString().slice(0, this.io.ptr).split('\n');
            cxt.log(colors.red('Error:') + ' in line: ' + (lines.length - 1) + ' ');
            l0 = lines.pop();
            l1 = lines.pop();
            cxt.log(l1 + ' ' + l0 + ' ');
            cxt.log(err.message);
        }
        cxt.log('.\n');
    }, cxt);
}

module.exports = core;
<commit_msg> fixed #13 ; implemented ACCEPT stub<commit_after>'use strict';

var def = require('./def'),
    Long = require('long'),
    colors = require('colors/safe'),
    expect = require('chai').expect;

function core (cxt) {
    cxt.setBase(10);
    var stash = {};

    def('\\', function () {
        this.io.parse('\\\n');
    }, cxt, { immediate: true });

    def('(', function () {
        this.io.parse('\\)');
    }, cxt, { immediate: true });

    def('.(', function () {
        var str = this.io.parse('\\)');
        this.log(str);
    }, cxt, { immediate: true });

// 6.1.0990 CR  “c-r” (CORE)
// ( -- )
// Cause subsequent output to appear at the beginning of the next line.
    def('cr', function () {
        cxt.log('\n');
    }, cxt);

// 6.1.2220 SPACE
    def('space', function () {
        cxt.log(' ');
    }, cxt);

// 6.1.2230 SPACES
    def('spaces', function () {
        cxt.log(' '.repeat(this.dpop()));
    }, cxt);

    def('bl', function () {
        this.dpush(32);
    }, cxt);

    def('.', function () {
        cxt.log(' ' + this.dpop());
    }, cxt);

    def('.s', function () {
        cxt.DS.forEach(function (e) {
            cxt.log(' ' + e);
        });
    }, cxt);

// 15.6.1.1280 DUMP (TOOLS)
// ( addr u -- )
// Display the contents of u consecutive addresses starting at addr.
// The format of the display is implementation dependent.
    def('dump', function () {
        var ilen = this.dpop();
        var addr = this.dpop();
        var i;
        for (i = 0; i < ilen; i++) {
            cxt.log(' ' + this.MEMi8[addr + i]);
        }
    }, cxt);

// 6.1.0895 CHAR “char” (CORE)
// ( “<spaces>name” -- char )
// Skip leading space delimiters. Parse name delimited by a space.
// Put the value of its first character onto the stack.
    def('char', function () {
        var w = this.io.word('\\s');
        this.dpush(w.charCodeAt(0));
    }, cxt);

// 6.1.0630 ?DUP “question-dupe” (CORE)
// ( x -- 0 | x x )
// Duplicate x if it is non-zero.

    def('?dup', function () {
        var t = this.dpop();
        if (t !== 0) {
            this.dpush(t);
        }
        this.dpush(t);
    }, cxt);

    def('hex', function () { this.setBase(16); }, cxt);

    def('decimal', function () { this.setBase(10); }, cxt);

// 6.1.0490 <# “less-number-sign”
// Initialize the pictured numeric output conversion process.
    def('<#', function () { // ( -- )
        this.picturedNumericOutputAddress = this.here + 128;
        this.picturedNumericOutputLength = 0;
    }, cxt);

// 6.1.0040 #>
    def('#>', function () {
        this.dpop();
        this.dpop();
        this.dpush(this.picturedNumericOutputAddress + 1);
        this.dpush(this.picturedNumericOutputLength);
    }, cxt);

// 6.1.1670 HOLD
    def('hold', function () {
        var char = this.dpop() >>> 0;
        this.MEMi8[this.picturedNumericOutputAddress] = char;
        this.picturedNumericOutputAddress -= 1;
        this.picturedNumericOutputLength += 1;
    }, cxt);

// 6.1.2210 SIGN
    def('sign', function () { // ( n -- )
        var t = this.dpop() | 0;
        if (t < 0) {
            this.MEMi8[this.picturedNumericOutputAddress] = 45;
            this.picturedNumericOutputAddress -= 1;
            this.picturedNumericOutputLength += 1;
        }
    }, cxt);

// 6.1.0030 # “number-sign”
// Divide ud1 by the number in BASE giving the quotient ud2 and the remainder n.
// (n is the least-significant digit of ud1.) Convert n to external form
// and add the resulting character to the beginning of the pictured numeric
// output string.
    def('#', function () { // ( ud1 -- ud2 )
        var t = this.dpop() >>> 0;
        var n = this.dpop() >>> 0;
        var dd = new Long(n, t, true);
        var base = this.getBase();

        var r1 = dd.mod(base);
        var char = r1.toInt();
        if (char < 10) {
            char += 48;
        } else {
            char += 55;
        }

        this.MEMi8[this.picturedNumericOutputAddress] = char;

        this.picturedNumericOutputAddress -= 1;
        this.picturedNumericOutputLength += 1;

        dd = dd.div(base);

        this.dpush(dd.low);
        this.dpush(dd.high);
    }, cxt);

// 6.1.0050 #S
    def('#s', function () {
        var t = this.dpop() >>> 0;
        var n = this.dpop() >>> 0;
        var dd = new Long(n, t, true);
        var base = this.getBase();

        do {
            var r1 = dd.mod(base);
            var char = r1.toInt();
            if (char < 10) {
                char += 48;
            } else {
                char += 55;
            }
            this.MEMi8[this.picturedNumericOutputAddress] = char;

            this.picturedNumericOutputAddress -= 1;
            this.picturedNumericOutputLength += 1;

            dd = dd.div(base, true);
        } while (!dd.eq(0));

        this.dpush(dd.low);
        this.dpush(dd.high);

    }, cxt);

// 6.1.0560 >IN
    def('>in', function () {
        this.dpush(this.toinAddr());
    }, cxt);

// 6.1.0570 >NUMBER
    def('>number', function () { }, cxt);

// 6.1.0670 ABORT
    def('abort', function () { }, cxt);

// 6.1.0680 ABORT"
    def('abort"', function () { }, cxt);

// 6.1.0695 ACCEPT
    def('accept', function () { // ( c-addr +n 1 -- +n2 )
        // TODO implement real ASYNC version of terminal input
        var t = this.dpop();
    }, cxt);

// 6.1.0750 BASE
    def('base', function () {
        this.dpush(this.baseAddr());
    }, cxt);

// 6.1.0980 COUNT
    def('count', function () {
        var addr = this.dpop();
        var len = this.MEMi8[addr];
        this.dpush(addr + 1);
        this.dpush(len);
    }, cxt);

// 6.1.1320 EMIT ( x -- )
    def('emit', function () {
        this.log(String.fromCharCode(this.dpop() >>> 0));
    }, cxt);

// 6.1.1360 EVALUATE ( i*x c-addr u -- j*x )
    def('evaluate', function () {
        var str = '';
        var len = this.dpop();
        var addr = this.dpop();

        // Save the current input source specification
        var buf = this.io.buf;
        var ptr = this.io.ptr;
        var last = this.io.last;

        // Make the string described by c-addr and u both the input
        // source and input buffer, set >IN to zero
        var i;
        for (i = 0; i < len; i++) {
            str += String.fromCharCode(this.MEMi8[addr + i]);
        }
        // , and interpret
        this.interpret(str);

        // When the parse area is empty, restore the prior input
        // source specification.
        this.io.buf = buf;
        this.io.ptr = ptr;
        this.io.last = last;
    }, cxt);

// 6.1.1380 EXIT
    def('exit', function () { }, cxt);

// 6.1.1540 FILL
    def('fill', function () { // ( c-addr u char -- )
        var char = this.dpop() >>> 0;
        var len = this.dpop() >>> 0;
        var addr = this.dpop() >>> 0;
        var i;
        for (i = 0; i < len; i++) {
            this.MEMi8[addr + i] = char;
        }
    }, cxt);

// 6.1.1550 FIND
    def('find', function () {
        'count'();
        var len = this.dpop();
        var addr = this.dpop();
        var str = '';
        var i;
        for (i = 0; i < len; i++) {
            str += String.fromCharCode(this.MEMi8[addr + i]);
        }
        str = str.toLowerCase();
        var wordLink = this.O[str];
        if (wordLink) {
            this.dpush(wordLink.index);
            if (wordLink.immediate) {
                this.dpush(1);
            } else {
                this.dpush(-1);
            }
        } else {
            this.dpush(addr);
            this.dpush(0);
        }
    }, cxt);

// 6.1.1750 KEY
    def('key', function () {
        this.dpush(42); // TODO
    }, cxt);

// 6.1.1900 MOVE
    def('move', function () { // ( addr1 addr2 u -- )
        var len = this.dpop() >>> 0;
        var dst = this.dpop() >>> 0;
        var src = this.dpop() >>> 0;
        var i;
        if (src > dst) {
            for (i = 0; i < len; i++) {
                this.MEMi8[dst + i] = this.MEMi8[src + i];
            }
        } else {
            for (i = len - 1; i >= 0; i--) {
                this.MEMi8[dst + i] = this.MEMi8[src + i];
            }
        }
    }, cxt);

// 6.1.2050 QUIT
    def('quit', function () { }, cxt);

// 6.1.2216 SOURCE
    def('source', function () {
        this.dpush(this.here);
        this.dpush(this.io.last - this.io.ptr);
    }, cxt);

// 6.1.2310 TYPE
    def('type', function () { // ( c-addr u -- )
        var len = this.dpop();
        var addr = this.dpop();
        var i, char;
        for (i = 0; i < len; i++) {
            char = this.MEMi8[addr + i];
            cxt.log(String.fromCharCode(char));
        }
    }, cxt);

// 6.1.2320 U.
    def('u.', function () {
        cxt.log(' ' + (this.dpop() >>> 0));
    }, cxt);

// 6.1.2450 WORD
    def('word', function () {
        var char = this.dpop();
        var w = this.io.word(String.fromCharCode(char));
        var i, ilen, here;
        here = this.here;
        for (i = 0, ilen = w.length; i < ilen; i++) {
            this.MEMi8[here + i] = w.charCodeAt(i);
        }
        this.MEMi8[here + ilen] = 0;
        this.here += (ilen + 1);
        this.dpush(here);
    }, cxt);

// tester

    def('testing', function () { this.io.parse('\\\n'); }, cxt);

    def('t{', function () { this.DS = []; }, cxt);

    def('->', function () {
        stash.DS = this.DS;
        this.DS = [];
    }, cxt);

    def('}t', function () {
        var lines, l0, l1;
        try {
            expect(stash.DS).deep.equal(this.DS);
        } catch (err) {
            lines = this.io.buf.toString().slice(0, this.io.ptr).split('\n');
            cxt.log(colors.red('Error:') + ' in line: ' + (lines.length - 1) + ' ');
            l0 = lines.pop();
            l1 = lines.pop();
            cxt.log(l1 + ' ' + l0 + ' ');
            cxt.log(err.message);
        }
        cxt.log('.\n');
    }, cxt);
}

module.exports = core;
<|endoftext|>


<commit_before>'use strict';

var async = require('async'),
  events = require('events'),
  util = require('util'),
  Config = require('./Config'),
  DataStore = require('./ds/DataStore'),
  Tenant = require('./resource/Tenant');

/**
 * Creates a new Client.
 *
 * @class
 */
function Client(config) {
  var self = this;

  // Call the constructor of the EventEmitter class -- this, allows us to
  // initialize our Client object as an EventEmitter, and allows us to fire off
  // events later on.
  events.EventEmitter.call(self);

  // Bind the user-supplied config property to our class so it can be accessed
  // like an object.
  self.config = new Config(config);

  // For backwards compatibility reasons, if no API key is specified we'll try
  // to grab the API credentials out of our new format and shove it into the old
  // format.  This can go away once we cut a release and decide to no longer
  // support the old configuration formatting.
  if (!self.config.apiKey) {
    self.config.apiKey = self.config.client.apiKey;
  }

  self._dataStore = new DataStore(self.config);

  // We'll maintain this class variable as an in-memory singleton for caching
  // purposes.  We do this because Tenants never ever change once a Client has
  // been initialized, so it makes sense to cache the Tenant object so we don't
  // make unnecessary API requests if this object is looked up more than once.
  self._currentTenant = null;

  self.mergeRemoteConfig(function(err) {
    return err ? self.emit('error', err): self.emit('ready');
  });
}

// By inheriting from `events.EventEmitter`, we're making our Client object a
// true EventEmitter -- allowing us to fire off events.
util.inherits(Client, Object);
util.inherits(Client, events.EventEmitter);

Client.prototype.mergeRemoteConfig = function(callback) {
  var self = this;
  var config = self.config;
  var application = config.application;

  function findApplication(name, cb) {
    self.getApplications({ name: application.name }, function(err, applications) {
      if (err) {
        return cb(err);
      }

      applications.detect(function(app, cb) {
        cb(application.name === app.name);
      }, function(app) {
        if (app) {
          self.config.application.href = app.href;
          return cb(null, app);
        }

        return cb(new Error('Application not found: "' + application.name + '"'));
      });
    });
  }

  function findDefaultDirectoryHref(application, cb) {
    application.getAccountStoreMappings(function(err, mappings) {
      if (err) {
        return cb(err);
      }
      mappings.detect(function(mapping,cb){
        cb(mapping.isDefaultAccountStore);
      },function(defaultMapping){
        if(defaultMapping){
          var href = defaultMapping.accountStore.href;
          if(href.match(/group/)){
            self.getGroup(href,function(err,group){
              cb(err,group &&group.directory.href);
            });
          }else{
            cb(null,href);
          }
        }else{
          cb(null,null);
        }
      });
    });
  }

  function applyConfig(directoryHref, cb) {
    if(!directoryHref){
      callback(null);
    }else{
      self.getDirectory(
        directoryHref,
        {expand:'passwordPolicy,accountCreationPolicy'},
        function(err,directory){
          if(err){
            cb(err);
          }else{
            self.config.web.verifyEmail.enabled =
              directory.accountCreationPolicy.verificationEmailStatus === 'ENABLED';
            self.config.web.changePassword.enabled = self.config.web.forgotPassword.enabled =
              directory.passwordPolicy.resetEmailStatus === 'ENABLED';
            cb(null);
          }
        }
      );
    }
  }

  if (application && application.href) {
    async.waterfall([
      self.getApplication.bind(self, application.href),
      findDefaultDirectoryHref,
      applyConfig
    ], callback);
  } else if (application && application.name) {
    async.waterfall([
      findApplication(application.name),
      findDefaultDirectoryHref,
      applyConfig
    ], callback);
  } else {
    process.nextTick(callback);
  }

};

/**
 * @callback getCurrentTenantCallback
 * @param {Error} err - The error (if there is one).
 * @param {Object} tenant - The retrieved Tenant object.
 */

/**
 * Retrieves the current Tenant object.
 *
 * @method getCurrentTenant
 * @param {Object} [options] - Options.
 * @param {getCurrentTenantCallback} callback - The callback that handles the
 *  response.
 */
Client.prototype.getCurrentTenant = function() {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;

  // First, we'll check to see if we've already cached the current tenant (since
  // a Tenant never changes once a Client has been initialized).  This prevents
  // us from making unnecessary repeat API requests if this method is called
  // multiple times.
  if (self._currentTenant) {
    return callback(null, self._currentTenant);
  }

  self._dataStore.getResource('/tenants/current', options, Tenant, function(err, tenant) {
    if (err) {
      return callback(err);
    }

    self._currentTenant = tenant;
    return callback(null, tenant);
  });
};

/**
 * @callback getResourceCallback
 * @param {Error} err - The error (if there is one).
 * @param {Object} resource - The retrieved resource object.
 */

/**
 * Retrieves a resource object by href.
 *
 * @method getResource
 * @param {String} href - The URI of the resource.
 * @param {Object} [query] - Name / value pairs to use as query parameters.
 * @param {Function} [constructor] - The constructor function that will be
 *  invoked when the given resource is retrieved.  EG: Account, Directory,
 *  Group, etc.  Defaults to `InstanceResource`.  If a resource returned from
 *  the API is a collection (not a single resource object), then each returned
 *  object in the `items` array will be passed into this constructor function
 *  and initialized.
 * @param {getResourceCallback} callback - The callback that handles the
 *  resposne.
 */
Client.prototype.getResource = function() {
  return this._dataStore.getResource.apply(this._dataStore, arguments);
};

/**
 * Creates a new resource on the server as a child of the specified `parentHref` location.  `parentHref` must be a collection resource endpoint.
 *
 * @param {String} parentHref the URI of the parent collection under which the new resource will be created as a child - must always be the first argument.
 * @param {Object} query (optional, defaults to `undefined`) name/value pairs to use as query parameters to `parentHref`
 * @param {Object} data the resource (name/value pairs) to send to the server.
 * @param {Function} instanceCtor (optional, defaults to `InstanceResource`)  The Constructor function to invoke for any instance resource returned by the server.  If the request result is a single resource, this function is used to construct the resource.  If the request result is a collection, the `instanceCtor` is used to construct each resource in the collection's `items` field.
 * @param {Function} callback the callback function to invoke with the constructed Resource. The callback's first argument is an `Error` object, the second is the constructed resource. Must always be the last argument.
 * @return {void} result is provided to `callback`.
 * @method createResource
 * @version 0.1
 */
Client.prototype.createResource = function createResource(/* parentHref, [query,] data, [InstanceConstructor,] callback */) {
  this._dataStore.createResource.apply(this._dataStore, arguments);
};

//convenience methods:

Client.prototype.getApplications = function getClientApplications(/* [options,] callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;
  return self.getCurrentTenant(function onGetCurrentTenantForApplications(err, tenant) {
    if (err) {
      return callback(err, null);
    }
    return tenant.getApplications(options, callback);
  });
};

Client.prototype.createApplication = function createClientApplication(/* app, [options,] callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var app = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;
  return self.getCurrentTenant(function onGetCurrentTenantToCreateApplication(err, tenant) {
    if (err) {
      return callback(err, null);
    }
    return tenant.createApplication(app, options, callback);
  });
};

Client.prototype.getDirectories = function getClientDirectories(/* [options,] callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;
  return self.getCurrentTenant(function onGetCurrentTenantForDirectories(err, tenant) {
    if (err) {
      return callback(err, null);
    }
    return tenant.getDirectories(options, callback);
  });
};

Client.prototype.createDirectory = function createClientDirectory(/* dir, [options,] callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var dir = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;
  return self.getCurrentTenant(function onGetCurrentTenantToCreateDirectory(err, tenant) {
    if (err) {
      return callback(err, null);
    }
    return tenant.createDirectory(dir, options, callback);
  });
};

// Individual resource type convenience methods:
Client.prototype.getAccount = function getClientAccount(/* href, [options,], callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var href = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;

  return self.getResource(href, options, require('./resource/Account'), callback);
};
Client.prototype.getApplication = function getClientApplication(/* href, [options,], callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var href = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;

  return self.getResource(href, options, require('./resource/Application'), callback);
};
Client.prototype.getDirectory = function getClientDirectory(/* href, [options,], callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var href = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;

  return self.getResource(href, options, require('./resource/Directory'), callback);
};
Client.prototype.getGroup = function getClientGroup(/* href, [options,], callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var href = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;

  return self.getResource(href, options, require('./resource/Group'), callback);
};
Client.prototype.getGroupMembership = function getClientGroupMembership(/* href, [options,], callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var href = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;

  return self.getResource(href, options, require('./resource/GroupMembership'), callback);
};

module.exports = Client;
<commit_msg>Refactoring Client.getApplications().<commit_after>'use strict';

var async = require('async'),
  events = require('events'),
  util = require('util'),
  Config = require('./Config'),
  DataStore = require('./ds/DataStore'),
  Tenant = require('./resource/Tenant');

/**
 * Creates a new Client.
 *
 * @class
 */
function Client(config) {
  var self = this;

  // Call the constructor of the EventEmitter class -- this, allows us to
  // initialize our Client object as an EventEmitter, and allows us to fire off
  // events later on.
  events.EventEmitter.call(self);

  // Bind the user-supplied config property to our class so it can be accessed
  // like an object.
  self.config = new Config(config);

  // For backwards compatibility reasons, if no API key is specified we'll try
  // to grab the API credentials out of our new format and shove it into the old
  // format.  This can go away once we cut a release and decide to no longer
  // support the old configuration formatting.
  if (!self.config.apiKey) {
    self.config.apiKey = self.config.client.apiKey;
  }

  self._dataStore = new DataStore(self.config);

  // We'll maintain this class variable as an in-memory singleton for caching
  // purposes.  We do this because Tenants never ever change once a Client has
  // been initialized, so it makes sense to cache the Tenant object so we don't
  // make unnecessary API requests if this object is looked up more than once.
  self._currentTenant = null;

  self.mergeRemoteConfig(function(err) {
    return err ? self.emit('error', err): self.emit('ready');
  });
}

// By inheriting from `events.EventEmitter`, we're making our Client object a
// true EventEmitter -- allowing us to fire off events.
util.inherits(Client, Object);
util.inherits(Client, events.EventEmitter);

Client.prototype.mergeRemoteConfig = function(callback) {
  var self = this;
  var config = self.config;
  var application = config.application;

  function findApplication(name, cb) {
    self.getApplications({ name: application.name }, function(err, applications) {
      if (err) {
        return cb(err);
      }

      applications.detect(function(app, cb) {
        cb(application.name === app.name);
      }, function(app) {
        if (app) {
          self.config.application.href = app.href;
          return cb(null, app);
        }

        return cb(new Error('Application not found: "' + application.name + '"'));
      });
    });
  }

  function findDefaultDirectoryHref(application, cb) {
    application.getAccountStoreMappings(function(err, mappings) {
      if (err) {
        return cb(err);
      }
      mappings.detect(function(mapping,cb){
        cb(mapping.isDefaultAccountStore);
      },function(defaultMapping){
        if(defaultMapping){
          var href = defaultMapping.accountStore.href;
          if(href.match(/group/)){
            self.getGroup(href,function(err,group){
              cb(err,group &&group.directory.href);
            });
          }else{
            cb(null,href);
          }
        }else{
          cb(null,null);
        }
      });
    });
  }

  function applyConfig(directoryHref, cb) {
    if(!directoryHref){
      callback(null);
    }else{
      self.getDirectory(
        directoryHref,
        {expand:'passwordPolicy,accountCreationPolicy'},
        function(err,directory){
          if(err){
            cb(err);
          }else{
            self.config.web.verifyEmail.enabled =
              directory.accountCreationPolicy.verificationEmailStatus === 'ENABLED';
            self.config.web.changePassword.enabled = self.config.web.forgotPassword.enabled =
              directory.passwordPolicy.resetEmailStatus === 'ENABLED';
            cb(null);
          }
        }
      );
    }
  }

  if (application && application.href) {
    async.waterfall([
      self.getApplication.bind(self, application.href),
      findDefaultDirectoryHref,
      applyConfig
    ], callback);
  } else if (application && application.name) {
    async.waterfall([
      findApplication(application.name),
      findDefaultDirectoryHref,
      applyConfig
    ], callback);
  } else {
    process.nextTick(callback);
  }

};

/**
 * @callback getCurrentTenantCallback
 * @param {Error} err - The error (if there is one).
 * @param {Object} tenant - The retrieved Tenant object.
 */

/**
 * Retrieves the current Tenant object.
 *
 * @method getCurrentTenant
 * @param {Object} [options] - Options.
 * @param {getCurrentTenantCallback} callback - The callback that handles the
 *  response.
 */
Client.prototype.getCurrentTenant = function() {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;

  // First, we'll check to see if we've already cached the current tenant (since
  // a Tenant never changes once a Client has been initialized).  This prevents
  // us from making unnecessary repeat API requests if this method is called
  // multiple times.
  if (self._currentTenant) {
    return callback(null, self._currentTenant);
  }

  self._dataStore.getResource('/tenants/current', options, Tenant, function(err, tenant) {
    if (err) {
      return callback(err);
    }

    self._currentTenant = tenant;
    return callback(null, tenant);
  });
};

/**
 * @callback getResourceCallback
 * @param {Error} err - The error (if there is one).
 * @param {Object} resource - The retrieved resource object.
 */

/**
 * Retrieves a resource object by href.
 *
 * @method getResource
 * @param {String} href - The URI of the resource.
 * @param {Object} [query] - Name / value pairs to use as query parameters.
 * @param {Function} [constructor] - The constructor function that will be
 *  invoked when the given resource is retrieved.  EG: Account, Directory,
 *  Group, etc.  Defaults to `InstanceResource`.  If a resource returned from
 *  the API is a collection (not a single resource object), then each returned
 *  object in the `items` array will be passed into this constructor function
 *  and initialized.
 * @param {getResourceCallback} callback - The callback that handles the
 *  resposne.
 */
Client.prototype.getResource = function() {
  return this._dataStore.getResource.apply(this._dataStore, arguments);
};

/**
 * Creates a new resource on the server as a child of the specified `parentHref` location.  `parentHref` must be a collection resource endpoint.
 *
 * @param {String} parentHref the URI of the parent collection under which the new resource will be created as a child - must always be the first argument.
 * @param {Object} query (optional, defaults to `undefined`) name/value pairs to use as query parameters to `parentHref`
 * @param {Object} data the resource (name/value pairs) to send to the server.
 * @param {Function} instanceCtor (optional, defaults to `InstanceResource`)  The Constructor function to invoke for any instance resource returned by the server.  If the request result is a single resource, this function is used to construct the resource.  If the request result is a collection, the `instanceCtor` is used to construct each resource in the collection's `items` field.
 * @param {Function} callback the callback function to invoke with the constructed Resource. The callback's first argument is an `Error` object, the second is the constructed resource. Must always be the last argument.
 * @return {void} result is provided to `callback`.
 * @method createResource
 * @version 0.1
 */
Client.prototype.createResource = function createResource(/* parentHref, [query,] data, [InstanceConstructor,] callback */) {
  this._dataStore.createResource.apply(this._dataStore, arguments);
};

/**
 * @callback getApplicationsCallback
 * @param {Error} err - The error (if there is one).
 * @param {Object} applications - The retrieved Application objects.
 */

/**
 * Retrieves all Application objects.
 *
 * @method
 * @param {Object} [options] - Options.
 * @param {getApplicationsCallback} callback - The callback that handles the
 *  response.
 */
Client.prototype.getApplications = function() {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;

  self.getCurrentTenant(function(err, tenant) {
    if (err) {
      return callback(err);
    }

    return tenant.getApplications(options, callback);
  });
};

Client.prototype.createApplication = function createClientApplication(/* app, [options,] callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var app = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;
  return self.getCurrentTenant(function onGetCurrentTenantToCreateApplication(err, tenant) {
    if (err) {
      return callback(err, null);
    }
    return tenant.createApplication(app, options, callback);
  });
};

Client.prototype.getDirectories = function getClientDirectories(/* [options,] callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;
  return self.getCurrentTenant(function onGetCurrentTenantForDirectories(err, tenant) {
    if (err) {
      return callback(err, null);
    }
    return tenant.getDirectories(options, callback);
  });
};

Client.prototype.createDirectory = function createClientDirectory(/* dir, [options,] callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var dir = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;
  return self.getCurrentTenant(function onGetCurrentTenantToCreateDirectory(err, tenant) {
    if (err) {
      return callback(err, null);
    }
    return tenant.createDirectory(dir, options, callback);
  });
};

// Individual resource type convenience methods:
Client.prototype.getAccount = function getClientAccount(/* href, [options,], callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var href = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;

  return self.getResource(href, options, require('./resource/Account'), callback);
};
Client.prototype.getApplication = function getClientApplication(/* href, [options,], callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var href = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;

  return self.getResource(href, options, require('./resource/Application'), callback);
};
Client.prototype.getDirectory = function getClientDirectory(/* href, [options,], callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var href = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;

  return self.getResource(href, options, require('./resource/Directory'), callback);
};
Client.prototype.getGroup = function getClientGroup(/* href, [options,], callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var href = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;

  return self.getResource(href, options, require('./resource/Group'), callback);
};
Client.prototype.getGroupMembership = function getClientGroupMembership(/* href, [options,], callback */) {
  var self = this;
  var args = Array.prototype.slice.call(arguments);
  var href = args.shift();
  var callback = args.pop();
  var options = (args.length > 0) ? args.shift() : null;

  return self.getResource(href, options, require('./resource/GroupMembership'), callback);
};

module.exports = Client;
<|endoftext|>


<commit_before>import events from 'events'

// Local modules.
import memoryAdapter from './adapter/adapters/memory'
import AdapterSingleton from './adapter/singleton'
import SerializerSingleton from './serializer/singleton'
import defineArguments from './common/define_arguments'
import validate from './record_type/validate'
import ensureTypes from './record_type/ensure_types'
import dispatch, * as middlewares from './dispatch'

// Static exports.
import Adapter from './adapter'
import Serializer from './serializer'
import * as errors from './common/errors'
import * as methods from './common/methods'
import change from './common/change'

/**
 * This is the default export of the `fortune` module. The Fortune class
 * subclasses the built-in `EventEmitter` class, and it has a few static
 * properties attached to it that may be useful to access:
 *
 * - `Adapter`: abstract base class for the Adapter.
 * - `adapters`: included adapters, defaults to memory adapter. Note that the
 * browser build also includes `indexedDB` and `webStorage` adapters.
 * - `Serializer`: abstract base class for the Serializer.
 * - `serializers`: included serializers, defaulting to *ad hoc* HTTP.
 * Server-side only.
 * - `net`: network protocol helpers, currently HTTP and WebSocket.
 * Server-side only.
 * - `errors`: custom typed errors, useful for throwing errors in transform
 * functions.
 * - `methods`: a hash that maps strings to symbols. Available are: `find`,
 * `create`, `update`, and `delete`.
 * - `change`: this is the symbol for the event that is emitted when a change
 * is done. The callback function receives an object keyed by method symbols.
 *
 * Note: in the browser version, `serializers` and `net` are omitted.
 */
export default class Fortune extends events.EventEmitter {

  /**
   * Create a new instance. The options object may be as follows:
   *
   * ```js
   * {
   *   // Adapter configuration. Default: `memoryAdapter`
   *   adapter: {
   *     // Must be a class that extends `Fortune.Adapter`, or a function
   *     // that accepts the Adapter class and returns a subclass. Required.
   *     type: Adapter => { ... },
   *
   *     // An options object that is specific to the adapter. Optional.
   *     options: { ... }
   *   },
   *
   *   // Serializers ordered by priority. Default: ad hoc JSON-over-HTTP.
   *   serializers: [{
   *     // Must be a class that extends `Fortune.Serializer`, or a function
   *     // that accepts the Serializer class and returns a subclass. Required.
   *     type: Serializer => { ... },
   *
   *     // An options object that is specific to the serializer. Optional.
   *     options: { ... }
   *   }],
   *
   *   // Whether or not to enforce referential integrity. Default: `true` for
   *   // server, `false` for browser.
   *   enforceLinks: true
   * }
   * ```
   *
   * @param {Object} [options]
   * @return {Fortune}
   */
  constructor (options = {}) {
    if (!('adapter' in options)) options.adapter = { type: memoryAdapter }
    if (!('enforceLinks' in options)) options.enforceLinks = true

    super()

    const [ recordTypes, transforms ] = [ {}, {} ]

    const flows = Object.keys(methods).reduce((hash, method) =>
      Object.assign(hash, {
        [methods[method]]: [ middlewares[method],
          middlewares.include, middlewares.end ]
          .map(fn => fn.bind(this))
      }), {})

    /*!
     * Adapter singleton that is coupled to the Fortune instance.
     *
     * @type {Adapter}
     */
    this.adapter = new AdapterSingleton({
      adapter: options.adapter, recordTypes
    })

    /*!
     * Serializer singleton that is coupled to the Fortune instance.
     *
     * @type {Serializer}
     */
    this.serializer = new SerializerSingleton({
      serializers: options.serializers, adapter: this.adapter,
      recordTypes
    })

    Object.defineProperties(this, {

      // 0 = not started, 1 = started, 2 = done.
      connectionStatus: { value: 0, writable: true },

      // Used for method chaining.
      currentType: { value: null, writable: true },

      // Configuration settings.
      options: { value: options },
      transforms: { value: transforms },
      recordTypes: { value: recordTypes },

      // Singleton instances.
      adapter: { value: this.adapter },
      serializer: { value: this.serializer },

      // Dispatch.
      flows: { value: flows }
    })
  }

  /**
   * This method should be called when all setup is done. After this method
   * succeeds, record types can not be defined. The resolved value
   * is the instance of Fortune.
   *
   * @return {Promise}
   */
  connect () {
    if (this.connectionStatus === 1)
      throw new Error(`Connection is in progress.`)

    else if (this.connectionStatus === 2)
      throw new Error(`Connection is already done.`)

    this.connectionStatus = 1

    return new Promise((resolve, reject) => {
      ensureTypes(this.recordTypes)

      this.adapter.connect().then(() => {
        this.connectionStatus = 2
        return resolve(this)
      }, error => {
        this.connectionStatus = 0
        return reject(error)
      })
    })
  }

  /**
   * Close adapter connection, and reset state. The resolved value is the
   * instance of Fortune.
   *
   * @return {Promise}
   */
  disconnect () {
    if (this.connectionStatus !== 2)
      throw new Error(`Instance has not been connected.`)

    this.connectionStatus = 1

    return new Promise((resolve, reject) =>
      this.adapter.disconnect().then(() => {
        this.connectionStatus = 0
        return resolve(this)
      }, error => {
        this.connectionStatus = 2
        return reject(error)
      }))
  }

  /**
   * Define a record type given a name and a set of field definitions. The
   * `fields` object only serves to enforce data types, and may be extended by
   * the specific `Adapter` to express more, such as validations, uniqueness,
   * indexing, etc. Here are some example field definitions:
   *
   * ```js
   * {
   *   // A singular value.
   *   name: { type: String },
   *
   *   // An array containing values of a single type.
   *   luckyNumbers: { type: Number, isArray: true },
   *
   *   // Creates a to-many link to `animal` record type. If the field `owner`
   *   // on the `animal` record type is not an array, this is a many-to-one
   *   // relationship, otherwise it is many-to-many.
   *   pets: { link: 'animal', isArray: true, inverse: 'owner' },
   *
   *   // The `min` and `max` keys are open to interpretation by the specific
   *   // adapter or serializer, which may introspect the field definition.
   *   thing: { type: Number, min: 0, max: 100 },
   *
   *   // Nested field definitions are invalid. Use `Object` type instead.
   *   nested: { thing: { ... } } // Will throw an error.
   * }
   * ```
   *
   * The allowed native types are `String`, `Number`, `Boolean`, `Date`,
   * `Object`, and `Buffer`. Note that the `Object` type should be a JSON
   * serializable object that may be persisted. The only other allowed type is
   * a `Symbol`, which may be used to represent custom types.
   *
   * @param {String} name - Name of the record type.
   * @param {Object} fields - A hash of field definition objects.
   * @return {Fortune}
   */
  defineType (name, fields) {
    const { recordTypes } = this

    if (this.connectionStatus !== 0)
      throw new Error(`Cannot define new record types after connection.`)

    if (typeof name !== 'string')
      throw new TypeError(`Name must be a string.`)

    if (!name.length)
      throw new Error(`Name must be non-trivial.`)

    if (name in recordTypes)
      throw new Error(`The record type "${name}" was already defined.`)

    if (typeof fields !== 'object')
      throw new TypeError(`Fields for "${name}" must be an object.`)

    recordTypes[name] = validate(fields)

    // Memoize the current name, for chaining methods.
    this.currentType = name

    return this
  }

  /**
   * This is the primary method for initiating a request. The options object
   * must be formatted as follows:
   *
   * ```js
   * {
   *   // The method is a either a function or a Symbol, keyed under
   *   // `methods` and may be one of `find`, `create`, `update`,  or `delete`.
   *   // To implement a custom method, pass a function that accepts one
   *   // argument, the context. It may return the context synchronously or as
   *   // a Promise. Default: `methods.find`.
   *   method: methods.find,
   *
   *   type: undefined, // Name of a type. Optional.
   *   ids: undefined, // An array of IDs. Optional.
   *
   *   // A 2-dimensional array specifying links to include. The first
   *   // dimension is a list, the second dimension is depth. For example:
   *   // [['comments'], ['comments', 'author']]
   *   include: [],
   *
   *   // Exactly the same as the adapter's `find` method options. The options
   *   // apply only to the primary type on `find` requests.
   *   options: { ... },
   *
   *   // Same as `options`, but is an object keyed by type. This is only
   *   // used in conjunction with the `include` option.
   *   includeOptions: { [type]: { ... } },
   *
   *   // The ID of the serializer to use for the input (request). Optional.
   *   serializerInput: undefined,
   *
   *   // The ID of the serializer to use for the output (response). Optional.
   *   serializerOutput: undefined,
   *
   *   meta: { ... }, // Meta-info of the request.
   *   payload: undefined // Payload of the request.
   * }
   * ```
   *
   * The response object looks much simpler:
   *
   * ```js
   * {
   *   meta: { ... }, // Meta-info of the response.
   *   payload: undefined // Payload of the response.
   * }
   * ```
   *
   * The resolved response object should always be typed.
   *
   * @param {Object} options
   * @param {...*} [args]
   * @return {Promise}
   */
  request () {
    if (this.connectionStatus !== 2)
      throw new Error(`Instance must be connected before making a request.`)

    return dispatch.apply(this, arguments)
  }

  /**
   * Define a transformation per record type.
   *
   * A transform function takes at least two arguments, the internal `context`
   * object and a single `record`. A special case is the `update` argument for
   * the `update` method.
   *
   * There are two kinds of transforms, before a record is written to transform
   * input, and after it is read to transform output, both are optional. If an
   * error occurs within an transform function, it will be forwarded to the
   * response. Use typed errors to provide the appropriate feedback. It is
   * important to note that `output` transforms are run every time a record is
   * shown in a response, so it should be idempotent.
   *
   * For a create request, the input transform must return the second argument
   * `record` either synchronously, or asynchronously as a Promise. The return
   * value of a delete request is inconsequential, but it may return a value or
   * a Promise. There is a special case of the `update` method accepting a
   * `update` object as a third parameter, which must be returned synchronously
   * or as a Promise.
   *
   * An example transform to apply a timestamp on a record before creation,
   * and displaying the timestamp in the server's locale:
   *
   * ```js
   * const { methods: {
   *   create: createMethod, update: updateMethod
   * } } = fortune
   *
   * store.transform((context, record, update) => {
   *   const { request: { method } } = context
   *
   *   if (method === createMethod) {
   *     record.timestamp = new Date()
   *     return record
   *   }
   *
   *   if (method === updateMethod) return update
   *
   *   // If we get here, return value of the delete method doesn't matter.
   *   return null
   * }, (context, record) => {
   *   record.timestamp = record.timestamp.toLocaleString()
   *   return record
   * })
   * ```
   *
   * Requests to update a record will have the updates already applied to the
   * record.
   *
   * @param {String} [type]
   * @param {Function} [input]
   * @param {Function} [output]
   * @return {Fortune}
   */
  transform (type, input, output) {
    const { recordTypes, transforms } = this

    if (arguments.length < 3) {
      input = arguments[0]
      output = arguments[1]
      type = this.currentType
    }

    if (![ input, output ].some(fn => typeof fn === 'function'))
      throw new TypeError(`A function must be passed to transform.`)

    if (!(type in recordTypes))
      throw new Error(`Attempted to define transform on "${type}" ` +
        `type which does not exist.`)

    if (!(type in transforms))
      transforms[type] = {}

    if (typeof input === 'function')
      transforms[type].input = input

    if (typeof output === 'function')
      transforms[type].output = output

    return this
  }

  /**
   * Convenience method to define only the `input` argument of a transform.
   *
   * @param {String} [type]
   * @param {Function} fn
   * @return {Fortune}
   */
  transformInput (type, fn) {
    if (arguments.length < 2) {
      fn = type
      type = this.currentType
    }

    return this.transform(type, fn, null)
  }

  /**
   * Convenience method to define only the `output` argument of a transform.
   *
   * @param {String} [type]
   * @param {Function} fn
   * @return {Fortune}
   */
  transformOutput (type, fn) {
    if (arguments.length < 2) {
      fn = type
      type = this.currentType
    }

    return this.transform(type, null, fn)
  }

  /**
   * This is a static method on the Fortune class that is an alternative to the
   * constructor method, for people who hate the `new` keyword with a passion.
   * This is exactly the same as invoking `new Fortune(options)`.
   *
   * @param {Object} [options]
   * @return {Fortune}
   */
  static create () {
    return new Fortune(...arguments)
  }

}

// Assign useful static properties to the default export.
defineArguments(Fortune, { Adapter, Serializer, errors, methods, change })
<commit_msg>use const instead of this<commit_after>import events from 'events'

// Local modules.
import memoryAdapter from './adapter/adapters/memory'
import AdapterSingleton from './adapter/singleton'
import SerializerSingleton from './serializer/singleton'
import defineArguments from './common/define_arguments'
import validate from './record_type/validate'
import ensureTypes from './record_type/ensure_types'
import dispatch, * as middlewares from './dispatch'

// Static exports.
import Adapter from './adapter'
import Serializer from './serializer'
import * as errors from './common/errors'
import * as methods from './common/methods'
import change from './common/change'

/**
 * This is the default export of the `fortune` module. The Fortune class
 * subclasses the built-in `EventEmitter` class, and it has a few static
 * properties attached to it that may be useful to access:
 *
 * - `Adapter`: abstract base class for the Adapter.
 * - `adapters`: included adapters, defaults to memory adapter. Note that the
 * browser build also includes `indexedDB` and `webStorage` adapters.
 * - `Serializer`: abstract base class for the Serializer.
 * - `serializers`: included serializers, defaulting to *ad hoc* HTTP.
 * Server-side only.
 * - `net`: network protocol helpers, currently HTTP and WebSocket.
 * Server-side only.
 * - `errors`: custom typed errors, useful for throwing errors in transform
 * functions.
 * - `methods`: a hash that maps strings to symbols. Available are: `find`,
 * `create`, `update`, and `delete`.
 * - `change`: this is the symbol for the event that is emitted when a change
 * is done. The callback function receives an object keyed by method symbols.
 *
 * Note: in the browser version, `serializers` and `net` are omitted.
 */
export default class Fortune extends events.EventEmitter {

  /**
   * Create a new instance. The options object may be as follows:
   *
   * ```js
   * {
   *   // Adapter configuration. Default: `memoryAdapter`
   *   adapter: {
   *     // Must be a class that extends `Fortune.Adapter`, or a function
   *     // that accepts the Adapter class and returns a subclass. Required.
   *     type: Adapter => { ... },
   *
   *     // An options object that is specific to the adapter. Optional.
   *     options: { ... }
   *   },
   *
   *   // Serializers ordered by priority. Default: ad hoc JSON-over-HTTP.
   *   serializers: [{
   *     // Must be a class that extends `Fortune.Serializer`, or a function
   *     // that accepts the Serializer class and returns a subclass. Required.
   *     type: Serializer => { ... },
   *
   *     // An options object that is specific to the serializer. Optional.
   *     options: { ... }
   *   }],
   *
   *   // Whether or not to enforce referential integrity. Default: `true` for
   *   // server, `false` for browser.
   *   enforceLinks: true
   * }
   * ```
   *
   * @param {Object} [options]
   * @return {Fortune}
   */
  constructor (options = {}) {
    if (!('adapter' in options)) options.adapter = { type: memoryAdapter }
    if (!('enforceLinks' in options)) options.enforceLinks = true

    super()

    const [ recordTypes, transforms ] = [ {}, {} ]

    const flows = Object.keys(methods).reduce((hash, method) =>
      Object.assign(hash, {
        [methods[method]]: [ middlewares[method],
          middlewares.include, middlewares.end ]
          .map(fn => fn.bind(this))
      }), {})

    /*!
     * Adapter singleton that is coupled to the Fortune instance.
     *
     * @type {Adapter}
     */
    const adapter = new AdapterSingleton({
      adapter: options.adapter, recordTypes
    })

    /*!
     * Serializer singleton that is coupled to the Fortune instance.
     *
     * @type {Serializer}
     */
    const serializer = new SerializerSingleton({
      serializers: options.serializers, adapter,
      recordTypes
    })

    Object.defineProperties(this, {

      // 0 = not started, 1 = started, 2 = done.
      connectionStatus: { value: 0, writable: true },

      // Used for method chaining.
      currentType: { value: null, writable: true },

      // Configuration settings.
      options: { value: options },
      transforms: { value: transforms },
      recordTypes: { value: recordTypes },

      // Singleton instances.
      adapter: { value: adapter },
      serializer: { value: serializer },

      // Dispatch.
      flows: { value: flows }
    })
  }

  /**
   * This method should be called when all setup is done. After this method
   * succeeds, record types can not be defined. The resolved value
   * is the instance of Fortune.
   *
   * @return {Promise}
   */
  connect () {
    if (this.connectionStatus === 1)
      throw new Error(`Connection is in progress.`)

    else if (this.connectionStatus === 2)
      throw new Error(`Connection is already done.`)

    this.connectionStatus = 1

    return new Promise((resolve, reject) => {
      ensureTypes(this.recordTypes)

      this.adapter.connect().then(() => {
        this.connectionStatus = 2
        return resolve(this)
      }, error => {
        this.connectionStatus = 0
        return reject(error)
      })
    })
  }

  /**
   * Close adapter connection, and reset state. The resolved value is the
   * instance of Fortune.
   *
   * @return {Promise}
   */
  disconnect () {
    if (this.connectionStatus !== 2)
      throw new Error(`Instance has not been connected.`)

    this.connectionStatus = 1

    return new Promise((resolve, reject) =>
      this.adapter.disconnect().then(() => {
        this.connectionStatus = 0
        return resolve(this)
      }, error => {
        this.connectionStatus = 2
        return reject(error)
      }))
  }

  /**
   * Define a record type given a name and a set of field definitions. The
   * `fields` object only serves to enforce data types, and may be extended by
   * the specific `Adapter` to express more, such as validations, uniqueness,
   * indexing, etc. Here are some example field definitions:
   *
   * ```js
   * {
   *   // A singular value.
   *   name: { type: String },
   *
   *   // An array containing values of a single type.
   *   luckyNumbers: { type: Number, isArray: true },
   *
   *   // Creates a to-many link to `animal` record type. If the field `owner`
   *   // on the `animal` record type is not an array, this is a many-to-one
   *   // relationship, otherwise it is many-to-many.
   *   pets: { link: 'animal', isArray: true, inverse: 'owner' },
   *
   *   // The `min` and `max` keys are open to interpretation by the specific
   *   // adapter or serializer, which may introspect the field definition.
   *   thing: { type: Number, min: 0, max: 100 },
   *
   *   // Nested field definitions are invalid. Use `Object` type instead.
   *   nested: { thing: { ... } } // Will throw an error.
   * }
   * ```
   *
   * The allowed native types are `String`, `Number`, `Boolean`, `Date`,
   * `Object`, and `Buffer`. Note that the `Object` type should be a JSON
   * serializable object that may be persisted. The only other allowed type is
   * a `Symbol`, which may be used to represent custom types.
   *
   * @param {String} name - Name of the record type.
   * @param {Object} fields - A hash of field definition objects.
   * @return {Fortune}
   */
  defineType (name, fields) {
    const { recordTypes } = this

    if (this.connectionStatus !== 0)
      throw new Error(`Cannot define new record types after connection.`)

    if (typeof name !== 'string')
      throw new TypeError(`Name must be a string.`)

    if (!name.length)
      throw new Error(`Name must be non-trivial.`)

    if (name in recordTypes)
      throw new Error(`The record type "${name}" was already defined.`)

    if (typeof fields !== 'object')
      throw new TypeError(`Fields for "${name}" must be an object.`)

    recordTypes[name] = validate(fields)

    // Memoize the current name, for chaining methods.
    this.currentType = name

    return this
  }

  /**
   * This is the primary method for initiating a request. The options object
   * must be formatted as follows:
   *
   * ```js
   * {
   *   // The method is a either a function or a Symbol, keyed under
   *   // `methods` and may be one of `find`, `create`, `update`,  or `delete`.
   *   // To implement a custom method, pass a function that accepts one
   *   // argument, the context. It may return the context synchronously or as
   *   // a Promise. Default: `methods.find`.
   *   method: methods.find,
   *
   *   type: undefined, // Name of a type. Optional.
   *   ids: undefined, // An array of IDs. Optional.
   *
   *   // A 2-dimensional array specifying links to include. The first
   *   // dimension is a list, the second dimension is depth. For example:
   *   // [['comments'], ['comments', 'author']]
   *   include: [],
   *
   *   // Exactly the same as the adapter's `find` method options. The options
   *   // apply only to the primary type on `find` requests.
   *   options: { ... },
   *
   *   // Same as `options`, but is an object keyed by type. This is only
   *   // used in conjunction with the `include` option.
   *   includeOptions: { [type]: { ... } },
   *
   *   // The ID of the serializer to use for the input (request). Optional.
   *   serializerInput: undefined,
   *
   *   // The ID of the serializer to use for the output (response). Optional.
   *   serializerOutput: undefined,
   *
   *   meta: { ... }, // Meta-info of the request.
   *   payload: undefined // Payload of the request.
   * }
   * ```
   *
   * The response object looks much simpler:
   *
   * ```js
   * {
   *   meta: { ... }, // Meta-info of the response.
   *   payload: undefined // Payload of the response.
   * }
   * ```
   *
   * The resolved response object should always be typed.
   *
   * @param {Object} options
   * @param {...*} [args]
   * @return {Promise}
   */
  request () {
    if (this.connectionStatus !== 2)
      throw new Error(`Instance must be connected before making a request.`)

    return dispatch.apply(this, arguments)
  }

  /**
   * Define a transformation per record type.
   *
   * A transform function takes at least two arguments, the internal `context`
   * object and a single `record`. A special case is the `update` argument for
   * the `update` method.
   *
   * There are two kinds of transforms, before a record is written to transform
   * input, and after it is read to transform output, both are optional. If an
   * error occurs within an transform function, it will be forwarded to the
   * response. Use typed errors to provide the appropriate feedback. It is
   * important to note that `output` transforms are run every time a record is
   * shown in a response, so it should be idempotent.
   *
   * For a create request, the input transform must return the second argument
   * `record` either synchronously, or asynchronously as a Promise. The return
   * value of a delete request is inconsequential, but it may return a value or
   * a Promise. There is a special case of the `update` method accepting a
   * `update` object as a third parameter, which must be returned synchronously
   * or as a Promise.
   *
   * An example transform to apply a timestamp on a record before creation,
   * and displaying the timestamp in the server's locale:
   *
   * ```js
   * const { methods: {
   *   create: createMethod, update: updateMethod
   * } } = fortune
   *
   * store.transform((context, record, update) => {
   *   const { request: { method } } = context
   *
   *   if (method === createMethod) {
   *     record.timestamp = new Date()
   *     return record
   *   }
   *
   *   if (method === updateMethod) return update
   *
   *   // If we get here, return value of the delete method doesn't matter.
   *   return null
   * }, (context, record) => {
   *   record.timestamp = record.timestamp.toLocaleString()
   *   return record
   * })
   * ```
   *
   * Requests to update a record will have the updates already applied to the
   * record.
   *
   * @param {String} [type]
   * @param {Function} [input]
   * @param {Function} [output]
   * @return {Fortune}
   */
  transform (type, input, output) {
    const { recordTypes, transforms } = this

    if (arguments.length < 3) {
      input = arguments[0]
      output = arguments[1]
      type = this.currentType
    }

    if (![ input, output ].some(fn => typeof fn === 'function'))
      throw new TypeError(`A function must be passed to transform.`)

    if (!(type in recordTypes))
      throw new Error(`Attempted to define transform on "${type}" ` +
        `type which does not exist.`)

    if (!(type in transforms))
      transforms[type] = {}

    if (typeof input === 'function')
      transforms[type].input = input

    if (typeof output === 'function')
      transforms[type].output = output

    return this
  }

  /**
   * Convenience method to define only the `input` argument of a transform.
   *
   * @param {String} [type]
   * @param {Function} fn
   * @return {Fortune}
   */
  transformInput (type, fn) {
    if (arguments.length < 2) {
      fn = type
      type = this.currentType
    }

    return this.transform(type, fn, null)
  }

  /**
   * Convenience method to define only the `output` argument of a transform.
   *
   * @param {String} [type]
   * @param {Function} fn
   * @return {Fortune}
   */
  transformOutput (type, fn) {
    if (arguments.length < 2) {
      fn = type
      type = this.currentType
    }

    return this.transform(type, null, fn)
  }

  /**
   * This is a static method on the Fortune class that is an alternative to the
   * constructor method, for people who hate the `new` keyword with a passion.
   * This is exactly the same as invoking `new Fortune(options)`.
   *
   * @param {Object} [options]
   * @return {Fortune}
   */
  static create () {
    return new Fortune(...arguments)
  }

}

// Assign useful static properties to the default export.
defineArguments(Fortune, { Adapter, Serializer, errors, methods, change })
<|endoftext|>