-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontainer.ts
More file actions
172 lines (150 loc) · 3.87 KB
/
container.ts
File metadata and controls
172 lines (150 loc) · 3.87 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/**
* Factory.
*/
export interface Factory<T> {
(...args: []): T;
}
/**
* Class constructor.
*/
export interface ClassConstructor<T> {
new(...args: any[]): T;
}
/**
* Classes and factories that can be injected into the container.
*/
export type Injectable<T> = Factory<T> | ClassConstructor<T>;
/**
* Dependency injection container.
*/
export abstract class Container {
/**
* Registered instances.
*
* @private
*/
private static readonly instances = new WeakMap<Injectable<unknown>, unknown>();
/**
* Registered instances.
*
* @private
*/
private static readonly providers = new WeakMap<Injectable<unknown>, () => unknown>();
/**
* Memoized function caches.
*
* @private
*/
private static readonly memos = new WeakMap<(...args: any[]) => any, Map<string, unknown>>();
/**
* Get an instance of a service.
*
* @param service
* @private
*/
private static serve<T>(service: Injectable<T>) {
if (this.supports(service)) {
return new (service as ClassConstructor<T>)();
}
return (service as Factory<T>)();
}
/**
* Register a service.
*
* @param service
* @param factory
* @see Container.serve
*/
static register<T>(
service: Injectable<T>,
factory?: Injectable<T>,
) {
this.instances.set(service, this.serve(factory ?? service));
this.providers.set(service, () => this.serve(factory ?? service));
}
/**
* Resolve a service or factory.
*
* When the service is not found, it will be registered into the container and returned.
*
* @param service
* @param fresh
* @see Container.registerx
*/
static resolve<T>(service: Injectable<T>, fresh: boolean = false) {
if (!this.providers.has(service)) {
this.register(service);
}
if (fresh) {
return this.providers.get(service)!() as T;
}
if (!this.instances.has(service)) {
this.instances.set(service, this.providers.get(service)!());
}
return this.instances.get(service) as T;
}
/**
* Memoize a function.
*
* The function will be called only once for each set of arguments.
*
* @param fn
*/
static memo<T extends (...args: any[]) => any>(fn: T) {
let cache = this.memos.get(fn);
if (!cache) {
cache = new Map<string, ReturnType<T>>();
this.memos.set(fn, cache);
}
/**
* Memoized function.
*
* @param args
*/
return ((...args: Parameters<T>) => {
const key = JSON.stringify(args);
if (!cache.has(key)) {
cache.set(key, fn(...args));
}
return cache.get(key)!;
}) as T;
}
/**
* Unregister a service.
*
* @param service
*/
static unregister<T>(service: Injectable<T>) {
this.instances.delete(service);
this.providers.delete(service);
}
/**
* Refresh a memoized function.
*
* @param fn
*/
static refresh<T extends (...args: any[]) => any>(fn: T) {
this.memos.delete(fn);
}
/**
* Check whether a class or service is supported to be registered into the container.
*
* @param service
* @private
*/
private static supports<T>(service: Injectable<T>) {
return Boolean(service instanceof Function && (service as ClassConstructor<T>).prototype);
}
}
/**
* Get an instance of a service.
*
* This function will register the service into the container if it is not already registered.
*
* @param service
* @param fresh
* @see Container.resolve
*/
export function app<T>(service: Injectable<T>, fresh: boolean = false) {
return Container.resolve(service, fresh);
}