Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ type PrimitiveJSONValue = string | number | boolean | undefined | null

export interface JSONArray extends Array<JSONValue> {}

type SerializableJSONValue =
| Symbol
export type SerializableJSONValue =
| symbol

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Just fixing a TS linting warning here, it's because of the same reason why we shouldn't use String instead of string

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah, seems right to me...

But I copied this over from superjson (link in the comment above). And they still have Symbol. Can you open an issue there and see whether we're missing something?

Or you'll help them find a bug - win win 😄

Oh, and what threw this error? I'm not getting anything.

@cprecioso cprecioso Apr 29, 2025

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I opened a PR, let's see if they have any complaints: ravionhq/superjson#318
The TypeScript Handbook explicitly calls this out so I'm not sure there's a reason for not merging it: https://www.typescriptlang.org/docs/handbook/declaration-files/do-s-and-don-ts.html#number-string-boolean-symbol-and-object

Oh, and what threw this error? I'm not getting anything.

When I wrote this it was because I thought I got the linting warning. Apparently the linter was my own brain 'cause I can't get it again 😂

| Set<SuperJSONValue>
| Map<SuperJSONValue, SuperJSONValue>
| undefined
Expand All @@ -30,14 +30,14 @@ type SerializableJSONValue =
| RegExp

// Here's where we excluded `ClassInstance` (which was `any`) from the union.
type SuperJSONValue =
export type SuperJSONValue =
| JSONValue
| SerializableJSONValue
| SuperJSONArray
| SuperJSONObject

interface SuperJSONArray extends Array<SuperJSONValue> {}
export interface SuperJSONArray extends Array<SuperJSONValue> {}

