mcpField
A
api-search-meilisearch

api-search-meilisearch

github.com/agents-inc开发2664 次安装2 天前更新
代码仓库

关于 api-search-meilisearch

api-search-meilisearch 是一份面向 TypeScript 项目的 Meilisearch 使用指南型 Skill,适用于在应用中实现搜索的开发者。它涵盖异步索引任务、索引配置、过滤、排序、多索引搜索及多租户访问模式。

主要功能

  1. 01配置 Meilisearch TypeScript 客户端
  2. 02处理异步文档索引与任务
  3. 03设置过滤、排序和搜索属性
  4. 04实现过滤、地理及多索引搜索
  5. 05使用租户令牌实现访问控制

适用场景

  1. 01为应用添加全文搜索与自动补全
  2. 02构建带筛选和分面的导航
  3. 03实现附近地点搜索与距离排序
  4. 04在多个索引或租户间搜索

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/agents-inc/skills --skill api-search-meilisearch

安装后在 Claude Code 中输入/api-search-meilisearch确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

metadata.yaml
reference.md
examples/
core.md
filtering.md
security.md
settings.md

详细介绍

Meilisearch 模式

快速指南: 使用 meilisearch(v0.56+)作为 Meilisearch v1.x 的 TypeScript 客户端。所有写入操作(添加文档、更改设置、创建索引)都是异步的——它们会返回 EnqueuedTaskPromise,并在后台队列中处理。使用搜索查询中的筛选或排序之前,必须先在索引上配置 filterableAttributes 和 sortableAttributes——这会触发完整的重新索引。使用 client.index("name") 获取延迟引用(不会发起网络调用),而 client.getIndex("name") 会从服务端获取索引。仅在脚本、种子数据和测试中对 EnqueuedTaskPromise 使用 .waitTask()——绝不要在请求处理程序中使用。


<critical_requirements>

严重:使用此技能前

所有代码都必须遵循 CLAUDE.md 中的项目约定(kebab-case、命名导出、导入顺序、import type、命名常量)

(必须在搜索查询中使用 filter 之前,先在索引上配置 filterableAttributes——如果某个属性不在 filterableAttributes 中,筛选会静默地返回无结果)

(必须在搜索查询中使用 sort 之前,先在索引上配置 sortableAttributes——对未配置属性进行排序会被静默忽略)

(绝不能在生产环境的请求处理程序中调用 .waitTask()——它会阻塞事件循环并轮询 Meilisearch,直到任务完成;仅在脚本、种子数据和测试中使用)

(如果文档没有 id 字段,必须显式设置主键——Meilisearch 仅在首次添加文档时自动推断主键,推断错误会导致后续批次的索引失败)

</critical_requirements>


示例

  • 核心模式 -- 客户端设置、文档操作、搜索基础、任务管理、TypeScript 集成
  • 过滤与分面 -- 过滤语法、分面搜索、地理搜索、可排序属性
  • 索引设置 -- 排名规则、错字容错、同义词、停用词、可搜索属性、分页
  • 安全与多租户 -- API 密钥、租户令牌、搜索规则、多租户模式

其他资源:

  • reference.md -- 搜索参数速查表、设置默认值、决策框架、反模式

自动检测: Meilisearch、meilisearch、MeiliSearch、meilisearch-js、client.index、addDocuments、updateDocuments、multiSearch、filterableAttributes、sortableAttributes、searchableAttributes、rankingRules、typoTolerance、tenant token、generateTenantToken、EnqueuedTaskPromise、waitTask、facets、_geoRadius、_geoBoundingBox、_geoPoint、instantsearch

适用场景:

  • 为应用添加全文搜索(商品搜索、内容搜索、自动补全)
  • 实现分面导航(类别筛选、价格范围、属性计数)
  • 构建地理位置感知搜索(查找附近地点、按距离排序)
  • 实现多租户搜索,让租户共用一个索引,但只能看到自己的文档
  • 同时跨多个索引搜索(多重搜索、联邦搜索)
  • 实现支持错字容错的即时搜索和实时文档索引

涵盖的关键模式:

  • 客户端初始化和连接管理
  • 文档的 CRUD 操作与异步任务处理
  • 支持过滤、排序、分面和高亮的搜索
  • 使用 _geoRadius、_geoBoundingBox 和距离排序进行地理搜索
  • 跨索引的多重搜索和联邦搜索
  • 索引设置配置(排名规则、错字容错、同义词、停用词)
  • 用于多租户访问控制的租户令牌
  • 用于指定搜索结果类型的 TypeScript 泛型

