@equinor/fusion-framework-module-app 6.0.3 → 6.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/CHANGELOG.md +204 -131
  2. package/dist/esm/AppClient.js +53 -4
  3. package/dist/esm/AppClient.js.map +1 -1
  4. package/dist/esm/AppModuleProvider.js +15 -0
  5. package/dist/esm/AppModuleProvider.js.map +1 -1
  6. package/dist/esm/app/App.js +157 -5
  7. package/dist/esm/app/App.js.map +1 -1
  8. package/dist/esm/app/actions.js +17 -0
  9. package/dist/esm/app/actions.js.map +1 -1
  10. package/dist/esm/app/create-reducer.js +3 -0
  11. package/dist/esm/app/create-reducer.js.map +1 -1
  12. package/dist/esm/app/create-state.js +4 -1
  13. package/dist/esm/app/create-state.js.map +1 -1
  14. package/dist/esm/app/flows.js +47 -1
  15. package/dist/esm/app/flows.js.map +1 -1
  16. package/dist/esm/errors.js +30 -0
  17. package/dist/esm/errors.js.map +1 -1
  18. package/dist/esm/version.js +1 -1
  19. package/dist/tsconfig.tsbuildinfo +1 -1
  20. package/dist/types/AppClient.d.ts +23 -1
  21. package/dist/types/AppModuleProvider.d.ts +12 -1
  22. package/dist/types/app/App.d.ts +58 -2
  23. package/dist/types/app/actions.d.ts +37 -1
  24. package/dist/types/app/create-reducer.d.ts +29 -0
  25. package/dist/types/app/events.d.ts +12 -1
  26. package/dist/types/app/flows.d.ts +16 -0
  27. package/dist/types/app/types.d.ts +6 -2
  28. package/dist/types/errors.d.ts +20 -0
  29. package/dist/types/types.d.ts +3 -0
  30. package/dist/types/version.d.ts +1 -1
  31. package/package.json +5 -3
  32. package/src/AppClient.ts +83 -4
  33. package/src/AppModuleProvider.ts +18 -1
  34. package/src/app/App.ts +267 -21
  35. package/src/app/actions.ts +34 -1
  36. package/src/app/create-reducer.ts +3 -0
  37. package/src/app/create-state.ts +12 -1
  38. package/src/app/events.ts +20 -1
  39. package/src/app/flows.ts +71 -1
  40. package/src/app/types.ts +6 -1
  41. package/src/errors.ts +43 -0
  42. package/src/types.ts +4 -0
  43. package/src/version.ts +1 -1
package/src/app/App.ts CHANGED
@@ -3,6 +3,7 @@ import type {
3
3
  AppScriptModule,
4
4
  AppManifest,
5
5
  AppConfig,
6
+ AppSettings,
6
7
  ConfigEnvironment,
7
8
  } from '../types';
8
9
  import { FlowSubject, Observable } from '@equinor/fusion-observable';
@@ -10,20 +11,22 @@ import { FlowSubject, Observable } from '@equinor/fusion-observable';
10
11
  import type { AppModuleProvider } from '../AppModuleProvider';
11
12
  import {
12
13
  combineLatest,
13
- filter,
14
- firstValueFrom,
15
- lastValueFrom,
16
- map,
17
14
  of,
18
- OperatorFunction,
15
+ type OperatorFunction,
19
16
  Subscription,
17
+ firstValueFrom,
18
+ lastValueFrom,
20
19
  } from 'rxjs';
20
+ import { defaultIfEmpty, filter, last, map, switchMap } from 'rxjs/operators';
21
+
21
22
  import { EventModule } from '@equinor/fusion-framework-module-event';
22
23
  import { AnyModule, ModuleType } from '@equinor/fusion-framework-module';
23
24
  import { createState } from './create-state';
24
25
  import { actions, Actions } from './actions';
25
26
  import { AppBundleState, AppBundleStateInitial } from './types';
26
27
 
28
+ import isEqual from 'fast-deep-equal';
29
+
27
30
  import './events';
28
31
 
29
32
  // TODO - move globally
@@ -65,6 +68,18 @@ export interface IApp<
65
68
  */
66
69
  get instance$(): Observable<AppModulesInstance<TModules>>;
