Skip to content

Commit

Permalink
feat(module): allow multilevel config (#882)
Browse files Browse the repository at this point in the history
  • Loading branch information
odinr authored May 31, 2023
1 parent 43854d9 commit 76b30c1
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 13 deletions.
27 changes: 27 additions & 0 deletions .changeset/funny-garlics-complain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
'@equinor/fusion-framework-module': minor
---

Add possibility to add multilevel config for module

```ts
type MyModuleConfig = {
foo: string;
bar?: number,
nested?: { up: boolean }
};

class MyModuleConfigurator extends BaseConfigBuilder<MyModuleConfig> {
public setFoo(cb: ModuleConfigCallback<string>) {
this._set('foo', cb);
}

public setBar(cb: ModuleConfigCallback<number>) {
this._set('bar', cb);
}

public setUp(cb: ModuleConfigCallback<boolean>) {
this._set('nested.up', cb);
}
}
```
71 changes: 59 additions & 12 deletions packages/modules/module/src/BaseConfigBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,53 @@ import { from, lastValueFrom, of, type Observable, type ObservableInput } from '
import { last, mergeMap, scan } from 'rxjs/operators';
import { Modules, ModuleType } from './types';

type ConfigPropType<T, Path extends string> = string extends Path
? unknown
: Path extends keyof T
? T[Path]
: Path extends `${infer K}.${infer R}`
? K extends keyof T
? ConfigPropType<T[K], R>
: unknown
: unknown;

type DotPrefix<T extends string> = T extends '' ? '' : `.${T}`;

type DotNestedKeys<T> = (
T extends object
? { [K in Exclude<keyof T, symbol>]: K | `${K}${DotPrefix<DotNestedKeys<T[K]>>}` }[Exclude<
keyof T,
symbol
>]
: ''
) extends infer D
? Extract<D, string>
: never;

/** helper function for extracting multilevel attribute keys */
const assignConfigValue = <T>(
obj: Record<string, unknown>,
prop: string | string[],
value: unknown
): T => {
const props = typeof prop === 'string' ? prop.split('.') : prop;
const attr = props.shift();
if (attr) {
obj[attr] ??= {};
props.length
? assignConfigValue(obj[attr] as Record<string, unknown>, props, value)
: Object.assign(obj, { [attr]: value });
}
return obj as T;
};

/**
* callback arguments for config builder callback function
* @template TRef parent instance
*/
export type ConfigBuilderCallbackArgs<TRef = unknown> = {
export type ConfigBuilderCallbackArgs<TConfig = unknown, TRef = unknown> = {
config: TConfig;

/** reference, parent modules */
ref?: TRef;

Expand Down Expand Up @@ -47,12 +89,13 @@ export type ConfigBuilderCallback<TReturn = unknown> = (
/**
* template class for building module config
*
* __Limitations:__
* this only allows configuring an attribute of config root level
*
* @example
* ```ts
* type MyModuleConfig = { foo: string; bar?: number };
* type MyModuleConfig = {
* foo: string;
* bar?: number,
* nested?: { up: boolean }
* };
*
* class MyModuleConfigurator extends BaseConfigBuilder<MyModuleConfig> {
* public setFoo(cb: ModuleConfigCallback<string>) {
Expand All @@ -62,13 +105,17 @@ export type ConfigBuilderCallback<TReturn = unknown> = (
* public setBar(cb: ModuleConfigCallback<number>) {
* this._set('bar', cb);
* }
*
* public setUp(cb: ModuleConfigCallback<boolean>) {
* this._set('nested.up', cb);
* }
* }
* ```
* @template TConfig expected config the builder will create
*/
export abstract class BaseConfigBuilder<TConfig = unknown> {
/** internal hashmap of registered callback functions */
#configCallbacks = {} as Record<keyof TConfig, ConfigBuilderCallback>;
#configCallbacks = {} as Record<string, ConfigBuilderCallback>;

/**
* request the builder to generate config
Expand All @@ -95,13 +142,13 @@ export abstract class BaseConfigBuilder<TConfig = unknown> {

/**
* internally set configuration of a config attribute
* @param target attribute name of config
* @param target attribute name of config dot notaded
* @param cb callback function for setting the attribute
* @template TKey keyof config (attribute name
* @template TKey keyof config
*/
protected _set<TKey extends keyof TConfig>(
target: TKey,
cb: ConfigBuilderCallback<TConfig[TKey]>
protected _set<TTarget extends DotNestedKeys<TConfig>>(
target: TTarget,
cb: ConfigBuilderCallback<ConfigPropType<TConfig, TTarget>>
) {
this.#configCallbacks[target] = cb;
}
Expand Down Expand Up @@ -131,7 +178,7 @@ export abstract class BaseConfigBuilder<TConfig = unknown> {
return { target, value };
}),
scan(
(acc, { target, value }) => Object.assign({}, acc, { [target]: value }),
(acc, { target, value }) => assignConfigValue(acc, target, value),
initial ?? ({} as TConfig)
),
last()
Expand Down
1 change: 0 additions & 1 deletion packages/modules/navigation/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ export type { Action, Path } from '@remix-run/router';

export { INavigationConfigurator, NavigationConfigurator } from './configurator';


export { NavigationModule, enableNavigation, module, moduleKey } from './module';

export { createHistory } from './createHistory';
Expand Down

0 comments on commit 76b30c1

Please sign in to comment.