@equinor/fusion-framework-module-context 7.0.4-next.0 → 8.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/CHANGELOG.md +36 -15
- package/README.md +168 -16
- package/dist/esm/ContextConfigBuilder.js +24 -6
- package/dist/esm/ContextConfigBuilder.js.map +1 -1
- package/dist/esm/ContextProvider.js +71 -15
- package/dist/esm/ContextProvider.js.map +1 -1
- package/dist/esm/client/ContextClient.js +13 -3
- package/dist/esm/client/ContextClient.js.map +1 -1
- package/dist/esm/configurator.js +40 -5
- package/dist/esm/configurator.js.map +1 -1
- package/dist/esm/{errors.js → errors/FusionContextSearchError.js} +3 -1
- package/dist/esm/errors/FusionContextSearchError.js.map +1 -0
- package/dist/esm/errors/index.js +2 -0
- package/dist/esm/errors/index.js.map +1 -0
- package/dist/esm/get-context-selector.js +14 -0
- package/dist/esm/get-context-selector.js.map +1 -0
- package/dist/esm/index.js +12 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/module.js +6 -0
- package/dist/esm/module.js.map +1 -1
- package/dist/esm/parse-context-item.js +31 -0
- package/dist/esm/parse-context-item.js.map +1 -0
- package/dist/esm/query-context-selector.js +12 -0
- package/dist/esm/query-context-selector.js.map +1 -0
- package/dist/esm/related-context-selector.js +12 -0
- package/dist/esm/related-context-selector.js.map +1 -0
- package/dist/esm/utils/extract-context-id-from-path.js +25 -0
- package/dist/esm/utils/extract-context-id-from-path.js.map +1 -0
- package/dist/esm/utils/index.js +10 -0
- package/dist/esm/utils/index.js.map +1 -1
- package/dist/esm/utils/resolve-context-from-path.js +11 -22
- package/dist/esm/utils/resolve-context-from-path.js.map +1 -1
- package/dist/esm/utils/resolve-initial-context.js +2 -0
- package/dist/esm/utils/resolve-initial-context.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esm/version.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/ContextConfigBuilder.d.ts +31 -1
- package/dist/types/ContextProvider.d.ts +74 -3
- package/dist/types/client/ContextClient.d.ts +8 -1
- package/dist/types/configurator.d.ts +134 -4
- package/dist/types/{errors.d.ts → errors/FusionContextSearchError.d.ts} +2 -0
- package/dist/types/errors/index.d.ts +1 -0
- package/dist/types/get-context-selector.d.ts +9 -0
- package/dist/types/index.d.ts +12 -0
- package/dist/types/module.d.ts +11 -0
- package/dist/types/parse-context-item.d.ts +8 -0
- package/dist/types/query-context-selector.d.ts +7 -0
- package/dist/types/related-context-selector.d.ts +7 -0
- package/dist/types/types.d.ts +23 -1
- package/dist/types/utils/extract-context-id-from-path.d.ts +16 -0
- package/dist/types/utils/index.d.ts +10 -0
- package/dist/types/utils/resolve-context-from-path.d.ts +1 -16
- package/dist/types/utils/resolve-initial-context.d.ts +1 -1
- package/dist/types/version.d.ts +1 -1
- package/package.json +12 -12
- package/src/ContextConfigBuilder.ts +47 -7
- package/src/ContextProvider.ts +115 -28
- package/src/client/ContextClient.ts +13 -3
- package/src/configurator.ts +152 -10
- package/src/{errors.ts → errors/FusionContextSearchError.ts} +2 -0
- package/src/errors/index.ts +1 -0
- package/src/get-context-selector.ts +18 -0
- package/src/index.ts +13 -0
- package/src/module.ts +11 -0
- package/src/parse-context-item.ts +39 -0
- package/src/query-context-selector.ts +15 -0
- package/src/related-context-selector.ts +15 -0
- package/src/types.ts +23 -1
- package/src/utils/extract-context-id-from-path.ts +30 -0
- package/src/utils/index.ts +10 -0
- package/src/utils/resolve-context-from-path.ts +13 -28
- package/src/utils/resolve-initial-context.ts +2 -0
- package/src/version.ts +1 -1
- package/dist/esm/errors.js.map +0 -1
- package/dist/esm/selectors.js +0 -58
- package/dist/esm/selectors.js.map +0 -1
- package/dist/types/selectors.d.ts +0 -19
- package/src/selectors.ts +0 -70
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@equinor/fusion-framework-module-context",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "8.0.1",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "./dist/esm/index.js",
|
|
6
6
|
"exports": {
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"types": "./dist/types/index.d.ts"
|
|
10
10
|
},
|
|
11
11
|
"./errors.js": {
|
|
12
|
-
"import": "./dist/esm/errors.js",
|
|
13
|
-
"types": "./dist/types/errors.d.ts"
|
|
12
|
+
"import": "./dist/esm/errors/index.js",
|
|
13
|
+
"types": "./dist/types/errors/index.d.ts"
|
|
14
14
|
},
|
|
15
15
|
"./utils": {
|
|
16
16
|
"import": "./dist/esm/utils/index.js",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"dist/types/index.d.ts"
|
|
26
26
|
],
|
|
27
27
|
"errors.js": [
|
|
28
|
-
"dist/types/errors.d.ts"
|
|
28
|
+
"dist/types/errors/index.d.ts"
|
|
29
29
|
],
|
|
30
30
|
"utils": [
|
|
31
31
|
"dist/types/utils/index.d.ts"
|
|
@@ -45,19 +45,19 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"fast-deep-equal": "^3.1.3",
|
|
48
|
-
"@equinor/fusion-query": "^
|
|
48
|
+
"@equinor/fusion-query": "^7.0.2"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
51
|
"rxjs": "^7.8.1",
|
|
52
|
-
"typescript": "^
|
|
53
|
-
"@equinor/fusion-framework-module": "^
|
|
54
|
-
"@equinor/fusion-framework-module
|
|
55
|
-
"@equinor/fusion-framework-module-
|
|
56
|
-
"@equinor/fusion-framework-module-
|
|
52
|
+
"typescript": "^7.0.2",
|
|
53
|
+
"@equinor/fusion-framework-module-navigation": "^7.0.5",
|
|
54
|
+
"@equinor/fusion-framework-module": "^6.1.1",
|
|
55
|
+
"@equinor/fusion-framework-module-services": "^8.0.2",
|
|
56
|
+
"@equinor/fusion-framework-module-event": "^6.0.1"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"rxjs": "^7.
|
|
60
|
-
"@equinor/fusion-framework-module": "^
|
|
59
|
+
"rxjs": "^7.0.0",
|
|
60
|
+
"@equinor/fusion-framework-module": "^6.1.1"
|
|
61
61
|
},
|
|
62
62
|
"scripts": {
|
|
63
63
|
"build": "tsc -b"
|
|
@@ -17,6 +17,14 @@ import type {
|
|
|
17
17
|
|
|
18
18
|
import type { ContextItem, QueryContextParameters, RelatedContextParameters } from './types';
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Callback passed to {@link IContextModuleConfigurator.addConfigBuilder}.
|
|
22
|
+
*
|
|
23
|
+
* Receives a {@link ContextConfigBuilder} and may use its setter methods
|
|
24
|
+
* to populate the context module configuration. The callback may be async.
|
|
25
|
+
*
|
|
26
|
+
* @typeParam TDeps - Module dependency array inferred from the configurator.
|
|
27
|
+
*/
|
|
20
28
|
export type ContextConfigBuilderCallback = <TDeps extends Array<AnyModule> = []>(
|
|
21
29
|
builder: ContextConfigBuilder<TDeps, ModuleInitializerArgs<IContextModuleConfigurator, TDeps>>,
|
|
22
30
|
) => void | Promise<void>;
|
|
@@ -44,19 +52,25 @@ export type ContextConfigBuilderCallback = <TDeps extends Array<AnyModule> = []>
|
|
|
44
52
|
* - The builder pattern allows chaining configuration methods for clarity and convenience.
|
|
45
53
|
* - The `requireInstance` method enables asynchronous retrieval of module instances by name.
|
|
46
54
|
*
|
|
47
|
-
* @todo - this should extend the BaseConfigBuilder
|
|
55
|
+
* @todo(#5120) - this should extend the BaseConfigBuilder
|
|
48
56
|
*
|
|
49
57
|
* @see ContextModuleConfig
|
|
50
58
|
* @see ModuleInitializerArgs
|
|
51
59
|
*/
|
|
52
60
|
export class ContextConfigBuilder<
|
|
53
61
|
TModules extends Array<AnyModule> = [],
|
|
62
|
+
// biome-ignore lint/suspicious/noExplicitAny: `ModuleInitializerArgs<any, any>` widens the constraint to accept initializer args for any configurator/module set \u2014 `unknown` breaks assignability of the concrete default type argument
|
|
54
63
|
TInit extends ModuleInitializerArgs<any, any> = ModuleInitializerArgs<
|
|
55
64
|
ContextModuleConfigurator,
|
|
56
65
|
TModules
|
|
57
66
|
>,
|
|
58
67
|
> {
|
|
59
68
|
#init: TInit;
|
|
69
|
+
/**
|
|
70
|
+
* Creates a new `ContextConfigBuilder`.
|
|
71
|
+
* @param init - Module initializer arguments used to resolve module instances.
|
|
72
|
+
* @param config - The partial context module config to build upon.
|
|
73
|
+
*/
|
|
60
74
|
constructor(
|
|
61
75
|
init: TInit,
|
|
62
76
|
public config: Partial<ContextModuleConfig> = {},
|
|
@@ -64,13 +78,31 @@ export class ContextConfigBuilder<
|
|
|
64
78
|
this.#init = init;
|
|
65
79
|
}
|
|
66
80
|
|
|
81
|
+
/**
|
|
82
|
+
* Requires a module instance by its registered key.
|
|
83
|
+
* @param module - The key of the module to resolve.
|
|
84
|
+
* @returns A promise that resolves to the requested module instance.
|
|
85
|
+
*/
|
|
67
86
|
requireInstance<TKey extends string = Extract<keyof Modules, string>>(
|
|
68
87
|
module: TKey,
|
|
69
88
|
): Promise<ModuleType<Modules[TKey]>>;
|
|
70
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Requires a module instance by name.
|
|
92
|
+
* @param module - The name of the module to resolve.
|
|
93
|
+
* @returns A promise that resolves to the requested module instance.
|
|
94
|
+
*/
|
|
71
95
|
requireInstance<T>(module: string): Promise<T>;
|
|
72
96
|
|
|
73
|
-
|
|
97
|
+
/**
|
|
98
|
+
* Requires a module instance by key or name.
|
|
99
|
+
* @param module - The key or name of the module to resolve.
|
|
100
|
+
* @returns A promise that resolves to the requested module instance.
|
|
101
|
+
*/
|
|
102
|
+
requireInstance(
|
|
103
|
+
module: string,
|
|
104
|
+
// biome-ignore lint/suspicious/noExplicitAny: implementation signature must satisfy both overloads above (`Promise<ModuleType<...>>` and `Promise<T>`); `unknown` is not assignable to the generic `Promise<T>` overload
|
|
105
|
+
): Promise<any> {
|
|
74
106
|
return this.#init.requireInstance(module);
|
|
75
107
|
}
|
|
76
108
|
|
|
@@ -147,6 +179,13 @@ export class ContextConfigBuilder<
|
|
|
147
179
|
this.config.generatePathFromContext = fn;
|
|
148
180
|
}
|
|
149
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Sets the function used to resolve the initial context during module post-initialization.
|
|
184
|
+
*
|
|
185
|
+
* @param fn - A function that returns an observable input emitting the initial context item.
|
|
186
|
+
* The default resolver extracts a context ID from the navigation path, falling
|
|
187
|
+
* back to the parent provider's current context.
|
|
188
|
+
*/
|
|
150
189
|
setResolveInitialContext(fn: ContextModuleConfig['resolveInitialContext']) {
|
|
151
190
|
this.config.resolveInitialContext = fn;
|
|
152
191
|
}
|
|
@@ -177,7 +216,7 @@ export class ContextConfigBuilder<
|
|
|
177
216
|
},
|
|
178
217
|
expire = 1 * 60 * 1000,
|
|
179
218
|
): void {
|
|
180
|
-
|
|
219
|
+
const clientConfig: ContextModuleConfig['client'] = {
|
|
181
220
|
get:
|
|
182
221
|
typeof client.get === 'function'
|
|
183
222
|
? {
|
|
@@ -191,7 +230,7 @@ export class ContextConfigBuilder<
|
|
|
191
230
|
query:
|
|
192
231
|
typeof client.query === 'function'
|
|
193
232
|
? {
|
|
194
|
-
// TODO - might cast to checksum
|
|
233
|
+
// TODO(#5118) - might cast to checksum
|
|
195
234
|
key: (args) => JSON.stringify(args),
|
|
196
235
|
client: {
|
|
197
236
|
fn: client.query,
|
|
@@ -200,12 +239,13 @@ export class ContextConfigBuilder<
|
|
|
200
239
|
}
|
|
201
240
|
: client.query,
|
|
202
241
|
};
|
|
242
|
+
this.config.client = clientConfig;
|
|
243
|
+
// only override the related-context client config if one was provided
|
|
203
244
|
if (client.related) {
|
|
204
|
-
|
|
205
|
-
this.config.client!.related =
|
|
245
|
+
clientConfig.related =
|
|
206
246
|
typeof client.related === 'function'
|
|
207
247
|
? {
|
|
208
|
-
// TODO - might cast to checksum
|
|
248
|
+
// TODO(#5118) - might cast to checksum
|
|
209
249
|
key: (args) => JSON.stringify(args),
|
|
210
250
|
client: {
|
|
211
251
|
fn: client.related,
|
package/src/ContextProvider.ts
CHANGED
|
@@ -346,35 +346,64 @@ export class ContextProvider
|
|
|
346
346
|
|
|
347
347
|
#contextQueue = new Subject<Observable<ContextItem<Record<string, unknown>>>>();
|
|
348
348
|
|
|
349
|
+
/**
|
|
350
|
+
* The underlying context client used to resolve and hold the current context item.
|
|
351
|
+
* @returns The internal {@link ContextClient} instance.
|
|
352
|
+
*/
|
|
349
353
|
public get contextClient() {
|
|
350
354
|
return this.#contextClient;
|
|
351
355
|
}
|
|
352
356
|
|
|
357
|
+
/**
|
|
358
|
+
* The query client used to search for context items.
|
|
359
|
+
* @returns The internal `Query` instance used by {@link queryContext}.
|
|
360
|
+
*/
|
|
353
361
|
public get queryClient() {
|
|
354
362
|
return this.#contextQuery;
|
|
355
363
|
}
|
|
356
364
|
|
|
365
|
+
/**
|
|
366
|
+
* Observable stream emitting the current context item.
|
|
367
|
+
* @returns Observable that emits the current `ContextItem`, `null`, or `undefined`.
|
|
368
|
+
*/
|
|
357
369
|
get currentContext$(): Observable<ContextItem | null | undefined> {
|
|
358
370
|
return this.#contextClient.currentContext$;
|
|
359
371
|
}
|
|
360
372
|
|
|
373
|
+
/**
|
|
374
|
+
* Snapshot of the current context item.
|
|
375
|
+
* @returns The current `ContextItem`, or `null`/`undefined` if not set.
|
|
376
|
+
*/
|
|
361
377
|
get currentContext(): ContextItem | undefined | null {
|
|
362
378
|
return this.#contextClient.currentContext;
|
|
363
379
|
}
|
|
364
380
|
|
|
365
|
-
/**
|
|
381
|
+
/**
|
|
382
|
+
* Sets the current context item.
|
|
383
|
+
* @deprecated do not use, will be removed
|
|
384
|
+
* @param context - The context item to set as current. Must not be `undefined`.
|
|
385
|
+
* @throws Error if `context` is `undefined`.
|
|
386
|
+
*/
|
|
366
387
|
set currentContext(context: ContextItem | null | undefined) {
|
|
367
388
|
console.warn(
|
|
368
389
|
'@deprecated',
|
|
369
390
|
'ContextProvider.currentContext',
|
|
370
391
|
'use setCurrentContextById|setCurrentContext|clearCurrentContext',
|
|
371
392
|
);
|
|
393
|
+
// undefined is reserved to mean "not yet initialized", so it cannot be set explicitly
|
|
372
394
|
if (context === undefined) {
|
|
373
395
|
throw Error('not allowed to set current context as undefined undefined!');
|
|
374
396
|
}
|
|
375
397
|
this.setCurrentContextAsync(context);
|
|
376
398
|
}
|
|
377
399
|
|
|
400
|
+
/**
|
|
401
|
+
* Creates a new instance of `ContextProvider`.
|
|
402
|
+
* @param args - Constructor arguments.
|
|
403
|
+
* @param args.config - The context module configuration.
|
|
404
|
+
* @param args.event - Optional event module instance for dispatching context change events.
|
|
405
|
+
* @param args.parentContext - Optional parent context provider. Deprecated, use {@link connectParentContext}.
|
|
406
|
+
*/
|
|
378
407
|
constructor(args: {
|
|
379
408
|
config: ContextModuleConfig;
|
|
380
409
|
event?: ModuleType<EventModule>;
|
|
@@ -385,6 +414,7 @@ export class ContextProvider
|
|
|
385
414
|
|
|
386
415
|
super({ version, config });
|
|
387
416
|
|
|
417
|
+
// warn about deprecated parentContext constructor arg
|
|
388
418
|
if (args.parentContext) {
|
|
389
419
|
console.warn(
|
|
390
420
|
'@deprecated',
|
|
@@ -398,16 +428,19 @@ export class ContextProvider
|
|
|
398
428
|
if (config.resolveContext) {
|
|
399
429
|
this.resolveContext = config.resolveContext?.bind(this);
|
|
400
430
|
}
|
|
431
|
+
// override validateContext if configured
|
|
401
432
|
if (config.validateContext) {
|
|
402
433
|
this.validateContext = config.validateContext?.bind(this);
|
|
403
434
|
}
|
|
404
435
|
|
|
436
|
+
// override extractContextIdFromPath if configured
|
|
405
437
|
if (config.extractContextIdFromPath) {
|
|
406
|
-
// @ts-
|
|
438
|
+
// @ts-expect-error
|
|
407
439
|
this.extractContextIdFromPath = config.extractContextIdFromPath;
|
|
408
440
|
}
|
|
441
|
+
// override generatePathFromContext if configured
|
|
409
442
|
if (config.generatePathFromContext) {
|
|
410
|
-
// @ts-
|
|
443
|
+
// @ts-expect-error
|
|
411
444
|
this.generatePathFromContext = config.generatePathFromContext;
|
|
412
445
|
}
|
|
413
446
|
|
|
@@ -418,6 +451,7 @@ export class ContextProvider
|
|
|
418
451
|
this.#contextClient = new ContextClient(config.client.get);
|
|
419
452
|
this.#contextQuery = new Query(config.client.query);
|
|
420
453
|
|
|
454
|
+
// only create the related-context query when the config provides one
|
|
421
455
|
if (config.client.related) {
|
|
422
456
|
this.#contextRelated = new Query(config.client.related);
|
|
423
457
|
}
|
|
@@ -436,10 +470,8 @@ export class ContextProvider
|
|
|
436
470
|
this.#subscriptions.add(
|
|
437
471
|
// observe current context changes
|
|
438
472
|
this.currentContext$
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
pairwise(),
|
|
442
|
-
)
|
|
473
|
+
// emit previous and next context together for change comparisons
|
|
474
|
+
.pipe(pairwise())
|
|
443
475
|
.subscribe(([previous, next]) => {
|
|
444
476
|
this.#event?.dispatchEvent('onCurrentContextChanged', {
|
|
445
477
|
source: this,
|
|
@@ -494,12 +526,13 @@ export class ContextProvider
|
|
|
494
526
|
provider: IContextProvider,
|
|
495
527
|
opt?: { skipFirst: boolean },
|
|
496
528
|
): Subscription {
|
|
529
|
+
// build a stream of validated context changes from the parent provider
|
|
497
530
|
const parentContext$ = provider.currentContext$.pipe(
|
|
498
531
|
// do not set context if parent has not initialized
|
|
499
532
|
filter((x): x is ContextItem | null => x !== undefined),
|
|
500
533
|
filter((next, index) => {
|
|
501
534
|
// skip first item if opt.skipFirst is true
|
|
502
|
-
// TODO: this is a bit hacky, should be handled in a better way
|
|
535
|
+
// TODO(#5121): this is a bit hacky, should be handled in a better way
|
|
503
536
|
if (opt?.skipFirst && index <= 1) {
|
|
504
537
|
console.debug('ContextProvider::connectParentContext', 'skipping first item', next);
|
|
505
538
|
return false;
|
|
@@ -508,8 +541,8 @@ export class ContextProvider
|
|
|
508
541
|
return this.currentContext?.id !== next?.id;
|
|
509
542
|
}),
|
|
510
543
|
switchMap(async (next) => {
|
|
544
|
+
// if parent context is null, just return
|
|
511
545
|
if (!next) {
|
|
512
|
-
// if parent context is null, just return
|
|
513
546
|
return { next };
|
|
514
547
|
}
|
|
515
548
|
// notify event observers that parent context is about to change and await for cancelation
|
|
@@ -524,15 +557,19 @@ export class ContextProvider
|
|
|
524
557
|
filter((x) => !x.canceled),
|
|
525
558
|
switchMap(({ next }) => {
|
|
526
559
|
// set current context with validation and resolution
|
|
527
|
-
return
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
560
|
+
return (
|
|
561
|
+
this.setCurrentContext(next, {
|
|
562
|
+
validate: true,
|
|
563
|
+
resolve: true,
|
|
564
|
+
})
|
|
565
|
+
// swallow errors so a failed context change doesn't break the parent subscription
|
|
566
|
+
.pipe(
|
|
567
|
+
catchError((err) => {
|
|
568
|
+
console.warn('ContextProvider::onParentContextChanged', 'setCurrentContext', err);
|
|
569
|
+
// do not emit any value if an error occurs
|
|
570
|
+
return EMPTY;
|
|
571
|
+
}),
|
|
572
|
+
)
|
|
536
573
|
);
|
|
537
574
|
}),
|
|
538
575
|
catchError((err) => {
|
|
@@ -568,6 +605,7 @@ export class ContextProvider
|
|
|
568
605
|
this.#contextClient
|
|
569
606
|
// resolve context item by id
|
|
570
607
|
.resolveContext(id)
|
|
608
|
+
// filter out invalid items and set as current context
|
|
571
609
|
.pipe(
|
|
572
610
|
// filter out invalid context items
|
|
573
611
|
filter((item): item is ContextItem => !!item),
|
|
@@ -609,6 +647,11 @@ export class ContextProvider
|
|
|
609
647
|
* If the observable is subscribe, unsubscribing __WILL__ abort the task and remove it from queue
|
|
610
648
|
*
|
|
611
649
|
* @param context context item which would be queue to set as current
|
|
650
|
+
* @param opt Optional settings.
|
|
651
|
+
* @param opt.validate Whether to validate the context item before setting it.
|
|
652
|
+
* @param opt.resolve Whether to attempt to resolve the context item if validation fails.
|
|
653
|
+
* @template T The type of the context item, which extends `ContextItem<Record<string, unknown>>` or can be `null`.
|
|
654
|
+
* @returns An observable that emits the context item once the queued task completes.
|
|
612
655
|
*/
|
|
613
656
|
public setCurrentContext<T extends ContextItem<Record<string, unknown>> | null>(
|
|
614
657
|
context: T,
|
|
@@ -620,6 +663,7 @@ export class ContextProvider
|
|
|
620
663
|
// wrapper for returning an observable to the caller
|
|
621
664
|
const subject$ = new Subject<T>();
|
|
622
665
|
|
|
666
|
+
// run the actual context-setting logic and relay results/errors to the caller's subject
|
|
623
667
|
const task$ = this._setCurrentContext(context, opt).pipe(
|
|
624
668
|
// send context item which was set to the caller
|
|
625
669
|
tap((x) => subject$.next(x)),
|
|
@@ -639,6 +683,7 @@ export class ContextProvider
|
|
|
639
683
|
// add task to internal queue
|
|
640
684
|
this.#contextQueue.next(task$ as Observable<ContextItem<Record<string, unknown>>>);
|
|
641
685
|
|
|
686
|
+
// tear down the queued task if the caller unsubscribes
|
|
642
687
|
return subject$.pipe(
|
|
643
688
|
// if caller subscribes, unsubscribe should abort queue entry
|
|
644
689
|
finalize(() => abort$.next(true)),
|
|
@@ -674,7 +719,7 @@ export class ContextProvider
|
|
|
674
719
|
* - Emit the context and complete the observable.
|
|
675
720
|
*
|
|
676
721
|
* @protected
|
|
677
|
-
* @
|
|
722
|
+
* @template T - The type of the context item, which extends `ContextItem<Record<string, unknown>>` or can be `null`.
|
|
678
723
|
* @param context - The new context to set.
|
|
679
724
|
* @param opt - Optional settings:
|
|
680
725
|
* - `validate`: Whether to validate the context before setting.
|
|
@@ -708,7 +753,10 @@ export class ContextProvider
|
|
|
708
753
|
// emit error and complete
|
|
709
754
|
return subscriber.error(Error('failed to validate provided context'));
|
|
710
755
|
}
|
|
756
|
+
// if resolve is enabled, attempt to resolve the invalid context before setting it
|
|
711
757
|
if (opt.resolve) {
|
|
758
|
+
// the recursive `_setCurrentContext` call below is re-entered with the already
|
|
759
|
+
// validated/resolved context, so the generic `T` cast is safe here.
|
|
712
760
|
return of(context)
|
|
713
761
|
.pipe(
|
|
714
762
|
// notify event observers that context is about to get resolved
|
|
@@ -727,6 +775,7 @@ export class ContextProvider
|
|
|
727
775
|
}),
|
|
728
776
|
// resolve context
|
|
729
777
|
switchMap((context) =>
|
|
778
|
+
// Pair the resolved context alongside the original for downstream consumers.
|
|
730
779
|
this.resolveContext(context).pipe(
|
|
731
780
|
map((resolved) => ({
|
|
732
781
|
context,
|
|
@@ -789,7 +838,7 @@ export class ContextProvider
|
|
|
789
838
|
*
|
|
790
839
|
* @see {@link setCurrentContext} for more details.
|
|
791
840
|
*
|
|
792
|
-
* @
|
|
841
|
+
* @template T - The type of the context item, which extends `ContextItem<Record<string, unknown>>` or can be `null`.
|
|
793
842
|
* @param context - The context item to set as the current context, or `null` to clear it.
|
|
794
843
|
* @param opt - Optional settings for context handling.
|
|
795
844
|
* @param opt.validate - If `true`, validates the context before setting it.
|
|
@@ -813,6 +862,7 @@ export class ContextProvider
|
|
|
813
862
|
*
|
|
814
863
|
* @param search - The search string to filter context items.
|
|
815
864
|
* @returns An Observable that emits an array of `ContextItem` objects matching the search criteria.
|
|
865
|
+
* @throws Re-throws the underlying cause of a `QueryClientError`, otherwise re-throws the original error.
|
|
816
866
|
*/
|
|
817
867
|
public queryContext(search: string): Observable<Array<ContextItem>> {
|
|
818
868
|
const query$ = this.queryClient
|
|
@@ -823,6 +873,7 @@ export class ContextProvider
|
|
|
823
873
|
type: this.#contextType,
|
|
824
874
|
}) as QueryContextParameters,
|
|
825
875
|
)
|
|
876
|
+
// unwrap query-client errors and expose the underlying cause to subscribers
|
|
826
877
|
.pipe(
|
|
827
878
|
catchError((err) => {
|
|
828
879
|
// if query client throws a QueryClientError, extract the cause and throw it
|
|
@@ -857,7 +908,9 @@ export class ContextProvider
|
|
|
857
908
|
* @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`.
|
|
858
909
|
*/
|
|
859
910
|
public validateContext(item: ContextItem<Record<string, unknown>>): boolean {
|
|
911
|
+
// no context type configured means every context item is considered valid
|
|
860
912
|
if (!this.#contextType) return true;
|
|
913
|
+
// normalize allowed types for a case-insensitive comparison
|
|
861
914
|
return this.#contextType.map((x) => x.toLowerCase()).includes(item.type.id.toLowerCase());
|
|
862
915
|
}
|
|
863
916
|
|
|
@@ -881,7 +934,10 @@ export class ContextProvider
|
|
|
881
934
|
// request related context items for the given context item with the same context type which the provider is configured with
|
|
882
935
|
return this.relatedContexts({ item, filter: { type: this.#contextType } }).pipe(
|
|
883
936
|
// filter out invalid context items
|
|
884
|
-
map((x) =>
|
|
937
|
+
map((x) =>
|
|
938
|
+
// keep only context items that validate against the provider's context type
|
|
939
|
+
x.filter((item) => this.validateContext(item)),
|
|
940
|
+
),
|
|
885
941
|
map((values) => {
|
|
886
942
|
// related context should be resolved to a single context item
|
|
887
943
|
const value = values.shift();
|
|
@@ -941,6 +997,7 @@ export class ContextProvider
|
|
|
941
997
|
return this.#contextRelated.query(args).pipe(
|
|
942
998
|
map(({ value }) => value),
|
|
943
999
|
catchError((err) => {
|
|
1000
|
+
// unwrap the underlying cause so callers see the original error
|
|
944
1001
|
if (err.cause) {
|
|
945
1002
|
throw err.cause;
|
|
946
1003
|
}
|
|
@@ -989,7 +1046,12 @@ export default ContextProvider;
|
|
|
989
1046
|
|
|
990
1047
|
declare module '@equinor/fusion-framework-module-event' {
|
|
991
1048
|
interface FrameworkEventMap {
|
|
992
|
-
|
|
1049
|
+
/**
|
|
1050
|
+
* Dispatched **before** the current context is changed.
|
|
1051
|
+
*
|
|
1052
|
+
* The event is cancelable — calling `event.preventDefault()` in a
|
|
1053
|
+
* listener will abort the context change.
|
|
1054
|
+
*/
|
|
993
1055
|
onCurrentContextChange: FrameworkEvent<
|
|
994
1056
|
FrameworkEventInit<
|
|
995
1057
|
{
|
|
@@ -998,7 +1060,12 @@ declare module '@equinor/fusion-framework-module-event' {
|
|
|
998
1060
|
IContextProvider
|
|
999
1061
|
>
|
|
1000
1062
|
>;
|
|
1001
|
-
|
|
1063
|
+
/**
|
|
1064
|
+
* Dispatched **after** the current context has changed.
|
|
1065
|
+
*
|
|
1066
|
+
* Contains both the previous and next context items, enabling
|
|
1067
|
+
* listeners to react to transitions.
|
|
1068
|
+
*/
|
|
1002
1069
|
onCurrentContextChanged: FrameworkEvent<
|
|
1003
1070
|
FrameworkEventInit<
|
|
1004
1071
|
{
|
|
@@ -1009,7 +1076,12 @@ declare module '@equinor/fusion-framework-module-event' {
|
|
|
1009
1076
|
>
|
|
1010
1077
|
>;
|
|
1011
1078
|
|
|
1012
|
-
|
|
1079
|
+
/**
|
|
1080
|
+
* Dispatched **before** a parent context change is applied locally.
|
|
1081
|
+
*
|
|
1082
|
+
* Cancelable — prevents the parent context from being mirrored into
|
|
1083
|
+
* this provider.
|
|
1084
|
+
*/
|
|
1013
1085
|
onParentContextChanged: FrameworkEvent<
|
|
1014
1086
|
FrameworkEventInit<
|
|
1015
1087
|
{
|
|
@@ -1019,7 +1091,12 @@ declare module '@equinor/fusion-framework-module-event' {
|
|
|
1019
1091
|
>
|
|
1020
1092
|
>;
|
|
1021
1093
|
|
|
1022
|
-
|
|
1094
|
+
/**
|
|
1095
|
+
* Dispatched **before** context resolution begins (when validation
|
|
1096
|
+
* fails and the caller requested resolution).
|
|
1097
|
+
*
|
|
1098
|
+
* Cancelable — aborting prevents the resolution attempt.
|
|
1099
|
+
*/
|
|
1023
1100
|
onSetContextResolve: FrameworkEvent<
|
|
1024
1101
|
FrameworkEventInit<
|
|
1025
1102
|
{
|
|
@@ -1029,7 +1106,12 @@ declare module '@equinor/fusion-framework-module-event' {
|
|
|
1029
1106
|
>
|
|
1030
1107
|
>;
|
|
1031
1108
|
|
|
1032
|
-
|
|
1109
|
+
/**
|
|
1110
|
+
* Dispatched **after** the context has been resolved to a new item.
|
|
1111
|
+
*
|
|
1112
|
+
* Cancelable — aborting prevents the resolved item from being
|
|
1113
|
+
* set as the current context.
|
|
1114
|
+
*/
|
|
1033
1115
|
onSetContextResolved: FrameworkEvent<
|
|
1034
1116
|
FrameworkEventInit<
|
|
1035
1117
|
{
|
|
@@ -1040,7 +1122,10 @@ declare module '@equinor/fusion-framework-module-event' {
|
|
|
1040
1122
|
>
|
|
1041
1123
|
>;
|
|
1042
1124
|
|
|
1043
|
-
|
|
1125
|
+
/**
|
|
1126
|
+
* Dispatched when context validation fails and resolution is not
|
|
1127
|
+
* enabled.
|
|
1128
|
+
*/
|
|
1044
1129
|
onSetContextValidationFailed: FrameworkEvent<
|
|
1045
1130
|
FrameworkEventInit<
|
|
1046
1131
|
{
|
|
@@ -1050,7 +1135,9 @@ declare module '@equinor/fusion-framework-module-event' {
|
|
|
1050
1135
|
>
|
|
1051
1136
|
>;
|
|
1052
1137
|
|
|
1053
|
-
|
|
1138
|
+
/**
|
|
1139
|
+
* Dispatched when context resolution fails with an error.
|
|
1140
|
+
*/
|
|
1054
1141
|
onSetContextResolveFailed: FrameworkEvent<
|
|
1055
1142
|
FrameworkEventInit<
|
|
1056
1143
|
{
|
|
@@ -25,7 +25,7 @@ export type GetContextParameters = { id: string };
|
|
|
25
25
|
* @template ContextItem The type of the context item managed by the client.
|
|
26
26
|
* @extends Observable<ContextItem | null | undefined>
|
|
27
27
|
*
|
|
28
|
-
* @todo - should this have `undefined` as a valid value?
|
|
28
|
+
* @todo(#5116) - should this have `undefined` as a valid value?
|
|
29
29
|
*
|
|
30
30
|
* @example
|
|
31
31
|
* ```typescript
|
|
@@ -70,6 +70,10 @@ export class ContextClient extends Observable<ContextItem | null | undefined> {
|
|
|
70
70
|
return this.#client;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* Creates a new `ContextClient`.
|
|
75
|
+
* @param options - Query constructor options used to fetch a `ContextItem` by its ID.
|
|
76
|
+
*/
|
|
73
77
|
constructor(options: QueryCtorOptions<ContextItem, GetContextParameters>) {
|
|
74
78
|
super((observer) => this.#currentContext$.subscribe(observer));
|
|
75
79
|
this.#client = new Query(options);
|
|
@@ -85,10 +89,11 @@ export class ContextClient extends Observable<ContextItem | null | undefined> {
|
|
|
85
89
|
* @param idOrItem - The context identifier (string), a `ContextItem`, or `null`. If omitted, the current context may be cleared.
|
|
86
90
|
*/
|
|
87
91
|
public setCurrentContext(idOrItem?: string | ContextItem | null): void {
|
|
92
|
+
// resolve string identifiers to a context item before setting
|
|
88
93
|
if (typeof idOrItem === 'string') {
|
|
89
|
-
// TODO - compare context
|
|
94
|
+
// TODO(#5117) - compare context
|
|
95
|
+
// TODO(#5117) should this catch error?
|
|
90
96
|
this.resolveContext(idOrItem)
|
|
91
|
-
// TODO should this catch error?
|
|
92
97
|
.pipe(catchError(() => EMPTY))
|
|
93
98
|
.subscribe((value) => this.setCurrentContext(value));
|
|
94
99
|
/** only add context if not match */
|
|
@@ -105,10 +110,12 @@ export class ContextClient extends Observable<ContextItem | null | undefined> {
|
|
|
105
110
|
* @throws Rethrows the underlying error cause if present, otherwise throws the original error.
|
|
106
111
|
*/
|
|
107
112
|
public resolveContext(id: string): Observable<ContextItem> {
|
|
113
|
+
// unwrap the query result into the resolved context item
|
|
108
114
|
return this.#client.query({ id }).pipe(
|
|
109
115
|
map((x) => x.value),
|
|
110
116
|
// unwrap error
|
|
111
117
|
catchError((err) => {
|
|
118
|
+
// unwrap the underlying cause so callers see the original error
|
|
112
119
|
if (err.cause) {
|
|
113
120
|
throw err.cause;
|
|
114
121
|
}
|
|
@@ -131,6 +138,9 @@ export class ContextClient extends Observable<ContextItem | null | undefined> {
|
|
|
131
138
|
return fn(this.resolveContext(id));
|
|
132
139
|
}
|
|
133
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Disposes of the client, completing the internal current-context subject.
|
|
143
|
+
*/
|
|
134
144
|
public dispose(): void {
|
|
135
145
|
this.#currentContext$.complete();
|
|
136
146
|
}
|