typescriptnestjsdecoratortypescript-decorator

NestJS Decorator is not a Function TypeError


I am building an API using NestJS.

I added Redis to the mix and created a Dynamic Module that creates a redis connection.
I then added Decorators to access the redis connection.

My problem. I can access the Decorator in other services.
But if I try to access it in the Module that's in the same folder as the Decorator I get a TypeError

@InjectRedisGraph(MainGraph) private readonly graph: Graph,
                     ^
TypeError: (0 , redis_graph_connection_decorator_1.InjectRedisGraph) is not a function

What I tried,

Looking at the transpiled js the exports of the decorator look fine.
VS Code can resolve all the classes/functions.
tsc does not throw any errors while transpiling.
All the configs are the default NestJS config files.

I hope it is just an oversight on my side. I am very new to NestJS and Typescript Decoratos, but I am somewhat lost right now.

The Code: decorator

import { Inject } from '@nestjs/common'
import { RedisClientIdentifier } from './redis-graph-connection.provider'

/**
 * Simple decorator to make usage of graphredis more verbose
 */
export const InjectRedisGraph = (graph: symbol): ParameterDecorator => {
  return Inject(graph)
}

export function doesExport() {
  console.log("It does")
}

module

import { DynamicModule, Logger, OnApplicationShutdown } from '@nestjs/common'
import { createRedisGraphProviders } from './redis-graph-connection.provider'
import { RedisClientOptions } from '@redis/client'
import { doesExport, InjectRedisGraph } from './redis-graph-connection.decorator'
import { MainGraph } from '../redis-store.constants'
import { Graph } from '@redis/graph'

export class RedisGraphModule implements OnApplicationShutdown {
  static logger = new Logger('redis-graph-client')

  static async forRootAsync(
    options: RedisGraphModuleOptions,
  ): Promise<DynamicModule> {
    const providers = await createRedisGraphProviders(options)
    return {
      module: RedisGraphModule,
      providers,
      exports: providers,
    }
  }

  //            |-> This throws TypeError, not a function
  constructor(@InjectRedisGraph(MainGraph) private readonly graph: Graph) {
    doesExport()
  }

  onApplicationShutdown() {
    // do some graph cleanup
  }
}

some other service, This works

import { Injectable, Logger } from '@nestjs/common'
import { EvidenceStore } from 'src/common/evidence-store.interface'
import { InjectRedisGraph } from 'src/redis-store/redis-graph-connection/redis-graph-connection.decorator'
import { MainGraph } from './redis-store.constants'
import { Graph } from '@redis/graph'

@Injectable()
export class RedisStore implements EvidenceStore {
  private readonly logger = new Logger('redis-graph-collector')

  constructor(
    @InjectRedisGraph(MainGraph) private readonly redisGraph: Graph,
  ) {}
...

redis-graph-connection.provider

import { createClient, Graph } from 'redis'
import { Provider } from '@nestjs/common'
import { RedisGraphModule, RedisGraphModuleOptions } from './redis-graph-connection.module'

export const RedisClientIdentifier = Symbol('redisClient')

export async function createRedisGraphProviders(
  options: RedisGraphModuleOptions,
): Promise<Array<Provider>> {
  const client = createClient(options.redisOptions)
  client.on('error', (err: Error) => {
    RedisGraphModule.logger.error(err.message, err.stack)
  })

  await client.connect()
  RedisGraphModule.logger.log('Connected')

  const providers: Array<Provider> = [
    {
      provide: RedisClientIdentifier,
      useValue: client,
    },
  ]

  options.graphs.forEach((graphName) => {
    providers.push({
      provide: graphName,
      useValue: new Graph(client, graphName.toString()),
    })
    RedisGraphModule.logger.log(`Created graph for ${graphName.toString()}`)
  })

  return providers
}


Solution

  • The main issue here is that your decorator file has a circular file import with the module file. What is happening is at runtime, this circular import is having one of the two files export undefined and the re-populating it at a later moment after every dependency has been resolved. At the time of calling the decorator (which happens at file import) the dependency is still undefined and you get this error. Circular imports are non deterministic, which is the other reason this sometimes works and sometimes doesn't