Skip to content
nreplPublic

About

An nREPL server for SCI that runs in a web page

Resources

Stars

0 stars

Watchers

1 watching

Forks

Latest commit

 

History

7 Commits

Folders and files

Repository files navigation

nrepl-sci

A sketch of an nREPL server for SCI, written in ClojureScript, small enough to run in a web page.

The server itself (nrepl.sci.server) knows nothing about sockets or threads. nrepl.sci.supervisor stands in front of it, and runs it in a thread of its own (nrepl.sci.evaluator), so code can wait for input and be interrupted. There are two hosts for all that:

  • nrepl.sci.node serves it over TCP in Node, which is what clients and proof connect to
  • nrepl.sci.worker runs it in Web Workers, which take and return the bencode of each message, so a page can show exactly what goes over the wire

Other nREPL servers for SCI

SCI doesn't come with an nREPL server, but some projects built on it have one:

  • sci.nrepl, which scittle uses, lets an editor evaluate code in a page. The page connects over a websocket to a relay running in babashka or on the JVM, and the editor connects to the relay. The relay answers describe and clone itself, but every session shares the page's one REPL, printed output isn't sent back as :out, and there's no interrupt or stdin.
  • nbb has a server of its own, for Node, with most of the common ops. It's part of nbb, though, its sessions share one REPL too, and there's no interrupt or stdin.
  • babashka.nrepl is for SCI in Clojure, on the JVM or in a native image.

nrepl-sci is for something else: a server that runs entirely in a page, with the page as its client, as in the playground on nrepl.org. Nothing else has to run, sessions have their own namespace, *1 and *e, as in nREPL, and code can read input and be interrupted. An editor can't connect to it there, which is what sci.nrepl is for. The Node server is mostly there so that clients and proof can test it.

Building

You'll need the Clojure CLI and Node.

make            # out/nrepl-sci.js, the Node server
make worker     # out/nrepl-sci-worker.js, the Web Worker
make test       # runs the tests against the Node server
make proof      # runs proof against the Node server

bin/nrepl-sci --port 7888 starts the Node server.

Status

It supports describe, clone, close, ls-sessions, eval, load-file, interrupt, stdin, completions, complete, lookup, info and eldoc, and passes every proof check that applies to it.

Completion and the lookups know the special forms too, with ClojureScript's docs for them, and so does doc, which is referred in user along with the rest of clojure.repl, as in Clojure's own REPL. Output goes out a line at a time, as in nREPL, or when it's flushed.

How code is interrupted

SCI evaluates code in one go, so the evaluator can't take messages while code runs. What it needs then comes through shared memory instead (nrepl.sci.mailbox), which it checks as the code calls functions: whether it's been interrupted, and whether there's a request it could take in between. That's how a session evaluating something doesn't hold up the others, and why read-line can wait for input with Atomics.wait while still answering requests from other sessions. A session's own evals wait their turn, as in nREPL. Checking only every 256th call keeps it cheap: code runs about as fast as without any of this.

A request taken in between runs on top of the code it interrupted, so that code can't stop, even when interrupted, until the request is done. A read-line taken in between holds it until it gets its input.

The seqs that never end, (range), (repeat x), iterate and cycle, check too, so (count (range)) stops when interrupted. Code stuck in JavaScript, like a regex that backtracks forever, never checks, so the supervisor gives it a second and then kills the evaluator and starts over. The sessions are still there afterwards, but everything evaluated in them is gone. A finite seq big enough to take a while ends the same way.

Browsers only give a page SharedArrayBuffer when it's cross-origin isolated, which takes the Cross-Origin-Opener-Policy and Cross-Origin-Embedder-Policy headers. Without it, the evaluator takes requests one at a time, read-line throws and interrupting always means starting over. bin/nrepl-sci --no-shared-memory runs the Node server that way too.

License

Copyright © 2026 Bozhidar Batsov and contributors.

Distributed under the Eclipse Public License 1.0, the same as Clojure.

About

An nREPL server for SCI that runs in a web page

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages