Skip to content

Commit

Permalink
Feat/module/add semver (#902)
Browse files Browse the repository at this point in the history
* docs(module): update tsdoc for  `Module` interface

* feat(module): add semver

* feat(module): add version attribute to module

* feat(module): add base module provider class

+ create a common interface for module providers
+ create a base class for creating module provider

* fix(module): add semver to legacy providers

* docs(module): create change log for semver

* docs(module): create changelog for base module provider
  • Loading branch information
odinr authored May 28, 2023
1 parent 1aecdf8 commit 3efbf0b
Show file tree
Hide file tree
Showing 10 changed files with 203 additions and 2 deletions.
11 changes: 11 additions & 0 deletions .changeset/afraid-jokes-trade.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@equinor/fusion-framework-module": minor
---

__Feat(module)__ add base module class

As a module developer there should be a base provider class to extend, which handles basic wireing.

Some aspects of providers should be the same for all, like `version` handling.

These new features does not change any existing code, only tooling for future development
20 changes: 20 additions & 0 deletions .changeset/smooth-chicken-promise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
"@equinor/fusion-framework-module": minor
---

__Feat(module): add semver__

In some cases other modules might require features in sibling modules
```ts
if (modules.context.version.satisfies('>=7.2')) {
// do some code
} else {
throw Error('this feature requires ContextModule of 7.2 or higher, please update depencies')
}
```

Usage:
- log telemetry about module usage and outdated application
- debug code runtime by knowing version of implementation
- write inter-opt when breaking changes accour

3 changes: 2 additions & 1 deletion packages/modules/module/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"directory": "packages/modules/module"
},
"dependencies": {
"rxjs": "^7.5.7"
"rxjs": "^7.5.7",
"semver": "^7.5.0"
},
"devDependencies": {
"typescript": "^4.9.3"
Expand Down
26 changes: 25 additions & 1 deletion packages/modules/module/src/configurator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ import type {
ModuleType,
} from './types';

import { SemanticVersion } from './lib/semantic-version';
import { BaseModuleProvider, type IModuleProvider } from './lib/provider';

export interface IModulesConfigurator<
TModules extends Array<AnyModule> = Array<AnyModule>,
TRef = any
Expand Down Expand Up @@ -254,10 +257,31 @@ export class ModulesConfigurator<TModules extends Array<AnyModule> = Array<AnyMo
// @ts-ignore
requireInstance,
hasModule,
})
}) as IModuleProvider
)
).pipe(
map((instance) => {
if (
!(instance instanceof BaseModuleProvider) &&
!(instance.version instanceof SemanticVersion)
) {
// TODO change to warn in future
logger.debug(
`🤷 module does not extends the [BaseModuleProvider] or exposes [SemanticVersion]`
);
try {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
instance.version =
module.version instanceof SemanticVersion
? module.version
: new SemanticVersion(
module.version ?? '0.0.0-unknown'
);
} catch (err) {
logger.error(`🚨 failed to set module version`);
}
}
logger.debug(`🚀 initialized ${logger.formatModuleName(module)}`);
return [key, instance];
})
Expand Down
47 changes: 47 additions & 0 deletions packages/modules/module/src/lib/provider/BaseModuleProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import SemanticVersion from '../semantic-version';

import { type IModuleProvider } from './IModuleProvider';

import { Subscription, type TeardownLogic } from 'rxjs';

type BaseModuleProviderCtorArgs<TConfig = unknown> = {
version: string;
config: TConfig;
};

/**
* Base class for creating module provider
*
* this is the interface which is returned after enabling a module
*/
export abstract class BaseModuleProvider<TConfig = unknown> implements IModuleProvider {
#version: SemanticVersion;
#subscriptions: Subscription;

public get version() {
return this.#version;
}

constructor(args: BaseModuleProviderCtorArgs<TConfig>) {
this.#version = new SemanticVersion(args.version);
this.#subscriptions = new Subscription();
this._init(args.config);
}

protected abstract _init(config: TConfig): void;

/**
* Add teardown down function, which is called on dispose.
*
* @param teardown dispose callback function
* @returns callback for removing the teardown
*/
protected _addTeardown(teardown: Exclude<TeardownLogic, void>): VoidFunction {
this.#subscriptions.add(teardown);
return () => this.#subscriptions.remove(teardown);
}

public dispose() {
this.#subscriptions.unsubscribe();
}
}
6 changes: 6 additions & 0 deletions packages/modules/module/src/lib/provider/IModuleProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import SemanticVersion from '../semantic-version';

export interface IModuleProvider {
get version(): SemanticVersion;
dispose: VoidFunction;
}
2 changes: 2 additions & 0 deletions packages/modules/module/src/lib/provider/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export type { IModuleProvider } from './IModuleProvider';
export { BaseModuleProvider } from './BaseModuleProvider';
13 changes: 13 additions & 0 deletions packages/modules/module/src/lib/semantic-version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { SemVer, satisfies } from 'semver';

/**
* Extension of {@link SemVer} to expose `satisfies`
* @see {@link [SemVer](https://www.npmjs.com/package/semver)}
*/
export class SemanticVersion extends SemVer {
public satisfies(arg: Parameters<typeof satisfies>[1]) {
return satisfies(this, arg);
}
}

export default SemanticVersion;
70 changes: 70 additions & 0 deletions packages/modules/module/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-explicit-any */

import SemanticVersion from './lib/semantic-version';
import { ObservableInput } from 'rxjs';

export type ModuleInitializerArgs<TConfig, TDeps extends Array<AnyModule> = []> = {
Expand All @@ -13,18 +14,87 @@ export type ModuleInitializerArgs<TConfig, TDeps extends Array<AnyModule> = []>
// | ((key: Extract<keyof ModulesInstanceType<TDeps>, string>) => boolean)
};

/**
* Interface which describes the structure of a module
*
* @TODO create a BaseModule which implements this interface
*
* @template TKey name of the module
* @template TType module instance type
* @template TConfig module configurator type
* @template TDeps optional peer module dependencies
*/
export interface Module<TKey extends string, TType, TConfig, TDeps extends Array<AnyModule> = []> {
/**
* package version
*/
version?: SemanticVersion;

/**
* uniq name of module, used as attribute name on module instances
*/
name: TKey;

/**
* _[optional]_
*
* Create a configurator builder which the consumer can use for configuring
*
* @TODO change return type to `ObservableInput`
* @TODO add reference to `IConfigurationBuilder`
*
* @param ref this would normally be the parent instance
* @returns a configurator build
*/
configure?: (ref?: any) => TConfig | Promise<TConfig>;

/**
* _[optional]_
*
* This method is called after the module initiator has created config.
* @see {@link Module.configure}
*
* @TODO change return type to `ObservableInput`
*
* @param config
* @returns void
*/
postConfigure?: (
config: Record<TKey, TConfig> & ModulesConfigType<ModulesType<TDeps>>
) => void | Promise<void>;

/**
* __[required]__
*
* Called after all configuration is done.
*
* Creates the instance to interact with
*
* @param args @see {@link ModuleInitializerArgs}
* @returns a provider instance which the consumer interact with
*/
initialize: (args: ModuleInitializerArgs<TConfig, TDeps>) => TType | Promise<TType>;

/**
* _[optional]_
*
* Called after the module is initialized
*
* @param args @see {@link ModuleInitializerArgs}
*/
postInitialize?: (args: {
ref?: any;
instance: TType;
modules: ModuleInstance; // Record<TKey, TType> & ModulesInstanceType<ModulesType<TDeps>>;
}) => ObservableInput<void>;

/**
* _[optional]_
*
* Cleanup callback
*
* @param args
*/
dispose?: (args: {
ref?: any;
instance: TType;
Expand Down
7 changes: 7 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11298,6 +11298,13 @@ semver@^6.0.0, semver@^6.3.0:
resolved "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==

semver@^7.5.0:
version "7.5.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.1.tgz#c90c4d631cf74720e46b21c1d37ea07edfab91ec"
integrity sha512-Wvss5ivl8TMRZXXESstBA4uR5iXgEN/VC5/sOcuXdVLzcdkz4HWetIoRfG5gb5X+ij/G9rw9YoGn3QoQ8OCSpw==
dependencies:
lru-cache "^6.0.0"

send@0.18.0:
version "0.18.0"
resolved "https://registry.npmjs.org/send/-/send-0.18.0.tgz"
Expand Down

0 comments on commit 3efbf0b

Please sign in to comment.