opinionated-machine 10.2.0 → 10.3.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # opinionated-machine
2
2
 
3
+ ## 10.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 197d746: Support non-GET requests in `SSEHttpClient.connect()` via new `method` and `body` connect options, so POST/PUT/PATCH SSE endpoints can be tested over real HTTP instead of only through `SSEInjectClient`. `method` is accepted in either case, matching the lowercase spelling used by route contracts. Bodies `fetch()` sends natively (strings, `URLSearchParams`, `FormData`, `Blob`, `ArrayBuffer`, typed arrays, `ReadableStream`) are passed through untouched, everything else is JSON-stringified, and `content-type: application/json` is only defaulted when it does not overwrite an encoding `fetch()` describes itself. The response body is now locked lazily, so `client.response.json()` still works for endpoints that answered with a regular HTTP response (e.g. an error raised before `sse.start()`), and a bodiless response is reported when events are consumed rather than from `connect()`. `awaitServerConnection` now matches the request method as well as the URL, so a path served by both a GET and a POST route resolves the right session; `SpiedSSESession` accordingly requires `request.method`, which every Fastify-backed session already carries.
8
+ - 197d746: Type `SSEConnectOptions.method` as the new exported `SSEInjectMethod`, derived from Fastify's own inject options instead of a hand-listed `'GET' | 'POST' | 'PUT' | 'PATCH'` union. `SSEInjectClient.connectWithBody()` now accepts every method `inject()` accepts (including `DELETE`, `HEAD`, `OPTIONS` and the lowercase spellings), and consumers can import the union instead of redeclaring it.
9
+
3
10
  ## 10.2.0
4
11
 
5
12
  ### Minor Changes
package/README.md CHANGED
@@ -2251,12 +2251,14 @@ The library provides utilities for testing SSE endpoints.
2251
2251
  | **Event delivery** | All events returned at once (after handler closes) | Events arrive incrementally |
2252
2252
  | **Connection lifecycle** | Handler must close for request to complete | Can stay open indefinitely |
2253
2253
  | **Server requirement** | No `listen()` needed | Requires a listening server (`SSETestServer.start(app)` or manual `app.listen()`) |
2254
- | **Best for** | `autoClose` SSE (OpenAI-style, batch exports) | `keepAlive` SSE (notifications, live feeds, rooms) |
2254
+ | **Request body** | `injectPayloadSSE` / `connectWithBody` | `method` + `body` connect options |
2255
+ | **Assertions before the handler finishes** | Not possible (response is buffered) | `client.response` is available as soon as headers arrive |
2256
+ | **Best for** | `autoClose` SSE (OpenAI-style, batch exports) | `keepAlive` SSE (notifications, live feeds, rooms), streams whose headers must be asserted mid-handler |
2255
2257
  | **Dual-mode sync** | Use `app.inject()` with `accept: 'application/json'` | Same |
2256
2258
 
2257
2259
  #### SSEHttpClient
2258
2260
 
2259
- For testing `keepAlive` SSE connections using real HTTP. Requires a listening server — use `SSETestServer.start(app)` to start your app on a random port:
2261
+ For testing `keepAlive` SSE connections using real HTTP, and for any assertion that has to happen on the wire while the handler is still running. Supports `GET`, `POST`, `PUT` and `PATCH`. Requires a listening server — use `SSETestServer.start(app)` to start your app on a random port:
2260
2262
 
2261
2263
  ```ts
2262
2264
  import { SSEHttpClient } from 'opinionated-machine'
@@ -2368,6 +2370,65 @@ expect(client.response.headers.get('content-type')).toContain('text/event-stream
2368
2370
  client.close()
2369
2371
  ```
2370
2372
 
