ts-ioc-container 72.1.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 (228) hide show
  1. package/AGENTS.md +179 -0
  2. package/cjm/container/Container.js +2 -2
  3. package/cjm/container/EmptyContainer.js +17 -15
  4. package/cjm/errors/ArgumentNotFoundError.js +1 -0
  5. package/cjm/errors/CannonSingletonApplyTwiceError.js +2 -14
  6. package/cjm/errors/CannotApplySingletonTwiceError.js +18 -0
  7. package/cjm/errors/ContainerDisposedError.js +1 -0
  8. package/cjm/errors/ContainerError.js +1 -0
  9. package/cjm/errors/ContainerNotFoundError.js +1 -0
  10. package/cjm/errors/DependencyMissingKeyError.js +1 -0
  11. package/cjm/errors/DependencyNotFoundError.js +1 -0
  12. package/cjm/errors/MethodNotImplementedError.js +1 -0
  13. package/cjm/errors/ProviderDisposedError.js +1 -0
  14. package/cjm/errors/TypedEventDisposedError.js +1 -0
  15. package/cjm/errors/UnsupportedTokenTypeError.js +1 -0
  16. package/cjm/index.js +5 -3
  17. package/cjm/provider/Provider.js +6 -6
  18. package/cjm/registration/Registration.js +2 -2
  19. package/cjm/token/ConstantToken.js +4 -4
  20. package/cjm/token/FunctionToken.js +1 -1
  21. package/cjm/token/GroupInstanceToken.js +4 -4
  22. package/cjm/token/toToken.js +1 -1
  23. package/cjm/utils/TypedEvent.js +1 -1
  24. package/cjm/utils/array.js +1 -1
  25. package/esm/container/Container.js +2 -2
  26. package/esm/container/EmptyContainer.js +17 -15
  27. package/esm/errors/ArgumentNotFoundError.js +1 -0
  28. package/esm/errors/CannonSingletonApplyTwiceError.js +2 -13
  29. package/esm/errors/CannotApplySingletonTwiceError.js +14 -0
  30. package/esm/errors/ContainerDisposedError.js +1 -0
  31. package/esm/errors/ContainerError.js +1 -0
  32. package/esm/errors/ContainerNotFoundError.js +1 -0
  33. package/esm/errors/DependencyMissingKeyError.js +1 -0
  34. package/esm/errors/DependencyNotFoundError.js +1 -0
  35. package/esm/errors/MethodNotImplementedError.js +1 -0
  36. package/esm/errors/ProviderDisposedError.js +1 -0
  37. package/esm/errors/TypedEventDisposedError.js +1 -0
  38. package/esm/errors/UnsupportedTokenTypeError.js +1 -0
  39. package/esm/index.js +1 -0
  40. package/esm/provider/Provider.js +6 -6
  41. package/esm/registration/Registration.js +2 -2
  42. package/esm/token/ConstantToken.js +4 -4
  43. package/esm/token/FunctionToken.js +1 -1
  44. package/esm/token/GroupInstanceToken.js +4 -4
  45. package/esm/token/toToken.js +1 -1
  46. package/esm/utils/TypedEvent.js +1 -1
  47. package/esm/utils/array.js +1 -1
  48. package/lib/ExecutionContext.ts +6 -0
  49. package/lib/container/AliasMap.ts +30 -0
  50. package/lib/container/AutoResolveModule.ts +24 -0
  51. package/lib/container/Container.ts +311 -0
  52. package/lib/container/EmptyContainer.ts +166 -0
  53. package/lib/container/IContainer.ts +156 -0
  54. package/lib/errors/ArgumentNotFoundError.ts +13 -0
  55. package/lib/errors/CannonSingletonApplyTwiceError.ts +6 -0
  56. package/lib/errors/CannotApplySingletonTwiceError.ts +22 -0
  57. package/lib/errors/ContainerDisposedError.ts +13 -0
  58. package/lib/errors/ContainerError.ts +15 -0
  59. package/lib/errors/ContainerNotFoundError.ts +13 -0
  60. package/lib/errors/DependencyMissingKeyError.ts +13 -0
  61. package/lib/errors/DependencyNotFoundError.ts +13 -0
  62. package/lib/errors/MethodNotImplementedError.ts +13 -0
  63. package/lib/errors/ProviderDisposedError.ts +22 -0
  64. package/lib/errors/TypedEventDisposedError.ts +22 -0
  65. package/lib/errors/UnsupportedTokenTypeError.ts +13 -0
  66. package/lib/hooks/HookCollector.ts +109 -0
  67. package/lib/hooks/HookContext.ts +85 -0
  68. package/lib/hooks/combinators.ts +51 -0
  69. package/lib/hooks/hook.ts +89 -0
  70. package/lib/hooks/injectProp.ts +14 -0
  71. package/lib/index.ts +181 -0
  72. package/lib/injector/IInjector.ts +88 -0
  73. package/lib/injector/MetadataInjector.ts +100 -0
  74. package/lib/injector/ProxyInjector.ts +31 -0
  75. package/lib/injector/SimpleInjector.ts +20 -0
  76. package/lib/metadata/class.ts +50 -0
  77. package/lib/metadata/method.ts +47 -0
  78. package/lib/metadata/parameter.ts +60 -0
  79. package/lib/metadata/target.ts +25 -0
  80. package/lib/provider/IProvider.ts +65 -0
  81. package/lib/provider/Provider.ts +174 -0
  82. package/lib/registration/IRegistration.ts +235 -0
  83. package/lib/registration/Registration.ts +122 -0
  84. package/lib/select.ts +33 -0
  85. package/lib/token/BindToken.ts +13 -0
  86. package/lib/token/ClassToken.ts +72 -0
  87. package/lib/token/ConstantToken.ts +50 -0
  88. package/lib/token/FunctionToken.ts +79 -0
  89. package/lib/token/GroupAliasToken.ts +92 -0
  90. package/lib/token/GroupInstanceToken.ts +70 -0
  91. package/lib/token/InjectionToken.ts +46 -0
  92. package/lib/token/SingleAliasToken.ts +84 -0
  93. package/lib/token/SingleToken.ts +93 -0
  94. package/lib/token/toToken.ts +46 -0
  95. package/lib/utils/ProxyRegistry.ts +149 -0
  96. package/lib/utils/TypedEvent.ts +96 -0
  97. package/lib/utils/array.ts +28 -0
  98. package/lib/utils/basic.ts +34 -0
  99. package/lib/utils/debounce.ts +18 -0
  100. package/lib/utils/errorHandler.ts +34 -0
  101. package/lib/utils/fp.ts +86 -0
  102. package/lib/utils/getConstructorChain.ts +17 -0
  103. package/lib/utils/memoize.ts +24 -0
  104. package/lib/utils/once.ts +24 -0
  105. package/lib/utils/shallowCache.ts +22 -0
  106. package/lib/utils/task.ts +33 -0
  107. package/lib/utils/throttle.ts +17 -0
  108. package/package.json +5 -3
  109. package/typings/ExecutionContext.d.ts +2 -0
  110. package/typings/ExecutionContext.d.ts.map +1 -0
  111. package/typings/container/AliasMap.d.ts +1 -0
  112. package/typings/container/AliasMap.d.ts.map +1 -0
  113. package/typings/container/AutoResolveModule.d.ts +16 -0
  114. package/typings/container/AutoResolveModule.d.ts.map +1 -0
  115. package/typings/container/Container.d.ts +60 -0
  116. package/typings/container/Container.d.ts.map +1 -0
  117. package/typings/container/EmptyContainer.d.ts +51 -0
  118. package/typings/container/EmptyContainer.d.ts.map +1 -0
  119. package/typings/container/IContainer.d.ts +75 -0
  120. package/typings/container/IContainer.d.ts.map +1 -0
  121. package/typings/errors/ArgumentNotFoundError.d.ts +3 -0
  122. package/typings/errors/ArgumentNotFoundError.d.ts.map +1 -0
  123. package/typings/errors/CannonSingletonApplyTwiceError.d.ts +6 -6
  124. package/typings/errors/CannonSingletonApplyTwiceError.d.ts.map +1 -0
  125. package/typings/errors/CannotApplySingletonTwiceError.d.ts +12 -0
  126. package/typings/errors/CannotApplySingletonTwiceError.d.ts.map +1 -0
  127. package/typings/errors/ContainerDisposedError.d.ts +3 -0
  128. package/typings/errors/ContainerDisposedError.d.ts.map +1 -0
  129. package/typings/errors/ContainerError.d.ts +7 -0
  130. package/typings/errors/ContainerError.d.ts.map +1 -0
  131. package/typings/errors/ContainerNotFoundError.d.ts +3 -0
  132. package/typings/errors/ContainerNotFoundError.d.ts.map +1 -0
  133. package/typings/errors/DependencyMissingKeyError.d.ts +3 -0
  134. package/typings/errors/DependencyMissingKeyError.d.ts.map +1 -0
  135. package/typings/errors/DependencyNotFoundError.d.ts +3 -0
  136. package/typings/errors/DependencyNotFoundError.d.ts.map +1 -0
  137. package/typings/errors/MethodNotImplementedError.d.ts +3 -0
  138. package/typings/errors/MethodNotImplementedError.d.ts.map +1 -0
  139. package/typings/errors/ProviderDisposedError.d.ts +6 -0
  140. package/typings/errors/ProviderDisposedError.d.ts.map +1 -0
  141. package/typings/errors/TypedEventDisposedError.d.ts +6 -0
  142. package/typings/errors/TypedEventDisposedError.d.ts.map +1 -0
  143. package/typings/errors/UnsupportedTokenTypeError.d.ts +3 -0
  144. package/typings/errors/UnsupportedTokenTypeError.d.ts.map +1 -0
  145. package/typings/hooks/HookCollector.d.ts +36 -0
  146. package/typings/hooks/HookCollector.d.ts.map +1 -0
  147. package/typings/hooks/HookContext.d.ts +10 -0
  148. package/typings/hooks/HookContext.d.ts.map +1 -0
  149. package/typings/hooks/combinators.d.ts +23 -0
  150. package/typings/hooks/combinators.d.ts.map +1 -0
  151. package/typings/hooks/hook.d.ts +39 -0
  152. package/typings/hooks/hook.d.ts.map +1 -0
  153. package/typings/hooks/injectProp.d.ts +9 -0
  154. package/typings/hooks/injectProp.d.ts.map +1 -0
  155. package/typings/index.d.ts +2 -0
  156. package/typings/index.d.ts.map +1 -0
  157. package/typings/injector/IInjector.d.ts +32 -0
  158. package/typings/injector/IInjector.d.ts.map +1 -0
  159. package/typings/injector/MetadataInjector.d.ts +58 -0
  160. package/typings/injector/MetadataInjector.d.ts.map +1 -0
  161. package/typings/injector/ProxyInjector.d.ts +13 -0
  162. package/typings/injector/ProxyInjector.d.ts.map +1 -0
  163. package/typings/injector/SimpleInjector.d.ts +13 -0
  164. package/typings/injector/SimpleInjector.d.ts.map +1 -0
  165. package/typings/metadata/class.d.ts +27 -0
  166. package/typings/metadata/class.d.ts.map +1 -0
  167. package/typings/metadata/method.d.ts +24 -0
  168. package/typings/metadata/method.d.ts.map +1 -0
  169. package/typings/metadata/parameter.d.ts +21 -0
  170. package/typings/metadata/parameter.d.ts.map +1 -0
  171. package/typings/metadata/target.d.ts +19 -0
  172. package/typings/metadata/target.d.ts.map +1 -0
  173. package/typings/provider/IProvider.d.ts +25 -0
  174. package/typings/provider/IProvider.d.ts.map +1 -0
  175. package/typings/provider/Provider.d.ts +30 -0
  176. package/typings/provider/Provider.d.ts.map +1 -0
  177. package/typings/registration/IRegistration.d.ts +137 -0
  178. package/typings/registration/IRegistration.d.ts.map +1 -0
  179. package/typings/registration/Registration.d.ts +23 -0
  180. package/typings/registration/Registration.d.ts.map +1 -0
  181. package/typings/select.d.ts +15 -0
  182. package/typings/select.d.ts.map +1 -0
  183. package/typings/token/BindToken.d.ts +3 -0
  184. package/typings/token/BindToken.d.ts.map +1 -0
  185. package/typings/token/ClassToken.d.ts +2 -0
  186. package/typings/token/ClassToken.d.ts.map +1 -0
  187. package/typings/token/ConstantToken.d.ts +14 -0
  188. package/typings/token/ConstantToken.d.ts.map +1 -0
  189. package/typings/token/FunctionToken.d.ts +10 -0
  190. package/typings/token/FunctionToken.d.ts.map +1 -0
  191. package/typings/token/GroupAliasToken.d.ts +14 -0
  192. package/typings/token/GroupAliasToken.d.ts.map +1 -0
  193. package/typings/token/GroupInstanceToken.d.ts +19 -0
  194. package/typings/token/GroupInstanceToken.d.ts.map +1 -0
  195. package/typings/token/InjectionToken.d.ts +8 -0
  196. package/typings/token/InjectionToken.d.ts.map +1 -0
  197. package/typings/token/SingleAliasToken.d.ts +6 -0
  198. package/typings/token/SingleAliasToken.d.ts.map +1 -0
  199. package/typings/token/SingleToken.d.ts +18 -0
  200. package/typings/token/SingleToken.d.ts.map +1 -0
  201. package/typings/token/toToken.d.ts +12 -0
  202. package/typings/token/toToken.d.ts.map +1 -0
  203. package/typings/utils/ProxyRegistry.d.ts +63 -0
  204. package/typings/utils/ProxyRegistry.d.ts.map +1 -0
  205. package/typings/utils/TypedEvent.d.ts +42 -0
  206. package/typings/utils/TypedEvent.d.ts.map +1 -0
  207. package/typings/utils/array.d.ts +7 -0
  208. package/typings/utils/array.d.ts.map +1 -0
  209. package/typings/utils/basic.d.ts +12 -0
  210. package/typings/utils/basic.d.ts.map +1 -0
  211. package/typings/utils/debounce.d.ts +2 -0
  212. package/typings/utils/debounce.d.ts.map +1 -0
  213. package/typings/utils/errorHandler.d.ts +4 -0
  214. package/typings/utils/errorHandler.d.ts.map +1 -0
  215. package/typings/utils/fp.d.ts +8 -0
  216. package/typings/utils/fp.d.ts.map +1 -0
  217. package/typings/utils/getConstructorChain.d.ts +9 -0
  218. package/typings/utils/getConstructorChain.d.ts.map +1 -0
  219. package/typings/utils/memoize.d.ts +10 -0
  220. package/typings/utils/memoize.d.ts.map +1 -0
  221. package/typings/utils/once.d.ts +2 -0
  222. package/typings/utils/once.d.ts.map +1 -0
  223. package/typings/utils/shallowCache.d.ts +2 -0
  224. package/typings/utils/shallowCache.d.ts.map +1 -0
  225. package/typings/utils/task.d.ts +13 -0
  226. package/typings/utils/task.d.ts.map +1 -0
  227. package/typings/utils/throttle.d.ts +2 -0
  228. package/typings/utils/throttle.d.ts.map +1 -0
