Node adapter available · 49 tests passing

Every request
owns its work.

Nelo gives TypeScript servers a clear lifetime for tasks, cancellation, resources, streaming, and shutdown.

Web Standards at the boundary. Explicit ownership underneath.

server.ts
import { Nelo } from "nelo";
import { serve } from "nelo/node";

const app = new Nelo();

app.get("/report", async (c) => {
  const report = c.fork(() => buildReport(c.signal));
  return c.json(await report);
});

await serve(app, { port: 3000 }).listen();
RequestOwned scopeResponse
49passing tests
16real-socket Node tests
20–24Node CI matrix
ESMclean tarball consumer smoke

Async work needs
an owner.

A handler can return while nested work, streams, and resources are still active. Nelo makes that boundary explicit instead of relying on developer discipline alone.

Without ownership

implicit lifetime
  • Tasks float.
    Promises may outlive the request that created them.
  • Cancellation stops early.
    Nested operations do not share one reason or signal.
  • Cleanup is scattered.
    Partial failures leave resources in uncertain states.

With Nelo

owned lifetime
  • Tasks are registered.
    c.fork() binds work to the current request.
  • One reason propagates.
    Disconnect and shutdown flow through the same ownership tree.
  • Resources close deterministically.
    Cleanup runs once and in LIFO order.

One request.
A visible lifetime.

The Node adapter connects Web Standards to real sockets, backpressure, client disconnects, and graceful shutdown without leaking Node-specific types into the portable root package.

GET /reporthandling
Handler-owned
route handler active
owned task joined
resource registered
Transport-owned
response body waiting
socket delivery waiting
shutdown signal idle

The request scope records work as soon as it starts.

Small surface.
Hard boundary.

Nelo does not replace the platform. It keeps standard Request and Response objects while adding the ownership primitives that server applications are missing.

Owned tasks

context.fork() creates eager, awaitable work that belongs to the request scope.

Typed cancellation

The first disconnect, error, or shutdown reason is preserved and propagated cooperatively.

LIFO cleanup

context.use() releases resources once, in reverse acquisition order, even after failure.

Backpressure

The Node adapter reads response streams incrementally and waits for drain before continuing.

Graceful shutdown

Stop accepting new requests, allow a grace window, then signal and close remaining sockets.

Web Standards

Application code works with Request, Response, Headers, ReadableStream, and AbortSignal.

Runtime status

The matrix reflects implemented and verified behavior, not aspirational compatibility.

Target
Status
Verified boundary
Portable core
Available
Request → app.fetch() → Response
Node.js 20 / 22 / 24
Adapter available
Real sockets, disconnects, streams, backpressure, shutdown
Deno
Core tested
Portable execution; production adapter not yet shipped
Cloudflare / Bun
Planned
No production adapter claim yet

Build inside a boundary.

Start with the portable Fetch API, then use nelo/node when transport-aware ownership is required.