@lppedd/di-wise-neo 0.15.0 → 0.16.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.
@@ -274,18 +274,46 @@ type Provider<Value = any> = ClassProvider<Value & object> | FactoryProvider<Val
274
274
  * Container creation options.
275
275
  */
276
276
  interface ContainerOptions {
277
+ /**
278
+ * The default scope for registrations.
279
+ *
280
+ * @defaultValue Scope.Transient
281
+ */
282
+ readonly defaultScope: Scope;
277
283
  /**
278
284
  * Whether to automatically register an unregistered class when resolving it as a token.
279
285
  *
280
286
  * @defaultValue false
281
287
  */
282
288
  readonly autoRegister: boolean;
289
+ }
290
+ /**
291
+ * Child container creation options.
292
+ */
293
+ interface ChildContainerOptions extends ContainerOptions {
283
294
  /**
284
- * The default scope for registrations.
295
+ * Whether to copy {@link ContainerHook}(s) from the parent container.
285
296
  *
286
- * @defaultValue Scope.Transient
297
+ * @defaultValue true
287
298
  */
288
- readonly defaultScope: Scope;
299
+ readonly copyHooks: boolean;
300
+ }
301
+ /**
302
+ * A hook into the lifecycle of a container-managed value.
303
+ */
304
+ interface ContainerHook {
305
+ /**
306
+ * Called when the container provides a value for a {@link Token}.
307
+ * - For _container_ scoped tokens, it is called only once when the token is first resolved and cached.
308
+ * - For _resolution_ scoped tokens, it is called once per token resolution graph.
309
+ * - For _transient_ scoped tokens, it is called each time the token is resolved,
310
+ * which might mean multiple times per resolution graph.
311
+ */
312
+ readonly onProvide: (value: unknown) => void;
313
+ /**
314
+ * Called when a _container_ scoped value is about to be disposed.
315
+ */
316
+ readonly onDispose: (value: unknown) => void;
289
317
  }
290
318
  /**
291
319
  * Container API.
@@ -308,7 +336,7 @@ interface Container {
308
336
  *
309
337
  * You can pass specific options to override the inherited ones.
310
338
  */
311
- createChild(options?: Partial<ContainerOptions>): Container;
339
+ createChild(options?: Partial<ChildContainerOptions>): Container;
312
340
  /**
313
341
  * Clears and returns all distinct cached values from this container's internal registry.
314
342
  * Values from {@link ValueProvider} registrations are not included, as they are never cached.
@@ -571,6 +599,18 @@ interface Container {
571
599
  * in the container's internal registry.
572
600
  */
573
601
  tryResolveAll<Value>(token: Token<Value>): Value[];
602
+ /**
603
+ * Adds a hook to observe the lifecycle of container-managed values.
604
+ *
605
+ * Does nothing if the hook has already been added.
606
+ */
607
+ addHook(hook: ContainerHook): void;
608
+ /**
609
+ * Removes a previously added hook.
610
+ *
611
+ * Does nothing if the hook has not been added yet.
612
+ */
613
+ removeHook(hook: ContainerHook): void;
574
614
  /**
575
615
  * Disposes this container and all its cached values.
576
616
  *
@@ -938,6 +978,15 @@ declare function injectAll<Instance extends object>(Class: Constructor<Instance>
938
978
  */
939
979
  declare function injectAll<Value>(token: Token<Value>): Value[];
940
980
 
981
+ /**
982
+ * Asserts that the current stack frame is within an injection context,
983
+ * meaning it has access to injection functions (`inject`, `optional`, etc.).
984
+ *
985
+ * @param fn The function performing the assertion, or a string name used in the error message.
986
+ * @throws {Error} If the current stack frame is not within an injection context.
987
+ */
988
+ declare function assertInjectionContext(fn: Function | string): void;
989
+
941
990
  /**
942
991
  * Injector API.
943
992
  */
