Beignet API reference
    Preparing search index...

    Module @beignet/devtools

    @beignet/devtools

    Development-time event timeline for Beignet apps. It records HTTP requests, errors, use case runs, domain events, jobs, scheduled tasks, and provider activity in a bounded in-memory buffer, then serves a live dashboard from your app.

    Devtools is enabled outside production by default and returns a no-op port in production unless you explicitly enable it.

    bun add @beignet/devtools
    

    Register the provider and server hook:

    import {
    createDevtoolsHooks,
    createDevtoolsProvider,
    } from "@beignet/devtools";
    import { createNextServer } from "@beignet/next";

    export const server = await createNextServer({
    ports,
    providers: [createDevtoolsProvider(), ...providers],
    hooks: [createDevtoolsHooks()],
    createContext: ({ req, ports }) => ({
    requestId: req.headers.get("x-request-id") ?? crypto.randomUUID(),
    ports,
    }),
    });

    Add a catch-all route:

    // app/api/devtools/[[...path]]/route.ts
    import { createDevtoolsRoute } from "@beignet/devtools";
    import { server } from "@/server";

    export const { GET, POST } = createDevtoolsRoute(server.ports.devtools, {
    basePath: "/api/devtools",
    });

    Open /api/devtools.

    The dashboard connects to the event stream with Server-Sent Events and falls back to polling when EventSource is unavailable. It includes tabs for the timeline, requests, use cases, errors, domain events, jobs, schedules, providers, and provider-owned events such as database/cache/storage/core/mail/auth/audit/rate limit activity, and custom events. Request rows expand into correlated events that share the same traceId or requestId.

    Use the toolbar to search across event summaries, paths, messages, names, watchers, IDs, and details. The dashboard also includes method, status, and watcher filters for narrowing noisy timelines.

    Provider-owned tabs render focused panels with subsystem metrics and rows. For example, database shows query/provider context, cache shows hit and failure counts, auth shows authenticated versus guest activity, audit shows durable activity records, and rate limits show allowed versus blocked checks.

    The default buffer is in memory. Enable local persistence when you want the timeline to survive dev server restarts:

    import {
    createDevtoolsProvider,
    createFileDevtoolsStore,
    } from "@beignet/devtools";

    createDevtoolsProvider({
    store: createFileDevtoolsStore({
    filePath: ".beignet/devtools/core/events.jsonl",
    }),
    });

    You can also enable the built-in file store through environment variables:

    DEVTOOLS_PERSIST=true
    DEVTOOLS_PERSIST_PATH=.beignet/devtools/core/events.jsonl

    The file store writes JSONL and compacts to the most recent configured events. POST /api/devtools/clear clears the in-memory buffer and the configured store.

    Devtools is OpenTelemetry-compatible without depending on the OpenTelemetry SDK. createDevtoolsHooks() reads incoming W3C traceparent headers, creates a local span when one is missing, exposes the current traceparent response header, and adds trace fields to captured events.

    All captured events can include:

    • traceId: W3C trace ID for distributed correlation
    • spanId: span ID for the operation represented by the event
    • parentSpanId: parent span ID when the event is nested
    • traceparent: W3C header value for the current span

    For object contexts, the hook also adds these fields before the handler runs so use case instrumentation can attach nested spans to the request trace.

    Devtools is organized around watchers. A watcher owns one category of capture and records typed events into the shared timeline.

    Built-in watchers:

    • requests records HTTP request timing and contract route activity.
    • errors records unhandled errors, use case failures, and devtools failures.
    • useCases records application command and query execution.
    • eventBus records domain event publishing.
    • jobs records background job lifecycle events.
    • schedules records scheduled task execution.
    • providers records provider setup, start, and stop activity.
    • db records database diagnostics from first-party providers.
    • cache records cache diagnostics from first-party providers.
    • storage records storage diagnostics from first-party providers.
    • uploads records upload preparation, signing, and completion.
    • mail records mail diagnostics from first-party providers.
    • notifications records notification intent and channel delivery.
    • auth records auth diagnostics from first-party providers.
    • audit records sanitized durable audit activity emitted by application code.
    • rateLimit records rate limit diagnostics from first-party providers.
    • custom records application and integration-specific diagnostic events.

    Configure watchers through the provider:

    createDevtoolsProvider({
    watchers: {
    requests: true,
    useCases: true,
    eventBus: false,
    jobs: false,
    schedules: true,
    db: true,
    },
    });

    Disabled watchers do not store matching events. The installed watcher metadata is available through ctx.ports.devtools.getWatchers() and the dashboard API.

    Custom integrations can also register watcher metadata for their own event types. Custom watcher tabs appear in the dashboard when they own custom events:

    createDevtoolsProvider({
    watchers: {
    search: {
    label: "Search",
    description: "Search query and indexing diagnostics.",
    eventTypes: ["custom"],
    },
    },
    });

    Then record events with watcher: "search" so the custom watcher controls whether they are stored.

    Bridge the application package's onRun hook once in your shared use case factory:

    import { createUseCase } from "@beignet/core/application";
    import { createDevtoolsUseCaseObserver } from "@beignet/devtools";

    export const useCase = createUseCase<AppContext>({
    onRun: createDevtoolsUseCaseObserver<AppContext>(),
    });

    The observer reads ctx.ports.devtools, ctx.requestId, and trace context fields by default. Use case start, end, and error phases share the same span when they run with the same request context.

    First-party and app-level providers should use createProviderInstrumentation() from @beignet/core/providers instead of depending on devtools directly. The helper accepts either a ports object or an instrumentation port, records through record(), and adds provider metadata to custom events. @beignet/devtools implements that instrumentation port when createDevtoolsProvider() is registered.

    import {
    createProvider,
    createProviderInstrumentation,
    } from "@beignet/core/providers";

    export const searchProvider = createProvider({
    name: "search",
    setup({ ports }) {
    const instrumentation = createProviderInstrumentation(ports, {
    providerName: "search",
    watcher: "custom",
    });

    return {
    ports: {
    search: {
    async query(text: string) {
    const results = await runSearch(text);

    instrumentation.custom({
    name: "search.query",
    label: "Search query",
    summary: `${results.length} results`,
    details: { resultCount: results.length },
    });

    return results;
    },
    },
    },
    };
    },
    });

    Use a built-in watcher such as db, cache, storage, mail, auth, audit, rateLimit, or schedules when the provider belongs to one of those categories. Use custom or a custom watcher name for application-specific integrations.

    Durable audit logs should still be written through your app's AuditLogPort. Use createDevtoolsAuditLog() when you also want sanitized audit activity in the local devtools timeline:

    import { createDevtoolsAuditLog } from "@beignet/devtools";

    const audit = createDevtoolsAuditLog({
    audit: durableAudit,
    devtools: ports.devtools,
    });

    The wrapper records the durable audit entry first, then emits a custom devtools event owned by the audit watcher. Devtools remains a local diagnostic view; it is not the durable audit store.

    When an audit port is transaction-scoped, emit the devtools mirror only after the transaction commits. Keeping the transaction-scoped audit port durable-only is preferable to showing a local audit event for work that later rolls back.

    Use record() for application-specific events. It fills id and timestamp.

    ctx.ports.devtools.record({
    type: "custom",
    watcher: "search",
    name: "search.query",
    label: "Search query",
    summary: "24 results in 18ms",
    details: {
    query,
    resultCount: 24,
    durationMs: 18,
    },
    });

    log() is still available when you already have a complete DevtoolsEvent.

    • GET /api/devtools serves the dashboard
    • GET /api/devtools/core/events returns JSON events
    • GET /api/devtools/stream returns a live Server-Sent Events stream
    • POST /api/devtools/clear clears the in-memory buffer and configured store

    Event list query parameters:

    • type: request, error, usecase, eventBus, job, schedule, provider, or custom
    • requestId: correlation ID
    • traceId: W3C trace ID
    • limit: maximum events to return, default 200
    DEVTOOLS_ENABLED=true
    DEVTOOLS_ENABLED=false
    DEVTOOLS_MAX_EVENTS=1000
    DEVTOOLS_PERSIST=true
    DEVTOOLS_PERSIST_PATH=.beignet/devtools/core/events.jsonl

    The default buffer keeps the latest 500 events. Persistence is opt-in and uses .beignet/devtools/core/events.jsonl by default when enabled without a custom path.

    The provider controls whether events are recorded. The HTTP route controls whether those events are exposed. Both default to development-only behavior. Route handlers return 404 when NODE_ENV === "production" unless explicitly enabled:

    export const { GET, POST } = createDevtoolsRoute(server.ports.devtools, {
    basePath: "/api/devtools",
    enabled: process.env.DEVTOOLS_ENABLED === "true",
    authorize: (req: Request) =>
    req.headers.get("x-devtools-token") === process.env.DEVTOOLS_TOKEN,
    });

    If authorize returns false, devtools responds with 404. If it returns a Response, that response is used, which lets applications return their own 403 or redirect response.

    Devtools uses the shared redaction helpers from @beignet/core/ports before events are stored. Sensitive keys such as authorization, cookie, set-cookie, x-api-key, token, password, secret, and credentials are replaced with [redacted].

    Request hooks record request headers for debugging, but do not record request or response bodies by default.

    You can add a custom redactor:

    createDevtoolsHooks({
    redact: (event) => ({
    ...event,
    details: scrub(event.details),
    }),
    });

    The in-memory store also accepts a redactor for custom setups:

    const devtools = createInMemoryDevtools({
    redact: (event) => event,
    });
    interface DevtoolsPort {
    log(event: DevtoolsEvent): void;
    record(event: DevtoolsEventInput): DevtoolsEvent;
    subscribe(listener: DevtoolsListener): () => void;
    getEvents(filter?: DevtoolsFilter): DevtoolsEvent[];
    getWatchers(): DevtoolsWatcher[];
    isWatcherEnabled(name: DevtoolsWatcherName): boolean;
    clear(): void | Promise<void>;
    }
    function createFileDevtoolsStore(options?: {
    filePath?: string;
    maxEvents?: number;
    compactEvery?: number;
    }): DevtoolsEventStore;
    function createProviderInstrumentation(
    target: ProviderInstrumentationTarget,
    options: {
    providerName: string;
    watcher?: string;
    redact?: (event: ProviderInstrumentationEventInput) => ProviderInstrumentationEventInput;
    },
    ): ProviderInstrumentation;
    function createDevtoolsAuditLog(options: {
    audit: AuditLogPort;
    devtools?: DevtoolsPort;
    emit?: boolean;
    redact?: (entry: AuditLogEntry) => AuditLogEntry;
    }): AuditLogPort;
    type DevtoolsEvent =
    | RequestEvent
    | ErrorEvent
    | UseCaseEvent
    | EventBusEvent
    | JobEvent
    | ScheduleEvent
    | ProviderEvent
    | CustomDevtoolsEvent;

    All events include id, timestamp, optional requestId, optional watcher, optional traceId, optional spanId, optional parentSpanId, optional traceparent, and optional redacted details.

    createDevtoolsHooks() accepts:

    type DevtoolsHooksOptions<Ctx> = {
    basePath?: string;
    requestIdHeader?: string | false;
    traceContextHeader?: string | false;
    getRequestId?: (args: {
    req: HttpRequestLike;
    ctx?: Ctx;
    response?: HttpResponseLike;
    }) => string | undefined;
    getTraceContext?: (args: {
    req: HttpRequestLike;
    ctx?: Ctx;
    response?: HttpResponseLike;
    }) => DevtoolsTraceContextInput | string | undefined;
    redact?: DevtoolsRedactor;
    };

    createDevtoolsRoute() and handleDevtoolsRequest() accept:

    type DevtoolsRequestOptions = {
    basePath: string;
    enabled?: boolean;
    authorize?: (
    req: Request,
    ) => boolean | Response | Promise<boolean | Response>;
    };

    The HTTP handlers return 404 when NODE_ENV === "production" by default. The provider also installs a no-op devtools port in production by default so app code does not need null checks.

    Devtools can contain sensitive request, error, and domain data. Keep it on local development routes unless you intentionally add authentication and redaction.

    MIT

    Interfaces

    BaseDevtoolsEvent
    CustomDevtoolsEvent
    DevtoolsAuditLogOptions
    DevtoolsEventStore
    DevtoolsFilter
    DevtoolsHooksOptions
    DevtoolsPort
    DevtoolsProviderOptions
    DevtoolsRequestOptions
    DevtoolsRouteAccessOptions
    DevtoolsRouteHandlers
    DevtoolsTraceContext
    DevtoolsTraceContextInput
    DevtoolsUseCaseObserverOptions
    DevtoolsUseCaseRunEvent
    DevtoolsWatcher
    ErrorEvent
    EventBusEvent
    FileDevtoolsStoreOptions
    InMemoryDevtoolsOptions
    JobEvent
    ParsedTraceparent
    ProviderEvent
    RequestEvent
    ScheduleEvent
    UseCaseEvent

    Type Aliases

    BuiltInDevtoolsWatcherName
    DevtoolsAuthorize
    DevtoolsAuthorizeResult
    DevtoolsConfig
    DevtoolsEvent
    DevtoolsEventInput
    DevtoolsListener
    DevtoolsRedactor
    DevtoolsSubscriptionEvent
    DevtoolsWatcherName
    DevtoolsWatcherOptions
    DevtoolsWatchersOptions
    ProviderCustomDevtoolsEventInput
    ProviderDevtools
    ProviderDevtoolsOptions

    Variables

    BUILT_IN_DEVTOOLS_WATCHER_NAMES
    defaultDevtoolsRedactor
    DEVTOOLS_EVENT_TYPES

    Functions

    applyDevtoolsRedaction
    authorizeDevtoolsRequest
    createChildDevtoolsTraceContext
    createDevtoolsAuditLog
    createDevtoolsEvent
    createDevtoolsHooks
    createDevtoolsProvider
    createDevtoolsRoute
    createDevtoolsTraceContext
    createDevtoolsUseCaseObserver
    createFileDevtoolsStore
    createInMemoryDevtools
    createProviderDevtools
    createRedactionFailureEvent
    createSpanId
    createTraceId
    createTraceparent
    devtoolsNotFoundResponse
    handleDevtoolsClearRequest
    handleDevtoolsEventsRequest
    handleDevtoolsRequest
    handleDevtoolsStreamRequest
    handleDevtoolsUIRequest
    isDevtoolsEventEnabled
    isDevtoolsEventType
    isDevtoolsPort
    isDevtoolsRouteEnabled
    isDevtoolsWatcherEnabled
    parseTraceparent
    resolveDevtoolsPort
    resolveDevtoolsWatchers