@equinor/fusion-framework-module-context 9.0.0-next.0 → 9.0.1
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/dist/esm/version.js +1 -1
- package/dist/esm/version.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +13 -10
- package/CHANGELOG.md +0 -1095
- package/docs/data-model.md +0 -134
- package/docs/lifecycle.md +0 -163
- package/docs/recipes.md +0 -89
- package/src/ContextModuleConfig.ts +0 -147
- package/src/ContextModuleConfigurator.interface.ts +0 -145
- package/src/ContextModuleConfigurator.ts +0 -295
- package/src/ContextProvider.ts +0 -1158
- package/src/__tests__/ContextModuleConfigurator.test.ts +0 -412
- package/src/__tests__/mock/context-mock.test.ts +0 -137
- package/src/__tests__/mock/create-context-item-factory.test.ts +0 -60
- package/src/__tests__/mock/create-context-items.test.ts +0 -58
- package/src/client/ContextClient.ts +0 -149
- package/src/errors/FusionContextSearchError.ts +0 -56
- package/src/errors/index.ts +0 -1
- package/src/index.ts +0 -29
- package/src/mock/ContextMockConfigurator.ts +0 -244
- package/src/mock/fixtures/create-context-item-factory.ts +0 -62
- package/src/mock/fixtures/create-context-items.ts +0 -80
- package/src/mock/fixtures/index.ts +0 -28
- package/src/mock/fixtures/string-to-seed.ts +0 -18
- package/src/mock/index.ts +0 -33
- package/src/mock/module.ts +0 -54
- package/src/module.ts +0 -178
- package/src/selectors/get-context-selector.ts +0 -14
- package/src/selectors/index.ts +0 -12
- package/src/selectors/query-context-selector.ts +0 -15
- package/src/selectors/related-context-selector.ts +0 -15
- package/src/types.ts +0 -98
- package/src/utils/enable-context.ts +0 -40
- package/src/utils/extract-context-id-from-path.ts +0 -39
- package/src/utils/index.ts +0 -15
- package/src/utils/parse-context-item.ts +0 -39
- package/src/utils/resolve-context-from-path.ts +0 -118
- package/src/utils/resolve-initial-context.ts +0 -58
- package/src/version.ts +0 -2
- package/tsconfig.json +0 -33
- package/vitest.config.ts +0 -11
package/src/ContextProvider.ts
DELETED
|
@@ -1,1158 +0,0 @@
|
|
|
1
|
-
import { EMPTY, lastValueFrom, Observable, of, Subject, Subscription, throwError } from 'rxjs';
|
|
2
|
-
import {
|
|
3
|
-
catchError,
|
|
4
|
-
filter,
|
|
5
|
-
finalize,
|
|
6
|
-
map,
|
|
7
|
-
pairwise,
|
|
8
|
-
switchMap,
|
|
9
|
-
takeUntil,
|
|
10
|
-
tap,
|
|
11
|
-
} from 'rxjs/operators';
|
|
12
|
-
|
|
13
|
-
import type { ContextModuleConfig } from './ContextModuleConfig';
|
|
14
|
-
|
|
15
|
-
import { BaseModuleProvider } from '@equinor/fusion-framework-module/provider';
|
|
16
|
-
import { version } from './version.js';
|
|
17
|
-
|
|
18
|
-
import { ContextClient } from './client/ContextClient';
|
|
19
|
-
import type { ContextItem, QueryContextParameters, RelatedContextParameters } from './types';
|
|
20
|
-
import type { ModuleType } from '@equinor/fusion-framework-module';
|
|
21
|
-
import type {
|
|
22
|
-
EventModule,
|
|
23
|
-
FrameworkEvent,
|
|
24
|
-
FrameworkEventInit,
|
|
25
|
-
} from '@equinor/fusion-framework-module-event';
|
|
26
|
-
import Query from '@equinor/fusion-query';
|
|
27
|
-
import type { SemVer } from 'semver';
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Interface representing a provider for managing and interacting with context items within an application.
|
|
31
|
-
*
|
|
32
|
-
* The `IContextProvider` interface defines a contract for querying, validating, resolving, and managing the current context state.
|
|
33
|
-
* It supports both synchronous and asynchronous operations, as well as observable streams for reactive programming.
|
|
34
|
-
* This interface is intended to be implemented by modules that encapsulate context-related logic, such as user, tenant, or environment context.
|
|
35
|
-
*
|
|
36
|
-
* ## Core Responsibilities
|
|
37
|
-
* - **Querying Contexts:** Search and retrieve context items based on search criteria, both as observables and promises.
|
|
38
|
-
* - **Current Context Management:** Get, set, and clear the current context, with support for validation and resolution.
|
|
39
|
-
* - **Context Resolution:** Resolve context items to their full representation, synchronously or asynchronously.
|
|
40
|
-
* - **Related Contexts:** Retrieve related context items based on specific parameters.
|
|
41
|
-
* - **Path Utilities:** Extract context IDs from paths and generate paths from context items, supporting deep linking and routing.
|
|
42
|
-
* - **Reactive State:** Expose the current context as an observable stream for reactive UI updates.
|
|
43
|
-
*
|
|
44
|
-
* ## Usage Example
|
|
45
|
-
* ```ts
|
|
46
|
-
* const provider: IContextProvider = ...;
|
|
47
|
-
* provider.currentContext$.subscribe(ctx => {
|
|
48
|
-
* // React to context changes
|
|
49
|
-
* });
|
|
50
|
-
* const items = await provider.queryContextAsync('search-term');
|
|
51
|
-
* ```
|
|
52
|
-
*
|
|
53
|
-
* ## Notes
|
|
54
|
-
* - Some members are marked as **DANGER** and are intended for advanced or internal use only.
|
|
55
|
-
* - Implementations should ensure thread safety and consistency of the current context state.
|
|
56
|
-
* - Optional methods for path extraction and generation enable integration with routing systems.
|
|
57
|
-
*
|
|
58
|
-
* @template T - The shape of the context item data.
|
|
59
|
-
*/
|
|
60
|
-
export interface IContextProvider {
|
|
61
|
-
/** DANGER */
|
|
62
|
-
readonly contextClient: ContextClient;
|
|
63
|
-
/** DANGER */
|
|
64
|
-
readonly queryClient: Query<ContextItem[], QueryContextParameters>;
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Observable stream emitting the current context item.
|
|
68
|
-
*
|
|
69
|
-
* - Emits `undefined` if the context has not been initialized.
|
|
70
|
-
* - Emits `null` when the current context is cleared.
|
|
71
|
-
* - Emits a `ContextItem` when a valid context is set.
|
|
72
|
-
*
|
|
73
|
-
* @example
|
|
74
|
-
* ```ts
|
|
75
|
-
* portal.context.currentContext$.subscribe(context => {
|
|
76
|
-
* if (context) {
|
|
77
|
-
* console.log('Current context:', context);
|
|
78
|
-
* } else if (context === null) {
|
|
79
|
-
* console.log('Current context cleared');
|
|
80
|
-
* }
|
|
81
|
-
* });
|
|
82
|
-
* ```
|
|
83
|
-
*/
|
|
84
|
-
readonly currentContext$: Observable<ContextItem | null | undefined>;
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Snapshot of the current context item.
|
|
88
|
-
*
|
|
89
|
-
* @remarks
|
|
90
|
-
* This property provides the current context item as a snapshot.
|
|
91
|
-
* It may be `null` or `undefined` if no context is set or if the context is not initialized.
|
|
92
|
-
* It is intended for synchronous access to the current context state.
|
|
93
|
-
*
|
|
94
|
-
* __Use {@link currentContext$} instead of this property to get the current context as an observable stream.__
|
|
95
|
-
*/
|
|
96
|
-
readonly currentContext: ContextItem | null | undefined;
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Queries the context items based on the provided search string.
|
|
100
|
-
*
|
|
101
|
-
* @example
|
|
102
|
-
* ```ts
|
|
103
|
-
* portal.context.queryContext('search-term').subscribe(contextItems => {
|
|
104
|
-
* console.log('Queried context items:', contextItems);
|
|
105
|
-
* });
|
|
106
|
-
* ```
|
|
107
|
-
*
|
|
108
|
-
* @param search The search string.
|
|
109
|
-
* @returns An observable that emits an array of context items.
|
|
110
|
-
*/
|
|
111
|
-
queryContext(search: string): Observable<Array<ContextItem>>;
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Queries the context items asynchronously based on the provided search string.
|
|
115
|
-
*
|
|
116
|
-
* @example
|
|
117
|
-
* ```ts
|
|
118
|
-
* portal.context.queryContextAsync('search-term').then(contextItems => {
|
|
119
|
-
* console.log('Queried context items:', contextItems);
|
|
120
|
-
* });
|
|
121
|
-
* ```
|
|
122
|
-
*
|
|
123
|
-
* @param search The search string.
|
|
124
|
-
* @returns A promise that resolves to an array of context items.
|
|
125
|
-
*/
|
|
126
|
-
queryContextAsync(search: string): Promise<Array<ContextItem>>;
|
|
127
|
-
|
|
128
|
-
/**
|
|
129
|
-
* Validates the given context item.
|
|
130
|
-
* This method is used to check if the context item meets the criteria defined in the provider's configuration.
|
|
131
|
-
*
|
|
132
|
-
* @example
|
|
133
|
-
* ```ts
|
|
134
|
-
* const currentContext = portal.context.currentContext;
|
|
135
|
-
* if(app.context.validateContext(currentContext)) {
|
|
136
|
-
* // the application can safely use the context item
|
|
137
|
-
* } else {
|
|
138
|
-
* // the context item is not valid, handle accordingly
|
|
139
|
-
* }
|
|
140
|
-
* ```
|
|
141
|
-
*
|
|
142
|
-
* @remarks
|
|
143
|
-
* This method will use the configured validation function to check if the context item is valid.
|
|
144
|
-
*
|
|
145
|
-
* @param item The context item to validate.
|
|
146
|
-
* @returns A boolean indicating whether the context item is valid or not.
|
|
147
|
-
*/
|
|
148
|
-
validateContext(item: ContextItem<Record<string, unknown>>): boolean;
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Resolves the context item as a stream.
|
|
152
|
-
*
|
|
153
|
-
* This method will try to resolve the context item based on the current context.
|
|
154
|
-
* This is useful when transferring context items between different parts of the application.
|
|
155
|
-
*
|
|
156
|
-
* @remarks
|
|
157
|
-
* A normal implementation of this method will first validate the context item and if it is not valid,
|
|
158
|
-
* it will use the {@link relatedContexts} method to find related context items.
|
|
159
|
-
*
|
|
160
|
-
* @example
|
|
161
|
-
* ```ts
|
|
162
|
-
* app.context.resolveContext(portal.context.currentContext).subscribe({
|
|
163
|
-
* next: (resolvedContext) => {
|
|
164
|
-
* // the application can safely use the resolved context item
|
|
165
|
-
* },
|
|
166
|
-
* error: (err) => {
|
|
167
|
-
* // handle error during context resolution
|
|
168
|
-
* },
|
|
169
|
-
* complete: () => {
|
|
170
|
-
* // context resolution completed
|
|
171
|
-
* }
|
|
172
|
-
* });
|
|
173
|
-
* ```
|
|
174
|
-
*
|
|
175
|
-
* @param current The current context item.
|
|
176
|
-
* @returns An observable that emits the resolved context item.
|
|
177
|
-
*/
|
|
178
|
-
resolveContext: (current: ContextItem) => Observable<ContextItem>;
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Resolves the context item asynchronously.
|
|
182
|
-
*
|
|
183
|
-
* @see {@link resolveContext} for more details.
|
|
184
|
-
*
|
|
185
|
-
* @param current The current context item.
|
|
186
|
-
* @returns A promise that resolves to the resolved context item.
|
|
187
|
-
*/
|
|
188
|
-
resolveContextAsync: (current: ContextItem) => Promise<ContextItem>;
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Retrieves the related context items based on the provided parameters.
|
|
192
|
-
*
|
|
193
|
-
* @param args The parameters for retrieving related context items.
|
|
194
|
-
* @returns An observable that emits an array of related context items.
|
|
195
|
-
*/
|
|
196
|
-
relatedContexts: (
|
|
197
|
-
args: RelatedContextParameters,
|
|
198
|
-
) => Observable<Array<ContextItem<Record<string, unknown>>>>;
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Retrieves the related context items asynchronously based on the provided parameters.
|
|
202
|
-
*
|
|
203
|
-
* @see {@link relatedContexts}
|
|
204
|
-
*
|
|
205
|
-
* @param args The parameters for retrieving related context items.
|
|
206
|
-
* @returns A promise that resolves to an array of related context items.
|
|
207
|
-
*/
|
|
208
|
-
relatedContextsAsync: (
|
|
209
|
-
args: RelatedContextParameters,
|
|
210
|
-
) => Promise<Array<ContextItem<Record<string, unknown>>>>;
|
|
211
|
-
|
|
212
|
-
/**
|
|
213
|
-
* Clears the current context.
|
|
214
|
-
* This method will set the current context to `null`.
|
|
215
|
-
*/
|
|
216
|
-
clearCurrentContext: VoidFunction;
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Sets the current context item by its ID.
|
|
220
|
-
*
|
|
221
|
-
* @param id The ID of the context item.
|
|
222
|
-
* @returns An observable that emits the current context item.
|
|
223
|
-
*/
|
|
224
|
-
setCurrentContextById(id: string): Observable<ContextItem<Record<string, unknown>>>;
|
|
225
|
-
|
|
226
|
-
/**
|
|
227
|
-
* Sets the current context item by its ID asynchronously.
|
|
228
|
-
*
|
|
229
|
-
* @see {@link setCurrentContextById}
|
|
230
|
-
*
|
|
231
|
-
* @param id The ID of the context item.
|
|
232
|
-
* @returns A promise that resolves to the current context item.
|
|
233
|
-
*/
|
|
234
|
-
setCurrentContextByIdAsync(id: string): Promise<ContextItem<Record<string, unknown>>>;
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Sets the current context item.
|
|
238
|
-
*
|
|
239
|
-
* Optionally validates and resolves the context item based on the provided settings.
|
|
240
|
-
*
|
|
241
|
-
* @param context The context item to set as the current context.
|
|
242
|
-
* @param opt Optional settings for the operation.
|
|
243
|
-
* @param opt.validate Specifies whether to validate the context item. Default is `true`.
|
|
244
|
-
* @param opt.resolve Specifies whether to resolve the context item. Default is `true`.
|
|
245
|
-
* @returns An observable that emits the current context item or `null`.
|
|
246
|
-
*/
|
|
247
|
-
setCurrentContext(
|
|
248
|
-
context: ContextItem<Record<string, unknown>> | null,
|
|
249
|
-
opt?: { validate?: boolean; resolve?: boolean },
|
|
250
|
-
): Observable<ContextItem<Record<string, unknown>> | null>;
|
|
251
|
-
|
|
252
|
-
/**
|
|
253
|
-
* Sets the current context item asynchronously.
|
|
254
|
-
*
|
|
255
|
-
* @see {@link setCurrentContext}
|
|
256
|
-
*
|
|
257
|
-
* @param context The context item to set as the current context.
|
|
258
|
-
* @param opt Optional settings for the operation.
|
|
259
|
-
* @param opt.validate Specifies whether to validate the context item. Default is `true`.
|
|
260
|
-
* @param opt.resolve Specifies whether to resolve the context item. Default is `true`.
|
|
261
|
-
* @returns A promise that resolves to the current context item or `null`.
|
|
262
|
-
*/
|
|
263
|
-
setCurrentContextAsync(
|
|
264
|
-
context: ContextItem<Record<string, unknown>> | null,
|
|
265
|
-
opt?: { validate?: boolean; resolve?: boolean },
|
|
266
|
-
): Promise<ContextItem<Record<string, unknown>> | null>;
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
* Method for extracting context id from a path.
|
|
270
|
-
*
|
|
271
|
-
* @remarks
|
|
272
|
-
* This method extracts the context ID from a given path using the extraction method
|
|
273
|
-
* provided to the provider via the configuration. If no extraction method is configured,
|
|
274
|
-
* it returns undefined.
|
|
275
|
-
*
|
|
276
|
-
* @param path path to resolve context from
|
|
277
|
-
* @returns the resolved context item id
|
|
278
|
-
*
|
|
279
|
-
* @example
|
|
280
|
-
* ```ts
|
|
281
|
-
* // configured with extracting id from path like '/context/:id'
|
|
282
|
-
* provider.extractContextIdFromPath('/context/1234'); // returns '1234'
|
|
283
|
-
* ```
|
|
284
|
-
*/
|
|
285
|
-
extractContextIdFromPath?: (path: string) => string | undefined;
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Method for generating path from a context item.
|
|
289
|
-
*
|
|
290
|
-
* @remarks
|
|
291
|
-
* This method generates a path for the context item using the generation method
|
|
292
|
-
* provided to the provider via the configuration. If no generation method is configured,
|
|
293
|
-
* it returns undefined.
|
|
294
|
-
*
|
|
295
|
-
* @param context context item to generate path from
|
|
296
|
-
* @param path current path
|
|
297
|
-
* @returns path for the context item
|
|
298
|
-
*/
|
|
299
|
-
generatePathFromContext?: (context: ContextItem, path: string) => string | undefined;
|
|
300
|
-
/**
|
|
301
|
-
* The version of the context provider.
|
|
302
|
-
*
|
|
303
|
-
* @remarks
|
|
304
|
-
* This property represents the version of the context provider, which can be a string or a SemVer object.
|
|
305
|
-
*/
|
|
306
|
-
readonly version: string | SemVer;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Provides context management functionality, including querying, setting, validating, and resolving context items.
|
|
311
|
-
*
|
|
312
|
-
* The `ContextProvider` class acts as a central service for handling context state, supporting asynchronous operations,
|
|
313
|
-
* event-driven updates, and integration with parent/child context providers. It manages a queue for context changes,
|
|
314
|
-
* supports validation and resolution logic, and can interact with related context items.
|
|
315
|
-
*
|
|
316
|
-
* Key Features:
|
|
317
|
-
* - Maintains the current context and exposes it as both an observable and a property.
|
|
318
|
-
* - Allows querying for context items based on search criteria and filters.
|
|
319
|
-
* - Supports setting the current context by ID or by context item, with optional validation and resolution.
|
|
320
|
-
* - Handles context changes asynchronously, queuing tasks and supporting cancellation.
|
|
321
|
-
* - Integrates with an event module to dispatch and listen for context-related events.
|
|
322
|
-
* - Supports connecting to a parent context provider to synchronize context state.
|
|
323
|
-
* - Provides methods for resolving related contexts and validating context items.
|
|
324
|
-
* - Manages subscriptions and ensures proper resource cleanup via `dispose`.
|
|
325
|
-
*
|
|
326
|
-
* @template T The type of context item managed by the provider.
|
|
327
|
-
*
|
|
328
|
-
* @remarks
|
|
329
|
-
* - Some methods and properties are marked as deprecated and may be removed in future versions.
|
|
330
|
-
* - Event integration is optional and depends on the presence of an event module.
|
|
331
|
-
* - The provider is designed to be extensible and can be configured via the `ContextModuleConfig`.
|
|
332
|
-
*
|
|
333
|
-
* @example
|
|
334
|
-
* ```typescript
|
|
335
|
-
* const provider = new ContextProvider({ config: myConfig, event: myEventModule });
|
|
336
|
-
* provider.setCurrentContextById('context-id').subscribe(...);
|
|
337
|
-
* ```
|
|
338
|
-
*/
|
|
339
|
-
export class ContextProvider
|
|
340
|
-
extends BaseModuleProvider<ContextModuleConfig>
|
|
341
|
-
implements IContextProvider
|
|
342
|
-
{
|
|
343
|
-
#contextClient: ContextClient;
|
|
344
|
-
#contextQuery: Query<Array<ContextItem>, QueryContextParameters>;
|
|
345
|
-
#contextRelated?: Query<Array<ContextItem>, RelatedContextParameters>;
|
|
346
|
-
|
|
347
|
-
#event?: ModuleType<EventModule>;
|
|
348
|
-
|
|
349
|
-
#subscriptions = new Subscription();
|
|
350
|
-
|
|
351
|
-
#contextType?: ContextModuleConfig['contextType'];
|
|
352
|
-
#contextFilter: ContextModuleConfig['contextFilter'];
|
|
353
|
-
#contextParameterFn: Required<ContextModuleConfig>['contextParameterFn'];
|
|
354
|
-
|
|
355
|
-
#contextQueue = new Subject<Observable<ContextItem<Record<string, unknown>>>>();
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* The underlying context client used to resolve and hold the current context item.
|
|
359
|
-
* @returns The internal {@link ContextClient} instance.
|
|
360
|
-
*/
|
|
361
|
-
public get contextClient() {
|
|
362
|
-
return this.#contextClient;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* The query client used to search for context items.
|
|
367
|
-
* @returns The internal `Query` instance used by {@link queryContext}.
|
|
368
|
-
*/
|
|
369
|
-
public get queryClient() {
|
|
370
|
-
return this.#contextQuery;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
/**
|
|
374
|
-
* Observable stream emitting the current context item.
|
|
375
|
-
* @returns Observable that emits the current `ContextItem`, `null`, or `undefined`.
|
|
376
|
-
*/
|
|
377
|
-
get currentContext$(): Observable<ContextItem | null | undefined> {
|
|
378
|
-
return this.#contextClient.currentContext$;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* Snapshot of the current context item.
|
|
383
|
-
* @returns The current `ContextItem`, or `null`/`undefined` if not set.
|
|
384
|
-
*/
|
|
385
|
-
get currentContext(): ContextItem | undefined | null {
|
|
386
|
-
return this.#contextClient.currentContext;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
/**
|
|
390
|
-
* Sets the current context item.
|
|
391
|
-
* @deprecated do not use, will be removed
|
|
392
|
-
* @param context - The context item to set as current. Must not be `undefined`.
|
|
393
|
-
* @throws Error if `context` is `undefined`.
|
|
394
|
-
*/
|
|
395
|
-
set currentContext(context: ContextItem | null | undefined) {
|
|
396
|
-
console.warn(
|
|
397
|
-
'@deprecated',
|
|
398
|
-
'ContextProvider.currentContext',
|
|
399
|
-
'use setCurrentContextById|setCurrentContext|clearCurrentContext',
|
|
400
|
-
);
|
|
401
|
-
// undefined is reserved to mean "not yet initialized", so it cannot be set explicitly
|
|
402
|
-
if (context === undefined) {
|
|
403
|
-
throw Error('not allowed to set current context as undefined undefined!');
|
|
404
|
-
}
|
|
405
|
-
this.setCurrentContextAsync(context);
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* Creates a new instance of `ContextProvider`.
|
|
410
|
-
* @param args - Constructor arguments.
|
|
411
|
-
* @param args.config - The context module configuration.
|
|
412
|
-
* @param args.event - Optional event module instance for dispatching context change events.
|
|
413
|
-
* @param args.parentContext - Optional parent context provider. Deprecated, use {@link connectParentContext}.
|
|
414
|
-
*/
|
|
415
|
-
constructor(args: {
|
|
416
|
-
config: ContextModuleConfig;
|
|
417
|
-
event?: ModuleType<EventModule>;
|
|
418
|
-
/** @deprecated use ContextProvider.connectParentContext */
|
|
419
|
-
parentContext?: IContextProvider;
|
|
420
|
-
}) {
|
|
421
|
-
const { config, event } = args;
|
|
422
|
-
|
|
423
|
-
super({ version, config });
|
|
424
|
-
|
|
425
|
-
// warn about deprecated parentContext constructor arg
|
|
426
|
-
if (args.parentContext) {
|
|
427
|
-
console.warn(
|
|
428
|
-
'@deprecated',
|
|
429
|
-
'parentContext as arg is deprecated, use ContextProvider.connectParentContext',
|
|
430
|
-
);
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
this.#event = event;
|
|
434
|
-
|
|
435
|
-
// set the resolve and validate context functions
|
|
436
|
-
if (config.resolveContext) {
|
|
437
|
-
this.resolveContext = config.resolveContext?.bind(this);
|
|
438
|
-
}
|
|
439
|
-
// override validateContext if configured
|
|
440
|
-
if (config.validateContext) {
|
|
441
|
-
this.validateContext = config.validateContext?.bind(this);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
// override extractContextIdFromPath if configured
|
|
445
|
-
if (config.extractContextIdFromPath) {
|
|
446
|
-
// @ts-expect-error - this is to avoid breaking change, the signature will be updated in future major release
|
|
447
|
-
this.extractContextIdFromPath = config.extractContextIdFromPath;
|
|
448
|
-
}
|
|
449
|
-
// override generatePathFromContext if configured
|
|
450
|
-
if (config.generatePathFromContext) {
|
|
451
|
-
// @ts-expect-error - this is to avoid breaking change, the signature will be updated in future major release
|
|
452
|
-
this.generatePathFromContext = config.generatePathFromContext;
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
this.#contextType = config.contextType;
|
|
456
|
-
this.#contextFilter = config.contextFilter;
|
|
457
|
-
|
|
458
|
-
// create clients
|
|
459
|
-
this.#contextClient = new ContextClient(config.client.get);
|
|
460
|
-
this.#contextQuery = new Query(config.client.query);
|
|
461
|
-
|
|
462
|
-
// only create the related-context query when the config provides one
|
|
463
|
-
if (config.client.related) {
|
|
464
|
-
this.#contextRelated = new Query(config.client.related);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
// set the context parameter function
|
|
468
|
-
this.#contextParameterFn =
|
|
469
|
-
config.contextParameterFn ??
|
|
470
|
-
// fallback to default
|
|
471
|
-
((args: Parameters<Required<ContextModuleConfig>['contextParameterFn']>[0]) => ({
|
|
472
|
-
search: args.search,
|
|
473
|
-
filter: { type: args.type },
|
|
474
|
-
}));
|
|
475
|
-
|
|
476
|
-
// if event module is available, setup event listeners
|
|
477
|
-
if (this.#event) {
|
|
478
|
-
this.#subscriptions.add(
|
|
479
|
-
// observe current context changes
|
|
480
|
-
this.currentContext$
|
|
481
|
-
// emit previous and next context together for change comparisons
|
|
482
|
-
.pipe(pairwise())
|
|
483
|
-
.subscribe(([previous, next]) => {
|
|
484
|
-
this.#event?.dispatchEvent('onCurrentContextChanged', {
|
|
485
|
-
source: this,
|
|
486
|
-
canBubble: true,
|
|
487
|
-
detail: { previous, next },
|
|
488
|
-
});
|
|
489
|
-
}),
|
|
490
|
-
);
|
|
491
|
-
this.#subscriptions.add(
|
|
492
|
-
// observe current context changes from child modules
|
|
493
|
-
this.#event.addEventListener('onCurrentContextChanged', (e) => {
|
|
494
|
-
// prevent infinite loop, only set context if source is not this
|
|
495
|
-
if (e.source !== this && e.detail.next !== undefined) {
|
|
496
|
-
this.setCurrentContext(e.detail.next);
|
|
497
|
-
}
|
|
498
|
-
}),
|
|
499
|
-
);
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
// wire up context queue
|
|
503
|
-
this.#subscriptions.add(
|
|
504
|
-
this.#contextQueue
|
|
505
|
-
.pipe(
|
|
506
|
-
// resolve context item from queue
|
|
507
|
-
switchMap((next) => next),
|
|
508
|
-
)
|
|
509
|
-
.subscribe((context) => {
|
|
510
|
-
// set context from resolved context item from queue
|
|
511
|
-
this.#contextClient.setCurrentContext(context ?? null);
|
|
512
|
-
}),
|
|
513
|
-
);
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
* Connects this context provider to a parent context provider, subscribing to changes in the parent's context.
|
|
518
|
-
*
|
|
519
|
-
* When the parent context changes, this method will:
|
|
520
|
-
* - Optionally skip the first emitted value from the parent (if `opt.skipFirst` is true).
|
|
521
|
-
* - Only update the current context if the context ID has changed.
|
|
522
|
-
* - Dispatch an `onParentContextChanged` event before updating, allowing for cancellation.
|
|
523
|
-
* - Set the current context with validation and resolution, handling errors gracefully.
|
|
524
|
-
*
|
|
525
|
-
* The subscription is automatically managed and will be cleaned up with the provider.
|
|
526
|
-
*
|
|
527
|
-
* @param provider - The parent context provider to connect to.
|
|
528
|
-
* @param opt - Optional settings.
|
|
529
|
-
* @param opt.skipFirst - If true, skips the first emitted value from the parent context.
|
|
530
|
-
* @returns A `Subscription` object representing the connection to the parent context.
|
|
531
|
-
*/
|
|
532
|
-
public connectParentContext(
|
|
533
|
-
provider: IContextProvider,
|
|
534
|
-
opt?: { skipFirst: boolean },
|
|
535
|
-
): Subscription {
|
|
536
|
-
// build a stream of validated context changes from the parent provider
|
|
537
|
-
const parentContext$ = provider.currentContext$.pipe(
|
|
538
|
-
// do not set context if parent has not initialized
|
|
539
|
-
filter((x): x is ContextItem | null => x !== undefined),
|
|
540
|
-
filter((next, index) => {
|
|
541
|
-
// skip first item if opt.skipFirst is true
|
|
542
|
-
// TODO(#5121): this is a bit hacky, should be handled in a better way
|
|
543
|
-
if (opt?.skipFirst && index <= 1) {
|
|
544
|
-
console.debug('ContextProvider::connectParentContext', 'skipping first item', next);
|
|
545
|
-
return false;
|
|
546
|
-
}
|
|
547
|
-
// only set context if it has changed
|
|
548
|
-
return this.currentContext?.id !== next?.id;
|
|
549
|
-
}),
|
|
550
|
-
switchMap(async (next) => {
|
|
551
|
-
// if parent context is null, just return
|
|
552
|
-
if (!next) {
|
|
553
|
-
return { next };
|
|
554
|
-
}
|
|
555
|
-
// notify event observers that parent context is about to change and await for cancelation
|
|
556
|
-
const onParentContextChanged = await this.#event?.dispatchEvent('onParentContextChanged', {
|
|
557
|
-
source: this,
|
|
558
|
-
detail: next,
|
|
559
|
-
cancelable: true,
|
|
560
|
-
});
|
|
561
|
-
return { next, canceled: onParentContextChanged?.canceled };
|
|
562
|
-
}),
|
|
563
|
-
// filter out canceled context changes
|
|
564
|
-
filter((x) => !x.canceled),
|
|
565
|
-
switchMap(({ next }) => {
|
|
566
|
-
// set current context with validation and resolution
|
|
567
|
-
return (
|
|
568
|
-
this.setCurrentContext(next, {
|
|
569
|
-
validate: true,
|
|
570
|
-
resolve: true,
|
|
571
|
-
})
|
|
572
|
-
// swallow errors so a failed context change doesn't break the parent subscription
|
|
573
|
-
.pipe(
|
|
574
|
-
catchError((err) => {
|
|
575
|
-
console.warn('ContextProvider::onParentContextChanged', 'setCurrentContext', err);
|
|
576
|
-
// do not emit any value if an error occurs
|
|
577
|
-
return EMPTY;
|
|
578
|
-
}),
|
|
579
|
-
)
|
|
580
|
-
);
|
|
581
|
-
}),
|
|
582
|
-
catchError((err) => {
|
|
583
|
-
console.warn('ContextProvider::onParentContextChanged', 'unhandled exception', err);
|
|
584
|
-
// do not emit any value if an error occurs
|
|
585
|
-
return EMPTY;
|
|
586
|
-
}),
|
|
587
|
-
);
|
|
588
|
-
|
|
589
|
-
// subscribe to parent context changes
|
|
590
|
-
const subscription = parentContext$.subscribe();
|
|
591
|
-
|
|
592
|
-
// add subscription to internal teardown
|
|
593
|
-
this.#subscriptions.add(subscription);
|
|
594
|
-
return subscription;
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
/**
|
|
598
|
-
* Sets the current context by resolving a context item using the provided ID.
|
|
599
|
-
*
|
|
600
|
-
* This method attempts to resolve a context item by its unique identifier,
|
|
601
|
-
* filters out any invalid or undefined items, and then sets the current context
|
|
602
|
-
* using the resolved item. The operation is performed as an Observable stream,
|
|
603
|
-
* allowing subscribers to react to the context change or handle errors.
|
|
604
|
-
*
|
|
605
|
-
* @param id - The unique identifier of the context item to resolve and set as current.
|
|
606
|
-
* @returns An Observable that emits the resolved and set {@link ContextItem}.
|
|
607
|
-
* Emits an error if the context cannot be resolved or set.
|
|
608
|
-
*/
|
|
609
|
-
public setCurrentContextById(id: string): Observable<ContextItem<Record<string, unknown>>> {
|
|
610
|
-
return new Observable((subscriber) => {
|
|
611
|
-
try {
|
|
612
|
-
this.#contextClient
|
|
613
|
-
// resolve context item by id
|
|
614
|
-
.resolveContext(id)
|
|
615
|
-
// filter out invalid items and set as current context
|
|
616
|
-
.pipe(
|
|
617
|
-
// filter out invalid context items
|
|
618
|
-
filter((item): item is ContextItem => !!item),
|
|
619
|
-
// set current context with validation and resolution
|
|
620
|
-
switchMap((item) => this.setCurrentContext(item)),
|
|
621
|
-
)
|
|
622
|
-
.subscribe(subscriber);
|
|
623
|
-
} catch (err) {
|
|
624
|
-
// catch any unhandled exceptions and emit error
|
|
625
|
-
subscriber.error(err);
|
|
626
|
-
}
|
|
627
|
-
});
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
/**
|
|
631
|
-
* Asynchronously sets the current context by its unique identifier and returns a promise
|
|
632
|
-
* that resolves to the corresponding `ContextItem`.
|
|
633
|
-
*
|
|
634
|
-
* This method wraps the observable returned by `setCurrentContextById` into a promise,
|
|
635
|
-
* allowing for async/await usage.
|
|
636
|
-
*
|
|
637
|
-
* @see {@link setCurrentContextById} for more details.
|
|
638
|
-
*
|
|
639
|
-
* @param id - The unique identifier of the context to set as current.
|
|
640
|
-
* @returns A promise that resolves to the `ContextItem` associated with the given ID.
|
|
641
|
-
*/
|
|
642
|
-
public setCurrentContextByIdAsync(id: string): Promise<ContextItem<Record<string, unknown>>> {
|
|
643
|
-
// return last value from observable
|
|
644
|
-
return lastValueFrom(this.setCurrentContextById(id));
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
/**
|
|
648
|
-
* Setting context is a complex operation, and might not happen immediately.
|
|
649
|
-
* When setting the context, a task is created and added to the queue.
|
|
650
|
-
* Once the task is completed, the returned observable will emit the value which will be the next state.
|
|
651
|
-
*
|
|
652
|
-
* Even tho this function returns a `Observable`, the task will be queued even tho nobody subscribes.
|
|
653
|
-
*
|
|
654
|
-
* If the observable is subscribe, unsubscribing __WILL__ abort the task and remove it from queue
|
|
655
|
-
*
|
|
656
|
-
* @param context context item which would be queue to set as current
|
|
657
|
-
* @param opt Optional settings.
|
|
658
|
-
* @param opt.validate Whether to validate the context item before setting it.
|
|
659
|
-
* @param opt.resolve Whether to attempt to resolve the context item if validation fails.
|
|
660
|
-
* @template T The type of the context item, which extends `ContextItem<Record<string, unknown>>` or can be `null`.
|
|
661
|
-
* @returns An observable that emits the context item once the queued task completes.
|
|
662
|
-
*/
|
|
663
|
-
public setCurrentContext<T extends ContextItem<Record<string, unknown>> | null>(
|
|
664
|
-
context: T,
|
|
665
|
-
opt?: { validate?: boolean; resolve?: boolean },
|
|
666
|
-
): Observable<T> {
|
|
667
|
-
// signal for aborting the queue entry
|
|
668
|
-
const abort$ = new Subject();
|
|
669
|
-
|
|
670
|
-
// wrapper for returning an observable to the caller
|
|
671
|
-
const subject$ = new Subject<T>();
|
|
672
|
-
|
|
673
|
-
// run the actual context-setting logic and relay results/errors to the caller's subject
|
|
674
|
-
const task$ = this._setCurrentContext(context, opt).pipe(
|
|
675
|
-
// send context item which was set to the caller
|
|
676
|
-
tap((x) => subject$.next(x)),
|
|
677
|
-
// abort task on signal
|
|
678
|
-
takeUntil(abort$),
|
|
679
|
-
// close the observable sent to the caller
|
|
680
|
-
finalize(() => subject$.complete()),
|
|
681
|
-
// catch any unhandled exceptions to not stall the queue
|
|
682
|
-
catchError((err) => {
|
|
683
|
-
// emit error to caller
|
|
684
|
-
subject$.error(err);
|
|
685
|
-
// skip setting any context
|
|
686
|
-
return EMPTY;
|
|
687
|
-
}),
|
|
688
|
-
);
|
|
689
|
-
|
|
690
|
-
// add task to internal queue
|
|
691
|
-
this.#contextQueue.next(task$ as Observable<ContextItem<Record<string, unknown>>>);
|
|
692
|
-
|
|
693
|
-
// tear down the queued task if the caller unsubscribes
|
|
694
|
-
return subject$.pipe(
|
|
695
|
-
// if caller subscribes, unsubscribe should abort queue entry
|
|
696
|
-
finalize(() => abort$.next(true)),
|
|
697
|
-
);
|
|
698
|
-
}
|
|
699
|
-
|
|
700
|
-
/**
|
|
701
|
-
* Sets the current context, optionally validating and resolving it.
|
|
702
|
-
*
|
|
703
|
-
* This method emits the provided context as an observable. If the context is the same as the current one,
|
|
704
|
-
* it emits and completes immediately. If validation is requested and fails, it either emits an error or,
|
|
705
|
-
* if resolution is enabled, attempts to resolve the context before setting it. The method dispatches various
|
|
706
|
-
* events to notify listeners about validation failures, resolution steps, and context changes, allowing
|
|
707
|
-
* cancellation at several stages.
|
|
708
|
-
*
|
|
709
|
-
* ### Step-by-step process:
|
|
710
|
-
* 1. **Check if the context is the same as the current one:**
|
|
711
|
-
* - If so, emit the context and complete the observable immediately.
|
|
712
|
-
* 2. **Validate the context (if requested):**
|
|
713
|
-
* - If validation fails and resolution is not enabled:
|
|
714
|
-
* - Dispatch the `onSetContextValidationFailed` event.
|
|
715
|
-
* - Emit an error and complete.
|
|
716
|
-
* - If validation fails but resolution is enabled:
|
|
717
|
-
* - Dispatch the `onSetContextResolve` event (cancelable).
|
|
718
|
-
* - If canceled, throw an error and abort.
|
|
719
|
-
* - Attempt to resolve the context using `resolveContext`.
|
|
720
|
-
* - Dispatch the `onSetContextResolved` event (cancelable) after resolution.
|
|
721
|
-
* - If canceled, throw an error and abort.
|
|
722
|
-
* - Recursively call `_setCurrentContext` with the resolved context (without validation/resolution).
|
|
723
|
-
* 3. **If validation passes or not requested:**
|
|
724
|
-
* - Dispatch the `onCurrentContextChange` event (cancelable).
|
|
725
|
-
* - If canceled, throw an error and abort.
|
|
726
|
-
* - Emit the context and complete the observable.
|
|
727
|
-
*
|
|
728
|
-
* @protected
|
|
729
|
-
* @template T - The type of the context item, which extends `ContextItem<Record<string, unknown>>` or can be `null`.
|
|
730
|
-
* @param context - The new context to set.
|
|
731
|
-
* @param opt - Optional settings:
|
|
732
|
-
* - `validate`: Whether to validate the context before setting.
|
|
733
|
-
* - `resolve`: Whether to attempt to resolve the context if validation fails.
|
|
734
|
-
* @returns An `Observable<T>` that emits the context when set, or errors if validation or resolution fails.
|
|
735
|
-
* @fires onSetContextValidationFailed - When context validation fails and resolution is not enabled.
|
|
736
|
-
* @fires onSetContextResolve - Before attempting to resolve the context.
|
|
737
|
-
* @fires onSetContextResolved - After the context has been resolved.
|
|
738
|
-
* @fires onCurrentContextChange - Before changing the current context.
|
|
739
|
-
* @throws Error if validation fails and resolution is not enabled, or if any event handler cancels the operation.
|
|
740
|
-
*/
|
|
741
|
-
protected _setCurrentContext<T extends ContextItem<Record<string, unknown>> | null>(
|
|
742
|
-
context: T,
|
|
743
|
-
opt?: { validate?: boolean; resolve?: boolean },
|
|
744
|
-
): Observable<T> {
|
|
745
|
-
return new Observable((subscriber) => {
|
|
746
|
-
// if context is the same as current, just emit and complete
|
|
747
|
-
if (context === this.currentContext) {
|
|
748
|
-
subscriber.next(context);
|
|
749
|
-
return subscriber.complete();
|
|
750
|
-
}
|
|
751
|
-
// check if context is provided and should be validated
|
|
752
|
-
if (context && opt?.validate && !this.validateContext(context)) {
|
|
753
|
-
// check if the resolve context is provided
|
|
754
|
-
if (!opt.resolve) {
|
|
755
|
-
// notify event observers that context validation failed since resolve is not provided
|
|
756
|
-
this.#event?.dispatchEvent('onSetContextValidationFailed', {
|
|
757
|
-
source: this,
|
|
758
|
-
detail: { context },
|
|
759
|
-
});
|
|
760
|
-
// emit error and complete
|
|
761
|
-
return subscriber.error(Error('failed to validate provided context'));
|
|
762
|
-
}
|
|
763
|
-
// if resolve is enabled, attempt to resolve the invalid context before setting it
|
|
764
|
-
if (opt.resolve) {
|
|
765
|
-
// the recursive `_setCurrentContext` call below is re-entered with the already
|
|
766
|
-
// validated/resolved context, so the generic `T` cast is safe here.
|
|
767
|
-
return of(context)
|
|
768
|
-
.pipe(
|
|
769
|
-
// notify event observers that context is about to get resolved
|
|
770
|
-
switchMap(async (context) => {
|
|
771
|
-
// wait for event listeners to handle the event
|
|
772
|
-
const event = await this.#event?.dispatchEvent('onSetContextResolve', {
|
|
773
|
-
source: this,
|
|
774
|
-
cancelable: true,
|
|
775
|
-
detail: { context },
|
|
776
|
-
});
|
|
777
|
-
// check if event was canceled and abort if so
|
|
778
|
-
if (event?.canceled) {
|
|
779
|
-
throw Error('resolving of context was canceled');
|
|
780
|
-
}
|
|
781
|
-
return context;
|
|
782
|
-
}),
|
|
783
|
-
// resolve context
|
|
784
|
-
switchMap((context) =>
|
|
785
|
-
// Pair the resolved context alongside the original for downstream consumers.
|
|
786
|
-
this.resolveContext(context).pipe(
|
|
787
|
-
map((resolved) => ({
|
|
788
|
-
context,
|
|
789
|
-
resolved,
|
|
790
|
-
})),
|
|
791
|
-
),
|
|
792
|
-
),
|
|
793
|
-
// notify event listeners that context was resolved
|
|
794
|
-
switchMap(async ({ context, resolved }) => {
|
|
795
|
-
// wait for event listeners to handle the event
|
|
796
|
-
const event = await this.#event?.dispatchEvent('onSetContextResolved', {
|
|
797
|
-
source: this,
|
|
798
|
-
cancelable: true,
|
|
799
|
-
detail: { context, resolved },
|
|
800
|
-
});
|
|
801
|
-
// check if event was canceled and abort if so
|
|
802
|
-
if (event?.canceled) {
|
|
803
|
-
throw Error('resolving of context was canceled');
|
|
804
|
-
}
|
|
805
|
-
return resolved;
|
|
806
|
-
}),
|
|
807
|
-
// recursive call to set current context without validation and resolution
|
|
808
|
-
switchMap((resolved) => this._setCurrentContext(resolved as unknown as T)),
|
|
809
|
-
)
|
|
810
|
-
.subscribe(subscriber);
|
|
811
|
-
}
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
// make the context an observable
|
|
815
|
-
return of(context)
|
|
816
|
-
.pipe(
|
|
817
|
-
// alert event listeners that context is about to change
|
|
818
|
-
switchMap(async (context) => {
|
|
819
|
-
const event = await this.#event?.dispatchEvent('onCurrentContextChange', {
|
|
820
|
-
source: this,
|
|
821
|
-
canBubble: true,
|
|
822
|
-
cancelable: true,
|
|
823
|
-
detail: { context: context },
|
|
824
|
-
});
|
|
825
|
-
|
|
826
|
-
// check if event was canceled and abort if so
|
|
827
|
-
if (event?.canceled) {
|
|
828
|
-
throw Error('change of context was aborted');
|
|
829
|
-
}
|
|
830
|
-
|
|
831
|
-
return context;
|
|
832
|
-
}),
|
|
833
|
-
)
|
|
834
|
-
.subscribe((context) => {
|
|
835
|
-
// emit context to the caller
|
|
836
|
-
subscriber.next(context);
|
|
837
|
-
// only take the first value and complete
|
|
838
|
-
subscriber.complete();
|
|
839
|
-
});
|
|
840
|
-
});
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
/**
|
|
844
|
-
* Asynchronously sets the current context and returns a promise that resolves with the provided context.
|
|
845
|
-
*
|
|
846
|
-
* @see {@link setCurrentContext} for more details.
|
|
847
|
-
*
|
|
848
|
-
* @template T - The type of the context item, which extends `ContextItem<Record<string, unknown>>` or can be `null`.
|
|
849
|
-
* @param context - The context item to set as the current context, or `null` to clear it.
|
|
850
|
-
* @param opt - Optional settings for context handling.
|
|
851
|
-
* @param opt.validate - If `true`, validates the context before setting it.
|
|
852
|
-
* @param opt.resolve - If `true`, resolves any dependencies or references in the context before setting it.
|
|
853
|
-
* @returns A promise that resolves with the context item that was set.
|
|
854
|
-
*/
|
|
855
|
-
public async setCurrentContextAsync<T extends ContextItem<Record<string, unknown>> | null>(
|
|
856
|
-
context: T,
|
|
857
|
-
opt?: { validate?: boolean; resolve?: boolean },
|
|
858
|
-
): Promise<T> {
|
|
859
|
-
return lastValueFrom(this.setCurrentContext(context, opt));
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
/**
|
|
863
|
-
* Queries the context for items matching the provided search string.
|
|
864
|
-
*
|
|
865
|
-
* This method constructs query parameters using the given search term and the current context type,
|
|
866
|
-
* then executes the query using the internal query client. If a context filter is defined, it is applied
|
|
867
|
-
* to the results before emitting them. Errors thrown by the query client of type `QueryClientError` will
|
|
868
|
-
* have their underlying cause re-thrown.
|
|
869
|
-
*
|
|
870
|
-
* @param search - The search string to filter context items.
|
|
871
|
-
* @returns An Observable that emits an array of `ContextItem` objects matching the search criteria.
|
|
872
|
-
* @throws Re-throws the underlying cause of a `QueryClientError`, otherwise re-throws the original error.
|
|
873
|
-
*/
|
|
874
|
-
public queryContext(search: string): Observable<Array<ContextItem>> {
|
|
875
|
-
const query$ = this.queryClient
|
|
876
|
-
.query(
|
|
877
|
-
// generate query parameters
|
|
878
|
-
this.#contextParameterFn({
|
|
879
|
-
search,
|
|
880
|
-
type: this.#contextType,
|
|
881
|
-
}) as QueryContextParameters,
|
|
882
|
-
)
|
|
883
|
-
// unwrap query-client errors and expose the underlying cause to subscribers
|
|
884
|
-
.pipe(
|
|
885
|
-
catchError((err) => {
|
|
886
|
-
// if query client throws a QueryClientError, extract the cause and throw it
|
|
887
|
-
if (err.name === 'QueryClientError') {
|
|
888
|
-
throw err.cause;
|
|
889
|
-
}
|
|
890
|
-
throw err;
|
|
891
|
-
}),
|
|
892
|
-
map((x) => x.value),
|
|
893
|
-
);
|
|
894
|
-
|
|
895
|
-
// apply context filter if available
|
|
896
|
-
return this.#contextFilter ? query$.pipe(map(this.#contextFilter)) : query$;
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
/**
|
|
900
|
-
* Asynchronously queries the context for items matching the provided search string.
|
|
901
|
-
*
|
|
902
|
-
* @see {@link queryContext} for more details.
|
|
903
|
-
*
|
|
904
|
-
* @param search - The search string used to filter context items.
|
|
905
|
-
* @returns A promise that resolves to an array of `ContextItem` objects matching the search criteria.
|
|
906
|
-
*/
|
|
907
|
-
public queryContextAsync(search: string): Promise<Array<ContextItem>> {
|
|
908
|
-
return lastValueFrom(this.queryContext(search));
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
/**
|
|
912
|
-
* Validates whether the provided context item matches one of the allowed context types.
|
|
913
|
-
*
|
|
914
|
-
* @param item - The context item to validate, containing a type with an `id` property.
|
|
915
|
-
* @returns `true` if the context type is not set or if the item's type ID matches one of the allowed types (case-insensitive); otherwise, `false`.
|
|
916
|
-
*/
|
|
917
|
-
public validateContext(item: ContextItem<Record<string, unknown>>): boolean {
|
|
918
|
-
// no context type configured means every context item is considered valid
|
|
919
|
-
if (!this.#contextType) return true;
|
|
920
|
-
// normalize allowed types for a case-insensitive comparison
|
|
921
|
-
return this.#contextType.map((x) => x.toLowerCase()).includes(item.type.id.toLowerCase());
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
/**
|
|
925
|
-
* Resolves a context item by fetching related context items of the same type as configured in the provider.
|
|
926
|
-
*
|
|
927
|
-
* This method:
|
|
928
|
-
* - Requests related context items matching the provider's context type.
|
|
929
|
-
* - Filters out invalid context items using `validateContext`.
|
|
930
|
-
* - Selects the first valid context item, throwing an error if none are found.
|
|
931
|
-
* - Logs a warning if multiple valid context items are found.
|
|
932
|
-
* - Returns the resolved context item as an observable.
|
|
933
|
-
*
|
|
934
|
-
* @param item - The context item for which to resolve related context.
|
|
935
|
-
* @returns An observable emitting the resolved context item.
|
|
936
|
-
* @throws Error if no valid related context item is found.
|
|
937
|
-
*/
|
|
938
|
-
public resolveContext(
|
|
939
|
-
item: ContextItem<Record<string, unknown>>,
|
|
940
|
-
): Observable<ContextItem<Record<string, unknown>>> {
|
|
941
|
-
// request related context items for the given context item with the same context type which the provider is configured with
|
|
942
|
-
return this.relatedContexts({ item, filter: { type: this.#contextType } }).pipe(
|
|
943
|
-
// filter out invalid context items
|
|
944
|
-
map((x) =>
|
|
945
|
-
// keep only context items that validate against the provider's context type
|
|
946
|
-
x.filter((item) => this.validateContext(item)),
|
|
947
|
-
),
|
|
948
|
-
map((values) => {
|
|
949
|
-
// related context should be resolved to a single context item
|
|
950
|
-
const value = values.shift();
|
|
951
|
-
|
|
952
|
-
// if no value is found, throw an error
|
|
953
|
-
if (!value) {
|
|
954
|
-
throw Error('failed to resolve context');
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
// if multiple items are found, log a warning
|
|
958
|
-
if (values.length) {
|
|
959
|
-
console.warn('ContextProvider::relatedContext', 'multiple items found 🤣', values);
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
// return the resolved context item
|
|
963
|
-
return value;
|
|
964
|
-
}),
|
|
965
|
-
);
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
/**
|
|
969
|
-
* Asynchronously resolves the provided context item.
|
|
970
|
-
*
|
|
971
|
-
* @see {@link resolveContext} for more details.
|
|
972
|
-
*
|
|
973
|
-
* @param item - The context item to resolve.
|
|
974
|
-
* @returns A promise that resolves to the resolved context item.
|
|
975
|
-
*/
|
|
976
|
-
public resolveContextAsync(
|
|
977
|
-
item: ContextItem<Record<string, unknown>>,
|
|
978
|
-
): Promise<ContextItem<Record<string, unknown>>> {
|
|
979
|
-
return lastValueFrom(this.resolveContext(item));
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
/**
|
|
983
|
-
* Retrieves related context items based on the provided parameters.
|
|
984
|
-
*
|
|
985
|
-
* This method queries the related context client to fetch an array of context items
|
|
986
|
-
* that are related to the specified parameters. If the related context client is not
|
|
987
|
-
* available, it returns an observable that emits an error.
|
|
988
|
-
*
|
|
989
|
-
* @param args - The parameters used to query for related context items.
|
|
990
|
-
* @returns An Observable that emits an array of related context items.
|
|
991
|
-
* @throws Error if no related context client is defined or if the query fails.
|
|
992
|
-
*/
|
|
993
|
-
public relatedContexts(
|
|
994
|
-
args: RelatedContextParameters,
|
|
995
|
-
): Observable<Array<ContextItem<Record<string, unknown>>>> {
|
|
996
|
-
// check if related context client is available
|
|
997
|
-
if (!this.#contextRelated) {
|
|
998
|
-
return throwError(() =>
|
|
999
|
-
Error('ContextProvider::relatedContexts - no client defined for resolving related context'),
|
|
1000
|
-
);
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
// request related context items
|
|
1004
|
-
return this.#contextRelated.query(args).pipe(
|
|
1005
|
-
map(({ value }) => value),
|
|
1006
|
-
catchError((err) => {
|
|
1007
|
-
// unwrap the underlying cause so callers see the original error
|
|
1008
|
-
if (err.cause) {
|
|
1009
|
-
throw err.cause;
|
|
1010
|
-
}
|
|
1011
|
-
throw err;
|
|
1012
|
-
}),
|
|
1013
|
-
);
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
/**
|
|
1017
|
-
* Asynchronously retrieves an array of related context items based on the provided parameters.
|
|
1018
|
-
*
|
|
1019
|
-
* @see {@link relatedContexts} for more details.
|
|
1020
|
-
*
|
|
1021
|
-
* @param args - The parameters used to determine which related contexts to retrieve.
|
|
1022
|
-
* @returns A promise that resolves to an array of `ContextItem` objects containing generic records.
|
|
1023
|
-
*/
|
|
1024
|
-
public relatedContextsAsync(
|
|
1025
|
-
args: RelatedContextParameters,
|
|
1026
|
-
): Promise<Array<ContextItem<Record<string, unknown>>>> {
|
|
1027
|
-
return lastValueFrom(this.relatedContexts(args));
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
/**
|
|
1031
|
-
* Clears the current context by setting it to null.
|
|
1032
|
-
*
|
|
1033
|
-
* This method is typically used to reset or remove the active context,
|
|
1034
|
-
* ensuring that subsequent operations do not reference any previous context state.
|
|
1035
|
-
*/
|
|
1036
|
-
public clearCurrentContext(): void {
|
|
1037
|
-
this.setCurrentContext(null);
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
/**
|
|
1041
|
-
* Disposes of resources held by the context provider.
|
|
1042
|
-
*
|
|
1043
|
-
* Unsubscribes from all active subscriptions and disposes of the context client,
|
|
1044
|
-
* ensuring that any allocated resources are properly released.
|
|
1045
|
-
*/
|
|
1046
|
-
dispose() {
|
|
1047
|
-
this.#subscriptions.unsubscribe();
|
|
1048
|
-
this.#contextClient.dispose();
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
export default ContextProvider;
|
|
1053
|
-
|
|
1054
|
-
declare module '@equinor/fusion-framework-module-event' {
|
|
1055
|
-
interface FrameworkEventMap {
|
|
1056
|
-
/**
|
|
1057
|
-
* Dispatched **before** the current context is changed.
|
|
1058
|
-
*
|
|
1059
|
-
* The event is cancelable — calling `event.preventDefault()` in a
|
|
1060
|
-
* listener will abort the context change.
|
|
1061
|
-
*/
|
|
1062
|
-
onCurrentContextChange: FrameworkEvent<
|
|
1063
|
-
FrameworkEventInit<
|
|
1064
|
-
{
|
|
1065
|
-
context: ContextItem | null;
|
|
1066
|
-
},
|
|
1067
|
-
IContextProvider
|
|
1068
|
-
>
|
|
1069
|
-
>;
|
|
1070
|
-
/**
|
|
1071
|
-
* Dispatched **after** the current context has changed.
|
|
1072
|
-
*
|
|
1073
|
-
* Contains both the previous and next context items, enabling
|
|
1074
|
-
* listeners to react to transitions.
|
|
1075
|
-
*/
|
|
1076
|
-
onCurrentContextChanged: FrameworkEvent<
|
|
1077
|
-
FrameworkEventInit<
|
|
1078
|
-
{
|
|
1079
|
-
next: ContextItem | null;
|
|
1080
|
-
previous?: ContextItem | null;
|
|
1081
|
-
},
|
|
1082
|
-
IContextProvider
|
|
1083
|
-
>
|
|
1084
|
-
>;
|
|
1085
|
-
|
|
1086
|
-
/**
|
|
1087
|
-
* Dispatched **before** a parent context change is applied locally.
|
|
1088
|
-
*
|
|
1089
|
-
* Cancelable — prevents the parent context from being mirrored into
|
|
1090
|
-
* this provider.
|
|
1091
|
-
*/
|
|
1092
|
-
onParentContextChanged: FrameworkEvent<
|
|
1093
|
-
FrameworkEventInit<
|
|
1094
|
-
{
|
|
1095
|
-
context: ContextItem | null;
|
|
1096
|
-
},
|
|
1097
|
-
IContextProvider
|
|
1098
|
-
>
|
|
1099
|
-
>;
|
|
1100
|
-
|
|
1101
|
-
/**
|
|
1102
|
-
* Dispatched **before** context resolution begins (when validation
|
|
1103
|
-
* fails and the caller requested resolution).
|
|
1104
|
-
*
|
|
1105
|
-
* Cancelable — aborting prevents the resolution attempt.
|
|
1106
|
-
*/
|
|
1107
|
-
onSetContextResolve: FrameworkEvent<
|
|
1108
|
-
FrameworkEventInit<
|
|
1109
|
-
{
|
|
1110
|
-
context: ContextItem;
|
|
1111
|
-
},
|
|
1112
|
-
IContextProvider
|
|
1113
|
-
>
|
|
1114
|
-
>;
|
|
1115
|
-
|
|
1116
|
-
/**
|
|
1117
|
-
* Dispatched **after** the context has been resolved to a new item.
|
|
1118
|
-
*
|
|
1119
|
-
* Cancelable — aborting prevents the resolved item from being
|
|
1120
|
-
* set as the current context.
|
|
1121
|
-
*/
|
|
1122
|
-
onSetContextResolved: FrameworkEvent<
|
|
1123
|
-
FrameworkEventInit<
|
|
1124
|
-
{
|
|
1125
|
-
context: ContextItem;
|
|
1126
|
-
resolved?: ContextItem | null;
|
|
1127
|
-
},
|
|
1128
|
-
IContextProvider
|
|
1129
|
-
>
|
|
1130
|
-
>;
|
|
1131
|
-
|
|
1132
|
-
/**
|
|
1133
|
-
* Dispatched when context validation fails and resolution is not
|
|
1134
|
-
* enabled.
|
|
1135
|
-
*/
|
|
1136
|
-
onSetContextValidationFailed: FrameworkEvent<
|
|
1137
|
-
FrameworkEventInit<
|
|
1138
|
-
{
|
|
1139
|
-
context: ContextItem;
|
|
1140
|
-
},
|
|
1141
|
-
IContextProvider
|
|
1142
|
-
>
|
|
1143
|
-
>;
|
|
1144
|
-
|
|
1145
|
-
/**
|
|
1146
|
-
* Dispatched when context resolution fails with an error.
|
|
1147
|
-
*/
|
|
1148
|
-
onSetContextResolveFailed: FrameworkEvent<
|
|
1149
|
-
FrameworkEventInit<
|
|
1150
|
-
{
|
|
1151
|
-
context: ContextItem;
|
|
1152
|
-
error: unknown;
|
|
1153
|
-
},
|
|
1154
|
-
IContextProvider
|
|
1155
|
-
>
|
|
1156
|
-
>;
|
|
1157
|
-
}
|
|
1158
|
-
}
|