kv
Per-app key-value storage. For what the kv-store is for (and when
db
fits better), see the Key-Value Data Store.
import { kv } from "@lodekit/sdk";Works in the browser and in server functions. Values are JSON-serialized; keys are strings of 1–512 characters. TTLs are in seconds; the timestamps in returned entries are milliseconds since the epoch. Writes travel in the scoped API’s request body, which caps at 100 kb per request.
kv.get()
Section titled “kv.get()”get<T = unknown>(key: string): Promise<T | undefined>key— the key to read.
Returns the value for key, or undefined if the key doesn’t exist or its TTL
has expired. Expiry is lazy: an expired row is deleted on read and reported
missing.
Example
Section titled “Example”const snoozed = await kv.get<boolean>(snoozeKey(plantId));kv.set()
Section titled “kv.set()”set(key: string, value: unknown, opts?: { ttl?: number }): Promise<void>key— the key to write.value— any JSON-serializable value.opts.ttl— time to live in seconds. Must be positive.
Upserts. With ttl, the entry expires that many seconds from now. Without it,
the entry never expires — and re-setting an existing key without ttl clears
any previous expiry.
Example
Section titled “Example”Greenhouse’s snooze flag expires on its own:
await kv.set(snoozeKey(data.plantId), true, { ttl: data.hours * 3600 });kv.del()
Section titled “kv.del()”del(key: string): Promise<boolean>Deletes key. Returns true if a row was deleted, false if it didn’t exist.
Example
Section titled “Example”await kv.del(snoozeKey(plantId));kv.has()
Section titled “kv.has()”has(key: string): Promise<boolean>Returns whether key exists (and hasn’t expired).
Example
Section titled “Example”if (await kv.has("seeded")) return;kv.incr()
Section titled “kv.incr()”incr(key: string, by?: number): Promise<number>key— the counter key.by— amount to add. Default1.
Atomically increments the number at key and returns the new value. A missing
key starts from 0. If the existing value isn’t a number, the call rejects. An
existing TTL is preserved (Redis INCR semantics).
Example
Section titled “Example”Greenhouse care streaks:
const streak = await kv.incr(streakKey(plantId));kv.list()
Section titled “kv.list()”list<T = unknown>(opts?: { prefix?: string; limit?: number; cursor?: string;}): Promise<{ entries: KvEntry<T>[]; nextCursor: string | null }>opts.prefix— only keys starting with this prefix.opts.limit— page size. Default 1000 (thekv.list_limitplatform setting), clamped to 1–1000.opts.cursor— resume after this key.
Returns entries ordered by key ascending, excluding expired ones. Pagination is
keyset: nextCursor is the last key of the page — pass it back as cursor for
the next page; null means you’ve reached the end.
Example
Section titled “Example”let cursor: string | undefined;do { const page = await kv.list({ prefix: "streak:", cursor }); for (const entry of page.entries) { // entry.key, entry.value, entry.createdAt, ... } cursor = page.nextCursor ?? undefined;} while (cursor !== undefined);kv.getMany()
Section titled “kv.getMany()”getMany<T = unknown>(keys: string[]): Promise<KvEntry<T>[]>keys— the keys to read.
Returns entries for the given keys in one round trip. Missing and expired keys
are skipped, so the result can be shorter than keys.
Example
Section titled “Example”Greenhouse fans in every plant’s streak and snooze state at once:
const keys = rows.flatMap(({ id }) => [streakKey(id), snoozeKey(id)]);const entries = keys.length ? await kv.getMany(keys) : [];kv.setMany()
Section titled “kv.setMany()”setMany(entries: { key: string; value: unknown }[], opts?: { ttl?: number }): Promise<void>entries— key/value pairs to write.opts.ttl— time to live in seconds, applied to every entry.
Writes all entries in a single transaction — either all land or none do.
Example
Section titled “Example”await kv.setMany([ { key: streakKey(1), value: 0 }, { key: streakKey(2), value: 0 },]);kv.collection()
Section titled “kv.collection()”collection<T>(name: string): Collection<T>name— the collection’s name.
A convenience layer over plain kv for small lists of items with auto-assigned
numeric ids. Returns a Collection handle:
interface Collection<T> { add(value: T): Promise<{ id: number; value: T }>; all(): Promise<{ id: number; value: T }[]>; get(id: number): Promise<T | undefined>; set(id: number, value: T): Promise<void>; remove(id: number): Promise<boolean>;}add(value)— stores the item under the next id and returns{ id, value }. Never sets a TTL.all()— every item, sorted bycreatedAtascending (insertion order). Pages through the store internally.get(id)— one item, orundefined.set(id, value)— replaces one item.remove(id)— deletes one item;trueif it existed.
Key convention: item id of collection name lives at the ordinary kv key
name:id (for example books:3), and the id sequence is a counter at
_seq:name advanced with kv.incr(). Collections are just kv keys — plain kv
calls see them (kv.list({ prefix: "books:" })) and vice versa.
Example
Section titled “Example”const books = kv.collection<{ title: string; author: string }>("books");const { id } = await books.add({ title: "The Overstory", author: "Richard Powers" });const shelf = await books.all();KvEntry
Section titled “KvEntry”interface KvEntry<T = unknown> { key: string; value: T; createdAt: number; updatedAt: number; expiresAt: number | null;}key— the entry’s key.value— the stored value.createdAt/updatedAt— ms-epoch timestamps.expiresAt— ms-epoch expiry, ornullfor entries without a TTL.