Docs

Request budgets

A timeout should not restart at every hop. Nifra admits one absolute wire deadline, clamps it to local policy, and exposes monotonic remaining time beside the existing cancellation signal.

Clamp once at admission

TS
import { server } from "@nifrajs/core/server"

const app = server({
  requestTimeoutMs: 2_000,
  acceptInboundDeadlines: true,
  maxInboundDeadlineMs: 5_000,
}).get("/report", async (c) => {
  // c.signal aborts at the SAME effective deadline.
  // remaining() is monotonic even if the wall clock jumps.
  return { remainingMs: c.budget.remaining() }
})

The canonical header is x-nifra-deadline, containing Unix epoch milliseconds. Admission is an explicit trust-boundary choice via acceptInboundDeadlines; ordinary public routes ignore the header by default. Once enabled, a client value can only shorten local work. Malformed values return 400, expired values return 408, and a request that exhausts an inherited deadline returns 504.

Reserve time for the caller

TS
// doc-check: skip - context and downstream adapter are established by the app
const child = c.budget.child(50) // keep 50ms to serialize the response

await fetch(url, {
  signal: child.signal,
  headers: withDeadlineHeader(undefined, child),
})

child(reserveMs) preserves response cleanup time without resetting the absolute deadline. Downstream adapters must also use remaining() for their actual timeout; forwarding the header alone is decorative.

Configure a finite requestTimeoutMs before handing c.budget to a production adapter. The no-timeout compatibility path is deliberately unbounded and is not written to the wire.