everything-dev
Version:
A consolidated product package for building Module Federation apps with oRPC APIs.
1 lines • 7.05 kB
Source Map (JSON)
{"version":3,"file":"site-client.cjs","names":["Context","Layer"],"sources":["../../../src/services/auth/site-client.ts"],"sourcesContent":["import { Context, Layer } from \"effect\";\n\n/**\n * The ONE site HTTP client (ticket #423): every CLI→site call — better-auth\n * `/api/auth/*` routes, oRPC `/api/rpc/*` routes, and plain account reads —\n * goes through here, with error parsing that understands every payload shape\n * the server emits. Previously three fetch styles hand-parsed three shapes;\n * the oRPC envelope (`{ json: { defined, code, message, data } }`) was the\n * one nobody parsed, so the server's FORBIDDEN detail was lost behind a bare\n * \"Deploy key could not be created (403)\".\n */\n\nexport interface SiteResponse {\n status: number;\n json: unknown;\n setCookie: string | null;\n}\n\n/** Extract the human detail from any error body shape the server emits —\n * undefined when nothing recognizable is present. */\nexport function parseSiteErrorDetail(status: number, json: unknown): string | undefined {\n if (json === null || json === undefined || typeof json !== \"object\") return undefined;\n const body = json as Record<string, unknown>;\n\n // oRPC envelope: the error object rides in `json` (success payloads ride\n // there too), carrying { defined, code, message, data }\n const inner = typeof body.json === \"object\" && body.json !== null ? body.json : body;\n const innerBody = inner as Record<string, unknown>;\n\n if (typeof innerBody.message === \"string\") return innerBody.message;\n if (\n typeof body.error === \"object\" &&\n body.error !== null &&\n !Array.isArray(body.error) &&\n typeof (body.error as Record<string, unknown>).message === \"string\"\n ) {\n return (body.error as { message: string }).message;\n }\n if (Array.isArray(body.error)) {\n const first = body.error[0] as Record<string, unknown> | undefined;\n if (first && typeof first.message === \"string\") return first.message;\n }\n void status;\n return undefined;\n}\n\nexport interface SiteClientShape {\n /** POST to a better-auth `/api/auth/*` route (origin header included for\n * cookie-bearing requests). */\n authPost: (\n siteUrl: string,\n path: string,\n body: unknown,\n cookiePair?: string,\n extraHeaders?: Record<string, string>,\n signal?: AbortSignal,\n ) => Promise<SiteResponse>;\n /** POST to an oRPC `/api/rpc/*` route — the input is wrapped in the RPC\n * `json` envelope; errors parse through `parseSiteErrorDetail`. */\n rpcPost: (\n siteUrl: string,\n path: string,\n input: unknown,\n cookiePair?: string,\n extraHeaders?: Record<string, string>,\n ) => Promise<SiteResponse>;\n /** Plain GET returning parsed JSON (account resolution). */\n getJson: (url: string, cookiePair?: string) => Promise<unknown>;\n}\n\nexport class SiteClient extends Context.Service<SiteClient, SiteClientShape>()(\n \"everything-dev/auth/site-client\",\n) {\n static readonly Live = Layer.succeed(\n this,\n SiteClient.of({\n authPost: (siteUrl, path, body, cookiePair, extraHeaders, signal) =>\n authPost(siteUrl, path, body, cookiePair, extraHeaders, signal),\n rpcPost: (siteUrl, path, input, cookiePair, extraHeaders) =>\n rpcPost(siteUrl, path, input, cookiePair, extraHeaders),\n getJson: (url, cookiePair) => getJson(url, cookiePair),\n }),\n );\n}\n\nexport async function authPost(\n siteUrl: string,\n path: string,\n body: unknown,\n cookiePair?: string,\n extraHeaders?: Record<string, string>,\n signal?: AbortSignal,\n): Promise<SiteResponse> {\n // Better Auth's origin check demands an Origin on cookie-bearing\n // requests — present the site's own origin, exactly as a browser would.\n const origin = new URL(siteUrl).origin;\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n origin,\n ...extraHeaders,\n };\n if (cookiePair) headers.cookie = cookiePair;\n const response = await fetch(`${siteUrl.replace(/\\/$/, \"\")}/api/auth${path}`, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n signal,\n });\n return {\n status: response.status,\n json: await response.json().catch(() => ({})),\n setCookie: response.headers.get(\"set-cookie\"),\n };\n}\n\nexport async function rpcPost(\n siteUrl: string,\n path: string,\n input: unknown,\n cookiePair?: string,\n extraHeaders?: Record<string, string>,\n): Promise<SiteResponse> {\n const origin = new URL(siteUrl).origin;\n const headers: Record<string, string> = {\n \"content-type\": \"application/json\",\n origin,\n ...(extraHeaders ?? {}),\n };\n if (cookiePair) headers.cookie = cookiePair;\n const response = await fetch(`${siteUrl.replace(/\\/$/, \"\")}${path}`, {\n method: \"POST\",\n headers,\n body: JSON.stringify({ json: input }),\n });\n return {\n status: response.status,\n json: await response.json().catch(() => ({})),\n setCookie: response.headers.get(\"set-cookie\"),\n };\n}\n\nexport async function getJson(url: string, cookiePair?: string): Promise<unknown> {\n const response = await fetch(url, {\n headers: cookiePair ? { cookie: cookiePair } : {},\n });\n return (await response.json().catch(() => ({}))) as unknown;\n}\n"],"mappings":";;;;;AAoBA,SAAgB,qBAAqB,QAAgB,MAAmC;CACtF,IAAI,SAAS,QAAQ,SAAS,UAAa,OAAO,SAAS,UAAU,OAAO;CAC5E,MAAM,OAAO;CAKb,MAAM,YADQ,OAAO,KAAK,SAAS,YAAY,KAAK,SAAS,OAAO,KAAK,OAAO;CAGhF,IAAI,OAAO,UAAU,YAAY,UAAU,OAAO,UAAU;CAC5D,IACE,OAAO,KAAK,UAAU,YACtB,KAAK,UAAU,QACf,CAAC,MAAM,QAAQ,KAAK,KAAK,KACzB,OAAQ,KAAK,MAAkC,YAAY,UAE3D,OAAQ,KAAK,MAA8B;CAE7C,IAAI,MAAM,QAAQ,KAAK,KAAK,GAAG;EAC7B,MAAM,QAAQ,KAAK,MAAM;EACzB,IAAI,SAAS,OAAO,MAAM,YAAY,UAAU,OAAO,MAAM;CAC/D;AAGF;AA0BA,IAAa,aAAb,MAAa,mBAAmBA,eAAQ,QAAqC,CAAC,CAC5E,iCACF,CAAC,CAAC;CACA,OAAgB,OAAOC,aAAM,QAC3B,MACA,WAAW,GAAG;EACZ,WAAW,SAAS,MAAM,MAAM,YAAY,cAAc,WACxD,SAAS,SAAS,MAAM,MAAM,YAAY,cAAc,MAAM;EAChE,UAAU,SAAS,MAAM,OAAO,YAAY,iBAC1C,QAAQ,SAAS,MAAM,OAAO,YAAY,YAAY;EACxD,UAAU,KAAK,eAAe,QAAQ,KAAK,UAAU;CACvD,CAAC,CACH;AACF;AAEA,eAAsB,SACpB,SACA,MACA,MACA,YACA,cACA,QACuB;CAIvB,MAAM,UAAkC;EACtC,gBAAgB;EAChB,QAHa,IAAI,IAAI,OAAO,CAAC,CAAC;EAI9B,GAAG;CACL;CACA,IAAI,YAAY,QAAQ,SAAS;CACjC,MAAM,WAAW,MAAM,MAAM,GAAG,QAAQ,QAAQ,OAAO,EAAE,EAAE,WAAW,QAAQ;EAC5E,QAAQ;EACR;EACA,MAAM,KAAK,UAAU,IAAI;EACzB;CACF,CAAC;CACD,OAAO;EACL,QAAQ,SAAS;EACjB,MAAM,MAAM,SAAS,KAAK,CAAC,CAAC,aAAa,CAAC,EAAE;EAC5C,WAAW,SAAS,QAAQ,IAAI,YAAY;CAC9C;AACF;AAEA,eAAsB,QACpB,SACA,MACA,OACA,YACA,cACuB;CAEvB,MAAM,UAAkC;EACtC,gBAAgB;EAChB,QAHa,IAAI,IAAI,OAAO,CAAC,CAAC;EAI9B,GAAI,gBAAgB,CAAC;CACvB;CACA,IAAI,YAAY,QAAQ,SAAS;CACjC,MAAM,WAAW,MAAM,MAAM,GAAG,QAAQ,QAAQ,OAAO,EAAE,IAAI,QAAQ;EACnE,QAAQ;EACR;EACA,MAAM,KAAK,UAAU,EAAE,MAAM,MAAM,CAAC;CACtC,CAAC;CACD,OAAO;EACL,QAAQ,SAAS;EACjB,MAAM,MAAM,SAAS,KAAK,CAAC,CAAC,aAAa,CAAC,EAAE;EAC5C,WAAW,SAAS,QAAQ,IAAI,YAAY;CAC9C;AACF;AAEA,eAAsB,QAAQ,KAAa,YAAuC;CAIhF,OAAQ,OAAM,MAHS,MAAM,KAAK,EAChC,SAAS,aAAa,EAAE,QAAQ,WAAW,IAAI,CAAC,EAClD,CAAC,EACqB,CAAC,KAAK,CAAC,CAAC,aAAa,CAAC,EAAE;AAChD"}