Skip to content

Адаптер Koa

@inferdi/koa - это middleware для Koa v3. Оно создаёт один scope запроса, выставляет его как ctx.state.di и очищает после события Node response finish или close.

Установка

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 запроса

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

Собственный ключ state

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

Опции

ОпцияПо умолчаниюНазначение
containerобязательнаКорневой контейнер. Middleware его не очищает.
key'di'Ключ в Koa state.
createScoperoot.createScope()Пользовательское создание scope запроса.
setupScopeнетНаполняет scope до следующего middleware.
disposeScopescope.dispose()Пользовательская очистка.
autoDisposetruefalse или предикат false передаёт владение.
onDisposeErrorctx.app.emit('error')Приёмник ошибок очистки.

Стриминг

Обычные тела потоковых ответов в Koa не требуют skip. Адаптер ждёт finish или close.

Используйте skipInferdiDispose(ctx) только когда код приложения намеренно держит scope дольше границы HTTP-ответа:

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

Downstream-ошибка всегда очищает scope; успешные запросы с пропущенной автоочисткой переходят во владение приложения.