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