Implement a JS interface for Datomish, demonstrating it with a small Firefox add-on. Fixes #53. r=nalexander

This commit is contained in:
Richard Newman 2016-09-22 16:42:18 -07:00
commit d6c074830f
35 changed files with 585 additions and 122 deletions

27
.babelrc Normal file
View file

@ -0,0 +1,27 @@
{
"env": {
"production": {
"presets": ["react", "react-optimize"]
},
"development": {
"presets": ["react"]
},
"test": {
"presets": ["react"]
}
},
"only": [
"test/js/**"
],
"plugins": [
"transform-es2015-destructuring",
"transform-es2015-parameters",
"transform-es2015-modules-commonjs",
"transform-async-to-generator",
"transform-object-rest-spread",
"transform-class-properties",
"transform-runtime"
],
"sourceMaps": "inline",
"retainLines": true
}

13
.gitignore vendored
View file

@ -22,11 +22,8 @@ pom.xml
pom.xml.asc
/.cljs_node_repl/
/.cljs_rhino_repl/
/release-browser
/release-browser/datomish.js
/release-browser/datomish.bare.js
/release-node
/release-node/datomish.js
/release-node/datomish.bare.js
/addon/datomish-test.xpi
/addon/datomish.js
/addon/built/index.js
/addon/node_modules/
/addon/release/datomish-test.xpi
/addon/release/datomish.js
/addon/release/index.js

View file

