@equinor/fusion-framework-module-http 6.2.5 → 6.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.
@@ -28,3 +28,24 @@ export declare class HttpJsonResponseError<TType = unknown, TResponse = Response
28
28
  data?: TType;
29
29
  });
30
30
  }
31
+ /**
32
+ * Represents an error that occurs when handling a server-sent event (SSE) HTTP response.
33
+ *
34
+ * @template TType - The type of additional data associated with the error.
35
+ * @template TResponse - The type of the HTTP response object.
36
+ *
37
+ * @extends HttpResponseError<TResponse>
38
+ */
39
+ export declare class ServerSentEventResponseError<TType = unknown, TResponse = Response> extends HttpResponseError<TResponse> {
40
+ static Name: string;
41
+ /**
42
+ * Creates a new instance of the error.
43
+ *
44
+ * @param message - The error message describing the cause of the error.
45
+ * @param response - The HTTP response associated with the error.
46
+ * @param options - Optional error options, which may include additional data of type `TType`.
47
+ */
48
+ constructor(message: string, response: TResponse, options?: ErrorOptions & {
49
+ data?: TType;
50
+ });
51
+ }
@@ -2,6 +2,7 @@ import { Subject } from 'rxjs';
2
2
  import type { Observable, ObservableInput } from 'rxjs';
3
3
  import type { IHttpRequestHandler, IHttpResponseHandler } from '../operators';
4
4
  import type { BlobResult, FetchRequest, FetchRequestInit, FetchResponse, IHttpClient, JsonRequest, StreamResponse } from './types';
5
+ import { type ServerSentEvent, type SseSelectorOptions } from '../selectors/sse-selector';
5
6
  /**
6
7
  * Configuration options for creating an `HttpClient` instance.
7
8
  *
@@ -114,6 +115,29 @@ export declare class HttpClient<TRequest extends FetchRequest = FetchRequest, TR
114
115
  * @returns A Promise that resolves to the blob result.
115
116
  */
116
117
  blob<T = BlobResult>(path: string, args?: FetchRequestInit<T, TRequest, TResponse>): Promise<T>;
118
+ /**
119
+ * Initiates a Server-Sent Events (SSE) stream request to the specified path and returns a stream of events.
120
+ *
121
+ * @template T - The type of the event data expected from the SSE stream.
122
+ * @param path - The endpoint path to connect to for the SSE stream.
123
+ * @param args - Optional fetch request initialization options, including headers and abort signal.
124
+ * @param options - Optional selector options for customizing the SSE stream, excluding the abort signal.
125
+ *
126
+ * @returns A `StreamResponse` that emits `ServerSentEvent<T>` objects as they are received from the server.
127
+ *
128
+ * @example
129
+ * const sse$ = httpClient.sse(
130
+ * '/events',
131
+ * { method: 'POST', body: JSON.stringify({ prompt: 'tell me a joke' }) },
132
+ * { eventFilter: ['message'] }
133
+ * );
134
+ * sse$.subscribe({
135
+ * next: (event) => console.log(event),
136
+ * error: (err) => console.error(err),
137
+ * complete: () => console.log('Completed'),
138
+ * });
139
+ */
140
+ sse$<T = unknown>(path: string, args?: FetchRequestInit<ServerSentEvent<T>, TRequest, TResponse> | null, options?: Omit<SseSelectorOptions<T>, 'abortSignal'>): StreamResponse<ServerSentEvent<T>>;
117
141
  /** @deprecated */
118
142
  jsonAsync<T = unknown>(path: string, args?: FetchRequestInit<T, JsonRequest<TRequest>, TResponse>): Promise<T>;
