SDKs
Six official clients, one behavior. Point any of them at a single node or at a discovery server — the SDK detects which from the server's own handshake, so development and production use identical code.
Every SDK ships the full feature set: authentication, TLS, discovery
replica lists, replication-aware routing (reads fail over, writes fan out
to all R owners), transparent reconnect with a single retry, always-on
keep-alive, opt-in transparent value compression, opt-in fire-and-forget
replica writes, opt-in read repair, and the same rendezvous-hashing
pipeline verified against shared cross-language test vectors. Values are
strings first: every get returns a string (strict UTF-8),
with a get_bytes-style companion for raw byte values.
| Language | Requires | Package | Docs |
|---|---|---|---|
| TypeScript / Node.js | Node 20+ | nanocached (npm) | README |
| Python | 3.11+, asyncio | nanocached (PyPI) | README |
| Java | 17+ | org.nanocached:nanocached (Maven Central) | README |
| Rust | tokio | nanocached (crates.io) | README |
| .NET | .NET 8+ | Nanocached (NuGet) | README |
| Go | 1.22+ | github.com/nanocached/nanocached/sdk/go | README |
None of the SDKs has runtime dependencies outside its language's standard library (Rust's client uses tokio; its TLS support is a default cargo feature that can be compiled out).
TypeScript
npm install nanocached
import { NanocachedClient } from "nanocached";
const client = await NanocachedClient.connect({
addresses: [{ host: "127.0.0.1", port: 8357 }],
});
await client.set("greeting", "hello", 60); // TTL seconds; omit = no expiry
const value = await client.get("greeting"); // string | null
const existed = await client.delete("greeting"); // boolean
await client.close();
Python
pip install nanocached
import asyncio
from nanocached import NanocachedClient
async def main():
async with await NanocachedClient.connect([("127.0.0.1", 8357)]) as client:
await client.set("greeting", "hello", ttl_seconds=60)
value = await client.get("greeting") # str | None
existed = await client.delete("greeting") # bool
asyncio.run(main())
Java
Group/artifact: org.nanocached:nanocached.
import org.nanocached.NanocachedClient;
import org.nanocached.NanocachedClient.Address;
import org.nanocached.NanocachedClient.Options;
try (NanocachedClient client = NanocachedClient.connect(
new Options().addresses(List.of(new Address("127.0.0.1", 8357))))) {
client.set("greeting", "hello", 60); // TTL seconds; omit = no expiry
Optional<String> value = client.get("greeting"); // empty when missing
boolean existed = client.delete("greeting");
}
Spring users: the separate org.nanocached:nanocached-spring
module implements Spring's CacheManager/Cache on
this SDK — named caches as namespaces, per-cache TTL, clear()
as the namespace's CLEAR. See
adapters/spring.
Spring Boot users can instead add
org.nanocached:nanocached-spring-boot-starter, which
autoconfigures both beans from nanocached.* properties — no
Java config. See
adapters/spring-boot-starter.
JSR-107 (javax.cache) users can instead add
org.nanocached:nanocached-jcache, an honest-subset
CachingProvider/CacheManager/Cache
implementation discoverable via Caching.getCachingProvider().
See
adapters/jcache.
Rust
cargo add nanocached
use nanocached::{NanocachedClient, Options};
let client = NanocachedClient::connect(
Options::new().addresses([("127.0.0.1", 8357)]),
).await?;
client.set("greeting", "hello", 60).await?; // TTL seconds; 0 = no expiry
let value: Option<String> = client.get("greeting").await?;
let existed: bool = client.delete("greeting").await?;
client.close().await;
.NET
dotnet add package Nanocached
using Nanocached;
using NanocachedClient client = await NanocachedClient.ConnectAsync(new Options {
Addresses = { ("127.0.0.1", 8357) },
});
await client.SetAsync("greeting", "hello", ttlSeconds: 60); // omit = no expiry
string? value = await client.GetAsync("greeting"); // null when missing
bool existed = await client.DeleteAsync("greeting");
Go
go get github.com/nanocached/nanocached/sdk/go
import nanocached "github.com/nanocached/nanocached/sdk/go"
client, err := nanocached.Connect(nanocached.Config{
Addresses: []nanocached.Address{{Host: "127.0.0.1", Port: 8357}},
})
if err != nil { /* ... */ }
defer client.Close()
err = client.Set("greeting", "hello", 60) // TTL seconds; 0 = no expiry
value, ok, err := client.Get("greeting") // ok=false when missing
existed, err := client.Delete("greeting")
Shared behavior, briefly
- Tuning knobs that tests can turn — each SDK exposes a handful of production constants (request timeout, keep-alive interval, the in-flight budgets for fire-and-forget replica writes and hedge legs, the batch-decompression cap) as mutable, documented-but-hidden values so its own test suite can shrink them. That is a deliberate convention (issue #488), not an API: they are not part of any SDK's public surface, changing one affects every client in the process, and a test that lowers one is expected to restore it. Applications configure what they need through the connect options instead.
- Framework adapters — separate, ecosystem-specific packages on
top of the SDKs (outside the SDK parity policy), published at the same
version as the SDK they sit on and depending on that SDK release: Spring
(
org.nanocached:nanocached-spring, adapters/spring, plus a Spring Boot starter,org.nanocached:nanocached-spring-boot-starter, at adapters/spring-boot-starter), ASP.NET Core'sIDistributedCache(dotnet add package Nanocached.Caching, adapters/dotnet), a Django cache backend (pip install nanocached-django, adapters/django), a cache-manager v5 store for Node (npm install nanocached-cache-manager, adapters/cache-manager), a JSR-107 (javax.cache) provider (org.nanocached:nanocached-jcache, adapters/jcache), and a Keyv storage adapter for cache-manager v6+/NestJS 11 (npm install nanocached-keyv, adapters/keyv). - Addresses — one list-shaped option in every SDK; list every discovery replica, and connect and refresh try them in order, skipping busy or unreachable replicas.
- Replication — R rides along with the node list, so there is nothing to configure; each client exposes the factor in use.
- Namespaces —
client.namespace("users")(Namespace(...)in .NET) returns a handle with the sameget/set/deleteoperations scoped to a flat, opaque namespace: the same key name in two namespaces is two entries. The handle shares the client's connections and routing; the un-namespaced API is unchanged and keeps the legacy wire frames, so it still works against pre-namespace servers. See the protocol for the wire form. A handle'sclear()wipes its namespace and the client'sclearAll()(clear_all/ClearAllAsync) wipes every namespace: both are sent to every member node, and on a node failure the SDK refreshes the node list once and retries before raising an error naming the node — never a silent partial clear. See c / F. - Counters —
client.incr(key, delta)(Incr/IncrAsyncin Go/.NET) adds a signeddeltato a key's stored value atomically on the node that owns it and returns the new value; a negativedeltadecrements, so there is no separatedecr. Available on a namespace handle too. As volatile as a plainset— LRU eviction and TTL expiry reclaim a counter the same as any other entry, so this is a fit for rate limiting and approximate counters, not for counts that must survive. In a cluster only the primary owner runs the increment; the SDK writes the result to the remaining owners as an ordinaryset, never by replaying the increment itself (replaying it could drift a replica from the primary). Incompatible with value compression: the SDKs rejectincr/decroutright on a client withcompressenabled, since the protocol has no way to tell a compressed value from a plain integer. See i. - Compare-and-set —
putIfAbsent,replace(present-only and exact-match forms), and a digest-conditioned delete, all atomic on the node that owns the key. An expected value comes from a prior read (its content digest, an opaque token the SDK computes for you) rather than being resent whole. Same cluster rule as counters: only the primary evaluates the condition, and the SDK writes the result to the remaining owners as an ordinaryset/delete. Not a distributed lock — LRU eviction can still silently break a lock built on it. See k / x. - Failure recovery — a stale routing table (a node answers
W) or a dead primary both trigger one node-list refresh and one retry; recovery is bounded by discovery's liveness timeout. - Reconnect — nodes close idle connections after 60 s; SDKs
keep their connections warm automatically (an internal 30 s
keep-alive), and redial transparently if one does die. The keep-alive
is a real
getof a reserved 21-byte key (a leading NUL byte followed bynanocached-keepalive) — treat that key as reserved by the SDKs. - Observability — failures the SDKs deliberately swallow
(replica-leg writes, read-repair writes, node-list refreshes) are
counted in monotonic per-client counters, exposed via a
stats()-style accessor in every SDK, so silently degrading replication or a stuck refresh is detectable. - Concurrency — every client is safe for concurrent use; requests are pipelined per connection (concurrent callers each pay only their own network latency, not everyone else's ahead of them — request pipelining).
- Value compression — off by default; when enabled, values at
or above a configurable threshold are transparently DEFLATE-compressed
on write and decompressed on read. Every client touching a given
keyspace must agree on the setting (value compression).
Also incompatible with counters: a client with
compressenabled raises immediately fromincr/decrrather than produce a value the protocol can't safely round-trip. - Fire-and-forget replica writes — off by default; when enabled, a write returns as soon as the primary owner acks instead of also waiting for replica legs, which finish in the background (bounded, with a synchronous fallback past the bound). A per-client latency trade-off, not a format decision — clients may mix settings freely (fire-and-forget replica writes).
- Read repair — off by default; when enabled, a clean miss probes the remaining owners before being accepted as final, repairing the primary in the background (no TTL) if one still holds the value. Closes the post-restart window client-side replication otherwise leaves open, at the cost of extra reads on the misses it applies to (read repair).