diff --git a/src/common/datomish/js.cljs b/src/common/datomish/js.cljs index 0c7ab46b..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 (js (cljify x))) - :db (fn [] (transact/db c)) + :db (fn [] (d/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 (d/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})))) + })))) diff --git a/src/node/datomish/cljify.cljs b/src/node/datomish/cljify.cljs index 3ef64a3f..3feaa3b1 100644 --- a/src/node/datomish/cljify.cljs +++ b/src/node/datomish/cljify.cljs @@ -1,7 +1,30 @@ (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] + ;; 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))) 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(); }