@equinor/fusion-framework-module-analytics 3.1.0 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/esm/version.js +1 -1
  2. package/dist/tsconfig.tsbuildinfo +1 -1
  3. package/dist/types/version.d.ts +1 -1
  4. package/package.json +11 -8
  5. package/CHANGELOG.md +0 -386
  6. package/docs/adapters.md +0 -79
  7. package/docs/collectors.md +0 -91
  8. package/docs/testing.md +0 -87
  9. package/docs/tracking-events.md +0 -16
  10. package/src/AnalyticsConfigurator.interface.ts +0 -77
  11. package/src/AnalyticsConfigurator.ts +0 -128
  12. package/src/AnalyticsProvider.interface.ts +0 -44
  13. package/src/AnalyticsProvider.ts +0 -150
  14. package/src/__tests__/MockAnalyticsAdapter.test.ts +0 -127
  15. package/src/__tests__/mock/analytics-mock-adapter.test.ts +0 -79
  16. package/src/adapters/AnalyticsAdapter.interface.ts +0 -36
  17. package/src/adapters/ConsoleAnalyticsAdapter.ts +0 -37
  18. package/src/adapters/FusionAnalyticsAdapter.ts +0 -95
  19. package/src/adapters/index.ts +0 -11
  20. package/src/collectors/AnalyticsCollector.interface.ts +0 -27
  21. package/src/collectors/AppLoadedCollector.ts +0 -94
  22. package/src/collectors/AppSelectedCollector.ts +0 -70
  23. package/src/collectors/ContextSelectedCollector.ts +0 -83
  24. package/src/collectors/create-schema.ts +0 -123
  25. package/src/collectors/index.ts +0 -12
  26. package/src/collectors/utils/app-key-schema.ts +0 -17
  27. package/src/collectors/utils/app-schema.ts +0 -22
  28. package/src/collectors/utils/context-schema.ts +0 -22
  29. package/src/collectors/utils/extract-app-key-metadata.ts +0 -15
  30. package/src/collectors/utils/extract-app-metadata.ts +0 -20
  31. package/src/collectors/utils/extract-context-metadata.ts +0 -19
  32. package/src/enable-analytics.ts +0 -44
  33. package/src/index.ts +0 -30
  34. package/src/logExporters/fusionOTLPLogExporter/FusionOTLPLogExporter.ts +0 -71
  35. package/src/logExporters/fusionOTLPLogExporter/HttpClientExporterTransport.ts +0 -123
  36. package/src/logExporters/index.ts +0 -10
  37. package/src/mock/MockAnalyticsAdapter.ts +0 -178
  38. package/src/mock/index.ts +0 -29
  39. package/src/module.ts +0 -59
  40. package/src/types.ts +0 -60
  41. package/src/version.ts +0 -2
  42. package/tsconfig.json +0 -29
  43. package/vitest.config.ts +0 -11