@ -88,6 +88,24 @@ brew install rlwrap
Run `lein cljsbuild auto advanced` to generate JavaScript into `target/`.
To build for a browser, into `release-browser`:
```
lein cljsbuild once release-browser
```
To build for node, into `release-node`:
```
lein cljsbuild once release-node
```
To package or install a JAR for node, modifying the source path appropriately (make sure you clean up swap or temp files in `src`!):
```
lein with-profile node jar
```
### Starting a ClojureScript REPL from the terminal
```

6
addon/.babelrc Normal file
View file

@ -0,0 +1,6 @@
{
"presets": ["es2015"],
"plugins": [
"transform-async-to-generator"
]
}

3
addon/CREDITS Normal file
View file

@ -0,0 +1,3 @@
Icon file is "Line Graph" by Cris Dobbins, from The Noun Project.
https://thenounproject.com/term/line-graph/145324/

3
addon/build.sh Executable file
View file

@ -0,0 +1,3 @@
cp ../target/release-browser/datomish.js release/
node_modules/.bin/webpack -p
cat src/wrapper.prefix built/index.js > release/index.js

View file

@ -1,15 +0,0 @@
var self = require("sdk/self");
console.log("Datomish Test");
console.log("This: " + this);
var datomish = require("datomish.js");
datomish.open("/tmp/foobar.db").then(function (db) {
console.log("Got " + db);
try {
db.close();
console.log("Closed.");
} catch (e) {
console.log("Couldn't close: " + e);
}
});

View file

@ -1,15 +1,23 @@
{
"title": "Datomish Test",
"name": "datomish-test",
"version": "0.0.1",
"description": "An example add-on that loads Datomish on top of Sqlite.jsm.",
"name": "datomish-example",
"version": "1.0.0",
"description": "A test add-on for Datomish and Firefox.",
"main": "index.js",
"author": "Richard Newman <rnewman@mozilla.com>",
"engines": {
"firefox": ">=48.0a1"
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "MPL-2.0",
"keywords": [
"jetpack"
]
"devDependencies": {
"babel": "^6.5.2",
"babel-cli": "^6.14.0",
"babel-core": "^6.14.0",
"babel-loader": "^6.2.5",
"babel-plugin-transform-async-to-generator": "^6.8.0",
"babel-preset-es2015": "^6.14.0",
"webpack": "^1.13.2"
},
"dependencies": {
"babel-polyfill": "^6.13.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,15 @@
{
"title": "Datomish Test",
"name": "datomish-test",
"version": "0.0.1",
"description": "An example add-on that loads Datomish on top of Sqlite.jsm.",
"main": "index.js",
"author": "Richard Newman <rnewman@mozilla.com>",
"engines": {
"firefox": ">=48.0a1"
},
"license": "MPL-2.0",
"keywords": [
"jetpack"
]
}

1
addon/release/run.sh Executable file
View file

@ -0,0 +1 @@
jpm run -b /Applications/FirefoxNightly.app/

92
addon/src/index.js Normal file
View file

@ -0,0 +1,92 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
var self = require("sdk/self");
var buttons = require('sdk/ui/button/action');
var tabs = require('sdk/tabs');
var datomish = require("datomish.js");
var schema = {
"name": "pages",
"attributes": [
{"name": "page/url",
"type": "string",
"cardinality": "one",
"unique": "identity",
"doc": "A page's URL."},
{"name": "page/title",
"type": "string",
"cardinality": "one",
"fulltext": true,
"doc": "A page's title."},
{"name": "page/content",
"type": "string",
"cardinality": "one", // Simple for now.
"fulltext": true,
"doc": "A snapshot of the page's content. Should be plain text."},
]
};
async function initDB(path) {
let db = await datomish.open(path);
await db.ensureSchema(schema);
return db;
}
async function findURLs(db) {
let query = `[:find ?page ?url ?title :in $ :where [?page :page/url ?url][(get-else $ ?page :page/title "") ?title]]`;
let options = new Object();
options["limit"] = 10;
return datomish.q(db.db(), query, options);
}
async function findPagesMatching(db, string) {
let query =
`[:find ?url ?title
:in $ ?str
:where
[(fulltext $ :any ?str) [[?page]]]
[?page :page/url ?url]
[(get-else $ ?page :page/title "") ?title]]`;
return datomish.q(db.db(), query, {"limit": 10, "inputs": {"str": string}});
}
async function savePage(db, url, title, content) {
let datom = {"db/id": 55, "page/url": url};
if (title) {
datom["page/title"] = title;
}
if (content) {
datom["page/content"] = content;
}
let txResult = await db.transact([datom]);
return txResult;
}
async function handleClick(state) {
let db = await datomish.open("/tmp/testing.db");
await db.ensureSchema(schema);
let txResult = await savePage(db, tabs.activeTab.url, tabs.activeTab.title, "Content goes here");
console.log("Transaction returned " + JSON.stringify(txResult));
console.log("Transaction instant: " + txResult.txInstant);
let results = await findURLs(db);
results = results.map(r => r[1]);
console.log("Query results: " + JSON.stringify(results));
let pages = await findPagesMatching(db, "goes");
console.log("Pages: " + JSON.stringify(pages));
await db.close();
}
var button = buttons.ActionButton({
id: "datomish-save",
label: "Save Page",
icon: "./datomish-48.png",
onClick: handleClick
});

4
addon/src/wrapper.prefix Normal file
View file

@ -0,0 +1,4 @@
// Monkeypatch.
var { setTimeout } = require("sdk/timers");
this.setTimeout = setTimeout;

21
addon/webpack.config.js Normal file
View file

@ -0,0 +1,21 @@
module.exports = {
entry: ['babel-polyfill', './src/index.js'],
output: {
filename: 'built/index.js'
},
target: 'webworker',
externals: {
'datomish.js': 'commonjs datomish.js',
'sdk/self': 'commonjs sdk/self',
'sdk/ui/button/action': 'commonjs sdk/ui/button/action',
'sdk/tabs': 'commonjs sdk/tabs'
},
module: {
loaders: [{
test: /\.js?$/,
exclude: /(node_modules)|(wrapper.prefix)/,
loader: 'babel'
}]
}
}

View file

@ -7,13 +7,32 @@
"version": "0.1.0-SNAPSHOT",
"description": "A persistent, embedded knowledge base inspired by Datomic and DataScript.",
"dependencies": {
"promise-sqlite": "1.2.1",
"promise-sqlite": "1.3.0",
"source-map-support": "ncalexan/node-source-map-support#fileUrls-plus",
"sqlite3": "mossop/node-sqlite3#v3.1.4.1",
"sqlite3": "3.1.4",
"thenify-all": "^1.6.0",
"ws": "1.1.1"
},
"scripts": {
"test": "babel-node test/js/tests.js"
},
"devDependencies": {
"babel-cli": "^6.14.0",
"babel-core": "6.14.0",
"babel-eslint": "6.1.2",
"babel-loader": "6.2.5",
"babel-plugin-transform-async-to-generator": "6.8.0",
"babel-plugin-transform-class-properties": "6.11.5",
"babel-plugin-transform-es2015-destructuring": "6.9.0",
"babel-plugin-transform-es2015-modules-commonjs": "6.11.5",
"babel-plugin-transform-es2015-parameters": "6.11.4",
"babel-plugin-transform-object-rest-spread": "6.8.0",
"babel-plugin-transform-runtime": "6.12.0",
"babel-polyfill": "6.13.0",
"babel-preset-react": "6.11.1",
"babel-preset-react-optimize": "1.0.1",
"babel-register": "6.14.0",
"babel-runtime": "6.11.6",
"tmp": "0.0.28"
},
"repository": {
@ -27,5 +46,7 @@
},
"homepage": "https://github.com/mozilla/datomish#readme",
"main": "./datomish.js",
"files": ["datomish.js"]
"files": [
"datomish.js"
]
}

View file

@ -1,4 +1,4 @@
(defproject datomish "0.1.0-SNAPSHOT"
(defproject datomish "0.1.1-SNAPSHOT"
:description "A persistent, embedded knowledge base inspired by Datomic and DataScript."
:url "https://github.com/mozilla/datomish"
:license {:name "Mozilla Public License Version 2.0"
@ -12,26 +12,35 @@
[jamesmacaulay/cljs-promises "0.1.0"]]
;; The browser will never require from the .JAR anyway.
:source-paths ["src/common" "src/node"]
:source-paths [
"src/common"
;; Can't be enabled by default: layers on top of cljsbuild!
;; Instead, add the :node profile:
;; lein with-profile node install
;"src/node"
]
:cljsbuild {:builds
{
:release-node
{
:source-paths ["src/node" "src/common"]
:source-paths ["src/common" "src/node"]
:assert false
:compiler
{
;; :externs specified in deps.cljs.
:elide-asserts true
:hashbang false
:language-in :ecmascript5
:language-out :ecmascript5
:optimizations :advanced
:output-dir "release-node"
:output-to "release-node/datomish.bare.js"
:output-dir "target/release-node"
:output-to "target/release-node/datomish.bare.js"
:output-wrapper false
:parallel-build true
:pretty-print false
:pretty-print true
:pseudo-names true
:static-fns true
:target :nodejs
}
:notify-command ["release-node/wrap_bare.sh"]}
@ -46,17 +55,17 @@
;; There's no point in generating a source map -- it'll be wrong
;; due to wrapping.
{
:source-paths ["src/browser" "src/common"]
:source-paths ["src/common" "src/browser"]
:assert false
:compiler
{
:elide-asserts true
:externs ["src/browser/externs.js"]
:externs ["src/browser/externs/datomish.js"]
:language-in :ecmascript5
:language-out :ecmascript5
:optimizations :advanced
:output-dir "release-browser"
:output-to "release-browser/datomish.bare.js"
:output-dir "target/release-browser"
:output-to "target/release-browser/datomish.bare.js"
:output-wrapper false
:parallel-build true
:preloads [datomish.preload]
@ -66,24 +75,9 @@
}
:notify-command ["release-browser/wrap_bare.sh"]}
:advanced
{:source-paths ["src/node" "src/common"]
:compiler
{
:language-in :ecmascript5
:language-out :ecmascript5
:output-dir "target/advanced"
:output-to "target/advanced/datomish.js"
:optimizations :advanced
:parallel-build true
:pretty-print true
:source-map "target/advanced/datomish.js.map"
:target :nodejs
}}
:test
{
:source-paths ["src/node" "src/common" "test"]
:source-paths ["src/common" "src/node" "test"]
:compiler
{
:language-in :ecmascript5
@ -98,7 +92,8 @@
}}
}}
:profiles {:dev {:dependencies [[cljsbuild "1.1.3"]
:profiles {:node {:source-paths ["src/common" "src/node"]}
:dev {:dependencies [[cljsbuild "1.1.3"]
[tempfile "0.2.0"]
[com.cemerick/piggieback "0.2.1"]
[org.clojure/tools.nrepl "0.2.10"]
@ -114,26 +109,5 @@
:doo {:build "test"}
:clean-targets ^{:protect false}
[
"target"
"release-node/cljs/"
"release-node/cljs_promises/"
"release-node/clojure/"
"release-node/datascript/"
"release-node/datomish/"
"release-node/honeysql/"
"release-node/taoensso/"
"release-node/datomish.bare.js"
"release-node/datomish.js"
"release-browser/cljs/"
"release-browser/cljs_promises/"
"release-browser/clojure/"
"release-browser/datascript/"
"release-browser/datomish/"
"release-browser/honeysql/"
"release-browser/taoensso/"
"release-browser/datomish.bare.js"
"release-browser/datomish.js"
]
:clean-targets ^{:protect false} ["target"]
)

View file

@ -1,2 +0,0 @@
var d = require('./datomish');
console.log(d.q("[:find ?e ?v :where [?e \"name\" ?v] {:x :y}]"));

View file

@ -2,6 +2,6 @@
set -e
(cat release-browser/wrapper.prefix; cat release-browser/datomish.bare.js; cat release-browser/wrapper.suffix) > release-browser/datomish.js
(cat release-browser/wrapper.prefix; cat target/release-browser/datomish.bare.js; cat release-browser/wrapper.suffix) > target/release-browser/datomish.js
echo "Packed release-browser/datomish.js"
echo "Packed target/release-browser/datomish.js"

View file

@ -1,2 +1,2 @@
var d = require('./datomish');
var d = require('../target/release-node/datomish');
console.log(d.q("[:find ?e ?v :where [?e \"name\" ?v] {:x :y}]"));

View file

@ -2,6 +2,6 @@
set -e
(cat release-node/wrapper.prefix && cat release-node/datomish.bare.js && cat release-node/wrapper.suffix) > release-node/datomish.js
(cat release-node/wrapper.prefix && cat target/release-node/datomish.bare.js && cat release-node/wrapper.suffix) > target/release-node/datomish.js
echo "Packed release-node/datomish.js"
echo "Packed target/release-node/datomish.js"

View file

@ -0,0 +1,47 @@
(ns datomish.cljify)
(defn cljify
"Does what `(js->clj o :keywordize-keys true) is supposed to do, but works
in environments with more than one context (e.g., web browsers).
See <http://dev.clojure.org/jira/browse/CLJS-439?focusedCommentId=43909>.
Note that Date instances are passed through."
[o]
(cond
(nil? o)
nil
;; Primitives.
(or
(true? o)
(false? o)
(number? o)
(string? o)
;; Dates are passed through.
(not (nil? (aget (aget o "__proto__") "getUTCMilliseconds"))))
o
;; Array.
(.isArray js/Array o)
(let [n (.-length o)]
(loop [i 0
acc (transient [])]
(if (< i n)
(recur (inc i) (conj! acc (cljify (aget o i))))
(persistent! acc))))
;; Object.
(not (nil? (aget (aget o "__proto__") "hasOwnProperty")))
(let [a (.keys js/Object o)
n (.-length a)]
(loop [i 0
acc (transient {})]
(if (< i n)
(let [key (aget a i)]
(recur (inc i) (assoc! acc
(keyword key)
(cljify (aget o key)))))
(persistent! acc))))
:else o))

