1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209 |
x14
x14
x14
x14
x14
x14
x14
x26
x26
x26
x26
x14
x359
x359
x359
x14
x142
x149
x149
x142
x14
x232
x232
x338
x338
x232
x232
x14
x129
x240
x240
x240
x240
x133
x129
x14
x142
x142
x142
x142
x14
x25
x25
x25
x25
x25
x25
x14 |
|
// Copyright 2018-2025 the Deno authors. MIT license.
// This module is browser compatible.
import type { MemoizationCache } from "./memoize.ts";
export type { MemoizationCache };
/**
* Least-recently-used cache.
*
* @experimental **UNSTABLE**: New API, yet to be vetted.
*
* @see {@link https://en.wikipedia.org/wiki/Cache_replacement_policies#LRU | Least-recently-used cache}
*
* Automatically removes entries above the max size based on when they were
* last accessed with `get`, `set`, or `has`.
*
* @typeParam K The type of the cache keys.
* @typeParam V The type of the cache values.
*
* @example Basic usage
* ```ts
* import { LruCache } from "@std/cache";
* import { assert, assertEquals } from "@std/assert";
*
* const MAX_SIZE = 3;
* const cache = new LruCache<string, number>(MAX_SIZE);
*
* cache.set("a", 1);
* cache.set("b", 2);
* cache.set("c", 3);
* cache.set("d", 4);
*
* // most recent values are stored up to `MAX_SIZE`
* assertEquals(cache.get("b"), 2);
* assertEquals(cache.get("c"), 3);
* assertEquals(cache.get("d"), 4);
*
* // less recent values are removed
* assert(!cache.has("a"));
* ```
*
* @example Adding a onEject function.
* ```ts
* import { LruCache } from "@std/cache";
* import { assertEquals } from "@std/assert";
*
* const cache = new LruCache<string, string>(3, { onEject: (key, value) => {
* console.log("Revoking: ", key)
* URL.revokeObjectURL(value)
* }});
*
* cache.set(
* "fast-url",
* URL.createObjectURL(new Blob(["Hello, World"], { type: "text/plain" }))
* );
*
* cache.delete("fast-url") // "Revoking: fast-url"
* assertEquals(cache.get("fast-url"), undefined)
* ```
*/
export class LruCache<K, V> extends Map<K, V>
implements MemoizationCache<K, V> {
/**
* The maximum number of entries to store in the cache.
*
* @example Max size
* ```ts no-assert
* import { LruCache } from "@std/cache";
* import { assertEquals } from "@std/assert";
*
* const cache = new LruCache<string, number>(100);
* assertEquals(cache.maxSize, 100);
* ```
*/
maxSize: number;
#eject: (ejectedKey: K, ejectedValue: V) => void;
/**
* Constructs a new `LruCache`.
*
* @param maxSize The maximum number of entries to store in the cache.
* @param options Additional options.
*/
constructor(
maxSize: number,
options?: { onEject: (ejectedKey: K, ejectedValue: V) => void },
) {
super();
this.maxSize = maxSize;
this.#eject = options?.onEject ?? (() => {});
}
#setMostRecentlyUsed(key: K, value: V): void {
// delete then re-add to ensure most recently accessed elements are last
super.delete(key);
super.set(key, value);
}
#pruneToMaxSize(): void {
if (this.size > this.maxSize) {
this.delete(this.keys().next().value!);
}
}
/**
* Checks whether an element with the specified key exists or not.
*
* @param key The key to check.
* @returns `true` if the cache contains the specified key, otherwise `false`.
*
* @example Checking for the existence of a key
* ```ts
* import { LruCache } from "@std/cache";
* import { assert } from "@std/assert";
*
* const cache = new LruCache<string, number>(100);
*
* cache.set("a", 1);
* assert(cache.has("a"));
* ```
*/
override has(key: K): boolean {
const exists = super.has(key);
if (exists) {
this.#setMostRecentlyUsed(key, super.get(key)!);
}
return exists;
}
/**
* Gets the element with the specified key.
*
* @param key The key to get the value for.
* @returns The value associated with the specified key, or `undefined` if the key is not present in the cache.
*
* @example Getting a value from the cache
* ```ts
* import { LruCache } from "@std/cache";
* import { assertEquals } from "@std/assert";
*
* const cache = new LruCache<string, number>(100);
*
* cache.set("a", 1);
* assertEquals(cache.get("a"), 1);
* ```
*/
override get(key: K): V | undefined {
if (super.has(key)) {
const value = super.get(key)!;
this.#setMostRecentlyUsed(key, value);
return value;
}
return undefined;
}
/**
* Sets the specified key to the specified value.
*
* @param key The key to set the value for.
* @param value The value to set.
* @returns `this` for chaining.
*
* @example Setting a value in the cache
* ```ts no-assert
* import { LruCache } from "@std/cache";
*
* const cache = new LruCache<string, number>(100);
* cache.set("a", 1);
* ```
*/
override set(key: K, value: V): this {
this.#setMostRecentlyUsed(key, value);
this.#pruneToMaxSize();
return this;
}
/**
* Deletes the value associated with the given key.
*
* @experimental **UNSTABLE**: New API, yet to be vetted.
*
* @param key The key to delete.
* @returns `true` if the key was deleted, `false` otherwise.
*
* @example Deleting a key from the cache
* ```ts
* import { LruCache } from "@std/cache";
* import { assertEquals } from "@std/assert/equals";
*
* const cache = new LruCache<string, number>(1);
*
* cache.set("a", 1);
* cache.delete("a");
* assertEquals(cache.has("a"), false);
* ```
*/
override delete(key: K): boolean {
const value = super.get(key);
if (value) {
this.#eject(key, value);
}
return super.delete(key);
}
}
|