# HG changeset patch # User cin # Date 2020-07-07 16:41:38 # Node ID 1b124b65514a9362d5c3da2b8986c0494d55000c # Parent 86e3aa3c3eea2e405ff98c26d5a99ede96bef392 improved interfaces and more tight type checking 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,23 +1,23 @@ import { TraceSource } from "../log/TraceSource"; -import { argumentNotNull, argumentNotEmptyString, isPrimitive, each, isNull } from "../safe"; -import { Descriptor, ServiceMap, PartialServiceMap } from "./interfaces"; +import { argumentNotNull, argumentNotEmptyString } from "../safe"; +import { Descriptor, ContainerProvided, ContainerServiceMap, ContainerKeys, ContainerResolve } from "./interfaces"; import { Container } from "./Container"; import { MapOf } from "../interfaces"; const trace = TraceSource.get("@implab/core/di/ActivationContext"); -export interface ActivationContextInfo { +export interface ActivationContextInfo { name: string; service: string; - scope: PartialServiceMap; + scope: ContainerServiceMap; } -export class ActivationContext { +export class ActivationContext { _cache: MapOf; - _services: PartialServiceMap; + _services: ContainerServiceMap; _stack: ActivationContextInfo[]; @@ -29,7 +29,7 @@ export class ActivationContext { container: Container; - constructor(container: Container, services: PartialServiceMap, name?: string, cache?: object, visited?: MapOf) { + constructor(container: Container, services: ContainerServiceMap, name?: string, cache?: object, visited?: MapOf) { argumentNotNull(container, "container"); argumentNotNull(services, "services"); @@ -45,11 +45,11 @@ export class ActivationContext { return this._name; } - resolve(name: K, def?: T): T { + resolve>(name: K, def?: ContainerResolve) { const d = this._services[name]; if (d !== undefined) { - return this.activate(d as Descriptor, name.toString()); + return this.activate(d, name.toString()); } else { if (def !== undefined && def !== null) return def; @@ -67,7 +67,7 @@ export class ActivationContext { register(name: K, service: Descriptor) { argumentNotEmptyString(name, "name"); - this._services[name] = service; + this._services[name] = service as any; } clone() { 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 @@ -3,7 +3,7 @@ import { ActivationContext } from "./Act import { isPrimitive } from "../safe"; import { isDescriptor } from "./traits"; -export class AggregateDescriptor implements Descriptor { +export class AggregateDescriptor implements Descriptor { _value: any; constructor(value: 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 @@ -26,10 +26,10 @@ type ExtractDependency = D extends type WalkDependencies = D extends primitive ? D : { [K in keyof D]: ExtractDependency }; -export class Builder { +export class Builder { declare

