@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
|
@@ -5,10 +5,13 @@ import type { IHttpRequestHandler, IHttpResponseHandler } from './lib/operators'
|
|
|
5
5
|
* Represents the options for constructing an `IHttpClient` instance.
|
|
6
6
|
*
|
|
7
7
|
* @template TInit - The type of the initial request object used by the `IHttpClient` instance.
|
|
8
|
+
* @template TResponse - The type of the response object used by the `IHttpClient` instance.
|
|
8
9
|
* @property {IHttpRequestHandler<TInit>} requestHandler - The request handler to be used by the `IHttpClient` instance.
|
|
10
|
+
* @property {IHttpResponseHandler<TResponse>} [responseHandler] - The response handler to be used by the `IHttpClient` instance.
|
|
9
11
|
*/
|
|
10
|
-
interface HttpClientConstructorOptions<TInit extends FetchRequest> {
|
|
12
|
+
interface HttpClientConstructorOptions<TInit extends FetchRequest, TResponse = Response> {
|
|
11
13
|
requestHandler: IHttpRequestHandler<TInit>;
|
|
14
|
+
responseHandler?: IHttpResponseHandler<TResponse>;
|
|
12
15
|
}
|
|
13
16
|
/**
|
|
14
17
|
* Represents a constructor for an `IHttpClient` instance.
|
|
@@ -19,12 +22,15 @@ interface HttpClientConstructorOptions<TInit extends FetchRequest> {
|
|
|
19
22
|
* @returns A new instance of the `TClient` type.
|
|
20
23
|
*/
|
|
21
24
|
interface HttpClientConstructor<TClient extends IHttpClient> {
|
|
22
|
-
new (uri: string, options: HttpClientConstructorOptions<HttpClientRequestInitType<TClient>>): TClient;
|
|
25
|
+
new (uri: string, options: HttpClientConstructorOptions<HttpClientRequestInitType<TClient>, HttpClientResponseType<TClient>>): TClient;
|
|
23
26
|
}
|
|
24
27
|
/**
|
|
25
|
-
*
|
|
28
|
+
* Configures how the provider creates a named `IHttpClient` instance.
|
|
26
29
|
*
|
|
27
|
-
*
|
|
30
|
+
* Use these options to define a base URL, MSAL scopes, shared request or response handlers,
|
|
31
|
+
* a custom client constructor, or per-instance setup through `onCreate`.
|
|
32
|
+
*
|
|
33
|
+
* @template TClient - The client type created from this configuration.
|
|
28
34
|
*/
|
|
29
35
|
export interface HttpClientOptions<TClient extends IHttpClient = IHttpClient> {
|
|
30
36
|
/** The base URI for the `IHttpClient` instance. */
|
|
@@ -38,7 +44,7 @@ export interface HttpClientOptions<TClient extends IHttpClient = IHttpClient> {
|
|
|
38
44
|
/** The request handler to be used by the `IHttpClient` instance. */
|
|
39
45
|
requestHandler?: IHttpRequestHandler<HttpClientRequestInitType<TClient>>;
|
|
40
46
|
/** The response handler to be used by the `IHttpClient` instance. */
|
|
41
|
-
responseHandler?: IHttpResponseHandler<
|
|
47
|
+
responseHandler?: IHttpResponseHandler<HttpClientResponseType<TClient>>;
|
|
42
48
|
}
|
|
43
49
|
/**
|
|
44
50
|
* Utility type that extracts the request init type from an `IHttpClient` implementation.
|
|
@@ -49,60 +55,77 @@ export interface HttpClientOptions<TClient extends IHttpClient = IHttpClient> {
|
|
|
49
55
|
*/
|
|
50
56
|
export type HttpClientRequestInitType<T extends IHttpClient> = T extends IHttpClient<infer U> ? U : never;
|
|
51
57
|
/**
|
|
52
|
-
*
|
|
53
|
-
*
|
|
58
|
+
* Utility type that extracts the response type from an `IHttpClient` implementation.
|
|
59
|
+
* This is useful for ensuring type safety when configuring response handlers for an `IHttpClient` instance.
|
|
60
|
+
*
|
|
61
|
+
* @template T - The type of the `IHttpClient` implementation.
|
|
62
|
+
* @returns The response type for the `IHttpClient` implementation.
|
|
63
|
+
*/
|
|
64
|
+
export type HttpClientResponseType<T extends IHttpClient> = T extends IHttpClient<infer _TRequest, infer TResponse> ? TResponse : never;
|
|
65
|
+
/**
|
|
66
|
+
* Registers and looks up named HTTP client configurations for the HTTP module.
|
|
67
|
+
*
|
|
68
|
+
* Each named configuration can later be turned into a fresh client instance by the provider.
|
|
69
|
+
*
|
|
70
|
+
* @template TClient - The base client type the provider creates.
|
|
54
71
|
*/
|
|
55
72
|
export interface IHttpClientConfigurator<TClient extends IHttpClient = IHttpClient> {
|
|
56
73
|
readonly clients: Record<string, HttpClientOptions<TClient>>;
|
|
57
74
|
readonly defaultHttpClientCtor: HttpClientConstructor<TClient>;
|
|
58
75
|
readonly defaultHttpRequestHandler: IHttpRequestHandler<HttpClientRequestInitType<TClient>>;
|
|
59
76
|
/**
|
|
60
|
-
*
|
|
61
|
-
* @param name
|
|
62
|
-
* @param args
|
|
77
|
+
* Registers or updates a named client configuration.
|
|
78
|
+
* @param name - The client key used later with `createClient(name)`.
|
|
79
|
+
* @param args - The configuration used when creating a client instance.
|
|
80
|
+
* @returns The configurator so registrations can be chained.
|
|
63
81
|
* @example
|
|
64
82
|
* ```ts
|
|
65
|
-
* configurator.http.configureClient('
|
|
66
|
-
* baseUri: 'https://
|
|
67
|
-
* defaultScopes: ['
|
|
83
|
+
* configurator.http.configureClient('catalog', {
|
|
84
|
+
* baseUri: 'https://api.example.com',
|
|
85
|
+
* defaultScopes: ['api://catalog-api/.default'],
|
|
68
86
|
* });
|
|
69
87
|
* ```
|
|
70
88
|
*/
|
|
71
89
|
configureClient<T extends TClient>(name: string, args: HttpClientOptions<T>): IHttpClientConfigurator<TClient>;
|
|
72
90
|
/**
|
|
73
|
-
*
|
|
74
|
-
* @param name
|
|
75
|
-
* @param uri base endpoint for the client
|
|
91
|
+
* Registers a named client with only a base URI.
|
|
92
|
+
* @param name - The client key used later with `createClient(name)`.
|
|
93
|
+
* @param uri - The base endpoint for the client.
|
|
94
|
+
* @returns The configurator so registrations can be chained.
|
|
76
95
|
*/
|
|
77
96
|
configureClient(name: string, uri: string): IHttpClientConfigurator<TClient>;
|
|
78
97
|
/**
|
|
79
|
-
*
|
|
80
|
-
* @param name
|
|
81
|
-
* @param onCreate callback
|
|
98
|
+
* Registers a named client using only an `onCreate` callback.
|
|
99
|
+
* @param name - The client key used later with `createClient(name)`.
|
|
100
|
+
* @param onCreate - The callback that runs for every created client instance.
|
|
101
|
+
* @returns The configurator so registrations can be chained.
|
|
102
|
+
* @example
|
|
82
103
|
* ```ts
|
|
83
|
-
* configurator.http.configureClient('
|
|
104
|
+
* configurator.http.configureClient('catalog', (client) => {
|
|
84
105
|
* client.requestHandler.add('logger', (request) => console.log(request));
|
|
85
106
|
* });
|
|
86
107
|
* ```
|
|
87
108
|
*/
|
|
88
109
|
configureClient<T extends TClient>(name: string, onCreate: (client: T) => void): HttpClientConfigurator<TClient>;
|
|
89
110
|
/**
|
|
90
|
-
*
|
|
111
|
+
* Checks whether a named client configuration exists.
|
|
112
|
+
* @param name - The client key to check.
|
|
113
|
+
* @returns `true` when a configuration exists for the key.
|
|
91
114
|
*/
|
|
92
115
|
hasClient(name: string): boolean;
|
|
93
116
|
}
|
|
94
117
|
/** @inheritdoc */
|
|
95
118
|
export declare class HttpClientConfigurator<TClient extends IHttpClient> implements IHttpClientConfigurator<TClient> {
|
|
96
119
|
protected _clients: Record<string, HttpClientOptions<TClient>>;
|
|
97
|
-
/**
|
|
120
|
+
/** Gets a shallow clone of all named client configurations. */
|
|
98
121
|
get clients(): Record<string, HttpClientOptions<TClient>>;
|
|
99
|
-
/**
|
|
122
|
+
/** Default constructor used when a client configuration does not provide `ctor`. */
|
|
100
123
|
readonly defaultHttpClientCtor: HttpClientConstructor<TClient>;
|
|
101
|
-
/**
|
|
124
|
+
/** Default request handler pipeline cloned into each created client instance. */
|
|
102
125
|
readonly defaultHttpRequestHandler: HttpRequestHandler<HttpClientRequestInitType<TClient>>;
|
|
103
126
|
/**
|
|
104
|
-
*
|
|
105
|
-
* @param client
|
|
127
|
+
* Creates a configurator with the default client constructor.
|
|
128
|
+
* @param client - The default client constructor used when `ctor` is not configured per client.
|
|
106
129
|
*/
|
|
107
130
|
constructor(client: HttpClientConstructor<TClient>);
|
|
108
131
|
/** @inheritdoc */
|
|
@@ -126,15 +126,15 @@ export declare class HttpClient<TRequest extends FetchRequest = FetchRequest, TR
|
|
|
126
126
|
* @returns A `StreamResponse` that emits `ServerSentEvent<T>` objects as they are received from the server.
|
|
127
127
|
*
|
|
128
128
|
* @example
|
|
129
|
-
* const sse$ = httpClient.sse(
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
129
|
+
* const sse$ = httpClient.sse$(
|
|
130
|
+
* '/events',
|
|
131
|
+
* { method: 'POST', body: JSON.stringify({ prompt: 'tell me a joke' }) },
|
|
132
|
+
* { eventFilter: ['message'] },
|
|
133
133
|
* );
|
|
134
134
|
* sse$.subscribe({
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
135
|
+
* next: (event) => console.log(event),
|
|
136
|
+
* error: (err) => console.error(err),
|
|
137
|
+
* complete: () => console.log('Completed'),
|
|
138
138
|
* });
|
|
139
139
|
*/
|
|
140
140
|
sse$<T = unknown>(path: string, args?: FetchRequestInit<ServerSentEvent<T>, TRequest, TResponse> | null, options?: Omit<SseSelectorOptions<T>, 'abortSignal'>): StreamResponse<ServerSentEvent<T>>;
|
|
@@ -3,4 +3,5 @@ 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
|
export * from './types';
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
export { jsonSelector } from './json-selector';
|
|
2
2
|
export { blobSelector } from './blob-selector';
|
|
3
|
+
export { createSseSelector } from './sse-selector';
|
|
3
4
|
export type { ResponseSelector } from '../client/types';
|
|
5
|
+
export type { DataParser, ServerSentEvent, SseSelector, SseSelectorOptions, } from './sse-selector';
|
package/dist/types/module.d.ts
CHANGED
|
@@ -23,23 +23,32 @@ export type HttpMsalModule = Module<'http', IHttpClientProvider<HttpClientMsal>,
|
|
|
23
23
|
MsalModule
|
|
24
24
|
]>;
|
|
25
25
|
/**
|
|
26
|
-
* HTTP module
|
|
26
|
+
* Default HTTP module definition for Fusion Framework applications.
|
|
27
|
+
*
|
|
28
|
+
* The module uses `HttpClientMsal` as the default client implementation and,
|
|
29
|
+
* when the auth module is available, installs a request handler that can acquire
|
|
30
|
+
* bearer tokens for scoped requests.
|
|
27
31
|
*/
|
|
28
32
|
export declare const module: HttpMsalModule;
|
|
29
33
|
/**
|
|
30
|
-
*
|
|
34
|
+
* Creates a module configurator for the HTTP module.
|
|
35
|
+
*
|
|
36
|
+
* Use this when you need lower-level access to the module configuration callback,
|
|
37
|
+
* for example when registering several named clients in one setup step.
|
|
38
|
+
*
|
|
39
|
+
* @param configure - The callback that receives the HTTP module configuration.
|
|
40
|
+
* @returns A module configurator for the HTTP module.
|
|
31
41
|
*/
|
|
32
42
|
export declare const configureHttp: <TRef = unknown>(configure: (config: ModuleConfigType<HttpMsalModule>, ref?: TRef) => void) => IModuleConfigurator<HttpMsalModule, TRef>;
|
|
33
43
|
/**
|
|
34
|
-
*
|
|
44
|
+
* Creates a module configurator that registers one named HTTP client.
|
|
35
45
|
*
|
|
36
|
-
* This
|
|
37
|
-
*
|
|
38
|
-
* and returns a module configurator that can be used to configure the HTTP module.
|
|
46
|
+
* This is the convenience API for the common case where a setup step only needs to
|
|
47
|
+
* register one client with `baseUri`, `defaultScopes`, handlers, or a custom constructor.
|
|
39
48
|
*
|
|
40
|
-
* @param name - The
|
|
41
|
-
* @param args - The
|
|
42
|
-
* @returns A module configurator that
|
|
49
|
+
* @param name - The client key used later with `createClient(name)`.
|
|
50
|
+
* @param args - The named client configuration.
|
|
51
|
+
* @returns A module configurator that registers the named client.
|
|
43
52
|
*/
|
|
44
53
|
export declare const configureHttpClient: <TRef = unknown>(name: string, args: HttpClientOptions<HttpClientMsal>) => IModuleConfigurator<HttpMsalModule, TRef>;
|
|
45
54
|
/**
|
package/dist/types/provider.d.ts
CHANGED
|
@@ -4,15 +4,19 @@ import type { IHttpRequestHandler } from './lib/operators';
|
|
|
4
4
|
import type { IHttpClient } from './lib/client';
|
|
5
5
|
import { BaseModuleProvider } from '@equinor/fusion-framework-module/provider';
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
7
|
+
* Thrown when `createClient(name)` is called with an unknown client key.
|
|
8
8
|
*
|
|
9
|
-
* This
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* @extends {Error}
|
|
9
|
+
* This is only used when the provided string is neither a registered client name
|
|
10
|
+
* nor an absolute `http:` or `https:` URL.
|
|
13
11
|
*/
|
|
14
12
|
export declare class ClientNotFoundException extends Error {
|
|
15
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Creates fresh HTTP client instances from named or ad-hoc configuration.
|
|
16
|
+
*
|
|
17
|
+
* A provider can create clients from registered names, inline `HttpClientOptions`,
|
|
18
|
+
* or absolute URLs treated as ad-hoc `baseUri` values.
|
|
19
|
+
*/
|
|
16
20
|
export interface IHttpClientProvider<TClient extends IHttpClient = IHttpClient> {
|
|
17
21
|
/**
|
|
18
22
|
* The default HTTP request handler used by the HttpClientProvider.
|
|
@@ -33,13 +37,17 @@ export interface IHttpClientProvider<TClient extends IHttpClient = IHttpClient>
|
|
|
33
37
|
createClient(key: string): TClient;
|
|
34
38
|
createClient(key: HttpClientOptions<TClient>): TClient;
|
|
35
39
|
/**
|
|
36
|
-
*
|
|
40
|
+
* Creates a client instance and casts it to a custom HTTP client type.
|
|
41
|
+
*
|
|
42
|
+
* This is most useful when the named client configuration uses a custom `ctor`
|
|
43
|
+
* that extends `HttpClient` with domain-specific methods.
|
|
37
44
|
* @example
|
|
38
45
|
* ```ts
|
|
39
|
-
* config.http.configureClient('foobar',
|
|
40
|
-
*
|
|
41
|
-
*
|
|
46
|
+
* config.http.configureClient('foobar', {
|
|
47
|
+
* ctor: MyClient,
|
|
48
|
+
* baseUri: 'https://foobar.com',
|
|
42
49
|
* });
|
|
50
|
+
* ```
|
|
43
51
|
*/
|
|
44
52
|
createCustomClient<T extends HttpClient>(key: string): T;
|
|
45
53
|
}
|
|
@@ -62,7 +70,7 @@ export declare class HttpClientProvider<TClient extends IHttpClient = IHttpClien
|
|
|
62
70
|
*/
|
|
63
71
|
hasClient(key: string): boolean;
|
|
64
72
|
/**
|
|
65
|
-
* Creates a
|
|
73
|
+
* Creates a fresh HTTP client instance from a named or ad-hoc configuration.
|
|
66
74
|
*
|
|
67
75
|
* @param keyOrConfig - The key or configuration object for the HTTP client.
|
|
68
76
|
* @returns The created HTTP client instance.
|
|
@@ -72,18 +80,14 @@ export declare class HttpClientProvider<TClient extends IHttpClient = IHttpClien
|
|
|
72
80
|
* If a string is provided, it is treated as the key for a pre-configured client in the `HttpClientProvider`.
|
|
73
81
|
* If an `HttpClientOptions` object is provided, it is used as the configuration for the new client instance.
|
|
74
82
|
*
|
|
75
|
-
* The method
|
|
76
|
-
*
|
|
77
|
-
* - `defaultScopes`: The default scopes to be used for authentication.
|
|
78
|
-
* - `onCreate`: An optional callback function that is called when the client instance is created.
|
|
79
|
-
* - `ctor`: The constructor function for the HTTP client, defaulting to the configured `defaultHttpClientCtor`.
|
|
80
|
-
* - `requestHandler`: The HTTP request handler to be used by the client, defaulting to the `defaultHttpRequestHandler`.
|
|
83
|
+
* The method applies `baseUri`, `defaultScopes`, `requestHandler`, `responseHandler`,
|
|
84
|
+
* a custom `ctor` when configured, and finally runs `onCreate` for the newly created instance.
|
|
81
85
|
*
|
|
82
86
|
* The created HTTP client instance is returned.
|
|
83
87
|
*/
|
|
84
88
|
createClient(keyOrConfig: string | HttpClientOptions<TClient>): TClient;
|
|
85
89
|
/**
|
|
86
|
-
* Creates a
|
|
90
|
+
* Creates a client instance and returns it as the requested custom client type.
|
|
87
91
|
*
|
|
88
92
|
* @param key - The key of the pre-configured HTTP client to create.
|
|
89
93
|
* @returns The created HTTP client instance, cast to the specified type `T`.
|
|
@@ -98,8 +102,9 @@ export declare class HttpClientProvider<TClient extends IHttpClient = IHttpClien
|
|
|
98
102
|
/**
|
|
99
103
|
* Resolves the configuration for an HTTP client based on the provided `keyOrConfig` parameter.
|
|
100
104
|
*
|
|
101
|
-
* If a string is provided, it is treated as
|
|
102
|
-
*
|
|
105
|
+
* If a string is provided, it is treated as either a pre-configured client key or,
|
|
106
|
+
* when it is an absolute `http:` or `https:` URL, as an ad-hoc `baseUri`.
|
|
107
|
+
* If an `HttpClientOptions` object is provided, it is used directly as the configuration for the new client instance.
|
|
103
108
|
*
|
|
104
109
|
* @param keyOrConfig - The key or configuration object for the HTTP client.
|
|
105
110
|
* @returns The resolved HTTP client configuration.
|
package/dist/types/version.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const version = "
|
|
1
|
+
export declare const version = "8.0.0";
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Configure HTTP Clients
|
|
2
|
+
|
|
3
|
+
Use HTTP client configuration when your application talks to the same backend more than once and you want one place to define base URLs, MSAL scopes, headers, and transport behavior.
|
|
4
|
+
|
|
5
|
+
The main configuration APIs are:
|
|
6
|
+
|
|
7
|
+
- `configureHttpClient(name, options)` for one named client
|
|
8
|
+
- `configureHttp(...)` for lower-level module configuration
|
|
9
|
+
- `createClient(name)` to create a fresh client instance from a named configuration
|
|
10
|
+
- `createCustomClient<T>(name)` to create a custom client class with the same configuration
|
|
11
|
+
|
|
12
|
+
## Named Clients vs Ad-Hoc Clients
|
|
13
|
+
|
|
14
|
+
| Use case | API | Why |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| One backend used throughout the app | `configureHttpClient(name, options)` | Keeps base URL, scopes, and shared behavior in one place |
|
|
17
|
+
| Several clients configured together | `configureHttp(...)` | Useful when a module or setup step owns multiple backends |
|
|
18
|
+
| One-off call to a specific base URL | `createClient({ baseUri })` | Useful for inline or temporary configuration |
|
|
19
|
+
| One-off call to an absolute URL | `createClient('https://api.example.com')` | Useful when the URL itself is the configuration |
|
|
20
|
+
|
|
21
|
+
> **Fusion app developers:** Higher-level frameworks like `@equinor/fusion-framework-app` can auto-register named clients from other sources (e.g. application config endpoints or service discovery) before your code runs. See the [`@equinor/fusion-framework-app` README](../../app/README.md) for details on how clients are registered at the app level and which source takes priority.
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
configurator.configureHttpClient('catalog', {
|
|
27
|
+
baseUri: '/api/catalog',
|
|
28
|
+
defaultScopes: ['api://catalog-api/.default'],
|
|
29
|
+
onCreate: (client) => {
|
|
30
|
+
client.requestHandler.setHeader('X-App-Name', 'portal');
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const client = framework.modules.http.createClient('catalog');
|
|
35
|
+
const items = await client.json('/items');
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
This pattern is the normal entry point for application code.
|
|
39
|
+
|
|
40
|
+
## Configuration Options
|
|
41
|
+
|
|
42
|
+
| Option | What it controls |
|
|
43
|
+
| --- | --- |
|
|
44
|
+
| `baseUri` | Base URL used to resolve request paths. Relative values are resolved against `window.location.origin`. |
|
|
45
|
+
| `defaultScopes` | Default MSAL scopes used by `HttpClientMsal` requests. |
|
|
46
|
+
| `ctor` | Custom client constructor when you want domain-specific client methods. |
|
|
47
|
+
| `onCreate` | Callback that runs for every newly created client instance. |
|
|
48
|
+
| `requestHandler` | Initial request handler pipeline cloned into created clients. |
|
|
49
|
+
| `responseHandler` | Initial response handler pipeline passed into created clients. |
|
|
50
|
+
|
|
51
|
+
## Use `onCreate` For Shared Client Behavior
|
|
52
|
+
|
|
53
|
+
`onCreate` is the main place to attach behavior that every fresh client instance should start with.
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
configurator.configureHttpClient('catalog', {
|
|
57
|
+
baseUri: '/api/catalog',
|
|
58
|
+
onCreate: (client) => {
|
|
59
|
+
client.requestHandler.setHeader('X-Feature', 'catalog');
|
|
60
|
+
client.responseHandler.add('reject-unauthorized', (response) => {
|
|
61
|
+
if (response.status === 401) {
|
|
62
|
+
throw new Error('Unauthorized');
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Use this for headers, logging, validation, transport guards, and response policies.
|
|
70
|
+
|
|
71
|
+
## MSAL Scope Behavior
|
|
72
|
+
|
|
73
|
+
When the auth module is available, the HTTP module can acquire an access token before sending the request.
|
|
74
|
+
|
|
75
|
+
- `defaultScopes` come from the configured client
|
|
76
|
+
- per-request `scopes` are appended to `defaultScopes`
|
|
77
|
+
- token acquisition only happens when the final scope list is non-empty
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
await client.json('/items', {
|
|
81
|
+
scopes: ['api://catalog-admin/.default'],
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
That request uses both the configured `defaultScopes` and the request-specific scopes.
|
|
86
|
+
|
|
87
|
+
## Direct Module Integration
|
|
88
|
+
|
|
89
|
+
If you are configuring modules directly instead of using higher-level framework helpers, use the package exports.
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import {
|
|
93
|
+
configureHttp,
|
|
94
|
+
configureHttpClient,
|
|
95
|
+
} from '@equinor/fusion-framework-module-http';
|
|
96
|
+
|
|
97
|
+
configurator.addConfig(configureHttpClient('catalog', {
|
|
98
|
+
baseUri: '/api/catalog',
|
|
99
|
+
}));
|
|
100
|
+
|
|
101
|
+
configurator.addConfig(configureHttp((http) => {
|
|
102
|
+
http.configureClient('search', {
|
|
103
|
+
baseUri: '/api/search',
|
|
104
|
+
});
|
|
105
|
+
}));
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Use `configureHttpClient(...)` when you only need one named client. Use `configureHttp(...)` when a module setup step owns several client registrations.
|
|
109
|
+
|
|
110
|
+
## Ad-Hoc Clients
|
|
111
|
+
|
|
112
|
+
The provider also supports one-off client creation without a named configuration.
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
const inlineClient = framework.modules.http.createClient({
|
|
116
|
+
baseUri: '/api/search',
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const urlClient = framework.modules.http.createClient('https://api.example.com');
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
If the string passed to `createClient()` is not a configured key but is an absolute `http:` or `https:` URL, the provider treats it as a `baseUri`.
|
|
123
|
+
|
|
124
|
+
## Custom Client Classes
|
|
125
|
+
|
|
126
|
+
Use `ctor` when you want to wrap the shared transport behavior in domain-specific methods.
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
import { HttpClient } from '@equinor/fusion-framework-module-http/client';
|
|
130
|
+
|
|
131
|
+
class ApiClient extends HttpClient {
|
|
132
|
+
getHealth(): Promise<{ status: string }> {
|
|
133
|
+
return this.json('/health');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
configurator.configureHttpClient('api', {
|
|
138
|
+
baseUri: '/api',
|
|
139
|
+
ctor: ApiClient,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const client = framework.modules.http.createCustomClient<ApiClient>('api');
|
|
143
|
+
const health = await client.getHealth();
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## What `createClient()` Does
|
|
147
|
+
|
|
148
|
+
Each call to `createClient()` creates a fresh client instance.
|
|
149
|
+
|
|
150
|
+
- the client starts with its configured `baseUri`
|
|
151
|
+
- `defaultScopes` are assigned to the client instance
|
|
152
|
+
- `requestHandler` and `responseHandler` configuration is applied to that instance
|
|
153
|
+
- `onCreate` runs for that specific instance
|
|
154
|
+
|
|
155
|
+
This matters because headers and handlers added to one created client do not leak into the next created client from the same named configuration.
|
|
156
|
+
|
|
157
|
+
## Missing Clients And Safe Lookup
|
|
158
|
+
|
|
159
|
+
Use `hasClient(name)` when a client configuration may be optional.
|
|
160
|
+
|
|
161
|
+
```typescript
|
|
162
|
+
if (framework.modules.http.hasClient('catalog')) {
|
|
163
|
+
const client = framework.modules.http.createClient('catalog');
|
|
164
|
+
await client.json('/items');
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
If you call `createClient(name)` with an unknown key, the provider throws `ClientNotFoundException`.
|
|
169
|
+
|
|
170
|
+
## Rule Of Thumb
|
|
171
|
+
|
|
172
|
+
- use named clients for stable backends
|
|
173
|
+
- use `onCreate` for shared transport behavior
|
|
174
|
+
- use `ctor` when you want domain-specific methods
|
|
175
|
+
- use ad-hoc clients only when the configuration is truly local
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# Observable Patterns
|
|
2
|
+
|
|
3
|
+
The observable API exposes the same HTTP transport pipeline as the promise API, but in a form that composes naturally with RxJS.
|
|
4
|
+
|
|
5
|
+
Use `fetch$()`, `json$()`, `blob$()`, and `sse$()` when your request depends on other streams, needs cancellation by unsubscribe, or should participate in a larger RxJS workflow.
|
|
6
|
+
|
|
7
|
+
## Promise Methods vs Observable Methods
|
|
8
|
+
|
|
9
|
+
| Need | Recommended API |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| One request and one result | `fetch()` or `json()` |
|
|
12
|
+
| RxJS composition | `fetch$()` or `json$()` |
|
|
13
|
+
| Automatic cancellation when input changes | `fetch$()` or `json$()` with `switchMap` |
|
|
14
|
+
| Observing transport activity | `request$`, `response$`, and the observable request methods |
|
|
15
|
+
| Streaming server events | `sse$()` |
|
|
16
|
+
|
|
17
|
+
Promise methods are implemented with `firstValueFrom(...)` on the corresponding observable method. That means both forms share the same underlying request pipeline.
|
|
18
|
+
|
|
19
|
+
## Core Observable Behavior
|
|
20
|
+
|
|
21
|
+
- observable request methods are cold
|
|
22
|
+
- nothing is sent until you subscribe
|
|
23
|
+
- unsubscribing aborts the underlying fetch
|
|
24
|
+
- `client.abort()` cancels every in-flight request started by that client instance
|
|
25
|
+
- `request$` and `response$` are per-client streams, not global streams
|
|
26
|
+
|
|
27
|
+
## Pattern: Request From Another Stream
|
|
28
|
+
|
|
29
|
+
Use `switchMap` when a new upstream value should replace the previous in-flight request.
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { switchMap } from 'rxjs/operators';
|
|
33
|
+
|
|
34
|
+
const item$ = selectedId$.pipe(
|
|
35
|
+
switchMap((id) => client.json$(`/items/${id}`)),
|
|
36
|
+
);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
This pattern works well for route parameters, selected rows, debounced search terms, and refresh triggers.
|
|
40
|
+
|
|
41
|
+
## Pattern: Observe Transport Activity
|
|
42
|
+
|
|
43
|
+
Each client instance exposes `request$` and `response$` so you can observe transport behavior alongside your result stream.
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
const requestLog = client.request$.subscribe((request) => {
|
|
47
|
+
console.debug('request', request.method, request.uri);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const responseLog = client.response$.subscribe((response) => {
|
|
51
|
+
console.debug('response', response.status, response.url);
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Use these streams for diagnostics, telemetry, debugging operators, or building test helpers.
|
|
56
|
+
|
|
57
|
+
## Pattern: Cancel By Unsubscribing
|
|
58
|
+
|
|
59
|
+
Observable requests are a good fit for component and service lifecycles because cancellation is built in.
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
const subscription = client.json$('/items').subscribe({
|
|
63
|
+
next: console.log,
|
|
64
|
+
error: console.error,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
subscription.unsubscribe();
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
After unsubscribe, the client aborts the underlying fetch.
|
|
71
|
+
|
|
72
|
+
## Pattern: Reuse Parsing With Selectors
|
|
73
|
+
|
|
74
|
+
Selectors work well with observable request methods because they separate transport from parsing.
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
const user$ = client.fetch$('/users/42', {
|
|
78
|
+
selector: userSelector,
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
This lets you keep parsing logic reusable while still composing the request with RxJS operators.
|
|
83
|
+
|
|
84
|
+
## Pattern: Use The Right Layer For The Job
|
|
85
|
+
|
|
86
|
+
For most advanced flows, the HTTP module works best when each layer has one responsibility.
|
|
87
|
+
|
|
88
|
+
- use `fetch$()` or `json$()` for transport
|
|
89
|
+
- use selectors for response parsing
|
|
90
|
+
- use request and response handlers for cross-cutting transport behavior
|
|
91
|
+
- use RxJS operators for orchestration and composition
|
|
92
|
+
|
|
93
|
+
## Common Observable Workflows
|
|
94
|
+
|
|
95
|
+
- a route parameter stream drives `json$()` with `switchMap`
|
|
96
|
+
- a user action stream triggers `fetch$()` and `catchError`
|
|
97
|
+
- a request is observed through `request$` for telemetry
|
|
98
|
+
- a response is observed through `response$` for diagnostics
|
|
99
|
+
- an SSE endpoint uses `sse$()` for incremental updates
|
|
100
|
+
|
|
101
|
+
## Rule Of Thumb
|
|
102
|
+
|
|
103
|
+
Start with `json()` if you just need the result of one request. Move to `json$()` or `fetch$()` when the request is part of a reactive workflow.
|