Effects

The AST is the contract. ZIO[R, E, A] is the body.

Handler is Request => ZIO[R, E, Response] when you are still on raw routes. Api.bind is In => ZIO[R, E, Out] once the endpoint exists. Both are ordinary ZIO. There is no event-loop rule. Accept, read, and write run on the JVM, on Node, and on Native.

the effect is the body · click a step

GET /shows/1 arrives. Path codec captures id = 1.

Request.get("/shows/1")

Walk a request

Click a step. Decode is not your code. The bind is. Encode is not your code. Interrupting the connection interrupts the fiber. Same widget as Bind the effect.

Middleware is still an effect

@@ applies middleware. a ++ b composes: the request hits b first (outer), then a. requestId, cors, compress, debug, and the auth helpers live here. They are Routes => Routes, not a parallel effect system.

{
  val routes =
    Routes(Method.GET / "x" -> Handler.text("ok")) @@ Middleware.requestId()
  routes(Request.get("/x")).map { res =>
    res.header("X-Request-Id").exists(_.nonEmpty)
  }
}
true

Streams are streams

Bodies on the wire are streams. Body.asString / asBytes are for in-memory bodies. SSE is a ZStream of ServerSentEvent. Do not pretend a stream is a List. Interrupt the client, the stream fiber stops.