119
143
  /**
@@ -53,7 +53,7 @@ export declare const requestInitSchema: z.ZodObject<{
53
53
  signal: z.ZodOptional<z.ZodType<AbortSignal, z.ZodTypeDef, AbortSignal>>;
54
54
  }, "strip", z.ZodTypeAny, {
55
55
  body?: string | Blob | ArrayBuffer | FormData | URLSearchParams | ReadableStream<unknown> | undefined;
56
- cache?: "default" | "force-cache" | "no-cache" | "no-store" | "only-if-cached" | "reload" | undefined;
56
+ cache?: "default" | "no-cache" | "force-cache" | "no-store" | "only-if-cached" | "reload" | undefined;
57
57
  credentials?: "include" | "omit" | "same-origin" | undefined;
58
58
  headers?: Headers | Record<string, string> | undefined;
59
59
  integrity?: string | undefined;
@@ -72,7 +72,7 @@ export declare const requestInitSchema: z.ZodObject<{
72
72
  browsingTopics?: boolean | undefined;
73
73
  }, {
74
74
  body?: string | Blob | ArrayBuffer | FormData | URLSearchParams | ReadableStream<unknown> | undefined;
75
- cache?: "default" | "force-cache" | "no-cache" | "no-store" | "only-if-cached" | "reload" | undefined;
75
+ cache?: "default" | "no-cache" | "force-cache" | "no-store" | "only-if-cached" | "reload" | undefined;
76
76
  credentials?: "include" | "omit" | "same-origin" | undefined;
77
77
  headers?: Headers | Record<string, string> | undefined;
78
78
  integrity?: string | undefined;
@@ -129,7 +129,7 @@ export declare const fetchRequestSchema: z.ZodObject<{
129
129
  uri: string;
130
130
  body?: string | Blob | ArrayBuffer | FormData | URLSearchParams | ReadableStream<unknown> | undefined;
131
131
  path?: string | undefined;
132
- cache?: "default" | "force-cache" | "no-cache" | "no-store" | "only-if-cached" | "reload" | undefined;
132
+ cache?: "default" | "no-cache" | "force-cache" | "no-store" | "only-if-cached" | "reload" | undefined;
133
133
  credentials?: "include" | "omit" | "same-origin" | undefined;
134
134
  headers?: Headers | Record<string, string> | undefined;
135
135
  integrity?: string | undefined;
@@ -150,7 +150,7 @@ export declare const fetchRequestSchema: z.ZodObject<{
150
150
  uri: string;
151
151
  body?: string | Blob | ArrayBuffer | FormData | URLSearchParams | ReadableStream<unknown> | undefined;
152
152
  path?: string | undefined;
153
- cache?: "default" | "force-cache" | "no-cache" | "no-store" | "only-if-cached" | "reload" | undefined;
153
+ cache?: "default" | "no-cache" | "force-cache" | "no-store" | "only-if-cached" | "reload" | undefined;
154
154
  credentials?: "include" | "omit" | "same-origin" | undefined;
155
155
  headers?: Headers | Record<string, string> | undefined;
156
156
  integrity?: string | undefined;
@@ -0,0 +1,32 @@
1
+ import type { OperatorFunction } from 'rxjs';
2
+ import { type ServerSentEvent, type SseSelectorOptions } from '../selectors/sse-selector';
3
+ /**
4
+ * An operator function for handling Server-Sent Events (SSE) in an RxJS pipeline.
5
+ *
6
+ * @template R - The type of the parsed data from the SSE.
7
+ * @template T - The type of the input `Response` object, defaults to `Response`.
8
+ *
9
+ * @param options - Configuration options for the SSE selector.
10
+ *
11
+ * @returns An `OperatorFunction` that transforms a stream of `Response` objects
12
+ * into a stream of `ServerSentEvent` objects containing parsed data of type `R`.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * import { sseMap } from '@equinor/fusion-framework-module-http/operators';
17
+ * import { fromFetch } from 'rxjs/fetch';
18
+ *
19
+ * const response$ = fromFetch('https://example.com/sse', {
20
+ * method: 'GET',
21
+ * headers: {
22
+ * 'Accept': 'text/event-stream',
23
+ * },
24
+ * }).pipe(
25
+ * sseMap( { /* SSE selector options *\/ })
26
+ * ).subscribe((event) => {
27
+ * console.log(event.data); // Process the parsed SSE data
28
+ * });
29
+ * ```
30
+ */
31
+ export declare const sseMap: <R = unknown, T extends Response = Response>(options?: SseSelectorOptions<R>) => OperatorFunction<T, ServerSentEvent<R>>;
32
+ export default sseMap;
@@ -0,0 +1,63 @@
1
+ import type { ResponseSelector } from '../client/types.js';
2
+ /**
3
+ * A type representing a function that parses a string into a specific data type.
4
+ *
5
+ * @typeParam TData - The type of the parsed data. Defaults to `unknown` if not specified.
6
+ * @param data - The string input to be parsed.
7
+ * @returns The parsed data of type `TData`.
8
+ */
9
+ export type DataParser<TData = unknown> = (data: string) => TData;
10
+ /**
11
+ * Represents a Server-Sent Event (SSE) with optional fields.
12
+ */
13
+ export type ServerSentEvent<TData = unknown> = {
14
+ id?: string;
15
+ event?: string;
16
+ data?: TData;
17
+ retry?: string;
18
+ };
19
+ /**
20
+ * Options for configuring the SSE (Server-Sent Events) selector.
21
+ */
22
+ export type SseSelectorOptions<TData = unknown> = {
23
+ dataParser?: DataParser<TData>;
24
+ /**
25
+ * A string or an array of strings specifying the events to filter.
26
+ * Only events matching the filter will be processed. If not provided,
27
+ * all events will be processed.
28
+ */
29
+ eventFilter?: string | string[];
30
+ /**
31
+ * A boolean indicating whether to skip processing heartbeat events.
32
+ * Defaults to `false` if not specified.
33
+ */
34
+ skipHeartbeats?: boolean;
35
+ /**
36
+ * An `AbortSignal` that can be used to abort the SSE operation.
37
+ * Useful for managing the lifecycle of the SSE connection.
38
+ */
39
+ abortSignal?: AbortSignal | null;
40
+ };
41
+ /**
42
+ * A type alias for selecting and transforming Server-Sent Events (SSE) from an HTTP response.
43
+ *
44
+ * @template TData - The type of the data contained within the Server-Sent Event. Defaults to `unknown`.
45
+ * @template TResponse - The type of the HTTP response object. Defaults to `Response`.
46
+ *
47
+ * This selector is used to process and extract `ServerSentEvent<TData>` objects
48
+ * from an HTTP `Response` object, enabling custom handling of SSE data streams.
49
+ */
50
+ export type SseSelector<TData = unknown, TResponse extends Response = Response> = ResponseSelector<ServerSentEvent<TData>, TResponse>;
51
+ /**
52
+ * Transforms an SSE response into an Observable stream of parsed events.
53
+ * @param response - The HTTP response with Content-Type: text/event-stream.
54
+ * @param options - Optional configuration for event filtering and heartbeat handling.
55
+ * @param options.eventFilter - Filter events by type (single string or array).
56
+ * @param options.skipHeartbeats - Skip empty/heartbeat events if true.
57
+ * @param options.dataParser - Custom parser for the data field.
58
+ * @param options.abortSignal - Abort signal to cancel the stream.
59
+ * @returns An Observable emitting parsed ServerSentEvent objects.
60
+ * @throws ServerSentEventResponseError if response is invalid or stream fails.
61
+ */
62
+ export declare const createSseSelector: <TData = unknown, TResponse extends Response = Response>(options?: SseSelectorOptions<TData>) => SseSelector<TData, TResponse>;
63
+ export default createSseSelector;
@@ -1 +1 @@
1
- export declare const version = "6.2.5";
1
+ export declare const version = "6.3.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@equinor/fusion-framework-module-http",
3
- "version": "6.2.5",
3
+ "version": "6.3.0",
4
4
  "description": "",
