Skip to content

Releases: krisk/Fuse

v7.6.0-beta.0

v7.6.0-beta.0 Pre-release
Pre-release

Choose a tag to compare

@krisk krisk released this 09 Aug 04:40

Beta preview of the MongoDB-style object query syntax. Install with npm install fuse.js@beta.

Features

  • search: add MongoDB-style object query syntax (#08459a9)

Express match types structurally instead of encoding them in magic characters:

const fuse = new Fuse(books, { keys: ['title', 'author'] })

fuse.search({ title: { $startsWith: 'old' }, author: { $eq: 'Kay' } })

Five operators ($fuzzy, $eq, $contains, $startsWith, $endsWith), plus $not for negation and field-local $and / $or.

Object String equivalent
$fuzzy term
$eq =term
$contains 'term
$startsWith ^term
$endsWith term$
$not: { $contains } !term
$not: { $startsWith } !^term
$not: { $endsWith } !term$

Highlights:

  • No useExtendedSearch flag. Object operators are unambiguous, so the flag that exists to parse magic characters in strings is not needed.
  • Exact parity with string syntax. An object query compiles to the same matchers as its string equivalent, so score and matches indices are identical.
  • Strict validation. Unknown operators, empty or non-string values, and illegal nesting throw immediately instead of silently degrading to a fuzzy search. The published types enforce the same grammar at compile time.
  • Composable. Field-local $and / $or for a field, and object leaves work anywhere a string leaf does inside logical $and / $or.
  • Works with FuseWorker, and basic builds stay lean (the compiler is behind the extended-search build flag).

$not wraps exactly one of $contains / $startsWith / $endsWith. $fuzzy and $eq have no inverse matcher, so negating them throws; $ne is reserved for a future whole-string inequality.

Docs: https://fusejs.io/extended-search#object-syntax

This is a prerelease. Feedback welcome before 7.6.0 goes stable.

v7.5.0

Choose a tag to compare

@krisk krisk released this 13 Jul 17:19

⚠️ Behavior changes

Every change in this release is a bug fix, but each one corrects a scoring or ranking bug. Scores and result ordering will shift for some queries. That is why this ships as a minor rather than a patch: the public API is unchanged and upgrading is a drop-in, but the results you get back can differ, and that should not arrive silently in a patch bump.

If you assert on exact score values or on a specific result order, expect those assertions to need updating. Re-baseline them against 7.5.0 rather than pinning to 7.4.x, since the 7.4.x behavior was wrong in the cases below.

  • Field-length normalisation now counts words correctly. Tabs and newlines were not treated as word separators, so a multi-line or tab-delimited field was scored as though it were one long word, making it look far shorter than it is. Fields containing \t, \n, or \r now score differently (#830).
  • Key weights are now normalised in object and keyless-logical search. Weights that did not sum to 1 were applied unnormalised, skewing the relative influence of each key. If your keys weights do not already sum to 1, your relative ranking changes (#833).
  • limit now returns the correct top-N when scores tie. A tie at the cutoff boundary could evict a result that should have been kept, so limit could return the wrong items, not merely the right items in a different order (#835).
  • Bitap respects minMatchCharLength in the exact-match shortcut. Matches shorter than minMatchCharLength were still reported via the exact-match fast path, so the matches array could contain entries it was configured to exclude (#831).

Bug Fixes

  • bitap: respect minMatchCharLength in exact-match shortcut (dbb98b6), closes #831
  • fieldNorm: count tabs and newlines as word separators (6fe85b0), closes #830
  • fieldNorm: count word-starts instead of space transitions (2946f97)
  • scoring: normalise key weights in object and keyless-logical search (e164b61), closes #833
  • search: keep the correct top-N under limit when scores tie (437f8f3), closes #835, thanks @spokodev for the report and the fix

Full Changelog: v7.4.2...v7.5.0

v7.4.2

Choose a tag to compare

@krisk krisk released this 05 Jun 22:23

Bug Fixes

  • types: ship CommonJS type declarations (.d.cts) so consumers on moduleResolution: node16/nodenext no longer hit TS1479 ("masquerading as ESM") when importing the package from a CommonJS project. The lib entries now resolve to a runtime-accurate export = declaration and the worker to its named declaration, via the require exports condition. (#780)

Full Changelog: v7.4.1...v7.4.2

v7.4.1

Choose a tag to compare

@krisk krisk released this 02 Jun 00:31

Bug Fixes

  • types: ship TypeScript declarations for fuse.js/worker (572ad1e), closes #828
  • types: add TypeScript declarations for fuse.js/worker-script (6ef6c33), closes #828

Both worker subpaths now resolve types under node16/nodenext and bundler module resolution.

v7.4.0

Choose a tag to compare

@krisk krisk released this 30 May 19:33

First stable on the 7.4 line. Aggregates everything from the 7.4.0-beta.1 β†’ 7.4.0-beta.8 cycle. See CHANGELOG.md for the per-beta breakdown.

Features

  • FuseWorker β€” parallel search via Web Workers (9ba192c). New FuseWorker class shards the collection across workers, runs searches in parallel, and preserves the same result ordering as Fuse. See the Web Workers guide.
  • token-search: tokenMatch: 'all' | 'any' option for AND/OR semantics (#827, 9f979d0). Default 'any' preserves existing behavior; 'all' requires every query word to match somewhere in a record (term-centric AND), useful for filter-style queries.
  • token-search: customizable tokenizer with unicode-aware default (#821, 8e55cae). New tokenize?: RegExp | (text: string) => string[] option; the default regex now segments CJK, Cyrillic, Greek, Arabic, etc. out of the box.

Bug Fixes

  • matches: report array-path keys as dotted strings (acd54e8). Previously match.key leaked a raw string[] for keys declared as path arrays (e.g. keys: [['author', 'firstName']]), contradicting the documented FuseResultMatch.key: string type. Now emits the canonical dotted string ('author.firstName'). Behavior change: consumers relying on the array shape need to update.
  • bitap: restrict highlight indices to matched window (#792, 622f105). Closes #505, #611, #691, #793.
  • index: correct doc-index alignment for Fuse<string> with blank docs (0b8e3ca).
  • core: invalidate searcher cache on collection mutation (fcf4228).
  • token-search: renumber inverted index after doc removal (ea9356d).
  • workers: preserve global refIndex across shards (e4217f9), match Fuse ordering and reject non-cloneable options (d571390), reject useTokenSearch in FuseWorker (8c6183d).
  • match: explicitly reject useTokenSearch in Fuse.match (3959d91).
  • correct fieldCount accounting and add reverse lookup in inverted index (54e702c).
  • guard against empty-string crash in fieldNorm (e550ab1); skip consecutive spaces in fieldNorm word counting (5929af6).
  • types: resolve typecheck errors and align KeyStore types (dbc115d).
  • docs: reflect data edits in playground demo (#825, 6eff909).

Performance

  • reuse bit arrays in Bitap search instead of allocating per error level (ec9b446)
  • replace forEach with for loops in search hot paths (1945f49)
  • fast-path Math.pow when exponent is 1 in computeScore (c82de57)
  • pre-allocate records array and use for loops in FuseIndex.create (5800036)
  • replace regex with loop for word counting in fieldNorm (5517a9b)
  • use filter instead of reverse-splice in removeAll / remove (c74823b, 8372b0a)
  • token-search: drop unused postings from inverted index (5ea216f)

Internal

  • refactor: replace extended-search matcher classes with plain functions (f8d61c9)
  • bench: consolidate benchmark/ into bench/ and add npm scripts (3b3a4c6)
  • ci: update Node matrix to 20, 22, 24 (b906906)
  • chore(deps): pin secure versions via npm overrides (3a1b118)

Docs

  • Migrated the site from VuePress to VitePress (663ee53)
  • New articles: "Using Fuse.js with React" (512bc7d), "Fuse.js vs Semantic Search" (5f93b4d)
  • New interactive demos in the fuzzy search article (edit distance, Bitap, threshold, search window)
  • Web Workers documentation and demo
  • Fuse Cloud landing page with waitlist

Behavior changes worth noting

  • FuseResultMatch.key now returns a dotted string for array-path keys (was a raw string[] in violation of the documented type). If you were doing match.key.join('.') to recover the dotted form, drop the .join.

v7.4.0-beta.8

v7.4.0-beta.8 Pre-release
Pre-release

Choose a tag to compare

@krisk krisk released this 25 May 01:48

Bug Fixes

  • matches: array-path keys (e.g. keys: [['author', 'firstName']]) are now reported as a dotted string ('author.firstName') in result.matches[].key, matching the documented FuseResultMatch.key: string type. Previously the raw string[] leaked through. (acd54e8)

v7.4.0-beta.7

v7.4.0-beta.7 Pre-release
Pre-release

Choose a tag to compare

@krisk krisk released this 22 May 03:08

Features

  • token-search: add tokenMatch: 'all' | 'any' for AND/OR (#827). 'all' makes token search require every query word to match somewhere in a record (across all fields and array elements) β€” the filtering behavior, where adding a word narrows results. Default 'any' is unchanged OR behavior.

Bug Fixes

  • docs: demo playground now reflects data edits on delete (#825)

Full changelog: v7.4.0-beta.6...v7.4.0-beta.7

v7.4.0-beta.6

v7.4.0-beta.6 Pre-release
Pre-release

Choose a tag to compare

@krisk krisk released this 14 May 04:13

Bug Fixes

includeMatches: true no longer reports stray highlight ranges far outside the actual match window. Previously, the bitap fuzzy scan marked every text character whose codepoint appeared in the search pattern's alphabet anywhere the scan visited, producing highlights like m, ic, l, o, ic scattered across olympic medical office when searching for olympics.

After the fix, highlight indices are restricted to the bitap-matched window ([bestLocation, bestLocation + patternLen - 1 + bestErrors]) plus exact-match windows from the index-of fast path.

Behavior change. Callers who built UIs around the previous noisier output may see fewer/narrower highlights. A residual tail of up to bestErrors characters can still appear immediately after the match window to preserve correctness in insertion-heavy fuzzy cases (e.g. abc vs abxc still highlights both ab and the matched c). To suppress short residual runs, set minMatchCharLength: N; runs shorter than N are filtered out.

Perf bonus. Removing the inner-loop mask write nets ~15% faster fuzzy search on +matches queries; non-+matches paths also pick up 2-5%.

v7.4.0-beta.5

v7.4.0-beta.5 Pre-release
Pre-release

Choose a tag to compare

@krisk krisk released this 11 May 00:22

Bug Fixes

  • index: correct doc-index alignment for Fuse<string> collections containing blank strings (0b8e3ca)

    Pre-fix, blank docs in a string collection caused records.length < _docs.length, and the mutation paths (add, removeAt, remove, removeAll) conflated records-array offset with source-array doc-index. Search results could be silently misaligned with the source array β€” e.g. removeAt(1) on ["", "apple", "banana"] could return "banana" as a match for a search for "apple".

    Affects any Fuse<string> collection where one or more docs are "" or whitespace-only. Object collections are unaffected.

    Additionally:

    • Fuse.removeAt(idx) now validates idx (integer, in range) atomically before any mutation.
    • Fuse.add("") no longer re-ingests the previous record into the token-search inverted index, and no longer crashes on all-blank collections.
    • FuseIndex.add, removeAt, removeAll gain cheap input guards for direct callers.

API note

FuseIndex.add gained a required second parameter:

// Before
FuseIndex<T>.add(doc: T): void

// After
FuseIndex<T>.add(doc: T, docIndex: number): IndexRecord | null

The public Fuse.add(doc) signature is unchanged. Only code that calls FuseIndex.add directly on an instance from Fuse.createIndex() needs to update; calling without docIndex now throws INVALID_DOC_INDEX.

Persisted-index migration

If you serialized a Fuse<string> index via getIndex().toJSON() from a pre-fix instance whose collection contained blanks and you ran any add/remove/removeAt/removeAll calls before serialization, those records may misrepresent the source array. Rebuild via new Fuse(docs, options) (drop the persisted-index argument) once after upgrading.

Indexes from clean string collections (no blanks, or blanks but no mutations) and all object-collection indexes are unaffected and load normally.

v7.4.0-beta.4

v7.4.0-beta.4 Pre-release
Pre-release

Choose a tag to compare

@krisk krisk released this 28 Apr 03:14

Performance

  • token-search: drop unused postings from inverted index (5ea216f)

The token-search query path only consumes df and fieldCount for IDF weighting. Postings (tf, keyIdx, subIdx) and the docTerms reverse map were built at index time but never read during search. Replacing them with a stats-only shape (df, fieldCount, docFieldCount, docTermFieldHits) preserves identical search behavior while reducing the inverted-index footprint.

Measured on a Zipfian synthetic corpus (3 keys: title/body/tags):

Corpus Heap savings Build speedup
1,000 docs 44.7% 1.31Γ—
10,000 docs 43.2% 1.26Γ—
50,000 docs 42.4% 1.21Γ—

Bundle: fuse.min.cjs gzipped 8.72 KB β†’ 8.44 KB.

Internal InvertedIndexData shape changed; the Posting interface is removed. Both surfaces are private (_invertedIndex is underscored, not part of the documented public API). All token-search behavioral tests pass unchanged.

Full changelog: v7.4.0-beta.3...v7.4.0-beta.4