@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
@@ -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
- * Represents the options for configuring an `IHttpClient` instance.
28
+ * Configures how the provider creates a named `IHttpClient` instance.
26
29
  *
27
- * @template TClient - The type of the `IHttpClient` instance to be configured.
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<HttpClientRequestInitType<TClient>>;
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
- * Instance for configuring http client
53
- * @template TClient base type of client that the provider will create
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
- * Configure a client with arguments
61
- * @param name name of the client
62
- * @param args option that are used cor creating a client
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('foo',{
66
- * baseUri: 'https://foo.bar',
67
- * defaultScopes: ['foobar/.default']
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
- * Configure a simple client by name to an endpoint
74
- * @param name name of the client
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
- * Creates a client with callback configuration
80
- * @param name name of the client
81
- * @param onCreate callback when a client is created
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('foo',(client) => {
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
- * Check if there is a configuration for provided name
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
- /** Get a clone of all configured clients */
120
+ /** Gets a shallow clone of all named client configurations. */
98
121
  get clients(): Record<string, HttpClientOptions<TClient>>;
99
- /** default class for creation of http clients */
122
+ /** Default constructor used when a client configuration does not provide `ctor`. */
100
123
  readonly defaultHttpClientCtor: HttpClientConstructor<TClient>;
101
- /** default request handler for http clients, applied on creation */
124
+ /** Default request handler pipeline cloned into each created client instance. */
102
125
  readonly defaultHttpRequestHandler: HttpRequestHandler<HttpClientRequestInitType<TClient>>;
103
126
  /**
104
- * Create a instance of http configuration
105
- * @param client defaultHttpRequestHandler
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
- * '/events',
131
- * { method: 'POST', body: JSON.stringify({ prompt: 'tell me a joke' }) },
132
- * { eventFilter: ['message'] }
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
- * next: (event) => console.log(event),
136
- * error: (err) => console.error(err),
137
- * complete: () => console.log('Completed'),
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';
@@ -23,23 +23,32 @@ export type HttpMsalModule = Module<'http', IHttpClientProvider<HttpClientMsal>,
23
23
  MsalModule
24
24
  ]>;
25
25
  /**
26
- * HTTP module with MSAL authentication.
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
- * Configures the HTTP module with MSAL authentication.
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
- * Configures the HTTP client with MSAL authentication.
44
+ * Creates a module configurator that registers one named HTTP client.
35
45
  *
36
- * This function creates a module configurator that can be used to configure the HTTP module
37
- * with MSAL authentication. The configurator takes a name and a set of HTTP client options,
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 name of the HTTP client configuration.
41
- * @param args - The HTTP client options, including the MSAL configuration.
42
- * @returns A module configurator that can be used to configure the HTTP module.
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
  /**
@@ -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
- * Exception thrown when a client cannot be found.
7
+ * Thrown when `createClient(name)` is called with an unknown client key.
8
8
  *
9
- * This error is typically used to indicate that a requested client instance
10
- * does not exist or cannot be located within the current context.
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
- * Class cast creation of custom client
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', (client) => {
40
- * client.ctor = MyClient;
41
- * client.uri = 'https://foobar.com';
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 new HTTP client instance with the specified configuration.
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 sets up the HTTP client with the following options:
76
- * - `baseUri`: The base URI for the HTTP client.
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 new HTTP client instance with the specified configuration.
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 the key for a pre-configured client in the `HttpClientProvider`.
102
- * If an `HttpClientOptions` object is provided, it is used as the configuration for the new client instance.
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.
@@ -1 +1 @@
1
- export declare const version = "7.0.8";
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.