/** * SvelteKit server hooks (TRUEREF-0009). * * Runs once when the Node.js server process starts. Initialises the database * and kicks off the indexing pipeline + job queue so queued jobs resume * automatically after a restart. */ import { initializeDatabase } from '$lib/server/db/index.js'; import { getClient } from '$lib/server/db/client.js'; import { initializePipeline } from '$lib/server/pipeline/startup.js'; import { createProviderFromProfile } from '$lib/server/embeddings/registry.js'; import { EmbeddingService } from '$lib/server/embeddings/embedding.service.js'; import { EmbeddingProfileEntity, type EmbeddingProfileEntityProps } from '$lib/server/models/embedding-profile.js'; import { EmbeddingProfileMapper } from '$lib/server/mappers/embedding-profile.mapper.js'; import { env } from '$env/dynamic/private'; import type { Handle } from '@sveltejs/kit'; // --------------------------------------------------------------------------- // Server initialisation — runs once on startup // --------------------------------------------------------------------------- try { initializeDatabase(); } catch (err) { console.error('[hooks.server] FATAL: database initialisation failed:', err); process.exit(1); } try { const db = getClient(); const activeProfileRow = db .prepare<[], EmbeddingProfileEntityProps>( 'SELECT * FROM embedding_profiles WHERE is_default = 1 AND enabled = 1 LIMIT 1' ) .get(); let embeddingService: EmbeddingService | null = null; if (activeProfileRow) { const activeProfile = EmbeddingProfileMapper.fromEntity( new EmbeddingProfileEntity(activeProfileRow) ); const provider = createProviderFromProfile(activeProfile); embeddingService = new EmbeddingService(db, provider, activeProfile.id); } // Read database path from environment const dbPath = env.DATABASE_URL; // Read indexing concurrency setting from database let concurrency = 2; // default if (dbPath) { const concurrencyRow = db .prepare<[], { value: string }>( "SELECT value FROM settings WHERE key = 'indexing.concurrency' LIMIT 1" ) .get(); if (concurrencyRow) { try { const parsed = JSON.parse(concurrencyRow.value); concurrency = parsed.value ?? 2; } catch { // If parsing fails, use default concurrency = 2; } } } initializePipeline(db, embeddingService, { concurrency, dbPath }); console.log('[hooks.server] Indexing pipeline initialised.'); } catch (err) { console.error( '[hooks.server] Failed to initialise pipeline:', err instanceof Error ? err.message : String(err) ); } // --------------------------------------------------------------------------- // CORS headers applied to all /api/* responses // --------------------------------------------------------------------------- const CORS_HEADERS = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PATCH, DELETE, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Authorization' } as const; // --------------------------------------------------------------------------- // Request handler — CORS + pass-through // --------------------------------------------------------------------------- export const handle: Handle = async ({ event, resolve }) => { const { pathname } = event.url; // Handle CORS pre-flight for all API routes. if (event.request.method === 'OPTIONS' && pathname.startsWith('/api/')) { return new Response(null, { status: 204, headers: CORS_HEADERS }); } const response = await resolve(event); // Attach CORS headers to all API responses. if (pathname.startsWith('/api/')) { for (const [key, value] of Object.entries(CORS_HEADERS)) { response.headers.set(key, value); } } return response; };