@t4h.framework/cache 0.3.0 → 0.4.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,236 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: framework-cache
|
|
3
|
+
description: >-
|
|
4
|
+
Guides correct use of @t4h.framework/cache in T4H Framework workflows:
|
|
5
|
+
CacheClaim for key-value storage inside activities, Serializable values, TTL
|
|
6
|
+
on set, runtime Claim providers, and OAuth2 token caching via @t4h.framework/http.
|
|
7
|
+
Use when implementing cache-backed activities, wiring CacheClaim providers,
|
|
8
|
+
testing cache get/set, or working in packages/cache.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# @t4h.framework/cache
|
|
12
|
+
|
|
13
|
+
Abstract **CacheClaim** for serializable key-value storage inside activities. The package defines the claim contract only; runtimes supply concrete backends (e.g. Redis).
|
|
14
|
+
|
|
15
|
+
Package path: `framework/packages/cache`. Peer dependency: `@t4h.framework/core` (`Serializable`, `Claim`, activities).
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Import surface
|
|
20
|
+
|
|
21
|
+
Only symbols exported from `src/cache.ts` exist in the public API:
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { CacheClaim, type CacheClaimSetOptions } from '@t4h.framework/cache'
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Do not invent additional exports or helpers not present in this package.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## API contract
|
|
32
|
+
|
|
33
|
+
### `CacheClaimSetOptions`
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
interface CacheClaimSetOptions {
|
|
37
|
+
ttl?: number
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`ttl` is optional. The README and `OAuth2` pass it in **milliseconds** (see OAuth2 section). Runtime implementations decide how to honor TTL; the abstract type does not document units beyond the optional number.
|
|
42
|
+
|
|
43
|
+
### `CacheClaim`
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
abstract class CacheClaim {
|
|
47
|
+
public abstract get<T extends Serializable>(
|
|
48
|
+
key: string,
|
|
49
|
+
): T | undefined | Promise<T | undefined>
|
|
50
|
+
|
|
51
|
+
public abstract set(
|
|
52
|
+
key: string,
|
|
53
|
+
value: Serializable,
|
|
54
|
+
options?: CacheClaimSetOptions,
|
|
55
|
+
): Promise<void>
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
| Method | Behavior |
|
|
60
|
+
|--------|----------|
|
|
61
|
+
| `get` | Returns stored value or `undefined`. May be sync or async — **always `await`** at call sites. |
|
|
62
|
+
| `set` | Stores a `Serializable` value. Always returns `Promise<void>`. Optional `ttl` in `options`. |
|
|
63
|
+
|
|
64
|
+
Values must satisfy `Serializable` from `@t4h.framework/core`: primitives, `null`, `undefined`, `Binary`, `SerializableClass`, and arrays/objects composed of those.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Declaring CacheClaim in an activity
|
|
69
|
+
|
|
70
|
+
Follow the same `Claim` pattern as other framework claims (see `framework-core` skill). Declare the claim on the activity, access it only inside activity methods while the claim context is active (`Claim.run` stack).
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { SyncActivity, Claim } from '@t4h.framework/core'
|
|
74
|
+
import { CacheClaim } from '@t4h.framework/cache'
|
|
75
|
+
|
|
76
|
+
class GetCachedConfigActivity extends SyncActivity<
|
|
77
|
+
[key: string],
|
|
78
|
+
{ key: string },
|
|
79
|
+
any,
|
|
80
|
+
any
|
|
81
|
+
> {
|
|
82
|
+
private readonly claims = new Claim({
|
|
83
|
+
cache: CacheClaim,
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
public async toInput(key: string) {
|
|
87
|
+
return { key }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
public async run(input: { key: string }) {
|
|
91
|
+
const cached = await this.claims.cache.get(input.key)
|
|
92
|
+
|
|
93
|
+
if (cached) return cached
|
|
94
|
+
|
|
95
|
+
const value = { setting: 'default' }
|
|
96
|
+
await this.claims.cache.set(input.key, value, { ttl: 3600 })
|
|
97
|
+
|
|
98
|
+
return value
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
public toOutput(output: any) {
|
|
102
|
+
return output
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Cache-aside pattern
|
|
108
|
+
|
|
109
|
+
1. `await cache.get(key)` — if hit, return.
|
|
110
|
+
2. Compute or fetch the value.
|
|
111
|
+
3. `await cache.set(key, value, { ttl })` — store for later runs/replays.
|
|
112
|
+
4. Return the value.
|
|
113
|
+
|
|
114
|
+
Use **stable, namespaced keys** (prefix + id) to avoid collisions across features or tenants.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Runtime provider
|
|
119
|
+
|
|
120
|
+
The runtime (or test harness) must register a concrete implementation:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
{ provide: CacheClaim, value: new RedisCacheClaimImpl() }
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`RedisCacheClaimImpl` is illustrative — implement `CacheClaim` in the runtime layer; this package does not ship a production backend.
|
|
127
|
+
|
|
128
|
+
Without a provider, claim resolution fails at runtime (`ClaimProviderNotFoundException` and related errors from core).
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Testing cache behavior
|
|
133
|
+
|
|
134
|
+
There are no unit tests under `packages/cache/`. Test with an in-memory `CacheClaim` double or `Claim.run`:
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
138
|
+
import { Claim } from '@t4h.framework/core'
|
|
139
|
+
import { CacheClaim } from '@t4h.framework/cache'
|
|
140
|
+
|
|
141
|
+
const store = new Map<string, unknown>()
|
|
142
|
+
|
|
143
|
+
const mockCache: CacheClaim = {
|
|
144
|
+
get: key => store.get(key) as any,
|
|
145
|
+
set: async (key, value) => { store.set(key, value) },
|
|
146
|
+
} as CacheClaim
|
|
147
|
+
|
|
148
|
+
await Claim.run(
|
|
149
|
+
[{ provide: CacheClaim, value: mockCache }],
|
|
150
|
+
() => myActivity.run(input),
|
|
151
|
+
)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
For full workflows, pass the same provider via `WorkflowTesting.run` (see **framework-core** skill). When testing TTL-dependent logic, implement `set` with TTL semantics in the test double.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Downstream consumer: OAuth2 (`@t4h.framework/http`)
|
|
159
|
+
|
|
160
|
+
`@t4h.framework/http` depends on this package for token caching only. `HttpAuth` and `OAuth2` declare `CacheClaim` via `Claim`:
|
|
161
|
+
|
|
162
|
+
```typescript
|
|
163
|
+
// HttpAuth — Symbol-keyed claims bag
|
|
164
|
+
public readonly [HTTP_AUTH_CLAIMS_KEY] = new Claim({ cache: CacheClaim })
|
|
165
|
+
|
|
166
|
+
// OAuth2
|
|
167
|
+
protected readonly claims = new Claim({
|
|
168
|
+
http: HttpClientRequestClaim,
|
|
169
|
+
cache: CacheClaim,
|
|
170
|
+
})
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
OAuth2 cache usage (`packages/http/src/models/OAuth2.ts`):
|
|
174
|
+
|
|
175
|
+
| Concern | Implementation |
|
|
176
|
+
|---------|----------------|
|
|
177
|
+
| Cache key | `` `oauth2:token:${this.config.clientId}` `` (`tokenCacheKey`) |
|
|
178
|
+
| Stored shape | `OAuth2Token` plus `expiresAt: number` (ms timestamp) |
|
|
179
|
+
| TTL on `set` | `Math.floor(token.expires_in * 1000 * 0.9)` — 90% of `expires_in` in **seconds**, converted to ms |
|
|
180
|
+
| Read path | `get` → if missing, fetch token and `set`; if `expiresAt > Date.now()`, return; else refresh or re-fetch |
|
|
181
|
+
|
|
182
|
+
Workflows using OAuth2 grant classes must wire **`CacheClaim`** and **`HttpClientRequestClaim`** in the runtime. See `framework-http` skill for HTTP details.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Implementing a custom `CacheClaim`
|
|
187
|
+
|
|
188
|
+
Subclass `CacheClaim` and implement both methods:
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
import { CacheClaim, type CacheClaimSetOptions } from '@t4h.framework/cache'
|
|
192
|
+
import type { Serializable } from '@t4h.framework/core'
|
|
193
|
+
|
|
194
|
+
export class MyCacheClaim extends CacheClaim {
|
|
195
|
+
public get<T extends Serializable>(
|
|
196
|
+
key: string,
|
|
197
|
+
): T | undefined | Promise<T | undefined> {
|
|
198
|
+
// ...
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
public set(
|
|
202
|
+
key: string,
|
|
203
|
+
value: Serializable,
|
|
204
|
+
options?: CacheClaimSetOptions,
|
|
205
|
+
): Promise<void> {
|
|
206
|
+
// honor options?.ttl if the backend supports it
|
|
207
|
+
return Promise.resolve()
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
Register: `{ provide: CacheClaim, value: new MyCacheClaim() }`.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Do / Don't
|
|
217
|
+
|
|
218
|
+
| Do | Don't |
|
|
219
|
+
|----|-------|
|
|
220
|
+
| `await` both `get` and `set` | Assume `get` is always synchronous |
|
|
221
|
+
| Store only `Serializable` values | Cache class instances, functions, or `Buffer` unless wrapped as `Binary` |
|
|
222
|
+
| Namespace keys (`feature:id`) | Use bare global keys like `"token"` |
|
|
223
|
+
| Provide `CacheClaim` in runtime/test `claims` | Use cache outside `Claim` / activity context |
|
|
224
|
+
| Use a test double with explicit TTL when testing expiry | Assume any in-memory mock enforces TTL unless you implement it |
|
|
225
|
+
| Match OAuth2 TTL units (ms) when mirroring that pattern | Mix seconds and milliseconds for `ttl` without checking the caller |
|
|
226
|
+
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Related packages
|
|
230
|
+
|
|
231
|
+
| Package | Role |
|
|
232
|
+
|---------|------|
|
|
233
|
+
| `@t4h.framework/core` | `Claim`, `Serializable`, activities, `WorkflowTesting`, `Internals` |
|
|
234
|
+
| `@t4h.framework/http` | OAuth2 token caching via `CacheClaim` |
|
|
235
|
+
|
|
236
|
+
For claim wiring, activity structure, and testing harnesses, use the **framework-core** skill. For HTTP and OAuth2, use the **framework-http** skill.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { Serializable } from '@t4h.framework/core';
|
|
2
|
+
export interface ActivityCacheClaimSetOptions {
|
|
3
|
+
ttl?: number;
|
|
4
|
+
}
|
|
5
|
+
export declare abstract class ActivityCacheClaim {
|
|
6
|
+
abstract get<T extends Serializable>(key: string): T | undefined | Promise<T | undefined>;
|
|
7
|
+
abstract set(key: string, value: Serializable, options?: ActivityCacheClaimSetOptions): Promise<void>;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=ActivityCacheClaim.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ActivityCacheClaim.d.ts","sourceRoot":"","sources":["../src/ActivityCacheClaim.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAEvD,MAAM,WAAW,4BAA4B;IAC3C,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,8BAAsB,kBAAkB;aACtB,GAAG,CAAC,CAAC,SAAS,YAAY,EACxC,GAAG,EAAE,MAAM,GACV,CAAC,GAAG,SAAS,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;aAEzB,GAAG,CACjB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,YAAY,EACnB,OAAO,CAAC,EAAE,4BAA4B,GACrC,OAAO,CAAC,IAAI,CAAC;CACjB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ActivityCacheClaim.js","sourceRoot":"","sources":["../src/ActivityCacheClaim.ts"],"names":[],"mappings":"AAMA,MAAM,OAAgB,kBAAkB;CAUvC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@t4h.framework/cache",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Cache module for the T4H Framework",
|
|
5
5
|
"homepage": "https://github.com/tech4humans-brasil/framework/tree/main/packages/cache",
|
|
6
6
|
"bugs": "https://github.com/tech4humans-brasil/framework/issues",
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
"types": "./dist/cache.d.ts",
|
|
21
21
|
"files": [
|
|
22
22
|
"dist",
|
|
23
|
-
"LICENSE"
|
|
23
|
+
"LICENSE",
|
|
24
|
+
".ai"
|
|
24
25
|
],
|
|
25
26
|
"scripts": {
|
|
26
27
|
"build": "tsc --project tsconfig.build.json",
|
|
@@ -28,14 +29,12 @@
|
|
|
28
29
|
"lint": "eslint .",
|
|
29
30
|
"prepublishOnly": "yarn build"
|
|
30
31
|
},
|
|
31
|
-
"dependencies": {
|
|
32
|
-
"@t4h.framework/core": "^0.3.0"
|
|
33
|
-
},
|
|
34
32
|
"devDependencies": {
|
|
33
|
+
"@t4h.framework/core": "^0.6.0",
|
|
35
34
|
"typescript": "^5.9.3"
|
|
36
35
|
},
|
|
37
36
|
"peerDependencies": {
|
|
38
|
-
"@t4h.framework/core": "^0.
|
|
37
|
+
"@t4h.framework/core": "^0.6.0"
|
|
39
38
|
},
|
|
40
39
|
"packageManager": "yarn@4.12.0",
|
|
41
40
|
"engines": {
|