Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/pinia/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export type {
_SubscriptionCallbackMutationBase,
PiniaCustomProperties,
PiniaCustomStateProperties,
PiniaCustomOptionsProperties,
DefineStoreOptionsBase,
DefineStoreOptions,
DefineSetupStoreOptions,
Expand Down
6 changes: 4 additions & 2 deletions packages/pinia/src/mapHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export type _StoreObject<S> =
infer Ids,
infer State,
infer Getters,
infer Actions
infer Actions,
infer Options
>
? {
[Id in `${Ids}${MapStoresCustomization extends Record<
Expand All @@ -47,7 +48,8 @@ export type _StoreObject<S> =
: string,
State,
Getters,
Actions
Actions,
Options
>
}
: {}
Expand Down
18 changes: 15 additions & 3 deletions packages/pinia/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
_ExtractGettersFromSetupStore,
_ExtractStateFromSetupStore,
_StoreWithState,
_Empty,
} from './types'
import { setActivePinia, piniaSymbol, Pinia, activePinia } from './rootStore'
import { IS_CLIENT } from './env'
Expand Down Expand Up @@ -834,7 +835,7 @@ export function defineStore<
>(
id: Id,
options: Omit<DefineStoreOptions<Id, S, G, A>, 'id'>
): StoreDefinition<Id, S, G, A>
): StoreDefinition<Id, S, G, A, DefineStoreOptions<Id, S, G, A>>

/**
* Creates a `useStore` function that retrieves the store instance
Expand All @@ -852,7 +853,16 @@ export function defineStore<Id extends string, SS>(
_ExtractGettersFromSetupStore<SS>,
_ExtractActionsFromSetupStore<SS>
>
): SetupStoreDefinition<Id, SS>
): SetupStoreDefinition<
Id,
SS,
DefineSetupStoreOptions<
Id,
_ExtractStateFromSetupStore<SS>,
_ExtractGettersFromSetupStore<SS>,
_ExtractActionsFromSetupStore<SS>
>
>

// allows unused stores to be tree shaken
/*! #__NO_SIDE_EFFECTS__ */
Expand Down Expand Up @@ -962,9 +972,11 @@ export function defineStore(
export interface SetupStoreDefinition<
Id extends string,
SS,
O = _Empty,
> extends StoreDefinition<
Id,
_ExtractStateFromSetupStore<SS>,
_ExtractGettersFromSetupStore<SS>,
_ExtractActionsFromSetupStore<SS>
_ExtractActionsFromSetupStore<SS>,
O
> {}
39 changes: 36 additions & 3 deletions packages/pinia/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,7 +448,7 @@ export type _StoreWithGetters_Readonly<G> = {
}

/**
* Store augmented with writable getters. For internal usage **only**.
* Store augmented with writable getters. For internal usage only.
*/
export type _StoreWithGetters_Writable<G> = {
[K in keyof G as G[K] extends WritableComputedRef<any>
Expand All @@ -467,13 +467,17 @@ export type Store<
G /* extends GettersTree<S>*/ = {},
// has the actions without the context (this) for typings
A /* extends ActionsTree */ = {},
// Options passed to `defineStore()`, exposed through
// `PiniaCustomOptionsProperties` so plugins can access custom options.
O = _Empty,
> = _StoreWithState<Id, S, G, A> &
UnwrapRef<S> &
_StoreWithGetters<G> &
// StoreWithActions<A> &
(_ActionsTree extends A ? {} : A) &
PiniaCustomProperties<Id, S, G, A> &
PiniaCustomStateProperties<S>
PiniaCustomStateProperties<S> &
PiniaCustomOptionsProperties<O>

/**
* Generic and type-unsafe version of Store. Doesn't fail on access with
Expand All @@ -495,14 +499,15 @@ export interface StoreDefinition<
S extends StateTree = StateTree,
G /* extends GettersTree<S>*/ = _GettersTree<S>,
A /* extends ActionsTree */ = _ActionsTree,
O = _Empty,
> {
/**
* Returns a store, creates it if necessary.
*
* @param pinia - Pinia instance to retrieve the store
* @param hot - dev only hot module replacement
*/
(pinia?: Pinia | null | undefined, hot?: StoreGeneric): Store<Id, S, G, A>
(pinia?: Pinia | null | undefined, hot?: StoreGeneric): Store<Id, S, G, A, O>

/**
* Id of the store. Used by map helpers.
Expand Down Expand Up @@ -532,6 +537,34 @@ export interface PiniaCustomProperties<
*/
export interface PiniaCustomStateProperties<S extends StateTree = StateTree> {}

/**
* Interface to be extended by the user when they add properties through
* plugins that need to access the options passed to `defineStore()`.
*
* The `O` parameter carries the resolved store options (including custom
* options added to {@link DefineStoreOptionsBase} through module
* augmentation), so plugin properties can be typed from them:
*
* @example
* ```ts
* declare module 'pinia' {
* export interface DefineStoreOptionsBase<S, Store> {
* stores?: Record<string, StoreDefinition>
* }
*
* export interface PiniaCustomOptionsProperties<O> {
* readonly stores: O extends { stores?: infer Stores }
* ? { [K in keyof Stores]: Stores[K] extends StoreDefinition
* ? ReturnType<Stores[K]>
* : never
* }
* : Record<string, Store>
* }
* }
* ```
*/
export interface PiniaCustomOptionsProperties<O = _Empty> {}

/**
* Type of an object of Getters that infers the argument. For internal usage only.
* For internal use **only**
Expand Down
81 changes: 81 additions & 0 deletions packages/pinia/test-dts/storeOptions.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import {
defineStore,
expectType,
PiniaCustomOptionsProperties,
Store,
StoreDefinition,
StoreGeneric,
} from './'

// Recreate the plugin from https://github.com/vuejs/pinia/issues/1247: a
// plugin adds a `stores` option to `DefineStoreOptionsBase` and exposes the
// instantiated stores on `this.stores`, typed from the store options.
declare module './' {
export interface DefineStoreOptionsBase<S, Store> {
stores?: Record<string, StoreDefinition>
marker?: string
}

export interface PiniaCustomOptionsProperties<O> {
readonly stores: O extends { stores?: infer Stores }
? {
[K in keyof Stores]: Stores[K] extends StoreDefinition
? ReturnType<Stores[K]>
: never
}
: Record<string, Store>
readonly marker: O extends { marker?: infer M } ? M : undefined
}
}

const useCounterStore = defineStore('counter', {
state: () => ({ count: 0 }),
actions: {
increment() {
this.count++
},
},
})

const useUserStore = defineStore('user', {
state: () => ({ name: 'initial' }),
actions: {
setName(name: string) {
this.name = name
},
},
})

const useMainStore = defineStore('main', {
state: () => ({ value: 0 }),
stores: {
counter: useCounterStore,
user: useUserStore,
},
marker: 'main-marker',
})

const usePlainStore = defineStore('plain', {
state: () => ({ value: 0 }),
})

// the `stores` option is exposed on the store instance through the plugin
// property, typed from the options passed to defineStore()
expectType<StoreGeneric>(useMainStore().stores.counter)
expectType<StoreGeneric>(useMainStore().stores.user)
useMainStore().stores.counter.increment()
useMainStore().stores.user.setName('other')

// a non-record option is readable with its augmented type
expectType<string | undefined>(useMainStore().marker)

// stores without a custom option still expose the plugin property
expectType<string | undefined>(usePlainStore().marker)

// `PiniaCustomOptionsProperties` is exported and usable standalone: with no
// custom options it resolves to the fallback shapes
expectType<PiniaCustomOptionsProperties<{}>>({ stores: {}, marker: undefined })
expectType<PiniaCustomOptionsProperties<{ marker: 'x' }>>({
stores: {},
marker: 'x',
})