diff --git a/src/main/ts/ActivationError.ts b/src/main/ts/ActivationError.ts --- a/src/main/ts/ActivationError.ts +++ b/src/main/ts/ActivationError.ts @@ -4,13 +4,13 @@ export interface ActivationItem { } export class ActivationError { - activationStack: ActivationItem[]; + readonly activationStack: ActivationItem[]; - service: string; + readonly service: string; - innerException: unknown; + readonly innerException: unknown; - message: string; + readonly message: string; constructor(service: string, activationStack: ActivationItem[], innerException: unknown) { this.message = "Failed to activate the service"; diff --git a/src/main/ts/ContextResolver.ts b/src/main/ts/ContextResolver.ts deleted file mode 100644 --- a/src/main/ts/ContextResolver.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { ActivationContext } from "./ActivationContext"; - -export class ContextResolver { - private readonly _context: ActivationContext; - - constructor(context: ActivationContext) { - this._context = context; - } - - resolve(name: K, opts?: O): () => (O extends { default: infer T } ? T : never) | NonNullable; - resolve(name: K, opts?: O): (O extends { default: infer T } ? T : never) | NonNullable; - resolve(name: keyof S, opts?: {lazy?: boolean, default?: unknown}) { - if (opts && opts.lazy) { - return () => "default" in opts ? this._context.resolve(name, opts.default) : this._context.resolve(name); - } else { - return opts && "default" in opts ? this._context.resolve(name, opts.default) : this._context.resolve(name); - } - } -} 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 @@ -1,26 +1,28 @@ -import { Resolver, RegistrationBuilder, LifetimeContainer, ConfigurableKeys } from "./interfaces"; +import { RegistrationBuilder, LifetimeContainer, ConfigurableKeys, IDescriptorBuilder, Ref, Resolved, DepsMap } from "./interfaces"; import { Descriptor, ILifetime, ActivationType } from "./interfaces"; import { DescriptorImpl, RegistrationOverridesMap } from "./DescriptorImpl"; import { LifetimeManager } from "./LifetimeManager"; -import { each, isKey, isPromise, isString, oid } from "./traits"; +import { each, isKey, isPromise, isString, key, oid } from "./traits"; /** * @template {S} Карта доступных зависимостей, как правило `ContainerServices` * @template {T} Тип сервиса */ -export class DescriptorBuilder { +export class DescriptorBuilder implements IDescriptorBuilder { private readonly _lifetimeContainer: LifetimeContainer; private readonly _cb: (d: Descriptor) => void; private readonly _eb: (err: unknown) => void; + private readonly _refs: DepsMap; + private _lifetime = LifetimeManager.empty(); private _overrides: RegistrationOverridesMap; private _cleanup?: (item: T) => void; - private _factory?: (resolve: Resolver) => T; + private _factory?: (refs: R) => T; private _pending = 1; @@ -41,7 +43,32 @@ export class DescriptorBuilder]: keyof S | Ref; }>(refs: X): + IDescriptorBuilder : + X[k] extends Ref ? Resolved : + never; + }> { + + each(refs, (v, k) => this._refs[k] = v); + + return this as IDescriptorBuilder : + X[k] extends Ref ? Resolved : + never; + }>; + } + factory(f: (refs: R) => T): void { + this._assertBuilding(); + this._factory = f; + this._finalize(); + this._complete(); + } + private _assertBuilding() { if (this._finalized) @@ -103,12 +130,12 @@ export class DescriptorBuilder) => T): void { + /*factory(f: (resolve: Resolver) => T): void { this._assertBuilding(); this._factory = f; this._finalize(); this._complete(); - } + }*/ value(v: T): void { this._assertBuilding(); 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,18 +1,19 @@ -import { Descriptor, ILifetime, ConfigurableKeys, Resolver } from "./interfaces"; +import { Descriptor, ILifetime, ConfigurableKeys, DepsMap, Ref } from "./interfaces"; import { ActivationContext } from "./ActivationContext"; -import { ContextResolver } from "./ContextResolver"; -import { each } from "./traits"; +import { each, isKey, key } from "./traits"; export type RegistrationOverridesMap = { [k in ConfigurableKeys]?: Descriptor> }; export interface DescriptorImplArgs { lifetime: ILifetime; - factory: (resolve: Resolver) => T; + factory: (refs: Record) => T; cleanup?: (item: T) => void; overrides?: RegistrationOverridesMap; + + dependencies?: DepsMap; } @@ -22,17 +23,21 @@ export class DescriptorImpl; - private readonly _factory: (resolve: Resolver) => T; + private readonly _factory: (refs: Record) => T; private readonly _cleanup?: (item: T) => void; - constructor(args: DescriptorImplArgs) { - this._lifetime = args.lifetime; - this._factory = args.factory; - if (args.cleanup) - this._cleanup = args.cleanup; - if (args.overrides) - this._overrides = args.overrides; + private readonly _deps?: DepsMap; + + constructor({ lifetime, factory, cleanup, overrides, dependencies }: DescriptorImplArgs) { + this._lifetime = lifetime; + this._factory = factory; + if (cleanup) + this._cleanup = cleanup; + if (overrides) + this._overrides = overrides; + if (dependencies) + this._deps = dependencies; } activate(context: ActivationContext): T { @@ -45,11 +50,26 @@ export class DescriptorImpl context.register(k, v)); - - const resolver = new ContextResolver(context); + const resolve = ({ name, lazy, ...opts }: Ref) => { + if (lazy) { + return () => "default" in opts ? context.resolve(name, opts.default) : context.resolve(name); + } else { + return "default" in opts ? context.resolve(name, opts.default) : context.resolve(name); + } + }; + const makeRefs = (deps?: DepsMap) => deps ? + Object.keys(deps) + .map(k => { + const ref = deps[k]; + return isKey(ref) ? + { [k]: resolve({ name: ref }) } : + { [k]: resolve(ref) }; + }) + .reduce((a, p) => ({ ...a, ...p }), {} ) as Record: + {} as Record; - const instance = this._factory.call(undefined, resolver.resolve.bind(resolver)); + const instance = this._factory.call(undefined, makeRefs(this._deps)); this._lifetime.store(instance, this._cleanup); 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 @@ -62,21 +62,20 @@ export class FluentConfiguration>(target: ServiceContainer) { + apply>(target: T) { let pending = 1; - const _t2 = target as ServiceContainer; - const reject = (ex: unknown) => { throw ex; }; const complete = () => !--pending; each(this._builders, (v, k) => { pending++; - const d = new DescriptorBuilder, NonNullable[typeof k]>>(_t2, + const d = new DescriptorBuilder, NonNullable[typeof k]>>( + target, result => { - _t2.register(k, result); + target.register(k, result); complete(); }, reject @@ -88,7 +87,7 @@ export class FluentConfiguration; + return target as T & ServiceContainer; } } 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,4 +1,5 @@ import { ActivationContext } from "./ActivationContext"; +import { key } from "./traits"; export type primitive = number | string | null | undefined | symbol; @@ -29,13 +30,28 @@ export interface Resolver(name: K, opts?: O): (O extends { default: infer T } ? T : never) | NonNullable; } -export interface DescriptorBuilder { +export type DepsMap = { [k in K]: SK | Ref }; + +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); + +export interface IDescriptorBuilder { /** * * @param f */ - factory(f: (resolve: Resolver) => T): void; + factory(f: (refs: R) => T): void; + + wants, keyof S>>(refs: X): + IDescriptorBuilder : + X[k] extends Ref ? Resolved: + never + }> override>(name: K, builder: RegistrationBuilder>): this; override>(services: { [name in K]: RegistrationBuilder> }): this; @@ -48,7 +64,7 @@ export interface DescriptorBuilder = (d: DescriptorBuilder) => void; +export type RegistrationBuilder = (d: IDescriptorBuilder) => void; export type RegistrationBuildersMap = ConfigurableKeys> = { [k in K]-?: RegistrationBuilder, NonNullable[k]>> 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,7 +1,6 @@ /* eslint max-classes-per-file: ["error", 20] */ import { describe, it } from "mocha"; -import {LifetimeManager} from "../LifetimeManager"; -import {Container} from "../Container"; +import { Container } from "../Container"; import { fluent } from "../traits"; class Foo { @@ -11,7 +10,7 @@ class Foo { class Bar { bar = "bar"; - constructor(foo?: () => Foo) {} + constructor(foo?: () => Foo) { } } interface Services { @@ -44,15 +43,23 @@ const config = fluent() .declare() .register({ bar: it => it - .lifetime("context") - .factory($ => new Bar($("zoo", {lazy: true, default: new Foo()}))), - foo: it => it.factory($ => new Foo()), + .lifetime("context") // тип активации, время жизни + .wants({ + zoo: "zoo", // зависимость + + zoo$: { name: "zoo", lazy: true } // отложенная активация, + //фабричный метод + }) + .factory(({ zoo$ }) => // фабрика получает объект с именованными зависимостями + // удобно для деструктурирования + new Bar(zoo$) // создается экземпляр сервиса + ), + foo: it => it.factory(() => new Foo()), baz: it => it.value(new Foo()) }) .done({}); - declare const container: Container; - +declare const container: Container>; const c2 = config.apply(container); -c2.resolve("baz"); \ No newline at end of file +c2.resolve("foo"); \ No newline at end of file