/vue-store

A small SPA storage system based on @vue/reactivity

Primary LanguageTypeScript

@samatech/vue-store

A small SPA storage system based on @vue/reactivity


Instructions

Install

# With NPM
npm i -S @samatech/vue-store

# With PNPM
pnpm i -S @samatech/vue-store

Usage

A basic module with explicit typing. See here for a slightly more advanced example.

import {
  IGetters,
  IMutations,
  IState,
  useModule,
  IModule,
  LocalStoragePlugin,
} from '@samatech/vue-store'

export type IUserModule = IModule<IUser, IUserGetters, IUserMutations>

interface IUser extends IState {
  name: string
}

interface IUserGetters extends IGetters {
  upperCaseName: () => string
}

interface IUserMutations extends IMutations {
  updateName: (name: string) => void
  logout: () => void
}

const getDefaultUser = (): IUser => ({
  name: '',
})

export const userModule = useModule<IUser, IUserGetters, IUserMutations>({
  name: 'user',
  version: 1,
  stateInit: getDefaultUser,
  getters: (state: IUser) => ({
    upperCaseName: () => state.name.toUpperCase(),
  }),
  mutations: (state: IUser) => ({
    updateName: (name: string) => (state.name = name),
    logout: () => {
      Object.assign(state, getDefaultUser())
    },
  }),
  plugins: [LocalStoragePlugin],
})

Typescript's ReturnType feature can be used to avoid the need for defining explicit interfaces:

import { LocalStoragePlugin, useModule, useRootModule } from '@samatech/vue-store'

interface IUser {
  name: string
}

const getDefaultUser = (): IUser => ({
  name: '',
})

const getters = (state: IUser) => ({
  upperCaseName: () => state.name.toUpperCase(),
})

const mutations = (state: IUser) => ({
  updateName: (name: string) => (state.name = name),
  logout: () => {
    Object.assign(state, getDefaultUser())
  },
})

const userModule = useModule<
  IUser,
  ReturnType<typeof getters>,
  ReturnType<typeof mutations>
>({
  name: 'user-store',
  version: 1,
  stateInit: getDefaultUser,
  getters,
  mutations,
  plugins: [LocalStoragePlugin],
})

export const store = useRootModule({
  name: 'web-store',
  version: 1,
  subModules: {
    user: userModule,
  },
})

Undefined handling

If a state field is set to undefined, it will not appear in the flattened module, or be saved with the LocalStoragePlugin. It is recommended to use null instead, and make use of strict type checking to avoid accidentally setting fields to undefined. It is possible to add undefined support to the LocalStoragePlugin, please file a feature request or submit a PR if you need this functionality.

Plugins

Plugins can help initialize state, and operate on state when it changes. A basic LocalStoragePlugin is provided for persisting a module's state to browser storage.

Writing plugins is straightforward, just provide an object conforming to IPlugin, or a function that accepts a module parameter and returns IPlugin.

interface IPlugin<S extends IState> {
  // Called when the module is initialized
  onStateInit?: (state: S) => S
  // Called any time the module's state changes
  onDataChange?: WatchCallback<UnwrapNestedRefs<S>, UnwrapNestedRefs<S> | undefined>
}

interface MyState {
  dots: string
}

const dummyPlugin: IPlugin<MyState> = {
  onDataChange: (value) => {
    value.dots += '.'
  },
}

Environment

TODO -- details about using alongside other versions of @vue/reactivity

Example

See the example folder.

Development

We use PNPM workspaces for development

# Clone
git clone git@github.com:samatechtw/vue-store
cd vue-store

# Install dependencies
pnpm install

# Run example
pnpm run all:dev

# Build
pnpm run build

License

MIT License © 2022 SamaTech Limited Company