···
// Clean up expired sessions every hour
168
-
setInterval(cleanupExpiredSessions, 60 * 60 * 1000);
168
+
const sessionCleanupInterval = setInterval(
169
+
cleanupExpiredSessions,
// Helper function to sync user subscriptions from Polar
async function syncUserSubscriptionsFromPolar(
···
// Periodic sync every 5 minutes as backup (SSE handles real-time updates)
283
+
const syncInterval = setInterval(
await whisperService.syncWithWhisper();
···
// Clean up stale files daily
295
-
setInterval(() => whisperService.cleanupStaleFiles(), 24 * 60 * 60 * 1000);
298
+
const fileCleanupInterval = setInterval(
299
+
() => whisperService.cleanupStaleFiles(),
300
+
24 * 60 * 60 * 1000,
const server = Bun.serve({
port: process.env.PORT ? Number.parseInt(process.env.PORT, 10) : 3000,
···
// Event-driven SSE stream with reconnection support
const stream = new ReadableStream({
async start(controller) {
1628
+
// Track this stream for graceful shutdown
1629
+
activeSSEStreams.add(controller);
const encoder = new TextEncoder();
let lastEventId = Math.floor(Date.now() / 1000);
···
current?.status === "failed"
1681
+
activeSSEStreams.delete(controller);
···
clearInterval(heartbeatInterval);
transcriptionEvents.off(transcriptionId, updateHandler);
1712
+
activeSSEStreams.delete(controller);
···
clearInterval(heartbeatInterval);
transcriptionEvents.off(transcriptionId, updateHandler);
1722
+
activeSSEStreams.delete(controller);
···
console.log(`🪻 Thistle running at http://localhost:${server.port}`);
3111
+
// Track active SSE streams for graceful shutdown
3112
+
const activeSSEStreams = new Set<ReadableStreamDefaultController>();
3114
+
// Graceful shutdown handler
3115
+
let isShuttingDown = false;
3117
+
async function shutdown(signal: string) {
3118
+
if (isShuttingDown) return;
3119
+
isShuttingDown = true;
3121
+
console.log(`\n${signal} received, starting graceful shutdown...`);
3123
+
// 1. Stop accepting new requests
3124
+
console.log("[Shutdown] Closing server...");
3127
+
// 2. Close all active SSE streams (safe to kill - sync will handle reconnection)
3128
+
console.log(`[Shutdown] Closing ${activeSSEStreams.size} active SSE streams...`);
3129
+
for (const controller of activeSSEStreams) {
3131
+
controller.close();
3136
+
activeSSEStreams.clear();
3138
+
// 3. Stop transcription service (closes streams to Murmur)
3139
+
whisperService.stop();
3141
+
// 4. Stop cleanup intervals
3142
+
console.log("[Shutdown] Stopping cleanup intervals...");
3143
+
clearInterval(sessionCleanupInterval);
3144
+
clearInterval(syncInterval);
3145
+
clearInterval(fileCleanupInterval);
3147
+
// 5. Close database connections
3148
+
console.log("[Shutdown] Closing database...");
3151
+
console.log("[Shutdown] Complete");
3155
+
// Register shutdown handlers
3156
+
process.on("SIGTERM", () => shutdown("SIGTERM"));
3157
+
process.on("SIGINT", () => shutdown("SIGINT"));