@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.
- package/CHANGELOG.md +22 -0
- package/README.md +3 -3
- package/dist/browser/index.d.ts +30 -6
- package/dist/browser/index.js +30 -8
- package/dist/browser/index.js.map +1 -1
- package/dist/node/index.cjs +31 -8
- package/dist/node/index.cjs.map +1 -1
- package/dist/node/index.d.cts +30 -6
- package/dist/node/index.d.mts +30 -6
- package/dist/node/index.mjs +31 -8
- package/dist/node/index.mjs.map +1 -1
- package/docs/api-reference.md +21 -6
- package/docs/configuration.md +42 -3
- package/docs/contributing/code-style.md +40 -0
- package/docs/contributing/issues.md +30 -0
- package/docs/contributing/project-structure.md +35 -0
- package/docs/contributing/scripts.md +38 -0
- package/docs/contributing/setup.md +49 -0
- package/docs/contributing/workflow.md +71 -0
- 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 +2 -2
package/docs/api-reference.md
CHANGED
|
@@ -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
|
-
):
|
|
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:**
|
|
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,
|
|
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
|
|
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
|
package/docs/configuration.md
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
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)**
|