Skip to content

@meshqljs/access-cache (@meshql/access-cache)

Cache access permission results per user so expensive rules (DB lookups, dynamic field lists) run once per TTL window.

Terminal window
npm install @meshqljs/access-cache @meshqljs/access @meshqljs/core
Terminal window
npx jsr add @meshql/access-cache @meshql/access @meshql/core

Published on npm as @meshqljs/access-cache and JSR as @meshql/access-cache.

import { createMesh } from "@meshqljs/core";
import { withAccessCache } from "@meshqljs/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);
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);
import { createRedisAccessCacheStore, withAccessCache } from "@meshqljs/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 });
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.

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

  • TTL: 60 seconds
  • Key prefix: meshql:access:
  • Principal: userId:role:tenantId from QueryContext

See also access and persisted-queries.