@maestro-js/agent-skills 1.0.0-alpha.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.
@@ -0,0 +1,306 @@
1
+ ---
2
+ name: broadcast
3
+ description: "\"Pattern-based event routing across channels and processes using @maestro-js/broadcast. Use when working with @maestro-js/broadcast, channel patterns, broadcast listeners, event name generation, or wildcard/dynamic-segment routing. Key capabilities: define typed channels with URL-like patterns (:id, optional segments, wildcards), emit and listen for events through a pluggable driver (e.g. Events/Redis), extract params from matched patterns, generate concrete event names from patterns, and manage listener lifecycles.\""
4
+ ---
5
+
6
+ # @maestro-js/broadcast
7
+
8
+ Pattern-based event routing that layers typed channels, dynamic-segment matching, and wildcard support on top of a pluggable driver (typically `@maestro-js/events`). Each channel is a typed event emitter scoped to a URL-like pattern; the driver distributes emitted events across processes.
9
+
10
+ ## Quick Setup
11
+
12
+ ```ts
13
+ import { Broadcast } from '@maestro-js/broadcast'
14
+ import { Events } from '@maestro-js/events'
15
+
16
+ // 1. Define typed channels
17
+ const userChannel = Broadcast.channel('/user/:id').create<{ name: string }>()
18
+ const chatChannel = Broadcast.channel('/chat(/:room)').create<{ message: string }>()
19
+
20
+ // 2. Create and register
21
+ const broadcast = Broadcast.Provider.create({
22
+ channels: [userChannel, chatChannel],
23
+ driver: Events // any BroadcastingDriver
24
+ })
25
+ Broadcast.Provider.register('default', broadcast)
26
+
27
+ // 3. Use via facade
28
+ Broadcast.addListener('/user/42', {
29
+ handler({ data, params }) {
30
+ console.log(data.name, params.id)
31
+ }
32
+ })
33
+
34
+ const event = Broadcast.generateEventName('/user/:id', { id: '42' })
35
+ Broadcast.emit(event, { name: 'Alice' })
36
+ ```
37
+
38
+ ## Channel Patterns and Parameters
39
+
40
+ Patterns use `@remix-run/route-pattern` syntax. Three segment types:
41
+
42
+ | Syntax | Example | Matches |
43
+ |---|---|---|
44
+ | `:param` | `/user/:id` | `/user/42` -> `{ id: '42' }` |
45
+ | `(/:param)` | `/chat(/:room)` | `/chat` or `/chat/general` |
46
+ | `*splat` | `/system/*splat` | `/system/logs/today` -> `{ splat: 'logs/today' }` |
47
+
48
+ Matching is case-insensitive. Static patterns (e.g. `/health`) also work.
49
+
50
+ ### Pattern Matching
51
+
52
+ Use `Broadcast.matchPattern` to test if an event name matches a pattern:
53
+
54
+ ```ts
55
+ const match = Broadcast.matchPattern('/user/:id', '/user/42')
56
+ // match.params.id === '42'
57
+ // match.eventName === '/user/42'
58
+ // match.eventNameBase === '/user/42'
59
+ // match.pattern === '/user/:id'
60
+
61
+ const noMatch = Broadcast.matchPattern('/user/:id', '/order/42')
62
+ // noMatch === null
63
+ ```
64
+
65
+ For wildcard patterns, `eventNameBase` strips the wildcard suffix:
66
+
67
+ ```ts
68
+ const match = Broadcast.matchPattern('/system/*splat', '/system/logs/today')
69
+ // match.params.splat === 'logs/today'
70
+ // match.eventNameBase === '/system'
71
+ ```
72
+
73
+ ### Event Name Generation
74
+
75
+ Interpolate params into a pattern to produce a concrete event name:
76
+
77
+ ```ts
78
+ Broadcast.generateEventName('/user/:id', { id: '42' })
79
+ // '/user/42'
80
+
81
+ Broadcast.generateEventName('/org/:orgId/user/:userId', { orgId: 'acme', userId: '42' })
82
+ // '/org/acme/user/42'
83
+
84
+ Broadcast.generateEventName('/chat(/:room)', { room: 'general' })
85
+ // '/chat/general'
86
+
87
+ Broadcast.generateEventName('/chat(/:room)')
88
+ // '/chat'
89
+ ```
90
+
91
+ ## API Reference
92
+
93
+ ### `Broadcast.channel(pattern)`
94
+
95
+ Create a channel builder. Call `.create<EventData>()` to produce a typed `Channel`.
96
+
97
+ ```ts
98
+ function channel<Pattern extends string>(pattern: Pattern): {
99
+ create<EventData extends Record<string, any>>(): Channel<Pattern, EventData>
100
+ }
101
+ ```
102
+
103
+ ### `Broadcast.Provider.create(config)`
104
+
105
+ Create a broadcast service instance from channels and a driver.
106
+
107
+ ```ts
108
+ function create<Channels extends Channel<string, Record<string, any>>[]>(config: {
109
+ channels: Channels
110
+ driver: BroadcastingDriver
111
+ }): BroadcastService<Channels>
112
+ ```
113
+
114
+ Throws if duplicate channel patterns are provided.
115
+
116
+ ### `Broadcast.Provider.register(name, service)`
117
+
118
+ Register the service instance in the named registry.
119
+
120
+ ### `Broadcast.provider(key)`
121
+
122
+ Resolve a named broadcast instance. Returns the full `BroadcastService` interface.
123
+
124
+ ### `Broadcast.emit(eventName, data)`
125
+
126
+ Broadcast data through the driver. Throws if the event name does not match any registered channel pattern.
127
+
128
+ ```ts
129
+ Broadcast.emit('/user/42', { name: 'Alice' })
130
+ ```
131
+
132
+ ### `Broadcast.addListener(eventName, listener)`
133
+
134
+ Subscribe to events matching a channel pattern. Returns a handle with `remove()`.
135
+
136
+ ```ts
137
+ const handle = Broadcast.addListener('/user/42', {
138
+ handler({ data, params, eventName }) {
139
+ // data: { name: string }
140
+ // params: { id: string }
141
+ // eventName: '/user/42'
142
+ }
143
+ })
144
+
145
+ handle.remove() // unsubscribe
146
+ ```
147
+
148
+ ### `Broadcast.getChannel(pattern)`
149
+
150
+ Retrieve a channel by its exact pattern string. Returns the channel without `emit` (read-only view).
151
+
152
+ ```ts
153
+ const channel = Broadcast.getChannel('/user/:id')
154
+ // channel.pattern === '/user/:id'
155
+ // channel.addListener(...)
156
+ ```
157
+
158
+ ### `Broadcast.generateEventName(pattern, params)`
159
+
160
+ Interpolate parameters into a pattern to produce a concrete event name string.
161
+
162
+ ### `Broadcast.matchPattern(pattern, eventName)`
163
+
164
+ Test if an event name matches a pattern. Returns `EventNameMatch` or `null`.
165
+
166
+ ### `Broadcast.removeAllListeners()`
167
+
168
+ Remove all active listeners from all channels and the driver.
169
+
170
+ ## BroadcastingDriver Interface
171
+
172
+ Any driver must implement:
173
+
174
+ ```ts
175
+ interface BroadcastingDriver {
176
+ emit(eventName: string, data: Record<string, any>): unknown
177
+ addListener(eventName: string, listener: (data: Record<string, any>) => void): unknown
178
+ removeListener(eventName: string, listener: (data: Record<string, any>) => void): unknown
179
+ removeAllListeners?(): unknown
180
+ }
181
+ ```
182
+
183
+ `@maestro-js/events` satisfies this interface. A minimal mock driver for testing:
184
+
185
+ ```ts
186
+ const listeners: Map<string, Set<(data: Record<string, any>) => void>> = new Map()
187
+
188
+ const mockDriver = {
189
+ emit(eventName: string, data: Record<string, any>) {
190
+ const set = listeners.get(eventName)
191
+ if (set) {
192
+ for (const listener of set) listener(data)
193
+ }
194
+ },
195
+ addListener(eventName: string, listener: (data: Record<string, any>) => void) {
196
+ if (!listeners.has(eventName)) listeners.set(eventName, new Set())
197
+ listeners.get(eventName)!.add(listener)
198
+ },
199
+ removeListener(eventName: string, listener: (data: Record<string, any>) => void) {
200
+ listeners.get(eventName)?.delete(listener)
201
+ }
202
+ }
203
+ ```
204
+
205
+ ## Key Types
206
+
207
+ ```ts
208
+ // Extracts union of dynamic param names from a pattern
209
+ type PatternParam<Pattern extends string>
210
+
211
+ // Match result from matchPattern
212
+ interface EventNameMatch<Pattern extends string> {
213
+ params: PatternParams<Pattern>
214
+ eventName: string
215
+ eventNameBase: string
216
+ pattern: Pattern
217
+ }
218
+
219
+ // Listener object
220
+ interface ChannelListener<Pattern extends string, EventData extends Record<string, any>> {
221
+ handler: (options: { data: EventData; params: PatternParams<Pattern>; eventName: string }) => void
222
+ }
223
+
224
+ // Handle returned by addListener
225
+ type AddListenerResult = { remove(): void }
226
+ ```
227
+
228
+ ## Common Patterns
229
+
230
+ ### Multiple Named Providers
231
+
232
+ ```ts
233
+ declare module '@maestro-js/broadcast' {
234
+ namespace Broadcast.Provider {
235
+ interface Keys {
236
+ default: [typeof userChannel, typeof chatChannel]
237
+ admin: [typeof adminChannel]
238
+ }
239
+ }
240
+ }
241
+
242
+ Broadcast.Provider.register('default', defaultBroadcast)
243
+ Broadcast.Provider.register('admin', adminBroadcast)
244
+
245
+ const admin = Broadcast.provider('admin')
246
+ admin.emit('/admin/alerts', { level: 'critical' })
247
+ ```
248
+
249
+ ### Listen Then Emit
250
+
251
+ ```ts
252
+ const orderChannel = Broadcast.channel('/order/:orderId').create<{ status: string }>()
253
+ const broadcast = Broadcast.Provider.create({ channels: [orderChannel], driver: Events })
254
+ Broadcast.Provider.register('default', broadcast)
255
+
256
+ // Subscribe
257
+ Broadcast.addListener('/order/123', {
258
+ handler({ data, params }) {
259
+ console.log(`Order ${params.orderId} is ${data.status}`)
260
+ }
261
+ })
262
+
263
+ // Emit with generated event name
264
+ const event = Broadcast.generateEventName('/order/:orderId', { orderId: '123' })
265
+ Broadcast.emit(event, { status: 'shipped' })
266
+ ```
267
+
268
+ ### Cleanup
269
+
270
+ ```ts
271
+ // Remove a single listener
272
+ const handle = Broadcast.addListener('/user/42', { handler() {} })
273
+ handle.remove()
274
+
275
+ // Remove all listeners at once
276
+ Broadcast.removeAllListeners()
277
+ ```
278
+
279
+ ## Testing
280
+
281
+ Run tests:
282
+
283
+ ```bash
284
+ pnpm --filter @maestro-js/broadcast test
285
+ ```
286
+
287
+ Or a single file:
288
+
289
+ ```bash
290
+ cd packages/broadcast && npx beartest ./tests/broadcast.test.ts
291
+ ```
292
+
293
+ Tests use `beartest-js` with `expect` for assertions. Use the mock driver pattern shown above for unit tests -- no real Redis or Events instance required.
294
+
295
+ ## Cross-Package Integration
296
+
297
+ - **@maestro-js/service-registry** -- powers the Provider pattern (create, register, resolve via proxy)
298
+ - **@maestro-js/events** -- primary driver for cross-process event distribution (Redis, WebSocket, or Local)
299
+ - **@remix-run/route-pattern** -- handles URL-like pattern matching and parameter extraction
300
+
301
+ ### Source Files
302
+
303
+ - `packages/broadcast/src/index.ts` -- main export, Provider, facade, channel factory
304
+ - `packages/broadcast/src/broadcasting-helpers.ts` -- `matchPattern` and `generateEventName` implementations
305
+ - `packages/broadcast/src/broadcasting-types.ts` -- all type definitions (Channel, Broadcast, BroadcastingDriver, etc.)
306
+ - `packages/broadcast/tests/broadcast.test.ts` -- full test suite
@@ -0,0 +1,337 @@
1
+ ---
2
+ name: cache-control
3
+ description: "HTTP Cache-Control-inspired caching for async functions with caller/callee directives. Use when working with @maestro-js/cache-control. Key capabilities: wrap async functions with cacheFn to add cache-control semantics, set response directives (maxAge, staleWhileRevalidate, noStore) from the callee, set request directives (maxAge, noStore) from the caller, automatic background revalidation of stale data, in-flight request deduplication, built-in LRU cache or custom cache backends, human-readable duration strings (e.g. '5 minutes'), structured logging of all cache lifecycle events, and optional tracing integration for observability."
4
+ ---
5
+
6
+ ## Overview
7
+
8
+ `@maestro-js/cache-control` provides an HTTP Cache-Control-inspired caching layer for async functions. Both the caller and callee provide directives that together determine whether a cached response is returned, a fresh value is fetched, or a stale value is served while revalidation happens in the background. It follows the maestro Provider pattern built on `@maestro-js/service-registry`.
9
+
10
+ ## Quick Setup
11
+
12
+ ```ts
13
+ import { CacheControl } from '@maestro-js/cache-control'
14
+ import { Log } from '@maestro-js/log'
15
+
16
+ // Create a logger
17
+ const logger = Log.create<[CacheControl.LogMessage]>({
18
+ transports: [{ write(msg) { console.log(msg) } }]
19
+ })
20
+
21
+ // Create and register the service
22
+ const cc = CacheControl.Provider.create({
23
+ cache: CacheControl.caches.leastRecentlyUsed({ max: 100 }),
24
+ logger
25
+ })
26
+ CacheControl.Provider.register('default', cc)
27
+ ```
28
+
29
+ After registering the default provider, use the facade directly:
30
+
31
+ ```ts
32
+ const getUser = CacheControl.cacheFn('users/getById', async (id: string) => {
33
+ const user = await fetchUser(id)
34
+ return CacheControl.response(user, {
35
+ maxAge: '5 minutes',
36
+ staleWhileRevalidate: '1 minute'
37
+ })
38
+ })
39
+
40
+ const user = await getUser('123', { maxAge: '5 minutes' })
41
+ ```
42
+
43
+ ## Cache Directives
44
+
45
+ ### Response Directives (Callee)
46
+
47
+ Set by the function being cached via `CacheControl.response(value, options)`:
48
+
49
+ | Directive | Type | Description |
50
+ |-----------|------|-------------|
51
+ | `maxAge` | `number \| DurationString` | Seconds the response remains fresh. Required unless `noStore` is set. |
52
+ | `staleWhileRevalidate` | `number \| DurationString \| undefined` | Seconds a stale response can be served while revalidating in the background. |
53
+ | `swr` | same as above | Alias for `staleWhileRevalidate`. |
54
+ | `noStore` | `true` | Return the value but never store it in the cache. |
55
+
56
+ ### Request Directives (Caller)
57
+
58
+ Passed as the last argument when calling a cached function:
59
+
60
+ | Directive | Type | Description |
61
+ |-----------|------|-------------|
62
+ | `maxAge` | `number \| DurationString \| null` | Maximum acceptable age (seconds) for a cached value. `null` means accept any age within response maxAge. |
63
+ | `noStore` | `boolean \| undefined` | Bypass the cache entirely -- always call the underlying function and do not store the result. |
64
+
65
+ ### Duration Strings
66
+
67
+ Both `maxAge` and `staleWhileRevalidate` accept human-readable duration strings:
68
+
69
+ ```ts
70
+ type CacheControlDurationString = `${number} ${'day' | 'days' | 'hour' | 'hours' | 'minute' | 'minutes' | 'second' | 'seconds'}`
71
+ ```
72
+
73
+ Examples: `'1 second'`, `'30 seconds'`, `'5 minutes'`, `'1 hour'`, `'7 days'`
74
+
75
+ ## API Reference
76
+
77
+ ### `CacheControl.Provider.create(config)`
78
+
79
+ Create a new CacheControl service instance.
80
+
81
+ ```ts
82
+ function create(config: {
83
+ cache: CacheControlCache
84
+ logger: Log.LogFunctions<[CacheControl.LogMessage]>
85
+ tracing?: CacheControl.Tracing
86
+ }): CacheControl.CacheControlService
87
+ ```
88
+
89
+ ### `CacheControl.Provider.register(name, service)`
90
+
91
+ Register a service instance in the named registry.
92
+
93
+ ```ts
94
+ CacheControl.Provider.register('default', cc)
95
+ ```
96
+
97
+ ### `CacheControl.provider(key)`
98
+
99
+ Resolve a named service instance. Returns `{ cacheFn }`.
100
+
101
+ ```ts
102
+ const { cacheFn } = CacheControl.provider('analytics')
103
+ ```
104
+
105
+ ### `CacheControl.cacheFn(options, func)`
106
+
107
+ Wrap an async function with cache-control semantics. Available on the facade (uses the `'default'` provider) or on a service instance.
108
+
109
+ ```ts
110
+ // String key prefix (arguments are hashed with object-hash)
111
+ const fn = CacheControl.cacheFn('my/prefix', async (id: string) => {
112
+ return CacheControl.response(await fetchData(id), { maxAge: 60 })
113
+ })
114
+
115
+ // Object options with custom argument hashing
116
+ const fn = CacheControl.cacheFn(
117
+ {
118
+ keyPrefix: 'my/prefix',
119
+ hashArguments: (args) => args[0]
120
+ },
121
+ async (id: string) => {
122
+ return CacheControl.response(await fetchData(id), { maxAge: 60 })
123
+ }
124
+ )
125
+ ```
126
+
127
+ **Signature:**
128
+
129
+ ```ts
130
+ cacheFn<Args, Returns>(
131
+ options: string | { keyPrefix: string; hashArguments?(args: Args): string },
132
+ func: (...args: Args) => Returns
133
+ ): (...args: [...Args, CacheControlRequestDirectives]) => Promise<UnwrapCacheResponse<Awaited<Returns>>>
134
+ ```
135
+
136
+ The wrapped function appends a `CacheControlRequestDirectives` argument. The callback **must** return `CacheControl.response(value, options)`.
137
+
138
+ ### `CacheControl.response(value, options)`
139
+
140
+ Create a cache-control response wrapper pairing a value with freshness directives.
141
+
142
+ ```ts
143
+ // With maxAge and staleWhileRevalidate
144
+ CacheControl.response(data, { maxAge: '5 minutes', staleWhileRevalidate: '15 minutes' })
145
+
146
+ // With swr alias
147
+ CacheControl.response(data, { maxAge: '5 minutes', swr: '15 minutes' })
148
+
149
+ // No-store (return value but do not cache)
150
+ CacheControl.response(data, { noStore: true })
151
+ ```
152
+
153
+ ### `CacheControl.caches.leastRecentlyUsed(config)`
154
+
155
+ Create an in-memory LRU cache backend.
156
+
157
+ ```ts
158
+ const cache = CacheControl.caches.leastRecentlyUsed({ max: 500 })
159
+ ```
160
+
161
+ ### `CacheControlCache` Interface
162
+
163
+ Implement this interface to provide a custom cache backend:
164
+
165
+ ```ts
166
+ interface CacheControlCache {
167
+ set(key: string, value: object, ttlSeconds: number): void | Promise<void>
168
+ get<T>(key: string): T | Promise<T> | undefined | null
169
+ del(key: string): void | Promise<void>
170
+ dump?(): any
171
+ }
172
+ ```
173
+
174
+ ## Common Patterns
175
+
176
+ ### Basic Cached Function
177
+
178
+ ```ts
179
+ const getUser = CacheControl.cacheFn('users/getById', async (id: string) => {
180
+ const user = await db.query('SELECT * FROM users WHERE id = ?', [id])
181
+ return CacheControl.response(user, { maxAge: '5 minutes', swr: '15 minutes' })
182
+ })
183
+
184
+ // Caller accepts any cached value within the response's maxAge
185
+ const user = await getUser('123', { maxAge: null })
186
+ ```
187
+
188
+ ### Force Fresh Data
189
+
190
+ ```ts
191
+ // Bypass cache -- always execute the function and do not store
192
+ const user = await getUser('123', { noStore: true })
193
+ ```
194
+
195
+ ### Caller-Limited Staleness
196
+
197
+ ```ts
198
+ // Only accept cached values generated within the last 60 seconds
199
+ const user = await getUser('123', { maxAge: 60 })
200
+
201
+ // Accept cached values up to 5 minutes old
202
+ const user = await getUser('123', { maxAge: '5 minutes' })
203
+ ```
204
+
205
+ ### Custom Argument Hashing
206
+
207
+ ```ts
208
+ const getReport = CacheControl.cacheFn(
209
+ {
210
+ keyPrefix: 'reports/monthly',
211
+ hashArguments: (args) => args[0]
212
+ },
213
+ async (month: string) => {
214
+ const report = await generateReport(month)
215
+ return CacheControl.response(report, { maxAge: '1 hour' })
216
+ }
217
+ )
218
+
219
+ await getReport('2025-01', { maxAge: null })
220
+ ```
221
+
222
+ ### Multiple Named Providers
223
+
224
+ ```ts
225
+ const fastCache = CacheControl.Provider.create({
226
+ cache: CacheControl.caches.leastRecentlyUsed({ max: 1000 }),
227
+ logger
228
+ })
229
+ CacheControl.Provider.register('fast', fastCache)
230
+
231
+ const { cacheFn } = CacheControl.provider('fast')
232
+ const fn = cacheFn('my/key', myFunction)
233
+ ```
234
+
235
+ ### Sensitive Data (No-Store Response)
236
+
237
+ ```ts
238
+ const getToken = CacheControl.cacheFn('auth/token', async (userId: string) => {
239
+ const token = await generateToken(userId)
240
+ return CacheControl.response(token, { noStore: true })
241
+ })
242
+ ```
243
+
244
+ ### Tracing Integration
245
+
246
+ ```ts
247
+ const cc = CacheControl.Provider.create({
248
+ cache: CacheControl.caches.leastRecentlyUsed({ max: 100 }),
249
+ logger,
250
+ tracing: {
251
+ startActiveSpan: (name, fn) => tracer.startActiveSpan(name, fn),
252
+ getActiveSpan: () => tracer.getActiveSpan()
253
+ }
254
+ })
255
+ ```
256
+
257
+ ## Testing
258
+
259
+ Run tests:
260
+
261
+ ```bash
262
+ pnpm --filter @maestro-js/cache-control test
263
+ # or
264
+ cd packages/cache-control && npx beartest ./tests/cache-control.test.ts
265
+ ```
266
+
267
+ Tests use `beartest-js` with `expect`. Create mock caches for testing:
268
+
269
+ ```ts
270
+ function mockWorkingCache(): CacheControl.CacheControlService['cache'] {
271
+ const map = new Map<string, any>()
272
+ return {
273
+ get(key) { return map.get(key) },
274
+ set(key, value) { map.set(key, value) },
275
+ del(key) { map.delete(key) }
276
+ }
277
+ }
278
+
279
+ function mockBrokenCache(): CacheControl.CacheControlService['cache'] {
280
+ return {
281
+ get() { throw new Error('This cache is broken') },
282
+ set(key, value) {},
283
+ del(key) {}
284
+ }
285
+ }
286
+ ```
287
+
288
+ Create a testing logger that tracks cache hits and background refreshes:
289
+
290
+ ```ts
291
+ const logger = Log.create<[CacheControl.LogMessage]>({
292
+ transports: [{
293
+ write(message) {
294
+ const data = message.data[0]
295
+ // Track events by callId to detect cache hits, errors, and background refreshes
296
+ }
297
+ }]
298
+ })
299
+ ```
300
+
301
+ Use `{ maxAge: null }` as the standard no-preference request directive in tests.
302
+
303
+ ## Cross-Package Integration
304
+
305
+ ### Dependencies
306
+
307
+ - **`@maestro-js/service-registry`** -- Provider pattern (create, register, resolve via Proxy)
308
+ - **`@maestro-js/log`** -- Structured logging of cache lifecycle events
309
+
310
+ ### Cache Backend
311
+
312
+ The `CacheControlCache` interface is deliberately simple. Any backend that implements `get`, `set`, and `del` works. The built-in `CacheControl.caches.leastRecentlyUsed()` uses the `lru-cache` npm package internally.
313
+
314
+ A custom backend backed by `@maestro-js/cache` (Redis, etc.) can be created by implementing the `CacheControlCache` interface and delegating to the cache package.
315
+
316
+ ### Log Events
317
+
318
+ All operations emit structured log messages of type `CacheControl.LogMessage`. Key events:
319
+
320
+ - `cacheFunctionCalled` -- function invoked with arguments and request directives
321
+ - `getCachedValueStart` / `getCachedValueSuccess` / `getCachedValueEmpty` / `getCachedValueError` -- cache lookup lifecycle
322
+ - `getCachedValueNotReused` -- caller's maxAge directive rejected a cached value
323
+ - `getCachedValueOutdated` -- entry expired past maxAge + staleWhileRevalidate window
324
+ - `getFreshValueStart` / `getFreshValueSuccess` / `getFreshValueError` -- fresh value fetch lifecycle
325
+ - `writeFreshValueSuccess` / `writeFreshValueError` -- writing to cache backend
326
+ - `refreshValueStart` / `refreshValueSuccess` / `refreshValueError` -- background stale-while-revalidate lifecycle
327
+ - `cacheFunctionReturned` / `cacheFunctionComplete` -- overall function lifecycle
328
+
329
+ ### Key File Locations
330
+
331
+ - Source: `packages/cache-control/src/index.ts`
332
+ - Types: `packages/cache-control/src/cache-control-types.ts`
333
+ - Cache lookup logic: `packages/cache-control/src/get-cache-control-value.ts`
334
+ - Fresh value + write logic: `packages/cache-control/src/get-fresh-cache-control-value.ts`
335
+ - Response parsing + duration strings: `packages/cache-control/src/parse-cache-response.ts`
336
+ - LRU cache backend: `packages/cache-control/src/lru-cache.ts`
337
+ - Tests: `packages/cache-control/tests/cache-control.test.ts`