5
5
  "main": "dist/esm/index.js",
6
6
  "types": "dist/types/index.d.ts",
@@ -40,8 +40,8 @@
40
40
  "dependencies": {
41
41
  "rxjs": "^7.8.1",
42
42
  "zod": "^3.23.8",
43
- "@equinor/fusion-framework-module": "^4.3.8",
44
- "@equinor/fusion-framework-module-msal": "^4.0.3"
43
+ "@equinor/fusion-framework-module": "^4.4.0",
44
+ "@equinor/fusion-framework-module-msal": "^4.0.4"
45
45
  },
46
46
  "devDependencies": {
47
47
  "typescript": "^5.8.2",
package/src/errors.ts CHANGED
@@ -40,3 +40,30 @@ export class HttpJsonResponseError<
40
40
  this.data = options?.data;
41
41
  }
42
42
  }
43
+
44
+ /**
45
+ * Represents an error that occurs when handling a server-sent event (SSE) HTTP response.
46
+ *
47
+ * @template TType - The type of additional data associated with the error.
48
+ * @template TResponse - The type of the HTTP response object.
49
+ *
50
+ * @extends HttpResponseError<TResponse>
51
+ */
52
+ export class ServerSentEventResponseError<
53
+ TType = unknown,
54
+ TResponse = Response,
55
+ > extends HttpResponseError<TResponse> {
56
+ static Name = 'ServerSentEventResponseError';
57
+
58
+ /**
59
+ * Creates a new instance of the error.
60
+ *
61
+ * @param message - The error message describing the cause of the error.
62
+ * @param response - The HTTP response associated with the error.
63
+ * @param options - Optional error options, which may include additional data of type `TType`.
64
+ */
65
+ constructor(message: string, response: TResponse, options?: ErrorOptions & { data?: TType }) {
66
+ super(message, response, options);
67
+ this.name = ServerSentEventResponseError.Name;
68
+ }
69
+ }
@@ -14,10 +14,17 @@ import type {
14
14
  FetchResponse,
15
15
  IHttpClient,
16
16
  JsonRequest,
17
+ ResponseSelector,
17
18
  StreamResponse,
18
19
  } from './types';
