cds-caching 3.0.1 → 3.1.1

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.
@@ -122,6 +122,22 @@ class AsyncOperations {
122
122
  };
123
123
  }
124
124
 
125
+ /** @private */
126
+ _resolveTags(options, value) {
127
+ const resolver = this.cache?.tagResolver;
128
+ if (resolver) {
129
+ return resolver.resolveTags(options?.tags, value, {
130
+ user: cds.context?.user?.id,
131
+ tenant: cds.context?.tenant,
132
+ locale: cds.context?.locale,
133
+ }) || [];
134
+ }
135
+ if (!Array.isArray(options?.tags)) return [];
136
+ return options.tags
137
+ .map((t) => (typeof t === 'string' ? t : t?.value))
138
+ .filter((t) => typeof t === 'string' && t);
139
+ }
140
+
125
141
  /** @private */
126
142
  async _wrap(key, asyncFunction, options, args) {
127
143
  // Create dynamic key based on function arguments
@@ -140,7 +156,8 @@ class AsyncOperations {
140
156
  args: args,
141
157
  cacheKey: cacheKey
142
158
  }),
143
- cacheOptions: JSON.stringify(options)
159
+ cacheOptions: JSON.stringify(options),
160
+ tags: this._resolveTags(options, undefined),
144
161
  };
145
162
 
146
163
  // Safely check if key exists in cache
