Source

operate/agent.js

const Adapter = require('./adapter')
const Tape = require('./tape')
const VM = require('./vm')
const defaultConfig = require('./config')

/**
 * The Agent module responsible for loading and running tapes.
 *
 * @class
 */

class Agent {

  /**
   * Creates an Agent instance.
   *
   * @param {Object} opts Configuration options
   * @return {Agent}
   */
  constructor(opts = {}) {
    this.config = {
      ...defaultConfig,
      ...opts
    }
  }

  /**
   * Loads a tape from the given txid.
   *
   * Fetches the tape transaction output as well as all of the required
   * functions, and returns a Promise.
   *
   * @param {String} txid Transaction id
   * @param {Object} opts Options
   * @return {Promise(Tape)}
   */
  async loadTape(txid, opts = {}) {
    const config = {
      ...this.config,
      ...opts
    }

    let index
    [txid, index] = txid.split('/')
    const [cache, cacheConfig] = this._adapterWithOpts(config.cache)
    const adapter = this._adapterWithOpts(config.tape_adapter)
    const tx = await cache.fetchTx(txid, cacheConfig, adapter)
    return await this._prepTape(tx, index, config)
  }

  /**
   * Loads a tape from the given query.
   *
   * The expected format of the query will depend on the Adapter in use.
   * The transactions as well as all required functions are loaded and a Promise
   * is returned resolving in a list of Tapes.
   *
   * @param {Object} query Query object
   * @param {Object} opts Options
   * @return {Promise(Array)}
   */
  async loadTapesBy(query, opts = {}) {
    const config = {
      ...this.config,
      ...opts
    }

    const [cache, cacheConfig] = this._adapterWithOpts(config.cache)
    const adapter = this._adapterWithOpts(config.tape_adapter)
    const txns = await cache.fetchTxBy(query, cacheConfig, adapter)
    return await this._prepTapes(txns, config)
  }

  /**
   * Runs the given tape executing each of the tape's cells and returns a tape.
   *
   * @param {Tape} tape Tape instance
   * @return {Promise(any)}
   */
  async runTape(tape, opts = {}) {
    const config = {
      ...this.config,
      ...opts
    }
    const vm = new VM({ agent: this, extensions: config.extensions }),
          state = config.state,
          execOpts = { state, strict: config.strict };
    return await tape.run(vm, execOpts)
  }

  /**
   * Takes a transaction, loads its required Ops and returns a tape.
   * @private
   */
  async _prepTape(tx, index, opts = {}) {
    const config = {
      ...this.config,
      ...opts
    }

    let tape
    try {
      tape = Tape.fromBPU(tx, index)
      const refs = tape.getOpRefs(config.aliases)
      let [cache, cacheConfig] = this._adapterWithOpts(config.cache)
      const adapter = this._adapterWithOpts(config.op_adapter)
      const ops = await cache.fetchOps(refs, cacheConfig, adapter)
      tape.setCellOps(ops, config.aliases)
    } catch (error) {
      if (this.config.strict) throw error;
    }

    return tape
  }

  /**
   * Iterates over an array of transactions, preparing each and returns an array
   * of tapes.
   * @private
   */
  async _prepTapes(txns, opts = {}) {
    const config = {
      ...this.config,
      ...opts
    }

    let tape
    const tapes = []
    for (let i = 0; i < txns.length; i++) {
      tape = await this._prepTape(txns[i], undefined, config)
      if (tape) tapes.push(tape);
    }
    return tapes
  }

  /**
   * Returns the adapter and options in a tuple pair.
   * @private
   */
  _adapterWithOpts(adapter) {
    return Array.isArray(adapter) ? adapter : [adapter, {}]
  }
}

module.exports = Agent