@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.
Files changed (113) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +242 -9
  3. package/dist/esm/BookmarkClient.interface.js +2 -0
  4. package/dist/esm/BookmarkClient.interface.js.map +1 -0
  5. package/dist/esm/BookmarkClient.js +174 -0
  6. package/dist/esm/BookmarkClient.js.map +1 -0
  7. package/dist/esm/BookmarkConfigurator.js +348 -0
  8. package/dist/esm/BookmarkConfigurator.js.map +1 -0
  9. package/dist/esm/BookmarkProvider.actions.js +60 -0
  10. package/dist/esm/BookmarkProvider.actions.js.map +1 -0
  11. package/dist/esm/BookmarkProvider.error.js +14 -0
  12. package/dist/esm/BookmarkProvider.error.js.map +1 -0
  13. package/dist/esm/BookmarkProvider.events.js +2 -0
  14. package/dist/esm/BookmarkProvider.events.js.map +1 -0
  15. package/dist/esm/BookmarkProvider.flows.js +219 -0
  16. package/dist/esm/BookmarkProvider.flows.js.map +1 -0
  17. package/dist/esm/BookmarkProvider.js +937 -0
  18. package/dist/esm/BookmarkProvider.js.map +1 -0
  19. package/dist/esm/BookmarkProvider.reducer.js +125 -0
  20. package/dist/esm/BookmarkProvider.reducer.js.map +1 -0
  21. package/dist/esm/BookmarkProvider.selectors.js +43 -0
  22. package/dist/esm/BookmarkProvider.selectors.js.map +1 -0
  23. package/dist/esm/BookmarkProvider.store.js +19 -0
  24. package/dist/esm/BookmarkProvider.store.js.map +1 -0
  25. package/dist/esm/bookmark-config.schema.js +30 -0
  26. package/dist/esm/bookmark-config.schema.js.map +1 -0
  27. package/dist/esm/bookmark-module.js +53 -0
  28. package/dist/esm/bookmark-module.js.map +1 -0
  29. package/dist/esm/bookmark.schemas.js +38 -0
  30. package/dist/esm/bookmark.schemas.js.map +1 -0
  31. package/dist/esm/enable-bookmark.js +20 -7
  32. package/dist/esm/enable-bookmark.js.map +1 -1
  33. package/dist/esm/index.js +3 -3
  34. package/dist/esm/index.js.map +1 -1
  35. package/dist/esm/version.js +1 -1
  36. package/dist/esm/version.js.map +1 -1
  37. package/dist/tsconfig.tsbuildinfo +1 -1
  38. package/dist/types/BookmarkClient.d.ts +42 -0
  39. package/dist/types/BookmarkClient.interface.d.ts +139 -0
  40. package/dist/types/BookmarkConfigurator.d.ts +100 -0
  41. package/dist/types/BookmarkProvider.actions.d.ts +525 -0
  42. package/dist/types/BookmarkProvider.d.ts +462 -0
  43. package/dist/types/BookmarkProvider.error.d.ts +9 -0
  44. package/dist/types/BookmarkProvider.events.d.ts +78 -0
  45. package/dist/types/BookmarkProvider.flows.d.ts +79 -0
  46. package/dist/types/BookmarkProvider.reducer.d.ts +302 -0
  47. package/dist/types/BookmarkProvider.selectors.d.ts +34 -0
  48. package/dist/types/BookmarkProvider.store.d.ts +30 -0
  49. package/dist/types/bookmark-config.schema.d.ts +118 -0
  50. package/dist/types/{module.d.ts → bookmark-module.d.ts} +3 -3
  51. package/dist/types/bookmark.schemas.d.ts +760 -0
  52. package/dist/types/enable-bookmark.d.ts +7 -6
  53. package/dist/types/index.d.ts +4 -3
  54. package/dist/types/types.d.ts +29 -65
  55. package/dist/types/version.d.ts +1 -1
  56. package/package.json +11 -5
  57. package/src/BookmarkClient.interface.ts +157 -0
  58. package/src/BookmarkClient.ts +226 -0
  59. package/src/BookmarkConfigurator.ts +374 -0
  60. package/src/BookmarkProvider.actions.ts +131 -0
  61. package/src/BookmarkProvider.error.ts +21 -0
  62. package/src/BookmarkProvider.events.ts +89 -0
  63. package/src/BookmarkProvider.flows.ts +426 -0
  64. package/src/BookmarkProvider.reducer.ts +141 -0
  65. package/src/BookmarkProvider.selectors.ts +55 -0
  66. package/src/BookmarkProvider.store.ts +52 -0
  67. package/src/BookmarkProvider.ts +1308 -0
  68. package/src/bookmark-config.schema.ts +41 -0
  69. package/src/bookmark-module.ts +70 -0
  70. package/src/bookmark.schemas.ts +50 -0
  71. package/src/enable-bookmark.ts +13 -20
  72. package/src/index.ts +15 -7
  73. package/src/types.ts +46 -73
  74. package/src/version.ts +1 -1
  75. package/dist/esm/bookmark-config-builder.js +0 -131
  76. package/dist/esm/bookmark-config-builder.js.map +0 -1
  77. package/dist/esm/bookmark-provider.js +0 -155
  78. package/dist/esm/bookmark-provider.js.map +0 -1
  79. package/dist/esm/client/bookmarkActions.js +0 -10
  80. package/dist/esm/client/bookmarkActions.js.map +0 -1
  81. package/dist/esm/client/bookmarkClient.js +0 -259
  82. package/dist/esm/client/bookmarkClient.js.map +0 -1
  83. package/dist/esm/client/bookmarkFlows.js +0 -31
  84. package/dist/esm/client/bookmarkFlows.js.map +0 -1
  85. package/dist/esm/client/bookmarkReducer.js +0 -29
  86. package/dist/esm/client/bookmarkReducer.js.map +0 -1
  87. package/dist/esm/configurator.js +0 -155
  88. package/dist/esm/configurator.js.map +0 -1
  89. package/dist/esm/module.js +0 -27
  90. package/dist/esm/module.js.map +0 -1
  91. package/dist/esm/utils/handle-url.js +0 -11
  92. package/dist/esm/utils/handle-url.js.map +0 -1
  93. package/dist/esm/utils/index.js +0 -2
  94. package/dist/esm/utils/index.js.map +0 -1
  95. package/dist/types/bookmark-config-builder.d.ts +0 -85
  96. package/dist/types/bookmark-provider.d.ts +0 -169
  97. package/dist/types/client/bookmarkActions.d.ts +0 -27
  98. package/dist/types/client/bookmarkClient.d.ts +0 -53
  99. package/dist/types/client/bookmarkFlows.d.ts +0 -22
  100. package/dist/types/client/bookmarkReducer.d.ts +0 -5
  101. package/dist/types/configurator.d.ts +0 -75
  102. package/dist/types/utils/handle-url.d.ts +0 -2
  103. package/dist/types/utils/index.d.ts +0 -1
  104. package/src/bookmark-config-builder.ts +0 -177
  105. package/src/bookmark-provider.ts +0 -344
  106. package/src/client/bookmarkActions.ts +0 -44
  107. package/src/client/bookmarkClient.ts +0 -327
  108. package/src/client/bookmarkFlows.ts +0 -67
  109. package/src/client/bookmarkReducer.ts +0 -35
  110. package/src/configurator.ts +0 -235
  111. package/src/module.ts +0 -40
  112. package/src/utils/handle-url.ts +0 -13
  113. package/src/utils/index.ts +0 -1
