56 results available
Sign in
Skills directory

cache

encoredev

·

Last changed
Hardened

This skill has been hardened by Chainguard

Last changed

This version passed the hardening gate. Hardening applied 7 targeted changes and preserved all other text. Classified findings before hardening: 0. Classified findings closed: 0. Classified findings remaining: 0. Unclassified scanner findings remaining: 0. Review findings closed: 9. Review findings remaining: 0.


Open before hardening

None

Open after hardening

None


Expand
File
Group
Subtype
Severity
Status

No findings available

0 findings


SKILL.md

Encore Caching (Redis)

Instructions

Encore's cache is a typed wrapper around Redis. Declare a CacheCluster once, then create Keyspace objects for each shape of data you need.

Cluster

import { CacheCluster } from "encore.dev/storage/cache";

const cluster = new CacheCluster("my-cache", {
  evictionPolicy: "allkeys-lru",
});

Reference a cluster from another service: const cluster = CacheCluster.named("my-cache");

Keyspaces on a shared cluster are not isolated from each other. Start each key pattern with the owning service name. Let the owning service handle writes. Keep sensitive data in a separate cluster, and share data with other services through explicit APIs.

Eviction policies: "allkeys-lru" (default), "noeviction", "allkeys-lfu", "allkeys-random", "volatile-lru", "volatile-lfu", "volatile-ttl", "volatile-random". With "noeviction", writes fail with an error once memory is full.

Keyspace types

Each keyspace has a key shape (used to build the Redis key from keyPattern) and a value type.

import {
  StringKeyspace,
  IntKeyspace,
  FloatKeyspace,
  StructKeyspace,
  StringListKeyspace,
  NumberListKeyspace,
  StringSetKeyspace,
  NumberSetKeyspace,
  expireIn,
} from "encore.dev/storage/cache";

// Strings
const tokens = new StringKeyspace<{ tokenId: string }>(cluster, {
  keyPattern: "token/:tokenId",
  defaultExpiry: expireIn(3600 * 1000),
});
await tokens.set({ tokenId: "abc" }, "value");
const val = await tokens.get({ tokenId: "abc" }); // undefined on miss

// Integers (atomic counters)
const counters = new IntKeyspace<{ userId: string }>(cluster, {
  keyPattern: "requests/:userId",
  defaultExpiry: expireIn(10 * 1000),
});
const count = await counters.increment({ userId: "user123" }, 1);

// Structs (JSON)
interface UserProfile { name: string; email: string; }
const profiles = new StructKeyspace<{ userId: string }, UserProfile>(cluster, {
  keyPattern: "profile/:userId",
  defaultExpiry: expireIn(3600 * 1000),
});
await profiles.set({ userId: "123" }, { name: "Alice", email: "alice@example.com" });

// Lists
const recent = new StringListKeyspace<{ userId: string }>(cluster, {
  keyPattern: "recent/:userId",
  defaultExpiry: expireIn(3600 * 1000),
});
await recent.pushRight({ userId: "user123" }, "item1", "item2");

// Sets
const tags = new StringSetKeyspace<{ articleId: string }>(cluster, {
  keyPattern: "tags/:articleId",
  defaultExpiry: expireIn(3600 * 1000),
});
await tags.add({ articleId: "post1" }, "typescript", "encore");
const has = await tags.contains({ articleId: "post1" }, "typescript");

Multi-field key patterns

interface Key { userId: string; resourcePath: string; }

const requests = new IntKeyspace<Key>(cluster, {
  keyPattern: "requests/:userId/:resourcePath",
  defaultExpiry: expireIn(10 * 1000),
});

Expiry helpers

import {
  expireIn,          // milliseconds
  expireInSeconds,
  expireInMinutes,
  expireInHours,
  expireDailyAt,     // a specific UTC time each day
  neverExpire,
  keepTTL,           // keep existing TTL when updating
} from "encore.dev/storage/cache";

Write options

await keyspace.set(key, value, { expiry: expireInMinutes(30) });
await keyspace.set(key, value, { expiry: keepTTL });
await keyspace.setIfNotExists(key, value);  // throws CacheKeyExists if present
await keyspace.replace(key, value);          // throws CacheMiss if absent

Errors

import { CacheMiss, CacheKeyExists } from "encore.dev/storage/cache";

const value = await keyspace.get(key);  // undefined on miss (does not throw)

Wrap calls to setIfNotExists() in error handling that catches CacheKeyExists. Wrap calls to replace() in error handling that catches CacheMiss. Handle each case explicitly so the caller gets a clear result rather than a server error.

Guidelines

  • Declare CacheCluster and keyspaces at package level.
  • Pick the most specific keyspace type — IntKeyspace for counters gives you atomic increment/decrement for free.
  • get() returns undefined on miss. setIfNotExists() throws CacheKeyExists when the key is present, and replace() throws CacheMiss when the key is absent.
  • Local development uses an in-memory Redis with a ~100-key cap — don't load-test it.
  • Take key fields such as userId from the authenticated caller rather than from request input. Check free-form fields such as resourcePath against an allowlist or a strict pattern that rejects the slash separator, so a value in one segment cannot collide with another key.
  • Under the default "allkeys-lru" policy, counters and tokens can be evicted under memory pressure. That resets rate limits and drops sessions. Keep security related counters and session data in a dedicated, adequately sized cluster using "noeviction" or a volatile policy. Treat limits backed by the cache as best effort, and choose explicitly whether to fail open or fail closed on cache errors.
  • Use the cache only for short-lived token data with short expiry times. Store a hash of a token rather than the token itself, in both values and key fields, because key names appear in Redis monitoring and key listing output. Keep authoritative session state in encore-database.
  • Cap or trim list and set sizes when user input drives pushes or adds, and give collection keyspaces an expiry.
  • Confirm how expiry applies to each increment before you rely on a counter for a fixed window. For fixed windows, include the current time window as a key field, and give every counter an expiry rather than neverExpire.
  • For durable storage, use encore-database (Postgres) or encore-bucket (object storage) instead.

Install

Install with chainctl
chainctl skill install public/encoredev/cache:latest
Get started with chainctl
View source

Harden your own skills

Bring Chainguard's hardening pipeline to your first-party skills — custom rulesets, automated remediation, full audit trail.

Provenance

Upstream sourcehttps://github.com/encoredev/skills
Upstream commitcb69bb1f246274419aed69c84ee5815955c4c499
OCI reference

skills.cgr.dev/public/github.com/encoredev/encore/cache:latest

License

Apache-2.0

Files

HARDENING.md

LICENSE

SKILL.md


The trusted source for open source

Talk to an expert
PrivacyTerms

Product

Chainguard ContainersChainguard LibrariesChainguard VMsChainguard OS PackagesChainguard ActionsChainguard Agent SkillsIntegrationsPricing
© 2026 Chainguard, Inc. All Rights Reserved.
Chainguard® and the Chainguard logo are registered trademarks of Chainguard, Inc. in the United States and/or other countries.
The other respective trademarks mentioned on this page are owned by the respective companies and use of them does not imply any affiliation or endorsement.