npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

cds-caching

v3.0.0

Published

A caching plugin for SAP CAP applications

Readme

Welcome to cds-caching

npm version monthly downloads

Overview

A caching plugin for the SAP Cloud Application Programming Model (CAP) that improves performance by caching slow remote service calls, complex operations, and queries.

Please also read the introduction blog post: Boosting performance in SAP Cloud Application Programming Model (CAP) applications with cds-caching.

Key Features

  • Read-Through Caching – Transparently cache CQN queries, CAP requests, or function calls
  • Pluggable Storage – In-memory, SQLite, Redis, PostgreSQL, SAP HANA, or CDS database
  • Multi-Tenancy – Automatic tenant isolation for SAP BTP MTX deployments
  • TTL & Tag Support – Time-based expiry and tag-based invalidation
  • Compression – LZ4 or GZIP compression for cached data
  • Metrics & Monitoring – Hit rates, latencies, key-level tracking, and an OData API
  • Annotations – Declarative caching via @cache annotations on entities and functions

Documentation

| Guide | Description | |-------|-------------| | Programmatic API | Full API reference for cache operations | | Protocol Support | Caching across OData, REST, GraphQL, HCQL, and MCP | | Key Management | Key templates, context awareness, custom keys | | Security | Authorization, cache key isolation, production checklist | | Metrics Guide | Statistics, monitoring, and performance tracking | | OpenTelemetry Integration | Distributed tracing and metrics export | | OData API | REST endpoints for management and monitoring | | Dashboard | Setup and usage of the monitoring dashboard | | Feature Activation | Reuse vs own: metrics, API, and dashboard activation | | Deployment Guide | SAP BTP deployment for Redis, PostgreSQL, HANA, CDS | | MTX Hybrid Test | Manual BTP trial checklist for issue #18 / MTX | | Migration Guide | Upgrading to 3.0 and earlier releases | | Example Application | Sample app with caching patterns | | Federation Integration | Using cds-caching with cds-data |

Getting Started

Installation

npm install cds-caching

Requirements

| Dependency | Supported versions | |------------|--------------------| | SAP CAP (@sap/cds) | >= 8 (including cds 9 and cds 10) | | Node.js | >= 22 (cds 10 requires Node 22+, v24 recommended) | | @cap-js/sqlite (SQLite store) | ^1 on cds 8, ^2 on cds 9, ^3 on cds 10 |

The plugin runtime supports cds 8, cds 9, and cds 10. When running on cds 10, use Node.js 22 or higher and @cap-js/sqlite ^3.

Minimal Configuration

{
  "cds": {
    "requires": {
      "caching": {
        "impl": "cds-caching"
      }
    }
  }
}

This uses the in-memory store — no additional setup needed for development.

Upgrading to 3.0? See Upgrading to 3.0 — flush persistent caches after deploy; keys and tags change shape. From 1.x, also see Upgrading to 2.0 for the metrics / metrics.reuse config shape.

Data Model

The plugin ships CDS entity definitions for database-backed features. These load conditionally based on your configuration — no manual model property needed.

Full guide: Feature Activation — decision tree, BTP/MTX best practices, and rules for avoiding duplicate model loading.

| Option | Entities loaded | Purpose | |--------|-----------------|---------| | "metrics": { "enabled": true, … } | Caches, Metrics, KeyMetrics | Metrics persistence (skipped if API loaded via reuse or using) | | "store": "cds" | CacheStore | CDS-backed cache storage | | metrics.reuse.api or using … index.cds | CachingApiService + metrics entities | OData API (/odata/v4/caching-api/) | | metrics.reuse.dashboard | Same as reuse API + UI at /caching-dashboard | Package reuse (see reuse & compose) | | None of the above | Nothing | External stores only; basic caching still works |

Do not combine metrics.reuse.* with manual using … index.cds or cds add caching-metrics for the same concern — see the Feature Activation guide.

The auto-loading injects CDS files into cds.env.roots at plugin load time, before CAP compiles the model. Run cds deploy after enabling database features.

Basic Usage

const cache = await cds.connect.to("caching")

// Key-value operations
await cache.set("bp:1000001", businessPartnerData, { ttl: 60000 })
const data = await cache.get("bp:1000001")

