ClojureScript Support
August 26, 2026 ยท View on GitHub
This feature is in beta. Please try it out and provide feedback.
Datahike runs in ClojureScript on both Node.js and browser environments. The core query engine, pull API, and entity API work identically to Clojure. The main differences are in storage backends and async operation.
Async Storage Model
ClojureScript environments often require async I/O (IndexedDB, network). To support this, Datahike's persistent-sorted-set indices use partial continuation-passing style (partial-cps): storage operations return channels instead of values, while in-memory operations remain synchronous.
This is transparent when using datahike.api - async backends return channels
from create-database, connect, transact!, etc.
Node.js
Node.js supports the file backend via konserve-node-filestore:
(ns my-app.core
(:require [datahike.api :as d]
[datahike.nodejs] ;; Registers :file backend
[cljs.core.async :refer [<!] :refer-macros [go]]))
(go
(let [config {:store {:backend :file
:path "./my-database"
:id #uuid "550e8400-e29b-41d4-a716-446655440000"}
:schema-flexibility :write}]
(<! (d/create-database config))
(let [conn (<! (d/connect config))]
(<! (d/transact! conn [{:name "Alice"}]))
(println (d/q '[:find ?n :where [?e :name ?n]] (d/db conn))))))
Browser
Browsers cannot use file storage directly. Datahike provides two browser-compatible backends:
Memory backend
Fast but not persistent across page reloads:
{:store {:backend :memory :id #uuid "550e8400-e29b-41d4-a716-446655440000"}}
IndexedDB backend
Persistent browser storage via konserve-indexeddb:
{:store {:backend :indexeddb :id #uuid "550e8400-e29b-41d4-a716-446655440000"}}
IndexedDB is async, so all operations return channels.
TieredStore
For optimal performance, use TieredStore which combines a fast memory frontend with a persistent IndexedDB backend:
{:store {:backend :tiered
:id #uuid "550e8400-e29b-41d4-a716-446655440000"
:frontend-config {:backend :memory
:id #uuid "550e8400-e29b-41d4-a716-446655440000"}
:backend-config {:backend :indexeddb
:id #uuid "550e8400-e29b-41d4-a716-446655440000"}}}
Writes go to both stores (write-through). Reads come from memory. On reconnection, TieredStore syncs cached data from IndexedDB into memory before the sync handshake, so only keys newer than cached timestamps are transferred.
Distributed Setup with KabelWriter
For browser applications that need a persistent backend, use the KabelWriter to connect to a JVM server. The server owns the database (using file storage) and streams updates to browser clients via WebSockets. See Streaming writer (Kabel) for setup instructions.
This architecture means:
- Transactions are sent to the server via RPC
- The server writes to its file store
- Updates are replicated to the client's TieredStore via konserve-sync
- Queries run locally against the synchronized in-memory store
For UIs where the round-trip latency is user-visible, see the
Optimistic Overlay โ datahike.optimistic
layers pending transactions on top of @conn via d/with so
listeners see the change immediately, then reconcile when the writer
confirms. Experimental.
JavaScript API
For JavaScript/TypeScript applications, Datahike provides a Promise-based API. See JavaScript API for documentation.
Installation:
npm install datahike@next
Example:
const d = require('datahike');
const config = {
store: {
backend: ':memory',
id: d.randomUuid()
},
'schema-flexibility': ':read' // Allow schemaless data (use kebab-case)
};
await d.createDatabase(config);
const conn = await d.connect(config);
await d.transact(conn, [{ name: 'Alice' }]);
const db = await d.db(conn); // db() is async for async backends
const results = await d.q('[:find ?n :where [?e :name ?n]]', db);
console.log(results);
// => [['Alice']]
Limitations
- History queries: Work but may be slower due to async index traversal
- Large datasets: Browser memory constraints apply; consider server-side queries for large result sets
- Concurrent tabs: Each tab has its own connection; use KabelWriter for cross-tab consistency