Modern C++20 SDK for KinopioHub, built on top of nats.c.
- Supported transport:
nats://andtls:// - Supported patterns: publish / subscribe, request / reply, service mode, scoped variables, latest-value caching, automatic reconnect
- Supported payloads:
nlohmann::json,std::string, raw bytes, empty payloads - Minimum language level: C++20
#include <nlohmann/json.hpp>
#include <kinopio/kinopio.hpp>
int main() {
kinopio::KinopioOptions options;
options.servers = {"nats://demo.nats.io:4222"};
kinopio::KinopioHub hub(options);
hub.connected(std::chrono::seconds(5));
auto scope = hub.getScope("chat");
auto messages = scope.getVariable("messages");
auto subscription = messages.sub([](const kinopio::Payload& payload, const kinopio::MessageMetadata&) {
const auto& json = std::get<nlohmann::json>(static_cast<const kinopio::Payload::Base&>(payload));
std::cout << json.at("message") << std::endl;
});
messages.pub(nlohmann::json{{"message", "Hello from C++"}});
}orderedrandomlatency
KinopioHub(KinopioOptions options = {})void connected(std::chrono::milliseconds timeout = 10s) constvoid reconnect() constPayload request(std::string_view subject, const Payload& data, const RequestOptions& options = {}) constvoid dispose() const noexceptScope getScope(std::string_view name) constStateListenerHandle onStateChange(StateChangeListener listener) constKinopioState state() const noexceptbool isConnected() const noexcept
Variable getVariable(std::string_view name) const
const std::string& subject() conststd::optional<Payload> value() constvoid pub(const Payload& data, const PublishOptions& options = {}) constSubscriptionHandle sub(SubscriptionCallback callback, const SubscribeOptions& options = {}) constPayload req(const Payload& data, const RequestOptions& options = {}) constServiceHandle serve(ServeHandler handler, const ServeOptions& options = {}) constvoid dispose() const noexcept
servers: candidate NATS server URLsserverSelectionMode: optional explicit mode overridenoRandomize: legacy compatibility field; whenserverSelectionModeis unset,truemaps toorderedandfalsemaps torandom
LeafRuntimeHandle startLeafNode(const LeafRuntimeOptions& options = {})std::filesystem::path defaultCacheDirectory()LeafRuntimeHandle::snapshot()returns process state,localReady,bridgeReady, resolved binary paths, runtime paths, generated TLS material, and URLsLeafRuntimeHandle::stop()stops the localnats-serverleaf process and is idempotent
discoveringfollowingLeaderleaderMissingGraceelectingstartingLeafleaderstopped
AutoLeafHandle startAutoLeaf(const AutoLeafOptions& options = {})AutoLeafHandle::snapshot()returns the current leader/follower state, stable node identity, elected leader info, manifest path, and optional local leaf snapshotAutoLeafHandle::stop()stops background discovery/election work and any owned local leaf runtime
Payloadis a type-safe variant of:nullptr,nlohmann::json,std::string,std::vector<std::uint8_t>- Default encoding: JSON -> UTF-8 JSON, string -> UTF-8 text, bytes -> raw bytes, null -> empty payload
- Default decoding:
empty payload -> null, UTF-8 JSON ->
nlohmann::json, UTF-8 non-JSON ->std::string, invalid UTF-8 -> raw bytes - Custom codecs replace the default serializer through
KinopioOptions::codec
- Construction is non-blocking. Call
connected()when you need to wait for readiness. - When multiple servers are configured and neither
serverSelectionModenornoRandomizeis set, the effective mode defaults tolatency. orderedkeeps the configured server order for initial connect and explicitreconnect().randomshuffles candidates at the start of each connection cycle, then keeps that order stable within the cycle.latencyprobes all configured candidates before initial connect and explicitreconnect(), preferring healthy servers with lower RTT and falling back to input order when all probes fail.- When
latencymode is active with more than one candidate, the hub also runs periodic background probes and may hot-switch to a better server when the measured improvement is at least 30 ms. - Hot-switching replays value tracking, user subscriptions, and service handlers onto the replacement connection before draining the old one.
Variable::value()returns the last successfully published or received payload.Variable::value(),sub(),serve(), andreq()continue to work after a successful hot-switch.- During the replay window for an external publish, callbacks can briefly be delivered twice because the old and new transport subscriptions may overlap for a moment.
- Repeated publishes with identical serialized bytes are suppressed per variable.
serve()replies with{ "error": true, "message": "..." }when the handler throws.req()returns that structured error payload as data; handler failures are not auto-thrown locally.- Successful hot-switches do not intentionally push the hub back through
disconnected. - Listener, subscription, service, hub, and variable cleanup are all idempotent.
- The optional leaf runtime lives behind
KINOPIO_BUILD_LEAF_RUNTIME=ON, includeskinopio/leaf.hpp, and links asKinopioHub::kinopiohub_leaf. - It is intentionally separate from the base
KinopioHub::kinopiohubtarget, so pure SDK consumers do not inherit host-process runtime behavior. startLeafNode()resolvesnats-serverin this order: explicitLeafRuntimeOptions::natsServerBinary, newest cached binary underdefaultCacheDirectory(), then automatic download of the latest stable official release when downloads are allowed.- Downloaded binaries are cached by release version. Reusing the cache does not require another network round-trip.
- The runtime auto-generates a private CA, server certificate, and server key inside the runtime directory for the local leaf listener, and exposes those paths through
LeafRuntimeSnapshot::tls. localReadymeans the localnats-serverprocess is up and its monitoring endpoint answers.bridgeReadymeans at least one upstream leaf remote is currently connected.- If upstream backbone remotes are unreachable, the local runtime can still start and serve local
nats://clients. - The current implementation uses host tools for lifecycle steps:
curlfor release download,taron macOS/Linux or PowerShell archive expansion on Windows, andopensslfor TLS material generation.
startAutoLeaf()adds a higher-level discovery and election loop on top of the optional leaf runtime.- It is also guarded by
KINOPIO_BUILD_LEAF_RUNTIME=ONand stays insidekinopio::leaf, rather than changing the base SDK runtime model. - Each node joins a discovery namespace, reuses a stable node identity, watches for an existing leader, waits through a grace window after leader loss, then participates in deterministic election.
- Elections prefer higher backbone quality scores. By default the score is derived from live probes of configured
LeafRuntimeOptions::upstreamLeafUrls; ties fall back to a stable node-id order. - A winning node starts its local leaf runtime and begins broadcasting leader metadata; followers expose that leader metadata through
AutoLeafSnapshotand a JSON discovery manifest file. - Recovery is non-preemptive by default: a node that comes back after failover follows the current leader instead of immediately stealing leadership back, which suppresses leader flapping.
- The first discovery transport is IPv4 UDP broadcast, configured through
AutoLeafOptions::discoveryBindHost,discoveryBroadcastAddress, anddiscoveryPort. - If you want remote consumers to use the announced leader URL, set a routable
AutoLeafOptions::advertiseHostand a compatibleleafRuntime.leafListenHost.
kinopio::KinopioOptions options;
options.servers = {
"nats://edge-a:4222",
"nats://edge-b:4222",
"nats://edge-c:4222",
};
// Optional. If omitted, multi-server mode defaults to latency.
options.serverSelectionMode = kinopio::ServerSelectionMode::latency;cmake -S . -B build \
-DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" \
-DBUILD_TESTING=ON
cmake --build build
ctest --test-dir build --output-on-failureManifest dependencies:
cnatsnlohmann-json
- Primary package path:
find_package(cnats CONFIG REQUIRED)andfind_package(nlohmann_json CONFIG REQUIRED) - If your packaged
cnatsexport links throughOpenSSL::SSL, make sure the normal CMakeOpenSSLpackage is resolvable in that environment as well - Test-only dependency:
Catch2 3is only needed whenBUILD_TESTING=ON - Development fallback: the top-level CMake project can fetch missing dependencies automatically if they are not preinstalled
- Packaging/install exports are intended for the primary package-manager path; for pure FetchContent development builds, use
-DKINOPIO_ENABLE_INSTALL=OFF
cmake -S . -B build \
-DKINOPIO_BUILD_LEAF_RUNTIME=ON \
-DBUILD_TESTING=ON \
-DKINOPIO_ENABLE_INSTALL=OFF
cmake --build build
ctest --test-dir build --output-on-failure- The optional leaf runtime adds the
KinopioHub::kinopiohub_leaftarget and thekinopio/leaf.hppheader. - The host that runs
startLeafNode()must providecurl,openssl, and eithertaror PowerShell archive extraction support. startAutoLeaf()additionally expects host UDP broadcast capability on the configured discovery port.
Example programs live under examples/:
auto_leaf.cppconnection.cppleaf_runtime.cpppublish.cppsubscribe.cpprequest_reply.cppscope.cpp
By default the connection-oriented examples use nats://demo.nats.io:4222. Override with KINOPIO_NATS_URL for a single server, or KINOPIO_NATS_URLS for a comma-separated multi-server list.
leaf_runtime.cpp is only built when KINOPIO_BUILD_LEAF_RUNTIME=ON. It starts a local leaf runtime, prints the resolved binary and generated TLS paths, and performs a local publish/subscribe self-check through snapshot.clientUrl. You can optionally provide comma-separated upstream remotes with KINOPIO_LEAF_UPSTREAM_URLS.
auto_leaf.cpp is also only built when KINOPIO_BUILD_LEAF_RUNTIME=ON. It starts the discovery/election loop, prints the observed auto-leaf state, and, when elected leader, runs the same local publish/subscribe self-check through the owned leaf runtime. Useful environment variables are KINOPIO_AUTO_LEAF_NAMESPACE, KINOPIO_AUTO_LEAF_DISCOVERY_PORT, KINOPIO_AUTO_LEAF_ADVERTISE_HOST, and KINOPIO_LEAF_UPSTREAM_URLS.
- Unit tests cover codec behavior, state changes, scope/variable cache, deduplication, error propagation, and RAII cleanup
- When install exports are enabled with packaged dependencies, CTest also runs an install-consumer smoke that verifies
find_package(KinopioHub CONFIG REQUIRED)for the base target and, when enabled, the optionalKinopioHub::kinopiohub_leaftarget - Integration tests are gated behind
BUILD_INTEGRATION_TESTS=ON - Integration tests also use
KINOPIO_NATS_URL, defaulting tonats://demo.nats.io:4222
Detailed capabilities and limitations: docs/capabilities.md
GPL-3.0-or-later. See LICENSE.