diff --git a/src/db/index.ts b/src/db/index.ts index 981b0ec..ae265e1 100644 --- a/src/db/index.ts +++ b/src/db/index.ts @@ -1,14 +1,16 @@ -import { drizzle as drizzlePg } from 'drizzle-orm/node-postgres'; -import { drizzle as drizzleSqlite } from 'drizzle-orm/better-sqlite3'; import { sql } from 'drizzle-orm'; -import { Pool } from 'pg'; -import Database from 'better-sqlite3'; import path from 'path'; import fs from 'fs'; import * as schema from './schema'; import * as authSchemaSqlite from './schema_auth_sqlite'; import * as authSchemaPostgres from './schema_auth_postgres'; +// Database driver modules are loaded lazily via require() inside getDrizzleDB() +// to avoid loading the unused driver (~15-20 MB each) in every serverless function. +// require() is used instead of dynamic import() because getDrizzleDB() must remain +// synchronous for the SQLite code path. + + const UNCLAIMED_USER_ID = 'unclaimed'; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -62,12 +64,20 @@ function getDrizzleDB() { dbIsPostgres = !!process.env.POSTGRES_URL; if (dbIsPostgres) { + // eslint-disable-next-line @typescript-eslint/no-require-imports + const { drizzle: drizzlePg } = require('drizzle-orm/node-postgres'); + // eslint-disable-next-line @typescript-eslint/no-require-imports + const { Pool } = require('pg'); const pool = new Pool({ connectionString: process.env.POSTGRES_URL, }); dbInstance = drizzlePg(pool, { schema: { ...schema, ...authSchemaPostgres } }); } else { // Fallback to SQLite + // eslint-disable-next-line @typescript-eslint/no-require-imports + const { drizzle: drizzleSqlite } = require('drizzle-orm/better-sqlite3'); + // eslint-disable-next-line @typescript-eslint/no-require-imports + const Database = require('better-sqlite3'); const dbPath = path.join(process.cwd(), 'docstore', 'sqlite3.db'); const dir = path.dirname(dbPath); if (!fs.existsSync(dir)) { diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts index 36982bd..bd82629 100644 --- a/src/lib/server/auth.ts +++ b/src/lib/server/auth.ts @@ -5,17 +5,13 @@ import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; import { db } from "@/db"; -import { rateLimiter } from "@/lib/server/rate-limiter"; import { isAuthEnabled, isAnonymousAuthSessionsEnabled } from "@/lib/server/auth-config"; -import { deleteUserStorageData } from "@/lib/server/user-data-cleanup"; import * as authSchemaSqlite from "@/db/schema_auth_sqlite"; import * as authSchemaPostgres from "@/db/schema_auth_postgres"; -import { - transferUserAudiobooks, - transferUserDocuments, - transferUserPreferences, - transferUserProgress, -} from "@/lib/server/claim-data"; + +// Heavy modules (S3 SDK, blobstore, rate-limiter, claim-data) are loaded +// lazily via dynamic import() inside the beforeDelete / onLinkAccount +// callbacks to avoid inflating every serverless function that touches auth. // ... @@ -80,6 +76,7 @@ const createAuth = () => betterAuth({ enabled: true, beforeDelete: async (user) => { try { + const { deleteUserStorageData } = await import('@/lib/server/user-data-cleanup'); await deleteUserStorageData(user.id, null); } catch (error) { console.error('[auth] Failed to clean up user storage before deletion:', error); @@ -123,6 +120,12 @@ const createAuth = () => betterAuth({ newUserEmail: newUser.user.email, }); + // Lazy-load heavy modules only when account linking actually happens + const [{ rateLimiter }, claimData] = await Promise.all([ + import('@/lib/server/rate-limiter'), + import('@/lib/server/claim-data'), + ]); + // Transfer rate limiting data (TTS char counts) from anonymous user to authenticated user try { await rateLimiter.transferAnonymousUsage(anonymousUser.user.id, newUser.user.id); @@ -134,7 +137,7 @@ const createAuth = () => betterAuth({ // Transfer audiobooks from anonymous user to new authenticated user try { - const transferred = await transferUserAudiobooks(anonymousUser.user.id, newUser.user.id); + const transferred = await claimData.transferUserAudiobooks(anonymousUser.user.id, newUser.user.id); if (transferred > 0) { console.log(`Successfully transferred ${transferred} audiobook(s) from anonymous user ${anonymousUser.user.id} to user ${newUser.user.id}`); } @@ -145,7 +148,7 @@ const createAuth = () => betterAuth({ // Transfer documents from anonymous user to new authenticated user try { - const transferred = await transferUserDocuments(anonymousUser.user.id, newUser.user.id); + const transferred = await claimData.transferUserDocuments(anonymousUser.user.id, newUser.user.id); if (transferred > 0) { console.log(`Successfully transferred ${transferred} document(s) from anonymous user ${anonymousUser.user.id} to user ${newUser.user.id}`); } @@ -156,7 +159,7 @@ const createAuth = () => betterAuth({ // Transfer preferences from anonymous user to new authenticated user try { - const transferred = await transferUserPreferences(anonymousUser.user.id, newUser.user.id); + const transferred = await claimData.transferUserPreferences(anonymousUser.user.id, newUser.user.id); if (transferred > 0) { console.log(`Successfully transferred preferences from anonymous user ${anonymousUser.user.id} to user ${newUser.user.id}`); } @@ -167,7 +170,7 @@ const createAuth = () => betterAuth({ // Transfer reading progress from anonymous user to new authenticated user try { - const transferred = await transferUserProgress(anonymousUser.user.id, newUser.user.id); + const transferred = await claimData.transferUserProgress(anonymousUser.user.id, newUser.user.id); if (transferred > 0) { console.log(`Successfully transferred ${transferred} progress row(s) from anonymous user ${anonymousUser.user.id} to user ${newUser.user.id}`); }