cds-caching 0.3.3 → 1.1.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 +691 -278
- package/cds-plugin.js +2 -2
- package/index.cds +165 -20
- package/lib/CachingService.d.ts +328 -0
- package/lib/CachingService.js +373 -0
- package/lib/operations/AsyncOperations.js +335 -0
- package/lib/operations/BasicOperations.js +211 -0
- package/lib/operations/CapOperations.js +540 -0
- package/lib/support/CacheStatisticsHandler.js +1124 -0
- package/lib/support/CacheStoreManager.js +120 -0
- package/lib/support/KeyManager.js +132 -0
- package/lib/support/RuntimeConfigurationManager.js +148 -0
- package/lib/support/StatisticsPersistenceManager.js +375 -0
- package/lib/support/TagResolver.js +125 -0
- package/lib/util.js +201 -0
- package/package.json +18 -13
- package/srv/caching-api-service.js +124 -0
- package/srv/CacheStatisticsHandler.js +0 -213
- package/srv/CachingService.js +0 -587
- package/srv/statistics-service.cds +0 -37
- package/srv/statistics-service.js +0 -72
- package/srv/util.js +0 -101
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
const TagResolver = require('../support/TagResolver');
|
|
2
|
+
/**
|
|
3
|
+
* Manages CAP-specific cache operations
|
|
4
|
+
*/
|
|
5
|
+
class CapOperations {
|
|
6
|
+
|
|
7
|
+
cacheAnnotatedFunctions = {
|
|
8
|
+
bound: [],
|
|
9
|
+
unbound: []
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
constructor(cache, keyManager, statistics, log, runtimeConfigManager) {
|
|
13
|
+
this.cache = cache;
|
|
14
|
+
this.keyManager = keyManager;
|
|
15
|
+
this.statistics = statistics;
|
|
16
|
+
this.tagResolver = new TagResolver();
|
|
17
|
+
this.runtimeConfigManager = runtimeConfigManager;
|
|
18
|
+
this.log = log || console;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Safely execute cache operations with error handling
|
|
23
|
+
* @param {Function} operation - The cache operation to execute
|
|
24
|
+
* @param {string} operationName - Name of the operation for logging
|
|
25
|
+
* @param {object} context - Context information for logging
|
|
26
|
+
* @returns {Promise<object>} - The result with error information
|
|
27
|
+
*/
|
|
28
|
+
async safeCacheOperation(operation, operationName, context = {}) {
|
|
29
|
+
try {
|
|
30
|
+
const result = await operation();
|
|
31
|
+
this.log.info('REEEESULT', { result });
|
|
32
|
+
return { success: true, result, error: null };
|
|
33
|
+
} catch (error) {
|
|
34
|
+
this.log.warn(`Cache ${operationName} failed:`, {
|
|
35
|
+
error: error.message,
|
|
36
|
+
stack: error.stack,
|
|
37
|
+
context: context
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
success: false,
|
|
42
|
+
result: null,
|
|
43
|
+
error: {
|
|
44
|
+
message: error.message,
|
|
45
|
+
operation: operationName,
|
|
46
|
+
context: context
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Send a request with caching with read-through capabilities.
|
|
54
|
+
*
|
|
55
|
+
* @param {object} arg1 - the request object
|
|
56
|
+
* @param {Service} service - the service to send the request to
|
|
57
|
+
* @param {object} options - the options for the request
|
|
58
|
+
* @returns {Promise<any>} - the result
|
|
59
|
+
*/
|
|
60
|
+
async send(request, service, options) {
|
|
61
|
+
const requestOptions = {
|
|
62
|
+
ttl: 0,
|
|
63
|
+
...(options || {}),
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(request.method?.toUpperCase()) || !service.send) {
|
|
67
|
+
return { result: null, cacheKey: null, metadata: { hit: false, latency: 0 }, cacheErrors: [] };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const keyParts = {
|
|
71
|
+
user: cds.context?.user?.id || cds.context?.user,
|
|
72
|
+
tenant: cds.context?.tenant,
|
|
73
|
+
locale: cds.context?.locale,
|
|
74
|
+
serviceName: service.name,
|
|
75
|
+
path: request.path || request.http?.req?.path,
|
|
76
|
+
method: request.method,
|
|
77
|
+
data: request.data,
|
|
78
|
+
params: request.params,
|
|
79
|
+
query: request.query,
|
|
80
|
+
event: request.event,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const key = this.keyManager.createKey(request, keyParts, requestOptions.key);
|
|
84
|
+
const startTime = process.hrtime();
|
|
85
|
+
|
|
86
|
+
// Track the miss with total latency (cache lookup + backend operation)
|
|
87
|
+
const metadata = {
|
|
88
|
+
dataType: request.send ? request.constructor.name : 'SendRequest',
|
|
89
|
+
operation: 'SEND',
|
|
90
|
+
operationType: 'READ_THROUGH',
|
|
91
|
+
tenant: request.tenant || cds.context?.tenant,
|
|
92
|
+
user: request.user?.id || cds.context?.user?.id,
|
|
93
|
+
locale: request.locale || cds.context?.locale,
|
|
94
|
+
target: request.target?.name,
|
|
95
|
+
query: request.query ? JSON.stringify(request.query) : undefined,
|
|
96
|
+
subject: request.subject ? JSON.stringify(request.subject) : undefined,
|
|
97
|
+
metadata: JSON.stringify({
|
|
98
|
+
serviceName: service.name,
|
|
99
|
+
path: request.path || request.http?.req?.path,
|
|
100
|
+
method: request.method,
|
|
101
|
+
url: request.url,
|
|
102
|
+
data: request.data,
|
|
103
|
+
params: request.params,
|
|
104
|
+
subject: request.subject,
|
|
105
|
+
query: request.query,
|
|
106
|
+
event: request.event,
|
|
107
|
+
headers: request.headers,
|
|
108
|
+
}),
|
|
109
|
+
cacheOptions: JSON.stringify(requestOptions)
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Safely check if key exists in cache
|
|
113
|
+
const hasKeyResult = await this.safeCacheOperation(
|
|
114
|
+
() => this.cache.has(key),
|
|
115
|
+
'has',
|
|
116
|
+
{ key, serviceName: service.name }
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const hasKey = hasKeyResult.success && hasKeyResult.result;
|
|
120
|
+
const cacheErrors = [];
|
|
121
|
+
|
|
122
|
+
if (hasKey) {
|
|
123
|
+
const latency = this.getElapsedMs(startTime);
|
|
124
|
+
|
|
125
|
+
// Safely record hit statistics
|
|
126
|
+
const hitStatsResult = await this.safeCacheOperation(
|
|
127
|
+
() => this.statistics.recordHit(latency, key, metadata),
|
|
128
|
+
'recordHit',
|
|
129
|
+
{ key, latency }
|
|
130
|
+
);
|
|
131
|
+
if (!hitStatsResult.success) {
|
|
132
|
+
cacheErrors.push(hitStatsResult.error);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Safely get value from cache
|
|
136
|
+
const getResult = await this.safeCacheOperation(
|
|
137
|
+
() => this.cache.send("GET", { key }),
|
|
138
|
+
'get',
|
|
139
|
+
{ key }
|
|
140
|
+
);
|
|
141
|
+
|
|
142
|
+
if (getResult.success && getResult.result?.value !== undefined) {
|
|
143
|
+
return {
|
|
144
|
+
result: getResult.result.value,
|
|
145
|
+
cacheKey: key,
|
|
146
|
+
metadata: { hit: true, latency: latency },
|
|
147
|
+
cacheErrors: cacheErrors
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Cache miss or cache error - delegate to underlying service
|
|
153
|
+
try {
|
|
154
|
+
const response = await service.send(request);
|
|
155
|
+
const totalLatency = this.getElapsedMs(startTime);
|
|
156
|
+
|
|
157
|
+
// Safely record miss statistics
|
|
158
|
+
const missStatsResult = await this.safeCacheOperation(
|
|
159
|
+
() => this.statistics.recordMiss(totalLatency, key, metadata),
|
|
160
|
+
'recordMiss',
|
|
161
|
+
{ key, latency: totalLatency }
|
|
162
|
+
);
|
|
163
|
+
if (!missStatsResult.success) {
|
|
164
|
+
cacheErrors.push(missStatsResult.error);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Safely store in cache
|
|
168
|
+
const wrappedValue = {
|
|
169
|
+
value: response,
|
|
170
|
+
tags: this.tagResolver.resolveTags(requestOptions.tags, response, { ...request.params, user: request.user?.id, tenant: request.tenant, locale: request.locale, hash: this.keyManager.createContentHash(request) }),
|
|
171
|
+
timestamp: Date.now()
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const setResult = await this.safeCacheOperation(
|
|
175
|
+
() => this.cache.send("SET", { key, value: wrappedValue, ttl: requestOptions.ttl || 0 }),
|
|
176
|
+
'set',
|
|
177
|
+
{ key, ttl: requestOptions.ttl }
|
|
178
|
+
);
|
|
179
|
+
if (!setResult.success) {
|
|
180
|
+
cacheErrors.push(setResult.error);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
result: response,
|
|
185
|
+
cacheKey: key,
|
|
186
|
+
metadata: { hit: false, latency: totalLatency },
|
|
187
|
+
cacheErrors: cacheErrors
|
|
188
|
+
};
|
|
189
|
+
} catch (serviceError) {
|
|
190
|
+
// If the underlying service fails, throw the error
|
|
191
|
+
this.log.error('Service operation failed:', {
|
|
192
|
+
error: serviceError.message,
|
|
193
|
+
serviceName: service.name,
|
|
194
|
+
key: key
|
|
195
|
+
});
|
|
196
|
+
throw serviceError;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Run a cached operation with automatic key generation
|
|
202
|
+
* @param {object} req - the request object
|
|
203
|
+
* @param {function} handler - the handler function
|
|
204
|
+
* @param {object} options - cache options
|
|
205
|
+
* @param {object} cache - the cache instance
|
|
206
|
+
* @returns {Promise<any>} - the result
|
|
207
|
+
*/
|
|
208
|
+
async run() {
|
|
209
|
+
const arg1 = arguments[0];
|
|
210
|
+
if (typeof arg1 === "object") {
|
|
211
|
+
switch (arg1.constructor.name) {
|
|
212
|
+
case "Request":
|
|
213
|
+
case "ODataRequest":
|
|
214
|
+
case "NoaRequest":
|
|
215
|
+
const req = arg1;
|
|
216
|
+
const next = arguments[1];
|
|
217
|
+
|
|
218
|
+
if (req.query?.UPDATE || req.query?.INSERT || req.query?.DELETE) {
|
|
219
|
+
return next();
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
req.cacheOptions = req.event ? this.extractFunctionCacheOptions(req, arguments[2]) : this.extractEntityCacheOptions(req, arguments[2]);
|
|
223
|
+
|
|
224
|
+
req.cacheKey = this.keyManager.createKey(req, {}, req.cacheOptions.key);
|
|
225
|
+
req.res?.setHeader('x-sap-cap-cache-key', req.cacheKey);
|
|
226
|
+
|
|
227
|
+
// Track cache operation timing
|
|
228
|
+
const startTime = process.hrtime();
|
|
229
|
+
|
|
230
|
+
// Safely get value from cache
|
|
231
|
+
const getResult = await this.safeCacheOperation(
|
|
232
|
+
() => this.cache.send("GET", { key: req.cacheKey }),
|
|
233
|
+
'get',
|
|
234
|
+
{ key: req.cacheKey, serviceName: req.target?.name }
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
const cacheHit = getResult.success && getResult.result?.value !== undefined;
|
|
238
|
+
const cacheLatency = this.getElapsedMs(startTime);
|
|
239
|
+
const metadata = this.extractMetadataFromRequest(req);
|
|
240
|
+
const cacheErrors = [];
|
|
241
|
+
|
|
242
|
+
if (cacheHit) {
|
|
243
|
+
// Cache hit
|
|
244
|
+
const hitStatsResult = await this.safeCacheOperation(
|
|
245
|
+
() => this.statistics.recordHit(cacheLatency, req.cacheKey, metadata),
|
|
246
|
+
'recordHit',
|
|
247
|
+
{ key: req.cacheKey, latency: cacheLatency }
|
|
248
|
+
);
|
|
249
|
+
if (!hitStatsResult.success) {
|
|
250
|
+
cacheErrors.push(hitStatsResult.error);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
req.res?.setHeader('x-sap-cap-cache', "hit");
|
|
254
|
+
return {
|
|
255
|
+
result: getResult.result.value,
|
|
256
|
+
cacheKey: req.cacheKey,
|
|
257
|
+
metadata: { hit: true, latency: cacheLatency },
|
|
258
|
+
cacheErrors: cacheErrors
|
|
259
|
+
};
|
|
260
|
+
} else {
|
|
261
|
+
// Cache miss - track the backend operation
|
|
262
|
+
try {
|
|
263
|
+
const response = await next();
|
|
264
|
+
const totalLatency = this.getElapsedMs(startTime);
|
|
265
|
+
|
|
266
|
+
// Safely record miss statistics
|
|
267
|
+
const missStatsResult = await this.safeCacheOperation(
|
|
268
|
+
() => this.statistics.recordMiss(totalLatency, req.cacheKey, metadata),
|
|
269
|
+
'recordMiss',
|
|
270
|
+
{ key: req.cacheKey, latency: totalLatency }
|
|
271
|
+
);
|
|
272
|
+
if (!missStatsResult.success) {
|
|
273
|
+
cacheErrors.push(missStatsResult.error);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
req.res?.setHeader('x-sap-cap-cache', "miss");
|
|
277
|
+
|
|
278
|
+
// Safely store in cache
|
|
279
|
+
const wrappedValue = {
|
|
280
|
+
value: response,
|
|
281
|
+
tags: this.tagResolver.resolveTags(req.cacheOptions.tags, response, { ...req.params, hash: this.keyManager.createContentHash(req) }),
|
|
282
|
+
timestamp: Date.now()
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
const setResult = await this.safeCacheOperation(
|
|
286
|
+
() => this.cache.send("SET", { key: req.cacheKey, value: wrappedValue, ttl: req.cacheOptions.ttl || 0 }),
|
|
287
|
+
'set',
|
|
288
|
+
{ key: req.cacheKey, ttl: req.cacheOptions.ttl }
|
|
289
|
+
);
|
|
290
|
+
|
|
291
|
+
if (!setResult.success) {
|
|
292
|
+
cacheErrors.push(setResult.error);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
result: response,
|
|
297
|
+
cacheKey: req.cacheKey,
|
|
298
|
+
metadata: { hit: false, latency: totalLatency },
|
|
299
|
+
cacheErrors: cacheErrors
|
|
300
|
+
};
|
|
301
|
+
} catch (serviceError) {
|
|
302
|
+
// If the underlying service fails, throw the error
|
|
303
|
+
this.log.error('Service operation failed:', {
|
|
304
|
+
error: serviceError.message,
|
|
305
|
+
serviceName: req.target?.name,
|
|
306
|
+
key: req.cacheKey
|
|
307
|
+
});
|
|
308
|
+
throw serviceError;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
case "cds.ql":
|
|
312
|
+
const query = arg1;
|
|
313
|
+
let srv = arguments[1] || cds;
|
|
314
|
+
|
|
315
|
+
if (query.SELECT) {
|
|
316
|
+
let options = {
|
|
317
|
+
ttl: 0,
|
|
318
|
+
tags: [],
|
|
319
|
+
...(arguments[2] || {}),
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
query.cacheKey = this.keyManager.createKey(query, { serviceName: srv?.name }, options.key);
|
|
323
|
+
|
|
324
|
+
// Track cache operation timing
|
|
325
|
+
const startTime = process.hrtime();
|
|
326
|
+
|
|
327
|
+
// Safely check if key exists in cache
|
|
328
|
+
const hasCachedValueResult = await this.safeCacheOperation(
|
|
329
|
+
() => this.cache.has(query.cacheKey),
|
|
330
|
+
'has',
|
|
331
|
+
{ key: query.cacheKey, serviceName: srv?.name }
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
const hasCachedValue = hasCachedValueResult.success && hasCachedValueResult.result;
|
|
335
|
+
const cacheLatency = this.getElapsedMs(startTime);
|
|
336
|
+
const metadata = {
|
|
337
|
+
dataType: 'Query',
|
|
338
|
+
operation: 'SELECT',
|
|
339
|
+
operationType: 'READ_THROUGH',
|
|
340
|
+
user: cds.context?.user?.id,
|
|
341
|
+
tenant: cds.context?.tenant,
|
|
342
|
+
locale: cds.context?.locale,
|
|
343
|
+
query: JSON.stringify(query.SELECT),
|
|
344
|
+
metadata: JSON.stringify({
|
|
345
|
+
query: query.SELECT,
|
|
346
|
+
user: cds.context?.user?.id,
|
|
347
|
+
tenant: cds.context?.tenant,
|
|
348
|
+
locale: cds.context?.locale,
|
|
349
|
+
serviceName: srv?.name,
|
|
350
|
+
path: query.path,
|
|
351
|
+
}),
|
|
352
|
+
cacheOptions: JSON.stringify(options)
|
|
353
|
+
};
|
|
354
|
+
const cacheErrors = [];
|
|
355
|
+
|
|
356
|
+
if (hasCachedValue) {
|
|
357
|
+
// Cache hit
|
|
358
|
+
const hitStatsResult = await this.safeCacheOperation(
|
|
359
|
+
() => this.statistics.recordHit(cacheLatency, query.cacheKey, metadata),
|
|
360
|
+
'recordHit',
|
|
361
|
+
{ key: query.cacheKey, latency: cacheLatency }
|
|
362
|
+
);
|
|
363
|
+
if (!hitStatsResult.success) {
|
|
364
|
+
cacheErrors.push(hitStatsResult.error);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const getResult = await this.safeCacheOperation(
|
|
368
|
+
() => this.cache.send("GET", { key: query.cacheKey }),
|
|
369
|
+
'get',
|
|
370
|
+
{ key: query.cacheKey }
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
if (getResult.success && getResult.result?.value !== undefined) {
|
|
374
|
+
return {
|
|
375
|
+
result: getResult.result.value,
|
|
376
|
+
cacheKey: query.cacheKey,
|
|
377
|
+
metadata: { hit: true, latency: cacheLatency },
|
|
378
|
+
cacheErrors: cacheErrors
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Cache miss or cache error
|
|
384
|
+
try {
|
|
385
|
+
const data = await srv.run(query);
|
|
386
|
+
const totalLatency = this.getElapsedMs(startTime);
|
|
387
|
+
|
|
388
|
+
// Safely record miss statistics
|
|
389
|
+
const missStatsResult = await this.safeCacheOperation(
|
|
390
|
+
() => this.statistics.recordMiss(totalLatency, query.cacheKey, metadata),
|
|
391
|
+
'recordMiss',
|
|
392
|
+
{ key: query.cacheKey, latency: totalLatency }
|
|
393
|
+
);
|
|
394
|
+
if (!missStatsResult.success) {
|
|
395
|
+
cacheErrors.push(missStatsResult.error);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Safely store in cache
|
|
399
|
+
const wrappedValue = {
|
|
400
|
+
value: data,
|
|
401
|
+
tags: this.tagResolver.resolveTags(options.tags, data, { ...query.params, hash: this.keyManager.createKey(query, { serviceName: srv?.name, template: '{hash}' }) }),
|
|
402
|
+
timestamp: Date.now()
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const setResult = await this.safeCacheOperation(
|
|
406
|
+
() => this.cache.send("SET", { key: query.cacheKey, value: wrappedValue, ttl: options.ttl || 0 }),
|
|
407
|
+
'set',
|
|
408
|
+
{ key: query.cacheKey, ttl: options.ttl }
|
|
409
|
+
);
|
|
410
|
+
if (!setResult.success) {
|
|
411
|
+
cacheErrors.push(setResult.error);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
this.log.info('REEEESULT', { setResult, cacheErrors, wrappedValue });
|
|
415
|
+
|
|
416
|
+
return {
|
|
417
|
+
result: data,
|
|
418
|
+
cacheKey: query.cacheKey,
|
|
419
|
+
metadata: { hit: false, latency: totalLatency },
|
|
420
|
+
cacheErrors: cacheErrors
|
|
421
|
+
};
|
|
422
|
+
} catch (serviceError) {
|
|
423
|
+
// If the underlying service fails, throw the error
|
|
424
|
+
this.log.error('Service operation failed:', {
|
|
425
|
+
error: serviceError.message,
|
|
426
|
+
serviceName: srv?.name,
|
|
427
|
+
key: query.cacheKey
|
|
428
|
+
});
|
|
429
|
+
throw serviceError;
|
|
430
|
+
}
|
|
431
|
+
} else {
|
|
432
|
+
return srv.run(query);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return { result: null, cacheKey: null, metadata: { hit: false, latency: 0 }, cacheErrors: [] };
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Extract cache options from request
|
|
441
|
+
* @param {object} req - the request object
|
|
442
|
+
* @param {object} options - default options
|
|
443
|
+
* @returns {object} - cache options
|
|
444
|
+
*/
|
|
445
|
+
extractCacheOptions(req, options = {}) {
|
|
446
|
+
const extractedOptions = { ...options };
|
|
447
|
+
|
|
448
|
+
// Extract from function annotations
|
|
449
|
+
if (req.target && req.target.name) {
|
|
450
|
+
const functionOptions = this.extractFunctionCacheOptions(req, options);
|
|
451
|
+
Object.assign(extractedOptions, functionOptions);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Extract from entity annotations
|
|
455
|
+
if (req.target && req.target.name) {
|
|
456
|
+
const entityOptions = this.extractEntityCacheOptions(req, options);
|
|
457
|
+
Object.assign(extractedOptions, entityOptions);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return extractedOptions;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Extract function cache options from request
|
|
465
|
+
* @param {object} req - the request object
|
|
466
|
+
* @param {object} options - default options
|
|
467
|
+
* @returns {object} - function cache options
|
|
468
|
+
*/
|
|
469
|
+
extractFunctionCacheOptions(req, options = {}) {
|
|
470
|
+
const functionType = req.query ? 'bound' : 'unbound';
|
|
471
|
+
const functionOptions = this.cacheAnnotatedFunctions[functionType].find(f => f.name === req.event);
|
|
472
|
+
|
|
473
|
+
return {
|
|
474
|
+
ttl: functionOptions?.['@cache.ttl'] || 0,
|
|
475
|
+
key: functionOptions?.['@cache.key'] || null,
|
|
476
|
+
tags: functionOptions?.['@cache.tags'] || [],
|
|
477
|
+
...(options || {}),
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Extract entity cache options from request
|
|
483
|
+
* @param {object} req - the request object
|
|
484
|
+
* @param {object} options - default options
|
|
485
|
+
* @returns {object} - entity cache options
|
|
486
|
+
*/
|
|
487
|
+
extractEntityCacheOptions(req, options = {}) {
|
|
488
|
+
return {
|
|
489
|
+
ttl: req.target?.['@cache.ttl'] || 0,
|
|
490
|
+
key: req.target?.['@cache.key'] || null,
|
|
491
|
+
tags: req.target?.['@cache.tags'] || [],
|
|
492
|
+
...(options || {}),
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Extract metadata from request for statistics
|
|
498
|
+
* @param {object} req - the request object
|
|
499
|
+
* @returns {object} - metadata object
|
|
500
|
+
*/
|
|
501
|
+
extractMetadataFromRequest(req) {
|
|
502
|
+
|
|
503
|
+
console.log(JSON.stringify(req.http?.req, null, 2));
|
|
504
|
+
|
|
505
|
+
const metadata = {
|
|
506
|
+
dataType: req.constructor.name,
|
|
507
|
+
serviceName: req.target?.name || '',
|
|
508
|
+
operation: 'RUN',
|
|
509
|
+
operationType: 'READ_THROUGH',
|
|
510
|
+
tenant: req.tenant,
|
|
511
|
+
user: req.user?.id,
|
|
512
|
+
locale: req.locale,
|
|
513
|
+
target: req.target?.name,
|
|
514
|
+
subject: req.subject ? JSON.stringify(req.subject) : undefined,
|
|
515
|
+
query: req.query?.SELECT ? JSON.stringify(req.query?.SELECT) : undefined,
|
|
516
|
+
metadata: JSON.stringify({
|
|
517
|
+
method: req.method,
|
|
518
|
+
data: req.data,
|
|
519
|
+
params: req.params,
|
|
520
|
+
path: req.http?.req?.path,
|
|
521
|
+
url: req.http?.req?.url
|
|
522
|
+
}),
|
|
523
|
+
cacheOptions: JSON.stringify(req.cacheOptions)
|
|
524
|
+
};
|
|
525
|
+
|
|
526
|
+
return metadata;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Get elapsed time in milliseconds
|
|
531
|
+
* @param {[number, number]} startTime - start time from process.hrtime()
|
|
532
|
+
* @returns {number} - elapsed time in milliseconds
|
|
533
|
+
*/
|
|
534
|
+
getElapsedMs(startTime) {
|
|
535
|
+
const [seconds, nanoseconds] = process.hrtime(startTime);
|
|
536
|
+
return (seconds * 1000) + (nanoseconds / 1000000);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
module.exports = CapOperations;
|