Skip to content

Typesafe responses (RPC Mode) #4

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

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft

Conversation

Carbonateb
Copy link

Adds a new object rpc that you use instead of the usual api, which automatically calls request.json() for you, and types it automatically.

// +server.ts
export const POST: RequestHandler = async ({ request }) => {
  const { data } = await validate(request, {
    email: z.string().email(),
    password: z.string().min(8),
  })

  // The type is automatically obtained with no extra effort
  return json({
    loginSuccessful: true,
  })
}
// (client code)
import { rpc } from "sveltekit-typesafe-api"

let result = await rpc.POST("/api", {
  body: { email: "test@example.com", password: "password123" },
})

image

How It Works & Limitations

The Vite plugin has been extended to also emit return type information to the api.d.ts file, like this:

type ProjectAPI = {
    POST: {
        "/api/login": { body: { email: string; password: string }; returns: { loginSuccessful: boolean; } };
    };
};

Obtaining the return type of the function is not trivial, as it always returns Response while we are actually interested in the data contained within. To solve this, I decided to tap into the most common pattern of making responses in SvelteKit - the built-in json function. The plugin will search for return json(...) and emit the type of whatever was passed into json, in a similar fashion to how the body type is scraped from await validate(...). If you don't use json, then the response is typed as any (I would have used unknown but obtaining it with the TypeScript Compiler API is currently quite involved)

Another limitation is only one return statement is supported. While I was able to get the types of all return json(...) instances, I was not able to create a union type from them.

Error Handling

I chose a simple and versatile behaviour: if the response code is anything other than 200, the RPC call will throw, and make the response object available.

import { rpc, RpcFetchError } from "sveltekit-typesafe-api"

try {
  let result = await rpc.POST("/api", {
    body: { email: "hello@example.com", password: "gotem33242" },
  })
} catch (error) {
  if (error instanceof RpcFetchError) {
    // The raw response, letting you do whatever you'd like
    error.response
  }
}

Other Considerations

There is potentially another way to implement this without adding a new rpc object, which would be to hijack the Response object and provide type information to response.json(). Though, doing it this way seems very hacky.


Closes #3

Copy link
Owner

@Guibi1 Guibi1 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the pr!

I have some concerns about the creation of a whole new object that essentially does the same thing. There might be a way to merge both api and rpc. Maybe an additionnal param? "json": true

@Guibi1
Copy link
Owner

Guibi1 commented Nov 18, 2024

For some reason I can't add comments so ill just dump them here:

In helpers.ts@getReturnType:

  • Is it viable to return null and use that to manually set the returnType string to "unknown" instead of relying on typeChecker.typeToString? This would fix the all return type.
  • We would need a way to make sure the json function call is the good json function, and not a user-defined one that doesn't have the same behaviour.
  • You left a Todo, is it fixed? If not, what is it exactly?

For a way to merge the api and rpc, I was thinking about returning a Promise<{ response: Response, data: ReturnType }> if the route has a defined return type and if the user enables it using the "json" key (should be on by default). The key would allow the user to disable the response.json() call if the endpoint doesn't support it, but ideally we get a TS error if we don't detect a return json and the user doesn't set the json key to false.

@Carbonateb Carbonateb marked this pull request as draft November 18, 2024 05:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Typesafe responses
2 participants