不适用场景:

  • 在关系型数据库上进行全文搜索(简单场景可使用数据库内置的全文搜索)
  • 日志聚合或分析查询(使用专用的日志/分析搜索引擎)
  • 不包含关键词组件的纯向量语义搜索(使用专用的向量数据库)
  • 搜索少于约 1,000 个文档(客户端筛选更简单)

理念

Meilisearch 是一种搜索引擎,而不是数据库。它会为快速检索建立文档索引,但不是事实来源。核心原则如下:

  1. 一切皆异步 -- 所有写入操作(文档、设置、索引管理)都会排队并异步处理。API 会立即返回任务 ID。设计应用时,不要依赖即时索引。
  2. 先配置,再搜索 -- 使用搜索查询前,必须先配置可过滤属性、可排序属性和可搜索属性。这会触发对所有文档的重新索引。
  3. 默认支持错字容错 -- Meilisearch 开箱即支持错字容错。对于特定字段(商品代码、序列号),应调整 typoTolerance 设置来禁用错字容错,而不是尝试手动实现精确匹配。
  4. 主键至关重要 -- 每个文档都需要唯一的主键。Meilisearch 会从第一份文档中自动推断主键,但显式指定优于隐式推断。创建索引时应设置主键。
  5. 搜索而非查询 -- Meilisearch 针对面向用户的搜索查询进行了优化(支持错字容错、前缀匹配和排名)。它不是 SQL 的替代品。结构化查询应使用过滤器,自然语言查询则使用搜索。

核心模式

模式 1:客户端设置

使用主机和 API 密钥初始化客户端。使用 client.index() 获取延迟加载的本地引用(不会发起网络调用)——优先使用它,而不是会请求服务器的 client.getIndex()。

// Good Example -- Typed client setup
import { Meilisearch } from "meilisearch";

function createSearchClient(): Meilisearch {
  const host = process.env.MEILISEARCH_URL;
  const apiKey = process.env.MEILISEARCH_API_KEY;
  if (!host) {
    throw new Error("MEILISEARCH_URL environment variable is required");
  }

  return new Meilisearch({ host, apiKey });
}

export { createSearchClient };

优点: 验证环境变量,使用命名导出,apiKey 为可选项(Meilisearch 在开发环境中允许未经身份验证的访问)

// Bad Example -- Hardcoded credentials
import { Meilisearch } from "meilisearch";
const client = new Meilisearch({
  host: "http://localhost:7700",
  apiKey: "masterKey123",
});

缺点: 硬编码的主机和 API 密钥会泄露到版本控制中,主密钥也会暴露(在生产环境中应使用范围受限的 API 密钥)

请参阅 examples/core.md,了解健康检查、AbortController 的用法以及自定义请求配置。


模式 2:文档索引(异步)

所有文档操作都会返回 EnqueuedTaskPromise。文档不会立即可供搜索——它们会进入任务队列。

// Good Example -- Add documents with explicit primary key
interface Product {
  productId: string;
  name: string;
  description: string;
  price: number;
  categories: string[];
}

const index = client.index<Product>("products");

// First add: set primary key explicitly
const task = await index.addDocuments(products, { primaryKey: "productId" });
// task.taskUid: number -- use this to track progress

优点: 显式指定主键可避免自动推断问题,TypeScript 泛型可确保文档结构的类型安全

// Bad Example -- Relying on auto-inference
const index = client.index("products");
await index.addDocuments(products); // No primary key specified
// If first document has both 'id' and 'productId', Meilisearch guesses wrong

缺点: Meilisearch 会根据第一个文档推断主键——如果推断错误,后续所有添加操作都可能因主键冲突而失败

请参阅 examples/core.md,了解更新、删除、批处理和任务管理模式。


模式 3:使用筛选器搜索

必须先配置 filterableAttributes,才能使用筛选器。筛选器语法使用类似 SQL 的运算符,包括 AND/OR/NOT。

// Good Example -- Search with filter and sort
const MIN_PRICE = 10;
const MAX_PRICE = 100;

