ProvenPin Install

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

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.