Last active
January 29, 2025 18:13
-
-
Save blakek/51a0d457d2eca1fd9c17d43abe26a447 to your computer and use it in GitHub Desktop.
An LRU Cache for Zapier… Add to a Code step and fill in `keyToCheck` and `storageId`. I basically use this as an imperfect-but-workable solution for skipping duplicates.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* @typedef {Object} InputData | |
* @property {string} keyToCheck | |
* @property {string} storageId | |
* | |
* @typedef {string | number | boolean | null | undefined | Record<string, JSONValue> | JSONValue[]} JSONValue | |
* | |
* @typedef {Object} StoreClient | |
* @property {() => Promise<void>} clear | |
* @property {(key: string) => Promise<void>} delete | |
* @property {(keys: string[]) => Promise<void>} deleteMany | |
* @property {(key: string) => Promise<JSONValue>} get | |
* @property {(keys: string[]) => Promise<Record<string, JSONValue>>} getMany | |
* @property {(key: string, value: JSONValue) => Promise<void>} set | |
* @property {(keyValues: Record<string, JSONValue>) => Promise<void>} setMany | |
*/ | |
/** @type {InputData} */ | |
// var inputData; | |
/** | |
* Keeps a list of recently used keys with a limit that expires the least recently used keys first. | |
* | |
* Storage format: | |
* { | |
* "__lru_cache__": { | |
* "key1": "2021-01-01T00:00:00.000Z", | |
* "key2": "2021-01-01T00:00:00.000Z" | |
* } | |
* } | |
*/ | |
class LRUCache { | |
/** @type {number} - The maximum number of entries to keep in the cache */ | |
maxEntries; | |
/** @type {Map<string, Date>} */ | |
cache = new Map(); | |
/** @type {StoreClient} */ | |
storage; | |
/** @type {"__lru_cache__"} */ | |
storagePrefix = "__lru_cache__"; | |
/** @type {StoreClient["get"] | null} */ | |
loadPromise = null; | |
constructor(storageId, { maxEntries = 250 } = {}) { | |
this.maxEntries = maxEntries; | |
this.storage = StoreClient(storageId); | |
// Start loading the cache from storage | |
this._loadCache(); | |
} | |
async has(key) { | |
await this._ensureLoaded(); | |
const hasKey = this.cache.has(key); | |
await this._touchCache(key); | |
return hasKey; | |
} | |
async _ensureLoaded() { | |
if (this.loadPromise) { | |
await this.loadPromise; | |
} | |
} | |
async _loadCache() { | |
// Don't allow multiple loads at once | |
if (this.loadPromise) { | |
return this.loadPromise; | |
} | |
this.loadPromise = this.storage.get(this.storagePrefix); | |
/** @type {Record<string, string>} */ | |
const cache = await this.loadPromise; | |
if (cache) { | |
const entries = Object.entries(cache); | |
for (const [key, value] of entries) { | |
this.cache.set(key, new Date(value)); | |
} | |
} | |
this.loadPromise = null; | |
} | |
async _saveCache() { | |
// Sort by date, limit to maxEntries, and convert to object | |
const entries = [...this.cache.entries()] | |
.sort(([, date1], [, date2]) => date2 - date1) | |
.slice(0, this.maxEntries) | |
.map(([key, date]) => [key, date.toISOString()]); | |
const cache = Object.fromEntries(entries); | |
await this.storage.set(this.storagePrefix, cache); | |
await this._loadCache(); | |
} | |
async _touchCache(key) { | |
this.cache.set(key, new Date()); | |
await this._saveCache(); | |
} | |
} | |
const cache = new LRUCache(inputData.storageId); | |
const isInCache = await cache.has(inputData.keyToCheck); | |
return { | |
isInCache, | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment