A powerful fuzzy search library for client-side collections in TypeScript.
- Zero Dependencies - Custom Levenshtein implementation, no external deps
- Fuzzy Matching - Configurable threshold (0-1) for fuzzy string matching
- Nested Field Support - Dot notation for deeply nested object properties
- Keywords - Add custom filtering logic for exact keyword matches
- Framework Integrations - React, Vue, Svelte, Solid, and Preact support
- TypeScript - Full type definitions included
npm install spotrYou can use Spotr via a CDN:
- unpkg:
https://unpkg.com/spotr - jsDelivr:
https://cdn.jsdelivr.net/npm/spotr - esm.sh:
https://esm.sh/spotr
import { Spotr } from 'spotr';
type Game = {
title: string;
genres: string[];
releaseYear: number;
completed: boolean;
};
const games = new Spotr<Game>({
collection: gamesArray,
threshold: 0.3,
fields: [
{ name: 'title', weight: 1 },
{ name: 'releaseYear', weight: 0.8 },
],
limit: 20,
});
const { results, matchedKeywords, tokens, warnings } = games.query('witcher');
// results: Array<{ item: Game, score: number }>Array or Set of objects to search.
collection: gamesArray; // or new Set(gamesArray)Properties to search against with weight configuration. Supports dot notation for nested objects.
fields: [
'title', // string shorthand, weight: 1
{ name: 'title', weight: 1 }, // full config
{ name: 'email', weight: 0.7 }, // lower priority
{ name: 'address.city', weight: 0.8 }, // nested field
];Keywords that trigger specific collection filtering before fuzzy matching.
keywords: {
mode: 'and',
definitions: [
{
name: 'completed',
triggers: ['done', 'complete', 'finished'],
handler: (collection) => collection.filter(item => item.completed),
},
{
name: 'platform',
triggers: ['ps4', 'ps5', 'xbox', 'pc', 'switch'],
handler: (collection, matchedTerms) =>
collection.filter(item =>
matchedTerms.some(term =>
item.platforms.some(p => p.toLowerCase().includes(term))
)
),
},
],
}Global fuzzy matching threshold. Default: 0.3
0= match anything (no filtering)0.3= recommended default1= exact match required
Maximum number of results to return. Default: Infinity
Enable case-sensitive matching. Default: false
Minimum query length to trigger matching. Default: 1
Maximum string length limit for both search query tokens and collection field values before truncation. Default: 1000
- Search query tokens (search terms) exceeding this limit are truncated
- Collection field values exceeding this limit are truncated
- Warnings are added to
result.warningswhen truncation occurs - Used for performance optimization to prevent slowdowns with very long strings in fuzzy matching
interface SpotrResult<T> {
results: ScoredResult<T>[]; // Array of { item, score }
matchedKeywords: MatchedKeyword[]; // Keywords that matched
tokens: string[]; // Non-keyword search terms
warnings: string[]; // Warnings (e.g., missing nested paths)
}import { useSpotr } from 'spotr/react';
function GameSearch({ games, searchQuery }) {
const spotr = useSpotr({
collection: games,
fields: [{ name: 'title', weight: 1 }],
});
const { results } = useMemo(
() => spotr.query(searchQuery),
[spotr, searchQuery]
);
return <ResultsTable results={results} />;
}import { useSpotr } from 'spotr/vue';
const games = ref<Game[]>([]);
const query = ref('');
const spotr = useSpotr(() => ({
collection: games.value,
fields: [{ name: 'title', weight: 1 }],
}));
const results = computed(() => spotr.value?.query(query.value));import { writable, derived } from 'svelte/store';
import { createSpotr } from 'spotr/svelte';
const spotr = createSpotr({
collection: games,
fields: [{ name: 'title', weight: 1 }],
});
const query = writable('');
const results = derived([spotr, query], ([$spotr, $query]) =>
$spotr.query($query)
);
// Use $query and $results in your Svelte templateimport { createSignal, createMemo } from 'solid-js';
import { createSpotr } from 'spotr/solid';
const spotr = createSpotr({
collection: games,
fields: [{ name: 'title', weight: 1 }],
});
const [query, setQuery] = createSignal('');
const results = createMemo(() => spotr().query(query()));
// spotr is a getter; use spotr().query(query()) in a memo for reactive resultsimport { useSpotr } from 'spotr/preact';
function GameSearch({ games, searchQuery }) {
const spotr = useSpotr({
collection: games,
fields: [{ name: 'title', weight: 1 }],
});
const { results } = useMemo(
() => spotr.query(searchQuery),
[spotr, searchQuery]
);
return <ResultsTable results={results} />;
}Search the collection with a string query.
Async search with optional debouncing.
Update the collection.
Access the current collection.
This project uses Bun as the package manager. After cloning the repository, install dependencies with:
bun installbun run build- Build the library (outputs todist/)bun run test- Run all tests oncebun run test:watch- Run tests in watch modebun run test:coverage- Run tests with coverage reportbun run test:coverage:watch- Run tests with coverage in watch modebun run typecheck- Type check the library (tsc --noEmit)bun run audit- Audit spotr, all examples, root, and site for vulnerabilities and apply fixes (scripts/audit-all.ts)bun run lint- Lint the codebase (ESLint)bun run format- Format the codebase (Prettier, whole repo from root)bun run format:check- Check formatting without modifyingbun run validate- Full validation (format check, lint, typecheck, test coverage, examples typecheck, and build)bun run size:check- Verify bundle size under 5KBbun run bench- Run Vitest benchmarksbun run clean- Remove dist and coverage directories
bun run examples:typecheck- Type check all example applicationsbun run examples:sync- Sync shared files fromexamples/shared/to all examplesbun run examples:install- Install dependencies for all examplesbun run examples:update- Update dependencies for all examplesbun run examples:dev- Launch interactive dev servers for a selected framework (starts all 5 examples on ports 5173-5177)bun run examples:warm- Warm up StackBlitz cache by visiting all 25 example pages (used in CI after deployment)
bun run prepare- Setup Husky git hooks (runs automatically on install)
To release a new version to npm, use the release script:
bun run releaseThe script automates the release process: it runs validate (format check, lint, typecheck, test coverage, examples typecheck, and build) and a bundle size check (enforcing a 5KB gzipped limit) before proceeding with version bumping, commit amending, and publishing.
Note: Due to a known npm bug with workspaces, npm version may not automatically commit and tag changes when the package.json is in a subdirectory. The release script automatically detects this and creates the commit and tag manually if needed.
You can also pass a version bump type directly:
bun run release patch # for patch releases
bun run release minor # for minor releases
bun run release major # for major releases
bun run release prerelease # increment existing pre-release
bun run release premajor --preid beta # start major pre-release with beta tag
bun run release preminor --preid=rc # start minor pre-release with rc tagSee CONTRIBUTING.md for the complete release process and manual release steps.
MIT