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

davepi-plugin-search

v0.1.1

Published

Full-text search for dAvePi. A schema declares `search: { fields, facets }` and the plugin creates the index on boot, keeps it in sync off the record event bus, and mounts REST `GET /api/{v}/{path}/search` plus a GraphQL `{path}Search` query — all tenant-

Readme

davepi-plugin-search

Full-text search for dAvePi. A schema opts in with a search block; the plugin builds the backend index at boot, keeps it in sync off the record event bus, and exposes tenant-scoped REST and GraphQL search surfaces — no route code to write. Pluggable backend: Typesense (default), Meilisearch, or Algolia.

Reach for this instead of Mongo $text the moment you need typo-tolerance, faceted filtering, weighted ranking, or sub-50ms latency at scale.

Install

npm install davepi-plugin-search

Add it to your project's package.json:

{ "davepi": { "plugins": ["davepi-plugin-search"] } }

The plugin stays dormant (logs a warning and does nothing) until SEARCH_API_KEY is set, so it's safe to declare before provisioning the search service.

Opt a schema in

// schema/versions/v1/product.js
module.exports = {
  path: 'product',
  collection: 'product',
  fields: [
    { name: 'userId', type: String, required: true },
    { name: 'title', type: String },
    { name: 'description', type: String },
    { name: 'tags', type: [String] },
    { name: 'category', type: String },
    { name: 'inStock', type: Boolean },
  ],
  search: {
    fields: [
      { name: 'title', weight: 3 }, // higher weight ranks higher
      { name: 'description' },        // bare string == weight 1
      'tags',
    ],
    facets: ['category', 'inStock'],
    sortDefaults: ['createdAt:desc'],
  },
};

Schemas without a search block are untouched.

Use it

REST (added automatically, no route code):

GET /api/v1/product/search?q=red+shoes&facet=category&filter=inStock:true
{
  "hits": [{ "_id": "…", "title": "Red running shoes", "score": 99 }],
  "facets": { "category": [{ "value": "footwear", "count": 12 }] },
  "found": 12
}

GraphQL:

query {
  productSearch(q: "red shoes", facet: ["category"], filter: "inStock:true") {
    hits
    facets
    found
  }
}

Both surfaces AND a userId:{caller} predicate (from the JWT / API key / client id) into every query server-side, so a caller can never read another tenant's rows — and that predicate can't be widened via the filter arg.

Programmatic (advanced / cross-schema):

const search = require('davepi-plugin-search');
const result = await search.query({ resource: 'product', q: 'red shoes', user });

Backfill / recovery

After declaring search on an already-populated schema — or after a backend outage that dropped sync events, or after a bulk write (bulk paths don't fan out per-record events) — backfill the index:

npx davepi-plugin-search reindex product

The plugin also detects a changed search config by hashing it; a mismatch on boot re-asserts the index settings and backfills automatically (with a warning).

Configuration

| Variable | Required | Default | Description | |----------|----------|---------|-------------| | SEARCH_BACKEND | no | typesense | One of typesense / meilisearch / algolia. | | SEARCH_API_URL | typesense / meilisearch | — | E.g. http://localhost:8108. | | SEARCH_API_KEY | yes (else dormant) | — | Admin API key used for indexing. | | SEARCH_PUBLIC_KEY | no | falls back to SEARCH_API_KEY | Restricted key used to mint per-request scoped search keys (Typesense / Algolia). | | ALGOLIA_APP_ID | algolia | — | Algolia application ID. | | SEARCH_INDEX_PREFIX | no | davepi | Index/collection name prefix (davepi_product). | | SEARCH_SYNC_DEBOUNCE_MS | no | 0 | If >0, batches index upserts within this window (helps under heavy write load). |

Scoped search keys (advanced)

When SEARCH_PUBLIC_KEY is set, POST /api/v1/{path}/search/token returns a tenant-scoped, search-only key a browser client can use to talk to the search service directly. Supported on Typesense and Algolia; Meilisearch returns a clear "unsupported" error in v1.

Backend setup

Typesense (default — modern, fast, self-hostable)

# docker-compose.yml
services:
  typesense:
    image: typesense/typesense:27.1
    ports: ["8108:8108"]
    command: --data-dir /data --api-key=xyz --enable-cors
    volumes: ["typesense-data:/data"]
volumes: { typesense-data: {} }
SEARCH_BACKEND=typesense
SEARCH_API_URL=http://localhost:8108
SEARCH_API_KEY=xyz

Meilisearch (self-hostable)

# docker-compose.yml
services:
  meilisearch:
    image: getmeili/meilisearch:v1.10
    ports: ["7700:7700"]
    environment: { MEILI_MASTER_KEY: masterKey }
    volumes: ["meili-data:/meili_data"]
volumes: { meili-data: {} }
SEARCH_BACKEND=meilisearch
SEARCH_API_URL=http://localhost:7700
SEARCH_API_KEY=masterKey

Algolia (hosted; paid beyond a small free tier)

Sign up at algolia.com, create an app, and grab the Application ID and an Admin API Key (Dashboard → Settings → API Keys).

SEARCH_BACKEND=algolia
ALGOLIA_APP_ID=YourAppId
SEARCH_API_KEY=YourAdminApiKey
SEARCH_PUBLIC_KEY=YourSearchOnlyApiKey   # optional, for scoped client keys

Notes

  • Default to Typesense or Meilisearch unless you specifically want Algolia's hosted service — both are free and self-hostable. Algolia is paid beyond a small tier.
  • Indexes can be larger than the source documents; the projection step (only declared search.fields + facets are indexed) is the main mitigation.
  • The package has zero runtime dependencies — adapters talk the backends' HTTP APIs via the global fetch (Node 18+).

License

ISC