View file

@ -1,3 +1,11 @@
var Object = {};
Object.keys = function (object) {};
Object.__proto__ = {};
Object.hasOwnProperty = function () {};
var Array = {};
Array.length = 0;
Array.isArray = function () {};
var SqliteStatic = {};
/**

View file

@ -713,6 +713,10 @@
Returns a transduced channel of [result err] pairs.
Closes the channel when fully consumed."
[db find options]
(let [unexpected (seq (clojure.set/difference (set (keys options)) #{:limit :order-by :inputs}))]
(when unexpected
(raise "Unexpected options: " unexpected {:bad-options unexpected})))
(let [{:keys [limit order-by inputs]} options
parsed (query/parse find)
context (-> db

View file

@ -5,43 +5,78 @@
(ns datomish.js
(:refer-clojure :exclude [])
(:require-macros
[datomish.pair-chan :refer [go-pair <?]])
[datomish.pair-chan :refer [go-pair <?]]
[datomish.promises :refer [go-promise]])
(:require
[cljs.core.async :as a :refer [take! <! >!]]
[cljs.reader]
[cljs-promises.core :refer [promise]]
[datomish.cljify :refer [cljify]]
[datomish.db :as db]
[datomish.db-factory :as db-factory]
[datomish.pair-chan]
[datomish.promises :refer [take-pair-as-promise!]]
[datomish.sqlite :as sqlite]
[datomish.simple-schema :as simple-schema]
[datomish.js-sqlite :as js-sqlite]
[datomish.transact :as transact]))
(defn- take-pair-as-promise! [ch]
;; Just like take-as-promise!, but aware that it's handling a pair channel.
(promise
(fn [resolve reject]
(letfn [(split-pair [[v e]]
(if e
(reject e)
(resolve v)))]
(cljs.core.async/take! ch split-pair)))))
;; Public API.
(defn ^:export db [conn]
(transact/db conn))
(defn ^:export q [db find options]
(let [find (cljs.reader/read-string find)
opts (cljify options)]
(take-pair-as-promise!
(db/<?q db find opts)
clj->js)))
(defn ^:export ensure-schema [conn simple-schema]
(let [simple-schema (cljify simple-schema)
datoms (simple-schema/simple-schema->schema simple-schema)]
(println "Transacting schema datoms" (pr-str datoms))
(take-pair-as-promise!
(transact/<transact!
conn
datoms)
clj->js)))
(def js->tx-data cljify)
(def ^:export tempid (partial db/id-literal :db.part/user))
(defn ^:export transact [conn tx-data]
;; 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 (<? (transact/<transact! conn tx-data))]
(select-keys tx-result
[:tempids
:added-idents
:added-attributes
:tx
:txInstant]))))
(catch js/Error e
(println "Error in transact:" e))))
(defn ^:export open [path]
;; Eventually, URI. For now, just a plain path (no file://).
(take-pair-as-promise!
(go-pair
(let [conn (<? (sqlite/<sqlite-connection path))
db (<? (db-factory/<db-with-sqlite-connection conn))]
(let [c (transact/connection-with-db db)]
(clj->js
{:conn c
:close (fn [] (db/close-db db))
:toString (fn [] (str "#<DB " path ">"))
:path path}))))))
(defn ^:export q [query & sources]
(let [query (cljs.reader/read-string query)]
(clj->js query)))
(go-promise clj->js
(let [conn (<? (sqlite/<sqlite-connection path))
db (<? (db-factory/<db-with-sqlite-connection conn))]
(let [c (transact/connection-with-db db)]
;; 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))
:ensureSchema (fn [simple-schema] (ensure-schema c simple-schema))
:transact (fn [tx-data] (transact c tx-data))
:close (fn [] (db/close-db db))
:toString (fn [] (str "#<DB " path ">"))
:path path}))))

View file

@ -0,0 +1,30 @@
(ns datomish.promises
#?(:cljs
(:require-macros
[datomish.pair-chan :refer [go-pair <?]]))
(:require
#?@(:clj [[datomish.pair-chan :refer [go-pair]]
[clojure.core.async :as a :refer [take!]]])
#?@(:cljs [[cljs-promises.core :refer [promise]]
[cljs.core.async :as a :refer [take!]]])))
(defn take-pair-as-promise!
"Just like take-as-promise!, but aware that it's handling a pair channel.
Also converts values, if desired."
([ch]
(take-pair-as-promise! ch identity))
([ch f]
(promise
(fn [resolve reject]
(take!
ch
(fn [[v e]]
(if e
(reject e)
(resolve (f v)))))))))
(defmacro go-promise [f & body]
`(datomish.promises/take-pair-as-promise!
(datomish.pair-chan/go-pair
~@body)
~f))

View file

@ -208,3 +208,4 @@
{:pre [(or (nil? schema) (map? schema))]}
(map->Schema {:schema (validate-schema schema)
:rschema (rschema schema)}))

View file

@ -0,0 +1,66 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
(ns datomish.simple-schema
#?(:cljs
(:require-macros
[datomish.pair-chan :refer [go-pair <?]]
[cljs.core.async.macros :refer [go]]))
(:require
[clojure.set]
[datomish.util :as util
#?(:cljs :refer-macros :clj :refer) [raise raise-str cond-let]]
[datomish.db :as db]
[datomish.schema :as ds]
[datomish.sqlite :as s]
[datomish.sqlite-schema :as sqlite-schema]
#?@(:clj [[datomish.pair-chan :refer [go-pair <?]]
[clojure.core.async :as a :refer [chan go <! >!]]])
#?@(:cljs [[datomish.pair-chan]
[cljs.core.async :as a :refer [chan <! >!]]])))
(defn- name->ident [name]
(when-not (and (string? name)
(not (empty? name)))
(raise "Invalid name " name {:error :invalid-name :name name}))
(keyword name))
(defn simple-schema-attributes->schema-parts [attrs]
(let [{:keys [cardinality type name unique doc fulltext]} attrs
value-type (when type (keyword (str "db.type/" type)))]
(when-not (and value-type
(contains? ds/value-type-map value-type))
(raise "Invalid type " type {:error :invalid-type :type type}))
(let [unique
(case unique
"identity" :db.unique/identity
"value" :db.unique/value
nil nil
(raise "Invalid unique " unique
{:error :invalid-unique :unique unique}))
cardinality
(case cardinality
"one" :db.cardinality/one
"many" :db.cardinality/many
nil nil
(raise "Invalid cardinality " cardinality
{:error :invalid-cardinality :cardinality cardinality}))]
(util/assoc-if
{:db/valueType value-type
:db/ident (name->ident name)
:db/id (db/id-literal :db.part/user)
:db.install/_attribute :db.part/db}
:db/doc doc
:db/unique unique
:db/fulltext fulltext
:db/cardinality cardinality))))
(defn simple-schema->schema [simple-schema]
(let [{:keys [name attributes]} simple-schema]
(map simple-schema-attributes->schema-parts attributes)))

View file

@ -0,0 +1,7 @@
(ns datomish.cljify)
(defn cljify
"In node, equivalent to `(js->clj o :keywordize-keys true).
See <http://dev.clojure.org/jira/browse/CLJS-439?focusedCommentId=43909>."
[o]
(js->clj o :keywordize-keys true))

View file

@ -5,9 +5,6 @@
(ns datomish.core
(:require [cljs.nodejs :as nodejs]))
(nodejs/enable-util-print!)
(defn -main [& args]
(println "Hello world!"))
(defn -main [& args])
(set! *main-cli-fn* -main)
(nodejs/enable-util-print!)

View file

@ -5,6 +5,7 @@
(ns datomish.promise-sqlite
(:require
[datomish.sqlite :as s]
[datomish.cljify :refer [cljify]]
[cljs-promises.async]
[cljs.nodejs :as nodejs]))
@ -20,7 +21,7 @@
(-each
[db sql bindings row-cb]
(let [cb (fn [row]
(row-cb (js->clj row :keywordize-keys true)))]
(row-cb (cljify row)))]
(cljs-promises.async/pair-port
(.each (.-db db) sql (or (clj->js bindings) #js []) (when row-cb cb)))))

1
src/node/deps.cljs Normal file
View file

@ -0,0 +1 @@
{:externs ["externs/datomish.js"]}

View file

@ -0,0 +1,13 @@
var sqlite = {};
sqlite.DB = {};
/**
* @return {Promise}
*/
sqlite.DB.open = function (path, options) {};
var DBVal = {};
DBVal.run = function (sql, bindings) {};
DBVal.close = function () {};
DBVal.each = function (sql, bindings, cb) {};

