Skip to content

Elysia アダプター

@inferdi/elysia は Elysia v1 のプラグインです。スコープドモードでは、1 つのリクエストスコープを作成し、それを Elysia コンテキスト上で公開し、ユーザーのエラーハンドラーが利用できるように保持し、onAfterResponse から破棄します。

インストール

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

リクエストスコープ

ts
type RequestContext = {
  requestId: string
  userId?: string
}

class Users {
  constructor(readonly request: RequestContext) {}

  profile(id: string) {
    return { id, userId: this.request.userId }
  }
}

const root = new Container()
  .declareScopeInputs<{ request: RequestContext }>()
  .registerClass('users', Users, ['request'], 'scoped')

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

カスタムのコンテキストキーの場合:

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

ルートは、型付けされた Elysia チェーン内で .use(inferdiElysia(...)) の後に登録する必要があります。

オプション

オプションデフォルト説明
container必須ルートコンテナ。
key'di'Elysia のコンテキストキー。
scopePerRequesttrueルート専用モードにするには false を設定します。
createScoperoot.createScope()カスタムのリクエストスコープ作成。
setupScopeなしスコープ作成後に追加の初期化を行います。
setupValidatedScopeなしElysia のバリデーション後に追加の初期化を行います。
disposeScopescope.dispose()カスタムの破棄。
autoDisposetruefalse または false を返す述語は所有権を移譲します。
onDisposeErrorconsole.errorクリーンアップ失敗のシンク。

ルート専用モード

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

ルート専用モードはルートコンテナを公開し、リクエストスコープのライフサイクルフックをインストールしません。スコープド専用のオプションは静的に拒否されます。

ライフサイクルに関する注意

クリーンアップはライフサイクル上 onAfterResponse にバインドされています。Elysia がそのフックに到達しなかった場合、アダプターはリクエストスコープが保持するリソースを解放できません。リクエストごとの記録は弱参照で保持されますが、リソースの破棄にはライフサイクルフックの実行が必要です。

バリデーション前に必要な値には setupScope を使用してください。バリデーション済みのボディ、クエリ、パラメータ、ヘッダー、または Cookie から導出される値には setupValidatedScope を使用してください。

ストリーミング

Elysia は、ストリームがドレインする前にストリーミングの Response を生成することがあります。スコープドサービスがルートの戻りの後に使用される場合は、skipInferdiDispose(context) を呼び出し、スコープを自分で破棄してください。

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 は成功したクリーンアップのみを抑制します。エラーパスでは、それでも破棄します。