Use kv datastore for auth (#272)
This commit is contained in:
parent
d741351e26
commit
b6cab643ef
10 changed files with 143 additions and 173 deletions
|
|
@ -37,7 +37,7 @@ This template ships with OpenAI `gpt-3.5-turbo` as the default. However, thanks
|
|||
|
||||
You can deploy your own version of the Next.js AI Chatbot to Vercel with one click:
|
||||
|
||||
[](https://vercel.com/new/clone?demo-title=Next.js+Chat&demo-description=A+full-featured%2C+hackable+Next.js+AI+chatbot+built+by+Vercel+Labs&demo-url=https%3A%2F%2Fchat.vercel.ai%2F&demo-image=%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F4aVPvWuTmBvzM5cEdRdqeW%2F4234f9baf160f68ffb385a43c3527645%2FCleanShot_2023-06-16_at_17.09.21.png&project-name=Next.js+Chat&repository-name=nextjs-chat&repository-url=https%3A%2F%2Fgithub.com%2Fvercel-labs%2Fai-chatbot&from=templates&skippable-integrations=1&env=OPENAI_API_KEY%2CAUTH_SECRET&envDescription=How+to+get+these+env+vars&envLink=https%3A%2F%2Fgithub.com%2Fvercel-labs%2Fai-chatbot%2Fblob%2Fmain%2F.env.example&teamCreateStatus=hidden&stores=[{"type":"kv"},{"type":"postgres"}])
|
||||
[](https://vercel.com/new/clone?demo-title=Next.js+Chat&demo-description=A+full-featured%2C+hackable+Next.js+AI+chatbot+built+by+Vercel+Labs&demo-url=https%3A%2F%2Fchat.vercel.ai%2F&demo-image=%2F%2Fimages.ctfassets.net%2Fe5382hct74si%2F4aVPvWuTmBvzM5cEdRdqeW%2F4234f9baf160f68ffb385a43c3527645%2FCleanShot_2023-06-16_at_17.09.21.png&project-name=Next.js+Chat&repository-name=nextjs-chat&repository-url=https%3A%2F%2Fgithub.com%2Fvercel-labs%2Fai-chatbot&from=templates&skippable-integrations=1&env=OPENAI_API_KEY%2CAUTH_SECRET&envDescription=How+to+get+these+env+vars&envLink=https%3A%2F%2Fgithub.com%2Fvercel-labs%2Fai-chatbot%2Fblob%2Fmain%2F.env.example&teamCreateStatus=hidden&stores=[{"type":"kv"}])
|
||||
|
||||
## Creating a KV Database Instance
|
||||
|
||||
|
|
@ -57,7 +57,6 @@ You will need to use the environment variables [defined in `.env.example`](.env.
|
|||
|
||||
```bash
|
||||
pnpm install
|
||||
pnpm seed
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,26 @@
|
|||
'use server'
|
||||
|
||||
import { signIn } from '@/auth'
|
||||
import { AuthResult } from '@/lib/types'
|
||||
import { User } from '@/lib/types'
|
||||
import { AuthError } from 'next-auth'
|
||||
import { z } from 'zod'
|
||||
import { kv } from '@vercel/kv'
|
||||
import { ResultCode } from '@/lib/utils'
|
||||
|
||||
export async function getUser(email: string) {
|
||||
const user = await kv.hgetall<User>(`user:${email}`)
|
||||
return user
|
||||
}
|
||||
|
||||
interface Result {
|
||||
type: string
|
||||
resultCode: ResultCode
|
||||
}
|
||||
|
||||
export async function authenticate(
|
||||
_prevState: AuthResult | undefined,
|
||||
_prevState: Result | undefined,
|
||||
formData: FormData
|
||||
) {
|
||||
): Promise<Result | undefined> {
|
||||
try {
|
||||
const email = formData.get('email')
|
||||
const password = formData.get('password')
|
||||
|
|
@ -27,24 +39,33 @@ export async function authenticate(
|
|||
await signIn('credentials', {
|
||||
email,
|
||||
password,
|
||||
redirectTo: '/'
|
||||
redirect: false
|
||||
})
|
||||
|
||||
return {
|
||||
type: 'success',
|
||||
resultCode: ResultCode.UserLoggedIn
|
||||
}
|
||||
} else {
|
||||
return { type: 'error', message: 'Invalid credentials!' }
|
||||
return {
|
||||
type: 'error',
|
||||
resultCode: ResultCode.InvalidCredentials
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof AuthError) {
|
||||
switch (error.type) {
|
||||
case 'CredentialsSignin':
|
||||
return { type: 'error', message: 'Invalid credentials!' }
|
||||
return {
|
||||
type: 'error',
|
||||
resultCode: ResultCode.InvalidCredentials
|
||||
}
|
||||
default:
|
||||
return {
|
||||
type: 'error',
|
||||
message: 'Something went wrong, please try again!'
|
||||
resultCode: ResultCode.UnknownError
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,50 @@
|
|||
'use server'
|
||||
|
||||
import { signIn } from '@/auth'
|
||||
import { db } from '@vercel/postgres'
|
||||
import { getStringFromBuffer } from '@/lib/utils'
|
||||
import { ResultCode, getStringFromBuffer } from '@/lib/utils'
|
||||
import { z } from 'zod'
|
||||
import { AuthResult } from '@/lib/types'
|
||||
import { kv } from '@vercel/kv'
|
||||
import { getUser } from '../login/actions'
|
||||
import { AuthError } from 'next-auth'
|
||||
|
||||
export async function createUser(
|
||||
email: string,
|
||||
hashedPassword: string,
|
||||
salt: string
|
||||
) {
|
||||
const existingUser = await getUser(email)
|
||||
|
||||
if (existingUser) {
|
||||
return {
|
||||
type: 'error',
|
||||
resultCode: ResultCode.UserAlreadyExists
|
||||
}
|
||||
} else {
|
||||
const user = {
|
||||
id: crypto.randomUUID(),
|
||||
email,
|
||||
password: hashedPassword,
|
||||
salt
|
||||
}
|
||||
|
||||
await kv.hmset(`user:${email}`, user)
|
||||
|
||||
return {
|
||||
type: 'success',
|
||||
resultCode: ResultCode.UserCreated
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Result {
|
||||
type: string
|
||||
resultCode: ResultCode
|
||||
}
|
||||
|
||||
export async function signup(
|
||||
_prevState: AuthResult | undefined,
|
||||
_prevState: Result | undefined,
|
||||
formData: FormData
|
||||
) {
|
||||
): Promise<Result | undefined> {
|
||||
const email = formData.get('email') as string
|
||||
const password = formData.get('password') as string
|
||||
|
||||
|
|
@ -34,42 +69,43 @@ export async function signup(
|
|||
)
|
||||
const hashedPassword = getStringFromBuffer(hashedPasswordBuffer)
|
||||
|
||||
const client = await db.connect()
|
||||
|
||||
try {
|
||||
await client.sql`
|
||||
INSERT INTO users (email, password, salt)
|
||||
VALUES (${email}, ${hashedPassword}, ${salt})
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
`
|
||||
const result = await createUser(email, hashedPassword, salt)
|
||||
|
||||
await signIn('credentials', {
|
||||
email,
|
||||
password,
|
||||
redirect: false
|
||||
})
|
||||
if (result.resultCode === ResultCode.UserCreated) {
|
||||
await signIn('credentials', {
|
||||
email,
|
||||
password,
|
||||
redirect: false
|
||||
})
|
||||
}
|
||||
|
||||
return { type: 'success', message: 'Account created!' }
|
||||
return result
|
||||
} catch (error) {
|
||||
const { message } = error as Error
|
||||
|
||||
if (
|
||||
message.startsWith('duplicate key value violates unique constraint')
|
||||
) {
|
||||
return { type: 'error', message: 'User already exists! Please log in.' }
|
||||
if (error instanceof AuthError) {
|
||||
switch (error.type) {
|
||||
case 'CredentialsSignin':
|
||||
return {
|
||||
type: 'error',
|
||||
resultCode: ResultCode.InvalidCredentials
|
||||
}
|
||||
default:
|
||||
return {
|
||||
type: 'error',
|
||||
resultCode: ResultCode.UnknownError
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
type: 'error',
|
||||
message: 'Something went wrong! Please try again.'
|
||||
resultCode: ResultCode.UnknownError
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
client.release()
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
type: 'error',
|
||||
message: 'Invalid entries, please try again!'
|
||||
resultCode: ResultCode.InvalidCredentials
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
19
auth.ts
19
auth.ts
|
|
@ -2,25 +2,8 @@ import NextAuth from 'next-auth'
|
|||
import Credentials from 'next-auth/providers/credentials'
|
||||
import { authConfig } from './auth.config'
|
||||
import { z } from 'zod'
|
||||
import { sql } from '@vercel/postgres'
|
||||
import { getStringFromBuffer } from './lib/utils'
|
||||
|
||||
interface User {
|
||||
id: string
|
||||
name: string
|
||||
email: string
|
||||
password: string
|
||||
salt: string
|
||||
}
|
||||
|
||||
async function getUser(email: string): Promise<User | undefined> {
|
||||
try {
|
||||
const user = await sql<User>`SELECT * FROM users WHERE email=${email}`
|
||||
return user.rows[0]
|
||||
} catch (error) {
|
||||
throw new Error('Failed to fetch user.')
|
||||
}
|
||||
}
|
||||
import { getUser } from './app/login/actions'
|
||||
|
||||
export const { auth, signIn, signOut } = NextAuth({
|
||||
...authConfig,
|
||||
|
|
|
|||
|
|
@ -6,26 +6,30 @@ import Link from 'next/link'
|
|||
import { useEffect } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import { IconSpinner } from './ui/icons'
|
||||
import { getMessageFromCode } from '@/lib/utils'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
export default function LoginForm() {
|
||||
const router = useRouter()
|
||||
const [result, dispatch] = useFormState(authenticate, undefined)
|
||||
|
||||
useEffect(() => {
|
||||
if (result) {
|
||||
if (result.type === 'error') {
|
||||
toast.error(result.message)
|
||||
toast.error(getMessageFromCode(result.resultCode))
|
||||
} else {
|
||||
toast.success(result.message)
|
||||
toast.success(getMessageFromCode(result.resultCode))
|
||||
router.refresh()
|
||||
}
|
||||
}
|
||||
}, [result])
|
||||
}, [result, router])
|
||||
|
||||
return (
|
||||
<form
|
||||
action={dispatch}
|
||||
className="flex flex-col items-center gap-4 space-y-3"
|
||||
>
|
||||
<div className="w-full flex-1 rounded-lg border bg-white px-6 pb-4 pt-8 shadow-md dark:bg-zinc-950 md:w-96">
|
||||
<div className="w-full flex-1 rounded-lg border bg-white px-6 pb-4 pt-8 shadow-md md:w-96 dark:bg-zinc-950">
|
||||
<h1 className="mb-3 text-2xl font-bold">Please log in to continue.</h1>
|
||||
<div className="w-full">
|
||||
<div>
|
||||
|
|
@ -84,7 +88,7 @@ function LoginButton() {
|
|||
|
||||
return (
|
||||
<button
|
||||
className="flex flex-row justify-center items-center my-4 h-10 w-full rounded-md bg-zinc-900 p-2 text-sm font-semibold text-zinc-100 hover:bg-zinc-800 dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-200"
|
||||
className="my-4 flex h-10 w-full flex-row items-center justify-center rounded-md bg-zinc-900 p-2 text-sm font-semibold text-zinc-100 hover:bg-zinc-800 dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-200"
|
||||
aria-disabled={pending}
|
||||
>
|
||||
{pending ? <IconSpinner /> : 'Log in'}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import Link from 'next/link'
|
|||
import { useEffect } from 'react'
|
||||
import { toast } from 'sonner'
|
||||
import { IconSpinner } from './ui/icons'
|
||||
import { getMessageFromCode } from '@/lib/utils'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
export default function SignupForm() {
|
||||
|
|
@ -15,10 +16,10 @@ export default function SignupForm() {
|
|||
useEffect(() => {
|
||||
if (result) {
|
||||
if (result.type === 'error') {
|
||||
toast.error(result.message)
|
||||
toast.error(getMessageFromCode(result.resultCode))
|
||||
} else {
|
||||
toast.success(getMessageFromCode(result.resultCode))
|
||||
router.refresh()
|
||||
toast.success(result.message)
|
||||
}
|
||||
}
|
||||
}, [result, router])
|
||||
|
|
@ -28,7 +29,7 @@ export default function SignupForm() {
|
|||
action={dispatch}
|
||||
className="flex flex-col items-center gap-4 space-y-3"
|
||||
>
|
||||
<div className="w-full flex-1 rounded-lg border bg-white px-6 pb-4 pt-8 shadow-md dark:bg-zinc-950 md:w-96">
|
||||
<div className="w-full flex-1 rounded-lg border bg-white px-6 pb-4 pt-8 shadow-md md:w-96 dark:bg-zinc-950">
|
||||
<h1 className="mb-3 text-2xl font-bold">Sign up for an account!</h1>
|
||||
<div className="w-full">
|
||||
<div>
|
||||
|
|
@ -85,7 +86,7 @@ function LoginButton() {
|
|||
|
||||
return (
|
||||
<button
|
||||
className="flex flex-row justify-center items-center my-4 h-10 w-full rounded-md bg-zinc-900 p-2 text-sm font-semibold text-zinc-100 hover:bg-zinc-800 dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-200"
|
||||
className="my-4 flex h-10 w-full flex-row items-center justify-center rounded-md bg-zinc-900 p-2 text-sm font-semibold text-zinc-100 hover:bg-zinc-800 dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-200"
|
||||
aria-disabled={pending}
|
||||
>
|
||||
{pending ? <IconSpinner /> : 'Create account'}
|
||||
|
|
|
|||
|
|
@ -28,3 +28,10 @@ export interface AuthResult {
|
|||
type: string
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface User extends Record<string, any> {
|
||||
id: string
|
||||
email: string
|
||||
password: string
|
||||
salt: string
|
||||
}
|
||||
|
|
|
|||
26
lib/utils.ts
26
lib/utils.ts
|
|
@ -61,3 +61,29 @@ export const getStringFromBuffer = (buffer: ArrayBuffer) =>
|
|||
Array.from(new Uint8Array(buffer))
|
||||
.map(b => b.toString(16).padStart(2, '0'))
|
||||
.join('')
|
||||
|
||||
export enum ResultCode {
|
||||
InvalidCredentials = 'INVALID_CREDENTIALS',
|
||||
InvalidSubmission = 'INVALID_SUBMISSION',
|
||||
UserAlreadyExists = 'USER_ALREADY_EXISTS',
|
||||
UnknownError = 'UNKNOWN_ERROR',
|
||||
UserCreated = 'USER_CREATED',
|
||||
UserLoggedIn = 'USER_LOGGED_IN'
|
||||
}
|
||||
|
||||
export const getMessageFromCode = (resultCode: string) => {
|
||||
switch (resultCode) {
|
||||
case ResultCode.InvalidCredentials:
|
||||
return 'Invalid credentials!'
|
||||
case ResultCode.InvalidSubmission:
|
||||
return 'Invalid submission, please try again!'
|
||||
case ResultCode.UserAlreadyExists:
|
||||
return 'User already exists, please log in!'
|
||||
case ResultCode.UserCreated:
|
||||
return 'User created, welcome!'
|
||||
case ResultCode.UnknownError:
|
||||
return 'Something went wrong, please try again!'
|
||||
case ResultCode.UserLoggedIn:
|
||||
return 'Logged in!'
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@
|
|||
"@vercel/analytics": "^1.1.2",
|
||||
"@vercel/kv": "^1.0.1",
|
||||
"@vercel/og": "^0.6.2",
|
||||
"@vercel/postgres": "^0.7.2",
|
||||
"ai": "^3.0.12",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"clsx": "^2.1.0",
|
||||
|
|
|
|||
106
pnpm-lock.yaml
generated
106
pnpm-lock.yaml
generated
|
|
@ -44,9 +44,6 @@ dependencies:
|
|||
'@vercel/og':
|
||||
specifier: ^0.6.2
|
||||
version: 0.6.2
|
||||
'@vercel/postgres':
|
||||
specifier: ^0.7.2
|
||||
version: 0.7.2
|
||||
ai:
|
||||
specifier: ^3.0.12
|
||||
version: 3.0.12(react@18.2.0)(solid-js@1.8.11)(svelte@4.2.9)(vue@3.4.15)(zod@3.22.4)
|
||||
|
|
@ -384,12 +381,6 @@ packages:
|
|||
'@jridgewell/resolve-uri': 3.1.1
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
|
||||
/@neondatabase/serverless@0.7.2:
|
||||
resolution: {integrity: sha512-wU3WA2uTyNO7wjPs3Mg0G01jztAxUxzd9/mskMmtPwPTjf7JKWi9AW5/puOGXLxmZ9PVgRFeBVRVYq5nBPhsCg==}
|
||||
dependencies:
|
||||
'@types/pg': 8.6.6
|
||||
dev: false
|
||||
|
||||
/@next/env@14.1.3:
|
||||
resolution: {integrity: sha512-VhgXTvrgeBRxNPjyfBsDIMvgsKDxjlpw4IAUsHCX8Gjl1vtHUYRT3+xfQ/wwvLPDd/6kqfLqk9Pt4+7gysuCKQ==}
|
||||
dev: false
|
||||
|
|
@ -1333,14 +1324,6 @@ packages:
|
|||
dependencies:
|
||||
undici-types: 5.26.5
|
||||
|
||||
/@types/pg@8.6.6:
|
||||
resolution: {integrity: sha512-O2xNmXebtwVekJDD+02udOncjVcMZQuTEQEMpKJ0ZRf5E7/9JJX3izhKUcUifBkyKpljyUM6BTgy2trmviKlpw==}
|
||||
dependencies:
|
||||
'@types/node': 20.11.5
|
||||
pg-protocol: 1.6.0
|
||||
pg-types: 2.2.0
|
||||
dev: false
|
||||
|
||||
/@types/prop-types@15.7.11:
|
||||
resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==}
|
||||
|
||||
|
|
@ -1465,16 +1448,6 @@ packages:
|
|||
yoga-wasm-web: 0.3.3
|
||||
dev: false
|
||||
|
||||
/@vercel/postgres@0.7.2:
|
||||
resolution: {integrity: sha512-IqR/ZAvoPGcPaXl9eWWB5KaA+w/81RzZa/18P4izQRHpNBkTGt9HwGfYi9+wut5UgxNq4QSX9A7HIQR6QDvX2Q==}
|
||||
engines: {node: '>=14.6'}
|
||||
dependencies:
|
||||
'@neondatabase/serverless': 0.7.2
|
||||
bufferutil: 4.0.8
|
||||
utf-8-validate: 6.0.3
|
||||
ws: 8.14.2(bufferutil@4.0.8)(utf-8-validate@6.0.3)
|
||||
dev: false
|
||||
|
||||
/@vue/compiler-core@3.4.15:
|
||||
resolution: {integrity: sha512-XcJQVOaxTKCnth1vCxEChteGuwG6wqnUHxAm1DO3gCz0+uXKaJNx8/digSz4dLALCy8n2lKq24jSUs8segoqIw==}
|
||||
dependencies:
|
||||
|
|
@ -1857,14 +1830,6 @@ packages:
|
|||
update-browserslist-db: 1.0.13(browserslist@4.22.2)
|
||||
dev: true
|
||||
|
||||
/bufferutil@4.0.8:
|
||||
resolution: {integrity: sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==}
|
||||
engines: {node: '>=6.14.2'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
node-gyp-build: 4.8.0
|
||||
dev: false
|
||||
|
||||
/busboy@1.6.0:
|
||||
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
|
||||
engines: {node: '>=10.16.0'}
|
||||
|
|
@ -4125,11 +4090,6 @@ packages:
|
|||
whatwg-url: 5.0.0
|
||||
dev: false
|
||||
|
||||
/node-gyp-build@4.8.0:
|
||||
resolution: {integrity: sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/node-releases@2.0.14:
|
||||
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
|
||||
dev: true
|
||||
|
|
@ -4337,26 +4297,6 @@ packages:
|
|||
is-reference: 3.0.2
|
||||
dev: false
|
||||
|
||||
/pg-int8@1.0.1:
|
||||
resolution: {integrity: sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
dev: false
|
||||
|
||||
/pg-protocol@1.6.0:
|
||||
resolution: {integrity: sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q==}
|
||||
dev: false
|
||||
|
||||
/pg-types@2.2.0:
|
||||
resolution: {integrity: sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==}
|
||||
engines: {node: '>=4'}
|
||||
dependencies:
|
||||
pg-int8: 1.0.1
|
||||
postgres-array: 2.0.0
|
||||
postgres-bytea: 1.0.0
|
||||
postgres-date: 1.0.7
|
||||
postgres-interval: 1.2.0
|
||||
dev: false
|
||||
|
||||
/picocolors@1.0.0:
|
||||
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
||||
|
||||
|
|
@ -4460,28 +4400,6 @@ packages:
|
|||
picocolors: 1.0.0
|
||||
source-map-js: 1.0.2
|
||||
|
||||
/postgres-array@2.0.0:
|
||||
resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
|
||||
engines: {node: '>=4'}
|
||||
dev: false
|
||||
|
||||
/postgres-bytea@1.0.0:
|
||||
resolution: {integrity: sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/postgres-date@1.0.7:
|
||||
resolution: {integrity: sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/postgres-interval@1.2.0:
|
||||
resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dependencies:
|
||||
xtend: 4.0.2
|
||||
dev: false
|
||||
|
||||
/preact-render-to-string@5.2.3(preact@10.11.3):
|
||||
resolution: {integrity: sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==}
|
||||
peerDependencies:
|
||||
|
|
@ -5528,14 +5446,6 @@ packages:
|
|||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/utf-8-validate@6.0.3:
|
||||
resolution: {integrity: sha512-uIuGf9TWQ/y+0Lp+KGZCMuJWc3N9BHA+l/UmHd/oUHwJJDeysyTRxNQVkbzsIWfGFbRe3OcgML/i0mvVRPOyDA==}
|
||||
engines: {node: '>=6.14.2'}
|
||||
requiresBuild: true
|
||||
dependencies:
|
||||
node-gyp-build: 4.8.0
|
||||
dev: false
|
||||
|
||||
/util-deprecate@1.0.2:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
dev: true
|
||||
|
|
@ -5682,22 +5592,6 @@ packages:
|
|||
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||
dev: true
|
||||
|
||||
/ws@8.14.2(bufferutil@4.0.8)(utf-8-validate@6.0.3):
|
||||
resolution: {integrity: sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
peerDependencies:
|
||||
bufferutil: ^4.0.1
|
||||
utf-8-validate: '>=5.0.2'
|
||||
peerDependenciesMeta:
|
||||
bufferutil:
|
||||
optional: true
|
||||
utf-8-validate:
|
||||
optional: true
|
||||
dependencies:
|
||||
bufferutil: 4.0.8
|
||||
utf-8-validate: 6.0.3
|
||||
dev: false
|
||||
|
||||
/xtend@4.0.2:
|
||||
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
|
||||
engines: {node: '>=0.4'}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue