All files / common/src/reflect reflect.context.ts

97.14% Statements 68/70
88.23% Branches 15/17
93.33% Functions 14/15
97.01% Lines 65/67

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 19410x           10x 10x 10x 10x   10x   10x                     10x   10x               10x 146x         146x 146x 146x             146x 146x 146x 146x 146x                   283x 168x   283x 116x     168x   167x 168x 167x       167x 910x 910x 910x                         6328x       6480x 6479x 6479x     6479x 2x             6477x                           108x 108x 108x 108x 108x   108x           6480x 6480x       6480x 422x   422x 270x   152x   152x   152x 152x 152x   152x       151x   421x                 6479x         168x 168x 168x       168x           13870x 13870x        
import { assert, ConstructorType } from '@aspectjs/common/utils';
 
/**
 * Returns an object to store global values across the framework
 */
 
import { ANNOTATION_CONTEXT_REGISTRY_PROVIDERS } from '../annotation/context/registry/annotation-context-registry.provider';
import { ANNOTATION_HOOK_REGISTRY_PROVIDERS } from '../annotation/factory/annotation-factory.provider';
import { ANNOTATION_REGISTRY_PROVIDERS } from '../annotation/registry/annotation-registry.provider';
import { ANNOTATION_TARGET_FACTORY_PROVIDERS } from '../annotation/target/annotation-target-factory.provider';
import { ReflectModuleConfiguration } from './module/reflect-module-config.type';
import { ReflectModule } from './module/reflect-module.type';
import type { ReflectProvider } from './reflect-provider.type';
import { RUNTIME_STATE_PROVIDER } from './runtime-state.provider';
 
@ReflectModule({
  providers: [
    RUNTIME_STATE_PROVIDER,
    ...ANNOTATION_REGISTRY_PROVIDERS,
    ...ANNOTATION_CONTEXT_REGISTRY_PROVIDERS,
    ...ANNOTATION_HOOK_REGISTRY_PROVIDERS,
    ...ANNOTATION_TARGET_FACTORY_PROVIDERS,
  ],
})
class AnnotationsModule {}
 
const DEFAULT_MODULES = [AnnotationsModule];
/**
 * @internal
 *
 * The ReflectContext is a container for the global values and services of the framework.
 * The services are added to the context in the form of {@link ReflectProvider}s,
 * through the use of {@link ReflectModuleConfiguration}s.
 */
export class ReflectContext {
  protected providersToResolve: Map<string, ReflectProvider[]> = new Map();
 
  protected providersRegistry: Map<
    string,
    { component: unknown; provider: ReflectProvider }[]
  > = new Map();
  protected addedProviders: Set<ReflectProvider> = new Set();
  protected modules: Set<ConstructorType> = new Set();
 
  /**
   * @internal
   * @param context
   */
  constructor(context?: ReflectContext) {
    this.providersToResolve = new Map(context?.providersToResolve);
    this.providersRegistry = new Map(context?.providersRegistry);
    this.addedProviders = new Set(context?.addedProviders);
    this.modules = new Set(context?.modules);
    this.registerModules(...DEFAULT_MODULES);
  }
 
  /**
   * Adds a module to the context. Modules are unique by their name.
   * Adding the same module twice has no effect.
   * @param module The module to add
   */
  registerModules(...modules: ConstructorType<unknown>[]): ReflectContext {
    // dedupe modules
    modules = [...new Set(modules).values()].filter(
      (m) => !this.modules.has(m),
    );
    if (!modules.length) {
      return this;
    }
 
    const providers = modules.flatMap((m) => getProviders(m));
 
    this.addProviders(providers);
    modules.forEach((m) => this.modules.add(m));
    return this;
  }
 
  private addProviders(providers: ReflectProvider[]) {
    providers.forEach((p) => {
      const providerName = getProviderName(p.provide);
      this.addedProviders.add(p);
      this.providersToResolve.set(
        providerName,
        (this.providersToResolve.get(providerName) ?? []).concat(p),
      );
    });
  }
  /**
   * Get a provider by its name or type.
   * @param provider The provider name or type.
   * @param T the provider type
   * @return The provider, if registered, undefined otherwise.
   */
  get<T>(providerType: ReflectProvider<T>['provide']): T {
    return this._get(getProviderName(providerType));
  }
 
  private _get<T>(providerName: string, neededBy: string[] = []): T {
    this._tryResolveProvider(providerName, neededBy);
    const candidates = this.providersRegistry.get(providerName);
    const provider = candidates?.[candidates?.length - 1]?.component;
 
    // if provider not found
    if (!provider) {
      throw new Error(
        `No ReflectContext provider found for ${providerName}${
          neededBy?.length ? `. Needed by ${neededBy.join(' -> ')}` : ''
        }`,
      );
    }
 
    return provider as T;
  }
 
  /**
   * Know if a provider is registered.
   * @param providerType The provider name or type.
   * @param T the provider type
   * @returns true if the provider is registered, false otherwise.
   */
  has<T>(providerType: ReflectProvider<T>['provide']): boolean {
    return !!this.get(providerType);
  }
 
  protected assign(context: ReflectContext) {
    assert(!!context);
    this.modules = context.modules;
    this.addedProviders = context.addedProviders;
    this.providersRegistry = context.providersRegistry;
    this.providersToResolve = context.providersToResolve;
 
    return this;
  }
  private _tryResolveProvider(
    providerType: string,
    neededBy: string[] = [],
  ): void {
    const providerName = getProviderName(providerType);
    const providers = this.providersToResolve.get(providerName);
 
    let component: unknown;
 
    while (providers?.length) {
      const provider = providers.shift()!; // remove p from the resolution list
      // if provider has no deps
      if (!provider.deps?.length) {
        component = provider.factory();
      } else {
        const deps: any[] = provider.deps.map((dep) => {
          // provider already resolved ?
          const depName = getProviderName(dep);
 
          try {
            neededBy.push(providerName);
            return this._get(depName, neededBy);
          } finally {
            neededBy.pop();
          }
        });
 
        component = provider.factory(...deps);
      }
      this.providersRegistry.set(
        providerName,
        (this.providersRegistry.get(providerName) ?? []).concat({
          component,
          provider,
        }),
      );
    }
 
    this.providersToResolve.delete(providerName);
  }
}
 
function getProviders(m: unknown): ReflectProvider[] {
  assert(!!m);
  const config: ReflectModuleConfiguration = (m as any)[Symbol.for('@ajs:rmd')];
  Iif (!config) {
    throw new TypeError(`object ${m} is not a module`);
  }
 
  return config.providers;
}
 
function getProviderName<T>(
  providerType: ReflectProvider<T>['provide'],
): string {
  assert(!!providerType);
  return typeof providerType === 'string'
    ? providerType
    : providerType.__providerName ?? providerType.name;
}