01290-hard-pinia

Back

declare function defineStore<S, G, A>(
  store: PiniaOptions<S, G, A>
): A & S & GetGetter<G>;

type GetGetter<T> = {
  [P in keyof T]: T[P] extends () => infer R ? R : T[P]
}
type PiniaOptions<S, G, A> = {
  id: string;
  state: () => S;
  getters: G & ThisType<Readonly<S> & GetGetter<G>>;
  actions: A & ThisType<S & Readonly<GetGetter<G>> & A>;
};

Solution by Vampirelee #32629

type FunToProp<G> = {
  [K in keyof G]: G[K] extends (...args: any[]) => any
    ? ReturnType<G[K]>
    : never;
};
declare function defineStore<S, G, A>(store: {
  id: string;
  state: () => S;
  getters: G & ThisType<Readonly<S> & FunToProp<G>>;
  actions: A & ThisType<S & A>;
}): A & S & FunToProp<G>;

Solution by vangie #32336

type Flatten<G extends Record<string, Function>> = {
  readonly [key in keyof G]: G[key] extends () => unknown ? ReturnType<G[key]> : never
}

declare function defineStore<S, G extends Record<string, Function>, A extends Record<string, Function>>(store: {
  id: string,
  state: () => S,
  getters: G & ThisType<Readonly<S> & Flatten<G>>,
  actions: A & ThisType<S & Flatten<G> & A>
}): A & S & Flatten<G>

Solution by Ayc0 #31451

type Resolve<O> = O extends { [P: string]: (...args: any[]) => any }
  ? {
      [P in keyof O]: ReturnType<O[P]>
    }
  : never

declare function defineStore<D, G, A>(store: {
  id: string
  state: () => D
  getters?: G & ThisType<Readonly<D & Resolve<G>>>
  actions?: A & ThisType<D & A>
}): D & Resolve<G> & A

Solution by Chan-Yuxi #30692

type BaseObject = Record<string, unknown>
// type BaseGetters = Record<string, (...args: any[]) => any>
type BaseGetters = Record<string, Function>
// type BaseActions = Record<string, (...args: any[]) => any>
type BaseActions = Record<string, Function>

type GetComputedGetters<Getters extends BaseGetters> = {
  [Key in keyof Getters]: Getters[Key] extends (...args: unknown[]) => infer RT ? RT : never
}

type Store<
  State extends BaseObject,
  Getters extends BaseGetters,
  Actions extends BaseActions
  > = {
  id: string
  state: () => State
  getters: Getters & ThisType<Readonly<State> & GetComputedGetters<Getters>>
  actions: Actions & ThisType<State & Actions & Readonly<Getters>>
} 

declare function defineStore<
  State extends BaseObject,
  Getters extends BaseGetters,
  Actions extends BaseActions
  >(store: Store<State, Getters, Actions>): State & GetComputedGetters<Getters> & Actions

Solution by aybykovskii #28848

type Getter<T> = {
  readonly [key in keyof T]: T[key] extends (...args: any) => any
    ? ReturnType<T[key]>
    : never;
};
declare function defineStore<
  S extends Record<string, any>,
  G extends Record<string, Function>,
  A extends Record<string, Function>,
>(store: {
  id: string;
  state: () => S;
  getters?: G & ThisType<Readonly<S> & Getter<G>>;
  actions?: A & ThisType<A & S & Getter<G>>;
}): S & Getter<G> & A;

Solution by disanrencheng #27879

type AnyObject = Record<string, unknown>;
type BaseGetters = Record<string, Function>;
type BaseActions = Record<string, (...args: any[]) => any>;

type ComputedGetters<G extends BaseGetters> = {
  readonly [K in keyof G]: G[K] extends (...args: any[]) => any ? ReturnType<G[K]> : never;
}

interface StoreOptions<
  State extends AnyObject, 
  Getters extends BaseGetters,
  Actions extends BaseActions
> {
  id: string;
  state: () => State;
  getters: Getters & ThisType<ComputedGetters<Getters> & Readonly<State>>,
  actions: Actions & ThisType<Actions & State & ComputedGetters<Getters>>,
};

type Store<
  State extends AnyObject, 
  Getters extends BaseGetters,
  Actions extends BaseActions
> = {
  init(): void;
  reset(): true;
} & Actions & State & ComputedGetters<Getters>;

declare function defineStore<
  State extends AnyObject, 
  Getters extends BaseGetters,
  Actions extends BaseActions
