{"_id":"@axfab/pocket-db","_rev":"6-e78fa883feadd002a9fbd459e0160dc3","name":"@axfab/pocket-db","dist-tags":{"latest":"0.1.5"},"versions":{"0.1.0":{"name":"@axfab/pocket-db","version":"0.1.0","keywords":["database","embedded","nosql","typescript"],"license":"MIT","_id":"@axfab/pocket-db@0.1.0","maintainers":[{"name":"axfab","email":"fabien.bavent@gmail.com"}],"homepage":"https://pocket-db.axfab.net/","bugs":{"url":"https://github.com/AxFab/pocket-db/issues"},"dist":{"shasum":"7fbcf11d4553e18d3f78c23f03ef2dbffa7b5448","tarball":"https://registry.npmjs.org/@axfab/pocket-db/-/pocket-db-0.1.0.tgz","fileCount":121,"integrity":"sha512-DneDa1fUbOAbkKkvcmKh2CxM3ElVC8xpiGH+9Zw1QwIH/sFGok3AMi4R33tb32uGTCw8erD539V8ZjvOOHcanQ==","signatures":[{"sig":"MEYCIQDjo4vyx8+oD0G2KtEq0MiSwR1ZxtejChW1bv5X32jg5gIhAKu6j/2jIhfH15gNuTqPPFSh/TPaqwWcQEUyDqPfmdQ+","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":239419},"main":"./dist/src/index.js","type":"module","types":"./dist/src/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./dist/src/index.d.ts","import":"./dist/src/index.js"}},"gitHead":"9383b830902ee85409bd3b2baf9c6ee295f49ba3","scripts":{"test":"npm run build && node --test \"dist/tests/**/*.test.js\"","bench":"npm run build && node dist/benchmarks/bench.js","build":"tsc -p tsconfig.json","prepublishOnly":"npm run build"},"_npmUser":{"name":"axfab","email":"fabien.bavent@gmail.com"},"repository":{"url":"git+https://github.com/AxFab/pocket-db.git","type":"git"},"_npmVersion":"11.6.1","description":"An embedded document database for Node.js, designed around a small file-backed storage engine.","directories":{},"_nodeVersion":"24.11.0","_hasShrinkwrap":false,"devDependencies":{"lowdb":"^7.0.1","lokijs":"^1.5.12","typescript":"^5.4.0","@types/node":"^20.12.0","@types/lokijs":"^1.5.14","better-sqlite3":"^12.10.0","@types/better-sqlite3":"^7.6.13"},"_npmOperationalInternal":{"tmp":"tmp/pocket-db_0.1.0_1779494405377_0.7703633240823435","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@axfab/pocket-db","version":"0.1.1","keywords":["database","embedded","nosql","typescript","document-store","append-only"],"license":"MIT","_id":"@axfab/pocket-db@0.1.1","maintainers":[{"name":"axfab","email":"fabien.bavent@gmail.com"}],"homepage":"https://pocket-db.axfab.net/","bugs":{"url":"https://github.com/AxFab/pocket-db/issues"},"dist":{"shasum":"2fd6ecf9cf5fe1b53d2c9ed2a8f7d45402d70c2b","tarball":"https://registry.npmjs.org/@axfab/pocket-db/-/pocket-db-0.1.1.tgz","fileCount":121,"integrity":"sha512-BTKIrK/eVIgXNs27nRQO7E8+42+jbKZbUAwyoRPHjLT4qKhI3K0aYltbC6xdPpHHF3lCin7qm+1Ok3BcL6kyqA==","signatures":[{"sig":"MEYCIQCLrG+ty9OL26MXn0nX2XR/YsXLf+dpgaqB37JjKIXbqAIhAJ8unHTt/8Vbi3VjSa0wkj0T6boOFigR3eyhBbYUXFzH","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":243970},"main":"./dist/src/index.js","type":"module","types":"./dist/src/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./dist/src/index.d.ts","import":"./dist/src/index.js"}},"gitHead":"9da6179797fea7765733747e3b1b46c059c5379e","scripts":{"test":"npm run build && node --test \"dist/tests/**/*.test.js\"","bench":"npm run build && node dist/benchmarks/bench.js","build":"tsc -p tsconfig.json","prepublishOnly":"npm run build"},"_npmUser":{"name":"axfab","email":"fabien.bavent@gmail.com"},"repository":{"url":"git+https://github.com/AxFab/pocket-db.git","type":"git"},"_npmVersion":"11.15.0","description":"An embedded document database for Node.js, designed around a small file-backed storage engine.","directories":{},"_nodeVersion":"24.11.0","_hasShrinkwrap":false,"devDependencies":{"lowdb":"^7.0.1","lokijs":"^1.5.12","typescript":"^5.4.0","@types/node":"^20.12.0","@types/lokijs":"^1.5.14","better-sqlite3":"^12.10.0","@types/better-sqlite3":"^7.6.13"},"_npmOperationalInternal":{"tmp":"tmp/pocket-db_0.1.1_1780249337166_0.432186633918032","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@axfab/pocket-db","version":"0.1.2","keywords":["database","embedded","nosql","typescript","document-store","append-only"],"license":"MIT","_id":"@axfab/pocket-db@0.1.2","maintainers":[{"name":"axfab","email":"fabien.bavent@gmail.com"}],"homepage":"https://pocket-db.axfab.net/","bugs":{"url":"https://github.com/AxFab/pocket-db/issues"},"dist":{"shasum":"7c9f07c6d39c9032863f17ae26481d7a8f909229","tarball":"https://registry.npmjs.org/@axfab/pocket-db/-/pocket-db-0.1.2.tgz","fileCount":196,"integrity":"sha512-pBPBzSrSqK6vH8mZ4L5Ks1hehqkqJYMty4g3/OuNV7Xo7LcBIEepZYegiqI7huwiqzEgoCwR9QzpVgRaaSfIvw==","signatures":[{"sig":"MEUCIQDU9VO/K2IZh3OQPgRnIqyVxVmRnM22mNgF3W3dxJJ7UwIgAoLfmiHILKq3pSL8SrSgDhWKJQukE+qBB6y/AbTEVE0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":450571},"main":"./dist/cjs/src/index.js","type":"module","types":"./dist/src/index.d.ts","module":"./dist/src/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/src/index.d.ts","import":"./dist/src/index.js","require":"./dist/cjs/src/index.js"}},"gitHead":"1abf64be762f22bc82b8af4e93c75f4ef251f7a8","scripts":{"test":"npm run build && node --test \"dist/tests/**/*.test.js\"","bench":"npm run build && node dist/benchmarks/bench.js","build":"npm run build:esm && npm run build:cjs","build:cjs":"tsc -p tsconfig.cjs.json && echo '{\"type\":\"commonjs\"}' > dist/cjs/src/package.json","build:esm":"tsc -p tsconfig.json","prepublishOnly":"npm run build"},"_npmUser":{"name":"axfab","email":"fabien.bavent@gmail.com"},"repository":{"url":"git+https://github.com/AxFab/pocket-db.git","type":"git"},"_npmVersion":"11.15.0","description":"An embedded document database for Node.js, designed around a small file-backed storage engine.","directories":{},"_nodeVersion":"24.11.0","_hasShrinkwrap":false,"devDependencies":{"lowdb":"^7.0.1","lokijs":"^1.5.12","typescript":"^5.4.0","@types/node":"^20.12.0","@types/lokijs":"^1.5.14","better-sqlite3":"^12.10.0","@types/better-sqlite3":"^7.6.13"},"_npmOperationalInternal":{"tmp":"tmp/pocket-db_0.1.2_1780904056148_0.35170184977160823","host":"s3://npm-registry-packages-npm-production"}},"0.1.3":{"name":"@axfab/pocket-db","version":"0.1.3","keywords":["database","embedded","nosql","typescript","document-store","append-only"],"license":"MIT","_id":"@axfab/pocket-db@0.1.3","maintainers":[{"name":"axfab","email":"fabien.bavent@gmail.com"}],"homepage":"https://pocket-db.axfab.net/","bugs":{"url":"https://github.com/AxFab/pocket-db/issues"},"dist":{"shasum":"313efb0b7ca07d1bc82736cd5ab93cfbade6cb27","tarball":"https://registry.npmjs.org/@axfab/pocket-db/-/pocket-db-0.1.3.tgz","fileCount":202,"integrity":"sha512-ol/x9Yevpgfi0HA8Y8/lwnerjUEkYb2OAIYEfxE6PrZuoB02eSoqx2V8o8q8LJAePvyn83ygnJ+Mufzh53qvtA==","signatures":[{"sig":"MEUCIBl/gs/eaE5mFiifH3qWNPl+1HRsXgX3uXnkt9WSH2HQAiEA/h5Q+hQIS2smeyWG8lTQnnECy+pQSrdNrxrSGT178+I=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":618013},"main":"./dist/cjs/src/index.js","type":"module","types":"./dist/src/index.d.ts","module":"./dist/src/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/src/index.d.ts","import":"./dist/src/index.js","require":"./dist/cjs/src/index.js"}},"gitHead":"952173a26b564d81fb67110ee77733798ebced44","scripts":{"lint":"eslint .","test":"npm run build && node --test \"dist/tests/**/*.test.js\"","bench":"npm run build && node dist/benchmarks/bench.js","build":"npm run build:esm && npm run build:cjs","lint:fix":"eslint . --fix","build:cjs":"tsc -p tsconfig.cjs.json && echo '{\"type\":\"commonjs\"}' > dist/cjs/src/package.json","build:esm":"tsc -p tsconfig.json","prepublishOnly":"npm run lint && npm run build"},"_npmUser":{"name":"axfab","email":"fabien.bavent@gmail.com"},"repository":{"url":"git+https://github.com/AxFab/pocket-db.git","type":"git"},"_npmVersion":"11.15.0","description":"An embedded document database for Node.js, designed around a small file-backed storage engine.","directories":{},"_nodeVersion":"24.11.0","_hasShrinkwrap":false,"devDependencies":{"lowdb":"^7.0.1","eslint":"^9.39.4","lokijs":"^1.5.12","globals":"^15.15.0","@eslint/js":"^9.39.4","typescript":"^5.4.0","@types/node":"^20.12.0","@types/lokijs":"^1.5.14","better-sqlite3":"^12.10.0","typescript-eslint":"^8.61.1","@types/better-sqlite3":"^7.6.13"},"_npmOperationalInternal":{"tmp":"tmp/pocket-db_0.1.3_1781610439134_0.7609475500657119","host":"s3://npm-registry-packages-npm-production"}},"0.1.4":{"name":"@axfab/pocket-db","version":"0.1.4","keywords":["database","embedded","nosql","typescript","document-store","append-only"],"license":"MIT","_id":"@axfab/pocket-db@0.1.4","maintainers":[{"name":"axfab","email":"fabien.bavent@gmail.com"}],"homepage":"https://pocket-db.axfab.net/","bugs":{"url":"https://github.com/AxFab/pocket-db/issues"},"dist":{"shasum":"3d3623e1ab33649ca0f4680951387be0217eab4b","tarball":"https://registry.npmjs.org/@axfab/pocket-db/-/pocket-db-0.1.4.tgz","fileCount":202,"integrity":"sha512-hufVcaeFgElwX0TM3BXLhKi7kALZPBmyWe+sgUUPM5/9gIpoE3rTeZZqrzveoJLUN3VqnIBwfrwHFCUFbQPQaw==","signatures":[{"sig":"MEQCIFKVegrnoDwJSALFKvoIP4bKwF7FhD5AaJzuVUM7/KlMAiAGMR9pIvIYrn+apKmhPbGFnfXvxqnpD1w0k9Vg87xTKQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":671366},"main":"./dist/cjs/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/cjs/index.js"}},"gitHead":"51aefef9684258c316f20566268aecd300e0585f","scripts":{"lint":"eslint .","test":"node --import tsx --test 'tests/*.test.ts'","bench":"npm run bench --workspace benchmarks","build":"npm run build:esm && npm run build:cjs","clean":"node -e \"require('fs').rmSync('dist', {recursive:true,force:true})\"","prepack":"npm run lint && npm run test && npm run build","lint:fix":"eslint . --fix","build:cjs":"tsc -p tsconfig.cjs.json && echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json","build:esm":"tsc -p tsconfig.json","test:coverage":"node --import tsx --test --experimental-test-coverage --test-coverage-exclude='tests/**' 'tests/*.test.ts'"},"_npmUser":{"name":"axfab","email":"fabien.bavent@gmail.com"},"repository":{"url":"git+https://github.com/AxFab/pocket-db.git","type":"git"},"workspaces":["benchmarks"],"_npmVersion":"11.18.0","description":"An embedded document database for Node.js, designed around a small file-backed storage engine.","directories":{},"_nodeVersion":"24.11.0","_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.21.0","eslint":"^9.39.4","globals":"^15.15.0","@eslint/js":"^9.39.4","typescript":"^5.4.0","@types/node":"^20.12.0","typescript-eslint":"^8.61.1"},"_npmOperationalInternal":{"tmp":"tmp/pocket-db_0.1.4_1783444018946_0.7204350912236774","host":"s3://npm-registry-packages-npm-production"}},"0.1.5":{"name":"@axfab/pocket-db","version":"0.1.5","description":"An embedded document database for Node.js, designed around a small file-backed storage engine.","type":"module","main":"./dist/cjs/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/cjs/index.js"}},"scripts":{"clean":"node -e \"require('fs').rmSync('dist', {recursive:true,force:true})\"","build":"npm run build:esm && npm run build:cjs","build:esm":"tsc -p tsconfig.json","build:cjs":"tsc -p tsconfig.cjs.json && echo '{\"type\":\"commonjs\"}' > dist/cjs/package.json","test":"node --import tsx --test 'tests/*.test.ts'","test:coverage":"node --import tsx --test --experimental-test-coverage --test-coverage-exclude='tests/**' 'tests/*.test.ts'","bench":"npm run bench --workspace benchmarks","lint":"eslint .","lint:fix":"eslint . --fix","prepack":"npm run lint && npm run test && npm run build"},"workspaces":["benchmarks"],"keywords":["database","embedded","nosql","typescript","document-store","append-only"],"license":"MIT","homepage":"https://pocket-db.axfab.net/","repository":{"type":"git","url":"git+https://github.com/AxFab/pocket-db.git"},"devDependencies":{"@eslint/js":"^9.39.4","@types/node":"^20.12.0","eslint":"^9.39.4","globals":"^15.15.0","tsx":"^4.21.0","typescript":"^5.4.0","typescript-eslint":"^8.61.1"},"engines":{"node":">=18"},"gitHead":"5ea90a2afc7e2b5924304036b950af8387ce14ff","_id":"@axfab/pocket-db@0.1.5","bugs":{"url":"https://github.com/AxFab/pocket-db/issues"},"_nodeVersion":"24.11.0","_npmVersion":"11.18.0","dist":{"integrity":"sha512-yEcv3N7uGB88tdDlgW1jmX8IW4Hh/VBBJHVK9rLj9zH5sGqIv9Y+eZLjIHOBmpEILQE3dmAOM1F+lM96BChp7Q==","shasum":"32172519181a3ad4e71d36c60dd749966b6a539c","tarball":"https://registry.npmjs.org/@axfab/pocket-db/-/pocket-db-0.1.5.tgz","fileCount":221,"unpackedSize":847431,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIHTnghggYyemB12Jh71XgXd/oUi1p5+9AQW/Jk2KZ4nqAiAMgmiXXKYBalN+gpskj5X4MNVfB3C4h1KbChNFn8tA3A=="}]},"_npmUser":{"name":"axfab","email":"fabien.bavent@gmail.com"},"directories":{},"maintainers":[{"name":"axfab","email":"fabien.bavent@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/pocket-db_0.1.5_1786800493817_0.6481769595410731"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-23T00:00:05.010Z","modified":"2026-08-15T13:28:14.172Z","0.1.0":"2026-05-23T00:00:05.603Z","0.1.1":"2026-05-31T17:42:17.303Z","0.1.2":"2026-06-08T07:34:16.277Z","0.1.3":"2026-06-16T11:47:19.285Z","0.1.4":"2026-07-07T17:06:59.115Z","0.1.5":"2026-08-15T13:28:13.966Z"},"bugs":{"url":"https://github.com/AxFab/pocket-db/issues"},"license":"MIT","homepage":"https://pocket-db.axfab.net/","keywords":["database","embedded","nosql","typescript","document-store","append-only"],"repository":{"type":"git","url":"git+https://github.com/AxFab/pocket-db.git"},"description":"An embedded document database for Node.js, designed around a small file-backed storage engine.","maintainers":[{"name":"axfab","email":"fabien.bavent@gmail.com"}],"readme":"\n<p align=\"center\">\n  <picture>\n    <source media=\"(prefers-color-scheme: dark)\" srcset=\"docs/pocket-db-dk.svg\">\n    <img src=\"docs/pocket-db.svg\" alt=\"pocket-db\" width=\"300\">\n  </picture>\n</p>\n\n**Pocket DB** — the local database for Electron, desktop and CLI apps.\nOne file, **zero native dependencies**, a familiar MongoDB-style API.\n\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/@axfab/pocket-db\"><img src=\"https://img.shields.io/npm/v/@axfab/pocket-db.svg\" alt=\"npm version\" /></a>\n  <a href=\"LICENSE\"><img src=\"https://img.shields.io/badge/license-MIT-blue.svg\" alt=\"MIT License\" /></a>\n  <img src=\"https://img.shields.io/badge/node-%3E%3D18-brightgreen\" alt=\"Node ≥ 18\" />\n  <a href=\"https://npmcharts.com/compare/@axfab/pocket-db?minimal=true\"><img src=\"https://img.shields.io/npm/dm/@axfab/pocket-db.svg\" alt=\"npm downloads\" /></a>\n</p>\n\n---\n\nAdd persistent local storage to a Node.js or Electron app — no server, no daemon,\nand **no native bindings**. No `node-gyp`, no rebuilding against every Electron\nversion, none of the `better-sqlite3` recompile dance. Run `npm install` and you\nhave a working document store backed by a single file.\n\n```ts\nimport { pocketDb } from \"@axfab/pocket-db\";\n\nconst db = pocketDb(\"./data.pdb\");\nconst users = db.collection(\"users\");\n\nusers.insertOne({ name: \"Ada\", role: \"admin\" });\nconst admins = users.find({ role: \"admin\" }).toArray();\n\ndb.close();\n```\n\nThat's the whole setup — open a file, work with collections of JSON documents, close.\n\n## Why pocket-db\n\n- 🪶 **Zero native dependencies** — pure TypeScript, no `node-gyp`, no per-Electron rebuilds\n- 📄 **Single file** — back up your whole database by copying one `.pdb` file\n- 🍃 **MongoDB-style API** — `find` / `insert` / `update` with the operators you already know\n- ⚡ **Append-only writes** — every mutation is a fast sequential append, with crash-safe batches\n- 🛡️ **Safe reads** — every query returns an independent copy, so mutating a result never corrupts your stored data\n\nInspired by SQLite (one embedded file) and MongoDB (document model), but intentionally\nsmall. The core rule — *never reserialise the whole database on a write* — makes every\ninsert, update and delete a fast append, and reads seek straight to a document's offset.\n\n**Good fit:** Electron & desktop apps, CLI tools, local servers, plugins, structured\ncaches, offline-first prototypes.\n**Not a fit:** multiple processes writing the same file at once, complex aggregation\npipelines, or anything that really wants a server database.\n\n---\n\n## Install\n\n```bash\nnpm install @axfab/pocket-db\n```\n\nNo native binaries. No optional dependencies. Pure TypeScript compiled to ESM. Node.js ≥ 18 is required (the package only relies on `structuredClone` and `Object.hasOwn`, both available since Node 17/16.9).\n\n**Bun / Deno:** not officially tested, but should work — the package touches only `node:fs`, `node:path`, `node:os`, `node:crypto`, and `Buffer`, all well-covered by both runtimes' Node compatibility layers, and `with { type: 'json' }` isn't used here so there's no import-attribute version floor to worry about.\n\n---\n\n## Quick start\n\n```ts\nimport { pocketDb } from \"@axfab/pocket-db\";\n\nconst db = pocketDb(\"./data.pdb\");\nconst users = db.collection(\"users\");\n\n// Insert\nconst { insertedId } = users.insertOne({ name: \"Ada\", role: \"admin\", age: 37 });\n\n// Find\nconst ada = users.findOne({ name: \"Ada\" });\nconsole.log(ada); // { _id: \"...\", name: \"Ada\", role: \"admin\", age: 37 }\n\n// Query with operators\nconst admins = users.find({ role: \"admin\", age: { $gte: 18 } }).toArray();\n\n// Update\nusers.updateOne(insertedId, { $set: { age: 38 }, $inc: { loginCount: 1 } });\n\n// Delete\nusers.deleteOne(insertedId);\n\ndb.close();\n```\n\n---\n\n## Core concepts\n\n### Single file, append-only\n\nAll writes are appended to the end of the file. Reads go directly to the byte offset of the document — no full-file scan. The in-memory state is rebuilt by replaying the log when `open()` is called. Deleted and updated documents leave dead records behind; `db.compact()` reclaims that space in a single forward pass.\n\n### Collections\n\nA database holds any number of named collections. Collections are created implicitly on first access and persisted to the log. Each collection has its own primary index (keyed by `_id`) and optional secondary indexes.\n\n### Document IDs\n\nEvery document gets a `_id`: a 24-character lowercase hex string (12-byte ObjectId layout — 4-byte timestamp, 5-byte random, 3-byte counter). You can supply your own `_id` on insert as long as it matches that format.\n\n### Safe reads\n\nEvery document returned by `findOne`, `find`, or a cursor is a fresh, independent object that you fully own. Mutating a query result never affects what is stored — exactly what you'd expect from a real database. This holds whether the read came from disk or from the [hot-document cache](#hot-document-cache) (cached reads are deep-cloned on the way out).\n\nThis is a deliberate guarantee: some in-memory stores return references to their internal objects by default, which is faster but means modifying a query result silently corrupts the database. Pocket DB always isolates your results.\n\n---\n\n## API\n\n### Opening a database\n\n```ts\nimport { pocketDb } from \"@axfab/pocket-db\";\nconst db = pocketDb(\"./data.pdb\");\n```\n\nYou can also merge both arguments by setting the `path` property in the options object, or pass all options together:\n\n```ts\nconst db = pocketDb({ path: \"./data.pdb\" });\n```\n\n`pocketDb` accepts an optional `OpenOptions` object as its second argument (or first, when using the object form):\n\n```ts\nconst db = pocketDb(\"./data.pdb\", {\n  durability: \"strict\",      // default: \"relaxed\"\n  serialization: \"bson\",     // default: \"json\" — only applies when creating a new file\n});\n```\n\n#### `durability`\n\nControls whether `fsync` is called after every write.\n\n- `\"relaxed\"` *(default)* — skips `fsync`. Writes reach the OS page cache but may be lost on a power failure or OS crash before the cache is flushed. Faster; suitable when losing the last few writes on a hard crash is acceptable.\n- `\"strict\"` — calls `fsync` after every `appendOperation`, guaranteeing data is on durable storage before the call returns. Safest; incurs one extra syscall per write.\n\n#### `serialization`\n\nSelects the document encoding format when **creating a new database file**. Opening an existing file always uses the format recorded in the file header — this option is ignored.\n\n- `\"json\"` *(default)* — documents stored as UTF-8 JSON. Human-readable, universally compatible.\n- `\"bson\"` — documents stored as BSON (Binary JSON). Supports double, string, document, array, boolean, null, int32, int64. More compact than JSON for numeric-heavy documents.\n- `\"amf3\"` — documents stored as AMF3 (Action Message Format 3). A compact binary format supporting undefined, null, boolean, integer, double, string, array, and object.\n\n### Database\n\n```ts\ndb.collection(name: string): Collection\ndb.getCollections(): string[]              // names of all registered collections\ndb.existsCollection(name: string): boolean\ndb.compact(): void                         // reclaim space from dead records\ndb.close(): void\n```\n\n### Collection\n\n```ts\ncollection.insertOne(doc): InsertOneResult\ncollection.insertMany(docs): InsertManyResult\n\ncollection.findOne(query?): Record | null\ncollection.find(query?): Cursor\ncollection.countDocuments(query?): number\ncollection.distinct(field, query?, options?: { limit?: number }): unknown[]\n\ncollection.updateOne(id | query, update): UpdateResult\ncollection.updateMany(query, update): UpdateResult\n\ncollection.replaceOne(id, doc): ReplaceOneResult\ncollection.replaceOne(doc & { _id }): ReplaceOneResult\n\ncollection.deleteOne(id | query): DeleteOneResult\ncollection.deleteMany(query?): DeleteManyResult\n\ncollection.createIndex(field, { type: \"string\" | \"number\", unique?: boolean }): CreateIndexResult\ncollection.dropIndex(field): DropIndexResult\ncollection.getIndexes(): { name: string; type: string; unique: boolean }[]\ncollection.existsIndex(name: string): boolean\ncollection.drop(): DropResult\n\ncollection.enableCache(maxBytes: number): void   // hot-document cache (off by default)\ncollection.disableCache(): void\ncollection.cacheStats(): DocumentCacheStats | null\n```\n\n### Cursor\n\n```ts\ncursor.next(): Record | null\ncursor.toArray(): Record[]\ncursor.count(): number\n\ncursor.sort(spec: Record<string, 1 | -1>): Cursor   // up to 4 fields\ncursor.limit(n: number): Cursor\ncursor.skip(n: number): Cursor\n```\n\n---\n\n## Query operators\n\nQueries are plain objects. A bare value is shorthand for `$eq`.\n\n| Operator | Description |\n|----------|-------------|\n| `$eq` | Strict equality (no type coercion) |\n| `$ne` | Not equal |\n| `$gt` / `$gte` | Greater than / greater than or equal |\n| `$lt` / `$lte` | Less than / less than or equal |\n| `$in` | Field value is in the given array |\n| `$nin` | Field value is not in the given array |\n| `$exists` | Field is present (`true`) or absent (`false`) |\n| `$regex` | String matches a regular expression (flags via `$options`; `g`/`y` rejected) |\n| `$not` | Negates an operator expression |\n| `$and` | Logical AND of sub-queries |\n| `$or` | Logical OR of sub-queries |\n| `$nor` | Logical NOR of sub-queries |\n\n```ts\n// Compound query\nusers.find({\n  $and: [\n    { role: { $in: [\"admin\", \"editor\"] } },\n    { age: { $gte: 18, $lt: 65 } }\n  ]\n});\n\n// Negation\nusers.find({ status: { $not: { $eq: \"banned\" } } });\n\n// OR\nusers.find({ $or: [{ role: \"admin\" }, { role: \"editor\" }] });\n\n// Regex (string pattern + $options, RegExp value, or bare RegExp shorthand)\nusers.find({ name: { $regex: \"^ada\", $options: \"i\" } });\nusers.find({ name: { $regex: /^ada/i } });\nusers.find({ name: /^Ada/ });\n```\n\n---\n\n## Update operators\n\nUpdates are expressed as operator objects applied to the current document.\n\n| Operator | Description |\n|----------|-------------|\n| `$set` | Set one or more fields |\n| `$unset` | Remove one or more fields |\n| `$inc` | Increment a numeric field |\n| `$mul` | Multiply a numeric field by a factor |\n| `$min` / `$max` | Set field only if new value is lower / higher |\n| `$rename` | Rename a field (missing source is a no-op) |\n| `$currentDate` | Set field to the current date (`true` / `{ $type: \"date\" }` → ISO string, `{ $type: \"timestamp\" }` → epoch ms) |\n| `$push` | Append a value to an array field |\n| `$addToSet` | Append a value only if no equal element exists |\n| `$pop` | Remove the last (`1`) or first (`-1`) array element |\n| `$pull` | Remove array elements equal to a value or matching a condition |\n| `$pullAll` | Remove array elements equal to any listed value |\n\n```ts\nusers.updateOne(id, {\n  $set: { role: \"editor\" },\n  $inc: { loginCount: 1 },\n  $mul: { score: 1.1 },\n  $currentDate: { lastLogin: true },\n  $addToSet: { tags: \"active\" },\n  $pull: { scores: { $lt: 10 } }\n});\n```\n\n`_id` is immutable and cannot be modified by any update operator.\n\n---\n\n## Indexes\n\nSecondary indexes speed up equality and range queries. They are rebuilt from the log at every open.\n\n```ts\n// Create\nusers.createIndex(\"role\", { type: \"string\" });\nusers.createIndex(\"age\",  { type: \"number\" });\n\n// Unique: rejects any write that would duplicate an existing value\nusers.createIndex(\"email\", { type: \"string\", unique: true });\n\n// Drop\nusers.dropIndex(\"role\");\n```\n\n`StringIndex` supports `$eq` and `$in` lookups. `NumberIndex` additionally supports `$gt`, `$gte`, `$lt`, `$lte` range scans. The query planner automatically picks the most selective available index for each query.\n\n`unique: true` (default `false`) turns an index into a uniqueness constraint, checked on every `insertOne`/`insertMany`/`replaceOne`/`updateOne`/`updateMany` before the write is committed. Only values matching the index's own type participate — a missing field or a value of a different type never conflicts. Creating a unique index over a collection that already has conflicting values throws and leaves the collection unchanged. See [docs/indexes.md](docs/indexes.md#unique-indexes) for details.\n\n---\n\n## Hot-document cache\n\nBy default every read decodes its document from the file. For workloads that read the same documents repeatedly, you can opt into an in-memory cache that keeps parsed *hot* documents around, so repeated reads skip both the file read and the decode.\n\n```ts\nusers.enableCache(16 * 1024 * 1024); // 16 MB budget; least-recently-used docs are evicted\nusers.cacheStats();                  // { hits, misses, evictions, bytes, documentCount, ... }\nusers.disableCache();                // free everything, back to zero overhead\n```\n\nThe cache is **off by default** — when disabled it costs nothing (no object is even allocated). It is keyed by `_id` and versioned by file offset, so it stays correct across updates, deletes, compaction, and open cursors (snapshot reads are preserved).\n\n**The gain:** on a 2,000-document JSON dataset, single-document reads get ~**2.9×** faster and even full scans ~**1.8×** faster, because skipping the read + JSON parse outweighs the cost of cloning the cached object.\n\n**The drawbacks:** it trades memory for speed, so size the budget for your hot working set. It is rebuilt empty on every `open()` (in-memory only, never persisted). And large scans populate the cache as they read — if the budget is smaller than a scan's footprint, a one-off scan can evict genuinely hot documents.\n\nSee [docs/cache.md](docs/cache.md) for the full design, internals, and benchmark methodology.\n\n---\n\n## Sorting and pagination\n\n```ts\nconst page = users\n  .find({ role: \"admin\" })\n  .sort({ age: -1, name: 1 })   // up to 4 sort fields\n  .skip(20)\n  .limit(10)\n  .toArray();\n```\n\nSort accepts `1` (ascending) and `-1` (descending). Missing values sort **first** in ascending order and **last** in descending order. Sorting is always eager — narrow the candidate set with an indexed query before sorting over large collections.\n\n---\n\n## Compaction\n\nDead records accumulate as documents are updated or deleted. `compact()` rewrites the file in a single forward pass, keeping only live data:\n\n```ts\ndb.compact();\n```\n\nAfter compaction, all in-memory indexes are refreshed automatically.\n\n---\n\n## Batch atomicity\n\n`insertMany`, `updateMany`, and `deleteMany` are crash-safe: if the process is killed mid-batch, the partial batch is silently discarded on the next open. Either all operations are visible or none are.\n\n---\n\n## File locking\n\n`pocketDb()` creates a `.lock` file next to the database file. A second `pocketDb()` on the same path from a different process will throw. Stale locks left by crashed processes are detected via PID check and cleared automatically.\n\nPocket DB is designed for **single-process use**. Multiple concurrent writers on the same file are not supported.\n\n---\n\n## TypeScript\n\nPocket DB is written in TypeScript and ships its own type declarations. All public types are exported from the package root:\n\n```ts\nimport { pocketDb } from \"@axfab/pocket-db\";\nimport type {\n  Database, Collection, Cursor,\n  InsertOneResult, InsertManyResult,\n  UpdateResult, ReplaceOneResult,\n  DeleteOneResult, DeleteManyResult,\n  CreateIndexResult, DropIndexResult, DropResult,\n  IndexInfo, OpenOptions, SortDirection,\n  DocumentCacheStats\n} from \"@axfab/pocket-db\";\n```\n\n---\n\n## Documentation\n\nThe `docs/` folder contains in-depth documentation available as a wiki:\n\n- [File format](docs/file-format.md) — binary layout, record structure, U29 encoding\n- [Storage semantics](docs/storage.md) — replay rules, write path, crash recovery\n- [Query & update model](docs/query.md) — operators, compilation, cursor semantics\n- [Indexes](docs/indexes.md) — primary index, StringIndex, NumberIndex, query planner\n- [Hot-document cache](docs/cache.md) — LRU cache internals, offset versioning, eviction, benchmarks\n- [Compaction](docs/compact.md) — algorithm, invariants, secondary index refresh\n\n---\n\n## Performance\n\nPocket DB is built for fast, durable writes. Benchmarked against other embedded\nstores on 1,000 documents across the operations in our benchmark suite — ops/sec,\nhigher is better.\n\n**The headline:** for durable writes, pocket-db is **40–700× faster** than every other\nfile-backed store here, and lands within ~12% of in-memory SQLite — which isn't even\ndurable.\n\n📊 **[Full results table → benchmarks/RESULTS.md](benchmarks/RESULTS.md)** — all\nadapters and operations side by side. Regenerate anytime with `npm run bench`, which\nprints a width-aware ranked view to the console and refreshes `RESULTS.md` and\n`results.json`.\n\n### Reading the results\n\n**Writes — where pocket-db shines.** Every mutation is a single sequential append:\nno B-tree rebalancing, no page allocation, no full-file reserialisation. That's why\n`insertOne`, `updateOne` and `deleteOne` leave every other persistent store far behind\nand sit right next to in-memory SQLite. For a desktop or Electron app writing to disk\non every user action, this is exactly the path that matters — and it stays durable.\n\n**Single-document reads are fast too.** `findById` runs at ~140k ops/sec — more than\nenough for typical app workloads, even though it reads from disk rather than RAM.\n\n**Full scans and sorts — the current tradeoff, by design.** json-file, lowdb and LokiJS\nwin on `findAll` and `sortByScore` because they hold the *entire dataset in memory* and\nserve reads from there. That speed has a hard ceiling: your database can never grow\nlarger than available RAM. Pocket DB keeps only its indexes in memory and reads each\ndocument from its file offset on demand — so it can back a database **far larger than\nRAM would ever allow**. The price today is slower full scans.\n\nA **hot-document cache** now closes most of that read gap without touching the append-only\nwrite model. Opt in with `collection.enableCache(maxBytes)` and hot documents stay parsed\nin memory: single-document reads get ~2.9× faster and full scans ~1.8× faster on the\n2,000-document JSON benchmark. It is off by default and trades memory for speed — see\n[docs/cache.md](docs/cache.md).\n\n**Reading the read numbers fairly.** pocket-db returns an independent copy of every\ndocument, so mutating a result never touches stored data. Some in-memory stores in this\ntable return references to their internal objects by default — fast, but modifying a query\nresult silently corrupts the database. Part of their read-throughput lead is simply the\ndefensive copy pocket-db makes and they skip; it buys a correctness guarantee we keep on\npurpose.\n\n**In short:** if your workload is write-heavy, needs durability, or outgrows memory,\npocket-db is the right tool. If you need pure in-memory read throughput on a dataset that\ncomfortably fits in RAM, an in-memory store still wins today.\n\nRun the benchmarks yourself:\n```bash\nnpm install\nnpm run bench\n```\n\n---\n\n## License\n\n[MIT](LICENSE) © Fabien Bavent\n","readmeFilename":"README.md"}