# @crema/arcadia-core-client Typed HTTP client + React bindings for the [arcadia](https://git.sky-ai.com/CremaUIStudio/arcadia-core) Phoenix API. Wraps the OpenAPI-spec'd surface at `/api/v1` with auth (Bearer JWT and/or service-account API key), tenant context (`X-Tenant-ID`), idempotency keys, error normalization, and rate-limit-aware retry. The client is **stateless about how the JWT is obtained** — callers pass `getToken` so token refresh and storage stay app-owned. ## Public API ```ts import { createArcadiaClient, ArcadiaProvider, useArcadia, useArcadiaClient, ArcadiaError, } from "@crema/arcadia-core-client"; ``` ## Usage in a Crema app In `app/root.tsx`, wrap providers: ```tsx import { ArcadiaProvider } from "@crema/arcadia-core-client"; sessionStorage.getItem("arcadia_token")} onUnauthorized={() => navigate("/login")} > {children} ; ``` In any component: ```tsx import { useArcadiaClient, ArcadiaError } from "@crema/arcadia-core-client"; function ResourceList() { const arcadia = useArcadiaClient(); const [items, setItems] = useState([]); useEffect(() => { arcadia .GET<{ data: Resource[] }>("/api/v1/digital_objects", { params: { page: 1 } }) .then((res) => setItems(res.data)) .catch((err: ArcadiaError) => { if (err.isRateLimited) showToast("Too many requests, slow down."); }); }, [arcadia]); } ``` ## Generated types Endpoint request/response types come from arcadia's live OpenAPI spec. Regenerate with: ```bash ARCADIA_OPENAPI_URL=http://localhost:4000/api/openapi \ node ../lib-arcadia-core-client/scripts/sync-spec.mjs ``` Requires `openapi-typescript` in the consuming app's devDeps: ```bash npm i -D openapi-typescript ``` The generated file lives at `src/generated/openapi.d.ts`. Until it's been generated at least once, the file is a stub and only the hand-written types in `src/types.ts` are useful. ## Two surfaces: generic + typed The client gives you both a generic-string API and a fully typed (OpenAPI-driven) API. They share the same auth/retry/error plumbing. ```ts const arcadia = useArcadiaClient(); // Generic — accepts any string path. Use when the spec is incomplete or // when calling endpoints outside the spec. const res = await arcadia.GET<{ data: Resource[] }>( "/api/v1/digital_objects", { params: { page: 1 } }, ); // Typed — paths, params, and responses inferred from the generated spec. // Throws ArcadiaError on non-2xx. const { data } = await arcadia.typed.GET("/api/v1/digital_objects", { params: { query: { page: 1 } }, }); ``` ## Realtime Phoenix Channels at `/socket/tenant`, opt-in via the provider. The socket auto-connects when `enableRealtime` is on, joins `tenant:` (and optionally `tenant::user:`), and disconnects on unmount. ```tsx sessionStorage.getItem("arcadia_access_token")} > … ``` ```tsx import { useArcadiaSubscription } from "@crema/arcadia-core-client"; function NotificationToasts() { useArcadiaSubscription("notification", (n) => { toast(n.title, n.body); }); return null; } ``` Known events on `TenantEventMap`: `notification`, `digital_object`, `announcement`, `status_update`, `event`, plus social: `social:notification`, `social:post`, `social:article`. The map is open-ended — apps can subscribe to any string event arcadia emits; payload type defaults to `Record`. For user-scoped events (those filtered to the current user), pass `{ scope: "user" }` and ensure `userId` was provided to the provider. ## Social bindings For the social surfaces (profiles, articles, discussion board, favourites, DNA-change subscriptions, notifications, search, @-mention search), there's a typed wrapper over the generic client: ```ts import { createArcadiaClient, createSocialBindings } from "@crema/arcadia-core-client"; const client = createArcadiaClient({ baseUrl, getToken }); const social = createSocialBindings(client); await social.createPost({ body_md: "hello" }); await social.search("agent design"); await social.addSubscription(agentId, { lastSeenDnaHash: currentHash }); ``` In a React app, prefer the `useSocial()` hook pattern (see `arcadia-aifirst-starter/app/lib/api/social.ts`) so the bindings pick up the ArcadiaProvider's auth context automatically. Realtime: the social write paths broadcast via Phoenix.PubSub → `ArcadiaWeb.TenantChannel` forwards them as `social:notification` / `social:post` / `social:article` channel events. Subscribe via `useArcadiaSubscription`. ## What's not in here yet - **TanStack Query helpers** — opt-in. Vibespace doesn't use Query today; we can layer it via a sub-export later. - **Token refresh helper** — the client surfaces 401 via `onUnauthorized`; the app decides whether to refresh + retry. A reference refresh helper may land later. ## Conventions - Inline imports only — no own `package.json` (lib lives by the consuming app's deps). - Path-aliased into apps via `tsconfig.json` `paths`: `@crema/arcadia-core-client` → `../lib-arcadia-core-client/src/index.tsx`. - Tailwind doesn't scan this lib — no UI; nothing to scan.