@neezco/cache 0.1.0 → 0.1.1
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/CHANGELOG.md +6 -0
- package/README.md +3 -3
- package/dist/browser/index.d.ts +11 -2
- package/dist/browser/index.js +2 -3
- package/dist/browser/index.js.map +1 -1
- package/dist/node/index.cjs +2 -3
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.d.cts +11 -2
- package/dist/node/index.d.mts +11 -2
- package/dist/node/index.mjs +2 -3
- package/dist/node/index.mjs.map +1 -1
- package/docs/api-reference.md +4 -2
- package/docs/configuration.md +1 -1
- package/docs/examples/stale-window.md +66 -0
- package/docs/examples/tag-based-invalidation.md +183 -0
- package/docs/examples.md +20 -125
- package/docs/getting-started.md +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Tag-Based Invalidation
|
|
2
|
+
|
|
3
|
+
Learn how to manage cache invalidation with tags. **Important:** Use tags only when you truly need group invalidation. For known keys, `cache.delete()` is more efficient.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## When to Use Tags vs Direct Deletion
|
|
8
|
+
|
|
9
|
+
### ✅ Use `cache.delete(key)` when:
|
|
10
|
+
|
|
11
|
+
- You know the exact key to invalidate
|
|
12
|
+
- You're invalidating a single entry
|
|
13
|
+
- Performance is critical
|
|
14
|
+
- You have few related entries
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
const cache = new LocalTtlCache();
|
|
18
|
+
|
|
19
|
+
cache.set("user:123", { name: "Alice" });
|
|
20
|
+
cache.set("user:456", { name: "Bob" });
|
|
21
|
+
|
|
22
|
+
// If you know the key, just delete it directly
|
|
23
|
+
cache.delete("user:123"); // Single O(1) operation
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 🏷️ Use `invalidateTag()` when:
|
|
27
|
+
|
|
28
|
+
- You don't know which keys are affected
|
|
29
|
+
- Multiple entries need invalidation together
|
|
30
|
+
- Data relationships are complex
|
|
31
|
+
- Convenience outweighs the performance cost
|
|
32
|
+
|
|
33
|
+
**Important:** Each tag associated with an entry adds overhead. Use tags strategically, not by default.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Basic Tag Usage
|
|
38
|
+
|
|
39
|
+
Tags allow you to invalidate multiple entries with one call:
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
const cache = new LocalTtlCache();
|
|
43
|
+
|
|
44
|
+
// Store user data with a 'users' tag
|
|
45
|
+
cache.set("user:123:profile", { name: "Alice" }, { tags: ["users"] });
|
|
46
|
+
cache.set("user:123:posts", [...], { tags: ["users"] });
|
|
47
|
+
cache.set("user:123:followers", [...], { tags: ["users"] });
|
|
48
|
+
|
|
49
|
+
// All three invalidated at once
|
|
50
|
+
cache.invalidateTag("users");
|
|
51
|
+
|
|
52
|
+
// All three are now expired
|
|
53
|
+
console.log(cache.get("user:123:profile")); // undefined
|
|
54
|
+
console.log(cache.get("user:123:posts")); // undefined
|
|
55
|
+
console.log(cache.get("user:123:followers")); // undefined
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Single vs Multiple Tags Per Entry
|
|
61
|
+
|
|
62
|
+
One entry can have one tag or multiple tags:
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
// Single tag per entry
|
|
66
|
+
cache.set("product:42", productData, { tags: "products" });
|
|
67
|
+
|
|
68
|
+
// Multiple tags per entry - can be invalidated multiple ways
|
|
69
|
+
cache.set("post:456", postData, {
|
|
70
|
+
tags: ["posts", "author:789", "published"],
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Any of these invalidates the post
|
|
74
|
+
cache.invalidateTag("posts"); // All posts
|
|
75
|
+
cache.invalidateTag("author:789"); // Posts by this author
|
|
76
|
+
cache.invalidateTag("published"); // All published content
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Designing Your Tag Strategy
|
|
82
|
+
|
|
83
|
+
Think carefully about the different ways you need to invalidate entries. Use tags that map to your domain concepts:
|
|
84
|
+
|
|
85
|
+
- **By resource type**: `"users"`, `"posts"`, `"products"`
|
|
86
|
+
- **By relationship**: `"author:789"`, `"follows:123"`, `"category:electronics"`
|
|
87
|
+
- **By state**: `"published"`, `"archived"`, `"pending"`
|
|
88
|
+
- **By context**: `"admin-only"`, `"premium-features"`, `"exports"`
|
|
89
|
+
|
|
90
|
+
Example: A blog post that belongs to multiple groups:
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
cache.set("post:456", postData, {
|
|
94
|
+
tags: [
|
|
95
|
+
"posts", // Invalidate all posts
|
|
96
|
+
"author:789", // Invalidate by author
|
|
97
|
+
"category:technology", // Invalidate by category
|
|
98
|
+
"published", // Invalidate by state
|
|
99
|
+
],
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// When the author deletes their account
|
|
103
|
+
cache.invalidateTag("author:789");
|
|
104
|
+
|
|
105
|
+
// When you want to hide all published content
|
|
106
|
+
cache.invalidateTag("published");
|
|
107
|
+
|
|
108
|
+
// When the category is renamed
|
|
109
|
+
cache.invalidateTag("category:technology");
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Real-World Pattern: User Data
|
|
115
|
+
|
|
116
|
+
Let's look at a realistic scenario with multiple user-related caches:
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
const cache = new LocalTtlCache({ defaultTtl: 5 * 60 * 1000 });
|
|
120
|
+
|
|
121
|
+
// ❌ AVOID THIS - Too many tags per entry
|
|
122
|
+
cache.set("user:123:profile", profileData, {
|
|
123
|
+
tags: ["users", "user:123", "profiles", "public-data", "profiles:basic"],
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// ✅ BETTER - Use tags only for necessary group invalidations
|
|
127
|
+
cache.set("user:123:profile", profileData, {
|
|
128
|
+
tags: ["users"], // Single tag for bulk operations
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// ✅ BEST - Use direct deletion when you know the key
|
|
132
|
+
if (userDeleted) {
|
|
133
|
+
cache.delete("user:123:profile"); // Direct, efficient
|
|
134
|
+
cache.delete("user:123:posts");
|
|
135
|
+
cache.delete("user:123:followers");
|
|
136
|
+
|
|
137
|
+
// Use tags only for complex relationships
|
|
138
|
+
cache.invalidateTag("users"); // Clear all user lists
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Compare the approaches:
|
|
143
|
+
|
|
144
|
+
```javascript
|
|
145
|
+
// Direct deletion - efficient when you know keys
|
|
146
|
+
cache.delete("user:123:profile"); // 1 operation, no tag overhead
|
|
147
|
+
|
|
148
|
+
// Tag invalidation - convenient for groups
|
|
149
|
+
cache.invalidateTag("users"); // Invalidates all user-related entries
|
|
150
|
+
// But processes every entry with that tag
|
|
151
|
+
|
|
152
|
+
// Mixed approach - best of both worlds
|
|
153
|
+
cache.delete("user:123:profile"); // Known entry
|
|
154
|
+
cache.invalidateTag("users"); // Group operations only
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Performance Consideration
|
|
160
|
+
|
|
161
|
+
Each tag on an entry adds a small memory and lookup cost. Keep tags minimal:
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
// ❌ Avoid excessive tags
|
|
165
|
+
cache.set("data", value, {
|
|
166
|
+
tags: ["all", "group1", "group2", "group3", "special"],
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// ✅ Use only tags you'll actually invalidate
|
|
170
|
+
cache.set("data", value, {
|
|
171
|
+
tags: ["group1"], // Only if you'll use cache.invalidateTag("group1")
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// ✅ Prefer direct deletion for single entries
|
|
175
|
+
cache.delete("key");
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
## Next
|
|
181
|
+
|
|
182
|
+
- **[Stale Windows](./stale-window.md)** - Control memory with graceful expiration
|
|
183
|
+
- **[Back to Examples](../examples.md)**
|
package/docs/examples.md
CHANGED
|
@@ -1,145 +1,40 @@
|
|
|
1
1
|
# Examples
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Learn Neezco Cache through practical examples for common use cases.
|
|
4
4
|
|
|
5
|
-
|
|
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:
|
|
5
|
+
---
|
|
101
6
|
|
|
102
|
-
|
|
103
|
-
const cache = new LocalTtlCache();
|
|
7
|
+
## 📚 Learning Examples
|
|
104
8
|
|
|
105
|
-
|
|
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"] });
|
|
9
|
+
Core concepts for using Neezco Cache effectively.
|
|
109
10
|
|
|
110
|
-
|
|
111
|
-
cache.invalidateTag("user:456");
|
|
11
|
+
### [Tag-Based Invalidation](./examples/tag-based-invalidation.md)
|
|
112
12
|
|
|
113
|
-
|
|
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
|
-
```
|
|
13
|
+
Learn how to manage cache invalidation—when to use tags and when to use direct deletion.
|
|
118
14
|
|
|
119
|
-
|
|
15
|
+
**Key concepts:**
|
|
120
16
|
|
|
121
|
-
|
|
17
|
+
- `cache.delete()` vs `cache.invalidateTag()`
|
|
18
|
+
- Single tag vs multiple tags
|
|
19
|
+
- When tags are worth the overhead
|
|
20
|
+
- Performance considerations
|
|
122
21
|
|
|
123
|
-
|
|
124
|
-
cache.set("user:789:profile", profileData, {
|
|
125
|
-
ttl: 10 * 60 * 1000,
|
|
126
|
-
tags: ["user:789", "profiles", "public-data"], // Multiple tags
|
|
127
|
-
});
|
|
22
|
+
### [Stale Windows](./examples/stale-window.md)
|
|
128
23
|
|
|
129
|
-
|
|
130
|
-
cache.invalidateTag("user:789");
|
|
24
|
+
Master stale windows to control memory usage and improve responsiveness.
|
|
131
25
|
|
|
132
|
-
|
|
133
|
-
cache.invalidateTag("profiles");
|
|
26
|
+
**Key concepts:**
|
|
134
27
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
28
|
+
- What stale windows are and how they work
|
|
29
|
+
- `purgeStaleOnGet` for tight memory constraints
|
|
30
|
+
- `purgeStaleOnSweep` for efficient batch cleanup
|
|
31
|
+
- Stale-while-revalidate pattern
|
|
32
|
+
- Per-entry stale window customization
|
|
138
33
|
|
|
139
34
|
---
|
|
140
35
|
|
|
141
36
|
## Navigation
|
|
142
37
|
|
|
143
|
-
- **[Getting Started](./getting-started.md)** -
|
|
38
|
+
- **[Getting Started](./getting-started.md)** - Installation and setup
|
|
144
39
|
- **[API Reference](./api-reference.md)** - All methods explained
|
|
145
40
|
- **[Configuration](./configuration.md)** - Customize your cache
|
package/docs/getting-started.md
CHANGED
package/package.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.1.0",
|
|
3
2
|
"name": "@neezco/cache",
|
|
4
3
|
"type": "module",
|
|
5
|
-
"module": "./dist/
|
|
4
|
+
"module": "./dist/node/index.mjs",
|
|
6
5
|
"main": "./dist/node/index.cjs",
|
|
7
6
|
"types": "./dist/node/index.d.cts",
|
|
8
7
|
"author": {
|
|
@@ -21,7 +20,7 @@
|
|
|
21
20
|
"exports": {
|
|
22
21
|
".": {
|
|
23
22
|
"require": "./dist/node/index.cjs",
|
|
24
|
-
"import": "./dist/
|
|
23
|
+
"import": "./dist/node/index.mjs"
|
|
25
24
|
},
|
|
26
25
|
"./package.json": "./package.json"
|
|
27
26
|
},
|
|
@@ -89,5 +88,6 @@
|
|
|
89
88
|
"engines": {
|
|
90
89
|
"node": ">=24.12.0",
|
|
91
90
|
"pnpm": ">=10.27.0"
|
|
92
|
-
}
|
|
91
|
+
},
|
|
92
|
+
"version": "0.1.1"
|
|
93
93
|
}
|