Files
ProjectE/apps/web-legacy/components/realtime-provider.tsx
T
Hermes fca56ab77e T1/Phase 1: scaffold Vite SPA + Hono API + Bun worker
- apps/web: Vite + React 19 + TanStack Router/Query + shadcn/ui
   - apps/api: Hono + Bun on :3001 with /api/health, /api/auth/*, /mcp stubs
   - apps/worker: Bun worker stub, DB connection, graceful SIGTERM
   - apps/web-legacy/: old Next.js code moved aside (preserved for T2-T8 reference)
   - Dockerfiles: api (Bun), worker (Bun), spa (multi-stage Caddy)
   - Caddyfile: serves dist + reverse-proxies /api/* + /mcp to api
   - docker-compose.yml: 4-service target (api, spa, db, worker)
   - packages/db/src/client.ts: shared Drizzle client for api + worker
   - db/client.ts: root-level alias for convenience

   Parent: t_e1cbd87d -> t_24c9c3fd (T0)
2026-08-01 01:15:31 +00:00

95 lines
2.5 KiB
TypeScript

'use client';
import {
createContext,
useContext,
useCallback,
useState,
useRef,
type ReactNode,
} from 'react';
import { useRealtime, type RealtimeEvent } from '@/hooks/use-realtime';
type EventCallback = (event: RealtimeEvent) => void;
interface RealtimeContextType {
connected: boolean;
error: string | null;
reconnect: () => void;
disconnect: () => void;
subscribe: (
collections: string[],
callback: EventCallback
) => () => void;
}
const RealtimeContext = createContext<RealtimeContextType | null>(null);
export function RealtimeProvider({ children }: { children: ReactNode }) {
const subscribersRef = useRef<Map<string, Set<EventCallback>>>(new Map());
const [, forceRender] = useState(0);
const handleEvent = useCallback((event: RealtimeEvent) => {
const collection = event.collection || '*';
const collectionSubs = subscribersRef.current.get(collection);
const globalSubs = subscribersRef.current.get('*');
if (collectionSubs) {
collectionSubs.forEach((cb) => cb(event));
}
if (globalSubs) {
globalSubs.forEach((cb) => cb(event));
}
}, []);
const { connected, error, reconnect, disconnect } = useRealtime({
onEvent: handleEvent,
});
const subscribe = useCallback(
(collections: string[], callback: EventCallback) => {
for (const collection of collections) {
if (!subscribersRef.current.has(collection)) {
subscribersRef.current.set(collection, new Set());
}
subscribersRef.current.get(collection)!.add(callback);
}
// Also register as a global subscriber
if (!subscribersRef.current.has('*')) {
subscribersRef.current.set('*', new Set());
}
subscribersRef.current.get('*')!.add(callback);
forceRender((n) => n + 1);
// Return unsubscribe function
return () => {
for (const collection of collections) {
subscribersRef.current.get(collection)?.delete(callback);
}
subscribersRef.current.get('*')?.delete(callback);
forceRender((n) => n + 1);
};
},
[]
);
return (
<RealtimeContext.Provider
value={{ connected, error, reconnect, disconnect, subscribe }}
>
{children}
</RealtimeContext.Provider>
);
}
export function useRealtimeContext() {
const context = useContext(RealtimeContext);
if (!context) {
throw new Error(
'useRealtimeContext must be used within a RealtimeProvider'
);
}
return context;
}