import type { Reducer, Store, Middleware, StoreEnhancer, compose, AnyAction } from 'redux' import { Context as ReactContext, ComponentType, FunctionComponent } from 'react' import { DefaultMemoizeOptions } from 'reselect' // universal helpers export type AnyComponent = ComponentType | FunctionComponent export type KeyType = string | number | boolean export type PathType = KeyType[] export type Selector = (state?: any, props?: any) => any export type Props = Record // nb! used in kea and react export type PartialRecord = Partial> // logic base class export interface Logic { // logic path: PathType pathString: string props: any lastProps: any key?: KeyType keyBuilder?: (props: any) => KeyType // core actionCreators: Record actionKeys: Record actionTypes: Record actions: Record asyncActions: Record cache: Record connections: { [pathString: string]: BuiltLogic } defaults: Record reducers: Record reducer?: ReducerFunction reducerOptions: Record selector?: Selector selectors: Record values: Record events: { beforeMount?: () => void afterMount?: () => void beforeUnmount?: () => void afterUnmount?: () => void propsChanged?: (props: any, oldProps: any) => void } // listeners listeners?: Record sharedListeners?: Record __keaTypeGenInternalSelectorTypes: Record __keaTypeGenInternalReducerActions: Record __keaTypeGenInternalExtraInput: Record } export interface BuiltLogicAdditions { _isKeaBuild: boolean mount: () => () => void unmount: () => void isMounted: () => boolean extend: ( extendedInput: LogicInput | LogicInput[], ) => LogicWrapper wrapper: LogicWrapper } export type BuiltLogic = LogicType & BuiltLogicAdditions export interface LogicWrapperAdditions { _isKea: boolean inputs: (LogicInput | LogicBuilder)[] // store as generic (props: T): T extends LogicType['props'] ? BuiltLogic : FunctionComponent (): BuiltLogic wrap: (Component: AnyComponent) => KeaComponent build: (props?: LogicType['props']) => BuiltLogic mount: () => () => void unmount: () => void /** Is a logic with the given key or props mounted? */ isMounted: (keyOrProps?: KeyType | Record) => boolean /** Find a mounted logic or return null */ findMounted: (keyOrProps?: KeyType | Record) => BuiltLogic | null /** Find all mounted logics */ findAllMounted: () => BuiltLogic[] /** Find a mounted logic or throw */ find: (keyOrProps?: KeyType | Record) => BuiltLogic extend: ( extendedInput: LogicInput, ) => LogicWrapper } export type LogicWrapper = LogicType & LogicWrapperAdditions export type LogicBuilder = (logic: BuiltLogic) => void // input helpers (using the generated logic type as input) export type PayloadCreatorDefinition = true | ((...args: any[]) => any) export type ActionDefinitions = LogicType['actionCreators'] extends Record ? Partial<{ [K in keyof LogicType['actionCreators']]: LogicType['actionCreators'][K] extends Function ? ReturnType['payload']['value'] extends true ? true : (...args: Parameters) => LogicType['actionCreators'][K]['payload'] : never }> : Record export interface KeaReduxAction extends AnyAction { type: string payload?: any } export interface KeaAction { (...args: any[]): KeaReduxAction _isKeaAction: boolean toString(): string } export type ReducerActions< LogicType extends Logic, ReducerType, > = LogicType['__keaTypeGenInternalReducerActions'] extends Record ? { [K in keyof LogicType['actionCreators']]?: ( state: ReducerType, payload: ReturnType['payload'], ) => ReducerType } : LogicType['__keaTypeGenInternalReducerActions'] extends Record ? { [K in keyof LogicType['actionCreators']]?: ( state: ReducerType, payload: ReturnType['payload'], ) => ReducerType } & { [K in keyof LogicType['__keaTypeGenInternalReducerActions']]?: ( state: ReducerType, payload: ReturnType['payload'], ) => ReducerType } : never export type ReducerDefault any, P extends Props> = | ReturnType | ((state: any, props: P) => ReturnType) export type ReducerDefinitions = { [K in keyof LogicType['reducers']]?: | [ ReducerDefault, Record, ReducerActions>, ] | [ ReducerDefault, ReducerActions>, ] | [ReducerDefault] | ReducerActions> } export type ReducerFunction = (state: S, action: KeaReduxAction, fullState: any) => S export type SelectorTuple = | [] | [Selector] | [Selector, Selector] | [Selector, Selector, Selector] | [Selector, Selector, Selector, Selector] | [Selector, Selector, Selector, Selector, Selector] | [Selector, Selector, Selector, Selector, Selector, Selector] | [Selector, Selector, Selector, Selector, Selector, Selector, Selector] | [Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector] | [Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector] | [Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector] | [Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector] | [ Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, ] | [ Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, ] | [ Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, ] | [ Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, ] | [ Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, Selector, ] export type SelectorDefinition = | [(s: Selectors, p: PropSelectors) => SelectorTuple, SelectorFunction] | [(s: Selectors, p: PropSelectors) => SelectorTuple, SelectorFunction, DefaultMemoizeOptions] export type LogicPropSelectors = { [PK in keyof LogicType['props']]: () => LogicType['props'][PK] } export type SelectorDefinitions = | { [K in keyof LogicType['__keaTypeGenInternalSelectorTypes']]?: SelectorDefinition< LogicType['selectors'], LogicPropSelectors, LogicType['__keaTypeGenInternalSelectorTypes'][K] > } | { [key: string]: SelectorDefinition, any> } export type BreakPointFunction = (() => void) & ((ms: number) => Promise) export type ListenerDefinitionsForRecord any>> = { [K in keyof A]?: ListenerFunction> | ListenerFunction>[] } export type ListenerDefinitions = LogicType['__keaTypeGenInternalReducerActions'] extends Record ? ListenerDefinitionsForRecord : LogicType['__keaTypeGenInternalReducerActions'] extends Record ? ListenerDefinitionsForRecord & ListenerDefinitionsForRecord : never export type EventDefinitions = { beforeMount?: (() => void) | (() => void)[] afterMount?: (() => void) | (() => void)[] beforeUnmount?: (() => void) | (() => void)[] afterUnmount?: (() => void) | (() => void)[] propsChanged?: | ((props: Logic['props'], oldProps: Logic['props']) => void) | ((props: Logic['props'], oldProps: Logic['props']) => void)[] } export type ListenerFunction = ( payload: A['payload'], breakpoint: BreakPointFunction, action: A, previousState: any, ) => void | Promise export type ListenerFunctionWrapper = (action: any, previousState: any) => void export type SharedListenerDefinitions = Record type WindowValuesDefinitions = Record any> type LoaderFunctions = { [K in keyof LogicType['actionCreators']]?: ( payload: ReturnType['payload'], breakpoint: BreakPointFunction, action: ReturnType, ) => ReducerReturnType | Promise } type LoaderDefinitions = { [K in keyof LogicType['reducers']]?: | ( | LoaderFunctions> | { __default: ReturnType } ) | [ReturnType, LoaderFunctions>] } export type ConnectDefinitions = | BuiltLogic | LogicWrapper | (BuiltLogic | LogicWrapper)[] | { logic?: (BuiltLogic | LogicWrapper)[] values?: any[] actions?: any[] } export type LogicInput = { inherit?: LogicWrapper[] extend?: LogicInput[] key?: (props: LogicType['props']) => KeyType path?: PathType | ((key: KeyType) => PathType) connect?: ConnectDefinitions | ((props: LogicType['props']) => ConnectDefinitions) actions?: ActionDefinitions | ((logic: LogicType) => ActionDefinitions) reducers?: ReducerDefinitions | ((logic: LogicType) => ReducerDefinitions) selectors?: SelectorDefinitions | ((logic: LogicType) => SelectorDefinitions) listeners?: ListenerDefinitions | ((logic: LogicType) => ListenerDefinitions) sharedListeners?: SharedListenerDefinitions | ((logic: LogicType) => SharedListenerDefinitions) events?: EventDefinitions | ((logic: LogicType) => EventDefinitions) defaults?: | ((logic: LogicType) => (state: any, props: LogicType['props']) => Record) | ((logic: LogicType) => Record) | Record // plugins loaders?: LoaderDefinitions | ((logic: LogicType) => LoaderDefinitions) windowValues?: WindowValuesDefinitions | ((logic: LogicType) => WindowValuesDefinitions) urlToAction?: (logic: LogicType) => Record< string, ( params: Record, searchParams: Record, hashParams: Record, payload: { method: 'PUSH' | 'REPLACE' | 'POP' pathname: string search: string searchParams: Record hash: string hashParams: Record url: string initial?: boolean }, previousLocation: { method: 'PUSH' | 'REPLACE' | 'POP' | null pathname: string search: string searchParams: Record hash: string hashParams: Record url: string }, ) => any > actionToUrl?: (logic: LogicType) => { [K in keyof LogicType['actionCreators']]?: ( payload: Record, ) => | void | string | [string] | [string, string | Record | undefined] | [string, string | Record | undefined, string | Record | undefined] | [ string, string | Record | undefined, string | Record | undefined, { replace?: boolean }, ] } [key: string]: unknown } & LogicType['__keaTypeGenInternalExtraInput'] /** MakeLogicType: - create a close-enough approximation of the logic's types if passed three interfaces: MakeLogicType - Values = { valueKey: type } - Actions = { actionKey: (id) => void } // <- this works - Actions = { actionKey: (id) => { id } } // <- adds type completion in reducers - Props = { id: 3 } */ export interface MakeLogicType< Values extends Record = Record, Actions = Record, LogicProps = Props, > extends Logic { actionCreators: { [ActionKey in keyof Actions]: Actions[ActionKey] extends AnyFunction ? ActionCreatorForPayloadBuilder : never } actionKeys: Record actionTypes: { [ActionKey in keyof Actions]: string } actions: { [ActionKey in keyof Actions]: Actions[ActionKey] extends AnyFunction ? ActionForPayloadBuilder : never } asyncActions: { [ActionKey in keyof Actions]: Actions[ActionKey] extends AnyFunction ? AsyncActionForPayloadBuilder : never } defaults: Values props: LogicProps reducer: ReducerFunction reducers: { [Value in keyof Values]: ReducerFunction } selector: (state: any, props: LogicProps) => Values selectors: { [Value in keyof Values]: (state: any, props: LogicProps) => Values[Value] } values: Values __keaTypeGenInternalSelectorTypes: { [K in keyof Values]: (...args: any) => Values[K] } } export interface KeaLogicTypeInput { values?: Record actions?: Record props?: Props __keaTypeGenInternalSelectorTypes?: Record __keaTypeGenInternalReducerActions?: Record __keaTypeGenInternalExtraInput?: Record } /** Create a logicType for a logic from minimal input. Used in typegen inline mode. */ export interface KeaLogicType extends Logic { actionCreators: { [ActionKey in keyof Input['actions']]: Input['actions'][ActionKey] extends AnyFunction ? ActionCreatorForPayloadBuilder : never } actionKeys: Record actionTypes: { [ActionKey in keyof Input['actions']]: string } actions: { [ActionKey in keyof Input['actions']]: Input['actions'][ActionKey] extends AnyFunction ? ActionForPayloadBuilder : never } defaults: Input['values'] extends Record ? Input['values'] : Record props: Input['props'] reducer: ReducerFunction reducers: { [Value in keyof Input['values']]: ReducerFunction } selector: (state: any, props: Input['props']) => Input['values'] selectors: { [Value in keyof Input['values']]: (state: any, props: Input['props']) => Input['values'][Value] } values: Input['values'] extends Record ? Input['values'] : Record __keaTypeGenInternalSelectorTypes: Input['__keaTypeGenInternalSelectorTypes'] extends Record ? Input['__keaTypeGenInternalSelectorTypes'] : Record __keaTypeGenInternalReducerActions: Input['__keaTypeGenInternalReducerActions'] extends Record ? Input['__keaTypeGenInternalReducerActions'] : Record __keaTypeGenInternalExtraInput: Input['__keaTypeGenInternalExtraInput'] extends Record ? Input['__keaTypeGenInternalExtraInput'] : Record } export type AnyFunction = (...args: any) => any export type ActionCreatorForPayloadBuilder = (...args: Parameters) => { type: string payload: ReturnType } export type ActionForPayloadBuilder = (...args: Parameters) => void export type AsyncActionForPayloadBuilder = (...args: Parameters) => Promise // kea setup stuff export interface CreateStoreOptions { paths: string[] reducers: Record preloadedState: Record | undefined middleware: Middleware[] compose: typeof compose enhancers: StoreEnhancer[] plugins: KeaPlugin[] } export interface InternalContextOptions { debug: boolean proxyFields: boolean flatDefaults: boolean attachStrategy: 'dispatch' | 'replace' detachStrategy: 'dispatch' | 'replace' | 'persist' defaultPath: string[] disableAsyncActions: boolean // ...otherOptions } export interface ContextOptions extends Partial { plugins?: KeaPlugin[] createStore?: boolean | Partial defaults?: Record } export interface KeaComponent extends FunctionComponent { _wrapper: LogicWrapper _wrappedComponent: AnyComponent } export interface PluginEvents { /** Run after creating a new context, before plugins are activated and the store is created */ afterOpenContext?: (context: Context, options: ContextOptions) => void /** Run after this plugin has been activated */ afterPlugin?: () => void /** Run before the redux store creation begins. Use it to add options (middleware, etc) to the store creator. */ beforeReduxStore?: (options: CreateStoreOptions) => void /** Run after the redux store is created. */ afterReduxStore?: (options: CreateStoreOptions, store: Store) => void /** Run before we start doing anything */ beforeKea?: (input: LogicInput | LogicBuilder) => void /** before the steps to build the logic (gets an array of inputs from kea(input).extend(input)) */ beforeBuild?: (logic: BuiltLogic, inputs: (LogicInput | LogicBuilder)[]) => void /** before the steps to convert input into logic (also run once per .extend()) */ beforeLogic?: (logic: BuiltLogic, input: LogicInput | LogicBuilder) => void /** after the steps to convert input into logic (also run once per .extend()) */ afterLogic?: (logic: BuiltLogic, input: LogicInput | LogicBuilder) => void /** called when building a logic with legeacy LogicInput objects, called after connect: {} runs in code */ legacyBuild?: (logic: BuiltLogic, input: LogicInput) => void /** called when building a logic with legeacy LogicInput objects, called after defaults are built in core */ legacyBuildAfterConnect?: (logic: BuiltLogic, input: LogicInput) => void /** called when building a logic with legeacy LogicInput objects, called after the legacy core plugin runs */ legacyBuildAfterDefaults?: (logic: BuiltLogic, input: LogicInput) => void /** after the steps to build the logic */ afterBuild?: (logic: BuiltLogic, inputs: (LogicInput | LogicBuilder)[]) => void /** Run before a logic store is mounted in React */ beforeMount?: (logic: BuiltLogic) => void /** Run after a logic store is mounted in React */ afterMount?: (logic: BuiltLogic) => void /** Run before a reducer is attached to Redux */ beforeAttach?: (logic: BuiltLogic) => void /** Run after a reducer is attached to Redux */ afterAttach?: (logic: BuiltLogic) => void /** Run before a logic is unmounted */ beforeUnmount?: (logic: BuiltLogic) => void /** Run after a logic is unmounted */ afterUnmount?: (logic: BuiltLogic) => void /** Run before a reducer is detached frm Redux */ beforeDetach?: (logic: BuiltLogic) => void /** Run after a reducer is detached frm Redux */ afterDetach?: (logic: BuiltLogic) => void /** Run before wrapping a React component */ beforeWrap?: (wrapper: LogicWrapper, Klass: AnyComponent) => void /** Run after wrapping a React component */ afterWrap?: (wrapper: LogicWrapper, Klass: AnyComponent, Kea: KeaComponent) => void /** Run after mounting and before rendering the component in React's scope (you can use hooks here) */ beforeRender?: (logic: BuiltLogic, props: Props) => void /** Run when we are removing kea from the system, e.g. when cleaning up after tests */ beforeCloseContext?: (context: Context) => void } export type PluginEventArrays = { [K in keyof PluginEvents]: PluginEvents[K][] } export interface KeaPlugin { /** Required: name of the plugin */ name: string /** Default values ...applied on top of built logic */ defaults?: () => Record /** Hook into various lifecycle events */ events?: PluginEvents } export interface WrapperContext { isBuilding: boolean keyBuilder: L['keyBuilder'] builtLogics: Map> } export interface Context { contextId: string plugins: { activated: KeaPlugin[] events: PluginEventArrays logicFields: Record contexts: Record> } inputCounter: number reducerDefaults: Record | undefined wrapperContexts: WeakMap buildHeap: Logic[] mount: { counter: Record mounted: Record } react: { contexts: WeakMap> } reducers: { tree: any roots: any redux: any whitelist: false | Record combined: ReducerFunction | undefined } // getter that always returns something store: Store // the created store if present __store: Store | undefined options: InternalContextOptions }