@lppedd/di-wise-neo 0.9.4 → 0.11.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.
@@ -147,7 +147,26 @@ interface ExistingProvider<Value> {
147
147
  /**
148
148
  * The existing token to alias.
149
149
  */
150
- readonly useExisting: Token<Value>;
150
+ readonly useExisting: Token<Value> | {
151
+ readonly token: Token<Value>;
152
+ readonly name?: string;
153
+ };
154
+ /**
155
+ * An optional name to qualify this provider.
156
+ * If specified, the token must be resolved using the same name.
157
+ *
158
+ * @example
159
+ * ```ts
160
+ * export class ExtensionContext {
161
+ * // Decorator-based injection
162
+ * constructor(@Inject(ISecretStorage) @Named("persistent") secretStorage: SecretStorage) {}
163
+ *
164
+ * // Function-based injection
165
+ * constructor(secretStorage = inject(ISecretStorage, "persistent")) {}
166
+ * }
167
+ * ```
168
+ */
169
+ readonly name?: string;
151
170
  }
152
171
  /**
153
172
  * A token provider.
@@ -155,7 +174,6 @@ interface ExistingProvider<Value> {
155
174
  type Provider<Value = any> = ClassProvider<Value & object> | FactoryProvider<Value> | ValueProvider<Value> | ExistingProvider<Value>;
156
175
 
157
176
  declare const Scope: {
158
- readonly Inherited: "Inherited";
159
177
  readonly Transient: "Transient";
160
178
  readonly Resolution: "Resolution";
161
179
  readonly Container: "Container";
@@ -203,7 +221,7 @@ interface ContainerOptions {
203
221
  /**
204
222
  * The default scope for registrations.
205
223
  *
206
- * @defaultValue Scope.Inherited
224
+ * @defaultValue Scope.Transient
207
225
  */
208
226
  readonly defaultScope: Scope;
209
227
  }
