# HG changeset patch # User cin # Date 2022-05-27 09:54:18 # Node ID 83fa5814462d5ed3c76a4f227d0cb8c8b7249f51 # Parent d9e74143f779f1b80b332286f85bb522d2020c5d Working on container builder diff --git a/.eslintrc.json b/.eslintrc.json --- a/.eslintrc.json +++ b/.eslintrc.json @@ -26,6 +26,7 @@ { "ignoreExpressions": true, "max": 1 } ], "@typescript-eslint/prefer-readonly": ["error"], + "semi": "off", "@typescript-eslint/semi": ["error"] } diff --git a/src/main/ts/ActivationContext.ts b/src/main/ts/ActivationContext.ts --- a/src/main/ts/ActivationContext.ts +++ b/src/main/ts/ActivationContext.ts @@ -1,4 +1,5 @@ import { Descriptor, ILifetime, RegistrationMap, LifetimeContainer, ConfigurableKeys } from "./interfaces"; +import { LifetimeManager } from "./LifetimeManager"; import { argumentNotNull } from "./traits"; export interface ActivationContextInfo { @@ -23,7 +24,7 @@ export class ActivationContext; - private readonly _containerLifetimeManager: LifetimeContainer; + private readonly _containerLifetimeManager: LifetimeManager; private readonly _parent: ActivationContext | undefined; @@ -35,7 +36,7 @@ export class ActivationContext>, name: string, service: Descriptor, cache = {}) { + constructor(containerLifetimeManager: LifetimeManager, services: Partial>, name: string, service: Descriptor, cache = {}) { this._name = name; this._service = service; this._cache = cache; @@ -49,7 +50,7 @@ export class ActivationContext() { - return this._containerLifetimeManager.createLifetime(); + return this._containerLifetimeManager.create(); } /** Resolves the specified dependency in the current context diff --git a/src/main/ts/Container.ts b/src/main/ts/Container.ts --- a/src/main/ts/Container.ts +++ b/src/main/ts/Container.ts @@ -1,47 +1,31 @@ import { ActivationContext } from "./ActivationContext"; import { ActivationError } from "./ActivationError"; -import { RegistrationMap, ContainerKeys, ServiceContainer, ContainerServices, ConfigurableServices, ConfigurableKeys, ServiceLocator, Descriptor} from "./interfaces"; +import { ContainerBuilder } from "./ContainerBuilder"; +import { RegistrationMap, ServiceContainer, ContainerServices, ServiceLocator, IContainerBuilder, Configurable, ContainerKeys} from "./interfaces"; import { LifetimeManager } from "./LifetimeManager"; -import { each, isKey } from "./traits"; -export class Container implements ServiceContainer { +export class Container> implements ServiceContainer { private readonly _services: Partial>>; private readonly _lifetimeManager: LifetimeManager; - private readonly _cleanup: (() => void)[]; - private _disposed: boolean; - constructor(parent?: Container) { - this._services = Object.create(parent ? parent._services : null) as typeof this._services; - this._cleanup = []; - this._services.container = { activate: () => this as ServiceLocator>}; - this._services.childContainer = { activate: () => this.createChildContainer() }; + constructor(services: Partial>>, lifetimeManager: LifetimeManager) { + this._services = services; + this._services.container = { activate: () => this as ContainerServices["container"]}; + this._services.childContainer = { activate: () => this.createChildContainer() as ContainerServices["childContainer"] }; this._disposed = false; - this._lifetimeManager = new LifetimeManager(); + this._lifetimeManager = lifetimeManager; } - register>(name: K, service: Descriptor, ConfigurableServices[K]>): void; - register>(services: {[k in K]: Descriptor, ConfigurableServices[k]>}): void; - register>(nameOrCollection: K | {[k in K]: Descriptor, ConfigurableServices[k]>}, service?: RegistrationMap>[K]) { - if (!isKey(nameOrCollection)) { - each(nameOrCollection, (v, k) => this.register(k, v)); - } else { - if (!service) - throw new Error("The service parameter must be a descriptor"); - - this._services[nameOrCollection] = service; - } + createChildContainer(): IContainerBuilder { + return new ContainerBuilder(this._services); } - createChildContainer(): ServiceContainer { - return new Container(this); - } - - resolve>(name: K): NonNullable[K]>; - resolve>(name: K, def: ContainerServices[K]): ContainerServices[K]; - resolve>(name: K, def?: ContainerServices[K]): ContainerServices[K] | undefined { + resolve>(name: K): NonNullable[K]>; + resolve, T>(name: K, def: T): NonNullable[K]> | T; + resolve, T>(name: K, def?: T) { // TODO: add logging // trace.debug("resolve {0}", name); const d = this._services[name]; @@ -52,7 +36,7 @@ export class Container() { - return this._lifetimeManager.create(); - } - destroy() { - return this.dispose(); - } - dispose() { if (this._disposed) return; this._disposed = true; - for (const f of this._cleanup) - f(); this._lifetimeManager.destroy(); } } diff --git a/src/main/ts/ContainerBuilder.ts b/src/main/ts/ContainerBuilder.ts new file mode 100644 --- /dev/null +++ b/src/main/ts/ContainerBuilder.ts @@ -0,0 +1,46 @@ +import { Container } from "./Container"; +import { DescriptorBuilder } from "./DescriptorBuilder"; +import { Configurable, ConfigurableKeys, ContainerServices, Descriptor, IContainerBuilder, IDescriptorBuilder, RegistrationMap, ServiceContainer } from "./interfaces"; +import { LifetimeManager } from "./LifetimeManager"; + +export class ContainerBuilder> implements IContainerBuilder{ + + private _pending = 1; + + private readonly _services: Partial>>; + + private readonly _lifetimeManager = new LifetimeManager(); + + constructor(parentServices?: object) { + this._services = Object.create(parentServices ? parentServices : null) as object; + } + + createServiceBuilder(name: K): IDescriptorBuilder, object> { + return new DescriptorBuilder(this._lifetimeManager, this._register(name), this._fail); + } + + build(): ServiceContainer { + this._assertBuilding(); + if(!this._complete()) + throw new Error("The configuration didn't complete."); + return new Container(this._services, this._lifetimeManager); + } + + private readonly _register = >(name: K) => (descriptor: Descriptor>) => { + this._complete(); + this._services[name] = descriptor; + }; + + private readonly _fail = (ex: unknown) => { + + }; + + private _assertBuilding() { + throw new Error("The descriptor builder is finalized"); + } + + private _complete() { + return !(--this._pending); + } + +} \ No newline at end of file diff --git a/src/main/ts/DescriptorBuilder.ts b/src/main/ts/DescriptorBuilder.ts --- a/src/main/ts/DescriptorBuilder.ts +++ b/src/main/ts/DescriptorBuilder.ts @@ -9,7 +9,7 @@ import { each, isKey, isPromise, isStrin * @template {T} Тип сервиса */ export class DescriptorBuilder implements IDescriptorBuilder { - private readonly _lifetimeContainer: LifetimeContainer; + private readonly _lifetimeManager: LifetimeManager; private readonly _cb: (d: Descriptor) => void; private readonly _eb: (err: unknown) => void; @@ -34,12 +34,12 @@ export class DescriptorBuilder) => void, eb: (err: unknown) => void) { - this._lifetimeContainer = container; + constructor(lifetimeManager: LifetimeManager, cb: (d: Descriptor) => void, eb: (err: unknown) => void) { + this._lifetimeManager = lifetimeManager; this._cb = cb; this._eb = eb; this._overrides = {}; @@ -92,7 +92,7 @@ export class DescriptorBuilder>( - this._lifetimeContainer, + this._lifetimeManager, result => { this._overrides[nameOrServices] = result; this._complete(); @@ -130,13 +130,6 @@ export class DescriptorBuilder) => T): void { - this._assertBuilding(); - this._factory = f; - this._finalize(); - this._complete(); - }*/ - value(v: T): void { this._assertBuilding(); this._cb({ @@ -150,7 +143,7 @@ export class DescriptorBuilder(activation: ActivationType, typeId?: string | object): ILifetime { switch (activation) { case "container": - return LifetimeManager.containerLifetime(this._lifetimeContainer); + return this._lifetimeManager.create(); case "hierarchy": return LifetimeManager.hierarchyLifetime(); case "context": diff --git a/src/main/ts/DescriptorImpl.ts b/src/main/ts/DescriptorImpl.ts --- a/src/main/ts/DescriptorImpl.ts +++ b/src/main/ts/DescriptorImpl.ts @@ -1,4 +1,4 @@ -import { Descriptor, ILifetime, ConfigurableKeys, DepsMap, Ref } from "./interfaces"; +import { Descriptor, ILifetime, ConfigurableKeys, DepsMap, Ref, IActivationContext } from "./interfaces"; import { ActivationContext } from "./ActivationContext"; import { each, isKey, key } from "./traits"; @@ -40,7 +40,7 @@ export class DescriptorImpl): T { + activate(context: IActivationContext): T { if (this._lifetime.has()) return this._lifetime.get(); @@ -58,7 +58,7 @@ export class DescriptorImpl) => deps ? + const makeRefs = (deps: typeof this._deps) => deps ? Object.keys(deps) .map(k => { const ref = deps[k]; diff --git a/src/main/ts/FluentConfiguration.ts b/src/main/ts/FluentConfiguration.ts --- a/src/main/ts/FluentConfiguration.ts +++ b/src/main/ts/FluentConfiguration.ts @@ -1,5 +1,5 @@ import { DescriptorBuilder } from "./DescriptorBuilder"; -import { ConfigurableKeys, ContainerServices, ConfigurableServices, RegistrationBuildersMap, ExtractRequired } from "./interfaces"; +import { ConfigurableKeys, ContainerServices, RegistrationBuildersMap, ExtractRequired, IContainerBuilder } from "./interfaces"; import { ServiceContainer } from "./interfaces"; import { argumentNotNull, each, isKey } from "./traits"; @@ -61,33 +61,11 @@ export class FluentConfiguration>(target: T) { - - let pending = 1; - - const reject = (ex: unknown) => { throw ex; }; - - const complete = () => !--pending; - + configure>(builder: T) { each(this._builders, (v, k) => { - pending++; - const d = new DescriptorBuilder, NonNullable[typeof k]>>( - target, - result => { - target.register(k, result); - complete(); - }, - reject - ); - - - v(d); + v(builder.createServiceBuilder(k)); }); - if (!complete()) - throw new Error("The configuration didn't complete."); - - return target as T & ServiceContainer; + builder.build() as T & ServiceContainer; } } diff --git a/src/main/ts/LifetimeManager.ts b/src/main/ts/LifetimeManager.ts --- a/src/main/ts/LifetimeManager.ts +++ b/src/main/ts/LifetimeManager.ts @@ -1,4 +1,4 @@ -import { IDestroyable, ILifetime } from "./interfaces"; +import { IActivationContext, IDestroyable, ILifetime } from "./interfaces"; import { ActivationContext } from "./ActivationContext"; import { argumentNotNull, isDestroyable } from "./traits"; @@ -115,7 +115,7 @@ export class LifetimeManager implements static hierarchyLifetime() { let _lifetime: ILifetime = unknownLifetime; return { - initialize(context: ActivationContext) { + initialize(context: IActivationContext) { if (_lifetime !== unknownLifetime) throw new Error("Cyclic reference activation detected"); diff --git a/src/main/ts/interfaces.ts b/src/main/ts/interfaces.ts --- a/src/main/ts/interfaces.ts +++ b/src/main/ts/interfaces.ts @@ -1,8 +1,6 @@ import { ActivationContext } from "./ActivationContext"; import { key } from "./traits"; -export type primitive = number | string | null | undefined | symbol; - export interface IDestroyable { destroy(): void; } @@ -32,10 +30,10 @@ export interface Resolver = { [k in K]: SK | Ref }; -export type Ref = { name: K, lazy?: L} | { name: K, lazy?: L, default: D }; +export type Ref = { name: K, lazy?: L } | { name: K, lazy?: L, default: D }; export type Resolved = - L extends true ? () => NonNullable | (unknown extends D ? never : D) : NonNullable | (unknown extends D ? never : D); + L extends true ? () => NonNullable | (unknown extends D ? never : D) : NonNullable | (unknown extends D ? never : D); export interface IDescriptorBuilder { @@ -49,7 +47,7 @@ export interface IDescriptorBuilder : - X[k] extends Ref ? Resolved: + X[k] extends Ref ? Resolved : never }> @@ -67,37 +65,43 @@ export interface IDescriptorBuilder = (d: IDescriptorBuilder) => void; export type RegistrationBuildersMap = ConfigurableKeys> = { - [k in K]-?: RegistrationBuilder, NonNullable[k]>> + [k in K]-?: RegistrationBuilder, NonNullable> }; export interface Descriptor { - activate(context: ActivationContext): T; + activate(context: IActivationContext): T; } -export type ConfigurableDescriptor> = Descriptor, ConfigurableServices[K]>; +export interface IActivationContext extends ServiceLocator { + createLifetime(): ILifetime; + + createContainerLifetime(): ILifetime; +} + +export type ConfigurableDescriptor> = Descriptor, S[K]>; export type RegistrationMap = { [k in K]-?: Descriptor; }; -export interface ProvidedServices { +export interface ContainerProvided> { container: ServiceLocator>; - childContainer: ServiceContainer; + childContainer: IContainerBuilder; } -export type ProvidedKeys = keyof ProvidedServices; - -export type ContainerKeys = keyof ContainerServices; +export type Configurable = { [k in keyof S & (keyof ContainerProvided)]: never; }; -export type Mix = { [k in keyof (S & X)]: k extends keyof X ? X[k] : S[k & keyof S] }; +export type ProvidedKeys = keyof ContainerProvided; -export type ContainerServices = Mix>; +export type ContainerServices> = S & ContainerProvided; export type ConfigurableKeys = Exclude; export type ConfigurableServices = Pick>; +export type ContainerKeys> = keyof S | keyof ContainerProvided; + export interface ServiceLocator { resolve(name: K): NonNullable; resolve(name: K, def: T): NonNullable | T; @@ -107,12 +111,17 @@ export interface LifetimeContainer { createLifetime(): ILifetime; } -export interface ServiceContainer extends ServiceLocator>, LifetimeContainer, IDestroyable { +export interface ServiceContainer> extends + ServiceLocator>, + IDestroyable { - register>(name: K, service: ConfigurableDescriptor): void; - register>(services: { [k in K]: ConfigurableDescriptor }): void; + createChildContainer(): IContainerBuilder; +} - createChildContainer(): ServiceContainer; +export interface IContainerBuilder> { + createServiceBuilder(name: K): IDescriptorBuilder>; + + build(): ServiceContainer; } @@ -128,7 +137,7 @@ export interface ILifetime { get(): T; - initialize(context: ActivationContext): void; + initialize(context: IActivationContext): void; store(item: T, cleanup?: (item: T) => void): void; } diff --git a/src/test/ts/t/container.ts b/src/test/ts/t/container.ts --- a/src/test/ts/t/container.ts +++ b/src/test/ts/t/container.ts @@ -1,6 +1,7 @@ /* eslint max-classes-per-file: ["error", 20] */ import { describe, it } from "mocha"; import { Container } from "../Container"; +import { ContainerServices } from "../interfaces"; import { fluent } from "../traits"; class Foo { @@ -59,7 +60,11 @@ const config = fluent() }) .done({}); -declare const container: Container>; -const c2 = config.apply(container); +declare const container: Container; +const c2 = config.configure(container); -c2.resolve("foo"); \ No newline at end of file +c2.resolve("foo"); + +declare const m :ContainerServices<{foo: Foo}>["container"]; + +m.resolve("container").resolve("container"); \ No newline at end of file