Key-Value Storage
Get, set, delete with JSON support and conditional writes
Data Structures
Hashes, lists, and sorted sets for complex state
Rate Limiting
Sliding window algorithm with one function call
Automatic TTL
Expiration in seconds, milliseconds, or timestamps
Overview
The KV Store provides a globally distributed key-value store with sub-millisecond latency. Store JSON values, implement rate limiting, build leaderboards with sorted sets, and manage user sessions — all without provisioning or managing infrastructure. Every key is automatically namespaced per app, ensuring complete data isolation.
Quick Start
Get started with key-value operations in seconds:
import { createKv } from '@sylphx/sdk/server'
const kv = createKv()
// Store a user profile with 1-hour TTL
await kv.set('user:123', { name: 'John', plan: 'pro' }, { ex: 3600 })
// Retrieve the profile
const { value, ttl } = await kv.get('user:123')
// value = { name: 'John', plan: 'pro' }, ttl = 3540
// Atomic page view counter
const views = await kv.incr('page:home:views')
// Rate limit API requests
const { success, remaining } = await kv.ratelimit('api:user:123', {
limit: 100,
window: '1h',
})
if (!success) {
return new Response('Too Many Requests', { status: 429 })
}Singleton Pattern
getKv() instead of createKv() for a singleton instance that reuses the same client across your application. Both read from SYLPHX_SECRET_KEY and SYLPHX_PLATFORM_URL environment variables.Basic Operations
Core key-value operations with full JSON support and conditional writes:
import { getKv } from '@sylphx/sdk/server'
const kv = getKv()
// SET — store any JSON-serializable value
await kv.set('user:123', { name: 'John', score: 100 })
// SET with TTL — expires in 1 hour (3600 seconds)
await kv.set('session:abc', { userId: '123' }, { ex: 3600 })
// SET with millisecond precision TTL
await kv.set('lock:resource', true, { px: 5000 }) // 5 seconds
// SET with Unix timestamp expiration
await kv.set('promo:summer', { discount: 20 }, {
exat: Math.floor(Date.now() / 1000) + 86400 // expires in 24h
})
// SET if Not eXists — only set if key is new
await kv.set('user:123:email', 'john@example.com', { nx: true })
// SET if eXists — only update existing keys
await kv.set('user:123:email', 'new@example.com', { xx: true })
// GET — returns value and remaining TTL
const { value, ttl } = await kv.get('user:123')
// value: { name: 'John', score: 100 }, ttl: null (no expiry)
// DELETE — returns number of keys removed (0 or 1)
const deleted = await kv.del('session:abc')
// EXISTS — check if a key exists
const exists = await kv.exists('user:123') // true
// EXPIRE — set or update TTL on existing key
await kv.expire('user:123', 7200) // expire in 2 hoursKey Namespacing
user:123 is stored as app:<appId>:user:123, ensuring complete isolation between applications.Rate Limiting
Built-in sliding window rate limiting with a single function call. No need to implement your own algorithm or manage server-side scripts:
import { getKv } from '@sylphx/sdk/server'
const kv = getKv()
// Basic API rate limiting — 100 requests per hour
const { success, remaining, limit, reset } = await kv.ratelimit(
'api:user:123',
{ limit: 100, window: '1h' }
)
if (!success) {
return new Response('Too Many Requests', {
status: 429,
headers: {
'X-RateLimit-Limit': String(limit),
'X-RateLimit-Remaining': String(remaining),
'X-RateLimit-Reset': String(reset),
'Retry-After': String(Math.ceil((reset - Date.now()) / 1000)),
},
})
}
// Different limits per tier
const limits = {
free: { limit: 100, window: '1h' },
pro: { limit: 1000, window: '1h' },
enterprise: { limit: 10000, window: '1h' },
}
const result = await kv.ratelimit(
`api:${userId}`,
limits[userPlan]
)
// Login attempt limiting — 5 attempts per 15 minutes
const loginResult = await kv.ratelimit(
`login:${email}`,
{ limit: 5, window: '15m' }
)
// Short burst limiting — 10 requests per 10 seconds
const burstResult = await kv.ratelimit(
`burst:${ip}`,
{ limit: 10, window: '10s' }
)| Window Format | Description | Example |
|---|---|---|
| Ns | N seconds | 10s, 30s |
| Nm | N minutes | 1m, 15m |
| Nh | N hours | 1h, 24h |
| Nd | N days | 1d, 7d |
Sliding Window Algorithm
Data Structures
Beyond simple key-value pairs, the KV store supports rich data structures for more complex use cases:
import { getKv } from '@sylphx/sdk/server'
const kv = getKv()
// Store user profile as a hash — update individual fields
await kv.hset('user:123', {
name: 'John Doe',
email: 'john@example.com',
plan: 'pro',
loginCount: 42,
})
// Get a single field
const name = await kv.hget('user:123', 'name')
// 'John Doe'
// Get all fields
const profile = await kv.hgetall('user:123')
// { name: 'John Doe', email: 'john@example.com', plan: 'pro', loginCount: 42 }
// Update just one field without touching the rest
await kv.hset('user:123', { loginCount: 43 })Hashes
Object-like storage. Update individual fields without rewriting the entire value.
User profiles, settings, metadataLists
Ordered collections. Push to head or tail, retrieve by range.
Activity feeds, queues, notificationsSorted Sets
Scored members with automatic ordering. Query by rank or score range.
Leaderboards, priority queues, rankingsBatch Operations
Reduce round trips with multi-key operations. Get or set up to 100 keys in a single request:
import { getKv } from '@sylphx/sdk/server'
const kv = getKv()
// MSET — set multiple keys atomically
await kv.mset([
{ key: 'config:theme', value: 'dark' },
{ key: 'config:locale', value: 'en-US' },
{ key: 'config:timezone', value: 'America/New_York' },
])
// MSET with shared TTL
await kv.mset(
[
{ key: 'cache:user:1', value: { name: 'Alice' } },
{ key: 'cache:user:2', value: { name: 'Bob' } },
],
{ ex: 300 } // all expire in 5 minutes
)
// MGET — get multiple keys in one round trip
const values = await kv.mget(['config:theme', 'config:locale', 'config:timezone'])
// { 'config:theme': 'dark', 'config:locale': 'en-US', 'config:timezone': 'America/New_York' }
// Missing keys return null
const mixed = await kv.mget(['exists:key', 'missing:key'])
// { 'exists:key': 'value', 'missing:key': null }Batch Limits
mget and mset accept up to 100 keys per request. For larger datasets, split operations into multiple batches.API Reference
Complete reference for all KV client methods:
| Method | Returns | Description |
|---|---|---|
| get(key) | { value, ttl } | Get value and remaining TTL |
| set(key, value, opts?) | boolean | Set value with optional TTL and NX/XX flags |
| del(key) | number | Delete a key (returns 0 or 1) |
| exists(key) | boolean | Check if key exists |
| incr(key, by?) | number | Atomic increment (negative to decrement) |
| expire(key, seconds) | boolean | Set or update TTL on existing key |
| mget(keys) | Record<string, T> | Get up to 100 values in one request |
| mset(entries, opts?) | void | Set up to 100 key-value pairs atomically |
| ratelimit(key, opts) | { success, remaining, ... } | Sliding window rate limiting |
| hset(key, fields) | number | Set hash fields (returns created count) |
| hget(key, field) | T | null | Get a single hash field |
| hgetall(key) | Record<string, T> | null | Get all hash fields and values |
| lpush(key, ...values) | number | Push to list head (returns new length) |
| lrange(key, start?, stop?) | T[] | Get list elements by index range |
| zadd(key, ...members) | number | Add scored members to sorted set |
| zrange(key, start?, stop?, opts?) | KvZMember[] | Get sorted set members by rank |
Set Options
| Property | Type | Description |
|---|---|---|
ex | number | Expire time in seconds |
px | number | Expire time in milliseconds |
exat | number | Unix timestamp (seconds) at which the key will expire |
pxat | number | Unix timestamp (milliseconds) at which the key will expire |
nx | boolean | Only set if the key does not already exist |
xx | boolean | Only set if the key already exists |
Rate Limit Result
| Property | Type | Description |
|---|---|---|
successrequired | boolean | Whether the request is allowed (under the rate limit) |
limitrequired | number | The configured maximum number of requests |
remainingrequired | number | Remaining requests in the current window |
resetrequired | number | Unix timestamp (ms) when the rate limit resets |
Best Practices
Use Descriptive Key Patterns
Follow a consistent naming convention like entity:id:field — for example user:123:profile, cache:api:response, counter:page:views
Always Set TTLs on Caches
Prevent stale data and unbounded memory growth by setting expiration on cached values. Use ex for seconds or px for millisecond precision.
Use NX for Distributed Locks
Combine SET with NX (not exists) and a TTL to implement safe distributed locks that auto-release on expiration.
Prefer Batch Operations
Use mget and mset instead of multiple individual calls. Each batch operation is a single network round trip, reducing latency.
Choose the Right Data Structure
Use hashes for objects with individual field access, lists for queues and feeds, sorted sets for ranked data like leaderboards.
Rate Limit at Multiple Layers
Apply rate limits at different granularities — per user, per IP, per endpoint — with different windows for burst vs sustained traffic.