BREAKING CHANGE: The existing `Store<Id, S, G, A>` types was trying to be generic when no types were specified but failing at it. Now, `Store` without any type will default to an empty Store. This enables a stricter version of `defineStore` when any of state, getters, and actions are missing. If you were using `Store` as a type, you should now use `StoreGeneric` instead, which also replaces `GenericStore` (marked as deprecated).
```diff
-function takeAnyStore(store: Store) {}
+function takeAnyStore(store: StoreGeneric) {}
```
import { ref, App, markRaw, effectScope } from 'vue'
import { registerPiniaDevtools, devtoolsPlugin } from './devtools'
import { IS_CLIENT } from './env'
-import { StateTree, Store } from './types'
+import { StateTree, StoreGeneric } from './types'
/**
* Creates a Pinia instance to be used by the application
// @ts-expect-error
_a: null,
_e: scope,
- _s: new Map<string, Store>(),
+ _s: new Map<string, StoreGeneric>(),
state,
})
CustomInspectorNode,
CustomInspectorState,
} from '@vue/devtools-api'
-import { Store, MutationType } from '../types'
+import { MutationType, StoreGeneric } from '../types'
import { DebuggerEvent } from 'vue'
import { Pinia } from '../rootStore'
import { isPinia } from './utils'
export const PINIA_ROOT_ID = '_root'
export function formatStoreForInspectorTree(
- store: Store | Pinia
+ store: StoreGeneric | Pinia
): CustomInspectorNode {
return '$id' in store
? {
}
export function formatStoreForInspectorState(
- store: Store | Pinia
+ store: StoreGeneric | Pinia
): CustomInspectorState {
if (isPinia(store)) {
const state: CustomInspectorState = {
state: Object.keys(store.$state).map((key) => ({
editable: true,
key,
- // @ts-expect-error
value: store.$state[key],
})),
}
state.getters = store._getters.map((getterName) => ({
editable: false,
key: getterName,
- // @ts-expect-error
value: store[getterName],
}))
}
state.customProperties = Array.from(store._customProperties).map((key) => ({
editable: true,
key,
- // @ts-expect-error
value: store[key],
}))
}
import { App, ComponentPublicInstance, toRaw } from 'vue'
import { Pinia, PiniaPluginContext } from '../rootStore'
import {
- Store,
GettersTree,
MutationType,
StateTree,
ActionsTree,
+ StoreGeneric,
} from '../types'
import {
actionGlobalCopyState,
key: 'getters',
editable: false,
value: store._getters.reduce((getters, key) => {
- // @ts-expect-error
getters[key] = store[key]
return getters
}, {} as GettersTree<StateTree>),
api.on.getInspectorTree((payload) => {
if (payload.app === app && payload.inspectorId === INSPECTOR_ID) {
- let stores: Array<Store | Pinia> = [pinia]
+ let stores: Array<StoreGeneric | Pinia> = [pinia]
stores = stores.concat(Array.from(pinia._s.values()))
payload.rootNodes = (
)
}
-function addStoreToDevtools(app: App, store: Store) {
+function addStoreToDevtools(app: App, store: StoreGeneric) {
if (!componentStateTypes.includes(getStoreType(store.$id))) {
componentStateTypes.push(getStoreType(store.$id))
}
* @param store - store to patch
* @param actionNames - list of actionst to patch
*/
-function patchActionForGrouping(store: Store, actionNames: string[]) {
+function patchActionForGrouping(store: StoreGeneric, actionNames: string[]) {
// original actions of the store as they are given by pinia. We are going to override them
const actions = actionNames.reduce((storeActions, actionName) => {
// use toRaw to avoid tracking #541
- // @ts-expect-error
storeActions[actionName] = toRaw(store)[actionName]
return storeActions
}, {} as ActionsTree)
for (const actionName in actions) {
- // @ts-expect-error
store[actionName] = function () {
// setActivePinia(store._p)
// the running action id is incremented in a before action hook
export function devtoolsPlugin<
Id extends string = string,
S extends StateTree = StateTree,
- G extends GettersTree<S> = GettersTree<S>,
+ G /* extends GettersTree<S> */ = GettersTree<S>,
A /* extends ActionsTree */ = ActionsTree
>({ app, store, options }: PiniaPluginContext<Id, S, G, A>) {
// HMR module
import { isRef, isReactive } from 'vue'
import { Pinia } from './rootStore'
-import { isPlainObject, Store, StoreDefinition, _Method } from './types'
+import { isPlainObject, StoreDefinition, StoreGeneric, _Method } from './types'
/**
* Checks if a function is a `StoreDefinition`
* @param initialUseStore - return of the defineStore to hot update
* @param hot - `import.meta.hot`
*/
-export function acceptHMRUpdate(
- initialUseStore: StoreDefinition<string, any, any, any>,
- hot: any
-) {
+export function acceptHMRUpdate(initialUseStore: StoreDefinition, hot: any) {
return (newModule: any) => {
const pinia: Pinia | undefined = hot.data.pinia || initialUseStore._pinia
return hot.invalidate()
}
- const existingStore: Store = pinia._s.get(id)!
+ const existingStore: StoreGeneric = pinia._s.get(id)!
if (!existingStore) {
console.log(`skipping hmr because store doesn't exist yet`)
return
export type {
StateTree,
Store,
+ StoreGeneric,
GenericStore,
StoreDefinition,
StoreWithGetters,
keyof S | keyof G | ((store: Store<Id, S, G, A>) => any)
>
> = {
- [key in keyof T]: () => T[key] extends (store: Store) => infer R
+ [key in keyof T]: () => T[key] extends (store: any) => infer R
? R
: T[key] extends keyof Store<Id, S, G, A>
? Store<Id, S, G, A>[T[key]]
return Array.isArray(keysOrMapper)
? keysOrMapper.reduce((reduced, key) => {
reduced[key] = function (this: ComponentPublicInstance) {
- // @ts-expect-error
return useStore(this.$pinia)[key]
} as () => any
return reduced
this: ComponentPublicInstance,
...args: any[]
) {
- // @ts-expect-error
- return (useStore(this.$pinia)[key] as _Method)(...args)
+ return useStore(this.$pinia)[key](...args)
}
return reduced
}, {} as _MapActionsReturn<A>)
this: ComponentPublicInstance,
...args: any[]
) {
- // @ts-expect-error
return useStore(this.$pinia)[keysOrMapper[key]](...args)
}
return reduced
// @ts-ignore
reduced[key] = {
get(this: ComponentPublicInstance) {
- // @ts-expect-error
return useStore(this.$pinia)[key]
},
set(this: ComponentPublicInstance, value) {
// it's easier to type it here as any
- // @ts-expect-error
return (useStore(this.$pinia)[key] = value as any)
},
}
// @ts-ignore
reduced[key] = {
get(this: ComponentPublicInstance) {
- // @ts-expect-error
return useStore(this.$pinia)[keysOrMapper[key]]
},
set(this: ComponentPublicInstance, value) {
// it's easier to type it here as any
- // @ts-expect-error
return (useStore(this.$pinia)[keysOrMapper[key]] = value as any)
},
}
import { App, EffectScope, InjectionKey, Plugin, Ref, warn } from 'vue'
import {
StateTree,
- StoreWithState,
- StateDescriptor,
PiniaCustomProperties,
_Method,
Store,
GettersTree,
ActionsTree,
PiniaCustomStateProperties,
- GenericStore,
DefineStoreOptionsInPlugin,
+ StoreGeneric,
} from './types'
/**
return activePinia!
}
-/**
- * Map of stores based on a Pinia instance. Allows setting and retrieving stores
- * for the current running application (with its pinia).
- */
-
-export const storesMap = new WeakMap<
- Pinia,
- Map<
- string,
- [
- StoreWithState<string, StateTree>,
- StateDescriptor<StateTree>,
- InjectionKey<Store>
- ]
- >
->()
-
/**
* Every application must own its own pinia to be able to create stores
*/
*
* @internal
*/
- _s: Map<string, GenericStore>
+ _s: Map<string, StoreGeneric>
/**
* Added by `createTestingPinia()` to bypass `useStore(pinia)`.
*
* @internal
*/
- _pStores?: Record<string, Store>
+ _pStores?: Record<string, StoreGeneric>
}
}
export interface PiniaPluginContext<
Id extends string = string,
S extends StateTree = StateTree,
- G extends GettersTree<S> = GettersTree<S>,
+ G /* extends GettersTree<S> */ = GettersTree<S>,
A /* extends ActionsTree */ = ActionsTree
> {
/**
_UnionToTuple,
DefineSetupStoreOptions,
DefineStoreOptionsInPlugin,
+ StoreGeneric,
} from './types'
import {
getActivePinia,
store._hotUpdate = markRaw((newStore) => {
newStore._hmrPayload.state.forEach((stateKey) => {
if (stateKey in store.$state) {
- // @ts-expect-error
const newStateTarget = newStore.$state[stateKey]
- // @ts-expect-error
const oldStateSource = store.$state[stateKey]
if (
typeof newStateTarget === 'object' &&
) {
patchObject(newStateTarget, oldStateSource)
} else {
- // @ts-expect-error
// transfer the ref
newStore.$state[stateKey] = oldStateSource
}
// remove deleted state properties
Object.keys(store.$state).forEach((stateKey) => {
if (!(stateKey in newStore.$state)) {
- // @ts-expect-error
delete store[stateKey]
}
})
pinia.state.value[$id] = toRef(newStore._hmrPayload, 'hotState')
for (const actionName in newStore._hmrPayload.actions) {
- const action: _Method =
- // @ts-expect-error
- newStore[actionName]
+ const action: _Method = newStore[actionName]
// @ts-expect-error: new key
store[actionName] =
// remove deleted getters
Object.keys(store._hmrPayload.getters).forEach((key) => {
if (!(key in newStore._hmrPayload.getters)) {
- // @ts-expect-error
delete store[key]
}
})
// remove old actions
Object.keys(store._hmrPayload.actions).forEach((key) => {
if (!(key in newStore._hmrPayload.actions)) {
- // @ts-expect-error
delete store[key]
}
})
return store
}
-// export function disposeStore(store: Store) {
+// export function disposeStore(store: StoreGeneric) {
// store._e
// }
*/
export function defineStore<
Id extends string,
- S extends StateTree,
- G extends GettersTree<S>,
+ S extends StateTree = {},
+ G extends GettersTree<S> = {},
// cannot extends ActionsTree because we loose the typings
- A /* extends ActionsTree */
+ A /* extends ActionsTree */ = {}
>(
id: Id,
options: Omit<DefineStoreOptions<Id, S, G, A>, 'id'>
*/
export function defineStore<
Id extends string,
- S extends StateTree,
- G extends GettersTree<S>,
+ S extends StateTree = {},
+ G extends GettersTree<S> = {},
// cannot extends ActionsTree because we loose the typings
- A /* extends ActionsTree */
+ A /* extends ActionsTree */ = {}
>(options: DefineStoreOptions<Id, S, G, A>): StoreDefinition<Id, S, G, A>
/**
_ExtractGettersFromSetupStore<SS>,
_ExtractActionsFromSetupStore<SS>
>
-export function defineStore(idOrOptions: any, setup?: any, setupOptions?: any) {
+export function defineStore(
+ // TODO: add proper types from above
+ idOrOptions: any,
+ setup?: any,
+ setupOptions?: any
+): StoreDefinition {
let id: string
let options:
| DefineStoreOptions<string, StateTree, GettersTree<StateTree>, ActionsTree>
id = idOrOptions.id
}
- function useStore(pinia?: Pinia | null, hot?: Store): Store {
+ function useStore(pinia?: Pinia | null, hot?: StoreGeneric): StoreGeneric {
const currentInstance = getCurrentInstance()
pinia =
// in test mode, ignore the argument provided as we can always retrieve a
}
}
- const store: Store = pinia._s.get(id)!
+ const store: StoreGeneric = pinia._s.get(id)!
if (__DEV__ && hot) {
const hotId = '__hot:' + id
true
)
- hot._hotUpdate(newStore as any)
+ hot._hotUpdate(newStore)
// cleanup the state properties and the store from the cache
delete pinia.state.value[hotId]
cache[id] = store
}
- return store
+ // StoreGeneric cannot be casted towards Store
+ return store as any
}
useStore.$id = id
Object.keys(options.actions || {}).forEach((action) => {
actionsCache[action] =
actionsCache[action] ||
- (stubActions
- ? createSpy!()
- : // @ts-expect-error:
- createSpy!(store[action]))
- // @ts-expect-error:
+ (stubActions ? createSpy!() : createSpy!(store[action]))
store[action] = actionsCache[action]
})
/**
* Object descriptor for Object.defineProperty
*/
-export interface StateDescriptor<S extends StateTree> {
+export interface StateDescriptor<S extends StateTree = {}> {
get(): S
set(newValue: S): void
}
export type StoreOnActionListenerContext<
Id extends string,
S extends StateTree,
- G extends GettersTree<S>,
+ G /* extends GettersTree<S> */,
A /* extends ActionsTree */
> = {
[Name in keyof A]: {
/**
* Store that is invoking the action
*/
- store: Store<Id, S, G, A>
+ store: ActionsTree extends A ? StoreGeneric : Store<Id, S, G, A>
/**
* Parameters passed to the action
* Argument of `store.$onAction()`
*/
export type StoreOnActionListener<
- Id extends string = string,
- S extends StateTree = StateTree,
- G extends GettersTree<S> = GettersTree<S>,
- A /* extends ActionsTree */ = ActionsTree
-> = (context: StoreOnActionListenerContext<Id, S, G, A>) => void
+ Id extends string,
+ S extends StateTree,
+ G /* extends GettersTree<S> */,
+ A /* extends ActionsTree */
+> = (
+ context: StoreOnActionListenerContext<
+ Id,
+ S,
+ G,
+ // {} creates a type of never due to how StoreOnActionListenerContext is defined
+ {} extends A ? ActionsTree : A
+ >
+) => void
/**
* Base store with state and functions
export interface StoreWithState<
Id extends string,
S extends StateTree,
- G extends GettersTree<StateTree> = GettersTree<S>,
- A /* extends ActionsTree */ = ActionsTree
+ G /* extends GettersTree<StateTree> */,
+ A /* extends ActionsTree */
> {
/**
* Unique identifier of the store
/**
* State of the Store. Setting it will replace the whole state.
*/
- $state: UnwrapRef<StateTree extends S ? {} : S> &
- PiniaCustomStateProperties<S>
+ $state: UnwrapRef<S> & PiniaCustomStateProperties<S>
/**
* Private property defining the pinia the store is attached to.
_getters?: string[]
/**
- * Used by devtools plugin to retrieve properties added with plugins. Removed in production.
+ * Used by devtools plugin to retrieve properties added with plugins. Removed
+ * in production. Can be used by the user to add property keys of the store
+ * that should be displayed in devtools.
*
* @internal
*/
*
* @internal
*/
- _hotUpdate(useStore: Store<Id, S, G, A>): void
+ _hotUpdate(useStore: StoreGeneric): void
/**
* Payload of the hmr update. Dev only.
*/
export type Store<
Id extends string = string,
- S extends StateTree = StateTree,
- G extends GettersTree<S> = GettersTree<S>,
+ S extends StateTree = {},
+ G /* extends GettersTree<S>*/ = {},
// has the actions without the context (this) for typings
- A /* extends ActionsTree */ = ActionsTree
-> = StoreWithState<Id, StateTree extends S ? {} : S, G, A> &
- (StateTree extends S ? {} : UnwrapRef<S>) &
- (GettersTree<S> extends G ? {} : StoreWithGetters<G>) &
+ A /* extends ActionsTree */ = {}
+> = StoreWithState<Id, S, G, A> &
+ UnwrapRef<S> &
+ StoreWithGetters<G> &
+ // StoreWithActions<A> &
(ActionsTree extends A ? {} : StoreWithActions<A>) &
PiniaCustomProperties<Id, S, G, A> &
PiniaCustomStateProperties<S>
+/**
+ * Generic and type-unsafe version of Store. Doesn't fail on access with
+ * strings, making it much easier to write generic functions that do not care
+ * about the kind of store that is passed.
+ */
+export type StoreGeneric = Store<
+ string,
+ StateTree,
+ GettersTree<StateTree>,
+ ActionsTree
+>
/**
* Generic and type-unsafe version of Store. Doesn't fail on access with
* strings, making it much easier to write generic functions that do not care
* about the kind of store that is passed.
+ * @deprecated Use `StoreGeneric` instead
*/
export type GenericStore<
Id extends string = string,
- S extends StateTree = any,
- G extends GettersTree<S> = GettersTree<S>,
+ S extends StateTree = StateTree,
+ G /* extends GettersTree<S> */ = GettersTree<S>,
// has the actions without the context (this) for typings
A /* extends ActionsTree */ = ActionsTree
> = StoreWithState<Id, S, G, A> &
export interface StoreDefinition<
Id extends string = string,
S extends StateTree = StateTree,
- G extends GettersTree<S> = GettersTree<S>,
+ G /* extends GettersTree<S>*/ = GettersTree<S>,
A /* extends ActionsTree */ = ActionsTree
> {
/**
* @param pinia - Pinia instance to retrieve the store
* @param hot - dev only hot module replacement
*/
- (pinia?: Pinia | null | undefined, hot?: Store): Store<Id, S, G, A>
+ (pinia?: Pinia | null | undefined, hot?: Store<Id, S, G, A>): Store<
+ Id,
+ S,
+ G,
+ A
+ >
/**
* Id of the store. Used by map helpers.
export interface PiniaCustomProperties<
Id extends string = string,
S extends StateTree = StateTree,
- G extends GettersTree<S> = GettersTree<S>,
+ G /* extends GettersTree<S> */ = GettersTree<S>,
A /* extends ActionsTree */ = ActionsTree
> {}
*/
export type GettersTree<S extends StateTree> = Record<
string,
- | ((
- state: UnwrapRef<
- (StateTree extends S ? {} : S) & PiniaCustomStateProperties<S>
- >
- ) => any)
+ | ((state: UnwrapRef<S> & UnwrapRef<PiniaCustomStateProperties<S>>) => any)
| (() => any)
>
export interface DefineStoreOptions<
Id extends string,
S extends StateTree,
- G extends GettersTree<S>,
+ G /* extends GettersTree<S> */,
A /* extends Record<string, StoreAction> */
> {
/**
* Optional object of getters.
*/
getters?: G &
- ThisType<
- UnwrapRef<StateTree extends S ? {} : S> &
- StoreWithGetters<G> &
- PiniaCustomProperties
- >
+ ThisType<UnwrapRef<S> & StoreWithGetters<G> & PiniaCustomProperties> &
+ GettersTree<S>
/**
* Optional object of actions.
actions?: A &
ThisType<
A &
- UnwrapRef<StateTree extends S ? {} : S> &
+ UnwrapRef<S> &
StoreWithState<Id, S, G, A> &
- StoreWithGetters<GettersTree<S> extends G ? {} : G> &
+ StoreWithGetters<G> &
PiniaCustomProperties
>
export interface DefineSetupStoreOptions<
Id extends string,
S extends StateTree,
- G extends ActionsTree, // TODO: naming
+ G,
A /* extends ActionsTree */
> extends Omit<
DefineStoreOptions<Id, S, G, A>,
export interface DefineStoreOptionsInPlugin<
Id extends string,
S extends StateTree,
- G extends ActionsTree, // TODO: naming
- A /* extends ActionsTree */
+ G,
+ A
> extends Omit<DefineStoreOptions<Id, S, G, A>, 'id'> {
/**
* Extracted object of actions. Added by useStore() when the store is built
if (options.debounce) {
return Object.keys(options.debounce).reduce((debouncedActions, action) => {
debouncedActions[action] = debounce(
- // @ts-expect-error: cannot be inferred
store[action],
options.debounce![action as keyof typeof options['actions']]
)
export * from '../dist/pinia'
+// export * from '../src'
export function describe(_name: string, _fn: () => void): void
export function expectType<T>(value: T): void
import {
expectType,
createPinia,
- Store,
+ StoreGeneric,
Pinia,
StateTree,
DefineStoreOptionsInPlugin,
const pinia = createPinia()
pinia.use(({ store, app, options, pinia }) => {
- expectType<Store>(store)
+ expectType<StoreGeneric>(store)
expectType<Pinia>(pinia)
expectType<App>(app)
expectType<
+import { StoreGeneric, defineStore, expectType } from './'
import { watch } from 'vue'
-import { defineStore, expectType, Store, GenericStore } from './'
const useStore = defineStore({
id: 'name',
// @ts-expect-error
noG.notExisting
-function takeStore<TStore extends Store>(store: TStore): TStore['$id'] {
+function takeStore<TStore extends StoreGeneric>(store: TStore): TStore['$id'] {
return store.$id
}
export const useSyncValueToStore = <
- TStore extends Store,
+ TStore extends StoreGeneric,
TKey extends keyof TStore['$state']
>(
propGetter: () => TStore[TKey],
takeStore(noG)
useSyncValueToStore(() => 2, noG, 'myState')
-declare var genericStore: GenericStore
+declare var genericStore: StoreGeneric
// should not fail like it does with Store
expectType<any>(genericStore.thing)
expectType<any>(genericStore.$state.thing)
takeStore(genericStore)
useSyncValueToStore(() => 2, genericStore, 'myState')
+// @ts-expect-error: this type is known so it should yield an error
useSyncValueToStore(() => false, genericStore, 'myState')
useSyncValueToStore(() => 2, genericStore, 'random')