Production-ready React integration recipe for webgpu-search. Provides an ergonomic, non-blocking hook with automatic query debouncing, race condition elimination, dynamic mutations, Unicode-exact highlight rendering, and observability telemetry.
- WebGPU Acceleration with Graceful CPU Fallback: Automatically harnesses client WebGPU compute pipelines and falls back cleanly to the multi-field reference CPU scorer on unsupported hardware or small corpora.
- Dedicated Web Worker Support: Run tokenization, dispatch, and ranking completely off the main thread via
SearchWorkerClientwith zero UI freezing. - Race Condition Immunity: Monotonic query counters and
AbortControllercancellation ensure slow earlier queries never overwrite fresh results. - Live Dynamic Mutations: Incremental batched
add,update, andremovemethods update the search state and mutation epoch without reloading or reallocating whole datasets. - Unicode-Exact Highlighting: Highlight ranges and
<mark>tags map accurately to original UTF-16 strings, preserving indentation and grapheme clusters. - Real-Time Observability: Exposes engine type (
webgpuvscpu), fallback reasons, memory metrics (vramBytes,ramBytes,totalBytes), and query latency timings.
bun add webgpu-search
# or
npm install webgpu-searchimport React from 'react';
import { useDocumentSearch } from './useDocumentSearch';
interface FileItem {
id: string;
name: string;
path: string;
}
const files: FileItem[] = [
{ id: '1', name: 'App.tsx', path: 'src/App.tsx' },
{ id: '2', name: 'SearchWorker.ts', path: 'src/workers/SearchWorker.ts' },
{ id: '3', name: 'fuzzy.wgsl', path: 'src/shaders/fuzzy.wgsl' },
];
export function FilePalette() {
const { query, setQuery, results, timings, engine } = useDocumentSearch<FileItem>({
initialDocs: files,
indexOptions: {
fields: [
{ name: 'name', weight: 2.0 },
{ name: 'path', weight: 1.0 },
],
},
searchOptions: {
mode: 'fuzzy',
highlight: true,
tag: 'mark',
escapeHtml: true,
limit: 20,
},
});
return (
<div>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="QuickOpen file..."
/>
<div>
Engine: {engine} | Latency: {timings?.totalMs.toFixed(2)} ms
</div>
<ul>
{results.map((hit) => (
<li key={String(hit.id)}>
{hit.highlightedText?.name ? (
<div dangerouslySetInnerHTML={{ __html: hit.highlightedText.name }} />
) : (
<div>{hit.doc.name}</div>
)}
<small>{hit.doc.path}</small>
</li>
))}
</ul>
</div>
);
}To search 50,000+ records off the main thread, pass a worker factory:
const { query, setQuery, results } = useDocumentSearch<FileItem>({
worker: () => new Worker(new URL('./search.worker.ts', import.meta.url), { type: 'module' }),
initialDocs: largeCorpus,
indexOptions: {
fields: ['title', 'content'],
},
});Where search.worker.ts contains:
import { startSearchWorker } from 'webgpu-search/worker';
startSearchWorker();const { add, remove, stats, mutationEpoch } = useDocumentSearch<Doc>({ ... });
// Add documents at runtime
await add({ id: 'doc-99', title: 'New Item' });
// Inspect memory and hardware allocations
console.log('VRAM bytes:', stats?.memory.vramBytes);
console.log('RAM bytes:', stats?.memory.ramBytes);
console.log('Fallback Reason:', stats?.fallbackReason);
console.log('Mutation Epoch:', mutationEpoch);