interface SuperJSONObject {
export interface SuperJSONObject {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I need to export these types because other types make reference to them.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hm, which ones?

I can't find any explicit references, so I'm guessing you meant that some types implicitly depend on them when creating declarations (the "type cannot be named without a reference to..." error).

So I've tried removing the exports from SuperJSONArray, SuperJSONValue, and SerializableJsonValue, rebuilding the SDK, and rebuilding the server. Everything seems to work OK.

Not that exporting these types is a problem (perhaps it's a good thing to do even if it doesn't solve anything), but I'd like to understand what's going on.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I can't find any explicit references, so I'm guessing you meant that some types implicitly depend on them when creating declarations (the "type cannot be named without a reference to..." error).

Yep, exactly.

So I've tried removing the exports from SuperJSONArray, SuperJSONValue, and SerializableJsonValue, rebuilding the SDK, and rebuilding the server. Everything seems to work OK.

Yep me too. I probably moved some stuff around and this is no longer relevant. I can undo if you want.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Oops just remembered, it is only visible in crud-testing:

🐝 --- Building SDK... ------------------------------------------------------------


[  Wasp  ] client/crud/tasks.ts(58,14): error TS4023: Exported variable 'tasks' has or is using name 'SuperJSONArray' from external module "/Users/carlos/Developer/Wasp/wasp/waspc/examples/crud-testing/.wasp/out/sdk/wasp/server/_types/serialization" but cannot be named.
[  Wasp  ] client/crud/tasks.ts(58,14): error TS4023: Exported variable 'tasks' has or is using name 'SuperJSONObject' from external module "/Users/carlos/Developer/Wasp/wasp/waspc/examples/crud-testing/.wasp/out/sdk/wasp/server/_types/serialization" but cannot be named.
[  Wasp  ] client/crud/tasks.ts(58,14): error TS7056: The inferred type of this node exceeds the maximum length the compiler will serialize. An explicit type annotation is needed.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should really remove the crud-testing app and move the code to todoApp so we don't have to test out with two different apps. We'll do it when we figure out all example apps.

[key: string]: SuperJSONValue
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { OAuth2Provider, OAuth2ProviderWithPKCE } from "arctic";

export function defineProvider<
OAuthClient extends OAuth2Provider | OAuth2ProviderWithPKCE
OAuthClient extends OAuth2Provider | OAuth2ProviderWithPKCE,
const Id extends string

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Adding the const type parameter makes it output a e.g. {id: "discord"} type instead of a {id: string} one. So then it typechecks correctly for the login provider actions.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nice improvement!

>({
id,
displayName,
oAuthClient,
}: {
id: string;
id: Id;
displayName: string;
oAuthClient: OAuthClient;
}) {
Expand Down

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The changes make sense but please test them if you haven't (the waspc/examples/todoApp doesn't have CRUD so it might have slipped through the cracks).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I did test crud-testing, do you think that covered it?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think so. crud-testing would be a pretty bad name if it didn't :)

But @infomiho can confirm

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yep, crud-testing covers the CRUD feature 👍

Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {
_{= crud.entityUpper =},
} from "../_types";
import type { Prisma } from "@prisma/client";
import type { Payload } from "../_types/serialization";
import type { Payload, SuperJSONObject } from "../_types/serialization";
import type {
{= crud.entityUpper =},
} from "wasp/entities";
Expand All @@ -37,7 +37,7 @@ type _WaspEntity = {= crud.entityUpper =}
/**
* PUBLIC API
*/
export namespace {= crud.name =} {
export declare namespace {= crud.name =} {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixing a TS lint, namespaces and modules should not be mixed in the runtime time, so we make it only a type thing.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Hm, can't get this error either. What threw it?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Nothing, just the recommendations on TypeScript's handbook. If we just do export declare there's no output, so no interplay between JS modules and namespaces.

{=# crud.operations.GetAll =}
export type GetAllQuery<Input extends Payload = never, Output extends Payload = Payload> = {= queryType =}<[_WaspEntityTagged], Input, Output>
{=/ crud.operations.GetAll =}
Expand All @@ -61,7 +61,7 @@ export namespace {= crud.name =} {

/**
* PRIVATE API
*
*
* The types with the `Resolved` suffix are the types that are used internally by the Wasp client
* to implement full-stack type safety.
*/
Expand All @@ -79,7 +79,7 @@ export type GetAllQueryResolved = typeof _waspGetAllQuery

{=# crud.operations.Get =}
{=^ overrides.Get.isDefined =}
type GetInput = Prisma.{= crud.entityUpper =}WhereUniqueInput
type GetInput = SuperJSONObject & Prisma.{= crud.entityUpper =}WhereUniqueInput

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The Prisma accepts inputs that are not serializable (e.g. functions), so this intersections makes it select only the kinds of inputs that are.
Otherwise it will fail in the following line as Wasp requires only SuperJSON-serializable inputs.

type GetOutput = _WaspEntity | null
export type GetQueryResolved = {= crud.name =}.GetQuery<GetInput, GetOutput>
{=/ overrides.Get.isDefined =}
Expand All @@ -91,7 +91,7 @@ export type GetQueryResolved = typeof _waspGetQuery

{=# crud.operations.Create =}
{=^ overrides.Create.isDefined =}
type CreateInput = Prisma.XOR<
type CreateInput = SuperJSONObject & Prisma.XOR<
Prisma.{= crud.entityUpper =}CreateInput,
Prisma.{= crud.entityUpper =}UncheckedCreateInput
>
Expand All @@ -106,7 +106,7 @@ export type CreateActionResolved = typeof _waspCreateAction

{=# crud.operations.Update =}
{=^ overrides.Update.isDefined =}
type UpdateInput = Prisma.XOR<
type UpdateInput = SuperJSONObject & Prisma.XOR<
Prisma.{= crud.entityUpper =}UpdateInput,
Prisma.{= crud.entityUpper =}UncheckedUpdateInput
>
Expand All @@ -123,7 +123,7 @@ export type UpdateActionResolved = typeof _waspUpdateAction

{=# crud.operations.Delete =}
{=^ overrides.Delete.isDefined =}
type DeleteInput = Prisma.{= crud.entityUpper =}WhereUniqueInput
type DeleteInput = SuperJSONObject & Prisma.{= crud.entityUpper =}WhereUniqueInput
type DeleteOutput = _WaspEntity
export type DeleteActionResolved = {= crud.name =}.DeleteAction<DeleteInput, DeleteOutput>
{=/ overrides.Delete.isDefined =}
Expand Down
8 changes: 4 additions & 4 deletions waspc/data/Generator/templates/sdk/wasp/server/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ type RequestWithExtraFields = Request & {
* if given middleware returns promise, reject of that promise will be correctly handled,
* meaning that error will be forwarded to next().
*/
export const handleRejection = (
export const handleRejection = <Req extends Request = RequestWithExtraFields, Res extends Response = Response>(
Comment thread
sodic marked this conversation as resolved.
Outdated
middleware: (
req: RequestWithExtraFields,
res: Response,
req: Req,
res: Res,
next: NextFunction
) => any
) =>
async (req: RequestWithExtraFields, res: Response, next: NextFunction) => {
async (req: Req, res: Res, next: NextFunction) => {
try {
await middleware(req, res, next)
Comment thread
sodic marked this conversation as resolved.
Outdated
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import {
serialize as superjsonSerialize,
} from 'superjson'
import { handleRejection } from 'wasp/server/utils'
{=# isAuthEnabled =}
import { makeAuthUserIfPossible } from 'wasp/auth/user'
{=/ isAuthEnabled =}
Comment on lines +4 to +6

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

wasp/auth/user doesn't exist if auth is not enabled


export function createOperation (handlerFn) {
return handleRejection(async (req, res) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import http from 'http'
import { Server, Socket } from 'socket.io'
import type { ServerType } from 'wasp/server/webSocket'

import { config, prisma } from 'wasp/server'

Expand All @@ -17,7 +16,7 @@ import { makeAuthUserIfPossible } from 'wasp/auth/user'
export async function init(server: http.Server): Promise<void> {
// TODO: In the future, we can consider allowing a clustering option.
// Ref: https://github.com/wasp-lang/wasp/issues/1228
const io: ServerType = new Server(server, {
const io = new Server(server, {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We we're not using ServerType anywhere (it's not returned from the function or anything), this line and the following lines were failing if it wasn't a socket.io Server, so I just removed the type annotation.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@infomiho Please comment on this one.

I'm guessing we wanted a relationship between SocketIO and what the user's function receives. But something seems to be missing. This type should have been connected to WebSocketDefinition.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The ServerType is defined based on the user defined handler:

import { webSocketFn as webSocketFn_ext } from 'wasp/src/webSocket'

// ...

export type ServerType = Parameters<WebSocketFn>[0]

// ...

type WebSocketFn = typeof webSocketFn_ext

where the wasp/src/webSocket exports:

export const webSocketFn: WebSocketDefinition<
  ClientToServerEvents,
  ServerToClientEvents,
  InterServerEvents
> = (io, context) => {
// ...
}

interface ServerToClientEvents {
  chatMessage: (msg: { id: string; username: string; text: string }) => void
}
interface ClientToServerEvents {
  chatMessage: (msg: string) => void
}
interface InterServerEvents {}

It basically takes the io param and gets its type based on the user defined events. Why this causes a type error - I'm not sure. Maybe there is something off with the way we infer the ServerType type.

It would be great if we can figure it out to have some extra type safety e.g. when users will import the io instance in their server code (currently not possible directly, see: #1289). If it's not possible / too much to do now, let's comment on the issue I linked and solve it in the future.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Found the error, engine.io version mismatch in todo-typescript between user code and .wasp/build/server, I'll see how I can fix

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Hey @infomiho let's try to sync up on this because I have some questions on how we could solve it

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

After talking with Miho, I created this issue #2726. While we fix it, we agreed that the best course of action would be to work around it by removing the type annotation.

cors: {
origin: config.frontendUrl,
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading