Skip to content

Elysia Adapter

@inferdi/elysia is an Elysia v1 plugin. In scoped mode it creates one request scope, exposes it on Elysia context, keeps it available for user error handlers, and disposes it from onAfterResponse.

Install

bash
pnpm add @inferdi/inferdi @inferdi/elysia elysia
ts
import { Elysia } from 'elysia'
import { inferdiElysia } from '@inferdi/elysia'

Request Scope

ts
const root = buildRootContainer()

const app = new Elysia()
  .use(inferdiElysia({
    container: root,
    setupScope: (scope, { request }) => {
      const ctx = scope.get('request')
      ctx.requestId = crypto.randomUUID()
      ctx.userId = request.headers.get('x-user-id') ?? undefined
    },
  }))
  .get('/users/:id', ({ di, params }) =>
    di.get('users').profile(params.id),
  )

For a custom context key:

ts
const app = new Elysia()
  .use(inferdiElysia({ container: root, key: 'container' }))
  .get('/users/:id', ({ container, params }) =>
    container.get('users').profile(params.id),
  )

Routes must be registered after .use(inferdiElysia(...)) in the typed Elysia chain.

Options

OptionDefaultDescription
containerrequiredRoot container.
key'di'Elysia context key.
scopePerRequesttrueSet false for root-only mode.
createScoperoot.createScope()Custom request scope creation.
setupScopenoneHydrates before validation and route handlers.
setupValidatedScopenoneHydrates after Elysia validation.
disposeScopescope.dispose()Custom disposal.
autoDisposetruefalse or predicate false transfers ownership.
onDisposeErrorconsole.errorCleanup failure sink.

Root-Only Mode

ts
const app = new Elysia()
  .use(inferdiElysia({
    container: root,
    scopePerRequest: false,
  }))
  .get('/health', ({ di }) => di.get('health').check())

Root-only mode exposes the root container and installs no request-scope lifecycle hooks. Scoped-only options are statically rejected.

Lifecycle Notes

Cleanup is lifecycle-bound to onAfterResponse. If Elysia never reaches that hook, the adapter cannot release resources held by the request scope. The per-request bookkeeping is weakly held, but resource disposal requires the lifecycle hook to run.

Use setupScope for values needed before validation. Use setupValidatedScope for values derived from validated body, query, params, headers, or cookies.

Streaming

Elysia can produce a streaming Response before the stream drains. If scoped services are used after the route returns, call skipInferdiDispose(context) and dispose the scope yourself.

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

app.get('/events', (context) => {
  skipInferdiDispose(context)

  const scope = context.di
  const events = scope.get('events')

  return new Response(new ReadableStream({
    async start(controller) {
      const encoder = new TextEncoder()

      try {
        for await (const event of events.subscribe()) {
          controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`))
        }
      } finally {
        await scope.dispose()
      }
    },
  }))
})

skipInferdiDispose suppresses only successful cleanup. Error paths still dispose.