67
70
 
71
+ /**
72
+ * Observable that emits the settings of the app.
73
+ * @returns An Observable that emits the app settings.
74
+ */
75
+ get settings$(): Observable<AppSettings>;
76
+
77
+ /**
78
+ * Observable that emits the status of the app.
79
+ * @returns An Observable that emits the app status.
80
+ */
81
+ get status$(): Observable<AppBundleState['status']>;
82
+
68
83
  /**
69
84
  * Gets the current state of the Application.
70
85
  * @returns The current state of the Application.
@@ -152,6 +167,56 @@ export interface IApp<
152
167
  */
153
168
  getConfigAsync(allow_cache?: boolean): Promise<AppConfig>;
154
169
 
170
+ /**
171
+ * Gets the app settings.
172
+ * @param force_refresh Whether to force refreshing the settings.
173
+ * @returns An observable that emits the app settings.
174
+ */
175
+ getSettings(force_refresh?: boolean): Observable<AppSettings>;
176
+
177
+ /**
178
+ * Retrieves the app settings asynchronously.
179
+ * @param allow_cache Whether to allow loading from cache.
180
+ * @returns A promise that resolves to the AppSettings.
181
+ */
182
+ getSettingsAsync(allow_cache?: boolean): Promise<AppSettings>;
183
+
184
+ /**
185
+ * Sets the app settings.
186
+ * @param settings The settings object to save.
187
+ * @returns An observable that emits the app settings.
188
+ */
189
+ updateSettings<T extends AppSettings>(settings: T): Observable<T>;
190
+
191
+ /**
192
+ * Sets the app settings asyncronously.
193
+ * @param settings The settings object to save.
194
+ * @returns An Promise that resolves the app settings.
195
+ */
196
+ updateSettingsAsync<T extends AppSettings>(settings: T): Promise<T>;
197
+
198
+ /**
199
+ * Updates a specific setting of the app.
200
+ * @param property The property to update.
201
+ * @param value The value to set.
202
+ * @returns An observable that emits the app settings.
203
+ */
204
+ updateSetting<T extends AppSettings, P extends keyof T>(
205
+ property: P,
206
+ value: T[P],
207
+ ): Observable<T[P]>;
208
+
209
+ /**
210
+ * Updates a specific setting of the app asynchronously.
211
+ * @param property The property to update.
212
+ * @param value The value to set.
213
+ * @returns A promise that resolves to the AppSettings.
214
+ */
215
+ updateSettingAsync<T extends AppSettings, P extends keyof T>(
216
+ property: P,
217
+ value: T[P],
218
+ ): Promise<T[P]>;
219
+
155
220
  /**
156
221
  * Gets the app manifest.
157
222
  * @param force_refresh Whether to force refreshing the manifest.
@@ -181,6 +246,8 @@ export interface IApp<
181
246
  getAppModuleAsync(allow_cache?: boolean): Promise<AppScriptModule>;
182
247
  }
183
248
 
249
+ const fallbackSettings: AppSettings = {};
250
+
184
251
  // TODO make streams distinct until changed from state
185
252
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
186
253
  export class App<
@@ -193,31 +260,39 @@ export class App<
193
260
  //#region === streams ===
194
261
 
195
262
  get manifest$(): Observable<AppManifest> {
196
- return this.#state.pipe(
197
- map(({ manifest }) => manifest),
198
- filterEmpty(),
199
- );
263
+ return this.#state.select((state) => state.manifest).pipe(filterEmpty());
200
264
  }
201
265
 
202
266
  get config$(): Observable<AppConfig<TEnv>> {
203
- return this.#state.pipe(
204
- map(({ config }) => config as AppConfig<TEnv>),
205
- filterEmpty(),
206
- );
267
+ return this.#state
268
+ .select((state) => state.config as AppConfig<TEnv>, isEqual)
269
+ .pipe(filterEmpty());
207
270
  }
208
271
 
209
272
  get modules$(): Observable<AppScriptModule> {
210
- return this.#state.pipe(
211
- map(({ modules }) => modules),
212
- filterEmpty(),
213
- );
273
+ return this.#state.select((state) => state.modules).pipe(filterEmpty());
214
274
  }
215
275
 
216
276
  get instance$(): Observable<AppModulesInstance<TModules>> {
217
- return this.#state.pipe(
218
- map(({ instance }) => instance as AppModulesInstance<TModules>),
219
- filterEmpty(),
220
- );
277
+ return this.#state
278
+ .select((state) => state.instance as AppModulesInstance<TModules>)
279
+ .pipe(filterEmpty());
280
+ }
281
+
282
+ get settings$(): Observable<AppSettings> {
283
+ return new Observable<AppSettings>((subscriber) => {
284
+ this.#state.next(actions.fetchSettings(this.appKey));
285
+ subscriber.add(
286
+ this.#state
287
+ .select((state) => state.settings, isEqual)
288
+ .pipe(filterEmpty(), defaultIfEmpty(fallbackSettings))
289
+ .subscribe(subscriber),
290
+ );
291
+ });
292
+ }
293
+
294
+ get status$(): Observable<AppBundleState['status']> {
295
+ return this.#state.select((state) => state.status);
221
296
  }
222
297
 
223
298
  //#endregion
@@ -358,6 +433,60 @@ export class App<
358
433
  });
359
434
  });
360
435
 
436
+ // monitor when application settings is loading
437
+ this.#state.addEffect(actions.fetchSettings.type, () => {
438
+ // dispatch event to notify listeners that the application settings is being loaded
439
+ event.dispatchEvent('onAppSettingsLoad', {
440
+ detail: { appKey },
441
+ source: this,
442
+ });
443
+ });
444
+
445
+ // monitor when application settings is loaded
446
+ this.#state.addEffect(actions.fetchSettings.success.type, (action) => {
447
+ // dispatch event to notify listeners that the application settings has been loaded
448
+ event.dispatchEvent('onAppSettingsLoaded', {
449
+ detail: { appKey, settings: action.payload },
450
+ source: this,
451
+ });
452
+ });
453
+
454
+ // monitor when application settings fails to load
455
+ this.#state.addEffect(actions.fetchSettings.failure.type, (action) => {
456
+ // dispatch event to notify listeners that the application settings failed to load
457
+ event.dispatchEvent('onAppSettingsFailure', {
458
+ detail: { appKey, error: action.payload },
459
+ source: this,
460
+ });
461
+ });
462
+
463
+ // monitor when application settings is updated
464
+ this.#state.addEffect(actions.updateSettings.type, (action) => {
465
+ // dispatch event to notify listeners that the application settings has been loaded
466
+ event.dispatchEvent('onAppSettingsUpdate', {
467
+ detail: { appKey, settings: action.payload.settings },
468
+ source: this,
469
+ });
470
+ });
471
+
472
+ // monitor when application settings is updated
473
+ this.#state.addEffect(actions.updateSettings.success.type, (action) => {
474
+ // dispatch event to notify listeners that the application settings has been loaded
475
+ event.dispatchEvent('onAppSettingsUpdated', {
476
+ detail: { appKey, settings: action.payload.settings },
477
+ source: this,
478
+ });
479
+ });
480
+
481
+ // monitor when application settings fails to updated
482
+ this.#state.addEffect(actions.updateSettings.failure.type, (action) => {
483
+ // dispatch event to notify listeners that the application settings has been loaded
484
+ event.dispatchEvent('onAppSettingsUpdateFailure', {
485
+ detail: { appKey, settings: action.payload },
486
+ source: this,
487
+ });
488
+ });
489
+
361
490
  // monitor when application script is loading
362
491
  this.#state.addEffect(actions.importApp.type, () => {
363
492
  // dispatch event to notify listeners that the application script is being loaded
@@ -529,6 +658,123 @@ export class App<
529
658
  return operator(this.getConfig(!allow_cache));
530
659
  }
531
660
 
661
+ public getSettings<T extends AppSettings>(force_refresh = false): Observable<T> {
662
+ return new Observable<T>((subscriber) => {
663
+ if (this.#state.value.settings) {
664
+ // emit current settings to the subscriber
665
+ subscriber.next(this.#state.value.settings as T);
666
+ if (!force_refresh) {
667
+ // since we have the settings and no force refresh, complete the stream
668
+ return subscriber.complete();
669
+ }
670
+ }
671
+
672
+ // when stream closes, dispose of subscription to change of state settings
673
+ subscriber.add(
674
+ // monitor changes to state changes of settings and emit to subscriber
675
+ this.#state.addEffect('set_settings', ({ payload }) => {
676
+ subscriber.next(payload as T);
677
+ }),
678
+ );
679
+
680
+ // when stream closes, dispose of subscription to fetch settings
681
+ subscriber.add(
682
+ // monitor success of fetching settings and emit to subscriber
683
+ this.#state.addEffect('fetch_settings::success', ({ payload }) => {
684
+ // application settings loaded, emit to subscriber and complete the stream
685
+ subscriber.next(payload as T);
686
+ subscriber.complete();
687
+ }),
688
+ );
689
+
690
+ // when stream closes, dispose of subscription to fetch settings
691
+ subscriber.add(
692
+ // monitor failure of fetching settings and emit error to subscriber
693
+ this.#state.addEffect('fetch_settings::failure', ({ payload }) => {
694
+ // application settings failed to load, emit error and complete the stream
695
+ subscriber.error(
696
+ Error('failed to load application settings', {
697
+ cause: payload,
698
+ }),
699
+ );
700
+ }),
701
+ );
702
+
703
+ this.#state.next(actions.fetchSettings(this.appKey));
704
+ });
705
+ }
706
+
707
+ public getSettingsAsync<T extends AppSettings>(allow_cache = true): Promise<T> {
708
+ // when allow_cache is true, use first emitted value, otherwise use last emitted value
709
+ const operator = allow_cache ? firstValueFrom : lastValueFrom;
710
+ return operator(this.getSettings<T>(!allow_cache));
711
+ }
712
+
713
+ public updateSettings<T extends AppSettings>(settings: T): Observable<T> {
714
+ return new Observable((subscriber) => {
715
+ subscriber.add(
716
+ // monitor failure of updating settings and emit error to subscriber
717
+ this.#state.addEffect('update_settings::failure', ({ payload }) => {
718
+ // request to reset settings to source state
719
+ this.#state.next(actions.fetchSettings(this.appKey));
720
+
721
+ // application settings failed to save, emit error and complete the stream
722
+ subscriber.error(
723
+ Error('failed to update application settings', {
724
+ cause: payload,
725
+ }),
726
+ );
727
+ }),
728
+ );
729
+
730
+ subscriber.add(
731
+ // monitor success of updating settings and emit to subscriber
732
+ this.#state.addEffect('update_settings::success', ({ payload }) => {
733
+ subscriber.next(payload as T);
734
+ subscriber.complete();
735
+ }),
736
+ );
737
+
738
+ // optimistic update of settings
739
+ this.#state.next(actions.setSettings(settings));
740
+
741
+ // request to update settings
742
+ this.#state.next(actions.updateSettings(this.appKey, settings));
743
+ });
744
+ }
745
+
746
+ public updateSettingsAsync<T extends AppSettings>(settings: T): Promise<T> {
747
+ return lastValueFrom(this.updateSettings(settings));
748
+ }
749
+
750
+ public updateSetting<T extends AppSettings, P extends keyof T>(
751
+ property: P,
752
+ value: T[P],
753
+ ): Observable<T[P]> {
754
+ const currentSettings$ =
755
+ this.#state.value.settings === undefined
756
+ ? // if settings are not loaded, fetch settings
757
+ this.getSettings().pipe(last())
758
+ : // if settings are loaded, use current settings
759
+ of(this.#state.value.settings);
760
+
761
+ return currentSettings$.pipe(
762
+ // merge current settings with new value
763
+ map((settings) => ({ ...settings, [property]: value })),
764
+ // update settings
765
+ switchMap((settings) => this.updateSettings<T>(settings as T)),
766
+ // return the updated property
767
+ map((settings) => settings[property] as T[P]),
768
+ );
769
+ }
770
+
771
+ public updateSettingAsync<T extends AppSettings, P extends keyof T>(
772
+ property: P,
773
+ value: T[P],
774
+ ): Promise<T[P]> {
775
+ return lastValueFrom(this.updateSetting<T, P>(property, value));
776
+ }
777
+
532
778
  public getManifest(force_refresh = false): Observable<AppManifest> {
533
779
  return new Observable((subscriber) => {
534
780
  if (this.#state.value.manifest) {
@@ -4,7 +4,13 @@ import {
4
4
  createAction,
5
5
  createAsyncAction,
6
6
  } from '@equinor/fusion-observable';
7
- import type { AppConfig, AppManifest, AppModulesInstance, AppScriptModule } from '../types';
7
+ import type {
8
+ AppConfig,
9
+ AppManifest,
10
+ AppModulesInstance,
11
+ AppScriptModule,
12
+ AppSettings,
13
+ } from '../types';
8
14
 
9
15
  const createActions = () => ({
10
16
  /** Manifest loading */
