@t4h.framework/cache 0.6.11 → 0.6.13
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/.ai/skills/framework-cache/SKILL.md +44 -151
- package/CHANGELOG.md +8 -0
- package/README.md +41 -35
- package/package.json +2 -2
|
@@ -1,98 +1,56 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: framework-cache
|
|
3
3
|
description: >-
|
|
4
|
-
Guides
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
Guides the use of CacheClaim from @t4h.framework/cache in T4H Framework
|
|
5
|
+
apps: reading and writing Serializable values from an activity, the ttl
|
|
6
|
+
option of CacheClaimSetOptions, registering a CacheClaim provider in the
|
|
7
|
+
runtime or in tests (mockClaim, TestWorkflowEnvironment), and the
|
|
8
|
+
CacheClaim that OAuth2 token caching in @t4h.framework/http needs. Use when
|
|
9
|
+
code imports CacheClaim, when a workflow that uses OAuth2 fails with
|
|
10
|
+
ClaimProviderNotFoundException for CacheClaim, or when you write a
|
|
11
|
+
CacheClaim implementation or test double.
|
|
9
12
|
---
|
|
10
13
|
|
|
11
14
|
# @t4h.framework/cache
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
`CacheClaim` is an abstract claim with `get(key)` and
|
|
17
|
+
`set(key, value, options?)`. The package has no implementation. The runtime or
|
|
18
|
+
the test must give one.
|
|
14
19
|
|
|
15
|
-
|
|
20
|
+
## Use the cache in an activity
|
|
16
21
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Import from the package entry. Its `exports` map has only `.`, so `tsc` rejects a deep import:
|
|
22
|
-
|
|
23
|
-
```typescript
|
|
24
|
-
import { CacheClaim, type CacheClaimSetOptions } from '@t4h.framework/cache'
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
---
|
|
28
|
-
|
|
29
|
-
## API contract
|
|
30
|
-
|
|
31
|
-
### `CacheClaimSetOptions`
|
|
32
|
-
|
|
33
|
-
```typescript
|
|
34
|
-
interface CacheClaimSetOptions {
|
|
35
|
-
ttl?: number
|
|
36
|
-
}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
`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.
|
|
40
|
-
|
|
41
|
-
### `CacheClaim`
|
|
22
|
+
1. Declare the claim in the activity with `new Claim({ cache: CacheClaim })`.
|
|
23
|
+
2. Call it only in `run()`. A claim is not available in workflow code.
|
|
24
|
+
3. Give each key a prefix (`feature:id`). All callers share one store.
|
|
42
25
|
|
|
43
26
|
```typescript
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
): Promise<void>
|
|
27
|
+
private readonly claims = new Claim({ cache: CacheClaim })
|
|
28
|
+
|
|
29
|
+
public async run({ id }: { id: string }) {
|
|
30
|
+
const key = `config:${id}`
|
|
31
|
+
const cached = await this.claims.cache.get<Config>(key)
|
|
32
|
+
if (cached) return cached
|
|
33
|
+
const value = await loadConfig(id)
|
|
34
|
+
await this.claims.cache.set(key, value)
|
|
35
|
+
return value
|
|
54
36
|
}
|
|
55
37
|
```
|
|
56
38
|
|
|
57
|
-
|
|
58
|
-
|--------|----------|
|
|
59
|
-
| `get` | Returns stored value or `undefined`. May be sync or async. |
|
|
60
|
-
| `set` | Stores a `Serializable` value. Always returns `Promise<void>`. Optional `ttl` in `options`. |
|
|
61
|
-
|
|
62
|
-
`value` is typed `Serializable` from `@t4h.framework/core` (primitives, `null`, `undefined`, `Binary`, `SerializableClass`, and arrays/objects of those), so `tsc` rejects other values.
|
|
63
|
-
|
|
64
|
-
---
|
|
65
|
-
|
|
66
|
-
## How workflows use it
|
|
67
|
-
|
|
68
|
-
Workflow code does not call `CacheClaim` directly. The claim backs the caching built into
|
|
69
|
-
framework-package primitives — most notably **OAuth2 token caching** in
|
|
70
|
-
`@t4h.framework/http`. Your only responsibility is that the runtime **registers
|
|
71
|
-
a `CacheClaim` provider** (below) so those primitives work. See **Downstream
|
|
72
|
-
consumer: OAuth2**.
|
|
73
|
-
|
|
74
|
-
---
|
|
75
|
-
|
|
76
|
-
## Runtime provider
|
|
77
|
-
|
|
78
|
-
The runtime (or test harness) must register a concrete implementation:
|
|
79
|
-
|
|
80
|
-
```typescript
|
|
81
|
-
{ provide: CacheClaim, value: new RedisCacheClaimImpl() }
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
`RedisCacheClaimImpl` is illustrative — implement `CacheClaim` in the runtime layer; this package does not ship a production backend.
|
|
85
|
-
|
|
86
|
-
A missing provider throws `ClaimProviderNotFoundException` (core `exceptions/ClaimException.ts`).
|
|
39
|
+
## Traps
|
|
87
40
|
|
|
88
|
-
|
|
41
|
+
- `get` can return a value or a `Promise`. Always `await` it.
|
|
42
|
+
- `value` must be `Serializable` from `@t4h.framework/core`. A `Date`, a `Map`
|
|
43
|
+
or a class instance that is not serializable fails `tsc`.
|
|
44
|
+
- `ttl` has no unit in the type. The implementation decides the unit. Do not
|
|
45
|
+
guess: read the implementation that your runtime gives. `OAuth2` in
|
|
46
|
+
`@t4h.framework/http` gives `ttl` in milliseconds.
|
|
47
|
+
- A workflow that uses an `OAuth2` grant from `@t4h.framework/http` needs a
|
|
48
|
+
`CacheClaim` provider, also if your code never imports `CacheClaim`.
|
|
49
|
+
- Import only from `@t4h.framework/cache`. The package has no deep exports.
|
|
89
50
|
|
|
90
|
-
##
|
|
51
|
+
## Test
|
|
91
52
|
|
|
92
|
-
|
|
93
|
-
`@t4h.framework/core/testing`, mocking `CacheClaim` (a workflow that uses OAuth2
|
|
94
|
-
also needs `HttpClientRequestClaim`). An in-memory `Map` makes a good double. See
|
|
95
|
-
the **framework-workflow-testing** skill for the harness API.
|
|
53
|
+
Give a `Map` double with `mockClaim`:
|
|
96
54
|
|
|
97
55
|
```typescript
|
|
98
56
|
import { CacheClaim } from '@t4h.framework/cache'
|
|
@@ -101,88 +59,23 @@ import { TestWorkflowEnvironment, mockClaim } from '@t4h.framework/core/testing'
|
|
|
101
59
|
|
|
102
60
|
const store = new Map<string, Serializable>()
|
|
103
61
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
await env.execute(workflow, input, {
|
|
62
|
+
await TestWorkflowEnvironment.create().execute(workflow, input, {
|
|
107
63
|
claims: [
|
|
108
64
|
mockClaim(CacheClaim, {
|
|
109
|
-
get: <T extends Serializable>(key: string)
|
|
65
|
+
get: <T extends Serializable>(key: string) =>
|
|
110
66
|
store.get(key) as T | undefined,
|
|
111
|
-
set: (key, value) => {
|
|
67
|
+
set: async (key, value) => {
|
|
112
68
|
store.set(key, value)
|
|
113
|
-
return Promise.resolve()
|
|
114
69
|
},
|
|
115
70
|
}),
|
|
116
|
-
// ...other claims the workflow's activities need (e.g. HttpClientRequestClaim)
|
|
117
71
|
],
|
|
118
72
|
})
|
|
119
73
|
```
|
|
120
74
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
---
|
|
124
|
-
|
|
125
|
-
## Downstream consumer: OAuth2 (`@t4h.framework/http`)
|
|
126
|
-
|
|
127
|
-
`@t4h.framework/http` depends on this package for token caching only — the
|
|
128
|
-
`HttpAuth` / `OAuth2` grant classes use `CacheClaim` internally to store tokens:
|
|
129
|
-
|
|
130
|
-
| Concern | Implementation |
|
|
131
|
-
|---------|----------------|
|
|
132
|
-
| Cache key | `` `oauth2:token:${this.config.clientId}` `` (`tokenCacheKey`) |
|
|
133
|
-
| Stored shape | `OAuth2Token` plus `expiresAt: number` (ms timestamp) |
|
|
134
|
-
| TTL on `set` | `Math.floor(token.expires_in * 1000 * 0.9)` — 90% of `expires_in` in **seconds**, converted to ms |
|
|
135
|
-
| Read path | `get` → if missing, fetch token and `set`; if `expiresAt > Date.now()`, return; else refresh or re-fetch |
|
|
136
|
-
|
|
137
|
-
Workflows using OAuth2 grant classes must wire **`CacheClaim`** and **`HttpClientRequestClaim`** in the runtime. See `framework-http` skill for HTTP details.
|
|
138
|
-
|
|
139
|
-
---
|
|
140
|
-
|
|
141
|
-
## Implementing a custom `CacheClaim`
|
|
142
|
-
|
|
143
|
-
Subclass `CacheClaim` and implement both methods:
|
|
144
|
-
|
|
145
|
-
```typescript
|
|
146
|
-
import { CacheClaim, type CacheClaimSetOptions } from '@t4h.framework/cache'
|
|
147
|
-
import type { Serializable } from '@t4h.framework/core'
|
|
148
|
-
|
|
149
|
-
export class MyCacheClaim extends CacheClaim {
|
|
150
|
-
public get<T extends Serializable>(
|
|
151
|
-
key: string,
|
|
152
|
-
): T | undefined | Promise<T | undefined> {
|
|
153
|
-
// ...
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
public set(
|
|
157
|
-
key: string,
|
|
158
|
-
value: Serializable,
|
|
159
|
-
options?: CacheClaimSetOptions,
|
|
160
|
-
): Promise<void> {
|
|
161
|
-
// honor options?.ttl if the backend supports it
|
|
162
|
-
return Promise.resolve()
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
Register: `{ provide: CacheClaim, value: new MyCacheClaim() }`.
|
|
168
|
-
|
|
169
|
-
---
|
|
170
|
-
|
|
171
|
-
## Do / Don't
|
|
172
|
-
|
|
173
|
-
| Do | Don't |
|
|
174
|
-
|----|-------|
|
|
175
|
-
| Namespace keys (`feature:id`) | Use bare global keys like `"token"` |
|
|
176
|
-
| Use a test double with explicit TTL when testing expiry | Assume any in-memory mock enforces TTL unless you implement it |
|
|
177
|
-
| Match OAuth2 TTL units (ms) when mirroring that pattern | Mix seconds and milliseconds for `ttl` without checking the caller |
|
|
178
|
-
|
|
179
|
-
---
|
|
180
|
-
|
|
181
|
-
## Related packages
|
|
75
|
+
The double does not expire entries. If the test depends on `ttl`, write that
|
|
76
|
+
logic in `set` and `get`.
|
|
182
77
|
|
|
183
|
-
|
|
184
|
-
|---------|------|
|
|
185
|
-
| `@t4h.framework/core` | `Claim`, `Serializable`, and the `TestWorkflowEnvironment` testing harness |
|
|
186
|
-
| `@t4h.framework/http` | OAuth2 token caching via `CacheClaim` |
|
|
78
|
+
## Reference
|
|
187
79
|
|
|
188
|
-
|
|
80
|
+
- README: https://www.npmjs.com/package/@t4h.framework/cache
|
|
81
|
+
- Types: `node_modules/@t4h.framework/cache/dist/CacheClaim.d.ts`
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# @t4h.framework/cache
|
|
2
2
|
|
|
3
|
+
## 0.6.13
|
|
4
|
+
|
|
5
|
+
## 0.6.12
|
|
6
|
+
|
|
7
|
+
### Patch Changes
|
|
8
|
+
|
|
9
|
+
- [#257](https://github.com/tech4humans-brasil/framework/pull/257) [`586bfb4`](https://github.com/tech4humans-brasil/framework/commit/586bfb4663f4a478527ee0c8db360603f034d615) Thanks [@gusteycamargo](https://github.com/gusteycamargo)! - The cache README is the short human doc of `CacheClaim`, and its example now compiles. The README and the skill no longer give a unit for `ttl`, because the type does not fix one.
|
|
10
|
+
|
|
3
11
|
## 0.6.11
|
|
4
12
|
|
|
5
13
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -1,70 +1,76 @@
|
|
|
1
1
|
# @t4h.framework/cache
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
`CacheClaim`: a claim for key-value storage of `Serializable` values in
|
|
4
|
+
activities. The package defines only the contract. The runtime gives the
|
|
5
|
+
implementation.
|
|
4
6
|
|
|
5
|
-
##
|
|
7
|
+
## Installation
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Abstract dependency for caching serializable values. An activity declares this claim when it needs to cache data (e.g. tokens, computed results). The runtime provides a concrete implementation (e.g. Redis, in-memory).
|
|
10
|
-
|
|
11
|
-
```typescript
|
|
12
|
-
import { CacheClaim } from '@t4h.framework/cache'
|
|
9
|
+
```bash
|
|
10
|
+
npm install @t4h.framework/cache
|
|
13
11
|
```
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
## Contract
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
| Method | Result |
|
|
16
|
+
| --------------------------- | ---------------------------------------------------------- |
|
|
17
|
+
| `get<T>(key)` | The stored value, or `undefined`. It can be sync or async. |
|
|
18
|
+
| `set(key, value, options?)` | `Promise<void>`. `value` must be `Serializable`. |
|
|
19
|
+
| `CacheClaimSetOptions.ttl` | Optional number. The type does not fix a unit. |
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
```
|
|
21
|
+
The implementation decides how to apply `ttl`. Before you set `ttl`, make sure
|
|
22
|
+
that you use the unit of the implementation that your runtime gives.
|
|
23
|
+
`OAuth2` in `@t4h.framework/http` uses this claim and gives `ttl` in
|
|
24
|
+
milliseconds.
|
|
27
25
|
|
|
28
|
-
|
|
26
|
+
## Usage in an activity
|
|
29
27
|
|
|
30
28
|
```typescript
|
|
31
|
-
import {
|
|
29
|
+
import { Claim, SyncActivity } from '@t4h.framework/core'
|
|
32
30
|
import { CacheClaim } from '@t4h.framework/cache'
|
|
33
31
|
|
|
34
|
-
|
|
32
|
+
type Config = { setting: string }
|
|
33
|
+
|
|
34
|
+
export class GetConfigActivity extends SyncActivity<
|
|
35
35
|
[key: string],
|
|
36
36
|
{ key: string },
|
|
37
|
-
|
|
37
|
+
Config,
|
|
38
|
+
Config
|
|
38
39
|
> {
|
|
39
|
-
private readonly claims = new Claim({
|
|
40
|
-
cache: CacheClaim,
|
|
41
|
-
})
|
|
40
|
+
private readonly claims = new Claim({ cache: CacheClaim })
|
|
42
41
|
|
|
43
|
-
public
|
|
42
|
+
public toInput(key: string) {
|
|
44
43
|
return { key }
|
|
45
44
|
}
|
|
46
45
|
|
|
47
|
-
public async run(
|
|
48
|
-
const
|
|
49
|
-
|
|
46
|
+
public async run({ key }: { key: string }) {
|
|
47
|
+
const cacheKey = `config:${key}`
|
|
48
|
+
const cached = await this.claims.cache.get<Config>(cacheKey)
|
|
50
49
|
if (cached) return cached
|
|
51
50
|
|
|
52
51
|
const value = { setting: 'default' }
|
|
53
|
-
await this.claims.cache.set(
|
|
54
|
-
|
|
52
|
+
await this.claims.cache.set(cacheKey, value)
|
|
55
53
|
return value
|
|
56
54
|
}
|
|
57
55
|
|
|
58
|
-
public toOutput(output:
|
|
56
|
+
public toOutput(output: Config) {
|
|
59
57
|
return output
|
|
60
58
|
}
|
|
61
59
|
}
|
|
62
60
|
```
|
|
63
61
|
|
|
64
|
-
|
|
62
|
+
Put a prefix on each key (`feature:id`), because all callers share one store.
|
|
63
|
+
|
|
64
|
+
## Provider
|
|
65
65
|
|
|
66
|
-
The runtime must
|
|
66
|
+
The runtime must give an implementation of `CacheClaim`:
|
|
67
67
|
|
|
68
68
|
```typescript
|
|
69
|
-
{ provide: CacheClaim, value: new
|
|
69
|
+
{ provide: CacheClaim, value: new MyCacheClaim() }
|
|
70
70
|
```
|
|
71
|
+
|
|
72
|
+
If no provider is registered, the activity fails with
|
|
73
|
+
`ClaimProviderNotFoundException` from `@t4h.framework/core`. In tests, use
|
|
74
|
+
`mockClaim(CacheClaim, { get, set })` from `@t4h.framework/core/testing`. A
|
|
75
|
+
`Map` is a good test double. A double applies `ttl` only if you write that
|
|
76
|
+
logic.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@t4h.framework/cache",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.13",
|
|
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",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"prepublishOnly": "yarn build"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@t4h.framework/core": "^0.
|
|
34
|
+
"@t4h.framework/core": "^0.21.1",
|
|
35
35
|
"typescript": "^5.9.3"
|
|
36
36
|
},
|
|
37
37
|
"peerDependencies": {
|