##// END OF EJS Templates
Refactoring, working on services lifetime
cin -
r131:674555892e14 ioc ts support
parent child
Show More
@@ -1,138 +1,123
1 import { TraceSource } from "../log/TraceSource";
1 import { TraceSource } from "../log/TraceSource";
2 import { argumentNotNull, argumentNotEmptyString } from "../safe";
2 import { argumentNotNull, argumentNotEmptyString } from "../safe";
3 import { Descriptor, ContainerProvided, ContainerServiceMap, ContainerKeys, ContainerResolve } from "./interfaces";
3 import { Descriptor, ContainerServiceMap, ContainerKeys, ContainerResolve } 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<S extends object> {
9 export interface ActivationContextInfo {
10 name: string;
10 name: string;
11
11
12 service: string;
12 service: string;
13
13
14 scope: ContainerServiceMap<S>;
15 }
14 }
16
15
17 export class ActivationContext<S extends object> {
16 export class ActivationContext<S extends object> {
18 _cache: MapOf<any>;
17 _cache: MapOf<any>;
19
18
20 _services: ContainerServiceMap<S>;
19 _services: ContainerServiceMap<S>;
21
20
22 _stack: ActivationContextInfo<S>[];
23
24 _visited: MapOf<any>;
21 _visited: MapOf<any>;
25
22
26 _name: string;
23 _name: string;
27
24
28 _localized: boolean = false;
25 _service: Descriptor<S, any>;
29
26
30 container: Container<S>;
27 _container: Container<S>;
28
29 _parent: ActivationContext<S> | undefined;
31
30
32 constructor(container: Container<S>, services: ContainerServiceMap<S>, name?: string, cache?: object, visited?: MapOf<any>) {
31 constructor(container: Container<S>, services: ContainerServiceMap<S>, name: string, service: Descriptor<S, any>) {
33 argumentNotNull(container, "container");
32 this._name = name;
34 argumentNotNull(services, "services");
33 this._service = service;
35
34 this._visited = {};
36 this._name = name || "<unnamed>";
35 this._cache = {};
37 this._visited = visited || {};
38 this._stack = [];
39 this._cache = cache || {};
40 this._services = services;
36 this._services = services;
41 this.container = container;
37 this._container = container;
42 }
38 }
43
39
44 getName() {
40 getName() {
45 return this._name;
41 return this._name;
46 }
42 }
47
43
44 getContainer() {
45 return this._container;
46 }
47
48 resolve<K extends ContainerKeys<S>>(name: K, def?: ContainerResolve<S, K>) {
48 resolve<K extends ContainerKeys<S>>(name: K, def?: ContainerResolve<S, K>) {
49 const d = this._services[name];
49 const d = this._services[name];
50
50
51 if (d !== undefined) {
51 if (d !== undefined) {
52 return this.activate(d, name.toString());
52 return this.activate(d, name.toString());
53 } else {
53 } else {
54 if (def !== undefined && def !== null)
54 if (def !== undefined && def !== null)
55 return def;
55 return def;
56 else
56 else
57 throw new Error(`Service ${name} not found`);
57 throw new Error(`Service ${name} not found`);
58 }
58 }
59 }
59 }
60
60
61 /**
61 /**
62 * registers services local to the the activation context
62 * registers services local to the the activation context
63 *
63 *
64 * @name{string} the name of the service
64 * @name{string} the name of the service
65 * @service{string} the service descriptor to register
65 * @service{string} the service descriptor to register
66 */
66 */
67 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>) {
67 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>) {
68 argumentNotEmptyString(name, "name");
68 argumentNotEmptyString(name, "name");
69
69
70 this._services[name] = service as any;
70 this._services[name] = service as any;
71 }
71 }
72
72
73 clone() {
74 return new ActivationContext<S>(
75 this.container,
76 this._services,
77 this._name,
78 this._cache,
79 this._visited
80 );
81 }
82
83 has(id: string) {
73 has(id: string) {
84 return id in this._cache;
74 return id in this._cache;
85 }
75 }
86
76
87 get<T>(id: string) {
77 get<T>(id: string) {
88 return this._cache[id];
78 return this._cache[id];
89 }
79 }
90
80
91 store(id: string, value: any) {
81 store(id: string, value: any) {
92 return (this._cache[id] = value);
82 return (this._cache[id] = value);
93 }
83 }
94
84
95 activate<T>(d: Descriptor<S, T>, name: string) {
85 activate<T>(d: Descriptor<S, T>, name: string) {
96 if (trace.isLogEnabled())
86 if (trace.isLogEnabled())
97 trace.log(`enter ${name} ${d}`);
87 trace.log(`enter ${name} ${d}`);
98
88
99 this.enter(name, d.toString());
89 const ctx = this.enter(d, name);
100 const v = d.activate(this);
90 const v = d.activate(ctx);
101 this.leave();
102
91
103 if (trace.isLogEnabled())
92 if (trace.isLogEnabled())
104 trace.log(`leave ${name}`);
93 trace.log(`leave ${name}`);
105
94
106 return v;
95 return v;
107 }
96 }
108
97
109 visit(id: string) {
98 visit(id: string) {
110 const count = this._visited[id] || 0;
99 const count = this._visited[id] || 0;
111 this._visited[id] = count + 1;
100 this._visited[id] = count + 1;
112 return count;
101 return count;
113 }
102 }
114
103
115 getStack() {
104 getStack(): ActivationContextInfo[] {
116 return this._stack.slice().reverse();
105 const stack = [{
106 name: this._name,
107 service: this._service.toString()
108 }];
109
110 return this._parent ?
111 stack.concat(this._parent.getStack()) :
112 stack;
117 }
113 }
118
114
119 private enter(name: string, service: string) {
115 private enter(service: Descriptor<S, any>, name: string): this {
120 this._stack.push({
116 const clone = Object.create(this);
121 name,
117 clone._name = name;
122 service,
118 clone._services = Object.create(this._services);
123 scope: this._services
119 clone._parent = this;
124 });
120 clone._service = service;
125 this._name = name;
121 return clone;
126 this._services = Object.create(this._services);
127 }
128
129 private leave() {
130 const ctx = this._stack.pop();
131 if (ctx) {
132 this._services = ctx.scope;
133 this._name = ctx.name;
134 } else {
135 trace.error("Trying to leave the last activation scope");
136 }
137 }
122 }
138 }
123 }
@@ -1,143 +1,135
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, Resolver, ContainerServiceMap, ContainerKeys, ContainerResolve } from "./interfaces";
4 import { ServiceMap, Descriptor, PartialServiceMap, ContainerProvided, Resolver, ContainerServiceMap, ContainerKeys, ContainerResolve, ILifetimeManager } 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";
11 import { each } from "../safe";
10
12
11 const trace = TraceSource.get("@implab/core/di/ActivationContext");
13 const trace = TraceSource.get("@implab/core/di/ActivationContext");
12
14
13 export class Container<S extends object = any> implements Resolver<S>, IDestroyable {
15 export class Container<S extends object = any> implements Resolver<S>, IDestroyable {
14 readonly _services: ContainerServiceMap<S>;
16 readonly _services: ContainerServiceMap<S>;
15
17
16 readonly _cache: MapOf<any>;
18 readonly _cache: MapOf<any>;
17
19
20 readonly _lifetimeManager: ILifetimeManager;
21
18 readonly _cleanup: (() => void)[];
22 readonly _cleanup: (() => void)[];
19
23
20 readonly _root: Container<S>;
24 readonly _root: Container<S>;
21
25
22 readonly _parent?: Container<S>;
26 readonly _parent?: Container<S>;
23
27
24 _disposed: boolean;
28 _disposed: boolean;
25
29
26 constructor(parent?: Container<S>) {
30 constructor(parent?: Container<S>) {
27 this._parent = parent;
31 this._parent = parent;
28 this._services = parent ? Object.create(parent._services) : {};
32 this._services = parent ? Object.create(parent._services) : {};
29 this._cache = {};
33 this._cache = {};
30 this._cleanup = [];
34 this._cleanup = [];
31 this._root = parent ? parent.getRootContainer() : this;
35 this._root = parent ? parent.getRootContainer() : this;
32 this._services.container = new ValueDescriptor(this) as any;
36 this._services.container = new ValueDescriptor(this) as any;
33 this._disposed = false;
37 this._disposed = false;
38 this._lifetimeManager = new LifetimeManager();
34 }
39 }
35
40
36 getRootContainer() {
41 getRootContainer() {
37 return this._root;
42 return this._root;
38 }
43 }
39
44
40 getParent() {
45 getParent() {
41 return this._parent;
46 return this._parent;
42 }
47 }
43
48
49 getLifetimeManager() {
50 return this._lifetimeManager;
51 }
52
44 resolve<K extends ContainerKeys<S>>(name: K, def?: ContainerResolve<S, K>): ContainerResolve<S, K> {
53 resolve<K extends ContainerKeys<S>>(name: K, def?: ContainerResolve<S, K>): ContainerResolve<S, K> {
45 trace.debug("resolve {0}", name);
54 trace.debug("resolve {0}", name);
46 const d = this._services[name];
55 const d = this._services[name];
47 if (d === undefined) {
56 if (d === undefined) {
48 if (def !== undefined)
57 if (def !== undefined)
49 return def;
58 return def;
50 else
59 else
51 throw new Error("Service '" + name + "' isn't found");
60 throw new Error("Service '" + name + "' isn't found");
52 } else {
61 } else {
53
62
54 const context = new ActivationContext<S>(this, this._services);
63 const context = new ActivationContext<S>(this, this._services, String(name), d);
55 try {
64 try {
56 return context.activate(d, name.toString());
65 return d.activate(context);
57 } catch (error) {
66 } catch (error) {
58 throw new ActivationError(name.toString(), context.getStack(), error);
67 throw new ActivationError(name.toString(), context.getStack(), error);
59 }
68 }
60 }
69 }
61 }
70 }
62
71
63 /**
72 /**
64 * @deprecated use resolve() method
73 * @deprecated use resolve() method
65 */
74 */
66 getService<K extends ContainerKeys<S>>(name: K, def?: ContainerResolve<S, K>) {
75 getService<K extends ContainerKeys<S>>(name: K, def?: ContainerResolve<S, K>) {
67 return this.resolve(name, def);
76 return this.resolve(name, def);
68 }
77 }
69
78
70 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>): this;
79 register<K extends keyof S>(name: K, service: Descriptor<S, S[K]>): this;
71 register(services: PartialServiceMap<S>): this;
80 register(services: PartialServiceMap<S>): this;
72 register<K extends keyof S>(nameOrCollection: K | ServiceMap<S>, service?: Descriptor<S, S[K]>) {
81 register<K extends keyof S>(nameOrCollection: K | ServiceMap<S>, service?: Descriptor<S, S[K]>) {
73 if (arguments.length === 1) {
82 if (arguments.length === 1) {
74 const data = nameOrCollection as ServiceMap<S>;
83 const data = nameOrCollection as ServiceMap<S>;
75
84
76 for (const name in data) {
85 each(data, (v, k) => this.register(k, v));
77 if (Object.prototype.hasOwnProperty.call(data, name)) {
78 this.register(name, data[name] as Descriptor<S, S[keyof S]>);
79 }
80 }
81 } else {
86 } else {
82 if (!isDescriptor(service))
87 if (!isDescriptor(service))
83 throw new Error("The service parameter must be a descriptor");
88 throw new Error("The service parameter must be a descriptor");
84
89
85 this._services[nameOrCollection as K] = service as any;
90 this._services[nameOrCollection as K] = service as any;
86 }
91 }
87 return this;
92 return this;
88 }
93 }
89
94
90 onDispose(callback: () => void) {
95 onDispose(callback: () => void) {
91 if (!(callback instanceof Function))
96 if (!(callback instanceof Function))
92 throw new Error("The callback must be a function");
97 throw new Error("The callback must be a function");
93 this._cleanup.push(callback);
98 this._cleanup.push(callback);
94 }
99 }
95
100
96 destroy() {
101 destroy() {
97 return this.dispose();
102 return this.dispose();
98 }
103 }
99 dispose() {
104 dispose() {
100 if (this._disposed)
105 if (this._disposed)
101 return;
106 return;
102 this._disposed = true;
107 this._disposed = true;
103 for (const f of this._cleanup)
108 for (const f of this._cleanup)
104 f();
109 f();
105 }
110 }
106
111
107 /**
112 /**
108 * @param{String|Object} config
113 * @param{String|Object} config
109 * The configuration of the contaier. Can be either a string or an object,
114 * The configuration of the contaier. Can be either a string or an object,
110 * if the configuration is an object it's treated as a collection of
115 * if the configuration is an object it's treated as a collection of
111 * services which will be registed in the contaier.
116 * services which will be registed in the contaier.
112 *
117 *
113 * @param{Function} opts.contextRequire
118 * @param{Function} opts.contextRequire
114 * The function which will be used to load a configuration or types for services.
119 * The function which will be used to load a configuration or types for services.
115 *
120 *
116 */
121 */
117 async configure(config: string | RegistrationMap<S>, opts?: any, ct = Cancellation.none) {
122 async configure(config: string | RegistrationMap<S>, opts?: any, ct = Cancellation.none) {
118 const c = new Configuration<S>(this);
123 const c = new Configuration<S>(this);
119
124
120 if (typeof (config) === "string") {
125 if (typeof (config) === "string") {
121 return c.loadConfiguration(config, opts && opts.contextRequire, ct);
126 return c.loadConfiguration(config, opts && opts.contextRequire, ct);
122 } else {
127 } else {
123 return c.applyConfiguration(config, opts && opts.contextRequire, ct);
128 return c.applyConfiguration(config, opts && opts.contextRequire, ct);
124 }
129 }
125 }
130 }
126
131
127 createChildContainer<S2 extends object = S>(): Container<S & S2> {
132 createChildContainer<S2 extends object = S>(): Container<S & S2> {
128 return new Container<S & S2>(this as any);
133 return new Container<S & S2>(this as any);
129 }
134 }
130
131 has(id: string | number) {
132 return id in this._cache;
133 }
134
135 get(id: string | number) {
136 return this._cache[id];
137 }
138
139 store(id: string | number, value: any) {
140 return (this._cache[id] = value);
141 }
142
143 }
135 }
@@ -1,92 +1,141
1 import { IDestroyable, MapOf } from "../interfaces";
1 import { IDestroyable, MapOf } from "../interfaces";
2 import { argumentNotNull, isDestroyable } from "../safe";
2 import { argumentNotNull, isDestroyable } from "../safe";
3 import { ILifetimeManager, ILifetime } from "./interfaces";
3 import { ILifetimeManager, ILifetime } from "./interfaces";
4 import { ActivationContext } from "./ActivationContext";
4 import { ActivationContext } from "./ActivationContext";
5
5
6 function safeCall(item: () => void) {
6 function safeCall(item: () => void) {
7 try {
7 try {
8 item();
8 item();
9 } catch {
9 } catch {
10 // silence
10 // silence!
11 }
11 }
12 }
12 }
13
13
14 const emptyLifetime: ILifetime = {
15 has() {
16 return false;
17 },
18
19 enter() {
20
21 },
22
23 get() {
24 throw new Error("The specified item isn't registered with this lifetime manager");
25 },
26
27 store() {
28 // does nothing
29 }
30
31 };
32
14 export class LifetimeManager implements IDestroyable, ILifetimeManager {
33 export class LifetimeManager implements IDestroyable, ILifetimeManager {
15 private _cleanup: (() => void)[] = [];
34 private _cleanup: (() => void)[] = [];
16 private _cache: MapOf<any> = {};
35 private _cache: MapOf<any> = {};
17 private _destroyed = false;
36 private _destroyed = false;
18
37
19 initialize(id: string, context: ActivationContext<any>): ILifetime {
38 initialize(id: string): ILifetime {
20 const self = this;
39 const self = this;
21 let pending = false;
40 let pending = false;
22 return {
41 return {
23 has() {
42 has() {
24 return (id in self._cache);
43 return (id in self._cache);
25 },
44 },
26
45
27 get() {
46 get() {
28 const t = self._cache[id];
47 const t = self._cache[id];
29 if (t === undefined)
48 if (t === undefined)
30 throw new Error(`The item with with the key ${id} isn't found`);
49 throw new Error(`The item with with the key ${id} isn't found`);
31 return t;
50 return t;
32 },
51 },
33
52
34 enter() {
53 enter() {
35 if (pending)
54 if (pending)
36 throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`);
55 throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`);
37 pending = true;
56 pending = true;
38 },
57 },
39
58
40 store(item: any, cleanup?: (item: any) => void) {
59 store(item: any, cleanup?: (item: any) => void) {
41 argumentNotNull(id, "id");
60 argumentNotNull(id, "id");
42 argumentNotNull(item, "item");
61 argumentNotNull(item, "item");
43
62
44 if (this.has())
63 if (this.has())
45 throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
64 throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
46 pending = false;
65 pending = false;
47
66
48 self._cache[id] = item;
67 self._cache[id] = item;
49
68
50 if (self._destroyed)
69 if (self._destroyed)
51 throw new Error("Lifetime manager is destroyed");
70 throw new Error("Lifetime manager is destroyed");
52 if (cleanup) {
71 if (cleanup) {
53 self._cleanup.push(() => cleanup(item));
72 self._cleanup.push(() => cleanup(item));
54 } else if (isDestroyable(item)) {
73 } else if (isDestroyable(item)) {
55 self._cleanup.push(() => item.destroy());
74 self._cleanup.push(() => item.destroy());
56 }
75 }
57 }
76 }
58 };
77 };
59 }
78 }
60
79
61
62
63
64
65
66
67 destroy() {
80 destroy() {
68 if (!this._destroyed) {
81 if (!this._destroyed) {
69 this._destroyed = true;
82 this._destroyed = true;
70 this._cleanup.forEach(safeCall);
83 this._cleanup.forEach(safeCall);
71 this._cleanup.length = 0;
84 this._cleanup.length = 0;
72 }
85 }
73 }
86 }
74
87
75 static readonly empty: ILifetimeManager = {
88 static readonly empty: ILifetimeManager = {
76 has() {
89 initialize(): ILifetime {
77 return false;
90 return emptyLifetime;
78 },
91 },
92 destroy() {
93 throw new Error("Trying to destroy empty lifetime manager, this is a bug.");
94 }
95
96 };
97
98 static readonly hierarchyLifetime: ILifetimeManager = {
99 initialize(id: string, context: ActivationContext<any>): ILifetime {
100 return context.getContainer().getLifetimeManager().initialize(id, context);
101 },
102 destroy() {
103 throw new Error("Trying to destroy hierarchy lifetime manager, this is a bug.");
104 }
105 };
79
106
80 get() {
107 static readonly singletonLifetime: ILifetimeManager = {
81 throw new Error("The specified item isn't registered with this lifetime manager");
108 initialize(id: string): ILifetime {
109 return singletonLifetimeManager.initialize(id);
82 },
110 },
111 destroy() {
112 throw new Error("Trying to destroy singleton lifetime manager, this is a bug.");
113 }
114 };
83
115
84 register() {
116 static readonly contextLifetime: ILifetimeManager = {
85 // does nothing
117 initialize(id: string, context: ActivationContext<any>): ILifetime {
118 return {
119 enter() {
120 if (context.visit(id))
121 throw new Error("Cyclic reference detected");
122 },
123 get() {
124 return context.get(id);
125 },
126 has() {
127 return context.has(id);
128 },
129 store(item: any) {
130 context.store(id, item);
131 }
132
133 };
86 },
134 },
87
88 destroy() {
135 destroy() {
89 throw new Error("Trying to destroy empty lifetime manager, this is a bug.");
136 throw new Error("Trying to destroy empty lifetime manager, this is a bug.");
90 }
137 }
91 };
138 };
92 }
139 }
140
141 const singletonLifetimeManager = new LifetimeManager();
@@ -1,157 +1,164
1 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
2 import { Descriptor, ServiceMap, PartialServiceMap, ActivationType, ILifetimeManager } from "./interfaces";
2 import { Descriptor, ServiceMap, PartialServiceMap, ILifetimeManager } from "./interfaces";
3 import { argumentNotNull, isPrimitive, keys, isNull } from "../safe";
3 import { argumentNotNull, 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 import { Container } from "./Container";
8
9
9 let cacheId = 0;
10 let cacheId = 0;
10
11
11 const trace = TraceSource.get("@implab/core/di/ActivationContext");
12 const trace = TraceSource.get("@implab/core/di/ActivationContext");
12
13
13 function injectMethod<T, M extends keyof T, S extends object, A>(target: T, method: M, context: ActivationContext<S>, args: A) {
14 function injectMethod<T, M extends keyof T, S extends object, A>(target: T, method: M, context: ActivationContext<S>, args: A) {
14
15
15 const m = target[method];
16 const m = target[method];
16 if (!m || typeof m !== "function")
17 if (!m || typeof m !== "function")
17 throw new Error("Method '" + method + "' not found");
18 throw new Error("Method '" + method + "' not found");
18
19
19 if (args instanceof Array)
20 if (args instanceof Array)
20 return m.apply(target, _parse(args, context, "." + method));
21 return m.apply(target, _parse(args, context, "." + method));
21 else
22 else
22 return m.call(target, _parse(args, context, "." + method));
23 return m.call(target, _parse(args, context, "." + method));
23 }
24 }
24
25
25 function makeCleanupCallback<T>(method: Cleaner<T>) {
26 function makeCleanupCallback<T>(method: Cleaner<T>) {
26 if (typeof (method) === "function") {
27 if (typeof (method) === "function") {
27 return (target: T) => {
28 return (target: T) => {
28 method(target);
29 method(target);
29 };
30 };
30 } else {
31 } else {
31 return (target: T) => {
32 return (target: T) => {
32 const m = target[method] as any;
33 const m = target[method] as any;
33 m.apply(target);
34 m.apply(target);
34 };
35 };
35 }
36 }
36 }
37 }
37
38
38 function _parse(value: any, context: ActivationContext<any>, path: string): any {
39 function _parse(value: any, context: ActivationContext<any>, path: string): any {
39 if (isPrimitive(value))
40 if (isPrimitive(value))
40 return value as any;
41 return value as any;
41
42
42 trace.debug("parse {0}", path);
43 trace.debug("parse {0}", path);
43
44
44 if (isDescriptor(value))
45 if (isDescriptor(value))
45 return context.activate(value, path);
46 return context.activate(value, path);
46
47
47 if (value instanceof Array)
48 if (value instanceof Array)
48 return value.map((x, i) => _parse(x, context, `${path}[${i}]`)) as any;
49 return value.map((x, i) => _parse(x, context, `${path}[${i}]`)) as any;
49
50
50 const t: any = {};
51 const t: any = {};
51
52
52 keys(value).forEach(p => t[p] = _parse(value[p], context, `${path}.${p}`));
53 keys(value).forEach(p => t[p] = _parse(value[p], context, `${path}.${p}`));
53
54
54 return t;
55 return t;
55 }
56 }
56
57
57 export type Cleaner<T> = ((x: T) => void) | MatchingMemberKeys<() => void, T>;
58 export type Cleaner<T> = ((x: T) => void) | MatchingMemberKeys<() => void, T>;
58
59
59 export type InjectionSpec<T> = {
60 export type InjectionSpec<T> = {
60 [m in keyof T]?: any;
61 [m in keyof T]?: any;
61 };
62 };
62
63
63 export interface ServiceDescriptorParams<S extends object, T, P extends any[]> {
64 export interface ServiceDescriptorParams<S extends object, T, P extends any[]> {
64 lifetime: ILifetimeManager;
65 owner: Container<S>;
66
67 lifetime?: ILifetimeManager;
65
68
66 params?: P;
69 params?: P;
67
70
68 inject?: InjectionSpec<T>[];
71 inject?: InjectionSpec<T>[];
69
72
70 services?: PartialServiceMap<S>;
73 services?: PartialServiceMap<S>;
71
74
72 cleanup?: Cleaner<T>;
75 cleanup?: Cleaner<T>;
73 }
76 }
74
77
75 export class ServiceDescriptor<S extends object, T, P extends any[]> implements Descriptor<S, T> {
78 export class ServiceDescriptor<S extends object, T, P extends any[]> implements Descriptor<S, T> {
76 _services: ServiceMap<S>;
79 _services: ServiceMap<S>;
77
80
78 _params: P | undefined;
81 _params: P | undefined;
79
82
80 _inject: InjectionSpec<T>[];
83 _inject: InjectionSpec<T>[];
81
84
82 _cleanup: ((item: T) => void) | undefined;
85 _cleanup: ((item: T) => void) | undefined;
83
86
84 _cacheId = String(++cacheId);
87 _cacheId = String(++cacheId);
85
88
86 _lifetime = LifetimeManager.empty;
89 _lifetime = LifetimeManager.empty;
87
90
91 _owner: Container<S>;
92
88 constructor(opts: ServiceDescriptorParams<S, T, P>) {
93 constructor(opts: ServiceDescriptorParams<S, T, P>) {
89 argumentNotNull(opts, "opts");
94 argumentNotNull(opts && opts.owner, "opts.owner");
95
96 this._owner = opts.owner;
90
97
91 if (opts.lifetime)
98 if (opts.lifetime)
92 this._lifetime = opts.lifetime;
99 this._lifetime = opts.lifetime;
93
100
94 if (!isNull(opts.params))
101 if (!isNull(opts.params))
95 this._params = opts.params;
102 this._params = opts.params;
96
103
97 this._inject = opts.inject || [];
104 this._inject = opts.inject || [];
98
105
99 this._services = (opts.services || {}) as ServiceMap<S>;
106 this._services = (opts.services || {}) as ServiceMap<S>;
100
107
101 if (opts.cleanup) {
108 if (opts.cleanup) {
102 if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function))
109 if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function))
103 throw new Error(
110 throw new Error(
104 "The cleanup parameter must be either a function or a function name");
111 "The cleanup parameter must be either a function or a function name");
105
112
106 this._cleanup = makeCleanupCallback(opts.cleanup);
113 this._cleanup = makeCleanupCallback(opts.cleanup);
107 }
114 }
108 }
115 }
109
116
110 activate(context: ActivationContext<S>) {
117 activate(context: ActivationContext<S>) {
111 const lifetime = this._lifetime.initialize(this._cacheId, context);
118 const lifetime = this._lifetime.initialize(this._cacheId, context);
112
119
113 if (lifetime.has()) {
120 if (lifetime.has()) {
114 return lifetime.get();
121 return lifetime.get();
115 } else {
122 } else {
116 lifetime.enter();
123 lifetime.enter();
117 const instance = this._create(context);
124 const instance = this._create(context);
118 lifetime.store(this._cacheId, this._cleanup);
125 lifetime.store(this._cacheId, this._cleanup);
119 return instance;
126 return instance;
120 }
127 }
121 }
128 }
122
129
123 _factory(...params: any[]): T {
130 _factory(...params: any[]): T {
124 throw Error("Not implemented");
131 throw Error("Not implemented");
125 }
132 }
126
133
127 _create(context: ActivationContext<S>) {
134 _create(context: ActivationContext<S>) {
128 trace.debug(`constructing ${context._name}`);
135 trace.debug(`constructing ${context._name}`);
129
136
130 if (this._services) {
137 if (this._services) {
131 keys(this._services).forEach(p => context.register(p, this._services[p]));
138 keys(this._services).forEach(p => context.register(p, this._services[p]));
132 }
139 }
133
140
134 let instance: T;
141 let instance: T;
135
142
136 if (this._params === undefined) {
143 if (this._params === undefined) {
137 instance = this._factory();
144 instance = this._factory();
138 } else if (this._params instanceof Array) {
145 } else if (this._params instanceof Array) {
139 instance = this._factory.apply(this, _parse(this._params, context, "args"));
146 instance = this._factory.apply(this, _parse(this._params, context, "args"));
140 } else {
147 } else {
141 instance = this._factory(_parse(this._params, context, "args"));
148 instance = this._factory(_parse(this._params, context, "args"));
142 }
149 }
143
150
144 if (this._inject) {
151 if (this._inject) {
145 this._inject.forEach(spec => {
152 this._inject.forEach(spec => {
146 for (const m in spec)
153 for (const m in spec)
147 injectMethod(instance, m, context, spec[m]);
154 injectMethod(instance, m, context, spec[m]);
148 });
155 });
149 }
156 }
150 return instance;
157 return instance;
151 }
158 }
152
159
153 clone() {
160 clone() {
154 return Object.create(this);
161 return Object.create(this);
155 }
162 }
156
163
157 }
164 }
@@ -1,55 +1,53
1 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
2 import { IDestroyable } from "../interfaces";
2 import { IDestroyable } from "../interfaces";
3
3
4 export interface Descriptor<S extends object = any, T = any> {
4 export interface Descriptor<S extends object = any, T = any> {
5 activate(context: ActivationContext<S>): T;
5 activate(context: ActivationContext<S>): T;
6
7 clone(): this;
8 }
6 }
9
7
10 export type ServiceMap<S extends object> = {
8 export type ServiceMap<S extends object> = {
11 [k in keyof S]: Descriptor<S, S[k]>;
9 [k in keyof S]: Descriptor<S, S[k]>;
12 };
10 };
13
11
14 export type ContainerKeys<S extends object> = keyof S | keyof ContainerProvided<S>;
12 export type ContainerKeys<S extends object> = keyof S | keyof ContainerProvided<S>;
15
13
16 export type ContainerResolve<S extends object, K> =
14 export type ContainerResolve<S extends object, K> =
17 K extends keyof ContainerProvided<S> ? ContainerProvided<S>[K] :
15 K extends keyof ContainerProvided<S> ? ContainerProvided<S>[K] :
18 K extends keyof S ? S[K] : never;
16 K extends keyof S ? S[K] : never;
19
17
20 export type ContainerServiceMap<S extends object> = {
18 export type ContainerServiceMap<S extends object> = {
21 [K in ContainerKeys<S>]: Descriptor<S, ContainerResolve<S, K>>;
19 [K in ContainerKeys<S>]: Descriptor<S, ContainerResolve<S, K>>;
22 };
20 };
23
21
24 export type PartialServiceMap<S extends object> = {
22 export type PartialServiceMap<S extends object> = {
25 [k in keyof S]?: Descriptor<S, S[k]>;
23 [k in keyof S]?: Descriptor<S, S[k]>;
26 };
24 };
27
25
28 export interface Resolver<S extends object> {
26 export interface Resolver<S extends object> {
29 resolve<K extends ContainerKeys<S>>(name: K, def?: ContainerResolve<S, K>): ContainerResolve<S, K>;
27 resolve<K extends ContainerKeys<S>>(name: K, def?: ContainerResolve<S, K>): ContainerResolve<S, K>;
30 }
28 }
31
29
32 export interface ContainerProvided<S extends object> {
30 export interface ContainerProvided<S extends object> {
33 container: Resolver<S>;
31 container: Resolver<S>;
34 }
32 }
35
33
36 export type ContainerRegistered<S extends object> = /*{
34 export type ContainerRegistered<S extends object> = /*{
37 [K in Exclude<keyof S, keyof ContainerProvided<S>>]: S[K];
35 [K in Exclude<keyof S, keyof ContainerProvided<S>>]: S[K];
38 };*/
36 };*/
39 Exclude<S, ContainerProvided<S>>;
37 Exclude<S, ContainerProvided<S>>;
40
38
41 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
39 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
42
40
43 export interface ILifetimeManager extends IDestroyable {
41 export interface ILifetimeManager extends IDestroyable {
44 initialize(id: string, context: ActivationContext<any>): ILifetime;
42 initialize(id: string, context: ActivationContext<any>): ILifetime;
45 }
43 }
46
44
47 export interface ILifetime {
45 export interface ILifetime {
48 has(): boolean;
46 has(): boolean;
49
47
50 get(): any;
48 get(): any;
51
49
52 enter(): void;
50 enter(): void;
53
51
54 store(item: any, cleanup?: (item: any) => void): void;
52 store(item: any, cleanup?: (item: any) => void): void;
55 } No newline at end of file
53 }
General Comments 0
You need to be logged in to leave comments. Login now