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

blitzdb

v3.0.0

Published

A NoSQL, MongoDB-styled in-memory database with persistent JSON storage, full query engine, aggregation pipeline, permissions model, and browser (localStorage) support.

Readme

BlitzDB

A NoSQL, MongoDB-styled database with a full query engine, aggregation pipeline, permissions model, and persistent JSON storage — for both Node.js and the browser (localStorage).

  • Zero external dependencies in the browser build
  • MongoDB-compatible query operators and update operators
  • Aggregation pipeline ($match, $group, $sort, $lookup, $unwind, …)
  • Permission model — Firestore-style rules, disabled by default
  • Multi-database support via BlitzDB manager or Database class
  • CJS + ESM — works with require() and import
  • Browser build — persists to localStorage; UMD + ESM variants

Table of Contents


Installation

npm install blitzdb

Quick Start

// CJS
const db = require("blitzdb");

// Insert documents
db.users.insertMany([
  { name: "Alice", age: 30, role: "admin" },
  { name: "Bob",   age: 25, role: "user"  },
]);

// Query with operators
const admins = db.users.find({ age: { $gte: 28 } }).toArray();

// Update
db.users.updateOne({ name: "Bob" }, { $inc: { age: 1 } });

// Delete
db.users.deleteOne({ name: "Bob" });

Imports & Module Formats

CommonJS (Node.js)

const db = require("blitzdb");                          // default db instance
const { Database, BlitzDB } = require("blitzdb");      // classes
const { PermissionModel, Cursor } = require("blitzdb");

ES Modules (Node.js)

import db from "blitzdb";
import { Database, BlitzDB, PermissionModel } from "blitzdb";

Browser — UMD (script tag)

<script src="node_modules/blitzdb/browser.js"></script>
<script>
  const db    = BlitzDB.default;
  const { Database, BlitzDB: Manager } = BlitzDB;
</script>

Browser — ESM

import db, { Database, BlitzDB } from "blitzdb/browser";
// or
import db from "./node_modules/blitzdb/browser.mjs";

Sub-path imports (explicit environment)

import db from "blitzdb/browser"; // always browser build
import db from "blitzdb/node";    // always Node.js build

Database Management

Default Database

The default export is a ready-to-use Database instance stored in _blitzdb/. Collections are auto-created on property access.

const db = require("blitzdb");
db.users.insert({ name: "Alice" });   // auto-creates "users" collection
db.posts.insert({ title: "Hello" });  // auto-creates "posts" collection

Multiple Databases

Use the Database class directly or the BlitzDB manager.

Database class

const { Database } = require("blitzdb");

const appDb  = new Database("myapp");         // stored in ./_myapp/
const testDb = new Database("test", {
  baseDir: "/tmp/databases"                   // override storage directory
});

appDb.users.insert({ name: "Alice" });
testDb.logs.insert({ level: "info", msg: "started" });

BlitzDB manager

const { BlitzDB } = require("blitzdb");

const manager = new BlitzDB({ baseDir: "./data" });

const appDb  = manager.db("myapp");
const logsDb = manager.db("logs");

console.log(manager.listDatabases());     // ["myapp", "logs"]
console.log(manager.countDatabases());    // 2
console.log(manager.isDatabaseExists("myapp")); // true

manager.dropDatabase("logs");

Database API

| Method | Description | |---|---| | db.collection(name) | Get or create a collection (safe alternative to property access) | | db.dropCollection(name) | Drop a collection and delete its file | | db.renameCollection(old, new) | Rename a collection | | db.listCollections() | Returns string[] of collection names | | db.isCollectionExists(name) | Returns boolean | | db.countCollections() | Returns total collection count | | db.stats() | { id, dir, collections, totalDocs, totalSize } | | db.dropDatabase() | Drop all collections and delete the database directory | | db.permissions | Access the PermissionModel for this database |


Collections

Insert

// Insert one document — returns the stored document with `_id`
db.users.insertOne({ name: "Alice", age: 30 });

// Alias
db.users.insert({ name: "Bob" });

