Redwood API - Alternative Types Definition Generator

Hi folks, having had a bit of a background in TypeScript - I switched on "strict": true for my redwood app a long time ago and for quite a while accepted that while the types for my app were pretty accurate, they were not always right and often hard to work with:

  • Error messages were very verbose
  • It was hard to describe types where the SDL types and Prisma models did not align perfectly
  • You could not really use those types to pass data between resolvers which wasn’t represented in the SDL

So, over the course of the last few months I had been working on an alternative to the Redwood codegen for the types and this morning I wrapped up all of my issues and 1.0’d it. There’s a long README explaining the full rationale and its feature-set here: GitHub - orta/redwood-codegen-api-types: Replacement types generator for your Redwood API

Getting Set Up

yarn -W add -D @orta/redwood-codegen-api-types

Then to run the codegen locally:

yarn redwood-alt-api-codegen

My recommendation is to add a new "scripts" for "types": "redwood-alt-api-codegen --eslint-fix " - then you can write yarn types and it will do the work. If you’d like to have the in-app types handle for you, I also made an ESLint rule to add the correct types to your resolvers ( here Custom ESLint Rules in Redwood )

3 Likes

Shipped 1.1.0 of the type generator, it now inlines the type narrowing for a resolver function’s return type making it trivial to re-use those in tests or through other files, with a focus on async funcs in particular:

For example:

import { db } from "src/lib/db";

export const gameSync = () => {}
export const gameAsync = async () => {}
export const gameAsync1Arg = (arg) => {}
export const gameAsync2Arg = (arg, obj) => {}
export const gameObj = {}

export const Game = {
  summary: "",
  summarySync: () => "",
  summaryAsync: async () => ""
};

Would get a d.ts like:

import type { Game as RTGame } from "./shared-return-types";
import type { Game as PGame } from "@prisma/client";
import type { GraphQLResolveInfo } from "graphql";
import type { RedwoodGraphQLContext } from "@redwoodjs/graphql-server/dist/functions/types";

/** SDL: gameSync: Game */
export interface GameSyncResolver {
  (args?: object, obj?: {...): RTGame | null | Promise<RTGame | null> | (() => Promise<RTGame | null>);
}

/** SDL: gameAsync: Game */
export interface GameAsyncResolver {
  (args?: object, obj?: { ... }): Promise<RTGame | null>;
}

/** SDL: gameAsync1Arg: Game */
export interface GameAsync1ArgResolver {
  (args: object, obj?: { ... }): RTGame | null | Promise<RTGame | null> | (() => Promise<RTGame | null>);
}

/** SDL: gameAsync2Arg: Game */
export interface GameAsync2ArgResolver {
  (args: object, obj: { ... }): RTGame | null | Promise<RTGame | null> | (() => Promise<RTGame | null>);
}

/** SDL: gameObj: Game */
export interface GameObjResolver {
  (args?: object, obj?: { ... }): RTGame | null;
}

type GameAsParent<Extended> = PGame & {
  summary: () => Promise<string>,
  summarySync: () => Promise<string>,
  summaryAsync: () => Promise<string>
} & Extended;

export interface GameTypeResolvers<Extended> {

  /** SDL: summary: String! */
  summary: string;

  /** SDL: summarySync: String! */
  summarySync: (args?: undefined, obj?: { ... }) => string | Promise<string> | (() => Promise<string>);

  /** SDL: summaryAsync: String! */
  summaryAsync: (args?: undefined, obj?: { ... }) => Promise<string>;
}

In concrete terms, for something like summaryAsync or gameAsync which are the majority of my resolvers (and nearly always the ones I want to test) the type has gone from string | Promise<string> | (() => Promise<string>); to Promise<string> - which means your tests dont need an as to tell TS you know the types.

In theory, I could do the same for a non async function, but it’d need to use the types from TypeScript, and I’m not gonna slow down the generator for that as I want it on an instant watch-mode eventually.

2 Likes