All files / src/internal/core token-resolver.mts

100% Statements 25/25
100% Branches 21/21
100% Functions 5/5
100% Lines 25/25

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                                        286x                           1532x 704x   828x                       2456x       37x   2419x                     1303x                                         998x 998x 672x   998x 998x 12x 986x 9x 5x   4x     994x 960x   34x 998x 5x       5x                 29x      
import type {
  AnyInjectableType,
  InjectionTokenType,
} from '../../token/injection-token.mjs'
 
import { DIError } from '../../errors/index.mjs'
import {
  BoundInjectionToken,
  FactoryInjectionToken,
  InjectionToken,
} from '../../token/injection-token.mjs'
import { getInjectableToken } from '../../utils/index.mjs'
 
/**
 * Handles token validation and resolution.
 *
 * Focuses on token validation, normalization, and argument validation.
 * Name generation is handled by NameResolver.
 */
export class TokenResolver {
  constructor(private readonly logger: Console | null = null) {}
 
  // ============================================================================
  // TOKEN NORMALIZATION
  // ============================================================================
 
  /**
   * Normalizes a token to an InjectionToken.
   * Handles class constructors by getting their injectable token.
   *
   * @param token A class constructor, InjectionToken, BoundInjectionToken, or FactoryInjectionToken
   * @returns The normalized InjectionTokenType
   */
  normalizeToken(token: AnyInjectableType): InjectionTokenType {
    if (typeof token === 'function') {
      return getInjectableToken(token)
    }
    return token as InjectionTokenType
  }
 
  /**
   * Gets the underlying "real" token from wrapped tokens.
   * For BoundInjectionToken and FactoryInjectionToken, returns the wrapped token.
   * For other tokens, returns the token itself.
   *
   * @param token The token to unwrap
   * @returns The underlying InjectionToken
   */
  getRealToken<T = unknown>(token: InjectionTokenType): InjectionToken<T> {
    if (
      token instanceof BoundInjectionToken ||
      token instanceof FactoryInjectionToken
    ) {
      return token.token as InjectionToken<T>
    }
    return token as InjectionToken<T>
  }
 
  /**
   * Convenience method that normalizes a token and then gets the real token.
   * Useful for checking registry entries where you need the actual registered token.
   *
   * @param token Any injectable type
   * @returns The underlying InjectionToken
   */
  getRegistryToken<T = unknown>(token: AnyInjectableType): InjectionToken<T> {
    return this.getRealToken(this.normalizeToken(token))
  }
 
  // ============================================================================
  // TOKEN VALIDATION
  // ============================================================================
 
  /**
   * Validates and resolves token arguments, handling factory token resolution and validation.
   *
   * @param token The token to validate
   * @param args Optional arguments
   * @returns [error, { actualToken, validatedArgs }]
   */
  validateAndResolveTokenArgs(
    token: AnyInjectableType,
    args?: any,
  ): [
    DIError | undefined,
    { actualToken: InjectionTokenType; validatedArgs?: any },
  ] {
    let actualToken = token as InjectionToken<any, any>
    if (typeof token === 'function') {
      actualToken = getInjectableToken(token)
    }
    let realArgs = args
    if (actualToken instanceof BoundInjectionToken) {
      realArgs = actualToken.value
    } else if (actualToken instanceof FactoryInjectionToken) {
      if (actualToken.resolved) {
        realArgs = actualToken.value
      } else {
        return [DIError.factoryTokenNotResolved(token.name), { actualToken }]
      }
    }
    if (!actualToken.schema) {
      return [undefined, { actualToken, validatedArgs: realArgs }]
    }
    const validatedArgs = actualToken.schema?.safeParse(realArgs)
    if (validatedArgs && !validatedArgs.success) {
      this.logger?.error(
        `[TokenResolver]#validateAndResolveTokenArgs(): Error validating args for ${actualToken.name.toString()}`,
        validatedArgs.error,
      )
      return [
        DIError.tokenValidationError(
          `Validation failed for ${actualToken.name.toString()}`,
          actualToken.schema,
          realArgs,
        ),
        { actualToken },
      ]
    }
    return [undefined, { actualToken, validatedArgs: validatedArgs?.data }]
  }
}