Overview
KV provides a minimal, portable key-value store for fast lookups. Store and retrieve opaque byte values by key, with optional time-to-live (TTL) for automatic expiration and atomic conditional writes for safe concurrent updates. Designed for caching, session state, feature flags, and distributed locks.
Platform Mapping
| Platform | Backing Service | Provisioned by |
|---|---|---|
| AWS | Amazon DynamoDB (on-demand) | Alien |
| GCP | Google Cloud Firestore | Alien |
| Azure | Azure Table Storage | Alien |
| Kubernetes / On-Prem | External (Redis, DynamoDB, etc.) | Cluster operator |
| Local | SQLite (embedded database) | Alien |
On Kubernetes / on-prem, KV is not provisioned by Alien. The cluster operator provides the backing service and configures it via Helm values.
When to Use
Use KV for fast key-based lookups — caching, session state, feature flags, distributed locks, idempotency tracking. Values are opaque bytes up to 24 KiB, with optional TTL.
Don't use KV for complex queries, relationships, or large documents. For full-text search, joins, or items larger than 24 KiB, use a dedicated database.
Stack Definition
Declare a KV resource in your alien.ts:
const cache = new alien.Kv("cache").build()| Parameter | Type | Description |
|---|---|---|
id | string | Resource identifier. [A-Za-z0-9-_], max 64 characters. |
KV has no additional configuration options. The backing service (DynamoDB, Firestore, Table Storage) is determined by the deployment platform.
Quick Start
import { kv } from "@alienplatform/sdk"
const cache = kv("cache")
await cache.setJson("user:123", { name: "Alice", plan: "pro" })
const entry = await cache.getJson("user:123")
// entry.value → { name: "Alice", plan: "pro" }, entry.version → opaque versionCore Operations
Set a Value
await cache.setJson("user:123", { name: "Alice" }) // JSON (auto-serialized)
await cache.set("greeting", "hello") // String
await cache.set("session:abc", data, { ttl: 3600 }) // Expires in 1 hour (TTL in seconds)
// Atomic create — only if key doesn't exist
const created = await cache.setJson("lock:res", { owner: "w1" }, { ifVersion: null })
// true if created, false if the key already existedGet a Value
const raw = await cache.get("user:123") // KvEntry<Buffer> | null
const text = await cache.getText("greeting") // KvEntry<string> | null
const user = await cache.getJson("user:123") // KvEntry<parsed JSON> | nullReturns null if the key does not exist or has expired. Each entry carries the value plus an opaque version for conditional writes.
Conditional Writes
Every read returns a version. Pass it back to write only if nothing changed in between:
const entry = await cache.getJson<Counter>("counter")
if (entry) {
const updated = await cache.setJson("counter", { n: entry.value.n + 1 }, {
ifVersion: entry.version, // compare-and-set
})
// updated === false → another writer won the race; re-read and retry
}See Behavior & Limits for the full semantics.
Delete, Exists, Scan
await cache.delete("user:123")
if (await cache.exists("user:123")) { /* ... */ }
// scan() resolves to a page of items plus a cursor
const page = await cache.scan("user:")
for (const { key, value } of page.items) {
console.log(key) // "user:123", "user:456", ...
}Patterns
Distributed Lock
const acquired = await cache.setJson("lock:report-gen", { owner: workerId }, {
ifVersion: null, // create only if absent (or expired)
ttl: 30,
})
if (acquired) {
try {
await generateReport()
} finally {
// Release only our own lock — a compare-and-delete won't remove
// a lock that expired and was taken over by another worker.
const lock = await cache.getJson<{ owner: string }>("lock:report-gen")
if (lock?.value.owner === workerId) {
await cache.delete("lock:report-gen", { ifVersion: lock.version })
}
}
}Cache with TTL
async function getUser(userId: string) {
const cached = await cache.getJson<User>(`user:${userId}`)
if (cached) return cached.value
const user = await fetchFromDatabase(userId)
await cache.setJson(`user:${userId}`, user, { ttl: 5 * 60 })
return user
}