>(store: StoreOptions<State, Getters, Actions>): Store<State, Getters, Actions>

Solution by viktor-sakhno-saritasa #27673

I haven't saw a solution where someone used generic constraints (i.e. extends ...). which I think is really important for the real library.

Here is my solution:

type AnyObject = Record<string, unknown>
type BaseGetters = Record<string, Function> ;
type BaseActions = Record<string, (...args:any[]) => any>;

type ComputedGetters<G extends BaseGetters> = {
  readonly [K in keyof G]: G[K] extends (...args:any[]) => any ? ReturnType<G[K]> : never;
};

interface StoreOptions<State extends AnyObject, Getters extends BaseGetters, Actions extends BaseActions> {
  id: string;
  state: () => State,
  getters: Getters & ThisType<ComputedGetters<Getters> & Readonly<State>>,
  actions: Actions & ThisType<Actions & State & ComputedGetters<Getters>>
}

type Store<
  State extends AnyObject, 
  Getters extends BaseGetters, 
  Actions extends BaseActions
>= {
  init(): void;
  reset(): true;
} & Actions & State & ComputedGetters<Getters>;

declare function defineStore<
  State extends AnyObject, 
  Getters extends BaseGetters, 
  Actions extends BaseActions
>(store: StoreOptions<State, Getters, Actions>): Store<State, Getters, Actions>;

Solution by Ayub-Begimkulov #27536

1290 - Pinia

It's a great feeling to see a real-world use-case where we can put these skills into use. Pinia is a state management library, and in this challenge we write a type for its the top-level API.

๐ŸŽฅ Video Explanation

Release Date: 2023-04-19 19:00 UTC

Pinia

๐Ÿ”ข Code

// ============= Test Cases =============
import type { Equal, Expect } from './test-utils'

const store = defineStore({
  id: '',
  state: () => ({
    num: 0,
    str: '',
  }),
  getters: {
    stringifiedNum() {
      // @ts-expect-error
      this.num += 1

      return this.num.toString()
    },
    parsedNum() {
      return parseInt(this.stringifiedNum)
    },
  },
  actions: {
    init() {
      this.reset()
      this.increment()
    },
    increment(step = 1) {
      this.num += step
    },
    reset() {
      this.num = 0

      // @ts-expect-error
      this.parsedNum = 0

      return true
    },
    setNum(value: number) {
      this.num = value
    },
  },
})

// @ts-expect-error
store.nopeStateProp
// @ts-expect-error
store.nopeGetter
// @ts-expect-error
store.stringifiedNum()
store.init()
// @ts-expect-error
store.init(0)
store.increment()
store.increment(2)
// @ts-expect-error
store.setNum()
// @ts-expect-error
store.setNum('3')
store.setNum(3)
const r = store.reset()

type A1 = typeof store.num;
type B1 = number;
type C1 = Expect<Equal<A1, B1>>;

type A2 = typeof store.str;
type B2 = string;
type C2 = Expect<Equal<A2, B2>>;

type A3 = typeof store.stringifiedNum;
type B3 = string;
type C3 = Expect<Equal<A3, B3>>;

type A4 = typeof store.parsedNum;
type B4 = number;
type C4 = Expect<Equal<A4, B4>>;

type A5 = typeof r;
type B5 = true;
type C5 = Expect<Equal<A5, B5>>;

// ============= Your Code Here =============
type ObjectValueReturnType<T> = {
  readonly [P in keyof T]:
    T[P] extends (...args: any[]) => infer R
    ? R
    : never
}

declare function defineStore<State, Getters, Actions> (
  store: {
    id: string
    state: () => State
    getters:
      & Getters
      & ThisType<
          & Readonly<State>
          & ObjectValueReturnType<Getters>
        >,
    actions:
      & Actions
      & ThisType<
          & State
          & ObjectValueReturnType<Getters>
          & Actions
        >
  },
):
  & State
  & ObjectValueReturnType<Getters>
  & Actions;

โž• More Solutions

For more video solutions to other challenges: see the umbrella list! https://github.com/type-challenges/type-challenges/issues/21338

Solution by dimitropoulos #26115

type Getters<T extends { [x: string]: Function }> = { readonly [P in keyof T]: T[P] extends () => infer R ? R : any };
type Store<
  _State extends { [x: string]: any },
  _GetterFuncs extends { [x: string]: Function },
  _Actions extends { [x: string]: Function }
