Join chat

Cleaning Log and Action Reasons

Logux uses reasons API for it to clean log from outdated actions.

In real systems, you may have different strategies to keep the action in the log. Development tools may need last 1000 action. Synchronization may require to keep all unsynchronized actions in the log. An application may need action with the latest value of each model field. Reasons API allow you to combine these different strategies.

Every action has an array of string in meta.reasons. An application can add or remove reasons of any action.

meta.reasons //=> ['syncing', 'devtool']

When you remove all reasons from the action, Logux removes it from the log:

// Will delete action with this ID
server.log.changeMeta(meta.id, { reasons: [] })

When Logux cleans event it call clean event listeners:

server.log.on('clean', (action, meta) => {
  console.log('Action was cleaned', action, meta)
})

If you add action without reasons, Logux will not even put it to the log. Logux will call preadd event, check reasons after, call add event, and remove an action from the memory if it doesn’t have any reasons.

// Actions will be removed from the memory
server.log.add(action)
server.log.add(action, { reasons: [] })

// Action will be kept in log until you removed this reason
server.log.add(action, { reasons: ['someReason'] })

Logux doesn’t synchronize reasons between nodes. The best way to set reasons is preadd event listener.

server.log.on('preadd', (action, meta) {
  meta.reasons.push('test')
})

You can use removeReason API to remove some reason from all action passed some criteria. See Log#removeReason API for criteria API.

server.log.removeReason('syncing', { maxAdded: lastSynced })
server.log.removeReason('devtool', { maxAdded: last - 1000 })

However, most of Logux implementation has built-in strategies on top of these reasons API.

keepLast

meta.keepLast is a shortcut to set a passed string as a reason and remove these reasons from all previous actions. Note that this shortcut will keep reason on the latest action according to meta.id and meta.time. Latest action could not be the action which you are adding.

// Keep action with latest name
server.log.add(
  { type: 'user/rename', userId: 380, name: 'New name' },
  { keepLast: ['user/380/name'] }
)

Server

By default, Logux Server doesn’t keep any actions in the memory. This is why you should keep all data in the database and get data from the database on every subscription.

If you want a complete event-sourcing system, you can implement a log store in persistent database and define actions cleaning strategy with reasons API and preadd event.

Logux Client

By default, Logux Client keeps actions with meta.sync, which was not synchronized yet.

Logux Client is low-level API. If you do not want to have a deal with complicated reasons API, Logux Redux is a better option.

Logux Redux

By default, Logux Redux will keep last 1000 actions without explicit meta.reasons.

// Logux Redux will keep 1000 actions without defined reasons
store.dispatch.crossTab(action)

// Logux Redux will not keep this action since we define reasons manually
store.dispatch.crossTab(action, { reasons: [] })

You can change actions limit by reasonlessHistory option in createLoguxCreator.

If Logux Redux needs cleaned action from time travel, it will call onMissedHistory callback. You can ask a user to reload the page or load the latest data state from the server because time travel can’t guarantee the result in this case.

let store = createLoguxCreator({
  …,
  onMissedHistory (action) {
    if (CRITICAL_ACTIONS.includes(action.type)) {
      store.dispatch.sync({ type: 'reload/state' }) // Ask server for latest state
    }
  }
})

Logux Redux keeps last 1000 action by setting timeTravel and timeTravelTab… reasons for new actions. If you want to specify reasons manually, you should set reasons in preadd event and set noAutoReason meta key:

store.log.on('preadd', (action, meta) => {
  if (action.type === 'user/rename') {
    meta.noAutoReason = true
    meta.keepLast = `user/${ action.userId }/name`
  }
})