Improve ClojureScript->JavaScript roundtripping, and flesh out example Node test.
This commit is contained in:
commit
f930d1312a
3 changed files with 92 additions and 26 deletions
|
@ -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}))))
|
}))))
|
||||||
|
|
|
@ -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)))
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue