BREAKING CHANGE: getters now receive the state as their first argument and it's properly typed so you can write getters with arrow functions:
```js
defineStore({
state: () => ({ n: 0 }),
getters: {
double: state => state.n * 2
}
})
```
To access other getters, you must still use the syntax that uses `this` **but it is now necessary to explicitely type the getter return type**. The same limitation exists in Vue for computed properties and it's a known limitation in TypeScript:
```ts
defineStore({
state: () => ({ n: 0 }),
getters: {
double: state => state.n * 2,
// the `: number` is necessary when accessing `this` inside of
// a getter
doublePlusOne(state): number {
return this.double + 1
},
}
})
```
For more information, refer to [the updated documentation for getters](https://pinia.esm.dev/core-concepts/getters.html).
'@typescript-eslint/no-namespace': 'off',
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/ban-types': 'off',
+ '@typescript-eslint/no-empty-interface': 'off',
},
}
import type Vue from 'vue'
import {
GenericStore,
- GenericStoreDefinition,
+ GettersTree,
Method,
StateTree,
Store,
StoreDefinition,
} from './types'
+type ComponentPublicInstance = Vue
+
/**
* Interface to allow customizing map helpers. Extend this interface with the
* following properties:
function getCachedStore<
Id extends string = string,
S extends StateTree = StateTree,
- G = Record<string, Method>,
+ G extends GettersTree<S> = GettersTree<S>,
A = Record<string, Method>
->(vm: Vue, useStore: StoreDefinition<Id, S, G, A>): Store<Id, S, G, A> {
- const cache = vm._pStores || (vm._pStores = {})
+>(
+ vm: ComponentPublicInstance,
+ useStore: StoreDefinition<Id, S, G, A>
+): Store<Id, S, G, A> {
+ const cache = '_pStores' in vm ? vm._pStores! : (vm._pStores = {})
const id = useStore.$id
return (cache[id] || (cache[id] = useStore(vm.$pinia))) as Store<Id, S, G, A>
}
*
* @param stores - list of stores to map to an object
*/
-export function mapStores<Stores extends GenericStoreDefinition[]>(
+export function mapStores<Stores extends any[]>(
...stores: [...Stores]
): Spread<Stores> {
+ if (__DEV__ && Array.isArray(stores[0])) {
+ console.warn(
+ `[🍍]: Directly pass all stores to "mapStores()" without putting them in an array:\n` +
+ `Replace\n` +
+ `\tmapStores([useAuthStore, useCartStore])\n` +
+ `with\n` +
+ `\tmapStores(useAuthStore, useCartStore)\n` +
+ `This will fail in production if not fixed.`
+ )
+ stores = stores[0]
+ }
+
return stores.reduce((reduced, useStore) => {
// @ts-ignore: $id is added by defineStore
reduced[useStore.$id + mapStoreSuffix] = function (this: Vue) {
}, {} as Spread<Stores>)
}
-type MapStateReturn<S extends StateTree, G> = {
+type MapStateReturn<S extends StateTree, G extends GettersTree<S>> = {
[key in keyof S | keyof G]: () => Store<string, S, G, {}>[key]
}
type MapStateObjectReturn<
Id extends string,
S extends StateTree,
- G,
+ G extends GettersTree<S>,
A,
T extends Record<
string,
export function mapState<
Id extends string,
S extends StateTree,
- G,
+ G extends GettersTree<S>,
A,
KeyMapper extends Record<
string,
* @param useStore - store to map from
* @param keys - array of state properties or getters
*/
-export function mapState<Id extends string, S extends StateTree, G, A>(
+export function mapState<
+ Id extends string,
+ S extends StateTree,
+ G extends GettersTree<S>,
+ A
+>(
useStore: StoreDefinition<Id, S, G, A>,
keys: Array<keyof S | keyof G>
): MapStateReturn<S, G>
export function mapState<
Id extends string,
S extends StateTree,
- G,
+ G extends GettersTree<S>,
A,
KeyMapper extends Record<
string,
): MapStateReturn<S, G> | MapStateObjectReturn<Id, S, G, A, KeyMapper> {
return Array.isArray(keysOrMapper)
? keysOrMapper.reduce((reduced, key) => {
- reduced[key] = function (this: Vue) {
+ reduced[key] = function (this: ComponentPublicInstance) {
return getCachedStore(this, useStore)[key]
} as () => any
return reduced
}, {} as MapStateReturn<S, G>)
: Object.keys(keysOrMapper).reduce((reduced, key: keyof KeyMapper) => {
- reduced[key] = function (this: Vue) {
+ reduced[key] = function (this: ComponentPublicInstance) {
const store = getCachedStore(this, useStore)
const storeKey = keysOrMapper[key]
// for some reason TS is unable to infer the type of storeKey to be a
/**
* Alias for `mapState()`. You should use `mapState()` instead.
- * @deprecated
+ * @deprecated use `mapState()` instead.
*/
export const mapGetters = mapState
export function mapActions<
Id extends string,
S extends StateTree,
- G,
+ G extends GettersTree<S>,
A,
KeyMapper extends Record<string, keyof A>
>(
* @param useStore - store to map from
* @param keys - array of action names to map
*/
-export function mapActions<Id extends string, S extends StateTree, G, A>(
+export function mapActions<
+ Id extends string,
+ S extends StateTree,
+ G extends GettersTree<S>,
+ A
+>(
useStore: StoreDefinition<Id, S, G, A>,
keys: Array<keyof A>
): MapActionsReturn<A>
export function mapActions<
Id extends string,
S extends StateTree,
- G,
+ G extends GettersTree<S>,
A,
KeyMapper extends Record<string, keyof A>
>(
): MapActionsReturn<A> | MapActionsObjectReturn<A, KeyMapper> {
return Array.isArray(keysOrMapper)
? keysOrMapper.reduce((reduced, key) => {
- reduced[key] = function (this: Vue, ...args: any[]) {
+ reduced[key] = function (
+ this: ComponentPublicInstance,
+ ...args: any[]
+ ) {
return (getCachedStore(this, useStore)[key] as Method)(...args)
} as Store<string, StateTree, {}, A>[keyof A]
return reduced
}, {} as MapActionsReturn<A>)
: Object.keys(keysOrMapper).reduce((reduced, key: keyof KeyMapper) => {
- reduced[key] = function (this: Vue, ...args: any[]) {
+ reduced[key] = function (
+ this: ComponentPublicInstance,
+ ...args: any[]
+ ) {
return getCachedStore(this, useStore)[keysOrMapper[key]](...args)
} as Store<string, StateTree, {}, A>[keyof KeyMapper[]]
return reduced
export function mapWritableState<
Id extends string,
S extends StateTree,
- G,
+ G extends GettersTree<S>,
A,
KeyMapper extends Record<string, keyof S>
>(
* @param useStore - store to map from
* @param keys - array of state properties
*/
-export function mapWritableState<Id extends string, S extends StateTree, G, A>(
+export function mapWritableState<
+ Id extends string,
+ S extends StateTree,
+ G extends GettersTree<S>,
+ A
+>(
useStore: StoreDefinition<Id, S, G, A>,
keys: Array<keyof S>
): MapWritableStateReturn<S>
export function mapWritableState<
Id extends string,
S extends StateTree,
- G,
+ G extends GettersTree<S>,
A,
KeyMapper extends Record<string, keyof S>
>(
): MapWritableStateReturn<S> | MapWritableStateObjectReturn<S, KeyMapper> {
return Array.isArray(keysOrMapper)
? keysOrMapper.reduce((reduced, key) => {
+ // @ts-ignore
reduced[key] = {
- get(this: Vue) {
+ get(this: ComponentPublicInstance) {
return getCachedStore(this, useStore)[key]
},
- set(this: Vue, value) {
+ set(this: ComponentPublicInstance, value) {
// it's easier to type it here as any
return (getCachedStore(this, useStore)[key] = value as any)
},
: Object.keys(keysOrMapper).reduce((reduced, key: keyof KeyMapper) => {
// @ts-ignore
reduced[key] = {
- get(this: Vue) {
+ get(this: ComponentPublicInstance) {
return getCachedStore(this, useStore)[keysOrMapper[key]]
},
- set(this: Vue, value) {
+ set(this: ComponentPublicInstance, value) {
// it's easier to type it here as any
return (getCachedStore(this, useStore)[
keysOrMapper[key]
StateDescriptor,
PiniaCustomProperties,
StoreDefinition,
+ GettersTree,
} from './types'
import { useStoreDevtools } from './devtools'
import {
function buildStoreToUse<
Id extends string,
S extends StateTree,
- G extends Record<string, Method>,
+ G extends GettersTree<S>,
A extends Record<string, Method>
>(
partialStore: StoreWithState<Id, S>,
const computedGetters: StoreWithGetters<G> = {} as StoreWithGetters<G>
for (const getterName in getters) {
+ // @ts-expect-error: it's only readonly for the users
computedGetters[getterName] = computed(() => {
setActivePinia(pinia)
// eslint-disable-next-line @typescript-eslint/no-use-before-define
+ // @ts-expect-error: the argument count is correct
return getters[getterName].call(store, store)
}) as StoreWithGetters<G>[typeof getterName]
}
export function defineStore<
Id extends string,
S extends StateTree,
- G /* extends Record<string, StoreGetterThis> */,
+ G extends GettersTree<S>,
A /* extends Record<string, StoreAction> */
>(options: {
id: Id
storeAndDescriptor[0],
storeAndDescriptor[1],
id,
- getters as Record<string, Method> | undefined,
+ getters as GettersTree<S> | undefined,
actions as Record<string, Method> | undefined
)
storeAndDescriptor[0],
storeAndDescriptor[1],
id,
- getters as Record<string, Method> | undefined,
+ getters as GettersTree<S> | undefined,
actions as Record<string, Method> | undefined
)
}
import { Pinia } from './rootStore'
+/**
+ * Generic state of a Store
+ */
export type StateTree = Record<string | number | symbol, any>
/**
)
}
-export type NonNullObject = Record<any, any>
-
export type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> }
// type DeepReadonly<T> = { readonly [P in keyof T]: DeepReadonly<T[P]> }
state: S
) => void
+/**
+ * Base store with state and functions
+ * @internal
+ */
export interface StoreWithState<Id extends string, S extends StateTree> {
/**
* Unique identifier of the store
/**
* Setups a callback to be called whenever the state changes.
- * @param callback - callback that is called whenever the state
- * @returns function that removes callback from subscriptions
+ *
+ * @param callback - callback passed to the watcher
+ * @returns function that removes the watcher
*/
$subscribe(callback: SubscriptionCallback<S>): () => void
}
// }
// in this type we forget about this because otherwise the type is recursive
+/**
+ * Store augmented for actions
+ *
+ * @internal
+ */
export type StoreWithActions<A> = {
[k in keyof A]: A[k] extends (...args: infer P) => infer R
? (...args: P) => R
: never
}
-// export interface StoreGetter<S extends StateTree, T = any> {
-// // TODO: would be nice to be able to define the getters here
-// (state: S, getters: Record<string, Ref<any>>): T
-// }
-
+/**
+ * Store augmented with getters
+ *
+ * @internal
+ */
export type StoreWithGetters<G> = {
- [k in keyof G]: G[k] extends (this: infer This, store?: any) => infer R
- ? R
- : never
+ readonly [k in keyof G]: G[k] extends (...args: any[]) => infer R ? R : never
}
// // in this type we forget about this because otherwise the type is recursive
// : never
// }
+/**
+ * Store type to build a store
+ */
export type Store<
Id extends string,
S extends StateTree,
- G,
+ G extends GettersTree<S>,
// has the actions without the context (this) for typings
A
> = StoreWithState<Id, S> &
StoreWithActions<A> &
PiniaCustomProperties<Id, S, G, A>
+// TODO: check if it's possible to add = to StoreDefinition and Store and cleanup GenericStore and the other one
+
/**
* Return type of `defineStore()`. Function that allows instantiating a store.
*/
export interface StoreDefinition<
Id extends string,
S extends StateTree,
- G /* extends Record<string, StoreGetterThis> */,
+ G extends GettersTree<S>,
A /* extends Record<string, StoreAction> */
> {
(pinia?: Pinia | null | undefined): Store<Id, S, G, A>
export type GenericStore = Store<
string,
StateTree,
- Record<string, Method>,
+ GettersTree<StateTree>,
Record<string, Method>
>
export type GenericStoreDefinition = StoreDefinition<
string,
StateTree,
- Record<string, Method>,
+ GettersTree<StateTree>,
Record<string, Method>
>
-export interface DevtoolHook {
- on(
- event: string,
- callback: (targetState: Record<string, StateTree>) => void
- ): void
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
- emit(event: string, ...payload: any[]): void
-}
-
-// add the __VUE_DEVTOOLS_GLOBAL_HOOK__ variable to the global namespace
-declare global {
- interface Window {
- __VUE_DEVTOOLS_GLOBAL_HOOK__?: DevtoolHook
- }
- namespace NodeJS {
- interface Global {
- __VUE_DEVTOOLS_GLOBAL_HOOK__?: DevtoolHook
- }
- }
-}
-
/**
* Properties that are added to every store by `pinia.use()`
*/
-// eslint-disable-next-line
export interface PiniaCustomProperties<
Id extends string = string,
S extends StateTree = StateTree,
- G = Record<string, Method>,
+ G extends GettersTree<S> = GettersTree<S>,
A = Record<string, Method>
> {}
+
+export type GettersTree<S extends StateTree> = Record<
+ string,
+ ((state: S) => any) | (() => any)
+>
+
+/**
+ * Options parameter of `defineStore()`. Can be extended to augment stores with
+ * the plugin API.
+ */
+export interface DefineStoreOptions<
+ Id extends string,
+ S extends StateTree,
+ G extends GettersTree<S>,
+ A /* extends Record<string, StoreAction> */
+> {
+ id: Id
+ state?: () => S
+ getters?: G & ThisType<S & StoreWithGetters<G> & PiniaCustomProperties>
+ // allow actions use other actions
+ actions?: A &
+ ThisType<
+ A &
+ S &
+ StoreWithState<Id, S> &
+ StoreWithGetters<G> &
+ PiniaCustomProperties
+ >
+}
-import { defineStore, expectType, mapStores } from '.'
+import { expectType, createPinia, defineStore, mapStores } from '.'
-declare module '../dist/src/index' {
+declare module '../dist/src' {
export interface MapStoresCustomization {
- // this is the only one that can be applied to work with other tests
suffix: 'Store'
}
+
+ export interface PiniaCustomProperties<Id, S, G, A> {
+ $actions: Array<keyof A>
+ }
+
+ export interface DefineStoreOptions<Id, S, G, A> {
+ debounce?: {
+ // Record<keyof A, number>
+ [k in keyof A]?: number
+ }
+ }
}
-const useCounter = defineStore({
- id: 'counter',
- state: () => ({ n: 0 }),
+const pinia = createPinia()
+
+pinia.use((context) => {
+ expectType<string>(context.options.id)
+ expectType<string>(context.store.$id)
+
+ return {
+ $actions: Object.keys(context.options.actions || {}),
+ }
+})
+
+const useStore = defineStore({
+ id: 'main',
+ actions: {
+ one() {},
+ two() {
+ this.one()
+ },
+ three() {
+ this.two()
+ },
+ },
+
+ debounce: {
+ one: 200,
+ two: 300,
+ // three: 100
+ },
})
-type CounterStore = ReturnType<typeof useCounter>
+type Procedure = (...args: any[]) => any
-const computedStores = mapStores(useCounter)
+function debounce<F extends Procedure>(fn: F, time = 200) {
+ return fn
+}
expectType<{
- counterStore: () => CounterStore
-}>(computedStores)
+ mainStore: () => ReturnType<typeof useStore>
+}>(mapStores(useStore))
+
+pinia.use(({ options, store }) => {
+ if (options.debounce) {
+ return Object.keys(options.debounce).reduce((debouncedActions, action) => {
+ debouncedActions[action] = debounce(
+ store[action],
+ options.debounce![action as keyof typeof options['actions']]
+ )
+ return debouncedActions
+ }, {} as Record<string, (...args: any[]) => any>)
+ }
+})
id: 'name',
state: () => ({ a: 'on' as 'on' | 'off', nested: { counter: 0 } }),
getters: {
- upper() {
- return this.a.toUpperCase()
- },
+ upper: (state) => state.a.toUpperCase(),
},
})
id: 'name',
state: () => ({ a: 'on' as 'on' | 'off', nested: { counter: 0 } }),
getters: {
- upper() {
- return this.a.toUpperCase()
- },
+ upper: (state) => state.a.toUpperCase(),
},
actions: {
toggleA() {
--- /dev/null
+import {
+ expectType,
+ createPinia,
+ GenericStore,
+ Pinia,
+ StateTree,
+ DefineStoreOptions,
+} from '.'
+
+const pinia = createPinia()
+
+pinia.use(({ store, options, pinia }) => {
+ expectType<GenericStore>(store)
+ expectType<Pinia>(pinia)
+ expectType<
+ DefineStoreOptions<
+ string,
+ StateTree,
+ Record<string, any>,
+ Record<string, any>
+ >
+ >(options)
+})
-import { GenericStore } from 'dist/src/types'
-import { defineStore, expectType, createPinia } from './'
-
-const pinia = createPinia()
-
-pinia.use(({ store }) => {
- expectType<GenericStore>(store)
-})
+import { defineStore, expectType } from './'
const useStore = defineStore({
id: 'name',
state: () => ({ a: 'on' as 'on' | 'off', nested: { counter: 0 } }),
getters: {
- upper() {
- return this.a.toUpperCase()
+ upper: (state) => {
+ expectType<'on' | 'off'>(state.a)
+ return state.a.toUpperCase() as 'ON' | 'OFF'
+ },
+ upperThis(): 'ON' | 'OFF' {
+ expectType<'on' | 'off'>(this.a)
+ return this.a.toUpperCase() as 'ON' | 'OFF'
+ },
+ other(): false {
+ expectType<string>(this.upper)
+ return false
+ },
+
+ doubleCounter: (state) => {
+ expectType<number>(state.nested.counter)
+ return state.nested.counter * 2
+ },
+ },
+ actions: {
+ doStuff() {
+ expectType<string>(this.upper)
+ expectType<false>(this.other)
+ },
+ otherOne() {
+ expectType<() => void>(this.doStuff)
},
},
})
-const store = useStore()
+let store = useStore()
expectType<{ a: 'on' | 'off' }>(store.$state)
expectType<number>(store.nested.counter)
expectType<'on' | 'off'>(store.a)
+expectType<'ON' | 'OFF'>(store.upper)
// @ts-expect-error
store.nonExistant
+// @ts-expect-error
+store.upper = 'thing'
+
// @ts-expect-error
store.nonExistant.stuff