# HG changeset patch # User cin # Date 2020-08-10 05:42:22 # Node ID 511bcc634d65b968f9dd5cea795eda10ab61821f # Parent 09ea4b9e373520f081d9aec014a08a94458d4b88 working on fluent configuration, di annotations removed diff --git a/src/amd/js/data/StoreAdapter.js b/src/amd/js/data/StoreAdapter.js --- a/src/amd/js/data/StoreAdapter.js +++ b/src/amd/js/data/StoreAdapter.js @@ -71,7 +71,6 @@ function(declare, safe, when, QueryResul }); mapped.total = total; var results = new QueryResults(mapped); - console.log(results); return results; }); }, diff --git a/src/main/ts/components/AsyncComponent.ts b/src/main/ts/components/AsyncComponent.ts --- a/src/main/ts/components/AsyncComponent.ts +++ b/src/main/ts/components/AsyncComponent.ts @@ -2,8 +2,10 @@ import { Cancellation } from "../Cancell import { IAsyncComponent, ICancellation, ICancellable, IDestroyable } from "../interfaces"; import { destroy } from "../safe"; +const noop = () => void (0); + export class AsyncComponent implements IAsyncComponent, ICancellable { - _cancel: ((e: any) => void) | undefined; + _cancel: ((e: any) => void) = noop; _completion: Promise = Promise.resolve(); @@ -26,7 +28,7 @@ export class AsyncComponent implements I // after the operation is complete we need to cleanup the // resources destroy(h); - this._cancel = undefined; + this._cancel = noop; } }; @@ -34,7 +36,6 @@ export class AsyncComponent implements I } cancel(reason: any) { - if (this._cancel) - this._cancel(reason); + this._cancel(reason); } } 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,6 +1,6 @@ import { TraceSource } from "../log/TraceSource"; import { argumentNotEmptyString } from "../safe"; -import { Descriptor, ContainerServiceMap, ContainerKeys, TypeOfService } from "./interfaces"; +import { Descriptor, ContainerServiceMap, ContainerKeys, TypeOfService, ILifetime } from "./interfaces"; import { Container } from "./Container"; import { MapOf } from "../interfaces"; @@ -13,6 +13,8 @@ export interface ActivationContextInfo { } +let nextId = 1; + export class ActivationContext { _cache: MapOf; @@ -73,18 +75,23 @@ export class ActivationContext(id: string) { - return this._cache[id]; - } - - store(id: string, value: any) { - return (this._cache[id] = value); - } - activate(d: Descriptor, name: string) { if (trace.isLogEnabled()) trace.log(`enter ${name} ${d}`); 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,7 +3,7 @@ import { ActivationType, ContainerKeys, TypeOfService, - ILifetimeManager + ILifetime } from "./interfaces"; import { argumentNotEmptyString, isPrimitive, isPromise, delegate, argumentOfType, argumentNotNull, get, primitive } from "../safe"; @@ -281,9 +281,11 @@ export class Configuration { - if (isPrimitive(data) || isDescriptor(data)) - return data; + _visit(data: any, name: string): Promise { + if (isPrimitive(data)) + return Promise.resolve(new ValueDescriptor(data)); + if (isDescriptor(data)) + return Promise.resolve(data); if (isDependencyRegistration(data)) { return this._visitDependencyRegistration(data, name); @@ -398,9 +400,11 @@ export class Configuration { + if (!(t instanceof Function)) + throw Error("$type (" + data.$type + ") is not a constructable"); + return t; + }); } const d = new TypeServiceDescriptor( @@ -427,20 +431,20 @@ export class Configuration) => { + return (cfg?: PartialServiceMap): any => { // защищаем контекст на случай исключения в процессе // активации const ct = cfg ? saved.clone() : saved; 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 @@ -2,6 +2,7 @@ import { IDestroyable, MapOf } from "../ import { argumentNotNull, isDestroyable } from "../safe"; import { ILifetimeManager, ILifetime } from "./interfaces"; import { ActivationContext } from "./ActivationContext"; +import { Container } from "./Container"; function safeCall(item: () => void) { try { @@ -16,7 +17,7 @@ const emptyLifetime: ILifetime = { return false; }, - enter() { + initialize() { }, @@ -30,6 +31,21 @@ const emptyLifetime: ILifetime = { }; +const unknownLifetime: ILifetime = { + has() { + throw new Error("The lifetime is unknown"); + }, + initialize() { + throw new Error("Can't call initialize on the unknown lifetime object"); + }, + get() { + throw new Error("The lifetime object isn't initialized"); + }, + store() { + throw new Error("Can't store a value in the unknown lifetime object"); + } +} + let nextId = 0; export class LifetimeManager implements IDestroyable, ILifetimeManager { @@ -39,7 +55,7 @@ export class LifetimeManager implements private _pending: MapOf = {}; - initialize(): ILifetime { + create(): ILifetime { const self = this; const id = ++nextId; return { @@ -54,7 +70,7 @@ export class LifetimeManager implements return t; }, - enter() { + initialize() { if (self._pending[id]) throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`); self._pending[id] = true; @@ -89,43 +105,71 @@ export class LifetimeManager implements } } - static readonly empty: ILifetimeManager = { - initialize(): ILifetime { - return emptyLifetime; - } - }; + static empty(): ILifetime { + return emptyLifetime; + } + + static hierarchyLifetime(): ILifetime { + let _lifetime = unknownLifetime; + return { + initialize(context: ActivationContext) { + if (_lifetime !== unknownLifetime) + throw new Error("Cyclic reference activation detected"); - static readonly hierarchyLifetime: ILifetimeManager = { - initialize(context: ActivationContext): ILifetime { - return context.getContainer().getLifetimeManager().initialize(context); - } - }; + _lifetime = context.getContainer().getLifetimeManager().create(context); + }, + get() { + return _lifetime.get(); + }, + has() { + return _lifetime.has(); + }, + store(item: any, cleanup?: (item: any) => void) { + return _lifetime.store(item, cleanup); + } + }; + } - static readonly contextLifetime: ILifetimeManager = { - initialize(context: ActivationContext): ILifetime { - const id = String(++nextId); - return { - enter() { - if (context.visit(id)) - throw new Error("Cyclic reference detected"); - }, - get() { - return context.get(id); - }, - has() { - return context.has(id); - }, - store(item: any) { - context.store(id, item); - } - }; - } - }; + static contextLifetime(): ILifetime { + let _lifetime = unknownLifetime; + return { + initialize(context: ActivationContext) { + if (_lifetime !== unknownLifetime) + throw new Error("Cyclic reference detected"); + _lifetime = context.createLifetime(); + }, + get() { + return _lifetime.get(); + }, + has() { + return _lifetime.has(); + }, + store(item: any) { + _lifetime.store(item); + } + }; + } - static singletonLifetime(typeId: string): ILifetimeManager { + static singletonLifetime(typeId: string): ILifetime { + return emptyLifetime; + } + + static containerLifetime(container: Container) { + let _lifetime = unknownLifetime; return { - initialize() { - return emptyLifetime; + initialize(context: ActivationContext) { + if (_lifetime !== unknownLifetime) + throw new Error("Cyclic reference detected"); + _lifetime = container.getLifetimeManager().create(context); + }, + get() { + return _lifetime.get(); + }, + has() { + return _lifetime.has(); + }, + store(item: any) { + _lifetime.store(item); } }; } 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 @@ -3,9 +3,26 @@ import { ActivationContext } from "./Act import { Descriptor, PartialServiceMap, TypeOfService, ContainerKeys } from "./interfaces"; export interface ReferenceDescriptorParams> { + /** + * The name of the descriptor + */ name: K; + + /** + * The flag that indicates that the referenced service isn't required to exist. + * If the reference is optional and the referenced service doesn't exist, + * the undefined or a default value will be returned. + */ optional?: boolean; + + /** + * a default value for the reference when the referenced service doesn't exist. + */ default?: TypeOfService; + + /** + * The service overrides + */ services?: PartialServiceMap; } @@ -29,7 +46,10 @@ export class ReferenceDescriptor; } - activate(context: ActivationContext) { + /** This method activates the referenced service if one exists + * @param context activation context which is used during current activation + */ + activate(context: ActivationContext): any { // добавляем сервисы if (this._services) { each(this._services, (v, k) => context.register(k, v)); 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 @@ -59,7 +59,7 @@ export type InjectionSpec = { }; export interface ServiceDescriptorParams { - lifetime?: ILifetimeManager; + lifetime?: ILifetime; params?: P; @@ -79,14 +79,14 @@ export class ServiceDescriptor void) | undefined; - _lifetimeManager = LifetimeManager.empty; + _lifetime = LifetimeManager.empty(); _objectLifetime: ILifetime | undefined; constructor(opts: ServiceDescriptorParams) { if (opts.lifetime) - this._lifetimeManager = opts.lifetime; + this._lifetime = opts.lifetime; if (!isNull(opts.params)) this._params = opts.params; @@ -105,15 +105,12 @@ export class ServiceDescriptor) { - if (!this._objectLifetime) - this._objectLifetime = this._lifetimeManager.initialize(context); - - const lifetime = this._objectLifetime; + const lifetime = this._lifetime; if (lifetime.has()) { return lifetime.get(); } else { - lifetime.enter(); + lifetime.initialize(context); const instance = this._create(context); lifetime.store(instance, this._cleanup); return instance; 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,5 +1,4 @@ -import { Resolver, ServiceModule, LazyDependencyOptions, DependencyOptions } from "./interfaces"; -import { AnnotationBuilder } from "../Annotations"; +import { Resolver, LazyDependencyOptions, DependencyOptions } from "./interfaces"; import { Container } from "../Container"; import { Descriptor, ILifetime, ContainerKeys } from "../interfaces"; import { ActivationContext } from "../ActivationContext"; @@ -12,9 +11,6 @@ export class DescriptorBuilder | ServiceModule) { - - } factory(f: (resolve: Resolver, activate: (lifetime: ILifetime, factory: () => any, cleanup?: (item: any) => void) => any) => T): void { this._cb({ @@ -34,7 +30,7 @@ export class DescriptorBuilder; }; -export type InferReferenceType, O> = O extends { default: infer X } ? (TypeOfService | X) : +export type InferReferenceType, O> = O extends { default: infer X } ? (TypeOfService | X) : O extends { optional: true } ? (TypeOfService | undefined) : TypeOfService; export interface Resolver { - , O extends LazyDependencyOptions>(this: void, name: K, opts: O): () => InferReferenceType; - , O extends DependencyOptions>(this: void, name: K, opts?: O): InferReferenceType; + , O extends LazyDependencyOptions>(this: void, name: K, opts: O): () => InferReferenceType; + , O extends DependencyOptions>(this: void, name: K, opts?: O): InferReferenceType; } export interface DescriptorBuilder { 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 @@ -38,7 +38,7 @@ export type ContainerRegistered): ILifetime; + create(context: ActivationContext): ILifetime; } /** @@ -51,7 +51,7 @@ export interface ILifetime { get(): any; - enter(): void; + initialize(context: ActivationContext): void; store(item: any, cleanup?: (item: any) => void): void; } 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 @@ -1,6 +1,5 @@ import { isPrimitive } from "../safe"; import { Descriptor } from "./interfaces"; -import { AnnotationBuilder } from "./Annotations"; import { Configuration } from "./fluent/Configuration"; export function isDescriptor(x: any): x is Descriptor { @@ -8,16 +7,6 @@ export function isDescriptor(x: any): x (x.activate instanceof Function); } -export function declare() { - return { - annotate() { - return new AnnotationBuilder(); - }, - configure(): Configuration { - throw new Error(); - }, - dependency() { - throw new Error(); - } - }; +export function configure() { + return new Configuration(); } diff --git a/src/main/ts/safe.ts b/src/main/ts/safe.ts --- a/src/main/ts/safe.ts +++ b/src/main/ts/safe.ts @@ -129,8 +129,7 @@ export function get(member: string, cont * @param {Function} cb функция, вызываемая для каждого элемента * @param {Object} thisArg значение, которое будет передано в качестве * this в cb. - * @returns Результат вызова функции cb, либо undefined - * если достигнут конец массива. + * @returns {void} */ export function each(obj: T, cb: (v: NonNullable, k: X) => void): void; export function each(array: T[], cb: (v: T, i: number) => void): void; @@ -138,18 +137,14 @@ export function each(obj: any, cb: any, export function each(obj: any, cb: any, thisArg?: any) { argumentNotNull(cb, "cb"); if (obj instanceof Array) { + let v: any; for (let i = 0; i < obj.length; i++) { - const x = cb.call(thisArg, obj[i], i); - if (x !== undefined) - return x; + v = obj[i]; + if (v !== undefined) + cb.call(thisArg, v, i); } } else { - const _keys = Object.keys(obj); - for (const k of _keys) { - const x = cb.call(thisArg, obj[k], k); - if (x !== undefined) - return x; - } + Object.keys(obj).forEach(k => obj[k] !== undefined && cb.call(thisArg, obj[k], k)); } } @@ -478,7 +473,7 @@ export function firstWhere( } export function isDestroyable(d: any): d is IDestroyable { - if (d && "destroy" in d && typeof(destroy) === "function") + if (d && "destroy" in d && typeof (destroy) === "function") return true; return false; } diff --git a/src/main/ts/text/StringFormat.ts b/src/main/ts/text/StringFormat.ts --- a/src/main/ts/text/StringFormat.ts +++ b/src/main/ts/text/StringFormat.ts @@ -1,4 +1,4 @@ -import { isPrimitive, isNull, each, isKeyof, get } from "../safe"; +import { isPrimitive, isNull, isKeyof, get } from "../safe"; import { MapOf } from "../interfaces"; type SubstFn = (name: string, format?: string) => string; 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,7 +1,6 @@ import { Foo } from "./Foo"; -import { annotate, dependency } from "./services"; -export const service = annotate(); +/* export const service = annotate(); @service.wire({ foo: dependency("foo"), @@ -9,22 +8,27 @@ export const service = annotate(); lazy: dependency("foo", { lazy: true }) }, host: dependency("host") -}, "") +}, "") */ export class Bar { barName = "Twister"; _v: Foo | undefined; - constructor(_opts: { - foo?: Foo; - nested?: { - lazy: () => Foo + constructor( + _opts: { + foo?: Foo; + nested?: { + lazy: () => Foo + }, + host: string }, - host: string - }, s: string) { + s: string + ) { if (_opts && _opts.foo) this._v = _opts.foo; + if (s) + this.barName = s; } setName(name: string) { 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,12 +1,11 @@ import { Bar } from "./Bar"; -import { annotate, dependency } from "./services"; // export service descriptor // через service передается информация о типе зависимости // даже если это шаблон. -export const service = annotate>(); +// export const service = annotate>(); -@service.wire() +// @service.wire() export class Box { private _value: T | undefined; @@ -14,7 +13,7 @@ export class Box { this._value = value; } - @service.inject(dependency("bar")) + // @service.inject(dependency("bar")) setValue(value: T) { this._value = value; return value; 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,10 +1,13 @@ -import { configure } from "./services"; +import { Services } from "./services"; +import { configure } from "../di/traits"; +import { LifetimeManager } from "../di/LifetimeManager"; -export const config = configure() +export const config = configure() .register("host", s => s.value("example.com")) .register("bar2", bar2 => Promise.all([import("./Foo"), import("./Bar")]) .then(([{ Foo }, { Bar }]) => { - const lifetime: any = undefined; // new HierarchyLifetime() + const lifetime = LifetimeManager.hierarchyLifetime(); + bar2.factory((resolve, activate) => { const bar = new Bar({ foo: activate(lifetime, () => new Foo()), 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 @@ -1,7 +1,6 @@ import { Foo } from "./Foo"; import { Bar } from "./Bar"; import { Box } from "./Box"; -import { declare } from "../di/traits"; /** * Сервисы доступные внутри контейнера @@ -18,8 +17,3 @@ export interface Services { host: string; } - -/** - * Экспортируем вспомогательные функции для описания сервисов и кинфогурации - */ -export const { dependency, annotate, configure } = declare(); diff --git a/src/test/ts/tests/ConfigurationBuilderTests.ts b/src/test/ts/tests/ConfigurationBuilderTests.ts deleted file mode 100644 --- a/src/test/ts/tests/ConfigurationBuilderTests.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Constructor } from "../interfaces"; -import { ActivationType } from "../di/interfaces"; - -namespace MyLib { - interface Resolver { - (name: K): S[K]; - (lazy: K): (overrides?: {}) => S[K]; - } - - interface RegistrationOptions { - activation?: ActivationType; - services?: BuilderContext; - } - - interface BuilderContext { - registerType( - name: K, - constructor: T, - options?: RegistrationOptions - ): BuilderContext; - - registerFactory( - name: K, - factory: (resolver: Resolver) => T, - options?: RegistrationOptions - ): BuilderContext; - - registerInstance( - name: K, - instance: T, - ownership?: boolean - ): BuilderContext; - } -} - -export = MyLib;