@equinor/fusion-framework-app 13.1.0 → 13.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.
@@ -1,49 +0,0 @@
1
- import { describe, it, expect, beforeEach } from 'vitest';
2
- import { firstValueFrom, take } from 'rxjs';
3
- import { AppConfigurator } from '../AppConfigurator';
4
- import type { AnyModule } from '@equinor/fusion-framework-module';
5
- import { SemanticVersion } from '@equinor/fusion-framework-module';
6
-
7
- describe('AppConfigurator', () => {
8
- let configurator: AppConfigurator;
9
-
10
- // Create a mock module for testing
11
- const createMockModule = (name: string, version = '1.0.0'): AnyModule => ({
12
- name,
13
- version: new SemanticVersion(version),
14
- initialize: () => ({ mockInstance: true }),
15
- });
16
-
17
- // Mock environment object
18
- const mockEnv = {
19
- manifest: {
20
- appKey: 'test-app',
21
- displayName: 'Test App',
22
- description: 'A test application',
23
- type: 'standalone' as const,
24
- build: {
25
- version: '1.0.0',
26
- entryPoint: 'index.js',
27
- },
28
- },
29
- };
30
-
31
- beforeEach(() => {
32
- configurator = new AppConfigurator(mockEnv);
33
- });
34
-
35
- describe('Event Name Prefixing', () => {
36
- it('should prefix event names with "AppConfigurator::"', async () => {
37
- // Trigger event by adding a config
38
- configurator.addConfig({
39
- module: createMockModule('test', '1.0.0'),
40
- configure: () => {},
41
- });
42
-
43
- // Wait for the first event to be emitted
44
- const event = await firstValueFrom(configurator.event$.pipe(take(1)));
45
-
46
- expect(event.name).toMatch(/^AppConfigurator::/);
47
- });
48
- });
49
- });
@@ -1,96 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import { AppConfig } from '@equinor/fusion-framework-module-app';
4
- import { enableTelemetry } from '@equinor/fusion-framework-module-telemetry';
5
-
6
- import { AppConfigurator } from '../../AppConfigurator.js';
7
- import { AppMockConfigurator } from '../../mock/AppMockConfigurator.js';
8
-
9
- const mockEnv = {
10
- manifest: {
11
- appKey: 'test-app',
12
- displayName: 'Test App',
13
- description: 'A test application',
14
- type: 'standalone' as const,
15
- build: {
16
- version: '1.0.0',
17
- entryPoint: 'index.js',
18
- },
19
- },
20
- };
21
-
22
- describe('AppMockConfigurator', () => {
23
- it('is a real AppConfigurator', () => {
24
- const configurator = new AppMockConfigurator(mockEnv);
25
-
26
- expect(configurator).toBeInstanceOf(AppMockConfigurator);
27
- expect(configurator).toBeInstanceOf(AppConfigurator);
28
- });
29
-
30
- it('constructs without throwing when env.config declares endpoints', async () => {
31
- // the base AppConfigurator constructor auto-registers these via addConfig,
32
- // before this class's own fields (#pinnedModules) are initialized
33
- const env = {
34
- ...mockEnv,
35
- config: new AppConfig({
36
- endpoints: { status: { url: 'https://status.example.com', scopes: [] } },
37
- }),
38
- };
39
-
40
- const configurator = new AppMockConfigurator(env);
41
- configurator.http.addMiddleware(async (uri, init, next) =>
42
- uri === 'https://status.example.com/health' ? Response.json({ ok: true }) : next(uri, init),
43
- );
44
-
45
- enableTelemetry(configurator);
46
- const modules = await configurator.initialize();
47
-
48
- await expect(modules.http.createClient('status').json('/health')).resolves.toEqual({
49
- ok: true,
50
- });
51
- });
52
-
53
- it('exposes the same http configurator the http module is built from', async () => {
54
- const configurator = new AppMockConfigurator(mockEnv);
55
-
56
- configurator.http.configureClient('catalog', { baseUri: 'https://api.example.com' });
57
- configurator.http.addMiddleware(async (uri, init, next) =>
58
- uri === 'https://api.example.com/items' ? Response.json([{ id: 1 }]) : next(uri, init),
59
- );
60
-
61
- // msal's config schema requires a telemetry module, normally wired by configureModules
62
- enableTelemetry(configurator);
63
- const modules = await configurator.initialize();
64
-
65
- await expect(modules.http.createClient('catalog').json('/items')).resolves.toEqual([{ id: 1 }]);
66
- });
67
-
68
- it('exposes the same msal configurator the auth module is built from', async () => {
69
- const configurator = new AppMockConfigurator(mockEnv);
70
-
71
- configurator.msal.setAccount({ name: 'Ada Lovelace' });
72
-
73
- // msal's config schema requires a telemetry module, normally wired by configureModules
74
- enableTelemetry(configurator);
75
- const modules = await configurator.initialize();
76
-
77
- expect(modules.auth.account?.name).toBe('Ada Lovelace');
78
- });
79
-
80
- it('answers a client registered through configureHttpClient via addMiddleware', async () => {
81
- const configurator = new AppMockConfigurator(mockEnv);
82
-
83
- configurator.configureHttpClient('status-api', { baseUri: 'https://status.example.com' });
84
- configurator.http.addMiddleware(async (uri, init, next) =>
85
- uri === 'https://status.example.com/status' ? Response.json({ ok: true }) : next(uri, init),
86
- );
87
-
88
- // msal's config schema requires a telemetry module, normally wired by configureModules
89
- enableTelemetry(configurator);
90
- const modules = await configurator.initialize();
91
-
92
- await expect(modules.http.createClient('status-api').json('/status')).resolves.toEqual({
93
- ok: true,
94
- });
95
- });
96
- });
@@ -1,112 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
-
3
- import type { Fusion } from '@equinor/fusion-framework';
4
- import { mockFramework } from '@equinor/fusion-framework/mock';
5
- import { AppConfig, type AppModule } from '@equinor/fusion-framework-module-app';
6
-
7
- import { AppMockConfigurator } from '../../mock/AppMockConfigurator.js';
8
- import { mockAppModules } from '../../mock/mock-app-modules.js';
9
-
10
- const env = {
11
- manifest: {
12
- appKey: 'test-app',
13
- displayName: 'Test App',
14
- description: 'A test application',
15
- type: 'standalone' as const,
16
- build: {
17
- version: '1.0.0',
18
- entryPoint: 'index.js',
19
- },
20
- },
21
- config: new AppConfig({ environment: { foo: 'bar' } }),
22
- };
23
-
24
- describe('mockApp', () => {
25
- it('initializes the app module pipeline with no configuration', async () => {
26
- const modules = await mockAppModules(undefined, env);
27
-
28
- expect(modules.event).toBeDefined();
29
- expect(modules.auth).toBeDefined();
30
- expect(modules.http).toBeDefined();
31
- });
32
-
33
- it('passes a real AppConfigurator to the callback', async () => {
34
- expect.assertions(1);
35
-
36
- await mockAppModules((configurator) => {
37
- expect(configurator).toBeInstanceOf(AppMockConfigurator);
38
- }, env);
39
- });
40
-
41
- it('answers a service-discovery-resolved client from the app’s own mocked http module', async () => {
42
- const modules = await mockAppModules((configurator) => {
43
- configurator.useFrameworkServiceClient('portal-api');
44
- // matches against the full resolved URL, so the host is part of the match to keep this
45
- // from also answering a different client's request
46
- configurator.http.addMiddleware(async (uri, init, next) =>
47
- uri === 'https://portal-api.fusion.test/items'
48
- ? Response.json([{ id: 1 }])
49
- : next(uri, init),
50
- );
51
- }, env);
52
-
53
- await expect(modules.http.createClient('portal-api').json('/items')).resolves.toEqual([
54
- { id: 1 },
55
- ]);
56
- });
57
-
58
- it('reuses an already-mocked fusion instance instead of creating a new one', async () => {
59
- expect.assertions(1);
60
-
61
- const fusion = await mockFramework((configurator) => {
62
- configurator.msal.setAccount({ name: 'Ada Lovelace' });
63
- });
64
-
65
- await mockAppModules(
66
- (_configurator, { fusion: parent }) => {
67
- expect(parent.modules.auth.account?.name).toBe('Ada Lovelace');
68
- },
69
- env,
70
- fusion,
71
- );
72
- });
73
-
74
- it('awaits an async configure callback before initialize resolves', async () => {
75
- const modules = await mockAppModules(async (configurator) => {
76
- await Promise.resolve();
77
- configurator.msal.setAccount({ name: 'Ada Lovelace' });
78
- }, env);
79
-
80
- expect(modules.auth.account?.name).toBe('Ada Lovelace');
81
- });
82
-
83
- it('serves this app’s own manifest and config through the default parent’s app module', async () => {
84
- expect.assertions(2);
85
-
86
- await mockAppModules(async (_configurator, { fusion }) => {
87
- // the default parent always has `app` enabled; typed as plain `Fusion` since callers
88
- // may pass in a parent without it, so the module set is narrowed just for this assertion
89
- const { app } = (fusion as Fusion<[AppModule]>).modules;
90
- app.setCurrentApp(env.manifest.appKey);
91
-
92
- await expect(app.current?.getManifestAsync()).resolves.toMatchObject({
93
- appKey: env.manifest.appKey,
94
- displayName: env.manifest.displayName,
95
- });
96
- await expect(app.current?.getConfigAsync()).resolves.toMatchObject({
97
- environment: env.config.environment,
98
- });
99
- }, env);
100
- });
101
-
102
- it('falls through to the real client for a manifest not matching this app’s own', async () => {
103
- expect.assertions(1);
104
-
105
- await mockAppModules(async (_configurator, { fusion }) => {
106
- const { app } = (fusion as Fusion<[AppModule]>).modules;
107
- app.setCurrentApp('some-other-app');
108
-
109
- await expect(app.current?.getManifestAsync()).rejects.toThrow();
110
- }, env);
111
- });
112
- });
@@ -1,54 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest';
2
-
3
- import { mockFramework } from '@equinor/fusion-framework/mock';
4
- import type { AuthenticationResult } from '@equinor/fusion-framework-module-msal';
5
-
6
- import { mockAppModules } from '../../mock/mock-app-modules.js';
7
-
8
- const env = {
9
- manifest: {
10
- appKey: 'test-app',
11
- displayName: 'Test App',
12
- description: 'A test application',
13
- type: 'standalone' as const,
14
- },
15
- };
16
-
17
- describe('msal hoisting', () => {
18
- it('delegates acquireToken to the parent’s auth module instead of building its own client', async () => {
19
- const fusion = await mockFramework();
20
- const result = { accessToken: 'parent-issued-token' } as AuthenticationResult;
21
- vi.spyOn(fusion.modules.auth, 'acquireToken').mockResolvedValue(result);
22
-
23
- const modules = await mockAppModules(undefined, env, fusion);
24
-
25
- // the app's own `auth` is a distinct (proxying) object, not the parent's instance itself
26
- expect(modules.auth).not.toBe(fusion.modules.auth);
27
- await expect(
28
- modules.auth.acquireToken({ request: { scopes: ['User.Read'] } }),
29
- ).resolves.toMatchObject({ accessToken: 'parent-issued-token' });
30
- });
31
-
32
- it('surfaces the parent’s acquisition failures instead of falling back to its own client', async () => {
33
- const fusion = await mockFramework();
34
- vi.spyOn(fusion.modules.auth, 'acquireToken').mockRejectedValue(
35
- new Error('acquisition failed'),
36
- );
37
-
38
- const modules = await mockAppModules(undefined, env, fusion);
39
-
40
- await expect(modules.auth.acquireToken({ request: { scopes: ['User.Read'] } })).rejects.toThrow(
41
- 'acquisition failed',
42
- );
43
- });
44
-
45
- it('reflects the parent’s signed-in account rather than signing in its own', async () => {
46
- const fusion = await mockFramework((configurator) => {
47
- configurator.msal.setAccount({ name: 'Ada Lovelace' });
48
- });
49
-
50
- const modules = await mockAppModules(undefined, env, fusion);
51
-
52
- expect(modules.auth.account?.name).toBe('Ada Lovelace');
53
- });
54
- });
@@ -1,71 +0,0 @@
1
- /**
2
- * @fileoverview Application module configuration factory
3
- *
4
- * Provides the core factory function for configuring and initializing
5
- * application-specific modules in the Fusion framework.
6
- */
7
-
8
- import type { Fusion } from '@equinor/fusion-framework';
9
- import type { AnyModule } from '@equinor/fusion-framework-module';
10
-
11
- import { AppConfigurator } from './AppConfigurator';
12
-
13
- import type { AppModulesInstance, AppModuleInitiator, AppEnv } from './types';
14
- import { initializeAppModules } from './initialize-app-modules';
15
-
16
- /**
17
- * Create an application module initializer for a Fusion application.
18
- *
19
- * `configureModules` is the primary entry point for setting up an application’s
20
- * module pipeline. It returns an async function that, when called with the Fusion
21
- * instance and the application environment, will:
22
- *
23
- * 1. Create an {@link AppConfigurator} with the provided environment.
24
- * 2. Wire up telemetry scoped to the application.
25
- * 3. Invoke the optional user-supplied configuration callback.
26
- * 4. Initialize all registered modules and dispatch an `onAppModulesLoaded` event.
27
- *
28
- * @template TModules - Additional application-specific modules to register.
29
- * @template TRef - The Fusion instance type, used as a configuration reference.
30
- * @template TEnv - The application environment descriptor (manifest, config, basename).
31
- *
32
- * @param cb - Optional configuration callback invoked before modules are initialized.
33
- * Use this to register HTTP clients, enable bookmarks, or add custom modules.
34
- * @returns An async initializer function that accepts `{ fusion, env }` and resolves
35
- * with the fully initialized application module instance.
36
- *
37
- * @example
38
- * ```ts
39
- * import { configureModules } from '@equinor/fusion-framework-app';
40
- *
41
- * const initialize = configureModules((configurator, { fusion, env }) => {
42
- * configurator.useFrameworkServiceClient('my-service');
43
- * });
44
- *
45
- * // Later, during app bootstrap:
46
- * const modules = await initialize({ fusion, env });
47
- * ```
48
- */
49
- export const configureModules =
50
- <
51
- TModules extends Array<AnyModule> | never,
52
- TRef extends Fusion = Fusion,
53
- TEnv extends AppEnv = AppEnv,
54
- >(
55
- cb?: AppModuleInitiator<TModules, TRef, TEnv>,
56
- ): ((args: { fusion: TRef; env: TEnv }) => Promise<AppModulesInstance<TModules>>) =>
57
- /**
58
- * Async initializer that bootstraps application modules.
59
- *
60
- * @param args - Object containing the Fusion instance and the application environment.
61
- * @param args.fusion - The active Fusion framework instance.
62
- * @param args.env - The application environment with manifest, config, and basename.
63
- * @returns The fully initialized application module instance.
64
- */
65
- async (args: { fusion: TRef; env: TEnv }): Promise<AppModulesInstance<TModules>> => {
66
- // Create app configurator
67
- const configurator = new AppConfigurator<TModules, TRef['modules'], TEnv>(args.env);
68
- return initializeAppModules(configurator, cb, args);
69
- };
70
-
71
- export default configureModules;
@@ -1,95 +0,0 @@
1
- import type {
2
- BookmarkModule,
3
- BookmarkPayloadGenerator,
4
- } from '@equinor/fusion-framework-module-bookmark';
5
- import type { IAppConfigurator } from './AppConfigurator';
6
-
7
- /**
8
- * Enable the bookmark module for a Fusion application.
9
- *
10
- * Adds bookmark support by wiring the portal’s bookmark provider into the
11
- * application’s module set. Payload generators registered by the application
12
- * are automatically cleaned up when the module is disposed, preventing memory
13
- * leaks across application load/unload cycles.
14
- *
15
- * Import this function from `@equinor/fusion-framework-app/enable-bookmark` or, for
16
- * React apps, from `@equinor/fusion-framework-react-app/bookmark`.
17
- *
18
- * @remarks
19
- * - The portal must expose a bookmark provider on `ref.bookmark`; if it is
20
- * missing, an error is logged and the module initializes as a no-op.
21
- * - The `@equinor/fusion-framework-module-bookmark` package must be installed,
22
- * but do **not** call its `enableBookmark` directly in app code — use this
23
- * app-level enabler instead.
24
- *
25
- * @param config - The application configurator to register the bookmark module on.
26
- *
27
- * @example
28
- * ```ts
29
- * import { configureModules } from '@equinor/fusion-framework-app';
30
- * import { enableBookmark } from '@equinor/fusion-framework-app/enable-bookmark';
31
- *
32
- * const initialize = configureModules((configurator) => {
33
- * enableBookmark(configurator);
34
- * });
35
- * ```
36
- */
37
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
- export const enableBookmark = (config: IAppConfigurator): void => {
39
- // collection of cleanup functions
40
- const cleanupFunctions = new Set<VoidFunction>();
41
- config.addConfig({
42
- module: {
43
- name: 'bookmark',
44
- initialize(args) {
45
- // get the bookmark provider from the ref (portal)
46
- const provider = args.ref?.bookmark;
47
- // Bail out early if the portal did not expose a bookmark provider.
48
- if (!provider) {
49
- console.error('Bookmark provider not found');
50
- return {};
51
- }
52
- // create a proxy to intercept the addPayloadGenerator method
53
- return new Proxy(provider, {
54
- get(target, prop) {
55
- // Intercept specific property access to wrap bookmark lifecycle management.
56
- switch (prop) {
57
- case 'addPayloadGenerator':
58
- return (generator: BookmarkPayloadGenerator) => {
59
- // catch the teardown function and add it to the cleanup functions
60
- const cleanupHandler = target.addPayloadGenerator(generator);
61
- cleanupFunctions.add(cleanupHandler);
62
- // wrap the teardown function to remove it from the cleanup functions
63
- return () => {
64
- cleanupFunctions.delete(cleanupHandler);
65
- cleanupHandler();
66
- };
67
- };
68
- }
69
- /**
70
- * If the property is not addPayloadGenerator, we want to access the property on the provider
71
- *
72
- * @remarks we can not use the Reflect API to access the property, as the provider is a proxy
73
- * and the Reflect API will not work as expected (can not access private properties)
74
- */
75
- if (prop in target) {
76
- // if the property is a function, bind it to the provider
77
- if (typeof target[prop] === 'function') {
78
- return target[prop].bind(target);
79
- }
80
- // access the property
81
- return target[prop];
82
- }
83
- },
84
- });
85
- },
86
- dispose() {
87
- // Run all registered cleanup functions to prevent memory leaks on module disposal.
88
- for (const teardown of cleanupFunctions) {
89
- teardown();
90
- }
91
- cleanupFunctions.clear();
92
- },
93
- } satisfies BookmarkModule,
94
- });
95
- };
@@ -1,49 +0,0 @@
1
- import { enableStateModule } from '@equinor/fusion-framework-module-state';
2
- import type { IStateModuleConfigurator } from '@equinor/fusion-framework-module-state';
3
- import type { IAppConfigurator } from './AppConfigurator';
4
- import type { AnyModule } from '@equinor/fusion-framework-module';
5
- import type { FusionModulesInstance } from '@equinor/fusion-framework';
6
-
7
- /**
8
- * Enables state management for the application with persistent storage.
9
- *
10
- * This is a thin, app-scoped convenience wrapper around `enableStateModule` — it registers
11
- * the state module on the app's configurator so app code can reach it via the app namespace,
12
- * and scopes the module's default storage to this app's own `manifest.appKey` (so unrelated
13
- * apps or widgets hosted alongside it never share its state). The state module resolves a
14
- * local PouchDB database, optionally synced with the Fusion App State backend when service
15
- * discovery and auth are configured; call `setStorage` on the configurator to override it.
16
- *
17
- * @warning Local storage is NOT encrypted. Do not store sensitive data such as passwords,
18
- * tokens, personal information, or any data that requires security protection.
19
- *
20
- * @see {@link https://github.com/equinor/fusion-framework/blob/main/packages/modules/state/README.md | State Module Documentation} for comprehensive usage examples and API reference.
21
- *
22
- * @template M - Array of modules to be configured.
23
- * @template R - The fusion modules instance type.
24
- * @param configurator - The application configurator to enable state management on.
25
- * @param configure - Optional config callback, receiving the module's `IStateModuleConfigurator`.
26
- *
27
- * @example
28
- * ```typescript
29
- * import { enableState } from '@equinor/fusion-framework-app';
30
- *
31
- * export const configure = (configurator) => {
32
- * enableState(configurator);
33
- * };
34
- *
35
- * // Later in your app, access the state provider
36
- * const stateProvider = modules.state;
37
- * await stateProvider.storeItem({ key: 'user-preference', value: { theme: 'dark' } });
38
- * const item = await stateProvider.getItem('user-preference');
39
- * ```
40
- */
41
- export function enableState<M extends AnyModule[], R extends FusionModulesInstance>(
42
- configurator: IAppConfigurator<M, R>,
43
- configure?: (builder: IStateModuleConfigurator) => void | Promise<void>,
44
- ): void {
45
- enableStateModule(configurator, async (builder) => {
46
- builder.setName(configurator.manifest.appKey);
47
- await configure?.(builder);
48
- });
49
- }
package/src/index.ts DELETED
@@ -1,34 +0,0 @@
1
- /**
2
- * @packageDocumentation
3
- *
4
- * `@equinor/fusion-framework-app` provides the configuration and initialization
5
- * layer for Fusion applications. Use this package to set up application modules,
6
- * configure HTTP clients, enable bookmarks, and integrate with telemetry and
7
- * service discovery.
8
- *
9
- * The main entry points are:
10
- *
11
- * - {@link configureModules} — factory that creates an application initializer
12
- * - {@link AppConfigurator} / {@link IAppConfigurator} — configurator for registering modules and HTTP clients
13
- * - Type aliases such as {@link AppModuleInitiator}, {@link AppEnv}, and {@link AppRenderFn}
14
- *
15
- * Bookmark support is available via the `@equinor/fusion-framework-app/enable-bookmark`
16
- * sub-path export.
17
- */
18
-
19
- export { AppConfigurator, IAppConfigurator, AppConfiguratorConstructor } from './AppConfigurator';
20
-
21
- export * from './types';
22
-
23
- export { configureModules, default } from './configure-modules';
24
-
25
- export { AppConfiguratorError } from './AppConfiguratorError';
26
-
27
- export { AppModulesConfiguredEvent } from './AppModulesConfiguredEvent';
28
-
29
- export { AppModulesInitializedEvent } from './AppModulesInitializedEvent';
30
-
31
- /**
32
- * @deprecated Use {@link configureModules} instead. This alias will be removed in a future major version.
33
- */
34
- export { configureModules as initAppModules } from './configure-modules';
@@ -1,98 +0,0 @@
1
- import type { Fusion } from '@equinor/fusion-framework';
2
- import type { AnyModule } from '@equinor/fusion-framework-module';
3
- import {
4
- enableTelemetry,
5
- type MetadataExtractor,
6
- } from '@equinor/fusion-framework-module-telemetry';
7
-
8
- import type { AppConfigurator } from './AppConfigurator';
9
- import type { AppModulesInstance, AppEnv } from './types';
10
-
11
- /**
12
- * Runs the telemetry wiring, the caller's configuration callback and module
13
- * initialization against an already constructed configurator.
14
- *
15
- * @remarks
16
- * Extracted so `mockAppModules` (`@equinor/fusion-framework-app/mock`) can drive the
17
- * exact same pipeline against an `AppMockConfigurator` instead of reimplementing
18
- * it — the same way `FrameworkConfigurator` and `FrameworkMockConfigurator`
19
- * share the framework's `init`.
20
- *
21
- * @param configurator - The (real or mock) app configurator to run the pipeline on.
22
- * @param cb - Configuration callback invoked before module initialization, or `undefined` to skip it.
23
- * @param args - Object containing the Fusion instance and the application environment.
24
- * @returns The fully initialized application module instance.
25
- * @template TModules - Application module descriptors beyond the default set.
26
- * @template TRef - The parent Fusion instance type.
27
- * @template TEnv - The application environment descriptor.
28
- * @template TConfigurator - The (real or mock) `AppConfigurator` subclass driving the pipeline.
29
- */
30
- export async function initializeAppModules<
31
- TModules extends Array<AnyModule> | never,
32
- TRef extends Fusion = Fusion,
33
- TEnv extends AppEnv = AppEnv,
34
- // Widened beyond the plain `AppConfigurator` so callers such as `mockAppModules` can
35
- // drive the same pipeline against a subclass (e.g. `AppMockConfigurator`) and have `cb`
36
- // typed against that subclass rather than the base `IAppConfigurator` interface.
37
- TConfigurator extends AppConfigurator<TModules, TRef['modules'], TEnv> = AppConfigurator<
38
- TModules,
39
- TRef['modules'],
40
- TEnv
41
- >,
42
- >(
43
- configurator: TConfigurator,
44
- cb:
45
- | ((configurator: TConfigurator, args: { fusion: TRef; env: TEnv }) => void | Promise<void>)
46
- | undefined,
47
- args: { fusion: TRef; env: TEnv },
48
- ): Promise<AppModulesInstance<TModules>> {
49
- const { fusion } = args;
50
-
51
- // Extract telemetry metadata from app manifest for tracking and debugging
52
- const metadataExtractor: MetadataExtractor = () => {
53
- return {
54
- fusion: {
55
- type: 'app-telemetry',
56
- app: {
57
- key: args.env.manifest?.appKey || 'unknown-app',
58
- version: args.env.manifest?.build?.version || 'unknown-version',
59
- },
60
- },
61
- };
62
- };
63
-
64
- // Enable telemetry collection for module configuration events
65
- // attachConfiguratorEvents automatically prefixes events with configurator class name
66
- enableTelemetry(configurator, {
67
- attachConfiguratorEvents: true,
68
- configure: (builder) => {
69
- builder.setMetadata(metadataExtractor);
70
- builder.setParent(fusion.modules.telemetry);
71
- // Scope telemetry to 'app' level for app-specific event filtering
72
- builder.setDefaultScope(['app']);
73
- },
74
- });
75
-
76
- // Allow user configuration callback to run before module initialization
77
- if (cb) {
78
- await Promise.resolve(cb(configurator, args));
79
- }
80
- // Type cast is safe because AppConfigurator.initialize() returns the exact module
81
- // instance that was registered and configured above. The intermediate 'unknown'
82
- // cast is necessary due to TypeScript's generic inference limitations with the
83
- // configurator's initialization chain, but the runtime value is guaranteed to match.
84
- const modules: AppModulesInstance<TModules> = (await configurator.initialize(
85
- args.fusion.modules,
86
- )) as unknown as AppModulesInstance<TModules>;
87
-
88
- // Dispatch app modules loaded event for app lifecycle tracking
89
- // TODO(#5061): remove check after fusion-cli is updated (app module is not enabled in fusion-cli)
90
- if (args.env.manifest?.appKey) {
91
- modules.event.dispatchEvent('onAppModulesLoaded', {
92
- detail: { appKey: args.env.manifest.appKey, manifest: args.env.manifest, modules },
93
- });
94
- }
95
- return modules;
96
- }
97
-
98
- export default initializeAppModules;