@equinor/fusion-framework-module-context 0.0.0-context-error-20240131144633

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 (61) hide show
  1. package/CHANGELOG.md +522 -0
  2. package/LICENSE +21 -0
  3. package/dist/esm/ContextConfigBuilder.js +79 -0
  4. package/dist/esm/ContextConfigBuilder.js.map +1 -0
  5. package/dist/esm/ContextProvider.js +279 -0
  6. package/dist/esm/ContextProvider.js.map +1 -0
  7. package/dist/esm/client/ContextClient.js +62 -0
  8. package/dist/esm/client/ContextClient.js.map +1 -0
  9. package/dist/esm/configurator.js +86 -0
  10. package/dist/esm/configurator.js.map +1 -0
  11. package/dist/esm/errors.js +26 -0
  12. package/dist/esm/errors.js.map +1 -0
  13. package/dist/esm/index.js +6 -0
  14. package/dist/esm/index.js.map +1 -0
  15. package/dist/esm/module.js +55 -0
  16. package/dist/esm/module.js.map +1 -0
  17. package/dist/esm/selectors.js +44 -0
  18. package/dist/esm/selectors.js.map +1 -0
  19. package/dist/esm/types.js +2 -0
  20. package/dist/esm/types.js.map +1 -0
  21. package/dist/esm/utils/enable-context.js +10 -0
  22. package/dist/esm/utils/enable-context.js.map +1 -0
  23. package/dist/esm/utils/index.js +3 -0
  24. package/dist/esm/utils/index.js.map +1 -0
  25. package/dist/esm/utils/resolve-context-from-path.js +17 -0
  26. package/dist/esm/utils/resolve-context-from-path.js.map +1 -0
  27. package/dist/esm/utils/resolve-initial-context.js +16 -0
  28. package/dist/esm/utils/resolve-initial-context.js.map +1 -0
  29. package/dist/esm/version.js +2 -0
  30. package/dist/esm/version.js.map +1 -0
  31. package/dist/tsconfig.tsbuildinfo +1 -0
  32. package/dist/types/ContextConfigBuilder.d.ts +25 -0
  33. package/dist/types/ContextProvider.d.ts +99 -0
  34. package/dist/types/client/ContextClient.d.ts +22 -0
  35. package/dist/types/configurator.d.ts +41 -0
  36. package/dist/types/errors.d.ts +8 -0
  37. package/dist/types/index.d.ts +5 -0
  38. package/dist/types/module.d.ts +20 -0
  39. package/dist/types/selectors.d.ts +4 -0
  40. package/dist/types/types.d.ts +34 -0
  41. package/dist/types/utils/enable-context.d.ts +4 -0
  42. package/dist/types/utils/index.d.ts +2 -0
  43. package/dist/types/utils/resolve-context-from-path.d.ts +8 -0
  44. package/dist/types/utils/resolve-initial-context.d.ts +7 -0
  45. package/dist/types/version.d.ts +1 -0
  46. package/package.json +65 -0
  47. package/src/ContextConfigBuilder.ts +131 -0
  48. package/src/ContextProvider.ts +555 -0
  49. package/src/client/ContextClient.ts +71 -0
  50. package/src/configurator.ts +158 -0
  51. package/src/errors.ts +19 -0
  52. package/src/index.ts +18 -0
  53. package/src/module.ts +91 -0
  54. package/src/selectors.ts +41 -0
  55. package/src/types.ts +33 -0
  56. package/src/utils/enable-context.ts +31 -0
  57. package/src/utils/index.ts +2 -0
  58. package/src/utils/resolve-context-from-path.ts +34 -0
  59. package/src/utils/resolve-initial-context.ts +29 -0
  60. package/src/version.ts +2 -0
  61. package/tsconfig.json +33 -0
@@ -0,0 +1,158 @@
1
+ import { ObservableInput } from 'rxjs';
2
+
3
+ import {
4
+ AnyModuleInstance,
5
+ ModuleInitializerArgs,
6
+ ModuleInstance,
7
+ ModulesInstanceType,
8
+ } from '@equinor/fusion-framework-module';
9
+ import { ServicesModule, IApiProvider } from '@equinor/fusion-framework-module-services';
10
+ import { NavigationModule } from '@equinor/fusion-framework-module-navigation';
11
+ import { getContextSelector, queryContextSelector, relatedContextSelector } from './selectors';
12
+ import { QueryCtorOptions } from '@equinor/fusion-query';
13
+ import {
14
+ ContextFilterFn,
15
+ ContextItem,
16
+ QueryContextParameters,
17
+ RelatedContextParameters,
18
+ } from './types';
19
+ import { GetContextParameters } from './client/ContextClient';
20
+ import { ContextConfigBuilder, ContextConfigBuilderCallback } from './ContextConfigBuilder';
21
+ import { type IContextProvider } from './ContextProvider';
22
+ import resolveInitialContext from './utils/resolve-initial-context';
23
+
24
+ export interface ContextModuleConfig {
25
+ client: {
26
+ get: QueryCtorOptions<ContextItem, GetContextParameters>;
27
+ query: QueryCtorOptions<ContextItem[], QueryContextParameters>;
28
+ related?: QueryCtorOptions<ContextItem[], RelatedContextParameters>;
29
+ };
30
+ contextType?: string[];
31
+ contextFilter?: ContextFilterFn;
32
+
33
+ /**
34
+ * connect context module to paren context module.
35
+ *
36
+ * _default: `true`_
37
+ */
38
+ connectParentContext?: boolean;
39
+
40
+ /** set initial context from parent, will await resolve */
41
+ skipInitialContext?: boolean;
42
+
43
+ /**
44
+ * Method for generating context query parameters.
45
+ */
46
+ contextParameterFn?: (args: {
47
+ search: string;
48
+ type: ContextModuleConfig['contextType'];
49
+ }) => string | QueryContextParameters;
50
+
51
+ resolveContext?: (
52
+ this: IContextProvider,
53
+ item: ContextItem | null,
54
+ ) => ReturnType<IContextProvider['resolveContext']>;
55
+
56
+ validateContext?: (
57
+ this: IContextProvider,
58
+ item: ContextItem | null,
59
+ ) => ReturnType<IContextProvider['validateContext']>;
60
+
61
+ resolveInitialContext?: (args: {
62
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
63
+ ref?: AnyModuleInstance | any;
64
+ modules: ModuleInstance;
65
+ }) => ObservableInput<ContextItem | void>;
66
+ }
67
+
68
+ export interface IContextModuleConfigurator {
69
+ addConfigBuilder: (init: ContextConfigBuilderCallback) => void;
70
+ }
71
+
72
+ export class ContextModuleConfigurator implements IContextModuleConfigurator {
73
+ defaultExpireTime = 1 * 60 * 1000;
74
+
75
+ #configBuilders: Array<ContextConfigBuilderCallback> = [];
76
+
77
+ addConfigBuilder(init: ContextConfigBuilderCallback): void {
78
+ this.#configBuilders.push(init);
79
+ }
80
+
81
+ protected async _getServiceProvider(
82
+ init: ModuleInitializerArgs<IContextModuleConfigurator, [ServicesModule]>,
83
+ ): Promise<IApiProvider> {
84
+ if (init.hasModule('services')) {
85
+ return init.requireInstance('services');
86
+ } else {
87
+ const parentServiceModule = (init.ref as ModulesInstanceType<[ServicesModule]>)
88
+ ?.services;
89
+ if (parentServiceModule) {
90
+ return parentServiceModule;
91
+ }
92
+ throw Error('no service services provider configures [ServicesModule]');
93
+ }
94
+ }
95
+
96
+ public async createConfig(
97
+ init: ModuleInitializerArgs<IContextModuleConfigurator, [ServicesModule, NavigationModule]>,
98
+ ): Promise<ContextModuleConfig> {
99
+ const config = await this.#configBuilders.reduce(
100
+ async (cur, cb) => {
101
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
102
+ const builder = new ContextConfigBuilder<any, any>(init, await cur);
103
+ await Promise.resolve(cb(builder));
104
+ return Object.assign(cur, builder.config);
105
+ },
106
+ Promise.resolve({} as Partial<ContextModuleConfig>),
107
+ );
108
+
109
+ config.resolveInitialContext ??= resolveInitialContext();
110
+
111
+ // TODO - make less lazy
112
+ config.client ??= await (async (): Promise<ContextModuleConfig['client']> => {
113
+ const apiProvider = await this._getServiceProvider(init);
114
+ const contextClient = await apiProvider.createContextClient('json$');
115
+ return {
116
+ get: {
117
+ client: {
118
+ fn: (args) =>
119
+ contextClient.get('v1', args, { selector: getContextSelector }),
120
+ },
121
+ key: ({ id }) => id,
122
+ expire: this.defaultExpireTime,
123
+ },
124
+ query: {
125
+ client: {
126
+ fn: (query) =>
127
+ contextClient.query(
128
+ 'v1',
129
+ { query },
130
+ { selector: queryContextSelector },
131
+ ),
132
+ },
133
+ // TODO - might cast to checksum
134
+ key: (args) => JSON.stringify(args),
135
+ expire: this.defaultExpireTime,
136
+ },
137
+ related: {
138
+ client: {
139
+ fn: (args) => {
140
+ return contextClient.related(
141
+ 'v1',
142
+ { id: args.item.id, query: { filter: args.filter } },
143
+ { selector: relatedContextSelector },
144
+ );
145
+ },
146
+ },
147
+ // TODO - might cast to checksum
148
+ key: (args) => JSON.stringify(args),
149
+ expire: this.defaultExpireTime,
150
+ },
151
+ };
152
+ })();
153
+
154
+ return config as ContextModuleConfig;
155
+ }
156
+ }
157
+
158
+ export default ContextModuleConfigurator;
package/src/errors.ts ADDED
@@ -0,0 +1,19 @@
1
+ export class FusionContextSearchError extends Error {
2
+ #details;
3
+
4
+ get title(): string {
5
+ return this.#details.title;
6
+ }
7
+
8
+ constructor(
9
+ details: {
10
+ title: string;
11
+ description?: string;
12
+ },
13
+ options?: ErrorOptions,
14
+ ) {
15
+ super(details.description ?? details.title, options);
16
+ this.#details = details;
17
+ this.name = 'FusionContextSearchError';
18
+ }
19
+ }
package/src/index.ts ADDED
@@ -0,0 +1,18 @@
1
+ export {
2
+ ContextModuleConfigurator,
3
+ IContextModuleConfigurator,
4
+ ContextModuleConfig,
5
+ } from './configurator';
6
+
7
+ export { IContextProvider, ContextProvider } from './ContextProvider';
8
+
9
+ export {
10
+ default,
11
+ ContextModule,
12
+ module as contextModule,
13
+ moduleKey as contextModuleKey,
14
+ } from './module';
15
+
16
+ export { enableContext } from './utils/enable-context';
17
+
18
+ export * from './types';
package/src/module.ts ADDED
@@ -0,0 +1,91 @@
1
+ import { catchError, EMPTY, from, Observable, switchMap, filter, Subscription } from 'rxjs';
2
+
3
+ import { Module, ModulesInstance } from '@equinor/fusion-framework-module';
4
+
5
+ import { EventModule } from '@equinor/fusion-framework-module-event';
6
+ import { ServicesModule } from '@equinor/fusion-framework-module-services';
7
+ import { NavigationModule } from '@equinor/fusion-framework-module-navigation';
8
+
9
+ import { IContextModuleConfigurator, ContextModuleConfigurator } from './configurator';
10
+ import { IContextProvider, ContextProvider } from './ContextProvider';
11
+ import { ContextItem } from './types';
12
+
13
+ export type ContextModuleKey = 'context';
14
+
15
+ export const moduleKey: ContextModuleKey = 'context';
16
+
17
+ export type ContextModule = Module<
18
+ ContextModuleKey,
19
+ IContextProvider,
20
+ IContextModuleConfigurator,
21
+ [ServicesModule, EventModule, NavigationModule]
22
+ >;
23
+
24
+ export const module: ContextModule = {
25
+ name: moduleKey,
26
+ configure: () => new ContextModuleConfigurator(),
27
+ initialize: async function (args) {
28
+ const config = await (args.config as ContextModuleConfigurator).createConfig(args);
29
+ const event = args.hasModule('event') ? await args.requireInstance('event') : undefined;
30
+ const parentProvider = (args.ref as ModulesInstance<[ContextModule]>)?.context;
31
+ const provider = new ContextProvider({ config, event, parentContext: parentProvider });
32
+
33
+ const subscription = new Subscription(() => provider.dispose());
34
+
35
+ this.postInitialize = (args) =>
36
+ new Observable((subscriber) => {
37
+ const resolveInitialContext$ = config.resolveInitialContext
38
+ ? from(config.resolveInitialContext(args)).pipe(
39
+ filter((item): item is ContextItem => !!item),
40
+ switchMap((item) =>
41
+ args.modules.context.setCurrentContext(item, {
42
+ validate: true,
43
+ resolve: true,
44
+ }),
45
+ ),
46
+ )
47
+ : EMPTY;
48
+
49
+ subscriber.add(
50
+ resolveInitialContext$
51
+ .pipe(
52
+ catchError((err) => {
53
+ console.warn(
54
+ 'ContextModule.postInitialize',
55
+ 'failed to resolve initial context',
56
+ err,
57
+ );
58
+ return EMPTY;
59
+ }),
60
+ )
61
+ .subscribe({
62
+ next: (item) => {
63
+ console.debug(
64
+ 'ContextModule.postInitialize',
65
+ `initial context was resolved to [${item ? item.id : 'none'}]`,
66
+ item,
67
+ );
68
+ },
69
+ complete: () => {
70
+ if (config.connectParentContext !== false && parentProvider) {
71
+ provider.connectParentContext(parentProvider);
72
+ }
73
+ subscriber.complete();
74
+ },
75
+ }),
76
+ );
77
+ });
78
+
79
+ this.dispose = () => subscription.unsubscribe();
80
+
81
+ return provider;
82
+ },
83
+ };
84
+
85
+ declare module '@equinor/fusion-framework-module' {
86
+ interface Modules {
87
+ context: ContextModule;
88
+ }
89
+ }
90
+
91
+ export default module;
@@ -0,0 +1,41 @@
1
+ import { ApiVersion, ApiContextEntity } from '@equinor/fusion-framework-module-services/context';
2
+ import type { GetContextResponse } from '@equinor/fusion-framework-module-services/context/get';
3
+ import type { QueryContextResponse } from '@equinor/fusion-framework-module-services/context/query';
4
+ import type { RelatedContextResponse } from '@equinor/fusion-framework-module-services/context/related';
5
+ import type { ContextItem, ContextItemType } from './types';
6
+
7
+ const parseContextType = (type: GetContextResponse<'v1'>['type']): ContextItemType => ({
8
+ id: type.id,
9
+ isChildType: type.isChildType,
10
+ parentTypeIds: type.parentTypeIds ?? [],
11
+ });
12
+
13
+ const parseContextItem = (item: ApiContextEntity<ApiVersion.v1>): ContextItem => {
14
+ return {
15
+ id: item.id,
16
+ externalId: item.externalId ?? undefined,
17
+ isActive: item.isActive,
18
+ isDeleted: item.isDeleted,
19
+ created: new Date(item.created),
20
+ source: item.source ?? undefined,
21
+ title: item.title ?? undefined,
22
+ type: parseContextType(item.type),
23
+ // TODO
24
+ value: item.value ?? {},
25
+ };
26
+ };
27
+
28
+ export const getContextSelector = async (response: Response): Promise<ContextItem> => {
29
+ const result = (await response.json()) as GetContextResponse<'v1'>;
30
+ return parseContextItem(result);
31
+ };
32
+
33
+ export const queryContextSelector = async (response: Response): Promise<ContextItem[]> => {
34
+ const result = (await response.json()) as QueryContextResponse<'v1'>;
35
+ return result.map(parseContextItem);
36
+ };
37
+
38
+ export const relatedContextSelector = async (response: Response): Promise<ContextItem[]> => {
39
+ const result = (await response.json()) as RelatedContextResponse<'v1'>;
40
+ return result.map(parseContextItem);
41
+ };
package/src/types.ts ADDED
@@ -0,0 +1,33 @@
1
+ export type ContextItem<TType extends Record<string, unknown> = Record<string, unknown>> = {
2
+ id: string;
3
+ externalId?: string;
4
+ source?: string;
5
+ type: ContextItemType;
6
+ value: TType;
7
+ title?: string;
8
+ subTitle?: string;
9
+ isActive?: boolean;
10
+ isDeleted?: boolean;
11
+ created?: Date;
12
+ updated?: Date;
13
+ graphic?: string;
14
+ meta?: string;
15
+ };
16
+
17
+ export interface ContextItemType {
18
+ id: string;
19
+ isChildType?: boolean;
20
+ parentTypeIds?: string[];
21
+ }
22
+
23
+ export type QueryContextParameters = {
24
+ search?: string;
25
+ filter?: {
26
+ type?: string[];
27
+ externalId?: string;
28
+ };
29
+ };
30
+
31
+ export type RelatedContextParameters = { item: ContextItem; filter?: { type?: string[] } };
32
+
33
+ export type ContextFilterFn = (items: ContextItem[]) => ContextItem[];
@@ -0,0 +1,31 @@
1
+ import type {
2
+ IModulesConfigurator,
3
+ AnyModule,
4
+ ModuleInitializerArgs,
5
+ } from '@equinor/fusion-framework-module';
6
+ import type { IContextModuleConfigurator } from '../configurator';
7
+ import type { ContextConfigBuilder } from '../ContextConfigBuilder';
8
+
9
+ import { module } from '../module';
10
+
11
+ /**
12
+ * Method for enabling the Service module
13
+ * @param configurator - configuration object
14
+ */
15
+ export const enableContext = (
16
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
17
+ configurator: IModulesConfigurator<any, any>,
18
+ builder?: <TDeps extends Array<AnyModule> = []>(
19
+ builder: ContextConfigBuilder<
20
+ TDeps,
21
+ ModuleInitializerArgs<IContextModuleConfigurator, TDeps>
22
+ >,
23
+ ) => void | Promise<void>,
24
+ ): void => {
25
+ configurator.addConfig({
26
+ module,
27
+ configure: (contextConfigurator) => {
28
+ builder && contextConfigurator.addConfigBuilder(builder);
29
+ },
30
+ });
31
+ };
@@ -0,0 +1,2 @@
1
+ export { enableContext } from './enable-context';
2
+ export { resolveInitialContext } from './resolve-initial-context';
@@ -0,0 +1,34 @@
1
+ import { EMPTY } from 'rxjs';
2
+
3
+ import { ModuleType } from '@equinor/fusion-framework-module';
4
+
5
+ import { type ContextModule } from '../module';
6
+
7
+ export type ContextPathResolveArgs = {
8
+ extract?: (path: string) => string | undefined;
9
+ validate?: (contextId: string) => boolean;
10
+ };
11
+
12
+ const matchGUID =
13
+ /^(?:(?:[0-9a-fA-F]){8}-(?:[0-9a-fA-F]){4}-(?:[0-9a-fA-F]){4}-(?:[0-9a-fA-F]){4}-(?:[0-9a-fA-F]){12})$/;
14
+
15
+ const extractContextIdFromPath = (path: string): string | undefined =>
16
+ path.replace(/^\/+/, '').split('/').shift();
17
+
18
+ const validateContextId = (contextId: string): boolean => !!contextId.match(matchGUID);
19
+
20
+ export const resolveContextFromPath =
21
+ (context: ModuleType<ContextModule>, args?: ContextPathResolveArgs) => (path: string) => {
22
+ const { extract = extractContextIdFromPath, validate = validateContextId } = args ?? {};
23
+ const contextId = extract(path);
24
+ if (!contextId) {
25
+ return EMPTY;
26
+ }
27
+ if (validate(contextId)) {
28
+ return context.contextClient.resolveContext(contextId);
29
+ }
30
+
31
+ throw Error(`Failed to validate context [${contextId}] from path [${path}]`);
32
+ };
33
+
34
+ export default resolveContextFromPath;
@@ -0,0 +1,29 @@
1
+ import { ModulesInstance } from '@equinor/fusion-framework-module';
2
+ import { ContextModule } from '../module';
3
+ import { type ContextModuleConfig } from '../configurator';
4
+ import { concat, EMPTY, first, of } from 'rxjs';
5
+
6
+ import { ContextPathResolveArgs, resolveContextFromPath } from './resolve-context-from-path';
7
+
8
+ export const resolveContextFromParent: ContextModuleConfig['resolveInitialContext'] = ({ ref }) => {
9
+ const parentContext = (ref as ModulesInstance<[ContextModule]>)?.context;
10
+ if (!parentContext) {
11
+ throw Error(['resolveContextFromNavigation', 'ref does not support context!'].join('\n'));
12
+ }
13
+ return parentContext.currentContext ? of(parentContext.currentContext) : EMPTY;
14
+ };
15
+
16
+ export const resolveInitialContext =
17
+ (options?: {
18
+ path?: ContextPathResolveArgs;
19
+ }): Required<ContextModuleConfig>['resolveInitialContext'] =>
20
+ ({ ref, modules }) => {
21
+ const { context, navigation } = modules;
22
+ const pathResolver = resolveContextFromPath(context, options?.path);
23
+ return concat(
24
+ navigation ? pathResolver(navigation.path.pathname) : EMPTY,
25
+ resolveContextFromParent({ ref, modules }),
26
+ ).pipe(first());
27
+ };
28
+
29
+ export default resolveInitialContext;
package/src/version.ts ADDED
@@ -0,0 +1,2 @@
1
+ // Generated by genversion.
2
+ export const version = '4.0.19';
package/tsconfig.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "extends": "../../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist/esm",
5
+ "rootDir": "src",
6
+ "declarationDir": "./dist/types",
7
+
8
+ },
9
+ "references": [
10
+ {
11
+ "path": "../../utils/query"
12
+ },
13
+ {
14
+ "path": "../module"
15
+ },
16
+ {
17
+ "path": "../navigation"
18
+ },
19
+ {
20
+ "path": "../event"
21
+ },
22
+ {
23
+ "path": "../services"
24
+ },
25
+ ],
26
+ "include": [
27
+ "src/**/*",
28
+ ],
29
+ "exclude": [
30
+ "node_modules",
31
+ "dist"
32
+ ]
33
+ }