@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/cjs/index.js CHANGED
@@ -34,7 +34,7 @@ function throwResolutionError(tokenInfo, aliases, cause) {
34
34
  function throwParameterResolutionError(ctor, methodKey, dependency, cause) {
35
35
  const location = getLocation(ctor, methodKey);
36
36
  const tokenName = getFullTokenName([
37
- dependency.tokenRef.getRefToken(),
37
+ dependency.tokenRef?.getRefToken(),
38
38
  dependency.name
39
39
  ]);
40
40
  const msg = tag(`failed to resolve dependency for ${location}(parameter #${dependency.index}: ${tokenName})`);
@@ -56,7 +56,7 @@ function getTokenName(token) {
56
56
  return token.name || "<unnamed>";
57
57
  }
58
58
  function getFullTokenName([token, name]) {
59
- const tokenName = token.name || "<unnamed>";
59
+ const tokenName = token ? token.name || "<unnamed>" : "<undefined token>";
60
60
  return name ? `${tokenName}["${name}"]` : tokenName;
61
61
  }
62
62
  function getCause(error) {
@@ -144,6 +144,16 @@ function ensureInjectionContext(name) {
144
144
  check(context, `${name} can only be invoked within an injection context`);
145
145
  return context;
146
146
  }
147
+ /**
148
+ * Asserts that the current stack frame is within an injection context,
149
+ * meaning it has access to injection functions (`inject`, `optional`, etc.).
150
+ *
151
+ * @param fn The function performing the assertion, or a string name used in the error message.
152
+ * @throws {Error} If the current stack frame is not within an injection context.
153
+ */ function assertInjectionContext(fn) {
154
+ const name = typeof fn === "function" ? `${fn.name || "<unnamed>"}()` : fn;
155
+ ensureInjectionContext(name);
156
+ }
147
157
  function createInjectionContext() {
148
158
  let current = null;
149
159
  function provide(next) {
@@ -216,15 +226,15 @@ function tokenRef(token) {
216
226
  }
217
227
  // @internal
218
228
  function isClassRef(value) {
219
- return value && typeof value === "object" && typeof value.getRefClass === "function";
229
+ return value != null && typeof value === "object" && typeof value.getRefClass === "function";
220
230
  }
221
231
  // @internal
222
232
  function isTokensRef(value) {
223
- return value && typeof value === "object" && typeof value.getRefTokens === "function";
233
+ return value != null && typeof value === "object" && typeof value.getRefTokens === "function";
224
234
  }
225
235
  // @internal
226
236
  function isTokenRef(value) {
227
- return value && typeof value === "object" && typeof value.getRefToken === "function";
237
+ return value != null && typeof value === "object" && typeof value.getRefToken === "function";
228
238
  }
229
239
 
230
240
  // @internal
@@ -533,21 +543,22 @@ const builders = new WeakSet();
533
543
  // @internal
534
544
  // @internal
535
545
  function isDisposable(value) {
536
- return value && typeof value === "object" && typeof value.dispose === "function";
546
+ return value != null && typeof value === "object" && typeof value.dispose === "function";
537
547
  }
538
548
 
539
549
  /**
540
550
  * The default implementation of a di-wise-neo {@link Container}.
541
551
  */ class ContainerImpl {
542
- constructor(parent, options){
552
+ constructor(parent, hooks, options){
543
553
  this.myChildren = new Set();
544
554
  this.myDisposed = false;
545
555
  this.myParent = parent;
556
+ this.myHooks = hooks ?? new Set();
546
557
  this.myOptions = {
547
- autoRegister: options?.autoRegister ?? false,
548
- defaultScope: options?.defaultScope ?? Scope.Transient
558
+ defaultScope: options?.defaultScope ?? Scope.Transient,
559
+ autoRegister: options?.autoRegister ?? false
549
560
  };
550
- this.myTokenRegistry = new TokenRegistry(this.myParent?.myTokenRegistry);
561
+ this.myTokenRegistry = new TokenRegistry(parent?.myTokenRegistry);
551
562
  }
552
563
  get registry() {
553
564
  return this.myTokenRegistry;
@@ -565,9 +576,10 @@ function isDisposable(value) {
565
576
  }
566
577
  createChild(options) {
567
578
  this.checkDisposed();
568
- const container = new ContainerImpl(this, {
569
- autoRegister: options?.autoRegister ?? this.myOptions.autoRegister,
570
- defaultScope: options?.defaultScope ?? this.myOptions.defaultScope
579
+ const hooks = options?.copyHooks === false ? undefined : new Set(this.myHooks);
580
+ const container = new ContainerImpl(this, hooks, {
581
+ defaultScope: options?.defaultScope ?? this.myOptions.defaultScope,
582
+ autoRegister: options?.autoRegister ?? this.myOptions.autoRegister
571
583
  });
572
584
  this.myChildren.add(container);
573
585
  return container;
@@ -651,6 +663,12 @@ function isDisposable(value) {
651
663
  this.checkDisposed();
652
664
  return this.resolveAllToken(token, true);
653
665
  }
666
+ addHook(hook) {
667
+ this.myHooks.add(hook);
668
+ }
669
+ removeHook(hook) {
670
+ this.myHooks.delete(hook);
671
+ }
654
672
  dispose() {
655
673
  if (this.myDisposed) {
656
674
  return;
@@ -669,12 +687,14 @@ function isDisposable(value) {
669
687
  for (const registration of registrations){
670
688
  const value = registration.value?.current;
671
689
  if (isDisposable(value) && !disposedRefs.has(value)) {
690
+ this.notifyDisposeHooks(value);
672
691
  disposedRefs.add(value);
673
692
  value.dispose();
674
693
  }
675
694
  }
676
695
  // Allow values to be GCed
677
696
  disposedRefs.clear();
697
+ this.myHooks.clear();
678
698
  }
679
699
  registerClass(Class) {
680
700
  const metadata = getMetadata(Class);
@@ -703,7 +723,9 @@ function isDisposable(value) {
703
723
  }
704
724
  });
705
725
  }
706
- // Eager-instantiate only if the class is container-scoped
726
+ // Eager-instantiate only if the class is container-scoped.
727
+ // Note that we are comparing the scope using the registration configured just above,
728
+ // which takes into account both the metadata and the container option as a fallback.
707
729
  if (metadata.eagerInstantiate && registration.options?.scope === Scope.Container) {
708
730
  this.resolveProviderValue(Class, registration);
709
731
  }
@@ -725,7 +747,9 @@ function isDisposable(value) {
725
747
  dependencies: metadata.dependencies
726
748
  };
727
749
  this.myTokenRegistry.set(token, registration);
728
- // Eager-instantiate only if the provided class is container-scoped
750
+ // Eager-instantiate only if the provided class is container-scoped.
751
+ // Note that we are comparing the scope using the registration configured just above,
752
+ // which takes into account both the metadata and the container option as a fallback.
729
753
  if (metadata.eagerInstantiate && registration.options?.scope === Scope.Container) {
730
754
  this.resolveProviderValue(token, registration);
731
755
  }
@@ -839,7 +863,7 @@ function isDisposable(value) {
839
863
  }
840
864
  if (isFactoryProvider(provider)) {
841
865
  const factory = provider.useFactory;
842
- return this.resolveScopedValue(token, registration, factory);
866
+ return this.resolveScopedValue(token, registration, ()=>factory());
843
867
  }
844
868
  if (isValueProvider(provider)) {
845
869
  return provider.useValue;
@@ -888,6 +912,7 @@ function isDisposable(value) {
888
912
  registration.value = {
889
913
  current: value
890
914
  };
915
+ this.notifyProvideHooks(value);
891
916
  return value;
892
917
  }
893
918
  case Scope.Resolution:
@@ -901,12 +926,15 @@ function isDisposable(value) {
901
926
  resolution.values.set(provider, {
902
927
  current: value
903
928
  });
929
+ this.notifyProvideHooks(value);
904
930
  return value;
905
931
  }
906
932
  case Scope.Transient:
907
933
  {
908
934
  const args = this.resolveCtorDependencies(registration);
909
- return this.injectMethodDependencies(registration, factory(args));
935
+ const value = this.injectMethodDependencies(registration, factory(args));
936
+ this.notifyProvideHooks(value);
937
+ return value;
910
938
  }
911
939
  }
912
940
  } finally{
@@ -955,6 +983,7 @@ function isDisposable(value) {
955
983
  }
956
984
  return instance;
957
985
  }
986
+ // Call context: decorator-based injection
958
987
  resolveArgs(deps, ctor, instance, methodKey) {
959
988
  const sortedDeps = deps.sort((a, b)=>a.index - b.index);
960
989
  const args = [];
@@ -967,8 +996,10 @@ function isDisposable(value) {
967
996
  }
968
997
  return args;
969
998
  }
999
+ // Call context: decorator-based injection
970
1000
  resolveDependency(dependency, instance) {
971
- const token = dependency.tokenRef.getRefToken();
1001
+ const token = dependency.tokenRef?.getRefToken();
1002
+ check(token, `token passed to @${dependency.appliedBy} was undefined (possible circular imports)`);
972
1003
  const name = dependency.name;
973
1004
  switch(dependency.appliedBy){
974
1005
  case "Inject":
@@ -981,6 +1012,16 @@ function isDisposable(value) {
981
1012
  return instance ? optionalAll(token) : this.tryResolveAll(token);
982
1013
  }
983
1014
  }
1015
+ notifyProvideHooks(value) {
1016
+ for (const hook of this.myHooks){
1017
+ hook.onProvide(value);
1018
+ }
1019
+ }
1020
+ notifyDisposeHooks(value) {
1021
+ for (const hook of this.myHooks){
1022
+ hook.onDispose(value);
1023
+ }
1024
+ }
984
1025
  checkDisposed() {
985
1026
  check(!this.myDisposed, "container is disposed");
986
1027
  }
@@ -989,7 +1030,7 @@ function isDisposable(value) {
989
1030
  /**
990
1031
  * Creates a new container.
991
1032
  */ function createContainer(options) {
992
- return new ContainerImpl(undefined, options);
1033
+ return new ContainerImpl(undefined, undefined, options);
993
1034
  }
994
1035
 
995
1036
  /**
@@ -1335,6 +1376,7 @@ exports.OptionalAll = OptionalAll;
1335
1376
  exports.Scope = Scope;
1336
1377
  exports.Scoped = Scoped;
1337
1378
  exports.applyMiddleware = applyMiddleware;
1379
+ exports.assertInjectionContext = assertInjectionContext;
1338
1380
  exports.build = build;
1339
1381
  exports.classRef = classRef;
1340
1382
  exports.createContainer = createContainer;