Skip to content

Flect kv

Part of the flect skill set.

Flect KV stores are Redis-compatible, powered by Valkey. Each store has its own key namespace. env.kv(binding) returns an official ioredis client whose keys are transparently prefixed, so stores never collide.

Terminal window
flect kv create my-cache
flect kv list
flect kv status <ref>
flect kv delete <ref>
[[kv]]
binding = "CACHE"
name = "my-cache"
import { createEnv } from '@getflect/sdk'
const env = createEnv()
const cache = await env.kv('CACHE') // official ioredis client
// strings + TTL — note the ioredis signature: set(key, val, 'EX', seconds)
await cache.set('session:abc', token, 'EX', 3600)
const val = await cache.get('session:abc')
await cache.del('session:abc')
// counters, lists, hashes, sets — full ioredis API
await cache.incr('hits:today')
await cache.lpush('queue:jobs', JSON.stringify(job))
await cache.hset('config', 'theme', 'dark')

Don’t invent methods — it’s the real ioredis. Use cache.set(k,v,'EX',ttl), not setJson/getJson.

async function getCached<T>(key: string, load: () => Promise<T>, ttl = 300): Promise<T> {
const hit = await cache.get(key)
if (hit) return JSON.parse(hit) as T
const data = await load()
await cache.set(key, JSON.stringify(data), 'EX', ttl)
return data
}

Invalidate on write (cache.del(key)) so the next read repopulates from the DB.

async function underLimit(ip: string, limit = 100): Promise<boolean> {
const key = `rl:${ip}:${Math.floor(Date.now() / 60000)}` // per minute
const n = await cache.incr(key)
if (n === 1) await cache.expire(key, 60)
return n <= limit
}

Keys are prefixed with the store’s namespace automatically — use short, natural keys. KEYS/SCAN only ever see your store’s keys.

Terminal window
flect dev # createEnv() resolves CACHE locally
docker run -d -p 6379:6379 valkey/valkey:8-alpine
# VALKEY_URL=redis://localhost:6379 (default)