@neezco/cache 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +7 -0
- package/README.md +55 -0
- package/dist/browser/index.d.ts +276 -0
- package/dist/browser/index.js +782 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/node/index.cjs +1153 -0
- package/dist/node/index.cjs.map +1 -0
- package/dist/node/index.d.cts +276 -0
- package/dist/node/index.d.mts +276 -0
- package/dist/node/index.mjs +1124 -0
- package/dist/node/index.mjs.map +1 -0
- package/docs/.gitkeep +0 -0
- package/docs/api-reference.md +285 -0
- package/docs/configuration.md +175 -0
- package/docs/examples.md +145 -0
- package/docs/getting-started.md +86 -0
- package/package.json +93 -0
package/docs/examples.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
## API Response Caching
|
|
4
|
+
|
|
5
|
+
Cache API responses so you don't hammer your backend with repeated requests:
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
const cache = new LocalTtlCache();
|
|
9
|
+
|
|
10
|
+
async function getUser(userId) {
|
|
11
|
+
// Check cache first
|
|
12
|
+
const cached = cache.get(`user:${userId}`);
|
|
13
|
+
if (cached) return cached;
|
|
14
|
+
|
|
15
|
+
// Not in cache or expired, fetch it
|
|
16
|
+
const user = await fetch(`/api/users/${userId}`).then(r => r.json());
|
|
17
|
+
|
|
18
|
+
// Store for 5 minutes, but serve stale for 1 more minute while refreshing
|
|
19
|
+
cache.set(`user:${userId}`, user, {
|
|
20
|
+
ttl: 5 * 60 * 1000,
|
|
21
|
+
staleWindow: 1 * 60 * 1000,
|
|
22
|
+
tags: `user:${userId}`, // Tag it for easy invalidation
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return user;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// When a user updates their profile
|
|
29
|
+
function handleUserUpdate(userId) {
|
|
30
|
+
cache.invalidateTag(`user:${userId}`); // Instantly clear their cache
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Database Query Caching
|
|
35
|
+
|
|
36
|
+
Speed up repeated database queries:
|
|
37
|
+
|
|
38
|
+
```javascript
|
|
39
|
+
const cache = new LocalTtlCache({
|
|
40
|
+
defaultTtl: 2 * 60 * 1000, // 2 minutes
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
async function getProducts() {
|
|
44
|
+
const cached = cache.get("products:list");
|
|
45
|
+
if (cached) return cached;
|
|
46
|
+
|
|
47
|
+
const products = await db.query("SELECT * FROM products");
|
|
48
|
+
cache.set("products:list", products, { tags: "products" });
|
|
49
|
+
return products;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function deleteProduct(id) {
|
|
53
|
+
await db.query("DELETE FROM products WHERE id = ?", [id]);
|
|
54
|
+
cache.invalidateTag("products"); // Clear all product caches
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function updateProduct(id, data) {
|
|
58
|
+
await db.query("UPDATE products SET ? WHERE id = ?", [data, id]);
|
|
59
|
+
cache.invalidateTag("products"); // Clear all product caches
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Graceful Stale Data Serving
|
|
64
|
+
|
|
65
|
+
Keep serving old data while fetching fresh data in the background:
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
const cache = new LocalTtlCache();
|
|
69
|
+
|
|
70
|
+
async function getWeatherWithBackground() {
|
|
71
|
+
// Fresh for 5 minutes, but we'll serve it for 2 more minutes even if expired
|
|
72
|
+
cache.set(
|
|
73
|
+
"weather:NYC",
|
|
74
|
+
{ temp: 72, city: "NYC", fetched: Date.now() },
|
|
75
|
+
{
|
|
76
|
+
ttl: 5 * 60 * 1000,
|
|
77
|
+
staleWindow: 2 * 60 * 1000, // Keep serving for 2 extra minutes while fetching
|
|
78
|
+
},
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// NEXT skipStale, full entry
|
|
83
|
+
// This is perfect for UIs where slight data staleness is acceptable
|
|
84
|
+
// You can serve old data instantly while refreshing in the background
|
|
85
|
+
async function fetchWeatherIfNeeded(city) {
|
|
86
|
+
let weather = cache.get(`weather:${city}`);
|
|
87
|
+
|
|
88
|
+
// Stale? Fetch fresh data in the background
|
|
89
|
+
if (!weather) {
|
|
90
|
+
weather = await fetchWeatherAPI(city);
|
|
91
|
+
getWeatherWithBackground(city, weather);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return weather;
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Tag-Based Invalidation
|
|
99
|
+
|
|
100
|
+
Group related data and clear it all at once:
|
|
101
|
+
|
|
102
|
+
```javascript
|
|
103
|
+
const cache = new LocalTtlCache();
|
|
104
|
+
|
|
105
|
+
// Store user data with a 'user:456' tag
|
|
106
|
+
cache.set("user:456:name", "Bob", { tags: ["user:456"] });
|
|
107
|
+
cache.set("user:456:email", "bob@example.com", { tags: ["user:456"] });
|
|
108
|
+
cache.set("user:456:preferences", { theme: "dark" }, { tags: ["user:456"] });
|
|
109
|
+
|
|
110
|
+
// User updates their profile - invalidate everything at once
|
|
111
|
+
cache.invalidateTag("user:456");
|
|
112
|
+
|
|
113
|
+
// All three are now expired
|
|
114
|
+
console.log(cache.get("user:456:name")); // undefined
|
|
115
|
+
console.log(cache.get("user:456:email")); // undefined
|
|
116
|
+
console.log(cache.get("user:456:preferences")); // undefined
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Multiple Tags Per Entry
|
|
120
|
+
|
|
121
|
+
An entry can have multiple tags for flexible invalidation:
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
cache.set("user:789:profile", profileData, {
|
|
125
|
+
ttl: 10 * 60 * 1000,
|
|
126
|
+
tags: ["user:789", "profiles", "public-data"], // Multiple tags
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Invalidate just the user's data
|
|
130
|
+
cache.invalidateTag("user:789");
|
|
131
|
+
|
|
132
|
+
// Or invalidate all profiles
|
|
133
|
+
cache.invalidateTag("profiles");
|
|
134
|
+
|
|
135
|
+
// Or invalidate public data across the entire app
|
|
136
|
+
cache.invalidateTag("public-data");
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## Navigation
|
|
142
|
+
|
|
143
|
+
- **[Getting Started](./getting-started.md)** - Back to basics
|
|
144
|
+
- **[API Reference](./api-reference.md)** - All methods explained
|
|
145
|
+
- **[Configuration](./configuration.md)** - Customize your cache
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Getting Started with Short‑Live
|
|
2
|
+
|
|
3
|
+
## Installation
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm install @neezco/cache
|
|
7
|
+
# or
|
|
8
|
+
yarn add @neezco/cache
|
|
9
|
+
# or
|
|
10
|
+
pnpm add @neezco/cache
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Recommended Setup Pattern
|
|
14
|
+
|
|
15
|
+
For better TypeScript autocomplete, define a dedicated cache module:
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import LocalTtlCache from "@neezco/cache";
|
|
19
|
+
|
|
20
|
+
const cacheInstance = new LocalTtlCache({
|
|
21
|
+
defaultTtl: 5 * 60 * 1000,
|
|
22
|
+
maxSize: 1_000,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const cache = cacheInstance;
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Domain‑oriented singleton caches
|
|
29
|
+
|
|
30
|
+
Instead of a single global cache, you can maintain **one singleton per domain** to avoid key collisions and keep responsibilities clear:
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import LocalTtlCache from "@neezco/cache";
|
|
34
|
+
|
|
35
|
+
// User‑related data
|
|
36
|
+
export const usersCache = new LocalTtlCache({
|
|
37
|
+
defaultTtl: 5 * 60 * 1000,
|
|
38
|
+
maxSize: 1_000,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Asset metadata
|
|
42
|
+
export const assetsCache = new LocalTtlCache({
|
|
43
|
+
defaultTtl: 10 * 60 * 1000,
|
|
44
|
+
maxSize: 2_000,
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Minimal Usage Example
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { cache } from "./path-to-your-cache-instance";
|
|
52
|
+
// or
|
|
53
|
+
// import { usersCache } from "./path-to-your-cache-instance";
|
|
54
|
+
// import { assetsCache } from "./path-to-your-cache-instance";
|
|
55
|
+
|
|
56
|
+
cache.set("user:123", "cached-data", {
|
|
57
|
+
ttl: 5 * 60 * 1000, // fresh for 5 minutes
|
|
58
|
+
staleWindow: 2 * 60 * 1000, // may be served stale for 2 more minutes
|
|
59
|
+
tags: ["user", "user:123"], // tags for later invalidation
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
console.log("After set:", cache.get("user:123")); // fresh value
|
|
63
|
+
|
|
64
|
+
setTimeout(
|
|
65
|
+
() => {
|
|
66
|
+
console.log(
|
|
67
|
+
"After TTL but within stale window:",
|
|
68
|
+
cache.get("user:123"), // stale value
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
cache.invalidateTag("user:123");
|
|
72
|
+
|
|
73
|
+
console.log(
|
|
74
|
+
"After tag invalidation:",
|
|
75
|
+
cache.get("user:123"), // undefined
|
|
76
|
+
);
|
|
77
|
+
},
|
|
78
|
+
6 * 60 * 1000,
|
|
79
|
+
); // 6 minutes
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Next Steps
|
|
83
|
+
|
|
84
|
+
- **[Examples](./examples.md)** - Real-world use cases
|
|
85
|
+
- **[API Reference](./api-reference.md)** – Complete method documentation with edge cases
|
|
86
|
+
- **[Configuration](./configuration.md)** – All options explained in detail
|
package/package.json
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.1.0",
|
|
3
|
+
"name": "@neezco/cache",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"module": "./dist/browser/index.js",
|
|
6
|
+
"main": "./dist/node/index.cjs",
|
|
7
|
+
"types": "./dist/node/index.d.cts",
|
|
8
|
+
"author": {
|
|
9
|
+
"name": "Daniel Hernández Ochoa",
|
|
10
|
+
"url": "https://github.com/DanhezCode"
|
|
11
|
+
},
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"homepage": "https://github.com/neezco/cache#readme",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/neezco/cache.git"
|
|
17
|
+
},
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/neezco/cache/issues"
|
|
20
|
+
},
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"require": "./dist/node/index.cjs",
|
|
24
|
+
"import": "./dist/browser/index.js"
|
|
25
|
+
},
|
|
26
|
+
"./package.json": "./package.json"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"bench": "node --max-old-space-size=4096 bench/minimal-bench.js",
|
|
30
|
+
"bench:watch": "node --watch-path dist --max-old-space-size=4096 bench/minimal-bench.js",
|
|
31
|
+
"bench:dev": "pnpm run build && concurrently \"pnpm run build:watch\" \"pnpm run bench:watch\"",
|
|
32
|
+
"bench:start": "pnpm run build && pnpm run bench",
|
|
33
|
+
"build": "tsdown",
|
|
34
|
+
"build:watch": "chokidar \"src/**/*\" -c \"pnpm run build\"",
|
|
35
|
+
"lint": "eslint . --ext .ts",
|
|
36
|
+
"lint:fix": "eslint . --ext .ts --fix",
|
|
37
|
+
"typecheck": "tsc --noEmit",
|
|
38
|
+
"format:prettier": "prettier --write .",
|
|
39
|
+
"format:all": "pnpm lint:fix && pnpm format:prettier",
|
|
40
|
+
"check": "concurrently \"pnpm format:all\" \"pnpm typecheck\"",
|
|
41
|
+
"check:all": "concurrently \"pnpm format:all\" \"pnpm typecheck\" \"pnpm test\"",
|
|
42
|
+
"test": "vitest run",
|
|
43
|
+
"test:watch": "vitest --watch",
|
|
44
|
+
"test:coverage": "vitest run --coverage",
|
|
45
|
+
"prepare": "husky"
|
|
46
|
+
},
|
|
47
|
+
"files": [
|
|
48
|
+
"dist",
|
|
49
|
+
"docs",
|
|
50
|
+
"CHANGELOG.md",
|
|
51
|
+
"README.md",
|
|
52
|
+
"LICENSE"
|
|
53
|
+
],
|
|
54
|
+
"description": "A simple and efficient in-memory cache for JavaScript and TypeScript projects, designed to provide fast access to frequently used data while minimizing memory usage.",
|
|
55
|
+
"keywords": [
|
|
56
|
+
"cache",
|
|
57
|
+
"in-memory cache",
|
|
58
|
+
"javascript cache",
|
|
59
|
+
"typescript cache",
|
|
60
|
+
"node.js cache",
|
|
61
|
+
"browser cache",
|
|
62
|
+
"ttl cache",
|
|
63
|
+
"cache library",
|
|
64
|
+
"stale-while-revalidate"
|
|
65
|
+
],
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"@commitlint/cli": "20.3.0",
|
|
68
|
+
"@commitlint/config-conventional": "20.3.0",
|
|
69
|
+
"@eslint/js": "9.39.2",
|
|
70
|
+
"@types/bun": "latest",
|
|
71
|
+
"@types/node": "25.0.6",
|
|
72
|
+
"@vitest/coverage-v8": "4.0.17",
|
|
73
|
+
"chokidar-cli": "3.0.0",
|
|
74
|
+
"concurrently": "9.2.1",
|
|
75
|
+
"eslint": "9.39.2",
|
|
76
|
+
"eslint-import-resolver-typescript": "4.4.4",
|
|
77
|
+
"eslint-plugin-import": "2.32.0",
|
|
78
|
+
"eslint-plugin-react": "7.37.5",
|
|
79
|
+
"globals": "17.0.0",
|
|
80
|
+
"husky": "9.1.7",
|
|
81
|
+
"prettier": "3.7.4",
|
|
82
|
+
"tsdown": "0.19.0",
|
|
83
|
+
"typescript-eslint": "8.51.0",
|
|
84
|
+
"vitest": "4.0.17"
|
|
85
|
+
},
|
|
86
|
+
"peerDependencies": {
|
|
87
|
+
"typescript": "5"
|
|
88
|
+
},
|
|
89
|
+
"engines": {
|
|
90
|
+
"node": ">=24.12.0",
|
|
91
|
+
"pnpm": ">=10.27.0"
|
|
92
|
+
}
|
|
93
|
+
}
|