@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.
@@ -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
- ## API Response Caching
3
+ Learn Neezco Cache through practical examples for common use cases.
4
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:
5
+ ---
101
6
 
102
- ```javascript
103
- const cache = new LocalTtlCache();
7
+ ## 📚 Learning Examples
104
8
 
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"] });
9
+ Core concepts for using Neezco Cache effectively.
109
10
 
110
- // User updates their profile - invalidate everything at once
111
- cache.invalidateTag("user:456");
11
+ ### [Tag-Based Invalidation](./examples/tag-based-invalidation.md)
112
12
 
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
- ```
13
+ Learn how to manage cache invalidation—when to use tags and when to use direct deletion.
118
14
 
119
- ## Multiple Tags Per Entry
15
+ **Key concepts:**
120
16
 
121
- An entry can have multiple tags for flexible invalidation:
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
- ```javascript
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
- // Invalidate just the user's data
130
- cache.invalidateTag("user:789");
24
+ Master stale windows to control memory usage and improve responsiveness.
131
25
 
132
- // Or invalidate all profiles
133
- cache.invalidateTag("profiles");
26
+ **Key concepts:**
134
27
 
135
- // Or invalidate public data across the entire app
136
- cache.invalidateTag("public-data");
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)** - Back to basics
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
@@ -1,4 +1,4 @@
1
- # Getting Started with Short‑Live
1
+ # Getting Started with Neezco Cache
2
2
 
3
3
  ## Installation
4
4
 
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/browser/index.js",
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/browser/index.js"
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
  }