@beignet/devtools

中文文档
0.0.65发布于 2026/11/08MITTypeScriptES Module近 30 天下载 1984

Development-time devtools for Beignet - mini Telescope for the server runtime

原始使用文档
文档目录17 节
  1. Agent skill
  2. Install
  3. Next.js setup
  4. Local persistence
  5. Trace context
  6. Watchers
  7. Use case instrumentation
  8. Provider instrumentation
  9. Audit activity
  10. Manual events
  11. Endpoints
  12. Configuration
  13. Redaction
  14. API
  15. Production safety
  16. Development
  17. License

@beignet/devtools

Runtime: Beignet requires Node.js 22.12 or newer. Bun is optional.

[!CAUTION] Beignet is experimental alpha software. The 0.0.x package line is for early evaluation, and APIs may change between releases while the framework settles.

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

The event provider is enabled outside production by default and returns a no-op port in production unless you explicitly enable it. HTTP routes auto-enable only in development.

createDevtoolsProvider(...) returns the stable DevtoolsProvider type. DevtoolsConfig describes its validated config; the Zod schema remains an internal implementation detail.

Agent skill

This package ships the TanStack Intent skill @beignet/devtools#runtime-safety. Load it when configuring capture, exposing the HTTP dashboard outside local development, adding authorization or custom redaction, reviewing persisted diagnostics, or debugging provider events.

Install

bun add @beignet/devtools @beignet/core

Next.js setup

Register the provider:

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

export const getServer = createNextServerLoader(() =>
  createNextServer({
    ports,
    providers: [createDevtoolsProvider(), ...providers],
    context: ({ ports, requestId, trace }) => ({
      requestId,
      ...trace,
      ports,
    }),
  }),
);

The provider is the only devtools wiring the server needs. Request and error events are recorded by the server's built-in instrumentation, which resolves the provider instrumentation port from final ports (ports.instrumentation, then ports.devtools). Configure headers, ignored paths, redaction, and capture decisions with the instrumentation option on createServer(...) / createNextServer(...).

Registering the provider is optional. If @beignet/devtools is installed but createDevtoolsProvider() is not registered, the server runs without devtools and beignet doctor reports an informational hint that does not fail doctor --strict.

Add a catch-all route:

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

export const { GET, POST } = createDevtoolsRoute(
  async () => (await getServer()).ports.devtools,
  {
    basePath: "/api/devtools",
    authorize: async (req) =>
      Boolean(await (await getServer()).ports.auth.getSession(req)),
  },
);

Sign in, then open /api/devtools.

The route remains hidden unless you provide authorize. Browser requests with an Origin header must also be same-origin. The generated app uses the auth port so a signed-in session can open the dashboard.

The dashboard connects to the event stream with Server-Sent Events and falls back to polling when EventSource is unavailable. A sidebar groups views by section — Overview, HTTP, Application, Infrastructure, and Operations — with live event-count badges per view, plus dynamic views for enabled custom watchers. On narrow screens, the sidebar collapses to an icon rail so the active view keeps usable space. The header shows clickable stats (events, requests, errors, use cases), live connection status, Pause/Resume, and Clear. Pausing buffers incoming events and reveals them on resume.

The stream sends a snapshot on every connection, emits heartbeat comments, and closes after four minutes so EventSource reconnects with a fresh bounded snapshot. When the host reports a disconnect through request abort or response cancellation, the stream releases the in-memory subscription; timed closure does the same. The response stream also caps unread encoded data at 1 MiB and closes so EventSource can reconnect instead of retaining an unbounded queue. Initial snapshots keep watcher metadata that fits, then take a contiguous newest-first event window and stop when the next entry cannot fit. The dashboard displays the resulting omittedEvents and omittedWatchers counts as a partial-snapshot warning, preventing an oversized stored event from causing a permanent reconnect loop or repeated inspection of the remaining buffer.

The dashboard follows the system color scheme and includes a manual light/dark/system toggle persisted in localStorage. The theme matches the shadcn look of generated Beignet starter apps.

Request rows expand into an end-to-end lifecycle view for events that share the same traceId or requestId. The detail renders a lifecycle waterfall: correlated spans drawn as relative timing bars (durations are measured at completion), alongside overview facts such as route match, contract name, request ID, trace ID, method, path, status, duration, and response ownership, a redaction note, correlated activity grouped by category, and the raw JSON.

Press / to focus search, which matches event summaries, paths, messages, names, watchers, IDs, and details. Method, status, and watcher filters narrow noisy timelines. Timeline rows lead with the human-readable summary, and IDs truncate to copy-on-click chips. The JSON detail viewer renders syntax highlighting, collapsible nodes, and a copy button.

