[READ-ONLY] One Calendar is a privacy-first calendar web app built with Next.js. It has modern security features, including e2ee, password-protected sharing, and self-destructing share links ๐Ÿ“… calendar.xyehr.cn
nextjs
0

Configure Feed

Select the types of activity you want to include in your feed.

fix: add tamper-evident evlog audit logging

+397 -66
+2
.gitignore
··· 44 44 .turbo 45 45 46 46 .env*.local 47 + .audit/ 48 + .evlog/
+21 -4
app/api/account/route.ts
··· 1 1 import { NextResponse } from 'next/server' 2 + import { withEvlog, useLogger } from '@/lib/evlog' 2 3 import { getServerSession } from '@/lib/auth-server' 3 4 import { db } from '@/lib/drizzle/client' 4 5 import { shares, calendarBackups } from '@/lib/drizzle/schema' ··· 6 7 7 8 export const runtime = 'nodejs' 8 9 9 - export async function DELETE() { 10 + export const DELETE = withEvlog(async function DELETE(_request: Request) { 10 11 try { 12 + const log = useLogger() 11 13 const session = await getServerSession() 12 14 const user = session?.user 13 - if (!user) 15 + if (!user) { 16 + log.audit?.({ 17 + action: 'account.delete', 18 + actor: { type: 'system', id: 'anonymous' }, 19 + target: { type: 'account', id: 'unknown' }, 20 + outcome: 'denied', 21 + reason: 'Authentication required', 22 + }) 14 23 return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) 24 + } 15 25 16 26 await db.transaction(async (tx) => { 17 - // Check if calendar_events table exists 18 27 const hasCalendarEventsTable = await tx.execute(sql` 19 28 SELECT 1 as ok FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'calendar_events' LIMIT 1 20 29 `) ··· 31 40 .where(eq(calendarBackups.userId, user.id)) 32 41 }) 33 42 43 + log.audit?.({ 44 + action: 'account.delete', 45 + actor: { type: 'user', id: user.id, email: user.email }, 46 + target: { type: 'account', id: user.id }, 47 + outcome: 'success', 48 + reason: 'User requested account data deletion', 49 + }) 50 + 34 51 return NextResponse.json({ success: true }) 35 52 } catch (e: any) { 36 53 return NextResponse.json( ··· 38 55 { status: 500 }, 39 56 ) 40 57 } 41 - } 58 + })
+49 -1
app/api/auth/[...all]/route.ts
··· 1 1 import { auth } from '@/lib/auth' 2 + import { withEvlog, useLogger } from '@/lib/evlog' 2 3 import { toNextJsHandler } from 'better-auth/next-js' 3 4 4 - export const { GET, POST } = toNextJsHandler(auth) 5 + const authHandlers = toNextJsHandler(auth) 6 + 7 + async function readAuthBody(request: Request) { 8 + if (request.method !== 'POST') return null 9 + try { 10 + return (await request.clone().json()) as Record<string, unknown> 11 + } catch { 12 + return null 13 + } 14 + } 15 + 16 + function authAction(pathname: string) { 17 + if (pathname.endsWith('/sign-in/email')) return 'auth.login' 18 + if (pathname.endsWith('/sign-out')) return 'auth.logout' 19 + if (pathname.endsWith('/sign-up/email')) return 'auth.register' 20 + if (pathname.includes('/reset-password')) return 'auth.password_reset' 21 + return null 22 + } 23 + 24 + async function handleAuth(request: Request) { 25 + const log = useLogger() 26 + const pathname = new URL(request.url).pathname 27 + const action = authAction(pathname) 28 + const body = await readAuthBody(request) 29 + const email = typeof body?.email === 'string' ? body.email : undefined 30 + const response = 31 + await authHandlers[request.method as keyof typeof authHandlers](request) 32 + 33 + if (action) { 34 + const success = response.status < 400 35 + log.audit?.({ 36 + action, 37 + actor: email 38 + ? { type: 'user', id: email, email } 39 + : { type: 'system', id: 'anonymous' }, 40 + target: { type: 'auth_session', id: email ?? 'unknown' }, 41 + outcome: success ? 'success' : 'failure', 42 + reason: success 43 + ? 'Better Auth request completed' 44 + : `Better Auth request failed with status ${response.status}`, 45 + }) 46 + } 47 + 48 + return response 49 + } 50 + 51 + export const GET = withEvlog(handleAuth) 52 + export const POST = withEvlog(handleAuth)
+77 -13
app/api/blob/route.ts
··· 1 1 import { NextRequest, NextResponse } from 'next/server' 2 + import { withEvlog, useLogger } from '@/lib/evlog' 2 3 import { getServerSession } from '@/lib/auth-server' 3 4 import { db } from '@/lib/drizzle/client' 4 5 import { calendarBackups } from '@/lib/drizzle/schema' ··· 20 21 }) 21 22 } 22 23 23 - export async function POST(req: NextRequest) { 24 + export const POST = withEvlog(async function POST(req: NextRequest) { 24 25 try { 26 + const log = useLogger() 25 27 const [session, body] = await Promise.all([getServerSession(), req.json()]) 26 - const userId = session?.user?.id 28 + const user = session?.user 29 + const userId = user?.id 27 30 28 - if (!userId) 31 + if (!userId) { 32 + log.audit?.({ 33 + action: 'calendar_backup.upsert', 34 + actor: { type: 'system', id: 'anonymous' }, 35 + target: { type: 'calendar_backup', id: 'unknown' }, 36 + outcome: 'denied', 37 + reason: 'Authentication required', 38 + }) 29 39 return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) 40 + } 30 41 31 42 const encrypted_data = body?.ciphertext 32 43 const iv = body?.iv ··· 47 58 set: { encryptedData: encrypted_data, iv, timestamp: new Date() }, 48 59 }) 49 60 61 + log.audit?.({ 62 + action: 'calendar_backup.upsert', 63 + actor: { type: 'user', id: userId, email: user?.email }, 64 + target: { type: 'calendar_backup', id: userId }, 65 + outcome: 'success', 66 + reason: 'User saved encrypted calendar backup', 67 + }) 50 68 return NextResponse.json({ success: true, backend: 'postgres' }) 51 69 } catch (e: unknown) { 52 70 const message = e instanceof Error ? e.message : 'Internal error' 53 71 return NextResponse.json({ error: message }, { status: 500 }) 54 72 } 55 - } 73 + }) 56 74 57 - export async function GET() { 75 + export const GET = withEvlog(async function GET(_req: NextRequest) { 58 76 try { 77 + const log = useLogger() 59 78 const session = await getServerSession() 60 - const userId = session?.user?.id 79 + const user = session?.user 80 + const userId = user?.id 61 81 62 - if (!userId) return jsonNoStore({ error: 'Unauthorized' }, { status: 401 }) 82 + if (!userId) { 83 + log.audit?.({ 84 + action: 'calendar_backup.export', 85 + actor: { type: 'system', id: 'anonymous' }, 86 + target: { type: 'calendar_backup', id: 'unknown' }, 87 + outcome: 'denied', 88 + reason: 'Authentication required', 89 + }) 90 + return jsonNoStore({ error: 'Unauthorized' }, { status: 401 }) 91 + } 63 92 64 93 const [result] = await db 65 94 .select({ ··· 70 99 .from(calendarBackups) 71 100 .where(eq(calendarBackups.userId, userId)) 72 101 73 - if (!result) return jsonNoStore({ error: 'Not found' }, { status: 404 }) 102 + if (!result) { 103 + log.audit?.({ 104 + action: 'calendar_backup.export', 105 + actor: { type: 'user', id: userId, email: user?.email }, 106 + target: { type: 'calendar_backup', id: userId }, 107 + outcome: 'failure', 108 + reason: 'No encrypted calendar backup found', 109 + }) 110 + return jsonNoStore({ error: 'Not found' }, { status: 404 }) 111 + } 112 + 113 + log.audit?.({ 114 + action: 'calendar_backup.export', 115 + actor: { type: 'user', id: userId, email: user?.email }, 116 + target: { type: 'calendar_backup', id: userId }, 117 + outcome: 'success', 118 + reason: 'User exported encrypted calendar backup', 119 + }) 74 120 75 121 return jsonNoStore({ 76 122 ciphertext: result.encryptedData, ··· 82 128 const message = e instanceof Error ? e.message : 'Internal error' 83 129 return jsonNoStore({ error: message }, { status: 500 }) 84 130 } 85 - } 131 + }) 86 132 87 - export async function DELETE() { 133 + export const DELETE = withEvlog(async function DELETE(_req: NextRequest) { 88 134 try { 135 + const log = useLogger() 89 136 const session = await getServerSession() 90 - const userId = session?.user?.id 137 + const user = session?.user 138 + const userId = user?.id 91 139 92 - if (!userId) 140 + if (!userId) { 141 + log.audit?.({ 142 + action: 'calendar_backup.delete', 143 + actor: { type: 'system', id: 'anonymous' }, 144 + target: { type: 'calendar_backup', id: 'unknown' }, 145 + outcome: 'denied', 146 + reason: 'Authentication required', 147 + }) 93 148 return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) 149 + } 94 150 95 151 await db.delete(calendarBackups).where(eq(calendarBackups.userId, userId)) 96 152 153 + log.audit?.({ 154 + action: 'calendar_backup.delete', 155 + actor: { type: 'user', id: userId, email: user?.email }, 156 + target: { type: 'calendar_backup', id: userId }, 157 + outcome: 'success', 158 + reason: 'User deleted encrypted calendar backup', 159 + }) 160 + 97 161 return NextResponse.json({ success: true, backend: 'postgres' }) 98 162 } catch (e: unknown) { 99 163 const message = e instanceof Error ? e.message : 'Internal error' 100 164 return NextResponse.json({ error: message }, { status: 500 }) 101 165 } 102 - } 166 + })
+22 -4
app/api/share/list/route.ts
··· 1 - import { NextResponse } from 'next/server' 1 + import { type NextRequest, NextResponse } from 'next/server' 2 + import { withEvlog, useLogger } from '@/lib/evlog' 2 3 import { getServerSession } from '@/lib/auth-server' 3 4 import crypto from 'crypto' 4 5 import { db } from '@/lib/drizzle/client' ··· 30 31 return decrypted 31 32 } 32 33 33 - export async function GET() { 34 + export const GET = withEvlog(async function GET(_req: NextRequest) { 35 + const log = useLogger() 34 36 const session = await getServerSession() 35 37 const user = session?.user 36 - if (!user) 38 + if (!user) { 39 + log.audit?.({ 40 + action: 'share.list', 41 + actor: { type: 'system', id: 'anonymous' }, 42 + target: { type: 'share_collection', id: 'unknown' }, 43 + outcome: 'denied', 44 + reason: 'Authentication required', 45 + }) 37 46 return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) 47 + } 38 48 39 49 const result = await db 40 50 .select() ··· 72 82 } 73 83 }) 74 84 85 + log.audit?.({ 86 + action: 'share.list', 87 + actor: { type: 'user', id: user.id, email: user.email }, 88 + target: { type: 'share_collection', id: user.id }, 89 + outcome: 'success', 90 + reason: 'User listed shares', 91 + }) 92 + 75 93 return NextResponse.json({ shares: shareList }) 76 - } 94 + })
+86 -11
app/api/share/route.ts
··· 1 1 import { type NextRequest, NextResponse } from 'next/server' 2 + import { withEvlog, useLogger } from '@/lib/evlog' 2 3 import { getServerSession } from '@/lib/auth-server' 3 4 import crypto from 'crypto' 4 5 import { db } from '@/lib/drizzle/client' ··· 48 49 return decrypted 49 50 } 50 51 51 - export async function POST(request: NextRequest) { 52 + export const POST = withEvlog(async function POST(request: NextRequest) { 52 53 try { 54 + const log = useLogger() 53 55 const body = await request.json() 54 56 const { id, data, password, burnAfterRead } = body as { 55 57 id?: string ··· 65 67 66 68 const session = await getServerSession() 67 69 const user = session?.user 68 - if (!user) 70 + if (!user) { 71 + log.audit?.({ 72 + action: 'share.create', 73 + actor: { type: 'system', id: 'anonymous' }, 74 + target: { type: 'share', id }, 75 + outcome: 'denied', 76 + reason: 'Authentication required', 77 + }) 69 78 return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) 79 + } 70 80 71 81 const hasPassword = typeof password === 'string' && password.length > 0 72 82 const burn = !!burnAfterRead ··· 104 114 }, 105 115 }) 106 116 117 + log.audit?.({ 118 + action: 'share.create', 119 + actor: { type: 'user', id: user.id, email: user.email }, 120 + target: { type: 'share', id }, 121 + outcome: 'success', 122 + reason: burn 123 + ? 'User created burn-after-read share' 124 + : 'User created share', 125 + }) 126 + 107 127 return NextResponse.json({ 108 128 success: true, 109 129 id, ··· 120 140 { status: 500 }, 121 141 ) 122 142 } 123 - } 143 + }) 124 144 125 - export async function GET(request: NextRequest) { 145 + export const GET = withEvlog(async function GET(request: NextRequest) { 146 + const log = useLogger() 126 147 const id = request.nextUrl.searchParams.get('id') 127 148 const password = request.nextUrl.searchParams.get('password') ?? '' 128 149 if (!id) ··· 164 185 } 165 186 }) 166 187 167 - if (result.status === 404) 188 + if (result.status === 404) { 189 + log.audit?.({ 190 + action: 'share.export', 191 + actor: { type: 'system', id: 'anonymous' }, 192 + target: { type: 'share', id }, 193 + outcome: 'failure', 194 + reason: 'Share not found', 195 + }) 168 196 return NextResponse.json({ error: 'Share not found' }, { status: 404 }) 169 - if (result.status === 401) 197 + } 198 + if (result.status === 401) { 199 + log.audit?.({ 200 + action: 'share.export', 201 + actor: { type: 'system', id: 'anonymous' }, 202 + target: { type: 'share', id }, 203 + outcome: 'denied', 204 + reason: 'Password required', 205 + }) 170 206 return NextResponse.json( 171 207 { 172 208 error: 'Password required', ··· 175 211 }, 176 212 { status: 401 }, 177 213 ) 178 - if (result.status === 403) 214 + } 215 + if (result.status === 403) { 216 + log.audit?.({ 217 + action: 'share.export', 218 + actor: { type: 'system', id: 'anonymous' }, 219 + target: { type: 'share', id }, 220 + outcome: 'denied', 221 + reason: result.protected 222 + ? 'Invalid password' 223 + : 'Failed to decrypt share data', 224 + }) 179 225 return NextResponse.json( 180 226 { 181 227 error: result.protected ··· 184 230 }, 185 231 { status: 403 }, 186 232 ) 233 + } 234 + 235 + log.audit?.({ 236 + action: result.burnAfterRead ? 'share.burn_after_read' : 'share.export', 237 + actor: { type: 'system', id: 'anonymous' }, 238 + target: { type: 'share', id }, 239 + outcome: 'success', 240 + reason: result.burnAfterRead 241 + ? 'Burn-after-read share exported and deleted' 242 + : 'Share exported', 243 + }) 187 244 188 245 return NextResponse.json({ 189 246 success: true, ··· 201 258 { status: 500 }, 202 259 ) 203 260 } 204 - } 261 + }) 205 262 206 - export async function DELETE(request: NextRequest) { 263 + export const DELETE = withEvlog(async function DELETE(request: NextRequest) { 264 + const log = useLogger() 207 265 const body = await request.json() 208 266 const { id } = body as { id?: string } 209 267 if (!id) ··· 211 269 212 270 const session = await getServerSession() 213 271 const user = session?.user 214 - if (!user) 272 + if (!user) { 273 + log.audit?.({ 274 + action: 'share.delete', 275 + actor: { type: 'system', id: 'anonymous' }, 276 + target: { type: 'share', id }, 277 + outcome: 'denied', 278 + reason: 'Authentication required', 279 + }) 215 280 return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) 281 + } 216 282 217 283 await db 218 284 .delete(shares) 219 285 .where(and(eq(shares.shareId, id), eq(shares.userId, user.id))) 286 + 287 + log.audit?.({ 288 + action: 'share.delete', 289 + actor: { type: 'user', id: user.id, email: user.email }, 290 + target: { type: 'share', id }, 291 + outcome: 'success', 292 + reason: 'User deleted share', 293 + }) 294 + 220 295 return NextResponse.json({ success: true }) 221 - } 296 + })
+12 -16
app/api/verify/route.ts
··· 1 1 import { NextRequest } from 'next/server' 2 2 import { withEvlog, useLogger, createError } from '@/lib/evlog' 3 - import { auth } from '@/lib/auth' 4 3 5 4 export const POST = withEvlog(async (request: NextRequest) => { 6 5 const log = useLogger() 7 - const session = await auth.api.getSession({ headers: request.headers }) 8 - 9 - if (session?.user) { 10 - log.set('actor', { id: session.user.id, email: session.user.email }) 11 - } 12 - 13 6 try { 14 7 const { token, action } = await request.json() 15 8 const secretKey = process.env.TURNSTILE_SECRET_KEY 16 9 17 - log.set('body', { 18 - token: token ? token.slice(0, 10) + '...' : null, 19 - action, 10 + log.set({ 11 + body: { 12 + token: token ? token.slice(0, 10) + '...' : null, 13 + action, 14 + }, 20 15 }) 21 16 22 17 if (!token) { ··· 27 22 fix: 'Provide token', 28 23 }) 29 24 } 30 - 25 + 31 26 if (!secretKey) { 32 27 throw createError({ 33 28 message: 'Missing secret', ··· 59 54 } 60 55 61 56 const data = await response.json() 62 - log.set('cloudflareResponse', data) 57 + log.set({ cloudflareResponse: data }) 63 58 64 59 if (data.success) { 65 - log.audit({ 66 - action: 'verify_captcha', 67 - actor: session?.user ? { id: session.user.id, email: session.user.email } : { id: 'anonymous' }, 68 - target: 'turnstile', 60 + log.audit?.({ 61 + action: 'captcha.verify', 62 + actor: { type: 'system', id: 'anonymous' }, 63 + target: { type: 'turnstile', id: action ?? 'unknown' }, 69 64 outcome: 'success', 65 + reason: 'CAPTCHA verification succeeded', 70 66 }) 71 67 return new Response(JSON.stringify({ success: true }), { 72 68 status: 200,
+64 -17
lib/evlog.ts
··· 1 - import { createEvlog } from 'evlog'; 2 - import { createFsDrain } from 'evlog/adapters/fs'; 3 - import { signed } from 'evlog/adapters/signed'; 1 + import { auditEnricher, auditOnly, signed, type AuditActor } from 'evlog' 2 + import { createAuthMiddleware } from 'evlog/better-auth' 3 + import { createFsDrain } from 'evlog/fs' 4 + import { createEvlog } from 'evlog/next' 5 + import { auth } from '@/lib/auth' 6 + 7 + const identify = createAuthMiddleware(auth) 8 + const mainDrain = createFsDrain({ dir: '.evlog/logs', pretty: false }) 9 + const auditDrain = auditOnly( 10 + signed(createFsDrain({ dir: '.audit', pretty: false }), { 11 + strategy: 'hash-chain', 12 + }), 13 + { await: true }, 14 + ) 15 + 16 + function actorFromEvent(event: Record<string, unknown>): AuditActor | null { 17 + const user = event.user 18 + if (user && typeof user === 'object') { 19 + const candidate = user as Record<string, unknown> 20 + if (typeof candidate.id === 'string') { 21 + return { 22 + type: 'user', 23 + id: candidate.id, 24 + ...(typeof candidate.email === 'string' 25 + ? { email: candidate.email } 26 + : {}), 27 + ...(typeof candidate.name === 'string' 28 + ? { displayName: candidate.name } 29 + : {}), 30 + } 31 + } 32 + } 33 + 34 + if (typeof event.userId === 'string') 35 + return { type: 'user', id: event.userId } 36 + 37 + return null 38 + } 39 + 40 + const auditEnrich = auditEnricher({ 41 + bridge: { 42 + getSession: ({ event }) => actorFromEvent(event as Record<string, unknown>), 43 + }, 44 + }) 4 45 5 - export const evlog = createEvlog({ 46 + const evlog = createEvlog({ 6 47 service: 'one-calendar', 7 - enrich: (event) => { 8 - return { 9 - ...event, 10 - timestamp: new Date().toISOString(), 11 - }; 48 + drain: async (ctx) => { 49 + const results = await Promise.allSettled([mainDrain(ctx), auditDrain(ctx)]) 50 + const rejected = results.find((result) => result.status === 'rejected') 51 + if (rejected?.status === 'rejected') throw rejected.reason 12 52 }, 13 - drain: [ 14 - signed(createFsDrain({ dir: '.audit' }), { 15 - strategy: 'hash-chain', 16 - await: true, 17 - }), 18 - ], 19 - }); 53 + enrich: auditEnrich, 54 + }) 55 + 56 + const withEvlogBase = evlog.withEvlog 20 57 21 - export const { withEvlog, useLogger, createError } = evlog; 58 + export const withEvlog: typeof evlog.withEvlog = (handler) => 59 + withEvlogBase(async (...args: Parameters<typeof handler>) => { 60 + const request = args[0] 61 + if (request instanceof Request) { 62 + const log = evlog.useLogger() 63 + await identify(log, request.headers, new URL(request.url).pathname) 64 + } 65 + return handler(...args) 66 + }) 67 + 68 + export const { useLogger, log, createError, createEvlogError } = evlog
+1
package.json
··· 29 29 "csv-parse": "latest", 30 30 "date-fns": "4.1.0", 31 31 "drizzle-orm": "^0.45.2", 32 + "evlog": "^2.17.0", 32 33 "framer-motion": "^12.7.4", 33 34 "geist": "latest", 34 35 "ics": "latest",
+63
pnpm-lock.yaml
··· 53 53 drizzle-orm: 54 54 specifier: ^0.45.2 55 55 version: 0.45.2(@electric-sql/pglite@0.4.1)(@prisma/client@7.8.0(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@6.0.3))(typescript@6.0.3))(@types/pg@8.20.0)(kysely@0.28.17)(mysql2@3.15.3)(pg@8.20.0)(postgres@3.4.9)(prisma@7.8.0(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@6.0.3)) 56 + evlog: 57 + specifier: ^2.17.0 58 + version: 2.17.0(hono@4.12.18)(next@16.2.6(@babel/core@7.29.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) 56 59 framer-motion: 57 60 specifier: ^12.7.4 58 61 version: 12.38.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) ··· 3624 3627 eventemitter3@5.0.4: 3625 3628 resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} 3626 3629 3630 + evlog@2.17.0: 3631 + resolution: {integrity: sha512-v6PWFV0SAEB04l70vENByG6o4r2v8KXIUNroeqYQ6uXb2xEzRRgRCxiAsAntb+Tqd4xPKenX+X67v/KyM6ar5g==} 3632 + peerDependencies: 3633 + '@nestjs/common': '>=11.1.19' 3634 + '@nuxt/kit': ^4.4.2 3635 + '@tanstack/start-client-core': ^1.167.20 3636 + ai: '>=6.0.168' 3637 + elysia: '>=1.4.28' 3638 + express: '>=5.2.1' 3639 + fastify: '>=5.8.5' 3640 + h3: ^1.15.11 3641 + hono: '' 3642 + next: '>=16.2.4' 3643 + nitro: ^3.0.260311-beta 3644 + nitropack: ^2.13.3 3645 + ofetch: ^1.5.1 3646 + react: '>=19.2.5' 3647 + react-router: '>=7.14.2' 3648 + vite: ^7.0.0 || ^8.0.0 3649 + peerDependenciesMeta: 3650 + '@nestjs/common': 3651 + optional: true 3652 + '@nuxt/kit': 3653 + optional: true 3654 + '@tanstack/start-client-core': 3655 + optional: true 3656 + ai: 3657 + optional: true 3658 + elysia: 3659 + optional: true 3660 + express: 3661 + optional: true 3662 + fastify: 3663 + optional: true 3664 + h3: 3665 + optional: true 3666 + hono: 3667 + optional: true 3668 + next: 3669 + optional: true 3670 + nitro: 3671 + optional: true 3672 + nitropack: 3673 + optional: true 3674 + ofetch: 3675 + optional: true 3676 + react: 3677 + optional: true 3678 + react-router: 3679 + optional: true 3680 + vite: 3681 + optional: true 3682 + 3627 3683 exsolve@1.0.8: 3628 3684 resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} 3629 3685 ··· 5233 5289 5234 5290 uuid@8.3.2: 5235 5291 resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} 5292 + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). 5236 5293 hasBin: true 5237 5294 5238 5295 valibot@1.2.0: ··· 8621 8678 esutils@2.0.3: {} 8622 8679 8623 8680 eventemitter3@5.0.4: {} 8681 + 8682 + evlog@2.17.0(hono@4.12.18)(next@16.2.6(@babel/core@7.29.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1): 8683 + optionalDependencies: 8684 + hono: 4.12.18 8685 + next: 16.2.6(@babel/core@7.29.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) 8686 + react: 18.3.1 8624 8687 8625 8688 exsolve@1.0.8: 8626 8689 optional: true