@@ -325,7 +343,8 @@ interface Container {
325
343
  *
326
344
  * If the class is not registered, but it is decorated with {@link AutoRegister},
327
345
  * or {@link ContainerOptions.autoRegister} is true, the class is registered automatically.
328
- * Otherwise, resolution fails.
346
+ * Otherwise, the resolution fails.
347
+ *
329
348
  * The scope for the automatic registration is determined by either
330
349
  * the {@link Scoped} decorator on the class, or {@link ContainerOptions.defaultScope}.
331
350
  *
@@ -373,7 +392,8 @@ interface Container {
373
392
  *
374
393
  * If the class is not registered, but it is decorated with {@link AutoRegister},
375
394
  * or {@link ContainerOptions.autoRegister} is true, the class is registered automatically.
376
- * Otherwise, resolution fails.
395
+ * Otherwise, the resolution fails.
396
+ *
377
397
  * The scope for the automatic registration is determined by either
378
398
  * the {@link Scoped} decorator on the class, or {@link ContainerOptions.defaultScope}.
379
399
  *
package/dist/es/index.mjs CHANGED
@@ -5,27 +5,38 @@ function check(condition, message) {
5
5
  }
6
6
  }
7
7
  // @internal
8
- function expectNever(value) {
9
- throw new TypeError(tag(`unexpected value ${String(value)}`));
8
+ function throwUnregisteredError(tokenInfo) {
9
+ throw new Error(tag(`unregistered token ${getFullTokenName(tokenInfo)}`));
10
10
  }
11
11
  // @internal
12
- function throwUnregisteredError(token, name) {
13
- const spec = name !== undefined ? `[name=${name}]` : "";
14
- throw new Error(tag(`unregistered token ${getTokenName(token)}${spec}`));
12
+ function throwTargetUnregisteredError(tokenInfo, aliases) {
13
+ const path = aliases.length > 0 ? ` (alias for ${getTokenPath(aliases)})` : "";
14
+ const desc = getFullTokenName(tokenInfo) + path;
15
+ const cause = `\n [cause] useExisting points to unregistered token ${getFullTokenName(aliases.at(-1))}`;
16
+ throw new Error(tag(`failed to resolve token ${desc}`) + cause);
15
17
  }
16
18
  // @internal
17
- function throwExistingUnregisteredError(token, cause) {
18
- const message = tag(`failed to resolve token ${getTokenName(token)}`);
19
- throw isError(cause) ? new Error(`${message}\n [cause] ${untag(cause.message)}`, {
19
+ function throwCircularAliasError(aliases) {
20
+ const path = getTokenPath(aliases);
21
+ throw new Error(tag(`circular alias detected while resolving ${path}`));
22
+ }
23
+ // @internal
24
+ function throwResolutionError(tokenInfo, aliases, cause) {
25
+ const path = aliases.length > 0 ? ` (alias for ${getTokenPath(aliases)})` : "";
26
+ const desc = getFullTokenName(tokenInfo) + path;
27
+ throw new Error(tag(`failed to resolve token ${desc}`) + getCause(cause), {
20
28
  cause
21
- }) : new Error(`${message}\n [cause] the aliased token ${getTokenName(cause)} is not registered`);
29
+ });
22
30
  }
23
31
  // @internal
24
32
  function throwParameterResolutionError(ctor, methodKey, dependency, cause) {
25
33
  const location = getLocation(ctor, methodKey);
26
- const tokenName = getTokenName(dependency.tokenRef.getRefToken());
27
- const message = tag(`failed to resolve dependency for ${location}(parameter #${dependency.index}: ${tokenName})`);
28
- throw new Error(`${message}\n [cause] ${untag(cause.message)}`, {
34
+ const tokenName = getFullTokenName([
35
+ dependency.tokenRef.getRefToken(),
36
+ dependency.name
37
+ ]);
38
+ const msg = tag(`failed to resolve dependency for ${location}(parameter #${dependency.index}: ${tokenName})`);
39
+ throw new Error(msg + getCause(cause), {
29
40
  cause
30
41
  });
31
42
  }
@@ -35,11 +46,27 @@ function getLocation(ctor, methodKey) {
35
46
  return methodKey ? `${ctorName}.${String(methodKey)}` : ctorName;
36
47
  }
37
48
  // @internal
49
+ function getTokenPath(tokens) {
50
+ return tokens.map(getFullTokenName).join(" \u2192 ");
51
+ }
52
+ // @internal
38
53
  function getTokenName(token) {
39
54
  return token.name || "<unnamed>";
40
55
  }
56
+ function getFullTokenName(tokenInfo) {
57
+ const [token, name] = tokenInfo;
58
+ const tokenName = token.name || "<unnamed>";
59
+ return name ? `${tokenName}["${name}"]` : tokenName;
60
+ }
61
+ function getCause(error) {
62
+ if (!error) {
63
+ return "";
64
+ }
65
+ const msg = isError(error) ? error.message : String(error);
66
+ return `\n [cause] ${untag(msg)}`;
67
+ }
41
68
  function isError(value) {
42
- return value && value.stack && value.message && typeof value.message === "string";
69
+ return value?.stack && typeof value?.message === "string";
43
70
  }
44
71
  function tag(message) {
45
72
  return `[di-wise-neo] ${message}`;
@@ -296,15 +323,13 @@ function isExistingProvider(provider) {
296
323
  }
297
324
 
298
325
  const Scope = {
299
- Inherited: "Inherited",
300
326
  Transient: "Transient",
301
327
  Resolution: "Resolution",
302
328
  Container: "Container"
303
329
  };
304
330
 
331
+ const typeSymbol = Symbol("di-wise-neo.typeToken");
305
332
  /**
306
- * Type API.
307
- */ /**
308
333
  * Creates a type token.
309
334
  *
310
335
  * @example
@@ -318,6 +343,7 @@ const Scope = {
318
343
  name: `Type<${typeName}>`,
319
344
  inter: createType,
320
345
  union: createType,
346
+ __type: typeSymbol,
321
347
  toString () {
322
348
  return type.name;
323
349
  }
@@ -325,6 +351,10 @@ const Scope = {
325
351
  return type;
326
352
  }
327
353
  // @internal
354
+ function isType(token) {
355
+ return token.__type === typeSymbol;
356
+ }
357
+ // @internal
328
358
  function isConstructor(token) {
329
359
  return typeof token === "function";
330
360
  }
@@ -430,8 +460,7 @@ class TokenRegistry {
430
460
  return Array.from(values);
431
461
  }
432
462
  getAllFromParent(token, name) {
433
- const thisRegistrations = this.myMap.get(token);
434
- let registrations = thisRegistrations || this.myParent?.getAllFromParent(token, name);
463
+ let registrations = this.myMap.get(token) || this.myParent?.getAllFromParent(token, name);
435
464
  if (registrations && name !== undefined) {
436
465
  registrations = registrations.filter((r)=>r.name === name);
437
466
  check(registrations.length < 2, `internal error: more than one registration named '${name}'`);
@@ -476,7 +505,7 @@ function isDisposable(value) {
476
505
  this.myParent = parent;
477
506
  this.myOptions = {
478
507
  autoRegister: false,
479
- defaultScope: Scope.Inherited,
508
+ defaultScope: Scope.Transient,
480
509
  ...options
481
510
  };
482
511
  this.myTokenRegistry = new TokenRegistry(this.myParent?.myTokenRegistry);
@@ -546,8 +575,9 @@ function isDisposable(value) {
546
575
  if (args.length === 1) {
547
576
  const Class = args[0];
548
577
  const metadata = getMetadata(Class);
578
+ const name = metadata.name;
549
579
  const registration = {
550
- name: metadata.name,
580
+ name: name,
551
581
  // The provider is of type ClassProvider, initialized by getMetadata
552
582
  provider: metadata.provider,
553
583
  options: {
@@ -561,8 +591,12 @@ function isDisposable(value) {
561
591
  // These tokens will point to the original Class token and will have the same scope.
562
592
  for (const token of metadata.tokensRef.getRefTokens()){
563
593
  this.myTokenRegistry.set(token, {
594
+ name: name,
564
595
  provider: {
565
- useExisting: Class
596
+ useExisting: {
597
+ token: Class,
598
+ name: name
599
+ }
566
600
  }
567
601
  });
568
602
  }
@@ -572,9 +606,8 @@ function isDisposable(value) {
572
606
  }
573
607
  } else {
574
608
  const [token, provider, options] = args;
575
- const existingProvider = isExistingProvider(provider);
576
- const name = existingProvider ? undefined : provider.name;
577
- check(name === undefined || name.trim(), `the name qualifier for token ${getTokenName(token)} must not be empty`);
609
+ const name = provider.name;
610
+ check(name === undefined || name.trim(), `name qualifier for token ${getTokenName(token)} must not be empty`);
578
611
  if (isClassProvider(provider)) {
579
612
  const metadata = getMetadata(provider.useClass);
580
613
  const registration = {
@@ -594,8 +627,9 @@ function isDisposable(value) {
594
627
  this.resolveProviderValue(token, registration);
595
628
  }
596
629
  } else {
597
- if (existingProvider) {
598
- check(token !== provider.useExisting, `token ${getTokenName(token)} cannot alias itself via useExisting`);
630
+ if (isExistingProvider(provider)) {
631
+ const [targetToken] = this.getTargetToken(provider);
632
+ check(token !== targetToken, `token ${getTokenName(token)} cannot alias itself via useExisting`);
599
633
  }
600
634
  this.myTokenRegistry.set(token, {
601
635
  name: name,
@@ -632,10 +666,7 @@ function isDisposable(value) {
632
666
  if (!registration && isConstructor(token)) {
633
667
  registration = this.autoRegisterClass(token, localName);
634
668
  }
635
- if (registration) {
636
- return this.resolveRegistration(token, registration, localName);
637
- }
638
- return localOptional ? undefined : throwUnregisteredError(token, localName);
669
+ return this.resolveRegistration(token, registration, localOptional, localName);
639
670
  }
640
671
  resolveAll(token, optional) {
641
672
  this.checkDisposed();
@@ -648,11 +679,13 @@ function isDisposable(value) {
648
679
  ];
649
680
  }
650
681
  }
651
- if (registrations.length > 0) {
652
- return registrations //
653
- .map((registration)=>this.resolveRegistration(token, registration)).filter((value)=>value != null);
682
+ if (registrations.length === 0 && !optional) {
683
+ throwUnregisteredError([
684
+ token
685
+ ]);
654
686
  }
655
- return optional ? [] : throwUnregisteredError(token);
687
+ return registrations //
688
+ .map((registration)=>this.resolveRegistration(token, registration, optional)).filter((value)=>value != null);
656
689
  }
657
690
  dispose() {
658
691
  if (this.myDisposed) {
@@ -679,26 +712,57 @@ function isDisposable(value) {
679
712
  // Allow values to be GCed
680
713
  disposedRefs.clear();
681
714
  }
682
- resolveRegistration(token, registration, name) {
683
- let currRegistration = registration;
684
- while(isExistingProvider(currRegistration.provider)){
685
- const targetToken = currRegistration.provider.useExisting;
686
- currRegistration = this.myTokenRegistry.get(targetToken, name);
687
- if (!currRegistration) {
688
- throwExistingUnregisteredError(token, targetToken);
715
+ resolveRegistration(token, registration, optional, name) {
716
+ const aliases = [];
717
+ while(registration && isExistingProvider(registration.provider)){
718
+ const [targetToken, targetName] = this.getTargetToken(registration.provider);
719
+ if (aliases.some(([t])=>t === targetToken)) {
720
+ throwCircularAliasError([
721
+ [
722
+ token,
723
+ name
724
+ ],
725
+ ...aliases
726
+ ]);
727
+ }
728
+ // eslint-disable-next-line no-param-reassign
729
+ registration = this.myTokenRegistry.get(targetToken, targetName);
730
+ aliases.push([
731
+ targetToken,
732
+ targetName
733
+ ]);
734
+ if (!registration && !optional) {
735
+ throwTargetUnregisteredError([
736
+ token,
737
+ name
738
+ ], aliases);
689
739
  }
690
740
  }
741
+ if (!registration) {
742
+ return optional ? undefined : throwUnregisteredError([
743
+ token,
744
+ name
745
+ ]);
746
+ }
691
747
  try {
692
- return this.resolveProviderValue(token, currRegistration);
748
+ return this.resolveProviderValue(token, registration);
693
749
  } catch (e) {
694
- // If we were trying to resolve a token registered via ExistingProvider,
695
- // we must add the cause of the error to the message
696
- if (isExistingProvider(registration.provider)) {
697
- throwExistingUnregisteredError(token, e);
698
- }
699
- throw e;
750
+ throwResolutionError([
751
+ token,
752
+ name
753
+ ], aliases, e);
700
754
  }
701
755
  }
756
+ getTargetToken(provider) {
757
+ const token = provider.useExisting;
758
+ return isType(token) || isConstructor(token) //
759
+ ? [
760
+ token
761
+ ] : [
762
+ token.token,
763
+ token.name
764
+ ];
765
+ }
702
766
  autoRegisterClass(Class, name) {
703
767
  const metadata = getMetadata(Class);
704
768
  const autoRegister = metadata.autoRegister ?? this.myOptions.autoRegister;
@@ -729,8 +793,7 @@ function isDisposable(value) {
729
793
  if (isValueProvider(provider)) {
730
794
  return provider.useValue;
731
795
  }
732
- check(!isExistingProvider(provider), "internal error: unexpected ExistingProvider");
733
- expectNever(provider);
796
+ check(false, "internal error: unexpected ExistingProvider");
734
797
  }
735
798
  resolveScopedValue(token, registration, factory) {
736
799
  let context = useInjectionContext();
@@ -742,16 +805,17 @@ function isDisposable(value) {
742
805
  }
743
806
  const resolution = context.resolution;
744
807
  const provider = registration.provider;
745
- const options = registration.options;
746
808
  if (resolution.stack.has(provider)) {
747
809
  const dependentRef = resolution.dependents.get(provider);
748
810
  check(dependentRef, ()=>{
749
- const path = resolution.tokenStack.map(getTokenName).join(" → ");
750
- return `circular dependency detected while resolving ${path} → ${getTokenName(token)}`;
811
+ const path = getTokenPath(resolution.tokenStack.concat(token).map((t)=>[
812
+ t
813
+ ]));
814
+ return `circular dependency detected while resolving ${path}`;
751
815
  });
752
816
  return dependentRef.current;
753
817
  }
754
- const scope = this.resolveScope(options?.scope, context);
818
+ const scope = registration.options?.scope ?? this.myOptions.defaultScope;
755
819
  const cleanups = [
756
820
  provideInjectionContext(context),
757
821
  resolution.tokenStack.push(token) && (()=>resolution.tokenStack.pop()),
@@ -798,13 +862,6 @@ function isDisposable(value) {
798
862
  cleanups.forEach((cleanup)=>cleanup && cleanup());
799
863
  }
800
864
  }
801
- resolveScope(scope = this.myOptions.defaultScope, context = useInjectionContext()) {
802
- if (scope === Scope.Inherited) {
803
- const dependentFrame = context?.resolution.stack.peek();
804
- return dependentFrame?.scope || Scope.Transient;
805
- }
806
- return scope;
807
- }
808
865
  resolveCtorDependencies(registration) {
809
866
  const dependencies = registration.dependencies;
810
867
  if (dependencies) {
@@ -817,7 +874,7 @@ function isDisposable(value) {
817
874
  check(ctor.length === ctorDeps.length, ()=>{
818
875
  const location = getLocation(ctor);
819
876
  const msg = `${location} expected ${ctor.length} decorated constructor parameters`;
820
- return msg + `, but found ${ctorDeps.length}`;
877
+ return `${msg}, but found ${ctorDeps.length}`;
821
878
  });
822
879
  return this.resolveArgs(ctorDeps, ctor);
823
880
  }
@@ -839,7 +896,7 @@ function isDisposable(value) {
839
896
  check(methodDeps.length === method.length, ()=>{
840
897
  const location = getLocation(ctor, methodKey);
841
898
  const msg = `${location} expected ${method.length} decorated method parameters`;
842
- return msg + `, but found ${methodDeps.length}`;
899
+ return `${msg}, but found ${methodDeps.length}`;
843
900
  });
844
901
  const args = this.resolveArgs(methodDeps, ctor, instance, methodKey);
845
902
  method.bind(instance)(...args);
@@ -874,7 +931,7 @@ function isDisposable(value) {
874
931
  }
875
932
  }
876
933
  checkDisposed() {
877
- check(!this.myDisposed, "the container is disposed");
934
+ check(!this.myDisposed, "container is disposed");
878
935
  }
879
936
  }
880
937
 
@@ -882,7 +939,7 @@ function isDisposable(value) {
882
939
  * Creates a new container.
883
940
  */ function createContainer(options = {
884
941
  autoRegister: false,
885
- defaultScope: Scope.Inherited
942
+ defaultScope: Scope.Transient
886
943
  }) {
887
944
  return new ContainerImpl(undefined, options);
888
945
  }
@@ -1079,7 +1136,7 @@ function InjectAll(token) {
1079
1136
  *
1080
1137
  * @__NO_SIDE_EFFECTS__
1081
1138
  */ function Named(name) {
1082
- check(name.trim(), "the @Named qualifier must not be empty");
1139
+ check(name.trim(), "@Named qualifier must not be empty");
1083
1140
  return function(target, propertyKey, parameterIndex) {
1084
1141
  if (parameterIndex === undefined) {
1085
1142
  // The decorator has been applied to the class
@@ -1181,22 +1238,15 @@ function OptionalAll(token) {
1181
1238
  * ```
1182
1239
  */ const Injector = /*@__PURE__*/ build(()=>{
1183
1240
  const context = ensureInjectionContext("Injector factory");
1184
- const resolution = context.resolution;
1185
- const dependentFrame = resolution.stack.peek();
1186
- const dependentRef = dependentFrame && resolution.dependents.get(dependentFrame.provider);
1187
1241
  const runInContext = (fn)=>{
1188
1242
  if (useInjectionContext()) {
1189
1243
  return fn();
1190
1244
  }
1191
- const cleanups = [
1192
- provideInjectionContext(context),
1193
- dependentFrame && resolution.stack.push(dependentFrame.provider, dependentFrame),
1194
- dependentRef && resolution.dependents.set(dependentFrame.provider, dependentRef)
1195
- ];
1245
+ const cleanup = provideInjectionContext(context);
1196
1246
  try {
1197
1247
  return fn();
1198
1248
  } finally{
1199
- cleanups.forEach((cleanup)=>cleanup?.());
1249
+ cleanup();
1200
1250
  }
1201
1251
  };
1202
1252
  return {