{"_id":"@codejoo/ffuzzy","_rev":"7-f0b26a1dd3844f97c0f0e9603c847ed5","name":"@codejoo/ffuzzy","dist-tags":{"latest":"0.8.1"},"versions":{"0.3.1":{"name":"@codejoo/ffuzzy","version":"0.3.1","keywords":["fuzzy-search","fuzzy","search","matcher","wasm","webassembly","typescript","fzf","nucleo","unicode","cjk","subsequence","fuzzy-matching","autocomplete","filter","highlight","browser","node"],"author":{"name":"jelon","email":"jelon@tbu.net"},"license":"MIT","_id":"@codejoo/ffuzzy@0.3.1","maintainers":[{"name":"29982416","email":"29982416@qq.com"}],"homepage":"https://github.com/icodejoo/ffuzzy","bugs":{"url":"https://github.com/icodejoo/ffuzzy/issues"},"dist":{"shasum":"113bc88ca26a8f1cefcf18f086ba919eb31bdfea","tarball":"https://registry.npmjs.org/@codejoo/ffuzzy/-/ffuzzy-0.3.1.tgz","fileCount":6,"integrity":"sha512-Yk2B1EDPU2OolzByFLAb79Da/c6pMXJKAxfNLfnMpX5Dd0s5OmSz9Fgi6kHy6AJJCY/WK4e28+YiOaA08FNrzQ==","signatures":[{"sig":"MEQCICCDE0nWhkEsJpaNIb1ndx1zjJFnkjZ0Xmu/t73J67JwAiBr6hks8MH8DCOhbs3fI8gWEleMyEmU3baNhs5kY38dzw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":125525},"type":"module","engines":{"node":">=18"},"exports":{".":{"types":"./ffuzzy.d.ts","import":"./ffuzzy.js"},"./lite":{"types":"./ffuzzy-lite.d.ts","import":"./ffuzzy-lite.js"}},"gitHead":"3ac6e70e0af7101a8485d66cbe8815bc57b80ee1","scripts":{"build":"bash ../scripts/build-wasm.sh","prepublishOnly":"node --input-type=module -e \"import{existsSync}from'node:fs';if(!existsSync('ffuzzy.js')){console.error('Run npm run build first');process.exit(1)}\""},"_npmUser":{"name":"29982416","email":"29982416@qq.com"},"repository":{"url":"git+https://github.com/icodejoo/ffuzzy.git","type":"git","directory":"wasm"},"_npmVersion":"11.6.2","description":"Fast fuzzy search for the web — WASM port of the ffuzzy C engine. Five search modes, Unicode, TypeScript, browser + Node.","directories":{},"sideEffects":false,"_nodeVersion":"24.11.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/ffuzzy_0.3.1_1782647556475_0.722374095033901","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@codejoo/ffuzzy","version":"0.5.0","keywords":["fuzzy-search","fuzzy","search","matcher","wasm","webassembly","typescript","fzf","nucleo","unicode","cjk","subsequence","fuzzy-matching","autocomplete","filter","highlight","browser","node"],"author":{"name":"jelon","email":"jelon@tbu.net"},"license":"MIT","_id":"@codejoo/ffuzzy@0.5.0","maintainers":[{"name":"29982416","email":"29982416@qq.com"}],"homepage":"https://github.com/icodejoo/ffuzzy","bugs":{"url":"https://github.com/icodejoo/ffuzzy/issues"},"dist":{"shasum":"26db763cb71020ac03ba586dc140dd6f67d9e6bf","tarball":"https://registry.npmjs.org/@codejoo/ffuzzy/-/ffuzzy-0.5.0.tgz","fileCount":6,"integrity":"sha512-Qqh7g43TWJpMsLz0o+8VXb6uy/hRJJc2lT6hZQ4YMmg0MPepjR1ejeNgRUYBgfSE76ThTKBhNXCvaVv6rIwB1g==","signatures":[{"sig":"MEUCIBRqVgMDNoLxOj3Qasu3ohOAWoCeUdebn8SxRtujwFD2AiEAj4Fi3Zh23Zjo5weMLRdYHsLBOf4fJyNnaAEfS+I2Big=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":142775},"type":"module","engines":{"node":">=18"},"exports":{".":{"types":"./ffuzzy.d.ts","import":"./ffuzzy.js"},"./lite":{"types":"./ffuzzy-lite.d.ts","import":"./ffuzzy-lite.js"}},"gitHead":"7b0a7aec12447ad6b37f333512086d6789b4ef10","scripts":{"test":"node build.mjs && node --test","build":"node build.mjs","build:engine":"bash build-engine.sh","prepublishOnly":"node build.mjs"},"_npmUser":{"name":"29982416","email":"29982416@qq.com"},"repository":{"url":"git+https://github.com/icodejoo/ffuzzy.git","type":"git","directory":"wasm"},"_npmVersion":"11.6.2","description":"Fast fuzzy search for the web — WASM port of the ffuzzy C engine. Five search modes, Unicode, TypeScript, browser + Node.","directories":{},"sideEffects":false,"_nodeVersion":"24.11.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/ffuzzy_0.5.0_1782659154841_0.5310426626155016","host":"s3://npm-registry-packages-npm-production"}},"0.6.0":{"name":"@codejoo/ffuzzy","version":"0.6.0","keywords":["fuzzy-search","fuzzy","search","matcher","wasm","webassembly","typescript","fzf","nucleo","unicode","cjk","subsequence","fuzzy-matching","autocomplete","filter","highlight","browser","node"],"author":{"name":"jelon","email":"jelon@tbu.net"},"license":"MIT","_id":"@codejoo/ffuzzy@0.6.0","maintainers":[{"name":"29982416","email":"29982416@qq.com"}],"homepage":"https://github.com/icodejoo/ffuzzy","bugs":{"url":"https://github.com/icodejoo/ffuzzy/issues"},"dist":{"shasum":"a1cec264e122fc7288ab788a023021e9f358a64d","tarball":"https://registry.npmjs.org/@codejoo/ffuzzy/-/ffuzzy-0.6.0.tgz","fileCount":7,"integrity":"sha512-pIox76v5wjBNCWoviAhuZL6hFxn4rDjtd5yGaNbL9QLbK2PahjqGt129TInhgPq78vZyfEdzduuT5kmueK6edA==","signatures":[{"sig":"MEUCIEjvwQFo/2Ed3U/kWV06Ln+30r1/clcBEaVFlKPyA+3sAiEAi30lkp97QVwOEIPyWXtovwY9XFDysdrwBK1MabwldAw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":170985},"type":"module","engines":{"node":">=18"},"exports":{".":{"types":"./ffuzzy.d.ts","import":"./ffuzzy.js"},"./lite":{"types":"./ffuzzy-lite.d.ts","import":"./ffuzzy-lite.js"}},"gitHead":"f57d160623f28f8597545ea2f82ea49aa9e2320b","scripts":{"test":"node build.mjs && node --test","build":"node build.mjs","build:engine":"bash build-engine.sh","prepublishOnly":"node build.mjs"},"_npmUser":{"name":"29982416","email":"29982416@qq.com"},"repository":{"url":"git+https://github.com/icodejoo/ffuzzy.git","type":"git","directory":"wasm"},"_npmVersion":"11.6.2","description":"Fast fuzzy search for the web — WASM port of the ffuzzy C engine. Five search modes, Unicode, TypeScript, browser + Node.","directories":{},"sideEffects":false,"_nodeVersion":"24.11.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"fuse.js":"^7.4.2"},"_npmOperationalInternal":{"tmp":"tmp/ffuzzy_0.6.0_1782747720528_0.2816389202898786","host":"s3://npm-registry-packages-npm-production"}},"0.7.0":{"name":"@codejoo/ffuzzy","version":"0.7.0","keywords":["fuzzy-search","fuzzy","search","matcher","wasm","webassembly","typescript","fzf","nucleo","unicode","cjk","subsequence","fuzzy-matching","autocomplete","filter","highlight","browser","node"],"author":{"name":"jelon","email":"jelon@tbu.net"},"license":"MIT","_id":"@codejoo/ffuzzy@0.7.0","maintainers":[{"name":"29982416","email":"29982416@qq.com"}],"homepage":"https://github.com/icodejoo/ffuzzy","bugs":{"url":"https://github.com/icodejoo/ffuzzy/issues"},"dist":{"shasum":"8d1a19643751da8cca29be7eb15df27c787031fc","tarball":"https://registry.npmjs.org/@codejoo/ffuzzy/-/ffuzzy-0.7.0.tgz","fileCount":7,"integrity":"sha512-c6A00PI0mlH0z+bMKCsCtsHd6JMGzBDSpK7Mz8cqq3XpssHFPFPXpWzWPx+RDD+NWS0e2bzoaHIC03faxTMPyA==","signatures":[{"sig":"MEQCIFQMhnLzbWZo6YNfr8KBb5Qb0K5gqR5HnJMQGCGozwC0AiAcRrj7FTU8ITpaWCN4OV2AppYBsAm2yv0MZSB7W8z78w==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":166625},"type":"module","engines":{"node":">=18"},"exports":{".":{"types":"./ffuzzy.d.ts","import":"./ffuzzy.js"},"./lite":{"types":"./ffuzzy-lite.d.ts","import":"./ffuzzy-lite.js"}},"gitHead":"964af6ae8c4f8758b0e3e781d1bd49392ba3a7e3","scripts":{"test":"node build.mjs && node --test","build":"node build.mjs","build:engine":"bash build-engine.sh","prepublishOnly":"node build.mjs"},"_npmUser":{"name":"29982416","email":"29982416@qq.com"},"repository":{"url":"git+https://github.com/icodejoo/ffuzzy.git","type":"git","directory":"wasm"},"_npmVersion":"11.6.2","description":"Fast fuzzy search for the web — WASM port of the ffuzzy C engine. Five search modes, Unicode, TypeScript, browser + Node.","directories":{},"sideEffects":false,"_nodeVersion":"24.11.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"fuse.js":"^7.4.2","fuzzysort":"^3.1.0"},"_npmOperationalInternal":{"tmp":"tmp/ffuzzy_0.7.0_1782822219779_0.36784404988348296","host":"s3://npm-registry-packages-npm-production"}},"0.8.0":{"name":"@codejoo/ffuzzy","version":"0.8.0","keywords":["fuzzy-search","fuzzy","search","matcher","wasm","webassembly","typescript","fzf","nucleo","unicode","cjk","subsequence","fuzzy-matching","autocomplete","filter","highlight","browser","node"],"author":{"name":"jelon","email":"jelon@tbu.net"},"license":"MIT","_id":"@codejoo/ffuzzy@0.8.0","maintainers":[{"name":"29982416","email":"29982416@qq.com"},{"name":"icodejoo","email":"gapkukb@gmail.com"}],"homepage":"https://github.com/icodejoo/dart-labs/tree/main/ffuzzy/wasm","bugs":{"url":"https://github.com/icodejoo/dart-labs/issues"},"dist":{"shasum":"2e91dd338a87c0b33f6b85520108c6ffca91bd7f","tarball":"https://registry.npmjs.org/@codejoo/ffuzzy/-/ffuzzy-0.8.0.tgz","fileCount":5,"integrity":"sha512-V2CnI3TtOn1syA2TVuEedjL0Nb/hkAEU+zGq/PEuzJdBnsQpjyfdX2Rl6LioHPe5c1N/dmBUvdo5p9aufycp3A==","signatures":[{"sig":"MEUCIQDV8ltReypzsxOoYf11dXe0xq+yhICFADSDdtCgX6JSSQIgApdWx8472oqZhuUNHKiFlPrlfbBBJoZQBI4vdLoUGPQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":110788},"type":"module","engines":{"node":">=18"},"exports":{".":{"types":"./dist/ffuzzy.d.mts","import":"./dist/ffuzzy.mjs"}},"gitHead":"4544a1e3641041dc7fddf5bd3ce9a88d740e66d1","scripts":{"test":"npm run build && node scripts/check_api_parity.mjs && node --test","build":"tsdown && terser dist/ffuzzy.mjs -o dist/ffuzzy.mjs --module --compress passes=2 --mangle","check":"node scripts/check_api_parity.mjs","build:all":"bash build-engine.sh && npm run build","build:engine":"bash build-engine.sh","prepublishOnly":"npm run build && node scripts/check_api_parity.mjs"},"_npmUser":{"name":"29982416","email":"29982416@qq.com"},"repository":{"url":"git+https://github.com/icodejoo/dart-labs.git","type":"git","directory":"ffuzzy/wasm"},"_npmVersion":"11.6.2","description":"Fast fuzzy search for the web — WASM port of the ffuzzy C engine. Five search modes, Unicode, TypeScript, browser + Node.","directories":{},"sideEffects":false,"_nodeVersion":"24.11.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"terser":"^5.49.0","tsdown":"^0.22.7","fuse.js":"^7.4.2","fuzzysort":"^3.1.0","typescript":"~5.7.3"},"_npmOperationalInternal":{"tmp":"tmp/ffuzzy_0.8.0_1784243398217_0.8729346367543211","host":"s3://npm-registry-packages-npm-production"}},"0.8.1":{"name":"@codejoo/ffuzzy","version":"0.8.1","description":"Fast fuzzy search for the web — WASM port of the ffuzzy C engine. Five search modes, Unicode, TypeScript, browser + Node.","type":"module","exports":{".":{"import":"./dist/ffuzzy.mjs","types":"./dist/ffuzzy.d.mts"}},"publishConfig":{"access":"public"},"scripts":{"build":"tsdown && terser dist/ffuzzy.mjs -o dist/ffuzzy.mjs --module --compress passes=2 --mangle","build:engine":"bash build-engine.sh","build:all":"bash build-engine.sh && npm run build","check":"node scripts/check_api_parity.mjs","test":"npm run build && node scripts/check_api_parity.mjs && node --test","prepublishOnly":"npm run build && node scripts/check_api_parity.mjs"},"keywords":["fuzzy-search","fuzzy","search","matcher","wasm","webassembly","typescript","fzf","nucleo","unicode","cjk","subsequence","fuzzy-matching","autocomplete","filter","highlight","browser","node"],"homepage":"https://github.com/icodejoo/dart-labs/tree/main/ffuzzy/wasm","repository":{"type":"git","url":"git+https://github.com/icodejoo/dart-labs.git","directory":"ffuzzy/wasm"},"bugs":{"url":"https://github.com/icodejoo/dart-labs/issues"},"license":"MIT","author":{"name":"jelon","email":"jelon@tbu.net"},"sideEffects":false,"engines":{"node":">=18"},"devDependencies":{"fuse.js":"^7.4.2","fuzzysort":"^3.1.0","terser":"^5.49.0","tsdown":"^0.22.7","typescript":"~5.7.3"},"gitHead":"8cce1506c250137ebbd870cf3bad8e6d7852a09a","_id":"@codejoo/ffuzzy@0.8.1","_nodeVersion":"24.11.1","_npmVersion":"11.6.2","dist":{"integrity":"sha512-YFN1FUAfYJFy2jlnQkp1Tc/yEr7DUACVn7FFuFk/93Ek7cvNgNsvymwjhFMuhRc0rVLE5jKe8G6S+8iJ9jGgHA==","shasum":"5548b3d5b765ced7db1c6052798bad2393e5a0d6","tarball":"https://registry.npmjs.org/@codejoo/ffuzzy/-/ffuzzy-0.8.1.tgz","fileCount":5,"unpackedSize":112696,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDpwUWLr/3rboIYb7kUaq0Ehv1qbHqlfHG2TiTCeTGcfwIgeOUHKlaSnawEFUWctJPJ0c/0gDLHNxT2VS2YPNmPwuw="}]},"_npmUser":{"name":"29982416","email":"29982416@qq.com"},"directories":{},"maintainers":[{"name":"29982416","email":"29982416@qq.com"},{"name":"icodejoo","email":"gapkukb@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ffuzzy_0.8.1_1784243784477_0.6405055416769334"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-28T11:52:36.194Z","modified":"2026-07-16T23:16:24.753Z","0.3.1":"2026-06-28T11:52:36.647Z","0.5.0":"2026-06-28T15:05:54.962Z","0.6.0":"2026-06-29T15:42:00.776Z","0.7.0":"2026-06-30T12:23:39.919Z","0.8.0":"2026-07-16T23:09:58.377Z","0.8.1":"2026-07-16T23:16:24.621Z"},"bugs":{"url":"https://github.com/icodejoo/dart-labs/issues"},"author":{"name":"jelon","email":"jelon@tbu.net"},"license":"MIT","homepage":"https://github.com/icodejoo/dart-labs/tree/main/ffuzzy/wasm","keywords":["fuzzy-search","fuzzy","search","matcher","wasm","webassembly","typescript","fzf","nucleo","unicode","cjk","subsequence","fuzzy-matching","autocomplete","filter","highlight","browser","node"],"repository":{"type":"git","url":"git+https://github.com/icodejoo/dart-labs.git","directory":"ffuzzy/wasm"},"description":"Fast fuzzy search for the web — WASM port of the ffuzzy C engine. Five search modes, Unicode, TypeScript, browser + Node.","maintainers":[{"name":"29982416","email":"29982416@qq.com"},{"name":"icodejoo","email":"gapkukb@gmail.com"}],"readme":"# @codejoo/ffuzzy\r\n\r\n[English](README.md) | 中文\r\n\r\n为 Web 提供的高性能排名模糊搜索 —— [ffuzzy](https://github.com/icodejoo/dart-labs/tree/main/ffuzzy) C 引擎的 WASM 移植版。\r\n\r\n模糊搜索 + 编辑距离搜索 · TypeScript · 浏览器 + Node · ~36 KB gzip\r\n\r\n## 安装\r\n\r\n```sh\r\nnpm install @codejoo/ffuzzy\r\n```\r\n\r\n## 快速上手\r\n\r\nWASM 模块由库内部管理——启动时调用一次 `ffuzzyInitialize()`，之后同步使用\r\n`FuzzyCorpus`，API 与 Dart 版完全对齐（无需传模块句柄）。\r\n\r\n```ts\r\nimport { ffuzzyInitialize, FuzzyCorpus } from '@codejoo/ffuzzy';\r\n\r\nawait ffuzzyInitialize();   // 启动时调用一次（WASM 实例化是异步的）\r\n\r\n// 纯字符串\r\nconst corpus = FuzzyCorpus.strings(['src/main.ts', 'README.md', 'package.json']);\r\ncorpus.fuzzy('src').forEach(h => console.log(h.raw, h.score));\r\ncorpus.dispose();\r\n\r\n// 任意对象 —— 命中携带原对象\r\nconst files = new FuzzyCorpus(myFiles, { stringOf: f => f.path });\r\nconst hit = files.fuzzy('src')[0];\r\nhit.raw;  // 原始对象\r\nfiles.dispose();\r\n```\r\n\r\n> 为什么要一次 `await`？浏览器禁止同步编译大于 4 KB 的 WASM 模块，所以引擎\r\n> 必须异步初始化。初始化完成后，所有调用都是同步的。\r\n\r\n## 搜索\r\n\r\n`fuzzy` 是核心模式——这是 WASM 真正碾压原生 JS 的场景（比 fuse.js 快 8-55×）。\r\n`substring` / `prefix` / `postfix`（别名 `suffix`）/ `exact` 走原生 JS 字符串操作\r\n（不经过 WASM 边界），各带 `*Raws` 变体，跳过 `FuzzyHit` 包装、速度更快：\r\n\r\n```ts\r\ncorpus.fuzzy('gems', { limit: 50 })     // 排名、评分、多字段\r\ncorpus.prefix('Super')\r\ncorpus.postfix('1000')                  // suffix() 是别名\r\ncorpus.exact('101024')\r\ncorpus.substring('ems')\r\ncorpus.fuzzyRaws('gems')                // T[] —— 跳过 FuzzyHit 包装\r\n```\r\n\r\n`fuzzy` 支持 fzf 风格操作符：`!term` 排除 · `^term` 强制前缀 ·\r\n`'term` 强制子串 · `term$` 强制后缀。\r\n\r\n### 编辑距离搜索 —— `approx()`\r\n\r\n基于 Myers bit-parallel Levenshtein，容忍拼写错误、替换和换位——与 `fuzzy`\r\n（子序列匹配）不同，它匹配与查询词编辑距离在 `maxDistance` 以内的项：\r\n\r\n```ts\r\ncorpus.approx('iphoen')                  // \"iPhone\" —— maxDistance 按查询长度自动推算\r\ncorpus.approx('iphoen', 2)               // 显式指定 maxDistance\r\ncorpus.approxRaws('iphoen')              // T[]\r\n```\r\n\r\n`maxDistance` 省略时自动推算：≤2 字符 → 0，3–5 → 1，6+ → 2。\r\n\r\n### 统一入口 —— `search()`\r\n\r\n```ts\r\ncorpus.search('iphoen', { strategy: 'fuzzy' })      // 默认，等价于 fuzzy()\r\ncorpus.search('iphoen', { strategy: 'approx' })     // 等价于 approx()\r\ncorpus.search('iphoen', { strategy: 'fallback' })   // 先子序列，无结果再编辑距离\r\ncorpus.search('iphoen', { strategy: 'merge' })       // 两者都跑，子序列命中在前\r\ncorpus.searchRaws('iphoen', { strategy: 'merge' })  // T[]\r\n```\r\n\r\n### 双结果 —— `dual()`\r\n\r\n单次 corpus 扫描跑两种算法，分桶返回：\r\n\r\n```ts\r\nconst { fuzzy, approx } = corpus.dual('iphoen');\r\n```\r\n\r\n## 选项\r\n\r\n```ts\r\nimport { FuzzyCorpus, FuzzyCase, FuzzyNorm, FuzzyScoring } from '@codejoo/ffuzzy';\r\n\r\nconst corpus = new FuzzyCorpus(items, {\r\n  stringOf: item => item.name,\r\n  options: {\r\n    caseMatching: FuzzyCase.smart,    // 0=区分大小写 1=不区分 2=智能（默认）\r\n    normalization: FuzzyNorm.smart,   // 0=不归一 1=智能变音符归一（默认）\r\n    limit: 50,                        // 最多返回数（0=全部）\r\n    highlight: false,                 // true 时填充 FuzzyHit.indices（默认 false）\r\n    scoring: FuzzyScoring.fast,       // fast（默认）/ off（不排名）/ nucleo（高精度）\r\n  },\r\n  matchPaths: false,   // 将 '/' 视为路径分隔符\r\n  preferPrefix: false, // 偏向靠前的命中加分\r\n});\r\n```\r\n\r\n单次调用覆盖：\r\n\r\n```ts\r\ncorpus.fuzzy('query', { limit: 10, highlight: true });\r\n```\r\n\r\n## 类型化对象搜索 —— `byKey` / `byKeys`\r\n\r\n泛型 `T` 从 items 数组自动推断，`hit.raw` 完全类型化：\r\n\r\n```ts\r\ninterface Game { gameId: string; gameName: string; platform: { id: string } }\r\n\r\n// 单字段 — hit.raw 推断为 Game\r\nconst byName = FuzzyCorpus.byKey(games, 'gameName');\r\nbyName.fuzzy('gems')[0].raw.gameId;   // ✓ 类型为 string\r\n\r\n// 多字段 — matchedKey 告诉你哪个字段命中\r\nconst corpus = FuzzyCorpus.byKeys(games, ['gameName', 'gameId']);\r\nconst hit = corpus.fuzzy('gems')[0];\r\nhit.raw.gameName;   // ✓ Game\r\nhit.matchedKey;     // 0 = gameName 命中，1 = gameId 命中\r\n\r\n// 点路径访问嵌套字段（IDE 有自动补全）\r\nconst byPlatform = FuzzyCorpus.byKey(games, 'platform.id');\r\nbyPlatform.fuzzy('226')[0]?.raw.gameId;  // ✓\r\n```\r\n\r\n字段不存在或值为 `null`/`undefined` 时静默返回 `''`，不会报错。\r\n\r\n## 多键搜索（拼音 / 罗马音）\r\n\r\n```ts\r\nimport { FuzzyCorpus, FuzzyKey, FuzzyKeyKind } from '@codejoo/ffuzzy';\r\n\r\ncorpus.addKey(item, [\r\n  FuzzyKey.kind('zhongguo', FuzzyKeyKind.pinyin),\r\n  FuzzyKey.kind('zg',       FuzzyKeyKind.initials),\r\n]);\r\n```\r\n\r\n变更：`add` / `addAll` / `addKey` / `update` / `removeAt` / `removeWhere` / `refresh` / `clear`。\r\n\r\n## 命中高亮\r\n\r\n搜索时传 `{ highlight: true }` 才会填充 `FuzzyHit.indices`（默认 `false` 以节省\r\nC 端 Pass 2 开销）。\r\n\r\n**方式 A —— `highlightHtml`**（便利函数，内置 HTML 转义，防 XSS）：\r\n\r\n```ts\r\nimport { highlightHtml } from '@codejoo/ffuzzy';\r\n\r\nconst [hit] = corpus.fuzzy('src', { highlight: true });\r\nelement.innerHTML = highlightHtml(hit.raw, hit.indices);\r\n// → '<mark>src</mark>/main.dart'\r\n// 自定义标签：highlightHtml(hit.raw, hit.indices, { tag: 'b' })\r\n```\r\n\r\n**方式 B —— 原始码点位置**（用于 Flutter 或自定义渲染）：\r\n\r\n```ts\r\nimport { fuzzyCodepointToUtf16 } from '@codejoo/ffuzzy';\r\n\r\nconst [hit] = corpus.fuzzy('src', { highlight: true });\r\nconst u16 = fuzzyCodepointToUtf16(hit.raw, hit.indices);\r\n// 将 u16 偏移量应用到 DOM Range / TextSpan / Highlight API\r\n```\r\n\r\n## `using` 语句\r\n\r\n```ts\r\nusing corpus = FuzzyCorpus.strings(items); // 离开作用域自动 dispose\r\n```\r\n\r\n## FuzzyHit 结构\r\n\r\n```ts\r\ninterface FuzzyHit<T> {\r\n  raw:             T;        // 命中的原始对象\r\n  index:           number;   // 在语料中的插入序号\r\n  score:           number;   // 匹配分（越高越好，仅同一次查询内可比）\r\n  matchedKind:     number;   // 命中键的类型（FuzzyKeyKind）\r\n  matchedKindCode: number;   // 原始整数 kind 值（内置 kind 与 matchedKind 相同，自定义 kind ≥ 100 时保留原值）\r\n  matchedKey:      number;   // 命中的是该 item 的第几个键\r\n  indices:         number[]; // 命中的码点位置 —— 仅 highlight:true 时有值\r\n}\r\n```\r\n\r\n## 性能\r\n\r\n基准测试语料：4886 条游戏名称（平均 15 字节 ASCII，`limit: 50`）。\r\n\r\n### 与 fuzzysort、fuse.js 对比\r\n\r\n| 数据量 | ffuzzy | fuzzysort | fuse.js | vs fuzzysort | vs fuse.js |\r\n|------:|-------:|----------:|--------:|:------------:|:----------:|\r\n| 4 886 条 | 120-220 µs | **19-103 µs** | 1.8-6.7 ms | 0.13-0.48× | **快 8-56×** |\r\n| 9 772 条 | 240-415 µs | **32-193 µs** | 3.6-13 ms | 0.13-0.47× | **快 9-55×** |\r\n| 24 430 条 | 0.6-1.1 ms | **92-695 µs** | 12-35 ms | 0.14-0.64× | **快 11-56×** |\r\n| 48 860 条 | 1.4-2.8 ms | **238-2440 µs** | 18-75 ms | 0.14-0.88× | **快 7-57×** |\r\n\r\n构建时间（一次性）：ffuzzy **2-9 ms** · fuse.js 2-20 ms · fuzzysort 5-27 ms\r\n\r\n> fuzzysort 是纯 JS 库，直接操作 V8 原生字符串，无 WASM 边界开销，纯 ASCII 场景下更快。\r\n> ffuzzy 在高命中率查询（如 `\"sp\"`，48k 条时：2.77 ms vs 2.44 ms）差距缩小，\r\n> 对比 fuse.js 则在所有规模下均大幅领先。\r\n\r\n### 功能对比\r\n\r\n| | ffuzzy | fuzzysort | fuse.js |\r\n|--|:------:|:---------:|:-------:|\r\n| 速度（纯 ASCII） | ★★★ | ★★★★★ | ★ |\r\n| 对比 fuse.js | **快 7-57×** | 约快 10× | 基准 |\r\n| CJK / 变音符折叠 | ✅ | ❌ | △ |\r\n| 多键搜索（拼音 / 罗马音） | ✅ `byKeys` | ❌ | ❌ |\r\n| 类型化 `byKey<T>` / 点路径 | ✅ | ❌ | ❌ |\r\n| Dart FFI（Flutter） | ✅ | ❌ | ❌ |\r\n| 排名质量 | nucleo DP | 前缀偏向 | Bitap/Levenshtein |\r\n| 编辑距离搜索 | ✅ `approx` | ❌ | △（Bitap） |\r\n| 包体积 | ~36 KB gzip | ~8 KB | ~24 KB |\r\n\r\n**选 ffuzzy 而不选 fuzzysort 的场景**：CJK 内容、拼音/罗马音转写、多字段搜索（`byKeys`），\r\n或同时使用 Flutter/Dart 包。纯 ASCII 无 Unicode 需求时，fuzzysort 更轻量。\r\n\r\n## 从源码构建\r\n\r\n```sh\r\ncd wasm\r\nnpm run build          # 快路：编译 src/ffuzzy-corpus.ts → dist/ffuzzy.mjs + .d.mts\r\n\r\n# 重建 WASM 引擎（需要 Emscripten ≥3.x）：\r\nnpm run build:engine    # emcc 编译 src/*.c → src/ffz.mjs，然后自动 npm run build\r\nnpm run build:all       # 引擎 + build 一步到位\r\n```\r\n\r\n## 相关\r\n\r\n- [pub.dev 上的 ffuzzy](https://pub.dev/packages/ffuzzy) —— Flutter / Dart 包\r\n- [GitHub](https://github.com/icodejoo/dart-labs/tree/main/ffuzzy)\r\n\r\n## 许可证\r\n\r\nMIT\r\n","readmeFilename":"README.zh-CN.md"}