@lppedd/di-wise-neo 0.5.3 → 0.7.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.
- package/README.md +45 -15
- package/dist/cjs/index.d.ts +115 -75
- package/dist/cjs/index.js +273 -176
- package/dist/cjs/index.js.map +1 -1
- package/dist/es/index.d.mts +115 -75
- package/dist/es/index.mjs +273 -177
- package/dist/es/index.mjs.map +1 -1
- package/package.json +6 -6
package/dist/cjs/index.js
CHANGED
@@ -129,23 +129,23 @@ function ensureInjectionContext(fn) {
|
|
129
129
|
return context;
|
130
130
|
}
|
131
131
|
|
132
|
-
function inject(token) {
|
132
|
+
function inject(token, name) {
|
133
133
|
const context = ensureInjectionContext(inject);
|
134
|
-
return context.container.resolve(token);
|
134
|
+
return context.container.resolve(token, name);
|
135
135
|
}
|
136
|
-
function injectBy(thisArg, token) {
|
136
|
+
function injectBy(thisArg, token, name) {
|
137
137
|
const context = ensureInjectionContext(injectBy);
|
138
138
|
const resolution = context.resolution;
|
139
139
|
const currentFrame = resolution.stack.peek();
|
140
140
|
if (!currentFrame) {
|
141
|
-
return inject(token);
|
141
|
+
return inject(token, name);
|
142
142
|
}
|
143
143
|
const currentRef = {
|
144
144
|
current: thisArg
|
145
145
|
};
|
146
146
|
const cleanup = resolution.dependents.set(currentFrame.provider, currentRef);
|
147
147
|
try {
|
148
|
-
return inject(token);
|
148
|
+
return inject(token, name);
|
149
149
|
} finally{
|
150
150
|
cleanup();
|
151
151
|
}
|
@@ -156,41 +156,59 @@ function injectAll(token) {
|
|
156
156
|
return context.container.resolveAll(token);
|
157
157
|
}
|
158
158
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
159
|
+
// @internal
|
160
|
+
class Metadata {
|
161
|
+
constructor(Class){
|
162
|
+
this.dependencies = {
|
163
|
+
constructor: [],
|
164
|
+
methods: new Map()
|
165
|
+
};
|
166
|
+
this.tokensRef = {
|
167
|
+
getRefTokens: ()=>new Set()
|
168
|
+
};
|
169
|
+
this.provider = {
|
170
|
+
useClass: Class
|
171
|
+
};
|
172
|
+
}
|
173
|
+
get name() {
|
174
|
+
return this.provider.name;
|
175
|
+
}
|
176
|
+
set name(name) {
|
177
|
+
this.provider.name = name;
|
178
|
+
}
|
179
|
+
getConstructorDependency(index) {
|
180
|
+
const i = this.dependencies.constructor.findIndex((d)=>d.index === index);
|
181
|
+
if (i > -1) {
|
182
|
+
return this.dependencies.constructor[i];
|
183
|
+
}
|
184
|
+
const dependency = {
|
185
|
+
index: index
|
186
|
+
};
|
187
|
+
this.dependencies.constructor.push(dependency);
|
188
|
+
return dependency;
|
189
|
+
}
|
190
|
+
getMethodDependency(method, index) {
|
191
|
+
let methodDeps = this.dependencies.methods.get(method);
|
192
|
+
if (!methodDeps) {
|
193
|
+
this.dependencies.methods.set(method, methodDeps = []);
|
194
|
+
}
|
195
|
+
const i = methodDeps.findIndex((d)=>d.index === index);
|
196
|
+
if (i > -1) {
|
197
|
+
return methodDeps[i];
|
198
|
+
}
|
199
|
+
const dependency = {
|
200
|
+
index: index
|
201
|
+
};
|
202
|
+
methodDeps.push(dependency);
|
203
|
+
return dependency;
|
204
|
+
}
|
176
205
|
}
|
177
206
|
// @internal
|
178
207
|
function getMetadata(Class) {
|
179
208
|
const originalClass = classIdentityMap.get(Class) ?? Class;
|
180
209
|
let metadata = metadataMap.get(originalClass);
|
181
210
|
if (!metadata) {
|
182
|
-
metadataMap.set(originalClass, metadata =
|
183
|
-
tokensRef: {
|
184
|
-
getRefTokens: ()=>new Set()
|
185
|
-
},
|
186
|
-
provider: {
|
187
|
-
useClass: originalClass
|
188
|
-
},
|
189
|
-
dependencies: {
|
190
|
-
constructor: [],
|
191
|
-
methods: new Map()
|
192
|
-
}
|
193
|
-
});
|
211
|
+
metadataMap.set(originalClass, metadata = new Metadata(originalClass));
|
194
212
|
}
|
195
213
|
if (metadata.provider.useClass !== Class) {
|
196
214
|
// This is part of the class identity mapping API (see setClassIdentityMapping).
|
@@ -204,32 +222,48 @@ function getMetadata(Class) {
|
|
204
222
|
// We must update useClass to be the extender class B so that instances created by the
|
205
223
|
// DI container match the consumer's registered class. Without this update, the DI
|
206
224
|
// system would instantiate the original class A, causing behavior inconsistencies.
|
207
|
-
//
|
208
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
209
225
|
metadata.provider.useClass = Class;
|
210
226
|
}
|
211
227
|
return metadata;
|
212
228
|
}
|
229
|
+
/**
|
230
|
+
* Registers a mapping between a generated (e.g., decorated or proxied) constructor
|
231
|
+
* and its original, underlying constructor.
|
232
|
+
*
|
233
|
+
* This allows libraries or consumers that manipulate constructors, such as through
|
234
|
+
* class decorators, to inform the DI system about the real "identity" of a class.
|
235
|
+
*
|
236
|
+
* @param transformedClass The constructor function returned by a class decorator or factory.
|
237
|
+
* @param originalClass The original constructor function.
|
238
|
+
*
|
239
|
+
* @remarks
|
240
|
+
* This API affects the core class identity resolution mechanism of the DI system.
|
241
|
+
* Incorrect usage may cause metadata to be misassociated, leading to subtle errors.
|
242
|
+
* Use only when manipulating constructors (e.g., via decorators or proxies),
|
243
|
+
* and ensure the mapping is correct.
|
244
|
+
*/ function setClassIdentityMapping(transformedClass, originalClass) {
|
245
|
+
classIdentityMap.set(transformedClass, originalClass);
|
246
|
+
}
|
213
247
|
const classIdentityMap = new WeakMap();
|
214
248
|
const metadataMap = new WeakMap();
|
215
249
|
|
216
|
-
function optional(token) {
|
250
|
+
function optional(token, name) {
|
217
251
|
const context = ensureInjectionContext(optional);
|
218
|
-
return context.container.resolve(token, true);
|
252
|
+
return context.container.resolve(token, true, name);
|
219
253
|
}
|
220
|
-
function optionalBy(thisArg, token) {
|
254
|
+
function optionalBy(thisArg, token, name) {
|
221
255
|
const context = ensureInjectionContext(optionalBy);
|
222
256
|
const resolution = context.resolution;
|
223
257
|
const currentFrame = resolution.stack.peek();
|
224
258
|
if (!currentFrame) {
|
225
|
-
return optional(token);
|
259
|
+
return optional(token, name);
|
226
260
|
}
|
227
261
|
const currentRef = {
|
228
262
|
current: thisArg
|
229
263
|
};
|
230
264
|
const cleanup = resolution.dependents.set(currentFrame.provider, currentRef);
|
231
265
|
try {
|
232
|
-
return optional(token);
|
266
|
+
return optional(token, name);
|
233
267
|
} finally{
|
234
268
|
cleanup();
|
235
269
|
}
|
@@ -288,28 +322,29 @@ const Scope = {
|
|
288
322
|
}
|
289
323
|
// @internal
|
290
324
|
function isConstructor(token) {
|
291
|
-
return typeof token
|
325
|
+
return typeof token === "function";
|
292
326
|
}
|
293
327
|
|
294
328
|
// @internal
|
295
329
|
function getTypeName(value) {
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
330
|
+
switch(typeof value){
|
331
|
+
case "string":
|
332
|
+
return `"${value}"`;
|
333
|
+
case "function":
|
334
|
+
return value.name && `typeof ${value.name}` || "Function";
|
335
|
+
case "object":
|
336
|
+
{
|
337
|
+
if (value === null) {
|
338
|
+
return "null";
|
339
|
+
}
|
340
|
+
const proto = Object.getPrototypeOf(value);
|
341
|
+
if (proto && proto !== Object.prototype) {
|
342
|
+
const constructor = proto.constructor;
|
343
|
+
if (typeof constructor === "function" && constructor.name) {
|
344
|
+
return constructor.name;
|
345
|
+
}
|
346
|
+
}
|
311
347
|
}
|
312
|
-
}
|
313
348
|
}
|
314
349
|
return typeof value;
|
315
350
|
}
|
@@ -320,28 +355,46 @@ class TokenRegistry {
|
|
320
355
|
this.myMap = new Map();
|
321
356
|
this.myParent = parent;
|
322
357
|
}
|
323
|
-
get(token) {
|
358
|
+
get(token, name) {
|
324
359
|
// To clarify, at(-1) means we take the last added registration for this token
|
325
|
-
return this.getAll(token)
|
360
|
+
return this.getAll(token, name).at(-1);
|
326
361
|
}
|
327
|
-
getAll(token) {
|
328
|
-
|
362
|
+
getAll(token, name) {
|
363
|
+
// Internal registrations cannot have a name
|
364
|
+
const internal = name !== undefined ? undefined : internals.get(token);
|
329
365
|
return internal && [
|
330
366
|
internal
|
331
|
-
] || this.getAllFromParent(token);
|
367
|
+
] || this.getAllFromParent(token, name);
|
332
368
|
}
|
333
369
|
set(token, registration) {
|
334
370
|
assert(!internals.has(token), `cannot register reserved token ${token.name}`);
|
335
371
|
let registrations = this.myMap.get(token);
|
336
372
|
if (!registrations) {
|
337
373
|
this.myMap.set(token, registrations = []);
|
374
|
+
} else if (registration.name !== undefined) {
|
375
|
+
const existing = registrations.filter((r)=>r.name === registration.name);
|
376
|
+
assert(existing.length === 0, `a ${token.name} token named '${registration.name}' is already registered`);
|
338
377
|
}
|
339
378
|
registrations.push(registration);
|
340
379
|
}
|
341
|
-
delete(token) {
|
380
|
+
delete(token, name) {
|
342
381
|
const registrations = this.myMap.get(token);
|
343
|
-
|
344
|
-
|
382
|
+
if (registrations) {
|
383
|
+
if (name !== undefined) {
|
384
|
+
const removedRegistrations = [];
|
385
|
+
const newRegistrations = [];
|
386
|
+
for (const registration of registrations){
|
387
|
+
const array = registration.name === name ? removedRegistrations : newRegistrations;
|
388
|
+
array.push(registration);
|
389
|
+
}
|
390
|
+
if (removedRegistrations.length > 0) {
|
391
|
+
this.myMap.set(token, newRegistrations);
|
392
|
+
return removedRegistrations;
|
393
|
+
}
|
394
|
+
}
|
395
|
+
this.myMap.delete(token);
|
396
|
+
}
|
397
|
+
return registrations ?? [];
|
345
398
|
}
|
346
399
|
deleteAll() {
|
347
400
|
const tokens = Array.from(this.myMap.keys());
|
@@ -369,9 +422,14 @@ class TokenRegistry {
|
|
369
422
|
}
|
370
423
|
return Array.from(values);
|
371
424
|
}
|
372
|
-
getAllFromParent(token) {
|
373
|
-
const
|
374
|
-
|
425
|
+
getAllFromParent(token, name) {
|
426
|
+
const thisRegistrations = this.myMap.get(token);
|
427
|
+
let registrations = thisRegistrations || this.myParent?.getAllFromParent(token, name);
|
428
|
+
if (registrations && name !== undefined) {
|
429
|
+
registrations = registrations.filter((r)=>r.name === name);
|
430
|
+
assert(registrations.length < 2, `internal error: more than one registration named '${name}'`);
|
431
|
+
}
|
432
|
+
return registrations ?? [];
|
375
433
|
}
|
376
434
|
}
|
377
435
|
// @internal
|
@@ -469,9 +527,6 @@ function isDisposable(value) {
|
|
469
527
|
getAllCached(token) {
|
470
528
|
this.checkDisposed();
|
471
529
|
const registrations = this.myTokenRegistry.getAll(token);
|
472
|
-
if (!registrations) {
|
473
|
-
return [];
|
474
|
-
}
|
475
530
|
const values = new Set();
|
476
531
|
for (const registration of registrations){
|
477
532
|
const value = registration.value;
|
@@ -493,50 +548,21 @@ function isDisposable(value) {
|
|
493
548
|
}
|
494
549
|
return Array.from(values);
|
495
550
|
}
|
496
|
-
isRegistered(token) {
|
551
|
+
isRegistered(token, name) {
|
497
552
|
this.checkDisposed();
|
498
|
-
return this.myTokenRegistry.get(token) !== undefined;
|
499
|
-
}
|
500
|
-
registerClass(token, Class, options) {
|
501
|
-
// This mess will go away once/if we remove the register method
|
502
|
-
// in favor of the multiple specialized ones
|
503
|
-
if (Class) {
|
504
|
-
const ctor = Class ?? token;
|
505
|
-
this.register(token, {
|
506
|
-
useClass: ctor
|
507
|
-
}, options);
|
508
|
-
} else {
|
509
|
-
this.register(token);
|
510
|
-
}
|
511
|
-
}
|
512
|
-
registerFactory(token, factory, options) {
|
513
|
-
this.register(token, {
|
514
|
-
useFactory: factory
|
515
|
-
}, options);
|
516
|
-
}
|
517
|
-
registerValue(token, value) {
|
518
|
-
this.register(token, {
|
519
|
-
useValue: value
|
520
|
-
});
|
521
|
-
}
|
522
|
-
registerAlias(targetToken, aliasTokens) {
|
523
|
-
// De-duplicate tokens
|
524
|
-
for (const alias of new Set(aliasTokens)){
|
525
|
-
this.register(alias, {
|
526
|
-
useExisting: targetToken
|
527
|
-
});
|
528
|
-
}
|
553
|
+
return this.myTokenRegistry.get(token, name) !== undefined;
|
529
554
|
}
|
530
555
|
register(...args) {
|
531
556
|
this.checkDisposed();
|
532
|
-
if (args.length
|
557
|
+
if (args.length === 1) {
|
533
558
|
const Class = args[0];
|
534
559
|
const metadata = getMetadata(Class);
|
535
560
|
const registration = {
|
561
|
+
name: metadata.name,
|
536
562
|
// The provider is of type ClassProvider, initialized by getMetadata
|
537
563
|
provider: metadata.provider,
|
538
564
|
options: {
|
539
|
-
scope: metadata.scope ?? this.myOptions.defaultScope
|
565
|
+
scope: metadata.scope?.value ?? this.myOptions.defaultScope
|
540
566
|
},
|
541
567
|
dependencies: metadata.dependencies
|
542
568
|
};
|
@@ -553,18 +579,19 @@ function isDisposable(value) {
|
|
553
579
|
}
|
554
580
|
// Eager-instantiate only if the class is container-scoped
|
555
581
|
if (metadata.eagerInstantiate && registration.options?.scope === Scope.Container) {
|
556
|
-
this.
|
582
|
+
this.resolveProviderValue(registration, registration.provider);
|
557
583
|
}
|
558
584
|
} else {
|
559
585
|
const [token, provider, options] = args;
|
560
586
|
if (isClassProvider(provider)) {
|
561
587
|
const metadata = getMetadata(provider.useClass);
|
562
588
|
const registration = {
|
589
|
+
// An explicit provider name overrides what is specified via @Named
|
590
|
+
name: metadata.name ?? provider.name,
|
563
591
|
provider: metadata.provider,
|
564
592
|
options: {
|
565
|
-
//
|
566
|
-
|
567
|
-
scope: metadata.scope ?? this.myOptions.defaultScope,
|
593
|
+
// Explicit registration options override what is specified via class decorators (e.g., @Scoped)
|
594
|
+
scope: metadata.scope?.value ?? this.myOptions.defaultScope,
|
568
595
|
...options
|
569
596
|
},
|
570
597
|
dependencies: metadata.dependencies
|
@@ -572,13 +599,15 @@ function isDisposable(value) {
|
|
572
599
|
this.myTokenRegistry.set(token, registration);
|
573
600
|
// Eager-instantiate only if the provided class is container-scoped
|
574
601
|
if (metadata.eagerInstantiate && registration.options?.scope === Scope.Container) {
|
575
|
-
this.
|
602
|
+
this.resolveProviderValue(registration, registration.provider);
|
576
603
|
}
|
577
604
|
} else {
|
578
|
-
|
605
|
+
const existingProvider = isExistingProvider(provider);
|
606
|
+
if (existingProvider) {
|
579
607
|
assert(token !== provider.useExisting, `the useExisting token ${token.name} cannot be the same as the token being registered`);
|
580
608
|
}
|
581
609
|
this.myTokenRegistry.set(token, {
|
610
|
+
name: existingProvider ? undefined : provider.name,
|
582
611
|
provider: provider,
|
583
612
|
options: options
|
584
613
|
});
|
@@ -586,12 +615,9 @@ function isDisposable(value) {
|
|
586
615
|
}
|
587
616
|
return this;
|
588
617
|
}
|
589
|
-
unregister(token) {
|
618
|
+
unregister(token, name) {
|
590
619
|
this.checkDisposed();
|
591
|
-
const registrations = this.myTokenRegistry.delete(token);
|
592
|
-
if (!registrations) {
|
593
|
-
return [];
|
594
|
-
}
|
620
|
+
const registrations = this.myTokenRegistry.delete(token, name);
|
595
621
|
const values = new Set();
|
596
622
|
for (const registration of registrations){
|
597
623
|
const value = registration.value;
|
@@ -601,22 +627,31 @@ function isDisposable(value) {
|
|
601
627
|
}
|
602
628
|
return Array.from(values);
|
603
629
|
}
|
604
|
-
resolve(token,
|
630
|
+
resolve(token, optionalOrName, name) {
|
605
631
|
this.checkDisposed();
|
606
|
-
|
632
|
+
let localOptional;
|
633
|
+
let localName;
|
634
|
+
if (typeof optionalOrName === "string") {
|
635
|
+
localName = optionalOrName;
|
636
|
+
} else {
|
637
|
+
localOptional = optionalOrName;
|
638
|
+
localName = name;
|
639
|
+
}
|
640
|
+
const registration = this.myTokenRegistry.get(token, localName);
|
607
641
|
if (registration) {
|
608
|
-
return this.resolveRegistration(token, registration);
|
642
|
+
return this.resolveRegistration(token, registration, localName);
|
609
643
|
}
|
610
644
|
if (isConstructor(token)) {
|
611
|
-
return this.instantiateClass(token,
|
645
|
+
return this.instantiateClass(token, localOptional);
|
612
646
|
}
|
613
|
-
return
|
647
|
+
return optionalOrName ? undefined : throwUnregisteredError(token);
|
614
648
|
}
|
615
649
|
resolveAll(token, optional) {
|
616
650
|
this.checkDisposed();
|
617
651
|
const registrations = this.myTokenRegistry.getAll(token);
|
618
|
-
if (registrations) {
|
619
|
-
return registrations
|
652
|
+
if (registrations.length > 0) {
|
653
|
+
return registrations //
|
654
|
+
.map((registration)=>this.resolveRegistration(token, registration)).filter((value)=>value != null);
|
620
655
|
}
|
621
656
|
if (isConstructor(token)) {
|
622
657
|
const instance = this.instantiateClass(token, optional);
|
@@ -652,12 +687,12 @@ function isDisposable(value) {
|
|
652
687
|
// Allow values to be GCed
|
653
688
|
disposedRefs.clear();
|
654
689
|
}
|
655
|
-
resolveRegistration(token, registration) {
|
690
|
+
resolveRegistration(token, registration, name) {
|
656
691
|
let currRegistration = registration;
|
657
692
|
let currProvider = currRegistration.provider;
|
658
693
|
while(isExistingProvider(currProvider)){
|
659
694
|
const targetToken = currProvider.useExisting;
|
660
|
-
currRegistration = this.myTokenRegistry.get(targetToken);
|
695
|
+
currRegistration = this.myTokenRegistry.get(targetToken, name);
|
661
696
|
if (!currRegistration) {
|
662
697
|
throwExistingUnregisteredError(token, targetToken);
|
663
698
|
}
|
@@ -688,7 +723,7 @@ function isDisposable(value) {
|
|
688
723
|
metadata.eagerInstantiate = eagerInstantiate;
|
689
724
|
}
|
690
725
|
}
|
691
|
-
const scope = this.resolveScope(metadata.scope);
|
726
|
+
const scope = this.resolveScope(metadata.scope?.value);
|
692
727
|
if (optional && scope === Scope.Container) {
|
693
728
|
// It would not be possible to resolve the class in container scope,
|
694
729
|
// as that would require prior registration.
|
@@ -788,7 +823,7 @@ function isDisposable(value) {
|
|
788
823
|
}
|
789
824
|
}
|
790
825
|
resolveScope(scope = this.myOptions.defaultScope, context = useInjectionContext()) {
|
791
|
-
if (scope
|
826
|
+
if (scope === Scope.Inherited) {
|
792
827
|
const dependentFrame = context?.resolution.stack.peek();
|
793
828
|
return dependentFrame?.scope || Scope.Transient;
|
794
829
|
}
|
@@ -798,7 +833,7 @@ function isDisposable(value) {
|
|
798
833
|
const dependencies = registration.dependencies;
|
799
834
|
if (dependencies) {
|
800
835
|
assert(isClassProvider(registration.provider), `internal error: not a ClassProvider`);
|
801
|
-
const ctorDeps = dependencies.constructor;
|
836
|
+
const ctorDeps = dependencies.constructor.filter((d)=>d.appliedBy);
|
802
837
|
if (ctorDeps.length > 0) {
|
803
838
|
// Let's check if all necessary constructor parameters are decorated.
|
804
839
|
// If not, we cannot safely create an instance.
|
@@ -809,13 +844,13 @@ function isDisposable(value) {
|
|
809
844
|
});
|
810
845
|
return ctorDeps.sort((a, b)=>a.index - b.index).map((dep)=>{
|
811
846
|
const token = dep.tokenRef.getRefToken();
|
812
|
-
switch(dep.
|
847
|
+
switch(dep.appliedBy){
|
813
848
|
case "Inject":
|
814
|
-
return this.resolve(token);
|
849
|
+
return this.resolve(token, dep.name);
|
815
850
|
case "InjectAll":
|
816
851
|
return this.resolveAll(token);
|
817
852
|
case "Optional":
|
818
|
-
return this.resolve(token, true);
|
853
|
+
return this.resolve(token, true, dep.name);
|
819
854
|
case "OptionalAll":
|
820
855
|
return this.resolveAll(token, true);
|
821
856
|
}
|
@@ -830,7 +865,9 @@ function isDisposable(value) {
|
|
830
865
|
assert(isClassProvider(registration.provider), `internal error: not a ClassProvider`);
|
831
866
|
const ctor = registration.provider.useClass;
|
832
867
|
// Perform method injection
|
833
|
-
for (const
|
868
|
+
for (const entry of dependencies.methods){
|
869
|
+
const key = entry[0];
|
870
|
+
const methodDeps = entry[1].filter((d)=>d.appliedBy);
|
834
871
|
// Let's check if all necessary method parameters are decorated.
|
835
872
|
// If not, we cannot safely invoke the method.
|
836
873
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
@@ -841,13 +878,13 @@ function isDisposable(value) {
|
|
841
878
|
});
|
842
879
|
const args = methodDeps.sort((a, b)=>a.index - b.index).map((dep)=>{
|
843
880
|
const token = dep.tokenRef.getRefToken();
|
844
|
-
switch(dep.
|
881
|
+
switch(dep.appliedBy){
|
845
882
|
case "Inject":
|
846
|
-
return injectBy(instance, token);
|
883
|
+
return injectBy(instance, token, dep.name);
|
847
884
|
case "InjectAll":
|
848
885
|
return injectAll(token);
|
849
886
|
case "Optional":
|
850
|
-
return optionalBy(instance, token);
|
887
|
+
return optionalBy(instance, token, dep.name);
|
851
888
|
case "OptionalAll":
|
852
889
|
return optionalAll(token);
|
853
890
|
}
|
@@ -878,7 +915,7 @@ function isDisposable(value) {
|
|
878
915
|
*
|
879
916
|
* @example
|
880
917
|
* ```ts
|
881
|
-
* @AutoRegister
|
918
|
+
* @AutoRegister()
|
882
919
|
* class Wizard {}
|
883
920
|
*
|
884
921
|
* const wizard = container.resolve(Wizard);
|
@@ -886,32 +923,45 @@ function isDisposable(value) {
|
|
886
923
|
* ```
|
887
924
|
*
|
888
925
|
* @__NO_SIDE_EFFECTS__
|
889
|
-
*/ function AutoRegister(
|
890
|
-
|
891
|
-
|
926
|
+
*/ function AutoRegister() {
|
927
|
+
return function(Class) {
|
928
|
+
const metadata = getMetadata(Class);
|
929
|
+
metadata.autoRegister = true;
|
930
|
+
};
|
892
931
|
}
|
893
932
|
|
894
933
|
/**
|
895
|
-
* Class decorator that
|
896
|
-
* in the container
|
934
|
+
* Class decorator that sets the class scope to **Container** and enables
|
935
|
+
* eager instantiation when the class is registered in the container.
|
897
936
|
*
|
898
937
|
* This causes the container to immediately create and cache the instance of the class,
|
899
938
|
* instead of deferring instantiation until the first resolution.
|
900
939
|
*
|
901
940
|
* @example
|
902
941
|
* ```ts
|
903
|
-
* @EagerInstantiate
|
904
|
-
* @Scoped(Scope.Container)
|
942
|
+
* @EagerInstantiate()
|
905
943
|
* class Wizard {}
|
906
944
|
*
|
907
|
-
* //
|
908
|
-
*
|
945
|
+
* // Wizard is registered with Container scope, and an instance
|
946
|
+
* // is immediately created and cached by the container
|
947
|
+
* container.register(Wizard);
|
909
948
|
* ```
|
910
949
|
*
|
911
950
|
* @__NO_SIDE_EFFECTS__
|
912
|
-
*/ function EagerInstantiate(
|
913
|
-
|
914
|
-
|
951
|
+
*/ function EagerInstantiate() {
|
952
|
+
return function(Class) {
|
953
|
+
const metadata = getMetadata(Class);
|
954
|
+
const currentScope = metadata.scope;
|
955
|
+
assert(!currentScope || currentScope.value === Scope.Container, ()=>{
|
956
|
+
const { value, appliedBy } = currentScope;
|
957
|
+
return `class ${Class.name}: Scope.${value} was already set by @${appliedBy},\n ` + `but @EagerInstantiate is trying to set a conflicting Scope.Container.\n ` + `Only one decorator should set the class scope, or all must agree on the same value.`;
|
958
|
+
});
|
959
|
+
metadata.eagerInstantiate = true;
|
960
|
+
metadata.scope = {
|
961
|
+
value: Scope.Container,
|
962
|
+
appliedBy: "EagerInstantiate"
|
963
|
+
};
|
964
|
+
};
|
915
965
|
}
|
916
966
|
|
917
967
|
function forwardRef(token) {
|
@@ -943,41 +993,31 @@ function isTokenRef(value) {
|
|
943
993
|
return value && typeof value === "object" && typeof value.getRefToken === "function";
|
944
994
|
}
|
945
995
|
|
946
|
-
|
947
|
-
|
948
|
-
//
|
996
|
+
// @internal
|
997
|
+
function updateParameterMetadata(decorator, target, propertyKey, parameterIndex, updateFn) {
|
998
|
+
// Error out immediately if the decorator has been applied to a static method
|
949
999
|
if (propertyKey !== undefined && typeof target === "function") {
|
950
|
-
assert(false, `@${decorator} cannot be used on static
|
1000
|
+
assert(false, `@${decorator} cannot be used on static method ${target.name}.${String(propertyKey)}`);
|
951
1001
|
}
|
952
|
-
const tokenRef = isTokenRef(token) ? token : forwardRef(()=>token);
|
953
1002
|
if (propertyKey === undefined) {
|
954
1003
|
// Constructor
|
955
1004
|
const metadata = getMetadata(target);
|
956
|
-
metadata.
|
957
|
-
|
958
|
-
tokenRef: tokenRef,
|
959
|
-
index: parameterIndex
|
960
|
-
});
|
1005
|
+
const dependency = metadata.getConstructorDependency(parameterIndex);
|
1006
|
+
updateFn(dependency);
|
961
1007
|
} else {
|
962
|
-
//
|
1008
|
+
// Instance method
|
963
1009
|
const metadata = getMetadata(target.constructor);
|
964
|
-
const
|
965
|
-
|
966
|
-
if (dep === undefined) {
|
967
|
-
dep = [];
|
968
|
-
methods.set(propertyKey, dep);
|
969
|
-
}
|
970
|
-
dep.push({
|
971
|
-
decorator: decorator,
|
972
|
-
tokenRef: tokenRef,
|
973
|
-
index: parameterIndex
|
974
|
-
});
|
1010
|
+
const dependency = metadata.getMethodDependency(propertyKey, parameterIndex);
|
1011
|
+
updateFn(dependency);
|
975
1012
|
}
|
976
1013
|
}
|
977
1014
|
|
978
1015
|
function Inject(token) {
|
979
1016
|
return function(target, propertyKey, parameterIndex) {
|
980
|
-
|
1017
|
+
updateParameterMetadata("Inject", target, propertyKey, parameterIndex, (dependency)=>{
|
1018
|
+
dependency.appliedBy = "Inject";
|
1019
|
+
dependency.tokenRef = isTokenRef(token) ? token : forwardRef(()=>token);
|
1020
|
+
});
|
981
1021
|
};
|
982
1022
|
}
|
983
1023
|
|
@@ -1003,19 +1043,66 @@ function Inject(token) {
|
|
1003
1043
|
|
1004
1044
|
function InjectAll(token) {
|
1005
1045
|
return function(target, propertyKey, parameterIndex) {
|
1006
|
-
|
1046
|
+
updateParameterMetadata("InjectAll", target, propertyKey, parameterIndex, (dependency)=>{
|
1047
|
+
dependency.appliedBy = "InjectAll";
|
1048
|
+
dependency.tokenRef = isTokenRef(token) ? token : forwardRef(()=>token);
|
1049
|
+
});
|
1050
|
+
};
|
1051
|
+
}
|
1052
|
+
|
1053
|
+
/**
|
1054
|
+
* Qualifies a class or an injected parameter with a unique name.
|
1055
|
+
*
|
1056
|
+
* This allows the container to distinguish between multiple implementations
|
1057
|
+
* of the same interface or type during registration and injection.
|
1058
|
+
*
|
1059
|
+
* @example
|
1060
|
+
* ```ts
|
1061
|
+
* @Named("dumbledore")
|
1062
|
+
* class Dumbledore implements Wizard {}
|
1063
|
+
*
|
1064
|
+
* // Register Dumbledore with Type<Wizard>
|
1065
|
+
* container.register(IWizard, { useClass: Dumbledore });
|
1066
|
+
* const dumbledore = container.resolve(IWizard, "dumbledore");
|
1067
|
+
* ```
|
1068
|
+
*
|
1069
|
+
* @__NO_SIDE_EFFECTS__
|
1070
|
+
*/ function Named(name) {
|
1071
|
+
if (!name.trim()) {
|
1072
|
+
assert(false, "the @Named qualifier cannot be empty or blank");
|
1073
|
+
}
|
1074
|
+
return function(target, propertyKey, parameterIndex) {
|
1075
|
+
if (parameterIndex === undefined) {
|
1076
|
+
// The decorator has been applied to the class
|
1077
|
+
const ctor = target;
|
1078
|
+
const metadata = getMetadata(ctor);
|
1079
|
+
assert(!metadata.name, `a @Named('${metadata.name}') qualifier has already been applied to ${ctor.name}`);
|
1080
|
+
metadata.name = name;
|
1081
|
+
} else {
|
1082
|
+
// The decorator has been applied to a method parameter
|
1083
|
+
updateParameterMetadata("Named", target, propertyKey, parameterIndex, (dependency)=>{
|
1084
|
+
assert(!dependency.name, `a @Named('${dependency.name}') qualifier has already been applied to the parameter`);
|
1085
|
+
dependency.name = name;
|
1086
|
+
});
|
1087
|
+
}
|
1007
1088
|
};
|
1008
1089
|
}
|
1009
1090
|
|
1010
1091
|
function Optional(token) {
|
1011
1092
|
return function(target, propertyKey, parameterIndex) {
|
1012
|
-
|
1093
|
+
updateParameterMetadata("Optional", target, propertyKey, parameterIndex, (dependency)=>{
|
1094
|
+
dependency.appliedBy = "Optional";
|
1095
|
+
dependency.tokenRef = isTokenRef(token) ? token : forwardRef(()=>token);
|
1096
|
+
});
|
1013
1097
|
};
|
1014
1098
|
}
|
1015
1099
|
|
1016
1100
|
function OptionalAll(token) {
|
1017
1101
|
return function(target, propertyKey, parameterIndex) {
|
1018
|
-
|
1102
|
+
updateParameterMetadata("OptionalAll", target, propertyKey, parameterIndex, (dependency)=>{
|
1103
|
+
dependency.appliedBy = "OptionalAll";
|
1104
|
+
dependency.tokenRef = isTokenRef(token) ? token : forwardRef(()=>token);
|
1105
|
+
});
|
1019
1106
|
};
|
1020
1107
|
}
|
1021
1108
|
|
@@ -1043,7 +1130,16 @@ function OptionalAll(token) {
|
|
1043
1130
|
*/ function Scoped(scope) {
|
1044
1131
|
return function(Class) {
|
1045
1132
|
const metadata = getMetadata(Class);
|
1046
|
-
|
1133
|
+
const currentScope = metadata.scope;
|
1134
|
+
assert(!currentScope || currentScope.value === scope, ()=>{
|
1135
|
+
const { value, appliedBy } = currentScope;
|
1136
|
+
const by = appliedBy === "Scoped" ? `another @${appliedBy} decorator` : `@${appliedBy}`;
|
1137
|
+
return `class ${Class.name}: Scope.${value} was already set by ${by},\n ` + `but @Scoped is trying to set a conflicting Scope.${scope}.\n ` + `Only one decorator should set the class scope, or all must agree on the same value.`;
|
1138
|
+
});
|
1139
|
+
metadata.scope = {
|
1140
|
+
value: scope,
|
1141
|
+
appliedBy: "Scoped"
|
1142
|
+
};
|
1047
1143
|
};
|
1048
1144
|
}
|
1049
1145
|
|
@@ -1144,6 +1240,7 @@ exports.Inject = Inject;
|
|
1144
1240
|
exports.InjectAll = InjectAll;
|
1145
1241
|
exports.Injectable = Injectable;
|
1146
1242
|
exports.Injector = Injector;
|
1243
|
+
exports.Named = Named;
|
1147
1244
|
exports.Optional = Optional;
|
1148
1245
|
exports.OptionalAll = OptionalAll;
|
1149
1246
|
exports.Scope = Scope;
|