()
| 92 | ); |
| 93 | |
| 94 | export const startServer = async (): Promise<void> => { |
| 95 | const config = loadConfig(); |
| 96 | const { AppLayer, betterAuth } = await makeSelfHostApp(); |
| 97 | |
| 98 | // Serve the built SPA, split by cacheability so a redeploy is picked up at |
| 99 | // once instead of stranding browsers on a stale shell: |
| 100 | // - `/assets/*` are Vite content-hashed (a new build emits new filenames), |
| 101 | // so they're safe to cache forever. |
| 102 | // - index.html (and the SPA fallback for client routes) is the mutable |
| 103 | // entry point that references those hashes; it must always revalidate, or |
| 104 | // a browser keeps an old index.html plus its old hashed bundles (still in |
| 105 | // cache) and renders a stale UI until a hard refresh. |
| 106 | // Without explicit headers `HttpStaticServer` sends no Cache-Control at all, |
| 107 | // so browsers heuristically cache index.html across deploys. The hashed |
| 108 | // `/assets` route is the more specific match, so it wins over the SPA |
| 109 | // catch-all. Other built-in API/docs/auth/mcp routes still take precedence; |
| 110 | // `spa: true` falls back to index.html for any remaining path (client routing). |
| 111 | const AssetsLive = HttpStaticServer.layer({ |
| 112 | root: assetsDir, |
| 113 | prefix: "/assets", |
| 114 | cacheControl: "public, max-age=31536000, immutable", |
| 115 | }).pipe(Layer.provide(BunFileSystem.layer), Layer.provide(BunPath.layer)); |
| 116 | |
| 117 | const SpaLive = HttpStaticServer.layer({ |
| 118 | root: distDir, |
| 119 | spa: true, |
| 120 | cacheControl: "no-cache", |
| 121 | }).pipe(Layer.provide(BunFileSystem.layer), Layer.provide(BunPath.layer)); |
| 122 | |
| 123 | const ServerLive = HttpRouter.serve(Layer.mergeAll(AppLayer, AssetsLive, SpaLive), { |
| 124 | middleware: selfHostHttpMiddleware(betterAuth), |
| 125 | }).pipe( |
| 126 | Layer.provide( |
| 127 | BunHttpServer.layer({ hostname: config.host, port: config.port, idleTimeout: 0 }), |
| 128 | ), |
| 129 | ); |
| 130 | |
| 131 | await BunRuntime.runMain(Layer.launch(ServerLive)); |
| 132 | }; |
| 133 | |
| 134 | if (import.meta.main) { |
| 135 | // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: process entry point; turn a pre-runtime startup failure (config/DB open) into a diagnosable log + non-zero exit instead of an opaque unhandled rejection |
no test coverage detected