aboutsummaryrefslogtreecommitdiff
path: root/extension/lib/wallet/query.ts
diff options
context:
space:
mode:
Diffstat (limited to 'extension/lib/wallet/query.ts')
-rw-r--r--extension/lib/wallet/query.ts283
1 files changed, 283 insertions, 0 deletions
diff --git a/extension/lib/wallet/query.ts b/extension/lib/wallet/query.ts
new file mode 100644
index 000000000..c67ce0193
--- /dev/null
+++ b/extension/lib/wallet/query.ts
@@ -0,0 +1,283 @@
+/*
+ This file is part of TALER
+ (C) 2016 GNUnet e.V.
+
+ TALER is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ TALER is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE. See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ TALER; see the file COPYING. If not, If not, see <http://www.gnu.org/licenses/>
+ */
+
+/// <reference path="../decl/urijs/URIjs.d.ts" />
+
+
+/**
+ * Database query abstractions.
+ * @module Query
+ * @author Florian Dold
+ */
+
+"use strict";
+
+
+export function Query(db) {
+ return new QueryRoot(db);
+}
+
+
+abstract class QueryStreamBase {
+ abstract subscribe(f: (isDone: boolean, value: any) => void);
+
+ root: QueryRoot;
+
+ constructor(root: QueryRoot) {
+ this.root = root;
+ }
+
+ indexJoin(storeName: string, indexName: string, key: any): QueryStreamBase {
+ // join on the source relation's key, which may be
+ // a path or a transformer function
+ this.root.stores.add(storeName);
+ return new QueryStreamIndexJoin(this, storeName, indexName, key);
+ }
+
+ filter(f: (any) => boolean): QueryStreamBase {
+ return new QueryStreamFilter(this, f);
+ }
+
+ reduce(f, acc?): Promise<any> {
+ let leakedResolve;
+ let p = new Promise((resolve, reject) => {
+ leakedResolve = resolve;
+ });
+
+ this.subscribe((isDone, value) => {
+ if (isDone) {
+ leakedResolve(acc);
+ return;
+ }
+ acc = f(value, acc);
+ });
+
+ return Promise.resolve().then(() => this.root.finish().then(() => p));
+ }
+}
+
+
+class QueryStreamFilter extends QueryStreamBase {
+ s: QueryStreamBase;
+ filterFn;
+
+ constructor(s: QueryStreamBase, filterFn) {
+ super(s.root);
+ this.s = s;
+ this.filterFn = filterFn;
+ }
+
+ subscribe(f) {
+ this.s.subscribe((isDone, value) => {
+ if (isDone) {
+ f(true, undefined);
+ return;
+ }
+ if (this.filterFn(value)) {
+ f(false, value)
+ }
+ });
+ }
+}
+
+
+class QueryStreamIndexJoin extends QueryStreamBase {
+ s: QueryStreamBase;
+ storeName;
+ key;
+ indexName;
+
+ constructor(s, storeName: string, indexName: string, key: any) {
+ super(s.root);
+ this.s = s;
+ this.storeName = storeName;
+ this.key = key;
+ this.indexName = indexName;
+ }
+
+ subscribe(f) {
+ this.s.subscribe((isDone, value) => {
+ if (isDone) {
+ f(true, undefined);
+ return;
+ }
+ let s = this.root.tx.objectStore(this.storeName).index(this.indexName);
+ let req = s.openCursor(IDBKeyRange.only(this.key(value)));
+ req.onsuccess = () => {
+ let cursor = req.result;
+ if (cursor) {
+ f(false, [value, cursor.value]);
+ cursor.continue();
+ } else {
+ f(true, undefined);
+ }
+ }
+ });
+ }
+
+}
+
+
+class IterQueryStream extends QueryStreamBase {
+ private qr: QueryRoot;
+ private storeName;
+ private options;
+
+ constructor(qr, storeName, options?) {
+ super(qr);
+ this.qr = qr;
+ this.options = options;
+ this.storeName = storeName;
+ }
+
+ subscribe(f) {
+ function doIt() {
+ let s;
+ if (this.options && this.options.indexName) {
+ s = this.qr.tx.objectStore(this.storeName)
+ .index(this.options.indexName);
+ } else {
+ s = this.qr.tx.objectStore(this.storeName);
+ }
+ let kr = undefined;
+ if (this.options && ("only" in this.options)) {
+ kr = IDBKeyRange.only(this.options.only);
+ }
+ let req = s.openCursor(kr);
+ req.onsuccess = (e) => {
+ let cursor: IDBCursorWithValue = req.result;
+ if (cursor) {
+ f(false, cursor.value);
+ cursor.continue();
+ } else {
+ f(true, undefined);
+ }
+ }
+ }
+
+ this.qr.work.push(doIt.bind(this));
+ }
+}
+
+
+class QueryRoot {
+ work = [];
+ db: IDBDatabase;
+ tx: IDBTransaction;
+ stores = new Set();
+ kickoffPromise;
+
+ constructor(db) {
+ this.db = db;
+ }
+
+ iter(storeName): QueryStreamBase {
+ this.stores.add(storeName);
+ return new IterQueryStream(this, storeName);
+ }
+
+ iterOnly(storeName, key): QueryStreamBase {
+ this.stores.add(storeName);
+ return new IterQueryStream(this, storeName, {only: key});
+ }
+
+ iterIndex(storeName, indexName, key) {
+ this.stores.add(storeName);
+ return new IterQueryStream(this, storeName, {indexName: indexName});
+ }
+
+ put(storeName, val): QueryRoot {
+ this.stores.add(storeName);
+ function doPut() {
+ this.tx.objectStore(storeName).put(val);
+ }
+
+ this.work.push(doPut.bind(this));
+ return this;
+ }
+
+ putAll(storeName, iterable): QueryRoot {
+ this.stores.add(storeName);
+ function doPutAll() {
+ for (let obj of iterable) {
+ this.tx.objectStore(storeName).put(obj);
+ }
+ }
+
+ this.work.push(doPutAll.bind(this));
+ return this;
+ }
+
+ add(storeName, val): QueryRoot {
+ this.stores.add(storeName);
+ function doAdd() {
+ this.tx.objectStore(storeName).add(val);
+ }
+
+ this.work.push(doAdd.bind(this));
+ return this;
+ }
+
+ get(storeName, key): Promise<any> {
+ this.stores.add(storeName);
+ let leakedResolve;
+ let p = new Promise((resolve, reject) => {
+ leakedResolve = resolve;
+ });
+ if (!leakedResolve) {
+ // According to ES6 spec (paragraph 25.4.3.1), this can't happen.
+ throw Error("assertion failed");
+ }
+ function doGet() {
+ let req = this.tx.objectStore(storeName).get(key);
+ req.onsuccess = (r) => {
+ leakedResolve(req.result);
+ };
+ }
+
+ this.work.push(doGet.bind(this));
+ return Promise.resolve().then(() => {
+ return this.finish().then(() => p);
+ });
+ }
+
+ finish(): Promise<void> {
+ if (this.kickoffPromise) {
+ return this.kickoffPromise;
+ }
+ this.kickoffPromise = new Promise((resolve, reject) => {
+
+ this.tx = this.db.transaction(Array.from(this.stores), "readwrite");
+ this.tx.oncomplete = () => {
+ resolve();
+ };
+ for (let w of this.work) {
+ w();
+ }
+ });
+ return this.kickoffPromise;
+ }
+
+ delete(storeName: string, key): QueryRoot {
+ this.stores.add(storeName);
+ function doDelete() {
+ this.tx.objectStore(storeName).delete(key);
+ }
+
+ this.work.push(doDelete.bind(this));
+ return this;
+ }
+} \ No newline at end of file