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
}
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