Files
stealth-ai-relay/frontend/src/lib/convex-polling.svelte.ts

114 lines
2.6 KiB
TypeScript

import { ConvexHttpClient } from 'convex/browser';
import { getContext, setContext } from 'svelte';
import type { FunctionReference, FunctionArgs, FunctionReturnType } from 'convex/server';
const POLLING_CONTEXT_KEY = 'convex-polling';
const POLL_INTERVAL = 1000;
type PollingContext = {
client: ConvexHttpClient;
};
export function hasWebSocketSupport(): boolean {
if (typeof window === 'undefined') return true;
try {
return 'WebSocket' in window && typeof WebSocket !== 'undefined';
} catch {
return false;
}
}
export function setupPollingConvex(url: string): void {
const client = new ConvexHttpClient(url);
setContext<PollingContext>(POLLING_CONTEXT_KEY, { client });
}
export function usePollingClient(): ConvexHttpClient {
const ctx = getContext<PollingContext>(POLLING_CONTEXT_KEY);
if (!ctx) {
throw new Error('Convex polling client not set up. Call setupPollingConvex first.');
}
return ctx.client;
}
type QueryState<T> = {
data: T | undefined;
error: Error | null;
isLoading: boolean;
};
export function usePollingMutation<Mutation extends FunctionReference<'mutation'>>(
mutation: Mutation
): (args: FunctionArgs<Mutation>) => Promise<FunctionReturnType<Mutation>> {
const client = usePollingClient();
return (args: FunctionArgs<Mutation>) => client.mutation(mutation, args);
}
export function usePollingQuery<Query extends FunctionReference<'query'>>(
query: Query,
argsGetter: () => FunctionArgs<Query> | 'skip'
): {
data: FunctionReturnType<Query> | undefined;
error: Error | null;
isLoading: boolean;
} {
const client = usePollingClient();
// eslint-disable-next-line prefer-const
let state = $state<QueryState<FunctionReturnType<Query>>>({
data: undefined,
error: null,
isLoading: true
});
let intervalId: ReturnType<typeof setInterval> | null = null;
let lastArgsJson = '';
async function poll() {
const args = argsGetter();
if (args === 'skip') {
state.isLoading = false;
return;
}
const argsJson = JSON.stringify(args);
if (argsJson !== lastArgsJson) {
state.isLoading = true;
lastArgsJson = argsJson;
}
try {
const result = await client.query(query, args);
state.data = result;
state.error = null;
state.isLoading = false;
} catch (err) {
state.error = err instanceof Error ? err : new Error(String(err));
state.isLoading = false;
}
}
$effect(() => {
poll();
intervalId = setInterval(poll, POLL_INTERVAL);
return () => {
if (intervalId) {
clearInterval(intervalId);
}
};
});
return {
get data() {
return state.data;
},
get error() {
return state.error;
},
get isLoading() {
return state.isLoading;
}
};
}