@@ -31,6 +37,33 @@ const createActions = () => ({
31
37
  (config: AppConfig) => ({ payload: config }),
32
38
  (error: unknown) => ({ payload: error }),
33
39
  ),
40
+ /** Settings loading */
41
+ setSettings: createAction('set_settings', (settings?: AppSettings) => ({
42
+ payload: settings,
43
+ })),
44
+ /** Fetching settings */
45
+ fetchSettings: createAsyncAction(
46
+ 'fetch_settings',
47
+ (appKey: string) => ({ payload: { appKey } }),
48
+ (settings: AppSettings) => ({ payload: settings }),
49
+ (error: unknown) => ({ payload: error }),
50
+ ),
51
+ /** Updating settings */
52
+ updateSettings: createAsyncAction(
53
+ 'update_settings',
54
+ (appKey: string, settings: AppSettings) => ({
55
+ payload: { appKey, settings },
56
+ }),
57
+ (settings: AppSettings) => ({
58
+ payload: settings,
59
+ }),
60
+ (error: unknown) => ({
61
+ payload: error,
62
+ }),
63
+ ),
64
+ updateSettingsAbort: createAction('update_settings::abort', (id: string) => ({
65
+ payload: id,
66
+ })),
34
67
  /** App loading */
35
68
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
69
  setModule: createAction('set_module', (module: any) => ({ payload: module })),
@@ -30,6 +30,9 @@ export const createReducer = (value: AppBundleStateInitial) =>
30
30
  .addCase(actions.setConfig, (state, action) => {
31
31
  state.config = action.payload;
32
32
  })
33
+ .addCase(actions.setSettings, (state, action) => {
34
+ state.settings = action.payload;
35
+ })
33
36
  .addCase(actions.setModule, (state, action) => {
34
37
  state.modules = action.payload;
35
38
  })
@@ -2,7 +2,13 @@ import { FlowSubject } from '@equinor/fusion-observable';
2
2
 
3
3
  import { createReducer } from './create-reducer';
4
4
 
5
- import { handleFetchManifest, handleFetchConfig, handleImportApplication } from './flows';
5
+ import {
6
+ handleFetchManifest,
7
+ handleFetchConfig,
8
+ handleFetchSettings,
9
+ handleUpdateSettings,
10
+ handleImportApplication,
11
+ } from './flows';
6
12
 
7
13
  import type { Actions } from './actions';
8
14
  import type { AppBundleState, AppBundleStateInitial } from './types';
@@ -23,6 +29,11 @@ export const createState = (
23
29
  // add handler for fetching config
24
30
  state.addFlow(handleFetchConfig(provider));
25
31
 
32
+ // add handler for fetching settings
33
+ state.addFlow(handleFetchSettings(provider));
34
+
35
+ state.addFlow(handleUpdateSettings(provider));
36
+
26
37
  // add handler for loading application script
27
38
  state.addFlow(handleImportApplication(provider));
28
39
 
package/src/app/events.ts CHANGED
@@ -2,7 +2,13 @@ import type { FrameworkEvent, FrameworkEventInit } from '@equinor/fusion-framewo
2
2
 
3
3
  import type { App } from './App';
4
4
 
5
- import type { AppConfig, AppManifest, AppModulesInstance, AppScriptModule } from '../types';
5
+ import type {
6
+ AppConfig,
7
+ AppManifest,
8
+ AppModulesInstance,
9
+ AppScriptModule,
10
+ AppSettings,
11
+ } from '../types';
6
12
 
7
13
  /** base event type for applications */
8
14
  export type AppEventEventInit<TDetail extends Record<string, unknown> | unknown = unknown> =
@@ -45,6 +51,19 @@ declare module '@equinor/fusion-framework-module-event' {
45
51
  }>;
46
52
  onAppConfigFailure: AppEventFailure;
47
53
 
54
+ onAppSettingsLoad: AppEvent;
55
+ /** fired when the application has loaded corresponding settings */
56
+ onAppSettingsLoaded: AppEvent<{
57
+ settings: AppSettings;
58
+ }>;
59
+ onAppSettingsFailure: AppEventFailure;
60
+
61
+ onAppSettingsUpdate: AppEvent;
62
+ onAppSettingsUpdated: AppEvent<{
63
+ settings: AppSettings;
64
+ }>;
65
+ onAppSettingsUpdateFailure: AppEventFailure;
66
+
48
67
  /** fired when the application has loaded corresponding javascript module */
49
68
  onAppScriptLoad: AppEvent;
50
69
  onAppScriptLoaded: AppEvent<{
package/src/app/flows.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { from, of, concat } from 'rxjs';
2
- import { catchError, filter, last, map, share, switchMap } from 'rxjs/operators';
2
+ import { catchError, concatMap, filter, last, map, share, switchMap } from 'rxjs/operators';
3
3
 
4
4
  import { actions } from './actions';
5
5
 
@@ -93,6 +93,76 @@ export const handleFetchConfig =
93
93
  }),
94
94
  );
