Improve ClojureScript->JavaScript roundtripping, and flesh out example Node test.

This commit is contained in:
Richard Newman 2016-10-05 16:41:12 -07:00
commit f930d1312a
3 changed files with 92 additions and 26 deletions

View file

@ -12,6 +12,7 @@
[cljs.reader] [cljs.reader]
[cljs-promises.core :refer [promise]] [cljs-promises.core :refer [promise]]
[datomish.cljify :refer [cljify]] [datomish.cljify :refer [cljify]]
[datomish.api :as d]
[datomish.db :as db] [datomish.db :as db]
[datomish.db-factory :as db-factory] [datomish.db-factory :as db-factory]
[datomish.pair-chan] [datomish.pair-chan]
@ -24,14 +25,13 @@
;; Public API. ;; Public API.
(defn ^:export db [conn] (def ^:export db d/db)
(transact/db conn))
(defn ^:export q [db find options] (defn ^:export q [db find options]
(let [find (cljs.reader/read-string find) (let [find (cljs.reader/read-string find)
opts (cljify options)] opts (cljify options)]
(take-pair-as-promise! (take-pair-as-promise!
(db/<?q db find opts) (d/<q db find opts)
clj->js))) clj->js)))
(defn ^:export ensure-schema [conn simple-schema] (defn ^:export ensure-schema [conn simple-schema]
@ -39,7 +39,7 @@
datoms (simple-schema/simple-schema->schema simple-schema)] datoms (simple-schema/simple-schema->schema simple-schema)]
(println "Transacting schema datoms" (pr-str datoms)) (println "Transacting schema datoms" (pr-str datoms))
(take-pair-as-promise! (take-pair-as-promise!
(transact/<transact! (d/<transact!
conn conn
datoms) datoms)
clj->js))) clj->js)))
@ -52,9 +52,8 @@
;; Expects a JS array as input. ;; Expects a JS array as input.
(try (try
(let [tx-data (js->tx-data tx-data)] (let [tx-data (js->tx-data tx-data)]
(println "Transacting:" (pr-str tx-data))
(go-promise clj->js (go-promise clj->js
(let [tx-result (<? (transact/<transact! conn tx-data))] (let [tx-result (<? (d/<transact! conn tx-data))]
(select-keys tx-result (select-keys tx-result
[:tempids [:tempids
:added-idents :added-idents
@ -73,10 +72,20 @@
;; We pickle the connection as a thunk here so it roundtrips through JS ;; We pickle the connection as a thunk here so it roundtrips through JS
;; without incident. ;; without incident.
{:conn (fn [] c) {:conn (fn [] c)
:roundtrip (fn [x] (clj->js (cljify x))) :db (fn [] (d/db c))
:db (fn [] (transact/db c)) :path path
;; Primary API.
:ensureSchema (fn [simple-schema] (ensure-schema c simple-schema)) :ensureSchema (fn [simple-schema] (ensure-schema c simple-schema))
:transact (fn [tx-data] (transact c tx-data)) :transact (fn [tx-data] (transact c tx-data))
:q (fn [find opts] (q (d/db c) find opts))
:close (fn [] (db/close-db db)) :close (fn [] (db/close-db db))
;; Some helpers for testing the bridge.
:equal =
:idx (fn [tempid] (:idx tempid))
:cljify cljify
:roundtrip (fn [x] (clj->js (cljify x)))
:toString (fn [] (str "#<DB " path ">")) :toString (fn [] (str "#<DB " path ">"))
:path path})))) }))))

View file

@ -1,7 +1,30 @@
(ns datomish.cljify) (ns datomish.cljify)
(defn cljify (defn cljify
"In node, equivalent to `(js->clj o :keywordize-keys true). "In node, equivalent to `(js->clj o :keywordize-keys true),
See <http://dev.clojure.org/jira/browse/CLJS-439?focusedCommentId=43909>." but successfully passes Clojure Records through. This allows JS API
[o] callers to round-trip values they receive from ClojureScript APIs."
(js->clj o :keywordize-keys true)) [x]
;; This implementation is almost identical to js->clj, but it allows
;; us to hook into the recursion into sequences and objects, and it
;; passes through records.
(if (record? x)
x
(cond
(satisfies? IEncodeClojure x)
(-js->clj x (apply array-map {:keywordize-keys true}))
(seq? x)
(doall (map cljify x))
(coll? x)
(into (empty x) (map cljify x))
(array? x)
(vec (map cljify x))
(identical? (type x) js/Object)
(into {} (for [k (js-keys x)]
[(keyword k) (cljify (aget x k))]))
:else x)))

View file

@ -15,29 +15,63 @@ var schema = {
{"name": "page/title", {"name": "page/title",
"type": "string", "type": "string",
"cardinality": "one", "cardinality": "one",
"doc": "A page's title."}, "doc": "A page's title."}
{"name": "page/starred",
"type": "boolean",
"cardinality": "one",
"doc": "Whether the page is starred."},
{"name": "page/visit",
"type": "ref",
"cardinality": "many",
"doc": "A visit to the page."}
] ]
}; };
async function testOpen() { async function testOpen() {
// Open a database.
let db = await datomish.open("/tmp/testing.db"); let db = await datomish.open("/tmp/testing.db");
// Make sure we have our current schema.
await db.ensureSchema(schema); await db.ensureSchema(schema);
let txResult = await db.transact([{"db/id": 55,
"page/url": "http://foo.com/bar", // Add some data. Note that we use a temporary ID (the real ID
"page/starred": true}]); // will be assigned by Datomish).
let txResult = await db.transact([
{"db/id": datomish.tempid(),
"page/url": "https://mozilla.org/",
"page/title": "Mozilla"}
]);
console.log("Transaction returned " + JSON.stringify(txResult)); console.log("Transaction returned " + JSON.stringify(txResult));
console.log("Transaction instant: " + txResult.txInstant); console.log("Transaction instant: " + txResult.txInstant);
let results = await datomish.q(db.db(), "[:find ?url :in $ :where [?e :page/url ?url]]")
// A simple query.
let results = await db.q("[:find ?url :in $ :where [?e :page/url ?url]]");
results = results.map(r => r[0]); results = results.map(r => r[0]);
console.log("Query results: " + JSON.stringify(results)); console.log("Query results: " + JSON.stringify(results));
// Let's extend our schema. In the real world this would typically happen
// across releases.
schema.attributes.push({"name": "page/visitedAt",
"type": "instant",
"cardinality": "many",
"doc": "A visit to the page."});
await db.ensureSchema(schema);
// Now we can make assertions with the new vocabulary about existing
// entities.
// Note that we simply let Datomish find which page we're talking about by
// URL -- the URL is a unique property -- so we just use a tempid again.
await db.transact([
{"db/id": datomish.tempid(),
"page/url": "https://mozilla.org/",
"page/visitedAt": (new Date())}
]);
// When did we most recently visit this page?
let date = (await db.q(
`[:find (max ?date)
:in $ ?url
:where
[?page :page/url ?url]
[?page :page/visitedAt ?date]]`,
{"inputs": {"url": "https://mozilla.org/"}}))[0][0];
console.log("Most recent visit: " + date);
// Close: we're done!
await db.close(); await db.close();
} }