@kuflow/kuflow-temporal-worker
Version:
Worker library used by KuFlow SDKs and Temporal.
217 lines • 8.06 kB
JavaScript
"use strict";
/**
* The MIT License
* Copyright © 2021-present KuFlow S.L.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.CacheBuilder = exports.Cache = void 0;
const NOO_OP = () => { }; // eslint-disable-line @typescript-eslint/no-empty-function
class Cache {
cache = new Map();
expireAfterAccess;
expireAfterWrite;
removalListener;
// Track ongoing loader operations for keys
inProgressLoads = new Map();
constructor(opts) {
this.expireAfterAccess = opts.expireAfterAccess;
this.expireAfterWrite = opts.expireAfterWrite;
this.removalListener = opts.removalListener;
}
/**
* Retrieve a value from the cache, or load it using the provided loader if absent or expired.
* Automatically refreshes the TTL upon access.
* @param key - The key of the value to retrieve.
* @param loader - A loader function to fetch the value if it's not in cache.
* @returns The value associated with the key.
*/
async get(key, loader) {
const cached = this.cache.get(key);
// Check if the value exists and is not expired
if (cached != null && this.expireAfterAccess > 0 && (cached.expiresAt == null || cached.expiresAt > Date.now())) {
// Reset the TTL since it's being accessed
this.createOrUpdateCacheEntry(key, cached.value, this.expireAfterAccess);
return cached.value;
}
// If the value is not in the cache, check if there is already a loader in progress
let loaderPromise = this.inProgressLoads.get(key);
if (loaderPromise != null) {
// Wait for the existing loader to complete
return await loaderPromise;
}
// Otherwise, start a new load operation
loaderPromise = (async () => {
try {
const value = await loader();
// Store the value in the cache
this.createOrUpdateCacheEntry(key, value, this.expireAfterAccess);
return value;
}
finally {
// Ensure the ongoing load is removed once complete
this.inProgressLoads.delete(key);
}
})();
// Save the loader promise to the in-progress map
this.inProgressLoads.set(key, loaderPromise);
// Return the promise for the current load operation
return await loaderPromise;
}
/**
* Add a key-value pair to the cache with an expiration time.
* @param key - The key to store.
* @param value - The value to store.
*/
put(key, value) {
this.createOrUpdateCacheEntry(key, value, this.expireAfterWrite);
}
/**
* Invalidate a specific key from the cache.
* @param key - The key to remove.
*/
invalidate(key) {
this.deleteCacheEntry(key);
}
/**
* Invalidate all items from the cache.
*/
invalidateAll() {
for (const [, cached] of this.cache.entries()) {
this.clearTimeoutCacheEntry(cached);
}
this.cache.clear();
}
/**
* Create or Update the cache cached entry.
* @param key - The key of the entry.
* @param value - The value of the entry.
* @param ttl - The ttl of the entry.
*/
createOrUpdateCacheEntry(key, value, ttl) {
let expiresAt = undefined;
let timeoutId = undefined;
// Clear any existing timeout for this key
const cached = this.cache.get(key);
this.clearTimeoutCacheEntry(cached);
if (ttl > 0) {
expiresAt = Date.now() + ttl;
// Schedule the item for automatic removal after the TTL expires
timeoutId = setTimeout(() => {
this.deleteCacheEntry(key);
}, ttl);
}
// Update or insert the new entry in the cache
this.cache.set(key, { value, expiresAt, timeoutId });
}
/**
* Deletes a cache entry identified by the specified key.
* This will also clear any associated timeout for the cache entry.
*
* @param key - The key of the cache entry to be deleted.
* @return No return value.
*/
deleteCacheEntry(key) {
const cached = this.cache.get(key);
if (cached == null) {
return;
}
this.clearTimeoutCacheEntry(cached);
this.cache.delete(key);
this.removalListener(key);
}
clearTimeoutCacheEntry(cached) {
if (cached?.timeoutId != null) {
clearTimeout(cached.timeoutId);
}
}
}
exports.Cache = Cache;
class CacheBuilder {
expireAfterAccess = 0; // Default TTL is 0 (disabled by default)
expireAfterWrite = 0; // Default TTL is 0 (disabled by default)
removalListener = NOO_OP;
static builder() {
return new CacheBuilder();
}
/**
* Set the expiration time for the cache after accessing the element.
* @param time - The time-to-live duration.
* @param unit - The time unit (such as milliseconds, seconds, minutes, etc.).
*/
withExpireAfterAccess(time, unit) {
this.expireAfterAccess = this.toMillis(time, unit);
return this;
}
/**
* Set the expiration time for the cache after write the element.
* @param time - The time-to-live duration.
* @param unit - The time unit (such as milliseconds, seconds, minutes, etc.).
*/
withExpireAfterWrite(time, unit) {
this.expireAfterWrite = this.toMillis(time, unit);
return this;
}
/**
* Sets a removal listener function to be called when an entry is removed.
*
* @param {Function} removalListener - A function that gets called with the key of the removed entry.
* @return {this} Returns the current instance to allow method chaining.
*/
withRemovalListener(removalListener) {
this.removalListener = removalListener;
return this;
}
toMillis(time, unit) {
if (time < 0) {
throw new Error('Time must be greater than 0');
}
const multiplier = this.multiplier(unit);
return time * multiplier;
}
multiplier(unit) {
switch (unit) {
case 'hour':
case 'hours':
return 60 * 60 * 1000;
case 'minute':
case 'minutes':
return 60 * 1000;
case 'second':
case 'seconds':
return 1000;
case 'millisecond':
case 'milliseconds':
return 1;
}
}
/**
* Build and return the Cache instance configured with the specified options.
*/
build() {
return new Cache({
expireAfterAccess: this.expireAfterAccess,
expireAfterWrite: this.expireAfterWrite,
removalListener: this.removalListener,
});
}
}
exports.CacheBuilder = CacheBuilder;
//# sourceMappingURL=kuflow-cache.js.map