@equinor/fusion-framework-module-http 7.0.8 → 8.0.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 (41) hide show
  1. package/CHANGELOG.md +34 -27
  2. package/README.md +141 -925
  3. package/dist/esm/configurator.js +5 -5
  4. package/dist/esm/configurator.js.map +1 -1
  5. package/dist/esm/lib/client/client-msal.js +3 -2
  6. package/dist/esm/lib/client/client-msal.js.map +1 -1
  7. package/dist/esm/lib/client/client.js +8 -7
  8. package/dist/esm/lib/client/client.js.map +1 -1
  9. package/dist/esm/lib/operators/index.js +1 -0
  10. package/dist/esm/lib/operators/index.js.map +1 -1
  11. package/dist/esm/lib/selectors/index.js +1 -0
  12. package/dist/esm/lib/selectors/index.js.map +1 -1
  13. package/dist/esm/module.js +18 -9
  14. package/dist/esm/module.js.map +1 -1
  15. package/dist/esm/provider.js +38 -27
  16. package/dist/esm/provider.js.map +1 -1
  17. package/dist/esm/version.js +1 -1
  18. package/dist/tsconfig.tsbuildinfo +1 -1
  19. package/dist/types/configurator.d.ts +49 -26
  20. package/dist/types/lib/client/client.d.ts +7 -7
  21. package/dist/types/lib/operators/index.d.ts +1 -0
  22. package/dist/types/lib/selectors/index.d.ts +2 -0
  23. package/dist/types/module.d.ts +18 -9
  24. package/dist/types/provider.d.ts +24 -19
  25. package/dist/types/version.d.ts +1 -1
  26. package/docs/client-configuration.md +175 -0
  27. package/docs/observable-patterns.md +103 -0
  28. package/docs/selectors-and-handlers.md +112 -0
  29. package/docs/server-sent-events.md +125 -0
  30. package/package.json +6 -6
  31. package/src/configurator.ts +54 -26
  32. package/src/lib/client/client-msal.ts +3 -2
  33. package/src/lib/client/client.ts +8 -9
  34. package/src/lib/operators/index.ts +1 -0
  35. package/src/lib/selectors/index.ts +7 -0
  36. package/src/module.ts +18 -9
  37. package/src/provider.ts +56 -34
  38. package/src/version.ts +1 -1
  39. package/tests/HttpClient.test.ts +90 -11
  40. package/tests/operators.test.ts +24 -0
  41. package/tests/sse.selector.test.ts +6 -7
