# https://github.com/lucaong/minisearch 项目说明书

生成时间：2026-06-26 16:11:46 UTC

## 目录

- [项目概述、安装与基本用法](#page-1)
- [索引架构、SearchableMap 与 BM25 排序](#page-2)
- [搜索 API、查询组合与自动建议](#page-3)
- [分词、字段提取、国际化与故障排查](#page-4)

<a id='page-1'></a>

## 项目概述、安装与基本用法

### 相关页面

相关主题：[索引架构、SearchableMap 与 BM25 排序](#page-2), [搜索 API、查询组合与自动建议](#page-3)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [README.md](https://github.com/lucaong/minisearch/blob/main/README.md)
- [package.json](https://github.com/lucaong/minisearch/blob/main/package.json)
- [src/MiniSearch.ts](https://github.com/lucaong/minisearch/blob/main/src/MiniSearch.ts)
- [src/SearchableMap/types.ts](https://github.com/lucaong/minisearch/blob/main/src/SearchableMap/types.ts)
- [src/MiniSearch.test.js](https://github.com/lucaong/minisearch/blob/main/src/MiniSearch.test.js)
- [examples/plain_js/README.md](https://github.com/lucaong/minisearch/blob/main/examples/plain_js/README.md)
</details>

# 项目概述、安装与基本用法

## 1. 项目概述

MiniSearch 是一个轻量但功能完整的内存型全文搜索引擎，专注于在浏览器与 Node.js 环境中以少量资源占用提供工业级搜索能力。它不依赖任何第三方运行时包，遵循 MIT 协议发布，并同时提供 ESM、CommonJS 与 UMD 三种产物以适配不同的加载环境。

从 [README.md:1-15](https://github.com/lucaong/minisearch/blob/main/README.md#L1-L15) 可以看到官方描述：「a tiny but powerful in‑memory fulltext search engine written in JavaScript. It is respectful of resources, and it can comfortably run both in Node and in the browser.」项目关键词包括 search、full text、fuzzy、prefix、auto suggest、auto complete 与 index，资料来源：[package.json:14-23](https://github.com/lucaong/minisearch/blob/main/package.json#L14-L23)。

引擎的核心能力来自 [src/MiniSearch.ts](https://github.com/lucaong/minisearch/blob/main/src/MiniSearch.ts) 中实现的倒排索引结构 `SearchableMap<FieldTermData>`，它通过 `RadixTree` 实现以支持前缀匹配，资料来源：[src/SearchableMap/types.ts:1-19](https://github.com/lucaong/minisearch/blob/main/src/SearchableMap/types.ts#L1-L19)。评分使用 BM25 + BM25+ 算法，并默认开启字段长度归一化。

```mermaid
flowchart LR
    A[文档集合] --> B[extractField 抽取字段]
    B --> C[tokenize 分词]
    C --> D[processTerm 标准化]
    D --> E[SearchableMap<br/>倒排索引]
    E --> F[search 查询]
    F --> G{搜索选项}
    G -->|prefix| H[前缀匹配]
    G -->|fuzzy| I[模糊匹配]
    G -->|combineWith| J[AND/OR 组合]
    H --> K[BM25 评分]
    I --> K
    J --> K
    K --> L[排序结果]
```

## 2. 安装

MiniSearch 当前版本为 7.2.0，以 ESM 为默认模块类型。资料来源：[package.json:1-21](https://github.com/lucaong/minisearch/blob/main/package.json#L1-L21)。

通过 npm 或 yarn 安装：

```bash
npm install minisearch
# 或者
yarn add minisearch
```

发布到 unpkg / jsDelivr 的 UMD 包可作为浏览器脚本直接引入，其入口配置在 `package.json` 中 `unpkg` 与 `jsdelivr` 字段，资料来源：[package.json:21-23](https://github.com/lucaong/minisearch/blob/main/package.json#L21-L23)。

仓库附带一个纯 JS 示例用于演示，运行方式见 [examples/plain_js/README.md:1-13](https://github.com/lucaong/minisearch/blob/main/examples/plain_js/README.md#L1-L13)：

```bash
cd examples/plain_js
python3 -m http.server     # 或 npx http-server -p 8000
# 然后访问 http://localhost:8000
```

## 3. 基本用法

创建一个索引并执行一次完整查询仅需数行代码，典型流程在 [src/MiniSearch.ts:51-95](https://github.com/lucaong/minisearch/blob/main/src/MiniSearch.ts#L51-L95) 的文档注释中给出：

```javascript
import MiniSearch from 'minisearch'

const documents = [
  { id: 1, title: 'Zen and the Art of Motorcycle Maintenance', text: 'I can see by my watch...', category: 'fiction' },
  { id: 2, title: 'Neuromancer', text: 'The sky above the port was...', category: 'fiction' }
]

const miniSearch = new MiniSearch({
  fields: ['title', 'text'],
  storeFields: ['title', 'category']
})

miniSearch.addAll(documents)

miniSearch.search('zen art motorcycle')
// => [
//   { id: 1, title: 'Zen and the Art of Motorcycle Maintenance', category: 'fiction', score: 2.77258 }
// ]
```

`id` 字段始终会被存储并返回；如果不显式指定，MiniSearch 会把 `id` 当作主键。`storeFields` 中的字段会随搜索结果一同返回，便于在 UI 中直接渲染，资料来源：[src/MiniSearch.ts:447-481](https://github.com/lucaong/minisearch/blob/main/src/MiniSearch.ts#L447-L481)。

通过 [src/MiniSearch.test.js:30-57](https://github.com/lucaong/minisearch/blob/main/src/MiniSearch.test.js#L30-L57) 可以看到，`tokenize` 与 `processTerm` 在索引时分别会被传入「字段文本 + 字段名」，允许按字段做差异化处理；测试同时验证 `processTerm` 可以通过返回字符串数组把单个词扩展为多个词。

## 4. 核心配置选项速查

下表汇总了构造 `MiniSearch` 时最常用的选项，详细类型定义见 [src/MiniSearch.ts:432-600](https://github.com/lucaong/minisearch/blob/main/src/MiniSearch.ts#L432-L600)。

| 选项 | 作用 | 默认值 / 说明 |
| --- | --- | --- |
| `fields` | 需要建索引的字段名列表 | 必填 |
| `storeFields` | 搜索结果中需要随同返回的字段 | 仅返回 `id` |
| `idField` | 文档主键字段名 | `id` |
| `tokenize` | 将字段文本拆分为 token | `string.split(SPACE_OR_PUNCTUATION)` |
| `processTerm` | 对 token 做归一化（如小写、词干） | `term.toLowerCase()` |
| `extractField` | 从文档中读取字段原始值 | `document[fieldName]` |
| `searchOptions` | 所有 `search()` 调用共享的默认搜索参数 | 无 |
| `BM25Params` (`k`, `b`, `d`) | BM25 / BM25+ 参数 | `{ k: 1.2, b: 0.7, d: 0.5 }` |

社区中关于中文/韩文检索的讨论（例如 [#201](https://github.com/lucaong/minisearch/issues/201)、[#312](https://github.com/lucaong/minisearch/issues/312)、[#314](https://github.com/lucaong/minisearch/issues/314)）表明默认分词器 `SPACE_OR_PUNCTUATION`（`/[\n\r\p{Z}\p{P}]+/u`）对 CJK 与黏着语支持有限；[#309](https://github.com/lucaong/minisearch/issues/309) 进一步指出该正则会把单/双引号也当作分隔符。对于非空格分词的语言，建议通过 `tokenize` 注入外部分词器。

## 5. 常见使用模式

- **模糊与前缀**：`search('neromancer', { fuzzy: 0.2 })` 可纠正拼写错误；`search('moto', { prefix: true })` 会在最后一个词上启用前缀匹配，资料来源：[src/MiniSearch.ts:624-680](https://github.com/lucaong/minisearch/blob/main/src/MiniSearch.ts#L624-L680)。
- **字段加权与文档加权**：`boostField` / `boostDocument` 在评分阶段对匹配项乘以因子，资料来源：[src/MiniSearch.ts:609-625](https://github.com/lucaong/minisearch/blob/main/src/MiniSearch.ts#L609-L625)。
- **过滤**：`filter: result => result.category === 'fiction'` 在合并各子查询后再统一筛选；[#304](https://github.com/lucaong/minisearch/issues/304) 提醒 `filter` 仅作用于最终结果而非子查询。
- **复合查询**：`combineWith: 'AND' | 'OR' | 'AND_NOT'` 配合嵌套 `queries` 数组可构建表达式树，资料来源：[src/MiniSearch.ts:712-740](https://github.com/lucaong/minisearch/blob/main/src/MiniSearch.ts#L712-L740)。
- **索引维护**：`add` / `addAll` 添加，`replace` 覆盖，`discard` 删除，`vacuum` 清理由删除留下的"脏"引用，资料来源：[src/MiniSearch.test.js:140-180](https://github.com/lucaong/minisearch/blob/main/src/MiniSearch.test.js#L140-L180)。
- **序列化**：`loadJSON` / `toJSON` 支持把索引持久化到磁盘或浏览器存储，便于构建期预索引，资料来源：[src/MiniSearch.ts:447-481](https://github.com/lucaong/minisearch/blob/main/src/MiniSearch.ts#L447-L481)。

## See Also

- 搜索选项与评分机制：详见仓库 `docs/` 目录及 [src/MiniSearch.ts](https://github.com/lucaong/minisearch/blob/main/src/MiniSearch.ts) 中的 `SearchOptions` 与 `BM25Params` 定义。
- 自动补全（`autoSuggest`）：[src/MiniSearch.ts:696-735](https://github.com/lucaong/minisearch/blob/main/src/MiniSearch.ts#L696-L735)。
- 浏览器演示与示例：<https://lucaong.github.io/minisearch/demo/> 与 [examples/plain_js/README.md](https://github.com/lucaong/minisearch/blob/main/examples/plain_js/README.md)。
- 相关项目：[minisearch-wasm](https://github.com/epoyraz/minisearch-wasm)（Rust + WebAssembly 移植），[garu-minisearch-tokenizer](https://www.npmjs.com/package/garu-minisearch-tokenizer)（韩文形态学分词插件）。

---

<a id='page-2'></a>

## 索引架构、SearchableMap 与 BM25 排序

### 相关页面

相关主题：[项目概述、安装与基本用法](#page-1), [搜索 API、查询组合与自动建议](#page-3), [分词、字段提取、国际化与故障排查](#page-4)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [src/MiniSearch.ts](https://github.com/lucaong/minisearch/blob/main/src/MiniSearch.ts)
- [src/SearchableMap/SearchableMap.ts](https://github.com/lucaong/minisearch/blob/main/src/SearchableMap/SearchableMap.ts)
- [src/SearchableMap/TreeIterator.ts](https://github.com/lucaong/minisearch/blob/main/src/SearchableMap/TreeIterator.ts)
- [src/SearchableMap/fuzzySearch.ts](https://github.com/lucaong/minisearch/blob/main/src/SearchableMap/fuzzySearch.ts)
- [src/SearchableMap/types.ts](https://github.com/lucaong/minisearch/blob/main/src/SearchableMap/types.ts)
- [DESIGN_DOCUMENT.md](https://github.com/lucaong/minisearch/blob/main/DESIGN_DOCUMENT.md)
- [README.md](https://github.com/lucaong/minisearch/blob/main/README.md)
- [package.json](https://github.com/lucaong/minisearch/blob/main/package.json)
</details>

# 索引架构、SearchableMap 与 BM25 排序

## 1. 总体架构与设计目标

MiniSearch 是一款定位为"浏览器与 Node.js 均可用的内存级全文搜索引擎"，强调"tiny but powerful"。其设计目标是：索引必须能完全装入进程内存；查询无需网络往返；同时支持精确、前缀、模糊匹配以及字段加权排序 资料来源：[README.md:15-40]()。在源码层面，`MiniSearch` 类将"倒排索引"封装在一个可遍历前缀的 `SearchableMap` 中，并将"评分"交给 BM25+ 实现，从而把"查得到"与"排得准"两件事解耦 资料来源：[src/MiniSearch.ts:382-410]()。

下图展示了从外部 API 到内部数据结构的对应关系：

```mermaid
flowchart LR
    A[add/addAll/replace] --> B[tokenize + processTerm]
    B --> C[倒排索引<br/>SearchableMap&lt;FieldTermData&gt;]
    C --> D[TreeIterator / fuzzySearch]
    D --> E[calcBM25Score]
    E --> F[SearchResult]
    G[search/autoSuggest] --> D
    H[discard/remove/vacuum] --> C
```

## 2. 倒排索引与 SearchableMap

倒排索引是 MiniSearch 的核心数据结构。`MiniSearch._index` 的类型为 `SearchableMap<FieldTermData>`，键为词项（term），值按字段 ID 组织文档频次与位置信息 资料来源：[src/MiniSearch.ts:388-410]()。

`SearchableMap` 底层是**压缩前缀树（Radix Tree）**，而不是常见的哈希表，这带来两个关键优势：

- **前缀搜索极其廉价**：`atPrefix(prefix)` 返回一个仅包含指定前缀条目且**可写**的视图，无需复制数据 资料来源：[src/SearchableMap/SearchableMap.ts:50-82]()。这是 `search({ prefix: true })` 和 `autoSuggest` 能即时响应的基础。
- **模糊搜索可枚举**：基于前缀树的 `TreeIterator` 配合 `fuzzySearch.ts` 中的编辑距离函数，按前缀范围枚举候选词项，再交给 BM25 打分 资料来源：[src/SearchableMap/TreeIterator.ts]()、[src/SearchableMap/fuzzySearch.ts]()。

`SearchableMap` 还导出了 `fromObject`、`atPrefix`、`keys`、`values` 等辅助 API，便于序列化、调试或自定义遍历 资料来源：[src/SearchableMap/SearchableMap.ts]()。这种"通用、可前缀枚举的映射"使 MiniSearch 能以纯 JS 实现出类 Lucene 的体验，而无需额外依赖 资料来源：[package.json:53-55]()（无运行时依赖）。

### 文档生命周期与索引维护

`MiniSearch` 维护一组配套状态以支持增量更新：

| 字段 | 作用 |
|---|---|
| `_documentIds / _idToShortId` | 在用户 ID 与内部紧凑 shortId 间映射 |
| `_fieldLength` / `_avgFieldLength` | 每文档每字段长度及全局平均，用于 BM25 长度归一化 |
| `_storedFields` | 可选的原始字段快照，用于搜索结果回填 |
| `_dirtCount` | 自上次 vacuum 以来被 `discard` 的文档计数 |

`discard` 与 `replace` 会把失效的文档引用留在索引里以摊销删除开销；累积到阈值时触发 `vacuum`，分批清理（`batchSize`、`batchWait`）以避免阻塞主线程 资料来源：[src/MiniSearch.ts:430-450]()。

## 3. BM25+ 排序与评分管线

MiniSearch 使用 BM25+（Okapi BM25 带频率归一化下界）作为相关度评分，参数通过 `SearchOptions.bm25` 注入：

| 参数 | 默认值 | 推荐范围 | 含义 |
|---|---|---|---|
| `k` | `1.2` | `1.2 ~ 2` | 词项频率饱和点，越大对高频越敏感 |
| `b` | `0.7` | `~0.75` | 字段长度归一化强度，`0` 关闭 |
| `d` | `0.5` | `0.5 ~ 1` | BM25+ 下界 δ，越大"长字段惩罚"越弱 |

资料来源：[src/MiniSearch.ts:158-185]()。打分函数 `calcBM25Score(termFreq, matchingCount, totalCount, fieldLength, avgFieldLength, bm25Params)` 在每个 (文档, 字段) 对上累计，再叠加字段加权（`boost`）、词项加权（`boostTerm`）和文档加权（`boostDocument`），三者相乘得到最终分数 资料来源：[src/MiniSearch.ts:188-220]()。

社区中关于"Fuzzy 分数异常"（issue #263）和"前缀权重偏低"（issue #299）的讨论，正是围绕这套权重管线展开的：模糊匹配会通过 `fuzzySearch` 枚举近邻词项并复用同一 BM25 公式，因此**近邻文档是否被命中取决于原始词项在索引里的频率**，调整 `k`、`b`、`prefix` 权重或使用 `boostDocument` 是最常见的调参手段 资料来源：[src/SearchableMap/fuzzySearch.ts]()。

## 4. 常见失败模式与排错建议

- **`Cannot read properties of undefined (reading 'keys')`**（issue #306）：通常由 `replace`/`discard` 后未 `vacuum` 又触发序列化或迭代造成，调用 `await index.vacuum()` 可复现并修复 资料来源：[src/MiniSearch.ts:430-450]()。
- **全局通配符 `MiniSearch.wildcard`**（issue #307）：`Symbol(*)` 会让 `executeQuery` 直接走全文档枚举，分支返回结构必须满足默认 `keys()` 形态，避免在自定义 `executeQuery` 重写中跳过 `score` 字段。
- **过滤仅作用于最终结果**（issue #304）：`filter` 回调在 `search()` 末尾统一应用，而不是在 `QueryCombination` 的子查询层级；若需子查询过滤，需在子树 `SearchOptions` 中显式提供。

## See Also

- [MiniSearch API 参考](https://lucaong.github.io/minisearch/classes/MiniSearch.MiniSearch.html)
- [CHANGELOG 与版本说明](https://github.com/lucaong/minisearch/blob/master/CHANGELOG.md)
- 社区插件：[garu-minisearch-tokenizer](https://www.npmjs.com/package/garu-minisearch-tokenizer)（针对非空格分词语言的扩展）

---

<a id='page-3'></a>

## 搜索 API、查询组合与自动建议

### 相关页面

相关主题：[项目概述、安装与基本用法](#page-1), [索引架构、SearchableMap 与 BM25 排序](#page-2), [分词、字段提取、国际化与故障排查](#page-4)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [src/MiniSearch.ts](https://github.com/lucaong/minisearch/blob/main/src/MiniSearch.ts)
- [src/MiniSearch.test.js](https://github.com/lucaong/minisearch/blob/main/src/MiniSearch.test.js)
- [README.md](https://github.com/lucaong/minisearch/blob/main/README.md)
- [package.json](https://github.com/lucaong/minisearch/blob/main/package.json)
- [examples/plain_js/README.md](https://github.com/lucaong/minisearch/blob/main/examples/plain_js/README.md)
- [src/SearchableMap/types.ts](https://github.com/lucaong/minisearch/blob/main/src/SearchableMap/types.ts)
</details>

# 搜索 API、查询组合与自动建议

## 概述

MiniSearch 的检索子系统围绕 `MiniSearch.search` 与 `MiniSearch.autoSuggest` 两个核心入口构建：前者把查询转换为带评分与匹配信息的命中结果，后者基于前缀/模糊匹配对查询词进行扩展并返回补全候选。整个库运行时不依赖任何外部包（`"dependencies": {}`），评分由内联实现的 BM25 + BM25+ 变体负责，资料来源：[package.json:11-39]()。MiniSearch 同时提供结构化查询表达式（`QueryCombination`）与通配符查询（`MiniSearch.wildcard`），使上层可以构造复合条件而无需手写解析器，资料来源：[src/MiniSearch.ts:Query & Wildcard types]()。

## 搜索 API 与查询表达式

### 查询形态

`Query` 类型被定义为 `QueryCombination | string | Wildcard`，分别支持：

- **字符串**：默认经 `tokenize` + `processTerm` 管线处理；
- **`QueryCombination`**：携带 `queries` 与 `combineWith`/`SearchOptions` 的嵌套表达式；
- **`MiniSearch.wildcard`**：通过 `static readonly wildcard: unique symbol` 标识，用于返回全部文档或保留外部排序。

资料来源：[src/MiniSearch.ts:Query type definition]()。

### 关键搜索选项

| 选项 | 作用 |
| --- | --- |
| `prefix` | 启用前缀搜索，可为布尔值或 `(term, index, terms) => boolean` 函数 |
| `fuzzy` | 启用模糊匹配：布尔值、`≥1` 的最大编辑距离或 0–1 的相对阈值 |
| `boostDocument` | `(documentId, term, storedFields) => number`，对命中加权 |
| `filter` | 在最终合并后的结果集上过滤，**不**逐子查询触发 |
| `combineWith` | 子查询组合策略：`'AND'` / `'OR'` / `'AND_NOT'` |

资料来源：[src/MiniSearch.ts:SearchOptions & BoostDocument]()。

### 结果结构与排序

`SearchResult` 包含 `id`、`terms`（文档中被命中的词条）、`queryTerms`（触发的查询词；前缀/模糊匹配时与 `terms` 可能不同）、`match`（按词条聚合的字段命中信息）、`score` 以及 `storeFields` 中声明的字段。底层会把 BM25 分数再乘以 `queryTerms.length` 作为质量因子；若查询为 `MiniSearch.wildcard` 且未提供 `boostDocument`，则跳过按分数排序以保留原始顺序，资料来源：[src/MiniSearch.ts:result aggregation & wildcard short-circuit]()。

```mermaid
flowchart LR
  A[Query string<br/>or QueryCombination<br/>or wildcard] --> B[tokenize + processTerm]
  B --> C[executeQuery]
  C --> D[per-document scoring<br/>BM25 + boostDocument]
  D --> E[filter]
  E -->|wildcard & no boost| F[preserve order]
  E -->|normal query| G[sort by score]
  F --> H[SearchResult[]]
  G --> H
```

## 查询组合

`QueryCombination` 是一个轻量的查询 DSL，允许应用层把用户输入解析成表达式树。例如：

```javascript
miniSearch.search({
  combineWith: 'AND',
  queries: ['zen', { combineWith: 'OR', queries: ['art', 'archery'] }]
})
```

社区已记录到组合场景的若干边界：

- **`combineWith: 'AND'` 边界**：当 `processTerm` 在部分词条上返回假值剔除后，命中集合可能与"所有词条都需命中"的直觉不符（issue #311）。
- **`filter` 仅作用于顶层**：类型签名虽像支持子查询过滤，但实现上只在最终结果处执行（issue #304）。
- **保留外部排序**：若希望保留按时间等键的输入顺序，可借助通配符 + `boostDocument` 缺省的快速路径，或在结果返回后做稳定排序。

资料来源：[src/MiniSearch.test.js:replace vacuum behavior]()、[src/MiniSearch.ts:executeQuery]()。

## 自动建议

`MiniSearch.autoSuggest(queryString, options)` 是搜索框自动补全入口。其默认行为与 `search` 不同：

- 默认对**最后一个**词条开启前缀搜索；
- 子词之间默认 `combineWith: 'AND'`；
- 仍接受 `fuzzy`、`filter`、`prefix`、`boostDocument` 等选项。

返回类型为 `Suggestion[]`，每项包含 `suggestion`（字符串）、`terms`（词条数组）与 `score`，按分数降序，例如：

```javascript
miniSearch.autoSuggest('neromancer', { fuzzy: 0.2 })
// => [{ suggestion: 'neuromancer', terms: ['neuromancer'], score: 1.03998 }]
```

资料来源：[src/MiniSearch.ts:autoSuggest JSDoc & default options]()。

## 常见陷阱与社区问题

1. **`SPACE_OR_PUNCTUATION` 匹配 Unicode 引号**：默认 `tokenize` 使用 `/[\n\r\p{Z}\p{P}]+/u`，会把英文撇号当成分隔符，导致 `song's` 被切分为 `song` 与 `s`（issue #309）。需要自定义 `tokenize`。
2. **非拉丁语种需要形态分析器**：默认分词器不切分中文/韩文；社区已发布 `garu-minisearch-tokenizer`（issue #312/#314）作为韩文插件，issue #201 也讨论了中文支持。
3. **`MiniSearch.wildcard` 误用**：把 `Symbol('*')` 字符串传入会触发 `Cannot read properties of undefined (reading 'map')`（issue #307）；必须使用 `MiniSearch.wildcard` 引用。
4. **模糊评分异常**：相同词条在不同长度文档上的 BM25 评分差异可能与直觉相悖（issue #263、#129），可通过调整 `BM25Params.k/b/d` 或 `boostDocument` 缓解。
5. **字段未参与搜索**：仅 `fields` 中列出的字段会进入倒排索引，`storeFields` 只决定返回字段（issue #298）。

## See Also

- [README.md](https://github.com/lucaong/minisearch/blob/main/README.md)
- [src/MiniSearch.ts](https://github.com/lucaong/minisearch/blob/main/src/MiniSearch.ts)
- [examples/plain_js/README.md](https://github.com/lucaong/minisearch/blob/main/examples/plain_js/README.md)
- [src/SearchableMap/types.ts](https://github.com/lucaong/minisearch/blob/main/src/SearchableMap/types.ts)

---

<a id='page-4'></a>

## 分词、字段提取、国际化与故障排查

### 相关页面

相关主题：[项目概述、安装与基本用法](#page-1), [索引架构、SearchableMap 与 BM25 排序](#page-2), [搜索 API、查询组合与自动建议](#page-3)

<details>
<summary>相关源码文件</summary>

以下源码文件用于生成本页说明：

- [src/MiniSearch.ts](https://github.com/lucaong/minisearch/blob/main/src/MiniSearch.ts)
- [src/SearchableMap/SearchableMap.ts](https://github.com/lucaong/minisearch/blob/main/src/SearchableMap/SearchableMap.ts)
- [src/SearchableMap/fuzzySearch.ts](https://github.com/lucaong/minisearch/blob/main/src/SearchableMap/fuzzySearch.ts)
- [README.md](https://github.com/lucaong/minisearch/blob/main/README.md)
- [src/MiniSearch.test.js](https://github.com/lucaong/minisearch/blob/main/src/MiniSearch.test.js)
- [CHANGELOG.md](https://github.com/lucaong/minisearch/blob/main/CHANGELOG.md)
</details>

# 分词、字段提取、国际化与故障排查

MiniSearch 是一个轻量级内存全文搜索引擎，文本分析流程由「文档 → 字段提取 → 字符串化 → 分词 → 词项处理 → 倒排索引」串联而成。理解这条管道是构建多语言搜索与诊断异常行为的关键。本文围绕默认实现、自定义扩展、国际化策略与社区典型故障四个维度展开。

## 1. 默认分词机制

### 1.1 `SPACE_OR_PUNCTUATION` 正则

默认分词器为 `string.split(SPACE_OR_PUNCTUATION)`，其中 `SPACE_OR_PUNCTUATION = /[\n\r\p{Z}\p{P}]+/u`。该正则使用 Unicode 属性类，能正确识别空格分隔符 `p{Z}` 与所有 Unicode 标点 `p{P}`，因此对拉丁字母及其变音符号（diacritics）、西里尔字母等非拉丁脚本均能给出合理的切分。测试套件验证了意大利语 `perché`、`luna` 与西里尔标题均可被命中（`资料来源：src/MiniSearch.test.js:default tokenization`）。

### 1.2 `tokenize` 与 `processTerm` 的职责拆分

`tokenize` 负责把字符串切成 token；`processTerm` 负责对每个 token 做归一化（如小写、词干提取）。两者均接收 `fieldName` 参数，便于按字段差异化处理。`processTerm` 若返回假值（falsy）则该词项被丢弃；若返回字符串数组，则数组每个元素都作为独立词项入库（`资料来源：src/MiniSearch.ts:71-90`）。

```javascript
tokenize: (text, fieldName) => text.split(SPACE_OR_PUNCTUATION),
processTerm: (term, fieldName) => term.toLowerCase(),
```

## 2. 字段提取与字符串化

`extractField` 从原始文档对象中取出字段值，`stringifyField` 将其转为字符串后再交给分词器。这种解耦让非字符串字段（如 `Date`、数字、嵌套对象）可以自由接入（`资料来源：src/MiniSearch.ts:extractField/stringifyField 类型定义`）。默认实现为 `document[fieldName]` 与 `value.toString()`；若字段是 `Date` 实例，可借助 `toLocaleDateString` 格式化为可读文本后纳入索引。社区曾讨论希望 `extractField` 支持返回非字符串值（[Issue #302](https://github.com/lucaong/minisearch/issues/302)），目前类型签名已为 `any`，由 `stringifyField` 兜底转换。

## 3. 国际化策略与索引流水线

由于默认正则基于 Unicode 属性，绝大多数脚本都能开箱即用。但**词与词之间无显式分隔符**的语言（如中文、韩文）需要外部分词器：

| 语言 | 默认行为 | 推荐方案 |
| ---- | -------- | -------- |
| 拉丁字母（含变音符号） | 正确 | 无需处理 |
| 西里尔 / 希腊字母 | 正确 | 无需处理 |
| 中文（无空格） | 退化为单字匹配 | 接入 jieba 等中文分词器（[Issue #201](https://github.com/lucaong/minisearch/issues/201)） |
| 韩文（助词黏着） | 词干与词形无法对齐 | 接入形态学分词器 [garu-minisearch-tokenizer](https://github.com/lucaong/minisearch/issues/312) |

```mermaid
flowchart LR
  A[原始文档] --> B[extractField]
  B --> C[stringifyField]
  C --> D[tokenize]
  D --> E[processTerm]
  E --> F[倒排索引]
```

只需在实例化时替换 `tokenize` 与 `processTerm`，即可让整条管道适配新语言，无需改动内部结构。

## 4. 典型故障与排查

### 4.1 默认分词器切分引号

`\p{P}` 覆盖了所有 Unicode 标点，包括直引号 `"` 与 `'`。这意味着 `song's` 会被切为 `song` 与 `s`，引发意外匹配（[Issue #309](https://github.com/lucaong/minisearch/issues/309)）。可显式提供 `tokenize: text => text.split(/[\n\r\p{Z}]+/u)` 仅按空白切分。

### 4.2 `MiniSearch.wildcard` 的安全使用

`MiniSearch.wildcard` 是库内置的 `Symbol('*')`（`资料来源：src/MiniSearch.ts:wildcard 静态成员`），用于「匹配全部文档」的快路径。若直接构造一个新的 `Symbol('*')` 传入 `search`，`executeQuery` 会因找不到对应索引条目而抛出 `Cannot read properties of undefined (reading 'map')`（[Issue #307](https://github.com/lucaong/minisearch/issues/307)）。务必通过 `MiniSearch.wildcard` 引用，而非自行构造。

### 4.3 `filter` 仅作用于最终结果

在 `QueryCombination` 中，`SearchOptions.filter` 仅对合并后的结果集生效，并不会下推到每个子查询（[Issue #304](https://github.com/lucaong/minisearch/issues/304)）。若需按子查询过滤，应在外层 `search` 之后自行二次筛选，或拆分为多次 `search` 调用。

### 4.4 `combineWith: 'AND'` 的边界情况

当自定义 `processTerm` 在某些条件下返回空字符串而非 `null`/`undefined` 时，`combineWith: 'AND'` 的求值可能与预期不符（[Issue #311](https://github.com/lucaong/minisearch/issues/311)）。建议在自定义 `processTerm` 中显式返回 `null`/`undefined` 来跳过词项，并结合单元测试验证子查询的 `terms` 数组。

### 4.5 `Cannot read properties of undefined (reading 'keys')`

此异常堆栈指向 `Object.keys(match)`，常见诱因包括：异步回调中引用了已被 `discard` 的文档 ID，或在调用 `search` 之间对索引进行了破坏性操作（[Issue #306](https://github.com/lucaong/minisearch/issues/306)）。可通过 `miniSearch.vacuum()` 清理无效引用，并在调用前确认文档集合的完整性。

## 5. 调试建议

1. 启用 TypeScript 严格模式并显式标注 `SearchOptions` 类型，避免把非字符串字段直接传给 `tokenize`。
2. 在批量 `replace`/`discard` 后调用 `miniSearch.vacuum()`，清除倒排索引中的失效引用。
3. 通过 `miniSearch.toJSON()` 序列化检查 `index` 字段，验证分词与归一化结果是否符合预期。
4. 参照 `src/MiniSearch.test.js` 中 `default tokenization` 的断言风格为自定义 `tokenize`/`processTerm` 编写最小化测试。

## See Also

- API 与搜索选项总览：[README.md](https://github.com/lucaong/minisearch/blob/main/README.md)
- BM25 评分参数：[src/MiniSearch.ts](https://github.com/lucaong/minisearch/blob/main/src/MiniSearch.ts) 中的 `BM25Params` 类型
- 模糊匹配实现：[src/SearchableMap/fuzzySearch.ts](https://github.com/lucaong/minisearch/blob/main/src/SearchableMap/fuzzySearch.ts)
- 倒排索引数据结构：[src/SearchableMap/SearchableMap.ts](https://github.com/lucaong/minisearch/blob/main/src/SearchableMap/SearchableMap.ts)
- 版本变更与破坏性修复：[CHANGELOG.md](https://github.com/lucaong/minisearch/blob/main/CHANGELOG.md)

---

<!-- evidence_pipeline_checked: true -->
<!-- evidence_injected: true -->

---

## Doramagic 踩坑日志

项目：lucaong/minisearch

摘要：发现 19 个潜在踩坑项，其中 4 个为 high/blocking；最高优先级：能力坑 - 来源证据：Filtering not working at sub-queries level。

## 1. 能力坑 · 来源证据：Filtering not working at sub-queries level

- 严重度：high
- 证据强度：source_linked
- 发现：GitHub 社区证据显示该项目存在一个能力理解相关的待验证问题：Filtering not working at sub-queries level
- 对用户的影响：可能增加新用户试用和生产接入成本。
- 证据：community_evidence:github | https://github.com/lucaong/minisearch/issues/304 | 来源类型 github_issue 暴露的待验证使用条件。

## 2. 运行坑 · 来源证据：Cannot read properties of undefined (reading 'keys')

- 严重度：high
- 证据强度：source_linked
- 发现：GitHub 社区证据显示该项目存在一个运行相关的待验证问题：Cannot read properties of undefined (reading 'keys')
- 对用户的影响：可能阻塞安装或首次运行。
- 证据：community_evidence:github | https://github.com/lucaong/minisearch/issues/306 | 来源类型 github_issue 暴露的待验证使用条件。

## 3. 运行坑 · 来源证据：[BUG] global wildcard symbol is not safe.

- 严重度：high
- 证据强度：source_linked
- 发现：GitHub 社区证据显示该项目存在一个运行相关的待验证问题：[BUG] global wildcard symbol is not safe.
- 对用户的影响：可能增加新用户试用和生产接入成本。
- 证据：community_evidence:github | https://github.com/lucaong/minisearch/issues/307 | 来源讨论提到 node 相关条件，需在安装/试用前复核。

## 4. 安全/权限坑 · 来源证据：tokenize() issue with SPACE_OR_PUNCTUATION and quotes

- 严重度：high
- 证据强度：source_linked
- 发现：GitHub 社区证据显示该项目存在一个安全/权限相关的待验证问题：tokenize() issue with SPACE_OR_PUNCTUATION and quotes
- 对用户的影响：可能影响授权、密钥配置或安全边界。
- 证据：community_evidence:github | https://github.com/lucaong/minisearch/issues/309 | 来源类型 github_issue 暴露的待验证使用条件。

## 5. 安装坑 · 来源证据：Allow return types other than string in extractField function

- 严重度：medium
- 证据强度：source_linked
- 发现：GitHub 社区证据显示该项目存在一个安装相关的待验证问题：Allow return types other than string in extractField function
- 对用户的影响：可能增加新用户试用和生产接入成本。
- 证据：community_evidence:github | https://github.com/lucaong/minisearch/issues/302 | 来源讨论提到 npm 相关条件，需在安装/试用前复核。

## 6. 安装坑 · 来源证据：Invalid demo link on npmjs website

- 严重度：medium
- 证据强度：source_linked
- 发现：GitHub 社区证据显示该项目存在一个安装相关的待验证问题：Invalid demo link on npmjs website
- 对用户的影响：可能增加新用户试用和生产接入成本。
- 证据：community_evidence:github | https://github.com/lucaong/minisearch/issues/300 | 来源讨论提到 npm 相关条件，需在安装/试用前复核。

## 7. 安装坑 · 来源证据：Question: is minisearch siutable for code search?

- 严重度：medium
- 证据强度：source_linked
- 发现：GitHub 社区证据显示该项目存在一个安装相关的待验证问题：Question: is minisearch siutable for code search?
- 对用户的影响：可能增加新用户试用和生产接入成本。
- 证据：community_evidence:github | https://github.com/lucaong/minisearch/issues/308 | 来源类型 github_issue 暴露的待验证使用条件。

## 8. 能力坑 · 能力判断依赖假设

- 严重度：medium
- 证据强度：source_linked
- 发现：README/documentation is current enough for a first validation pass.
- 对用户的影响：假设不成立时，用户拿不到承诺的能力。
- 证据：capability.assumptions | https://github.com/lucaong/minisearch | README/documentation is current enough for a first validation pass.

## 9. 维护坑 · 维护活跃度未知

- 严重度：medium
- 证据强度：source_linked
- 发现：未记录 last_activity_observed。
- 对用户的影响：新项目、停更项目和活跃项目会被混在一起，推荐信任度下降。
- 证据：evidence.maintainer_signals | https://github.com/lucaong/minisearch | last_activity_observed missing

- 严重度：medium
- 证据强度：source_linked
- 发现：no_demo
- 证据：downstream_validation.risk_items | https://github.com/lucaong/minisearch | no_demo; severity=medium

## 11. 安全/权限坑 · 存在评分风险

- 严重度：medium
- 证据强度：source_linked
- 发现：no_demo
- 对用户的影响：风险会影响是否适合普通用户安装。
- 证据：risks.scoring_risks | https://github.com/lucaong/minisearch | no_demo; severity=medium

## 12. 安全/权限坑 · 来源证据：Community Korean tokenizer: garu-minisearch-tokenizer

- 严重度：medium
- 证据强度：source_linked
- 发现：GitHub 社区证据显示该项目存在一个安全/权限相关的待验证问题：Community Korean tokenizer: garu-minisearch-tokenizer
- 对用户的影响：可能影响授权、密钥配置或安全边界。
- 证据：community_evidence:github | https://github.com/lucaong/minisearch/issues/314 | 来源讨论提到 npm 相关条件，需在安装/试用前复核。

## 13. 安全/权限坑 · 来源证据：Edge case in combineWith:'AND'

- 严重度：medium
- 证据强度：source_linked
- 发现：GitHub 社区证据显示该项目存在一个安全/权限相关的待验证问题：Edge case in combineWith:'AND'
- 对用户的影响：可能影响授权、密钥配置或安全边界。
- 证据：community_evidence:github | https://github.com/lucaong/minisearch/issues/311 | 来源类型 github_issue 暴露的待验证使用条件。

## 14. 安全/权限坑 · 来源证据：Grabbing <H1>, <H2>, <H3> headers

- 严重度：medium
- 证据强度：source_linked
- 发现：GitHub 社区证据显示该项目存在一个安全/权限相关的待验证问题：Grabbing <H1>, <H2>, <H3> headers
- 对用户的影响：可能影响授权、密钥配置或安全边界。
- 证据：community_evidence:github | https://github.com/lucaong/minisearch/issues/296 | 来源类型 github_issue 暴露的待验证使用条件。

## 15. 安全/权限坑 · 来源证据：Korean tokenizer plugin: garu-minisearch-tokenizer

- 严重度：medium
- 证据强度：source_linked
- 发现：GitHub 社区证据显示该项目存在一个安全/权限相关的待验证问题：Korean tokenizer plugin: garu-minisearch-tokenizer
- 对用户的影响：可能影响授权、密钥配置或安全边界。
- 证据：community_evidence:github | https://github.com/lucaong/minisearch/issues/312 | 来源讨论提到 npm 相关条件，需在安装/试用前复核。

## 16. 安全/权限坑 · 来源证据：Not including all fields in search

- 严重度：medium
- 证据强度：source_linked
- 发现：GitHub 社区证据显示该项目存在一个安全/权限相关的待验证问题：Not including all fields in search
- 对用户的影响：可能影响授权、密钥配置或安全边界。
- 证据：community_evidence:github | https://github.com/lucaong/minisearch/issues/298 | 来源讨论提到 node 相关条件，需在安装/试用前复核。

## 17. 安全/权限坑 · 来源证据：Question about combineWith

- 严重度：medium
- 证据强度：source_linked
- 发现：GitHub 社区证据显示该项目存在一个安全/权限相关的待验证问题：Question about combineWith
- 对用户的影响：可能影响授权、密钥配置或安全边界。
- 证据：community_evidence:github | https://github.com/lucaong/minisearch/issues/297 | 来源类型 github_issue 暴露的待验证使用条件。

## 18. 维护坑 · issue/PR 响应质量未知

- 严重度：low
- 证据强度：source_linked
- 发现：issue_or_pr_quality=unknown。
- 对用户的影响：用户无法判断遇到问题后是否有人维护。
- 证据：evidence.maintainer_signals | https://github.com/lucaong/minisearch | issue_or_pr_quality=unknown

## 19. 维护坑 · 发布节奏不明确

- 严重度：low
- 证据强度：source_linked
- 发现：release_recency=unknown。
- 对用户的影响：安装命令和文档可能落后于代码，用户踩坑概率升高。
- 证据：evidence.maintainer_signals | https://github.com/lucaong/minisearch | release_recency=unknown

<!-- canonical_name: lucaong/minisearch; human_manual_source: deepwiki_human_wiki -->
