Cnerium and Vix
Cnerium is designed to live inside the Vix backend model.
It does not introduce a second application runtime, a second router, or a second way to structure HTTP backends. A Cnerium application starts as a normal Vix application. The developer creates a vix::App, registers ordinary routes with Vix, then attaches Cnerium to add reliability semantics to selected write operations.
This is the central relationship:
vix::App app;
auto cnerium = cnerium::attach(app);The order matters conceptually. Vix is the application owner. Cnerium is attached to it.
Vix remains the application layer
Vix owns the backend application model.
That includes the HTTP server, routing, middleware, request and response types, runtime lifecycle, static files, templates, WebSocket runtime, configuration, build workflow, development workflow, and production workflow.
A normal route remains a normal Vix route:
app.get("/health", [](vix::Request &req, vix::Response &res)
{
(void)req;
res.json({
{"ok", true}
});
});Cnerium does not need to wrap this route. There is no reliability problem to solve here. A health check can be repeated many times without creating duplicate application state.
The same applies to most read-only endpoints:
GET /health
GET /status
GET /products
GET /orders/{id}
GET /assets/app.cssThose endpoints belong directly to Vix.
Cnerium adds reliability to selected routes
Cnerium is used when a route represents a critical write operation.
For example:
POST /orders
POST /payments
POST /invoices
POST /users/register
POST /subscriptions
POST /workflows/startThose endpoints are different from ordinary reads because executing them twice can create duplicated state. A retry after a timeout may be legitimate from the client’s perspective, but unsafe if the server treats it as a new operation.
Cnerium adds a durable route:
cnerium.durable_post(
"/orders",
"orders.create",
[](cnerium::DurableRequest &request)
{
return cnerium::created({
{"ok", true}
});
});This route is still registered into the Vix application. Vix receives the request, parses it, matches the route, and writes the response. Cnerium runs around the handler to decide whether the operation should execute, replay a stored response, or fail with a conflict.
Why Cnerium does not own vix::App
Earlier Cnerium designs can be tempting to write like this:
cnerium::App app;That shape is misleading.
Even if cnerium::App internally wraps a Vix app, it gives the developer the impression that Cnerium is a second backend framework. It suggests that using Cnerium means leaving the Vix application model and entering a new one.
The current direction avoids that confusion.
The application should be written as:
vix::App app;
auto cnerium = cnerium::attach(app);This makes the ownership explicit. Vix owns the backend. Cnerium attaches to it.
The result is easier to understand for a developer who already knows Vix. They do not need to learn a new application object. They only need to learn when and how to make a route durable.
Ownership boundary
The ownership boundary is simple.
Vix owns:
application startup
HTTP server
routing
middleware
request parsing
response writing
static files
templates
WebSocket transport
runtime lifecycle
build workflow
development workflow
production workflow
Cnerium owns:
durable route registration helpers
durable request wrapper
durable response type
idempotency checks
request body hashing
replay protection
stored responses
Cnerium store keys
application-level realtime events for durable operations
Softadastra SDK owns:
durable storage foundation
local persistence
sync-related foundations
SDK client API used by CneriumThe point is not to split the ecosystem into separate worlds. The point is to keep each layer responsible for the work it is designed to do.
Vix is the runtime and backend framework layer. Cnerium is the reliability layer. The Softadastra SDK is the durable storage foundation behind Cnerium.
Request flow
A normal Vix route follows the standard Vix request flow:
client
-> Vix HTTP server
-> Vix router
-> Vix route handler
-> Vix response writerA Cnerium durable route adds a reliability decision before the user handler:
client
-> Vix HTTP server
-> Vix router
-> Cnerium durable route
-> idempotency check
-> execute handler
-> replay stored response
-> reject unsafe retry
-> Vix response writerThe HTTP transport does not change. The Vix router does not change. The response is still written through Vix. Cnerium only controls whether the durable handler should run.
DurableRequest and Vix Request
Cnerium handlers receive cnerium::DurableRequest, not vix::Request.
That does not mean Cnerium owns HTTP. DurableRequest is a wrapper around the Vix request. It exposes the request data needed by durable handlers and adds reliability-specific helpers such as the idempotency key and request hash.
Example:
cnerium.durable_post(
"/orders",
"orders.create",
[](cnerium::DurableRequest &request)
{
const auto body = request.json();
const std::string key = request.idempotency_key_value();
return cnerium::created({
{"ok", true},
{"key", key}
});
});For ordinary routes, use vix::Request and vix::Response. For durable routes, use cnerium::DurableRequest and return a cnerium::DurableResponse.
That distinction is intentional. Durable routes have a different execution contract.
DurableResponse and Vix Response
A durable handler returns a cnerium::DurableResponse.
Cnerium needs this response to be storable. A normal vix::Response is written directly to the client. A durable response must also be convertible into a stored response so Cnerium can replay it later if the same request is retried.
Example:
return cnerium::created({
{"ok", true},
{"order_id", order_id}
});Internally, Cnerium converts that durable response into the Vix response writer. The application developer should not need to manually write to vix::Response inside a durable handler.
The durable response is part of the reliability model. It gives Cnerium a response it can persist and replay.
Why normal Vix routes still matter
Cnerium should not be used for every route.
A backend usually contains many endpoints that do not need durable write semantics. For those endpoints, using normal Vix routes keeps the code clearer and avoids unnecessary idempotency requirements.
A good backend can mix both styles:
app.get("/health", health_handler);
app.get("/products", list_products);
app.get("/orders/{id}", get_order);
cnerium.durable_post("/orders", "orders.create", create_order);
cnerium.durable_post("/payments", "payments.create", create_payment);This is the intended model. Vix handles the general backend. Cnerium protects the critical operations.
WebSocket relationship
Vix provides the WebSocket runtime.
Cnerium does not implement its own WebSocket server, session model, frame parser, rooms, or connection lifecycle. When Cnerium emits an application event, that event is delivered through Vix WebSocket.
Example:
cnerium.emit(
"order.created",
cnerium::support::object({
{"order_id", cnerium::Json(order_id)}
})
);This is an application-level event. Cnerium describes what happened. Vix handles the realtime transport.
The same boundary applies here: Cnerium should not become the place where the full WebSocket system is documented. That belongs to Vix. Cnerium only documents how durable operations can emit events through the attached Vix runtime.
Configuration relationship
Vix configuration remains responsible for Vix behavior.
Cnerium configuration is only for Cnerium-level behavior: application name, Cnerium data directory, node id, Softadastra-related local persistence, and realtime event attachment options.
A typical explicit setup looks like this:
vix::App app;
cnerium::app::AppConfig config = cnerium::app::AppConfig::development();
config.set_name("orders-service");
config.set_data_dir("data/cnerium");
config.set_node_id("orders-node");
config.enable_realtime("/ws", "0.0.0.0", 9090);
auto cnerium = cnerium::attach(app, std::move(config));This configuration does not make Cnerium the application owner. It only configures the attached reliability layer.
Lifecycle relationship
The lifecycle should remain explicit:
if (!cnerium.start())
{
return 1;
}
app.run();cnerium.start() starts the Cnerium runtime resources such as the store and optional realtime support. app.run() starts the Vix HTTP application and blocks until shutdown.
Cnerium should not call app.run() for the user. That would blur the ownership boundary.
Likewise, Cnerium should not close an app it does not own. The vix::App lifecycle remains controlled by the application.
Practical rule
Use this rule when deciding where code belongs:
If the code is about HTTP application structure, it belongs to Vix.
If the code is about making a critical operation safe under retries, it belongs to Cnerium.
If the code is about durable local storage and sync foundations used behind the reliability layer, it belongs behind the Softadastra SDK.
That rule keeps the system understandable.
Summary
Cnerium and Vix are not competing layers.
Vix is the backend application foundation. Cnerium attaches to Vix and adds reliability semantics for selected write operations. The Softadastra SDK provides the durable foundation behind Cnerium.
A developer should be able to start from a normal Vix backend, add Cnerium with one attachment call, and protect critical routes without changing the rest of the application architecture.