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 +135 -98
- package/lib/Services/Indexation.operation.d.ts +1 -0
- package/lib/Services/Indexation.operation.js +1 -0
- package/lib/server/config/server.js +6 -17
- package/lib/utility/Cookie.utils.d.ts +34 -0
- package/lib/utility/Cookie.utils.js +94 -0
- package/lib/utility/Cors.utils.d.ts +13 -0
- package/lib/utility/Cors.utils.js +47 -0
- package/lib/utility/StaticFile.utils.d.ts +15 -0
- package/lib/utility/StaticFile.utils.js +113 -0
- package/package.json +1 -4
package/README.md
CHANGED
|
@@ -1,41 +1,40 @@
|
|
|
1
1
|
# AxioDB: The Embedded Database for Node.js
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/js/axiodb)
|
|
4
|
-
[](https://www.npmjs.com/package/axiodb)
|
|
5
4
|
[](https://www.npmjs.com/package/axiodb)
|
|
6
5
|
[](https://www.npmjs.com/package/axiodb)
|
|
7
6
|
[](https://www.npmjs.com/package/axiodb)
|
|
8
7
|
[](https://www.npmjs.com/package/axiodb)
|
|
8
|
+
[](https://www.jsdelivr.com/package/npm/axiodb)
|
|
9
9
|
[](https://www.npmjs.com/package/axiodb)
|
|
10
|
-
[](https://www.jsdelivr.com/package/npm/axiodb)
|
|
11
10
|
[](https://www.npmjs.com/package/axiodb)
|
|
12
|
-
[](https://github.com/nexoral/AxioDB/actions/workflows/Push.yml)
|
|
15
|
-
[](https://github.com/nexoral/AxioDB/actions/workflows/github-code-scanning/codeql)
|
|
16
|
-
[](https://socket.dev/npm/package/axiodb)
|
|
17
|
-
[](https://github.com/nexoral/AxioDB)
|
|
18
|
-
|
|
11
|
+
[](https://www.npmjs.com/package/axiodb)
|
|
19
12
|
[](https://nodejs.org)
|
|
20
13
|
[](https://github.com/nexoral/AxioDB/actions/workflows/Push.yml)
|
|
14
|
+
[](https://bun.sh)
|
|
15
|
+
[](https://deno.com)
|
|
21
16
|
[](https://www.typescriptlang.org/)
|
|
22
|
-
[](https://opensource.org/licenses/MIT)
|
|
18
|
+
[](https://github.com/nexoral/AxioDB)
|
|
19
|
+
[](https://github.com/nexoral/AxioDB/forks)
|
|
20
|
+
[](https://github.com/nexoral/AxioDB/graphs/contributors)
|
|
21
|
+
[](https://github.com/nexoral/AxioDB/commits/main)
|
|
27
22
|
|
|
28
|
-
|
|
23
|
+
[](https://github.com/nexoral/AxioDB/actions/workflows/Push.yml)
|
|
24
|
+
[](https://github.com/nexoral/AxioDB/actions/workflows/github-code-scanning/codeql)
|
|
29
25
|
|
|
30
|
-
**
|
|
26
|
+
👉 **[Official Documentation — axiodb.in](https://axiodb.in/)**: full guides, API reference, and examples. This README is a quick start.
|
|
31
27
|
|
|
32
|
-
|
|
28
|
+
## Quick start
|
|
33
29
|
|
|
34
|
-
|
|
30
|
+
```bash
|
|
31
|
+
npm install axiodb
|
|
32
|
+
```
|
|
35
33
|
|
|
36
34
|
```javascript
|
|
37
35
|
const { AxioDB } = require('axiodb');
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
**
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
122
|
+
### CLI and Desktop GUI — system-wide
|
|
66
123
|
|
|
67
|
-
AxioDB ships a CLI
|
|
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
|
-
|
|
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
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
axiodb
|
|
120
|
-
axiodb
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
185
|
+
// Update — the first document matching the query.
|
|
156
186
|
await users.update({ documentId: userId }).UpdateOne({ age: 31 });
|
|
157
187
|
|
|
158
|
-
// Delete
|
|
188
|
+
// Delete — the first document matching the query.
|
|
159
189
|
await users.delete({ documentId: userId }).deleteOne();
|
|
160
190
|
```
|
|
161
191
|
|
|
162
|
-
Use `UpdateMany()`
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
18
|
-
const
|
|
19
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
},
|