|
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ |
|
/* eslint-disable @typescript-eslint/no-explicit-any */ |
|
|
|
import { |
|
CommitOptions, |
|
DispatchOptions, |
|
mapActions, |
|
mapGetters, |
|
mapMutations, |
|
mapState, |
|
} from "vuex"; |
|
import { Promisable, UnionToIntersection } from "type-fest"; |
|
|
|
type ActionMethod = (payload?: any) => Promisable<any>; |
|
type ActionRecord = Record<string, ActionMethod>; |
|
|
|
// credit |
|
// https://stackoverflow.com/questions/69643202/how-to-use-typescript-generics-to-correctly-map-between-keys-and-values-with-ind |
|
type GetKeyByValue<Obj, Value> = { |
|
[Prop in keyof Obj]: Obj[Prop] extends Value ? Prop : never; |
|
}[keyof Obj]; |
|
|
|
type ConditionalDispatchPayload<P> = undefined extends P |
|
? [payload?: P, options?: DispatchOptions] |
|
: [payload: P, options?: DispatchOptions]; |
|
|
|
type ConditionalDispatchArgs<F extends ActionMethod> = F extends ( |
|
injectee: ActionContextTyped, |
|
payload: infer P |
|
) => any |
|
? ConditionalDispatchPayload<P> |
|
: F extends (payload: infer P) => any |
|
? ConditionalDispatchPayload<P> |
|
: never; |
|
|
|
type DispatchTyped<T, A extends ActionMethod> = ( |
|
type: T, |
|
...args: ConditionalDispatchArgs<A> |
|
) => Promise<Awaited<ReturnType<A>>>; |
|
|
|
type MutationMethod = (payload?: any) => void; |
|
type MutationRecord = Record<string, MutationMethod>; |
|
|
|
type ConditionalMutationPayload<P> = undefined extends P |
|
? [payload?: P, options?: CommitOptions] |
|
: [payload: P, options?: CommitOptions]; |
|
|
|
type ConditionalMutationArgs<F extends MutationMethod> = F extends ( |
|
state: infer S, |
|
payload: infer P |
|
) => void |
|
? ConditionalMutationPayload<P> |
|
: F extends (payload: infer P) => void |
|
? ConditionalMutationPayload<P> |
|
: never; |
|
|
|
type CommitTyped<T, M extends MutationMethod> = ( |
|
type: T, |
|
...args: ConditionalMutationArgs<M> |
|
) => ReturnType<M>; |
|
|
|
type CommitRecord<M extends MutationRecord> = UnionToIntersection< |
|
{ |
|
[K in keyof M]: CommitTyped<K, M[K]>; |
|
}[keyof M] |
|
>; |
|
|
|
type GetterRecord = Record<string, any>; |
|
|
|
type StateRecord = Record<string, any>; |
|
|
|
type DispatchRecord<A extends ActionRecord> = UnionToIntersection< |
|
{ |
|
[K in keyof A]: DispatchTyped<K, A[K]>; |
|
}[keyof A] |
|
>; |
|
|
|
export type ActionContextTyped< |
|
A extends ActionRecord = any, |
|
S extends StateRecord = any, |
|
RS extends StateRecord = any, |
|
G extends GetterRecord = any, |
|
RG extends GetterRecord = any, |
|
M extends MutationRecord = any |
|
> = { |
|
dispatch: DispatchRecord<A>; |
|
commit: CommitRecord<M>; |
|
state: S; |
|
getters: G; |
|
rootState: RS; |
|
rootGetters: RG; |
|
}; |
|
export type ActionHandlerTyped< |
|
F extends ActionMethod, |
|
A extends ActionRecord, |
|
S extends StateRecord, |
|
RS extends StateRecord, |
|
G extends GetterRecord, |
|
RG extends GetterRecord, |
|
M extends MutationRecord |
|
> = F extends (...args: infer P) => infer R |
|
? (injectee: ActionContextTyped<A, S, RS, G, RG, M>, ...args: P) => R |
|
: never; |
|
/** typed action tree */ |
|
export type ActionTreeTyped< |
|
A extends ActionRecord = any, |
|
S extends StateRecord = any, |
|
RS extends StateRecord = any, |
|
G extends GetterRecord = any, |
|
RG extends GetterRecord = any, |
|
M extends MutationRecord = any |
|
> = { |
|
[K in keyof A]: ActionHandlerTyped<A[K], A, S, RS, G, RG, M>; |
|
}; |
|
|
|
type MutationHandlerTyped<F, S> = F extends (...args: infer P) => void |
|
? (state: S, ...args: P) => void |
|
: never; |
|
export type MutationTreeTyped< |
|
S extends StateRecord = any, |
|
M extends MutationRecord = any |
|
> = { |
|
[K in keyof M]: MutationHandlerTyped<M[K], S>; |
|
}; |
|
|
|
type GetterHandlerTyped< |
|
F, |
|
S extends StateRecord, |
|
RS extends StateRecord, |
|
G extends GetterRecord, |
|
RG extends GetterRecord |
|
> = (state: S, getters: G, rootState: RS, rootGetters: RG) => F; |
|
|
|
export type GetterTreeTyped< |
|
S extends StateRecord = any, |
|
RS extends StateRecord = any, |
|
G extends GetterRecord = any, |
|
RG extends GetterRecord = any |
|
> = { |
|
[K in keyof G]: GetterHandlerTyped<G[K], S, RS, G, RG>; |
|
}; |
|
|
|
// returns a type which skips the first context argument |
|
type OmitActionContext<F> = F extends (...args: infer P) => infer R |
|
? (...args: P) => Promise<Awaited<R>> |
|
: never; |
|
|
|
type ModuleState<Modules extends ModuleRecord> = { |
|
[M in keyof Modules]: ReturnType<Modules[M]["state"]>; |
|
}; |
|
|
|
type ModuleGettersTree<MT, MD extends ModuleDefinition> = UnionToIntersection<{ |
|
[GT in keyof MD["getters"] as AddNamespace<GT, MT>]: ReturnType< |
|
MD["getters"][GT] |
|
>; |
|
}>; |
|
|
|
type ModuleGetters< |
|
Modules extends ModuleRecord, |
|
Namespace = "" |
|
> = UnionToIntersection< |
|
{ |
|
[M in keyof Modules]: ModuleGettersTree< |
|
AddNamespace<M, Namespace>, |
|
Modules[M] |
|
>; |
|
}[keyof Modules] |
|
>; |
|
|
|
type ModuleDefinition<S extends StateRecord = any> = { |
|
namespaced: boolean; |
|
state: () => S; |
|
getters: GetterTreeTyped; |
|
actions: ActionTreeTyped; |
|
mutations: any; |
|
}; |
|
type ModuleRecord = Record<string, ModuleDefinition>; |
|
|
|
type AddNamespace<Key, Namespace = ""> = Namespace extends string |
|
? Key extends string |
|
? `${Namespace}${"" extends Namespace ? "" : "/"}${Key}` |
|
: never |
|
: never; |
|
|
|
type DispatchActions< |
|
Actions extends ActionTreeTyped, |
|
Namespace = "" |
|
> = UnionToIntersection< |
|
{ |
|
[K in keyof Actions]: DispatchTyped<AddNamespace<K, Namespace>, Actions[K]>; |
|
}[keyof Actions] |
|
>; |
|
|
|
type DispatchModules<Modules extends ModuleRecord, Namespace = ""> = { |
|
[K in keyof Modules]: DispatchActions< |
|
Modules[K]["actions"], |
|
AddNamespace<Modules[K] extends ModuleDefinition ? K : never, Namespace> |
|
>; |
|
}; |
|
|
|
type DispatchModulesTyped<Modules extends ModuleRecord> = UnionToIntersection< |
|
DispatchModules<Modules>[keyof DispatchModules<Modules>] |
|
>; |
|
|
|
type CommitMutations< |
|
Mutations extends MutationTreeTyped, |
|
Namespace = "" |
|
> = UnionToIntersection< |
|
{ |
|
[K in keyof Mutations]: CommitTyped< |
|
AddNamespace<K, Namespace>, |
|
Mutations[K] |
|
>; |
|
}[keyof Mutations] |
|
>; |
|
|
|
/** Typed wrapper for mapActions on the root store |
|
* |
|
* NOTE: needs to be called with extra parenthesis to infer map keys correctly |
|
* |
|
* @example |
|
* mapActionsRoot<TYPE>()(map) |
|
* |
|
*/ |
|
export const mapActionsRoot = < |
|
S extends ActionRecord, |
|
Key extends keyof S & string = keyof S & string |
|
>() => { |
|
function anonymous<Mp extends Key[]>( |
|
map: Mp |
|
): { |
|
[K in Mp[number]]: OmitActionContext<S[K]>; |
|
}; |
|
function anonymous<Mp extends Key[]>(map: Mp) { |
|
return mapActions(map) as never; |
|
} |
|
return anonymous; |
|
}; |
|
|
|
/** Typed wrapper for mapActions using a namespaced store |
|
* |
|
* NOTE: needs to be called with extra parenthesis to infer map keys correctly |
|
* |
|
* @example |
|
* mapActionsNamespaced<TYPE>()(namespace, map) |
|
* |
|
*/ |
|
export const mapActionsNamespaced = < |
|
S extends ActionRecord, |
|
Key extends keyof S & string = keyof S & string |
|
>() => { |
|
function anonymous<Mp extends Key[]>( |
|
namespace: string, |
|
map: Mp |
|
): { |
|
[K in Mp[number]]: OmitActionContext<S[K]>; |
|
}; |
|
function anonymous<Mp extends Key[]>(namespace: string, map: Mp) { |
|
return mapActions(namespace, map) as never; |
|
} |
|
|
|
return anonymous; |
|
}; |
|
|
|
/** Typed wrapper for mapActions using a namespaced store and renaming the keys |
|
* |
|
* NOTE: needs to be called with extra parenthesis to infer map keys correctly |
|
* |
|
* @example |
|
* mapActionsNamespacedWithRename<TYPE>()(namespace, map) |
|
* |
|
*/ |
|
export const mapActionsNamespacedWithRename = < |
|
S extends ActionRecord, |
|
Key extends keyof S & string = keyof S & string |
|
>() => { |
|
function anonymous<Prop extends string, Mp extends Record<Prop, Key>>( |
|
namespace: string, |
|
map: Mp |
|
): { |
|
[P in Key as GetKeyByValue<Mp, P>]: OmitActionContext<S[P]>; |
|
}; |
|
function anonymous<Prop extends string, Mp extends Record<Prop, Key>>( |
|
namespace: string, |
|
map: Mp |
|
) { |
|
return mapActions(namespace, map) as never; |
|
} |
|
return anonymous; |
|
}; |
|
|
|
/** Typed wrapper for mapActions on the root store |
|
* |
|
* NOTE: needs to be called with extra parenthesis to infer map keys correctly |
|
* |
|
* @example |
|
* mapActionsRoot<TYPE>()(map) |
|
* |
|
*/ |
|
export const mapGettersRoot = < |
|
S extends GetterRecord, |
|
Key extends keyof S & string = keyof S & string |
|
>() => { |
|
function anonymous<Mp extends Key[]>( |
|
map: Mp |
|
): { |
|
[K in Mp[number]]: () => S[K]; |
|
}; |
|
function anonymous<Mp extends Key[]>(map: Mp) { |
|
return mapGetters(map) as never; |
|
} |
|
return anonymous; |
|
}; |
|
|
|
/** Typed wrapper for mapGetters using a namespaced store |
|
* |
|
* NOTE: needs to be called with extra parenthesis to infer map keys correctly |
|
* |
|
* @example |
|
* mapGettersNamespaced<TYPE>()(namespace, map) |
|
* |
|
*/ |
|
export const mapGettersNamespaced = < |
|
S extends GetterRecord, |
|
Key extends keyof S & string = keyof S & string |
|
>() => { |
|
function anonymous<Mp extends Key[]>( |
|
namespace: string, |
|
map: Mp |
|
): { |
|
[K in Mp[number]]: () => S[K]; |
|
}; |
|
function anonymous<Mp extends Key[]>(namespace: string, map: Mp) { |
|
return mapGetters(namespace, map) as never; |
|
} |
|
return anonymous; |
|
}; |
|
|
|
/** Typed wrapper for mapGetters using a namespaced store and renaming the keys |
|
* |
|
* NOTE: needs to be called with extra parenthesis to infer map keys correctly |
|
* |
|
* @example |
|
* mapGettersNamespacedWithRename<TYPE>()(namespace, map) |
|
* |
|
*/ |
|
export const mapGettersNamespacedWithRename = < |
|
S extends GetterRecord, |
|
Key extends keyof S & string = keyof S & string |
|
>() => { |
|
function anonymous<Prop extends string, Mp extends Record<Prop, Key>>( |
|
namespace: string, |
|
map: Mp |
|
): { |
|
[P in Key as GetKeyByValue<Mp, P>]: () => S[P]; |
|
}; |
|
function anonymous<Prop extends string, Mp extends Record<Prop, Key>>( |
|
namespace: string, |
|
map: Mp |
|
) { |
|
return mapGetters(namespace, map) as never; |
|
} |
|
return anonymous; |
|
}; |
|
|
|
/** Typed wrapper for mapState on the root store |
|
* |
|
* NOTE: needs to be called with extra parenthesis to infer map keys correctly |
|
* |
|
* @example |
|
* mapStateRoot<TYPE>()(map) |
|
* |
|
*/ |
|
export const mapStateRoot = < |
|
S extends StateRecord, |
|
Key extends keyof S & string = keyof S & string |
|
>() => { |
|
function anonymous<Mp extends Key[]>( |
|
map: Mp |
|
): { |
|
[K in Mp[number]]: () => S[K]; |
|
}; |
|
function anonymous<Mp extends Key[]>(map: Mp) { |
|
return mapState(map) as never; |
|
} |
|
return anonymous; |
|
}; |
|
|
|
/** Typed wrapper for mapState using a namespaced store |
|
* |
|
* NOTE: needs to be called with extra parenthesis to infer map keys correctly |
|
* |
|
* @example |
|
* mapStateNamespaced<TYPE>()(namespace, map) |
|
* |
|
*/ |
|
export const mapStateNamespaced = < |
|
S extends StateRecord, |
|
Key extends keyof S & string = keyof S & string |
|
>() => { |
|
function anonymous<Mp extends Key[]>( |
|
namespace: string, |
|
map: Mp |
|
): { |
|
[K in Mp[number]]: () => S[K]; |
|
}; |
|
function anonymous<Mp extends Key[]>(namespace: string, map: Mp) { |
|
return mapState(namespace, map) as never; |
|
} |
|
return anonymous; |
|
}; |
|
|
|
/** Typed wrapper for mapState using a namespaced store and renaming the keys |
|
* |
|
* NOTE: needs to be called with extra parenthesis to infer map keys correctly |
|
* |
|
* @example |
|
* mapStateNamespacedWithRename<TYPE>()(namespace, map) |
|
* |
|
*/ |
|
export const mapStateNamespacedWithRename = < |
|
S extends StateRecord, |
|
Key extends keyof S & string = keyof S & string |
|
>() => { |
|
function anonymous<Prop extends string, Mp extends Record<Prop, Key>>( |
|
namespace: string, |
|
map: Mp |
|
): { |
|
[P in Key as GetKeyByValue<Mp, P>]: () => S[P]; |
|
}; |
|
function anonymous<Prop extends string, Mp extends Record<Prop, Key>>( |
|
namespace: string, |
|
map: Mp |
|
) { |
|
return mapState(namespace, map) as never; |
|
} |
|
return anonymous; |
|
}; |
|
|
|
/** Typed wrapper for {@link external:mapMutations} on the root store |
|
* |
|
* NOTE: needs to be called with extra parenthesis to infer map keys correctly |
|
* |
|
* @example |
|
* mapMutationsRoot<TYPE>()(map) |
|
* |
|
*/ |
|
export const mapMutationsRoot = < |
|
S extends MutationRecord, |
|
Key extends keyof S & string = keyof S & string |
|
>() => { |
|
function anonymous<Mp extends Key[]>( |
|
map: Mp |
|
): { |
|
[K in Mp[number]]: S[K]; |
|
}; |
|
function anonymous<Mp extends Key[]>(map: Mp) { |
|
return mapMutations(map) as never; |
|
} |
|
return anonymous; |
|
}; |
|
|
|
/** Typed wrapper for mapMutations using a namespaced store |
|
* |
|
* NOTE: needs to be called with extra parenthesis to infer map keys correctly |
|
* |
|
* @example |
|
* mapMutationsNamespaced<TYPE>()(namespace, map) |
|
* |
|
*/ |
|
export const mapMutationsNamespaced = < |
|
S extends MutationRecord, |
|
Key extends keyof S & string = keyof S & string |
|
>() => { |
|
function anonymous<Mp extends Key[]>( |
|
namespace: string, |
|
map: Mp |
|
): { |
|
[K in Mp[number]]: S[K]; |
|
}; |
|
function anonymous<Mp extends Key[]>(namespace: string, map: Mp) { |
|
return mapMutations(namespace, map) as never; |
|
} |
|
return anonymous; |
|
}; |
|
|
|
/** Typed wrapper for mapMutations using a namespaced store and renaming the keys |
|
* |
|
* NOTE: needs to be called with extra parenthesis to infer map keys correctly |
|
* |
|
* @example |
|
* mapMutationsNamespacedWithRename<TYPE>()(namespace, map) |
|
* |
|
*/ |
|
export const mapMutationsNamespacedWithRename = < |
|
S extends MutationRecord, |
|
Key extends keyof S & string = keyof S & string |
|
>() => { |
|
function anonymous<Prop extends string, Mp extends Record<Prop, Key>>( |
|
namespace: string, |
|
map: Mp |
|
): { |
|
[P in Key as GetKeyByValue<Mp, P>]: S[P]; |
|
}; |
|
function anonymous<Prop extends string, Mp extends Record<Prop, Key>>( |
|
namespace: string, |
|
map: Mp |
|
) { |
|
return mapMutations(namespace, map) as never; |
|
} |
|
return anonymous; |
|
}; |
|
|
|
export type StoreTyped< |
|
RS extends StateRecord, |
|
AT extends ActionTreeTyped, |
|
MT extends MutationTreeTyped, |
|
G extends GetterRecord, |
|
Modules extends ModuleRecord |
|
> = { |
|
readonly state: RS & ModuleState<Modules>; |
|
readonly getters: G & ModuleGetters<Modules>; |
|
|
|
dispatch: DispatchActions<AT> & DispatchModulesTyped<Modules>; |
|
commit: CommitMutations<MT>; |
|
}; |
That woulld be nice if
mapStateRoot
() could work with mapper callbacks too.