@equinor/fusion-framework-module-context 8.0.3 → 9.0.0-next.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 +76 -0
- package/README.md +78 -2
- package/dist/esm/ContextModuleConfig.js +2 -0
- package/dist/esm/ContextModuleConfig.js.map +1 -0
- package/dist/esm/ContextModuleConfigurator.interface.js +2 -0
- package/dist/esm/ContextModuleConfigurator.interface.js.map +1 -0
- package/dist/esm/ContextModuleConfigurator.js +211 -0
- package/dist/esm/ContextModuleConfigurator.js.map +1 -0
- package/dist/esm/ContextProvider.js +1 -1
- package/dist/esm/ContextProvider.js.map +1 -1
- package/dist/esm/__tests__/ContextModuleConfigurator.test.js +282 -0
- package/dist/esm/__tests__/ContextModuleConfigurator.test.js.map +1 -0
- package/dist/esm/__tests__/mock/context-mock.test.js +101 -0
- package/dist/esm/__tests__/mock/context-mock.test.js.map +1 -0
- package/dist/esm/__tests__/mock/create-context-item-factory.test.js +45 -0
- package/dist/esm/__tests__/mock/create-context-item-factory.test.js.map +1 -0
- package/dist/esm/__tests__/mock/create-context-items.test.js +46 -0
- package/dist/esm/__tests__/mock/create-context-items.test.js.map +1 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/mock/ContextMockConfigurator.js +206 -0
- package/dist/esm/mock/ContextMockConfigurator.js.map +1 -0
- package/dist/esm/mock/fixtures/create-context-item-factory.js +50 -0
- package/dist/esm/mock/fixtures/create-context-item-factory.js.map +1 -0
- package/dist/esm/mock/fixtures/create-context-items.js +53 -0
- package/dist/esm/mock/fixtures/create-context-items.js.map +1 -0
- package/dist/esm/mock/fixtures/index.js +22 -0
- package/dist/esm/mock/fixtures/index.js.map +1 -0
- package/dist/esm/mock/fixtures/string-to-seed.js +15 -0
- package/dist/esm/mock/fixtures/string-to-seed.js.map +1 -0
- package/dist/esm/mock/index.js +31 -0
- package/dist/esm/mock/index.js.map +1 -0
- package/dist/esm/mock/module.js +41 -0
- package/dist/esm/mock/module.js.map +1 -0
- package/dist/esm/module.js +35 -6
- package/dist/esm/module.js.map +1 -1
- package/dist/esm/{get-context-selector.js → selectors/get-context-selector.js} +1 -4
- package/dist/esm/selectors/get-context-selector.js.map +1 -0
- package/dist/esm/selectors/index.js +13 -0
- package/dist/esm/selectors/index.js.map +1 -0
- package/dist/esm/{query-context-selector.js → selectors/query-context-selector.js} +1 -1
- package/dist/esm/selectors/query-context-selector.js.map +1 -0
- package/dist/esm/{related-context-selector.js → selectors/related-context-selector.js} +1 -1
- package/dist/esm/selectors/related-context-selector.js.map +1 -0
- package/dist/esm/utils/enable-context.js +1 -1
- package/dist/esm/utils/enable-context.js.map +1 -1
- package/dist/esm/utils/index.js +2 -0
- package/dist/esm/utils/index.js.map +1 -1
- package/dist/esm/utils/parse-context-item.js.map +1 -0
- package/dist/esm/utils/resolve-initial-context.js +3 -1
- package/dist/esm/utils/resolve-initial-context.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/esm/version.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/{configurator.d.ts → ContextModuleConfig.d.ts} +4 -64
- package/dist/types/ContextModuleConfigurator.d.ts +97 -0
- package/dist/types/{ContextConfigBuilder.d.ts → ContextModuleConfigurator.interface.d.ts} +27 -47
- package/dist/types/ContextProvider.d.ts +1 -1
- package/dist/types/__tests__/ContextModuleConfigurator.test.d.ts +1 -0
- package/dist/types/__tests__/mock/context-mock.test.d.ts +1 -0
- package/dist/types/__tests__/mock/create-context-item-factory.test.d.ts +1 -0
- package/dist/types/__tests__/mock/create-context-items.test.d.ts +1 -0
- package/dist/types/index.d.ts +3 -1
- package/dist/types/mock/ContextMockConfigurator.d.ts +155 -0
- package/dist/types/mock/fixtures/create-context-item-factory.d.ts +40 -0
- package/dist/types/mock/fixtures/create-context-items.d.ts +59 -0
- package/dist/types/mock/fixtures/index.d.ts +21 -0
- package/dist/types/mock/fixtures/string-to-seed.d.ts +12 -0
- package/dist/types/mock/index.d.ts +30 -0
- package/dist/types/mock/module.d.ts +38 -0
- package/dist/types/module.d.ts +5 -3
- package/dist/types/{get-context-selector.d.ts → selectors/get-context-selector.d.ts} +1 -3
- package/dist/types/selectors/index.d.ts +12 -0
- package/dist/types/{query-context-selector.d.ts → selectors/query-context-selector.d.ts} +1 -1
- package/dist/types/{related-context-selector.d.ts → selectors/related-context-selector.d.ts} +1 -1
- package/dist/types/utils/enable-context.d.ts +4 -5
- package/dist/types/utils/index.d.ts +2 -0
- package/dist/types/{parse-context-item.d.ts → utils/parse-context-item.d.ts} +1 -1
- package/dist/types/utils/resolve-initial-context.d.ts +1 -1
- package/dist/types/version.d.ts +1 -1
- package/docs/data-model.md +134 -0
- package/docs/lifecycle.md +163 -0
- package/docs/recipes.md +89 -0
- package/package.json +36 -8
- package/src/ContextModuleConfig.ts +147 -0
- package/src/ContextModuleConfigurator.interface.ts +145 -0
- package/src/ContextModuleConfigurator.ts +295 -0
- package/src/ContextProvider.ts +1 -2
- package/src/__tests__/ContextModuleConfigurator.test.ts +412 -0
- package/src/__tests__/mock/context-mock.test.ts +137 -0
- package/src/__tests__/mock/create-context-item-factory.test.ts +60 -0
- package/src/__tests__/mock/create-context-items.test.ts +58 -0
- package/src/index.ts +3 -5
- package/src/mock/ContextMockConfigurator.ts +244 -0
- package/src/mock/fixtures/create-context-item-factory.ts +62 -0
- package/src/mock/fixtures/create-context-items.ts +80 -0
- package/src/mock/fixtures/index.ts +28 -0
- package/src/mock/fixtures/string-to-seed.ts +18 -0
- package/src/mock/index.ts +33 -0
- package/src/mock/module.ts +54 -0
- package/src/module.ts +45 -17
- package/src/{get-context-selector.ts → selectors/get-context-selector.ts} +2 -6
- package/src/selectors/index.ts +12 -0
- package/src/{query-context-selector.ts → selectors/query-context-selector.ts} +2 -2
- package/src/{related-context-selector.ts → selectors/related-context-selector.ts} +2 -2
- package/src/utils/enable-context.ts +6 -10
- package/src/utils/index.ts +2 -0
- package/src/{parse-context-item.ts → utils/parse-context-item.ts} +1 -1
- package/src/utils/resolve-initial-context.ts +5 -2
- package/src/version.ts +1 -1
- package/tsconfig.json +6 -0
- package/vitest.config.ts +11 -0
- package/dist/esm/ContextConfigBuilder.js +0 -175
- package/dist/esm/ContextConfigBuilder.js.map +0 -1
- package/dist/esm/configurator.js +0 -104
- package/dist/esm/configurator.js.map +0 -1
- package/dist/esm/get-context-selector.js.map +0 -1
- package/dist/esm/parse-context-item.js.map +0 -1
- package/dist/esm/query-context-selector.js.map +0 -1
- package/dist/esm/related-context-selector.js.map +0 -1
- package/src/ContextConfigBuilder.ts +0 -258
- package/src/configurator.ts +0 -300
- /package/dist/esm/{parse-context-item.js → utils/parse-context-item.js} +0 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import type { AnyModuleInstance, ModuleInstance } from '@equinor/fusion-framework-module';
|
|
2
|
+
import type { ObservableInput } from 'rxjs';
|
|
3
|
+
import type { QueryCtorOptions } from '@equinor/fusion-query';
|
|
4
|
+
|
|
5
|
+
import type {
|
|
6
|
+
ContextFilterFn,
|
|
7
|
+
ContextItem,
|
|
8
|
+
QueryContextParameters,
|
|
9
|
+
RelatedContextParameters,
|
|
10
|
+
} from './types';
|
|
11
|
+
import type { GetContextParameters } from './client/ContextClient';
|
|
12
|
+
import type { IContextProvider } from './ContextProvider';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Resolved configuration for the context module.
|
|
16
|
+
*
|
|
17
|
+
* Holds query clients, type filters, parent-connection settings, and
|
|
18
|
+
* optional callbacks for validation, resolution, and path integration.
|
|
19
|
+
* Produced by {@link ContextModuleConfigurator.createConfigAsync} after all
|
|
20
|
+
* registered config builders have run.
|
|
21
|
+
*
|
|
22
|
+
* @see IContextModuleConfigurator — fluent API for populating this config.
|
|
23
|
+
* @see ContextProvider — runtime consumer of this config.
|
|
24
|
+
*/
|
|
25
|
+
export interface ContextModuleConfig {
|
|
26
|
+
/**
|
|
27
|
+
* Query client options used to fetch, search, and resolve related context items.
|
|
28
|
+
*
|
|
29
|
+
* - `get` — retrieves a single context item by ID.
|
|
30
|
+
* - `query` — searches context items by text and optional type filter.
|
|
31
|
+
* - `related` — fetches context items related to a given item (used during resolution).
|
|
32
|
+
*/
|
|
33
|
+
client: {
|
|
34
|
+
get: QueryCtorOptions<ContextItem, GetContextParameters>;
|
|
35
|
+
query: QueryCtorOptions<ContextItem[], QueryContextParameters>;
|
|
36
|
+
related?: QueryCtorOptions<ContextItem[], RelatedContextParameters>;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Allowed context type IDs (e.g. `['ProjectMaster', 'Facility']`).
|
|
41
|
+
*
|
|
42
|
+
* When set, {@link ContextProvider.validateContext} only accepts items
|
|
43
|
+
* whose `type.id` matches one of these values (case-insensitive).
|
|
44
|
+
*/
|
|
45
|
+
contextType?: string[];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Optional post-query filter applied to the result set returned by
|
|
49
|
+
* {@link ContextProvider.queryContext}.
|
|
50
|
+
*/
|
|
51
|
+
contextFilter?: ContextFilterFn;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Whether to connect the context module to a parent context module.
|
|
55
|
+
*
|
|
56
|
+
* When `true` (the default), the provider subscribes to the parent's
|
|
57
|
+
* `currentContext$` and mirrors changes into its own state.
|
|
58
|
+
*
|
|
59
|
+
* @defaultValue `true`
|
|
60
|
+
*/
|
|
61
|
+
connectParentContext?: boolean;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* When `true`, skips resolving an initial context from the path or parent
|
|
65
|
+
* during module post-initialization.
|
|
66
|
+
*/
|
|
67
|
+
skipInitialContext?: boolean;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Extracts a context ID from a URL path segment.
|
|
71
|
+
*
|
|
72
|
+
* Used during initial context resolution and deep-link support.
|
|
73
|
+
* If not provided, the default GUID-based extractor is used.
|
|
74
|
+
*
|
|
75
|
+
* @param path - The URL path to inspect.
|
|
76
|
+
* @returns The extracted context ID, or `undefined` if none is found.
|
|
77
|
+
*/
|
|
78
|
+
extractContextIdFromPath?: (path: string) => string | undefined;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Generates a URL path that embeds the given context item's ID.
|
|
82
|
+
*
|
|
83
|
+
* Used by navigation integrations to update the browser URL when
|
|
84
|
+
* the context changes.
|
|
85
|
+
*
|
|
86
|
+
* @param context - The active context item.
|
|
87
|
+
* @param path - The current URL path.
|
|
88
|
+
* @returns The updated path, or `undefined` to leave it unchanged.
|
|
89
|
+
*/
|
|
90
|
+
generatePathFromContext?: (context: ContextItem, path: string) => string | undefined;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Transforms a user search string and the configured context type into
|
|
94
|
+
* the query parameters sent to the context API.
|
|
95
|
+
*
|
|
96
|
+
* Override this to customise how free-text searches are mapped to the
|
|
97
|
+
* backend query contract.
|
|
98
|
+
*/
|
|
99
|
+
contextParameterFn?: (args: {
|
|
100
|
+
search: string;
|
|
101
|
+
type: ContextModuleConfig['contextType'];
|
|
102
|
+
}) => string | QueryContextParameters;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Custom context resolution strategy.
|
|
106
|
+
*
|
|
107
|
+
* Called with `this` bound to the {@link IContextProvider} when a context
|
|
108
|
+
* item fails validation and the caller requests resolution.
|
|
109
|
+
*
|
|
110
|
+
* @param item - The context item to resolve, or `null`.
|
|
111
|
+
* @returns An observable emitting the resolved context item.
|
|
112
|
+
*/
|
|
113
|
+
resolveContext?: (
|
|
114
|
+
this: IContextProvider,
|
|
115
|
+
item: ContextItem | null,
|
|
116
|
+
) => ReturnType<IContextProvider['resolveContext']>;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Custom context validation strategy.
|
|
120
|
+
*
|
|
121
|
+
* Called with `this` bound to the {@link IContextProvider} to decide
|
|
122
|
+
* whether a candidate context item is acceptable.
|
|
123
|
+
*
|
|
124
|
+
* @param item - The context item to validate, or `null`.
|
|
125
|
+
* @returns `true` if the item is valid.
|
|
126
|
+
*/
|
|
127
|
+
validateContext?: (
|
|
128
|
+
this: IContextProvider,
|
|
129
|
+
item: ContextItem | null,
|
|
130
|
+
) => ReturnType<IContextProvider['validateContext']>;
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Resolves the initial context during module post-initialization.
|
|
134
|
+
*
|
|
135
|
+
* The default implementation tries to extract a context ID from the
|
|
136
|
+
* current navigation path, falling back to the parent provider's context.
|
|
137
|
+
*
|
|
138
|
+
* @param args - Module reference and instance map.
|
|
139
|
+
* @returns An observable input emitting the initial context item, or void.
|
|
140
|
+
*/
|
|
141
|
+
resolveInitialContext?: (args: {
|
|
142
|
+
// biome-ignore lint/suspicious/noExplicitAny: `AnyModuleInstance | any` intentionally widens to accept any module instance shape for `ref`
|
|
143
|
+
ref?: AnyModuleInstance | any;
|
|
144
|
+
modules: ModuleInstance;
|
|
145
|
+
// biome-ignore lint/suspicious/noConfusingVoidType: `void` here relies on TypeScript's special-cased "void-returning callback accepts any return value" behavior — `undefined` would break assignability of resolver functions that only conditionally emit a `ContextItem`
|
|
146
|
+
}) => ObservableInput<ContextItem | void>;
|
|
147
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { Modules, ModuleType } from '@equinor/fusion-framework-module';
|
|
2
|
+
import type { QueryCtorOptions, QueryFn } from '@equinor/fusion-query';
|
|
3
|
+
|
|
4
|
+
import type { ContextModuleConfig } from './ContextModuleConfig';
|
|
5
|
+
import type { ContextItem, QueryContextParameters, RelatedContextParameters } from './types';
|
|
6
|
+
import type { GetContextParameters } from './client/ContextClient';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Callback passed to {@link IContextModuleConfigurator.addConfigBuilder}.
|
|
10
|
+
*
|
|
11
|
+
* Receives the {@link IContextModuleConfigurator} itself and may use its
|
|
12
|
+
* setter methods to populate the context module configuration. The
|
|
13
|
+
* callback may be async.
|
|
14
|
+
*/
|
|
15
|
+
export type ContextConfigBuilderCallback = (
|
|
16
|
+
builder: IContextModuleConfigurator,
|
|
17
|
+
) => void | Promise<void>;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Public configurator contract for the context module.
|
|
21
|
+
*
|
|
22
|
+
* Consumers call {@link addConfigBuilder} to register one or more
|
|
23
|
+
* {@link ContextConfigBuilderCallback} functions that will run during
|
|
24
|
+
* module initialization to populate the {@link ContextModuleConfig}, using
|
|
25
|
+
* the fluent setter methods declared below.
|
|
26
|
+
*/
|
|
27
|
+
export interface IContextModuleConfigurator {
|
|
28
|
+
/**
|
|
29
|
+
* Registers a configuration callback that receives this configurator.
|
|
30
|
+
*
|
|
31
|
+
* Multiple builders can be added; they execute sequentially against the
|
|
32
|
+
* same configurator instance, so later calls win when they touch the
|
|
33
|
+
* same field.
|
|
34
|
+
*
|
|
35
|
+
* @param init - Builder callback invoked during module initialization.
|
|
36
|
+
*/
|
|
37
|
+
addConfigBuilder: (init: ContextConfigBuilderCallback) => void;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Requires a module instance by its registered key or name.
|
|
41
|
+
*
|
|
42
|
+
* Only resolvable from within a {@link ContextConfigBuilderCallback} —
|
|
43
|
+
* throws if called before module initialization has started.
|
|
44
|
+
*
|
|
45
|
+
* @param module - The key or name of the module to resolve.
|
|
46
|
+
* @returns A promise that resolves to the requested module instance.
|
|
47
|
+
*/
|
|
48
|
+
requireInstance<TKey extends string = Extract<keyof Modules, string>>(
|
|
49
|
+
module: TKey,
|
|
50
|
+
): Promise<ModuleType<Modules[TKey]>>;
|
|
51
|
+
requireInstance<T>(module: string): Promise<T>;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Sets the context type for the current configuration.
|
|
55
|
+
*
|
|
56
|
+
* @param type - The context type to assign, as defined by `ContextModuleConfig['contextType']`.
|
|
57
|
+
*/
|
|
58
|
+
setContextType(type: ContextModuleConfig['contextType']): void;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Sets the context filter function for the configuration.
|
|
62
|
+
*
|
|
63
|
+
* @param filter - A function that determines whether a context should be included, as defined by `ContextModuleConfig['contextFilter']`.
|
|
64
|
+
*/
|
|
65
|
+
setContextFilter(filter: ContextModuleConfig['contextFilter']): void;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Sets the function or configuration used to connect to a parent context.
|
|
69
|
+
*
|
|
70
|
+
* @param connect - The function or configuration that defines how to connect to the parent context.
|
|
71
|
+
*/
|
|
72
|
+
connectParentContext(connect: ContextModuleConfig['connectParentContext']): void;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Sets the function used to provide context parameters for the module configuration.
|
|
76
|
+
*
|
|
77
|
+
* @param fn - A function conforming to the `contextParameterFn` type defined in `ContextModuleConfig`.
|
|
78
|
+
*/
|
|
79
|
+
setContextParameterFn(fn: ContextModuleConfig['contextParameterFn']): void;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Sets the function used to validate the context within the configuration.
|
|
83
|
+
*
|
|
84
|
+
* @param fn - A function that implements the `validateContext` signature from `ContextModuleConfig`.
|
|
85
|
+
*/
|
|
86
|
+
setValidateContext(fn: ContextModuleConfig['validateContext']): void;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Sets the function used to resolve the context for the module configuration.
|
|
90
|
+
*
|
|
91
|
+
* @param fn - A function that defines how the context should be resolved, conforming to the `resolveContext` type from `ContextModuleConfig`.
|
|
92
|
+
*/
|
|
93
|
+
setResolveContext(fn: ContextModuleConfig['resolveContext']): void;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Sets the function responsible for extracting the context ID from a given path.
|
|
97
|
+
*
|
|
98
|
+
* @param fn - A function that defines how to extract the context ID from a path.
|
|
99
|
+
* This function should match the type defined in `ContextModuleConfig['extractContextIdFromPath']`.
|
|
100
|
+
*/
|
|
101
|
+
setContextPathExtractor(fn: ContextModuleConfig['extractContextIdFromPath']): void;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Sets the function responsible for generating a path from the context.
|
|
105
|
+
*
|
|
106
|
+
* @param fn - A function that takes a context and generates a corresponding path.
|
|
107
|
+
*/
|
|
108
|
+
setContextPathGenerator(fn: ContextModuleConfig['generatePathFromContext']): void;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Sets the function used to resolve the initial context during module post-initialization.
|
|
112
|
+
*
|
|
113
|
+
* @param fn - A function that returns an observable input emitting the initial context item.
|
|
114
|
+
* The default resolver extracts a context ID from the navigation path, falling
|
|
115
|
+
* back to the parent provider's current context.
|
|
116
|
+
*/
|
|
117
|
+
setResolveInitialContext(fn: ContextModuleConfig['resolveInitialContext']): void;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Sets the context client configuration for fetching context items.
|
|
121
|
+
*
|
|
122
|
+
* This method allows you to provide custom query functions or query constructor options
|
|
123
|
+
* for retrieving single context items (`get`), querying multiple context items (`query`),
|
|
124
|
+
* and optionally fetching related context items (`related`). Each query can be provided
|
|
125
|
+
* as either a function or a configuration object. The expiration time for cached results
|
|
126
|
+
* can also be specified.
|
|
127
|
+
*
|
|
128
|
+
* @param client - An object containing the query functions or options for `get`, `query`, and optionally `related` context items.
|
|
129
|
+
* @param expire - Optional. The expiration time (in milliseconds) for cached query results. Defaults to 1 minute.
|
|
130
|
+
*/
|
|
131
|
+
setContextClient(
|
|
132
|
+
client: {
|
|
133
|
+
get:
|
|
134
|
+
| QueryFn<ContextItem, GetContextParameters>
|
|
135
|
+
| QueryCtorOptions<ContextItem, GetContextParameters>;
|
|
136
|
+
query:
|
|
137
|
+
| QueryFn<ContextItem[], QueryContextParameters>
|
|
138
|
+
| QueryCtorOptions<ContextItem[], QueryContextParameters>;
|
|
139
|
+
related?:
|
|
140
|
+
| QueryFn<ContextItem[], RelatedContextParameters>
|
|
141
|
+
| QueryCtorOptions<ContextItem[], RelatedContextParameters>;
|
|
142
|
+
},
|
|
143
|
+
expire?: number,
|
|
144
|
+
): void;
|
|
145
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
import { from, lastValueFrom } from 'rxjs';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
BaseConfigBuilder,
|
|
5
|
+
type ModuleInitializerArgs,
|
|
6
|
+
type ModulesInstanceType,
|
|
7
|
+
type Modules,
|
|
8
|
+
type ModuleType,
|
|
9
|
+
} from '@equinor/fusion-framework-module';
|
|
10
|
+
import type { ServicesModule, IApiProvider } from '@equinor/fusion-framework-module-services';
|
|
11
|
+
import type { NavigationModule } from '@equinor/fusion-framework-module-navigation';
|
|
12
|
+
import { getContextSelector, queryContextSelector, relatedContextSelector } from './selectors';
|
|
13
|
+
import type { QueryCtorOptions, QueryFn } from '@equinor/fusion-query';
|
|
14
|
+
import type { ContextItem, QueryContextParameters, RelatedContextParameters } from './types';
|
|
15
|
+
import type { GetContextParameters } from './client/ContextClient';
|
|
16
|
+
import resolveInitialContext from './utils/resolve-initial-context';
|
|
17
|
+
import type { ContextModuleConfig } from './ContextModuleConfig';
|
|
18
|
+
import type {
|
|
19
|
+
ContextConfigBuilderCallback,
|
|
20
|
+
IContextModuleConfigurator,
|
|
21
|
+
} from './ContextModuleConfigurator.interface';
|
|
22
|
+
|
|
23
|
+
export type { ContextModuleConfig } from './ContextModuleConfig';
|
|
24
|
+
export type {
|
|
25
|
+
ContextConfigBuilderCallback,
|
|
26
|
+
IContextModuleConfigurator,
|
|
27
|
+
} from './ContextModuleConfigurator.interface';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Default implementation of {@link IContextModuleConfigurator}.
|
|
31
|
+
*
|
|
32
|
+
* Collects {@link ContextConfigBuilderCallback} registrations and, when
|
|
33
|
+
* {@link createConfigAsync} is called, runs them in order — each callback
|
|
34
|
+
* receives this configurator itself and populates the config through its
|
|
35
|
+
* setter methods, which register into {@link BaseConfigBuilder._set} — to
|
|
36
|
+
* produce the final {@link ContextModuleConfig}.
|
|
37
|
+
*
|
|
38
|
+
* If no custom client is configured, the configurator falls back to
|
|
39
|
+
* creating one from the {@link ServicesModule} API provider.
|
|
40
|
+
*
|
|
41
|
+
* @remarks
|
|
42
|
+
* Extends {@link BaseConfigBuilder} and fully reuses its `_set`/`_buildConfig`
|
|
43
|
+
* machinery. Setters always pass an `async () => value` callback to `_set`,
|
|
44
|
+
* never the raw value directly — `_set` decides whether it received a value
|
|
45
|
+
* or a deferred callback by checking `typeof value_or_cb === 'function'`, so
|
|
46
|
+
* fields that are themselves functions (e.g. `validateContext`) would
|
|
47
|
+
* otherwise be misread as callbacks and invoked instead of stored.
|
|
48
|
+
*/
|
|
49
|
+
export class ContextModuleConfigurator
|
|
50
|
+
extends BaseConfigBuilder<ContextModuleConfig>
|
|
51
|
+
implements IContextModuleConfigurator
|
|
52
|
+
{
|
|
53
|
+
/** Default cache TTL (in ms) for context query results. */
|
|
54
|
+
defaultExpireTime = 1 * 60 * 1000;
|
|
55
|
+
|
|
56
|
+
#configBuilders: Array<ContextConfigBuilderCallback> = [];
|
|
57
|
+
#init?: ModuleInitializerArgs<IContextModuleConfigurator, [ServicesModule, NavigationModule]>;
|
|
58
|
+
|
|
59
|
+
/** @inheritdoc */
|
|
60
|
+
addConfigBuilder(init: ContextConfigBuilderCallback): void {
|
|
61
|
+
this.#configBuilders.push(init);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** @inheritdoc */
|
|
65
|
+
requireInstance<TKey extends string = Extract<keyof Modules, string>>(
|
|
66
|
+
module: TKey,
|
|
67
|
+
): Promise<ModuleType<Modules[TKey]>>;
|
|
68
|
+
/** @inheritdoc */
|
|
69
|
+
requireInstance<T>(module: string): Promise<T>;
|
|
70
|
+
/** @inheritdoc */
|
|
71
|
+
requireInstance(
|
|
72
|
+
module: string,
|
|
73
|
+
// biome-ignore lint/suspicious/noExplicitAny: implementation signature must satisfy both overloads above (`Promise<ModuleType<...>>` and `Promise<T>`); `unknown` is not assignable to the generic `Promise<T>` overload
|
|
74
|
+
): Promise<any> {
|
|
75
|
+
// requireInstance is only meaningful once module initialization has begun and #init is set
|
|
76
|
+
if (!this.#init) {
|
|
77
|
+
throw Error('requireInstance can only be called during module configuration');
|
|
78
|
+
}
|
|
79
|
+
// #init's requireInstance is narrowed to this module's own declared deps; widen for the public any-module overload above
|
|
80
|
+
return this.#init.requireInstance(module as never);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** @inheritdoc */
|
|
84
|
+
setContextType(type: ContextModuleConfig['contextType']): void {
|
|
85
|
+
this._set('contextType', async () => type);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** @inheritdoc */
|
|
89
|
+
setContextFilter(filter: ContextModuleConfig['contextFilter']): void {
|
|
90
|
+
this._set('contextFilter', async () => filter);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** @inheritdoc */
|
|
94
|
+
connectParentContext(connect: ContextModuleConfig['connectParentContext']): void {
|
|
95
|
+
this._set('connectParentContext', async () => connect);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** @inheritdoc */
|
|
99
|
+
setContextParameterFn(fn: ContextModuleConfig['contextParameterFn']): void {
|
|
100
|
+
this._set('contextParameterFn', async () => fn);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** @inheritdoc */
|
|
104
|
+
setValidateContext(fn: ContextModuleConfig['validateContext']): void {
|
|
105
|
+
this._set('validateContext', async () => fn);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** @inheritdoc */
|
|
109
|
+
setResolveContext(fn: ContextModuleConfig['resolveContext']): void {
|
|
110
|
+
this._set('resolveContext', async () => fn);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** @inheritdoc */
|
|
114
|
+
setContextPathExtractor(fn: ContextModuleConfig['extractContextIdFromPath']): void {
|
|
115
|
+
this._set('extractContextIdFromPath', async () => fn);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** @inheritdoc */
|
|
119
|
+
setContextPathGenerator(fn: ContextModuleConfig['generatePathFromContext']): void {
|
|
120
|
+
this._set('generatePathFromContext', async () => fn);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** @inheritdoc */
|
|
124
|
+
setResolveInitialContext(fn: ContextModuleConfig['resolveInitialContext']): void {
|
|
125
|
+
this._set('resolveInitialContext', async () => fn);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** @inheritdoc */
|
|
129
|
+
setContextClient(
|
|
130
|
+
client: {
|
|
131
|
+
get:
|
|
132
|
+
| QueryFn<ContextItem, GetContextParameters>
|
|
133
|
+
| QueryCtorOptions<ContextItem, GetContextParameters>;
|
|
134
|
+
query:
|
|
135
|
+
| QueryFn<ContextItem[], QueryContextParameters>
|
|
136
|
+
| QueryCtorOptions<ContextItem[], QueryContextParameters>;
|
|
137
|
+
related?:
|
|
138
|
+
| QueryFn<ContextItem[], RelatedContextParameters>
|
|
139
|
+
| QueryCtorOptions<ContextItem[], RelatedContextParameters>;
|
|
140
|
+
},
|
|
141
|
+
expire = 1 * 60 * 1000,
|
|
142
|
+
): void {
|
|
143
|
+
const clientConfig: ContextModuleConfig['client'] = {
|
|
144
|
+
get:
|
|
145
|
+
typeof client.get === 'function'
|
|
146
|
+
? {
|
|
147
|
+
key: ({ id }) => id,
|
|
148
|
+
client: {
|
|
149
|
+
fn: client.get,
|
|
150
|
+
},
|
|
151
|
+
expire,
|
|
152
|
+
}
|
|
153
|
+
: client.get,
|
|
154
|
+
query:
|
|
155
|
+
typeof client.query === 'function'
|
|
156
|
+
? {
|
|
157
|
+
// TODO(#5118) - might cast to checksum
|
|
158
|
+
key: (args) => JSON.stringify(args),
|
|
159
|
+
client: {
|
|
160
|
+
fn: client.query,
|
|
161
|
+
},
|
|
162
|
+
expire,
|
|
163
|
+
}
|
|
164
|
+
: client.query,
|
|
165
|
+
};
|
|
166
|
+
// only override the related-context client config if one was provided
|
|
167
|
+
if (client.related) {
|
|
168
|
+
clientConfig.related =
|
|
169
|
+
typeof client.related === 'function'
|
|
170
|
+
? {
|
|
171
|
+
// TODO(#5118) - might cast to checksum
|
|
172
|
+
key: (args) => JSON.stringify(args),
|
|
173
|
+
client: {
|
|
174
|
+
fn: client.related,
|
|
175
|
+
},
|
|
176
|
+
expire,
|
|
177
|
+
}
|
|
178
|
+
: client.related;
|
|
179
|
+
}
|
|
180
|
+
this._set('client', async () => clientConfig);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Resolves the services API provider, preferring the local module
|
|
185
|
+
* instance and falling back to the parent module.
|
|
186
|
+
*
|
|
187
|
+
* @param init - Module initializer arguments.
|
|
188
|
+
* @returns The resolved API provider.
|
|
189
|
+
* @throws Error if no services module is available.
|
|
190
|
+
*/
|
|
191
|
+
protected async _getServiceProvider(
|
|
192
|
+
init: ModuleInitializerArgs<IContextModuleConfigurator, [ServicesModule]>,
|
|
193
|
+
): Promise<IApiProvider> {
|
|
194
|
+
// prefer the local services module instance if available
|
|
195
|
+
if (init.hasModule('services')) {
|
|
196
|
+
return init.requireInstance('services');
|
|
197
|
+
}
|
|
198
|
+
const parentServiceModule = (init.ref as ModulesInstanceType<[ServicesModule]>)?.services;
|
|
199
|
+
// fall back to the parent module's services instance
|
|
200
|
+
if (!parentServiceModule) {
|
|
201
|
+
throw Error('no service services provider configures [ServicesModule]');
|
|
202
|
+
}
|
|
203
|
+
return parentServiceModule;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Runs all registered config builders, then resolves the accumulated
|
|
208
|
+
* `_set` registrations into the final {@link ContextModuleConfig}.
|
|
209
|
+
*
|
|
210
|
+
* @param init - Module initializer arguments including dependency instances.
|
|
211
|
+
* @param initial - Optional partial config inherited from a parent module.
|
|
212
|
+
* @returns The fully resolved context module configuration.
|
|
213
|
+
*/
|
|
214
|
+
protected async _createConfig(
|
|
215
|
+
init: ModuleInitializerArgs<IContextModuleConfigurator, [ServicesModule, NavigationModule]>,
|
|
216
|
+
initial?: Partial<ContextModuleConfig>,
|
|
217
|
+
): Promise<ContextModuleConfig> {
|
|
218
|
+
// requireInstance resolves lazily against this init for the remainder of config building
|
|
219
|
+
this.#init = init;
|
|
220
|
+
|
|
221
|
+
// run each registered config builder in sequence; each calls setXxx(), registering into _set
|
|
222
|
+
await this.#configBuilders.reduce((prev, cb) => prev.then(() => cb(this)), Promise.resolve());
|
|
223
|
+
|
|
224
|
+
// resolve every registered _set callback into the actual config object
|
|
225
|
+
const config = await lastValueFrom(from(this._buildConfig(init, initial)));
|
|
226
|
+
|
|
227
|
+
// route through _processConfig, so a subclass overriding it (e.g. ContextMockConfigurator) still runs
|
|
228
|
+
return lastValueFrom(from(this._processConfig(config, init)));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Defaults `resolveInitialContext` and `client` when a builder callback
|
|
233
|
+
* didn't set them.
|
|
234
|
+
*
|
|
235
|
+
* If no `resolveInitialContext` was set, the default path + parent
|
|
236
|
+
* resolver is used. If no `client` was set, one is created from the
|
|
237
|
+
* {@link ServicesModule} API provider.
|
|
238
|
+
*
|
|
239
|
+
* @param config - The config accumulated from registered builder callbacks.
|
|
240
|
+
* @param init - Module initializer arguments including dependency instances.
|
|
241
|
+
* @returns The fully resolved context module configuration.
|
|
242
|
+
*/
|
|
243
|
+
protected async _processConfig(
|
|
244
|
+
config: Partial<ContextModuleConfig>,
|
|
245
|
+
init: ModuleInitializerArgs<IContextModuleConfigurator, [ServicesModule, NavigationModule]>,
|
|
246
|
+
): Promise<ContextModuleConfig> {
|
|
247
|
+
config.resolveInitialContext ??= resolveInitialContext({
|
|
248
|
+
path: {
|
|
249
|
+
extract: config.extractContextIdFromPath,
|
|
250
|
+
validate: config.extractContextIdFromPath ? () => true : undefined,
|
|
251
|
+
},
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// TODO(#5119) - make less lazy
|
|
255
|
+
config.client ??= await (async (): Promise<ContextModuleConfig['client']> => {
|
|
256
|
+
const apiProvider = await this._getServiceProvider(init);
|
|
257
|
+
const contextClient = await apiProvider.createContextClient('json$');
|
|
258
|
+
return {
|
|
259
|
+
get: {
|
|
260
|
+
client: {
|
|
261
|
+
fn: (args) => contextClient.get('v1', args, { selector: getContextSelector }),
|
|
262
|
+
},
|
|
263
|
+
key: ({ id }) => id,
|
|
264
|
+
expire: this.defaultExpireTime,
|
|
265
|
+
},
|
|
266
|
+
query: {
|
|
267
|
+
client: {
|
|
268
|
+
fn: (query) => contextClient.query('v1', { query }, { selector: queryContextSelector }),
|
|
269
|
+
},
|
|
270
|
+
// TODO(#5118) - might cast to checksum
|
|
271
|
+
key: (args) => JSON.stringify(args),
|
|
272
|
+
expire: this.defaultExpireTime,
|
|
273
|
+
},
|
|
274
|
+
related: {
|
|
275
|
+
client: {
|
|
276
|
+
fn: (args) => {
|
|
277
|
+
return contextClient.related(
|
|
278
|
+
'v1',
|
|
279
|
+
{ id: args.item.id, query: { filter: args.filter } },
|
|
280
|
+
{ selector: relatedContextSelector },
|
|
281
|
+
);
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
// TODO(#5118) - might cast to checksum
|
|
285
|
+
key: (args) => JSON.stringify(args),
|
|
286
|
+
expire: this.defaultExpireTime,
|
|
287
|
+
},
|
|
288
|
+
};
|
|
289
|
+
})();
|
|
290
|
+
|
|
291
|
+
return config as ContextModuleConfig;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export default ContextModuleConfigurator;
|
package/src/ContextProvider.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
tap,
|
|
11
11
|
} from 'rxjs/operators';
|
|
12
12
|
|
|
13
|
-
import type { ContextModuleConfig } from './
|
|
13
|
+
import type { ContextModuleConfig } from './ContextModuleConfig';
|
|
14
14
|
|
|
15
15
|
import { BaseModuleProvider } from '@equinor/fusion-framework-module/provider';
|
|
16
16
|
import { version } from './version.js';
|
|
@@ -505,7 +505,6 @@ export class ContextProvider
|
|
|
505
505
|
.pipe(
|
|
506
506
|
// resolve context item from queue
|
|
507
507
|
switchMap((next) => next),
|
|
508
|
-
tap((x) => console.debug('ContextProvider::#contextQueue', x)),
|
|
509
508
|
)
|
|
510
509
|
.subscribe((context) => {
|
|
511
510
|
// set context from resolved context item from queue
|