Skip to content

Adaptador de Koa

@inferdi/koa es middleware de Koa v3. Crea un scope de petición, lo expone como ctx.state.di y lo libera después de que la respuesta de Node finaliza o se cierra.

Instalación

bash
pnpm add @inferdi/inferdi @inferdi/koa koa
pnpm add -D @types/koa
ts
import Koa from 'koa'
import { inferdiKoa, type InferdiScopeOf } from '@inferdi/koa'

Scope de petición

ts
const root = buildRootContainer()

declare module 'koa' {
  interface DefaultState {
    di: InferdiScopeOf<typeof root>
  }
}

const app = new Koa()

app.use(inferdiKoa({
  container: root,
  setupScope: (scope, ctx) => {
    const request = scope.get('request')
    request.requestId = crypto.randomUUID()
    request.userId = ctx.get('x-user-id') || undefined
    request.ip = ctx.ip
  },
}))

app.use(async (ctx) => {
  const id = ctx.path.split('/').pop() ?? ''
  ctx.body = await ctx.state.di.get('users').profile(id)
})

Clave de state personalizada

ts
import type { DefaultState, ParameterizedContext } from 'koa'
import { type InferdiKoaState, type InferdiScopeOf } from '@inferdi/koa'

type AppState =
  & DefaultState
  & InferdiKoaState<InferdiScopeOf<typeof root>, 'container'>

type AppContext = ParameterizedContext<AppState>

app.use(inferdiKoa({ container: root, key: 'container' }))

app.use(async (ctx: AppContext) => {
  ctx.body = await ctx.state.container.get('users').profile('42')
})

Opciones

OpciónPor defectoDescripción
containerrequeridoContenedor raíz. Este middleware nunca lo libera.
key'di'Clave del state de Koa.
createScoperoot.createScope()Creación personalizada del scope de petición.
setupScopeningunoHidrata el scope antes del middleware aguas abajo.
disposeScopescope.dispose()Liberación personalizada.
autoDisposetruefalse o un predicado false transfiere la propiedad.
onDisposeErrorctx.app.emit('error')Sumidero de fallos de limpieza.

Streaming

Los cuerpos de stream normales de Koa no necesitan un skip. El adaptador espera al finish o close.

Usa skipInferdiDispose(ctx) solo cuando el código de la aplicación mantiene el scope intencionadamente más allá del límite de la respuesta HTTP, como en el trabajo en segundo plano:

ts
import { skipInferdiDispose } from '@inferdi/koa'

app.use(async (ctx) => {
  skipInferdiDispose(ctx)
  const scope = ctx.state.di

  queue.add(async () => {
    try {
      await scope.get('jobs').run()
    } finally {
      await scope.dispose()
    }
  })

  ctx.body = { status: 'queued' }
})

Un error aguas abajo siempre libera el scope; las peticiones omitidas que tienen éxito pasan a ser propiedad de la aplicación.