dependency-container.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. import { __awaiter } from "tslib";
  2. import { isClassProvider, isFactoryProvider, isNormalToken, isTokenProvider, isValueProvider } from "./providers";
  3. import { isProvider } from "./providers/provider";
  4. import { isConstructorToken, isTokenDescriptor, isTransformDescriptor } from "./providers/injection-token";
  5. import Registry from "./registry";
  6. import Lifecycle from "./types/lifecycle";
  7. import ResolutionContext from "./resolution-context";
  8. import { formatErrorCtor } from "./error-helpers";
  9. import { DelayedConstructor } from "./lazy-helpers";
  10. import { isDisposable } from "./types/disposable";
  11. import Interceptors from "./interceptors";
  12. export const typeInfo = new Map();
  13. class InternalDependencyContainer {
  14. constructor(parent) {
  15. this.parent = parent;
  16. this._registry = new Registry();
  17. this.interceptors = new Interceptors();
  18. this.disposed = false;
  19. this.disposables = new Set();
  20. }
  21. register(token, providerOrConstructor, options = { lifecycle: Lifecycle.Transient }) {
  22. this.ensureNotDisposed();
  23. let provider;
  24. if (!isProvider(providerOrConstructor)) {
  25. provider = { useClass: providerOrConstructor };
  26. }
  27. else {
  28. provider = providerOrConstructor;
  29. }
  30. if (isTokenProvider(provider)) {
  31. const path = [token];
  32. let tokenProvider = provider;
  33. while (tokenProvider != null) {
  34. const currentToken = tokenProvider.useToken;
  35. if (path.includes(currentToken)) {
  36. throw new Error(`Token registration cycle detected! ${[...path, currentToken].join(" -> ")}`);
  37. }
  38. path.push(currentToken);
  39. const registration = this._registry.get(currentToken);
  40. if (registration && isTokenProvider(registration.provider)) {
  41. tokenProvider = registration.provider;
  42. }
  43. else {
  44. tokenProvider = null;
  45. }
  46. }
  47. }
  48. if (options.lifecycle === Lifecycle.Singleton ||
  49. options.lifecycle == Lifecycle.ContainerScoped ||
  50. options.lifecycle == Lifecycle.ResolutionScoped) {
  51. if (isValueProvider(provider) || isFactoryProvider(provider)) {
  52. throw new Error(`Cannot use lifecycle "${Lifecycle[options.lifecycle]}" with ValueProviders or FactoryProviders`);
  53. }
  54. }
  55. this._registry.set(token, { provider, options });
  56. return this;
  57. }
  58. registerType(from, to) {
  59. this.ensureNotDisposed();
  60. if (isNormalToken(to)) {
  61. return this.register(from, {
  62. useToken: to
  63. });
  64. }
  65. return this.register(from, {
  66. useClass: to
  67. });
  68. }
  69. registerInstance(token, instance) {
  70. this.ensureNotDisposed();
  71. return this.register(token, {
  72. useValue: instance
  73. });
  74. }
  75. registerSingleton(from, to) {
  76. this.ensureNotDisposed();
  77. if (isNormalToken(from)) {
  78. if (isNormalToken(to)) {
  79. return this.register(from, {
  80. useToken: to
  81. }, { lifecycle: Lifecycle.Singleton });
  82. }
  83. else if (to) {
  84. return this.register(from, {
  85. useClass: to
  86. }, { lifecycle: Lifecycle.Singleton });
  87. }
  88. throw new Error('Cannot register a type name as a singleton without a "to" token');
  89. }
  90. let useClass = from;
  91. if (to && !isNormalToken(to)) {
  92. useClass = to;
  93. }
  94. return this.register(from, {
  95. useClass
  96. }, { lifecycle: Lifecycle.Singleton });
  97. }
  98. resolve(token, context = new ResolutionContext(), isOptional = false) {
  99. this.ensureNotDisposed();
  100. const registration = this.getRegistration(token);
  101. if (!registration && isNormalToken(token)) {
  102. if (isOptional) {
  103. return undefined;
  104. }
  105. throw new Error(`Attempted to resolve unregistered dependency token: "${token.toString()}"`);
  106. }
  107. this.executePreResolutionInterceptor(token, "Single");
  108. if (registration) {
  109. const result = this.resolveRegistration(registration, context);
  110. this.executePostResolutionInterceptor(token, result, "Single");
  111. return result;
  112. }
  113. if (isConstructorToken(token)) {
  114. const result = this.construct(token, context);
  115. this.executePostResolutionInterceptor(token, result, "Single");
  116. return result;
  117. }
  118. throw new Error("Attempted to construct an undefined constructor. Could mean a circular dependency problem. Try using `delay` function.");
  119. }
  120. executePreResolutionInterceptor(token, resolutionType) {
  121. if (this.interceptors.preResolution.has(token)) {
  122. const remainingInterceptors = [];
  123. for (const interceptor of this.interceptors.preResolution.getAll(token)) {
  124. if (interceptor.options.frequency != "Once") {
  125. remainingInterceptors.push(interceptor);
  126. }
  127. interceptor.callback(token, resolutionType);
  128. }
  129. this.interceptors.preResolution.setAll(token, remainingInterceptors);
  130. }
  131. }
  132. executePostResolutionInterceptor(token, result, resolutionType) {
  133. if (this.interceptors.postResolution.has(token)) {
  134. const remainingInterceptors = [];
  135. for (const interceptor of this.interceptors.postResolution.getAll(token)) {
  136. if (interceptor.options.frequency != "Once") {
  137. remainingInterceptors.push(interceptor);
  138. }
  139. interceptor.callback(token, result, resolutionType);
  140. }
  141. this.interceptors.postResolution.setAll(token, remainingInterceptors);
  142. }
  143. }
  144. resolveRegistration(registration, context) {
  145. this.ensureNotDisposed();
  146. if (registration.options.lifecycle === Lifecycle.ResolutionScoped &&
  147. context.scopedResolutions.has(registration)) {
  148. return context.scopedResolutions.get(registration);
  149. }
  150. const isSingleton = registration.options.lifecycle === Lifecycle.Singleton;
  151. const isContainerScoped = registration.options.lifecycle === Lifecycle.ContainerScoped;
  152. const returnInstance = isSingleton || isContainerScoped;
  153. let resolved;
  154. if (isValueProvider(registration.provider)) {
  155. resolved = registration.provider.useValue;
  156. }
  157. else if (isTokenProvider(registration.provider)) {
  158. resolved = returnInstance
  159. ? registration.instance ||
  160. (registration.instance = this.resolve(registration.provider.useToken, context))
  161. : this.resolve(registration.provider.useToken, context);
  162. }
  163. else if (isClassProvider(registration.provider)) {
  164. resolved = returnInstance
  165. ? registration.instance ||
  166. (registration.instance = this.construct(registration.provider.useClass, context))
  167. : this.construct(registration.provider.useClass, context);
  168. }
  169. else if (isFactoryProvider(registration.provider)) {
  170. resolved = registration.provider.useFactory(this);
  171. }
  172. else {
  173. resolved = this.construct(registration.provider, context);
  174. }
  175. if (registration.options.lifecycle === Lifecycle.ResolutionScoped) {
  176. context.scopedResolutions.set(registration, resolved);
  177. }
  178. return resolved;
  179. }
  180. resolveAll(token, context = new ResolutionContext(), isOptional = false) {
  181. this.ensureNotDisposed();
  182. const registrations = this.getAllRegistrations(token);
  183. if (!registrations && isNormalToken(token)) {
  184. if (isOptional) {
  185. return [];
  186. }
  187. throw new Error(`Attempted to resolve unregistered dependency token: "${token.toString()}"`);
  188. }
  189. this.executePreResolutionInterceptor(token, "All");
  190. if (registrations) {
  191. const result = registrations.map(item => this.resolveRegistration(item, context));
  192. this.executePostResolutionInterceptor(token, result, "All");
  193. return result;
  194. }
  195. const result = [this.construct(token, context)];
  196. this.executePostResolutionInterceptor(token, result, "All");
  197. return result;
  198. }
  199. isRegistered(token, recursive = false) {
  200. this.ensureNotDisposed();
  201. return (this._registry.has(token) ||
  202. (recursive &&
  203. (this.parent || false) &&
  204. this.parent.isRegistered(token, true)));
  205. }
  206. reset() {
  207. this.ensureNotDisposed();
  208. this._registry.clear();
  209. this.interceptors.preResolution.clear();
  210. this.interceptors.postResolution.clear();
  211. }
  212. clearInstances() {
  213. this.ensureNotDisposed();
  214. for (const [token, registrations] of this._registry.entries()) {
  215. this._registry.setAll(token, registrations
  216. .filter(registration => !isValueProvider(registration.provider))
  217. .map(registration => {
  218. registration.instance = undefined;
  219. return registration;
  220. }));
  221. }
  222. }
  223. createChildContainer() {
  224. this.ensureNotDisposed();
  225. const childContainer = new InternalDependencyContainer(this);
  226. for (const [token, registrations] of this._registry.entries()) {
  227. if (registrations.some(({ options }) => options.lifecycle === Lifecycle.ContainerScoped)) {
  228. childContainer._registry.setAll(token, registrations.map(registration => {
  229. if (registration.options.lifecycle === Lifecycle.ContainerScoped) {
  230. return {
  231. provider: registration.provider,
  232. options: registration.options
  233. };
  234. }
  235. return registration;
  236. }));
  237. }
  238. }
  239. return childContainer;
  240. }
  241. beforeResolution(token, callback, options = { frequency: "Always" }) {
  242. this.interceptors.preResolution.set(token, {
  243. callback: callback,
  244. options: options
  245. });
  246. }
  247. afterResolution(token, callback, options = { frequency: "Always" }) {
  248. this.interceptors.postResolution.set(token, {
  249. callback: callback,
  250. options: options
  251. });
  252. }
  253. dispose() {
  254. return __awaiter(this, void 0, void 0, function* () {
  255. this.disposed = true;
  256. const promises = [];
  257. this.disposables.forEach(disposable => {
  258. const maybePromise = disposable.dispose();
  259. if (maybePromise) {
  260. promises.push(maybePromise);
  261. }
  262. });
  263. yield Promise.all(promises);
  264. });
  265. }
  266. getRegistration(token) {
  267. if (this.isRegistered(token)) {
  268. return this._registry.get(token);
  269. }
  270. if (this.parent) {
  271. return this.parent.getRegistration(token);
  272. }
  273. return null;
  274. }
  275. getAllRegistrations(token) {
  276. if (this.isRegistered(token)) {
  277. return this._registry.getAll(token);
  278. }
  279. if (this.parent) {
  280. return this.parent.getAllRegistrations(token);
  281. }
  282. return null;
  283. }
  284. construct(ctor, context) {
  285. if (ctor instanceof DelayedConstructor) {
  286. return ctor.createProxy((target) => this.resolve(target, context));
  287. }
  288. const instance = (() => {
  289. const paramInfo = typeInfo.get(ctor);
  290. if (!paramInfo || paramInfo.length === 0) {
  291. if (ctor.length === 0) {
  292. return new ctor();
  293. }
  294. else {
  295. throw new Error(`TypeInfo not known for "${ctor.name}"`);
  296. }
  297. }
  298. const params = paramInfo.map(this.resolveParams(context, ctor));
  299. return new ctor(...params);
  300. })();
  301. if (isDisposable(instance)) {
  302. this.disposables.add(instance);
  303. }
  304. return instance;
  305. }
  306. resolveParams(context, ctor) {
  307. return (param, idx) => {
  308. try {
  309. if (isTokenDescriptor(param)) {
  310. if (isTransformDescriptor(param)) {
  311. return param.multiple
  312. ? this.resolve(param.transform).transform(this.resolveAll(param.token, new ResolutionContext(), param.isOptional), ...param.transformArgs)
  313. : this.resolve(param.transform).transform(this.resolve(param.token, context, param.isOptional), ...param.transformArgs);
  314. }
  315. else {
  316. return param.multiple
  317. ? this.resolveAll(param.token, new ResolutionContext(), param.isOptional)
  318. : this.resolve(param.token, context, param.isOptional);
  319. }
  320. }
  321. else if (isTransformDescriptor(param)) {
  322. return this.resolve(param.transform, context).transform(this.resolve(param.token, context), ...param.transformArgs);
  323. }
  324. return this.resolve(param, context);
  325. }
  326. catch (e) {
  327. throw new Error(formatErrorCtor(ctor, idx, e));
  328. }
  329. };
  330. }
  331. ensureNotDisposed() {
  332. if (this.disposed) {
  333. throw new Error("This container has been disposed, you cannot interact with a disposed container");
  334. }
  335. }
  336. }
  337. export const instance = new InternalDependencyContainer();
  338. export default instance;