log.js
function add (action, meta) {
    if (typeof action.type === 'undefined') {
      throw new Error('Expected "type" in action')
    }

    if (!meta) meta = { }

    var newId = false
    if (typeof meta.id === 'undefined') {
      newId = true
      meta.id = this.generateId()
    }

    if (typeof meta.time === 'undefined') {
      meta.time = parseInt(meta.id)
    }

    if (typeof meta.reasons === 'undefined') {
      meta.reasons = []
    } else if (!Array.isArray(meta.reasons)) {
      meta.reasons = [meta.reasons]
    }

    meta.reasons.forEach(function (reason) {
      if (typeof reason !== 'string') {
        throw new Error('Expected "reasons" to be strings')
      }
    })

    var log = this
    this.emitter.emit('preadd', action, meta)

    if (meta.keepLast) {
      this.removeReason(meta.keepLast, { olderThan: meta })
      meta.reasons.push(meta.keepLast)
    }

    if (meta.reasons.length === 0 && newId) {
      this.emitter.emit('add', action, meta)
      this.emitter.emit('clean', action, meta)
      return Promise.resolve(meta)
    } else if (meta.reasons.length === 0) {
      return this.store.byId(meta.id).then(function (result) {
        if (result[0]) {
          return false
        } else {
          log.emitter.emit('add', action, meta)
          log.emitter.emit('clean', action, meta)
          return meta
        }
      })
    } else {
      return this.store.add(action, meta).then(function (addedMeta) {
        if (addedMeta === false) {
          return false
        } else {
          log.emitter.emit('add', action, addedMeta)
          return addedMeta
        }
      })
    }
  }
function add (action, meta) {
    if (typeof action.type === 'undefined') {
      throw new Error('Expected "type" in action')
    }

    if (!meta) meta = { }

    var newId = false
    if (typeof meta.id === 'undefined') {
      newId = true
      meta.id = this.generateId()
    }

    if (typeof meta.time === 'undefined') {
      meta.time = parseInt(meta.id)
    }

    if (typeof meta.reasons === 'undefined') {
      meta.reasons = []
    } else if (!Array.isArray(meta.reasons)) {
      meta.reasons = [meta.reasons]
    }

    meta.reasons.forEach(function (reason) {
      if (typeof reason !== 'string') {
        throw new Error('Expected "reasons" to be strings')
      }
    })

    var log = this
    this.emitter.emit('preadd', action, meta)

    if (meta.keepLast) {
      this.removeReason(meta.keepLast, { olderThan: meta })
      meta.reasons.push(meta.keepLast)
    }

    if (meta.reasons.length === 0 && newId) {
      this.emitter.emit('add', action, meta)
      this.emitter.emit('clean', action, meta)
      return Promise.resolve(meta)
    } else if (meta.reasons.length === 0) {
      return this.store.byId(meta.id).then(function (result) {
        if (result[0]) {
          return false
        } else {
          log.emitter.emit('add', action, meta)
          log.emitter.emit('clean', action, meta)
          return meta
        }
      })
    } else {
      return this.store.add(action, meta).then(function (addedMeta) {
        if (addedMeta === false) {
          return false
        } else {
          log.emitter.emit('add', action, addedMeta)
          return addedMeta
        }
      })
    }
  }
function add ( action , meta ) { if ( typeof action . type === 'undefined' ) { throw new Error ( 'Expected "type" in action' ) } if ( ! meta ) meta = { } var newId = false if ( typeof meta . id === 'undefined' ) { newId = true meta . id = this . generateId ( ) } if ( typeof meta . time === 'undefined' ) { meta . time = parseInt ( meta . id ) } if ( typeof meta . reasons === 'undefined' ) { meta . reasons = [ ] } else if ( ! Array . isArray ( meta . reasons ) ) { meta . reasons = [ meta . reasons ] } meta . reasons . forEach ( function ( reason ) { if ( typeof reason !== 'string' ) { throw new Error ( 'Expected "reasons" to be strings' ) } } ) var log = this this . emitter . emit ( 'preadd' , action , meta ) if ( meta . keepLast ) { this . removeReason ( meta . keepLast , { olderThan : meta } ) meta . reasons . push ( meta . keepLast ) } if ( meta . reasons . length === 0 && newId ) { this . emitter . emit ( 'add' , action , meta ) this . emitter . emit ( 'clean' , action , meta ) return Promise . resolve ( meta ) } else if ( meta . reasons . length === 0 ) { return this . store . byId ( meta . id ) . then ( function ( result ) { if ( result [ 0 ] ) { return false } else { log . emitter . emit ( 'add' , action , meta ) log . emitter . emit ( 'clean' , action , meta ) return meta } } ) } else { return this . store . add ( action , meta ) . then ( function ( addedMeta ) { if ( addedMeta === false ) { return false } else { log . emitter . emit ( 'add' , action , addedMeta ) return addedMeta } } ) } }
Add action to log.

It will set `id`, `time` (if they was missed) and `added` property
to `meta` and call all listeners.

@param {Action} action The new action.
@param {Meta} [meta] Open structure for action metadata.
@param {string} [meta.id] Unique action ID.
@param {number} [meta.time] Action created time.
Milliseconds since UNIX epoch.
@param {string[]} [meta.reasons] Why action should be kept in log.
Action without reasons will be removed.
@param {string} [meta.keepLast] Set code as reason and remove this reasons
from previous actions.
@return {Promise} Promise with `meta` if action was added to log
or `false` if action was already in log

@example
removeButton.addEventListener('click', () => {
log.add({ type: 'users:remove', user: id })
})
Add action to log .