@neezco/cache 0.1.0 → 0.2.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.
@@ -14,7 +14,13 @@ Creates a new cache instance.
14
14
 
15
15
  **Parameters:**
16
16
 
17
- - `options` (optional) - Configuration object. See [Configuration Guide](./configuration.md) for details.
17
+ - `options` (optional) - Configuration object. See [Configuration Guide](./configuration.md) for details, including:
18
+ - `defaultTtl` - Default entry lifetime
19
+ - `defaultStaleWindow` - Default stale serving window
20
+ - `maxSize` - Maximum number of entries allowed
21
+ - `maxMemorySize` - Maximum memory usage (MB) allowed
22
+ - `purgeStaleOnGet` / `purgeStaleOnSweep` - Stale entry handling
23
+ - `onExpire` / `onDelete` - Lifecycle callbacks
18
24
 
19
25
  ---
20
26
 
@@ -33,7 +39,7 @@ set(
33
39
  staleWindow?: number;
34
40
  tags?: string | string[];
35
41
  }
36
- ): void
42
+ ): boolean
37
43
  ```
38
44
 
39
45
  **Parameters:**
@@ -44,16 +50,20 @@ set(
44
50
  - `options.staleWindow` - How long to serve stale data after expiration (in milliseconds)
45
51
  - `options.tags` - One or more tags for group invalidation
46
52
 
47
- **Returns:** Nothing (void)
53
+ **Returns:** `true` if the entry was set or updated, `false` if rejected due to limits or invalid input
48
54
 
49
55
  **Example:**
50
56
 
51
57
  ```javascript