2373
+ **POST/PUT/PATCH endpoints**
2374
+
2375
+ `connect()` also issues non-GET requests, so SSE endpoints that take a request body can be tested over real HTTP. Pass `method` and `body`. The method is accepted in either case, so the lowercase spelling your contracts already use (`method: 'post'`) works as-is:
2376
+
2377
+ ```ts
2378
+ const client = await SSEHttpClient.connect(server.baseUrl, '/api/chat/completions', {
2379
+ method: 'POST',
2380
+ body: { message: 'Hello', stream: true },
2381
+ })
2382
+
2383
+ const events = await client.collectEvents((event) => event.event === 'done')
2384
+ ```
2385
+
2386
+ Bodies `fetch()` can send natively — strings, `URLSearchParams`, `FormData`, `Blob`/`File`, `ArrayBuffer`, typed arrays (`Buffer`, `Uint8Array`, …) and `ReadableStream` — are passed through untouched; anything else is JSON-stringified. `content-type: application/json` is defaulted for JSON-stringified and string bodies (so a raw string stays verbatim, which is handy for asserting on malformed payloads), unless you provide your own content type. Payloads that describe their own encoding — `URLSearchParams`, `FormData`, `Blob` — keep the content type `fetch()` gives them:
2387
+
2388
+ ```ts
2389
+ // Sent as application/x-www-form-urlencoded, not JSON-stringified into `{}`
2390
+ const client = await SSEHttpClient.connect(server.baseUrl, '/api/chat/completions', {
2391
+ method: 'post',
2392
+ body: new URLSearchParams({ message: 'Hello' }),
2393
+ })
2394
+ ```
2395
+
2396
+ A body without a non-GET `method` throws — `fetch()` cannot attach one to a GET request.
2397
+
2398
+ **Asserting on the wire before the handler finishes**
2399
+
2400
+ `connect()` resolves as soon as HTTP headers arrive, and `client.response` is populated at that point. That is what makes the "open the stream before the slow work starts" behaviour testable: assert on status and headers while the handler is still awaiting its slow call, then let it proceed.
2401
+
2402
+ ```ts
2403
+ // Handler calls sse.start() and only then makes its slow LLM call
2404
+ const client = await SSEHttpClient.connect(server.baseUrl, '/api/chat/completions', {
2405
+ method: 'POST',
2406
+ body: { message: 'Hello', stream: true },
2407
+ })
2408
+
2409
+ // Already on the wire while the LLM call is still in flight
2410
+ expect(client.response.status).toBe(200)
2411
+ expect(client.response.headers.get('content-type')).toContain('text/event-stream')
2412
+
2413
+ releaseSlowCall()
2414
+ const events = await client.collectEvents((event) => event.event === 'done')
2415
+ ```
2416
+
2417
+ The mirror case works too: a failure raised *before* `sse.start()` reaches the client as the JSON status the contract declares, not as a terminal `error` event. The response body is only locked once you start consuming events, so it can still be read as JSON:
2418
+
2419
+ ```ts
2420
+ const client = await SSEHttpClient.connect(server.baseUrl, '/api/chat/completions', {
2421
+ method: 'POST',
2422
+ body: { message: 'Hello', stream: true },
2423
+ })
2424
+
2425
+ expect(client.response.status).toBe(503)
2426
+ expect(client.response.headers.get('content-type')).not.toContain('text/event-stream')
2427
+ expect(await client.response.json()).toEqual({ message: 'Upstream unavailable' })
2428
+ ```
2429
+
2430
+ Read that body *before* `close()`: closing aborts the request, so a body read after it (or from a `finally { client.close() }` block that runs first) rejects with an `AbortError`.
2431
+
2371
2432
  #### SSEInjectClient
2372
2433
 
2373
2434
  For testing `autoClose` SSE streams (like OpenAI completions). Uses Fastify's `inject()` - no `app.listen()` needed:
@@ -2388,6 +2449,14 @@ const conn = await client.connectWithBody(
2388
2449
  { model: 'gpt-4', messages: [...], stream: true },
2389
2450
  )
2390
2451
 
2452
+ // Any other method inject() accepts works too - the option is typed as
2453
+ // `SSEInjectMethod`, exported so you never have to redeclare that union
2454
+ const conn = await client.connectWithBody(
2455
+ '/api/exports/42',
2456
+ { reason: 'cleanup' },
2457
+ { method: 'DELETE' },
2458
+ )
2459
+
2391
2460
  // All events are available immediately (inject waits for handler to complete)
2392
2461
  expect(conn.getStatusCode()).toBe(200)
