Skip to content

Commit

Permalink
Merge branch 'teunmooij-master'
Browse files Browse the repository at this point in the history
  • Loading branch information
cressie176 committed Aug 7, 2022
2 parents 7b3467a + 2ff545a commit 34995b2
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 7 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 4.1.0

- Added optional dependencies
- Updated typescript definitions to better reflect all options of Systemic:
- optional type param on `Systemic` to set assumed components of master system when creating a subsystem
- allow setting a simple dependency on component that doesn't need it (to force the order in which dependencies are created)

## 4.0.3

- Remove standard-version because it is deprecated and doesnt add much value
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,19 @@ module.exports = () => Systemic()
```
<!-- prettier-ignore-end -->

#### Optional Dependencies

By default an error is thrown if a dependency is not available on system start. Sometimes a component might have an optional dependency on a component they may or may not be available in the system, typically when using subsystems. In this situation a dependency can be marked as optional.

<!-- prettier-ignore-start -->
```js
module.exports = () => Systemic()
.add('app', app())
.add('server', server())
.dependsOn('app', { component: 'routes', optional: true });
```
<!-- prettier-ignore-end -->

#### Overriding Components

Attempting to add the same component twice will result in an error, but sometimes you need to replace existing components with test doubles. Under such circumstances use `set` instead of `add`
Expand Down
12 changes: 6 additions & 6 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,19 +37,21 @@ export type Component<TComponent, TDependencies extends Record<string, unknown>
stop?: () => Promise<void>;
};

type SimpleDependsOnOption<TDependencyKeys, TSystemic> = TDependencyKeys & keyof TSystemic;
type SimpleDependsOnOption<TSystemic> = keyof TSystemic;
type MappingDependsOnOption<TDependencyKeys, TSystemic> = TDependencyKeys extends keyof TSystemic
? {
component: keyof TSystemic;
destination?: TDependencyKeys;
optional?: boolean;
source?: string;
}
: {
component: keyof TSystemic;
destination: TDependencyKeys;
optional?: boolean;
source?: string;
};
type DependsOnOption<TDependencyKeys, TSystemic> = SimpleDependsOnOption<TDependencyKeys, TSystemic> | MappingDependsOnOption<TDependencyKeys, TSystemic>;
type DependsOnOption<TDependencyKeys, TSystemic> = SimpleDependsOnOption<TSystemic> | MappingDependsOnOption<TDependencyKeys, TSystemic>;

type DependsOn<TSystemic extends Record<string, unknown>, TDependencies extends Record<string, unknown>> = {
/**
Expand All @@ -60,9 +62,7 @@ type DependsOn<TSystemic extends Record<string, unknown>, TDependencies extends
dependsOn: <TNames extends DependsOnOption<keyof TDependencies, TSystemic>[]>(...names: TNames) => SystemicBuild<TSystemic, MissingDependencies<TDependencies, TNames>>;
};

type SystemicBuild<TSystemic extends Record<string, unknown>, TDependencies extends Record<string, unknown>> = [RequiredKeys<TDependencies>] extends [never]
? Systemic<TSystemic> & ([keyof TDependencies] extends [never] ? {} : DependsOn<TSystemic, TDependencies>)
: DependsOn<TSystemic, TDependencies>;
type SystemicBuild<TSystemic extends Record<string, unknown>, TDependencies extends Record<string, unknown>> = [RequiredKeys<TDependencies>] extends [never] ? Systemic<TSystemic> & DependsOn<TSystemic, TDependencies> : DependsOn<TSystemic, TDependencies>;

/**
* Systemic system.
Expand Down Expand Up @@ -158,6 +158,6 @@ export type Systemic<T extends Record<string, unknown>> = {
* Creates a system to which components for dependency injection can be added
* @returns An empty systemic system
*/
declare function Systemic(options?: { name?: string }): Systemic<{}>;
declare function Systemic<TMaster extends Record<string, unknown> = {}>(options?: { name?: string }): Systemic<TMaster>;

export default Systemic;
7 changes: 6 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ module.exports = function (_params) {
? {
component: arg,
destination: arg,
optional: false,
}
: Object.assign({}, { destination: arg.component }, arg);
if (!record.component) throw new Error(format('Component %s has an invalid dependency %s', currentDefinition.name, JSON.stringify(arg)));
Expand Down Expand Up @@ -197,7 +198,11 @@ module.exports = function (_params) {
definitions[name].dependencies,
{},
(accumulator, dependency, cb) => {
if (!hasProp(definitions, dependency.component)) return cb(new Error(format('Component %s has an unsatisfied dependency on %s', name, dependency.component)));
if (!hasProp(definitions, dependency.component) && !dependency.optional) return cb(new Error(format('Component %s has an unsatisfied dependency on %s', name, dependency.component)));
if (!hasProp(definitions, dependency.component)) {
debug('Skipping unsatisfied optional dependency %s for component %s', dependency.component, name);
return cb(null, accumulator);
}
if (!dependency.hasOwnProperty('source') && definitions[dependency.component].scoped) dependency.source = name;
dependency.source ? debug('Injecting dependency %s.%s as %s into %s', dependency.component, dependency.source, dependency.destination, name) : debug('Injecting dependency %s as %s into %s', dependency.component, dependency.destination, name);
const component = getProp(system, dependency.component);
Expand Down
13 changes: 13 additions & 0 deletions test/systemic.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,19 @@ describe('System', () => {
});
});

it('should accept missing optional dependency', (test, done) => {
system
.add('bar', new CallbackComponent())
.add('foo', new CallbackComponent())
.dependsOn({ component: 'bar', optional: true }, { component: 'baz', optional: true })
.start((err, components) => {
assert.ifError(err);
assert(components.foo.dependencies.bar);
assert.equal(components.foo.dependencies.baz, undefined);
done();
});
});

it('should reject invalid dependencies', () => {
assert.throws(
() => {
Expand Down

0 comments on commit 34995b2

Please sign in to comment.