diff --git a/README.md b/README.md
index 380cf7d..d1878a2 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
[![GitHub Workflow Status (Bun)](https://img.shields.io/github/actions/workflow/status/wellwelwel/lru.min/ci_bun.yml?event=push&label=&branch=main&logo=bun&logoColor=ffffff&color=f368e0)](https://github.com/wellwelwel/lru.min/actions/workflows/ci_bun.yml?query=branch%3Amain)
[![GitHub Workflow Status (Deno)](https://img.shields.io/github/actions/workflow/status/wellwelwel/lru.min/ci_deno.yml?event=push&label=&branch=main&logo=deno&logoColor=ffffff&color=079992)](https://github.com/wellwelwel/lru.min/actions/workflows/ci_deno.yml?query=branch%3Amain)
-🔥 An extremely fast and efficient LRU Cache for JavaScript (Browser compatible) — **6.8KB**.
+🔥 An extremely fast and efficient LRU Cache for JavaScript (Browser compatible) — **8.8KB**.
@@ -45,6 +45,7 @@ deno add npm:lru.min
import { createLRU } from 'lru.min';
const max = 2;
+const maxAge = 300000; // 5m
const onEviction = (key, value) => {
console.log(`Key "${key}" with value "${value}" has been evicted.`);
};
@@ -52,6 +53,7 @@ const onEviction = (key, value) => {
const LRU = createLRU({
max,
onEviction,
+ maxAge,
});
LRU.set('A', 'My Value');
@@ -72,7 +74,9 @@ LRU.clear(); // LRU.evict(max)
// => Key "C" with value "Another Value" has been evicted.
-LRU.set('D', "You're amazing 💛");
+LRU.set('D', "You're amazing 💛", {
+ maxAge: Number.POSITIVE_INFINITY,
+});
LRU.size; // 1
LRU.max; // 2
@@ -83,6 +87,30 @@ LRU.resize(10);
LRU.size; // 1
LRU.max; // 10
LRU.available; // 9
+
+LRU.set('E', 'Yet Another Value');
+
+[...LRU.debug()];
+/**
+ * [
+ * {
+ * key: 'E',
+ * value: 'Yet Another Value',
+ * maxAge: 300000,
+ * expiresAt: 299999.90000000596,
+ * isExpired: false,
+ * position: 0
+ * },
+ * {
+ * key: 'D',
+ * value: "You're amazing 💛",
+ * maxAge: Infinity,
+ * expiresAt: Infinity,
+ * isExpired: false,
+ * position: 1
+ * },
+ * ]
+ */
```
> For _up-to-date_ documentation, always follow the [**README.md**](https://github.com/wellwelwel/lru.min?tab=readme-ov-file#readme) in the **GitHub** repository.
diff --git a/benchmark/worker.cjs b/benchmark/worker.cjs
index 39ada81..b056aae 100644
--- a/benchmark/worker.cjs
+++ b/benchmark/worker.cjs
@@ -20,7 +20,7 @@ const measurePerformance = (fn) => {
};
};
-const times = 100;
+const times = 10;
const max = 100000;
const brute = 1000000;
diff --git a/benchmark/worker.mjs b/benchmark/worker.mjs
index 1e53bc7..8ddd765 100644
--- a/benchmark/worker.mjs
+++ b/benchmark/worker.mjs
@@ -21,7 +21,7 @@ const measurePerformance = (fn) => {
};
};
-const times = 100;
+const times = 10;
const max = 100000;
const brute = 1000000;
diff --git a/package.json b/package.json
index e8566a6..f16ec58 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "lru.min",
"version": "1.1.1",
- "description": "🔥 An extremely fast and efficient LRU cache for JavaScript with high compatibility (including Browsers) — 6.8KB.",
+ "description": "🔥 An extremely fast and efficient LRU cache for JavaScript with high compatibility (including Browsers) — 8.8KB.",
"main": "./lib/index.js",
"module": "./lib/index.mjs",
"types": "./lib/index.d.ts",
@@ -34,6 +34,7 @@
"build:browser": "tsx tools/browserfy.ts",
"build:esm": "esbuild src/index.ts --outfile=lib/index.mjs --platform=node --target=node12 --format=esm",
"build": "tsc && npm run build:esm && npm run build:browser",
+ "postbuild": "tsx tools/strip-comments.ts",
"test:node": "poku --node -p",
"test:bun": "poku --bun -p",
"test:deno": "poku --deno -p",
diff --git a/src/index.ts b/src/index.ts
index 37f5ab5..93fb607 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,8 +1,7 @@
-import { performance } from 'node:perf_hooks';
-
export type CacheOptions = {
/** Maximum number of items the cache can hold. */
max: number;
+ /** Maximum age (in ms) for items before they are considered stale. */
maxAge?: number;
/** Function called when an item is evicted from the cache. */
onEviction?: (key: Key, value: Value) => unknown;
@@ -17,7 +16,13 @@ export const createLRU = (options: CacheOptions) => {
if (typeof maxAge === 'number' && maxAge <= 0)
throw new TypeError('`maxAge` must be a positive number');
- const Age = typeof performance?.now === 'object' ? performance : Date;
+ const Age = (() => {
+ try {
+ return typeof performance.now === 'function' ? performance : Date;
+ } catch {
+ return Date;
+ }
+ })();
let size = 0;
let head = 0;
@@ -116,6 +121,35 @@ export const createLRU = (options: CacheOptions) => {
return true;
};
+ const _debug = (key: Key) => {
+ const index = keyMap.get(key);
+
+ if (index === undefined) return;
+
+ const ageItem = ageList[index];
+ const expiresAt = expList[index];
+ const now = Age.now();
+ const timeRemaining = expiresAt !== 0 ? expiresAt - now : 0;
+ const isExpired = expiresAt !== 0 && now > expiresAt;
+
+ let current = tail;
+ let position = 0;
+
+ while (current !== index && current !== 0) {
+ current = prev[current];
+ position++;
+ }
+
+ return {
+ key,
+ value: valList[index],
+ maxAge: ageItem,
+ expiresAt: timeRemaining > 0 ? timeRemaining : 0,
+ isExpired,
+ position,
+ };
+ };
+
return {
/** Adds a key-value pair to the cache. Updates the value if the key already exists. */
set(key: Key, value: Value, options?: { maxAge?: number }): undefined {
@@ -344,6 +378,39 @@ export const createLRU = (options: CacheOptions) => {
max = newMax;
},
+ *debug(key?: Key): Generator<
+ | {
+ /** Item key. */
+ key: Key;
+ /** Item value. */
+ value: Value | undefined;
+ /** Time in milliseconds. */
+ maxAge: number;
+ /** Time in milliseconds. */
+ expiresAt: number;
+ /** When it's `true`, the next interaction with the key will evict it. */
+ isExpired: boolean;
+ /** From the most recent (`0`) to the oldest (`max`). */
+ position: number;
+ }
+ | undefined
+ > {
+ if (key !== undefined) {
+ const result = _debug(key);
+
+ if (result) yield result;
+
+ return;
+ }
+
+ let current = tail;
+
+ for (let i = 0; i < size; i++) {
+ yield _debug(keyList[current]!);
+ current = prev[current];
+ }
+ },
+
/** Returns the maximum number of items that can be stored in the cache. */
get max() {
return max;
diff --git a/t.ts b/t.ts
deleted file mode 100644
index 7a24f2f..0000000
--- a/t.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { createLRU } from './src/index.js';
-
-const LRU = createLRU({
- max: 100,
- maxAge: 1000,
-});
-
-LRU.set('A', 'Value');
-
-setTimeout(() => {
- console.log(LRU.get('A'));
-}, 2000);
diff --git a/tools/browserfy.ts b/tools/browserfy.ts
index 0f615cf..252f002 100644
--- a/tools/browserfy.ts
+++ b/tools/browserfy.ts
@@ -4,9 +4,10 @@ import { transformAsync } from '@babel/core';
import { minify } from 'terser';
(async () => {
- const contents = (await readFile('src/index.ts', 'utf8'))
- .replace(/export const/gim, 'window.')
- .replace(/import { performance } from 'node:perf_hooks';/gim, '');
+ const contents = (await readFile('src/index.ts', 'utf8')).replace(
+ /export const/gim,
+ 'window.'
+ );
const result = await esbuild.build({
stdin: {
diff --git a/tools/strip-comments.ts b/tools/strip-comments.ts
new file mode 100644
index 0000000..e34342a
--- /dev/null
+++ b/tools/strip-comments.ts
@@ -0,0 +1,19 @@
+import { promises as fs } from 'node:fs';
+import { listFiles } from 'poku';
+
+const ensureNodeCompatibility = async (path: string) => {
+ const files = await listFiles(path, {
+ filter: /\.(|m)?(j)?s$/,
+ });
+
+ console.log('Ensuring no unnecessary comments for:', files);
+
+ for (const file of files) {
+ const raw = await fs.readFile(file, 'utf8');
+ const content = raw.replace(/(\/\*)((?:|[^1])+?)\//gim, '');
+
+ await fs.writeFile(file, content, { encoding: 'utf8' });
+ }
+};
+
+ensureNodeCompatibility('./lib');