axiodb 22.16.0 → 22.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,41 +1,40 @@
1
1
  # AxioDB: The Embedded Database for Node.js
2
2
 
3
3
  [![npm version](https://badge.fury.io/js/axiodb.svg)](https://badge.fury.io/js/axiodb)
4
- [![npm version shields](https://img.shields.io/npm/v/axiodb?logo=npm&label=npm)](https://www.npmjs.com/package/axiodb)
5
4
  [![npm downloads total](https://img.shields.io/npm/dt/axiodb.svg)](https://www.npmjs.com/package/axiodb)
6
5
  [![npm downloads yearly](https://img.shields.io/npm/dy/axiodb.svg)](https://www.npmjs.com/package/axiodb)
7
6
  [![npm downloads weekly](https://img.shields.io/npm/dw/axiodb.svg)](https://www.npmjs.com/package/axiodb)
8
7
  [![npm downloads monthly](https://img.shields.io/npm/dm/axiodb.svg)](https://www.npmjs.com/package/axiodb)
8
+ [![jsDelivr hits](https://img.shields.io/jsdelivr/npm/hm/axiodb.svg)](https://www.jsdelivr.com/package/npm/axiodb)
9
9
  [![install size](https://img.shields.io/npm/unpacked-size/axiodb?label=install%20size)](https://www.npmjs.com/package/axiodb)
10
- [![jsDelivr hits](https://img.shields.io/jsdelivr/npm/hm/axiodb?label=jsDelivr)](https://www.jsdelivr.com/package/npm/axiodb)
11
10
  [![npm types](https://img.shields.io/npm/types/axiodb?label=types)](https://www.npmjs.com/package/axiodb)
12
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
13
-
14
- [![Push to Registry](https://github.com/nexoral/AxioDB/actions/workflows/Push.yml/badge.svg?branch=main)](https://github.com/nexoral/AxioDB/actions/workflows/Push.yml)
15
- [![CodeQL](https://github.com/nexoral/AxioDB/actions/workflows/github-code-scanning/codeql/badge.svg?branch=main)](https://github.com/nexoral/AxioDB/actions/workflows/github-code-scanning/codeql)
16
- [![Socket Security](https://socket.dev/api/badge/npm/package/axiodb)](https://socket.dev/npm/package/axiodb)
17
- [![GitHub Stars](https://img.shields.io/github/stars/nexoral/AxioDB?style=social)](https://github.com/nexoral/AxioDB)
18
-
11
+ [![Zero native deps](https://img.shields.io/badge/dependencies-0%20native-success)](https://www.npmjs.com/package/axiodb)
19
12
  [![Node.js Version](https://img.shields.io/badge/node-%3E%3D20.0.0-brightgreen)](https://nodejs.org)
20
13
  [![Tested on Node.js](https://img.shields.io/badge/tested%20on-20%20%7C%2021%20%7C%2022%20%7C%2023%20%7C%2024%20%7C%2025%20%7C%2026-blue)](https://github.com/nexoral/AxioDB/actions/workflows/Push.yml)
14
+ [![Bun tested](https://img.shields.io/badge/Bun%20tested-v1.4.0-black?logo=bun)](https://bun.sh)
15
+ [![Deno partial](https://img.shields.io/badge/Deno-partial%209%2F12-red)](https://deno.com)
21
16
  [![TypeScript](https://img.shields.io/badge/TypeScript-6.0-blue)](https://www.typescriptlang.org/)
22
- [![Zero Dependencies](https://img.shields.io/badge/dependencies-0%20native-success)](https://www.npmjs.com/package/axiodb)
23
-
24
- 👉 **[Official Documentation — axiodb.in](https://axiodb.in/)**: Full guides, API reference, and examples. This README is a quick start — see the site for everything else.
25
-
26
- ---
17
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
18
+ [![GitHub Stars](https://img.shields.io/github/stars/nexoral/AxioDB?style=social)](https://github.com/nexoral/AxioDB)
19
+ [![GitHub Forks](https://img.shields.io/github/forks/nexoral/AxioDB?style=social)](https://github.com/nexoral/AxioDB/forks)
20
+ [![Contributors](https://img.shields.io/github/contributors/nexoral/AxioDB)](https://github.com/nexoral/AxioDB/graphs/contributors)
21
+ [![Last Commit](https://img.shields.io/github/last-commit/nexoral/AxioDB)](https://github.com/nexoral/AxioDB/commits/main)
27
22
 
28
- ## What is AxioDB?
23
+ [![Push to Registry](https://github.com/nexoral/AxioDB/actions/workflows/Push.yml/badge.svg?branch=main)](https://github.com/nexoral/AxioDB/actions/workflows/Push.yml)
24
+ [![CodeQL](https://github.com/nexoral/AxioDB/actions/workflows/github-code-scanning/codeql/badge.svg?branch=main)](https://github.com/nexoral/AxioDB/actions/workflows/github-code-scanning/codeql)
29
25
 
30
- **Embedded database for Node.js, zero native deps.** Replaces SQLite, LowDB, NeDB & raw JSON files. `npm install axiodb` and you have a database — MongoDB-style queries, ACID transactions, no server, no `node-gyp`, no `electron-rebuild`.
26
+ 👉 **[Official Documentation — axiodb.in](https://axiodb.in/)**: full guides, API reference, and examples. This README is a quick start.
31
27
 
32
- **Problem:** `better-sqlite3` needs compiled binaries, `electron-rebuild` on every Electron update, per-platform builds. Plain JSON files have no query/cache/index.
28
+ ## Quick start
33
29
 
34
- **Solution:** AxioDB is a file-based document database with ACID transactions and MongoDB-style queries on plain JavaScript objects.
30
+ ```bash
31
+ npm install axiodb
32
+ ```
35
33
 
36
34
  ```javascript
37
35
  const { AxioDB } = require('axiodb');
38
- const db = new AxioDB({ GUI: true }); // Dashboard at http://localhost:27018
36
+
37
+ const db = new AxioDB({ GUI: true }); // Dashboard at http://localhost:27018
39
38
  const users = await (await db.createDB('AppDB')).createCollection('users');
40
39
 
41
40
  await users.insert({ name: 'Alice', age: 30 });
@@ -43,32 +42,86 @@ const { data } = await users.query({ age: { $gt: 25 } }).Sort({ age: -1 }).Limit
43
42
  console.log(data.documents);
44
43
  ```
45
44
 
46
- ## Objective
45
+ No server to run, no `node-gyp`, no `electron-rebuild`. Requires Node.js ≥20 (also verified on Bun v1.4.0).
46
+
47
+ ## Why AxioDB
48
+
49
+ **The problem.** `better-sqlite3` needs compiled binaries and an `electron-rebuild` on every Electron update. Plain JSON files have no query layer, no cache, and no indexes. NeDB has been unmaintained since 2016.
50
+
51
+ **The solution.** A file-based document database with MongoDB-style queries and real ACID transactions, running on plain JavaScript objects — pure JS, so it installs on any platform Node.js runs on.
52
+
53
+ ## When to use it
54
+
55
+ **Great for** Electron apps, CLI tools, embedded and local-first software, and rapid prototyping.
56
+
57
+ **Sweet spot** 10K–500K documents in a local application that wants a real database without a database server.
58
+
59
+ **Not for** datasets beyond ~500K documents, hundreds of concurrent users, JOINs, or replication/sharding — use PostgreSQL or MongoDB there. Measured limits, including per-document disk overhead and scan costs: [axiodb.in/limitations](https://axiodb.in/limitations).
60
+
61
+ ## Features
62
+
63
+ **Core** — the embedded engine
64
+
65
+ * **MongoDB-style queries** — `{ age: { $gt: 25 } }`, 19 operators, plus `.hint()` and `.findByIds()`
66
+ * **ACID transactions** — commit/rollback, savepoints, write-ahead log, crash recovery via `Transaction.recoverTransactions()`
67
+ * **Aggregation** — 19 stages including `$lookup` joins, extensible through `OperatorRegistry`
68
+ * **Indexes** — dual-write memory + disk index, served by a TTL-backed `IndexCache`
69
+ * **InMemoryCache** — per-instance, invalidated on write, tunable via `{ Cache, minTTL, maxTTL, cacheClearUp }`
70
+
71
+ **Self-hostable server** — the same engine, reachable remotely
72
+
73
+ * **Embedded** — `new AxioDB({...})` in-process, no server, the default
74
+ * **HTTP API** — port 27018, 41 documented endpoints, REST and OpenAPI
75
+ * **AxioDBCloud (TCP)** — port 27019, 32 commands, optional `TCPAuth` and TLS
76
+ * **MCP server** — port 27020, 43 tools for AI agents, bundled in the Docker image only
77
+ * **Docker** — `theankansaha/axiodb`, every surface configurable by environment variable
78
+
79
+ **Clients and apps**
80
+
81
+ * **Dashboard** — browser UI served by the HTTP surface, for browsing collections and running queries
82
+ * **AxioDBCloud client** — the TypeScript client for the TCP surface, with connection pooling and auto-reconnect
83
+ * **CLI** — Go binary for 12 platforms: interactive REPL, TLS, export/import
84
+ * **Desktop app** — Electron GUI for Linux, macOS, and Windows
85
+
86
+ ## Stability
87
+
88
+ AxioDB is **actively developed and not yet at 1.0**. Semantic versioning is followed, but treat minor releases with care and keep independent backups of your data directory.
89
+
90
+ | Tier | What it covers |
91
+ |---|---|
92
+ | **Stable** | Core CRUD, queries, indexes, transactions, aggregation, InMemoryCache |
93
+ | **Usable, evolving** | HTTP API, AxioDBCloud TCP, Dashboard, CLI, RBAC, MCP server |
94
+ | **Experimental** | Deno support, currently passing 9 of 12 engine tests — worker threads are pending |
95
+
96
+ Data at rest is stored as plain JSON files, not encrypted. Encrypt the volume if that matters to you.
97
+
98
+ ## Testing
47
99
 
48
- **Great for:** Electron, CLI tools, embedded systems, local-first apps, rapid prototyping.
100
+ The full suite is **329 tests across 14 suites**, all passing, each in an isolated child process. Benchmarked at 100,000 documents on an AMD Ryzen 5 5500U / Node v26.8.1:
49
101
 
50
- **Sweet spot:** Local applications, desktop apps, CLI tools, and services that need a simple document database without a separate database server.
102
+ | Operation | Time |
103
+ |---|---|
104
+ | Indexed query | 1–2 ms |
105
+ | `documentId` lookup | <1 ms |
106
+ | Cache hit | <1 ms |
107
+ | Insert single | ~31 ms |
108
+ | Rollback | ~8 ms |
51
109
 
52
- **Not for:** 10M+ docs, hundreds of concurrent users, JOINs, replication/sharding — use PostgreSQL/MongoDB.
110
+ Full per-suite breakdown: [axiodb.in/performance](https://axiodb.in/performance).
53
111
 
54
- ---
112
+ Run it yourself with `npm test`.
55
113
 
56
114
  ## Installation
57
115
 
58
- ### npm (Library)
116
+ ### npm — the library
59
117
 
60
118
  ```bash
61
119
  npm install axiodb
62
- # Node.js ≥20 (also verified on Bun v1.4.0)
63
120
  ```
64
121
 
65
- ### CLI & Desktop GUI (System-wide)
122
+ ### CLI and Desktop GUI — system-wide
66
123
 
67
- AxioDB ships a CLI tool and a native Desktop GUI (Electron). The installer
68
- presents a menu — **1) CLI**, **2) GUI**, **3) Both** — when run interactively,
69
- and defaults to CLI when piped (`curl | bash`).
70
-
71
- #### Quick Install (interactive)
124
+ AxioDB ships a Go CLI and an Electron desktop app for Linux, macOS, and Windows. The installer shows a menu when run interactively — **1)** CLI, **2)** GUI, **3)** both — and defaults to CLI when piped.
72
125
 
73
126
  ```bash
74
127
  # Linux / macOS
@@ -78,65 +131,43 @@ curl -fsSL https://raw.githubusercontent.com/nexoral/AxioDB/main/cli/Scripts/ins
78
131
  irm https://raw.githubusercontent.com/nexoral/AxioDB/main/cli/Scripts/install.ps1 | iex
79
132
  ```
80
133
 
81
- #### Non-Interactive (choose what you want)
82
-
83
- Set `CHOICE` before piping to select without the menu:
134
+ In scripts and CI, set `CHOICE` before piping to skip the menu:
84
135
 
85
136
  | `CHOICE` | Installs |
86
137
  |----------|----------|
87
- | `1` | CLI only |
88
- | `2` | GUI (Desktop) only |
138
+ | `1` | CLI only (also the default when piped) |
139
+ | `2` | Desktop GUI only |
89
140
  | `3` | CLI + GUI |
90
141
 
91
142
  ```bash
92
- # GUI (Desktop app) — Linux
143
+ # Desktop GUI — Linux
93
144
  curl -fsSL https://raw.githubusercontent.com/nexoral/AxioDB/main/cli/Scripts/install.sh | CHOICE=2 bash
94
145
 
95
- # GUI (Desktop app) — Windows
146
+ # Desktop GUI — Windows
96
147
  $env:CHOICE=2; irm https://raw.githubusercontent.com/nexoral/AxioDB/main/cli/Scripts/install.ps1 | iex
97
148
 
98
- # Both CLI + GUI — Linux
149
+ # CLI + GUI — Linux
99
150
  curl -fsSL https://raw.githubusercontent.com/nexoral/AxioDB/main/cli/Scripts/install.sh | CHOICE=3 bash
100
151
 
101
- # Both CLI + GUI — Windows
152
+ # CLI + GUI — Windows
102
153
  $env:CHOICE=3; irm https://raw.githubusercontent.com/nexoral/AxioDB/main/cli/Scripts/install.ps1 | iex
103
154
  ```
104
155
 
105
- You can also download installers directly from
106
- [GitHub Releases](https://github.com/nexoral/AxioDB/releases).
107
-
108
- #### Start a temporary local server
156
+ Prebuilt binaries are also on [GitHub Releases](https://github.com/nexoral/AxioDB/releases).
109
157
 
110
- The CLI can start a disposable local AxioDB server for development. It installs
111
- the published `axiodb` npm package into a temporary directory, keeps the server
112
- attached to the terminal, and deletes its data when you press `Ctrl+C`.
113
- Node.js ≥20 and npm are required; the command offers platform-specific
114
- installation help when they are missing.
158
+ ### Docker
115
159
 
116
160
  ```bash
117
- axiodb serve http # HTTP API only: http://localhost:27018/api
118
- axiodb serve tcp # TCP without authentication: axiodb://localhost:27019
119
- axiodb serve tcp-auth # TCP authentication only
120
- axiodb serve full # HTTP + authenticated TCP, GUI disabled
121
- axiodb serve tcp-auth mypassword # same, seeding admin with mypassword
161
+ docker run -d --name axiodb-server \
162
+ -p 27018:27018 -p 27019:27019 \
163
+ -v axiodb-data:/app \
164
+ theankansaha/axiodb
122
165
  ```
123
166
 
124
- The HTTP and TCP ports are fixed at `27018` and `27019`. The `http`, `tcp-auth`
125
- and `full` presets seed the shared `admin` account; pass an optional password as
126
- the second argument to choose it yourself and skip the forced first-login change.
127
- Without one, `admin/admin` is seeded and TCP refuses it until the password is
128
- changed through HTTP/GUI, since TCP has no password-change command. The `tcp`
129
- preset creates no admin account and rejects the password argument.
130
-
131
- When embedding, the same option is `AdminPassword` in the `AxioDB` constructor.
132
- In Docker it is `AXIODB_ADMIN_PASSWORD`.
133
-
167
+ All surfaces default to on, with a seeded `admin` account. See [Docker/README.md](Docker/README.md) for every environment variable.
134
168
 
135
169
  ## Basic CRUD
136
170
 
137
- CRUD means **Create, Read, Update, and Delete**. The following example creates a database and
138
- collection, then demonstrates each basic operation:
139
-
140
171
  ```javascript
141
172
  const { AxioDB } = require('axiodb');
142
173
 
@@ -144,51 +175,57 @@ const db = new AxioDB();
144
175
  const database = await db.createDB('AppDB');
145
176
  const users = await database.createCollection('users');
146
177
 
147
- // Create: insert a document. AxioDB adds documentId and updatedAt automatically.
178
+ // Create — documentId and updatedAt are added automatically.
148
179
  const created = await users.insert({ name: 'Alice', email: 'alice@example.com', age: 30 });
149
180
  const userId = created.data.documentId;
150
181
 
151
- // Read: query documents and execute the chainable reader.
182
+ // Read — the reader is chainable and lazy until .exec().
152
183
  const result = await users.query({ age: { $gte: 18 } }).exec();
153
- console.log(result.data.documents);
154
184
 
155
- // Update: update the first document matching the query.
185
+ // Update — the first document matching the query.
156
186
  await users.update({ documentId: userId }).UpdateOne({ age: 31 });
157
187
 
158
- // Delete: delete the first document matching the query.
188
+ // Delete — the first document matching the query.
159
189
  await users.delete({ documentId: userId }).deleteOne();
160
190
  ```
161
191
 
162
- Use `UpdateMany()` or `deleteMany()` when the operation should affect every matching document.
163
- Updates are flat merges; MongoDB update operators such as `$inc`, `$set`, and `$push` are not
164
- supported.
192
+ Use `UpdateMany()` and `deleteMany()` to affect every match. Updates are flat merges — MongoDB update operators like `$inc`, `$set`, and `$push` are not supported.
165
193
 
166
- ## Features
194
+ ## Running a local server
195
+
196
+ `axiodb serve` starts a disposable AxioDB for development: it installs the published npm package into a temporary directory, keeps the server attached to your terminal, and deletes the data on `Ctrl+C`. Requires Node.js ≥20 and npm.
197
+
198
+ ```bash
199
+ axiodb serve http # HTTP API only
200
+ axiodb serve tcp # TCP, no authentication
201
+ axiodb serve tcp-auth mypassword # authenticated TCP
202
+ axiodb serve full # HTTP + authenticated TCP
203
+ ```
204
+
205
+ Ports are fixed at `27018` (HTTP) and `27019` (TCP). The optional password seeds the shared `admin` account; `tcp-auth` requires one, since it has no HTTP surface to rotate the password through. Embedding uses the `AdminPassword` constructor option, and Docker uses `AXIODB_ADMIN_PASSWORD`.
167
206
 
168
- * **Zero native deps** — pure JS, no `node-gyp`, no `electron-rebuild`
169
- * **MongoDB-style queries** — `{ age: { $gt: 25 } }`, 19 operators + `hint()` + `findByIds()`
170
- * **ACID transactions** — `savepoint`/`rollbackTo`/`WAL`, crashes recover via `Transaction.recoverTransactions()`
171
- * **Aggregation** — 19 stages, `$lookup` joins, `OperatorRegistry` custom ops
172
- * **InMemoryCache + indexes** — per-instance cache, dual-write, auto `IndexCache`
173
- * **Configurable cache** — `{ Cache, minTTL, maxTTL, cacheClearUp }` controls the per-instance InMemoryCache (default `5–15m` randomized TTL, `Cache: false` disables it) → [axiodb.in/api-reference](https://axiodb.in/api-reference)
174
- * **Ports:** GUI `27018` · TCP `27019` `AxioDBCloud` · MCP `27020` Docker-only
207
+ ## Documentation
175
208
 
176
- > **Docs:** `axiodb.in` is the single source — this README is a quick start only.
209
+ | Page | Covers |
210
+ |---|---|
211
+ | [axiodb.in](https://axiodb.in/) | Documentation home |
212
+ | [Create a database](https://axiodb.in/create-database) | Constructor options, collections, indexes |
213
+ | [API reference](https://axiodb.in/api-reference) | Full method signatures and types |
214
+ | [AxioDBCloud](https://axiodb.in/cloud) | Remote TCP access, TLS, authentication |
215
+ | [Server API](https://axiodb.in/server-api) | The 41 HTTP endpoints |
216
+ | [CLI](https://axiodb.in/cli) | Go CLI reference |
217
+ | [Docker](https://axiodb.in/docker) | Image and environment variables |
218
+ | [Security](https://axiodb.in/security) | RBAC roles, password policy, rate limits |
219
+ | [MCP server](https://axiodb.in/mcp-server) | AI agent integration |
220
+ | [Limitations](https://axiodb.in/limitations) | Scope and measured limits |
221
+ | [Comparison](https://axiodb.in/comparison) | Against SQLite, JSON files, lowdb, nedb, better-sqlite3 |
177
222
 
178
- * **AxioDBCloud (TCP)** — remote `AxioDBCloud` client, 30 commands, optional `TCPAuth` + `TLS` → [axiodb.in/cloud](https://axiodb.in/cloud)
179
- * **CLI (Go)** — `axiodb document insert/query` `--hint` `find-by-ids` `transaction begin/commit` `user change-password` (HTTP `27018` for management, TCP `27019` for data) → [axiodb.in/cli](https://axiodb.in/cli)
180
- * **Docker** — `theankansaha/axiodb` `AXIODB_GUI/TCP/MCP` `27018/27019/27020` → [axiodb.in/docker](https://axiodb.in/docker)
181
- * **MCP Server** — 43 tools `axiodb_login` → `sessionId` + `withConfirmation` `Docker/mcp/tools/*.js` → [axiodb.in/mcp-server](https://axiodb.in/mcp-server)
182
- * **GUI + RBAC** — `Super Admin/Admin/View`, `mustChangePassword`, `LoginRateLimiter` → [axiodb.in/security](https://axiodb.in/security)
183
- * **API & Types** — `Document/src/data/serverApi.ts` 41 endpoints `openapi.json`, TS 6.0 strict → [axiodb.in/api-reference](https://axiodb.in/api-reference) [axiodb.in/server-api](https://axiodb.in/server-api)
223
+ ## Contributing, security & license
184
224
 
185
- ---
225
+ Contributions are welcome — see [CONTRIBUTING.md](CONTRIBUTING.md) and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md).
186
226
 
187
- ## Contributing, License & Support
227
+ Security issues: report privately through [GitHub Security Advisories](https://github.com/nexoral/AxioDB/security/advisories/new). Policy details in [SECURITY.md](SECURITY.md).
188
228
 
189
- * **Contributing:** see [CONTRIBUTING.md](CONTRIBUTING.md) + [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
190
- * **Security:** see [SECURITY.md](SECURITY.md) — `admin/admin` must change password, report via GitHub Security Advisories
191
- * **License:** MIT — [LICENSE](LICENSE)
192
- * **Support:** ⭐ star, 🐛 issues, 💡 discussions — [https://github.com/nexoral/AxioDB](https://github.com/nexoral/AxioDB) · [sponsor](https://github.com/sponsors/AnkanSaha)
229
+ Released under the [MIT License](LICENSE). Author: Ankan Saha.
193
230
 
194
- **Author:** Ankan Saha · **Docs:** `cd Document && npm run dev` `http://localhost:5173`
231
+ Support the project with a ⭐ on [GitHub](https://github.com/nexoral/AxioDB) or through [GitHub Sponsors](https://github.com/sponsors/AnkanSaha).
@@ -10,6 +10,7 @@ import { InMemoryCache } from "../Memory/memory.operation";
10
10
  * @param {string} options.CustomPath - Custom path for database storage. Defaults to current directory.
11
11
  * @param {boolean} options.TCP - Enable/disable TCP server (port 27019). Defaults to false.
12
12
  * @param {boolean} options.TCPAuth - Require username/password authentication (same RBAC users as the GUI) for TCP connections. Defaults to false.
13
+ * @param {string} options.AdminPassword - Password the admin account is seeded with when the config database is first created, clearing its forced password change so it can authenticate over TCP immediately. Defaults to the built-in admin/admin, which TCP rejects until rotated through the HTTP/GUI. Ignored once the users collection already exists.
13
14
  * @param {boolean} options.Cache - Enable the built-in InMemoryCache shared across the whole instance. Defaults to true.
14
15
  * @param {number} options.minTTL - Minimum cache entry lifetime in minutes. Defaults to 5.
15
16
  * @param {number} options.maxTTL - Maximum cache entry lifetime in minutes. Defaults to 15.
@@ -41,6 +41,7 @@ const memory_operation_1 = require("../Memory/memory.operation");
41
41
  * @param {string} options.CustomPath - Custom path for database storage. Defaults to current directory.
42
42
  * @param {boolean} options.TCP - Enable/disable TCP server (port 27019). Defaults to false.
43
43
  * @param {boolean} options.TCPAuth - Require username/password authentication (same RBAC users as the GUI) for TCP connections. Defaults to false.
44
+ * @param {string} options.AdminPassword - Password the admin account is seeded with when the config database is first created, clearing its forced password change so it can authenticate over TCP immediately. Defaults to the built-in admin/admin, which TCP rejects until rotated through the HTTP/GUI. Ignored once the users collection already exists.
44
45
  * @param {boolean} options.Cache - Enable the built-in InMemoryCache shared across the whole instance. Defaults to true.
45
46
  * @param {number} options.minTTL - Minimum cache entry lifetime in minutes. Defaults to 5.
46
47
  * @param {number} options.maxTTL - Maximum cache entry lifetime in minutes. Defaults to 15.
@@ -14,9 +14,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  exports.default = createAxioDBControlServer;
16
16
  const fastify_1 = __importDefault(require("fastify"));
17
- const cors_1 = __importDefault(require("@fastify/cors"));
18
- const static_1 = __importDefault(require("@fastify/static"));
19
- const cookie_1 = __importDefault(require("@fastify/cookie"));
17
+ const Cors_utils_1 = __importDefault(require("../../utility/Cors.utils"));
18
+ const StaticFile_utils_1 = __importDefault(require("../../utility/StaticFile.utils"));
19
+ const Cookie_utils_1 = require("../../utility/Cookie.utils");
20
20
  const path_1 = __importDefault(require("path"));
21
21
  const fs_1 = __importDefault(require("fs"));
22
22
  const keys_1 = require("./keys");
@@ -44,17 +44,10 @@ function createAxioDBControlServer(AxioDBInstance_1) {
44
44
  });
45
45
  }
46
46
  }));
47
- yield AxioDBControlServer.register(cors_1.default, {
48
- origin: keys_1.CORS_CONFIG.ORIGIN,
49
- methods: keys_1.CORS_CONFIG.METHODS,
50
- allowedHeaders: keys_1.CORS_CONFIG.ALLOWED_HEADERS,
51
- credentials: keys_1.CORS_CONFIG.ALLOW_CREDENTIALS,
52
- exposedHeaders: keys_1.CORS_CONFIG.EXPOSED_HEADERS,
53
- maxAge: keys_1.CORS_CONFIG.MAX_AGE, // preflight cache duration
54
- });
47
+ (0, Cors_utils_1.default)(AxioDBControlServer);
55
48
  // Cookie support for session-based authentication (no signing secret needed -
56
49
  // the cookie value is meaningless without a matching entry in SessionStore's map)
57
- yield AxioDBControlServer.register(cookie_1.default);
50
+ (0, Cookie_utils_1.registerCookies)(AxioDBControlServer);
58
51
  AxioDBControlServer.addHook("onSend", (_request, reply) => __awaiter(this, void 0, void 0, function* () {
59
52
  for (const [header, value] of Object.entries(keys_1.SECURITY_HEADERS)) {
60
53
  reply.header(header, value);
@@ -70,11 +63,7 @@ function createAxioDBControlServer(AxioDBInstance_1) {
70
63
  }
71
64
  });
72
65
  if (guiEnabled) {
73
- yield AxioDBControlServer.register(static_1.default, {
74
- root: keys_1.staticPath,
75
- prefix: "/",
76
- decorateReply: false,
77
- });
66
+ (0, StaticFile_utils_1.default)(AxioDBControlServer, { root: keys_1.staticPath });
78
67
  AxioDBControlServer.get("/", (request, reply) => __awaiter(this, void 0, void 0, function* () {
79
68
  const indexPath = path_1.default.join(keys_1.staticPath, "index.html");
80
69
  const stream = fs_1.default.createReadStream(indexPath);
@@ -0,0 +1,34 @@
1
+ import type { FastifyReply, FastifyRequest } from "fastify";
2
+ export interface CookieSerializeOptions {
3
+ path?: string;
4
+ domain?: string;
5
+ maxAge?: number;
6
+ expires?: Date;
7
+ httpOnly?: boolean;
8
+ secure?: boolean | "auto";
9
+ sameSite?: "lax" | "strict" | "none" | boolean;
10
+ }
11
+ export declare function parseCookieHeader(header: string): Record<string, string>;
12
+ export declare function serializeCookie(name: string, value: string, options?: CookieSerializeOptions): string;
13
+ declare module "fastify" {
14
+ interface FastifyRequest {
15
+ cookies: Record<string, string>;
16
+ }
17
+ interface FastifyReply {
18
+ setCookie(name: string, value: string, options?: CookieSerializeOptions): FastifyReply;
19
+ clearCookie(name: string, options?: Pick<CookieSerializeOptions, "path" | "domain">): FastifyReply;
20
+ }
21
+ }
22
+ /**
23
+ * Populates `request.cookies` and attaches the `setCookie` / `clearCookie`
24
+ * helpers to each reply.
25
+ *
26
+ * The reply helpers are assigned per-request rather than through `decorate`:
27
+ * a Fastify decorator lands on the instance, not on every reply object, so
28
+ * `reply.setCookie` would be undefined in route handlers.
29
+ */
30
+ export declare function registerCookies(fastify: FastifyInstanceLike): void;
31
+ interface FastifyInstanceLike {
32
+ addHook(name: string, hook: (request: FastifyRequest, reply: FastifyReply, done: (err?: Error) => void) => void): void;
33
+ }
34
+ export {};
@@ -0,0 +1,94 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseCookieHeader = parseCookieHeader;
4
+ exports.serializeCookie = serializeCookie;
5
+ exports.registerCookies = registerCookies;
6
+ /**
7
+ * Cookie support for the control server.
8
+ *
9
+ * Sessions are server-side: the cookie carries only a `sid` that must match an
10
+ * entry in `SessionStore`, so the value itself needs no integrity protection.
11
+ * What this has to get right is correct parsing - a mis-split cookie would
12
+ * silently drop a valid session or authenticate the wrong one.
13
+ */
14
+ const COOKIE_PAIR = /; */;
15
+ function decode(value) {
16
+ if (!value.includes("%") && !value.includes("+")) {
17
+ return value;
18
+ }
19
+ try {
20
+ return decodeURIComponent(value);
21
+ }
22
+ catch (_a) {
23
+ // A malformed escape must not fail the whole request - hand back the raw
24
+ // value so it simply misses the session lookup instead.
25
+ return value;
26
+ }
27
+ }
28
+ function parseCookieHeader(header) {
29
+ const result = {};
30
+ for (const pair of header.split(COOKIE_PAIR)) {
31
+ const index = pair.indexOf("=");
32
+ if (index < 0) {
33
+ continue;
34
+ }
35
+ const name = pair.slice(0, index).trim();
36
+ // First occurrence wins, matching how browsers and the plugin behave.
37
+ if (name.length === 0 || name in result) {
38
+ continue;
39
+ }
40
+ result[name] = decode(pair.slice(index + 1).trim());
41
+ }
42
+ return result;
43
+ }
44
+ function serializeCookie(name, value, options = {}) {
45
+ var _a;
46
+ let cookie = `${name}=${encodeURIComponent(value)}`;
47
+ if (options.maxAge !== undefined) {
48
+ cookie += `; Max-Age=${Math.floor(options.maxAge)}`;
49
+ }
50
+ if (options.domain) {
51
+ cookie += `; Domain=${options.domain}`;
52
+ }
53
+ cookie += `; Path=${(_a = options.path) !== null && _a !== void 0 ? _a : "/"}`;
54
+ if (options.expires) {
55
+ cookie += `; Expires=${options.expires.toUTCString()}`;
56
+ }
57
+ if (options.httpOnly) {
58
+ cookie += "; HttpOnly";
59
+ }
60
+ if (options.secure) {
61
+ cookie += "; Secure";
62
+ }
63
+ if (options.sameSite && options.sameSite !== true) {
64
+ const mode = String(options.sameSite);
65
+ cookie += `; SameSite=${mode.charAt(0).toUpperCase()}${mode.slice(1)}`;
66
+ }
67
+ return cookie;
68
+ }
69
+ /**
70
+ * Populates `request.cookies` and attaches the `setCookie` / `clearCookie`
71
+ * helpers to each reply.
72
+ *
73
+ * The reply helpers are assigned per-request rather than through `decorate`:
74
+ * a Fastify decorator lands on the instance, not on every reply object, so
75
+ * `reply.setCookie` would be undefined in route handlers.
76
+ */
77
+ function registerCookies(fastify) {
78
+ fastify.addHook("onRequest", (request, reply, done) => {
79
+ const header = request.headers.cookie;
80
+ request.cookies = header ? parseCookieHeader(header) : {};
81
+ reply.setCookie = (name, value, options = {}) => {
82
+ // "auto" follows the connection, so a plain-HTTP dev server still receives a
83
+ // usable cookie while any TLS-terminated deployment gets the secure flag.
84
+ const secure = options.secure === "auto" ? request.protocol === "https" : options.secure;
85
+ reply.header("Set-Cookie", serializeCookie(name, value, Object.assign(Object.assign({}, options), { secure })));
86
+ return reply;
87
+ };
88
+ reply.clearCookie = (name, options = {}) => {
89
+ reply.header("Set-Cookie", serializeCookie(name, "", Object.assign(Object.assign({}, options), { maxAge: 0, expires: new Date(0) })));
90
+ return reply;
91
+ };
92
+ done();
93
+ });
94
+ }
@@ -0,0 +1,13 @@
1
+ import type { FastifyInstance } from "fastify";
2
+ /**
3
+ * CORS for the control server, written against the same `CORS_CONFIG` the
4
+ * `@fastify/cors` plugin used.
5
+ *
6
+ * Two behaviours are deliberate:
7
+ * - The origin is echoed back verbatim, never `*`. Browsers reject a wildcard
8
+ * on a credentialed (cookie-bearing) request, and the GUI's session cookie is
9
+ * exactly that.
10
+ * - `Vary: Origin` is always sent, so a shared cache cannot serve one origin's
11
+ * response to another.
12
+ */
13
+ export default function registerCors(fastify: FastifyInstance): void;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.default = registerCors;
13
+ const keys_1 = require("../server/config/keys");
14
+ /**
15
+ * CORS for the control server, written against the same `CORS_CONFIG` the
16
+ * `@fastify/cors` plugin used.
17
+ *
18
+ * Two behaviours are deliberate:
19
+ * - The origin is echoed back verbatim, never `*`. Browsers reject a wildcard
20
+ * on a credentialed (cookie-bearing) request, and the GUI's session cookie is
21
+ * exactly that.
22
+ * - `Vary: Origin` is always sent, so a shared cache cannot serve one origin's
23
+ * response to another.
24
+ */
25
+ function registerCors(fastify) {
26
+ fastify.addHook("onRequest", (request, reply) => __awaiter(this, void 0, void 0, function* () {
27
+ const origin = request.headers.origin;
28
+ if (origin && keys_1.CORS_CONFIG.ORIGIN.includes(origin)) {
29
+ reply.header("Access-Control-Allow-Origin", origin);
30
+ reply.header("Access-Control-Allow-Credentials", String(keys_1.CORS_CONFIG.ALLOW_CREDENTIALS));
31
+ reply.header("Access-Control-Expose-Headers", keys_1.CORS_CONFIG.EXPOSED_HEADERS.join(", "));
32
+ }
33
+ reply.header("Vary", "Origin");
34
+ if (request.method !== "OPTIONS") {
35
+ return;
36
+ }
37
+ // Echo back what the browser asked for; falling back to our list would
38
+ // reject a legitimate request carrying an extra standard header.
39
+ const requested = request.headers["access-control-request-headers"];
40
+ reply.header("Access-Control-Allow-Headers", requested !== null && requested !== void 0 ? requested : keys_1.CORS_CONFIG.ALLOWED_HEADERS.join(", "));
41
+ reply.header("Access-Control-Allow-Methods", keys_1.CORS_CONFIG.METHODS.join(", "));
42
+ reply.header("Access-Control-Max-Age", String(keys_1.CORS_CONFIG.MAX_AGE));
43
+ // A preflight never reaches a route, so answer it here rather than needing
44
+ // a throwaway OPTIONS handler on every router.
45
+ yield reply.code(204).send();
46
+ }));
47
+ }
@@ -0,0 +1,15 @@
1
+ import type { FastifyInstance } from "fastify";
2
+ /**
3
+ * Maps a URL path to a file inside `root`, or returns null when the result would
4
+ * escape it. Any traversal sequence resolves outside `root` and is rejected here.
5
+ */
6
+ export declare function resolveWithinRoot(root: string, urlPath: string): string | null;
7
+ export interface StaticOptions {
8
+ root: string;
9
+ }
10
+ /**
11
+ * Serves files from `root` for GET/HEAD requests that no route has claimed.
12
+ * Registered as an `onRequest` hook rather than a wildcard route so it composes
13
+ * with the existing router instead of competing with it.
14
+ */
15
+ export default function registerStatic(fastify: FastifyInstance, options: StaticOptions): void;
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.resolveWithinRoot = resolveWithinRoot;
16
+ exports.default = registerStatic;
17
+ const fs_1 = __importDefault(require("fs"));
18
+ const path_1 = __importDefault(require("path"));
19
+ /**
20
+ * Static file serving for the control server.
21
+ *
22
+ * The job was never just "read a file" - it was mapping a request path to a file
23
+ * inside a fixed root without ever escaping it. That containment is the part
24
+ * worth keeping explicit, so it is written out here rather than left implicit:
25
+ * every candidate path is resolved and compared against the root, and the
26
+ * `onRequest` dot-dot guard in `config/server.ts` is a second layer in front.
27
+ */
28
+ const MIME_TYPES = {
29
+ ".html": "text/html; charset=utf-8",
30
+ ".js": "text/javascript; charset=utf-8",
31
+ ".mjs": "text/javascript; charset=utf-8",
32
+ ".css": "text/css; charset=utf-8",
33
+ ".json": "application/json; charset=utf-8",
34
+ ".svg": "image/svg+xml",
35
+ ".png": "image/png",
36
+ ".jpg": "image/jpeg",
37
+ ".jpeg": "image/jpeg",
38
+ ".gif": "image/gif",
39
+ ".ico": "image/x-icon",
40
+ ".webp": "image/webp",
41
+ ".woff": "font/woff",
42
+ ".woff2": "font/woff2",
43
+ ".ttf": "font/ttf",
44
+ ".eot": "application/vnd.ms-fontobject",
45
+ ".map": "application/json; charset=utf-8",
46
+ ".txt": "text/plain; charset=utf-8",
47
+ ".wasm": "application/wasm",
48
+ };
49
+ function contentTypeFor(filePath) {
50
+ var _a;
51
+ return (_a = MIME_TYPES[path_1.default.extname(filePath).toLowerCase()]) !== null && _a !== void 0 ? _a : "application/octet-stream";
52
+ }
53
+ /**
54
+ * Maps a URL path to a file inside `root`, or returns null when the result would
55
+ * escape it. Any traversal sequence resolves outside `root` and is rejected here.
56
+ */
57
+ function resolveWithinRoot(root, urlPath) {
58
+ let decoded;
59
+ try {
60
+ decoded = decodeURIComponent(urlPath);
61
+ }
62
+ catch (_a) {
63
+ return null;
64
+ }
65
+ const candidate = path_1.default.resolve(root, `.${path_1.default.posix.normalize(`/${decoded}`)}`);
66
+ const rootWithSep = root.endsWith(path_1.default.sep) ? root : root + path_1.default.sep;
67
+ if (candidate !== root && !candidate.startsWith(rootWithSep)) {
68
+ return null;
69
+ }
70
+ return candidate;
71
+ }
72
+ /**
73
+ * Serves files from `root` for GET/HEAD requests that no route has claimed.
74
+ * Registered as an `onRequest` hook rather than a wildcard route so it composes
75
+ * with the existing router instead of competing with it.
76
+ */
77
+ function registerStatic(fastify, options) {
78
+ const root = path_1.default.resolve(options.root);
79
+ fastify.addHook("onRequest", (request, reply) => __awaiter(this, void 0, void 0, function* () {
80
+ var _a;
81
+ if (request.method !== "GET" && request.method !== "HEAD") {
82
+ return;
83
+ }
84
+ // A reply already sent means a route or an earlier hook claimed the request.
85
+ if (reply.sent) {
86
+ return;
87
+ }
88
+ const urlPath = ((_a = request.raw.url) !== null && _a !== void 0 ? _a : "/").split("?", 1)[0];
89
+ const target = resolveWithinRoot(root, urlPath);
90
+ if (target === null) {
91
+ return;
92
+ }
93
+ let stats;
94
+ try {
95
+ stats = yield fs_1.default.promises.stat(target);
96
+ }
97
+ catch (_b) {
98
+ return;
99
+ }
100
+ // Directories fall through so the app can serve its own index route.
101
+ if (!stats.isFile()) {
102
+ return;
103
+ }
104
+ const type = contentTypeFor(target);
105
+ reply.header("Content-Type", type);
106
+ reply.header("Content-Length", String(stats.size));
107
+ if (request.method === "HEAD") {
108
+ yield reply.code(200).send();
109
+ return;
110
+ }
111
+ yield reply.type(type).send(fs_1.default.createReadStream(target));
112
+ }));
113
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "axiodb",
3
- "version": "22.16.0",
3
+ "version": "22.18.0",
4
4
  "description": "The embedded database for Node.js. Replaces SQLite, LowDB, NeDB & raw JSON files with MongoDB-style queries, ACID transactions, zero native dependencies, and built-in InMemoryCache. No compilation, no platform issues. Perfect for desktop apps, CLI tools, and embedded systems.",
5
5
  "main": "./lib/config/DB.js",
6
6
  "types": "./lib/config/DB.d.ts",
@@ -59,10 +59,7 @@
59
59
  "url": "https://github.com/nexoral/AxioDB"
60
60
  },
61
61
  "dependencies": {
62
- "@fastify/cookie": "^10.0.1",
63
- "@fastify/cors": "^11.0.1",
64
62
  "@fastify/multipart": "^9.0.3",
65
- "@fastify/static": "^10.1.3",
66
63
  "fastify": "^5.8.5",
67
64
  "tar": "^7.5.16"
68
65
  },