// Insert multiple — returns array of stored documents
db.users.insertMany([
  { name: "Carol", age: 25 },
  { name: "Dave",  age: 28 },
]);

Documents receive a UUID _id unless you supply one:

db.users.insert({ _id: "custom-id-001", name: "Eve" });

Find / Query

// Find all
db.users.find().toArray();

// Find with query
db.users.find({ role: "admin" }).toArray();

// Projection (inclusion)
db.users.find({}, { name: 1, age: 1 }).toArray();

// Projection (exclusion)
db.users.find({}, { password: 0 }).toArray();

// Find one
db.users.findOne({ name: "Alice" });

// Find by _id
db.users.findById("uuid-here");

Cursor Methods

find() returns a Cursor. Chain any of the following before calling .toArray():

db.users
  .find({ role: "admin" })
  .sort({ age: -1 })          // sort descending by age
  .skip(10)                   // skip 10 documents
  .limit(5)                   // return at most 5
  .project({ name: 1 })       // apply projection
  .toArray();                 // materialise

// Other cursor methods
cursor.count();               // number of matching docs after skip/limit
cursor.first();               // first document or null
cursor.forEach(doc => {});    // iterate
cursor.map(doc => doc.name);  // transform
for (const doc of cursor) {}  // iterable

Count & Distinct

db.users.count();                       // all documents
db.users.count({ role: "admin" });      // with query
db.users.countDocuments({ age: { $gt: 18 } });
db.users.estimatedDocumentCount();      // fast, no query

db.users.distinct("role");              // ["admin", "user"]
db.users.distinct("role", { age: { $gt: 20 } });

Pagination

const result = db.users.paginate(
  { role: "admin" },          // query
  {
    page:     1,              // 1-indexed
    pageSize: 10,
    sort:     { name: 1 },
    projection: { password: 0 },
  }
);

// result shape:
// { data: [...], total: 42, page: 1, pageSize: 10, totalPages: 5 }

Update

updateOne(query, update, options?)

Updates the first matching document. Returns { updated, upserted }.

db.users.updateOne(
  { name: "Alice" },
  { $set: { age: 31 }, $push: { tags: "admin" } }
);

// Upsert — insert if not found
db.users.updateOne(
  { name: "Zed" },
  { $set: { age: 20 } },
  { upsert: true }
);

updateMany(query, update)

Updates all matching documents. Returns array of updated documents.

db.users.updateMany({ role: "user" }, { $inc: { loginCount: 1 } });

replaceOne(query, replacement, options?)

Replaces the entire document (keeps _id). Returns { updated, upserted }.

db.users.replaceOne({ name: "Bob" }, { name: "Bob", age: 26, role: "moderator" });

Delete

// Delete first matching document — returns the deleted doc
db.users.deleteOne({ name: "Bob" });

// Delete all matching — returns array of deleted docs
db.users.deleteMany({ role: "guest" });

// Delete all (empty query)
db.users.deleteMany({});

Find-and-Modify

// Returns doc AFTER update by default
db.users.findOneAndUpdate({ name: "Alice" }, { $inc: { age: 1 } });

// Return doc BEFORE update
db.users.findOneAndUpdate(
  { name: "Alice" },
  { $set: { online: true } },
  { returnDocument: "before" }
);

db.users.findOneAndReplace({ name: "Alice" }, { name: "Alice", age: 99 });
db.users.findOneAndDelete({ name: "Alice" });  // returns deleted doc

Bulk Write

db.users.bulkWrite([
  { insertOne:  { document: { name: "Eve", age: 22 } } },
  { updateOne:  { filter: { name: "Alice" }, update: { $set: { age: 31 } } } },
  { updateMany: { filter: { role: "user" }, update: { $inc: { loginCount: 1 } } } },
  { replaceOne: { filter: { name: "Bob" }, replacement: { name: "Bob", age: 26 } } },
  { deleteOne:  { filter: { name: "Eve" } } },
  { deleteMany: { filter: { role: "guest" } } },
]);
// Returns: [{ op, result }, ...]

Collection Utilities

// Check if document exists by _id
db.users.isDocumentExists("some-uuid");  // boolean

