@martel/calyx 1.10.1 → 1.12.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/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [1.12.0](https://github.com/bmartel/calyx/compare/v1.11.0...v1.12.0) (2026-07-01)
2
+
3
+
4
+ ### Features
5
+
6
+ * **core:** implement core NestJS feature parity extensions ([ba69f26](https://github.com/bmartel/calyx/commit/ba69f266d1b6ead3ab2ce4f6c5b112258131ed75))
7
+
8
+ # [1.11.0](https://github.com/bmartel/calyx/compare/v1.10.1...v1.11.0) (2026-07-01)
9
+
10
+
11
+ ### Features
12
+
13
+ * **graphql,openapi:** support schema-first compiling, custom context and validation schema auto-enrichment ([e8dcce7](https://github.com/bmartel/calyx/commit/e8dcce78d75097ac88a8a3a984b5834d4d7f1818))
14
+
1
15
  ## [1.10.1](https://github.com/bmartel/calyx/compare/v1.10.0...v1.10.1) (2026-07-01)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@martel/calyx",
3
- "version": "1.10.1",
3
+ "version": "1.12.0",
4
4
  "description": "High-performance Bun-native NestJS-compatible framework",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -18,7 +18,9 @@ export class CacheInterceptor implements NestInterceptor {
18
18
  }
19
19
 
20
20
  const url = new URL(req.url);
21
- const key = `http_cache::${url.pathname}${url.search}`;
21
+ const handler = context.getHandler();
22
+ const key = Reflect.getMetadata('cache_metadata_key', handler) ?? `http_cache::${url.pathname}${url.search}`;
23
+ const ttl = Reflect.getMetadata('cache_metadata_ttl', handler);
22
24
 
23
25
  const cached = await this.cacheService.get(key);
24
26
  if (cached !== undefined) {
@@ -26,7 +28,7 @@ export class CacheInterceptor implements NestInterceptor {
26
28
  }
27
29
 
28
30
  const result = await next.handle();
29
- await this.cacheService.set(key, result);
31
+ await this.cacheService.set(key, result, ttl);
30
32
  return result;
31
33
  }
32
34
  }
@@ -0,0 +1,4 @@
1
+ import { SetMetadata } from '../core/decorators.ts';
2
+
3
+ export const CacheKey = (key: string) => SetMetadata('cache_metadata_key', key);
4
+ export const CacheTTL = (ttl: number) => SetMetadata('cache_metadata_ttl', ttl);
@@ -1,3 +1,4 @@
1
1
  export * from './cache.service.ts';
2
2
  export * from './cache.interceptor.ts';
3
3
  export * from './cache.module.ts';
4
+ export * from './decorators.ts';
@@ -47,14 +47,14 @@ class ContainerModuleRef extends ModuleRef {
47
47
  const requestContext = contextId instanceof Map ? contextId : new Map<any, any>();
48
48
  const strict = options?.strict ?? true;
49
49
  if (strict) {
50
- return this.container.resolveTokenInModuleContext(this.moduleClass, token, requestContext);
50
+ return await this.container.resolveTokenInModuleContextAsync(this.moduleClass, token, requestContext);
51
51
  } else {
52
- return this.container.resolveTokenGlobally(token, requestContext);
52
+ return await this.container.resolveTokenGloballyAsync(token, requestContext);
53
53
  }
54
54
  }
55
55
 
56
56
  async create<T>(type: Type<T>): Promise<T> {
57
- return this.container.instantiateClass(type, this.moduleClass);
57
+ return await this.container.instantiateClassAsync(type, this.moduleClass);
58
58
  }
59
59
  }
60
60
 
@@ -288,6 +288,8 @@ export class CalyxContainer {
288
288
  private createInstanceFromProvider(provider: Provider, targetModuleClass: any, requestContext?: Map<any, any>): any {
289
289
  if (typeof provider !== 'function' && 'useValue' in provider) {
290
290
  return provider.useValue;
291
+ } else if (typeof provider !== 'function' && 'useExisting' in provider) {
292
+ return this.resolveTokenInModuleContext(targetModuleClass, resolveForwardRef(provider.useExisting), requestContext);
291
293
  } else if (typeof provider !== 'function' && 'useClass' in provider) {
292
294
  return this.instantiateClass(provider.useClass, targetModuleClass, requestContext);
293
295
  } else if (typeof provider !== 'function' && 'useFactory' in provider) {
@@ -415,13 +417,31 @@ export class CalyxContainer {
415
417
  throw new Error(`calyx DI: Instance of "${String(token.name ?? token)}" not found in any module context`);
416
418
  }
417
419
 
418
- bootstrap(rootModule: any) {
420
+ bootstrap(rootModule: any): void | Promise<void> {
419
421
  this.addModule(rootModule);
420
-
421
- // Compute scopes of all providers and controllers (including bubble-up)
422
422
  this.resolveProviderAndControllerScopes();
423
423
 
424
- // Instantiate all singleton providers
424
+ let hasAsync = false;
425
+ for (const record of this.modules.values()) {
426
+ for (const provider of record.providers.values()) {
427
+ if (typeof provider !== 'function' && 'useFactory' in provider) {
428
+ if (provider.useFactory.constructor.name === 'AsyncFunction') {
429
+ hasAsync = true;
430
+ break;
431
+ }
432
+ }
433
+ }
434
+ if (hasAsync) break;
435
+ }
436
+
437
+ if (hasAsync) {
438
+ return this.bootstrapAsync(rootModule);
439
+ }
440
+
441
+ this.bootstrapSync(rootModule);
442
+ }
443
+
444
+ private bootstrapSync(rootModule: any) {
425
445
  for (const [moduleClass, record] of this.modules.entries()) {
426
446
  for (const token of record.providers.keys()) {
427
447
  const scope = this.providerScopes.get(token) ?? Scope.DEFAULT;
@@ -431,7 +451,6 @@ export class CalyxContainer {
431
451
  }
432
452
  }
433
453
 
434
- // Instantiate all singleton controllers
435
454
  for (const [moduleClass, record] of this.modules.entries()) {
436
455
  for (const controllerClass of record.controllers) {
437
456
  const scope = this.controllerScopes.get(controllerClass) ?? Scope.DEFAULT;
@@ -441,7 +460,6 @@ export class CalyxContainer {
441
460
  }
442
461
  }
443
462
 
444
- // Instantiate module classes themselves
445
463
  for (const [moduleClass, record] of this.modules.entries()) {
446
464
  if (record.instances.get(moduleClass) === null) {
447
465
  const instance = this.instantiateClass(moduleClass, moduleClass);
@@ -452,6 +470,35 @@ export class CalyxContainer {
452
470
  this.compileControllerFactories();
453
471
  }
454
472
 
473
+ private async bootstrapAsync(rootModule: any) {
474
+ for (const [moduleClass, record] of this.modules.entries()) {
475
+ for (const token of record.providers.keys()) {
476
+ const scope = this.providerScopes.get(token) ?? Scope.DEFAULT;
477
+ if (scope === Scope.DEFAULT) {
478
+ await this.resolveTokenInModuleContextAsync(moduleClass, token);
479
+ }
480
+ }
481
+ }
482
+
483
+ for (const [moduleClass, record] of this.modules.entries()) {
484
+ for (const controllerClass of record.controllers) {
485
+ const scope = this.controllerScopes.get(controllerClass) ?? Scope.DEFAULT;
486
+ if (scope === Scope.DEFAULT) {
487
+ await this.resolveControllerAsync(moduleClass, controllerClass);
488
+ }
489
+ }
490
+ }
491
+
492
+ for (const [moduleClass, record] of this.modules.entries()) {
493
+ if (record.instances.get(moduleClass) === null) {
494
+ const instance = await this.instantiateClassAsync(moduleClass, moduleClass);
495
+ record.instances.set(moduleClass, instance);
496
+ }
497
+ }
498
+
499
+ this.compileControllerFactories();
500
+ }
501
+
455
502
  private resolveController(moduleClass: any, controllerClass: any): any {
456
503
  const record = this.modules.get(moduleClass)!;
457
504
  if (record.instances.has(controllerClass)) {
@@ -462,6 +509,16 @@ export class CalyxContainer {
462
509
  return instance;
463
510
  }
464
511
 
512
+ private async resolveControllerAsync(moduleClass: any, controllerClass: any): Promise<any> {
513
+ const record = this.modules.get(moduleClass)!;
514
+ if (record.instances.has(controllerClass)) {
515
+ return record.instances.get(controllerClass);
516
+ }
517
+ const instance = await this.instantiateClassAsync(controllerClass, moduleClass);
518
+ record.instances.set(controllerClass, instance);
519
+ return instance;
520
+ }
521
+
465
522
  resolveTokenGlobally<T>(token: InjectionToken, requestContext?: Map<any, any>): T {
466
523
  const scope = this.providerScopes.get(token) ?? Scope.DEFAULT;
467
524
  if (scope === Scope.REQUEST && requestContext?.has(token)) {
@@ -475,6 +532,180 @@ export class CalyxContainer {
475
532
  return this.getGlobalOrAnyInstance(token);
476
533
  }
477
534
 
535
+ async resolveTokenGloballyAsync<T>(token: InjectionToken, requestContext?: Map<any, any>): Promise<T> {
536
+ const scope = this.providerScopes.get(token) ?? Scope.DEFAULT;
537
+ if (scope === Scope.REQUEST && requestContext?.has(token)) {
538
+ return requestContext.get(token);
539
+ }
540
+ for (const [moduleClass, record] of this.modules.entries()) {
541
+ if (record.providers.has(token)) {
542
+ return await this.resolveTokenInModuleContextAsync(moduleClass, token, requestContext);
543
+ }
544
+ }
545
+ return this.getGlobalOrAnyInstance(token);
546
+ }
547
+
548
+ async resolveTokenInModuleContextAsync<T>(moduleClass: any, token: InjectionToken, requestContext?: Map<any, any>): Promise<T> {
549
+ const isResolving = this.resolvingStack.some(
550
+ (item) => item.moduleClass === moduleClass && item.token === token
551
+ );
552
+ if (isResolving) {
553
+ const path = this.resolvingStack
554
+ .map((item) => `${item.moduleClass.name ?? item.moduleClass}::${String(item.token.name ?? item.token)}`)
555
+ .join(' -> ');
556
+ throw new Error(`Circular dependency detected: ${path} -> ${moduleClass.name ?? moduleClass}::${String(token.name ?? token)}`);
557
+ }
558
+
559
+ this.resolvingStack.push({ moduleClass, token });
560
+
561
+ try {
562
+ const record = this.modules.get(moduleClass);
563
+ if (!record) {
564
+ throw new Error(`Module ${moduleClass.name || moduleClass} is not registered in the container`);
565
+ }
566
+
567
+ if (token === REQUEST) {
568
+ if (!requestContext || !requestContext.has(REQUEST)) {
569
+ throw new Error(`calyx DI: REQUEST token resolved outside of a request context`);
570
+ }
571
+ return requestContext.get(REQUEST);
572
+ }
573
+
574
+ if (token === ModuleRef) {
575
+ const instance = record.instances.get(ModuleRef);
576
+ if (instance) return instance;
577
+ const moduleRefInstance = new ContainerModuleRef(this, moduleClass);
578
+ record.instances.set(ModuleRef, moduleRefInstance);
579
+ return moduleRefInstance as any;
580
+ }
581
+
582
+ if (token === moduleClass) {
583
+ const instance = record.instances.get(moduleClass);
584
+ if (instance) return instance;
585
+ const instantiatedModule = await this.instantiateClassAsync(moduleClass, moduleClass, requestContext);
586
+ record.instances.set(moduleClass, instantiatedModule);
587
+ return instantiatedModule;
588
+ }
589
+
590
+ const resolution = this.findProviderDefinition(moduleClass, token);
591
+ if (!resolution) {
592
+ const currentResolving = this.resolvingStack[this.resolvingStack.length - 2];
593
+ if (currentResolving) {
594
+ const parentClass = currentResolving.token;
595
+ if (typeof parentClass === 'function') {
596
+ const optionalParams: Set<number> = Reflect.getMetadata(METADATA_KEYS.OPTIONAL_PARAMS, parentClass) || new Set();
597
+ const injectTokens: Map<number, InjectionToken> = Reflect.getMetadata(METADATA_KEYS.INJECT_TOKENS, parentClass) || new Map();
598
+ const paramTypes = Reflect.getMetadata('design:paramtypes', parentClass) || [];
599
+
600
+ let isOptional = false;
601
+ for (let i = 0; i < paramTypes.length; i++) {
602
+ const pToken = injectTokens.get(i) ?? paramTypes[i];
603
+ const resolvedPToken = resolveForwardRef(pToken);
604
+ if (resolvedPToken === token && optionalParams.has(i)) {
605
+ isOptional = true;
606
+ break;
607
+ }
608
+ }
609
+ if (isOptional) {
610
+ return undefined as any;
611
+ }
612
+ }
613
+ }
614
+ throw new Error(`calyx DI: Cannot resolve dependency "${String(token.name ?? token)}" in module ${moduleClass.name || moduleClass}`);
615
+ }
616
+
617
+ const { targetModuleClass, provider } = resolution;
618
+ const targetRecord = this.modules.get(targetModuleClass)!;
619
+
620
+ const scope = this.providerScopes.get(token) ?? Scope.DEFAULT;
621
+
622
+ if (scope === Scope.REQUEST) {
623
+ if (!requestContext) {
624
+ throw new Error(`calyx DI: Cannot resolve request-scoped provider "${String(token.name ?? token)}" without request context.`);
625
+ }
626
+ if (requestContext.has(token)) {
627
+ return requestContext.get(token);
628
+ }
629
+ const instance = await this.createInstanceFromProviderAsync(provider, targetModuleClass, requestContext);
630
+ requestContext.set(token, instance);
631
+ return instance;
632
+ }
633
+
634
+ if (scope === Scope.TRANSIENT) {
635
+ return await this.createInstanceFromProviderAsync(provider, targetModuleClass, requestContext);
636
+ }
637
+
638
+ if (targetRecord.instances.has(token)) {
639
+ return targetRecord.instances.get(token);
640
+ }
641
+
642
+ const instance = await this.createInstanceFromProviderAsync(provider, targetModuleClass, requestContext);
643
+ targetRecord.instances.set(token, instance);
644
+ return instance;
645
+ } finally {
646
+ this.resolvingStack.pop();
647
+ }
648
+ }
649
+
650
+ private async createInstanceFromProviderAsync(provider: Provider, targetModuleClass: any, requestContext?: Map<any, any>): Promise<any> {
651
+ if (typeof provider !== 'function' && 'useValue' in provider) {
652
+ return provider.useValue;
653
+ } else if (typeof provider !== 'function' && 'useExisting' in provider) {
654
+ return await this.resolveTokenInModuleContextAsync(targetModuleClass, resolveForwardRef(provider.useExisting), requestContext);
655
+ } else if (typeof provider !== 'function' && 'useClass' in provider) {
656
+ return await this.instantiateClassAsync(provider.useClass, targetModuleClass, requestContext);
657
+ } else if (typeof provider !== 'function' && 'useFactory' in provider) {
658
+ const injectTokens = provider.inject || [];
659
+ const args = await Promise.all(
660
+ injectTokens.map((t) => this.resolveTokenInModuleContextAsync(targetModuleClass, resolveForwardRef(t), requestContext))
661
+ );
662
+ const res = provider.useFactory(...args);
663
+ return res instanceof Promise ? await res : res;
664
+ } else {
665
+ return await this.instantiateClassAsync(provider as Type<any>, targetModuleClass, requestContext);
666
+ }
667
+ }
668
+
669
+ public async instantiateClassAsync(Class: Type<any>, moduleClass: any, requestContext?: Map<any, any>): Promise<any> {
670
+ if (
671
+ !Reflect.hasMetadata(METADATA_KEYS.INJECTABLE, Class) &&
672
+ !Reflect.hasMetadata(METADATA_KEYS.CONTROLLER, Class) &&
673
+ !Reflect.hasMetadata(METADATA_KEYS.MODULE, Class)
674
+ ) {
675
+ throw new Error(`Class ${Class.name} is missing @Injectable(), @Controller() or @Module() decorator`);
676
+ }
677
+
678
+ const paramTypes: any[] = Reflect.getMetadata('design:paramtypes', Class) || [];
679
+ const injectTokens: Map<number, InjectionToken> = Reflect.getMetadata(METADATA_KEYS.INJECT_TOKENS, Class) || new Map();
680
+ const optionalParams: Set<number> = Reflect.getMetadata(METADATA_KEYS.OPTIONAL_PARAMS, Class) || new Set();
681
+
682
+ const args = await Promise.all(
683
+ paramTypes.map(async (paramType, i) => {
684
+ const token = injectTokens.get(i) ?? paramType;
685
+ const resolvedToken = resolveForwardRef(token);
686
+
687
+ try {
688
+ return await this.resolveTokenInModuleContextAsync(moduleClass, resolvedToken, requestContext);
689
+ } catch (err) {
690
+ if (optionalParams.has(i)) {
691
+ return undefined;
692
+ }
693
+ throw err;
694
+ }
695
+ })
696
+ );
697
+
698
+ const instance = new Class(...args);
699
+
700
+ const propertyInjects: Map<string | symbol, InjectionToken> =
701
+ Reflect.getOwnMetadata(METADATA_KEYS.PROPERTY_INJECTS, Class) || new Map();
702
+ for (const [propertyKey, token] of propertyInjects.entries()) {
703
+ instance[propertyKey] = await this.resolveTokenInModuleContextAsync(moduleClass, token, requestContext);
704
+ }
705
+
706
+ return instance;
707
+ }
708
+
478
709
  resolveControllerInRequestContext(moduleClass: any, controllerClass: any, requestContext: Map<any, any>): any {
479
710
  const scope = this.controllerScopes.get(controllerClass) ?? Scope.DEFAULT;
480
711
  if (scope === Scope.REQUEST) {
@@ -668,6 +899,8 @@ export class CalyxContainer {
668
899
  let valExpr = '';
669
900
  if (typeof provider !== 'function' && 'useValue' in provider) {
670
901
  valExpr = `c[${getClosureIdx(provider.useValue)}]`;
902
+ } else if (typeof provider !== 'function' && 'useExisting' in provider) {
903
+ return compileToken(resolveForwardRef(provider.useExisting), targetModuleClass);
671
904
  } else {
672
905
  const ClassToInstantiate = typeof provider === 'function'
673
906
  ? provider
package/src/core/index.ts CHANGED
@@ -3,3 +3,5 @@ export * from './decorators.ts';
3
3
  export * from './module-ref.ts';
4
4
  export * from './container.ts';
5
5
  export * from './reflector.ts';
6
+ export * from './lazy-module-loader.ts';
7
+ export * from './testing-module.ts';
@@ -0,0 +1,29 @@
1
+ import { Injectable } from './decorators.ts';
2
+ import { CalyxContainer } from './container.ts';
3
+ import { ModuleRef } from './module-ref.ts';
4
+
5
+ @Injectable()
6
+ export class LazyModuleLoader {
7
+ constructor(private readonly moduleRef: ModuleRef) {}
8
+
9
+ async load(loader: () => Promise<any> | any): Promise<ModuleRef> {
10
+ let moduleClass = await loader();
11
+ if (moduleClass && typeof moduleClass === 'object' && 'default' in moduleClass) {
12
+ moduleClass = moduleClass.default;
13
+ }
14
+
15
+ const container: CalyxContainer = (this.moduleRef as any).container;
16
+ if (!container) {
17
+ throw new Error('LazyModuleLoader: Container reference not found on moduleRef');
18
+ }
19
+
20
+ await container.bootstrap(moduleClass);
21
+
22
+ const record = container.getModuleRecord(moduleClass);
23
+ if (!record) {
24
+ throw new Error(`LazyModuleLoader: Module record not found for ${moduleClass.name}`);
25
+ }
26
+
27
+ return record.instances.get(ModuleRef);
28
+ }
29
+ }
@@ -44,7 +44,12 @@ export interface FactoryProvider {
44
44
  scope?: Scope;
45
45
  }
46
46
 
47
- export type Provider = Type<any> | ValueProvider | ClassProvider | FactoryProvider;
47
+ export interface ExistingProvider {
48
+ provide: InjectionToken;
49
+ useExisting: InjectionToken;
50
+ }
51
+
52
+ export type Provider = Type<any> | ValueProvider | ClassProvider | FactoryProvider | ExistingProvider;
48
53
 
49
54
  export interface ModuleMetadata {
50
55
  imports?: any[];
@@ -0,0 +1,119 @@
1
+ import { CalyxContainer } from './container.ts';
2
+ import { ModuleMetadata, Type, InjectionToken } from './metadata.ts';
3
+ import { Module } from './decorators.ts';
4
+ import { ModuleRef } from './module-ref.ts';
5
+ import { CalyxApplication } from '../http/application.ts';
6
+
7
+ export class TestingModule extends ModuleRef {
8
+ constructor(public readonly container: CalyxContainer, private readonly rootModuleClass: any) {
9
+ super();
10
+ }
11
+
12
+ createCalyxApplication(): CalyxApplication {
13
+ const app = new CalyxApplication(this.rootModuleClass);
14
+ (app as any).container = this.container;
15
+ return app;
16
+ }
17
+
18
+ get<T>(token: InjectionToken, options?: { strict: boolean }): T {
19
+ const strict = options?.strict ?? false;
20
+ if (strict) {
21
+ return this.container.resolveTokenInModuleContext(this.rootModuleClass, token);
22
+ } else {
23
+ return this.container.getGlobalOrAnyInstance(token);
24
+ }
25
+ }
26
+
27
+ async resolve<T>(token: InjectionToken, contextId?: any, options?: { strict: boolean }): Promise<T> {
28
+ const requestContext = contextId instanceof Map ? contextId : new Map<any, any>();
29
+ const strict = options?.strict ?? false;
30
+ if (strict) {
31
+ return await this.container.resolveTokenInModuleContextAsync(this.rootModuleClass, token, requestContext);
32
+ } else {
33
+ return await this.container.resolveTokenGloballyAsync(token, requestContext);
34
+ }
35
+ }
36
+
37
+ async create<T>(type: Type<T>): Promise<T> {
38
+ return await this.container.instantiateClassAsync(type, this.rootModuleClass);
39
+ }
40
+
41
+ async close() {
42
+ const instances = this.container.getProviderAndControllerInstances();
43
+ for (const inst of instances) {
44
+ if (inst && typeof inst.onModuleDestroy === 'function') {
45
+ await inst.onModuleDestroy();
46
+ }
47
+ if (inst && typeof inst.onApplicationShutdown === 'function') {
48
+ await inst.onApplicationShutdown();
49
+ }
50
+ }
51
+ }
52
+ }
53
+
54
+ export class TestingModuleBuilder {
55
+ private overrides = new Map<InjectionToken, { useValue?: any; useClass?: any; useFactory?: any; inject?: any[] }>();
56
+
57
+ constructor(private readonly metadata: ModuleMetadata) {}
58
+
59
+ overrideProvider(token: InjectionToken) {
60
+ const builder = {
61
+ useValue: (value: any) => {
62
+ this.overrides.set(token, { useValue: value });
63
+ return this;
64
+ },
65
+ useClass: (clazz: any) => {
66
+ this.overrides.set(token, { useClass: clazz });
67
+ return this;
68
+ },
69
+ useFactory: (options: { factory: (...args: any[]) => any; inject?: any[] }) => {
70
+ this.overrides.set(token, { useFactory: options.factory, inject: options.inject });
71
+ return this;
72
+ },
73
+ };
74
+ return builder;
75
+ }
76
+
77
+ overrideGuard(token: any) { return this.overrideProvider(token); }
78
+ overrideInterceptor(token: any) { return this.overrideProvider(token); }
79
+ overridePipe(token: any) { return this.overrideProvider(token); }
80
+ overrideFilter(token: any) { return this.overrideProvider(token); }
81
+
82
+ async compile(): Promise<TestingModule> {
83
+ @Module({
84
+ imports: this.metadata.imports || [],
85
+ controllers: this.metadata.controllers || [],
86
+ providers: this.metadata.providers || [],
87
+ exports: this.metadata.exports || [],
88
+ })
89
+ class RootTestModule {}
90
+
91
+ const container = new CalyxContainer();
92
+ container.addModule(RootTestModule);
93
+
94
+ // Apply overrides to module records
95
+ for (const [token, override] of this.overrides.entries()) {
96
+ for (const record of (container as any).modules.values()) {
97
+ if (record.providers.has(token)) {
98
+ if ('useValue' in override) {
99
+ record.providers.set(token, { provide: token, useValue: override.useValue });
100
+ } else if ('useClass' in override) {
101
+ record.providers.set(token, { provide: token, useClass: override.useClass });
102
+ } else if ('useFactory' in override) {
103
+ record.providers.set(token, { provide: token, useFactory: override.useFactory, inject: override.inject });
104
+ }
105
+ }
106
+ }
107
+ }
108
+
109
+ await container.bootstrap(RootTestModule);
110
+
111
+ return new TestingModule(container, RootTestModule);
112
+ }
113
+ }
114
+
115
+ export class Test {
116
+ static createTestingModule(metadata: ModuleMetadata): TestingModuleBuilder {
117
+ return new TestingModuleBuilder(metadata);
118
+ }
119
+ }