19
20
 
20
21
  import { HttpResponseError } from '../../errors';
22
+ import {
23
+ createSseSelector,
24
+ SseSelector,
25
+ type ServerSentEvent,
26
+ type SseSelectorOptions,
27
+ } from '../selectors/sse-selector';
21
28
 
22
29
  /**
23
30
  * Configuration options for creating an `HttpClient` instance.
@@ -222,6 +229,51 @@ export class HttpClient<
222
229
  return firstValueFrom(this.blob$(path, args));
223
230
  }
224
231
 
232
+ /**
233
+ * Initiates a Server-Sent Events (SSE) stream request to the specified path and returns a stream of events.
234
+ *
235
+ * @template T - The type of the event data expected from the SSE stream.
236
+ * @param path - The endpoint path to connect to for the SSE stream.
237
+ * @param args - Optional fetch request initialization options, including headers and abort signal.
238
+ * @param options - Optional selector options for customizing the SSE stream, excluding the abort signal.
239
+ *
240
+ * @returns A `StreamResponse` that emits `ServerSentEvent<T>` objects as they are received from the server.
241
+ *
242
+ * @example
243
+ * const sse$ = httpClient.sse(
244
+ * '/events',
245
+ * { method: 'POST', body: JSON.stringify({ prompt: 'tell me a joke' }) },
246
+ * { eventFilter: ['message'] }
247
+ * );
248
+ * sse$.subscribe({
249
+ * next: (event) => console.log(event),
250
+ * error: (err) => console.error(err),
251
+ * complete: () => console.log('Completed'),
252
+ * });
253
+ */
254
+ public sse$<T = unknown>(
255
+ path: string,
256
+ args?: FetchRequestInit<ServerSentEvent<T>, TRequest, TResponse> | null,
257
+ options?: Omit<SseSelectorOptions<T>, 'abortSignal'>,
258
+ ): StreamResponse<ServerSentEvent<T>> {
259
+ // Setup default common headers for SSE
260
+ const headers = new Headers(args?.headers);
261
+ headers.append('Accept', 'text/event-stream');
262
+ headers.append('Content-Type', 'text/event-stream');
263
+ headers.append('Cache-Control', 'no-cache');
264
+ headers.append('Connection', 'keep-alive');
265
+
266
+ // Create the selector using the provided options and the abort signal from args
267
+ const selector = createSseSelector<T>({ ...options, abortSignal: args?.signal });
268
+
269
+ // Call the fetch$ method with the provided path and the constructed init object
270
+ return this._fetch$(path, { selector, ...args, headers } as FetchRequestInit<
271
+ ServerSentEvent<T>,
272
+ TRequest,
273
+ TResponse
274
+ >);
275
+ }
276
+
225
277
  /** @deprecated */
