1
17export const SEC_API_URL = (process.env.SEC_API_URL ?? "http://localhost:4000").replace(/\/$/, "");
18export const SEC_WEB_URL = (process.env.SEC_WEB_URL ?? "http://localhost:3000").replace(/\/$/, "");
19export const SEC_REQUIRE_LIVE = process.env.SEC_REQUIRE_LIVE === "1";
20
21
26export async function apiReachable(base: string = SEC_API_URL): Promise<boolean> {
27 try {
28 const res = await fetch(`${base}/api/v1/health`, { signal: AbortSignal.timeout(10_000) });
29 return res.status < 500;
30 } catch {
31 return false;
32 }
33}
34
35let _liveOk: boolean | null = null;
36
37
42export async function checkLiveApp(): Promise<boolean> {
43 if (_liveOk === null) _liveOk = await apiReachable();
44 if (!_liveOk && SEC_REQUIRE_LIVE) {
45 throw new Error(
46 `SEC_REQUIRE_LIVE=1 but the API at ${SEC_API_URL} is unreachable — the stack is not up (CI should have booted it).`
47 );
48 }
49 return _liveOk;
50}
51
52
58export function apiFetch(
59 path: string,
60 init: RequestInit = {},
61 opts: { timeoutMs?: number } = {}
62): Promise<Response> {
63 const normalized =
64 path.startsWith("/api/v1/") || path.startsWith("/internal/")
65 ? path
66 : `/api/v1${path.startsWith("/") ? path : `/${path}`}`;
67 const headers = new Headers(init.headers);
68 if (init.body && !headers.has("content-type")) headers.set("content-type", "application/json");
69 return fetch(`${SEC_API_URL}${normalized}`, {
70 ...init,
71 headers,
72 signal: AbortSignal.timeout(opts.timeoutMs ?? 30_000)
73 });
74}
75
76
77export function webFetch(
78 path: string,
79 init: RequestInit = {},
80 opts: { timeoutMs?: number } = {}
81): Promise<Response> {
82 return fetch(`${SEC_WEB_URL}${path}`, { ...init, signal: AbortSignal.timeout(opts.timeoutMs ?? 15_000) });
83}
84