Files
investor-flow/app/src/lib/trpc.ts
T

84 lines
2.5 KiB
TypeScript
Raw Normal View History

// Thin typed client over the tRPC backend (DESIGN.md §2). Slice 1 uses a fetch wrapper
// rather than @trpc/client to keep the Next app decoupled from the server's Node-native
// type graph; the server is a real tRPC server and full end-to-end client types can be
// layered later without changing this surface.
export interface Quote {
symbol: string;
price: number;
bid?: number | null;
ask?: number | null;
change?: number | null;
changePercent?: number | null;
iv?: number | null;
}
export interface PriceCandle {
ts: string;
o: number;
h: number;
l: number;
c: number;
v: number;
adjClose?: number | null;
}
export interface SymbolMeta {
symbol: string;
name?: string | null;
sector?: string | null;
industry?: string | null;
exchange?: string | null;
tickerKind: string;
peers?: string[] | null;
}
export interface Snapshot {
symbol: string;
quote: Quote | null;
candles: PriceCandle[] | null;
sector: SymbolMeta | null;
stale: { quote: boolean; candles: boolean; sector: boolean };
}
export interface AuthUser {
userId: string;
email?: string;
complexity?: string;
riskTolerance?: string;
convexityPosture?: string;
}
async function parse<T>(res: Response): Promise<T> {
const json = await res.json();
if (json?.error) {
const msg = json.error.message ?? `tRPC error ${json.error.data?.code ?? ""}`;
throw new Error(typeof msg === "string" ? msg : "tRPC error");
}
return json.result.data as T;
}
async function trpcQuery<T>(procedure: string, input?: unknown): Promise<T> {
const search = input ? `?input=${encodeURIComponent(JSON.stringify(input))}` : "";
const res = await fetch(`/api/trpc/${procedure}${search}`, {
headers: { "content-type": "application/json" },
credentials: "include",
});
return parse<T>(res);
}
async function trpcMutate<T>(procedure: string, input: unknown): Promise<T> {
const res = await fetch(`/api/trpc/${procedure}`, {
method: "POST",
headers: { "content-type": "application/json" },
credentials: "include",
body: JSON.stringify(input),
});
return parse<T>(res);
}
export const api = {
market: { snapshot: (symbol: string) => trpcQuery<Snapshot>("market.snapshot", { symbol }) },
auth: {
signup: (email: string, password: string) => trpcMutate<{ userId: string }>("auth.signup", { email, password }),
login: (email: string, password: string) => trpcMutate<{ userId: string }>("auth.login", { email, password }),
logout: () => trpcMutate<{ ok: boolean }>("auth.logout", {}),
me: () => trpcQuery<AuthUser | null>("auth.me"),
},
};