226
278
  public jsonAsync<T = unknown>(
227
279
  path: string,
@@ -0,0 +1,44 @@
1
+ import type { OperatorFunction } from 'rxjs';
2
+ import { switchMap } from 'rxjs/operators';
3
+ import {
4
+ createSseSelector,
5
+ type ServerSentEvent,
6
+ type SseSelectorOptions,
7
+ } from '../selectors/sse-selector';
8
+
9
+ /**
10
+ * An operator function for handling Server-Sent Events (SSE) in an RxJS pipeline.
11
+ *
12
+ * @template R - The type of the parsed data from the SSE.
13
+ * @template T - The type of the input `Response` object, defaults to `Response`.
14
+ *
15
+ * @param options - Configuration options for the SSE selector.
16
+ *
17
+ * @returns An `OperatorFunction` that transforms a stream of `Response` objects
18
+ * into a stream of `ServerSentEvent` objects containing parsed data of type `R`.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * import { sseMap } from '@equinor/fusion-framework-module-http/operators';
23
+ * import { fromFetch } from 'rxjs/fetch';
24
+ *
25
+ * const response$ = fromFetch('https://example.com/sse', {
26
+ * method: 'GET',
27
+ * headers: {
28
+ * 'Accept': 'text/event-stream',
29
+ * },
30
+ * }).pipe(
31
+ * sseMap( { /* SSE selector options *\/ })
32
+ * ).subscribe((event) => {
33
+ * console.log(event.data); // Process the parsed SSE data
34
+ * });
35
+ * ```
36
+ */
37
+ export const sseMap =
38
+ <R = unknown, T extends Response = Response>(
39
+ options?: SseSelectorOptions<R>,
40
+ ): OperatorFunction<T, ServerSentEvent<R>> =>
41
+ (source) =>
42
+ source.pipe(switchMap(createSseSelector<R, T>(options)));
43
+
44
+ export default sseMap;
@@ -0,0 +1,262 @@
1
+ import { EMPTY, from, fromEvent, type Observable } from 'rxjs';
2
+ import { finalize, takeUntil } from 'rxjs/operators';
3
+
4
+ import type { ResponseSelector } from '../client/types.js';
5
+ import { ServerSentEventResponseError } from '../../errors.js';
6
+
7
+ /**
8
+ * A type representing a function that parses a string into a specific data type.
9
+ *
10
+ * @typeParam TData - The type of the parsed data. Defaults to `unknown` if not specified.
11
+ * @param data - The string input to be parsed.
12
+ * @returns The parsed data of type `TData`.
13
+ */
14
+ export type DataParser<TData = unknown> = (data: string) => TData;
15
+
16
+ const defaultDataParser: DataParser = (data: string) => {
17
+ try {
18
+ return JSON.parse(data);
19
+ } catch {
20
+ return data;
21
+ }
22
+ };
23
+
24
+ /**
25
+ * Represents a Server-Sent Event (SSE) with optional fields.
26
+ */
27
+ export type ServerSentEvent<TData = unknown> = {
28
+ id?: string;
29
+ event?: string;
30
+ data?: TData;
31
+ retry?: string;
32
+ };
33
+
34
+ /**
35
+ * Parses a string containing Server-Sent Events (SSE) data into individual event objects.
36
+ *
37
+ * @template TData - The type of the parsed `data` field in the event.
38
+ * @param text - The raw SSE data as a string, where events are separated by double newlines.
39
+ * @param options - Optional configuration for parsing the events.
40
+ * @param options.dataParser - A custom parser function for the `data` field. Defaults to JSON parsing,
41
+ * falling back to returning the raw string if parsing fails.
42
+ *
43
+ * @returns A generator that yields `ServerSentEvent` objects parsed from the input string.
44
+ *
45
+ * @remarks
46
+ * - Empty lines and events with no fields are ignored.
47
+ * - If the `data` field cannot be parsed as JSON, it is returned as a plain string.
48
+ * - Fields other than `data` are stored as strings in the resulting `ServerSentEvent` object.
49
+ *
50
+ * @example
51
+ * ```typescript
52
+ * const sseData = `
53
+ * id: 1
54
+ * event: message
55
+ * data: {"key":"value"}
56
+ *
57
+ * id: 2
58
+ * event: update
59
+ * data: plain text
60
+ * `;
61
+ *
62
+ * for (const event of parseEvents(sseData)) {
63
+ * console.log(event);
64
+ * }
65
+ * // Output:
66
+ * // { id: "1", event: "message", data: { key: "value" } }
67
+ * // { id: "2", event: "update", data: "plain text" }
68
+ * ```
69
+ */
70
+ function* parseEvents<TData = unknown>(
71
+ text: string,
72
+ options?: { dataParser?: DataParser<TData> },
73
+ ): Generator<ServerSentEvent<TData>> {
74
+ // Split the input string into individual event strings using double newline as separator
75
+ const eventStrings = text.split('\n\n');
76
+
77
+ const dataParser = options?.dataParser || (defaultDataParser as DataParser<TData>);
78
+
79
+ // Iterate through each event string
80
+ for (const eventStr of eventStrings) {
81
+ // Skip empty event strings (after trimming whitespace)
82
+ if (!eventStr.trim()) continue;
83
+
84
+ // Split the event string into lines (fields) using single newline
85
+ const lines = eventStr.split('\n');
86
+
87
+ // Use reduce to process each line in the event string
88
+ const event = lines.reduce(
89
+ (event, line) => {
90
+ // Skip empty lines
91
+ if (!line) return event;
92
+
93
+ // Find the index of the first colon, which separates field name and value
94
+ const colonIndex = line.indexOf(':');
95
+
96
+ // Skip lines without a colon (invalid format)
97
+ if (colonIndex === -1) return event;
98
+
99
+ // Extract the field name (before colon) and trim whitespace
100
+ const field = line.slice(0, colonIndex).trim();
101
+ // Extract the field value (after colon) and trim whitespace
102
+ const value = line.slice(colonIndex + 1).trim();
103
+
104
+ // Handle the 'data' field specially, attempting JSON parsing
105
+ if (field === 'data') {
106
+ event.data = dataParser(value);
107
+ } else {
108
+ // For non-data fields, assign the value as a string to the event object
109
+ (event as Record<string, unknown>)[field] = value;
110
+ }
111
+
112
+ return event;
113
+ },
114
+ {} as ServerSentEvent<TData>,
115
+ );
116
+
117
+ // Only emit the event to the results if it has at least one field
118
+ if (Object.keys(event).length) {
119
+ yield event;
120
+ }
121
+ }
122
+ }
123
+
124
+ async function* readStream<TData>(
125
+ reader: ReadableStreamDefaultReader<Uint8Array>,
126
+ options?: {
127
+ dataParser?: DataParser<TData>;
128
+ skipHeartbeats?: boolean;
129
+ eventFilter?: string | string[];
130
+ },
131
+ ): AsyncGenerator<ServerSentEvent<TData>> {
132
+ const skipHeartbeats = !!options?.skipHeartbeats;
133
+
134
+ const eventFilter = options?.eventFilter
135
+ ? Array.isArray(options.eventFilter)
136
+ ? options.eventFilter
137
+ : [options.eventFilter]
138
+ : null;
139
+
140
+ const decoder = new TextDecoder();
141
+
142
+ while (true) {
143
+ const { done, value } = await reader.read();
144
+ if (done) {
145
+ break;
146
+ }
147
+
148
+ const text = decoder.decode(value, { stream: true });
149
+ const events = parseEvents<TData>(text, { dataParser: options?.dataParser });
150
+ for (const event of events) {
151
+ if (event.retry) {
152
+ await new Promise((resolve) =>
153
+ setTimeout(resolve, Number.parseInt(event.retry ?? '300', 10)),
154
+ );
155
+ continue;
156
+ }
157
+ if (skipHeartbeats) {
158
+ // Skip comment-based heartbeats (no event, data, or id)
159
+ if (!event.event && !event.data && !event.id) {
160
+ continue;
161
+ }
162
+ // Skip named heartbeat events (e.g., event: heartbeat or event: ping)
163
+ if (event.event && ['heartbeat', 'ping'].includes(event.event)) {
164
+ continue;
165
+ }
166
+ }
167
+ if (!eventFilter || (event.event && eventFilter.includes(event.event))) {
168
+ yield event as ServerSentEvent<TData>;
169
+ }
170
+ }
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Options for configuring the SSE (Server-Sent Events) selector.
176
+ */
177
+ export type SseSelectorOptions<TData = unknown> = {
178
+ dataParser?: DataParser<TData>;
179
+ /**
180
+ * A string or an array of strings specifying the events to filter.
181
+ * Only events matching the filter will be processed. If not provided,
182
+ * all events will be processed.
183
+ */
184
+ eventFilter?: string | string[];
185
+
186
+ /**
187
+ * A boolean indicating whether to skip processing heartbeat events.
188
+ * Defaults to `false` if not specified.
189
+ */
190
+ skipHeartbeats?: boolean;
191
+
192
+ /**
193
+ * An `AbortSignal` that can be used to abort the SSE operation.
194
+ * Useful for managing the lifecycle of the SSE connection.
195
+ */
196
+ abortSignal?: AbortSignal | null;
197
+ };
198
+
199
+ /**
200
+ * A type alias for selecting and transforming Server-Sent Events (SSE) from an HTTP response.
201
+ *
202
+ * @template TData - The type of the data contained within the Server-Sent Event. Defaults to `unknown`.
203
+ * @template TResponse - The type of the HTTP response object. Defaults to `Response`.
204
+ *
205
+ * This selector is used to process and extract `ServerSentEvent<TData>` objects
206
+ * from an HTTP `Response` object, enabling custom handling of SSE data streams.
207
+ */
208
+ export type SseSelector<TData = unknown, TResponse extends Response = Response> = ResponseSelector<
209
+ ServerSentEvent<TData>,
210
+ TResponse
211
+ >;
212
+
213
+ /**
214
+ * Transforms an SSE response into an Observable stream of parsed events.
215
+ * @param response - The HTTP response with Content-Type: text/event-stream.
216
+ * @param options - Optional configuration for event filtering and heartbeat handling.
217
+ * @param options.eventFilter - Filter events by type (single string or array).
218
+ * @param options.skipHeartbeats - Skip empty/heartbeat events if true.
219
+ * @param options.dataParser - Custom parser for the data field.
220
+ * @param options.abortSignal - Abort signal to cancel the stream.
221
+ * @returns An Observable emitting parsed ServerSentEvent objects.
222
+ * @throws ServerSentEventResponseError if response is invalid or stream fails.
223
+ */
224
+ export const createSseSelector = <TData = unknown, TResponse extends Response = Response>(
225
+ options?: SseSelectorOptions<TData>,
226
+ ): SseSelector<TData, TResponse> => {
227
+ return (response: TResponse): Observable<ServerSentEvent<TData>> => {
228
+ if (!response.ok) {
229
+ throw new ServerSentEventResponseError(`HTTP error! Status: ${response.status}`, response);
230
+ }
231
+
232
+ if (!response.body) {
233
+ throw new ServerSentEventResponseError('Response body is not readable', response);
234
+ }
235
+
236
+ if (!response.headers.get('Content-Type')?.includes('text/event-stream')) {
237
+ throw new ServerSentEventResponseError('Response is not a text/event-stream', response);
238
+ }
239
+
240
+ const reader = response.body.getReader();
241
+
242
+ return from(
243
+ readStream<TData>(reader, {
244
+ dataParser: options?.dataParser,
245
+ skipHeartbeats: options?.skipHeartbeats,
246
+ eventFilter: options?.eventFilter,
247
+ }),
248
+ ).pipe(
249
+ // Stop reading if the abort signal is triggered
250
+ takeUntil(options?.abortSignal ? fromEvent(options.abortSignal, 'abort') : EMPTY),
251
+ finalize(async () => {
252
+ // cancel just in case of a pre-mature exit
253
+ await reader.cancel().catch(() => {
254
+ /** ignore cancellation errors */
255
+ });
256
+ reader.releaseLock();
257
+ }),
258
+ );
259
+ };
260
+ };
261
+
262
+ export default createSseSelector;
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '6.2.5';
2
+ export const version = '6.3.0';