diff --git a/components/dashboard-nav.tsx b/components/dashboard-nav.tsx
index b54d7eb..906765d 100644
--- a/components/dashboard-nav.tsx
+++ b/components/dashboard-nav.tsx
@@ -38,6 +38,7 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
export function DashboardNav() {
const pathname = usePathname()
+ const currentPath = pathname ?? ""
const router = useRouter()
const { toast } = useToast()
const { setTheme, theme } = useTheme()
@@ -69,10 +70,10 @@ export function DashboardNav() {
]
const isActive = (path: string) => {
- if (path === "/dashboard" && pathname === "/dashboard") {
+ if (path === "/dashboard" && currentPath === "/dashboard") {
return true
}
- return path !== "/dashboard" && pathname.startsWith(path)
+ return path !== "/dashboard" && currentPath.startsWith(path)
}
const handleLogout = async () => {
diff --git a/components/dashboard-shell.tsx b/components/dashboard-shell.tsx
index f26ec39..61c065d 100644
--- a/components/dashboard-shell.tsx
+++ b/components/dashboard-shell.tsx
@@ -37,6 +37,7 @@ interface DashboardShellProps {
export function DashboardShell({ children }: DashboardShellProps) {
const router = useRouter()
const pathname = usePathname()
+ const currentPath = pathname ?? ""
const [isMobile, setIsMobile] = useState(false)
const [isOpen, setIsOpen] = useState(false)
@@ -58,10 +59,10 @@ export function DashboardShell({ children }: DashboardShellProps) {
// Check if the menu item should be active
const isActive = (path: string) => {
- if (path === "/dashboard" && pathname === "/dashboard") {
+ if (path === "/dashboard" && currentPath === "/dashboard") {
return true
}
- return path !== "/dashboard" && pathname.startsWith(path)
+ return path !== "/dashboard" && currentPath.startsWith(path)
}
const handleLogout = async () => {
diff --git a/components/providers.tsx b/components/providers.tsx
index 4e0d273..5c38314 100644
--- a/components/providers.tsx
+++ b/components/providers.tsx
@@ -7,6 +7,7 @@ import { ThemeProvider } from "@/components/theme-provider"
import { PageViewTracker } from "@/components/page-view-tracker"
import { CookieBanner } from "@/components/cookie-banner"
import { Logger } from "@/components/logger"
+import { RealtimeProvider } from "@/components/realtime-provider"
import { Toaster } from "@/components/ui/toaster"
interface ProvidersProps {
@@ -19,7 +20,7 @@ export function Providers({ children }: ProvidersProps) {
- {children}
+ {children}
diff --git a/components/realtime-provider.tsx b/components/realtime-provider.tsx
new file mode 100644
index 0000000..1413485
--- /dev/null
+++ b/components/realtime-provider.tsx
@@ -0,0 +1,50 @@
+"use client"
+
+import type { ReactNode } from "react"
+import { useEffect } from "react"
+import { useRouter } from "next/navigation"
+import { getRealtimeClient } from "@/lib/realtime-client"
+
+interface RealtimeProviderProps {
+ children: ReactNode
+}
+
+export function RealtimeProvider({ children }: RealtimeProviderProps) {
+ const router = useRouter()
+
+ useEffect(() => {
+ const controller = new AbortController()
+
+ fetch("/api/socket/io", { signal: controller.signal }).catch(() => {
+ // The socket route will respond immediately once the server is ready.
+ })
+
+ const socket = getRealtimeClient()
+
+ if (!socket) {
+ controller.abort()
+ return
+ }
+
+ const handleEvent = () => {
+ router.refresh()
+ }
+
+ const handleConnect = () => {
+ router.refresh()
+ }
+
+ socket.on("connect", handleConnect)
+
+ socket.on("realtime:event", handleEvent)
+
+ return () => {
+ controller.abort()
+ socket.off("realtime:event", handleEvent)
+ socket.off("connect", handleConnect)
+ }
+ }, [router])
+
+ return <>{children}>
+}
+
diff --git a/components/unsubscribe-client.tsx b/components/unsubscribe-client.tsx
index 0879ad4..95a4ef2 100644
--- a/components/unsubscribe-client.tsx
+++ b/components/unsubscribe-client.tsx
@@ -17,7 +17,7 @@ import {
export default function UnsubscribeClient() {
const searchParams = useSearchParams()
- const token = searchParams.get("token")
+ const token = searchParams?.get("token") ?? null
const [status, setStatus] = useState<
"verifying" | "ready" | "processing" | "success" | "error" | "preferences"
>(token ? "verifying" : "error")
diff --git a/lib/db.ts b/lib/db.ts
index c45f426..d9b9787 100644
--- a/lib/db.ts
+++ b/lib/db.ts
@@ -1,5 +1,7 @@
import type { PrismaClient as PrismaClientType } from "@prisma/client"
+import { registerPrismaRealtime } from "@/lib/realtime-events"
+
type PrismaClientConstructor = new (...args: unknown[]) => PrismaClientType
const globalForPrisma = globalThis as unknown as { prisma?: PrismaClientType }
@@ -108,6 +110,10 @@ const prisma =
})
: createStubClient())
+if (PrismaClientCtor) {
+ registerPrismaRealtime(prisma as PrismaClientType)
+}
+
if (process.env.NODE_ENV !== "production") {
globalForPrisma.prisma = prisma
}
diff --git a/lib/realtime-client.ts b/lib/realtime-client.ts
new file mode 100644
index 0000000..7df9c10
--- /dev/null
+++ b/lib/realtime-client.ts
@@ -0,0 +1,24 @@
+"use client"
+
+import { io, type Socket } from "socket.io-client"
+
+let socketInstance: Socket | null = null
+
+export function getRealtimeClient() {
+ if (typeof window === "undefined") {
+ return null
+ }
+
+ if (!socketInstance) {
+ socketInstance = io({
+ path: "/api/socket/io",
+ transports: ["websocket", "polling"],
+ autoConnect: true,
+ reconnection: true,
+ reconnectionDelay: 1000,
+ reconnectionDelayMax: 5000,
+ })
+ }
+
+ return socketInstance
+}
diff --git a/lib/realtime-events.ts b/lib/realtime-events.ts
new file mode 100644
index 0000000..fa820e6
--- /dev/null
+++ b/lib/realtime-events.ts
@@ -0,0 +1,78 @@
+import type { PrismaClient } from "@prisma/client"
+
+import { enqueueRealtimeEvent } from "@/lib/realtime-queue"
+
+type PrismaMiddleware = Parameters[0]
+type PrismaMiddlewareParams = Parameters[0]
+type PrismaMiddlewareNext = Parameters[1]
+
+const MUTATION_ACTIONS = new Set([
+ "create",
+ "createMany",
+ "update",
+ "updateMany",
+ "upsert",
+ "delete",
+ "deleteMany",
+])
+
+const globalForRealtime = globalThis as unknown as {
+ prismaRealtimeRegistered?: boolean
+}
+
+export function registerPrismaRealtime(prisma: PrismaClient) {
+ if (globalForRealtime.prismaRealtimeRegistered) {
+ return
+ }
+
+ const middlewareCapablePrisma = prisma as PrismaClient & {
+ $use?: PrismaClient["$use"]
+ }
+
+ if (typeof middlewareCapablePrisma.$use !== "function") {
+ console.warn("Prisma client does not support middleware; skipping realtime registration")
+ globalForRealtime.prismaRealtimeRegistered = true
+ return
+ }
+
+ const realtimeMiddleware: PrismaMiddleware = async (
+ params: PrismaMiddlewareParams,
+ next: PrismaMiddlewareNext,
+ ) => {
+ const result = await next(params)
+
+ if (MUTATION_ACTIONS.has(params.action)) {
+ const model = params.model ?? "unknown"
+
+ try {
+ const enqueuePromise = enqueueRealtimeEvent(`prisma:${model}:${params.action}`, {
+ model,
+ action: params.action,
+ args: params.args,
+ result,
+ timestamp: Date.now(),
+ })
+
+ void enqueuePromise.catch(error => {
+ console.error("Failed to enqueue realtime Prisma event", error)
+ })
+ } catch (error) {
+ console.error("Failed to enqueue realtime Prisma event", error)
+ }
+ }
+
+ return result
+ }
+
+ middlewareCapablePrisma.$use(realtimeMiddleware)
+
+ globalForRealtime.prismaRealtimeRegistered = true
+}
+
+export async function broadcastRealtimeEvent(event: string, payload: unknown) {
+ try {
+ await enqueueRealtimeEvent(event, payload)
+ } catch (error) {
+ console.error("Failed to enqueue realtime event", error)
+ }
+}
diff --git a/lib/realtime-queue.ts b/lib/realtime-queue.ts
new file mode 100644
index 0000000..c925308
--- /dev/null
+++ b/lib/realtime-queue.ts
@@ -0,0 +1,89 @@
+import { Queue, Worker } from "bullmq"
+import type { JobsOptions } from "bullmq"
+
+import { createRedisConnection, getRedisUrl } from "@/lib/redis"
+import { getRealtimeIO } from "@/lib/realtime"
+
+const queueName = "realtime-events"
+
+const globalForQueue = globalThis as unknown as {
+ realtimeQueue?: Queue
+ realtimeWorker?: Worker
+ realtimeQueueConnection?: ReturnType
+ realtimeWorkerConnection?: ReturnType
+}
+
+function getQueueConnection() {
+ if (!globalForQueue.realtimeQueueConnection) {
+ globalForQueue.realtimeQueueConnection = createRedisConnection()
+ }
+
+ return globalForQueue.realtimeQueueConnection
+}
+
+function getWorkerConnection() {
+ if (!globalForQueue.realtimeWorkerConnection) {
+ globalForQueue.realtimeWorkerConnection = createRedisConnection()
+ }
+
+ return globalForQueue.realtimeWorkerConnection
+}
+
+export function getRealtimeQueue() {
+ const connection = getQueueConnection()
+
+ if (!globalForQueue.realtimeQueue) {
+ globalForQueue.realtimeQueue = new Queue(queueName, {
+ connection,
+ })
+ }
+
+ return globalForQueue.realtimeQueue
+}
+
+function ensureWorker() {
+ const connection = getWorkerConnection()
+
+ if (!globalForQueue.realtimeWorker) {
+ globalForQueue.realtimeWorker = new Worker(
+ queueName,
+ async job => {
+ const io = getRealtimeIO()
+
+ if (io) {
+ io.emit("realtime:event", {
+ event: job.name,
+ payload: job.data,
+ jobId: job.id,
+ timestamp: Date.now(),
+ })
+
+ io.emit(job.name, job.data)
+ }
+ },
+ { connection },
+ )
+
+ globalForQueue.realtimeWorker.on("error", error => {
+ console.error("Realtime worker error", error)
+ })
+ }
+}
+
+export async function enqueueRealtimeEvent(event: string, payload: unknown, options?: JobsOptions) {
+ const queue = getRealtimeQueue()
+ ensureWorker()
+
+ await queue.add(event, payload, {
+ removeOnComplete: { age: 60, count: 1000 },
+ removeOnFail: { age: 60 * 60, count: 100 },
+ ...options,
+ })
+}
+
+export function describeRealtimeQueue() {
+ return {
+ name: queueName,
+ redis: getRedisUrl(),
+ }
+}
diff --git a/lib/realtime.ts b/lib/realtime.ts
new file mode 100644
index 0000000..0d17537
--- /dev/null
+++ b/lib/realtime.ts
@@ -0,0 +1,14 @@
+import type { Server as IOServer } from "socket.io"
+
+const globalForRealtime = globalThis as unknown as {
+ realtimeIO?: IOServer
+}
+
+export function getRealtimeIO() {
+ return globalForRealtime.realtimeIO
+}
+
+export function setRealtimeIO(io: IOServer) {
+ globalForRealtime.realtimeIO = io
+ return io
+}
diff --git a/lib/redis.ts b/lib/redis.ts
new file mode 100644
index 0000000..2bf5cc0
--- /dev/null
+++ b/lib/redis.ts
@@ -0,0 +1,28 @@
+import IORedis from "ioredis"
+
+const redisUrl = process.env.REDIS_URL || "redis://127.0.0.1:6379"
+
+const baseOptions = {
+ maxRetriesPerRequest: null as number | null,
+ enableReadyCheck: true,
+}
+
+const globalForRedis = globalThis as unknown as {
+ redisConnection?: IORedis
+}
+
+export function createRedisConnection() {
+ return new IORedis(redisUrl, baseOptions)
+}
+
+export function getRedisConnection() {
+ if (!globalForRedis.redisConnection) {
+ globalForRedis.redisConnection = createRedisConnection()
+ }
+
+ return globalForRedis.redisConnection
+}
+
+export function getRedisUrl() {
+ return redisUrl
+}
diff --git a/next-env.d.ts b/next-env.d.ts
index 1b3be08..3cd7048 100644
--- a/next-env.d.ts
+++ b/next-env.d.ts
@@ -1,5 +1,6 @@
///
///
+///
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/package.json b/package.json
index fd8d22f..f847077 100644
--- a/package.json
+++ b/package.json
@@ -59,6 +59,7 @@
"@react-three/fiber": "^9.0.0",
"autoprefixer": "^10.4.20",
"bcryptjs": "3.0.2",
+ "bullmq": "^5.63.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"cmdk": "1.0.4",
@@ -67,6 +68,7 @@
"framer-motion": "^11.11.7",
"immer": "10.1.3",
"input-otp": "1.4.1",
+ "ioredis": "^5.8.2",
"lucide-react": "^0.454.0",
"next": "15.4.7",
"next-auth": "4.24.13",
@@ -80,6 +82,8 @@
"react-resizable-panels": "^2.1.7",
"react-syntax-highlighter": "^16.1.0",
"recharts": "2.15.0",
+ "socket.io": "^4.8.1",
+ "socket.io-client": "^4.8.1",
"sonner": "^1.7.1",
"swr": "^2.3.3",
"tailwind-merge": "^2.5.5",
diff --git a/pages/api/socket/io.ts b/pages/api/socket/io.ts
new file mode 100644
index 0000000..a9c4ebb
--- /dev/null
+++ b/pages/api/socket/io.ts
@@ -0,0 +1,48 @@
+import type { NextApiRequest } from "next"
+import { Server as IOServer } from "socket.io"
+
+import type { NextApiResponseServerIO } from "@/types/next"
+import { getRealtimeIO, setRealtimeIO } from "@/lib/realtime"
+
+export const config = {
+ api: {
+ bodyParser: false,
+ },
+}
+
+export default function handler(req: NextApiRequest, res: NextApiResponseServerIO) {
+ const existingServer = res.socket.server as typeof res.socket.server & { io?: IOServer }
+
+ if (!existingServer.io) {
+ const io = new IOServer(res.socket.server, {
+ path: "/api/socket/io",
+ cors: {
+ origin: process.env.NEXT_PUBLIC_SITE_URL || "*",
+ },
+ })
+
+ setRealtimeIO(io)
+ existingServer.io = io
+
+ io.on("connection", socket => {
+ socket.emit("realtime:event", {
+ event: "socket:connected",
+ socketId: socket.id,
+ timestamp: Date.now(),
+ })
+
+ socket.on("disconnect", reason => {
+ socket.broadcast.emit("realtime:event", {
+ event: "socket:disconnected",
+ socketId: socket.id,
+ reason,
+ timestamp: Date.now(),
+ })
+ })
+ })
+ } else if (!getRealtimeIO()) {
+ setRealtimeIO(existingServer.io)
+ }
+
+ res.end()
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8aca571..9e8091e 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -119,6 +119,9 @@ importers:
bcryptjs:
specifier: 3.0.2
version: 3.0.2
+ bullmq:
+ specifier: ^5.63.0
+ version: 5.63.0
class-variance-authority:
specifier: ^0.7.1
version: 0.7.1
@@ -143,6 +146,9 @@ importers:
input-otp:
specifier: 1.4.1
version: 1.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ ioredis:
+ specifier: ^5.8.2
+ version: 5.8.2
lucide-react:
specifier: ^0.454.0
version: 0.454.0(react@18.3.1)
@@ -182,6 +188,12 @@ importers:
recharts:
specifier: 2.15.0
version: 2.15.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
+ socket.io:
+ specifier: ^4.8.1
+ version: 4.8.1
+ socket.io-client:
+ specifier: ^4.8.1
+ version: 4.8.1
sonner:
specifier: ^1.7.1
version: 1.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -710,6 +722,9 @@ packages:
cpu: [x64]
os: [win32]
+ '@ioredis/commands@1.4.0':
+ resolution: {integrity: sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==}
+
'@isaacs/cliui@8.0.2':
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
engines: {node: '>=12'}
@@ -738,6 +753,36 @@ packages:
peerDependencies:
three: '>= 0.159.0'
+ '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3':
+ resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3':
+ resolution: {integrity: sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==}
+ cpu: [x64]
+ os: [darwin]
+
+ '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3':
+ resolution: {integrity: sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==}
+ cpu: [arm64]
+ os: [linux]
+
+ '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3':
+ resolution: {integrity: sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==}
+ cpu: [arm]
+ os: [linux]
+
+ '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3':
+ resolution: {integrity: sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==}
+ cpu: [x64]
+ os: [linux]
+
+ '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3':
+ resolution: {integrity: sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==}
+ cpu: [x64]
+ os: [win32]
+
'@next/env@15.4.7':
resolution: {integrity: sha512-PrBIpO8oljZGTOe9HH0miix1w5MUiGJ/q83Jge03mHEE0E3pyqzAy2+l5G6aJDbXoobmxPJTVhbCuwlLtjSHwg==}
@@ -1714,6 +1759,9 @@ packages:
'@so-ric/colorspace@1.1.6':
resolution: {integrity: sha512-/KiKkpHNOBgkFJwu9sh48LkHSMYGyuTcSFK/qMBdnOAlrRJzRSXAOFB5qwzaVQuDl8wAvHVMkaASQDReTahxuw==}
+ '@socket.io/component-emitter@3.1.2':
+ resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
+
'@standard-schema/spec@1.0.0':
resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==}
@@ -1726,6 +1774,9 @@ packages:
'@types/cookie@0.6.0':
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
+ '@types/cors@2.8.19':
+ resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==}
+
'@types/d3-array@3.2.2':
resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==}
@@ -1889,6 +1940,10 @@ packages:
'@webgpu/types@0.1.66':
resolution: {integrity: sha512-YA2hLrwLpDsRueNDXIMqN9NTzD6bCDkuXbOSe0heS+f8YE8usA6Gbv1prj81pzVHrbaAma7zObnIC+I6/sXJgA==}
+ accepts@1.3.8:
+ resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
+ engines: {node: '>= 0.6'}
+
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@@ -1951,6 +2006,10 @@ packages:
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
+ base64id@2.0.0:
+ resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==}
+ engines: {node: ^4.5.0 || >= 5.9}
+
baseline-browser-mapping@2.8.18:
resolution: {integrity: sha512-UYmTpOBwgPScZpS4A+YbapwWuBwasxvO/2IOHArSsAhL/+ZdmATBXTex3t+l2hXwLVYK382ibr/nKoY9GKe86w==}
hasBin: true
@@ -1987,6 +2046,9 @@ packages:
buffer@6.0.3:
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
+ bullmq@5.63.0:
+ resolution: {integrity: sha512-HT1iM3Jt4bZeg3Ru/MxrOy2iIItxcl1Pz5Ync1Vrot70jBpVguMxFEiSaDU57BwYwR4iwnObDnzct2lirKkX5A==}
+
c12@3.1.0:
resolution: {integrity: sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==}
peerDependencies:
@@ -2048,6 +2110,10 @@ packages:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
+ cluster-key-slot@1.1.2:
+ resolution: {integrity: sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==}
+ engines: {node: '>=0.10.0'}
+
cmdk@1.0.4:
resolution: {integrity: sha512-AnsjfHyHpQ/EFeAnG216WY7A5LiYCoZzCSygiLvfXC3H3LFGCprErteUcszaVluGOhuOTbJS3jWHrSDYPBBygg==}
peerDependencies:
@@ -2101,6 +2167,14 @@ packages:
resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
engines: {node: '>= 0.6'}
+ cors@2.8.5:
+ resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
+ engines: {node: '>= 0.10'}
+
+ cron-parser@4.9.0:
+ resolution: {integrity: sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q==}
+ engines: {node: '>=12.0.0'}
+
cross-env@7.0.3:
resolution: {integrity: sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==}
engines: {node: '>=10.14', npm: '>=6', yarn: '>=1'}
@@ -2168,6 +2242,15 @@ packages:
date-fns@4.1.0:
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
+ debug@4.3.7:
+ resolution: {integrity: sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'}
@@ -2193,6 +2276,10 @@ packages:
defu@6.1.4:
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
+ denque@2.1.0:
+ resolution: {integrity: sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==}
+ engines: {node: '>=0.10'}
+
dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
@@ -2261,6 +2348,17 @@ packages:
enabled@2.0.0:
resolution: {integrity: sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==}
+ engine.io-client@6.6.3:
+ resolution: {integrity: sha512-T0iLjnyNWahNyv/lcjS2y4oE358tVS/SYQNxYXGAJ9/GLgH4VCvOQ/mhTjqU88mLZCQgiG8RIegFHYCdVC+j5w==}
+
+ engine.io-parser@5.2.3:
+ resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==}
+ engines: {node: '>=10.0.0'}
+
+ engine.io@6.6.4:
+ resolution: {integrity: sha512-ZCkIjSYNDyGn0R6ewHDtXgns/Zre/NT6Agvq1/WobF7JXgFff4SeDroKiCO3fNJreU9YG429Sc81o4w5ok/W5g==}
+ engines: {node: '>=10.2.0'}
+
escalade@3.2.0:
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
engines: {node: '>=6'}
@@ -2538,6 +2636,10 @@ packages:
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
engines: {node: '>=12'}
+ ioredis@5.8.2:
+ resolution: {integrity: sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q==}
+ engines: {node: '>=12.22.0'}
+
is-alphabetical@2.0.1:
resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==}
@@ -2659,6 +2761,12 @@ packages:
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
engines: {node: '>=10'}
+ lodash.defaults@4.2.0:
+ resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==}
+
+ lodash.isarguments@3.1.0:
+ resolution: {integrity: sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg==}
+
lodash.merge@4.6.2:
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
@@ -2691,6 +2799,10 @@ packages:
peerDependencies:
react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc
+ luxon@3.7.2:
+ resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==}
+ engines: {node: '>=12'}
+
maath@0.10.8:
resolution: {integrity: sha512-tRvbDF0Pgqz+9XUa4jjfgAQ8/aPKmQdWXilFu2tMy4GWj4NOsx99HlULO4IeREfbO3a0sA145DZYyvXPkybm0g==}
peerDependencies:
@@ -2713,6 +2825,14 @@ packages:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
+ mime-db@1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
@@ -2733,6 +2853,13 @@ packages:
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+ msgpackr-extract@3.0.3:
+ resolution: {integrity: sha512-P0efT1C9jIdVRefqjzOQ9Xml57zpOXnIuS+csaB4MdZbTdmGDLo8XhzBG1N7aO11gKDDkJvBLULeFTo46wwreA==}
+ hasBin: true
+
+ msgpackr@1.11.5:
+ resolution: {integrity: sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==}
+
mz@2.7.0:
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
@@ -2744,6 +2871,10 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+ negotiator@0.6.3:
+ resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
+ engines: {node: '>= 0.6'}
+
next-auth@4.24.13:
resolution: {integrity: sha512-sgObCfcfL7BzIK76SS5TnQtc3yo2Oifp/yIpfv6fMfeBOiBJkDWF3A2y9+yqnmJ4JKc2C+nMjSjmgDeTwgN1rQ==}
peerDependencies:
@@ -2785,9 +2916,16 @@ packages:
sass:
optional: true
+ node-abort-controller@3.1.1:
+ resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==}
+
node-fetch-native@1.6.7:
resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==}
+ node-gyp-build-optional-packages@5.2.2:
+ resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==}
+ hasBin: true
+
node-releases@2.0.26:
resolution: {integrity: sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==}
@@ -3155,6 +3293,14 @@ packages:
react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ redis-errors@1.2.0:
+ resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==}
+ engines: {node: '>=4'}
+
+ redis-parser@3.0.0:
+ resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==}
+ engines: {node: '>=4'}
+
refractor@5.0.0:
resolution: {integrity: sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==}
@@ -3216,6 +3362,21 @@ packages:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
+ socket.io-adapter@2.5.5:
+ resolution: {integrity: sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==}
+
+ socket.io-client@4.8.1:
+ resolution: {integrity: sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==}
+ engines: {node: '>=10.0.0'}
+
+ socket.io-parser@4.2.4:
+ resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==}
+ engines: {node: '>=10.0.0'}
+
+ socket.io@4.8.1:
+ resolution: {integrity: sha512-oZ7iUCxph8WYRHHcjBEc9unw3adt5CmSNlppj/5Q4k2RIrhl8Z5yY2Xr4j9zj0+wzVZ0bxmYoGSzKJnRl6A4yg==}
+ engines: {node: '>=10.2.0'}
+
sonner@1.7.4:
resolution: {integrity: sha512-DIS8z4PfJRbIyfVFDVnK9rO3eYDtse4Omcm6bt0oEr5/jtLgysmjuBl1frJ9E/EQZrFmKx2A8m/s5s9CRXIzhw==}
peerDependencies:
@@ -3232,6 +3393,9 @@ packages:
stack-trace@0.0.10:
resolution: {integrity: sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==}
+ standard-as-callback@2.1.0:
+ resolution: {integrity: sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==}
+
stats-gl@2.4.2:
resolution: {integrity: sha512-g5O9B0hm9CvnM36+v7SFl39T7hmAlv541tU81ME8YeSb3i1CIP5/QdDeSB3A0la0bKNHpxpwxOVRo2wFTYEosQ==}
peerDependencies:
@@ -3442,10 +3606,18 @@ packages:
resolution: {integrity: sha512-6Z7Ma2aVEWisaL6TvBCy7P8rm2LQoPv6dJ7ecIaIixHcwfbJ0x7mWdbcwlIM5IGQxPZSFYeqRCqlOOeKoJYMkw==}
engines: {node: '>= 4'}
+ uuid@11.1.0:
+ resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==}
+ hasBin: true
+
uuid@8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
+ vary@1.1.2:
+ resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
+ engines: {node: '>= 0.8'}
+
vaul@0.9.9:
resolution: {integrity: sha512-7afKg48srluhZwIkaU+lgGtFCUsYBSGOl8vcc8N/M3YQlZFlynHD15AE+pwrYdc826o7nrIND4lL9Y6b9WWZZQ==}
peerDependencies:
@@ -3486,6 +3658,22 @@ packages:
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
engines: {node: '>=12'}
+ ws@8.17.1:
+ resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==}
+ 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
+
+ xmlhttprequest-ssl@2.1.2:
+ resolution: {integrity: sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==}
+ engines: {node: '>=0.4.0'}
+
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
@@ -4214,6 +4402,8 @@ snapshots:
'@img/sharp-win32-x64@0.34.4':
optional: true
+ '@ioredis/commands@1.4.0': {}
+
'@isaacs/cliui@8.0.2':
dependencies:
string-width: 5.1.2
@@ -4249,6 +4439,24 @@ snapshots:
promise-worker-transferable: 1.0.4
three: 0.170.0
+ '@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3':
+ optional: true
+
+ '@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.3':
+ optional: true
+
+ '@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.3':
+ optional: true
+
+ '@msgpackr-extract/msgpackr-extract-linux-arm@3.0.3':
+ optional: true
+
+ '@msgpackr-extract/msgpackr-extract-linux-x64@3.0.3':
+ optional: true
+
+ '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.3':
+ optional: true
+
'@next/env@15.4.7': {}
'@next/eslint-plugin-next@15.4.7':
@@ -5382,6 +5590,8 @@ snapshots:
color: 5.0.2
text-hex: 1.0.0
+ '@socket.io/component-emitter@3.1.2': {}
+
'@standard-schema/spec@1.0.0': {}
'@swc/helpers@0.5.15':
@@ -5392,6 +5602,10 @@ snapshots:
'@types/cookie@0.6.0': {}
+ '@types/cors@2.8.19':
+ dependencies:
+ '@types/node': 22.18.12
+
'@types/d3-array@3.2.2': {}
'@types/d3-color@3.1.3': {}
@@ -5585,6 +5799,11 @@ snapshots:
'@webgpu/types@0.1.66': {}
+ accepts@1.3.8:
+ dependencies:
+ mime-types: 2.1.35
+ negotiator: 0.6.3
+
acorn-jsx@5.3.2(acorn@8.15.0):
dependencies:
acorn: 8.15.0
@@ -5639,6 +5858,8 @@ snapshots:
base64-js@1.5.1: {}
+ base64id@2.0.0: {}
+
baseline-browser-mapping@2.8.18: {}
bcryptjs@3.0.2: {}
@@ -5677,6 +5898,18 @@ snapshots:
base64-js: 1.5.1
ieee754: 1.2.1
+ bullmq@5.63.0:
+ dependencies:
+ cron-parser: 4.9.0
+ ioredis: 5.8.2
+ msgpackr: 1.11.5
+ node-abort-controller: 3.1.1
+ semver: 7.7.3
+ tslib: 2.8.1
+ uuid: 11.1.0
+ transitivePeerDependencies:
+ - supports-color
+
c12@3.1.0:
dependencies:
chokidar: 4.0.3
@@ -5743,6 +5976,8 @@ snapshots:
clsx@2.1.1: {}
+ cluster-key-slot@1.1.2: {}
+
cmdk@1.0.4(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -5790,6 +6025,15 @@ snapshots:
cookie@0.7.2: {}
+ cors@2.8.5:
+ dependencies:
+ object-assign: 4.1.1
+ vary: 1.1.2
+
+ cron-parser@4.9.0:
+ dependencies:
+ luxon: 3.7.2
+
cross-env@7.0.3:
dependencies:
cross-spawn: 7.0.6
@@ -5846,6 +6090,10 @@ snapshots:
date-fns@4.1.0: {}
+ debug@4.3.7:
+ dependencies:
+ ms: 2.1.3
+
debug@4.4.3:
dependencies:
ms: 2.1.3
@@ -5862,6 +6110,8 @@ snapshots:
defu@6.1.4: {}
+ denque@2.1.0: {}
+
dequal@2.0.3: {}
destr@2.0.5: {}
@@ -5917,6 +6167,36 @@ snapshots:
enabled@2.0.0: {}
+ engine.io-client@6.6.3:
+ dependencies:
+ '@socket.io/component-emitter': 3.1.2
+ debug: 4.3.7
+ engine.io-parser: 5.2.3
+ ws: 8.17.1
+ xmlhttprequest-ssl: 2.1.2
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ engine.io-parser@5.2.3: {}
+
+ engine.io@6.6.4:
+ dependencies:
+ '@types/cors': 2.8.19
+ '@types/node': 22.18.12
+ accepts: 1.3.8
+ base64id: 2.0.0
+ cookie: 0.7.2
+ cors: 2.8.5
+ debug: 4.3.7
+ engine.io-parser: 5.2.3
+ ws: 8.17.1
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
escalade@3.2.0: {}
escape-string-regexp@4.0.0: {}
@@ -6197,6 +6477,20 @@ snapshots:
internmap@2.0.3: {}
+ ioredis@5.8.2:
+ dependencies:
+ '@ioredis/commands': 1.4.0
+ cluster-key-slot: 1.1.2
+ debug: 4.4.3
+ denque: 2.1.0
+ lodash.defaults: 4.2.0
+ lodash.isarguments: 3.1.0
+ redis-errors: 1.2.0
+ redis-parser: 3.0.0
+ standard-as-callback: 2.1.0
+ transitivePeerDependencies:
+ - supports-color
+
is-alphabetical@2.0.1: {}
is-alphanumerical@2.0.1:
@@ -6294,6 +6588,10 @@ snapshots:
dependencies:
p-locate: 5.0.0
+ lodash.defaults@4.2.0: {}
+
+ lodash.isarguments@3.1.0: {}
+
lodash.merge@4.6.2: {}
lodash@4.17.21: {}
@@ -6330,6 +6628,8 @@ snapshots:
dependencies:
react: 18.3.1
+ luxon@3.7.2: {}
+
maath@0.10.8(@types/three@0.181.0)(three@0.170.0):
dependencies:
'@types/three': 0.181.0
@@ -6348,6 +6648,12 @@ snapshots:
braces: 3.0.3
picomatch: 2.3.1
+ mime-db@1.52.0: {}
+
+ mime-types@2.1.35:
+ dependencies:
+ mime-db: 1.52.0
+
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.12
@@ -6366,6 +6672,22 @@ snapshots:
ms@2.1.3: {}
+ msgpackr-extract@3.0.3:
+ dependencies:
+ node-gyp-build-optional-packages: 5.2.2
+ optionalDependencies:
+ '@msgpackr-extract/msgpackr-extract-darwin-arm64': 3.0.3
+ '@msgpackr-extract/msgpackr-extract-darwin-x64': 3.0.3
+ '@msgpackr-extract/msgpackr-extract-linux-arm': 3.0.3
+ '@msgpackr-extract/msgpackr-extract-linux-arm64': 3.0.3
+ '@msgpackr-extract/msgpackr-extract-linux-x64': 3.0.3
+ '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.3
+ optional: true
+
+ msgpackr@1.11.5:
+ optionalDependencies:
+ msgpackr-extract: 3.0.3
+
mz@2.7.0:
dependencies:
any-promise: 1.3.0
@@ -6376,6 +6698,8 @@ snapshots:
natural-compare@1.4.0: {}
+ negotiator@0.6.3: {}
+
next-auth@4.24.13(@auth/core@0.34.3(nodemailer@7.0.7))(next@15.4.7(@babel/core@7.28.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nodemailer@7.0.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@babel/runtime': 7.28.4
@@ -6422,8 +6746,15 @@ snapshots:
- '@babel/core'
- babel-plugin-macros
+ node-abort-controller@3.1.1: {}
+
node-fetch-native@1.6.7: {}
+ node-gyp-build-optional-packages@5.2.2:
+ dependencies:
+ detect-libc: 2.1.2
+ optional: true
+
node-releases@2.0.26: {}
nodemailer@7.0.7: {}
@@ -6768,6 +7099,12 @@ snapshots:
tiny-invariant: 1.3.3
victory-vendor: 36.9.2
+ redis-errors@1.2.0: {}
+
+ redis-parser@3.0.0:
+ dependencies:
+ redis-errors: 1.2.0
+
refractor@5.0.0:
dependencies:
'@types/hast': 3.0.4
@@ -6843,6 +7180,47 @@ snapshots:
signal-exit@4.1.0: {}
+ socket.io-adapter@2.5.5:
+ dependencies:
+ debug: 4.3.7
+ ws: 8.17.1
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ socket.io-client@4.8.1:
+ dependencies:
+ '@socket.io/component-emitter': 3.1.2
+ debug: 4.3.7
+ engine.io-client: 6.6.3
+ socket.io-parser: 4.2.4
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ socket.io-parser@4.2.4:
+ dependencies:
+ '@socket.io/component-emitter': 3.1.2
+ debug: 4.3.7
+ transitivePeerDependencies:
+ - supports-color
+
+ socket.io@4.8.1:
+ dependencies:
+ accepts: 1.3.8
+ base64id: 2.0.0
+ cors: 2.8.5
+ debug: 4.3.7
+ engine.io: 6.6.4
+ socket.io-adapter: 2.5.5
+ socket.io-parser: 4.2.4
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
sonner@1.7.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
react: 18.3.1
@@ -6854,6 +7232,8 @@ snapshots:
stack-trace@0.0.10: {}
+ standard-as-callback@2.1.0: {}
+
stats-gl@2.4.2(@types/three@0.181.0)(three@0.170.0):
dependencies:
'@types/three': 0.181.0
@@ -7074,8 +7454,12 @@ snapshots:
utility-types@3.11.0: {}
+ uuid@11.1.0: {}
+
uuid@8.3.2: {}
+ vary@1.1.2: {}
+
vaul@0.9.9(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@radix-ui/react-dialog': 1.1.15(@types/react-dom@19.2.2(@types/react@19.2.2))(@types/react@19.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -7144,6 +7528,10 @@ snapshots:
string-width: 5.1.2
strip-ansi: 7.1.2
+ ws@8.17.1: {}
+
+ xmlhttprequest-ssl@2.1.2: {}
+
yallist@3.1.1: {}
yallist@4.0.0: {}
diff --git a/types/next.d.ts b/types/next.d.ts
new file mode 100644
index 0000000..038318e
--- /dev/null
+++ b/types/next.d.ts
@@ -0,0 +1,11 @@
+import type { Server as HttpServer } from "http"
+import type { Server as IOServer } from "socket.io"
+import type { NextApiResponse } from "next"
+
+export type NextApiResponseServerIO = NextApiResponse & {
+ socket: NextApiResponse["socket"] & {
+ server: HttpServer & {
+ io?: IOServer
+ }
+ }
+}