95
95
 
96
+ /**
97
+ * Handles the fetch settings action by fetching the app settings from the provider,
98
+ * filtering out null values, and dispatching success or failure actions accordingly.
99
+ *
100
+ * @param provider The AppModuleProvider used to fetch the app settings.
101
+ * @returns A Flow function that takes an Observable of actions and returns an Observable of actions.
102
+ */
103
+ export const handleFetchSettings =
104
+ (provider: AppModuleProvider): Flow<Actions, AppBundleState> =>
105
+ (action$) =>
106
+ action$.pipe(
107
+ // only handle fetch settings request actions
108
+ filter(actions.fetchSettings.match),
109
+ // when request is received, abort any ongoing request and start new
110
+ switchMap(({ payload }) => {
111
+ const { appKey } = payload;
112
+
113
+ // fetch settings from provider
114
+ const subject = from(provider.getAppSettings(appKey)).pipe(
115
+ // filter out null values
116
+ filter((x) => !!x),
117
+ // allow multiple subscriptions
118
+ share(),
119
+ );
120
+
121
+ // first load settings and then dispatch success action
122
+ return concat(
123
+ subject.pipe(map((settings) => actions.setSettings(settings))),
124
+ subject.pipe(
125
+ last(),
126
+ map((settings) => actions.fetchSettings.success(settings)),
127
+ ),
128
+ ).pipe(
129
+ // catch any error and dispatch failure action
130
+ catchError((err) => {
131
+ return of(actions.fetchSettings.failure(err));
132
+ }),
133
+ );
134
+ }),
135
+ );
136
+
137
+ /**
138
+ * Handles the set settings action by setting the app settings from the provider,
139
+ * filtering out null values, and dispatching success or failure actions accordingly.
140
+ *
141
+ * @param provider The AppModuleProvider used to fetch the app settings.
142
+ * @returns A Flow function that takes an Observable of actions and returns an Observable of actions.
143
+ */
144
+ export const handleUpdateSettings =
145
+ (provider: AppModuleProvider): Flow<Actions, AppBundleState> =>
146
+ (action$) => {
147
+ return action$.pipe(filter(actions.updateSettings.match)).pipe(
148
+ switchMap(({ payload }) => {
149
+ const { appKey, settings } = payload;
150
+ return provider.updateAppSettings(appKey, settings).pipe(
151
+ // take the last value
152
+ last(),
153
+ // request updating of settings and dispatch success action
154
+ concatMap((updatedSettings) =>
155
+ from([
156
+ actions.setSettings(updatedSettings),
157
+ actions.updateSettings.success(updatedSettings),
158
+ ]),
159
+ ),
160
+ catchError((err) => of(actions.updateSettings.failure(err))),
161
+ );
162
+ }),
163
+ );
164
+ };
165
+
96
166
  /**
97
167
  * Handles the import application flow.
98
168
  * @returns A flow that takes in actions and returns an observable of AppBundleState.
package/src/app/types.ts CHANGED
@@ -1,10 +1,13 @@
1
+ import { ActionBaseType } from '@equinor/fusion-observable';
1
2
  import type {
2
3
  AppManifest,
3
4
  AppConfig,
4
5
  AppModulesInstance,
5
6
  AppScriptModule,
6
7
  ConfigEnvironment,
8
+ AppSettings,
7
9
  } from '../types';
10
+ import { Actions } from './actions';
8
11
 
9
12
  /**
10
13
  * Represents the state of an application bundle.
@@ -16,6 +19,7 @@ import type {
16
19
  * @property {Set<string>} status - A set of strings representing the status of the application.
17
20
  * @property {AppManifest} [manifest] - An optional manifest describing the application.
18
21
  * @property {AppConfig<TConfig>} [config] - An optional configuration object for the application.
22
+ * @property {AppSettings} [settings] - An optional application settings object.
19
23
  * @property {AppScriptModule} [modules] - An optional script module for the application.
20
24
  * @property {AppModulesInstance<TModules>} [instance] - An optional instance of the application modules.
21
25
  */