@@ -156,16 +173,6 @@ class AsyncOperations {
156
173
  if (hasKey) {
157
174
  const latency = this.getElapsedMs(startTime);
158
175
 
159
- // Safely record hit statistics
160
- const hitStatsResult = await this.safeCacheOperation(
161
- () => this.statistics.recordHit(latency, cacheKey, metadata),
162
- 'recordHit',
163
- { key: cacheKey, latency }
164
- );
165
- if (!hitStatsResult.success) {
166
- cacheErrors.push(hitStatsResult.error);
167
- }
168
-
169
176
  // Safely get value from cache
170
177
  const getResult = await this.safeCacheOperation(
171
178
  () => this.cache.send("GET", { key: cacheKey }),
@@ -174,6 +181,19 @@ class AsyncOperations {
174
181
  );
175
182
 
176
183
  if (getResult.success && getResult.result?.value !== undefined) {
184
+ if (Array.isArray(getResult.result.tags) && getResult.result.tags.length > 0) {
185
+ metadata.tags = getResult.result.tags;
186
+ }
187
+
188
+ const hitStatsResult = await this.safeCacheOperation(
189
+ () => this.statistics.recordHit(latency, cacheKey, metadata),
190
+ 'recordHit',
191
+ { key: cacheKey, latency }
192
+ );
193
+ if (!hitStatsResult.success) {
194
+ cacheErrors.push(hitStatsResult.error);
195
+ }
196
+
177
197
  return {
178
198
  result: getResult.result.value,
179
199
  cacheKey,
@@ -187,6 +207,8 @@ class AsyncOperations {
187
207
  try {
188
208
  const response = await asyncFunction(...args);
189
209
  const latency = this.getElapsedMs(startTime);
210
+ const tags = this._resolveTags(options, response);
211
+ metadata.tags = tags;
190
212
 
191
213
  // Safely record miss statistics
192
214
  const missStatsResult = await this.safeCacheOperation(
@@ -201,7 +223,7 @@ class AsyncOperations {
201
223
  // Safely store in cache
202
224
  const wrappedValue = {
203
225
  value: response,
204
- tags: options.tags || [],
226
+ tags,
205
227
  timestamp: Date.now()
206
228
  };
207
229
 
@@ -281,7 +303,8 @@ class AsyncOperations {
281
303
  args: args,
282
304
  cacheKey: cacheKey
283
305
  }),
284
- cacheOptions: JSON.stringify(options)
306
+ cacheOptions: JSON.stringify(options),
307
+ tags: this._resolveTags(options, undefined),
285
308
  };
286
309
 
287
310
  // Safely check if key exists in cache
@@ -297,16 +320,6 @@ class AsyncOperations {
297
320
  if (hasKey) {
298
321
  const latency = this.getElapsedMs(startTime);
299
322
 
300
- // Safely record hit statistics
301
- const hitStatsResult = await this.safeCacheOperation(
302
- () => this.statistics.recordHit(latency, cacheKey, metadata),
303
- 'recordHit',
304
- { key: cacheKey, latency }
305
- );
306
- if (!hitStatsResult.success) {
307
- cacheErrors.push(hitStatsResult.error);
308
- }
309
-
310
323
  // Safely get value from cache
311
324
  const getResult = await this.safeCacheOperation(
312
325
  () => this.cache.send("GET", { key: cacheKey }),
@@ -315,6 +328,19 @@ class AsyncOperations {
315
328
  );
316
329
 
317
330
  if (getResult.success && getResult.result?.value !== undefined) {
331
+ if (Array.isArray(getResult.result.tags) && getResult.result.tags.length > 0) {
332
+ metadata.tags = getResult.result.tags;
333
+ }
334
+
335
+ const hitStatsResult = await this.safeCacheOperation(
336
+ () => this.statistics.recordHit(latency, cacheKey, metadata),
337
+ 'recordHit',
338
+ { key: cacheKey, latency }
339
+ );
340
+ if (!hitStatsResult.success) {
341
+ cacheErrors.push(hitStatsResult.error);
342
+ }
343
+
318
344
  return {
319
345
  result: getResult.result.value,
320
346
  cacheKey,
@@ -328,6 +354,8 @@ class AsyncOperations {
328
354
  try {
329
355
  const response = await asyncFunction(...args);
330
356
  const latency = this.getElapsedMs(startTime);
357
+ const tags = this._resolveTags(options, response);
358
+ metadata.tags = tags;
331
359
 
332
360
  // Safely record miss statistics
333
361
  const missStatsResult = await this.safeCacheOperation(
@@ -342,7 +370,7 @@ class AsyncOperations {
342
370
  // Safely store in cache
343
371
  const wrappedValue = {
344
372
  value: response,
345
- tags: options.tags || [],
373
+ tags,
346
374
  timestamp: Date.now()
347
375
  };
348
376
 
@@ -28,9 +28,10 @@ class BasicOperations {
28
28
  * @returns {Promise<void>}
29
29
  */
30
30
  async set(key, value, options = {}, tx = null) {
31
+ const tags = this.tagResolver.resolveTags(options.tags, value, options.params) || [];
31
32
  const wrappedValue = {
32
33
  value,
33
- tags: this.tagResolver.resolveTags(options.tags, value, options.params) || [],
34
+ tags,
34
35
  timestamp: Date.now()
35
36
  };
36
37
  const createdKey = this.keyManager.createKey(key, {}, options.key);
@@ -48,7 +49,8 @@ class BasicOperations {
48
49
  operation: 'SET',
49
50
  operationType: 'BASIC',
50
51
  metadata: JSON.stringify({ key: createdKey, ttl: options.ttl || 0 }),
51
- cacheOptions: JSON.stringify(options)
52
+ cacheOptions: JSON.stringify(options),
53
+ tags,
52
54
  };
53
55
  this.statistics.recordNativeSet(createdKey, metadata);
54
56
 
@@ -84,7 +86,8 @@ class BasicOperations {
84
86
  dataType: 'Operation',
85
87
  operation: 'GET',
86
88
  operationType: 'BASIC',
87
- metadata: JSON.stringify({ key: createdKey })
89
+ metadata: JSON.stringify({ key: createdKey }),
90
+ tags: wrappedValue?.tags || [],
88
91
  };
89
92
  const isHit = wrappedValue !== undefined && wrappedValue !== null;
90
93
  this.statistics.recordNativeGet(createdKey, isHit, metadata);
@@ -129,6 +129,13 @@ class CapOperations {
129
129
 
130
130
  const key = this.keyManager.createKey(request, keyParts, requestOptions.key);
131
131
  const startTime = process.hrtime();
132
+ const tagParams = {
133
+ ...request.params,
134
+ user: request.user?.id,
135
+ tenant: request.tenant,
136
+ locale: request.locale,
137
+ hash: this.keyManager.createContentHash(request),
138
+ };
132
139
 
133
140
  // Track the miss with total latency (cache lookup + backend operation)
134
141
  const metadata = {
@@ -153,7 +160,9 @@ class CapOperations {
153
160
  event: request.event,
154
161
  headers: redactHeaders(request.headers),
155
162
  }),
156
- cacheOptions: JSON.stringify(requestOptions)
163
+ cacheOptions: JSON.stringify(requestOptions),
164
+ // Static tags resolve without a payload; data tags fill in on miss after the response.
165
+ tags: this.tagResolver.resolveTags(requestOptions.tags, undefined, tagParams) || [],
157
166
  };
158
167
 
159
168
  // Safely check if key exists in cache
@@ -169,17 +178,7 @@ class CapOperations {
169
178
  if (hasKey) {
170
179
  const latency = this.getElapsedMs(startTime);
171
180
 
172
- // Safely record hit statistics
173
- const hitStatsResult = await this.safeCacheOperation(
174
- () => this.statistics.recordHit(latency, key, metadata),
175
- 'recordHit',
176
- { key, latency }
177
- );
178
- if (!hitStatsResult.success) {
179
- cacheErrors.push(hitStatsResult.error);
180
- }
181
-
182
- // Safely get value from cache
181
+ // Safely get value from cache (before recording so entry tags are available)
183
182
  const getResult = await this.safeCacheOperation(
184
183
  () => this.cache.send("GET", { key }),
185
184
  'get',
@@ -187,6 +186,19 @@ class CapOperations {
187
186
  );
188
187
 
189
188
  if (getResult.success && getResult.result?.value !== undefined) {
189
+ if (Array.isArray(getResult.result.tags) && getResult.result.tags.length > 0) {
190
+ metadata.tags = getResult.result.tags;
191
+ }
192
+
193
+ const hitStatsResult = await this.safeCacheOperation(
194
+ () => this.statistics.recordHit(latency, key, metadata),
195
+ 'recordHit',
196
+ { key, latency }
197
+ );
198
+ if (!hitStatsResult.success) {
199
+ cacheErrors.push(hitStatsResult.error);
200
+ }
201
+
190
202
  return {
191
203
  result: getResult.result.value,
192
204
  cacheKey: key,
@@ -200,6 +212,8 @@ class CapOperations {
200
212
  try {
201
213
  const response = await service.send(request);
202
214
  const totalLatency = this.getElapsedMs(startTime);
215
+ const tags = this.tagResolver.resolveTags(requestOptions.tags, response, tagParams) || [];
216
+ metadata.tags = tags;
203
217
 
204
218
  // Safely record miss statistics
205
219
  const missStatsResult = await this.safeCacheOperation(
@@ -214,7 +228,7 @@ class CapOperations {
214
228
  // Safely store in cache
215
229
  const wrappedValue = {
216
230
  value: response,
217
- tags: this.tagResolver.resolveTags(requestOptions.tags, response, { ...request.params, user: request.user?.id, tenant: request.tenant, locale: request.locale, hash: this.keyManager.createContentHash(request) }),
231
+ tags,
218
232
  timestamp: Date.now()
219
233
  };
220
234
 
@@ -311,9 +325,14 @@ class CapOperations {
311
325
  const cacheHit = getResult.success && getResult.result?.value !== undefined;
312
326
  const cacheLatency = this.getElapsedMs(startTime);
313
327
  const metadata = this.extractMetadataFromRequest(req);
328
+ const tagParams = { ...req.params, hash: this.keyManager.createContentHash(req) };
314
329
  const cacheErrors = [];
315
330
 
316
331
  if (cacheHit) {
332
+ metadata.tags = (Array.isArray(getResult.result.tags) && getResult.result.tags.length > 0)
333
+ ? getResult.result.tags
334
+ : (this.tagResolver.resolveTags(req.cacheOptions.tags, getResult.result.value, tagParams) || []);
335
+
317
336
  // Cache hit
318
337
  const hitStatsResult = await this.safeCacheOperation(
319
338
  () => this.statistics.recordHit(cacheLatency, req.cacheKey, metadata),
@@ -336,6 +355,8 @@ class CapOperations {
336
355
  try {
337
356
  const response = await next();
338
357
  const totalLatency = this.getElapsedMs(startTime);
358
+ const tags = this.tagResolver.resolveTags(req.cacheOptions.tags, response, tagParams) || [];
359
+ metadata.tags = tags;
339
360
 
340
361
  // Safely record miss statistics
341
362
  const missStatsResult = await this.safeCacheOperation(
@@ -352,7 +373,7 @@ class CapOperations {
352
373
  // Safely store in cache
353
374
  const wrappedValue = {
354
375
  value: response,
355
- tags: this.tagResolver.resolveTags(req.cacheOptions.tags, response, { ...req.params, hash: this.keyManager.createContentHash(req) }),
376
+ tags,
356
377
  timestamp: Date.now()
357
378
  };
358
379
 
@@ -407,6 +428,7 @@ class CapOperations {
407
428
 
408
429
  const hasCachedValue = hasCachedValueResult.success && hasCachedValueResult.result;
409
430
  const cacheLatency = this.getElapsedMs(startTime);
431
+ const tagParams = { ...query.params, hash: this.keyManager.createContentHash(query) };
410
432
  const metadata = {
411
433
  dataType: 'Query',
412
434
  operation: 'SELECT',
@@ -423,21 +445,12 @@ class CapOperations {
423
445
  serviceName: srv?.name,
424
446
  path: query.path,
425
447
  }),
426
- cacheOptions: JSON.stringify(options)
448
+ cacheOptions: JSON.stringify(options),
449
+ tags: this.tagResolver.resolveTags(options.tags, undefined, tagParams) || [],
427
450
  };
428
451
  const cacheErrors = [];
429
452
 
430
453
  if (hasCachedValue) {
431
- // Cache hit
432
- const hitStatsResult = await this.safeCacheOperation(
433
- () => this.statistics.recordHit(cacheLatency, query.cacheKey, metadata),
434
- 'recordHit',
435
- { key: query.cacheKey, latency: cacheLatency }
436
- );
437
- if (!hitStatsResult.success) {
438
- cacheErrors.push(hitStatsResult.error);
439
- }
440
-
441
454
  const getResult = await this.safeCacheOperation(
442
455
  () => this.cache.send("GET", { key: query.cacheKey }),
443
456
  'get',
@@ -445,6 +458,19 @@ class CapOperations {
445
458
  );
446
459
 
447
460
  if (getResult.success && getResult.result?.value !== undefined) {
461
+ if (Array.isArray(getResult.result.tags) && getResult.result.tags.length > 0) {
462
+ metadata.tags = getResult.result.tags;
463
+ }
464
+
465
+ const hitStatsResult = await this.safeCacheOperation(
466
+ () => this.statistics.recordHit(cacheLatency, query.cacheKey, metadata),
467
+ 'recordHit',
468
+ { key: query.cacheKey, latency: cacheLatency }
469
+ );
470
+ if (!hitStatsResult.success) {
471
+ cacheErrors.push(hitStatsResult.error);
472
+ }
473
+
448
474
  return {
449
475
  result: getResult.result.value,
450
476
  cacheKey: query.cacheKey,
@@ -458,6 +484,8 @@ class CapOperations {
458
484
  try {
459
485
  const data = await srv.run(query);
460
486
  const totalLatency = this.getElapsedMs(startTime);
487
+ const tags = this.tagResolver.resolveTags(options.tags, data, tagParams) || [];
488
+ metadata.tags = tags;
461
489
 
462
490
  // Safely record miss statistics
463
491
  const missStatsResult = await this.safeCacheOperation(
@@ -472,7 +500,7 @@ class CapOperations {
472
500
  // Safely store in cache
473
501
  const wrappedValue = {
474
502
  value: data,
475
- tags: this.tagResolver.resolveTags(options.tags, data, { ...query.params, hash: this.keyManager.createContentHash(query) }),
503
+ tags,
476
504
  timestamp: Date.now()
477
505
  };
478
506
 
@@ -590,7 +618,11 @@ class CapOperations {
590
618
  path: req.http?.req?.path,
591
619
  url: req.http?.req?.url
592
620
  }),
593
- cacheOptions: JSON.stringify(req.cacheOptions)
621
+ cacheOptions: JSON.stringify(req.cacheOptions),
622
+ tags: this.tagResolver.resolveTags(req.cacheOptions?.tags, undefined, {
623
+ ...req.params,
624
+ hash: this.keyManager.createContentHash(req),
625
+ }) || [],
594
626
  };
595
627
 
596
628
  return metadata;