@t4h.framework/http 0.3.1 → 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.
@@ -1,12 +1,11 @@
1
1
  ---
2
2
  name: framework-http
3
3
  description: >-
4
- Guides correct use of @t4h.framework/http in T4H Framework workflows: HTTP
5
- activities (HttpRequestActivity, AbstractHttpRequestActivity), claims
6
- (HttpClientRequestClaim, FileSystemClaim, CacheClaim for OAuth), the http
7
- client, request/response bodies, and auth. Use when implementing or testing
8
- HTTP calls in workflows, wiring HTTP claim providers, extending HTTP
9
- activities, OAuth2/Basic auth, or working in packages/http.
4
+ Guides correct use of @t4h.framework/http in T4H Framework workflows: the
5
+ `http` / `HttpClient` client, claims (HttpClientRequestClaim, FileSystemClaim,
6
+ CacheClaim for OAuth), request/response bodies, and auth. Use when making or
7
+ testing HTTP calls in workflows, wiring HTTP claim providers, OAuth2/Basic
8
+ auth, or working in packages/http.
10
9
  ---
11
10
 
12
11
  # @t4h.framework/http
@@ -19,8 +18,6 @@ Import surface (from `src/http.ts` only — do not reference unexported modules
19
18
  import {
20
19
  http,
21
20
  HttpClient,
22
- HttpRequestActivity,
23
- AbstractHttpRequestActivity,
24
21
  HttpClientRequestClaim,
25
22
  HttpAuth,
26
23
  OAuth2,
@@ -28,13 +25,15 @@ import {
28
25
  OAuth2GrantResourceOwnerPassword,
29
26
  Body,
30
27
  Response,
31
- type HttpRequestActivityInput,
32
- type HttpRequestActivityOutput,
33
28
  type HttpClientRequestClaimInput,
34
29
  type HttpClientRequestClaimOutput,
35
30
  } from '@t4h.framework/http'
36
31
  ```
37
32
 
33
+ > Workflows use the **`http` / `HttpClient`** client. The package also exports
34
+ > the underlying `HttpRequestActivity` / `AbstractHttpRequestActivity`, but
35
+ > workflow code never imports, instantiates, or extends them.
36
+
38
37
  ## Architecture
39
38
 
40
39
  `Workflow` → `http`/`HttpClient` (`History.bind(HttpRequestActivity)`) → `AbstractHttpRequestActivity.run` → `HttpClientRequestClaim.request` + `FileSystemClaim.write` (response → `Body`/`Response`).
@@ -78,11 +77,11 @@ abstract class HttpClientRequestClaim {
78
77
  }
79
78
  ```
80
79
 
81
- Dev: register a concrete `HttpClientRequestClaim` in the runtime (or mock in tests via `Claim.run`).
80
+ The runtime provides a concrete implementation; workflow tests mock it (see **Testing** below).
82
81
 
83
- ### FileSystemClaim (required by activities)
82
+ ### FileSystemClaim (required by the HTTP activity)
84
83
 
85
- `AbstractHttpRequestActivity` persists the response body with `fs.write(\`${uuid}-http-request-activity-response-body.${ext}\`, response.body)`. Extension comes from `content-type` via `mime`, or `.bin` if missing. Tests mock `FileSystemClaim.write` to build a `Binary` for `Body`.
84
+ The HTTP activity persists the response body to `fs` (path `\`${uuid}-http-request-activity-response-body.${ext}\``, extension from `content-type` via `mime`, or `.bin`) and returns it wrapped in a `Body`. So any workflow that makes an HTTP call also needs a `FileSystemClaim` provider; tests mock its `write` to return a `Binary`.
86
85
 
87
86
  ### CacheClaim (OAuth only)
88
87
 
@@ -117,74 +116,21 @@ Invalid body shape throws `Invalid body`.
117
116
 
118
117
  ---
119
118
 
120
- ## Activities
121
-
122
- ### HttpRequestActivity
123
-
124
- Direct use when workflow input matches HTTP config:
125
-
126
- ```typescript
127
- import { HttpRequestActivity } from '@t4h.framework/http'
128
-
129
- // Inside Claim.run or reconciler context:
130
- const activity = new HttpRequestActivity()
131
- const output = await activity.run({
132
- url: 'https://api.example.com/data',
133
- method: 'GET',
134
- })
135
- // Serializable output is HttpRequestActivityOutput; toOutput wraps Response
136
- ```
137
-
138
- `toInput` / `toHttpInput` pass input through; `toOutput` returns `new Response(output)`.
119
+ ## Making HTTP calls in a workflow
139
120
 
140
- ### AbstractHttpRequestActivity (custom activities)
121
+ Always use the ready **`http` / `HttpClient`** helper — it is what the package
122
+ exports for workflows and it drives the underlying HTTP activity through the
123
+ reconciler for you (replay-safe). Never define, extend, or hand-instantiate an
124
+ activity in workflow code.
141
125
 
142
- Extend with **four** type parameters: `TArgs`, `TInput`, `TSerializableOutput`, `TOutput`.
143
-
144
- Implement:
145
-
146
- - `toInput(...args)` — map workflow args → activity input
147
- - `toOutput(serializable, ...args)` — map serializable result → workflow return type
148
- - `protected toHttpInput(input)` — map to `HttpRequestActivityInput`
149
- - `protected toHttpOutput(output)` — map `HttpRequestActivityOutput` → serializable stored in history
126
+ For a typed result, read the response and map with plain TypeScript:
150
127
 
151
128
  ```typescript
152
- import {
153
- AbstractHttpRequestActivity,
154
- type HttpRequestActivityInput,
155
- type HttpRequestActivityOutput,
156
- } from '@t4h.framework/http'
157
- import type { Serializable } from '@t4h.framework/core'
158
-
159
- class FetchStatusActivity extends AbstractHttpRequestActivity<
160
- [endpoint: string],
161
- { endpoint: string },
162
- { status: number },
163
- { status: number }
164
- > {
165
- public async toInput(endpoint: string) {
166
- return { endpoint }
167
- }
168
-
169
- protected async toHttpInput(input: { endpoint: string }): Promise<HttpRequestActivityInput> {
170
- return { url: input.endpoint, method: 'GET' }
171
- }
172
-
173
- protected async toHttpOutput(output: HttpRequestActivityOutput): Promise<{ status: number }> {
174
- return { status: output.status }
175
- }
176
-
177
- public toOutput(output: { status: number }) {
178
- return output
179
- }
180
- }
129
+ const res = await http.get(`${environments.SERVICE_URL}/data`)
130
+ const data = await res.body.json<MyType>()
181
131
  ```
182
132
 
183
- Static helpers (for tests or advanced form building):
184
-
185
- - `AbstractHttpRequestActivity.getContent(body)`
186
- - `AbstractHttpRequestActivity.append(form, input, currentKey?)`
187
- - `AbstractHttpRequestActivity.getFormData(input)`
133
+ See **HttpClient and `http`** and **Response and Body** below.
188
134
 
189
135
  ---
190
136
 
@@ -217,7 +163,7 @@ const client = new HttpClient({
217
163
 
218
164
  Methods: `request(url, config?)`, `get`, `post(url, body, config?)`, `put`, `patch`, `delete`. Per-request `auth` in config merges over client `config.auth`.
219
165
 
220
- Workflow example (`examples/basic/src/workflows/simple.ts`):
166
+ Workflow example:
221
167
 
222
168
  ```typescript
223
169
  import { Workflow } from '@t4h.framework/core'
@@ -254,6 +200,13 @@ await response.body.stream()
254
200
 
255
201
  `body.json()` throws `Invalid content type` for non-JSON types (e.g. `text/plain`, `text/html`).
256
202
 
203
+ `Body` extends `SerializableClass` (`packages/http/src/models/Body.ts`) and is
204
+ persisted through `FileSystemClaim`, so the `Response`/`Body` returned by the
205
+ `http` client **is replay-safe** — it is serialized between reconciler steps and
206
+ rebuilt on replay. You do **not** need a custom activity to "flatten" the body
207
+ into history; read it with `await res.body.json<T>()` in the workflow and
208
+ transform with plain TypeScript.
209
+
257
210
  Serializable shape: `{ binary: ... }` via `toSerializable` / `fromSerializable`.
258
211
 
259
212
  ---
@@ -308,95 +261,75 @@ Requires `CacheClaim` + `HttpClientRequestClaim` in the runtime.
308
261
 
309
262
  ## Testing
310
263
 
311
- Run package tests: `yarn test` in `packages/http` (Vitest).
312
-
313
- ### 1. Unit-test activity `run` with mocked claims
314
-
315
- Pattern from `AbstractHttpRequestActivity.spec.ts`:
264
+ Test the **workflow** with `TestWorkflowEnvironment` from
265
+ `@t4h.framework/core/testing`, mocking the two claims the HTTP activity uses:
266
+ `HttpClientRequestClaim` (the network request) and `FileSystemClaim` (where the
267
+ activity persists the response body). See the **framework-workflow-testing** skill
268
+ for the full harness API.
316
269
 
317
270
  ```typescript
318
- import { Claim } from '@t4h.framework/core'
319
- import { FileSystemClaim } from '@t4h.framework/fs'
320
271
  import { Readable } from 'node:stream'
272
+ import { Binary } from '@t4h.framework/core'
273
+ import { TestWorkflowEnvironment, mockClaim } from '@t4h.framework/core/testing'
321
274
  import { HttpClientRequestClaim } from '@t4h.framework/http'
275
+ import { FileSystemClaim } from '@t4h.framework/fs'
322
276
 
323
- const mockRequest = vi.fn().mockResolvedValue({
324
- status: 200,
325
- headers: { 'content-type': 'application/json' },
326
- body: Readable.from(Buffer.from('{"ok":true}')),
327
- })
328
-
329
- const mockWrite = vi.fn().mockImplementation(async (_path, readable) => {
330
- const chunks = await readable.toArray()
331
- return new MockBinary(Buffer.concat(chunks), 'application/json')
332
- })
333
-
334
- const result = await Claim.run(
335
- [
336
- { provide: HttpClientRequestClaim, value: { request: mockRequest } },
337
- { provide: FileSystemClaim, value: { write: mockWrite, read: vi.fn() } },
338
- ],
339
- () => new MyActivity().run(input),
340
- )
341
-
342
- await expect(result.body.json()).resolves.toEqual({ ok: true })
343
- ```
344
-
345
- Assert `mockRequest` received `url`, `method`, merged headers, and JSON buffer for `{ json: ... }` bodies.
346
-
347
- ### 2. Assert HttpClient schedules HttpRequestActivity
277
+ // Minimal Binary the FileSystemClaim mock returns from write().
278
+ class MockBinary extends Binary {
279
+ public readonly contentType = 'application/json'
280
+ public readonly contentLength: number
348
281
 
349
- Pattern from `HttpClient.spec.ts`:
282
+ constructor(private readonly data: Buffer) {
283
+ super()
284
+ this.contentLength = data.byteLength
285
+ }
350
286
 
351
- ```typescript
352
- import { History, PushActivity } from '@t4h.framework/core'
353
- import { HttpRequestActivity } from '@t4h.framework/http'
354
-
355
- async function captureTrigger(fn: () => unknown) {
356
- try {
357
- await History.run([], fn)
358
- } catch (error) {
359
- if (error instanceof PushActivity) return error
360
- throw error
287
+ public stream() {
288
+ return Readable.from(this.data)
361
289
  }
362
290
  }
363
291
 
364
- const trigger = await captureTrigger(() => http.post('/users', { json: { name: 'Tony' } }))
365
- expect(trigger.activity.constructor).toBe(HttpRequestActivity)
366
- expect(trigger.input).toMatchObject({ method: 'POST', body: { json: { name: 'Tony' } } })
367
- ```
368
-
369
- ### 3. Integration-test workflows with mocked claims
292
+ const body = Buffer.from(JSON.stringify({ ok: true }))
370
293
 
371
- Use `WorkflowTesting.run` with explicit claim providers and spies on mock implementations:
294
+ // mockImplementation (not mockResolvedValue): replay runs the workflow twice, so
295
+ // each call must hand back a fresh, unconsumed stream.
296
+ const mockRequest = vi.fn().mockImplementation(async () => ({
297
+ status: 200,
298
+ headers: { 'content-type': 'application/json' },
299
+ body: Readable.from(body),
300
+ }))
372
301
 
373
- ```typescript
374
- import { WorkflowTesting } from '@t4h.framework/core/testing'
375
- import { HttpClientRequestClaim, type HttpClientRequestClaimInput } from '@t4h.framework/http'
302
+ const mockWrite = vi.fn().mockImplementation(async () => new MockBinary(body))
376
303
 
377
- const mockRequest = vi.fn().mockResolvedValue({ status: 200, headers: {}, body: Readable.from('{}') })
304
+ const env = TestWorkflowEnvironment.create({ now: 0 })
378
305
 
379
- await WorkflowTesting.run(workflow, input, {
306
+ const output = await env.execute(workflow, input, {
380
307
  claims: [
381
- { provide: HttpClientRequestClaim, value: { request: mockRequest } as HttpClientRequestClaim },
382
- { provide: FileSystemClaim, value: mockFs },
308
+ mockClaim(HttpClientRequestClaim, { request: mockRequest }),
309
+ mockClaim(FileSystemClaim, { write: mockWrite, read: vi.fn() }),
383
310
  ],
311
+ env: { SERVICE_URL: 'https://api.example.com' },
384
312
  })
313
+
314
+ // request input carries a URL object
315
+ expect(mockRequest.mock.calls[0][0].url).toEqual(
316
+ new URL('https://api.example.com/data'),
317
+ )
385
318
  ```
386
319
 
387
- Inspect `mockRequest.mock.calls` for URLs, methods, and bodies. See **framework-core** skill for `WorkflowTesting` options.
320
+ Inspect `mockRequest.mock.calls` for URLs, methods, and bodies.
388
321
 
389
322
  ---
390
323
 
391
324
  ## Checklist for agents
392
325
 
393
- 1. Prefer **`http` / `HttpClient`** in workflows unless you need a typed custom activity.
326
+ 1. In workflows, **always** use **`http` / `HttpClient`** — never define or extend an activity. Get typed, replay-safe results with `await res.body.json<T>()` plus plain TypeScript.
394
327
  2. Never call `fetch` directly in workflow code if the step must be replayable — use the package APIs.
395
328
  3. Ensure runtime provides **`HttpClientRequestClaim`** and **`FileSystemClaim`** (or pass mocks in tests).
396
329
  4. Use **`{ json: ... }`**, **`{ text: ... }`**, or **`{ form: ... }`** for bodies — not raw `Buffer` on activity input.
397
330
  5. Read responses with **`await response.body.json()`** or **`text()`**; check `status` explicitly (no automatic throw on 4xx/5xx).
398
331
  6. For OAuth, wire **`CacheClaim`** and use exported grant classes only.
399
- 7. When adding tests, mirror **`Claim.run`** + claim mocks or **`History.run`** + **`PushActivity`** patterns from `packages/http/src/**/__tests__`.
332
+ 7. Test workflows with **`TestWorkflowEnvironment`** + **`mockClaim`**, mocking **`HttpClientRequestClaim`** and **`FileSystemClaim`**; assert on `mockRequest.mock.calls`.
400
333
  8. Do not document or import APIs not exported from `@t4h.framework/http`.
401
334
 
402
- See also: `packages/http/README.md`, `examples/basic/src/workflows/simple.ts`.
335
+ See also: `packages/http/README.md`; **framework-workflow-testing** for the test harness.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@t4h.framework/http",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "HTTP module for the T4H Framework",
5
5
  "homepage": "https://github.com/tech4humans-brasil/framework/tree/main/packages/http",
6
6
  "bugs": "https://github.com/tech4humans-brasil/framework/issues",
@@ -38,18 +38,18 @@
38
38
  "uuidv7": "^1.1.0"
39
39
  },
40
40
  "devDependencies": {
41
- "@t4h.framework/cache": "^0.4.0",
42
- "@t4h.framework/core": "^0.7.0",
43
- "@t4h.framework/fs": "^0.4.0",
41
+ "@t4h.framework/cache": "^0.5.0",
42
+ "@t4h.framework/core": "^0.8.0",
43
+ "@t4h.framework/fs": "^0.5.0",
44
44
  "@types/type-is": "^1.6.7",
45
45
  "@vitest/coverage-v8": "^4.0.18",
46
46
  "typescript": "^5.9.3",
47
47
  "vitest": "^4.0.18"
48
48
  },
49
49
  "peerDependencies": {
50
- "@t4h.framework/cache": "^0.4.0",
51
- "@t4h.framework/core": "^0.7.0",
52
- "@t4h.framework/fs": "^0.4.0"
50
+ "@t4h.framework/cache": "^0.5.0",
51
+ "@t4h.framework/core": "^0.8.0",
52
+ "@t4h.framework/fs": "^0.5.0"
53
53
  },
54
54
  "packageManager": "yarn@4.12.0",
55
55
  "engines": {