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

cry-db

v2.4.22

Published

database access with mongo

Downloads

708

Readme

cry-db

A MongoDB wrapper library providing a high-level API for database operations with built-in support for revisions, soft-delete, archiving, blocking, auditing, and real-time publish events.

Architecture

Base          Connection management, ObjectId utilities, timestamps
  └─ Db       Database selection, collection/index management
      └─ Mongo  Full CRUD, soft-delete, archive, block, audit, publish events
Repo          Convenience wrapper around Mongo bound to a single collection
  • Mongo — operates on explicit (collection, query, ...) arguments
  • Repo<T> — wraps a Mongo instance with a fixed collection, so every call omits the collection parameter

Quick Start

import { Repo, Mongo } from 'cry-db';

// Repo — single-collection convenience class
const users = new Repo('users', 'mydb');

// Mongo — multi-collection class
const mongo = new Mongo('mydb');

Connection uses MONGO_URL env var (default mongodb://127.0.0.1:27017) and MONGO_DB env var for the database name.

Record Lifecycle

Documents in cry-db have three visibility states controlled by metadata flags. Each state is opt-in and independent — a record can be in any combination of states simultaneously.

| State | Flag | Opt-in | Filtered from queries | Reversible | |-----------|------------|--------------------------|------------------------------|------------| | Active | (none) | — | No | — | | Deleted | _deleted | useSoftDelete(true) | Yes, unless returnDeleted | No (use hardDelete to purge) | | Archived | _archived| useArchive(true) | Yes, unless returnArchived | Yes (unarchiveOne) | | Blocked | _blocked | — | No (always returned) | Yes (unblockOne) |

Query filtering rules:

  • When soft-delete is enabled, all query methods (find, findOne, findById, findByIds, count, findNewer, findNewerMany, findByIdsInManyCollections) automatically add { _deleted: { $exists: false } } to the query. Pass { returnDeleted: true } in QueryOpts to override.
  • When archive is enabled, the same query methods automatically add { _archived: { $exists: false } }. Pass { returnArchived: true } to override.
  • Both filters apply independently. A record that is both deleted and archived is hidden by both filters — you need both returnDeleted and returnArchived to see it.
  • findAll bypasses both filters entirely.

CRUD Operations

Insert

let user = await users.insert({ name: 'Alice', age: 30 });
// user._id, user._rev, user._ts are set automatically when revisions are enabled

let many = await users.insertMany([{ name: 'Bob' }, { name: 'Carol' }]);

Query

await users.find({ age: { $gte: 25 } });
await users.find({ age: { $gte: 25 } }, { sort: { name: 1 }, limit: 10, skip: 0 });
await users.findOne({ name: 'Alice' });
await users.findById(id);
await users.findByIds([id1, id2]);
await users.findAll(query);           // bypasses soft-delete/archive filters
await users.count({ age: { $gte: 25 } });

Update

await users.updateOne({ name: 'Alice' }, { $set: { age: 31 } });
await users.update({ active: true }, { $inc: { loginCount: 1 } });  // updateMany
await users.save({ $set: { age: 31 } }, userId);
await users.upsert({ email: '[email protected]' }, { $set: { name: 'Alice' } });

Delete

await users.deleteOne({ name: 'Alice' });  // soft-delete when enabled, else hard delete
await users.delete({ inactive: true });     // bulk soft-delete / hard delete
await users.hardDeleteOne(id);              // always physically removes
await users.hardDelete({});                 // physically remove all matching

Batch Sync

await users.upsertBatch({
    upsert: [{ _id: id1, name: 'Alice' }, { _id: id2, name: 'Bob' }],
    delete: [id3],
});

Features

Revisions

When enabled, every write increments _rev and updates the _ts (Timestamp) field.

users.useRevisions(true);

Soft Delete

When enabled, deleteOne / delete set _deleted: Date instead of physically removing the document. Query operations automatically exclude deleted records. Deleted records cannot be "undeleted" — use hardDelete to purge them permanently.

users.useSoftDelete(true);

await users.deleteOne({ name: 'Alice' });      // sets _deleted: Date, increments _rev
await users.delete({ inactive: true });        // bulk soft-delete
await users.find({});                           // excludes deleted
await users.find({}, { returnDeleted: true });  // includes deleted
await users.hardDelete({ inactive: true });     // physically removes (bypasses soft-delete)

Archive

When enabled, query operations automatically exclude records with _archived set. Unlike soft-delete, archiving is fully reversible.

users.useArchive(true);

// Archive by query
await users.archiveOne({ name: 'Alice' });        // sets _archived: Date, increments _rev
await users.unarchiveOne({ name: 'Alice' });       // removes _archived, increments _rev

// Archive by id
await users.archiveOneById(id);                    // archive single record by _id
await users.unarchiveOneById(id);                  // unarchive single record by _id

// Archive multiple by ids
await users.archiveManyByIds([id1, id2, id3]);     // returns array of archived docs (skips already archived)
await users.unarchiveManyByIds([id1, id2, id3]);   // returns array of unarchived docs (skips non-archived)

// Query behavior
await users.find({});                              // excludes archived
await users.find({}, { returnArchived: true });    // includes archived

Archive and soft-delete are independent — a record can be both archived and deleted. The returnArchived and returnDeleted options control each filter separately.

Block

Block/unblock sets or removes the _blocked field. Blocked records are not filtered from queries (unlike deleted/archived).

await users.blockOne({ name: 'Alice' });
await users.unblockOne({ name: 'Alice' });

Sequences

Auto-incrementing field values managed atomically via a dedicated _sequences collection. Use the special string directives 'SEQ_NEXT' and 'SEQ_LAST' as field values during insert.

| Directive | Behavior | |-----------|----------| | 'SEQ_NEXT' | Increment the sequence counter and use the new value | | 'SEQ_LAST' | Use the current sequence value without incrementing |

// Single insert — orderNo gets the next sequence value
await users.insert({ name: 'Alice', orderNo: 'SEQ_NEXT' });
// => { name: 'Alice', orderNo: 1, _id: ... }

await users.insert({ name: 'Bob', orderNo: 'SEQ_NEXT' });
// => { name: 'Bob', orderNo: 2, _id: ... }

// SEQ_LAST returns the current value (no increment)
await users.insert({ name: 'Carol', orderNo: 'SEQ_LAST' });
// => { name: 'Carol', orderNo: 2, _id: ... }

Sequences are per-collection per-field. On first use, the sequence auto-seeds from the maximum existing value in the collection. If the collection is emptied, the sequence resets to 0.

Batch inserts are optimized — a range of sequence numbers is reserved atomically in a single operation, then distributed across the batch:

await users.insertMany([
    { name: 'A', orderNo: 'SEQ_NEXT' },  // orderNo: 3
    { name: 'B', orderNo: 'SEQ_LAST' },  // orderNo: 3 (current value after A)
    { name: 'C', orderNo: 'SEQ_NEXT' },  // orderNo: 4
]);

Multiple fields can use independent sequences in the same document:

await users.insert({ invoiceNo: 'SEQ_NEXT', lineNo: 'SEQ_NEXT' });

To reset a collection's sync sequence:

await users.resetCollectionSync();

Auditing

Records changes to a separate audit log collection.

users.useAuditing(true);
users.auditToCollection('dblog');
users.auditCollection();  // enable auditing for this repo's collection

await users.dbLogGet(entityId);    // retrieve audit log for an entity
await users.dbLogPurge(entityId);  // purge audit log

Publish Events

Real-time events emitted on insert/update/delete for data synchronization.

users.emitPublishEvents(true);     // full document payloads
users.emitPublishRevEvents(true);  // lightweight revision-only payloads

users.on('publish', (channel, data) => { /* ... */ });
users.on('publishRev', (channel, data) => { /* ... */ });

Transactions

await users.startTransaction();
// ... operations ...
await users.commitTransaction();
// or
await users.abortTransaction();

// or callback style:
await users.withTransaction(async (client, session) => {
    // ... operations ...
});

Entity Metadata

Every document can have these system fields (managed automatically when the respective feature is enabled):

| Field | Type | Description | |------------|-----------|-----------------------------------------------| | _id | ObjectId | Document identifier | | _rev | number | Revision counter (incremented on each write) | | _ts | Timestamp | Server timestamp (set on each write) | | _deleted | Date | Soft-delete timestamp | | _archived| Date | Archive timestamp | | _blocked | Date | Block timestamp |

QueryOpts

Options passed to query methods:

| Option | Type | Description | |-------------------|----------------------|----------------------------------------------| | project | Projection | Fields to include/exclude | | sort | Record<string, 1|-1> | Sort specification | | limit | number | Max documents to return | | skip | number | Documents to skip | | collation | CollationOptions | MongoDB collation options | | readPreference | ReadPreference | Read preference | | returnDeleted | boolean | Include soft-deleted records | | returnArchived | boolean | Include archived records |

Additional Methods

users.distinct<string>('status');                      // distinct field values (sorted)
users.findNewer(timestamp, query, opts);               // find records newer than timestamp
mongo.findNewerMany([{ collection, timestamp }]);      // batch findNewer across collections
mongo.findByIdsInManyCollections([{ collection, ids }]); // batch findByIds across collections
users.isUnique('email', '[email protected]', excludeId);          // uniqueness check
users.aggregate(pipeline);                             // aggregation pipeline
users.createIndex('name_1', { name: 1 });              // create index
users.indexes();                                       // list indexes

Environment Variables

| Variable | Default | Description | |----------|---------|-------------| | MONGO_URL | mongodb://127.0.0.1:27017 | MongoDB connection URL | | MONGO_DB | — | Default database name | | AUDIT_COLLECTIONS | — | Comma-separated list of collections to audit |

Testing

npm test

Requires a running MongoDB instance on localhost.