# @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.