// Stats
db.users.stats();
// { name, count, size (bytes), indexes: [...] }

// Drop collection (delete all data + file)
db.users.drop();

// Rename collection
db.users.rename("members");

Query Operators

Comparison

| Operator | Description | Example | |---|---|---| | $eq | Equal | { age: { $eq: 30 } } | | $ne | Not equal | { role: { $ne: "guest" } } | | $gt | Greater than | { age: { $gt: 18 } } | | $gte | Greater than or equal | { age: { $gte: 18 } } | | $lt | Less than | { score: { $lt: 100 } } | | $lte | Less than or equal | { score: { $lte: 100 } } | | $in | In array | { role: { $in: ["admin","mod"] } } | | $nin | Not in array | { role: { $nin: ["guest"] } } |

Logical

// $and — all conditions must match
db.users.find({ $and: [{ age: { $gte: 18 } }, { role: "admin" }] });

// $or — any condition must match
db.users.find({ $or: [{ role: "admin" }, { role: "mod" }] });

// $nor — no condition must match
db.users.find({ $nor: [{ role: "guest" }] });

// $not — negates a condition
db.users.find({ age: { $not: { $lt: 18 } } });

Array

| Operator | Description | |---|---| | $in | Field value is in given array | | $all | Array field contains all given values | | $size | Array field has exactly N elements | | $elemMatch | At least one array element matches a sub-query |

db.posts.find({ tags: { $all: ["js", "node"] } });
db.posts.find({ comments: { $size: 3 } });
db.posts.find({ scores: { $elemMatch: { $gte: 90 } } });

Element

db.users.find({ email: { $exists: true } });
db.users.find({ age:   { $exists: false } });
db.users.find({ name:  { $type: "string" } });

Evaluation

// $regex
db.users.find({ name: { $regex: "^Al", $options: "i" } });

// $mod — modulo
db.items.find({ qty: { $mod: [4, 0] } });  // qty divisible by 4

Dot-notation (nested fields)

All operators work with dot-notation paths:

db.users.find({ "address.city": "Mumbai" });
db.users.find({ "profile.age": { $gte: 18 } });

Update Operators

Field

| Operator | Description | Example | |---|---|---| | $set | Set field value | { $set: { age: 31 } } | | $unset | Remove a field | { $unset: { tempToken: "" } } | | $inc | Increment by N | { $inc: { views: 1 } } | | $mul | Multiply by N | { $mul: { price: 1.1 } } | | $min | Set if new value is smaller | { $min: { score: 50 } } | | $max | Set if new value is larger | { $max: { score: 95 } } | | $rename | Rename a field | { $rename: { oldName: "newName" } } |

Array

| Operator | Description | |---|---| | $push | Append value to array | | $push + $each | Append multiple values | | $push + $sort | Push and sort | | $push + $slice | Push and trim to N elements | | $push + $position | Insert at position | | $pull | Remove elements matching value or query | | $pullAll | Remove all occurrences of listed values | | $addToSet | Push only if not already present | | $addToSet + $each | Add multiple unique values | | $pop | Remove first (-1) or last (1) element |

// $push with modifiers
db.leaderboard.updateOne({ name: "Alice" }, {
  $push: {
    scores: {
      $each:     [95, 87, 100],
      $sort:     -1,           // sort descending
      $slice:    5,            // keep top 5
      $position: 0,            // insert at start (before sort)
    }
  }
});

// $pull with sub-query
db.users.updateOne({ name: "Alice" }, {
  $pull: { tags: { $in: ["temp", "draft"] } }
});

// $addToSet
db.users.updateOne({ name: "Bob" }, {
  $addToSet: { roles: { $each: ["editor", "viewer"] } }
});

Bitwise

db.flags.updateOne({ name: "mask" }, {
  $bit: { flags: { and: 0b1100, or: 0b0001, xor: 0b0010 } }
});

Aggregation Pipeline

collection.aggregate(pipeline) accepts an array of stage objects and returns a plain array of documents.

