From: Eduardo San Martin Morote Date: Fri, 9 Jul 2021 18:48:29 +0000 (+0200) Subject: refactor(plugins): make it work with actions X-Git-Tag: v2.0.0-rc.0~83 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1e56c008c170a767443d8236bda5f3e6f4e08e81;p=thirdparty%2Fvuejs%2Fpinia.git refactor(plugins): make it work with actions --- diff --git a/__tests__/ssr.spec.ts b/__tests__/ssr.spec.ts index 9860ec60..7d0a417b 100644 --- a/__tests__/ssr.spec.ts +++ b/__tests__/ssr.spec.ts @@ -9,7 +9,7 @@ import { useCartStore } from './pinia/stores/cart' describe('SSR', () => { const App = { - ssrRender(ctx: any, push: any, parent: any) { + ssrRender(ctx: any, push: any, _parent: any) { push( `
${ssrInterpolate(ctx.user.name)}: ${ssrInterpolate( ctx.cart.items diff --git a/__tests__/storePlugins.spec.ts b/__tests__/storePlugins.spec.ts index 89621e49..313571c3 100644 --- a/__tests__/storePlugins.spec.ts +++ b/__tests__/storePlugins.spec.ts @@ -1,6 +1,6 @@ -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 { @@ -156,4 +156,58 @@ describe('store plugins', () => { 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) { + 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() + }) }) diff --git a/__tests__/storeSetup.spec.ts b/__tests__/storeSetup.spec.ts index 6431f9c4..df617d90 100644 --- a/__tests__/storeSetup.spec.ts +++ b/__tests__/storeSetup.spec.ts @@ -39,6 +39,18 @@ describe('store with setup syntax', () => { 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() @@ -55,17 +67,6 @@ describe('store with setup syntax', () => { 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() diff --git a/src/devtools/plugin.ts b/src/devtools/plugin.ts index 82b55e45..ade52b4e 100644 --- a/src/devtools/plugin.ts +++ b/src/devtools/plugin.ts @@ -368,7 +368,7 @@ export function devtoolsPlugin< A /* extends ActionsTree */ = ActionsTree >({ app, store, options, pinia }: PiniaPluginContext) { // 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 diff --git a/src/mapHelpers.ts b/src/mapHelpers.ts index 5f04f0e8..7d4a5eba 100644 --- a/src/mapHelpers.ts +++ b/src/mapHelpers.ts @@ -273,7 +273,8 @@ export function mapState< // function return typeof storeKey === 'function' ? (storeKey as (store: Store) => any).call(this, store) - : store[storeKey as keyof typeof store] + : // @ts-expect-error + store[storeKey] } return reduced }, {} as _MapStateObjectReturn) diff --git a/src/rootStore.ts b/src/rootStore.ts index a5d276f9..a1402c9b 100644 --- a/src/rootStore.ts +++ b/src/rootStore.ts @@ -11,6 +11,7 @@ import { ActionsTree, PiniaCustomStateProperties, GenericStore, + DefineStoreOptionsInPlugin, } from './types' /** @@ -162,7 +163,7 @@ export interface PiniaPluginContext< /** * Current store being extended. */ - options: DefineStoreOptions + options: DefineStoreOptionsInPlugin } /** diff --git a/src/store.ts b/src/store.ts index 43c53dd6..931543b3 100644 --- a/src/store.ts +++ b/src/store.ts @@ -32,6 +32,9 @@ import { ActionsTree, SubscriptionCallbackMutation, _UnionToTuple, + DefineOptionStoreOptions, + DefineSetupStoreOptions, + DefineStoreOptionsInPlugin, } from './types' import { getActivePinia, @@ -68,15 +71,6 @@ function innerPatch( const { assign } = Object -export interface DefineSetupStoreOptions< - Id extends string, - S extends StateTree, - G extends ActionsTree, // TODO: naming - A extends ActionsTree -> { - hydrate?(store: Store, initialState: S | undefined): void -} - function isComputed(o: any): o is ComputedRef { return o && o.effect && o.effect.computed } @@ -86,7 +80,10 @@ function createOptionsStore< S extends StateTree, G extends GettersTree, A extends ActionsTree ->(options: DefineStoreOptions, pinia: Pinia): Store { +>( + options: DefineOptionStoreOptions, + pinia: Pinia +): Store { const { id, state, actions, getters } = options function $reset() { pinia.state.value[id] = state ? state() : {} @@ -110,13 +107,7 @@ function createOptionsStore< ) } - 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 @@ -134,13 +125,16 @@ function createSetupStore< >( $id: Id, setup: () => SS, - options: DefineSetupStoreOptions = {} + options: DefineStoreOptions = {} ): Store { const pinia = getActivePinia() let scope!: EffectScope - const hydrate = options.hydrate || innerPatch - // @ts-expect-error - const state = options.state + const buildState = (options as DefineOptionStoreOptions).state + + const optionsForPlugin: DefineStoreOptionsInPlugin = { + actions: {} as A, + ...options, + } // watcher options for $subscribe const $subscribeOptions: WatchOptions = { deep: true, flush: 'sync' } @@ -168,7 +162,7 @@ function createSetupStore< let subscriptions: SubscriptionCallback[] = markRaw([]) let actionSubscriptions: StoreOnActionListener[] = markRaw([]) let debuggerEvents: DebuggerEvent[] | DebuggerEvent - const initialState = pinia.state.value[$id] as S | undefined + const initialState = pinia.state.value[$id] as UnwrapRef | undefined if (!initialState) { // should be set in Vue 2 @@ -187,6 +181,7 @@ function createSetupStore< } // 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(() => { @@ -291,8 +286,14 @@ function createSetupStore< 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 @@ -300,8 +301,8 @@ function createSetupStore< 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) } @@ -343,6 +344,9 @@ function createSetupStore< 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)) { @@ -391,22 +395,30 @@ function createSetupStore< 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 @@ -548,7 +560,9 @@ export function defineStore< G extends GettersTree, // cannot extends ActionsTree because we loose the typings A /* extends ActionsTree */ ->(options: DefineStoreOptions): StoreDefinition { +>( + options: DefineOptionStoreOptions +): StoreDefinition { const { id } = options function useStore(pinia?: Pinia | null) { diff --git a/src/testing.ts b/src/testing.ts index 18c3efef..75db70d2 100644 --- a/src/testing.ts +++ b/src/testing.ts @@ -85,10 +85,10 @@ export function createTestingPinia({ const spiedActions = new Map>() 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] = diff --git a/src/types.ts b/src/types.ts index d1eff336..a69ffba5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -455,7 +455,7 @@ export type ActionsTree = Record * 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, @@ -480,6 +480,7 @@ export interface DefineStoreOptions< StoreWithGetters & PiniaCustomProperties > + /** * Optional object of actions. */ @@ -491,8 +492,62 @@ export interface DefineStoreOptions< StoreWithGetters 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, initialState: UnwrapRef): void +} + +export interface DefineSetupStoreOptions< + Id extends string, + S extends StateTree, + G extends ActionsTree, // TODO: naming + A /* extends ActionsTree */ +> extends Pick, '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, '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, + A /* extends ActionsTree */ +> = DefineOptionStoreOptions | DefineSetupStoreOptions + export type _UnionToTuple = _UnionToTupleRecursively<[], U> type _Overwrite = {