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