const results = db.orders.aggregate([
  { $match:   { status: "complete" } },
  { $group:   { _id: "$userId", total: { $sum: "$amount" }, count: { $sum: 1 } } },
  { $sort:    { total: -1 } },
  { $limit:   10 },
  { $project: { _id: 1, total: 1 } },
]);

Supported Stages

| Stage | Description | |---|---| | $match | Filter documents by query | | $project | Include/exclude/rename fields | | $sort | Sort documents | | $limit | Limit document count | | $skip | Skip N documents | | $count | Count documents into a named field | | $group | Group and accumulate | | $unwind | Deconstruct an array field | | $lookup | Left join another collection | | $addFields | Add computed fields | | $replaceRoot | Replace root document | | $sample | Random N documents |

$group Accumulators

| Accumulator | Description | |---|---| | $sum | Sum of values (or 1 for count) | | $avg | Average | | $min | Minimum value | | $max | Maximum value | | $push | Collect values into array | | $addToSet | Collect unique values | | $first | First value in group | | $last | Last value in group |

db.sales.aggregate([
  { $group: {
    _id:      "$category",
    revenue:  { $sum: "$amount" },
    avgPrice: { $avg: "$price" },
    minPrice: { $min: "$price" },
    items:    { $push: "$name" },
    uniqueTags: { $addToSet: "$tag" },
  }},
  { $sort: { revenue: -1 } },
]);

$lookup (join)

db.orders.aggregate([
  { $lookup: {
    from:         "users",      // collection name
    localField:   "userId",
    foreignField: "_id",
    as:           "user",
  }},
]);

$unwind

db.posts.aggregate([
  { $unwind: "$tags" },          // simple
  { $unwind: {
    path: "$comments",
    preserveNullAndEmptyArrays: true,
  }},
]);

Indexes

// Create index
db.users.createIndex("email", { unique: true });
db.users.createIndex("role");
db.users.createIndex("optionalField", { sparse: true }); // skip docs missing the field

// List indexes
db.users.listIndexes();
// [{ field: "email", unique: true, sparse: false }, ...]

// Drop index
db.users.dropIndex("role");

A unique index throws Error: [BlitzDB] Unique index violation … on insert/update that would create a duplicate.


Validators

Add validation functions to a collection. Return true (or undefined) to pass, or a string to fail with that message.

db.users.addValidator(doc => {
  if (!doc.name)      return "name is required";
  if (doc.age < 0)    return "age must be non-negative";
  if (!doc.email?.includes("@")) return "invalid email";
  return true; // or just return nothing
});

db.users.insert({ name: "Alice", age: -1, email: "bad" });
// throws: [BlitzDB] Validation failed: age must be non-negative

Multiple validators can be chained:

db.products
  .addValidator(doc => doc.price >= 0   || "price must be ≥ 0")
  .addValidator(doc => doc.name?.length || "name must not be empty");

Permission Model

By default the permission model is disabled (all operations allowed). Enable it and add rules to restrict access, similar to Firestore Security Rules.

const db = new Database("myapp");
const { permissions } = db;

permissions.enable();

// Allow/deny by collection and action
permissions.allow("*",       "*");            // allow everything (default-open pattern)
permissions.deny("admin",    "*");            // deny all on "admin"
permissions.allow("admin",   "read");         // but allow reads on "admin"

// Conditional rules — doc & context are passed to the condition function
permissions.setContext({ userId: "user-123", role: "user" });

permissions.deny("orders", "delete", (doc, ctx) => {
  return ctx.role !== "admin";  // deny delete unless user is admin
});

permissions.allow("posts", "write", (doc, ctx) => {
  return doc?.authorId === ctx.userId;  // only allow writing your own posts
});

Rule evaluation

Rules are evaluated in order. The last matching rule wins. If no rule matches, the operation is allowed (open by default).

Actions

| Action | Covers | |---|---| | "read" | find, findOne, findById, count, distinct, paginate, aggregate | | "insert" / "write" | insert, insertOne, insertMany | | "update" / "write" | updateOne, updateMany, replaceOne, findOneAndUpdate, findOneAndReplace | | "delete" / "write" | deleteOne, deleteMany, findOneAndDelete, drop | | "*" | All actions |

Permission API

