Skip to content

Commit

Permalink
feat(core): added basic action implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
andrewcourtice committed Jun 25, 2022
1 parent 1b617ac commit ede3d69
Show file tree
Hide file tree
Showing 5 changed files with 190 additions and 49 deletions.
6 changes: 6 additions & 0 deletions core/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ export const EVENTS = {
success: 'mutation:success',
error: 'mutation:error',
},
action: {
before: 'action:before',
after: 'action:after',
success: 'action:success',
error: 'action:error',
},
ssr: {
initServer: 'ssr:init:server',
initClient: 'ssr:init:client',
Expand Down
36 changes: 27 additions & 9 deletions core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,19 @@ import type {
} from 'vue';

import type {
ActionEventData,
BaseState,
EventPayload,
ExtendedStore,
Extension,
HarlemPlugin,
InternalStores,
MutationEventData,
MutationTriggerHandler,
PluginOptions,
Store,
PublicStore,
StoreOptions,
Trigger,
TriggerEventData,
TriggerHandler,
} from './types';

export {
Expand Down Expand Up @@ -111,7 +113,11 @@ function installPlugin(plugin: HarlemPlugin, app: App): void {
export const on = eventEmitter.on.bind(eventEmitter);
export const once = eventEmitter.once.bind(eventEmitter);

export function createStore<TState extends BaseState, TExtensions extends Extension<TState>[]>(name: string, state: TState, options?: Partial<StoreOptions<TState, TExtensions>>): Store<TState> & ExtendedStore<TExtensions> {
export function createStore<TState extends BaseState, TExtensions extends Extension<TState>[]>(
name: string,
state: TState,
options?: Partial<StoreOptions<TState, TExtensions>>
): PublicStore<TState, TExtensions> {
const {
allowOverwrite,
providers,
Expand All @@ -136,22 +142,29 @@ export function createStore<TState extends BaseState, TExtensions extends Extens
store.emit(EVENTS.devtools.update, SENDER, state);
};

const getMutationTrigger = (eventName: string) => {
return <TPayload = any, TResult = any>(mutationName: string | string[], handler: MutationTriggerHandler<TPayload, TResult>) => {
const mutations = ([] as string[]).concat(mutationName);
const getTrigger = <TEventData extends TriggerEventData>(eventName: string, prop: keyof TEventData): Trigger<TEventData> => {
return (name: string | string[], handler: TriggerHandler<TEventData>) => {
const mutations = ([] as string[]).concat(name);

return store.on(eventName, (event?: EventPayload<MutationEventData<TPayload, TResult>>) => {
if (event && mutations.includes(event.data.mutation)) {
return store.on(eventName, (event?: EventPayload<TEventData>) => {
if (event && mutations.includes(event.data[prop] as unknown as string)) {
handler(event.data);
}
});
};
};

const getMutationTrigger = (name: string) => getTrigger<MutationEventData>(name, 'mutation');
const getActionTrigger = (name: string) => getTrigger<ActionEventData>(name, 'action');

const onBeforeMutation = getMutationTrigger(EVENTS.mutation.before);
const onAfterMutation = getMutationTrigger(EVENTS.mutation.after);
const onMutationSuccess = getMutationTrigger(EVENTS.mutation.success);
const onMutationError = getMutationTrigger(EVENTS.mutation.error);
const onBeforeAction = getActionTrigger(EVENTS.action.before);
const onAfterAction = getActionTrigger(EVENTS.action.after);
const onActionSuccess = getActionTrigger(EVENTS.action.success);
const onActionError = getActionTrigger(EVENTS.action.error);

const extendedStore = getExtendedStore<TState, TExtensions>(store, extensions);

Expand All @@ -164,9 +177,14 @@ export function createStore<TState extends BaseState, TExtensions extends Extens
onAfterMutation,
onMutationSuccess,
onMutationError,
onBeforeAction,
onAfterAction,
onActionSuccess,
onActionError,
state: store.state,
getter: store.getter.bind(store),
mutation: store.mutation.bind(store),
action: store.action.bind(store),
suppress: store.suppress.bind(store),
on: store.on.bind(store),
once: store.once.bind(store),
Expand Down
41 changes: 40 additions & 1 deletion core/src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import {
} from 'vue';

import type {
Action,
ActionBody,
ActionEventData,
BaseState,
EventHandler,
EventListener,
Expand Down Expand Up @@ -229,10 +232,46 @@ export default class Store<TState extends BaseState = any> implements InternalSt
return mutation;
}

public action<TPayload, TResult = void>(name: string, body: ActionBody<TState, TPayload, TResult>): Action<TPayload, TResult> {
const mutate = (mutator: Mutator<TState, undefined, void>) => this.write(name, SENDER, mutator);

const action = (async (payload: TPayload) => {
let result: TResult;

const emit = (event: string) => this.emit(event, SENDER, {
action: name,
payload,
result,
} as ActionEventData);

emit(EVENTS.action.before);

try {
const providedPayload = this.providers.payload(payload) ?? payload;

result = await body(providedPayload, mutate);
emit(EVENTS.action.success);
} catch (error) {
emit(EVENTS.action.error);
throw error;
} finally {
emit(EVENTS.action.after);
}

return result;
}) as Action<TPayload, TResult>;

this.register('actions', name, () => action);

return action;
}

public write<TResult = void>(name: string, sender: string, mutator: Mutator<TState, undefined, TResult>, suppress?: boolean): TResult {
const mutation = () => this.mutate(name, sender, mutator, undefined);

return (suppress ? () => this.suppress(mutation) : mutation)();
return suppress
? this.suppress(mutation)
: mutation();
}

public destroy(): void {
Expand Down
74 changes: 36 additions & 38 deletions core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import type {
DeepReadonly,
} from 'vue';

type UnionToIntersection<U> = (U extends any ? (arg: U) => any : never) extends ((arg: infer I) => void) ? I : never;
type UnionToIntersection<TValue> = (TValue extends any ? (arg: TValue) => any : never) extends ((arg: infer I) => void) ? I : never;

export type BaseState = object;
//export type BaseState = object;
export type BaseState = Record<PropertyKey, any>;
export type StoreProvider<TState extends BaseState> = keyof StoreProviders<TState>;
export type ReadState<TState extends BaseState> = DeepReadonly<TState>;
export type WriteState<TState extends BaseState> = TState;
Expand All @@ -16,11 +17,16 @@ export type RegistrationValueProducer = () => unknown;
export type Getter<TState extends BaseState, TResult> = (state: ReadState<TState>) => TResult;
export type Mutator<TState extends BaseState, TPayload, TResult = void> = (state: WriteState<TState>, payload: TPayload) => TResult;
export type Mutation<TPayload, TResult = void> = undefined extends TPayload ? (payload?: TPayload) => TResult : (payload: TPayload) => TResult;
export type InternalStores = Map<string, InternalStore<any>>;
export type ActionBody<TState extends BaseState, TPayload = undefined, TResult = void> = (payload: TPayload, mutator: (mutate: Mutator<TState, undefined, void>) => void) => Promise<TResult>;
export type Action<TPayload, TResult = void> = undefined extends TPayload ? (payload?: TPayload) => Promise<TResult> : (payload: TPayload) => Promise<TResult>;
export type EventHandler<TData = any> = (payload?: EventPayload<TData>) => void;
export type MutationTriggerHandler<TPayload, TResult> = (data: MutationEventData<TPayload, TResult>) => void;
export type TriggerHandler<TEventData extends TriggerEventData> = (data: TEventData) => void;
export type Trigger<TEventData extends TriggerEventData> = (name: string | string[], handler: TriggerHandler<TEventData>) => EventListener;
export type InternalStores = Map<string, InternalStore<any>>;
export type Extension<TState extends BaseState> = (store: InternalStore<TState>) => Record<string, any>;
export type ExtendedStore<TExtensions extends Extension<any>[]> = UnionToIntersection<ReturnType<TExtensions[number]>>;
export type ExtensionAPIs<TExtensions extends Extension<any>[]> = UnionToIntersection<ReturnType<TExtensions[number]>>;
export type PublicStore<TState extends BaseState, TExtensions extends Extension<TState>[]> = Store<TState> & ExtensionAPIs<TExtensions>;
// export type PublicStore<TState extends BaseState, TExtensions extends Extension<TState>[]> = Omit<Store<TState>, keyof ExtensionAPIs<TExtensions>> & ExtensionAPIs<TExtensions>

export interface Emittable {
on(event: string, handler: EventHandler): EventListener;
Expand All @@ -39,12 +45,19 @@ export interface EventPayload<TData = any> {
data: TData;
}

export interface MutationEventData<TPayload = any, TResult = any> {
mutation: string;
export interface TriggerEventData<TPayload = any, TResult = any> {
payload: TPayload;
result?: TResult;
}

export interface MutationEventData<TPayload = any, TResult = any> extends TriggerEventData<TPayload, TResult> {
mutation: string;
}

export interface ActionEventData<TPayload = any, TResult = any> extends TriggerEventData<TPayload, TResult> {
action: string;
}

export interface StoreRegistration {
type: RegistrationType;
producer: RegistrationValueProducer;
Expand All @@ -67,6 +80,14 @@ export interface StoreBase<TState extends BaseState> {
*/
mutation<TPayload, TResult = void>(name: string, mutator: Mutator<TState, TPayload, TResult>): Mutation<TPayload, TResult>;

/**
* Register an action on this store
*
* @param name - The name of this action
* @param body - The function to execute as part of this action. This function receives a payload and mutator function as it's parameters.
*/
action<TPayload, TResult = void>(name: string, body: ActionBody<TState, TPayload, TResult>): Action<TPayload, TResult>;

/**
* Listen to an event on this store. This is useful for creating triggers.
*
Expand Down Expand Up @@ -257,37 +278,14 @@ export interface Store<TState extends BaseState> extends StoreBase<TState> {
*/
state: ReadState<TState>;

/**
* A convenience method to register triggers for before mutation events
*
* @param mutationName - The name(s) of the mutation(s) to listen to
* @param handler - The handler that will be called when this event is triggered
*/
onBeforeMutation<TPayload = any, TResult = any>(mutationName: string | string[], handler: MutationTriggerHandler<TPayload, TResult>): EventListener;

/**
* A convenience method to register triggers for after mutation events
*
* @param mutationName - The name(s) of the mutation(s) to listen to
* @param handler - The handler that will be called when this event is triggered
*/
onAfterMutation<TPayload = any, TResult = any>(mutationName: string | string[], handler: MutationTriggerHandler<TPayload, TResult>): EventListener;

/**
* A convenience method to register triggers for mutation success events
*
* @param mutationName - The name(s) of the mutation(s) to listen to
* @param handler - The handler that will be called when this event is triggered
*/
onMutationSuccess<TPayload = any, TResult = any>(mutationName: string | string[], handler: MutationTriggerHandler<TPayload, TResult>): EventListener;

/**
* A convenience method to register triggers for mutation error events
*
* @param mutationName - The name(s) of the mutation(s) to listen to
* @param handler - The handler that will be called when this event is triggered
*/
onMutationError<TPayload = any, TResult = any>(mutationName: string | string[], handler: MutationTriggerHandler<TPayload, TResult>): EventListener;
onBeforeMutation: Trigger<MutationEventData>;
onAfterMutation: Trigger<MutationEventData>;
onMutationSuccess: Trigger<MutationEventData>;
onMutationError: Trigger<MutationEventData>;
onBeforeAction: Trigger<ActionEventData>;
onAfterAction: Trigger<ActionEventData>;
onActionSuccess: Trigger<ActionEventData>;
onActionError: Trigger<ActionEventData>;
}

export interface HarlemPlugin {
Expand Down
Loading

0 comments on commit ede3d69

Please sign in to comment.