UNPKG

state-decorator

Version:
387 lines (386 loc) 16.1 kB
export type PromiseResult<Type> = Type extends Promise<infer X> ? X : null; export type NotifyFunc = (msg: string) => void; export type PromiseIdMap = { [promiseId: string]: boolean; }; export type LoadingMap<A> = { [pId in keyof A]?: boolean; }; export type LoadingParallelMap<A> = { [P in keyof A]?: PromiseIdMap; }; export type InternalLoadingMap<A> = LoadingParallelMap<A>; export type LoadingProps<A> = { loading: boolean; loadingMap: LoadingMap<A>; loadingParallelMap: LoadingParallelMap<A>; }; export type LoadingMapProps<A> = Pick<LoadingProps<A>, 'loadingMap'>; export type PromiseIdErrorMap = { [promiseId: string]: Error; }; export type ErrorMap<A> = { [pId in keyof A]?: Error; }; export type ErrorParallelMap<A> = { [P in keyof A]?: PromiseIdErrorMap; }; export type ErrorProps<A> = { errorMap: ErrorMap<A>; }; export interface DecoratedActions { [name: string]: (...args: any[]) => Promise<any> | void; } /** * The State decorator can manage only one asynchronous action of same name at a time. * This enum is enumertaing all the policies in such case. */ export declare enum ConflictPolicy { /** * Reject the promise. */ REJECT = "reject", /** * Return a resolved promise with no result. */ IGNORE = "ignore", /** * Execute only the last call that conflicts after the one ongoing. */ KEEP_LAST = "keepLast", /** * Execute all calls one after another. */ KEEP_ALL = "keepAll", /** * Execute all calls one after another. * @see getPromiseId */ PARALLEL = "parallel", /** * Reuse the ongoing promise if any and if possible (same arguments). Only for GET requests! */ REUSE = "reuse", /** * Abort current promise if it's marked as abortable (make sure to manage properly the rejection) */ ABORT = "abort" } export type PromiseProvider<S, DS, F extends (...args: any[]) => any, A, P> = (ctx: GetPromiseInvocationContext<S, DS, F, P, A>) => ReturnType<F> | null; /** * Simple form of a synchronous action. */ export type SimpleSyncAction<S, F extends (...args: any[]) => any, P, DS, FxRes = Partial<S>> = (ctx: InvocationContext<S, DS, F, P>) => FxRes | null; export type SyncAction<S, F extends (...args: any[]) => any, A, P, DS, FxRes = Partial<S>> = { /** * Function that update the state when the action is called. */ effects?: (ctx: EffectsInvocationContext<S, DS, F, P>) => FxRes | null; /** * Debounces the action if this parameter is defined. */ debounceTimeout?: number; /** * Debounces the action side effects if this parameter is defined. */ debounceSideEffectsTimeout?: number; /** * Function that calls side effects, ie. effects that are not changing the state directly. */ sideEffects?: (ctx: SideEffectsInvocationContext<S, DS, F, P, A>) => void; }; /** * Try to abort an action, if the action is <code>abortable</code>. * @param actionName The action name * @param promiseId If the action conflict type is <code>ConflictType.PARALLEL</code>, the identifier of the promise. * @returns <code>true</code> if the action is abortable and an action is ongoing, <code>false</code> otherwise. */ export type AbortActionCallback<A> = (actionName: keyof A, promiseId?: string) => boolean; export type GetErrorMessage<S, DS, F extends (...args: any[]) => any, P> = (ctx: ErrorMessageInvocationContext<S, DS, F, P>) => string | null; export type ContextState<S> = { state: S; s: S; }; export type ContextDerivedStateState<DS> = { derived: DS; ds: DS; }; export type ContextBase<S, P> = ContextState<S> & { props: P; p: P; }; export type ContextWithDerived<S, DS, P> = ContextBase<S, P> & ContextDerivedStateState<DS>; export type InvocationContextActions<A> = { actions: A; a: A; }; export type WarningNotifyFunc = { notifyWarning?: NotifyFunc; }; export type InvocationContext<S, DS, F extends (...args: any[]) => any, P> = ContextWithDerived<S, DS, P> & { args: Parameters<F>; promiseId: string; }; export type EffectsInvocationContext<S, DS, F extends (...args: any[]) => any, P> = InvocationContext<S, DS, F, P> & { result: PromiseResult<ReturnType<F>>; res: PromiseResult<ReturnType<F>>; }; export type GetPromiseInvocationContext<S, DS, F extends (...args: any[]) => any, P, A> = InvocationContextActions<A> & InvocationContext<S, DS, F, P> & { abortSignal: AbortSignal; }; export type ErrorEffectsInvocationContext<S, DS, F extends (...args: any[]) => any, P> = InvocationContext<S, DS, F, P> & { error: Error; err: Error; }; export type SuccessMessageInvocationContext<S, DS, F extends (...args: any[]) => any, P> = EffectsInvocationContext<S, DS, F, P>; export type ErrorMessageInvocationContext<S, DS, F extends (...args: any[]) => any, P> = ErrorEffectsInvocationContext<S, DS, F, P>; export type ClearErrorFunc<A> = (actionName: keyof A, promiseId?: string) => void; export type ClearError<A> = { clearError: ClearErrorFunc<A>; }; export type SideEffectsInvocationContext<S, DS, F extends (...args: any[]) => any, P, A> = EffectsInvocationContext<S, DS, F, P> & InvocationContextActions<A> & WarningNotifyFunc & ClearError<A>; export type ErrorSideEffectsInvocationContext<S, DS, F extends (...args: any[]) => any, P, A> = ErrorEffectsInvocationContext<S, DS, F, P> & InvocationContextActions<A> & WarningNotifyFunc; export type OnMountInvocationContext<S, A, P> = ContextBase<S, P> & InvocationContextActions<A>; export type AbortedActions<A> = Partial<Record<keyof A, string[]>>; export type OnUnMountInvocationContext<S, A, P> = ContextBase<S, P> & { abortedActions: AbortedActions<A>; }; export type EffectsType = 'preEffects' | 'effects' | 'errorEffects' | 'optimisticEffects' | null; export type MiddlewareActionContext<S, A, P> = { name: keyof A; type: EffectsType; isAsync: boolean; context: InvocationContext<S, any, any, P>; }; export type MiddlewareStoreContext<S, A extends DecoratedActions, P> = { actions: StoreActions<S, A, P, any, any>; state: S; setState: (s: S) => void; options: StoreOptions<S, A, P, any>; }; export type MiddlewareFactory<S, A extends DecoratedActions, P> = () => Middleware<S, A, P>; /** * A state change middleware. * Allow to listen and change a state change on the fly. */ export type Middleware<S, A extends DecoratedActions, P> = { /** * Initializes the middleware. Called when the store is initializing. */ init: (storeContext: MiddlewareStoreContext<S, A, P>) => void; /** * Invoked on a state change has occurred and returns a new state or <code>null</code> if unchanged. */ effects: (actionContext: MiddlewareActionContext<S, A, P>, oldState: S, newState: S, loading: boolean) => null | { state: S; loading: boolean; }; /** * Destroys the middleware. Called when the store is being destroyed. */ destroy: () => void; }; export type AsyncActionBase<S, F extends (...args: any[]) => any, A, P, DS, FxRes = Partial<S>> = { /** * This action can be aborted. An abortSignal will be injected to the <code>promise</code> / <code>promiseGet</code>. */ abortable?: boolean; /** * A function that provides the success message to pass to the notifySuccess function passed as property to the StateDecorator. */ getSuccessMessage?: (ctx: SuccessMessageInvocationContext<S, DS, F, P>) => string; /** * A function that provides the error message to pass to the notifyError function passed as property to the StateDecorator. */ getErrorMessage?: GetErrorMessage<S, DS, F, P>; /** * If set, called with the result of the promise to update the current state. */ effects?: (ctx: EffectsInvocationContext<S, DS, F, P>) => FxRes | null; /** * If set, called with the error of the promise to update the current state. */ errorEffects?: (ctx: ErrorEffectsInvocationContext<S, DS, F, P>) => FxRes | null; /** * Whether reject the promise instead of handling it. */ rejectPromiseOnError?: boolean; /** * When the error will not be managed by an errorEffect, errorSideEffect, getErrorMessage, * the state decorator will not display an error message and will not return a failed promise * (unless <code>rejectPromiseOnError</code>) is set. */ isErrorManaged?: boolean; /** * Handle side effects when the request succeeded. * @param result The result of the request * @param newData The data after the reducer is applied * @param args The argument during the call of the request function * @param props The props passed to the state decorator * @param actions The actions available * @param notifyWarning If specified, a notify function to indicate that the action is a semi success (use successMessage / getSuccessMessage otherwise). */ sideEffects?: (ctx: SideEffectsInvocationContext<S, DS, F, P, A>) => void; /** * Handle side effects when the request failed. * @param error The error of the request * @param args The argument during the call of the request function * @param props The props passed to the state decorator * @param actions The actions available * @param notifyWarning If specified, a notify function to indicate that the action is a semi failure (use errorMessage / getErrorMessage otherwise). */ errorSideEffects?: (ctx: ErrorSideEffectsInvocationContext<S, DS, F, P, A>) => void; /** * Retrieve the state to set as current data before the promise is resolved. * If the there's no reducer, this data will be used after the promise is done. */ preEffects?: (ctx: InvocationContext<S, DS, F, P>) => FxRes | null; /** * Retrieve the state to set as current data before the promise is resolved. * If the there's no reducer, this data will be used after the promise is done. */ optimisticEffects?: (ctx: InvocationContext<S, DS, F, P>) => FxRes | null; /** * Policy to apply when a call to an asynchronous action is done but a previous call is still not resolved. */ conflictPolicy?: ConflictPolicy; /** * Get an identifier for an action call. Used only when conflictPolicy is ConflictPolicy.PARALLEL. * This information will be available in loadingMap. loadingMap[actionName] will be an array of promise identifiers. */ getPromiseId?: (...args: Parameters<F>) => string; /** * Number of retries in case of error (failed to fetch). * Do not use this if you are creating objects... * Default value is 0 (no retry). */ retryCount?: number; /** * Seed of delay between each retry in milliseconds. * The applied delay is retryDelaySeed * retry count. * Default value is 1000 (1 second). */ retryDelaySeed?: number; /** * Function to test if the error will trigger an action retry or will fail directly. */ isTriggerRetryError?: (e: Error) => boolean; }; export type AsyncActionPromise<S, F extends (...args: any[]) => any, A, P, DS = {}, FxRes = Partial<S>> = AsyncActionBase<S, F, A, P, DS, FxRes> & { /** * The request, returns a promise */ getPromise: PromiseProvider<S, DS, F, A, P>; }; export type AsyncActionPromiseGet<S, F extends (...args: any[]) => any, A, P, DS, FxRes = Partial<S>> = AsyncActionBase<S, F, A, P, DS, FxRes> & { /** * The request, returns a promise that is a GET request. * A shortcut to set: * - retryCount: 3 * - conflictPolicy: ConflictPolicy.REUSE */ getGetPromise: PromiseProvider<S, DS, F, A, P>; }; export type AsyncAction<S, F extends (...args: any[]) => any, A, P, DS = {}, FxRes = Partial<S>> = AsyncActionPromise<S, F, A, P, DS, FxRes> | AsyncActionPromiseGet<S, F, A, P, DS, FxRes>; export type StoreAction<S, F extends (...args: any[]) => any, A, P, DS = {}, FxRes = Partial<S>> = AsyncAction<S, F, A, P, DS, FxRes> | SimpleSyncAction<S, F, P, DS, FxRes> | SyncAction<S, F, A, P, DS, FxRes>; /** * S: The type of the state * A: The type of the actions to pass to the children (used to check keys only). */ export type StoreActions<S, A extends DecoratedActions, P = {}, DS = {}, FxRes = Partial<S>> = { [Prop in keyof A]: StoreAction<S, A[Prop], A, P, DS, FxRes>; }; export type OnPropsChangeEffectsContext<S, DS, P> = ContextWithDerived<S, DS, P> & { indices: number[]; index: number; isInit: boolean; getInitialState: (p: P) => S; }; export type OnPropsChangeSideEffectsContext<S, P, A, DS = {}> = OnPropsChangeEffectsContext<S, DS, P> & { actions: A; a: A; }; type DerivedStateOption<S, P, DS> = { [k in keyof DS]: { getDeps?: (ctx: ContextBase<S, P>) => any[]; get: (ctx: ContextBase<S, P> & ContextDerivedStateState<DS>) => DS[k]; derivedDeps?: (keyof DS)[]; }; }; export type OnPropsChangeOptions<S, DS, A, P, FxRes = Partial<S>> = { onMount?: boolean; onMountDeferred?: boolean; getDeps: (p: P) => any[]; effects?: (ctx: OnPropsChangeEffectsContext<S, DS, P>) => FxRes; sideEffects?: (ctx: OnPropsChangeSideEffectsContext<S, P, A, DS>) => void; }; export type StoreOptions<S, A, P = {}, DS = {}, FxRes = Partial<S>> = { /** * The state decorator name. Use in debug traces to identify the useStateDecorator instance. */ name?: string; /** * In development only (NODE_ENV="development"), activates state changes traces on the console. */ logEnabled?: boolean; /** * Indicates that all asynchronous actions errors are managed externally. * Same as setting isErrorManaged to true for all actions. */ isErrorManaged?: boolean; /** * List of action names that are marked as loading at initial time. * If you must start your initial actions in a useSideEffect, a render is done before * first actions can be trigerred, some actions can be marked as loading at initial time. */ initialActionsMarkedLoading?: (keyof A)[]; /** * One or several configurations of inbound properties change managements. */ onPropsChange?: OnPropsChangeOptions<S, DS, A, P, FxRes> | OnPropsChangeOptions<S, DS, A, P, FxRes>[]; /** * The callback function called if an asynchronous function succeeded and a success messsage is defined. */ notifySuccess?: NotifyFunc; /** * The callback function called if an asynchronous function fails and an error messsage is defined. */ notifyError?: NotifyFunc; /** * The callback function injected in onDone and onFail of asynchronous functions to notify a semi success / failed request. */ notifyWarning?: NotifyFunc; /** * Initial actions. */ onMount?: (ctx: OnMountInvocationContext<S, A, P>) => void; /** * Initial actions executed after initial render. * */ onMountDeferred?: (ctx: OnMountInvocationContext<S, A, P>) => void; /** * Callback on store destruction. */ onUnmount?: (ctx: OnUnMountInvocationContext<S, A, P>) => Promise<any> | void; /** * Compute derived state */ derivedState?: DerivedStateOption<S, P, DS>; /** * Disable auto spread of state on effects (v6 behavior). */ fullStateEffects?: boolean; }; type StoreConfigBase<S, A extends DecoratedActions, P = {}, DS = {}> = { actions: StoreActions<S, A, P, DS>; middlewares?: MiddlewareFactory<S, A, P>[]; } & StoreOptions<S, A, P, DS>; export type StoreConfigObj<S, A extends DecoratedActions, P = {}, DS = {}> = StoreConfigBase<S, A, P, DS> & { initialState: S; }; export type StoreConfigFunc<S, A extends DecoratedActions, P = {}, DS = {}> = StoreConfigBase<S, A, P, DS> & { getInitialState: (p: P) => S; }; export type StoreConfig<S, A extends DecoratedActions, P = {}, DS = {}> = StoreConfigObj<S, A, P, DS> | StoreConfigFunc<S, A, P, DS>; export {};