> = {
  id: string,
  state: () => _State,
  getters: _GetterFuncs & ThisType<Readonly<_State> & Getters<_GetterFuncs>>,
  actions: _Actions & ThisType<_Actions & _State & Getters<_GetterFuncs>>
}

declare function defineStore<
  _S extends { [x: string]: any },
  _G extends { [x: string]: Function },
  _A extends { [x: string]: Function }
>(store: Store<_S, _G, _A>): Readonly<_S> & _A & Getters<_G>;

Solution by E-uler #24960

type GettersReturnType<G> = {
  readonly [K in keyof G]: G[K] extends (...args: never[]) => infer R ? R : never;
}

interface Store<S, G, A> {
	id: string
	state: () => S
	getters?: G & ThisType<Readonly<S> & GettersReturnType<G> & A>
	actions?: A & ThisType<S & GettersReturnType<G>  & A>
}

declare function defineStore<S, G, A>(store: Store<S, G, A>): Readonly<S> & GettersReturnType<G> & A

Solution by TKBnice #23318

type Getters<G> = {
  readonly [K in keyof G]: G[K] extends (...args: any[]) => infer R ? R : never
}

interface StoreOptions<S, G, A> {
  id: string
  state: () => S
  getters: G & ThisType<Readonly<S> & Getters<G>>
  actions: A & ThisType<S & Getters<G> & Readonly<A>>
}

declare function defineStore<S, G, A> (
  storeOptions: StoreOptions<S, G, A>,
): S & Getters<G> & Readonly<A>

Solution by drylint #23214

type InferGetters<T> = T extends Record<string, (...args: any[]) => any> ? {
  [K in keyof T]: ReturnType<T[K]>
}: never;

declare function defineStore<State, G, A>(store: {
  id: string,
  state: () => State
  getters: G & ThisType<Readonly<State> & InferGetters<G> & A>,
  actions: A & ThisType<State & Readonly<InferGetters<G>> & A>
}): State & InferGetters<G> & A

Solution by coderyoo1 #23024

// easily comparable function type
type Fn = (...args: any) => unknown;
// tranform object to contain all attributes and transform function types to their return types
type TypeOrReturnType<T> = {
  [key in keyof T]: T[key] extends Fn ? ReturnType<T[key]> : T[key]
};

type StoreConfig<S, G, A> = {
  id: string;
  state: () => S;
  getters: G & ThisType<TypeOrReturnType<G> & Readonly<S>>;
  actions: A & ThisType<A & S & Readonly<TypeOrReturnType<G>>>;
};

declare function defineStore<S,G,A>(store: StoreConfig<S,G,A>): A & S & TypeOrReturnType<G>

Solution by Karamuto #22036

// ๆœ‰็‚นไนฑใ€‚ใ€‚ใ€‚ๆ‹ผๆ‹ผๅ‡‘ๅ‡‘็š„ๆœ€็ปˆๆŠŠ้”™่ฏฏๆถˆ้™คๅฎŒไบ†ใ€‚ใ€‚ใ€‚
declare function defineStore<S, G, A>(store: StoreOptioon<S, G, A>)
    : A & S & GETTERS<G> & StoreOptioon<S, G, A>

type GETTERS<G> = { [K in keyof G]: G[K] extends () => infer R ? R : never }

type StoreOptioon<S, G, A> = {
    id: string
    state?: () => S
    getters?: G & ThisType<GETTERS<G> & Readonly<S>>
    actions?: A & ThisType<S & A>
}

Solution by goddnsgit #21906

type StoreType = Record<string, any>;
type GettersType = Record<string, Function>;
type ActionsType = Record<string, Function>;

type TransformGetters<T> = {
  [P in keyof T]: T[P] extends () => infer R ? R : never;
};

interface Pinia<
  Store extends StoreType,
  Getters extends GettersType,
  Actions extends ActionsType
> {
  id: string;
  state: () => Store;
  getters: Getters & ThisType<Readonly<Store> & TransformGetters<Getters>>;
  actions: Actions & ThisType<Store & Actions>;
}

declare function defineStore<
  Store extends StoreType,
  Getters extends GettersType,
  Actions extends ActionsType
>(
  store: Pinia<Store, Getters, Actions>
): Store & Actions & TransformGetters<Getters>;

Solution by so11y #21233

