npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

vuex-accessors

v3.0.6

Published

Type-safe wrapper for vuex.

Downloads

17

Readme

vuex-accessors

This library is a simple wrapper for vuex API. Its purpose is to provide better type safety when using vuex with typescript.

Installation

npm install --save vuex-accessors

Example

Let's say we want to create a vuex store which will manage the state of a single numeric variable counter. We start by providing an interface and implementation for its state:

./root-state.ts

export interface IRootState {
    counter: number
}

export const rootState: IRootState = {
    counter: 0
}

Next we define accessors for its getters, mutations and actions in a separate file.

./root.ts

import {createRootOptions, AccessorFactory} from "vuex-accessors"
import {rootState, IRootState} from "./root-state"

// first we instantiate a factory instance which we will use to create accessors.
const f = AccessorFactory.root<IRootState>()

// now we create an object with accessors
export const root = {
    getters: {
        counter: f.getter<number>(state => state.counter)
    },
    mutations: {
        setCounter: f.mutationWPayload<number>((state, payload) => state.counter = payload),
        
        incrementCounter: f.mutation(state => state.counter++),
        
        decrementCounter: f.mutation(state => state.counter--)
    },
    actions: {
        countdown: f.action<number>(context => {
            return new Promise<number>(resolve => {
                const interval = setInterval(() => {
                    // here we are alraeady accessing getter through our accessor with type safety
                    // `true` flag will be explained later
                    const currentValue = counter.getters.counter.get(context.getters, true)
                    log.debug(currentValue)

                    if (currentValue === 0) {
                        clearInterval(interval)
                        resolve(currentValue)
                    } else {
                        counter.mutations.decrementCounter.commit(context)
                    }
                }, 1000)
            })
        })
    }
}

// Now we can use our `root` object with accessors to generate the actual StoreOptions<IRootState> object for 
// vuex API. `createRootOptions` function expects an object which conforms to the 
// IAccessorsBundle<IRootStore, any> interface so type checking is performed here.
export const storeOptions = createRootOptions(rootState, root)

We exported two objects from root.ts: root and storeOptions. storeOptions is only required for store initialization, but root is later used for access. storeOptions now looks like this.

{
    getters: {
        counter: state => state.counter
    },
    mutations: {
        setCounter: (state, payload) => state.counter = payload,
        incrementCounter: state => state.counter++,
        decrementCounter: state => state.counter--
    },
    actions: {
        countdown: context => {
            return new Promise<number>(resolve => {
                const interval = setInterval(() => {
                    const currentValue = root.getters.counter.get(context.getters, true)
                    log.debug(currentValue)

                    if (currentValue === 0) {
                        clearInterval(interval)
                        resolve(currentValue)
                    } else {
                        counter.mutations.decrementCounter.commit(context)
                    }
                }, 1000)
            })
        }
    }
}

This is the StoreOptions<IRootState> object which can now be used to initialize vuex store.

./store.ts

import Vue from "vue"
import Vuex, {Store} from "vuex"
import {IRootState} from "./root-state"
import {storeOptions} from "./root"

Vue.use(Vuex)

export const store: Store<IRootState> = new Vuex.Store<IRootState>(storeOptions)

Store can be safely accessed with accessors we defined in root object.

import {root} from "... wherever root.ts is located relatively to this file ..."

// ... somewhere inside vue component
    const store = this.$store

    let currentCounterValue = root.getters.counter.get(store.getters)
    log.debug(currentCounterValue) // 0

    root.mutations.setCounter.commit(store, 2)

    currentCounterValue = root.getters.counter.get(store.getters)
    log.debug(currentCounterValue) // 2

    root.mutations.incrementCounter.commit(store)

    currentCounterValue = root.getters.counter.get(store.getters)
    log.debug(currentCounterValue) // 3

    root.actions.countdown.dispatch(store).then(value => {
        log.debug(value) // 0    
    })

As you can see, getters, mutations, and actions are used by calling get, commit and dispatch methods respectively on their accessor objects. commit and dispatch must be provided with context or store object. They will internally call vuex's commit or dispatch function with full namespace and mutation/action name. get on the other hand receives getters object and an optional second parameter local. local is false by default. If you set it to true, getter will be called only by name, not the full path (namespace + "/" + name). You can set this flag when you call getter of a namespaced module inside its own context (see action accessor in the example above).

Modules

Modules are created in a similar fashion. There are two differences:

  1. AccessorFactory is instantiated by calling module(namespace?: string) static method and optionally providing namespace if module should be namespaced.
  2. Instead of createRootOptions, createModule is called to generate Module object for registration in vuex store.

API

Here is the summary of all factory methods available for the creation of accessors.

/**
 * Create getter accessor.
 */
getter<ReturnT>(
    func: (state: StateT, getters: any, rootState?: RootStateT, rootGetters?: any) => ReturnT,
    name?: string
)

/**
 * Create mutation accessor.
 */
mutation(
    func: (state: StateT) => void,
    name?: string
)

/**
 * Create mutation accessor with payload.
 */
mutationWPayload<PayloadT>(
    func: (state: StateT, payload: PayloadT) => void,
    name?: string
)

/**
 * Create action accessor.
 */
action<ReturnT>(
    func: (context: ActionContext<StateT, RootStateT>) => Promise<ReturnT>,
    name?: string
)

/**
 * Create action accessor with payload.
 */
actionWPayload<PayloadT, ReturnT>(
    func: (context: ActionContext<StateT, RootStateT>, payload: PayloadT) => Promise<ReturnT>,
    name?: string
)

Each factory method receives optional second parameter name. We didn't use this parameter in our example. If provided, name will be the actual key used in the generated StoreOptions or Module object. If you don't provide it, accessor's key will be used implicitly. For example, if we had defined our counter getter like this:

// ...
    getters: {
        counter: f.getter<number>(state => state.counter),
        "getCounter"
    }
// ...

the actual StoreOptions object would then look like this:

// ...
    getters: {
        getCounter: state => state.counter
    }
// ...