Skip to content
Sponsor

Local quickstart

Run one Astilba Cache miss and hit with the implemented memory Store in the current source workspace.

This walkthrough caches one product with the smallest configuration the current source can run. The first read calls the factory; the second reuses the stored result.

The portable constructor requires explicit capabilities:

Part Purpose here
createCache() Creates the application-facing cache.
memory() Supplies a bounded, per-instance Store. This walkthrough uses it as a development L2 because a factory fill currently requires L2.
Clock Supplies logical time to the portable kernel.
Rng Supplies randomness without hard-coding a platform source into the kernel.
Factory Loads the product when no stored value is usable.

No Registry or Bus is configured, so this example demonstrates read, fill, and reuse—not distributed invalidation.

cache.ts
import { createCache, memory, t } from "@astilba/cache"
import type { Clock, Rng } from "@astilba/cache"
interface Product {
id: string
name: string
}
const clock: Clock = { now: () => Date.now() }
const rng: Rng = { next: () => Math.random() }
// memory() is normally an isolate-local L1. Using it as L2 here gives the
// unreleased kernel the Store it requires without pretending this is durable.
const developmentStore = memory({
clock,
maxEntries: 512,
maxBytes: 5_000_000,
})
const cache = createCache({
namespace: "storefront",
clock,
rng,
l2: developmentStore,
})
const productId = "sku-123"
let originLoads = 0
async function loadProduct(
id: string,
signal: AbortSignal,
): Promise<Product> {
if (signal.aborted) throw signal.reason
originLoads += 1
return { id, name: "Canvas backpack" }
}
const options = {
key: `product:${productId}`,
tags: [t`product:${productId}`],
factory: ({ signal }: { signal: AbortSignal }) =>
loadProduct(productId, signal),
}
const first = await cache.getOrSet(options)
const second = await cache.getOrSet(options)
console.log(first, second, originLoads) // same product, one origin load

The first call misses, runs loadProduct(), encodes the result, and writes it to the supplied Store. The second call resolves the same canonical key and reads that stored value. The tag records a dependency you could later invalidate after adding a Registry.

The application-facing portion begins at options: choose a stable key, declare dependency tags, and provide the origin factory. A runtime adapter should own most of the construction above it.

This example does exercise:

  • the implemented memory() Store, including its entry and byte bounds;
  • the implemented t tag builder;
  • the root createCache() and getOrSet() paths;
  • the built-in JSON codec and one in-isolate singleflight identity.

It deliberately leaves out:

  • durability or sharing across isolates;
  • explicit invalidation, which requires a Registry;
  • live invalidation delivery and recovery, which require Registry, Bus, and L2 together;
  • elapsed TTL and grace behavior, which remains unfinished even though the options are typed;
  • request identity, cross-isolate locks, telemetry, and the React Router response-cache integration.
  • Cloudflare Workers replaces the manual clock, random source, and development Store with the current Workers factory and drivers.
  • React Router exposes that cache through server middleware and carries request identity to reads.
  • Cache fundamentals explains the storage and invalidation vocabulary.
  • Inspect cache behavior shows how to inspect a cache value with explain() and configure event telemetry.
  • Implementation status lists every incomplete or provisional surface.