View file

@ -13,6 +13,7 @@
[datomish.db.debug :refer [<datoms-after <transactions-after <shallow-entity <fulltext-values]]
[datomish.util :as util #?(:cljs :refer-macros :clj :refer) [raise cond-let]]
[datomish.schema :as ds]
[datomish.simple-schema]
[datomish.sqlite :as s]
[datomish.sqlite-schema]
[datomish.datom]
@ -816,4 +817,37 @@
(finally
(<? (d/<close conn))))))))
(deftest-db test-simple-schema conn
(let [in {:name "mystuff"
:attributes [{:name "foo/age"
:type "long"
:cardinality "one"}
{:name "foo/name"
:type "string"
:cardinality "many"
:doc "People can have many names."}
{:name "foo/id"
:type "string"
:cardinality "one"
:unique "value"}]}
expected [{:db/ident :foo/age
:db/valueType :db.type/long
:db/cardinality :db.cardinality/one
:db.install/_attribute :db.part/db}
{:db/ident :foo/name
:db/valueType :db.type/string
:db/cardinality :db.cardinality/many
:db/doc "People can have many names."
:db.install/_attribute :db.part/db}
{:db/ident :foo/id
:db/valueType :db.type/string
:db/cardinality :db.cardinality/one
:db/unique :db.unique/value
:db.install/_attribute :db.part/db}]]
(testing "Simple schemas are expanded."
(is (= (map #(dissoc %1 :db/id) (datomish.simple-schema/simple-schema->schema in))
expected)))))
#_ (time (t/run-tests))

46
test/js/tests.js Normal file
View file

@ -0,0 +1,46 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
var datomish = require("../../target/release-node/datomish.js");
var schema = {
"name": "pages",
"attributes": [
{"name": "page/url",
"type": "string",
"cardinality": "one",
"unique": "identity",
"doc": "A page's URL."},
{"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."}
]
};
async function testOpen() {
let db = await datomish.open("/tmp/testing.db");
await db.ensureSchema(schema);
let txResult = await db.transact([{"db/id": 55,
"page/url": "http://foo.com/bar",
"page/starred": true}]);
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]]")
results = results.map(r => r[0]);
console.log("Query results: " + JSON.stringify(results));
await db.close();
}
testOpen()
.then((r) => console.log("Done."))
.catch((e) => console.log("Failure: " + e.stack));