@@ -25,9 +29,10 @@ export type AppBundleState<
25
29
  TModules = any,
26
30
  > = {
27
31
  appKey: string;
28
- status: Set<string>;
32
+ status: Set<ActionBaseType<Actions>>;
29
33
  manifest?: AppManifest;
30
34
  config?: AppConfig<TConfig>;
35
+ settings?: AppSettings;
31
36
  modules?: AppScriptModule;
32
37
  instance?: AppModulesInstance<TModules>;
33
38
  };
package/src/errors.ts CHANGED
@@ -86,6 +86,49 @@ export class AppConfigError extends Error {
86
86
  }
87
87
  }
88
88
 
89
+ /**
90
+ * Represents an error that occurs while fetching application settings.
91
+ */
92
+ export class AppSettingsError extends Error {
93
+ /**
94
+ * Creates an instance of `AppSettingsError` based on the HTTP response status.
95
+ * @param response The HTTP response.
96
+ * @param options Additional error options.
97
+ * @returns An instance of `AppSettingsError` based on the HTTP response status.
98
+ */
99
+ static fromHttpResponse(response: Response, options?: ErrorOptions): AppSettingsError {
100
+ switch (response.status) {
101
+ case 401:
102
+ return new AppSettingsError(
103
+ 'unauthorized',
104
+ 'failed to load application settings, request not authorized',
105
+ options,
106
+ );
107
+ case 404:
108
+ return new AppSettingsError('not_found', 'application not found', options);
109
+ }
110
+ return new AppSettingsError(
111
+ 'unknown',
112
+ `failed to load application settings, status code ${response.status}`,
113
+ options,
114
+ );
115
+ }
116
+
117
+ /**
118
+ * Creates an instance of `AppSettingsError`.
119
+ * @param type The type of the application error.
120
+ * @param message The error message.
121
+ * @param options Additional error options.
122
+ */
123
+ constructor(
124
+ public readonly type: AppErrorType,
125
+ message?: string,
126
+ options?: ErrorOptions,
127
+ ) {
128
+ super(message, options);
129
+ }
130
+ }
131
+
89
132
  /**
90
133
  * Represents an error that occurs when loading the application script.
91
134
  */
package/src/types.ts CHANGED
@@ -23,6 +23,10 @@ export type AppEnv<TEnv extends ConfigEnvironment = ConfigEnvironment, TProps =
23
23
  // TODO: change to module-services when new app service is created
24
24
  export type ModuleDeps = [HttpModule, ServiceDiscoveryModule, EventModule];
25
25
 
26
+ export interface AppSettings {
27
+ [key: string]: unknown;
28
+ }
29
+
26
30
  // TODO: remove `report` and `launcher` when legacy apps are removed
27
31
  export type AppType = 'standalone' | 'report' | 'launcher' | 'template';
28
32
 
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '6.0.3';
2
+ export const version = '6.1.1';