Skip to content

Repository files navigation

pocket-db

pocket-shell

Try pocket-db in one command — no install, no server, no code.

pocket-shell npm version pocket-db npm version MIT License Node ≥ 18

npx @axfab/pocket-shell ./data.pdb
pocket-shell — connected to ./data.pdb
"db" is ready. Try: db.getCollections(), db.<collection>.find(), db.stats(), .help, .exit

pocket-shell> db.users.insertOne({ name: "Ada", role: "admin" })
{ acknowledged: true, insertedId: '...' }

pocket-shell> db.users.find({ role: "admin" })
┌─────────┬──────────────────────────┬────────┬─────────┐
│ (index) │ _id                      │ name   │ role    │
├─────────┼──────────────────────────┼────────┼─────────┤
│ 0       │ '...'                    │ 'Ada'  │ 'admin' │
└─────────┴──────────────────────────┴────────┴─────────┘

pocket-shell> .exit

That's a real pocket-db database — a single-file, zero-native-dependency, MongoDB-style embedded store for Node.js and Electron — created, queried, and closed with nothing installed beforehand. No daemon to start (unlike mongosh, which needs a running mongod), no config, no schema. npx, a path, and you're querying data.


Why pocket-db

pocket-db is built for apps that need real persistence without a server: Electron & desktop apps, CLI tools, local servers, plugins, structured caches, offline-first prototypes.

  • 🪶 Zero native dependencies — pure TypeScript, no node-gyp, no rebuild-per-Electron-version dance
  • 📄 Single file — back up your whole database by copying one .pdb file
  • 🍃 MongoDB-style APIfind / insert / update with the query and update operators you already know
  • Append-only writes — every mutation is a fast sequential append, crash-safe, no full-file reserialisation
  • 🛡️ Safe reads — every query returns an independent copy, so mutating a result never corrupts stored data
  • 🚀 Fast, durable writes — 40–700× faster than other file-backed stores in its own benchmarks, within ~12% of in-memory SQLite (which isn't even durable)

Full details, the benchmark methodology, and the complete API live in pocket-db's own README — this shell is just the fastest way to kick the tires before you commit to reading it.

Using pocket-db in your own project

Once you're sold, it's a two-line integration — no shell involved:

npm install @axfab/pocket-db
import { pocketDb } from "@axfab/pocket-db";

const db = pocketDb("./data.pdb");
const users = db.collection("users");

users.insertOne({ name: "Ada", role: "admin" });
const admins = users.find({ role: "admin" }).toArray();

db.close();

Anything you just tried at the pocket-shell> prompt is the exact same API, called from your own code.


Why a separate package

pocket-db stays strictly zero-dependency and minimal. pocket-shell depends on it as a normal dependency (not a peer) specifically so npx @axfab/pocket-shell works without a prior npm install — that's the friction it exists to remove. It also means the shell can iterate and release independently of the storage engine.

Install

Nothing to install for occasional use — npx @axfab/pocket-shell ./data.pdb fetches and runs it. For repeated use:

npm install -g @axfab/pocket-shell

Installing (globally, or as a project dependency) also links two extra commands, pocket-shell-import and pocket-shell-export — identical to pocket-shell import/pocket-shell export (see below), just without the leading subcommand, for scripts that want a dedicated executable:

pocket-shell-import ./data.pdb ./seed.json
pocket-shell-export ./data.pdb --collection users > users.json

Usage

pocket-shell [path] [options]
pocket-shell import <path> <file.json> [--collection <name>]
pocket-shell export <path> [--collection <name>]

Arguments:
  path                  Path to the .pdb file (default: pocket.pdb). Created automatically if it doesn't exist yet.

Options:
  --eval <code>         Run JS non-interactively against the opened db, print the result, then exit.
  -h, --help             Show help and exit.
  -v, --version          Show the installed version and exit.

db in scope

The REPL is a real Node repl with a db object injected into its context — there is no custom query language to learn. db.collection("users") is pocket-db's actual API; db.users is mongosh-style shorthand for the same thing, implemented with a plain Proxy (no dependency needed):

db.users.insertOne({ name: "Bob" });
db.users.find({ role: "admin" }).sort({ name: 1 }).limit(10).toArray();
db.users.updateOne({ name: "Bob" }, { $set: { role: "admin" } });
db.users.deleteOne({ name: "Bob" });
db.getCollections();

.sort() / .limit() / .skip() / .toArray() are inherited as-is from pocket-db's cursor — nothing here reimplements them. A bare cursor (e.g. db.users.find(...) with no .toArray()) is materialized automatically for display, mongosh-style, so you rarely need to call .toArray() yourself at the prompt.

Results print through a custom writer: arrays of documents render as a table, stats() results print with human-readable byte sizes next to the raw numbers (see below), everything else through util.inspect with colors — no chalk, no extra dependency. Long strings are truncated so one bloated field can't flood the terminal.

Command history persists across sessions in ~/.pocket_shell_history (Node's built-in repl history, nothing custom).

Index management, compact(), drop(), stats()

pocket-db's full Collection/Database API is available through db and db.<collection> — pocket-shell doesn't gate any of it — including the management surface beyond plain CRUD:

db.users.createIndex("email", { type: "string", unique: true });
db.users.getIndexes();
db.users.dropIndex("email");

db.users.drop();       // drops the collection; re-inserting recreates it, same as pocket-db itself
db.compact();          // rewrites the file, discarding dead (deleted/updated) records

db.stats();            // database-wide: file size, live/dead byte counts, document & collection counts
db.users.stats();      // same, scoped to one collection, plus its index count

db.stats() and db.<collection>.stats() print mongosh-style, with a human-readable size next to every byte count:

pocket-shell> db.stats()
{
  path: '/path/to/data.pdb',
  sizeOnDisk: 292 (292 B),
  collectionCount: 1,
  documentCount: 2,
  operationCount: 5,
  tombstoneCount: 2,
  liveBytes: 232 (232 B),
  deadBytes: 48 (48 B)
}

One thing db.stats() does not report: the file's serialization format (JSON/BSON/AMF3). pocket-db's public API doesn't expose it outside of opening the file, so pocket-shell doesn't fabricate it — reading the raw file header ourselves would duplicate storage-layer logic that belongs to pocket-db, and would be one file-format change away from silently going stale.

Import / export

# One collection, from/to a flat array of documents:
npx @axfab/pocket-shell import ./data.pdb ./users.json --collection users
npx @axfab/pocket-shell export ./data.pdb --collection users > users.json

# The whole database at once, keyed by collection name — the two commands round-trip:
npx @axfab/pocket-shell export ./data.pdb > full-backup.json
npx @axfab/pocket-shell import ./restored.pdb ./full-backup.json

export prints JSON to stdout; redirect it with your shell. It never creates <path> — exporting is read-only by nature, so a typo'd path fails loudly instead of silently producing an empty export.

import loads <file.json> into <path>, creating <path> if it doesn't exist yet (same as the REPL). Two shapes are accepted, matching what export produces on each side:

  • --collection <name>: <file.json> is a JSON array of documents (a bare object is also accepted, as a single document) — all loaded into that one collection.
  • no --collection: <file.json> is a JSON object mapping collection name to an array of documents, e.g. { "users": [...], "posts": [...] } — each key is imported into the matching collection.

Each collection is loaded with a single insertMany, which pocket-db validates as a whole batch (document ids, unique indexes) before writing anything — so a failing collection leaves nothing partially inserted for itself. Collections already imported earlier in the same run stay committed, though, since each is its own transaction; a multi-collection import isn't atomic across the whole file.

Run pocket-shell import --help / pocket-shell export --help for the full rundown. Both are also available as their own commands once installed — see Install.

Non-interactive mode

npx @axfab/pocket-shell ./data.pdb --eval "db.users.find({ role: 'admin' })"

Runs the given code, prints its result the same way the REPL would, and exits — closing the database cleanly either way. Useful in CI, and for generating reproducible demo captures without replaying an interactive session by hand for every recording.

Missing file

pocket-db creates a .pdb file that doesn't exist yet rather than erroring. pocket-shell keeps that behavior (so it stays a true zero-friction npx ./data.pdb tool, including non-interactively) but prints a line telling you a new file was created, so it's never a silent surprise.

Clean shutdown

.exit, Ctrl+D, Ctrl+C, and SIGTERM all close the database before the process exits, so an interrupted session doesn't leave a stale lock file behind.

A real JS REPL — accepted risk

There is deliberately no custom command language here: pocket-shell hands you a genuine repl with db in scope, exactly like the standard Node REPL (or mongosh) does. That means it evaluates arbitrary JavaScript — the same trust model as running node interactively. This is a local, single-user, single-process tool with no server exposure, so that's an accepted trade-off, not an oversight: don't pipe untrusted input into it.

Scope

Covers all three SPECS.md milestones: open a file, the core CRUD + query surface (insertOne/insertMany/find/findOne/updateOne/updateMany/deleteOne/deleteMany/getCollections), pretty-printing, history and --eval ("Lot 1"); advanced management (createIndex/dropIndex/getIndexes, drop(), compact(), stats(), "Lot 2"); and import/export ("Lot 3").

Also out of scope by design: multiple database files in one session (pocket-db is single-file, so there's no use <db> the way mongosh has across a multi-database server), and any kind of admin/multi-user tooling — pocket-db is single-process, single-file, and this shell inherits that philosophy.

Development

npm install
npm run build   # tsc -> dist/
npm test        # node's built-in test runner via tsx

docs/pocket-db.svg and docs/pocket-db-dk.svg (the logo at the top of this README, light and dark variants) are copied from pocket-db's own docs/ — update them from there if that logo ever changes.

License

MIT


pocket-shell is a companion tool for pocket-db. If it got you curious, the real project — code, benchmarks, docs — is there. ⭐ it, file issues, or just npm install @axfab/pocket-db and start building.

About

Interactive mongosh-style REPL for pocket-db — zero server, zero friction (npx @axfab/pocket-shell ./data.pdb)

Resources

Contributing

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages