opinionated-machine 10.3.0 → 10.5.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.
Files changed (32) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +107 -3
  3. package/dist/lib/api-contracts/apiRouteBuilder.d.ts +1 -1
  4. package/dist/lib/api-contracts/apiRouteBuilder.js +36 -1
  5. package/dist/lib/api-contracts/apiRouteBuilder.js.map +1 -1
  6. package/dist/lib/sse/index.d.ts +1 -0
  7. package/dist/lib/sse/index.js +1 -0
  8. package/dist/lib/sse/index.js.map +1 -1
  9. package/dist/lib/sse/sseSendDiagnostics.d.ts +134 -0
  10. package/dist/lib/sse/sseSendDiagnostics.js +277 -0
  11. package/dist/lib/sse/sseSendDiagnostics.js.map +1 -0
  12. package/dist/lib/testing/apiSseEventValidation.d.ts +40 -0
  13. package/dist/lib/testing/apiSseEventValidation.js +78 -0
  14. package/dist/lib/testing/apiSseEventValidation.js.map +1 -0
  15. package/dist/lib/testing/apiSseHttpHelpers.d.ts +168 -0
  16. package/dist/lib/testing/apiSseHttpHelpers.js +214 -0
  17. package/dist/lib/testing/apiSseHttpHelpers.js.map +1 -0
  18. package/dist/lib/testing/apiSseInjectHelpers.d.ts +17 -2
  19. package/dist/lib/testing/apiSseInjectHelpers.js +227 -56
  20. package/dist/lib/testing/apiSseInjectHelpers.js.map +1 -1
  21. package/dist/lib/testing/apiSseTestTypes.d.ts +83 -3
  22. package/dist/lib/testing/index.d.ts +3 -2
  23. package/dist/lib/testing/index.js +1 -0
  24. package/dist/lib/testing/index.js.map +1 -1
  25. package/dist/lib/testing/sseHttpClient.d.ts +52 -0
  26. package/dist/lib/testing/sseHttpClient.js +77 -0
  27. package/dist/lib/testing/sseHttpClient.js.map +1 -1
  28. package/dist/lib/testing/sseInjectClient.d.ts +17 -0
  29. package/dist/lib/testing/sseInjectClient.js +31 -0
  30. package/dist/lib/testing/sseInjectClient.js.map +1 -1
  31. package/dist/lib/testing/sseTestTypes.d.ts +36 -2
  32. package/package.json +1 -1
@@ -0,0 +1,214 @@
1
+ import { buildRequestPath } from '@lokalise/api-contracts';
2
+ import { describeSendFailures, openSSEDiagnosticsScope, unhandledSendFailures, } from "../sse/sseSendDiagnostics.js";
3
+ import { assertSSEResponse, mediaTypeOf, SSE_CONTENT_TYPE } from "./apiSseEventValidation.js";
4
+ import { SSEHttpClient } from "./sseHttpClient.js";
5
+ /**
6
+ * A live SSE connection over real HTTP, read through the contract that declares it.
7
+ *
8
+ * The contract-typed counterpart of {@link SSEHttpClient}: events arrive incrementally, as
9
+ * they do there, but each one is JSON-parsed, validated against the contract's `sseResponse` /
10
+ * `sseBody` schemas and typed as a discriminated union on `event` — the same events
11
+ * `injectApiSSE` produces, so a suite can move an assertion between the two paths unchanged.
12
+ */
13
+ export class ApiSSEHttpClient {
14
+ /** The underlying untyped client, for the parts of it this wrapper doesn't cover. */
15
+ raw;
16
+ contract;
17
+ scope;
18
+ /** @internal Built by {@link connectApiSSE}. */
19
+ constructor(raw, contract, scope) {
20
+ this.raw = raw;
21
+ this.contract = contract;
22
+ this.scope = scope;
23
+ }
24
+ /**
25
+ * The fetch `Response`, available before any event is consumed — so status and headers can
26
+ * be asserted while the handler is still producing events.
27
+ */
28
+ get response() {
29
+ return this.raw.response;
30
+ }
31
+ /**
32
+ * Yield the contract's events as they arrive, typed and validated per event name.
33
+ *
34
+ * Throws — before yielding anything — if the endpoint answered with something other than an
35
+ * event stream (an error raised before `sse.start()`, say), naming its status and body
36
+ * instead of reporting an empty stream.
37
+ *
38
+ * @param signal - Optional `AbortSignal` to stop the generator early
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * for await (const event of client.events()) {
43
+ * if (event.event === 'issue') expect(event.data.severity).toBe('minor')
44
+ * if (event.event === 'review') break
45
+ * }
46
+ * ```
47
+ */
48
+ async *events(signal) {
49
+ await this.assertStreamResponse('events()');
50
+ yield* this.raw.apiEvents(this.contract, signal);
51
+ if (!signal?.aborted) {
52
+ // The server ended the stream: anything the handler failed to send is known now, and
53
+ // is why an expected event never arrived.
54
+ this.assertNoSendFailures('events()');
55
+ this.releaseScopeIfClosed();
56
+ }
57
+ }
58
+ /**
59
+ * Collect events until a count is reached or a predicate matches, each one typed and
60
+ * validated against the contract.
61
+ *
62
+ * A collection that ends short — the stream closed early, or the wait timed out — usually
63
+ * means the handler failed to send an event it was supposed to; when that is what happened,
64
+ * the thrown error names the event and its validation issues instead of leaving the test to
65
+ * report a missing event with no reason.
66
+ *
67
+ * Throws straight away if the endpoint answered with something other than an event stream,
68
+ * rather than waiting out the timeout on a stream that was never going to arrive.
69
+ *
70
+ * @param countOrPredicate - Number of events to collect, or a predicate that ends collection
71
+ * (the matching event is included). The predicate is invoked exactly once per event.
72
+ * @param timeout - Maximum time to wait in milliseconds (default: 5000)
73
+ */
74
+ async collectEvents(countOrPredicate, timeout) {
75
+ await this.assertStreamResponse('collectEvents()');
76
+ // Whether the caller's predicate matched is remembered as it runs, so satisfaction can be
77
+ // decided afterwards without invoking it a second time on the events it already saw.
78
+ let matched = false;
79
+ const target = typeof countOrPredicate === 'number'
80
+ ? countOrPredicate
81
+ : (event) => {
82
+ matched = countOrPredicate(event) || matched;
83
+ return matched;
84
+ };
85
+ let collected;
86
+ try {
87
+ collected = await this.raw.collectApiEvents(this.contract, target, timeout);
88
+ }
89
+ catch (err) {
90
+ // The collection failed outright: every recorded failure is context worth reporting,
91
+ // including the ones the route recovered from.
92
+ const failures = this.scope.failures();
93
+ this.releaseScopeIfClosed();
94
+ if (failures.length === 0) {
95
+ throw err;
96
+ }
97
+ throw new Error(`collectEvents() — ${describeSendFailures(failures)}\nRaised while collecting: ${err instanceof Error ? err.message : String(err)}`);
98
+ }
99
+ // `collectEvents` also returns short when the server closed the stream before the target
100
+ // was met, which is exactly what a failed send looks like from here.
101
+ const satisfied = typeof countOrPredicate === 'number' ? collected.length >= countOrPredicate : matched;
102
+ if (!satisfied) {
103
+ this.assertNoSendFailures('collectEvents()');
104
+ }
105
+ this.releaseScopeIfClosed();
106
+ return collected;
107
+ }
108
+ /**
109
+ * Throw what the handler failed to send and did not recover from, if anything was recorded
110
+ * for this connection.
111
+ *
112
+ * A failure the route caught and streamed around left the response it meant to produce, so
113
+ * it is reported through {@link ApiSSEHttpClient.sendFailures} instead of failing the read.
114
+ */
115
+ assertNoSendFailures(reader) {
116
+ const failures = unhandledSendFailures(this.scope.failures());
117
+ if (failures.length > 0) {
118
+ throw new Error(`${reader} — ${describeSendFailures(failures)}`);
119
+ }
120
+ }
121
+ /**
122
+ * Reject a response that is not an event stream, with its status and body.
123
+ *
124
+ * Without this a `401` (or any other pre-stream error response) reads as a stream that
125
+ * never produced an event: `collectEvents` waits out its full timeout and reports "got 0",
126
+ * with the actual status nowhere in the failure.
127
+ */
128
+ async assertStreamResponse(reader) {
129
+ const { response } = this.raw;
130
+ const contentType = response.headers.get('content-type') ?? undefined;
131
+ let body;
132
+ if (mediaTypeOf(contentType) !== SSE_CONTENT_TYPE) {
133
+ // Only read the body once the response is known not to be a stream, and never from the
134
+ // live response, whose body the client still needs.
135
+ body = await readBodySnapshot(response);
136
+ this.scope.dispose();
137
+ }
138
+ assertSSEResponse(response.status, contentType, reader, body);
139
+ }
140
+ /**
141
+ * Unregister the diagnostics scope once the stream is over, keeping what it recorded.
142
+ *
143
+ * `close()` is the usual trigger; a test that reads a stream to its end and never closes the
144
+ * client would otherwise leave the scope registered for the rest of the process.
145
+ */
146
+ releaseScopeIfClosed() {
147
+ if (this.raw.isClosed) {
148
+ this.scope.dispose();
149
+ }
150
+ }
151
+ /**
152
+ * The sends the handler could not make on this connection — a payload that failed the
153
+ * contract's schema for its event, say — recorded instead of being left in the server log.
154
+ *
155
+ * Includes the failures the route recovered from (`handled: true`), which the readers pass
156
+ * over precisely because the response was still the one the route meant to produce.
157
+ *
158
+ * Only routes built with this package's `buildApiRoute` report them.
159
+ */
160
+ sendFailures() {
161
+ return this.scope.failures();
162
+ }
163
+ /** Close the connection from the client side. */
164
+ close() {
165
+ this.scope.dispose();
166
+ this.raw.close();
167
+ }
168
+ }
169
+ /** The body of a non-stream response, for an error message; never worth failing over. */
170
+ async function readBodySnapshot(response) {
171
+ try {
172
+ return await response.clone().text();
173
+ }
174
+ catch {
175
+ // Already consumed, or aborted mid-read: the status and content-type still say enough.
176
+ return undefined;
177
+ }
178
+ }
179
+ export async function connectApiSSE(baseUrl, contract, params, options) {
180
+ // biome-ignore lint/suspicious/noExplicitAny: params shape depends on the contract
181
+ const requestParams = params;
182
+ const path = buildRequestPath(contract.pathResolver(requestParams.pathParams), requestParams.pathPrefix);
183
+ // `headers` may be a factory, exactly as the contract client and `injectApiSSE` accept it.
184
+ const callerHeaders = typeof requestParams.headers === 'function'
185
+ ? await requestParams.headers()
186
+ : requestParams.headers;
187
+ // Records the sends the handler could not make, matched to this connection by a header the
188
+ // route builder honours only for scopes open in this process.
189
+ const scope = openSSEDiagnosticsScope();
190
+ const connectOptions = {
191
+ method: contract.method,
192
+ headers: { ...callerHeaders, ...scope.headers },
193
+ ...(requestParams.queryParams !== undefined && { query: requestParams.queryParams }),
194
+ ...(requestParams.body !== undefined && { body: requestParams.body }),
195
+ };
196
+ // A connection that never happened has no reader to dispose its scope later, and a scope
197
+ // left registered outlives the test that opened it.
198
+ try {
199
+ if (!options) {
200
+ const raw = await SSEHttpClient.connect(baseUrl, path, connectOptions);
201
+ return new ApiSSEHttpClient(raw, contract, scope);
202
+ }
203
+ const { client: raw, serverConnection } = await SSEHttpClient.connect(baseUrl, path, {
204
+ ...connectOptions,
205
+ awaitServerConnection: options.awaitServerConnection,
206
+ });
207
+ return { client: new ApiSSEHttpClient(raw, contract, scope), serverConnection };
208
+ }
209
+ catch (error) {
210
+ scope.dispose();
211
+ throw error;
212
+ }
213
+ }
214
+ //# sourceMappingURL=apiSseHttpHelpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apiSseHttpHelpers.js","sourceRoot":"","sources":["../../../lib/testing/apiSseHttpHelpers.ts"],"names":[],"mappings":"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"}
@@ -31,8 +31,12 @@ export declare function bindApiEvents<Contract extends ApiContract>(contract: Co
31
31
  * statuses expose no JSON body through `bodyForStatus`; read them with `events()`, or use
32
32
  * `injectByApiContract` when you want the JSON side.
33
33
  *
34
- * Best for SSE endpoints that complete — Fastify's `inject()` waits for the whole response.
35
- * For long-lived connections, use `SSEHttpClient` against a real HTTP server.
34
+ * The response is injected as a stream, so `head` resolves as soon as the handler starts
35
+ * streaming and `stream()` yields each event as it is written — a test can assert
36
+ * progressive delivery without a listening server. `closed` and `events()` still wait for
37
+ * the response to complete, so an endpoint that never closes its stream (a `keepAlive`
38
+ * session) can only be read through `stream()`; use `SSEHttpClient` / `connectApiSSE`
39
+ * against a real server for those.
36
40
  *
37
41
  * @param app - Fastify instance
38
42
  * @param contract - Contract built with `defineApiContract`
@@ -55,6 +59,17 @@ export declare function bindApiEvents<Contract extends ApiContract>(contract: Co
55
59
  *
56
60
  * @example
57
61
  * ```typescript
62
+ * // Progressive delivery: each event is observed while the handler is still working
63
+ * const { head, stream } = injectApiSSE(app, lqaTextSegmentContract, { body: { segment } })
64
+ * expect((await head).statusCode).toBe(200)
65
+ *
66
+ * for await (const event of stream()) {
67
+ * if (event.event === 'issue') expect(handlerFinished).toBe(false)
68
+ * }
69
+ * ```
70
+ *
71
+ * @example
72
+ * ```typescript
58
73
  * // A documented pre-stream error response, typed by the contract's 400 schema
59
74
  * const { bodyForStatus } = injectApiSSE(app, lqaTextSegmentContract, { body: { segment: '' } })
60
75
  * const error = await bodyForStatus(400)
@@ -1,16 +1,19 @@
1
- import { getSseSchemaByEventName, resolveResponseEntry, } from '@lokalise/api-contracts';
2
- import { injectByApiContract } from '@lokalise/fastify-api-contracts';
3
- import { parseSSEEvents } from "../sse/sseParser.js";
1
+ import { buildRequestPath, resolveResponseEntry, } from '@lokalise/api-contracts';
2
+ import { parseSSEBuffer, parseSSEEvents } from "../sse/sseParser.js";
3
+ import { describeSendFailures, openSSEDiagnosticsScope, unhandledSendFailures, } from "../sse/sseSendDiagnostics.js";
4
+ import { assertSSEResponse, mediaTypeOf, resolveApiSseSchemas, SSE_CONTENT_TYPE, validateApiSseEvent, } from "./apiSseEventValidation.js";
4
5
  import { truncateBody } from "./sseInjectShared.js";
5
- const SSE_CONTENT_TYPE = 'text/event-stream';
6
+ /**
7
+ * Methods whose contracts can declare a request body.
8
+ *
9
+ * Exactly the methods of `PayloadApiContract`; `GetApiContract` and `DeleteApiContract` type
10
+ * `requestBodySchema` as `never`, so no contract can declare a body for any other verb.
11
+ */
12
+ const METHODS_WITH_BODY = new Set(['POST', 'PUT', 'PATCH']);
6
13
  /** Read a response header that light-my-request may expose as an array. */
7
14
  function readHeader(value) {
8
15
  return Array.isArray(value) ? value[0] : value;
9
16
  }
10
- /** Strip `; charset=…` style parameters from a media type. */
11
- function mediaTypeOf(contentType) {
12
- return contentType?.split(';')[0]?.trim().toLowerCase();
13
- }
14
17
  const STATUS_RANGE_KEYS = ['1xx', '2xx', '3xx', '4xx', '5xx'];
15
18
  /**
16
19
  * The `responsesByStatusCode` key that serves a status, following the same
@@ -93,6 +96,10 @@ export function bindApiBodyForStatus(contract, closed) {
93
96
  return parseJsonBody(schema, expected, res.body);
94
97
  });
95
98
  }
99
+ /** Reject a response that is not an event stream, naming the reader that asked for one. */
100
+ function assertSSEHead(head, reader, body) {
101
+ assertSSEResponse(head.statusCode, readHeader(head.headers['content-type']), reader, body);
102
+ }
96
103
  /**
97
104
  * Build an `events` accessor bound to one `injectApiSSE` call: parses the SSE body and
98
105
  * validates every event against the contract's `sseBody` schemas.
@@ -103,40 +110,156 @@ export function bindApiEvents(contract, closed) {
103
110
  return async () => {
104
111
  const res = await closed;
105
112
  // Merges the SSE schemas of every declared status, not just the successful ones.
106
- const schemaByEventName = getSseSchemaByEventName(contract);
107
- if (!schemaByEventName) {
108
- throw new Error('events() — the contract declares no SSE response');
113
+ const schemaByEventName = resolveApiSseSchemas(contract, 'events()');
114
+ assertSSEHead(res, 'events()', res.body);
115
+ return parseSSEEvents(res.body).map((event) => validateApiSseEvent(schemaByEventName, event, 'events()'));
116
+ };
117
+ }
118
+ /**
119
+ * Consumes an injected SSE response as it is written, so events can be read while the handler
120
+ * is still running and the completed body is still available afterwards.
121
+ *
122
+ * Fastify's `inject({ payloadAsStream: true })` resolves as soon as the response head is on
123
+ * the wire — for a streaming handler, at `sse.start()` — and exposes the payload as a
124
+ * `Readable` that receives every write. This pump drains it eagerly (so a slow or absent
125
+ * consumer never blocks the handler), keeping both the parsed events seen so far and the raw
126
+ * body text, and wakes any generator waiting on more.
127
+ */
128
+ class InjectedSSEStream {
129
+ /** Head of the response, available before the handler finished streaming. */
130
+ head;
131
+ /** Full response, once the stream ended. */
132
+ closed;
133
+ received = [];
134
+ waiters = [];
135
+ ended = false;
136
+ failure;
137
+ constructor(injected) {
138
+ this.head = injected.then((res) => ({
139
+ statusCode: res.statusCode,
140
+ headers: res.headers,
141
+ }));
142
+ this.closed = this.drain(injected);
143
+ // The pump owns both promises from the moment the request is injected; a caller reading
144
+ // only one of them (or neither, for a request it expects to fail) must not turn the other
145
+ // into an unhandled rejection. Awaiting either still rejects as usual.
146
+ this.head.catch(() => { });
147
+ this.closed.catch(() => { });
148
+ }
149
+ async drain(injected) {
150
+ let res;
151
+ try {
152
+ res = await injected;
109
153
  }
110
- const contentType = mediaTypeOf(readHeader(res.headers['content-type']));
111
- if (contentType !== SSE_CONTENT_TYPE) {
112
- throw new Error(`events() — response is not an SSE stream (status ${res.statusCode}, content-type ${contentType ?? 'absent'}); use bodyForStatus(${res.statusCode}) for declared error responses. Body: ${truncateBody(res.body)}`);
154
+ catch (err) {
155
+ this.fail(err);
156
+ throw err;
113
157
  }
114
- return parseSSEEvents(res.body).map((event) => {
115
- // An SSE event without an `event:` field is a `message` event per the spec.
116
- const name = event.event ?? 'message';
117
- const schema = schemaByEventName[name];
118
- if (!schema) {
119
- throw new Error(`events() — the contract declares no schema for event "${name}"`);
158
+ let body = '';
159
+ let buffer = '';
160
+ const decoder = new TextDecoder();
161
+ try {
162
+ for await (const chunk of res.stream()) {
163
+ const text = typeof chunk === 'string' ? chunk : decoder.decode(chunk, { stream: true });
164
+ body += text;
165
+ buffer += text;
166
+ const { events, remaining } = parseSSEBuffer(buffer);
167
+ buffer = remaining;
168
+ if (events.length > 0) {
169
+ this.received.push(...events);
170
+ this.notify();
171
+ }
120
172
  }
121
- let parsedJson;
122
- try {
123
- parsedJson = JSON.parse(event.data);
173
+ }
174
+ catch (err) {
175
+ this.fail(err);
176
+ throw err;
177
+ }
178
+ // A stream that ended without a terminating blank line still carries one last event.
179
+ const trailing = parseSSEEvents(buffer);
180
+ if (trailing.length > 0) {
181
+ this.received.push(...trailing);
182
+ }
183
+ this.ended = true;
184
+ this.notify();
185
+ return {
186
+ statusCode: res.statusCode,
187
+ headers: res.headers,
188
+ body,
189
+ };
190
+ }
191
+ fail(err) {
192
+ this.failure = err;
193
+ this.ended = true;
194
+ this.notify();
195
+ }
196
+ notify() {
197
+ const waiters = this.waiters;
198
+ this.waiters = [];
199
+ for (const wake of waiters) {
200
+ wake();
201
+ }
202
+ }
203
+ /** Resolve once more events arrived, the stream ended, or the caller aborted. */
204
+ nextTick(signal) {
205
+ return new Promise((resolve) => {
206
+ let settled = false;
207
+ const wake = () => {
208
+ if (settled) {
209
+ return;
210
+ }
211
+ settled = true;
212
+ signal?.removeEventListener('abort', wake);
213
+ resolve();
214
+ };
215
+ this.waiters.push(wake);
216
+ signal?.addEventListener('abort', wake, { once: true });
217
+ });
218
+ }
219
+ /**
220
+ * Yield every event of the stream, from the first one, waiting for more while the handler
221
+ * is still writing. Replayable: each call starts from the beginning of the stream.
222
+ */
223
+ async *events(signal) {
224
+ let index = 0;
225
+ while (true) {
226
+ if (signal?.aborted) {
227
+ return;
124
228
  }
125
- catch (err) {
126
- throw new Error(`events() — data of event "${name}" is not valid JSON: ${err.message}; data: ${truncateBody(event.data)}`);
229
+ while (index < this.received.length) {
230
+ // biome-ignore lint/style/noNonNullAssertion: index is bounded by the array length
231
+ yield this.received[index++];
232
+ if (signal?.aborted) {
233
+ return;
234
+ }
127
235
  }
128
- const parsed = schema.safeParse(parsedJson);
129
- if (!parsed.success) {
130
- throw new Error(`events() — data of event "${name}" does not match the declared schema: ${parsed.error.message}; data: ${truncateBody(event.data)}`);
236
+ if (this.failure) {
237
+ throw this.failure;
131
238
  }
132
- return {
133
- ...(event.id !== undefined && { id: event.id }),
134
- ...(event.retry !== undefined && { retry: event.retry }),
135
- event: name,
136
- data: parsed.data,
137
- };
138
- });
139
- };
239
+ if (this.ended) {
240
+ return;
241
+ }
242
+ await this.nextTick(signal);
243
+ }
244
+ }
245
+ }
246
+ /**
247
+ * Throw the sends the route could not make and did not recover from, if any were recorded for
248
+ * this request.
249
+ *
250
+ * A payload that fails the contract's schema for its event makes `session.send()` throw
251
+ * inside the handler; the event never reaches the wire and the reason only reaches the server
252
+ * log. Reading the stream through these helpers surfaces it here instead, so the test fails
253
+ * on the event that was never sent rather than on the next one it did receive.
254
+ */
255
+ function assertNoSendFailures(scope, reader) {
256
+ // A failure the route caught and streamed around is context, not a verdict: the response
257
+ // the test read is the one the route meant to produce. Only the failures that truncated it
258
+ // explain a missing event, so only those fail the read — the rest stay on `sendFailures()`.
259
+ const failures = unhandledSendFailures(scope.failures());
260
+ if (failures.length > 0) {
261
+ throw new Error(`${reader} — ${describeSendFailures(failures)}`);
262
+ }
140
263
  }
141
264
  /**
142
265
  * Inject an SSE request using a contract built with `defineApiContract` + `sseResponse` /
@@ -153,8 +276,12 @@ export function bindApiEvents(contract, closed) {
153
276
  * statuses expose no JSON body through `bodyForStatus`; read them with `events()`, or use
154
277
  * `injectByApiContract` when you want the JSON side.
155
278
  *
156
- * Best for SSE endpoints that complete — Fastify's `inject()` waits for the whole response.
157
- * For long-lived connections, use `SSEHttpClient` against a real HTTP server.
279
+ * The response is injected as a stream, so `head` resolves as soon as the handler starts
280
+ * streaming and `stream()` yields each event as it is written — a test can assert
281
+ * progressive delivery without a listening server. `closed` and `events()` still wait for
282
+ * the response to complete, so an endpoint that never closes its stream (a `keepAlive`
283
+ * session) can only be read through `stream()`; use `SSEHttpClient` / `connectApiSSE`
284
+ * against a real server for those.
158
285
  *
159
286
  * @param app - Fastify instance
160
287
  * @param contract - Contract built with `defineApiContract`
@@ -177,6 +304,17 @@ export function bindApiEvents(contract, closed) {
177
304
  *
178
305
  * @example
179
306
  * ```typescript
307
+ * // Progressive delivery: each event is observed while the handler is still working
308
+ * const { head, stream } = injectApiSSE(app, lqaTextSegmentContract, { body: { segment } })
309
+ * expect((await head).statusCode).toBe(200)
310
+ *
311
+ * for await (const event of stream()) {
312
+ * if (event.event === 'issue') expect(handlerFinished).toBe(false)
313
+ * }
314
+ * ```
315
+ *
316
+ * @example
317
+ * ```typescript
180
318
  * // A documented pre-stream error response, typed by the contract's 400 schema
181
319
  * const { bodyForStatus } = injectApiSSE(app, lqaTextSegmentContract, { body: { segment: '' } })
182
320
  * const error = await bodyForStatus(400)
@@ -186,27 +324,60 @@ export function bindApiEvents(contract, closed) {
186
324
  export function injectApiSSE(app, contract, params) {
187
325
  // biome-ignore lint/suspicious/noExplicitAny: params shape depends on the contract
188
326
  const requestParams = params;
189
- const closed = injectByApiContract(app, contract, {
190
- ...requestParams,
191
- // `accept` first so an explicit caller header still wins; headers may be a factory,
192
- // which `injectByApiContract` resolves for us — resolve the caller's here too.
193
- headers: async () => ({
194
- accept: SSE_CONTENT_TYPE,
195
- ...(typeof requestParams.headers === 'function'
196
- ? await requestParams.headers()
197
- : requestParams.headers),
198
- }),
199
- }).then((res) => ({
200
- statusCode: res.statusCode,
201
- headers: res.headers,
202
- body: res.body,
203
- }));
327
+ const method = contract.method.toUpperCase();
328
+ const url = buildRequestPath(contract.pathResolver(requestParams.pathParams), requestParams.pathPrefix);
329
+ // Records the sends the handler could not make (a payload that failed its event schema),
330
+ // matched to this request by a header the route builder honours only for open scopes.
331
+ const scope = openSSEDiagnosticsScope();
332
+ const injected = (async () => {
333
+ // `headers` may be a factory, exactly as `injectByApiContract` accepts it.
334
+ const callerHeaders = typeof requestParams.headers === 'function'
335
+ ? await requestParams.headers()
336
+ : requestParams.headers;
337
+ return app.inject({
338
+ method,
339
+ url,
340
+ // `accept` first so an explicit caller header still wins; the diagnostics header last,
341
+ // since it addresses this call's scope and nothing else may claim it.
342
+ headers: { accept: SSE_CONTENT_TYPE, ...callerHeaders, ...scope.headers },
343
+ ...(requestParams.queryParams !== undefined && { query: requestParams.queryParams }),
344
+ ...(METHODS_WITH_BODY.has(method) && { payload: requestParams.body }),
345
+ payloadAsStream: true,
346
+ });
347
+ })();
348
+ const pump = new InjectedSSEStream(injected);
349
+ const { head, closed } = pump;
350
+ // Failures are only complete once the response is, and the scope must not outlive the
351
+ // request either way; disposing snapshots what was recorded, so the readers below still
352
+ // report it afterwards.
353
+ void closed.then(() => scope.dispose(), () => scope.dispose());
354
+ const bufferedEvents = bindApiEvents(contract, closed);
355
+ const events = async () => {
356
+ await closed;
357
+ assertNoSendFailures(scope, 'events()');
358
+ return bufferedEvents();
359
+ };
360
+ const stream = async function* (signal) {
361
+ const schemaByEventName = resolveApiSseSchemas(contract, 'stream()');
362
+ assertSSEHead(await head, 'stream()');
363
+ for await (const event of pump.events(signal)) {
364
+ yield validateApiSseEvent(schemaByEventName, event, 'stream()');
365
+ }
366
+ if (!signal?.aborted) {
367
+ // The stream ended: anything the handler failed to send is known now, and is the
368
+ // reason an expected event never arrived.
369
+ assertNoSendFailures(scope, 'stream()');
370
+ }
371
+ };
204
372
  return {
205
373
  closed,
374
+ head,
375
+ sendFailures: () => scope.failures(),
206
376
  bodyForStatus: bindApiBodyForStatus(contract, closed),
207
- // `events` is typed `never` for contracts that declare no SSE response, which no concrete
208
- // function satisfies — the binder returns the callable form and it is narrowed here.
209
- events: bindApiEvents(contract, closed),
377
+ // `events` / `stream` are typed `never` for contracts that declare no SSE response, which
378
+ // no concrete function satisfies — the callable forms are narrowed here.
379
+ events: events,
380
+ stream: stream,
210
381
  };
211
382
  }
212
383
  //# sourceMappingURL=apiSseInjectHelpers.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"apiSseInjectHelpers.js","sourceRoot":"","sources":["../../../lib/testing/apiSseInjectHelpers.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"apiSseInjectHelpers.js","sourceRoot":"","sources":["../../../lib/testing/apiSseInjectHelpers.ts"],"names":[],"mappings":"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"}