Cache
SemitraCache is the cache subsystem in Semitra.
Use it when you want fast read/write access to derived or reusable values.
What cache does
Section titled “What cache does”Semitra cache supports:
get()andread()set()andwrite()has()andexists()delete()remember()andfetch()- custom serialization and deserialization
- scoped namespaces
Scope model
Section titled “Scope model”Cache entries can be:
- tenant-scoped
- global
If tenant prefixing is enabled, Semitra derives a namespace from the current tenant automatically.
Example use cases
Section titled “Example use cases”- cache a permissions lookup for a short period
- memoize an expensive query result
- store a derived API payload for repeated reads
- cache feature flags or configuration snapshots
- reduce pressure on D1 for read-heavy endpoints
The common Semitra pattern is a tenant-scoped read-through cache:
import { SemitraCache } from "@semitra/cli";
const cache = SemitraCache.fromKvNamespace(env.CACHE, { keyPrefix: "api" });
const permissions = await cache.remember( `permissions:${userId}`, async () => loadPermissions(userId), { namespace: "auth", scope: "tenant", ttl: 60 });That is a good fit when the same request shape repeatedly resolves the same derived data for a tenant or authenticated user.
When the source-of-truth record changes, invalidate the cached derivative explicitly:
await cache.delete("posts:index", { namespace: "api" });await cache.delete(`post:${postId}`, { namespace: "api" });That gives you a clean split: D1 stays authoritative, while cache entries stay cheap and disposable.
Good use cases
Section titled “Good use cases”- read-through caching
- short-lived derived data
- tenant-specific lookup results
- expensive computations that are safe to reuse
When not to use cache
Section titled “When not to use cache”Do not use cache for:
- source-of-truth data
- data that must survive eviction
- state that needs transactional durability
Cache should accelerate reads, not replace persistence.