permissions.enable()                          // enable the model
permissions.disable()                         // disable (allow all)
permissions.setContext(obj)                   // set ctx passed to condition functions
permissions.allow(collection, action, cond?)  // add an allow rule
permissions.deny(collection, action, cond?)   // add a deny rule
permissions.addRule({ collection, action, condition, allow }) // low-level
permissions.check(collection, action, doc?)   // returns boolean

Browser Usage

The browser build uses localStorage instead of the filesystem. The API is identical to the Node.js version.

Storage keys follow the pattern blitzdb:<dbId>:<collectionName>.

<!-- UMD -->
<script src="browser.js"></script>
<script>
  const db = BlitzDB.default;

  db.notes.insert({ title: "Hello", body: "World" });

  const notes = db.notes.find().toArray();
  console.log(notes);

  // Multiple databases
  const { Database } = BlitzDB;
  const userDb = new Database("user-data");
  userDb.preferences.insert({ theme: "dark" });
</script>
// ESM
import db, { Database, BlitzDB } from "./browser.mjs";

db.todos.insertMany([
  { text: "Buy milk",   done: false },
  { text: "Write code", done: true  },
]);

const pending = db.todos.find({ done: false }).toArray();

Custom storage adapter

Provide any object with getItem / setItem / removeItem / key / length before loading BlitzDB:

globalThis._blitzStorage = myCustomStorageAdapter;

API Reference

Database / BlitzDB manager

| Method | Returns | Description | |---|---|---| | new Database(id, options?) | Database | Create/open a database. Folder: _<id> | | new BlitzDB(options?) | BlitzDB | Create a manager | | manager.db(id) | Database | Get/create database | | manager.listDatabases() | string[] | All database IDs on disk | | manager.isDatabaseExists(id) | boolean | — | | manager.countDatabases() | number | — | | manager.dropDatabase(id) | void | Delete database and directory | | manager.stats() | object[] | Stats per database |

Collection

| Method | Returns | Description | |---|---|---| | insert(doc) | doc | Insert one document | | insertOne(doc) | doc | Alias for insert | | insertMany(docs) | doc[] | Insert multiple documents | | find(query?, proj?) | Cursor | Find matching documents | | findOne(query?, proj?) | doc \| null | First matching document | | findById(id) | doc \| null | Find by _id | | count(query?) | number | Count matching documents | | countDocuments(query?) | number | Alias for count | | estimatedDocumentCount() | number | Fast total count | | distinct(field, query?) | any[] | Unique values for field | | paginate(query?, opts?) | PaginateResult | Paginated results | | aggregate(pipeline) | doc[] | Aggregation pipeline | | updateOne(q, upd, opts?) | { updated, upserted } | Update first match | | updateMany(q, upd) | doc[] | Update all matches | | replaceOne(q, rep, opts?) | { updated, upserted } | Replace first match | | findOneAndUpdate(q, upd, opts?) | doc \| null | Find, update, return doc | | findOneAndReplace(q, rep, opts?) | doc \| null | Find, replace, return doc | | findOneAndDelete(q) | doc \| null | Find, delete, return doc | | deleteOne(query) | doc \| null | Delete first match | | deleteMany(query?) | doc[] | Delete all matches | | bulkWrite(ops) | result[] | Execute multiple operations | | createIndex(field, opts?) | this | Create an index | | dropIndex(field) | this | Drop an index | | listIndexes() | IndexInfo[] | List indexes | | addValidator(fn) | this | Add a validation function | | isDocumentExists(id) | boolean | Check if document exists | | stats() | CollectionStats | Collection statistics | | drop() | void | Drop collection + file | | rename(newName) | void | Rename collection |

Cursor

| Method | Description | |---|---| | .sort(spec) | { field: 1 \| -1 } | | .skip(n) | Skip N documents | | .limit(n) | Limit to N documents | | .project(spec) | Apply projection | | .toArray() | Materialise as array | | .count() | Count after skip/limit | | .first() | First doc or null | | .forEach(fn) | Iterate | | .map(fn) | Transform | | for...of | Iterable |


License

ISC © susant swain