Why Scout?
Arsenal's fuzzy / fuzzyFilter helpers perform pairwise Levenshtein distance — O(n·m) per item per query. For ≤200 items they are fine. For 500–100k items with real-time keystrokes, you need an index.
Scout builds a trigram inverted index at construction time. Query time is O(candidates) — only items that share at least one trigram with the query are scored, so performance stays flat as the corpus grows.
| Feature | Arsenal fuzzy* | Scout createIndex | Fuse.js |
|---|---|---|---|
| Bundle size | ~3 KB | 4.2 KB | ~23 KB |
| Zero dependencies | @vielzeug/ripple peer (reactive layer only) | ||
| Algorithm | Levenshtein | Trigram + Dice coefficient | Bitap |
| Query time | O(n·m) | O(candidates) | O(n·m) |
| Stateful index | |||
| Match highlighting | |||
| Reactive layer | ripple signals + debounce | ||
| Incremental updates | Partial |
Use Scout when you need search over 500+ items, real-time UI search boxes (combobox, command palette), or reactive query state with ripple signals.
Consider arsenal.fuzzyFilter when you have fewer than 200 items and don't need a persistent index.
Installation
sh
pnpm add @vielzeug/scoutsh
npm install @vielzeug/scoutsh
yarn add @vielzeug/scoutQuick Start
ts
import { createIndex } from '@vielzeug/scout';
const index = createIndex(users, {
fields: [
{ field: 'name', weight: 2 }, // name ranks higher
{ field: 'email' },
],
});
const results = index.search('alice');
// [{ item: User, score: 0.85, matches: [{ field: 'name', ranges: [[0, 5]] }] }]Features
createIndex()— Trigram inverted index; construction O(corpus × field_length), query O(candidates)- Per-field weights — Promote
namematches over secondary fields; any field accepts a customstringify createReactiveSearch()— Index + reactiveSearchStatein one call;.indexfor incremental mutationscreateSearch()— Reactive search state backed by an existingScoutIndex; share one index across many stateshighlight()/highlightField()— Split field text intoHighlightPart[]fragments for styled renderingfindMatchRanges()— Compute match ranges for custom display strings (truncated previews, formatted values)toSearchFn()— Drop-insearchFnadapter for sourcerer'sLocalSourcetoFilterPredicate()— Snapshot(item: T) => booleanpredicate forArray.filteror vault queries- Incremental updates —
add()/remove()/reindex()patch the index in O(field_length); no full rebuild
Documentation
See Also
- Arsenal — Use
fuzzyFilterfor ad-hoc filtering of small lists (< 200 items) without building an index - Ripple —
createReactiveSearch()andcreateSearch()use Ripple signals for reactive query state and debounce - Sourcerer —
toSearchFn()adapts aScoutIndexas a drop-insearchFnforcreateLocalSource - Vault —
toFilterPredicate()wraps a one-time Scout query as a vault-compatiblefilter()predicate