import API, { Meta } from "@encore.dev/sidecar-api"; import log from "./log/mod"; import { setRuntimeForLogging_encoreInternal } from "./log/internal/mod"; import { createEncoreInternalRouter } from "./http/encore-routes"; import ServerHandler, { createApiRouter } from "./http/server"; import { ApiRoute } from "./http/types"; import type { Runtime } from "./jsruntime"; export type { Runtime } from "./jsruntime"; import { inject, runtime } from "./jsruntime"; import { logCtxHook } from "./reqtrack/logging"; export { Version } from "./conf/version"; export async function initRuntime(runtime: Runtime): Promise { setRuntimeForLogging_encoreInternal(runtime, logCtxHook); inject(runtime); await initSidecarAPI(); log.trace("encore runtime initialized"); } async function initSidecarAPI(): Promise { // Now ensure the sidecar is up and running let lastError = new Error("unable to connect to encore sidecar"); for (let tryCount = 0; tryCount < 10; tryCount++) { try { _api = new API(runtime().apiTransport()); const response = await api().meta.health({}, { timeoutMs: 5000 }); switch (response.status) { case Meta.HealthResponse_Result.HEALTHY: await loadRuntimeConfig(_api); // If the sidecar is health log.debug("connected to encore sidecar", { version: response.version, }); return; case Meta.HealthResponse_Result.INITIALISING: lastError = new Error("encore sidecar is still starting"); break; case Meta.HealthResponse_Result.FAILURE: lastError = new Error("encore sidecar failed to start"); break; default: lastError = new Error( `encore sidecar sent an invalid health status: ${response.status}`, ); break; } } catch (e) { if (e instanceof Error) { lastError = e; } else { lastError = new Error(`encore sidecar failed to start: ${e}`); } } // If where we want to be, wait a bit and try again await new Promise((resolve) => setTimeout(resolve, 250)); } // If here, we failed to connect to the sidecar log.error(lastError, "unable to connect to encore sidecar"); throw lastError; } let _api: API | undefined; /** * Returns the API client for the sidecar */ export function api(): API { if (!_api) { throw new Error("API not initialized"); } return _api; } async function loadRuntimeConfig(api: API): Promise { const resp = await api.meta.runtimeConfig({}, { timeoutMs: 5000 }); _config = resp.config; } let _config: Meta.SimpleConfig | undefined; /** * Returns the runtime config */ export function config(): Meta.SimpleConfig { if (!_config) { throw new Error("config not initialized"); } return _config; } // The address this runtime is listening on, // In the form "host:port". let _listenURL: URL | undefined; export function listenURL(): URL | undefined { return _listenURL; } /** * Starts serving the applications API */ export function serve(apiRoutes: ApiRoute[]): void { const rt = runtime(); let hostname = "0.0.0.0"; let port = Number(rt.env("PORT")); const listenAddr = rt.env("ENCORE_LISTEN_ADDR"); if (listenAddr) { const parts = listenAddr.split(":"); hostname = parts[0]; if (!hostname) { hostname = "0.0.0.0"; } port = Number(parts[1]); } if (isNaN(port) || port <= 0) { log.warn("no port set or invalid port, defaulting to 8080"); port = 8080; } const apiRouter = createApiRouter(apiRoutes); const encoreRouter = createEncoreInternalRouter(); log.debug("starting server...", { hostname, port }); try { _listenURL = rt.startHTTPServer(hostname, port, async (req, res) => { return ServerHandler(apiRouter, encoreRouter, req, res); }); log.debug("server started", { url: _listenURL.toString() }); } catch (e: unknown) { log.error(e, "failed to start server"); throw e; } }