@equinor/fusion-framework-module-context 4.0.2 → 4.0.4

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.
@@ -50,6 +50,10 @@ export declare class ContextProvider implements IContextProvider {
50
50
  validate?: boolean;
51
51
  resolve?: boolean;
52
52
  }): Observable<T>;
53
+ protected _setCurrentContext<T extends ContextItem<Record<string, unknown>> | null>(context: T, opt?: {
54
+ validate?: boolean;
55
+ resolve?: boolean;
56
+ }): Observable<T>;
53
57
  setCurrentContextAsync<T extends ContextItem<Record<string, unknown>> | null>(context: T, opt?: {
54
58
  validate?: boolean;
55
59
  resolve?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@equinor/fusion-framework-module-context",
3
- "version": "4.0.2",
3
+ "version": "4.0.4",
4
4
  "description": "",
5
5
  "main": "./dist/esm/index.js",
6
6
  "exports": {
@@ -49,5 +49,5 @@
49
49
  "@equinor/fusion-framework-module": ">=1.2.5",
50
50
  "rxjs": "^7.5.7"
51
51
  },
52
- "gitHead": "3cd1a9e01c56fc9afb72f2df474a7b066b4215c4"
52
+ "gitHead": "b369a37e2b205b8d91d9b9a8907195f7f673a344"
53
53
  }
@@ -1,13 +1,14 @@
1
+ import { EMPTY, lastValueFrom, Observable, of, Subject, Subscription, throwError } from 'rxjs';
1
2
  import {
2
- EMPTY,
3
- firstValueFrom,
4
- lastValueFrom,
5
- Observable,
6
- of,
7
- Subscription,
8
- throwError,
9
- } from 'rxjs';
10
- import { catchError, filter, map, pairwise, switchMap } from 'rxjs/operators';
3
+ catchError,
4
+ filter,
5
+ finalize,
6
+ map,
7
+ pairwise,
8
+ switchMap,
9
+ takeUntil,
10
+ tap,
11
+ } from 'rxjs/operators';
11
12
 
12
13
  import { ContextModuleConfig } from './configurator';
13
14
 
@@ -73,6 +74,8 @@ export class ContextProvider implements IContextProvider {
73
74
  #contextFilter: ContextModuleConfig['contextFilter'];
74
75
  #contextParameterFn: Required<ContextModuleConfig['contextParameterFn']>;
75
76
 
77
+ #contextQueue = new Subject<Observable<ContextItem<Record<string, unknown>>>>();
78
+
76
79
  public get contextClient() {
77
80
  return this.#contextClient;
78
81
  }
@@ -159,6 +162,15 @@ export class ContextProvider implements IContextProvider {
159
162
  })
160
163
  );
161
164
  }
165
+
166
+ this.#subscriptions.add(
167
+ this.#contextQueue
168
+ .pipe(
169
+ switchMap((next) => next),
170
+ tap((x) => console.debug('ContextProvider::#contextQueue', x))
171
+ )
172
+ .subscribe((context) => this.#contextClient.setCurrentContext(context ?? null))
173
+ );
162
174
  }
163
175
 
