typescript - Hur man använder numeriskt användar-id med next-auth
Jag hittade nyligen och började testa create-t3-app som en ny bas för NextJS-projekt eftersom det tar hand om mycket av boilerplate-uppsättningen för saker som TypeScript, trpc, prisma och next-auth, så det skulle spara mig mycket tid. Även om det är relevant tror jag inte att det är källan till mitt problem. Mitt problem är att jag använder en MySQL-databas med autoinkrementerande användar-ID:n, men det verkar som att typerna för next-auth-paketet tvingar mig att använda en sträng (DefaultUser i next-auth/core/types.d.ts och AdapterUser i next-auth/adapters.d.ts sätter båda typen av id till sträng och kommentarerna säger UUIDs). Tanken att jag eventuellt skulle kunna utöka det som finns för att stödja numeriska användar-ID:n, lade jag till detta i next-auth.d.ts:
import { DefaultSession, DefaultUser } from 'next-auth'
declare module 'next-auth' {
interface User extends DefaultUser {
id: number;
}
interface Session {
user?: {
id: number;
} & DefaultSession['user'];
}
}
Vilket verkar fungera på de flesta ställen förutom i [...nextauth].ts där det ger mig detta fel
Typen 'string | number' kan inte tilldelas typen 'number'. Typen 'string' kan inte tilldelas typen 'number'.ts(2322)
På raden session.user.id=user.id i den här delen av koden
export const authOptions: NextAuthOptions = {
// Include user.id on session
callbacks: {
session({ session, user }) {
if (session.user) {
session.user.id = user.id
}
return session
}
},
adapter: PrismaAdapter(prisma),
providers: []
}
export default NextAuth(authOptions)
TypeScript-felet försvinner om jag tar bort raden id: string; från AdapterUser i next-auth/adapters.d.ts, vilket då faller tillbaka på id: number; eftersom jag satt det på User
export interface AdapterUser extends User {
id: string; // <-- I removed this
email: string;
emailVerified: Date | null;
}
Jag tycker inte att jag borde behöva ändra bibliotekets typer för att stödja numeriska användar-ID:n, men jag har slut på idéer om hur jag ska lösa detta och har inte hittat några svar online. Eller bör jag inte använda numeriska ID:n även om min databas gör det? Om det hjälper, använder jag
next 12.3.1
next-auth 4.12.3
react 18.2.0
react-dom 18.2.0
superjson 1.9.1
zod 3.18.0
eslint 8.22.0
postcss 8.4.14
prettier 2.7.1
prisma 4.4.0
tailwindcss 3.1.6
typescrypt 4.8.0
Svar #1
Tyvärr verkar det inte vara möjligt att "laga" eller ändra typerna som biblioteket har - även att försöka ändra NextAuthOptions fungerar inte eftersom efterföljande egenskapdefinitioner måste ha samma typ. Under tiden kommer du att behöva använda en kontroll/omvandling vid körning eller göra en typomvandling:
export const authOptions: NextAuthOptions = {
// Include user.id on session
callbacks: {
session({ session, user }) {
if (session.user) {
if (typeof user.id !== "number") throw new Error("id should a number");
session.user.id = user.id // OK
// session.user.id = +user.id // more dangerous but still works
// session.user.id = user.id as number // also dangerous
}
return session
}
},
adapter: PrismaAdapter(prisma),
providers: []
}
Svar #2
Som en fortsättning på det ovan accepterade svaret använde jag runtime-typkontroller för att göra mitt liv lite enklare.
I mitt fall hade min Prisma-användare några extra attribut också:
model User {
id Int @id @default(autoincrement())
uuid String @unique @default(cuid())
name String?
email String? @unique
emailVerified DateTime?
image String?
accounts Account[]
categories Category[]
sessions Session[]
records Record[]
permissionsGranted SharePermission[] @relation("permissions_granted")
permissionsReceived SharePermission[] @relation("permissions_received")
@@unique([id, uuid])
@@index(fields: [id, uuid], name: "user_by_id_idx")
}
Så jag definierade dessa typvakter:
import { User as PrismaUser } from '@prisma/client';
import { Session, User } from 'next-auth';
export interface IdentifiedSession extends Session {
id: number;
user: PrismaUser;
}
export const isNextAuthUser = (value: unknown): value is User => {
const { id, name, email, image } = (value ?? {}) as User;
const idIsValid = typeof id === 'number';
const nameIsValid = typeof name === 'string' || !name;
const emailIsValid = typeof email === 'string' || !email;
const imageIsValid = typeof image === 'string' || !image;
return idIsValid && nameIsValid && emailIsValid && imageIsValid;
};
export const isPrismaUser = (value: unknown): value is PrismaUser => {
const { uuid, emailVerified } = (value ?? {}) as PrismaUser;
const uuidIsValid = typeof uuid === 'string';
const emailVerifiedIsEmail = emailVerified instanceof Date || !emailVerified;
return isNextAuthUser(value) && uuidIsValid && emailVerifiedIsEmail;
};
export const isSession = (value: unknown): value is Session => {
const { user, expires } = (value ?? {}) as Session;
const userIsValid = isPrismaUser(user) || isNextAuthUser(user) || !user;
const expiresIsValid = typeof expires === 'string';
return expiresIsValid && userIsValid;
};
export const isIdentifiedSession = (value: unknown): value is IdentifiedSession => {
const session = (value ?? {}) as IdentifiedSession;
const sessionIsValid = isSession(session);
const sessionUserIsValid = isPrismaUser(session.user);
return sessionIsValid && sessionUserIsValid;
};
Sedan, i sessionsåteruppringningen, använde jag en av typvaktarna på följande sätt:
session: async ({ session, user }) => {
if (!isPrismaUser(user)) {
return session;
}
const identifiedSession: IdentifiedSession = {
...session,
user,
id: user.id,
};
return identifiedSession;
}
Vilket innebär att signaturen på callbacken nu är Session | IdentifiedSession, vilket jag hanterar i nedströmskonsumenter på detta sätt
export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const session = await getSession({ req });
if (!isIdentifiedSession(session)) {
return {
props: {
records: [],
},
};
}
const records = await prisma.record.findMany({
where: {
reporter: {
id: session.id,
},
},
orderBy: {
date: 'asc',
},
include: {
category: {
select: {
title: true,
},
},
},
});
return {
props: { records },
};
};
