Lodestone

Docs

Stores

Lodestone keeps a little state: the model catalogue, which models are failing (the circuit breaker), saved answers, and, if you use them, rate-limit and spend counters. By default that state lives in memory, which is lost when a serverless function ends and is not shared between functions. For anything real, give Lodestone a store.

A store is two methods:

interface KeyValueStore {
  get(key: string): Promise<unknown>;
  set(key: string, value: unknown, opts?: { ttlMs?: number }): Promise<void>;
}

ttlMs says how long a value is worth keeping; a store that cannot expire values may ignore it. A store that can also count atomically (incrBy) makes the rate limiter exact across every function. Ready-made adapters need no client library: they take the binding or token you already have.

import { createHealth, createLodestone, netlifyBlobsStore } from '@extrafemi-ai/lodestone';
import { getStore } from '@netlify/blobs';

const store = netlifyBlobsStore(getStore('lodestone'));
const ai = createLodestone({ store, health: createHealth({ store }), /* ... */ });
AdapterUse it onExpires valuesCounts atomicallyNotes
netlifyBlobsStore(getStore('x'))Netlify Functionsby Lodestone (a value carries its own expiry)noFine for the catalogue, cached answers and the breaker. No atomic add, and writes from separate functions can overwrite each other.
cloudflareKvStore(env.KV)Cloudflare Workersby KVnoEventually consistent, and one key can be written about once a second. Fine for the catalogue and cached answers; the breaker learns a little slower. Not for counters.
upstashStore({ url, token })anywhere with fetch, including Vercel KVby RedisyesThe one to use for per-caller rate limits and spend counters. Plain fetch, so it runs on Workers, Deno, Bun and Node.
denoKvStore(await Deno.openKv())Deno and Deno Deployby Deno KVno
memoryStore()tests, one long-running processyesyes (in this process)The default.

Which store for which job

JobA plain store (Blobs, KV, Deno KV)Upstash
Model catalogue, cached answersfinefine
Circuit breakerfinefine
Per-caller rate limitexact inside one process, approximate between processesexact
Spend limit (createSpendLimit)exact inside one process, approximate between processesas good as a get/set ledger can be (see protecting your budget)

Writing your own

Anything that gets and sets JSON by key will do. Keys contain colons (lodestone:health); if your store dislikes them, safeKey(key) returns a version it will accept (anything outside letters, digits, ., _ and - becomes ~ and two hex digits, so two different keys never collide).

What comes back from a store is checked before it is used: a catalogue, breaker record or ledger of the wrong shape or an absurd size is ignored, so a store that someone else can write to cannot make Lodestone hide every model or grow without limit.