Skip to content

Commit

Permalink
feat(action): added action triggers
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewcourtice committed Sep 8, 2021
1 parent 766c6f4 commit b58fc6a
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 2 deletions.
11 changes: 10 additions & 1 deletion extensions/action/src/constants.ts
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
export const SENDER = 'extension:action';
export const SENDER = 'extension:action';

export const EVENTS = {
action: {
before: 'action:before',
after: 'action:after',
success: 'action:success',
error: 'action:error',
},
} as const;
41 changes: 40 additions & 1 deletion extensions/action/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Task from '@harlem/task';

import {
SENDER,
EVENTS,
} from './constants';

import {
Expand All @@ -10,6 +11,7 @@ import {

import {
BaseState,
EventPayload,
InternalStore,
Mutator,
} from '@harlem/core';
Expand All @@ -21,6 +23,8 @@ import {
import type {
Action,
ActionBody,
ActionEventData,
ActionHookHandler,
ActionOptions,
ActionPredicate,
ActionStoreState,
Expand Down Expand Up @@ -86,6 +90,11 @@ export default function actionsExtension<TState extends BaseState>() {
};

const mutate = (mutator: Mutator<TState, undefined, void>) => _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) {
Expand All @@ -105,24 +114,33 @@ export default function actionsExtension<TState extends BaseState>() {
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) {
if (error instanceof DOMException) {
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);
Expand All @@ -133,6 +151,18 @@ export default function actionsExtension<TState extends BaseState>() {
}) as Action<TPayload, TResult>;
}

function getActionHook(eventName: string) {
return <TPayload = any, TResult = any>(actionName: string | string[], handler: ActionHookHandler<TPayload, TResult>) => {
const actions = ([] as string[]).concat(actionName);

return _store.on(eventName, (event?: EventPayload<ActionEventData<TPayload, TResult>>) => {
if (event && actions.includes(event.data.action)) {
handler(event.data);
}
});
};
}

function hasActionRun(name: string) {
return _store.state.$actions[name].runCount > 0;
}
Expand Down Expand Up @@ -192,6 +222,11 @@ export default function actionsExtension<TState extends BaseState>() {
}));
}

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,
Expand All @@ -200,6 +235,10 @@ export default function actionsExtension<TState extends BaseState>() {
hasActionFailed,
getActionErrors,
resetActionState,
onBeforeAction,
onAfterAction,
onActionSuccess,
onActionError,
};
};
}
7 changes: 7 additions & 0 deletions extensions/action/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Task, {
export type ActionBody<TState extends BaseState, TPayload = undefined, TResult = void> = (payload: TPayload, mutator: (mutate: Mutator<TState, undefined, void>) => void, controller: AbortController, onAbort: (callback: TaskAbortCallback) => void) => Promise<TResult>;
export type Action<TPayload, TResult = void> = undefined extends TPayload ? (payload?: TPayload, controller?: AbortController) => Task<TResult> : (payload: TPayload, controller?: AbortController) => Task<TResult>;
export type ActionPredicate<TPayload = unknown> = (payload?: TPayload) => boolean;
export type ActionHookHandler<TPayload, TResult> = (data: ActionEventData<TPayload, TResult>) => void;

export interface ActionTaskState {
runCount: number;
Expand All @@ -24,4 +25,10 @@ export interface ActionStoreState {
export interface ActionOptions {
parallel: boolean;
autoClearErrors: boolean;
}

export interface ActionEventData<TPayload = any, TResult = any> {
action: string;
payload: TPayload;
result?: TResult;
}

0 comments on commit b58fc6a

Please sign in to comment.