@@ -0,0 +1,426 @@
1
+ import { from, of, merge, concat } from 'rxjs';
2
+ import {
3
+ switchMap,
4
+ map,
5
+ catchError,
6
+ filter,
7
+ concatMap,
8
+ mergeMap,
9
+ groupBy,
10
+ throttleTime,
11
+ } from 'rxjs/operators';
12
+
13
+ import { type Flow, type Observable, getBaseType } from '@equinor/fusion-observable';
14
+
15
+ import { bookmarkActions as actions, type BookmarkActions } from './BookmarkProvider.actions';
16
+ import type { IBookmarkClient } from './BookmarkClient.interface';
17
+ import type { BookmarkState } from './BookmarkProvider.store';
18
+ import { BookmarkFlowError } from './BookmarkProvider.error';
19
+
20
+ const defaultThrottleTime = 200;
21
+
22
+ /**
23
+ * Handles the fetch bookmark action by making an API request to get the bookmark by its ID.
24
+ *
25
+ * @param api - The bookmark client API.
26
+ * @returns A flow that handles the fetch bookmark action.
27
+ */
28
+ export const handleFetchBookmark =
29
+ (api: IBookmarkClient): Flow<BookmarkActions> =>
30
+ (action$) => {
31
+ /**
32
+ * Observable that represents the flow of fetching bookmarks.
33
+ * It listens for `fetchBookmark` actions, makes an API request to get the bookmark by ID,
34
+ * and emits corresponding success or failure actions based on the API response.
35
+ */
36
+ const flow$ = action$.pipe(
37
+ filter(actions.fetchBookmark.match),
38
+ groupBy((action) => action.payload),
39
+ mergeMap((group) => group.pipe(throttleTime(defaultThrottleTime))),
40
+ switchMap((action) =>
41
+ from(api.getBookmarkById(action.payload)).pipe(
42
+ map((bookmark) => actions.fetchBookmark.success(bookmark, action.meta)),
43
+ catchError((error) =>
44
+ of(
45
+ actions.fetchBookmark.failure(
46
+ new BookmarkFlowError('Failed to fetch bookmark', action, {
47
+ cause: error,
48
+ }),
49
+ action.meta,
50
+ ),
51
+ ),
52
+ ),
53
+ ),
54
+ ),
55
+ );
56
+ return flow$;
57
+ };
58
+
59
+ /**
60
+ * Creates a flow for handling fetching bookmark data.
61
+ *
62
+ * @param api - The bookmark client API.
63
+ * @returns A flow of bookmark actions.
64
+ */
65
+ export const handleFetchBookmarkData =
66
+ (api: IBookmarkClient): Flow<BookmarkActions> =>
67
+ (action$) => {
68
+ const flow$ = action$.pipe(
69
+ filter(actions.fetchBookmarkData.match),
70
+ groupBy((action) => JSON.stringify(action.payload)),
71
+ mergeMap((group) => group.pipe(throttleTime(defaultThrottleTime))),
72
+ switchMap((action) => {
73
+ return from(api.getBookmarkData(action.payload)).pipe(
74
+ map((data) =>
75
+ actions.fetchBookmarkData.success(action.payload, data, action.meta),
76
+ ),
77
+ catchError((error) =>
78
+ of(
79
+ actions.fetchBookmarkData.failure(
80
+ new BookmarkFlowError('Failed to fetch bookmark payload', action, {
81
+ cause: error,
82
+ }),
83
+ action.meta,
84
+ ),
85
+ ),
86
+ ),
87
+ );
88
+ }),
89
+ );
90
+ return flow$;
91
+ };
92
+
93
+ /**
94
+ * Creates a Flow for handling fetching users bookmarks.
95
+ *
96
+ * @param api - The bookmark API client to use for fetching bookmarks.
97
+ * @param options - Optional configuration options, including a `throttle` value in milliseconds.
98
+ * @returns An observable that emits the result of the bookmark fetch operation.
99
+ */
100
+ export const handleFetchAllBookmark =
101
+ (api: IBookmarkClient): Flow<BookmarkActions> =>
102
+ (action$: Observable<BookmarkActions>) => {
103
+ /**
104
+ * This flow is triggered by the `fetchBookmarks` action and uses the `throttleTime` operator
105
+ * to limit the number of requests made to the API. The `switchMap` operator is used to make
106
+ * the API call and map the response to the appropriate action (success or failure).
107
+ */
108
+ const flow$ = action$.pipe(
109
+ filter(actions.fetchBookmarks.match),
110
+ groupBy((action) => JSON.stringify(action.payload)),
111
+ switchMap((group) => group.pipe(throttleTime(defaultThrottleTime))),
112
+ switchMap((action) =>
113
+ from(api.getAllBookmarks(action.payload)).pipe(
114
+ map((value) => actions.fetchBookmarks.success(value, action.meta)),
115
+ catchError((error) =>
116
+ of(
117
+ actions.fetchBookmarks.failure(
118
+ new BookmarkFlowError('Failed to fetch all bookmarks', action, {
119
+ cause: error,
120
+ }),
121
+ action.meta,
122
+ ),
123
+ ),
124
+ ),
125
+ ),
126
+ ),
127
+ );
128
+ return flow$;
129
+ };
130
+
131
+ /**
132
+ * Creates a Flow for handling creating bookmarks.
133
+ *
134
+ * @param api - An instance of the `IBookmarkClient` interface, which provides the necessary API methods for creating bookmarks.
135
+ * @returns A flow that listens for `createBookmark` actions, creates a new bookmark using the provided API.
136
+ */
137
+ export const handleCreateBookmark =
138
+ (api: IBookmarkClient): Flow<BookmarkActions> =>
139
+ (action$: Observable<BookmarkActions>) => {
140
+ /**
141
+ * This flow listens for the `createBookmark` action, and then uses the `api.createBookmark` function to create a new bookmark.
142
+ * If the bookmark is created successfully, it dispatches the `createBookmark.success` action with the new bookmark.
143
+ * If there is an error creating the bookmark, it dispatches the `fetchBookmark.failure` action with an error.
144
+ *
145
+ * The `concatMap` operator is used to prevent aborting the request if a new `createBookmark` action is dispatched while the previous request is in flight.
146
+ */
147
+ const flow$ = action$.pipe(
148
+ filter(actions.createBookmark.match),
149
+ concatMap((action) => {
150
+ return from(api.createBookmark(action.payload)).pipe(
151
+ map((bookmark) => actions.createBookmark.success(bookmark, action.meta)),
152
+ catchError((error) =>
153
+ of(
154
+ actions.createBookmark.failure(
155
+ new BookmarkFlowError('Failed to create new bookmark', action, {
156
+ cause: error,
157
+ }),
158
+ action.meta,
159
+ ),
160
+ ),
161
+ ),
162
+ );
163
+ }),
164
+ );
165
+ return flow$;
166
+ };
167
+
168
+ /**
169
+ * Creates a Flow for handling updating bookmarks.
170
+ *
171
+ * @param api - An instance of the `IBookmarkClient` interface, which provides the necessary API methods for updating bookmarks.
172
+ * @returns A flow that listens for `updateBookmark` actions, updates the bookmark using the provided API.
173
+ */
174
+ export const handleUpdateBookmark =
175
+ (api: IBookmarkClient): Flow<BookmarkActions> =>
176
+ (action$: Observable<BookmarkActions>) => {
177
+ /**
178
+ * This flow listens for `updateBookmark` actions, then calls the `api.updateBookmark` function with the action payload.
179
+ * If the update is successful, it dispatches a `updateBookmark.success` action with the updated bookmark.
180
+ * If there is an error, it dispatches a `fetchBookmark.failure` action with the error.
181
+ *
182
+ * The `concatMap` operator is used to prevent aborting the request if a new `updateBookmark` action is dispatched while the previous request is in flight.
183
+ */
184
+ const flow$ = action$.pipe(
185
+ filter(actions.updateBookmark.match),
186
+ concatMap((action) => {
187
+ const { bookmarkId, updates } = action.payload;
188
+ return from(api.updateBookmark(bookmarkId, updates)).pipe(
189
+ map((bookmark) => actions.updateBookmark.success(bookmark, action.meta)),
190
+ catchError((error) =>
191
+ of(
192
+ actions.updateBookmark.failure(
193
+ new BookmarkFlowError('Failed to update bookmark', action, {
194
+ cause: error,
195
+ }),
196
+ action.meta,
197
+ ),
198
+ ),
199
+ ),
200
+ );
201
+ }),
202
+ );
203
+ return flow$;
204
+ };
205
+
206
+ /**
207
+ * Creates a flow for handling deleting bookmarks.
208
+ *
209
+ * @param api - An instance of the `IBookmarkClient` interface, which provides the necessary API methods for deleting bookmarks.
210
+ * @returns A flow that listens for `deleteBookmark` actions, deletes the bookmark using the provided API.
211
+ */
212
+ export const handleDeleteBookmark =
213
+ (api: IBookmarkClient): Flow<BookmarkActions> =>
214
+ (action$: Observable<BookmarkActions>) => {
215
+ /**
216
+ * - Listens for the `deleteBookmark` action.
217
+ * - Calls the `api.deleteBookmark` function to delete the bookmark.
218
+ * - Maps the successful response to the `deleteBookmark.success` action.
219
+ * - Handles errors by dispatching the `fetchBookmark.failure` action with a `BookmarkFlowError`.
220
+ * - Uses `concatMap` to prevent aborting the request if a new action is dispatched while the previous request is in flight.
221
+ */
222
+ const flow$ = action$.pipe(
223
+ filter(actions.deleteBookmark.match),
224
+ concatMap((action) =>
225
+ from(api.deleteBookmark(action.payload)).pipe(
226
+ map(() => actions.deleteBookmark.success(action.payload, action.meta)),
227
+ catchError((error) =>
228
+ of(
229
+ actions.deleteBookmark.failure(
230
+ new BookmarkFlowError('Failed to delete bookmark', action, {
231
+ cause: error,
232
+ }),
233
+ action.meta,
234
+ ),
235
+ ),
236
+ ),
237
+ ),
238
+ ),
239
+ );
240
+ return flow$;
241
+ };
242
+
243
+ /**
244
+ * Creates a Flow for handling deleting bookmarks.
245
+ *
246
+ * @param api - An instance of the `IBookmarkClient` interface, which provides the necessary API methods for managing favorite bookmarks.
247
+ * @returns A flow that listens for `removeBookmarkAsFavorite` actions, removes the bookmark from the user's favorites using the provided API.
248
+ */
249
+ export const handleRemoveBookmarkFromFavorites =
250
+ (api: IBookmarkClient): Flow<BookmarkActions> =>
251
+ (action$: Observable<BookmarkActions>) => {
252
+ /**
253
+ * - Listens for the `removeBookmarkAsFavourite` action.
254
+ * - Calls the `api.removeBookmarkFromFavorites` function to remove the bookmark from favorites.
255
+ * - On success, dispatches the `removeBookmarkAsFavourite.success` action.
256
+ * - On error, dispatches the `fetchBookmark.failure` action with a `BookmarkFlowError`.
257
+ * - Uses `concatMap` to prevent aborting the request if a new action is dispatched while the previous request is in flight.
258
+ */
259
+ const flow$ = action$.pipe(
260
+ filter(actions.removeBookmarkAsFavourite.match),
261
+ // use concat to prevent aborting the request if a new action is dispatched while the previous request is in flight
262
+ concatMap((action) =>
263
+ from(api.removeBookmarkFromFavorites(action.payload)).pipe(
264
+ map(() =>
265
+ actions.removeBookmarkAsFavourite.success(action.payload, action.meta),
266
+ ),
267
+ catchError((error) =>
268
+ of(
269
+ actions.removeBookmarkAsFavourite.failure(
270
+ new BookmarkFlowError(
271
+ 'Failed to remove bookmark as favorite',
272
+ action,
273
+ {
274
+ cause: error,
275
+ },
276
+ ),
277
+ action.meta,
278
+ ),
279
+ ),
280
+ ),
281
+ ),
282
+ ),
283
+ );
284
+
285
+ return flow$;
286
+ };
287
+
288
+ /**
289
+ * Handles the flow of adding a bookmark as a favorite.
290
+ *
291
+ * @param api - The `IBookmarkClient` API instance to use for interacting with bookmarks.
292
+ * @returns An RxJS `Flow` that handles the `addBookmarkAsFavourite` action.
293
+ */
294
+ export const handleAddBookmarkAsFavorite =
295
+ (api: IBookmarkClient): Flow<BookmarkActions> =>
296
+ (action$: Observable<BookmarkActions>) => {
297
+ /**
298
+ * Handles the flow for adding a bookmark as a favorite.
299
+ *
300
+ * This flow listens for the `addBookmarkAsFavourite` action, and then:
301
+ * 1. Calls the `addBookmarkToFavorites` API to add the bookmark to the user's favorites.
302
+ * 2. If the API call is successful, dispatches the `addBookmarkAsFavourite.success` action.
303
+ * 3. If the API call fails, dispatches the `addBookmarkAsFavourite.failure` action with an error.
304
+ */
305
+ const flow$ = action$.pipe(
306
+ filter(actions.addBookmarkAsFavourite.match),
307
+ concatMap((action) =>
308
+ from(api.addBookmarkToFavorites(action.payload)).pipe(
309
+ map(() => actions.addBookmarkAsFavourite.success(action.payload, action.meta)),
310
+ catchError((error) =>
311
+ of(
312
+ actions.addBookmarkAsFavourite.failure(
313
+ new BookmarkFlowError(
314
+ 'Failed to add bookmark as favorite',
315
+ action,
316
+ {
317
+ cause: error,
318
+ },
319
+ ),
320
+ action.meta,
321
+ ),
322
+ ),
323
+ ),
324
+ ),
325
+ ),
326
+ );
327
+
328
+ return flow$;
329
+ };
330
+
331
+ /**
332
+ * Creates a flow for handling bookmark removal.
333
+ *
334
+ * This function observes actions related to deleting a bookmark or removing a bookmark as a favorite, and dispatches the appropriate actions for each case.
335
+ * If the bookmark is currently a favorite, it will dispatch an action to remove it as a favorite.
336
+ * If the bookmark is not a favorite, it will dispatch an action to delete it.
337
+ *
338
+ * @param api - An instance of the `IBookmarkClient` interface, which provides the necessary API methods checking if bookmarks are favorites.
339
+ * @returns A flow that handles the removal of a bookmark.
340
+ */
341
+ export const handleRemoveBookmark =
342
+ (api: IBookmarkClient): Flow<BookmarkActions> =>
343
+ (action$) => {
344
+ /**
345
+ * Handles the removal of a bookmark from the application.
346
+ *
347
+ * This function is responsible for determining whether the bookmark being removed is a favorite or not,
348
+ * and then dispatching the appropriate action to either remove the bookmark as a favorite or delete the bookmark entirely.
349
+ *
350
+ * Uses `concatMap` to prevent aborting the request if a new action is dispatched while the previous request is in flight.
351
+ */
352
+ const dispatch$ = action$.pipe(
353
+ filter(actions.removeBookmark.match),
354
+ concatMap(({ payload: bookmarkId }) => {
355
+ return from(api.isBookmarkFavorite(bookmarkId)).pipe(
356
+ map((isFavorite) => {
357
+ const action = isFavorite
358
+ ? actions.removeBookmarkAsFavourite
359
+ : actions.deleteBookmark;
360
+ return action(bookmarkId);
361
+ }),
362
+ );
363
+ }),
364
+ );
365
+
366
+ /**
367
+ * Handles failures for deleting a bookmark or removing a bookmark as a favorite.
368
+ */
369
+ const failure$ = merge(
370
+ action$.pipe(filter(actions.deleteBookmark.failure.match)),
371
+ action$.pipe(filter(actions.removeBookmarkAsFavourite.failure.match)),
372
+ ).pipe(map((action) => actions.removeBookmark.failure(action.payload, action.meta)));
373
+
374
+ /**
375
+ * Handles the success of deleting a bookmark or removing a bookmark as a favorite.
376
+ */
377
+ const success$ = merge(
378
+ action$.pipe(filter(actions.deleteBookmark.success.match)),
379
+ action$.pipe(filter(actions.removeBookmarkAsFavourite.success.match)),
380
+ ).pipe(
381
+ map((action) =>
382
+ actions.removeBookmark.success(
383
+ {
384
+ type: getBaseType(action.type),
385
+ bookmarkId: action.payload,
386
+ },
387
+ action.meta,
388
+ ),
389
+ ),
390
+ );
391
+
392
+ /**
393
+ * First dispatch either deletions of bookmark or removals of bookmarks as favorites.
394
+ * Then observer the results of those actions and dispatch the appropriate actions.
395
+ */
396
+ const flow$ = concat(dispatch$, merge(failure$, success$));
397
+
398
+ return flow$;
399
+ };
400
+
401
+ /**
402
+ * Defines a set of flows that handle various bookmark-related actions, such as fetching, creating, updating, and deleting bookmarks.
403
+ *
404
+ * @param api - An instance of `IBookmarkClient` that provides the necessary API methods for interacting with the bookmark service.
405
+ * @returns A combined flow that handles all the bookmark-related actions.
406
+ */
407
+ export const bookmarkApiFlows = (api: IBookmarkClient): Flow<BookmarkActions, BookmarkState> => {
408
+ // return handleFetchAllBookmark(api);
409
+ /**
410
+ * Combines multiple Bookmark-related observable flows into a single observable stream.
411
+ * The resulting observable stream emits the combined effects of these flows,
412
+ * which can be used to update the application state.
413
+ */
414
+ return (actions$: Observable<BookmarkActions>, state$: Observable<BookmarkState>) =>
415
+ of(
416
+ handleFetchBookmark,
417
+ handleFetchBookmarkData,
418
+ handleFetchAllBookmark,
419
+ handleCreateBookmark,
420
+ handleUpdateBookmark,
421
+ handleDeleteBookmark,
422
+ handleRemoveBookmark,
423
+ handleRemoveBookmarkFromFavorites,
424
+ handleAddBookmarkAsFavorite,
425
+ ).pipe(mergeMap((flow) => flow(api)(actions$, state$)));
426
+ };
@@ -0,0 +1,141 @@
1
+ import {
2
+ createReducer,
3
+ getBaseType,
4
+ isCompleteAction,
5
+ isFailureAction,
6
+ isRequestAction,
7
+ isSuccessAction,
8
+ type ActionBaseType,
9
+ } from '@equinor/fusion-observable';
10
+
11
+ import { bookmarkActions, type BookmarkActions } from './BookmarkProvider.actions';
12
+ import { type BookmarkState } from './BookmarkProvider.store';
13
+ import { BookmarkFlowError } from './BookmarkProvider.error';
14
+ import { BookmarkWithoutData } from './types';
15
+
16
+ /**
17
+ * Utility function that extracts the base action type from a given action object.
18
+ */
19
+ const getBookmarkBaseAction = <T extends BookmarkActions>(action: T): ActionBaseType<T> => {
20
+ return getBaseType(action.type) as ActionBaseType<T>;
21
+ };
22
+
23
+ /**
24
+ * The default initial state for the BookmarkProvider reducer.
25
+ */
26
+ const defaultInitialState: BookmarkState = {
27
+ status: new Set<ActionBaseType<BookmarkActions>>(),
28
+ errors: {} as Record<ActionBaseType<BookmarkActions>, BookmarkFlowError>,
29
+ bookmarks: {},
30
+ };
31
+
32
+ /**
33
+ * Creates a reducer for managing the state of bookmarks.
34
+ *
35
+ * @todo add fast-deep-equal to compare bookmarks
36
+ *
37
+ * @param initialState - The initial state of the bookmarks.
38
+ * @returns A reducer function for managing the bookmarks state.
39
+ */
40
+ export const createBookmarkReducer = (initialState?: Partial<BookmarkState>) =>
41
+ createReducer<BookmarkState, BookmarkActions>(
42
+ { ...defaultInitialState, ...initialState },
43
+ (builder) => {
44
+ builder
45
+ .addCase(bookmarkActions.fetchBookmark.success, (state, action) => {
46
+ if (action.payload.id in state.bookmarks) {
47
+ state.bookmarks[action.payload.id] = action.payload;
48
+ }
49
+ })
50
+ .addCase(bookmarkActions.fetchBookmarkData.success, (state, action) => {
51
+ const { bookmarkId, data } = action.payload;
52
+ if (state.currentBookmark?.id === bookmarkId) {
53
+ state.currentBookmark.payload = data;
54
+ }
55
+ })
56
+ .addCase(bookmarkActions.fetchBookmarks.success, (state, action) => {
57
+ // normalize the bookmarks array into a record
58
+ state.bookmarks = action.payload.reduce(
59
+ (acc, bookmark) => {
60
+ acc[bookmark.id] = bookmark;
61
+ return acc;
62
+ },
63
+ {} as Record<string, BookmarkWithoutData>,
64
+ );
65
+ })
66
+ .addCase(bookmarkActions.setBookmark, (state, action) => {
67
+ const bookmarkId = action.payload.id;
68
+ if (bookmarkId in state.bookmarks) {
69
+ state.bookmarks[bookmarkId] = action.payload;
70
+ }
71
+ if (state.currentBookmark?.id === bookmarkId) {
72
+ state.currentBookmark = action.payload;
73
+ }
74
+ })
75
+ .addCase(bookmarkActions.setCurrentBookmark, (state, action) => {
76
+ state.currentBookmark = action.payload;
77
+ })
78
+ .addCase(bookmarkActions.createBookmark.success, (state, action) => {
79
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
80
+ const { payload, ...bookmark } = action.payload;
81
+ state.bookmarks[bookmark.id] = bookmark;
82
+ })
83
+ .addCase(bookmarkActions.updateBookmark.success, (state, action) => {
84
+ const bookmarkId = action.payload.id;
85
+ const hasBookmark = bookmarkId in state.bookmarks;
86
+ const isCurrent = state.currentBookmark?.id === bookmarkId;
87
+
88
+ // get the current bookmark
89
+ const current = hasBookmark
90
+ ? state.bookmarks[bookmarkId]
91
+ : isCurrent
92
+ ? state.currentBookmark
93
+ : null;
94
+
95
+ // merge the current bookmark with the new data
96
+ const next = { ...current, ...action.payload };
97
+
98
+ // if the bookmark is in the current state, update it
99
+ if (hasBookmark) {
100
+ state.bookmarks[bookmarkId] = next;
101
+ }
102
+
103
+ // if the bookmark is the selected bookmark, update it
104
+ if (isCurrent) {
105
+ state.currentBookmark = next;
106
+ }
107
+ })
108
+
109
+ /** removal of bookmarks */
110
+ .addCase(bookmarkActions.deleteBookmark.success, (state, action) => {
111
+ delete state.bookmarks[action.payload];
112
+ })
113
+ .addCase(bookmarkActions.removeBookmarkAsFavourite.success, (state, action) => {
114
+ delete state.bookmarks[action.payload];
115
+ })
116
+
117
+ /** when a request is made, add the action type to the status object */
118
+ .addMatcher(isRequestAction, (state, action) => {
119
+ const actionName = getBookmarkBaseAction(action);
120
+ state.status.add(actionName);
121
+ })
122
+
123
+ /** when a request succeeds, remove the error from the errors object */
124
+ .addMatcher(isSuccessAction, (state, action) => {
125
+ const actionName = getBookmarkBaseAction(action);
126
+ delete state.errors[actionName];
127
+ })
128
+
129
+ /** when a request fails, add the error to the errors object */
130
+ .addMatcher(isFailureAction, (state, action) => {
131
+ const actionName = getBookmarkBaseAction(action);
132
+ state.errors[actionName] = action.payload;
133
+ })
134
+
135
+ /** when a request is complete, remove the status from the status set */
136
+ .addMatcher(isCompleteAction, (state, action) => {
137
+ const actionName = getBookmarkBaseAction(action);
138
+ state.status.delete(actionName);
139
+ });
140
+ },
141
+ );
@@ -0,0 +1,55 @@
1
+ import { BookmarkFlowError } from './BookmarkProvider.error';
2
+ import { BookmarkState } from './BookmarkProvider.store';
3
+ import type { Bookmark, BookmarkData } from './types';
4
+
5
+ /**
6
+ * Selects all bookmarks from the application state.
7
+ */
8
+ export const bookmarksSelector = (state: BookmarkState): Bookmark[] => {
9
+ return Object.values(state.bookmarks) as Bookmark[];
10
+ };
11
+
12
+ /**
13
+ * Retrieves a bookmark from the state by its ID.
14
+ */
15
+ export const bookmarkSelector = <T extends BookmarkData>(
16
+ state: BookmarkState,
17
+ id: string,
18
+ ): Bookmark<T> | undefined => {
19
+ return state.bookmarks[id] as Bookmark<T>;
20
+ };
21
+
22
+ /**
23
+ * Selects the active bookmark from the bookmark state.
24
+ *
25
+ * if there is an active bookmark, the function returns the bookmark data.
26
+ *
27
+ * if there is no active bookmark, the function returns null.
28
+ * if the active bookmark was never set, the function returns undefined.
29
+ */
30
+ export const activeBookmarkSelector = <T extends BookmarkData>(
31
+ state: BookmarkState,
32
+ ): Bookmark<T> | null => {
33
+ const selectedBookmark = state.currentBookmark as Bookmark<T>;
34
+ return selectedBookmark || null;
35
+ };
36
+
37
+ /**
38
+ * Selects the status from the bookmark state.
39
+ *
40
+ * @param state - The bookmark state.
41
+ * @returns The status from the bookmark state.
42
+ */
43
+ export const statusSelector = (state: BookmarkState): BookmarkState['status'] => {
44
+ return state.status;
45
+ };
46
+
47
+ /**
48
+ * Selects and returns an array of bookmark flow errors from the state.
49
+ *
50
+ * @param state - The bookmark state.
51
+ * @returns An array of bookmark flow errors.
52
+ */
53
+ export const errorsSelector = (state: BookmarkState): Array<BookmarkFlowError> => {
54
+ return Object.values(state.errors);
55
+ };
@@ -0,0 +1,52 @@
1
+ import { FlowSubject, type ActionBaseType } from '@equinor/fusion-observable';
2
+
3
+ import { type BookmarkActions } from './BookmarkProvider.actions';
4
+ import { type BookmarkFlowError } from './BookmarkProvider.error';
5
+ import { createBookmarkReducer } from './BookmarkProvider.reducer';
6
+ import { bookmarkApiFlows } from './BookmarkProvider.flows';
7
+ import type { IBookmarkClient } from './BookmarkClient.interface';
8
+ import type { Bookmark, BookmarkWithoutData } from './types';
9
+
10
+ /**
11
+ * Represents the state of the BookmarkProvider store.
12
+ */
13
+ export type BookmarkState = {
14
+ // current actions performed on the store
15
+ status: Set<ActionBaseType<BookmarkActions>>;
16
+ // errors that have occurred during the bookmark flow
17
+ errors: Record<ActionBaseType<BookmarkActions>, BookmarkFlowError>;
18
+ // the currently active bookmark, if any
19
+ currentBookmark?: Bookmark | null;
20
+ // the collection of bookmarks, keyed by their IDs
21
+ bookmarks: Record<string, BookmarkWithoutData>;
22
+ };
23
+
24
+ // export type BookmarkStoreFunctions = ActionCalls<typeof bookmarkActions>;
25
+
26
+ /**
27
+ * Represents the store for bookmarks, which is a flow subject that manages the state and actions for bookmarks.
28
+ */
29
+ export type BookmarkStore = FlowSubject<BookmarkState, BookmarkActions>;
30
+
31
+ /**
32
+ * Creates a new BookmarkStore instance with the provided initial state and client.
33
+ *
34
+ * @param args - An object containing the initial state and client for the bookmark store.
35
+ * @param args.initial - The initial state for the bookmark store.
36
+ * @param args.client - The IBookmarkClient instance to bookmark store flows.
37
+ * @returns A new BookmarkStore instance.
38
+ */
39
+ export const createBookmarkStore = (args: {
40
+ initial?: Partial<BookmarkState>;
41
+ client: IBookmarkClient;
42
+ }): BookmarkStore => {
43
+ // create the store
44
+ const store = new FlowSubject<BookmarkState, BookmarkActions>(
45
+ createBookmarkReducer(args.initial),
46
+ );
47
+
48
+ // add the bookmark API flows to the store
49
+ store.addFlow(bookmarkApiFlows(args.client));
50
+
51
+ return store;
52
+ };