@gantryland/task-cache 0.2.0 → 0.2.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/README.md +165 -43
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# @gantryland/task-cache
|
|
2
2
|
|
|
3
|
-
Cache primitives and combinators for `@gantryland/task`.
|
|
3
|
+
Cache primitives and combinators for `@gantryland/task`. Compose caching into TaskFn pipelines with minimal surface area and predictable behavior.
|
|
4
4
|
|
|
5
|
-
Works
|
|
5
|
+
- Works with any TaskFn, no framework coupling.
|
|
6
|
+
- Built-in in-memory store with tag invalidation and events.
|
|
7
|
+
- Stale-while-revalidate and dedupe support out of the box.
|
|
8
|
+
- Works in browser and Node.js with no dependencies.
|
|
6
9
|
|
|
7
10
|
## Installation
|
|
8
11
|
|
|
@@ -10,11 +13,25 @@ Works in browser and Node.js with no dependencies.
|
|
|
10
13
|
npm install @gantryland/task-cache
|
|
11
14
|
```
|
|
12
15
|
|
|
16
|
+
## Contents
|
|
17
|
+
|
|
18
|
+
- [Quick start](#quick-start)
|
|
19
|
+
- [Design goals](#design-goals)
|
|
20
|
+
- [When to use task-cache](#when-to-use-task-cache)
|
|
21
|
+
- [When not to use task-cache](#when-not-to-use-task-cache)
|
|
22
|
+
- [Core concepts](#core-concepts)
|
|
23
|
+
- [Flow](#flow)
|
|
24
|
+
- [API](#api)
|
|
25
|
+
- [Common patterns](#common-patterns)
|
|
26
|
+
- [Integrations](#integrations)
|
|
27
|
+
- [Related packages](#related-packages)
|
|
28
|
+
- [Tests](#tests)
|
|
29
|
+
|
|
13
30
|
## Quick start
|
|
14
31
|
|
|
15
32
|
```typescript
|
|
16
33
|
import { Task } from "@gantryland/task";
|
|
17
|
-
import { MemoryCacheStore, cache
|
|
34
|
+
import { MemoryCacheStore, cache } from "@gantryland/task-cache";
|
|
18
35
|
import { pipe } from "@gantryland/task-combinators";
|
|
19
36
|
|
|
20
37
|
const store = new MemoryCacheStore();
|
|
@@ -28,15 +45,27 @@ const usersTask = new Task(
|
|
|
28
45
|
|
|
29
46
|
await usersTask.run(); // fetches and caches
|
|
30
47
|
await usersTask.run(); // cache hit
|
|
31
|
-
|
|
32
|
-
const swrTask = new Task(
|
|
33
|
-
pipe(
|
|
34
|
-
(signal) => fetch("/api/users", { signal }).then((r) => r.json()),
|
|
35
|
-
staleWhileRevalidate("users", store, { ttl: 30_000, staleTtl: 60_000 })
|
|
36
|
-
)
|
|
37
|
-
);
|
|
38
48
|
```
|
|
39
49
|
|
|
50
|
+
This example shows `cache` reuse for fresh data.
|
|
51
|
+
|
|
52
|
+
## Design goals
|
|
53
|
+
|
|
54
|
+
- Make caching explicit and composable at the TaskFn level.
|
|
55
|
+
- Keep stores minimal so you can swap implementations.
|
|
56
|
+
- Provide deterministic cache semantics and clear invalidation paths.
|
|
57
|
+
|
|
58
|
+
## When to use task-cache
|
|
59
|
+
|
|
60
|
+
- You want reuse across TaskFn calls with TTLs.
|
|
61
|
+
- You need stale-while-revalidate behavior.
|
|
62
|
+
- You want tag-based invalidation or cache events.
|
|
63
|
+
|
|
64
|
+
## When not to use task-cache
|
|
65
|
+
|
|
66
|
+
- You need a full data layer with automatic normalization.
|
|
67
|
+
- You cannot tolerate stale reads of any kind.
|
|
68
|
+
|
|
40
69
|
## Core concepts
|
|
41
70
|
|
|
42
71
|
### CacheStore
|
|
@@ -68,35 +97,131 @@ type CacheEntry<T> = {
|
|
|
68
97
|
}
|
|
69
98
|
```
|
|
70
99
|
|
|
71
|
-
###
|
|
100
|
+
### CacheEvent
|
|
72
101
|
|
|
73
102
|
Stores can emit events to power analytics, logging, or invalidation tracing.
|
|
74
103
|
|
|
75
104
|
Event types: `hit`, `miss`, `stale`, `set`, `invalidate`, `clear`, `revalidate`.
|
|
76
105
|
|
|
106
|
+
### CacheKey
|
|
107
|
+
|
|
108
|
+
Cache keys can be strings, numbers, or symbols. Use `cacheKey` for consistent keys across call sites.
|
|
109
|
+
|
|
110
|
+
## Flow
|
|
111
|
+
|
|
112
|
+
```text
|
|
113
|
+
cache(): fresh -> return
|
|
114
|
+
cache(): stale/miss -> fetch -> store -> return
|
|
115
|
+
|
|
116
|
+
staleWhileRevalidate(): fresh -> return
|
|
117
|
+
staleWhileRevalidate(): stale -> return stale -> revalidate in background
|
|
118
|
+
staleWhileRevalidate(): miss -> fetch -> store -> return
|
|
119
|
+
```
|
|
120
|
+
|
|
77
121
|
## API
|
|
78
122
|
|
|
123
|
+
### API at a glance
|
|
124
|
+
|
|
125
|
+
| Member | Purpose | Returns |
|
|
126
|
+
| --- | --- | --- |
|
|
127
|
+
| **Stores** | | |
|
|
128
|
+
| [`MemoryCacheStore`](#memorycachestore) | In-memory store with tags and events | `MemoryCacheStore` |
|
|
129
|
+
| **Combinators** | | |
|
|
130
|
+
| [`cache`](#cache) | Cache with TTL and dedupe | `(taskFn) => TaskFn` |
|
|
131
|
+
| [`staleWhileRevalidate`](#stalewhilerevalidate) | Serve stale and refresh in background | `(taskFn) => TaskFn` |
|
|
132
|
+
| [`invalidateOnResolve`](#invalidateonresolve) | Invalidate after TaskFn resolves | `(taskFn) => TaskFn` |
|
|
133
|
+
| **Helpers** | | |
|
|
134
|
+
| [`cacheKey`](#cachekey) | Build consistent cache keys | `string` |
|
|
135
|
+
|
|
79
136
|
### MemoryCacheStore
|
|
80
137
|
|
|
81
|
-
In-memory
|
|
138
|
+
In-memory CacheStore with tag invalidation and event emission.
|
|
82
139
|
|
|
83
140
|
```typescript
|
|
84
|
-
import { MemoryCacheStore } from "@gantryland/task-cache";
|
|
85
|
-
|
|
86
141
|
const store = new MemoryCacheStore();
|
|
87
|
-
store.subscribe((event) => console.log(event.type, event.key));
|
|
88
|
-
store.invalidateTags(["users"]);
|
|
89
142
|
```
|
|
90
143
|
|
|
144
|
+
#### store.get
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
store.get<T>(key: CacheKey): CacheEntry<T> | undefined
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Read an entry by key.
|
|
151
|
+
|
|
152
|
+
#### store.set
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
store.set<T>(key: CacheKey, entry: CacheEntry<T>): void
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Write an entry by key and emit a `set` event.
|
|
159
|
+
|
|
160
|
+
#### store.delete
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
store.delete(key: CacheKey): void
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
Delete an entry by key and emit an `invalidate` event.
|
|
167
|
+
|
|
168
|
+
#### store.clear
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
store.clear(): void
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
Clear all entries and emit a `clear` event.
|
|
175
|
+
|
|
176
|
+
#### store.has
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
store.has(key: CacheKey): boolean
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Check whether a key exists.
|
|
183
|
+
|
|
184
|
+
#### store.keys
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
store.keys(): Iterable<CacheKey>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Return all keys.
|
|
191
|
+
|
|
192
|
+
#### store.subscribe
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
store.subscribe(listener: (event: CacheEvent) => void): () => void
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Listen to cache events. Returns an unsubscribe function.
|
|
199
|
+
|
|
200
|
+
#### store.emit
|
|
201
|
+
|
|
202
|
+
```typescript
|
|
203
|
+
store.emit(event: CacheEvent): void
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Emit a cache event to subscribers.
|
|
207
|
+
|
|
208
|
+
#### store.invalidateTags
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
store.invalidateTags(tags: string[]): void
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Invalidate all entries matching any tag.
|
|
215
|
+
|
|
91
216
|
### cache
|
|
92
217
|
|
|
93
|
-
Return cached data if fresh
|
|
218
|
+
Return cached data if fresh; otherwise fetch and cache. Dedupe is enabled by default.
|
|
94
219
|
|
|
95
220
|
```typescript
|
|
96
|
-
cache("users", store, { ttl: 60_000, tags: ["users"] })
|
|
221
|
+
cache("users", store, { ttl: 60_000, tags: ["users"], dedupe: true })
|
|
97
222
|
```
|
|
98
223
|
|
|
99
|
-
|
|
224
|
+
#### CacheOptions
|
|
100
225
|
|
|
101
226
|
```typescript
|
|
102
227
|
type CacheOptions = {
|
|
@@ -109,7 +234,7 @@ type CacheOptions = {
|
|
|
109
234
|
|
|
110
235
|
### staleWhileRevalidate
|
|
111
236
|
|
|
112
|
-
Return stale data (if within stale window) and refresh in the background.
|
|
237
|
+
Return stale data (if within the stale window) and refresh in the background.
|
|
113
238
|
|
|
114
239
|
```typescript
|
|
115
240
|
staleWhileRevalidate("users", store, { ttl: 30_000, staleTtl: 60_000 })
|
|
@@ -133,7 +258,9 @@ Helper for consistent cache keys.
|
|
|
133
258
|
cacheKey("user", userId)
|
|
134
259
|
```
|
|
135
260
|
|
|
136
|
-
##
|
|
261
|
+
## Common patterns
|
|
262
|
+
|
|
263
|
+
Use these patterns for most usage.
|
|
137
264
|
|
|
138
265
|
### Cache with Task and pipe
|
|
139
266
|
|
|
@@ -200,12 +327,27 @@ const createTask = new Task(
|
|
|
200
327
|
### In-flight dedupe
|
|
201
328
|
|
|
202
329
|
```typescript
|
|
203
|
-
import { cache } from "@gantryland/task-cache";
|
|
204
|
-
|
|
205
330
|
cache("users", store, { ttl: 10_000, dedupe: true });
|
|
206
331
|
cache("users", store, { ttl: 10_000, dedupe: false });
|
|
207
332
|
```
|
|
208
333
|
|
|
334
|
+
### Observe cache events
|
|
335
|
+
|
|
336
|
+
```typescript
|
|
337
|
+
const store = new MemoryCacheStore();
|
|
338
|
+
|
|
339
|
+
const unsub = store.subscribe((event) => {
|
|
340
|
+
console.log(event.type, event.key, event.entry?.updatedAt);
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// Later
|
|
344
|
+
unsub();
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## Integrations
|
|
348
|
+
|
|
349
|
+
Compose with other Gantryland utilities. This section shows common pairings.
|
|
350
|
+
|
|
209
351
|
### Persist cache with task-storage
|
|
210
352
|
|
|
211
353
|
```typescript
|
|
@@ -224,26 +366,6 @@ const task = new Task(
|
|
|
224
366
|
);
|
|
225
367
|
```
|
|
226
368
|
|
|
227
|
-
### Observe cache events
|
|
228
|
-
|
|
229
|
-
```typescript
|
|
230
|
-
const store = new MemoryCacheStore();
|
|
231
|
-
|
|
232
|
-
const unsub = store.subscribe((event) => {
|
|
233
|
-
console.log(event.type, event.key, event.entry?.updatedAt);
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
// Later
|
|
237
|
-
unsub();
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
## Notes
|
|
241
|
-
|
|
242
|
-
- `cache` is strict: expired entries are re-fetched.
|
|
243
|
-
- `staleWhileRevalidate` returns stale data during the stale window and refreshes in the background.
|
|
244
|
-
- In-flight dedupe is enabled by default (`dedupe: false` to opt out).
|
|
245
|
-
- Tag invalidation requires a store that supports `invalidateTags` (MemoryCacheStore and StorageCacheStore do).
|
|
246
|
-
|
|
247
369
|
## Related packages
|
|
248
370
|
|
|
249
371
|
- [@gantryland/task](../task/) - Core Task abstraction
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gantryland/task-cache",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Cache store and combinator for @gantryland/task",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"task",
|
|
@@ -36,6 +36,6 @@
|
|
|
36
36
|
},
|
|
37
37
|
"homepage": "https://github.com/joehoot/gantryland/tree/main/packages/task-cache#readme",
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@gantryland/task": "^0.2.
|
|
39
|
+
"@gantryland/task": "^0.2.1"
|
|
40
40
|
}
|
|
41
41
|
}
|