# @picloud/client TypeScript client for [PiCloud](../../README.md). Three capabilities, all **script-mediated** — there is no direct KV / docs / users access from the browser (the hybrid model, by design): 1. **Typed HTTP** to dev-defined script endpoints. 2. **SSE realtime** subscriptions to externally-subscribable pub/sub topics. 3. **Auth-flow helpers** over your own dev-defined login/logout endpoints. ```ts import { PicloudClient } from '@picloud/client'; const client = new PicloudClient({ baseURL: 'https://api.example.com', getAuthToken: () => localStorage.getItem('auth_token') }); // Typed HTTP interface CreateUserReq { name: string; email?: string; role: string } interface CreateUserRes { id: string; name: string; created_at: string } const user = await client .endpoint('/api/users') .post({ name: 'alice', role: 'admin' }); // SSE subscription const unsubscribe = client.subscribe('chat-room-123', (event) => { console.log('got event:', event.message); }); unsubscribe(); // Token-gated topic (token obtained from one of YOUR script endpoints, // which calls `pubsub::subscriber_token`) client.subscribe('chat-room-123', cb, { token: 'eyJhbGc...' }); // Auth helpers (call dev-defined endpoints under the hood) await client.auth.login('alice@example.com', 'password'); await client.auth.logout(); const token = client.auth.token; ``` ## React ```tsx import { PicloudProvider, useTopic, useEndpoint } from '@picloud/client/react'; // Wrap your tree once: function ChatRoom({ roomId }: { roomId: string }) { const messages = useTopic(`chat-room-${roomId}`); return ; } function UserProfile({ id }: { id: string }) { const { data, loading, error } = useEndpoint(`/api/users/${id}`).get(); if (loading) return ; if (error) return ; return
{data?.name}
; } ``` ## Svelte ```ts import { topicStore, endpointStore } from '@picloud/client/svelte'; const messages = topicStore(client, `chat-room-${roomId}`); // $messages is an array that grows as events arrive const userQuery = endpointStore(client, `/api/users/${id}`).get(); // $userQuery is { data, loading, error } ``` > The Svelte helpers take the `client` explicitly (a store isn't a component, > so there's no React-style context to read). ## Optional runtime validation (zod / valibot) No hard dependency — the adapter is the `{ parse(input): T }` shape. A Zod schema satisfies it directly; wrap Valibot in one line: ```ts import { z } from 'zod'; const UserSchema = z.object({ id: z.string(), name: z.string() }); const user = await client.endpoint('/api/users/1').get({ validate: UserSchema }); // valibot: import * as v from 'valibot'; const schema = v.object({ id: v.string() }); const adapter = { parse: (i: unknown) => v.parse(schema, i) }; ``` ## Transport notes - SSE is implemented over streaming `fetch` (not native `EventSource`) so the client can refresh an expired token on a 401, send `Last-Event-ID` on resume, and apply its own exponential backoff (1s → 2s → 4s … capped at 30s). - **React Native** has no native `EventSource`, but it also can't stream `fetch` bodies on all engines — if you target RN, supply a streaming-capable `fetch` polyfill via the `fetch` option, or use a `react-native-sse`-based adapter. (Server-side `Last-Event-ID` replay is not implemented in v1.1.6; the client sends the header so it's ready when the server adds replay.) ## Build / test ```sh npm install npm run lint # tsc --noEmit (strict) npm run test # vitest npm run build # tsup → dist/ (ESM + CJS + .d.ts) ```