npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zbsearch/docs-index

v4.0.0

Published

Turns documentation sources into a ZBSearch index, and queries it in the browser

Readme

@zbsearch/docs-index

The engine-facing half of the ZBSearch documentation integrations: it turns markdown into search records, builds a ZBSearch index from them, and queries that index in the browser.

It is the shared core behind @zbsearch/plugin-docusaurus and @zbsearch/plugin-starlight. Use it directly when you are wiring ZBSearch into a framework neither of those covers.

Installation

npm install @zbsearch/docs-index

Building an index

import { buildIndex, dialectOf, parseMarkdown } from '@zbsearch/docs-index/node'
import { HIERARCHY_SEPARATOR, type SearchRecord } from '@zbsearch/docs-index'

const file = 'docs/intro.md'
const parsed = parseMarkdown(await readFile(file, 'utf8'), { dialect: dialectOf(file) })

const records: SearchRecord[] = parsed.sections.map((section) => ({
  title: parsed.title ?? 'Introduction',
  section: section.heading,
  hierarchy: ['Guides', 'Introduction', ...section.ancestors].join(HIERARCHY_SEPARATOR),
  content: section.content,
  url: section.anchor ? `/docs/intro#${section.anchor}` : '/docs/intro',
  category: 'Docs',
  path: section.ancestors.join(HIERARCHY_SEPARATOR)
}))

const payload = await buildIndex(records, 'english')

parseMarkdown splits a document into one section per heading. Front matter, fenced code, MDX imports and JSX are removed first; anchors follow the same rules Docusaurus and Starlight use, including explicit {#custom-id} syntax.

The dialect option decides how the source is read. It defaults to md, which is plain CommonMark; pass mdx — or let dialectOf(filePath) pick from the extension — for files whose import and export lines carry their ESM meaning. Reading a .md file as mdx is not harmless: MDX disables indented code blocks, so an indented line can turn into a heading.

buildIndex returns a JSON-serializable payload. Only title, section, hierarchy and content are tokenized — url, category and path ride along untouched and come back on every hit.

Searching in the browser

import { createIndexLoader, createSearcher } from '@zbsearch/docs-index'

const loadIndex = createIndexLoader(async () => (await fetch('/zbsearch-index.json')).json())

const searcher = createSearcher(loadIndex, {
  boost: { title: 4, section: 3, hierarchy: 1.5, content: 1 },
  maxResults: 12,
  tolerance: 1,
  threshold: 0,
  snippetLength: 140
})

createIndexLoader fetches and rehydrates at most once per page session, sharing one promise between concurrent callers and forgetting a failed attempt so the next one retries. ZBSearch itself is imported dynamically, which keeps it out of the bundle until someone actually searches.

The resulting searcher is exactly the shape @zbsearch/searchbox-react expects.

Exports

| Entry point | Contents | | --------------------------- | ------------------------------------------------------------------------ | | @zbsearch/docs-index | Record shape, schema, defaults, and the browser-side loader and searcher | | @zbsearch/docs-index/node | parseMarkdown, stripInlineMarkup and buildIndex |

License

Apache-2.0