如何在NestJS中使用更新的配置重新初始化EdgeDB数据库提供程序?

huangapple go评论64阅读模式
英文:

How to re-initialize EdgeDB database provider with updated configuration in NestJS?

问题

我正在开发一个由NestJS和EdgeDB支持的个人项目。我想利用它们的新功能,称为'访问策略',这需要重新初始化一个包含当前用户ID的配置的数据库客户端。最初,我认为可以简单地调用一个专门设计用于覆盖现有数据库客户端的方法,但这种方法似乎无效,因为初始化后,注入的提供者似乎不受任何尝试的变化的影响。以下是代码片段示例:

数据库提供者

@Injectable()
export class EdgeDBService implements OnModuleInit {
  client = edgedb.createClient()

  async onModuleInit() {
    await this.client.ensureConnected()
  }

  public async setGlobals(globals: { current_user_id: string }) {
    // 尝试添加当前用户ID以供将来的所有查询使用
    this.client = this.client.withGlobals(globals)

    // 在这里,`current_user_id` 返回正确的值
    console.log(await this.client.query(`select global current_user_id;`))
  }

  public async query<Expr extends EdgeQLTypes.Expression>(expression: Expr): Promise<$infer<Expr>> {
    return await expression.run(this.client)
  }
}

用于检索宠物的假设提供者

@Injectable()
export class PetsService {
  constructor(private edgedb: EdgeDBService) {}

  getPets() {
    // 在这里什么也得不到。
    console.log(await this.client.query(`select global current_user_id;`))

    // 由于未设置`current_user_id`,此查询不返回任何内容。
    return this.edgedb.query(
      e.select(e.Pets, (item) => ({
        ...e.Pets['*'],
      }))
    )
  }
}

因此,似乎不可能简单地重写EdgeDBService中的this.client属性,或者我错了吗?在初始化后是否有可能修改提供者,或者单例注入范围是否阻止了这一点?

非常感谢帮助解决这个问题 🙏

英文:

I'm developing a personal project backed by NestJS and EdgeDB. I'd like to take advantage of their new feature called 'access policies' which requires re-initializing a database client with a config that contains current user's ID. Initially I thought that I could simply call a special method which is designed to overwrite existing database client, however this approach didn't work since the injected provider is seemingly unaffected by any of the attempted mutations after the initialization. Here are code snippets to demonstrate:

Database provider

@Injectable()
export class EdgeDBService implements OnModuleInit {
  client = edgedb.createClient()

  async onModuleInit() {
    await this.client.ensureConnected()
  }

  public async setGlobals(globals: { current_user_id: string }) {
    // Attempting to tack on current user ID to all future queries
    this.client = this.client.withGlobals(globals)

    // `current_user_id` returns correct value here
    console.log(await this.client.query(`select global current_user_id;`))
  }

  public async query&lt;Expr extends EdgeQLTypes.Expression&gt;(expression: Expr): Promise&lt;$infer&lt;Expr&gt;&gt; {
    return await expression.run(this.client)
  }
}

Hypothetical provider for retrieving books

@Injectable()
export class PetsService {
  constructor(private edgedb: EdgeDBService) {}

  getPets() {
    // Getting nothing here.
    console.log(await this.client.query(`select global current_user_id;`))

    // This query returns nothing since `current_user_id` is not set.
    return this.edgedb.query(
      e.select(e.Pets, (item) =&gt; ({
        ...e.Pets[&#39;*&#39;],
      }))
    )
}

So it seems like it's not really possible to simply re-write the this.client property in EdgeDBService, or am I wrong? Is it possible to alter a provider after the initialization at all, or the singleton injection scope prevents that?

Any help with figuring this out is highly appreciated 🙏

答案1

得分: 0

最终我自己找到了解决办法,根据与注入范围相关的线索。默认情况下,单个提供者实例(单例)在整个应用程序中共享。尝试在运行时更改其属性将不会反映在已经注入的实例中(例如上面的PetsService)。原来可以通过指定不同的scope来调整提供者的注入方式(文档)。我选择了REQUEST,因为它完全符合我的使用情况:

> REQUEST:为每个传入的请求独立创建提供者的新实例。在请求完成处理后,该实例将被垃圾回收。

切换到REQUEST范围使我能够在构造函数中获取request对象(文档),提取JWT,解码并使用全局变量重新初始化EdgeDB客户端以使访问策略正常工作。以下是代码:

import { CONTEXT } from '@nestjs/graphql'

@Injectable({ scope: Scope.REQUEST })
export class EdgeDBService implements OnModuleInit {
  client = edgedb.createClient()

  constructor(
    @Inject(CONTEXT) private context: { req: Request },
    private moduleRef: ModuleRef
  ) {
    const token = context.req.headers['authorization']?.replace('Bearer ', '')
    if (token) {
      const authService = this.moduleRef.get(AuthService, { strict: false })
      const decoded = authService.decodeJWT(token)
      this.client = this.client.withGlobals({
        current_user_id: decoded.sub,
      })
    }
  }

  ...
}
英文:

Eventually figured this out on my own going off a clue related to injection scope. By default, a single provider instance (singleton) is shared across the entire app. Attempting to mutate its properties at runtime won't reflect in the already injected instances (PetsService in the example above). Turns out that the way the provider is injected could be adjusted by specifying a different scope (docs). I went with with REQUEST as it perfectly fits my use case:

> REQUEST: A new instance of the provider is created exclusively for each incoming request. The instance is garbage-collected after the request has completed processing.

Switching over to REQUEST scope allowed me to grab the request object in the constructor (docs), extract JWT, decode it and re-initialize EdgeDB client with a global variable required for access policies to work. Here's the code:

import { CONTEXT } from &#39;@nestjs/graphql&#39;

@Injectable({ scope: Scope.REQUEST })
export class EdgeDBService implements OnModuleInit {
  client = edgedb.createClient()

  constructor(
    @Inject(CONTEXT) private context: { req: Request },
    private moduleRef: ModuleRef
  ) {
    const token = context.req.headers[&#39;authorization&#39;]?.replace(&#39;Bearer &#39;, &#39;&#39;)
    if (token) {
      const authService = this.moduleRef.get(AuthService, { strict: false })
      const decoded = authService.decodeJWT(token)
      this.client = this.client.withGlobals({
        current_user_id: decoded.sub,
      })
    }
  }

  ...
}

huangapple
  • 本文由 发表于 2023年7月13日 19:01:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/76678625.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定