##// END OF EJS Templates
working on services lifetime
cin -
r130:1af015b1d265 ioc ts support
parent child
Show More
@@ -1,70 +1,92
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 } from "./interfaces";
3 import { ILifetimeManager, ILifetime } from "./interfaces";
4 import { ActivationContext } from "./ActivationContext";
4
5
5 function safeCall(item: () => void) {
6 function safeCall(item: () => void) {
6 try {
7 try {
7 item();
8 item();
8 } catch {
9 } catch {
9 // silence
10 // silence
10 }
11 }
11 }
12 }
12
13
13 export class LifetimeManager implements IDestroyable, ILifetimeManager {
14 export class LifetimeManager implements IDestroyable, ILifetimeManager {
14 private _cleanup: (() => void)[] = [];
15 private _cleanup: (() => void)[] = [];
15 private _cache: MapOf<any> = {};
16 private _cache: MapOf<any> = {};
16 private _destroyed = false;
17 private _destroyed = false;
17
18
18 has(id: string) {
19 initialize(id: string, context: ActivationContext<any>): ILifetime {
19 return id in this._cache;
20 const self = this;
20 }
21 let pending = false;
22 return {
23 has() {
24 return (id in self._cache);
25 },
26
27 get() {
28 const t = self._cache[id];
29 if (t === undefined)
30 throw new Error(`The item with with the key ${id} isn't found`);
31 return t;
32 },
21
33
22 get(id: string) {
34 enter() {
23 const t = this._cache[id];
35 if (pending)
24 if (t === undefined)
36 throw Error(`Cyclic reference detected: the item with the key ${id} is already activating.`);
25 throw new Error(`The item with with the key ${id} isn't found`);
37 pending = true;
26 return t;
38 },
39
40 store(item: any, cleanup?: (item: any) => void) {
41 argumentNotNull(id, "id");
42 argumentNotNull(item, "item");
43
44 if (this.has())
45 throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
46 pending = false;
47
48 self._cache[id] = item;
49
50 if (self._destroyed)
51 throw new Error("Lifetime manager is destroyed");
52 if (cleanup) {
53 self._cleanup.push(() => cleanup(item));
54 } else if (isDestroyable(item)) {
55 self._cleanup.push(() => item.destroy());
56 }
57 }
58 };
27 }
59 }
28
60
29 register(id: string, item: any, cleanup?: (item: any) => void) {
61
30 argumentNotNull(id, "id");
62
31 argumentNotNull(item, "item");
32 if (this.has(id))
33 throw new Error(`The item with with the key ${id} already registered with this lifetime manager`);
34 this._cache[id] = item;
35
63
36 if (this._destroyed)
64
37 throw new Error("Lifetime manager is destroyed");
65
38 if (cleanup) {
39 this._cleanup.push(() => cleanup(item));
40 } else if (isDestroyable(item)) {
41 this._cleanup.push(() => item.destroy());
42 }
43 }
44
66
45 destroy() {
67 destroy() {
46 if (!this._destroyed) {
68 if (!this._destroyed) {
47 this._destroyed = true;
69 this._destroyed = true;
48 this._cleanup.forEach(safeCall);
70 this._cleanup.forEach(safeCall);
49 this._cleanup.length = 0;
71 this._cleanup.length = 0;
50 }
72 }
51 }
73 }
52
74
53 static readonly empty: ILifetimeManager = {
75 static readonly empty: ILifetimeManager = {
54 has() {
76 has() {
55 return false;
77 return false;
56 },
78 },
57
79
58 get() {
80 get() {
59 throw new Error("The specified item isn't registered with this lifetime manager");
81 throw new Error("The specified item isn't registered with this lifetime manager");
60 },
82 },
61
83
62 register() {
84 register() {
63 // does nothing
85 // does nothing
64 },
86 },
65
87
66 destroy() {
88 destroy() {
67 throw new Error("Trying to destroy empty lifetime manager, this is a bug.");
89 throw new Error("Trying to destroy empty lifetime manager, this is a bug.");
68 }
90 }
69 };
91 };
70 }
92 }
@@ -1,155 +1,157
1 import { ActivationContext } from "./ActivationContext";
1 import { ActivationContext } from "./ActivationContext";
2 import { Descriptor, ServiceMap, PartialServiceMap, ActivationType, ILifetimeManager } from "./interfaces";
2 import { Descriptor, ServiceMap, PartialServiceMap, ActivationType, 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
8
9 let cacheId = 0;
9 let cacheId = 0;
10
10
11 const trace = TraceSource.get("@implab/core/di/ActivationContext");
11 const trace = TraceSource.get("@implab/core/di/ActivationContext");
12
12
13 function injectMethod<T, M extends keyof T, S extends object, A>(target: T, method: M, context: ActivationContext<S>, args: A) {
13 function injectMethod<T, M extends keyof T, S extends object, A>(target: T, method: M, context: ActivationContext<S>, args: A) {
14
14
15 const m = target[method];
15 const m = target[method];
16 if (!m || typeof m !== "function")
16 if (!m || typeof m !== "function")
17 throw new Error("Method '" + method + "' not found");
17 throw new Error("Method '" + method + "' not found");
18
18
19 if (args instanceof Array)
19 if (args instanceof Array)
20 return m.apply(target, _parse(args, context, "." + method));
20 return m.apply(target, _parse(args, context, "." + method));
21 else
21 else
22 return m.call(target, _parse(args, context, "." + method));
22 return m.call(target, _parse(args, context, "." + method));
23 }
23 }
24
24
25 function makeClenupCallback<T>(method: Cleaner<T>) {
25 function makeCleanupCallback<T>(method: Cleaner<T>) {
26 if (typeof (method) === "function") {
26 if (typeof (method) === "function") {
27 return (target: T) => {
27 return (target: T) => {
28 method(target);
28 method(target);
29 };
29 };
30 } else {
30 } else {
31 return (target: T) => {
31 return (target: T) => {
32 (target[method] as any)();
32 const m = target[method] as any;
33 m.apply(target);
33 };
34 };
34 }
35 }
35 }
36 }
36
37
37 function _parse(value: any, context: ActivationContext<any>, path: string): any {
38 function _parse(value: any, context: ActivationContext<any>, path: string): any {
38 if (isPrimitive(value))
39 if (isPrimitive(value))
39 return value as any;
40 return value as any;
40
41
41 trace.debug("parse {0}", path);
42 trace.debug("parse {0}", path);
42
43
43 if (isDescriptor(value))
44 if (isDescriptor(value))
44 return context.activate(value, path);
45 return context.activate(value, path);
45
46
46 if (value instanceof Array)
47 if (value instanceof Array)
47 return value.map((x, i) => _parse(x, context, `${path}[${i}]`)) as any;
48 return value.map((x, i) => _parse(x, context, `${path}[${i}]`)) as any;
48
49
49 const t: any = {};
50 const t: any = {};
50
51
51 keys(value).forEach(p => t[p] = _parse(value[p], context, `${path}.${p}`));
52 keys(value).forEach(p => t[p] = _parse(value[p], context, `${path}.${p}`));
52
53
53 return t;
54 return t;
54 }
55 }
55
56
56 export type Cleaner<T> = ((x: T) => void) | MatchingMemberKeys<() => void, T>;
57 export type Cleaner<T> = ((x: T) => void) | MatchingMemberKeys<() => void, T>;
57
58
58 export type InjectionSpec<T> = {
59 export type InjectionSpec<T> = {
59 [m in keyof T]?: any;
60 [m in keyof T]?: any;
60 };
61 };
61
62
62 export interface ServiceDescriptorParams<S extends object, T, P extends any[]> {
63 export interface ServiceDescriptorParams<S extends object, T, P extends any[]> {
63 lifetime: ILifetimeManager;
64 lifetime: ILifetimeManager;
64
65
65 params?: P;
66 params?: P;
66
67
67 inject?: InjectionSpec<T>[];
68 inject?: InjectionSpec<T>[];
68
69
69 services?: PartialServiceMap<S>;
70 services?: PartialServiceMap<S>;
70
71
71 cleanup?: Cleaner<T>;
72 cleanup?: Cleaner<T>;
72 }
73 }
73
74
74 export class ServiceDescriptor<S extends object, T, P extends any[]> implements Descriptor<S, T> {
75 export class ServiceDescriptor<S extends object, T, P extends any[]> implements Descriptor<S, T> {
75 _services: ServiceMap<S>;
76 _services: ServiceMap<S>;
76
77
77 _params: P | undefined;
78 _params: P | undefined;
78
79
79 _inject: InjectionSpec<T>[];
80 _inject: InjectionSpec<T>[];
80
81
81 _cleanup: ((item: T) => void) | undefined;
82 _cleanup: ((item: T) => void) | undefined;
82
83
83 _cacheId = String(++cacheId);
84 _cacheId = String(++cacheId);
84
85
85 _lifetime = LifetimeManager.empty;
86 _lifetime = LifetimeManager.empty;
86
87
87 constructor(opts: ServiceDescriptorParams<S, T, P>) {
88 constructor(opts: ServiceDescriptorParams<S, T, P>) {
88 argumentNotNull(opts, "opts");
89 argumentNotNull(opts, "opts");
89
90
90 if (opts.lifetime)
91 if (opts.lifetime)
91 this._lifetime = opts.lifetime;
92 this._lifetime = opts.lifetime;
92
93
93 if (!isNull(opts.params))
94 if (!isNull(opts.params))
94 this._params = opts.params;
95 this._params = opts.params;
95
96
96 this._inject = opts.inject || [];
97 this._inject = opts.inject || [];
97
98
98 this._services = (opts.services || {}) as ServiceMap<S>;
99 this._services = (opts.services || {}) as ServiceMap<S>;
99
100
100 if (opts.cleanup) {
101 if (opts.cleanup) {
101 if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function))
102 if (!(typeof (opts.cleanup) === "string" || opts.cleanup instanceof Function))
102 throw new Error(
103 throw new Error(
103 "The cleanup parameter must be either a function or a function name");
104 "The cleanup parameter must be either a function or a function name");
104
105
105 this._cleanup = makeClenupCallback(opts.cleanup);
106 this._cleanup = makeCleanupCallback(opts.cleanup);
106 }
107 }
107 }
108 }
108
109
109 activate(context: ActivationContext<S>) {
110 activate(context: ActivationContext<S>) {
110 const lifetime = this._lifetime.initialize(this._cacheId, context);
111 const lifetime = this._lifetime.initialize(this._cacheId, context);
111
112
112 if (lifetime.has()) {
113 if (lifetime.has()) {
113 return lifetime.get();
114 return lifetime.get();
114 } else {
115 } else {
116 lifetime.enter();
115 const instance = this._create(context);
117 const instance = this._create(context);
116 lifetime.store(this._cacheId, this._cleanup);
118 lifetime.store(this._cacheId, this._cleanup);
117 return instance;
119 return instance;
118 }
120 }
119 }
121 }
120
122
121 _factory(...params: any[]): T {
123 _factory(...params: any[]): T {
122 throw Error("Not implemented");
124 throw Error("Not implemented");
123 }
125 }
124
126
125 _create(context: ActivationContext<S>) {
127 _create(context: ActivationContext<S>) {
126 trace.debug(`constructing ${context._name}`);
128 trace.debug(`constructing ${context._name}`);
127
129
128 if (this._services) {
130 if (this._services) {
129 keys(this._services).forEach(p => context.register(p, this._services[p]));
131 keys(this._services).forEach(p => context.register(p, this._services[p]));
130 }
132 }
131
133
132 let instance: T;
134 let instance: T;
133
135
134 if (this._params === undefined) {
136 if (this._params === undefined) {
135 instance = this._factory();
137 instance = this._factory();
136 } else if (this._params instanceof Array) {
138 } else if (this._params instanceof Array) {
137 instance = this._factory.apply(this, _parse(this._params, context, "args"));
139 instance = this._factory.apply(this, _parse(this._params, context, "args"));
138 } else {
140 } else {
139 instance = this._factory(_parse(this._params, context, "args"));
141 instance = this._factory(_parse(this._params, context, "args"));
140 }
142 }
141
143
142 if (this._inject) {
144 if (this._inject) {
143 this._inject.forEach(spec => {
145 this._inject.forEach(spec => {
144 for (const m in spec)
146 for (const m in spec)
145 injectMethod(instance, m, context, spec[m]);
147 injectMethod(instance, m, context, spec[m]);
146 });
148 });
147 }
149 }
148 return instance;
150 return instance;
149 }
151 }
150
152
151 clone() {
153 clone() {
152 return Object.create(this);
154 return Object.create(this);
153 }
155 }
154
156
155 }
157 }
@@ -1,51 +1,55
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
6
7 clone(): this;
7 clone(): this;
8 }
8 }
9
9
10 export type ServiceMap<S extends object> = {
10 export type ServiceMap<S extends object> = {
11 [k in keyof S]: Descriptor<S, S[k]>;
11 [k in keyof S]: Descriptor<S, S[k]>;
12 };
12 };
13
13
14 export type ContainerKeys<S extends object> = keyof S | keyof ContainerProvided<S>;
14 export type ContainerKeys<S extends object> = keyof S | keyof ContainerProvided<S>;
15
15
16 export type ContainerResolve<S extends object, K> =
16 export type ContainerResolve<S extends object, K> =
17 K extends keyof ContainerProvided<S> ? ContainerProvided<S>[K] :
17 K extends keyof ContainerProvided<S> ? ContainerProvided<S>[K] :
18 K extends keyof S ? S[K] : never;
18 K extends keyof S ? S[K] : never;
19
19
20 export type ContainerServiceMap<S extends object> = {
20 export type ContainerServiceMap<S extends object> = {
21 [K in ContainerKeys<S>]: Descriptor<S, ContainerResolve<S, K>>;
21 [K in ContainerKeys<S>]: Descriptor<S, ContainerResolve<S, K>>;
22 };
22 };
23
23
24 export type PartialServiceMap<S extends object> = {
24 export type PartialServiceMap<S extends object> = {
25 [k in keyof S]?: Descriptor<S, S[k]>;
25 [k in keyof S]?: Descriptor<S, S[k]>;
26 };
26 };
27
27
28 export interface Resolver<S extends object> {
28 export interface Resolver<S extends object> {
29 resolve<K extends ContainerKeys<S>>(name: K, def?: ContainerResolve<S, K>): ContainerResolve<S, K>;
29 resolve<K extends ContainerKeys<S>>(name: K, def?: ContainerResolve<S, K>): ContainerResolve<S, K>;
30 }
30 }
31
31
32 export interface ContainerProvided<S extends object> {
32 export interface ContainerProvided<S extends object> {
33 container: Resolver<S>;
33 container: Resolver<S>;
34 }
34 }
35
35
36 export type ContainerRegistered<S extends object> = /*{
36 export type ContainerRegistered<S extends object> = /*{
37 [K in Exclude<keyof S, keyof ContainerProvided<S>>]: S[K];
37 [K in Exclude<keyof S, keyof ContainerProvided<S>>]: S[K];
38 };*/
38 };*/
39 Exclude<S, ContainerProvided<S>>;
39 Exclude<S, ContainerProvided<S>>;
40
40
41 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
41 export type ActivationType = "singleton" | "container" | "hierarchy" | "context" | "call";
42
42
43 export interface ILifetimeManager extends IDestroyable {
43 export interface ILifetimeManager extends IDestroyable {
44 initialize(id: string, context: ActivationContext<any>): ILifetime;
44 initialize(id: string, context: ActivationContext<any>): ILifetime;
45 }
45 }
46
46
47 export interface ILifetime {
47 export interface ILifetime {
48 has(): boolean;
48 has(): boolean;
49
49 get(): any;
50 get(): any;
51
52 enter(): void;
53
50 store(item: any, cleanup?: (item: any) => void): void;
54 store(item: any, cleanup?: (item: any) => void): void;
51 } No newline at end of file
55 }
General Comments 0
You need to be logged in to leave comments. Login now