@lppedd/di-wise-neo 0.3.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.
Files changed (42) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +488 -0
  3. package/dist/cjs/index.d.ts +772 -0
  4. package/dist/cjs/index.js +1055 -0
  5. package/dist/cjs/index.js.map +1 -0
  6. package/dist/es/index.d.mts +772 -0
  7. package/dist/es/index.mjs +1037 -0
  8. package/dist/es/index.mjs.map +1 -0
  9. package/package.json +77 -0
  10. package/src/container.ts +292 -0
  11. package/src/decorators/autoRegister.ts +24 -0
  12. package/src/decorators/decorators.ts +47 -0
  13. package/src/decorators/index.ts +7 -0
  14. package/src/decorators/inject.ts +42 -0
  15. package/src/decorators/injectAll.ts +45 -0
  16. package/src/decorators/injectable.ts +61 -0
  17. package/src/decorators/optional.ts +39 -0
  18. package/src/decorators/optionalAll.ts +42 -0
  19. package/src/decorators/scoped.ts +32 -0
  20. package/src/defaultContainer.ts +519 -0
  21. package/src/errors.ts +47 -0
  22. package/src/index.ts +16 -0
  23. package/src/inject.ts +88 -0
  24. package/src/injectAll.ts +21 -0
  25. package/src/injectionContext.ts +46 -0
  26. package/src/injector.ts +117 -0
  27. package/src/metadata.ts +41 -0
  28. package/src/middleware.ts +85 -0
  29. package/src/optional.ts +65 -0
  30. package/src/optionalAll.ts +19 -0
  31. package/src/provider.ts +61 -0
  32. package/src/scope.ts +8 -0
  33. package/src/token.ts +84 -0
  34. package/src/tokenRegistry.ts +165 -0
  35. package/src/tokensRef.ts +46 -0
  36. package/src/utils/context.ts +19 -0
  37. package/src/utils/disposable.ts +10 -0
  38. package/src/utils/invariant.ts +6 -0
  39. package/src/utils/keyedStack.ts +26 -0
  40. package/src/utils/typeName.ts +28 -0
  41. package/src/utils/weakRefMap.ts +28 -0
  42. package/src/valueRef.ts +3 -0
