@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.
Files changed (2) hide show
  1. package/README.md +165 -43
  2. 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`. Designed to compose with TaskFn pipelines and shared across the Task ecosystem.
3
+ Cache primitives and combinators for `@gantryland/task`. Compose caching into TaskFn pipelines with minimal surface area and predictable behavior.
4
4
 
5
- Works in browser and Node.js with no dependencies.
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, staleWhileRevalidate } from "@gantryland/task-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
- ### Cache events
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 store with tag invalidation and event emission.
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, otherwise fetch and cache.
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
- Options:
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
- ## Practical examples
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.0",
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.0"
39
+ "@gantryland/task": "^0.2.1"
40
40
  }
41
41
  }