How to strongly type the response of SvelteKit endpoint in the component?
Asked Answered
T

2

6

So it looks like I have to separately type the response body going out of an endpoint and the props that I get from that body in the component. For example:

// index.ts (endpoint)

import type { RequestHandler } from "./__types";

export const get: RequestHandler<{
  foo: number;
  bar: string;
}> = () => {
  return {
    body: {
      foo: 42,
      bar: "hello"
    }
  };
};
// index.svelte (component)

<script lang="ts">
  export let foo;  // Not typed?
  export let bar;
</script>

I guess my question is why am I typing the response going out if I don't benefit from that typing in the component props?

Tropho answered 16/6, 2022 at 3:42 Comment(1)
There is an open issue around auto-generating endpoint types as well (currently slated for post-SvelteKit 1.0). You can follow that issue for progress: github.com/sveltejs/kit/issues/647Anent
J
3

This simply seems unsupported at the moment. The only thing that is generated are the params along the path (placeholders like [id] in file names).

Also, the source of truth would be the component, not the other way around, as all endpoints besides get can just return whatever they want.


I tried to work around this using type inference, but the generated component types do not appear to be accessible in TS files. If someone knows a workaround for that, it would be easy to type the endpoint correctly.

// src/lib/typing.ts
import type { SvelteComponentTyped } from 'svelte';

export type ComponentProps<T> = T extends SvelteComponentTyped<infer P> ? P : never;
import type { RequestHandler } from './__types';
import type Index from './index.svelte';
import type { ComponentProps } from '$lib/typing';

type Props = ComponentProps<Index>; // Unfortunately resolved to `any`

export const get: RequestHandler<Props> = async () => {
  //...
}

Inside a Svelte file:

<script lang="ts">
    import type { ComponentProps } from '$lib/typing';
    import type Index from './index.svelte';

    type Props = ComponentProps<Index>;
</script>

vs code screenshot of type resolution

Jobless answered 16/6, 2022 at 8:44 Comment(0)
C
0

As brunnerh notes, there isn't currently (as of writing this) a good way to take the strongly-typed component/endpoint, and pass its definition to the other. In the past, I've used the endpoint to store a "response" interface, which can easily be imported by co-located components.

// +server.ts
import { json } from '@sveltejs/kit';

export const GET = async () => {
    // ...
    return json({ type: "success", msg: "Hello, world!" } satisfies _res);
                                                       // ^ typed
};

export type _res = {
    type: "success" | "error";
    msg: string;
};
<!-- Component.svelte -->
<script lang="ts">
    import { onMount } from 'svelte';
    import type { _res } from './endpoint/+server';

    let text = '';
    onMount(async () => {
        const res = await fetch('/version/endpoint');
        const obj: _res = await res.json();
                 //^ typed
        text = obj.msg;
    });
</script>

{text}

Note on modern SvelteKit

I generally avoid fetching inside components, as it is usually messy.

In most cases, you can get away with data loading for GET, and form actions for POST. load() is strongly typed, thanks to codegen.

Once inside your component, you can achieve strong-typing by either:

  • creating an interface (following the example above)
  • importing the type from the page function:
<script lang="ts">
    import type { load } from './+page';
    export let data: Awaited<ReturnType<typeof load>>;
             //^ let data: { type: "success" | "error"; msg: string; }
</script>

You can return custom JSON in form actions, but it's not currently strongly typed. You have the option to combine data loading and form actions to achieve this, by migrating your API "response" logic into load():

form submitted -> server action -> invalidateAll() -> load() re-ran -> page updated

Chinaware answered 24/8 at 22:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.