Feathers-vuex: Save last query & timestamp by default

Created on 19 Jun 2018  路  7Comments  路  Source: feathersjs-ecosystem/feathers-vuex

Steps to reproduce

  1. dispatch a find in one page of your app.
  2. Navigate to another that effectively needs the same data.

Expected behavior

We should have a way to tell how old the cache is and what query populated it. That would allow us to say, not run the query again if it was ran less than a minute ago.

Actual behavior

The cache has no origin information, no information about whether the connection dropped since it was populated and no information about the query that populated it. Therefore, we have no guarantees about the data and are thus forced to implement our optimization or fire the potentially non-trivial query again.

Working with feathers-vuex is currently quite wordy... we should look for ways to cut down lines for the typical app.

Proposed Solution

Lets add a property to the vuex state with

last: {
  createdAt: 14245678921
  query: 'find'
  params: '{}'
}

All 7 comments

I've found myself checking the cache and then fetching if not available a few too many times... it would be nice if feathers-vuex services came with an anyGet and anyFind action, which checks the cache for answers and if nothing is found, pulls from the web. A max age field would be a nice parameter as well.

getAssignment: async function () {
        let assignment = this.getAssignmentGetter(this.$route.params.id)
        if (typeof assignment === 'undefined') {
          return this.getAssignmentAction(this.$route.params.id)
        } else {
          return assignment
        }
  },
  created () {
    ; (async () => {
      let assignment = await this.getAssignment()
     })()
  }

Update

I ended up using the builtin customization system... making maxAge work would take modifying the core. But this is great.

const simpleServices = ['org', 'course', 'assignment']
const standardCustomization = {
  actions: {
    anyGet: async function ({ commit, dispatch, getters }, args) {
      let entity = getters.get(args)
      if (typeof entity === 'undefined') {
        await dispatch('get', args)
        return getters.get(args)
      } else {
        return entity
      }
    }
  }
}

export default new Vuex.Store({
  plugins: [
    ...simpleServices.map(s => service(s, standardCustomization)),

in use:

  methods: {
    ...mapActions('assignment', {
      getAssignment: 'anyGet'
    })
  },
  created () {
    ; (async () => {
        let assignment = await this.getAssignment(this.$route.params.id)
    })()
  }

@marshallswain I'm using allGet and anyFind in all my projects. Any get in particular drastically reduces network load and the risk of race conditions from sub components trying to getter/get when an upstream component hasn't finished action/get

they are very small and work incredibly well... would you welcome a pull request for allGet? I've found the use cases and benefits of anyFind less common. Though I use Mongo... so that may have something to do with it.

// VUEX
export const standardCustomization = {
  actions: {
    allGet: async function ({ commit, dispatch, getters }, args) {
      let entity = getters.get(args)
      if (typeof entity === 'undefined') {
        await dispatch('get', args)
        return getters.get(args)
      } else {
        dispatch('get', args)
        return entity
      }
    },
    anyFind: async function ({ commit, dispatch, getters }, args) {
      let result = getters.find(args)
      if (result.total === 0) {
        await dispatch('find', args)
        return getters.find(args)
      } else {
        return result
      }
    }
  }
}

Should I open a new issue for this? as the cache expiration idea is irrelevant as most of us are using reactive updates anyway.

On the other hand no object should be so large that it will have a meaningful impact on the network.

@rayfoss A PR would be great! A new issue might be better if you feel like you might implement the main feature of this issue sometime.

I also like the idea of a maxAge-based cache. As a baby step towards this, I've added a createdAt timestamp to the pagination data in [email protected].

In the future, I'll look into using can-query-logic to implement smarter cacheing.

fun real world edge case:

Google play music will only get the playlist list once and pull from cache every time you go to the playlist list page. It will reactively add to the list, but if your laptop is asleep, you'll end up with missing playlists even though you've "browsed" back and forth between pages inside Google Play Music.

AllGet would solve this, but it would be expensive for Google. AllGet([id, maxAge]) would fix the problem without hitting the server every time.


Off topic, I would really love built in eager updates support that doesn't require a local proxy copy to support vuex strict mode. Something like patch([id, params, vuexParams]) where I can say {eager: true, rollback: false}.

@rayfoss the new pagination object for each store now provides enough information to make this possible. You get all sorts of info, now: https://feathers-vuex.feathers-plus.com/service-module.html#querying-with-find-pagination

Was this page helpful?
0 / 5 - 0 ratings

Related issues

juancampuzano picture juancampuzano  路  4Comments

RubyRubenstahl picture RubyRubenstahl  路  3Comments

coderinblack08 picture coderinblack08  路  7Comments

DreaMinder picture DreaMinder  路  6Comments

daenuprobst picture daenuprobst  路  5Comments