@equinor/fusion-framework-module-app 7.0.4-next.0 → 7.1.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.
@@ -1,13 +1,21 @@
1
1
  import { type Observable, type ObservableInput } from 'rxjs';
2
2
  import { type IHttpClient } from '@equinor/fusion-framework-module-http';
3
- import type { AppConfig, AppManifest, AppSettings, ConfigEnvironment } from './types';
3
+ import type { AppBuildManifest, AppConfig, AppManifest, AppSettings, ConfigEnvironment } from './types';
4
4
  export interface IAppClient extends Disposable {
5
5
  /**
6
- * Fetch app manifest by appKey
6
+ * Fetch app manifest by appKey and tag
7
7
  */
8
8
  getAppManifest: (args: {
9
9
  appKey: string;
10
+ tag?: string;
10
11
  }) => ObservableInput<AppManifest>;
12
+ /**
13
+ * Fetch app build manifest by appKey and tag
14
+ */
15
+ getAppBuild: (args: {
16
+ appKey: string;
17
+ tag?: string;
18
+ }) => ObservableInput<AppBuildManifest>;
11
19
  /**
12
20
  * Fetch all app manifests
13
21
  */
@@ -44,8 +52,13 @@ export interface IAppClient extends Disposable {
44
52
  export declare class AppClient implements IAppClient {
45
53
  #private;
46
54
  constructor(client: IHttpClient);
55
+ getAppBuild(args: {
56
+ appKey: string;
57
+ tag?: string;
58
+ }): Observable<AppBuildManifest>;
47
59
  getAppManifest(args: {
48
60
  appKey: string;
61
+ tag?: string;
49
62
  }): Observable<AppManifest>;
50
63
  getAppManifests(args: {
51
64
  filterByCurrentUser?: boolean;
@@ -1,7 +1,7 @@
1
1
  import { type Observable } from 'rxjs';
2
2
  import type { ModuleType } from '@equinor/fusion-framework-module';
3
3
  import type { EventModule } from '@equinor/fusion-framework-module-event';
4
- import type { AppConfig, AppManifest, AppSettings, ConfigEnvironment, CurrentApp } from './types';
4
+ import type { AppConfig, AppManifest, AppReference, AppSettings, ConfigEnvironment, CurrentApp } from './types';
5
5
  import { App, type IApp } from './app/App';
6
6
  import type { AppModuleConfig } from './AppConfigurator';
7
7
  import type { AppBundleStateInitial } from './app/types';
@@ -29,8 +29,9 @@ export declare class AppModuleProvider {
29
29
  /**
30
30
  * fetch an application by key
31
31
  * @param appKey - application key
32
+ * @param tag - application tag (optional)
32
33
  */
33
- getAppManifest(appKey: string): Observable<AppManifest>;
34
+ getAppManifest(appKey: string, tag?: string): Observable<AppManifest>;
34
35
  getAppManifests(filter?: {
35
36
  filterByCurrentUser: boolean;
36
37
  }): Observable<AppManifest[]>;
@@ -59,7 +60,7 @@ export declare class AppModuleProvider {
59
60
  * set the current application, will internally resolve manifest
60
61
  * @param appKey - application key
61
62
  */
62
- setCurrentApp(appKeyOrApp: string | IApp): void;
63
+ setCurrentApp(appKeyOrApp: string | IApp | AppReference): void;
63
64
  clearCurrentApp(): void;
64
65
  get assetUri(): string;
65
66
  /**
@@ -196,6 +196,7 @@ export declare class App<TEnv extends ConfigEnvironment = ConfigEnvironment, TMo
196
196
  get status$(): Observable<AppBundleState['status']>;
197
197
  get state(): Readonly<AppBundleState>;
198
198
  get appKey(): string;
199
+ get tag(): string | undefined;
199
200
  get manifest(): Readonly<AppManifest> | undefined;
200
201
  get manifestAsync(): Promise<Readonly<AppManifest>>;
201
202
  get config(): AppConfig<TEnv> | undefined;
@@ -6,7 +6,10 @@ declare const createActions: () => {
6
6
  created: number;
7
7
  update: boolean | undefined;
8
8
  }>;
9
- fetchManifest: import("@equinor/fusion-observable").ActionCreatorWithPreparedPayload<[key: string, update?: boolean | undefined], string, "fetch_manifest::request", never, {
9
+ fetchManifest: import("@equinor/fusion-observable").ActionCreatorWithPreparedPayload<[key: string, tag?: string | undefined, update?: boolean | undefined], {
10
+ key: string;
11
+ tag: string | undefined;
12
+ }, "fetch_manifest::request", never, {
10
13
  update: boolean | undefined;
11
14
  }> & {
12
15
  success: import("@equinor/fusion-observable").ActionCreatorWithPreparedPayload<[manifest: AppManifest], AppManifest, "fetch_manifest::success", never, never>;
@@ -54,7 +57,10 @@ export declare const actions: {
54
57
  created: number;
55
58
  update: boolean | undefined;
56
59
  }>;
57
- fetchManifest: import("@equinor/fusion-observable").ActionCreatorWithPreparedPayload<[key: string, update?: boolean | undefined], string, "fetch_manifest::request", never, {
60
+ fetchManifest: import("@equinor/fusion-observable").ActionCreatorWithPreparedPayload<[key: string, tag?: string | undefined, update?: boolean | undefined], {
61
+ key: string;
62
+ tag: string | undefined;
63
+ }, "fetch_manifest::request", never, {
58
64
  update: boolean | undefined;
59
65
  }> & {
60
66
  success: import("@equinor/fusion-observable").ActionCreatorWithPreparedPayload<[manifest: AppManifest], AppManifest, "fetch_manifest::success", never, never>;
@@ -2,7 +2,10 @@ import type { AppBundleState, AppBundleStateInitial } from './types';
2
2
  export declare const createReducer: (value: AppBundleStateInitial) => import("@equinor/fusion-observable").ReducerWithInitialState<AppBundleState, import("@equinor/fusion-observable").PayloadAction<import("..").AppManifest, "set_manifest", {
3
3
  created: number;
4
4
  update: boolean | undefined;
5
- }, never> | import("@equinor/fusion-observable").PayloadAction<string, "fetch_manifest::request", {
5
+ }, never> | import("@equinor/fusion-observable").PayloadAction<{
6
+ key: string;
7
+ tag: string | undefined;
8
+ }, "fetch_manifest::request", {
6
9
  update: boolean | undefined;
7
10
  }, never> | {
8
11
  payload: import("..").AppConfig<import("../AppConfig").ConfigEnvironment>;
@@ -17,6 +17,7 @@ import type { Actions } from './actions';
17
17
  */
18
18
  export type AppBundleState<TConfig extends ConfigEnvironment = ConfigEnvironment, TModules = any> = {
19
19
  appKey: string;
20
+ tag?: string;
20
21
  status: Set<ActionBaseType<Actions>>;
21
22
  manifest?: AppManifest;
22
23
  config?: AppConfig<TConfig>;
@@ -39,6 +39,26 @@ export declare class AppConfigError extends Error {
39
39
  */
40
40
  constructor(type: AppErrorType, message?: string, options?: ErrorOptions);
41
41
  }
42
+ /**
43
+ * Represents an error that occurs in the application build.
44
+ */
45
+ export declare class AppBuildError extends Error {
46
+ readonly type: AppErrorType;
47
+ /**
48
+ * Creates an instance of `AppBuildError` based on the HTTP response status.
49
+ * @param response The HTTP response.
50
+ * @param options Additional error options.
51
+ * @returns An instance of `AppBuildError` based on the HTTP response status.
52
+ */
53
+ static fromHttpResponse(response: Response, options?: ErrorOptions): AppBuildError;
54
+ /**
55
+ * Creates an instance of `AppBuildError`.
56
+ * @param type The type of the application error.
57
+ * @param message The error message.
58
+ * @param options Additional error options.
59
+ */
60
+ constructor(type: AppErrorType, message?: string, options?: ErrorOptions);
61
+ }
42
62
  /**
43
63
  * Represents an error that occurs while fetching application settings.
44
64
  */
@@ -14,6 +14,13 @@ export type AppEnv<TEnv extends ConfigEnvironment = ConfigEnvironment, TProps =
14
14
  config?: AppConfig<TEnv>;
15
15
  props?: TProps;
16
16
  };
17
+ /**
18
+ * Reference to an app, used to fetch app manifest from app service
19
+ */
20
+ export type AppReference = {
21
+ appKey: string;
22
+ tag?: string;
23
+ };
17
24
  export type ModuleDeps = [HttpModule, ServiceDiscoveryModule, EventModule];
18
25
  export interface AppSettings {
19
26
  [key: string]: unknown;
@@ -1 +1 @@
1
- export declare const version = "7.0.4-next.0";
1
+ export declare const version = "7.1.0";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@equinor/fusion-framework-module-app",
3
- "version": "7.0.4-next.0",
3
+ "version": "7.1.0",
4
4
  "description": "",
5
5
  "main": "dist/esm/index.js",
6
6
  "exports": {
@@ -61,16 +61,16 @@
61
61
  "rxjs": "^7.8.1",
62
62
  "uuid": "^13.0.0",
63
63
  "zod": "^4.1.8",
64
- "@equinor/fusion-observable": "^8.5.7-next.0",
65
- "@equinor/fusion-query": "^6.0.2-next.0"
64
+ "@equinor/fusion-query": "^6.0.1",
65
+ "@equinor/fusion-observable": "^8.5.6"
66
66
  },
67
67
  "devDependencies": {
68
68
  "typescript": "^5.8.2",
69
- "@equinor/fusion-framework-module": "^5.0.6-next.0",
70
- "@equinor/fusion-framework-module-http": "^7.0.5-next.1",
71
- "@equinor/fusion-framework-module-msal": "^6.0.0-next.1",
72
- "@equinor/fusion-framework-module-service-discovery": "^9.0.5-next.1",
73
- "@equinor/fusion-framework-module-event": "^4.4.1-next.0"
69
+ "@equinor/fusion-framework-module": "^5.0.5",
70
+ "@equinor/fusion-framework-module-event": "^4.4.0",
71
+ "@equinor/fusion-framework-module-http": "^7.0.4",
72
+ "@equinor/fusion-framework-module-msal": "^5.1.2",
73
+ "@equinor/fusion-framework-module-service-discovery": "^9.0.4"
74
74
  },
75
75
  "scripts": {
76
76
  "build": "tsc -b"
package/src/AppClient.ts CHANGED
@@ -10,17 +10,28 @@ import {
10
10
  } from '@equinor/fusion-framework-module-http';
11
11
  import { jsonSelector } from '@equinor/fusion-framework-module-http/selectors';
12
12
 
13
- import { ApiApplicationSchema } from './schemas';
13
+ import { ApiApplicationBuildSchema, ApiApplicationSchema } from './schemas';
14
14
 
15
- import type { AppConfig, AppManifest, AppSettings, ConfigEnvironment } from './types';
16
- import { AppConfigError, AppManifestError, AppSettingsError } from './errors';
15
+ import type {
16
+ AppBuildManifest,
17
+ AppConfig,
18
+ AppManifest,
19
+ AppSettings,
20
+ ConfigEnvironment,
21
+ } from './types';
22
+ import { AppBuildError, AppConfigError, AppManifestError, AppSettingsError } from './errors';
17
23
  import { AppConfigSelector } from './AppClient.Selectors';
18
24
 
19
25
  export interface IAppClient extends Disposable {
20
26
  /**
21
- * Fetch app manifest by appKey
27
+ * Fetch app manifest by appKey and tag
22
28
  */
23
- getAppManifest: (args: { appKey: string }) => ObservableInput<AppManifest>;
29
+ getAppManifest: (args: { appKey: string; tag?: string }) => ObservableInput<AppManifest>;
30
+
31
+ /**
32
+ * Fetch app build manifest by appKey and tag
33
+ */
34
+ getAppBuild: (args: { appKey: string; tag?: string }) => ObservableInput<AppBuildManifest>;
24
35
 
25
36
  /**
26
37
  * Fetch all app manifests
@@ -81,6 +92,7 @@ export class AppClient implements IAppClient {
81
92
  #manifest: Query<AppManifest, { appKey: string }>;
82
93
  #manifests: Query<AppManifest[], { filterByCurrentUser?: boolean } | undefined>;
83
94
  #config: Query<AppConfig, { appKey: string; tag?: string }>;
95
+ #build: Query<AppBuildManifest, { appKey: string; tag?: string }>;
84
96
  #settings: Query<AppSettings, { appKey: string; settings?: AppSettings }>;
85
97
  #client: IHttpClient;
86
98
 
@@ -88,10 +100,28 @@ export class AppClient implements IAppClient {
88
100
  this.#client = client;
89
101
 
90
102
  const expire = 1 * 60 * 1000;
91
- this.#manifest = new Query<AppManifest, { appKey: string }>({
103
+
104
+ this.#build = new Query<AppBuildManifest, { appKey: string; tag?: string }>({
92
105
  client: {
93
- fn: ({ appKey }) => {
94
- return client.json(`/persons/me/apps/${appKey}`, {
106
+ fn: ({ appKey, tag }) => {
107
+ return client.json(`/apps/${appKey}/builds/${tag}`, {
108
+ headers: {
109
+ 'Api-Version': '1.0',
110
+ },
111
+ selector: async (res: Response) =>
112
+ ApiApplicationBuildSchema.parse(await jsonSelector(res)) as AppBuildManifest,
113
+ });
114
+ },
115
+ },
116
+ queueOperator: 'merge',
117
+ key: ({ appKey, tag }) => `${appKey}@${tag}`,
118
+ expire,
119
+ });
120
+
121
+ this.#manifest = new Query<AppManifest, { appKey: string; tag?: string }>({
122
+ client: {
123
+ fn: ({ appKey, tag }) => {
124
+ return client.json$(tag ? `/apps/${appKey}@${tag}` : `/persons/me/apps/${appKey}`, {
95
125
  headers: {
96
126
  'Api-Version': '1.0',
97
127
  },
@@ -156,7 +186,24 @@ export class AppClient implements IAppClient {
156
186
  });
157
187
  }
158
188
 
159
- getAppManifest(args: { appKey: string }): Observable<AppManifest> {
189
+ getAppBuild(args: { appKey: string; tag?: string }): Observable<AppBuildManifest> {
190
+ return this.#build.query(args).pipe(
191
+ map((res) => res.value as AppBuildManifest),
192
+ catchError((err) => {
193
+ const cause = err?.cause || err;
194
+
195
+ if (cause instanceof AppBuildError) {
196
+ throw cause;
197
+ }
198
+ if (cause instanceof HttpJsonResponseError || cause instanceof HttpResponseError) {
199
+ throw AppBuildError.fromHttpResponse(cause.response, { cause });
200
+ }
201
+ throw new AppBuildError('unknown', 'failed to load build', { cause });
202
+ }),
203
+ );
204
+ }
205
+
206
+ getAppManifest(args: { appKey: string; tag?: string }): Observable<AppManifest> {
160
207
  return this.#manifest.query(args).pipe(
161
208
  queryValue,
162
209
  catchError((err) => {
@@ -251,6 +298,7 @@ export class AppClient implements IAppClient {
251
298
  this.#manifests.complete();
252
299
  this.#config.complete();
253
300
  this.#settings.complete();
301
+ this.#build.complete();
254
302
  }
255
303
  }
256
304
 
@@ -12,7 +12,14 @@ import {
12
12
  import type { ModuleType } from '@equinor/fusion-framework-module';
13
13
  import type { EventModule } from '@equinor/fusion-framework-module-event';
14
14
 
15
- import type { AppConfig, AppManifest, AppSettings, ConfigEnvironment, CurrentApp } from './types';
15
+ import type {
16
+ AppConfig,
17
+ AppManifest,
18
+ AppReference,
19
+ AppSettings,
20
+ ConfigEnvironment,
21
+ CurrentApp,
22
+ } from './types';
16
23
 
17
24
  import { App, filterEmpty, type IApp } from './app/App';
18
25
  import type { AppModuleConfig } from './AppConfigurator';
@@ -103,9 +110,10 @@ export class AppModuleProvider {
103
110
  /**
104
111
  * fetch an application by key
105
112
  * @param appKey - application key
113
+ * @param tag - application tag (optional)
106
114
  */
107
- public getAppManifest(appKey: string): Observable<AppManifest> {
108
- return from(this.#appClient.getAppManifest({ appKey }));
115
+ public getAppManifest(appKey: string, tag?: string): Observable<AppManifest> {
116
+ return from(this.#appClient.getAppManifest({ appKey, tag }));
109
117
  }
110
118
 
111
119
  public getAppManifests(filter?: { filterByCurrentUser: boolean }): Observable<AppManifest[]> {
@@ -152,10 +160,23 @@ export class AppModuleProvider {
152
160
  * set the current application, will internally resolve manifest
153
161
  * @param appKey - application key
154
162
  */
155
- public setCurrentApp(appKeyOrApp: string | IApp): void {
156
- const app =
157
- typeof appKeyOrApp === 'string' ? this.createApp({ appKey: appKeyOrApp }) : appKeyOrApp;
158
- this.#current$.next(app as CurrentApp);
163
+ public setCurrentApp(appKeyOrApp: string | IApp | AppReference): void {
164
+ if (typeof appKeyOrApp === 'string') {
165
+ const newApp = new App({ appKey: appKeyOrApp }, { provider: this, event: this.#event });
166
+ this.#current$.next(newApp as CurrentApp);
167
+ return;
168
+ }
169
+
170
+ if (appKeyOrApp.appKey && 'tag' in appKeyOrApp) {
171
+ const newApp = new App(
172
+ { appKey: appKeyOrApp.appKey, tag: appKeyOrApp.tag },
173
+ { provider: this, event: this.#event },
174
+ );
175
+ this.#current$.next(newApp as CurrentApp);
176
+ return;
177
+ }
178
+
179
+ this.#current$.next(appKeyOrApp as CurrentApp);
159
180
  }
160
181
 
161
182
  public clearCurrentApp(): void {
@@ -171,7 +192,7 @@ export class AppModuleProvider {
171
192
  * @deprecated
172
193
  */
173
194
  public createApp(value: AppBundleStateInitial): App {
174
- console.warn();
195
+ console.warn('AppModuleProvider.createApp is deprecated and should not be used.');
175
196
  return new App(value, { provider: this, event: this.#event });
176
197
  }
177
198
 
package/src/app/App.ts CHANGED
@@ -311,6 +311,9 @@ export class App<
311
311
  get appKey(): string {
312
312
  return this.#state.value.appKey;
313
313
  }
314
+ get tag(): string | undefined {
315
+ return this.#state.value.tag;
316
+ }
314
317
 
315
318
  get manifest(): Readonly<AppManifest> | undefined {
316
319
  return this.state.manifest;
@@ -564,7 +567,8 @@ export class App<
564
567
  }),
565
568
  error: (err) => {
566
569
  // emit error and complete the stream
567
- subscriber.error(err), this.#state.next(actions.initialize.failure(err));
570
+ subscriber.error(err);
571
+ this.#state.next(actions.initialize.failure(err));
568
572
  },
569
573
  complete: () => {
570
574
  // dispatch initialize success action to indicate that the application has been initialized
@@ -586,7 +590,7 @@ export class App<
586
590
  }
587
591
 
588
592
  public loadManifest(update?: boolean) {
589
- this.#state.next(actions.fetchManifest(this.appKey, update));
593
+ this.#state.next(actions.fetchManifest(this.appKey, this.tag, update));
590
594
  }
591
595
 
592
596
  public updateManifest(manifest: AppManifest, replace?: false) {
@@ -25,7 +25,7 @@ const createActions = () => ({
25
25
 
26
26
  fetchManifest: createAsyncAction(
27
27
  'fetch_manifest',
28
- (key: string, update?: boolean) => ({ payload: key, meta: { update } }),
28
+ (key: string, tag?: string, update?: boolean) => ({ payload: { key, tag }, meta: { update } }),
29
29
  (manifest: AppManifest) => ({ payload: manifest }),
30
30
  (error: unknown) => ({ payload: error }),
31
31
  ),
package/src/app/flows.ts CHANGED
@@ -24,12 +24,12 @@ export const handleFetchManifest =
24
24
  // when request is received, abort any ongoing request and start new
25
25
  switchMap((action) => {
26
26
  const {
27
- payload: appKey,
27
+ payload: { key, tag },
28
28
  meta: { update },
29
29
  } = action;
30
30
 
31
31
  // fetch manifest from provider
32
- const subject = from(provider.getAppManifest(appKey)).pipe(
32
+ const subject = from(provider.getAppManifest(key, tag)).pipe(
33
33
  // filter out null values
34
34
  filter((x) => !!x),
35
35
  // allow multiple subscriptions
package/src/app/types.ts CHANGED
@@ -29,6 +29,7 @@ export type AppBundleState<
29
29
  TModules = any,
30
30
  > = {
31
31
  appKey: string;
32
+ tag?: string;
32
33
  status: Set<ActionBaseType<Actions>>;
33
34
  manifest?: AppManifest;
34
35
  config?: AppConfig<TConfig>;
package/src/errors.ts CHANGED
@@ -90,6 +90,51 @@ export class AppConfigError extends Error {
90
90
  }
91
91
  }
92
92
 
93
+ /**
94
+ * Represents an error that occurs in the application build.
95
+ */
96
+ export class AppBuildError extends Error {
97
+ /**
98
+ * Creates an instance of `AppBuildError` based on the HTTP response status.
99
+ * @param response The HTTP response.
100
+ * @param options Additional error options.
101
+ * @returns An instance of `AppBuildError` based on the HTTP response status.
102
+ */
103
+ static fromHttpResponse(response: Response, options?: ErrorOptions): AppBuildError {
104
+ switch (response.status) {
105
+ case 401:
106
+ return new AppBuildError(
107
+ 'unauthorized',
108
+ 'failed to load application build metadata, request not authorized',
109
+ options,
110
+ );
111
+ case 404:
112
+ return new AppBuildError('not_found', 'application build metadata not found', options);
113
+ case 410:
114
+ return new AppBuildError('deleted', 'application build deleted', options);
115
+ }
116
+ return new AppBuildError(
117
+ 'unknown',
118
+ `failed to load application build, status code ${response.status}`,
119
+ options,
120
+ );
121
+ }
122
+
123
+ /**
124
+ * Creates an instance of `AppBuildError`.
125
+ * @param type The type of the application error.
126
+ * @param message The error message.
127
+ * @param options Additional error options.
128
+ */
129
+ constructor(
130
+ public readonly type: AppErrorType,
131
+ message?: string,
132
+ options?: ErrorOptions,
133
+ ) {
134
+ super(message, options);
135
+ }
136
+ }
137
+
93
138
  /**
94
139
  * Represents an error that occurs while fetching application settings.
95
140
  */
package/src/types.ts CHANGED
@@ -20,6 +20,14 @@ export type AppEnv<TEnv extends ConfigEnvironment = ConfigEnvironment, TProps =
20
20
  props?: TProps;
21
21
  };
22
22
 
23
+ /**
24
+ * Reference to an app, used to fetch app manifest from app service
25
+ */
26
+ export type AppReference = {
27
+ appKey: string;
28
+ tag?: string; // defaults to 'latest'
29
+ };
30
+
23
31
  // TODO: change to module-services when new app service is created
24
32
  export type ModuleDeps = [HttpModule, ServiceDiscoveryModule, EventModule];
25
33
 
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated by genversion.
2
- export const version = '7.0.4-next.0';
2
+ export const version = '7.1.0';