Skip to content
scout logoScoutUtilities
Trigram-indexed fuzzy search with per-field weights, match highlighting, and an optional reactive layer.
v0.0.14.2 KB gzipBrowser · Node.js · SSR · Deno
createIndexcreateReactiveSearchcreateSearchfindMatchRangeshighlighthighlightFieldtoFilterPredicatetoSearchFn

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.

FeatureArsenal fuzzy*Scout createIndexFuse.js
Bundle size~3 KB4.2 KB~23 KB
Zero dependencies @vielzeug/ripple peer (reactive layer only)
AlgorithmLevenshteinTrigram + Dice coefficientBitap
Query timeO(n·m)O(candidates)O(n·m)
Stateful index
Match highlighting
Reactive layerripple signals + debounce
Incremental updatesPartial

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/scout
sh
npm install @vielzeug/scout
sh
yarn add @vielzeug/scout

Quick 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 name matches over secondary fields; any field accepts a custom stringify
  • createReactiveSearch() — Index + reactive SearchState in one call; .index for incremental mutations
  • createSearch() — Reactive search state backed by an existing ScoutIndex; share one index across many states
  • highlight() / highlightField() — Split field text into HighlightPart[] fragments for styled rendering
  • findMatchRanges() — Compute match ranges for custom display strings (truncated previews, formatted values)
  • toSearchFn() — Drop-in searchFn adapter for sourcerer's LocalSource
  • toFilterPredicate() — Snapshot (item: T) => boolean predicate for Array.filter or vault queries
  • Incremental updates — add() / remove() / reindex() patch the index in O(field_length); no full rebuild

Documentation

See Also

  • Arsenal — Use fuzzyFilter for ad-hoc filtering of small lists (< 200 items) without building an index
  • RipplecreateReactiveSearch() and createSearch() use Ripple signals for reactive query state and debounce
  • SourcerertoSearchFn() adapts a ScoutIndex as a drop-in searchFn for createLocalSource
  • VaulttoFilterPredicate() wraps a one-time Scout query as a vault-compatible filter() predicate