// Read-through caching for CQN queries
const { result } = await cache.rt.run(query, db, { ttl: 30000 })

// Read-through caching for remote services
const { result } = await cache.rt.send(request, remoteService, { ttl: 10000 })

// Function caching
const cachedFn = cache.rt.wrap("expensive-op", expensiveFunction, { ttl: 3600 })
const { result } = await cachedFn("param1")

Annotation-Based Caching

service MyService {
  @cache: { ttl: 10000 }
  entity Products as projection on db.Products;

  @cache: { ttl: 10000, invalidateOnWrite: true }
  entity Orders as projection on db.Orders;

  @cache: { ttl: 60000 }
  function getRecommendations() returns array of Products;
}

When invalidateOnWrite is set, the cache for that entity is automatically cleared after any CREATE, UPDATE, or DELETE operation, so subsequent reads always return fresh data.

Annotations are protocol-agnostic: cds-caching binds at the CAP service-handler level, so a single @cache annotation applies whether the request arrives via OData, REST, GraphQL, HCQL, or the new MCP protocol adapter — no protocol-specific configuration required. MCP is read-only, so its reads are cached while writes over other protocols still invalidate the shared entries. See the Protocol Support guide for details.

Configuration

Store Types

| Store | Config | Use Case | Adapter Package | |-------|--------|----------|-----------------| | In-Memory | "memory" | Development, small-scale | Built-in | | SQLite | "sqlite" | Medium-size, single instance | @resolid/keyv-sqlite or @keyv/sqlite | | Redis | "redis" | Production, distributed | @keyv/redis | | PostgreSQL | "postgres" | Production, when Redis unavailable | @keyv/postgres | | CDS Database | "cds" | Production, HANA, multi-tenant | None (uses app's DB) | | SAP HANA | "hana" | Direct HANA connection | keyv-hana |

Recommendation: Use store: 'cds' for CAP applications on SAP HANA — it reuses your app's DB connection, requires no extra packages, and supports multi-tenancy automatically. Use store: 'redis' for best performance in distributed setups.

Full Configuration Options

{
  "cds": {
    "requires": {
      "caching": {
        "impl": "cds-caching",
        "namespace": "caching",
        "store": "redis",
        "compression": "lz4",
        "throwOnErrors": false,
        "transactionalOperations": false,
        "credentials": { },
        "metrics": {
          "enabled": true,
          "persistenceInterval": 60000,
          "reuse": {
            "api": false,
            "dashboard": false
          }
        },
        "keyManagement": {
          "isUserAware": false,
          "isTenantAware": false,
          "isLocaleAware": false
        }
      }
    }
  }
}

| Option | Default | Description | |--------|---------|-------------| | store | "memory" | Storage backend (memory, sqlite, redis, postgres, hana, cds) | | namespace | service name | Key prefix for store isolation | | compression | none | "lz4" or "gzip" | | throwOnErrors | false | Whether basic operations throw on cache errors | | operationTimeout | 2000 | Milliseconds a single cache operation may take before it counts as a failure; 0 removes the bound (docs) | | transactionalOperations | false | Isolate basic ops in dedicated cache transactions | | metrics | none | Metrics collection and persistence (see Feature Activation) | | metrics.enabled | false | Enable metrics collection | | metrics.persistenceInterval | 60000 | Interval (ms) for persisting hourly stats to the database | | metrics.reuse.api | false | Register CachingApiService from the plugin package (alternative: using … index.cds) | | metrics.reuse.dashboard | false | Serve the UI from the plugin (alternative: cds add caching-metrics) | | metrics.ui5Url | SAPUI5 CDN, pinned | UI5 runtime for the served dashboard; set this to serve UI5 yourself (docs) | | encryption.key | none | 32-byte key (base64 or hex) enabling AES-256-GCM encryption of cached values (docs) | | encryption.enabled | false | Require encryption without committing the key; startup fails if no key is supplied | | encryption.keyEnv | none | Name of the environment variable holding the key, for platform-supplied secrets (docs) | | statistics / dashboard | — | Removed in 3.0 — use metrics / metrics.reuse (migration) | | keyManagement.isTenantAware | false (auto true in MTX) | Include tenant in cache keys | | keyManagement.isUserAware | false | Include user in cache keys | | keyManagement.isLocaleAware | false | Include locale in cache keys |

Environment-Specific Configuration

{
  "cds": {
    "requires": {
      "caching": {
        "impl": "cds-caching",
        "store": "redis",
        "[development]": {
          "credentials": { "host": "localhost", "port": 6379 }
        },
        "[production]": {
          "credentials": { "url": "redis://production-redis:6379" }
        }
      }
    }
  }
}

Redis Connection Tuning

For store: "redis", the credentials object is passed through to @keyv/redis / @redis/client. You can use either a connection URL or explicit socket options:

{
  "cds": {
    "requires": {
      "caching": {
        "impl": "cds-caching",
        "store": "redis",
        "throwOnErrors": false,
        "credentials": {
          "url": "rediss://your-redis:6380",
          "pingInterval": 30000,
          "socket": { "keepAlive": true }
        }
      }
    }
  }
}

Use a redis:// or rediss:// URL, or connect via socket.host / socket.port instead of url.

If you see SocketClosedUnexpectedlyError in the logs every few minutes — common with TLS Redis, load balancers, or firewalls that drop idle connections — add pingInterval (milliseconds). The client sends periodic PING commands to keep the connection alive. Place it at the top level of credentials, not inside socket. TCP keepAlive under socket is enabled by default in @redis/client; pingInterval helps when the network path does not honor it.

With the default throwOnErrors: false, disconnects are logged but the application continues: cache operations fall back to misses and @keyv/redis reconnects automatically. If you set throwOnErrors: true, connection errors surface as thrown errors instead; reconnection still happens in the background either way.

An outage cannot stall requests. Commands are never queued while the connection is down, and every cache operation runs under operationTimeout (2s by default), so an unreachable store degrades to cache misses instead of leaving requests waiting.

For detailed key configuration and deployment instructions, see Key Management and Deployment Guide.

Service Integration

The plugin includes CachingApiService, an OData service for managing caches, browsing entries, and viewing metrics. It powers the dashboard and can be consumed by any OData client.

See Feature Activation for reuse vs own activation, BTP/MTX best practices, and API authorization.

Quick reference:

| Goal | Approach | |------|----------| | Local dev with dashboard | metrics.reuse.api + metrics.reuse.dashboard + metrics.enabled | | BTP with HTML5 repo | cds add caching-metrics + metrics.enabled — no metrics.reuse.dashboard | | API only, no UI | metrics.reuse.api or using … index.cds + metrics.enabled |

The API requires an authenticated user by default. Restrict it to an administrative role with a fully-qualified annotate — do not repeat the using import:

annotate plugin.cds_caching.CachingApiService with @requires: 'CacheAdmin';

See the Security Guide for the production checklist, including cache key isolation for user-filtered data.

Multi-Tenancy (MTX)

cds-caching supports SAP BTP multi-tenant applications using @sap/cds-mtxs. When multitenancy is detected, the plugin automatically:

  • Enables tenant-aware cache keys ({tenant}:{hash})
  • Defers database operations to request-time (avoids startup crashes without tenant context)
  • Partitions in-memory metrics per tenant and persists them via cds.spawn({ tenant }) into that tenant’s HDI
  • Lazily seeds Caches rows on dashboard / CachingApi access (not only READ Caches)

Recommended Setup

{
  "cds": {
    "requires": {
      "multitenancy": true,
      "caching": {
        "impl": "cds-caching",
        "store": "cds",
        "metrics": {
          "enabled": true,
          "persistenceInterval": 60000
        }
      }
    }
  }
}

With store: 'cds', each tenant's cache data lives in its own HDI container — fully isolated by CAP's Service Manager. On HANA, cds build emits CacheStore and Caches / Metrics / KeyMetrics .hdbtable artifacts when metrics or the Caching API are enabled.

Alternatively, use store: 'redis' for shared Redis with automatic tenant-prefixed keys:

{
  "cds": {
    "requires": {
      "multitenancy": true,
      "caching": {
        "impl": "cds-caching",
        "store": "redis",
        "credentials": { "socket": { "host": "localhost", "port": 6379 } }
      }
    }
  }
}

isTenantAware is automatically set to true in MTX mode. Set "isTenantAware": false in keyManagement to explicitly opt out.

For MTX production setup (dashboard, API authorization, store: 'cds'), see the Feature Activation Guide — Multi-tenancy (MTX). To verify against a BTP trial in hybrid mode, see MTX Hybrid Test.

Usage Patterns

Deprecation Notice: cache.run(), cache.send(), cache.wrap(), cache.exec() are deprecated since v1.0. Use cache.rt.run(), cache.rt.send(), cache.rt.wrap(), cache.rt.exec() instead. See Migration Guide.

Read-Through Query Caching

const { result } = await cache.rt.run(
  SELECT.from(BusinessPartners).where({ type: '2' }), 
  db, 
  { ttl: 30000 }
)

Read-Through Remote Service Caching

this.on('READ', BusinessPartners, async (req, next) => {
  const bupa = await cds.connect.to('API_BUSINESS_PARTNER')
  const { result } = await cache.rt.run(req, bupa, { ttl: 30000 })
  return result
})

ApplicationService Caching with prepend

this.prepend(() => {
  this.on('READ', MyEntity, async (req, next) => {
    const cache = await cds.connect.to("caching")
    const { result } = await cache.rt.run(req, next)
    return result
  })
})

Function Caching

// Wrap: create a cached version of a function
const cachedFn = cache.rt.wrap("bp-data", fetchBPData, { ttl: 3600, tags: ['bp'] })
const { result } = await cachedFn("1000001", true)

// Exec: immediate one-off execution with caching
const { result } = await cache.rt.exec("product", fetchProduct, ["1000001"], { ttl: 3600 })

Cache Invalidation

// Time-based (TTL)
await cache.set("key", value, { ttl: 60000 })

// Key-based
await cache.delete("bp:1000001")

// Tag-based
await cache.set("bp:1000001", data, { tags: [{ value: "bp-list" }] })
await cache.set("bp:1000002", data, { tags: [{ value: "bp-list" }] })
await cache.deleteByTag("bp-list")

// Dynamic tags from data
await cache.set("bp-list", bpArray, { 
  tags: [{ data: "businessPartner", prefix: "bp-" }] 
})

Automatic Invalidation on Write

For annotation-based entity caching, use invalidateOnWrite to automatically clear all cached queries for an entity whenever its data changes:

@cache: { ttl: 10000, invalidateOnWrite: true }
entity CachedProducts as projection on db.Products;

This registers after handlers for CREATE, UPDATE, and DELETE that call deleteByTag with an entity-level tag. All cached variants (filtered, sorted, paginated, single-entity) are invalidated at once.

For more usage patterns, error handling details, and TypeScript support, see Programmatic API.

Statistics & Monitoring

To persist metrics to the database, add a metrics block. See the Feature Activation Guide.

{
  "cds": {
    "requires": {
      "caching": {
        "impl": "cds-caching",
        "store": "redis",
        "metrics": {
          "enabled": true,
          "persistenceInterval": 60000
        }
      }
    }
  }
}

You can also enable metrics at runtime:

const cache = await cds.connect.to("caching")
await cache.setMetricsEnabled(true)
await cache.setKeyMetricsEnabled(true)

const stats = await cache.getCurrentMetrics()

To add the monitoring dashboard, use metrics.reuse.dashboard for local reuse, or cds add caching-metrics for BTP. See the Feature Activation Guide and Dashboard Guide.

Cache Dashboard

See the full Metrics Guide →

API Reference

| API | Description | |-----|-------------| | Programmatic API | JavaScript methods for cache operations | | OData API | REST endpoints for monitoring and management |

Related Projects

cds-data provides composable CAP plugins for federation, replication, and materialization. Use cds-caching as the optional peer for cache.strategy: 'response' on @federation.delegate / @federation.replicate views — see Federation Integration.

Contributing

Contributions are welcome! Please submit pull requests to the repository.

Dashboard UI sources live in app/dashboard-src/. From the repo root, use npm run start:dashboard (with cds watch in examples/app) for TypeScript development, and npm run build:dashboard to regenerate the pre-built bundle in app/dashboard/.

License

This project is licensed under the MIT License - see the LICENSE file for details.