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

@isomx/meilisearch-index-mgr

v4.3.7

Published

A helper around the Meilisearch JavaScript client that makes it easier to drive rich, reactive search UIs. It wraps a single Meilisearch Index with an RxJS-powered queue, optional debouncing, easy in-flight cancellation, and an optional local cache of hit

Readme

Summary

A helper around the Meilisearch JavaScript client that makes it easier to drive rich, reactive search UIs. It wraps a single Meilisearch Index with an RxJS-powered queue, optional debouncing, easy in-flight cancellation, and an optional local cache of hits for instant access later.

Features

  • Observable stream of results you can subscribe to from any component
  • Optional debouncing for fast, low-latency typing experiences
  • Auto-abandon in-flight requests when a new one is queued
  • Simple local cache of hits with configurable cache key and case-normalization
  • "Load more" helper that appends results to your existing hits

Installation

  • Peer dependency: meilisearch ^0.54
  • Dependency: rxjs ^6.6

Use your package manager of choice:

  • npm: npm i @isomorix/meilisearch-index-mgr
  • yarn: yarn add @isomorix/meilisearch-index-mgr
  • pnpm: pnpm add @isomorix/meilisearch-index-mgr

Quick start

  • Create a Meilisearch client and index
  • Create an IndexMgr instance
  • Subscribe to results and/or fire requests

Example

import { MeiliSearch } from 'meilisearch';
import { IndexMgr } from '@isomorix/meilisearch-index-mgr';

const client = new MeiliSearch({ host: 'http://127.0.0.1:7700' });
const index = client.index('movies');

const mgr = new IndexMgr(index, {
  debounce: 200,
  autoAbandonInFlight: true,
  cache: true,
  cacheKey: 'id',
  lowerCaseCacheKeys: false,
});

// Render from anywhere by subscribing
const sub = mgr.subscribe(({ resp, query, error }) => {
  if (error) {
    console.error('Search failed', error);
    return;
  }
  if (!resp) return;
  console.log('Results for', query, resp.hits);
});

// Fire a request from a different component (no subscription required here)
mgr.next({ query: 'batman', options: { limit: 10 } });

// Later, when done
sub.unsubscribe();
mgr.complete();

Common UI patterns

Debounced search input (vanilla JS)

const input = document.querySelector('#search');
input.addEventListener('input', (e) => {
  mgr.next({ query: e.target.value, options: { limit: 5 } });
});
// Debouncing is handled by the mgr options.

Autocomplete dropdown (vanilla JS)

  • Subscribe to mgr and render a dropdown under your input
  • Use payload.options.attributesToHighlight and highlight pre/post tags to style matches

Example:

const dropdown = document.querySelector('#ac');

const acSub = mgr.subscribe(({ resp }) => {
  if (!resp) return;
  dropdown.innerHTML = resp.hits.map(h => `<div class="item">${h.title}</div>`).join('');
  dropdown.hidden = resp.hits.length === 0;
});

input.addEventListener('keydown', (e) => {
  if (e.key === 'Enter') dropdown.hidden = true;
});

Load more / infinite scroll

  • The response is enriched with payload.hasMore and payload.getMore(limit?), also available on payload.resp.hasMore and payload.resp.getMore(limit?).
  • Call payload.getMore() (or payload.resp.getMore()) to fetch the next page. Returned hits are appended to payload.resp.hits and a new Array is set on the provided payload containing all results.

Example:

const loadMoreBtn = document.querySelector('#loadMore');

const listSub = mgr.subscribe(({ resp }) => {
  if (!resp) return;
  renderList(resp.hits);
  loadMoreBtn.disabled = !resp.hasMore;
});

loadMoreBtn.addEventListener('click', () => {
  const current = mgr.getResp();
  if (current && current.getMore) {
    // Optionally pass a new limit for the next page
    current.getMore(20).subscribe();
  }
});

Using the local cache

  • Enable cache with cache: true (or provide your own object)
  • Choose a cacheKey that uniquely identifies a document (e.g., id or slug)
  • Retrieve items quickly without hitting the server: mgr.getFromCache(key)

Example:

const hit = mgr.getFromCache('movie-123');
if (hit) {
  // Render instantly while you also kick off a background refresh
}

Abandoning requests (e.g., fast typing)

  • Use mgr.get(payload) if you want an observable you can unsubscribe to cancel
  • Or set autoAbandonInFlight: true and use mgr.next(payload) to auto-cancel previous requests

Example:

const subReq = mgr.get({ query: 'super', options: { limit: 5 } }).subscribe(({ resp }) => {
  // Handle results
});
// Cancel in-flight
subReq.unsubscribe();

React example (functional)

import React, { useEffect, useMemo, useState } from 'react';
import { MeiliSearch } from 'meilisearch';
import { IndexMgr } from '@isomorix/meilisearch-index-mgr';

