@equinor/fusion-framework-module-app 2.1.5 → 2.2.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 +11 -0
- package/dist/esm/AppModuleProvider.js +91 -0
- package/dist/esm/AppModuleProvider.js.map +1 -0
- package/dist/esm/app/App.js +172 -0
- package/dist/esm/app/App.js.map +1 -0
- package/dist/esm/app/actions.js +17 -0
- package/dist/esm/app/actions.js.map +1 -0
- package/dist/esm/app/create-reducer.js +25 -0
- package/dist/esm/app/create-reducer.js.map +1 -0
- package/dist/esm/app/create-state.js +12 -0
- package/dist/esm/app/create-state.js.map +1 -0
- package/dist/esm/app/flows.js +17 -0
- package/dist/esm/app/flows.js.map +1 -0
- package/dist/esm/app/types.js +2 -0
- package/dist/esm/app/types.js.map +1 -0
- package/dist/esm/configurator.js +3 -3
- package/dist/esm/configurator.js.map +1 -1
- package/dist/esm/errors.js +37 -0
- package/dist/esm/errors.js.map +1 -0
- package/dist/esm/events.js +7 -0
- package/dist/esm/events.js.map +1 -0
- package/dist/esm/index.js +4 -2
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/module.js +5 -2
- package/dist/esm/module.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/AppModuleProvider.d.ts +26 -0
- package/dist/types/app/App.d.ts +34 -0
- package/dist/types/app/actions.d.ts +46 -0
- package/dist/types/app/create-reducer.d.ts +3 -0
- package/dist/types/app/create-state.d.ts +5 -0
- package/dist/types/app/flows.d.ts +7 -0
- package/dist/types/app/types.d.ts +9 -0
- package/dist/types/configurator.d.ts +8 -8
- package/dist/types/errors.d.ts +16 -0
- package/dist/types/events.d.ts +22 -0
- package/dist/types/index.d.ts +4 -2
- package/dist/types/module.d.ts +4 -3
- package/dist/types/types.d.ts +29 -1
- package/package.json +14 -6
- package/src/AppModuleProvider.ts +159 -0
- package/src/app/App.ts +243 -0
- package/src/app/actions.ts +51 -0
- package/src/app/create-reducer.ts +41 -0
- package/src/app/create-state.ts +21 -0
- package/src/app/flows.ts +61 -0
- package/src/app/types.ts +11 -0
- package/src/configurator.ts +13 -13
- package/src/errors.ts +53 -0
- package/src/events.ts +34 -0
- package/src/index.ts +9 -2
- package/src/module.ts +9 -5
- package/src/types.ts +42 -1
- package/dist/esm/provider.js +0 -83
- package/dist/esm/provider.js.map +0 -1
- package/dist/types/provider.d.ts +0 -43
- package/src/provider.ts +0 -181
package/src/app/App.ts
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
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 } 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
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
|
+
export class App<TEnv = any, TModules extends Array<AnyModule> | unknown = unknown> {
|
|
30
|
+
#state: FlowSubject<AppBundleState, Actions>;
|
|
31
|
+
|
|
32
|
+
//#region === streams ===
|
|
33
|
+
|
|
34
|
+
get manifest$(): Observable<AppManifest> {
|
|
35
|
+
return this.#state.pipe(
|
|
36
|
+
map(({ manifest }) => manifest),
|
|
37
|
+
filterEmpty()
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get config$(): Observable<AppConfig<TEnv>> {
|
|
42
|
+
return this.#state.pipe(
|
|
43
|
+
map(({ config }) => config),
|
|
44
|
+
filterEmpty()
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
get modules$(): Observable<AppScriptModule> {
|
|
49
|
+
return this.#state.pipe(
|
|
50
|
+
map(({ modules }) => modules),
|
|
51
|
+
filterEmpty()
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
get instance$(): Observable<AppModulesInstance<TModules>> {
|
|
56
|
+
return this.#state.pipe(
|
|
57
|
+
map(({ instance }) => instance as AppModulesInstance<TModules>),
|
|
58
|
+
filterEmpty()
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
//#endregion
|
|
63
|
+
|
|
64
|
+
get appKey(): string {
|
|
65
|
+
return this.#state.value.appKey;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get manifest(): Promise<AppManifest> {
|
|
69
|
+
return firstValueFrom(this.manifest$);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
get config(): Promise<AppConfig<TEnv>> {
|
|
73
|
+
return firstValueFrom(this.config$);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
get instance(): AppModulesInstance<TModules> | undefined {
|
|
77
|
+
return this.#state.value.instance as AppModulesInstance<TModules>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
constructor(
|
|
81
|
+
appKey: string,
|
|
82
|
+
args: { provider: AppModuleProvider; event?: ModuleType<EventModule> }
|
|
83
|
+
) {
|
|
84
|
+
this.#state = createState(appKey, args.provider);
|
|
85
|
+
|
|
86
|
+
const subscriptions = new Subscription();
|
|
87
|
+
|
|
88
|
+
if (args.event) {
|
|
89
|
+
subscriptions.add(
|
|
90
|
+
args.event.addEventListener('onAppModulesLoaded', (e) => {
|
|
91
|
+
if (e.detail.appKey === appKey) {
|
|
92
|
+
this.#state.next(actions.setInstance(e.detail.modules));
|
|
93
|
+
}
|
|
94
|
+
})
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
this.dispose = () => {
|
|
99
|
+
if (this.#state.value.instance) {
|
|
100
|
+
this.#state.value.instance.dispose();
|
|
101
|
+
}
|
|
102
|
+
subscriptions.unsubscribe();
|
|
103
|
+
this.#state.complete();
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
public initialize(): Observable<[AppManifest, AppScriptModule, AppConfig]> {
|
|
108
|
+
return combineLatest([this.getManifest(), this.getAppModule(), this.getConfig()]);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public loadConfig() {
|
|
112
|
+
this.#state.next(actions.fetchConfig(this.appKey));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
public loadManifest() {
|
|
116
|
+
this.#state.next(actions.fetchManifest(this.appKey));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public async loadAppModule(allow_cache = true) {
|
|
120
|
+
const manifest = await this.getManifestAsync(allow_cache);
|
|
121
|
+
this.#state.next(actions.importApp(manifest.entry));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
public getConfig(force_refresh = false): Observable<AppConfig> {
|
|
125
|
+
return new Observable((subscriber) => {
|
|
126
|
+
if (this.#state.value.config) {
|
|
127
|
+
subscriber.next(this.#state.value.config);
|
|
128
|
+
if (!force_refresh) {
|
|
129
|
+
return subscriber.complete();
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
subscriber.add(
|
|
133
|
+
this.#state.addEffect('set_config', ({ payload }) => {
|
|
134
|
+
subscriber.next(payload);
|
|
135
|
+
})
|
|
136
|
+
);
|
|
137
|
+
subscriber.add(
|
|
138
|
+
this.#state.addEffect('fetch_config::success', ({ payload }) => {
|
|
139
|
+
subscriber.next(payload);
|
|
140
|
+
subscriber.complete();
|
|
141
|
+
})
|
|
142
|
+
);
|
|
143
|
+
subscriber.add(
|
|
144
|
+
this.#state.addEffect('fetch_config::failure', ({ payload }) => {
|
|
145
|
+
subscriber.error(
|
|
146
|
+
Error('failed to load application config', {
|
|
147
|
+
cause: payload,
|
|
148
|
+
})
|
|
149
|
+
);
|
|
150
|
+
})
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
this.loadConfig();
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
public getConfigAsync(allow_cache = true): Promise<AppConfig> {
|
|
158
|
+
const operator = allow_cache ? firstValueFrom : lastValueFrom;
|
|
159
|
+
return operator(this.getConfig(!allow_cache));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
public getManifest(force_refresh = false): Observable<AppManifest> {
|
|
163
|
+
return new Observable((subscriber) => {
|
|
164
|
+
if (this.#state.value.manifest) {
|
|
165
|
+
subscriber.next(this.#state.value.manifest);
|
|
166
|
+
if (!force_refresh) {
|
|
167
|
+
return subscriber.complete();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
subscriber.add(
|
|
171
|
+
this.#state.addEffect('set_manifest', ({ payload }) => {
|
|
172
|
+
subscriber.next(payload);
|
|
173
|
+
})
|
|
174
|
+
);
|
|
175
|
+
subscriber.add(
|
|
176
|
+
this.#state.addEffect('fetch_manifest::success', ({ payload }) => {
|
|
177
|
+
subscriber.next(payload);
|
|
178
|
+
subscriber.complete();
|
|
179
|
+
})
|
|
180
|
+
);
|
|
181
|
+
subscriber.add(
|
|
182
|
+
this.#state.addEffect('fetch_manifest::failure', ({ payload }) => {
|
|
183
|
+
subscriber.error(
|
|
184
|
+
Error('failed to load application manifest', {
|
|
185
|
+
cause: payload,
|
|
186
|
+
})
|
|
187
|
+
);
|
|
188
|
+
})
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
this.loadManifest();
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
public getManifestAsync(allow_cache = true): Promise<AppManifest> {
|
|
196
|
+
const operator = allow_cache ? firstValueFrom : lastValueFrom;
|
|
197
|
+
return operator(this.getManifest(!allow_cache));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
public getAppModule(force_refresh = false): Observable<AppScriptModule> {
|
|
201
|
+
return new Observable((subscriber) => {
|
|
202
|
+
if (this.#state.value.modules) {
|
|
203
|
+
subscriber.next(this.#state.value.modules);
|
|
204
|
+
if (!force_refresh) {
|
|
205
|
+
return subscriber.complete();
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
subscriber.add(
|
|
209
|
+
this.#state.addEffect('set_module', ({ payload }) => {
|
|
210
|
+
subscriber.next(payload);
|
|
211
|
+
})
|
|
212
|
+
);
|
|
213
|
+
subscriber.add(
|
|
214
|
+
this.#state.addEffect('import_app::success', ({ payload }) => {
|
|
215
|
+
subscriber.next(payload);
|
|
216
|
+
subscriber.complete();
|
|
217
|
+
})
|
|
218
|
+
);
|
|
219
|
+
subscriber.add(
|
|
220
|
+
this.#state.addEffect('import_app::failure', ({ payload }) => {
|
|
221
|
+
subscriber.error(
|
|
222
|
+
Error('failed to load application modules from script', {
|
|
223
|
+
cause: payload,
|
|
224
|
+
})
|
|
225
|
+
);
|
|
226
|
+
})
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
subscriber.add(
|
|
230
|
+
this.getManifest().subscribe((manifest) =>
|
|
231
|
+
of(this.#state.next(actions.importApp(manifest.entry)))
|
|
232
|
+
)
|
|
233
|
+
);
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
public getAppModuleAsync(allow_cache = true): Promise<AppScriptModule> {
|
|
238
|
+
const operator = allow_cache ? firstValueFrom : lastValueFrom;
|
|
239
|
+
return operator(this.getAppModule(!allow_cache));
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
public dispose: VoidFunction;
|
|
243
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
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) => ({
|
|
12
|
+
payload: manifest,
|
|
13
|
+
meta: { created: Date.now() },
|
|
14
|
+
})),
|
|
15
|
+
fetchManifest: createAsyncAction(
|
|
16
|
+
'fetch_manifest',
|
|
17
|
+
(key: string) => ({ payload: key }),
|
|
18
|
+
(manifest: AppManifest) => ({ payload: manifest }),
|
|
19
|
+
(error: unknown) => ({ payload: error })
|
|
20
|
+
),
|
|
21
|
+
/** Config loading */
|
|
22
|
+
setConfig: createAction('set_config', (config: AppConfig) => ({ payload: config })),
|
|
23
|
+
fetchConfig: createAsyncAction(
|
|
24
|
+
'fetch_config',
|
|
25
|
+
(key: string) => ({ payload: key }),
|
|
26
|
+
(config: AppConfig) => ({ payload: config }),
|
|
27
|
+
(error: unknown) => ({ payload: error })
|
|
28
|
+
),
|
|
29
|
+
/** App loading */
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
31
|
+
setModule: createAction('set_module', (module: any) => ({ payload: module })),
|
|
32
|
+
importApp: createAsyncAction(
|
|
33
|
+
'import_app',
|
|
34
|
+
(entrypoint: string) => ({ payload: entrypoint }),
|
|
35
|
+
(module: AppScriptModule) => ({ payload: module }),
|
|
36
|
+
(error: unknown) => ({ payload: error })
|
|
37
|
+
),
|
|
38
|
+
|
|
39
|
+
// App Instance
|
|
40
|
+
setInstance: createAction('set_instance', (instance: AppModulesInstance) => ({
|
|
41
|
+
payload: instance,
|
|
42
|
+
})),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
export const actions = createActions();
|
|
46
|
+
|
|
47
|
+
export type ActionBuilder = ReturnType<typeof createActions>;
|
|
48
|
+
|
|
49
|
+
export type ActionMap = ActionInstanceMap<ActionBuilder>;
|
|
50
|
+
|
|
51
|
+
export type Actions = ActionTypes<typeof actions>;
|
|
@@ -0,0 +1,41 @@
|
|
|
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 } from './types';
|
|
15
|
+
|
|
16
|
+
export const createReducer = (appKey: string) =>
|
|
17
|
+
makeReducer({ appKey, status: new Set() } as AppBundleState, (builder) =>
|
|
18
|
+
builder
|
|
19
|
+
.addCase(actions.setManifest, (state, action) => {
|
|
20
|
+
state.manifest = action.payload;
|
|
21
|
+
})
|
|
22
|
+
.addCase(actions.setConfig, (state, action) => {
|
|
23
|
+
state.config = action.payload;
|
|
24
|
+
})
|
|
25
|
+
.addCase(actions.setModule, (state, action) => {
|
|
26
|
+
state.modules = action.payload;
|
|
27
|
+
})
|
|
28
|
+
.addCase(actions.setInstance, (state, action) => {
|
|
29
|
+
state.instance = action.payload;
|
|
30
|
+
})
|
|
31
|
+
/** mark status as loading {{type}} */
|
|
32
|
+
.addMatcher(isRequestAction, (state, action) => {
|
|
33
|
+
state.status.add(actionBaseType(action));
|
|
34
|
+
})
|
|
35
|
+
/** clear status {{type}} */
|
|
36
|
+
.addMatcher(isCompleteAction, (state, action) => {
|
|
37
|
+
state.status.delete(actionBaseType(action));
|
|
38
|
+
})
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
export default createReducer;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { FlowSubject } from '@equinor/fusion-observable';
|
|
2
|
+
|
|
3
|
+
import { createReducer } from './create-reducer';
|
|
4
|
+
|
|
5
|
+
import { handleFetchManifest, handleFetchConfig, handleImportApplication } from './flows';
|
|
6
|
+
|
|
7
|
+
import type { Actions } from './actions';
|
|
8
|
+
import type { AppBundleState } from './types';
|
|
9
|
+
import type { AppModuleProvider } from '../AppModuleProvider';
|
|
10
|
+
|
|
11
|
+
export const createState = (
|
|
12
|
+
appKey: string,
|
|
13
|
+
provider: AppModuleProvider
|
|
14
|
+
): FlowSubject<AppBundleState, Actions> => {
|
|
15
|
+
const reducer = createReducer(appKey);
|
|
16
|
+
const state = new FlowSubject<AppBundleState, Actions>(reducer);
|
|
17
|
+
state.addFlow(handleFetchManifest(provider));
|
|
18
|
+
state.addFlow(handleFetchConfig(provider));
|
|
19
|
+
state.addFlow(handleImportApplication());
|
|
20
|
+
return state;
|
|
21
|
+
};
|
package/src/app/flows.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { from, of, merge } from 'rxjs';
|
|
2
|
+
import { catchError, filter, last, map, switchMap } from 'rxjs/operators';
|
|
3
|
+
|
|
4
|
+
import { actions } from './actions';
|
|
5
|
+
|
|
6
|
+
import type { Flow } from '@equinor/fusion-observable';
|
|
7
|
+
import type { AppModuleProvider } from '../AppModuleProvider';
|
|
8
|
+
import type { Actions } from './actions';
|
|
9
|
+
import { AppBundleState } from './types';
|
|
10
|
+
|
|
11
|
+
export const handleFetchManifest =
|
|
12
|
+
(provider: AppModuleProvider): Flow<Actions, AppBundleState> =>
|
|
13
|
+
(action$) =>
|
|
14
|
+
action$.pipe(
|
|
15
|
+
filter(actions.fetchManifest.match),
|
|
16
|
+
switchMap(({ payload: appKey }) => {
|
|
17
|
+
const fetch$ = from(provider.getAppManifest(appKey)).pipe(
|
|
18
|
+
filter((x) => !!x),
|
|
19
|
+
map(actions.setManifest)
|
|
20
|
+
);
|
|
21
|
+
return merge(
|
|
22
|
+
fetch$,
|
|
23
|
+
fetch$.pipe(
|
|
24
|
+
last(),
|
|
25
|
+
map(({ payload }) => actions.fetchManifest.success(payload))
|
|
26
|
+
)
|
|
27
|
+
).pipe(
|
|
28
|
+
catchError((err) => {
|
|
29
|
+
return of(actions.fetchManifest.failure(err));
|
|
30
|
+
})
|
|
31
|
+
);
|
|
32
|
+
})
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
export const handleFetchConfig =
|
|
36
|
+
(provider: AppModuleProvider): Flow<Actions, AppBundleState> =>
|
|
37
|
+
(action$) =>
|
|
38
|
+
action$.pipe(
|
|
39
|
+
filter(actions.fetchConfig.match),
|
|
40
|
+
switchMap(({ payload: appKey }) => {
|
|
41
|
+
const fetch$ = from(provider.getAppConfig(appKey)).pipe(map(actions.setConfig));
|
|
42
|
+
return merge(
|
|
43
|
+
fetch$,
|
|
44
|
+
fetch$.pipe(
|
|
45
|
+
last(),
|
|
46
|
+
map(({ payload }) => actions.fetchConfig.success(payload))
|
|
47
|
+
)
|
|
48
|
+
).pipe(catchError((err) => of(actions.fetchConfig.failure(err))));
|
|
49
|
+
})
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
export const handleImportApplication = (): Flow<Actions, AppBundleState> => (action$) =>
|
|
53
|
+
action$.pipe(
|
|
54
|
+
filter(actions.importApp.match),
|
|
55
|
+
switchMap(({ payload }) => {
|
|
56
|
+
return from(import(payload)).pipe(
|
|
57
|
+
map(actions.importApp.success),
|
|
58
|
+
catchError((err) => of(actions.importApp.failure(err)))
|
|
59
|
+
);
|
|
60
|
+
})
|
|
61
|
+
);
|
package/src/app/types.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AppConfig, AppManifest, AppModulesInstance, AppScriptModule } from '../types';
|
|
2
|
+
|
|
3
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
4
|
+
export type AppBundleState<TConfig = any, TModules = any> = {
|
|
5
|
+
appKey: string;
|
|
6
|
+
status: Set<string>;
|
|
7
|
+
manifest?: AppManifest;
|
|
8
|
+
config?: AppConfig<TConfig>;
|
|
9
|
+
modules?: AppScriptModule;
|
|
10
|
+
instance?: AppModulesInstance<TModules>;
|
|
11
|
+
};
|
package/src/configurator.ts
CHANGED
|
@@ -4,18 +4,18 @@ import { QueryCtorOptions } from '@equinor/fusion-query';
|
|
|
4
4
|
import { moduleKey } from './module';
|
|
5
5
|
import type { AppConfig, AppManifest, ModuleDeps } from './types';
|
|
6
6
|
|
|
7
|
-
export interface
|
|
8
|
-
|
|
7
|
+
export interface AppModuleConfig {
|
|
8
|
+
getAppManifest: QueryCtorOptions<AppManifest, { appKey: string }>;
|
|
9
9
|
// TODO: add filter
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
getAppManifests: QueryCtorOptions<AppManifest[], void>;
|
|
11
|
+
getAppConfig: QueryCtorOptions<AppConfig, { appKey: string; tag?: string }>;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
export interface IAppConfigurator<TDeps extends Array<AnyModule>> {
|
|
15
15
|
createConfig: (
|
|
16
16
|
args: ModuleInitializerArgs<IAppConfigurator<TDeps>, TDeps>
|
|
17
|
-
) => Promise<
|
|
18
|
-
processConfig: (config:
|
|
17
|
+
) => Promise<AppModuleConfig>;
|
|
18
|
+
processConfig: (config: AppModuleConfig) => AppModuleConfig;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export class AppConfigurator<TDeps extends ModuleDeps = ModuleDeps>
|
|
@@ -49,22 +49,22 @@ export class AppConfigurator<TDeps extends ModuleDeps = ModuleDeps>
|
|
|
49
49
|
|
|
50
50
|
public async createConfig(
|
|
51
51
|
args: ModuleInitializerArgs<IAppConfigurator<TDeps>, TDeps>
|
|
52
|
-
): Promise<
|
|
52
|
+
): Promise<AppModuleConfig> {
|
|
53
53
|
const httpClient = await this._createHttpClient(args);
|
|
54
|
-
const config:
|
|
55
|
-
|
|
54
|
+
const config: AppModuleConfig = {
|
|
55
|
+
getAppManifest: {
|
|
56
56
|
client: {
|
|
57
|
-
fn: ({ appKey }) => httpClient.json
|
|
57
|
+
fn: ({ appKey }) => httpClient.json$<AppManifest>(`/api/apps/${appKey}`),
|
|
58
58
|
},
|
|
59
59
|
key: ({ appKey }) => appKey,
|
|
60
60
|
},
|
|
61
|
-
|
|
61
|
+
getAppManifests: {
|
|
62
62
|
client: {
|
|
63
63
|
fn: () => httpClient.json$(`/api/apps`),
|
|
64
64
|
},
|
|
65
65
|
key: () => 'apps',
|
|
66
66
|
},
|
|
67
|
-
|
|
67
|
+
getAppConfig: {
|
|
68
68
|
client: {
|
|
69
69
|
fn: ({ appKey, tag }) =>
|
|
70
70
|
httpClient.json$(`/api/apps/${appKey}/config${tag ? `?tag=${tag}` : ''}`),
|
|
@@ -75,7 +75,7 @@ export class AppConfigurator<TDeps extends ModuleDeps = ModuleDeps>
|
|
|
75
75
|
return this.processConfig(config);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
public processConfig(config:
|
|
78
|
+
public processConfig(config: AppModuleConfig): AppModuleConfig {
|
|
79
79
|
return config;
|
|
80
80
|
}
|
|
81
81
|
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
type AppErrorType = 'not_found' | 'unauthorized' | 'unknown';
|
|
2
|
+
|
|
3
|
+
export class AppManifestError extends Error {
|
|
4
|
+
static fromHttpResponse(response: Response, options?: ErrorOptions) {
|
|
5
|
+
switch (response.status) {
|
|
6
|
+
case 401:
|
|
7
|
+
return new AppManifestError(
|
|
8
|
+
'unauthorized',
|
|
9
|
+
'failed to load application manifest, request not authorized',
|
|
10
|
+
options
|
|
11
|
+
);
|
|
12
|
+
case 404:
|
|
13
|
+
return new AppManifestError('not_found', 'application manifest not found', options);
|
|
14
|
+
}
|
|
15
|
+
return new AppManifestError(
|
|
16
|
+
'unknown',
|
|
17
|
+
`failed to load application manifest, status code ${response.status}`,
|
|
18
|
+
options
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
constructor(public readonly type: AppErrorType, message?: string, options?: ErrorOptions) {
|
|
22
|
+
super(message, options);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class AppConfigError extends Error {
|
|
27
|
+
static fromHttpResponse(response: Response, options?: ErrorOptions) {
|
|
28
|
+
switch (response.status) {
|
|
29
|
+
case 401:
|
|
30
|
+
return new AppConfigError(
|
|
31
|
+
'unauthorized',
|
|
32
|
+
'failed to load application config, request not authorized',
|
|
33
|
+
options
|
|
34
|
+
);
|
|
35
|
+
case 404:
|
|
36
|
+
return new AppConfigError('not_found', 'application config not found', options);
|
|
37
|
+
}
|
|
38
|
+
return new AppConfigError(
|
|
39
|
+
'unknown',
|
|
40
|
+
`failed to load application config, status code ${response.status}`,
|
|
41
|
+
options
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
constructor(public readonly type: AppErrorType, message?: string, options?: ErrorOptions) {
|
|
45
|
+
super(message, options);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class AppScriptModuleError extends Error {
|
|
50
|
+
constructor(public readonly type: AppErrorType, message?: string, options?: ErrorOptions) {
|
|
51
|
+
super(message, options);
|
|
52
|
+
}
|
|
53
|
+
}
|
package/src/events.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { AnyModule } from '@equinor/fusion-framework-module';
|
|
2
|
+
|
|
3
|
+
import { FrameworkEvent, FrameworkEventInit } from '@equinor/fusion-framework-module-event';
|
|
4
|
+
import { App } from 'app/App';
|
|
5
|
+
import AppModuleProvider from 'AppModuleProvider';
|
|
6
|
+
|
|
7
|
+
import { AppModulesInstance } from './types';
|
|
8
|
+
|
|
9
|
+
type AppModulesLoadedEventInit<TModules extends Array<AnyModule> | unknown = unknown> =
|
|
10
|
+
FrameworkEventInit<{
|
|
11
|
+
appKey: string;
|
|
12
|
+
modules: AppModulesInstance<TModules>;
|
|
13
|
+
}>;
|
|
14
|
+
|
|
15
|
+
export class AppModulesLoadedEvent<
|
|
16
|
+
TModules extends Array<AnyModule> | unknown = unknown
|
|
17
|
+
> extends FrameworkEvent<AppModulesLoadedEventInit<TModules>> {
|
|
18
|
+
constructor(
|
|
19
|
+
appKey: string,
|
|
20
|
+
modules: AppModulesInstance<TModules>,
|
|
21
|
+
init?: Omit<AppModulesLoadedEventInit<TModules>, 'detail'>
|
|
22
|
+
) {
|
|
23
|
+
super('onAppModulesLoaded', { ...init, detail: { appKey, modules } });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
declare module '@equinor/fusion-framework-module-event' {
|
|
28
|
+
interface FrameworkEventMap {
|
|
29
|
+
onAppModulesLoaded: AppModulesLoadedEvent;
|
|
30
|
+
onCurrentAppChanged: FrameworkEvent<
|
|
31
|
+
FrameworkEventInit<{ next?: App; previous?: App }, AppModuleProvider>
|
|
32
|
+
>;
|
|
33
|
+
}
|
|
34
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
export {
|
|
2
|
-
|
|
1
|
+
export {
|
|
2
|
+
AppConfigurator,
|
|
3
|
+
IAppConfigurator,
|
|
4
|
+
AppModuleConfig as IAppModuleConfig,
|
|
5
|
+
} from './configurator';
|
|
6
|
+
export { AppModuleProvider } from './AppModuleProvider';
|
|
3
7
|
|
|
8
|
+
export { App } from './app/App';
|
|
9
|
+
|
|
10
|
+
export * from './events';
|
|
4
11
|
export * from './types';
|
|
5
12
|
|
|
6
13
|
export { default, AppModule, module, moduleKey } from './module';
|
package/src/module.ts
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { Module } from '@equinor/fusion-framework-module';
|
|
1
|
+
import { IModulesConfigurator, Module } from '@equinor/fusion-framework-module';
|
|
2
2
|
import { ModuleDeps } from './types';
|
|
3
3
|
|
|
4
4
|
import { IAppConfigurator, AppConfigurator } from './configurator';
|
|
5
|
-
import {
|
|
5
|
+
import { AppModuleProvider } from './AppModuleProvider';
|
|
6
6
|
|
|
7
7
|
export const moduleKey = 'app';
|
|
8
8
|
|
|
9
9
|
export type AppModule = Module<
|
|
10
10
|
typeof moduleKey,
|
|
11
|
-
|
|
11
|
+
AppModuleProvider,
|
|
12
12
|
IAppConfigurator<ModuleDeps>,
|
|
13
13
|
ModuleDeps
|
|
14
14
|
>;
|
|
@@ -19,13 +19,17 @@ export const module: AppModule = {
|
|
|
19
19
|
initialize: async (args) => {
|
|
20
20
|
const config = await args.config.createConfig(args);
|
|
21
21
|
const event = await args.requireInstance('event').catch(() => undefined);
|
|
22
|
-
return new
|
|
22
|
+
return new AppModuleProvider({ config, event });
|
|
23
23
|
},
|
|
24
24
|
dispose: (args) => {
|
|
25
|
-
(args.instance as unknown as
|
|
25
|
+
(args.instance as unknown as AppModuleProvider).dispose();
|
|
26
26
|
},
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
+
export const enableAppModule = (configurator: IModulesConfigurator) => {
|
|
30
|
+
configurator.addConfig({ module });
|
|
31
|
+
};
|
|
32
|
+
|
|
29
33
|
export default module;
|
|
30
34
|
|
|
31
35
|
declare module '@equinor/fusion-framework-module' {
|