UNPKG

effector

Version:

Business logic with ease

1,524 lines (1,424 loc) 116 kB
/** * This tuple type is intended for use as a generic constraint to infer concrete * tuple type of ANY length. * * @see https://github.com/krzkaczor/ts-essentials/blob/a4c2485bc3f37843267820ec552aa662251767bc/lib/types.ts#L169 */ type Tuple<T = unknown> = [T?, ...T[]] type RoTuple<T = unknown> = readonly [T?, ...T[]] /** * Non inferential type parameter usage. NoInfer in source and in return of fn helps with detecting loose objects against target type. * * @see https://github.com/microsoft/TypeScript/issues/14829#issuecomment-504042546 */ type NoInfer<T> = [T][T extends any ? 0 : never] /** * Generic Json type */ type Json = | null | undefined | boolean | string | number | Json[] | {[k: string]: Json} // Type for extention purpose. Represents combinable sample source. export type Combinable = {[key: string]: Store<any>} | Tuple<Store<any>> // Helper type, which unwraps combinable sample source value. export type GetCombinedValue<T> = Show<{ [K in keyof T]: T[K] extends Store<infer U> ? U : never }> export type StoreValue<T> = T extends Store<infer S> ? S : never export type EventPayload<T> = T extends Event<infer P> ? P : never export type UnitValue<T> = T extends Unit<infer V> ? V : never export type EffectParams<FX extends Effect<any, any, any>> = FX extends Effect< infer P, any, any > ? P : never export type EffectResult<FX extends Effect<any, any, any>> = FX extends Effect< any, infer D, any > ? D : never export type EffectError<FX extends Effect<any, any, any>> = FX extends Effect< any, any, infer E > ? E : never // Taken from the source code of typescript 4.5. Remove when we separate types for different versions /** * Recursively unwraps the "awaited type" of a type. Non-promise "thenables" should resolve to `never`. This emulates the behavior of `await`. */ type Awaited<T> = T extends null | undefined ? T // special case for `null | undefined` when not in `--strictNullChecks` mode : T extends object // `await` only unwraps object types with a callable then. Non-object types are not unwrapped. ? T extends {then(onfulfilled: infer F): any} // thenable, extracts the first argument to `then()` ? F extends (value: infer V) => any // if the argument to `then` is callable, extracts the argument ? Awaited<V> // recursively unwrap the value : never // the argument to `then` was not callable. : T // argument was not an object : T // non-thenable type OptionalParams<Args extends any[]> = Args['length'] extends 0 // does handler accept 0 arguments? ? void // works since TS v3.3.3 : 0 | 1 extends Args['length'] // is the first argument optional? /** * Applying `infer` to a variadic arguments here we'll get `Args` of * shape `[T]` or `[T?]`, where T(?) is a type of handler `params`. * In case T is optional we get `T | undefined` back from `Args[0]`. * We lose information about argument's optionality, but we can make it * optional again by appending `void` type, so the result type will be * `T | undefined | void`. * * The disadvantage of this method is that we can't restore optonality * in case of `params?: any` because in a union `any` type absorbs any * other type (`any | undefined | void` becomes just `any`). And we * have similar situation also with the `unknown` type. */ ? Args[0] | void : Args[0] type EffectByHandler<FN extends Function, Fail> = FN extends (...args: infer Args) => infer Done ? Effect<OptionalParams<Args>, Awaited<Done>, Fail> : never export const version: string export type kind = 'store' | 'event' | 'effect' | 'domain' | 'scope' export type Observer<A> = { readonly next?: (value: A) => void //error(err: Error): void //complete(): void } export type Observable<T> = { subscribe: (observer: Observer<T>) => Subscription } export type Subscription = { (): void unsubscribe(): void } export interface Unit<T> { readonly kind: kind readonly __: T } export interface UnitTargetable<T> extends Unit<T> { readonly targetable: true } export type CompositeName = { shortName: string fullName: string path: Array<string> } /** * This is a workaround for https://github.com/microsoft/TypeScript/issues/35162 * * The problem was that we couldn't use guard as sample's clock parameter because * sample's clock-related generic inferred as `unknown` in cases when guard returned * `Event<T>`. This happens because `Event` has a callable signature. With `Unit<T>` * as the return type we won't see any problems. */ type EventAsReturnType<Payload> = any extends Payload ? Event<Payload> : never type EventCallableAsReturnType<Payload> = any extends Payload ? EventCallable<Payload> : never /** * Event you can subscribe to. * It represents a user action, a step in the application process, a command to execute, or an intention to make modifications, among other things. */ export interface Event<Payload> extends Unit<Payload> { kind: "event" map<T>(fn: (payload: Payload) => T): EventAsReturnType<T> filter<T extends Payload>(config: { fn(payload: Payload): payload is T }): EventAsReturnType<T> filter(config: {fn(payload: Payload): boolean}): EventAsReturnType<Payload> filterMap<T>(fn: (payload: Payload) => T | undefined): EventAsReturnType<T> watch(watcher: (payload: Payload) => any): Subscription subscribe(observer: Observer<Payload>): Subscription /** * @deprecated use .compositeName.fullName instead */ getType(): string compositeName: CompositeName sid: string | null shortName: string } /** * The function you can call to trigger an event. */ export interface EventCallable<Payload> extends Event<Payload>, UnitTargetable<Payload> { kind: "event" (payload: Payload): Payload (this: IfUnknown<Payload, void, Payload extends void ? void : `Error: Expected 1 argument, but got 0`>, payload?: Payload): void prepend<Before = void>(fn: (_: Before) => Payload): EventCallable<Before> } /** * Container for (possibly async) side effects */ export interface Effect<Params, Done, Fail = Error> extends UnitTargetable<Params> { kind: "effect" (params: Params): Promise<Done> readonly done: Event<{params: Params; result: Done}> readonly doneData: Event<Done> readonly fail: Event<{params: Params; error: Fail}> readonly failData: Event<Fail> readonly finally: Event< | { status: 'done' params: Params result: Done } | { status: 'fail' params: Params error: Fail } > readonly use: { (handler: (params: Params) => Promise<Done> | Done): Effect< Params, Done, Fail > getCurrent(): (params: Params) => Promise<Done> } pending: Store<boolean> inFlight: Store<number> watch(watcher: (payload: Params) => any): Subscription filter<T extends Params>(config: { fn(payload: Params): payload is T }): EventAsReturnType<T> filter(config: {fn(payload: Params): boolean}): EventAsReturnType<Params> filterMap<T>(fn: (payload: Params) => T | undefined): EventAsReturnType<T> map<T>(fn: (params: Params) => T): EventAsReturnType<T> prepend<Before>(fn: (_: Before) => Params): EventCallable<Before> subscribe(observer: Observer<Params>): Subscription getType(): string compositeName: CompositeName sid: string | null shortName: string } type InferValueFromTupleOfUnits<T extends Tuple<Unit<any>>> = T[number] extends Unit<infer R> ? R : never type InferValueFromTupleOfUnitTargetables<T extends Tuple<UnitTargetable<any>>> = T[number] extends UnitTargetable<infer R>? R : never export interface Store<State> extends Unit<State> { kind: "store" map<T>(fn: (state: State) => T, config?: {skipVoid?: boolean}): Store<T> updates: Event<State> getState(): State subscribe(listener: Observer<State> | ((state: State) => any)): Subscription watch<E>(watcher: (state: State, payload: undefined) => any): Subscription watch<E>( trigger: Unit<E>, watcher: (state: State, payload: E) => any, ): Subscription /** * @deprecated use js pipe instead */ thru<U>(fn: (store: Store<State>) => U): U defaultState: State compositeName: CompositeName shortName: string sid: string | null } /** * Hacky way to force TS perform checks against unsafe widening */ interface StoreValueType<X> { _: X (type: X): void } export interface StoreWritable<State> extends Store<State>, UnitTargetable<State> { kind: "store" readonly ____: StoreValueType<State> on<E>( trigger: Unit<E>, reducer: (state: State, payload: E) => State | void, ): this on<E>( triggers: Unit<E>[], reducer: (state: State, payload: E) => State | void, ): this on<E extends Tuple<Unit<any>>>( triggers: E, reducer: (state: State, payload: InferValueFromTupleOfUnits<E>) => State | void, ): this off(trigger: Unit<any>): this reset(...triggers: Array<Unit<any>>): this reset(triggers: Array<Unit<any>>): this reinit: EventCallable<void> } interface InternalStore<State> extends StoreWritable<State> { setState(state: State): void } export const is: { unit(obj: unknown): obj is Unit<any> | UnitTargetable<any> store<O, T>( obj: O | Unit<T> | UnitTargetable<T>, ): obj is typeof obj extends Unit<T> ? Store<T> | StoreWritable<T> : Store<any> | StoreWritable<any> event<O, T>( obj: O | Unit<T> | UnitTargetable<T> ): obj is typeof obj extends Unit<T> ? Event<T> | EventCallable<T> : Event<any> | EventCallable<any> effect<O, T, P, F>( obj: O | Effect<T, P, F> ): obj is Effect<T, P, F> domain(obj: unknown): obj is Domain scope(obj: unknown): obj is Scope attached<E extends Effect<any, any, any>>(obj: unknown): obj is E targetable<T>(obj: Unit<T>): obj is UnitTargetable<T> } /** * A way to group and process events, stores and effects. Useful for logging and assigning a reset trigger to many effects. * Domain is notified via onCreateEvent, onCreateStore, onCreateEffect, onCreateDomain methods when events, stores, effects, or nested domains are created */ export class Domain implements Unit<any> { readonly kind: kind readonly __: any onCreateEvent(hook: (newEvent: EventCallable<unknown>) => any): Subscription onCreateEffect( hook: (newEffect: Effect<unknown, unknown, unknown>) => any, ): Subscription onCreateStore( hook: (newStore: InternalStore<unknown>) => any, ): Subscription onCreateDomain(hook: (newDomain: Domain) => any): Subscription event<Payload = void>(name?: string): EventCallable<Payload> event<Payload = void>(config: {name?: string; sid?: string}): EventCallable<Payload> createEvent<Payload = void>(name?: string): EventCallable<Payload> createEvent<Payload = void>(config: { name?: string sid?: string }): EventCallable<Payload> effect<FN extends Function>(handler: FN): EffectByHandler<FN, Error> effect<Params, Done, Fail = Error>( handler: (params: Params) => Done | Promise<Done>, ): Effect<Params, Done, Fail> effect<FN extends Function, Fail>(handler: FN): EffectByHandler<FN, Fail> effect<Params, Done, Fail = Error>( name?: string, config?: { handler?: (params: Params) => Promise<Done> | Done sid?: string }, ): Effect<Params, Done, Fail> effect<Params, Done, Fail = Error>(config: { handler?: (params: Params) => Promise<Done> | Done sid?: string name?: string }): Effect<Params, Done, Fail> createEffect<FN extends Function>(handler: FN): EffectByHandler<FN, Error> createEffect<Params, Done, Fail = Error>( handler: (params: Params) => Done | Promise<Done>, ): Effect<Params, Done, Fail> createEffect<FN extends Function, Fail>(handler: FN): EffectByHandler<FN, Fail> createEffect<FN extends Function>(config: { name?: string handler: FN sid?: string }): EffectByHandler<FN, Error> createEffect<Params, Done, Fail = Error>( name?: string, config?: { handler?: (params: Params) => Promise<Done> | Done sid?: string }, ): Effect<Params, Done, Fail> createEffect<Params, Done, Fail = Error>(config: { handler?: (params: Params) => Promise<Done> | Done sid?: string name?: string }): Effect<Params, Done, Fail> domain(name?: string): Domain createDomain(name?: string): Domain store<State, SerializedState extends Json = Json>( defaultState: State, config?: { name?: string sid?: string updateFilter?: (update: State, current: State) => boolean serialize?: | 'ignore' | { write: (state: State) => SerializedState read: (json: SerializedState) => State } }, ): StoreWritable<State> createStore<State, SerializedState extends Json = Json>( defaultState: State, config?: { name?: string sid?: string updateFilter?: (update: State, current: State) => boolean serialize?: | 'ignore' | { write: (state: State) => SerializedState read: (json: SerializedState) => State } skipVoid?: boolean }, ): StoreWritable<State> sid: string | null compositeName: CompositeName shortName: string getType(): string history: { domains: Set<Domain> stores: Set<StoreWritable<any>> effects: Set<Effect<any, any, any>> events: Set<EventCallable<any>> } } export type ID = string export type StateRefOp = | {type: 'map'; from?: StateRef; fn?: (value: any) => any} | {type: 'field'; from: StateRef; field: string} export type StateRef = { id: ID current: any type?: 'list' | 'shape' before?: StateRefOp[] noInit?: boolean sid?: string } export type Stack = { value: any a: any b: any parent?: Stack node: Node page?: any scope?: Scope meta?: Record<string, any> } type BarrierPriorityTag = 'barrier' | 'sampler' | 'effect' type FromValue = { from: 'value' store: any } type FromStore = { from: 'store' store: StateRef } type FromRegister = { from: 'a' | 'b' | 'stack' } type ToRegister = { to: 'a' | 'b' | 'stack' } type ToStore = { to: 'store' target: StateRef } type MoveCmd<Data> = { id: ID type: 'mov' data: Data order?: { priority: BarrierPriorityTag barrierID?: number } } export type Cmd = | Compute | Mov type MovValReg = MoveCmd<FromValue & ToRegister> type MovValStore = MoveCmd<FromValue & ToStore> type MovStoreReg = MoveCmd<FromStore & ToRegister> type MovStoreStore = MoveCmd<FromStore & ToStore> type MovRegReg = MoveCmd<FromRegister & ToRegister> type MovRegStore = MoveCmd<FromRegister & ToStore> export type Mov = | MovValReg | MovValStore | MovStoreReg | MovStoreStore | MovRegReg | MovRegStore export type Compute = { id: ID type: 'compute' data: { fn?: (data: any, scope: {[key: string]: any}, reg: Stack) => any safe: boolean filter: boolean pure: boolean } order?: { priority: BarrierPriorityTag barrierID?: number } } export type Node = { id: ID next: Array<Node> seq: Array<Cmd> scope: {[field: string]: any} meta: {[field: string]: any} family: { type: 'regular' | 'crosslink' | 'domain' links: Node[] owners: Node[] } } export const step: { compute(data: { fn?: (data: any, scope: {[key: string]: any}, stack: Stack) => any batch?: boolean priority?: BarrierPriorityTag | false safe?: boolean filter?: boolean pure?: boolean }): Compute filter(data: { fn: (data: any, scope: {[field: string]: any}, stack: Stack) => boolean pure?: boolean }): Compute run(data: {fn: (data: any, scope: {[field: string]: any}, stack: Stack) => any}): Compute mov(data: { from?: 'value' | 'store' | 'stack' | 'a' | 'b' to?: 'stack' | 'a' | 'b' | 'store' store?: StateRef target?: StateRef batch?: boolean priority?: BarrierPriorityTag | false }): Mov } /* `forward` types */ type ForwardTarget = UnitTargetable<unknown> | ReadonlyArray<UnitTargetable<unknown>> type CleanSingleTarget< Target extends UnitTargetable<unknown>, Clock, > = Target extends UnitTargetable<infer T> ? T extends void ? UnitTargetable<unknown> : T extends Clock ? UnitTargetable<T> // Needed to force typecheck : UnitTargetable<Clock> : never type CleanTarget< Target extends ForwardTarget, From, > = Target extends UnitTargetable<any> ? CleanSingleTarget<Target, From> : { [K in keyof Target]: Target[K] extends UnitTargetable<unknown> ? CleanSingleTarget<Target[K], From> : never } /** * Method to create connection between units in a declarative way. Sends updates from one set of units to another * @deprecated use `sample({clock, target})` instead */ export function forward<From, T extends ForwardTarget>(opts: { /** * By default TS picks "best common type" `T` between `from` and `to` arguments. * This lets us forward from `string | number` to `string` for instance, and * this is wrong. * * Fortunately we have a way to disable such behavior. By adding `& {}` to some * generic type we tell TS "do not try to infer this generic type from * corresponding argument type". * * Generic `T` won't be inferred from `from` any more. Forwarding from "less * strict" to "more strict" will produce an error as expected. * * @see https://www.typescriptlang.org/docs/handbook/type-inference.html#best-common-type */ from: Unit<From & {}> to: CleanTarget<T, From> }): Subscription /** * Method to create connection between units in a declarative way. Sends updates from one set of units to another * @deprecated use `sample({clock, target})` instead */ export function forward(opts: { from: Unit<any> to: ReadonlyArray<UnitTargetable<void>> }): Subscription /** * Method to create connection between units in a declarative way. Sends updates from one set of units to another * @deprecated use `sample({clock, target})` instead */ export function forward(opts: { from: ReadonlyArray<Unit<any>> to: ReadonlyArray<UnitTargetable<void>> }): Subscription /** * Method to create connection between units in a declarative way. Sends updates from one set of units to another * @deprecated use `sample({clock, target})` instead */ export function forward(opts: { from: ReadonlyArray<Unit<any>> to: UnitTargetable<void> }): Subscription /** * Method to create connection between units in a declarative way. Sends updates from one set of units to another * @deprecated use `sample({clock, target})` instead */ export function forward<To, From extends To>(opts: { from: ReadonlyArray<Unit<From>> to: UnitTargetable<To> | ReadonlyArray<UnitTargetable<To>> }): Subscription // Allow `* -> void` forwarding (e.g. `string -> void`). /** * Method to create connection between units in a declarative way. Sends updates from one set of units to another * @deprecated use `sample({clock, target})` instead */ export function forward(opts: {from: Unit<any>; to: UnitTargetable<void>}): Subscription // Do not remove the signature below to avoid breaking change! /** * Method to create connection between units in a declarative way. Sends updates from one set of units to another * @deprecated use `sample({clock, target})` instead */ export function forward<To, From extends To>(opts: { from: Unit<From> to: UnitTargetable<To> | ReadonlyArray<UnitTargetable<To>> }): Subscription /** * Merges array of units (events, effects or stores), returns a new event, which fires upon trigger of any of given units * @param units array of units to be merged */ export function merge<T>(units: ReadonlyArray<Unit<T>>): EventAsReturnType<T> /** * Merges array of units (events, effects or stores), returns a new event, which fires upon trigger of any of given units * @param units array of units to be merged */ export function merge<T extends ReadonlyArray<Unit<any>>>( units: T, ): T[number] extends Unit<infer R> ? Event<R> : never /** * Method for destroying units and graph nodes. Low level tool, usually absent in common applications * @param unit unit to be erased * @param opts optional configuration object */ export function clearNode(unit: Unit<any> | Node, opts?: {deep?: boolean}): void /** * Method to create a new graph node. Low level tool, usually absent in common applications */ export function createNode(opts?: { node?: Array<Cmd | false | void | null> parent?: Array<Unit<any> | Node> child?: Array<Unit<any> | Node> scope?: {[field: string]: any} meta?: {[field: string]: any} family?: { type?: 'regular' | 'crosslink' | 'domain' owners?: Unit<any> | Node | Array<Unit<any> | Node> links?: Unit<any> | Node | Array<Unit<any> | Node> } regional?: boolean }): Node /** * Allows to directly start computation from given unit or graph node. Low level tool, usually absent in common applications * @param unit unit or graph node to launch * @param payload data to pass to computation */ export function launch<T>(unit: Unit<T> | Node, payload: T): void /** * Allows to directly start computation from given unit or graph node. Low level tool, usually absent in common applications * @param config configuration object */ export function launch<T>(config: { target: Unit<T> | Node params: T defer?: boolean page?: any scope?: Scope meta?: Record<string, any> }): void /** * Allows to directly start computation from given unit or graph node. Low level tool, usually absent in common applications * @param config configuration object */ export function launch(config: { target: Array<Unit<any> | Node> params: any[] defer?: boolean page?: any scope?: Scope meta?: Record<string, any> }): void /** * Method to create an event subscribed to given observable * @param observable object with `subscribe` method, e.g. rxjs stream or redux store */ export function fromObservable<T>(observable: unknown): Event<T> /** * Creates an event */ export function createEvent<E = void>(eventName?: string): EventCallable<E> /** * Creates an event */ export function createEvent<E = void>(config: { name?: string sid?: string domain?: Domain }): EventCallable<E> /** * Creates an effect * @param handler function to handle effect calls */ export function createEffect<FN extends Function>(handler: FN): EffectByHandler<FN, Error> /** * Creates an effect * @param handler function to handle effect calls */ export function createEffect<Params, Done, Fail = Error>( handler: (params: Params) => Done | Promise<Done>, ): Effect<Params, Done, Fail> /** * Creates an effect * @param handler function to handle effect calls */ export function createEffect<FN extends Function, Fail>(handler: FN): EffectByHandler<FN, Fail> /** * Creates an effect */ export function createEffect<FN extends Function>(name: string, config: { handler: FN sid?: string domain?: Domain }): EffectByHandler<FN, Error> /** * Creates an effect */ export function createEffect<Params, Done, Fail = Error>( effectName?: string, config?: { handler?: (params: Params) => Promise<Done> | Done sid?: string domain?: Domain }, ): Effect<Params, Done, Fail> /** * Creates an effect */ export function createEffect<FN extends Function>(config: { name?: string handler: FN sid?: string domain?: Domain }): EffectByHandler<FN, Error> /** * Creates an effect */ export function createEffect<Params, Done, Fail = Error>(config: { name?: string handler?: (params: Params) => Promise<Done> | Done sid?: string domain?: Domain }): Effect<Params, Done, Fail> /** * Creates a store * @param defaultState default state * @param config optional configuration object */ export function createStore<State, SerializedState extends Json = Json>( defaultState: State, config?: { skipVoid?: boolean; name?: string; sid?: string updateFilter?: (update: State, current: State) => boolean serialize?: | 'ignore' | { write: (state: State) => SerializedState read: (json: SerializedState) => State } domain?: Domain; }, ): StoreWritable<State> export function setStoreName<State>(store: Store<State>, name: string): void type UnionToIntersection<Union> = ( Union extends any ? (k: Union) => void : never ) extends (k: infer intersection) => void ? intersection : never; type GetUnionLast<Union> = UnionToIntersection< Union extends any ? () => Union : never > extends () => infer Last ? Last : never; /** * Chooses one of the cases by given conditions. It "splits" source unit into several events, which fires when payload matches their conditions. * Works like pattern matching for payload values and external stores * @param source unit which will trigger computation in split * @param match object with matching functions which allows to trigger one of created events */ export function split< S, Match extends {[name: string]: (payload: S) => boolean} >( source: Unit<S>, match: Match, ): Show<{ [K in keyof Match]: Match[K] extends (p: any) => p is infer R ? Event<R> : Event<S> } & {__: Event<S>}> type MatchConstraint<Source> = Unit<any> | ((p: UnitValue<Source>) => void) | Record<string, ((p: UnitValue<Source>) => boolean) | Store<boolean>>; type CaseRecord<Keys extends PropertyKey = string> = Partial<Record<Keys | '__', UnitTargetable<any> | RoTuple<UnitTargetable<any>>>>; /** * Chooses one of cases by given conditions. It "splits" source unit into several targets, which fires when payload matches their conditions. * Works like pattern matching for payload values and external units */ export function split< Clock extends Unit<any> | RoTuple<Unit<any>>, Source extends Unit<any>, Match extends MatchConstraint<Source>, Cases extends CaseRecord<InferMatchKeys<Match>>, >( config: SplitConfig<Clock, Source, Match, Cases> ): void; type SplitConfig< Clock, Source, Match extends MatchConstraint<Source>, Cases extends CaseRecord<InferMatchKeys<Match>> > = Exclude<keyof Cases, '__'> extends InferMatchKeys<Match> ? TypeOfMatch<Match> extends 'record' ? SplitImpl<Clock, Source, Match, Cases> : TypeOfMatch<Match> extends 'unit' ? SplitImpl<Clock, Source, Match, Cases> : TypeOfMatch<Match> extends 'fn' ? SplitImpl<Clock, Source, Match, Cases> : {clock?: Clock; source: Source; match: Match; cases: Cases} : { clock?: Clock; source: Source; match: RebuildMatch<Source, Match, Cases>; cases: { [K in keyof Cases as K extends InferMatchKeys<Match> | '__' ? K : never]: Cases[K] }; }; type InferMatchKeys<Match> = Match extends Unit<infer Keys> ? Keys extends PropertyKey ? Keys : never : Match extends (source: any) => infer Keys ? Keys extends PropertyKey ? Keys : never : Match extends Record<string, ((source: any) => boolean) | Store<boolean>> ? keyof Match : never; type TypeOfMatch<Match> = Match extends Unit<any> ? 'unit' : Match extends (s: any) => void ? 'fn' : Match extends Record<string, ((p: any) => void) | Store<boolean>> ? 'record' : never; type RebuildMatch< Source, Match, Cases, Keys extends PropertyKey = Exclude<keyof Cases, '__'> > = Match extends Unit<any> ? Unit<Keys> : Match extends (p: UnitValue<Source>) => void ? (p: UnitValue<Source>) => Keys : Match extends Record<string, ((p: UnitValue<Source>) => boolean) | Store<boolean>> ? { [K in Keys]: K extends keyof Match ? Match[K] : (p: UnitValue<Source>) => boolean | Store<boolean> } : never; type SplitImpl< Clock, Source, Match, Cases > = MatchCasesIsAssignable<Source, Match, Cases> extends infer AssignableDict ? AssignableDict extends Record<string, 'yes'> ? { clock?: Clock; source: Source; match: Match; cases: Cases } : MatchHasInference<Source, Match> extends 'yes' ? { clock?: Clock; source: Source; match: RebuildMatchInference<Source, Match, AssignableDict>; cases: Show<RebuildCases<Source, Match, Cases>>; } : { clock?: Clock; source: RebuildSource<Source, Cases>; match: Match; cases: Show<RebuildCases<Source, Match, Cases>>; } : never; type MatchValueReader<Match, K, Source> = K extends keyof Match ? Match[K] extends (src: any) => src is infer R ? UnitValue<Source> extends R ? UnitValue<Source> : R : UnitValue<Source> : UnitValue<Source>; type MatchHasInference<Source, Match> = Match extends Record<string, ((p: UnitValue<Source>) => boolean) | Store<boolean>> ? 'yes' extends { [K in keyof Match]: UnitValue<Source> extends MatchValueReader<Match, K, Source> ? 'no' : 'yes' }[keyof Match] ? 'yes' : 'no' : 'no'; type MatchCasesIsAssignable< Source, Match, Cases > = { [K in keyof Cases]: IfCaseAssignableToValue<Cases[K], MatchValueReader<Match, K, Source>> } type IfValidCaseValue<CaseValue, MatchValue, Y, N> = WhichType<CaseValue> extends 'void' | 'unknown' ? Y : IfAssignable<MatchValue, CaseValue, Y, N>; type IfCaseAssignableToValue<Case, Value> = Case extends UnitTargetable<any> ? IfValidCaseValue<UnitValue<Case>, Value, 'yes', ['no', UnitValue<Case>]> : Case extends RoTuple<UnitTargetable<any>> ? IsCaseAssignableToValueLoop<Case, Value> : never; type IsCaseAssignableToValueLoop<Cases extends RoTuple<Unit<any>>, Value> = Cases extends readonly [infer Case, ...infer Rest] ? Rest extends readonly any[] ? IfValidCaseValue<UnitValue<Case>, Value, 'yes', 'no'> extends 'yes' ? IsCaseAssignableToValueLoop<Rest, Value> : ['no', UnitValue<Case>] : never : 'yes'; type RebuildMatchInference< Source, Match, AssignableDict > = Match extends Record<string, ((p: UnitValue<Source>) => boolean) | Store<boolean>> ? { [K in keyof Match]: K extends keyof AssignableDict ? AssignableDict[K] extends ['no', infer Value] ? Value extends UnitValue<Source> ? (source: UnitValue<Source>) => source is Value : never : Match[K] : Match[K] } : never; type RebuildCases<Source, Match, Cases> = { [K in keyof Cases]: K extends InferMatchKeys<Match> | '__' ? RebuildCase<MatchValueReader<Match, K, Source>, Cases[K]> : Cases[K]; } type RebuildCase<MatchValue, Case> = Case extends UnitTargetable<infer CaseValue> ? IfValidCaseValue<CaseValue, MatchValue, Case, UnitTargetable<MatchValue>> : Case extends RoTuple<UnitTargetable<any>> ? RebuildCaseLoop<Case, MatchValue> : never; type RebuildCaseLoop<Cases extends readonly any[], Value, Result extends readonly any[] = []> = Cases extends readonly [infer Case, ...infer Rest] ? Rest extends readonly any[] ? RebuildCaseLoop< Rest, Value, [ ...Result, IfValidCaseValue<UnitValue<Case>, Value, Case, UnitTargetable<Value>> ] > : Result : Result; type RebuildSource<Source, Cases> = { [K in keyof Cases]: GetFirstUnassignableCase<UnitValue<Source>, Cases[K]> } extends infer Values ? Values extends Record<string, never> ? Source : { [K in keyof Values as [Values[K]] extends [never] ? never : K]: Values[K] } extends infer InvalidValues ? Unit<GetUnionLast<InvalidValues[keyof InvalidValues]>> : never : never; type GetFirstUnassignableCase<SourceValue, Case> = Case extends UnitTargetable<infer CaseValue> ? IfValidCaseValue<CaseValue, SourceValue, SourceValue, CaseValue> : Case extends RoTuple<UnitTargetable<any>> ? GetFirstUnassignableLoop<Case, SourceValue> : never; type GetFirstUnassignableLoop<Cases extends RoTuple<Unit<any>>, Value> = Cases extends readonly [infer Case, ...infer Rest] ? Rest extends readonly any[] ? IfValidCaseValue<UnitValue<Case>, Value, 'yes', 'no'> extends 'yes' ? GetFirstUnassignableLoop<Rest, Value> : UnitValue<Case> : never : never; /** * Shorthand for creating events attached to store by providing object with reducers for them * @param store target store * @param api object with reducers */ export function createApi< S, Api extends {[name: string]: ((store: S, e: any) => (S | void))} >( store: StoreWritable<S>, api: Api, ): { [K in keyof Api]: ((store: S, e: void) => (S | void)) extends Api[K] ? EventCallable<void> : Api[K] extends ((store: S) => (S | void)) ? EventCallable<void> : Api[K] extends ((store: S, e: infer E) => (S | void)) ? EventCallable<E extends void ? Exclude<E, undefined> | void : E> : any } /** * Creates a Store out of successful results of Effect. * It works like a shortcut for `createStore(defaultState).on(effect.done, (_, {result}) => result)` * @param effect source effect * @param defaultState initial state of new store */ export function restore<Done>( effect: Effect<any, Done, any>, defaultState: Done, ): StoreWritable<Done> /** * Creates a Store out of successful results of Effect. * It works like a shortcut for `createStore(defaultState).on(effect.done, (_, {result}) => result)` * @param effect source effect * @param defaultState initial state of new store */ export function restore<Done>( effect: Effect<any, Done, any>, defaultState: null, ): StoreWritable<Done | null> /** * Creates a Store from Event. * It works like a shortcut for `createStore(defaultState).on(event, (_, payload) => payload)` * @param event source event * @param defaultState initial state of new store */ export function restore<E>(event: Event<E>, defaultState: E): StoreWritable<E> /** * Creates a Store from Event. * It works like a shortcut for `createStore(defaultState).on(event, (_, payload) => payload)` * @param event source event * @param defaultState initial state of new store */ export function restore<E>(event: Event<E>, defaultState: null): StoreWritable<E | null> export function restore<T extends Event<any>>(event: T): never export function restore<T extends Effect<any, any, any>>(effect: T): never export function restore<State extends {[key: string]: Store<any> | any}>( state: State, ): { [K in keyof State]: State[K] extends Store<infer S> ? StoreWritable<S> : StoreWritable<State[K]> } /** * Creates a domain */ export function createDomain(domainName?: string, config?: { domain?: Domain }): Domain export function createDomain(config?: { name?: string; domain?: Domain }): Domain type WhichTypeKind = | 'never' | 'any' | 'unknown' | 'void' | 'undefined' | 'value' type NotType<T extends WhichTypeKind> = Exclude<WhichTypeKind, T> type WhichType<T> = [T] extends [never] ? 'never' : [unknown] extends [T] ? [0] extends [1 & T] ? 'any' : 'unknown' : [T] extends [void] ? [void] extends [T] ? 'void' : 'undefined' : 'value' type BuiltInObject = | Error | Date | RegExp | Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array | ReadonlyMap<unknown, unknown> | ReadonlySet<unknown> | WeakMap<object, unknown> | WeakSet<object> | ArrayBuffer | DataView | Function | Promise<unknown> | Generator type UnitObject = Store<any> | Event<any> | Effect<any, any, any> | Unit<any> /** * Force typescript to print real type instead of geneic types * * It's better to see {a: string; b: number} * instead of GetCombinedValue<{a: Store<string>; b: Store<number>}> * */ type Show<A extends any> = A extends BuiltInObject ? A : A extends UnitObject ? A : { [K in keyof A]: A[K] } // & {} /* sample types */ type TupleObject<T extends Array<any>> = { [I in Exclude<keyof T, keyof any[]>]: T[I] } type IfAny<T, Y, N> = 0 extends (1 & T) ? Y : N; type IfUnknown<T, Y, N> = 0 extends (1 & T) ? N : unknown extends T ? Y : N; type IfAssignable<T, U, Y, N> = (<G>() => IfAny<T & U, 0, G extends T ? 1 : 2>) extends (<G>() => IfAny<T & U, 0, G extends U ? 1 : 2>) ? Y : (T extends U ? never : 1) extends never ? Y : T extends Array<any> ? number extends T['length'] ? N : U extends Array<any> ? number extends U['length'] ? N : TupleObject<T> extends TupleObject<U> ? Y : N : N : N type Source<A> = Unit<A> | Combinable type Clock<B> = Unit<B> | Tuple<Unit<any>> type Target = UnitTargetable<any> | Tuple<any> type GetTupleWithoutAny<T> = T extends Array<infer U> ? U extends Unit<infer Value> ? IfAny<Value, never, Value> : never : never type GetMergedValue<T> = GetTupleWithoutAny<T> extends never ? any : GetTupleWithoutAny<T> type GetSource<S> = S extends Unit<infer Value> ? Value : GetCombinedValue<S> type GetClock<C> = C extends Unit<infer Value> ? Value : GetMergedValue<C> /** Replaces incompatible unit type with string error message. * There is no error message if target type is void. */ type ReplaceUnit<Target, Result, Value> = IfAssignable<Result, Value, Target, Value extends void ? Target : 'incompatible unit in target' > // [...T] is used to show sample result as a tuple (not array) type TargetTuple<Target extends Array<unknown>, Result> = [...{ [Index in keyof Target]: Target[Index] extends UnitTargetable<infer Value> ? ReplaceUnit<Target[Index], Result, Value> : 'non-unit item in target' }] type MultiTarget<Target, Result> = Target extends UnitTargetable<infer Value> ? ReplaceUnit<Target, Result, Value> : Target extends Tuple<unknown> ? TargetTuple<Target, Result> : 'non-unit item in target' type SampleImpl< Target, Source, Clock, FLBool, FilterFun, FN, FNInf, FNInfSource extends ( Source extends Unit<any> | SourceRecord ? TypeOfSource<Source> : never ), FNInfClock extends ( Clock extends Units ? TypeOfClock<Clock> : never ), FNAltArg, FLUnit, SomeFN, > = // no target unknown extends Target // no target, no source ? unknown extends Source ? unknown extends Clock ? [message: {error: 'either target, clock or source should exists'}] // no target, no source, has clock : Clock extends Units ? SampleFilterDef< ModeSelector< 'clock | | filter | fn | ', 'clock | | filter | | ', 'clock | | | fn | ', 'clock | | | | ', SomeFN >, Source, Clock, FLUnit, FLBool, FilterFun, FN, FNInf, FNInfSource, FNInfClock, FNAltArg, SomeFN > : [message: {error: 'clock should be unit or array of units'; got: Clock}] // no target, has source : Source extends Unit<any> | SourceRecord // no target, has source, no clock ? unknown extends Clock ? SampleFilterDef< ModeSelector< ' | source | filter | fn | ', ' | source | filter | | ', ' | source | | fn | ', ' | source | | | ', SomeFN >, Source, Clock, FLUnit, FLBool, FilterFun, FN, FNInf, FNInfSource, FNInfClock, FNAltArg, SomeFN > // no target, has source, has clock : Clock extends Units ? SampleFilterDef< ModeSelector< 'clock | source | filter | fn | ', 'clock | source | filter | | ', 'clock | source | | fn | ', 'clock | source | | | ', SomeFN >, Source, Clock, FLUnit, FLBool, FilterFun, FN, FNInf, FNInfSource, FNInfClock, FNAltArg, SomeFN > : [message: {error: 'clock should be unit or array of units'; got: Clock}] : [message: {error: 'source should be unit or object with stores'; got: Source}] // has target : Target extends UnitsTarget | ReadonlyArray<UnitTargetable<any>> // has target, no source ? unknown extends Source ? unknown extends Clock ? [message: {error: 'either target, clock or source should exists'}] // has target, no source, has clock : Clock extends Units ? SampleFilterTargetDef< ModeSelector< 'clock | | filter | fn | target', 'clock | | filter | | target', 'clock | | | fn | target', 'clock | | | | target', SomeFN >, Target, Source, Clock, FLUnit, FLBool, FilterFun, FN, FNInf, FNInfSource, FNInfClock, FNAltArg, SomeFN > : [message: {error: 'clock should be unit or array of units'; got: Clock}] // has target, has source : Source extends Unit<any> | SourceRecord // has target, has source, no clock ? unknown extends Clock ? SampleFilterTargetDef< ModeSelector< ' | source | filter | fn | target', ' | source | filter | | target', ' | source | | fn | target', ' | source | | | target', SomeFN >, Target, Source, Clock, FLUnit, FLBool, FilterFun, FN, FNInf, FNInfSource, FNInfClock, FNAltArg, SomeFN > // has target, has source, has clock : Clock extends Units ? SampleFilterTargetDef< ModeSelector< 'clock | source | filter | fn | target', 'clock | source | filter | | target', 'clock | source | | fn | target', 'clock | source | | | target', SomeFN >, Target, Source, Clock, FLUnit, FLBool, FilterFun, FN, FNInf, FNInfSource, FNInfClock, FNAltArg, SomeFN > : [message: {error: 'clock should be unit or array of units'; got: Clock}] : [message: {error: 'source should be unit or object with stores'; got: Source}] : Target extends InvalidUnitsTarget ? [message: {error: 'derived units are not allowed in target'; got: Target}] : [message: {error: 'target should be unit or array of units'; got: Target}] type ModeSelector< FilterAndFN, FilterOnly, FNOnly, None, SomeFN, > = unknown extends SomeFN ? FilterAndFN : SomeFN extends {fn: any; filter: any} ? FilterAndFN : SomeFN extends {filter: any} ? FilterOnly : SomeFN extends {fn: any} ? FNOnly : None type SampleRet< Target, Source, Clock, FLUnit, FLBool, FilterFun, FN, FNAltArg, FNInf, FNInfSource extends ( Source extends Unit<any> | SourceRecord ? TypeOfSource<Source> : never ), FNInfClock extends ( Clock extends Units ? TypeOfClock<Clock> : never ), SomeFN, ForceTargetInference > = unknown extends Target ? unknown extends Clock ? unknown extends Source ? never : Source extends Unit<any> | SourceRecord // has filter, has fn ? unknown extends SomeFN ? FLUnit extends Unit<any> ? FN extends (src: TypeOfSource<Source>) => any ? EventAsReturnType<ReturnType<FN>> : never : FLBool extends BooleanConstructor ? FNAltArg extends (arg: NonFalsy<TypeOfSource<Source>>) => any ? EventAsReturnType<ReturnType<FNAltArg>> : never : FilterFun extends (src: TypeOfSource<Source>) => src is FNInfSource ? FNInf extends (src: FNInfSource) => any ? EventAsReturnType<ReturnType<FNInf>> : never : FN extends (src: TypeOfSource<Source>) => any ? EventAsReturnType<ReturnType<FN>> : never // has filter, has fn : SomeFN extends {filter: any; fn: any} ? FLUnit extends Unit<any> ? FN extends (src: TypeOfSource<Source>) => any ? EventAsReturnType<ReturnType<FN>> : never : FLBool extends BooleanConstructor ? FNAltArg extends (arg: NonFalsy<TypeOfSource<Source>>) => any ? EventAsReturnType<ReturnType<FNAltArg>> : never : FilterFun extends (src: TypeOfSource<Source>) => src is FNInfSource ? FNInf extends (src: FNInfSource) => any ? EventAsReturnType<ReturnType<FNInf>> : never : FN extends (src: TypeOfSource<Source>) => any ? EventAsReturnType<ReturnType<FN>> : never // no filter, has fn : SomeFN extends {fn: any} ? FN extends (src: TypeOfSource<Source>) => any ? Source extends Store<any> | SourceRecord ? Store<ReturnType<FN>> : EventAsReturnType<ReturnType<FN>> : never // has filter, no fn : SomeFN extends {filter: any} ? FLUnit extends Unit<any> ? EventAsReturnType<TypeOfSource<Source>> : FLBool extends BooleanConstructor ? EventAsReturnType<NonFalsy<TypeOfSource<Source>>> : FilterFun extends (src: TypeOfSource<Source>) => src is FNInfSource ? EventAsReturnType<FNInfSource> : EventAsReturnType<TypeOfSource<Source>> // no filter, no fn : Source extends Store<any> | SourceRecord ? Store<TypeOfSource<Source>> : EventAsReturnType<TypeOfSource<Source>> : never : unknown extends Source ? Clock extends Units // has filter, has fn ? unknown extends SomeFN ? FLUnit extends Unit<any> ? FN extends (clk: TypeOfClock<Clock>) => any ? EventAsReturnType<ReturnType<FN>> : never : FLBool extends BooleanConstructor ? FNAltArg extends (arg: NonFalsy<TypeOfClock<Clock>>) => any ? EventAsReturnType<ReturnType<FNAltArg>> : never : FilterFun extends (clk: TypeOfClock<Clock>) => clk is FNInfClock ? FNInf extends (clk: FNInfClock) => any ? EventAsReturnType<ReturnType<FNInf>> : never : FN extends (clk: TypeOfClock<Clock>) => any ? EventAsReturnType<ReturnType<FN>> : never // has filter, has fn : SomeFN extends {filter: any; fn: any} ? FLUnit extends Unit<any> ? FN extends (clk: TypeOfClock<Clock>) => any ? EventAsReturnType<ReturnType<FN>> : never : FLBool extends BooleanConstructor ? FNAltArg extends (arg: NonFalsy<TypeOfClock<Clock>>) => any ? EventAsReturnType<ReturnType<FNAltArg>> : never : FilterFun extends (clk: TypeOfClock<Clock>) => clk is FNInfClock ? FNInf extends (src: FNInfClock) => any ? EventAsReturnType<ReturnType<FNInf>> : never : FN extends (clk: TypeOfClock<Clock>) => any ? EventAsReturnType<ReturnType<FN>> : never // no filter, has fn : SomeFN extends {fn: any} ? FN extends (clk: TypeOfClock<Clock>) => any ? Clock extends Store<any> ? Store<ReturnType<FN>> : EventAsReturnType<ReturnType<FN>> : never // has filter, no fn : SomeFN extends {filter: any} ? FLUnit extends Unit<any> ? EventAsReturnType<TypeOfClock<Clock>> : FLBool extends BooleanConstructor ? EventAsReturnType<NonFalsy<TypeOfClock<Clock>>> : FilterFun extends (clk: TypeOfClock<Clock>) => clk is FNInfClock ? EventAsReturnType<FNInfClock> : EventAsReturnType<TypeOfClock<Clock>> // no filter, no fn : Clock extends Store<any> ? Store<TypeOfClock<Clock>> : EventAsReturnType<TypeOfClock<Clock>> : never : Clock extends Units ? Source extends Unit<any> | SourceRecord // has filter, has fn ? unknown extends SomeFN ? FLUnit extends Unit<any> ? FN extends (src: TypeOfSource<Source>, clk: TypeOfClock<Clock>) => any ? EventAsReturnType<ReturnType<FN>> : never : FLBool extends BooleanConstructor ? FNAltArg extends (arg: NonFalsy<TypeOfSource<Source>>, clk: TypeOfClock<Clock>) => any ? EventAsReturnType<ReturnType<FNAltArg>> : never : FilterFun extends (src: TypeOfSource<Source>, clk: TypeOfClock<Clock>) => src is FNInfSource ? FNInf extends (src: FNInfSource, clk: TypeOfClock<Clock>) =