nuxt-typed-vuex
Advanced tools
Comparing version
@@ -5,2 +5,9 @@ # Changelog | ||
### [0.1.6](https://github.com/danielroe/nuxt-typed-vuex/compare/v0.1.5...v0.1.6) (2019-10-11) | ||
### Features | ||
* add rootState and rootGetters type helpers ([2661017](https://github.com/danielroe/nuxt-typed-vuex/commit/2661017)) | ||
### [0.1.5](https://github.com/danielroe/nuxt-typed-vuex/compare/v0.1.4...v0.1.5) (2019-10-06) | ||
@@ -7,0 +14,0 @@ |
@@ -17,4 +17,4 @@ import { Store, GetterTree, MutationTree, ActionTree, DispatchOptions, CommitOptions } from 'vuex'; | ||
}; | ||
declare type ModuleTransformer<T> = T extends NuxtModules ? { | ||
[P in keyof T]: MergedStoreType<T[P] & BlankStore>; | ||
declare type ModuleTransformer<T, O = string> = T extends NuxtModules ? { | ||
[P in keyof T]: MergedStoreType<T[P] & BlankStore, O>; | ||
} : {}; | ||
@@ -46,3 +46,3 @@ interface BlankStore { | ||
} | ||
declare type MergedStoreType<T extends NuxtStore> = StateType<T['state']> & GettersTransformer<T['getters']> & MutationsTransformer<T['mutations']> & ActionTransformer<T['actions']> & ModuleTransformer<T['modules']>; | ||
declare type MergedStoreType<T extends NuxtStore, K = string> = ('state' extends K ? StateType<T['state']> : {}) & ('getters' extends K ? GettersTransformer<T['getters']> : {}) & ('mutations' extends K ? MutationsTransformer<T['mutations']> : {}) & ('actions' extends K ? ActionTransformer<T['actions']> : {}) & ('modules' extends K ? ModuleTransformer<T['modules']> : {}); | ||
declare type StoreParameter<T extends () => any> = Parameters<T>[1] extends undefined ? never : Parameters<T>[1]; | ||
@@ -67,2 +67,4 @@ interface Dispatch<T extends Record<string, () => any>> { | ||
}; | ||
export declare type RootStateHelper<T extends Required<NuxtStore>> = StateType<T['state']> & ModuleTransformer<T['modules'], 'state'>; | ||
export declare type RootGettersHelper<T extends Required<NuxtStore>> = GettersTransformer<T['getters']> & ModuleTransformer<T['modules'], 'getters'>; | ||
export declare const getStoreType: <T extends Record<string, any>, G, M, A, S extends Record<string, Partial<NuxtStore> & { | ||
@@ -72,2 +74,4 @@ state: () => unknown; | ||
actionContext: ActionContext<NuxtStoreInput<T, G, M, A, S> & BlankStore>; | ||
rootState: RootStateHelper<NuxtStoreInput<T, G, M, A, S> & BlankStore>; | ||
rootGetters: RootGettersHelper<NuxtStoreInput<T, G, M, A, S> & BlankStore>; | ||
storeInstance: ActionContext<NuxtStoreInput<T, G, M, A, S> & BlankStore> & Pick<Store<StateType<T>>, "app" | "replaceState" | "subscribe" | "subscribeAction" | "watch" | "registerModule" | "unregisterModule" | "hotUpdate" | "$router">; | ||
@@ -77,6 +81,6 @@ }; | ||
state: () => unknown; | ||
}>>(store: NuxtStoreInput<T, G, M, A, S>) => MergedStoreType<NuxtStoreInput<T, G, M, A, S> & BlankStore>; | ||
export declare const useAccessor: <T extends Record<string, any>, G extends GetterTree<StateType<T>, StateType<T>>, M extends MutationTree<StateType<T>>, A extends ActionTree<StateType<T>, StateType<T>>, S extends Record<string, Partial<NuxtStore> & { | ||
}>>(store: NuxtStoreInput<T, G, M, A, S>) => MergedStoreType<NuxtStoreInput<T, G, M, A, S> & BlankStore, string>; | ||
export declare const useAccessor: <T extends Record<string, any>, G extends GetterTree<StateType<T>, any>, M extends MutationTree<StateType<T>>, A extends ActionTree<StateType<T>, any>, S extends Record<string, Partial<NuxtStore> & { | ||
state: () => unknown; | ||
}>>(store: Store<StateType<T>>, input: Required<NuxtStoreInput<T, G, M, A, S>>) => MergedStoreType<NuxtStoreInput<T, G, M, A, S> & BlankStore>; | ||
}>>(store: Store<StateType<T>>, input: Required<NuxtStoreInput<T, G, M, A, S>>) => MergedStoreType<NuxtStoreInput<T, G, M, A, S> & BlankStore, string>; | ||
export declare const getAccessorFromStore: (pattern: any) => (store: Store<any>) => any; | ||
@@ -91,3 +95,3 @@ export declare const getterTree: <S, T extends GetterTree<StateType<S>, any>>(_state: S, tree: T) => T; | ||
} | ||
export declare const actionTree: <S extends Record<string, any>, G extends GetterTree<StateType<S>, {}>, M extends MutationTree<StateType<S>>, T extends ModifiedActionTree<Required<NuxtStoreInput<S, G, M, {}, {}>>>>(_store: NuxtStoreInput<S, G, M, {}, {}>, tree: T) => T; | ||
export declare const actionTree: <S extends Record<string, any>, G extends GetterTree<StateType<S>, any>, M extends MutationTree<StateType<S>>, T extends ModifiedActionTree<Required<NuxtStoreInput<S, G, M, {}, {}>>>>(_store: NuxtStoreInput<S, G, M, {}, {}>, tree: T) => T; | ||
export {}; |
export const getStoreType = (store) => { | ||
return { | ||
actionContext: {}, | ||
rootState: {}, | ||
rootGetters: {}, | ||
storeInstance: {}, | ||
@@ -5,0 +7,0 @@ }; |
{ | ||
"name": "nuxt-typed-vuex", | ||
"version": "0.1.5", | ||
"version": "0.1.6", | ||
"description": "A typed store accessor for Nuxt.", | ||
@@ -20,3 +20,3 @@ "repository": "danielroe/nuxt-typed-vuex", | ||
"build": "tsc", | ||
"prepublish": "yarn build", | ||
"prepare": "yarn build", | ||
"release": "yarn test && standard-version && git push --follow-tags && npm publish", | ||
@@ -27,12 +27,12 @@ "generate": "yarn build && yarn nuxt generate test/fixture", | ||
"devDependencies": { | ||
"@babel/core": "^7.6.2", | ||
"@babel/core": "^7.6.4", | ||
"@babel/plugin-transform-runtime": "^7.6.2", | ||
"@babel/preset-env": "^7.6.2", | ||
"@babel/preset-env": "^7.6.3", | ||
"@babel/preset-typescript": "^7.6.0", | ||
"@nuxt/typescript-build": "^0.3.1", | ||
"@nuxt/typescript-runtime": "^0.2.1", | ||
"@nuxtjs/eslint-config": "^1.1.2", | ||
"@babel/preset-typescript": "^7.6.0", | ||
"@nuxt/typescript-build": "^0.3.0", | ||
"@nuxt/typescript-runtime": "^0.2.0", | ||
"@types/jest": "^24.0.18", | ||
"@typescript-eslint/eslint-plugin": "^2.3.1", | ||
"@typescript-eslint/parser": "^2.3.1", | ||
"@typescript-eslint/eslint-plugin": "^2.3.3", | ||
"@typescript-eslint/parser": "^2.3.3", | ||
"babel-eslint": "^10.0.3", | ||
@@ -52,4 +52,9 @@ "babel-jest": "^24.9.0", | ||
"standard-version": "^7.0.0", | ||
"typescript": "^3.6.3" | ||
"ts-loader": "^6.2.0", | ||
"typescript": "^3.6.4" | ||
}, | ||
"dependencies": { | ||
"@nuxt/types": "*", | ||
"vuex": "*" | ||
} | ||
} |
317
README.md
@@ -1,3 +0,20 @@ | ||
# 🏦 Nuxt Typed Vuex | ||
<h1 align="center" >🏦 Nuxt Typed Vuex</h1> | ||
<p align="center">A vanilla, strongly-typed store for Nuxt</p> | ||
<p align="center"> | ||
<a href="https://david-dm.org/danielroe/nuxt-typed-vuex"> | ||
<img alt="" src="https://david-dm.org/danielroe/nuxt-typed-vuex/status.svg?style=flat-square"> | ||
</a> | ||
<a href="https://npmjs.com/package/nuxt-typed-vuex"> | ||
<img alt="" src="https://img.shields.io/npm/v/nuxt-typed-vuex/latest.svg?style=flat-square"> | ||
</a> | ||
<a href="https://npmjs.com/package/nuxt-typed-vuex"> | ||
<img alt="" src="https://img.shields.io/npm/dt/nuxt-typed-vuex.svg?style=flat-square"> | ||
</a> | ||
</p> | ||
<p align="center"> | ||
<a href="https://nuxt-typed-vuex.danielcroe.com">Read documentation</a> | ||
</p> | ||
## Summary | ||
@@ -7,4 +24,2 @@ | ||
**Note**: This has been developed to suit my needs but additional use cases and contributions are very welcome. | ||
 | ||
@@ -14,298 +29,4 @@ | ||
## Usage | ||
**Note**: This has been developed to suit my needs but additional use cases and contributions are very welcome. | ||
Add module to your `nuxt.config`: | ||
```ts | ||
modules: [ | ||
'nuxt-typed-vuex', | ||
], | ||
``` | ||
You will now have access to an injected store accessor (`this.$accessor`) throughout your project. Getters, state (if a getter with the same name doesn't already exist), actions and mutations are available at the root level, with submodules nested under their own namespace. | ||
### Typing the accessor | ||
The accessor injected by this module is not typed by default, so you will need to add your own type definitions, for which a helper function, `getAccessorType`, is provided. This function only serves to return the correct type of the accessor so that it can be used where you see fit. | ||
```ts | ||
import { getAccessorType } from 'nuxt-typed-vuex' | ||
import * as store from '~/store' | ||
... | ||
const accessorType = getAccessorType(store) | ||
// Now you can access the type of the accessor. | ||
const accessor: typeof accessorType | ||
``` | ||
### Typing actions within the store | ||
I recommend the following patterns: | ||
**Note**: There are a number of helper functions created to assist in typing actions within the store, although they are strictly optional. They do not **transform** the objects provided to them, but simply inject the correct types. | ||
#### State | ||
If there is any ambiguity in your initial object (of particular note are empty arrays), make sure to provide types as well. | ||
```ts | ||
export const state = () => ({ | ||
emails: [] as string[], | ||
}) | ||
// If needed, you can define state for use in vanilla Vuex types | ||
export type RootState = ReturnType<typeof state> | ||
``` | ||
#### Getters | ||
Vanilla getters are functions that receive state, as well as the other getters. `nuxt-typed-vuex` can't type the getters received, but does provide a helper function to reduce boilerplate: | ||
```ts | ||
// Vanilla | ||
export const getters = { | ||
email: (state: RootState) => (state.emails.length ? state.emails[0] : ''), | ||
aDependentGetter: (_state: RootState, getters: any) => getters.email, | ||
} | ||
// Helper function | ||
import { getterTree } from 'nuxt-typed-vuex' | ||
export const getters = getterTree(state, { | ||
email: state => (state.emails.length ? state.emails[0] : ''), | ||
aDependentGetter: (_state, getters) => getters.email, | ||
}) | ||
``` | ||
#### Mutations | ||
Vanilla mutations are functions that receive state and an optional payload. Again, a helper function is provided to reduce boilerplate. | ||
```ts | ||
// Vanilla | ||
export const mutations = { | ||
setEmail(state: RootState, newValue: string) { | ||
state.email = newValue | ||
}, | ||
initialiseStore() { | ||
console.log('initialised') | ||
}, | ||
} | ||
// Helper function | ||
import { mutationTree } from 'nuxt-typed-vuex' | ||
export const getters = mutationTree(state, { | ||
setEmail(state, newValue: string) { | ||
state.email = newValue | ||
}, | ||
initialiseStore() { | ||
console.log('initialised') | ||
}, | ||
}) | ||
``` | ||
#### Actions | ||
Actions are more complicated. In vanilla Vuex, they can either be objects (not supported by `nuxt-typed-vuex`) or async functions that have access to an instance (through which they can access the strongly-typed accessor injected above). | ||
```ts | ||
// Vanilla | ||
export const actions = { | ||
async resetEmail( | ||
this: Store<RootState>, | ||
{ commit }: ActionContext<RootState, RootState>, | ||
payload: string | ||
) { | ||
// Not typed - avoid | ||
commit('initialiseStore') | ||
// Typed | ||
this.app.$accessor.initialiseStore() | ||
}, | ||
} | ||
// Helper function | ||
export const actions = actionTree( | ||
{ state, getters, mutations }, | ||
{ | ||
async resetEmail({ commit, dispatch }) { | ||
// Typed | ||
commit('initialiseStore') | ||
// Not typed | ||
dispatch('resetEmail') | ||
// Typed | ||
this.app.$accessor.resetEmail() | ||
}, | ||
} | ||
) | ||
``` | ||
#### Accessing namespaced state, getters, actions and mutations from mutations | ||
This is currently a work in progress. | ||
#### `getStoreType` (deprecated) | ||
There is another helper function: `getStoreType`. | ||
This is largely obviated by the `actionTree` helper above. | ||
It does not correctly type actions and mutations for submodules, but may be useful for simpler setups. Consider it a placeholder for future development. | ||
```ts | ||
import { getStoreType } from 'nuxt-typed-vuex' | ||
import * as store from '~/store' | ||
const { storeType, storeInstance } = getStoreType(store) | ||
const store: storeInstance | ||
// You will now get type checking on getters, actions, state and mutations | ||
store.commit('SAMPLE_MUTATION', 30) | ||
``` | ||
## Example | ||
### Setup | ||
`store/index.ts`: | ||
```ts | ||
import { getAccessorType } from 'nuxt-typed-vuex' | ||
import * as submodule from '~/store/submodule' | ||
export const state = () => ({ | ||
email: '', | ||
ids: [] as number[], | ||
}) | ||
type RootState = ReturnType<typeof state> | ||
export const getters = { | ||
email: (state: RootState) => state.email, | ||
} | ||
export const mutations = { | ||
SET_EMAIL(state: RootState, newValue: string) { | ||
state.email = newValue | ||
}, | ||
} | ||
export const actions = { | ||
async resetEmail({ commit }: ActionContext<RootState, RootState>) { | ||
commit('SET_EMAIL', '') | ||
}, | ||
} | ||
export const accessorType = getAccessorType({ | ||
actions, | ||
getters, | ||
mutations, | ||
state, | ||
modules: { | ||
// The key (submodule) needs to match the Nuxt namespace (e.g. submodule.ts) | ||
submodule, | ||
}, | ||
}) | ||
``` | ||
`index.d.ts`: | ||
```ts | ||
import { accessorType } from '~/store' | ||
declare module 'vue/types/vue' { | ||
interface Vue { | ||
$accessor: typeof accessorType | ||
} | ||
} | ||
declare module '@nuxt/types' { | ||
interface NuxtAppOptions { | ||
$accessor: typeof accessorType | ||
} | ||
} | ||
``` | ||
### Usage | ||
#### Within components | ||
`components/sampleComponent.vue`: | ||
```ts | ||
import { Component, Vue } from 'nuxt-property-decorator' | ||
@Component({ | ||
fetch({ app: { $accessor } }) { | ||
// Accessing within fetch or asyncData | ||
$accessor.fetchItems() | ||
}, | ||
}) | ||
export default class SampleComponent extends Vue { | ||
get email() { | ||
// This (behind the scenes) returns this.$store.getters['email'] | ||
return this.$accessor.email | ||
} | ||
resetEmail() { | ||
// Executes this.$store.dispatch('submodule/resetEmail', 'thing') | ||
this.$accessor.submodule.submoduleAction('thing') | ||
} | ||
} | ||
``` | ||
#### Middleware | ||
`middleware/test.ts`: | ||
```ts | ||
import { Context } from '@nuxt/types' | ||
export default ({ redirect, app: { $accessor } }: Context) => { | ||
// You can access the store here | ||
if ($accessor.email) return redirect('/') | ||
} | ||
``` | ||
## Notes | ||
### Usage with IE11 | ||
You may need to transpile this module for usage with IE11. | ||
`nuxt.config`: | ||
```ts | ||
... | ||
build: { | ||
transpile: [ | ||
/nuxt-typed-vuex/ | ||
], | ||
}, | ||
``` | ||
## Key limitations and assumptions | ||
1. You should not use the Vuex helper functions, such as `GetterTree`, in your store, as it interferes with type inference. | ||
However, your store properties will be checked at the point you pass them into `getAccessorType` and you will get an error if (for example) you pass in a getter that doesn't match Vuex's type signature for a getter. | ||
2. This may require additional work for submodules split into separate `state.ts`, `actions.ts`, `mutations.ts` and `getters.ts`. | ||
3. Note that this does not support [object-style commits](https://vuex.vuejs.org/guide/mutations.html#object-style-commit). | ||
## License | ||
[MIT License](./LICENSE) - Copyright © Daniel Roe |
Sorry, the diff of this file is not supported yet
Wildcard dependency
QualityPackage has a dependency with a floating version range. This can cause issues if the dependency publishes a new major version.
Found 2 instances in 1 package
186
3.91%18776
-22.04%2
Infinity%26
4%2
Infinity%31
-90%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added