@equinor/fusion-framework-module-app 0.0.0-next-20230915111233

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 (82) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/CHANGELOG.md +555 -0
  3. package/LICENSE +21 -0
  4. package/dist/esm/AppConfigBuilder.js +57 -0
  5. package/dist/esm/AppConfigBuilder.js.map +1 -0
  6. package/dist/esm/AppConfigurator.js +82 -0
  7. package/dist/esm/AppConfigurator.js.map +1 -0
  8. package/dist/esm/AppModuleProvider.js +104 -0
  9. package/dist/esm/AppModuleProvider.js.map +1 -0
  10. package/dist/esm/app/App.js +250 -0
  11. package/dist/esm/app/App.js.map +1 -0
  12. package/dist/esm/app/actions.js +21 -0
  13. package/dist/esm/app/actions.js.map +1 -0
  14. package/dist/esm/app/create-reducer.js +30 -0
  15. package/dist/esm/app/create-reducer.js.map +1 -0
  16. package/dist/esm/app/create-state.js +12 -0
  17. package/dist/esm/app/create-state.js.map +1 -0
  18. package/dist/esm/app/events.js +2 -0
  19. package/dist/esm/app/events.js.map +1 -0
  20. package/dist/esm/app/flows.js +17 -0
  21. package/dist/esm/app/flows.js.map +1 -0
  22. package/dist/esm/app/index.js +3 -0
  23. package/dist/esm/app/index.js.map +1 -0
  24. package/dist/esm/app/types.js +2 -0
  25. package/dist/esm/app/types.js.map +1 -0
  26. package/dist/esm/enable-app-module.js +10 -0
  27. package/dist/esm/enable-app-module.js.map +1 -0
  28. package/dist/esm/errors.js +37 -0
  29. package/dist/esm/errors.js.map +1 -0
  30. package/dist/esm/events.js +2 -0
  31. package/dist/esm/events.js.map +1 -0
  32. package/dist/esm/helpers.js +2 -0
  33. package/dist/esm/helpers.js.map +1 -0
  34. package/dist/esm/index.js +7 -0
  35. package/dist/esm/index.js.map +1 -0
  36. package/dist/esm/module.js +26 -0
  37. package/dist/esm/module.js.map +1 -0
  38. package/dist/esm/types.js +2 -0
  39. package/dist/esm/types.js.map +1 -0
  40. package/dist/esm/version.js +2 -0
  41. package/dist/esm/version.js.map +1 -0
  42. package/dist/tsconfig.tsbuildinfo +1 -0
  43. package/dist/types/AppConfigBuilder.d.ts +26 -0
  44. package/dist/types/AppConfigurator.d.ts +28 -0
  45. package/dist/types/AppModuleProvider.d.ts +29 -0
  46. package/dist/types/app/App.d.ts +70 -0
  47. package/dist/types/app/actions.d.ts +60 -0
  48. package/dist/types/app/create-reducer.d.ts +3 -0
  49. package/dist/types/app/create-state.d.ts +5 -0
  50. package/dist/types/app/events.d.ts +28 -0
  51. package/dist/types/app/flows.d.ts +7 -0
  52. package/dist/types/app/index.d.ts +2 -0
  53. package/dist/types/app/types.d.ts +10 -0
  54. package/dist/types/enable-app-module.d.ts +3 -0
  55. package/dist/types/errors.d.ts +16 -0
  56. package/dist/types/events.d.ts +11 -0
  57. package/dist/types/helpers.d.ts +2 -0
  58. package/dist/types/index.d.ts +7 -0
  59. package/dist/types/module.d.ts +13 -0
  60. package/dist/types/types.d.ts +73 -0
  61. package/dist/types/version.d.ts +1 -0
  62. package/package.json +50 -0
  63. package/src/AppConfigBuilder.ts +89 -0
  64. package/src/AppConfigurator.ts +104 -0
  65. package/src/AppModuleProvider.ts +181 -0
  66. package/src/app/App.ts +399 -0
  67. package/src/app/actions.ts +63 -0
  68. package/src/app/create-reducer.ts +45 -0
  69. package/src/app/create-state.ts +21 -0
  70. package/src/app/events.ts +67 -0
  71. package/src/app/flows.ts +61 -0
  72. package/src/app/index.ts +3 -0
  73. package/src/app/types.ts +17 -0
  74. package/src/enable-app-module.ts +21 -0
  75. package/src/errors.ts +65 -0
  76. package/src/events.ts +18 -0
  77. package/src/helpers.ts +4 -0
  78. package/src/index.ts +16 -0
  79. package/src/module.ts +30 -0
  80. package/src/types.ts +95 -0
  81. package/src/version.ts +2 -0
  82. package/tsconfig.json +33 -0
