diff --git a/src/main/ts/di/ActivationContext.ts b/src/main/ts/di/ActivationContext.ts --- a/src/main/ts/di/ActivationContext.ts +++ b/src/main/ts/di/ActivationContext.ts @@ -1,7 +1,6 @@ import { TraceSource } from "../log/TraceSource"; import { argumentNotEmptyString } from "../safe"; -import { Descriptor, ContainerServiceMap, ContainerKeys, TypeOfService, ILifetime } from "./interfaces"; -import { Container } from "./Container"; +import { Descriptor, ContainerServiceMap, ContainerKeys, TypeOfService, ILifetime, ServiceContainer } from "./interfaces"; import { MapOf } from "../interfaces"; const trace = TraceSource.get("@implab/core/di/ActivationContext"); @@ -30,7 +29,7 @@ export class ActivationContext; - _container: Container; + _container: ServiceContainer; _parent: ActivationContext | undefined; @@ -42,7 +41,7 @@ export class ActivationContext, services: ContainerServiceMap, name: string, service: Descriptor) { + constructor(container: ServiceContainer, services: ContainerServiceMap, name: string, service: Descriptor) { this._name = name; this._service = service; this._visited = {}; diff --git a/src/main/ts/di/Configuration.ts b/src/main/ts/di/Configuration.ts --- a/src/main/ts/di/Configuration.ts +++ b/src/main/ts/di/Configuration.ts @@ -3,13 +3,12 @@ import { ActivationType, ContainerKeys, TypeOfService, - ILifetime + ILifetime, ServiceContainer } from "./interfaces"; import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get, primitive } from "../safe"; import { AggregateDescriptor } from "./AggregateDescriptor"; import { ValueDescriptor } from "./ValueDescriptor"; -import { Container } from "./Container"; import { ReferenceDescriptor } from "./ReferenceDescriptor"; import { TypeServiceDescriptor } from "./TypeServiceDescriptor"; import { FactoryServiceDescriptor } from "./FactoryServiceDescriptor"; @@ -149,7 +148,7 @@ export class Configuration; + readonly _container: ServiceContainer; _path: Array; @@ -157,7 +156,7 @@ export class Configuration) { + constructor(container: ServiceContainer) { argumentNotNull(container, "container"); this._container = container; this._path = []; diff --git a/src/main/ts/di/Container.ts b/src/main/ts/di/Container.ts --- a/src/main/ts/di/Container.ts +++ b/src/main/ts/di/Container.ts @@ -1,11 +1,11 @@ import { ActivationContext } from "./ActivationContext"; import { ValueDescriptor } from "./ValueDescriptor"; import { ActivationError } from "./ActivationError"; -import { ServiceMap, Descriptor, PartialServiceMap, ServiceLocator, ContainerServiceMap, ContainerKeys, TypeOfService } from "./interfaces"; +import { ServiceMap, Descriptor, PartialServiceMap, ContainerServiceMap, ContainerKeys, TypeOfService, ServiceContainer } from "./interfaces"; import { TraceSource } from "../log/TraceSource"; import { Configuration, RegistrationMap } from "./Configuration"; import { Cancellation } from "../Cancellation"; -import { IDestroyable, PromiseOrValue, ICancellation } from "../interfaces"; +import { IDestroyable, ICancellation } from "../interfaces"; import { isDescriptor } from "./traits"; import { LifetimeManager } from "./LifetimeManager"; import { each, isString } from "../safe"; @@ -14,7 +14,7 @@ import { FluentConfiguration } from "./f const trace = TraceSource.get("@implab/core/di/ActivationContext"); -export class Container implements ServiceLocator, IDestroyable { +export class Container implements ServiceContainer, IDestroyable { readonly _services: ContainerServiceMap; readonly _lifetimeManager: LifetimeManager; @@ -91,6 +91,7 @@ export class Container void) { if (!(callback instanceof Function)) throw new Error("The callback must be a function"); @@ -134,13 +135,13 @@ export class Container(config: Promise<{ default: ContainerConfiguration; }>, ct?: ICancellation): Promise>; - applyConfig(config: Promise<{ [p in P]: ContainerConfiguration; }>, prop: P, ct?: ICancellation): Promise>; + applyConfig(config: Promise<{ default: ContainerConfiguration; }>, ct?: ICancellation): Promise>; + applyConfig(config: Promise<{ [p in P]: ContainerConfiguration; }>, prop: P, ct?: ICancellation): Promise>; async applyConfig( config: Promise<{ [p in P | "default"]: ContainerConfiguration; }>, propOrCt?: P | ICancellation, ct?: ICancellation - ): Promise> { + ): Promise> { const mod = await config; let _ct: ICancellation; diff --git a/src/main/ts/di/LifetimeManager.ts b/src/main/ts/di/LifetimeManager.ts --- a/src/main/ts/di/LifetimeManager.ts +++ b/src/main/ts/di/LifetimeManager.ts @@ -1,8 +1,7 @@ import { IDestroyable, MapOf } from "../interfaces"; -import { argumentNotNull, isDestroyable, primitive, isNull, argumentNotEmptyString } from "../safe"; -import { ILifetime } from "./interfaces"; +import { argumentNotNull, isDestroyable, argumentNotEmptyString } from "../safe"; +import { ILifetime, ServiceContainer } from "./interfaces"; import { ActivationContext } from "./ActivationContext"; -import { Container } from "./Container"; function safeCall(item: () => void) { try { @@ -48,7 +47,7 @@ const unknownLifetime: ILifetime = Objec let nextId = 0; -const singletons: { [k in keyof any]: any; } = {}; +const singletons: any = {}; export class LifetimeManager implements IDestroyable { private _cleanup: (() => void)[] = []; @@ -176,7 +175,7 @@ export class LifetimeManager implements }; } - static containerLifetime(container: Container) { + static containerLifetime(container: ServiceContainer) { let _lifetime = unknownLifetime; return { initialize(context: ActivationContext) { diff --git a/src/main/ts/di/fluent/DescriptorBuilder.ts b/src/main/ts/di/fluent/DescriptorBuilder.ts --- a/src/main/ts/di/fluent/DescriptorBuilder.ts +++ b/src/main/ts/di/fluent/DescriptorBuilder.ts @@ -1,12 +1,11 @@ import { Resolver, RegistrationBuilder } from "./interfaces"; -import { Container } from "../Container"; -import { Descriptor, ILifetime, ActivationType, PartialServiceMap } from "../interfaces"; +import { Descriptor, ILifetime, ActivationType, PartialServiceMap, ServiceContainer } from "../interfaces"; import { DescriptorImpl } from "./DescriptorImpl"; import { LifetimeManager } from "../LifetimeManager"; import { isString, each, isPrimitive, isPromise, oid } from "../../safe"; export class DescriptorBuilder { - private readonly _container: Container; + private readonly _container: ServiceContainer; private readonly _cb: (d: Descriptor) => void; private readonly _eb: (err: any) => void; @@ -23,7 +22,7 @@ export class DescriptorBuilder, cb: (d: Descriptor) => void, eb: (err: any) => void) { + constructor(container: ServiceContainer, cb: (d: Descriptor) => void, eb: (err: any) => void) { this._container = container; this._cb = cb; this._eb = eb; diff --git a/src/main/ts/di/fluent/FluentConfiguration.ts b/src/main/ts/di/fluent/FluentConfiguration.ts --- a/src/main/ts/di/fluent/FluentConfiguration.ts +++ b/src/main/ts/di/fluent/FluentConfiguration.ts @@ -1,8 +1,8 @@ -import { Container } from "../Container"; import { argumentNotNull, each, isPrimitive, isPromise } from "../../safe"; import { DescriptorBuilder } from "./DescriptorBuilder"; import { RegistrationBuilder, FluentRegistrations, ContainerConfiguration } from "./interfaces"; import { Cancellation } from "../../Cancellation"; +import { ServiceContainer } from "../interfaces"; export class FluentConfiguration { @@ -29,13 +29,13 @@ export class FluentConfiguration(target: Container, ct = Cancellation.none) { + apply(target: ServiceContainer, ct = Cancellation.none) { let pending = 1; - const _t2 = target as unknown as Container; + const _t2 = target as unknown as ServiceContainer; - return new Promise>((resolve, reject) => { + return new Promise>((resolve, reject) => { function guard(v: void | Promise) { if (isPromise(v)) v.catch(reject); @@ -47,7 +47,7 @@ export class FluentConfiguration { pending++; - const d = new DescriptorBuilder(_t2, + const d = new DescriptorBuilder(_t2, result => { _t2.register(k, result); complete(); diff --git a/src/main/ts/di/fluent/interfaces.ts b/src/main/ts/di/fluent/interfaces.ts --- a/src/main/ts/di/fluent/interfaces.ts +++ b/src/main/ts/di/fluent/interfaces.ts @@ -1,7 +1,6 @@ import { primitive } from "../../safe"; -import { TypeOfService, ContainerKeys, ActivationType, ILifetime } from "../interfaces"; +import { TypeOfService, ContainerKeys, ActivationType, ILifetime, ServiceContainer } from "../interfaces"; import { ICancellation } from "../../interfaces"; -import { Container } from "../Container"; export interface DependencyOptions { optional?: boolean; @@ -49,7 +48,7 @@ export interface DescriptorBuilder { - apply(target: Container, ct: ICancellation): Promise>; + apply(target: ServiceContainer, ct?: ICancellation): Promise>; } export type RegistrationBuilder = (d: DescriptorBuilder, ct?: ICancellation) => void | Promise; diff --git a/src/main/ts/di/interfaces.ts b/src/main/ts/di/interfaces.ts --- a/src/main/ts/di/interfaces.ts +++ b/src/main/ts/di/interfaces.ts @@ -1,4 +1,5 @@ import { ActivationContext } from "./ActivationContext"; +import { LifetimeManager } from "./LifetimeManager"; export interface Descriptor { activate(context: ActivationContext): T; @@ -26,6 +27,14 @@ export interface ServiceLocator>(name: K, def?: TypeOfService): TypeOfService; } +export interface ServiceContainer extends ServiceLocator { + getLifetimeManager(): LifetimeManager; + register(name: K, service: Descriptor): this; + register(services: PartialServiceMap): this; + + createChildContainer(): ServiceContainer; +} + export interface ContainerProvided { container: ServiceLocator; } diff --git a/src/test/ts/mock/services.ts b/src/test/ts/mock/services.ts --- a/src/test/ts/mock/services.ts +++ b/src/test/ts/mock/services.ts @@ -20,3 +20,7 @@ export interface ChildServices extends S bar: Bar; } + +export interface FooServices { + foo: Foo; +} diff --git a/src/test/ts/tests/FluentContainerTests.ts b/src/test/ts/tests/FluentContainerTests.ts --- a/src/test/ts/tests/FluentContainerTests.ts +++ b/src/test/ts/tests/FluentContainerTests.ts @@ -5,7 +5,8 @@ import { Container } from "../di/Contain import { Foo } from "../mock/Foo"; import { Box } from "../mock/Box"; import { delay } from "../safe"; -import { Services } from "../mock/services"; +import { FooServices, Services } from "../mock/services"; +import { ContainerConfiguration } from "../di/fluent/interfaces"; test("Simple fluent config", async t => { const config = fluent<{ host: string; bar: Bar; foo: Foo }>() @@ -67,3 +68,13 @@ test("Container applyConfig", async t => t.assert(container.resolve("host"), "Should resolve simple value"); }); + +test("Child container config", async t => { + const container = await new Container<{}>().applyConfig(import("../mock/config")); + + const fooServices: ContainerConfiguration = (await import("../mock/config2")).default; + + const child = await fooServices.apply(container.createChildContainer()); + + t.assert(child.resolve("foo"), "foo should be resolved"); +});