cache function

Middleware cache({
  1. required String cacheName,
  2. bool wait = false,
  3. String? cacheControl,
  4. FutureOr<String> keyGenerator(
    1. Context c
    )?,
  5. List<int> cacheableStatusCodes = const [200],
  6. void onCacheNotAvailable()?,
})

In-memory response cache middleware.

Caches full responses keyed by URL (or a custom keyGenerator). On a cache hit the response is returned immediately without running the handler. Cached entries are automatically invalidated when max-age in cacheControl expires.

Responses with Cache-Control: no-store/no-cache/private, Vary: *, or Set-Cookie headers are never cached.

// Basic — cache all 200 responses for 1 hour
app.get('*', cache(
  cacheName: 'my-app',
  cacheControl: 'max-age=3600',
));

// Cache specific status codes
app.get('*', cache(
  cacheName: 'my-app',
  cacheControl: 'max-age=3600',
  cacheableStatusCodes: [200, 404, 412],
));

// Custom key + lifecycle hooks
app.use(cache(
  cacheName: 'my-app-v1',
  keyGenerator: (c) => '${c.req.method}:${c.req.path}',
  onCacheNotAvailable: () =>
      print('Custom log: Cache API is not available.'),
));

Implementation

Middleware cache({
  required String cacheName,
  bool wait = false, // kept for API compatibility
  String? cacheControl,
  FutureOr<String> Function(Context c)? keyGenerator,
  List<int> cacheableStatusCodes = const [200],
  void Function()? onCacheNotAvailable,
}) {
  final store = _openStore(cacheName);
  final maxAge = _parseMaxAge(cacheControl);

  return (Context c, Next next) async {
    final key = keyGenerator != null
        ? await keyGenerator(c)
        : c.req.url.toString();

    // ── Cache hit ──────────────────────────────────────────────────────────
    final entry = store[key];
    if (entry != null && !entry.isExpired) {
      c.respond(entry.response);
      if (cacheControl != null) c.header('Cache-Control', cacheControl);
      return;
    }

    // Remove stale entry so it doesn't block future caching
    if (entry != null && entry.isExpired) store.remove(key);

    // ── Cache miss ─────────────────────────────────────────────────────────
    await next();

    final response = c.response;
    if (response == null) return;

    if (!cacheableStatusCodes.contains(response.statusCode)) return;
    if (_shouldSkipCache(response)) return;

    if (cacheControl != null) c.header('Cache-Control', cacheControl);

    final expiresAt = maxAge != null
        ? DateTime.now().add(Duration(seconds: maxAge))
        : null;

    store[key] = _CachedEntry(response, expiresAt);
  };
}