From b58fc6afbdbfb101c993260e844d2389aca35157 Mon Sep 17 00:00:00 2001 From: Andrew Courtice Date: Wed, 8 Sep 2021 11:21:53 +1000 Subject: [PATCH] feat(action): added action triggers --- extensions/action/src/constants.ts | 11 +++++++- extensions/action/src/index.ts | 41 +++++++++++++++++++++++++++++- extensions/action/src/types.ts | 7 +++++ 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/extensions/action/src/constants.ts b/extensions/action/src/constants.ts index 520e4a2b..1f0bad95 100644 --- a/extensions/action/src/constants.ts +++ b/extensions/action/src/constants.ts @@ -1 +1,10 @@ -export const SENDER = 'extension:action'; \ No newline at end of file +export const SENDER = 'extension:action'; + +export const EVENTS = { + action: { + before: 'action:before', + after: 'action:after', + success: 'action:success', + error: 'action:error', + }, +} as const; \ No newline at end of file diff --git a/extensions/action/src/index.ts b/extensions/action/src/index.ts index 8a62e76a..674033d7 100644 --- a/extensions/action/src/index.ts +++ b/extensions/action/src/index.ts @@ -2,6 +2,7 @@ import Task from '@harlem/task'; import { SENDER, + EVENTS, } from './constants'; import { @@ -10,6 +11,7 @@ import { import { BaseState, + EventPayload, InternalStore, Mutator, } from '@harlem/core'; @@ -21,6 +23,8 @@ import { import type { Action, ActionBody, + ActionEventData, + ActionHookHandler, ActionOptions, ActionPredicate, ActionStoreState, @@ -86,6 +90,11 @@ export default function actionsExtension() { }; const mutate = (mutator: Mutator) => _store.write(name, SENDER, mutator); + const emit = (event: string, payload: unknown, result?: unknown) => _store.emit(event, SENDER, { + action: name, + payload, + result, + } as ActionEventData); return ((payload: TPayload, controller?: AbortController) => { if (!parallel && tasks.size > 0) { @@ -105,13 +114,19 @@ export default function actionsExtension() { const complete = () => (tasks.delete(task), removeInstance(name, id)); const fail = () => reject(new ActionAbortError(name, id)); + let result: TResult | undefined; + onAbort(() => (complete(), fail())); addInstance(name, id, payload); + emit(EVENTS.action.before, payload); + try { const providedPayload = _store.providers.payload(payload) ?? payload; - const result = await body(providedPayload, mutate, controller, onAbort); + result = await body(providedPayload, mutate, controller, onAbort); + + emit(EVENTS.action.success, payload, result); incrementRunCount(name); resolve(result); } catch (error) { @@ -119,10 +134,13 @@ export default function actionsExtension() { return fail(); // Fetch has been cancelled } + emit(EVENTS.action.error, payload); + incrementRunCount(name); addError(name, id, error); reject(error); } finally { + emit(EVENTS.action.after, payload, result); complete(); } }, controller); @@ -133,6 +151,18 @@ export default function actionsExtension() { }) as Action; } + function getActionHook(eventName: string) { + return (actionName: string | string[], handler: ActionHookHandler) => { + const actions = ([] as string[]).concat(actionName); + + return _store.on(eventName, (event?: EventPayload>) => { + if (event && actions.includes(event.data.action)) { + handler(event.data); + } + }); + }; + } + function hasActionRun(name: string) { return _store.state.$actions[name].runCount > 0; } @@ -192,6 +222,11 @@ export default function actionsExtension() { })); } + const onBeforeAction = getActionHook(EVENTS.action.before); + const onAfterAction = getActionHook(EVENTS.action.after); + const onActionSuccess = getActionHook(EVENTS.action.success); + const onActionError = getActionHook(EVENTS.action.error); + return { action, hasActionRun, @@ -200,6 +235,10 @@ export default function actionsExtension() { hasActionFailed, getActionErrors, resetActionState, + onBeforeAction, + onAfterAction, + onActionSuccess, + onActionError, }; }; } \ No newline at end of file diff --git a/extensions/action/src/types.ts b/extensions/action/src/types.ts index 5742d305..4e7a7fe9 100644 --- a/extensions/action/src/types.ts +++ b/extensions/action/src/types.ts @@ -10,6 +10,7 @@ import Task, { export type ActionBody = (payload: TPayload, mutator: (mutate: Mutator) => void, controller: AbortController, onAbort: (callback: TaskAbortCallback) => void) => Promise; export type Action = undefined extends TPayload ? (payload?: TPayload, controller?: AbortController) => Task : (payload: TPayload, controller?: AbortController) => Task; export type ActionPredicate = (payload?: TPayload) => boolean; +export type ActionHookHandler = (data: ActionEventData) => void; export interface ActionTaskState { runCount: number; @@ -24,4 +25,10 @@ export interface ActionStoreState { export interface ActionOptions { parallel: boolean; autoClearErrors: boolean; +} + +export interface ActionEventData { + action: string; + payload: TPayload; + result?: TResult; } \ No newline at end of file