New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

cached-lookup

Package Overview
Dependencies
Maintainers
1
Versions
26
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

cached-lookup - npm Package Compare versions

Comparing version 3.0.3 to 4.0.0

205

index.js

@@ -0,10 +1,28 @@

/**
* @template T
*/
class CachedLookup {
#lookup;
#value;
#promise;
#updated_at = 0;
#cache = {};
#promises = {};
/**
* @typedef {Boolean|Number|String} SupportedArgumentTypes
*/
/**
* @typedef {Object} ValueRecord
* @property {T} value
* @property {Number} updated_at
*/
/**
* Creates a new CachedLookup instance with the specified lookup function.
* The lookup function can be both synchronous or asynchronous.
*
* @param {function(...(SupportedArgumentTypes|Array<SupportedArgumentTypes>)):T} lookup
*/
constructor(lookup) {
// Ensure lookup is a function type
if (typeof lookup !== 'function') throw new Error('new CachedLookup(lookup) -> lookup must be a Function');
if (typeof lookup !== 'function') throw new Error('new CachedLookup(lookup) -> lookup must be a Function.');
this.#lookup = lookup;

@@ -14,102 +32,175 @@ }

/**
* Returns whether current cache value is valid or not based on provided age in milliseconds.
* Returns an identifier string based on the provided array of arguments.
*
* @private
* @param {Number} max_age In Milliseconds
* @returns {Boolean}
* @param {Array<SupportedArgumentTypes>} args
* @returns {String}
*/
_is_cache_valid(max_age) {
return this.#updated_at + max_age > Date.now();
_arguments_to_identifier(args) {
return args.join('');
}
/**
* Cached the provided value and renews the cache availability.
* Retrieves the cached value record for the provided set of arguments.
*
* @private
* @param {Array<SupportedArgumentTypes>} args
* @param {Number} max_age
* @returns {ValueRecord=}
*/
_cache_value(value) {
this.#value = value;
this.#promise = undefined;
this.#updated_at = Date.now();
_get_cache(args, max_age) {
// Retrieve the identifier string for the provided arguments
const identifier = this._arguments_to_identifier(args);
// Attempt to lookup the value record for the specified arguments
const record = this.#cache[identifier];
// Return the value record if it exists and is not older than the specified maximum age
if (record && record.updated_at > Date.now() - max_age) return record;
}
/**
* Returns cached value if the cached value is not older than the specified maximum age in milliseconds.
* This method automatically retrieves a fresh value if the cached value is older than the specified maximum age.
* Sets the cached value record for the provided set of arguments and value.
*
* @param {Number} max_age In Milliseconds
* @returns {Promise}
* @private
* @param {Array<SupportedArgumentTypes>} args
* @param {T} value
*/
cached(max_age) {
// Ensure max_age is a valid greater than zero number
if (typeof max_age !== 'number' || max_age < 0)
throw new Error('CachedLookup.cached(max_age) -> max_age must be a number that is greater than zero.');
_set_cache(args, value) {
// Retrieve the identifier string for the provided arguments
const identifier = this._arguments_to_identifier(args);
// Return value from cache if it is still valid
if (this._is_cache_valid(max_age)) return Promise.resolve(this.#value);
// Initialize the record structure for the specified arguments if it does not exist
if (!this.#cache[identifier])
this.#cache[identifier] = {
value: null,
updated_at: null,
};
// Initiate a lookup for a fresh value
return this.fresh();
// Fill the record values with the provided value and current timestamp
this.#cache[identifier].value = value;
this.#cache[identifier].updated_at = Date.now();
}
/**
* Fetches a fresh value from the lookup handler and returns result.
* @returns {Promise}
* Retrieves a fresh value from the lookup and returns result.
*
* @private
* @param {Array<SupportedArgumentTypes>} args
* @returns {Promise<T>}
*/
fresh() {
// Return a pending promise if one exists for an in flight lookup
if (this.#promise) return this.#promise;
_get_fresh_value(args) {
// Retrieve the identifier string for the provided arguments
const identifier = this._arguments_to_identifier(args);
// Create a new promise for the lookup operation and cache it locally
const scope = this;
this.#promise = new Promise(async (resolve, reject) => {
// Safely execute lookup handler to retrieve an output value
let output;
// Resolve an existing promise for the specified arguments if it exists
if (this.#promises[identifier]) return this.#promises[identifier];
// Initialize a new promise which will be resolved when the value is resolved
const reference = this;
this.#promises[identifier] = new Promise(async (resolve, reject) => {
// Attempt to resolve the value for the specified arguments from the lookup
let value;
try {
output = await scope.#lookup();
value = await reference.#lookup(...args);
} catch (error) {
// Reject the error from the lookup operation
return reject(error);
}
// Cache the output value and resolve the promise
scope._cache_value(output);
resolve(output);
// Store the resolved value in the cache for the specified arguments
reference._set_cache(args, value);
// Resolve the promise with the resolved value
resolve(value);
// Cleanup the promise for the specified arguments
delete reference.#promises[identifier];
});
return this.#promise;
// Return the promise to the caller
return this.#promises[identifier];
}
/**
* Expires the current cached value marking the instance to retrieve a fresh value on next call.
* Returns a cached value that is up to max_age milliseconds old for the provided set of arguments.
* Falls back to a fresh value if the cache value is older than max_age.
*
* @param {Number} max_age In Milliseconds
* @param {...(SupportedArgumentTypes|Array<SupportedArgumentTypes>)} args
* @returns {Promise<T>}
*/
expire() {
this.#updated_at = 0;
cached(max_age, ...args) {
// Ensure max_age is a valid greater than zero number
if (typeof max_age !== 'number' || max_age < 0)
throw new Error('CachedLookup.cached(max_age) -> max_age must be a number that is greater than zero.');
// Retrieve a serialized Array of arguments ignoring the first argument (max_age)
const serialized = Array.from(arguments).slice(1);
// Attempt to resolve the cached value from the cached value record
const record = this._get_cache(serialized, max_age);
if (record) return Promise.resolve(record.value);
// Resolve the fresh value for the provided arguments in array serialization
return this._get_fresh_value(serialized);
}
/* CachedLookup Getters */
/**
* Returns most recently cached value for this lookup instance.
* Returns a fresh value and automatically updates the internal cache with this value for the provided set of arguments.
*
* @param {...(SupportedArgumentTypes|Array<SupportedArgumentTypes>)} args
* @returns {Promise<T>}
*/
get value() {
return this.#value;
fresh(...args) {
// Serialize the arguments into an Array
const serialized = Array.from(arguments);
// Resolve the fresh value for the provided serialized arguments
return this._get_fresh_value(serialized);
}
/**
* Returns the milliseconds unix timestamp of the last cached value update.
* @returns {Number}
* Expires the cached value for the provided set of arguments.
*
* @param {...(SupportedArgumentTypes|Array<SupportedArgumentTypes>)} args
* @returns {Boolean} True if the cache value was expired, false otherwise.
*/
get updated_at() {
return this.#updated_at;
expire(...args) {
// Retrieve the identifier string for the provided arguments
const identifier = this._arguments_to_identifier(Array.from(arguments));
// Remove the cached value record for the specified arguments
if (this.#cache[identifier]) {
delete this.#cache[identifier];
return true;
}
return false;
}
/**
* Returns whether this instance is currently fetching a fresh value.
* Returns whether a fresh value is currently being resolved for the provided set of arguments.
*
* @param {...(SupportedArgumentTypes|Array<SupportedArgumentTypes>)} args
* @returns {Boolean}
*/
get in_flight() {
return this.#promise instanceof Promise;
in_flight(...args) {
// Retrieve the identifier string for the provided arguments
const identifier = this._arguments_to_identifier(Array.from(arguments));
// Return true if there is a promise for the specified arguments
return this.#promises[identifier] !== undefined;
}
/* CachedLookup Getters */
/**
* Returns the underlying cache object which contains the cached values identified by their serialized arguments.
*
* @returns {Map<String, ValueRecord>}
*/
get cache() {
return this.#cache;
}
}
module.exports = CachedLookup;
{
"name": "cached-lookup",
"version": "3.0.3",
"version": "4.0.0",
"description": "A Simple Package To Cache And Save On Expensive Lookups & Operations.",

@@ -5,0 +5,0 @@ "main": "index.js",

@@ -20,4 +20,4 @@ # CachedLookup: A Simple Package To Cache And Save On Expensive Lookups & Operations.

- TypeScript Support
- Asynchronous By Nature
- Customizable Cache Lifetime
- Parameter Based Caching
- Dynamic Cache Consumption

@@ -39,20 +39,27 @@ - Extremely Lightweight

// Create an instance that caches for 5 seconds
// This will ensure, new data is only fetched every 5 seconds
const CurrencyLookup = new CachedLookup(async () => {
// Hit some third-party API to retrieve fresh currency data
const result = await get_currency_values();
// Create a cached lookup instance which fetches music concerts from different cities on a specific date
const ConcertsLookup = new CachedLookup(async (city) => {
// Assume that the function get_city_concerts() is calling a Third-Party API which has a rate limit
const concerts = await get_city_concerts(city);
// Perform some local parsing of the resolved data
const parsed = parse_data(result);
// Return parsed data for cached-lookup to cache and serve instantly for the next 5 seconds
return parsed;
// Simply return the data and CachedLookup will handle the rest
return concerts;
});
// Some webserver route utilizing the CachedLookup instance to serve currency data
webserver.get('/api/currency', async (request, response) => {
// This will return the cached value for 5 seconds before retrieving a fresh value
const data = await CurrencyLookup.cached(5000);
return response.send(data);
// Create some route which serves this data with a 10 second intermittent cache
webserver.get('/api/concerts/:city', async (request, response) => {
// Retrieve the city value from the request - assume there is user validation done on this here
const city = request.path_parameters.city;
// Retrieve data from the CachedLookup with the cached() and pass the city in the call to the lookup handler
// Be sure to specify the first parameter as the max_age of the cached value in milliseconds
// In our case, 10 seconds would be 10,000 milliseconds
const concerts = await ConcertsLookup.cached(1000 * 10, city);
// Simply return the data to the user
// Because we retrieved this data from the ConcertsLookup with the cached() method
// We can safely assume that we will only perform up to 1 Third-Party API request per city every 10 seconds
return response.json({
concerts
});
});

@@ -66,5 +73,6 @@ ```

* `new CachedLookup(Function: lookup)`: Creates a new CachedLookup instance.
* `lookup` [`Function`]: Lookup handler which is invocated to get fresh results.
* `lookup` [`Function`]: Lookup handler which is called to get fresh values.
* **Note!** this callback can be either `synchronous` or `asynchronous`.
* **Note!** you must return/resolve a value through this callback for the caching to work properly.
* **Note!** arguments passed to the methods below will be available in each call to this handler.

@@ -74,16 +82,20 @@ #### CachedLookup Properties

| :-------- | :------- | :------------------------- |
| `value` | `Any` | Most recent cached value |
| `updated_at` | `Number` | Unix timestamp in milliseconds of latest update |
| `in_flight` | `Boolean` | Whether instance is currently looking up a fresh value |
| `cached` | `Object` | Internal cache store used to cache values. |
#### CachedLookup Methods
* `cached(Number: max_age)`: Consumes cached value with a fallback to fresh value if cached value has expired.
* **Returns** a `Promise` which is then resolved to the lookup value.
* `cached(Number: max_age, ...arguments)`: Returns the cached value for the provided set of arguments from the lookup handler.
* **Returns** a `Promise` which is then resolved to the lookup resolved value.
* **Note** parameter `max_age` should be a `Number` in `milliseconds` to specify the maximum acceptable cache age.
* **Note** this method will reject when the lookup handler also rejects.
* `fresh()`: Retrieves fresh value from the lookup handler.
* **Returns** a `Promise` which is then resolved to the lookup value.
* `expire()`: Expires current cached value marking instance to fetch fresh value on next `cached()` invocation.
* **Note** this method will automatically fall back to a `fresh()` call if no viable cache value is available.
* **Note** the returned `Promise` will **reject** when the lookup handler also rejects.
* **Note** the provided `arguments` in after the `max_age` will be available inside of the `lookup` handler function.
* `fresh(...arguments)`: Retrieves the fresh value for the provided set of arguments from the lookup handler.
* **Returns** a `Promise` which is then resolved to the lookup resolved value.
* `expire(...arguments)`: Expires the cached value (If one exists) for the provided set of arguments.
* **Returns** a `Boolean` which specifies whether a cache value was expired or not.
* `in_flight(...arguments)`: Checks whether a fresh value is currently being resolved for the provided set of arguments.
* **Returns** a `Boolean` to specify the result.
* **Note** the `...arguments` are **optional** for all methods above and you may call each function without any arguments to a single global value.
## License
[MIT](./LICENSE)
type LookupHandler<T extends any> = () => T | Promise<T>
type SupportedTypes = string | number | boolean;
type SupportedArgumentTypes = SupportedTypes | SupportedTypes[];
interface ValueRecord {
value: T,
updated_at: number
};
export default class CachedLookup<T extends any> {
/**
* Constructs a cached lookup with the specified lookup handler.
*
* @param lookup
* Creates a new CachedLookup instance with the specified lookup function.
* The lookup function can be both synchronous or asynchronous.
*
* @param {function(...(SupportedArgumentTypes|Array<SupportedArgumentTypes>)):T} lookup
*/

@@ -13,39 +20,43 @@ constructor(lookup: LookupHandler<T>)

/**
* Returns cached value if the cached value is not older than the specified maximum age in milliseconds.
* This method automatically retrieves a fresh value if the cached value is older than the specified maximum age.
*
* Returns a cached value that is up to max_age milliseconds old for the provided set of arguments.
* Falls back to a fresh value if the cache value is older than max_age.
*
* @param {Number} max_age In Milliseconds
* @returns {Promise}
* @param {...(SupportedArgumentTypes)} args
* @returns {Promise<T>}
*/
cached(max_age: number): Promise<T>;
cached(max_age: number, ...args: SupportedArgumentTypes): Promise<T>;
/**
* Fetches a fresh value from the lookup handler and returns result.
* @returns {Promise}
* Returns a fresh value and automatically updates the internal cache with this value for the provided set of arguments.
*
* @param {...(SupportedArgumentTypes)} args
* @returns {Promise<T>}
*/
fresh(): Promise<T>;
fresh(...args: SupportedArgumentTypes): Promise<T>;
/**
* Expires the current cached value marking the instance to retrieve a fresh value on next call.
* Expires the cached value for the provided set of arguments.
*
* @param {...(SupportedArgumentTypes)} args
* @returns {Boolean} True if the cache value was expired, false otherwise.
*/
expire(): void;
expire(...args: SupportedArgumentTypes): bool;
/* CachedLookup Getters */
/**
* Returns most recently cached value for this lookup instance.
* Returns whether a fresh value is currently being resolved for the provided set of arguments.
*
* @param {...(SupportedArgumentTypes)} args
* @returns {Boolean}
*/
get value(): T;
in_flight(...args: SupportedArgumentTypes): bool;
/**
* Returns the milliseconds unix timestamp of the last cached value update.
* @returns {Number}
*/
get updated_at(): number;
/* CachedLookup Getters */
/**
* Returns whether this instance is currently fetching a fresh value.
* @returns {Boolean}
* Returns the underlying cache object which contains the cached values identified by their serialized arguments.
*
* @returns {Map<String, ValueRecord>}
*/
get in_flight(): boolean;
get cache(): Map<string, ValueRecord>;
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc