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

js-idb

v1.0.1

Published

Simple JSON database with fast search over data via indexes and type checking

Downloads

14

Readme

js-idb

A lightweight JSON database for TypeScript with schema validation and indexed search. Zero dependencies. Works in-memory or persisted to disk.

  • Written in TypeScript with full type inference
  • Schema validation with string, number, boolean, object types
  • Fast indexed search with prefix, suffix, contains, and range queries
  • Sorting by indexed fields (ascending/descending) without client-side sort
  • In-memory or file-based persistence
  • Zero dependencies, Node.js 24+

Install

npm install js-idb

Quick start

import { createDB } from "js-idb";

const db = createDB({
  collections: {
    users: {
      schema: {
        name: { type: "string", index: true, indexSetting: { ignoreCase: true } },
        age: { type: "number", index: true },
        active: { type: "boolean", default: true },
        meta: { type: "object" },
      },
    },
  },
});

db.users.add({ name: "Josef", age: 30, active: true, meta: { role: "admin" } });
db.users.find({ name: "josef" }); // case-insensitive match

Schema

Each collection requires a schema. Fields support four types:

| Type | JS type | Indexable | Notes | |------|---------|-----------|-------| | string | string | Yes | Supports ignoreCase index setting | | number | number | Yes | NaN is rejected | | boolean | boolean | Yes | | | object | Record<string, unknown> | No | Arbitrary data, nesting allowed, no type checking on contents |

Field options

{
  type: "string",            // required — field type
  index: true,               // optional — enable search via find()
  indexSetting: {             // optional — only for indexed string fields
    ignoreCase: true,
  },
  default: "",               // optional — applied when field is omitted on add
}
  • All fields are required on add unless they have a default
  • default values are validated against the field type at database creation
  • update always accepts partial records

API

createDB(options)

Creates a database instance.

const db = createDB({
  path: "./data",  // optional — omit for in-memory only
  collections: {
    users: { schema: { /* ... */ } },
  },
});

With file persistence, each collection is stored as <name>.data.json and <name>.meta.json. Indexes are rebuilt from data on load.

collection.add(record): Document

Inserts a single record. Returns the record with an auto-generated _id.

const doc = db.users.add({ name: "Josef", age: 30, active: true, meta: {} });
// doc._id — auto-generated unique ID

collection.addMany(records): Document[]

Inserts multiple records in a single batch (one write operation).

const docs = db.users.addMany([
  { name: "Karel", age: 25, active: true, meta: {} },
  { name: "Anna", age: 35, active: false, meta: {} },
]);

collection.get(id): Document | undefined

Retrieves a single record by ID.

const doc = db.users.get("some-id");

collection.all(options?): Document[]

Returns all records in the collection. Supports optional sorting.

const docs = db.users.all();
const sorted = db.users.all({ sort: 'age' });    // ascending
const desc = db.users.all({ sort: '-age' });      // descending

collection.find(query, options?): Document[]

Searches indexed fields. All queried fields must have index: true. Multiple fields are intersected (AND). Supports optional sorting.

// String queries
db.users.find({ name: "josef" });       // exact match
db.users.find({ name: "jos%" });        // prefix
db.users.find({ name: "%sef" });        // suffix
db.users.find({ name: "%ose%" });       // contains

// Number queries
db.users.find({ age: "30" });           // exact
db.users.find({ age: ">20" });          // greater than
db.users.find({ age: ">=20" });         // greater than or equal
db.users.find({ age: "<30" });          // less than
db.users.find({ age: "<=30" });         // less than or equal

// Boolean queries
db.users.find({ active: "true" });

// Compound (intersection)
db.users.find({ name: "jos%", age: "<=30" });

// With sorting
db.users.find({ age: ">20" }, { sort: "name" });   // results sorted by name
db.users.find({ active: "true" }, { sort: "-age" }); // sorted by age descending

collection.update(id, partial): Document

Updates specific fields on an existing record. Accepts a partial record.

const updated = db.users.update(doc._id, { name: "Josef II" });

collection.remove(id): void

Deletes a record by ID.

db.users.remove(doc._id);

collection.clear(): void

Removes all records from the collection.

db.users.clear();

collection.count: number

Returns the number of records in the collection.

db.users.count; // 42

db.collection(name)

Access a collection by name (useful for dynamic access).

const col = db.collection("users");

TypeScript

Types are inferred from the schema automatically:

const db = createDB({
  collections: {
    users: {
      schema: {
        name: { type: "string" },
        age: { type: "number" },
      },
    },
  },
});

const doc = db.users.add({ name: "Josef", age: 30 });
doc.name; // string
doc.age;  // number
doc._id;  // string

For more control (e.g. making fields with defaults optional), provide your own interface:

interface User {
  name: string;
  age: number;
  active?: boolean; // optional — schema has default: true
}

const db = createDB<{ users: User }>({
  collections: {
    users: {
      schema: {
        name: { type: "string", index: true },
        age: { type: "number" },
        active: { type: "boolean", default: true },
      },
    },
  },
});

db.users.add({ name: "Josef", age: 30 }); // active is optional
db.users.update(id, { age: 31 });         // Partial<User>
const doc = db.users.get(id);             // User & { _id: string } | undefined

Sorting

Both all() and find() accept an optional { sort } parameter. Prefix the field name with - for descending order.

db.users.all({ sort: 'age' });                        // ascending by age
db.users.all({ sort: '-name' });                      // descending by name
db.users.find({ active: 'true' }, { sort: 'name' });  // filtered + sorted

Only indexed fields can be used for sorting. Since indexes are stored as sorted arrays, sorting is O(n) — a linear scan of pre-sorted data — instead of the O(n log n) required by a client-side sort.

Performance

Indexed search uses sorted data structures, not full scans.

In-memory mode

  • All data and indexes live in RAM — fastest possible reads and writes
  • Data is lost when the process exits
  • Best for temporary data, caches, or browser environments

File persistence

  • Data and indexes live on disk — nothing is held in memory
  • Every operation (get, find, add, update, remove) reads from and writes to disk
  • addMany batches into a single write — significantly faster than individual add calls
  • Indexes are persisted in the meta file and used directly on search — no rebuilding on startup
  • On startup, the stored schema is validated against the provided schema — if they differ, files are regenerated (stale data is wiped)
  • Best for small to medium datasets that need to survive restarts

License

MIT