anchordb
v1.3.2
Published
MongoDB + Mongoose for mobile apps. Offline-first local database with optional sync.
Maintainers
Readme
anchordb
MongoDB + Mongoose, for mobile apps. A local, persistent document database with a Mongoose-shaped API that works completely offline — and optionally synchronises with a backend.
Overview · Report a bug or get support
npm install anchordbQuick start — no backend required
import { AnchorDB, Schema } from "anchordb";
const db = new AnchorDB({ name: "my-app" }); // no sync block → purely local
const userSchema = new Schema({
name: { type: String, required: true, trim: true },
email: { type: String, required: true, unique: true, lowercase: true },
age: { type: Number, min: 0, max: 150 },
role: { type: String, enum: ["admin", "user"], default: "user" },
}, { timestamps: true });
const User = db.model("User", userSchema);
await User.create({ name: "Nisala", age: 28, email: "[email protected]" });
const adults = await User.find({ age: { $gte: 18 } })
.sort("-createdAt")
.limit(10);That is Mongoose code. It runs on a phone, offline, with no server anywhere.
Two principles
1. Zero re-learning. If you know MongoDB and Mongoose on the backend, you already know this:
the same Schema, model(), query builder, populate, ObjectId, index options and error codes
(err.code === 11000, err.errors[path].kind). Anything Mongoose does that AnchorDB does not is
written down, never silently reinterpreted.
2. Sync is optional. An app with no backend is the default case, not a degraded one. With no
sync block there is no mutation queue, no network code, no sync metadata and no tombstones.
Adding sync later requires no changes to your CRUD code.
Decorators (optional)
Identical in shape to @nestjs/mongoose, so the same decorated class can be shared between an
Ionic/Angular app and a NestJS backend:
import { Schema, Prop, SchemaFactory } from "anchordb/decorators";
@Schema({ timestamps: true })
export class User {
@Prop({ required: true }) name!: string;
@Prop(String) nickname!: string;
@Prop([String]) tags!: string[];
}
export const UserSchema = SchemaFactory.createForClass(User);Requires experimentalDecorators + emitDecoratorMetadata for type inference — or pass the type
explicitly (@Prop(String)) and no metadata is needed at all.
Storage adapters
| Adapter | Import | Platform | Verified here |
| --- | --- | --- | --- |
| Memory | MemoryAdapter from anchordb | any | yes — conformance suite |
| Node SQLite | anchordb/storage/node-sqlite | Node 22.5+ (built in, no native deps) | yes — conformance suite |
| IndexedDB | anchordb/storage/indexeddb | browser, PWA, ionic serve | yes — conformance suite |
| Expo SQLite | anchordb/storage/expo-sqlite | React Native, iOS/Android | no — see below |
| Capacitor SQLite | anchordb/storage/capacitor-sqlite | Ionic native | no — see below |
All adapters sit behind one DatabaseAdapter interface, and the three verified ones are held to a
single 70-test conformance suite, so unique-index semantics, null handling and write atomicity
cannot drift between them.
Interchange with MongoDB
Export and import speak what mongoexport writes and mongoimport reads, in both directions and
in both formats:
// Extended JSON — lossless. NDJSON by default, which mongoimport reads with no extra flag.
const file = await Model.export({ mode: "canonical" });
// Only what a filter matches, run as a real query so it is cast and indexed like any other.
const page = await Model.export({ filter: { status: "active" } });
// CSV — for spreadsheets, and for mongoimport --type=csv --headerline.
const csv = await Model.export({ format: "csv" });
// Import auto-detects a JSON array, NDJSON or CSV.
await db.import(text, { mode: "upsert" });mongoimportCommand() prints the exact command that ingests the result.
CSV carries no types, so on import values are inferred conservatively and then cast through the
collection's schema — a declared Number comes back a number rather than the text the file held.
Two things CSV genuinely cannot round-trip, stated rather than hidden: null and a missing field
are both an empty cell (read back as missing, matching mongoimport --ignoreBlanks), and nested
fields travel as dotted paths.
Extended JSON has three modes, matching the ones MongoDB Compass offers: canonical (lossless),
relaxed (readable), and default (relaxed, except integers past 2^53 which are marked
$numberLong so they do not silently become doubles).
Status
1.0.0 — early, and 0.x means breaking changes are expected.
631 tests across 17 files. Implemented and covered: the local database, Mongoose-shaped schema
and validation, decorators, indexes with real E11000 enforcement, the lazy query builder,
populate, documents with .save(), aggregation, UTC timestamps and timezones, MongoDB Extended
JSON and CSV interop, the offline mutation queue, the sync engine with all five conflict strategies,
and the inspector protocol.
New in 0.2.0
updateManyanddeleteManyon the inspector protocol, behind a newdocuments:bulkcapability. Both route through the ordinary Model API, so a bulk write from Anchor Lens validates, stamps and queues exactly like an app write. An empty filter is refused unless explicitly acknowledged — an inspector that can empty a collection on a mis-tap is worse than one that asks twice. The capability is negotiated, so an older agent and a newer Lens still connect.- CSV export and import, and a
filteron export. - A MongoDB bridge protocol (
MongoBridgeClient), so Anchor Lens can pull collections from a real MongoDB or push a device into one. The driver runs inanchordb-relay— React Native has no TCP sockets — and both directions carry the same Extended JSON as the file export. defaultExtended JSON mode.- A no-op update is now matched but not modified, as in MongoDB:
modifiedCountexcludes documents the update did not change, and theirupdatedAtand version key are left alone. It previously counted every match, because the timestamp was stamped before the comparison. Beyond correctness this matters for bulk writes — setting a field over 10,000 documents no longer queues 10,000 mutations when only a few hundred needed changing.
What is NOT verified
Stated plainly rather than left to be discovered:
- Expo SQLite and Capacitor SQLite have never been executed. They are written against the documented APIs and typechecked, but this package is developed on a machine with no simulator or device. All of their query, index and unique-enforcement logic is inherited from a shared base that is covered by the conformance suite; the unverified surface is roughly forty lines of driver plumbing each. Test on a device before relying on them in production.
- The REST sync transport is typechecked but has no live-HTTP test. The protocol it speaks is covered end to end through an in-process transport.
MongooseStoreinanchordb-sync-serverhas not run against a live MongoDB here.
Not supported in v1
Documented rather than silently absent: discriminators, refPath, transactions/sessions,
server-side change streams, text/2dsphere/hashed indexes, collation, and the
$graphLookup/$geoNear/$search/$merge/$out/$unionWith aggregation stages.
The AnchorDB family
Six packages. Only anchordb is required — the rest exist so that an offline-only app never
has to download Express, and an Express server never has to download React.
| Package | What it is | Runs where |
| --- | --- | --- |
| anchordb (this package) | The database — schema, models, queries, aggregation, optional sync | phone · browser · Node |
| anchordb-react | React and React Native hooks | the device |
| anchordb-angular | Angular / Ionic module, DI and RxJS observables | the device |
| anchordb-sync-server | Server half of sync — Express, NestJS, Next.js | your backend |
| anchordb-relay | Dev relay for the Anchor Lens inspector | your laptop |
| anchordb-lens-link | Open a QA build's database in Anchor Lens on the same phone, from a file | the device, in QA builds |
Each one needs a different third-party framework as a peer dependency, and npm resolves those per package rather than per import — which is why they are not one package. Full reasoning and API reference: github.com/knnadeera/anchordb.
Bugs, support and feedback
Report a bug, ask for help or suggest a feature on the AnchorDB project page —
choose anchordb as the package, and the reply comes by email.
A report that can be acted on straight away has:
- the package and exact version — what
npm ls anchordbprints - where it runs — Expo, Ionic, a browser or Node — and which storage adapter
- the smallest snippet that reproduces it
- the full error, including
code,keyPatternandkeyValuefor an E11000 - what you expected to happen instead
Sync problems: say which conflict strategy you use, and whether the mutation shows as parked in Anchor Lens — a push rejected by a unique index is parked rather than retried forever.
License
MIT
