cds-caching 0.3.2 → 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 +603 -278
- package/cds-plugin.js +2 -2
- package/index.cds +165 -20
- package/lib/CachingService.d.ts +328 -0
- package/lib/CachingService.js +369 -0
- package/lib/operations/AsyncOperations.js +172 -0
- package/lib/operations/BasicOperations.js +201 -0
- package/lib/operations/CapOperations.js +332 -0
- package/lib/support/CacheStatisticsHandler.js +1124 -0
- package/lib/support/CacheStoreManager.js +103 -0
- package/lib/support/KeyManager.js +132 -0
- package/lib/support/RuntimeConfigurationManager.js +146 -0
- package/lib/support/StatisticsPersistenceManager.js +375 -0
- package/lib/support/TagResolver.js +125 -0
- package/lib/util.js +201 -0
- package/package.json +17 -13
- package/srv/caching-api-service.js +124 -0
- package/srv/CacheStatisticsHandler.js +0 -213
- package/srv/CachingService.js +0 -588
- package/srv/statistics-service.cds +0 -37
- package/srv/statistics-service.js +0 -72
- package/srv/util.js +0 -101
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manages basic cache operations with statistics tracking
|
|
3
|
+
*/
|
|
4
|
+
class BasicOperations {
|
|
5
|
+
constructor(cache, keyManager, tagResolver, statistics) {
|
|
6
|
+
this.cache = cache;
|
|
7
|
+
this.keyManager = keyManager;
|
|
8
|
+
this.tagResolver = tagResolver;
|
|
9
|
+
this.statistics = statistics;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Set a value in the cache
|
|
14
|
+
* @param {string|object} key - the key to set
|
|
15
|
+
* @param {any} value - the value to cache
|
|
16
|
+
* @param {object} options - cache options
|
|
17
|
+
* @returns {Promise<void>}
|
|
18
|
+
*/
|
|
19
|
+
async set(key, value, options = {}) {
|
|
20
|
+
const wrappedValue = {
|
|
21
|
+
value,
|
|
22
|
+
tags: this.tagResolver.resolveTags(options.tags, value, options.params) || [],
|
|
23
|
+
timestamp: Date.now()
|
|
24
|
+
};
|
|
25
|
+
const createdKey = this.keyManager.createKey(key, {}, options.key);
|
|
26
|
+
await this.cache.send('SET', {
|
|
27
|
+
key: createdKey,
|
|
28
|
+
value: wrappedValue,
|
|
29
|
+
ttl: options.ttl || 0
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const metadata = {
|
|
33
|
+
dataType: 'Operation',
|
|
34
|
+
operation: 'SET',
|
|
35
|
+
operationType: 'BASIC',
|
|
36
|
+
metadata: JSON.stringify({ key: createdKey, ttl: options.ttl || 0 }),
|
|
37
|
+
cacheOptions: JSON.stringify(options)
|
|
38
|
+
};
|
|
39
|
+
this.statistics.recordNativeSet(createdKey, metadata);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Get a value from the cache
|
|
44
|
+
* @param {string|object} key - the key to get
|
|
45
|
+
* @returns {Promise<any>} - the cached value
|
|
46
|
+
*/
|
|
47
|
+
async get(key) {
|
|
48
|
+
const createdKey = this.keyManager.createKey(key);
|
|
49
|
+
const wrappedValue = await this.cache.send('GET', {
|
|
50
|
+
key: createdKey
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Record the native get operation
|
|
54
|
+
const metadata = {
|
|
55
|
+
dataType: 'Operation',
|
|
56
|
+
operation: 'GET',
|
|
57
|
+
operationType: 'BASIC',
|
|
58
|
+
metadata: JSON.stringify({ key: createdKey })
|
|
59
|
+
};
|
|
60
|
+
const isHit = wrappedValue !== undefined && wrappedValue !== null;
|
|
61
|
+
this.statistics.recordNativeGet(createdKey, isHit, metadata);
|
|
62
|
+
|
|
63
|
+
return wrappedValue?.value;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Check if a key exists in the cache
|
|
68
|
+
* @param {string|object} key - the key to check
|
|
69
|
+
* @returns {Promise<boolean>} - whether the key exists
|
|
70
|
+
*/
|
|
71
|
+
async has(key) {
|
|
72
|
+
const createdKey = this.keyManager.createKey(key);
|
|
73
|
+
return this.cache.cache.has(createdKey);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Delete a key from the cache
|
|
78
|
+
* @param {string|object} key - the key to delete
|
|
79
|
+
* @returns {Promise<boolean>} - whether the key was deleted
|
|
80
|
+
*/
|
|
81
|
+
async delete(key) {
|
|
82
|
+
const createdKey = this.keyManager.createKey(key);
|
|
83
|
+
const result = await this.cache.send('DELETE', {
|
|
84
|
+
key: createdKey
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// Record the native delete operation
|
|
88
|
+
const metadata = {
|
|
89
|
+
dataType: 'Operation',
|
|
90
|
+
operation: 'DELETE',
|
|
91
|
+
operationType: 'BASIC',
|
|
92
|
+
metadata: JSON.stringify({ key: createdKey })
|
|
93
|
+
};
|
|
94
|
+
this.statistics.recordNativeDelete(createdKey, metadata);
|
|
95
|
+
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Clear all cache entries
|
|
101
|
+
* @returns {Promise<void>}
|
|
102
|
+
*/
|
|
103
|
+
async clear() {
|
|
104
|
+
await this.cache.send('CLEAR');
|
|
105
|
+
|
|
106
|
+
// Record the native clear operation
|
|
107
|
+
const metadata = {
|
|
108
|
+
dataType: 'Operation',
|
|
109
|
+
operation: 'CLEAR',
|
|
110
|
+
operationType: 'BASIC',
|
|
111
|
+
metadata: JSON.stringify({ cache: this.cache.name })
|
|
112
|
+
};
|
|
113
|
+
this.statistics.recordNativeClear(metadata);
|
|
114
|
+
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Delete all keys that have a specific tag
|
|
119
|
+
* @param {string} tag - the tag to match
|
|
120
|
+
* @returns {Promise<void>}
|
|
121
|
+
*/
|
|
122
|
+
async deleteByTag(tag) {
|
|
123
|
+
for await (const [key, wrappedValue] of this.iterator()) {
|
|
124
|
+
if (wrappedValue?.tags?.includes(tag)) {
|
|
125
|
+
await this.delete(key);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Record the native deleteByTag operation
|
|
130
|
+
const metadata = {
|
|
131
|
+
dataType: 'Operation',
|
|
132
|
+
operation: 'DELETE_BY_TAG',
|
|
133
|
+
operationType: 'BASIC',
|
|
134
|
+
metadata: JSON.stringify({ tag: tag, cache: this.cache.name })
|
|
135
|
+
};
|
|
136
|
+
this.statistics.recordNativeDeleteByTag(tag, metadata);
|
|
137
|
+
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get metadata for a key
|
|
142
|
+
* @param {string|object} key - the key to get metadata for
|
|
143
|
+
* @returns {Promise<object|null>} - the metadata or null if not found
|
|
144
|
+
*/
|
|
145
|
+
async metadata(key) {
|
|
146
|
+
const createdKey = this.keyManager.createKey(key);
|
|
147
|
+
const wrappedValue = await this.cache.send('GET', {
|
|
148
|
+
key: createdKey
|
|
149
|
+
});
|
|
150
|
+
if (!wrappedValue) return null;
|
|
151
|
+
|
|
152
|
+
const { value, ...metadata } = wrappedValue;
|
|
153
|
+
return metadata;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get tags for a key
|
|
158
|
+
* @param {string|object} key - the key to get tags for
|
|
159
|
+
* @returns {Promise<string[]>} - the tags
|
|
160
|
+
*/
|
|
161
|
+
async tags(key) {
|
|
162
|
+
const createdKey = this.keyManager.createKey(key);
|
|
163
|
+
const wrappedValue = await this.cache.send('GET', {
|
|
164
|
+
key: createdKey
|
|
165
|
+
});
|
|
166
|
+
return wrappedValue?.tags || [];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get a raw value from the cache without statistics tracking
|
|
171
|
+
* @param {string|object} key - the key to get
|
|
172
|
+
* @returns {Promise<any>} - the raw cached value
|
|
173
|
+
*/
|
|
174
|
+
async getRaw(key) {
|
|
175
|
+
const createdKey = this.keyManager.createKey(key);
|
|
176
|
+
const wrappedValue = await this.cache.send('GET', {
|
|
177
|
+
key: createdKey
|
|
178
|
+
});
|
|
179
|
+
return wrappedValue?.value;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Iterator for all cache entries
|
|
184
|
+
* @returns {AsyncIterator} - iterator for cache entries
|
|
185
|
+
*/
|
|
186
|
+
async *iterator() {
|
|
187
|
+
for await (const [key, value] of this.cache.cache.iterator()) {
|
|
188
|
+
if (typeof value === "string") {
|
|
189
|
+
try {
|
|
190
|
+
yield [key, JSON.parse(value)];
|
|
191
|
+
} catch (error) {
|
|
192
|
+
yield [key, value];
|
|
193
|
+
}
|
|
194
|
+
} else {
|
|
195
|
+
yield [key, value];
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = BasicOperations;
|
|
@@ -0,0 +1,332 @@
|
|
|
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
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Send a request with caching with read-through capabilities.
|
|
24
|
+
*
|
|
25
|
+
* @param {object} arg1 - the request object
|
|
26
|
+
* @param {Service} service - the service to send the request to
|
|
27
|
+
* @param {object} options - the options for the request
|
|
28
|
+
* @returns {Promise<any>} - the result
|
|
29
|
+
*/
|
|
30
|
+
async send(request, service, options) {
|
|
31
|
+
const requestOptions = {
|
|
32
|
+
ttl: 0,
|
|
33
|
+
...(options || {}),
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(request.method?.toUpperCase()) || !service.send) {
|
|
37
|
+
return { result: null, cacheKey: null, metadata: { hit: false, latency: 0 } };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const keyParts = {
|
|
41
|
+
user: cds.context?.user?.id || cds.context?.user,
|
|
42
|
+
tenant: cds.context?.tenant,
|
|
43
|
+
locale: cds.context?.locale,
|
|
44
|
+
serviceName: service.name,
|
|
45
|
+
path: request.path || request.http?.req?.path,
|
|
46
|
+
method: request.method,
|
|
47
|
+
data: request.data,
|
|
48
|
+
params: request.params,
|
|
49
|
+
query: request.query,
|
|
50
|
+
event: request.event,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const key = this.keyManager.createKey(request, keyParts, requestOptions.key);
|
|
54
|
+
const startTime = process.hrtime();
|
|
55
|
+
|
|
56
|
+
// Track the miss with total latency (cache lookup + backend operation)
|
|
57
|
+
const metadata = {
|
|
58
|
+
dataType: request.send ? request.constructor.name : 'SendRequest',
|
|
59
|
+
operation: 'SEND',
|
|
60
|
+
operationType: 'READ_THROUGH',
|
|
61
|
+
tenant: request.tenant || cds.context?.tenant,
|
|
62
|
+
user: request.user?.id || cds.context?.user?.id,
|
|
63
|
+
locale: request.locale || cds.context?.locale,
|
|
64
|
+
target: request.target?.name,
|
|
65
|
+
query: request.query ? JSON.stringify(request.query) : undefined,
|
|
66
|
+
subject: request.subject ? JSON.stringify(request.subject) : undefined,
|
|
67
|
+
metadata: JSON.stringify({
|
|
68
|
+
serviceName: service.name,
|
|
69
|
+
path: request.path || request.http?.req?.path,
|
|
70
|
+
method: request.method,
|
|
71
|
+
url: request.url,
|
|
72
|
+
data: request.data,
|
|
73
|
+
params: request.params,
|
|
74
|
+
subject: request.subject,
|
|
75
|
+
query: request.query,
|
|
76
|
+
event: request.event,
|
|
77
|
+
headers: request.headers,
|
|
78
|
+
}),
|
|
79
|
+
cacheOptions: JSON.stringify(requestOptions)
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
if (await this.cache.has(key)) {
|
|
83
|
+
const latency = this.getElapsedMs(startTime);
|
|
84
|
+
|
|
85
|
+
this.statistics.recordHit(latency, key, metadata);
|
|
86
|
+
|
|
87
|
+
const wrappedValue = await this.cache.send("GET", { key });
|
|
88
|
+
return { result: wrappedValue?.value, cacheKey: key, metadata: { hit: true, latency: latency } };
|
|
89
|
+
} else {
|
|
90
|
+
const response = await service.send(request);
|
|
91
|
+
const totalLatency = this.getElapsedMs(startTime);
|
|
92
|
+
this.statistics.recordMiss(totalLatency, key, metadata);
|
|
93
|
+
|
|
94
|
+
const wrappedValue = {
|
|
95
|
+
value: response,
|
|
96
|
+
tags: this.tagResolver.resolveTags(requestOptions.tags, response, { ...request.params, user: request.user?.id, tenant: request.tenant, locale: request.locale, hash: this.keyManager.createContentHash(request) }),
|
|
97
|
+
timestamp: Date.now()
|
|
98
|
+
};
|
|
99
|
+
await this.cache.send("SET", { key, value: wrappedValue, ttl: requestOptions.ttl || 0 });
|
|
100
|
+
|
|
101
|
+
return { result: response, cacheKey: key, metadata: { hit: false, latency: totalLatency } };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Run a cached operation with automatic key generation
|
|
109
|
+
* @param {object} req - the request object
|
|
110
|
+
* @param {function} handler - the handler function
|
|
111
|
+
* @param {object} options - cache options
|
|
112
|
+
* @param {object} cache - the cache instance
|
|
113
|
+
* @returns {Promise<any>} - the result
|
|
114
|
+
*/
|
|
115
|
+
async run() {
|
|
116
|
+
const arg1 = arguments[0];
|
|
117
|
+
if (typeof arg1 === "object") {
|
|
118
|
+
switch (arg1.constructor.name) {
|
|
119
|
+
case "Request":
|
|
120
|
+
case "ODataRequest":
|
|
121
|
+
case "NoaRequest":
|
|
122
|
+
const req = arg1;
|
|
123
|
+
const next = arguments[1];
|
|
124
|
+
|
|
125
|
+
if (req.query?.UPDATE || req.query?.INSERT || req.query?.DELETE) {
|
|
126
|
+
return next();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
req.cacheOptions = req.event ? this.extractFunctionCacheOptions(req, arguments[2]) : this.extractEntityCacheOptions(req, arguments[2]);
|
|
130
|
+
|
|
131
|
+
req.cacheKey = this.keyManager.createKey(req, {}, req.cacheOptions.key);
|
|
132
|
+
req.res?.setHeader('x-sap-cap-cache-key', req.cacheKey);
|
|
133
|
+
|
|
134
|
+
// Track cache operation timing
|
|
135
|
+
const startTime = process.hrtime();
|
|
136
|
+
const wrappedValue = await this.cache.send("GET", { key: req.cacheKey });
|
|
137
|
+
const cacheHit = wrappedValue?.value !== undefined;
|
|
138
|
+
const cacheLatency = this.getElapsedMs(startTime);
|
|
139
|
+
const metadata = this.extractMetadataFromRequest(req);
|
|
140
|
+
|
|
141
|
+
if (cacheHit) {
|
|
142
|
+
// Cache hit
|
|
143
|
+
this.statistics.recordHit(cacheLatency, req.cacheKey, metadata);
|
|
144
|
+
req.res?.setHeader('x-sap-cap-cache', "hit");
|
|
145
|
+
|
|
146
|
+
const wrappedValue = await this.cache.send("GET", { key: req.cacheKey });
|
|
147
|
+
return { result: wrappedValue?.value, cacheKey: req.cacheKey, metadata: { hit: true, latency: cacheLatency } };
|
|
148
|
+
} else {
|
|
149
|
+
// Cache miss - track the backend operation
|
|
150
|
+
const response = await next();
|
|
151
|
+
const totalLatency = this.getElapsedMs(startTime);
|
|
152
|
+
|
|
153
|
+
// Track the miss with total latency (cache lookup + backend operation)
|
|
154
|
+
this.statistics.recordMiss(totalLatency, req.cacheKey, metadata);
|
|
155
|
+
req.res?.setHeader('x-sap-cap-cache', "miss");
|
|
156
|
+
|
|
157
|
+
const wrappedValue = {
|
|
158
|
+
value: response,
|
|
159
|
+
tags: this.tagResolver.resolveTags(req.cacheOptions.tags, response, { ...req.params, hash: this.keyManager.createContentHash(req) }),
|
|
160
|
+
timestamp: Date.now()
|
|
161
|
+
};
|
|
162
|
+
await this.cache.send("SET", { key: req.cacheKey, value: wrappedValue, ttl: req.cacheOptions.ttl || 0 });
|
|
163
|
+
return { result: response, cacheKey: req.cacheKey, metadata: { hit: false, latency: totalLatency } };
|
|
164
|
+
}
|
|
165
|
+
case "cds.ql":
|
|
166
|
+
const query = arg1;
|
|
167
|
+
let srv = arguments[1] || cds;
|
|
168
|
+
|
|
169
|
+
if (query.SELECT) {
|
|
170
|
+
let options = {
|
|
171
|
+
ttl: 0,
|
|
172
|
+
tags: [],
|
|
173
|
+
...(arguments[2] || {}),
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
query.cacheKey = this.keyManager.createKey(query, { serviceName: srv?.name }, options.key);
|
|
177
|
+
|
|
178
|
+
// Track cache operation timing
|
|
179
|
+
const startTime = process.hrtime();
|
|
180
|
+
const hasCachedValue = await this.cache.has(query.cacheKey);
|
|
181
|
+
const cacheLatency = this.getElapsedMs(startTime);
|
|
182
|
+
const metadata = {
|
|
183
|
+
dataType: 'Query',
|
|
184
|
+
operation: 'SELECT',
|
|
185
|
+
operationType: 'READ_THROUGH',
|
|
186
|
+
user: cds.context?.user?.id,
|
|
187
|
+
tenant: cds.context?.tenant,
|
|
188
|
+
locale: cds.context?.locale,
|
|
189
|
+
query: JSON.stringify(query.SELECT),
|
|
190
|
+
metadata: JSON.stringify({
|
|
191
|
+
query: query.SELECT,
|
|
192
|
+
user: cds.context?.user?.id,
|
|
193
|
+
tenant: cds.context?.tenant,
|
|
194
|
+
locale: cds.context?.locale,
|
|
195
|
+
serviceName: srv?.name,
|
|
196
|
+
path: query.path,
|
|
197
|
+
}),
|
|
198
|
+
cacheOptions: JSON.stringify(options)
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
if (hasCachedValue) {
|
|
202
|
+
// Cache hit
|
|
203
|
+
this.statistics.recordHit(cacheLatency, query.cacheKey, metadata);
|
|
204
|
+
|
|
205
|
+
const wrappedValue = await this.cache.send("GET", { key: query.cacheKey });
|
|
206
|
+
return { result: wrappedValue?.value, cacheKey: query.cacheKey, metadata: { hit: true, latency: cacheLatency } };
|
|
207
|
+
} else {
|
|
208
|
+
// Cache miss
|
|
209
|
+
const data = await srv.run(query);
|
|
210
|
+
const totalLatency = this.getElapsedMs(startTime);
|
|
211
|
+
|
|
212
|
+
// Track the miss with total latency (cache lookup + backend operation)
|
|
213
|
+
this.statistics.recordMiss(totalLatency, query.cacheKey, metadata);
|
|
214
|
+
|
|
215
|
+
const wrappedValue = {
|
|
216
|
+
value: data,
|
|
217
|
+
tags: this.tagResolver.resolveTags(options.tags, data, { ...query.params, hash: this.keyManager.createKey(query, { serviceName: srv?.name, template: '{hash}' }) }),
|
|
218
|
+
timestamp: Date.now()
|
|
219
|
+
};
|
|
220
|
+
await this.cache.send("SET", { key: query.cacheKey, value: wrappedValue, ttl: options.ttl || 0 });
|
|
221
|
+
return { result: data, cacheKey: query.cacheKey, metadata: { hit: false, latency: totalLatency } };
|
|
222
|
+
}
|
|
223
|
+
} else {
|
|
224
|
+
return srv.run(query);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Extract cache options from request
|
|
233
|
+
* @param {object} req - the request object
|
|
234
|
+
* @param {object} options - default options
|
|
235
|
+
* @returns {object} - cache options
|
|
236
|
+
*/
|
|
237
|
+
extractCacheOptions(req, options = {}) {
|
|
238
|
+
const extractedOptions = { ...options };
|
|
239
|
+
|
|
240
|
+
// Extract from function annotations
|
|
241
|
+
if (req.target && req.target.name) {
|
|
242
|
+
const functionOptions = this.extractFunctionCacheOptions(req, options);
|
|
243
|
+
Object.assign(extractedOptions, functionOptions);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Extract from entity annotations
|
|
247
|
+
if (req.target && req.target.name) {
|
|
248
|
+
const entityOptions = this.extractEntityCacheOptions(req, options);
|
|
249
|
+
Object.assign(extractedOptions, entityOptions);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return extractedOptions;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Extract function cache options from request
|
|
257
|
+
* @param {object} req - the request object
|
|
258
|
+
* @param {object} options - default options
|
|
259
|
+
* @returns {object} - function cache options
|
|
260
|
+
*/
|
|
261
|
+
extractFunctionCacheOptions(req, options = {}) {
|
|
262
|
+
const functionType = req.query ? 'bound' : 'unbound';
|
|
263
|
+
const functionOptions = this.cacheAnnotatedFunctions[functionType].find(f => f.name === req.event);
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
ttl: functionOptions?.['@cache.ttl'] || 0,
|
|
267
|
+
key: functionOptions?.['@cache.key'] || null,
|
|
268
|
+
tags: functionOptions?.['@cache.tags'] || [],
|
|
269
|
+
...(options || {}),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Extract entity cache options from request
|
|
275
|
+
* @param {object} req - the request object
|
|
276
|
+
* @param {object} options - default options
|
|
277
|
+
* @returns {object} - entity cache options
|
|
278
|
+
*/
|
|
279
|
+
extractEntityCacheOptions(req, options = {}) {
|
|
280
|
+
return {
|
|
281
|
+
ttl: req.target?.['@cache.ttl'] || 0,
|
|
282
|
+
key: req.target?.['@cache.key'] || null,
|
|
283
|
+
tags: req.target?.['@cache.tags'] || [],
|
|
284
|
+
...(options || {}),
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Extract metadata from request for statistics
|
|
290
|
+
* @param {object} req - the request object
|
|
291
|
+
* @returns {object} - metadata object
|
|
292
|
+
*/
|
|
293
|
+
extractMetadataFromRequest(req) {
|
|
294
|
+
|
|
295
|
+
console.log(JSON.stringify(req.http?.req, null, 2));
|
|
296
|
+
|
|
297
|
+
const metadata = {
|
|
298
|
+
dataType: req.constructor.name,
|
|
299
|
+
serviceName: req.target?.name || '',
|
|
300
|
+
operation: 'RUN',
|
|
301
|
+
operationType: 'READ_THROUGH',
|
|
302
|
+
tenant: req.tenant,
|
|
303
|
+
user: req.user?.id,
|
|
304
|
+
locale: req.locale,
|
|
305
|
+
target: req.target?.name,
|
|
306
|
+
subject: req.subject ? JSON.stringify(req.subject) : undefined,
|
|
307
|
+
query: req.query?.SELECT ? JSON.stringify(req.query?.SELECT) : undefined,
|
|
308
|
+
metadata: JSON.stringify({
|
|
309
|
+
method: req.method,
|
|
310
|
+
data: req.data,
|
|
311
|
+
params: req.params,
|
|
312
|
+
path: req.http?.req?.path,
|
|
313
|
+
url: req.http?.req?.url
|
|
314
|
+
}),
|
|
315
|
+
cacheOptions: JSON.stringify(req.cacheOptions)
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
return metadata;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Get elapsed time in milliseconds
|
|
323
|
+
* @param {[number, number]} startTime - start time from process.hrtime()
|
|
324
|
+
* @returns {number} - elapsed time in milliseconds
|
|
325
|
+
*/
|
|
326
|
+
getElapsedMs(startTime) {
|
|
327
|
+
const [seconds, nanoseconds] = process.hrtime(startTime);
|
|
328
|
+
return (seconds * 1000) + (nanoseconds / 1000000);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
module.exports = CapOperations;
|