ts-ioc-container 72.0.0 → 72.2.0

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.
Files changed (235) hide show
  1. package/AGENTS.md +179 -0
  2. package/README.md +94 -0
  3. package/cjm/container/Container.js +2 -2
  4. package/cjm/container/EmptyContainer.js +17 -15
  5. package/cjm/errors/ArgumentNotFoundError.js +1 -0
  6. package/cjm/errors/CannonSingletonApplyTwiceError.js +2 -14
  7. package/cjm/errors/CannotApplySingletonTwiceError.js +18 -0
  8. package/cjm/errors/ContainerDisposedError.js +1 -0
  9. package/cjm/errors/ContainerError.js +1 -0
  10. package/cjm/errors/ContainerNotFoundError.js +1 -0
  11. package/cjm/errors/DependencyMissingKeyError.js +1 -0
  12. package/cjm/errors/DependencyNotFoundError.js +1 -0
  13. package/cjm/errors/MethodNotImplementedError.js +1 -0
  14. package/cjm/errors/ProviderDisposedError.js +1 -0
  15. package/cjm/errors/TypedEventDisposedError.js +1 -0
  16. package/cjm/errors/UnsupportedTokenTypeError.js +1 -0
  17. package/cjm/index.js +8 -3
  18. package/cjm/metadata/class.js +3 -1
  19. package/cjm/metadata/method.js +3 -1
  20. package/cjm/metadata/parameter.js +7 -1
  21. package/cjm/provider/Provider.js +6 -6
  22. package/cjm/registration/Registration.js +2 -2
  23. package/cjm/token/ConstantToken.js +4 -4
  24. package/cjm/token/FunctionToken.js +1 -1
  25. package/cjm/token/GroupInstanceToken.js +4 -4
  26. package/cjm/token/toToken.js +1 -1
  27. package/cjm/utils/TypedEvent.js +1 -1
  28. package/cjm/utils/array.js +1 -1
  29. package/esm/container/Container.js +2 -2
  30. package/esm/container/EmptyContainer.js +17 -15
  31. package/esm/errors/ArgumentNotFoundError.js +1 -0
  32. package/esm/errors/CannonSingletonApplyTwiceError.js +2 -13
  33. package/esm/errors/CannotApplySingletonTwiceError.js +14 -0
  34. package/esm/errors/ContainerDisposedError.js +1 -0
  35. package/esm/errors/ContainerError.js +1 -0
  36. package/esm/errors/ContainerNotFoundError.js +1 -0
  37. package/esm/errors/DependencyMissingKeyError.js +1 -0
  38. package/esm/errors/DependencyNotFoundError.js +1 -0
  39. package/esm/errors/MethodNotImplementedError.js +1 -0
  40. package/esm/errors/ProviderDisposedError.js +1 -0
  41. package/esm/errors/TypedEventDisposedError.js +1 -0
  42. package/esm/errors/UnsupportedTokenTypeError.js +1 -0
  43. package/esm/index.js +4 -3
  44. package/esm/metadata/class.js +1 -0
  45. package/esm/metadata/method.js +1 -0
  46. package/esm/metadata/parameter.js +5 -0
  47. package/esm/provider/Provider.js +6 -6
  48. package/esm/registration/Registration.js +2 -2
  49. package/esm/token/ConstantToken.js +4 -4
  50. package/esm/token/FunctionToken.js +1 -1
  51. package/esm/token/GroupInstanceToken.js +4 -4
  52. package/esm/token/toToken.js +1 -1
  53. package/esm/utils/TypedEvent.js +1 -1
  54. package/esm/utils/array.js +1 -1
  55. package/lib/ExecutionContext.ts +6 -0
  56. package/lib/container/AliasMap.ts +30 -0
  57. package/lib/container/AutoResolveModule.ts +24 -0
  58. package/lib/container/Container.ts +311 -0
  59. package/lib/container/EmptyContainer.ts +166 -0
  60. package/lib/container/IContainer.ts +156 -0
  61. package/lib/errors/ArgumentNotFoundError.ts +13 -0
  62. package/lib/errors/CannonSingletonApplyTwiceError.ts +6 -0
  63. package/lib/errors/CannotApplySingletonTwiceError.ts +22 -0
  64. package/lib/errors/ContainerDisposedError.ts +13 -0
  65. package/lib/errors/ContainerError.ts +15 -0
  66. package/lib/errors/ContainerNotFoundError.ts +13 -0
  67. package/lib/errors/DependencyMissingKeyError.ts +13 -0
  68. package/lib/errors/DependencyNotFoundError.ts +13 -0
  69. package/lib/errors/MethodNotImplementedError.ts +13 -0
  70. package/lib/errors/ProviderDisposedError.ts +22 -0
  71. package/lib/errors/TypedEventDisposedError.ts +22 -0
  72. package/lib/errors/UnsupportedTokenTypeError.ts +13 -0
  73. package/lib/hooks/HookCollector.ts +109 -0
  74. package/lib/hooks/HookContext.ts +85 -0
  75. package/lib/hooks/combinators.ts +51 -0
  76. package/lib/hooks/hook.ts +89 -0
  77. package/lib/hooks/injectProp.ts +14 -0
  78. package/lib/index.ts +181 -0
  79. package/lib/injector/IInjector.ts +88 -0
  80. package/lib/injector/MetadataInjector.ts +100 -0
  81. package/lib/injector/ProxyInjector.ts +31 -0
  82. package/lib/injector/SimpleInjector.ts +20 -0
  83. package/lib/metadata/class.ts +50 -0
  84. package/lib/metadata/method.ts +47 -0
  85. package/lib/metadata/parameter.ts +60 -0
  86. package/lib/metadata/target.ts +25 -0
  87. package/lib/provider/IProvider.ts +65 -0
  88. package/lib/provider/Provider.ts +174 -0
  89. package/lib/registration/IRegistration.ts +235 -0
  90. package/lib/registration/Registration.ts +122 -0
  91. package/lib/select.ts +33 -0
  92. package/lib/token/BindToken.ts +13 -0
  93. package/lib/token/ClassToken.ts +72 -0
  94. package/lib/token/ConstantToken.ts +50 -0
  95. package/lib/token/FunctionToken.ts +79 -0
  96. package/lib/token/GroupAliasToken.ts +92 -0
  97. package/lib/token/GroupInstanceToken.ts +70 -0
  98. package/lib/token/InjectionToken.ts +46 -0
  99. package/lib/token/SingleAliasToken.ts +84 -0
  100. package/lib/token/SingleToken.ts +93 -0
  101. package/lib/token/toToken.ts +46 -0
  102. package/lib/utils/ProxyRegistry.ts +149 -0
  103. package/lib/utils/TypedEvent.ts +96 -0
  104. package/lib/utils/array.ts +28 -0
  105. package/lib/utils/basic.ts +34 -0
  106. package/lib/utils/debounce.ts +18 -0
  107. package/lib/utils/errorHandler.ts +34 -0
  108. package/lib/utils/fp.ts +86 -0
  109. package/lib/utils/getConstructorChain.ts +17 -0
  110. package/lib/utils/memoize.ts +24 -0
  111. package/lib/utils/once.ts +24 -0
  112. package/lib/utils/shallowCache.ts +22 -0
  113. package/lib/utils/task.ts +33 -0
  114. package/lib/utils/throttle.ts +17 -0
  115. package/package.json +5 -3
  116. package/typings/ExecutionContext.d.ts +2 -0
  117. package/typings/ExecutionContext.d.ts.map +1 -0
  118. package/typings/container/AliasMap.d.ts +1 -0
  119. package/typings/container/AliasMap.d.ts.map +1 -0
  120. package/typings/container/AutoResolveModule.d.ts +16 -0
  121. package/typings/container/AutoResolveModule.d.ts.map +1 -0
  122. package/typings/container/Container.d.ts +60 -0
  123. package/typings/container/Container.d.ts.map +1 -0
  124. package/typings/container/EmptyContainer.d.ts +51 -0
  125. package/typings/container/EmptyContainer.d.ts.map +1 -0
  126. package/typings/container/IContainer.d.ts +75 -0
  127. package/typings/container/IContainer.d.ts.map +1 -0
  128. package/typings/errors/ArgumentNotFoundError.d.ts +3 -0
  129. package/typings/errors/ArgumentNotFoundError.d.ts.map +1 -0
  130. package/typings/errors/CannonSingletonApplyTwiceError.d.ts +6 -6
  131. package/typings/errors/CannonSingletonApplyTwiceError.d.ts.map +1 -0
  132. package/typings/errors/CannotApplySingletonTwiceError.d.ts +12 -0
  133. package/typings/errors/CannotApplySingletonTwiceError.d.ts.map +1 -0
  134. package/typings/errors/ContainerDisposedError.d.ts +3 -0
  135. package/typings/errors/ContainerDisposedError.d.ts.map +1 -0
  136. package/typings/errors/ContainerError.d.ts +7 -0
  137. package/typings/errors/ContainerError.d.ts.map +1 -0
  138. package/typings/errors/ContainerNotFoundError.d.ts +3 -0
  139. package/typings/errors/ContainerNotFoundError.d.ts.map +1 -0
  140. package/typings/errors/DependencyMissingKeyError.d.ts +3 -0
  141. package/typings/errors/DependencyMissingKeyError.d.ts.map +1 -0
  142. package/typings/errors/DependencyNotFoundError.d.ts +3 -0
  143. package/typings/errors/DependencyNotFoundError.d.ts.map +1 -0
  144. package/typings/errors/MethodNotImplementedError.d.ts +3 -0
  145. package/typings/errors/MethodNotImplementedError.d.ts.map +1 -0
  146. package/typings/errors/ProviderDisposedError.d.ts +6 -0
  147. package/typings/errors/ProviderDisposedError.d.ts.map +1 -0
  148. package/typings/errors/TypedEventDisposedError.d.ts +6 -0
  149. package/typings/errors/TypedEventDisposedError.d.ts.map +1 -0
  150. package/typings/errors/UnsupportedTokenTypeError.d.ts +3 -0
  151. package/typings/errors/UnsupportedTokenTypeError.d.ts.map +1 -0
  152. package/typings/hooks/HookCollector.d.ts +36 -0
  153. package/typings/hooks/HookCollector.d.ts.map +1 -0
  154. package/typings/hooks/HookContext.d.ts +10 -0
  155. package/typings/hooks/HookContext.d.ts.map +1 -0
  156. package/typings/hooks/combinators.d.ts +23 -0
  157. package/typings/hooks/combinators.d.ts.map +1 -0
  158. package/typings/hooks/hook.d.ts +39 -0
  159. package/typings/hooks/hook.d.ts.map +1 -0
  160. package/typings/hooks/injectProp.d.ts +9 -0
  161. package/typings/hooks/injectProp.d.ts.map +1 -0
  162. package/typings/index.d.ts +5 -3
  163. package/typings/index.d.ts.map +1 -0
  164. package/typings/injector/IInjector.d.ts +32 -0
  165. package/typings/injector/IInjector.d.ts.map +1 -0
  166. package/typings/injector/MetadataInjector.d.ts +58 -0
  167. package/typings/injector/MetadataInjector.d.ts.map +1 -0
  168. package/typings/injector/ProxyInjector.d.ts +13 -0
  169. package/typings/injector/ProxyInjector.d.ts.map +1 -0
  170. package/typings/injector/SimpleInjector.d.ts +13 -0
  171. package/typings/injector/SimpleInjector.d.ts.map +1 -0
  172. package/typings/metadata/class.d.ts +28 -0
  173. package/typings/metadata/class.d.ts.map +1 -0
  174. package/typings/metadata/method.d.ts +25 -0
  175. package/typings/metadata/method.d.ts.map +1 -0
  176. package/typings/metadata/parameter.d.ts +22 -0
  177. package/typings/metadata/parameter.d.ts.map +1 -0
  178. package/typings/metadata/target.d.ts +19 -0
  179. package/typings/metadata/target.d.ts.map +1 -0
  180. package/typings/provider/IProvider.d.ts +25 -0
  181. package/typings/provider/IProvider.d.ts.map +1 -0
  182. package/typings/provider/Provider.d.ts +30 -0
  183. package/typings/provider/Provider.d.ts.map +1 -0
  184. package/typings/registration/IRegistration.d.ts +137 -0
  185. package/typings/registration/IRegistration.d.ts.map +1 -0
  186. package/typings/registration/Registration.d.ts +23 -0
  187. package/typings/registration/Registration.d.ts.map +1 -0
  188. package/typings/select.d.ts +15 -0
  189. package/typings/select.d.ts.map +1 -0
  190. package/typings/token/BindToken.d.ts +3 -0
  191. package/typings/token/BindToken.d.ts.map +1 -0
  192. package/typings/token/ClassToken.d.ts +2 -0
  193. package/typings/token/ClassToken.d.ts.map +1 -0
  194. package/typings/token/ConstantToken.d.ts +14 -0
  195. package/typings/token/ConstantToken.d.ts.map +1 -0
  196. package/typings/token/FunctionToken.d.ts +10 -0
  197. package/typings/token/FunctionToken.d.ts.map +1 -0
  198. package/typings/token/GroupAliasToken.d.ts +14 -0
  199. package/typings/token/GroupAliasToken.d.ts.map +1 -0
  200. package/typings/token/GroupInstanceToken.d.ts +19 -0
  201. package/typings/token/GroupInstanceToken.d.ts.map +1 -0
  202. package/typings/token/InjectionToken.d.ts +8 -0
  203. package/typings/token/InjectionToken.d.ts.map +1 -0
  204. package/typings/token/SingleAliasToken.d.ts +6 -0
  205. package/typings/token/SingleAliasToken.d.ts.map +1 -0
  206. package/typings/token/SingleToken.d.ts +18 -0
  207. package/typings/token/SingleToken.d.ts.map +1 -0
  208. package/typings/token/toToken.d.ts +12 -0
  209. package/typings/token/toToken.d.ts.map +1 -0
  210. package/typings/utils/ProxyRegistry.d.ts +63 -0
  211. package/typings/utils/ProxyRegistry.d.ts.map +1 -0
  212. package/typings/utils/TypedEvent.d.ts +42 -0
  213. package/typings/utils/TypedEvent.d.ts.map +1 -0
  214. package/typings/utils/array.d.ts +7 -0
  215. package/typings/utils/array.d.ts.map +1 -0
  216. package/typings/utils/basic.d.ts +12 -0
  217. package/typings/utils/basic.d.ts.map +1 -0
  218. package/typings/utils/debounce.d.ts +2 -0
  219. package/typings/utils/debounce.d.ts.map +1 -0
  220. package/typings/utils/errorHandler.d.ts +4 -0
  221. package/typings/utils/errorHandler.d.ts.map +1 -0
  222. package/typings/utils/fp.d.ts +8 -0
  223. package/typings/utils/fp.d.ts.map +1 -0
  224. package/typings/utils/getConstructorChain.d.ts +9 -0
  225. package/typings/utils/getConstructorChain.d.ts.map +1 -0
  226. package/typings/utils/memoize.d.ts +10 -0
  227. package/typings/utils/memoize.d.ts.map +1 -0
  228. package/typings/utils/once.d.ts +2 -0
  229. package/typings/utils/once.d.ts.map +1 -0
  230. package/typings/utils/shallowCache.d.ts +2 -0
  231. package/typings/utils/shallowCache.d.ts.map +1 -0
  232. package/typings/utils/task.d.ts +13 -0
  233. package/typings/utils/task.d.ts.map +1 -0
  234. package/typings/utils/throttle.d.ts +2 -0
  235. package/typings/utils/throttle.d.ts.map +1 -0
