Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stronger typing on makeApi #425

Open
andenacitelli opened this issue Apr 17, 2023 · 10 comments
Open

Stronger typing on makeApi #425

andenacitelli opened this issue Apr 17, 2023 · 10 comments
Labels
need help pinned issues that should not be closed by bot

Comments

@andenacitelli
Copy link

andenacitelli commented Apr 17, 2023

Just took me about twenty minutes to figure out what was wrong with this:

const commonParameters = parametersBuilder()
  .addHeader("authorization", z.string().min(1))
  .addHeader("email", z.string().email());

const usersApi = makeApi([
  {
    method: "post",
    path: "/",
    response: z.undefined(),
    params: commonParameters.build(),
  },
  {
    method: "get",
    path: "/",
    response: UserSchema,
    params: commonParameters.build(),
  },
  {
    method: "put",
    path: "/",
    response: UserSchema,
    params: commonParameters.addBody(UserCreateInputSchema).build(),
  },
  {
    method: "delete",
    path: "/",
    response: z.undefined(),
    params: commonParameters.build(),
  },
]);

The issue is that params is supposed to be parameters. I didn't have any kind of error pop up in my editor. Is it possible to more strongly type this so that it gives you an error in your editor, or does TypeScript just not allow this? Or is something in my editor just messed up?

And sidenote, love the project! Coupled with zod-prisma-types, this is a much quicker-to-prototype alternative to OpenAPI that also doesn't require a code generation step and tends to integrate a bit more smoothly with Prisma. Has the same disadvantage as tRPC where it couples you to TypeScript, but I honestly feel like that's an advantage for smaller projects.

@ecyrbe
Copy link
Owner

ecyrbe commented Apr 17, 2023

Hello @aacitelli,

This is due to typescript allowing unknown properties on functions (function matching is contravariant, while here we would like it to be covariant), check this:
image

@ecyrbe
Copy link
Owner

ecyrbe commented Apr 17, 2023

in zodios v11 you'll be able to use as const and satisfies together as a workaround:
image

@ecyrbe ecyrbe added need help pinned issues that should not be closed by bot labels Apr 17, 2023
@ecyrbe
Copy link
Owner

ecyrbe commented Apr 17, 2023

If anyone has a solution to this, i'll keep this open in case someone has a working idea

@cloudbring
Copy link

Thanks to this issue, it solved a problem I have been having with params instead of parameters and using parametersBuilder() and when using that const, using .build() when setting it on the apiBuilder parameter member.

Example below:

const movieParams = parametersBuilder().addParameters('Query', {
  limit: z.number().optional().default(1000),
  offset: z.number().optional().default(0),
  page: z.number().optional().default(1),
  _id: z.string().optional(),
});

export const movieApi = apiBuilder({
  method: 'get',
  path: '/movie',
  alias: 'getMovies',
  description: 'Get all movies',
  parameters: movieParams.build(),
  response: movieResponse,
}).build();

This got the type signatures to finally calm down and not keep giving me TS errors.

@Dimava

This comment was marked as outdated.

@Dimava
Copy link

Dimava commented Jul 11, 2023

declare function makeApi<Api extends ZodiosEndpointDefinitions>(
    api: Narrow<Api>
        // & NoInfer<
            & readonly ZodiosEndpointDefinition<any>[]
            & readonly {[K in keyof Api[0] as K extends X ? never: K]: never}[]
        // >
    ): Api;
type X = Extract<keyof ZodiosEndpointDefinition, string>
type NoInfer<T> = [T][T extends any ? 0 : never]
interface ZodiosEndpointDefinition<R = unknown> { /*...*/ } // to make it extandable for custom keys

is a minimum requirement to have key autocompletion and key exclusion
Problems: it's not [A, B], is't (A | B)[]

edit: const in template is not needed, forgot to remove after testing it

@ecyrbe
Copy link
Owner

ecyrbe commented Jul 11, 2023

there are some tricks out there to do some kind of strict type matching, but i failed to make them work with type narrowing + tuples.
My only hope is that typescript will add a statisfies keyword for generics like they did for as const.
So far the opened issue for this has a negative feedback from typescript team that don't want to implement it.
We need to change their mind somehow

@Dimava
Copy link

Dimava commented Jul 11, 2023

type UpTo<N extends number, A extends number[] = []> = 
| number extends N ? number
: A['length'] extends N ? A[number]
: UpTo<N, [...A, A['length']]>;

type Proper<T> =
 & {[K in keyof T | X]?: K extends X ? unknown: never}
 & T;

type Foo<Api extends any[]> = NoInfer<{ [K in UpTo<99>]?: Proper<Api[K]> }>
declare function makeApi<Api extends ZodiosEndpointDefinitions>(
    api: 
        Narrow<Api> & Foo<Api>
    ): Api;
type X = Extract<keyof ZodiosEndpointDefinition, string>
type NoInfer<T> = [T][T extends any ? 0 : never]

Somehow (Magic🌈™️ ) works with tuples

@ecyrbe
Copy link
Owner

ecyrbe commented Jul 11, 2023

Nice trick, unfortunately this will impact perf big time.
Also we have tuples in objects in tuples in the definition which make this even harder and slower if we where to use something like this :(

@Dimava
Copy link

Dimava commented Jul 11, 2023

I may try ArkType -inspired validator approach

Do you have a benchmark?


Why is makeApi api: Item[] and not api: Record<alias, Item> by the way ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
need help pinned issues that should not be closed by bot
Projects
None yet
Development

No branches or pull requests

4 participants