@@ -1147,5 +1196,5 @@ declare function optionalAll<Instance extends object>(Class: Constructor<Instanc
1147
1196
  */
1148
1197
  declare function optionalAll<Value>(token: Token<Value>): Value[];
1149
1198
 
1150
- export { AutoRegister, EagerInstantiate, Inject, InjectAll, Injectable, Injector, Named, Optional, OptionalAll, Scope, Scoped, applyMiddleware, build, classRef, createContainer, createType, inject, injectAll, injectBy, optional, optionalAll, optionalBy, setClassIdentityMapping, tokenRef };
1151
- export type { ClassProvider, ClassRef, Constructor, Container, ContainerOptions, ExistingProvider, FactoryProvider, Middleware, MiddlewareComposer, Provider, ProviderType, RegistrationOptions, Token, TokenRef, Tokens, TokensRef, Type, ValueProvider };
1199
+ export { AutoRegister, EagerInstantiate, Inject, InjectAll, Injectable, Injector, Named, Optional, OptionalAll, Scope, Scoped, applyMiddleware, assertInjectionContext, build, classRef, createContainer, createType, inject, injectAll, injectBy, optional, optionalAll, optionalBy, setClassIdentityMapping, tokenRef };
1200
+ export type { ChildContainerOptions, ClassProvider, ClassRef, Constructor, Container, ContainerHook, ContainerOptions, ExistingProvider, FactoryProvider, Middleware, MiddlewareComposer, Provider, ProviderType, RegistrationOptions, Token, TokenRef, Tokens, TokensRef, Type, ValueProvider };
package/dist/es/index.mjs CHANGED
@@ -32,7 +32,7 @@ function throwResolutionError(tokenInfo, aliases, cause) {
32
32
  function throwParameterResolutionError(ctor, methodKey, dependency, cause) {
33
33
  const location = getLocation(ctor, methodKey);
34
34
  const tokenName = getFullTokenName([
35
- dependency.tokenRef.getRefToken(),
35
+ dependency.tokenRef?.getRefToken(),
36
36
  dependency.name
37
37
  ]);
38
38
  const msg = tag(`failed to resolve dependency for ${location}(parameter #${dependency.index}: ${tokenName})`);
@@ -54,7 +54,7 @@ function getTokenName(token) {
54
54
  return token.name || "<unnamed>";
55
55
  }
56
56
  function getFullTokenName([token, name]) {
57
- const tokenName = token.name || "<unnamed>";
57
+ const tokenName = token ? token.name || "<unnamed>" : "<undefined token>";
58
58
  return name ? `${tokenName}["${name}"]` : tokenName;
59
59
  }
60
60
  function getCause(error) {
@@ -142,6 +142,16 @@ function ensureInjectionContext(name) {
142
142
  check(context, `${name} can only be invoked within an injection context`);
143
143
  return context;
144
144
  }
145
+ /**
146
+ * Asserts that the current stack frame is within an injection context,
147
+ * meaning it has access to injection functions (`inject`, `optional`, etc.).
148
+ *
149
+ * @param fn The function performing the assertion, or a string name used in the error message.
150
+ * @throws {Error} If the current stack frame is not within an injection context.
151
+ */ function assertInjectionContext(fn) {
152
+ const name = typeof fn === "function" ? `${fn.name || "<unnamed>"}()` : fn;
153
+ ensureInjectionContext(name);
154
+ }
145
155
  function createInjectionContext() {
146
156
  let current = null;
147
157
  function provide(next) {
@@ -214,15 +224,15 @@ function tokenRef(token) {
214
224
  }
215
225
  // @internal
216
226
  function isClassRef(value) {
217
- return value && typeof value === "object" && typeof value.getRefClass === "function";
227
+ return value != null && typeof value === "object" && typeof value.getRefClass === "function";
218
228
  }
219
229
  // @internal
220
230
  function isTokensRef(value) {
221
- return value && typeof value === "object" && typeof value.getRefTokens === "function";
231
+ return value != null && typeof value === "object" && typeof value.getRefTokens === "function";
222
232
  }
223
233
  // @internal
224
234
  function isTokenRef(value) {
225
- return value && typeof value === "object" && typeof value.getRefToken === "function";
235
+ return value != null && typeof value === "object" && typeof value.getRefToken === "function";
226
236
  }
227
237
 
228
238
  // @internal
@@ -531,21 +541,22 @@ const builders = new WeakSet();
531
541
  // @internal
532
542
  // @internal
533
543
  function isDisposable(value) {
534
- return value && typeof value === "object" && typeof value.dispose === "function";
544
+ return value != null && typeof value === "object" && typeof value.dispose === "function";
535
545
  }
536
546
 
537
547
  /**
538
548
  * The default implementation of a di-wise-neo {@link Container}.
539
549
  */ class ContainerImpl {
540
- constructor(parent, options){
550
+ constructor(parent, hooks, options){
541
551
  this.myChildren = new Set();
542
552
  this.myDisposed = false;
543
553
  this.myParent = parent;
554
+ this.myHooks = hooks ?? new Set();
544
555
  this.myOptions = {
545
- autoRegister: options?.autoRegister ?? false,
546
- defaultScope: options?.defaultScope ?? Scope.Transient
556
+ defaultScope: options?.defaultScope ?? Scope.Transient,
557
+ autoRegister: options?.autoRegister ?? false
547
558
  };
548
- this.myTokenRegistry = new TokenRegistry(this.myParent?.myTokenRegistry);
559
+ this.myTokenRegistry = new TokenRegistry(parent?.myTokenRegistry);
549
560
  }
550
561
  get registry() {
551
562
  return this.myTokenRegistry;
@@ -563,9 +574,10 @@ function isDisposable(value) {
563
574
  }
564
575
  createChild(options) {
565
576
  this.checkDisposed();
566
- const container = new ContainerImpl(this, {
567
- autoRegister: options?.autoRegister ?? this.myOptions.autoRegister,
568
- defaultScope: options?.defaultScope ?? this.myOptions.defaultScope
577
+ const hooks = options?.copyHooks === false ? undefined : new Set(this.myHooks);
578
+ const container = new ContainerImpl(this, hooks, {
579
+ defaultScope: options?.defaultScope ?? this.myOptions.defaultScope,
580
+ autoRegister: options?.autoRegister ?? this.myOptions.autoRegister
569
581
  });
570
582
  this.myChildren.add(container);
571
583
  return container;
@@ -649,6 +661,12 @@ function isDisposable(value) {
649
661
  this.checkDisposed();
650
662
  return this.resolveAllToken(token, true);
651
663
  }
664
+ addHook(hook) {
665
+ this.myHooks.add(hook);
666
+ }
667
+ removeHook(hook) {
668
+ this.myHooks.delete(hook);
669
+ }
652
670
  dispose() {
653
671
  if (this.myDisposed) {
654
672
  return;
@@ -667,12 +685,14 @@ function isDisposable(value) {
667
685
  for (const registration of registrations){
668
686
  const value = registration.value?.current;
669
687
  if (isDisposable(value) && !disposedRefs.has(value)) {
688
+ this.notifyDisposeHooks(value);
670
689
  disposedRefs.add(value);
671
690
  value.dispose();
672
691
  }
673
692
  }
674
693
  // Allow values to be GCed
675
694
  disposedRefs.clear();
695
+ this.myHooks.clear();
676
696
  }
677
697
  registerClass(Class) {
678
698
  const metadata = getMetadata(Class);
@@ -701,7 +721,9 @@ function isDisposable(value) {
701
721
  }
702
722
  });
703
723
  }
704
- // Eager-instantiate only if the class is container-scoped
724
+ // Eager-instantiate only if the class is container-scoped.
725
+ // Note that we are comparing the scope using the registration configured just above,
726
+ // which takes into account both the metadata and the container option as a fallback.
705
727
  if (metadata.eagerInstantiate && registration.options?.scope === Scope.Container) {
706
728
  this.resolveProviderValue(Class, registration);
707
729
  }
@@ -723,7 +745,9 @@ function isDisposable(value) {
723
745
  dependencies: metadata.dependencies
724
746
  };
725
747
  this.myTokenRegistry.set(token, registration);
726
- // Eager-instantiate only if the provided class is container-scoped
748
+ // Eager-instantiate only if the provided class is container-scoped.
749
+ // Note that we are comparing the scope using the registration configured just above,
750
+ // which takes into account both the metadata and the container option as a fallback.
727
751
  if (metadata.eagerInstantiate && registration.options?.scope === Scope.Container) {
728
752
  this.resolveProviderValue(token, registration);
729
753
  }
@@ -837,7 +861,7 @@ function isDisposable(value) {
837
861
  }
838
862
  if (isFactoryProvider(provider)) {
839
863
  const factory = provider.useFactory;
840
- return this.resolveScopedValue(token, registration, factory);
864
+ return this.resolveScopedValue(token, registration, ()=>factory());
841
865
  }
842
866
  if (isValueProvider(provider)) {
843
867
  return provider.useValue;
@@ -886,6 +910,7 @@ function isDisposable(value) {
886
910
  registration.value = {
887
911
  current: value
888
912
  };
913
+ this.notifyProvideHooks(value);
889
914
  return value;
890
915
  }
891
916
  case Scope.Resolution:
@@ -899,12 +924,15 @@ function isDisposable(value) {
899
924
  resolution.values.set(provider, {
900
925
  current: value
901
926
  });
927
+ this.notifyProvideHooks(value);
902
928
  return value;
903
929
  }
904
930
  case Scope.Transient:
905
931
  {
906
932
  const args = this.resolveCtorDependencies(registration);
907
- return this.injectMethodDependencies(registration, factory(args));
933
+ const value = this.injectMethodDependencies(registration, factory(args));
934
+ this.notifyProvideHooks(value);
935
+ return value;
908
936
  }
909
937
  }
910
938
  } finally{
@@ -953,6 +981,7 @@ function isDisposable(value) {
953
981
  }
954
982
  return instance;
955
983
  }
984
+ // Call context: decorator-based injection
956
985
  resolveArgs(deps, ctor, instance, methodKey) {
957
986
  const sortedDeps = deps.sort((a, b)=>a.index - b.index);
958
987
  const args = [];
@@ -965,8 +994,10 @@ function isDisposable(value) {
965
994
  }
966
995
  return args;
967
996
  }
997
+ // Call context: decorator-based injection
968
998
  resolveDependency(dependency, instance) {
969
- const token = dependency.tokenRef.getRefToken();
999
+ const token = dependency.tokenRef?.getRefToken();
1000
+ check(token, `token passed to @${dependency.appliedBy} was undefined (possible circular imports)`);
970
1001
  const name = dependency.name;
971
1002
  switch(dependency.appliedBy){
972
1003
  case "Inject":
@@ -979,6 +1010,16 @@ function isDisposable(value) {
979
1010
  return instance ? optionalAll(token) : this.tryResolveAll(token);
980
1011
  }
981
1012
  }
1013
+ notifyProvideHooks(value) {
1014
+ for (const hook of this.myHooks){
1015
+ hook.onProvide(value);
1016
+ }
1017
+ }
1018
+ notifyDisposeHooks(value) {
1019
+ for (const hook of this.myHooks){
1020
+ hook.onDispose(value);
1021
+ }
1022
+ }
982
1023
  checkDisposed() {
983
1024
  check(!this.myDisposed, "container is disposed");
984
1025
  }
@@ -987,7 +1028,7 @@ function isDisposable(value) {
987
1028
  /**
988
1029
  * Creates a new container.
989
1030
  */ function createContainer(options) {
990
- return new ContainerImpl(undefined, options);
1031
+ return new ContainerImpl(undefined, undefined, options);
991
1032
  }
992
1033
 
993
1034
  /**
@@ -1321,5 +1362,5 @@ function OptionalAll(token) {
1321
1362
  return container;
1322
1363
  }
1323
1364
 
1324
- export { AutoRegister, EagerInstantiate, Inject, InjectAll, Injectable, Injector, Named, Optional, OptionalAll, Scope, Scoped, applyMiddleware, build, classRef, createContainer, createType, inject, injectAll, injectBy, optional, optionalAll, optionalBy, setClassIdentityMapping, tokenRef };
1365
+ export { AutoRegister, EagerInstantiate, Inject, InjectAll, Injectable, Injector, Named, Optional, OptionalAll, Scope, Scoped, applyMiddleware, assertInjectionContext, build, classRef, createContainer, createType, inject, injectAll, injectBy, optional, optionalAll, optionalBy, setClassIdentityMapping, tokenRef };
1325
1366
  //# sourceMappingURL=index.mjs.map