@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.
- package/CHANGELOG.md +34 -27
- package/README.md +141 -925
- package/dist/esm/configurator.js +5 -5
- package/dist/esm/configurator.js.map +1 -1
- package/dist/esm/lib/client/client-msal.js +3 -2
- package/dist/esm/lib/client/client-msal.js.map +1 -1
- package/dist/esm/lib/client/client.js +8 -7
- package/dist/esm/lib/client/client.js.map +1 -1
- package/dist/esm/lib/operators/index.js +1 -0
- package/dist/esm/lib/operators/index.js.map +1 -1
- package/dist/esm/lib/selectors/index.js +1 -0
- package/dist/esm/lib/selectors/index.js.map +1 -1
- package/dist/esm/module.js +18 -9
- package/dist/esm/module.js.map +1 -1
- package/dist/esm/provider.js +38 -27
- package/dist/esm/provider.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/configurator.d.ts +49 -26
- package/dist/types/lib/client/client.d.ts +7 -7
- package/dist/types/lib/operators/index.d.ts +1 -0
- package/dist/types/lib/selectors/index.d.ts +2 -0
- package/dist/types/module.d.ts +18 -9
- package/dist/types/provider.d.ts +24 -19
- package/dist/types/version.d.ts +1 -1
- package/docs/client-configuration.md +175 -0
- package/docs/observable-patterns.md +103 -0
- package/docs/selectors-and-handlers.md +112 -0
- package/docs/server-sent-events.md +125 -0
- package/package.json +6 -6
- package/src/configurator.ts +54 -26
- package/src/lib/client/client-msal.ts +3 -2
- package/src/lib/client/client.ts +8 -9
- package/src/lib/operators/index.ts +1 -0
- package/src/lib/selectors/index.ts +7 -0
- package/src/module.ts +18 -9
- package/src/provider.ts +56 -34
- package/src/version.ts +1 -1
- package/tests/HttpClient.test.ts +90 -11
- package/tests/operators.test.ts +24 -0
- package/tests/sse.selector.test.ts +6 -7
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# Selectors And Handlers
|
|
2
|
+
|
|
3
|
+
Selectors and handlers are related, but they solve different problems.
|
|
4
|
+
|
|
5
|
+
- selectors transform a `Response` into the value your application consumes
|
|
6
|
+
- request handlers shape or validate the outgoing request
|
|
7
|
+
- response handlers apply transport-level response rules before selector parsing runs
|
|
8
|
+
|
|
9
|
+
Use this split to keep parsing logic separate from transport policy.
|
|
10
|
+
|
|
11
|
+
## Choose The Right Tool
|
|
12
|
+
|
|
13
|
+
| Need | Use |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| Parse JSON, blobs, or SSE events | selector |
|
|
16
|
+
| Add headers, normalize methods, validate request shape | request handler |
|
|
17
|
+
| Reject certain responses before parsing | response handler |
|
|
18
|
+
| Reuse parsing rules across multiple calls | selector |
|
|
19
|
+
| Reuse transport behavior across a client configuration | handler |
|
|
20
|
+
|
|
21
|
+
## Built-In Selectors
|
|
22
|
+
|
|
23
|
+
| Selector | What it returns | Notes |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| `jsonSelector` | parsed JSON or `undefined` for `204` | Throws `HttpJsonResponseError` on non-OK responses and includes parsed error payloads when possible |
|
|
26
|
+
| `blobSelector` | `{ filename?, blob }` | Extracts `filename` from the `content-disposition` header when present |
|
|
27
|
+
| `createSseSelector` | `Observable<ServerSentEvent<T>>` | Lower-level SSE selector used by `client.sse$()` |
|
|
28
|
+
|
|
29
|
+
## Custom Selector Example
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import {
|
|
33
|
+
jsonSelector,
|
|
34
|
+
type ResponseSelector,
|
|
35
|
+
} from '@equinor/fusion-framework-module-http/selectors';
|
|
36
|
+
|
|
37
|
+
type User = {
|
|
38
|
+
id: string;
|
|
39
|
+
name: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const userSelector: ResponseSelector<User> = async (response) => {
|
|
43
|
+
const data = await jsonSelector<User>(response);
|
|
44
|
+
return {
|
|
45
|
+
...data,
|
|
46
|
+
name: data.name.trim(),
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
That selector can then be passed to `client.fetch('/users/42', { selector: userSelector })`.
|
|
52
|
+
|
|
53
|
+
## Request And Response Handlers
|
|
54
|
+
|
|
55
|
+
Handlers are sequential operator pipelines attached to each client instance.
|
|
56
|
+
|
|
57
|
+
- `add(key, operator)` throws if the key already exists
|
|
58
|
+
- `set(key, operator)` adds or replaces an operator
|
|
59
|
+
- `remove(key)` removes an operator
|
|
60
|
+
- returning `void` keeps the previous value flowing through the pipeline
|
|
61
|
+
- operators run in insertion order
|
|
62
|
+
|
|
63
|
+
The public handler types and utilities are exported from `@equinor/fusion-framework-module-http/operators`.
|
|
64
|
+
|
|
65
|
+
## Default Request Pipeline
|
|
66
|
+
|
|
67
|
+
Each created client starts with a default request pipeline containing:
|
|
68
|
+
|
|
69
|
+
1. `capitalizeRequestMethodOperator()`
|
|
70
|
+
2. `requestValidationOperator()`
|
|
71
|
+
|
|
72
|
+
That means lowercase methods are normalized to uppercase and request options are validated in non-throwing mode by default.
|
|
73
|
+
|
|
74
|
+
## Built-In Request Operators
|
|
75
|
+
|
|
76
|
+
- `capitalizeRequestMethodOperator(options?)` uppercases `request.method` and can suppress warnings with `silent: true`
|
|
77
|
+
- `requestValidationOperator(options?)` validates the request against the package schema and can optionally return parsed values
|
|
78
|
+
|
|
79
|
+
## Example: Configure Both Parsing And Transport
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import {
|
|
83
|
+
capitalizeRequestMethodOperator,
|
|
84
|
+
requestValidationOperator,
|
|
85
|
+
} from '@equinor/fusion-framework-module-http/operators';
|
|
86
|
+
|
|
87
|
+
configurator.configureHttpClient('catalog', {
|
|
88
|
+
baseUri: '/api/catalog',
|
|
89
|
+
onCreate: (client) => {
|
|
90
|
+
client.requestHandler.set('validate-strict', requestValidationOperator({
|
|
91
|
+
parse: true,
|
|
92
|
+
strict: true,
|
|
93
|
+
}));
|
|
94
|
+
|
|
95
|
+
client.requestHandler.add('normalize-method', capitalizeRequestMethodOperator());
|
|
96
|
+
|
|
97
|
+
client.responseHandler.add('reject-server-errors', (response) => {
|
|
98
|
+
if (response.status >= 500) {
|
|
99
|
+
throw new Error(`Unexpected response status: ${response.status}`);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Rule Of Thumb
|
|
107
|
+
|
|
108
|
+
- use handlers for cross-cutting transport concerns such as headers, validation, logging, token behavior, or rejecting bad responses
|
|
109
|
+
- use selectors for parsing a response into a domain shape
|
|
110
|
+
- use observables for orchestration and composition
|
|
111
|
+
|
|
112
|
+
When a piece of logic changes how the request travels, it usually belongs in a handler. When it changes what value the caller receives, it usually belongs in a selector.
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Server-Sent Events
|
|
2
|
+
|
|
3
|
+
Use `client.sse$()` when an endpoint returns `text/event-stream` and you want parsed `ServerSentEvent<T>` objects instead of manually reading the response body.
|
|
4
|
+
|
|
5
|
+
This is the highest-level SSE API in the package. If you need lower-level composition, use `createSseSelector` or `sseMap`.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
const events$ = client.sse$<{ message: string }>('/events', undefined, {
|
|
11
|
+
eventFilter: 'message',
|
|
12
|
+
skipHeartbeats: true,
|
|
13
|
+
});
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Subscribe to the result the same way you would any other observable HTTP call.
|
|
17
|
+
|
|
18
|
+
## `sse$()` Inputs
|
|
19
|
+
|
|
20
|
+
| Input | Where it goes | Purpose |
|
|
21
|
+
| --- | --- | --- |
|
|
22
|
+
| request init | second argument to `sse$()` | HTTP method, body, headers, and `signal` |
|
|
23
|
+
| SSE selector options | third argument to `sse$()` | `eventFilter`, `skipHeartbeats`, and custom parsing |
|
|
24
|
+
|
|
25
|
+
`client.sse$()` uses the request `signal` as the abort signal for the underlying SSE reader.
|
|
26
|
+
|
|
27
|
+
## SSE Options
|
|
28
|
+
|
|
29
|
+
| Option | Type | What it does |
|
|
30
|
+
| --- | --- | --- |
|
|
31
|
+
| `eventFilter` | `string | string[]` | Emits only matching named SSE events |
|
|
32
|
+
| `skipHeartbeats` | `boolean` | Skips empty heartbeat events and named `heartbeat` or `ping` events |
|
|
33
|
+
| `dataParser` | `(data: string) => T` | Overrides the default parser for the `data:` field |
|
|
34
|
+
|
|
35
|
+
By default, the parser tries `JSON.parse(...)` first and falls back to the raw string when parsing fails.
|
|
36
|
+
|
|
37
|
+
## What `sse$()` Does For You
|
|
38
|
+
|
|
39
|
+
- adds `Accept: text/event-stream`
|
|
40
|
+
- adds `Content-Type: text/event-stream`
|
|
41
|
+
- adds `Cache-Control: no-cache` and `Connection: keep-alive`
|
|
42
|
+
- validates that the response is a readable `text/event-stream`
|
|
43
|
+
- parses `data:` fields into `ServerSentEvent<T>` objects
|
|
44
|
+
- honors `retry:` directives by delaying before continuing to read
|
|
45
|
+
- stops reading when the observable is unsubscribed or the abort signal fires
|
|
46
|
+
|
|
47
|
+
## Abort Behavior
|
|
48
|
+
|
|
49
|
+
Pass an `AbortSignal` through the request init when you need explicit cancellation.
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
const abortController = new AbortController();
|
|
53
|
+
|
|
54
|
+
const events$ = client.sse$<{ message: string }>(
|
|
55
|
+
'/events',
|
|
56
|
+
{ signal: abortController.signal },
|
|
57
|
+
{ skipHeartbeats: true },
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
abortController.abort();
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Unsubscribing from the observable also stops the underlying stream.
|
|
64
|
+
|
|
65
|
+
## Lower-Level SSE APIs
|
|
66
|
+
|
|
67
|
+
### `createSseSelector`
|
|
68
|
+
|
|
69
|
+
Use `createSseSelector` when you want to compose SSE handling with `fetch()` or `fetch$()` yourself.
|
|
70
|
+
|
|
71
|
+
Unlike `client.sse$()`, the lower-level SSE APIs do not add SSE request headers for you. Add the headers your endpoint expects, especially `Accept: text/event-stream`.
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
import { createSseSelector } from '@equinor/fusion-framework-module-http/selectors';
|
|
75
|
+
|
|
76
|
+
const selector = createSseSelector<{ message: string }>({
|
|
77
|
+
eventFilter: ['message', 'update'],
|
|
78
|
+
skipHeartbeats: true,
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const events$ = client.fetch$('/events', {
|
|
82
|
+
headers: {
|
|
83
|
+
Accept: 'text/event-stream',
|
|
84
|
+
'Cache-Control': 'no-cache',
|
|
85
|
+
Connection: 'keep-alive',
|
|
86
|
+
},
|
|
87
|
+
selector,
|
|
88
|
+
});
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### `sseMap`
|
|
92
|
+
|
|
93
|
+
Use `sseMap` when you already have a response observable and want to transform that stream into SSE events.
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { sseMap } from '@equinor/fusion-framework-module-http/operators';
|
|
97
|
+
|
|
98
|
+
const events$ = client.fetch$('/events', {
|
|
99
|
+
headers: {
|
|
100
|
+
Accept: 'text/event-stream',
|
|
101
|
+
'Cache-Control': 'no-cache',
|
|
102
|
+
Connection: 'keep-alive',
|
|
103
|
+
},
|
|
104
|
+
}).pipe(
|
|
105
|
+
sseMap<{ message: string }>({
|
|
106
|
+
skipHeartbeats: true,
|
|
107
|
+
}),
|
|
108
|
+
);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Error Handling
|
|
112
|
+
|
|
113
|
+
The SSE APIs throw `ServerSentEventResponseError` when:
|
|
114
|
+
|
|
115
|
+
- the response is not OK
|
|
116
|
+
- the response body is not readable
|
|
117
|
+
- the response does not advertise `text/event-stream`
|
|
118
|
+
|
|
119
|
+
Handle SSE errors the same way you handle any other observable request error.
|
|
120
|
+
|
|
121
|
+
## Rule Of Thumb
|
|
122
|
+
|
|
123
|
+
- use `sse$()` for normal application code
|
|
124
|
+
- use `createSseSelector` when you want selector-level control
|
|
125
|
+
- use `sseMap` when you already have a response observable pipeline
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@equinor/fusion-framework-module-http",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/esm/index.js",
|
|
6
6
|
"types": "dist/types/index.d.ts",
|
|
@@ -58,13 +58,13 @@
|
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
60
|
"rxjs": "^7.8.1",
|
|
61
|
-
"zod": "^4.
|
|
62
|
-
"@equinor/fusion-framework-module": "^
|
|
63
|
-
"@equinor/fusion-framework-module-msal": "^
|
|
61
|
+
"zod": "^4.3.6",
|
|
62
|
+
"@equinor/fusion-framework-module": "^6.0.0",
|
|
63
|
+
"@equinor/fusion-framework-module-msal": "^8.0.0"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"typescript": "^5.
|
|
67
|
-
"vitest": "^
|
|
66
|
+
"typescript": "^5.9.3",
|
|
67
|
+
"vitest": "^4.1.0"
|
|
68
68
|
},
|
|
69
69
|
"scripts": {
|
|
70
70
|
"build": "tsc -b",
|
package/src/configurator.ts
CHANGED
|
@@ -11,10 +11,13 @@ import type { IHttpRequestHandler, IHttpResponseHandler } from './lib/operators'
|
|
|
11
11
|
* Represents the options for constructing an `IHttpClient` instance.
|
|
12
12
|
*
|
|
13
13
|
* @template TInit - The type of the initial request object used by the `IHttpClient` instance.
|
|
14
|
+
* @template TResponse - The type of the response object used by the `IHttpClient` instance.
|
|
14
15
|
* @property {IHttpRequestHandler<TInit>} requestHandler - The request handler to be used by the `IHttpClient` instance.
|
|
16
|
+
* @property {IHttpResponseHandler<TResponse>} [responseHandler] - The response handler to be used by the `IHttpClient` instance.
|
|
15
17
|
*/
|
|
16
|
-
interface HttpClientConstructorOptions<TInit extends FetchRequest> {
|
|
18
|
+
interface HttpClientConstructorOptions<TInit extends FetchRequest, TResponse = Response> {
|
|
17
19
|
requestHandler: IHttpRequestHandler<TInit>;
|
|
20
|
+
responseHandler?: IHttpResponseHandler<TResponse>;
|
|
18
21
|
}
|
|
19
22
|
|
|
20
23
|
/**
|
|
@@ -28,14 +31,20 @@ interface HttpClientConstructorOptions<TInit extends FetchRequest> {
|
|
|
28
31
|
interface HttpClientConstructor<TClient extends IHttpClient> {
|
|
29
32
|
new (
|
|
30
33
|
uri: string,
|
|
31
|
-
options: HttpClientConstructorOptions<
|
|
34
|
+
options: HttpClientConstructorOptions<
|
|
35
|
+
HttpClientRequestInitType<TClient>,
|
|
36
|
+
HttpClientResponseType<TClient>
|
|
37
|
+
>,
|
|
32
38
|
): TClient;
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
/**
|
|
36
|
-
*
|
|
42
|
+
* Configures how the provider creates a named `IHttpClient` instance.
|
|
37
43
|
*
|
|
38
|
-
*
|
|
44
|
+
* Use these options to define a base URL, MSAL scopes, shared request or response handlers,
|
|
45
|
+
* a custom client constructor, or per-instance setup through `onCreate`.
|
|
46
|
+
*
|
|
47
|
+
* @template TClient - The client type created from this configuration.
|
|
39
48
|
*/
|
|
40
49
|
export interface HttpClientOptions<TClient extends IHttpClient = IHttpClient> {
|
|
41
50
|
/** The base URI for the `IHttpClient` instance. */
|
|
@@ -54,7 +63,7 @@ export interface HttpClientOptions<TClient extends IHttpClient = IHttpClient> {
|
|
|
54
63
|
requestHandler?: IHttpRequestHandler<HttpClientRequestInitType<TClient>>;
|
|
55
64
|
|
|
56
65
|
/** The response handler to be used by the `IHttpClient` instance. */
|
|
57
|
-
responseHandler?: IHttpResponseHandler<
|
|
66
|
+
responseHandler?: IHttpResponseHandler<HttpClientResponseType<TClient>>;
|
|
58
67
|
}
|
|
59
68
|
|
|
60
69
|
/**
|
|
@@ -68,8 +77,21 @@ export type HttpClientRequestInitType<T extends IHttpClient> =
|
|
|
68
77
|
T extends IHttpClient<infer U> ? U : never;
|
|
69
78
|
|
|
70
79
|
/**
|
|
71
|
-
*
|
|
72
|
-
*
|
|
80
|
+
* Utility type that extracts the response type from an `IHttpClient` implementation.
|
|
81
|
+
* This is useful for ensuring type safety when configuring response handlers for an `IHttpClient` instance.
|
|
82
|
+
*
|
|
83
|
+
* @template T - The type of the `IHttpClient` implementation.
|
|
84
|
+
* @returns The response type for the `IHttpClient` implementation.
|
|
85
|
+
*/
|
|
86
|
+
export type HttpClientResponseType<T extends IHttpClient> =
|
|
87
|
+
T extends IHttpClient<infer _TRequest, infer TResponse> ? TResponse : never;
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Registers and looks up named HTTP client configurations for the HTTP module.
|
|
91
|
+
*
|
|
92
|
+
* Each named configuration can later be turned into a fresh client instance by the provider.
|
|
93
|
+
*
|
|
94
|
+
* @template TClient - The base client type the provider creates.
|
|
73
95
|
*/
|
|
74
96
|
export interface IHttpClientConfigurator<TClient extends IHttpClient = IHttpClient> {
|
|
75
97
|
readonly clients: Record<string, HttpClientOptions<TClient>>;
|
|
@@ -77,14 +99,15 @@ export interface IHttpClientConfigurator<TClient extends IHttpClient = IHttpClie
|
|
|
77
99
|
readonly defaultHttpRequestHandler: IHttpRequestHandler<HttpClientRequestInitType<TClient>>;
|
|
78
100
|
|
|
79
101
|
/**
|
|
80
|
-
*
|
|
81
|
-
* @param name
|
|
82
|
-
* @param args
|
|
102
|
+
* Registers or updates a named client configuration.
|
|
103
|
+
* @param name - The client key used later with `createClient(name)`.
|
|
104
|
+
* @param args - The configuration used when creating a client instance.
|
|
105
|
+
* @returns The configurator so registrations can be chained.
|
|
83
106
|
* @example
|
|
84
107
|
* ```ts
|
|
85
|
-
* configurator.http.configureClient('
|
|
86
|
-
* baseUri: 'https://
|
|
87
|
-
* defaultScopes: ['
|
|
108
|
+
* configurator.http.configureClient('catalog', {
|
|
109
|
+
* baseUri: 'https://api.example.com',
|
|
110
|
+
* defaultScopes: ['api://catalog-api/.default'],
|
|
88
111
|
* });
|
|
89
112
|
* ```
|
|
90
113
|
*/
|
|
@@ -94,18 +117,21 @@ export interface IHttpClientConfigurator<TClient extends IHttpClient = IHttpClie
|
|
|
94
117
|
): IHttpClientConfigurator<TClient>;
|
|
95
118
|
|
|
96
119
|
/**
|
|
97
|
-
*
|
|
98
|
-
* @param name
|
|
99
|
-
* @param uri base endpoint for the client
|
|
120
|
+
* Registers a named client with only a base URI.
|
|
121
|
+
* @param name - The client key used later with `createClient(name)`.
|
|
122
|
+
* @param uri - The base endpoint for the client.
|
|
123
|
+
* @returns The configurator so registrations can be chained.
|
|
100
124
|
*/
|
|
101
125
|
configureClient(name: string, uri: string): IHttpClientConfigurator<TClient>;
|
|
102
126
|
|
|
103
127
|
/**
|
|
104
|
-
*
|
|
105
|
-
* @param name
|
|
106
|
-
* @param onCreate callback
|
|
128
|
+
* Registers a named client using only an `onCreate` callback.
|
|
129
|
+
* @param name - The client key used later with `createClient(name)`.
|
|
130
|
+
* @param onCreate - The callback that runs for every created client instance.
|
|
131
|
+
* @returns The configurator so registrations can be chained.
|
|
132
|
+
* @example
|
|
107
133
|
* ```ts
|
|
108
|
-
* configurator.http.configureClient('
|
|
134
|
+
* configurator.http.configureClient('catalog', (client) => {
|
|
109
135
|
* client.requestHandler.add('logger', (request) => console.log(request));
|
|
110
136
|
* });
|
|
111
137
|
* ```
|
|
@@ -116,7 +142,9 @@ export interface IHttpClientConfigurator<TClient extends IHttpClient = IHttpClie
|
|
|
116
142
|
): HttpClientConfigurator<TClient>;
|
|
117
143
|
|
|
118
144
|
/**
|
|
119
|
-
*
|
|
145
|
+
* Checks whether a named client configuration exists.
|
|
146
|
+
* @param name - The client key to check.
|
|
147
|
+
* @returns `true` when a configuration exists for the key.
|
|
120
148
|
*/
|
|
121
149
|
hasClient(name: string): boolean;
|
|
122
150
|
}
|
|
@@ -127,15 +155,15 @@ export class HttpClientConfigurator<TClient extends IHttpClient>
|
|
|
127
155
|
{
|
|
128
156
|
protected _clients: Record<string, HttpClientOptions<TClient>> = {};
|
|
129
157
|
|
|
130
|
-
/**
|
|
158
|
+
/** Gets a shallow clone of all named client configurations. */
|
|
131
159
|
public get clients(): Record<string, HttpClientOptions<TClient>> {
|
|
132
160
|
return { ...this._clients };
|
|
133
161
|
}
|
|
134
162
|
|
|
135
|
-
/**
|
|
163
|
+
/** Default constructor used when a client configuration does not provide `ctor`. */
|
|
136
164
|
readonly defaultHttpClientCtor: HttpClientConstructor<TClient>;
|
|
137
165
|
|
|
138
|
-
/**
|
|
166
|
+
/** Default request handler pipeline cloned into each created client instance. */
|
|
139
167
|
readonly defaultHttpRequestHandler = new HttpRequestHandler<HttpClientRequestInitType<TClient>>({
|
|
140
168
|
// convert all request methods to uppercase
|
|
141
169
|
'capitalize-method': capitalizeRequestMethodOperator(),
|
|
@@ -144,8 +172,8 @@ export class HttpClientConfigurator<TClient extends IHttpClient>
|
|
|
144
172
|
});
|
|
145
173
|
|
|
146
174
|
/**
|
|
147
|
-
*
|
|
148
|
-
* @param client
|
|
175
|
+
* Creates a configurator with the default client constructor.
|
|
176
|
+
* @param client - The default client constructor used when `ctor` is not configured per client.
|
|
149
177
|
*/
|
|
150
178
|
constructor(client: HttpClientConstructor<TClient>) {
|
|
151
179
|
this.defaultHttpClientCtor = client;
|
|
@@ -67,9 +67,10 @@ export class HttpClientMsal<
|
|
|
67
67
|
* Merges the default scopes defined in the `HttpClientMsal` class with the scopes provided in the `init` parameter, if any.
|
|
68
68
|
* This ensures that the request includes the necessary scopes for MSAL authentication.
|
|
69
69
|
*/
|
|
70
|
-
const args =
|
|
70
|
+
const args = {
|
|
71
|
+
...init,
|
|
71
72
|
scopes: this.defaultScopes.concat(init?.scopes || []),
|
|
72
|
-
}
|
|
73
|
+
} as FetchRequestInit<T, TRequest, TResponse>;
|
|
73
74
|
|
|
74
75
|
return super._fetch$(path, args);
|
|
75
76
|
}
|
package/src/lib/client/client.ts
CHANGED
|
@@ -14,14 +14,12 @@ import type {
|
|
|
14
14
|
FetchResponse,
|
|
15
15
|
IHttpClient,
|
|
16
16
|
JsonRequest,
|
|
17
|
-
ResponseSelector,
|
|
18
17
|
StreamResponse,
|
|
19
18
|
} from './types';
|
|
20
19
|
|
|
21
20
|
import { HttpResponseError } from '../../errors';
|
|
22
21
|
import {
|
|
23
22
|
createSseSelector,
|
|
24
|
-
SseSelector,
|
|
25
23
|
type ServerSentEvent,
|
|
26
24
|
type SseSelectorOptions,
|
|
27
25
|
} from '../selectors/sse-selector';
|
|
@@ -240,15 +238,15 @@ export class HttpClient<
|
|
|
240
238
|
* @returns A `StreamResponse` that emits `ServerSentEvent<T>` objects as they are received from the server.
|
|
241
239
|
*
|
|
242
240
|
* @example
|
|
243
|
-
* const sse$ = httpClient.sse(
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
241
|
+
* const sse$ = httpClient.sse$(
|
|
242
|
+
* '/events',
|
|
243
|
+
* { method: 'POST', body: JSON.stringify({ prompt: 'tell me a joke' }) },
|
|
244
|
+
* { eventFilter: ['message'] },
|
|
247
245
|
* );
|
|
248
246
|
* sse$.subscribe({
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
247
|
+
* next: (event) => console.log(event),
|
|
248
|
+
* error: (err) => console.error(err),
|
|
249
|
+
* complete: () => console.log('Completed'),
|
|
252
250
|
* });
|
|
253
251
|
*/
|
|
254
252
|
public sse$<T = unknown>(
|
|
@@ -329,6 +327,7 @@ export class HttpClient<
|
|
|
329
327
|
const { selector, ...options } = args || {};
|
|
330
328
|
const response$ = of({
|
|
331
329
|
...options,
|
|
330
|
+
path,
|
|
332
331
|
uri: this._resolveUrl(path),
|
|
333
332
|
} as TRequest).pipe(
|
|
334
333
|
/** prepare request, allow extensions to modify request */
|
|
@@ -3,5 +3,6 @@ export { HttpResponseHandler } from './http-response-handler';
|
|
|
3
3
|
export { ProcessOperators } from './process-operators';
|
|
4
4
|
export { capitalizeRequestMethodOperator } from './capitalize-request-method.operator';
|
|
5
5
|
export { requestValidationOperator } from './request-validation.operator';
|
|
6
|
+
export { sseMap } from './sse.operator';
|
|
6
7
|
|
|
7
8
|
export * from './types';
|
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
export { jsonSelector } from './json-selector';
|
|
2
2
|
export { blobSelector } from './blob-selector';
|
|
3
|
+
export { createSseSelector } from './sse-selector';
|
|
3
4
|
|
|
4
5
|
export type { ResponseSelector } from '../client/types';
|
|
6
|
+
export type {
|
|
7
|
+
DataParser,
|
|
8
|
+
ServerSentEvent,
|
|
9
|
+
SseSelector,
|
|
10
|
+
SseSelectorOptions,
|
|
11
|
+
} from './sse-selector';
|
package/src/module.ts
CHANGED
|
@@ -40,7 +40,11 @@ export type HttpMsalModule = Module<
|
|
|
40
40
|
>;
|
|
41
41
|
|
|
42
42
|
/**
|
|
43
|
-
* HTTP module
|
|
43
|
+
* Default HTTP module definition for Fusion Framework applications.
|
|
44
|
+
*
|
|
45
|
+
* The module uses `HttpClientMsal` as the default client implementation and,
|
|
46
|
+
* when the auth module is available, installs a request handler that can acquire
|
|
47
|
+
* bearer tokens for scoped requests.
|
|
44
48
|
*/
|
|
45
49
|
export const module: HttpMsalModule = {
|
|
46
50
|
name: 'http',
|
|
@@ -95,7 +99,13 @@ export const module: HttpMsalModule = {
|
|
|
95
99
|
};
|
|
96
100
|
|
|
97
101
|
/**
|
|
98
|
-
*
|
|
102
|
+
* Creates a module configurator for the HTTP module.
|
|
103
|
+
*
|
|
104
|
+
* Use this when you need lower-level access to the module configuration callback,
|
|
105
|
+
* for example when registering several named clients in one setup step.
|
|
106
|
+
*
|
|
107
|
+
* @param configure - The callback that receives the HTTP module configuration.
|
|
108
|
+
* @returns A module configurator for the HTTP module.
|
|
99
109
|
*/
|
|
100
110
|
export const configureHttp = <TRef = unknown>(
|
|
101
111
|
configure: (config: ModuleConfigType<HttpMsalModule>, ref?: TRef) => void,
|
|
@@ -105,15 +115,14 @@ export const configureHttp = <TRef = unknown>(
|
|
|
105
115
|
});
|
|
106
116
|
|
|
107
117
|
/**
|
|
108
|
-
*
|
|
118
|
+
* Creates a module configurator that registers one named HTTP client.
|
|
109
119
|
*
|
|
110
|
-
* This
|
|
111
|
-
*
|
|
112
|
-
* and returns a module configurator that can be used to configure the HTTP module.
|
|
120
|
+
* This is the convenience API for the common case where a setup step only needs to
|
|
121
|
+
* register one client with `baseUri`, `defaultScopes`, handlers, or a custom constructor.
|
|
113
122
|
*
|
|
114
|
-
* @param name - The
|
|
115
|
-
* @param args - The
|
|
116
|
-
* @returns A module configurator that
|
|
123
|
+
* @param name - The client key used later with `createClient(name)`.
|
|
124
|
+
* @param args - The named client configuration.
|
|
125
|
+
* @returns A module configurator that registers the named client.
|
|
117
126
|
*/
|
|
118
127
|
export const configureHttpClient = <TRef = unknown>(
|
|
119
128
|
name: string,
|