# CLI reference Source: https://docs.eventdbx.com/cli-reference/introduction Complete guide to the dbx command surface—lifecycle, schemas, aggregates, events, plugins, and upgrades. The `dbx` CLI is the single entry point for starting the daemon, managing schemas, writing events, and supervising plugins. Every command accepts `--config ` if you need to target a non-default configuration file. ## Server lifecycle * `dbx start [--port ] [--data-dir ] [--foreground] [--restrict=]` – launch the daemon. `default` validates when schemas exist; `strict` requires schemas for every aggregate; `off` bypasses validation entirely. * `dbx stop` – stop the running daemon referenced by the PID file. * `dbx status` – print port, PID, uptime, and restriction mode. * `dbx restart [start options…]` – stop then start with new options. * `dbx destroy [--yes]` – remove the PID file, data dir, and config after confirmation. ## Configuration `dbx config [--port ] [--data-dir ] [--cache-threshold ] [--dek ] [--list-page-size ] [--page-limit ] [--plugin-max-attempts ] [--snapshot-threshold ] [--clear-snapshot-threshold]` * First invocation should set a 32-byte base64 DEK so payloads, snapshots, and `tokens.json` are encrypted. * `--list-page-size` changes the default page size for `aggregate list`; `--page-limit` caps any requested page. * `--plugin-max-attempts` controls how many retries occur before a job becomes “dead”. ## Domains * `dbx checkout [--create|--delete] [--force] [--remote ] [--port ] [--token ] [--remote-tenant ]` – activate a domain (creating or deleting it with the flags) and optionally store remote replication settings in `remote.json` for later `push`/`pull`/`watch` commands. * `dbx merge --from [--into ] [--overwrite-schemas]` – copy events + schemas from one domain into another. Existing aggregates abort unless explicitly overwritten. ## Tokens * `dbx token generate --group --user [--expiration ] [--limit ] [--keep-alive]` * `dbx token list` * `dbx token revoke --token ` * `dbx token refresh --token [--expiration ] [--limit ]` Tokens are Ed25519 signed; keys live under `[auth]` in `config.toml`. See [Authorization](/core-concepts/authorization) for action/resource patterns, tenant scoping, and example token recipes. ## Tenants * `dbx tenant assign --shard ` – pin a tenant to a shard label instead of hashing across the configured shard count. * `dbx tenant unassign ` – drop the manual shard override. * `dbx tenant list [--shard ] [--json]` – print assignments plus `quota_mb` and `usage_mb` columns so you can watch noisy tenants in plain text or JSON. * `dbx tenant stats [--json]` – summarise how many explicit assignments sit on each shard. * `dbx tenant quota set --max-mb ` / `dbx tenant quota clear ` – enforce or remove per-tenant storage ceilings. * `dbx tenant quota recalc ` – rescan a tenant’s RocksDB files to refresh usage counters after restores or manual maintenance. ## Schemas * `dbx schema create --events [--snapshot-threshold ]` * `dbx schema add --events ` * `dbx schema remove ` * `dbx schema annotate [--note ] [--clear]` * `dbx schema list` Schemas are stored on disk and enforced whenever restriction mode is `default` or `strict`. ## Aggregates * `dbx aggregate create --aggregate --aggregate-id --event [--field KEY=VALUE...] [--payload ] [--metadata ] [--note ] [--token ] [--json]` * `dbx aggregate apply --aggregate --aggregate-id --event --field KEY=VALUE... [--payload ] [--publish PLUGIN[:MODE[:PRIORITY]] ...] [--stage] [--token ] [--note ]` * `dbx aggregate patch --aggregate --aggregate-id --event --patch [--publish PLUGIN[:MODE[:PRIORITY]] ...] [--stage] [--token ] [--metadata ] [--note ]` * `dbx aggregate list [--aggregate ] [--cursor ] [--take ] [--stage] [--filter ] [--sort ] [--include-archived|--archived-only]` * `dbx aggregate get --aggregate --aggregate-id [--version ] [--include-events]` * `dbx aggregate verify --aggregate --aggregate-id ` * `dbx aggregate snapshot --aggregate --aggregate-id [--comment ]` * `dbx aggregate archive|restore --aggregate --aggregate-id [--comment ]` * `dbx aggregate remove --aggregate --aggregate-id ` – only succeeds when version is still 0. * `dbx aggregate commit` – flush all staged events in a single atomic batch. `--publish` can be repeated to target specific plugins per write (`PLUGIN[:MODE[:PRIORITY]]`); omit it to fan out to all enabled plugins using their configured payload modes. Complexity hints live on the [features page](/features#aggregate-operation-costs). Hot aggregates typically stay in the RocksDB block cache, keeping latency near-constant. ## Events * `dbx events [--aggregate ] [--aggregate-id ] [--cursor ] [--take ] [--filter ] [--sort ] [--json] [--include-archived|--archived-only]` * `dbx events --event [--json]` – omit `--event` if the first positional argument is a Snowflake id. * `dbx aggregate export [] [--all] --output [--format csv|json] [--zip] [--pretty]` `--filter` accepts SQL-like expressions (for example, `payload.status = "open" AND metadata.note LIKE "retry%"`). Prefix fields with `payload.`, `metadata.`, or `extensions.`. ## Snapshots * `dbx snapshots list [--aggregate ] [--aggregate-id ] [--version ] [--json]` – enumerate snapshots with optional aggregate scoping and version filters. * `dbx snapshots create [--comment ]` – materialise a new snapshot of the current aggregate state. * `dbx snapshots get [--json]` – fetch a specific snapshot by Snowflake id. ## Plugins and queues * `dbx plugin install|enable|disable|remove` * `dbx plugin start|stop|status ` – manage supervised process plugin workers. * `dbx plugin config tcp|http|log|process` – choose payload slices via `--payload `. * `dbx plugin test […]` * `dbx plugin list` * `dbx plugin replay [] [--payload-mode ]` * `dbx queue`, `dbx queue clear`, `dbx queue retry [--event-id ]` Queues guarantee delivery with exponential backoff. Clearing dead entries prompts for confirmation to avoid accidental loss. ## Replication & automation * `dbx push [--aggregate ] [--id ]` * `dbx pull [--aggregate ] [--id ]` * `dbx watch --mode [--aggregate ] [--interval ] [--background] [--run-once] [--skip-if-active]` * `dbx watch status [--all]` Replications abort if either side diverges for a given aggregate. Use `--concurrency ` on push/pull to tune throughput when mirroring large domains. ## Upgrades * `dbx upgrade [|latest] [--no-switch] [--print-only]` – downloads the requested release to `~/.eventdbx/versions///` and switches the active binary unless `--no-switch` is set. * `dbx upgrade use ` – jump between installed releases without downloading. * `dbx upgrade installed [--json]` * `dbx upgrade --suppress ` / `dbx upgrade --clear-suppress` * `dbx upgrade list [--limit ] [--json]` Versions lower than `v1.13.2` are unsupported; use `dbx upgrade@` as a shortcut syntax. Set `DBX_NO_UPGRADE_CHECK=1` to silence startup reminders in automation. ## Maintenance * `dbx backup --output [--force]` * `dbx restore --input [--data-dir ] [--force]` Always stop the daemon before running backups or restores to keep Merkle trees consistent. # .NET SDK Source: https://docs.eventdbx.com/client-sdks/dotnet Call EventDBX from C#/.NET apps with the asynchronous EventDbx.Client TCP client. The .NET SDK targets .NET 8+, opens a persistent TCP connection to the EventDBX control socket, and speaks Cap'n Proto over Noise NNpsk0 by default. It returns JSON payloads so you can hydrate POCOs with `System.Text.Json` or forward envelopes directly to workers. ## Feature highlights * Async-first API surface with Task-returning methods and `IAsyncDisposable` cleanup. * Create/apply/patch/archive helpers plus per-call publish target routing to plugins. * Aggregate reads for `get`, `select`, `list`, `events`, and `verify` with filters, cursors, and sorting. * Snapshot helpers (`create`, `list`, `get`) and control-plane admin for schemas and tenants. * Noise transport enabled by default; opt out with `UseNoise = false` for lab sockets. * Per-call token overrides; tenant selection happens during the initial handshake. ## Install ```powershell theme={null} dotnet add package EventDbx.Client ``` Targets .NET 8.0. The package is cross-platform and works on Windows, Linux, and macOS. ## Connect and configure ```csharp theme={null} using EventDbx; var options = new EventDbxClientOptions { Host = Environment.GetEnvironmentVariable("EVENTDBX_HOST") ?? "127.0.0.1", Port = int.TryParse(Environment.GetEnvironmentVariable("EVENTDBX_PORT"), out var port) ? port : 6363, Token = Environment.GetEnvironmentVariable("EVENTDBX_TOKEN") ?? throw new InvalidOperationException("EVENTDBX_TOKEN is required"), TenantId = Environment.GetEnvironmentVariable("EVENTDBX_TENANT_ID") ?? "default", RequestTimeout = TimeSpan.FromSeconds(10), ConnectTimeout = TimeSpan.FromSeconds(5), UseNoise = true, // set false when connecting to plaintext lab sockets }; await using var client = await EventDbxClient.ConnectAsync(options); ``` `ConnectAsync` performs the handshake and returns a connected client. Dispose it during graceful shutdown to release the socket. ### Runtime configuration | Variable | Default | Description | | -------------------- | ----------- | -------------------------------------------------- | | `EVENTDBX_HOST` | `127.0.0.1` | Hostname or IP address of the control socket. | | `EVENTDBX_PORT` | `6363` | TCP port for the control plane. | | `EVENTDBX_TOKEN` | *empty* | Control token sent during the handshake; required. | | `EVENTDBX_TENANT_ID` | `default` | Tenant identifier included in the hello message. | Pass these into `EventDbxClientOptions`; the client does not read environment variables automatically. ## Manage authentication and tenancy Override tokens per call to scope permissions. Tenant IDs are fixed at connect time; spin up a separate client per tenant. ```csharp theme={null} var sessionToken = httpContext.Request.Headers["x-eventdbx-token"].FirstOrDefault(); var updated = await client.AppendEventAsync(new AppendEventParams { AggregateType = "person", AggregateId = "p-110", EventType = "person_email_updated", Payload = new { email = "sora+primary@example.com" }, Metadata = new Dictionary { ["@actor"] = httpContext.User.Identity?.Name }, Note = "Changed by profile service", PublishTargets = new[] { new PublishTarget(plugin: "webhook", mode: "async") }, Token = string.IsNullOrWhiteSpace(sessionToken) ? null : sessionToken, }); ``` If no override is provided, the client reuses the token from `EventDbxClientOptions`. ## Write aggregates and events ```csharp theme={null} await client.CreateAggregateAsync(new CreateAggregateParams { AggregateType = "person", AggregateId = "p-110", EventType = "person_registered", Payload = new { first_name = "Jaya", last_name = "Singh", email = "jaya@example.com" }, Metadata = new Dictionary { ["@actor"] = "svc-directory" }, }); await client.AppendEventAsync(new AppendEventParams { AggregateType = "person", AggregateId = "p-110", EventType = "person_email_updated", Payload = new { email = "jaya+alerts@example.com" }, }); await client.PatchEventAsync(new PatchEventParams { AggregateType = "person", AggregateId = "p-110", EventType = "person_registered", Patch = new[] { new { op = "replace", path = "/first_name", value = "Jayah" } }, }); await client.SetArchiveAsync(new SetArchiveParams { AggregateType = "person", AggregateId = "p-110", Archived = true, Note = "customer request", }); // restore by setting Archived = false await client.SetArchiveAsync(new SetArchiveParams { AggregateType = "person", AggregateId = "p-110", Archived = false, }); ``` Payloads, metadata, and patches are serialized with `System.Text.Json`; anonymous types or POCOs both work. ## Read aggregates and events ```csharp theme={null} using System.Text.Json; var latest = await client.GetAggregateAsync("person", "p-110"); if (latest.Found) { using var doc = JsonDocument.Parse(latest.AggregateJson!); var email = doc.RootElement.GetProperty("payload").GetProperty("email").GetString(); } var projection = await client.SelectAggregateAsync(new SelectAggregateParams { AggregateType = "person", AggregateId = "p-110", Fields = new[] { "payload.first_name", "payload.email" }, }); var firstPage = await client.ListAggregatesAsync(new ListAggregatesOptions { Take = 25, Filter = "person.archived = false AND person.last_name LIKE 'S%'", Sort = new[] { new AggregateSortOption(AggregateSortField.CreatedAt, descending: true) }, }); if (firstPage.HasNextCursor) { var next = await client.ListAggregatesAsync(new ListAggregatesOptions { Cursor = firstPage.NextCursor }); } var history = await client.ListEventsAsync("person", "p-110", new ListEventsOptions { Take = 100 }); var merkle = await client.VerifyAggregateAsync("person", "p-110"); ``` `ListAggregatesAsync` and `ListEventsAsync` return JSON strings and cursor metadata; feed `NextCursor` into subsequent calls to resume paging. ## Filters, sorting, and pagination Filters use the same SQL-like grammar as the control plane (`field = value AND other_field > 10`). Sort fields accept names such as `aggregate_type`, `aggregate_id`, `created_at`, `updated_at`, and `archived`. You can pass a raw string via `SortText` (`"created_at:desc,aggregate_id:asc"`) or a strongly typed list of `AggregateSortOption`. Timestamp cursors (`ts::`) pass through unchanged when used with timestamp sorts. ## Snapshots and admin helpers ```csharp theme={null} await client.CreateSnapshotAsync(new CreateSnapshotParams { AggregateType = "person", AggregateId = "p-110", Comment = "pre-migration snapshot", }); var snapshots = await client.ListSnapshotsAsync(new ListSnapshotsOptions { AggregateType = "person" }); var snapshot = await client.GetSnapshotAsync(new GetSnapshotParams { SnapshotId = 42 }); var schemasJson = await client.ListSchemasAsync(); var replaced = await client.ReplaceSchemasAsync(new ReplaceSchemasParams { Schemas = new { person = new { version = 1 } } }); var publish = await client.TenantSchemaPublishAsync(new TenantSchemaPublishParams { TenantId = "default", Activate = true, Reload = true, }); var assigned = await client.TenantAssignAsync(new TenantAssignParams { TenantId = "tenant-123", ShardId = "shard-a" }); var quota = await client.TenantQuotaSetAsync(new TenantQuotaSetParams { TenantId = "tenant-123", MaxStorageMb = 1024 }); ``` These helpers cover point-in-time snapshots plus schema and tenant management flows (assign/unassign, quotas, reloads, and schema publication). ## Noise transport Noise NNpsk0 is enabled by default and derives a PSK from the control token. Only disable it for trusted lab sockets by setting `UseNoise = false` in `EventDbxClientOptions`. # Go SDK Source: https://docs.eventdbx.com/client-sdks/go Use the EventDBX Go client to call the control-plane TCP API over Cap'n Proto. The Go client opens a framed TCP session to the control plane, performs the Noise NNpsk0 handshake, and exchanges Cap'n Proto control messages. It mirrors the verb set used by the other SDKs and returns raw JSON strings so you can hydrate your own structs. ## Feature highlights * Create/apply/patch/archive helpers with optional publish targets and metadata. * Aggregate reads for `get`, `select`, `list`, `events`, and `verify` with cursors, filters, and sorting. * Snapshot helpers (`create`, `list`, `get`) plus admin verbs for schemas and tenants. * Noise transport on by default; disable only for trusted lab sockets. * Retries with backoff and verbose mutation responses by default. ## Install ```bash theme={null} go get github.com/eventdbx/eventdbx-go ``` Modules are tagged with semver (`v0.x.y`). Pin a specific tag in `go.mod` for reproducible builds. ## Connect and configure ```go theme={null} package main import ( "log" "os" "strconv" eventdbx "github.com/eventdbx/eventdbx-go" ) func envOr(key, fallback string) string { if v := os.Getenv(key); v != "" { return v } return fallback } func envInt(key string, fallback int) int { if v := os.Getenv(key); v != "" { if n, err := strconv.Atoi(v); err == nil { return n } } return fallback } func mustEnv(key string) string { if v := os.Getenv(key); v != "" { return v } log.Fatalf("%s is required", key) return "" } func main() { cli, err := eventdbx.NewClient(eventdbx.Config{ Host: envOr("EVENTDBX_HOST", eventdbx.DefaultHost), Port: envInt("EVENTDBX_PORT", eventdbx.DefaultPort), Token: mustEnv("EVENTDBX_TOKEN"), TenantID: envOr("EVENTDBX_TENANT_ID", "default"), UseNoise: eventdbx.Bool(true), }) if err != nil { log.Fatal(err) } defer cli.Close() } ``` `NewClient` performs the handshake and returns a connected client. Close it during graceful shutdown to release the socket. ### Runtime configuration | Variable | Default | Description | | -------------------- | ------------- | ---------------------------------------------------------- | | `EVENTDBX_HOST` | `127.0.0.1` | Hostname or IP address of the control socket. | | `EVENTDBX_PORT` | `6363` | TCP port for the control plane. | | `EVENTDBX_TOKEN` | *empty* | Control token sent during the handshake; required. | | `EVENTDBX_TENANT_ID` | `default` | Tenant identifier included in the hello message. | | `EVENTDBX_NO_NOISE` | *unset/false* | When set to `1`, disables Noise for plaintext lab sockets. | ## Write aggregates and events ```go theme={null} note := "customer sign-up" meta := `{"@actor":"svc-directory"}` _, err := cli.Create("person", "p-110", "person_registered", `{ "first_name":"Rafi", "last_name":"Stone", "email":"rafi@example.com" }`, &eventdbx.ApplyOptions{ Note: ¬e, MetadataJSON: &meta, }) if err != nil { log.Fatal(err) } _, _ = cli.Apply("person", "p-110", "person_email_updated", `{"email":"rafi+alerts@example.com"}`, nil) _, _ = cli.Patch("person", "p-110", "person_registered", `[{"op":"replace","path":"/first_name","value":"Rafiq"}]`, nil) _, _ = cli.Archive("person", "p-110", nil) _, _ = cli.Restore("person", "p-110", nil) ``` Publish targets can be passed via `ApplyOptions.PublishTargets` or `PatchOptions.PublishTargets` to route events to specific plugins. ## Read aggregates and events ```go theme={null} latest, _ := cli.Get("person", "p-110") if latest.Found { log.Println("aggregate json", latest.AggregateJSON) } projection, _ := cli.Select("person", "p-110", []string{"payload.first_name", "payload.email"}) take := uint64(25) filter := "person.archived = false" page, _ := cli.List(&eventdbx.ListAggregatesOptions{ Take: &take, Filter: &filter, SortOptions: []eventdbx.AggregateSortOption{ {Field: eventdbx.AggregateSortCreatedAt, Descending: true}, }, }) eventsTake := uint64(50) events, _ := cli.Events("person", "p-110", &eventdbx.ListEventsOptions{Take: &eventsTake}) merkle, _ := cli.Verify("person", "p-110") ``` `List` and `Events` return JSON strings plus cursor metadata (`HasNextCursor`, `NextCursor`). Feed `NextCursor` into subsequent calls to resume paging. ### Filters, sorting, and pagination Filters use the same SQL-like grammar as the control plane (`field = value AND other_field > 10`). Sort fields accept `aggregate_type`, `aggregate_id`, `created_at`, `updated_at`, and `archived`. Provide a raw string via `Sort` (`"created_at:desc,aggregate_id:asc"`) or structured options via `SortOptions`. ## Snapshots and admin helpers ```go theme={null} comment := "pre-migration" _, _ = cli.CreateSnapshot("person", "p-110", &eventdbx.CreateSnapshotOptions{Comment: &comment}) aggType := "person" snaps, _ := cli.ListSnapshots(&eventdbx.ListSnapshotsOptions{AggregateType: &aggType}) snap, _ := cli.GetSnapshot(42) schemas, _ := cli.ListSchemas() _, _ = cli.ReplaceSchemas(`{"person":{"version":1}}`) assign, _ := cli.TenantAssign("tenant-123", "shard-a") quota, _ := cli.TenantQuotaSet("tenant-123", 1024) _, _ = cli.TenantSchemaPublish("default", &eventdbx.TenantSchemaPublishOptions{Activate: true, Reload: true}) ``` These helpers cover point-in-time snapshots plus schema and tenant management flows (assign/unassign, quotas, reloads, and schema publication). ## Noise transport Noise NNpsk0 is enabled by default and derives a PSK from the control token. Disable only for trusted lab sockets by setting `EVENTDBX_NO_NOISE=1` or `UseNoise: eventdbx.Bool(false)` in `Config`. ## Testing * Unit tests run with `go test ./...`. * Integration tests expect a running control plane; set `EVENTDBX_INTEGRATION=1` along with `EVENTDBX_TOKEN`, `EVENTDBX_TENANT_ID`, and optionally `EVENTDBX_HOST`/`EVENTDBX_PORT` before running `go test`. The Go client methods are synchronous and accept no context parameter; handle timeouts via `Config.ReadTimeout` and `Config.ConnectTimeout`. # Java SDK Source: https://docs.eventdbx.com/client-sdks/java Write aggregates and build projections using the JVM-native EventDBX client. The Java SDK targets Java 17+ and mirrors the Node.js control client. It opens a persistent TCP connection to the EventDBX control socket, negotiates Noise + Cap'n Proto frames, and exposes blocking APIs for aggregate mutations, queries, and pagination. Defaults read from `EVENTDBX_*` environment variables so you can drop it into Spring, Micronaut, Quarkus, or standalone JVM services. ## Feature highlights * Blocking control-socket client with parity to `eventdbxjs` (`create`, `apply`, `patch`, `archive` / `restore`, `list`, `events`, `get`, `select`). * Noise XX (+ PSK) transport enabled by default; opt into plaintext with `noNoise` / `EVENTDBX_NO_NOISE` when the server allows it. * Publish target routing per mutation via `PublishTarget.of(...)` or `publishTarget("plugin:mode:priority")`. * Retry/backoff, connect/request timeouts, and env-driven defaults for host, port, token, tenant, and verbosity. * JSON payloads/metadata handled with Jackson `JsonNode` so you can work with strongly typed DTOs or raw trees. ## Install ### Maven ```xml theme={null} com.eventdbx eventdbx-java 0.1.8 ``` ### Gradle (Kotlin DSL) ```kotlin theme={null} dependencies { implementation("com.eventdbx:eventdbx-java:0.1.8") } ``` The Noise handshake depends on the native `snownoise` library in this repo (`native/snownoise`). Run `cargo build --release --manifest-path native/snownoise/Cargo.toml` and ensure the built library is on `java.library.path` (or set `SNOWNOISE_LIB` / `SNOWNOISE_LIB_PATH`) before connecting. Artifacts are published to Maven Central: [https://central.sonatype.com/artifact/com.eventdbx/eventdbx-java](https://central.sonatype.com/artifact/com.eventdbx/eventdbx-java) ## Quickstart ```java theme={null} import com.eventdbx.client.*; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import java.time.Duration; import java.util.List; ObjectMapper mapper = new ObjectMapper(); EventDbxConfig config = EventDbxConfig.builder() // host/port/token/tenant/verbose/noNoise default to EVENTDBX_* when omitted .retryPolicy(RetryPolicy.builder() .maxAttempts(3) .initialDelay(Duration.ofMillis(100)) .maxDelay(Duration.ofSeconds(1)) .build()) .build(); try (EventDbxClient client = new EventDbxClient(config)) { client.connect(); // optional; operations auto-connect AggregateSnapshot created = client.create( "person", "p-110", "person_registered", CreateAggregateOptions.builder() .payload(mapper.readTree("{\"first_name\":\"Noor\",\"last_name\":\"Ali\",\"email\":\"noor@example.com\"}")) .metadata(mapper.readTree("{\"@actor\":\"svc-directory\"}")) .publishTarget("search-indexer") .build()); EventRecord appended = client.apply( "person", "p-110", "person_email_updated", AppendOptions.builder() .payload(mapper.readTree("{\"email\":\"noor+alerts@example.com\"}")) .note("Changed by directory sync") .publishTarget(PublishTarget.fromString("analytics-engine:event-only")) .build()); Page page = client.list("person", PageOptions.builder() .take(50) .filter("person.archived = false") .build()); AggregateSnapshot latest = client.get("person", "p-110"); JsonNode projection = client.select("person", "p-110", List.of("payload.email", "metadata.@actor")); } ``` `EventDbxClient` is `AutoCloseable`, and defaults will pull host/port/token/tenant from the environment. Per-call `token` overrides let you scope mutations to a request without rebuilding the client. ## Publish targets ```java theme={null} import com.fasterxml.jackson.databind.ObjectMapper; import java.util.List; ObjectMapper mapper = new ObjectMapper(); List targets = List.of( PublishTarget.of("analytics-engine"), PublishTarget.fromString("fraud-worker:event-only:high") ); client.apply( "invoice", "inv-42", "invoice_paid", AppendOptions.builder() .payload(mapper.createObjectNode().put("status", "paid")) .publishTargets(targets) .build()); ``` Omit publish targets to fan out to every enabled plugin. String specs follow `plugin:mode:priority` (mode/priority optional). ## Write aggregates and events ```java theme={null} import com.fasterxml.jackson.databind.ObjectMapper; import java.util.List; ObjectMapper mapper = new ObjectMapper(); client.create( "person", "p-110", "person_registered", CreateAggregateOptions.builder() .publishTarget("search-indexer") .build()); client.apply( "person", "p-110", "person_email_updated", AppendOptions.builder() .payload(mapper.createObjectNode().put("email", "noor+alerts@example.com")) .metadata(mapper.createObjectNode().put("@actor", "svc-directory")) .build()); client.patch( "person", "p-110", "person_registered", List.of(new JsonPatchOperation("replace", "/last_name", null, mapper.valueToTree("Ali-Khan"))), PatchOptions.builder() .note("surname canonicalised") .publishTarget("analytics-engine:event-only") .build()); client.archive("person", "p-110", ArchiveOptions.builder().note("customer request").build()); client.restore("person", "p-110", ArchiveOptions.builder().build()); ``` `create` seeds a snapshot and first event atomically, `apply` appends events, `patch` issues RFC 6902 operations against historical payloads, and `archive`/`restore` toggle write access while preserving history. ## Read aggregates and events ```java theme={null} AggregateSnapshot state = client.get("person", "p-110"); JsonNode sparse = client.select("person", "p-110", List.of("payload.email", "metadata.note")); Page firstPage = client.list("person", PageOptions.builder() .take(25) .includeArchived(false) .filter("person.archived = false AND person.last_name LIKE 'A%'") .sort("aggregateId") .build()); if (firstPage.nextCursor() != null) { Page nextPage = client.list("person", PageOptions.builder() .cursor(firstPage.nextCursor()) .build()); // keep paging with nextCursor } Page history = client.events("person", "p-110", PageOptions.builder() .take(100) .token("user-session-token") // per-request override .build()); ``` Use `Page.nextCursor()` to resume pagination, and `token` on `PageOptions`, `AppendOptions`, `CreateAggregateOptions`, or `PatchOptions` to scope a call without rebuilding the client. ## Retry and timeouts * `RetryPolicy` controls exponential backoff (`maxAttempts`, `initialDelay`, `maxDelay`); defaults to a single attempt. * `connectTimeout` and `requestTimeout` live on `EventDbxConfig` and default to 3s / 10s respectively. * `client.isConnected()` and `client.disconnect()` help when hot reloading or orchestrating graceful shutdowns. ## Runtime configuration | Variable | Default | Description | | ------------------------ | -------------------------------------- | ------------------------------------------------------------------- | | `EVENTDBX_HOST` | `127.0.0.1` | Hostname or IP address of the control socket. | | `EVENTDBX_PORT` | `6363` | TCP port for the control plane. | | `EVENTDBX_TOKEN` | *empty* | Control token forwarded during the handshake. | | `EVENTDBX_TENANT_ID` | *empty* | Tenant identifier included in the initial hello. | | `EVENTDBX_VERBOSE` | `false` | Set `1/true` to request verbose mutation responses. | | `EVENTDBX_NO_NOISE` | `false` | Set `1/true` to request plaintext transport (server must allow it). | | `EVENTDBX_NOISE_PATTERN` | `Noise_NNpsk0_25519_ChaChaPoly_SHA256` | Override the Noise pattern used during the handshake. | ## Noise transport Noise is enabled by default with a PSK derived from the control token. Only disable it for controlled testing by passing `noNoise(true)` on `EventDbxConfig` or setting `EVENTDBX_NO_NOISE=1`; production deployments should keep Noise on. You can also override the handshake pattern with `EVENTDBX_NOISE_PATTERN` when experimenting with other Noise variants. ## Development & testing ```bash theme={null} cargo build --release --manifest-path native/snownoise/Cargo.toml mvn test ``` Run the native build once per platform so the Noise handshake can load its bindings, then execute the Maven tests or package tasks as usual. # JavaScript SDK Source: https://docs.eventdbx.com/client-sdks/javascript Interact with EventDBX from Node.js, TypeScript, and Edge runtimes using the eventdbxjs client. The JavaScript SDK (`eventdbxjs`) is a native Node-API addon (via [`napi-rs`](https://napi.rs/)) that speaks directly to the EventDBX control socket over TCP/Cap’n Proto. It targets Node.js services, serverless API routes, and any runtime that exposes standard TCP primitives. ## Feature highlights * JSON (de)serialisation for aggregate snapshots and event envelopes. * RFC 6902 JSON Patch helpers baked into `client.patch`. * Async/await-friendly API surface with Promise-based helpers. * Automatic retries with configurable exponential backoff. * Per-call overrides for tokens, metadata, patch operations, and archived filters. * Cross-platform builds covering macOS, Linux, Windows, and WASI targets. ## Install ```bash theme={null} npm install eventdbxjs # or pnpm add eventdbxjs ``` Requires Node.js 18+ or any runtime that exposes WHATWG `fetch`, Web Crypto APIs, and raw TCP access to port 6363. ## Connect and configure ```ts theme={null} import { createClient } from "eventdbxjs"; const client = createClient({ ip: process.env.EVENTDBX_HOST ?? "127.0.0.1", port: Number(process.env.EVENTDBX_PORT) || 6363, token: process.env.EVENTDBX_TOKEN, tenantId: process.env.EVENTDBX_TENANT_ID ?? "default", verbose: false, retry: { attempts: 3, initialDelayMs: 100, maxDelayMs: 1_000, }, }); await client.connect(); ``` `createClient` falls back to environment variables when options are omitted. Set `EVENTDBX_HOST`, `EVENTDBX_PORT`, `EVENTDBX_TOKEN`, and `EVENTDBX_TENANT_ID` (for multi-tenant deployments) to avoid hardcoding credentials in code. The `retry` option enables exponential backoff without recreating the client. ### Connection lifecycle tips * Connect once per process and reuse the underlying TCP socket to avoid repeated TLS/token handshakes. * `await client.disconnect()` during graceful shutdown so the server releases cursors quickly. * `await client.isConnected()` lets you guard re-use when hot reloading in development. ### Serverless or Edge entry points ```ts theme={null} import { createClient, type DbxClient } from "eventdbxjs"; let cached: DbxClient | null = null; async function getClient() { if (!cached) { cached = createClient({ ip: process.env.EVENTDBX_HOST, port: Number(process.env.EVENTDBX_PORT) || 6363, token: process.env.EVENTDBX_TOKEN, tenantId: process.env.EVENTDBX_TENANT_ID, }); } if (!(await cached.isConnected())) { await cached.connect(); } return cached; } export default async function handler(request: Request) { const client = await getClient(); const aggregate = await client.get( "person", new URL(request.url).searchParams.get("id")! ); return Response.json(aggregate); } ``` Module-level caches keep the connection alive across invocations in serverless/Edge runtimes that support global scope reuse (`Next.js`, `Remix`, Bun's serverless runtime, or any environment that exposes TCP primitives). ## Runtime configuration | Variable | Default | Description | | -------------------- | ----------- | --------------------------------------------------------- | | `EVENTDBX_HOST` | `127.0.0.1` | Hostname or IP address of the control socket. | | `EVENTDBX_PORT` | `6363` | TCP port that hosts the control plane. | | `EVENTDBX_TOKEN` | *empty* | Authentication token that the client forwards on connect. | | `EVENTDBX_TENANT_ID` | *empty* | Tenant identifier included in the handshake. | Set these variables or pass explicit overrides through `createClient`. Multi-tenant deployments always require `tenantId` (or `EVENTDBX_TENANT_ID`) so the server routes control requests correctly. ## Manage authentication and tenancy Tokens and tenant identifiers can be set globally or per call. Every method that issues a request to the server accepts an optional `token`, so you can scope permissions to an end-user session without recreating the client. ```ts theme={null} const sessionToken = request.headers.get("x-eventdbx-token") ?? undefined; await client.apply("person", "p-110", "person_email_updated", { payload: { email: request.body.email }, token: sessionToken, metadata: { "@actor": request.headers.get("x-actor"), }, note: `Changed by ${request.headers.get("x-actor")}`, }); ``` Use this pattern when a single process talks to multiple tenants or when privileged background jobs share infrastructure with user-triggered API routes. ### Retry configuration Transport-level failures (socket resets, Cap’n Proto decode errors, etc.) can be retried automatically. Retries are disabled by default (`attempts = 1`), so opt in by setting the `retry` object: ```ts theme={null} const client = createClient({ token: process.env.EVENTDBX_TOKEN, retry: { attempts: 4, // total tries = initial attempt + (attempts - 1) retries initialDelayMs: 100, maxDelayMs: 2_000, }, }); ``` Backoff doubles per retry until `maxDelayMs` is hit. Logical server errors (validation failures, authorization errors, etc.) still bubble to your application immediately. ## Write aggregates and events ```ts theme={null} // create – register a new aggregate + first event in one call await client.create("person", "p-110", "person_registered", { payload: { first_name: "Jaya", last_name: "Singh", email: "jaya@example.com", }, metadata: { "@actor": "svc-directory", note: "person created by hi@example.com", }, }); // apply – append events to an existing aggregate await client.apply("person", "p-110", "person_email_updated", { payload: { email: "jaya+alerts@example.com" }, }); // patch – apply RFC 6902 JSON patches to historical payloads await client.patch("person", "p-110", "person_registered", [ { op: "replace", path: "/first_name", value: "Jayah" }, ]); // archive – archive an aggregate, move to secondary index await client.archive("person", "p-110", { comment: "big beautiful comment", }); // restore – restore an aggreate, mvoe to primary index await client.restore("person", "p-110"); ``` `create` seeds a snapshot and first event atomically. `apply` appends new events. `patch` issues RFC 6902 operations against the requested historical payload, and `archive`/`restore` toggle write access while preserving history. ## Read aggregates and events ```ts theme={null} // get – fetch the latest aggregate snapshot and metadata type Person = { first_name: string; last_name: string; email: string }; const state: Person = await client.get("person", "p-110"); // list – paginate aggregates with filters, sort, and cursor helpers const { items: people, nextCursor } = await client.list("person", { take: 50, cursor: "a:person:xxx", filter: "person.status = true AND person.last_name = 'thach'", }); // select – project fields without returning the rest of the payload const summary = await client.select("person", "p-110", [ "first_name", "last_name", ]); // events – pull the full envelope stream const { items: history } = await client.events("person", "p-110"); ``` Use `get` when you need the entire snapshot, `select` for sparse projections, `list` to traverse many aggregates, and `events` for CDC-style replay. All read methods share the same pagination helpers and filter grammar, returning a `PageResult` whose `items` contain either aggregates or event envelopes. ## Filters, sorting, and pagination Filters use the same SQL-like shorthand as the EventDBX server (`field = value AND other_field > 10`), and sort fields accept names such as `aggregateType`, `aggregateId`, `version`, `merkleRoot`, and `archived`. Each entry in `PageResult.items` is the aggregate snapshot or event payload returned by the control socket. ```ts theme={null} const firstPage = await client.list("person", { take: 25, includeArchived: false, filter: "person.archived = false AND person.last_name LIKE 'S%'", sort: [{ field: "aggregateId" }, { field: "version", descending: true }], }); if (firstPage.nextCursor) { const nextPage = await client.list("person", { cursor: firstPage.nextCursor, }); // Resume until nextCursor is undefined } ``` `includeArchived`, `archivedOnly`, and request-scoped `token` overrides are part of the `PageOptions` passed to `list` and `events`. Always feed the returned `nextCursor` back into the next call when you need to resume pagination after hitting server-side page limits. When you sort aggregates by `created_at` or `updated_at`, you can use shorthand timestamp cursors with the same control socket used by `eventdbxjs`: pass `cursor: "ts::"` alongside the timestamp sort and the server expands it into the full token (`ts:::::...`) for you. ## TypeScript surface area The SDK exports helper types so you can strongly type payloads, retries, and pagination in your application. ```ts theme={null} /* eslint-disable */ export declare class DbxClient { constructor(options?: ClientOptions | undefined | null); /** Establish a TCP connection to the EventDBX control socket. */ connect(): Promise; /** Close the underlying socket, if connected. */ disconnect(): Promise; /** Returns `true` if a socket connection is currently held. */ isConnected(): Promise; /** Returns the configured endpoint (host/port). */ get endpoint(): ClientEndpoint; /** List aggregates, optionally restricting to a specific aggregate type. */ list( aggregateType?: string | undefined | null, options?: PageOptions | undefined | null ): Promise; /** Fetch a single aggregate snapshot. */ get(aggregateType: string, aggregateId: string): Promise; /** Select a subset of fields from an aggregate snapshot. */ select( aggregateType: string, aggregateId: string, fields: Array ): Promise; /** List events for an aggregate. */ events( aggregateType: string, aggregateId: string, options?: PageOptions | undefined | null ): Promise; /** Append a new event with an arbitrary JSON payload. */ apply( aggregateType: string, aggregateId: string, eventType: string, options?: AppendOptions | undefined | null ): Promise; /** Create an aggregate and emit its initial event. */ create( aggregateType: string, aggregateId: string, eventType: string, options?: CreateAggregateOptions | undefined | null ): Promise; /** Archive an aggregate. */ archive( aggregateType: string, aggregateId: string, options?: SetArchiveOptions | undefined | null ): Promise; /** Restore an archived aggregate. */ restore( aggregateType: string, aggregateId: string, options?: SetArchiveOptions | undefined | null ): Promise; /** Apply a JSON Patch to the aggregate. Returns the updated snapshot. */ patch( aggregateType: string, aggregateId: string, eventType: string, operations: JsonPatch[], options?: PatchOptions | undefined | null ): Promise; /** Create a snapshot for the provided aggregate. */ createSnapshot( aggregateType: string, aggregateId: string, options?: CreateSnapshotOptions | undefined | null ): Promise; /** List snapshots with optional aggregate filters. */ listSnapshots( options?: ListSnapshotsOptions | undefined | null ): Promise>; /** Fetch a snapshot by identifier. */ getSnapshot( snapshotId: bigint | number, options?: GetSnapshotOptions | undefined | null ): Promise; } export interface AppendOptions { payload?: JsonValue; metadata?: JsonValue; note?: string; token?: string; publishTargets?: Array; } export interface ClientEndpoint { ip: string; port: number; } export interface ClientOptions { ip?: string; port?: number; token?: string; tenantId?: string; verbose?: boolean; retry?: RetryOptions; } export interface CreateAggregateOptions { token?: string; payload?: JsonValue; metadata?: JsonValue; note?: string; publishTargets?: Array; } export declare function createClient( options?: ClientOptions | undefined | null ): DbxClient; export interface CreateSnapshotOptions { token?: string; comment?: string; } export interface GetSnapshotOptions { token?: string; } export type JsonPatch = | JsonPatchAddReplaceTest | JsonPatchRemove | JsonPatchMoveCopy; export interface JsonPatchAddReplaceTest { op: "add" | "replace" | "test"; path: string; value: JsonValue; } export interface JsonPatchMoveCopy { op: "move" | "copy"; from: string; path: string; } export interface JsonPatchRemove { op: "remove"; path: string; } export type JsonValue = any; export interface ListSnapshotsOptions { aggregateType?: string; aggregateId?: string; version?: bigint | number; token?: string; } export interface PageOptions { cursor?: string; take?: number; includeArchived?: boolean; archivedOnly?: boolean; token?: string; filter?: string; sort?: string; } export interface PageResult { items: Array; nextCursor?: string; } export interface PatchOptions { metadata?: JsonValue; note?: string; token?: string; publishTargets?: Array; } export declare const enum PayloadMode { All = "all", EventOnly = "event-only", StateOnly = "state-only", SchemaOnly = "schema-only", EventAndSchema = "event-and-schema", ExtensionsOnly = "extensions-only", } export declare const enum Priority { High = "high", Normal = "normal", Low = "low", } export interface PublishTargetOptions { plugin: string; mode?: PayloadMode; priority?: Priority; } export interface RetryOptions { attempts?: number; initialDelayMs?: number; maxDelayMs?: number; } export interface SetArchiveOptions { token?: string; note?: string; comment?: string; } ``` The generated `index.d.ts` file still types aggregate and event payloads as `any`, but the runtime values follow the `Aggregate` and `Event` shapes above, so you can layer your own domain-specific interfaces on top. # Overview Source: https://docs.eventdbx.com/client-sdks/overview Use the official EventDBX client libraries across Python, Java, PHP, NodeJS, .NET, and Go. Every SDK speaks the same control-socket protocol (Noise + Cap'n Proto via `control.capnp`) over the control port (typically TCP 6363) and reuses one TCP connection per process. Install the package for your stack, connect once, and reuse the client. Deep dives link to the full language guides. Prefer SDKs over raw sockets—the clients ship retries, pagination helpers, and typed payload handling so product teams can focus on domain logic. All clients can route writes to specific plugins with publish targets (`plugin[:mode[:priority]]`), and accept per-call overrides so you can fan out to analytics, webhooks, or custom workers without rebuilding the client. ## See it, shape it “See It (L-G-S), Shape It (C-A-P-A-R)” is the EventDBX shorthand for the operational verbs—short, visual, sticky, and a clear split between reads and writes. **See It — read actions** * `list` – paginate across aggregates (or staged aggregates) with consistent filters and limits. * `get` – fetch the latest aggregate state, metadata, and Merkle information in one call. * `select` – return a filtered subset of fields or dot-paths from aggregate state without transferring the full payload. **Shape It — event-writing actions** * `create` – register a brand-new aggregate and seed the first event via the control plane. * `apply` – append a new event to an existing aggregate after running schema validation. * `patch` – submit JSON Patch documents that adjust historical payloads. * `archive` – mark aggregates read-only so they remain queryable but reject new events. * `restore` – flip archived aggregates back to active, allowing writes again. ## JavaScript ```bash theme={null} npm install eventdbxjs ``` ```ts theme={null} import { createClient } from "eventdbxjs"; const client = createClient({ ip: process.env.EVENTDBX_HOST ?? "127.0.0.1", port: Number(process.env.EVENTDBX_PORT) || 6363, token: process.env.EVENTDBX_TOKEN, tenantId: process.env.EVENTDBX_TENANT_ID ?? "default", }); await client.connect(); // returns a reusable TCP socket; call disconnect on shutdown ``` * Native Node-API addon that speaks Cap'n Proto over TCP with Noise by default. * Promise-first API with async iterables for streaming reads; optional per-call token/publish targets. * Retries/backoff and verbose/non-verbose mutation responses. * [Deep dive to JavaScript SDK](/client-sdks/javascript) ## Python ```bash theme={null} python -m pip install eventdbx ``` ```python theme={null} from eventdbx import EventDBXClient with EventDBXClient( token="control_token", tenant_id="tenant-123", host="127.0.0.1", port=6363, ) as client: latest = client.get(aggregate_type="person", aggregate_id="p-110") ``` * Synchronous TCP client; handshake happens on construction and reuses one socket. * Noise XX on by default; toggle with `no_noise` / `EVENTDBX_NO_NOISE` for lab setups. * Optional retries and verbose/non-verbose mutation responses; includes admin helpers for schemas/tenants. * [Deep dive to Python SDK](/client-sdks/python) ## Java ```xml theme={null} com.eventdbx eventdbx-java 0.1.8 ``` ```java theme={null} import com.eventdbx.client.*; EventDbxConfig config = EventDbxConfig.builder() .token(System.getenv("EVENTDBX_TOKEN")) .tenant("default") .build(); try (EventDbxClient client = new EventDbxClient(config)) { client.connect(); var snapshot = client.get("person", "p-110"); } ``` * Blocking control client mirroring Node parity (`create`, `apply`, `patch`, reads). * Noise transport via the bundled `snownoise` native library; retries/backoff configurable. * Publish targets, per-call token overrides, and Jackson-based JSON handling. * [Deep dive to Java SDK](/client-sdks/java) ## PHP ```bash theme={null} composer require eventdbx/eventdbx-php ``` ```php theme={null} use EventDbx\Client; $client = new Client([ 'host' => getenv('EVENTDBX_HOST') ?: '127.0.0.1', 'port' => (int) (getenv('EVENTDBX_PORT') ?: 6363), 'token' => getenv('EVENTDBX_TOKEN'), 'tenantId' => getenv('EVENTDBX_TENANT_ID') ?: 'default', ]); $state = $client->get('person', 'p-110'); ``` * Native FFI wrapper over the Rust control client; Noise on by default, configurable per client. * Per-call publish targets and token overrides; methods return JSON payloads/envelopes. * Requires building the bundled native library before first use. * [Deep dive to PHP SDK](/client-sdks/php) ## .NET ```powershell theme={null} dotnet add package EventDbx.Client ``` ```csharp theme={null} using EventDbx; var options = new EventDbxClientOptions { Host = Environment.GetEnvironmentVariable("EVENTDBX_HOST") ?? "127.0.0.1", Port = int.TryParse(Environment.GetEnvironmentVariable("EVENTDBX_PORT"), out var port) ? port : 6363, Token = Environment.GetEnvironmentVariable("EVENTDBX_TOKEN") ?? throw new InvalidOperationException("EVENTDBX_TOKEN is required"), TenantId = Environment.GetEnvironmentVariable("EVENTDBX_TENANT_ID") ?? "default", }; await using var client = await EventDbxClient.ConnectAsync(options); ``` * Async-first TCP client with Noise enabled by default; dispose with `IAsyncDisposable`. * Methods return JSON strings plus cursor metadata; per-call token overrides supported. * Snapshot and schema/tenant admin helpers mirror the control plane. * [Deep dive to .NET SDK](/client-sdks/dotnet) ## Go ```bash theme={null} go get github.com/eventdbx/eventdbx-go ``` ```go theme={null} package main import ( "log" "os" eventdbx "github.com/eventdbx/eventdbx-go" ) func main() { cli, err := eventdbx.NewClient(eventdbx.Config{ Host: os.Getenv("EVENTDBX_HOST"), Port: eventdbx.DefaultPort, Token: os.Getenv("EVENTDBX_TOKEN"), TenantID: "default", UseNoise: eventdbx.Bool(true), }) if err != nil { log.Fatal(err) } defer cli.Close() } ``` * Synchronous client returning JSON strings; configure connect/read timeouts in `Config`. * Noise NNpsk0 enabled by default; set `EVENTDBX_NO_NOISE` or `UseNoise` to disable in labs. * Pagination with cursors for aggregates/events plus snapshots and schema/tenant admin helpers. * [Deep dive to Go SDK](/client-sdks/go) ## Rust ```bash theme={null} cargo add eventdbx-client ``` ```rust theme={null} use eventdbx_client::{ClientConfig, EventDbxClient}; #[tokio::main] async fn main() -> eventdbx_client::Result<()> { let config = ClientConfig::new("127.0.0.1", std::env::var("EVENTDBX_TOKEN")?) .with_tenant("default") .with_port(6363); let client = EventDbxClient::connect(config).await?; let _state = client.get_aggregate("person", "p-110").await?; Ok(()) } ``` * Tokio-native async client with Noise XX enabled by default and configurable timeouts. * Request/response builders keep payloads/metadata strongly typed via `serde_json`. * Per-call publish targets, notes, and token overrides for scoped mutations. * [Deep dive to Rust SDK](/client-sdks/rust) ## Common patterns * **Config from env**: Provide host/port/token/tenant via environment variables so deployments stay portable; most clients also expose explicit option fields. * **Connect once**: Create one client per process/tenant, reuse the TCP socket, and close/dispose on shutdown to release cursors cleanly. * **Publish targets**: Route writes to specific plugins with `plugin[:mode[:priority]]`; omit targets to fan out to all enabled plugins. * **Retries/timeouts**: Opt into retries/backoff for transport errors and tune connect/request timeouts for your SLOs. * **Per-call scope**: Override tokens per request when scoping permissions to a user session without rebuilding the client. If your language is missing, open an issue or follow the control protocol in `control.capnp` to build your own. # PHP SDK Source: https://docs.eventdbx.com/client-sdks/php Use EventDBX from PHP 8.1+ via a native FFI client. The PHP SDK is a thin FFI wrapper around the native Rust control client. It keeps feature parity with the other control clients (`create`, `apply`, `patch`, `archive` / `restore`, `list`, `events`, `get`, `select`, `verify`) while negotiating Noise + Cap'n Proto frames directly against the control socket. Defaults come from `EVENTDBX_*` environment variables, so you can drop it into Laravel, Symfony, or plain PHP services. ## Feature highlights * Native control-socket client over Noise XX + Cap'n Proto with parity to other SDKs (`create`, `apply`, `patch`, `archive` / `restore`, `list`, `events`, `get`, `select`, `verify`). * Publish targets and per-call token overrides on mutations; payload/metadata/note fields accept any JSON-serializable data. * Pagination helpers for aggregates and events with `take`, `cursor`, and filtering/sorting by aggregate type. * Defaults for host/port/token/tenant pulled from `EVENTDBX_*`; configure timeouts and Noise on/off flags per client. * PHP 8.1+ with the `ffi` extension; no PSR HTTP client required because it talks to the control socket directly. ## Install ```bash theme={null} composer require eventdbx/eventdbx-php ``` Build the native library before running the client:
cd vendor/eventdbx/eventdbx-php/native
  cargo build --release
