Overview
nREPL largely consists of three abstractions: handlers, middleware, and transports. These are roughly analogous to the handlers, middleware, and adapters of Ring, though there are some important semantic differences. Finally, nREPL is fundamentally message-oriented and asynchronous (in contrast to most REPLs that build on top of streams provided by e.g. terminals).
Messages
It is convention to express nREPL messages as EDN (Clojure) maps. For most purposes, it’s sufficient to imagine that we communicate with nREPL via EDN, much the same way as we send and receive JSON to and from a REST endpoint. This is typically not what actually happens, and the details are discussed in the transport section, however, this conceptual simplification serves us well.
Requests
Each message sent to an nREPL endpoint constitutes a "request" to perform a
particular operation, which is indicated by a :op
entry. Each operation may
further require the incoming message to contain other data. Which data an
operation requires or may accept varies; for example, a message to evaluate
some code might look like this:
{:op "eval" :code "(+ 1 2 3)"}
The result(s) of performing each operation may be sent back to the nREPL client in one or more response messages, the contents of which again depend upon the operation.
Responses
The server may produce multiple messages in response to each client message (request). The structure of the response is unique per each message type, but there are a few fundamental properties that will always be around in the responses:
-
:id
The ID of the request for which the response was generated. -
:session
The ID of the session for which the response was generated. -
:status
The status of the response. Here there would either be something like "done" if a request has been fully processed or the reason for a failure (e.g. "namespace-not-found"). Not every response message would have the status key. If some request generated multiple response messages only the final one would have the status attached to it.
As mentioned earlier each op would produce different response messages. Here’s what you can expect
to see in responses generated as a result of an eval
op invocation.
-
:ns
The stringified value of*ns*
at the time of the response message’s generation. -
:out
Contains content written to*out*
while the request’s code was being evaluated. Messages containingout
content may be sent at the discretion of the server, though at minimum corresponding with flushes of the underlying stream/writer. -
:err
Same as:out
, but for*err*
. -
:value
The result of printing a result of evaluating a form in the code sent in the corresponding request. More than one value may be sent, if more than one form can be read from the request’s code string. In contrast to the output written to*out*
and*err*
, this may be usefully/reliably read and utilized by the client, e.g. in tooling contexts, assuming the evaluated code returns a printable and readable value. Interactive clients will likely want to simply stream:value
's content to their UI’s primary output / log.
Note that evaluations that are interrupted may nevertheless result in multiple response messages being sent prior to the interrupt occurring.
Your favourite editor/nREPL client might have some utility to
monitor the exchange of messages between the client and nREPL
(e.g. CIDER has a That’s a great way to get a better understanding of nREPL server responses. |
A partial clojure.spec
for nREPL messages in provided as nrepl.spec
. This covers
most messages/responses supported by the default middlewares.