@63klabs/cache-data 1.3.5 → 1.3.7
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/CHANGELOG.md +49 -2
- package/CONTRIBUTING.md +167 -0
- package/README.md +139 -27
- package/package.json +13 -5
- package/src/lib/dao-cache.js +1418 -294
- package/src/lib/dao-endpoint.js +165 -41
- package/src/lib/tools/AWS.classes.js +82 -0
- package/src/lib/tools/CachedParametersSecrets.classes.js +98 -7
- package/src/lib/tools/ClientRequest.class.js +43 -10
- package/src/lib/tools/Connections.classes.js +148 -13
- package/src/lib/tools/DebugAndLog.class.js +244 -75
- package/src/lib/tools/ImmutableObject.class.js +44 -2
- package/src/lib/tools/RequestInfo.class.js +38 -0
- package/src/lib/tools/Response.class.js +245 -81
- package/src/lib/tools/ResponseDataModel.class.js +123 -47
- package/src/lib/tools/Timer.class.js +138 -26
- package/src/lib/tools/index.js +89 -2
- package/src/lib/tools/utils.js +40 -4
- package/src/lib/utils/InMemoryCache.js +221 -0
package/src/lib/tools/utils.js
CHANGED
|
@@ -11,6 +11,35 @@ const printMsg = function() {
|
|
|
11
11
|
console.log("This is a message from the demo package");
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Safely clones an object, handling Promises and other non-cloneable values.
|
|
16
|
+
* If the object contains Promises, they will be converted to empty objects.
|
|
17
|
+
* Falls back to JSON.parse(JSON.stringify()) if structuredClone fails.
|
|
18
|
+
*
|
|
19
|
+
* @param {*} obj - The object to clone
|
|
20
|
+
* @returns {*} A deep clone of the object
|
|
21
|
+
*/
|
|
22
|
+
const safeClone = function(obj) {
|
|
23
|
+
if (obj === null || typeof obj !== 'object') {
|
|
24
|
+
return obj;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
// Try structuredClone first (fastest for most cases)
|
|
29
|
+
return structuredClone(obj);
|
|
30
|
+
} catch (e) {
|
|
31
|
+
// If structuredClone fails (e.g., due to Promises), fall back to JSON pattern
|
|
32
|
+
// This will convert Promises to empty objects, but won't throw
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(JSON.stringify(obj));
|
|
35
|
+
} catch (jsonError) {
|
|
36
|
+
// If even JSON fails, return the original object
|
|
37
|
+
// This handles circular references and other edge cases
|
|
38
|
+
return obj;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
14
43
|
/**
|
|
15
44
|
* Given a secret string, returns a string padded out at the beginning
|
|
16
45
|
* with * or passed character leaving only the specified number of characters unobfuscated.
|
|
@@ -227,6 +256,9 @@ const sanitize = function (obj) {
|
|
|
227
256
|
*/
|
|
228
257
|
const hashThisData = function(algorithm, data, options = {}) {
|
|
229
258
|
|
|
259
|
+
// Clone options to avoid modifying the original
|
|
260
|
+
options = safeClone(options);
|
|
261
|
+
|
|
230
262
|
// set default values for options
|
|
231
263
|
if ( !( "salt" in options) ) { options.salt = ""; }
|
|
232
264
|
if ( !( "iterations" in options) || options.iterations < 1 ) { options.iterations = 1; }
|
|
@@ -234,7 +266,8 @@ const hashThisData = function(algorithm, data, options = {}) {
|
|
|
234
266
|
|
|
235
267
|
// if it is an object or array, then parse it to remove non-data elements (functions, etc)
|
|
236
268
|
if ( !options.skipParse && (typeof data === "object" || Array.isArray(data))) {
|
|
237
|
-
data
|
|
269
|
+
// Normalize the data structure first using JSON.stringify with custom replacer
|
|
270
|
+
const normalized = JSON.parse(JSON.stringify(data, (key, value) => {
|
|
238
271
|
switch (typeof value) {
|
|
239
272
|
case 'bigint':
|
|
240
273
|
return value.toString();
|
|
@@ -244,6 +277,8 @@ const hashThisData = function(algorithm, data, options = {}) {
|
|
|
244
277
|
return value;
|
|
245
278
|
}
|
|
246
279
|
}));
|
|
280
|
+
// Then clone for safety
|
|
281
|
+
data = safeClone(normalized);
|
|
247
282
|
options.skipParse = true; // set to true so we don't parse during recursion
|
|
248
283
|
}
|
|
249
284
|
|
|
@@ -272,7 +307,7 @@ const hashThisData = function(algorithm, data, options = {}) {
|
|
|
272
307
|
// iterate through the keys alphabetically and add the key and value to the arrayOfStuff
|
|
273
308
|
keys.forEach((key) => {
|
|
274
309
|
// clone options
|
|
275
|
-
const opts =
|
|
310
|
+
const opts = safeClone(options);
|
|
276
311
|
opts.iterations = 1; // don't iterate during recursion, only at end
|
|
277
312
|
|
|
278
313
|
const value = hashThisData(algorithm, data[key], opts);
|
|
@@ -285,11 +320,11 @@ const hashThisData = function(algorithm, data, options = {}) {
|
|
|
285
320
|
valueStr = `-:::${dataType}:::${data.toString()}`;
|
|
286
321
|
}
|
|
287
322
|
|
|
288
|
-
const hash = crypto.createHash(algorithm);
|
|
289
323
|
let hashOfData = "";
|
|
290
324
|
|
|
291
325
|
// hash for the number of iterations
|
|
292
326
|
for (let i = 0; i < options.iterations; i++) {
|
|
327
|
+
const hash = crypto.createHash(algorithm);
|
|
293
328
|
hash.update(valueStr + hashOfData + options.salt);
|
|
294
329
|
hashOfData = hash.digest('hex');
|
|
295
330
|
}
|
|
@@ -301,5 +336,6 @@ module.exports = {
|
|
|
301
336
|
printMsg,
|
|
302
337
|
sanitize,
|
|
303
338
|
obfuscate,
|
|
304
|
-
hashThisData
|
|
339
|
+
hashThisData,
|
|
340
|
+
safeClone
|
|
305
341
|
};
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InMemoryCache - Ultra-fast in-memory L0 cache for AWS Lambda
|
|
3
|
+
*
|
|
4
|
+
* Provides microsecond-level cache access using JavaScript Map with LRU eviction.
|
|
5
|
+
* Designed for Lambda execution model with synchronous operations and no background processes.
|
|
6
|
+
* @example
|
|
7
|
+
* // Create cache with automatic sizing based on Lambda memory
|
|
8
|
+
* const cache = new InMemoryCache();
|
|
9
|
+
*
|
|
10
|
+
* // Store data with expiration
|
|
11
|
+
* const data = { user: 'john', email: 'john@example.com' };
|
|
12
|
+
* const expiresAt = Date.now() + (5 * 60 * 1000); // 5 minutes
|
|
13
|
+
* cache.set('user:123', data, expiresAt);
|
|
14
|
+
*
|
|
15
|
+
* // Retrieve data
|
|
16
|
+
* const result = cache.get('user:123');
|
|
17
|
+
* if (result.cache === 1) {
|
|
18
|
+
* console.log('Cache hit:', result.data);
|
|
19
|
+
* }
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // Create cache with explicit max entries
|
|
23
|
+
* const cache = new InMemoryCache({ maxEntries: 10000 });
|
|
24
|
+
*
|
|
25
|
+
* // Check cache info
|
|
26
|
+
* const info = cache.info();
|
|
27
|
+
* console.log(`Cache size: ${info.size}/${info.maxEntries}`);
|
|
28
|
+
* console.log(`Memory: ${info.memoryMB}MB`);
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* // Use with Cache.init() for in-memory caching
|
|
32
|
+
* Cache.init({
|
|
33
|
+
* useInMemoryCache: true,
|
|
34
|
+
* inMemoryCacheMaxEntries: 5000
|
|
35
|
+
* });
|
|
36
|
+
*
|
|
37
|
+
* // Cache automatically uses InMemoryCache as L0 cache
|
|
38
|
+
* const cacheInstance = new Cache(connection, profile);
|
|
39
|
+
* const data = await cacheInstance.read(); // Checks in-memory first
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* // Clear cache when needed
|
|
43
|
+
* cache.clear();
|
|
44
|
+
* console.log('Cache cleared');
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
class InMemoryCache {
|
|
48
|
+
#cache;
|
|
49
|
+
#maxEntries;
|
|
50
|
+
#memoryMB;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Creates a new InMemoryCache instance
|
|
54
|
+
*
|
|
55
|
+
* @param {Object} options - Configuration options
|
|
56
|
+
* @param {number} [options.maxEntries] - Maximum number of entries (overrides calculation)
|
|
57
|
+
* @param {number} [options.entriesPerGB=5000] - Heuristic for calculating maxEntries from memory
|
|
58
|
+
* @param {number} [options.defaultMaxEntries=1000] - Fallback if Lambda memory unavailable
|
|
59
|
+
*/
|
|
60
|
+
constructor(options = {}) {
|
|
61
|
+
const {
|
|
62
|
+
maxEntries,
|
|
63
|
+
entriesPerGB = 5000,
|
|
64
|
+
defaultMaxEntries = 1000
|
|
65
|
+
} = options;
|
|
66
|
+
|
|
67
|
+
// Initialize Map storage
|
|
68
|
+
this.#cache = new Map();
|
|
69
|
+
|
|
70
|
+
// Determine MAX_ENTRIES
|
|
71
|
+
if (maxEntries !== undefined && maxEntries !== null) {
|
|
72
|
+
// Use explicit maxEntries parameter
|
|
73
|
+
this.#maxEntries = maxEntries;
|
|
74
|
+
this.#memoryMB = null;
|
|
75
|
+
} else {
|
|
76
|
+
// Calculate from Lambda memory allocation
|
|
77
|
+
const lambdaMemory = process.env.AWS_LAMBDA_FUNCTION_MEMORY_SIZE;
|
|
78
|
+
|
|
79
|
+
if (lambdaMemory !== undefined && lambdaMemory !== null) {
|
|
80
|
+
this.#memoryMB = parseInt(lambdaMemory, 10);
|
|
81
|
+
|
|
82
|
+
if (!isNaN(this.#memoryMB) && this.#memoryMB > 0) {
|
|
83
|
+
// Calculate: (memoryMB / 1024) * entriesPerGB
|
|
84
|
+
this.#maxEntries = Math.floor((this.#memoryMB / 1024) * entriesPerGB);
|
|
85
|
+
} else {
|
|
86
|
+
// Invalid memory value, use default
|
|
87
|
+
this.#maxEntries = defaultMaxEntries;
|
|
88
|
+
this.#memoryMB = null;
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
// Lambda memory not available, use default
|
|
92
|
+
this.#maxEntries = defaultMaxEntries;
|
|
93
|
+
this.#memoryMB = null;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Ensure maxEntries is at least 1
|
|
98
|
+
if (this.#maxEntries < 1) {
|
|
99
|
+
this.#maxEntries = 1;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Retrieves a cache entry by key
|
|
105
|
+
*
|
|
106
|
+
* Returns an object containing the cache status and the cached data (if available).
|
|
107
|
+
* The cache status indicates whether the lookup was a hit (1), miss (0), or expired (-1).
|
|
108
|
+
* For cache hits, the data property contains the stored CacheDataFormat object. For misses, data is null.
|
|
109
|
+
* For expired entries, data contains the expired CacheDataFormat object before it's removed from cache.
|
|
110
|
+
*
|
|
111
|
+
* @param {string} key - Cache key to look up
|
|
112
|
+
* @returns {{cache: number, data: Object|null}} Lookup result with cache status and data
|
|
113
|
+
* @returns {number} return.cache - Cache status: 1 (hit), 0 (miss), or -1 (expired)
|
|
114
|
+
* @returns {Object|null} return.data - The cached CacheDataFormat object on hit/expired, or null on miss
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* // Cache hit
|
|
118
|
+
* cache.get('myKey')
|
|
119
|
+
* // => { cache: 1, data: { cache: { body: '...', headers: {...}, expires: 1234567890, statusCode: '200' } } }
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* // Cache miss
|
|
123
|
+
* cache.get('nonExistent') // => { cache: 0, data: null }
|
|
124
|
+
*
|
|
125
|
+
* @example
|
|
126
|
+
* // Expired entry
|
|
127
|
+
* cache.get('expiredKey')
|
|
128
|
+
* // => { cache: -1, data: { cache: { body: '...', headers: {...}, expires: 1234567890, statusCode: '200' } } }
|
|
129
|
+
*/
|
|
130
|
+
get(key) {
|
|
131
|
+
// Check if key exists
|
|
132
|
+
if (!this.#cache.has(key)) {
|
|
133
|
+
return { cache: 0, data: null };
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const entry = this.#cache.get(key);
|
|
137
|
+
const now = Date.now();
|
|
138
|
+
|
|
139
|
+
// Check if expired
|
|
140
|
+
if (entry.expiresAt <= now) {
|
|
141
|
+
// Delete expired entry
|
|
142
|
+
this.#cache.delete(key);
|
|
143
|
+
return { cache: -1, data: entry.value };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Valid entry - update LRU position by deleting and re-setting
|
|
147
|
+
this.#cache.delete(key);
|
|
148
|
+
this.#cache.set(key, entry);
|
|
149
|
+
|
|
150
|
+
return { cache: 1, data: entry.value };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Stores a cache entry with expiration
|
|
155
|
+
*
|
|
156
|
+
* Adds or updates a cache entry with the specified key, value, and expiration time.
|
|
157
|
+
* If the key already exists, it will be updated and moved to the most recent position (LRU).
|
|
158
|
+
* If the cache is at maximum capacity, the least recently used entry will be evicted automatically.
|
|
159
|
+
* The expiresAt timestamp should be in milliseconds since epoch (e.g., Date.now() + ttl).
|
|
160
|
+
*
|
|
161
|
+
* @param {string} key - Cache key to store the value under
|
|
162
|
+
* @param {Object} value - CacheDataFormat object or any data structure to cache
|
|
163
|
+
* @param {number} expiresAt - Expiration timestamp in milliseconds since epoch (e.g., Date.now() + 60000 for 1 minute)
|
|
164
|
+
* @returns {void}
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* // Store a cache entry that expires in 5 minutes
|
|
168
|
+
* const fiveMinutes = 5 * 60 * 1000;
|
|
169
|
+
* cache.set('myKey', { cache: { body: 'response data', headers: {}, expires: Date.now() + fiveMinutes, statusCode: '200' } }, Date.now() + fiveMinutes);
|
|
170
|
+
*
|
|
171
|
+
* @example
|
|
172
|
+
* // Store with a specific expiration timestamp
|
|
173
|
+
* const expirationTime = Date.now() + (60 * 60 * 1000); // 1 hour from now
|
|
174
|
+
* cache.set('sessionData', { userId: 123, token: 'abc' }, expirationTime);
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* // Update an existing entry (moves to most recent position)
|
|
178
|
+
* cache.set('existingKey', { updated: 'data' }, Date.now() + 300000);
|
|
179
|
+
*/
|
|
180
|
+
set(key, value, expiresAt) {
|
|
181
|
+
// If key exists, delete it first for LRU repositioning
|
|
182
|
+
if (this.#cache.has(key)) {
|
|
183
|
+
this.#cache.delete(key);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Check capacity and evict if necessary
|
|
187
|
+
if (this.#cache.size >= this.#maxEntries) {
|
|
188
|
+
// Get first (oldest) entry
|
|
189
|
+
const oldestKey = this.#cache.keys().next().value;
|
|
190
|
+
this.#cache.delete(oldestKey);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Store new entry
|
|
194
|
+
this.#cache.set(key, { value, expiresAt });
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Clears all entries from the cache
|
|
199
|
+
*/
|
|
200
|
+
clear() {
|
|
201
|
+
this.#cache.clear();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Returns information about the cache state
|
|
206
|
+
*
|
|
207
|
+
* @returns {Object} Cache information
|
|
208
|
+
* @returns {number} return.size - Current number of entries
|
|
209
|
+
* @returns {number} return.maxEntries - Maximum capacity
|
|
210
|
+
* @returns {number|null} return.memoryMB - Lambda memory allocation (if available)
|
|
211
|
+
*/
|
|
212
|
+
info() {
|
|
213
|
+
return {
|
|
214
|
+
size: this.#cache.size,
|
|
215
|
+
maxEntries: this.#maxEntries,
|
|
216
|
+
memoryMB: this.#memoryMB
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
module.exports = InMemoryCache;
|