Install | Minimum Viable Snippet | Using Datomic REST | API Overview | Limitations | Developing
datomic-cljs provides an interface to Datomic that is as close as possible to the native Clojure API. It approximates Clojure's blocking API with core.async. It supports both Node.js and modern browsers.
Warning: This is incomplete, alpha software. Everything is subject to change.
Leiningen
[com.zachallaun/datomic-cljs "0.0.1-alpha-1"]
npm
If you're targeting Node, datomic-cljs has an additional dependency.
Add the following to your package.json
:
"dependencies" : {
"request" : "2.27.0"
}
(ns example
(:require [datomic-cljs.api :as d]
[cljs.core.async :refer [<!]])
(:require-macros [cljs.core.async.macros :refer [go]]))
;; Find the entity ID associated with a person named "Frank".
;; Change his name to be "Franky".
(go
(let [conn (d/connect "localhost" 9898 "local" "friends")
[[eid]] (<! (d/q '[:find ?e :where [?e :person/name "Frank"]]
(d/db conn)))]
(<! (d/transact conn [[:db/add eid :person/name "Franky"]]))))
datomic-cljs talks to a Datomic database through the Datomic REST service. Datomic REST is a standalone server that sits in front of a transactor and exposes Datomic through HTTP.
For the sake of these examples, I'll assume that you're using Datomic Free and that you have a DATOMIC_HOME
environment variable set, pointing to the directory of your peer library.
First, start a transactor.
$ $DATOMIC_HOME/bin/transactor $DATOMIC_HOME/config/samples/free-transactor-template.properties
By default, this will start a Datomic Free transactor at datomic:free://localhost:4334/
.
With a transactor in place, you can start the Datomic REST service.
If you're planning to use datomic-cljs in the browser, you'll have to include the --origins
flag to specify allowed origins for cross-origin resource sharing.
If you're planning to use datomic-cljs on node, --origins
can be omitted from the following.
$ $DATOMIC_HOME/bin/rest --port 9898 --origins http://0.0.0.0:8000 local datomic:free://localhost:4334/
The rest
script accepts repeated alias/transactor pairs.
local
is an alias that specifies your transactor.
Once you have the service running, you can visit http://localhost:9898 in your browser to see documentation for the REST API and interact with the service through web forms.
In general, datomic-cljs exposes the same API as its Clojure counterpart. But, where the Clojure library would block awaiting a result to communicate, datomic-cljs returns a core.async channel. To learn more about core.async, I recommend David Nolen's article Communicating Sequential Processes and the core.async API documentation.
See examples/friends.cljs for an example of much of what follows.
(defn example
(:require [datomic-cljs.api :as d])
(:require-macros [datomic-cljs.macros :refer [<?]]))
Everything exciting happens in datomic-cljs.api
, with the exception of the single <?
macro from datomic-cljs.macros
.
(See Error Handling for information on <?
.)
(def conn (d/connect "localhost" ;; hostname of your running REST service
9898 ;; port
"local" ;; transactor alias
"friends")) ;; database name
This returns a core.async channel that will contain either a database connection or an error.
(go
(let [conn (<? (d/create-database "localhost" 9898 "local" "friends"))]
...))
Accepts the same arguments as connect
, but will attempt to create a new database, connecting to it if it already exists.
This returns a core.async channel that will contain either a database connection or an error.
For the most part, these are quite similar to their Clojure API counterparts, except that they return core.async channels eventually containing either results or errors. (See the Datomic reference for more information on what's possible.)
This function differs in two ways.
Its result is just a plain hash-map, in contrast to the Clojure API's lazily-evaluating, hash-map-like Entity object.
Attributes in the Entity that are refs to other entities will not contain nested entity maps. Instead, they will contain entity ids.
This avoids circular references.
To access nested entities, you'll have to pass the entity ids back to datomic-cljs.api/entity
.
;; Find an entity whose name is "Caroll".
;; Get all of her friend entities asynchronously, parking on the first.
(go
(let [db (d/db conn)
[[eid]] (<? (d/q '[:find ?e :where [?e :person/name "Caroll"]] db))
friends (->> (<? (d/entity db eid))
:person/friends
(map #(d/entity db %)))]
(<? (first friends))))
Added. This limits the results to a certain number. It composes in the same way as the other query operations.
Added. This begins returning results at a certain number. It composes in the same way as the other query operations.
Errors are put directly on return channels.
To keep from wrapping each parking take in a conditional, datomic-cljs.macros/<?
is introduced.
Instead of cljs.core.async/<!
, use datomic-cljs.macros/<?
to take from channels.
If a js/Error
is taken, it will be thrown.
Here's the Minimum Viable Snippet rewritten to handle errors:
(ns example
(:require [datomic-cljs.api :as d]
[cljs.core.async :refer [<!]])
(:require-macros [cljs.core.async.macros :refer [go]]
[datomic-cljs.macros :refer [<?]]))
;; Find the entity ID associated with a person named "Frank".
;; Change his name to be "Franky".
;; If any async operation returns an error, rethrow it!
(go
(try
(let [conn (d/connect "localhost" 9898 "local" "friends")
[[eid]] (<? (d/q '[:find ?e :where [?e :person/name "Frank"]]
(d/db conn)))]
(<? (d/transact conn [[:db/add eid :person/name "Franky"]])))
(catch js/Error e
(println "Something went wrong!"))))
There's work left to be done. For missing pieces of the API, see the bottom of api.cljs.
Things we don't have but probably should:
- For the browser, some kind of authentication story. Transaction is currently wide open.
- Much better test coverage, including tests for things like malformed input.
Testing involves shoving errors into a vector and printing it. If the vector's empty, there are no errors and we rejoice.
Assumptions are documented at the top of t_api.cljs.
$ lein cljsbuild once
$ node target/test.js
$ lein cljsbuild once
$ python -m SimpleHTTPServer 8000 # then visit 0.0.0.0:8000
This will serve the project root. Navigate to the index, which will include the browser tests, and open the console to see the results.
Note: You may see nasty CORS-related errors in the browser tests. They're unavoidable.