@equinor/fusion-framework-module-bookmark 1.2.13 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/README.md +242 -9
- package/dist/esm/BookmarkClient.interface.js +2 -0
- package/dist/esm/BookmarkClient.interface.js.map +1 -0
- package/dist/esm/BookmarkClient.js +174 -0
- package/dist/esm/BookmarkClient.js.map +1 -0
- package/dist/esm/BookmarkConfigurator.js +348 -0
- package/dist/esm/BookmarkConfigurator.js.map +1 -0
- package/dist/esm/BookmarkProvider.actions.js +60 -0
- package/dist/esm/BookmarkProvider.actions.js.map +1 -0
- package/dist/esm/BookmarkProvider.error.js +14 -0
- package/dist/esm/BookmarkProvider.error.js.map +1 -0
- package/dist/esm/BookmarkProvider.events.js +2 -0
- package/dist/esm/BookmarkProvider.events.js.map +1 -0
- package/dist/esm/BookmarkProvider.flows.js +219 -0
- package/dist/esm/BookmarkProvider.flows.js.map +1 -0
- package/dist/esm/BookmarkProvider.js +937 -0
- package/dist/esm/BookmarkProvider.js.map +1 -0
- package/dist/esm/BookmarkProvider.reducer.js +125 -0
- package/dist/esm/BookmarkProvider.reducer.js.map +1 -0
- package/dist/esm/BookmarkProvider.selectors.js +43 -0
- package/dist/esm/BookmarkProvider.selectors.js.map +1 -0
- package/dist/esm/BookmarkProvider.store.js +19 -0
- package/dist/esm/BookmarkProvider.store.js.map +1 -0
- package/dist/esm/bookmark-config.schema.js +30 -0
- package/dist/esm/bookmark-config.schema.js.map +1 -0
- package/dist/esm/bookmark-module.js +53 -0
- package/dist/esm/bookmark-module.js.map +1 -0
- package/dist/esm/bookmark.schemas.js +38 -0
- package/dist/esm/bookmark.schemas.js.map +1 -0
- package/dist/esm/enable-bookmark.js +20 -7
- package/dist/esm/enable-bookmark.js.map +1 -1
- package/dist/esm/index.js +3 -3
- package/dist/esm/index.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/BookmarkClient.d.ts +42 -0
- package/dist/types/BookmarkClient.interface.d.ts +139 -0
- package/dist/types/BookmarkConfigurator.d.ts +100 -0
- package/dist/types/BookmarkProvider.actions.d.ts +525 -0
- package/dist/types/BookmarkProvider.d.ts +462 -0
- package/dist/types/BookmarkProvider.error.d.ts +9 -0
- package/dist/types/BookmarkProvider.events.d.ts +78 -0
- package/dist/types/BookmarkProvider.flows.d.ts +79 -0
- package/dist/types/BookmarkProvider.reducer.d.ts +302 -0
- package/dist/types/BookmarkProvider.selectors.d.ts +34 -0
- package/dist/types/BookmarkProvider.store.d.ts +30 -0
- package/dist/types/bookmark-config.schema.d.ts +118 -0
- package/dist/types/{module.d.ts → bookmark-module.d.ts} +3 -3
- package/dist/types/bookmark.schemas.d.ts +760 -0
- package/dist/types/enable-bookmark.d.ts +7 -6
- package/dist/types/index.d.ts +4 -3
- package/dist/types/types.d.ts +29 -65
- package/dist/types/version.d.ts +1 -1
- package/package.json +11 -5
- package/src/BookmarkClient.interface.ts +157 -0
- package/src/BookmarkClient.ts +226 -0
- package/src/BookmarkConfigurator.ts +374 -0
- package/src/BookmarkProvider.actions.ts +131 -0
- package/src/BookmarkProvider.error.ts +21 -0
- package/src/BookmarkProvider.events.ts +89 -0
- package/src/BookmarkProvider.flows.ts +426 -0
- package/src/BookmarkProvider.reducer.ts +141 -0
- package/src/BookmarkProvider.selectors.ts +55 -0
- package/src/BookmarkProvider.store.ts +52 -0
- package/src/BookmarkProvider.ts +1308 -0
- package/src/bookmark-config.schema.ts +41 -0
- package/src/bookmark-module.ts +70 -0
- package/src/bookmark.schemas.ts +50 -0
- package/src/enable-bookmark.ts +13 -20
- package/src/index.ts +15 -7
- package/src/types.ts +46 -73
- package/src/version.ts +1 -1
- package/dist/esm/bookmark-config-builder.js +0 -131
- package/dist/esm/bookmark-config-builder.js.map +0 -1
- package/dist/esm/bookmark-provider.js +0 -155
- package/dist/esm/bookmark-provider.js.map +0 -1
- package/dist/esm/client/bookmarkActions.js +0 -10
- package/dist/esm/client/bookmarkActions.js.map +0 -1
- package/dist/esm/client/bookmarkClient.js +0 -259
- package/dist/esm/client/bookmarkClient.js.map +0 -1
- package/dist/esm/client/bookmarkFlows.js +0 -31
- package/dist/esm/client/bookmarkFlows.js.map +0 -1
- package/dist/esm/client/bookmarkReducer.js +0 -29
- package/dist/esm/client/bookmarkReducer.js.map +0 -1
- package/dist/esm/configurator.js +0 -155
- package/dist/esm/configurator.js.map +0 -1
- package/dist/esm/module.js +0 -27
- package/dist/esm/module.js.map +0 -1
- package/dist/esm/utils/handle-url.js +0 -11
- package/dist/esm/utils/handle-url.js.map +0 -1
- package/dist/esm/utils/index.js +0 -2
- package/dist/esm/utils/index.js.map +0 -1
- package/dist/types/bookmark-config-builder.d.ts +0 -85
- package/dist/types/bookmark-provider.d.ts +0 -169
- package/dist/types/client/bookmarkActions.d.ts +0 -27
- package/dist/types/client/bookmarkClient.d.ts +0 -53
- package/dist/types/client/bookmarkFlows.d.ts +0 -22
- package/dist/types/client/bookmarkReducer.d.ts +0 -5
- package/dist/types/configurator.d.ts +0 -75
- package/dist/types/utils/handle-url.d.ts +0 -2
- package/dist/types/utils/index.d.ts +0 -1
- package/src/bookmark-config-builder.ts +0 -177
- package/src/bookmark-provider.ts +0 -344
- package/src/client/bookmarkActions.ts +0 -44
- package/src/client/bookmarkClient.ts +0 -327
- package/src/client/bookmarkFlows.ts +0 -67
- package/src/client/bookmarkReducer.ts +0 -35
- package/src/configurator.ts +0 -235
- package/src/module.ts +0 -40
- package/src/utils/handle-url.ts +0 -13
- package/src/utils/index.ts +0 -1
|
@@ -0,0 +1,1308 @@
|
|
|
1
|
+
import { Observable, Subscription, forkJoin, from, lastValueFrom, of } from 'rxjs';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
filter,
|
|
5
|
+
switchMap,
|
|
6
|
+
tap,
|
|
7
|
+
map,
|
|
8
|
+
mergeScan,
|
|
9
|
+
timeout,
|
|
10
|
+
raceWith,
|
|
11
|
+
first,
|
|
12
|
+
catchError,
|
|
13
|
+
} from 'rxjs/operators';
|
|
14
|
+
|
|
15
|
+
import { Draft, Patch, produce, enablePatches, castDraft } from 'immer';
|
|
16
|
+
|
|
17
|
+
import { v4 as generateGUID } from 'uuid';
|
|
18
|
+
|
|
19
|
+
import deepEqual from 'fast-deep-equal/es6';
|
|
20
|
+
|
|
21
|
+
import { FrameworkEvent, FrameworkEventInitType } from '@equinor/fusion-framework-module-event';
|
|
22
|
+
|
|
23
|
+
import { isFailureAction } from '@equinor/fusion-observable';
|
|
24
|
+
|
|
25
|
+
import { SemanticVersion } from '@equinor/fusion-framework-module';
|
|
26
|
+
|
|
27
|
+
import type {
|
|
28
|
+
Bookmark,
|
|
29
|
+
BookmarkData,
|
|
30
|
+
BookmarkWithoutData,
|
|
31
|
+
Bookmarks,
|
|
32
|
+
BookmarkModuleConfig,
|
|
33
|
+
} from './types';
|
|
34
|
+
|
|
35
|
+
import { BookmarkNew, BookmarkUpdate, type IBookmarkClient } from './BookmarkClient.interface';
|
|
36
|
+
import { BookmarkActions, bookmarkActions } from './BookmarkProvider.actions';
|
|
37
|
+
import { BookmarkState, createBookmarkStore, type BookmarkStore } from './BookmarkProvider.store';
|
|
38
|
+
import {
|
|
39
|
+
activeBookmarkSelector,
|
|
40
|
+
bookmarkSelector,
|
|
41
|
+
bookmarksSelector,
|
|
42
|
+
errorsSelector,
|
|
43
|
+
} from './BookmarkProvider.selectors';
|
|
44
|
+
|
|
45
|
+
import { BookmarkFlowError, BookmarkProviderError } from './BookmarkProvider.error';
|
|
46
|
+
|
|
47
|
+
import { BookmarkProviderEventMap } from './BookmarkProvider.events';
|
|
48
|
+
import { version } from './version';
|
|
49
|
+
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
51
|
+
export type BookmarkPayloadGenerator<TData extends BookmarkData = any> = (
|
|
52
|
+
payload?: Draft<Partial<TData>> | null,
|
|
53
|
+
initial?: Partial<TData> | null,
|
|
54
|
+
) => Promise<Partial<TData> | void> | Partial<TData> | void;
|
|
55
|
+
|
|
56
|
+
export type BookmarkUpdateOptions = {
|
|
57
|
+
excludePayloadGeneration?: boolean;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type BookmarkCreateArgs<T extends BookmarkData> = Omit<
|
|
61
|
+
BookmarkNew<T>,
|
|
62
|
+
'appKey' | 'contextId' | 'sourceSystem'
|
|
63
|
+
> &
|
|
64
|
+
Partial<Pick<BookmarkNew<T>, 'appKey'>>;
|
|
65
|
+
|
|
66
|
+
enablePatches();
|
|
67
|
+
|
|
68
|
+
const defaultTimeout = 2 * 60 * 1000; // 2 minutes
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The `BookmarkProvider` class is responsible for managing bookmarks in the application.
|
|
72
|
+
* It provides methods for creating, updating, and removing bookmarks, as well as managing the current bookmark and the list of bookmarks.
|
|
73
|
+
*
|
|
74
|
+
* The `BookmarkProvider` uses a `BookmarkStore` to manage the state of the bookmarks, and an `IBookmarkClient` to interact with the backend API.
|
|
75
|
+
*
|
|
76
|
+
* The `BookmarkProvider` also supports event listeners for various bookmark-related events, such as when the current bookmark changes or when a bookmark is created, updated, or removed.
|
|
77
|
+
*/
|
|
78
|
+
export class BookmarkProvider {
|
|
79
|
+
/** provided configuration for the bookmark provider */
|
|
80
|
+
#config: BookmarkModuleConfig;
|
|
81
|
+
|
|
82
|
+
/** state machine of the bookmark provider */
|
|
83
|
+
#store: BookmarkStore;
|
|
84
|
+
|
|
85
|
+
/** collection of callback for creating bookmark payload */
|
|
86
|
+
#payloadGenerators: Array<BookmarkPayloadGenerator> = [];
|
|
87
|
+
|
|
88
|
+
/** collection of subscriptions for bookmark events */
|
|
89
|
+
#subscriptions = new Subscription();
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Gets the semantic version of the bookmark provider.
|
|
93
|
+
*/
|
|
94
|
+
public get version(): SemanticVersion {
|
|
95
|
+
return new SemanticVersion(version);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public get filters(): BookmarkModuleConfig['filters'] {
|
|
99
|
+
// TODO - freeze the config object?
|
|
100
|
+
return this.#config.filters;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Gets the `IBookmarkClient` instance used by this `BookmarkProvider`.
|
|
105
|
+
*/
|
|
106
|
+
public get client(): IBookmarkClient {
|
|
107
|
+
return this.#config.client;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Gets the currently active bookmark (if any).
|
|
112
|
+
*/
|
|
113
|
+
public get currentBookmark$(): Observable<Bookmark | null> {
|
|
114
|
+
return this.#store.select(activeBookmarkSelector, deepEqual);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Gets an observable that emits the current list of bookmarks.
|
|
119
|
+
*/
|
|
120
|
+
public get bookmarks$(): Observable<Array<BookmarkWithoutData>> {
|
|
121
|
+
this.getAllBookmarksAsync();
|
|
122
|
+
return this.#store.select(bookmarksSelector, deepEqual);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Gets the current list of bookmarks.
|
|
127
|
+
*/
|
|
128
|
+
public get bookmarks(): Array<BookmarkWithoutData> {
|
|
129
|
+
return bookmarksSelector(this.#store.value);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Gets the currently active bookmark.
|
|
134
|
+
*/
|
|
135
|
+
public get currentBookmark(): Bookmark | null | undefined {
|
|
136
|
+
return activeBookmarkSelector(this.#store.value);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Represents an observable stream of the bookmark status.
|
|
141
|
+
*/
|
|
142
|
+
public get status$(): Observable<BookmarkState['status']> {
|
|
143
|
+
return this.#store.select((x) => x.status);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Gets an observable that emits the current list of bookmark errors.
|
|
148
|
+
*/
|
|
149
|
+
public get errors$(): Observable<Array<BookmarkFlowError>> {
|
|
150
|
+
// TODO - add deep diff
|
|
151
|
+
return this.#store.select(errorsSelector, deepEqual);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Determines whether there are any bookmark creators configured.
|
|
156
|
+
* `true` if there are any bookmark creators configured, `false` otherwise.
|
|
157
|
+
*/
|
|
158
|
+
public get hasBookmarkCreators(): boolean {
|
|
159
|
+
return this.#payloadGenerators.length > 0;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Gets the source system value from the configuration.
|
|
164
|
+
*/
|
|
165
|
+
public get sourceSystem(): BookmarkModuleConfig['sourceSystem'] {
|
|
166
|
+
return this.#config.sourceSystem;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Gets the resolved application from the bookmark module configuration.
|
|
171
|
+
*/
|
|
172
|
+
public get resolvedApplication(): BookmarkModuleConfig['resolve']['application'] {
|
|
173
|
+
return this.#config.resolve.application;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Gets the resolved context from the bookmark module configuration.
|
|
178
|
+
*/
|
|
179
|
+
public get resolvedContext(): BookmarkModuleConfig['resolve']['context'] {
|
|
180
|
+
return this.#config.resolve.context;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
protected get _apiClient(): IBookmarkClient {
|
|
184
|
+
return this.#config.client;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* configured logger instance.
|
|
189
|
+
*/
|
|
190
|
+
protected get _log(): BookmarkModuleConfig['log'] {
|
|
191
|
+
return this.#config.log;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* configured resolvers
|
|
196
|
+
*/
|
|
197
|
+
protected get _resolve(): BookmarkModuleConfig['resolve'] {
|
|
198
|
+
return this.#config.resolve;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* configured event provider.
|
|
203
|
+
*/
|
|
204
|
+
protected get _event(): BookmarkModuleConfig['eventProvider'] {
|
|
205
|
+
return this.#config.eventProvider;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* configured parent bookmark provider
|
|
210
|
+
*/
|
|
211
|
+
protected get _parent(): BookmarkModuleConfig['parent'] {
|
|
212
|
+
return this.#config.parent;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Constructs a new `BookmarkProvider` instance.
|
|
217
|
+
*
|
|
218
|
+
* @param config - The configuration for the bookmark module, including the client, initial state, and optional parent provider.
|
|
219
|
+
*/
|
|
220
|
+
constructor(config: BookmarkModuleConfig) {
|
|
221
|
+
this.#config = config;
|
|
222
|
+
|
|
223
|
+
this.#store = createBookmarkStore({
|
|
224
|
+
client: config.client,
|
|
225
|
+
initial: { currentBookmark: this._parent?.currentBookmark ?? null },
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// add teardown logic for disposing the store
|
|
229
|
+
this.#subscriptions.add(() => this.#store.complete());
|
|
230
|
+
|
|
231
|
+
// subscribe to the store actions and log them
|
|
232
|
+
this.#subscriptions.add(
|
|
233
|
+
this.#store.action$.subscribe((action) => {
|
|
234
|
+
if (isFailureAction(action)) {
|
|
235
|
+
this._log?.error(`Action: ${action.type}`, action);
|
|
236
|
+
} else {
|
|
237
|
+
this._log?.debug(`Action: ${action.type}`, action);
|
|
238
|
+
}
|
|
239
|
+
}),
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
// subscribe to the current bookmark changes and dispatch events
|
|
243
|
+
this.#subscriptions.add(
|
|
244
|
+
this.#store.select(activeBookmarkSelector).subscribe((bookmark) => {
|
|
245
|
+
this._dispatchEvent('onCurrentBookmarkChanged', { detail: bookmark });
|
|
246
|
+
}),
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
// subscribe to child bookmark provider changes
|
|
250
|
+
// TODO - add support for disabling this feature
|
|
251
|
+
if (this._event) {
|
|
252
|
+
this._event.addEventListener('onCurrentBookmarkChanged', (event) => {
|
|
253
|
+
// only update the current bookmark if the event source is not this provider
|
|
254
|
+
if (event.source !== this) {
|
|
255
|
+
this.setCurrentBookmarkAsync(event.detail);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (this._parent) {
|
|
261
|
+
// if a parent bookmark provider is configured, subscribe to its current bookmark changes
|
|
262
|
+
try {
|
|
263
|
+
this.#subscriptions.add(
|
|
264
|
+
this._parent.currentBookmark$.subscribe((bookmark) => {
|
|
265
|
+
this.setCurrentBookmarkAsync(bookmark);
|
|
266
|
+
}),
|
|
267
|
+
);
|
|
268
|
+
} catch (e) {
|
|
269
|
+
this._log?.error('Failed to subscribe to parent bookmark provider', e);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Registers an event listener for the specified event on the BookmarkProvider.
|
|
276
|
+
*
|
|
277
|
+
* @param eventName - The name of the event to listen for. Must be a key of the `BookmarkProviderEventMap` type.
|
|
278
|
+
* @param callback - The callback function to be invoked when the event is triggered.
|
|
279
|
+
* The callback will receive the event object as its parameter, which will be of the type corresponding to the `eventName`.
|
|
280
|
+
* @returns A function that can be called to remove the event listener.
|
|
281
|
+
*/
|
|
282
|
+
public on<TType extends keyof BookmarkProviderEventMap>(
|
|
283
|
+
eventName: TType,
|
|
284
|
+
callback: (event: BookmarkProviderEventMap[TType]) => void,
|
|
285
|
+
): VoidFunction {
|
|
286
|
+
if (!this._event) {
|
|
287
|
+
this._log?.warn('Failed to register event listener, event provider not configured');
|
|
288
|
+
return () => {};
|
|
289
|
+
}
|
|
290
|
+
return this._event?.addEventListener(eventName, callback);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Adds a new payload generator function to the BookmarkProvider.
|
|
295
|
+
* The payload generator function will be used to generate the payload for a bookmark-related action.
|
|
296
|
+
*
|
|
297
|
+
* @param fn - The payload generator function to add. It should be of type `PayloadGenerator<T>`, where `T` is the type of the bookmark data.
|
|
298
|
+
* @returns A function that can be called to remove the added payload generator.
|
|
299
|
+
*/
|
|
300
|
+
public addPayloadGenerator<T extends BookmarkData>(
|
|
301
|
+
fn: BookmarkPayloadGenerator<T>,
|
|
302
|
+
): VoidFunction {
|
|
303
|
+
this._log?.debug(`adding bookmark payload generator: ${fn.name}`);
|
|
304
|
+
|
|
305
|
+
// register the payload generator function
|
|
306
|
+
this.#payloadGenerators.push(fn as BookmarkPayloadGenerator);
|
|
307
|
+
|
|
308
|
+
// dispatch event to notify listeners of the added payload generator
|
|
309
|
+
this._dispatchEvent('onBookmarkPayloadCreatorAdded', {
|
|
310
|
+
detail: fn as BookmarkPayloadGenerator,
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// return a function to remove the added payload generator
|
|
314
|
+
return () => {
|
|
315
|
+
this._log?.debug(`removing bookmark payload generator: ${fn.name}`);
|
|
316
|
+
this.#payloadGenerators = this.#payloadGenerators.filter((g) => g !== fn);
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Generates a payload by applying registered generator functions to an accumulator object.
|
|
322
|
+
*
|
|
323
|
+
* @param initial - The initial value of the accumulator object.
|
|
324
|
+
* @returns An observable that emits the generated payload.
|
|
325
|
+
* @template T - The type of the generated payload.
|
|
326
|
+
*/
|
|
327
|
+
public generatePayload<T extends BookmarkData>(
|
|
328
|
+
initial?: Partial<T> | null,
|
|
329
|
+
): Observable<T | null | undefined> {
|
|
330
|
+
this._log?.debug(`generating payload`, initial);
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Generates a payload by applying a generator function to an accumulator object.
|
|
334
|
+
* The generator function can modify the accumulator object using immer's draft mechanism.
|
|
335
|
+
*
|
|
336
|
+
* @param acc - The accumulator object.
|
|
337
|
+
* @param generator - The generator function that modifies the accumulator object.
|
|
338
|
+
* @returns A promise that resolves to the generated payload.
|
|
339
|
+
*/
|
|
340
|
+
const generatePayload = async (acc: Partial<T>, generator: BookmarkPayloadGenerator<T>) => {
|
|
341
|
+
// produce payload from generator
|
|
342
|
+
return produce<Partial<T>, Draft<Partial<T>> | null>(
|
|
343
|
+
acc,
|
|
344
|
+
async (draft) => {
|
|
345
|
+
// execute the generator
|
|
346
|
+
const result = await Promise.resolve(generator(draft, initial));
|
|
347
|
+
if (result) {
|
|
348
|
+
this._log?.warn(
|
|
349
|
+
`bookmark data generator ${generator.name} returned a value, but it should not do this, since the data is an immer draft object`,
|
|
350
|
+
);
|
|
351
|
+
return castDraft(result);
|
|
352
|
+
} else if (result === null) {
|
|
353
|
+
this._log?.info(
|
|
354
|
+
`bookmark data generator ${generator.name} wish to clear the bookmark data`,
|
|
355
|
+
);
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
(patches: Patch[]) => {
|
|
360
|
+
if (patches.length > 0) {
|
|
361
|
+
this._log?.debug(
|
|
362
|
+
`bookmark data generator ${generator.name} produced patches`,
|
|
363
|
+
patches,
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
);
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Observable that emits the generated payload.
|
|
372
|
+
*/
|
|
373
|
+
const result$ = from(this.#payloadGenerators).pipe(
|
|
374
|
+
mergeScan(generatePayload, initial ?? {}, 1),
|
|
375
|
+
tap((payload) => this._log?.debug(`generated payload`, { initial, payload })),
|
|
376
|
+
) as Observable<T | null | undefined>;
|
|
377
|
+
|
|
378
|
+
return result$;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Retrieves a bookmark with the specified bookmarkId and returns an observable that emits the combined result of fetching the bookmark and bookmark data.
|
|
383
|
+
* @template T The type of the bookmark payload.
|
|
384
|
+
* @param bookmarkId The unique identifier of the bookmark.
|
|
385
|
+
* @param options An optional object that allows excluding the bookmark payload from the result.
|
|
386
|
+
* @returns An observable that emits the combined result of fetching the bookmark and bookmark data.
|
|
387
|
+
*/
|
|
388
|
+
public getBookmark<T extends BookmarkData>(
|
|
389
|
+
bookmarkId: string,
|
|
390
|
+
options?: { excludePayload?: boolean },
|
|
391
|
+
) {
|
|
392
|
+
this._log?.debug(`fetching bookmark: ${bookmarkId}`, options);
|
|
393
|
+
|
|
394
|
+
// fetch the bookmark and bookmark data
|
|
395
|
+
return forkJoin({
|
|
396
|
+
bookmark: this._getBookmarkInfo(bookmarkId),
|
|
397
|
+
payload: options?.excludePayload ? of({}) : this._getBookmarkData(bookmarkId),
|
|
398
|
+
}).pipe(
|
|
399
|
+
catchError((error) => {
|
|
400
|
+
this._log?.error(`Failed to fetch bookmark: ${bookmarkId}`, error);
|
|
401
|
+
throw error;
|
|
402
|
+
}),
|
|
403
|
+
// combine the bookmark and payload into a single object
|
|
404
|
+
map(({ bookmark, payload }) => Object.assign({}, bookmark, { payload }) as Bookmark<T>),
|
|
405
|
+
tap((bookmark) => {
|
|
406
|
+
this._log?.debug(`Bookmark fetched: ${bookmark.id}`, bookmark);
|
|
407
|
+
}),
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Retrieves a bookmark asynchronously.
|
|
413
|
+
*
|
|
414
|
+
* @param id - The ID of the bookmark to retrieve.
|
|
415
|
+
* @param options - Optional parameters for the retrieval.
|
|
416
|
+
* @param options.excludePayload - Specifies whether to exclude the payload from the bookmark.
|
|
417
|
+
* @returns A promise that resolves to the retrieved bookmark, or null if the bookmark is not found.
|
|
418
|
+
*/
|
|
419
|
+
public getBookmarkAsync<T extends BookmarkData>(
|
|
420
|
+
id: string,
|
|
421
|
+
options?: { excludePayload?: boolean },
|
|
422
|
+
): Promise<Bookmark<T> | null> {
|
|
423
|
+
return lastValueFrom(this.getBookmark<T>(id, options));
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* Asynchronously retrieves all bookmarks from the store.
|
|
428
|
+
*
|
|
429
|
+
* @returns A Promise that resolves to the Bookmarks object containing all bookmarks.
|
|
430
|
+
*/
|
|
431
|
+
public getAllBookmarks(): Observable<Bookmarks> {
|
|
432
|
+
return new Observable<Bookmarks>((observer) => {
|
|
433
|
+
this._log?.debug(`fetching all bookmarks`);
|
|
434
|
+
|
|
435
|
+
// generate the filter parameters
|
|
436
|
+
const filter$ = forkJoin({
|
|
437
|
+
sourceSystem: of(this.sourceSystem),
|
|
438
|
+
appKey: this.#config.filters?.application
|
|
439
|
+
? from(this._resolve.application()).pipe(map((x) => x?.appKey))
|
|
440
|
+
: of(undefined),
|
|
441
|
+
contextId: this.#config.filters?.context
|
|
442
|
+
? from(this._resolve.context()).pipe(map((x) => x?.id))
|
|
443
|
+
: of(undefined),
|
|
444
|
+
}).pipe(
|
|
445
|
+
catchError((err) => {
|
|
446
|
+
const error = new BookmarkProviderError(
|
|
447
|
+
'Could not fetch bookmarks, failed to resolve filter parameters',
|
|
448
|
+
err,
|
|
449
|
+
);
|
|
450
|
+
this._log?.error(error.message, error);
|
|
451
|
+
throw error;
|
|
452
|
+
}),
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
// execute the fetch bookmarks action when the filter parameters are resolved
|
|
456
|
+
observer.add(
|
|
457
|
+
filter$.subscribe((filter) => {
|
|
458
|
+
this.#store.next(bookmarkActions.fetchBookmarks(filter));
|
|
459
|
+
}),
|
|
460
|
+
);
|
|
461
|
+
|
|
462
|
+
// monitor the failure case when fetching bookmarks
|
|
463
|
+
const failure$ = this.#store.action$.pipe(
|
|
464
|
+
filter(bookmarkActions.fetchBookmarks.failure.match),
|
|
465
|
+
map((action) => {
|
|
466
|
+
this._log?.error('Failed to fetch bookmarks', action.payload);
|
|
467
|
+
throw new BookmarkProviderError('Failed to fetch bookmarks', action.payload);
|
|
468
|
+
}),
|
|
469
|
+
timeout({
|
|
470
|
+
each: defaultTimeout,
|
|
471
|
+
with: () => {
|
|
472
|
+
throw new BookmarkProviderError('Timeout while fetching bookmarks');
|
|
473
|
+
},
|
|
474
|
+
}),
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
// monitor the success case when fetching bookmarks
|
|
478
|
+
const request$ = this.#store.action$.pipe(
|
|
479
|
+
filter(bookmarkActions.fetchBookmarks.success.match),
|
|
480
|
+
map((a) => a.payload),
|
|
481
|
+
first(),
|
|
482
|
+
tap((bookmarks) => {
|
|
483
|
+
this._log?.debug(`fetched all bookmarks`, bookmarks);
|
|
484
|
+
}),
|
|
485
|
+
raceWith(failure$),
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
request$.subscribe(observer);
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* Asynchronously retrieves all bookmarks from the store.
|
|
494
|
+
*
|
|
495
|
+
* @returns A Promise that resolves to the Bookmarks object containing all bookmarks.
|
|
496
|
+
*/
|
|
497
|
+
public async getAllBookmarksAsync(): Promise<Bookmarks> {
|
|
498
|
+
return lastValueFrom(this.getAllBookmarks());
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Sets the current bookmark to the specified bookmark or ID.
|
|
503
|
+
* If the bookmark is already the current bookmark, it returns the bookmark directly.
|
|
504
|
+
* Otherwise, it emits the next bookmark or null through an observable.
|
|
505
|
+
*
|
|
506
|
+
* By proving `null` as the bookmark_or_id, the current bookmark will be cleared.
|
|
507
|
+
*
|
|
508
|
+
* @param bookmark_or_id - The bookmark or ID to set as the current bookmark.
|
|
509
|
+
* @returns An observable that emits the next bookmark or null.
|
|
510
|
+
* @template T - The type of the bookmark data.
|
|
511
|
+
*/
|
|
512
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
513
|
+
public setCurrentBookmark<T extends BookmarkData = any>(
|
|
514
|
+
bookmark_or_id: Bookmark<T> | string | null,
|
|
515
|
+
): Observable<Bookmark<T> | null> {
|
|
516
|
+
// check if the bookmark is already the current bookmark
|
|
517
|
+
if (bookmark_or_id == this.currentBookmark) return of(bookmark_or_id);
|
|
518
|
+
|
|
519
|
+
this._log?.debug(`setting current bookmark`, bookmark_or_id);
|
|
520
|
+
|
|
521
|
+
// resolve the next bookmark
|
|
522
|
+
const next$: Observable<Bookmark<T> | null> =
|
|
523
|
+
typeof bookmark_or_id === 'string'
|
|
524
|
+
? this.getBookmark<T>(bookmark_or_id)
|
|
525
|
+
: of(bookmark_or_id ?? null);
|
|
526
|
+
|
|
527
|
+
return next$.pipe(
|
|
528
|
+
switchMap(async (next) => {
|
|
529
|
+
const current = this.currentBookmark;
|
|
530
|
+
const { type, canceled } = await this._dispatchEvent('onCurrentBookmarkChange', {
|
|
531
|
+
detail: { current, next },
|
|
532
|
+
});
|
|
533
|
+
if (canceled) {
|
|
534
|
+
const error = new BookmarkProviderError(
|
|
535
|
+
`event: ${type} was canceled by listener for change to ${next?.id ?? 'none'}, from ${current?.id ?? 'none'}`,
|
|
536
|
+
);
|
|
537
|
+
this._log?.info(error.message);
|
|
538
|
+
throw error;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return next;
|
|
542
|
+
}),
|
|
543
|
+
tap((next) => {
|
|
544
|
+
// set the current bookmark in the store
|
|
545
|
+
this.#store.next(bookmarkActions.setCurrentBookmark(next));
|
|
546
|
+
}),
|
|
547
|
+
catchError((err) => {
|
|
548
|
+
const error = new BookmarkProviderError(
|
|
549
|
+
'Could not set current bookmark, resolve of bookmark failed',
|
|
550
|
+
err,
|
|
551
|
+
);
|
|
552
|
+
this._log?.error(error.message, error);
|
|
553
|
+
throw error;
|
|
554
|
+
}),
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Sets the current bookmark.
|
|
560
|
+
* This method dispatches an event to notify listeners of a change in the current bookmark,
|
|
561
|
+
* and then dispatches a request to set the active bookmark in the application state.
|
|
562
|
+
*
|
|
563
|
+
* The request will be canceled if the subscription is unsubscribed before the request completes.
|
|
564
|
+
*
|
|
565
|
+
* @param bookmarkId - The ID of the bookmark to set as the current bookmark.
|
|
566
|
+
* @returns A subscription to the operation that sets the current bookmark.
|
|
567
|
+
*/
|
|
568
|
+
public setCurrentBookmarkAsync<T extends BookmarkData>(
|
|
569
|
+
bookmark_or_id: Bookmark<T> | string | null,
|
|
570
|
+
): Promise<Bookmark<T> | null> {
|
|
571
|
+
return lastValueFrom(this.setCurrentBookmark<T>(bookmark_or_id));
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/**
|
|
575
|
+
* Creates a new bookmark with the provided bookmark data.
|
|
576
|
+
* @template T - The type of bookmark data.
|
|
577
|
+
* @param {BookmarkCreateArgs<T>} newBookmarkData - The data for creating the bookmark.
|
|
578
|
+
* @returns {Observable<Bookmark<T>>} - An observable that emits the created bookmark.
|
|
579
|
+
*/
|
|
580
|
+
public createBookmark<T extends BookmarkData>(
|
|
581
|
+
newBookmarkData: BookmarkCreateArgs<T>,
|
|
582
|
+
): Observable<Bookmark<T>> {
|
|
583
|
+
return new Observable<Bookmark<T>>((subscriber) => {
|
|
584
|
+
const { ref, action$ } = this._useScopedActions();
|
|
585
|
+
|
|
586
|
+
this._log?.debug(`creating new bookmark, ref: ${ref}`, newBookmarkData);
|
|
587
|
+
|
|
588
|
+
// resolve the bookmark
|
|
589
|
+
const bookmark$ = forkJoin({
|
|
590
|
+
appKey: newBookmarkData.appKey
|
|
591
|
+
? of(newBookmarkData.appKey)
|
|
592
|
+
: from(this._resolve.application()).pipe(
|
|
593
|
+
map((app) => {
|
|
594
|
+
if (!app?.appKey) {
|
|
595
|
+
throw new BookmarkProviderError(
|
|
596
|
+
'Failed to resolve application key',
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
return app.appKey;
|
|
600
|
+
}),
|
|
601
|
+
),
|
|
602
|
+
contextId: this._resolve.context().then((context) => context?.id),
|
|
603
|
+
payload: this.generatePayload<T>(newBookmarkData.payload),
|
|
604
|
+
sourceSystem: of(this.sourceSystem),
|
|
605
|
+
}).pipe(
|
|
606
|
+
catchError((err) => {
|
|
607
|
+
const error = new BookmarkProviderError(
|
|
608
|
+
'Could not create new bookmark, failed to resolve bookmark data',
|
|
609
|
+
err,
|
|
610
|
+
);
|
|
611
|
+
this._log?.error(error.message, error);
|
|
612
|
+
throw error;
|
|
613
|
+
}),
|
|
614
|
+
// merge the resolved data with the new bookmark data
|
|
615
|
+
map(
|
|
616
|
+
(resolvedData) =>
|
|
617
|
+
({
|
|
618
|
+
...newBookmarkData,
|
|
619
|
+
...resolvedData,
|
|
620
|
+
}) as BookmarkNew<T>,
|
|
621
|
+
),
|
|
622
|
+
);
|
|
623
|
+
|
|
624
|
+
// notify listeners that a bookmark is about to be created
|
|
625
|
+
const dispatch$ = bookmark$.pipe(
|
|
626
|
+
switchMap(async (bookmark) => {
|
|
627
|
+
// notify listeners that a bookmark is about to be created
|
|
628
|
+
const { canceled, type } = await this._dispatchEvent('onBookmarkCreate', {
|
|
629
|
+
detail: bookmark,
|
|
630
|
+
cancelable: true,
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
// throw an error if the event is canceled
|
|
634
|
+
if (canceled) {
|
|
635
|
+
const error = new BookmarkProviderError(
|
|
636
|
+
`event: ${type} was canceled by listener for creating bookmark: ${ref}`,
|
|
637
|
+
);
|
|
638
|
+
this._log?.info(error.message);
|
|
639
|
+
throw error;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
return bookmark;
|
|
643
|
+
}),
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
// execute the create bookmark action when the bookmark is resolved and not canceled
|
|
647
|
+
subscriber.add(
|
|
648
|
+
dispatch$.subscribe({
|
|
649
|
+
error: (error) => subscriber.error(error),
|
|
650
|
+
next: (newBookmark) => {
|
|
651
|
+
// request the store to create the bookmark
|
|
652
|
+
this.#store.next(bookmarkActions.createBookmark(newBookmark, { ref }));
|
|
653
|
+
},
|
|
654
|
+
}),
|
|
655
|
+
);
|
|
656
|
+
|
|
657
|
+
// monitor the failure case when creating a bookmark
|
|
658
|
+
const failure$ = action$.pipe(
|
|
659
|
+
filter(bookmarkActions.createBookmark.failure.match),
|
|
660
|
+
map(({ payload: cause }) => {
|
|
661
|
+
const error = new BookmarkProviderError(`Failed to create bookmark: ${ref}`, {
|
|
662
|
+
cause,
|
|
663
|
+
});
|
|
664
|
+
this._log?.warn(error.message);
|
|
665
|
+
throw error;
|
|
666
|
+
}),
|
|
667
|
+
timeout({
|
|
668
|
+
each: defaultTimeout,
|
|
669
|
+
with: () => {
|
|
670
|
+
throw new BookmarkProviderError(`Timeout while creating bookmark: ${ref}`);
|
|
671
|
+
},
|
|
672
|
+
}),
|
|
673
|
+
);
|
|
674
|
+
|
|
675
|
+
// monitor the success case when creating a bookmark
|
|
676
|
+
const request$ = action$.pipe(
|
|
677
|
+
filter(bookmarkActions.createBookmark.success.match),
|
|
678
|
+
map(({ payload }) => payload as Bookmark<T>),
|
|
679
|
+
first(),
|
|
680
|
+
tap((bookmark) => {
|
|
681
|
+
this._log?.info(`Bookmark created: ${bookmark.id}, ref: ${ref}`);
|
|
682
|
+
this._dispatchEvent('onBookmarkCreated', { detail: bookmark });
|
|
683
|
+
}),
|
|
684
|
+
raceWith(failure$),
|
|
685
|
+
);
|
|
686
|
+
|
|
687
|
+
// emit the created bookmark
|
|
688
|
+
request$.subscribe(subscriber);
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* Asynchronously creates a new bookmark.
|
|
694
|
+
*
|
|
695
|
+
* @param bookmark - The new bookmark to create.
|
|
696
|
+
* @returns A promise that resolves to the created bookmark with its associated data.
|
|
697
|
+
*/
|
|
698
|
+
public createBookmarkAsync<T extends BookmarkData>(
|
|
699
|
+
args: BookmarkCreateArgs<T>,
|
|
700
|
+
): Promise<Bookmark<T>> {
|
|
701
|
+
return lastValueFrom(this.createBookmark<T>(args));
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Updates a bookmark with the specified bookmarkId and bookmarkUpdates.
|
|
706
|
+
*
|
|
707
|
+
* @template T - The type of the bookmark data.
|
|
708
|
+
* @param {string} bookmarkId - The identifier of the bookmark to update.
|
|
709
|
+
* @param {BookmarkUpdate<T>} [bookmarkUpdates] - The updates to apply to the bookmark.
|
|
710
|
+
* @param {BookmarkUpdateOptions} [options] - The options for updating the bookmark.
|
|
711
|
+
* @returns {Promise<Bookmark<T>>} - A promise that resolves to the updated bookmark.
|
|
712
|
+
*/
|
|
713
|
+
public updateBookmark<T extends BookmarkData>(
|
|
714
|
+
bookmarkId: string,
|
|
715
|
+
bookmarkUpdates?: BookmarkUpdate<T>,
|
|
716
|
+
options?: BookmarkUpdateOptions,
|
|
717
|
+
): Observable<Bookmark<T>> {
|
|
718
|
+
if (!bookmarkUpdates && options?.excludePayloadGeneration) {
|
|
719
|
+
throw new BookmarkProviderError(
|
|
720
|
+
'Cannot update bookmark without updates and excludePayloadGeneration option',
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
return new Observable<Bookmark<T>>((subscriber) => {
|
|
725
|
+
const { ref, action$ } = this._useScopedActions();
|
|
726
|
+
|
|
727
|
+
this._log?.debug(`Updating bookmark: ${bookmarkId}, ref: ${ref}`);
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Generate updates with payload
|
|
731
|
+
* @remarks
|
|
732
|
+
* If `excludePayloadGeneration` is `true`, it emits the `bookmarkUpdates` directly.
|
|
733
|
+
* If `excludePayloadGeneration` is `false`, it generates the payload using the `generatePayload` method and emits the updated `bookmarkUpdates` with the generated payload.
|
|
734
|
+
*/
|
|
735
|
+
const updates$ = options?.excludePayloadGeneration
|
|
736
|
+
? of(bookmarkUpdates as BookmarkUpdate<T>)
|
|
737
|
+
: this.generatePayload<T>(bookmarkUpdates?.payload).pipe(
|
|
738
|
+
map(
|
|
739
|
+
(payload) =>
|
|
740
|
+
({
|
|
741
|
+
...bookmarkUpdates,
|
|
742
|
+
payload,
|
|
743
|
+
}) as BookmarkUpdate<T>,
|
|
744
|
+
),
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
// notify listeners that a bookmark is about to be updated
|
|
748
|
+
const dispatch$ = updates$.pipe(
|
|
749
|
+
switchMap(async (updates) => {
|
|
750
|
+
const { canceled, type } = await this._dispatchEvent('onBookmarkUpdate', {
|
|
751
|
+
detail: {
|
|
752
|
+
current: bookmarkSelector(this.#store.value, bookmarkId),
|
|
753
|
+
updates,
|
|
754
|
+
},
|
|
755
|
+
cancelable: true,
|
|
756
|
+
});
|
|
757
|
+
|
|
758
|
+
if (canceled) {
|
|
759
|
+
const error = new BookmarkProviderError(
|
|
760
|
+
`event: ${type} was canceled by listener for updating bookmark: ${bookmarkId}, ref: ${ref}`,
|
|
761
|
+
);
|
|
762
|
+
this._log?.warn(error.message, updates);
|
|
763
|
+
throw error;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
return updates;
|
|
767
|
+
}),
|
|
768
|
+
);
|
|
769
|
+
|
|
770
|
+
// execute the update bookmark action when the updates are resolved and not canceled
|
|
771
|
+
subscriber.add(
|
|
772
|
+
dispatch$.subscribe({
|
|
773
|
+
error: (error) => subscriber.error(error),
|
|
774
|
+
next: (updates) => {
|
|
775
|
+
// trigger the store to update the bookmark
|
|
776
|
+
this.#store.next(
|
|
777
|
+
bookmarkActions.updateBookmark({ bookmarkId, updates }, { ref }),
|
|
778
|
+
);
|
|
779
|
+
},
|
|
780
|
+
}),
|
|
781
|
+
);
|
|
782
|
+
|
|
783
|
+
// monitor the failure case when updating a bookmark
|
|
784
|
+
const failure$ = action$.pipe(
|
|
785
|
+
filter(bookmarkActions.updateBookmark.failure.match),
|
|
786
|
+
map(({ payload: cause }) => {
|
|
787
|
+
const error = new BookmarkProviderError(
|
|
788
|
+
`Failed to update bookmark: ${bookmarkId}`,
|
|
789
|
+
{
|
|
790
|
+
cause,
|
|
791
|
+
},
|
|
792
|
+
);
|
|
793
|
+
this._log?.info(error.message);
|
|
794
|
+
throw error;
|
|
795
|
+
}),
|
|
796
|
+
timeout({
|
|
797
|
+
each: defaultTimeout,
|
|
798
|
+
with: () => {
|
|
799
|
+
throw new BookmarkProviderError(
|
|
800
|
+
`Timeout while updating bookmark: ${bookmarkId}`,
|
|
801
|
+
);
|
|
802
|
+
},
|
|
803
|
+
}),
|
|
804
|
+
);
|
|
805
|
+
|
|
806
|
+
// monitor the success case when updating a bookmark
|
|
807
|
+
const request$ = action$.pipe(
|
|
808
|
+
filter(bookmarkActions.updateBookmark.success.match),
|
|
809
|
+
first(),
|
|
810
|
+
map(
|
|
811
|
+
// TODO: add payload if current bookmark is the same as the updated bookmark
|
|
812
|
+
({ payload }): Bookmark<T> =>
|
|
813
|
+
({
|
|
814
|
+
...bookmarkSelector(this.#store.value, payload.id),
|
|
815
|
+
payload: payload.payload,
|
|
816
|
+
}) as Bookmark<T>,
|
|
817
|
+
),
|
|
818
|
+
tap((bookmark) => {
|
|
819
|
+
this._log?.info(`Bookmark updated: ${bookmark.id}, ref: ${ref}`);
|
|
820
|
+
this._dispatchEvent('onBookmarkUpdated', { detail: bookmark });
|
|
821
|
+
}),
|
|
822
|
+
raceWith(failure$),
|
|
823
|
+
);
|
|
824
|
+
|
|
825
|
+
// emit the updated bookmark
|
|
826
|
+
request$.subscribe(subscriber);
|
|
827
|
+
});
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
/**
|
|
831
|
+
* Updates a bookmark asynchronously.
|
|
832
|
+
*
|
|
833
|
+
* @param bookmark - The bookmark to update.
|
|
834
|
+
* @returns A promise that resolves to the updated bookmark with its associated data.
|
|
835
|
+
*/
|
|
836
|
+
public updateBookmarkAsync<T extends BookmarkData>(
|
|
837
|
+
bookmarkId: string,
|
|
838
|
+
bookmarkUpdates?: BookmarkUpdate<T>,
|
|
839
|
+
options?: BookmarkUpdateOptions,
|
|
840
|
+
): Promise<Bookmark<T>> {
|
|
841
|
+
return lastValueFrom(this.updateBookmark<T>(bookmarkId, bookmarkUpdates, options));
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Deletes a bookmark with the specified bookmarkId.
|
|
846
|
+
*
|
|
847
|
+
* @param bookmarkId - The unique identifier of the bookmark to be deleted.
|
|
848
|
+
* @returns A Promise that resolves when the bookmark is successfully deleted.
|
|
849
|
+
* @throws {BookmarkProviderError} If there is an error deleting the bookmark.
|
|
850
|
+
*/
|
|
851
|
+
public deleteBookmark(bookmarkId: string): Observable<void> {
|
|
852
|
+
return new Observable<void>((subscriber) => {
|
|
853
|
+
const { ref, action$ } = this._useScopedActions();
|
|
854
|
+
|
|
855
|
+
this._log?.debug(`Removing bookmark: ${bookmarkId}, ref: ${ref}`);
|
|
856
|
+
|
|
857
|
+
// bookmark to delete
|
|
858
|
+
const bookmark = bookmarkSelector(this.#store.value, bookmarkId) ?? {
|
|
859
|
+
id: bookmarkId,
|
|
860
|
+
};
|
|
861
|
+
|
|
862
|
+
// observable that dispatches the 'onBookmarkDelete' event and maps the result
|
|
863
|
+
const dispatch$ = from(
|
|
864
|
+
this._dispatchEvent('onBookmarkDelete', {
|
|
865
|
+
detail: bookmark,
|
|
866
|
+
cancelable: true,
|
|
867
|
+
}),
|
|
868
|
+
).pipe(
|
|
869
|
+
map(({ canceled, type, detail }) => {
|
|
870
|
+
if (canceled) {
|
|
871
|
+
const error = new BookmarkProviderError(
|
|
872
|
+
`event: ${type} was canceled by listener for removing bookmark: ${bookmarkId}, ref: ${ref}`,
|
|
873
|
+
);
|
|
874
|
+
this._log?.warn(error.message);
|
|
875
|
+
throw error;
|
|
876
|
+
}
|
|
877
|
+
return detail;
|
|
878
|
+
}),
|
|
879
|
+
);
|
|
880
|
+
|
|
881
|
+
// execute the delete bookmark action when the event is not canceled
|
|
882
|
+
subscriber.add(
|
|
883
|
+
dispatch$.subscribe({
|
|
884
|
+
error: (error) => subscriber.error(error),
|
|
885
|
+
next: (bookmark) => {
|
|
886
|
+
// request the store to delete the bookmark
|
|
887
|
+
this.#store.next(bookmarkActions.deleteBookmark(bookmark.id, { ref }));
|
|
888
|
+
},
|
|
889
|
+
}),
|
|
890
|
+
);
|
|
891
|
+
|
|
892
|
+
// monitor the failure case when deleting a bookmark
|
|
893
|
+
const failure$ = action$.pipe(
|
|
894
|
+
filter(bookmarkActions.deleteBookmark.failure.match),
|
|
895
|
+
map(({ payload: cause }) => {
|
|
896
|
+
const error = new BookmarkProviderError(
|
|
897
|
+
`Failed to update bookmark: ${bookmarkId}`,
|
|
898
|
+
{
|
|
899
|
+
cause,
|
|
900
|
+
},
|
|
901
|
+
);
|
|
902
|
+
this._log?.warn(error.message);
|
|
903
|
+
throw error;
|
|
904
|
+
}),
|
|
905
|
+
timeout({
|
|
906
|
+
each: defaultTimeout,
|
|
907
|
+
with: () => {
|
|
908
|
+
throw new BookmarkProviderError(
|
|
909
|
+
`Timeout while updating bookmark: ${bookmarkId}`,
|
|
910
|
+
);
|
|
911
|
+
},
|
|
912
|
+
}),
|
|
913
|
+
);
|
|
914
|
+
|
|
915
|
+
// monitor the success case when deleting a bookmark
|
|
916
|
+
const request$ = action$.pipe(
|
|
917
|
+
filter(bookmarkActions.deleteBookmark.success.match),
|
|
918
|
+
map(() => undefined),
|
|
919
|
+
first(),
|
|
920
|
+
tap(() => {
|
|
921
|
+
this._log?.info(`Removed bookmark: ${bookmark.id}, ref: ${ref}`);
|
|
922
|
+
this._dispatchEvent('onBookmarkDeleted', { detail: bookmark });
|
|
923
|
+
}),
|
|
924
|
+
raceWith(failure$),
|
|
925
|
+
);
|
|
926
|
+
|
|
927
|
+
// emit the deleted bookmark
|
|
928
|
+
request$.subscribe(subscriber);
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* Deletes a bookmark asynchronously.
|
|
934
|
+
*
|
|
935
|
+
* @param bookmarkId - The ID of the bookmark to delete.
|
|
936
|
+
* @returns A Promise that resolves when the bookmark is deleted.
|
|
937
|
+
*/
|
|
938
|
+
public async deleteBookmarkAsync(bookmarkId: string): Promise<void> {
|
|
939
|
+
return lastValueFrom(this.deleteBookmark(bookmarkId));
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/**
|
|
943
|
+
* Adds a bookmark to the favorites.
|
|
944
|
+
*
|
|
945
|
+
* @param bookmarkId - The ID of the bookmark to add.
|
|
946
|
+
* @returns A Promise that resolves to the added bookmark, or null if the bookmark was not added.
|
|
947
|
+
*/
|
|
948
|
+
public addBookmarkToFavorites(bookmarkId: string): Observable<BookmarkWithoutData | undefined> {
|
|
949
|
+
/**
|
|
950
|
+
* Observable that represents the result of adding a bookmark as a favorite.
|
|
951
|
+
* Emits a `Bookmark` object or `undefined` if the operation fails to resolve added bookmark.
|
|
952
|
+
*/
|
|
953
|
+
const result$ = new Observable<Bookmark | undefined>((subscriber) => {
|
|
954
|
+
const { ref, action$ } = this._useScopedActions();
|
|
955
|
+
|
|
956
|
+
this._log?.debug(`Adding bookmark: ${bookmarkId} to favourites, ref: ${ref}`);
|
|
957
|
+
|
|
958
|
+
// observable that dispatches the 'onBookmarkFavouriteAdd' event and maps the result
|
|
959
|
+
const dispatch$ = from(
|
|
960
|
+
this._dispatchEvent('onBookmarkFavouriteAdd', {
|
|
961
|
+
detail: { id: bookmarkId },
|
|
962
|
+
cancelable: true,
|
|
963
|
+
}),
|
|
964
|
+
).pipe(
|
|
965
|
+
map(({ canceled, type }) => {
|
|
966
|
+
if (canceled) {
|
|
967
|
+
const error = new BookmarkProviderError(
|
|
968
|
+
`event: ${type} was canceled by listener for adding favourite bookmark: ${bookmarkId}, ref: ${ref}`,
|
|
969
|
+
);
|
|
970
|
+
this._log?.info(error.message);
|
|
971
|
+
throw error;
|
|
972
|
+
}
|
|
973
|
+
}),
|
|
974
|
+
);
|
|
975
|
+
|
|
976
|
+
// execute the add bookmark as a favorite action when the event is not canceled
|
|
977
|
+
subscriber.add(
|
|
978
|
+
dispatch$.subscribe({
|
|
979
|
+
error: (error) => subscriber.error(error),
|
|
980
|
+
next: () => {
|
|
981
|
+
this.#store.next(
|
|
982
|
+
bookmarkActions.addBookmarkAsFavourite(bookmarkId, { ref }),
|
|
983
|
+
);
|
|
984
|
+
},
|
|
985
|
+
}),
|
|
986
|
+
);
|
|
987
|
+
|
|
988
|
+
// monitor the failure case when adding a bookmark as a favorite
|
|
989
|
+
const failure$ = action$.pipe(
|
|
990
|
+
filter(bookmarkActions.addBookmarkAsFavourite.failure.match),
|
|
991
|
+
map(({ payload: cause }) => {
|
|
992
|
+
const error = new BookmarkProviderError(
|
|
993
|
+
`Failed to add bookmark to favorites: ${bookmarkId}`,
|
|
994
|
+
{ cause },
|
|
995
|
+
);
|
|
996
|
+
this._log?.warn(error.message);
|
|
997
|
+
throw error;
|
|
998
|
+
}),
|
|
999
|
+
timeout({
|
|
1000
|
+
each: defaultTimeout,
|
|
1001
|
+
with: () => {
|
|
1002
|
+
throw new BookmarkProviderError(
|
|
1003
|
+
`Timeout while adding bookmark to favorites: ${bookmarkId}`,
|
|
1004
|
+
);
|
|
1005
|
+
},
|
|
1006
|
+
}),
|
|
1007
|
+
);
|
|
1008
|
+
|
|
1009
|
+
// monitor the success case when adding a bookmark as a favorite
|
|
1010
|
+
const request$ = action$.pipe(
|
|
1011
|
+
filter(bookmarkActions.addBookmarkAsFavourite.success.match),
|
|
1012
|
+
switchMap(({ payload }) => this.getBookmark(payload, { excludePayload: true })),
|
|
1013
|
+
tap((bookmark) => {
|
|
1014
|
+
this._log?.info(`Added bookmark: ${bookmarkId} to favourites, ref: ${ref}`);
|
|
1015
|
+
this._dispatchEvent('onBookmarkFavouriteAdded', { detail: bookmark });
|
|
1016
|
+
}),
|
|
1017
|
+
raceWith(failure$),
|
|
1018
|
+
);
|
|
1019
|
+
|
|
1020
|
+
// emit the added bookmark
|
|
1021
|
+
return request$.subscribe(subscriber);
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1024
|
+
return result$;
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
/**
|
|
1028
|
+
* Asynchronously adds a bookmark to the user's favorites.
|
|
1029
|
+
*
|
|
1030
|
+
* @param bookmarkId - The ID of the bookmark to add to the user's favorites.
|
|
1031
|
+
* @returns A Promise that resolves to the added Bookmark, or null if the operation failed.
|
|
1032
|
+
*/
|
|
1033
|
+
public addBookmarkToFavoritesAsync(bookmarkId: string): Promise<Bookmark | undefined> {
|
|
1034
|
+
return lastValueFrom(this.addBookmarkToFavorites(bookmarkId));
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
/**
|
|
1038
|
+
* Removes a bookmark as a favorite.
|
|
1039
|
+
*
|
|
1040
|
+
* @param bookmarkId - The ID of the bookmark to remove as a favorite.
|
|
1041
|
+
* @returns A Promise that resolves when the bookmark is successfully removed as a favorite.
|
|
1042
|
+
* @throws {BookmarkProviderError} If the event is canceled or if there is a failure in removing the bookmark as a favorite.
|
|
1043
|
+
*/
|
|
1044
|
+
public removeBookmarkAsFavorite(bookmarkId: string): Observable<void> {
|
|
1045
|
+
/**
|
|
1046
|
+
* Represents an observable that emits void values when a bookmark's favorite is removed.
|
|
1047
|
+
*/
|
|
1048
|
+
const result$ = new Observable<void>((subscriber) => {
|
|
1049
|
+
const { ref, action$ } = this._useScopedActions();
|
|
1050
|
+
|
|
1051
|
+
this._log?.debug(`Removing bookmark: ${bookmarkId} from favourites, ref: ${ref}`);
|
|
1052
|
+
|
|
1053
|
+
// get the bookmark to remove as a favorite
|
|
1054
|
+
const bookmark = bookmarkSelector(this.#store.value, bookmarkId) ?? { id: bookmarkId };
|
|
1055
|
+
|
|
1056
|
+
// observable that dispatches the 'onBookmarkFavouriteRemove' event and maps the result
|
|
1057
|
+
const dispatch$ = from(
|
|
1058
|
+
this._dispatchEvent('onBookmarkFavouriteRemove', {
|
|
1059
|
+
detail: bookmark,
|
|
1060
|
+
cancelable: true,
|
|
1061
|
+
}),
|
|
1062
|
+
).pipe(
|
|
1063
|
+
map(({ canceled, type, detail }) => {
|
|
1064
|
+
if (canceled) {
|
|
1065
|
+
const error = new BookmarkProviderError(
|
|
1066
|
+
`event: ${type} was canceled by listener for removing favourite bookmark: ${bookmarkId}, ref: ${ref}`,
|
|
1067
|
+
);
|
|
1068
|
+
this._log?.warn(error.message);
|
|
1069
|
+
throw error;
|
|
1070
|
+
}
|
|
1071
|
+
return detail;
|
|
1072
|
+
}),
|
|
1073
|
+
);
|
|
1074
|
+
|
|
1075
|
+
// execute the remove bookmark as a favorite action when the event is not canceled
|
|
1076
|
+
subscriber.add(
|
|
1077
|
+
dispatch$.subscribe({
|
|
1078
|
+
error: (error) => subscriber.error(error),
|
|
1079
|
+
next: (detail) => {
|
|
1080
|
+
// request the store to remove the bookmark as a favorite
|
|
1081
|
+
this.#store.next(
|
|
1082
|
+
bookmarkActions.removeBookmarkAsFavourite(detail.id, { ref }),
|
|
1083
|
+
);
|
|
1084
|
+
},
|
|
1085
|
+
}),
|
|
1086
|
+
);
|
|
1087
|
+
|
|
1088
|
+
// monitor the failure case when removing a bookmark as a favorite
|
|
1089
|
+
const failure$ = action$.pipe(
|
|
1090
|
+
filter(bookmarkActions.removeBookmarkAsFavourite.failure.match),
|
|
1091
|
+
map(({ payload: cause }) => {
|
|
1092
|
+
const error = new BookmarkProviderError(
|
|
1093
|
+
`Failed to remove bookmark: ${bookmarkId} from favourites`,
|
|
1094
|
+
{
|
|
1095
|
+
cause,
|
|
1096
|
+
},
|
|
1097
|
+
);
|
|
1098
|
+
this._log?.warn(error.message);
|
|
1099
|
+
throw error;
|
|
1100
|
+
}),
|
|
1101
|
+
timeout({
|
|
1102
|
+
each: defaultTimeout,
|
|
1103
|
+
with: () => {
|
|
1104
|
+
throw new BookmarkProviderError(
|
|
1105
|
+
`Timeout while removing bookmark: ${bookmarkId} from favourites`,
|
|
1106
|
+
);
|
|
1107
|
+
},
|
|
1108
|
+
}),
|
|
1109
|
+
);
|
|
1110
|
+
|
|
1111
|
+
// monitor the success case when removing a bookmark as a favorite
|
|
1112
|
+
const request$ = action$.pipe(
|
|
1113
|
+
filter(bookmarkActions.removeBookmarkAsFavourite.success.match),
|
|
1114
|
+
tap(() => {
|
|
1115
|
+
this._log?.info(`Removed bookmark: ${bookmarkId} from favourites, ref: ${ref}`);
|
|
1116
|
+
this._dispatchEvent('onBookmarkFavouriteRemoved', { detail: bookmark });
|
|
1117
|
+
}),
|
|
1118
|
+
map(() => {}),
|
|
1119
|
+
raceWith(failure$),
|
|
1120
|
+
);
|
|
1121
|
+
|
|
1122
|
+
// emit the removed bookmark
|
|
1123
|
+
request$.subscribe(subscriber);
|
|
1124
|
+
});
|
|
1125
|
+
|
|
1126
|
+
return result$;
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
/**
|
|
1130
|
+
* Removes a bookmark as a favorite asynchronously.
|
|
1131
|
+
*
|
|
1132
|
+
* @param bookmarkId - The ID of the bookmark to remove as a favorite.
|
|
1133
|
+
* @returns A Promise that resolves when the bookmark is successfully removed as a favorite.
|
|
1134
|
+
*/
|
|
1135
|
+
public removeBookmarkAsFavoriteAsync(bookmarkId: string): Promise<void> {
|
|
1136
|
+
return lastValueFrom(this.removeBookmarkAsFavorite(bookmarkId));
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
/**
|
|
1140
|
+
* Checks if a bookmark is in the favorites.
|
|
1141
|
+
* @param bookmarkId - The ID of the bookmark to check.
|
|
1142
|
+
* @returns A promise that resolves to a boolean indicating whether the bookmark is in the favorites.
|
|
1143
|
+
*/
|
|
1144
|
+
public isBookmarkInFavorites(bookmarkId: string): Observable<boolean> {
|
|
1145
|
+
this._log?.debug(`Checking if bookmark: ${bookmarkId} is in favourites`);
|
|
1146
|
+
|
|
1147
|
+
// Check if the bookmark is a favorite
|
|
1148
|
+
return from(this._apiClient.isBookmarkFavorite(bookmarkId)).pipe(
|
|
1149
|
+
catchError((err) => {
|
|
1150
|
+
const error = new BookmarkProviderError(
|
|
1151
|
+
`Failed to check if bookmark: ${bookmarkId} is in favourites`,
|
|
1152
|
+
err,
|
|
1153
|
+
);
|
|
1154
|
+
this._log?.error(error.message, error);
|
|
1155
|
+
throw error;
|
|
1156
|
+
}),
|
|
1157
|
+
tap((isFavorite) => {
|
|
1158
|
+
this._log?.debug(
|
|
1159
|
+
`Bookmark: ${bookmarkId} is ${isFavorite ? 'in' : 'not in'} favourites`,
|
|
1160
|
+
);
|
|
1161
|
+
}),
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
/**
|
|
1166
|
+
* Checks if the specified bookmark is in the user's favorites.
|
|
1167
|
+
*
|
|
1168
|
+
* @param bookmarkId - The ID of the bookmark to check.
|
|
1169
|
+
* @returns A Promise that resolves to a boolean indicating whether the bookmark is in the user's favorites.
|
|
1170
|
+
*/
|
|
1171
|
+
public isBookmarkInFavoritesAsync(bookmarkId: string): Promise<boolean> {
|
|
1172
|
+
return lastValueFrom(this.isBookmarkInFavorites(bookmarkId));
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
protected _getBookmarkInfo(
|
|
1176
|
+
bookmarkId: string,
|
|
1177
|
+
options?: { timeout?: number },
|
|
1178
|
+
): Observable<BookmarkWithoutData> {
|
|
1179
|
+
// only actions with the specified reference are considered
|
|
1180
|
+
const { action$ } = this._useScopedActions(bookmarkId);
|
|
1181
|
+
|
|
1182
|
+
// observable stream that emits the bookmark data when the fetch action is successful
|
|
1183
|
+
const success$ = action$.pipe(
|
|
1184
|
+
filter(bookmarkActions.fetchBookmark.success.match),
|
|
1185
|
+
map((action) => action.payload),
|
|
1186
|
+
first(),
|
|
1187
|
+
);
|
|
1188
|
+
|
|
1189
|
+
// observable stream that emits an error when fetching bookmark data fails
|
|
1190
|
+
const failure$ = this.#store.action$.pipe(
|
|
1191
|
+
filter(bookmarkActions.fetchBookmark.failure.match),
|
|
1192
|
+
map((action) => {
|
|
1193
|
+
throw new BookmarkProviderError(
|
|
1194
|
+
`Failed to fetch bookmark data: ${bookmarkId}`,
|
|
1195
|
+
action.payload,
|
|
1196
|
+
);
|
|
1197
|
+
}),
|
|
1198
|
+
timeout({
|
|
1199
|
+
each: options?.timeout ?? 2 * 60 * 1000,
|
|
1200
|
+
with: () => {
|
|
1201
|
+
throw new BookmarkProviderError(
|
|
1202
|
+
`Timeout while fetching bookmark data: ${bookmarkId}`,
|
|
1203
|
+
);
|
|
1204
|
+
},
|
|
1205
|
+
}),
|
|
1206
|
+
);
|
|
1207
|
+
|
|
1208
|
+
this.#store.next(bookmarkActions.fetchBookmark(bookmarkId, { ref: bookmarkId }));
|
|
1209
|
+
|
|
1210
|
+
return success$.pipe(raceWith(failure$));
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
protected _getBookmarkData<T extends BookmarkData>(
|
|
1214
|
+
bookmarkId: string,
|
|
1215
|
+
options?: { timeout?: number },
|
|
1216
|
+
): Observable<T | null> {
|
|
1217
|
+
// only actions with the specified reference are considered
|
|
1218
|
+
const { action$ } = this._useScopedActions(bookmarkId);
|
|
1219
|
+
|
|
1220
|
+
// observable stream that emits the bookmark data when the fetch action is successful
|
|
1221
|
+
const success$ = action$.pipe(
|
|
1222
|
+
filter(bookmarkActions.fetchBookmarkData.success.match),
|
|
1223
|
+
map((action) => action.payload.data as T),
|
|
1224
|
+
first(),
|
|
1225
|
+
);
|
|
1226
|
+
|
|
1227
|
+
// observable stream that emits an error when fetching bookmark data fails
|
|
1228
|
+
const failure$ = this.#store.action$.pipe(
|
|
1229
|
+
filter(bookmarkActions.fetchBookmarkData.failure.match),
|
|
1230
|
+
map((action) => {
|
|
1231
|
+
throw new BookmarkProviderError(
|
|
1232
|
+
`Failed to fetch bookmark data: ${bookmarkId}`,
|
|
1233
|
+
action.payload,
|
|
1234
|
+
);
|
|
1235
|
+
}),
|
|
1236
|
+
timeout({
|
|
1237
|
+
each: options?.timeout ?? defaultTimeout,
|
|
1238
|
+
with: () => {
|
|
1239
|
+
throw new BookmarkProviderError(
|
|
1240
|
+
`Timeout while fetching bookmark data: ${bookmarkId}`,
|
|
1241
|
+
);
|
|
1242
|
+
},
|
|
1243
|
+
}),
|
|
1244
|
+
);
|
|
1245
|
+
|
|
1246
|
+
// request the store to fetch the bookmark data
|
|
1247
|
+
this.#store.next(bookmarkActions.fetchBookmarkData(bookmarkId, { ref: bookmarkId }));
|
|
1248
|
+
|
|
1249
|
+
return success$.pipe(raceWith(failure$));
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
/**
|
|
1253
|
+
* Dispatches an event of the specified type with the provided arguments.
|
|
1254
|
+
*
|
|
1255
|
+
* @param type - The type of the event to dispatch.
|
|
1256
|
+
* @param args - The arguments to pass to the event.
|
|
1257
|
+
* @returns A promise that resolves with the result of the event dispatch.
|
|
1258
|
+
*/
|
|
1259
|
+
protected async _dispatchEvent<TType extends keyof BookmarkProviderEventMap>(
|
|
1260
|
+
type: TType,
|
|
1261
|
+
args: FrameworkEventInitType<BookmarkProviderEventMap[NoInfer<TType>]>,
|
|
1262
|
+
): Promise<BookmarkProviderEventMap[NoInfer<TType>]> {
|
|
1263
|
+
// create a new event instance
|
|
1264
|
+
const event = new FrameworkEvent(type, {
|
|
1265
|
+
source: this,
|
|
1266
|
+
canBubble: true,
|
|
1267
|
+
...args,
|
|
1268
|
+
}) as BookmarkProviderEventMap[NoInfer<TType>];
|
|
1269
|
+
|
|
1270
|
+
if (this._event) {
|
|
1271
|
+
this._log?.debug(`dispatching event ${type}`, args);
|
|
1272
|
+
const { canceled } = await this._event.dispatchEvent(event);
|
|
1273
|
+
if (canceled) {
|
|
1274
|
+
this._log?.debug(`event ${type} was canceled`, args);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
return event;
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
protected _useScopedActions<TAction extends BookmarkActions>(
|
|
1281
|
+
ref?: string,
|
|
1282
|
+
): {
|
|
1283
|
+
ref: string;
|
|
1284
|
+
action$: Observable<TAction>;
|
|
1285
|
+
} {
|
|
1286
|
+
/**
|
|
1287
|
+
* Generates a unique identifier for the operation
|
|
1288
|
+
*/
|
|
1289
|
+
ref ??= generateGUID();
|
|
1290
|
+
|
|
1291
|
+
/**
|
|
1292
|
+
* Observable stream of actions filtered by a specific reference.
|
|
1293
|
+
*/
|
|
1294
|
+
const action$ = this.#store.action$.pipe(
|
|
1295
|
+
filter((action): action is TAction => 'meta' in action && action.meta?.ref === ref),
|
|
1296
|
+
);
|
|
1297
|
+
|
|
1298
|
+
return { ref, action$ };
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
/**
|
|
1302
|
+
* Disposes the BookmarkProvider.
|
|
1303
|
+
*/
|
|
1304
|
+
dispose(): void {
|
|
1305
|
+
this._log?.debug('disposing BookmarkProvider');
|
|
1306
|
+
this.#subscriptions.unsubscribe();
|
|
1307
|
+
}
|
|
1308
|
+
}
|