const results = await index.search("wireless headphones", {
  filter: `price >= ${MIN_PRICE} AND price <= ${MAX_PRICE} AND categories = "electronics"`,
  sort: ["price:asc"],
  limit: 20,
});
// results.hits: Product[], results.estimatedTotalHits: number

优点: 使用命名常量表示筛选值,将文本搜索与结构化筛选结合,并显式设置结果数量上限

// Bad Example -- Filtering without configuring filterableAttributes
const index = client.index("products");
// MISSING: await index.updateFilterableAttributes(["price", "categories"])
const results = await index.search("headphones", {
  filter: "price < 50", // Returns 0 results -- silently fails!
});

缺点: 如果属性不在 filterableAttributes 中,筛选器会返回空结果且不报错——这是 Meilisearch 最常见的易错点

请参阅 examples/filtering.md,了解分面搜索、地理筛选器和高级筛选器语法。


模式 4:多重搜索

在单个请求中跨多个索引搜索。联邦搜索会将结果合并为统一列表。

// Good Example -- Multi-search across indexes
const results = await client.multiSearch({
  queries: [
    { indexUid: "products", q: "laptop", limit: 5 },
    { indexUid: "articles", q: "laptop review", limit: 5 },
  ],
});
// results.results[0].hits -- products
// results.results[1].hits -- articles

// Federated search -- merged results
const federated = await client.multiSearch({
  federation: {},
  queries: [
    { indexUid: "products", q: "laptop" },
    { indexUid: "articles", q: "laptop" },
  ],
});
// federated.hits -- single merged list sorted by relevance

优点: 通过单个网络请求搜索多个索引,联邦搜索可对跨索引结果进行统一排名

请参阅 examples/core.md,了解带查询权重的联邦搜索。


模式 5:任务管理

写入操作是异步的。使用任务 UID 跟踪进度。仅在脚本和测试中使用 .waitTask()。

// Good Example -- Task tracking in a seed script
const task = await index.addDocuments(products, {
  primaryKey: "productId",
});

// In scripts/seeds: wait for completion
const completed = await task.waitTask();
if (completed.status === "failed") {
  throw new Error(`Indexing failed: ${completed.error?.message}`);
}
console.log(`Indexed ${completed.details?.indexedDocuments} documents`);

优点: 在种子数据脚本中(而非请求处理程序中)使用 .waitTask(),检查错误状态并查看任务详情

// Bad Example -- Waiting in a request handler
app.post("/products", async (req, res) => {
  const task = await index.addDocuments([req.body]);
  await task.waitTask(); // BLOCKS the request until Meilisearch processes the task!
  res.json({ success: true });
});

缺点: .waitTask() 会反复轮询 Meilisearch,阻塞请求处理程序——任务可能因队列深度不同而耗时数秒或数分钟

请参阅 examples/core.md,了解批量任务管理和任务状态轮询。


模式 6:索引设置配置

更改设置会触发完整的重新索引。请在添加文档之前配置设置,以避免重新索引。

// Good Example -- Configure index before adding documents
const index = client.index("products");

// Step 1: Configure settings (triggers re-index)
await index
  .updateSettings({
    filterableAttributes: ["price", "categories", "brand", "inStock"],
    sortableAttributes: ["price", "createdAt"],
    searchableAttributes: ["name", "description", "brand"],
    rankingRules: [
      "words",
      "typo",
      "proximity",
      "attribute",
      "sort",
      "exactness",
      "price:asc",
    ],
    typoTolerance: {
      disableOnAttributes: ["sku", "barcode"],
    },
    synonyms: {
      phone: ["smartphone", "mobile"],
      laptop: ["notebook"],
    },
  })
  .waitTask(); // OK in setup script

// Step 2: Add documents (indexes with correct settings)
await index.addDocuments(products, { primaryKey: "productId" });

优点: 在添加文档前配置设置可避免重复重新索引,在精确匹配字段上禁用拼写容错,并为常见别名定义同义词

请参阅 examples/settings.md,了解所有设置选项、停用词和分页配置。


<decision_framework>

决策框架

该选择哪种搜索方式?
What kind of search do I need?
-- Single index, text query? -> index.search(query, options)
-- Multiple indexes, separate results? -> client.multiSearch({ queries })
-- Multiple indexes, merged results? -> client.multiSearch({ federation: {}, queries })
-- Browse/filter without text? -> index.search("", { filter, sort })  (placeholder search)
筛选还是搜索?
How should users find data?
-- Natural language, typo-tolerant? -> Use the `q` parameter (search)
-- Exact attribute matching? -> Use `filter` parameter
-- Both? -> Combine: search("query", { filter: "category = 'X'" })
-- Browsing without a query? -> Placeholder search: search("", { filter, sort })
分页策略?
How should I paginate results?
-- Infinite scroll / load more? -> Use offset + limit (default)
-- Page numbers (page 1, 2, 3)? -> Use page + hitsPerPage
-- NOTE: Default maxTotalHits is 1000 -- increase in pagination settings if needed
任务管理策略?
How should I handle async operations?
-- Seed script / migration? -> .waitTask() is fine
-- Test setup? -> .waitTask() to ensure data is ready
-- API request handler? -> Fire-and-forget, return task UID to client
-- Need confirmation? -> Return taskUid, let client poll GET /tasks/:uid

</decision_framework>


<red_flags>

危险信号

高优先级问题:

  • 未先配置 filterableAttributes / sortableAttributes 就进行筛选或排序——筛选会静默返回空结果,排序会被静默忽略
  • 在生产请求处理程序中使用 .waitTask()——会阻塞事件循环,导致高负载时请求超时
  • 在客户端代码中使用主密钥——会暴露完整的管理员权限;请使用仅限搜索的 API 密钥或租户令牌
  • 未显式设置主键——Meilisearch 会根据第一个文档自动推断主键,可能选错字段,导致后续所有索引操作失败

中优先级问题:

  • 添加文档后才配置设置——会触发所有文档的完整重新索引,大型数据集可能需要几分钟
  • 超出默认的 maxTotalHits: 1000 分页限制——搜索结果会静默限制在 1000 条;如需更深层分页,可通过设置中的 pagination.maxTotalHits 提高上限
  • 在筛选条件中使用 AND / OR 却未加括号——AND 的优先级高于 OR,导致筛选结果出乎预期
  • 未处理任务失败——失败的任务不会更改索引,但只有检查任务状态才能看到错误

易踩问题与边界情况:

  • 地理搜索必须在 filterableAttributes 中包含 _geo——仅添加带有 _geo 字段的文档还不够,必须显式列出该属性
  • client.index("name") 不会创建索引,也不会验证索引是否存在——它只会返回本地引用;要实际创建索引,请使用 client.createIndex("name")
  • 搜索空字符串(search(""))是有效的“占位搜索”——会返回所有符合筛选条件的文档,适用于浏览和分面导航
  • Meilisearch 任务队列上限约为 10 GiB——如果队列已满,新的写入操作会因 no_space_left_on_device 而失败;请定期删除已完成的任务
  • 同义词不适用于筛选——即使已将“phone”和“smartphone”配置为同义词,按“phone”筛选也不会匹配包含“smartphone”的文档
  • _geo 字段格式严格:必须为 { lat: number, lng: number }——使用 longitude 而非 lng 会导致 invalid_document_geo_field 错误
  • 更改设置(如 filterableAttributes 等)也会排入任务队列——不会立即生效;依赖新设置前,请等待任务完成
  • hitsPerPage 和 page 参数会覆盖 offset / limit——不要在同一查询中混用这两种分页方式
  • 默认 maxTotalHits 为 1000——即使使用基于 offset 的分页,若不提高此设置,也无法访问位置 1000 之后的文档

</red_flags>


<critical_reminders>

重要提醒

所有代码都必须遵循 CLAUDE.md 中的项目约定(kebab-case、命名导出、导入顺序、import type、命名常量)

(必须先在索引上配置 filterableAttributes,再在搜索查询中使用 filter——如果属性不在 filterableAttributes 中,筛选会静默返回空结果)

(必须先在索引上配置 sortableAttributes,再在搜索查询中使用 sort——对未配置的属性进行排序会被静默忽略)

(绝不能在生产请求处理程序中调用 .waitTask()——它会阻塞事件循环,轮询 Meilisearch 直到任务完成;仅在脚本、种子数据和测试中使用)

(当文档缺少 id 字段时,必须显式设置主键——Meilisearch 仅在首次添加文档时自动推断主键,推断错误会导致后续批次的索引操作失败)

不遵循这些规则会导致搜索静默失败、请求超时和索引错误。

</critical_reminders>

评论

支持 Markdown