The errors view groups failures by owner: route, framework, provider, job, schedule, outbox, client-side, devtools, or unknown. Each row shows the message, owner, related route/status, request ID, trace ID, and nearby correlated events so you can see whether a failure belongs to application code, the framework boundary, provider work, or background execution.

Focus panels render subsystem metrics, row summaries, and drill-down fields for each Beignet workflow primitive. Database shows query counts, failures, inferred table names, redaction status, provider name, port, parameter count, and request/trace IDs when the runtime can preserve active request context. Outbox shows delivered/retry/dead-letter counts, message name/kind, attempts, retry timing, and related request/trace context. Jobs and schedules show lifecycle status, provider, retry/error details, cron metadata, and request context. Cache, storage, uploads, mail, payments, entitlements, notifications, auth, audit, policies, and rate limits show domain-specific metrics such as hits/misses, missing objects, file counts, recipients, checkout sessions, verified webhooks, product access decisions, channels, session state, durable audit actors/resources, policy decisions, and allowed versus blocked checks.

Use the request lifecycle view first when debugging a route. It shows whether the response was route-owned or framework-owned, which use cases ran, which correlated provider events were recorded, which jobs/outbox/schedule/domain events were triggered, and whether stored metadata was redacted.

You can assert this graph in your own integration tests: drive real HTTP write and error requests, read the devtools events endpoint, and verify that request rows, use-case spans, durable audit-port side effects, and route-owned errors stay correlated by request metadata. Provider queries and durable outbox writes appear in the same timeline.

Local persistence

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.

Trace context

Devtools is OpenTelemetry-compatible without depending on the OpenTelemetry SDK. The trace primitives live in @beignet/core/tracing: the server 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

Spread the trace context argument into your app context so use case instrumentation can attach nested spans to the request trace.

Watchers

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.
  • outbox records durable outbox delivery, retries, and dead letters.
  • schedules records schedule 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.
  • flags records feature flag evaluations, explicit exposures, and tracking events 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.
  • payments records checkout, portal, refund, and webhook diagnostics from payment providers.
  • entitlements records product access decisions from billing or plan state.
  • 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.
  • policies records application authorization policy decisions.
  • 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,
    outbox: true,
    schedules: true,
    db: true,
    payments: 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 views appear in the dashboard sidebar 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.

Use case instrumentation

createUseCase(...) from @beignet/core/application instruments every run by default — no devtools-specific wiring is needed:

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

export const useCase = createUseCase<AppContext>();

Each run resolves the instrumentation port from ctx.ports (ports.instrumentation, then ports.devtools) and reads ctx.requestId and trace context fields. Use case start, end, and error phases share the same span. Pass instrumentation: false to opt out.

Provider instrumentation

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.

When provider instrumentation records an event during an active request, the runtime preserves async request context, and the event does not already include correlation fields, devtools adds the active requestId, traceId, and traceparent. This keeps provider work correlated with the route, hook, and use-case timeline without making providers depend on app-specific context types.

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, payments, flags, entitlements, auth, audit, policies, rateLimit, or schedules when the provider belongs to one of those categories. Use custom or a custom watcher name for application-specific integrations.

Audit activity

Durable audit logs should still be written through your app's AuditLogPort. Use createInstrumentedAuditLog() from @beignet/core/ports when you also want sanitized audit activity in the local devtools timeline:

import { createInstrumentedAuditLog } from "@beignet/core/ports";

const audit = createInstrumentedAuditLog({
  audit: durableAudit,
  instrumentation: ports,
});

The wrapper records the durable audit entry first, then emits a custom event owned by the audit watcher into the resolved instrumentation port. 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.

Manual events

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: {
    queryLength: query.length,
    resultCount: 24,
    durationMs: 18,
  },
});

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

Endpoints

  • 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 with a fresh snapshot on each connection
  • 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

Configuration

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. Route handlers auto-enable only when NODE_ENV === "development", but an enabled route still requires an app-owned authorize callback by default. Unset, test, preview, staging, and production environments also require enabled: true:

import { createDevtoolsRoute } from "@beignet/devtools";
import { getServer } from "@/server";

export const { GET, POST } = createDevtoolsRoute(
  async () => (await getServer()).ports.devtools,
  {
    basePath: "/api/devtools",
    enabled: process.env.DEVTOOLS_ENABLED === "true",
    authorize: async (req: Request) => {
      const session = await (await getServer()).ports.auth.getSession(req);
      return session?.user.id === process.env.DEVTOOLS_ADMIN_USER_ID;
    },
  },
);

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.

