@regibyte/cljam
v0.0.20
Published
[](https://www.npmjs.com/package/@regibyte/cljam) [](LICENSE)
Maintainers
Readme
@regibyte/cljam
A Clojure interpreter written in TypeScript. Embeds in any JS/TS project as a library, runs as a standalone CLI on Node.js 18+ or Bun, and exposes a full nREPL server compatible with Calva, Cursive, and CIDER.
What it is
cljam is an interpreter. Source code is read, macro-expanded, and evaluated at runtime. An incremental compiler is built in — hot-path forms compile to native closures at definition time — but there is no Clojure → JavaScript file output today.
It is designed to be embedded. The core session API is a plain TypeScript object: create a session, inject host capabilities, evaluate strings. The CLI and nREPL server are thin wrappers around the same session.
Installation
# As a library
npm install @regibyte/cljam
# or
bun add @regibyte/cljam# As a CLI (Node.js 18+ or Bun)
npm install -g @regibyte/cljam
# or
bun install -g @regibyte/cljamEmbedding
import { createSession, printString, nodePreset } from '@regibyte/cljam'
const session = createSession({
...nodePreset(),
})
const result = session.evaluate('(map inc [1 2 3])')
console.log(printString(result)) // => (2 3 4)nodePreset() wires up Node.js filesystem and standard I/O. Use browserPreset() for the DOM, sandboxPreset() for an isolated evaluation context with no host access.
Injecting host capabilities
hostBindings makes any TypeScript value available as js/<key> in Clojure:
import { createSession, sandboxPreset } from '@regibyte/cljam'
const session = createSession({
...sandboxPreset(),
hostBindings: {
db: {
findUser: (id: number) => ({ id, name: 'Alice' }),
},
},
});; Method call: (. obj method args...)
(. js/db findUser 42)
;; => {:id 42 :name "Alice"}Using libraries
import { createSession, nodePreset } from '@regibyte/cljam'
import { library as dateLib } from '@regibyte/cljam-date'
const session = createSession({
...nodePreset(),
libraries: [dateLib],
})
session.evaluate(`
(require '[cljam.date :as d])
(d/to-iso (d/now))
`)CLI
# Interactive REPL
cljam repl
# Run a file
cljam run my-script.clj
# Start nREPL server (default port 7888)
cljam nrepl-server
cljam nrepl-server --port 7889 --host 0.0.0.0nREPL
Full TCP nREPL server with bencode transport. Supports eval, load-file, complete, clone, close, describe, and interrupt. Writes .nrepl-port on startup for auto-connect.
Calva (VS Code)
Add to .vscode/settings.json:
{
"calva.replConnectSequences": [
{
"name": "cljam nREPL",
"projectType": "generic",
"nReplPortFile": [".nrepl-port"]
}
]
}Then: Calva: Connect to Running REPL Server in Project → select cljam nREPL.
CIDER (Emacs)
M-x cider-connect RET
Host: localhost RET
Port: 7888 RETCursive (IntelliJ)
Run → Edit Configurations → + → Clojure REPL → Remote → host localhost, port 7888.
Source Root Discovery
When running cljam nrepl-server or cljam run, cljam reads the cljam.sourceRoots field from package.json:
{
"cljam": {
"sourceRoots": ["src/clojure"]
}
}Source roots control how require resolves namespace files. Falls back to the current working directory if not set.
Language Features
- Immutable collections: vectors, maps, sets, lists
- Namespaces with
ns,require,refer,alias - Multi-arity and variadic functions
- Sequential and associative destructuring (nested,
:keys,:syms,:strs, kwargs) - Macros:
defmacro, quasiquote/unquote/splicing,macroexpand,macroexpand-all - Atoms for controlled mutable state
loop/recurwith tail-call optimization- Transducers:
transduce,intowith xf - Threading macros:
->,->> - Anonymous function shorthand:
#(+ % 1) try/catch/finallywith predicate-based discriminators (no class hierarchy)- Protocols and records:
defprotocol,defrecord,extend-protocol,extend-type - Multimethods:
defmulti,defmethod, keyword hierarchy (derive,isa?) - Character literals:
\a,\space,\newline,\uXXXX - EDN reader:
clojure.edn/read-string, reader tags#instand#uuid - JS interop:
js/namespace,.member access,js/new
Standard namespaces bundled at startup: clojure.core clojure.string clojure.edn clojure.math clojure.test — all implemented in Clojure itself and readable in the source tree.
JS Interop
Inject host values with hostBindings, then access them as js/<key> in Clojure:
createSession({
...nodePreset(),
hostBindings: { Math, console, myLib: require('./my-lib') },
});; Method call: (. obj method args...)
(. js/Math pow 2 10) ;; => 1024.0
(. js/console log "hello")
;; Constructors
(def now (js/new js/Date))
(. now toISOString) ;; => "2026-04-14T..."
;; Injected objects — hostBindings: { myLib: require('./my-lib') }
(. js/myLib greet "world") ;; => calls myLib.greet("world")
;; Call a bare function with no this binding
(js/call js/myFn 1 2 3)
;; Load a Node/npm module via (:require ["specifier" :as alias])
;; Requires importModule: (s) => import(s) in session options(ns demo
(:require ["node:path" :as path]))
(. path join "a" "b" "c") ;; => "a/b/c"Building Libraries
A cljam library is a TypeScript package that provides a CljamLibrary manifest containing Clojure namespaces and/or native runtime modules. The cljam gen-lib-source command generates the TypeScript sources map from your .clj files.
Quickstart
# devDependency in your library package
npm install --save-dev @regibyte/cljam// package.json
{
"scripts": {
"gen:sources": "cljam gen-lib-source src/clojure src/generated/sources.ts",
"prepublishOnly": "npm run gen:sources"
}
}// conjure.ts — the library manifest
import type { CljamLibrary } from '@regibyte/cljam'
import { sources } from './src/generated/sources'
export const library: CljamLibrary = {
id: 'my-lib',
sources,
};; src/clojure/my-lib/core.clj
(ns my-lib.core)
(defn greet [name] (str "Hello, " name "!"))Run npm run gen:sources to regenerate sources.ts whenever you edit .clj files. The generated file is checked in — it only rewrites when content changes, so diffs stay clean.
For libraries with native TypeScript modules (wrapping JS APIs), see @regibyte/cljam-date as a reference implementation.
Protocols and Records
defprotocol, defrecord, extend-protocol, and extend-type work as in JVM Clojure. Dispatch uses keyword type tags instead of Java class names:
(require '[clojure.math :as math])
(defprotocol IArea
(area [shape] "Compute the area"))
(defrecord Circle [radius])
(defrecord Rect [w h])
(extend-protocol IArea
:user/Circle (area [c] (* math/PI (:radius c) (:radius c)))
:user/Rect (area [r] (* (:w r) (:h r))))
(area (->Circle 5)) ;; => 78.53981633974483
(area (->Rect 4 6)) ;; => 24Type tags are keywords of the form :ns/RecordName for records, and :string :int :boolean etc. for built-in types. Use (type x) to get the tag for any value.
Testing with clojure.test
clojure.test is built in. *.test.clj and *.spec.clj files run natively in Vitest via cljTestPlugin():
(ns my-app.core-test
(:require [clojure.test :refer [deftest is testing run-tests]]))
(deftest arithmetic-works
(testing "addition"
(is (= 4 (+ 2 2))))
(testing "strings"
(is (= "hello" (str "hel" "lo")))))
(run-tests 'my-app.core-test)
;; {:test 1 :pass 2 :fail 0 :error 0}To enable *.test.clj → Vitest integration:
// vite.config.ts
import { cljTestPlugin } from '@regibyte/cljam/vite-plugin'
export default { plugins: [cljTestPlugin()] }Key Differences from JVM Clojure
| JVM Clojure | cljam |
|---|---|
| Java interop (.method, new Foo, java.lang.*) | Not available — use js/ namespace |
| defrecord, defprotocol, extend-protocol | Available — dispatch on keyword type tags, not Java classes |
| deftype | Not available |
| future, agent, STM (ref, dosync) | Not available — use (async ...) with @deref as await |
| Long, BigDecimal, ratio literals (1/3) | Numbers are IEEE-754 floats |
| Class-based catch (catch Exception e) | Predicate/keyword discriminators only |
| import, Java class hierarchy | Not available |
| ##NaN, ##Inf, ##-Inf reader literals | Not yet — use (clojure.math/sqrt -1) for NaN, (clojure.math/log 0) for -∞ |
LLM Integration
@regibyte/cljam-mcp is an MCP server that exposes a persistent cljam REPL to LLM agents. Eval code, load files, introspect with describe, and pair-program with a human developer in Calva — sharing the same live runtime.
See @regibyte/cljam-mcp for setup instructions.
License
MIT
