MCPcopy Create free account
hub / github.com/UsefulSoftwareCo/executor / startServer

Function startServer

apps/host-selfhost/src/serve.ts:110–167  ·  view source on GitHub ↗
()

Source from the content-addressed store, hash-verified

108 );
109
110export const startServer = async (): Promise<void> => {
111 const config = loadConfig();
112 const { AppLayer, betterAuth } = await makeSelfHostApp();
113
114 // Serve the built SPA, split by cacheability so a redeploy is picked up at
115 // once instead of stranding browsers on a stale shell:
116 // - `/assets/*` are Vite content-hashed (a new build emits new filenames),
117 // so they're safe to cache forever.
118 // - index.html (and the SPA fallback for client routes) is the mutable
119 // entry point that references those hashes; it must always revalidate, or
120 // a browser keeps an old index.html plus its old hashed bundles (still in
121 // cache) and renders a stale UI until a hard refresh.
122 // Without explicit headers `HttpStaticServer` sends no Cache-Control at all,
123 // so browsers heuristically cache index.html across deploys. The hashed
124 // `/assets` route is the more specific match, so it wins over the SPA
125 // catch-all. Other built-in API/docs/auth/mcp routes still take precedence;
126 // `spa: true` falls back to index.html for any remaining path (client routing).
127 const AssetsLive = HttpStaticServer.layer({
128 root: assetsDir,
129 prefix: "/assets",
130 cacheControl: "public, max-age=31536000, immutable",
131 }).pipe(Layer.provide(BunFileSystem.layer), Layer.provide(BunPath.layer));
132
133 const SpaLive = HttpStaticServer.layer({
134 root: distDir,
135 spa: true,
136 cacheControl: "no-cache",
137 }).pipe(Layer.provide(BunFileSystem.layer), Layer.provide(BunPath.layer));
138
139 // Server-scope finalizer: flush buffered analytics on graceful shutdown.
140 const AnalyticsFlushLive = Layer.effectDiscard(
141 Effect.addFinalizer(() => Effect.promise(() => disposeAnalytics())),
142 );
143
144 // OTLP export, or `Layer.empty` when no collector is configured (see
145 // ./telemetry). The `http.server` envelope span each request's `withSpan`
146 // children parent under is NOT wired here: `HttpEffect.toHandled` already
147 // wraps every served app in `HttpMiddleware.tracer`, which also continues an
148 // inbound `traceparent` so a request arriving through a tunnel keeps one
149 // trace id. Adding it here as well produced two identical `http.server`
150 // spans per request, one the parent of the other.
151 const TelemetryLive = makeTelemetryLive();
152
153 const ServerLive = HttpRouter.serve(Layer.mergeAll(AppLayer, AssetsLive, SpaLive), {
154 middleware: selfHostHttpMiddleware(betterAuth),
155 }).pipe(
156 Layer.provide(
157 BunHttpServer.layer({ hostname: config.host, port: config.port, idleTimeout: 0 }),
158 ),
159 );
160
161 // Provided UNDER the server, not merged beside it: the tracer must already be
162 // in scope while the app's layers build, or spans created during construction
163 // resolve the default no-op tracer and are silently dropped.
164 await BunRuntime.runMain(
165 Layer.launch(Layer.merge(ServerLive, AnalyticsFlushLive).pipe(Layer.provide(TelemetryLive))),
166 );
167};

Callers 1

serve.tsFile · 0.70

Calls 5

loadConfigFunction · 0.90
makeSelfHostAppFunction · 0.90
disposeAnalyticsFunction · 0.90
makeTelemetryLiveFunction · 0.90
selfHostHttpMiddlewareFunction · 0.85

Tested by

no test coverage detected