WIP. A little custom music server
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

add graceful shutdown

+58 -6
+9 -3
backend/src/api.ts
··· 216 216 }) {} 217 217 218 218 export function startApi() { 219 - return new Elysia() 219 + const app = new Elysia() 220 220 .use(openapi()) 221 221 .get("/", "Hello Elysia") 222 222 .get("/albums", () => runtime.runPromise(ApiService.getAlbumList())) ··· 235 235 }), 236 236 }, 237 237 ) 238 - .get("/songs", () => pipe(ApiService.getAllSongs(), runtime.runPromise)) 239 - .listen(3003); 238 + .get("/songs", () => pipe(ApiService.getAllSongs(), runtime.runPromise)); 239 + 240 + const server = app.listen(3003); 241 + 242 + return { 243 + server, 244 + runtime, 245 + }; 240 246 } 241 247 242 248 type GetSongType = {
+49 -3
backend/src/index.ts
··· 1 1 import { BunContext, BunRuntime } from "@effect/platform-bun"; 2 - import { Config, Cron, Effect, Either, Layer, Option, Schedule } from "effect"; 2 + import { Config, Cron, Deferred, Effect, Either, Fiber, Layer, Option, Schedule } from "effect"; 3 3 import { drizzle } from "drizzle-orm/bun-sqlite"; 4 4 import { migrate } from "drizzle-orm/bun-sqlite/migrator"; 5 5 import { Database } from "bun:sqlite"; ··· 19 19 const syncCron = Cron.parse("*/1 * * * *").pipe(Either.getRight, Option.getOrThrow); 20 20 const syncSchedule = Schedule.cron(syncCron); 21 21 22 + const SHUTDOWN_TIMEOUT_MS = 10_000; 23 + 22 24 const main = Effect.gen(function* () { 23 25 // Run migrations first 24 26 yield* Effect.log("Running database migrations..."); ··· 30 32 // Then start the application 31 33 const folderPath = yield* Config.string("FOLDER_PATH"); 32 34 33 - startApi(); 35 + const { server, runtime } = startApi(); 36 + yield* Effect.log("API server started on port 3003"); 37 + 38 + // Create a deferred to handle shutdown signal 39 + const shutdownSignal = yield* Deferred.make<void>(); 34 40 35 - yield* syncLibraryStream(folderPath); 41 + // Setup graceful shutdown handlers 42 + const setupShutdownHandlers = Effect.sync(() => { 43 + const handleShutdown = (signal: string) => { 44 + Effect.runFork( 45 + Effect.gen(function* () { 46 + yield* Effect.log(`Received ${signal}, starting graceful shutdown...`); 47 + yield* Deferred.succeed(shutdownSignal, undefined); 48 + }).pipe(Effect.provide(AppLayer)), 49 + ); 50 + }; 51 + 52 + process.on("SIGTERM", () => handleShutdown("SIGTERM")); 53 + process.on("SIGINT", () => handleShutdown("SIGINT")); 54 + }); 55 + 56 + yield* setupShutdownHandlers; 57 + 58 + // Start library sync in the background 59 + const syncFiber = yield* Effect.fork(syncLibraryStream(folderPath)); 60 + 61 + // Wait for shutdown signal 62 + yield* Deferred.await(shutdownSignal); 63 + 64 + // Begin graceful shutdown 65 + yield* Effect.log("Shutting down API server..."); 66 + 67 + // Stop accepting new connections 68 + yield* Effect.tryPromise(() => server.stop()); 69 + yield* Effect.log("API server stopped"); 70 + 71 + // Interrupt the sync fiber 72 + yield* Effect.log("Stopping library sync..."); 73 + yield* Fiber.interrupt(syncFiber); 74 + yield* Effect.log("Library sync stopped"); 75 + 76 + // Dispose of the runtime to clean up database connections 77 + yield* Effect.log("Closing database connections..."); 78 + yield* Effect.tryPromise(() => runtime.dispose()); 79 + yield* Effect.log("Database connections closed"); 80 + 81 + yield* Effect.log("Graceful shutdown completed"); 36 82 }).pipe(Effect.provide(AppLayer)); 37 83 38 84 BunRuntime.runMain(main);