1

I'm embarrassed asking this because I've spent hours looking at other questions, and I know this has been asked a dozen times, yet I remain confused.

I am using typescript and Vuex, and am trying to make a wrapper around an object of Getters to check if that module has been initialized first.

The use would look something like

//index.ts
import { Module } from 'vuex';

import { makeSafeGetters } from './safeGetters'

export const testModule: Module<testState, RootState> = {
  getters: makeSafeGetters(getters),
//safeGetters.ts
/**
 * This takes the getters from a module and wraps in a safety call that ensures
 * you are not accessing uninitialized stores
 *
 * @param getterList - the getter tree exported from the `<module>.getters.ts`
 */
export function makeSafeGetters<U extends BaseState<any>>
(getterList: GetterTree<U, RootState>) {
  const getters: GetterTree<U, RootState> = {};
  for (const key in getterList) {
    if (Object.prototype.hasOwnProperty.call(getterList, key)) {
      const getter = getterList[key];
      getters[key] = safeGetter(getter);
    }
  }
  return getters;
}

/**
 * Wraps a getter with a call to make sure the store has been initialized
 *
 * @param getter - the getter to be wrapped
 */
function safeGetter<V, T extends Getter<V extends BaseState<any> ? V : never, RootState>>
(getter: T): T {
  return (state, ...args): ReturnType<T> => {
    if (!state.initialized && !state.loading) {
      console.error(`${getter.name}: <ModuleNameHere> not initialized, make sure store has been fetched.\
  Check stack for module name`,
      { state });
    }
    return getter(state, ...args);
  };
}
//type.ts
export interface BaseState<T> {
  initialized: boolean;
  loading: Promise<any> | null | boolean;
  values: T;
}

My problem is in the safeGetter function. On the line return (state, ...args) I get the following error

Type '(state: V extends BaseState<any> ? V : never, getters: any, rootState: RootState, rootGetters: any) => ReturnType<T>' is not assignable to type 'T'.
  '(state: V extends BaseState<any> ? V : never, getters: any, rootState: RootState, rootGetters: any) => ReturnType<T>' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Getter<V extends BaseState<any> ? V : never, RootState>'.ts(2322)

Something that is important to me is that the types of the getters are transparent through makeSafeGetters, such that if I hover over makeSafeGetters in index.ts I get

(alias) makeSafeGetters<TestState>(getterList: GetterTree<TestState, RootState>): GetterTree<TestState, RootState>

A similar question was asked here https://stackoverflow.com/a/59148659/6100005, but I don't know how to apply that to my circumstance without clobbering the types

Max Coplan
  • 1,111
  • 13
  • 27
  • I'm not familiar with vuex, but can you make the argument type more specific than `(getter: T)`, such as have a separate type variable for the parameters and args: something like `safeGetter(getter: (state: S, ...args: A) => R): (state: S, ...args: A) => R` ? I think the problem arises from the unconstrained nature of `T`: if you don't know the specific type of a function typed as `T`, then you will have trouble convincing the type system you have exactly replicated its type. Your `T extends Getter<...>` is an upper bound, but the **actual** type of `T` imposes a stricter upper bound. – jtbandes Sep 08 '20 at 22:20
  • Now that I read your similar question link, I realize that's the same thing suggested in the accepted answer ;) – jtbandes Sep 08 '20 at 22:29
  • Can you make the return type `Getter ? V : never, RootState>` instead of `T`? – Mingwei Samuel Sep 09 '20 at 05:26

0 Answers0