diff --git a/src/main/ts/di/AggregateDescriptor.ts b/src/main/ts/di/AggregateDescriptor.ts --- a/src/main/ts/di/AggregateDescriptor.ts +++ b/src/main/ts/di/AggregateDescriptor.ts @@ -1,19 +1,20 @@ -import { Descriptor, isDescriptor, Parse } from "./interfaces"; +import { Descriptor } from "./interfaces"; import { ActivationContext } from "./ActivationContext"; import { isPrimitive } from "../safe"; +import { isDescriptor } from "./traits"; -export class AggregateDescriptor implements Descriptor> { - _value: T; +export class AggregateDescriptor implements Descriptor { + _value: any; - constructor(value: T) { + constructor(value: any) { this._value = value; } - activate(context: ActivationContext) { + activate(context: ActivationContext): T { return this._parse(this._value, context, "$value"); } - _parse(value: V, context: ActivationContext, path: string): Parse { + _parse(value: any, context: ActivationContext, path: string): any { if (isPrimitive(value)) return value as any; diff --git a/src/main/ts/di/Annotations.ts b/src/main/ts/di/Annotations.ts --- a/src/main/ts/di/Annotations.ts +++ b/src/main/ts/di/Annotations.ts @@ -1,4 +1,5 @@ import { Constructor } from "../interfaces"; +import { primitive } from "../safe"; export interface InjectOptions { lazy?: boolean; @@ -14,25 +15,28 @@ interface Lazy exte lazy: true; } -type Setter = (v: T) => void; - type Compatible = T1 extends T2 ? any : never; type ExtractService = K extends keyof S ? S[K] : K; -type ExtractDependency = D extends { $dependency: infer K } ? D extends { lazy: true } ? () => ExtractService : ExtractService : VisitDependency; +type ExtractDependency = D extends { $dependency: infer K } ? + D extends { lazy: true } ? () => ExtractService : ExtractService : + WalkDependencies; -type VisitDependency = D extends {} ? { [K in keyof D]: ExtractDependency } : D; +type WalkDependencies = D extends primitive ? D : + { [K in keyof D]: ExtractDependency }; -interface Config { - dependency(name: K): Dependency; +interface Services { + get(name: K): Dependency; lazy(name: K): Lazy; - build(): Builder; + build(): Builder; } -export declare function services(): Config; +export declare function services(): Services; + +export declare function build(): Builder; export class Builder { consume

(...args: P) { @@ -40,13 +44,17 @@ export class Builder { }; } - inject(dependency: K) { + inject

(...args: P) { // K = "bar" // M = "setValue" // S[K] = Bar // T[M] = (value: string) => void // P[m] = (value: V) => void - return (target: P, memberName: M, descriptor: TypedPropertyDescriptor>>) => { + return any }, M extends keyof (T | X)>( + target: X, + memberName: M, + descriptor: TypedPropertyDescriptor) => any, T[M]>> + ) => { }; } @@ -55,4 +63,13 @@ export class Builder { return this as Builder; } + get(name: K): Dependency { + throw new Error(); + } + + lazy(name: K): Lazy { + throw new Error(); + } + + } 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 @@ -1,17 +1,6 @@ import { - ServiceRegistration, - TypeRegistration, - FactoryRegistration, - ServiceMap, - isDescriptor, - isDependencyRegistration, - DependencyRegistration, - ValueRegistration, - ActivationType, - isValueRegistration, - isTypeRegistration, - isFactoryRegistration, - PartialServiceMap + PartialServiceMap, + ActivationType } from "./interfaces"; import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get } from "../safe"; @@ -25,7 +14,81 @@ import { TraceSource } from "../log/Trac import { ConfigError } from "./ConfigError"; import { Cancellation } from "../Cancellation"; import { makeResolver } from "./ResolverHelper"; -import { ICancellation } from "../interfaces"; +import { ICancellation, Constructor, Factory } from "../interfaces"; +import { isDescriptor } from "./traits"; + +export interface RegistrationScope { + + /** сервисы, которые регистрируются в контексте активации и таким образом + * могут переопределять ранее зарегистрированные сервисы. за это свойство + * нужно платить, кроме того порядок активации будет влиять на результат + * разрешения зависимостей. + */ + services?: PartialServiceMap; +} + +/** + * Базовый интефейс конфигурации сервисов + */ +export interface ServiceRegistration extends RegistrationScope { + + activation?: ActivationType; + + params?: P; + + inject?: object | object[]; + + cleanup?: ((instance: T) => void) | string; +} + +export interface TypeRegistration extends ServiceRegistration { + $type: string | (new (...params: P) => T); + +} + +export interface FactoryRegistration extends ServiceRegistration { + $factory: string | ( (...params: P) => T); +} + +export interface ValueRegistration { + $value: T; + parse?: boolean; +} + +export interface DependencyRegistration extends RegistrationScope { + $dependency: K; + lazy?: boolean; + optional?: boolean; + default?: S[K]; +} + +const _activationTypes: { [k in ActivationType]: number; } = { + singleton: 1, + container: 2, + hierarchy: 3, + context: 4, + call: 5 +}; + +export function isTypeRegistration(x: any): x is TypeRegistration { + return (!isPrimitive(x)) && ("$type" in x); +} + +export function isFactoryRegistration(x: any): x is FactoryRegistration { + return (!isPrimitive(x)) && ("$factory" in x); +} + +export function isValueRegistration(x: any): x is ValueRegistration { + return (!isPrimitive(x)) && ("$value" in x); +} + +export function isDependencyRegistration(x: any): x is DependencyRegistration { + return (!isPrimitive(x)) && ("$dependency" in x); +} + +export function isActivationType(x: string): x is ActivationType { + return typeof x === "string" && x in _activationTypes; +} const trace = TraceSource.get("@implab/core/di/Configuration"); async function mapAll(data: any[], map?: (v: any, k: number) => any): Promise; @@ -180,7 +243,7 @@ export class Configuration { trace.debug("<{0}", name); } - async _visit(data: T, name: string): Promise { + async _visit(data: any, name: string): Promise { if (isPrimitive(data) || isDescriptor(data)) return data; @@ -196,10 +259,10 @@ export class Configuration { return this._visitArray(data, name); } - return this._visitObject(data as T & object, name); + return this._visitObject(data, name); } - async _visitObject(data: T, name: string) { + async _visitObject(data: any, name: string) { if (data.constructor && data.constructor.prototype !== Object.prototype) return new ValueDescriptor(data); @@ -259,30 +322,7 @@ export class Configuration { this._visit(data.params, "params"); if (data.activation) { - if (typeof (data.activation) === "string") { - switch (data.activation.toLowerCase()) { - case "singleton": - opts.activation = ActivationType.Singleton; - break; - case "container": - opts.activation = ActivationType.Container; - break; - case "hierarchy": - opts.activation = ActivationType.Hierarchy; - break; - case "context": - opts.activation = ActivationType.Context; - break; - case "call": - opts.activation = ActivationType.Call; - break; - default: - throw new Error("Unknown activation type: " + - data.activation); - } - } else { - opts.activation = Number(data.activation); - } + opts.activation = data.activation; } if (data.cleanup) @@ -312,7 +352,7 @@ export class Configuration { return d; } - async _visitTypeRegistration(data: TypeRegistration, name: string) { + async _visitTypeRegistration(data: TypeRegistration, name: string) { argumentNotNull(data.$type, "data.$type"); this._enter(name); @@ -324,7 +364,7 @@ export class Configuration { opts.type = this._resolveType(moduleName, typeName); } - const d = new TypeServiceDescriptor( + const d = new TypeServiceDescriptor( await mapAll(opts) ); @@ -333,14 +373,14 @@ export class Configuration { return d; } - async _visitFactoryRegistration(data: FactoryRegistration, name: string) { + async _visitFactoryRegistration(data: FactoryRegistration, name: string) { argumentOfType(data.$factory, Function, "data.$factory"); this._enter(name); const opts = this._makeServiceParams(data); opts.factory = data.$factory; - const d = new FactoryServiceDescriptor( + const d = new FactoryServiceDescriptor( await mapAll(opts) ); 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,16 +1,17 @@ import { ActivationContext } from "./ActivationContext"; import { ValueDescriptor } from "./ValueDescriptor"; import { ActivationError } from "./ActivationError"; -import { isDescriptor, ServiceMap, Descriptor, PartialServiceMap, ContainerServices, Resolver } from "./interfaces"; +import { ServiceMap, Descriptor, PartialServiceMap, ContainerServices, Resolver } from "./interfaces"; import { TraceSource } from "../log/TraceSource"; import { Configuration } from "./Configuration"; import { Cancellation } from "../Cancellation"; import { MapOf } from "../interfaces"; +import { isDescriptor } from "./traits"; const trace = TraceSource.get("@implab/core/di/ActivationContext"); export class Container implements Resolver { - readonly _services: PartialServiceMap>; + readonly _services: PartialServiceMap>; readonly _cache: MapOf; diff --git a/src/main/ts/di/FactoryServiceDescriptor.ts b/src/main/ts/di/FactoryServiceDescriptor.ts --- a/src/main/ts/di/FactoryServiceDescriptor.ts +++ b/src/main/ts/di/FactoryServiceDescriptor.ts @@ -1,6 +1,5 @@ import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor"; import { argumentNotNull, oid } from "../safe"; -import { ActivationType } from "./interfaces"; export interface FactoryServiceDescriptorParams extends ServiceDescriptorParams { factory: (...args: P) => T; @@ -15,7 +14,7 @@ export class FactoryServiceDescriptor opts.factory.apply(null, args as any); - if (opts.activation === ActivationType.Singleton) { + if (opts.activation === "singleton") { this._cacheId = oid(opts.factory); } } diff --git a/src/main/ts/di/ReferenceDescriptor.ts b/src/main/ts/di/ReferenceDescriptor.ts --- a/src/main/ts/di/ReferenceDescriptor.ts +++ b/src/main/ts/di/ReferenceDescriptor.ts @@ -26,7 +26,7 @@ export class ReferenceDescriptor; + _services: PartialServiceMap; constructor(opts: ReferenceDescriptorParams) { argumentNotEmptyString(opts && opts.name, "opts.name"); @@ -53,7 +53,7 @@ export class ReferenceDescriptor, (v, k) => ct.register(k, v)); + each(cfg, (v, k) => ct.register(k, v)); } return this._optional ? ct.resolve(this._name, this._default) : ct diff --git a/src/main/ts/di/ServiceDescriptor.ts b/src/main/ts/di/ServiceDescriptor.ts --- a/src/main/ts/di/ServiceDescriptor.ts +++ b/src/main/ts/di/ServiceDescriptor.ts @@ -1,8 +1,9 @@ import { ActivationContext } from "./ActivationContext"; -import { Descriptor, ActivationType, ServiceMap, isDescriptor, Parse, PartialServiceMap } from "./interfaces"; +import { Descriptor, ServiceMap, PartialServiceMap, ActivationType } from "./interfaces"; import { Container } from "./Container"; -import { argumentNotNull, isPrimitive, each, keys, isNull } from "../safe"; +import { argumentNotNull, isPrimitive, keys, isNull } from "../safe"; import { TraceSource } from "../log/TraceSource"; +import { isDescriptor } from "./traits"; let cacheId = 0; @@ -33,7 +34,7 @@ function makeClenupCallback(target: any, } } -function _parse(value: T, context: ActivationContext, path: string): Parse { +function _parse(value: any, context: ActivationContext, path: string): any { if (isPrimitive(value)) return value as any; @@ -77,7 +78,7 @@ export class ServiceDescriptor; @@ -125,7 +126,7 @@ export class ServiceDescriptor) { trace.debug(`constructing ${context._name}`); - if (this._activationType !== ActivationType.Call && + if (this._activationType !== "call" && context.visit(this._cacheId) > 0) throw new Error("Recursion detected"); 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,68 +1,16 @@ -import { isPrimitive, primitive } from "../safe"; import { ActivationContext } from "./ActivationContext"; -import { Constructor, Factory } from "../interfaces"; export interface Descriptor { activate(context: ActivationContext): T; } -export function isDescriptor(x: any): x is Descriptor { - return (!isPrimitive(x)) && - (x.activate instanceof Function); -} - -export type ServiceMap = { - [k in keyof S2]: Descriptor; +export type ServiceMap = { + [k in keyof S]: Descriptor; }; -export type PartialServiceMap = Partial>; - -export enum ActivationType { - Singleton = 1, - Container, - Hierarchy, - Context, - Call -} - -export interface RegistrationWithServices { - services?: ServiceMap; -} - -export interface ServiceRegistration extends RegistrationWithServices { - - activation?: "singleton" | "container" | "hierarchy" | "context" | "call"; - - params?: P; - - inject?: object | object[]; - - cleanup?: ((instance: T) => void) | string; -} - -export interface TypeRegistration extends ServiceRegistration { - $type: string | Constructor; -} - -export interface FactoryRegistration extends ServiceRegistration { - $factory: string | Factory; -} - -export interface ValueRegistration { - $value: T; - parse?: boolean; -} - -export interface DependencyRegistration extends RegistrationWithServices { - $dependency: K; - lazy?: boolean; - optional?: boolean; - default?: S[K]; -} - -export type Parse = T extends primitive ? T: - T extends Descriptor ? V : - { [K in keyof T]: Parse }; +export type PartialServiceMap = { + [k in keyof S]?: Descriptor; +}; export interface Resolver { resolve, T extends ContainerServices[K] = ContainerServices[K]>(name: K, def?: T): T; @@ -70,19 +18,4 @@ export interface Resolver { export type ContainerServices = S & { container: Resolver; }; - -export function isTypeRegistration(x: any): x is TypeRegistration { - return (!isPrimitive(x)) && ("$type" in x); -} - -export function isFactoryRegistration(x: any): x is FactoryRegistration { - return (!isPrimitive(x)) && ("$factory" in x); -} - -export function isValueRegistration(x: any): x is ValueRegistration { - return (!isPrimitive(x)) && ("$value" in x); -} - -export function isDependencyRegistration(x: any): x is DependencyRegistration { - return (!isPrimitive(x)) && ("$dependency" in x); -} +export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call"; diff --git a/src/main/ts/di/traits.ts b/src/main/ts/di/traits.ts new file mode 100644 --- /dev/null +++ b/src/main/ts/di/traits.ts @@ -0,0 +1,7 @@ +import { isPrimitive } from "../safe"; +import { Descriptor } from "./interfaces"; + +export function isDescriptor(x: any): x is Descriptor { + return (!isPrimitive(x)) && + (x.activate instanceof Function); +} diff --git a/src/test/ts/mock/Bar.ts b/src/test/ts/mock/Bar.ts --- a/src/test/ts/mock/Bar.ts +++ b/src/test/ts/mock/Bar.ts @@ -1,22 +1,22 @@ import { Foo } from "./Foo"; -// import { config } from "./config"; +import { build } from "./config"; -// const service = config.build("bar"); +const service = build(); -// @service.consume({ -// f: config.dependency("foo"), -// nested: { -// lazy: config.lazy("foo") -// } -// }) +@service.consume({ + foo: service.get("foo"), + nested: { + lazy: service.lazy("foo") + } +}) export class Bar { barName = "bar"; _v: Foo | undefined; - constructor(_opts: { - foo: Foo; - nested: { + constructor(_opts?: { + foo?: Foo; + nested?: { lazy: () => Foo } }) { diff --git a/src/test/ts/mock/Box.ts b/src/test/ts/mock/Box.ts --- a/src/test/ts/mock/Box.ts +++ b/src/test/ts/mock/Box.ts @@ -1,15 +1,17 @@ import { services } from "../di/Annotations"; import { Bar } from "./Bar"; +import { Foo } from "./Foo"; // declare required dependencies const config = services<{ bar: Bar; + foo: Foo; }>(); // export service descriptor export const service = config.build>(); -@service.consume(config.dependency("bar")) +@service.consume(config.get("bar")) export class Box { private _value: T | undefined; @@ -17,9 +19,10 @@ export class Box { this._value = value; } - // @service.inject("bar") - setValue(value: T) { + @service.inject( config.get("bar")) + setValue(value?: T) { this._value = value; + return value; } setObj(value: object) { diff --git a/src/test/ts/mock/config.ts b/src/test/ts/mock/config.ts --- a/src/test/ts/mock/config.ts +++ b/src/test/ts/mock/config.ts @@ -1,8 +1,7 @@ import { Foo } from "./Foo"; import { Bar } from "./Bar"; import { Box } from "./Box"; -import { primitive } from "../safe"; -import { Constructor } from "../interfaces"; +import { Builder } from "../di/Annotations"; interface Services { foo: Foo; @@ -15,24 +14,14 @@ interface Services { } -interface TypeDescriptor> { - $type: C; +const services = { + build: () => { + return new Builder(); + } +}; - params: Wrap>; -} +namespace services { -function typeRegistration>(target: C, params: Wrap>): TypeDescriptor { - throw new Error(); } -declare function register(): { type>(target: C, params: Wrap>): TypeDescriptor}; - -type Wrap = T extends primitive ? T : - { [k in keyof T]: Wrap } | TypeDescriptor>; - -const config: Wrap = { - foo: typeRegistration(Foo, []), - bar: typeRegistration(Bar, [{ foo: null as any, nested: null as any }]), - box: register>().type(Box, [{ $type: Bar, params: [] }]), - host: "" -}; +export = services; diff --git a/src/test/ts/tests/ContainerTests.ts b/src/test/ts/tests/ContainerTests.ts --- a/src/test/ts/tests/ContainerTests.ts +++ b/src/test/ts/tests/ContainerTests.ts @@ -91,4 +91,5 @@ test("Load configuration from module", a t.assert(!isNull(b1), "bar should not be null"); t.assert(!isNull(b1._v), "bar.foo should not be null"); + });