Implement a JS interface for Datomish, demonstrating it with a small Firefox add-on. Fixes #53. r=nalexander
This commit is contained in:
commit
d6c074830f
35 changed files with 585 additions and 122 deletions
27
.babelrc
Normal file
27
.babelrc
Normal 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
13
.gitignore
vendored
|
@ -22,11 +22,8 @@ pom.xml
|
||||||
pom.xml.asc
|
pom.xml.asc
|
||||||
/.cljs_node_repl/
|
/.cljs_node_repl/
|
||||||
/.cljs_rhino_repl/
|
/.cljs_rhino_repl/
|
||||||
/release-browser
|
/addon/built/index.js
|
||||||
/release-browser/datomish.js
|
/addon/node_modules/
|
||||||
/release-browser/datomish.bare.js
|
/addon/release/datomish-test.xpi
|
||||||
/release-node
|
/addon/release/datomish.js
|
||||||
/release-node/datomish.js
|
/addon/release/index.js
|
||||||
/release-node/datomish.bare.js
|
|
||||||
/addon/datomish-test.xpi
|
|
||||||
/addon/datomish.js
|
|
||||||
|
|
18
README.md
18
README.md
|
@ -88,6 +88,24 @@ brew install rlwrap
|
||||||
|
|
||||||
Run `lein cljsbuild auto advanced` to generate JavaScript into `target/`.
|
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
|
### Starting a ClojureScript REPL from the terminal
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
6
addon/.babelrc
Normal file
6
addon/.babelrc
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"presets": ["es2015"],
|
||||||
|
"plugins": [
|
||||||
|
"transform-async-to-generator"
|
||||||
|
]
|
||||||
|
}
|
3
addon/CREDITS
Normal file
3
addon/CREDITS
Normal 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
3
addon/build.sh
Executable 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
|
|
@ -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);
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,15 +1,23 @@
|
||||||
{
|
{
|
||||||
"title": "Datomish Test",
|
"name": "datomish-example",
|
||||||
"name": "datomish-test",
|
"version": "1.0.0",
|
||||||
"version": "0.0.1",
|
"description": "A test add-on for Datomish and Firefox.",
|
||||||
"description": "An example add-on that loads Datomish on top of Sqlite.jsm.",
|
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"author": "Richard Newman <rnewman@mozilla.com>",
|
"scripts": {
|
||||||
"engines": {
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
"firefox": ">=48.0a1"
|
|
||||||
},
|
},
|
||||||
|
"author": "",
|
||||||
"license": "MPL-2.0",
|
"license": "MPL-2.0",
|
||||||
"keywords": [
|
"devDependencies": {
|
||||||
"jetpack"
|
"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"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
BIN
addon/release/data/datomish-48.png
Normal file
BIN
addon/release/data/datomish-48.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
15
addon/release/package.json
Normal file
15
addon/release/package.json
Normal 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
1
addon/release/run.sh
Executable file
|
@ -0,0 +1 @@
|
||||||
|
jpm run -b /Applications/FirefoxNightly.app/
|
92
addon/src/index.js
Normal file
92
addon/src/index.js
Normal 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
4
addon/src/wrapper.prefix
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
// Monkeypatch.
|
||||||
|
var { setTimeout } = require("sdk/timers");
|
||||||
|
this.setTimeout = setTimeout;
|
||||||
|
|
21
addon/webpack.config.js
Normal file
21
addon/webpack.config.js
Normal 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'
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
27
package.json
27
package.json
|
@ -7,13 +7,32 @@
|
||||||
"version": "0.1.0-SNAPSHOT",
|
"version": "0.1.0-SNAPSHOT",
|
||||||
"description": "A persistent, embedded knowledge base inspired by Datomic and DataScript.",
|
"description": "A persistent, embedded knowledge base inspired by Datomic and DataScript.",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"promise-sqlite": "1.2.1",
|
"promise-sqlite": "1.3.0",
|
||||||
"source-map-support": "ncalexan/node-source-map-support#fileUrls-plus",
|
"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",
|
"thenify-all": "^1.6.0",
|
||||||
"ws": "1.1.1"
|
"ws": "1.1.1"
|
||||||
},
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "babel-node test/js/tests.js"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"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"
|
"tmp": "0.0.28"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@ -27,5 +46,7 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/mozilla/datomish#readme",
|
"homepage": "https://github.com/mozilla/datomish#readme",
|
||||||
"main": "./datomish.js",
|
"main": "./datomish.js",
|
||||||
"files": ["datomish.js"]
|
"files": [
|
||||||
|
"datomish.js"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
72
project.clj
72
project.clj
|
@ -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."
|
:description "A persistent, embedded knowledge base inspired by Datomic and DataScript."
|
||||||
:url "https://github.com/mozilla/datomish"
|
:url "https://github.com/mozilla/datomish"
|
||||||
:license {:name "Mozilla Public License Version 2.0"
|
:license {:name "Mozilla Public License Version 2.0"
|
||||||
|
@ -12,26 +12,35 @@
|
||||||
[jamesmacaulay/cljs-promises "0.1.0"]]
|
[jamesmacaulay/cljs-promises "0.1.0"]]
|
||||||
|
|
||||||
;; The browser will never require from the .JAR anyway.
|
;; 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
|
:cljsbuild {:builds
|
||||||
{
|
{
|
||||||
:release-node
|
:release-node
|
||||||
{
|
{
|
||||||
:source-paths ["src/node" "src/common"]
|
:source-paths ["src/common" "src/node"]
|
||||||
:assert false
|
:assert false
|
||||||
:compiler
|
:compiler
|
||||||
{
|
{
|
||||||
|
;; :externs specified in deps.cljs.
|
||||||
:elide-asserts true
|
:elide-asserts true
|
||||||
:hashbang false
|
:hashbang false
|
||||||
:language-in :ecmascript5
|
:language-in :ecmascript5
|
||||||
:language-out :ecmascript5
|
:language-out :ecmascript5
|
||||||
:optimizations :advanced
|
:optimizations :advanced
|
||||||
:output-dir "release-node"
|
:output-dir "target/release-node"
|
||||||
:output-to "release-node/datomish.bare.js"
|
:output-to "target/release-node/datomish.bare.js"
|
||||||
:output-wrapper false
|
:output-wrapper false
|
||||||
:parallel-build true
|
:parallel-build true
|
||||||
:pretty-print false
|
:pretty-print true
|
||||||
|
:pseudo-names true
|
||||||
|
:static-fns true
|
||||||
:target :nodejs
|
:target :nodejs
|
||||||
}
|
}
|
||||||
:notify-command ["release-node/wrap_bare.sh"]}
|
:notify-command ["release-node/wrap_bare.sh"]}
|
||||||
|
@ -46,17 +55,17 @@
|
||||||
;; There's no point in generating a source map -- it'll be wrong
|
;; There's no point in generating a source map -- it'll be wrong
|
||||||
;; due to wrapping.
|
;; due to wrapping.
|
||||||
{
|
{
|
||||||
:source-paths ["src/browser" "src/common"]
|
:source-paths ["src/common" "src/browser"]
|
||||||
:assert false
|
:assert false
|
||||||
:compiler
|
:compiler
|
||||||
{
|
{
|
||||||
:elide-asserts true
|
:elide-asserts true
|
||||||
:externs ["src/browser/externs.js"]
|
:externs ["src/browser/externs/datomish.js"]
|
||||||
:language-in :ecmascript5
|
:language-in :ecmascript5
|
||||||
:language-out :ecmascript5
|
:language-out :ecmascript5
|
||||||
:optimizations :advanced
|
:optimizations :advanced
|
||||||
:output-dir "release-browser"
|
:output-dir "target/release-browser"
|
||||||
:output-to "release-browser/datomish.bare.js"
|
:output-to "target/release-browser/datomish.bare.js"
|
||||||
:output-wrapper false
|
:output-wrapper false
|
||||||
:parallel-build true
|
:parallel-build true
|
||||||
:preloads [datomish.preload]
|
:preloads [datomish.preload]
|
||||||
|
@ -66,24 +75,9 @@
|
||||||
}
|
}
|
||||||
:notify-command ["release-browser/wrap_bare.sh"]}
|
: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
|
:test
|
||||||
{
|
{
|
||||||
:source-paths ["src/node" "src/common" "test"]
|
:source-paths ["src/common" "src/node" "test"]
|
||||||
:compiler
|
:compiler
|
||||||
{
|
{
|
||||||
:language-in :ecmascript5
|
: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"]
|
[tempfile "0.2.0"]
|
||||||
[com.cemerick/piggieback "0.2.1"]
|
[com.cemerick/piggieback "0.2.1"]
|
||||||
[org.clojure/tools.nrepl "0.2.10"]
|
[org.clojure/tools.nrepl "0.2.10"]
|
||||||
|
@ -114,26 +109,5 @@
|
||||||
|
|
||||||
:doo {:build "test"}
|
:doo {:build "test"}
|
||||||
|
|
||||||
:clean-targets ^{:protect false}
|
:clean-targets ^{:protect false} ["target"]
|
||||||
[
|
|
||||||
"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"
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,2 +0,0 @@
|
||||||
var d = require('./datomish');
|
|
||||||
console.log(d.q("[:find ?e ?v :where [?e \"name\" ?v] {:x :y}]"));
|
|
|
@ -2,6 +2,6 @@
|
||||||
|
|
||||||
set -e
|
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"
|
||||||
|
|
|
@ -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}]"));
|
console.log(d.q("[:find ?e ?v :where [?e \"name\" ?v] {:x :y}]"));
|
||||||
|
|
|
@ -2,6 +2,6 @@
|
||||||
|
|
||||||
set -e
|
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"
|
||||||
|
|
47
src/browser/datomish/cljify.cljs
Normal file
47
src/browser/datomish/cljify.cljs
Normal 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))
|
|
@ -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 = {};
|
var SqliteStatic = {};
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -713,6 +713,10 @@
|
||||||
Returns a transduced channel of [result err] pairs.
|
Returns a transduced channel of [result err] pairs.
|
||||||
Closes the channel when fully consumed."
|
Closes the channel when fully consumed."
|
||||||
[db find options]
|
[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
|
(let [{:keys [limit order-by inputs]} options
|
||||||
parsed (query/parse find)
|
parsed (query/parse find)
|
||||||
context (-> db
|
context (-> db
|
||||||
|
|
|
@ -5,43 +5,78 @@
|
||||||
(ns datomish.js
|
(ns datomish.js
|
||||||
(:refer-clojure :exclude [])
|
(:refer-clojure :exclude [])
|
||||||
(:require-macros
|
(:require-macros
|
||||||
[datomish.pair-chan :refer [go-pair <?]])
|
[datomish.pair-chan :refer [go-pair <?]]
|
||||||
|
[datomish.promises :refer [go-promise]])
|
||||||
(:require
|
(:require
|
||||||
[cljs.core.async :as a :refer [take! <! >!]]
|
[cljs.core.async :as a :refer [take! <! >!]]
|
||||||
[cljs.reader]
|
[cljs.reader]
|
||||||
[cljs-promises.core :refer [promise]]
|
[cljs-promises.core :refer [promise]]
|
||||||
|
[datomish.cljify :refer [cljify]]
|
||||||
[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]
|
||||||
|
[datomish.promises :refer [take-pair-as-promise!]]
|
||||||
[datomish.sqlite :as sqlite]
|
[datomish.sqlite :as sqlite]
|
||||||
|
[datomish.simple-schema :as simple-schema]
|
||||||
[datomish.js-sqlite :as js-sqlite]
|
[datomish.js-sqlite :as js-sqlite]
|
||||||
[datomish.transact :as transact]))
|
[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.
|
;; 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]
|
(defn ^:export open [path]
|
||||||
;; Eventually, URI. For now, just a plain path (no file://).
|
;; Eventually, URI. For now, just a plain path (no file://).
|
||||||
(take-pair-as-promise!
|
(go-promise clj->js
|
||||||
(go-pair
|
(let [conn (<? (sqlite/<sqlite-connection path))
|
||||||
(let [conn (<? (sqlite/<sqlite-connection path))
|
db (<? (db-factory/<db-with-sqlite-connection conn))]
|
||||||
db (<? (db-factory/<db-with-sqlite-connection conn))]
|
(let [c (transact/connection-with-db db)]
|
||||||
(let [c (transact/connection-with-db db)]
|
;; We pickle the connection as a thunk here so it roundtrips through JS
|
||||||
(clj->js
|
;; without incident.
|
||||||
{:conn c
|
{:conn (fn [] c)
|
||||||
:close (fn [] (db/close-db db))
|
:roundtrip (fn [x] (clj->js (cljify x)))
|
||||||
:toString (fn [] (str "#<DB " path ">"))
|
:db (fn [] (transact/db c))
|
||||||
:path path}))))))
|
:ensureSchema (fn [simple-schema] (ensure-schema c simple-schema))
|
||||||
|
:transact (fn [tx-data] (transact c tx-data))
|
||||||
(defn ^:export q [query & sources]
|
:close (fn [] (db/close-db db))
|
||||||
(let [query (cljs.reader/read-string query)]
|
:toString (fn [] (str "#<DB " path ">"))
|
||||||
(clj->js query)))
|
:path path}))))
|
||||||
|
|
30
src/common/datomish/promises.cljc
Normal file
30
src/common/datomish/promises.cljc
Normal 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))
|
|
@ -208,3 +208,4 @@
|
||||||
{:pre [(or (nil? schema) (map? schema))]}
|
{:pre [(or (nil? schema) (map? schema))]}
|
||||||
(map->Schema {:schema (validate-schema schema)
|
(map->Schema {:schema (validate-schema schema)
|
||||||
:rschema (rschema schema)}))
|
:rschema (rschema schema)}))
|
||||||
|
|
||||||
|
|
66
src/common/datomish/simple_schema.cljc
Normal file
66
src/common/datomish/simple_schema.cljc
Normal 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)))
|
||||||
|
|
7
src/node/datomish/cljify.cljs
Normal file
7
src/node/datomish/cljify.cljs
Normal 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))
|
|
@ -5,9 +5,6 @@
|
||||||
(ns datomish.core
|
(ns datomish.core
|
||||||
(:require [cljs.nodejs :as nodejs]))
|
(:require [cljs.nodejs :as nodejs]))
|
||||||
|
|
||||||
(nodejs/enable-util-print!)
|
(defn -main [& args])
|
||||||
|
|
||||||
(defn -main [& args]
|
|
||||||
(println "Hello world!"))
|
|
||||||
|
|
||||||
(set! *main-cli-fn* -main)
|
(set! *main-cli-fn* -main)
|
||||||
|
(nodejs/enable-util-print!)
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
(ns datomish.promise-sqlite
|
(ns datomish.promise-sqlite
|
||||||
(:require
|
(:require
|
||||||
[datomish.sqlite :as s]
|
[datomish.sqlite :as s]
|
||||||
|
[datomish.cljify :refer [cljify]]
|
||||||
[cljs-promises.async]
|
[cljs-promises.async]
|
||||||
[cljs.nodejs :as nodejs]))
|
[cljs.nodejs :as nodejs]))
|
||||||
|
|
||||||
|
@ -20,7 +21,7 @@
|
||||||
(-each
|
(-each
|
||||||
[db sql bindings row-cb]
|
[db sql bindings row-cb]
|
||||||
(let [cb (fn [row]
|
(let [cb (fn [row]
|
||||||
(row-cb (js->clj row :keywordize-keys true)))]
|
(row-cb (cljify row)))]
|
||||||
(cljs-promises.async/pair-port
|
(cljs-promises.async/pair-port
|
||||||
(.each (.-db db) sql (or (clj->js bindings) #js []) (when row-cb cb)))))
|
(.each (.-db db) sql (or (clj->js bindings) #js []) (when row-cb cb)))))
|
||||||
|
|
||||||
|
|
1
src/node/deps.cljs
Normal file
1
src/node/deps.cljs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{:externs ["externs/datomish.js"]}
|
13
src/node/externs/datomish.js
Normal file
13
src/node/externs/datomish.js
Normal 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) {};
|
|
@ -13,6 +13,7 @@
|
||||||
[datomish.db.debug :refer [<datoms-after <transactions-after <shallow-entity <fulltext-values]]
|
[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.util :as util #?(:cljs :refer-macros :clj :refer) [raise cond-let]]
|
||||||
[datomish.schema :as ds]
|
[datomish.schema :as ds]
|
||||||
|
[datomish.simple-schema]
|
||||||
[datomish.sqlite :as s]
|
[datomish.sqlite :as s]
|
||||||
[datomish.sqlite-schema]
|
[datomish.sqlite-schema]
|
||||||
[datomish.datom]
|
[datomish.datom]
|
||||||
|
@ -816,4 +817,37 @@
|
||||||
(finally
|
(finally
|
||||||
(<? (d/<close conn))))))))
|
(<? (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))
|
#_ (time (t/run-tests))
|
||||||
|
|
46
test/js/tests.js
Normal file
46
test/js/tests.js
Normal 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));
|
Loading…
Reference in a new issue