@@ -1,79 +0,0 @@
1
- import { describe, expect, it, vi } from 'vitest';
2
- import { ModulesConfigurator } from '@equinor/fusion-framework-module';
3
- import { Subject } from 'rxjs';
4
-
5
- import { enableAnalytics } from '../../enable-analytics.js';
6
- import { ConsoleAnalyticsAdapter } from '../../adapters/ConsoleAnalyticsAdapter.js';
7
- import { MockAnalyticsAdapter } from '../../mock/MockAnalyticsAdapter.js';
8
- import type { IAnalyticsConfigurator } from '../../AnalyticsConfigurator.interface.js';
9
- import type { IAnalyticsProvider } from '../../AnalyticsProvider.interface.js';
10
- import type { AnalyticsEvent } from '../../types.js';
11
-
12
- /**
13
- * Initializes the analytics module through the real module system, with a
14
- * `MockAnalyticsAdapter` registered alongside whatever the test configures.
15
- *
16
- * @remarks
17
- * Deliberately avoids hand-building an `AnalyticsProvider`. Testing the
18
- * adapter's own logic in isolation (see `MockAnalyticsAdapter.test.ts`) can't
19
- * prove it actually receives events through the real configure -> initialize
20
- * -> collector/adapter dispatch pipeline every other adapter goes through.
21
- *
22
- * @param configure - Optional callback to register additional adapters/collectors.
23
- * @returns The real `IAnalyticsProvider` instance and the recording adapter.
24
- */
25
- const initializeWith = async (
26
- configure?: (builder: IAnalyticsConfigurator) => void,
27
- ): Promise<{ provider: IAnalyticsProvider; recorder: MockAnalyticsAdapter }> => {
28
- const recorder = new MockAnalyticsAdapter();
29
- const configurator = new ModulesConfigurator([]);
30
-
31
- enableAnalytics(configurator, (builder) => {
32
- builder.setAdapter('mock', async () => recorder);
33
- configure?.(builder);
34
- });
35
-
36
- const instances = await configurator.initialize();
37
- const provider = (instances as unknown as { analytics: IAnalyticsProvider }).analytics;
38
-
39
- return { provider, recorder };
40
- };
41
-
42
- describe('MockAnalyticsAdapter (through the real analytics module)', () => {
43
- it('observes events pushed via provider.trackAnalytic', async () => {
44
- const { provider, recorder } = await initializeWith();
45
-
46
- provider.trackAnalytic({ name: 'button-click', value: 'save' });
47
-
48
- expect(recorder.getAnalytics('button-click')).toHaveLength(1);
49
- });
50
-
51
- it('observes events emitted by a real registered collector', async () => {
52
- const clicks$ = new Subject<AnalyticsEvent>();
53
- const { recorder } = await initializeWith((builder) => {
54
- builder.setCollector('clicks', async () => clicks$);
55
- });
56
-
57
- clicks$.next({ name: 'window-click', value: 42 });
58
-
59
- const event = await recorder.waitForAnalytic('window-click');
60
- expect(event.value).toBe(42);
61
- });
62
-
63
- it('does not interfere with other adapters registered alongside it', async () => {
64
- const logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
65
- const { provider, recorder } = await initializeWith((builder) => {
66
- builder.setAdapter('console', async () => new ConsoleAnalyticsAdapter());
67
- });
68
-
69
- provider.trackAnalytic({ name: 'page-view', value: null });
70
-
71
- expect(recorder.getAnalytics('page-view')).toHaveLength(1);
72
- expect(logSpy).toHaveBeenCalledWith('Analytics::Adapter::Console', {
73
- name: 'page-view',
74
- value: null,
75
- });
76
-
77
- logSpy.mockRestore();
78
- });
79
- });
@@ -1,36 +0,0 @@
1
- import type { AnalyticsEvent } from '../types.js';
2
-
3
- /**
4
- * Contract for an analytics adapter that receives events and exports them to a backend.
5
- *
6
- * @remarks
7
- * Implement this interface to create a custom adapter. Register it via
8
- * {@link IAnalyticsConfigurator.setAdapter}. The adapter must also implement
9
- * `Disposable` for resource cleanup.
10
- *
11
- * @template T - The analytics event type handled by the adapter, defaults to {@link AnalyticsEvent}.
12
- *
13
- * @example
14
- * ```ts
15
- * class MyAdapter implements IAnalyticsAdapter {
16
- * registerAnalytic(event) { fetch('/analytics', { method: 'POST', body: JSON.stringify(event) }); }
17
- * [Symbol.dispose]() { /* cleanup *\/ }
18
- * }
19
- * ```
20
- */
21
- export interface IAnalyticsAdapter<T extends AnalyticsEvent = AnalyticsEvent> extends Disposable {
22
- /**
23
- * Initializes the analytics adapter for setup and configuration.
24
- *
25
- * @returns Promise that resolves when initialization is complete, or void for synchronous initialization.
26
- */
27
- initialize?(): Promise<void> | void;
28
-
29
- /**
30
- * Exports analytics event to the configured backend.
31
- *
32
- * @param events - Array of analytics events to export.
33
- * @returns Promise that resolves when export is complete.
34
- */
35
- registerAnalytic(event: T): Promise<void> | void;
36
- }
@@ -1,37 +0,0 @@
1
- import type { IAnalyticsAdapter } from './AnalyticsAdapter.interface.js';
2
- import type { AnalyticsEvent } from '../types.js';
3
-
4
- /**
5
- * Analytics adapter that logs every event to the browser console.
6
- *
7
- * @remarks
8
- * Useful during development and debugging. Each event is logged with the
9
- * prefix `Analytics::Adapter::Console`.
10
- *
11
- * @template T - Analytics event type, defaults to {@link AnalyticsEvent}.
12
- *
13
- * @example
14
- * ```ts
15
- * import { ConsoleAnalyticsAdapter } from '@equinor/fusion-framework-module-analytics/adapters';
16
- *
17
- * builder.setAdapter('console', async () => new ConsoleAnalyticsAdapter());
18
- * ```
19
- */
20
- export class ConsoleAnalyticsAdapter<T extends AnalyticsEvent = AnalyticsEvent>
21
- implements IAnalyticsAdapter
22
- {
23
- /**
24
- * Logs the event to the console.
25
- *
26
- * @param event - The analytics event to log.
27
- * @returns Resolves once the event has been logged.
28
- */
29
- registerAnalytic(event: T): Promise<void> | void {
30
- console.log('Analytics::Adapter::Console', event);
31
- }
32
-
33
- /** No teardown is required for this adapter. */
34
- [Symbol.dispose]() {
35
- // no-op
36
- }
37
- }
@@ -1,95 +0,0 @@
1
- import type { IAnalyticsAdapter } from './AnalyticsAdapter.interface.js';
2
- import type { AnalyticsEvent } from '../types.js';
3
-
4
- import {
5
- LoggerProvider,
6
- BatchLogRecordProcessor,
7
- type ReadableLogRecord,
8
- } from '@opentelemetry/sdk-logs';
9
- import type { OTLPExporterBase } from '@opentelemetry/otlp-exporter-base';
10
- import {
11
- SeverityNumber,
12
- type LogAttributes,
13
- type Logger,
14
- type LogRecord,
15
- } from '@opentelemetry/api-logs';
16
- import { resourceFromAttributes } from '@opentelemetry/resources';
17
- import { version } from '../version.js';
18
- import { v7 as uuid } from 'uuid';
19
-
20
- /**
21
- * Analytics adapter that forwards events to an OpenTelemetry log exporter.
22
- *
23
- * @remarks
24
- * Uses `@opentelemetry/sdk-logs` to batch and export log records through the
25
- * provided {@link OTLPExporterBase} transport. Each analytics event is mapped
26
- * to an OTLP `LogRecord` with severity `INFO`.
27
- *
28
- * Resource attributes automatically include the module version, a unique
29
- * session ID (UUIDv7), and the portal ID supplied at construction time.
30
- *
31
- * @template T - Analytics event type, defaults to {@link AnalyticsEvent}.
32
- *
33
- * @example
34
- * ```ts
35
- * import { FusionAnalyticsAdapter } from '@equinor/fusion-framework-module-analytics/adapters';
36
- * import { OTLPLogExporter } from '@equinor/fusion-framework-module-analytics/logExporters';
37
- *
38
- * builder.setAdapter('fusion-log', async () => {
39
- * const logExporter = new OTLPLogExporter({ url: '/v1/logs' });
40
- * return new FusionAnalyticsAdapter({ portalId: 'my-portal', logExporter });
41
- * });
42
- * ```
43
- *
44
- * @see {@link OTLPExporterBase}
45
- */
46
- export class FusionAnalyticsAdapter<T extends AnalyticsEvent = AnalyticsEvent>
47
- implements IAnalyticsAdapter
48
- {
49
- #logExporter: OTLPExporterBase<ReadableLogRecord[]>;
50
- #loggerProvider: LoggerProvider;
51
- #logger: Logger;
52
-
53
- /**
54
- * Creates a new `FusionAnalyticsAdapter`.
55
- *
56
- * @param args - Construction options.
57
- * @param args.portalId - Portal identifier attached to every exported log record.
58
- * @param args.logExporter - An OTLP-compatible log exporter for transport.
59
- */
60
- constructor(args: { portalId: string; logExporter: OTLPExporterBase<ReadableLogRecord[]> }) {
61
- this.#logExporter = args.logExporter;
62
-
63
- this.#loggerProvider = new LoggerProvider({
64
- processors: [new BatchLogRecordProcessor({ exporter: this.#logExporter })],
65
- resource: resourceFromAttributes({
66
- 'module.version': version,
67
- 'session.id': uuid(),
68
- 'portal.id': args.portalId,
69
- }),
70
- });
71
- this.#logger = this.#loggerProvider.getLogger('fusion');
72
- }
73
-
74
- /**
75
- * Maps an analytics event to an OTLP `LogRecord` and emits it via the logger.
76
- *
77
- * @param event - The analytics event to map and emit.
78
- * @returns Resolves once the event has been emitted to the logger.
79
- */
80
- registerAnalytic(event: T): Promise<void> | void {
81
- const logRecord: Partial<LogRecord> = {
82
- eventName: event.name,
83
- attributes: event.attributes as LogAttributes,
84
- body: event.value,
85
- severityNumber: SeverityNumber.INFO,
86
- };
87
- this.#logger.emit(logRecord);
88
- }
89
-
90
- /** Shuts down the log exporter and logger provider, flushing remaining records. */
91
- [Symbol.dispose]() {
92
- this.#logExporter.shutdown();
93
- this.#loggerProvider.shutdown();
94
- }
95
- }
@@ -1,11 +0,0 @@
1
- /**
2
- * Re-exports for built-in analytics adapters.
3
- *
4
- * @remarks
5
- * Import from `'@equinor/fusion-framework-module-analytics/adapters'`.
6
- *
7
- * @packageDocumentation
8
- */
9
- export { IAnalyticsAdapter } from './AnalyticsAdapter.interface.js';
10
- export { ConsoleAnalyticsAdapter } from './ConsoleAnalyticsAdapter.js';
11
- export { FusionAnalyticsAdapter } from './FusionAnalyticsAdapter.js';
@@ -1,27 +0,0 @@
1
- import type { Subscribable } from 'rxjs';
2
- import type { AnalyticsEvent } from '../types.js';
3
-
4
- /**
5
- * Contract for an analytics collector that observes application state and emits
6
- * structured analytics events.
7
- *
8
- * @remarks
9
- * Implement this interface (or extend {@link BaseCollector}) to create a custom
10
- * collector. Register it via {@link IAnalyticsConfigurator.setCollector}.
11
- *
12
- * The collector must be `Subscribable` so the provider can listen for emitted
13
- * events. Optionally implement `initialize` for async setup (e.g. resolving
14
- * module dependencies).
15
- *
16
- * @template T - Analytics event type emitted by this collector, defaults to {@link AnalyticsEvent}.
17
- */
18
- export interface IAnalyticsCollector<T extends AnalyticsEvent = AnalyticsEvent>
19
- extends Subscribable<T> {
20
- /**
21
- * Initializes the analytics collector for setup and configuration.
22
- * This method is optional - if not implemented, the collector should be directly subscribable.
23
- *
24
- * @returns Promise that resolves when initialization is complete, or void for synchronous initialization.
25
- */
26
- initialize?(): Promise<void> | void;
27
- }
@@ -1,94 +0,0 @@
1
- import { BaseCollector, createSchema } from './create-schema.js';
2
- import { type AppItemType, appSchema } from './utils/app-schema.js';
3
- import { extractAppMetadata } from './utils/extract-app-metadata.js';
4
- import { type ContextItemType, contextSchema } from './utils/context-schema.js';
5
- import { extractContextMetadata } from './utils/extract-context-metadata.js';
6
- import type { IAnalyticsCollector } from './AnalyticsCollector.interface.js';
7
-
8
- import type {
9
- AppModulesInstance,
10
- AppManifest,
11
- AppModuleProvider,
12
- } from '@equinor/fusion-framework-module-app';
13
- import type { IEventModuleProvider } from '@equinor/fusion-framework-module-event';
14
-
15
- import { type ObservableInput, Subject } from 'rxjs';
16
- import { z } from 'zod';
17
- import type { ContextModule } from '@equinor/fusion-framework-module-context';
18
-
19
- const EVENT_NAME = 'onAppModulesLoaded';
20
-
21
- /** Zod schema for the `app-loaded` event (value + attributes). */
22
- const eventSchema = createSchema(appSchema, z.object({ context: contextSchema }));
23
-
24
- /**
25
- * Collector that emits an analytics event whenever an application’s modules
26
- * finish loading.
27
- *
28
- * @remarks
29
- * Listens to the framework `onAppModulesLoaded` event, extracts application
30
- * manifest metadata, and includes the current context (if available) in the
31
- * event attributes.
32
- *
33
- * Register via:
34
- * ```ts
35
- * builder.setCollector('app-loaded', async (args) => {
36
- * const event = await args.requireInstance('event');
37
- * const app = await args.requireInstance('app');
38
- * return new AppLoadedCollector(event, app);
39
- * });
40
- * ```
41
- */
42
- export class AppLoadedCollector
43
- extends BaseCollector<AppItemType, { context?: ContextItemType }>
44
- implements IAnalyticsCollector
45
- {
46
- #eventProvider: IEventModuleProvider;
47
- #appProvider: AppModuleProvider;
48
-
49
- /**
50
- * @param eventProvider - Fusion event module provider to listen for app-loaded events.
51
- * @param appProvider - Fusion app module provider for fallback manifest data.
52
- */
53
- constructor(eventProvider: IEventModuleProvider, appProvider: AppModuleProvider) {
54
- super('app-loaded', eventSchema);
55
- this.#eventProvider = eventProvider;
56
- this.#appProvider = appProvider;
57
- }
58
-
59
- /**
60
- * Builds an observable that emits an analytics event whenever the app-loaded
61
- * event fires, falling back to `appProvider` for manifest data when needed.
62
- *
63
- * @returns An observable input emitting the app-loaded event's value and attributes.
64
- */
65
- _initialize(): ObservableInput<{
66
- value: AppItemType;
67
- attributes: { context?: ContextItemType };
68
- }> {
69
- const subject = new Subject<{
70
- value: AppItemType;
71
- attributes: { context?: ContextItemType };
72
- }>();
73
- this.#eventProvider.addEventListener(EVENT_NAME, (event) => {
74
- // Fallback to appProvider for manifest if app is not updated with latest
75
- // payload for onAppModulesLoaded (missing manifest).
76
- const manifest =
77
- (event.detail.manifest as AppManifest) ?? this.#appProvider.current?.manifest;
78
- const modules = event.detail.modules as AppModulesInstance<[ContextModule]>;
79
-
80
- const data = {
81
- value: manifest && extractAppMetadata(manifest),
82
- attributes: {
83
- context:
84
- modules.context?.currentContext &&
85
- extractContextMetadata(modules.context.currentContext),
86
- },
87
- };
88
-
89
- subject.next(data);
90
- });
91
-
92
- return subject;
93
- }
94
- }
@@ -1,70 +0,0 @@
1
- import { map, type ObservableInput, pairwise } from 'rxjs';
2
- import type { IAnalyticsCollector } from './AnalyticsCollector.interface.js';
3
- import { BaseCollector, createSchema } from './create-schema.js';
4
- import { type AppKeyType, appKeySchema } from './utils/app-key-schema.js';
5
- import { extractAppKeyMetadata } from './utils/extract-app-key-metadata.js';
6
-
7
- import type { AppModuleProvider } from '@equinor/fusion-framework-module-app';
8
-
9
- import { z } from 'zod';
10
-
11
- /** Zod schema for the `app-selected` event (value + attributes). */
12
- const eventSchema = createSchema(appKeySchema, z.object({ previous: appKeySchema }));
13
-
14
- /**
15
- * Collector that emits an analytics event whenever the active Fusion application changes.
16
- *
17
- * @remarks
18
- * Listens to `AppModuleProvider.current$`, pairs consecutive values, and emits
19
- * both the new and previous app key metadata.
20
- *
21
- * Register via:
22
- * ```ts
23
- * builder.setCollector('app-selected', async (args) => {
24
- * const app = await args.requireInstance('app');
25
- * return new AppSelectedCollector(app);
26
- * });
27
- * ```
28
- */
29
- export class AppSelectedCollector
30
- extends BaseCollector<AppKeyType, { previous?: AppKeyType }>
31
- implements IAnalyticsCollector
32
- {
33
- #appProvider: AppModuleProvider;
34
-
35
- /**
36
- * @param appProvider - Fusion app module provider to observe.
37
- */
38
- constructor(appProvider: AppModuleProvider) {
39
- super('app-selected', eventSchema);
40
- this.#appProvider = appProvider;
41
- }
42
-
43
- /**
44
- * Builds an observable that emits an analytics event each time the selected
45
- * app changes, including the previous app's key metadata.
46
- *
47
- * @returns An observable input emitting the newly selected app's value and attributes.
48
- */
49
- _initialize(): ObservableInput<{
50
- value: AppKeyType;
51
- attributes: { previous?: AppKeyType };
52
- }> {
53
- // Pair each app-selection emission with the previously selected app
54
- const appSelected$ = this.#appProvider.current$.pipe(pairwise());
55
-
56
- // Map the [previous, next] pair into the analytics event shape
57
- const data$ = appSelected$.pipe(
58
- map(([prev, next]) => {
59
- return {
60
- value: extractAppKeyMetadata(next),
61
- attributes: {
62
- previous: prev && extractAppKeyMetadata(prev),
63
- },
64
- };
65
- }),
66
- );
67
-
68
- return data$;
69
- }
70
- }
@@ -1,83 +0,0 @@
1
- import { distinctUntilChanged, map, type ObservableInput, pairwise } from 'rxjs';
2
- import type { IAnalyticsCollector } from './AnalyticsCollector.interface.js';
3
- import type { IContextProvider } from '@equinor/fusion-framework-module-context';
4
- import type { AppModuleProvider } from '@equinor/fusion-framework-module-app';
5
- import { z } from 'zod';
6
- import { BaseCollector, createSchema } from './create-schema.js';
7
- import { type ContextItemType, contextSchema } from './utils/context-schema.js';
8
- import { extractContextMetadata } from './utils/extract-context-metadata.js';
9
-
10
- /** Zod schema for the `context-selected` event (value + attributes). */
11
- const eventSchema = createSchema(
12
- contextSchema,
13
- z.object({ previous: contextSchema, appKey: z.string().optional() }),
14
- );
15
-
16
- /**
17
- * Collector that emits an analytics event whenever the active Fusion context changes.
18
- *
19
- * @remarks
20
- * Listens to `IContextProvider.currentContext$`, de-duplicates by context ID,
21
- * pairs consecutive values, and emits both the new and previous context
22
- * metadata. The current application key is included in attributes.
23
- *
24
- * Register via:
25
- * ```ts
26
- * builder.setCollector('context-selected', async (args) => {
27
- * const ctx = await args.requireInstance('context');
28
- * const app = await args.requireInstance('app');
29
- * return new ContextSelectedCollector(ctx, app);
30
- * });
31
- * ```
32
- */
33
- export class ContextSelectedCollector
34
- extends BaseCollector<ContextItemType, { previous?: ContextItemType; appKey?: string }>
35
- implements IAnalyticsCollector
36
- {
37
- #contextProvider: IContextProvider;
38
- #appProvider: AppModuleProvider;
39
-
40
- /**
41
- * @param contextProvider - Fusion context module provider to observe.
42
- * @param appProvider - Fusion app module provider for the current app key.
43
- */
44
- constructor(contextProvider: IContextProvider, appProvider: AppModuleProvider) {
45
- super('context-selected', eventSchema);
46
- this.#contextProvider = contextProvider;
47
- this.#appProvider = appProvider;
48
- }
49
-
50
- /**
51
- * Builds an observable that emits an analytics event each time the selected
52
- * context changes, including the previous context and current app key.
53
- *
54
- * @returns An observable input emitting the newly selected context's value and attributes.
55
- */
56
- _initialize(): ObservableInput<{
57
- value: ContextItemType;
58
- attributes: { previous?: ContextItemType; appKey?: string };
59
- }> {
60
- // Track context changes, pairing each new context with the previously selected one
61
- const contextSelected$ = this.#contextProvider.currentContext$.pipe(
62
- // Only emit when an actual change has happened.
63
- distinctUntilChanged((prev, curr) => prev?.id === curr?.id),
64
- // Provide both the old and the new value.
65
- pairwise(),
66
- );
67
-
68
- // Map the [previous, next] pair into the analytics event shape
69
- const data$ = contextSelected$.pipe(
70
- map(([prev, next]) => {
71
- return {
72
- value: next && extractContextMetadata(next),
73
- attributes: {
74
- previous: prev && extractContextMetadata(prev),
75
- appKey: this.#appProvider.current?.appKey,
76
- },
77
- };
78
- }),
79
- );
80
-
81
- return data$;
82
- }
83
- }
@@ -1,123 +0,0 @@
1
- import type { AnyValue, AnyValueMap, AnalyticsEvent } from '../types.js';
2
- import type { IAnalyticsCollector } from './AnalyticsCollector.interface.js';
3
-
4
- import { from, map, type ObservableInput, Subject, type Observer, type Unsubscribable } from 'rxjs';
5
- import { z } from 'zod';
6
-
7
- /**
8
- * Creates a Zod schema for validating {@link AnalyticsEvent} instances with
9
- * typed value and attributes.
10
- *
11
- * @template TValue - Schema type for the event body.
12
- * @template TAttr - Schema type for the event attributes.
13
- * @param value - Zod schema validating the event body.
14
- * @param attributes - Zod schema validating the event attributes.
15
- * @returns A Zod object schema matching `{ name, value, attributes? }`.
16
- */
17
- export const createSchema = <TValue = AnyValue, TAttr = AnyValueMap>(
18
- value: z.ZodSchema<TValue>,
19
- attributes: z.ZodSchema<TAttr>,
20
- ) => {
21
- return z.object({
22
- name: z.string(),
23
- value: value,
24
- attributes: attributes.optional(),
25
- });
26
- };
27
-
28
- /** Inferred Zod schema type produced by {@link createSchema}. */
29
- export type CollectorSchema<TValue = AnyValue, TAttr = AnyValueMap> = ReturnType<
30
- typeof createSchema<TValue, TAttr>
31
- >;
32
-
33
- /**
34
- * Abstract base class for analytics collectors that validates events against a
35
- * Zod schema before emitting.
36
- *
37
- * @remarks
38
- * Subclasses implement {@link BaseCollector._initialize | _initialize} to return
39
- * an observable of raw `{ value, attributes }` objects. `BaseCollector` wraps
40
- * each emission with the collector name, validates it against the supplied schema,
41
- * and publishes through an internal `Subject`.
42
- *
43
- * @template TValue - The event body type.
44
- * @template TAttr - The event attributes type.
45
- *
46
- * @example
47
- * ```ts
48
- * class MyCollector extends BaseCollector<string, { page: string }> {
49
- * constructor() {
50
- * super('my-event', createSchema(z.string(), z.object({ page: z.string() })));
51
- * }
52
- * _initialize() {
53
- * return of({ value: 'hello', attributes: { page: '/home' } });
54
- * }
55
- * }
56
- * ```
57
- */
58
- export abstract class BaseCollector<
59
- TValue extends AnyValue,
60
- TAttr extends AnyValueMap = AnyValueMap,
61
- > implements IAnalyticsCollector<AnalyticsEvent<TValue, TAttr>>
62
- {
63
- #schema: CollectorSchema<TValue, TAttr>;
64
- #name: string;
65
- #subject: Subject<z.infer<CollectorSchema<TValue, TAttr>>> = new Subject<
66
- z.infer<CollectorSchema<TValue, TAttr>>
67
- >();
68
-
69
- /**
70
- * Creates a new `BaseCollector`.
71
- *
72
- * @param name - Event name assigned to every emission (e.g. `'context-selected'`).
73
- * @param schema - Zod schema used to validate each event before publishing.
74
- */
75
- constructor(name: string, schema: CollectorSchema<TValue, TAttr>) {
76
- this.#name = name;
77
- this.#schema = schema;
78
- }
79
-
80
- /**
81
- * Returns an observable source of raw `{ value, attributes }` objects.
82
- *
83
- * @remarks
84
- * Subclasses implement this to provide the domain-specific event stream.
85
- * The base class wraps the output with the collector name and validates it.
86
- *
87
- * @returns An observable input of value/attribute pairs.
88
- */
89
- abstract _initialize(): ObservableInput<{ value: TValue; attributes: TAttr }>;
90
-
91
- /**
92
- * Subscribes to the source returned by {@link BaseCollector._initialize},
93
- * validates each emission with the Zod schema, and publishes through the
94
- * internal subject.
95
- *
96
- * @returns Resolves once the source has been subscribed to.
97
- */
98
- initialize(): Promise<void> | void {
99
- // Shape each emission into the named/validated event before publishing
100
- from(this._initialize())
101
- .pipe(
102
- map(({ value, attributes }) => {
103
- return {
104
- name: this.#name,
105
- value: value,
106
- attributes: attributes,
107
- };
108
- }),
109
- map((x) => this.#schema.parse(x)),
110
- )
111
- .subscribe(this.#subject);
112
- }
113
-
114
- /**
115
- * Subscribes an observer to the validated event stream.
116
- *
117
- * @param observer - Partial observer receiving validated events.
118
- * @returns An `Unsubscribable` handle.
119
- */
120
- subscribe(observer: Partial<Observer<z.infer<CollectorSchema<TValue, TAttr>>>>): Unsubscribable {
121
- return this.#subject.subscribe(observer);
122
- }
123
- }
@@ -1,12 +0,0 @@
1
- /**
2
- * Re-exports for built-in analytics collectors.
3
- *
4
- * @remarks
5
- * Import from `'@equinor/fusion-framework-module-analytics/collectors'`.
6
- *
7
- * @packageDocumentation
8
- */
9
- export { IAnalyticsCollector } from './AnalyticsCollector.interface.js';
10
- export { ContextSelectedCollector } from './ContextSelectedCollector.js';
11
- export { AppSelectedCollector } from './AppSelectedCollector.js';
12
- export { AppLoadedCollector } from './AppLoadedCollector.js';