All files / src/decorators injectable.decorator.mts

77.77% Statements 7/9
87.5% Branches 14/16
100% Functions 2/2
77.77% Lines 7/9

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                                                                                                                                                                                                                  419x       419x           419x           419x   419x     419x   419x      
import type { z } from 'zod/v4'
 
import type {
  BaseInjectionTokenSchemaType,
  ClassType,
  ClassTypeWithArgument,
  ClassTypeWithInstance,
  ClassTypeWithInstanceAndArgument,
  ClassTypeWithInstanceAndOptionalArgument,
  ClassTypeWithOptionalArgument,
  ClassTypeWithoutArguments,
  InjectionTokenSchemaType,
  OptionalInjectionTokenSchemaType,
} from '../token/injection-token.mjs'
import type { Registry } from '../token/registry.mjs'
 
import { InjectableScope, InjectableType } from '../enums/index.mjs'
import { InjectableTokenMeta } from '../symbols/index.mjs'
import { InjectionToken } from '../token/injection-token.mjs'
import { globalRegistry } from '../token/registry.mjs'
 
export interface InjectableOptions {
  scope?: InjectableScope
  token?: InjectionToken<any, any>
  schema?: InjectionTokenSchemaType
  registry?: Registry
  priority?: number
}
 
// #1 Simple constructorless class
export function Injectable(): <T extends ClassTypeWithoutArguments>(
  target: T,
  context?: ClassDecoratorContext,
) => T
export function Injectable(options: {
  scope?: InjectableScope
  registry: Registry
  priority?: number
}): <T extends ClassTypeWithoutArguments>(
  target: T,
  context?: ClassDecoratorContext,
) => T
export function Injectable(options: {
  scope: InjectableScope
  priority?: number
}): <T extends ClassTypeWithoutArguments>(
  target: T,
  context?: ClassDecoratorContext,
) => T
// #2 Class with schema
export function Injectable<Schema extends InjectionTokenSchemaType>(options: {
  scope?: InjectableScope
  schema: Schema
  registry?: Registry
  priority?: number
}): <T extends ClassTypeWithArgument<z.output<Schema>>>(
  target: T,
  context?: ClassDecoratorContext,
) => T
 
// #3 Class with typeless token and schema
export function Injectable<Type, Schema>(options: {
  scope?: InjectableScope
  token: InjectionToken<Type, Schema>
  registry?: Registry
  priority?: number
}): Schema extends BaseInjectionTokenSchemaType
  ? Type extends undefined
    ? <T extends ClassTypeWithArgument<z.output<Schema>>>(
        target: T,
        context?: ClassDecoratorContext,
      ) => T
    : <T extends ClassTypeWithInstanceAndArgument<Type, z.output<Schema>>>(
        target: T,
        context?: ClassDecoratorContext,
      ) => T
  : Schema extends OptionalInjectionTokenSchemaType
    ? Type extends undefined
      ? <T extends ClassTypeWithOptionalArgument<z.output<Schema>>>(
          target: T,
          context?: ClassDecoratorContext,
        ) => T
      : <
          T extends ClassTypeWithInstanceAndOptionalArgument<
            Type,
            z.output<Schema>
          >,
        >(
          target: T,
          context?: ClassDecoratorContext,
        ) => T
    : Schema extends undefined
      ? <R extends ClassTypeWithInstance<Type>>(
          target: R,
          context?: ClassDecoratorContext,
        ) => R
      : never
 
export function Injectable({
  scope = InjectableScope.Singleton,
  token,
  schema,
  registry = globalRegistry,
  priority = 0,
}: InjectableOptions = {}) {
  return <T extends ClassType>(
    target: T,
    context?: ClassDecoratorContext,
  ): T => {
    Iif (
      (context && context.kind !== 'class') ||
      (target instanceof Function && !context)
    ) {
      throw new Error('[DI] @Injectable decorator can only be used on classes.')
    }
    Iif (schema && token) {
      throw new Error(
        '[DI] @Injectable decorator cannot have both a token and a schema',
      )
    }
    let injectableToken: InjectionToken<any, any> =
      token ?? InjectionToken.create(target, schema as InjectionTokenSchemaType)
 
    registry.set(injectableToken, scope, target, InjectableType.Class, priority)
 
    // @ts-expect-error
    target[InjectableTokenMeta] = injectableToken
 
    return target
  }
}