wm-realtime-kit
v1.0.0
Published
Shared realtime WebSocket + TanStack Query package for all apps
Downloads
15
Maintainers
Readme
@your-org/realtime
Shared realtime package for all apps. Wraps WebSocket (API Gateway) + TanStack Query so UI updates automatically when your Lambda sends an event — no page refresh needed.
How it works
User action → Lambda → saves to DB
→ sends WebSocket event (e.g. "user-created")
→ all connected clients receive it
→ TanStack Query invalidates cache → refetches
→ UI updates instantly ✅Installation
npm install @your-org/realtime
# peer deps (already in your apps)
npm install @tanstack/react-query reactSetup (once per app)
1. Wrap your layout with RealtimeProvider
app/layout.tsx
import { RealtimeProvider } from '@your-org/realtime';
export default function RootLayout({ children }) {
return (
<html>
<body>
<RealtimeProvider config={{ wsUrl: process.env.NEXT_PUBLIC_WS_URL! }}>
{children}
</RealtimeProvider>
</body>
</html>
);
}
NEXT_PUBLIC_WS_URLis set per branch in Amplify — no code changes needed between dev and prod.
2. Authenticated connections (optional)
If your WebSocket API Gateway checks $request.header.Authorization, pass the Cognito token:
<RealtimeProvider config={{
wsUrl: process.env.NEXT_PUBLIC_WS_URL!,
token: idToken, // from your auth package
}}>Usage
useRealtimeQuery — replace useQuery / useEffect fetches
import { useRealtimeQuery } from '@your-org/realtime';
// Before
const [users, setUsers] = useState([]);
useEffect(() => {
fetch('/api/users').then(r => r.json()).then(setUsers);
}, []);
// After — 2 lines change, everything else is automatic
const { data: users } = useRealtimeQuery({
queryKey: ['users'],
queryFn: () => fetch('/api/users').then(r => r.json()),
realtimeEvent: 'user-updated', // or ['user-created', 'user-updated', 'user-deleted']
});All standard TanStack Query options (staleTime, enabled, select, etc.) are supported.
useRealtimeEvent — react to an event without fetching
import { useRealtimeEvent } from '@your-org/realtime';
useRealtimeEvent('notification-received', (data) => {
toast.info(data.message);
});useRealtimeStatus — show a connection indicator
import { useRealtimeStatus } from '@your-org/realtime';
const status = useRealtimeStatus();
// "connecting" | "connected" | "disconnected" | "error"useRealtime — raw access (send messages, manual subscribe)
import { useRealtime } from '@your-org/realtime';
const { send, subscribe, status } = useRealtime();
// Send a message to the server
send({ action: 'ping' });Lambda usage — @your-org/realtime/lambda
Call this from any Lambda after a DB mutation. It handles connection lookup, fan-out, and stale connection cleanup automatically.
Setup (env vars per Lambda — no code changes between dev/prod)
WS_ENDPOINT=https://xxx.execute-api.region.amazonaws.com/devThat's the only env var needed. The Prisma client you already have handles the DB side.
broadcastEvent(prisma, event, data, options?)
import { broadcastEvent } from '@your-org/realtime/lambda';
// Broadcast to ALL connected users
await broadcastEvent(prisma, 'mindmap-created', newMindmap);
// Broadcast to specific users only
await broadcastEvent(prisma, 'user-updated', updatedUser, {
userIds: ['user-123', 'user-456'],
});Full Lambda example
import { broadcastEvent } from '@your-org/realtime/lambda';
export const handler = async (event) => {
// 1. Save to DB as normal
const newUser = await prisma.user.create({ data: { ... } });
// 2. Broadcast — one line, everything else is automatic
await broadcastEvent(prisma, 'user-created', newUser);
return { statusCode: 200, body: JSON.stringify(newUser) };
};What broadcastEvent handles internally
| Thing | Handled automatically |
|---|---|
| Look up connections from WebSocketConnection table | ✅ |
| API Gateway Management API client setup | ✅ |
| Fan-out to all connections in parallel | ✅ |
| Stale connection cleanup (410 Gone) | ✅ |
| Broadcast to all users or specific userIds | ✅ |
Return value
const result = await broadcastEvent(prisma, 'user-created', data);
// { sent: 5, failed: 0, staleCleaned: 1 }Lambda — manual approach (if not using the package)
If you prefer to handle WebSocket sending yourself, your Lambda just needs to post to the API Gateway connection endpoint:
// Lambda handler (Node.js)
import { ApiGatewayManagementApiClient, PostToConnectionCommand } from '@aws-sdk/client-apigatewaymanagementapi';
const client = new ApiGatewayManagementApiClient({
endpoint: `https://${domainName}/${stage}`,
});
// Send to all connected clients (iterate your connections table)
await client.send(new PostToConnectionCommand({
ConnectionId: connectionId,
Data: JSON.stringify({
event: 'user-created', // ← matches realtimeEvent in useRealtimeQuery
data: { id: '123', name: 'Alice' },
}),
}));Environment variables
| Variable | Dev | Prod |
|---|---|---|
| NEXT_PUBLIC_WS_URL | wss://xxx.execute-api.region.amazonaws.com/dev | wss://xxx.execute-api.region.amazonaws.com/prod |
Set these per branch in Amplify. The package reads whichever value is present — no code changes needed.
Per-app integration checklist
- [ ] Add
NEXT_PUBLIC_WS_URLto Amplify environment variables (dev + prod branches) - [ ] Wrap
app/layout.tsxwith<RealtimeProvider> - [ ] Replace
useEffectfetches withuseRealtimeQuery - [ ] Done ✅
Estimated time per app: 1–2 hours
