cds-caching 0.3.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,37 +3,138 @@
3
3
  ## Overview
4
4
 
5
5
  This plugin for the [SAP Cloud Application Programming Model (CAP)](https://cap.cloud.sap/docs/) provides a caching service to improve performance in CAP applications.
6
+
6
7
  While CAP in general performs well for most use cases, caching can help with:
7
8
  - Slow remote service calls
8
- - Complex calculations
9
- - Heavy queries
10
- - External API integration
9
+ - Complex operations
10
+ - Slow queries
11
+ - Other performance bottle necks
11
12
 
12
- While caching can help with these, it also adds complexity and should be used judiciously.
13
+ While cds-caching can be a big helper, an additional caching layer also adds complexity and should be used judiciously.
13
14
 
14
15
  Please also read the introduction blog post in the SAP Community: [Boosting performance in SAP Cloud Application Programming Model (CAP) applications with cds-caching](https://community.sap.com/t5/technology-blogs-by-members/boosting-performance-in-sap-cloud-application-programming-model-cap/ba-p/14002015).
15
16
 
16
- ### Caching vs. Replication
17
-
18
- It's important to understand the difference between **caching** and **data replication**:
19
-
20
- * **Caching** temporarily stores data to reduce latency and improve response times. It's ideal for read-heavy workloads but does not maintain data integrity or understand data semantics.
21
-
22
- * **Replication** creates full, persistent copies of remote data within your application to ensure availability and enable seamless data sharing across systems. It focuses on resilience rather than performance optimization.
23
-
24
- cds-caching is specifically designed for efficient caching, not data replication.
25
-
26
17
  ### Key Features
27
18
 
28
19
  * **Flexible Key-Value Store** – Store and retrieve data using simple key-based access.
29
20
  * **CachingService** – A cds.Service implementation with an intuitive API for seamless integration into CAP.
30
- * **Event Handling** – Monitor and react to cache events, such as before/after storage and retrieval.
21
+ * **Read-Through Capabilities** – Let the caching service handle the cache set and get operatios for you
31
22
  * **CAP-specific Caching** – Effortlessly cache CQN queries or CAP cds.Requests using code or the @cache annotation.
32
23
  * **TTL Support** – Automatically manage data expiration with configurable time-to-live (TTL) settings.
33
24
  * **Tag Support** – Use dynamic tags for flexible cache invalidation options.
34
25
  * **Pluggable Storage Options** – Choose between in-memory caching, SQLite or Redis.
35
26
  * **Compression** – Compress cached data to save memory using LZ4 or GZIP.
36
- * **Integrated Statistics** – Monitor cache performance with hit rates, latencies, and more.
27
+ * **Integrated Metrics** – Monitor cache performance with hit rates, latencies, and more.
28
+ * **API** – Access basic cache operations and metrics via API
29
+ * **Event Handling** – Monitor and react to cache events, such as before/after storage and retrieval.
30
+
31
+ ### Checkout detailed information on how to use cds-caching
32
+
33
+ > - [Programmatic API](docs/programmatic-api.md)
34
+ > - [Key Management](docs/key-management.md)
35
+ > - [Metrics Guide](docs/metrics-guide.md)
36
+ > - [OData API Reference](docs/odata-api.md)
37
+
38
+ ## 🚨 Breaking Changes: Migrating from cds-caching 0.x
39
+
40
+ > **⚠️ Important:** Version 1.x contains breaking changes. Please review the migration guide below.
41
+
42
+ ### 🔄 API Changes for read-through methods
43
+
44
+ Version 1.x introduces new methods that provide more insights into the read-through caching as they also directly return the genrated cache `key` and some caching `metadata`. The should be preferrably used instead of the old methods.
45
+
46
+ | **Old Method** | **New Method** | **Key Differences** |
47
+ |----------------|----------------|---------------------|
48
+ | `cache.run()` | `cache.rt.run()` | Returns `{result, cacheKey, metadata}` instead of just `result` |
49
+ | `cache.send()` | `cache.rt.send()` | Returns `{result, cacheKey, metadata}` instead of just `result` |
50
+ | `cache.wrap()` | `cache.rt.wrap()` | Returns `{result, cacheKey, metadata}` instead of just `result` |
51
+ | `cache.exec()` | `cache.rt.exec()` | Returns `{result, cacheKey, metadata}` instead of just `result` |
52
+
53
+ ### 🔑 Key Template Changes
54
+
55
+ **Before (0.x):**
56
+ ```javascript
57
+ // Old syntax - object with template property
58
+ await cache.set(query, result, {
59
+ key: { template: "user:{user}:{hash}" }
60
+ })
61
+ ```
62
+
63
+ **After (1.x):**
64
+ ```javascript
65
+ // New syntax - direct string template
66
+ await cache.set(query, result, {
67
+ key: "user:{user}:{hash}"
68
+ })
69
+ ```
70
+
71
+ ### 🌍 Context Awareness Changes
72
+
73
+ **Default Behavior Changed:**
74
+ - **0.x:** Context (user, tenant, locale) was automatically included in some cache keys (ODataRequests)
75
+ - **1.x:** Context is **disabled by default** and can be enabled for **ALL** keys (unless overwritten)
76
+
77
+ **To Enable Context Awareness:**
78
+ ```json
79
+ {
80
+ "cds": {
81
+ "requires": {
82
+ "caching": {
83
+ ...
84
+ "keyManagement": {
85
+ "isUserAware": true, // Include user context in cache keys
86
+ "isTenantAware": true, // Include tenant context in cache keys
87
+ "isLocaleAware": false // Include locale context in cache keys
88
+ }
89
+ }
90
+ }
91
+ }
92
+ }
93
+ ```
94
+
95
+ ### 📚 Migration Examples
96
+
97
+ **Example 1: Basic Caching**
98
+ ```javascript
99
+ // ❌ Old way (deprecated, but will still work)
100
+ const result = await cache.run(query, db)
101
+
102
+ // ✅ New way
103
+ const { result, cacheKey, metadata } = await cache.rt.run(query, db)
104
+ ```
105
+
106
+ **Example 2: Function Wrapping**
107
+ ```javascript
108
+ // ❌ Old way (deprecated, but will still work)
109
+ const cachedFn = cache.wrap("key", expensiveOperation)
110
+ const result = await cachedFn("param1", "param2")
111
+
112
+ // ✅ New way
113
+ const cachedFn = cache.rt.wrap("key", expensiveOperation)
114
+ const { result, cacheKey, metadata } = await cachedFn("param1", "param2")
115
+ ```
116
+
117
+ **Example 3: Custom Key Templates**
118
+ ```javascript
119
+ // ❌ Old way (will not work anymore)
120
+ await cache.set(data, value, {
121
+ key: { template: "user:{user}:{hash}" }
122
+ })
123
+
124
+ // ✅ New way
125
+ await cache.set(data, value, {
126
+ key: "user:{user}:{hash}"
127
+ })
128
+ ```
129
+
130
+ ### 🔍 What's New
131
+
132
+ - **Enhanced Metadata:** All read-through operations now return cache keys and performance metadata
133
+ - **Better Performance:** Context awareness is opt-in, reducing unnecessary key complexity
134
+ - **Improved Debugging:** Access to generated cache keys for troubleshooting
135
+ - **Flexible Configuration:** Global and per-operation key template control
136
+
137
+ For detailed API documentation, see [Programmatic API Reference](docs/programmatic-api.md).
37
138
 
38
139
  ### Installation
39
140
 
@@ -43,56 +144,136 @@ Installing and using cds-caching is straightforward since it's a CAP plugin. Sim
43
144
  npm install cds-caching
44
145
  ```
45
146
 
46
- Next, add a caching service configuration to your package.json. You can even define **multiple caching services**, which is recommended if you need to cache different types of data within your application.
147
+ ### TypeScript Support
47
148
 
48
- ```javascript
149
+ cds-caching includes comprehensive TypeScript definitions. The library is written in JavaScript but provides full TypeScript support for better development experience.
150
+
151
+ #### Basic Usage with TypeScript
152
+
153
+ ```typescript
154
+ import { CachingService, CacheOptions, ReadThroughResult } from 'cds-caching';
155
+
156
+ const cache = await cds.connect.to('caching') as CachingService;
157
+
158
+ // Basic cache operations
159
+ await cache.set('my-key', { data: 'value' }, { ttl: 3600 });
160
+ const value = await cache.get('my-key');
161
+
162
+ // Read-through operations with full type safety
163
+ const { result, cacheKey, metadata } = await cache.rt.send(request, service, {
164
+ ttl: 1800,
165
+ tags: ['user-data']
166
+ });
167
+
168
+ // Function wrapping with type inference
169
+ const cachedFunction = cache.rt.wrap('expensive-operation', async (id: string) => {
170
+ return await this.performExpensiveOperation(id);
171
+ });
172
+
173
+ const { result: operationResult } = await cachedFunction('user-123');
174
+
175
+ ```
176
+
177
+
178
+ ### Configuration
179
+
180
+ The cds-caching plugin supports comprehensive configuration through `package.json`. Here are all available configuration options:
181
+
182
+ #### Basic Service Configuration
183
+
184
+ **Minimal setup** (in-memory cache for development):
185
+
186
+ ```json
49
187
  {
50
188
  "cds": {
51
189
  "requires": {
52
190
  "caching": {
53
191
  "impl": "cds-caching",
54
- "namespace": "my::app::caching"
192
+ "namespace": "caching"
55
193
  },
56
- // Optional: Define a specific caching service for Business Partner API
194
+ // Recommended: Define a specific caching service for different caching requirements
57
195
  "bp-caching": {
58
196
  "impl": "cds-caching",
59
- "namespace": "my::app::bp-caching"
197
+ "namespace": "bp-caching"
60
198
  }
61
199
  }
62
200
  }
63
201
  }
64
202
  ```
65
203
 
66
- ### Advanced Configuration
67
-
68
- For more control, you can specify additional options:
204
+ **Advanced configuration** with all options:
69
205
 
70
- ```javascript
206
+ ```json
71
207
  {
72
208
  "cds": {
73
209
  "requires": {
74
210
  "caching": {
75
211
  "impl": "cds-caching",
76
- "namespace": "my::app::caching",
77
- "store": "in-memory", // "in-memory" or "sqlite" or "redis"
212
+ "namespace": "caching",
213
+ "store": "in-memory", // "in-memory", "sqlite", or "redis"
78
214
  "compression": "lz4", // "lz4" or "gzip"
79
- "credentials": { // if store is redis or sqlite
80
-
81
- // Redis specific
215
+ "credentials": {
216
+ // Redis configuration
82
217
  "host": "localhost",
83
218
  "port": 6379,
84
219
  "password": "optional",
85
- "uri": "redis://..." // Alternative: Redis connection URI
86
-
87
- // SQLite specific
88
- "url": "sqlite://./cache.sqlite"
220
+ "url": "redis://..." // Alternative: Redis connection URI
221
+
222
+ // SQLite configuration
223
+ "url": "sqlite://./cache.sqlite",
89
224
  "table": "cache",
90
225
  "busyTimeout": 10000
226
+ }
227
+ }
228
+ }
229
+ }
230
+ }
231
+ ```
232
+
233
+ #### Read-Through (RT) Key Configuration
234
+
235
+ Configure default key templates for read-through operations:
236
+
237
+ ```json
238
+ {
239
+ "cds": {
240
+ "requires": {
241
+ "caching": {
242
+ ...
243
+ "keyManagement": {
244
+ "isUserAware": true, // Include user context in cache keys
245
+ "isTenantAware": true, // Include tenant context in cache keys
246
+ "isLocaleAware": false // Include locale context in cache keys
247
+ }
248
+ }
249
+ }
250
+ }
251
+ }
252
+ ```
253
+
254
+ **Default behavior** (if not configured): All context elements are disabled by default.
255
+
256
+ #### Environment-Specific Configuration
257
+
258
+ You can override settings for different environments:
259
+
260
+ ```json
261
+ {
262
+ "cds": {
263
+ "requires": {
264
+ "caching": {
265
+ "impl": "cds-caching",
266
+ "store": "redis",
267
+ "[development]": {
268
+ "credentials": {
269
+ "host": "localhost",
270
+ "port": 6379
271
+ }
91
272
  },
92
- "statistics": {
93
- "enabled": true,
94
- "persistenceInterval": 60000, // Optional: Interval for statistics persistence
95
- "maxLatencies": 1000 // Optional: Maximum number of latencies to track
273
+ "[production]": {
274
+ "credentials": {
275
+ "url": "redis://production-redis:6379"
276
+ }
96
277
  }
97
278
  }
98
279
  }
@@ -100,6 +281,19 @@ For more control, you can specify additional options:
100
281
  }
101
282
  ```
102
283
 
284
+ For detailed information on RT key generation and advanced configuration options, see [Key Management](docs/key-management.md) and [Programmatic API Reference](docs/programmatic-api.md).
285
+
286
+ ### Service Definition
287
+
288
+ Add the following cds definition to your data model:
289
+
290
+ ```
291
+ using {plugin.cds_caching.CachingApiService} from 'cds-caching/index.cds';
292
+
293
+ // Don't forget to protect the service, e.g.
294
+ annotate CachingApiService with @requires: 'authenticated-user';
295
+ ```
296
+
103
297
  ### Real-World Usage and Deployment
104
298
 
105
299
 
@@ -200,8 +394,20 @@ resources:
200
394
 
201
395
  ### Usage Patterns
202
396
 
203
- The caching service provides a flexible API for caching data in CAP applications. Here are the key usage patterns:
204
- #### 1. Low-Level Key-Value API
397
+ > ⚠️ **Deprecation Notice**: The following methods are deprecated since version 1.0 and will be removed in a future version:
398
+ > - `cache.run()` - use `cache.rt.run()` instead
399
+ > - `cache.exec()` - use `cache.rt.exec()` instead
400
+ > - `cache.wrap()` - use `cache.rt.wrap()` instead
401
+ > - `cache.send()` - use `cache.rt.send()` instead
402
+ >
403
+ > The `rt.xxx` methods provide enhanced functionality including:
404
+ > - **Read-through metadata**: Information about cache hits/misses and latency
405
+ > - **Consistent return format**: All methods return `{ result, cacheKey, metadata }` by default
406
+ >
407
+ > **Migration**: Simply replace `cache.method()` with `cache.rt.method()` and access the result via `.result` property if needed.
408
+
409
+ The caching service provides a flexible API for caching data in CAP applications ([full API](docs/programmatic-api.md)). Here are the key usage patterns:
410
+ #### 1. Low-Level Key-Value API for Read-Aside Caching
205
411
 
206
412
  The most basic way to use cds-caching is through its key-value API:
207
413
 
@@ -210,16 +416,16 @@ The most basic way to use cds-caching is through its key-value API:
210
416
  const cache = await cds.connect.to("caching")
211
417
 
212
418
  // Store a value (can be any object)
213
- await cache.set("key", "value")
419
+ await cache.set("bp:1000001", businessPartnerData)
214
420
 
215
421
  // Retrieve the value
216
- await cache.get("key") // => value
422
+ await cache.get("bp:1000001") // => businessPartnerData
217
423
 
218
424
  // Check if the key exists
219
- await cache.has("key") // => true/false
425
+ await cache.has("bp:1000001") // => true/false
220
426
 
221
427
  // Delete the key
222
- await cache.delete("key")
428
+ await cache.delete("bp:1000001")
223
429
 
224
430
  // Clear the whole cache
225
431
  await cache.clear()
@@ -231,7 +437,7 @@ For more advanced CAP integration, cache CAP's CQN queries directly. By passing
231
437
 
232
438
  ```javascript
233
439
  // Create and execute a CQN query
234
- const query = SELECT.from(Foo)
440
+ const query = SELECT.from(BusinessPartners).where({ businessPartnerType: '2' })
235
441
  const result = await db.run(query)
236
442
 
237
443
  // Cache the result
@@ -240,20 +446,26 @@ await cache.set(query, result)
240
446
  // Retrieve from cache using the same query
241
447
  const cachedResult = await cache.get(query)
242
448
  ```
243
- Handling the cache manually via read-aside pattern is possible, but the caching service provides a more convenient way to cache and retrieve CQN queries. By using the `run` method, the caching service will transparently cache the result of the query and return the cached result if available for all further requests.
449
+ Handling the cache manually via read-aside pattern is possible, but the caching service provides a more convenient way to cache and retrieve CQN queries. By using the `rt.run` method, the caching service will transparently cache the result of the query and return the cached result if available for all further requests.
244
450
 
245
451
  ```javascript
246
- const query = SELECT.from(Foo)
452
+ const query = SELECT.from(BusinessPartners).where({ businessPartnerType: '2' })
247
453
 
248
454
  // Runs the query internally and caches the result
249
- const result = await cache.run(query, db)
455
+ const { result } = await cache.rt.run(query, db)
250
456
  ```
251
457
 
252
- This will transparently cache the result of the query and return the cached result if available for all further requests.
458
+
459
+ Because the cache key has been dynamically created at runtime, it will also be returned:
460
+
461
+ ```javascript
462
+ // Access the cacheKey for later usage
463
+ const { result, cacheKey } = await cache.rt.run(query, db)
464
+ ```
253
465
 
254
466
  #### 3. RemoteService Request-Level Caching
255
467
 
256
- Cache entire CAP requests with context awareness (e.g. user, tenant, locale, etc.), which is useful for caching slow remote service calls or even application services. The caching service will automatically generate a key for the request based on the request object and the current user, tenant and locale.
468
+ Cache entire CAP requests with context awareness (e.g. user, tenant, locale, etc.), which is useful for caching slow remote service calls or even application services. The caching service will automatically generate a key for the request based on the request object and the current user, tenant and locale (if not configured otherwise).
257
469
 
258
470
  ```javascript
259
471
  // Cache the requests to an exposed external entity
@@ -262,18 +474,19 @@ this.on('READ', BusinessPartners, async (req, next) => {
262
474
  let value = await cache.get(req)
263
475
  if(!value) {
264
476
  value = await bupa.run(req)
265
- await cache.set(req, value, { ttl: 3600 })
477
+ await cache.set(req, value, { ttl: 30000 })
266
478
  }
267
479
  return value
268
480
  })
269
481
  ```
270
482
 
271
- Alternatively use read-through caching via the `run` method to let the caching service handle the caching transparently:
483
+ Alternatively use read-through caching via the `rt.run` method to let the caching service handle the caching transparently:
272
484
 
273
485
  ```javascript
274
486
  this.on('READ', BusinessPartners, async (req, next) => {
275
487
  const bupa = await cds.connect.to('API_BUSINESS_PARTNER')
276
- return await cache.run(req, bupa)
488
+ const { result } = await cache.rt.run(req, bupa)
489
+ return result
277
490
  })
278
491
 
279
492
  ```
@@ -283,7 +496,7 @@ This will transparently cache the result of the request and return the cached re
283
496
  ### 4. ApplicationService Request-Level Caching
284
497
 
285
498
 
286
- > Caching an entire entity should be used with caution, as it will cache all permutations of requests ($filter, $expand, $orderby, etc.) on the entity, which will lead to a huge number of cache entries. Use this only for entities where you can guarantee a low number of different queries.
499
+ > Caching an entire entity should be used with caution, as it will cache all permutations of requests ($select, $filter, $expand, $orderby, etc.) on the entity, which may lead to a huge number of cache entries. Use this only for entities where you can guarantee a low number of different queries.
287
500
 
288
501
 
289
502
  But not only external services can be cached, it's also possible to cache requests against an ApplicationService.
@@ -299,7 +512,8 @@ class MyService extends cds.ApplicationService {
299
512
  const { MyEntity } = this.entities;
300
513
  this.on('READ', MyEntity, async (req, next) => {
301
514
  const cache = cds.connect.to("caching");
302
- return cache.run(req, next);
515
+ const { result } = await cache.rt.run(req, next)
516
+ return result;
303
517
  });
304
518
  });
305
519
  return super.init()
@@ -314,7 +528,7 @@ Alternatively to doing this via code, you can use annotations to enable caching
314
528
  ```
315
529
  service MyService {
316
530
  @cache: {
317
- ttl: 3600
531
+ ttl: 10000 // 10 seconds
318
532
  }
319
533
  entity BusinessPartners as projection on BusinessPartner {
320
534
  // ... entity definition
@@ -322,7 +536,7 @@ service MyService {
322
536
 
323
537
 
324
538
  @cache: {
325
- ttl: 1800,
539
+ ttl: 100000, // 10 seconds
326
540
  tags: [{
327
541
  template: 'user-{user}'
328
542
  }]
@@ -337,35 +551,55 @@ While not directly related to CAP functionality, the caching service provides tw
337
551
 
338
552
  ```javascript
339
553
  // Using wrap() to create a cached version of a function
340
- const expensiveOperation = async (value) => {
341
- // ... some expensive computation
342
- return result
554
+ const fetchBusinessPartnerData = async (businessPartnerId, includeAddresses) => {
555
+ // ... some expensive computation to fetch BP data
556
+ return businessPartnerData
343
557
  }
344
558
 
345
- // Creates a cached version of the function
346
- const cachedOperation = cache.wrap("key", expensiveOperation, {
559
+ // Creates a cached version of the function.
560
+ const cachedBpOperation = cache.rt.wrap("bp-data", fetchBusinessPartnerData, {
347
561
  ttl: 3600,
348
- tags: ['computation']
562
+ tags: ['business-partner']
349
563
  })
350
564
 
351
565
  // Each call checks cache first, only executes if cache miss
352
- const result = await cachedOperation("input")
566
+ const result = await cachedBpOperation("1000001", true)
353
567
 
354
568
  // Using exec() for immediate execution with caching
355
- const result = await cache.exec("key", async () => {
356
- // ... some expensive computation
357
- return result
358
- }, {
569
+ const result = await cache.rt.exec("product-data", async (productId) => {
570
+ // ... some expensive computation to fetch product data
571
+ return productData
572
+ }, ["1000001"], {
359
573
  ttl: 3600,
360
- tags: ['computation']
574
+ tags: ['product']
575
+ })
576
+ ```
577
+
578
+ The key differences between `rt.wrap()` and `rt.exec()`:
579
+ - `rt.wrap()` returns a new function that includes caching logic
580
+ - `rt.exec()` immediately executes the function and caches the result
581
+ - Use `rt.wrap()` when you need to reuse the cached function multiple times
582
+ - Use `rt.exec()` for one-off executions with caching
583
+
584
+ #### Dynamic Key Generation
585
+
586
+ All `rt.xxx` methods automatically generate dynamic cache keys based on function arguments (`wrap`, `exec`) and request/query parameters. This ensures that different function calls with different arguments are cached separately.
587
+
588
+ ```javascript
589
+ // Different arguments = different cache keys
590
+ const result1 = await cachedBpOperation("1000001", true) // Cache key: "bp-data:1000001:true"
591
+ const result2 = await cachedBpOperation("1000002", false) // Cache key: "bp-data:1000002:false"
592
+ ```
593
+
594
+ You can override this behavior by providing a custom key template:
595
+
596
+ ```javascript
597
+ const cachedOperation = cache.rt.wrap("bp-profile", fetchBusinessPartnerData, {
598
+ key: "profile:{args[0]}:{args[1]}"
361
599
  })
362
600
  ```
363
601
 
364
- The key differences between `wrap()` and `exec()`:
365
- - `wrap()` returns a new function that includes caching logic
366
- - `exec()` immediately executes the function and caches the result
367
- - Use `wrap()` when you need to reuse the cached function multiple times
368
- - Use `exec()` for one-off executions with caching
602
+ For detailed information on how read-through keys are generated and configured, see [Key Management](docs/key-management.md).
369
603
 
370
604
  ### Cache Invalidation Strategies
371
605
 
@@ -383,19 +617,19 @@ The TTL can be specified for individually through all cache methods (e.g. `set`,
383
617
  await cache.set("key", "value", { ttl: 60000 })
384
618
 
385
619
  // Run with 30 seconds TTL
386
- const result = await cache.run(query, db, { ttl: 30000 })
620
+ const { result } = await cache.rt.run(query, db, { ttl: 30000 })
387
621
 
388
622
  // Send with 10 seconds TTL
389
- const result = await cache.send(request, service, { ttl: 10000 })
623
+ const { result } = await cache.rt.send(request, service, { ttl: 10000 })
390
624
 
391
625
  // Wrap with 10 seconds TTL
392
- const cachedOperation = cache.wrap("key", expensiveOperation, { ttl: 10000 })
626
+ const cachedOperation = cache.rt.wrap("key", expensiveOperation, { ttl: 10000 })
393
627
 
394
628
  // Exec with 10 seconds TTL
395
- const result = await cache.exec("key", async () => {
629
+ const { result } = await cache.rt.exec("key", async () => {
396
630
  // ... some expensive computation
397
631
  return result
398
- }, {
632
+ }, [] {
399
633
  ttl: 10000
400
634
  })
401
635
  ```
@@ -408,43 +642,42 @@ Key-based invalidation is a way to invalidate cache entries based on a specific
408
642
  await cache.delete("key")
409
643
  ```
410
644
 
411
- Keys are critical for cache invalidation. To allow custom key management, you can override the auto-generated key. This option is available for all essential methods (e.g cache.set, cache.run, cache.send, cache.createKey) and for the annotations.
645
+ Keys are critical for cache invalidation. To allow custom key management, you can override the auto-generated key. This option is available for all essential methods (e.g cache.set, cache.rt.run, cache.rt.send, cache.createKey) and for the annotations.
646
+
647
+ **Read-Through (RT) Methods**: All `rt.xxx` methods automatically generate dynamic cache keys and return them in the response. The generated keys include configurable context (user, tenant, locale) and a content hash. For detailed information on RT key generation, see [Key Management](docs/key-management.md).
412
648
 
413
649
  ```javascript
414
650
  // No key override given, string will just be used as keys
415
- await cache.set('key', 'value') // key: key
651
+ await cache.set('bp:1000001', businessPartnerData) // key: bp:1000001
416
652
 
417
653
  // No key override given, objects will be smartly hashed
418
- await cache.set(SELECT.from(Foo)) // key: bd3f3690d3e96a569bd89d9e207a89af
654
+ await cache.set(SELECT.from(BusinessPartners)) // key: bd3f3690d3e96a569bd89d9e207a89af
419
655
 
420
656
  // Automatically build the key for retrieval/deletion
421
- cache.createKey(SELECT.from(Foo)) // key: bd3f3690d3e96a569bd89d9e207a89af
657
+ cache.createKey(SELECT.from(BusinessPartners)) // key: bd3f3690d3e96a569bd89d9e207a89af
422
658
 
423
659
  // Override and use your own key based on a fixed value
424
- await cache.set(SELECT.from(Foo, 1), { key: { value: "foo:1" } })
660
+ await cache.set(SELECT.from(BusinessPartners, 1000001), { key: "bp:1000001" })
425
661
 
426
- // Override and only for requests, use request context information
427
- await cache.run(req, remoteService, { key: { template: "mykey:{tenant}:{user}:{locale}:{hash}" } })
662
+ // RT methods return the generated cache key
663
+ const { result, cacheKey } = await cache.rt.run(query, db)
664
+ console.log('Generated key:', cacheKey) // e.g., "tenant-acme:user-john:locale-en:hash-abc123"
428
665
 
429
- // This requests will be cached for all users and for each locale
430
- await cache.set(req, remoteService, { key: { template: "mykey:{user}:{locale}:{hash}" } })
431
- ```
666
+ // Override RT key template for requests
667
+ await cache.rt.run(req, remoteService, { key: "mykey:{tenant}:{user}:{locale}:{hash}" })
432
668
 
433
- Overriding keys support the following configuration options:
434
- - `value` generates a static value
435
- - `prefix` – will add this piece at the beginning
436
- - `suffix` - will ad this piece at the end
437
- - `template` - will set a value filled with placeholders, available placeholders are (only relevant for cds.Requests):
438
- - `{user}`: The current user
439
- - `{tenant}`: The current tenant
440
- - `{locale}`: The current locale
441
- - `{hash}`: The hash of the request query/params/data/path/etc.
669
+ // This requests will be cached for all users and for each locale
670
+ await cache.rt.run(req, remoteService, { key: "mykey:{user}:{locale}:{hash}" })
442
671
 
443
- With well-structured keys, invalidating cache entries becomes a lot easier. However, for more complex scenarios tags provide an even more effective solution, as tags can automatically be created based on the cached data.
672
+ // Function wrapping with custom key template
673
+ const cachedFunction = cache.rt.wrap("user-data", expensiveOperation, {
674
+ key: "user:{user}:{args[0]}"
675
+ })
676
+ ```
444
677
 
445
678
  #### 3. Tag-Based
446
679
 
447
- Tags are a way to invalidate cache entries based on a specific tag. Tags need to be provided explicitly when storing a value in the cache and are supported for all cache methods (e.g. `set`, `run`, `send`, `wrap`, `exec`).
680
+ Tags are a way to invalidate cache entries based on a specific tag. Tags need to be provided explicitly when storing a value in the cache and are supported for all cache methods (e.g. `set`, `rt.run`, `rt.send`, `rt.wrap`, `rt.exec`).
448
681
  Tags can be provided as an array of strings or as an array of objects with the following properties:
449
682
  - `value`: The value to use for the tag.
450
683
  - `data`: A field from the value to use for the tag. This is working for objects and arrays of objects.
@@ -461,22 +694,22 @@ Templates support the following properties:
461
694
 
462
695
  ```javascript
463
696
  // Store with static tag
464
- await cache.set("key", "value", {
465
- tags: [{ value: "user-123" }]
697
+ await cache.set("bp:1000001", businessPartnerData, {
698
+ tags: [{ value: "bp-1000001" }]
466
699
  })
467
700
 
468
701
  // Store with template tag (will generate a tag like "tenant-global-user-anonymous")
469
- await cache.set("key", "value", {
702
+ await cache.set("bp:1000001", businessPartnerData, {
470
703
  tags: [{ template: "tenant-{tenant}-user-{user}" }]
471
704
  })
472
705
 
473
706
  // Store with data-based tag
474
- await cache.set("key", { id: 123, name: "Product" }, {
475
- tags: [{ data: "id", prefix: "product-" }]
707
+ await cache.set("product:1000001", { productId: 1000001, name: "Laptop Computer" }, {
708
+ tags: [{ data: "productId", prefix: "product-" }]
476
709
  })
477
710
 
478
711
  // Invalidate by tag
479
- await cache.deleteByTag('user-123')
712
+ await cache.deleteByTag('bp-1000001')
480
713
  ```
481
714
  This is really useful for invalidating cache entries based on a specific attribute or context.
482
715
 
@@ -488,17 +721,17 @@ Dynamic tags using data `data` property are a way to invalidate cache entries ba
488
721
 
489
722
  const businessPartners = [
490
723
  {
491
- businessPartner: 1,
492
- name: 'John Doe'
724
+ businessPartner: 1000001,
725
+ name: 'Acme Corporation'
493
726
  },
494
727
  {
495
- businessPartner: 2,
496
- name: 'Jane Doe'
728
+ businessPartner: 1000002,
729
+ name: 'Tech Solutions Ltd'
497
730
  }
498
731
  ]
499
732
 
500
733
  // Store with dynamic tags
501
- await cache.set("key", businessPartners, {
734
+ await cache.set("bp-list", businessPartners, {
502
735
  tags: [
503
736
  { data: 'businessPartner', prefix: 'bp-' },
504
737
  { value: "businessPartner" }
@@ -506,17 +739,17 @@ await cache.set("key", businessPartners, {
506
739
  })
507
740
 
508
741
  // Introspect the tags
509
- const tags = await cache.tags("key") // => ["bp-1", "bp-2", "businessPartner"]
742
+ const tags = await cache.tags("bp-list") // => ["bp-1000001", "bp-1000002", "businessPartner"]
510
743
 
511
744
  // Invalidate by tag
512
- await cache.deleteByTag('bp-1')
513
- await cache.deleteByTag('bp-2')
745
+ await cache.deleteByTag('bp-1000001')
746
+ await cache.deleteByTag('bp-1000002')
514
747
  ```
515
748
 
516
- This is really usefull for caching results with multiple rows where you can't predict the tags beforehand or when you want to invalidate cache entries based on the data itself. This is also possible for the `run` method.
749
+ This is really usefull for caching results with multiple rows where you can't predict the tags beforehand or when you want to invalidate cache entries based on the data itself. This is also possible for the `rt.run` method.
517
750
 
518
751
  ```javascript
519
- const result = await cache.run(query, db, {
752
+ const result = await cache.rt.run(query, db, {
520
753
  tags: [{ data: 'businessPartner', prefix: 'bp-' }]
521
754
  })
522
755
  ```
@@ -582,222 +815,314 @@ Instead of caching entire OData service responses, focus on:
582
815
  3. **Multi-Tenant**: Use appropriate namespacing and key strategies
583
816
  4. **Redis Setup**: Ensure proper configuration for production use
584
817
 
585
- ## Full API
586
-
587
- ### `cache.createKey(key: any)` : `string`
588
-
589
- Creates a key from a string or an object. This method is used internally when passing keys to the cache methods, so you don't need to call it directly other then to retrieve the dynamic generated key for a given object.
590
-
591
- #### `key: any`
592
-
593
- The key to create the key from. The key can be a string or an object. If an object is used, it will be hashed to a string key using MD5. cds.Requests are handled explicitly as the dynamic generated key includes the user, tenant and locale and query hash.
594
-
595
- #### Returns
596
-
597
- A string key.
598
-
599
- ---
600
-
601
- ### `await cache.set(key: any, value: any[, options: object])`
602
-
603
- Sets a value in the cache.
604
-
605
- #### `key: any`
606
-
607
- The key to store the value under. The key handling is the same as for the `≈` method.
608
-
609
- #### `value: any`
610
-
611
- The value to store in the cache. The value will be serialized to a string using `JSON.stringify` (unless the value is already a string).
612
-
613
- #### `options: object`
614
-
615
- Object literal containing cache options.
616
-
617
- The following properties are accepted:
618
-
619
- | Property | Description | Example |
620
- | ------------- | ------------- | ----------
621
- | ttl | Time-to-live in milliseconds. | `1000`
622
- | key | Key override for the cache for full control over the key management (see chapter Cache Invalidation Strategies) | `{template: 'user-{user}', value: '123'}`
623
- | tags | Array of tags to associate with the value. Tags can be dynamic based on the stored cache data (see chapter Cache Invalidation Strategies) | `[{template: 'user-{user}', value: '123'}]`
624
-
625
- ---
626
-
627
- ### `await cache.get(key: any)`
628
-
629
- Gets a value from the cache.
630
-
631
- #### `key: any`
632
-
633
- The key to retrieve the value from. The key handling is the same as for the `createKey` method.
634
-
635
- #### Returns
636
-
637
- The deserialized value from the cache or `undefined` if the value does not exist.
638
-
639
- ---
640
-
641
- ### `await cache.has(key: any)`
818
+ ## Enhanced Statistics & Monitoring
642
819
 
643
- Checks if a value exists in the cache.
820
+ The plugin now includes comprehensive statistics and monitoring capabilities that provide deep insights into cache performance and help optimize cache usage.
644
821
 
645
- #### `key: any`
822
+ [See the full Metrics Guide →](docs/metrics-guide.md)
646
823
 
647
- The key to check for existence. The key handling is the same as for the `createKey` method.
648
-
649
- #### Returns
650
-
651
- `true` if the value exists in the cache, `false` otherwise.
652
-
653
- ---
654
-
655
- ### `await cache.delete(key: any)`
656
-
657
- Deletes a value from the cache.
658
-
659
- #### `key: any`
660
-
661
- The key to delete the value from. The key handling is the same as for the `createKey` method.
662
-
663
- ---
664
-
665
- ### `await cache.clear()`
666
-
667
- Clears the whole cache.
668
-
669
- ---
670
-
671
- ### `await cache.deleteByTag(tag: string)`
672
-
673
- Deletes all values from the cache that are associated with the given tag.
674
-
675
- #### `tag: string`
676
-
677
- The tag to delete the values from.
678
-
679
- ---
680
-
681
- ### `await cache.run(query: cds.CQN , service: cds.Service)`
682
-
683
- Runs a query against the provided service and caches the result for all further requests. This method is useful for read-through caching. (see Usage Patterns and [CAP docs](https://cap.cloud.sap/docs/node.js/core-services#srv-run-query) for more information)
684
-
685
- #### `object: cds.CQN`
686
-
687
- The CQN query to run.
688
-
689
- #### `service: cds.Service`
690
-
691
- The service to run the query on.
692
-
693
- #### Returns
694
-
695
- The result of the query, either from the cache or the service.
696
-
697
- ---
824
+ ### Key Features
698
825
 
699
- ### `await cache.send(request: cds.Request, service: cds.Service)`
826
+ - **Real-time Metrics**: Monitor cache performance with detailed hit rates, latencies, and throughput
827
+ - **Key-level Tracking**: Track performance metrics for individual cache keys
828
+ - **Historical Data**: Store and analyze metrics over time (hourly/daily periods)
829
+ - **Performance Analytics**: Calculate cache efficiency, error rates, and response times
830
+ - **Runtime Configuration**: Enable/disable metrics at runtime without restart
831
+ - **API Access**: Access metrics programmatically or via OData service
700
832
 
701
- Sends a request to a cds.Service and caches the result. In contrast to the `run` method, this method is useful for caching full cds.Requests.
833
+ ### Metrics Overview
702
834
 
703
- #### `request: cds.Request`
835
+ cds-caching provides two types of metrics:
704
836
 
705
- The request to send.
837
+ #### 1. General Cache Metrics
838
+ Track overall cache performance including:
839
+ - **Hit/Miss Statistics**: Total hits, misses, and hit ratios
840
+ - **Latency Metrics**: Average, min, max, and percentile latencies for hits and misses
841
+ - **Performance Metrics**: Throughput (requests/second), error rates, cache efficiency
842
+ - **Memory Usage**: Current memory consumption and item count
843
+ - **Native Operations**: Counts of direct cache operations (set, get, delete, etc.)
706
844
 
707
- #### `service: cds.Service `
845
+ #### 2. Key-level Metrics
846
+ Track performance for individual cache keys including:
847
+ - **Key-specific Statistics**: Hits, misses, and hit ratios per key
848
+ - **Context Information**: Data type, service name, entity name, operation type
849
+ - **Enhanced Metadata**: Query text, request info, function names, user/tenant context
850
+ - **Performance Tracking**: Latency and throughput metrics per key
708
851
 
709
- The service to send the request to.
852
+ ### Enabling Metrics
710
853
 
711
- #### Returns
854
+ Metrics are disabled by default to minimize performance impact. They can only be enabled/disabled via the programmatic API or OData API at runtime, not through package.json configuration.
712
855
 
713
- The result of the request, either from the cache or the service.
856
+ To enable metrics programmatically:
714
857
 
715
- ---
858
+ ```javascript
859
+ // Connect to the caching service
860
+ const cache = await cds.connect.to("caching")
716
861
 
717
- ### `await cache.wrap(key: any, fn: async function, options: object)`
862
+ // Enable metrics at runtime
863
+ await cache.setMetricsEnabled(true)
864
+ await cache.setKeyMetricsEnabled(true)
865
+ ```
718
866
 
719
- Wraps a function in a cache.
867
+ Or via OData API:
720
868
 
721
- #### `key: any`
869
+ ```http
870
+ ### Enable general metrics
871
+ POST http://localhost:4004/odata/v4/caching-api/Caches('caching')/setMetricsEnabled
872
+ Content-Type: application/json
722
873
 
723
- The key to store the cached function under. The key handling is the same as for the `createKey` method.
874
+ {
875
+ "enabled": true
876
+ }
724
877
 
725
- #### `fn: async function`
878
+ ### Enable key-level metrics
879
+ POST http://localhost:4004/odata/v4/caching-api/Caches('caching')/setKeyMetricsEnabled
880
+ Content-Type: application/json
726
881
 
727
- The async function to wrap in a cache.
882
+ {
883
+ "enabled": true
884
+ }
885
+ ```
728
886
 
729
- #### `options: object`
887
+ ### Accessing Metrics via Caching Service
730
888
 
731
- The options to use for the cache.
889
+ The caching service provides comprehensive metrics collection and persistence capabilities. Metrics are automatically collected during cache operations and can be accessed both in real-time and from historical data.
732
890
 
733
- #### Returns
891
+ #### Metrics Persistence
734
892
 
735
- A cached version of the function. The cached function will check the cache first and only execute the function if the cache miss.
893
+ **Transient Metrics**: Current statistics are kept in memory and provide real-time insights into cache performance:
894
+ - Hit/miss ratios
895
+ - Current latency statistics
896
+ - Active cache entries
897
+ - Key-level performance data
736
898
 
737
- ---
899
+ **Persisted Metrics**: Historical data is automatically stored in the database for long-term analysis:
900
+ - Hourly aggregated statistics
901
+ - Key-level metrics over time
902
+ - Performance trends and patterns
903
+ - Cache efficiency analysis
738
904
 
739
- ### `await cache.exec(key: any, fn: async function, options: object)`
905
+ #### Current Statistics
740
906
 
741
- Executes a function and caches the result. This method is useful for one-off executions with caching.
907
+ ```javascript
908
+ // Connect to the caching service
909
+ const cache = await cds.connect.to("caching")
742
910
 
743
- #### `key: any`
911
+ // Get current statistics
912
+ const stats = await cache.getCurrentStats()
913
+ console.log('Hit ratio:', stats.hitRatio)
914
+ console.log('Average hit latency:', stats.avgHitLatency)
915
+ console.log('Throughput:', stats.throughput)
916
+
917
+ // Get current key metrics
918
+ const keyMetrics = await cache.getCurrentKeyMetrics()
919
+ for (const [key, metrics] of keyMetrics) {
920
+ console.log(`Key ${key}:`, {
921
+ hits: metrics.hits,
922
+ misses: metrics.misses,
923
+ hitRatio: metrics.hitRatio,
924
+ avgHitLatency: metrics.avgHitLatency
925
+ })
926
+ }
927
+ ```
744
928
 
745
- The key to store the cached function under. The key handling is the same as for the `createKey` method.
929
+ #### Historical Metrics
746
930
 
747
- #### `fn: async function`
931
+ ```javascript
932
+ // Get metrics for a specific time period
933
+ const from = new Date('2024-01-01')
934
+ const to = new Date('2024-01-31')
935
+ const historicalStats = await cache.getMetrics(from, to)
748
936
 
749
- The async function to execute.
937
+ // Get key-specific metrics
938
+ const keyStats = await cache.getKeyMetrics('my-cache-key', from, to)
939
+ ```
750
940
 
751
- #### `options: object`
941
+ #### Runtime Configuration
752
942
 
753
- The options to use for the cache.
943
+ ```javascript
944
+ // Enable/disable metrics at runtime
945
+ await cache.setMetricsEnabled(true)
946
+ await cache.setKeyMetricsEnabled(true)
947
+
948
+ // Get current configuration
949
+ const config = await cache.getRuntimeConfiguration()
950
+ console.log('Metrics enabled:', config.metricsEnabled)
951
+ console.log('Key metrics enabled:', config.keyMetricsEnabled)
952
+
953
+ // Clear metrics
954
+ await cache.clearMetrics()
955
+ await cache.clearKeyMetrics()
956
+ ```
754
957
 
755
- #### Returns
958
+ ### Metrics Data Structure
756
959
 
757
- The result of the function.
960
+ #### General Cache Statistics (Metrics Entity)
758
961
 
759
- ---
962
+ ```javascript
963
+ {
964
+ // Entity identification
965
+ ID: "daily:2024-01-15", // Unique identifier (period:date)
966
+ cache: "caching", // Cache name
967
+ timestamp: "2024-01-15T10:30:00Z", // When metrics were recorded
968
+ period: "daily", // Aggregation period (hourly/daily/monthly)
969
+
970
+ // Read-through metrics
971
+ hits: 1500, // Number of cache hits
972
+ misses: 300, // Number of cache misses
973
+ errors: 5, // Number of errors
974
+ totalRequests: 1800, // Total read-through requests
975
+
976
+ // Read-through latency metrics (milliseconds)
977
+ avgHitLatency: 2.5, // Average hit latency
978
+ minHitLatency: 0.1, // Minimum hit latency
979
+ maxHitLatency: 15.2, // Maximum hit latency
980
+ avgMissLatency: 45.8, // Average miss latency
981
+ minMissLatency: 12.3, // Minimum miss latency
982
+ maxMissLatency: 120.5, // Maximum miss latency
983
+ avgReadThroughLatency: 8.9, // Average read-through latency
984
+
985
+ // Read-through performance metrics
986
+ hitRatio: 0.833, // Hit ratio as percentage (83.3%)
987
+ throughput: 25.5, // Requests per second
988
+ errorRate: 0.003, // Error rate as percentage (0.3%)
989
+ cacheEfficiency: 18.3, // Miss latency / hit latency ratio
990
+
991
+ // Native operation metrics
992
+ nativeSets: 200, // Number of direct set operations
993
+ nativeGets: 800, // Number of direct get operations
994
+ nativeDeletes: 50, // Number of direct delete operations
995
+ nativeClears: 2, // Number of clear operations
996
+ nativeDeleteByTags: 10, // Number of delete-by-tag operations
997
+ nativeErrors: 1, // Number of native operation errors
998
+ totalNativeOperations: 1063, // Total native operations
999
+ nativeThroughput: 17.7, // Native operations per second
1000
+ nativeErrorRate: 0.001, // Native operation error rate (0.1%)
1001
+
1002
+ // System metrics
1003
+ memoryUsage: 52428800, // Memory usage in bytes
1004
+ itemCount: 150, // Number of items in cache
1005
+ uptimeMs: 7200000 // Cache uptime in milliseconds
1006
+ }
1007
+ ```
760
1008
 
761
- ### `await cache.iterator() : AsyncIterator<{ key: string, value: { value: any, tags: string[], timestamp: number } }>`
1009
+ #### Key-level Metrics (KeyMetrics Entity)
762
1010
 
763
- Returns an iterator over all cache entries.
1011
+ ```javascript
1012
+ {
1013
+ // Entity identification
1014
+ ID: "key:user-preferences:123", // Unique identifier
1015
+ cache: "caching", // Cache name
1016
+ keyName: "user-preferences:123", // Cache key name
1017
+ lastAccess: "2024-01-15T10:30:00Z", // Last access time
1018
+ period: "current", // Period type (current/hourly/daily)
1019
+ operationType: "read_through", // Operation category (read_through/native/mixed)
1020
+
1021
+ // Read-through metrics
1022
+ hits: 45, // Number of hits for this key
1023
+ misses: 5, // Number of misses for this key
1024
+ errors: 0, // Number of errors for this key
1025
+ totalRequests: 50, // Total requests for this key
1026
+ hitRatio: 0.9, // Hit ratio for this key (90%)
1027
+ cacheEfficiency: 21.2, // Cache efficiency for this key
1028
+
1029
+ // Read-through latency metrics (milliseconds)
1030
+ avgHitLatency: 1.2, // Average hit latency for this key
1031
+ minHitLatency: 0.5, // Minimum hit latency for this key
1032
+ maxHitLatency: 3.1, // Maximum hit latency for this key
1033
+ avgMissLatency: 25.4, // Average miss latency for this key
1034
+ minMissLatency: 15.2, // Minimum miss latency for this key
1035
+ maxMissLatency: 45.8, // Maximum miss latency for this key
1036
+ avgReadThroughLatency: 3.8, // Average read-through latency for this key
1037
+
1038
+ // Read-through performance metrics
1039
+ throughput: 2.5, // Requests per second for this key
1040
+ errorRate: 0.0, // Error rate for this key (0%)
1041
+
1042
+ // Native operation metrics for this key
1043
+ nativeHits: 10, // Native hits for this key
1044
+ nativeMisses: 2, // Native misses for this key
1045
+ nativeSets: 5, // Native sets for this key
1046
+ nativeDeletes: 1, // Native deletes for this key
1047
+ nativeClears: 0, // Native clears for this key
1048
+ nativeDeleteByTags: 0, // Native delete-by-tags for this key
1049
+ nativeErrors: 0, // Native errors for this key
1050
+ totalNativeOperations: 18, // Total native operations for this key
1051
+ nativeThroughput: 0.5, // Native operations per second for this key
1052
+ nativeErrorRate: 0.0, // Native error rate for this key
1053
+
1054
+ // Context and metadata
1055
+ dataType: "request", // Type of data (query/request/function/custom)
1056
+ operation: "READ", // Cache operation type
1057
+ metadata: '{"ttl":3600}', // JSON string with additional metadata
1058
+ context: '{"user":"john.doe","tenant":"acme"}', // JSON string with context
1059
+ query: "SELECT * FROM UserPreferences WHERE userId = '123'", // CQL query text
1060
+ subject: '{"entity":"UserPreferences"}', // JSON string with subject info
1061
+ target: "UserService", // Target service name
1062
+ tenant: "acme", // Tenant information
1063
+ user: "john.doe", // User information
1064
+ locale: "en-US", // Locale information
1065
+ cacheOptions: '{"ttl":3600}', // JSON string with cache options
1066
+ timestamp: "2024-01-15T09:00:00Z" // When this key was first accessed
1067
+ }
1068
+ ```
764
1069
 
765
- #### Returns
1070
+ ### Best Practices for Metrics
766
1071
 
767
- An iterator over all cache entries.
1072
+ 1. **Enable Selectively**: Only enable metrics when needed for monitoring or debugging
1073
+ 2. **Monitor Memory Usage**: Key metrics can consume significant memory for large caches
1074
+ 3. **Set Appropriate Intervals**: Balance persistence frequency with performance impact
1075
+ 4. **Use Historical Data**: Analyze trends over time to optimize cache configuration
1076
+ 5. **Monitor Error Rates**: High error rates may indicate configuration issues
1077
+ 6. **Track Cache Efficiency**: Aim for high cache efficiency (miss latency >> hit latency)
768
1078
 
769
- ---
1079
+ ## API Reference
770
1080
 
771
- ### `await cache.tags(key: any) : string[]`
1081
+ The cds-caching plugin provides two APIs for managing cache operations:
772
1082
 
773
- Returns the tags for a given key.
1083
+ - **Programmatic API** - JavaScript methods for use within your CAP application code
1084
+ - **OData API** - REST endpoints for external applications and monitoring tools
774
1085
 
775
- #### `key: any`
1086
+ ### Programmatic API
776
1087
 
777
- The key to get the tags for. The key handling is the same as for the `createKey` method.
1088
+ The programmatic API provides methods for direct cache operations within your CAP application:
778
1089
 
779
- #### Returns
1090
+ ```javascript
1091
+ // Connect to the caching service
1092
+ const cache = await cds.connect.to("caching")
780
1093
 
781
- An array of tags. If the key does not exist, an empty array is returned.
1094
+ // Basic operations
1095
+ await cache.set("key", "value")
1096
+ const value = await cache.get("key")
1097
+ await cache.delete("key")
782
1098
 
783
- ---
1099
+ // Read-through operations
1100
+ const result = await cache.rt.run(query, db)
1101
+ const result = await cache.rt.send(request, service)
784
1102
 
785
- ### `await cache.metadata(key: any) : { tags: string[], timestamp: number } | undefined`
1103
+ // Metrics and statistics
1104
+ const stats = await cache.getCurrentStats()
1105
+ const keyMetrics = await cache.getCurrentKeyMetrics()
1106
+ ```
786
1107
 
787
- Returns the metadata for a given key.
1108
+ [See the full Programmatic API Reference →](docs/programmatic-api.md)
788
1109
 
789
- #### `key: any`
1110
+ ### OData API
790
1111
 
791
- The key to get the metadata for. The key handling is the same as for the `createKey` method.
1112
+ The OData API provides REST endpoints for external applications, monitoring tools, and administrative interfaces:
792
1113
 
793
- #### Returns
1114
+ ```http
1115
+ ### Get cache statistics
1116
+ GET /odata/v4/caching-api/Metrics?$filter=cache eq 'mycache'
794
1117
 
795
- An object containing the metadata for the given key or `undefined` if the key does not exist. The metadata object contains the following properties:
1118
+ ### Get cache entries
1119
+ GET /odata/v4/caching-api/Caches('mycache')/getEntries()
796
1120
 
797
- - `tags`: An array of tags.
798
- - `timestamp`: The timestamp of the cache entry.
1121
+ ### Clear cache
1122
+ POST /odata/v4/caching-api/Caches('mycache')/clear()
1123
+ ```
799
1124
 
800
- ---
1125
+ [See the full OData API Reference →](docs/odata-api.md)
801
1126
 
802
1127
  ### Contributing
803
1128