{"_id":"@brennoleondesouza/woton","name":"@brennoleondesouza/woton","dist-tags":{"latest":"0.2.0"},"versions":{"0.2.0":{"name":"@brennoleondesouza/woton","version":"0.2.0","description":"Encrypted embedded database for Node.js with a simple JavaScript-first API.","type":"module","license":"MIT","author":"","homepage":"https://github.com/AnThophicous/woton#readme","repository":{"type":"git","url":"git+https://github.com/AnThophicous/woton.git"},"bugs":{"url":"https://github.com/AnThophicous/woton/issues"},"main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"bin":{"woton":"dist/cli.js"},"engines":{"node":">=20.0.0"},"scripts":{"clean":"node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"","build":"npm run clean && node ./node_modules/typescript/bin/tsc -p tsconfig.json","test":"npm run clean && node ./node_modules/typescript/bin/tsc -p tsconfig.test.json && node scripts/run-tests.mjs","typecheck":"node ./node_modules/typescript/bin/tsc -p tsconfig.json --noEmit","benchmark:build":"node ./node_modules/typescript/bin/tsc -p benchmarks/tsconfig.json","benchmark":"npm run benchmark:build && node benchmarks/.build/run.js","benchmark:smoke":"npm run benchmark:build && node benchmarks/.build/run.js --smoke","release:check":"npm test && npm pack --dry-run","prepack":"npm run build"},"keywords":["database","embedded","encrypted","sqlite","typescript","node"],"devDependencies":{"@types/node":"^20.17.19","typescript":"^5.7.3"},"gitHead":"a0a5fd2cba1f2c5f6e7993505027df34b07742f8","_id":"@brennoleondesouza/woton@0.2.0","_nodeVersion":"24.14.1","_npmVersion":"11.11.0","dist":{"integrity":"sha512-zA8FgjyX2pCh2sQvH7xBhyblvRekTJ40cv95k6kq7ZmIl9ThE6ERqHRC4qlaPWBHqT1E4REc1VkjIOIN1jnuiA==","shasum":"52e48593a120f5bf6fa58df924e6d9581c9566bb","tarball":"https://registry.npmjs.org/@brennoleondesouza/woton/-/woton-0.2.0.tgz","fileCount":73,"unpackedSize":490179,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCuqdFyQeVaBrEKDix17T3wvzZEIkahLbHb7Qp7v7sZPwIhALDfRpfmYfVx7p9mnzdEPosTvheZ0bGCsYW4p9jgHtHi"}]},"_npmUser":{"name":"brennoleondesouza","email":"resumindoanimes60@gmail.com"},"directories":{},"maintainers":[{"name":"brennoleondesouza","email":"resumindoanimes60@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/woton_0.2.0_1777214762010_0.3571466063151003"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-26T14:46:01.913Z","0.2.0":"2026-04-26T14:46:02.162Z","modified":"2026-04-26T14:46:02.452Z"},"maintainers":[{"name":"brennoleondesouza","email":"resumindoanimes60@gmail.com"}],"description":"Encrypted embedded database for Node.js with a simple JavaScript-first API.","homepage":"https://github.com/AnThophicous/woton#readme","keywords":["database","embedded","encrypted","sqlite","typescript","node"],"repository":{"type":"git","url":"git+https://github.com/AnThophicous/woton.git"},"bugs":{"url":"https://github.com/AnThophicous/woton/issues"},"license":"MIT","readme":"# Woton\r\n\r\n<p align=\"center\">\r\n  <img src=\"https://raw.githubusercontent.com/AnThophicous/woton/main/Images/Banner.png\" alt=\"Woton banner\" width=\"600\">\r\n</p>\r\n\r\n\r\nWoton is an encrypted embedded database for Node.js, written in TypeScript and exposed through a JavaScript-first API. It stores local JSON documents in a single `.wtdb` database file, with a small query builder, a compact text language for CLI and scripts, transactions, WAL recovery, and an experimental paged-storage layer for future lower-level engines.\r\n\r\n> Status: `0.2.0`, alpha-stage. Woton is usable for local experimentation, prototypes, tests, CLIs, and small embedded data stores. It is not marketed as production-ready security software. Do not treat it as audited, hardened, or suitable for high-risk secrets until the project has had external security review, larger benchmarks, fuzzing, and operational hardening.\r\n\r\nLinks:\r\n\r\n- GitHub repository: <https://github.com/AnThophicous/woton>\r\n- NPM package: <https://www.npmjs.com/package/@brennoleondesouza%2Fwoton>\n\r\n## Table of Contents\r\n\r\n- [Install](#install)\r\n- [Create Your First Database](#create-your-first-database)\r\n- [Opening Databases](#opening-databases)\r\n- [Data Model](#data-model)\r\n- [Collections and CRUD](#collections-and-crud)\r\n- [TypeScript](#typescript)\r\n- [Query Builder](#query-builder)\r\n- [Indexes](#indexes)\r\n- [Woton Language API](#woton-language-api)\r\n- [CLI](#cli)\r\n- [Transactions](#transactions)\r\n- [Persistence, Flush, and Checkpoints](#persistence-flush-and-checkpoints)\r\n- [Backups](#backups)\r\n- [Stats](#stats)\r\n- [Password Rotation](#password-rotation)\r\n- [Security Model and Limits](#security-model-and-limits)\r\n- [WAL, Recovery, and Lock Files](#wal-recovery-and-lock-files)\r\n- [Performance Notes](#performance-notes)\r\n- [Lown Multi-Database Scheduler](#lown-multi-database-scheduler)\r\n- [Advanced and Experimental Internals](#advanced-and-experimental-internals)\r\n- [Tests and Benchmarks](#tests-and-benchmarks)\r\n- [Publishing and Repository Hygiene](#publishing-and-repository-hygiene)\r\n- [Common Errors](#common-errors)\r\n- [License](#license)\r\n\r\n## Install\r\n\r\nWoton requires Node.js `20+` and is ESM-only.\r\n\r\n```bash\r\nnpm install @brennoleondesouza/woton\n```\r\n\r\nFor local development from this repository:\r\n\r\n```bash\r\nnpm install\r\nnpm test\r\nnpm run build\r\n```\r\n\r\n## Create Your First Database\r\n\r\nCreate a database file with a high-entropy password or passphrase. The default minimum password length is 16 bytes, not 16 characters. Non-ASCII strings can use more than one byte per character, but length is not the same thing as entropy. Prefer a secret manager, an environment variable populated by your deployment system, or a long generated passphrase.\r\n\r\n```ts\r\nimport { Woton } from \"@brennoleondesouza/woton\";\n\r\nconst password = process.env.WOTON_PASSWORD;\r\n\r\nif (!password) {\r\n  throw new Error(\"Set WOTON_PASSWORD first.\");\r\n}\r\n\r\nconst db = await Woton.open({\r\n  path: \"./data/app.wtdb\",\r\n  password\r\n});\r\n\r\nconst users = db.collection(\"users\");\r\n\r\nawait users.index(\"email\");\r\n\r\nawait users.insert({\r\n  id: \"ada\",\r\n  name: \"Ada Lovelace\",\r\n  email: \"ada@example.com\",\r\n  active: true,\r\n  age: 36\r\n});\r\n\r\nconst ada = await users.where(\"email\", \"ada@example.com\").first();\r\n\r\nconsole.log(ada);\r\n\r\nawait db.close();\r\n```\r\n\r\nThe first open creates `./data/app.wtdb`. During normal use Woton may also create `./data/app.wtdb-wal` and `./data/app.wtdb-lock`.\r\n\r\n## Opening Databases\r\n\r\n```ts\r\nconst db = await Woton.open({\r\n  path: \"./data/app.wtdb\",\r\n  password,\r\n  autosave: true,\r\n  minPasswordLength: 16,\r\n  checkpointEveryWrites: 1000,\r\n  forceUnlock: false\r\n});\r\n```\r\n\r\n`Woton.open` options:\r\n\r\n| Option | Type | Default | Description |\r\n| --- | --- | --- | --- |\r\n| `path` | `string` | required | Database path. It must end in `.wtdb`. |\r\n| `password` | `string \\| Buffer` | required | Password material used for encryption key derivation. |\r\n| `autosave` | `boolean` | `true` | When true, writes are appended to the encrypted WAL and later checkpointed. |\r\n| `minPasswordLength` | `number` | `16` | Minimum password size in bytes. |\r\n| `checkpointEveryWrites` | `number` | `1000` | Number of pending WAL operations before Woton writes a fresh encrypted checkpoint. |\r\n| `forceUnlock` | `boolean` | `false` | Dangerous last-resort lock removal. Use only after verifying no process is using the database. |\r\n\r\nAlways call `await db.close()` when done. `close()` flushes pending data and releases the lock file.\r\n\r\n## Data Model\r\n\r\nWoton stores documents in named collections:\r\n\r\n```text\r\ndatabase\r\n  users\r\n    ada\r\n    grace\r\n  posts\r\n    post-1\r\n```\r\n\r\nEach record is a JSON-safe object plus metadata:\r\n\r\n```ts\r\ntype WotonRecord<T extends object> = T & {\r\n  id: string;\r\n  createdAt: string;\r\n  updatedAt: string;\r\n};\r\n```\r\n\r\nAccepted values:\r\n\r\n- `string`\r\n- finite `number`\r\n- `boolean`\r\n- `null`\r\n- arrays of JSON-safe values\r\n- plain objects with JSON-safe values\r\n\r\nRejected values include `undefined`, functions, symbols, `bigint`, class instances, and `Date` objects. Store dates as strings:\r\n\r\n```ts\r\nawait events.insert({\r\n  id: \"release\",\r\n  startsAt: new Date().toISOString()\r\n});\r\n```\r\n\r\nName rules:\r\n\r\n- Collections: start with a letter, max 64 characters, then letters, numbers, `_`, `:`, or `-`.\r\n- Record IDs: start with a letter or number, max 128 characters, then letters, numbers, `_`, `.`, `:`, or `-`.\r\n- Field paths: start with a letter or `_`, max 128 characters, then letters, numbers, `_`, `.`, or `-`.\r\n\r\n## Collections and CRUD\r\n\r\nYou can use the collection wrapper:\r\n\r\n```ts\r\nconst users = db.collection(\"users\");\r\n```\r\n\r\nOr call database methods directly:\r\n\r\n```ts\r\nawait db.insert(\"users\", { id: \"ada\", name: \"Ada\" });\r\n```\r\n\r\nCreate collections explicitly when useful:\r\n\r\n```ts\r\nawait db.createCollection(\"users\");\r\nconst info = await db.collections();\r\nawait db.dropCollection(\"old_users\");\r\n```\r\n\r\nInsert:\r\n\r\n```ts\r\nconst created = await users.insert({\r\n  id: \"ada\",\r\n  name: \"Ada\",\r\n  email: \"ada@example.com\"\r\n});\r\n```\r\n\r\nInsert with separate ID:\r\n\r\n```ts\r\nawait users.insert(\r\n  { name: \"Grace\", email: \"grace@example.com\" },\r\n  { id: \"grace\" }\r\n);\r\n```\r\n\r\nInsert without an ID to generate a UUID:\r\n\r\n```ts\r\nconst record = await users.insert({ name: \"Lin\" });\r\nconsole.log(record.id);\r\n```\r\n\r\nReplace or create with `put`:\r\n\r\n```ts\r\nawait users.put(\"ada\", {\r\n  name: \"Ada Lovelace\",\r\n  email: \"ada@example.com\"\r\n});\r\n```\r\n\r\n`put` preserves `createdAt` when replacing an existing record and updates `updatedAt`.\r\n\r\nRead:\r\n\r\n```ts\r\nconst ada = await users.get(\"ada\"); // WotonRecord<User> | null\r\nconst all = await users.all();\r\nconst total = await users.count();\r\n```\r\n\r\nPatch:\r\n\r\n```ts\r\nawait users.update(\"ada\", {\r\n  active: true\r\n});\r\n```\r\n\r\n`update` is a shallow merge. It cannot change `id` or `createdAt`.\r\n\r\nDelete:\r\n\r\n```ts\r\nconst existed = await users.delete(\"ada\");\r\n```\r\n\r\n## TypeScript\r\n\r\nWoton ships TypeScript declarations from `dist/index.d.ts`.\r\n\r\n```ts\r\nimport { Woton, type WotonRecord } from \"@brennoleondesouza/woton\";\n\r\ninterface User {\r\n  name: string;\r\n  email: string;\r\n  age: number;\r\n  active: boolean;\r\n  profile?: {\r\n    country: string;\r\n  };\r\n}\r\n\r\nconst db = await Woton.open({\r\n  path: \"./data/app.wtdb\",\r\n  password: process.env.WOTON_PASSWORD!\r\n});\r\n\r\nconst users = db.collection<User>(\"users\");\r\n\r\nconst created: WotonRecord<User> = await users.insert({\r\n  id: \"ada\",\r\n  name: \"Ada\",\r\n  email: \"ada@example.com\",\r\n  age: 36,\r\n  active: true\r\n});\r\n\r\nconst activeUsers = await users\r\n  .where(\"active\", true)\r\n  .sort(\"name\", \"asc\")\r\n  .find();\r\n```\r\n\r\nWoton validates JSON safety at runtime. TypeScript helps at compile time, but it does not replace runtime validation.\r\n\r\n## Query Builder\r\n\r\nThe query builder supports equality, comparison, string/array operators, ordering, limits, and offsets.\r\n\r\n```ts\r\nconst page = await users\r\n  .where(\"active\", true)\r\n  .and(\"age\", \">=\", 18)\r\n  .sort(\"name\", \"asc\")\r\n  .skip(20)\r\n  .take(20)\r\n  .find();\r\n```\r\n\r\n`where(\"field\", value)` is shorthand for `where(\"field\", \"==\", value)`.\r\n\r\nSupported operators:\r\n\r\n- `=`\r\n- `==`\r\n- `!=`\r\n- `>`\r\n- `>=`\r\n- `<`\r\n- `<=`\r\n- `contains`\r\n- `startsWith`\r\n- `endsWith`\r\n- `in`\r\n\r\nNested fields use dot paths:\r\n\r\n```ts\r\nconst brUsers = await users\r\n  .where(\"profile.country\", \"BR\")\r\n  .find();\r\n```\r\n\r\nCounting through a query:\r\n\r\n```ts\r\nconst activeCount = await users\r\n  .where(\"active\", true)\r\n  .count();\r\n```\r\n\r\n## Indexes\r\n\r\nIndexes are declared per collection and field:\r\n\r\n```ts\r\nawait users.index(\"email\");\r\nawait users.index(\"profile.country\");\r\n```\r\n\r\nRemove an index:\r\n\r\n```ts\r\nawait users.unindex(\"email\");\r\n```\r\n\r\nCurrent indexes accelerate equality queries only: `=` and `==`. Other operators still work, but they scan the candidate records after any applicable equality index is used.\r\n\r\n```ts\r\nawait users.index(\"email\");\r\n\r\nconst ada = await users\r\n  .where(\"email\", \"==\", \"ada@example.com\")\r\n  .first();\r\n```\r\n\r\nIndexes are tracked in the encrypted database state and rebuilt in memory when the database opens.\r\n\r\n## Woton Language API\r\n\r\nWoton includes a small text language for CLI, scripts, and automation. It is intentionally not SQL and does not use `eval`.\r\n\r\n```ts\r\nawait db.query(\"make users\");\r\nawait db.query('put users { \"id\": \"ada\", \"name\": \"Ada\", \"active\": true }');\r\nawait db.query(\"index users email\");\r\nawait db.query('from users where active == true sort name asc limit 10');\r\n```\r\n\r\nCommands:\r\n\r\n```text\r\nmake users\r\ndrop users\r\nindex users email\r\nunindex users email\r\nput users { \"id\": \"ada\", \"name\": \"Ada\" }\r\nget users ada\r\nset users ada { \"active\": true }\r\ndel users ada\r\ndelete users ada\r\nfrom users where age >= 18 sort name asc limit 10\r\ncount users where active == true\r\n```\r\n\r\nJSON payloads for `put` and `set` must be valid JSON objects. Query values can be quoted strings, numbers, booleans, `null`, arrays, or JSON values where supported by the tokenizer.\r\n\r\n## CLI\r\n\r\nAfter installation or build, the `woton` binary opens one `.wtdb` file and runs one Woton language command. The password is read from `WOTON_PASSWORD`; it is not accepted as a CLI argument.\r\n\r\n```bash\r\nWOTON_PASSWORD=\"use a high entropy secret\" woton ./data/app.wtdb \"from users limit 10\"\r\n```\r\n\r\nFrom stdin:\r\n\r\n```bash\r\necho 'count users where active == true' | WOTON_PASSWORD=\"use a high entropy secret\" woton ./data/app.wtdb\r\n```\r\n\r\nLocal repository build:\r\n\r\n```bash\r\nnpm run build\r\nnode dist/cli.js ./data/app.wtdb \"from users limit 10\"\r\n```\r\n\r\n## Transactions\r\n\r\nTransactions group multiple writes into one queued operation. If the handler throws, Woton rolls back in-memory changes before the transaction is committed.\r\n\r\n```ts\r\nawait db.transaction(async (tx) => {\r\n  const users = tx.collection<User>(\"users\");\r\n  const audit = tx.collection(\"audit\");\r\n\r\n  await users.update(\"ada\", { active: false });\r\n  await audit.insert({\r\n    action: \"user.deactivated\",\r\n    userId: \"ada\",\r\n    at: new Date().toISOString()\r\n  });\r\n});\r\n```\r\n\r\nThe transaction API mirrors collection CRUD and query builder methods. Woton serializes writes per database instance through an internal write queue.\r\n\r\n## Persistence, Flush, and Checkpoints\r\n\r\nWith the default `autosave: true`, each write appends encrypted WAL frames to `.wtdb-wal`. Woton writes a fresh encrypted checkpoint to `.wtdb` after `checkpointEveryWrites` pending WAL operations, and also during `flush()` or `close()` when dirty data exists.\r\n\r\n```ts\r\nawait db.flush();\r\nawait db.close();\r\n```\r\n\r\n`flush()` waits for queued writes and checkpoints dirty state. Checkpointing uses an atomic temp-file write and rename pattern.\r\n\r\n### `autosave: false` Warning\r\n\r\n`autosave: false` disables WAL appends for writes. This can be useful for fast bulk loading, but durability is weaker: unflushed writes can be lost if the process exits or crashes before `flush()` or `close()`.\r\n\r\n```ts\r\nconst db = await Woton.open({\r\n  path: \"./data/import.wtdb\",\r\n  password,\r\n  autosave: false\r\n});\r\n\r\nfor (const row of rows) {\r\n  await db.collection(\"events\").insert(row);\r\n}\r\n\r\nawait db.flush();\r\nawait db.close();\r\n```\r\n\r\nUse `autosave: false` only when your application can tolerate replaying or losing the current batch.\r\n\r\n## Backups\r\n\r\n```ts\r\nawait db.backup(\"./backups/app-copy.wtdb\");\r\n```\r\n\r\n`backup()` flushes first, then copies the encrypted `.wtdb` file. The backup remains encrypted with the current password. Keep backups out of Git and package artifacts.\r\n\r\n## Stats\r\n\r\n```ts\r\nconst stats = await db.stats();\r\n```\r\n\r\nExample shape:\r\n\r\n```json\r\n{\r\n  \"path\": \"./data/app.wtdb\",\r\n  \"formatVersion\": 2,\r\n  \"collections\": 2,\r\n  \"records\": 1200,\r\n  \"indexes\": 3,\r\n  \"fileSizeBytes\": 94012,\r\n  \"journalSizeBytes\": 0,\r\n  \"pendingJournalOperations\": 0,\r\n  \"encrypted\": true\r\n}\r\n```\r\n\r\nWhen a checkpoint has run, `lastCheckpoint` may include timing fields such as serialization, encryption, write, fsync, rename, directory fsync, total time, and byte size.\r\n\r\n## Password Rotation\r\n\r\n```ts\r\nawait db.changePassword(newPassword);\r\n```\r\n\r\n`changePassword()` validates the new password, creates a new encryption key, checkpoints the current state, and clears pending WAL state. Use a secret manager or high-entropy passphrase. The default minimum is 16 bytes; applications can raise it with `minPasswordLength`.\r\n\r\n## Security Model and Limits\r\n\r\nWoton currently provides:\r\n\r\n- AES-256-GCM encryption for the main `.wtdb` file.\r\n- `scrypt`-based key derivation.\r\n- Random salt and IV values.\r\n- Authenticated encrypted WAL frames.\r\n- File extension checks for `.wtdb`.\r\n- Validation for collection names, record IDs, field paths, and JSON-safe documents.\r\n- A parser that does not execute user code.\r\n- Single-process lock files to reduce accidental concurrent opens.\r\n\r\nImportant limits:\r\n\r\n- Woton is alpha-stage and has not had an external security audit.\r\n- Do not claim or assume production-ready security.\r\n- Woton protects data at rest only when password handling, host security, backups, logs, and memory exposure are also handled by your application.\r\n- A weak password can still be brute-forced offline from a stolen database.\r\n- Woton does not provide access control, users, roles, network authentication, or row-level permissions.\r\n- Woton is not a distributed system and does not provide distributed consensus or distributed locks.\r\n- Lock files are advisory process/file coordination, not a security boundary.\r\n\r\n## WAL, Recovery, and Lock Files\r\n\r\nMain database files:\r\n\r\n- `.wtdb`: encrypted database checkpoint.\r\n- `.wtdb-wal`: encrypted write-ahead log frames for writes since the last checkpoint.\r\n- `.wtdb-lock`: lock file for a currently open database.\r\n\r\nOn open, Woton reads the encrypted checkpoint, reads committed WAL frames, applies them in sequence, rebuilds indexes as needed, and checkpoints recovered state. Truncated final WAL records are ignored as incomplete. Corruption before the end of the WAL is treated as a file error.\r\n\r\nWoton is single-writer per database file. Do not open the same `.wtdb` for writing from multiple processes. The lock file helps catch local accidental concurrent opens, but it is not a distributed lock and should not be used for network filesystem coordination.\r\n\r\nStale lock recovery:\r\n\r\n- If the lock belongs to the same host and the recorded process is no longer alive, Woton can remove it.\r\n- If the lock is corrupt, belongs to another host, or looks active, opening fails.\r\n- `forceUnlock: true` deletes the lock as a dangerous last resort. Use it only after verifying no process is using the database file. Misuse can corrupt or lose data.\r\n\r\n## Performance Notes\r\n\r\nThe default high-level Woton engine keeps the working database state and indexes in memory. Writes append compact encrypted WAL frames and periodic checkpoints rewrite the encrypted snapshot.\r\n\r\nGood fits today:\r\n\r\n- Small to medium embedded datasets.\r\n- Local CLI tools.\r\n- Tests and fixtures.\r\n- Desktop or single-process Node utilities.\r\n- Prototypes that need encrypted local persistence.\r\n\r\nBe careful with:\r\n\r\n- Very large datasets.\r\n- Many concurrent writers.\r\n- Network filesystems.\r\n- Workloads that need streaming scans, partial loading, or multi-process writes.\r\n- High-risk regulated production secrets.\r\n\r\nIndex equality fields you query often. Use `autosave: false` only for replayable bulk loads. Run benchmarks with your own data shape before depending on performance claims.\r\n\r\n## Lown Multi-Database Scheduler\r\n\r\nLown is Woton's multi-database coordinator. It opens multiple Woton databases and schedules work using database priorities, operation priorities, aging, observed query behavior, and optional semantic scoring.\r\n\r\nUse Lown when one Node process needs to coordinate several local Woton database files:\r\n\r\n```ts\r\nimport { Lown } from \"@brennoleondesouza/woton\";\n\r\nconst lown = await Lown.open({\r\n  databases: {\r\n    auth: {\r\n      path: \"./data/auth.wtdb\",\r\n      password,\r\n      priority: 9\r\n    },\r\n    logs: {\r\n      path: \"./data/logs.wtdb\",\r\n      password,\r\n      priority: 2,\r\n      autosave: false\r\n    }\r\n  },\r\n  scheduler: {\r\n    maxConcurrent: 1,\r\n    agingMs: 250,\r\n    maxQueueSize: 1024\r\n  }\r\n});\r\n```\r\n\r\nScheduler defaults:\r\n\r\n- `maxConcurrent`: `1`\r\n- `agingMs`: `250`\r\n- `maxQueueSize`: `1024`\r\n\r\nPriorities are numbers from `0` to `10`. The default database priority is `5`. Operation-level priority also defaults to `5`. Higher priority work is scheduled first, while aging prevents older queued work from being ignored forever.\r\n\r\n### Accessing Databases\r\n\r\n```ts\r\nconst auth = lown.database(\"auth\");\r\nconst alsoAuth = lown.get(\"auth\");\r\nconst viaProxy = lown.db.auth;\r\n```\r\n\r\nAll three return a `LownDatabase`.\r\n\r\n### Collections Through Lown\r\n\r\n```ts\r\nconst sessions = lown.db.auth.collection(\"sessions\");\r\n\r\nawait sessions.insert({\r\n  id: \"session-1\",\r\n  userId: \"ada\",\r\n  expiresAt: new Date(Date.now() + 3600_000).toISOString()\r\n});\r\n\r\nconst session = await sessions.get(\"session-1\");\r\n```\r\n\r\n`LownCollection` supports `insert`, `create`, `put`, `get`, `update`, `delete`, `all`, `count`, `index`, `unindex`, `where`, and `query`.\r\n\r\n### Priority Management\r\n\r\n```ts\r\nlown.priority(\"auth\", 10);\r\nconsole.log(lown.priority(\"auth\"));\r\n\r\nlown.db.logs.priority(1);\r\nconsole.log(lown.db.logs.priority());\r\n```\r\n\r\nDatabase names may contain letters, numbers, `_`, `.`, `:`, and `-`, up to 128 characters, and must start with a letter or number.\r\n\r\n### Attach and Detach\r\n\r\n```ts\r\nawait lown.attach(\"cache\", {\r\n  path: \"./data/cache.wtdb\",\r\n  password,\r\n  priority: 4\r\n});\r\n\r\nconst removed = await lown.detach(\"cache\");\r\n```\r\n\r\n`detach()` closes the underlying Woton database and removes its scheduler/model state.\r\n\r\n### Status, Stats, and Idle\r\n\r\n```ts\r\nconsole.log(lown.status());\r\nconsole.log(lown.schedulerStats());\r\nconsole.log(lown.rank());\r\n\r\nawait lown.idle();\r\nawait lown.close();\r\n```\r\n\r\n`status()` returns ranked database information plus scheduler stats. `schedulerStats()` returns queue, running, completed, failed, and rejected counts. `idle()` resolves when queued and running tasks are finished.\r\n\r\n### Explain, Rank, Run, and Schedule\r\n\r\n`explain()` lets you inspect how Lown scores a text command or explicit context without running it:\r\n\r\n```ts\r\nconst decision = lown.explain(\"auth\", {\r\n  kind: \"read\",\r\n  operation: \"get\",\r\n  collection: \"sessions\",\r\n  conditions: [{ field: \"id\", operator: \"==\", value: \"session-1\" }],\r\n  priority: 8,\r\n  tags: [\"login\"]\r\n});\r\n\r\nconsole.log(decision.score, decision.confidence);\r\n```\r\n\r\n`LownQueryContext` contains:\r\n\r\n```ts\r\ninterface LownQueryContext {\r\n  database: string;\r\n  kind: \"read\" | \"write\" | \"query\" | \"admin\" | \"maintenance\";\r\n  operation: string;\r\n  collection?: string;\r\n  conditions?: readonly QueryCondition[];\r\n  count?: boolean;\r\n  text?: string;\r\n  priority?: number;\r\n  tags?: readonly string[];\r\n}\r\n```\r\n\r\nRun a named operation:\r\n\r\n```ts\r\nawait lown.db.auth.run(\"refresh session\", async (db) => {\r\n  return db.collection(\"sessions\").get(\"session-1\");\r\n});\r\n```\r\n\r\nSchedule with a full context:\r\n\r\n```ts\r\nawait lown.db.auth.schedule(\r\n  {\r\n    kind: \"write\",\r\n    operation: \"session.touch\",\r\n    collection: \"sessions\",\r\n    conditions: [{ field: \"id\", operator: \"==\", value: \"session-1\" }],\r\n    priority: 9,\r\n    tags: [\"auth\", \"interactive\"]\r\n  },\r\n  async (db) => {\r\n    return db.collection(\"sessions\").update(\"session-1\", {\r\n      touchedAt: new Date().toISOString()\r\n    });\r\n  }\r\n);\r\n```\r\n\r\nOperation `priority` is independent from database priority. Use it for request-level urgency, for example an interactive login check versus background log ingestion.\r\n\r\n### Lown Language Queries\r\n\r\n```ts\r\nawait lown.db.auth.query('put users { \"id\": \"ada\", \"email\": \"ada@example.com\" }');\r\nawait lown.db.auth.query('from users where email == \"ada@example.com\" limit 1');\r\n```\r\n\r\nLown parses language commands into scheduling context before executing them.\r\n\r\n### Lown Maintenance Wrappers\r\n\r\n`LownDatabase` wraps maintenance and admin operations so they also go through the scheduler:\r\n\r\n```ts\r\nawait lown.db.auth.transaction(async (tx) => {\r\n  await tx.collection(\"users\").update(\"ada\", { active: true });\r\n});\r\n\r\nawait lown.db.auth.flush();\r\nawait lown.db.auth.backup(\"./backups/auth.wtdb\");\r\nawait lown.db.auth.changePassword(newPassword);\r\nconst stats = await lown.db.auth.stats();\r\n```\r\n\r\n### Queue Full Behavior\r\n\r\nIf the number of queued tasks reaches `maxQueueSize`, new tasks are rejected with `WotonValidationError: Lown scheduler queue is full.` The scheduler increments its `rejected` count. Callers should catch this and retry, shed work, or apply backpressure.\r\n\r\n## Advanced and Experimental Internals\r\n\r\nThese APIs are exported for experiments and future storage work. They are not the default `Woton.open` storage engine and should be treated as advanced/experimental.\r\n\r\n### PageManager\r\n\r\n`PageManager` manages fixed-size page files with checksums, an LRU-like clean-page cache, and a page WAL.\r\n\r\n```ts\r\nimport { PageManager } from \"@brennoleondesouza/woton\";\n\r\nconst pages = await PageManager.open({\r\n  path: \"./data/pages.bin\",\r\n  pageSize: 4096,\r\n  cachePages: 128\r\n});\r\n\r\nconst pageId = pages.allocatePage();\r\npages.writePage(pageId, Buffer.from(\"hello\"));\r\nawait pages.flush();\r\nawait pages.close();\r\n```\r\n\r\nOptions include `path`, `pageSize` (`4096`, `8192`, or `16384`), `cachePages`, optional `cipher`, and `readOnly`. Page WAL sidecars use `-pwal`.\r\n\r\n### EncryptedPageManager\r\n\r\n`EncryptedPageManager` wraps `PageManager` with AES-GCM encrypted pages and a key sidecar:\r\n\r\n```ts\r\nimport { EncryptedPageManager } from \"@brennoleondesouza/woton\";\n\r\nconst pages = await EncryptedPageManager.open({\r\n  path: \"./data/pages.enc\",\r\n  password,\r\n  pageSize: 8192\r\n});\r\n```\r\n\r\nIt uses a `-pkey` sidecar for key metadata and `-pwal` for the page WAL.\r\n\r\n### PagedBTree\r\n\r\n`PagedBTree` stores string keys mapped to pointer values:\r\n\r\n```ts\r\nimport { PagedBTree } from \"@brennoleondesouza/woton\";\n\r\nconst tree = await PagedBTree.open({\r\n  path: \"./data/users.index\",\r\n  pageSize: 4096,\r\n  encryptionPassword: password\r\n});\r\n\r\nawait tree.set(\"users\\0ada\", {\r\n  pageId: 1,\r\n  slot: 0,\r\n  checksum: 123\r\n});\r\n\r\nconst pointer = await tree.get(\"users\\0ada\");\r\n```\r\n\r\nWhen encrypted, it uses `EncryptedPageManager` and therefore the same `-pkey` and `-pwal` sidecars.\r\n\r\n### PagedRecordStore\r\n\r\n`PagedRecordStore` stores full `WotonRecord` objects in record pages and uses a `PagedBTree` ID index.\r\n\r\n```ts\r\nimport { PagedRecordStore, type WotonRecord } from \"@brennoleondesouza/woton\";\n\r\nconst store = await PagedRecordStore.open({\r\n  path: \"./data/records.store\",\r\n  pageSize: 4096,\r\n  encryptionPassword: password,\r\n  binaryCachePath: \"./data/records.cache\"\r\n});\r\n\r\nconst record: WotonRecord<{ email: string }> = {\r\n  id: \"ada\",\r\n  createdAt: new Date().toISOString(),\r\n  updatedAt: new Date().toISOString(),\r\n  email: \"ada@example.com\"\r\n};\r\n\r\nawait store.put(\"users\", record);\r\nconst found = await store.get(\"users\", \"ada\");\r\n```\r\n\r\nImportant: `PagedRecordStore.put(collection, record)` expects a full `WotonRecord` with `id`, `createdAt`, and `updatedAt`. It does not add those fields for you.\r\n\r\nSidecars:\r\n\r\n- `-rid`: record ID B+Tree.\r\n- `-bc`: default binary cache file when enabled.\r\n- `-bkey`: binary cache key metadata when encrypted.\r\n- `-pwal`: page WAL for page-backed files.\r\n- `-pkey`: encrypted page key metadata.\r\n\r\n### BinaryCache\r\n\r\n`BinaryCache` is pointer-cache infrastructure, not a document cache. It maps normalized equality-query hashes to `{ pageId, slot, checksum }` pointers. Defaults are `maxEntries: 256` and `maxFileBytes: 16KB`.\r\n\r\n```ts\r\nimport { BinaryCache, equalityQueryHash } from \"@brennoleondesouza/woton\";\n\r\nconst cache = await BinaryCache.open({\r\n  path: \"./data/records-bc\",\r\n  password,\r\n  maxEntries: 256\r\n});\r\n\r\nconst queryHash = equalityQueryHash(\"users\", \"email\", \"ada@example.com\");\r\n\r\nawait cache.put({\r\n  queryHash,\r\n  pageId: 1,\r\n  slot: 0,\r\n  checksum: 123\r\n});\r\n```\r\n\r\nEncrypted binary caches use a `-bkey` sidecar.\r\n\r\n### WCW\r\n\r\nWCW means Woton Connected Worker. It observes repeated equality lookups on `PagedRecordStore`, then warms `BinaryCache` asynchronously by scanning in a worker thread.\r\n\r\nDefaults:\r\n\r\n- `minHits`: `3`\r\n- `minRecords`: `4096`\r\n- `maxTrackedQueries`: `128`\r\n- `maxPendingTasks`: `16`\r\n- `maxConcurrentTasks`: `1`\r\n\r\nWCW warms only hot equality queries after `minHits` and skips stores below `minRecords` by default.\r\n\r\n```ts\r\nconst store = await PagedRecordStore.open({\r\n  path: \"./data/records.store\",\r\n  encryptionPassword: password,\r\n  wcw: {\r\n    minHits: 3,\r\n    minRecords: 4096\r\n  }\r\n});\r\n\r\nawait store.findFirstByEquality(\"users\", \"email\", \"ada@example.com\");\r\nawait store.waitForConnectedWorkers();\r\nconsole.log(store.connectedWorkerStats());\r\n```\r\n\r\n## Tests and Benchmarks\r\n\r\nRun the test suite:\r\n\r\n```bash\r\nnpm test\r\n```\r\n\r\nBuild:\r\n\r\n```bash\r\nnpm run build\r\n```\r\n\r\nBenchmark smoke test:\r\n\r\n```bash\r\nnpm run benchmark:smoke\r\n```\r\n\r\nFull benchmark command:\r\n\r\n```bash\r\nnpm run benchmark\r\n```\r\n\r\nPackage inspection:\r\n\r\n```bash\r\nnpm pack --dry-run\r\n```\r\n\r\nBenchmarks are synthetic. They do not replace testing with your own data shape, disk, OS, and workload.\r\n\r\nLatest local smoke benchmark used for the initial GitHub/NPM publish:\r\n\r\n- Date: April 26, 2026\r\n- Runtime: Node.js `v24.14.1`\r\n- Platform: Windows `win32/x64`\r\n- Command: `node --expose-gc benchmarks/.build/run.js --sizes=1000,10000 --get-samples=500`\r\n\r\n| Records | Insert `autosave:false` | Flush | Reopen | Query no index | Query with index | Count | File size | Final RSS / heap |\r\n| ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |\r\n| 1,000 | 67.19 ms, 14,884/s | 38.18 ms | 135.41 ms | 4.32 ms | 0.43 ms | 0.57 ms | 190.29 KB | 52.67 MB / 5.99 MB |\r\n| 10,000 | 493.27 ms, 20,273/s | 164.84 ms | 219.52 ms | 18.99 ms | 0.36 ms | 0.12 ms | 1.86 MB | 70.82 MB / 6.05 MB |\r\n\r\nCheckpoint details from that run:\r\n\r\n| Records | Serialize | Encrypt | Write | Fsync | Rename |\r\n| ---: | ---: | ---: | ---: | ---: | ---: |\r\n| 1,000 | 29.30 ms | 0.93 ms | 0.48 ms | 3.04 ms | 1.61 ms |\r\n| 10,000 | 115.93 ms | 6.58 ms | 25.74 ms | 7.54 ms | 1.68 ms |\r\n\r\n## Publishing and Repository Hygiene\r\n\r\nBefore publishing:\r\n\r\n```bash\r\nnpm test\r\nnpm run build\r\nnpm run benchmark:smoke\r\nnpm pack --dry-run\r\n```\r\n\r\nFor release checks:\r\n\r\n```bash\r\nnpm run release:check\r\n```\r\n\r\nGitHub and NPM hygiene:\r\n\r\n- Keep the package ESM-compatible with `main`, `types`, and `exports` pointing at `dist`.\r\n- Verify the npm tarball with `npm pack --dry-run`.\r\n- Keep generated benchmark output out of published artifacts unless intentionally included.\r\n- Keep README, LICENSE, and SECURITY.md accurate before publishing.\r\n- Do not publish or commit secrets.\r\n\r\nRecommended `.gitignore` and `.npmignore` exclusions for applications using Woton:\r\n\r\n```gitignore\r\n# Woton databases and runtime files\r\n*.wtdb\r\n*.wtdb-wal\r\n*.wtdb-lock\r\n*-pwal\r\n*-pkey\r\n*-rid\r\n*-bc\r\n*-bkey\r\n\r\n# Secrets and local environments\r\n.env\r\n.env.*\r\n!.env.example\r\n\r\n# Backups, benchmark data, and package tarballs\r\nbackups/\r\nbenchmarks/results/\r\n*.tgz\r\n```\r\n\r\nDo not commit database files, WAL files, lock files, sidecars, environment files, backups, benchmark data, or package tarballs.\r\n\r\n## Common Errors\r\n\r\n`Woton databases must use the .wtdb extension.`\r\n\r\nUse a path ending in `.wtdb`.\r\n\r\n`Set WOTON_PASSWORD before opening a database.`\r\n\r\nThe CLI requires the password in the `WOTON_PASSWORD` environment variable.\r\n\r\n`The database is already open or locked`\r\n\r\nAnother process may have the database open, or a stale `.wtdb-lock` exists. Close the other process first. Use `forceUnlock` only after verifying no process is using the database.\r\n\r\n`Could not decrypt`\r\n\r\nThe password is wrong, the file is corrupt, or the database/key material does not match. Check that you are opening the intended file with the intended password.\r\n\r\n`Lown scheduler queue is full.`\r\n\r\nLown rejected work because its queue reached `maxQueueSize`. Apply backpressure, retry later, raise `maxQueueSize`, or reduce submitted work.\r\n\r\n`Record is too large for one page.`\r\n\r\nThe experimental `PagedRecordStore` requires each packed record to fit in one page. Use a larger page size or reduce record size.\r\n\r\n`Invalid collection name`, `Invalid record id`, or `Invalid field path`\r\n\r\nUse the naming rules documented in [Data Model](#data-model).\r\n\r\n## License\r\n\r\nMIT.\r\n\r\n<p align=\"center\">\r\n  <img src=\"https://raw.githubusercontent.com/AnThophicous/woton/main/Images/Woton.png\" alt=\"Woton logo\" width=\"180\">\r\n</p>\r\n","readmeFilename":"README.md","_rev":"1-4aa4d17d20594b0aaf914ec17600427a"}