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

Cannot use interfaces to type endpoint outputs #1997

Closed
JeanJPNM opened this issue Jul 22, 2021 · 8 comments · Fixed by #4897
Closed

Cannot use interfaces to type endpoint outputs #1997

JeanJPNM opened this issue Jul 22, 2021 · 8 comments · Fixed by #4897
Labels
documentation Improvements or additions to documentation p2-nice-to-have SvelteKit cannot be used by a small number of people, quality of life improvements, etc. types / typescript
Milestone

Comments

@JeanJPNM
Copy link
Contributor

JeanJPNM commented Jul 22, 2021

Describe the bug

When setting up and endpoint with typescript, it is not possible to have interfaces as endpoint outputs

Reproduction

// can be any endpoint
import type { RequestHandler } from '@sveltejs/kit';
type GetOutput = {
  message: string;
};
interface PostOutput {
  message: string;
}
type PutOutput = PostOutput;

// works
export const get: RequestHandler<unknown, unknown, GetOutput> = async () => {
  return {
    status: 200,
    body: {
      message: 'hello world',
    },
  };
};

// complains
export const post: RequestHandler<unknown, unknown, PostOutput> = async () => {
  return {
    status: 200,
    body: {
      message: 'hello world',
    },
  };
};

// complains
export const put: RequestHandler<unknown, unknown, PutOutput> = async () => {
  return {
    status: 200,
    body: {
      message: 'hello world',
    },
  };
};

Logs

src/routes/demo.ts:20:53 - error TS2344: Type 'PostOutput' does not satisfy the constraint 'DefaultBody'.
  Type 'PostOutput' is not assignable to type '{ [x: string]: JSONValue; }'.
    Index signature is missing in type 'PostOutput'.

20 export const post: RequestHandler<unknown, unknown, PostOutput> = async () => {
                                                       ~~~~~~~~~~

src/routes/demo.ts:30:52 - error TS2344: Type 'PostOutput' does not satisfy the constraint 'DefaultBody'.

30 export const put: RequestHandler<unknown, unknown, PutOutput> = async () => {
                                                      ~~~~~~~~~


Found 2 errors.

System Info

System:
    OS: Windows 10 10.0.19043
    CPU: (4) x64 Intel(R) Core(TM) i5-3230M CPU @ 2.60GHz   
    Memory: 375.01 MB / 3.89 GB
  Binaries:
    Node: 14.14.0 - C:\Program Files\nodejs\node.EXE        
    npm: 6.14.8 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Chrome: 92.0.4515.107
    Edge: Spartan (44.19041.1023.0), Chromium (91.0.864.71) 
    Internet Explorer: 11.0.19041.1
  npmPackages:
    @sveltejs/adapter-node: ^1.0.0-next.35 => 1.0.0-next.35
    @sveltejs/kit: ^1.0.0-next.136 => 1.0.0-next.136
    svelte: ^3.40.1 => 3.40.1
    vite: ^2.4.3 => 2.4.3

Severity

blocking an upgrade

Additional Information

The issue is inside the JSONValue type, using it seems to require all the types assigned to it to have string indexes, can be worked around by using the unsafe any type, which defeats the whole purpose of having a typed endpoint

@JeanJPNM JeanJPNM changed the title Cannot have interfaces as endpoint outputs Cannot use interfaces to type endpoint outputs Jul 22, 2021
@benmccann
Copy link
Member

I'm not sure I'm enough of a TypeScript expert to know how to fix this, but I agree it's an issue. If you want to send a PR, I'd merge a fix for this

@benmccann benmccann added the bug Something isn't working label Jul 23, 2021
@utkarshkukreti
Copy link
Contributor

Possibly related to microsoft/TypeScript#42825?

@JeanJPNM
Copy link
Contributor Author

This issue seems to date back to 2017 microsoft/TypeScript#15300

@cristovao-trevisan
Copy link
Contributor

cristovao-trevisan commented Jul 24, 2021

This can be fixed by changing

diff --git a/node_modules/@sveltejs/kit/types/endpoint.d.ts b/node_modules/@sveltejs/kit/types/endpoint.d.ts
index 2687562..1963e8d 100644
--- a/node_modules/@sveltejs/kit/types/endpoint.d.ts
+++ b/node_modules/@sveltejs/kit/types/endpoint.d.ts
@@ -8,7 +8,7 @@ type JSONValue =
 	| null
 	| Date
 	| JSONValue[]
-	| { [key: string]: JSONValue };
+	| Record<string, JSONValue>;
 
 type DefaultBody = JSONValue | Uint8Array;
 

in endpoint..d.ts

@JeanJPNM
Copy link
Contributor Author

That is not actually a fix, typescript will give an error: error TS2456: Type alias 'JSONValue' circularly references itself.

@ignatiusmb
Copy link
Member

This is somewhat a limitation from TypeScript itself -- we want any JSON values to be valid, but can't really properly type it and the current type JSONValue is the best one we can use (microsoft/TypeScript#1897). It is still better than any type, which would not guard against the changes from #1382.

Unfortunately, the fix/workaround right now needs to happen on the user side, but it's pretty simple and won't take too long. Pass in any interface that is going to be returned in the endpoint through the interface below, and it should trick TS while still retaining any property inference

export type Typify<T> = { [K in keyof T]: Typify<T[K]> };

// for this issue
type GetOutput = Typify<{ message: string }>;
type PostOutput = Typify<{ message: string }>;
type PutOutput = PostOutput;

@benmccann benmccann added documentation Improvements or additions to documentation and removed bug Something isn't working labels Jul 27, 2021
@benmccann benmccann added the p2-nice-to-have SvelteKit cannot be used by a small number of people, quality of life improvements, etc. label Aug 4, 2021
@char-khan
Copy link

char-khan commented Feb 25, 2022

I don't know why, but @ignatiusmb 's solution doesn't work for me.
And regardless of it, I believe JSONValue shoud be any.
It is redundant to do type manipulation to make sure it is serializable, and I also think that passing value that cannot be serialized for server-side use should be allowed.

UPDATE:
sry, it works, but my thought haven't changed.

@coryvirok
Copy link
Contributor

For anyone still running into this I thought I'd share the pattern I'm using based on @ignatiusmb idea:

in client.d.ts (which is where I put all of my client (browser) types that can also be used on the server)

// From https://github.com/sveltejs/kit/issues/1997#issuecomment-887614097
type Typify<T> = { [K in keyof T]: Typify<T[K]> }

declare namespace Client {
  declare namespace Page {
    type UserPage = Typify<_Page.UserPage>
  }
}

declare namespace _Page {
  interface UserPage {
    // ...
  }
}

This works but it makes intellisense for dealing with Client.Page.UserPage sort of a nightmare because it shows the Typify type for every field in the Client.Page.UserPage type.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation p2-nice-to-have SvelteKit cannot be used by a small number of people, quality of life improvements, etc. types / typescript
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants