You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NestJS同目录模块使用自定义Decorator触发TypeError问题排查

NestJS自定义装饰器在同目录模块中报错TypeError: not a function

我用NestJS构建API,集成Redis后编写了Dynamic Module用于建立Redis连接,同时自定义了InjectRedisGraph装饰器来获取Redis连接实例。当前遇到的问题是:该装饰器在其他服务中可以正常使用,但在同目录的RedisGraphModule中使用时,会抛出如下TypeError:

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

已尝试的解决方案

  • 使用绝对路径和相对路径导入装饰器,均失败
  • 装饰器文件中导出的普通console.log函数可正常执行
  • 为Module添加@Injectable装饰器,无效果(装饰器本身未抛出错误)
  • 将装饰器声明移入Module文件可正常运行,但希望保持文件分离

补充说明:编译后的JS代码中装饰器导出正常;VS Code可解析所有类/函数;tsc编译无错误;使用NestJS默认配置。我是NestJS和TypeScript装饰器新手,希望找到问题根源。


相关代码

装饰器代码(redis-graph-connection.decorator.ts)

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

/**
 * 简化RedisGraph实例注入的装饰器
 */
export const InjectRedisGraph = (graph: symbol): ParameterDecorator => {
  return Inject(graph)
}

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

模块代码(redis-graph-connection.module.ts)

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

  // 此处抛出TypeError: not a function
  constructor(@InjectRedisGraph(MainGraph) private readonly graph: Graph) {
    doesExport()
  }

  onApplicationShutdown() {
    // RedisGraph实例清理逻辑
  }
}

正常工作的服务代码(redis-store.service.ts)

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,
  ) {}
// 业务逻辑代码
...
}

Provider代码(redis-graph-connection.provider.ts)

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
}

问题原因与解决方案

问题根源

这是循环导入导致的加载顺序问题:

  • RedisGraphModule 导入装饰器文件
  • 装饰器文件导入redis-graph-connection.provider
  • Provider文件又导入RedisGraphModule

当NestJS初始化模块时,循环引用会导致装饰器在模块初始化阶段未被正确加载,从而出现"not a function"错误。

解决步骤

1. 打破循环导入

  • 将RedisClientIdentifier从Provider文件移至独立的常量文件(如redis-graph.constants.ts),让装饰器和Provider均从该文件导入,避免互相引用。
  • 调整Provider文件的日志逻辑,不再依赖RedisGraphModule的静态logger,改为独立创建Logger实例。

2. 调整模块初始化逻辑

模块类本身无需通过构造函数注入Graph实例,可通过注入Redis客户端实例来处理应用关闭时的清理逻辑。

修改后的示例代码

新增常量文件(redis-graph.constants.ts)

export const RedisClientIdentifier = Symbol('redisClient')

装饰器文件修改

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

export const InjectRedisGraph = (graph: symbol): ParameterDecorator => {
  return Inject(graph)
}

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

Provider文件修改

import { createClient, Graph } from 'redis'
import { Provider, Logger } from '@nestjs/common'
import { RedisClientIdentifier } from './redis-graph.constants'

const logger = new Logger('redis-graph-client')

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

  await client.connect()
  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()),
    })
    logger.log(`Created graph for ${graphName.toString()}`)
  })

  return providers
}

模块文件修改

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

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

  constructor(@Inject(RedisClientIdentifier) private readonly client: any) {}

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

  onApplicationShutdown() {
    return this.client.quit()
  }
}

打破循环导入后,装饰器即可在模块中正常使用。

内容的提问来源于stack exchange,提问作者Lukas

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 21:30:41