52
- cache.set("user:123", "cached-value", {
58
+ const success = cache.set("user:123", "cached-value", {
53
59
  ttl: 5 * 60 * 1000, // Expires in 5 minutes
54
60
  staleWindow: 1 * 60 * 1000, // Serve stale for 1 more minute
55
61
  tags: ["user:123"], // Tag for invalidation
56
62
  });
63
+
64
+ if (!success) {
65
+ console.log("Entry rejected: cache at maxSize or maxMemorySize limit");
66
+ }
57
67
  ```
58
68
 
59
69
  **Edge Cases:**
@@ -64,6 +74,9 @@ cache.set("user:123", "cached-value", {
64
74
  - If `staleWindow` is larger than `ttl`, the entry can be served as stale for longer than it was fresh
65
75
  - Tags are optional; only necessary for group invalidation via `invalidateTag()`
66
76
  - Setting a key to `null` is valid; calling `cache.set("user:123", null)` overwrites any previous value and stores `null` as the new value
77
+ - Returns `false` if value is `undefined` (existing value remains untouched if it exists)
78
+ - Returns `false` if new entry would exceed [`maxSize`](./configuration.md#maxsize-number) limit, but updating existing keys always succeeds
79
+ - Returns `false` if new entry would exceed [`maxMemorySize`](./configuration.md#maxmemorysize-number) limit, but updating existing keys always succeeds (not supported in browsers)
67
80
 
68
81
  ---
69
82
 
@@ -210,17 +223,19 @@ console.log(cache.size); // 0
210
223
 
211
224
  ---
212
225
 
213
- ### `invalidateTag(tags, asStale)`
226
+ ### `invalidateTag(tags, options)`
214
227
 
215
228
  Mark all entries with one or more tags as expired (or stale, if requested).
216
229
 
217
230
  ```typescript
218
231
  invalidateTag(
219
232
  tags: string | string[],
220
- options: { asStale?: boolean }
233
+ options?: InvalidateTagOptions
221
234
  ): void
222
235
  ```
223
236
 
237
+ Options type: `InvalidateTagOptions` — an extensible object with `asStale?: boolean`.
238
+
224
239
  **Parameters:**
225
240
 
226
241
  - `tags` - A single tag (string) or array of tags to invalidate
@@ -49,7 +49,7 @@ cache.set("data", { value: "old" });
49
49
 
50
50
  ### `maxSize` (number)
51
51
 
52
- The maximum number of entries the cache can hold. When the limit is reached, the cache automatically cleans up expired entries and removes less-used items.
52
+ The maximum number of entries the cache can hold. When the limit is reached, new entries are ignored while existing keys can still be updated.
53
53
 
54
54
  - **Type**: `number`
55
55
  - **Default**: `Infinite` (no limit)
@@ -60,9 +60,46 @@ const cache = new LocalTtlCache({
60
60
  maxSize: 10_000, // Only keep up to 10,000 items
61
61
  });
62
62
 
63
- // When you exceed 10_000 items new entries are ignored
63
+ cache.set("key1", "value1"); // OK - within limit
64
+ cache.set("key10001", "value"); // Ignored - would exceed maxSize
65
+ cache.set("key1", "updated"); // OK - updating existing key is always allowed
64
66
  ```
65
67
 
68
+ **Edge Cases:**
69
+
70
+ - New entries are rejected silently when maxSize is reached
71
+ - Updating existing keys is **always allowed**, regardless of maxSize limit
72
+ - Value set to `Infinity` or `0` means unlimited entries
73
+
74
+ ---
75
+
76
+ ### `maxMemorySize` (number)
77
+
78
+ The maximum memory size in MB the cache can consume. When the limit is reached, new entries are ignored while existing keys can still be updated.
79
+
80
+ - **Type**: `number` (in megabytes)
81
+ - **Default**: `Infinite` (no limit)
82
+ - **Example**: `maxMemorySize: 512` (512 MB)
83
+ - **Note**: Only enforced in Node.js environments (browser environments ignore this setting)
84
+
85
+ ```javascript
86
+ const cache = new LocalTtlCache({
87
+ maxMemorySize: 512, // Cache uses at most 512 MB
88
+ });
89
+
90
+ cache.set("key1", largeObject); // OK - within memory limit
91
+ cache.set("key2", anotherLargeObject); // May be ignored if total memory would exceed 512 MB
92
+ cache.set("key1", "updated"); // OK - updating existing key is allowed
93
+ ```
94
+
95
+ **Edge Cases:**
96
+
97
+ - New entries are rejected silently when memory limit is reached
98
+ - Updating existing keys is **always allowed**, regardless of memory limit
99
+ - Measured via Node.js `process.memoryUsage().rss`
100
+ - Value set to `Infinity` or `0` means unlimited memory
101
+ - **Not supported in browser environments** — this setting is ignored when running in a browser
102
+
66
103
  ### `purgeStaleOnGet` (boolean)
67
104
 
68
105
  Whether to delete stale entries immediately after retrieving them.
@@ -84,6 +121,8 @@ cache.set("data", "value");
84
121
  // Subsequent calls return undefined
85
122
  ```
86
123
 
124
+ ---
125
+
87
126
  ### `purgeStaleOnSweep` (boolean)
88
127
 
89
128
  Whether to delete stale entries during automatic cleanup (sweep) operations.
@@ -105,7 +144,7 @@ const cache = new LocalTtlCache({
105
144
 
106
145
  ### Callbacks
107
146
 
108
- Short-Live supports callbacks for monitoring cache operations:
147
+ Neezco Cache supports callbacks for monitoring cache operations:
109
148
 
110
149
  #### `onExpire(key, value)`
111
150
 
@@ -0,0 +1,40 @@
1
+ # Code Style Guidelines
2
+
3
+ This project prioritizes **functional programming with controlled mutability for maximum performance**.
4
+
5
+ ## Core Principles
6
+
7
+ - **Pure Functions First**: All business logic implemented as pure functions without side effects
8
+ - **One Function = One Responsibility**: Keep functions small and focused on a single task
9
+ - **Dependency Injection**: Inject dependencies when they can change or have multiple implementations
10
+ - **Explicit Behavior**: Functions only do what they promise, with no hidden state or implicit behavior
11
+ - **Strict Type Safety**: Avoid `any` and use precise, verifiable TypeScript types
12
+ - **Controlled Mutability**: State mutations are explicit and localized, never global
13
+ - **Testable in Isolation**: Functions must be testable without global dependencies
14
+
15
+ ## Documentation Standards
16
+
17
+ - Use **advanced TSDoc** for all functions: document purpose, parameters, return types, and edge cases
18
+ - Adjust documentation level to actual complexity
19
+ - Document technical or internal information with inline comments or TSDoc
20
+ - Update `/doc` directory only for externally relevant or highly complex features
21
+ - Keep explanations direct, user-focused, and free of internal implementation details
22
+
23
+ ### Example
24
+
25
+ ```typescript
26
+ /**
27
+ * Validates if an entry is fresh and can be used.
28
+ * @param state - The cache state.
29
+ * @param entry - The cache entry to validate.
30
+ * @param now - The current timestamp.
31
+ * @returns True if the entry is fresh, false otherwise.
32
+ */
33
+ export const isFresh = (state: CacheState, entry: CacheEntry, now: number): boolean => {
34
+ return entry[0][1] > now;
35
+ };
36
+ ```
37
+
38
+ ---
39
+
40
+ **Remember:** Keep it simple, keep it functional, keep it tested.
@@ -0,0 +1,30 @@
1
+ # Opening Issues
2
+
3
+ Thank you for helping improve Neezco Cache!
4
+
5
+ ## 🐛 Bug Reports
6
+
7
+ When reporting a bug, please include:
8
+
9
+ - **Clear description** of the issue
10
+ - **Steps to reproduce** the problem
11
+ - **Expected vs actual behavior**
12
+ - **Your environment**: Node.js version, OS, etc.
13
+
14
+ **[Open a bug report →](https://github.com/neezco/cache/issues)**
15
+
16
+ ## ✨ Feature Requests
17
+
18
+ When requesting a feature, please:
19
+
20
+ - **Describe** what you want to achieve
21
+ - **Explain why** this feature would be useful
22
+ - **Provide examples** or use cases
23
+
24
+ **[Open a feature request →](https://github.com/neezco/cache/issues)**
25
+
26
+ ---
27
+
28
+ ## Questions?
29
+
30
+ Feel free to open a discussion on GitHub. We're happy to help!
@@ -0,0 +1,35 @@
1
+ # Project Structure
2
+
3
+ Neezco Cache follows a **functional, domain-driven architecture**.
4
+
5
+ ## Directory Layout
6
+
7
+ ```
8
+ src/
9
+ ├── index.ts # Public API exports and LocalTtlCache class
10
+ ├── types.ts # Type definitions
11
+ ├── defaults.ts # Default configuration values
12
+ ├── cache/ # Core caching operations
13
+ │ ├── set.ts # Set/update cache entries
14
+ │ ├── get.ts # Get entries (with expiration/stale checks)
15
+ │ ├── delete.ts # Delete entries
16
+ │ └── ...
17
+ ├── sweep/ # Automatic cleanup operations
18
+ │ ├── sweep.ts # Main sweep loop
19
+ │ └── ... # Optimization utilities
20
+ └── utils/ # Utilities (memory monitoring, etc.)
21
+ └── ...
22
+ ```
23
+
24
+ ## Core Philosophy
25
+
26
+ - **Pure Functions First**: All business logic is implemented as pure functions
27
+ - **Dependency Injection**: Dependencies that can change are injected
28
+ - **Simple & Testable**: Each function has a single responsibility
29
+ - **Type Safety**: Strict TypeScript typing throughout
30
+
31
+ The [`LocalTtlCache`](../../src/index.ts) class is a convenience wrapper around these pure functions. It provides object-oriented API while maintaining functional code inside.
32
+
33
+ ---
34
+
35
+ **When adding new functionality:** Follow the domain structure. Create functions in the appropriate `src/` subdirectory.
@@ -0,0 +1,38 @@
1
+ # Available Scripts
2
+
3
+ Use these scripts during development:
4
+
5
+ ## Building
6
+
7
+ | Script | Purpose |
8
+ | ------------------ | ---------------------------------------------- |
9
+ | `pnpm build` | Transpiles TypeScript to JavaScript |
10
+ | `pnpm build:watch` | Watches for changes and rebuilds automatically |
11
+
12
+ ## Linting & Formatting
13
+
14
+ | Script | Purpose |
15
+ | ---------------------- | ----------------------------------------- |
16
+ | `pnpm lint` | Runs ESLint to detect code quality issues |
17
+ | `pnpm lint:fix` | Automatically fixes ESLint problems |
18
+ | `pnpm format:prettier` | Formats the entire codebase with Prettier |
19
+ | `pnpm format:all` | Runs linting fixes and Prettier together |
20
+
21
+ ## Type Checking & Validation
22
+
23
+ | Script | Purpose |
24
+ | ---------------- | ------------------------------------------------------------------------ |
25
+ | `pnpm typecheck` | Runs TypeScript type checker without emitting files |
26
+ | `pnpm check:all` | **Run before committing.** Runs formatting and type checking in parallel |
27
+
28
+ ## Testing
29
+
30
+ | Script | Purpose |
31
+ | -------------------- | ---------------------------------------------------- |
32
+ | `pnpm test` | Runs the test suite once |
33
+ | `pnpm test:watch` | Runs tests in watch mode (useful during development) |
34
+ | `pnpm test:coverage` | Generates test coverage reports |
35
+
36
+ ---
37
+
38
+ **Before committing:** Always run `pnpm check:all` to ensure everything passes.
@@ -0,0 +1,49 @@
1
+ # Setting Up Your Environment
2
+
3
+ ## Prerequisites
4
+
5
+ This repository comes pre-configured with:
6
+
7
+ - **ESLint** for linting
8
+ - **Prettier** for formatting
9
+ - **Husky** for Git hooks
10
+ - **Conventional Commits** validation
11
+ - **Type checking** and **test scripts**
12
+
13
+ ## Installation
14
+
15
+ 1. **Fork and clone** the repository:
16
+
17
+ ```bash
18
+ git clone https://github.com/YOUR_USERNAME/cache.git
19
+ cd cache
20
+ ```
21
+
22
+ 2. **Add upstream repository**:
23
+
24
+ ```bash
25
+ git remote add upstream https://github.com/neezco/cache.git
26
+ ```
27
+
28
+ 3. **Install dependencies**:
29
+
30
+ ```bash
31
+ pnpm install
32
+ ```
33
+
34
+ 4. **Initialize Husky** for Git hooks:
35
+
36
+ ```bash
37
+ pnpm prepare
38
+ ```
39
+
40
+ ## What Husky Does
41
+
42
+ Once initialized, Husky automatically validates your changes:
43
+
44
+ | Hook | What it runs |
45
+ | -------------- | ----------------------------------- |
46
+ | **Pre-commit** | ESLint, Prettier, and type checking |
47
+ | **Pre-push** | Full test suite |
48
+
49
+ You're ready to start contributing!
@@ -0,0 +1,71 @@
1
+ # Contribution Workflow
2
+
3
+ ## 1. Create a Feature Branch
4
+
5
+ ```bash
6
+ git checkout -b feat/your-feature-name
7
+ ```
8
+
9
+ Use conventional commit types for branch names:
10
+
11
+ - `feat/` for features
12
+ - `fix/` for bug fixes
13
+ - `docs/` for documentation
14
+ - `refactor/` for refactoring
15
+
16
+ ## 2. Make Your Changes
17
+
18
+ Follow the [Code Style Guidelines](code-style.md).
19
+
20
+ ## 3. Verify Your Work
21
+
22
+ Run the full check suite before committing:
23
+
24
+ ```bash
25
+ pnpm check:all
26
+ ```
27
+
28
+ This runs formatting, linting, and type checking in parallel.
29
+
30
+ ## 4. Commit with Conventional Commits
31
+
32
+ ```bash
33
+ git commit -m "feat: add your feature"
34
+ ```
35
+
36
+ Husky validates your commit message. Use these types:
37
+
38
+ | Type | Purpose |
39
+ | ----------- | ----------------------------------------- |
40
+ | `feat:` | A new feature |
41
+ | `fix:` | A bug fix |
42
+ | `docs:` | Documentation changes |
43
+ | `style:` | Formatting only (no code changes) |
44
+ | `refactor:` | Code refactoring without behavior changes |
45
+ | `test:` | Adding or updating tests |
46
+ | `chore:` | Maintenance tasks (configs, tooling, CI) |
47
+
48
+ ### Examples
49
+
50
+ ```bash
51
+ git commit -m "feat: add has method to LocalTtlCache"
52
+ git commit -m "fix: prevent expired entries from being returned"
53
+ git commit -m "test: add test coverage for stale window behavior"
54
+ git commit -m "docs: update API reference"
55
+ ```
56
+
57
+ ## 5. Push and Create a Pull Request
58
+
59
+ ```bash
60
+ git push origin feat/your-feature-name
61
+ ```
62
+
63
+ Then create a pull request on GitHub:
64
+
65
+ - Reference any related issues
66
+ - Provide a clear description of your changes
67
+ - Ensure all tests and checks pass
68
+
69
+ ---
70
+
71
+ **That's it!** The maintainers will review your PR soon.
@@ -0,0 +1,66 @@
1
+ # Stale Windows
2
+
3
+ Understand how to use stale windows to control memory usage and improve performance gracefully.
4
+
5
+ ---
6
+
7
+ ## What is a Stale Window?
8
+
9
+ A stale window is a period after an entry expires where it can still be served. Instead of immediately removing expired data, Neezco Cache allows you to keep serving it while managing memory:
10
+
11
+ ```javascript
12
+ const cache = new LocalTtlCache({
13
+ defaultTtl: 5 * 60 * 1000, // Fresh for 5 minutes
14
+ defaultStaleWindow: 2 * 60 * 1000, // Serve stale for 2 more minutes
15
+ });
16
+
17
+ cache.set("data", "current value");
18
+
19
+ // Minutes 0-5: data is fresh
20
+ console.log(cache.get("data")); // "current value"
21
+
22
+ // Minutes 5-7: data is expired but still served (stale)
23
+ console.log(cache.get("data")); // "current value" (as stale)
24
+
25
+ // After 7 minutes: data is completely gone
26
+ console.log(cache.get("data")); // undefined
27
+ ```
28
+
29
+ ---
30
+
31
+ ## Stale Windows as Memory Control
32
+
33
+ The real power of stale windows is **controlling how memory is freed** in response to application needs.
34
+
35
+ After an entry expires, it can still be served as stale data. What happens next depends on your configuration: you can delete it immediately, delete it during background cleanup, or keep it around until memory is needed.
36
+
37
+ ---
38
+
39
+ ## Memory Control: Purging Stale Entries
40
+
41
+ By default, stale entries remain in cache until explicitly deleted or storage is needed. Two options control what happens:
42
+
43
+ **`purgeStaleOnGet: true`** — Delete stale entries immediately when accessed.
44
+
45
+ **`purgeStaleOnSweep: true`** — Delete stale entries during background cleanup operations.
46
+
47
+ Both are optional and default to `false`, meaning stale data persists until you need the memory.
48
+
49
+ ---
50
+
51
+ ## Summary
52
+
53
+ Stale windows give your application flexibility to match its memory profile:
54
+
55
+ - **Need tight memory control?** Use `purgeStaleOnGet` to delete immediately
56
+ - **Normal resource availability?** Let stale data remain—it's useful
57
+ - **Configurable behavior** through `purgeStaleOnGet` and `purgeStaleOnSweep`
58
+
59
+ The key benefit: graceful degradation. Your app continues serving data even after expiration, keeping the user experience responsive while managing memory appropriately.
60
+
61
+ ---
62
+
63
+ ## Next
64
+
65
+ - **[Tag-Based Invalidation](./tag-based-invalidation.md)** - Control cache invalidation
66
+ - **[Back to Examples](../examples.md)**
@@ -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)**