$ pip install pyfusejs
fuse.js, rebuilt in Python. Same results — proved, not claimed.
Ayan Bag · github.com/ayanbag/pyfuse
Compares two strings.
That's the whole job.
Search a collection. Weighted fields, nested paths, ranked results.
A server to deploy, tune and pay for.
fuse.js is exactly that missing middle — and it only runs in JavaScript.
The capability isn't missing from computing. It's trapped in the wrong ecosystem.
| Bitap core | bit-parallel approximate matching, 32-char word |
| Weighted keys + nested paths | author.lastName, tags[], normalised weights |
| Extended query operators | =exact 'include ^prefix suffix$ !inverse |
| Logical composition | $and / $or query trees |
| Token search | inverted index with IDF / BM25-style ranking |
| Scoring | bitap × key weight × field-length norm |
~3,283 lines · zero runtime dependencies · workers/* excluded (DECISIONS §17)
Anyone can write a fuzzy search library.
The hard part is proving it behaves like the original.
So the original became an oracle:
test/*.test.js (UNMODIFIED)
↓ import Fuse from '../dist/fuse.mjs' ← vitest alias
fuse_shim.mjs → py_bridge.mjs (Atomics.wait) → server.py → pyfuse
All 13 spec files import the engine from one specifier. Redirect that, and the tests run against Python.
sortFn, getFn, function tokenize, Fuse.use — a closure over a live JS heap cannot be serialised
fuse.js mutates the caller's array; this port copies it — deliberate, DECISIONS §13
"Use Fuse(...)" not "Use new Fuse(...)" — new is JavaScript syntax, DECISIONS §19
Every failure is classified in compat/README.md. Nothing is suppressed to force a pass.
Identical result sets · identical ordering · identical match indices · identical refIndex
Random datasets, queries and options generated with hypothesis, fed to both engines, results compared field by field. Not example tests — property tests.
pow(0.1, 0.3846666666666666) CPython : 0.4124139370464501 ← correctly rounded V8 : 0.41241393704645002 ← fdlibm, not correctly rounded
They disagree by exactly 1 ULP on 10.04% of calls. Scoring does one pow per matched key.
I transcribed V8's fdlibm pow — 200 lines, 95.6% bit-exact — then stopped.
The residual error was uniform across every branch, which means the algorithm was right and the target was wrong. Closing it meant reverse-engineering V8 with nothing to diff against.
Consequence, disclosed: 1 ULP can break a score tie → 8 order flips in 51,569 (0.016%).
“The float divergence only affects the last digits.
It never changes the output.”
— what I wrote in my notes, early on
It was wrong. 1 ULP is enough to split an exact tie, and the tie-break then fires in one engine but not the other — so ordering does change, 8 times in 51,569.
It is now its own category in fuzz/log.txt, and the claim is corrected in README.md and DECISIONS §1 — rather than quietly dropped.
10K+ free-text tickets a month. Misspelled services, pasted stack traces.
Fuzzy search is the cheap pre-filter before the expensive LLM path.
→ 5 auto-routed · 1 suggested · 2 escalated
2M+ rows, Oracle → SQL Server. The join key does not survive the migration.
ACME CORPORATION LTD
vs Acme Corp. Limited
→ matched · needs-review · missing, both ways
Both ship in examples/ and run in one command. Everything I work on is Python. The tool was not.
| pyfuse | fuse.js | |
|---|---|---|
| throughput | 7.0 /s | 92.8 /s |
| latency p99 | 637.0 ms | 49.8 ms |
| peak RSS | 31.3 MiB | 53.7 MiB |
V8 JITs the Bitap inner loop; CPython interprets it. The value here is reach and parity, not speed — and an honest regression is worth more than a cherry-picked win.
just test · just compat · just fuzz · just demo
Every claim in this deck is one command away.