@@ -0,0 +1,79 @@
1
+ import type { IContainer } from '../container/IContainer';
2
+ import { forwardArgs, InjectionToken } from './InjectionToken';
3
+ import { InjectFn } from '../hooks/hook';
4
+ import { ArgsFn, ResolveOptions } from '../provider/IProvider';
5
+ import { MethodNotImplementedError } from '../errors/MethodNotImplementedError';
6
+ import { Serializable } from '../utils/basic';
7
+
8
+ /**
9
+ * A token that resolves by calling an `InjectFn` with the resolution context.
10
+ *
11
+ * @example
12
+ * const CurrentScope = new FunctionToken(({ scope }) => scope);
13
+ */
14
+ export class FunctionToken<T = any> extends InjectionToken<T> implements Serializable {
15
+ private readonly _getArgsFn: ArgsFn;
16
+ private readonly _isLazy: boolean;
17
+
18
+ constructor(
19
+ private readonly fn: InjectFn<T>,
20
+ {
21
+ getArgsFn = forwardArgs,
22
+ isLazy = false,
23
+ tags = [],
24
+ }: { getArgsFn?: ArgsFn; isLazy?: boolean; tags?: string[] } = {},
25
+ ) {
26
+ super(tags);
27
+ this._getArgsFn = getArgsFn;
28
+ this._isLazy = isLazy;
29
+ }
30
+
31
+ resolve(s: IContainer, { args = [], lazy }: ResolveOptions = {}): T {
32
+ return this.fn({
33
+ scope: s,
34
+ args: this._getArgsFn({ scope: s, args }),
35
+ lazy: this._isLazy || lazy,
36
+ });
37
+ }
38
+
39
+ args(...newArgs: unknown[]): InjectionToken<T> {
40
+ const parentFn = this._getArgsFn;
41
+ return new FunctionToken<T>(this.fn, {
42
+ getArgsFn: (options) => [...parentFn(options), ...newArgs],
43
+ isLazy: this._isLazy,
44
+ tags: this.getTags(),
45
+ });
46
+ }
47
+
48
+ argsFn(fn: (s: IContainer) => unknown[]): InjectionToken<T> {
49
+ const parentFn = this._getArgsFn;
50
+ return new FunctionToken<T>(this.fn, {
51
+ getArgsFn: (options) => [...parentFn(options), ...fn(options.scope)],
52
+ isLazy: this._isLazy,
53
+ tags: this.getTags(),
54
+ });
55
+ }
56
+
57
+ lazy(): InjectionToken<T> {
58
+ return new FunctionToken<T>(this.fn, {
59
+ getArgsFn: this._getArgsFn,
60
+ isLazy: true,
61
+ tags: this.getTags(),
62
+ });
63
+ }
64
+
65
+ addTags(...tags: string[]): FunctionToken<T> {
66
+ return new FunctionToken<T>(this.fn, {
67
+ getArgsFn: this._getArgsFn,
68
+ isLazy: this._isLazy,
69
+ tags: [...this.getTags(), ...tags],
70
+ });
71
+ }
72
+
73
+ /**
74
+ * @throws {MethodNotImplementedError} always — a function token has no underlying key.
75
+ */
76
+ toString(): string {
77
+ throw new MethodNotImplementedError('FunctionToken.toString is not implemented');
78
+ }
79
+ }
@@ -0,0 +1,92 @@
1
+ import { DependencyKey, IContainer } from '../container/IContainer';
2
+ import { forwardArgs, InjectionToken } from './InjectionToken';
3
+ import { IRegistration } from '../registration/IRegistration';
4
+ import { BindToken } from './BindToken';
5
+ import { ArgsFn, ResolveOptions } from '../provider/IProvider';
6
+ import { Serializable } from '../utils/basic';
7
+
8
+ /**
9
+ * A token for every dependency registered under an alias. As a `bindTo(...)`
10
+ * target it adds the alias; resolving returns all matches as an array.
11
+ *
12
+ * @example
13
+ * const IMiddlewareToken = toGroupAlias<IMiddleware>('IMiddleware');
14
+ *
15
+ * @register(bindTo(IMiddlewareToken))
16
+ * class Auth implements IMiddleware {}
17
+ *
18
+ * IMiddlewareToken.resolve(scope); // IMiddleware[]
19
+ */
20
+ export class GroupAliasToken<T = any> extends InjectionToken<T[]> implements BindToken<T>, Serializable {
21
+ private readonly _getArgsFn: ArgsFn;
22
+ private readonly _isLazy: boolean;
23
+
24
+ constructor(
25
+ readonly token: DependencyKey,
26
+ {
27
+ getArgsFn = forwardArgs,
28
+ isLazy = false,
29
+ tags = [],
30
+ }: { getArgsFn?: ArgsFn; isLazy?: boolean; tags?: string[] } = {},
31
+ ) {
32
+ super(tags);
33
+ this._getArgsFn = getArgsFn;
34
+ this._isLazy = isLazy;
35
+ }
36
+
37
+ select<R>(fn: (target: T[]) => R[]) {
38
+ return (s: IContainer) => fn(this.resolve(s));
39
+ }
40
+
41
+ resolve(s: IContainer, { args = [], lazy }: ResolveOptions = {}): T[] {
42
+ return s.resolveByAlias(this.token, {
43
+ args: this._getArgsFn({ scope: s, args }),
44
+ lazy: this._isLazy || lazy,
45
+ });
46
+ }
47
+
48
+ bindTo(r: IRegistration<T>) {
49
+ r.bindToAlias(this.token);
50
+ }
51
+
52
+ args(...newArgs: unknown[]) {
53
+ const parentFn = this._getArgsFn;
54
+ return new GroupAliasToken<T>(this.token, {
55
+ getArgsFn: (options) => [...parentFn(options), ...newArgs],
56
+ isLazy: this._isLazy,
57
+ tags: this.getTags(),
58
+ });
59
+ }
60
+
61
+ argsFn(fn: (s: IContainer) => unknown[]) {
62
+ const parentFn = this._getArgsFn;
63
+ return new GroupAliasToken<T>(this.token, {
64
+ getArgsFn: (options) => [...parentFn(options), ...fn(options.scope)],
65
+ isLazy: this._isLazy,
66
+ tags: this.getTags(),
67
+ });
68
+ }
69
+
70
+ lazy() {
71
+ return new GroupAliasToken<T>(this.token, {
72
+ getArgsFn: this._getArgsFn,
73
+ isLazy: true,
74
+ tags: this.getTags(),
75
+ });
76
+ }
77
+
78
+ addTags(...tags: string[]): GroupAliasToken<T> {
79
+ return new GroupAliasToken<T>(this.token, {
80
+ getArgsFn: this._getArgsFn,
81
+ isLazy: this._isLazy,
82
+ tags: [...this.getTags(), ...tags],
83
+ });
84
+ }
85
+
86
+ toString(): string {
87
+ return this.token.toString();
88
+ }
89
+ }
90
+
91
+ /** Creates a {@link GroupAliasToken} for `token`. */
92
+ export const toGroupAlias = <T>(token: DependencyKey) => new GroupAliasToken<T>(token);
@@ -0,0 +1,70 @@
1
+ import { InjectionToken } from './InjectionToken';
2
+ import type { IContainer } from '../container/IContainer';
3
+ import { MethodNotImplementedError } from '../errors/MethodNotImplementedError';
4
+
5
+ import { Instance, Serializable } from '../utils/basic';
6
+
7
+ /** Selects instances for a {@link GroupInstanceToken}. */
8
+ export type InstancePredicate = (dep: unknown) => boolean;
9
+
10
+ /**
11
+ * Resolves the instances already created in a scope (and, by default, its
12
+ * child scopes) that match a predicate. Created by `select.instances(...)`.
13
+ */
14
+ export class GroupInstanceToken extends InjectionToken<Instance[]> implements Serializable {
15
+ private isCascade: boolean;
16
+
17
+ constructor(
18
+ private predicate: InstancePredicate,
19
+ { tags = [], isCascade = true }: { tags?: string[]; isCascade?: boolean } = {},
20
+ ) {
21
+ super(tags);
22
+ this.isCascade = isCascade;
23
+ }
24
+
25
+ select<R>(fn: (target: Instance) => R) {
26
+ return (s: IContainer) => this.resolve(s).map(fn);
27
+ }
28
+
29
+ /**
30
+ * @throws {MethodNotImplementedError} always — a group instance token cannot receive static args.
31
+ */
32
+ args(...deps: unknown[]): this {
33
+ throw new MethodNotImplementedError('GroupInstanceToken.args is not implemented');
34
+ }
35
+
36
+ /**
37
+ * @throws {MethodNotImplementedError} always — a group instance token cannot receive resolved args.
38
+ */
39
+ argsFn(getArgsFn: (s: IContainer) => unknown[]): InjectionToken<Instance[]> {
40
+ throw new MethodNotImplementedError('GroupInstanceToken.argsFn is not implemented');
41
+ }
42
+
43
+ /**
44
+ * @throws {MethodNotImplementedError} always — a group instance token cannot be made lazy.
45
+ */
46
+ lazy(): InjectionToken<Instance[]> {
47
+ throw new MethodNotImplementedError('GroupInstanceToken.lazy is not implemented');
48
+ }
49
+
50
+ /** Whether instances of child scopes are included (default `true`). */
51
+ cascade(isTrue: boolean): this {
52
+ this.isCascade = isTrue;
53
+ return this;
54
+ }
55
+
56
+ resolve(c: IContainer): Instance[] {
57
+ return c.getInstances(this.isCascade).filter(this.predicate);
58
+ }
59
+
60
+ addTags(...tags: string[]): GroupInstanceToken {
61
+ return new GroupInstanceToken(this.predicate, { tags: [...this.getTags(), ...tags], isCascade: this.isCascade });
62
+ }
63
+
64
+ /**
65
+ * @throws {MethodNotImplementedError} always — a group instance token has no underlying key.
66
+ */
67
+ toString(): string {
68
+ throw new MethodNotImplementedError('GroupInstanceToken.toString is not implemented');
69
+ }
70
+ }
@@ -0,0 +1,46 @@
1
+ import { type IContainer, type Tag } from '../container/IContainer';
2
+ import { ArgsFn, ResolveOptions } from '../provider/IProvider';
3
+ import { Is } from '../utils/basic';
4
+
5
+ /**
6
+ * The default `getArgsFn` of every token: the runtime `args` a token is
7
+ * resolved with reach the provider as they are, and `token.args(...)` /
8
+ * `token.argsFn(...)` append after them.
9
+ */
10
+ export const forwardArgs: ArgsFn = ({ args = [] }) => args;
11
+
12
+ /** Base class of every token: something resolvable from a scope that can be specialized with args, lazy and tags. */
13
+ export abstract class InjectionToken<T = any> {
14
+ private readonly tags: Set<Tag>;
15
+
16
+ protected constructor(tags: Tag[] = []) {
17
+ this.tags = new Set(tags);
18
+ }
19
+
20
+ abstract resolve(s: IContainer, options?: ResolveOptions): T;
21
+ abstract args(...deps: unknown[]): InjectionToken<T>;
22
+ abstract argsFn(getArgsFn: (s: IContainer) => unknown[]): InjectionToken<T>;
23
+ abstract lazy(): InjectionToken<T>;
24
+ abstract addTags(...tags: Tag[]): InjectionToken<T>;
25
+
26
+ hasTag(tag: Tag): boolean {
27
+ return this.tags.has(tag);
28
+ }
29
+
30
+ protected getTags(): Tag[] {
31
+ return [...this.tags];
32
+ }
33
+ }
34
+
35
+ /** Narrows `target` to an {@link InjectionToken}. */
36
+ export function isInjectionToken<T = any>(target: unknown): target is InjectionToken<T> {
37
+ return (
38
+ Is.object(target) &&
39
+ 'resolve' in target &&
40
+ 'args' in target &&
41
+ 'argsFn' in target &&
42
+ 'lazy' in target &&
43
+ 'hasTag' in target &&
44
+ 'addTags' in target
45
+ );
46
+ }
@@ -0,0 +1,84 @@
1
+ import { DependencyKey, IContainer } from '../container/IContainer';
2
+ import { forwardArgs, InjectionToken } from './InjectionToken';
3
+ import { IRegistration } from '../registration/IRegistration';
4
+ import { BindToken } from './BindToken';
5
+ import { ArgsFn, ResolveOptions } from '../provider/IProvider';
6
+ import { Serializable } from '../utils/basic';
7
+
8
+ /**
9
+ * Like {@link GroupAliasToken}, but resolves exactly one dependency registered
10
+ * under the alias, and throws `DependencyNotFoundError` when there is none.
11
+ */
12
+ export class SingleAliasToken<T = any> extends InjectionToken<T> implements BindToken<T>, Serializable {
13
+ private readonly _getArgsFn: ArgsFn;
14
+ private readonly _isLazy: boolean;
15
+
16
+ constructor(
17
+ readonly token: DependencyKey,
18
+ {
19
+ getArgsFn = forwardArgs,
20
+ isLazy = false,
21
+ tags = [],
22
+ }: { getArgsFn?: ArgsFn; isLazy?: boolean; tags?: string[] } = {},
23
+ ) {
24
+ super(tags);
25
+ this._getArgsFn = getArgsFn;
26
+ this._isLazy = isLazy;
27
+ }
28
+
29
+ select<R>(fn: (target: T) => R) {
30
+ return (s: IContainer) => fn(this.resolve(s));
31
+ }
32
+
33
+ resolve(s: IContainer, { args = [], lazy }: ResolveOptions = {}): T {
34
+ return s.resolveOneByAlias(this.token, {
35
+ args: this._getArgsFn({ scope: s, args }),
36
+ lazy: this._isLazy || lazy,
37
+ });
38
+ }
39
+
40
+ bindTo(r: IRegistration<T>) {
41
+ r.bindToAlias(this.token);
42
+ }
43
+
44
+ args(...newArgs: unknown[]) {
45
+ const parentFn = this._getArgsFn;
46
+ return new SingleAliasToken<T>(this.token, {
47
+ getArgsFn: (options) => [...parentFn(options), ...newArgs],
48
+ isLazy: this._isLazy,
49
+ tags: this.getTags(),
50
+ });
51
+ }
52
+
53
+ argsFn(fn: (s: IContainer) => unknown[]) {
54
+ const parentFn = this._getArgsFn;
55
+ return new SingleAliasToken<T>(this.token, {
56
+ getArgsFn: (options) => [...parentFn(options), ...fn(options.scope)],
57
+ isLazy: this._isLazy,
58
+ tags: this.getTags(),
59
+ });
60
+ }
61
+
62
+ lazy() {
63
+ return new SingleAliasToken<T>(this.token, {
64
+ getArgsFn: this._getArgsFn,
65
+ isLazy: true,
66
+ tags: this.getTags(),
67
+ });
68
+ }
69
+
70
+ addTags(...tags: string[]): SingleAliasToken<T> {
71
+ return new SingleAliasToken<T>(this.token, {
72
+ getArgsFn: this._getArgsFn,
73
+ isLazy: this._isLazy,
74
+ tags: [...this.getTags(), ...tags],
75
+ });
76
+ }
77
+
78
+ toString(): string {
79
+ return this.token.toString();
80
+ }
81
+ }
82
+
83
+ /** Creates a {@link SingleAliasToken} for `token`. */
84
+ export const toSingleAlias = <T>(token: DependencyKey) => new SingleAliasToken<T>(token);
@@ -0,0 +1,93 @@
1
+ import { DependencyKey, IContainer } from '../container/IContainer';
2
+ import { forwardArgs, InjectionToken } from './InjectionToken';
3
+ import { IRegistration } from '../registration/IRegistration';
4
+ import { ArgsFn, ResolveOptions } from '../provider/IProvider';
5
+ import { Serializable } from '../utils/basic';
6
+
7
+ /**
8
+ * A typed handle for one dependency key - the usual way to name a dependency.
9
+ * `args(...)`, `argsFn(...)` and `lazy()` return a new token and never change
10
+ * this one, so one token can be specialized per injection site.
11
+ *
12
+ * @example
13
+ * const ILoggerToken = new SingleToken<ILogger>('ILogger');
14
+ *
15
+ * @register(bindTo(ILoggerToken))
16
+ * class Logger implements ILogger {}
17
+ *
18
+ * class App {
19
+ * constructor(@inject(by(ILoggerToken)) logger: ILogger) {}
20
+ * }
21
+ * ILoggerToken.resolve(scope);
22
+ * ILoggerToken.args('prefix').lazy().resolve(scope);
23
+ */
24
+ export class SingleToken<T = any> extends InjectionToken<T> implements Serializable {
25
+ private readonly _getArgsFn: ArgsFn;
26
+ private readonly _isLazy: boolean;
27
+
28
+ constructor(
29
+ public token: DependencyKey,
30
+ {
31
+ getArgsFn = forwardArgs,
32
+ isLazy = false,
33
+ tags = [],
34
+ }: { getArgsFn?: ArgsFn; isLazy?: boolean; tags?: string[] } = {},
35
+ ) {
36
+ super(tags);
37
+ this._getArgsFn = getArgsFn;
38
+ this._isLazy = isLazy;
39
+ }
40
+
41
+ select<R>(fn: (target: T) => R) {
42
+ return (s: IContainer) => fn(this.resolve(s));
43
+ }
44
+
45
+ resolve(s: IContainer, { args = [], lazy }: ResolveOptions = {}): T {
46
+ return s.resolve(this.token, {
47
+ args: this._getArgsFn({ scope: s, args }),
48
+ lazy: this._isLazy || lazy,
49
+ });
50
+ }
51
+
52
+ bindTo(r: IRegistration<T>) {
53
+ r.bindToKey(this.token);
54
+ }
55
+
56
+ args(...newArgs: unknown[]) {
57
+ const parentFn = this._getArgsFn;
58
+ return new SingleToken<T>(this.token, {
59
+ getArgsFn: (options) => [...parentFn(options), ...newArgs],
60
+ isLazy: this._isLazy,
61
+ tags: this.getTags(),
62
+ });
63
+ }
64
+
65
+ argsFn(fn: (s: IContainer) => unknown[]) {
66
+ const parentFn = this._getArgsFn;
67
+ return new SingleToken<T>(this.token, {
68
+ getArgsFn: (options) => [...parentFn(options), ...fn(options.scope)],
69
+ isLazy: this._isLazy,
70
+ tags: this.getTags(),
71
+ });
72
+ }
73
+
74
+ lazy() {
75
+ return new SingleToken<T>(this.token, {
76
+ getArgsFn: this._getArgsFn,
77
+ isLazy: true,
78
+ tags: this.getTags(),
79
+ });
80
+ }
81
+
82
+ addTags(...tags: string[]): SingleToken<T> {
83
+ return new SingleToken<T>(this.token, {
84
+ getArgsFn: this._getArgsFn,
85
+ isLazy: this._isLazy,
86
+ tags: [...this.getTags(), ...tags],
87
+ });
88
+ }
89
+
90
+ toString(): string {
91
+ return this.token.toString();
92
+ }
93
+ }
@@ -0,0 +1,46 @@
1
+ import { DependencyKey, isDependencyKey } from '../container/IContainer';
2
+ import { SingleToken } from './SingleToken';
3
+ import { ClassToken } from './ClassToken';
4
+ import { FunctionToken } from './FunctionToken';
5
+ import { UnsupportedTokenTypeError } from '../errors/UnsupportedTokenTypeError';
6
+ import { InjectionToken, isInjectionToken } from './InjectionToken';
7
+ import { InjectFn } from '../hooks/hook';
8
+ import { type constructor, Is } from '../utils/basic';
9
+ import { ConstantToken } from './ConstantToken';
10
+
11
+ /** Anything `toToken(...)` turns into a token: a token, a key, a class or an `InjectFn`. */
12
+ export type Injectable<T = any> = InjectFn<T> | InjectionToken<T> | DependencyKey | constructor<T>;
13
+
14
+ /**
15
+ * @throws {UnsupportedTokenTypeError} when `token` is not an `InjectionToken`, a `DependencyKey`, a constructor, or a function.
16
+ */
17
+ export const toToken = <T = any>(token: Injectable<T>): InjectionToken<T> => {
18
+ if (token instanceof InjectionToken) {
19
+ return token;
20
+ }
21
+
22
+ if (isDependencyKey(token)) {
23
+ return new SingleToken(token);
24
+ }
25
+
26
+ if (Is.constructor(token)) {
27
+ return new ClassToken(token);
28
+ }
29
+
30
+ if (typeof token === 'function') {
31
+ return new FunctionToken(token as InjectFn<T>);
32
+ }
33
+
34
+ throw new UnsupportedTokenTypeError(
35
+ `Unknown token ${token}: expected an InjectionToken, a DependencyKey (string | symbol), a class or a function.`,
36
+ );
37
+ };
38
+
39
+ /**
40
+ * Wraps a runtime argument so it can be resolved uniformly: an `InjectionToken`
41
+ * is returned as-is, anything else becomes a `ConstantToken` of itself. The
42
+ * library never applies it to the args list - a call site that wants
43
+ * "resolve tokens, pass literals through" does so explicitly, e.g.
44
+ * `@inject(({ scope, args = [] }) => argToToken(args[0]).resolve(scope))`.
45
+ */
46
+ export const argToToken = <T = unknown>(v: T): InjectionToken<T> => (isInjectionToken<T>(v) ? v : new ConstantToken(v));
@@ -0,0 +1,149 @@
1
+ type ProxyState<T extends object> = {
2
+ getTarget: () => T;
3
+ };
4
+
5
+ /**
6
+ * The proxy operations the library depends on. Depend on this rather than on
7
+ * {@link ProxyRegistry} itself, so a caller can substitute its own
8
+ * implementation - the concrete registry is a singleton and cannot be
9
+ * constructed a second time.
10
+ */
11
+ export interface IProxyRegistry {
12
+ /**
13
+ * Returns the real object behind `value`, unwrapping proxies stacked to any
14
+ * depth. A value that is not a proxy is returned as is.
15
+ *
16
+ * The library calls this itself wherever a proxy would give the wrong answer -
17
+ * every read of class metadata (see `resolveConstructor`, `getHooks`,
18
+ * `resolveArgs`) and every identity check against a tracked instance (see
19
+ * `IContainer.hasInstance`). Pass the container's own values to those APIs as
20
+ * they come; unwrapping by hand is for code that needs the real object for its
21
+ * own reasons.
22
+ */
23
+ unwrap<T extends object>(value: T): T;
24
+
25
+ createProxy<T extends object>(target: T, handler?: ProxyHandler<T>): T;
26
+
27
+ /**
28
+ * Creates a proxy that resolves its target on first access and caches it.
29
+ */
30
+ createLazyProxy<T extends object>(resolveInstance: () => T): T;
31
+
32
+ /**
33
+ * Wraps `resolveInstance` in a lazy proxy when `isLazy`, otherwise calls it
34
+ * right away.
35
+ */
36
+ toLazyIf<T extends object>(resolveInstance: () => T, isLazy?: boolean): T;
37
+ }
38
+
39
+ /**
40
+ * Creates proxies and tracks them, so a proxy can be traced back to the real
41
+ * object it stands for. The library's own {@link IProxyRegistry}.
42
+ *
43
+ * Process-wide singleton: a proxy created anywhere must be unwrappable
44
+ * everywhere, and the registry holds no configuration that would justify a
45
+ * second instance. Obtain it with {@link ProxyRegistry.getInstance}.
46
+ *
47
+ * Entries are held weakly - a proxy that becomes unreachable is collected along
48
+ * with its state.
49
+ */
50
+ export class ProxyRegistry implements IProxyRegistry {
51
+ private static instance?: ProxyRegistry;
52
+
53
+ private readonly states = new WeakMap<object, ProxyState<object>>();
54
+
55
+ private constructor() {}
56
+
57
+ static getInstance(): ProxyRegistry {
58
+ ProxyRegistry.instance ??= new ProxyRegistry();
59
+ return ProxyRegistry.instance;
60
+ }
61
+
62
+ /**
63
+ * Returns the real object behind `value`, unwrapping proxies stacked to any
64
+ * depth. A value that is not a proxy is returned as is.
65
+ */
66
+ unwrap<T extends object>(value: T): T {
67
+ return this.isProxy(value) ? this.getTarget(value) : value;
68
+ }
69
+
70
+ createProxy<T extends object>(target: T, handler: ProxyHandler<T> = {}): T {
71
+ const proxy = new Proxy(target, handler);
72
+ return this.register(proxy, { getTarget: () => target });
73
+ }
74
+
75
+ /**
76
+ * Creates a proxy that resolves its target on first access and caches it.
77
+ * The target is unwrapped on resolution, so wrapping a lazy proxy in another
78
+ * lazy proxy never stacks beyond one level.
79
+ */
80
+ createLazyProxy<T extends object>(resolveInstance: () => T): T {
81
+ let instance: T | undefined;
82
+ const state: ProxyState<T> = {
83
+ getTarget: () => {
84
+ instance = instance ?? this.unwrap(resolveInstance());
85
+ return instance;
86
+ },
87
+ };
88
+
89
+ const proxy = new Proxy(
90
+ {},
91
+ {
92
+ get: (_, prop) => {
93
+ const target = state.getTarget();
94
+ // @ts-ignore
95
+ return target[prop];
96
+ },
97
+ // The proxy stands in for the target, so writes must reach it. Without this
98
+ // the empty object above would absorb them - including writes a method makes
99
+ // to `this` when it is called through the proxy.
100
+ set: (_, prop, value) => {
101
+ const target = state.getTarget();
102
+ // @ts-ignore
103
+ target[prop] = value;
104
+ return true;
105
+ },
106
+ },
107
+ ) as T;
108
+
109
+ return this.register(proxy, state);
110
+ }
111
+
112
+ /**
113
+ * Wraps `resolveInstance` in a lazy proxy when `isLazy`, otherwise calls it
114
+ * right away.
115
+ */
116
+ toLazyIf<T extends object>(resolveInstance: () => T, isLazy: boolean = false): T {
117
+ return isLazy ? this.createLazyProxy(resolveInstance) : resolveInstance();
118
+ }
119
+
120
+ private isProxy(value: object): boolean {
121
+ return this.states.has(value);
122
+ }
123
+
124
+ private getTarget<T extends object>(value: T): T {
125
+ const target = this.states.get(value)!.getTarget() as T;
126
+ return this.unwrap(target);
127
+ }
128
+
129
+ private register<T extends object>(proxy: T, state: ProxyState<T>): T {
130
+ this.states.set(proxy, state as ProxyState<object>);
131
+ return proxy;
132
+ }
133
+ }
134
+
135
+ /**
136
+ * The real object behind `value`, unwrapping proxies stacked to any depth. A
137
+ * value that is not a proxy is returned as is.
138
+ *
139
+ * A shortcut for `ProxyRegistry.getInstance().unwrap(value)` - the registry is a
140
+ * process-wide singleton, so unwrapping never needs an instance of its own. The
141
+ * library already unwraps wherever a proxy would give the wrong answer (reading
142
+ * class metadata, and `IContainer.hasInstance`), so reach for this only when
143
+ * your own code needs the real object - an identity check of your own, or
144
+ * bypassing a lazy proxy on purpose.
145
+ *
146
+ * Note that unwrapping a lazy proxy resolves its target, which is the point:
147
+ * there is no real object to hand back until it does.
148
+ */
149
+ export const unwrapProxy = <T extends object>(value: T): T => ProxyRegistry.getInstance().unwrap(value);