@@ -0,0 +1,1055 @@
1
+ Object.defineProperty(exports, '__esModule', { value: true });
2
+
3
+ // @internal
4
+ function assert(condition, message) {
5
+ if (!condition) {
6
+ const resolvedMessage = typeof message === "string" ? message : message();
7
+ throw new Error(tag(resolvedMessage));
8
+ }
9
+ }
10
+ // @internal
11
+ function expectNever(value) {
12
+ throw new TypeError(tag(`unexpected value ${String(value)}`));
13
+ }
14
+ // @internal
15
+ function throwUnregisteredError(token) {
16
+ throw new Error(tag(`unregistered token ${token.name}`));
17
+ }
18
+ // @internal
19
+ function throwExistingUnregisteredError(sourceToken, targetTokenOrError) {
20
+ let message = tag(`token resolution error encountered while resolving ${sourceToken.name}`);
21
+ if (isError(targetTokenOrError)) {
22
+ message += `\n [cause] ${untag(targetTokenOrError.message)}`;
23
+ } else {
24
+ message += `\n [cause] the aliased token ${targetTokenOrError.name} is not registered`;
25
+ }
26
+ throw new Error(message);
27
+ }
28
+ function isError(value) {
29
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
30
+ return value && value.stack && value.message && typeof value.message === "string";
31
+ }
32
+ function tag(message) {
33
+ return `[di-wise-neo] ${message}`;
34
+ }
35
+ function untag(message) {
36
+ return message.startsWith("[di-wise-neo]") //
37
+ ? message.substring(13).trimStart() : message;
38
+ }
39
+
40
+ // @internal
41
+ function createInjectionContext() {
42
+ let current = null;
43
+ function provide(next) {
44
+ const prev = current;
45
+ current = next;
46
+ return ()=>current = prev;
47
+ }
48
+ function use() {
49
+ return current;
50
+ }
51
+ return [
52
+ provide,
53
+ use
54
+ ];
55
+ }
56
+
57
+ // @internal
58
+ function invariant(condition) {
59
+ if (!condition) {
60
+ throw new Error("invariant violation");
61
+ }
62
+ }
63
+
64
+ // @internal
65
+ class KeyedStack {
66
+ has(key) {
67
+ return this.myKeys.has(key);
68
+ }
69
+ peek() {
70
+ const entry = this.myEntries.at(-1);
71
+ return entry?.value;
72
+ }
73
+ push(key, value) {
74
+ invariant(!this.has(key));
75
+ this.myKeys.add(key);
76
+ this.myEntries.push({
77
+ key,
78
+ value
79
+ });
80
+ return ()=>{
81
+ this.myEntries.pop();
82
+ this.myKeys.delete(key);
83
+ };
84
+ }
85
+ constructor(){
86
+ this.myEntries = new Array();
87
+ this.myKeys = new WeakSet();
88
+ }
89
+ }
90
+
91
+ // @internal
92
+ class WeakRefMap {
93
+ get(key) {
94
+ const ref = this.myMap.get(key);
95
+ if (ref) {
96
+ const value = ref.deref();
97
+ if (value) {
98
+ return value;
99
+ }
100
+ this.myMap.delete(key);
101
+ }
102
+ }
103
+ set(key, value) {
104
+ invariant(!this.get(key));
105
+ this.myMap.set(key, new WeakRef(value));
106
+ return ()=>{
107
+ this.myMap.delete(key);
108
+ };
109
+ }
110
+ constructor(){
111
+ this.myMap = new WeakMap();
112
+ }
113
+ }
114
+
115
+ // @internal
116
+ function createResolution() {
117
+ return {
118
+ stack: new KeyedStack(),
119
+ values: new WeakRefMap(),
120
+ dependents: new WeakRefMap()
121
+ };
122
+ }
123
+ // @internal
124
+ const [provideInjectionContext, useInjectionContext] = createInjectionContext();
125
+ // @internal
126
+ function ensureInjectionContext(fn) {
127
+ const context = useInjectionContext();
128
+ assert(context, `${fn.name}() can only be invoked within an injection context`);
129
+ return context;
130
+ }
131
+
132
+ function inject(token) {
133
+ const context = ensureInjectionContext(inject);
134
+ return context.container.resolve(token);
135
+ }
136
+ function injectBy(thisArg, token) {
137
+ const context = ensureInjectionContext(injectBy);
138
+ const resolution = context.resolution;
139
+ const currentFrame = resolution.stack.peek();
140
+ if (!currentFrame) {
141
+ return inject(token);
142
+ }
143
+ const currentRef = {
144
+ current: thisArg
145
+ };
146
+ const cleanup = resolution.dependents.set(currentFrame.provider, currentRef);
147
+ try {
148
+ return inject(token);
149
+ } finally{
150
+ cleanup();
151
+ }
152
+ }
153
+
154
+ function injectAll(token) {
155
+ const context = ensureInjectionContext(injectAll);
156
+ return context.container.resolveAll(token);
157
+ }
158
+
159
+ // @internal
160
+ function getMetadata(Class) {
161
+ let metadata = metadataMap.get(Class);
162
+ if (!metadata) {
163
+ metadataMap.set(Class, metadata = {
164
+ tokensRef: {
165
+ getRefTokens: ()=>new Set()
166
+ },
167
+ provider: {
168
+ useClass: Class
169
+ },
170
+ dependencies: {
171
+ constructor: [],
172
+ methods: new Map()
173
+ }
174
+ });
175
+ }
176
+ return metadata;
177
+ }
178
+ const metadataMap = new WeakMap();
179
+
180
+ function optional(token) {
181
+ const context = ensureInjectionContext(optional);
182
+ return context.container.resolve(token, true);
183
+ }
184
+ function optionalBy(thisArg, token) {
185
+ const context = ensureInjectionContext(optionalBy);
186
+ const resolution = context.resolution;
187
+ const currentFrame = resolution.stack.peek();
188
+ if (!currentFrame) {
189
+ return optional(token);
190
+ }
191
+ const currentRef = {
192
+ current: thisArg
193
+ };
194
+ const cleanup = resolution.dependents.set(currentFrame.provider, currentRef);
195
+ try {
196
+ return optional(token);
197
+ } finally{
198
+ cleanup();
199
+ }
200
+ }
201
+
202
+ function optionalAll(token) {
203
+ const context = ensureInjectionContext(optionalAll);
204
+ return context.container.resolveAll(token, true);
205
+ }
206
+
207
+ // @internal
208
+ function isClassProvider(provider) {
209
+ return "useClass" in provider;
210
+ }
211
+ // @internal
212
+ function isExistingProvider(provider) {
213
+ return "useExisting" in provider;
214
+ }
215
+ // @internal
216
+ function isFactoryProvider(provider) {
217
+ return "useFactory" in provider;
218
+ }
219
+ // @internal
220
+ function isValueProvider(provider) {
221
+ return "useValue" in provider;
222
+ }
223
+
224
+ const Scope = {
225
+ Inherited: "Inherited",
226
+ Transient: "Transient",
227
+ Resolution: "Resolution",
228
+ Container: "Container"
229
+ };
230
+
231
+ /**
232
+ * Type API.
233
+ */ /**
234
+ * Create a type token.
235
+ *
236
+ * @example
237
+ * ```ts
238
+ * const Spell = Type<Spell>("Spell");
239
+ * ```
240
+ *
241
+ * @__NO_SIDE_EFFECTS__
242
+ */ function Type(typeName) {
243
+ const type = {
244
+ name: `Type<${typeName}>`,
245
+ inter: Type,
246
+ union: Type,
247
+ toString () {
248
+ return type.name;
249
+ }
250
+ };
251
+ return type;
252
+ }
253
+ // @internal
254
+ function isConstructor(token) {
255
+ return typeof token == "function";
256
+ }
257
+
258
+ // @internal
259
+ function getTypeName(value) {
260
+ if (typeof value == "string") {
261
+ return `"${value}"`;
262
+ }
263
+ if (typeof value == "function") {
264
+ return value.name && `typeof ${value.name}` || "Function";
265
+ }
266
+ if (typeof value == "object") {
267
+ if (value === null) {
268
+ return "null";
269
+ }
270
+ const proto = Object.getPrototypeOf(value);
271
+ if (proto && proto !== Object.prototype) {
272
+ const constructor = proto.constructor;
273
+ if (typeof constructor == "function" && constructor.name) {
274
+ return constructor.name;
275
+ }
276
+ }
277
+ }
278
+ return typeof value;
279
+ }
280
+
281
+ // @internal
282
+ class TokenRegistry {
283
+ constructor(parent){
284
+ this.parent = parent;
285
+ this.myMap = new Map();
286
+ }
287
+ get(token) {
288
+ // To clarify, at(-1) means we take the last added registration for this token
289
+ return this.getAll(token)?.at(-1);
290
+ }
291
+ getAll(token) {
292
+ const internal = internals.get(token);
293
+ return internal && [
294
+ internal
295
+ ] || this.getAllFromParent(token);
296
+ }
297
+ set(token, registration) {
298
+ assert(!internals.has(token), `cannot register reserved token ${token.name}`);
299
+ let registrations = this.myMap.get(token);
300
+ if (!registrations) {
301
+ this.myMap.set(token, registrations = []);
302
+ }
303
+ registrations.push(registration);
304
+ }
305
+ delete(token) {
306
+ const registrations = this.myMap.get(token);
307
+ this.myMap.delete(token);
308
+ return registrations;
309
+ }
310
+ deleteAll() {
311
+ const tokens = Array.from(this.myMap.keys());
312
+ const registrations = Array.from(this.myMap.values()).flat();
313
+ this.myMap.clear();
314
+ return [
315
+ tokens,
316
+ registrations
317
+ ];
318
+ }
319
+ clearRegistrations() {
320
+ const values = new Set();
321
+ for (const registrations of this.myMap.values()){
322
+ for(let i = 0; i < registrations.length; i++){
323
+ const registration = registrations[i];
324
+ const value = registration.value;
325
+ if (value) {
326
+ values.add(value.current);
327
+ }
328
+ registrations[i] = {
329
+ ...registration,
330
+ value: undefined
331
+ };
332
+ }
333
+ }
334
+ return Array.from(values);
335
+ }
336
+ getAllFromParent(token) {
337
+ const registrations = this.myMap.get(token);
338
+ return registrations || this.parent?.getAllFromParent(token);
339
+ }
340
+ }
341
+ // @internal
342
+ function isBuilder(provider) {
343
+ return builders.has(provider);
344
+ }
345
+ /**
346
+ * Create a one-off type token from a factory function.
347
+ *
348
+ * @example
349
+ * ```ts
350
+ * class Wizard {
351
+ * wand = inject(
352
+ * Build(() => {
353
+ * const wand = inject(Wand);
354
+ * wand.owner = this;
355
+ * // ...
356
+ * return wand;
357
+ * }),
358
+ * );
359
+ * }
360
+ * ```
361
+ *
362
+ * @__NO_SIDE_EFFECTS__
363
+ */ function Build(factory) {
364
+ const token = Type(`Build<${getTypeName(factory)}>`);
365
+ const provider = {
366
+ useFactory: factory
367
+ };
368
+ internals.set(token, {
369
+ provider: provider,
370
+ options: {
371
+ scope: Scope.Transient
372
+ }
373
+ });
374
+ builders.add(provider);
375
+ return token;
376
+ }
377
+ const internals = new WeakMap();
378
+ const builders = new WeakSet();
379
+
380
+ // @internal
381
+ // @internal
382
+ function isDisposable(value) {
383
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
384
+ return value && typeof value === "object" && typeof value.dispose === "function";
385
+ }
386
+
387
+ /**
388
+ * The default implementation of a di-wise-neo {@link Container}.
389
+ */ class DefaultContainer {
390
+ constructor(myParent, options){
391
+ this.myParent = myParent;
392
+ // eslint-disable-next-line no-use-before-define
393
+ this.myChildren = new Set();
394
+ this.myDisposed = false;
395
+ this.myOptions = {
396
+ autoRegister: false,
397
+ defaultScope: Scope.Inherited,
398
+ ...options
399
+ };
400
+ this.myTokenRegistry = new TokenRegistry(this.myParent?.myTokenRegistry);
401
+ }
402
+ get registry() {
403
+ return this.myTokenRegistry;
404
+ }
405
+ get options() {
406
+ return {
407
+ ...this.myOptions
408
+ };
409
+ }
410
+ get parent() {
411
+ return this.myParent;
412
+ }
413
+ get isDisposed() {
414
+ return this.myDisposed;
415
+ }
416
+ createChild(options) {
417
+ this.checkDisposed();
418
+ const container = new DefaultContainer(this, {
419
+ ...this.myOptions,
420
+ ...options
421
+ });
422
+ this.myChildren.add(container);
423
+ return container;
424
+ }
425
+ clearCache() {
426
+ this.checkDisposed();
427
+ return this.myTokenRegistry.clearRegistrations();
428
+ }
429
+ getCached(token) {
430
+ this.checkDisposed();
431
+ const registration = this.myTokenRegistry.get(token);
432
+ return registration?.value?.current;
433
+ }
434
+ getAllCached(token) {
435
+ this.checkDisposed();
436
+ const registrations = this.myTokenRegistry.getAll(token);
437
+ if (!registrations) {
438
+ return [];
439
+ }
440
+ const values = new Set();
441
+ for (const registration of registrations){
442
+ const value = registration.value;
443
+ if (value) {
444
+ values.add(value.current);
445
+ }
446
+ }
447
+ return Array.from(values);
448
+ }
449
+ resetRegistry() {
450
+ this.checkDisposed();
451
+ const [, registrations] = this.myTokenRegistry.deleteAll();
452
+ const values = new Set();
453
+ for (const registration of registrations){
454
+ const value = registration.value;
455
+ if (value) {
456
+ values.add(value.current);
457
+ }
458
+ }
459
+ return Array.from(values);
460
+ }
461
+ isRegistered(token) {
462
+ this.checkDisposed();
463
+ return this.myTokenRegistry.get(token) !== undefined;
464
+ }
465
+ register(...args) {
466
+ this.checkDisposed();
467
+ if (args.length == 1) {
468
+ const Class = args[0];
469
+ const metadata = getMetadata(Class);
470
+ // Register the class itself
471
+ this.myTokenRegistry.set(Class, {
472
+ // The provider is of type ClassProvider, initialized by getMetadata
473
+ provider: metadata.provider,
474
+ options: {
475
+ scope: metadata.scope
476
+ },
477
+ dependencies: metadata.dependencies
478
+ });
479
+ // Register the additional tokens specified via class decorators.
480
+ // These tokens will point to the original Class token and will have the same scope.
481
+ for (const token of metadata.tokensRef.getRefTokens()){
482
+ this.myTokenRegistry.set(token, {
483
+ provider: {
484
+ useExisting: Class
485
+ }
486
+ });
487
+ }
488
+ } else {
489
+ const [token, provider, options] = args;
490
+ if (isClassProvider(provider)) {
491
+ const Class = provider.useClass;
492
+ const metadata = getMetadata(Class);
493
+ this.myTokenRegistry.set(token, {
494
+ provider: metadata.provider,
495
+ options: {
496
+ // The explicit registration options override what is specified
497
+ // via class decorators (e.g., @Scoped)
498
+ scope: metadata.scope,
499
+ ...options
500
+ },
501
+ dependencies: metadata.dependencies
502
+ });
503
+ } else {
504
+ if (isExistingProvider(provider)) {
505
+ assert(token !== provider.useExisting, `the useExisting token ${token.name} cannot be the same as the token being registered`);
506
+ }
507
+ this.myTokenRegistry.set(token, {
508
+ provider: provider,
509
+ options: options
510
+ });
511
+ }
512
+ }
513
+ return this;
514
+ }
515
+ unregister(token) {
516
+ this.checkDisposed();
517
+ const registrations = this.myTokenRegistry.delete(token);
518
+ if (!registrations) {
519
+ return [];
520
+ }
521
+ const values = new Set();
522
+ for (const registration of registrations){
523
+ const value = registration.value;
524
+ if (value) {
525
+ values.add(value.current);
526
+ }
527
+ }
528
+ return Array.from(values);
529
+ }
530
+ resolve(token, optional) {
531
+ this.checkDisposed();
532
+ const registration = this.myTokenRegistry.get(token);
533
+ if (registration) {
534
+ return this.resolveRegistration(token, registration);
535
+ }
536
+ if (isConstructor(token)) {
537
+ return this.instantiateClass(token, optional);
538
+ }
539
+ return optional ? undefined : throwUnregisteredError(token);
540
+ }
541
+ resolveAll(token, optional) {
542
+ this.checkDisposed();
543
+ const registrations = this.myTokenRegistry.getAll(token);
544
+ if (registrations) {
545
+ return registrations.map((registration)=>this.resolveRegistration(token, registration)).filter((value)=>value != null);
546
+ }
547
+ if (isConstructor(token)) {
548
+ const instance = this.instantiateClass(token, optional);
549
+ return instance === undefined // = could not resolve, but since it is optional
550
+ ? [] : [
551
+ instance
552
+ ];
553
+ }
554
+ return optional ? [] : throwUnregisteredError(token);
555
+ }
556
+ dispose() {
557
+ if (this.myDisposed) {
558
+ return;
559
+ }
560
+ // Dispose children containers first
561
+ for (const child of this.myChildren){
562
+ child.dispose();
563
+ }
564
+ this.myChildren.clear();
565
+ // Remove ourselves from our parent container
566
+ this.myParent?.myChildren?.delete(this);
567
+ this.myDisposed = true;
568
+ const [, registrations] = this.myTokenRegistry.deleteAll();
569
+ const disposedRefs = new Set();
570
+ // Dispose all resolved (aka instantiated) tokens that implement the Disposable interface
571
+ for (const registration of registrations){
572
+ const value = registration.value?.current;
573
+ if (isDisposable(value) && !disposedRefs.has(value)) {
574
+ disposedRefs.add(value);
575
+ value.dispose();
576
+ }
577
+ }
578
+ // Allow values to be GCed
579
+ disposedRefs.clear();
580
+ }
581
+ resolveRegistration(token, registration) {
582
+ let currRegistration = registration;
583
+ let currProvider = currRegistration.provider;
584
+ while(isExistingProvider(currProvider)){
585
+ const targetToken = currProvider.useExisting;
586
+ currRegistration = this.myTokenRegistry.get(targetToken);
587
+ if (!currRegistration) {
588
+ throwExistingUnregisteredError(token, targetToken);
589
+ }
590
+ currProvider = currRegistration.provider;
591
+ }
592
+ try {
593
+ return this.resolveProviderValue(currRegistration, currProvider);
594
+ } catch (e) {
595
+ // If we were trying to resolve a token registered via ExistingProvider,
596
+ // we must add the cause of the error to the message
597
+ if (isExistingProvider(registration.provider)) {
598
+ throwExistingUnregisteredError(token, e);
599
+ }
600
+ throw e;
601
+ }
602
+ }
603
+ instantiateClass(Class, optional) {
604
+ const metadata = getMetadata(Class);
605
+ if (metadata.autoRegister ?? this.myOptions.autoRegister) {
606
+ this.register(Class);
607
+ return this.resolve(Class);
608
+ }
609
+ const scope = this.resolveScope(metadata.scope);
610
+ if (optional && scope === Scope.Container) {
611
+ // It would not be possible to resolve the class in container scope,
612
+ // as that would require prior registration.
613
+ // However, since resolution is marked optional, we simply return undefined.
614
+ return undefined;
615
+ }
616
+ assert(scope !== Scope.Container, `unregistered class ${Class.name} cannot be resolved in container scope`);
617
+ const registration = {
618
+ provider: metadata.provider,
619
+ options: {
620
+ scope: scope
621
+ },
622
+ dependencies: metadata.dependencies
623
+ };
624
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
625
+ return this.resolveScopedValue(registration, (args)=>new Class(...args));
626
+ }
627
+ resolveProviderValue(registration, provider) {
628
+ assert(registration.provider === provider, "internal error: mismatching provider");
629
+ if (isClassProvider(provider)) {
630
+ const Class = provider.useClass;
631
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
632
+ return this.resolveScopedValue(registration, (args)=>new Class(...args));
633
+ }
634
+ if (isFactoryProvider(provider)) {
635
+ const factory = provider.useFactory;
636
+ return this.resolveScopedValue(registration, factory);
637
+ }
638
+ if (isValueProvider(provider)) {
639
+ return provider.useValue;
640
+ }
641
+ if (isExistingProvider(provider)) {
642
+ assert(false, "internal error: unexpected ExistingProvider");
643
+ }
644
+ expectNever(provider);
645
+ }
646
+ resolveScopedValue(registration, factory) {
647
+ let context = useInjectionContext();
648
+ if (!context || context.container !== this) {
649
+ context = {
650
+ container: this,
651
+ resolution: createResolution()
652
+ };
653
+ }
654
+ const resolution = context.resolution;
655
+ const provider = registration.provider;
656
+ const options = registration.options;
657
+ if (resolution.stack.has(provider)) {
658
+ const dependentRef = resolution.dependents.get(provider);
659
+ assert(dependentRef, "circular dependency detected");
660
+ return dependentRef.current;
661
+ }
662
+ const scope = this.resolveScope(options?.scope, context);
663
+ const cleanups = [
664
+ provideInjectionContext(context),
665
+ !isBuilder(provider) && resolution.stack.push(provider, {
666
+ provider,
667
+ scope
668
+ })
669
+ ];
670
+ try {
671
+ switch(scope){
672
+ case Scope.Container:
673
+ {
674
+ const valueRef = registration.value;
675
+ if (valueRef) {
676
+ return valueRef.current;
677
+ }
678
+ const args = this.resolveConstructorDependencies(registration);
679
+ const value = this.injectDependencies(registration, factory(args));
680
+ registration.value = {
681
+ current: value
682
+ };
683
+ return value;
684
+ }
685
+ case Scope.Resolution:
686
+ {
687
+ const valueRef = resolution.values.get(provider);
688
+ if (valueRef) {
689
+ return valueRef.current;
690
+ }
691
+ const args = this.resolveConstructorDependencies(registration);
692
+ const value = this.injectDependencies(registration, factory(args));
693
+ resolution.values.set(provider, {
694
+ current: value
695
+ });
696
+ return value;
697
+ }
698
+ case Scope.Transient:
699
+ {
700
+ const args = this.resolveConstructorDependencies(registration);
701
+ return this.injectDependencies(registration, factory(args));
702
+ }
703
+ }
704
+ } finally{
705
+ cleanups.forEach((cleanup)=>cleanup && cleanup());
706
+ }
707
+ }
708
+ resolveScope(scope = this.myOptions.defaultScope, context = useInjectionContext()) {
709
+ if (scope == Scope.Inherited) {
710
+ const dependentFrame = context?.resolution.stack.peek();
711
+ return dependentFrame?.scope || Scope.Transient;
712
+ }
713
+ return scope;
714
+ }
715
+ resolveConstructorDependencies(registration) {
716
+ const dependencies = registration.dependencies;
717
+ if (dependencies) {
718
+ assert(isClassProvider(registration.provider), `internal error: not a ClassProvider`);
719
+ const ctorDeps = dependencies.constructor;
720
+ if (ctorDeps.length > 0) {
721
+ // Let's check if all necessary constructor parameters are decorated.
722
+ // If not, we cannot safely create an instance.
723
+ const ctor = registration.provider.useClass;
724
+ assert(ctor.length === ctorDeps.length, ()=>{
725
+ const msg = `expected ${ctor.length} decorated constructor parameters in ${ctor.name}`;
726
+ return msg + `, but found ${ctorDeps.length}`;
727
+ });
728
+ return ctorDeps.sort((a, b)=>a.index - b.index).map((dep)=>{
729
+ const token = dep.tokenRef.getRefToken();
730
+ switch(dep.decorator){
731
+ case "Inject":
732
+ return this.resolve(token);
733
+ case "InjectAll":
734
+ return this.resolveAll(token);
735
+ case "Optional":
736
+ return this.resolve(token, true);
737
+ case "OptionalAll":
738
+ return this.resolveAll(token, true);
739
+ }
740
+ });
741
+ }
742
+ }
743
+ return [];
744
+ }
745
+ injectDependencies(registration, instance) {
746
+ const dependencies = registration.dependencies;
747
+ if (dependencies) {
748
+ assert(isClassProvider(registration.provider), `internal error: not a ClassProvider`);
749
+ const ctor = registration.provider.useClass;
750
+ // Perform method injection
751
+ for (const [key, methodDeps] of dependencies.methods){
752
+ // Let's check if all necessary method parameters are decorated.
753
+ // If not, we cannot safely invoke the method.
754
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
755
+ const method = instance[key];
756
+ assert(methodDeps.length === method.length, ()=>{
757
+ const msg = `expected ${method.length} decorated method parameters`;
758
+ return msg + ` in ${ctor.name}.${String(key)}, but found ${methodDeps.length}`;
759
+ });
760
+ const args = methodDeps.sort((a, b)=>a.index - b.index).map((dep)=>{
761
+ const token = dep.tokenRef.getRefToken();
762
+ switch(dep.decorator){
763
+ case "Inject":
764
+ return injectBy(instance, token);
765
+ case "InjectAll":
766
+ return injectAll(token);
767
+ case "Optional":
768
+ return optionalBy(instance, token);
769
+ case "OptionalAll":
770
+ return optionalAll(token);
771
+ }
772
+ });
773
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
774
+ method.bind(instance)(...args);
775
+ }
776
+ }
777
+ return instance;
778
+ }
779
+ checkDisposed() {
780
+ assert(!this.myDisposed, "the container is disposed");
781
+ }
782
+ }
783
+
784
+ /**
785
+ * Creates a new container.
786
+ */ function createContainer(options = {
787
+ autoRegister: false,
788
+ defaultScope: Scope.Inherited
789
+ }) {
790
+ return new DefaultContainer(undefined, options);
791
+ }
792
+
793
+ /**
794
+ * Class decorator for enabling auto-registration of an unregistered class
795
+ * when first resolving it from the container.
796
+ *
797
+ * @example
798
+ * ```ts
799
+ * @AutoRegister()
800
+ * class Wizard {}
801
+ *
802
+ * const wizard = container.resolve(Wizard);
803
+ * container.isRegistered(Wizard); // => true
804
+ * ```
805
+ *
806
+ * @__NO_SIDE_EFFECTS__
807
+ */ function AutoRegister(enable = true) {
808
+ return function(Class) {
809
+ const metadata = getMetadata(Class);
810
+ metadata.autoRegister = enable;
811
+ };
812
+ }
813
+
814
+ function forwardRef(token) {
815
+ return {
816
+ getRefTokens: ()=>{
817
+ // Normalize the single token here, so that we don't have
818
+ // to do it at every getRefTokens call site
819
+ const tokenOrTokens = token();
820
+ const tokensArray = Array.isArray(tokenOrTokens) ? tokenOrTokens : [
821
+ tokenOrTokens
822
+ ];
823
+ return new Set(tokensArray);
824
+ },
825
+ getRefToken: ()=>{
826
+ const tokenOrTokens = token();
827
+ assert(!Array.isArray(tokenOrTokens), "internal error: ref tokens should be a single token");
828
+ return tokenOrTokens;
829
+ }
830
+ };
831
+ }
832
+ // @internal
833
+ function isTokensRef(value) {
834
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
835
+ return value && typeof value === "object" && typeof value.getRefTokens === "function";
836
+ }
837
+ // @internal
838
+ function isTokenRef(value) {
839
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
840
+ return value && typeof value === "object" && typeof value.getRefToken === "function";
841
+ }
842
+
843
+ function processDecoratedParameter(decorator, token, target, propertyKey, parameterIndex) {
844
+ // Error out immediately if the decorator has been applied
845
+ // to a static property or a static method
846
+ if (propertyKey !== undefined && typeof target === "function") {
847
+ assert(false, `@${decorator} cannot be used on static member ${target.name}.${String(propertyKey)}`);
848
+ }
849
+ const tokenRef = isTokenRef(token) ? token : forwardRef(()=>token);
850
+ if (propertyKey === undefined) {
851
+ // Constructor
852
+ const metadata = getMetadata(target);
853
+ metadata.dependencies.constructor.push({
854
+ decorator: decorator,
855
+ tokenRef: tokenRef,
856
+ index: parameterIndex
857
+ });
858
+ } else {
859
+ // Normal instance method
860
+ const metadata = getMetadata(target.constructor);
861
+ const methods = metadata.dependencies.methods;
862
+ let dep = methods.get(propertyKey);
863
+ if (dep === undefined) {
864
+ dep = [];
865
+ methods.set(propertyKey, dep);
866
+ }
867
+ dep.push({
868
+ decorator: decorator,
869
+ tokenRef: tokenRef,
870
+ index: parameterIndex
871
+ });
872
+ }
873
+ }
874
+
875
+ function Inject(token) {
876
+ return function(target, propertyKey, parameterIndex) {
877
+ processDecoratedParameter("Inject", token, target, propertyKey, parameterIndex);
878
+ };
879
+ }
880
+
881
+ /**
882
+ * @__NO_SIDE_EFFECTS__
883
+ */ function Injectable(...args) {
884
+ return function(Class) {
885
+ const metadata = getMetadata(Class);
886
+ const arg0 = args[0];
887
+ const tokensRef = isTokensRef(arg0) ? arg0 : forwardRef(()=>args);
888
+ const existingTokensRef = metadata.tokensRef;
889
+ metadata.tokensRef = {
890
+ getRefTokens: ()=>{
891
+ const existingTokens = existingTokensRef.getRefTokens();
892
+ for (const token of tokensRef.getRefTokens()){
893
+ existingTokens.add(token);
894
+ }
895
+ return existingTokens;
896
+ }
897
+ };
898
+ };
899
+ }
900
+
901
+ function InjectAll(token) {
902
+ return function(target, propertyKey, parameterIndex) {
903
+ processDecoratedParameter("InjectAll", token, target, propertyKey, parameterIndex);
904
+ };
905
+ }
906
+
907
+ function Optional(token) {
908
+ return function(target, propertyKey, parameterIndex) {
909
+ processDecoratedParameter("Optional", token, target, propertyKey, parameterIndex);
910
+ };
911
+ }
912
+
913
+ function OptionalAll(token) {
914
+ return function(target, propertyKey, parameterIndex) {
915
+ processDecoratedParameter("OptionalAll", token, target, propertyKey, parameterIndex);
916
+ };
917
+ }
918
+
919
+ /**
920
+ * Class decorator for setting the scope of the decorated type when registering it.
921
+ *
922
+ * The scope set by this decorator can be overridden by explicit registration options, if provided.
923
+ *
924
+ * @example
925
+ * ```ts
926
+ * @Scoped(Scope.Container)
927
+ * class Wizard {}
928
+ *
929
+ * container.register(Wizard);
930
+ *
931
+ * // Under the hood
932
+ * container.register(
933
+ * Wizard,
934
+ * { useClass: Wizard },
935
+ * { scope: Scope.Container },
936
+ * );
937
+ * ```
938
+ *
939
+ * @__NO_SIDE_EFFECTS__
940
+ */ function Scoped(scope) {
941
+ return function(Class) {
942
+ const metadata = getMetadata(Class);
943
+ metadata.scope = scope;
944
+ };
945
+ }
946
+
947
+ /**
948
+ * Injector token for dynamic injections.
949
+ *
950
+ * @example
951
+ * ```ts
952
+ * class Wizard {
953
+ * private injector = inject(Injector);
954
+ * private wand?: Wand;
955
+ *
956
+ * getWand(): Wand {
957
+ * return (this.wand ??= this.injector.inject(Wand));
958
+ * }
959
+ * }
960
+ *
961
+ * const wizard = container.resolve(Wizard);
962
+ * wizard.getWand(); // => Wand
963
+ * ```
964
+ */ const Injector = /*@__PURE__*/ Build(function Injector() {
965
+ const context = ensureInjectionContext(Injector);
966
+ const resolution = context.resolution;
967
+ const dependentFrame = resolution.stack.peek();
968
+ const dependentRef = dependentFrame && resolution.dependents.get(dependentFrame.provider);
969
+ function withCurrentContext(fn) {
970
+ if (useInjectionContext()) {
971
+ return fn();
972
+ }
973
+ const cleanups = [
974
+ provideInjectionContext(context),
975
+ dependentFrame && resolution.stack.push(dependentFrame.provider, dependentFrame),
976
+ dependentRef && resolution.dependents.set(dependentFrame.provider, dependentRef)
977
+ ];
978
+ try {
979
+ return fn();
980
+ } finally{
981
+ for (const cleanup of cleanups){
982
+ cleanup?.();
983
+ }
984
+ }
985
+ }
986
+ return {
987
+ inject: (token)=>withCurrentContext(()=>inject(token)),
988
+ injectAll: (token)=>withCurrentContext(()=>injectAll(token)),
989
+ optional: (token)=>withCurrentContext(()=>optional(token)),
990
+ optionalAll: (token)=>withCurrentContext(()=>optionalAll(token))
991
+ };
992
+ });
993
+
994
+ /**
995
+ * Apply middleware functions to a container.
996
+ *
997
+ * Middlewares are applied in array order, but execute in reverse order.
998
+ *
999
+ * @example
1000
+ * ```ts
1001
+ * const container = applyMiddleware(
1002
+ * createContainer(),
1003
+ * [A, B],
1004
+ * );
1005
+ * ```
1006
+ *
1007
+ * The execution order will be:
1008
+ *
1009
+ * 1. B before
1010
+ * 2. A before
1011
+ * 3. original function
1012
+ * 4. A after
1013
+ * 5. B after
1014
+ *
1015
+ * This allows outer middlewares to wrap and control the behavior of inner middlewares.
1016
+ */ function applyMiddleware(container, middlewares) {
1017
+ const composer = {
1018
+ use (key, wrap) {
1019
+ // We need to bind the 'this' context of the function to the container
1020
+ // before passing it to the middleware wrapper.
1021
+ //
1022
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access,@typescript-eslint/no-unsafe-call
1023
+ const fn = container[key].bind(container);
1024
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
1025
+ container[key] = wrap(fn);
1026
+ return composer;
1027
+ }
1028
+ };
1029
+ const api = container.api ||= {
1030
+ ...container
1031
+ };
1032
+ for (const middleware of middlewares){
1033
+ middleware(composer, api);
1034
+ }
1035
+ return container;
1036
+ }
1037
+
1038
+ exports.AutoRegister = AutoRegister;
1039
+ exports.Build = Build;
1040
+ exports.Inject = Inject;
1041
+ exports.InjectAll = InjectAll;
1042
+ exports.Injectable = Injectable;
1043
+ exports.Injector = Injector;
1044
+ exports.Optional = Optional;
1045
+ exports.OptionalAll = OptionalAll;
1046
+ exports.Scope = Scope;
1047
+ exports.Scoped = Scoped;
1048
+ exports.Type = Type;
1049
+ exports.applyMiddleware = applyMiddleware;
1050
+ exports.createContainer = createContainer;
1051
+ exports.forwardRef = forwardRef;
1052
+ exports.inject = inject;
1053
+ exports.injectAll = injectAll;
1054
+ exports.injectBy = injectBy;
1055
+ //# sourceMappingURL=index.js.map