From 0b6ac81ed5fcb37d92b743e26de6622a9cdfe55d Mon Sep 17 00:00:00 2001 From: Richard Newman Date: Wed, 5 Oct 2016 12:53:57 -0700 Subject: [PATCH 1/5] Part 1: extend 'db' JS object with more useful methods. --- src/common/datomish/js.cljs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/common/datomish/js.cljs b/src/common/datomish/js.cljs index 0c7ab46b..2cc72d68 100644 --- a/src/common/datomish/js.cljs +++ b/src/common/datomish/js.cljs @@ -73,10 +73,20 @@ ;; We pickle the connection as a thunk here so it roundtrips through JS ;; without incident. {:conn (fn [] c) - :roundtrip (fn [x] (clj->js (cljify x))) :db (fn [] (transact/db c)) + :path path + + ;; Primary API. :ensureSchema (fn [simple-schema] (ensure-schema c simple-schema)) :transact (fn [tx-data] (transact c tx-data)) + :q (fn [find opts] (q (transact/db c) find opts)) :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 "#")) - :path path})))) + })))) From c7d0a8596b124cb409a7abae71bc7b7fe2f6ebcc Mon Sep 17 00:00:00 2001 From: Richard Newman Date: Wed, 5 Oct 2016 12:54:26 -0700 Subject: [PATCH 2/5] Part 2: extend 'cljify' implementation to round-trip records like TempId. --- src/node/datomish/cljify.cljs | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/node/datomish/cljify.cljs b/src/node/datomish/cljify.cljs index 3ef64a3f..fe8691df 100644 --- a/src/node/datomish/cljify.cljs +++ b/src/node/datomish/cljify.cljs @@ -1,7 +1,27 @@ (ns datomish.cljify) (defn cljify - "In node, equivalent to `(js->clj o :keywordize-keys true). - See ." - [o] - (js->clj o :keywordize-keys true)) + "In node, equivalent to `(js->clj o :keywordize-keys true), + but successfully passes Clojure Records through. This allows JS API + callers to round-trip values they receive from ClojureScript APIs." + [x] + (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))) From ce67644fd5a62af0efbff5a82558c67a3f12ffcd Mon Sep 17 00:00:00 2001 From: Richard Newman Date: Wed, 5 Oct 2016 12:54:48 -0700 Subject: [PATCH 3/5] Part 3: expand example Node code. --- test/js/tests.js | 60 +++++++++++++++++++++++++++++++++++++----------- 1 file changed, 47 insertions(+), 13 deletions(-) diff --git a/test/js/tests.js b/test/js/tests.js index e69b55c5..c9dc4c2a 100644 --- a/test/js/tests.js +++ b/test/js/tests.js @@ -15,29 +15,63 @@ var schema = { {"name": "page/title", "type": "string", "cardinality": "one", - "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."} + "doc": "A page's title."} ] }; async function testOpen() { + // Open a database. let db = await datomish.open("/tmp/testing.db"); + + // Make sure we have our current schema. await db.ensureSchema(schema); - let txResult = await db.transact([{"db/id": 55, - "page/url": "http://foo.com/bar", - "page/starred": true}]); + + // Add some data. Note that we use a temporary ID (the real ID + // 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 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]); + 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(); } From 61757e271c1832e2421f91d6e84b32c24a7b4581 Mon Sep 17 00:00:00 2001 From: Richard Newman Date: Wed, 5 Oct 2016 14:06:36 -0700 Subject: [PATCH 4/5] Review comment: use datomish.api where possible. --- src/common/datomish/js.cljs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/common/datomish/js.cljs b/src/common/datomish/js.cljs index 2cc72d68..f649c6d9 100644 --- a/src/common/datomish/js.cljs +++ b/src/common/datomish/js.cljs @@ -12,6 +12,7 @@ [cljs.reader] [cljs-promises.core :refer [promise]] [datomish.cljify :refer [cljify]] + [datomish.api :as d] [datomish.db :as db] [datomish.db-factory :as db-factory] [datomish.pair-chan] @@ -24,14 +25,13 @@ ;; Public API. -(defn ^:export db [conn] - (transact/db conn)) +(def ^:export db d/db) (defn ^:export q [db find options] (let [find (cljs.reader/read-string find) opts (cljify options)] (take-pair-as-promise! - (db/js))) (defn ^:export ensure-schema [conn simple-schema] @@ -39,7 +39,7 @@ datoms (simple-schema/simple-schema->schema simple-schema)] (println "Transacting schema datoms" (pr-str datoms)) (take-pair-as-promise! - (transact/js))) @@ -52,9 +52,8 @@ ;; Expects a JS array as input. (try (let [tx-data (js->tx-data tx-data)] - (println "Transacting:" (pr-str tx-data)) (go-promise clj->js - (let [tx-result ( Date: Wed, 5 Oct 2016 14:07:07 -0700 Subject: [PATCH 5/5] Review comment: add comment about cljify. --- src/node/datomish/cljify.cljs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/node/datomish/cljify.cljs b/src/node/datomish/cljify.cljs index fe8691df..3feaa3b1 100644 --- a/src/node/datomish/cljify.cljs +++ b/src/node/datomish/cljify.cljs @@ -5,6 +5,9 @@ but successfully passes Clojure Records through. This allows JS API callers to round-trip values they receive from ClojureScript APIs." [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