eventdbx 1.5.5

An event-sourced, key-value, write-side database system.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
# EventDBX

You'll likely enjoy this database system. Worry less about how you structure your data and focus more on your business logic.

## Overview

EventDBX is an event-sourced, key-value, write-side database system designed to provide immutable, append-only storage for events across various domains. It is ideal for applications requiring detailed audit trails for compliance, complex business processes involving states, and high data integrity levels.

## Getting Started

Follow the steps below to spin up EventDBX locally. All commands are expected to run from the repository root unless stated otherwise.

1. **Install prerequisites**

   - Rust toolchain (edition 2024) via [`rustup`](https://rustup.rs/)
   - `rocksdb` is vendored through the Rust crate, so no extra native packages are required.

2. **Clone and build**

   ```bash
   git clone https://github.com/thachp/eventdbx.git
   cd eventdbx
   cargo build
   ```

3. **(Optional) Run checks**

   ```bash
   cargo fmt -- --check
   cargo check
   cargo test    # if you add tests locally
   ```

4. **Create the initial configuration**
   Supply a 32-byte data-encryption key (DEK) encoded as Base64. You can generate one on macOS/Linux with `openssl rand -base64 32`.

   ```bash
   cargo run -- config --dek "$(openssl rand -base64 32)"
   ```

5. **Start the server**

   ```bash
   cargo run -- start --foreground
   ```

   - Omit `--foreground` to daemonise the process.
   - Use `--data-dir <path>` to override the default `./.eventdbx` directory.
   - Restriction (schema enforcement) is enabled by default; disable it with `--restrict=false` if you need a permissive environment.
   - Choose the HTTP surface with `--api graphQL`, `--api rest`, or `--api both` (default).

6. **Define a schema (recommended when running in restricted mode)**

   ```bash
   cargo run -- schema create \
     --aggregate patient \
     --events patient-added,patient-updated \
     --snapshot-threshold 100
   ```

7. **Issue a token for CLI access**

   ```bash
   cargo run -- token generate \
     --group admin \
     --user jane \
     --expiration 3600
   ```

8. **Append an event**
   ```bash
   cargo run -- aggregate apply \
     --aggregate patient \
     --aggregate-id p-001 \
     --event patient-added \
     --field name="Jane Doe" \
     --field status=active
   ```

You now have a working EventDBX instance with an initial aggregate. Explore the [Command-Line Reference](#command-line-reference) for the full set of supported operations.

## Features

- **Flexible JSON payloads**: Events accept arbitrary JSON payloads; scalar values are normalized into strings for state tracking, while structured objects remain fully queryable.
- **Immutable Data Structure**: Once data is entered into EventDBX, it becomes immutable, meaning it cannot be altered or deleted. This characteristic is crucial for applications where the accuracy and traceability of historical data are paramount, such as medical records, financial transactions, and supply chain management. Data can be archived, moving from short-term to long-term storage, but cannot be deleted.
- **Event Sourcing and Replay**: EventDBX is built on the principle of event sourcing, storing all changes to the data as a sequence of events. This allows for the complete replay of events to reconstruct the database's state at any point in time, thereby enhancing data recovery and audit capabilities. Unlike traditional databases that execute update statements to modify data, this system is event-driven. Aggregate state changes are defined in the event object, allowing these events to be replayed at any time to reconstruct the aggregate's current state.
- **Merkle Tree Integration**: Each aggregate in EventDBX is associated with a Merkle tree of events, enabling verification of data integrity. The Merkle tree structure ensures that any data tampering can be detected, offering an additional security layer against data corruption.
- **Built-in Audit Trails**: EventDBX automatically maintains a comprehensive audit trail of all transactions, a feature invaluable for meeting compliance and regulatory requirements. It provides transparent and tamper-evident records. During audits, administrators can issue specific tokens to auditors to access and review specific aggregate instances and all relevant events associated with those instances.
- **Security with Token-Based Authorization**: EventDBX implements token-based authorization to manage database access. This approach allows for precise control over who can access and modify data, protecting against unauthorized changes. Unlike systems where a single application user account performs CRUD operations, EventDBX mandates that each user of the application obtains their own access token with a specific time horizon. For example, a doctor will receive their own access token, linked to their identifier (IAM-managed outside EventDBX), generated by the system for each event they handle, rather than having a single application user manage all CRUD operations. This ensures the system can accurately track that it was the doctor who made changes to an aggregate state, not the application.
- **Encrypted Payloads & Secrets at Rest**: Event payloads, aggregate snapshots, and `tokens.json` are encrypted transparently when a DEK is configured. Metadata such as aggregate identifiers, versions, and Merkle roots remain readable so plugins, replication, and integrity checks keep working without additional configuration.
- **Powered by RocksDB and Rust**: At its core, EventualDB utilizes RocksDB for storage, taking advantage of its high performance and efficiency. The system is developed in Rust, known for its safety, efficiency, and concurrency capabilities, ensuring that EventualDB is both rapid and dependable.

## Command-Line Reference

EventDBX ships a single `eventdbx` binary. Every command accepts an optional `--config <path>` to point at an alternate configuration file.

### Server lifecycle

- `eventdbx start [--port <u16>] [--data-dir <path>] [--foreground] [--restrict | --restrict=false]`  
  Launches the server. Schema validation is enforced by default; pass `--restrict=false` to run in permissive mode.
- `eventdbx stop`  
  Stops the running daemon referenced by the PID file.
- `eventdbx status`  
  Prints the current port, PID, uptime, and whether restriction is enabled.
- `eventdbx restart [start options…]`  
  Stops the existing daemon (if any) and restarts it with the provided options.
- `eventdbx destroy [--yes]`  
  Removes the PID file, data directory, and configuration file after confirmation (or immediately with `--yes`).

### Configuration

- `eventdbx config [--port <u16>] [--data-dir <path>] [--master-key <secret>] [--dek <secret>] [--memory-threshold <usize>] [--list-page-size <usize>] [--page-limit <usize>] [--plugin-max-attempts <u32>]`  
  Persists configuration updates. The first invocation must include both `--master-key` and `--dek`. `--list-page-size` sets the default page size for aggregate listings (default 10), `--page-limit` caps any requested page size across list and event endpoints (default 1000, alias `--event-page-limit`), and `--plugin-max-attempts` controls how many retries are attempted before an event is marked dead (default 10).

### Tokens

- `eventdbx token generate --group <name> --user <name> [--expiration <secs>] [--limit <writes>] [--keep-alive]`  
  Issues a new token tied to a Unix-style group and user.
- `eventdbx token list`  
  Lists all tokens with status, expiry, and remaining writes.
- `eventdbx token revoke --token <value>`  
  Revokes a token immediately.
- `eventdbx token refresh --token <value> [--expiration <secs>] [--limit <writes>]`  
  Extends the lifetime or write allowance of an existing token.

### Schemas

- `eventdbx schema create --aggregate <name> --events <event1,event2,...> [--snapshot-threshold <u64>]`
- `eventdbx schema add --aggregate <name> --events <event1,event2,...>`
- `eventdbx schema remove --aggregate <name> --event <name>`
- `eventdbx schema list`

Schemas are stored on disk; when the server runs with restriction enabled, incoming events must satisfy the recorded schema.

### Aggregates

- `eventdbx aggregate apply --aggregate <type> --aggregate-id <id> --event <name> --field KEY=VALUE... [--stage]`  
  Appends an event immediately—use `--stage` to queue it for a later commit.
- `eventdbx aggregate list [--skip <n>] [--take <n>] [--stage]`  
  Lists aggregates with version, Merkle root, and archive status; pass `--stage` to display queued events instead.
- `eventdbx aggregate get --aggregate <type> --aggregate-id <id> [--version <u64>] [--include-events]`
- `eventdbx aggregate replay --aggregate <type> --aggregate-id <id> [--skip <n>] [--take <n>]`
- `eventdbx aggregate verify --aggregate <type> --aggregate-id <id>`
- `eventdbx aggregate snapshot --aggregate <type> --aggregate-id <id> [--comment <text>]`
- `eventdbx aggregate archive --aggregate <type> --aggregate-id <id> [--comment <text>]`
- `eventdbx aggregate restore --aggregate <type> --aggregate-id <id> [--comment <text>]`
- `eventdbx aggregate remove --aggregate <type> --aggregate-id <id>`    Removes an aggregate that has no events (version still 0).
- `eventdbx aggregate commit`  
  Flushes all staged events in a single atomic transaction.

Staged events are stored in `.eventdbx/staged_events.json`. Use `aggregate apply --stage` to add entries to this queue, inspect them with `aggregate list --stage`, and persist the entire batch with `aggregate commit`. Events are validated against the active schema (when restriction is enabled) during both staging and commit. The commit operation writes every pending event in one RocksDB batch, guaranteeing all-or-nothing persistence.

### Plugins

- `eventdbx plugin map --aggregate <name> --field <field> --datatype <type>`  
  Records the base column type for a field; add `--plugin postgres [--plugin-name <label>]` to override the Postgres mapping only.
- `eventdbx plugin config postgres --connection <connection-string> [--name <label>] [--disable]`
- `eventdbx plugin config csv --name <label> --output-dir <dir> [--disable]`
- `eventdbx plugin config tcp --name <label> --host <hostname> --port <u16> [--disable]`
- `eventdbx plugin config http --name <label> --endpoint <host|url> [--https] [--header KEY=VALUE]... [--disable]`
- `eventdbx plugin config grpc --name <label> --endpoint <host|url> [--disable]`
- `eventdbx plugin config json --name <label> --path <file> [--pretty] [--disable]`
- `eventdbx plugin config log --name <label> --level <trace|debug|info|warn|error> [--template "text with {aggregate} {event} {id}"] [--disable]`
- `eventdbx plugin enable <label>`
- `eventdbx plugin disable <label>`
- `eventdbx plugin remove <label>`
- `eventdbx plugin test`
- `eventdbx plugin list`
- `eventdbx plugin queue`
- `eventdbx plugin queue clear`
- `eventdbx plugin queue retry [--event-id <uuid>]`
- `eventdbx plugin replay <plugin-name> <aggregate> [<aggregate_id>]`

Clearing dead entries prompts for confirmation to avoid accidental removal. Manual retries run the failed events immediately; use `--event-id` to target a specific entry.

### Replication

- `eventdbx remote add <name> <endpoint> --public-key <base64>`  
  Registers a standby and pins its Ed25519 public key.
- `eventdbx remote rm <name>`  
  Removes a configured remote.
- `eventdbx remote ls`  
  Lists remotes with their endpoints.
- `eventdbx remote show <name>`  
  Displays the endpoint and pinned key for a remote.
- `eventdbx remote key [--show-path]`  
  Prints this node's replication public key (generated on first run).
- `eventdbx remote push <name> [--dry-run] [--batch-size <n>] [--aggregate <type>...] [--aggregate-id <type:id>...]`  
  Streams local events to the remote in fast-forward mode; dry runs report pending changes.
- `eventdbx remote pull <name> [--dry-run] [--batch-size <n>] [--aggregate <type>...] [--aggregate-id <type:id>...]`  
  Fast-forwards the local node from the remote, reporting changes in dry-run mode.

Replication keys live alongside the data directory (`replication.key` / `replication.pub`) and are created automatically the first time the CLI loads configuration. The standby gRPC listener defaults to `127.0.0.1:7443`; override it in `config.toml` via `replication.bind_addr` when you expose the replica on another interface. When the HTTP server processes writes it streams committed events to every configured remote over gRPC using the pinned public keys. Use `--aggregate` repeatedly to scope push/pull to specific aggregate types when you only need to sync a subset of data, and `--aggregate-id TYPE:ID` to target individual aggregates.
Use `--aggregate` repeatedly to scope push/pull to specific aggregate types when you only need to sync a subset of data.

### Maintenance

- `eventdbx backup --output <path> [--force]`  
  Creates a compressed archive with the entire EventDBX data directory and configuration. Stop the server before running a backup to avoid partial snapshots.
- `eventdbx restore --input <path> [--data-dir <path>] [--force]`  
  Restores data from a backup archive. Use `--data-dir` to override the stored location, and `--force` to overwrite non-empty destinations. The server must be stopped before restoring.

Plugins fire after every committed event to keep external systems in sync. Each plugin sends or records different data:

Failed deliveries are automatically queued and retried with exponential backoff. The server keeps attempting until the plugin succeeds or the aggregate is removed, ensuring transient outages do not drop notifications.
Use `eventdbx plugin queue` to inspect pending/dead event IDs.

Plugin configurations are stored in `.eventdbx/plugins.json`. Each plugin instance requires a unique `--name` so you can update, enable, disable, remove, or replay it later. `plugin enable` validates connectivity (creating directories, touching files, or checking network access) before marking the plugin active. Remove a plugin only after disabling it with `plugin disable <name>`. `plugin replay` resends stored events for a single aggregate instance—or every instance of a type—through the selected plugin.

- **Postgres**: Upserts aggregate state into a Postgres table, expanding columns based on schema mappings or `plugin map --plugin postgres` overrides.
- **CSV**: Appends state snapshots into `<aggregate>.csv`, expanding columns as new fields appear.
- **TCP**: Writes a single-line JSON `EventRecord` to the configured socket.
- **HTTP**: POSTs the `EventRecord` JSON to the endpoint with optional headers; add `--https` during configuration to force HTTPS when the endpoint lacks a scheme.
- **gRPC**: Sends `EventRecord` batches to a remote gRPC endpoint compatible with the replication `ApplyEvents` API.

Example gRPC configuration:

```bash
# point at an existing replication-compatible listener
eventdbx plugin config grpc \
  --name audit-grpc \
  --endpoint grpc://replica.internal:8800

# enable the plugin once connectivity is confirmed
eventdbx plugin enable audit-grpc

# inspect status
eventdbx plugin list
```
- **JSON**: Appends the `EventRecord` JSON (pretty if requested) to the given file.
- **Log**: Emits a formatted line via `tracing` at the configured level. By default: `aggregate=<type> id=<id> event=<event>`.

Example TCP/HTTP/JSON payload (`EventRecord`):

```json
{
  "aggregate_type": "patient",
  "aggregate_id": "p-001",
  "event_type": "patient-updated",
  "payload": {
    "status": "inactive",
    "comment": "Archived via API"
  },
  "metadata": {
    "event_id": "45c3013e-9b95-4ed0-9af9-1a465f81d3cf",
    "created_at": "2024-12-01T17:22:43.512345Z",
    "issued_by": {
      "group": "admin",
      "user": "jane"
    }
  },
  "version": 5,
  "hash": "cafe…",
  "merkle_root": "deadbeef…"
}
```

## REST API

The server exposes a small HTTP API (served on port `7070` by default). All endpoints require a bearer token unless otherwise noted.

| Method & Path                                                               | Description                                                         |
| --------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| `GET /health`                                                               | Liveness probe (unauthenticated).                                   |
| `GET /v1/aggregates`                                                        | Lists aggregates; supports `skip`/`take` query parameters.          |
| `GET /v1/aggregates/{aggregate_type}/{aggregate_id}`                        | Returns the current state for a specific aggregate.                 |
| `GET /v1/aggregates/{aggregate_type}/{aggregate_id}/events`                 | Lists events for an aggregate; supports `skip`/`take` pagination.   |
| `POST /v1/events`                                                           | Appends an event; aggregate identifiers are provided in the body.   |
| `GET /v1/aggregates/{aggregate_type}/{aggregate_id}/verify`                 | Computes and returns the Merkle root for integrity verification.    |

Paginated responses cap `take` at the configurable `page_limit` (default `1000`). Adjust it with `eventdbx config --page-limit <n>` if you need larger pages.

All authenticated requests must include `Authorization: Bearer <token>` with a token issued via the CLI.

### cURL examples

```bash
# Post a JSON event (global endpoint)
curl \
  -X POST \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "aggregate_type": "patient",
        "aggregate_id": "p-001",
        "event_type": "patient-updated",
        "payload": {
          "status": "inactive",
          "meta": {"source": "api"}
        }
      }' \
  http://localhost:7070/v1/events

# Retrieve the first 10 events for an aggregate (supports `skip`/`take` up to `page_limit`)
curl \
  -H "Authorization: Bearer TOKEN" \
  "http://localhost:7070/v1/aggregates/patient/p-001/events?skip=0&take=10"


# Health check
curl http://localhost:7070/health

# List aggregates (replace TOKEN with an active value)
curl \
  -H "Authorization: Bearer TOKEN" \
  http://localhost:7070/v1/aggregates

# Append an event (global endpoint)
curl \
  -X POST \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "aggregate_type": "patient",
        "aggregate_id": "p-001",
        "event_type": "patient-updated",
        "payload": {
          "status": "inactive",
          "comment": "Archived via API"
        }
      }' \
  http://localhost:7070/v1/events

```

## GraphQL API

GraphQL is served on `/graphql`, and an interactive Playground is available via `GET /graphql` or `/graphql/playground`. Supply the same bearer token header used for REST requests.

### Query example

```bash
curl -X POST \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "query": "query RecentAggregates($take: Int!) { aggregates(take: $take) { aggregate_type aggregate_id version merkle_root state } }",
        "variables": { "take": 5 }
      }' \
  http://localhost:7070/graphql
```

Sample response:

```json
{
  "data": {
    "aggregates": [
      {
        "aggregate_type": "patient",
        "aggregate_id": "p-001",
        "version": 3,
        "merkle_root": "8f4c3d…",
        "state": {
          "name": "Jane Doe",
          "status": "active"
        }
      }
    ]
  }
}
```

### Mutation example

```bash
curl -X POST \
  -H "Authorization: Bearer TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
        "query": "mutation Append($input: AppendEventInput!) { appendEvent(input: $input) { aggregate_type aggregate_id version payload } }",
        "variables": {
          "input": {
            "aggregate_type": "patient",
            "aggregate_id": "p-001",
            "event_type": "patient-updated",
            "payload": { "status": "inactive", "comment": "Archived via GraphQL" }
          }
        }
      }' \
  http://localhost:7070/graphql
```

The mutation triggers the same validation, replication, and plugin notifications as the REST endpoint.

## Contributing

### **Quick Start**

1. **Report Issues**: Found a bug or have a suggestion? [Open an issue](https://chat.openai.com/g/g-kvXdAN8VA-eventdbx-guide/c/72d2fa39-c3e2-4e40-bd61-5694f7b82aab#) with detailed information.
2. **Contribute Code**:
   - **Fork & Clone**: Fork the EventDBX repo and clone your fork.
   - **Branch**: Create a branch for your changes from **`develop`**.
   - **Develop**: Make your changes, adhering to our coding standards. Add or update tests as necessary.
   - **Commit**: Use [Conventional Commits](https://www.conventionalcommits.org/) for clear, structured commit messages (e.g., **`feat: add new feature`** or **`fix: correct a bug`**).
   - **Pull Request**: Submit a pull request (PR) against the **`develop`** branch of the original repo. Describe your changes and link to any related issues.

### **Guidelines**

- **Formatting**: A project-wide Prettier configuration lives at `.prettierrc.json`; use it for Markdown/JSON/YAML changes.
- **Commit linting**: Conventional Commit headers are enforced through `.commitlintrc.json`.
- **Code Review**: Your PR will be reviewed by the team. Be open to feedback and make requested adjustments.
- **Merge**: Once approved, your PR will be merged into the project, and you'll be credited as a contributor.

## License

EventDBX is licensed under the [MIT](https://github.com/thachp/eventdbx/blob/HEAD/apps/system/) License.