newX 1.3: islands to disk, server-mode islands, and an image proxy

Observability

Request tracing

Instrument your application with request-scoped spans. Every trace carries a correlation id, and spans created inside a traced request inherit it automatically, so you can drill from a log line into a waterfall trace in your APM.

How tracing works

The tracing layer is a lightweight wrapper around OpenTelemetry-compatible tracers. X never initializes an OTel SDK — your application provides a tracer whose interface matches the small TracerLike surface (the real OTel startSpan method works). When no tracer is configured, every tracing call is a synchronous no-op with zero overhead.

Setting up a tracer

Call setTracer once at application startup with your OTel tracer. Then wrap your request handler with withRequestTracing to create an x.http root span for every request:

src/server.ts
import { setTracer, withRequestTracing } from "@thexjs/core";import { trace } from "@opentelemetry/api";// Your OTel SDK setup — initialise the provider firstconst otelTracer = trace.getTracer("my-app");setTracer(otelTracer);// Wrap the framework's own request handlerconst app = createApp({ ... });Bun.serve({  fetch: withRequestTracing((req) => app.fetch(req)),  port: 3000,});

Every incoming request now gets an x.http root span with attributes route, method, x.requestId, and (once the response is written) http.response.status_code.

Instrumenting loaders and actions

Use tracePhase to create child spans inside your loaders and server functions. The span inherits the current request's correlation id automatically:

src/pages/dashboard.tsx — loader with tracing
import { tracePhase } from "@thexjs/core";export async function loader({}: LoaderArgs) {  const users = await tracePhase("fetch.users", { "db.table": "users" }, async () => {    return db.query("SELECT * FROM users ORDER BY created_at DESC").all();  });  return { users };}

If the request is not being traced (e.g. a build-time render or a background job), tracePhase runs the function with zero overhead — no span is created. For synchronous work, use tracePhaseSync with the same signature.

Database trace attributes

The dbTraceAttributes helper builds a standard set of DB-span attributes — system, operation, and a redacted statement — for use with any database driver:

src/lib/db.ts
import { tracePhase, dbTraceAttributes } from "@thexjs/core";function queryUsers() {  return tracePhase("db.query", dbTraceAttributes("sqlite", "SELECT * FROM users WHERE id = ?"), async () => {    return db.query("SELECT * FROM users WHERE id = ?").all();  });}

The statement has its quoted literals masked (so bound parameters never appear in traces) and is truncated to 512 characters. The redaction layer also strips bearer/authorization-shaped patterns before the attribute is set.

Error status codes

When an instrumented phase throws, the span is marked with error status and the exception is recorded. The OTEL_ERROR_STATUS_CODE constant matches OpenTelemetry's SpanStatusCode.ERROR (2) for when you need to set error status yourself:

manual error handling
import { OTEL_ERROR_STATUS_CODE, getTracer } from "@thexjs/core";const span = getTracer().startSpan("custom.work");try {  const result = doWork();  span.end();  return result;} catch (error) {  span.recordException(error);  span.setStatus({ code: OTEL_ERROR_STATUS_CODE, message: String(error) });  span.end();  throw error;}

Low-level APIs

For advanced use cases — writing a custom server, running background jobs, or instrumenting non-request code — the underlying primitives are exported directly:

  • runWithRequestSpan(requestId, attributes, work) runs work inside a root span and an AsyncLocalStorage context, returning the work's value.
  • traceRequestId(req) returns the request id a given Request is traced under, minting one if none exists.
  • setTracer(tracer) and getTracer() manage the active tracer globally.