export function MovieSearch() {
  const [hits, setHits] = useState([]);
  const [query, setQuery] = useState('');

  const mgr = useMemo(() => {
    const client = new MeiliSearch({ host: 'http://127.0.0.1:7700' });
    return new IndexMgr(client.index('movies'), { debounce: 200, autoAbandonInFlight: true });
  }, []);

  useEffect(() => {
    const sub = mgr.subscribe(({ resp }) => {
      if (resp) setHits(resp.hits);
    });
    return () => { sub.unsubscribe(); mgr.complete(); };
  }, [mgr]);

  useEffect(() => { mgr.next({ query, options: { limit: 8 } }); }, [mgr, query]);

  return (
    <div>
      <input value={query} onChange={(e) => setQuery(e.target.value)} placeholder="Search..." />
      <ul>{hits.map(h => <li key={h.id}>{h.title}</li>)}</ul>
    </div>
  );
}

Vue 3 example

<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { MeiliSearch } from 'meilisearch';
import { IndexMgr } from '@isomorix/meilisearch-index-mgr';

const query = ref('');
const hits = ref([]);
let mgr;

onMounted(() => {
  const client = new MeiliSearch({ host: 'http://127.0.0.1:7700' });
  mgr = new IndexMgr(client.index('movies'), { debounce: 200, autoAbandonInFlight: true });
  const sub = mgr.subscribe(({ resp }) => { if (resp) hits.value = resp.hits; });
  // store subscription if you need to unsubscribe later
});

function onInput(e) {
  mgr.next({ query: e.target.value, options: { limit: 8 } });
}

onBeforeUnmount(() => { mgr?.complete(); });
</script>
<template>
  <input :value="query" @input="onInput" placeholder="Search movies" />
  <ul>
    <li v-for="h in hits" :key="h.id">{{ h.title }}</li>
  </ul>
</template>

API summary

IndexMgr constructor

new IndexMgr(index: Meili Index, options?: IndexMgrOptions)

  • index: client.index('name') from meilisearch
  • options:
    • debounce?: number — milliseconds to delay before running the latest queued request
    • cache?: true | object — enable caching of hits into the object
    • cacheKey?: string — property name on each hit used as the cache key
    • lowerCaseCacheKeys?: boolean — normalize keys to lowercase
    • preparePayload?: (payload) => payload — hook to modify payloads before execution
    • autoAbandonInFlight?: boolean — cancel in-flight request when next() queues a new one

IndexMgr methods

  • get(payload): Observable — queue a request and return an observable that emits the payload when done. Unsubscribe to cancel in-flight.
  • next(payload): void — queue a request without returning an observable. Results are emitted to subscribers of the IndexMgr instance.
  • execute(payload): Observable — run one-off request without touching cache or notifying subscribers.
  • subscribe(observerOrNext?): Subscription | Observable — subscribe to payloads with results; if no arg, returns the observable stream.
  • reset(): IndexMgr — clears cache (if enabled) and last value.
  • complete(): void — dispose and complete internal streams.
  • getValue(): payload | null — last completed payload.
  • getResp(): response | undefined — last completed response.
  • getFromCache(key: string): object | undefined — retrieve a cached hit.

Payload (what you pass to get()/next()/execute())

  • query?: string — search query
  • options?: SearchParams — meilisearch search options
  • prevHits?: any[] — when using getMore internally, previous hits are merged with the next page
  • resp?: SearchResponse — set on completion
  • hasMore?: boolean — indicates if more results are available
  • getMore?: (limit?: number) => Observable — helper to fetch next page and append to resp.hits
  • error?: any — set if the search fails

TypeScript support

  • This package ships types at types/index.d.ts.
  • After installing, editors/TS should resolve types automatically.
  • You can parameterize the document shape: IndexMgr<MyDoc>

Example:

type Movie = { id: string; title: string; }; 
const mgr = new IndexMgr<Movie>(index, { cache: true, cacheKey: 'id' });
const sub = mgr.subscribe(({ resp }) => {
  const titles: string[] = (resp?.hits ?? []).map(h => h.title);
});

Best practices and tips

  • Use debounce option to debounce typing to reduce load and avoid flicker; 150–300ms is common.
  • Use autoAbandonInFlight in UIs where a new keystroke makes prior results irrelevant.
  • Keep cacheKey stable (id/uuid/slug) if you enable caching; combine with lowerCaseCacheKeys for human-entered keys.
  • Show loading states by tracking the time between queuing and receiving a response.
  • Use getMore() to build infinite scroll; it automatically appends to resp.hits.
  • Always clean up: unsubscribe() subscriptions you own and call indexMgr.complete() when disposing the IndexMgr.

Error handling

  • Subscribe to errors by inspecting payload.error in your subscription callback.
  • Network cancellations (AbortError) when you cancel are expected; consider ignoring those in UI.

SSR notes

  • This library is UI/runtime focused. If using with SSR, instantiate and dispose inside request or per-view lifecycles to avoid leaking subscriptions.