164
176
  public connectParentContext(
@@ -167,7 +179,7 @@ export class ContextProvider implements IContextProvider {
167
179
  ): Subscription {
168
180
  const parentContext$ = provider.currentContext$.pipe(
169
181
  // do not set context if parent has not initialized
170
- filter((x) => x !== undefined),
182
+ filter((x): x is ContextItem | null => x !== undefined),
171
183
  filter((next, index) => {
172
184
  if (opt?.skipFirst && index <= 1) {
173
185
  console.debug(
@@ -177,7 +189,7 @@ export class ContextProvider implements IContextProvider {
177
189
  );
178
190
  return false;
179
191
  }
180
- return this.currentContext !== next;
192
+ return this.currentContext?.id !== next?.id;
181
193
  }),
182
194
  switchMap(async (next) => {
183
195
  if (next) {
@@ -195,10 +207,6 @@ export class ContextProvider implements IContextProvider {
195
207
  }),
196
208
  filter((x) => !x.canceled),
197
209
  switchMap(({ next }) => {
198
- if (!next) {
199
- this.clearCurrentContext();
200
- return EMPTY;
201
- }
202
210
  return this.setCurrentContext(next, {
203
211
  validate: true,
204
212
  resolve: true,
@@ -241,12 +249,58 @@ export class ContextProvider implements IContextProvider {
241
249
  }
242
250
 
243
251
  public setCurrentContextByIdAsync(id: string): Promise<ContextItem<Record<string, unknown>>> {
244
- return firstValueFrom(this.setCurrentContextById(id));
252
+ return lastValueFrom(this.setCurrentContextById(id));
245
253
  }
246
254
 
255
+ /**
256
+ * Setting context is a complex operation, and might not happen immediately.
257
+ * When setting the context, a task is created and added to the queue.
258
+ * Once the task is completed, the returned observable will emit the value which will be the next state.
259
+ *
260
+ * Even tho this function returns a `Observable`, the task will be queued even tho nobody subscribes.
261
+ *
262
+ * If the observable is subscribe, unsubscribing __WILL__ abort the task and remove it from queue
263
+ *
264
+ * @param context context item which would be queue to set as current
265
+ */
247
266
  public setCurrentContext<T extends ContextItem<Record<string, unknown>> | null>(
248
267
  context: T,
249
268
  opt?: { validate?: boolean; resolve?: boolean }
269
+ ): Observable<T> {
270
+ /** signal for aborting the queue entry */
271
+ const abort$ = new Subject();
272
+
273
+ /** wrapper for returning an observable to the caller */
274
+ const subject$ = new Subject<T>();
275
+
276
+ const task$ = this._setCurrentContext(context, opt).pipe(
277
+ /** send context item which was set to the caller */
278
+ tap((x) => subject$.next(x)),
279
+ /** abort task on signal */
280
+ takeUntil(abort$),
281
+ /** close observable sent to caller */
282
+ finalize(() => subject$.complete()),
283
+ /** catch errors to not stall queue */
284
+ catchError((err) => {
285
+ /** send the error to the caller */
286
+ subject$.error(err);
287
+ /** skip setting any context */
288
+ return EMPTY;
289
+ })
290
+ );
291
+
292
+ /** add task to internal queue */
293
+ this.#contextQueue.next(task$ as Observable<ContextItem<Record<string, unknown>>>);
294
+
295
+ return subject$.pipe(
296
+ /** if caller subscribes, unsubscribe should abort queue entry */
297
+ finalize(() => abort$.next(true))
298
+ );
299
+ }
300
+
301
+ protected _setCurrentContext<T extends ContextItem<Record<string, unknown>> | null>(
302
+ context: T,
303
+ opt?: { validate?: boolean; resolve?: boolean }
250
304
  ): Observable<T> {
251
305
  return new Observable((subscriber) => {
252
306
  if (context === this.currentContext) {
@@ -331,7 +385,6 @@ export class ContextProvider implements IContextProvider {
331
385
  })
332
386
  )
333
387
  .subscribe((context) => {
334
- this.#contextClient.setCurrentContext(context ?? null);
335
388
  subscriber.next(context);
336
389
  subscriber.complete();
337
390
  });
@@ -342,7 +395,7 @@ export class ContextProvider implements IContextProvider {
342
395
  context: T,
343
396
  opt?: { validate?: boolean; resolve?: boolean }
344
397
  ): Promise<T> {
345
- return firstValueFrom(this.setCurrentContext(context, opt));
398
+ return lastValueFrom(this.setCurrentContext(context, opt));
346
399
  }
347
400
 
348
401
  public queryContext(search: string): Observable<Array<ContextItem>> {
@@ -422,8 +475,8 @@ export class ContextProvider implements IContextProvider {
422
475
  return lastValueFrom(this.relatedContexts(args));
423
476
  }
424
477
 
425
- public clearCurrentContext() {
426
- this.setCurrentContextAsync(null);
478
+ public clearCurrentContext(): void {
479
+ this.setCurrentContext(null);
427
480
  }
428
481
 
429
482
  dispose() {
package/src/module.ts CHANGED
@@ -68,9 +68,7 @@ export const module: ContextModule = {
68
68
  },
69
69
  complete: () => {
70
70
  // TODO - do we need to check first?
71
- provider.connectParentContext(parentProvider, {
72
- skipFirst: !!provider.currentContext,
73
- });
71
+ provider.connectParentContext(parentProvider);
74
72
  subscriber.complete();
75
73
  },
76
74
  })