GettersTree,
DefineStoreOptions,
GenericStore,
+ StoreOnActionListener,
} from './types'
import { useStoreDevtools } from './devtools'
import {
let isListening = true
const subscriptions: SubscriptionCallback<S>[] = []
+ const actionSubscriptions: StoreOnActionListener[] = []
function $patch(stateMutation: (state: S) => void): void
function $patch(partialState: DeepPartial<S>): void
return removeSubscription
}
+ function $onAction(callback: StoreOnActionListener) {
+ actionSubscriptions.push(callback)
+
+ const removeSubscription = () => {
+ const idx = actionSubscriptions.indexOf(callback)
+ if (idx > -1) {
+ actionSubscriptions.splice(idx, 1)
+ }
+ }
+
+ if (getCurrentInstance()) {
+ onUnmounted(removeSubscription)
+ }
+
+ return removeSubscription
+ }
+
function $reset() {
pinia.state.value[$id] = buildState()
}
const storeWithState: StoreWithState<Id, S> = {
$id,
_p: markRaw(pinia),
+ _as: actionSubscriptions,
// $state is added underneath
$patch,
$subscribe,
+ $onAction,
$reset,
} as StoreWithState<Id, S>
]
}
+const noop = () => {}
/**
* Creates a store bound to the lifespan of where the function is called. This
* means creating the store inside of a component's setup will bound it to the
const wrappedActions: StoreWithActions<A> = {} as StoreWithActions<A>
for (const actionName in actions) {
- wrappedActions[actionName] = function () {
+ wrappedActions[actionName] = function (this: Store<Id, S, G, A>) {
setActivePinia(pinia)
- // eslint-disable-next-line
- return actions[actionName].apply(store, (arguments as unknown) as any[])
+ /* eslint-disable-next-line */
+ const args = Array.from(arguments)
+ const localStore = this || store
+
+ let afterCallback: () => void = noop
+ let onErrorCallback: (error: unknown) => void = noop
+ function after(callback: () => void) {
+ afterCallback = callback
+ }
+ function onError(callback: (error: unknown) => void) {
+ onErrorCallback = callback
+ }
+
+ partialStore._as.forEach((callback) => {
+ callback({ args, name: actionName, store: localStore, after, onError })
+ })
+
+ let ret
+
+ try {
+ ret = actions[actionName].apply(localStore, args as unknown as any[])
+ Promise.resolve(ret).then(afterCallback).catch(onErrorCallback)
+ } catch (error) {
+ throw error
+ }
+
+ return ret
} as StoreWithActions<A>[typeof actionName]
}
export type DeepPartial<T> = { [K in keyof T]?: DeepPartial<T[K]> }
// type DeepReadonly<T> = { readonly [P in keyof T]: DeepReadonly<T[P]> }
+/**
+ * Possible types for SubscriptionCallback
+ */
+export enum MutationType {
+ /**
+ * Direct mutation of the state:
+ *
+ * - `store.name = 'new name'`
+ * - `store.$state.name = 'new name'`
+ * - `store.list.push('new item')`
+ */
+ direct = 'direct',
+
+ /**
+ * Mutated the state with `$patch` and an object
+ *
+ * - `store.$patch({ name: 'newName' })`
+ */
+ patchObject = 'patch object',
+
+ /**
+ * Mutated the state with `$patch` and a function
+ *
+ * - `store.$patch(state => state.name = 'newName')`
+ */
+ patchFunction = 'patch function',
+
+ // maybe reset? for $state = {} and $reset
+}
+
+/**
+ * Context object passed to callbacks of `store.$onAction(context => {})`
+ */
+export interface StoreOnActionListenerContext {
+ /**
+ * Sets up a hook once the action is finished.
+ */
+ after: (callback: () => void) => void
+
+ /**
+ * Sets up a hook if the action fails.
+ */
+ onError: (callback: (error: unknown) => void) => void
+
+ // TODO: pass generics
+ /**
+ * Store that is invoking the action
+ */
+ store: GenericStore
+
+ /**
+ * Name of the action
+ */
+ name: string
+
+ /**
+ * Parameters passed to the action
+ */
+ args: any[]
+}
+
+/**
+ * Argument of `store.$onAction()`
+ */
+export type StoreOnActionListener = (
+ context: StoreOnActionListenerContext
+) => void
+
+/**
+ * Callback of a subscription
+ */
export type SubscriptionCallback<S> = (
mutation: { storeName: string; type: string; payload: DeepPartial<S> },
state: S
$reset(): void
/**
- * Setups a callback to be called whenever the state changes.
+ * Setups a callback to be called whenever the state changes. It also returns
+ * a function to remove the callback. Note than when calling
+ * `store.$subscribe()` inside of a component, it will be automatically
+ * cleanup up when the component gets unmounted.
*
* @param callback - callback passed to the watcher
+ * @param onTrigger - DEV ONLY watcher debugging
+ * (https://v3.vuejs.org/guide/reactivity-computed-watchers.html#watcher-debugging)
+ * @returns function that removes the watcher
+ */
+ $subscribe(
+ callback: SubscriptionCallback<S>,
+ onTrigger?: (event: any) => void
+ ): () => void
+
+ /**
+ * Array of registered action subscriptions.
+ *
+ * @internal
+ */
+ _as: StoreOnActionListener[]
+
+ /**
+ * @alpha Please send feedback at https://github.com/posva/pinia/issues/240
+ * Setups a callback to be called every time an action is about to get
+ * invoked. The callback receives an object with all the relevant information
+ * of the invoked action:
+ * - `store`: the store it is invoked on
+ * - `name`: The name of the action
+ * - `args`: The parameters passed to the action
+ *
+ * On top of these, it receives two functions that allow setting up a callback
+ * once the action finishes or when it fails.
+ *
+ * It also returns a function to remove the callback. Note than when calling
+ * `store.$onAction()` inside of a component, it will be automatically cleanup
+ * up when the component gets unmounted.
+ *
+ * @example
+ *
+ *```js
+ *store.$onAction(({ after, onError }) => {
+ * // Here you could share variables between all of the hooks as well as
+ * // setting up watchers and clean them up
+ * after(() => {
+ * // can be used to cleanup side effects
+ * })
+ * onError((error) => {
+ * // can be used to pass up errors
+ * })
+ *})
+ *```
+ *
+ * @param callback - callback called before every action
* @returns function that removes the watcher
*/
- $subscribe(callback: SubscriptionCallback<S>): () => void
+ $onAction(callback: StoreOnActionListener): () => void
}
/**