2393
2462
  const events = conn.getReceivedEvents()
@@ -4,12 +4,16 @@ import type { SSESession } from './AbstractSSEController.ts';
4
4
  *
5
5
  * Both the `SSESession` produced by this package's own SSE routes and the one
6
6
  * produced by `@lokalise/fastify-api-contracts` (used by `buildApiRoute`)
7
- * satisfy it, which lets a single spy be attached to either route style.
7
+ * satisfy it, which lets a single spy be attached to either route style. Both
8
+ * carry a Fastify request, so `method` is always there to match a waiter
9
+ * against - a path served by both a GET and a POST route needs it to resolve
10
+ * the right session.
8
11
  */
9
12
  export type SpiedSSESession = {
10
13
  id: string;
11
14
  request: {
12
15
  url: string;
16
+ method: string;
13
17
  };
14
18
  };
15
19
  export type SSESessionEvent<TSession extends SpiedSSESession = SSESession> = {
@@ -1 +1 @@
1
- {"version":3,"file":"SSESessionSpy.js","sourceRoot":"","sources":["../../../lib/sse/SSESessionSpy.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SSESessionSpy.js","sourceRoot":"","sources":["../../../lib/sse/SSESessionSpy.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  export { injectApiSSE } from './apiSseInjectHelpers.js';
2
2
  export type { ApiDeclaredResponseBody, ApiDeclaredResponseStatus, ApiSSEEvent, ApiSSEEventReader, InjectApiSSEParams, InjectApiSSEResult, } from './apiSseTestTypes.js';
3
- export { type HasSessionSpy, SSEHttpClient, type SSEHttpConnectOptions, type SSEHttpConnectResult, type SSEHttpConnectWithSessionSpyOptions, type SSEHttpConnectWithSpyOptions, } from './sseHttpClient.js';
3
+ export { type HasSessionSpy, SSEHttpClient, type SSEHttpConnectOptions, type SSEHttpConnectResult, type SSEHttpConnectWithSessionSpyOptions, type SSEHttpConnectWithSpyOptions, type SSEHttpMethod, } from './sseHttpClient.js';
4
4
  export { SSEInjectClient, SSEInjectConnection } from './sseInjectClient.js';
5
5
  export { injectPayloadSSE, injectSSE } from './sseInjectHelpers.js';
6
6
  export { type CreateSSESessionSpyResult, createSSESessionSpy, type SSESessionSpyHooks, type SSESessionSpyRouteOptions, } from './sseSessionSpyFactory.js';
7
7
  export { SSETestServer } from './sseTestServer.js';
8
- export type { InjectPayloadSSEOptions, InjectSSEOptions, InjectSSEResult, SSEConnectOptions, SSEResponse, SSETestConnection, } from './sseTestTypes.js';
8
+ export type { InjectPayloadSSEOptions, InjectSSEOptions, InjectSSEResult, SSEConnectOptions, SSEInjectMethod, SSEResponse, SSETestConnection, } from './sseTestTypes.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/testing/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AASvD,OAAO,EAEL,aAAa,GAKd,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAC3E,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACnE,OAAO,EAEL,mBAAmB,GAGpB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../lib/testing/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAA;AASvD,OAAO,EAEL,aAAa,GAMd,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAC3E,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAA;AACnE,OAAO,EAEL,mBAAmB,GAGpB,MAAM,2BAA2B,CAAA;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA"}
@@ -7,6 +7,16 @@ import { type ParsedSSEEvent } from '../sse/sseParser.ts';
7
7
  export type HasSessionSpy = {
8
8
  connectionSpy: SSESessionSpy;
9
9
  };
10
+ /** Canonical, on-the-wire spelling of a supported method. */
11
+ type NormalizedSSEHttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH';
12
+ /**
13
+ * HTTP methods supported when connecting to an SSE endpoint.
14
+ *
15
+ * Both spellings are accepted and normalized internally, so the lowercase form
16
+ * used by route contracts (`buildContract({ method: 'post' })`) can be handed
17
+ * over as-is.
18
+ */
19
+ export type SSEHttpMethod = NormalizedSSEHttpMethod | Lowercase<NormalizedSSEHttpMethod>;
10
20
  /**
11
21
  * Options for connecting to an SSE endpoint via HTTP.
12
22
  */
@@ -15,6 +25,24 @@ export type SSEHttpConnectOptions = {
15
25
  query?: Record<string, string | undefined>;
16
26
  /** Additional headers to send with the request */
17
27
  headers?: Record<string, string>;
28
+ /** HTTP method to use, upper- or lowercase (default: 'GET') */
29
+ method?: SSEHttpMethod;
30
+ /**
31
+ * Request body.
32
+ *
33
+ * Values `fetch()` can send natively - strings, `URLSearchParams`, `FormData`,
34
+ * `Blob`/`File`, `ArrayBuffer`, typed arrays (`Buffer`, `Uint8Array`, ...) and
35
+ * `ReadableStream` - are passed through untouched. Anything else is
36
+ * JSON-stringified.
37
+ *
38
+ * `content-type: application/json` is defaulted for JSON-stringified and string
39
+ * bodies, unless `headers` already provides a content type. Bodies that describe
40
+ * their own encoding (`URLSearchParams`, `FormData`, `Blob`) are left for
41
+ * `fetch()` to label, so their content type is never overwritten with a wrong one.
42
+ *
43
+ * Requires a non-GET `method`.
44
+ */
45
+ body?: unknown;
18
46
  };
19
47
  /**
20
48
  * Options for connecting with automatic server-side connection waiting,
@@ -70,6 +98,13 @@ export type SSEHttpConnectResult<TSession extends SpiedSSESession = SSESession>
70
98
  * - **Long-lived connections** that stay open indefinitely
71
99
  * - **Real-time notifications** where events arrive over time
72
100
  * - **Push-based streaming** where the client waits for server-initiated events
101
+ * - **Assertions on the wire while the handler is still running** - `connect()`
102
+ * resolves as soon as headers arrive, so `response.status` / `response.headers`
103
+ * can be checked before the handler produces its first event
104
+ *
105
+ * GET, POST, PUT and PATCH are all supported (see `method` / `body` in
106
+ * {@link SSEHttpConnectOptions}), so POST SSE endpoints that take a request
107
+ * body can be tested over real HTTP too.
73
108
  *
74
109
  * **When to use SSEHttpClient vs SSEInjectClient:**
75
110
  *
@@ -113,14 +148,33 @@ export type SSEHttpConnectResult<TSession extends SpiedSSESession = SSESession>
113
148
  * ```
114
149
  */
115
150
  export declare class SSEHttpClient {
116
- /** The fetch Response object. Available immediately after connect() returns. */
151
+ /**
152
+ * The fetch Response object. Available immediately after connect() returns,
153
+ * before any event is consumed, so status and headers can be asserted while
154
+ * the handler is still running.
155
+ *
156
+ * The response body is only locked once events are first consumed, so
157
+ * `response.json()` / `response.text()` still work for endpoints that
158
+ * answered with a regular HTTP response instead of a stream (for example an
159
+ * error raised before `sse.start()`). Read that body *before* calling
160
+ * `close()` - `close()` aborts the request, which rejects any pending or
161
+ * subsequent body read with an `AbortError`.
162
+ */
117
163
  readonly response: Response;
118
164
  private readonly abortController;
119
- private readonly reader;
165
+ private readonly responseBody;
166
+ private streamReader;
120
167
  private readonly decoder;
121
168
  private buffer;
122
169
  private closed;
123
170
  private constructor();
171
+ /**
172
+ * Lazily acquire the stream reader, locking the response body on first use.
173
+ *
174
+ * A missing body is reported here rather than from the constructor, so a
175
+ * bodiless response (e.g. `204`) can still be inspected via `response`.
176
+ */
177
+ private get reader();
124
178
  /**
125
179
  * Connect to an SSE endpoint.
126
180
  *
@@ -130,7 +184,7 @@ export declare class SSEHttpClient {
130
184
  *
131
185
  * @param baseUrl - Base URL of the server (e.g., 'http://localhost:3000')
132
186
  * @param path - SSE endpoint path (e.g., '/api/notifications')
133
- * @param options - Connection options (query params, headers)
187
+ * @param options - Connection options (method, body, query params, headers)
134
188
  * @returns Connected SSE client ready to receive events
135
189
  *
136
190
  * @example
@@ -142,6 +196,16 @@ export declare class SSEHttpClient {
142
196
  * { query: { userId: '123' }, headers: { authorization: 'Bearer token' } }
143
197
  * )
144
198
  *
199
+ * // POST with a JSON body (content-type defaults to application/json)
200
+ * const client = await SSEHttpClient.connect(
201
+ * 'http://localhost:3000',
202
+ * '/api/chat/completions',
203
+ * { method: 'POST', body: { message: 'Hello', stream: true } }
204
+ * )
205
+ * // Headers are on the wire before the handler finished its slow work
206
+ * expect(client.response.status).toBe(200)
207
+ * expect(client.response.headers.get('content-type')).toContain('text/event-stream')
208
+ *
145
209
  * // With awaitServerConnection (waits for server-side registration)
146
210
  * const { client, serverConnection } = await SSEHttpClient.connect(
147
211
  * 'http://localhost:3000',
@@ -230,6 +294,11 @@ export declare class SSEHttpClient {
230
294
  *
231
295
  * This aborts the underlying fetch request. Call this when done
232
296
  * consuming events to clean up resources.
297
+ *
298
+ * Aborting also discards any unread response body, so for a non-stream
299
+ * response (an error raised before `sse.start()`, say) read
300
+ * `response.json()` / `response.text()` before calling this.
233
301
  */
234
302
  close(): void;
235
303
  }
304
+ export {};
@@ -1,5 +1,54 @@
1
1
  import { stringify } from 'fast-querystring';
2
2
  import { parseSSEBuffer } from "../sse/sseParser.js";
3
+ /**
4
+ * Whether `fetch()` can send this value as-is. Everything else is JSON-stringified,
5
+ * so binary and form payloads are not silently mangled into `{}` or `{"0":1}`.
6
+ */
7
+ function isFetchBody(body) {
8
+ return (typeof body === 'string' ||
9
+ body instanceof URLSearchParams ||
10
+ body instanceof FormData ||
11
+ body instanceof Blob ||
12
+ body instanceof ArrayBuffer ||
13
+ ArrayBuffer.isView(body) ||
14
+ body instanceof ReadableStream);
15
+ }
16
+ /**
17
+ * Whether the body carries its own content type, which `fetch()` sets correctly
18
+ * (form encoding, multipart boundary, blob type) and we must not overwrite.
19
+ */
20
+ function describesOwnContentType(body) {
21
+ return body instanceof URLSearchParams || body instanceof FormData || body instanceof Blob;
22
+ }
23
+ function hasContentTypeHeader(headers) {
24
+ return Object.keys(headers).some((header) => header.toLowerCase() === 'content-type');
25
+ }
26
+ /**
27
+ * Resolve the value handed to `fetch()` as the request body, defaulting the
28
+ * content type (into `headers`) whenever we are the ones choosing the encoding.
29
+ */
30
+ function buildRequestBody(body, method, headers) {
31
+ if (body === undefined) {
32
+ return undefined;
33
+ }
34
+ if (method === 'GET') {
35
+ throw new Error("SSEHttpClient.connect(): a request body requires a non-GET method. Pass e.g. { method: 'POST', body }.");
36
+ }
37
+ if (isFetchBody(body)) {
38
+ if (!describesOwnContentType(body) && !hasContentTypeHeader(headers)) {
39
+ headers['content-type'] = 'application/json';
40
+ }
41
+ return body;
42
+ }
43
+ const serializedBody = JSON.stringify(body);
44
+ if (serializedBody === undefined) {
45
+ throw new Error(`SSEHttpClient.connect(): a body of type ${typeof body} cannot be serialized to JSON. Pass a JSON-serializable value, a string, or a body fetch() accepts natively.`);
46
+ }
47
+ if (!hasContentTypeHeader(headers)) {
48
+ headers['content-type'] = 'application/json';
49
+ }
50
+ return serializedBody;
51
+ }
3
52
  /**
4
53
  * SSE client for testing long-lived connections using real HTTP.
5
54
  *
@@ -10,6 +59,13 @@ import { parseSSEBuffer } from "../sse/sseParser.js";
10
59
  * - **Long-lived connections** that stay open indefinitely
11
60
  * - **Real-time notifications** where events arrive over time
12
61
  * - **Push-based streaming** where the client waits for server-initiated events
62
+ * - **Assertions on the wire while the handler is still running** - `connect()`
63
+ * resolves as soon as headers arrive, so `response.status` / `response.headers`
64
+ * can be checked before the handler produces its first event
65
+ *
66
+ * GET, POST, PUT and PATCH are all supported (see `method` / `body` in
67
+ * {@link SSEHttpConnectOptions}), so POST SSE endpoints that take a request
68
+ * body can be tested over real HTTP too.
13
69
  *
14
70
  * **When to use SSEHttpClient vs SSEInjectClient:**
15
71
  *
@@ -53,20 +109,44 @@ import { parseSSEBuffer } from "../sse/sseParser.js";
53
109
  * ```
54
110
  */
55
111
  export class SSEHttpClient {
56
- /** The fetch Response object. Available immediately after connect() returns. */
112
+ /**
113
+ * The fetch Response object. Available immediately after connect() returns,
114
+ * before any event is consumed, so status and headers can be asserted while
115
+ * the handler is still running.
116
+ *
117
+ * The response body is only locked once events are first consumed, so
118
+ * `response.json()` / `response.text()` still work for endpoints that
119
+ * answered with a regular HTTP response instead of a stream (for example an
120
+ * error raised before `sse.start()`). Read that body *before* calling
121
+ * `close()` - `close()` aborts the request, which rejects any pending or
122
+ * subsequent body read with an `AbortError`.
123
+ */
57
124
  response;
58
125
  abortController;
59
- reader;
126
+ responseBody;
127
+ streamReader;
60
128
  decoder = new TextDecoder();
61
129
  buffer = '';
62
130
  closed = false;
63
131
  constructor(response, abortController) {
64
132
  this.response = response;
65
133
  this.abortController = abortController;
66
- if (!response.body) {
67
- throw new Error('SSE response has no body');
134
+ this.responseBody = response.body;
135
+ }
136
+ /**
137
+ * Lazily acquire the stream reader, locking the response body on first use.
138
+ *
139
+ * A missing body is reported here rather than from the constructor, so a
140
+ * bodiless response (e.g. `204`) can still be inspected via `response`.
141
+ */
142
+ get reader() {
143
+ if (!this.streamReader) {
144
+ if (!this.responseBody) {
145
+ throw new Error(`SSE response has no body to stream (status ${this.response.status}). Inspect \`client.response\` instead of consuming events.`);
146
+ }
147
+ this.streamReader = this.responseBody.getReader();
68
148
  }
69
- this.reader = response.body.getReader();
149
+ return this.streamReader;
70
150
  }
71
151
  static async connect(baseUrl, path, options) {
72
152
  // Build path with query string
@@ -77,30 +157,34 @@ export class SSEHttpClient {
77
157
  pathWithQuery = `${path}?${queryString}`;
78
158
  }
79
159
  }
160
+ // Normalize once - contracts spell methods in lowercase, the wire and Node use uppercase
161
+ const method = (options?.method ?? 'GET').toUpperCase();
162
+ const headers = {
163
+ Accept: 'text/event-stream',
164
+ ...options?.headers,
165
+ };
166
+ const body = buildRequestBody(options?.body, method, headers);
80
167
  // Connect - fetch() returns when headers are received
81
168
  const abortController = new AbortController();
82
- const response = await fetch(`${baseUrl}${pathWithQuery}`, {
83
- headers: {
84
- Accept: 'text/event-stream',
85
- ...options?.headers,
86
- },
169
+ const requestInit = {
170
+ method,
171
+ headers,
172
+ body,
87
173
  signal: abortController.signal,
88
- });
89
- let client;
90
- try {
91
- client = new SSEHttpClient(response, abortController);
92
- }
93
- catch (error) {
94
- // Nothing owns the response yet, so abort it here rather than leaking it.
95
- abortController.abort();
96
- throw error;
174
+ };
175
+ if (body instanceof ReadableStream) {
176
+ // A streamed request body has to declare half-duplex explicitly, or fetch() rejects it
177
+ ;
178
+ requestInit.duplex = 'half';
97
179
  }
180
+ const response = await fetch(`${baseUrl}${pathWithQuery}`, requestInit);
181
+ const client = new SSEHttpClient(response, abortController);
98
182
  // If awaitServerConnection is specified, wait for server-side registration
99
183
  if (options && 'awaitServerConnection' in options && options.awaitServerConnection) {
100
184
  const awaitOptions = options.awaitServerConnection;
101
185
  const waitOptions = {
102
186
  timeout: awaitOptions.timeout ?? 5000,
103
- predicate: (conn) => conn.request.url === pathWithQuery,
187
+ predicate: (conn) => conn.request.url === pathWithQuery && conn.request.method.toUpperCase() === method,
104
188
  };
105
189
  try {
106
190
  // Both branches call the same method; the spy is invariant in its session
@@ -272,11 +356,17 @@ export class SSEHttpClient {
272
356
  *
273
357
  * This aborts the underlying fetch request. Call this when done
274
358
  * consuming events to clean up resources.
359
+ *
360
+ * Aborting also discards any unread response body, so for a non-stream
361
+ * response (an error raised before `sse.start()`, say) read
362
+ * `response.json()` / `response.text()` before calling this.
275
363
  */
276
364
  close() {
277
365
  this.closed = true;
278
- // Cancel the reader first to prevent unhandled rejections from pending reads
279
- this.reader.cancel().catch(() => {
366
+ // Cancel the reader first to prevent unhandled rejections from pending reads.
367
+ // Only touch it if events were actually consumed - acquiring it here would
368
+ // lock a body the caller may still want to read as JSON/text.
369
+ this.streamReader?.cancel().catch(() => {
280
370
  // Expected: may already be closed or errored
281
371
  });
282
372
  this.abortController.abort();
@@ -1 +1 @@
1
- {"version":3,"file":"sseHttpClient.js","sourceRoot":"","sources":["../../../lib/testing/sseHttpClient.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sseHttpClient.js","sourceRoot":"","sources":["../../../lib/testing/sseHttpClient.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,16 @@
1
1
  import type { AnySSEContractDefinition, HttpStatusCode } from '@lokalise/api-contracts';
2
+ import type { InjectOptions } from 'fastify';
2
3
  import type { z } from 'zod';
3
4
  import type { ParsedSSEEvent } from '../sse/sseParser.ts';
5
+ /**
6
+ * HTTP methods Fastify's `inject()` accepts, in either case.
7
+ *
8
+ * Derived from Fastify's own inject options instead of being hand-listed, so it
9
+ * tracks whatever the installed Fastify supports and never needs to be redefined
10
+ * downstream. Note that Fastify's `HTTPMethods` is wider than this - it also
11
+ * covers `SEARCH`, `QUERY` and the WebDAV verbs, which `inject()` does not take.
12
+ */
13
+ export type SSEInjectMethod = NonNullable<InjectOptions['method']>;
4
14
  /** Safely infer the output type of an optional Zod schema property. */
5
15
  type InferOptionalSchema<T, Fallback = unknown> = NonNullable<T> extends z.ZodTypeAny ? z.infer<NonNullable<T>> : Fallback;
6
16
  /**
@@ -56,7 +66,8 @@ export interface SSETestConnection {
56
66
  */
57
67
  export type SSEConnectOptions = {
58
68
  headers?: Record<string, string>;
59
- method?: 'GET' | 'POST' | 'PUT' | 'PATCH';
69
+ /** Any method `inject()` accepts (default: `'POST'` for `connectWithBody`) */
70
+ method?: SSEInjectMethod;
60
71
  body?: unknown;
61
72
  };
62
73
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opinionated-machine",
3
- "version": "10.2.0",
3
+ "version": "10.3.0",
4
4
  "description": "Very opinionated DI framework for fastify, built on top of awilix ",
5
5
  "type": "module",
6
6
  "license": "MIT",