UNPKG

@typescript-tea/core

Version:

The Elm Architecture for typescript

114 lines (102 loc) 3.88 kB
/** @ignore */ /** */ /** * Commands and Subscriptions are both effects and they can both be batched and mapped. * This module handles the batching and mapping of both commands and subscriptions * in a generic way. * Each effect specifies a "home" that it belongs to. The effects can then be * gathered by "home" and passed to the Effect Manager for that "home". * This is an internal module which is not intended for outside usage. * Please use only the Cmd and Sub modules externally. */ export type Effect<A> = BatchedEffect<A> | MappedEffect<A, unknown> | LeafEffect<A>; export const InternalHome = "__internal"; export type InternalHome = typeof InternalHome; export type LeafEffect<_A, Home = string> = { readonly home: Home; readonly type: string; }; export type BatchedEffect<A> = { readonly home: InternalHome; readonly type: "Batched"; readonly list: ReadonlyArray<Effect<A>>; }; export type MappedEffect<A1, A2> = { readonly home: InternalHome; readonly type: "Mapped"; readonly actionMapper: (a1: A1) => A2; readonly original: BatchedEffect<A1> | MappedEffect<A1, A2> | LeafEffect<A1>; }; export function batchEffects<A>(effects: ReadonlyArray<Effect<A> | undefined>): BatchedEffect<A> { return { home: InternalHome, type: "Batched", list: effects.filter((c) => c !== undefined) as ReadonlyArray<Effect<A>>, }; } export function mapEffect<A1, A2>( actionMapper: (a1: A1) => A2, c: BatchedEffect<A1> | MappedEffect<A1, A2> | LeafEffect<A1> | undefined ): MappedEffect<A1, A2> | undefined { return c === undefined ? undefined : { home: InternalHome, type: "Mapped", actionMapper, original: c }; } export type LeafEffectMapper<A1 = unknown, A2 = unknown> = ( actionMapper: (a1: A1) => A2, effect: Effect<A1> ) => LeafEffect<A2>; export type GatheredEffects<A> = { // This type is mutable for efficency // eslint-disable-next-line functional/prefer-readonly-type [home: string]: { // eslint-disable-next-line functional/prefer-readonly-type readonly cmds: Array<LeafEffect<A>>; // eslint-disable-next-line functional/prefer-readonly-type readonly subs: Array<LeafEffect<A>>; }; }; export type EffectMapper<A1 = unknown, A2 = unknown, THome = unknown> = { readonly home: THome; readonly mapCmd: LeafEffectMapper<A1, A2>; readonly mapSub: LeafEffectMapper<A1, A2>; }; /** * This function is optimized for performance. Hence the ugly boolean * and the mutable input param. */ export function gatherEffects<A>( getEffectMapper: (home: string) => EffectMapper, gatheredEffects: GatheredEffects<A>, isCmd: boolean, effect: Effect<unknown>, actionMapper: ((a1: unknown) => unknown) | undefined = undefined ): void { if (effect.home === InternalHome) { const internalEffect = effect as BatchedEffect<unknown> | MappedEffect<unknown, unknown>; switch (internalEffect.type) { case "Batched": { internalEffect.list.flatMap((c) => gatherEffects(getEffectMapper, gatheredEffects, isCmd, c, actionMapper)); return; } case "Mapped": gatherEffects( getEffectMapper, gatheredEffects, isCmd, internalEffect.original, actionMapper ? (a) => actionMapper(internalEffect.actionMapper(a)) : internalEffect.actionMapper ); return; default: { const exhaustive: never = internalEffect; throw new Error(`Invalid result type ${exhaustive}`); } } } else { const manager = getEffectMapper(effect.home); if (!gatheredEffects[effect.home]) { gatheredEffects[effect.home] = { cmds: [], subs: [] }; } const list = isCmd ? gatheredEffects[effect.home].cmds : gatheredEffects[effect.home].subs; const mapper = isCmd ? manager.mapCmd : manager.mapSub; list.push(actionMapper ? mapper(actionMapper, effect) : effect); } }