Files
xevion.dev/web/src/lib/api.server.ts
Xevion dd1ce186d2 refactor: consolidate HTTP client for TCP/Unix socket handling
Extract reqwest client creation into dedicated HttpClient abstraction that handles both TCP and Unix socket connections transparently. Simplifies proxy logic by removing duplicate URL construction and client selection throughout the codebase.
2026-01-07 14:34:32 -06:00

89 lines
2.4 KiB
TypeScript

import { getLogger } from "@logtape/logtape";
import { env } from "$env/dynamic/private";
const logger = getLogger(["ssr", "lib", "api"]);
interface FetchOptions extends RequestInit {
fetch?: typeof fetch;
}
interface BunFetchOptions extends RequestInit {
unix?: string;
}
/**
* Create a socket-aware fetch function
* Automatically handles Unix socket vs TCP based on UPSTREAM_URL
*/
function createSmartFetch(upstreamUrl: string | undefined) {
if (!upstreamUrl) {
const error = "UPSTREAM_URL environment variable not set";
logger.error(error);
throw new Error(error);
}
const isUnixSocket =
upstreamUrl.startsWith("/") || upstreamUrl.startsWith("./");
const baseUrl = isUnixSocket ? "http://localhost" : upstreamUrl;
return async function smartFetch<T>(
path: string,
options?: FetchOptions,
): Promise<T> {
const url = `${baseUrl}${path}`;
const method = options?.method ?? "GET";
// Unix sockets require Bun's native fetch
// SvelteKit's fetch doesn't support the 'unix' option
const fetchFn = isUnixSocket ? fetch : (options?.fetch ?? fetch);
const fetchOptions: BunFetchOptions = {
...options,
signal: options?.signal ?? AbortSignal.timeout(30_000),
};
// Remove custom fetch property from options (not part of standard RequestInit)
delete (fetchOptions as Record<string, unknown>).fetch;
// Add Unix socket path if needed
if (isUnixSocket) {
fetchOptions.unix = upstreamUrl;
}
logger.debug("API request", {
method,
url,
path,
isUnixSocket,
});
try {
const response = await fetchFn(url, fetchOptions);
if (!response.ok) {
logger.error("API request failed", {
method,
url,
status: response.status,
statusText: response.statusText,
});
throw new Error(`API error: ${response.status} ${response.statusText}`);
}
const data = await response.json();
logger.debug("API response", { method, url, status: response.status });
return data;
} catch (error) {
logger.error("API request exception", {
method,
url,
error: error instanceof Error ? error.message : String(error),
});
throw error;
}
};
}
// Export the configured smart fetch function
export const apiFetch = createSmartFetch(env.UPSTREAM_URL);