mirror of
https://github.com/SrIzan10/next-auth.git
synced 2026-05-01 10:55:20 +00:00
Compare commits
25 Commits
@auth/svel
...
@auth/core
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3a85de2c5f | ||
|
|
d47b56743e | ||
|
|
363440e515 | ||
|
|
60c5037ee1 | ||
|
|
97394baed1 | ||
|
|
f94abb8f70 | ||
|
|
bbfc11e74c | ||
|
|
2a70514df1 | ||
|
|
96d666465f | ||
|
|
ecbf0be22e | ||
|
|
87ec13bd00 | ||
|
|
c0f9af4c56 | ||
|
|
c7b36f45a3 | ||
|
|
68ff69f9eb | ||
|
|
23c0a393da | ||
|
|
f130f62a91 | ||
|
|
c111b436d2 | ||
|
|
ea895b8864 | ||
|
|
cfedc3b1a3 | ||
|
|
287a5fc05a | ||
|
|
f3ad659e91 | ||
|
|
48b9a0203e | ||
|
|
39fbccb783 | ||
|
|
f207e94146 | ||
|
|
b845729cdb |
90
.github/workflows/release.yml
vendored
90
.github/workflows/release.yml
vendored
@@ -11,51 +11,53 @@ on:
|
||||
# TODO: Support latest releases
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
name:
|
||||
name:
|
||||
type: choice
|
||||
description: Package name (npm)
|
||||
options:
|
||||
- "@auth/core"
|
||||
- "@auth/nextjs"
|
||||
- "@auth/dgraph-adapter"
|
||||
- "@auth/drizzle-adapter"
|
||||
- "@auth/dynamodb-adapter"
|
||||
- "@auth/fauna-adapter"
|
||||
- "@auth/firebase-adapter"
|
||||
- "@auth/mikro-orm-adapter"
|
||||
- "@auth/mongodb-adapter"
|
||||
- "@auth/neo4j-adapter"
|
||||
- "@auth/pouchdb-adapter"
|
||||
- "@auth/prisma-adapter"
|
||||
- "@auth/sequelize-adapter"
|
||||
- "@auth/supabase-adapter"
|
||||
- "@auth/typeorm-adapter"
|
||||
- "@auth/upstash-redis-adapter"
|
||||
- "@auth/xata-adapter"
|
||||
- "next-auth"
|
||||
- "@auth/core"
|
||||
- "@auth/nextjs"
|
||||
- "@auth/dgraph-adapter"
|
||||
- "@auth/drizzle-adapter"
|
||||
- "@auth/dynamodb-adapter"
|
||||
- "@auth/fauna-adapter"
|
||||
- "@auth/firebase-adapter"
|
||||
- "@auth/mikro-orm-adapter"
|
||||
- "@auth/mongodb-adapter"
|
||||
- "@auth/neo4j-adapter"
|
||||
- "@auth/pouchdb-adapter"
|
||||
- "@auth/prisma-adapter"
|
||||
- "@auth/sequelize-adapter"
|
||||
- "@auth/supabase-adapter"
|
||||
- "@auth/typeorm-adapter"
|
||||
- "@auth/upstash-redis-adapter"
|
||||
- "@auth/xata-adapter"
|
||||
- "next-auth"
|
||||
# TODO: Infer from package name
|
||||
path:
|
||||
type: choice
|
||||
description: Directory name (packages/*)
|
||||
options:
|
||||
- "core"
|
||||
- "frameworks-nextjs"
|
||||
- "adapter-dgraph"
|
||||
- "adapter-drizzle"
|
||||
- "adapter-dynamodb"
|
||||
- "adapter-fauna"
|
||||
- "adapter-firebase"
|
||||
- "adapter-mikro-orm"
|
||||
- "adapter-mongodb"
|
||||
- "adapter-neo4j"
|
||||
- "adapter-pouchdb"
|
||||
- "adapter-prisma"
|
||||
- "adapter-sequelize"
|
||||
- "adapter-supabase"
|
||||
- "adapter-typeorm"
|
||||
- "adapter-upstash-redis"
|
||||
- "adapter-xata"
|
||||
- "next-auth"
|
||||
- "core"
|
||||
- "frameworks-nextjs"
|
||||
- "adapter-dgraph"
|
||||
- "adapter-drizzle"
|
||||
- "adapter-dynamodb"
|
||||
- "adapter-fauna"
|
||||
- "adapter-firebase"
|
||||
- "adapter-mikro-orm"
|
||||
- "adapter-mongodb"
|
||||
- "adapter-neo4j"
|
||||
- "adapter-pouchdb"
|
||||
- "adapter-prisma"
|
||||
- "adapter-sequelize"
|
||||
- "adapter-supabase"
|
||||
- "adapter-typeorm"
|
||||
- "adapter-upstash-redis"
|
||||
- "adapter-xata"
|
||||
- "next-auth"
|
||||
env:
|
||||
FORCE_COLOR: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@@ -75,6 +77,11 @@ jobs:
|
||||
cache: "pnpm"
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
- name: Build
|
||||
run: pnpm build
|
||||
env:
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
- name: Run tests
|
||||
run: pnpm test
|
||||
timeout-minutes: 15
|
||||
@@ -82,7 +89,12 @@ jobs:
|
||||
UPSTASH_REDIS_URL: ${{ secrets.UPSTASH_REDIS_URL }}
|
||||
UPSTASH_REDIS_KEY: ${{ secrets.UPSTASH_REDIS_KEY }}
|
||||
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
- name: Upload Turbo artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: turbo-report
|
||||
path: .turbo/runs/
|
||||
# - name: Run E2E tests
|
||||
# if: github.repository == 'nextauthjs/next-auth'
|
||||
# run: pnpm e2e
|
||||
@@ -91,7 +103,7 @@ jobs:
|
||||
# AUTH0_USERNAME: ${{ secrets.AUTH0_USERNAME }}
|
||||
# AUTH0_PASSWORD: ${{ secrets.AUTH0_PASSWORD }}
|
||||
# TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
|
||||
# TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
|
||||
# TURBO_TEAM: ${{ vars.TURBO_TEAM }}
|
||||
# - name: Upload E2E artifacts
|
||||
# if: github.repository == 'nextauthjs/next-auth'
|
||||
# uses: actions/upload-artifact@v3
|
||||
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -65,6 +65,7 @@ packages/adapter-prisma/prisma/dev.db
|
||||
packages/adapter-prisma/prisma/migrations
|
||||
db.sqlite
|
||||
packages/adapter-supabase/supabase/.branches
|
||||
packages/adapter-drizzle/.drizzle
|
||||
|
||||
# Tests
|
||||
coverage
|
||||
@@ -97,5 +98,7 @@ packages/frameworks-sveltekit/vite.config.js.timestamp-*
|
||||
packages/frameworks-sveltekit/vite.config.ts.timestamp-*
|
||||
|
||||
# Adapters
|
||||
docs/docs/reference/adapter
|
||||
|
||||
docs/docs/reference/adapter
|
||||
## Drizzle migration folder
|
||||
.drizzle
|
||||
@@ -3,4 +3,4 @@
|
||||
This folder contains a Next.js app using NextAuth.js for local development. See the following section on how to start:
|
||||
|
||||
[Setting up local environment
|
||||
](https://github.com/nextauthjs/next-auth/blob/main/CONTRIBUTING.md#setting-up-local-environment)
|
||||
](https://github.com/nextauthjs/.github/blob/main/CONTRIBUTING.md#setting-up-local-environment)
|
||||
@@ -22,7 +22,7 @@
|
||||
"@prisma/client": "^3",
|
||||
"@supabase/supabase-js": "^2.0.5",
|
||||
"faunadb": "^4",
|
||||
"next": "13.3.0",
|
||||
"next": "13.4.0",
|
||||
"next-auth": "workspace:*",
|
||||
"nodemailer": "^6",
|
||||
"react": "^18",
|
||||
|
||||
@@ -2,11 +2,11 @@ import { Auth, type AuthConfig } from "@auth/core"
|
||||
|
||||
// Providers
|
||||
import Apple from "@auth/core/providers/apple"
|
||||
import Asgardeo from "@auth/core/providers/asgardeo"
|
||||
// import Asgardeo from "@auth/core/providers/asgardeo"
|
||||
import Auth0 from "@auth/core/providers/auth0"
|
||||
import AzureAD from "@auth/core/providers/azure-ad"
|
||||
import AzureB2C from "@auth/core/providers/azure-ad-b2c"
|
||||
import BeyondIdentity from "@auth/core/providers/beyondidentity"
|
||||
// import BeyondIdentity from "@auth/core/providers/beyondidentity"
|
||||
import BoxyHQSAML from "@auth/core/providers/boxyhq-saml"
|
||||
// import Cognito from "@auth/core/providers/cognito"
|
||||
import Credentials from "@auth/core/providers/credentials"
|
||||
@@ -86,8 +86,8 @@ export const authConfig: AuthConfig = {
|
||||
return { name: "Fill Murray", email: "bill@fillmurray.com", image: "https://www.fillmurray.com/64/64", id: "1", foo: "" }
|
||||
},
|
||||
}),
|
||||
Apple({ clientId: process.env.APPLE_ID, clientSecret: process.env.APPLE_SECRET }),
|
||||
Asgardeo({ clientId: process.env.ASGARDEO_CLIENT_ID, clientSecret: process.env.ASGARDEO_CLIENT_SECRET, issuer: process.env.ASGARDEO_ISSUER }),
|
||||
Apple({ clientId: process.env.APPLE_ID, clientSecret: process.env.APPLE_SECRET as string }),
|
||||
// Asgardeo({ clientId: process.env.ASGARDEO_CLIENT_ID, clientSecret: process.env.ASGARDEO_CLIENT_SECRET, issuer: process.env.ASGARDEO_ISSUER }),
|
||||
Auth0({ clientId: process.env.AUTH0_ID, clientSecret: process.env.AUTH0_SECRET, issuer: process.env.AUTH0_ISSUER }),
|
||||
AzureAD({
|
||||
clientId: process.env.AZURE_AD_CLIENT_ID,
|
||||
@@ -95,11 +95,11 @@ export const authConfig: AuthConfig = {
|
||||
tenantId: process.env.AZURE_AD_TENANT_ID,
|
||||
}),
|
||||
AzureB2C({ clientId: process.env.AZURE_B2C_ID, clientSecret: process.env.AZURE_B2C_SECRET, issuer: process.env.AZURE_B2C_ISSUER }),
|
||||
BeyondIdentity({
|
||||
clientId: process.env.BEYOND_IDENTITY_CLIENT_ID,
|
||||
clientSecret: process.env.BEYOND_IDENTITY_CLIENT_SECRET,
|
||||
issuer: process.env.BEYOND_IDENTITY_ISSUER,
|
||||
}),
|
||||
// BeyondIdentity({
|
||||
// clientId: process.env.BEYOND_IDENTITY_CLIENT_ID,
|
||||
// clientSecret: process.env.BEYOND_IDENTITY_CLIENT_SECRET,
|
||||
// issuer: process.env.BEYOND_IDENTITY_ISSUER,
|
||||
// }),
|
||||
BoxyHQSAML({ issuer: "https://jackson-demo.boxyhq.com", clientId: "tenant=boxyhq.com&product=saml-demo.boxyhq.com", clientSecret: "dummy" }),
|
||||
// Cognito({ clientId: process.env.COGNITO_ID, clientSecret: process.env.COGNITO_SECRET, issuer: process.env.COGNITO_ISSUER }),
|
||||
Descope({ clientId: process.env.DESCOPE_ID, clientSecret: process.env.DESCOPE_SECRET }),
|
||||
@@ -108,7 +108,7 @@ export const authConfig: AuthConfig = {
|
||||
Facebook({ clientId: process.env.FACEBOOK_ID, clientSecret: process.env.FACEBOOK_SECRET }),
|
||||
Foursquare({ clientId: process.env.FOURSQUARE_ID, clientSecret: process.env.FOURSQUARE_SECRET }),
|
||||
Freshbooks({ clientId: process.env.FRESHBOOKS_ID, clientSecret: process.env.FRESHBOOKS_SECRET }),
|
||||
GitHub({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET, redirectProxy: process.env.AUTH_REDIRECT_PROXY_URL }),
|
||||
GitHub({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET, redirectProxyUrl: process.env.AUTH_REDIRECT_PROXY_URL }),
|
||||
Gitlab({ clientId: process.env.GITLAB_ID, clientSecret: process.env.GITLAB_SECRET }),
|
||||
Google({ clientId: process.env.GOOGLE_ID, clientSecret: process.env.GOOGLE_SECRET }),
|
||||
// IDS4({ clientId: process.env.IDS4_ID, clientSecret: process.env.IDS4_SECRET, issuer: process.env.IDS4_ISSUER }),
|
||||
@@ -117,7 +117,7 @@ export const authConfig: AuthConfig = {
|
||||
Line({ clientId: process.env.LINE_ID, clientSecret: process.env.LINE_SECRET }),
|
||||
LinkedIn({ clientId: process.env.LINKEDIN_ID, clientSecret: process.env.LINKEDIN_SECRET }),
|
||||
Mailchimp({ clientId: process.env.MAILCHIMP_ID, clientSecret: process.env.MAILCHIMP_SECRET }),
|
||||
Notion({ clientId: process.env.NOTION_ID, clientSecret: process.env.NOTION_SECRET, redirectUri: process.env.NOTION_REDIRECT_URI }),
|
||||
Notion({ clientId: process.env.NOTION_ID, clientSecret: process.env.NOTION_SECRET, redirectUri: process.env.NOTION_REDIRECT_URI as string }),
|
||||
// Okta({ clientId: process.env.OKTA_ID, clientSecret: process.env.OKTA_SECRET, issuer: process.env.OKTA_ISSUER }),
|
||||
Osu({ clientId: process.env.OSU_CLIENT_ID, clientSecret: process.env.OSU_CLIENT_SECRET }),
|
||||
Patreon({ clientId: process.env.PATREON_ID, clientSecret: process.env.PATREON_SECRET }),
|
||||
@@ -162,4 +162,4 @@ function AuthHandler(...args: any[]) {
|
||||
|
||||
export default AuthHandler(authConfig)
|
||||
|
||||
export const config = { runtime: "experimental-edge" }
|
||||
export const config = { runtime: "edge" }
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"svelte": "3.55.0",
|
||||
"svelte-check": "2.10.2",
|
||||
"typescript": "4.9.4",
|
||||
"vite": "4.0.1"
|
||||
"vite": "4.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/core": "workspace:*",
|
||||
|
||||
@@ -37,22 +37,31 @@ This documentation site is based on the [Docusaurus](https://docusaurus.io) fram
|
||||
|
||||
To start a local environment of this project, please do the following.
|
||||
|
||||
1. Clone the repository.
|
||||
1. Clone the repo:
|
||||
|
||||
```bash
|
||||
$ git clone https://github.com/nextauthjs/docs.git
|
||||
```sh
|
||||
git clone git@github.com:nextauthjs/next-auth.git
|
||||
cd next-auth
|
||||
```
|
||||
|
||||
2. Install dependencies
|
||||
2. Set up the correct pnpm version, using [Corepack](https://nodejs.org/api/corepack.html). Run the following in the project'a root:
|
||||
|
||||
```bash
|
||||
$ npm install
|
||||
```sh
|
||||
corepack enable pnpm
|
||||
```
|
||||
|
||||
3. Start the development server
|
||||
(Now, if you run `pnpm --version`, it should print the same verion as the `packageManager` property in the [`package.json` file](https://github.com/nextauthjs/next-auth/blob/main/package.json))
|
||||
|
||||
3. Install packages. Developing requires Node.js v18:
|
||||
|
||||
```sh
|
||||
pnpm install
|
||||
```
|
||||
|
||||
4. Start the development server
|
||||
|
||||
```bash
|
||||
$ npm start
|
||||
$ pnpm dev:docs
|
||||
```
|
||||
|
||||
And thats all! Now you should have a local copy of this docs site running at [localhost:3000](http://localhost:3000)!
|
||||
|
||||
@@ -34,7 +34,7 @@ npm install -D nodemailer
|
||||
|
||||
## 2. Setting up a SMTP service
|
||||
|
||||
Next we need a [SMTP service](https://sendgrid.com/blog/what-is-an-smtp-server/) which will be in charge of sending emails from our application. There's a number of services available for this, however [here are the ones](http://nodemailer.com/smtp/well-known/) known to work with `nodemailer`.
|
||||
Next we need a [SMTP service](https://sendgrid.com/blog/what-is-an-smtp-server/) which will be in charge of sending emails from our application. There's a number of services available for this, however [here are the ones](https://community.nodemailer.com/2-0-0-beta/setup-smtp/well-known-services) known to work with `nodemailer`.
|
||||
|
||||
:::info
|
||||
For this tutorial, we're going to be using [Sendgrid](https://sendgrid.com/), but any of the services linked above should work the same
|
||||
|
||||
@@ -100,11 +100,12 @@ NextAuth.js provides [`useSession()`](/reference/react/#usesession) - a [React H
|
||||
|
||||
```ts title="pages/_app.tsx"
|
||||
import { SessionProvider } from "next-auth/react"
|
||||
import type { AppProps } from 'next/app'
|
||||
|
||||
export default function App({
|
||||
Component,
|
||||
pageProps: { session, ...pageProps },
|
||||
}) {
|
||||
}: AppProps) {
|
||||
return (
|
||||
<SessionProvider session={session}>
|
||||
<Component {...pageProps} />
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
title: Using a database adapter
|
||||
---
|
||||
|
||||
An **Adapter** in Auth.js connects your application to whatever database or backend system you want to use to store data for users, their accounts, sessions, etc. Adapters are optional, unless you need to persist user information in your own database, or you want to implement certain flows. The [Email Provider](/getting-started/email-tutorial) requires an adapter to be able to save [Verification Tokens](/reference/adapters/models#verification-token).
|
||||
An **Adapter** in Auth.js connects your application to whatever database or backend system you want to use to store data for users, their accounts, sessions, etc. Adapters are optional, unless you need to persist user information in your own database, or you want to implement certain flows. The [Email Provider](/getting-started/email-tutorial) requires an adapter to be able to save [Verification Tokens](/reference/adapters#verification-token).
|
||||
|
||||
:::tip
|
||||
When using a database, you can still use JWT for session handling for fast access. See the [`session.strategy`](/reference/configuration/auth-config#session) option. Read about the trade-offs of JWT in the [FAQ](/concepts/faq#json-web-tokens).
|
||||
|
||||
@@ -30,7 +30,7 @@ You can override any of the options to suit your own use case.
|
||||
## Configuration
|
||||
|
||||
1. Auth.js does not include `nodemailer` as a dependency, so you'll need to install it yourself if you want to use the Email Provider. Run `npm install nodemailer` or `yarn add nodemailer`.
|
||||
2. You will need an SMTP account; ideally for one of the [services known to work with `nodemailer`](https://community.nodemailer.com/2-0-0-beta/setup-smtp/well-known-services/).
|
||||
2. You will need an SMTP account; such as [the official Nodemailer recommended service](https://nodemailer.com/about/#example) of [Forward Email](https://forwardemail.net).
|
||||
3. There are two ways to configure the SMTP server connection.
|
||||
|
||||
You can either use a connection string or a `nodemailer` configuration object.
|
||||
@@ -40,8 +40,8 @@ You can either use a connection string or a `nodemailer` configuration object.
|
||||
Create an `.env` file to the root of your project and add the connection string and email address.
|
||||
|
||||
```js title=".env" {1}
|
||||
EMAIL_SERVER=smtp://username:password@smtp.example.com:587
|
||||
EMAIL_FROM=noreply@example.com
|
||||
EMAIL_SERVER=smtp://username:password@smtp.forwardemail.net:587
|
||||
EMAIL_FROM=support@example.com
|
||||
```
|
||||
|
||||
Now you can add the email provider like this:
|
||||
@@ -64,7 +64,7 @@ In your `.env` file in the root of your project simply add the configuration obj
|
||||
```js title=".env"
|
||||
EMAIL_SERVER_USER=username
|
||||
EMAIL_SERVER_PASSWORD=password
|
||||
EMAIL_SERVER_HOST=smtp.example.com
|
||||
EMAIL_SERVER_HOST=smtp.forwardemail.net
|
||||
EMAIL_SERVER_PORT=587
|
||||
EMAIL_FROM=noreply@example.com
|
||||
```
|
||||
|
||||
@@ -8,6 +8,10 @@ Using an Auth.js / NextAuth.js adapter you can connect to any database service o
|
||||
<a href="/reference/adapter/dgraph" class="adapter-card">
|
||||
<img src="/img/adapters/dgraph.png" width="30" />
|
||||
<h4 class="adapter-card__title">Dgraph Adapter</h4>
|
||||
</a>
|
||||
<a href="/reference/adapter/drizzle" class="adapter-card">
|
||||
<img src="/img/adapters/drizzle-orm.png" width="30" />
|
||||
<h4 class="adapter-card__title">Drizzle Adapter</h4>
|
||||
</a>
|
||||
<a href="/reference/adapter/dynamodb" class="adapter-card">
|
||||
<img src="/img/adapters/dynamodb.png" width="30" />
|
||||
@@ -67,10 +71,8 @@ Using an Auth.js / NextAuth.js adapter you can connect to any database service o
|
||||
If you don't find an adapter for the database or service you use, you can always create one yourself. Have a look at our guide on [how to create a database adapter](/guides/adapters/creating-a-database-adapter).
|
||||
:::
|
||||
|
||||
|
||||
## Models
|
||||
|
||||
|
||||
Auth.js can be used with any database. Models tell you what structures Auth.js expects from your database. Models will vary slightly depending on which adapter you use, but in general, will look something like this:
|
||||
|
||||
```mermaid
|
||||
@@ -131,7 +133,7 @@ If a user first signs in with an OAuth provider, then their email address is aut
|
||||
This provides a way to contact users and for users to maintain access to their account and sign in using email in the event they are unable to sign in with the OAuth provider in the future (if the [Email Provider](/reference/core/providers_email) is configured).
|
||||
:::
|
||||
|
||||
User creation in the database is automatic and happens when the user is logging in for the first time with a provider.
|
||||
User creation in the database is automatic and happens when the user is logging in for the first time with a provider.
|
||||
If the first sign-in is via the [OAuth Provider](/reference/core/providers_oauth), the default data saved is `id`, `name`, `email` and `image`. You can add more profile data by returning extra fields in your [OAuth provider](/guides/providers/custom-provider)'s [`profile()`](/reference/core/providers#profile) callback.
|
||||
|
||||
If the first sign-in is via the [Email Provider](/reference/core/providers_email), then the saved user will have `id`, `email`, `emailVerified`, where `emailVerified` is the timestamp of when the user was created.
|
||||
|
||||
@@ -265,6 +265,7 @@ const docusaurusConfig = {
|
||||
? []
|
||||
: [
|
||||
typedocAdapter("Dgraph"),
|
||||
typedocAdapter("Drizzle"),
|
||||
typedocAdapter("DynamoDB"),
|
||||
typedocAdapter("Fauna"),
|
||||
typedocAdapter("Firebase"),
|
||||
|
||||
@@ -55,6 +55,7 @@ module.exports = {
|
||||
link: { type: "doc", id: "reference/adapters/index" },
|
||||
items: [
|
||||
{ type: "doc", id: "reference/adapter/dgraph/index" },
|
||||
{ type: "doc", id: "reference/adapter/drizzle/index" },
|
||||
{ type: "doc", id: "reference/adapter/dynamodb/index" },
|
||||
{ type: "doc", id: "reference/adapter/fauna/index" },
|
||||
{ type: "doc", id: "reference/adapter/firebase/index" },
|
||||
|
||||
@@ -91,7 +91,7 @@ html[data-theme="dark"] .navbar__item.navbar__link[href*="npm"]:before {
|
||||
position: absolute;
|
||||
color: #000;
|
||||
top: -10px;
|
||||
right: -45px;
|
||||
right: 4px;
|
||||
font-size: 9px;
|
||||
background-color: #ccc;
|
||||
padding: 2px 5px;
|
||||
|
||||
@@ -101,13 +101,13 @@ export default function Home() {
|
||||
.fetch("https://api.github.com/repos/nextauthjs/next-auth")
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
const navLinks = document.getElementsByClassName(
|
||||
"navbar__item navbar__link"
|
||||
const githubLink = document.querySelector(
|
||||
".navbar__item.navbar__link[href*='github']"
|
||||
)
|
||||
const githubStat = document.createElement("span")
|
||||
githubStat.innerHTML = kFormatter(data.stargazers_count)
|
||||
githubStat.className = "github-counter"
|
||||
navLinks[4].appendChild(githubStat)
|
||||
githubLink.appendChild(githubStat)
|
||||
})
|
||||
}, [])
|
||||
return (
|
||||
|
||||
BIN
docs/static/img/adapters/drizzle-orm.png
vendored
Normal file
BIN
docs/static/img/adapters/drizzle-orm.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 94 KiB |
@@ -7,7 +7,7 @@
|
||||
"build:app": "turbo run build --filter=next-auth-app",
|
||||
"build:docs": "turbo run build --filter=docs",
|
||||
"build": "turbo run build --filter=next-auth --filter=@next-auth/* --filter=@auth/* --no-deps",
|
||||
"test": "turbo run test --concurrency=1 --filter=[HEAD^1] --filter=./packages/* --filter=!@*upstash* --filter=!*dynamodb-*",
|
||||
"test": "turbo run test --concurrency=1 --filter=[HEAD^1] --filter=./packages/* --filter=!@*upstash* --filter=!*dynamodb-* --filter=!*app*",
|
||||
"clean": "turbo run clean --no-cache",
|
||||
"dev:db": "turbo run dev --parallel --continue --filter=next-auth-app...",
|
||||
"dev": "turbo run dev --parallel --continue --filter=next-auth-app... --filter=!./packages/adapter-*",
|
||||
@@ -43,7 +43,7 @@
|
||||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"prettier": "2.8.1",
|
||||
"prettier-plugin-svelte": "^2.8.1",
|
||||
"turbo": "1.10.1",
|
||||
"turbo": "^1.10.3",
|
||||
"typescript": "4.9.4"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
1
packages/adapter-drizzle/.npmrc
Normal file
1
packages/adapter-drizzle/.npmrc
Normal file
@@ -0,0 +1 @@
|
||||
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
|
||||
28
packages/adapter-drizzle/README.md
Normal file
28
packages/adapter-drizzle/README.md
Normal file
@@ -0,0 +1,28 @@
|
||||
<p align="center">
|
||||
<br/>
|
||||
<a href="https://authjs.dev" target="_blank">
|
||||
<img height="64px" src="https://authjs.dev/img/logo/logo-sm.png" />
|
||||
</a>
|
||||
<a href="https://github.com/drizzle-team/drizzle-orm" target="_blank">
|
||||
<img height="64px" src="https://pbs.twimg.com/profile_images/1598308842391179266/CtXrfLnk_400x400.jpg"/>
|
||||
</a>
|
||||
<h3 align="center"><b>Drizzle ORM Adapter</b> - NextAuth.js / Auth.js</a></h3>
|
||||
<p align="center" style="align: center;">
|
||||
<a href="https://npm.im/@auth/drizzle-adapter">
|
||||
<img src="https://img.shields.io/badge/TypeScript-blue?style=flat-square" alt="TypeScript" />
|
||||
</a>
|
||||
<a href="https://npm.im/@auth/drizzle-adapter">
|
||||
<img alt="npm" src="https://img.shields.io/npm/v/@auth/drizzle-adapter?color=green&label=@auth/drizzle-adapter&style=flat-square">
|
||||
</a>
|
||||
<a href="https://www.npmtrends.com/@auth/drizzle-adapter">
|
||||
<img src="https://img.shields.io/npm/dm/@auth/drizzle-adapter?label=%20downloads&style=flat-square" alt="Downloads" />
|
||||
</a>
|
||||
<a href="https://github.com/nextauthjs/next-auth/stargazers">
|
||||
<img src="https://img.shields.io/github/stars/nextauthjs/next-auth?style=flat-square" alt="Github Stars" />
|
||||
</a>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
Check out the documentation at [authjs.dev](https://authjs.dev/reference/adapter/drizzle).
|
||||
65
packages/adapter-drizzle/package.json
Normal file
65
packages/adapter-drizzle/package.json
Normal file
@@ -0,0 +1,65 @@
|
||||
{
|
||||
"name": "@auth/drizzle-adapter",
|
||||
"version": "0.1.0",
|
||||
"description": "Drizzle adapter for Auth.js.",
|
||||
"homepage": "https://authjs.dev",
|
||||
"repository": "https://github.com/nextauthjs/next-auth",
|
||||
"bugs": {
|
||||
"url": "https://github.com/nextauthjs/next-auth/issues"
|
||||
},
|
||||
"author": "Anthony Shew",
|
||||
"type": "module",
|
||||
"types": "./index.d.ts",
|
||||
"files": [
|
||||
"*.js",
|
||||
"*.d.ts*",
|
||||
"lib",
|
||||
"src"
|
||||
],
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./index.d.ts",
|
||||
"import": "./index.js"
|
||||
}
|
||||
},
|
||||
"license": "ISC",
|
||||
"keywords": [
|
||||
"next-auth",
|
||||
"@auth",
|
||||
"Auth.js",
|
||||
"next.js",
|
||||
"oauth",
|
||||
"drizzle"
|
||||
],
|
||||
"private": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "find . -type d -name \".drizzle\" | xargs rm -rf",
|
||||
"test": "pnpm test:mysql && pnpm test:sqlite && pnpm test:pg",
|
||||
"test:mysql": "pnpm clean && ./tests/mysql/test.sh",
|
||||
"test:sqlite": "pnpm clean && ./tests/sqlite/test.sh",
|
||||
"test:pg": "pnpm clean && ./tests/pg/test.sh",
|
||||
"build": "tsc",
|
||||
"dev": "drizzle-kit generate:mysql --schema=src/schema.ts --out=.drizzle && tsc -w"
|
||||
},
|
||||
"dependencies": {
|
||||
"@auth/core": "workspace:*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next-auth/adapter-test": "workspace:*",
|
||||
"@next-auth/tsconfig": "workspace:*",
|
||||
"@types/better-sqlite3": "^7.6.4",
|
||||
"@types/uuid": "^8.3.3",
|
||||
"better-sqlite3": "^8.4.0",
|
||||
"drizzle-kit": "^0.19.5",
|
||||
"drizzle-orm": "^0.27.0",
|
||||
"jest": "^27.4.3",
|
||||
"mysql2": "^3.2.0",
|
||||
"postgres": "^3.3.4"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "@next-auth/adapter-test/jest"
|
||||
}
|
||||
}
|
||||
268
packages/adapter-drizzle/src/index.ts
Normal file
268
packages/adapter-drizzle/src/index.ts
Normal file
@@ -0,0 +1,268 @@
|
||||
/**
|
||||
* <div style={{display: "flex", justifyContent: "space-between", alignItems: "center", padding: 16}}>
|
||||
* <p style={{fontWeight: "normal"}}>Official <a href="https://orm.drizzle.team">Drizzle ORM</a> adapter for Auth.js / NextAuth.js.</p>
|
||||
* <a href="https://orm.drizzle.team">
|
||||
* <img style={{display: "block"}} src="/img/adapters/drizzle-orm.png" width="38" />
|
||||
* </a>
|
||||
* </div>
|
||||
*
|
||||
* ## Installation
|
||||
*
|
||||
* ```bash npm2yarn2pnpm
|
||||
* npm install drizzle-orm @auth/drizzle-adapter
|
||||
* npm install drizzle-kit --save-dev
|
||||
* ```
|
||||
*
|
||||
* @module @auth/drizzle-adapter
|
||||
*/
|
||||
|
||||
import { mySqlDrizzleAdapter } from "./lib/mysql.js"
|
||||
import { pgDrizzleAdapter } from "./lib/pg.js"
|
||||
import { SQLiteDrizzleAdapter } from "./lib/sqlite.js"
|
||||
import {
|
||||
isMySqlDatabase,
|
||||
isPgDatabase,
|
||||
isSQLiteDatabase,
|
||||
SqlFlavorOptions,
|
||||
} from "./lib/utils.js"
|
||||
|
||||
import type { Adapter } from "@auth/core/adapters"
|
||||
|
||||
/**
|
||||
* Add the adapter to your `app/api/[...nextauth]/route.js` next-auth configuration object.
|
||||
*
|
||||
* ```ts title="pages/api/auth/[...nextauth].ts"
|
||||
* import NextAuth from "next-auth"
|
||||
* import GoogleProvider from "next-auth/providers/google"
|
||||
* import { DrizzleAdapter } from "@auth/drizzle-adapter"
|
||||
* import { db } from "./schema"
|
||||
*
|
||||
* export default NextAuth({
|
||||
* adapter: DrizzleAdapter(db),
|
||||
* providers: [
|
||||
* GoogleProvider({
|
||||
* clientId: process.env.GOOGLE_CLIENT_ID,
|
||||
* clientSecret: process.env.GOOGLE_CLIENT_SECRET,
|
||||
* }),
|
||||
* ],
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* ## Setup
|
||||
*
|
||||
* First, create a schema that includes [the minimum requirements for a `next-auth` adapter](/reference/adapters#models). You can select your favorite SQL flavor below and copy it.
|
||||
* Additionally, you may extend the schema from the minimum requirements to suit your needs.
|
||||
*
|
||||
* - [Postgres](#postgres)
|
||||
* - [MySQL](#mysql)
|
||||
* - [SQLite](#sqlite)
|
||||
*
|
||||
* ### Postgres
|
||||
|
||||
* ```ts title="schema.ts"
|
||||
* import {
|
||||
* timestamp,
|
||||
* pgTable,
|
||||
* text,
|
||||
* primaryKey,
|
||||
* integer
|
||||
* } from "drizzle-orm/pg-core"
|
||||
* import type { AdapterAccount } from '@auth/core/adapters'
|
||||
*
|
||||
* export const users = pgTable("users", {
|
||||
* id: text("id").notNull().primaryKey(),
|
||||
* name: text("name"),
|
||||
* email: text("email").notNull(),
|
||||
* emailVerified: timestamp("emailVerified", { mode: "date" }),
|
||||
* image: text("image"),
|
||||
* })
|
||||
*
|
||||
* export const accounts = pgTable(
|
||||
* "accounts",
|
||||
* {
|
||||
* userId: text("userId")
|
||||
* .notNull()
|
||||
* .references(() => users.id, { onDelete: "cascade" }),
|
||||
* type: text("type").$type<AdapterAccount["type"]>().notNull(),
|
||||
* provider: text("provider").notNull(),
|
||||
* providerAccountId: text("providerAccountId").notNull(),
|
||||
* refresh_token: text("refresh_token"),
|
||||
* access_token: text("access_token"),
|
||||
* expires_at: integer("expires_at"),
|
||||
* token_type: text("token_type"),
|
||||
* scope: text("scope"),
|
||||
* id_token: text("id_token"),
|
||||
* session_state: text("session_state"),
|
||||
* },
|
||||
* (account) => ({
|
||||
* compoundKey: primaryKey(account.provider, account.providerAccountId),
|
||||
* })
|
||||
* )
|
||||
*
|
||||
* export const sessions = pgTable("sessions", {
|
||||
* sessionToken: text("sessionToken").notNull().primaryKey(),
|
||||
* userId: text("userId")
|
||||
* .notNull()
|
||||
* .references(() => users.id, { onDelete: "cascade" }),
|
||||
* expires: timestamp("expires", { mode: "date" }).notNull(),
|
||||
* })
|
||||
*
|
||||
* export const verificationTokens = pgTable(
|
||||
* "verificationToken",
|
||||
* {
|
||||
* identifier: text("identifier").notNull(),
|
||||
* token: text("token").notNull(),
|
||||
* expires: timestamp("expires", { mode: "date" }).notNull(),
|
||||
* },
|
||||
* (vt) => ({
|
||||
* compoundKey: primaryKey(vt.identifier, vt.token),
|
||||
* })
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* ### MySQL
|
||||
*
|
||||
* ```ts title="schema.ts"
|
||||
* import {
|
||||
* int,
|
||||
* timestamp,
|
||||
* mysqlTable,
|
||||
* primaryKey,
|
||||
* varchar,
|
||||
* } from "drizzle-orm/mysql-core"
|
||||
* import type { AdapterAccount } from "@auth/core/adapters"
|
||||
*
|
||||
* export const users = mysqlTable("users", {
|
||||
* id: varchar("id", { length: 255 }).notNull().primaryKey(),
|
||||
* name: varchar("name", { length: 255 }),
|
||||
* email: varchar("email", { length: 255 }).notNull(),
|
||||
* emailVerified: timestamp("emailVerified", { mode: "date", fsp: 3 }).defaultNow(),
|
||||
* image: varchar("image", { length: 255 }),
|
||||
* })
|
||||
*
|
||||
* export const accounts = mysqlTable(
|
||||
* "accounts",
|
||||
* {
|
||||
* userId: varchar("userId", { length: 255 })
|
||||
* .notNull()
|
||||
* .references(() => users.id, { onDelete: "cascade" }),
|
||||
* type: varchar("type", { length: 255 }).$type<AdapterAccount["type"]>().notNull(),
|
||||
* provider: varchar("provider", { length: 255 }).notNull(),
|
||||
* providerAccountId: varchar("providerAccountId", { length: 255 }).notNull(),
|
||||
* refresh_token: varchar("refresh_token", { length: 255 }),
|
||||
* access_token: varchar("access_token", { length: 255 }),
|
||||
* expires_at: int("expires_at"),
|
||||
* token_type: varchar("token_type", { length: 255 }),
|
||||
* scope: varchar("scope", { length: 255 }),
|
||||
* id_token: varchar("id_token", { length: 255 }),
|
||||
* session_state: varchar("session_state", { length: 255 }),
|
||||
* },
|
||||
* (account) => ({
|
||||
* compoundKey: primaryKey(account.provider, account.providerAccountId),
|
||||
* })
|
||||
* )
|
||||
*
|
||||
* export const sessions = mysqlTable("sessions", {
|
||||
* sessionToken: varchar("sessionToken", { length: 255 }).notNull().primaryKey(),
|
||||
* userId: varchar("userId", { length: 255 })
|
||||
* .notNull()
|
||||
* .references(() => users.id, { onDelete: "cascade" }),
|
||||
* expires: timestamp("expires", { mode: "date" }).notNull(),
|
||||
* })
|
||||
*
|
||||
* export const verificationTokens = mysqlTable(
|
||||
* "verificationToken",
|
||||
* {
|
||||
* identifier: varchar("identifier", { length: 255 }).notNull(),
|
||||
* token: varchar("token", { length: 255 }).notNull(),
|
||||
* expires: timestamp("expires", { mode: "date" }).notNull(),
|
||||
* },
|
||||
* (vt) => ({
|
||||
* compoundKey: primaryKey(vt.identifier, vt.token),
|
||||
* })
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* ### SQLite
|
||||
*
|
||||
* ```ts title="schema.ts"
|
||||
* import { integer, sqliteTable, text, primaryKey } from "drizzle-orm/sqlite-core"
|
||||
* import type { AdapterAccount } from "@auth/core/adapters"
|
||||
*
|
||||
* export const users = sqliteTable("users", {
|
||||
* id: text("id").notNull().primaryKey(),
|
||||
* name: text("name"),
|
||||
* email: text("email").notNull(),
|
||||
* emailVerified: integer("emailVerified", { mode: "timestamp_ms" }),
|
||||
* image: text("image"),
|
||||
* })
|
||||
*
|
||||
* export const accounts = sqliteTable(
|
||||
* "accounts",
|
||||
* {
|
||||
* userId: text("userId")
|
||||
* .notNull()
|
||||
* .references(() => users.id, { onDelete: "cascade" }),
|
||||
* type: text("type").$type<AdapterAccount["type"]>().notNull(),
|
||||
* provider: text("provider").notNull(),
|
||||
* providerAccountId: text("providerAccountId").notNull(),
|
||||
* refresh_token: text("refresh_token"),
|
||||
* access_token: text("access_token"),
|
||||
* expires_at: integer("expires_at"),
|
||||
* token_type: text("token_type"),
|
||||
* scope: text("scope"),
|
||||
* id_token: text("id_token"),
|
||||
* session_state: text("session_state"),
|
||||
* },
|
||||
* (account) => ({
|
||||
* compoundKey: primaryKey(account.provider, account.providerAccountId),
|
||||
* })
|
||||
* )
|
||||
*
|
||||
* export const sessions = sqliteTable("sessions", {
|
||||
* sessionToken: text("sessionToken").notNull().primaryKey(),
|
||||
* userId: text("userId")
|
||||
* .notNull()
|
||||
* .references(() => users.id, { onDelete: "cascade" }),
|
||||
* expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
|
||||
* })
|
||||
*
|
||||
* export const verificationTokens = sqliteTable(
|
||||
* "verificationToken",
|
||||
* {
|
||||
* identifier: text("identifier").notNull(),
|
||||
* token: text("token").notNull(),
|
||||
* expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
|
||||
* },
|
||||
* (vt) => ({
|
||||
* compoundKey: primaryKey(vt.identifier, vt.token),
|
||||
* })
|
||||
* )
|
||||
* ```
|
||||
*
|
||||
* ## Migrating your database
|
||||
* With your schema now described in your code, you'll need to migrate your database to your schema.
|
||||
*
|
||||
* For full documentation on how to run migrations with Drizzle, [visit the Drizzle documentation](https://orm.drizzle.team/kit-docs/overview#running-migrations).
|
||||
*
|
||||
* ---
|
||||
*
|
||||
**/
|
||||
export function DrizzleAdapter<SqlFlavor extends SqlFlavorOptions>(
|
||||
db: SqlFlavor
|
||||
): Adapter {
|
||||
if (isMySqlDatabase(db)) {
|
||||
// We need to cast to unknown since the type overlaps (PScale is MySQL based)
|
||||
return mySqlDrizzleAdapter(db)
|
||||
}
|
||||
|
||||
if (isPgDatabase(db)) {
|
||||
return pgDrizzleAdapter(db)
|
||||
}
|
||||
|
||||
if (isSQLiteDatabase(db)) {
|
||||
return SQLiteDrizzleAdapter(db)
|
||||
}
|
||||
|
||||
throw new Error("Unsupported database type in Auth.js Drizzle adapter.")
|
||||
}
|
||||
255
packages/adapter-drizzle/src/lib/mysql.ts
Normal file
255
packages/adapter-drizzle/src/lib/mysql.ts
Normal file
@@ -0,0 +1,255 @@
|
||||
import { and, eq } from "drizzle-orm"
|
||||
import {
|
||||
int,
|
||||
timestamp,
|
||||
mysqlTable,
|
||||
primaryKey,
|
||||
varchar,
|
||||
} from "drizzle-orm/mysql-core"
|
||||
|
||||
import type { Adapter, AdapterAccount } from "@auth/core/adapters"
|
||||
import type { MySql2Database } from "drizzle-orm/mysql2"
|
||||
|
||||
export const users = mysqlTable("users", {
|
||||
id: varchar("id", { length: 255 }).notNull().primaryKey(),
|
||||
name: varchar("name", { length: 255 }),
|
||||
email: varchar("email", { length: 255 }).notNull(),
|
||||
emailVerified: timestamp("emailVerified", {
|
||||
mode: "date",
|
||||
fsp: 3,
|
||||
}).defaultNow(),
|
||||
image: varchar("image", { length: 255 }),
|
||||
})
|
||||
|
||||
export const accounts = mysqlTable(
|
||||
"accounts",
|
||||
{
|
||||
userId: varchar("userId", { length: 255 })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
type: varchar("type", { length: 255 })
|
||||
.$type<AdapterAccount["type"]>()
|
||||
.notNull(),
|
||||
provider: varchar("provider", { length: 255 }).notNull(),
|
||||
providerAccountId: varchar("providerAccountId", { length: 255 }).notNull(),
|
||||
refresh_token: varchar("refresh_token", { length: 255 }),
|
||||
access_token: varchar("access_token", { length: 255 }),
|
||||
expires_at: int("expires_at"),
|
||||
token_type: varchar("token_type", { length: 255 }),
|
||||
scope: varchar("scope", { length: 255 }),
|
||||
id_token: varchar("id_token", { length: 255 }),
|
||||
session_state: varchar("session_state", { length: 255 }),
|
||||
},
|
||||
(account) => ({
|
||||
compoundKey: primaryKey(account.provider, account.providerAccountId),
|
||||
})
|
||||
)
|
||||
|
||||
export const sessions = mysqlTable("sessions", {
|
||||
sessionToken: varchar("sessionToken", { length: 255 }).notNull().primaryKey(),
|
||||
userId: varchar("userId", { length: 255 })
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
expires: timestamp("expires", { mode: "date" }).notNull(),
|
||||
})
|
||||
|
||||
export const verificationTokens = mysqlTable(
|
||||
"verificationToken",
|
||||
{
|
||||
identifier: varchar("identifier", { length: 255 }).notNull(),
|
||||
token: varchar("token", { length: 255 }).notNull(),
|
||||
expires: timestamp("expires", { mode: "date" }).notNull(),
|
||||
},
|
||||
(vt) => ({
|
||||
compoundKey: primaryKey(vt.identifier, vt.token),
|
||||
})
|
||||
)
|
||||
|
||||
export const schema = { users, accounts, sessions, verificationTokens }
|
||||
export type DefaultSchema = typeof schema
|
||||
|
||||
export function mySqlDrizzleAdapter(
|
||||
client: MySql2Database<Record<string, never>>
|
||||
): Adapter {
|
||||
return {
|
||||
async createUser(data) {
|
||||
const id = crypto.randomUUID()
|
||||
|
||||
await client.insert(users).values({ ...data, id })
|
||||
|
||||
return await client
|
||||
.select()
|
||||
.from(users)
|
||||
.where(eq(users.id, id))
|
||||
.then((res) => res[0])
|
||||
},
|
||||
async getUser(data) {
|
||||
const thing =
|
||||
(await client
|
||||
.select()
|
||||
.from(users)
|
||||
.where(eq(users.id, data))
|
||||
.then((res) => res[0])) ?? null
|
||||
|
||||
return thing
|
||||
},
|
||||
async getUserByEmail(data) {
|
||||
const user =
|
||||
(await client
|
||||
.select()
|
||||
.from(users)
|
||||
.where(eq(users.email, data))
|
||||
.then((res) => res[0])) ?? null
|
||||
|
||||
return user
|
||||
},
|
||||
async createSession(data) {
|
||||
await client.insert(sessions).values(data)
|
||||
|
||||
return await client
|
||||
.select()
|
||||
.from(sessions)
|
||||
.where(eq(sessions.sessionToken, data.sessionToken))
|
||||
.then((res) => res[0])
|
||||
},
|
||||
async getSessionAndUser(data) {
|
||||
const sessionAndUser =
|
||||
(await client
|
||||
.select({
|
||||
session: sessions,
|
||||
user: users,
|
||||
})
|
||||
.from(sessions)
|
||||
.where(eq(sessions.sessionToken, data))
|
||||
.innerJoin(users, eq(users.id, sessions.userId))
|
||||
.then((res) => res[0])) ?? null
|
||||
|
||||
return sessionAndUser
|
||||
},
|
||||
async updateUser(data) {
|
||||
if (!data.id) {
|
||||
throw new Error("No user id.")
|
||||
}
|
||||
|
||||
await client.update(users).set(data).where(eq(users.id, data.id))
|
||||
|
||||
return await client
|
||||
.select()
|
||||
.from(users)
|
||||
.where(eq(users.id, data.id))
|
||||
.then((res) => res[0])
|
||||
},
|
||||
async updateSession(data) {
|
||||
await client
|
||||
.update(sessions)
|
||||
.set(data)
|
||||
.where(eq(sessions.sessionToken, data.sessionToken))
|
||||
|
||||
return await client
|
||||
.select()
|
||||
.from(sessions)
|
||||
.where(eq(sessions.sessionToken, data.sessionToken))
|
||||
.then((res) => res[0])
|
||||
},
|
||||
async linkAccount(rawAccount) {
|
||||
await client
|
||||
.insert(accounts)
|
||||
.values(rawAccount)
|
||||
.then((res) => res[0])
|
||||
},
|
||||
async getUserByAccount(account) {
|
||||
const dbAccount =
|
||||
(await client
|
||||
.select()
|
||||
.from(accounts)
|
||||
.where(
|
||||
and(
|
||||
eq(accounts.providerAccountId, account.providerAccountId),
|
||||
eq(accounts.provider, account.provider)
|
||||
)
|
||||
)
|
||||
.leftJoin(users, eq(accounts.userId, users.id))
|
||||
.then((res) => res[0])) ?? null
|
||||
|
||||
if (!dbAccount) {
|
||||
return null
|
||||
}
|
||||
|
||||
return dbAccount.users
|
||||
},
|
||||
async deleteSession(sessionToken) {
|
||||
const session =
|
||||
(await client
|
||||
.select()
|
||||
.from(sessions)
|
||||
.where(eq(sessions.sessionToken, sessionToken))
|
||||
.then((res) => res[0])) ?? null
|
||||
|
||||
await client
|
||||
.delete(sessions)
|
||||
.where(eq(sessions.sessionToken, sessionToken))
|
||||
|
||||
return session
|
||||
},
|
||||
async createVerificationToken(token) {
|
||||
await client.insert(verificationTokens).values(token)
|
||||
|
||||
return await client
|
||||
.select()
|
||||
.from(verificationTokens)
|
||||
.where(eq(verificationTokens.identifier, token.identifier))
|
||||
.then((res) => res[0])
|
||||
},
|
||||
async useVerificationToken(token) {
|
||||
try {
|
||||
const deletedToken =
|
||||
(await client
|
||||
.select()
|
||||
.from(verificationTokens)
|
||||
.where(
|
||||
and(
|
||||
eq(verificationTokens.identifier, token.identifier),
|
||||
eq(verificationTokens.token, token.token)
|
||||
)
|
||||
)
|
||||
.then((res) => res[0])) ?? null
|
||||
|
||||
await client
|
||||
.delete(verificationTokens)
|
||||
.where(
|
||||
and(
|
||||
eq(verificationTokens.identifier, token.identifier),
|
||||
eq(verificationTokens.token, token.token)
|
||||
)
|
||||
)
|
||||
|
||||
return deletedToken
|
||||
} catch (err) {
|
||||
throw new Error("No verification token found.")
|
||||
}
|
||||
},
|
||||
async deleteUser(id) {
|
||||
const user = await client
|
||||
.select()
|
||||
.from(users)
|
||||
.where(eq(users.id, id))
|
||||
.then((res) => res[0] ?? null)
|
||||
|
||||
await client.delete(users).where(eq(users.id, id))
|
||||
|
||||
return user
|
||||
},
|
||||
async unlinkAccount(account) {
|
||||
await client
|
||||
.delete(accounts)
|
||||
.where(
|
||||
and(
|
||||
eq(accounts.providerAccountId, account.providerAccountId),
|
||||
eq(accounts.provider, account.provider)
|
||||
)
|
||||
)
|
||||
|
||||
return undefined
|
||||
},
|
||||
}
|
||||
}
|
||||
225
packages/adapter-drizzle/src/lib/pg.ts
Normal file
225
packages/adapter-drizzle/src/lib/pg.ts
Normal file
@@ -0,0 +1,225 @@
|
||||
import { and, eq } from "drizzle-orm"
|
||||
import {
|
||||
timestamp,
|
||||
pgTable,
|
||||
text,
|
||||
primaryKey,
|
||||
integer,
|
||||
} from "drizzle-orm/pg-core"
|
||||
|
||||
import type { PostgresJsDatabase } from "drizzle-orm/postgres-js"
|
||||
import type { Adapter, AdapterAccount } from "@auth/core/adapters"
|
||||
|
||||
export const users = pgTable("users", {
|
||||
id: text("id").notNull().primaryKey(),
|
||||
name: text("name"),
|
||||
email: text("email").notNull(),
|
||||
emailVerified: timestamp("emailVerified", { mode: "date" }),
|
||||
image: text("image"),
|
||||
})
|
||||
|
||||
export const accounts = pgTable(
|
||||
"accounts",
|
||||
{
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
type: text("type").$type<AdapterAccount["type"]>().notNull(),
|
||||
provider: text("provider").notNull(),
|
||||
providerAccountId: text("providerAccountId").notNull(),
|
||||
refresh_token: text("refresh_token"),
|
||||
access_token: text("access_token"),
|
||||
expires_at: integer("expires_at"),
|
||||
token_type: text("token_type"),
|
||||
scope: text("scope"),
|
||||
id_token: text("id_token"),
|
||||
session_state: text("session_state"),
|
||||
},
|
||||
(account) => ({
|
||||
compoundKey: primaryKey(account.provider, account.providerAccountId),
|
||||
})
|
||||
)
|
||||
|
||||
export const sessions = pgTable("sessions", {
|
||||
sessionToken: text("sessionToken").notNull().primaryKey(),
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
expires: timestamp("expires", { mode: "date" }).notNull(),
|
||||
})
|
||||
|
||||
export const verificationTokens = pgTable(
|
||||
"verificationToken",
|
||||
{
|
||||
identifier: text("identifier").notNull(),
|
||||
token: text("token").notNull(),
|
||||
expires: timestamp("expires", { mode: "date" }).notNull(),
|
||||
},
|
||||
(vt) => ({
|
||||
compoundKey: primaryKey(vt.identifier, vt.token),
|
||||
})
|
||||
)
|
||||
|
||||
export const schema = { users, accounts, sessions, verificationTokens }
|
||||
export type DefaultSchema = typeof schema
|
||||
|
||||
export function pgDrizzleAdapter(
|
||||
client: PostgresJsDatabase<Record<string, never>>
|
||||
): Adapter {
|
||||
return {
|
||||
async createUser(data) {
|
||||
return await client
|
||||
.insert(users)
|
||||
.values({ ...data, id: crypto.randomUUID() })
|
||||
.returning()
|
||||
.then((res) => res[0] ?? null)
|
||||
},
|
||||
async getUser(data) {
|
||||
return await client
|
||||
.select()
|
||||
.from(users)
|
||||
.where(eq(users.id, data))
|
||||
.then((res) => res[0] ?? null)
|
||||
},
|
||||
async getUserByEmail(data) {
|
||||
return await client
|
||||
.select()
|
||||
.from(users)
|
||||
.where(eq(users.email, data))
|
||||
.then((res) => res[0] ?? null)
|
||||
},
|
||||
async createSession(data) {
|
||||
return await client
|
||||
.insert(sessions)
|
||||
.values(data)
|
||||
.returning()
|
||||
.then((res) => res[0])
|
||||
},
|
||||
async getSessionAndUser(data) {
|
||||
return await client
|
||||
.select({
|
||||
session: sessions,
|
||||
user: users,
|
||||
})
|
||||
.from(sessions)
|
||||
.where(eq(sessions.sessionToken, data))
|
||||
.innerJoin(users, eq(users.id, sessions.userId))
|
||||
.then((res) => res[0] ?? null)
|
||||
},
|
||||
async updateUser(data) {
|
||||
if (!data.id) {
|
||||
throw new Error("No user id.")
|
||||
}
|
||||
|
||||
return await client
|
||||
.update(users)
|
||||
.set(data)
|
||||
.where(eq(users.id, data.id))
|
||||
.returning()
|
||||
.then((res) => res[0])
|
||||
},
|
||||
async updateSession(data) {
|
||||
return await client
|
||||
.update(sessions)
|
||||
.set(data)
|
||||
.where(eq(sessions.sessionToken, data.sessionToken))
|
||||
.returning()
|
||||
.then((res) => res[0])
|
||||
},
|
||||
async linkAccount(rawAccount) {
|
||||
const updatedAccount = await client
|
||||
.insert(accounts)
|
||||
.values(rawAccount)
|
||||
.returning()
|
||||
.then((res) => res[0])
|
||||
|
||||
// Drizzle will return `null` for fields that are not defined.
|
||||
// However, the return type is expecting `undefined`.
|
||||
const account = {
|
||||
...updatedAccount,
|
||||
access_token: updatedAccount.access_token ?? undefined,
|
||||
token_type: updatedAccount.token_type ?? undefined,
|
||||
id_token: updatedAccount.id_token ?? undefined,
|
||||
refresh_token: updatedAccount.refresh_token ?? undefined,
|
||||
scope: updatedAccount.scope ?? undefined,
|
||||
expires_at: updatedAccount.expires_at ?? undefined,
|
||||
session_state: updatedAccount.session_state ?? undefined,
|
||||
}
|
||||
|
||||
return account
|
||||
},
|
||||
async getUserByAccount(account) {
|
||||
const dbAccount =
|
||||
(await client
|
||||
.select()
|
||||
.from(accounts)
|
||||
.where(
|
||||
and(
|
||||
eq(accounts.providerAccountId, account.providerAccountId),
|
||||
eq(accounts.provider, account.provider)
|
||||
)
|
||||
)
|
||||
.leftJoin(users, eq(accounts.userId, users.id))
|
||||
.then((res) => res[0])) ?? null
|
||||
|
||||
if (!dbAccount) {
|
||||
return null
|
||||
}
|
||||
|
||||
return dbAccount.users
|
||||
},
|
||||
async deleteSession(sessionToken) {
|
||||
const session = await client
|
||||
.delete(sessions)
|
||||
.where(eq(sessions.sessionToken, sessionToken))
|
||||
.returning()
|
||||
.then((res) => res[0] ?? null)
|
||||
|
||||
return session
|
||||
},
|
||||
async createVerificationToken(token) {
|
||||
return await client
|
||||
.insert(verificationTokens)
|
||||
.values(token)
|
||||
.returning()
|
||||
.then((res) => res[0])
|
||||
},
|
||||
async useVerificationToken(token) {
|
||||
try {
|
||||
return await client
|
||||
.delete(verificationTokens)
|
||||
.where(
|
||||
and(
|
||||
eq(verificationTokens.identifier, token.identifier),
|
||||
eq(verificationTokens.token, token.token)
|
||||
)
|
||||
)
|
||||
.returning()
|
||||
.then((res) => res[0] ?? null)
|
||||
} catch (err) {
|
||||
throw new Error("No verification token found.")
|
||||
}
|
||||
},
|
||||
async deleteUser(id) {
|
||||
await client
|
||||
.delete(users)
|
||||
.where(eq(users.id, id))
|
||||
.returning()
|
||||
.then((res) => res[0] ?? null)
|
||||
},
|
||||
async unlinkAccount(account) {
|
||||
const { type, provider, providerAccountId, userId } = await client
|
||||
.delete(accounts)
|
||||
.where(
|
||||
and(
|
||||
eq(accounts.providerAccountId, account.providerAccountId),
|
||||
eq(accounts.provider, account.provider)
|
||||
)
|
||||
)
|
||||
.returning()
|
||||
.then((res) => res[0] ?? null)
|
||||
|
||||
return { provider, type, providerAccountId, userId }
|
||||
},
|
||||
}
|
||||
}
|
||||
203
packages/adapter-drizzle/src/lib/sqlite.ts
Normal file
203
packages/adapter-drizzle/src/lib/sqlite.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
import { eq, and } from "drizzle-orm"
|
||||
import {
|
||||
integer,
|
||||
sqliteTable,
|
||||
text,
|
||||
primaryKey,
|
||||
BaseSQLiteDatabase,
|
||||
} from "drizzle-orm/sqlite-core"
|
||||
|
||||
import type { Adapter, AdapterAccount } from "@auth/core/adapters"
|
||||
|
||||
export const users = sqliteTable("users", {
|
||||
id: text("id").notNull().primaryKey(),
|
||||
name: text("name"),
|
||||
email: text("email").notNull(),
|
||||
emailVerified: integer("emailVerified", { mode: "timestamp_ms" }),
|
||||
image: text("image"),
|
||||
})
|
||||
|
||||
export const accounts = sqliteTable(
|
||||
"accounts",
|
||||
{
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
type: text("type").$type<AdapterAccount["type"]>().notNull(),
|
||||
provider: text("provider").notNull(),
|
||||
providerAccountId: text("providerAccountId").notNull(),
|
||||
refresh_token: text("refresh_token"),
|
||||
access_token: text("access_token"),
|
||||
expires_at: integer("expires_at"),
|
||||
token_type: text("token_type"),
|
||||
scope: text("scope"),
|
||||
id_token: text("id_token"),
|
||||
session_state: text("session_state"),
|
||||
},
|
||||
(account) => ({
|
||||
compoundKey: primaryKey(account.provider, account.providerAccountId),
|
||||
})
|
||||
)
|
||||
|
||||
export const sessions = sqliteTable("sessions", {
|
||||
sessionToken: text("sessionToken").notNull().primaryKey(),
|
||||
userId: text("userId")
|
||||
.notNull()
|
||||
.references(() => users.id, { onDelete: "cascade" }),
|
||||
expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
|
||||
})
|
||||
|
||||
export const verificationTokens = sqliteTable(
|
||||
"verificationToken",
|
||||
{
|
||||
identifier: text("identifier").notNull(),
|
||||
token: text("token").notNull(),
|
||||
expires: integer("expires", { mode: "timestamp_ms" }).notNull(),
|
||||
},
|
||||
(vt) => ({
|
||||
compoundKey: primaryKey(vt.identifier, vt.token),
|
||||
})
|
||||
)
|
||||
|
||||
export const schema = { users, accounts, sessions, verificationTokens }
|
||||
export type DefaultSchema = typeof schema
|
||||
|
||||
export function SQLiteDrizzleAdapter(
|
||||
client: BaseSQLiteDatabase<any, any>
|
||||
): Adapter {
|
||||
return {
|
||||
createUser(data) {
|
||||
return client
|
||||
.insert(users)
|
||||
.values({ ...data, id: crypto.randomUUID() })
|
||||
.returning()
|
||||
.get()
|
||||
},
|
||||
getUser(data) {
|
||||
return client.select().from(users).where(eq(users.id, data)).get() ?? null
|
||||
},
|
||||
getUserByEmail(data) {
|
||||
return (
|
||||
client.select().from(users).where(eq(users.email, data)).get() ?? null
|
||||
)
|
||||
},
|
||||
createSession(data) {
|
||||
return client.insert(sessions).values(data).returning().get()
|
||||
},
|
||||
getSessionAndUser(data) {
|
||||
return (
|
||||
client
|
||||
.select({
|
||||
session: sessions,
|
||||
user: users,
|
||||
})
|
||||
.from(sessions)
|
||||
.where(eq(sessions.sessionToken, data))
|
||||
.innerJoin(users, eq(users.id, sessions.userId))
|
||||
.get() ?? null
|
||||
)
|
||||
},
|
||||
updateUser(data) {
|
||||
if (!data.id) {
|
||||
throw new Error("No user id.")
|
||||
}
|
||||
|
||||
return client
|
||||
.update(users)
|
||||
.set(data)
|
||||
.where(eq(users.id, data.id))
|
||||
.returning()
|
||||
.get()
|
||||
},
|
||||
updateSession(data) {
|
||||
return client
|
||||
.update(sessions)
|
||||
.set(data)
|
||||
.where(eq(sessions.sessionToken, data.sessionToken))
|
||||
.returning()
|
||||
.get()
|
||||
},
|
||||
linkAccount(rawAccount) {
|
||||
const updatedAccount = client
|
||||
.insert(accounts)
|
||||
.values(rawAccount)
|
||||
.returning()
|
||||
.get()
|
||||
|
||||
const account: AdapterAccount = {
|
||||
...updatedAccount,
|
||||
type: updatedAccount.type,
|
||||
access_token: updatedAccount.access_token ?? undefined,
|
||||
token_type: updatedAccount.token_type ?? undefined,
|
||||
id_token: updatedAccount.id_token ?? undefined,
|
||||
refresh_token: updatedAccount.refresh_token ?? undefined,
|
||||
scope: updatedAccount.scope ?? undefined,
|
||||
expires_at: updatedAccount.expires_at ?? undefined,
|
||||
session_state: updatedAccount.session_state ?? undefined,
|
||||
}
|
||||
|
||||
return account
|
||||
},
|
||||
getUserByAccount(account) {
|
||||
const results = client
|
||||
.select()
|
||||
.from(accounts)
|
||||
.leftJoin(users, eq(users.id, accounts.userId))
|
||||
.where(
|
||||
and(
|
||||
eq(accounts.provider, account.provider),
|
||||
eq(accounts.providerAccountId, account.providerAccountId)
|
||||
)
|
||||
)
|
||||
.get()
|
||||
|
||||
return results?.users ?? null
|
||||
},
|
||||
deleteSession(sessionToken) {
|
||||
return (
|
||||
client
|
||||
.delete(sessions)
|
||||
.where(eq(sessions.sessionToken, sessionToken))
|
||||
.returning()
|
||||
.get() ?? null
|
||||
)
|
||||
},
|
||||
createVerificationToken(token) {
|
||||
return client.insert(verificationTokens).values(token).returning().get()
|
||||
},
|
||||
useVerificationToken(token) {
|
||||
try {
|
||||
return (
|
||||
client
|
||||
.delete(verificationTokens)
|
||||
.where(
|
||||
and(
|
||||
eq(verificationTokens.identifier, token.identifier),
|
||||
eq(verificationTokens.token, token.token)
|
||||
)
|
||||
)
|
||||
.returning()
|
||||
.get() ?? null
|
||||
)
|
||||
} catch (err) {
|
||||
throw new Error("No verification token found.")
|
||||
}
|
||||
},
|
||||
deleteUser(id) {
|
||||
return client.delete(users).where(eq(users.id, id)).returning().get()
|
||||
},
|
||||
unlinkAccount(account) {
|
||||
client
|
||||
.delete(accounts)
|
||||
.where(
|
||||
and(
|
||||
eq(accounts.providerAccountId, account.providerAccountId),
|
||||
eq(accounts.provider, account.provider)
|
||||
)
|
||||
)
|
||||
.run()
|
||||
|
||||
return undefined
|
||||
},
|
||||
}
|
||||
}
|
||||
47
packages/adapter-drizzle/src/lib/utils.ts
Normal file
47
packages/adapter-drizzle/src/lib/utils.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { MySqlDatabase } from "drizzle-orm/mysql-core"
|
||||
import { PgDatabase } from "drizzle-orm/pg-core"
|
||||
import { BaseSQLiteDatabase } from "drizzle-orm/sqlite-core"
|
||||
|
||||
import type { AnyMySqlTable } from "drizzle-orm/mysql-core"
|
||||
import type { AnyPgTable } from "drizzle-orm/pg-core"
|
||||
import type { AnySQLiteTable } from "drizzle-orm/sqlite-core"
|
||||
import type { DefaultSchema as PgSchema } from "./pg.js"
|
||||
import type { DefaultSchema as MySqlSchema } from "./mysql.js"
|
||||
import type { DefaultSchema as SQLiteSchema } from "./sqlite.js"
|
||||
|
||||
export type AnyMySqlDatabase = MySqlDatabase<any, any>
|
||||
export type AnyPgDatabase = PgDatabase<any, any, any>
|
||||
export type AnySQLiteDatabase = BaseSQLiteDatabase<any, any, any, any>
|
||||
|
||||
export interface MinimumSchema {
|
||||
mysql: MySqlSchema & Record<string, AnyMySqlTable>
|
||||
pg: PgSchema & Record<string, AnyPgTable>
|
||||
sqlite: SQLiteSchema & Record<string, AnySQLiteTable>
|
||||
}
|
||||
|
||||
export type SqlFlavorOptions =
|
||||
| AnyMySqlDatabase
|
||||
| AnyPgDatabase
|
||||
| AnySQLiteDatabase
|
||||
|
||||
export type ClientFlavors<Flavor> = Flavor extends AnyMySqlDatabase
|
||||
? MinimumSchema["mysql"]
|
||||
: Flavor extends AnyPgDatabase
|
||||
? MinimumSchema["pg"]
|
||||
: Flavor extends AnySQLiteDatabase
|
||||
? MinimumSchema["sqlite"]
|
||||
: never
|
||||
|
||||
export function isMySqlDatabase(
|
||||
db: any
|
||||
): db is MySqlDatabase<any, any, any, any> {
|
||||
return db instanceof MySqlDatabase
|
||||
}
|
||||
|
||||
export function isPgDatabase(db: any): db is PgDatabase<any, any, any> {
|
||||
return db instanceof PgDatabase
|
||||
}
|
||||
|
||||
export function isSQLiteDatabase(db: any): db is AnySQLiteDatabase {
|
||||
return db instanceof BaseSQLiteDatabase
|
||||
}
|
||||
43
packages/adapter-drizzle/tests/fixtures.ts
Normal file
43
packages/adapter-drizzle/tests/fixtures.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
// This work is needed as workaround to Drizzle truncating millisecond precision.
|
||||
// https://github.com/drizzle-team/drizzle-orm/pull/668
|
||||
|
||||
import { randomUUID } from "../../adapter-test"
|
||||
|
||||
const emailVerified = new Date()
|
||||
emailVerified.setMilliseconds(0)
|
||||
|
||||
const ONE_WEEK_FROM_NOW = new Date(Date.now() + 1000 * 60 * 60 * 24 * 7)
|
||||
ONE_WEEK_FROM_NOW.setMilliseconds(0)
|
||||
const FIFTEEN_MINUTES_FROM_NOW = new Date(Date.now() + 15 * 60 * 1000)
|
||||
FIFTEEN_MINUTES_FROM_NOW.setMilliseconds(0)
|
||||
|
||||
const ONE_MONTH = 1000 * 60 * 60 * 24 * 30
|
||||
const ONE_MONTH_FROM_NOW = new Date(Date.now() + ONE_MONTH)
|
||||
ONE_MONTH_FROM_NOW.setMilliseconds(0)
|
||||
|
||||
export const fixtures = {
|
||||
user: {
|
||||
email: "fill@murray.com",
|
||||
image: "https://www.fillmurray.com/460/300",
|
||||
name: "Fill Murray",
|
||||
emailVerified,
|
||||
},
|
||||
session: {
|
||||
sessionToken: randomUUID(),
|
||||
expires: ONE_WEEK_FROM_NOW,
|
||||
},
|
||||
sessionUpdateExpires: ONE_MONTH_FROM_NOW,
|
||||
verificationTokenExpires: FIFTEEN_MINUTES_FROM_NOW,
|
||||
account: {
|
||||
provider: "github",
|
||||
providerAccountId: randomUUID(),
|
||||
type: "oauth",
|
||||
access_token: randomUUID(),
|
||||
expires_at: ONE_MONTH / 1000,
|
||||
id_token: randomUUID(),
|
||||
refresh_token: randomUUID(),
|
||||
token_type: "bearer",
|
||||
scope: "user",
|
||||
session_state: randomUUID(),
|
||||
},
|
||||
}
|
||||
13
packages/adapter-drizzle/tests/mysql/drizzle.config.ts
Normal file
13
packages/adapter-drizzle/tests/mysql/drizzle.config.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { Config } from "drizzle-kit"
|
||||
|
||||
export default {
|
||||
schema: "./tests/mysql/schema.ts",
|
||||
out: "./tests/mysql/.drizzle",
|
||||
driver: "mysql2",
|
||||
dbCredentials: {
|
||||
host: "localhost",
|
||||
user: "root",
|
||||
password: "password",
|
||||
database: "next-auth",
|
||||
},
|
||||
} satisfies Config
|
||||
71
packages/adapter-drizzle/tests/mysql/index.test.ts
Normal file
71
packages/adapter-drizzle/tests/mysql/index.test.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { runBasicTests } from "../../../adapter-test"
|
||||
import { DrizzleAdapter } from "../../src"
|
||||
import { db, sessions, verificationTokens, accounts, users } from "./schema"
|
||||
import { eq, and } from "drizzle-orm"
|
||||
import { fixtures } from "../fixtures"
|
||||
|
||||
globalThis.crypto ??= require("node:crypto").webcrypto
|
||||
|
||||
runBasicTests({
|
||||
adapter: DrizzleAdapter(db),
|
||||
fixtures,
|
||||
db: {
|
||||
connect: async () => {
|
||||
await Promise.all([
|
||||
db.delete(sessions),
|
||||
db.delete(accounts),
|
||||
db.delete(verificationTokens),
|
||||
db.delete(users),
|
||||
])
|
||||
},
|
||||
disconnect: async () => {
|
||||
await Promise.all([
|
||||
db.delete(sessions),
|
||||
db.delete(accounts),
|
||||
db.delete(verificationTokens),
|
||||
db.delete(users),
|
||||
])
|
||||
},
|
||||
user: async (id) => {
|
||||
const user = await db
|
||||
.select()
|
||||
.from(users)
|
||||
.where(eq(users.id, id))
|
||||
.then((res) => res[0] ?? null)
|
||||
return user
|
||||
},
|
||||
session: async (sessionToken) => {
|
||||
const session = await db
|
||||
.select()
|
||||
.from(sessions)
|
||||
.where(eq(sessions.sessionToken, sessionToken))
|
||||
.then((res) => res[0] ?? null)
|
||||
|
||||
return session
|
||||
},
|
||||
account: (provider_providerAccountId) => {
|
||||
const account = db
|
||||
.select()
|
||||
.from(accounts)
|
||||
.where(
|
||||
eq(
|
||||
accounts.providerAccountId,
|
||||
provider_providerAccountId.providerAccountId
|
||||
)
|
||||
)
|
||||
.then((res) => res[0] ?? null)
|
||||
return account
|
||||
},
|
||||
verificationToken: (identifier_token) =>
|
||||
db
|
||||
.select()
|
||||
.from(verificationTokens)
|
||||
.where(
|
||||
and(
|
||||
eq(verificationTokens.token, identifier_token.token),
|
||||
eq(verificationTokens.identifier, identifier_token.identifier)
|
||||
)
|
||||
)
|
||||
.then((res) => res[0]) ?? null,
|
||||
},
|
||||
})
|
||||
29
packages/adapter-drizzle/tests/mysql/schema.ts
Normal file
29
packages/adapter-drizzle/tests/mysql/schema.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { AdapterAccount } from "@auth/core/adapters"
|
||||
import {
|
||||
mysqlTable,
|
||||
varchar,
|
||||
timestamp,
|
||||
int,
|
||||
primaryKey,
|
||||
} from "drizzle-orm/mysql-core"
|
||||
import { drizzle } from "drizzle-orm/mysql2"
|
||||
import { createPool } from "mysql2"
|
||||
import {
|
||||
users,
|
||||
accounts,
|
||||
sessions,
|
||||
verificationTokens,
|
||||
schema,
|
||||
} from "../../src/lib/mysql"
|
||||
|
||||
const poolConnection = createPool({
|
||||
host: "localhost",
|
||||
user: "root",
|
||||
password: "password",
|
||||
database: "next-auth",
|
||||
})
|
||||
|
||||
export { users, accounts, sessions, verificationTokens }
|
||||
export const db = drizzle(poolConnection, {
|
||||
schema: schema,
|
||||
})
|
||||
22
packages/adapter-drizzle/tests/mysql/test.sh
Executable file
22
packages/adapter-drizzle/tests/mysql/test.sh
Executable file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "Initializing container for MySQL tests."
|
||||
|
||||
MYSQL_DATABASE=next-auth
|
||||
MYSQL_ROOT_PASSWORD=password
|
||||
MYSQL_CONTAINER_NAME=next-auth-mysql-test
|
||||
|
||||
docker run -d --rm \
|
||||
-e MYSQL_DATABASE=${MYSQL_DATABASE} \
|
||||
-e MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD} \
|
||||
--name "${MYSQL_CONTAINER_NAME}" \
|
||||
-p 3306:3306 \
|
||||
mysql:8 \
|
||||
--default-authentication-plugin=mysql_native_password
|
||||
|
||||
echo "Waiting 15 sec for db to start..." && sleep 15
|
||||
|
||||
drizzle-kit generate:mysql --config=./tests/mysql/drizzle.config.ts
|
||||
drizzle-kit push:mysql --config=./tests/mysql/drizzle.config.ts
|
||||
jest ./tests/mysql/index.test.ts --forceExit
|
||||
docker stop ${MYSQL_CONTAINER_NAME}
|
||||
13
packages/adapter-drizzle/tests/pg/drizzle.config.ts
Normal file
13
packages/adapter-drizzle/tests/pg/drizzle.config.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { Config } from "drizzle-kit"
|
||||
|
||||
export default {
|
||||
schema: "./tests/pg/schema.ts",
|
||||
out: "./tests/pg/.drizzle",
|
||||
dbCredentials: {
|
||||
database: "nextauth",
|
||||
host: "nextauth",
|
||||
user: "nextauth",
|
||||
password: "nextauth",
|
||||
port: 5432,
|
||||
},
|
||||
} satisfies Config
|
||||
65
packages/adapter-drizzle/tests/pg/index.test.ts
Normal file
65
packages/adapter-drizzle/tests/pg/index.test.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { runBasicTests } from "../../../adapter-test"
|
||||
import { DrizzleAdapter } from "../../src"
|
||||
import { db, accounts, sessions, users, verificationTokens } from "./schema"
|
||||
import { eq, and } from "drizzle-orm"
|
||||
import { fixtures } from "../fixtures"
|
||||
|
||||
globalThis.crypto ??= require("node:crypto").webcrypto
|
||||
|
||||
runBasicTests({
|
||||
adapter: DrizzleAdapter(db),
|
||||
fixtures,
|
||||
db: {
|
||||
connect: async () => {
|
||||
await Promise.all([
|
||||
db.delete(sessions),
|
||||
db.delete(accounts),
|
||||
db.delete(verificationTokens),
|
||||
db.delete(users),
|
||||
])
|
||||
},
|
||||
disconnect: async () => {
|
||||
await Promise.all([
|
||||
db.delete(sessions),
|
||||
db.delete(accounts),
|
||||
db.delete(verificationTokens),
|
||||
db.delete(users),
|
||||
])
|
||||
},
|
||||
user: async (id) =>
|
||||
db
|
||||
.select()
|
||||
.from(users)
|
||||
.where(eq(users.id, id))
|
||||
.then((res) => res[0] ?? null),
|
||||
session: (sessionToken) =>
|
||||
db
|
||||
.select()
|
||||
.from(sessions)
|
||||
.where(eq(sessions.sessionToken, sessionToken))
|
||||
.then((res) => res[0] ?? null),
|
||||
account: (provider_providerAccountId) => {
|
||||
return db
|
||||
.select()
|
||||
.from(accounts)
|
||||
.where(
|
||||
eq(
|
||||
accounts.providerAccountId,
|
||||
provider_providerAccountId.providerAccountId
|
||||
)
|
||||
)
|
||||
.then((res) => res[0] ?? null)
|
||||
},
|
||||
verificationToken: (identifier_token) =>
|
||||
db
|
||||
.select()
|
||||
.from(verificationTokens)
|
||||
.where(
|
||||
and(
|
||||
eq(verificationTokens.token, identifier_token.token),
|
||||
eq(verificationTokens.identifier, identifier_token.identifier)
|
||||
)
|
||||
)
|
||||
.then((res) => res[0] ?? null),
|
||||
},
|
||||
})
|
||||
10
packages/adapter-drizzle/tests/pg/migrator.ts
Normal file
10
packages/adapter-drizzle/tests/pg/migrator.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { migrate } from "drizzle-orm/postgres-js/migrator"
|
||||
import { db } from "./schema"
|
||||
|
||||
const migrator = async () => {
|
||||
await migrate(db, { migrationsFolder: "./tests/pg/.drizzle" })
|
||||
}
|
||||
|
||||
migrator()
|
||||
.then(() => process.exit(0))
|
||||
.catch(() => process.exit(1))
|
||||
11
packages/adapter-drizzle/tests/pg/schema.ts
Normal file
11
packages/adapter-drizzle/tests/pg/schema.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { drizzle } from "drizzle-orm/postgres-js"
|
||||
import postgres from "postgres"
|
||||
import { users, accounts, sessions, verificationTokens } from "../../src/lib/pg"
|
||||
|
||||
const connectionString = "postgres://nextauth:nextauth@localhost:5432/nextauth"
|
||||
const sql = postgres(connectionString, { max: 1 })
|
||||
|
||||
export const db = drizzle(sql, {
|
||||
schema: { users, accounts, sessions, verificationTokens },
|
||||
})
|
||||
export { users, accounts, sessions, verificationTokens }
|
||||
25
packages/adapter-drizzle/tests/pg/test.sh
Executable file
25
packages/adapter-drizzle/tests/pg/test.sh
Executable file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
echo "Initializing container for PostgreSQL tests."
|
||||
|
||||
PGUSER=nextauth
|
||||
PGPASSWORD=nextauth
|
||||
PGDATABASE=nextauth
|
||||
PGPORT=5432
|
||||
PG_CONTAINER_NAME=next-auth-postgres-test
|
||||
|
||||
docker run -d --rm \
|
||||
-e POSTGRES_USER=${PGUSER} \
|
||||
-e POSTGRES_PASSWORD=${PGUSER} \
|
||||
-e POSTGRES_DB=${PGDATABASE} \
|
||||
-e POSTGRES_HOST_AUTH_METHOD=trust \
|
||||
--name "${PG_CONTAINER_NAME}" \
|
||||
-p ${PGPORT}:5432 \
|
||||
postgres:15.3
|
||||
|
||||
echo "Waiting 15 sec for db to start..." && sleep 15
|
||||
|
||||
drizzle-kit generate:pg --config=./tests/pg/drizzle.config.ts
|
||||
npx tsx ./tests/pg/migrator.ts
|
||||
jest ./tests/pg/index.test.ts --forceExit
|
||||
docker stop ${PG_CONTAINER_NAME}
|
||||
10
packages/adapter-drizzle/tests/sqlite/drizzle.config.ts
Normal file
10
packages/adapter-drizzle/tests/sqlite/drizzle.config.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { Config } from "drizzle-kit"
|
||||
|
||||
export default {
|
||||
schema: "./tests/sqlite/schema.ts",
|
||||
out: "./tests/sqlite/.drizzle",
|
||||
driver: "better-sqlite",
|
||||
dbCredentials: {
|
||||
url: "./db.sqlite",
|
||||
},
|
||||
} satisfies Config
|
||||
60
packages/adapter-drizzle/tests/sqlite/index.test.ts
Normal file
60
packages/adapter-drizzle/tests/sqlite/index.test.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import { runBasicTests } from "../../../adapter-test"
|
||||
import { DrizzleAdapter } from "../../src"
|
||||
import { db, accounts, sessions, users, verificationTokens } from "./schema"
|
||||
import { eq, and } from "drizzle-orm"
|
||||
|
||||
globalThis.crypto ??= require("node:crypto").webcrypto
|
||||
|
||||
runBasicTests({
|
||||
adapter: DrizzleAdapter(db),
|
||||
db: {
|
||||
connect: async () => {
|
||||
await Promise.all([
|
||||
db.delete(sessions),
|
||||
db.delete(accounts),
|
||||
db.delete(verificationTokens),
|
||||
db.delete(users),
|
||||
])
|
||||
},
|
||||
disconnect: async () => {
|
||||
await Promise.all([
|
||||
db.delete(sessions),
|
||||
db.delete(accounts),
|
||||
db.delete(verificationTokens),
|
||||
db.delete(users),
|
||||
])
|
||||
},
|
||||
user: (id) => db.select().from(users).where(eq(users.id, id)).get() ?? null,
|
||||
session: (sessionToken) =>
|
||||
db
|
||||
.select()
|
||||
.from(sessions)
|
||||
.where(eq(sessions.sessionToken, sessionToken))
|
||||
.get() ?? null,
|
||||
account: (provider_providerAccountId) => {
|
||||
return (
|
||||
db
|
||||
.select()
|
||||
.from(accounts)
|
||||
.where(
|
||||
eq(
|
||||
accounts.providerAccountId,
|
||||
provider_providerAccountId.providerAccountId
|
||||
)
|
||||
)
|
||||
.get() ?? null
|
||||
)
|
||||
},
|
||||
verificationToken: (identifier_token) =>
|
||||
db
|
||||
.select()
|
||||
.from(verificationTokens)
|
||||
.where(
|
||||
and(
|
||||
eq(verificationTokens.token, identifier_token.token),
|
||||
eq(verificationTokens.identifier, identifier_token.identifier)
|
||||
)
|
||||
)
|
||||
.get() ?? null,
|
||||
},
|
||||
})
|
||||
20
packages/adapter-drizzle/tests/sqlite/schema.ts
Normal file
20
packages/adapter-drizzle/tests/sqlite/schema.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { drizzle } from "drizzle-orm/better-sqlite3"
|
||||
import Database from "better-sqlite3"
|
||||
import {
|
||||
users,
|
||||
accounts,
|
||||
sessions,
|
||||
verificationTokens,
|
||||
} from "../../src/lib/sqlite"
|
||||
|
||||
const sqlite = new Database("db.sqlite")
|
||||
|
||||
export { users, accounts, sessions, verificationTokens }
|
||||
export const db = drizzle(sqlite, {
|
||||
schema: {
|
||||
users,
|
||||
accounts,
|
||||
sessions,
|
||||
verificationTokens,
|
||||
},
|
||||
})
|
||||
12
packages/adapter-drizzle/tests/sqlite/test.sh
Executable file
12
packages/adapter-drizzle/tests/sqlite/test.sh
Executable file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eu
|
||||
|
||||
|
||||
echo "Running SQLite tests."
|
||||
|
||||
rm -f db.sqlite
|
||||
|
||||
drizzle-kit generate:sqlite --config=./tests/sqlite/drizzle.config.ts
|
||||
drizzle-kit push:sqlite --config=./tests/sqlite/drizzle.config.ts
|
||||
jest ./tests/sqlite/index.test.ts --forceExit
|
||||
25
packages/adapter-drizzle/tsconfig.json
Normal file
25
packages/adapter-drizzle/tsconfig.json
Normal file
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"extends": "@next-auth/tsconfig/tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"baseUrl": ".",
|
||||
"isolatedModules": true,
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"outDir": ".",
|
||||
"rootDir": "src",
|
||||
"skipDefaultLibCheck": true,
|
||||
"strictNullChecks": true,
|
||||
"stripInternal": true,
|
||||
"declarationMap": true,
|
||||
"declaration": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"*.js",
|
||||
"*.d.ts",
|
||||
]
|
||||
}
|
||||
@@ -15,6 +15,13 @@ const requiredMethods = [
|
||||
]
|
||||
export interface TestOptions {
|
||||
adapter: Adapter
|
||||
fixtures?: {
|
||||
user?: any
|
||||
session?: any
|
||||
account?: any
|
||||
sessionUpdateExpires?: Date
|
||||
verificationTokenExpires?: Date
|
||||
},
|
||||
db: {
|
||||
/** Generates UUID v4 by default. Use it to override how the test suite should generate IDs, like user id. */
|
||||
id?: () => string
|
||||
@@ -67,11 +74,11 @@ export async function runBasicTests(options: TestOptions) {
|
||||
await options.db.disconnect?.()
|
||||
})
|
||||
|
||||
let user: any = {
|
||||
let user: any = options.fixtures?.user ?? {
|
||||
email: "fill@murray.com",
|
||||
image: "https://www.fillmurray.com/460/300",
|
||||
name: "Fill Murray",
|
||||
emailVerified: new Date(),
|
||||
emailVerified: new Date()
|
||||
}
|
||||
|
||||
if (process.env.CUSTOM_MODEL === "1") {
|
||||
@@ -79,12 +86,12 @@ export async function runBasicTests(options: TestOptions) {
|
||||
user.phone = "00000000000"
|
||||
}
|
||||
|
||||
const session: any = {
|
||||
const session: any = options.fixtures?.session ?? {
|
||||
sessionToken: randomUUID(),
|
||||
expires: ONE_WEEK_FROM_NOW,
|
||||
}
|
||||
|
||||
const account: any = {
|
||||
const account: any = options.fixtures?.account ?? {
|
||||
provider: "github",
|
||||
providerAccountId: randomUUID(),
|
||||
type: "oauth",
|
||||
@@ -175,15 +182,17 @@ export async function runBasicTests(options: TestOptions) {
|
||||
test("updateSession", async () => {
|
||||
let dbSession = await db.session(session.sessionToken)
|
||||
|
||||
expect(dbSession.expires.valueOf()).not.toBe(ONE_MONTH_FROM_NOW.valueOf())
|
||||
const expires = options.fixtures?.sessionUpdateExpires ?? ONE_MONTH_FROM_NOW
|
||||
|
||||
expect(dbSession.expires.valueOf()).not.toBe(expires.valueOf())
|
||||
|
||||
await adapter.updateSession({
|
||||
sessionToken: session.sessionToken,
|
||||
expires: ONE_MONTH_FROM_NOW,
|
||||
expires,
|
||||
})
|
||||
|
||||
dbSession = await db.session(session.sessionToken)
|
||||
expect(dbSession.expires.valueOf()).toBe(ONE_MONTH_FROM_NOW.valueOf())
|
||||
expect(dbSession.expires.valueOf()).toBe(expires.valueOf())
|
||||
})
|
||||
|
||||
test("linkAccount", async () => {
|
||||
@@ -232,7 +241,7 @@ export async function runBasicTests(options: TestOptions) {
|
||||
const verificationToken = {
|
||||
token: hashedToken,
|
||||
identifier,
|
||||
expires: FIFTEEN_MINUTES_FROM_NOW,
|
||||
expires: options.fixtures?.verificationTokenExpires ?? FIFTEEN_MINUTES_FROM_NOW,
|
||||
}
|
||||
await adapter.createVerificationToken?.(verificationToken)
|
||||
|
||||
@@ -251,7 +260,7 @@ export async function runBasicTests(options: TestOptions) {
|
||||
const verificationToken = {
|
||||
token: hashedToken,
|
||||
identifier,
|
||||
expires: FIFTEEN_MINUTES_FROM_NOW,
|
||||
expires: options.fixtures?.verificationTokenExpires ?? FIFTEEN_MINUTES_FROM_NOW,
|
||||
}
|
||||
await adapter.createVerificationToken?.(verificationToken)
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ module.exports = {
|
||||
".(js|jsx)$": ["@swc/jest", swcConfig],
|
||||
},
|
||||
transformIgnorePatterns: ["[/\\\\]node_modules[/\\\\].+\\.(js|jsx)$"],
|
||||
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
|
||||
moduleFileExtensions: ["mjs", "cjs", "ts", "tsx", "js", "jsx", "json", "node"],
|
||||
rootDir: ".",
|
||||
// coverageDirectory: "<rootDir>/coverage/",
|
||||
// collectCoverageFrom: ["<rootDir>/packages/*/src/**/*.{ts,tsx}"],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@auth/core",
|
||||
"version": "0.10.0",
|
||||
"version": "0.10.1",
|
||||
"description": "Authentication for the Web.",
|
||||
"keywords": [
|
||||
"authentication",
|
||||
|
||||
@@ -122,7 +122,7 @@ export async function handleOAuth(
|
||||
throw new Error("TODO: Handle www-authenticate challenges as needed")
|
||||
}
|
||||
|
||||
let profile: Profile
|
||||
let profile: Profile = {}
|
||||
let tokens: TokenSet & Pick<Account, "expires_at">
|
||||
|
||||
if (provider.type === "oidc") {
|
||||
@@ -153,7 +153,8 @@ export async function handleOAuth(
|
||||
}
|
||||
|
||||
if (userinfo?.request) {
|
||||
profile = await userinfo.request({ tokens, provider })
|
||||
const _profile = await userinfo.request({ tokens, provider })
|
||||
if (_profile instanceof Object) profile = _profile
|
||||
} else if (userinfo?.url) {
|
||||
const userinfoResponse = await o.userInfoRequest(
|
||||
as,
|
||||
|
||||
@@ -31,7 +31,7 @@ type EndpointRequest<C, R, P> = (
|
||||
callbackUrl: string
|
||||
}
|
||||
}
|
||||
) => Awaitable<R>
|
||||
) => Awaitable<R> | void
|
||||
|
||||
/** Gives granular control of the request to the given endpoint */
|
||||
interface AdvancedEndpointHandler<P extends UrlParams, C, R> {
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
"svelte-check": "^2.9.2",
|
||||
"tslib": "^2.4.1",
|
||||
"typescript": "^4.9.3",
|
||||
"vite": "^4.0.0",
|
||||
"vite": "^4.0.5",
|
||||
"vitest": "^0.25.3"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
8215
pnpm-lock.yaml
generated
8215
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
packages:
|
||||
- "packages/*"
|
||||
- "apps/dev/*"
|
||||
- "apps/playgrounds/*"
|
||||
- "docs"
|
||||
|
||||
14
turbo.json
14
turbo.json
@@ -3,7 +3,8 @@
|
||||
"pipeline": {
|
||||
"build": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": ["dist/**/*", "*.js", "*.d.ts", "*.d.ts.map"]
|
||||
"outputs": ["dist/**/*", "*.js", "*.d.ts", "*.d.ts.map"],
|
||||
"outputMode": "new-only"
|
||||
},
|
||||
"next-auth#build": {
|
||||
"dependsOn": ["^build"],
|
||||
@@ -17,7 +18,8 @@
|
||||
"react/**",
|
||||
"*.js",
|
||||
"*.d.ts"
|
||||
]
|
||||
],
|
||||
"outputMode": "new-only"
|
||||
},
|
||||
"@auth/core#build": {
|
||||
"dependsOn": ["^build"],
|
||||
@@ -29,11 +31,13 @@
|
||||
"*.d.ts.map",
|
||||
"src/lib/pages/styles.ts",
|
||||
"src/providers/oauth-types.ts"
|
||||
]
|
||||
],
|
||||
"outputMode": "new-only"
|
||||
},
|
||||
"@auth/sveltekit#build": {
|
||||
"dependsOn": ["^build"],
|
||||
"outputs": [".svelte-kit/**", "client.*", "index.*"]
|
||||
"outputs": [".svelte-kit/**", "client.*", "index.*"],
|
||||
"outputMode": "new-only"
|
||||
},
|
||||
"clean": {
|
||||
"cache": false
|
||||
@@ -42,7 +46,7 @@
|
||||
"cache": false
|
||||
},
|
||||
"test": {
|
||||
"outputs": []
|
||||
"outputMode": "new-only"
|
||||
},
|
||||
"e2e": {
|
||||
"outputs": ["playwright-report/**"]
|
||||
|
||||
Reference in New Issue
Block a user