@stone-js/service-container 0.0.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +340 -0
- package/dist/Container.d.ts +178 -0
- package/dist/Proxiable.d.ts +16 -0
- package/dist/declarations.d.ts +45 -0
- package/dist/errors/ContainerError.d.ts +62 -0
- package/dist/index.js +573 -0
- package/dist/models/Binding.d.ts +41 -0
- package/dist/models/Factory.d.ts +25 -0
- package/dist/models/Instance.d.ts +21 -0
- package/dist/models/ResolverBinding.d.ts +26 -0
- package/dist/models/Singleton.d.ts +25 -0
- package/package.json +90 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,573 @@
|
|
|
1
|
+
import { lowerFirst } from 'lodash-es';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Class representing a Proxiable.
|
|
5
|
+
*
|
|
6
|
+
* This class allows instances to be wrapped in a Proxy, enabling custom behaviors for property access, assignment, etc.
|
|
7
|
+
*
|
|
8
|
+
* @author Mr. Stone <evensstone@gmail.com>
|
|
9
|
+
*/
|
|
10
|
+
/* eslint-disable-next-line @typescript-eslint/no-extraneous-class */
|
|
11
|
+
class Proxiable {
|
|
12
|
+
/**
|
|
13
|
+
* Creates a Proxiable instance wrapped in a Proxy.
|
|
14
|
+
*
|
|
15
|
+
* @param handler - A trap object for the proxy, which defines custom behavior for fundamental operations (e.g., property lookup, assignment, etc.).
|
|
16
|
+
* @returns A new proxy object for this instance.
|
|
17
|
+
*/
|
|
18
|
+
constructor(handler) {
|
|
19
|
+
return new Proxy(this, handler);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Abstract class representing a Binding.
|
|
25
|
+
*
|
|
26
|
+
* This abstract class serves as the base class for all types of bindings in the service container. It holds a value and provides an abstract method
|
|
27
|
+
* to resolve and return that value, allowing different subclasses to implement their own resolution logic. Bindings are used to manage dependencies
|
|
28
|
+
* and control how objects are instantiated within the container.
|
|
29
|
+
*
|
|
30
|
+
* @template V - The type of value that this binding holds.
|
|
31
|
+
* @author Mr. Stone <evensstone@gmail.com>
|
|
32
|
+
*/
|
|
33
|
+
class Binding {
|
|
34
|
+
/**
|
|
35
|
+
* The value held by the binding.
|
|
36
|
+
*
|
|
37
|
+
* This value is resolved at runtime, either directly or through a resolver function.
|
|
38
|
+
*/
|
|
39
|
+
value;
|
|
40
|
+
/**
|
|
41
|
+
* Create a new instance of Binding.
|
|
42
|
+
*
|
|
43
|
+
* @param value - The value to be held by the binding.
|
|
44
|
+
*/
|
|
45
|
+
constructor(value) {
|
|
46
|
+
this.value = value;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Check if the value has been resolved.
|
|
50
|
+
*
|
|
51
|
+
* @returns A boolean indicating whether the value has been resolved.
|
|
52
|
+
*/
|
|
53
|
+
isResolved() {
|
|
54
|
+
return this.value !== undefined;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Class representing a ContainerError.
|
|
60
|
+
*
|
|
61
|
+
* @author Mr. Stone <evensstone@gmail.com>
|
|
62
|
+
*/
|
|
63
|
+
class ContainerError extends Error {
|
|
64
|
+
/**
|
|
65
|
+
* Error type indicating an alias conflict.
|
|
66
|
+
*/
|
|
67
|
+
static ALIAS_TYPE = 'alias';
|
|
68
|
+
/**
|
|
69
|
+
* Error type indicating that the resolver is not a function.
|
|
70
|
+
*/
|
|
71
|
+
static RESOLVER_TYPE = 'resolver';
|
|
72
|
+
/**
|
|
73
|
+
* Error type indicating a resolution failure.
|
|
74
|
+
*/
|
|
75
|
+
static RESOLUTION_TYPE = 'resolution';
|
|
76
|
+
/**
|
|
77
|
+
* Error type indicating an attempt to alias an unbound value.
|
|
78
|
+
*/
|
|
79
|
+
static ALIAS_UNBOUND_TYPE = 'alias_unbound';
|
|
80
|
+
/**
|
|
81
|
+
* Error type indicating that a value is not a service.
|
|
82
|
+
*/
|
|
83
|
+
static NOT_A_SERVICE_TYPE = 'not_a_service';
|
|
84
|
+
/**
|
|
85
|
+
* Error type indicating an error thrown by the resolver function.
|
|
86
|
+
*/
|
|
87
|
+
static CANNOT_RESOLVE_TYPE = 'cannot_resolve';
|
|
88
|
+
/**
|
|
89
|
+
* Error type indicating a circular dependency.
|
|
90
|
+
*/
|
|
91
|
+
static CIRCULAR_DEPENDENCY_TYPE = 'circular_dependency';
|
|
92
|
+
/**
|
|
93
|
+
* The type of the error.
|
|
94
|
+
*/
|
|
95
|
+
type;
|
|
96
|
+
/**
|
|
97
|
+
* Create a ContainerError.
|
|
98
|
+
*
|
|
99
|
+
* @param type - The type of the error.
|
|
100
|
+
* @param message - The error message or key related to the error.
|
|
101
|
+
*/
|
|
102
|
+
constructor(type, message) {
|
|
103
|
+
super();
|
|
104
|
+
this.type = type;
|
|
105
|
+
this.name = 'ContainerError';
|
|
106
|
+
this.message = this.getMessage(type, message);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Retrieve the error message based on the type and provided message.
|
|
110
|
+
*
|
|
111
|
+
* @param type - The type of the error.
|
|
112
|
+
* @param message - The error message or key related to the error.
|
|
113
|
+
* @returns The formatted error message.
|
|
114
|
+
*/
|
|
115
|
+
getMessage(type, message) {
|
|
116
|
+
const messages = {
|
|
117
|
+
[ContainerError.RESOLUTION_TYPE]: this.getResolutionMessage(message),
|
|
118
|
+
[ContainerError.ALIAS_TYPE]: `${String(message)} is aliased to itself`,
|
|
119
|
+
[ContainerError.CANNOT_RESOLVE_TYPE]: `Failed to resolve binding: ${String(message)}`,
|
|
120
|
+
[ContainerError.ALIAS_UNBOUND_TYPE]: `Cannot alias an unbound value : ${String(message)}`,
|
|
121
|
+
[ContainerError.CIRCULAR_DEPENDENCY_TYPE]: `Circular dependency detected for key: ${String(message)}`,
|
|
122
|
+
[ContainerError.RESOLVER_TYPE]: `Invalid resolver: Expected a function but received ${typeof message}`,
|
|
123
|
+
[ContainerError.NOT_A_SERVICE_TYPE]: `This (${String(message)}) is not a service. Must contain $$metadata$$ static property or must use @Service decorator.`
|
|
124
|
+
};
|
|
125
|
+
return messages[type] ?? 'An error has occurred.';
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Retrieve the resolution message based on the key.
|
|
129
|
+
*
|
|
130
|
+
* @param key - The key for which the resolution failed.
|
|
131
|
+
* @returns The formatted resolution error message.
|
|
132
|
+
*/
|
|
133
|
+
getResolutionMessage(key) {
|
|
134
|
+
let value = '';
|
|
135
|
+
if (key === undefined) {
|
|
136
|
+
value = 'undefined';
|
|
137
|
+
}
|
|
138
|
+
else if (key === null) {
|
|
139
|
+
value = 'null';
|
|
140
|
+
}
|
|
141
|
+
else if (typeof key === 'function') {
|
|
142
|
+
const funcName = key.name !== '' ? `: ${key.name}` : '';
|
|
143
|
+
value = `[Function${funcName}]`;
|
|
144
|
+
}
|
|
145
|
+
else if (typeof key === 'object') {
|
|
146
|
+
value = `[Object: ${key.constructor.name}]`;
|
|
147
|
+
}
|
|
148
|
+
else if (typeof key === 'string') {
|
|
149
|
+
value = `type ${typeof key} with a value of '${key}'`;
|
|
150
|
+
}
|
|
151
|
+
else if (typeof key === 'symbol') {
|
|
152
|
+
value = key.toString();
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
value = `type ${typeof key} with a value of ${String(key)}`;
|
|
156
|
+
}
|
|
157
|
+
return `Failed to resolve a binding with a key of ${value} from the service container.`;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Class representing a ResolverBinding.
|
|
163
|
+
*
|
|
164
|
+
* This class extends the Binding class, using a resolver function to lazily resolve the value when needed.
|
|
165
|
+
*
|
|
166
|
+
* @template V - The type of value that this binding holds.
|
|
167
|
+
* @author Mr. Stone <evensstone@gmail.com>
|
|
168
|
+
*/
|
|
169
|
+
class ResolverBinding extends Binding {
|
|
170
|
+
/**
|
|
171
|
+
* The resolver function used to provide the binding value.
|
|
172
|
+
*
|
|
173
|
+
* This function will be called when the value is needed, allowing for lazy instantiation
|
|
174
|
+
* and dependency resolution. It should return an instance of type `V`.
|
|
175
|
+
*/
|
|
176
|
+
resolver;
|
|
177
|
+
/**
|
|
178
|
+
* Create a new instance of ResolverBinding.
|
|
179
|
+
*
|
|
180
|
+
* @param resolver - The resolver function to provide the binding value.
|
|
181
|
+
* @throws ContainerError if the resolver is not a function.
|
|
182
|
+
*/
|
|
183
|
+
constructor(resolver) {
|
|
184
|
+
super();
|
|
185
|
+
if (typeof resolver !== 'function') {
|
|
186
|
+
throw new ContainerError(ContainerError.RESOLVER_TYPE, resolver);
|
|
187
|
+
}
|
|
188
|
+
this.resolver = resolver;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Class representing a Factory.
|
|
194
|
+
*
|
|
195
|
+
* The Factory class extends the ResolverBinding class, providing a mechanism to resolve a new instance each time the binding is resolved.
|
|
196
|
+
* This ensures that a fresh instance is created with each call to the `resolve` method.
|
|
197
|
+
*
|
|
198
|
+
* @template V - The type of value that this binding holds.
|
|
199
|
+
* @author Mr. Stone <evensstone@gmail.com>
|
|
200
|
+
*/
|
|
201
|
+
class Factory extends ResolverBinding {
|
|
202
|
+
/**
|
|
203
|
+
* Resolve and return the value of the binding.
|
|
204
|
+
*
|
|
205
|
+
* Each time this method is called, a new value is resolved using the resolver function.
|
|
206
|
+
* This is intended for cases where a fresh instance is required for each resolution, such as factories or transient dependencies.
|
|
207
|
+
*
|
|
208
|
+
* @param container - The container to resolve dependencies from.
|
|
209
|
+
* @returns The resolved value of the binding.
|
|
210
|
+
* @throws ContainerError if the value cannot be resolved.
|
|
211
|
+
*/
|
|
212
|
+
resolve(container) {
|
|
213
|
+
try {
|
|
214
|
+
return this.resolver(container);
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
throw new ContainerError(ContainerError.CANNOT_RESOLVE_TYPE, error.message);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Class representing an Instance.
|
|
224
|
+
*
|
|
225
|
+
* This class extends the Binding class and directly holds an instance value.
|
|
226
|
+
* It provides a straightforward resolution mechanism that simply returns the stored value.
|
|
227
|
+
*
|
|
228
|
+
* @template V - The type of value that this binding holds.
|
|
229
|
+
* @author Mr. Stone <evensstone@gmail.com>
|
|
230
|
+
*/
|
|
231
|
+
class Instance extends Binding {
|
|
232
|
+
/**
|
|
233
|
+
* Resolve and return the value of the binding.
|
|
234
|
+
*
|
|
235
|
+
* @param _container - Container to resolve dependencies (not used in this implementation).
|
|
236
|
+
* @returns The resolved value of the binding.
|
|
237
|
+
*/
|
|
238
|
+
resolve(_container) {
|
|
239
|
+
return this.value;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Class representing a Singleton.
|
|
245
|
+
*
|
|
246
|
+
* The Singleton class extends the ResolverBinding class, ensuring that the value is only resolved once.
|
|
247
|
+
* Subsequent calls to the `resolve` method will return the previously resolved value, making it behave as a singleton.
|
|
248
|
+
*
|
|
249
|
+
* @template V - The type of value that this binding holds.
|
|
250
|
+
* @author Mr. Stone <evensstone@gmail.com>
|
|
251
|
+
*/
|
|
252
|
+
class Singleton extends ResolverBinding {
|
|
253
|
+
/**
|
|
254
|
+
* Resolve and return the value of the binding.
|
|
255
|
+
*
|
|
256
|
+
* If the value has already been resolved, return the cached value. Otherwise, use the resolver function
|
|
257
|
+
* to resolve the value, store it, and return it.
|
|
258
|
+
*
|
|
259
|
+
* @param container - The container to resolve dependencies from.
|
|
260
|
+
* @returns The resolved value of the binding.
|
|
261
|
+
* @throws ContainerError if the value cannot be resolved.
|
|
262
|
+
*/
|
|
263
|
+
resolve(container) {
|
|
264
|
+
if (!this.isResolved()) {
|
|
265
|
+
try {
|
|
266
|
+
this.value = this.resolver(container);
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
throw new ContainerError(ContainerError.CANNOT_RESOLVE_TYPE, error.message);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return this.value;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Class representing a Container.
|
|
278
|
+
*
|
|
279
|
+
* The Container class acts as a dependency injection container, managing bindings and resolving instances.
|
|
280
|
+
* It supports different types of bindings, such as singletons, factories, and instances, and allows the use of aliases for bindings.
|
|
281
|
+
* This makes it easier to manage and resolve complex dependency trees in an application.
|
|
282
|
+
*
|
|
283
|
+
* @author Mr. Stone <evensstone@gmail.com>
|
|
284
|
+
*/
|
|
285
|
+
class Container extends Proxiable {
|
|
286
|
+
aliases;
|
|
287
|
+
resolvingKeys = new Set();
|
|
288
|
+
bindings;
|
|
289
|
+
/**
|
|
290
|
+
* Create a container.
|
|
291
|
+
*
|
|
292
|
+
* Initializes the container with empty alias and binding maps.
|
|
293
|
+
*/
|
|
294
|
+
constructor() {
|
|
295
|
+
super({
|
|
296
|
+
get: (target, prop, receiver) => {
|
|
297
|
+
if (Reflect.has(target, prop)) {
|
|
298
|
+
return Reflect.get(target, prop, receiver);
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
return target.make(prop);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
this.aliases = new Map();
|
|
306
|
+
this.bindings = new Map();
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Retrieve the value of the bindings property.
|
|
310
|
+
*
|
|
311
|
+
* @returns A map of all bindings registered in the container.
|
|
312
|
+
*/
|
|
313
|
+
getBindings() {
|
|
314
|
+
return this.bindings;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Retrieve the value of the aliases property.
|
|
318
|
+
*
|
|
319
|
+
* @returns A map of all aliases registered in the container.
|
|
320
|
+
*/
|
|
321
|
+
getAliases() {
|
|
322
|
+
return this.aliases;
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Set a binding as alias.
|
|
326
|
+
*
|
|
327
|
+
* Adds one or more aliases for a given binding key.
|
|
328
|
+
*
|
|
329
|
+
* @param key - The binding value.
|
|
330
|
+
* @param aliases - One or more strings representing the aliases.
|
|
331
|
+
* @returns The container instance.
|
|
332
|
+
*/
|
|
333
|
+
alias(key, aliases) {
|
|
334
|
+
[].concat(aliases).forEach((alias) => {
|
|
335
|
+
if (key === alias) {
|
|
336
|
+
throw new ContainerError(ContainerError.ALIAS_TYPE, key);
|
|
337
|
+
}
|
|
338
|
+
else if (!this.has(key)) {
|
|
339
|
+
throw new ContainerError(ContainerError.ALIAS_UNBOUND_TYPE, key);
|
|
340
|
+
}
|
|
341
|
+
this.aliases.set(alias, key);
|
|
342
|
+
});
|
|
343
|
+
return this;
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Check if an alias exists in the container.
|
|
347
|
+
*
|
|
348
|
+
* @param alias - The alias to check.
|
|
349
|
+
* @returns True if the alias exists, false otherwise.
|
|
350
|
+
*/
|
|
351
|
+
isAlias(alias) {
|
|
352
|
+
return this.aliases.has(alias);
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Get a binding key by its alias.
|
|
356
|
+
*
|
|
357
|
+
* @param alias - The alias name.
|
|
358
|
+
* @returns The binding key associated with the alias, or undefined if not found.
|
|
359
|
+
*/
|
|
360
|
+
getAliasKey(alias) {
|
|
361
|
+
return this.aliases.get(alias);
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Set class name as camelCase alias.
|
|
365
|
+
*
|
|
366
|
+
* Automatically assigns a camelCase alias to a given class based on its name or metadata.
|
|
367
|
+
*
|
|
368
|
+
* @param Class - The class to alias.
|
|
369
|
+
* @returns The container instance.
|
|
370
|
+
*/
|
|
371
|
+
asAlias(Class) {
|
|
372
|
+
if (typeof Class !== 'function' || !Object.prototype.hasOwnProperty.call(Class, 'prototype')) {
|
|
373
|
+
return this;
|
|
374
|
+
}
|
|
375
|
+
return this.alias(Class, lowerFirst(Class.$$metadata$$?.name ?? Class.name));
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Bind a single instance or value into the container under the provided key.
|
|
379
|
+
*
|
|
380
|
+
* @param key - The key to associate with the value.
|
|
381
|
+
* @param value - The value to be bound.
|
|
382
|
+
* @returns The container instance.
|
|
383
|
+
*/
|
|
384
|
+
instance(key, value) {
|
|
385
|
+
this.bindings.set(key, new Instance(value));
|
|
386
|
+
return this;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Bind a single instance or value into the container under the provided key if not already bound.
|
|
390
|
+
*
|
|
391
|
+
* @param key - The key to associate with the value.
|
|
392
|
+
* @param value - The value to be bound.
|
|
393
|
+
* @returns The container instance.
|
|
394
|
+
*/
|
|
395
|
+
instanceIf(key, value) {
|
|
396
|
+
if (!this.bound(key)) {
|
|
397
|
+
this.instance(key, value);
|
|
398
|
+
}
|
|
399
|
+
return this;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Bind a resolver function into the container under the provided key as a singleton.
|
|
403
|
+
*
|
|
404
|
+
* The resolver function will be called once, and the resulting value will be cached for future use.
|
|
405
|
+
*
|
|
406
|
+
* @param key - The key to associate with the singleton value.
|
|
407
|
+
* @param resolver - The resolver function to provide the value.
|
|
408
|
+
* @returns The container instance.
|
|
409
|
+
*/
|
|
410
|
+
singleton(key, resolver) {
|
|
411
|
+
this.bindings.set(key, new Singleton(resolver));
|
|
412
|
+
return this;
|
|
413
|
+
}
|
|
414
|
+
/**
|
|
415
|
+
* Bind a resolver function into the container under the provided key as a singleton if not already bound.
|
|
416
|
+
*
|
|
417
|
+
* @param key - The key to associate with the singleton value.
|
|
418
|
+
* @param resolver - The resolver function to provide the value.
|
|
419
|
+
* @returns The container instance.
|
|
420
|
+
*/
|
|
421
|
+
singletonIf(key, resolver) {
|
|
422
|
+
if (!this.bound(key)) {
|
|
423
|
+
this.singleton(key, resolver);
|
|
424
|
+
}
|
|
425
|
+
return this;
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Bind a resolver function into the container under the provided key, returning a new instance each time.
|
|
429
|
+
*
|
|
430
|
+
* @param key - The key to associate with the value.
|
|
431
|
+
* @param resolver - The resolver function to provide the value.
|
|
432
|
+
* @returns The container instance.
|
|
433
|
+
*/
|
|
434
|
+
binding(key, resolver) {
|
|
435
|
+
this.bindings.set(key, new Factory(resolver));
|
|
436
|
+
return this;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Bind a resolver function into the container under the provided key, returning a new instance each time if not already bound.
|
|
440
|
+
*
|
|
441
|
+
* @param key - The key to associate with the value.
|
|
442
|
+
* @param resolver - The resolver function to provide the value.
|
|
443
|
+
* @returns The container instance.
|
|
444
|
+
*/
|
|
445
|
+
bindingIf(key, resolver) {
|
|
446
|
+
if (!this.bound(key)) {
|
|
447
|
+
this.binding(key, resolver);
|
|
448
|
+
}
|
|
449
|
+
return this;
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Resolve a registered value from the container by its key.
|
|
453
|
+
*
|
|
454
|
+
* @param key - The key to resolve.
|
|
455
|
+
* @returns The resolved value.
|
|
456
|
+
* @throws ContainerError if the key cannot be resolved.
|
|
457
|
+
*/
|
|
458
|
+
make(key) {
|
|
459
|
+
key = this.getAliasKey(key) ?? key;
|
|
460
|
+
if (this.resolvingKeys.has(key)) {
|
|
461
|
+
throw new ContainerError(ContainerError.CIRCULAR_DEPENDENCY_TYPE, key);
|
|
462
|
+
}
|
|
463
|
+
this.resolvingKeys.add(key);
|
|
464
|
+
try {
|
|
465
|
+
if (this.bindings.has(key)) {
|
|
466
|
+
return this.bindings.get(key)?.resolve(this);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
finally {
|
|
470
|
+
this.resolvingKeys.delete(key);
|
|
471
|
+
}
|
|
472
|
+
throw new ContainerError(ContainerError.RESOLUTION_TYPE, key);
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Resolve a value from the container by its key, binding it if necessary.
|
|
476
|
+
*
|
|
477
|
+
* @param key - The key to resolve.
|
|
478
|
+
* @param singleton - Whether to bind as a singleton if not already bound.
|
|
479
|
+
* @returns The resolved value.
|
|
480
|
+
*/
|
|
481
|
+
resolve(key, singleton = false) {
|
|
482
|
+
if (this.has(key)) {
|
|
483
|
+
return this.make(key);
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
return this.autoBinding(key, key, singleton).make(key);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Resolve a value from the container by its key and return it in a factory function.
|
|
491
|
+
*
|
|
492
|
+
* @param key - The key to resolve.
|
|
493
|
+
* @returns A factory function that returns the resolved value.
|
|
494
|
+
*/
|
|
495
|
+
factory(key) {
|
|
496
|
+
return () => this.make(key);
|
|
497
|
+
}
|
|
498
|
+
/**
|
|
499
|
+
* Check if a value is already bound in the container by its key.
|
|
500
|
+
*
|
|
501
|
+
* @param key - The key to check.
|
|
502
|
+
* @returns True if the key is bound, false otherwise.
|
|
503
|
+
*/
|
|
504
|
+
bound(key) {
|
|
505
|
+
return this.bindings.has(this.getAliasKey(key) ?? key);
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Check if a value is already bound in the container by its key.
|
|
509
|
+
*
|
|
510
|
+
* @param key - The key to check.
|
|
511
|
+
* @returns True if the key is bound, false otherwise.
|
|
512
|
+
*/
|
|
513
|
+
has(key) {
|
|
514
|
+
return this.bound(key);
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Reset the container so that all bindings are removed.
|
|
518
|
+
*
|
|
519
|
+
* @returns The container instance.
|
|
520
|
+
*/
|
|
521
|
+
clear() {
|
|
522
|
+
this.aliases.clear();
|
|
523
|
+
this.bindings.clear();
|
|
524
|
+
return this;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Register services with zero configuration.
|
|
528
|
+
*
|
|
529
|
+
* @param classes - Classes representing the services to be registered in the container.
|
|
530
|
+
* @returns The container instance.
|
|
531
|
+
*/
|
|
532
|
+
register(classes) {
|
|
533
|
+
for (const Class of [].concat(classes)) {
|
|
534
|
+
if (Class.$$metadata$$?.service !== undefined) {
|
|
535
|
+
const { singleton = true, alias } = Class.$$metadata$$.service;
|
|
536
|
+
this.autoBinding(Class, Class, singleton, alias ?? []);
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
throw new ContainerError(ContainerError.NOT_A_SERVICE_TYPE, Class);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return this;
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* AutoBind value to the service container.
|
|
546
|
+
*
|
|
547
|
+
* @param name - A key to make the binding. Can be anything.
|
|
548
|
+
* @param item - The item to bind.
|
|
549
|
+
* @param singleton - Bind as singleton when true.
|
|
550
|
+
* @param alias - Key binding aliases.
|
|
551
|
+
* @returns The container instance.
|
|
552
|
+
*/
|
|
553
|
+
autoBinding(name, item, singleton = true, alias = []) {
|
|
554
|
+
const key = name;
|
|
555
|
+
const value = item ?? name;
|
|
556
|
+
if (!this.bound(key)) {
|
|
557
|
+
if (typeof value === 'function') {
|
|
558
|
+
const callable = value;
|
|
559
|
+
const resolver = Object.prototype.hasOwnProperty.call(callable, 'prototype')
|
|
560
|
+
? (container) => new callable.prototype.constructor(container)
|
|
561
|
+
: (container) => callable(container);
|
|
562
|
+
singleton ? this.singleton(key, resolver) : this.binding(key, resolver);
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
this.instance(key, value);
|
|
566
|
+
}
|
|
567
|
+
this.alias(key, alias);
|
|
568
|
+
}
|
|
569
|
+
return this;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
export { Binding, Container, ContainerError, Factory, Instance, Proxiable, ResolverBinding, Singleton };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Container } from '../Container';
|
|
2
|
+
import { BindingValue } from '../declarations';
|
|
3
|
+
/**
|
|
4
|
+
* Abstract class representing a Binding.
|
|
5
|
+
*
|
|
6
|
+
* This abstract class serves as the base class for all types of bindings in the service container. It holds a value and provides an abstract method
|
|
7
|
+
* to resolve and return that value, allowing different subclasses to implement their own resolution logic. Bindings are used to manage dependencies
|
|
8
|
+
* and control how objects are instantiated within the container.
|
|
9
|
+
*
|
|
10
|
+
* @template V - The type of value that this binding holds.
|
|
11
|
+
* @author Mr. Stone <evensstone@gmail.com>
|
|
12
|
+
*/
|
|
13
|
+
export declare abstract class Binding<V extends BindingValue> {
|
|
14
|
+
/**
|
|
15
|
+
* The value held by the binding.
|
|
16
|
+
*
|
|
17
|
+
* This value is resolved at runtime, either directly or through a resolver function.
|
|
18
|
+
*/
|
|
19
|
+
protected value?: V;
|
|
20
|
+
/**
|
|
21
|
+
* Create a new instance of Binding.
|
|
22
|
+
*
|
|
23
|
+
* @param value - The value to be held by the binding.
|
|
24
|
+
*/
|
|
25
|
+
constructor(value?: V);
|
|
26
|
+
/**
|
|
27
|
+
* Check if the value has been resolved.
|
|
28
|
+
*
|
|
29
|
+
* @returns A boolean indicating whether the value has been resolved.
|
|
30
|
+
*/
|
|
31
|
+
protected isResolved(): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Resolve and return the value of the binding.
|
|
34
|
+
*
|
|
35
|
+
* This abstract method must be implemented by subclasses to provide specific resolution logic.
|
|
36
|
+
*
|
|
37
|
+
* @param container - The container to resolve dependencies from.
|
|
38
|
+
* @returns The resolved value of the binding.
|
|
39
|
+
*/
|
|
40
|
+
abstract resolve(container: Container): V | undefined;
|
|
41
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Container } from '../Container';
|
|
2
|
+
import { BindingValue } from '../declarations';
|
|
3
|
+
import { ResolverBinding } from './ResolverBinding';
|
|
4
|
+
/**
|
|
5
|
+
* Class representing a Factory.
|
|
6
|
+
*
|
|
7
|
+
* The Factory class extends the ResolverBinding class, providing a mechanism to resolve a new instance each time the binding is resolved.
|
|
8
|
+
* This ensures that a fresh instance is created with each call to the `resolve` method.
|
|
9
|
+
*
|
|
10
|
+
* @template V - The type of value that this binding holds.
|
|
11
|
+
* @author Mr. Stone <evensstone@gmail.com>
|
|
12
|
+
*/
|
|
13
|
+
export declare class Factory<V extends BindingValue> extends ResolverBinding<V> {
|
|
14
|
+
/**
|
|
15
|
+
* Resolve and return the value of the binding.
|
|
16
|
+
*
|
|
17
|
+
* Each time this method is called, a new value is resolved using the resolver function.
|
|
18
|
+
* This is intended for cases where a fresh instance is required for each resolution, such as factories or transient dependencies.
|
|
19
|
+
*
|
|
20
|
+
* @param container - The container to resolve dependencies from.
|
|
21
|
+
* @returns The resolved value of the binding.
|
|
22
|
+
* @throws ContainerError if the value cannot be resolved.
|
|
23
|
+
*/
|
|
24
|
+
resolve(container: Container): V;
|
|
25
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Binding } from './Binding';
|
|
2
|
+
import { Container } from '../Container';
|
|
3
|
+
import { BindingValue } from '../declarations';
|
|
4
|
+
/**
|
|
5
|
+
* Class representing an Instance.
|
|
6
|
+
*
|
|
7
|
+
* This class extends the Binding class and directly holds an instance value.
|
|
8
|
+
* It provides a straightforward resolution mechanism that simply returns the stored value.
|
|
9
|
+
*
|
|
10
|
+
* @template V - The type of value that this binding holds.
|
|
11
|
+
* @author Mr. Stone <evensstone@gmail.com>
|
|
12
|
+
*/
|
|
13
|
+
export declare class Instance<V extends BindingValue> extends Binding<V> {
|
|
14
|
+
/**
|
|
15
|
+
* Resolve and return the value of the binding.
|
|
16
|
+
*
|
|
17
|
+
* @param _container - Container to resolve dependencies (not used in this implementation).
|
|
18
|
+
* @returns The resolved value of the binding.
|
|
19
|
+
*/
|
|
20
|
+
resolve(_container: Container): V | undefined;
|
|
21
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Binding } from './Binding';
|
|
2
|
+
import { BindingValue, Resolver } from '../declarations';
|
|
3
|
+
/**
|
|
4
|
+
* Class representing a ResolverBinding.
|
|
5
|
+
*
|
|
6
|
+
* This class extends the Binding class, using a resolver function to lazily resolve the value when needed.
|
|
7
|
+
*
|
|
8
|
+
* @template V - The type of value that this binding holds.
|
|
9
|
+
* @author Mr. Stone <evensstone@gmail.com>
|
|
10
|
+
*/
|
|
11
|
+
export declare abstract class ResolverBinding<V extends BindingValue> extends Binding<V> {
|
|
12
|
+
/**
|
|
13
|
+
* The resolver function used to provide the binding value.
|
|
14
|
+
*
|
|
15
|
+
* This function will be called when the value is needed, allowing for lazy instantiation
|
|
16
|
+
* and dependency resolution. It should return an instance of type `V`.
|
|
17
|
+
*/
|
|
18
|
+
protected readonly resolver: Resolver<V>;
|
|
19
|
+
/**
|
|
20
|
+
* Create a new instance of ResolverBinding.
|
|
21
|
+
*
|
|
22
|
+
* @param resolver - The resolver function to provide the binding value.
|
|
23
|
+
* @throws ContainerError if the resolver is not a function.
|
|
24
|
+
*/
|
|
25
|
+
constructor(resolver: Resolver<V>);
|
|
26
|
+
}
|