npm i elysiayarn add elysiapnpm add elysiabun add elysiaimport { 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;
}