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

feat(ts): support module augmentation #1681

Merged
merged 6 commits into from
Apr 10, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 2 additions & 0 deletions types/_utils.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ export type NullableParams<T> = {

export type WithAdditionalParams<T extends Record<string, any>> = T &
Record<string, unknown>

export type Awaitable<T> = T | PromiseLike<T>
56 changes: 30 additions & 26 deletions types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@

import { ConnectionOptions } from "typeorm"
import { Adapter } from "./adapters"
import { JWTEncodeParams, JWTDecodeParams, JWTOptions, JWT } from "./jwt"
import { JWTOptions, JWT } from "./jwt"
import { AppProvider, Providers } from "./providers"
import { NextApiRequest, NextApiResponse, NextApiHandler } from "./_next"
import { NonNullParams, WithAdditionalParams } from "./_utils"
import { Awaitable, NonNullParams, WithAdditionalParams } from "./_utils"

export interface NextAuthOptions {
providers: Providers
Expand Down Expand Up @@ -62,30 +62,34 @@ export interface AppOptions
providers: AppProvider[]
}

export interface CallbacksOptions {
signIn?:
| (() => true)
| ((
user: User,
account: Record<string, unknown>,
profile: Record<string, unknown>
) => Promise<never | string | boolean>)
redirect?: (url: string, baseUrl: string) => Promise<string>
session?:
| ((session: Session) => WithAdditionalParams<Session>)
| ((
session: Session,
userOrToken: User | JWT
) => Promise<WithAdditionalParams<Session>>)
jwt?:
| ((token: JWT) => WithAdditionalParams<JWT>)
| ((
token: JWT,
user: User,
account: Record<string, unknown>,
profile: Record<string, unknown>,
isNewUser: boolean
) => Promise<WithAdditionalParams<JWT>>)
export interface Account extends Record<string, unknown> {
accessToken: string
idToken?: string
refreshToken?: string
access_token: string
expires_in?: number | null
refresh_token?: string
id_token?: string
id: string
provider: string
type: string
}
export interface Profile extends Record<string, unknown> {}

export interface CallbacksOptions<
P extends Record<string, unknown> = Profile,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Given Profile is just Record<string, unknown>, maybe we can simplify this line to 💭

export interface CallbacksOptions<
  P = Record<string, unknown>,
  A extends Record<string, unknown> = Account

Copy link
Member Author

Choose a reason for hiding this comment

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

It is because it's kind of unfinished, as we may want to make it extensible as the other interfaces. We could either try to somehow infer it from providers, or add it as a generic to NextAuth (same goes for Account, they are not necessarily used anywhere else than NextAuth, so generic could work just fine. Or we could go all in on Module Augmentation. Haven't decided yet. 🤔) In any case, I think that could be a subsequent PR

A extends Record<string, unknown> = Account
> {
signIn?(user: User, account: A, profile: P): Awaitable<string | boolean>
redirect?(url: string, baseUrl: string): Awaitable<string>
session?(session: Session, userOrToken: JWT | User): Awaitable<Session>
Copy link
Collaborator

Choose a reason for hiding this comment

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

This type will force the session callback to always take 2 parameters session, userOrToken. Are we coolio with that? I  👀  the default callback for the session is just using one, maybe we should change it to:

session?:(session: Session, userOrToken?: JWT | User): Awaitable<Session>

Copy link
Member Author

@balazsorban44 balazsorban44 Apr 10, 2021

Choose a reason for hiding this comment

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

Yes, so the defulat session callback actually is just an identity function and does nothing than return the first param. In any other case, when the user defines/overrides this, they will use the second param to extend session in some way. So I think it's good. 🙂

jwt?(
token: JWT,
user?: User,
account?: A,
profile?: P,
isNewUser?: boolean
): Awaitable<JWT>
}

export interface CookieOption {
Expand Down
8 changes: 4 additions & 4 deletions types/tests/server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,10 @@ const allConfig = {
},
async jwt(
token: JWTType.JWT,
user: NextAuthTypes.User,
account: Record<string, unknown>,
profile: Record<string, unknown>,
isNewUser: boolean
user?: NextAuthTypes.User,
account?: Record<string, unknown>,
profile?: Record<string, unknown>,
isNewUser?: boolean
) {
return token
},
Expand Down
75 changes: 65 additions & 10 deletions www/docs/getting-started/typescript.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,77 @@
---
id: typescript
title: TypeScript Support
title: TypeScript
---

Currently, NextAuth.js relies on the community to provide TypeScript types. You can download it from [DefinitelyTyped](https://www.npmjs.com/package/@types/next-auth).
NextAuth.js comes with its own types, so you can safely use it in your TypeScript projects. Even if you don't use TypeScript, IDEs like VSCode will pick this up, to provide you with a better developer experience. While you are typing, you will get suggestions of what certain objects are, and sometimes also links to documentation, and examples.

Add it to your project with:
:::note
The types at [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped) under the name of `@types/next-auth` are now deprecated, and not maintained anymore.
:::

```sh
npm i -D @types/next-auth
***
## Module Augmentaion

`next-auth` comes with certain types/interfaces, that are shared across submodules. Good examples are `Session` and `JWT`. Ideally, you should only need to create these types at a single place, and TS should pick them up in every location where they are referenced. Luckily, this is exactly what Module Agumentation can do for us. Define your shared interfaces in a single location, and get type-safety across your application, when you use `next-auth` (or one of its submodules).

1. Let's look at `Session`:

```ts title="pages/api/[...nextauth].ts"
import NextAuth from "next-auth"

export default NextAuth({
callbacks: {
session(session, token) {
return session // The type here should match the one returned in `useSession()`
}
}
})
```

or
```ts title="pages/index.ts"
import { useSession } from "next-auth/client"

```sh
yarn add -D @types/next-auth
export default function IndexPage() {
// `session` should match `callbacks.session()` in `NextAuth()`
const [session] = useSession()

return (
// Your component
)
}
```

You can find an initial Pull Request at [next-auth#516](https://github.com/nextauthjs/next-auth/pull/516) adding TypeScript. At the time of this writing, it looks like we would like to go from a complete migration to a more relaxed, incremental rewrite.
To extend/augment this type, create a `types/next-auth.d.ts` file in your project:

```ts title="types/next-auth.d.ts"
import NextAuth from "next-auth"

declare module "next-auth" {
interface Session {
user: {
/** The user's postal address. */
address: string
}
}
}
```

Make sure that the `types` folder is added to [`typeRoots`](https://www.typescriptlang.org/tsconfig/#typeRoots) in your project's `tsconfig.json` file.

2. Check out `JWT` also:

```ts title="types/next-auth.d.ts"
declare module "next-auth/jwt" {
interface JWT {
/** OpenID ID Token */
idToken?: string
}
}
```

Note that this time we declared `JWT` inside `next-auth/jwt`, as this is its default location.


## Contributing

Feel free to open a Pull Request, if you would like to contribute!
Contributions of any kind are always welcome, especially for TypeScript. Please keep in mind that we are a small team working on this project in our free time. We will try our best to give support, but if you think you have a solution for a problem, please open a PR!