diff --git a/apps/web/package.json b/apps/web/package.json index 5b63bea..6f05667 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -18,6 +18,7 @@ "@sveltejs/kit": "^2.43.2", "@sveltejs/vite-plugin-svelte": "^6.2.0", "@types/bun": "^1.3.0", + "better-sqlite3": "^9.4.5", "svelte": "^5.39.5", "svelte-check": "^4.3.2", "typescript": "^5.9.2", diff --git a/apps/web/src/lib/server/db.ts b/apps/web/src/lib/server/db.ts index 32f9735..ae943c9 100644 --- a/apps/web/src/lib/server/db.ts +++ b/apps/web/src/lib/server/db.ts @@ -6,7 +6,13 @@ function applyDefaultPragmas(instance: any): void { try { instance.exec(`PRAGMA busy_timeout = 5000;`); // Intentar activar WAL (si no es soportado, SQLite devolverá 'memory' u otro modo) - instance.query(`PRAGMA journal_mode = WAL`).get(); + try { + if (typeof instance.query === 'function') { + instance.query(`PRAGMA journal_mode = WAL`)?.get?.(); + } else { + instance.prepare?.(`PRAGMA journal_mode = WAL`)?.get?.(); + } + } catch {} instance.exec(`PRAGMA synchronous = NORMAL;`); instance.exec(`PRAGMA wal_autocheckpoint = 1000;`); // Asegurar claves foráneas siempre activas @@ -16,10 +22,27 @@ function applyDefaultPragmas(instance: any): void { } } +/** + * Intenta cargar un constructor de Database compatible: + * - En Bun (SSR nativo): bun:sqlite + * - En Node (Vite dev SSR): better-sqlite3 + */ +async function importSqliteDatabase(): Promise { + try { + // @ts-ignore + if (typeof Bun !== 'undefined') { + const mod = await import('bun:sqlite'); + return (mod as any).Database; + } + } catch {} + const mod = await import('better-sqlite3'); + return (mod as any).default || (mod as any).Database || mod; +} + /** * Abre la BD compartida. En desarrollo, si el archivo no existe y DEV_AUTOSEED_DB=true, * inicializa el esquema (migraciones) y siembra datos de demo. - * Nota: uso de import dinámico de 'bun:sqlite' para que el build con Node no falle. + * Nota: usa bun:sqlite si está disponible; en SSR Node usa better-sqlite3. */ async function openDb(filename: string = 'tasks.db'): Promise { const absolutePath = resolveDbAbsolutePath(filename); @@ -32,20 +55,51 @@ async function openDb(filename: string = 'tasks.db'): Promise { if (err?.code !== 'EEXIST') throw err; } - const { Database } = await import('bun:sqlite'); - const instance = new Database(absolutePath); + const DatabaseCtor = await importSqliteDatabase(); + const instance = new DatabaseCtor(absolutePath); applyDefaultPragmas(instance); // Auto-inicialización y seed sólo en desarrollo y primer arranque if (firstCreate && isDev() && DEV_AUTOSEED_DB) { - try { - const dbModule = await import('../../../../../src/db'); - if (typeof (dbModule as any).initializeDatabase === 'function') { - (dbModule as any).initializeDatabase(instance); + // Detectar entorno Bun vs Node (Vite dev) + const isBun = typeof (globalThis as any).Bun !== 'undefined'; + + if (isBun) { + // En Bun podemos reutilizar initializeDatabase del repo principal + try { + const dbModule = await import('../../../../../src/db'); + if (typeof (dbModule as any).initializeDatabase === 'function') { + (dbModule as any).initializeDatabase(instance); + } + } catch (e) { + console.warn('[web/db] No se pudo ejecutar initializeDatabase en dev (Bun):', e); + } + } else { + // En SSR Node: aplicar migraciones directamente con compat para .query + try { + const mod = await import('../../../../../src/db/migrations/index.ts'); + const list = (mod as any).migrations as any[]; + const compat: any = instance; + if (typeof compat.query !== 'function') { + compat.query = (sql: string) => ({ + all: () => compat.prepare(sql).all(), + get: () => compat.prepare(sql).get() + }); + } + try { compat.exec?.(`PRAGMA foreign_keys = ON;`); } catch {} + for (const m of list) { + try { + await (m.up as any)(compat); + } catch (e) { + console.warn('[web/db] Error aplicando migración en dev (Node):', (m as any)?.name ?? '(sin nombre)', e); + } + } + } catch (e) { + console.warn('[web/db] No se pudieron aplicar migraciones en dev (Node):', e); } - } catch (e) { - console.warn('[web/db] No se pudo ejecutar initializeDatabase en dev:', e); } + + // Seed de datos de demo try { const seed = await import('./dev-seed'); if (typeof (seed as any).seedDev === 'function') { diff --git a/apps/web/src/lib/server/dev-seed.ts b/apps/web/src/lib/server/dev-seed.ts index 5290059..8c03d92 100644 --- a/apps/web/src/lib/server/dev-seed.ts +++ b/apps/web/src/lib/server/dev-seed.ts @@ -16,7 +16,7 @@ export async function seedDev(db: any, defaultUser: string): Promise { try { db.exec(`PRAGMA foreign_keys = ON;`); } catch {} // Si ya hay tareas, asumimos BD poblada try { - const row = db.query(`SELECT COUNT(*) AS c FROM tasks`).get() as any; + const row = db.prepare(`SELECT COUNT(*) AS c FROM tasks`).get() as any; if (row && Number(row.c || 0) > 0) return; } catch {}