##// END OF EJS Templates
Completely removed IoC annotations...
cin -
r135:03e32ec7c20b ioc ts support
parent child
Show More
@@ -0,0 +1,63
1 import { Descriptor, PartialServiceMap, ILifetime, ContainerKeys } from "../interfaces";
2 import { ActivationContext } from "../ActivationContext";
3 import { each } from "../../safe";
4 import { DependencyOptions, LazyDependencyOptions, Resolver } from "./interfaces";
5
6 export interface DescriptorImplArgs<S extends object, T> {
7 lifetime: ILifetime;
8
9 factory: (resolve: Resolver<S>) => T;
10
11 cleanup?: (item: T) => void;
12
13 overrides?: PartialServiceMap<S>;
14 }
15
16 export class DescriptorImpl<S extends object, T> implements Descriptor<S, T> {
17
18 private readonly _overrides?: PartialServiceMap<S>;
19
20 private readonly _lifetime: ILifetime;
21
22 private readonly _factory: (resolve: Resolver<S>) => T;
23
24 private readonly _cleanup?: (item: T) => void;
25
26 constructor(args: DescriptorImplArgs<S, T>) {
27 this._lifetime = args.lifetime;
28 this._factory = args.factory;
29 if (args.cleanup)
30 this._cleanup = args.cleanup;
31 if (args.overrides)
32 this._overrides = args.overrides;
33 }
34
35 activate(context: ActivationContext<S>): T {
36
37 if (this._lifetime.has())
38 return this._lifetime.get();
39
40 this._lifetime.initialize(context);
41
42 if (this._overrides)
43 each(this._overrides, (v, k) => context.register(k, v));
44
45 const resolve = (name: ContainerKeys<S>, opts?: DependencyOptions | LazyDependencyOptions) => {
46 if (opts && "lazy" in opts && opts.lazy) {
47 const c2 = context.clone();
48 return () => {
49 return opts.optional ? c2.resolve(name, opts.default) : c2.resolve(name);
50 };
51 } else {
52 return opts && opts.optional ? context.resolve(name, opts.default) : context.resolve(name);
53 }
54 };
55
56 const instance = this._factory.call(undefined, resolve);
57
58 this._lifetime.store(instance, this._cleanup);
59
60 return instance;
61 }
62
63 }
@@ -0,0 +1,58
1 import { Container } from "../Container";
2 import { argumentNotNull, each, isPrimitive, isPromise } from "../../safe";
3 import { DescriptorBuilder } from "./DescriptorBuilder";
4 import { RegistrationBuilder, FluentRegistrations } from "./interfaces";
5 import { Cancellation } from "../../Cancellation";
6
7 export class FluentConfiguration<S extends object, Y extends keyof S = keyof S> {
8
9 _builders: { [k in keyof S]?: RegistrationBuilder<S, S[k]> } = {};
10
11 register<K extends Y>(name: K, builder: RegistrationBuilder<S, S[K]>): FluentConfiguration<S, Exclude<Y, K>>;
12 register<K extends Y>(config: FluentRegistrations<K, S>): FluentConfiguration<S, Exclude<Y, K>>;
13 register<K extends Y>(nameOrConfig: K | FluentRegistrations<K, S>, builder?: RegistrationBuilder<S, S[K]>): FluentConfiguration<S, Exclude<Y, K>> {
14 if (isPrimitive(nameOrConfig)) {
15 argumentNotNull(builder, "builder");
16 this._builders[nameOrConfig] = builder;
17 } else {
18 each(nameOrConfig, (v, k) => this.register(k, v));
19 }
20
21 return this;
22 }
23
24 apply(target: Container<S>, ct = Cancellation.none) {
25
26 let pending = 1;
27
28 return new Promise((resolve, reject) => {
29 function guard(v: void | Promise<void>) {
30 if (isPromise(v))
31 v.catch(reject);
32 }
33
34 function complete() {
35 if (!--pending)
36 resolve();
37 }
38 each(this._builders, (v, k) => {
39 pending++;
40 const d = new DescriptorBuilder<S, any>(target,
41 result => {
42 target.register(k, result);
43 complete();
44 },
45 reject
46 );
47
48 try {
49 guard(v(d, ct));
50 } catch (e) {
51 reject(e);
52 }
53 });
54 complete();
55 });
56 }
57
58 }
@@ -0,0 +1,54
1 import { test } from "./TestTraits";
2 import { fluent } from "../di/traits";
3 import { Bar } from "../mock/Bar";
4 import { Container } from "../di/Container";
5 import { Foo } from "../mock/Foo";
6 import { Box } from "../mock/Box";
7 import { delay } from "../safe";
8
9 test("Simple fluent config", async t => {
10 const config = fluent<{ host: string; bar: Bar; foo: Foo }>()
11 .register({
12 host: it => it.value("example.com"),
13 bar: it => it.factory(resolve => new Bar({ host: resolve("host") }, "s-bar")),
14 foo: it => import("../mock/Foo").then(m => it.lifetime("container").factory(() => new m.Foo()))
15 });
16
17 const container = new Container<{ host: string; bar: Bar; foo: Foo; }>();
18 await config.apply(container);
19
20 t.equal(container.resolve("host"), "example.com", "The value should be resolved");
21 t.assert(container.resolve("bar"), "The service should de activated");
22 t.equal(container.resolve("foo"), container.resolve("foo"), "The service should be activated once");
23 });
24
25 test("Nested async configuration", async t => {
26 const container = await new Container<{
27 foo: Foo;
28 box: Box<Foo>
29 }>().fluent({
30 foo: it => delay(0).then(() => it.factory(() => new Foo())),
31 box: it => it.lifetime("context").factory($dependency => new Box($dependency("foo")))
32 });
33
34 t.assert(container.resolve("box").getValue(), "The dependency should be set");
35 t.equals(container.resolve("box").getValue(), container.resolve("box").getValue(), "The service should be activated once")
36 });
37
38 test("Bad fluent config", async t => {
39 try {
40 await new Container<{
41 foo: Foo;
42 box: Box<Foo>
43 }>().fluent({
44 foo: it => delay(0).then(() => it.factory(() => new Foo())),
45 box: it => it.lifetime("context")
46 .override("foo", () => { throw new Error("bad override"); })
47 .factory($dependency => new Box($dependency("foo")))
48 });
49 t.fail("Should throw");
50 } catch (e) {
51 t.pass("The configuration should fail");
52 t.equal(e.message, "bad override", "the error should pass");
53 }
54 });
@@ -1,140 +1,170
1 import { TraceSource } from "../log/TraceSource";
1 import { TraceSource } from "../log/TraceSource";
2 import { argumentNotEmptyString } from "../safe";
2 import { argumentNotEmptyString } from "../safe";
3 import { Descriptor, ContainerServiceMap, ContainerKeys, TypeOfService, ILifetime } from "./interfaces";
3 import { Descriptor, ContainerServiceMap, ContainerKeys, TypeOfService, ILifetime } from "./interfaces";
4 import { Container } from "./Container";
4 import { Container } from "./Container";
5 import { MapOf } from "../interfaces";
5 import { MapOf } from "../interfaces";
6
6
7 const trace = TraceSource.get("@implab/core/di/ActivationContext");
7 const trace = TraceSource.get("@implab/core/di/ActivationContext");
8
8
9 export interface ActivationContextInfo {
9 export interface ActivationContextInfo {
10 name: string;
10 name: string;
11
11
12 service: string;
12 service: string;
13
13
14 }
14 }
15
15
16 let nextId = 1;
16 let nextId = 1;
17
17
18 /** This class is created once per `Container.resolve` method call and used to
19 * cache dependencies and to track created instances. The activation context
20 * tracks services with `context` activation type.
21 */
18 export class ActivationContext<S extends object> {
22 export class ActivationContext<S extends object> {
19 _cache: MapOf<any>;
23 _cache: MapOf<any>;
20
24
21 _services: ContainerServiceMap<S>;
25 _services: ContainerServiceMap<S>;
22
26
23 _visited: MapOf<any>;
27 _visited: MapOf<any>;
24
28
25 _name: string;
29 _name: string;
26
30
27 _service: Descriptor<S, any>;
31 _service: Descriptor<S, any>;
28
32
29 _container: Container<S>;
33 _container: Container<S>;
30
34
31 _parent: ActivationContext<S> | undefined;
35 _parent: ActivationContext<S> | undefined;
32
36
37 /** Creates a new activation context with the specified parameters.
38 * @param container the container which starts the activation process
39 * @param services the initial service registrations
40 * @param name the name of the service being activated, this parameter is
41 * used for the debug purpose.
42 * @param service the service to activate, this parameter is used for the
43 * debug purpose.
44 */
33 constructor(container: Container<S>, services: ContainerServiceMap<S>, name: string, service: Descriptor<S, any>) {
45 constructor(container: Container<S>, services: ContainerServiceMap<S>, name: string, service: Descriptor<S, any>) {
34 this._name = name;
46 this._name = name;
35 this._service = service;
47 this._service = service;
36 this._visited = {};
48 this._visited = {};
37 this._cache = {};
49 this._cache = {};
38 this._services = services;
50 this._services = services;
39 this._container = container;
51 this._container = container;
40 }
52 }
41
53
54 /** the name of the current resolving dependency */
42 getName() {
55 getName() {
43 return this._name;
56 return this._name;
44 }
57 }
45
58
59 /** Returns the container for which 'resolve' method was called */
46 getContainer() {
60 getContainer() {
47 return this._container;
61 return this._container;
48 }
62 }
49
63
64 /** Resolves the specified dependency in the current context
65 * @param name The name of the dependency being resolved
66 */
50 resolve<K extends ContainerKeys<S>>(name: K): TypeOfService<S, K>;
67 resolve<K extends ContainerKeys<S>>(name: K): TypeOfService<S, K>;
68 /** Resolves the specified dependency with the specified default value if
69 * the dependency is missing.
70 *
71 * @param name The name of the dependency being resolved
72 * @param def A default value to return in case of the specified dependency
73 * is missing.
74 */
51 resolve<K extends ContainerKeys<S>, T>(name: K, def: T): TypeOfService<S, K> | T;
75 resolve<K extends ContainerKeys<S>, T>(name: K, def: T): TypeOfService<S, K> | T;
76 /** Resolves the specified dependency and returns undefined in case if the
77 * dependency is missing.
78 *
79 * @param name The name of the dependency being resolved
80 */
52 resolve<K extends ContainerKeys<S>>(name: K, def: undefined): TypeOfService<S, K> | undefined;
81 resolve<K extends ContainerKeys<S>>(name: K, def: undefined): TypeOfService<S, K> | undefined;
53 resolve<K extends ContainerKeys<S>, T>(name: K, def?: T): TypeOfService<S, K> | T | undefined {
82 resolve<K extends ContainerKeys<S>, T>(name: K, def?: T): TypeOfService<S, K> | T | undefined {
54 const d = this._services[name];
83 const d = this._services[name];
55
84
56 if (d !== undefined) {
85 if (d !== undefined) {
57 return this.activate(d, name.toString());
86 return this.activate(d, name.toString());
58 } else {
87 } else {
59 if (arguments.length > 1)
88 if (arguments.length > 1)
60 return def;
89 return def;
61 else
90 else
62 throw new Error(`Service ${name} not found`);
91 throw new Error(`Service ${name} not found`);
63 }
92 }
64 }
93 }
65
94
66 /**
95 /**
67 * registers services local to the the activation context
96 * registers services local to the the activation context
68 *
97 *
69 * @name{string} the name of the service
98 * @name{string} the name of the service
70 * @service{string} the service descriptor to register
99 * @service{string} the service descriptor to register
71 */
100 */
72 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>) {
101 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>) {
73 argumentNotEmptyString(name, "name");
102 argumentNotEmptyString(name, "name");
74
103
75 this._services[name] = service as any;
104 this._services[name] = service as any;
76 }
105 }
77
106
78 createLifetime(): ILifetime {
107 createLifetime(): ILifetime {
79 const id = nextId++;
108 const id = nextId++;
80 const me = this;
109 const me = this;
81 return {
110 return {
82 initialize() {
111 initialize() {
83 },
112 },
84 has() {
113 has() {
85 return id in me._cache;
114 return id in me._cache;
86 },
115 },
87 get() {
116 get() {
88 return me._cache[id];
117 return me._cache[id];
89 },
118 },
90 store(item: any) {
119 store(item: any) {
91 me._cache[id] = item;
120 me._cache[id] = item;
92 }
121 }
93 };
122 };
94 }
123 }
124
95 activate<T>(d: Descriptor<S, T>, name: string) {
125 activate<T>(d: Descriptor<S, T>, name: string) {
96 if (trace.isLogEnabled())
126 if (trace.isLogEnabled())
97 trace.log(`enter ${name} ${d}`);
127 trace.log(`enter ${name} ${d}`);
98
128
99 const ctx = this.enter(d, name);
129 const ctx = this.enter(d, name);
100 const v = d.activate(ctx);
130 const v = d.activate(ctx);
101
131
102 if (trace.isLogEnabled())
132 if (trace.isLogEnabled())
103 trace.log(`leave ${name}`);
133 trace.log(`leave ${name}`);
104
134
105 return v;
135 return v;
106 }
136 }
107
137
108 visit(id: string) {
138 visit(id: string) {
109 const count = this._visited[id] || 0;
139 const count = this._visited[id] || 0;
110 this._visited[id] = count + 1;
140 this._visited[id] = count + 1;
111 return count;
141 return count;
112 }
142 }
113
143
114 getStack(): ActivationContextInfo[] {
144 getStack(): ActivationContextInfo[] {
115 const stack = [{
145 const stack = [{
116 name: this._name,
146 name: this._name,
117 service: this._service.toString()
147 service: this._service.toString()
118 }];
148 }];
119
149
120 return this._parent ?
150 return this._parent ?
121 stack.concat(this._parent.getStack()) :
151 stack.concat(this._parent.getStack()) :
122 stack;
152 stack;
123 }
153 }
124
154
125 private enter(service: Descriptor<S, any>, name: string): this {
155 private enter(service: Descriptor<S, any>, name: string): this {
126 const clone = Object.create(this);
156 const clone = Object.create(this);
127 clone._name = name;
157 clone._name = name;
128 clone._services = Object.create(this._services);
158 clone._services = Object.create(this._services);
129 clone._parent = this;
159 clone._parent = this;
130 clone._service = service;
160 clone._service = service;
131 return clone;
161 return clone;
132 }
162 }
133
163
134 /** Creates a clone for the current context, used to protect it from modifications */
164 /** Creates a clone for the current context, used to protect it from modifications */
135 clone(): this {
165 clone(): this {
136 const clone = Object.create(this);
166 const clone = Object.create(this);
137 clone._services = Object.create(this._services);
167 clone._services = Object.create(this._services);
138 return clone;
168 return clone;
139 }
169 }
140 }
170 }
@@ -1,132 +1,139
1 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
2 import { ValueDescriptor } from "./ValueDescriptor";
2 import { ValueDescriptor } from "./ValueDescriptor";
3 import { ActivationError } from "./ActivationError";
3 import { ActivationError } from "./ActivationError";
4 import { ServiceMap, Descriptor, PartialServiceMap, ContainerProvided, ServiceLocator, ContainerServiceMap, ContainerKeys, TypeOfService, ILifetimeManager } from "./interfaces";
4 import { ServiceMap, Descriptor, PartialServiceMap, ServiceLocator, ContainerServiceMap, ContainerKeys, TypeOfService } from "./interfaces";
5 import { TraceSource } from "../log/TraceSource";
5 import { TraceSource } from "../log/TraceSource";
6 import { Configuration, RegistrationMap } from "./Configuration";
6 import { Configuration, RegistrationMap } from "./Configuration";
7 import { Cancellation } from "../Cancellation";
7 import { Cancellation } from "../Cancellation";
8 import { MapOf, IDestroyable } from "../interfaces";
8 import { MapOf, IDestroyable } from "../interfaces";
9 import { isDescriptor } from "./traits";
9 import { isDescriptor } from "./traits";
10 import { LifetimeManager } from "./LifetimeManager";
10 import { LifetimeManager } from "./LifetimeManager";
11 import { each } from "../safe";
11 import { each } from "../safe";
12 import { FluentRegistrations } from "./fluent/interfaces";
13 import { FluentConfiguration } from "./fluent/FluentConfiguration";
12
14
13 const trace = TraceSource.get("@implab/core/di/ActivationContext");
15 const trace = TraceSource.get("@implab/core/di/ActivationContext");
14
16
15 export class Container<S extends object = any> implements ServiceLocator<S>, IDestroyable {
17 export class Container<S extends object = any> implements ServiceLocator<S>, IDestroyable {
16 readonly _services: ContainerServiceMap<S>;
18 readonly _services: ContainerServiceMap<S>;
17
19
18 readonly _lifetimeManager: ILifetimeManager;
20 readonly _lifetimeManager: LifetimeManager;
19
21
20 readonly _cleanup: (() => void)[];
22 readonly _cleanup: (() => void)[];
21
23
22 readonly _root: Container<S>;
24 readonly _root: Container<S>;
23
25
24 readonly _parent?: Container<S>;
26 readonly _parent?: Container<S>;
25
27
26 _disposed: boolean;
28 _disposed: boolean;
27
29
28 constructor(parent?: Container<S>) {
30 constructor(parent?: Container<S>) {
29 this._parent = parent;
31 this._parent = parent;
30 this._services = parent ? Object.create(parent._services) : {};
32 this._services = parent ? Object.create(parent._services) : {};
31 this._cleanup = [];
33 this._cleanup = [];
32 this._root = parent ? parent.getRootContainer() : this;
34 this._root = parent ? parent.getRootContainer() : this;
33 this._services.container = new ValueDescriptor(this) as any;
35 this._services.container = new ValueDescriptor(this) as any;
34 this._disposed = false;
36 this._disposed = false;
35 this._lifetimeManager = new LifetimeManager();
37 this._lifetimeManager = new LifetimeManager();
36 }
38 }
37
39
38 getRootContainer() {
40 getRootContainer() {
39 return this._root;
41 return this._root;
40 }
42 }
41
43
42 getParent() {
44 getParent() {
43 return this._parent;
45 return this._parent;
44 }
46 }
45
47
46 getLifetimeManager() {
48 getLifetimeManager() {
47 return this._lifetimeManager;
49 return this._lifetimeManager;
48 }
50 }
49
51
50 resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K> {
52 resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K> {
51 trace.debug("resolve {0}", name);
53 trace.debug("resolve {0}", name);
52 const d = this._services[name];
54 const d = this._services[name];
53 if (d === undefined) {
55 if (d === undefined) {
54 if (def !== undefined)
56 if (def !== undefined)
55 return def;
57 return def;
56 else
58 else
57 throw new Error("Service '" + name + "' isn't found");
59 throw new Error("Service '" + name + "' isn't found");
58 } else {
60 } else {
59
61
60 const context = new ActivationContext<S>(this, this._services, String(name), d);
62 const context = new ActivationContext<S>(this, this._services, String(name), d);
61 try {
63 try {
62 return d.activate(context);
64 return d.activate(context);
63 } catch (error) {
65 } catch (error) {
64 throw new ActivationError(name.toString(), context.getStack(), error);
66 throw new ActivationError(name.toString(), context.getStack(), error);
65 }
67 }
66 }
68 }
67 }
69 }
68
70
69 /**
71 /**
70 * @deprecated use resolve() method
72 * @deprecated use resolve() method
71 */
73 */
72 getService<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>) {
74 getService<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>) {
73 return this.resolve(name, def);
75 return this.resolve(name, def);
74 }
76 }
75
77
76 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>): this;
78 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>): this;
77 register(services: PartialServiceMap<S>): this;
79 register(services: PartialServiceMap<S>): this;
78 register<K extends keyof S>(nameOrCollection: K | ServiceMap<S>, service?: Descriptor<S, S[K]>) {
80 register<K extends keyof S>(nameOrCollection: K | ServiceMap<S>, service?: Descriptor<S, S[K]>) {
79 if (arguments.length === 1) {
81 if (arguments.length === 1) {
80 const data = nameOrCollection as ServiceMap<S>;
82 const data = nameOrCollection as ServiceMap<S>;
81
83
82 each(data, (v, k) => this.register(k, v));
84 each(data, (v, k) => this.register(k, v));
83 } else {
85 } else {
84 if (!isDescriptor(service))
86 if (!isDescriptor(service))
85 throw new Error("The service parameter must be a descriptor");
87 throw new Error("The service parameter must be a descriptor");
86
88
87 this._services[nameOrCollection as K] = service as any;
89 this._services[nameOrCollection as K] = service as any;
88 }
90 }
89 return this;
91 return this;
90 }
92 }
91
93
92 onDispose(callback: () => void) {
94 onDispose(callback: () => void) {
93 if (!(callback instanceof Function))
95 if (!(callback instanceof Function))
94 throw new Error("The callback must be a function");
96 throw new Error("The callback must be a function");
95 this._cleanup.push(callback);
97 this._cleanup.push(callback);
96 }
98 }
97
99
98 destroy() {
100 destroy() {
99 return this.dispose();
101 return this.dispose();
100 }
102 }
101 dispose() {
103 dispose() {
102 if (this._disposed)
104 if (this._disposed)
103 return;
105 return;
104 this._disposed = true;
106 this._disposed = true;
105 for (const f of this._cleanup)
107 for (const f of this._cleanup)
106 f();
108 f();
107 }
109 }
108
110
109 /**
111 /**
110 * @param{String|Object} config
112 * @param{String|Object} config
111 * The configuration of the contaier. Can be either a string or an object,
113 * The configuration of the contaier. Can be either a string or an object,
112 * if the configuration is an object it's treated as a collection of
114 * if the configuration is an object it's treated as a collection of
113 * services which will be registed in the contaier.
115 * services which will be registed in the contaier.
114 *
116 *
115 * @param{Function} opts.contextRequire
117 * @param{Function} opts.contextRequire
116 * The function which will be used to load a configuration or types for services.
118 * The function which will be used to load a configuration or types for services.
117 *
119 *
118 */
120 */
119 async configure(config: string | RegistrationMap<S>, opts?: any, ct = Cancellation.none) {
121 async configure(config: string | RegistrationMap<S>, opts?: any, ct = Cancellation.none) {
120 const c = new Configuration<S>(this);
122 const c = new Configuration<S>(this);
121
123
122 if (typeof (config) === "string") {
124 if (typeof (config) === "string") {
123 return c.loadConfiguration(config, opts && opts.contextRequire, ct);
125 return c.loadConfiguration(config, opts && opts.contextRequire, ct);
124 } else {
126 } else {
125 return c.applyConfiguration(config, opts && opts.contextRequire, ct);
127 return c.applyConfiguration(config, opts && opts.contextRequire, ct);
126 }
128 }
127 }
129 }
128
130
131 async fluent<K extends keyof S>(config: FluentRegistrations<K, S>, ct = Cancellation.none): Promise<this> {
132 await new FluentConfiguration<S>().register(config).apply(this, ct);
133 return this;
134 }
135
129 createChildContainer<S2 extends object = S>(): Container<S & S2> {
136 createChildContainer<S2 extends object = S>(): Container<S & S2> {
130 return new Container<S & S2>(this as any);
137 return new Container<S & S2>(this as any);
131 }
138 }
132 }
139 }
@@ -1,176 +1,198
1 import { IDestroyable, MapOf } from "../interfaces";
1 import { IDestroyable, MapOf } from "../interfaces";
2 import { argumentNotNull, isDestroyable } from "../safe";
2 import { argumentNotNull, isDestroyable, primitive, isNull, argumentNotEmptyString } from "../safe";
3 import { ILifetimeManager, ILifetime } from "./interfaces";
3 import { ILifetime } from "./interfaces";
4 import { ActivationContext } from "./ActivationContext";
4 import { ActivationContext } from "./ActivationContext";
5 import { Container } from "./Container";
5 import { Container } from "./Container";
6
6
7 function safeCall(item: () => void) {
7 function safeCall(item: () => void) {
8 try {
8 try {
9 item();
9 item();
10 } catch {
10 } catch {
11 // silence!
11 // silence!
12 }
12 }
13 }
13 }
14
14
15 const emptyLifetime: ILifetime = {
15 const emptyLifetime: ILifetime = Object.freeze({
16 has() {
16 has() {
17 return false;
17 return false;
18 },
18 },
19
19
20 initialize() {
20 initialize() {
21
21
22 },
22 },
23
23
24 get() {
24 get() {
25 throw new Error("The specified item isn't registered with this lifetime manager");
25 throw new Error("The specified item isn't registered with this lifetime manager");
26 },
26 },
27
27
28 store() {
28 store() {
29 // does nothing
29 // does nothing
30 }
30 }
31
31
32 };
32 });
33
33
34 const unknownLifetime: ILifetime = {
34 const unknownLifetime: ILifetime = Object.freeze({
35 has() {
35 has() {
36 throw new Error("The lifetime is unknown");
36 return false;
37 },
37 },
38 initialize() {
38 initialize() {
39 throw new Error("Can't call initialize on the unknown lifetime object");
39 throw new Error("Can't call initialize on the unknown lifetime object");
40 },
40 },
41 get() {
41 get() {
42 throw new Error("The lifetime object isn't initialized");
42 throw new Error("The lifetime object isn't initialized");
43 },
43 },
44 store() {
44 store() {
45 throw new Error("Can't store a value in the unknown lifetime object");
45 throw new Error("Can't store a value in the unknown lifetime object");
46 }
46 }
47 }
47 });
48
48
49 let nextId = 0;
49 let nextId = 0;
50
50
51 export class LifetimeManager implements IDestroyable, ILifetimeManager {
51 const singletons: { [k in keyof any]: any; } = {};
52
53 export class LifetimeManager implements IDestroyable {
52 private _cleanup: (() => void)[] = [];
54 private _cleanup: (() => void)[] = [];
53 private _cache: MapOf<any> = {};
55 private _cache: MapOf<any> = {};
54 private _destroyed = false;
56 private _destroyed = false;
55
57
56 private _pending: MapOf<boolean> = {};
58 private _pending: MapOf<boolean> = {};
57
59
58 create(): ILifetime {
60 create(): ILifetime {
59 const self = this;
61 const self = this;
60 const id = ++nextId;
62 const id = ++nextId;
61 return {
63 return {
62 has() {
64 has() {
63 return (id in self._cache);
65 return (id in self._cache);
64 },
66 },
65
67
66 get() {
68 get() {
67 const t = self._cache[id];
69 const t = self._cache[id];
68 if (t === undefined)
70 if (t === undefined)
69 throw new Error(`The item with with the key ${id} isn't found`);
71 throw new Error(`The item with with the key ${id} isn't found`);
70 return t;
72 return t;
71 },
73 },
72
74
73 initialize() {
75 initialize() {
74 if (self._pending[id])
76 if (self._pending[id])
75 throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`);
77 throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`);
76 self._pending[id] = true;
78 self._pending[id] = true;
77 },
79 },
78
80
79 store(item: any, cleanup?: (item: any) => void) {
81 store(item: any, cleanup?: (item: any) => void) {
80 argumentNotNull(id, "id");
82 argumentNotNull(id, "id");
81 argumentNotNull(item, "item");
83 argumentNotNull(item, "item");
82
84
83 if (this.has())
85 if (this.has())
84 throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
86 throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
85 delete self._pending[id];
87 delete self._pending[id];
86
88
87 self._cache[id] = item;
89 self._cache[id] = item;
88
90
89 if (self._destroyed)
91 if (self._destroyed)
90 throw new Error("Lifetime manager is destroyed");
92 throw new Error("Lifetime manager is destroyed");
91 if (cleanup) {
93 if (cleanup) {
92 self._cleanup.push(() => cleanup(item));
94 self._cleanup.push(() => cleanup(item));
93 } else if (isDestroyable(item)) {
95 } else if (isDestroyable(item)) {
94 self._cleanup.push(() => item.destroy());
96 self._cleanup.push(() => item.destroy());
95 }
97 }
96 }
98 }
97 };
99 };
98 }
100 }
99
101
100 destroy() {
102 destroy() {
101 if (!this._destroyed) {
103 if (!this._destroyed) {
102 this._destroyed = true;
104 this._destroyed = true;
103 this._cleanup.forEach(safeCall);
105 this._cleanup.forEach(safeCall);
104 this._cleanup.length = 0;
106 this._cleanup.length = 0;
105 }
107 }
106 }
108 }
107
109
108 static empty(): ILifetime {
110 static empty(): ILifetime {
109 return emptyLifetime;
111 return emptyLifetime;
110 }
112 }
111
113
112 static hierarchyLifetime(): ILifetime {
114 static hierarchyLifetime(): ILifetime {
113 let _lifetime = unknownLifetime;
115 let _lifetime = unknownLifetime;
114 return {
116 return {
115 initialize(context: ActivationContext<any>) {
117 initialize(context: ActivationContext<any>) {
116 if (_lifetime !== unknownLifetime)
118 if (_lifetime !== unknownLifetime)
117 throw new Error("Cyclic reference activation detected");
119 throw new Error("Cyclic reference activation detected");
118
120
119 _lifetime = context.getContainer().getLifetimeManager().create(context);
121 _lifetime = context.getContainer().getLifetimeManager().create();
120 },
122 },
121 get() {
123 get() {
122 return _lifetime.get();
124 return _lifetime.get();
123 },
125 },
124 has() {
126 has() {
125 return _lifetime.has();
127 return _lifetime.has();
126 },
128 },
127 store(item: any, cleanup?: (item: any) => void) {
129 store(item: any, cleanup?: (item: any) => void) {
128 return _lifetime.store(item, cleanup);
130 return _lifetime.store(item, cleanup);
129 }
131 }
130 };
132 };
131 }
133 }
132
134
133 static contextLifetime(): ILifetime {
135 static contextLifetime(): ILifetime {
134 let _lifetime = unknownLifetime;
136 let _lifetime = unknownLifetime;
135 return {
137 return {
136 initialize(context: ActivationContext<any>) {
138 initialize(context: ActivationContext<any>) {
137 if (_lifetime !== unknownLifetime)
139 if (_lifetime !== unknownLifetime)
138 throw new Error("Cyclic reference detected");
140 throw new Error("Cyclic reference detected");
139 _lifetime = context.createLifetime();
141 _lifetime = context.createLifetime();
140 },
142 },
141 get() {
143 get() {
142 return _lifetime.get();
144 return _lifetime.get();
143 },
145 },
144 has() {
146 has() {
145 return _lifetime.has();
147 return _lifetime.has();
146 },
148 },
147 store(item: any) {
149 store(item: any) {
148 _lifetime.store(item);
150 _lifetime.store(item);
149 }
151 }
150 };
152 };
151 }
153 }
152
154
153 static singletonLifetime(typeId: string): ILifetime {
155 static singletonLifetime(typeId: string): ILifetime {
154 return emptyLifetime;
156 argumentNotEmptyString(typeId, "typeId");
157 let pending = false;
158 return {
159 has() {
160 return typeId in singletons;
161 },
162 get() {
163 if (!this.has())
164 throw new Error(`The instance ${typeId} doesn't exists`);
165 return singletons[typeId];
166 },
167 initialize() {
168 if (pending)
169 throw new Error("Cyclic reference detected");
170 pending = true;
171 },
172 store(item: any) {
173 singletons[typeId] = item;
174 pending = false;
175 }
176 };
155 }
177 }
156
178
157 static containerLifetime(container: Container<any>) {
179 static containerLifetime(container: Container<any>) {
158 let _lifetime = unknownLifetime;
180 let _lifetime = unknownLifetime;
159 return {
181 return {
160 initialize(context: ActivationContext<any>) {
182 initialize(context: ActivationContext<any>) {
161 if (_lifetime !== unknownLifetime)
183 if (_lifetime !== unknownLifetime)
162 throw new Error("Cyclic reference detected");
184 throw new Error("Cyclic reference detected");
163 _lifetime = container.getLifetimeManager().create(context);
185 _lifetime = container.getLifetimeManager().create();
164 },
186 },
165 get() {
187 get() {
166 return _lifetime.get();
188 return _lifetime.get();
167 },
189 },
168 has() {
190 has() {
169 return _lifetime.has();
191 return _lifetime.has();
170 },
192 },
171 store(item: any) {
193 store(item: any) {
172 _lifetime.store(item);
194 _lifetime.store(item);
173 }
195 }
174 };
196 };
175 }
197 }
176 }
198 }
@@ -1,154 +1,154
1 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
2 import { Descriptor, ServiceMap, PartialServiceMap, ILifetimeManager, ILifetime } from "./interfaces";
2 import { Descriptor, ServiceMap, PartialServiceMap, ILifetime } from "./interfaces";
3 import { isPrimitive, keys, isNull } from "../safe";
3 import { isPrimitive, keys, isNull } from "../safe";
4 import { TraceSource } from "../log/TraceSource";
4 import { TraceSource } from "../log/TraceSource";
5 import { isDescriptor } from "./traits";
5 import { isDescriptor } from "./traits";
6 import { LifetimeManager } from "./LifetimeManager";
6 import { LifetimeManager } from "./LifetimeManager";
7 import { MatchingMemberKeys } from "../interfaces";
7 import { MatchingMemberKeys } from "../interfaces";
8
8
9 const trace = TraceSource.get("@implab/core/di/ActivationContext");
9 const trace = TraceSource.get("@implab/core/di/ActivationContext");
10
10
11 function injectMethod<T, M extends keyof T, S extends object, A>(target: T, method: M, context: ActivationContext<S>, args: A) {
11 function injectMethod<T, M extends keyof T, S extends object, A>(target: T, method: M, context: ActivationContext<S>, args: A) {
12
12
13 const m = target[method];
13 const m = target[method];
14 if (!m || typeof m !== "function")
14 if (!m || typeof m !== "function")
15 throw new Error("Method '" + method + "' not found");
15 throw new Error("Method '" + method + "' not found");
16
16
17 if (args instanceof Array)
17 if (args instanceof Array)
18 return m.apply(target, _parse(args, context, "." + method));
18 return m.apply(target, _parse(args, context, "." + method));
19 else
19 else
20 return m.call(target, _parse(args, context, "." + method));
20 return m.call(target, _parse(args, context, "." + method));
21 }
21 }
22
22
23 function makeCleanupCallback<T>(method: Cleaner<T>) {
23 function makeCleanupCallback<T>(method: Cleaner<T>) {
24 if (typeof (method) === "function") {
24 if (typeof (method) === "function") {
25 return (target: T) => {
25 return (target: T) => {
26 method(target);
26 method(target);
27 };
27 };
28 } else {
28 } else {
29 return (target: T) => {
29 return (target: T) => {
30 const m = target[method] as any;
30 const m = target[method] as any;
31 m.apply(target);
31 m.apply(target);
32 };
32 };
33 }
33 }
34 }
34 }
35
35
36 function _parse(value: any, context: ActivationContext<any>, path: string): any {
36 function _parse(value: any, context: ActivationContext<any>, path: string): any {
37 if (isPrimitive(value))
37 if (isPrimitive(value))
38 return value as any;
38 return value as any;
39
39
40 trace.debug("parse {0}", path);
40 trace.debug("parse {0}", path);
41
41
42 if (isDescriptor(value))
42 if (isDescriptor(value))
43 return context.activate(value, path);
43 return context.activate(value, path);
44
44
45 if (value instanceof Array)
45 if (value instanceof Array)
46 return value.map((x, i) => _parse(x, context, `${path}[${i}]`)) as any;
46 return value.map((x, i) => _parse(x, context, `${path}[${i}]`)) as any;
47
47
48 const t: any = {};
48 const t: any = {};
49
49
50 keys(value).forEach(p => t[p] = _parse(value[p], context, `${path}.${p}`));
50 keys(value).forEach(p => t[p] = _parse(value[p], context, `${path}.${p}`));
51
51
52 return t;
52 return t;
53 }
53 }
54
54
55 export type Cleaner<T> = ((x: T) => void) | MatchingMemberKeys<() => void, T>;
55 export type Cleaner<T> = ((x: T) => void) | MatchingMemberKeys<() => void, T>;
56
56
57 export type InjectionSpec<T> = {
57 export type InjectionSpec<T> = {
58 [m in keyof T]?: any;
58 [m in keyof T]?: any;
59 };
59 };
60
60
61 export interface ServiceDescriptorParams<S extends object, T, P extends any[]> {
61 export interface ServiceDescriptorParams<S extends object, T, P extends any[]> {
62 lifetime?: ILifetime;
62 lifetime?: ILifetime;
63
63
64 params?: P;
64 params?: P;
65
65
66 inject?: InjectionSpec<T>[];
66 inject?: InjectionSpec<T>[];
67
67
68 services?: PartialServiceMap<S>;
68 services?: PartialServiceMap<S>;
69
69
70 cleanup?: Cleaner<T>;
70 cleanup?: Cleaner<T>;
71 }
71 }
72
72
73 export class ServiceDescriptor<S extends object, T, P extends any[]> implements Descriptor<S, T> {
73 export class ServiceDescriptor<S extends object, T, P extends any[]> implements Descriptor<S, T> {
74 _services: ServiceMap<S>;
74 _services: ServiceMap<S>;
75
75
76 _params: P | undefined;
76 _params: P | undefined;
77
77
78 _inject: InjectionSpec<T>[];
78 _inject: InjectionSpec<T>[];
79
79
80 _cleanup: ((item: T) => void) | undefined;
80 _cleanup: ((item: T) => void) | undefined;
81
81
82 _lifetime = LifetimeManager.empty();
82 _lifetime = LifetimeManager.empty();
83
83
84 _objectLifetime: ILifetime | undefined;
84 _objectLifetime: ILifetime | undefined;
85
85
86 constructor(opts: ServiceDescriptorParams<S, T, P>) {
86 constructor(opts: ServiceDescriptorParams<S, T, P>) {
87
87
88 if (opts.lifetime)
88 if (opts.lifetime)
89 this._lifetime = opts.lifetime;
89 this._lifetime = opts.lifetime;
90
90
91 if (!isNull(opts.params))
91 if (!isNull(opts.params))
92 this._params = opts.params;
92 this._params = opts.params;
93
93
94 this._inject = opts.inject || [];
94 this._inject = opts.inject || [];
95
95
96 this._services = (opts.services || {}) as ServiceMap<S>;
96 this._services = (opts.services || {}) as ServiceMap<S>;
97
97
98 if (opts.cleanup) {
98 if (opts.cleanup) {
99 if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function))
99 if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function))
100 throw new Error(
100 throw new Error(
101 "The cleanup parameter must be either a function or a function name");
101 "The cleanup parameter must be either a function or a function name");
102
102
103 this._cleanup = makeCleanupCallback(opts.cleanup);
103 this._cleanup = makeCleanupCallback(opts.cleanup);
104 }
104 }
105 }
105 }
106
106
107 activate(context: ActivationContext<S>) {
107 activate(context: ActivationContext<S>) {
108 const lifetime = this._lifetime;
108 const lifetime = this._lifetime;
109
109
110 if (lifetime.has()) {
110 if (lifetime.has()) {
111 return lifetime.get();
111 return lifetime.get();
112 } else {
112 } else {
113 lifetime.initialize(context);
113 lifetime.initialize(context);
114 const instance = this._create(context);
114 const instance = this._create(context);
115 lifetime.store(instance, this._cleanup);
115 lifetime.store(instance, this._cleanup);
116 return instance;
116 return instance;
117 }
117 }
118 }
118 }
119
119
120 _factory(...params: any[]): T {
120 _factory(...params: any[]): T {
121 throw Error("Not implemented");
121 throw Error("Not implemented");
122 }
122 }
123
123
124 _create(context: ActivationContext<S>) {
124 _create(context: ActivationContext<S>) {
125 trace.debug(`constructing ${context._name}`);
125 trace.debug(`constructing ${context._name}`);
126
126
127 if (this._services) {
127 if (this._services) {
128 keys(this._services).forEach(p => context.register(p, this._services[p]));
128 keys(this._services).forEach(p => context.register(p, this._services[p]));
129 }
129 }
130
130
131 let instance: T;
131 let instance: T;
132
132
133 if (this._params === undefined) {
133 if (this._params === undefined) {
134 instance = this._factory();
134 instance = this._factory();
135 } else if (this._params instanceof Array) {
135 } else if (this._params instanceof Array) {
136 instance = this._factory.apply(this, _parse(this._params, context, "args"));
136 instance = this._factory.apply(this, _parse(this._params, context, "args"));
137 } else {
137 } else {
138 instance = this._factory(_parse(this._params, context, "args"));
138 instance = this._factory(_parse(this._params, context, "args"));
139 }
139 }
140
140
141 if (this._inject) {
141 if (this._inject) {
142 this._inject.forEach(spec => {
142 this._inject.forEach(spec => {
143 for (const m in spec)
143 for (const m in spec)
144 injectMethod(instance, m, context, spec[m]);
144 injectMethod(instance, m, context, spec[m]);
145 });
145 });
146 }
146 }
147 return instance;
147 return instance;
148 }
148 }
149
149
150 clone() {
150 clone() {
151 return Object.create(this);
151 return Object.create(this);
152 }
152 }
153
153
154 }
154 }
@@ -1,54 +1,147
1 import { Resolver, LazyDependencyOptions, DependencyOptions } from "./interfaces";
1 import { Resolver, RegistrationBuilder } from "./interfaces";
2 import { Container } from "../Container";
2 import { Container } from "../Container";
3 import { Descriptor, ILifetime, ContainerKeys } from "../interfaces";
3 import { Descriptor, ILifetime, ActivationType, PartialServiceMap } from "../interfaces";
4 import { ActivationContext } from "../ActivationContext";
4 import { DescriptorImpl } from "./DescriptorImpl";
5 import { LifetimeManager } from "../LifetimeManager";
6 import { isString, each, isPrimitive, isPromise, oid } from "../../safe";
7
8 export class DescriptorBuilder<S extends object, T> {
9 private readonly _container: Container<S>;
10 private readonly _cb: (d: Descriptor<S, T>) => void;
11
12 private readonly _eb: (err: any) => void;
5
13
6 export class DescriptorBuilder<T, S extends object> {
14 private _lifetime = LifetimeManager.empty();
7 readonly _container: Container<S>;
15
8 readonly _cb: (d: Descriptor<S, T>) => void;
16 private _overrides?: PartialServiceMap<S>;
17
18 private _cleanup?: (item: T) => void;
9
19
10 constructor(container: Container<S>, cb: (d: Descriptor<S, T>) => void) {
20 private _factory?: (resolve: Resolver<S>) => T;
21
22 private _pending = 1;
23
24 private _failed = false;
25
26 constructor(container: Container<S>, cb: (d: Descriptor<S, T>) => void, eb: (err: any) => void) {
11 this._container = container;
27 this._container = container;
12 this._cb = cb;
28 this._cb = cb;
29 this._eb = eb;
30 }
31
32 build<T2>(): DescriptorBuilder<S, T2> {
33 this._defer();
34 return new DescriptorBuilder<S, T2>(this._container, () => this._complete(), err => this._fail(err));
13 }
35 }
14
36
15 factory(f: (resolve: Resolver<S>, activate: (lifetime: ILifetime, factory: () => any, cleanup?: (item: any) => void) => any) => T): void {
37 override<K extends keyof S>(name: K, builder: RegistrationBuilder<S, S[K]>): this;
16 this._cb({
38 override<K extends keyof S>(services: { [name in K]: RegistrationBuilder<S, S[K]> }): this;
17 activate(context: ActivationContext<S>) {
39 override<K extends keyof S>(nameOrServices: K | { [name in K]: RegistrationBuilder<S, S[K]> }, builder?: RegistrationBuilder<S, S[K]>): this {
18 const resolve = (name: ContainerKeys<S>, opts?: DependencyOptions | LazyDependencyOptions) => {
40 const overrides: PartialServiceMap<S> = this._overrides ?
19 if (opts && "lazy" in opts && opts.lazy) {
41 this._overrides :
20 const c2 = context.clone();
42 (this._overrides = {});
21 return () => {
43
22 return opts.optional ? c2.resolve(name, opts.default) : c2.resolve(name);
44 const guard = (v: void | Promise<void>) => {
23 };
45 if (isPromise(v))
24 } else {
46 v.catch(err => this._fail(err));
25 return opts && opts.optional ? context.resolve(name, opts.default) : context.resolve(name);
26 }
27 };
47 };
28
48
29 const activate = (lifetime: ILifetime, factory: () => any, cleanup?: (item: any) => void) => {
49 if (isPrimitive(nameOrServices)) {
30 if (lifetime.has()) {
50 if (builder) {
31 return lifetime.get();
51 this._defer();
52 const d = new DescriptorBuilder<S, S[K]>(
53 this._container,
54 result => {
55 overrides[nameOrServices] = result;
56 this._complete();
57 },
58 err => this._fail(err)
59 );
60
61 try {
62 guard(builder(d));
63 } catch (err) {
64 this._fail(err);
65 }
66 }
32 } else {
67 } else {
33 lifetime.initialize(context);
68 each(nameOrServices, (v, k) => this.override(k, v));
34 const instance = factory();
69 }
35 lifetime.store(instance, cleanup);
70 return this;
36 return instance;
37 }
71 }
38
72
39 };
73 lifetime(lifetime: "singleton", typeId: string): this;
74 lifetime(lifetime: ILifetime | Exclude<ActivationType, "singleton">): this;
75 lifetime(lifetime: ILifetime | ActivationType, typeId?: string): this {
76 if (isString(lifetime)) {
77 this._lifetime = this._resolveLifetime(lifetime, typeId);
78 } else {
79 this._lifetime = lifetime;
80 }
81 return this;
82 }
40
83
41 return f(resolve, activate);
84 cleanup(cb: (item: T) => void): this {
85 this._cleanup = cb;
86 return this;
42 }
87 }
43 });
88
89 factory(f: (resolve: Resolver<S>) => T): void {
90 this._factory = f;
91 this._complete();
44 }
92 }
45
93
46 value(v: T): void {
94 value(v: T): void {
47 this._cb({
95 this._cb({
48 activate() {
96 activate() {
49 return v;
97 return v;
50 }
98 }
51 });
99 });
52 }
100 }
53
101
102 _resolveLifetime(activation: ActivationType, typeId?: string | object) {
103 switch (activation) {
104 case "container":
105 return LifetimeManager.containerLifetime(this._container);
106 case "hierarchy":
107 return LifetimeManager.hierarchyLifetime();
108 case "context":
109 return LifetimeManager.contextLifetime();
110 case "singleton":
111 if (!typeId)
112 throw Error("The singleton activation requires a typeId");
113
114 const _oid = isString(typeId) ? typeId : oid(typeId);
115
116 return LifetimeManager.singletonLifetime(_oid);
117 default:
118 return LifetimeManager.empty();
54 }
119 }
120 }
121
122 _defer() {
123 this._pending++;
124 }
125
126 _complete() {
127 if (--this._pending === 0) {
128 if (!this._factory)
129 throw new Error("The factory must be specified");
130
131 this._cb(new DescriptorImpl<S, T>({
132 lifetime: this._lifetime,
133 factory: this._factory,
134 overrides: this._overrides,
135 cleanup: this._cleanup
136 }));
137 }
138 }
139
140 _fail(err: any) {
141 if (!this._failed) {
142 this._failed = true;
143 this._eb.call(undefined, err);
144 }
145 }
146
147 }
@@ -1,48 +1,52
1 import { primitive } from "../../safe";
1 import { primitive } from "../../safe";
2 import { AnnotationBuilder } from "../Annotations";
2 import { TypeOfService, ContainerKeys, ActivationType, ILifetime } from "../interfaces";
3 import { ILifetime, TypeOfService, ContainerKeys } from "../interfaces";
3 import { ICancellation } from "../../interfaces";
4
4
5 export interface DependencyOptions {
5 export interface DependencyOptions {
6 optional?: boolean;
6 optional?: boolean;
7 default?: any;
7 default?: any;
8 }
8 }
9
9
10 export interface LazyDependencyOptions extends DependencyOptions {
10 export interface LazyDependencyOptions extends DependencyOptions {
11 lazy: true;
11 lazy: true;
12 }
12 }
13
13
14 export type ExtractService<K, S> = K extends keyof S ? S[K] : never;
14 export type ExtractService<K, S> = K extends keyof S ? S[K] : never;
15
15
16 export type ExtractDependency<D, S> = D extends { $dependency: infer K } ?
16 export type ExtractDependency<D, S> = D extends { $dependency: infer K } ?
17 D extends { lazy: true } ? () => ExtractService<K, S> : ExtractService<K, S> :
17 D extends { lazy: true } ? () => ExtractService<K, S> : ExtractService<K, S> :
18 D extends { $type: new (...args: any[]) => infer I } ? I :
18 D extends { $type: new (...args: any[]) => infer I } ? I :
19 D extends { $factory: (...args: any[]) => infer R } ? R :
19 D extends { $factory: (...args: any[]) => infer R } ? R :
20 WalkDependencies<D, S>;
20 WalkDependencies<D, S>;
21
21
22 export type WalkDependencies<D, S> = D extends primitive ? D :
22 export type WalkDependencies<D, S> = D extends primitive ? D :
23 { [K in keyof D]: ExtractDependency<D[K], S> };
23 { [K in keyof D]: ExtractDependency<D[K], S> };
24
24
25 export type ServiceModule<T, S extends object, M extends keyof any = "service"> = {
26 [m in M]: AnnotationBuilder<T, S>;
27 };
28
29 export type InferReferenceType<S extends object, K extends ContainerKeys<S>, O> = O extends { default: infer X } ? (TypeOfService<S, K> | X) :
25 export type InferReferenceType<S extends object, K extends ContainerKeys<S>, O> = O extends { default: infer X } ? (TypeOfService<S, K> | X) :
30 O extends { optional: true } ? (TypeOfService<S, K> | undefined) :
26 O extends { optional: true } ? (TypeOfService<S, K> | undefined) :
31 TypeOfService<S, K>;
27 TypeOfService<S, K>;
32
28
33 export interface Resolver<S extends object> {
29 export interface Resolver<S extends object> {
34 <K extends ContainerKeys<S>, O extends LazyDependencyOptions>(this: void, name: K, opts: O): () => InferReferenceType<S, K, O>;
30 <K extends ContainerKeys<S>, O extends LazyDependencyOptions>(this: void, name: K, opts: O): () => InferReferenceType<S, K, O>;
35 <K extends ContainerKeys<S>, O extends DependencyOptions>(this: void, name: K, opts?: O): InferReferenceType<S, K, O>;
31 <K extends ContainerKeys<S>, O extends DependencyOptions>(this: void, name: K, opts?: O): InferReferenceType<S, K, O>;
36 }
32 }
37
33
38 export interface DescriptorBuilder<T, S extends object> {
34 export interface DescriptorBuilder<S extends object, T> {
39 service(service: AnnotationBuilder<T, S> | ServiceModule<T, S>): void;
35 factory(f: (resolve: Resolver<S>) => T): void;
36
37 build<T2>(): DescriptorBuilder<S, T2>;
40
38
41 factory(f: (resolve: Resolver<S>, activate: <T2>(lifetime: ILifetime, factory: () => T2, cleanup?: (item: T2) => void) => T2) => T): void;
39 override<K extends keyof S>(name: K, builder: RegistrationBuilder<S, S[K]>): this;
40 override<K extends keyof S>(services: { [name in K]: RegistrationBuilder<S, S[K]> }): this;
41
42 lifetime(lifetime: "singleton", typeId: any): this;
43 lifetime(lifetime: ILifetime | Exclude<ActivationType, "singleton">): this;
44
45 cleanup(cb: (item: T) => void): this;
42
46
43 value(v: T): void;
47 value(v: T): void;
44 }
48 }
45
49
46 export interface Configuration<S extends object, Y extends keyof S = keyof S> {
50 export type RegistrationBuilder<S extends object, T> = (d: DescriptorBuilder<S, T>, ct?: ICancellation) => void | Promise<void>;
47 register<K extends Y>(name: K, builder: (d: DescriptorBuilder<S[K], S>) => void): Configuration<S, Exclude<Y, K>>;
51
48 }
52 export type FluentRegistrations<K extends keyof S, S extends object> = { [k in K]: RegistrationBuilder<S, S[k]> };
@@ -1,57 +1,53
1 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
2
2
3 export interface Descriptor<S extends object = any, T = any> {
3 export interface Descriptor<S extends object = any, T = any> {
4 activate(context: ActivationContext<S>): T;
4 activate(context: ActivationContext<S>): T;
5 }
5 }
6
6
7 export type ServiceMap<S extends object> = {
7 export type ServiceMap<S extends object> = {
8 [k in keyof S]: Descriptor<S, S[k]>;
8 [k in keyof S]: Descriptor<S, S[k]>;
9 };
9 };
10
10
11 export type ContainerKeys<S extends object> = keyof S | keyof ContainerProvided<S>;
11 export type ContainerKeys<S extends object> = keyof S | keyof ContainerProvided<S>;
12
12
13 export type TypeOfService<S extends object, K> =
13 export type TypeOfService<S extends object, K> =
14 K extends keyof ContainerProvided<S> ? ContainerProvided<S>[K] :
14 K extends keyof ContainerProvided<S> ? ContainerProvided<S>[K] :
15 K extends keyof S ? S[K] : never;
15 K extends keyof S ? S[K] : never;
16
16
17 export type ContainerServiceMap<S extends object> = {
17 export type ContainerServiceMap<S extends object> = {
18 [K in ContainerKeys<S>]: Descriptor<S, TypeOfService<S, K>>;
18 [K in ContainerKeys<S>]: Descriptor<S, TypeOfService<S, K>>;
19 };
19 };
20
20
21 export type PartialServiceMap<S extends object> = {
21 export type PartialServiceMap<S extends object> = {
22 [k in keyof S]?: Descriptor<S, S[k]>;
22 [k in keyof S]?: Descriptor<S, S[k]>;
23 };
23 };
24
24
25 export interface ServiceLocator<S extends object> {
25 export interface ServiceLocator<S extends object> {
26 resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K>;
26 resolve<K extends ContainerKeys<S>>(name: K, def?: TypeOfService<S, K>): TypeOfService<S, K>;
27 }
27 }
28
28
29 export interface ContainerProvided<S extends object> {
29 export interface ContainerProvided<S extends object> {
30 container: ServiceLocator<S>;
30 container: ServiceLocator<S>;
31 }
31 }
32
32
33 export type ContainerRegistered<S extends object> = /*{
33 export type ContainerRegistered<S extends object> = /*{
34 [K in Exclude<keyof S, keyof ContainerProvided<S>>]: S[K];
34 [K in Exclude<keyof S, keyof ContainerProvided<S>>]: S[K];
35 };*/
35 };*/
36 Exclude<S, ContainerProvided<S>>;
36 Exclude<S, ContainerProvided<S>>;
37
37
38 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
38 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
39
39
40 export interface ILifetimeManager {
41 create(context: ActivationContext<any>): ILifetime;
42 }
43
44 /**
40 /**
45 * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет
41 * Интерфейс для управления жизнью экземпляра объекта. Каждая регистрация имеет
46 * свой собственный объект `ILifetime`, который создается при первой активации
42 * свой собственный объект `ILifetime`, который создается при первой активации
47 */
43 */
48 export interface ILifetime {
44 export interface ILifetime {
49 /** Проверяет, что уже создан экземпляр объекта */
45 /** Проверяет, что уже создан экземпляр объекта */
50 has(): boolean;
46 has(): boolean;
51
47
52 get(): any;
48 get(): any;
53
49
54 initialize(context: ActivationContext<any>): void;
50 initialize(context: ActivationContext<any>): void;
55
51
56 store(item: any, cleanup?: (item: any) => void): void;
52 store(item: any, cleanup?: (item: any) => void): void;
57 }
53 }
@@ -1,12 +1,11
1 import { isPrimitive } from "../safe";
1 import { isPrimitive } from "../safe";
2 import { Descriptor } from "./interfaces";
2 import { Descriptor } from "./interfaces";
3 import { Configuration } from "./fluent/Configuration";
3 import { FluentConfiguration } from "./fluent/FluentConfiguration";
4
5 export function isDescriptor(x: any): x is Descriptor {
4 export function isDescriptor(x: any): x is Descriptor {
6 return (!isPrimitive(x)) &&
5 return (!isPrimitive(x)) &&
7 (x.activate instanceof Function);
6 (x.activate instanceof Function);
8 }
7 }
9
8
10 export function configure<S extends object>() {
9 export function fluent<S extends object>() {
11 return new Configuration<S>();
10 return new FluentConfiguration<S>();
12 }
11 }
@@ -1,500 +1,502
1 import { ICancellable, Constructor, IDestroyable } from "./interfaces";
1 import { ICancellable, Constructor, IDestroyable, PromiseOrValue } from "./interfaces";
2 import { Cancellation } from "./Cancellation";
2 import { Cancellation } from "./Cancellation";
3
3
4 let _nextOid = 0;
4 let _nextOid = 0;
5 const _oid = typeof Symbol === "function" ?
5 const _oid = typeof Symbol === "function" ?
6 Symbol("__implab__oid__") :
6 Symbol("__implab__oid__") :
7 "__implab__oid__";
7 "__implab__oid__";
8
8
9 export function oid(instance: null | undefined): undefined;
10 export function oid(instance: NonNullable<any>): string;
9 export function oid(instance: any): string | undefined {
11 export function oid(instance: any): string | undefined {
10 if (isNull(instance))
12 if (isNull(instance))
11 return undefined;
13 return undefined;
12
14
13 if (_oid in instance)
15 if (_oid in instance)
14 return instance[_oid];
16 return instance[_oid];
15 else
17 else
16 return (instance[_oid] = "oid_" + (++_nextOid));
18 return (instance[_oid] = "oid_" + (++_nextOid));
17 }
19 }
18
20
19 export function keys<T>(arg: T): (Extract<keyof T, string>)[] {
21 export function keys<T>(arg: T): (Extract<keyof T, string>)[] {
20 return isObject(arg) && arg ? Object.keys(arg) as (Extract<keyof T, string>)[] : [];
22 return isObject(arg) && arg ? Object.keys(arg) as (Extract<keyof T, string>)[] : [];
21 }
23 }
22
24
23 export function isKeyof<T>(k: string, target: T): k is Extract<keyof T, string> {
25 export function isKeyof<T>(k: string, target: T): k is Extract<keyof T, string> {
24 return target && typeof target === "object" && k in target;
26 return target && typeof target === "object" && k in target;
25 }
27 }
26
28
27 export function argumentNotNull(arg: any, name: string) {
29 export function argumentNotNull(arg: any, name: string) {
28 if (arg === null || arg === undefined)
30 if (arg === null || arg === undefined)
29 throw new Error("The argument " + name + " can't be null or undefined");
31 throw new Error("The argument " + name + " can't be null or undefined");
30 }
32 }
31
33
32 export function argumentNotEmptyString(arg: any, name: string) {
34 export function argumentNotEmptyString(arg: any, name: string) {
33 if (typeof (arg) !== "string" || !arg.length)
35 if (typeof (arg) !== "string" || !arg.length)
34 throw new Error("The argument '" + name + "' must be a not empty string");
36 throw new Error("The argument '" + name + "' must be a not empty string");
35 }
37 }
36
38
37 export function argumentNotEmptyArray(arg: any, name: string) {
39 export function argumentNotEmptyArray(arg: any, name: string) {
38 if (!(arg instanceof Array) || !arg.length)
40 if (!(arg instanceof Array) || !arg.length)
39 throw new Error("The argument '" + name + "' must be a not empty array");
41 throw new Error("The argument '" + name + "' must be a not empty array");
40 }
42 }
41
43
42 export function argumentOfType(arg: any, type: Constructor<{}>, name: string) {
44 export function argumentOfType(arg: any, type: Constructor<{}>, name: string) {
43 if (!(arg instanceof type))
45 if (!(arg instanceof type))
44 throw new Error("The argument '" + name + "' type doesn't match");
46 throw new Error("The argument '" + name + "' type doesn't match");
45 }
47 }
46
48
47 export function isObject(val: any): val is object {
49 export function isObject(val: any): val is object {
48 return typeof val === "object";
50 return typeof val === "object";
49 }
51 }
50
52
51 export function isNull(val: any): val is null | undefined {
53 export function isNull(val: any): val is null | undefined {
52 return (val === null || val === undefined);
54 return (val === null || val === undefined);
53 }
55 }
54
56
55 export type primitive = symbol | string | number | boolean | undefined | null;
57 export type primitive = symbol | string | number | boolean | undefined | null;
56
58
57 export function isPrimitive(val: any): val is primitive {
59 export function isPrimitive(val: any): val is primitive {
58 return (val === null || val === undefined || typeof (val) === "string" ||
60 return (val === null || val === undefined || typeof (val) === "string" ||
59 typeof (val) === "number" || typeof (val) === "boolean");
61 typeof (val) === "number" || typeof (val) === "boolean");
60 }
62 }
61
63
62 export function isInteger(val: any): val is number {
64 export function isInteger(val: any): val is number {
63 return parseInt(val, 10) === val;
65 return parseInt(val, 10) === val;
64 }
66 }
65
67
66 export function isNumber(val: any): val is number {
68 export function isNumber(val: any): val is number {
67 return parseFloat(val) === val;
69 return parseFloat(val) === val;
68 }
70 }
69
71
70 export function isString(val: any): val is string {
72 export function isString(val: any): val is string {
71 return typeof (val) === "string" || val instanceof String;
73 return typeof (val) === "string" || val instanceof String;
72 }
74 }
73
75
74 export function isPromise<T = any>(val: any): val is PromiseLike<T> {
76 export function isPromise<T = any>(val: any): val is PromiseLike<T> {
75 return val && typeof val.then === "function";
77 return val && typeof val.then === "function";
76 }
78 }
77
79
78 export function isCancellable(val: any): val is ICancellable {
80 export function isCancellable(val: any): val is ICancellable {
79 return val && typeof val.cancel === "function";
81 return val && typeof val.cancel === "function";
80 }
82 }
81
83
82 export function isNullOrEmptyString(val: any): val is ("" | null | undefined) {
84 export function isNullOrEmptyString(val: any): val is ("" | null | undefined) {
83 return (val === null || val === undefined ||
85 return (val === null || val === undefined ||
84 ((typeof (val) === "string" || val instanceof String) && val.length === 0));
86 ((typeof (val) === "string" || val instanceof String) && val.length === 0));
85 }
87 }
86
88
87 export function isNotEmptyArray<T = any>(arg: any): arg is T[] {
89 export function isNotEmptyArray<T = any>(arg: any): arg is T[] {
88 return (arg instanceof Array && arg.length > 0);
90 return (arg instanceof Array && arg.length > 0);
89 }
91 }
90
92
91 function _isStrictMode(this: any) {
93 function _isStrictMode(this: any) {
92 return !this;
94 return !this;
93 }
95 }
94
96
95 function _getNonStrictGlobal(this: any) {
97 function _getNonStrictGlobal(this: any) {
96 return this;
98 return this;
97 }
99 }
98
100
99 export function getGlobal() {
101 export function getGlobal() {
100 // in es3 we can't use indirect call to eval, since it will
102 // in es3 we can't use indirect call to eval, since it will
101 // be executed in the current call context.
103 // be executed in the current call context.
102 if (!_isStrictMode()) {
104 if (!_isStrictMode()) {
103 return _getNonStrictGlobal();
105 return _getNonStrictGlobal();
104 } else {
106 } else {
105 // tslint:disable-next-line:no-eval
107 // tslint:disable-next-line:no-eval
106 return eval.call(null, "this");
108 return eval.call(null, "this");
107 }
109 }
108 }
110 }
109
111
110 export function get(member: string, context?: object) {
112 export function get(member: string, context?: object) {
111 argumentNotEmptyString(member, "member");
113 argumentNotEmptyString(member, "member");
112 let that = context || getGlobal();
114 let that = context || getGlobal();
113 const parts = member.split(".");
115 const parts = member.split(".");
114 for (const m of parts) {
116 for (const m of parts) {
115 if (!m)
117 if (!m)
116 continue;
118 continue;
117 if (isNull(that = that[m]))
119 if (isNull(that = that[m]))
118 break;
120 break;
119 }
121 }
120 return that;
122 return that;
121 }
123 }
122
124
123 /**
125 /**
124 * Выполняет метод для каждого элемента массива, останавливается, когда
126 * Выполняет метод для каждого элемента массива, останавливается, когда
125 * либо достигнут конец массива, либо функция <c>cb</c> вернула
127 * либо достигнут конец массива, либо функция <c>cb</c> вернула
126 * значение.
128 * значение.
127 *
129 *
128 * @param {Array | Object} obj массив элементов для просмотра
130 * @param {Array | Object} obj массив элементов для просмотра
129 * @param {Function} cb функция, вызываемая для каждого элемента
131 * @param {Function} cb функция, вызываемая для каждого элемента
130 * @param {Object} thisArg значение, которое будет передано в качестве
132 * @param {Object} thisArg значение, которое будет передано в качестве
131 * <c>this</c> в <c>cb</c>.
133 * <c>this</c> в <c>cb</c>.
132 * @returns {void}
134 * @returns {void}
133 */
135 */
134 export function each<T>(obj: T, cb: <X extends keyof T>(v: NonNullable<T[X]>, k: X) => void): void;
136 export function each<T>(obj: T, cb: <X extends keyof T>(v: NonNullable<T[X]>, k: X) => void): void;
135 export function each<T>(array: T[], cb: (v: T, i: number) => void): void;
137 export function each<T>(array: T[], cb: (v: T, i: number) => void): void;
136 export function each(obj: any, cb: any, thisArg?: any): any;
138 export function each(obj: any, cb: any, thisArg?: any): any;
137 export function each(obj: any, cb: any, thisArg?: any) {
139 export function each(obj: any, cb: any, thisArg?: any) {
138 argumentNotNull(cb, "cb");
140 argumentNotNull(cb, "cb");
139 if (obj instanceof Array) {
141 if (obj instanceof Array) {
140 let v: any;
142 let v: any;
141 for (let i = 0; i < obj.length; i++) {
143 for (let i = 0; i < obj.length; i++) {
142 v = obj[i];
144 v = obj[i];
143 if (v !== undefined)
145 if (v !== undefined)
144 cb.call(thisArg, v, i);
146 cb.call(thisArg, v, i);
145 }
147 }
146 } else {
148 } else {
147 Object.keys(obj).forEach(k => obj[k] !== undefined && cb.call(thisArg, obj[k], k));
149 Object.keys(obj).forEach(k => obj[k] !== undefined && cb.call(thisArg, obj[k], k));
148 }
150 }
149 }
151 }
150
152
151 /** Copies property values from a source object to the destination and returns
153 /** Copies property values from a source object to the destination and returns
152 * the destination onject.
154 * the destination onject.
153 *
155 *
154 * @param dest The destination object into which properties from the source
156 * @param dest The destination object into which properties from the source
155 * object will be copied.
157 * object will be copied.
156 * @param source The source of values which will be copied to the destination
158 * @param source The source of values which will be copied to the destination
157 * object.
159 * object.
158 * @param template An optional parameter specifies which properties should be
160 * @param template An optional parameter specifies which properties should be
159 * copied from the source and how to map them to the destination. If the
161 * copied from the source and how to map them to the destination. If the
160 * template is an array it contains the list of property names to copy from the
162 * template is an array it contains the list of property names to copy from the
161 * source to the destination. In case of object the templates contains the map
163 * source to the destination. In case of object the templates contains the map
162 * where keys are property names in the source and the values are property
164 * where keys are property names in the source and the values are property
163 * names in the destination object. If the template isn't specified then the
165 * names in the destination object. If the template isn't specified then the
164 * own properties of the source are entirely copied to the destination.
166 * own properties of the source are entirely copied to the destination.
165 *
167 *
166 */
168 */
167 export function mixin<T extends object, S extends object>(dest: T, source: S, template?: keyof S[]): T & S;
169 export function mixin<T extends object, S extends object>(dest: T, source: S, template?: keyof S[]): T & S;
168 export function mixin<T extends object, S extends object, R extends object = T>(dest: T, source: S, template: { [p in keyof S]?: keyof R; }): T & R;
170 export function mixin<T extends object, S extends object, R extends object = T>(dest: T, source: S, template: { [p in keyof S]?: keyof R; }): T & R;
169 export function mixin<T extends object, S extends object>(dest: T, source: S, template?: any): any {
171 export function mixin<T extends object, S extends object>(dest: T, source: S, template?: any): any {
170 argumentNotNull(dest, "dest");
172 argumentNotNull(dest, "dest");
171 const _res: any = dest as any;
173 const _res: any = dest as any;
172
174
173 if (isPrimitive(source))
175 if (isPrimitive(source))
174 return _res;
176 return _res;
175
177
176 if (template instanceof Array) {
178 if (template instanceof Array) {
177 template.forEach(p => {
179 template.forEach(p => {
178 if (isKeyof(p, source))
180 if (isKeyof(p, source))
179 _res[p] = source[p];
181 _res[p] = source[p];
180 });
182 });
181 } else if (template) {
183 } else if (template) {
182 keys(source).forEach(p => {
184 keys(source).forEach(p => {
183 if (isKeyof(p, template))
185 if (isKeyof(p, template))
184 _res[template[p]] = source[p];
186 _res[template[p]] = source[p];
185 });
187 });
186 } else {
188 } else {
187 keys(source).forEach(p => _res[p] = source[p]);
189 keys(source).forEach(p => _res[p] = source[p]);
188 }
190 }
189
191
190 return _res;
192 return _res;
191 }
193 }
192
194
193 /** Wraps the specified function to emulate an asynchronous execution.
195 /** Wraps the specified function to emulate an asynchronous execution.
194 * @param{Object} thisArg [Optional] Object which will be passed as 'this' to the function.
196 * @param{Object} thisArg [Optional] Object which will be passed as 'this' to the function.
195 * @param{Function|String} fn [Required] Function wich will be wrapped.
197 * @param{Function|String} fn [Required] Function wich will be wrapped.
196 */
198 */
197 export function async<T, F extends (...args: any[]) => T | PromiseLike<T>>(
199 export function async<T, F extends (...args: any[]) => T | PromiseLike<T>>(
198 fn: F,
200 fn: F,
199 thisArg?: ThisParameterType<F>
201 thisArg?: ThisParameterType<F>
200 ): (...args: Parameters<F>) => PromiseLike<T>;
202 ): (...args: Parameters<F>) => PromiseLike<T>;
201 export function async<T, M extends string, O extends { [m in M]?: (...args: any[]) => T | PromiseLike<T> }>(
203 export function async<T, M extends string, O extends { [m in M]?: (...args: any[]) => T | PromiseLike<T> }>(
202 fn: M,
204 fn: M,
203 thisArg: O
205 thisArg: O
204 ): (...args: Parameters<NonNullable<O[M]>>) => PromiseLike<T>;
206 ): (...args: Parameters<NonNullable<O[M]>>) => PromiseLike<T>;
205 export function async(_fn: any, thisArg: any): (...args: any[]) => PromiseLike<any> {
207 export function async(_fn: any, thisArg: any): (...args: any[]) => PromiseLike<any> {
206 let fn = _fn;
208 let fn = _fn;
207
209
208 if (arguments.length === 2 && !(fn instanceof Function))
210 if (arguments.length === 2 && !(fn instanceof Function))
209 fn = thisArg[fn];
211 fn = thisArg[fn];
210
212
211 if (fn == null)
213 if (fn == null)
212 throw new Error("The function must be specified");
214 throw new Error("The function must be specified");
213
215
214 function wrapresult(x: any, e?: any): PromiseLike<any> {
216 function wrapresult(x: any, e?: any): PromiseLike<any> {
215 if (e) {
217 if (e) {
216 return {
218 return {
217 then(cb, eb) {
219 then(cb, eb) {
218 try {
220 try {
219 return eb ? wrapresult(eb(e)) : this;
221 return eb ? wrapresult(eb(e)) : this;
220 } catch (e2) {
222 } catch (e2) {
221 return wrapresult(null, e2);
223 return wrapresult(null, e2);
222 }
224 }
223 }
225 }
224 };
226 };
225 } else {
227 } else {
226 if (x && x.then)
228 if (x && x.then)
227 return x;
229 return x;
228 return {
230 return {
229 then(cb) {
231 then(cb) {
230 try {
232 try {
231 return cb ? wrapresult(cb(x)) : this;
233 return cb ? wrapresult(cb(x)) : this;
232 } catch (e2) {
234 } catch (e2) {
233 return wrapresult(e2);
235 return wrapresult(e2);
234 }
236 }
235 }
237 }
236 };
238 };
237 }
239 }
238 }
240 }
239
241
240 return (...args) => {
242 return (...args) => {
241 try {
243 try {
242 return wrapresult(fn.apply(thisArg, args));
244 return wrapresult(fn.apply(thisArg, args));
243 } catch (e) {
245 } catch (e) {
244 return wrapresult(null, e);
246 return wrapresult(null, e);
245 }
247 }
246 };
248 };
247 }
249 }
248
250
249 export function delegate<T extends object, F extends (this: T, ...args: any[]) => any>(
251 export function delegate<T extends object, F extends (this: T, ...args: any[]) => any>(
250 target: T,
252 target: T,
251 method: F
253 method: F
252 ): OmitThisParameter<F>;
254 ): OmitThisParameter<F>;
253 export function delegate<M extends string, T extends { [m in M]?: (...args: any[]) => any; }>(
255 export function delegate<M extends string, T extends { [m in M]?: (...args: any[]) => any; }>(
254 target: T,
256 target: T,
255 method: M
257 method: M
256 ): OmitThisParameter<T[M]>;
258 ): OmitThisParameter<T[M]>;
257 export function delegate(target: any, _method: any): (...args: any[]) => any {
259 export function delegate(target: any, _method: any): (...args: any[]) => any {
258 let method: any;
260 let method: any;
259 if (!(_method instanceof Function)) {
261 if (!(_method instanceof Function)) {
260 argumentNotNull(target, "target");
262 argumentNotNull(target, "target");
261 method = target[_method];
263 method = target[_method];
262 if (!(method instanceof Function))
264 if (!(method instanceof Function))
263 throw new Error("'method' argument must be a Function or a method name");
265 throw new Error("'method' argument must be a Function or a method name");
264 } else {
266 } else {
265 method = _method;
267 method = _method;
266 }
268 }
267
269
268 return (...args) => {
270 return (...args) => {
269 return method.apply(target, args);
271 return method.apply(target, args);
270 };
272 };
271 }
273 }
272
274
273 export function delay(timeMs: number, ct = Cancellation.none) {
275 export function delay(timeMs: number, ct = Cancellation.none) {
274 ct.throwIfRequested();
276 ct.throwIfRequested();
275 return new Promise((resolve, reject) => {
277 return new Promise((resolve, reject) => {
276 const h = ct.register(e => {
278 const h = ct.register(e => {
277 clearTimeout(id);
279 clearTimeout(id);
278 reject(e);
280 reject(e);
279 // we don't nedd to unregister h, since ct is already disposed
281 // we don't nedd to unregister h, since ct is already disposed
280 });
282 });
281 const id = setTimeout(() => {
283 const id = setTimeout(() => {
282 h.destroy();
284 h.destroy();
283 resolve();
285 resolve();
284 }, timeMs);
286 }, timeMs);
285
287
286 });
288 });
287 }
289 }
288
290
289 /** Returns resolved promise, awaiting this method will cause the asynchronous
291 /** Returns resolved promise, awaiting this method will cause the asynchronous
290 * completion of the rest of the code.
292 * completion of the rest of the code.
291 */
293 */
292 export function fork() {
294 export function fork() {
293 return Promise.resolve();
295 return Promise.resolve();
294 }
296 }
295
297
296 /** Always throws Error, can be used as a stub for the methods which should be
298 /** Always throws Error, can be used as a stub for the methods which should be
297 * assigned later and are required to be not null.
299 * assigned later and are required to be not null.
298 */
300 */
299 export function notImplemented(): never {
301 export function notImplemented(): never {
300 throw new Error("Not implemeted");
302 throw new Error("Not implemeted");
301 }
303 }
302 /**
304 /**
303 * Iterates over the specified array of items and calls the callback `cb`, if
305 * Iterates over the specified array of items and calls the callback `cb`, if
304 * the result of the callback is a promise the next item from the array will be
306 * the result of the callback is a promise the next item from the array will be
305 * proceeded after the promise is resolved.
307 * proceeded after the promise is resolved.
306 *
308 *
307 */
309 */
308 export function pmap<T, T2>(
310 export function pmap<T, T2>(
309 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
311 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
310 cb: (item: T, i: number) => T2 | PromiseLike<T2>
312 cb: (item: T, i: number) => T2 | PromiseLike<T2>
311 ): T2[] | PromiseLike<T2[]> {
313 ): T2[] | PromiseLike<T2[]> {
312 argumentNotNull(cb, "cb");
314 argumentNotNull(cb, "cb");
313
315
314 if (isPromise(items)) {
316 if (isPromise(items)) {
315 return items.then(data => pmap(data, cb));
317 return items.then(data => pmap(data, cb));
316 } else {
318 } else {
317
319
318 if (isNull(items) || !items.length)
320 if (isNull(items) || !items.length)
319 return [];
321 return [];
320
322
321 let i = 0;
323 let i = 0;
322 const result = new Array<T2>();
324 const result = new Array<T2>();
323
325
324 const next = (): any => {
326 const next = (): any => {
325 while (i < items.length) {
327 while (i < items.length) {
326 const r = cb(items[i], i);
328 const r = cb(items[i], i);
327 const ri = i;
329 const ri = i;
328 i++;
330 i++;
329 if (isPromise(r)) {
331 if (isPromise(r)) {
330 return r.then(x => {
332 return r.then(x => {
331 result[ri] = x;
333 result[ri] = x;
332 return next();
334 return next();
333 });
335 });
334 } else {
336 } else {
335 result[ri] = r;
337 result[ri] = r;
336 }
338 }
337 }
339 }
338 return result;
340 return result;
339 };
341 };
340
342
341 return next();
343 return next();
342 }
344 }
343 }
345 }
344
346
345 export function pfor<T>(
347 export function pfor<T>(
346 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
348 items: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
347 cb: (item: T, i: number) => any
349 cb: (item: T, i: number) => any
348 ): void | PromiseLike<void> {
350 ): void | PromiseLike<void> {
349 argumentNotNull(cb, "cb");
351 argumentNotNull(cb, "cb");
350
352
351 if (isPromise(items)) {
353 if (isPromise(items)) {
352 return items.then(data => pfor(data, cb));
354 return items.then(data => pfor(data, cb));
353 } else {
355 } else {
354 if (isNull(items) || !items.length)
356 if (isNull(items) || !items.length)
355 return;
357 return;
356
358
357 let i = 0;
359 let i = 0;
358
360
359 const next = (): any => {
361 const next = (): any => {
360 while (i < items.length) {
362 while (i < items.length) {
361 const r = cb(items[i], i);
363 const r = cb(items[i], i);
362 i++;
364 i++;
363 if (isPromise(r))
365 if (isPromise(r))
364 return r.then(next);
366 return r.then(next);
365 }
367 }
366 };
368 };
367
369
368 return next();
370 return next();
369 }
371 }
370 }
372 }
371
373
372 export function first<T>(sequence: ArrayLike<T>): T;
374 export function first<T>(sequence: ArrayLike<T>): T;
373 export function first<T>(sequence: PromiseLike<ArrayLike<T>>): PromiseLike<T>;
375 export function first<T>(sequence: PromiseLike<ArrayLike<T>>): PromiseLike<T>;
374 export function first<T>(
376 export function first<T>(
375 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
377 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
376 cb?: (x: T) => void,
378 cb?: (x: T) => void,
377 err?: (x: Error) => void
379 err?: (x: Error) => void
378 ): void;
380 ): void;
379 /**
381 /**
380 * Выбирает первый элемент из последовательности, или обещания, если в
382 * Выбирает первый элемент из последовательности, или обещания, если в
381 * качестве параметра используется обещание, оно должно вернуть массив.
383 * качестве параметра используется обещание, оно должно вернуть массив.
382 *
384 *
383 * @param {Function} cb обработчик результата, ему будет передан первый
385 * @param {Function} cb обработчик результата, ему будет передан первый
384 * элемент последовательности в случае успеха
386 * элемент последовательности в случае успеха
385 * @param {Function} err обработчик исключения, если массив пустой, либо
387 * @param {Function} err обработчик исключения, если массив пустой, либо
386 * не массив
388 * не массив
387 *
389 *
388 * @remarks Если не указаны ни cb ни err, тогда функция вернет либо
390 * @remarks Если не указаны ни cb ни err, тогда функция вернет либо
389 * обещание, либо первый элемент.
391 * обещание, либо первый элемент.
390 * @async
392 * @async
391 */
393 */
392 export function first<T>(
394 export function first<T>(
393 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
395 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
394 cb?: (x: T) => void,
396 cb?: (x: T) => void,
395 err?: (x: Error) => void
397 err?: (x: Error) => void
396 ) {
398 ) {
397 if (isPromise(sequence)) {
399 if (isPromise(sequence)) {
398 return sequence.then(res => first(res, cb as any /* force to pass undefined cb */, err));
400 return sequence.then(res => first(res, cb as any /* force to pass undefined cb */, err));
399 } else if (sequence && "length" in sequence) {
401 } else if (sequence && "length" in sequence) {
400 if (sequence.length === 0) {
402 if (sequence.length === 0) {
401 if (err)
403 if (err)
402 return err(new Error("The sequence is empty"));
404 return err(new Error("The sequence is empty"));
403 else
405 else
404 throw new Error("The sequence is empty");
406 throw new Error("The sequence is empty");
405 } else if (cb) {
407 } else if (cb) {
406 return cb(sequence[0]);
408 return cb(sequence[0]);
407 } else {
409 } else {
408 return sequence[0];
410 return sequence[0];
409 }
411 }
410 } else {
412 } else {
411 if (err)
413 if (err)
412 return err(new Error("The sequence is required"));
414 return err(new Error("The sequence is required"));
413 else
415 else
414 throw new Error("The sequence is required");
416 throw new Error("The sequence is required");
415 }
417 }
416 }
418 }
417
419
418 export function firstWhere<T>(
420 export function firstWhere<T>(
419 sequence: ArrayLike<T>,
421 sequence: ArrayLike<T>,
420 predicate: (x: T) => boolean
422 predicate: (x: T) => boolean
421 ): T;
423 ): T;
422 export function firstWhere<T>(
424 export function firstWhere<T>(
423 sequence: PromiseLike<ArrayLike<T>>,
425 sequence: PromiseLike<ArrayLike<T>>,
424 predicate: (x: T) => boolean
426 predicate: (x: T) => boolean
425 ): PromiseLike<T>;
427 ): PromiseLike<T>;
426 export function firstWhere<T>(
428 export function firstWhere<T>(
427 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
429 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
428 predicate: (x: T) => boolean,
430 predicate: (x: T) => boolean,
429 cb: (x: T) => void,
431 cb: (x: T) => void,
430 err?: (x: Error) => void
432 err?: (x: Error) => void
431 ): void;
433 ): void;
432
434
433 export function firstWhere<T>(
435 export function firstWhere<T>(
434 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
436 sequence: ArrayLike<T> | PromiseLike<ArrayLike<T>>,
435 predicate?: (x: T) => boolean,
437 predicate?: (x: T) => boolean,
436 cb?: (x: T) => any,
438 cb?: (x: T) => any,
437 err?: (x: Error) => any
439 err?: (x: Error) => any
438 ) {
440 ) {
439 if (isPromise(sequence)) {
441 if (isPromise(sequence)) {
440 return sequence.then(res => firstWhere(
442 return sequence.then(res => firstWhere(
441 res,
443 res,
442 predicate as any /* force to pass undefined predicate */,
444 predicate as any /* force to pass undefined predicate */,
443 cb as any /* force to pass undefined cb */,
445 cb as any /* force to pass undefined cb */,
444 err)
446 err)
445 );
447 );
446 } else if (sequence && "length" in sequence) {
448 } else if (sequence && "length" in sequence) {
447 if (sequence.length === 0) {
449 if (sequence.length === 0) {
448 if (err)
450 if (err)
449 err(new Error("The sequence is empty"));
451 err(new Error("The sequence is empty"));
450 else
452 else
451 throw new Error("The sequence is empty");
453 throw new Error("The sequence is empty");
452 } else {
454 } else {
453 if (!predicate) {
455 if (!predicate) {
454 return cb ? cb(sequence[0]) && void (0) : sequence[0];
456 return cb ? cb(sequence[0]) && void (0) : sequence[0];
455 } else {
457 } else {
456 for (let i = 0; i < sequence.length; i++) {
458 for (let i = 0; i < sequence.length; i++) {
457 const v = sequence[i];
459 const v = sequence[i];
458 if (predicate(v))
460 if (predicate(v))
459 return cb ? cb(v) : v;
461 return cb ? cb(v) : v;
460 }
462 }
461 if (err)
463 if (err)
462 err(new Error("The sequence doesn't contain matching items"));
464 err(new Error("The sequence doesn't contain matching items"));
463 else
465 else
464 throw new Error("The sequence doesn't contain matching items");
466 throw new Error("The sequence doesn't contain matching items");
465 }
467 }
466 }
468 }
467 } else {
469 } else {
468 if (err)
470 if (err)
469 err(new Error("The sequence is required"));
471 err(new Error("The sequence is required"));
470 else
472 else
471 throw new Error("The sequence is required");
473 throw new Error("The sequence is required");
472 }
474 }
473 }
475 }
474
476
475 export function isDestroyable(d: any): d is IDestroyable {
477 export function isDestroyable(d: any): d is IDestroyable {
476 if (d && "destroy" in d && typeof (destroy) === "function")
478 if (d && "destroy" in d && typeof (destroy) === "function")
477 return true;
479 return true;
478 return false;
480 return false;
479 }
481 }
480
482
481 export function destroy(d: any) {
483 export function destroy(d: any) {
482 if (d && "destroy" in d)
484 if (d && "destroy" in d)
483 d.destroy();
485 d.destroy();
484 }
486 }
485
487
486 /**
488 /**
487 * Used to mark that the async operation isn't awaited intentionally.
489 * Used to mark that the async operation isn't awaited intentionally.
488 * @param p The promise which represents the async operation.
490 * @param p The promise which represents the async operation.
489 */
491 */
490 export function nowait(p: Promise<any>) {
492 export function nowait(p: Promise<any>) {
491 }
493 }
492
494
493 /** represents already destroyed object.
495 /** represents already destroyed object.
494 */
496 */
495 export const destroyed = {
497 export const destroyed = {
496 /** Calling to this method doesn't affect anything, noop.
498 /** Calling to this method doesn't affect anything, noop.
497 */
499 */
498 destroy() {
500 destroy() {
499 }
501 }
500 };
502 };
@@ -1,23 +1,26
1 import { Services } from "./services";
1 import { Services } from "./services";
2 import { configure } from "../di/traits";
2 import { fluent } from "../di/traits";
3 import { LifetimeManager } from "../di/LifetimeManager";
3
4 export default fluent<Services>().register({
5 host: it => it.value("example.com"),
4
6
5 export const config = configure<Services>()
7 bar2: it => Promise.all([import("./Foo"), import("./Bar")])
6 .register("host", s => s.value("example.com"))
8 .then(([{ Foo }, { Bar }]) => it
7 .register("bar2", bar2 => Promise.all([import("./Foo"), import("./Bar")])
9 .lifetime("container")
8 .then(([{ Foo }, { Bar }]) => {
10 .override({
9 const lifetime = LifetimeManager.hierarchyLifetime();
11 host: it2 => it2.value("simple.org"),
10
12 foo: it2 => it2.value(new Foo())
11 bar2.factory((resolve, activate) => {
13 })
14 .factory(resolve => {
12 const bar = new Bar({
15 const bar = new Bar({
13 foo: activate(lifetime, () => new Foo()),
16 foo: new Foo(),
14 nested: {
17 nested: {
15 lazy: resolve("foo", { lazy: true })
18 lazy: resolve("foo", { lazy: true })
16 },
19 },
17 host: resolve("host")
20 host: resolve("host")
18 }, "some text");
21 }, "some text");
19 bar.setName(resolve("host"));
22 bar.setName(resolve("host"));
20 return bar;
23 return bar;
24 })
25 )
21 });
26 });
22 })
23 );
@@ -1,107 +1,106
1 import { test } from "./TestTraits";
1 import { test } from "./TestTraits";
2 import { Container } from "../di/Container";
2 import { Container } from "../di/Container";
3 import { ReferenceDescriptor } from "../di/ReferenceDescriptor";
3 import { ReferenceDescriptor } from "../di/ReferenceDescriptor";
4 import { AggregateDescriptor } from "../di/AggregateDescriptor";
4 import { AggregateDescriptor } from "../di/AggregateDescriptor";
5 import { ValueDescriptor } from "../di/ValueDescriptor";
5 import { ValueDescriptor } from "../di/ValueDescriptor";
6 import { Foo } from "../mock/Foo";
6 import { Foo } from "../mock/Foo";
7 import { Bar } from "../mock/Bar";
7 import { Bar } from "../mock/Bar";
8 import { isNull } from "../safe";
8 import { isNull } from "../safe";
9 import { Box } from "ts/mock/Box";
10
9
11 test("Container register/resolve tests", async t => {
10 test("Container register/resolve tests", async t => {
12 const container = new Container<{
11 const container = new Container<{
13 "bla-bla": string;
12 "bla-bla": string;
14 "connection": string;
13 "connection": string;
15 "dbParams": {
14 "dbParams": {
16 timeout: number;
15 timeout: number;
17 connection: string;
16 connection: string;
18 }
17 }
19 }>();
18 }>();
20
19
21 const connection1 = "db://localhost";
20 const connection1 = "db://localhost";
22
21
23 t.throws(
22 t.throws(
24 () => container.register("bla-bla", "bla-bla" as any),
23 () => container.register("bla-bla", "bla-bla" as any),
25 "Do not allow to register anything other than descriptors"
24 "Do not allow to register anything other than descriptors"
26 );
25 );
27
26
28 t.doesNotThrow(
27 t.doesNotThrow(
29 () => container.register("connection", new ValueDescriptor(connection1)),
28 () => container.register("connection", new ValueDescriptor(connection1)),
30 "register ValueDescriptor"
29 "register ValueDescriptor"
31 );
30 );
32
31
33 t.equals(container.resolve("connection"), connection1, "resolve string value");
32 t.equals(container.resolve("connection"), connection1, "resolve string value");
34
33
35 t.doesNotThrow(
34 t.doesNotThrow(
36 () => container.register(
35 () => container.register(
37 "dbParams",
36 "dbParams",
38 new AggregateDescriptor({
37 new AggregateDescriptor({
39 timeout: 10,
38 timeout: 10,
40 connection: new ReferenceDescriptor({ name: "connection" })
39 connection: new ReferenceDescriptor({ name: "connection" })
41 })
40 })
42 ),
41 ),
43 "register AggregateDescriptor"
42 "register AggregateDescriptor"
44 );
43 );
45
44
46 const dbParams = container.resolve("dbParams");
45 const dbParams = container.resolve("dbParams");
47 t.equals(dbParams.connection, connection1, "should get string value 'dbParams.connection'");
46 t.equals(dbParams.connection, connection1, "should get string value 'dbParams.connection'");
48 });
47 });
49
48
50 test("Container configure/resolve tests", async t => {
49 test("Container configure/resolve tests", async t => {
51
50
52 const container = new Container<{
51 const container = new Container<{
53 foo: Foo;
52 foo: Foo;
54 box: Bar;
53 box: Bar;
55 bar: Bar;
54 bar: Bar;
56 db: any;
55 db: any;
57 }>();
56 }>();
58
57
59 await container.configure({
58 await container.configure({
60 foo: {
59 foo: {
61 $type: Foo
60 $type: Foo
62 },
61 },
63
62
64 box: {
63 box: {
65 $type: Bar,
64 $type: Bar,
66 params: {
65 params: {
67 $dependency: "foo"
66 $dependency: "foo"
68 }
67 }
69 },
68 },
70
69
71 bar: {
70 bar: {
72 $type: Bar,
71 $type: Bar,
73 params: [{
72 params: [{
74 db: {
73 db: {
75 provider: {
74 provider: {
76 $dependency: "db"
75 $dependency: "db"
77 }
76 }
78 }
77 }
79 }]
78 }]
80 }
79 }
81 });
80 });
82 t.pass("should configure from js object");
81 t.pass("should configure from js object");
83
82
84 const f1 = container.resolve("foo");
83 const f1 = container.resolve("foo");
85
84
86 t.assert(!isNull(f1), "foo should be not null");
85 t.assert(!isNull(f1), "foo should be not null");
87
86
88 t.throws(() => container.resolve("bar"), "should not resolve dependency 'db'");
87 t.throws(() => container.resolve("bar"), "should not resolve dependency 'db'");
89
88
90 });
89 });
91
90
92 test("Load configuration from module", async t => {
91 test("Load configuration from module", async t => {
93 const container = new Container();
92 const container = new Container();
94
93
95 await container.configure("../mock/config1", { contextRequire: require });
94 await container.configure("../mock/config1", { contextRequire: require });
96 t.pass("The configuration should load");
95 t.pass("The configuration should load");
97
96
98 const f1 = container.resolve("foo");
97 const f1 = container.resolve("foo");
99
98
100 t.assert(!isNull(f1), "foo should be not null");
99 t.assert(!isNull(f1), "foo should be not null");
101
100
102 const b1 = container.resolve("bar") as Bar;
101 const b1 = container.resolve("bar") as Bar;
103
102
104 t.assert(!isNull(b1), "bar should not be null");
103 t.assert(!isNull(b1), "bar should not be null");
105 t.assert(!isNull(b1._v), "bar.foo should not be null");
104 t.assert(!isNull(b1._v), "bar.foo should not be null");
106
105
107 });
106 });
@@ -1,14 +1,12
1 {
1 {
2 "extends": "../tsconfig",
2 "extends": "../tsconfig",
3 "compilerOptions": {
3 "compilerOptions": {
4 //"rootDir": "ts",
5 "baseUrl": ".",
6 "rootDirs": [
4 "rootDirs": [
7 "ts",
5 "ts",
8 "../main/ts"
6 "../main/ts"
9 ]
7 ]
10 },
8 },
11 "include" : [
9 "include" : [
12 "ts/**/*.ts"
10 "ts/**/*.ts"
13 ]
11 ]
14 } No newline at end of file
12 }
@@ -1,10 +1,11
1 define([
1 define([
2 "./ActivatableTests",
2 "./ActivatableTests",
3 "./trace-test",
3 "./trace-test",
4 "./TraceSourceTests",
4 "./TraceSourceTests",
5 "./CancellationTests",
5 "./CancellationTests",
6 "./ObservableTests",
6 "./ObservableTests",
7 "./ContainerTests",
7 "./ContainerTests",
8 "./SafeTests",
8 "./SafeTests",
9 "./TextTests"
9 "./TextTests",
10 "./FluentContainerTests"
10 ]); No newline at end of file
11 ]);
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now