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.
Understand the setup
Section titled “Understand the setup”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.
Read or fill one product
Section titled “Read or fill one product”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 loadThe 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.
Know what is and is not exercised
Section titled “Know what is and is not exercised”This example does exercise:
- the implemented
memory()Store, including its entry and byte bounds; - the implemented
ttag builder; - the root
createCache()andgetOrSet()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.
Move to a runtime integration
Section titled “Move to a runtime 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.