type GetRes<T> = T extends (...args: any[]) => infer R ? R : never
declare function defineStore<State, Getters, Actions, _Getters = {
  readonly [P in keyof Getters]: GetRes<Getters[P]>
}>(store: {
  id: string,
  state: (this: void) => State,
  getters?: Getters & ThisType<_Getters & Readonly<State>>
  actions?: Actions & ThisType<State & _Getters & Actions>
}): State & _Getters & Actions;

Solution by BulatDashiev #16942

// your answers

type StoreOptions<State, Getters, Actions> = {
  id?: string;
  state?: () => State;
  getters?: Getters & ThisType<Readonly<State> & {
    readonly [P in keyof Getters]: Getters[P] extends (...args: any) => infer R
      ? R
      : never 
  }>;
  actions?: Actions & ThisType<State & {
    readonly [P in keyof Getters]: Getters[P] extends (...args: any) => infer R
      ? R
      : never 
  } & Actions>;
}

declare function defineStore<State, Getters, Actions>(store: StoreOptions<State, Getters, Actions>): Readonly<State> & {
  readonly [P in keyof Getters]: Getters[P] extends (...args: any) => infer R
    ? R
    : never 
} & Actions

Solution by humandetail #16480

// your answers
type Store<
    I extends string,
    S extends object,
    G extends object,
    A extends object> =
{
  id: I,
  state: ()=>S,
  getters: G&ThisType<Readonly<S>&Map<G>>,
  actions: A&ThisType<A&S&Readonly<G>>  
} ;

declare function defineStore<
    I extends string,
    S extends object,
    G extends object,
    A extends object>(store: Store<I,S,G,A>): Readonly<S&Map<G>&A>  ;

Solution by justBadProgrammer #15903


// We don't need this helper, but it's really nice to do this as people use nested
// state a lot in Pinia / Vuex / your favourite state management solution.
type DeepReadonly<T> = {
  readonly [K in keyof T]: 
    T[K] extends object 
      ? DeepReadonly<T[K]> 
      : T[K]
}

// For computed getters, map the values from functions to their return types,
// representing their cached values.
//   There's an enhancement we should make here which is that if the getters list a
// function with at least one parameter, this is not a computed property, but a
// function that returns that function (or something like that - as obviously you
// can't cache something (fully) which accepts parameters).
type Compute<Getters extends object> = Readonly<{
  [P in keyof Getters]: Getters[P] extends () => infer Result
    ? Result
    : never
}>

// Getters get (deep) readonly access to the state, and all of the other (computed) getters
type Getters<State> = {
  [P in keyof any]: (
    this: DeepReadonly<State> & Compute<Getters<State>>, 
    ...args: any[]
  ) => any
}

// Actions get non-readonly access to the state, and all of the other (computed) getters
type Actions<State> = {
  [P in keyof any]: (
    this: State & Compute<Getters<State>>, 
    ...args: any[]
  ) => any
}

type Options<State extends object, GettersWithState, ActionsWithState> = {
  id: string,
  state: () => State,
  getters: GettersWithState,
  actions: ActionsWithState
}

type Store<State extends object, Getters extends object, Actions extends object> = 
  State
  &
  Compute<Getters>
  &
  Actions

// We infer the State, and then place constraints on the user-defined Getters & 
// Actions based on the State, which leads to some great auto-complete & type-checking
// when calling defineStore.
declare function defineStore<
  State extends object, 
  GettersWithState extends Getters<State>,
  ActionsWithState extends Actions<State>
>(store: Options<State, GettersWithState, ActionsWithState>)
  : Store<State, GettersWithState, ActionsWithState>

Solution by its-lee #13771

// your answers
declare function defineStore<State, Getters, Actions,
  ThisGetters = {
    readonly [K in keyof State]: State[K]
  } & {
    readonly [K in keyof Getters]: Getters[K] extends () => infer R ? R : Getters[K]
  } extends infer Merge ? { [K in keyof Merge]: Merge[K] } : never,
  ThisActions = State & Getters & Actions
>(store: {
  id: string,
  state: () => State,
  getters: Getters & ThisType<ThisGetters>,
  actions: Actions & ThisType<ThisActions>,
}): ThisGetters & Actions

Solution by liuxing95 #13056

// your answers
declare function defineStore<S, G, A>(store: {
  id: string;
  state: () => S;
  getters: G &
    ThisType<
      Readonly<S> & { [K in keyof G]: G[K] extends () => infer R ? R : never }
    >;
  actions: A & ThisType<S & A>;
}): S & { [K in keyof G]: G[K] extends () => infer R ? R : never } & A;

