Skip to content

Elysia

Derive auditLog on the request context with global plugin scope and optional waitUntil.

Updated View as Markdown
npm i elysia
import { Elysia } from 'elysia';
import { AuditLogger } from 'logbun';
import { auditPlugin } from 'logbun/plugins/elysia';
import type { IAdapter } from 'logbun';

type Actions = 'course.created' | 'course.deleted';

declare const adapter: IAdapter;
declare function tenantIdFromVerifiedSession(ctx: unknown): string | undefined;

const audit = new AuditLogger<Actions>({
  namespace: 'api',
  adapter,
  requireTenantId: true,
});
await audit.ready;

const app = new Elysia()
  .use(
    auditPlugin(audit, {
      trustedProxyCount: 1,
      getTenantId: (ctx) => tenantIdFromVerifiedSession(ctx),
      // Workers: Elysia does not inject ExecutionContext.
      // getWaitUntil: () => (task) => executionCtx.waitUntil(task),
    }),
  )
  .post('/courses', async ({ auditLog, body }) => {
    auditLog.fire('course.created', {
      actorId: 'user_1',
      entityId: 'course_1',
    });
    await auditLog.fireAsync('course.created', {
      actorId: 'user_1',
      entityId: 'course_1',
    });
  });

The plugin is registered as { name: 'logbun' } with .derive({ as: 'global' }), so a parent app.use(auditPlugin(...)) sees auditLog on the request context. Local (non-global) derive would stay isolated to the plugin instance.

getTenantId receives the full Elysia derive context (same ctx as getWaitUntil), not { request } only.

extractWaitUntil is also exported from logbun/plugins/elysia. It reads executionCtx / executionContext / ctx / store / a request-scoped waitUntil already on the derive context. It does not import cloudflare:workers. On Workers, pass getWaitUntil.

Options:

{
  trustedProxyCount?: number; // default 0
  getTenantId?: (ctx: unknown) => string | undefined;
  getWaitUntil?: (ctx: unknown) => ((task: Promise<unknown>) => void) | undefined;
}
Navigation

Type to search…

↑↓ navigate↵ selectEsc close