typescriptfp-ts

Get the static type from generic runtime type


Try to get the ApiResponse<T> static TS generic type from the generic runtime type created by io-ts. No information in official documentation

Expect type:

// runtime type
const ApiResponseCodec = <C extends t.Mixed>(codec: C) =>
  t.type({
    code: t.string,
    message: t.union([t.string, t.undefined]),
    result: codec,
  });

// expect type
type ApiResponse<T> = {
  code: string;
  message: string | undefined;
  result: T;
}

Complete code:

import { pipe } from 'fp-ts/lib/function';
import * as E from 'fp-ts/lib/Either';
import * as t from 'io-ts';

const ArticleDTOCodec = t.type({
  id: t.number,
  title: t.string,
});
type ArticleDTO = t.TypeOf<typeof ArticleDTOCodec>;

const PaginationResultCodec = <C extends t.Mixed>(codec: C) =>
  t.type({
    resultList: codec,
    totalItem: t.number,
  });

type PaginationResult<C extends t.Mixed> = t.TypeOf<ReturnType<typeof PaginationResultCodec<C>>>;

const ApiResponseCodec = <C extends t.Mixed>(codec: C) =>
  t.type({
    code: t.string,
    message: t.union([t.string, t.undefined]),
    result: codec,
  });

// Does not infer the type correctly.
type ApiResponse<C extends t.Mixed> = t.TypeOf<ReturnType<typeof ApiResponseCodec<C>>>;

const GetArticlesByPageResponseCodec = ApiResponseCodec(PaginationResultCodec(t.array(ArticleDTOCodec)));

export const decodeApiResponse = (res: ApiResponse<PaginationResult<ArticleDTO[]>>) => {
  return pipe(
    res,
    GetArticlesByPageResponseCodec.decode,
    E.fold(
      (e) => 'no',
      (res) => 'yes',
    ),
  );
};

Got error throws by ApiResponse<PaginationResult<ArticleDTO[]>>:

Type '{ resultList: unknown; totalItem: number; }' does not satisfy the constraint 'Mixed'.
  Type '{ resultList: unknown; totalItem: number; }' is missing the following properties from type 'Type<any, any, unknown>': name, is, validate, encode, and 7 more.ts(2344)

TS Playground


Solution

  • You'd need to pass a t.Type<ArticleDTO[]> instead of ArticleDTO[] in the ApiResponse<PaginationResult<ArticleDTO[]>> and also wrap the PaginationResult<...> in the t.Type. Instead, you could use the actual type decoded by the codec as the type variable. Something like this.

    import { pipe } from 'fp-ts/lib/function';
    import * as E from 'fp-ts/lib/Either';
    import * as t from 'io-ts';
    
    const ArticleDTOCodec = t.type({
      id: t.number,
      title: t.string,
    });
    type ArticleDTO = t.TypeOf<typeof ArticleDTOCodec>;
    
    const PaginationResultCodec = <A>(codec: t.Type<A>) =>
      t.type({
        resultList: codec,
        totalItem: t.number,
      });
    
    type PaginationResult<A> = t.TypeOf<ReturnType<typeof PaginationResultCodec<A>>>;
    
    const ApiResponseCodec = <A>(codec: t.Type<A>) =>
      t.type({
        code: t.string,
        message: t.union([t.string, t.undefined]),
        result: codec,
      });
    
    type ApiResponse<A> = t.TypeOf<ReturnType<typeof ApiResponseCodec<A>>>;
    
    const GetArticlesByPageResponseCodec = ApiResponseCodec(PaginationResultCodec(t.array(ArticleDTOCodec)));
    
    export const decodeApiResponse = (res: ApiResponse<PaginationResult<ArticleDTO[]>>) => {
      return pipe(
        res,
        GetArticlesByPageResponseCodec.decode,
        E.fold(
          (e) => 'no',
          (res) => 'yes',
        ),
      );
    };