package/README.md CHANGED
@@ -1,994 +1,210 @@
1
- # Fusion Framework Http Module
2
-
3
- > __The Fusion Framework HTTP Module provides a streamlined and powerful HTTP client for making requests to APIs.__
4
- >
5
- > The module supports both asynchronous (Promise-based) and observable (RxJS-based) execution methods, allowing you to choose the approach that best suits your use case.
6
- >
7
- > It also offers advanced features, such as **MSAL** (Microsoft Authentication Library) integration, request and response operators, and response selectors, to enhance the functionality and flexibility of HTTP communication.
8
-
9
- Whether you're building a simple application or a complex portal, the Fusion Framework HTTP Module equips you with the essential tools to handle HTTP requests efficiently and effectively. With its rich feature set and intuitive API, the module simplifies the process of working with HTTP clients, enabling you to focus on building robust and scalable applications.
10
-
11
- **Key Features:**
12
- - Streamlined API for easy configuration and operation of HTTP clients
13
- - Integrated MSAL (Microsoft Authentication Library) support for robust authentication
14
- - Unified management system for HTTP client configurations
15
- - Factory method for client creation, ensuring optimal state management
16
- - Capabilities for intercepting and modifying requests and responses
17
- - Familiar syntax inspired by the [Web's Fetch API](https://developer.mozilla.org/en-US/docs/Web/HTTP)
18
-
19
- ## Package
20
-
21
- | Namespace | Description |
22
- | ------------------------------------------------- | --------------------- |
23
- | `@equinor/fusion-framework-module-http` | http module |
24
- | `@equinor/fusion-framework-module-http/client` | http clients |
25
- | `@equinor/fusion-framework-module-http/selectors` | http selectors |
26
- | `@equinor/fusion-framework-module-http/operators` | http client operators |
27
- | `@equinor/fusion-framework-module-http/errors` | http client errors |
28
-
29
- ## Usage
30
- Working with Fusion Framework, HTTP clients are defined during the configuration phase. The configuration is called by the Framework during initialization, ensuring that the HTTP clients are ready to be used. This approach allows for centralized and optimized client configuration, promoting consistency, performance, and security in HTTP communication within the Fusion Framework ecosystem.
31
-
32
- see [Configuring HTTP Clients](#configuring-http-clients) for more information.
33
-
34
- ```ts
35
- /**
36
- * Example: Configuring an HTTP client with MSAL authentication in Fusion Framework
37
- * This configuration is performed during the initialization phase of the app.
38
- */
39
- const configure = (configurator: IModulesConfigurator) => {
40
- /**
41
- * Configure an HTTP client named 'msalClient'
42
- * This client is set up to communicate with 'https://api.example.com'
43
- * and utilizes MSAL for authentication, specifying a default scope.
44
- */
45
- configurator.http.configureClient('msalClient', {
46
- baseUri: 'https://api.example.com',
47
- defaultScopes: ['api://example-api/.default'], // MSAL scopes
48
- });
49
-
50
- // Additional configuration can be added here
51
- }
52
-
53
- // Note: This is a basic setup. For more advanced configurations, please refer to the documentation.
54
- ```
55
-
56
- When the application is initialized, the configuration is called, and the HTTP client becomes ready to be used.
57
-
58
- see [Working with HTTP clients](#working-with-http-clients) for more information.
59
-
60
- ```ts
61
- /**
62
- * Example: Using an HTTP client in Fusion Framework
63
- * This code is executed after the app configuration has been initialized.
64
- */
65
- const client = modules.http.createClient('myClient');
66
-
67
- /** Make a request using the client */
68
- client.json('/some-endpoint')
69
- .then(data => console.log(data))
70
- .catch(error => console.error('Error:', error));
71
- ```
1
+ # Fusion Framework HTTP Module
72
2
 
73
- ## Concepts
3
+ `@equinor/fusion-framework-module-http` is the HTTP layer for Fusion Framework applications and modules. It gives you a fetch-style client factory with framework-aware configuration, MSAL-aware requests, RxJS observables, and reusable request/response pipelines.
74
4
 
75
- ### Pure fetch
5
+ If you only remember one thing about this package, make it this: you configure clients once, and you create fresh client instances when you use them.
76
6
 
77
- The HTTP module extends the native `fetch` API, providing a streamlined experience for making HTTP requests. It adds features and capabilities to simplify common tasks and ensure compatibility with existing code and libraries.
7
+ If you are new to Fusion Framework or just want to call an API, start with `configureHttpClient(...)`, `createClient(...)`, and `json()`.
78
8
 
79
- ### Observable
9
+ If you are building more advanced workflows, this package also supports RxJS observable composition, request and response stream inspection, custom selectors, request and response operators, MSAL-aware requests, and server-sent events.
80
10
 
81
- The HTTP module uses RxJS observables for handling HTTP requests and responses. Observables provide a flexible and powerful way to work with asynchronous data streams. They allow you to process and transform data in a declarative and composable manner. Unsubscribing from an observable automatically aborts the corresponding HTTP request, preventing unnecessary network traffic and ensuring efficient request management.
11
+ ## What This Package Gives You
82
12
 
83
- ### Defining Clients Upfront
13
+ - Named HTTP client configurations for each backend you talk to.
14
+ - Fresh client instances so headers and handlers from one usage do not leak into the next.
15
+ - Promise and Observable APIs on top of the same request pipeline.
16
+ - Built-in support for JSON, blobs, and server-sent events.
17
+ - Request and response handlers for cross-cutting behavior.
18
+ - MSAL scope support when the auth module is available.
84
19
 
85
- In the Fusion Framework, HTTP clients are configured during initialization to ensure they are ready for use. This promotes centralized configuration, optimized performance, and enhances consistency, security, and modularity in HTTP communication.
20
+ ## Mental Model
86
21
 
87
- ### Client factory
88
- The HTTP module provides a client factory method for creating HTTP clients. This method allows you to specify the client's name and retrieve a configured instance. Using the client factory ensures clean and predictable client instances without any mutations from previous requests. This promotes a reliable state for each client, avoiding unexpected behavior caused by shared state.
22
+ Think of the HTTP module as a client factory, not a singleton HTTP client.
89
23
 
90
- ### Process operators
24
+ 1. Configure a named client during app or module setup.
25
+ 2. Call `createClient(name)` when you need to make requests.
26
+ 3. Use `fetch`, `json`, `blob`, or `sse$` on that client instance.
27
+ 4. Add shared behavior in `onCreate`, `requestHandler`, and `responseHandler`.
91
28
 
92
- Process operators are functions that can be added to the HTTP client to customize its behavior and add additional functionality to the request/response lifecycle. They allow you to intercept and modify requests and responses, add custom headers, log data, validate responses, and more. By using process operators, you can extend the functionality of the HTTP client to meet your application's specific requirements.
29
+ Each request goes through the same high-level flow:
93
30
 
94
- ### Request execution
31
+ 1. Resolve `baseUri` and request path into a full URL.
32
+ 2. Run request handlers.
33
+ 3. Emit the request on `request$`.
34
+ 4. Execute the fetch call.
35
+ 5. Run response handlers.
36
+ 6. Emit the response on `response$`.
37
+ 7. Apply an optional selector.
95
38
 
96
- When executing HTTP requests _(1)_, the internal fetch method will create an `Observable` _(2)_ from the provided the request options. The stream will then process the request handlers _(3 - 5)_ before emitting the request to the HttpClient request `Subject` _(6)_.
39
+ ## Start Here If You Are New
97
40
 
98
- Then a native `fetch` call is made to the HTTP service _(7)_. If the request is successful _(17)_, the response is processed by the response handlers _(18 - 20)_ before being emitted to the HttpClient response `Subject` _(21)_. The response is then processed by the response selector _(22)_ before being emitted to the user (_23_).
41
+ The simplest useful path looks like this:
99
42
 
100
- In case of an error _(17)_, the error is emitted to the user _(18)_.
43
+ 1. Register a named client for one backend.
44
+ 2. Create a client from that name where you need it.
45
+ 3. Call `json()` for normal API calls.
101
46
 
102
- If the user calls abort on the client _(8)_, or triggers provided `AbortSignal` in call args, the observable will abort the request _(10)_. `fetch` will throw an abort error _(11)_, which is emitted to the user _(12)_.
47
+ That gives you:
103
48
 
104
- If the user unsubscribes from the observable _(13)_ or trigger provided `AbortSignal` in call args, the request is aborted _(14)_.
49
+ - consistent base URLs
50
+ - shared auth scope configuration
51
+ - shared headers or guards through `onCreate`
52
+ - one fresh client instance per usage
105
53
 
54
+ If you are unsure whether to use promises or observables, start with `json()`. Move to `json$()` when you need RxJS operators, cancellation by unsubscribe, or stream composition.
106
55
 
107
- > [!NOTE]
108
- > - When an error is emitted, the observable will complete.
109
- > - Observables only emit errors if they are subscribed to.
110
- > - fetch observables are cold, meaning they will close when request is completed or aborted.
56
+ ## When To Use Which API
111
57
 
112
- > [!IMPORTANT]
113
- > HttpClient stream functions will not execute until subscribed to.
58
+ | Need | Use |
59
+ | --- | --- |
60
+ | Call a normal JSON API | `json()` or `json$()` |
61
+ | Work with the raw `Response` | `fetch()` or `fetch$()` |
62
+ | Download a file or blob response | `blob()` or `blob$()` |
63
+ | Consume a streaming `text/event-stream` endpoint | `sse$()` |
64
+ | Apply standard headers, scopes, or logging per backend | named client configuration + `onCreate` |
114
65
 
115
- ```mermaid
116
- sequenceDiagram
117
- autonumber
118
- actor User
119
- participant Client
66
+ ## Install
120
67
 
121
- User->>Client: calls fetch method
122
- create participant Observable
123
- Client->>+Observable: create observable
124
- Observable->>+Client: prepare request
125
- loop
126
- Client->>Client: process request handlers
127
- end
128
- Client-->>-Observable: request object
129
- Observable-->>Client: emit request
130
- create participant HTTPService as Http Service
131
- Observable->>+HTTPService: fetch
132
- break when aborted
133
- User->>Client: abort requests
134
- Client->>Observable: close observable
135
- Observable->>HTTPService: abort request
136
- HTTPService-->>Observable: Abort error
137
- Observable--xUser: emit error
138
- end
139
- break when unsubscribe
140
- User--xObservable: unsubscribe
141
- Observable->>HTTPService: abort request
142
- end
143
- alt is request error
144
- HTTPService-->>Observable: error
145
- Observable--xUser: emit error
146
- end
147
- HTTPService-->>-Observable: response
148
- Observable-->>+Client: prepare response
149
- loop
150
- Client->>Client: process response handlers
151
- end
152
- Client-->>-Observable: response object
153
- Observable-->>Client: emit response
154
- opt
155
- Observable->>Observable: apply response selector
156
- end
157
- Observable--x-User: emit result
68
+ ```bash
69
+ pnpm add @equinor/fusion-framework-module-http
158
70
  ```
159
71
 
160
- ## Configuring HTTP Clients
161
-
162
- Configuring HTTP clients before the application renders is crucial for several reasons:
163
-
164
- 1. **Centralized Configuration**: By configuring clients upfront, we establish a single source of truth for all HTTP client settings. This centralization simplifies the management and maintenance of client configurations throughout the application.
165
-
166
- 2. **Performance Optimization**: Pre-configuring clients improves performance by ensuring they are ready to use when needed, eliminating on-the-fly configuration that can slow down API calls.
167
-
168
- 3. **Consistency**: Configuring clients before rendering ensures consistent usage of client configurations across the application, promoting uniformity in API call implementation.
169
-
170
- 4. **Environment-specific Settings**: This approach facilitates easy configuration of environment-specific settings, such as different base URLs for development, staging, and production environments.
171
-
172
- 5. **Security**: Configuring clients before rendering enables the setup of essential security measures, including authentication handlers and default scopes, ensuring secure API calls from the outset.
173
-
174
- 6. **Modularity**: This configuration approach supports a modular architecture, allowing different modules to define their own HTTP client configurations, which can then be combined before rendering the application.
175
-
176
- By following this approach, you can enhance the efficiency, consistency, security, and modularity of your application's HTTP communication within the Fusion Framework ecosystem.
177
-
178
- ### Configuration Options
179
-
180
- When configuring an HTTP client in the Fusion Framework, you can specify various settings to customize its behavior. The following options are available for configuring an HTTP client:
181
-
182
- | Property | Description |
183
- | ----------------- | ---------------------------------------------------------- |
184
- | `baseUri` | The base URI for the API endpoint. |
185
- | `defaultScopes` | The default scopes for MSAL authentication. |
186
- | `selector` | The response selector for processing the response. |
187
- | `onCreate` | A callback function to execute when the client is created. |
188
- | `requestHandler` | The request operators for processing outgoing requests. |
189
- | `responseHandler` | The response operators for processing incoming responses. |
190
- | `ctor` | The constructor function for a custom client. |
191
-
192
-
193
- ### Basic configuration
72
+ ## Quick Start
194
73
 
195
- To configure a basic HTTP client in the Fusion Framework, you can use the following example. This setup is ideal for simple API consumption that does not require advanced authentication mechanisms.
74
+ Configure a named client during setup:
196
75
 
197
76
  ```typescript
198
- /** Example of basic configuration for an HTTP client in the Fusion Framework */
199
- const configure = (configurator: IModulesConfigurator) => {
200
- /**
201
- * Configure an HTTP client named 'myClient'.
202
- * This client is set up to communicate with 'https://api.example.com'.
203
- */
204
- configurator.http.configureClient(
205
- 'myClient',
206
- 'https://api.example.com'
207
- );
208
-
209
- // Additional basic configuration can be added here
210
- }
211
- ```
212
-
213
- ### Configuration with MSAL Authentication
214
-
215
- For consuming APIs that require secure authentication, it is recommended to configure an HTTP client with MSAL (Microsoft Authentication Library). The following example demonstrates how to set up an HTTP client with MSAL authentication, specifying a default scope.
216
-
217
- ```ts
218
- /**
219
- * Example: Configuring an HTTP client with MSAL authentication in Fusion Framework
220
- * This configuration is performed during the initialization phase of the app.
221
- */
222
- const configure = (configurator: IModulesConfigurator) => {
223
- /**
224
- * Configure an HTTP client named 'msalClient'
225
- * This client is set up to communicate with 'https://api.example.com'
226
- * and utilizes MSAL for authentication, specifying a default scope.
227
- */
228
- configurator.http.configureClient('msalClient', {
229
- baseUri: 'https://api.example.com',
230
- defaultScopes: ['api://example-api/.default'],
231
- });
232
-
233
- // Additional configuration can be added here
234
- }
235
- ```
236
-
237
- ### Advanced Configuration
238
-
239
- #### Configuring a http client with callback
240
-
241
- You can configure an HTTP client using a callback function, which allows for more dynamic and flexible configuration. Here's an example:
242
-
243
- ```ts
244
- /**
245
- * Example of configuring an HTTP client with a callback in Fusion Framework.
246
- * This configuration is performed during the initialization phase of the app.
247
- * The callback function provides more flexibility in setting up the client.
248
- */
249
- const configure = (configurator: IModulesConfigurator) => {
250
- /** Configure an HTTP client named 'callbackClient' using a callback function. */
251
- configurator.http.configureClient('callbackClient', (client) => {
252
- /** Set the base URI for API requests */
253
- client.uri = 'https://api.example.com';
254
-
255
- /** Add a custom request handler */
256
- client.requestHandler.add('logger', (request) => {
257
- console.log('Outgoing request:', request);
258
- // Perform any asynchronous operations if needed before the request is sent
259
- });
260
-
261
- /** Add a custom response handler */
262
- client.responseHandler.add('errorChecker', (response) => {
263
- if (!response.ok) {
264
- console.error('Error in response:', response.status, response.statusText);
265
- switch(response.status) {
266
- case 401:
267
- // Handle unauthorized error
268
- break;
269
- case 500:
270
- // Handle server error
271
- break;
272
- default:
273
- // Handle other errors
274
- break;
275
- }
276
- }
277
- });
278
-
279
- /** Set default scopes for authentication. These scopes are required for accessing the specified API. */
280
- client.defaultScopes = ['api://example-api/.default'];
281
-
282
- // Additional configuration can be added here
283
- });
284
- }
285
- ```
286
-
287
-
288
- #### Configuration with custom client
289
-
290
- You can also configure an HTTP client with a custom client instance. This approach allows you to define a custom client with specific settings and behaviors tailored to your application's requirements.
291
-
292
- ```ts
293
- /** example of configuring an HTTP client with a custom client instance */
294
- class CustomHttpClient implements IHttpClient {
295
- /** custom implementation */
296
- }
297
-
298
- const configure = (configurator: IModulesConfigurator) => {
299
- /** Configure an HTTP client named 'customClient' with a custom client instance. */
300
- configurator.http.configureClient('custom', {
301
- ctor: CustomHttpClient,
302
- baseUri: 'https://api.example.com',
303
- });
304
- }
305
- ```
306
-
307
-
308
- ## Working with HTTP clients
309
-
310
- To create an HTTP client, you can use the `createClient` method provided by the module. This method takes the name of the client you want to create and returns an instance of the HTTP client configured with the specified settings:
311
-
312
- ```ts
313
- /**
314
- * Example: Creating an HTTP client in Fusion Framework
315
- * Assume a client named 'msalClient' has been configured
316
- * Assume 'modules' is included in the scope
317
- */
318
- const msalClient = modules.http.createClient('msalClient');
319
- ```
320
-
321
- Once you have created a client, you can use it to make requests to APIs. The client provides several methods for executing HTTP requests, such as `fetch`, `json`, and `blob`, which return Promises, and `fetch$`, `json$`, and `blob$`, which return observables.
322
-
323
- ### Async vs Observable Execution
324
-
325
- The Fusion Framework HTTP module offers developers the flexibility to choose between asynchronous (Promise-based) and observable (RxJS-based) methods for executing HTTP requests. This allows developers to select the approach that best suits their specific use case.
326
-
327
- It's worth noting that both approaches utilize the same underlying RxJS-based implementation, ensuring consistent behavior regardless of the chosen execution method.
328
-
329
- > [!TIP]
330
- > While async methods are more familiar to many developers, observable methods offer additional flexibility and power when dealing with complex data flows or when fine-grained control over the request lifecycle is needed.
331
-
332
- #### Async Execution
333
-
334
- For simpler use cases, the module also exposes async methods that return Promises:
335
-
336
- ```ts
337
- /** Example of using async execution with the HTTP client */
338
- msalClient.json<MyDataType>('/api/data')
339
- .then(data => console.log('Received data:', data))
340
- .catch(error => console.error('An error occurred:', error))
341
- .finally(() => console.log('Async operation completed'));
342
- ```
343
-
344
- Async execution is beneficial for:
345
- - Simpler, more familiar syntax for many developers
346
- - Easy integration with async/await patterns
347
- - Straightforward error handling with try/catch
348
-
349
- #### Observable Execution
350
-
351
- The module uses RxJS observables at its core, which provides powerful stream processing capabilities. Observable methods are denoted with a `$` suffix:
352
-
353
- ```ts
354
- /** Example of using observable execution with the HTTP client */
355
- msalClient.json$<MyDataType>('/api/data').subscribe({
356
- next: (data) => console.log('Received data:', data),
357
- error: (error) => console.error('An error occurred:', error),
358
- complete: () => console.log('Observable completed'),
359
- });
360
- ```
361
-
362
- Observable execution is particularly useful for:
363
- - Handling real-time data streams
364
- - Implementing complex data transformations
365
- - Cancelling ongoing requests
366
-
367
- ### Working with MSAL
368
-
369
- When working with MSAL authentication, you can specify the required scopes for each request. By default, the client will use the `defaultScopes` configured for the HTTP client. However, you can also overload the scopes for individual API calls by providing an array of scopes in the request options.
370
-
371
- > [!IMPORTANT]
372
- > By default the module will add a request operator which will acquire a token from MSAL before the request is sent. This token is added as a bearer token in the request header.
373
-
374
- > [!WARNING]
375
- > The scopes provided in the request options will override the default scopes configured for the client.
376
-
377
- ```ts
378
- /**
379
- * Example: Making a request with custom MSAL scopes
380
- * This request will be authenticated using the specified scopes.
381
- */
382
- msalClient.json('/some-endpoint', {
383
- scopes: ['api://example-api/.admin']
384
- })
385
- ```
386
-
387
- ### Working with JSON
388
-
389
- To interact with JSON APIs, you can use the `json` or `json$` methods.
390
-
391
- - The `json$` method returns an Observable that emits the parsed JSON data.
392
- - The `json` method returns a promise that resolves when the first value from `json$` is emitted.
393
- - Both methods automatically parse the response as JSON.
394
- - They also add standard headers to the request.
395
- - The response is returned as a `JsonResponse<T>`.
396
-
397
- > [!TIP]
398
- > Use [typed responses](#use-typed-responses) to ensure that you're using the correct types.
399
-
400
- > [!IMPORTANT]
401
- > - **Request Body**: Will only use `JSON.stringify`, pre-process data if needed.
402
- > - **Response Body**: Will only use `response.json()`, post-process data if needed.
403
-
404
- ```ts
405
- /** Example of using the json method to patch data */
406
- const data = {
407
- id: 1,
408
- name: 'John Doe',
409
- }
410
- /** executing a async request */
411
- client.json('/some-endpoint', { method: "PATCH", body: data });
412
- /** executing an observable request */
413
- client.json$('/some-endpoint', { method: "PATCH", body: data })
414
- ```
415
-
416
- ### Handling Errors
417
-
418
- When working with async functions and observables, it's important to handle errors properly! The HTTP client provides two custom error types for handling HTTP-related errors:
419
-
420
- **`HttpResponseError`**: This error is thrown when there's an issue with the HTTP response. It includes the original response object, allowing you to access additional details about the error.
421
-
422
- **`HttpJsonResponseError`**: This error is used when there's an issue parsing JSON data from the response. It includes the parsed data (if available) in addition to the response object.
423
-
424
- When using the HTTP client, you can catch these errors and handle them appropriately:
425
-
426
- ```ts
427
- /** Example of handling errors when using the HTTP client */
428
- const processError = (error: unknown) => {
429
- /** Check the type of error and handle it accordingly */
430
- if (error instanceof HttpJsonResponseError) {
431
- /** This error will be thrown if there's an issue parsing JSON data */
432
- console.error('JSON Response Error:', error.message, error.data);
433
- } else if (error instanceof HttpResponseError) {
434
- /** This error will be thrown if there's an issue with the HTTP response */
435
- console.error('HTTP Response Error:', error.message, error.response);
436
- } else {
437
- /** Handle other types of errors */
438
- console.error('Unknown error:', error);
439
- }
440
- }
441
- // async
442
- try { const data = await client.json('/some-endpoint'); }
443
- catch (error) { processError(error) }
444
- finally { /** handle complete */ }
445
-
446
- // observable
447
- client.json('/some-endpoint').subscribe({
448
- next: (response) => { /** Handle response */ },
449
- error: processError,
450
- complete: () => { /** Handle complete */ }
77
+ configurator.configureHttpClient('catalog', {
78
+ baseUri: '/api/catalog',
79
+ defaultScopes: ['api://catalog-api/.default'],
80
+ onCreate: (client) => {
81
+ client.requestHandler.setHeader('X-App-Name', 'portal');
82
+ },
451
83
  });
452
84
  ```
453
85
 
454
- > [!IMPORTANT]
455
- > Example only logs the error, your application should handle errors appropriately based on the use case.
456
-
457
- ### Use typed responses
458
-
459
- When coding in TypeScript and using the HTTP client, it's important to use typed responses. This helps ensure that you handle errors correctly and that you're using the correct types.
460
-
461
- ```ts
462
- /** Example of using typed responses with the HTTP client */
463
- interface User {
464
- id: number;
465
- name: string;
466
- }
467
-
468
- /**
469
- * Fetch a user by ID.
470
- * @param id - The ID of the user to fetch.
471
- * @returns A promise that resolves to the user data.
472
- */
473
- const getUser = (id: number): Promise<User> => client.json<User>(`/users/${id}`);
474
- ```
475
-
476
- ### Using Response Selectors
477
-
478
- A good practice when working with data returned from an API is to use reusable selectors. This helps ensure that you don't have to write the same code over and over again.
479
-
480
- Selectors are functions that process and transform the raw HTTP response before it's returned to your application. The HTTP client provides built-in selectors for common use cases, such as JSON parsing and blob handling.
481
-
482
- > [!TIP]
483
- > When providing selectors, the response type is inferred based on the selector's return type.
484
-
485
- Here is an example of a selector that checks if a resource exists and a selector that parses a CSV file:
486
- ```ts
487
- /**
488
- * Response selector for processing HTTP responses, like HEAD requests.
489
- * This selector checks if a resource exists based on the response status.
490
- * @param response - The HTTP response to process.
491
- * @returns A boolean indicating whether the resource exists.
492
- */
493
- export const resourceExistsSelector = (response: Response): boolean => {
494
- /** Check if the response status is OK (200-299) */
495
- if(response.ok) {
496
- return true;
497
- /** Check if the response status is Not Found (404) */
498
- } else if(response.status === 404) {
499
- return false;
500
- }
501
- throw Error(`Unexpected response status: ${response.status}`);
502
- }
503
-
504
- /** definition of the CSV data type */
505
- export type CsvData = string[][];
506
-
507
- /**
508
- * Response selector for processing CSV data.
509
- * This selector parses the response text as CSV data.
510
- * @param response - The HTTP response to process.
511
- * @returns The parsed CSV data as a 2D array of strings.
512
- */
513
- export const csvSelector: ResponseSelector = async (response: Response): Promise<CsvData> => {
514
- const text = await response.text();
515
- return text.split('\n').map(line => line.split(','));
516
- };
517
- ```
518
-
519
- Here is an example of how to use the selectors in your application:
520
-
521
- ```ts
522
- import { resourceExistsSelector, csvSelector, type CsvData } from './selectors';
523
-
524
- /**
525
- * Check if a resource exists based on its ID.
526
- * @param client - The HTTP client to use for the request.
527
- * @param itemId - The ID of the resource to check.
528
- * @returns A promise that resolves to a boolean indicating whether the resource exists.
529
- */
530
- export const hasResource = (client: HttpClient, itemId: string): Promise<boolean> => {
531
- return client.fetch(`/resource/${itemId}`, {
532
- method: 'HEAD',
533
- selector: resourceExistsSelector
534
- });
535
- }
536
-
537
- /**
538
- * Get CSV data from a file.
539
- * @param client - The HTTP client to use for the request.
540
- * @param filename - The name of the CSV file to fetch.
541
- * @returns A promise that resolves to the parsed CSV data.
542
- */
543
- export const getCSVData = (client: HttpClient, filename: string): Promise<CsvData> => {
544
- return client.fetch(filename, { selector: csvSelector });
545
- }
546
- ```
547
-
548
- #### Reusing Selectors
549
-
550
- You can reuse selectors across different parts of your application. This helps ensure that you don't have to write the same code multiple times.
551
-
552
- ```ts
553
- /** example of reusing the json selector */
554
- import { jsonSelector } from '@equinor/fusion-framework-module-http/selectors';
555
-
556
- /** definition of the data schema */
557
- import { schema, SchemaType } from './schema';
558
-
559
- /**
560
- * Response selector for parsing JSON data.
561
- * @param response - The HTTP response to process.
562
- * @returns The parsed JSON data.
563
- */
564
- export const dataParserSelector: ResponseSelector<SchemaType> => async(response) => {
565
- /** Parse the response, with the default jsonSelector */
566
- const rawData = await jsonSelector(response);
567
- /** Parse the data using the schema */
568
- return schema.parse(rawData);
569
- }
570
- ```
571
-
572
- #### Observable Selectors
573
-
574
- The `ResponseSelector` type is a generic type that allows you to return `ObservableInput` types. This means that the selector supports Promises, Observables, AsyncIterables, and other types that implement the `ObservableInput` interface.
575
-
576
- ```ts
577
- /** example of using an observable selector */
578
- import { from } from 'rxjs';
579
- import { switchMap } from 'rxjs/operators';
580
- import { jsonSelector } from '@equinor/fusion-framework-module-http/selectors';
581
-
582
- /** definition of the data schema */
583
- import { schema, SchemaType } from './schema';
584
-
585
- export const dataParserSelector: ResponseSelector => (response): Observable<SchemaType> => {
586
- /** convert the response to an observable */
587
- return from(jsonSelector(response)).pipe(
588
- /** map the async data using the schema */
589
- switchMap(schema.parseAsync)
590
- );
591
- }
592
- ```
593
-
594
- ### Use the abort functionality for cancellable requests
595
-
596
- When using the HTTP client, it's important to use the abort functionality for cancellable requests. This helps ensure that you don't have to handle the cancellation yourself.
597
-
598
- ```ts
599
- /** Example of using the abort functionality with the HTTP client */
600
- useEffect(() => {
601
- /** Create an abort controller */
602
- const abortController = new AbortController();
603
- try {
604
- /** Make a request using the client with abort signal */
605
- client.fetch(
606
- '/long-running-operation',
607
- {
608
- signal: abortController.signal
609
- }
610
- ).then(setData);
611
- } catch (error) {
612
- /** check if the error is an abort error */
613
- if ((error as Error).name === 'AbortError') {
614
- console.log('Request was aborted');
615
- } else {
616
- setError(error);
617
- }
618
- }
619
- // Cleanup function to abort the request when the component unmounts
620
- return () => {
621
- abortController.abort();
622
- };
623
- }, [client]);
624
- ```
625
-
626
- > [!NOTE]
627
- > Using observable streams, the request is aborted when the request is no longer observed.
628
- > ```ts
629
- > useEffect(() => {
630
- > const sub = client.json$('/api').subscribe({
631
- > next: setData,
632
- > error: setError
633
- > });
634
- > return () => sub.unsubscribe();
635
- > }, [client]);
636
-
637
- > [!TIP]
638
- > `HttpClient.abort` will cancel all ongoing requests.
639
-
640
- ### Utilize request and response operators
641
-
642
- The HTTP client provides the ability to add custom request and response operators. These operators allow you to intercept and modify requests before they are sent and responses before they are processed.
643
-
644
- **Functionality:**
645
-
646
- - Collection Management: Operators can add, set, get, and manage a collection of operators.
647
- - Chaining: Operators are processed in sequence, allowing for a chain of modifications.
648
- - Reusable: Operators can be shared across different operators.
649
- - Extensible: Custom operators can be created for specific needs.
650
-
651
- ```ts
652
- /**
653
- * Definition of the process operator.
654
- * @template T - this will be either the request or response object.
655
- * @template R - this will be the return type of the process operator.
656
- */
657
- type ProcessOperator<T, R = T> = (request: T) => R | void | Promise<R | void>;
658
-
659
- /**
660
- * interface for process operators
661
- * @template T - this will be either the request or response object.
662
- */
663
- interface IProcessOperators<T> {
664
- /** Add a process operator to the collection */
665
- add: (name: string, operator: ProcessOperator<T>) => void;
666
- /** Set a process operator in the collection */
667
- set: (name: string, operator: ProcessOperator<T>) => void;
668
- /** Get a process operator from the collection */
669
- get: (name: string) => ProcessOperator<T> | undefined;
670
- /** Remove a process operator from the collection */
671
- remove: (name: string) => void;
672
- }
673
- ```
674
-
675
- > [!CAUTION]
676
- > Even though the process operator can return a value, it is not recommended to do so. This can cause unexpected behavior.
677
-
678
- > [!WARNING]
679
- > - Handlers are permanent to the client instance.
680
- > - `add` will throw error if a handler with the same name already exists.
681
- > - `set` will override existing handlers with the same name.
682
-
683
- > [!IMPORTANT]
684
- > Handlers are executed in the order they are added. This means that you should add handlers that are more specific to the end of the list.
685
-
686
- #### Request Handlers
687
-
688
- You can add request handlers to modify outgoing requests:
689
-
690
- ```ts
691
- /**
692
- * Example of adding a request handler to set a custom header
693
- * The request handler implements the `IHttpRequestHandler` which exposes the `setHeader` method.
694
- */
695
- client.requestHandler.setHeader('X-Custom-Header', 'CustomValue');
696
-
697
- /** Example of adding a request handler to log all outgoing requests */
698
- client.requestHandler.add(
699
- 'request-logger',
700
- (request) => {
701
- console.debug('Outgoing request:', request.url);
702
- }
703
- );
704
- ```
705
-
706
- ##### Available Request Handlers
707
-
708
- __`capitalizeRequestMethodOperator`__
709
-
710
- operator to ensure that the HTTP method of a given request is in uppercase.
711
-
712
- > [!NOTE]
713
- > by default this plugin will log a warning if the method was not in uppercase.
714
- > This can be disabled by setting the `silent` option to `true`.
715
-
716
- ```typescript
717
- import { capitalizeRequestMethodOperator } from '@equinor/fusion-framework-module-http/operators';
718
- client.requestHandler.add(
719
- 'capatalize-method',
720
- capitalizeRequestMethodOperator()
721
- );
722
-
723
- // transforms `method` to uppercase and logs a warning.
724
- client.get('https://example.com', { method: 'get' });
725
- ```
726
-
727
- __`requestValidationOperator`__
728
-
729
- operator to validate the request before it is sent.
730
-
731
- > [!NOTE]
732
- > By default this plugin will only log a warning if the request is invalid.
733
- > To allow the plugin to modify `FetchRequest` set the `parse` option to `true`.
734
- > __NOTE__ this will also throw an error if the request is invalid.
86
+ Use the configured client after the framework is initialized:
735
87
 
736
88
  ```typescript
737
- import { requestValidationOperator } from '@equinor/fusion-framework-module-http/operators';
738
- client.requestHandler.add(
739
- 'validate-request',
740
- requestValidationOperator(
741
- {
742
- // use parsed schema values for request
743
- parse: true,
744
- // only allow defined defined options
745
- strict: false,
746
- }
747
- )
748
- );
749
- ```
750
-
751
- > [!WARNING]
752
- > Validating `RequestInit` should not be necessary, but a helpful tool for development.
753
-
754
-
755
- #### Response Handlers
756
-
757
- > [!IMPORTANT]
758
- > Response operators should not modify the response object directly, this might lead to unexpected behavior, like providing the wrong response to the next operator and the provided response selector.
759
- > 2nd, there are no good way to infer the response type, so the response object should be returned as is.
760
-
761
- intercept and modify responses before they are processed:
762
-
763
- ```ts
764
- /** Example of adding a response handler to log all incoming responses */
765
- client.responseHandler.add(
766
- 'response-logger',
767
- (response) => {
768
- console.log('Incoming response:', response.url, response.status);
769
- }
770
- );
89
+ type CatalogItem = {
90
+ id: string;
91
+ title: string;
92
+ };
771
93
 
772
- /** example of adding a response handler to validate the response */
773
- client.responseHandler.add(
774
- 'response-validator'
775
- (response) => {
776
- if (response.status === 401) {
777
- throw Error('response was 401');
778
- }
779
- }
780
- );
94
+ const client = framework.modules.http.createClient('catalog');
95
+ const items = await client.json<CatalogItem[]>('/items');
781
96
  ```
782
97
 
783
- ### Executing Calls
98
+ What is happening here:
784
99
 
785
- The HTTP client provides several methods to execute fetch calls. Here are some examples:
100
+ - `catalog` is a named configuration, not a long-lived shared client instance.
101
+ - `createClient('catalog')` creates a fresh client with the configured base URI, handlers, and default scopes.
102
+ - `json()` runs the request pipeline and returns parsed JSON.
786
103
 
787
- ```ts
788
- /** Example of executing fetch call with the HTTP client */
789
- client.fetch('/users');
790
- client.fetch$('/users');
104
+ ## Public Entry Points
791
105
 
792
- /** Example of executing a JSON api call with the HTTP client */
793
- client.json<Users>('/users');
794
- client.json$<Users>('/users');
106
+ | Entry point | Purpose |
107
+ | --- | --- |
108
+ | `@equinor/fusion-framework-module-http` | Module definition, configurator/provider helpers, exported errors, and shared client types |
109
+ | `@equinor/fusion-framework-module-http/client` | `HttpClient`, `HttpClientMsal`, and client-related types |
110
+ | `@equinor/fusion-framework-module-http/operators` | `HttpRequestHandler`, `HttpResponseHandler`, `ProcessOperators`, `capitalizeRequestMethodOperator`, `requestValidationOperator`, `sseMap`, and operator types |
111
+ | `@equinor/fusion-framework-module-http/selectors` | `jsonSelector`, `blobSelector`, `createSseSelector`, `ResponseSelector`, and SSE selector types |
112
+ | `@equinor/fusion-framework-module-http/errors` | `HttpResponseError`, `HttpJsonResponseError`, and `ServerSentEventResponseError` |
795
113
 
796
- /** Example of executing a JSON api call with options */
797
- client.json<BlogPost>('/posts', {
798
- method: 'POST',
799
- body: { title: 'New Post', content: 'Content here' },
800
- })
114
+ For most teams, the top-level package plus the `selectors` or `operators` subpaths are enough.
801
115
 
802
- /** Example of executing a blob call */
803
- client.blob('/image.jpg').then(
804
- ({ filename, blob }) => {
805
- const url = URL.createObjectURL(blob);
806
- return `<a download='${filename}' href='${url}'>`
807
- }
808
- );
116
+ ## Configuring Clients
809
117
 
810
- /** example of Server Sent Events */
811
- client.sse$('/chatbot', {
812
- method: 'POST',
813
- body: { prompt: 'Tell me a joke' }
814
- })
118
+ Use named clients when you want a stable configuration for a backend: a base URL, default MSAL scopes, shared headers, and request or response policies.
815
119
 
816
- /** Example of executing named function */
817
- client.execute<Users>('json', '/users'); // same as client.json<Users>('/users');
120
+ The HTTP module supports three common configuration styles:
818
121
 
819
- ```
122
+ - named clients with `configureHttpClient(name, options)`
123
+ - lower-level module configuration with `configureHttp(...)`
124
+ - ad-hoc clients with `createClient({ baseUri })` or `createClient('https://...')`
820
125
 
821
- ### Monitoring HTTP Activity
126
+ MSAL scope behavior is part of client configuration:
822
127
 
823
- The HTTP client provides observables that allow you to monitor all incoming requests and responses. This can be useful for logging, debugging, and tracking network activity in your application.
128
+ - `defaultScopes` belong to the configured client
129
+ - per-request `scopes` are appended to `defaultScopes`
130
+ - token acquisition only happens when the auth module is available and the final scope list is non-empty
824
131
 
825
- ```ts
826
- /** Example of monitoring all incoming requests */
827
- client.request$.subscribe(request => {
828
- console.log('Incoming request:', request);
829
- });
132
+ See [Client Configuration](docs/client-configuration.md) for configuration options, `onCreate`, direct module integration, custom client classes, ad-hoc clients, and `ClientNotFoundException` behavior.
830
133
 
831
- /** Example of monitoring all incoming responses */
832
- client.response$.subscribe(response => {
833
- console.log('Incoming response:', response);
834
- });
835
- ```
134
+ ## Core API
836
135
 
837
- ### Server-Sent Events (SSE)
136
+ Each call to `createClient()` returns a fresh instance with its own handler state.
838
137
 
839
- The HTTP client supports Server-Sent Events (SSE) through the `sse$` method. While this feature is versatile and can be used for various real-time communication scenarios, such as notifications, dashboards, or collaborative tools, it was primarily developed to facilitate streaming responses from chatbots.
138
+ | Method | Returns | Use it when |
139
+ | --- | --- | --- |
140
+ | `fetch(path, init?)` | `Promise<Response \| T>` | You want the raw `Response`, or you want to provide a custom selector |
141
+ | `fetch$(path, init?)` | `Observable<Response \| T>` | You want RxJS composition or cancellation by unsubscribing |
142
+ | `json(path, init?)` | `Promise<T>` | You are calling a JSON API |
143
+ | `json$(path, init?)` | `Observable<T>` | You want the JSON API call as an observable |
144
+ | `blob(path, init?)` | `Promise<BlobResult>` | You are downloading a file or binary payload |
145
+ | `blob$(path, init?)` | `Observable<BlobResult>` | You want blob responses in an observable pipeline |
146
+ | `sse$(path, init?, options?)` | `Observable<ServerSentEvent<T>>` | You are consuming server-sent events |
147
+ | `execute(method, path, init?)` | `fetch`, `fetch$`, `json`, or `json$` result | You need to pick one of those methods dynamically |
148
+ | `abort()` | `void` | You want to cancel every in-flight request started by this client instance |
840
149
 
841
- Key benefits of SSE:
842
- - Ideal for live updates, such as chatbot interactions, notifications, or collaborative tools.
843
- - Provides an efficient way to handle server-driven data updates.
844
- - Simplifies real-time communication with minimal setup.
150
+ Deprecated aliases still exist, but new code should prefer `fetch()` and `json()` over `fetchAsync()` and `jsonAsync()`.
845
151
 
846
- The `sse$` method enables you to subscribe to a continuous stream of events sent by the server, making it particularly useful for applications requiring dynamic, real-time interactions, such as conversational AI or chatbot systems. This functionality ensures seamless and efficient communication between the client and server, enhancing the user experience in scenarios where immediate feedback or updates are critical.
152
+ ### Promise Or Observable?
847
153
 
848
- #### Usage
154
+ - Use promise methods when you just want the result of a single request.
155
+ - Use observable methods when you want RxJS composition, cancellation by unsubscribe, or stream-based integration.
156
+ - Observable methods are cold. Nothing is sent until you subscribe.
157
+ - Promise methods are implemented with `firstValueFrom(...)` on the corresponding observable method.
158
+ - `request$` and `response$` let you observe outgoing requests and incoming responses for that client instance.
849
159
 
850
- The `sse$` method returns an observable that emits events as they are received from the server. It uses `client.fetch$` under the hood, abstracting common boilerplate code such as setting headers, configuring the request, and handling the response stream.
160
+ In practical terms:
851
161
 
852
- ```typescript
853
- /** Example of using the sse$ method to subscribe to server-sent events */
854
- const eventStream$ = client.sse$<{ message: string }>('/events');
162
+ - `json('/items')` is the simplest choice for page load or command-style calls.
163
+ - `json$('/items')` is the better choice when the request depends on another stream such as route params, user selections, debounced search terms, refresh triggers, or SSE event handling.
855
164
 
856
- const subscription = eventStream$.subscribe({
857
- next: (event) => console.log('Received event:', event),
858
- error: (error) => console.error('An error occurred:', error),
859
- complete: () => console.log('Event stream completed'),
860
- });
165
+ See [Observable Patterns](docs/observable-patterns.md) for RxJS composition, cancellation, request and response inspection, and selector reuse.
861
166
 
862
- // To stop listening to events, unsubscribe from the observable
863
- subscription.unsubscribe();
864
- ```
167
+ ## Selectors And Handlers
865
168
 
866
- #### Options
169
+ Selectors and handlers solve different problems:
867
170
 
868
- | Option | Description |
869
- | ---------------- | ------------------------------------------------------------------------------------ |
870
- | `dataParser` | A function to parse the raw event data into a desired format. |
871
- | `skipHeartbeats` | A boolean indicating whether to skip heartbeat events (default: `false`). |
872
- | `eventFilter` | An array of event types to filter. Only events matching these types will be emitted. |
171
+ - selectors shape the response value your application consumes
172
+ - request handlers shape outgoing transport behavior
173
+ - response handlers enforce response policies before selector parsing runs
873
174
 
874
- #### Customizing SSE Behavior
175
+ Built-in selectors include `jsonSelector`, `blobSelector`, and `createSseSelector`. The default request pipeline includes `capitalizeRequestMethodOperator()` and `requestValidationOperator()`.
875
176
 
876
- You can customize the behavior of the SSE stream by providing options to the `sse$` method. For example, you can specify a custom data parser or handle reconnection logic.
177
+ See [Selectors and Handlers](docs/selectors-and-handlers.md) for guidance on when to use each API, how to add reusable operators, and how to keep transport concerns separate from response parsing.
877
178
 
878
- ```typescript
879
- /** Example of customizing SSE behavior with options */
880
- const customEventStream$ = client.sse$<MyEventData & { timestamp: Date }>(
881
- '/custom-events',
882
- { method: 'POST', body: 'tell me a joke' },
883
- {
884
- dataParser: (data) => {
885
- const parsedData = JSON.parse(data) as MyEventData;
886
- return { ...parsedData, timestamp: new Date() };
887
- },
888
- }
889
- );
890
-
891
- customEventStream$.subscribe({
892
- next: (event) => console.log('Custom event received:', event),
893
- error: (error) => console.error('An error occurred:', error),
894
- complete: () => console.log('Custom event stream completed'),
895
- });
896
- ```
179
+ ## Server-Sent Events
897
180
 
898
- #### Aborting SSE Requests
181
+ Use `client.sse$()` when the endpoint returns `text/event-stream` and you want parsed event objects instead of manually reading the stream. For lower-level composition, use `createSseSelector` or `sseMap`.
899
182
 
900
- You can abort an SSE request by using the `abort` method of the HTTP client or by unsubscribing from the observable.
183
+ See [Server-Sent Events](docs/server-sent-events.md) for `sse$()` usage, event filtering, heartbeat handling, abort behavior, lower-level SSE helpers, and `ServerSentEventResponseError` handling.
901
184
 
902
- ```typescript
903
- /** Example of aborting an SSE request */
904
- const abortController = new AbortController();
185
+ ## Error Types
905
186
 
906
- const abortableEventStream$ = client.sse$<{ message: string }>('/abortable-events', {
907
- signal: abortController.signal,
908
- });
187
+ | Error | When it is used |
188
+ | --- | --- |
189
+ | `ClientNotFoundException` | `createClient(name)` is called with a key that is neither configured nor an absolute `http:` or `https:` URL |
190
+ | `HttpResponseError` | Generic response or selector failure, including synchronous selector execution failures |
191
+ | `HttpJsonResponseError` | JSON parsing failures or non-OK JSON responses |
192
+ | `ServerSentEventResponseError` | Invalid SSE responses such as non-OK status, unreadable body, or wrong content type |
909
193
 
910
- const subscription = abortableEventStream$.subscribe({
911
- next: (event) => console.log('Received event:', event),
912
- error: (error) => console.error('An error occurred:', error),
913
- complete: () => console.log('Event stream completed'),
914
- });
194
+ Native fetch errors can still surface as well, including abort and network failures.
915
195
 
916
- // Abort the request
917
- abortController.abort();
918
- ```
196
+ ## Advanced Guides
919
197
 
920
- > [!NOTE]
921
- > Unsubscribing from the observable will also abort the SSE request automatically.
922
-
923
- #### Using `sseSelector` with `httpClient.fetch`
924
-
925
- If you need to create an SSE call from scratch without using the `sse$` method, you can use the `sseSelector` directly with the `httpClient.fetch` method. Here's an example:
926
-
927
- ```typescript
928
- import { createSseSelector } from '@equinor/fusion-framework-module-http/selectors';
929
-
930
- /** Example of using sseSelector with httpClient.fetch */
931
- const sseSelector = createSseSelector<{ message: string }>({
932
- dataParser: (data) => JSON.parse(data),
933
- skipHeartbeats: true,
934
- eventFilter: ['message', 'update'],
935
- });
936
-
937
- const headers = new Headers({
938
- 'Accept': 'text/event-stream',
939
- 'Cache-Control': 'no-cache',
940
- 'Connection': 'keep-alive',
941
- });
942
-
943
- const sseStream$ = client.fetch('/events', {
944
- selector: sseSelector,
945
- headers,
946
- method: 'GET', // SSE calls typically use GET
947
- });
948
-
949
- const subscription = sseStream$.subscribe({
950
- next: (event) => console.log('Received event:', event),
951
- error: (error) => console.error('An error occurred:', error),
952
- complete: () => console.log('Event stream completed'),
953
- });
954
-
955
- // To stop listening to events, unsubscribe from the observable
956
- subscription.unsubscribe();
957
- ```
958
-
959
- #### Using `sseMap` with `client.fetch$`
960
-
961
- The `sseMap` operator can be used with `client.fetch$` to process Server-Sent Events (SSE) in a declarative manner. This approach allows you to handle SSE streams while leveraging the flexibility of RxJS.
962
-
963
- ```typescript
964
- import { sseMap } from '@equinor/fusion-framework-module-http/operators';
965
-
966
- /** Example of using sseMap with client.fetch$ */
967
- const sseStream$ = client.fetch$('/events', {
968
- method: 'GET',
969
- headers: {
970
- 'Accept': 'text/event-stream',
971
- 'Cache-Control': 'no-cache',
972
- 'Connection': 'keep-alive',
973
- },
974
- }).pipe(
975
- sseMap<{ message: string }>({
976
- dataParser: (data) => JSON.parse(data),
977
- skipHeartbeats: true,
978
- eventFilter: ['message', 'update'],
979
- }),
980
- );
981
-
982
- const subscription = sseStream$.subscribe({
983
- next: (event) => console.log('Received event:', event),
984
- error: (error) => console.error('An error occurred:', error),
985
- complete: () => console.log('SSE stream completed'),
986
- });
987
-
988
- // To stop listening to events, unsubscribe from the observable
989
- subscription.unsubscribe();
990
- ```
198
+ - [Client Configuration](docs/client-configuration.md): named clients, `configureHttpClient`, `configureHttp`, `onCreate`, custom client classes, and ad-hoc clients
199
+ - [Observable Patterns](docs/observable-patterns.md): `fetch$`, `json$`, `request$`, `response$`, cancellation, and RxJS composition
200
+ - [Selectors and Handlers](docs/selectors-and-handlers.md): `jsonSelector`, `blobSelector`, request handlers, response handlers, and built-in operators
201
+ - [Server-Sent Events](docs/server-sent-events.md): `sse$`, `createSseSelector`, `sseMap`, event filtering, heartbeats, and abort behavior
991
202
 
992
- This example demonstrates how to use `sseMap` with `client.fetch$` to process SSE data efficiently.
203
+ ## Things To Remember
993
204
 
994
- This approach provides more flexibility for customizing the SSE behavior while still leveraging the `httpClient.fetch` method. However, for most use cases, `client.sse$` is recommended as it simplifies the process by abstracting common boilerplate code.
205
+ - `createClient(name)` throws `ClientNotFoundException` for unknown client keys.
206
+ - `hasClient(name)` is useful when a client configuration may be optional.
207
+ - `json()` and `json$()` stringify object bodies with `JSON.stringify(...)` and append JSON request headers.
208
+ - `fetch()` and `fetch$()` leave the response untouched unless you provide a `selector`.
209
+ - `execute()` currently supports `fetch`, `fetch$`, `json`, and `json$` only.
210
+ - If you configure a client through `config.http.configureClient(name, callback)`, that callback is stored as `onCreate` and runs every time a new client instance is created.