TinyStore

KV / Once

Once

once runs a function once per key and keeps its answer: a payment requested twice by a flaky network charges once, and the second request gets the same receipt. With a short keep, it is a memo that many callers share: the dashboard's panels are computed once for everyone who asks at once.

const charges = store.once<Receipt>('charges', { keep: '1d' })

const receipt = await charges.run(request.headers.get('idempotency-key')!, () =>
	stripe.charge(order, { idempotencyKey: order.id }),
)
charges = store.once("charges", Receipt, keep="1d")

receipt = await charges.run(request.headers["idempotency-key"], lambda: stripe.charge(order, idempotency_key=order.id))
charges, err := kv.OpenOnce[Receipt](store, "charges", kv.Keep(24*time.Hour))

receipt, err := charges.Run(ctx, r.Header.Get("Idempotency-Key"), func(ctx context.Context) (Receipt, error) {
	return stripe.Charge(ctx, order, order.ID)
})
let charges = store.once::<Receipt>("charges").keep(Duration::from_hours(24)).open()?;

let receipt = charges.run(&idempotency_key, || stripe.charge(&order, &order.id))?;

What run does

When run(key, fn) is calledIt
the key has a kept answerreturns it, and runs nothing
the key is running elsewherewaits for that run, in this process or another, and returns its answer
the key is freeruns fn, keeps its answer for keep, and returns it
fn throwskeeps nothing: the next run runs again

A run of the same key inside its own run is invalid, because it would wait for itself (contract KV-11).

A memo

const panels = store.once<Panel[]>('panels', { keep: '30s' })
const data = await panels.run(`dashboard:${id}`, () => computePanels(id))
panels = store.once("panels", list[Panel], keep="30s")
data = await panels.run(f"dashboard:{id}", lambda: compute_panels(id))
panels, err := kv.OpenOnce[[]Panel](store, "panels", kv.Keep(30*time.Second))
data, err := panels.Run(ctx, "dashboard:"+id, func(ctx context.Context) ([]Panel, error) { return computePanels(ctx, id) })
let panels = store.once::<Vec<Panel>>("panels").keep(Duration::from_secs(30)).open()?;
let data = panels.run(&format!("dashboard:{id}"), || compute_panels(&id))?;

Ten users who open the dashboard in the same second share one computation, and the next 30 seconds answer from what it kept.

What it does not promise

once runs a key at most once while its answer is kept. It cannot make an effect happen exactly once: if the process dies after the charge and before the answer is kept, the next run charges again. Pass the same key to the payment provider too, so that its own deduplication covers that gap. A declined card that must not be charged again is an answer to keep, not an error to throw.

See also

Edit this page on GitHub Updated 19 October 2026

esc
↑↓ navigate ↵ open ⇥ filter 0 results