Browser requests that carry an Origin header must be same-origin; authorization does not permit a cross-origin browser request.

For a local development server whose HTTP listener is bound exclusively to loopback, you can deliberately omit authentication:

createDevtoolsRoute(devtools, {
  basePath: "/api/devtools",
  allowUnauthenticatedDevelopmentAccess: true,
});

This option works only when NODE_ENV === "development" and the request URL uses localhost, a *.localhost name, 127.0.0.0/8, or [::1]. Do not use it with a listener bound to 0.0.0.0, a LAN interface, a container-accessible interface, or a hosted development environment. A Fetch Request exposes its destination URL, not the requester's network address, so the hostname check is not peer authentication.

Use cookie/session authorization for the embedded dashboard. Its same-origin fetch and EventSource requests include cookies, but browsers cannot attach an application-defined authorization header to EventSource.

Redaction

Devtools uses the shared redaction helpers from @beignet/core/ports before events are stored. Sensitive keys such as authorization, proxy-authorization, cookie, set-cookie, x-api-key, accessKey, jwt, session, token, password, secret, and credentials are replaced with [redacted]. High-confidence credential shapes embedded in strings—including Bearer and Basic credentials, JWTs, credential-bearing URLs, secret assignments, and private keys—are scrubbed too, including in error messages and stacks.

Server request instrumentation records request headers for debugging, but does not record request or response bodies by default. The dashboard marks request lifecycle details when sensitive fields were redacted and warns if secret-shaped metadata keys remain visible in stored events.

You can add a custom redactor through the server's instrumentation option:

const server = await createNextServer({
  // ...
  instrumentation: {
    redact: (event) => ({
      ...event,
      details: scrub(event.details),
    }),
  },
});

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

const devtools = createInMemoryDevtools({
  redact: (event) => event,
});

API

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;
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.

Request/error capture is configured on the server, not in this package. See the instrumentation option on createServer(...) in @beignet/core/server:

type ServerInstrumentationOptions<Ctx> = {
  requestIdHeader?: string | false; // default "x-request-id"
  traceContextHeader?: string | false; // default "traceparent"
  ignorePaths?: readonly string[]; // default ["/api/devtools"]
  redact?: (
    event: ProviderInstrumentationEventInput,
  ) => ProviderInstrumentationEventInput;
  shouldCapture?: (args: {
    req: HttpRequestLike;
    ctx?: Ctx;
    contract: HttpContractConfig;
    response: HttpResponseLike;
    error?: unknown;
  }) => boolean;
};

createDevtoolsRoute() and handleDevtoolsRequest() accept:

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

Production safety

The HTTP handlers auto-enable only when NODE_ENV === "development", but they remain hidden without authorize or the explicit local-only development opt-in. Setting enabled: true in another environment never activates the unauthenticated opt-in. The provider also installs a no-op devtools port in production by default so app code does not need null checks.

Requests that carry an Origin header must match the request URL's origin. Treat that as a browser safeguard, not a substitute for authorization.

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

Development

The dashboard UI is a React app in ui/. bun run build:ui compiles it into one self-contained HTML file and embeds it in the package as a generated module (src/ui-html.generated.ts, gitignored); bun run build runs build:ui and then the TypeScript build. From a fresh clone, run bun run build once before running this package's tests directly — the root bun run test handles that ordering through turbo.

License

MIT

版本与发布记录

@beignet/devtools 共发布 65 个版本,首次发布于 2026/05/27;过去 12 个月发布了 65 个版本。

版本发布日期距今
0.0.65latest2026/11/08今天
0.0.642026/11/034 天前
0.0.632026/11/026 天前
0.0.622026/11/017 天前
0.0.612026/11/017 天前
0.0.602026/11/017 天前

关于 @beignet/devtools 的常见问题

@beignet/devtools 是什么?

Development-time devtools for Beignet - mini Telescope for the server runtime

如何安装 @beignet/devtools?

在项目目录运行 npm install @beignet/devtools,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:@beignet/core(>=0.0.3 <1.0.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@beignet/devtools 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@beignet/devtools 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@beignet/devtools 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.12.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@beignet/devtools 可以商用吗?

@beignet/devtools 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@beignet/devtools 的下载量有多少?

根据 npm 官方下载统计,@beignet/devtools 在 2026/10/08 – 2026/11/06 这 30 天内被下载 1,984 次,日均约 66 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@beignet/devtools 还在维护吗?

最新版本 0.0.65 发布于 2026/11/08(今天);至今共发布 65 个版本,其中过去 12 个月发布了 65 个;首次发布于 2026/05/27。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。