describe('SSR', () => {
const App = {
- ssrRender(ctx: any, push: any, parent: any) {
+ ssrRender(ctx: any, push: any, _parent: any) {
push(
`<div>${ssrInterpolate(ctx.user.name)}: ${ssrInterpolate(
ctx.cart.items
-import { createPinia, defineStore } from '../src'
+import { createPinia, defineSetupStore, defineStore } from '../src'
import { mount } from '@vue/test-utils'
-import { App, ref, toRef } from 'vue'
+import { App, computed, Ref, ref, toRef } from 'vue'
declare module '../src' {
export interface PiniaCustomProperties<Id> {
expect(store2.$state.shared).toBe(1)
expect(store2.shared).toBe(1)
})
+
+ it('passes the options of the options store', (done) => {
+ const options = {
+ id: 'main',
+ state: () => ({ n: 0 }),
+ actions: {
+ increment() {
+ // @ts-expect-error
+ this.n++
+ },
+ },
+ getters: {
+ a() {
+ return 'a'
+ },
+ },
+ }
+ const useStore = defineStore(options)
+ const pinia = createPinia()
+ mount({ template: 'none' }, { global: { plugins: [pinia] } })
+
+ pinia.use((context) => {
+ expect(context.options).toEqual(options)
+ done()
+ })
+ useStore(pinia)
+ })
+
+ it('passes the options of a setup store', (done) => {
+ function increment(n: Ref<number>) {
+ n.value++
+ }
+
+ const useStore = defineSetupStore('main', () => {
+ const n = ref(0)
+
+ const a = computed(() => 'a')
+
+ return { n, increment, a }
+ })
+ const pinia = createPinia()
+ mount({ template: 'none' }, { global: { plugins: [pinia] } })
+
+ pinia.use((context) => {
+ expect(context.options).toEqual({
+ actions: {
+ increment,
+ },
+ })
+ done()
+ })
+
+ useStore()
+ })
})
expect(store.$state).not.toHaveProperty('increment')
})
+ it('can store a function', () => {
+ const store = defineSetupStore('main', () => {
+ const fn = ref(() => {})
+ function action() {}
+ return { fn, action }
+ })()
+ expectType<{ fn: () => void }>(store.$state)
+ expect(store.$state).toEqual({ fn: expect.any(Function) })
+ expect(store.fn).toEqual(expect.any(Function))
+ store.action()
+ })
+
it('can directly access state at the store level', () => {
const store = useStore()
expect(upperCased.value).toBe('ED')
})
- // it('watch', () => {
- // setActivePinia(createPinia())
- // defineStore({
- // id: 'main',
- // state: () => ({
- // name: 'Eduardo',
- // counter: 0,
- // }),
- // })()
- // })
-
it('state can be watched', async () => {
const store = useStore()
const spy = jest.fn()
A /* extends ActionsTree */ = ActionsTree
>({ app, store, options, pinia }: PiniaPluginContext<Id, S, G, A>) {
// original actions of the store as they are given by pinia. We are going to override them
- const actions = Object.keys(options.actions || ({} as A)).reduce(
+ const actions = Object.keys(options.actions).reduce(
(storeActions, actionName) => {
// @ts-expect-error
// use toRaw to avoid tracking #541
// function
return typeof storeKey === 'function'
? (storeKey as (store: Store<Id, S, G, A>) => any).call(this, store)
- : store[storeKey as keyof typeof store]
+ : // @ts-expect-error
+ store[storeKey]
}
return reduced
}, {} as _MapStateObjectReturn<Id, S, G, A, KeyMapper>)
ActionsTree,
PiniaCustomStateProperties,
GenericStore,
+ DefineStoreOptionsInPlugin,
} from './types'
/**
/**
* Current store being extended.
*/
- options: DefineStoreOptions<Id, S, G, A>
+ options: DefineStoreOptionsInPlugin<Id, S, G, A>
}
/**
ActionsTree,
SubscriptionCallbackMutation,
_UnionToTuple,
+ DefineOptionStoreOptions,
+ DefineSetupStoreOptions,
+ DefineStoreOptionsInPlugin,
} from './types'
import {
getActivePinia,
const { assign } = Object
-export interface DefineSetupStoreOptions<
- Id extends string,
- S extends StateTree,
- G extends ActionsTree, // TODO: naming
- A extends ActionsTree
-> {
- hydrate?(store: Store<Id, S, G, A>, initialState: S | undefined): void
-}
-
function isComputed(o: any): o is ComputedRef {
return o && o.effect && o.effect.computed
}
S extends StateTree,
G extends GettersTree<S>,
A extends ActionsTree
->(options: DefineStoreOptions<Id, S, G, A>, pinia: Pinia): Store<Id, S, G, A> {
+>(
+ options: DefineOptionStoreOptions<Id, S, G, A>,
+ pinia: Pinia
+): Store<Id, S, G, A> {
const { id, state, actions, getters } = options
function $reset() {
pinia.state.value[id] = state ? state() : {}
)
}
- const store = createSetupStore(
- id,
- setup,
- // TODO: actual hydrate option to be added to options store
- // @ts-expect-error: fixme
- options
- )
+ const store = createSetupStore(id, setup, options)
store.$reset = $reset
>(
$id: Id,
setup: () => SS,
- options: DefineSetupStoreOptions<Id, S, G, A> = {}
+ options: DefineStoreOptions<Id, S, G, A> = {}
): Store<Id, S, G, A> {
const pinia = getActivePinia()
let scope!: EffectScope
- const hydrate = options.hydrate || innerPatch
- // @ts-expect-error
- const state = options.state
+ const buildState = (options as DefineOptionStoreOptions<Id, S, G, A>).state
+
+ const optionsForPlugin: DefineStoreOptionsInPlugin<Id, S, G, A> = {
+ actions: {} as A,
+ ...options,
+ }
// watcher options for $subscribe
const $subscribeOptions: WatchOptions = { deep: true, flush: 'sync' }
let subscriptions: SubscriptionCallback<S>[] = markRaw([])
let actionSubscriptions: StoreOnActionListener<Id, S, G, A>[] = markRaw([])
let debuggerEvents: DebuggerEvent[] | DebuggerEvent
- const initialState = pinia.state.value[$id] as S | undefined
+ const initialState = pinia.state.value[$id] as UnwrapRef<S> | undefined
if (!initialState) {
// should be set in Vue 2
}
// TODO: idea create skipSerialize that marks properties as non serializable and they are skipped
+ // TODO: store the scope somewhere
const setupStore = pinia._e.run(() => {
scope = effectScope()
return scope.run(() => {
function $reset() {
// TODO: is it worth? probably should be removed
- // maybe it can stop the effect and create it again
- // pinia.state.value[$id] = buildState()
+ // maybe it can stop the effect and create it again but should be a plugin
+ if (buildState) {
+ pinia.state.value[$id] = buildState()
+ } else if (__DEV__) {
+ throw new Error(
+ `🍍: Store "${$id}" is build using the setup syntax and does not implement $reset().`
+ )
+ }
}
// overwrite existing actions to support $onAction
const prop = setupStore[key]
if ((isRef(prop) && !isComputed(prop)) || isReactive(prop)) {
- // @ts-expect-error: fixme
- if (!options.state) {
+ // createOptionStore already did this
+ if (!buildState) {
// mark it as a piece of state to be serialized
pinia.state.value[$id][key] = toRef(setupStore as any, key)
}
return ret
}
+ // list actions so they can be used in plugins
+ // @ts-expect-error
+ optionsForPlugin.actions[key] = prop
} else if (__DEV__ && IS_CLIENT) {
// add getters for devtools
if (isComputed(prop)) {
store,
app: pinia._a,
pinia,
- // TODO: completely different options...
// @ts-expect-error
- options,
+ options: optionsForPlugin,
})
Object.keys(extensions || {}).forEach((key) =>
store._customProperties.add(key)
)
assign(store, extensions)
} else {
- // @ts-expect-error: conflict between A and ActionsTree
- assign(store, extender({ store, app: pinia._a, pinia, options }))
+ assign(
+ store,
+ extender({
+ // @ts-expect-error: conflict between A and ActionsTree
+ store,
+ app: pinia._a,
+ pinia,
+ // @ts-expect-error
+ options: optionsForPlugin,
+ })
+ )
}
})
if (initialState) {
- hydrate(store, initialState)
+ ;(options.hydrate || innerPatch)(store, initialState)
}
return store
G extends GettersTree<S>,
// cannot extends ActionsTree because we loose the typings
A /* extends ActionsTree */
->(options: DefineStoreOptions<Id, S, G, A>): StoreDefinition<Id, S, G, A> {
+>(
+ options: DefineOptionStoreOptions<Id, S, G, A>
+): StoreDefinition<Id, S, G, A> {
const { id } = options
function useStore(pinia?: Pinia | null) {
const spiedActions = new Map<string, Record<string, any>>()
pinia.use(({ store, options }) => {
- if (!spiedActions.has(options.id)) {
- spiedActions.set(options.id, {})
+ if (!spiedActions.has(store.$id)) {
+ spiedActions.set(store.$id, {})
}
- const actionsCache = spiedActions.get(options.id)!
+ const actionsCache = spiedActions.get(store.$id)!
Object.keys(options.actions || {}).forEach((action) => {
actionsCache[action] =
* Options parameter of `defineStore()`. Can be extended to augment stores with
* the plugin API.
*/
-export interface DefineStoreOptions<
+export interface DefineOptionStoreOptions<
Id extends string,
S extends StateTree,
G extends GettersTree<S>,
StoreWithGetters<G> &
PiniaCustomProperties
>
+
/**
* Optional object of actions.
*/
StoreWithGetters<GettersTree<S> extends G ? {} : G> &
PiniaCustomProperties
>
+
+ /**
+ * Allows hydrating the store during SSR when there is an available state in
+ * pinia.state.
+ *
+ * @param store - the store
+ * @param initialState - initialState
+ */
+ hydrate?(store: Store<Id, S, G, A>, initialState: UnwrapRef<S>): void
+}
+
+export interface DefineSetupStoreOptions<
+ Id extends string,
+ S extends StateTree,
+ G extends ActionsTree, // TODO: naming
+ A /* extends ActionsTree */
+> extends Pick<DefineOptionStoreOptions<Id, S, G, A>, 'hydrate'> {
+ /**
+ * Extracted actions. Added by useStore(). SHOULD NOT be added by the user when
+ * creating the store. Can be used in plugins to get the list of actions in a
+ * store defined with a setup function. Note this is always defined
+ */
+ actions?: A
+}
+
+/**
+ * Available `options` when creating a pinia plugin.
+ */
+export interface DefineStoreOptionsInPlugin<
+ Id extends string,
+ S extends StateTree,
+ G extends ActionsTree, // TODO: naming
+ A /* extends ActionsTree */
+> extends Omit<DefineOptionStoreOptions<Id, S, G, A>, 'id'> {
+ /**
+ * Extracted object of actions. Added by useStore() when the store is built
+ * using the setup API, otherwise uses the one passed to `defineStore()`.
+ * Defaults to an empty object if no actions are defined.
+ */
+ actions: A
+
+ /**
+ * Id of the store. Only available when the options API is used.
+ *
+ * @deprecated Use `store.$id` instead.
+ */
+ id?: Id
}
+export type DefineStoreOptions<
+ Id extends string,
+ S extends StateTree,
+ G extends GettersTree<S>,
+ A /* extends ActionsTree */
+> = DefineOptionStoreOptions<Id, S, G, A> | DefineSetupStoreOptions<Id, S, G, A>
+
export type _UnionToTuple<U> = _UnionToTupleRecursively<[], U>
type _Overwrite<T, S extends any> = {