Skip to content

Add userSignupFields types to public signup functions #2641

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

Open
wants to merge 13 commits into
base: main
Choose a base branch
from

Conversation

FranjoMindek
Copy link
Contributor

@FranjoMindek FranjoMindek commented Apr 8, 2025

Fixes #2592

@@ -25,7 +30,7 @@ export type ProviderConfig = {
export type RequestWithWasp = Request & { wasp?: { [key: string]: any } }

// PRIVATE API
export type PossibleUserFields = Expand<Partial<UserEntityCreateInput>>
export type PossibleUserFields = Expand<Partial<Exclude<UserEntityCreateInput, 'auth'>>>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Prisma always ships this field. Do we want to keep it?

import { onBeforeSignupHook, onAfterSignupHook } from '../../hooks.js';

export function getSignupRoute({
userSignupFields,
}: {
userSignupFields?: UserSignupFields;
}) {
return handleRejection(async function signup(req, res) {
return handleRejection(async function signup(
req: Request<{ username: string; password: string } & UserSignupFieldsData>,
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We don't really use types here, but it will help once we hopefully escape this template madness.

@FranjoMindek
Copy link
Contributor Author

Type result:

image

For prisma schema:

image

@FranjoMindek FranjoMindek self-assigned this Apr 8, 2025
@FranjoMindek FranjoMindek marked this pull request as ready for review April 8, 2025 12:18
@FranjoMindek
Copy link
Contributor Author

OAuth thingies are done on server out of the reach of the user. Don't think we have anything to do there.

@infomiho infomiho self-requested a review April 8, 2025 12:37
Copy link
Contributor

@infomiho infomiho left a comment

Choose a reason for hiding this comment

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

I think we should approach this from a different angle because I think the current implementation lies with types 😄

The types lie

Let's say that my User model looks like this:

model User {
  id Int @id @default(autoincrement())

  isOnAfterSignupHookCalled Boolean @default(false)
  isOnAfterLoginHookCalled  Boolean @default(false)

  tasks   Task[]
  address String?
  votes   TaskVote[]
}

I get the following extra fields on the signup fn:

type UserSignupFieldsData = {
    isOnAfterSignupHookCalled?: boolean | undefined;
    isOnAfterLoginHookCalled?: boolean | undefined;
    address?: string | null | undefined;
    tasks?: Prisma.TaskCreateNestedManyWithoutUserInput | undefined;
    votes?: Prisma.TaskVoteCreateNestedManyWithoutUserInput | undefined;
}

But my extra user signup fields are defined like this:

export const userSignupFields = defineUserSignupFields({
  address: (data) => {
    if (typeof data.address !== 'string') {
      throw new Error('Address is required.')
    }
    if (data.address.length < 10) {
      throw new Error('Address must be at least 10 characters long.')
    }
    return data.address
  },
})

On the client, I can call the signup fn like this:

await signup({ email, password, isOnAfterLoginHookCalled: true })

without any type errors. But the runtime code won't save the isOnAfterSignupHookCalled field value.

I'd advise maybe getting the type of the userSignupFields user-defined fn to get the correct types.

address should ideally be required

One extra thing to note here, address is validated in userSignupFields but I guess we can't infer that it should be required unless we redefine how users provide us validation for the extra fields (e.g. providing us with a Zod schema).

await signup({ email, password, address: undefined })

That's out of scope for this PR, but we might want to define an issue for that if we can formulate it clearly.

// PUBLIC API
export function defineUserSignupFields(fields: UserSignupFields): UserSignupFields {
export function defineUserSignupFields<T extends UserSignupFields>(
fields: T & LimitTo<T, UserSignupFields>
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Throws error if user uses keys outside of UserSignupFields.
Required since we use generics now.

? {
[K in keyof T]: T[K] extends (data: any) => infer R ? R : never
}
: {}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

On fail its {} so it doesn't change the type of objects which extend this type.

defineUserSignupFields,
} from "wasp/server/auth";
} from 'wasp/server/auth'

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Someone didn't use prettier, should I exclude these changes from this PR?

@FranjoMindek FranjoMindek requested a review from infomiho April 14, 2025 11:15
@FranjoMindek
Copy link
Contributor Author

Type should be inferred properly now while keeping the type guard at defineUserSignupFields unchanged.

Reminder for myself not to import client and sever stuff at the same time again 😄 .
Screwed my wasp project and took me into the wrong path during debugging.

@sodic sodic requested review from sodic and removed request for sodic April 16, 2025 12:00
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.

Adding extra user signup fields is not reflected in signup() types
2 participants