(...args: P) { return ) => T>(constructor: C) => { - return constructor as C & { service: Builder }; + }; } @@ -54,7 +54,7 @@ export class Builder { } -interface Declaration { +interface Declaration { define(): Builder; dependency(name: K, opts: { lazy: true }): Lazy; @@ -63,13 +63,15 @@ interface Declaration { config(): Config; } -interface ServiceModule { - service: Builder; -} +type ServiceModule = { + [m in M]: Builder; +}; -interface Config { - register(name: K, builder: Builder): Config>; - register(name: K, m: Promise>): Config>; +export interface Config { + register(name: K, builder: Builder): Config>; + register(name: K, m: Promise>): Config>; + register(name: K, m: Promise>, x: M): Config>; + } export declare function declare(): Declaration; 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,9 +1,11 @@ import { PartialServiceMap, - ActivationType + ActivationType, + ContainerKeys, + ContainerResolve } from "./interfaces"; -import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get } from "../safe"; +import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get, primitive } from "../safe"; import { AggregateDescriptor } from "./AggregateDescriptor"; import { ValueDescriptor } from "./ValueDescriptor"; import { Container } from "./Container"; @@ -14,23 +16,23 @@ import { TraceSource } from "../log/Trac import { ConfigError } from "./ConfigError"; import { Cancellation } from "../Cancellation"; import { makeResolver } from "./ResolverHelper"; -import { ICancellation, Constructor, Factory } from "../interfaces"; +import { ICancellation } from "../interfaces"; import { isDescriptor } from "./traits"; -export interface RegistrationScope { +export interface RegistrationScope { /** сервисы, которые регистрируются в контексте активации и таким образом * могут переопределять ранее зарегистрированные сервисы. за это свойство * нужно платить, кроме того порядок активации будет влиять на результат * разрешения зависимостей. */ - services?: PartialServiceMap; + services?: RegistrationMap; } /** * Базовый интефейс конфигурации сервисов */ -export interface ServiceRegistration extends RegistrationScope { +export interface ServiceRegistration extends RegistrationScope { activation?: ActivationType; @@ -41,13 +43,13 @@ export interface ServiceRegistration void) | string; } -export interface TypeRegistration extends ServiceRegistration { +export interface TypeRegistration extends ServiceRegistration { $type: string | (new (...params: P) => T); } -export interface FactoryRegistration extends ServiceRegistration { - $factory: string | ( (...params: P) => T); +export interface FactoryRegistration extends ServiceRegistration { + $factory: string | ((...params: P) => T); } export interface ValueRegistration { @@ -55,13 +57,27 @@ export interface ValueRegistration { parse?: boolean; } -export interface DependencyRegistration extends RegistrationScope { +export interface DependencyRegistration = ContainerKeys> extends RegistrationScope { $dependency: K; lazy?: boolean; optional?: boolean; - default?: S[K]; + default?: ContainerResolve; } +export type Registration = T extends primitive ? T : + ( + T | + { [k in keyof T]: Registration } | + TypeRegistration | + FactoryRegistration | + ValueRegistration | + DependencyRegistration + ); + +export type RegistrationMap = { + [k in keyof S]?: Registration; +}; + const _activationTypes: { [k in ActivationType]: number; } = { singleton: 1, container: 2, @@ -82,7 +98,7 @@ export function isValueRegistration(x: a return (!isPrimitive(x)) && ("$value" in x); } -export function isDependencyRegistration(x: any): x is DependencyRegistration { +export function isDependencyRegistration(x: any): x is DependencyRegistration { return (!isPrimitive(x)) && ("$dependency" in x); } @@ -112,11 +128,11 @@ async function mapAll(data: any, map?: ( export type ModuleResolver = (moduleName: string, ct?: ICancellation) => any; -export class Configuration { +export class Configuration { _hasInnerDescriptors = false; - _container: Container; + readonly _container: Container; _path: Array; @@ -152,13 +168,13 @@ export class Configuration { ); } - async applyConfiguration(data: object, contextRequire?: any, ct = Cancellation.none) { + async applyConfiguration(data: RegistrationMap, contextRequire?: any, ct = Cancellation.none) { argumentNotNull(data, "data"); await this._applyConfiguration(data, await makeResolver(void (0), contextRequire), ct); } - async _applyConfiguration(data: object, resolver?: ModuleResolver, ct = Cancellation.none) { + async _applyConfiguration(data: RegistrationMap, resolver?: ModuleResolver, ct = Cancellation.none) { trace.log("applyConfiguration"); this._configName = "$"; @@ -213,16 +229,13 @@ export class Configuration { return this._require(moduleName); } - async _visitRegistrations(data: any, name: string) { + async _visitRegistrations(data: RegistrationMap, name: string) { this._enter(name); if (data.constructor && data.constructor.prototype !== Object.prototype) throw new Error("Configuration must be a simple object"); - const o: PartialServiceMap = {}; - const keys = Object.keys(data); - const services = await mapAll(data, async (v, k) => { const d = await this._visit(v, k.toString()); return isDescriptor(d) ? d : new AggregateDescriptor(d); @@ -298,7 +311,7 @@ export class Configuration { return v; } - _makeServiceParams(data: ServiceRegistration) { + _makeServiceParams(data: ServiceRegistration) { const opts: any = { owner: this._container }; 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,17 +1,17 @@ import { ActivationContext } from "./ActivationContext"; import { ValueDescriptor } from "./ValueDescriptor"; import { ActivationError } from "./ActivationError"; -import { ServiceMap, Descriptor, PartialServiceMap, ContainerServices, Resolver } from "./interfaces"; +import { ServiceMap, Descriptor, PartialServiceMap, ContainerProvided, Resolver, ContainerServiceMap, ContainerKeys, ContainerResolve } from "./interfaces"; import { TraceSource } from "../log/TraceSource"; -import { Configuration } from "./Configuration"; +import { Configuration, RegistrationMap } 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>; +export class Container implements Resolver { + readonly _services: ContainerServiceMap; readonly _cache: MapOf; @@ -41,7 +41,7 @@ export class Container implemen return this._parent; } - resolve, T extends ContainerServices[K] = ContainerServices[K]>(name: K, def?: T): T { + resolve>(name: K, def?: ContainerResolve): ContainerResolve { trace.debug("resolve {0}", name); const d = this._services[name]; if (d === undefined) { @@ -53,7 +53,7 @@ export class Container implemen const context = new ActivationContext(this, this._services); try { - return context.activate(d as Descriptor, name.toString()); + return context.activate(d, name.toString()); } catch (error) { throw new ActivationError(name.toString(), context.getStack(), error); } @@ -63,7 +63,7 @@ export class Container implemen /** * @deprecated use resolve() method */ - getService[K] = ContainerServices[K]>(name: K, def?: T) { + getService>(name: K, def?: ContainerResolve) { return this.resolve(name, def); } @@ -111,7 +111,7 @@ export class Container implemen * The function which will be used to load a configuration or types for services. * */ - async configure(config: string | object, opts?: any, ct = Cancellation.none) { + async configure(config: string | RegistrationMap, opts?: any, ct = Cancellation.none) { const c = new Configuration(this); if (typeof (config) === "string") { @@ -121,7 +121,7 @@ export class Container implemen } } - createChildContainer } = S>(): Container { + createChildContainer(): Container { return new Container(this as any); } 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,11 +1,11 @@ import { ServiceDescriptor, ServiceDescriptorParams } from "./ServiceDescriptor"; import { argumentNotNull, oid } from "../safe"; -export interface FactoryServiceDescriptorParams extends ServiceDescriptorParams { +export interface FactoryServiceDescriptorParams extends ServiceDescriptorParams { factory: (...args: P) => T; } -export class FactoryServiceDescriptor extends ServiceDescriptor { +export class FactoryServiceDescriptor extends ServiceDescriptor { constructor(opts: FactoryServiceDescriptorParams) { super(opts); 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 @@ -1,30 +1,26 @@ -import { isNull, argumentNotEmptyString, each, keys } from "../safe"; +import { argumentNotEmptyString, each } from "../safe"; import { ActivationContext } from "./ActivationContext"; -import { ServiceMap, Descriptor, PartialServiceMap } from "./interfaces"; +import { Descriptor, PartialServiceMap, ContainerResolve, ContainerKeys } from "./interfaces"; import { ActivationError } from "./ActivationError"; -export interface ReferenceDescriptorParams { +export interface ReferenceDescriptorParams> { name: K; lazy?: boolean; optional?: boolean; - default?: S[K]; + default?: ContainerResolve; services?: PartialServiceMap; } -function defined(v: T | undefined) { - if (v === undefined) - throw Error(); - return v; -} +export class ReferenceDescriptor = ContainerKeys> + implements Descriptor | ((args?: PartialServiceMap) => ContainerResolve)> { -export class ReferenceDescriptor implements Descriptor ) => S[K])> { _name: K; _lazy = false; _optional = false; - _default: S[K] | undefined; + _default: ContainerResolve | undefined; _services: PartialServiceMap; @@ -35,7 +31,7 @@ export class ReferenceDescriptor; + this._services = (opts.services || {}) as PartialServiceMap; } activate(context: ActivationContext) { 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 @@ -9,7 +9,7 @@ let cacheId = 0; const trace = TraceSource.get("@implab/core/di/ActivationContext"); -function injectMethod(target: T, method: M, context: ActivationContext, args: A) { +function injectMethod(target: T, method: M, context: ActivationContext, args: A) { const m = target[method]; if (!m || typeof m !== "function") @@ -59,7 +59,7 @@ export type InjectionSpec = { [m in keyof T]?: any; }; -export interface ServiceDescriptorParams { +export interface ServiceDescriptorParams { activation?: ActivationType; owner: Container; @@ -73,7 +73,7 @@ export interface ServiceDescriptorParams cleanup?: Cleaner; } -export class ServiceDescriptor implements Descriptor { +export class ServiceDescriptor implements Descriptor { _instance: T | undefined; _hasInstance = false; diff --git a/src/main/ts/di/TypeServiceDescriptor.ts b/src/main/ts/di/TypeServiceDescriptor.ts --- a/src/main/ts/di/TypeServiceDescriptor.ts +++ b/src/main/ts/di/TypeServiceDescriptor.ts @@ -2,11 +2,11 @@ import { ServiceDescriptor, ServiceDescr import { Constructor, Factory } from "../interfaces"; import { argumentNotNull, isPrimitive } from "../safe"; -export interface TypeServiceDescriptorParams extends ServiceDescriptorParams { +export interface TypeServiceDescriptorParams extends ServiceDescriptorParams { type: Constructor; } -export class TypeServiceDescriptor extends ServiceDescriptor { +export class TypeServiceDescriptor extends ServiceDescriptor { _type: Constructor; constructor(opts: TypeServiceDescriptorParams) { 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,21 +1,38 @@ import { ActivationContext } from "./ActivationContext"; -export interface Descriptor { +export interface Descriptor { activate(context: ActivationContext): T; } -export type ServiceMap = { +export type ServiceMap = { [k in keyof S]: Descriptor; }; -export type PartialServiceMap = { +export type ContainerKeys = keyof S | keyof ContainerProvided; + +export type ContainerResolve = + K extends keyof ContainerProvided ? ContainerProvided[K] : + K extends keyof S ? S[K] : never; + +export type ContainerServiceMap = { + [K in ContainerKeys]: Descriptor>; +}; + +export type PartialServiceMap = { [k in keyof S]?: Descriptor; }; -export interface Resolver { - resolve, T extends ContainerServices[K] = ContainerServices[K]>(name: K, def?: T): T; +export interface Resolver { + resolve>(name: K, def?: ContainerResolve): ContainerResolve; +} + +export interface ContainerProvided { + container: Resolver; } -export type ContainerServices = S & { - container: Resolver; -}; + +export type ContainerRegistered = /*{ + [K in Exclude>]: S[K]; +};*/ + Exclude>; + export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call"; diff --git a/src/main/ts/di/traits.ts b/src/main/ts/di/traits.ts --- a/src/main/ts/di/traits.ts +++ b/src/main/ts/di/traits.ts @@ -4,4 +4,4 @@ import { Descriptor } from "./interfaces export function isDescriptor(x: any): x is Descriptor { return (!isPrimitive(x)) && (x.activate instanceof Function); -} +} \ No newline at end of file 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 @@ -2,5 +2,5 @@ import { config } from "./services"; config() .register("bar", import("./Bar")) - .register("box", import("./Box")) - .register("foo", import("./Foo"), "Foo"); + .register("box", import("./Box"), "service"); + //.register("foo", import("./Foo"), "Foo"); 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 @@ -6,10 +6,17 @@ import { ValueDescriptor } from "../di/V import { Foo } from "../mock/Foo"; import { Bar } from "../mock/Bar"; import { isNull } from "../safe"; -import { Descriptor } from "../di/interfaces"; +import { Box } from "ts/mock/Box"; test("Container register/resolve tests", async t => { - const container = new Container(); + const container = new Container<{ + "bla-bla": string; + "connection": string; + "dbParams": { + timeout: number; + connection: string; + } + }>(); const connection1 = "db://localhost"; @@ -42,7 +49,11 @@ test("Container register/resolve tests", test("Container configure/resolve tests", async t => { - const container = new Container(); + const container = new Container<{ + foo: Foo; + box: Bar; + bar: Bar; + }>(); await container.configure({ foo: {