Skip to content

Commit

Permalink
feat(core-kernel): support creation of scoped child containers
Browse files Browse the repository at this point in the history
  • Loading branch information
Brian Faust committed Aug 22, 2019
1 parent ade3a5d commit 9d12bb1
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 22 deletions.
81 changes: 68 additions & 13 deletions packages/core-kernel/src/container.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
Resolver,
} from "awilix";
import { isClass, isFunction } from "typechecker";
import { InvalidType } from "./errors/kernel";
import { InvalidBindingName, InvalidType } from "./errors/kernel";

/**
* @export
Expand All @@ -30,18 +30,6 @@ export class Container {
*/
private readonly container: AwilixContainer = createContainer();

/**
* Resolves the registration with the given name.
*
* @template T
* @param {string} name
* @returns {T}
* @memberof Container
*/
public resolve<T = any>(name: string): T {
return this.container.resolve<T>(name);
}

/**
* @TODO: remove any after initial migration
*
Expand All @@ -53,6 +41,10 @@ export class Container {
* @memberof Container
*/
public bind<T = any>(name: string, concrete: T | ClassOrFunctionReturning<T>): void {
if (this.usesReservedBindingName(name)) {
throw new InvalidBindingName(name);
}

let binding: Resolver<T> | BuildResolver<T> & DisposableResolver<T>;

if (isClass(concrete)) {
Expand All @@ -77,6 +69,10 @@ export class Container {
* @memberof Container
*/
public singleton<T = any>(name: string, concrete: ClassOrFunctionReturning<T>): void {
if (this.usesReservedBindingName(name)) {
throw new InvalidBindingName(name);
}

let binding: BuildResolver<T> & DisposableResolver<T>;

if (isClass(concrete)) {
Expand All @@ -90,6 +86,32 @@ export class Container {
this.container.register<T>(name, binding.singleton());
}

/**
* Resolves the registration with the given name.
*
* @template T
* @param {string} name
* @returns {T}
* @memberof Container
*/
public resolve<T = any>(name: string): T {
return this.container.resolve<T>(name);
}

/**
* Disposes this container and it's children, calling the disposer
* on all disposable registrations and clearing the cache.
*
* @remarks
* Only applies to registrations with `SCOPED` or `SINGLETON` lifetime.
*
* @returns {Promise<void>}
* @memberof Container
*/
public async dispose(): Promise<void> {
this.container.dispose();
}

/**
* Resolves to the specified registration.
*
Expand Down Expand Up @@ -126,4 +148,37 @@ export class Container {
public build<T>(targetOrResolver: ClassOrFunctionReturning<T> | Resolver<T>, opts?: BuildResolverOptions<{}>) {
return this.container.build(targetOrResolver, opts);
}

/**
* Creates a scoped container with this one as the parent.
*
* @remark
* By default all scopes are returned as-is and not bound to the container!
*
* This is due to the fact that scopes are recommended to be used internally
* in your plugin to have an isolated container that doesn't pollute the core
* container and gives you full access to the power of {@link https://github.com/jeffijoe/awilix | awilix}.
*
* Use scopes with care as they are completely under your own control, detached from core.
*
* @returns {AwilixContainer}
* @memberof Container
*/
public createScope(): AwilixContainer {
return this.container.createScope();
}

/**
* @private
* @param {string} name
* @returns {boolean}
* @memberof Container
*/
private usesReservedBindingName(name: string): boolean {
if (name.startsWith("scopes.")) {
return true;
}

return false;
}
}
47 changes: 38 additions & 9 deletions packages/core-kernel/src/contracts/core-kernel/container.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,47 @@
import { BuildResolverOptions, ClassOrFunctionReturning, Resolver } from "awilix";
import { AwilixContainer } from "awilix";

export interface IContainer {
/**
* Resolve the given name from the container.
* Register a class within the container.
*
* @template T
* @param {string} name
* @returns {T}
* @param {(T | ClassOrFunctionReturning<T>)} concrete
* @memberof IContainer
*/
resolve<T = any>(name: string): T;
bind<T = any>(name: string, concrete: T | ClassOrFunctionReturning<T>): void;

/**
* Register a class within the container.
*
* @template T
* @param {string} name
* @param {(T | ClassOrFunctionReturning<T>)} concrete
* @param {ClassOrFunctionReturning<T>} concrete
* @memberof IContainer
*/
bind<T = any>(name: string, concrete: T | ClassOrFunctionReturning<T>): void;

singleton<T = any>(name: string, concrete: ClassOrFunctionReturning<T>): void;
/**
* Register a class within the container.
* Resolve the given name from the container.
*
* @template T
* @param {string} name
* @param {ClassOrFunctionReturning<T>} concrete
* @returns {T}
* @memberof IContainer
*/
singleton<T = any>(name: string, concrete: ClassOrFunctionReturning<T>): void;
resolve<T = any>(name: string): T;

/**
* Disposes this container and it's children, calling the disposer
* on all disposable registrations and clearing the cache.
*
* @remarks
* Only applies to registrations with `SCOPED` or `SINGLETON` lifetime.
*
* @returns {Promise<void>}
* @memberof Container
*/
dispose(): Promise<void>;

/**
* Alias a registration to a different name.
Expand Down Expand Up @@ -59,4 +71,21 @@ export interface IContainer {
* @memberof IContainer
*/
build<T>(targetOrResolver: ClassOrFunctionReturning<T> | Resolver<T>, opts?: BuildResolverOptions<{}>): any;

/**
* Creates a scoped container with this one as the parent.
*
* @remark
* By default all scopes are returned as-is and not bound to the container!
*
* This is due to the fact that scopes are recommended to be used internally
* in your plugin to have an isolated container that doesn't pollute the core
* container and gives you full access to the power of {@link https://github.com/jeffijoe/awilix | awilix}.
*
* Use scopes with care as they are completely under your own control, detached from core.
*
* @returns {AwilixContainer}
* @memberof IContainer
*/
createScope(): AwilixContainer;
}
15 changes: 15 additions & 0 deletions packages/core-kernel/src/errors/kernel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,3 +218,18 @@ export class NotImplementedError extends KernelError {
super(`Method [${method}] is not implemented in [${klass}].`);
}
}

/**
* @export
* @class InvalidBindingName
* @extends {KernelError}
*/
export class InvalidBindingName extends KernelError {
/**
* @param {string} name
* @memberof InvalidBindingName
*/
constructor(name: string) {
super(`The name/prefix [${name}] is reserved.`);
}
}

0 comments on commit 9d12bb1

Please sign in to comment.