type _ArrayType<AT> = AT extends Array<infer T> ? T : never
+/**
+ * Marks a function as an action for `$onAction`
+ * @internal
+ */
+const ACTION_MARKER = Symbol()
+/**
+ * Action name symbol. Allows to add a name to an action after defining it
+ * @internal
+ */
+const ACTION_NAME = Symbol()
+/**
+ * Function type extended with action markers
+ * @internal
+ */
+interface MarkedAction<Fn extends _Method = _Method> {
+ (...args: Parameters<Fn>): ReturnType<Fn>
+ [ACTION_MARKER]: boolean
+ [ACTION_NAME]: string
+}
+
function mergeReactiveObjects<
T extends Record<any, unknown> | Map<unknown, unknown> | Set<unknown>,
>(target: T, patchToApply: _DeepPartial<T>): T {
A extends _ActionsTree,
>(
$id: Id,
- setup: () => SS,
+ setup: (helpers: SetupStoreHelpers) => SS,
options:
| DefineSetupStoreOptions<Id, S, G, A>
| DefineStoreOptions<Id, S, G, A> = {},
}
/**
- * Wraps an action to handle subscriptions.
- *
+ * Helper that wraps function so it can be tracked with $onAction
+ * @param fn - action to wrap
* @param name - name of the action
- * @param action - action to wrap
- * @returns a wrapped action to handle subscriptions
*/
- function wrapAction(name: string, action: _Method) {
- return function (this: any) {
+ const action = <Fn extends _Method>(fn: Fn, name: string = ''): Fn => {
+ if (ACTION_MARKER in fn) {
+ // we ensure the name is set from the returned function
+ ;(fn as unknown as MarkedAction<Fn>)[ACTION_NAME] = name
+ return fn
+ }
+
+ const wrappedAction = function (this: any) {
setActivePinia(pinia)
const args = Array.from(arguments)
// @ts-expect-error
triggerSubscriptions(actionSubscriptions, {
args,
- name,
+ name: wrappedAction[ACTION_NAME],
store,
after,
onError,
let ret: unknown
try {
- ret = action.apply(this && this.$id === $id ? this : store, args)
+ ret = fn.apply(this && this.$id === $id ? this : store, args)
// handle sync errors
} catch (error) {
triggerSubscriptions(onErrorCallbackList, error)
// trigger after callbacks
triggerSubscriptions(afterCallbackList, ret)
return ret
- }
+ } as MarkedAction<Fn>
+
+ wrappedAction[ACTION_MARKER] = true
+ wrappedAction[ACTION_NAME] = name // will be set later
+
+ // @ts-expect-error: we are intentionally limiting the returned type to just Fn
+ // because all the added properties are internals that are exposed through `$onAction()` only
+ return wrappedAction
}
const _hmrPayload = /*#__PURE__*/ markRaw({
// TODO: idea create skipSerialize that marks properties as non serializable and they are skipped
const setupStore = runWithContext(() =>
- pinia._e.run(() => (scope = effectScope()).run(setup)!)
+ pinia._e.run(() => (scope = effectScope()).run(() => setup({ action }))!)
)!
// overwrite existing actions to support $onAction
}
// action
} else if (typeof prop === 'function') {
- // @ts-expect-error: we are overriding the function we avoid wrapping if
- const actionValue = __DEV__ && hot ? prop : wrapAction(key, prop)
+ const actionValue = __DEV__ && hot ? prop : action(prop as _Method, key)
// this a hot module replacement store because the hotUpdate method needs
// to do it with the right context
/* istanbul ignore if */
})
for (const actionName in newStore._hmrPayload.actions) {
- const action: _Method = newStore[actionName]
+ const actionFn: _Method = newStore[actionName]
- set(store, actionName, wrapAction(actionName, action))
+ set(store, actionName, action(actionFn, actionName))
}
// TODO: does this work in both setup and option store?
? UnwrapRef<S>
: _ExtractStateFromSetupStore<SS>
-// type a1 = _ExtractStateFromSetupStore<{ a: Ref<number>; action: () => void }>
-// type a2 = _ExtractActionsFromSetupStore<{ a: Ref<number>; action: () => void }>
-// type a3 = _ExtractGettersFromSetupStore<{
-// a: Ref<number>
-// b: ComputedRef<string>
-// action: () => void
-// }>
+export interface SetupStoreHelpers {
+ action: <Fn extends _Method>(fn: Fn) => Fn
+}
/**
* Creates a `useStore` function that retrieves the store instance
*/
export function defineStore<Id extends string, SS>(
id: Id,
- storeSetup: () => SS,
+ storeSetup: (helpers: SetupStoreHelpers) => SS,
options?: DefineSetupStoreOptions<
Id,
_ExtractStateFromSetupStore<SS>,
import { acceptHMRUpdate, defineStore } from 'pinia'
import { getNASAPOD } from '../api/nasa'
-export const useNasaStore = defineStore('nasa-pod-swrv', () => {
+export const useNasaStore = defineStore('nasa-pod-swrv', ({ action }) => {
// can't go past today
const today = new Date().toISOString().slice(0, 10)
}
)
- function incrementDay(date: string) {
+ const incrementDay = action((date: string) => {
const from = new Date(date).getTime()
currentDate.value = new Date(from + 1000 * 60 * 60 * 24)
.toISOString()
.slice(0, 10)
- }
+ })
- function decrementDay(date: string) {
+ const decrementDay = action((date: string) => {
const from = new Date(date).getTime()
currentDate.value = new Date(from - 1000 * 60 * 60 * 24)
.toISOString()
.slice(0, 10)
- }
+ })
return { image, currentDate, incrementDay, decrementDay, error, isValidating }
})