Skip to content

Cache

SemitraCache is the cache subsystem in Semitra.

Use it when you want fast read/write access to derived or reusable values.

Semitra cache supports:

  • get() and read()
  • set() and write()
  • has() and exists()
  • delete()
  • remember() and fetch()
  • custom serialization and deserialization
  • scoped namespaces

Cache entries can be:

  • tenant-scoped
  • global

If tenant prefixing is enabled, Semitra derives a namespace from the current tenant automatically.

  • 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.

  • read-through caching
  • short-lived derived data
  • tenant-specific lookup results
  • expensive computations that are safe to reuse

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.