Solution by SeptEarlyMorning #12136

declare function defineStore<State, Getters, Actions,
  ThisGetters = {
    readonly [K in keyof State]: State[K]
  } & {
    readonly [K in keyof Getters]: Getters[K] extends () => infer R ? R : Getters[K]
  } extends infer Merge ? { [K in keyof Merge]: Merge[K] } : never,
  ThisActions = State & Getters & Actions
>(store: {
  id: string,
  state: () => State
  getters: Getters & ThisType<ThisGetters>
  actions: Actions & ThisType<ThisActions>,
}): ThisGetters & Actions

Playground

Solution by teamchong #11824


declare function defineStore<S, G, A>(store: {
  id: string
  state: () => S
  getters: G & ThisType<S & {[K in keyof G]: G[K] extends () => infer TReturn ? TReturn : G[K]}>
  actions: A & ThisType<S &A & {[K in keyof G]: G[K] extends () => infer TReturn ? TReturn : G[K]} >
}): S & A & {[K in keyof G]: G[K] extends () => infer TReturn ? TReturn : G[K]}

Solution by wqs576222103 #11070

// ไธ็กฎๅฎš่ฟ™ๆ ทๆ˜ฏไธๆ˜ฏๅฏน็š„ ๐Ÿ˜ฅ

type UnionToIntersect<T> = (T extends T ? (x: T) => unknown : never) extends (x: infer P) => unknown ? P : never;

type Getter<T, K = keyof T> = UnionToIntersect<
  K extends keyof T ?
    {
      [Key in K]: T[Key] extends () => infer ReturnType ? ReturnType : never
    }
  : never
>;

declare function defineStore<S, G, A>(store: {
  id: string,
  state(): S,
  getters: G & ThisType<Getter<G> & S>,
  actions: A & ThisType<A & S & Getter<G>>
}): S & Getter<G> & A;

Solution by XkSuperCool #10613

declare function defineStore<S,G extends Record<string,any>,A>(store: {
  id: string,
  state:() => S,
  getters: G & ThisType<S & {
    [key in keyof G]: (G[key] extends () => infer Rrturn ? Rrturn: G[key])
  } >,
  actions: A & ThisType<A & S & ({
    [key in keyof G]: (G[key] extends () => infer Rrturn ? Rrturn: G[key])
  })>
}): A & S & ({
    [key in keyof G]: (G[key] extends () => infer Rrturn ? Rrturn: G[key])
  })

Solution by EGyoung #8616

// your answers
type GetGettersType<T> = {
  [P in keyof T]: T[P] extends (...args: unknown[]) => infer R ? R : never
}

declare function defineStore<D, G, A>(store: {
  id: string
  state: () => D
  getters: G & ThisType<Readonly<D> & GetGettersType<G>>
  actions: A & ThisType<D & Readonly<G> & A>
}): GetGettersType<G> & A & D

Solution by godlanbo #7578

// 1290 - Pinia

type GetThisWithoutState<Getters, Actions> = { readonly [K in keyof Getters]: Getters[K] extends (...args: never[]) => infer R ? R : never; } & Actions;

declare function defineStore< State, Getters, Actions, ThisWithoutState = GetThisWithoutState<Getters, Actions>

(store: { id: string; state: () => State; getters?: Getters & ThisType<Readonly & ThisWithoutState>; actions?: Actions & ThisType<State & ThisWithoutState>; }): Readonly & ThisWithoutState;

Solution by Carefree-happy #7118

type MapGetters<G> = { readonly [K in keyof G]: G[K] extends (...args: any[]) => infer R ? R : never }
declare function defineStore<S, G, A>(store: {
  id: string,
  state: () => S,
  getters: G & ThisType<Readonly<S> & MapGetters<G>>,
  actions: A & ThisType<S & MapGetters<G> & A>,
}): S & MapGetters<G> & A & {init: () => void }


Solution by gulewei #4142

type ReturnX<T> = {
  [P in keyof T]: T[P] extends (...args: unknown[]) => infer R ? R : never
}

interface Options<S, G, A> {
  id: string
  state: () => S
  getters: G & ThisType<Readonly<S> & Readonly<ReturnX<G>>>
  actions: A & ThisType<S & Readonly<ReturnX<G>> & A>
}

declare function defineStore<S, G, A>(store: Options<S, G, A>): S & ReturnX<G> & A

Solution by Luncher #4089