From 39547fcbd79e37b5c678eb8b6fc18b1b35d52431 Mon Sep 17 00:00:00 2001 From: Yaroslav Surilov Date: Thu, 30 Nov 2023 00:11:22 +0200 Subject: [PATCH] feat(infra): rework project struct, configure TSC, emit *.d.ts --- fifo-cache/{src/types.ts => index.d.ts} | 29 +++-- fifo-cache/index.js | 66 ++++++++++++ fifo-cache/{src => }/index.ts | 28 ++++- lfu-cache/index.d.ts | 1 + lfu-cache/index.js | 1 + lfu-cache/{src => }/index.ts | 0 lfu-cache/src/types.ts | 0 lru-cache/{src/types.ts => index.d.ts} | 21 ++-- lru-cache/index.js | 73 +++++++++++++ lru-cache/{src => }/index.ts | 26 ++++- rr-cache/index.d.ts | 57 ++++++++++ rr-cache/{src/index.ts => index.js} | 57 ++++------ rr-cache/index.ts | 136 ++++++++++++++++++++++++ rr-cache/src/types.ts | 41 ------- slru-cache/index.d.ts | 1 + slru-cache/index.js | 1 + slru-cache/{src => }/index.ts | 0 slru-cache/src/types.ts | 0 tsconfig.json | 20 ++-- ttl-cache-v1/index.d.ts | 6 ++ ttl-cache-v1/index.js | 33 ++++++ ttl-cache-v1/{src => }/index.ts | 0 ttl-cache-v2/index.d.ts | 6 ++ ttl-cache-v2/index.js | 33 ++++++ ttl-cache-v2/{src => }/index.ts | 0 25 files changed, 519 insertions(+), 117 deletions(-) rename fifo-cache/{src/types.ts => index.d.ts} (62%) create mode 100644 fifo-cache/index.js rename fifo-cache/{src => }/index.ts (77%) create mode 100644 lfu-cache/index.d.ts create mode 100644 lfu-cache/index.js rename lfu-cache/{src => }/index.ts (100%) delete mode 100644 lfu-cache/src/types.ts rename lru-cache/{src/types.ts => index.d.ts} (71%) create mode 100644 lru-cache/index.js rename lru-cache/{src => }/index.ts (73%) create mode 100644 rr-cache/index.d.ts rename rr-cache/{src/index.ts => index.js} (74%) create mode 100644 rr-cache/index.ts delete mode 100644 rr-cache/src/types.ts create mode 100644 slru-cache/index.d.ts create mode 100644 slru-cache/index.js rename slru-cache/{src => }/index.ts (100%) delete mode 100644 slru-cache/src/types.ts create mode 100644 ttl-cache-v1/index.d.ts create mode 100644 ttl-cache-v1/index.js rename ttl-cache-v1/{src => }/index.ts (100%) create mode 100644 ttl-cache-v2/index.d.ts create mode 100644 ttl-cache-v2/index.js rename ttl-cache-v2/{src => }/index.ts (100%) diff --git a/fifo-cache/src/types.ts b/fifo-cache/index.d.ts similarity index 62% rename from fifo-cache/src/types.ts rename to fifo-cache/index.d.ts index f04263a..48202ba 100644 --- a/fifo-cache/src/types.ts +++ b/fifo-cache/index.d.ts @@ -5,8 +5,7 @@ interface IFifoCache { store: (key: any, value: any) => void; has: (key: any) => boolean; clear: () => void; -}; - +} type TConfigOptions = { /** * Capacity means how many items can be stored at the same time in cache. @@ -15,18 +14,14 @@ type TConfigOptions = { */ capacity: number; }; - -type TNode = { - data: { - key: any, - value: any - }; - next?: TNode; -}; - - -export { - IFifoCache, - TConfigOptions, - TNode -}; \ No newline at end of file +declare class FifoCache implements IFifoCache { + #private; + constructor(options: TConfigOptions); + get size(): number; + set capacity(value: number); + read(key: any): any; + store(key: any, value: any): void; + has(key: any): boolean; + clear(): void; +} +export default FifoCache; diff --git a/fifo-cache/index.js b/fifo-cache/index.js new file mode 100644 index 0000000..d5df997 --- /dev/null +++ b/fifo-cache/index.js @@ -0,0 +1,66 @@ +; +const Node = (data, next) => ({ data, next }); +class FifoCache { + #capacity; + #store; + #map; + constructor(options) { + const { capacity } = options; + if (!Number.isInteger(capacity) || capacity <= 0) + throw new Error('invalid "capacity": positive integer expected'); + this.#capacity = capacity; + // Store is a Singly linked list to support fast add (to tail) and delete (from head) records. + this.#store = { head: null, tail: null }; + // Struct for fast access to cache records, use as lookup table. + this.#map = new Map(); + } + get size() { + return this.#map.size; + } + set capacity(value) { + if (!Number.isInteger(value) || value <= 0) + throw new Error('invalid "capacity": positive integer expected'); + if (value < this.#capacity) { + // @todo: implement + } + this.#capacity = value; + } + read(key) { + if (this.#map.has(key)) { + return this.#map.get(key)?.data.value; + } + return null; + } + store(key, value) { + // check if cache capacity limit is reached + if (this.#map.size === this.#capacity) { + // evict head since we are out of capacity + const node = this.#store.head; + this.#store.head = node.next; + this.#map.delete(node.data.key); + node.next = null; + } + const node = Node({ key, value }); + if (this.#map.size === 0) { + this.#store.head = node; + } + else if (this.#map.size === 1) { + this.#store.tail = node; + this.#store.head.next = this.#store.tail; + } + else { + this.#store.tail.next = node; + this.#store.tail = node; + } + this.#map.set(key, node); + } + has(key) { + return this.#map.has(key); + } + clear() { + this.#store.head = null; + this.#store.tail = null; + this.#map.clear(); + } +} +export default FifoCache; diff --git a/fifo-cache/src/index.ts b/fifo-cache/index.ts similarity index 77% rename from fifo-cache/src/index.ts rename to fifo-cache/index.ts index 74147c6..0ae62b5 100644 --- a/fifo-cache/src/index.ts +++ b/fifo-cache/index.ts @@ -1,4 +1,28 @@ -import { IFifoCache, TConfigOptions, TNode } from './types.js'; +interface IFifoCache { + get size(): number; + set capacity(value: number); + read: (key: any) => any; + store: (key: any, value: any) => void; + has: (key: any) => boolean; + clear: () => void; +}; + +type TConfigOptions = { + /** + * Capacity means how many items can be stored at the same time in cache. + * For FIFO cache, by definition, capacity is a required restriction, + * without it becomes almost meaningless, so this option is mandatory. + */ + capacity: number; +}; + +type TNode = { + data: { + key: any, + value: any + }; + next?: TNode; +}; const Node = (data: {key: any, value: any}, next?: TNode) => ({data, next}); @@ -20,7 +44,7 @@ class FifoCache implements IFifoCache { } get size() { - return this.#store.size; + return this.#map.size; } set capacity (value: number) { diff --git a/lfu-cache/index.d.ts b/lfu-cache/index.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/lfu-cache/index.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/lfu-cache/index.js b/lfu-cache/index.js new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/lfu-cache/index.js @@ -0,0 +1 @@ +export {}; diff --git a/lfu-cache/src/index.ts b/lfu-cache/index.ts similarity index 100% rename from lfu-cache/src/index.ts rename to lfu-cache/index.ts diff --git a/lfu-cache/src/types.ts b/lfu-cache/src/types.ts deleted file mode 100644 index e69de29..0000000 diff --git a/lru-cache/src/types.ts b/lru-cache/index.d.ts similarity index 71% rename from lru-cache/src/types.ts rename to lru-cache/index.d.ts index dfa1235..23b5aca 100644 --- a/lru-cache/src/types.ts +++ b/lru-cache/index.d.ts @@ -6,8 +6,7 @@ interface ILruCache { has: (key: any) => boolean; remove: (key: any) => void; clear: () => void; -}; - +} type TConfigOptions = { /** * Capacity means how many items can be stored at the same time in cache. @@ -23,9 +22,15 @@ type TConfigOptions = { */ checkLowMemory?: boolean; }; - - -export { - ILruCache, - TConfigOptions -}; \ No newline at end of file +declare class LruCache implements ILruCache { + #private; + constructor(options: TConfigOptions); + get size(): number; + set capacity(value: number); + read(key: any): any; + store(key: any, value: any): void; + has(key: any): boolean; + remove(key: any): void; + clear(): void; +} +export default LruCache; diff --git a/lru-cache/index.js b/lru-cache/index.js new file mode 100644 index 0000000..9d0caf2 --- /dev/null +++ b/lru-cache/index.js @@ -0,0 +1,73 @@ +; +const getTimestamp = () => Date.now(); +class LruCache { + #capacity; + #store; + #accessMap; + constructor(options) { + const { capacity, checkLowMemory } = options; + let checkLowMemoryTimer; + if (!Number.isInteger(capacity) || capacity <= 0) + throw new Error('invalid "capacity": positive integer expected'); + if (typeof checkLowMemory !== 'boolean') + throw new Error('option "checkLowMemory" must be boolean if provided'); + this.#capacity = capacity; + if (checkLowMemory) { + checkLowMemoryTimer = setInterval(() => { + // @todo + }, 5000); + // checkLowMemoryTimer.unref?.(); + } + this.#store = new Map(); + // struct for tracking of access to cache items + this.#accessMap = new Map(); + } + get size() { + return this.#store.size; + } + set capacity(value) { + if (!Number.isInteger(value) || value <= 0) + throw new Error('invalid "capacity": positive integer expected'); + this.#capacity = value; + } + read(key) { + if (this.#store.has(key)) { + // update access time + this.#accessMap.set(key, getTimestamp()); + return this.#store.get(key); + } + return null; + } + store(key, value) { + const timestamp = getTimestamp(); + // check if cache size limit is reached + if (this.#store.size === this.#capacity) { + // first, get item with the lowest priority (oldest based on timestamp) + const min = { + key: null, + value: +Infinity + }; + for (const [itemKey, itemValue] of this.#accessMap) { + if (itemValue < min.value) { + min.key = itemKey; + min.value = itemValue; + } + } + this.#accessMap.delete(min.key); + this.#store.delete(min.key); + } + this.#accessMap.set(key, timestamp); + this.#store.set(key, value); + } + has(key) { + return this.#store.has(key); + } + remove(key) { + this.#store.delete(key); + } + clear() { + this.#store.clear(); + this.#accessMap.clear(); + } +} +export default LruCache; diff --git a/lru-cache/src/index.ts b/lru-cache/index.ts similarity index 73% rename from lru-cache/src/index.ts rename to lru-cache/index.ts index b074558..6beeec0 100644 --- a/lru-cache/src/index.ts +++ b/lru-cache/index.ts @@ -1,4 +1,28 @@ -import { ILruCache, TConfigOptions } from './types.js'; +interface ILruCache { + get size(): number; + set capacity(value: number); + read: (key: any) => any; + store: (key: any, value: any) => void; + has: (key: any) => boolean; + remove: (key: any) => void; + clear: () => void; +}; + +type TConfigOptions = { + /** + * Capacity means how many items can be stored at the same time in cache. + * For LRU cache, by definition, capacity is a required restriction, + * without it becomes almost meaningless, so this option is mandatory. + */ + capacity: number; + /** + * Detect low memory situation and act accordingly: clear cache to free up memory + * and prevent storing new records temporarily. + * If such situation is just hypothetical, it's better to not enable this feature + * to not waste system resources. + */ + checkLowMemory?: boolean; +}; const getTimestamp = () => Date.now(); diff --git a/rr-cache/index.d.ts b/rr-cache/index.d.ts new file mode 100644 index 0000000..822d57b --- /dev/null +++ b/rr-cache/index.d.ts @@ -0,0 +1,57 @@ +type TStats = { + size: number; + capacity: number; + locked: boolean; +}; +interface IRRCache { + get stats(): TStats; + set locked(state: boolean); + read: (key: any) => any; + add: (key: any, value: any) => void; + has: (key: any) => boolean; + remove: (key: any) => void; + clear: () => void; +} +type TConfigOptions = { + /** + * Capacity means how many items can be stored at the same time in cache. + * For RR cache, by definition, capacity is a required restriction, + * without it becomes almost meaningless, so this option is mandatory. + */ + capacity: number; +}; +declare class RRCache implements IRRCache { + #private; + constructor(options: TConfigOptions); + get stats(): { + size: number; + capacity: number; + locked: boolean; + }; + set locked(state: boolean); + /** + * Read value stored in cache by assosiated key. + * @param {*} key - cache record's key + * @return {*|null} record's value retrieved by key or null if record is absent + */ + read(key: any): any; + add(key: any, value: any): void; + /** + * Check if record by given key exists in cache. + * @param {*} key - cache record's key + * @return {boolean} return true if record is in the cache + */ + has(key: any): boolean; + /** + * Remove an item from the cache. + * @param {*} key - cache record's key + * @return {void} + */ + remove(key: any): void; + /** + * Remove all items from the cache. + * @return {void} + */ + clear(): void; +} +export default RRCache; diff --git a/rr-cache/src/index.ts b/rr-cache/index.js similarity index 74% rename from rr-cache/src/index.ts rename to rr-cache/index.js index 3247b97..e224f72 100644 --- a/rr-cache/src/index.ts +++ b/rr-cache/index.js @@ -1,24 +1,19 @@ -import { IRRCache, TConfigOptions } from './types.js'; - -class RRCache implements IRRCache { - #capacity: number; - #locked: boolean; - #keys: Array; - #freeSlots: Array; +class RRCache { + #capacity; + #locked; + #keys; + #freeSlots; #store; - - constructor ( options: TConfigOptions ) { + constructor(options) { const { capacity } = options; - - if (!Number.isInteger(capacity) || capacity <= 0) throw new Error('invalid "capacity": positive integer expected'); - + if (!Number.isInteger(capacity) || capacity <= 0) + throw new Error('invalid "capacity": positive integer expected'); this.#capacity = capacity; this.#locked = false; this.#keys = new Array(capacity); this.#freeSlots = []; this.#store = new Map(); } - get stats() { return { size: this.#store.size, @@ -26,59 +21,52 @@ class RRCache implements IRRCache { locked: this.#locked }; } - - set locked (state: boolean) { + set locked(state) { this.#locked = state; } - /** * Read value stored in cache by assosiated key. * @param {*} key - cache record's key * @return {*|null} record's value retrieved by key or null if record is absent */ - read (key: any) { + read(key) { if (this.#store.has(key)) { return this.#store.get(key).value; } - return null; } - - add (key: any, value: any) { - if (this.#locked) return; - - let keyIndex: number; - + add(key, value) { + if (this.#locked) + return; + let keyIndex; // check if cache capacity limit is reached if (this.#store.size === this.#capacity) { // evict randomly selected entry since we are out of capacity keyIndex = Math.random() * this.#capacity | 0; this.#store.delete(this.#keys[keyIndex]); - } else { + } + else { keyIndex = this.#freeSlots.length ? this.#freeSlots.pop() : this.#store.size; } - this.#keys[keyIndex] = key; - this.#store.set(key, {keyIndex, value}); + this.#store.set(key, { keyIndex, value }); } - /** * Check if record by given key exists in cache. * @param {*} key - cache record's key * @return {boolean} return true if record is in the cache */ - has (key: any) { + has(key) { return this.#store.has(key); } - /** * Remove an item from the cache. * @param {*} key - cache record's key * @return {void} */ - remove (key: any) { + remove(key) { if (this.#store.has(key)) { const keyIndex = this.#store.get(key).keyIndex; this.#keys[keyIndex] = null; @@ -86,17 +74,14 @@ class RRCache implements IRRCache { this.#freeSlots.push(keyIndex); } } - /** * Remove all items from the cache. * @return {void} */ - clear () { + clear() { this.#keys.length = this.#freeSlots.length = 0; this.#keys.length = this.#capacity; this.#store.clear(); } } - - -export default RRCache; \ No newline at end of file +export default RRCache; diff --git a/rr-cache/index.ts b/rr-cache/index.ts new file mode 100644 index 0000000..7dd9a67 --- /dev/null +++ b/rr-cache/index.ts @@ -0,0 +1,136 @@ +type TStats = { + // represent number of cache entries + size: number; + // represent value of cache capacity + capacity: number; + // represent if cache is locked currently + locked: boolean; + // @todo: may contain other props like cache misses, etc. +} + +interface IRRCache { + // get cache statistics + get stats(): TStats; + // set lock state, if locked cache isn't growing + set locked(state: boolean); + // read value from cache by its key + read: (key: any) => any; + // add value to cache by corresponding key + add: (key: any, value: any) => void; + // check if cache contains value by given key + has: (key: any) => boolean; + // remove value from cache by associated key + remove: (key: any) => void; + // clear cache by removing all stored data and release all auxiliary resources + clear: () => void; +} + +type TConfigOptions = { + /** + * Capacity means how many items can be stored at the same time in cache. + * For RR cache, by definition, capacity is a required restriction, + * without it becomes almost meaningless, so this option is mandatory. + */ + capacity: number; +} + +class RRCache implements IRRCache { + #capacity: number; + #locked: boolean; + #keys: Array; + #freeSlots: Array; + #store; + + constructor ( options: TConfigOptions ) { + const { capacity } = options; + + if (!Number.isInteger(capacity) || capacity <= 0) throw new Error('invalid "capacity": positive integer expected'); + + this.#capacity = capacity; + this.#locked = false; + this.#keys = new Array(capacity); + this.#freeSlots = []; + this.#store = new Map(); + } + + get stats() { + return { + size: this.#store.size, + capacity: this.#capacity, + locked: this.#locked + }; + } + + set locked (state: boolean) { + this.#locked = state; + } + + /** + * Read value stored in cache by assosiated key. + * @param {*} key - cache record's key + * @return {*|null} record's value retrieved by key or null if record is absent + */ + read (key: any) { + if (this.#store.has(key)) { + return this.#store.get(key).value; + } + + return null; + } + + add (key: any, value: any) { + if (this.#locked) return; + + let keyIndex: number; + + // check if cache capacity limit is reached + if (this.#store.size === this.#capacity) { + // evict randomly selected entry since we are out of capacity + keyIndex = Math.random() * this.#capacity | 0; + this.#store.delete(this.#keys[keyIndex]); + } else { + keyIndex = this.#freeSlots.length + ? this.#freeSlots.pop() as number + : this.#store.size; + } + + this.#keys[keyIndex] = key; + this.#store.set(key, {keyIndex, value}); + } + + /** + * Check if record by given key exists in cache. + * @param {*} key - cache record's key + * @return {boolean} return true if record is in the cache + */ + has (key: any) { + return this.#store.has(key); + } + + /** + * Remove an item from the cache. + * @param {*} key - cache record's key + * @return {void} + */ + remove (key: any) { + if (this.#store.has(key)) { + const keyIndex = this.#store.get(key).keyIndex; + this.#keys[keyIndex] = null; + this.#store.delete(key); + this.#freeSlots.push(keyIndex); + } + } + + /** + * Remove all items from the cache. + * @return {void} + */ + clear () { + this.#keys.length = this.#freeSlots.length = 0; + this.#keys.length = this.#capacity; + this.#store.clear(); + } +} + + +export default RRCache; \ No newline at end of file diff --git a/rr-cache/src/types.ts b/rr-cache/src/types.ts deleted file mode 100644 index ff3ac36..0000000 --- a/rr-cache/src/types.ts +++ /dev/null @@ -1,41 +0,0 @@ -type TStats = { - // represent number of cache entries - size: number; - // represent value of cache capacity - capacity: number; - // represent if cache is locked currently - locked: boolean; - // @todo: may contain other props like cache misses, etc. -}; - -interface IRRCache { - // get cache statistics - get stats(): TStats; - // set lock state, if locked cache isn't growing - set locked(state: boolean); - // read value from cache by its key - read: (key: any) => any; - // add value to cache by corresponding key - add: (key: any, value: any) => void; - // check if cache contains value by given key - has: (key: any) => boolean; - // remove value from cache by associated key - remove: (key: any) => void; - // clear cache by removing all stored data and release all auxiliary resources - clear: () => void; -}; - -type TConfigOptions = { - /** - * Capacity means how many items can be stored at the same time in cache. - * For RR cache, by definition, capacity is a required restriction, - * without it becomes almost meaningless, so this option is mandatory. - */ - capacity: number; -}; - - -export { - IRRCache, - TConfigOptions -}; \ No newline at end of file diff --git a/slru-cache/index.d.ts b/slru-cache/index.d.ts new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/slru-cache/index.d.ts @@ -0,0 +1 @@ +export {}; diff --git a/slru-cache/index.js b/slru-cache/index.js new file mode 100644 index 0000000..cb0ff5c --- /dev/null +++ b/slru-cache/index.js @@ -0,0 +1 @@ +export {}; diff --git a/slru-cache/src/index.ts b/slru-cache/index.ts similarity index 100% rename from slru-cache/src/index.ts rename to slru-cache/index.ts diff --git a/slru-cache/src/types.ts b/slru-cache/src/types.ts deleted file mode 100644 index e69de29..0000000 diff --git a/tsconfig.json b/tsconfig.json index 4b4ab29..a85b0f0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,23 +1,19 @@ { "compilerOptions": { - "rootDir": ".", - "outDir": "build", - "target": "es2017", + "target": "es2022", "module": "nodenext", "moduleResolution": "nodenext", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, - "skipLibCheck": true + "alwaysStrict": true, + "skipLibCheck": true, + "declaration": true }, "include": [ - "fifo-cache/src/*", - "lfu-cache/src/*", - "lru-cache/src/*", - "rr-cache/src/*", - "slru-cache/src/*", - "ttl-cache-v1/src/*", - "ttl-cache-v2/src/*", - "spec.ts" + "*/index.ts" + ], + "exclude": [ + "node_modules" ] } diff --git a/ttl-cache-v1/index.d.ts b/ttl-cache-v1/index.d.ts new file mode 100644 index 0000000..a0d9587 --- /dev/null +++ b/ttl-cache-v1/index.d.ts @@ -0,0 +1,6 @@ +/** + * TTL Cache implementation, version 1. + * Cache is self cleaning, without external cleanup trigger. + * As a drawback, timer objects allocate additional memory. + */ +export {}; diff --git a/ttl-cache-v1/index.js b/ttl-cache-v1/index.js new file mode 100644 index 0000000..321b8f5 --- /dev/null +++ b/ttl-cache-v1/index.js @@ -0,0 +1,33 @@ +/** + * TTL Cache implementation, version 1. + * Cache is self cleaning, without external cleanup trigger. + * As a drawback, timer objects allocate additional memory. + */ +class TtlCache { + #store; + constructor() { + this.#store = Object.create(null); + } + read(key) { + return this.#store[key]; + } + store(key, value, timeLimit, onExpireCallback) { + const record = { + value, + timeout: setTimeout(() => { + this.remove(key); + return onExpireCallback?.(key, value); + }, timeLimit) + }; + record.timeout.unref?.(); + this.#store[key] = record; + } + remove(key) { + const record = this.#store[key]; + if (record) { + clearTimeout(record.timeout); + } + delete this.#store[key]; + } +} +export {}; diff --git a/ttl-cache-v1/src/index.ts b/ttl-cache-v1/index.ts similarity index 100% rename from ttl-cache-v1/src/index.ts rename to ttl-cache-v1/index.ts diff --git a/ttl-cache-v2/index.d.ts b/ttl-cache-v2/index.d.ts new file mode 100644 index 0000000..128427c --- /dev/null +++ b/ttl-cache-v2/index.d.ts @@ -0,0 +1,6 @@ +/** + * TTL Cache implementation, version 2. + * One of the simplest approach to manage record expiration. + * As a drawback, if there are no data read, all records (actual and expired) still allocate memory. + */ +export {}; diff --git a/ttl-cache-v2/index.js b/ttl-cache-v2/index.js new file mode 100644 index 0000000..70d7a66 --- /dev/null +++ b/ttl-cache-v2/index.js @@ -0,0 +1,33 @@ +/** + * TTL Cache implementation, version 2. + * One of the simplest approach to manage record expiration. + * As a drawback, if there are no data read, all records (actual and expired) still allocate memory. + */ +class TtlCache { + #store; + constructor() { + this.#store = Object.create(null); + } + read(key) { + const record = this.#store[key]; + if (!record) { + return null; + } + if (record.expiredAt < Date.now()) { + this.remove(key); + return null; + } + return record.value; + } + store(key, value, ttl) { + const record = { + value, + expiredAt: Date.now() + ttl + }; + this.#store[key] = record; + } + remove(key) { + delete this.#store[key]; + } +} +export {}; diff --git a/ttl-cache-v2/src/index.ts b/ttl-cache-v2/index.ts similarity index 100% rename from ttl-cache-v2/src/index.ts rename to ttl-cache-v2/index.ts