lunr search index: working versions (tested 2026-10-16)
Build and query a lunr full-text index with title boosting, field-scoped queries and JSON round-tripping, fully offline.
passed5 passed · 0 failed2026-10-16 02:18 UTC- Status
- passed
- Last passed
- 2026-10-16 02:18 UTC
- Tests
- 5 / 5
- Language
- TypeScript
- Runtime
- node v22.23.3
pinned versions4
lunr@2.3.9@types/lunr@2.3.7@types/node@26.6.5tsx@4.23.15
run it
npm install --no-audit --no-fund --ignore-scripts=false npm test
test environment
node v22.23.3 linux 6.17.0-1022-azure x64 runner: github-actions 38839686104
The code
Exactly the files that passed. Your agent can pull them with get_recipe("lunr-search-index") (install), or see them on GitHub.
package.json
{
"name": "lunr-search-index",
"private": true,
"type": "module",
"scripts": {
"test": "node --import tsx --test test/*.test.ts"
},
"dependencies": {
"lunr": "2.3.9"
},
"devDependencies": {
"@types/lunr": "2.3.7",
"@types/node": "26.6.5",
"tsx": "4.23.15"
}
}
src/search.ts
import lunr from "lunr";
export type Document = {
id: string;
title: string;
body: string;
};
export type SearchResult = {
ref: string;
score: number;
};
export function buildIndex(documents: Document[]): lunr.Index {
return lunr(function (this: lunr.Builder) {
this.ref("id");
this.field("title", { boost: 10 });
this.field("body");
for (const doc of documents) {
this.add(doc);
}
});
}
export function search(index: lunr.Index, query: string): SearchResult[] {
return index.search(query).map((result) => ({
ref: result.ref,
score: result.score,
}));
}
export function serialize(index: lunr.Index): string {
return JSON.stringify(index);
}
export function deserialize(serialized: string): lunr.Index {
return lunr.Index.load(JSON.parse(serialized));
}
test/search.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import {
buildIndex,
deserialize,
search,
serialize,
type Document,
} from "../src/search.ts";
const docs: Document[] = [
{
id: "search",
title: "Full text search",
body: "lunr builds a search index in the browser.",
},
{
id: "serialize",
title: "Index serialization",
body: "Persist a search index using toJSON and reload it.",
},
{
id: "boost",
title: "Boosting fields",
body: "Title matches score higher than body matches.",
},
];
test("finds documents by term", () => {
const index = buildIndex(docs);
const results = search(index, "browser");
assert.equal(results.length, 1);
assert.equal(results[0]!.ref, "search");
assert.ok(results[0]!.score > 0);
});
test("returns an empty result set for unknown terms", () => {
const index = buildIndex(docs);
assert.deepEqual(search(index, "kubernetes"), []);
});
test("supports field-scoped queries", () => {
const index = buildIndex(docs);
const results = search(index, "title:serialization");
assert.deepEqual(
results.map((r) => r.ref),
["serialize"],
);
});
test("boosts title matches over body matches", () => {
const index = buildIndex(docs);
const results = search(index, "index");
assert.deepEqual(
results.map((r) => r.ref),
["serialize", "search"],
);
assert.ok(results[0]!.score > results[1]!.score);
});
test("round-trips an index through toJSON and lunr.Index.load", () => {
const index = buildIndex(docs);
const before = search(index, "browser");
const json = serialize(index);
assert.equal(typeof json, "string");
const reloaded = deserialize(json);
const after = search(reloaded, "browser");
assert.deepEqual(after, before);
assert.equal(after[0]!.ref, "search");
});
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
~/.cursor/mcp.json
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }.vscode/mcp.json
{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }~/.codeium/windsurf/mcp_config.json
{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }MCP settings
{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.