Add a babelified test file, Webpack the add-on, and make the JS API work.

We concatenate a simple setTimeout monkeypatch onto the add-on itself.
This commit is contained in:
Richard Newman 2016-09-21 14:07:04 -07:00
parent 360f7622e8
commit 17d7eaec7b
16 changed files with 317 additions and 55 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
}

7
.gitignore vendored
View file

@ -28,5 +28,8 @@ pom.xml.asc
/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

6
addon/.babelrc Normal file
View file

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

2
addon/build.sh Executable file
View file

@ -0,0 +1,2 @@
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/

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

@ -0,0 +1,101 @@
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 txResult = await db.transact([{"db/id": 55,
"page/title": title,
"page/url": url
// "page/starred": true
}]);
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); //datomish.q(db.db(), "[:find ?url :in $ :where [?e :page/url ?url]]");
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();
}
/*
async function handleClick(state) {
console.log("Handling click: " + state);
let tab = tabs.activeTab;
console.log("Active tab: " + tab);
console.log("Active tab: " + tab.url);
console.log("Active tab: " + tab.title);
let db = await initDB("/tmp/datomish.db");
console.log("Opened DB: " + db);
await savePage(db, tab.url, tab.title, "Content goes here.");
console.log("Saved page.");
let urls = await findURLs(db);
console.log("URLs: " + JSON.stringify(urls));
let results = await findPagesMatching(db, "goes");
console.log("Pages: " + JSON.stringify(results));
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

@ -13,7 +13,26 @@
"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

@ -10,6 +10,7 @@
[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]
@ -18,14 +19,17 @@
[datomish.js-sqlite :as js-sqlite]
[datomish.transact :as transact]))
(defn- take-pair-as-promise! [ch]
(defn- take-pair-as-promise! [ch f]
;; Just like take-as-promise!, but aware that it's handling a pair channel.
;; Also converts values, if desired.
(promise
(fn [resolve reject]
(letfn [(split-pair [[v e]]
(if e
(reject e)
(resolve v)))]
(do
(println "Got error:" e)
(reject e))
(resolve (f v))))]
(cljs.core.async/take! ch split-pair)))))
;; Public API.
@ -35,32 +39,46 @@
(defn ^:export q [db find options]
(let [find (cljs.reader/read-string find)
options (js->clj options)]
opts (cljify options)]
(println "Running query " (pr-str find) (pr-str {:foo find}) (pr-str opts))
(take-pair-as-promise!
(db/<?q db find options))))
(go-pair
(let [res (<? (db/<?q db find opts))]
(println "Got results: " (pr-str res))
(clj->js res)))
identity)))
(defn ^:export ensure-schema [conn simple-schema]
(let [simple-schema (js->clj simple-schema)]
(println "simple-schema: " (pr-str 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
(simple-schema/simple-schema->schema simple-schema)))))
datoms)
clj->js)))
(defn js->tx-data [tx-data]
;; Objects to maps.
;; Arrays to arrays.
;; RHS strings… well, some of them will be richer types.
;; TODO
(println "Converting" (pr-str tx-data) "to" (pr-str (js->clj tx-data :keywordize-keys true)))
(println "Converting" (pr-str tx-data) "to" (pr-str (js->clj tx-data :keywordize-keys true)))
(js->clj tx-data))
(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.
(let [tx-data (js->tx-data tx-data)]
(take-pair-as-promise!
(transact/<transact! conn tx-data))))
(try
(let [tx-data (js->tx-data tx-data)]
(println "Transacting:" (pr-str tx-data))
(take-pair-as-promise!
(go-pair
(let [tx-result (<? (transact/<transact! conn tx-data))]
(select-keys tx-result
[:tempids
:added-idents
:added-attributes
:tx
:txInstant])))
clj->js))
(catch js/Error e
(println "Error in transact:" e))))
(defn ^:export open [path]
;; Eventually, URI. For now, just a plain path (no file://).
@ -69,11 +87,15 @@
(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
:ensureSchema (fn [simple-schema] (ensure-schema c simple-schema))
:transact (fn [tx-data] (transact c tx-data))
:q (fn [find options] (q (transact/db c) find options))
:close (fn [] (db/close-db db))
:toString (fn [] (str "#<DB " path ">"))
:path path}))))))
(clj->js
;; 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}))))
identity))

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("../../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));