Skip to content
Sponsor

Preview walkthrough

Follow one Astilba Cache miss and hit with the smallest configuration the current source can run.

This walkthrough caches one product. The first call loads it from the source; the second call reuses the stored result.

The current source makes the setup more explicit than a finished runtime integration should. It requires:

Part Meaning in this example
cache The object application code reads through.
DevelopmentStore A minimal key/value Store backed by a Map. It stands in for a real storage driver.
L2 The Store supplied as l2. L2 is the shared or durable tier in the Cache model, although this Map is neither shared nor durable.
Clock The source of time supplied to the portable kernel.
Rng The source of randomness supplied to the portable kernel.
Factory loadProduct(), which runs when Cache cannot reuse a stored value.

You do not need Registry, Bus, Lock, a custom Codec, or telemetry for this read-and-reuse path.

cache.ts
import { compound, createCache } from "@astilba/cache"
import type {
Clock,
Rng,
Store,
StoreValue,
StoreWriteOptions,
} from "@astilba/cache"
class DevelopmentStore implements Store {
readonly #values = new Map<string, StoreValue>()
async get(key: string): Promise<StoreValue | undefined> {
return this.#values.get(key)
}
async set(
key: string,
value: string,
options?: StoreWriteOptions,
): Promise<void> {
this.#values.set(key, {
value,
...(options?.metadata === undefined
? {}
: { metadata: options.metadata }),
})
}
async delete(key: string): Promise<void> {
this.#values.delete(key)
}
}
interface Product {
id: string
name: string
}
const clock: Clock = { now: () => Date.now() }
const rng: Rng = { next: () => Math.random() }
const store = new DevelopmentStore()
const cache = createCache({
namespace: "storefront",
clock,
rng,
l2: store,
})
const productId = "sku-123"
let originLoads = 0
const loadProduct = async (id: string): Promise<Product> => {
originLoads += 1
return { id, name: "Canvas backpack" }
}
const options = {
key: `product:${productId}`,
tags: [compound("product", productId)],
factory: async ({ signal }: { signal: AbortSignal }) => {
if (signal.aborted) throw signal.reason
return loadProduct(productId)
},
}
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 the factory, and writes the value to the supplied store. The second call resolves the same canonical key and reads that stored value. The compound tag records a stable dependency target for later invalidation.

Read the application-facing part from const options downward: choose a key, optionally attach dependency tags, and provide the factory. Everything above it is preview-only runtime wiring that a supported adapter should eventually hide.

  • It has no L1, so all retained values live in the supplied store.
  • It has no Registry or Bus, so tag and key invalidation methods are not usable.
  • It deliberately omits TTL and grace because elapsed-time behavior is unfinished.
  • Its Store has no durability, replication, limits, or write classification.

For coordinated invalidation, add registry and bus together; do not add only one. Keep L2 for factory fills and durable delta replay. The tested Cloudflare implementations are still internal and cannot be imported from a supported package entry point.