meshql-access-cache (@meshql/access-cache)
Cache access permission results per user so expensive rules (DB lookups, dynamic field lists) run once per TTL window.
Install
Section titled “Install”npm install meshql-access-cache meshql-access meshql-core# ornpx jsr add @meshql/access-cache @meshql/access @meshql/corePublished on npm as meshql-access-cache and JSR as @meshql/access-cache.
Example (in-memory, dev)
Section titled “Example (in-memory, dev)”import { createMesh } from "@meshql/core";import { withAccessCache } from "@meshql/access-cache";
const mesh = createMesh({ entities: { /* … */ } });
const { invalidate } = withAccessCache( mesh, { rules: { "user.email": (ctx) => ctx.role === "admin", }, rowAccess: { post: async (ctx, entityId) => { // expensive DB check — cached for 60s per user + post id return canReadPost(ctx.userId, entityId); }, }, }, { ttlSeconds: 60 },);
// After role change or content update:await invalidate.invalidateUser(userId);Production (Upstash Redis)
Section titled “Production (Upstash Redis)”import { createRedisAccessCacheStore, withAccessCache } from "@meshql/access-cache";
const store = createRedisAccessCacheStore({ url: process.env.UPSTASH_REDIS_REST_URL!, token: process.env.UPSTASH_REDIS_REST_TOKEN!,});
withAccessCache(mesh, accessOptions, { store, ttlSeconds: 60 });Upstash is Redis-compatible over HTTPS — no native Redis client required. Row access and dynamic field rules use the async store; static field rules fall back to live evaluation when the store is async-only.
Invalidation API
Section titled “Invalidation API”| Method | Purpose |
|---|---|
invalidateUser(userId) |
Clear cache for one user |
invalidateRole(role) |
Clear cache for one role principal |
invalidateAll() |
Clear entire access cache prefix |
Call these after role changes, permission updates, or content publishes that affect row-level access.
Defaults
Section titled “Defaults”- TTL: 60 seconds
- Key prefix:
meshql:access: - Principal:
userId:role:tenantIdfromQueryContext
See also access and persisted-queries.