The client will auto-load `native/target/release/libeventdbx_php_native.(so|dylib|dll)`. Ensure FFI is allowed for your SAPI (`ffi.enable=preload` in `php.ini`, and add the built library path to `ffi.preload` if your environment restricts dynamic loads).
## Quickstart ```php theme={null} use EventDbx\Client; $client = new Client([ // host/port/token/tenant fall back to EVENTDBX_* when omitted 'token' => getenv('EVENTDBX_TOKEN'), 'host' => getenv('EVENTDBX_HOST') ?: '127.0.0.1', 'port' => (int) (getenv('EVENTDBX_PORT') ?: 6363), // 'tenantId' => getenv('EVENTDBX_TENANT_ID') ?: 'default', // 'noNoise' => true, // only when the server allows plaintext ]); $client->create('person', 'p-110', 'person_registered', [ 'payload' => ['first_name' => 'Noor', 'last_name' => 'Ali', 'email' => 'noor@example.com'], 'metadata' => ['@actor' => 'svc-directory'], 'note' => 'seeded via importer', 'publishTargets' => [['plugin' => 'search-indexer']], ]); $client->apply('person', 'p-110', 'person_email_updated', [ 'payload' => ['email' => 'noor+alerts@example.com'], 'token' => 'per-call-token', // optional override for this mutation ]); $page = $client->list('person', ['take' => 50, 'filter' => 'person.archived = false']); $state = $client->get('person', 'p-110'); $events = $client->events('person', 'p-110'); ``` ## Publish targets ```php theme={null} $client->apply('invoice', 'inv-42', 'invoice_paid', [ 'payload' => ['status' => 'paid'], 'publishTargets' => [ ['plugin' => 'analytics-engine'], ['plugin' => 'fraud-worker', 'mode' => 'event-only', 'priority' => 'high'], ], ]); ``` Omit publish targets to fan out to all enabled plugins. Specs follow `plugin` with optional `mode` and `priority`. ## Write aggregates and events ```php theme={null} $client->create('person', 'p-110', 'person_registered'); $client->apply('person', 'p-110', 'person_email_updated', [ 'payload' => ['email' => 'noor+alerts@example.com'], 'metadata' => ['@actor' => 'svc-directory'], 'note' => 'synced from directory', ]); $client->patch('person', 'p-110', 'person_registered', [ ['op' => 'replace', 'path' => '/last_name', 'value' => 'Ali-Khan'], ], ['note' => 'canonicalised surname']); $client->archive('person', 'p-110', ['note' => 'customer request']); $client->restore('person', 'p-110'); $client->verify('person', 'p-110'); ``` `create` seeds the aggregate and its first event atomically; `apply` appends events; `patch` issues RFC 6902 operations against prior payloads; `archive`/`restore` toggle write access while preserving history. ## Read aggregates and events ```php theme={null} $state = $client->get('person', 'p-110'); $sparse = $client->select('person', 'p-110', ['payload.email', 'metadata.@actor']); $firstPage = $client->list('person', [ 'take' => 25, 'includeArchived' => false, 'sort' => 'aggregateId', ]); if (!empty($firstPage['nextCursor'])) { $nextPage = $client->list('person', ['cursor' => $firstPage['nextCursor']]); // keep paging with nextCursor } $history = $client->events('person', 'p-110', [ 'take' => 100, 'token' => 'per-request-token', // optional override ]); ``` Pagination responses include `items` and `nextCursor`; pass `cursor` back to keep paging. Filters and sorts follow the control-plane syntax (e.g., `aggregate_type = "person"` is added automatically when listing a specific type). ## Runtime configuration | Variable | Default | Description | | -------------------- | ----------- | ------------------------------------------------------------------- | | `EVENTDBX_HOST` | `127.0.0.1` | Hostname or IP address of the control socket. | | `EVENTDBX_PORT` | `6363` | TCP port for the control plane. | | `EVENTDBX_TOKEN` | *required* | Control token forwarded during the handshake. | | `EVENTDBX_TENANT_ID` | `default` | Tenant identifier included in the initial hello. | | `EVENTDBX_NO_NOISE` | `false` | Set `1/true` to request plaintext transport (server must allow it). | Additional constructor options (all optional): `protocol_version`, `connect_timeout_ms`, `request_timeout_ms`, `tenantId`, and `noNoise`. ## Noise transport Noise is enabled by default with a PSK derived from the control token. Only disable it for controlled testing by passing `['noNoise' => true]` to the client config or setting `EVENTDBX_NO_NOISE=1`; production deployments should keep Noise on. ## Development & testing ```bash theme={null} cargo build --release --manifest-path native/Cargo.toml composer test ``` Build the native library once per platform so PHP can load it via FFI, then run the PHPUnit suite. Ensure `ffi.enable` is permitted in your PHP SAPI and restart PHP-FPM after changing `php.ini`. # Python SDK Source: https://docs.eventdbx.com/client-sdks/python Build EventDBX integrations with the synchronous `eventdbx` TCP client. The Python SDK targets CPython 3.9+ and mirrors the ergonomics of the Node and Rust clients. It opens a persistent TCP control connection to the EventDBX control socket, performs a Noise XX handshake by default, and exchanges Cap'n Proto messages for aggregate reads/writes. ## Feature highlights * Synchronous TCP client that speaks the same Cap'n Proto control protocol as `eventdbxjs`. * Noise XX (+ PSK) transport enabled by default; opt out with `no_noise=True` / `EVENTDBX_NO_NOISE=1` for test labs. * Mutation helpers for `create`, `apply`, `patch`, `archive`, and `restore`, plus per-call publish target routing. * Read helpers for `list`, `events`, `get`, `select`, and `verify` with pagination and sorting. * Snapshot and schema/tenant admin helpers (`create_snapshot`, `list_snapshots`, `tenant_schema_publish`, `tenant_assign`, quotas, etc.). ## Install ```bash theme={null} python -m pip install --upgrade pip python -m pip install eventdbx ``` `pycapnp` depends on Cap'n Proto system libraries. Install them first (`brew install capnp` on macOS or `apt-get install capnproto libcapnp-dev` on Debian/Ubuntu) before running `pip install`. ## Connect and configure ```python theme={null} import json import os from eventdbx import EventDBXClient, PublishTarget, RetryOptions with EventDBXClient( token=os.getenv("EVENTDBX_TOKEN", "control_token"), tenant_id=os.getenv("EVENTDBX_TENANT_ID", "tenant-123"), host=os.getenv("EVENTDBX_HOST", "127.0.0.1"), port=int(os.getenv("EVENTDBX_PORT", "6363")), verbose=True, # set False to return boolean acknowledgements no_noise=False, # set True or EVENTDBX_NO_NOISE=1 to skip Noise in test labs retry=RetryOptions(attempts=3, initial_delay_ms=100, max_delay_ms=1_000), ) as client: created = client.create( aggregate_type="orders", aggregate_id="ord_123", event_type="created", payload_json=json.dumps({"total": 42.15}), publish_targets=[PublishTarget(plugin="webhook", mode="async")], ) ``` `EventDBXClient` performs the handshake during construction and is a context manager; exit the block to close the socket. `payload_json` and `metadata_json` accept serialized JSON strings. When `verbose=True` (default) mutation methods return stored JSON blobs; set `verbose=False` to receive boolean acknowledgements. ### Runtime configuration | Variable | Default | Description | | -------------------- | ----------- | ----------------------------------------------------------- | | `EVENTDBX_HOST` | `127.0.0.1` | Hostname or IP address of the control socket. | | `EVENTDBX_PORT` | `6363` | TCP port for the control plane. | | `EVENTDBX_TOKEN` | *empty* | Control token; required when constructing the client. | | `EVENTDBX_TENANT_ID` | *empty* | Tenant identifier included in the initial hello. | | `EVENTDBX_NO_NOISE` | `false` | Set `1/true` to request plaintext transport (testing only). | Pass these values from your environment into `EventDBXClient` as shown above. Only `EVENTDBX_NO_NOISE` is read automatically when `no_noise` is omitted. ## Manage authentication and tenancy Tokens and tenant identifiers are required when constructing the client. Create separate client instances per tenant or per control token. The same socket is reused for all calls until the context exits or `close()` is invoked. ## Retry configuration Transport-level failures (socket resets, Cap'n Proto decode errors, etc.) can be retried automatically. Retries are disabled by default (`attempts = 1`); opt in by passing `retry`: ```python theme={null} from eventdbx import EventDBXClient, RetryOptions client = EventDBXClient( token="control_token", tenant_id="tenant-123", retry=RetryOptions( attempts=4, # initial try + 3 retries initial_delay_ms=100, max_delay_ms=2_000, ), ) ``` Only transport errors are retried; logical API errors surface immediately. ## Publish targets Select which plugins receive a write, override payload mode, and bump priority per call: ```python theme={null} from eventdbx import EventDBXClient, PublishTarget with EventDBXClient(token="", tenant_id="") as client: client.apply( aggregate_type="invoice", aggregate_id="inv-1", event_type="invoice_paid", payload_json="{}", publish_targets=[ PublishTarget(plugin="analytics-engine", mode="event-only"), PublishTarget(plugin="fraud-worker", mode="all", priority="high"), ], ) ``` Omit `publish_targets` to fan out to every enabled plugin using their configured payload mode. ## Write aggregates and events ```python theme={null} import json client.create( aggregate_type="person", aggregate_id="p-110", event_type="person_registered", payload_json=json.dumps({"first_name": "Jaya", "last_name": "Singh", "email": "jaya@example.com"}), metadata_json=json.dumps({"@actor": "svc-directory", "note": "created by hi@example.com"}), ) client.apply( aggregate_type="person", aggregate_id="p-110", event_type="person_email_updated", payload_json=json.dumps({"email": "jaya+alerts@example.com"}), ) client.patch( aggregate_type="person", aggregate_id="p-110", event_type="person_registered", patches=[{"op": "replace", "path": "/first_name", "value": "Jayah"}], ) client.archive(aggregate_type="person", aggregate_id="p-110", note="customer request") client.restore(aggregate_type="person", aggregate_id="p-110") ``` `create` seeds a snapshot and first event atomically, `apply` appends events, `patch` issues RFC 6902 operations against historical payloads, and `archive`/`restore` toggle write access while preserving history. ## Read aggregates and events ```python theme={null} latest = client.get(aggregate_type="person", aggregate_id="p-110") projection = client.select( aggregate_type="person", aggregate_id="p-110", fields=["payload.email", "metadata.@actor"], ) first_page = client.list( take=25, filter_expr="person.archived = false AND person.last_name LIKE 'S%'", sort="aggregate_id", ) if first_page.has_next_cursor: next_page = client.list(cursor=first_page.next_cursor) history = client.events(aggregate_type="person", aggregate_id="p-110", take=100) merkle_root = client.verify(aggregate_type="person", aggregate_id="p-110") ``` Use `next_cursor` to resume pagination. `list` returns aggregate snapshots; `events` returns full envelopes. ## Filters, sorting, and pagination Filters use the same SQL-like grammar as the server (`field = value AND other_field > 10`). Sort fields accept names such as `aggregate_id`, `aggregate_type`, `version`, `created_at`, `updated_at`, and `archived`. You can pass a string (`"created_at:desc,aggregate_id:asc"`) or a list of `AggregateSortOption` objects. When sorting by timestamps, shorthand cursors (`ts::`) can be used with `cursor`. ## Snapshots and admin helpers * Snapshots: `create_snapshot`, `list_snapshots`, and `get_snapshot` handle point-in-time copies. * Schema & tenant admin: `list_schemas` / `replace_schemas`, `tenant_schema_publish`, `tenant_assign` / `tenant_unassign`, and quota helpers wrap control-plane maintenance flows. ## Noise transport Noise XX with a token-derived PSK is enabled by default. Disable it only for controlled testing by passing `no_noise=True` (or setting `EVENTDBX_NO_NOISE`). Production deployments should keep Noise on. ## Development & testing ```bash theme={null} python3 -m venv .venv && source .venv/bin/activate pip install -e ."[dev]" pytest ``` If `pycapnp` fails to build, install the Cap'n Proto toolchain first and retry the pip install. # Rust SDK Source: https://docs.eventdbx.com/client-sdks/rust Tokio-native EventDBX client with Noise-by-default transport and publish-target routing. `eventdbx-client` is an async crate that speaks the EventDBX control socket (Cap'n Proto over TCP with a Noise XX handshake derived from your token). It covers list/get/select/events reads plus create/apply/patch/archive mutations. ## Feature highlights * Tokio-native async client; connect once with `EventDbxClient::connect` and reuse the socket. * Noise XX (+ PSK from your token) on by default; opt out with `.with_noise(false)` when the server permits plaintext. * Serde-friendly JSON payloads/metadata for `create`, `append`, `patch`, and archive/restore helpers. * Pagination, filters, and sort directives (`created_at`, `aggregate_id`, etc.) on `list_aggregates` and `list_events`. * Per-call overrides for publish targets, notes, metadata, and tokens to scope mutations. * Configurable connect/request timeouts (defaults: 5s connect, 10s request). ## Install ```bash theme={null} cargo add eventdbx-client ``` ## Connect and configure ```rust theme={null} use eventdbx_client::{ AppendEventRequest, ClientConfig, EventDbxClient, ListAggregatesOptions, PublishTarget, SetAggregateArchiveRequest, }; use serde_json::json; #[tokio::main] async fn main() -> eventdbx_client::Result<()> { let config = ClientConfig::new( std::env::var("EVENTDBX_HOST").unwrap_or_else(|_| "127.0.0.1".into()), std::env::var("EVENTDBX_TOKEN")?, ) .with_tenant(std::env::var("EVENTDBX_TENANT").unwrap_or_else(|_| "default".into())) .with_port( std::env::var("EVENTDBX_PORT") .ok() .and_then(|p| p.parse().ok()) .unwrap_or(6363), ) .with_noise(true); // set false to request plaintext when the server allows it let client = EventDbxClient::connect(config).await?; // append an event with metadata and publish targets let mut append = AppendEventRequest::new( "person", "p-110", "person_registered", json!({ "first_name": "Nia", "last_name": "Patel", "email": "nia@example.com" }), ); append.metadata = Some(json!({ "@actor": "svc-directory" })); append.note = Some("seed data".into()); append .publish_targets .push(PublishTarget::new("search-indexer").with_mode("event-only")); client.append_event(append).await?; // list aggregates with sort + pagination let mut list_opts = ListAggregatesOptions::default(); list_opts.take = Some(10); list_opts.sort_text = Some("created_at:desc, aggregate_id:asc".into()); let aggregates = client.list_aggregates(list_opts).await?; println!("aggregates: {}", aggregates.aggregates); // archive an aggregate with a note let mut archive = SetAggregateArchiveRequest::new("person", "p-110", true); archive.note = Some("cleanup".into()); client.set_aggregate_archive(archive).await?; Ok(()) } ``` `ClientConfig::new(host, token)` defaults to `port = 6363`, `tenant = "default"`, Noise enabled, `connect_timeout = 5s`, and `request_timeout = 10s` per RPC. Build one config per tenant/token and reuse the client for the process lifetime. Call `.with_request_timeout(None)` to disable per-call timeouts. ## Runtime configuration | Variable | Default | Description | | ------------------- | ----------- | ------------------------------------------------------------------------------------ | | `EVENTDBX_HOST` | `127.0.0.1` | Hostname or IP address of the control socket. | | `EVENTDBX_PORT` | `6363` | TCP port for the control plane. | | `EVENTDBX_TOKEN` | *empty* | Control token forwarded during the handshake. | | `EVENTDBX_TENANT` | `default` | Tenant identifier included in the initial hello. | | `EVENTDBX_NO_NOISE` | `false` | Set `1/true` on the server side to permit plaintext; pair with `.with_noise(false)`. | The crate does not read these automatically; pass them into `ClientConfig` as shown above. ## Manage authentication and tenancy Pass the control token and tenant into `ClientConfig` up front. Per-call token overrides are available on request structs/options (`ListAggregatesOptions.token`, `AppendEventRequest.token`, etc.) so you can scope permissions to a user session without rebuilding the client. ## Publish targets Direct mutations to specific plugins and override payload mode/priority per call: ```rust theme={null} use eventdbx_client::{AppendEventRequest, PublishTarget}; use serde_json::json; let mut request = AppendEventRequest::new("invoice", "inv-1", "invoice_created", json!({})); request.publish_targets = vec![ PublishTarget::new("analytics-engine").with_mode("event-only"), PublishTarget::new("fraud-worker").with_mode("all").with_priority("high"), ]; client.append_event(request).await?; ``` Skip `publish_targets` to fan out to all enabled plugins with their default payload mode. Publish targets are only effective when the named plugins are installed, configured, and running. ## Write aggregates and events ```rust theme={null} use eventdbx_client::{ AppendEventRequest, CreateAggregateRequest, EventDbxClient, PatchEventRequest, PublishTarget, SetAggregateArchiveRequest, }; use serde_json::json; let mut create = CreateAggregateRequest::new( "person", "p-120", "person_registered", json!({ "first_name": "Ari", "email": "ari@example.com" }), ); create.publish_targets.push(PublishTarget::new("search")); let created = client.create_aggregate(create).await?; let mut append = AppendEventRequest::new( "person", "p-120", "person_email_updated", json!({ "email": "ari+alerts@example.com" }), ); append.note = Some("user changed email".into()); client.append_event(append).await?; let mut patch = PatchEventRequest::new( "person", "p-120", "person_registered", json!([{ "op": "replace", "path": "/email", "value": "ari@corp.example" }]), ); client.patch_event(patch).await?; let mut archive = SetAggregateArchiveRequest::new("person", "p-120", true); archive.note = Some("cleanup".into()); client.set_aggregate_archive(archive).await?; let restore = SetAggregateArchiveRequest::new("person", "p-120", false); client.set_aggregate_archive(restore).await?; ``` `create_aggregate` seeds a snapshot and first event atomically, `append_event` appends events, `patch_event` issues RFC 6902 operations against historical payloads, and `set_aggregate_archive` toggles write access while preserving history. ## Read aggregates and events ```rust theme={null} use eventdbx_client::{ EventDbxClient, ListAggregatesOptions, ListEventsOptions, SelectAggregateRequest, }; let got = client.get_aggregate("person", "p-110").await?; let select = client .select_aggregate(SelectAggregateRequest::new( "person", "p-110", vec!["payload.email".into(), "metadata.@actor".into()], )) .await?; let mut list = ListAggregatesOptions::default(); list.take = Some(20); list.sort_text = Some("created_at:desc".into()); let aggregates = client.list_aggregates(list).await?; if let Some(cursor) = aggregates.next_cursor.as_deref() { let mut next = ListAggregatesOptions::default(); next.cursor = Some(cursor.to_string()); let more = client.list_aggregates(next).await?; } let events = client .list_events("person", "p-120", ListEventsOptions::default()) .await?; let merkle = client.verify_aggregate("person", "p-120").await?; ``` Use `next_cursor` to resume pagination. `verify_aggregate` returns the Merkle root for integrity checks. ## Filters, sorting, and pagination Filters use the same SQL-like grammar as the server (`field = value AND other_field > 10`). Sort directives accept fields like `aggregate_id`, `aggregate_type`, `created_at`, `updated_at`, and `archived`. Pass a string (`"created_at:desc, aggregate_id:asc"`) via `ListAggregatesOptions.sort_text` or build `AggregateSort` entries. Use `include_archived` / `archived_only` when traversing soft-deleted aggregates. Feed `next_cursor` back into subsequent `list_aggregates` calls to continue paging. ## Noise transport Noise XX is enabled by default using a PSK derived from your control token. Disable it only for controlled testing with `.with_noise(false)` and a server configured to honor `EVENTDBX_NO_NOISE`; production deployments should keep Noise on. ## Development & testing ```bash theme={null} cargo test ``` Use `cargo check` for quick compilation cycles and `cargo fmt --check` in CI to keep formatting consistent. # Event Sourcing Model Source: https://docs.eventdbx.com/core-concepts/aggregate-events Model aggregates, snapshots, and events so write-side history stays deterministic and verifiable. Every state change flows through three artifacts: events, the aggregates they append to, and snapshots that keep reads fast. Calibrate each piece deliberately so you can replay history, prove integrity, and power downstream projections without touching the write path. **How they fit together** * Events are immutable facts appended in order; they are the single source of truth and anchor Merkle proofs. * Aggregates group related events, enforce identifiers and schemas, and expose the current state derived from the full event stream. * Snapshots cache that derived state at specific versions so reads and plugins stay fast without replaying the entire history. * Replays rebuild snapshots from events; snapshots prove what state existed at a version; aggregates tie both to a consistent identity. * Derived aggregate state can also serve reads/plugins directly when you don't need a cached snapshot. ```mermaid theme={null} flowchart Write[Write/API call] --> Event[Events
immutable facts] Event -->|append| Aggregate[Aggregate stream
versioned timeline] Aggregate -->|replay| State[Derived state] State -->|serve| Reads[Reads / Plugins] State -->|persist| Snapshot[Snapshots
versioned cache] Snapshot -->|serve| Reads Aggregate -->|Merkle root| Proof[Integrity proofs] Event -.->|replay| Snapshot ``` ## Snapshots * Materialize deterministic snapshots to serve reads quickly without replaying full histories. * Schedule rebuilds or let EventDBX auto-refresh them once schema thresholds are reached; add `dbx snapshots create` for point-in-time checkpoints before migrations or audits. * Tune retention, compression, and pruning strategies to balance cost versus recovery speed; pair exports with Merkle roots to prove integrity. [Deep dive → Snapshot guide](../aggregate-events/snapshot) ## Aggregates * Keep identifiers stable, scoped, and easy to shard across teams. * Use metadata to track lifecycle decisions, ownership, and reconciliation markers. * Avoid coupling unrelated concerns into a single aggregate—fan out instead; use domains to hard-wall environments and tenants via `dbx checkout`. [Deep dive → Aggregate guide](../aggregate-events/aggregate) ## Events * Version payload schemas carefully so older events remain readable. * Lean on metadata for traceability, idempotency, and auditing hooks; `extensions-only` payload mode lets plugins consume hints without payloads. * Rebuild downstream read models by plugin-replaying events (`dbx plugin replay … --payload-mode `) whenever integrations drift; queue backoff keeps writes decoupled from slow consumers. [Deep dive → Event guide](../aggregate-events/events) # Authorization Source: https://docs.eventdbx.com/core-concepts/authorization Scope EventDBX tokens with actions, resources, tenants, and sensible TTLs. EventDBX keeps authorization simple and explicit: every request carries a bearer token, and that token spells out which verbs it can execute, which aggregates it can touch, and which tenants it applies to. This page shows how to express those scopes with `--action`, `--resource`, and `--tenant` so you can hand out least-privilege credentials that mirror the way you partition domains and services. Tokens gate every control-socket call. They carry three main claims: * **Actions** – verbs such as `aggregate.read` or `schema.write`. * **Resources** – what the action can touch; defaults to `"*"` unless you scope it. * **Tenants** – which tenants the token may access (required by `dbx token generate`; repeat `--tenant` for more). Wildcard matching * `*` matches any length; `?` matches a single character. * `*.*` (actions) and `*` (resources) are root-level patterns—use sparingly. ## Common actions * Aggregates (only): `aggregate.read`, `aggregate.create`, `aggregate.append`, `aggregate.archive` * `aggregate.read` – list aggregates, get one, list events, list/get snapshots, select fields. * `aggregate.append` – apply/patch events and create snapshots. * `aggregate.archive` – archive or restore an aggregate. * `aggregate.create` – create a new aggregate. * `schema.read`, `schema.write` * `tenant.manage` * `*.*` (root; equivalent to “all actions”) Actions without resources * `schema.read`, `schema.write`, and `tenant.manage` only check the action; `--resource` is ignored for these. Passing none is fine. ## Resource patterns * `aggregate::` – single aggregate (`aggregate:orders:123`) * `aggregate::*` – any id of that type (`aggregate:orders:*`) * `aggregate:*:*` – any aggregate * `*` – any resource (CLI default) If a command checks only the action (for example, `schema.read`), you can omit `--resource`; the CLI will set it to `"*"`. ## Examples ### Admin (root) token for one tenant ```bash theme={null} dbx token generate \ --group platform --user admin \ --action "*.*" --resource "*" \ --tenant prod \ --ttl 7200 ``` ### Read-only access to one aggregate type ```bash theme={null} dbx token generate \ --group analytics --user reader \ --action aggregate.read \ --resource "aggregate:orders:*" \ --tenant prod \ --ttl 1800 ``` ### Writer for a service (create + append) ```bash theme={null} dbx token generate \ --group svc-orders --user api \ --action aggregate.create \ --action aggregate.append \ --resource "aggregate:orders:*" \ --tenant prod \ --write-limit 1000 \ --ttl 900 ``` ### Narrow access to a single aggregate ```bash theme={null} dbx token generate \ --group support --user agent \ --action aggregate.read \ --action aggregate.append \ --resource "aggregate:customers:42" \ --tenant prod \ --ttl 600 ``` ### Schema admin ```bash theme={null} dbx token generate \ --group platform --user schemas \ --action schema.write \ --resource "*" \ --tenant prod ``` Tips * Prefer short TTLs and `--write-limit` for automation. * Repeat `--tenant` to authorize multiple tenants; calls fail if the tenant is not present in the token. * Use `dbx token refresh` to rotate without re-provisioning clients. ## How this maps to other systems * Think of `action` as an IAM “permission” (for example, `aggregate.append` ≈ “write events”), and `resource` as an IAM resource ARN-like filter (`aggregate:orders:*` ≈ “all orders aggregates”). * Wildcards behave like glob patterns, not regex; keep them narrow to avoid overreach in audits. * Plugins and SDKs only need bearer tokens—no cookies or sessions—so you can mint short-lived, task-specific credentials the same way you would scoped API tokens in other platforms. * If you federate identity elsewhere (OAuth2/JWT providers), you can mint EventDBX tokens from a service role and pass tenant/action/resource scopes that mirror the upstream role’s permissions. # Best practices Source: https://docs.eventdbx.com/core-concepts/best-practices Field-tested tips for modeling, securing, and operating EventDBX with fast writes and reliable fan-out. EventDBX stays fast when writes never wait on downstream work. Use these practices to keep that promise while giving read models, auditors, and operators the data they need. ## Model and domains * Create one domain per bounded context or tenant; switch with `dbx checkout --create` and persist remote settings there so `push`/`pull`/`watch` reuse stored credentials. * Keep aggregate shapes small and purpose-built; avoid “god” aggregates that mix unrelated workflows. * Prefer task-oriented events (what happened) over CRUD deltas; payloads should read like facts you can replay later. ## Schemas and restriction * Start with `restrict=default` to allow undeclared aggregates while validating declared ones; move to `restrict=strict` once schemas stabilise. * Capture allowed fields per event and use column rules (required, length, range, regex, format) to encode intent. Snapshot thresholds belong in the schema so they travel with the aggregate. * Reject ambiguous names up front: keep aggregate and event names snake\_case and avoid overloaded fields that mean different things in different events. ## Snapshots and verification * Use schema-driven snapshot thresholds to refresh high-churn aggregates frequently; call `dbx snapshots create` for point-in-time checkpoints before migrations or audits. * Pair exports with Merkle proofs: `dbx aggregate verify` (or snapshot metadata) gives auditors a hash to detect tampering without replaying traffic. * Prune old snapshots only when storage pressure demands it; they are cheap, deterministic rebuild points for downstream systems. ## Plugins, payload modes, and replay * Configure plugins with the smallest payload mode they need (`event-only`, `state-only`, `event-and-schema`, or `extensions-only` for metadata-only fan-out) to minimise blast radius and cost. * Let the queue absorb backpressure; do not couple plugin latency to writes. Monitor retries and prune done jobs on a schedule. * Use `dbx plugin replay [] --payload-mode ` to reseed read models or validate new selectors without re-enabling writes. Keep `emit_events=true` only for plugins that should consume the queue. ## Security and operations * Set a DEK once (`dbx config --dek `) so payloads, snapshots, and tokens encrypt at rest; rotate Ed25519 keys and tokens regularly with scoped TTLs and write limits. * Isolate experiments with domains and domain-scoped tokens; avoid reusing production credentials on non-prod remotes. * Before replication, push schemas first, then data; abort on divergence and recover by cloning from the authoritative domain instead of forcing mismatched histories. * Treat the staging file (`aggregate apply --stage`) as a batch tool, not a long-term buffer; commit or clear it to avoid surprises. * Issue fine-grained auth tokens with `--action` and `--resource` (or env defaults) so each token only touches the aggregates and verbs it needs. Pair short TTLs with scoped write limits for automation; use `dbx token refresh` instead of reissuing secrets. # Domain Context Source: https://docs.eventdbx.com/core-concepts/domain-context Organize work into bounded contexts and switch between them without losing integrity. Domains keep schemas, aggregates, and replication schedules scoped to a single bounded context. Operators can hop between them instantly, but each remains isolated on disk so experiments never corrupt production roots. Switching domains is a daily task for anyone running multiple environments (dev, staging, prod) or business units (payments, risk, fulfillment). EventDBX keeps every domain isolated on disk so context switches are quick yet safe. EventDBX uses “domain” and “tenant” interchangeably: `dbx tenant list` enumerates the same contexts that `dbx checkout` activates. ## Checkout workflow ```bash theme={null} dbx tenant list #> tenant shard quota_mb usage_mb count #> payments-prod shard-0013 30 0 1 #> payments-dev shard-0009 - 0 1 dbx checkout payments-dev dbx status ``` `dbx tenant list` is the easiest way to discover available domains and where they live. `dbx checkout` updates the active RocksDB path, snapshots directory, config overrides, and default plugin namespace. Follow it with `dbx status` to confirm where commands will apply before you mutate data. ## Attach remotes per domain Associate a remote endpoint while you switch contexts so replication commands have everything they need: ```bash theme={null} dbx checkout payments-prod \ --remote replica.acme.internal:6363 \ --token "$REPLICA_TOKEN" \ --remote-tenant acme-prod ``` `dbx checkout` persists the remote address, token, and remote tenant under the domain’s `remote.json`, so later `dbx push`/`dbx pull`/`dbx watch` calls can use the stored values without retyping credentials. Re-run `dbx checkout` with just `--remote`, `--token`, or `--remote-tenant` to rotate one piece, and add `--port` when the remote listens on a non-default socket. Common flags: `-c/--create` to make the domain if it does not exist, positional `NAME` or `-d/--domain` to pick the context, `--delete --force` for cleanups. Use host:port on `--remote` (defaults to port 6363 if omitted) and pair it with `--token` plus `--remote-tenant` when the remote hosts multiple tenants: ```bash theme={null} dbx checkout -c rc11 \ --remote 127.0.0.1:6464 \ --token "testng" \ --remote-tenant rc11 ``` ## Naming and structure tips * Prefix domains with the product or bounded context, suffix with the environment (`orders-prod`, `orders-staging`). * Let the configured data directory handle sharding; attach storage per shard if you want independent backup schedules instead of hard-coding per-domain paths. * Commit a `.dbx-domain` file at the root of each repo to remind developers which domain their scripts expect. ## Replication awareness Replication jobs are domain-aware. A standby that tracks `payments-prod` retains its own queue offsets. When you switch to `payments-dev`, replication commands only touch that domain: ```bash theme={null} dbx checkout payments-dev dbx push dev-standby ``` Quick replication examples that reuse the remote address/token stored via `dbx checkout`: ```bash theme={null} # One-off sync to the configured remote (all aggregates) dbx push remote1 # Pull updates back down (integrity checks run automatically) dbx pull remote1 # Continuous streaming with safety checks dbx watch remote1 --mode push --background --skip-if-active dbx watch status remote1 ``` This isolation prevents accidental promotion of the wrong dataset during incidents. ## Multi-tenant operators If you host EventDBX for multiple customers, run one domain per tenant. Combine `dbx checkout` with shell prompts (`PS1`) or terminal titles that show the active domain so humans never confuse tenants. Automation should always pass `--domain` explicitly. The end result: you can switch contexts in seconds without worrying that your commands will leak into another environment or corrupt an unrelated business line. # Philosophy Source: https://docs.eventdbx.com/core-concepts/philosophy Understand the principles that shape EventDBX—flexibility, integrity, security, auditability, and extensibility. EventDBX is fundamentally built around these ideas: append-only truth, reproducible state, and integrations that never block writes. This page is the high-level compass; jump into the deep dives when you need field-by-field details or CLI examples. ```mermaid theme={null} flowchart TD classDef pillar fill:#0f172a,stroke:#0f172a,color:#fff classDef concept fill:#e5e7eb,stroke:#0f172a,color:#0f172a Truth[Append-only truth] --- State[Reproducible state] State --- NonBlock[Integrations never block writes] NonBlock --- Truth Flexibility([Flexibility]) --- NonBlock Extensibility([Extensibility]) --- NonBlock Integrity([Integrity]) --- State Auditability([Auditability]) --- State Security([Security]) --- Truth class Truth,State,NonBlock pillar class Flexibility,Extensibility,Integrity,Auditability,Security concept ``` ## Flexibility * Start in `default` mode so teams can append JSON payloads without schema friction, then switch to `strict` once data shapes stabilise. * Use `dbx checkout` to spin up isolated domains (or delete them) and persist remote settings for replication. This keeps experiments, bounded contexts, and environments walled off. * Feed each read model only the slices it needs by tailoring plugin payload selectors (including the `extensions-only` mode for metadata-only fan-out) and overriding them ad hoc with `dbx plugin replay --payload-mode …`. [Deep dive → Flexibility](./flexibility) ## Data integrity * Per-aggregate Merkle trees and deterministic snapshots keep proofs cheap to recompute and compare across nodes. * Schemas use explicit column definitions with built-in types, formats, and validation rules (required, length, range, regex) so intent stays unambiguous. * Toggle `dbx restrict` between permissive and strict modes to match your environment without rewriting history. Auto-snapshots fire at configurable thresholds; create point-in-time snapshots on demand when you need a checkpoint. [Deep dive → Data integrity](./data-integrity) ## Security * Encrypt payloads, snapshots, and tokens at rest with the DEK; enforce Noise handshakes in transit so credentials never cross plaintext. * Append-only storage plus Merkle roots make the log immutable and tamper-evident. * Generate Ed25519 tokens per role with fine-grained scopes (TTL, write limits), rotate them regularly, and keep authentication passwordless by design. [Deep dive → Security](./security) ## Auditability * Every aggregate keeps an append-only history plus deterministic snapshots for quick reads. * Export proofs to auditors or partner teams without replaying production traffic; include Merkle roots with exported snapshots. * Stream or plugin-replay events with precise payload modes to retrace every change, then use CLI helpers to archive, restore, and verify aggregate timelines. [Deep dive → Auditability](./auditability) ## Extensibility * Build plugins that emit just the payload slices downstream systems expect, and let the queue absorb backpressure so writes stay fast. * Extend schema rules, column types, transports, or plugin payload modes without stopping the daemon. * Bridge EventDBX into existing buses, CDC pipelines, serverless workers, or ad hoc replays—plugins operate asynchronously and can be toggled per domain. [Deep dive → Extensibility](./extensibility) # Relationships Source: https://docs.eventdbx.com/core-concepts/relationships Link aggregates with reference fields, resolve them on reads, and trace inbound referrers safely. Reference columns let you model relationships without leaving EventDBX’s append-only flow. They canonicalise identifiers, enforce integrity rules at write time, and keep an index of inbound links so deletes/archives stay safe. ## Declare reference fields * Mark fields as references with `dbx schema field --type reference [--required] [--reference-integrity ] [--reference-tenant ] [--reference-aggregate ] [--reference-cascade ]`. * Accepted shapes: `domain#aggregate#id`, `aggregate#id`, or `#id` (shorthands fill in the current domain and/or aggregate type). Values are normalised to canonical lowercase segments on write. * Integrity: `strong` (default) rejects writes when the target aggregate is missing or forbidden; `weak` keeps the canonical text so you can backfill later. * Constraints: pin references to a tenant/domain and/or aggregate type to prevent drift. Cross-tenant targets are rejected. * Cascade: `none`/`restrict` block archive/delete while referrers exist; `nullify` auto-patches referrer fields to `null` when the target is archived or removed. * Migrating legacy IDs? After updating the schema, run `dbx aggregate migrate --event --field [,...]` to rewrite values into canonical form and backfill the reference index. ## Model relationships * One-to-many / many-to-one: put a reference on the “many” side (e.g., `order.customer_ref`). Use referrer lookups to list all children for a parent without duplicating state. * Many-to-many: create a join aggregate (e.g., `membership` with `user_ref` and `group_ref`), keep both refs `strong`, and query referrers from either side to fan out. * Self-references and hierarchies: reference the same aggregate type (or even the same instance) to model trees; resolution depth limits (default 2, max 5) prevent runaway cycles. ## Resolve relationships on reads * Attach resolved targets to reads with `dbx aggregate get --resolve [--resolve-depth ]`; the response adds a `resolved` tree containing referenced aggregates plus status per hop (`ok`, `not_found`, `forbidden`, `cycle`, `depth_exceeded`). * Bulk listings support the same flag (`dbx aggregate list --resolve --json ...`); depth defaults to `reference_default_depth` (configurable) and is capped by `reference_max_depth`. * Resolution only follows references within the active tenant/domain and requires the caller to have read access to each target. ## Inspect referrers and guard lifecycle changes * See who points to an aggregate with `dbx aggregate referrers [--json]`; results include `aggregate_type`, `aggregate_id`, and the `path` of the reference field. * The control API exposes the same data via `listReferrers`, enabling UI visualisations or pre-flight checks in automation. * Archive/remove protections: referrers with `none`/`restrict` cascades block the operation; `nullify` cascades are auto-patched so the target can be safely archived or deleted. # Replication Source: https://docs.eventdbx.com/core-concepts/replication Design standby replicas, promote nodes safely, and automate push/pull/watch workflows. EventDBX replication is flexible: you can run a primary/standby pattern or a multi-writer mesh. `push`, `pull`, and `watch` keep Merkle trees aligned across nodes; divergences stop the sync so you never rewrite history silently. Replication is asynchronous, not a distributed transaction. Expect lag that depends on your `watch` interval (`--interval `, default 300) or how often you run `push`/`pull`. ## Remote setup and schemas * Remote connection details (address, token, optional tenant) are stored per domain in `remote.json`; configure them with `dbx checkout -d --remote --token "" [--remote-tenant ]` so `push`/`pull`/`watch` know where to talk. * Schemas replicate separately with `dbx push schema ` / `dbx pull schema `; `--publish` activates the pushed snapshot on the remote. * Typical flow: checkout and configure the remote, push schemas, then `dbx push [--aggregate --id --concurrency N]`; later `dbx pull [...]` to ingest new remote events. `dbx watch --mode push|pull|bidirectional --interval [--background]` schedules recurring syncs (see README.md and docs/cli.md). ## Pattern 1: Standby (one writer) * Pick a single writer (primary) per domain; configure standbys as pull-only or accept pushes from the primary. * Use `dbx watch --mode push` on the primary to stream changes out, or `--mode pull` on standbys to ingest from the primary. Either way, only one side writes. * Before promoting a standby, compare Merkle roots and last offsets; cut writes to the old primary, then flip clients. * Example (hospital hub-and-spoke): the regional data center writes; each facility runs `dbx watch hub --mode pull` to stay current. If the hub fails, promote one facility after verifying hashes. * Attach remotes per domain (persisted in `remote.json`) with `dbx checkout -d --remote --token [--remote-tenant ]` so subsequent `push`/`pull`/`watch` calls reuse stored endpoints and credentials. ```mermaid theme={null} flowchart LR Clients --> Primary[Primary writer] Primary -->|dbx watch --mode push| StandbyA[Standby A] Primary -->|dbx watch --mode push| StandbyB[Standby B] StandbyA -->|promote after hash check| Primary StandbyB -.->|pull-only| Primary classDef node fill:#e5e7eb,stroke:#0f172a,color:#0f172a classDef writer fill:#0f172a,stroke:#0f172a,color:#fff class Primary writer class Clients,StandbyA,StandbyB node ``` ## Pattern 2: Mesh (multi-writer) * Allow writes in multiple regions, then synchronise with `dbx watch --mode bidirectional` between peers. Use fine-grained scopes (`--aggregate`, `--id`) to limit blast radius if needed. * Conflicts are avoided by design: EventDBX aborts on diverging histories. If two writers append conflicting events to the same aggregate, resolve by choosing the authoritative timeline and reseeding the other node. * Example (hospital mesh): facilities in different regions write local events (admissions, vitals), and peers run `dbx watch --mode bidirectional --aggregate patient` so patient aggregates converge. If a conflict surfaces, pick the authoritative facility and re-sync from it. ```mermaid theme={null} flowchart LR subgraph Region A A[Writer A] end subgraph Region B B[Writer B] end subgraph Region C C[Writer C] end A <--> |dbx watch --mode bidirectional| B B <--> |dbx watch --mode bidirectional| C C <--> |dbx watch --mode bidirectional| A classDef node fill:#e5e7eb,stroke:#0f172a,color:#0f172a class A,B,C node ``` ## Replication command reference ### watch Streams changes continuously with automatic retries and backoff. ```bash theme={null} dbx watch standby-a --mode push --background dbx watch primary --mode pull --aggregate patient dbx watch peer-1 --mode bidirectional --aggregate patient --id p-001 --run-once ``` * Modes: `push` (send to remote), `pull` (ingest from remote), `bidirectional` (push then pull in one cycle). * Mechanics: reuses the same safety rails as one-off push/pull (version guardrails, Merkle root parity, archive flag sync) and reads remote settings from `remote.json` written by `dbx checkout`. * Scopes: `--aggregate `, `--id ` to target specific streams. * Flags: `--background` to daemonise, `--run-once` for a single pass, `--skip-if-active` to avoid overlap (state is persisted so `dbx watch status ` can report last result), `--concurrency ` to tune parallelism. ```mermaid theme={null} flowchart TD Start([dbx watch ]) --> Mode{Mode} Mode -->|push| Cycle[Run push cycle] Mode -->|pull| Cycle Mode -->|bidirectional| BiCycle[Run push then pull] Cycle --> Check{Active watcher?} BiCycle --> Check Check -->|yes --skip-if-active| Sleep[Sleep until next interval / exit if run-once] Check -->|no| Work[Parallel push/pull workers
version check + Merkle parity
append/import events
sync archive flags] Work --> Finish[Record result] Finish --> Sleep Sleep -->|run-once| End([Exit]) Sleep -->|interval elapsed| Mode ``` ### push One-off transfer from the current domain to a remote (daemon must be stopped for the target domain). ```bash theme={null} dbx push standby-b dbx push standby-b --aggregate patient --id p-001 --concurrency 8 ``` * Scopes: `--aggregate `, `--id `; otherwise every aggregate is considered. * Throughput: `--concurrency ` (defaults to CPU count) fans work across aggregates. * Mechanics: ensures the local RocksDB store exists (`CURRENT` file present) and the daemon is stopped, then for each aggregate refuses to push if the remote is ahead, compares Merkle roots at the remote version, creates the aggregate remotely if missing, appends only the missing events, and mirrors archive status. * Consistency: aborts on divergence, exits cleanly when everything is already synchronised, and never overwrites remote history. ### pull One-off ingest from a remote into the current domain. ```bash theme={null} dbx pull primary dbx pull primary --aggregate patient --id p-001 --concurrency 8 ``` * Scopes: `--aggregate `, `--id `. * Throughput: `--concurrency ` across aggregates. * Mechanics: lists matching aggregates from the remote, refuses to run if the local copy is ahead, fetches only new events, recomputes the Merkle root to validate the combined history, imports events, and synchronises archive flags locally. * Safety: aborts on any version/hash mismatch to avoid rewriting local history and returns success when nothing changed. ### Monitoring and recovery * Commands exit non-zero on failure; wrap `push`/`pull` in retries or let `watch` run continuously. * Monitor `/metrics` (Prometheus) for replication counters and lag. * On failure: identify the last successful aggregate/version, rerun scoped `push|pull` or restart `watch` (it resumes from its cursor). If a divergence is detected, pick the authoritative side, reseed with `push|pull`, then restart `watch`. * Safety guarantees: refuses overwrites when versions diverge, checks Merkle root parity before applying deltas, and short-circuits when there is nothing new. * Always push schemas first (`dbx push schema `) before syncing events so validation stays in lockstep. # Schema Definition Source: https://docs.eventdbx.com/core-concepts/schema-definition Design event and snapshot schemas that balance iteration speed with strict guarantees. Schema enforcement in EventDBX is opt-in and gradual. Start flexible, codify columns as you learn, then tighten validation once the model hardens. Schemas live in `schemas.json` (per domain/tenant), drive validation for every write, and include snapshot thresholds so materialisation stays predictable. ## What schemas control * Field allow-lists per event (which fields each event may set). * Column types and rules (required/optional, ranges, regexes, formats, nested properties). * Snapshot cadence per aggregate (`snapshot_threshold`) plus field visibility (`schema hide`). * Versioning and history via publish/activate so changes stay auditable. ## Define fields and events * Supported column types: `integer`, `float`, `decimal(precision,scale)`, `boolean`, `text`, `timestamp`, `date`, `json`, `binary`, `object`. * Allow fields per event: `dbx schema alter --add ` (or `--remove/--set/--clear`). * Shape fields: `dbx schema field --type [--required|--not-required] [--format ...] [--length-min ... --length-max ...] [--range-min ... --range-max ...] [--regex ...] [--contains ...] [--properties ]`. * Snapshot cadence belongs in the schema: `dbx schema create ... --snapshot-threshold ` and `dbx schema alter ... --snapshot-threshold ` override the global default. [Deep dive → Column definition](../schema-definition/column-definition) ## Validation and formats * Inline rules via `dbx schema field`: required/optional, contains/does\_not\_contain, regex (repeatable), length, numeric/date range, nested `properties` for objects. * Apply semantic formats with `--format `; drop them with `--clear-format`. * Hide sensitive fields from aggregate detail responses: `dbx schema hide --aggregate --field `. * Test in `restrict=default`, then enforce in `restrict=strict` once confident. [Deep dive → Rules](../schema-definition/rules) · [Deep dive → Field formats](../schema-definition/format) ## Versioning workflow Schemas are versioned artifacts. Edit via the CLI or by touching `schemas.json`, then: 1. `dbx schema publish` – snapshot the current schema (per tenant/domain). 2. `dbx schema activate --version ` – make a published version active. 3. `dbx schema reload` – force the daemon to pick up the active version if needed. Inspection tools: * `dbx schema history` – list published versions with who/when/why. * `dbx schema show --version ` – view a specific version. * `dbx schema diff --from --to ` – compare versions (patch/unified/split). * `dbx schema rollback --version ` – restore a prior version as active. Validation helpers: * `dbx schema validate --event --payload ` – test payloads against the active schema. * `dbx schema list` – inspect current aggregates and snapshot thresholds. Even when editing `schemas.json` directly, publish/activate to keep history auditable and nodes consistent. # Deployment Source: https://docs.eventdbx.com/deployment Run EventDBX under Docker, wire up client libraries, and operate the daemon in production. ## Run with Docker ```bash theme={null} # Pull the latest published release docker pull thachp/eventdbx:latest # Start the daemon with persistent storage and required ports docker run \ --name eventdbx \ --detach \ --publish 7070:7070 \ --publish 6363:6363 \ --volume "$PWD/data:/var/lib/eventdbx" \ thachp/eventdbx:latest ``` * The image publishes to Docker Hub as `thachp/eventdbx`, so `docker pull` always grabs the latest signed release you can test locally on macOS, Linux, or WSL without extra installers. * Mount a persistent volume so Merkle trees, schemas, and queued jobs survive restarts. * Port `7070` exposes the HTTP API and `/metrics` endpoint for monitoring; `6363` is the Noise-secured control + replication socket the CLI, client SDK, `watch`, and peers use—apps typically talk via the SDK on `6363`. * Pass `--env EVENTDBX_RESTRICT=strict` or update `config.toml` inside the volume to enforce schema validation globally. The Docker image bundles the same `dbx` binary as the CLI, so you can exec into the container and run administrative commands without installing extra tooling. ## Promote to cloud VMs When you are ready for production, reuse the same Docker Hub image on a virtual machine and keep the data directory on attached block storage: * **Azure:** Attach a Managed Disk (Premium SSD or Ultra) to the VM, format it (e.g., `/mnt/eventdbx`), and bind-mount it into Docker with `--volume /mnt/eventdbx:/var/lib/eventdbx`. * **GCP:** Add a Persistent Disk, mount it under `/var/lib/eventdbx`, and either run the container via `gcloud compute ssh` or use a startup script that runs `docker run ... --volume /var/lib/eventdbx:/var/lib/eventdbx`. * **AWS:** Provision an EBS volume (gp3 or io2), attach it to the EC2 instance, mount it (such as `/data/eventdbx`), and pass that mount into Docker. In every cloud, ensure the VM’s firewall allows the same `7070`/`6363` ports and that the attached volume is marked to persist independently of the VM lifecycle so you never lose aggregate history if the machine reboots or is replaced. ## Cloud reference diagram ```mermaid theme={null} flowchart subgraph Clients Users[Clients / Services] end Users --> App[App / Service using Client SDK] App -->|Noise| Daemon[EventDBX daemon
Docker on VM] CLI[CLI / Admin] -->|Tunnel| Daemon Daemon -->|append/query| Storage[Block storage
/var/lib/eventdbx] Daemon -->|/metrics| Metrics[Prometheus / Grafana] Daemon -->|plugin queue| Plugins[HTTP/TCP/Process plugins] Plugins --> Dest[Downstream systems] Daemon -.->|push/pull| Peer[Remote EventDBX] classDef node fill:#e5e7eb,stroke:#0f172a,color:#0f172a classDef infra fill:#0f172a,stroke:#0f172a,color:#fff class Daemon infra class Users,App,CLI,Storage,Metrics,Plugins,Dest,Peer node ``` ## Security defaults * EventDBX is designed to sit behind your application servers or API gateway, not as a public-facing endpoint. Keep the container on a private VPC / VNet subnet and restrict inbound traffic to trusted app hosts or VPN jump boxes. * If ports `7070`/`6363` accidentally reach the public internet, the Noise Protocol handshake plus JWT-based auth still gate every request, so unauthenticated clients cannot replay traffic or bypass signature checks—but plan to rotate credentials and lock the network down immediately. * Tokens use Ed25519 signatures configured under `[auth]` in `config.toml`. Store the private key on the server only; distribute the public key to services that validate tokens. * Event payloads, aggregate snapshots, and `tokens.json` encrypt transparently when you provide a data-encryption key via `dbx config --dek `. Metadata (aggregate ids, Merkle roots) stays readable so plugins and integrity checks continue working. ## Observability * Prometheus-compatible metrics live under `/metrics` on the HTTP port. They include request latency, plugin queue depth, and retry counters so you can wire dashboards in Grafana, Datadog, or any collector that speaks the Prometheus exposition format. * Use `dbx queue`, `dbx queue retry`, and `dbx queue clear` to inspect or remediate jobs that failed fan-out. Retries use exponential backoff until the plugin succeeds or the aggregate disappears. ## Backups and restores Stop the server before snapshotting the data directory to avoid partial Merkle trees. ```bash theme={null} dbx backup --output ~/backups/eventdbx.tar.gz dbx restore --input ~/backups/eventdbx.tar.gz --data-dir /var/lib/eventdbx ``` Pass `--force` to overwrite non-empty destinations and `--data-dir` to restore into a custom path. # Features Source: https://docs.eventdbx.com/features Dive into EventDBX capabilities: immutable storage, validation modes, column types, and performance characteristics. EventDBX focuses on the write side of CQRS so you can capture every mutation as an immutable event, recompute state on demand, and let plugins deliver data to downstream systems. This page expands on the capabilities highlighted in the overview. ## Core capabilities * **Flexible JSON payloads** – events accept arbitrary JSON; scalars normalize into strings for consistent state tracking, while nested objects remain queryable through filters like `payload.status = "active"`. * **Immutable data structure** – once persisted, events cannot be edited or deleted. You can archive aggregates or shift them into long-term storage, but history stays intact for audits and compliance. * **Event sourcing and replay** – replay stored events through plugins with `dbx plugin replay ` to rebuild read models or inspect historical state. Updates are expressed as events rather than in-place writes. * **Merkle tree integration** – every aggregate maintains a Merkle tree of its events so `dbx aggregate verify` can detect tampering by recomputing the root hash. * **Built-in audit trails** – administrators can issue scoped tokens for auditors, granting read-only access to selected aggregates and their events. * **Extensible read models** – plugins consume the job queue and can request only the slices they need (event payload, materialized state, schema, or combinations). This keeps the write path isolated from read-model concerns. * **Domain sharding** – create multiple domains to hard-segregate data; each shard keeps its own Merkle tree, quotas, and replication targets so tenants cannot bleed into each other's history. * **Remote replication** – `dbx push`, `dbx pull`, and `dbx watch` keep domains in sync with Merkle + version checks. Divergent histories abort automatically instead of rewriting events. * **Observability and security** – Prometheus metrics cover HTTP traffic and plugin queue health, while Ed25519-signed tokens plus optional payload encryption protect access. ## Restriction modes EventDBX enforces schemas based on the restriction mode you choose at startup (`dbx start --restrict=`): | Mode | Flag | Description | | ----------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | | **Off** | `--restrict=off` or `false` | No validation. Ideal for prototyping when schemas are still evolving. | | **Default** | `--restrict=default` or `true` | Validates whenever a schema exists but allows aggregates without one. Matches legacy behaviour and helps teams roll out validation gradually. | | **Strict** | `--restrict=strict` | Every aggregate must declare a schema before events can be appended. Missing schemas fail fast with clear errors. | Switch modes without migrating data; just restart the daemon with the desired flag. ## Column definitions Schemas are powered by a `column_types` map that defines both storage type and validation rules. | Type | Accepted input | Notes | | -------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------- | | `integer` | JSON numbers or strings that parse to signed 64-bit integers | Rejects values outside the `i64` range. | | `float` | JSON numbers or numeric strings | Stored as `f64`; accepts scientific notation. | | `decimal(precision,scale)` | JSON numbers or strings | Enforces total digits ≤ precision and fractional digits ≤ scale. | | `boolean` | JSON booleans, `0/1`, `"true"`, `"false"` | Normalized to literal `true`/`false`. | | `text` | UTF-8 strings | Combine with `length`, `contains`, or `regex`. | | `timestamp` | RFC 3339 timestamps | Normalized to UTC. | | `date` | `YYYY-MM-DD` strings | Parsed as calendar dates without timezones. | | `json` | Any JSON value | No per-field validation; store free-form payloads. | | `binary` | Base64-encoded strings | Rules operate on decoded byte lengths. | | `object` | JSON objects | Enable nested validation with the `properties` rule. | Rules you can layer on a column: * `required` – field must be present on every event payload. * `contains` / `does_not_contain` – substring checks for `text`. * `regex` – one or more patterns for `text`. * `format` – built-in validators such as `email`, `url`, `credit_card`, `camel_case`, `snake_case`, `pascal_case`, `upper_case_snake_case`, `country_code`, `iso_8601`, and `wgs_84`. * `length` – `{ "min": , "max": }` for `text` or decoded `binary` payloads. * `range` – `{ "min": , "max": }` for numeric or temporal types. * `properties` – nested `column_types` for `object` fields, letting you keep applying the same rule set recursively. ## Aggregate operation costs Most commands boil down to a handful of RocksDB reads/writes. Use the table below when planning workloads or sizing clusters: | Operation | Time complexity | Notes | | ------------------ | ------------------------- | ---------------------------------------------------------- | | `aggregate list` | O(k) | `k` = requested page size (defaults to `list_page_size`). | | `aggregate get` | O(log N + Eₐ + P) | One state read plus optional event scan and JSON parsing. | | `aggregate select` | O(log N + P\_selected) | Same state read as `get`; dot-path traversal is in-memory. | | `aggregate apply` | O(P) | Payload validation + merge + append in one batch write. | | `aggregate patch` | O(log N + P + patch\_ops) | Reads current state, applies JSON Patch, appends delta. | | `aggregate verify` | O(Eₐ) | Recomputes the Merkle root across events. | Staged events live in `.eventdbx/staged_events.json`. Queue them with `aggregate apply --stage`, preview with `aggregate list --stage`, and flush the entire batch via `aggregate commit` for all-or-nothing persistence. ## Performance Testing Performance benchmarks and workload scenarios live in the [eventdbx-perf repository](https://github.com/thachp/eventdbx-perf). Tests run on the same host with Docker-based databases, a single-threaded client, and datasets up to 10 M records. Latency numbers represent mean operation time; throughput is operations per second (converted from ns → µs). EventDBX consistently outperforms PostgreSQL (\~2×), MongoDB (\~3×), and SQL Server (>10×) while keeping latency below one microsecond. # Guidelines Source: https://docs.eventdbx.com/guidelines Shared engineering, documentation, and community practices for the EventDBX project. These guidelines keep EventDBX cohesive across code, docs, and community touchpoints. Pair them with the [Contributing guide](/contributing) for step-by-step workflows. ## Core principles * **Security by default** – assume every change touches sensitive data; never ship secrets, always validate payloads, and keep dependency audits flowing. * **Deterministic builds** – document exact toolchain versions in `README.md` or per-component guides so other contributors reproduce results without guesswork. * **Traceable decisions** – capture architecture or product decisions in RFCs, issues, or discussions and link them from the relevant docs page. ## Engineering practices 1. Start from a fresh branch named `feat/` or `fix/`. 2. Keep commits reviewable and describe intent, not implementation details. 3. Add or update automated tests (`cargo test`, integration harnesses) whenever behavior, schemas, or CLI surfaces change. 4. Run `cargo fmt` and `cargo clippy --all-targets --all-features` before opening a pull request so reviewers see consistent style and lint hygiene. Use feature flags rather than long-lived branches for experimental functionality, and prefer backwards-compatible migrations so downstream plugins stay stable. ## Documentation standards * Every new feature, command, or configuration surface needs at least a short note in the docs. * Frontmatter (`title`, `description`) should be specific enough to appear alone in search results. * Add `Tip`, `Warning`, or tables when they help the reader act faster; avoid duplicating long CLI outputs unless they illustrate a concept. * Link to related sections (for example, `/core-concepts/schema-definition`) so readers can build context progressively. ## Issue and PR hygiene * Open issues with reproduction steps, environment details, and the observed vs. expected behavior. * Reference issues from pull requests using `Fixes #123` (or `Relates to #123` for partial work) so automation can trace deployments. * Surface breaking changes in both the PR description and `CHANGELOG.md` under the “Unreleased” heading. ## Community expectations We follow the [Contributor Covenant](https://www.contributor-covenant.org/). Interactions in issues, discussions, and Slack should stay respectful, inclusive, and focused on the work. Use private channels or the security mailing list when disclosure requires it. # Overview Source: https://docs.eventdbx.com/index Event-sourced write-side database with append-only integrity, Merkle-verified audit trails, and pluggable read models for teams building event-driven apps. EventDBX is an append-only, event-sourced write database that keeps your aggregates verifiable, replicas aligned, and downstream systems hydrated without touching the write path. Instead of hand-building bespoke schemas up front, you capture events, validate them with Merkle-backed integrity checks, and fan them out through plugins that specialize in the read side. ## Core Motivations EventDBX succeeds when it: * Enables developers to quickly build applications without hand-designing schemas or wrestling with ORMs; schemas stay an afterthought you tighten later. * Enforces task-based thinking over CRUD. Start from what customers tell you and persist what they know instead of designing tables first and training users to fit them; workflows stay aligned with intent. * Provides a trustworthy source of truth for other systems. The append-only event log plus Merkle verification prevents silent edits—no administrator can tweak a column without leaving evidence—so downstream systems can trust replicated histories instead of worrying about invisible hotfixes. The CLI binary is `dbx`; older releases exposed an `eventdbx` alias, but `dbx` is the canonical entry point. ## Architecture at a glance EventDBX follows the CQRS first principle: isolate the write path (capture + validate + append) from read responsibilities (materialization, search, analytics) so each side can scale and evolve independently. EventDBX is deliberately write-path-first—plugins own the read models so writes stay fast, append-only, and verifiable. 1. **Events in, state verified** – `dbx aggregate apply` validates payloads (when restriction is enabled), merges them into the aggregate snapshot, and appends the event. Per-aggregate Merkle trees detect tampering immediately. 2. **Plugins out** – every committed event flows into a durable job queue. TCP/HTTP/log/process emitters decide which payload slices they care about, while queue backoff keeps delivery reliable. 3. **Domains & replication** – `dbx checkout` creates isolated storage roots per bounded context, and `dbx push/pull/watch` mirror those roots across environments with concurrency controls. ## Where EventDBX fits EventDBX lives on the write side: only trusted services send commands, and plugins keep read models fresh. End users never touch EventDBX directly—they hit the downstream systems that plugins maintain. ```mermaid theme={null} flowchart TB %% Write-side box subgraph WRITE_SIDE[Immutable Write-Side] B[EventDBX write-side] end %% Read-side + external systems subgraph READ_PIPELINE[Read-Side & External Systems] C[Plugin Queue] D[Plugins & Connectors] subgraph INTERNAL_READS[Internal Read Models] E[Read Models
search · analytics · ml] end subgraph EXTERNAL_SYSTEMS[Other Systems] X1[(PostgreSQL)] X2[(OpenSearch)] X3[(Redis)] end end %% Main flow A[Product APIs & Services] -->|commands| B B -->|enqueue| C C --> D %% Plugins feed both internal & external systems D --> E D --> X1 D --> X2 D --> X3 %% Products read from internal & external sources E --> A X1 --> A X2 --> A X3 --> A %% External users F[End Users & Clients] -->|http/graphql/etc| A %% Forbidden path F -.->|no direct access| B %% Styles style B fill:#0d3b66,stroke:#0d3b66,color:#fff style WRITE_SIDE fill:#0d3b6620,stroke:#0d3b6644 style READ_PIPELINE fill:#f5f5f5,stroke:#ccc style INTERNAL_READS fill:#ffffff,stroke:#ccc style EXTERNAL_SYSTEMS fill:#ffffff,stroke:#ccc style F stroke:#888 ``` The diagram shows commands flowing into EventDBX, jobs entering the plugin queue, and fan-out into read models that front the user-facing APIs. ## Choose your next step Follow the CLI quickstart to start the daemon, define schemas, and append your first events. Explore built-in capabilities—immutability, encryption, restriction modes, performance, and column types. Run EventDBX under Docker, issue tokens, connect TypeScript clients, and schedule replication jobs. ## Performance snapshot EventDBX keeps throughput flat even as datasets grow past 50M records (\~100 GB of storage) thanks to RocksDB batching and hot-aggregate caching. | Engine | Throughput (ops/s) | Latency (µs) | Notes | | --------------- | ------------------ | ------------- | ------------------------------------------------------------ | | **EventDBX** | **1 400 – 2 000** | **0.5 – 0.8** | Append-only core plus caching keeps latency sub-microsecond. | | PostgreSQL 15 | 1 000 – 1 900 | 0.6 – 1.0 | Strong baseline, heavier planner/WAL overhead. | | MongoDB 7 | 400 – 1 000 | 1.5 – 2.5 | Flexible JSON store with higher serialization cost. | | SQL Server 2022 | 50 – 180 | 5 – 20 | Drops quickly past 100 K ops because of locking + B-trees. | Benchmarks come from the [eventdbx-perf](https://github.com/eventdbx/eventdbx-perf) suite (Dockerized backends, single-threaded clients, datasets up to 50M records). # License Source: https://docs.eventdbx.com/license EventDBX is distributed under the MIT License; here is a human-readable summary and the full legal text. EventDBX uses the permissive [MIT License](https://opensource.org/license/mit/), letting you use, modify, and redistribute the project with minimal restrictions. The canonical legal text lives in [`LICENSE`](https://github.com/thachp/eventdbx/blob/main/LICENSE); keep that file intact in forks or redistributions. ## Quick summary (non-legal) * ✅ You can use EventDBX in commercial and private projects. * ✅ You can modify the source, distribute binaries, and sublicense it. * ✅ You retain ownership of your changes, but they remain under MIT when distributed with EventDBX. * ⚠️ You must include the original copyright notice and license text. * ⚠️ There is no warranty—use at your own risk. ## Full text ``` MIT License Copyright (c) 2025 Patrick Thach Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ``` If you need a different license for proprietary extensions, keep them in a separate repository with their own LICENSE file so downstream users understand the distinction. # Quickstart Source: https://docs.eventdbx.com/quickstart Install the `dbx` CLI, start the daemon, mint a token, and write your first aggregate. EventDBX ships as a single CLI named `dbx`. Follow the steps below to go from zero to a running daemon with authenticated client access. ```bash theme={null} npm install -g eventdbx dbx start --foreground ``` * Omit `--foreground` to run as a background service. * Use `--restrict=strict` once your schemas are locked in. * Logs stream to stdout; press `Ctrl+C` to stop the daemon when running in the foreground. Generate boostrap token for initial access ```bash theme={null} dbx token bootstrap --stdout ``` * Revoke tokens with `dbx token revoke ` if they leak. * Mint short-lived tokens with `dbx token generate ... --ttl 10m`. * See [Authorization](/core-concepts/authorization) for scoping `--action`/`--resource` and tenants. ```ts theme={null} import { createClient } from "eventdbxjs"; const client = createClient({ ip: process.env.EVENTDBX_HOST ?? "127.0.0.1", port: Number(process.env.EVENTDBX_PORT) || 6363, token: process.env.EVENTDBX_TOKEN, tenantId: "default", }); await client.connect(); await client.create("person", "p-110", "person_created", { payload: { fist_name: "Demo", last_name: "Demo", email: "jane@example.com", }, metadata: { "@createdBy": "" }, note: "person created by ", }); const history = await client.events("person", "p-110"); console.log("Event count:", history.length); await client.disconnect(); ``` Swap in the Python, Java, PHP, .NET, or Go SDKs described on the Client SDKs page if you prefer another language. ## Next steps * **Schema setup**: use CLI schema commands to create and evolve schemas (e.g., `dbx schema create ...`, `dbx schema add ...`, `dbx schema remove ...`, `dbx schema field ...`, `dbx schema alter ...`), or edit the `schema.json` files under your shard folders if you prefer to define them manually; see `dbx schema --help` for the full command surface. * **Snapshots and verification**: `dbx aggregate snapshot ` materializes the latest state, and `dbx aggregate verify` recomputes Merkle roots when you need to prove integrity. * **Replication**: run `dbx watch standby` to stream changes to a standby node, or `dbx push/pull` for one-off syncs. * **Plugins**: configure HTTP or TCP plugins to fan events out to downstream services once your domain is populated. Every CLI command accepts `--config ` if you need to target a custom configuration file or environment. # Cap’n Proto Source: https://docs.eventdbx.com/technical/captain-protocol How EventDBX uses Cap’n Proto for plugin delivery and replication. EventDBX uses Cap’n Proto in three places: * **Plugin delivery**: the Capnp plugin sends event/state/schema envelopes over TCP to a listening worker, respecting each plugin’s payload mode. * **Control channel (CLI/SDK)**: Cap’n Proto frames carry `create/apply/patch/archive/restore`, snapshot, and read operations (`get/list/select/events/verify`, schema list, etc.) over a Noise-secured TCP connection. * **Replication (captain)**: Cap’n Proto over TCP synchronises domains between nodes, with Noise handshakes and Merkle prechecks to prevent silent drift. Both transports keep latency low and structure the payload so downstream services can decode without JSON overhead. ## Plugin delivery payloads * Envelope fields: aggregate id/type, event type/version/id, payload JSON, metadata JSON, extensions JSON, hash, Merkle root, optional state entries, optional schema JSON. * Payload modes (`all`, `event-only`, `state-only`, `event-and-schema`, `extensions-only`) strip unused sections before send. * Sequence numbers increment per connection so receivers can order/dedupe deliveries. * Transport: TCP to the configured host:port from the plugin definition; the queue lives in EventDBX, connections are short-lived per delivery. ## Handshake at a glance ```mermaid theme={null} sequenceDiagram participant Client participant Server Client->>Server: TCP connect Client-->>Server: Noise_XX handshake (keys) Server-->>Client: Noise_XX complete Client->>Server: HELLO {domain, version, token_fingerprint} Server->>Client: ACCEPT {options} Client->>Server: MERKLE {aggregate_root(s)} Server->>Client: MERKLE {aggregate_root(s)} Note over Client,Server: Streams begin (control frames, events, snapshots) with flow control ``` ## Control RPCs (CLI/SDK) The CLI and SDKs use the same Cap’n Proto control channel (over Noise + TCP, default 6363) for write and read verbs: * Writes: `create`, `apply`, `patch`, `archive`, `restore`, snapshot `create`. * Reads: `get`, `list`, `select`, `events`, `verify`, snapshot `list`/`get`, schema `list`. * Every request includes the domain (tenant) and bearer token; responses carry JSON-encoded payloads and cursors. Configure remotes per domain with `dbx checkout --remote --token [--remote-tenant ]`; the control channel reuses those settings. This keeps aggregate operations consistent whether the server is local or remote; the channel is binary, authenticated, and resumes cleanly after reconnects. ## Replication specifics * Captain streams replication data over the same Noise-secured TCP channel, negotiating options via HELLO/ACCEPT before exchanging Merkle roots. * Supports one-way (`push`/`pull`) and bidirectional sync; conflicts halt transfer instead of rewriting history. * Tune parallelism with `--concurrency` (push/pull) and `watch --concurrency/--interval`; lower `--interval` for tighter RPO, at the cost of more chatter. ## Handshake 1. Establish TCP and perform a Noise\_XX handshake (keys/metadata configured via `dbx checkout --remote … --token …`). 2. Client sends a `HELLO` frame with domain, protocol version, and token fingerprint. 3. Server replies with `ACCEPT` plus negotiated options (chunk limits, features). 4. Both sides exchange Merkle roots for requested aggregates to detect drift before streaming jobs or events. Example (illustrative JSON; wire format is Cap’n Proto): ```json theme={null} { "type": "HELLO", "domain": "payments-prod", "version": 3, "token": "sha256:c974..." } ``` ## Flow control * Credit-based flow control: receivers advertise capacity; senders batch up to negotiated limits. * Heartbeats (`PING`/`PONG`) keep idle links alive; on failure, clients retry from the last acknowledged cursor. * For plugins, payload modes (`all`, `event-only`, `state-only`, `event-and-schema`, `extensions-only`) decide which fields are populated in the envelope. ## Error handling Common error frames: | Code | Meaning | Resolution | | --------------- | ---------------------------- | ---------------------------------------------------- | | `AUTH_FAILED` | Token invalid or lacks scope | Mint a new token scoped to replication. | | `HASH_MISMATCH` | Merkle roots differ | Run `dbx integrity scan` and repair before retrying. | | `BACKPRESSURE` | Receiver overwhelmed | Lower batch size or add more standby capacity. | Clients retry automatically with exponential backoff unless the error is fatal (auth, version mismatch). ## Versioning Protocol versions are backwards compatible within a major release. The server negotiates the highest mutually supported version. Keep plugins and replication peers up to date to benefit from improvements. ## Observability Cap’n Proto paths emit metrics such as: * `captain_chunks_sent_total` * `captain_ack_latency_ms` * `captain_hash_mismatch_total` Scrape `/metrics` or subscribe to the audit log to keep an eye on replication health. Understanding Captain helps operators tune replication, debug stalls, and plan upgrades confidently. # Noise Protocol Source: https://docs.eventdbx.com/technical/noise-protocol Understand how EventDBX uses Noise for encrypted, token-derived sessions across control, replication, and plugin channels. Noise is a flexible cryptographic framework for building custom handshakes. EventDBX uses a token-derived `Noise_NNpsk0_25519_ChaChaPoly_SHA256` pattern for control, replication, and plugin Cap’n Proto channels instead of TLS. ## Why Noise? * **Small footprint** – fits into sidecar binaries and embedded agents that cannot afford a full TLS stack. * **Token-derived PSK** – hashes the bearer token into a PSK so access control and transport security share the same credential. * **Deterministic configuration** – concise patterns make audits straightforward and minimise configuration drift. ## Handshake flow 1. The initiator derives a PSK by hashing the bearer token; no static keys are exchanged out of band. 2. The initiator sends an ephemeral key encrypted with the PSK (Noise `e` message). 3. The responder verifies the PSK, responds with its ephemeral key, and both sides derive symmetric session keys. 4. Session keys protect all subsequent frames (control RPCs, replication data, plugin envelopes). Pseudo-code: ```text theme={null} -> e (psk) <- e, ee (psk) ``` EventDBX enforces frame size limits and restarts sessions on reconnect. Tokens rotate independently of the Noise profile: change a token, and the PSK changes automatically. ## Configuration Noise is on by default for control, replication, and Capnp plugin sockets; no extra TLS is required. The effective protocol string is fixed in the binary: ``` Noise_NNpsk0_25519_ChaChaPoly_SHA256 ``` Remote endpoints and tokens are set per domain via `dbx checkout --remote --token [--remote-tenant ]`. Tokens are the only secret you need to rotate. ## Operational guidance * Monitor `noise_handshake_failures_total`; spikes usually indicate bad or expired tokens. * Log handshake fingerprints only in debug mode—never expose tokens or derived PSKs. * For zero-trust requirements, you can still layer WireGuard/IPSec, but the built-in Noise handshake already authenticates with the same token you use for authorization. Noise is the default transport security layer for EventDBX traffic (control, replication, Capnp plugin). Reach for additional tunnels only when compliance or defense-in-depth policies call for a second encryption boundary. # Plugins Source: https://docs.eventdbx.com/technical/plugins Understand the plugin surface area before diving into each transport-specific guide. Plugins dequeue committed events, transform the payload slices they care about, and push them to external systems. Definitions are stored at the system data root (for example `~/.eventdbx/plugins.json`); configure with `dbx plugin config ` instead of editing TOML. Start here to pick a transport, then follow the linked sections for configuration and operational details. ## Overview * Learn how payload modes and the queue work together (`emit_events`, retries, payload trimming). * Decide whether to run lightweight emitters (TCP/HTTP/Log) or supervised binaries (process plugins). * Track plugin logs/queue state so you can remediate failures quickly (`dbx queue`, `dbx plugin test`). [Deep dive → Plugin overview](./plugins/overview) ## HTTP * POST plugin envelopes to HTTP(S) endpoints with configurable headers. * Pair with payload modes (`event-only`, `state-only`, etc.) to avoid over-sharing data. * Retries are handled by the EventDBX plugin queue (`plugin_max_attempts`). [Deep dive → HTTP plugin](./plugins/http) ## TCP * Write JSON envelopes over TCP (one connection per delivery) for simple, low-latency consumers. * Keep consumers fast; the plugin queue retries on failure. [Deep dive → TCP plugin](./plugins/tcp) ## Process * Run supervised binaries; EventDBX installs, starts/stops, and retries when they fail. * Choose minimal payload modes to reduce queue pressure; use `dbx plugin start|stop|status`. [Deep dive → Process plugin](./plugins/process) ## Log * Emit envelopes to the EventDBX log target for quick debugging or lightweight audit trails. * Forward logs with your existing log pipeline (Splunk, Datadog, etc.). [Deep dive → Log plugin](./plugins/log) # Use Cases Source: https://docs.eventdbx.com/use-cases Map the domains where EventDBX’s immutable event log, schemas, and replication model shine. Below is a structured overview of real-world systems where EventDBX reduces operational overhead or provides verifiable change history. Use it to identify parallels in your own architecture, validate design assumptions, or surface new patterns worth prototyping. ## Systems that require full historical reconstruction When knowing how the data changed is just as important as the latest state. You want to rewind time, inspect every mutation, and prove why the present looks the way it does. * Clinical records, diagnostic history, and treatment decisions * Financial flows: payments, ledger postings, and reconciliations * Livestock lifecycle tracking (birth, vaccination, weight, custody transitions) * Supply-chain custody by SKU/lot with location stamps * Manufacturing station logs with operator notes and calibration changes * Insurance claim histories, reopenings, and adjustments * Customer support timelines tied to entitlements and SLAs * User activity / behavior trails for DSARs and internal governance * Audit logs for compliance reviews (SOX, SOC 2, HIPAA, ISO) ## Safety-critical and mission-critical machines Machines where every micro-event must be part of a forensic trail. Immutable truth underpins investigations and reliability guarantees. * Autonomous drones (flight logs, IMU data, command streams) * Rockets and launch vehicles (telemetry, valve events, thrust changes) * Satellites (orbital events, subsystem adjustments) * Cars and EVs (ADAS signals, battery cycles, braking telemetry) * Industrial robots (sensor fusion, error states, operator overrides) * Aviation systems (flight control inputs, airspeed changes) * Medical devices, rail signaling, power grid SCADA trails ## IoT and edge computing Small devices speak in streams. The world changes in discrete steps, not overwrites, which makes append-only event storage a natural fit. * Environmental sensors reporting temperature, pressure, humidity, GPS, vibration * Home automation devices coordinating scenes and actuators * Smart meters for water, electric, and gas * Agricultural sensors (soil moisture, rumen monitors, RFID readers) * Factory-floor devices tracking cycle counts and wear * Fleet telematics (location, driver behavior, maintenance alerts) ## Offline-first or intermittently connected systems Connectivity is flaky, but truth must eventually sync. Peer-to-peer replication and staged writes become superpowers here. * Drones syncing logs after missions * Field technicians uploading equipment logs when connectivity appears * Farm devices recording events during the day and syncing overnight * Mobile apps that need durable local write-side storage * Disaster-response systems coordinating across degraded networks * Remote mining, maritime, and oilfield operations with sparse connectivity ## Distributed and replicated systems Multiple nodes exchange truth safely, often across regions or tenants. EventDBX acts like Git for application data when you need optimistic sync with conflict detection. * Peer-to-peer collaboration tools * Replicated ledgers for internal services * Multi-region microservices and sharded control planes * Multi-tenant SaaS domains with per-tenant branches * AI agent memory stores (MCP workflows, autonomous agents) * Edge → core fan-in for devices, vehicles, or field sites ## Systems that need versioned schemas When your model evolves alongside the business. Schemas become part of the story instead of a fear-inducing chore. * SaaS products with constantly shifting feature sets * Data pipelines that require backward-compatible contract enforcement * Multi-team systems where coordinated migrations are painful * Regulated applications that need explicit schema-version trails * Public APIs and SDKs where contract drift must be prevented ## Observability, analytics, and replay scenarios Business logic often depends on sequences, not snapshots. EventDBX lets you replay, fork, and experiment without losing provenance. * Fraud and anomaly detection pipelines * User behavior tracking and growth experimentation * Manufacturing yield analysis * Predictive maintenance * Timeline-based debugging and retroactive fixes * Model evaluation, regression testing, and reruns on new features or heuristics * Pricing, risk, and policy simulations where replay matters ## Immutable, tamper-evident logs Cryptographic truth is a hard requirement. Noise Protocol handshakes plus Merkle roots provide a “truth engine” that proves nothing was altered. * Healthcare compliance attestations * Financial auditing trails * Legal and forensic records * Provenance tracking (food, supply chain, art, documents) * Internal security logs * Data-integrity guarantees for AI agents and copilots * Chain-of-custody evidence and eDiscovery timelines ## Developer-focused workflows Teams that value speed and iteration. Developers spend more time shipping and less time wrestling schema migrations or spinning ad-hoc databases. * Backend systems that must avoid painful migrations * Serverless apps running on Vercel, Cloudflare, or GCP functions * Prototyping new domain logic or feature spikes * Local-first development environments * CLIs and automation tools that need durable state without a full database * Feature flag, experiment, and config stores that require history and rollbacks ## Domain-driven design systems Events are the primary business concepts, so aggregates map directly to ubiquitous language. * Order lifecycles (created → paid → shipped → delivered) * Subscription lifecycles * Ticketing and incident systems * Project and task management tools * Identity systems (`user_registered`, `user_verified`, `user_updated`) * Billing, invoicing, and entitlements that depend on event trails ## AI memory and agent orchestration Agents need stable memory more than fast SQL queries. EventDBX gives them a durable commit log for reasoning, replay, and accountability. * Long-term memory for autonomous agents * Durable commit logs for workflow engines * Reproducible task sequences and battle-tested playbooks * Shared knowledge bases across cooperating agents * Immutable “thought trails” with verifiable cross-agent messaging * LLM evaluation harnesses and experiment logs with rollback/replay ## Niche but powerful engineering cases Places where traditional databases feel too heavy or too mutable. If it can emit events, it can live in EventDBX. * Embedded systems needing local persistence * Hardware that requires cryptographic audit logs * Devices persisting straight to SSDs or SD cards * Large-scale simulation logs * Scientific experiments (lab equipment event trails) * Geospatial event trails (GPS traces, route reconstruction) * Game telemetry, esports analytics, and virtual-world state trails *** EventDBX excels when change itself is the product. Pick the pattern that matches your reality, plug into the CLI/SDK stack, and treat every mutation as an asset instead of a liability.