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 🙏

© 2024 – Pkg Stats / Ryan Hefner

lofi-dx

v1.2.0

Published

A small, fast, local-first, searchable index for client side apps written in Typescript.

Downloads

139

Readme

lofi-dx

Build Status Code Style: Google

A small, fast, local-first, searchable index for client side apps written in Typescript. Has support for required, negated, and phrase queries. A set of default stopwords that can be extended are filtered out from queries and the index. Search results are ranked using simple TF-IDF.

The purpose of this is to allow making queries like +jaguar speed "south america" -car in a client side application (think offline PWA):

Demo app

Usage

First install the npm package using npm i lofi-dx, then import it in your project. Now create an index and add documents to it:

import * as lofi from 'lofi-dx';

const index = lofi.createIndex({
  uidKey: 'id', // document unique identifier
  fields: ['title'] // document fields to index
});

index.addDocuments([
  { id: 3, name: 'Mike', title: 'Chief Forward Impact Engineer 3 Foo' },
  { id: 7, name: 'Joe Doe', title: 'Chief Interactions Liason' },
  { id: 11, name: 'Alice Smith', title: 'UX Designer Bar Baz' },
  { id: 21, name: 'Jamie Black', title: 'Foo Graphic Designer Biz' },
  { id: 32, name: 'Joe Brown', title: 'Senior Software Engineer Barfoo' },
  { id: 49, name: 'Helen Queen', title: 'Staff Dynamic Resonance Orchestrator Foo' },
  { id: 55, name: 'Mary', title: 'Queen Product Program Executive Manager Foo' },
  { id: 101, name: 'Alan Smith', title: 'Bar Senior Staff Software Engineer 3 Foobar' },
]);

Search the index:

const people = lofi.createSearch(index);
const results = people.search(`"software engineer" ux designer -"engineer 3"`);
console.log(results);
[
  { "id": 11, "name": "Alice Smith", "title": "UX Designer Bar Baz" },
  { "id": 21, "name": "Jamie Black", "title": "Foo Graphic Designer Biz"},
  { "id": 32, "name": "Joe Brown", "title": "Senior Software Engineer Barfoo" }
]

See example directory for UI integration.

Inverted Index

An inverted index is an index of words and which documents those words occur in. Instead of linearly scanning every document looking for words, the inverted index reverses the logic by using the words to find the documents. Positions of every term occurrence are included in the index to support phrase queries and are delta encoded and base36 encoded before entering the index.

The index's internal word map is represented space efficiently as follows:

{
  // word
  "yosemite": { 
    // document UID: metadata
    "2":"ae",
    "3":"5o,nb,2a,2c,n,31",
    "7":"j5",
    "15":"39,1jn"
  },

  "other": {...}
}

Note: This has been tested only in English and likely won't work with other alphabets.

Memory

Given that this is a client-side solution to full-text search, the documents and the index are loaded in memory. Although the index is represented space efficiently, keep in mind that a client side full-text search implementation is likely not practical for large enough datasets.

The point of client side full-text search is to improve the user experience in offline mode, or when Internet connection is flakey, or when such client side feature is desirable over querying a server. However, if your app runs into memory issues and crashes the Browser tab because you're trying to load many megabytes worth of documents, then that will actually derail the UX. Have a cap on the total bytes you're storing client-side and loading into memory.

Persistence

There are methods for writing the index to localStorage using a TTL and later loading it but no other assumptions are made. Perhaps localStorage (limited to 5MB) works for your usecase or you may need to reach for IndexedDB.

// Create storage for an index
const storage = lofi.createStorage(index);

async function loadDocs() {
  // load index from storage
  if (storage.isSaved()) {
    await storage.load();
    return;
  }
  
  // fetch docs & add them to the index
  const response = await fetch('./data.json');
  const { data } = await response.json();
  index.addDocuments(data);

  // save to storage
  const ONE_DAY = 1000 * 60 * 60 * 24;
  storage.save({ ttl: ONE_DAY });
}

// Maybe later...
// await storage.clear();