@@ -0,0 +1,181 @@
1
+ import {
2
+ BehaviorSubject,
3
+ catchError,
4
+ distinctUntilChanged,
5
+ map,
6
+ Observable,
7
+ pairwise,
8
+ Subscription,
9
+ takeWhile,
10
+ } from 'rxjs';
11
+
12
+ import { ModuleType } from '@equinor/fusion-framework-module';
13
+ import { HttpResponseError } from '@equinor/fusion-framework-module-http';
14
+ import { EventModule } from '@equinor/fusion-framework-module-event';
15
+
16
+ import { Query } from '@equinor/fusion-query';
17
+
18
+ import type { AppConfig, AppManifest, CurrentApp } from './types';
19
+
20
+ import { App, filterEmpty, IApp } from './app/App';
21
+ import { AppModuleConfig } from './AppConfigurator';
22
+ import { AppConfigError, AppManifestError } from './errors';
23
+ import { AppBundleStateInitial } from './app/types';
24
+
25
+ export class AppModuleProvider {
26
+ static compareAppManifest<T extends AppManifest>(a?: T, b?: T): boolean {
27
+ return JSON.stringify(a) === JSON.stringify(b);
28
+ }
29
+
30
+ public appClient: Query<AppManifest, { appKey: string }>;
31
+ #appsClient: Query<AppManifest[], void>;
32
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
+ #configClient: Query<AppConfig<any>, { appKey: string; tag?: string }>;
34
+
35
+ #current$: BehaviorSubject<CurrentApp>;
36
+
37
+ #subscription = new Subscription();
38
+
39
+ #event?: ModuleType<EventModule>;
40
+
41
+ /**
42
+ * fetch an application by key
43
+ * @param appKey - application key
44
+ */
45
+ get current(): CurrentApp {
46
+ return this.#current$.value;
47
+ }
48
+
49
+ get current$(): Observable<CurrentApp> {
50
+ return this.#current$.pipe(
51
+ distinctUntilChanged((prev, next) => {
52
+ if (prev && next) {
53
+ return prev.appKey === next.appKey;
54
+ }
55
+ return prev === next;
56
+ }),
57
+ );
58
+ }
59
+
60
+ constructor(args: { config: AppModuleConfig; event?: ModuleType<EventModule> }) {
61
+ const { event, config } = args;
62
+
63
+ this.#event = event;
64
+
65
+ this.#current$ = new BehaviorSubject<CurrentApp>(undefined);
66
+
67
+ this.appClient = new Query(config.client.getAppManifest);
68
+ this.#appsClient = new Query(config.client.getAppManifests);
69
+ this.#configClient = new Query(config.client.getAppConfig);
70
+
71
+ this.#subscription.add(() => this.appClient.complete());
72
+ this.#subscription.add(() => this.#appsClient.complete());
73
+ this.#subscription.add(() => this.#configClient.complete());
74
+ this.#subscription.add(
75
+ this.current$
76
+ .pipe(
77
+ pairwise(),
78
+ takeWhile(() => !!event),
79
+ )
80
+ .subscribe(([previous, next]) => {
81
+ event?.dispatchEvent('onCurrentAppChanged', {
82
+ source: this,
83
+ detail: { previous, next },
84
+ });
85
+ }),
86
+ );
87
+
88
+ this.#subscription.add(
89
+ this.#current$
90
+ .pipe(
91
+ pairwise(),
92
+ map(([previous]) => previous),
93
+ filterEmpty(),
94
+ )
95
+ .subscribe((app) => app.dispose()),
96
+ );
97
+ }
98
+
99
+ /**
100
+ * fetch an application by key
101
+ * @param appKey - application key
102
+ */
103
+ public getAppManifest(appKey: string): Observable<AppManifest> {
104
+ return Query.extractQueryValue(
105
+ this.appClient.query({ appKey }).pipe(
106
+ catchError((err) => {
107
+ /** extract cause, since error will be a `QueryError` */
108
+ const { cause } = err;
109
+ if (cause instanceof AppManifestError) {
110
+ throw cause;
111
+ }
112
+ if (cause instanceof HttpResponseError) {
113
+ throw AppManifestError.fromHttpResponse(cause.response, { cause });
114
+ }
115
+ throw new AppManifestError('unknown', 'failed to load manifest', { cause });
116
+ }),
117
+ ),
118
+ );
119
+ }
120
+
121
+ /**
122
+ * fetch all applications
123
+ */
124
+ public getAllAppManifests(): Observable<AppManifest[]> {
125
+ return Query.extractQueryValue(this.#appsClient.query());
126
+ }
127
+
128
+ /**
129
+ * fetch configuration for an application
130
+ * @param appKey - application key
131
+ */
132
+ public getAppConfig<TType = unknown>(
133
+ appKey: string,
134
+ tag?: string,
135
+ ): Observable<AppConfig<TType>> {
136
+ return Query.extractQueryValue(
137
+ this.#configClient.query({ appKey, tag }).pipe(
138
+ catchError((err) => {
139
+ /** extract cause, since error will be a `QueryError` */
140
+ const { cause } = err;
141
+ if (cause instanceof AppConfigError) {
142
+ throw cause;
143
+ }
144
+ if (cause instanceof HttpResponseError) {
145
+ throw AppConfigError.fromHttpResponse(cause.response, { cause });
146
+ }
147
+ throw new AppConfigError('unknown', 'failed to load config', { cause });
148
+ }),
149
+ ),
150
+ );
151
+ }
152
+
153
+ /**
154
+ * set the current application, will internally resolve manifest
155
+ * @param appKey - application key
156
+ */
157
+ public setCurrentApp(appKeyOrApp: string | IApp): void {
158
+ const app =
159
+ typeof appKeyOrApp === 'string' ? this.createApp({ appKey: appKeyOrApp }) : appKeyOrApp;
160
+ this.#current$.next(app as CurrentApp);
161
+ }
162
+
163
+ public clearCurrentApp(): void {
164
+ this.#current$.next(null);
165
+ }
166
+
167
+ /**
168
+ * This should not be used, only for legacy creation backdoor
169
+ * @deprecated
170
+ */
171
+ public createApp(value: AppBundleStateInitial): App {
172
+ console.warn();
173
+ return new App(value, { provider: this, event: this.#event });
174
+ }
175
+
176
+ public dispose() {
177
+ this.#subscription.unsubscribe();
178
+ }
179
+ }
180
+
181
+ export default AppModuleProvider;
package/src/app/App.ts ADDED
@@ -0,0 +1,399 @@
1
+ import { AppConfig, AppManifest, AppModulesInstance, AppScriptModule } from '../types';
2
+ import { FlowSubject, Observable } from '@equinor/fusion-observable';
3
+
4
+ import type { AppModuleProvider } from '../AppModuleProvider';
5
+ import {
6
+ combineLatest,
7
+ filter,
8
+ firstValueFrom,
9
+ lastValueFrom,
10
+ map,
11
+ of,
12
+ OperatorFunction,
13
+ Subscription,
14
+ } from 'rxjs';
15
+ import { EventModule } from '@equinor/fusion-framework-module-event';
16
+ import { AnyModule, ModuleType } from '@equinor/fusion-framework-module';
17
+ import { createState } from './create-state';
18
+ import { actions, Actions } from './actions';
19
+ import { AppBundleState, AppBundleStateInitial } from './types';
20
+
21
+ import './events';
22
+
23
+ // TODO - move globally
24
+ export function filterEmpty<T>(): OperatorFunction<T | null | undefined, T> {
25
+ return filter((value): value is T => value !== undefined && value !== null);
26
+ }
27
+
28
+ // TODO add comments!
29
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
+ export interface IApp<TEnv = any, TModules extends Array<AnyModule> | unknown = unknown> {
31
+ get manifest$(): Observable<AppManifest>;
32
+ get config$(): Observable<AppConfig<TEnv>>;
33
+ get modules$(): Observable<AppScriptModule>;
34
+ get instance$(): Observable<AppModulesInstance<TModules>>;
35
+ get state(): AppBundleState;
36
+ get appKey(): string;
37
+ get manifest(): AppManifest | undefined;
38
+ get manifestAsync(): Promise<AppManifest>;
39
+ get config(): AppConfig<TEnv> | undefined;
40
+ get instance(): AppModulesInstance<TModules> | undefined;
41
+
42
+ initialize(): Observable<{
43
+ manifest: AppManifest;
44
+ script: AppScriptModule;
45
+ config: AppConfig;
46
+ }>;
47
+
48
+ loadConfig(): void;
49
+
50
+ loadManifest(): void;
51
+
52
+ loadAppModule(allow_cache?: boolean): void;
53
+
54
+ getConfig(force_refresh?: boolean): Observable<AppConfig>;
55
+ getConfigAsync(allow_cache?: boolean): Promise<AppConfig>;
56
+
57
+ getManifest(force_refresh?: boolean): Observable<AppManifest>;
58
+ getManifestAsync(allow_cache?: boolean): Promise<AppManifest>;
59
+
60
+ getAppModule(force_refresh?: boolean): Observable<AppScriptModule>;
61
+ getAppModuleAsync(allow_cache?: boolean): Promise<AppScriptModule>;
62
+ }
63
+
64
+ // TODO make streams distinct until changed from state
65
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
66
+ export class App<TEnv = any, TModules extends Array<AnyModule> | unknown = unknown>
67
+ implements IApp<TEnv, TModules>
68
+ {
69
+ #state: FlowSubject<AppBundleState, Actions>;
70
+
71
+ //#region === streams ===
72
+
73
+ get manifest$(): Observable<AppManifest> {
74
+ return this.#state.pipe(
75
+ map(({ manifest }) => manifest),
76
+ filterEmpty(),
77
+ );
78
+ }
79
+
80
+ get config$(): Observable<AppConfig<TEnv>> {
81
+ return this.#state.pipe(
82
+ map(({ config }) => config),
83
+ filterEmpty(),
84
+ );
85
+ }
86
+
87
+ get modules$(): Observable<AppScriptModule> {
88
+ return this.#state.pipe(
89
+ map(({ modules }) => modules),
90
+ filterEmpty(),
91
+ );
92
+ }
93
+
94
+ get instance$(): Observable<AppModulesInstance<TModules>> {
95
+ return this.#state.pipe(
96
+ map(({ instance }) => instance as AppModulesInstance<TModules>),
97
+ filterEmpty(),
98
+ );
99
+ }
100
+
101
+ //#endregion
102
+
103
+ get state(): AppBundleState {
104
+ // todo deep-freeze
105
+ return this.#state.value;
106
+ }
107
+
108
+ get appKey(): string {
109
+ return this.#state.value.appKey;
110
+ }
111
+
112
+ get manifest(): AppManifest | undefined {
113
+ return this.state.manifest;
114
+ }
115
+
116
+ get manifestAsync(): Promise<AppManifest> {
117
+ return firstValueFrom(this.manifest$);
118
+ }
119
+
120
+ get config(): AppConfig<TEnv> | undefined {
121
+ return this.state.config;
122
+ }
123
+
124
+ get configAsync(): Promise<AppConfig<TEnv>> {
125
+ return firstValueFrom(this.config$);
126
+ }
127
+
128
+ get instance(): AppModulesInstance<TModules> | undefined {
129
+ return this.#state.value.instance as AppModulesInstance<TModules>;
130
+ }
131
+
132
+ constructor(
133
+ value: AppBundleStateInitial,
134
+ args: {
135
+ provider: AppModuleProvider;
136
+ event?: ModuleType<EventModule>;
137
+ },
138
+ ) {
139
+ this.#state = createState(value, args.provider);
140
+
141
+ const { appKey } = value;
142
+ const { event } = args;
143
+
144
+ event && this.#registerEvents(event);
145
+
146
+ const subscriptions = new Subscription();
147
+ if (event) {
148
+ subscriptions.add(() => {
149
+ event.dispatchEvent('onAppDispose', { detail: { appKey } });
150
+ });
151
+ subscriptions.add(
152
+ event.addEventListener('onAppModulesLoaded', (e) => {
153
+ if (e.detail.appKey === appKey) {
154
+ this.#state.next(actions.setInstance(e.detail.modules));
155
+ }
156
+ }),
157
+ );
158
+ }
159
+
160
+ this.dispose = () => {
161
+ subscriptions?.unsubscribe();
162
+ if (this.#state.value.instance) {
163
+ /** tear down modules of application */
164
+ this.#state.value.instance.dispose();
165
+ }
166
+ this.#state.complete();
167
+ };
168
+ }
169
+
170
+ #registerEvents(event: ModuleType<EventModule>): void {
171
+ const { appKey } = this;
172
+
173
+ this.#state.addEffect(actions.fetchManifest.success.type, (action) => {
174
+ event.dispatchEvent('onAppManifestLoaded', {
175
+ detail: { appKey, manifest: action.payload },
176
+ source: this,
177
+ });
178
+ });
179
+
180
+ this.#state.addEffect(actions.fetchConfig.success.type, (action) => {
181
+ event.dispatchEvent('onAppConfigLoaded', {
182
+ detail: { appKey, manifest: action.payload },
183
+ source: this,
184
+ });
185
+ });
186
+
187
+ this.#state.addEffect(actions.importApp.success.type, (action) => {
188
+ event.dispatchEvent('onAppScriptLoaded', {
189
+ detail: { appKey, manifest: action.payload },
190
+ source: this,
191
+ });
192
+ });
193
+
194
+ this.#state.addEffect(actions.initialize.type, () => {
195
+ event.dispatchEvent('onAppInitialize', {
196
+ detail: { appKey },
197
+ source: this,
198
+ });
199
+ });
200
+
201
+ this.#state.addEffect(actions.initialize.success.type, () => {
202
+ event.dispatchEvent('onAppInitialized', {
203
+ detail: { appKey },
204
+ source: this,
205
+ });
206
+ });
207
+
208
+ this.#state.addEffect(actions.initialize.failure.type, ({ payload }) => {
209
+ event.dispatchEvent('onAppInitializeFailed', {
210
+ detail: { appKey, error: payload },
211
+ source: this,
212
+ });
213
+ });
214
+ }
215
+
216
+ /**
217
+ * The initializing request won`t trigger until subscribing to the returned observable
218
+ *
219
+ * @example
220
+ * ```ts
221
+ * app.initialize().subscribe({
222
+ * next: (value) => {
223
+ * value.script.render(el, ...);
224
+ * },
225
+ * error: (err) => console.error('failed to load application', err),
226
+ * complete: () => setInitializingApp(false)
227
+ * })
228
+ * ```
229
+ */
230
+ public initialize(): Observable<{
231
+ manifest: AppManifest;
232
+ script: AppScriptModule;
233
+ config: AppConfig;
234
+ }> {
235
+ return new Observable((observer) => {
236
+ this.#state.next(actions.initialize());
237
+ observer.add(
238
+ combineLatest([
239
+ this.getManifest(),
240
+ this.getAppModule(),
241
+ this.getConfig(),
242
+ ]).subscribe({
243
+ next: ([manifest, script, config]) =>
244
+ observer.next({
245
+ manifest,
246
+ script,
247
+ config,
248
+ }),
249
+ error: (err) => {
250
+ observer.error(err), this.#state.next(actions.initialize.failure(err));
251
+ },
252
+ complete: () => {
253
+ this.#state.next(actions.initialize.success());
254
+ observer.complete();
255
+ },
256
+ }),
257
+ );
258
+ });
259
+ }
260
+
261
+ public loadConfig() {
262
+ this.#state.next(actions.fetchConfig(this.appKey));
263
+ }
264
+
265
+ public loadManifest(update?: boolean) {
266
+ this.#state.next(actions.fetchManifest(this.appKey, update));
267
+ }
268
+
269
+ public updateManifest(manifest: AppManifest, replace?: false) {
270
+ this.#state.next(actions.setManifest(manifest, !replace));
271
+ }
272
+
273
+ public async loadAppModule(allow_cache = true) {
274
+ const manifest = await this.getManifestAsync(allow_cache);
275
+ this.#state.next(actions.importApp(manifest.entry));
276
+ }
277
+
278
+ public getConfig(force_refresh = false): Observable<AppConfig> {
279
+ return new Observable((subscriber) => {
280
+ if (this.#state.value.config) {
281
+ subscriber.next(this.#state.value.config);
282
+ if (!force_refresh) {
283
+ return subscriber.complete();
284
+ }
285
+ }
286
+ subscriber.add(
287
+ this.#state.addEffect('set_config', ({ payload }) => {
288
+ subscriber.next(payload);
289
+ }),
290
+ );
291
+ subscriber.add(
292
+ this.#state.addEffect('fetch_config::success', ({ payload }) => {
293
+ subscriber.next(payload);
294
+ subscriber.complete();
295
+ }),
296
+ );
297
+ subscriber.add(
298
+ this.#state.addEffect('fetch_config::failure', ({ payload }) => {
299
+ subscriber.error(
300
+ Error('failed to load application config', {
301
+ cause: payload,
302
+ }),
303
+ );
304
+ }),
305
+ );
306
+
307
+ this.loadConfig();
308
+ });
309
+ }
310
+
311
+ public getConfigAsync(allow_cache = true): Promise<AppConfig> {
312
+ const operator = allow_cache ? firstValueFrom : lastValueFrom;
313
+ return operator(this.getConfig(!allow_cache));
314
+ }
315
+
316
+ public getManifest(force_refresh = false): Observable<AppManifest> {
317
+ return new Observable((subscriber) => {
318
+ if (this.#state.value.manifest) {
319
+ subscriber.next(this.#state.value.manifest);
320
+ if (!force_refresh) {
321
+ return subscriber.complete();
322
+ }
323
+ }
324
+ subscriber.add(
325
+ this.#state.addEffect('set_manifest', ({ payload }) => {
326
+ subscriber.next(payload);
327
+ }),
328
+ );
329
+ subscriber.add(
330
+ this.#state.addEffect('fetch_manifest::success', ({ payload }) => {
331
+ subscriber.next(payload);
332
+ subscriber.complete();
333
+ }),
334
+ );
335
+ subscriber.add(
336
+ this.#state.addEffect('fetch_manifest::failure', ({ payload }) => {
337
+ subscriber.error(
338
+ Error('failed to load application manifest', {
339
+ cause: payload,
340
+ }),
341
+ );
342
+ }),
343
+ );
344
+
345
+ this.loadManifest();
346
+ });
347
+ }
348
+
349
+ public getManifestAsync(allow_cache = true): Promise<AppManifest> {
350
+ const operator = allow_cache ? firstValueFrom : lastValueFrom;
351
+ return operator(this.getManifest(!allow_cache));
352
+ }
353
+
354
+ public getAppModule(force_refresh = false): Observable<AppScriptModule> {
355
+ return new Observable((subscriber) => {
356
+ if (this.#state.value.modules) {
357
+ subscriber.next(this.#state.value.modules);
358
+ if (!force_refresh) {
359
+ return subscriber.complete();
360
+ }
361
+ }
362
+ subscriber.add(
363
+ this.#state.addEffect('set_module', ({ payload }) => {
364
+ subscriber.next(payload);
365
+ }),
366
+ );
367
+ subscriber.add(
368
+ this.#state.addEffect('import_app::success', ({ payload }) => {
369
+ subscriber.next(payload);
370
+ subscriber.complete();
371
+ }),
372
+ );
373
+ subscriber.add(
374
+ this.#state.addEffect('import_app::failure', ({ payload }) => {
375
+ subscriber.error(
376
+ Error('failed to load application modules from script', {
377
+ cause: payload,
378
+ }),
379
+ );
380
+ }),
381
+ );
382
+
383
+ subscriber.add(
384
+ this.getManifest().subscribe((manifest) =>
385
+ of(this.#state.next(actions.importApp(manifest.entry))),
386
+ ),
387
+ );
388
+ });
389
+ }
390
+
391
+ public getAppModuleAsync(allow_cache = true): Promise<AppScriptModule> {
392
+ const operator = allow_cache ? firstValueFrom : lastValueFrom;
393
+ return operator(this.getAppModule(!allow_cache));
394
+ }
395
+
396
+ public dispose: VoidFunction;
397
+ }
398
+
399
+ export default App;
@@ -0,0 +1,63 @@
1
+ import {
2
+ ActionInstanceMap,
3
+ ActionTypes,
4
+ createAction,
5
+ createAsyncAction,
6
+ } from '@equinor/fusion-observable';
7
+ import { AppConfig, AppManifest, AppModulesInstance, AppScriptModule } from '../types';
8
+
9
+ const createActions = () => ({
10
+ /** Manifest loading */
11
+ setManifest: createAction('set_manifest', (manifest: AppManifest, update?: boolean) => ({
12
+ payload: manifest,
13
+ meta: {
14
+ // TODO when updating
15
+ created: Date.now(),
16
+ update,
17
+ },
18
+ })),
19
+
20
+ fetchManifest: createAsyncAction(
21
+ 'fetch_manifest',
22
+ (key: string, update?: boolean) => ({ payload: key, meta: { update } }),
23
+ (manifest: AppManifest) => ({ payload: manifest }),
24
+ (error: unknown) => ({ payload: error }),
25
+ ),
26
+ /** Config loading */
27
+ setConfig: createAction('set_config', (config: AppConfig) => ({ payload: config })),
28
+ fetchConfig: createAsyncAction(
29
+ 'fetch_config',
30
+ (key: string) => ({ payload: key }),
31
+ (config: AppConfig) => ({ payload: config }),
32
+ (error: unknown) => ({ payload: error }),
33
+ ),
34
+ /** App loading */
35
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
+ setModule: createAction('set_module', (module: any) => ({ payload: module })),
37
+ importApp: createAsyncAction(
38
+ 'import_app',
39
+ (entrypoint: string) => ({ payload: entrypoint }),
40
+ (module: AppScriptModule) => ({ payload: module }),
41
+ (error: unknown) => ({ payload: error }),
42
+ ),
43
+
44
+ // App Instance
45
+ setInstance: createAction('set_instance', (instance: AppModulesInstance) => ({
46
+ payload: instance,
47
+ })),
48
+
49
+ initialize: createAsyncAction(
50
+ 'initialize_app',
51
+ () => ({ payload: null }),
52
+ () => ({ payload: null }),
53
+ (error: unknown) => ({ payload: error }),
54
+ ),
55
+ });
56
+
57
+ export const actions = createActions();
58
+
59
+ export type ActionBuilder = ReturnType<typeof createActions>;
60
+
61
+ export type ActionMap = ActionInstanceMap<ActionBuilder>;
62
+
63
+ export type Actions = ActionTypes<typeof actions>;
@@ -0,0 +1,45 @@
1
+ import {
2
+ actionBaseType,
3
+ createReducer as makeReducer,
4
+ isCompleteAction,
5
+ isRequestAction,
6
+ } from '@equinor/fusion-observable';
7
+
8
+ import { enableMapSet } from 'immer';
9
+
10
+ enableMapSet();
11
+
12
+ import { actions } from './actions';
13
+
14
+ import { AppBundleState, AppBundleStateInitial } from './types';
15
+
16
+ export const createReducer = (value: AppBundleStateInitial) =>
17
+ makeReducer({ ...value, status: new Set() } as AppBundleState, (builder) =>
18
+ builder
19
+ .addCase(actions.setManifest, (state, action) => {
20
+ if (action.meta.update) {
21
+ state.manifest = { ...state.manifest, ...action.payload };
22
+ } else {
23
+ state.manifest = action.payload;
24
+ }
25
+ })
26
+ .addCase(actions.setConfig, (state, action) => {
27
+ state.config = action.payload;
28
+ })
29
+ .addCase(actions.setModule, (state, action) => {
30
+ state.modules = action.payload;
31
+ })
32
+ .addCase(actions.setInstance, (state, action) => {
33
+ state.instance = action.payload;
34
+ })
35
+ /** mark status as loading {{type}} */
36
+ .addMatcher(isRequestAction, (state, action) => {
37
+ state.status.add(actionBaseType(action));
38
+ })
39
+ /** clear status {{type}} */
40
+ .addMatcher(isCompleteAction, (state, action) => {
41
+ state.status.delete(actionBaseType(action));
42
+ }),
43
+ );
44
+
45
+ export default createReducer;