@@ -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);
@@ -0,0 +1,96 @@
1
+ import { TypedEventDisposedError } from '../errors/TypedEventDisposedError';
2
+
3
+ export type TypedEventListener<TArgs extends unknown[]> = (...args: TArgs) => void;
4
+
5
+ export type Unsubscribe = () => void;
6
+
7
+ /**
8
+ * The subscriber's side of a {@link TypedEvent}: attach, detach, tear down.
9
+ * Hand this out instead of the event itself so that only the owner can `emit`.
10
+ */
11
+ export interface ITypedEvent<TArgs extends unknown[]> {
12
+ /**
13
+ * Attaches `listener` and returns a function which detaches it again. A
14
+ * listener subscribed more than once is still delivered each emission once.
15
+ *
16
+ * @throws {TypedEventDisposedError} when the event has already been disposed.
17
+ */
18
+ subscribe(listener: TypedEventListener<TArgs>): Unsubscribe;
19
+
20
+ /**
21
+ * Detaches `listener`. Harmless when it is not subscribed, or after `dispose`.
22
+ */
23
+ unsubscribe(listener: TypedEventListener<TArgs>): void;
24
+
25
+ /**
26
+ * Detaches every listener and rejects further `subscribe` / `emit` calls.
27
+ */
28
+ dispose(): void;
29
+ }
30
+
31
+ /**
32
+ * A typed, multi-listener event. `TArgs` is the argument list a listener
33
+ * receives, so a `TypedEvent<[IContainer]>` accepts `(scope: IContainer) => void`
34
+ * listeners unchanged.
35
+ *
36
+ * Emission delivers to the listeners subscribed at the moment `emit` is called,
37
+ * in subscription order. A listener detached mid-emission is skipped for the
38
+ * rest of that emission; one attached mid-emission receives only later ones.
39
+ */
40
+ export class TypedEvent<TArgs extends unknown[] = []> implements ITypedEvent<TArgs> {
41
+ private readonly listeners = new Set<TypedEventListener<TArgs>>();
42
+ private isDisposed = false;
43
+
44
+ /**
45
+ * @throws {TypedEventDisposedError} when the event has already been disposed.
46
+ */
47
+ subscribe(listener: TypedEventListener<TArgs>): Unsubscribe {
48
+ this.validate();
49
+ this.listeners.add(listener);
50
+ return () => this.unsubscribe(listener);
51
+ }
52
+
53
+ unsubscribe(listener: TypedEventListener<TArgs>): void {
54
+ this.listeners.delete(listener);
55
+ }
56
+
57
+ /**
58
+ * Calls every current listener with `args`. What a listener throws surfaces
59
+ * out of `emit` and stops delivery to the listeners after it.
60
+ *
61
+ * @throws {TypedEventDisposedError} when the event has already been disposed.
62
+ * @throws {unknown} rethrows whatever a listener threw.
63
+ */
64
+ emit(...args: TArgs): void {
65
+ this.validate();
66
+ // Iterate a snapshot so a listener can (un)subscribe without disturbing this emission;
67
+ // a listener detached mid-emission is still skipped for the rest of it.
68
+ for (const listener of [...this.listeners]) {
69
+ if (this.listeners.has(listener)) {
70
+ listener(...args);
71
+ }
72
+ }
73
+ }
74
+
75
+ /**
76
+ * A copy of the current listeners, in subscription order.
77
+ */
78
+ getListeners(): TypedEventListener<TArgs>[] {
79
+ return [...this.listeners];
80
+ }
81
+
82
+ dispose(): void {
83
+ this.listeners.clear();
84
+ this.isDisposed = true;
85
+ }
86
+
87
+ /**
88
+ * @throws {TypedEventDisposedError} when the event has already been disposed.
89
+ */
90
+ private validate(): void {
91
+ TypedEventDisposedError.assert(
92
+ !this.isDisposed,
93
+ 'TypedEvent is already disposed: its scope was disposed, so it accepts no new listeners.',
94
+ );
95
+ }
96
+ }
@@ -0,0 +1,28 @@
1
+ import { ArgumentNotFoundError } from '../errors/ArgumentNotFoundError';
2
+
3
+ export type Predicate<T> = (value: T) => boolean;
4
+
5
+ export const Filter = {
6
+ exclude: <T>(arr: Set<T> | T[]) => {
7
+ const excludeSet = arr instanceof Array ? new Set(arr) : arr;
8
+ return (v: T) => !excludeSet.has(v);
9
+ },
10
+ };
11
+
12
+ /**
13
+ * Returns a picker of the first argument in `args` matching `predicate` - the
14
+ * shape `singleton(getCacheKey)` consumers expect.
15
+ *
16
+ * @throws {ArgumentNotFoundError} when no argument matches `predicate`
17
+ */
18
+ export const findOrFail =
19
+ <T>(predicate: Predicate<T>) =>
20
+ (args: unknown[] = []): T => {
21
+ const index = args.findIndex((arg) => predicate(arg as T));
22
+
23
+ if (index === -1) {
24
+ throw new ArgumentNotFoundError(`No argument matches the predicate (received ${args.length} args).`);
25
+ }
26
+
27
+ return args[index] as T;
28
+ };
@@ -0,0 +1,34 @@
1
+ /** A class that constructs `T`. */
2
+ export type constructor<T> = new (...args: any[]) => T;
3
+
4
+ /** Something with a meaningful `toString()`, e.g. a token used as a cache key. */
5
+ export interface Serializable {
6
+ toString(): string;
7
+ }
8
+
9
+ /**
10
+ * Tags a structural type with a name, so `Instance` reads as its own type in
11
+ * signatures and error messages instead of a bare `object`. The tag is optional
12
+ * on purpose: values stay assignable without a cast.
13
+ */
14
+ export type Branded<TBrand extends string, T> = T & { readonly __brand?: TBrand };
15
+
16
+ /**
17
+ * What the container tracks and reflects on: the object a constructor produced.
18
+ * Deliberately not a constructor signature - an instance is not its own constructor.
19
+ */
20
+ export type Instance = Branded<'Instance', object>;
21
+
22
+ export const Is = {
23
+ nullish: <T>(value: T | undefined | null): value is null | undefined => value === undefined || value === null,
24
+ object: (target: unknown): target is object => target !== null && typeof target === 'object',
25
+ instance: (target: unknown): target is Instance => Object.prototype.hasOwnProperty.call(target, 'constructor'),
26
+ constructor: (target: unknown): target is constructor<unknown> => typeof target === 'function' && !!target.prototype,
27
+ };
28
+
29
+ // Every object inherits `Object.prototype.toString`, so only an overridden one counts as serialization.
30
+ export function isSerializable(target: unknown): target is Serializable {
31
+ return Is.object(target) && typeof target.toString === 'function' && target.toString !== Object.prototype.toString;
32
+ }
33
+
34
+ export const toString = (target: unknown): string => (isSerializable(target) ? target.toString() : String(target));
@@ -0,0 +1,18 @@
1
+ /** Method decorator: delays the call until `ms` passed without another call (per instance). */
2
+ export const debounce =
3
+ (ms: number): MethodDecorator =>
4
+ (target, propertyKey, descriptor: PropertyDescriptor) => {
5
+ const originalMethod = descriptor.value;
6
+ const timerMap = new WeakMap<object, ReturnType<typeof setTimeout>>();
7
+ descriptor.value = function (...args: unknown[]) {
8
+ const prev = timerMap.get(this);
9
+ if (prev !== undefined) {
10
+ clearTimeout(prev);
11
+ }
12
+ timerMap.set(
13
+ this,
14
+ setTimeout(() => originalMethod.apply(this, args), ms),
15
+ );
16
+ };
17
+ return descriptor;
18
+ };