import type { ActionCreator , ActionCreatorProps , Creator } from "@ngrx/store" ; import type { NotAllowedInPropsCheck } from "@ngrx/store/src/models" ; import type { ValueOf } from "ts-essentials" ; import { mapKeys } from "remeda" ; type MatchPropName = "match" ; type TypedAction < T extends string > = { readonly type : T ; } ; type RawObject = object ; type PropsRecord = { [ T in string ] : ActionCreatorProps < RawObject > | null ; } & { [ K in MatchPropName ] ? : never ; } ; type ActionsRecord < P extends PropsRecord = PropsRecord > = { [ T in Extract < keyof P , string > ] : P [ T ] extends ActionCreatorProps < RawObject > ? ActionCreator < T , ( props : P [ T ] [ "_p" ] ) => { payload : P [ T ] [ "_p" ] ; } & TypedAction < T > > : ActionCreator < T , ( ) => TypedAction < T > > ; } ; export type UnionOf < T extends ActionsRecord > = Exclude < ReturnType < ValueOf < Omit < StrictOmit < T , MatchPropName > , symbol | number > > > , boolean > ; export type UnionOfRecord < P extends PropsRecord , T extends ActionsRecord < P > = ActionsRecord < P > > = { [ K in Exclude < keyof T , MatchPropName > ] : ReturnType < T [ K ] > ; } ; type Cases < Record , A > = { [ T in keyof Record ] : ( value : Record [ T ] extends { payload : infer P ; } ? P : void ) => A ; } ; type Match < P extends PropsRecord > = < A > ( variant : UnionOf < ActionsRecord < P > > , cases : Cases < UnionOfRecord < P > , A > & { default ? : never ; } | Partial < Cases < UnionOfRecord < P > , A > > & { default : ( variant : UnionOf < ActionsRecord < P > > ) => A ; } ) => A ; export const props = < P extends SafeProps , SafeProps = NotAllowedInPropsCheck < P > > ( ) : ActionCreatorProps < P > => { return { _as : "props" , _p : undefined ! } ; } ; const defineType = < T extends string > ( type , creator ) : ActionCreator < T > => { return Object . defineProperty ( creator , 'type' , { value : type , writable : false } ) as ActionCreator < T > ; } ; export const propsRecordToActionsRecord = < P extends PropsRecord > ( value , { prefix } ) : ActionsRecord < P > & { [ K in MatchPropName ] : Match < P > ; } & { is : ( value : UnionOf < ActionsRecord > ) => value is UnionOf < ActionsRecord < P > > ; } => { const prefixedTypes = new Set < string > ( ) ; const resolvePrefixedType = ( key ) : string => { const result = `${ prefix }:${ String ( key ) }` ; prefixedTypes . add ( result ) ; return result ; } ; return { ... Object . entries ( value ) . reduce ( ( accumulator , [ key , actionCreatorProps ] ) => { const type = resolvePrefixedType ( key ) ; return { ... accumulator , [ key ] : actionCreatorProps ? defineType ( type , ( value ) => ( { payload : value , type } ) ) : defineType ( type , ( ) => ( { type } ) ) , } ; } , { } as ActionsRecord < P > ) , match ( value , matchers ) { const matcher = mapKeys ( matchers , ( key ) => resolvePrefixedType ( key ) ) [ value . type ] ?? matchers . default ; if ( typeof matcher !== "function" ) { throw new Error ( `Failed to resolve matching handler for the "${ String ( value . type ) }" action` ) ; } const args = ( "payload" in value ? [ value . payload ] : [ ] ) ; return matcher ( ... args ) ; } , is : ( ( value ) => prefixedTypes . has ( value . type ) ) as any } ; } ;