import type { AdminApi, AuthApi, BlobsApi, PluginsApi, ProvidersApi, SharingApi, StorageApi, TurnsApi, } from '@scn-chat/server/api-types' import { type ClientResponse, hc } from 'hono/client' import type { SuccessStatusCode } from 'hono/utils/http-status' import { errorDetails, type Issue } from './lib/response.ts' /** Clients for calling the hono server's /api routes. **/ export const api = { auth: hc('/api'), chats: hc('/api'), turns: hc('/api'), providers: hc('/api'), plugins: hc('/api/plugins'), blobs: hc('/api'), sharing: hc('/api'), admin: hc('/api/admin'), } /** Build the request options for a JSON body with api calls. */ export const json = (body: unknown) => ({ init: { body: JSON.stringify(body), headers: { 'content-type': 'application/json' } }, }) const unauthorized = new Set<() => void>() /** Call a listener whenever a request finds the session has ended. */ export function onUnauthorized(listener: () => void): () => void { unauthorized.add(listener) return () => unauthorized.delete(listener) } export class ApiError extends Error { readonly status: number /** Problems with individual form fields. */ readonly issues: Issue[] constructor(status: number, message: string, issues: Issue[] = []) { super(message) this.name = 'ApiError' this.status = status this.issues = issues } } /** The body of the successful responses among a route's possible responses. */ type SuccessBody = R extends ClientResponse ? S extends SuccessStatusCode ? T : never : never /** Read a response's JSON. Throws ApiError on failure. */ export async function read>( response: Promise, ): Promise> { const res = await response if (res.status === 401) for (const listener of unauthorized) listener() if (!res.ok) { const { message, issues } = await errorDetails(res) if (res.status !== 401) console.error(`Request failed: ${res.status} ${res.url}`, message) throw new ApiError(res.status, message, issues) } return (await res.json()) as SuccessBody }