Files
trueref-legacy/src/hooks.server.ts
2026-03-30 19:11:09 +02:00

118 lines
3.7 KiB
TypeScript

/**
* 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;
};