TypeScript SDKs for the poolse Chat-as-a-Service backend.
This repository is a pnpm monorepo. Four packages, each usable independently:
| Package | What | Use when |
|---|---|---|
@poolse/sdk |
Headless TypeScript SDK. REST + WebSocket + offline queue. No React, no DOM. | Building for Node, vanilla JS, mobile (RN), or you want zero UI opinions. |
@poolse/react |
Headless React hooks built on @poolse/sdk. Provider + useMessages / useTyping / usePresence / etc. No UI. |
You want real-time wired up but you're building your own UI from scratch. |
@poolse/react-ui |
Plug-and-play React components built on @poolse/react. CSS-variable theming + render-slot escape hatches. |
You want a working chat in 5 minutes; eject to lower layers later if needed. |
@poolse/react-native |
Plug-and-play React Native components — same component names + prop shapes as react-ui, JS theme tokens, Expo-ready. |
You're shipping an Expo / bare React Native app. |
You can mix and match. <ConversationView> uses the hooks from @poolse/react, which use the client from @poolse/sdk — drop down a layer at any point without re-architecting.
pnpm add @poolse/sdk @poolse/react @poolse/react-uinpx expo install \
@poolse/sdk @poolse/react @poolse/react-native \
react-native-svg \
react-native-markdown-display \
react-native-safe-area-context \
expo-clipboard \
expo-image-picker \
expo-document-picker \
expo-image-manipulatorAll peer dependencies are required — see @poolse/react-native README for what each one powers and the iOS Info.plist keys you'll need.
import '@poolse/react-ui/styles.css';
import { PoolseProvider } from '@poolse/react';
import { ConversationView } from '@poolse/react-ui';
const config = {
apiUrl: import.meta.env.VITE_POOLSE_URL ?? 'http://localhost:4000',
getToken: async () => fetchJwtFromYourBackend(),
};
export default function App() {
return (
<PoolseProvider config={config}>
<ConversationView conversationId="..." />
</PoolseProvider>
);
}import { PoolseProvider, useMessages, useTyping } from '@poolse/react';
function Chat({ conversationId }: { conversationId: string }) {
const { messages, send, loadMore, hasMore } = useMessages(conversationId);
const { typing, signalTyping } = useTyping(conversationId);
return (
<YourCustomChatUI
messages={messages}
typing={typing}
onSend={(body) => send({ body })}
onInput={signalTyping}
/>
);
}import { Poolse } from '@poolse/sdk';
const chat = new Poolse({ apiUrl, getToken });
const conv = chat.realtime.conversation('conv-uuid');
conv.onMessage((msg) => render(msg));
conv.sendTyping();
await chat.conversations.one('conv-uuid').messages.send({ body: 'hello' });@poolse/react-ui defaults are driven by CSS variables — rebrand without touching JS:
:root {
--poolse-color-primary: #ff5722;
--poolse-color-self-bubble: #ff5722;
--poolse-radius: 8px;
}Need more control? Three escape hatches in order:
- Render slots — swap a specific subcomponent:
<ConversationView renderMessage={(msg, currentUserId) => <MyBubble {...} />} />
- Component composition — use individual pieces (
<MessageBubble>,<MessageComposer>,<TypingIndicator>) directly. - Drop to
@poolse/react— write your own component using the same hooks<ConversationView>uses. No fork.
The SDK doesn't store user names or avatars — that's your app's data. Pass userResolver once on the provider and every sender label, mention dropdown, member list, and read-receipt tooltip lights up automatically:
<PoolseProvider
config={{
apiUrl,
getToken,
userResolver: async (userId) => {
const u = await fetch(`/api/users/by-poolse-id/${userId}`).then((r) => r.json());
return { displayName: u.full_name, avatarUrl: u.avatar_url };
},
}}
>See @poolse/react for caching semantics.
- A11y baseline —
role="log"messages, live regions for typing + status + connection, combobox ARIA on mentions, focus management on thread + lightbox, scopedprefers-reduced-motion. - Mobile baseline — 44px touch targets, 16px composer (no iOS auto-zoom),
safe-area-inseton overlays + composer,…tap-reveal for message actions, full-screen thread pane <760px. - Attachments — multi-file picker, drag-and-drop a batch onto the conversation, XHR progress per item with cancel + retry-on-error dismissal, image lightbox with ESC + click-outside + body-scroll lock.
All work runs inside Docker — nothing in this project should be installed on the host (matches the backend repo's rule).
docker compose run --rm node pnpm install # one-time
docker compose run --rm node pnpm check # everything CI runs (typecheck + lint + format + test + build)
docker compose run --rm node pnpm test # all packages
docker compose run --rm node pnpm -F @poolse/sdk test # just one package
docker compose run --rm node pnpm build # build all packagespoolse-js-sdk/
├── packages/
│ ├── sdk/ @poolse/sdk
│ ├── react/ @poolse/react
│ ├── react-ui/ @poolse/react-ui
│ └── react-native/ @poolse/react-native
├── tsconfig.base.json ← shared base for all packages
├── eslint.config.js ← shared (flat config, ESLint 9)
├── .prettierrc.json
├── docker-compose.yml
└── Dockerfile.dev