diff --git a/addons/Dexie.Observable/dist/README.md b/addons/Dexie.Observable/dist/README.md
deleted file mode 100644
index f54e59357..000000000
--- a/addons/Dexie.Observable/dist/README.md
+++ /dev/null
@@ -1,37 +0,0 @@
-## Can't find dexie-observable.js?
-Transpiled code (dist version) IS ONLY checked in to
-the [releases](https://github.com/dfahlander/Dexie.js/tree/releases/addons/Dexie.Observable/dist)
-branch.
-
-## Download
-[unpkg.com/dexie-observable/dist/dexie-observable.js](https://unpkg.com/dexie-observable/dist/dexie-observable.js)
-
-[unpkg.com/dexie-observable/dist/dexie-observable.min.js](https://unpkg.com/dexie-observable/dist/dexie-observable.min.js)
-
-[unpkg.com/dexie-observable/dist/dexie-observable.js.map](https://unpkg.com/dexie-observable/dist/dexie-observable.js.map)
-
-[unpkg.com/dexie-observable/dist/dexie-observable.min.js.map](https://unpkg.com/dexie-observable/dist/dexie-observable.min.js.map)
-
-## npm
-```
-npm install dexie-observable --save
-```
-## bower
-Since Dexie v1.3.4, addons are included in the dexie bower package.
-```
-$ bower install dexie --save
-$ ls bower_components/dexie/addons/Dexie.Observable/dist
-dexie-observable.js dexie-observable.js.map dexie-observable.min.js dexie-observable.min.js.map
-
-```
-## Or build them yourself...
-Fork Dexie.js, then:
-```
-git clone https://github.com/YOUR-USERNAME/Dexie.js.git
-cd Dexie.js
-npm install
-cd addons/Dexie.Observable
-npm run build # or npm run watch
-
-```
-If you're on windows, you need to use an elevated command prompt of some reason to get `npm install` to work.
diff --git a/addons/Dexie.Observable/dist/dexie-observable.js b/addons/Dexie.Observable/dist/dexie-observable.js
new file mode 100644
index 000000000..281d33781
--- /dev/null
+++ b/addons/Dexie.Observable/dist/dexie-observable.js
@@ -0,0 +1,840 @@
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('dexie')) :
+ typeof define === 'function' && define.amd ? define(['dexie'], factory) :
+ (global.Dexie = global.Dexie || {}, global.Dexie.Observable = factory(global.Dexie));
+}(this, (function (Dexie) { 'use strict';
+
+Dexie = 'default' in Dexie ? Dexie['default'] : Dexie;
+
+///
+
+/**
+ * Dexie.Observable.js
+ * ===================
+ * Dexie addon for observing database changes not just on local db instance but also on other instances and windows.
+ *
+ * version: 0.1.9 Alpha, Thu Oct 13 2016
+ *
+ * Disclaimber: This addon is in alpha status meaning that
+ * its API and behavior may change.
+ *
+ */
+var global = self;
+
+/** class DatabaseChange
+ *
+ * Object contained by the _changes table.
+ */
+var DatabaseChange = Dexie.defineClass({
+ rev: Number, // Auto-incremented primary key
+ source: String, // Optional source creating the change. Set if transaction.source was set when doing the operation.
+ table: String, // Table name
+ key: Object, // Primary key. Any type.
+ type: Number, // 1 = CREATE, 2 = UPDATE, 3 = DELETE
+ obj: Object, // CREATE: obj contains the object created.
+ mods: Object, // UPDATE: mods contains the modifications made to the object.
+ oldObj: Object // DELETE: oldObj contains the object deleted. UPDATE: oldObj contains the old object before updates applied.
+});
+
+// Import some usable helper functions
+var override = Dexie.override;
+var Promise = Dexie.Promise;
+var browserIsShuttingDown = false;
+
+function Observable(db) {
+ ///
+ /// Extension to Dexie providing Syncronization capabilities to Dexie.
+ ///
+ ///
+
+ var NODE_TIMEOUT = 20000,
+ // 20 seconds before local db instances are timed out. This is so that old changes can be deleted when not needed and to garbage collect old _syncNodes objects.
+ HIBERNATE_GRACE_PERIOD = 20000,
+ // 20 seconds
+ // LOCAL_POLL: The time to wait before polling local db for changes and cleaning up old nodes.
+ // Polling for changes is a fallback only needed in certain circomstances (when the onstorage event doesnt reach all listeners - when different browser windows doesnt share the same process)
+ LOCAL_POLL = 2000,
+ // 1 second. In real-world there will be this value + the time it takes to poll().
+ CREATE = 1,
+ UPDATE = 2,
+ DELETE = 3;
+
+ var localStorage = Observable.localStorageImpl;
+
+ /** class SyncNode
+ *
+ * Object contained in the _syncNodes table.
+ */
+ var SyncNode = Dexie.defineClass({
+ //id: Number,
+ myRevision: Number,
+ type: String, // "local" or "remote"
+ lastHeartBeat: Number,
+ deleteTimeStamp: Number, // In case lastHeartBeat is too old, a value of now + HIBERNATE_GRACE_PERIOD will be set here. If reached before node wakes up, node will be deleted.
+ url: String, // Only applicable for "remote" nodes. Only used in Dexie.Syncable.
+ isMaster: Number, // 1 if true. Not using Boolean because it's not possible to index Booleans in IE implementation of IDB.
+
+ // Below properties should be extended in Dexie.Syncable. Not here. They apply to remote nodes only (type == "remote"):
+ syncProtocol: String, // Tells which implementation of ISyncProtocol to use for remote syncing.
+ syncContext: null,
+ syncOptions: Object,
+ connected: false, // FIXTHIS: Remove! Replace with status.
+ status: Number,
+ appliedRemoteRevision: null,
+ remoteBaseRevisions: [{ local: Number, remote: null }],
+ dbUploadState: {
+ tablesToUpload: [String],
+ currentTable: String,
+ currentKey: null,
+ localBaseRevision: Number
+ }
+ });
+
+ var mySyncNode = null;
+
+ // Allow other addons to access the local sync node. May be needed by Dexie.Syncable.
+ Object.defineProperty(db, "_localSyncNode", {
+ get: function () {
+ return mySyncNode;
+ }
+ });
+
+ var pollHandle = null;
+
+ if (Dexie.fake) {
+ // This code will never run.
+ // It's here just to enable auto-complete in visual studio - helps a lot when writing code.
+ db.version(1).stores({
+ _syncNodes: "++id,myRevision,lastHeartBeat",
+ _changes: "++rev",
+ _intercomm: "++id,destinationNode",
+ _uncommittedChanges: "++id,node"
+ });
+ db._syncNodes.mapToClass(SyncNode);
+ db._changes.mapToClass(DatabaseChange);
+ mySyncNode = new SyncNode({
+ myRevision: 0,
+ type: "local",
+ lastHeartBeat: Date.now(),
+ deleteTimeStamp: null
+ });
+ }
+
+ //
+ // Override parsing the stores to add "_changes" and "_syncNodes" tables.
+ //
+ db.Version.prototype._parseStoresSpec = override(db.Version.prototype._parseStoresSpec, function (origFunc) {
+ return function (stores, dbSchema) {
+ // Create the _changes and _syncNodes tables
+ stores["_changes"] = "++rev";
+ stores["_syncNodes"] = "++id,myRevision,lastHeartBeat,url,isMaster,type,status";
+ stores["_intercomm"] = "++id,destinationNode";
+ stores["_uncommittedChanges"] = "++id,node"; // For remote syncing when server returns a partial result.
+ // Call default implementation. Will populate the dbSchema structures.
+ origFunc.call(this, stores, dbSchema);
+ // Allow UUID primary keys using $$ prefix on primary key or indexes
+ Object.keys(dbSchema).forEach(function (tableName) {
+ var schema = dbSchema[tableName];
+ if (schema.primKey.name.indexOf('$$') === 0) {
+ schema.primKey.uuid = true;
+ schema.primKey.name = schema.primKey.name.substr(2);
+ schema.primKey.keyPath = schema.primKey.keyPath.substr(2);
+ }
+ });
+ // Now mark all observable tables
+ Object.keys(dbSchema).forEach(function (tableName) {
+ // Marked observable tables with "observable" in their TableSchema.
+ if (tableName.indexOf('_') !== 0 && tableName.indexOf('$') !== 0) {
+ dbSchema[tableName].observable = true;
+ }
+ });
+ };
+ });
+
+ //
+ // Make sure to subscribe to "creating", "updating" and "deleting" hooks for all observable tables that were created in the stores() method.
+ //
+ db._tableFactory = override(db._tableFactory, function (origCreateTable) {
+ return function createTable(mode, tableSchema, transactionPromiseFactory) {
+ var table = origCreateTable.apply(this, arguments);
+ if (table.schema.observable && transactionPromiseFactory === db._transPromiseFactory) {
+ // Only crudMonitor when creating
+ crudMonitor(table);
+ }
+ if (table.name === "_syncNodes" && transactionPromiseFactory === db._transPromiseFactory) {
+ table.mapToClass(SyncNode);
+ }
+ return table;
+ };
+ });
+
+ // changes event on db:
+ db.on.addEventType({
+ changes: 'asap',
+ cleanup: [promisableChain, nop], // fire (nodesTable, changesTable, trans). Hook called when cleaning up nodes. Subscribers may return a Promise to to more stuff. May do additional stuff if local sync node is master.
+ message: 'asap'
+ });
+
+ //
+ // Overide transaction creation to always include the "_changes" store when any observable store is involved.
+ //
+ db._createTransaction = override(db._createTransaction, function (origFunc) {
+ return function (mode, storenames, dbschema, parent) {
+ if (db.dynamicallyOpened()) return origFunc.apply(this, arguments); // Don't observe dynamically opened databases.
+ var addChanges = false;
+ if (mode === 'readwrite' && storenames.some(function (storeName) {
+ return dbschema[storeName] && dbschema[storeName].observable;
+ })) {
+ // At least one included store is a observable store. Make sure to also include the _changes store.
+ addChanges = true;
+ storenames = storenames.slice(0); // Clone
+ if (storenames.indexOf("_changes") === -1) storenames.push("_changes"); // Otherwise, firefox will hang... (I've reported the bug to Mozilla@Bugzilla)
+ }
+ // Call original db._createTransaction()
+ var trans = origFunc.call(this, mode, storenames, dbschema, parent);
+ // If this transaction is bound to any observable table, make sure to add changes when transaction completes.
+ if (addChanges) {
+ trans._lastWrittenRevision = 0;
+ trans.on('complete', function () {
+ if (trans._lastWrittenRevision) {
+ // Changes were written in this transaction.
+ if (!parent) {
+ // This is root-level transaction, i.e. a physical commit has happened.
+ // Delay-trigger a wakeup call:
+ if (wakeupObservers.timeoutHandle) clearTimeout(wakeupObservers.timeoutHandle);
+ wakeupObservers.timeoutHandle = setTimeout(function () {
+ delete wakeupObservers.timeoutHandle;
+ wakeupObservers(trans._lastWrittenRevision);
+ }, 25);
+ } else {
+ // This is just a virtual commit of a sub transaction.
+ // Wait with waking up observers until root transaction has committed.
+ // Make sure to mark root transaction so that it will wakeup observers upon commit.
+ var rootTransaction = function findRootTransaction(trans) {
+ return trans.parent ? findRootTransaction(trans.parent) : trans;
+ }(parent);
+ rootTransaction._lastWrittenRevision = Math.max(trans._lastWrittenRevision, rootTransaction.lastWrittenRevision || 0);
+ }
+ }
+ });
+ // Derive "source" property from parent transaction by default
+ if (trans.parent && trans.parent.source) trans.source = trans.parent.source;
+ }
+ return trans;
+ };
+ });
+
+ // If Observable.latestRevsion[db.name] is undefined, set it to 0 so that comparing against it always works.
+ // You might think that it will always be undefined before this call, but in case another Dexie instance in the same
+ // window with the same database name has been created already, this static property will already be set correctly.
+ Observable.latestRevision[db.name] = Observable.latestRevision[db.name] || 0;
+
+ function wakeupObservers(lastWrittenRevision) {
+ // Make sure Observable.latestRevision[db.name] is still below our value, now when some time has elapsed and other db instances in same window possibly could have made changes too.
+ if (Observable.latestRevision[db.name] < lastWrittenRevision) {
+ // Set the static property lastRevision[db.name] to the revision of the last written change.
+ Observable.latestRevision[db.name] = lastWrittenRevision;
+ // Wakeup ourselves, and any other db instances on this window:
+ Dexie.ignoreTransaction(function () {
+ Observable.on('latestRevisionIncremented').fire(db.name, lastWrittenRevision);
+ });
+ // Observable.on.latestRevisionIncremented will only wakeup db's in current window.
+ // We need a storage event to wakeup other windwos.
+ // Since indexedDB lacks storage events, let's use the storage event from WebStorage just for
+ // the purpose to wakeup db instances in other windows.
+ localStorage.setItem('Dexie.Observable/latestRevision/' + db.name, lastWrittenRevision); // In IE, this will also wakeup our own window. However, onLatestRevisionIncremented will work around this by only running once per revision id.
+ }
+ }
+
+ db.close = override(db.close, function (origClose) {
+ return function () {
+ if (db.dynamicallyOpened()) return origClose.apply(this, arguments); // Don't observe dynamically opened databases.
+ // Teardown our framework.
+ if (wakeupObservers.timeoutHandle) {
+ clearTimeout(wakeupObservers.timeoutHandle);
+ delete wakeupObservers.timeoutHandle;
+ }
+ Observable.on('latestRevisionIncremented').unsubscribe(onLatestRevisionIncremented);
+ Observable.on('suicideNurseCall').unsubscribe(onSuicide);
+ Observable.on('intercomm').unsubscribe(onIntercomm);
+ Observable.on('beforeunload').unsubscribe(onBeforeUnload);
+ // Inform other db instances in same window that we are dying:
+ if (mySyncNode && mySyncNode.id) {
+ Observable.on.suicideNurseCall.fire(db.name, mySyncNode.id);
+ // Inform other windows as well:
+ localStorage.setItem('Dexie.Observable/deadnode:' + mySyncNode.id.toString() + '/' + db.name, "dead"); // In IE, this will also wakeup our own window. cleanup() may trigger twice per other db instance. But that doesnt to anything.
+ mySyncNode.deleteTimeStamp = 1; // One millisecond after 1970. Makes it occur in the past but still keeps it truthy.
+ mySyncNode.lastHeartBeat = 0;
+ db._syncNodes.put(mySyncNode); // This async operation may be cancelled since the browser is closing down now.
+ mySyncNode = null;
+ }
+
+ if (pollHandle) clearTimeout(pollHandle);
+ pollHandle = null;
+ return origClose.apply(this, arguments);
+ };
+ });
+
+ // Override Dexie.delete() in order to delete Observable.latestRevision[db.name].
+ db.delete = override(db.delete, function (origDelete) {
+ return function () {
+ return origDelete.apply(this, arguments).then(function (result) {
+ // Reset Observable.latestRevision[db.name]
+ Observable.latestRevision[db.name] = 0;
+ return result;
+ });
+ };
+ });
+
+ //
+ // The Creating/Updating/Deleting hook will make sure any change is stored to the changes table
+ //
+ function crudMonitor(table) {
+ ///
+ var tableName = table.name;
+
+ table.hook('creating').subscribe(function (primKey, obj, trans) {
+ ///
+ var rv = undefined;
+ if (primKey === undefined && table.schema.primKey.uuid) {
+ primKey = rv = Observable.createUUID();
+ if (table.schema.primKey.keyPath) {
+ Dexie.setByKeyPath(obj, table.schema.primKey.keyPath, primKey);
+ }
+ }
+
+ var change = {
+ source: trans.source || null, // If a "source" is marked on the transaction, store it. Useful for observers that want to ignore their own changes.
+ table: tableName,
+ key: primKey === undefined ? null : primKey,
+ type: CREATE,
+ obj: obj
+ };
+
+ var promise = db._changes.add(change).then(function (rev) {
+ trans._lastWrittenRevision = Math.max(trans._lastWrittenRevision, rev);
+ return rev;
+ });
+
+ // Wait for onsuccess so that we have the primKey if it is auto-incremented and update the change item if so.
+ this.onsuccess = function (resultKey) {
+ if (primKey != resultKey) promise._then(function () {
+ change.key = resultKey;
+ db._changes.put(change);
+ });
+ };
+ this.onerror = function (err) {
+ // If the main operation fails, make sure to regret the change
+ promise._then(function (rev) {
+ // Will only happen if app code catches the main operation error to prohibit transaction from aborting.
+ db._changes.delete(rev);
+ });
+ };
+
+ return rv;
+ });
+
+ table.hook('updating').subscribe(function (mods, primKey, oldObj, trans) {
+ ///
+ // mods may contain property paths with undefined as value if the property
+ // is being deleted. Since we cannot persist undefined we need to act
+ // like those changes is setting the value to null instead.
+ var modsWithoutUndefined = {};
+ // As of current Dexie version (1.0.3) hook may be called even if it wouldnt really change.
+ // Therefore we may do that kind of optimization here - to not add change entries if
+ // there's nothing to change.
+ var anythingChanged = false;
+ var newObj = Dexie.deepClone(oldObj);
+ for (var propPath in mods) {
+ var mod = mods[propPath];
+ if (typeof mod === 'undefined') {
+ Dexie.delByKeyPath(newObj, propPath);
+ modsWithoutUndefined[propPath] = null; // Null is as close we could come to deleting a property when not allowing undefined.
+ anythingChanged = true;
+ } else {
+ var currentValue = Dexie.getByKeyPath(oldObj, propPath);
+ if (mod !== currentValue && JSON.stringify(mod) !== JSON.stringify(currentValue)) {
+ Dexie.setByKeyPath(newObj, propPath, mod);
+ modsWithoutUndefined[propPath] = mod;
+ anythingChanged = true;
+ }
+ }
+ }
+ if (anythingChanged) {
+ var change = {
+ source: trans.source || null, // If a "source" is marked on the transaction, store it. Useful for observers that want to ignore their own changes.
+ table: tableName,
+ key: primKey,
+ type: UPDATE,
+ mods: modsWithoutUndefined,
+ oldObj: oldObj,
+ obj: newObj
+ };
+ var promise = db._changes.add(change); // Just so we get the correct revision order of the update...
+ this.onsuccess = function () {
+ promise._then(function (rev) {
+ trans._lastWrittenRevision = Math.max(trans._lastWrittenRevision, rev);
+ });
+ };
+ this.onerror = function (err) {
+ // If the main operation fails, make sure to regret the change.
+ promise._then(function (rev) {
+ // Will only happen if app code catches the main operation error to prohibit transaction from aborting.
+ db._changes.delete(rev);
+ });
+ };
+ }
+ });
+
+ table.hook('deleting').subscribe(function (primKey, obj, trans) {
+ ///
+ var promise = db._changes.add({
+ source: trans.source || null, // If a "source" is marked on the transaction, store it. Useful for observers that want to ignore their own changes.
+ table: tableName,
+ key: primKey,
+ type: DELETE,
+ oldObj: obj
+ }).then(function (rev) {
+ trans._lastWrittenRevision = Math.max(trans._lastWrittenRevision, rev);
+ return rev;
+ });
+ this.onerror = function () {
+ // If the main operation fails, make sure to regret the change.
+ // Using _then because if promise is already fullfilled, the standard then() would
+ // do setTimeout() and we would loose the transaction.
+ promise._then(function (rev) {
+ // Will only happen if app code catches the main operation error to prohibit transaction from aborting.
+ db._changes.delete(rev);
+ });
+ };
+ });
+ }
+
+ // When db opens, make sure to start monitor any changes before other db operations will start.
+ db.on("ready", function startObserving() {
+ if (db.dynamicallyOpened()) return db; // Don't observe dynamically opened databases.
+ return db.table("_changes").orderBy("rev").last(function (lastChange) {
+ // Since startObserving() is called before database open() method, this will be the first database operation enqueued to db.
+ // Therefore we know that the retrieved value will be This query will
+ var latestRevision = lastChange ? lastChange.rev : 0;
+ mySyncNode = new SyncNode({
+ myRevision: latestRevision,
+ type: "local",
+ lastHeartBeat: Date.now(),
+ deleteTimeStamp: null,
+ isMaster: 0
+ });
+ if (Observable.latestRevision[db.name] < latestRevision) {
+ // Side track . For correctness whenever setting Observable.latestRevision[db.name] we must make sure the event is fired if increased:
+ // There are other db instances in same window that hasnt yet been informed about a new revision
+ Observable.latestRevision[db.name] = latestRevision;
+ Dexie.ignoreTransaction(function () {
+ Observable.on.latestRevisionIncremented.fire(latestRevision);
+ });
+ }
+ // Add new sync node or if this is a reopening of the database after a close() call, update it.
+ return db.transaction('rw', '_syncNodes', function () {
+ db._syncNodes.where('isMaster').equals(1).count(function (anyMasterNode) {
+ if (!anyMasterNode) {
+ // There's no master node. Let's take that role then.
+ mySyncNode.isMaster = 1;
+ }
+ // Add our node to DB and start subscribing to events
+ db._syncNodes.add(mySyncNode).then(function () {
+ Observable.on('latestRevisionIncremented', onLatestRevisionIncremented); // Wakeup when a new revision is available.
+ Observable.on('beforeunload', onBeforeUnload);
+ Observable.on('suicideNurseCall', onSuicide);
+ Observable.on('intercomm', onIntercomm);
+ // Start polling for changes and do cleanups:
+ pollHandle = setTimeout(poll, LOCAL_POLL);
+ });
+ });
+ }).then(function () {
+ cleanup();
+ });
+ //cleanup();
+ //});
+ });
+ }, true); // True means the on(ready) event will survive a db reopening (db.close() / db.open()).
+
+ var handledRevision = 0;
+
+ function onLatestRevisionIncremented(dbname, latestRevision) {
+ if (dbname === db.name) {
+ if (handledRevision >= latestRevision) return; // Make sure to only run once per revision. (Workaround for IE triggering storage event on same window)
+ handledRevision = latestRevision;
+ Dexie.vip(function () {
+ readChanges(latestRevision).catch('DatabaseClosedError', function (e) {
+ // Handle database closed error gracefully while reading changes.
+ // Don't trigger unhandledrejection
+ // Even though we intercept the close() method, it might be called when in the middle of
+ // reading changes and then that flow will cancel with DatabaseClosedError.
+ });
+ });
+ }
+ }
+
+ function readChanges(latestRevision, recursion, wasPartial) {
+ // Whenever changes are read, fire db.on("changes") with the array of changes. Eventually, limit the array to 1000 entries or so (an entire database is
+ // downloaded from server AFTER we are initiated. For example, if first sync call fails, then after a while we get reconnected. However, that scenario
+ // should be handled in case database is totally empty we should fail if sync is not available)
+ if (!recursion && readChanges.ongoingOperation) {
+ // We are already reading changes. Prohibit a parallell execution of this which would lead to duplicate trigging of 'changes' event.
+ // Instead, the callback in toArray() will always check Observable.latestRevision[db.name] to see if it has changed and if so, re-launch readChanges().
+ // The caller should get the Promise instance from the ongoing operation so that the then() method will resolve when operation is finished.
+ return readChanges.ongoingOperation;
+ }
+
+ var partial = false;
+ var ourSyncNode = mySyncNode; // Because mySyncNode can suddenly be set to null on database close, and worse, can be set to a new value if database is reopened.
+ if (!ourSyncNode) {
+ return Promise.reject("Database closed");
+ }
+ var LIMIT = 1000;
+ var promise = db._changes.where("rev").above(ourSyncNode.myRevision).limit(LIMIT).toArray(function (changes) {
+ if (changes.length > 0) {
+ var lastChange = changes[changes.length - 1];
+ partial = changes.length === LIMIT;
+ db.on('changes').fire(changes, partial);
+ ourSyncNode.myRevision = lastChange.rev;
+ } else if (wasPartial) {
+ // No more changes, BUT since we have triggered on('changes') with partial = true,
+ // we HAVE TO trigger changes again with empty list and partial = false
+ db.on('changes').fire([], false);
+ }
+
+ return db.table("_syncNodes").update(ourSyncNode, {
+ lastHeartBeat: Date.now(),
+ deleteTimeStamp: null, // Reset "deleteTimeStamp" flag if it was there.
+ myRevision: ourSyncNode.myRevision
+ });
+ }).then(function (nodeWasUpdated) {
+ if (!nodeWasUpdated) {
+ // My node has been deleted. We must have been lazy and got removed by another node.
+ if (browserIsShuttingDown) {
+ throw new Error("Browser is shutting down");
+ } else {
+ db.close();
+ console.error("Out of sync"); // TODO: What to do? Reload the page?
+ if (global.location) global.location.reload(true);
+ throw new Error("Out of sync"); // Will make current promise reject
+ }
+ }
+
+ // Check if more changes have come since we started reading changes in the first place. If so, relaunch readChanges and let the ongoing promise not
+ // resolve until all changes have been read.
+ if (partial || Observable.latestRevision[db.name] > ourSyncNode.myRevision) {
+ // Either there were more than 1000 changes or additional changes where added while we were reading these changes,
+ // In either case, call readChanges() again until we're done.
+ return readChanges(Observable.latestRevision[db.name], (recursion || 0) + 1, partial);
+ }
+ }).finally(function () {
+ delete readChanges.ongoingOperation;
+ });
+
+ if (!recursion) {
+ readChanges.ongoingOperation = promise;
+ }
+ return promise;
+ }
+
+ function poll() {
+ pollHandle = null;
+ var currentInstance = mySyncNode.id;
+ Dexie.vip(function () {
+ // VIP ourselves. Otherwise we might not be able to consume intercomm messages from master node before database has finished opening. This would make DB stall forever. Cannot rely on storage-event since it may not always work in some browsers of different processes.
+ readChanges(Observable.latestRevision[db.name]).then(cleanup).then(consumeIntercommMessages).catch('DatabaseClosedError', function (e) {
+ // Handle database closed error gracefully while reading changes.
+ // Don't signal 'unhandledrejection'.
+ // Even though we intercept the close() method, it might be called when in the middle of
+ // reading changes and then that flow will cancel with DatabaseClosedError.
+ }).finally(function () {
+ // Poll again in given interval:
+ if (mySyncNode && mySyncNode.id === currentInstance) {
+ pollHandle = setTimeout(poll, LOCAL_POLL);
+ }
+ });
+ });
+ }
+
+ function cleanup() {
+ var ourSyncNode = mySyncNode;
+ if (!ourSyncNode) return Promise.reject("Database closed");
+ return db.transaction('rw', '_syncNodes', '_changes', '_intercomm', function () {
+ // Cleanup dead local nodes that has no heartbeat for over a minute
+ // Dont do the following:
+ //nodes.where("lastHeartBeat").below(Date.now() - NODE_TIMEOUT).and(function (node) { return node.type == "local"; }).delete();
+ // Because client may have been in hybernate mode and recently woken up. That would lead to deletion of all nodes.
+ // Instead, we should mark any old nodes for deletion in a minute or so. If they still dont wakeup after that minute we could consider them dead.
+ var weBecameMaster = false;
+ db._syncNodes.where("lastHeartBeat").below(Date.now() - NODE_TIMEOUT).and(function (node) {
+ return node.type === 'local';
+ }).modify(function (node) {
+ if (node.deleteTimeStamp && node.deleteTimeStamp < Date.now()) {
+ // Delete the node.
+ delete this.value;
+ // Cleanup localStorage "deadnode:" entry for this node (localStorage API was used to wakeup other windows (onstorage event) - an event type missing in indexedDB.)
+ localStorage.removeItem('Dexie.Observable/deadnode:' + node.id + '/' + db.name);
+ // Check if we are deleting a master node
+ if (node.isMaster) {
+ // The node we are deleting is master. We must take over that role.
+ // OK to call nodes.update(). No need to call Dexie.vip() because nodes is opened in existing transaction!
+ db._syncNodes.update(ourSyncNode, { isMaster: 1 });
+ weBecameMaster = true;
+ }
+ // Cleanup intercomm messages destinated to the node being deleted:
+ db._intercomm.where("destinationNode").equals(node.id).modify(function (msg) {
+ // OK to call intercomm. No need to call Dexie.vip() because intercomm is opened in existing transaction!
+ // Delete the message from DB and if someone is waiting for reply, let ourselved answer the request.
+ delete this.value;
+ if (msg.wantReply) {
+ // Message wants a reply, meaning someone must take over its messages when it dies. Let us be that one!
+ Dexie.ignoreTransaction(function () {
+ consumeMessage(msg);
+ });
+ }
+ });
+ } else if (!node.deleteTimeStamp) {
+ // Mark the node for deletion
+ node.deleteTimeStamp = Date.now() + HIBERNATE_GRACE_PERIOD;
+ }
+ }).then(function () {
+ // Cleanup old revisions that no node is interested of.
+ Observable.deleteOldChanges(db);
+ return db.on("cleanup").fire(weBecameMaster);
+ });
+ });
+ }
+
+ function onBeforeUnload(event) {
+ // Mark our own sync node for deletion.
+ if (!mySyncNode) return;
+ browserIsShuttingDown = true;
+ mySyncNode.deleteTimeStamp = 1; // One millisecond after 1970. Makes it occur in the past but still keeps it truthy.
+ mySyncNode.lastHeartBeat = 0;
+ db._syncNodes.put(mySyncNode); // This async operation may be cancelled since the browser is closing down now.
+ Observable.wereTheOneDying = true; // If other nodes in same window wakes up by this call, make sure they dont start taking over mastership and stuff...
+ // Inform other windows that we're gone, so that they may take over our role if needed. Setting localStorage item below will trigger Observable.onStorage, which will trigger onSuicie() below:
+ localStorage.setItem('Dexie.Observable/deadnode:' + mySyncNode.id.toString() + '/' + db.name, "dead"); // In IE, this will also wakeup our own window. However, that is doublechecked in nursecall subscriber below.
+ }
+
+ function onSuicide(dbname, nodeID) {
+ if (dbname === db.name && !Observable.wereTheOneDying) {
+ // Make sure it's dead indeed. Second bullet. Why? Because it has marked itself for deletion in the onbeforeunload event, which is fired just before window dies.
+ // It's own call to put() may have been cancelled.
+ // Note also that in IE, this event may be called twice, but that doesnt harm!
+ Dexie.vip(function () {
+ db._syncNodes.update(nodeID, { deleteTimeStamp: 1, lastHeartBeat: 0 }).then(cleanup);
+ });
+ }
+ }
+
+ //
+ // Intercommunication between nodes
+ //
+ // Enable inter-process communication between browser windows
+
+ var requestsWaitingForReply = {};
+
+ db.sendMessage = function (type, message, destinationNode, options) {
+ /// Type of message
+ /// Message to send
+ /// ID of destination node
+ /// {wantReply: Boolean, isFailure: Boolean, requestId: Number}. If wantReply, the returned promise will complete with the reply from remote. Otherwise it will complete when message has been successfully sent.
+ if (!mySyncNode) return Promise.reject("Database closed");
+ options = options || {};
+ var msg = { message: message, destinationNode: destinationNode, sender: mySyncNode.id, type: type };
+ Dexie.extend(msg, options); // wantReply: wantReply, success: !isFailure, requestId: ...
+ var tables = ["_intercomm"];
+ if (options.wantReply) tables.push("_syncNodes"); // If caller wants a reply, include "_syncNodes" in transaction to check that there's a reciever there. Otherwise, new master will get it.
+ return db.transaction('rw?', tables, function () {
+ if (options.wantReply) {
+ // Check that there is a reciever there to take the request.
+ return db._syncNodes.where('id').equals(destinationNode).count(function (recieverAlive) {
+ if (recieverAlive) return addMessage(msg);else return db._syncNodes.where('isMaster').above(0).first(function (masterNode) {
+ msg.destinationNode = masterNode.id;
+ return addMessage(msg);
+ });
+ });
+ } else {
+ addMessage(msg); // No need to return Promise. Caller dont need a reply.
+ }
+
+ function addMessage(msg) {
+ return db._intercomm.add(msg).then(function (messageId) {
+ localStorage.setItem("Dexie.Observable/intercomm/" + db.name, messageId.toString());
+ Dexie.ignoreTransaction(function () {
+ Observable.on.intercomm.fire(db.name);
+ });
+ if (options.wantReply) {
+ return new Promise(function (resolve, reject) {
+ requestsWaitingForReply[messageId.toString()] = { resolve: resolve, reject: reject };
+ });
+ }
+ });
+ }
+ });
+ };
+
+ db.broadcastMessage = function (type, message, bIncludeSelf) {
+ if (!mySyncNode) return Promise.reject("Database closed");
+ var mySyncNodeId = mySyncNode.id;
+ db._syncNodes.each(function (node) {
+ if (node.type === 'local' && (bIncludeSelf || node.id !== mySyncNodeId)) {
+ db.sendMessage(type, message, node.id);
+ }
+ });
+ };
+
+ db.observable = {};
+ db.observable.SyncNode = SyncNode;
+
+ function consumeIntercommMessages() {
+ // Check if we got messages:
+ if (!mySyncNode) return Promise.reject("Database closed");
+ return db.table('_intercomm').where("destinationNode").equals(mySyncNode.id).modify(function (msg) {
+ // For each message, fire the event and remove message.
+ delete this.value;
+ Dexie.ignoreTransaction(function () {
+ consumeMessage(msg);
+ });
+ });
+ }
+
+ function consumeMessage(msg) {
+ if (msg.type === 'response') {
+ // This is a response. Lookup pending request and fulfill it's promise.
+ var request = requestsWaitingForReply[msg.requestId.toString()];
+ if (request) {
+ if (msg.isFailure) {
+ request.reject(msg.message.error);
+ } else {
+ request.resolve(msg.message.result);
+ }
+ delete requestsWaitingForReply[msg.requestId.toString()];
+ }
+ } else {
+ // This is a message or request. Fire the event and add an API for the subscriber to use if reply is requested
+ msg.resolve = function (result) {
+ db.sendMessage('response', { result: result }, msg.sender, { requestId: msg.id });
+ };
+ msg.reject = function (error) {
+ db.sendMessage('response', { error: error.toString() }, msg.sender, { isFailure: true, requestId: msg.id });
+ };
+ var message = msg.message;
+ delete msg.message;
+ Dexie.extend(msg, message);
+ db.on.message.fire(msg);
+ }
+ }
+
+ function onIntercomm(dbname) {
+ // When storage event trigger us to check
+ if (dbname === db.name) {
+ consumeIntercommMessages();
+ }
+ }
+}
+
+//
+// Help functions
+//
+
+function nop() {}
+
+function promisableChain(f1, f2) {
+ if (f1 === nop) return f2;
+ return function () {
+ var res = f1.apply(this, arguments);
+ if (res && typeof res.then === 'function') {
+ var thiz = this,
+ args = arguments;
+ return res.then(function () {
+ return f2.apply(thiz, args);
+ });
+ }
+ return f2.apply(this, arguments);
+ };
+}
+
+//
+// Static properties and methods
+//
+
+Observable.latestRevision = {}; // Latest revision PER DATABASE. Example: Observable.latestRevision.FriendsDB = 37;
+Observable.on = Dexie.Events(null, "latestRevisionIncremented", "suicideNurseCall", "intercomm", "beforeunload"); // fire(dbname, value);
+Observable.createUUID = function () {
+ // Decent solution from http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
+ var d = Date.now();
+ var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
+ var r = (d + Math.random() * 16) % 16 | 0;
+ d = Math.floor(d / 16);
+ return (c === 'x' ? r : r & 0x7 | 0x8).toString(16);
+ });
+ return uuid;
+};
+
+Observable.deleteOldChanges = function (db) {
+ db._syncNodes.orderBy("myRevision").first(function (oldestNode) {
+ var timeout = Date.now() + 300,
+ timedout = false;
+ db._changes.where("rev").below(oldestNode.myRevision).until(function () {
+ return timedout = Date.now() > timeout;
+ }).delete().then(function () {
+ // If not done garbage collecting, reschedule a continuation of it until done.
+ if (timedout) setTimeout(function () {
+ Observable.deleteOldChanges(db);
+ }, 10);
+ });
+ });
+};
+
+Observable._onStorage = function onStorage(event) {
+ // We use the onstorage event to trigger onLatestRevisionIncremented since we will wake up when other windows modify the DB as well!
+ if (event.key.indexOf("Dexie.Observable/") === 0) {
+ // For example "Dexie.Observable/latestRevision/FriendsDB"
+ var parts = event.key.split('/');
+ var prop = parts[1];
+ var dbname = parts[2];
+ if (prop === 'latestRevision') {
+ var rev = parseInt(event.newValue, 10);
+ if (!isNaN(rev) && rev > Observable.latestRevision[dbname]) {
+ Observable.latestRevision[dbname] = rev;
+ Dexie.ignoreTransaction(function () {
+ Observable.on('latestRevisionIncremented').fire(dbname, rev);
+ });
+ }
+ } else if (prop.indexOf("deadnode:") === 0) {
+ var nodeID = parseInt(prop.split(':')[1], 10);
+ if (event.newValue) {
+ Observable.on.suicideNurseCall.fire(dbname, nodeID);
+ }
+ } else if (prop === 'intercomm') {
+ if (event.newValue) {
+ Observable.on.intercomm.fire(dbname);
+ }
+ }
+ }
+};
+
+Observable._onBeforeUnload = function () {
+ Observable.on.beforeunload.fire();
+};
+
+Observable.localStorageImpl = global.localStorage;
+
+//
+// Map window events to static events in Dexie.Observable:
+//
+if (global.addEventListener) {
+ global.addEventListener("storage", Observable._onStorage);
+ global.addEventListener("beforeunload", Observable._onBeforeUnload);
+}
+// Register addon:
+Dexie.Observable = Observable;
+Dexie.addons.push(Observable);
+
+return Observable;
+
+})));
+//# sourceMappingURL=dexie-observable.js.map
diff --git a/addons/Dexie.Observable/dist/dexie-observable.js.map b/addons/Dexie.Observable/dist/dexie-observable.js.map
new file mode 100644
index 000000000..6637f0fe6
--- /dev/null
+++ b/addons/Dexie.Observable/dist/dexie-observable.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"dexie-observable.js","sources":["../tools/tmp/src/Dexie.Observable.js"],"sourcesContent":["/// \n\n/**\r\n * Dexie.Observable.js\r\n * ===================\r\n * Dexie addon for observing database changes not just on local db instance but also on other instances and windows.\r\n *\r\n * version: {version} Alpha, {date}\r\n *\r\n * Disclaimber: This addon is in alpha status meaning that\r\n * its API and behavior may change.\r\n *\r\n */\nimport Dexie from 'dexie';\n\nvar global = self;\n\n/** class DatabaseChange\r\n *\r\n * Object contained by the _changes table.\r\n */\nvar DatabaseChange = Dexie.defineClass({\n rev: Number, // Auto-incremented primary key\n source: String, // Optional source creating the change. Set if transaction.source was set when doing the operation.\n table: String, // Table name\n key: Object, // Primary key. Any type.\n type: Number, // 1 = CREATE, 2 = UPDATE, 3 = DELETE\n obj: Object, // CREATE: obj contains the object created.\n mods: Object, // UPDATE: mods contains the modifications made to the object.\n oldObj: Object // DELETE: oldObj contains the object deleted. UPDATE: oldObj contains the old object before updates applied.\n});\n\n// Import some usable helper functions\nvar override = Dexie.override;\nvar Promise = Dexie.Promise;\nvar browserIsShuttingDown = false;\n\nexport default function Observable(db) {\n /// \n /// Extension to Dexie providing Syncronization capabilities to Dexie.\n /// \n /// \n\n var NODE_TIMEOUT = 20000,\n // 20 seconds before local db instances are timed out. This is so that old changes can be deleted when not needed and to garbage collect old _syncNodes objects.\n HIBERNATE_GRACE_PERIOD = 20000,\n // 20 seconds\n // LOCAL_POLL: The time to wait before polling local db for changes and cleaning up old nodes. \n // Polling for changes is a fallback only needed in certain circomstances (when the onstorage event doesnt reach all listeners - when different browser windows doesnt share the same process)\n LOCAL_POLL = 2000,\n // 1 second. In real-world there will be this value + the time it takes to poll().\n CREATE = 1,\n UPDATE = 2,\n DELETE = 3;\n\n var localStorage = Observable.localStorageImpl;\n\n /** class SyncNode\r\n *\r\n * Object contained in the _syncNodes table.\r\n */\n var SyncNode = Dexie.defineClass({\n //id: Number,\n myRevision: Number,\n type: String, // \"local\" or \"remote\"\n lastHeartBeat: Number,\n deleteTimeStamp: Number, // In case lastHeartBeat is too old, a value of now + HIBERNATE_GRACE_PERIOD will be set here. If reached before node wakes up, node will be deleted.\n url: String, // Only applicable for \"remote\" nodes. Only used in Dexie.Syncable.\n isMaster: Number, // 1 if true. Not using Boolean because it's not possible to index Booleans in IE implementation of IDB.\n\n // Below properties should be extended in Dexie.Syncable. Not here. They apply to remote nodes only (type == \"remote\"):\n syncProtocol: String, // Tells which implementation of ISyncProtocol to use for remote syncing. \n syncContext: null,\n syncOptions: Object,\n connected: false, // FIXTHIS: Remove! Replace with status.\n status: Number,\n appliedRemoteRevision: null,\n remoteBaseRevisions: [{ local: Number, remote: null }],\n dbUploadState: {\n tablesToUpload: [String],\n currentTable: String,\n currentKey: null,\n localBaseRevision: Number\n }\n });\n\n var mySyncNode = null;\n\n // Allow other addons to access the local sync node. May be needed by Dexie.Syncable.\n Object.defineProperty(db, \"_localSyncNode\", {\n get: function () {\n return mySyncNode;\n }\n });\n\n var pollHandle = null;\n\n if (Dexie.fake) {\n // This code will never run.\n // It's here just to enable auto-complete in visual studio - helps a lot when writing code.\n db.version(1).stores({\n _syncNodes: \"++id,myRevision,lastHeartBeat\",\n _changes: \"++rev\",\n _intercomm: \"++id,destinationNode\",\n _uncommittedChanges: \"++id,node\"\n });\n db._syncNodes.mapToClass(SyncNode);\n db._changes.mapToClass(DatabaseChange);\n mySyncNode = new SyncNode({\n myRevision: 0,\n type: \"local\",\n lastHeartBeat: Date.now(),\n deleteTimeStamp: null\n });\n }\n\n //\n // Override parsing the stores to add \"_changes\" and \"_syncNodes\" tables.\n //\n db.Version.prototype._parseStoresSpec = override(db.Version.prototype._parseStoresSpec, function (origFunc) {\n return function (stores, dbSchema) {\n // Create the _changes and _syncNodes tables\n stores[\"_changes\"] = \"++rev\";\n stores[\"_syncNodes\"] = \"++id,myRevision,lastHeartBeat,url,isMaster,type,status\";\n stores[\"_intercomm\"] = \"++id,destinationNode\";\n stores[\"_uncommittedChanges\"] = \"++id,node\"; // For remote syncing when server returns a partial result.\n // Call default implementation. Will populate the dbSchema structures.\n origFunc.call(this, stores, dbSchema);\n // Allow UUID primary keys using $$ prefix on primary key or indexes\n Object.keys(dbSchema).forEach(function (tableName) {\n var schema = dbSchema[tableName];\n if (schema.primKey.name.indexOf('$$') === 0) {\n schema.primKey.uuid = true;\n schema.primKey.name = schema.primKey.name.substr(2);\n schema.primKey.keyPath = schema.primKey.keyPath.substr(2);\n }\n });\n // Now mark all observable tables\n Object.keys(dbSchema).forEach(function (tableName) {\n // Marked observable tables with \"observable\" in their TableSchema.\n if (tableName.indexOf('_') !== 0 && tableName.indexOf('$') !== 0) {\n dbSchema[tableName].observable = true;\n }\n });\n };\n });\n\n //\n // Make sure to subscribe to \"creating\", \"updating\" and \"deleting\" hooks for all observable tables that were created in the stores() method.\n //\n db._tableFactory = override(db._tableFactory, function (origCreateTable) {\n return function createTable(mode, tableSchema, transactionPromiseFactory) {\n var table = origCreateTable.apply(this, arguments);\n if (table.schema.observable && transactionPromiseFactory === db._transPromiseFactory) {\n // Only crudMonitor when creating \n crudMonitor(table);\n }\n if (table.name === \"_syncNodes\" && transactionPromiseFactory === db._transPromiseFactory) {\n table.mapToClass(SyncNode);\n }\n return table;\n };\n });\n\n // changes event on db:\n db.on.addEventType({\n changes: 'asap',\n cleanup: [promisableChain, nop], // fire (nodesTable, changesTable, trans). Hook called when cleaning up nodes. Subscribers may return a Promise to to more stuff. May do additional stuff if local sync node is master.\n message: 'asap'\n });\n\n //\n // Overide transaction creation to always include the \"_changes\" store when any observable store is involved.\n //\n db._createTransaction = override(db._createTransaction, function (origFunc) {\n return function (mode, storenames, dbschema, parent) {\n if (db.dynamicallyOpened()) return origFunc.apply(this, arguments); // Don't observe dynamically opened databases.\n var addChanges = false;\n if (mode === 'readwrite' && storenames.some(function (storeName) {\n return dbschema[storeName] && dbschema[storeName].observable;\n })) {\n // At least one included store is a observable store. Make sure to also include the _changes store.\n addChanges = true;\n storenames = storenames.slice(0); // Clone\n if (storenames.indexOf(\"_changes\") === -1) storenames.push(\"_changes\"); // Otherwise, firefox will hang... (I've reported the bug to Mozilla@Bugzilla)\n }\n // Call original db._createTransaction()\n var trans = origFunc.call(this, mode, storenames, dbschema, parent);\n // If this transaction is bound to any observable table, make sure to add changes when transaction completes.\n if (addChanges) {\n trans._lastWrittenRevision = 0;\n trans.on('complete', function () {\n if (trans._lastWrittenRevision) {\n // Changes were written in this transaction.\n if (!parent) {\n // This is root-level transaction, i.e. a physical commit has happened.\n // Delay-trigger a wakeup call:\n if (wakeupObservers.timeoutHandle) clearTimeout(wakeupObservers.timeoutHandle);\n wakeupObservers.timeoutHandle = setTimeout(function () {\n delete wakeupObservers.timeoutHandle;\n wakeupObservers(trans._lastWrittenRevision);\n }, 25);\n } else {\n // This is just a virtual commit of a sub transaction.\n // Wait with waking up observers until root transaction has committed.\n // Make sure to mark root transaction so that it will wakeup observers upon commit.\n var rootTransaction = function findRootTransaction(trans) {\n return trans.parent ? findRootTransaction(trans.parent) : trans;\n }(parent);\n rootTransaction._lastWrittenRevision = Math.max(trans._lastWrittenRevision, rootTransaction.lastWrittenRevision || 0);\n }\n }\n });\n // Derive \"source\" property from parent transaction by default\n if (trans.parent && trans.parent.source) trans.source = trans.parent.source;\n }\n return trans;\n };\n });\n\n // If Observable.latestRevsion[db.name] is undefined, set it to 0 so that comparing against it always works.\n // You might think that it will always be undefined before this call, but in case another Dexie instance in the same\n // window with the same database name has been created already, this static property will already be set correctly.\n Observable.latestRevision[db.name] = Observable.latestRevision[db.name] || 0;\n\n function wakeupObservers(lastWrittenRevision) {\n // Make sure Observable.latestRevision[db.name] is still below our value, now when some time has elapsed and other db instances in same window possibly could have made changes too.\n if (Observable.latestRevision[db.name] < lastWrittenRevision) {\n // Set the static property lastRevision[db.name] to the revision of the last written change.\n Observable.latestRevision[db.name] = lastWrittenRevision;\n // Wakeup ourselves, and any other db instances on this window:\n Dexie.ignoreTransaction(function () {\n Observable.on('latestRevisionIncremented').fire(db.name, lastWrittenRevision);\n });\n // Observable.on.latestRevisionIncremented will only wakeup db's in current window.\n // We need a storage event to wakeup other windwos.\n // Since indexedDB lacks storage events, let's use the storage event from WebStorage just for\n // the purpose to wakeup db instances in other windows.\n localStorage.setItem('Dexie.Observable/latestRevision/' + db.name, lastWrittenRevision); // In IE, this will also wakeup our own window. However, onLatestRevisionIncremented will work around this by only running once per revision id.\n }\n }\n\n db.close = override(db.close, function (origClose) {\n return function () {\n if (db.dynamicallyOpened()) return origClose.apply(this, arguments); // Don't observe dynamically opened databases.\n // Teardown our framework.\n if (wakeupObservers.timeoutHandle) {\n clearTimeout(wakeupObservers.timeoutHandle);\n delete wakeupObservers.timeoutHandle;\n }\n Observable.on('latestRevisionIncremented').unsubscribe(onLatestRevisionIncremented);\n Observable.on('suicideNurseCall').unsubscribe(onSuicide);\n Observable.on('intercomm').unsubscribe(onIntercomm);\n Observable.on('beforeunload').unsubscribe(onBeforeUnload);\n // Inform other db instances in same window that we are dying:\n if (mySyncNode && mySyncNode.id) {\n Observable.on.suicideNurseCall.fire(db.name, mySyncNode.id);\n // Inform other windows as well:\n localStorage.setItem('Dexie.Observable/deadnode:' + mySyncNode.id.toString() + '/' + db.name, \"dead\"); // In IE, this will also wakeup our own window. cleanup() may trigger twice per other db instance. But that doesnt to anything.\n mySyncNode.deleteTimeStamp = 1; // One millisecond after 1970. Makes it occur in the past but still keeps it truthy.\n mySyncNode.lastHeartBeat = 0;\n db._syncNodes.put(mySyncNode); // This async operation may be cancelled since the browser is closing down now.\n mySyncNode = null;\n }\n\n if (pollHandle) clearTimeout(pollHandle);\n pollHandle = null;\n return origClose.apply(this, arguments);\n };\n });\n\n // Override Dexie.delete() in order to delete Observable.latestRevision[db.name].\n db.delete = override(db.delete, function (origDelete) {\n return function () {\n return origDelete.apply(this, arguments).then(function (result) {\n // Reset Observable.latestRevision[db.name]\n Observable.latestRevision[db.name] = 0;\n return result;\n });\n };\n });\n\n //\n // The Creating/Updating/Deleting hook will make sure any change is stored to the changes table\n //\n function crudMonitor(table) {\n /// \n var tableName = table.name;\n\n table.hook('creating').subscribe(function (primKey, obj, trans) {\n /// \n var rv = undefined;\n if (primKey === undefined && table.schema.primKey.uuid) {\n primKey = rv = Observable.createUUID();\n if (table.schema.primKey.keyPath) {\n Dexie.setByKeyPath(obj, table.schema.primKey.keyPath, primKey);\n }\n }\n\n var change = {\n source: trans.source || null, // If a \"source\" is marked on the transaction, store it. Useful for observers that want to ignore their own changes.\n table: tableName,\n key: primKey === undefined ? null : primKey,\n type: CREATE,\n obj: obj\n };\n\n var promise = db._changes.add(change).then(function (rev) {\n trans._lastWrittenRevision = Math.max(trans._lastWrittenRevision, rev);\n return rev;\n });\n\n // Wait for onsuccess so that we have the primKey if it is auto-incremented and update the change item if so.\n this.onsuccess = function (resultKey) {\n if (primKey != resultKey) promise._then(function () {\n change.key = resultKey;\n db._changes.put(change);\n });\n };\n this.onerror = function (err) {\n // If the main operation fails, make sure to regret the change\n promise._then(function (rev) {\n // Will only happen if app code catches the main operation error to prohibit transaction from aborting.\n db._changes.delete(rev);\n });\n };\n\n return rv;\n });\n\n table.hook('updating').subscribe(function (mods, primKey, oldObj, trans) {\n /// \n // mods may contain property paths with undefined as value if the property\n // is being deleted. Since we cannot persist undefined we need to act\n // like those changes is setting the value to null instead.\n var modsWithoutUndefined = {};\n // As of current Dexie version (1.0.3) hook may be called even if it wouldnt really change.\n // Therefore we may do that kind of optimization here - to not add change entries if\n // there's nothing to change.\n var anythingChanged = false;\n var newObj = Dexie.deepClone(oldObj);\n for (var propPath in mods) {\n var mod = mods[propPath];\n if (typeof mod === 'undefined') {\n Dexie.delByKeyPath(newObj, propPath);\n modsWithoutUndefined[propPath] = null; // Null is as close we could come to deleting a property when not allowing undefined.\n anythingChanged = true;\n } else {\n var currentValue = Dexie.getByKeyPath(oldObj, propPath);\n if (mod !== currentValue && JSON.stringify(mod) !== JSON.stringify(currentValue)) {\n Dexie.setByKeyPath(newObj, propPath, mod);\n modsWithoutUndefined[propPath] = mod;\n anythingChanged = true;\n }\n }\n }\n if (anythingChanged) {\n var change = {\n source: trans.source || null, // If a \"source\" is marked on the transaction, store it. Useful for observers that want to ignore their own changes.\n table: tableName,\n key: primKey,\n type: UPDATE,\n mods: modsWithoutUndefined,\n oldObj: oldObj,\n obj: newObj\n };\n var promise = db._changes.add(change); // Just so we get the correct revision order of the update...\n this.onsuccess = function () {\n promise._then(function (rev) {\n trans._lastWrittenRevision = Math.max(trans._lastWrittenRevision, rev);\n });\n };\n this.onerror = function (err) {\n // If the main operation fails, make sure to regret the change.\n promise._then(function (rev) {\n // Will only happen if app code catches the main operation error to prohibit transaction from aborting.\n db._changes.delete(rev);\n });\n };\n }\n });\n\n table.hook('deleting').subscribe(function (primKey, obj, trans) {\n /// \n var promise = db._changes.add({\n source: trans.source || null, // If a \"source\" is marked on the transaction, store it. Useful for observers that want to ignore their own changes.\n table: tableName,\n key: primKey,\n type: DELETE,\n oldObj: obj\n }).then(function (rev) {\n trans._lastWrittenRevision = Math.max(trans._lastWrittenRevision, rev);\n return rev;\n });\n this.onerror = function () {\n // If the main operation fails, make sure to regret the change.\n // Using _then because if promise is already fullfilled, the standard then() would\n // do setTimeout() and we would loose the transaction.\n promise._then(function (rev) {\n // Will only happen if app code catches the main operation error to prohibit transaction from aborting.\n db._changes.delete(rev);\n });\n };\n });\n }\n\n // When db opens, make sure to start monitor any changes before other db operations will start.\n db.on(\"ready\", function startObserving() {\n if (db.dynamicallyOpened()) return db; // Don't observe dynamically opened databases.\n return db.table(\"_changes\").orderBy(\"rev\").last(function (lastChange) {\n // Since startObserving() is called before database open() method, this will be the first database operation enqueued to db.\n // Therefore we know that the retrieved value will be This query will\n var latestRevision = lastChange ? lastChange.rev : 0;\n mySyncNode = new SyncNode({\n myRevision: latestRevision,\n type: \"local\",\n lastHeartBeat: Date.now(),\n deleteTimeStamp: null,\n isMaster: 0\n });\n if (Observable.latestRevision[db.name] < latestRevision) {\n // Side track . For correctness whenever setting Observable.latestRevision[db.name] we must make sure the event is fired if increased:\n // There are other db instances in same window that hasnt yet been informed about a new revision\n Observable.latestRevision[db.name] = latestRevision;\n Dexie.ignoreTransaction(function () {\n Observable.on.latestRevisionIncremented.fire(latestRevision);\n });\n }\n // Add new sync node or if this is a reopening of the database after a close() call, update it.\n return db.transaction('rw', '_syncNodes', function () {\n db._syncNodes.where('isMaster').equals(1).count(function (anyMasterNode) {\n if (!anyMasterNode) {\n // There's no master node. Let's take that role then.\n mySyncNode.isMaster = 1;\n }\n // Add our node to DB and start subscribing to events\n db._syncNodes.add(mySyncNode).then(function () {\n Observable.on('latestRevisionIncremented', onLatestRevisionIncremented); // Wakeup when a new revision is available.\n Observable.on('beforeunload', onBeforeUnload);\n Observable.on('suicideNurseCall', onSuicide);\n Observable.on('intercomm', onIntercomm);\n // Start polling for changes and do cleanups:\n pollHandle = setTimeout(poll, LOCAL_POLL);\n });\n });\n }).then(function () {\n cleanup();\n });\n //cleanup();\n //});\n });\n }, true); // True means the on(ready) event will survive a db reopening (db.close() / db.open()).\n\n var handledRevision = 0;\n\n function onLatestRevisionIncremented(dbname, latestRevision) {\n if (dbname === db.name) {\n if (handledRevision >= latestRevision) return; // Make sure to only run once per revision. (Workaround for IE triggering storage event on same window)\n handledRevision = latestRevision;\n Dexie.vip(function () {\n readChanges(latestRevision).catch('DatabaseClosedError', function (e) {\n // Handle database closed error gracefully while reading changes.\n // Don't trigger unhandledrejection\n // Even though we intercept the close() method, it might be called when in the middle of\n // reading changes and then that flow will cancel with DatabaseClosedError.\n });\n });\n }\n }\n\n function readChanges(latestRevision, recursion, wasPartial) {\n // Whenever changes are read, fire db.on(\"changes\") with the array of changes. Eventually, limit the array to 1000 entries or so (an entire database is\n // downloaded from server AFTER we are initiated. For example, if first sync call fails, then after a while we get reconnected. However, that scenario\n // should be handled in case database is totally empty we should fail if sync is not available)\n if (!recursion && readChanges.ongoingOperation) {\n // We are already reading changes. Prohibit a parallell execution of this which would lead to duplicate trigging of 'changes' event.\n // Instead, the callback in toArray() will always check Observable.latestRevision[db.name] to see if it has changed and if so, re-launch readChanges().\n // The caller should get the Promise instance from the ongoing operation so that the then() method will resolve when operation is finished.\n return readChanges.ongoingOperation;\n }\n\n var partial = false;\n var ourSyncNode = mySyncNode; // Because mySyncNode can suddenly be set to null on database close, and worse, can be set to a new value if database is reopened.\n if (!ourSyncNode) {\n return Promise.reject(\"Database closed\");\n }\n var LIMIT = 1000;\n var promise = db._changes.where(\"rev\").above(ourSyncNode.myRevision).limit(LIMIT).toArray(function (changes) {\n if (changes.length > 0) {\n var lastChange = changes[changes.length - 1];\n partial = changes.length === LIMIT;\n db.on('changes').fire(changes, partial);\n ourSyncNode.myRevision = lastChange.rev;\n } else if (wasPartial) {\n // No more changes, BUT since we have triggered on('changes') with partial = true,\n // we HAVE TO trigger changes again with empty list and partial = false\n db.on('changes').fire([], false);\n }\n\n return db.table(\"_syncNodes\").update(ourSyncNode, {\n lastHeartBeat: Date.now(),\n deleteTimeStamp: null, // Reset \"deleteTimeStamp\" flag if it was there.\n myRevision: ourSyncNode.myRevision\n });\n }).then(function (nodeWasUpdated) {\n if (!nodeWasUpdated) {\n // My node has been deleted. We must have been lazy and got removed by another node.\n if (browserIsShuttingDown) {\n throw new Error(\"Browser is shutting down\");\n } else {\n db.close();\n console.error(\"Out of sync\"); // TODO: What to do? Reload the page?\n if (global.location) global.location.reload(true);\n throw new Error(\"Out of sync\"); // Will make current promise reject\n }\n }\n\n // Check if more changes have come since we started reading changes in the first place. If so, relaunch readChanges and let the ongoing promise not\n // resolve until all changes have been read.\n if (partial || Observable.latestRevision[db.name] > ourSyncNode.myRevision) {\n // Either there were more than 1000 changes or additional changes where added while we were reading these changes,\n // In either case, call readChanges() again until we're done.\n return readChanges(Observable.latestRevision[db.name], (recursion || 0) + 1, partial);\n }\n }).finally(function () {\n delete readChanges.ongoingOperation;\n });\n\n if (!recursion) {\n readChanges.ongoingOperation = promise;\n }\n return promise;\n }\n\n function poll() {\n pollHandle = null;\n var currentInstance = mySyncNode.id;\n Dexie.vip(function () {\n // VIP ourselves. Otherwise we might not be able to consume intercomm messages from master node before database has finished opening. This would make DB stall forever. Cannot rely on storage-event since it may not always work in some browsers of different processes.\n readChanges(Observable.latestRevision[db.name]).then(cleanup).then(consumeIntercommMessages).catch('DatabaseClosedError', function (e) {\n // Handle database closed error gracefully while reading changes.\n // Don't signal 'unhandledrejection'.\n // Even though we intercept the close() method, it might be called when in the middle of\n // reading changes and then that flow will cancel with DatabaseClosedError.\n }).finally(function () {\n // Poll again in given interval:\n if (mySyncNode && mySyncNode.id === currentInstance) {\n pollHandle = setTimeout(poll, LOCAL_POLL);\n }\n });\n });\n }\n\n function cleanup() {\n var ourSyncNode = mySyncNode;\n if (!ourSyncNode) return Promise.reject(\"Database closed\");\n return db.transaction('rw', '_syncNodes', '_changes', '_intercomm', function () {\n // Cleanup dead local nodes that has no heartbeat for over a minute\n // Dont do the following:\n //nodes.where(\"lastHeartBeat\").below(Date.now() - NODE_TIMEOUT).and(function (node) { return node.type == \"local\"; }).delete();\n // Because client may have been in hybernate mode and recently woken up. That would lead to deletion of all nodes.\n // Instead, we should mark any old nodes for deletion in a minute or so. If they still dont wakeup after that minute we could consider them dead.\n var weBecameMaster = false;\n db._syncNodes.where(\"lastHeartBeat\").below(Date.now() - NODE_TIMEOUT).and(function (node) {\n return node.type === 'local';\n }).modify(function (node) {\n if (node.deleteTimeStamp && node.deleteTimeStamp < Date.now()) {\n // Delete the node.\n delete this.value;\n // Cleanup localStorage \"deadnode:\" entry for this node (localStorage API was used to wakeup other windows (onstorage event) - an event type missing in indexedDB.)\n localStorage.removeItem('Dexie.Observable/deadnode:' + node.id + '/' + db.name);\n // Check if we are deleting a master node\n if (node.isMaster) {\n // The node we are deleting is master. We must take over that role.\n // OK to call nodes.update(). No need to call Dexie.vip() because nodes is opened in existing transaction!\n db._syncNodes.update(ourSyncNode, { isMaster: 1 });\n weBecameMaster = true;\n }\n // Cleanup intercomm messages destinated to the node being deleted:\n db._intercomm.where(\"destinationNode\").equals(node.id).modify(function (msg) {\n // OK to call intercomm. No need to call Dexie.vip() because intercomm is opened in existing transaction!\n // Delete the message from DB and if someone is waiting for reply, let ourselved answer the request.\n delete this.value;\n if (msg.wantReply) {\n // Message wants a reply, meaning someone must take over its messages when it dies. Let us be that one!\n Dexie.ignoreTransaction(function () {\n consumeMessage(msg);\n });\n }\n });\n } else if (!node.deleteTimeStamp) {\n // Mark the node for deletion\n node.deleteTimeStamp = Date.now() + HIBERNATE_GRACE_PERIOD;\n }\n }).then(function () {\n // Cleanup old revisions that no node is interested of.\n Observable.deleteOldChanges(db);\n return db.on(\"cleanup\").fire(weBecameMaster);\n });\n });\n }\n\n function onBeforeUnload(event) {\n // Mark our own sync node for deletion.\n if (!mySyncNode) return;\n browserIsShuttingDown = true;\n mySyncNode.deleteTimeStamp = 1; // One millisecond after 1970. Makes it occur in the past but still keeps it truthy.\n mySyncNode.lastHeartBeat = 0;\n db._syncNodes.put(mySyncNode); // This async operation may be cancelled since the browser is closing down now.\n Observable.wereTheOneDying = true; // If other nodes in same window wakes up by this call, make sure they dont start taking over mastership and stuff...\n // Inform other windows that we're gone, so that they may take over our role if needed. Setting localStorage item below will trigger Observable.onStorage, which will trigger onSuicie() below:\n localStorage.setItem('Dexie.Observable/deadnode:' + mySyncNode.id.toString() + '/' + db.name, \"dead\"); // In IE, this will also wakeup our own window. However, that is doublechecked in nursecall subscriber below.\n }\n\n function onSuicide(dbname, nodeID) {\n if (dbname === db.name && !Observable.wereTheOneDying) {\n // Make sure it's dead indeed. Second bullet. Why? Because it has marked itself for deletion in the onbeforeunload event, which is fired just before window dies.\n // It's own call to put() may have been cancelled.\n // Note also that in IE, this event may be called twice, but that doesnt harm!\n Dexie.vip(function () {\n db._syncNodes.update(nodeID, { deleteTimeStamp: 1, lastHeartBeat: 0 }).then(cleanup);\n });\n }\n }\n\n //\n // Intercommunication between nodes\n //\n // Enable inter-process communication between browser windows\n\n var requestsWaitingForReply = {};\n\n db.sendMessage = function (type, message, destinationNode, options) {\n /// Type of message\n /// Message to send\n /// ID of destination node\n /// {wantReply: Boolean, isFailure: Boolean, requestId: Number}. If wantReply, the returned promise will complete with the reply from remote. Otherwise it will complete when message has been successfully sent.\n if (!mySyncNode) return Promise.reject(\"Database closed\");\n options = options || {};\n var msg = { message: message, destinationNode: destinationNode, sender: mySyncNode.id, type: type };\n Dexie.extend(msg, options); // wantReply: wantReply, success: !isFailure, requestId: ...\n var tables = [\"_intercomm\"];\n if (options.wantReply) tables.push(\"_syncNodes\"); // If caller wants a reply, include \"_syncNodes\" in transaction to check that there's a reciever there. Otherwise, new master will get it.\n return db.transaction('rw?', tables, function () {\n if (options.wantReply) {\n // Check that there is a reciever there to take the request.\n return db._syncNodes.where('id').equals(destinationNode).count(function (recieverAlive) {\n if (recieverAlive) return addMessage(msg);else return db._syncNodes.where('isMaster').above(0).first(function (masterNode) {\n msg.destinationNode = masterNode.id;\n return addMessage(msg);\n });\n });\n } else {\n addMessage(msg); // No need to return Promise. Caller dont need a reply.\n }\n\n function addMessage(msg) {\n return db._intercomm.add(msg).then(function (messageId) {\n localStorage.setItem(\"Dexie.Observable/intercomm/\" + db.name, messageId.toString());\n Dexie.ignoreTransaction(function () {\n Observable.on.intercomm.fire(db.name);\n });\n if (options.wantReply) {\n return new Promise(function (resolve, reject) {\n requestsWaitingForReply[messageId.toString()] = { resolve: resolve, reject: reject };\n });\n }\n });\n }\n });\n };\n\n db.broadcastMessage = function (type, message, bIncludeSelf) {\n if (!mySyncNode) return Promise.reject(\"Database closed\");\n var mySyncNodeId = mySyncNode.id;\n db._syncNodes.each(function (node) {\n if (node.type === 'local' && (bIncludeSelf || node.id !== mySyncNodeId)) {\n db.sendMessage(type, message, node.id);\n }\n });\n };\n\n db.observable = {};\n db.observable.SyncNode = SyncNode;\n\n function consumeIntercommMessages() {\n // Check if we got messages:\n if (!mySyncNode) return Promise.reject(\"Database closed\");\n return db.table('_intercomm').where(\"destinationNode\").equals(mySyncNode.id).modify(function (msg) {\n // For each message, fire the event and remove message.\n delete this.value;\n Dexie.ignoreTransaction(function () {\n consumeMessage(msg);\n });\n });\n }\n\n function consumeMessage(msg) {\n if (msg.type === 'response') {\n // This is a response. Lookup pending request and fulfill it's promise.\n var request = requestsWaitingForReply[msg.requestId.toString()];\n if (request) {\n if (msg.isFailure) {\n request.reject(msg.message.error);\n } else {\n request.resolve(msg.message.result);\n }\n delete requestsWaitingForReply[msg.requestId.toString()];\n }\n } else {\n // This is a message or request. Fire the event and add an API for the subscriber to use if reply is requested\n msg.resolve = function (result) {\n db.sendMessage('response', { result: result }, msg.sender, { requestId: msg.id });\n };\n msg.reject = function (error) {\n db.sendMessage('response', { error: error.toString() }, msg.sender, { isFailure: true, requestId: msg.id });\n };\n var message = msg.message;\n delete msg.message;\n Dexie.extend(msg, message);\n db.on.message.fire(msg);\n }\n }\n\n function onIntercomm(dbname) {\n // When storage event trigger us to check\n if (dbname === db.name) {\n consumeIntercommMessages();\n }\n }\n}\n\n//\n// Help functions\n//\n\nfunction nop() {};\n\nfunction promisableChain(f1, f2) {\n if (f1 === nop) return f2;\n return function () {\n var res = f1.apply(this, arguments);\n if (res && typeof res.then === 'function') {\n var thiz = this,\n args = arguments;\n return res.then(function () {\n return f2.apply(thiz, args);\n });\n }\n return f2.apply(this, arguments);\n };\n}\n\n//\n// Static properties and methods\n// \n\nObservable.latestRevision = {}; // Latest revision PER DATABASE. Example: Observable.latestRevision.FriendsDB = 37;\nObservable.on = Dexie.Events(null, \"latestRevisionIncremented\", \"suicideNurseCall\", \"intercomm\", \"beforeunload\"); // fire(dbname, value);\nObservable.createUUID = function () {\n // Decent solution from http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript\n var d = Date.now();\n var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {\n var r = (d + Math.random() * 16) % 16 | 0;\n d = Math.floor(d / 16);\n return (c === 'x' ? r : r & 0x7 | 0x8).toString(16);\n });\n return uuid;\n};\n\nObservable.deleteOldChanges = function (db) {\n db._syncNodes.orderBy(\"myRevision\").first(function (oldestNode) {\n var timeout = Date.now() + 300,\n timedout = false;\n db._changes.where(\"rev\").below(oldestNode.myRevision).until(function () {\n return timedout = Date.now() > timeout;\n }).delete().then(function () {\n // If not done garbage collecting, reschedule a continuation of it until done.\n if (timedout) setTimeout(function () {\n Observable.deleteOldChanges(db);\n }, 10);\n });\n });\n};\n\nObservable._onStorage = function onStorage(event) {\n // We use the onstorage event to trigger onLatestRevisionIncremented since we will wake up when other windows modify the DB as well!\n if (event.key.indexOf(\"Dexie.Observable/\") === 0) {\n // For example \"Dexie.Observable/latestRevision/FriendsDB\"\n var parts = event.key.split('/');\n var prop = parts[1];\n var dbname = parts[2];\n if (prop === 'latestRevision') {\n var rev = parseInt(event.newValue, 10);\n if (!isNaN(rev) && rev > Observable.latestRevision[dbname]) {\n Observable.latestRevision[dbname] = rev;\n Dexie.ignoreTransaction(function () {\n Observable.on('latestRevisionIncremented').fire(dbname, rev);\n });\n }\n } else if (prop.indexOf(\"deadnode:\") === 0) {\n var nodeID = parseInt(prop.split(':')[1], 10);\n if (event.newValue) {\n Observable.on.suicideNurseCall.fire(dbname, nodeID);\n }\n } else if (prop === 'intercomm') {\n if (event.newValue) {\n Observable.on.intercomm.fire(dbname);\n }\n }\n }\n};\n\nObservable._onBeforeUnload = function () {\n Observable.on.beforeunload.fire();\n};\n\nObservable.localStorageImpl = global.localStorage;\n\n//\n// Map window events to static events in Dexie.Observable:\n//\nif (global.addEventListener) {\n global.addEventListener(\"storage\", Observable._onStorage);\n global.addEventListener(\"beforeunload\", Observable._onBeforeUnload);\n}\n// Register addon:\nDexie.Observable = Observable;\nDexie.addons.push(Observable);"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/addons/Dexie.Observable/dist/dexie-observable.min.js b/addons/Dexie.Observable/dist/dexie-observable.min.js
new file mode 100644
index 000000000..edeb924cd
--- /dev/null
+++ b/addons/Dexie.Observable/dist/dexie-observable.min.js
@@ -0,0 +1,2 @@
+!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n(require("dexie")):"function"==typeof define&&define.amd?define(["dexie"],n):(e.Dexie=e.Dexie||{},e.Dexie.Observable=n(e.Dexie))}(this,function(e){"use strict";function n(l){function u(t){n.latestRevision[l.name]=t)return;I=t,e.vip(function(){m(t).catch("DatabaseClosedError",function(e){})})}}function m(e,t,i){if(!t&&m.ongoingOperation)return m.ongoingOperation;var r=!1,a=j;if(!a)return s.reject("Database closed");var u=1e3,d=l._changes.where("rev").above(a.myRevision).limit(u).toArray(function(e){if(e.length>0){var n=e[e.length-1];r=e.length===u,l.on("changes").fire(e,r),a.myRevision=n.rev}else i&&l.on("changes").fire([],!1);return l.table("_syncNodes").update(a,{lastHeartBeat:Date.now(),deleteTimeStamp:null,myRevision:a.myRevision})}).then(function(e){if(!e)throw c?new Error("Browser is shutting down"):(l.close(),console.error("Out of sync"),o.location&&o.location.reload(!0),new Error("Out of sync"));if(r||n.latestRevision[l.name]>a.myRevision)return m(n.latestRevision[l.name],(t||0)+1,r)}).finally(function(){delete m.ongoingOperation});return t||(m.ongoingOperation=d),d}function v(){B=null;var t=j.id;e.vip(function(){m(n.latestRevision[l.name]).then(y).then(b).catch("DatabaseClosedError",function(e){}).finally(function(){j&&j.id===t&&(B=setTimeout(v,N))})})}function y(){var t=j;return t?l.transaction("rw","_syncNodes","_changes","_intercomm",function(){var i=!1;l._syncNodes.where("lastHeartBeat").below(Date.now()-x).and(function(e){return"local"===e.type}).modify(function(n){n.deleteTimeStamp&&n.deleteTimeStampi}).delete().then(function(){o&&setTimeout(function(){n.deleteOldChanges(e)},10)})})},n._onStorage=function(t){if(0===t.key.indexOf("Dexie.Observable/")){var i=t.key.split("/"),o=i[1],r=i[2];if("latestRevision"===o){var a=parseInt(t.newValue,10);!isNaN(a)&&a>n.latestRevision[r]&&(n.latestRevision[r]=a,e.ignoreTransaction(function(){n.on("latestRevisionIncremented").fire(r,a)}))}else if(0===o.indexOf("deadnode:")){var s=parseInt(o.split(":")[1],10);t.newValue&&n.on.suicideNurseCall.fire(r,s)}else"intercomm"===o&&t.newValue&&n.on.intercomm.fire(r)}},n._onBeforeUnload=function(){n.on.beforeunload.fire()},n.localStorageImpl=o.localStorage,o.addEventListener&&(o.addEventListener("storage",n._onStorage),o.addEventListener("beforeunload",n._onBeforeUnload)),e.Observable=n,e.addons.push(n),n});
+//# sourceMappingURL=dexie-observable.min.js.map
\ No newline at end of file
diff --git a/addons/Dexie.Observable/dist/dexie-observable.min.js.map b/addons/Dexie.Observable/dist/dexie-observable.min.js.map
new file mode 100644
index 000000000..bd9a3cee6
--- /dev/null
+++ b/addons/Dexie.Observable/dist/dexie-observable.min.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["../tools/tmp/src/Dexie.Observable.js"],"names":["Observable","db","wakeupObservers","lastWrittenRevision","latestRevision","name","Dexie","ignoreTransaction","on","fire","localStorage","setItem","crudMonitor","table","tableName","hook","subscribe","primKey","obj","trans","rv","undefined","schema","uuid","createUUID","keyPath","setByKeyPath","change","source","key","type","CREATE","promise","_changes","add","then","rev","_lastWrittenRevision","Math","max","this","onsuccess","resultKey","_then","put","onerror","err","delete","mods","oldObj","modsWithoutUndefined","anythingChanged","newObj","deepClone","propPath","mod","delByKeyPath","currentValue","getByKeyPath","JSON","stringify","UPDATE","DELETE","onLatestRevisionIncremented","dbname","handledRevision","vip","readChanges","catch","e","recursion","wasPartial","ongoingOperation","partial","ourSyncNode","mySyncNode","Promise","reject","LIMIT","where","above","myRevision","limit","toArray","changes","length","lastChange","update","lastHeartBeat","Date","now","deleteTimeStamp","nodeWasUpdated","browserIsShuttingDown","Error","close","console","error","global","location","reload","finally","poll","pollHandle","currentInstance","id","cleanup","consumeIntercommMessages","setTimeout","LOCAL_POLL","transaction","weBecameMaster","_syncNodes","below","NODE_TIMEOUT","and","node","modify","value","removeItem","isMaster","_intercomm","equals","msg","wantReply","consumeMessage","HIBERNATE_GRACE_PERIOD","deleteOldChanges","onBeforeUnload","event","wereTheOneDying","toString","onSuicide","nodeID","request","requestsWaitingForReply","requestId","isFailure","message","resolve","result","sendMessage","sender","extend","onIntercomm","localStorageImpl","SyncNode","defineClass","Number","String","url","syncProtocol","syncContext","syncOptions","Object","connected","status","appliedRemoteRevision","remoteBaseRevisions","local","remote","dbUploadState","tablesToUpload","currentTable","currentKey","localBaseRevision","defineProperty","get","fake","version","stores","_uncommittedChanges","mapToClass","DatabaseChange","Version","prototype","_parseStoresSpec","override","origFunc","dbSchema","call","keys","forEach","indexOf","substr","observable","_tableFactory","origCreateTable","mode","tableSchema","transactionPromiseFactory","apply","arguments","_transPromiseFactory","addEventType","promisableChain","nop","_createTransaction","storenames","dbschema","parent","dynamicallyOpened","addChanges","some","storeName","slice","push","rootTransaction","findRootTransaction","timeoutHandle","clearTimeout","origClose","unsubscribe","suicideNurseCall","origDelete","orderBy","last","latestRevisionIncremented","count","anyMasterNode","destinationNode","options","tables","addMessage","messageId","intercomm","recieverAlive","first","masterNode","broadcastMessage","bIncludeSelf","mySyncNodeId","each","f1","f2","res","thiz","args","self","Events","d","replace","c","r","random","floor","oldestNode","timeout","timedout","until","_onStorage","parts","split","prop","parseInt","newValue","isNaN","_onBeforeUnload","beforeunload","addEventListener","addons"],"mappings":"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","file":"dist/dexie-observable.min.js.map","sourcesContent":["/// \n\n/**\r\n * Dexie.Observable.js\r\n * ===================\r\n * Dexie addon for observing database changes not just on local db instance but also on other instances and windows.\r\n *\r\n * version: {version} Alpha, {date}\r\n *\r\n * Disclaimber: This addon is in alpha status meaning that\r\n * its API and behavior may change.\r\n *\r\n */\nimport Dexie from 'dexie';\n\nvar global = self;\n\n/** class DatabaseChange\r\n *\r\n * Object contained by the _changes table.\r\n */\nvar DatabaseChange = Dexie.defineClass({\n rev: Number, // Auto-incremented primary key\n source: String, // Optional source creating the change. Set if transaction.source was set when doing the operation.\n table: String, // Table name\n key: Object, // Primary key. Any type.\n type: Number, // 1 = CREATE, 2 = UPDATE, 3 = DELETE\n obj: Object, // CREATE: obj contains the object created.\n mods: Object, // UPDATE: mods contains the modifications made to the object.\n oldObj: Object // DELETE: oldObj contains the object deleted. UPDATE: oldObj contains the old object before updates applied.\n});\n\n// Import some usable helper functions\nvar override = Dexie.override;\nvar Promise = Dexie.Promise;\nvar browserIsShuttingDown = false;\n\nexport default function Observable(db) {\n /// \n /// Extension to Dexie providing Syncronization capabilities to Dexie.\n /// \n /// \n\n var NODE_TIMEOUT = 20000,\n // 20 seconds before local db instances are timed out. This is so that old changes can be deleted when not needed and to garbage collect old _syncNodes objects.\n HIBERNATE_GRACE_PERIOD = 20000,\n // 20 seconds\n // LOCAL_POLL: The time to wait before polling local db for changes and cleaning up old nodes. \n // Polling for changes is a fallback only needed in certain circomstances (when the onstorage event doesnt reach all listeners - when different browser windows doesnt share the same process)\n LOCAL_POLL = 2000,\n // 1 second. In real-world there will be this value + the time it takes to poll().\n CREATE = 1,\n UPDATE = 2,\n DELETE = 3;\n\n var localStorage = Observable.localStorageImpl;\n\n /** class SyncNode\r\n *\r\n * Object contained in the _syncNodes table.\r\n */\n var SyncNode = Dexie.defineClass({\n //id: Number,\n myRevision: Number,\n type: String, // \"local\" or \"remote\"\n lastHeartBeat: Number,\n deleteTimeStamp: Number, // In case lastHeartBeat is too old, a value of now + HIBERNATE_GRACE_PERIOD will be set here. If reached before node wakes up, node will be deleted.\n url: String, // Only applicable for \"remote\" nodes. Only used in Dexie.Syncable.\n isMaster: Number, // 1 if true. Not using Boolean because it's not possible to index Booleans in IE implementation of IDB.\n\n // Below properties should be extended in Dexie.Syncable. Not here. They apply to remote nodes only (type == \"remote\"):\n syncProtocol: String, // Tells which implementation of ISyncProtocol to use for remote syncing. \n syncContext: null,\n syncOptions: Object,\n connected: false, // FIXTHIS: Remove! Replace with status.\n status: Number,\n appliedRemoteRevision: null,\n remoteBaseRevisions: [{ local: Number, remote: null }],\n dbUploadState: {\n tablesToUpload: [String],\n currentTable: String,\n currentKey: null,\n localBaseRevision: Number\n }\n });\n\n var mySyncNode = null;\n\n // Allow other addons to access the local sync node. May be needed by Dexie.Syncable.\n Object.defineProperty(db, \"_localSyncNode\", {\n get: function () {\n return mySyncNode;\n }\n });\n\n var pollHandle = null;\n\n if (Dexie.fake) {\n // This code will never run.\n // It's here just to enable auto-complete in visual studio - helps a lot when writing code.\n db.version(1).stores({\n _syncNodes: \"++id,myRevision,lastHeartBeat\",\n _changes: \"++rev\",\n _intercomm: \"++id,destinationNode\",\n _uncommittedChanges: \"++id,node\"\n });\n db._syncNodes.mapToClass(SyncNode);\n db._changes.mapToClass(DatabaseChange);\n mySyncNode = new SyncNode({\n myRevision: 0,\n type: \"local\",\n lastHeartBeat: Date.now(),\n deleteTimeStamp: null\n });\n }\n\n //\n // Override parsing the stores to add \"_changes\" and \"_syncNodes\" tables.\n //\n db.Version.prototype._parseStoresSpec = override(db.Version.prototype._parseStoresSpec, function (origFunc) {\n return function (stores, dbSchema) {\n // Create the _changes and _syncNodes tables\n stores[\"_changes\"] = \"++rev\";\n stores[\"_syncNodes\"] = \"++id,myRevision,lastHeartBeat,url,isMaster,type,status\";\n stores[\"_intercomm\"] = \"++id,destinationNode\";\n stores[\"_uncommittedChanges\"] = \"++id,node\"; // For remote syncing when server returns a partial result.\n // Call default implementation. Will populate the dbSchema structures.\n origFunc.call(this, stores, dbSchema);\n // Allow UUID primary keys using $$ prefix on primary key or indexes\n Object.keys(dbSchema).forEach(function (tableName) {\n var schema = dbSchema[tableName];\n if (schema.primKey.name.indexOf('$$') === 0) {\n schema.primKey.uuid = true;\n schema.primKey.name = schema.primKey.name.substr(2);\n schema.primKey.keyPath = schema.primKey.keyPath.substr(2);\n }\n });\n // Now mark all observable tables\n Object.keys(dbSchema).forEach(function (tableName) {\n // Marked observable tables with \"observable\" in their TableSchema.\n if (tableName.indexOf('_') !== 0 && tableName.indexOf('$') !== 0) {\n dbSchema[tableName].observable = true;\n }\n });\n };\n });\n\n //\n // Make sure to subscribe to \"creating\", \"updating\" and \"deleting\" hooks for all observable tables that were created in the stores() method.\n //\n db._tableFactory = override(db._tableFactory, function (origCreateTable) {\n return function createTable(mode, tableSchema, transactionPromiseFactory) {\n var table = origCreateTable.apply(this, arguments);\n if (table.schema.observable && transactionPromiseFactory === db._transPromiseFactory) {\n // Only crudMonitor when creating \n crudMonitor(table);\n }\n if (table.name === \"_syncNodes\" && transactionPromiseFactory === db._transPromiseFactory) {\n table.mapToClass(SyncNode);\n }\n return table;\n };\n });\n\n // changes event on db:\n db.on.addEventType({\n changes: 'asap',\n cleanup: [promisableChain, nop], // fire (nodesTable, changesTable, trans). Hook called when cleaning up nodes. Subscribers may return a Promise to to more stuff. May do additional stuff if local sync node is master.\n message: 'asap'\n });\n\n //\n // Overide transaction creation to always include the \"_changes\" store when any observable store is involved.\n //\n db._createTransaction = override(db._createTransaction, function (origFunc) {\n return function (mode, storenames, dbschema, parent) {\n if (db.dynamicallyOpened()) return origFunc.apply(this, arguments); // Don't observe dynamically opened databases.\n var addChanges = false;\n if (mode === 'readwrite' && storenames.some(function (storeName) {\n return dbschema[storeName] && dbschema[storeName].observable;\n })) {\n // At least one included store is a observable store. Make sure to also include the _changes store.\n addChanges = true;\n storenames = storenames.slice(0); // Clone\n if (storenames.indexOf(\"_changes\") === -1) storenames.push(\"_changes\"); // Otherwise, firefox will hang... (I've reported the bug to Mozilla@Bugzilla)\n }\n // Call original db._createTransaction()\n var trans = origFunc.call(this, mode, storenames, dbschema, parent);\n // If this transaction is bound to any observable table, make sure to add changes when transaction completes.\n if (addChanges) {\n trans._lastWrittenRevision = 0;\n trans.on('complete', function () {\n if (trans._lastWrittenRevision) {\n // Changes were written in this transaction.\n if (!parent) {\n // This is root-level transaction, i.e. a physical commit has happened.\n // Delay-trigger a wakeup call:\n if (wakeupObservers.timeoutHandle) clearTimeout(wakeupObservers.timeoutHandle);\n wakeupObservers.timeoutHandle = setTimeout(function () {\n delete wakeupObservers.timeoutHandle;\n wakeupObservers(trans._lastWrittenRevision);\n }, 25);\n } else {\n // This is just a virtual commit of a sub transaction.\n // Wait with waking up observers until root transaction has committed.\n // Make sure to mark root transaction so that it will wakeup observers upon commit.\n var rootTransaction = function findRootTransaction(trans) {\n return trans.parent ? findRootTransaction(trans.parent) : trans;\n }(parent);\n rootTransaction._lastWrittenRevision = Math.max(trans._lastWrittenRevision, rootTransaction.lastWrittenRevision || 0);\n }\n }\n });\n // Derive \"source\" property from parent transaction by default\n if (trans.parent && trans.parent.source) trans.source = trans.parent.source;\n }\n return trans;\n };\n });\n\n // If Observable.latestRevsion[db.name] is undefined, set it to 0 so that comparing against it always works.\n // You might think that it will always be undefined before this call, but in case another Dexie instance in the same\n // window with the same database name has been created already, this static property will already be set correctly.\n Observable.latestRevision[db.name] = Observable.latestRevision[db.name] || 0;\n\n function wakeupObservers(lastWrittenRevision) {\n // Make sure Observable.latestRevision[db.name] is still below our value, now when some time has elapsed and other db instances in same window possibly could have made changes too.\n if (Observable.latestRevision[db.name] < lastWrittenRevision) {\n // Set the static property lastRevision[db.name] to the revision of the last written change.\n Observable.latestRevision[db.name] = lastWrittenRevision;\n // Wakeup ourselves, and any other db instances on this window:\n Dexie.ignoreTransaction(function () {\n Observable.on('latestRevisionIncremented').fire(db.name, lastWrittenRevision);\n });\n // Observable.on.latestRevisionIncremented will only wakeup db's in current window.\n // We need a storage event to wakeup other windwos.\n // Since indexedDB lacks storage events, let's use the storage event from WebStorage just for\n // the purpose to wakeup db instances in other windows.\n localStorage.setItem('Dexie.Observable/latestRevision/' + db.name, lastWrittenRevision); // In IE, this will also wakeup our own window. However, onLatestRevisionIncremented will work around this by only running once per revision id.\n }\n }\n\n db.close = override(db.close, function (origClose) {\n return function () {\n if (db.dynamicallyOpened()) return origClose.apply(this, arguments); // Don't observe dynamically opened databases.\n // Teardown our framework.\n if (wakeupObservers.timeoutHandle) {\n clearTimeout(wakeupObservers.timeoutHandle);\n delete wakeupObservers.timeoutHandle;\n }\n Observable.on('latestRevisionIncremented').unsubscribe(onLatestRevisionIncremented);\n Observable.on('suicideNurseCall').unsubscribe(onSuicide);\n Observable.on('intercomm').unsubscribe(onIntercomm);\n Observable.on('beforeunload').unsubscribe(onBeforeUnload);\n // Inform other db instances in same window that we are dying:\n if (mySyncNode && mySyncNode.id) {\n Observable.on.suicideNurseCall.fire(db.name, mySyncNode.id);\n // Inform other windows as well:\n localStorage.setItem('Dexie.Observable/deadnode:' + mySyncNode.id.toString() + '/' + db.name, \"dead\"); // In IE, this will also wakeup our own window. cleanup() may trigger twice per other db instance. But that doesnt to anything.\n mySyncNode.deleteTimeStamp = 1; // One millisecond after 1970. Makes it occur in the past but still keeps it truthy.\n mySyncNode.lastHeartBeat = 0;\n db._syncNodes.put(mySyncNode); // This async operation may be cancelled since the browser is closing down now.\n mySyncNode = null;\n }\n\n if (pollHandle) clearTimeout(pollHandle);\n pollHandle = null;\n return origClose.apply(this, arguments);\n };\n });\n\n // Override Dexie.delete() in order to delete Observable.latestRevision[db.name].\n db.delete = override(db.delete, function (origDelete) {\n return function () {\n return origDelete.apply(this, arguments).then(function (result) {\n // Reset Observable.latestRevision[db.name]\n Observable.latestRevision[db.name] = 0;\n return result;\n });\n };\n });\n\n //\n // The Creating/Updating/Deleting hook will make sure any change is stored to the changes table\n //\n function crudMonitor(table) {\n /// \n var tableName = table.name;\n\n table.hook('creating').subscribe(function (primKey, obj, trans) {\n /// \n var rv = undefined;\n if (primKey === undefined && table.schema.primKey.uuid) {\n primKey = rv = Observable.createUUID();\n if (table.schema.primKey.keyPath) {\n Dexie.setByKeyPath(obj, table.schema.primKey.keyPath, primKey);\n }\n }\n\n var change = {\n source: trans.source || null, // If a \"source\" is marked on the transaction, store it. Useful for observers that want to ignore their own changes.\n table: tableName,\n key: primKey === undefined ? null : primKey,\n type: CREATE,\n obj: obj\n };\n\n var promise = db._changes.add(change).then(function (rev) {\n trans._lastWrittenRevision = Math.max(trans._lastWrittenRevision, rev);\n return rev;\n });\n\n // Wait for onsuccess so that we have the primKey if it is auto-incremented and update the change item if so.\n this.onsuccess = function (resultKey) {\n if (primKey != resultKey) promise._then(function () {\n change.key = resultKey;\n db._changes.put(change);\n });\n };\n this.onerror = function (err) {\n // If the main operation fails, make sure to regret the change\n promise._then(function (rev) {\n // Will only happen if app code catches the main operation error to prohibit transaction from aborting.\n db._changes.delete(rev);\n });\n };\n\n return rv;\n });\n\n table.hook('updating').subscribe(function (mods, primKey, oldObj, trans) {\n /// \n // mods may contain property paths with undefined as value if the property\n // is being deleted. Since we cannot persist undefined we need to act\n // like those changes is setting the value to null instead.\n var modsWithoutUndefined = {};\n // As of current Dexie version (1.0.3) hook may be called even if it wouldnt really change.\n // Therefore we may do that kind of optimization here - to not add change entries if\n // there's nothing to change.\n var anythingChanged = false;\n var newObj = Dexie.deepClone(oldObj);\n for (var propPath in mods) {\n var mod = mods[propPath];\n if (typeof mod === 'undefined') {\n Dexie.delByKeyPath(newObj, propPath);\n modsWithoutUndefined[propPath] = null; // Null is as close we could come to deleting a property when not allowing undefined.\n anythingChanged = true;\n } else {\n var currentValue = Dexie.getByKeyPath(oldObj, propPath);\n if (mod !== currentValue && JSON.stringify(mod) !== JSON.stringify(currentValue)) {\n Dexie.setByKeyPath(newObj, propPath, mod);\n modsWithoutUndefined[propPath] = mod;\n anythingChanged = true;\n }\n }\n }\n if (anythingChanged) {\n var change = {\n source: trans.source || null, // If a \"source\" is marked on the transaction, store it. Useful for observers that want to ignore their own changes.\n table: tableName,\n key: primKey,\n type: UPDATE,\n mods: modsWithoutUndefined,\n oldObj: oldObj,\n obj: newObj\n };\n var promise = db._changes.add(change); // Just so we get the correct revision order of the update...\n this.onsuccess = function () {\n promise._then(function (rev) {\n trans._lastWrittenRevision = Math.max(trans._lastWrittenRevision, rev);\n });\n };\n this.onerror = function (err) {\n // If the main operation fails, make sure to regret the change.\n promise._then(function (rev) {\n // Will only happen if app code catches the main operation error to prohibit transaction from aborting.\n db._changes.delete(rev);\n });\n };\n }\n });\n\n table.hook('deleting').subscribe(function (primKey, obj, trans) {\n /// \n var promise = db._changes.add({\n source: trans.source || null, // If a \"source\" is marked on the transaction, store it. Useful for observers that want to ignore their own changes.\n table: tableName,\n key: primKey,\n type: DELETE,\n oldObj: obj\n }).then(function (rev) {\n trans._lastWrittenRevision = Math.max(trans._lastWrittenRevision, rev);\n return rev;\n });\n this.onerror = function () {\n // If the main operation fails, make sure to regret the change.\n // Using _then because if promise is already fullfilled, the standard then() would\n // do setTimeout() and we would loose the transaction.\n promise._then(function (rev) {\n // Will only happen if app code catches the main operation error to prohibit transaction from aborting.\n db._changes.delete(rev);\n });\n };\n });\n }\n\n // When db opens, make sure to start monitor any changes before other db operations will start.\n db.on(\"ready\", function startObserving() {\n if (db.dynamicallyOpened()) return db; // Don't observe dynamically opened databases.\n return db.table(\"_changes\").orderBy(\"rev\").last(function (lastChange) {\n // Since startObserving() is called before database open() method, this will be the first database operation enqueued to db.\n // Therefore we know that the retrieved value will be This query will\n var latestRevision = lastChange ? lastChange.rev : 0;\n mySyncNode = new SyncNode({\n myRevision: latestRevision,\n type: \"local\",\n lastHeartBeat: Date.now(),\n deleteTimeStamp: null,\n isMaster: 0\n });\n if (Observable.latestRevision[db.name] < latestRevision) {\n // Side track . For correctness whenever setting Observable.latestRevision[db.name] we must make sure the event is fired if increased:\n // There are other db instances in same window that hasnt yet been informed about a new revision\n Observable.latestRevision[db.name] = latestRevision;\n Dexie.ignoreTransaction(function () {\n Observable.on.latestRevisionIncremented.fire(latestRevision);\n });\n }\n // Add new sync node or if this is a reopening of the database after a close() call, update it.\n return db.transaction('rw', '_syncNodes', function () {\n db._syncNodes.where('isMaster').equals(1).count(function (anyMasterNode) {\n if (!anyMasterNode) {\n // There's no master node. Let's take that role then.\n mySyncNode.isMaster = 1;\n }\n // Add our node to DB and start subscribing to events\n db._syncNodes.add(mySyncNode).then(function () {\n Observable.on('latestRevisionIncremented', onLatestRevisionIncremented); // Wakeup when a new revision is available.\n Observable.on('beforeunload', onBeforeUnload);\n Observable.on('suicideNurseCall', onSuicide);\n Observable.on('intercomm', onIntercomm);\n // Start polling for changes and do cleanups:\n pollHandle = setTimeout(poll, LOCAL_POLL);\n });\n });\n }).then(function () {\n cleanup();\n });\n //cleanup();\n //});\n });\n }, true); // True means the on(ready) event will survive a db reopening (db.close() / db.open()).\n\n var handledRevision = 0;\n\n function onLatestRevisionIncremented(dbname, latestRevision) {\n if (dbname === db.name) {\n if (handledRevision >= latestRevision) return; // Make sure to only run once per revision. (Workaround for IE triggering storage event on same window)\n handledRevision = latestRevision;\n Dexie.vip(function () {\n readChanges(latestRevision).catch('DatabaseClosedError', function (e) {\n // Handle database closed error gracefully while reading changes.\n // Don't trigger unhandledrejection\n // Even though we intercept the close() method, it might be called when in the middle of\n // reading changes and then that flow will cancel with DatabaseClosedError.\n });\n });\n }\n }\n\n function readChanges(latestRevision, recursion, wasPartial) {\n // Whenever changes are read, fire db.on(\"changes\") with the array of changes. Eventually, limit the array to 1000 entries or so (an entire database is\n // downloaded from server AFTER we are initiated. For example, if first sync call fails, then after a while we get reconnected. However, that scenario\n // should be handled in case database is totally empty we should fail if sync is not available)\n if (!recursion && readChanges.ongoingOperation) {\n // We are already reading changes. Prohibit a parallell execution of this which would lead to duplicate trigging of 'changes' event.\n // Instead, the callback in toArray() will always check Observable.latestRevision[db.name] to see if it has changed and if so, re-launch readChanges().\n // The caller should get the Promise instance from the ongoing operation so that the then() method will resolve when operation is finished.\n return readChanges.ongoingOperation;\n }\n\n var partial = false;\n var ourSyncNode = mySyncNode; // Because mySyncNode can suddenly be set to null on database close, and worse, can be set to a new value if database is reopened.\n if (!ourSyncNode) {\n return Promise.reject(\"Database closed\");\n }\n var LIMIT = 1000;\n var promise = db._changes.where(\"rev\").above(ourSyncNode.myRevision).limit(LIMIT).toArray(function (changes) {\n if (changes.length > 0) {\n var lastChange = changes[changes.length - 1];\n partial = changes.length === LIMIT;\n db.on('changes').fire(changes, partial);\n ourSyncNode.myRevision = lastChange.rev;\n } else if (wasPartial) {\n // No more changes, BUT since we have triggered on('changes') with partial = true,\n // we HAVE TO trigger changes again with empty list and partial = false\n db.on('changes').fire([], false);\n }\n\n return db.table(\"_syncNodes\").update(ourSyncNode, {\n lastHeartBeat: Date.now(),\n deleteTimeStamp: null, // Reset \"deleteTimeStamp\" flag if it was there.\n myRevision: ourSyncNode.myRevision\n });\n }).then(function (nodeWasUpdated) {\n if (!nodeWasUpdated) {\n // My node has been deleted. We must have been lazy and got removed by another node.\n if (browserIsShuttingDown) {\n throw new Error(\"Browser is shutting down\");\n } else {\n db.close();\n console.error(\"Out of sync\"); // TODO: What to do? Reload the page?\n if (global.location) global.location.reload(true);\n throw new Error(\"Out of sync\"); // Will make current promise reject\n }\n }\n\n // Check if more changes have come since we started reading changes in the first place. If so, relaunch readChanges and let the ongoing promise not\n // resolve until all changes have been read.\n if (partial || Observable.latestRevision[db.name] > ourSyncNode.myRevision) {\n // Either there were more than 1000 changes or additional changes where added while we were reading these changes,\n // In either case, call readChanges() again until we're done.\n return readChanges(Observable.latestRevision[db.name], (recursion || 0) + 1, partial);\n }\n }).finally(function () {\n delete readChanges.ongoingOperation;\n });\n\n if (!recursion) {\n readChanges.ongoingOperation = promise;\n }\n return promise;\n }\n\n function poll() {\n pollHandle = null;\n var currentInstance = mySyncNode.id;\n Dexie.vip(function () {\n // VIP ourselves. Otherwise we might not be able to consume intercomm messages from master node before database has finished opening. This would make DB stall forever. Cannot rely on storage-event since it may not always work in some browsers of different processes.\n readChanges(Observable.latestRevision[db.name]).then(cleanup).then(consumeIntercommMessages).catch('DatabaseClosedError', function (e) {\n // Handle database closed error gracefully while reading changes.\n // Don't signal 'unhandledrejection'.\n // Even though we intercept the close() method, it might be called when in the middle of\n // reading changes and then that flow will cancel with DatabaseClosedError.\n }).finally(function () {\n // Poll again in given interval:\n if (mySyncNode && mySyncNode.id === currentInstance) {\n pollHandle = setTimeout(poll, LOCAL_POLL);\n }\n });\n });\n }\n\n function cleanup() {\n var ourSyncNode = mySyncNode;\n if (!ourSyncNode) return Promise.reject(\"Database closed\");\n return db.transaction('rw', '_syncNodes', '_changes', '_intercomm', function () {\n // Cleanup dead local nodes that has no heartbeat for over a minute\n // Dont do the following:\n //nodes.where(\"lastHeartBeat\").below(Date.now() - NODE_TIMEOUT).and(function (node) { return node.type == \"local\"; }).delete();\n // Because client may have been in hybernate mode and recently woken up. That would lead to deletion of all nodes.\n // Instead, we should mark any old nodes for deletion in a minute or so. If they still dont wakeup after that minute we could consider them dead.\n var weBecameMaster = false;\n db._syncNodes.where(\"lastHeartBeat\").below(Date.now() - NODE_TIMEOUT).and(function (node) {\n return node.type === 'local';\n }).modify(function (node) {\n if (node.deleteTimeStamp && node.deleteTimeStamp < Date.now()) {\n // Delete the node.\n delete this.value;\n // Cleanup localStorage \"deadnode:\" entry for this node (localStorage API was used to wakeup other windows (onstorage event) - an event type missing in indexedDB.)\n localStorage.removeItem('Dexie.Observable/deadnode:' + node.id + '/' + db.name);\n // Check if we are deleting a master node\n if (node.isMaster) {\n // The node we are deleting is master. We must take over that role.\n // OK to call nodes.update(). No need to call Dexie.vip() because nodes is opened in existing transaction!\n db._syncNodes.update(ourSyncNode, { isMaster: 1 });\n weBecameMaster = true;\n }\n // Cleanup intercomm messages destinated to the node being deleted:\n db._intercomm.where(\"destinationNode\").equals(node.id).modify(function (msg) {\n // OK to call intercomm. No need to call Dexie.vip() because intercomm is opened in existing transaction!\n // Delete the message from DB and if someone is waiting for reply, let ourselved answer the request.\n delete this.value;\n if (msg.wantReply) {\n // Message wants a reply, meaning someone must take over its messages when it dies. Let us be that one!\n Dexie.ignoreTransaction(function () {\n consumeMessage(msg);\n });\n }\n });\n } else if (!node.deleteTimeStamp) {\n // Mark the node for deletion\n node.deleteTimeStamp = Date.now() + HIBERNATE_GRACE_PERIOD;\n }\n }).then(function () {\n // Cleanup old revisions that no node is interested of.\n Observable.deleteOldChanges(db);\n return db.on(\"cleanup\").fire(weBecameMaster);\n });\n });\n }\n\n function onBeforeUnload(event) {\n // Mark our own sync node for deletion.\n if (!mySyncNode) return;\n browserIsShuttingDown = true;\n mySyncNode.deleteTimeStamp = 1; // One millisecond after 1970. Makes it occur in the past but still keeps it truthy.\n mySyncNode.lastHeartBeat = 0;\n db._syncNodes.put(mySyncNode); // This async operation may be cancelled since the browser is closing down now.\n Observable.wereTheOneDying = true; // If other nodes in same window wakes up by this call, make sure they dont start taking over mastership and stuff...\n // Inform other windows that we're gone, so that they may take over our role if needed. Setting localStorage item below will trigger Observable.onStorage, which will trigger onSuicie() below:\n localStorage.setItem('Dexie.Observable/deadnode:' + mySyncNode.id.toString() + '/' + db.name, \"dead\"); // In IE, this will also wakeup our own window. However, that is doublechecked in nursecall subscriber below.\n }\n\n function onSuicide(dbname, nodeID) {\n if (dbname === db.name && !Observable.wereTheOneDying) {\n // Make sure it's dead indeed. Second bullet. Why? Because it has marked itself for deletion in the onbeforeunload event, which is fired just before window dies.\n // It's own call to put() may have been cancelled.\n // Note also that in IE, this event may be called twice, but that doesnt harm!\n Dexie.vip(function () {\n db._syncNodes.update(nodeID, { deleteTimeStamp: 1, lastHeartBeat: 0 }).then(cleanup);\n });\n }\n }\n\n //\n // Intercommunication between nodes\n //\n // Enable inter-process communication between browser windows\n\n var requestsWaitingForReply = {};\n\n db.sendMessage = function (type, message, destinationNode, options) {\n /// Type of message\n /// Message to send\n /// ID of destination node\n /// {wantReply: Boolean, isFailure: Boolean, requestId: Number}. If wantReply, the returned promise will complete with the reply from remote. Otherwise it will complete when message has been successfully sent.\n if (!mySyncNode) return Promise.reject(\"Database closed\");\n options = options || {};\n var msg = { message: message, destinationNode: destinationNode, sender: mySyncNode.id, type: type };\n Dexie.extend(msg, options); // wantReply: wantReply, success: !isFailure, requestId: ...\n var tables = [\"_intercomm\"];\n if (options.wantReply) tables.push(\"_syncNodes\"); // If caller wants a reply, include \"_syncNodes\" in transaction to check that there's a reciever there. Otherwise, new master will get it.\n return db.transaction('rw?', tables, function () {\n if (options.wantReply) {\n // Check that there is a reciever there to take the request.\n return db._syncNodes.where('id').equals(destinationNode).count(function (recieverAlive) {\n if (recieverAlive) return addMessage(msg);else return db._syncNodes.where('isMaster').above(0).first(function (masterNode) {\n msg.destinationNode = masterNode.id;\n return addMessage(msg);\n });\n });\n } else {\n addMessage(msg); // No need to return Promise. Caller dont need a reply.\n }\n\n function addMessage(msg) {\n return db._intercomm.add(msg).then(function (messageId) {\n localStorage.setItem(\"Dexie.Observable/intercomm/\" + db.name, messageId.toString());\n Dexie.ignoreTransaction(function () {\n Observable.on.intercomm.fire(db.name);\n });\n if (options.wantReply) {\n return new Promise(function (resolve, reject) {\n requestsWaitingForReply[messageId.toString()] = { resolve: resolve, reject: reject };\n });\n }\n });\n }\n });\n };\n\n db.broadcastMessage = function (type, message, bIncludeSelf) {\n if (!mySyncNode) return Promise.reject(\"Database closed\");\n var mySyncNodeId = mySyncNode.id;\n db._syncNodes.each(function (node) {\n if (node.type === 'local' && (bIncludeSelf || node.id !== mySyncNodeId)) {\n db.sendMessage(type, message, node.id);\n }\n });\n };\n\n db.observable = {};\n db.observable.SyncNode = SyncNode;\n\n function consumeIntercommMessages() {\n // Check if we got messages:\n if (!mySyncNode) return Promise.reject(\"Database closed\");\n return db.table('_intercomm').where(\"destinationNode\").equals(mySyncNode.id).modify(function (msg) {\n // For each message, fire the event and remove message.\n delete this.value;\n Dexie.ignoreTransaction(function () {\n consumeMessage(msg);\n });\n });\n }\n\n function consumeMessage(msg) {\n if (msg.type === 'response') {\n // This is a response. Lookup pending request and fulfill it's promise.\n var request = requestsWaitingForReply[msg.requestId.toString()];\n if (request) {\n if (msg.isFailure) {\n request.reject(msg.message.error);\n } else {\n request.resolve(msg.message.result);\n }\n delete requestsWaitingForReply[msg.requestId.toString()];\n }\n } else {\n // This is a message or request. Fire the event and add an API for the subscriber to use if reply is requested\n msg.resolve = function (result) {\n db.sendMessage('response', { result: result }, msg.sender, { requestId: msg.id });\n };\n msg.reject = function (error) {\n db.sendMessage('response', { error: error.toString() }, msg.sender, { isFailure: true, requestId: msg.id });\n };\n var message = msg.message;\n delete msg.message;\n Dexie.extend(msg, message);\n db.on.message.fire(msg);\n }\n }\n\n function onIntercomm(dbname) {\n // When storage event trigger us to check\n if (dbname === db.name) {\n consumeIntercommMessages();\n }\n }\n}\n\n//\n// Help functions\n//\n\nfunction nop() {};\n\nfunction promisableChain(f1, f2) {\n if (f1 === nop) return f2;\n return function () {\n var res = f1.apply(this, arguments);\n if (res && typeof res.then === 'function') {\n var thiz = this,\n args = arguments;\n return res.then(function () {\n return f2.apply(thiz, args);\n });\n }\n return f2.apply(this, arguments);\n };\n}\n\n//\n// Static properties and methods\n// \n\nObservable.latestRevision = {}; // Latest revision PER DATABASE. Example: Observable.latestRevision.FriendsDB = 37;\nObservable.on = Dexie.Events(null, \"latestRevisionIncremented\", \"suicideNurseCall\", \"intercomm\", \"beforeunload\"); // fire(dbname, value);\nObservable.createUUID = function () {\n // Decent solution from http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript\n var d = Date.now();\n var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {\n var r = (d + Math.random() * 16) % 16 | 0;\n d = Math.floor(d / 16);\n return (c === 'x' ? r : r & 0x7 | 0x8).toString(16);\n });\n return uuid;\n};\n\nObservable.deleteOldChanges = function (db) {\n db._syncNodes.orderBy(\"myRevision\").first(function (oldestNode) {\n var timeout = Date.now() + 300,\n timedout = false;\n db._changes.where(\"rev\").below(oldestNode.myRevision).until(function () {\n return timedout = Date.now() > timeout;\n }).delete().then(function () {\n // If not done garbage collecting, reschedule a continuation of it until done.\n if (timedout) setTimeout(function () {\n Observable.deleteOldChanges(db);\n }, 10);\n });\n });\n};\n\nObservable._onStorage = function onStorage(event) {\n // We use the onstorage event to trigger onLatestRevisionIncremented since we will wake up when other windows modify the DB as well!\n if (event.key.indexOf(\"Dexie.Observable/\") === 0) {\n // For example \"Dexie.Observable/latestRevision/FriendsDB\"\n var parts = event.key.split('/');\n var prop = parts[1];\n var dbname = parts[2];\n if (prop === 'latestRevision') {\n var rev = parseInt(event.newValue, 10);\n if (!isNaN(rev) && rev > Observable.latestRevision[dbname]) {\n Observable.latestRevision[dbname] = rev;\n Dexie.ignoreTransaction(function () {\n Observable.on('latestRevisionIncremented').fire(dbname, rev);\n });\n }\n } else if (prop.indexOf(\"deadnode:\") === 0) {\n var nodeID = parseInt(prop.split(':')[1], 10);\n if (event.newValue) {\n Observable.on.suicideNurseCall.fire(dbname, nodeID);\n }\n } else if (prop === 'intercomm') {\n if (event.newValue) {\n Observable.on.intercomm.fire(dbname);\n }\n }\n }\n};\n\nObservable._onBeforeUnload = function () {\n Observable.on.beforeunload.fire();\n};\n\nObservable.localStorageImpl = global.localStorage;\n\n//\n// Map window events to static events in Dexie.Observable:\n//\nif (global.addEventListener) {\n global.addEventListener(\"storage\", Observable._onStorage);\n global.addEventListener(\"beforeunload\", Observable._onBeforeUnload);\n}\n// Register addon:\nDexie.Observable = Observable;\nDexie.addons.push(Observable);"]}
\ No newline at end of file
diff --git a/addons/Dexie.Syncable/dist/README.md b/addons/Dexie.Syncable/dist/README.md
deleted file mode 100644
index 38706a48d..000000000
--- a/addons/Dexie.Syncable/dist/README.md
+++ /dev/null
@@ -1,37 +0,0 @@
-## Can't find dexie-syncable.js?
-Transpiled code (dist version) IS ONLY checked in to
-the [releases](https://github.com/dfahlander/Dexie.js/tree/releases/addons/Dexie.Syncable/dist).
-branch.
-
-## Download
-[unpkg.com/dexie-syncable/dist/dexie-syncable.js](https://unpkg.com/dexie-syncable/dist/dexie-syncable.js)
-
-[unpkg.com/dexie-syncable/dist/dexie-syncable.min.js](https://unpkg.com/dexie-syncable/dist/dexie-syncable.min.js)
-
-[unpkg.com/dexie-syncable/dist/dexie-syncable.js.map](https://unpkg.com/dexie-syncable/dist/dexie-syncable.js.map)
-
-[unpkg.com/dexie-syncable/dist/dexie-syncable.min.js.map](https://unpkg.com/dexie-syncable/dist/dexie-syncable.min.js.map)
-
-## npm
-```
-npm install dexie-syncable --save
-```
-## bower
-Since Dexie v1.3.4, addons are included in the dexie bower package.
-```
-$ bower install dexie --save
-$ ls bower_components/dexie/addons/Dexie.Syncable/dist
-dexie-syncable.js dexie-syncable.js.map dexie-syncable.min.js dexie-syncable.min.js.map
-
-```
-## Or build them yourself...
-Fork Dexie.js, then:
-```
-git clone https://github.com/YOUR-USERNAME/Dexie.js.git
-cd Dexie.js
-npm install
-cd addons/Dexie.Syncable
-npm run build # or npm run watch
-
-```
-If you're on windows, you need to use an elevated command prompt of some reason to get `npm install` to work.
diff --git a/addons/Dexie.Syncable/dist/dexie-syncable.js b/addons/Dexie.Syncable/dist/dexie-syncable.js
new file mode 100644
index 000000000..d40b9b4c1
--- /dev/null
+++ b/addons/Dexie.Syncable/dist/dexie-syncable.js
@@ -0,0 +1,1028 @@
+(function (global, factory) {
+ typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('dexie'), require('dexie-observable')) :
+ typeof define === 'function' && define.amd ? define(['dexie', 'dexie-observable'], factory) :
+ (global.Dexie = global.Dexie || {}, global.Dexie.Syncable = factory(global.Dexie,global.dexieObservable));
+}(this, (function (Dexie,dexieObservable) { 'use strict';
+
+Dexie = 'default' in Dexie ? Dexie['default'] : Dexie;
+
+///
+///
+///
+/**
+ * Dexie.Syncable.js
+ * ===================
+ * Dexie addon for syncing indexedDB with remote endpoints.
+ *
+ * version: 0.1.9 Alpha, Thu Oct 13 2016
+ *
+ * Disclaimber: This addon is in alpha status meaning that
+ * its API and behavior may change.
+ *
+ */
+
+// Depend on 'dexie-observable'
+// To support both ES6,AMD,CJS and UMD (plain script), we just import it and then access it as "Dexie.Observable".
+// That way, our plugin works in all UMD cases.
+// If target platform would only be module based (ES6/AMD/CJS), we could have done 'import Observable from "dexie-observable"'.
+var override = Dexie.override;
+var Promise = Dexie.Promise;
+var setByKeyPath = Dexie.setByKeyPath;
+var Observable = Dexie.Observable;
+
+function Syncable(db) {
+ ///
+
+ var activePeers = [];
+
+ // Change Types
+ var CREATE = 1,
+ UPDATE = 2,
+ DELETE = 3;
+
+ // Statuses
+ var Statuses = Syncable.Statuses;
+
+ var MAX_CHANGES_PER_CHUNK = 1000;
+
+ db.on('message', function (msg) {
+ // Message from other local node arrives...
+ Dexie.vip(function () {
+ if (msg.type === 'connect') {
+ // We are master node and another non-master node wants us to do the connect.
+ db.syncable.connect(msg.protocolName, msg.url, msg.options).then(msg.resolve, msg.reject);
+ } else if (msg.type === 'disconnect') {
+ db.syncable.disconnect(msg.url).then(msg.resolve, msg.reject);
+ } else if (msg.type === 'syncStatusChanged') {
+ // We are client and a master node informs us about syncStatus change.
+ // Lookup the connectedProvider and call its event
+ db.syncable.on.statusChanged.fire(msg.newStatus, msg.url);
+ }
+ });
+ });
+
+ db.on('cleanup', function (weBecameMaster) {
+ // A cleanup (done in Dexie.Observable) may result in that a master node is removed and we become master.
+ if (weBecameMaster) {
+ // We took over the master role in Observable's cleanup method
+ db._syncNodes.where('type').equals('remote').and(function (node) {
+ return node.status !== Statuses.OFFLINE && node.status !== Statuses.ERROR;
+ }).each(function (connectedRemoteNode) {
+ // There are connected remote nodes that we must take over
+ // Since we may be in the on(ready) event, we must get VIPed to continue
+ Dexie.ignoreTransaction(function () {
+ Dexie.vip(function () {
+ db.syncable.connect(connectedRemoteNode.syncProtocol, connectedRemoteNode.url, connectedRemoteNode.syncOptions);
+ });
+ });
+ });
+ }
+ });
+
+ db.on('ready', function onReady() {
+ // Again, in onReady: If we ARE master, make sure to connect to remote servers that is in a connected state.
+ if (db._localSyncNode && db._localSyncNode.isMaster) {
+ // Make sure to connect to remote servers that is in a connected state (NOT OFFLINE or ERROR!)
+ return db._syncNodes.where('type').equals('remote').and(function (node) {
+ return node.status !== Statuses.OFFLINE && node.status !== Statuses.ERROR;
+ }).toArray(function (connectedRemoteNodes) {
+ // There are connected remote nodes that we must take over
+ if (connectedRemoteNodes.length > 0) {
+ return Promise.all(connectedRemoteNodes.map(function (node) {
+ return db.syncable.connect(node.syncProtocol, node.url, node.syncOptions).catch(function (err) {
+ return undefined; // If a node fails to connect, don't make db.open() reject. Accept it!
+ });
+ }));
+ }
+ });
+ }
+ }, true); // True means the ready event will survive a db reopen - db.close()/db.open()
+
+
+ db.syncable = {};
+
+ db.syncable.getStatus = function (url, cb) {
+ if (db.isOpen()) {
+ return Dexie.vip(function () {
+ return db._syncNodes.where('url').equals(url).first(function (node) {
+ return node ? node.status : Statuses.OFFLINE;
+ });
+ }).then(cb);
+ } else {
+ return Promise.resolve(Syncable.Statuses.OFFLINE).then(cb);
+ }
+ };
+
+ db.syncable.list = function () {
+ return db._syncNodes.where('type').equals('remote').toArray(function (a) {
+ return a.map(function (node) {
+ return node.url;
+ });
+ });
+ };
+
+ db.syncable.on = Dexie.Events(db, { statusChanged: "asap" });
+
+ db.syncable.disconnect = function (url) {
+ if (db._localSyncNode && db._localSyncNode.isMaster) {
+ activePeers.filter(function (peer) {
+ return peer.url === url;
+ }).forEach(function (peer) {
+ peer.disconnect(Statuses.OFFLINE);
+ });
+ } else {
+ db._syncNodes.where('isMaster').above(0).first(function (masterNode) {
+ db.sendMessage('disconnect', { url: url }, masterNode.id, { wantReply: true });
+ });
+ }
+
+ return db._syncNodes.where("url").equals(url).modify(function (node) {
+ node.status = Statuses.OFFLINE;
+ });
+ };
+
+ db.syncable.connect = function (protocolName, url, options) {
+ options = options || {}; // Make sure options is always an object because 1) Provider expects it to be. 2) We'll be persisting it and you cannot persist undefined.
+ var protocolInstance = Syncable.registeredProtocols[protocolName];
+
+ if (protocolInstance) {
+ if (db.isOpen() && db._localSyncNode) {
+ // Database is open
+ if (db._localSyncNode.isMaster) {
+ // We are master node
+ return connect(protocolInstance, protocolName, url, options, db._localSyncNode.id);
+ } else {
+ // We are not master node
+ // Request master node to do the connect:
+ db.table('_syncNodes').where('isMaster').above(0).first(function (masterNode) {
+ // There will always be a master node. In theory we may self have become master node when we come here. But that's ok. We'll request ourselves.
+ return db.sendMessage('connect', { protocolName: protocolName, url: url, options: options }, masterNode.id, { wantReply: true });
+ });
+ return Promise.resolve();
+ }
+ } else {
+ // Database not yet open
+ // Wait for it to open
+ return new Promise(function (resolve, reject) {
+ db.on("ready", function syncWhenReady() {
+ return Dexie.vip(function () {
+ return db.syncable.connect(protocolName, url, options).then(resolve).catch(function (err) {
+ // Reject the promise returned to the caller of db.syncable.connect():
+ reject(err);
+ // but resolve the promise that db.on("ready") waits for, because database should succeed to open even if the connect operation fails!
+ });
+ });
+ });
+ });
+ }
+ } else {
+ throw new Error("ISyncProtocol '" + protocolName + "' is not registered in Dexie.Syncable.registerSyncProtocol()");
+ return new Promise(); // For code completion
+ }
+ };
+
+ db.syncable.delete = function (url) {
+ // Notice: Caller should call db.syncable.disconnect(url) and wait for it to finish before calling db.syncable.delete(url)
+ // Surround with a readwrite-transaction
+ return db.transaction('rw', db._syncNodes, db._changes, db._uncommittedChanges, function () {
+ // Find the node
+ db._syncNodes.where("url").equals(url).toArray(function (nodes) {
+ // If it's found (or even several found, as detected by @martindiphoorn),
+ // let's delete it (or them) and cleanup _changes and _uncommittedChanges
+ // accordingly.
+ if (nodes.length > 0) {
+ var nodeIDs = nodes.map(function (node) {
+ return node.id;
+ });
+ // The following 'return' statement is not needed right now, but I leave it
+ // there because if we would like to add a 'then()' statement to the main ,
+ // operation above ( db._syncNodes.where("url").equals(url).toArray(...) ) ,
+ // this return statement will asure that the whole chain is waited for
+ // before entering the then() callback.
+ return db._syncNodes.where('id').anyOf(nodeIDs).delete().then(function () {
+ // When theese nodes are gone, let's clear the _changes table
+ // from all revisions older than the oldest node.
+ // Delete all changes older than revision of oldest node:
+ Observable.deleteOldChanges();
+ // Also don't forget to delete all uncommittedChanges for the deleted node:
+ return db._uncommittedChanges.where('node').anyOf(nodeIDs).delete();
+ });
+ }
+ });
+ });
+ };
+
+ db.syncable.unsyncedChanges = function (url) {
+ return db._syncNodes.where("url").equals(url).first(function (node) {
+ return db._changes.where('rev').above(node.myRevision).toArray();
+ });
+ };
+
+ function connect(protocolInstance, protocolName, url, options, dbAliveID) {
+ ///
+ var existingPeer = activePeers.filter(function (peer) {
+ return peer.url === url;
+ });
+ if (existingPeer.length > 0) {
+ // Never create multiple syncNodes with same protocolName and url. Instead, let the next call to connect() return the same promise that
+ // have already been started and eventually also resolved. If promise has already resolved (node connected), calling existing promise.then() will give a callback directly.
+ return existingPeer[0].connectPromise;
+ }
+
+ var connectPromise = getOrCreateSyncNode(options).then(function (node) {
+ return connectProtocol(node);
+ });
+
+ var rejectConnectPromise = null;
+ var disconnected = false;
+ var hasMoreToGive = true;
+ var activePeer = {
+ url: url,
+ status: Statuses.OFFLINE,
+ connectPromise: connectPromise,
+ on: Dexie.Events(null, "disconnect"),
+ disconnect: function (newStatus, error) {
+ if (!disconnected) {
+ activePeer.on.disconnect.fire(newStatus, error);
+ var pos = activePeers.indexOf(activePeer);
+ if (pos >= 0) activePeers.splice(pos, 1);
+ if (error && rejectConnectPromise) rejectConnectPromise(error);
+ }
+ disconnected = true;
+ }
+ };
+ activePeers.push(activePeer);
+
+ return connectPromise;
+
+ function stillAlive() {
+ // A better method than doing db.isOpen() because the same db instance may have been reopened, but then this sync call should be dead
+ // because the new instance should be considered a fresh instance and will have another local node.
+ return db._localSyncNode && db._localSyncNode.id === dbAliveID;
+ }
+
+ function getOrCreateSyncNode(options) {
+ return db.transaction('rw', db._syncNodes, function () {
+ if (!url) throw new Error("Url cannot be empty");
+ // Returning a promise from transaction scope will make the transaction promise resolve with the value of that promise.
+
+
+ return db._syncNodes.where("url").equalsIgnoreCase(url).first(function (node) {
+ //
+ // PersistedContext : IPersistedContext
+ //
+ function PersistedContext(nodeID, otherProps) {
+ this.nodeID = nodeID;
+ if (otherProps) Dexie.extend(this, otherProps);
+ }
+
+ PersistedContext.prototype.save = function () {
+ // Store this instance in the syncContext property of the node it belongs to.
+ return Dexie.vip(function () {
+ return node.save();
+ });
+ };
+
+ if (node) {
+ // Node already there. Make syncContext become an instance of PersistedContext:
+ node.syncContext = new PersistedContext(node.id, node.syncContext);
+ node.syncProtocol = protocolName; // In case it was changed (would be very strange but...) could happen...
+ db._syncNodes.put(node);
+ } else {
+ // Create new node and sync everything
+ node = new db.observable.SyncNode();
+ node.myRevision = -1;
+ node.appliedRemoteRevision = null;
+ node.remoteBaseRevisions = [];
+ node.type = "remote";
+ node.syncProtocol = protocolName;
+ node.url = url;
+ node.syncOptions = options;
+ node.lastHeartBeat = Date.now();
+ node.dbUploadState = null;
+ Promise.resolve(function () {
+ // If options.initialUpload is explicitely false, set myRevision to currentRevision.
+ if (options.initialUpload === false) return db._changes.lastKey(function (currentRevision) {
+ node.myRevision = currentRevision;
+ });
+ }).then(function () {
+ db._syncNodes.add(node).then(function (nodeId) {
+ node.syncContext = new PersistedContext(nodeId); // Update syncContext in db with correct nodeId.
+ db._syncNodes.put(node);
+ });
+ });
+ }
+
+ return node; // returning node will make the db.transaction()-promise resolve with this value.
+ });
+ });
+ }
+
+ function connectProtocol(node) {
+ ///
+
+ function changeStatusTo(newStatus) {
+ if (node.status !== newStatus) {
+ node.status = newStatus;
+ node.save();
+ db.syncable.on.statusChanged.fire(newStatus, url);
+ // Also broadcast message to other nodes about the status
+ db.broadcastMessage("syncStatusChanged", { newStatus: newStatus, url: url }, false);
+ }
+ }
+
+ activePeer.on('disconnect', function (newStatus) {
+ if (!isNaN(newStatus)) changeStatusTo(newStatus);
+ });
+
+ var connectedContinuation;
+ changeStatusTo(Statuses.CONNECTING);
+ return doSync();
+
+ function doSync() {
+ // Use enque() to ensure only a single promise execution at a time.
+ return enque(doSync, function () {
+ // By returning the Promise returned by getLocalChangesForNode() a final catch() on the sync() method will also catch error occurring in entire sequence.
+ return getLocalChangesForNode_autoAckIfEmpty(node, function sendChangesToProvider(changes, remoteBaseRevision, partial, nodeModificationsOnAck) {
+ // Create a final Promise for the entire sync() operation that will resolve when provider calls onSuccess().
+ // By creating finalPromise before calling protocolInstance.sync() it is possible for provider to call onError() immediately if it wants.
+ var finalSyncPromise = new Promise(function (resolve, reject) {
+ rejectConnectPromise = function (err) {
+ reject(err);
+ };
+ Dexie.asap(function () {
+ try {
+ protocolInstance.sync(node.syncContext, url, options, remoteBaseRevision, node.appliedRemoteRevision, changes, partial, applyRemoteChanges, onChangesAccepted, function (continuation) {
+ resolve(continuation);
+ }, onError);
+ } catch (ex) {
+ onError(ex, Infinity);
+ }
+
+ function onError(error, again) {
+ reject(error);
+ if (stillAlive()) {
+ if (!isNaN(again) && again < Infinity) {
+ setTimeout(function () {
+ if (stillAlive()) {
+ changeStatusTo(Statuses.SYNCING);
+ doSync();
+ }
+ }, again);
+ changeStatusTo(Statuses.ERROR_WILL_RETRY, error);
+ if (connectedContinuation && connectedContinuation.disconnect) connectedContinuation.disconnect();
+ connectedContinuation = null;
+ } else {
+ abortTheProvider(error); // Will fire ERROR on statusChanged event.
+ }
+ }
+ }
+ });
+ });
+
+ return finalSyncPromise.then(function () {
+ // Resolve caller of db.syncable.connect() with undefined. Not with continuation!
+ });
+
+ function onChangesAccepted() {
+ Object.keys(nodeModificationsOnAck).forEach(function (keyPath) {
+ Dexie.setByKeyPath(node, keyPath, nodeModificationsOnAck[keyPath]);
+ });
+ node.save();
+ // We dont know if onSuccess() was called by provider yet. If it's already called, finalPromise.then() will execute immediately,
+ // otherwise it will execute when finalSyncPromise resolves.
+ finalSyncPromise.then(continueSendingChanges);
+ }
+ });
+ }, dbAliveID);
+ }
+
+ function abortTheProvider(error) {
+ activePeer.disconnect(Statuses.ERROR, error);
+ }
+
+ function getBaseRevisionAndMaxClientRevision(node) {
+ ///
+ if (node.remoteBaseRevisions.length === 0) return {
+ // No remoteBaseRevisions have arrived yet. No limit on clientRevision and provide null as remoteBaseRevision:
+ maxClientRevision: Infinity,
+ remoteBaseRevision: null
+ };
+ for (var i = node.remoteBaseRevisions.length - 1; i >= 0; --i) {
+ if (node.myRevision >= node.remoteBaseRevisions[i].local) {
+ // Found a remoteBaseRevision that fits node.myRevision. Return remoteBaseRevision and eventually a roof maxClientRevision pointing out where next remoteBaseRevision bases its changes on.
+ return {
+ maxClientRevision: i === node.remoteBaseRevisions.length - 1 ? Infinity : node.remoteBaseRevisions[i + 1].local,
+ remoteBaseRevision: node.remoteBaseRevisions[i].remote
+ };
+ }
+ }
+ // There are at least one item in the list but the server hasnt yet become up-to-date with the 0 revision from client.
+ return {
+ maxClientRevision: node.remoteBaseRevisions[0].local,
+ remoteBaseRevision: null
+ };
+ }
+
+ function getLocalChangesForNode_autoAckIfEmpty(node, cb) {
+ return getLocalChangesForNode(node, function autoAck(changes, remoteBaseRevision, partial, nodeModificationsOnAck) {
+ if (changes.length === 0 && 'myRevision' in nodeModificationsOnAck && nodeModificationsOnAck.myRevision !== node.myRevision) {
+ Object.keys(nodeModificationsOnAck).forEach(function (keyPath) {
+ Dexie.setByKeyPath(node, keyPath, nodeModificationsOnAck[keyPath]);
+ });
+ node.save();
+ return getLocalChangesForNode(node, autoAck);
+ } else {
+ return cb(changes, remoteBaseRevision, partial, nodeModificationsOnAck);
+ }
+ });
+ }
+
+ function getLocalChangesForNode(node, cb) {
+ ///
+ /// Based on given node's current revision and state, this function makes sure to retrieve next chunk of changes
+ /// for that node.
+ ///
+ ///
+ /// Callback that will retrieve next chunk of changes and a boolean telling if it's a partial result or not. If truthy, result is partial and there are more changes to come. If falsy, these changes are the final result.
+
+ if (node.myRevision >= 0) {
+ // Node is based on a revision in our local database and will just need to get the changes that has occurred since that revision.
+ var brmcr = getBaseRevisionAndMaxClientRevision(node);
+ return getChangesSinceRevision(node.myRevision, MAX_CHANGES_PER_CHUNK, brmcr.maxClientRevision, function (changes, partial, nodeModificationsOnAck) {
+ return cb(changes, brmcr.remoteBaseRevision, partial, nodeModificationsOnAck);
+ });
+ } else {
+ // Node hasn't got anything from our local database yet. We will need to upload entire DB to the node in the form of CREATE changes.
+ // Check if we're in the middle of already doing that:
+ if (node.dbUploadState === null) {
+ // Initiatalize dbUploadState
+ var tablesToUpload = db.tables.filter(function (table) {
+ return table.schema.observable;
+ }).map(function (table) {
+ return table.name;
+ });
+ if (tablesToUpload.length === 0) return Promise.resolve(cb([], null, false, {})); // There are no synched tables at all.
+ var dbUploadState = {
+ tablesToUpload: tablesToUpload,
+ currentTable: tablesToUpload.shift(),
+ currentKey: null
+ };
+ return db._changes.orderBy('rev').last(function (lastChange) {
+ dbUploadState.localBaseRevision = lastChange && lastChange.rev || 0;
+ var collection = db.table(dbUploadState.currentTable).orderBy(':id');
+ return getTableObjectsAsChanges(dbUploadState, [], collection);
+ });
+ } else if (node.dbUploadState.currentKey) {
+ var collection = db.table(node.dbUploadState.currentTable).where(':id').above(node.dbUploadState.currentKey);
+ return getTableObjectsAsChanges(Dexie.deepClone(node.dbUploadState), [], collection);
+ } else {
+ var collection = db.table(dbUploadState.currentTable).orderBy(':id');
+ return getTableObjectsAsChanges(Dexie.deepClone(node.dbUploadState), [], collection);
+ }
+ }
+
+ function getTableObjectsAsChanges(state, changes, collection) {
+ ///
+ ///
+ ///
+ var limitReached = false;
+ return collection.until(function () {
+ if (changes.length === MAX_CHANGES_PER_CHUNK) {
+ limitReached = true;
+ return true;
+ }
+ }).each(function (item, cursor) {
+ changes.push({
+ type: CREATE,
+ table: state.currentTable,
+ key: cursor.key,
+ obj: cursor.value
+ });
+ state.currentKey = cursor.key;
+ }).then(function () {
+ if (limitReached) {
+ // Limit reached. Send partial result.
+ hasMoreToGive = true;
+ return cb(changes, null, true, { dbUploadState: state });
+ } else {
+ // Done iterating this table. Check if there are more tables to go through:
+ if (state.tablesToUpload.length === 0) {
+ // Done iterating all tables
+ // Now append changes occurred during our dbUpload:
+ var brmcr = getBaseRevisionAndMaxClientRevision(node);
+ return getChangesSinceRevision(state.localBaseRevision, MAX_CHANGES_PER_CHUNK - changes.length, brmcr.maxClientRevision, function (additionalChanges, partial, nodeModificationsOnAck) {
+ changes = changes.concat(additionalChanges);
+ nodeModificationsOnAck.dbUploadState = null;
+ return cb(changes, brmcr.remoteBaseRevision, partial, nodeModificationsOnAck);
+ });
+ } else {
+ // Not done iterating all tables. Continue on next table:
+ state.currentTable = state.tablesToUpload.shift();
+ return getTableObjectsAsChanges(state, changes, db.table(state.currentTable).orderBy(':id'));
+ }
+ }
+ });
+ }
+
+ function getChangesSinceRevision(revision, maxChanges, maxRevision, cb) {
+ /// Callback that will retrieve next chunk of changes and a boolean telling if it's a partial result or not. If truthy, result is partial and there are more changes to come. If falsy, these changes are the final result.
+ var changeSet = {};
+ var numChanges = 0;
+ var partial = false;
+ var ignoreSource = node.id;
+ var nextRevision = revision;
+ return db.transaction('r', db._changes, function () {
+ var query = maxRevision === Infinity ? db._changes.where('rev').above(revision) : db._changes.where('rev').between(revision, maxRevision, false, true);
+ query.until(function () {
+ if (numChanges === maxChanges) {
+ partial = true;
+ return true;
+ }
+ }).each(function (change) {
+ // Note the revision in nextRevision:
+ nextRevision = change.rev;
+ if (change.source === ignoreSource) return;
+ // Our _changes table contains more info than required (old objs, source etc). Just make sure to include the nescessary info:
+ var changeToSend = {
+ type: change.type,
+ table: change.table,
+ key: change.key
+ };
+ if (change.type === CREATE) changeToSend.obj = change.obj;else if (change.type === UPDATE) changeToSend.mods = change.mods;
+
+ var id = change.table + ":" + change.key;
+ var prevChange = changeSet[id];
+ if (!prevChange) {
+ // This is the first change on this key. Add it unless it comes from the source that we are working against
+ changeSet[id] = changeToSend;
+ ++numChanges;
+ } else {
+ // Merge the oldchange with the new change
+ var nextChange = changeToSend;
+ var mergedChange = function () {
+ switch (prevChange.type) {
+ case CREATE:
+ switch (nextChange.type) {
+ case CREATE:
+ return nextChange; // Another CREATE replaces previous CREATE.
+ case UPDATE:
+ return combineCreateAndUpdate(prevChange, nextChange); // Apply nextChange.mods into prevChange.obj
+ case DELETE:
+ return nextChange; // Object created and then deleted. If it wasnt for that we MUST handle resent changes, we would skip entire change here. But what if the CREATE was sent earlier, and then CREATE/DELETE at later stage? It would become a ghost object in DB. Therefore, we MUST keep the delete change! If object doesnt exist, it wont harm!
+ }
+ break;
+ case UPDATE:
+ switch (nextChange.type) {
+ case CREATE:
+ return nextChange; // Another CREATE replaces previous update.
+ case UPDATE:
+ return combineUpdateAndUpdate(prevChange, nextChange); // Add the additional modifications to existing modification set.
+ case DELETE:
+ return nextChange; // Only send the delete change. What was updated earlier is no longer of interest.
+ }
+ break;
+ case DELETE:
+ switch (nextChange.type) {
+ case CREATE:
+ return nextChange; // A resurection occurred. Only create change is of interest.
+ case UPDATE:
+ return prevChange; // Nothing to do. We cannot update an object that doesnt exist. Leave the delete change there.
+ case DELETE:
+ return prevChange; // Still a delete change. Leave as is.
+ }
+ break;
+ }
+ }();
+ changeSet[id] = mergedChange;
+ }
+ });
+ }).then(function () {
+ var changes = Object.keys(changeSet).map(function (key) {
+ return changeSet[key];
+ });
+ hasMoreToGive = partial;
+ return cb(changes, partial, { myRevision: nextRevision });
+ });
+ }
+ }
+
+ function applyRemoteChanges(remoteChanges, remoteRevision, partial, clear) {
+ return enque(applyRemoteChanges, function () {
+ if (!stillAlive()) return Promise.reject("Database not open");
+ // FIXTHIS: Check what to do if clear() is true!
+ return (partial ? saveToUncommitedChanges(remoteChanges) : finallyCommitAllChanges(remoteChanges, remoteRevision)).catch(function (error) {
+ abortTheProvider(error);
+ return Promise.reject(error);
+ });
+ }, dbAliveID);
+
+ function saveToUncommitedChanges(changes) {
+ return db.transaction('rw', db._uncommittedChanges, function () {
+ changes.forEach(function (change) {
+ var changeToAdd = {
+ node: node.id,
+ type: change.type,
+ table: change.table,
+ key: change.key
+ };
+ if (change.obj) changeToAdd.obj = change.obj;
+ if (change.mods) changeToAdd.mods = change.mods;
+ db._uncommittedChanges.add(changeToAdd);
+ });
+ }).then(function () {
+ node.appliedRemoteRevision = remoteRevision;
+ node.save();
+ });
+ }
+
+ function finallyCommitAllChanges(changes, remoteRevision) {
+ //alert("finallyCommitAllChanges() will now start its job.");
+ //var tick = Date.now();
+
+ // 1. Open a write transaction on all tables in DB
+ return db.transaction('rw', db.tables.filter(function (table) {
+ return table.name === '_changes' || table.name === '_uncommittedChanges' || table.schema.observable;
+ }), function () {
+ var trans = Dexie.currentTransaction;
+ var localRevisionBeforeChanges = 0;
+ db._changes.orderBy('rev').last(function (lastChange) {
+ // Store what revision we were at before committing the changes
+ localRevisionBeforeChanges = lastChange && lastChange.rev || 0;
+ }).then(function () {
+ // Specify the source. Important for the change consumer to ignore changes originated from self!
+ trans.source = node.id;
+ // 2. Apply uncommitted changes and delete each uncommitted change
+ return db._uncommittedChanges.where('node').equals(node.id).toArray();
+ }).then(function (uncommittedChanges) {
+ return applyChanges(uncommittedChanges, 0);
+ }).then(function () {
+ return db._uncommittedChanges.where('node').equals(node.id).delete();
+ }).then(function () {
+ // 3. Apply last chunk of changes
+ return applyChanges(changes, 0);
+ }).then(function () {
+ // Get what revision we are at now:
+ return db._changes.orderBy('rev').last();
+ }).then(function (lastChange) {
+ var currentLocalRevision = lastChange && lastChange.rev || 0;
+ // 4. Update node states (appliedRemoteRevision, remoteBaseRevisions and eventually myRevision)
+ node.appliedRemoteRevision = remoteRevision;
+ node.remoteBaseRevisions.push({ remote: remoteRevision, local: currentLocalRevision });
+ if (node.myRevision === localRevisionBeforeChanges) {
+ // If server was up-to-date before we added new changes from the server, update myRevision to last change
+ // because server is still up-to-date! This is also important in order to prohibit getLocalChangesForNode() from
+ // ever sending an empty change list to server, which would otherwise be done every second time it would send changes.
+ node.myRevision = currentLocalRevision;
+ }
+ // Garbage collect remoteBaseRevisions not in use anymore:
+ if (node.remoteBaseRevisions.length > 1) {
+ for (var i = node.remoteBaseRevisions.length - 1; i > 0; --i) {
+ if (node.myRevision >= node.remoteBaseRevisions[i].local) {
+ node.remoteBaseRevisions.splice(0, i);
+ break;
+ }
+ }
+ }
+ node.save(); // We are not including _syncNodes in transaction, so this save() call will execute in its own transaction.
+ //var tock = Date.now();
+ //alert("finallyCommitAllChanges() has done its job. " + changes.length + " changes applied in " + ((tock - tick) / 1000) + "seconds");
+ });
+
+ function applyChanges(changes, offset) {
+ ///
+ ///
+ var lastChangeType = 0;
+ var lastCreatePromise = null;
+ if (offset >= changes.length) return Promise.resolve(null);
+ var change = changes[offset];
+ var table = db.table(change.table);
+ while (change && change.type === CREATE) {
+ // Optimize CREATE changes because on initial sync with server, the entire DB will be downloaded in forms of CREATE changes.
+ // Instead of waiting for each change to resolve, do all CREATE changes in bulks until another type of change is stepped upon.
+ // This case is the only case that allows i to increment and the for-loop to continue since it does not return anything.
+ var specifyKey = !table.schema.primKey.keyPath;
+ lastCreatePromise = function (change, table, specifyKey) {
+ return (specifyKey ? table.add(change.obj, change.key) : table.add(change.obj)).catch("ConstraintError", function (e) {
+ return specifyKey ? table.put(change.obj, change.key) : table.put(change.obj);
+ });
+ }(change, table, specifyKey);
+ change = changes[++offset];
+ if (change) table = db.table(change.table);
+ }
+
+ if (lastCreatePromise) {
+ // We did some CREATE changes but now stumbled upon another type of change.
+ // Let's wait for the last CREATE change to resolve and then call applyChanges again at current position. Next time, lastCreatePromise will be null and a case below will happen.
+ return lastCreatePromise.then(function () {
+ return offset < changes.length ? applyChanges(changes, offset) : null;
+ });
+ }
+
+ if (change) {
+ if (change.type === UPDATE) {
+ return table.update(change.key, change.mods).then(function () {
+ // Wait for update to resolve before taking next change. Why? Because it will lock transaction anyway since we are listening to CRUD events here.
+ return applyChanges(changes, offset + 1);
+ });
+ }
+
+ if (change.type === DELETE) {
+ return table.delete(change.key).then(function () {
+ // Wait for delete to resolve before taking next change. Why? Because it will lock transaction anyway since we are listening to CRUD events here.
+ return applyChanges(changes, offset + 1);
+ });
+ }
+ }
+
+ return Promise.resolve(null); // Will return null or a Promise and make the entire applyChanges promise finally resolve.
+ }
+ });
+ }
+ }
+
+ //
+ //
+ // Continuation Patterns Follows
+ //
+ //
+
+ function continueSendingChanges(continuation) {
+ if (!stillAlive()) {
+ // Database was closed.
+ if (continuation.disconnect) continuation.disconnect();
+ return;
+ }
+
+ connectedContinuation = continuation;
+ activePeer.on('disconnect', function () {
+ if (connectedContinuation) {
+ if (connectedContinuation.react) {
+ try {
+ // react pattern must provide a disconnect function.
+ connectedContinuation.disconnect();
+ } catch (e) {}
+ }
+ connectedContinuation = null; // Stop poll() pattern from polling again and abortTheProvider() from being called twice.
+ }
+ });
+
+ if (continuation.react) {
+ continueUsingReactPattern(continuation);
+ } else {
+ continueUsingPollPattern(continuation);
+ }
+ }
+
+ // React Pattern (eager)
+ function continueUsingReactPattern(continuation) {
+ var changesWaiting, // Boolean
+ isWaitingForServer; // Boolean
+
+
+ function onChanges() {
+ if (connectedContinuation) {
+ changeStatusTo(Statuses.SYNCING);
+ if (isWaitingForServer) changesWaiting = true;else {
+ reactToChanges();
+ }
+ }
+ }
+
+ db.on('changes', onChanges);
+
+ // Override disconnect() to also unsubscribe to onChanges.
+ activePeer.on('disconnect', function () {
+ db.on.changes.unsubscribe(onChanges);
+ });
+
+ function reactToChanges() {
+ if (!connectedContinuation) return;
+ changesWaiting = false;
+ isWaitingForServer = true;
+ getLocalChangesForNode_autoAckIfEmpty(node, function (changes, remoteBaseRevision, partial, nodeModificationsOnAck) {
+ if (!connectedContinuation) return;
+ if (changes.length > 0) {
+ continuation.react(changes, remoteBaseRevision, partial, function onChangesAccepted() {
+ Object.keys(nodeModificationsOnAck).forEach(function (keyPath) {
+ Dexie.setByKeyPath(node, keyPath, nodeModificationsOnAck[keyPath]);
+ });
+ node.save();
+ // More changes may be waiting:
+ reactToChanges();
+ });
+ } else {
+ isWaitingForServer = false;
+ if (changesWaiting) {
+ // A change jumped in between the time-spot of quering _changes and getting called back with zero changes.
+ // This is an expreemely rare scenario, and eventually impossible. But need to be here because it could happen in theory.
+ reactToChanges();
+ } else {
+ changeStatusTo(Statuses.ONLINE);
+ }
+ }
+ }).catch(abortTheProvider);
+ }
+
+ reactToChanges();
+ }
+
+ // Poll Pattern
+ function continueUsingPollPattern() {
+
+ function syncAgain() {
+ getLocalChangesForNode_autoAckIfEmpty(node, function (changes, remoteBaseRevision, partial, nodeModificationsOnAck) {
+
+ protocolInstance.sync(node.syncContext, url, options, remoteBaseRevision, node.appliedRemoteRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess, onError);
+
+ function onChangesAccepted() {
+ Object.keys(nodeModificationsOnAck).forEach(function (keyPath) {
+ Dexie.setByKeyPath(node, keyPath, nodeModificationsOnAck[keyPath]);
+ });
+ node.save();
+ }
+
+ function onSuccess(continuation) {
+ if (!connectedContinuation) {
+ // Got disconnected before succeeding. Quit.
+ return;
+ }
+ connectedContinuation = continuation;
+ if (partial) {
+ // We only sent partial changes. Need to do another round asap.
+ syncAgain();
+ } else {
+ // We've sent all changes now (in sync!)
+ if (!isNaN(continuation.again) && continuation.again < Infinity) {
+ // Provider wants to keep polling. Set Status to ONLINE.
+ changeStatusTo(Statuses.ONLINE);
+ setTimeout(function () {
+ if (connectedContinuation) {
+ changeStatusTo(Statuses.SYNCING);
+ syncAgain();
+ }
+ }, continuation.again);
+ } else {
+ // Provider seems finished polling. Since we are never going to poll again,
+ // disconnect provider and set status to OFFLINE until another call to db.syncable.connect().
+ activePeer.disconnect(Statuses.OFFLINE);
+ }
+ }
+ }
+
+ function onError(error, again) {
+ if (!isNaN(again) && again < Infinity) {
+ if (connectedContinuation) {
+ setTimeout(function () {
+ if (connectedContinuation) {
+ changeStatusTo(Statuses.SYNCING);
+ syncAgain();
+ }
+ }, again);
+ changeStatusTo(Statuses.ERROR_WILL_RETRY);
+ } // else status is already changed since we got disconnected.
+ } else {
+ abortTheProvider(error); // Will fire ERROR on onStatusChanged.
+ }
+ }
+ }).catch(abortTheProvider);
+ }
+
+ if (hasMoreToGive) {
+ syncAgain();
+ } else if (connectedContinuation && !isNaN(connectedContinuation.again) && connectedContinuation.again < Infinity) {
+ changeStatusTo(Statuses.ONLINE);
+ setTimeout(function () {
+ if (connectedContinuation) {
+ changeStatusTo(Statuses.SYNCING);
+ syncAgain();
+ }
+ }, connectedContinuation.again);
+ }
+ }
+ }
+ }
+
+ db.close = override(db.close, function (origClose) {
+ return function () {
+ activePeers.forEach(function (peer) {
+ peer.disconnect();
+ });
+ return origClose.apply(this, arguments);
+ };
+ });
+
+ var syncNodeSaveQueContexts = {};
+ db.observable.SyncNode.prototype.save = function () {
+ var self = this;
+ return db.transaction('rw?', db._syncNodes, function () {
+ db._syncNodes.put(self);
+ });
+ };
+
+ function enque(context, fn, instanceID) {
+ function _enque() {
+ if (!context.ongoingOperation) {
+ context.ongoingOperation = Dexie.ignoreTransaction(function () {
+ return Dexie.vip(function () {
+ return fn();
+ });
+ }).then(function (res) {
+ delete context.ongoingOperation;
+ return res;
+ });
+ } else {
+ context.ongoingOperation = context.ongoingOperation.then(function () {
+ return enque(context, fn, instanceID);
+ });
+ }
+ return context.ongoingOperation;
+ }
+
+ if (!instanceID) {
+ // Caller wants to enque it until database becomes open.
+ if (db.isOpen()) {
+ return _enque();
+ } else {
+ return Promise.reject(new Error("Database was closed"));
+ }
+ } else if (db._localSyncNode && instanceID === db._localSyncNode.id) {
+ // DB is already open but queuer doesnt want it to be queued if database has been closed (request bound to current instance of DB)
+ return _enque();
+ } else {
+ return Promise.reject(new Error("Database was closed"));
+ }
+ }
+
+ function combineCreateAndUpdate(prevChange, nextChange) {
+ var clonedChange = Dexie.deepClone(prevChange); // Clone object before modifying since the earlier change in db.changes[] would otherwise be altered.
+ Object.keys(nextChange.mods).forEach(function (keyPath) {
+ setByKeyPath(clonedChange.obj, keyPath, nextChange.mods[keyPath]);
+ });
+ return clonedChange;
+ }
+
+ function combineUpdateAndUpdate(prevChange, nextChange) {
+ var clonedChange = Dexie.deepClone(prevChange); // Clone object before modifying since the earlier change in db.changes[] would otherwise be altered.
+ Object.keys(nextChange.mods).forEach(function (keyPath) {
+ // If prev-change was changing a parent path of this keyPath, we must update the parent path rather than adding this keyPath
+ var hadParentPath = false;
+ Object.keys(prevChange.mods).filter(function (parentPath) {
+ return keyPath.indexOf(parentPath + '.') === 0;
+ }).forEach(function (parentPath) {
+ setByKeyPath(clonedChange[parentPath], keyPath.substr(parentPath.length + 1), nextChange.mods[keyPath]);
+ hadParentPath = true;
+ });
+ if (!hadParentPath) {
+ // Add or replace this keyPath and its new value
+ clonedChange.mods[keyPath] = nextChange.mods[keyPath];
+ }
+ // In case prevChange contained sub-paths to the new keyPath, we must make sure that those sub-paths are removed since
+ // we must mimic what would happen if applying the two changes after each other:
+ Object.keys(prevChange.mods).filter(function (subPath) {
+ return subPath.indexOf(keyPath + '.') === 0;
+ }).forEach(function (subPath) {
+ delete clonedChange[subPath];
+ });
+ });
+ return clonedChange;
+ }
+}
+
+Syncable.Statuses = {
+ ERROR: -1, // An irrepairable error occurred and the sync provider is dead.
+ OFFLINE: 0, // The sync provider hasnt yet become online, or it has been disconnected.
+ CONNECTING: 1, // Trying to connect to server
+ ONLINE: 2, // Connected to server and currently in sync with server
+ SYNCING: 3, // Syncing with server. For poll pattern, this is every poll call. For react pattern, this is when local changes are being sent to server.
+ ERROR_WILL_RETRY: 4 // An error occured such as net down but the sync provider will retry to connect.
+};
+
+Syncable.StatusTexts = {
+ "-1": "ERROR",
+ "0": "OFFLINE",
+ "1": "CONNECTING",
+ "2": "ONLINE",
+ "3": "SYNCING",
+ "4": "ERROR_WILL_RETRY"
+};
+
+Syncable.registeredProtocols = {}; // Map when key is the provider name.
+
+Syncable.registerSyncProtocol = function (name, protocolInstance) {
+ ///
+ /// Register a syncronization protocol that can syncronize databases with remote servers.
+ ///
+ /// Provider name
+ /// Implementation of ISyncProtocol
+ Syncable.registeredProtocols[name] = protocolInstance;
+};
+
+// Register addon in Dexie:
+Dexie.Syncable = Syncable;
+Dexie.addons.push(Syncable);
+
+return Syncable;
+
+})));
+//# sourceMappingURL=dexie-syncable.js.map
diff --git a/addons/Dexie.Syncable/dist/dexie-syncable.js.map b/addons/Dexie.Syncable/dist/dexie-syncable.js.map
new file mode 100644
index 000000000..ae32a2552
--- /dev/null
+++ b/addons/Dexie.Syncable/dist/dexie-syncable.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"dexie-syncable.js","sources":["../tools/tmp/src/Dexie.Syncable.js"],"sourcesContent":["/// \n/// \n/// \n/**\r\n * Dexie.Syncable.js\r\n * ===================\r\n * Dexie addon for syncing indexedDB with remote endpoints.\r\n *\r\n * version: {version} Alpha, {date}\r\n *\r\n * Disclaimber: This addon is in alpha status meaning that\r\n * its API and behavior may change.\r\n *\r\n */\n\nimport Dexie from \"dexie\";\n// Depend on 'dexie-observable'\n// To support both ES6,AMD,CJS and UMD (plain script), we just import it and then access it as \"Dexie.Observable\".\n// That way, our plugin works in all UMD cases.\n// If target platform would only be module based (ES6/AMD/CJS), we could have done 'import Observable from \"dexie-observable\"'.\nimport \"dexie-observable\";\n\nvar override = Dexie.override,\n Promise = Dexie.Promise,\n setByKeyPath = Dexie.setByKeyPath,\n Observable = Dexie.Observable;\n\nexport default function Syncable(db) {\n /// \n\n var activePeers = [];\n\n // Change Types\n var CREATE = 1,\n UPDATE = 2,\n DELETE = 3;\n\n // Statuses\n var Statuses = Syncable.Statuses;\n\n var MAX_CHANGES_PER_CHUNK = 1000;\n\n db.on('message', function (msg) {\n // Message from other local node arrives...\n Dexie.vip(function () {\n if (msg.type === 'connect') {\n // We are master node and another non-master node wants us to do the connect.\n db.syncable.connect(msg.protocolName, msg.url, msg.options).then(msg.resolve, msg.reject);\n } else if (msg.type === 'disconnect') {\n db.syncable.disconnect(msg.url).then(msg.resolve, msg.reject);\n } else if (msg.type === 'syncStatusChanged') {\n // We are client and a master node informs us about syncStatus change.\n // Lookup the connectedProvider and call its event\n db.syncable.on.statusChanged.fire(msg.newStatus, msg.url);\n }\n });\n });\n\n db.on('cleanup', function (weBecameMaster) {\n // A cleanup (done in Dexie.Observable) may result in that a master node is removed and we become master.\n if (weBecameMaster) {\n // We took over the master role in Observable's cleanup method\n db._syncNodes.where('type').equals('remote').and(function (node) {\n return node.status !== Statuses.OFFLINE && node.status !== Statuses.ERROR;\n }).each(function (connectedRemoteNode) {\n // There are connected remote nodes that we must take over\n // Since we may be in the on(ready) event, we must get VIPed to continue\n Dexie.ignoreTransaction(function () {\n Dexie.vip(function () {\n db.syncable.connect(connectedRemoteNode.syncProtocol, connectedRemoteNode.url, connectedRemoteNode.syncOptions);\n });\n });\n });\n }\n });\n\n db.on('ready', function onReady() {\n // Again, in onReady: If we ARE master, make sure to connect to remote servers that is in a connected state.\n if (db._localSyncNode && db._localSyncNode.isMaster) {\n // Make sure to connect to remote servers that is in a connected state (NOT OFFLINE or ERROR!)\n return db._syncNodes.where('type').equals('remote').and(function (node) {\n return node.status !== Statuses.OFFLINE && node.status !== Statuses.ERROR;\n }).toArray(function (connectedRemoteNodes) {\n // There are connected remote nodes that we must take over\n if (connectedRemoteNodes.length > 0) {\n return Promise.all(connectedRemoteNodes.map(function (node) {\n return db.syncable.connect(node.syncProtocol, node.url, node.syncOptions).catch(function (err) {\n return undefined; // If a node fails to connect, don't make db.open() reject. Accept it!\n });\n }));\n }\n });\n }\n }, true); // True means the ready event will survive a db reopen - db.close()/db.open()\n\n\n db.syncable = {};\n\n db.syncable.getStatus = function (url, cb) {\n if (db.isOpen()) {\n return Dexie.vip(function () {\n return db._syncNodes.where('url').equals(url).first(function (node) {\n return node ? node.status : Statuses.OFFLINE;\n });\n }).then(cb);\n } else {\n return Promise.resolve(Syncable.Statuses.OFFLINE).then(cb);\n }\n };\n\n db.syncable.list = function () {\n return db._syncNodes.where('type').equals('remote').toArray(function (a) {\n return a.map(function (node) {\n return node.url;\n });\n });\n };\n\n db.syncable.on = Dexie.Events(db, { statusChanged: \"asap\" });\n\n db.syncable.disconnect = function (url) {\n if (db._localSyncNode && db._localSyncNode.isMaster) {\n activePeers.filter(function (peer) {\n return peer.url === url;\n }).forEach(function (peer) {\n peer.disconnect(Statuses.OFFLINE);\n });\n } else {\n db._syncNodes.where('isMaster').above(0).first(function (masterNode) {\n db.sendMessage('disconnect', { url: url }, masterNode.id, { wantReply: true });\n });\n }\n\n return db._syncNodes.where(\"url\").equals(url).modify(function (node) {\n node.status = Statuses.OFFLINE;\n });\n };\n\n db.syncable.connect = function (protocolName, url, options) {\n options = options || {}; // Make sure options is always an object because 1) Provider expects it to be. 2) We'll be persisting it and you cannot persist undefined.\n var protocolInstance = Syncable.registeredProtocols[protocolName];\n\n if (protocolInstance) {\n if (db.isOpen() && db._localSyncNode) {\n // Database is open\n if (db._localSyncNode.isMaster) {\n // We are master node\n return connect(protocolInstance, protocolName, url, options, db._localSyncNode.id);\n } else {\n // We are not master node\n // Request master node to do the connect:\n db.table('_syncNodes').where('isMaster').above(0).first(function (masterNode) {\n // There will always be a master node. In theory we may self have become master node when we come here. But that's ok. We'll request ourselves.\n return db.sendMessage('connect', { protocolName: protocolName, url: url, options: options }, masterNode.id, { wantReply: true });\n });\n return Promise.resolve();\n }\n } else {\n // Database not yet open\n // Wait for it to open\n return new Promise(function (resolve, reject) {\n db.on(\"ready\", function syncWhenReady() {\n return Dexie.vip(function () {\n return db.syncable.connect(protocolName, url, options).then(resolve).catch(function (err) {\n // Reject the promise returned to the caller of db.syncable.connect():\n reject(err);\n // but resolve the promise that db.on(\"ready\") waits for, because database should succeed to open even if the connect operation fails!\n });\n });\n });\n });\n }\n } else {\n throw new Error(\"ISyncProtocol '\" + protocolName + \"' is not registered in Dexie.Syncable.registerSyncProtocol()\");\n return new Promise(); // For code completion\n }\n };\n\n db.syncable.delete = function (url) {\n // Notice: Caller should call db.syncable.disconnect(url) and wait for it to finish before calling db.syncable.delete(url)\n // Surround with a readwrite-transaction\n return db.transaction('rw', db._syncNodes, db._changes, db._uncommittedChanges, function () {\n // Find the node\n db._syncNodes.where(\"url\").equals(url).toArray(function (nodes) {\n // If it's found (or even several found, as detected by @martindiphoorn),\n // let's delete it (or them) and cleanup _changes and _uncommittedChanges\n // accordingly.\n if (nodes.length > 0) {\n var nodeIDs = nodes.map(function (node) {\n return node.id;\n });\n // The following 'return' statement is not needed right now, but I leave it \n // there because if we would like to add a 'then()' statement to the main ,\n // operation above ( db._syncNodes.where(\"url\").equals(url).toArray(...) ) , \n // this return statement will asure that the whole chain is waited for \n // before entering the then() callback.\n return db._syncNodes.where('id').anyOf(nodeIDs).delete().then(function () {\n // When theese nodes are gone, let's clear the _changes table\n // from all revisions older than the oldest node.\n // Delete all changes older than revision of oldest node:\n Observable.deleteOldChanges();\n // Also don't forget to delete all uncommittedChanges for the deleted node:\n return db._uncommittedChanges.where('node').anyOf(nodeIDs).delete();\n });\n }\n });\n });\n };\n\n db.syncable.unsyncedChanges = function (url) {\n return db._syncNodes.where(\"url\").equals(url).first(function (node) {\n return db._changes.where('rev').above(node.myRevision).toArray();\n });\n };\n\n function connect(protocolInstance, protocolName, url, options, dbAliveID) {\n /// \n var existingPeer = activePeers.filter(function (peer) {\n return peer.url === url;\n });\n if (existingPeer.length > 0) {\n // Never create multiple syncNodes with same protocolName and url. Instead, let the next call to connect() return the same promise that\n // have already been started and eventually also resolved. If promise has already resolved (node connected), calling existing promise.then() will give a callback directly.\n return existingPeer[0].connectPromise;\n }\n\n var connectPromise = getOrCreateSyncNode(options).then(function (node) {\n return connectProtocol(node);\n });\n\n var rejectConnectPromise = null;\n var disconnected = false;\n var hasMoreToGive = true;\n var activePeer = {\n url: url,\n status: Statuses.OFFLINE,\n connectPromise: connectPromise,\n on: Dexie.Events(null, \"disconnect\"),\n disconnect: function (newStatus, error) {\n if (!disconnected) {\n activePeer.on.disconnect.fire(newStatus, error);\n var pos = activePeers.indexOf(activePeer);\n if (pos >= 0) activePeers.splice(pos, 1);\n if (error && rejectConnectPromise) rejectConnectPromise(error);\n }\n disconnected = true;\n }\n };\n activePeers.push(activePeer);\n\n return connectPromise;\n\n function stillAlive() {\n // A better method than doing db.isOpen() because the same db instance may have been reopened, but then this sync call should be dead\n // because the new instance should be considered a fresh instance and will have another local node.\n return db._localSyncNode && db._localSyncNode.id === dbAliveID;\n }\n\n function getOrCreateSyncNode(options) {\n return db.transaction('rw', db._syncNodes, function () {\n if (!url) throw new Error(\"Url cannot be empty\");\n // Returning a promise from transaction scope will make the transaction promise resolve with the value of that promise.\n\n\n return db._syncNodes.where(\"url\").equalsIgnoreCase(url).first(function (node) {\n //\n // PersistedContext : IPersistedContext\n //\n function PersistedContext(nodeID, otherProps) {\n this.nodeID = nodeID;\n if (otherProps) Dexie.extend(this, otherProps);\n }\n\n PersistedContext.prototype.save = function () {\n // Store this instance in the syncContext property of the node it belongs to.\n return Dexie.vip(function () {\n return node.save();\n });\n };\n\n if (node) {\n // Node already there. Make syncContext become an instance of PersistedContext:\n node.syncContext = new PersistedContext(node.id, node.syncContext);\n node.syncProtocol = protocolName; // In case it was changed (would be very strange but...) could happen...\n db._syncNodes.put(node);\n } else {\n // Create new node and sync everything\n node = new db.observable.SyncNode();\n node.myRevision = -1;\n node.appliedRemoteRevision = null;\n node.remoteBaseRevisions = [];\n node.type = \"remote\";\n node.syncProtocol = protocolName;\n node.url = url;\n node.syncOptions = options;\n node.lastHeartBeat = Date.now();\n node.dbUploadState = null;\n Promise.resolve(function () {\n // If options.initialUpload is explicitely false, set myRevision to currentRevision.\n if (options.initialUpload === false) return db._changes.lastKey(function (currentRevision) {\n node.myRevision = currentRevision;\n });\n }).then(function () {\n db._syncNodes.add(node).then(function (nodeId) {\n node.syncContext = new PersistedContext(nodeId); // Update syncContext in db with correct nodeId.\n db._syncNodes.put(node);\n });\n });\n }\n\n return node; // returning node will make the db.transaction()-promise resolve with this value.\n });\n });\n }\n\n function connectProtocol(node) {\n /// \n\n function changeStatusTo(newStatus) {\n if (node.status !== newStatus) {\n node.status = newStatus;\n node.save();\n db.syncable.on.statusChanged.fire(newStatus, url);\n // Also broadcast message to other nodes about the status\n db.broadcastMessage(\"syncStatusChanged\", { newStatus: newStatus, url: url }, false);\n }\n }\n\n activePeer.on('disconnect', function (newStatus) {\n if (!isNaN(newStatus)) changeStatusTo(newStatus);\n });\n\n var connectedContinuation;\n changeStatusTo(Statuses.CONNECTING);\n return doSync();\n\n function doSync() {\n // Use enque() to ensure only a single promise execution at a time.\n return enque(doSync, function () {\n // By returning the Promise returned by getLocalChangesForNode() a final catch() on the sync() method will also catch error occurring in entire sequence.\n return getLocalChangesForNode_autoAckIfEmpty(node, function sendChangesToProvider(changes, remoteBaseRevision, partial, nodeModificationsOnAck) {\n // Create a final Promise for the entire sync() operation that will resolve when provider calls onSuccess().\n // By creating finalPromise before calling protocolInstance.sync() it is possible for provider to call onError() immediately if it wants.\n var finalSyncPromise = new Promise(function (resolve, reject) {\n rejectConnectPromise = function (err) {\n reject(err);\n };\n Dexie.asap(function () {\n try {\n protocolInstance.sync(node.syncContext, url, options, remoteBaseRevision, node.appliedRemoteRevision, changes, partial, applyRemoteChanges, onChangesAccepted, function (continuation) {\n resolve(continuation);\n }, onError);\n } catch (ex) {\n onError(ex, Infinity);\n }\n\n function onError(error, again) {\n reject(error);\n if (stillAlive()) {\n if (!isNaN(again) && again < Infinity) {\n setTimeout(function () {\n if (stillAlive()) {\n changeStatusTo(Statuses.SYNCING);\n doSync();\n }\n }, again);\n changeStatusTo(Statuses.ERROR_WILL_RETRY, error);\n if (connectedContinuation && connectedContinuation.disconnect) connectedContinuation.disconnect();\n connectedContinuation = null;\n } else {\n abortTheProvider(error); // Will fire ERROR on statusChanged event.\n }\n }\n }\n });\n });\n\n return finalSyncPromise.then(function () {\n // Resolve caller of db.syncable.connect() with undefined. Not with continuation!\n });\n\n function onChangesAccepted() {\n Object.keys(nodeModificationsOnAck).forEach(function (keyPath) {\n Dexie.setByKeyPath(node, keyPath, nodeModificationsOnAck[keyPath]);\n });\n node.save();\n // We dont know if onSuccess() was called by provider yet. If it's already called, finalPromise.then() will execute immediately,\n // otherwise it will execute when finalSyncPromise resolves.\n finalSyncPromise.then(continueSendingChanges);\n }\n });\n }, dbAliveID);\n }\n\n function abortTheProvider(error) {\n activePeer.disconnect(Statuses.ERROR, error);\n }\n\n function getBaseRevisionAndMaxClientRevision(node) {\n /// \n if (node.remoteBaseRevisions.length === 0) return {\n // No remoteBaseRevisions have arrived yet. No limit on clientRevision and provide null as remoteBaseRevision:\n maxClientRevision: Infinity,\n remoteBaseRevision: null\n };\n for (var i = node.remoteBaseRevisions.length - 1; i >= 0; --i) {\n if (node.myRevision >= node.remoteBaseRevisions[i].local) {\n // Found a remoteBaseRevision that fits node.myRevision. Return remoteBaseRevision and eventually a roof maxClientRevision pointing out where next remoteBaseRevision bases its changes on.\n return {\n maxClientRevision: i === node.remoteBaseRevisions.length - 1 ? Infinity : node.remoteBaseRevisions[i + 1].local,\n remoteBaseRevision: node.remoteBaseRevisions[i].remote\n };\n }\n }\n // There are at least one item in the list but the server hasnt yet become up-to-date with the 0 revision from client. \n return {\n maxClientRevision: node.remoteBaseRevisions[0].local,\n remoteBaseRevision: null\n };\n }\n\n function getLocalChangesForNode_autoAckIfEmpty(node, cb) {\n return getLocalChangesForNode(node, function autoAck(changes, remoteBaseRevision, partial, nodeModificationsOnAck) {\n if (changes.length === 0 && 'myRevision' in nodeModificationsOnAck && nodeModificationsOnAck.myRevision !== node.myRevision) {\n Object.keys(nodeModificationsOnAck).forEach(function (keyPath) {\n Dexie.setByKeyPath(node, keyPath, nodeModificationsOnAck[keyPath]);\n });\n node.save();\n return getLocalChangesForNode(node, autoAck);\n } else {\n return cb(changes, remoteBaseRevision, partial, nodeModificationsOnAck);\n }\n });\n }\n\n function getLocalChangesForNode(node, cb) {\n /// \n /// Based on given node's current revision and state, this function makes sure to retrieve next chunk of changes\n /// for that node.\n /// \n /// \n /// Callback that will retrieve next chunk of changes and a boolean telling if it's a partial result or not. If truthy, result is partial and there are more changes to come. If falsy, these changes are the final result.\n\n if (node.myRevision >= 0) {\n // Node is based on a revision in our local database and will just need to get the changes that has occurred since that revision.\n var brmcr = getBaseRevisionAndMaxClientRevision(node);\n return getChangesSinceRevision(node.myRevision, MAX_CHANGES_PER_CHUNK, brmcr.maxClientRevision, function (changes, partial, nodeModificationsOnAck) {\n return cb(changes, brmcr.remoteBaseRevision, partial, nodeModificationsOnAck);\n });\n } else {\n // Node hasn't got anything from our local database yet. We will need to upload entire DB to the node in the form of CREATE changes.\n // Check if we're in the middle of already doing that:\n if (node.dbUploadState === null) {\n // Initiatalize dbUploadState\n var tablesToUpload = db.tables.filter(function (table) {\n return table.schema.observable;\n }).map(function (table) {\n return table.name;\n });\n if (tablesToUpload.length === 0) return Promise.resolve(cb([], null, false, {})); // There are no synched tables at all.\n var dbUploadState = {\n tablesToUpload: tablesToUpload,\n currentTable: tablesToUpload.shift(),\n currentKey: null\n };\n return db._changes.orderBy('rev').last(function (lastChange) {\n dbUploadState.localBaseRevision = lastChange && lastChange.rev || 0;\n var collection = db.table(dbUploadState.currentTable).orderBy(':id');\n return getTableObjectsAsChanges(dbUploadState, [], collection);\n });\n } else if (node.dbUploadState.currentKey) {\n var collection = db.table(node.dbUploadState.currentTable).where(':id').above(node.dbUploadState.currentKey);\n return getTableObjectsAsChanges(Dexie.deepClone(node.dbUploadState), [], collection);\n } else {\n var collection = db.table(dbUploadState.currentTable).orderBy(':id');\n return getTableObjectsAsChanges(Dexie.deepClone(node.dbUploadState), [], collection);\n }\n }\n\n function getTableObjectsAsChanges(state, changes, collection) {\n /// \n /// \n /// \n var limitReached = false;\n return collection.until(function () {\n if (changes.length === MAX_CHANGES_PER_CHUNK) {\n limitReached = true;\n return true;\n }\n }).each(function (item, cursor) {\n changes.push({\n type: CREATE,\n table: state.currentTable,\n key: cursor.key,\n obj: cursor.value\n });\n state.currentKey = cursor.key;\n }).then(function () {\n if (limitReached) {\n // Limit reached. Send partial result.\n hasMoreToGive = true;\n return cb(changes, null, true, { dbUploadState: state });\n } else {\n // Done iterating this table. Check if there are more tables to go through:\n if (state.tablesToUpload.length === 0) {\n // Done iterating all tables\n // Now append changes occurred during our dbUpload:\n var brmcr = getBaseRevisionAndMaxClientRevision(node);\n return getChangesSinceRevision(state.localBaseRevision, MAX_CHANGES_PER_CHUNK - changes.length, brmcr.maxClientRevision, function (additionalChanges, partial, nodeModificationsOnAck) {\n changes = changes.concat(additionalChanges);\n nodeModificationsOnAck.dbUploadState = null;\n return cb(changes, brmcr.remoteBaseRevision, partial, nodeModificationsOnAck);\n });\n } else {\n // Not done iterating all tables. Continue on next table:\n state.currentTable = state.tablesToUpload.shift();\n return getTableObjectsAsChanges(state, changes, db.table(state.currentTable).orderBy(':id'));\n }\n }\n });\n }\n\n function getChangesSinceRevision(revision, maxChanges, maxRevision, cb) {\n /// Callback that will retrieve next chunk of changes and a boolean telling if it's a partial result or not. If truthy, result is partial and there are more changes to come. If falsy, these changes are the final result.\n var changeSet = {};\n var numChanges = 0;\n var partial = false;\n var ignoreSource = node.id;\n var nextRevision = revision;\n return db.transaction('r', db._changes, function () {\n var query = maxRevision === Infinity ? db._changes.where('rev').above(revision) : db._changes.where('rev').between(revision, maxRevision, false, true);\n query.until(function () {\n if (numChanges === maxChanges) {\n partial = true;\n return true;\n }\n }).each(function (change) {\n // Note the revision in nextRevision:\n nextRevision = change.rev;\n if (change.source === ignoreSource) return;\n // Our _changes table contains more info than required (old objs, source etc). Just make sure to include the nescessary info:\n var changeToSend = {\n type: change.type,\n table: change.table,\n key: change.key\n };\n if (change.type === CREATE) changeToSend.obj = change.obj;else if (change.type === UPDATE) changeToSend.mods = change.mods;\n\n var id = change.table + \":\" + change.key;\n var prevChange = changeSet[id];\n if (!prevChange) {\n // This is the first change on this key. Add it unless it comes from the source that we are working against\n changeSet[id] = changeToSend;\n ++numChanges;\n } else {\n // Merge the oldchange with the new change\n var nextChange = changeToSend;\n var mergedChange = function () {\n switch (prevChange.type) {\n case CREATE:\n switch (nextChange.type) {\n case CREATE:\n return nextChange; // Another CREATE replaces previous CREATE.\n case UPDATE:\n return combineCreateAndUpdate(prevChange, nextChange); // Apply nextChange.mods into prevChange.obj\n case DELETE:\n return nextChange; // Object created and then deleted. If it wasnt for that we MUST handle resent changes, we would skip entire change here. But what if the CREATE was sent earlier, and then CREATE/DELETE at later stage? It would become a ghost object in DB. Therefore, we MUST keep the delete change! If object doesnt exist, it wont harm!\n }\n break;\n case UPDATE:\n switch (nextChange.type) {\n case CREATE:\n return nextChange; // Another CREATE replaces previous update.\n case UPDATE:\n return combineUpdateAndUpdate(prevChange, nextChange); // Add the additional modifications to existing modification set.\n case DELETE:\n return nextChange; // Only send the delete change. What was updated earlier is no longer of interest.\n }\n break;\n case DELETE:\n switch (nextChange.type) {\n case CREATE:\n return nextChange; // A resurection occurred. Only create change is of interest.\n case UPDATE:\n return prevChange; // Nothing to do. We cannot update an object that doesnt exist. Leave the delete change there.\n case DELETE:\n return prevChange; // Still a delete change. Leave as is.\n }\n break;\n }\n }();\n changeSet[id] = mergedChange;\n }\n });\n }).then(function () {\n var changes = Object.keys(changeSet).map(function (key) {\n return changeSet[key];\n });\n hasMoreToGive = partial;\n return cb(changes, partial, { myRevision: nextRevision });\n });\n }\n }\n\n function applyRemoteChanges(remoteChanges, remoteRevision, partial, clear) {\n return enque(applyRemoteChanges, function () {\n if (!stillAlive()) return Promise.reject(\"Database not open\");\n // FIXTHIS: Check what to do if clear() is true!\n return (partial ? saveToUncommitedChanges(remoteChanges) : finallyCommitAllChanges(remoteChanges, remoteRevision)).catch(function (error) {\n abortTheProvider(error);\n return Promise.reject(error);\n });\n }, dbAliveID);\n\n function saveToUncommitedChanges(changes) {\n return db.transaction('rw', db._uncommittedChanges, function () {\n changes.forEach(function (change) {\n var changeToAdd = {\n node: node.id,\n type: change.type,\n table: change.table,\n key: change.key\n };\n if (change.obj) changeToAdd.obj = change.obj;\n if (change.mods) changeToAdd.mods = change.mods;\n db._uncommittedChanges.add(changeToAdd);\n });\n }).then(function () {\n node.appliedRemoteRevision = remoteRevision;\n node.save();\n });\n }\n\n function finallyCommitAllChanges(changes, remoteRevision) {\n //alert(\"finallyCommitAllChanges() will now start its job.\");\n //var tick = Date.now();\n\n // 1. Open a write transaction on all tables in DB\n return db.transaction('rw', db.tables.filter(function (table) {\n return table.name === '_changes' || table.name === '_uncommittedChanges' || table.schema.observable;\n }), function () {\n var trans = Dexie.currentTransaction;\n var localRevisionBeforeChanges = 0;\n db._changes.orderBy('rev').last(function (lastChange) {\n // Store what revision we were at before committing the changes\n localRevisionBeforeChanges = lastChange && lastChange.rev || 0;\n }).then(function () {\n // Specify the source. Important for the change consumer to ignore changes originated from self!\n trans.source = node.id;\n // 2. Apply uncommitted changes and delete each uncommitted change\n return db._uncommittedChanges.where('node').equals(node.id).toArray();\n }).then(function (uncommittedChanges) {\n return applyChanges(uncommittedChanges, 0);\n }).then(function () {\n return db._uncommittedChanges.where('node').equals(node.id).delete();\n }).then(function () {\n // 3. Apply last chunk of changes\n return applyChanges(changes, 0);\n }).then(function () {\n // Get what revision we are at now:\n return db._changes.orderBy('rev').last();\n }).then(function (lastChange) {\n var currentLocalRevision = lastChange && lastChange.rev || 0;\n // 4. Update node states (appliedRemoteRevision, remoteBaseRevisions and eventually myRevision)\n node.appliedRemoteRevision = remoteRevision;\n node.remoteBaseRevisions.push({ remote: remoteRevision, local: currentLocalRevision });\n if (node.myRevision === localRevisionBeforeChanges) {\n // If server was up-to-date before we added new changes from the server, update myRevision to last change\n // because server is still up-to-date! This is also important in order to prohibit getLocalChangesForNode() from\n // ever sending an empty change list to server, which would otherwise be done every second time it would send changes.\n node.myRevision = currentLocalRevision;\n }\n // Garbage collect remoteBaseRevisions not in use anymore:\n if (node.remoteBaseRevisions.length > 1) {\n for (var i = node.remoteBaseRevisions.length - 1; i > 0; --i) {\n if (node.myRevision >= node.remoteBaseRevisions[i].local) {\n node.remoteBaseRevisions.splice(0, i);\n break;\n }\n }\n }\n node.save(); // We are not including _syncNodes in transaction, so this save() call will execute in its own transaction.\n //var tock = Date.now();\n //alert(\"finallyCommitAllChanges() has done its job. \" + changes.length + \" changes applied in \" + ((tock - tick) / 1000) + \"seconds\");\n });\n\n function applyChanges(changes, offset) {\n /// \n /// \n var lastChangeType = 0;\n var lastCreatePromise = null;\n if (offset >= changes.length) return Promise.resolve(null);\n var change = changes[offset];\n var table = db.table(change.table);\n while (change && change.type === CREATE) {\n // Optimize CREATE changes because on initial sync with server, the entire DB will be downloaded in forms of CREATE changes.\n // Instead of waiting for each change to resolve, do all CREATE changes in bulks until another type of change is stepped upon.\n // This case is the only case that allows i to increment and the for-loop to continue since it does not return anything.\n var specifyKey = !table.schema.primKey.keyPath;\n lastCreatePromise = function (change, table, specifyKey) {\n return (specifyKey ? table.add(change.obj, change.key) : table.add(change.obj)).catch(\"ConstraintError\", function (e) {\n return specifyKey ? table.put(change.obj, change.key) : table.put(change.obj);\n });\n }(change, table, specifyKey);\n change = changes[++offset];\n if (change) table = db.table(change.table);\n }\n\n if (lastCreatePromise) {\n // We did some CREATE changes but now stumbled upon another type of change.\n // Let's wait for the last CREATE change to resolve and then call applyChanges again at current position. Next time, lastCreatePromise will be null and a case below will happen.\n return lastCreatePromise.then(function () {\n return offset < changes.length ? applyChanges(changes, offset) : null;\n });\n }\n\n if (change) {\n if (change.type === UPDATE) {\n return table.update(change.key, change.mods).then(function () {\n // Wait for update to resolve before taking next change. Why? Because it will lock transaction anyway since we are listening to CRUD events here.\n return applyChanges(changes, offset + 1);\n });\n }\n\n if (change.type === DELETE) {\n return table.delete(change.key).then(function () {\n // Wait for delete to resolve before taking next change. Why? Because it will lock transaction anyway since we are listening to CRUD events here.\n return applyChanges(changes, offset + 1);\n });\n }\n }\n\n return Promise.resolve(null); // Will return null or a Promise and make the entire applyChanges promise finally resolve.\n }\n });\n }\n }\n\n //\n //\n // Continuation Patterns Follows\n //\n //\n\n function continueSendingChanges(continuation) {\n if (!stillAlive()) {\n // Database was closed.\n if (continuation.disconnect) continuation.disconnect();\n return;\n }\n\n connectedContinuation = continuation;\n activePeer.on('disconnect', function () {\n if (connectedContinuation) {\n if (connectedContinuation.react) {\n try {\n // react pattern must provide a disconnect function.\n connectedContinuation.disconnect();\n } catch (e) {}\n }\n connectedContinuation = null; // Stop poll() pattern from polling again and abortTheProvider() from being called twice.\n }\n });\n\n if (continuation.react) {\n continueUsingReactPattern(continuation);\n } else {\n continueUsingPollPattern(continuation);\n }\n }\n\n // React Pattern (eager)\n function continueUsingReactPattern(continuation) {\n var changesWaiting, // Boolean\n isWaitingForServer; // Boolean\n\n\n function onChanges() {\n if (connectedContinuation) {\n changeStatusTo(Statuses.SYNCING);\n if (isWaitingForServer) changesWaiting = true;else {\n reactToChanges();\n }\n }\n }\n\n db.on('changes', onChanges);\n\n // Override disconnect() to also unsubscribe to onChanges.\n activePeer.on('disconnect', function () {\n db.on.changes.unsubscribe(onChanges);\n });\n\n function reactToChanges() {\n if (!connectedContinuation) return;\n changesWaiting = false;\n isWaitingForServer = true;\n getLocalChangesForNode_autoAckIfEmpty(node, function (changes, remoteBaseRevision, partial, nodeModificationsOnAck) {\n if (!connectedContinuation) return;\n if (changes.length > 0) {\n continuation.react(changes, remoteBaseRevision, partial, function onChangesAccepted() {\n Object.keys(nodeModificationsOnAck).forEach(function (keyPath) {\n Dexie.setByKeyPath(node, keyPath, nodeModificationsOnAck[keyPath]);\n });\n node.save();\n // More changes may be waiting:\n reactToChanges();\n });\n } else {\n isWaitingForServer = false;\n if (changesWaiting) {\n // A change jumped in between the time-spot of quering _changes and getting called back with zero changes.\n // This is an expreemely rare scenario, and eventually impossible. But need to be here because it could happen in theory.\n reactToChanges();\n } else {\n changeStatusTo(Statuses.ONLINE);\n }\n }\n }).catch(abortTheProvider);\n }\n\n reactToChanges();\n }\n\n // Poll Pattern\n function continueUsingPollPattern() {\n\n function syncAgain() {\n getLocalChangesForNode_autoAckIfEmpty(node, function (changes, remoteBaseRevision, partial, nodeModificationsOnAck) {\n\n protocolInstance.sync(node.syncContext, url, options, remoteBaseRevision, node.appliedRemoteRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess, onError);\n\n function onChangesAccepted() {\n Object.keys(nodeModificationsOnAck).forEach(function (keyPath) {\n Dexie.setByKeyPath(node, keyPath, nodeModificationsOnAck[keyPath]);\n });\n node.save();\n }\n\n function onSuccess(continuation) {\n if (!connectedContinuation) {\n // Got disconnected before succeeding. Quit.\n return;\n }\n connectedContinuation = continuation;\n if (partial) {\n // We only sent partial changes. Need to do another round asap.\n syncAgain();\n } else {\n // We've sent all changes now (in sync!)\n if (!isNaN(continuation.again) && continuation.again < Infinity) {\n // Provider wants to keep polling. Set Status to ONLINE.\n changeStatusTo(Statuses.ONLINE);\n setTimeout(function () {\n if (connectedContinuation) {\n changeStatusTo(Statuses.SYNCING);\n syncAgain();\n }\n }, continuation.again);\n } else {\n // Provider seems finished polling. Since we are never going to poll again,\n // disconnect provider and set status to OFFLINE until another call to db.syncable.connect().\n activePeer.disconnect(Statuses.OFFLINE);\n }\n }\n }\n\n function onError(error, again) {\n if (!isNaN(again) && again < Infinity) {\n if (connectedContinuation) {\n setTimeout(function () {\n if (connectedContinuation) {\n changeStatusTo(Statuses.SYNCING);\n syncAgain();\n }\n }, again);\n changeStatusTo(Statuses.ERROR_WILL_RETRY);\n } // else status is already changed since we got disconnected.\n } else {\n abortTheProvider(error); // Will fire ERROR on onStatusChanged.\n }\n }\n }).catch(abortTheProvider);\n }\n\n if (hasMoreToGive) {\n syncAgain();\n } else if (connectedContinuation && !isNaN(connectedContinuation.again) && connectedContinuation.again < Infinity) {\n changeStatusTo(Statuses.ONLINE);\n setTimeout(function () {\n if (connectedContinuation) {\n changeStatusTo(Statuses.SYNCING);\n syncAgain();\n }\n }, connectedContinuation.again);\n }\n }\n }\n }\n\n db.close = override(db.close, function (origClose) {\n return function () {\n activePeers.forEach(function (peer) {\n peer.disconnect();\n });\n return origClose.apply(this, arguments);\n };\n });\n\n var syncNodeSaveQueContexts = {};\n db.observable.SyncNode.prototype.save = function () {\n var self = this;\n return db.transaction('rw?', db._syncNodes, function () {\n db._syncNodes.put(self);\n });\n };\n\n function enque(context, fn, instanceID) {\n function _enque() {\n if (!context.ongoingOperation) {\n context.ongoingOperation = Dexie.ignoreTransaction(function () {\n return Dexie.vip(function () {\n return fn();\n });\n }).then(function (res) {\n delete context.ongoingOperation;\n return res;\n });\n } else {\n context.ongoingOperation = context.ongoingOperation.then(function () {\n return enque(context, fn, instanceID);\n });\n }\n return context.ongoingOperation;\n }\n\n if (!instanceID) {\n // Caller wants to enque it until database becomes open.\n if (db.isOpen()) {\n return _enque();\n } else {\n return Promise.reject(new Error(\"Database was closed\"));\n }\n } else if (db._localSyncNode && instanceID === db._localSyncNode.id) {\n // DB is already open but queuer doesnt want it to be queued if database has been closed (request bound to current instance of DB)\n return _enque();\n } else {\n return Promise.reject(new Error(\"Database was closed\"));\n }\n }\n\n function combineCreateAndUpdate(prevChange, nextChange) {\n var clonedChange = Dexie.deepClone(prevChange); // Clone object before modifying since the earlier change in db.changes[] would otherwise be altered.\n Object.keys(nextChange.mods).forEach(function (keyPath) {\n setByKeyPath(clonedChange.obj, keyPath, nextChange.mods[keyPath]);\n });\n return clonedChange;\n }\n\n function combineUpdateAndUpdate(prevChange, nextChange) {\n var clonedChange = Dexie.deepClone(prevChange); // Clone object before modifying since the earlier change in db.changes[] would otherwise be altered.\n Object.keys(nextChange.mods).forEach(function (keyPath) {\n // If prev-change was changing a parent path of this keyPath, we must update the parent path rather than adding this keyPath\n var hadParentPath = false;\n Object.keys(prevChange.mods).filter(function (parentPath) {\n return keyPath.indexOf(parentPath + '.') === 0;\n }).forEach(function (parentPath) {\n setByKeyPath(clonedChange[parentPath], keyPath.substr(parentPath.length + 1), nextChange.mods[keyPath]);\n hadParentPath = true;\n });\n if (!hadParentPath) {\n // Add or replace this keyPath and its new value\n clonedChange.mods[keyPath] = nextChange.mods[keyPath];\n }\n // In case prevChange contained sub-paths to the new keyPath, we must make sure that those sub-paths are removed since\n // we must mimic what would happen if applying the two changes after each other:\n Object.keys(prevChange.mods).filter(function (subPath) {\n return subPath.indexOf(keyPath + '.') === 0;\n }).forEach(function (subPath) {\n delete clonedChange[subPath];\n });\n });\n return clonedChange;\n }\n};\n\nSyncable.Statuses = {\n ERROR: -1, // An irrepairable error occurred and the sync provider is dead.\n OFFLINE: 0, // The sync provider hasnt yet become online, or it has been disconnected.\n CONNECTING: 1, // Trying to connect to server\n ONLINE: 2, // Connected to server and currently in sync with server\n SYNCING: 3, // Syncing with server. For poll pattern, this is every poll call. For react pattern, this is when local changes are being sent to server.\n ERROR_WILL_RETRY: 4 // An error occured such as net down but the sync provider will retry to connect.\n};\n\nSyncable.StatusTexts = {\n \"-1\": \"ERROR\",\n \"0\": \"OFFLINE\",\n \"1\": \"CONNECTING\",\n \"2\": \"ONLINE\",\n \"3\": \"SYNCING\",\n \"4\": \"ERROR_WILL_RETRY\"\n};\n\nSyncable.registeredProtocols = {}; // Map when key is the provider name.\n\nSyncable.registerSyncProtocol = function (name, protocolInstance) {\n /// \n /// Register a syncronization protocol that can syncronize databases with remote servers.\n /// \n /// Provider name\n /// Implementation of ISyncProtocol\n Syncable.registeredProtocols[name] = protocolInstance;\n};\n\n// Register addon in Dexie:\nDexie.Syncable = Syncable;\nDexie.addons.push(Syncable);"],"names":[],"mappings":";;;;;;;;AAAA;;;;;;;;;;;;;;;AAeA,AACA;;;;AAIA,AAEA,IAAI,QAAQ,GAAG,KAAK,CAAC,QAAQ;IACzB,OAAO,GAAG,KAAK,CAAC,OAAO;IACvB,YAAY,GAAG,KAAK,CAAC,YAAY;IACjC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;;AAElC,AAAe,SAAS,QAAQ,CAAC,EAAE,EAAE;;;IAGjC,IAAI,WAAW,GAAG,EAAE,CAAC;;;IAGrB,IAAI,MAAM,GAAG,CAAC;QACV,MAAM,GAAG,CAAC;QACV,MAAM,GAAG,CAAC,CAAC;;;IAGf,IAAI,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC;;IAEjC,IAAI,qBAAqB,GAAG,IAAI,CAAC;;IAEjC,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,UAAU,GAAG,EAAE;;QAE5B,KAAK,CAAC,GAAG,CAAC,YAAY;YAClB,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,EAAE;;gBAExB,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,EAAE,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;aAC7F,MAAM,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,EAAE;gBAClC,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;aACjE,MAAM,IAAI,GAAG,CAAC,IAAI,KAAK,mBAAmB,EAAE;;;gBAGzC,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC;aAC7D;SACJ,CAAC,CAAC;KACN,CAAC,CAAC;;IAEH,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,UAAU,cAAc,EAAE;;QAEvC,IAAI,cAAc,EAAE;;YAEhB,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE;gBAC7D,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,OAAO,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,KAAK,CAAC;aAC7E,CAAC,CAAC,IAAI,CAAC,UAAU,mBAAmB,EAAE;;;gBAGnC,KAAK,CAAC,iBAAiB,CAAC,YAAY;oBAChC,KAAK,CAAC,GAAG,CAAC,YAAY;wBAClB,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,mBAAmB,CAAC,YAAY,EAAE,mBAAmB,CAAC,GAAG,EAAE,mBAAmB,CAAC,WAAW,CAAC,CAAC;qBACnH,CAAC,CAAC;iBACN,CAAC,CAAC;aACN,CAAC,CAAC;SACN;KACJ,CAAC,CAAC;;IAEH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,OAAO,GAAG;;QAE9B,IAAI,EAAE,CAAC,cAAc,IAAI,EAAE,CAAC,cAAc,CAAC,QAAQ,EAAE;;YAEjD,OAAO,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE;gBACpE,OAAO,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,OAAO,IAAI,IAAI,CAAC,MAAM,KAAK,QAAQ,CAAC,KAAK,CAAC;aAC7E,CAAC,CAAC,OAAO,CAAC,UAAU,oBAAoB,EAAE;;gBAEvC,IAAI,oBAAoB,CAAC,MAAM,GAAG,CAAC,EAAE;oBACjC,OAAO,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE;wBACxD,OAAO,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC,KAAK,CAAC,UAAU,GAAG,EAAE;4BAC3F,OAAO,SAAS,CAAC;yBACpB,CAAC,CAAC;qBACN,CAAC,CAAC,CAAC;iBACP;aACJ,CAAC,CAAC;SACN;KACJ,EAAE,IAAI,CAAC,CAAC;;;IAGT,EAAE,CAAC,QAAQ,GAAG,EAAE,CAAC;;IAEjB,EAAE,CAAC,QAAQ,CAAC,SAAS,GAAG,UAAU,GAAG,EAAE,EAAE,EAAE;QACvC,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE;YACb,OAAO,KAAK,CAAC,GAAG,CAAC,YAAY;gBACzB,OAAO,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,UAAU,IAAI,EAAE;oBAChE,OAAO,IAAI,GAAG,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC;iBAChD,CAAC,CAAC;aACN,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;SACf,MAAM;YACH,OAAO,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;SAC9D;KACJ,CAAC;;IAEF,EAAE,CAAC,QAAQ,CAAC,IAAI,GAAG,YAAY;QAC3B,OAAO,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE;YACrE,OAAO,CAAC,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE;gBACzB,OAAO,IAAI,CAAC,GAAG,CAAC;aACnB,CAAC,CAAC;SACN,CAAC,CAAC;KACN,CAAC;;IAEF,EAAE,CAAC,QAAQ,CAAC,EAAE,GAAG,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,EAAE,aAAa,EAAE,MAAM,EAAE,CAAC,CAAC;;IAE7D,EAAE,CAAC,QAAQ,CAAC,UAAU,GAAG,UAAU,GAAG,EAAE;QACpC,IAAI,EAAE,CAAC,cAAc,IAAI,EAAE,CAAC,cAAc,CAAC,QAAQ,EAAE;YACjD,WAAW,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE;gBAC/B,OAAO,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC;aAC3B,CAAC,CAAC,OAAO,CAAC,UAAU,IAAI,EAAE;gBACvB,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;aACrC,CAAC,CAAC;SACN,MAAM;YACH,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,UAAU,EAAE;gBACjE,EAAE,CAAC,WAAW,CAAC,YAAY,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,UAAU,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;aAClF,CAAC,CAAC;SACN;;QAED,OAAO,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE;YACjE,IAAI,CAAC,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC;SAClC,CAAC,CAAC;KACN,CAAC;;IAEF,EAAE,CAAC,QAAQ,CAAC,OAAO,GAAG,UAAU,YAAY,EAAE,GAAG,EAAE,OAAO,EAAE;QACxD,OAAO,GAAG,OAAO,IAAI,EAAE,CAAC;QACxB,IAAI,gBAAgB,GAAG,QAAQ,CAAC,mBAAmB,CAAC,YAAY,CAAC,CAAC;;QAElE,IAAI,gBAAgB,EAAE;YAClB,IAAI,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,cAAc,EAAE;;gBAElC,IAAI,EAAE,CAAC,cAAc,CAAC,QAAQ,EAAE;;oBAE5B,OAAO,OAAO,CAAC,gBAAgB,EAAE,YAAY,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,CAAC,cAAc,CAAC,EAAE,CAAC,CAAC;iBACtF,MAAM;;;oBAGH,EAAE,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,UAAU,EAAE;;wBAE1E,OAAO,EAAE,CAAC,WAAW,CAAC,SAAS,EAAE,EAAE,YAAY,EAAE,YAAY,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,UAAU,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;qBACpI,CAAC,CAAC;oBACH,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;iBAC5B;aACJ,MAAM;;;gBAGH,OAAO,IAAI,OAAO,CAAC,UAAU,OAAO,EAAE,MAAM,EAAE;oBAC1C,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,SAAS,aAAa,GAAG;wBACpC,OAAO,KAAK,CAAC,GAAG,CAAC,YAAY;4BACzB,OAAO,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,YAAY,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,UAAU,GAAG,EAAE;;gCAEtF,MAAM,CAAC,GAAG,CAAC,CAAC;;6BAEf,CAAC,CAAC;yBACN,CAAC,CAAC;qBACN,CAAC,CAAC;iBACN,CAAC,CAAC;aACN;SACJ,MAAM;YACH,MAAM,IAAI,KAAK,CAAC,iBAAiB,GAAG,YAAY,GAAG,8DAA8D,CAAC,CAAC;YACnH,OAAO,IAAI,OAAO,EAAE,CAAC;SACxB;KACJ,CAAC;;IAEF,EAAE,CAAC,QAAQ,CAAC,MAAM,GAAG,UAAU,GAAG,EAAE;;;QAGhC,OAAO,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,EAAE,EAAE,CAAC,QAAQ,EAAE,EAAE,CAAC,mBAAmB,EAAE,YAAY;;YAExF,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,UAAU,KAAK,EAAE;;;;gBAI5D,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE;oBAClB,IAAI,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,UAAU,IAAI,EAAE;wBACpC,OAAO,IAAI,CAAC,EAAE,CAAC;qBAClB,CAAC,CAAC;;;;;;oBAMH,OAAO,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,CAAC,YAAY;;;;wBAItE,UAAU,CAAC,gBAAgB,EAAE,CAAC;;wBAE9B,OAAO,EAAE,CAAC,mBAAmB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,CAAC;qBACvE,CAAC,CAAC;iBACN;aACJ,CAAC,CAAC;SACN,CAAC,CAAC;KACN,CAAC;;IAEF,EAAE,CAAC,QAAQ,CAAC,eAAe,GAAG,UAAU,GAAG,EAAE;QACzC,OAAO,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,UAAU,IAAI,EAAE;YAChE,OAAO,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,OAAO,EAAE,CAAC;SACpE,CAAC,CAAC;KACN,CAAC;;IAEF,SAAS,OAAO,CAAC,gBAAgB,EAAE,YAAY,EAAE,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE;;QAEtE,IAAI,YAAY,GAAG,WAAW,CAAC,MAAM,CAAC,UAAU,IAAI,EAAE;YAClD,OAAO,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC;SAC3B,CAAC,CAAC;QACH,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE;;;YAGzB,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC;SACzC;;QAED,IAAI,cAAc,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE;YACnE,OAAO,eAAe,CAAC,IAAI,CAAC,CAAC;SAChC,CAAC,CAAC;;QAEH,IAAI,oBAAoB,GAAG,IAAI,CAAC;QAChC,IAAI,YAAY,GAAG,KAAK,CAAC;QACzB,IAAI,aAAa,GAAG,IAAI,CAAC;QACzB,IAAI,UAAU,GAAG;YACb,GAAG,EAAE,GAAG;YACR,MAAM,EAAE,QAAQ,CAAC,OAAO;YACxB,cAAc,EAAE,cAAc;YAC9B,EAAE,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,YAAY,CAAC;YACpC,UAAU,EAAE,UAAU,SAAS,EAAE,KAAK,EAAE;gBACpC,IAAI,CAAC,YAAY,EAAE;oBACf,UAAU,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;oBAChD,IAAI,GAAG,GAAG,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;oBAC1C,IAAI,GAAG,IAAI,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;oBACzC,IAAI,KAAK,IAAI,oBAAoB,EAAE,oBAAoB,CAAC,KAAK,CAAC,CAAC;iBAClE;gBACD,YAAY,GAAG,IAAI,CAAC;aACvB;SACJ,CAAC;QACF,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;;QAE7B,OAAO,cAAc,CAAC;;QAEtB,SAAS,UAAU,GAAG;;;YAGlB,OAAO,EAAE,CAAC,cAAc,IAAI,EAAE,CAAC,cAAc,CAAC,EAAE,KAAK,SAAS,CAAC;SAClE;;QAED,SAAS,mBAAmB,CAAC,OAAO,EAAE;YAClC,OAAO,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC,UAAU,EAAE,YAAY;gBACnD,IAAI,CAAC,GAAG,EAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC;;;;gBAIjD,OAAO,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,UAAU,IAAI,EAAE;;;;oBAI1E,SAAS,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE;wBAC1C,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;wBACrB,IAAI,UAAU,EAAE,KAAK,CAAC,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;qBAClD;;oBAED,gBAAgB,CAAC,SAAS,CAAC,IAAI,GAAG,YAAY;;wBAE1C,OAAO,KAAK,CAAC,GAAG,CAAC,YAAY;4BACzB,OAAO,IAAI,CAAC,IAAI,EAAE,CAAC;yBACtB,CAAC,CAAC;qBACN,CAAC;;oBAEF,IAAI,IAAI,EAAE;;wBAEN,IAAI,CAAC,WAAW,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,WAAW,CAAC,CAAC;wBACnE,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;wBACjC,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;qBAC3B,MAAM;;wBAEH,IAAI,GAAG,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,EAAE,CAAC;wBACpC,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC;wBACrB,IAAI,CAAC,qBAAqB,GAAG,IAAI,CAAC;wBAClC,IAAI,CAAC,mBAAmB,GAAG,EAAE,CAAC;wBAC9B,IAAI,CAAC,IAAI,GAAG,QAAQ,CAAC;wBACrB,IAAI,CAAC,YAAY,GAAG,YAAY,CAAC;wBACjC,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC;wBACf,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC;wBAC3B,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;wBAChC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;wBAC1B,OAAO,CAAC,OAAO,CAAC,YAAY;;4BAExB,IAAI,OAAO,CAAC,aAAa,KAAK,KAAK,EAAE,OAAO,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,eAAe,EAAE;gCACvF,IAAI,CAAC,UAAU,GAAG,eAAe,CAAC;6BACrC,CAAC,CAAC;yBACN,CAAC,CAAC,IAAI,CAAC,YAAY;4BAChB,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,MAAM,EAAE;gCAC3C,IAAI,CAAC,WAAW,GAAG,IAAI,gBAAgB,CAAC,MAAM,CAAC,CAAC;gCAChD,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;6BAC3B,CAAC,CAAC;yBACN,CAAC,CAAC;qBACN;;oBAED,OAAO,IAAI,CAAC;iBACf,CAAC,CAAC;aACN,CAAC,CAAC;SACN;;QAED,SAAS,eAAe,CAAC,IAAI,EAAE;;;YAG3B,SAAS,cAAc,CAAC,SAAS,EAAE;gBAC/B,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS,EAAE;oBAC3B,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC;oBACxB,IAAI,CAAC,IAAI,EAAE,CAAC;oBACZ,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,aAAa,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC;;oBAElD,EAAE,CAAC,gBAAgB,CAAC,mBAAmB,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,CAAC,CAAC;iBACvF;aACJ;;YAED,UAAU,CAAC,EAAE,CAAC,YAAY,EAAE,UAAU,SAAS,EAAE;gBAC7C,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;aACpD,CAAC,CAAC;;YAEH,IAAI,qBAAqB,CAAC;YAC1B,cAAc,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;YACpC,OAAO,MAAM,EAAE,CAAC;;YAEhB,SAAS,MAAM,GAAG;;gBAEd,OAAO,KAAK,CAAC,MAAM,EAAE,YAAY;;oBAE7B,OAAO,qCAAqC,CAAC,IAAI,EAAE,SAAS,qBAAqB,CAAC,OAAO,EAAE,kBAAkB,EAAE,OAAO,EAAE,sBAAsB,EAAE;;;wBAG5I,IAAI,gBAAgB,GAAG,IAAI,OAAO,CAAC,UAAU,OAAO,EAAE,MAAM,EAAE;4BAC1D,oBAAoB,GAAG,UAAU,GAAG,EAAE;gCAClC,MAAM,CAAC,GAAG,CAAC,CAAC;6BACf,CAAC;4BACF,KAAK,CAAC,IAAI,CAAC,YAAY;gCACnB,IAAI;oCACA,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE,OAAO,EAAE,kBAAkB,EAAE,IAAI,CAAC,qBAAqB,EAAE,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,UAAU,YAAY,EAAE;wCACnL,OAAO,CAAC,YAAY,CAAC,CAAC;qCACzB,EAAE,OAAO,CAAC,CAAC;iCACf,CAAC,OAAO,EAAE,EAAE;oCACT,OAAO,CAAC,EAAE,EAAE,QAAQ,CAAC,CAAC;iCACzB;;gCAED,SAAS,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE;oCAC3B,MAAM,CAAC,KAAK,CAAC,CAAC;oCACd,IAAI,UAAU,EAAE,EAAE;wCACd,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,QAAQ,EAAE;4CACnC,UAAU,CAAC,YAAY;gDACnB,IAAI,UAAU,EAAE,EAAE;oDACd,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;oDACjC,MAAM,EAAE,CAAC;iDACZ;6CACJ,EAAE,KAAK,CAAC,CAAC;4CACV,cAAc,CAAC,QAAQ,CAAC,gBAAgB,EAAE,KAAK,CAAC,CAAC;4CACjD,IAAI,qBAAqB,IAAI,qBAAqB,CAAC,UAAU,EAAE,qBAAqB,CAAC,UAAU,EAAE,CAAC;4CAClG,qBAAqB,GAAG,IAAI,CAAC;yCAChC,MAAM;4CACH,gBAAgB,CAAC,KAAK,CAAC,CAAC;yCAC3B;qCACJ;iCACJ;6BACJ,CAAC,CAAC;yBACN,CAAC,CAAC;;wBAEH,OAAO,gBAAgB,CAAC,IAAI,CAAC,YAAY;;yBAExC,CAAC,CAAC;;wBAEH,SAAS,iBAAiB,GAAG;4BACzB,MAAM,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,OAAO,CAAC,UAAU,OAAO,EAAE;gCAC3D,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,sBAAsB,CAAC,OAAO,CAAC,CAAC,CAAC;6BACtE,CAAC,CAAC;4BACH,IAAI,CAAC,IAAI,EAAE,CAAC;;;4BAGZ,gBAAgB,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC;yBACjD;qBACJ,CAAC,CAAC;iBACN,EAAE,SAAS,CAAC,CAAC;aACjB;;YAED,SAAS,gBAAgB,CAAC,KAAK,EAAE;gBAC7B,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;aAChD;;YAED,SAAS,mCAAmC,CAAC,IAAI,EAAE;;gBAE/C,IAAI,IAAI,CAAC,mBAAmB,CAAC,MAAM,KAAK,CAAC,EAAE,OAAO;;oBAE9C,iBAAiB,EAAE,QAAQ;oBAC3B,kBAAkB,EAAE,IAAI;iBAC3B,CAAC;gBACF,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,mBAAmB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,EAAE;oBAC3D,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE;;wBAEtD,OAAO;4BACH,iBAAiB,EAAE,CAAC,KAAK,IAAI,CAAC,mBAAmB,CAAC,MAAM,GAAG,CAAC,GAAG,QAAQ,GAAG,IAAI,CAAC,mBAAmB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK;4BAC/G,kBAAkB,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,MAAM;yBACzD,CAAC;qBACL;iBACJ;;gBAED,OAAO;oBACH,iBAAiB,EAAE,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAAK;oBACpD,kBAAkB,EAAE,IAAI;iBAC3B,CAAC;aACL;;YAED,SAAS,qCAAqC,CAAC,IAAI,EAAE,EAAE,EAAE;gBACrD,OAAO,sBAAsB,CAAC,IAAI,EAAE,SAAS,OAAO,CAAC,OAAO,EAAE,kBAAkB,EAAE,OAAO,EAAE,sBAAsB,EAAE;oBAC/G,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,YAAY,IAAI,sBAAsB,IAAI,sBAAsB,CAAC,UAAU,KAAK,IAAI,CAAC,UAAU,EAAE;wBACzH,MAAM,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,OAAO,CAAC,UAAU,OAAO,EAAE;4BAC3D,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,sBAAsB,CAAC,OAAO,CAAC,CAAC,CAAC;yBACtE,CAAC,CAAC;wBACH,IAAI,CAAC,IAAI,EAAE,CAAC;wBACZ,OAAO,sBAAsB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;qBAChD,MAAM;wBACH,OAAO,EAAE,CAAC,OAAO,EAAE,kBAAkB,EAAE,OAAO,EAAE,sBAAsB,CAAC,CAAC;qBAC3E;iBACJ,CAAC,CAAC;aACN;;YAED,SAAS,sBAAsB,CAAC,IAAI,EAAE,EAAE,EAAE;;;;;;;;gBAQtC,IAAI,IAAI,CAAC,UAAU,IAAI,CAAC,EAAE;;oBAEtB,IAAI,KAAK,GAAG,mCAAmC,CAAC,IAAI,CAAC,CAAC;oBACtD,OAAO,uBAAuB,CAAC,IAAI,CAAC,UAAU,EAAE,qBAAqB,EAAE,KAAK,CAAC,iBAAiB,EAAE,UAAU,OAAO,EAAE,OAAO,EAAE,sBAAsB,EAAE;wBAChJ,OAAO,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,kBAAkB,EAAE,OAAO,EAAE,sBAAsB,CAAC,CAAC;qBACjF,CAAC,CAAC;iBACN,MAAM;;;oBAGH,IAAI,IAAI,CAAC,aAAa,KAAK,IAAI,EAAE;;wBAE7B,IAAI,cAAc,GAAG,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,KAAK,EAAE;4BACnD,OAAO,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC;yBAClC,CAAC,CAAC,GAAG,CAAC,UAAU,KAAK,EAAE;4BACpB,OAAO,KAAK,CAAC,IAAI,CAAC;yBACrB,CAAC,CAAC;wBACH,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,CAAC;wBACjF,IAAI,aAAa,GAAG;4BAChB,cAAc,EAAE,cAAc;4BAC9B,YAAY,EAAE,cAAc,CAAC,KAAK,EAAE;4BACpC,UAAU,EAAE,IAAI;yBACnB,CAAC;wBACF,OAAO,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,UAAU,UAAU,EAAE;4BACzD,aAAa,CAAC,iBAAiB,GAAG,UAAU,IAAI,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC;4BACpE,IAAI,UAAU,GAAG,EAAE,CAAC,KAAK,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;4BACrE,OAAO,wBAAwB,CAAC,aAAa,EAAE,EAAE,EAAE,UAAU,CAAC,CAAC;yBAClE,CAAC,CAAC;qBACN,MAAM,IAAI,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE;wBACtC,IAAI,UAAU,GAAG,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;wBAC7G,OAAO,wBAAwB,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,EAAE,EAAE,UAAU,CAAC,CAAC;qBACxF,MAAM;wBACH,IAAI,UAAU,GAAG,EAAE,CAAC,KAAK,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;wBACrE,OAAO,wBAAwB,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,EAAE,EAAE,UAAU,CAAC,CAAC;qBACxF;iBACJ;;gBAED,SAAS,wBAAwB,CAAC,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE;;;;oBAI1D,IAAI,YAAY,GAAG,KAAK,CAAC;oBACzB,OAAO,UAAU,CAAC,KAAK,CAAC,YAAY;wBAChC,IAAI,OAAO,CAAC,MAAM,KAAK,qBAAqB,EAAE;4BAC1C,YAAY,GAAG,IAAI,CAAC;4BACpB,OAAO,IAAI,CAAC;yBACf;qBACJ,CAAC,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,MAAM,EAAE;wBAC5B,OAAO,CAAC,IAAI,CAAC;4BACT,IAAI,EAAE,MAAM;4BACZ,KAAK,EAAE,KAAK,CAAC,YAAY;4BACzB,GAAG,EAAE,MAAM,CAAC,GAAG;4BACf,GAAG,EAAE,MAAM,CAAC,KAAK;yBACpB,CAAC,CAAC;wBACH,KAAK,CAAC,UAAU,GAAG,MAAM,CAAC,GAAG,CAAC;qBACjC,CAAC,CAAC,IAAI,CAAC,YAAY;wBAChB,IAAI,YAAY,EAAE;;4BAEd,aAAa,GAAG,IAAI,CAAC;4BACrB,OAAO,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC,CAAC;yBAC5D,MAAM;;4BAEH,IAAI,KAAK,CAAC,cAAc,CAAC,MAAM,KAAK,CAAC,EAAE;;;gCAGnC,IAAI,KAAK,GAAG,mCAAmC,CAAC,IAAI,CAAC,CAAC;gCACtD,OAAO,uBAAuB,CAAC,KAAK,CAAC,iBAAiB,EAAE,qBAAqB,GAAG,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,iBAAiB,EAAE,UAAU,iBAAiB,EAAE,OAAO,EAAE,sBAAsB,EAAE;oCACnL,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;oCAC5C,sBAAsB,CAAC,aAAa,GAAG,IAAI,CAAC;oCAC5C,OAAO,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,kBAAkB,EAAE,OAAO,EAAE,sBAAsB,CAAC,CAAC;iCACjF,CAAC,CAAC;6BACN,MAAM;;gCAEH,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC,cAAc,CAAC,KAAK,EAAE,CAAC;gCAClD,OAAO,wBAAwB,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;6BAChG;yBACJ;qBACJ,CAAC,CAAC;iBACN;;gBAED,SAAS,uBAAuB,CAAC,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,EAAE,EAAE;;oBAEpE,IAAI,SAAS,GAAG,EAAE,CAAC;oBACnB,IAAI,UAAU,GAAG,CAAC,CAAC;oBACnB,IAAI,OAAO,GAAG,KAAK,CAAC;oBACpB,IAAI,YAAY,GAAG,IAAI,CAAC,EAAE,CAAC;oBAC3B,IAAI,YAAY,GAAG,QAAQ,CAAC;oBAC5B,OAAO,EAAE,CAAC,WAAW,CAAC,GAAG,EAAE,EAAE,CAAC,QAAQ,EAAE,YAAY;wBAChD,IAAI,KAAK,GAAG,WAAW,KAAK,QAAQ,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,WAAW,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;wBACvJ,KAAK,CAAC,KAAK,CAAC,YAAY;4BACpB,IAAI,UAAU,KAAK,UAAU,EAAE;gCAC3B,OAAO,GAAG,IAAI,CAAC;gCACf,OAAO,IAAI,CAAC;6BACf;yBACJ,CAAC,CAAC,IAAI,CAAC,UAAU,MAAM,EAAE;;4BAEtB,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC;4BAC1B,IAAI,MAAM,CAAC,MAAM,KAAK,YAAY,EAAE,OAAO;;4BAE3C,IAAI,YAAY,GAAG;gCACf,IAAI,EAAE,MAAM,CAAC,IAAI;gCACjB,KAAK,EAAE,MAAM,CAAC,KAAK;gCACnB,GAAG,EAAE,MAAM,CAAC,GAAG;6BAClB,CAAC;4BACF,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE,YAAY,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE,YAAY,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;;4BAE3H,IAAI,EAAE,GAAG,MAAM,CAAC,KAAK,GAAG,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC;4BACzC,IAAI,UAAU,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC;4BAC/B,IAAI,CAAC,UAAU,EAAE;;gCAEb,SAAS,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC;gCAC7B,EAAE,UAAU,CAAC;6BAChB,MAAM;;gCAEH,IAAI,UAAU,GAAG,YAAY,CAAC;gCAC9B,IAAI,YAAY,GAAG,YAAY;oCAC3B,QAAQ,UAAU,CAAC,IAAI;wCACnB,KAAK,MAAM;4CACP,QAAQ,UAAU,CAAC,IAAI;gDACnB,KAAK,MAAM;oDACP,OAAO,UAAU,CAAC;gDACtB,KAAK,MAAM;oDACP,OAAO,sBAAsB,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;gDAC1D,KAAK,MAAM;oDACP,OAAO,UAAU,CAAC;6CACzB;4CACD,MAAM;wCACV,KAAK,MAAM;4CACP,QAAQ,UAAU,CAAC,IAAI;gDACnB,KAAK,MAAM;oDACP,OAAO,UAAU,CAAC;gDACtB,KAAK,MAAM;oDACP,OAAO,sBAAsB,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;gDAC1D,KAAK,MAAM;oDACP,OAAO,UAAU,CAAC;6CACzB;4CACD,MAAM;wCACV,KAAK,MAAM;4CACP,QAAQ,UAAU,CAAC,IAAI;gDACnB,KAAK,MAAM;oDACP,OAAO,UAAU,CAAC;gDACtB,KAAK,MAAM;oDACP,OAAO,UAAU,CAAC;gDACtB,KAAK,MAAM;oDACP,OAAO,UAAU,CAAC;6CACzB;4CACD,MAAM;qCACb;iCACJ,EAAE,CAAC;gCACJ,SAAS,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC;6BAChC;yBACJ,CAAC,CAAC;qBACN,CAAC,CAAC,IAAI,CAAC,YAAY;wBAChB,IAAI,OAAO,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,CAAC,UAAU,GAAG,EAAE;4BACpD,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC;yBACzB,CAAC,CAAC;wBACH,aAAa,GAAG,OAAO,CAAC;wBACxB,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC,CAAC;qBAC7D,CAAC,CAAC;iBACN;aACJ;;YAED,SAAS,kBAAkB,CAAC,aAAa,EAAE,cAAc,EAAE,OAAO,EAAE,KAAK,EAAE;gBACvE,OAAO,KAAK,CAAC,kBAAkB,EAAE,YAAY;oBACzC,IAAI,CAAC,UAAU,EAAE,EAAE,OAAO,OAAO,CAAC,MAAM,CAAC,mBAAmB,CAAC,CAAC;;oBAE9D,OAAO,CAAC,OAAO,GAAG,uBAAuB,CAAC,aAAa,CAAC,GAAG,uBAAuB,CAAC,aAAa,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,KAAK,EAAE;wBACtI,gBAAgB,CAAC,KAAK,CAAC,CAAC;wBACxB,OAAO,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;qBAChC,CAAC,CAAC;iBACN,EAAE,SAAS,CAAC,CAAC;;gBAEd,SAAS,uBAAuB,CAAC,OAAO,EAAE;oBACtC,OAAO,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC,mBAAmB,EAAE,YAAY;wBAC5D,OAAO,CAAC,OAAO,CAAC,UAAU,MAAM,EAAE;4BAC9B,IAAI,WAAW,GAAG;gCACd,IAAI,EAAE,IAAI,CAAC,EAAE;gCACb,IAAI,EAAE,MAAM,CAAC,IAAI;gCACjB,KAAK,EAAE,MAAM,CAAC,KAAK;gCACnB,GAAG,EAAE,MAAM,CAAC,GAAG;6BAClB,CAAC;4BACF,IAAI,MAAM,CAAC,GAAG,EAAE,WAAW,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC;4BAC7C,IAAI,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;4BAChD,EAAE,CAAC,mBAAmB,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;yBAC3C,CAAC,CAAC;qBACN,CAAC,CAAC,IAAI,CAAC,YAAY;wBAChB,IAAI,CAAC,qBAAqB,GAAG,cAAc,CAAC;wBAC5C,IAAI,CAAC,IAAI,EAAE,CAAC;qBACf,CAAC,CAAC;iBACN;;gBAED,SAAS,uBAAuB,CAAC,OAAO,EAAE,cAAc,EAAE;;;;;oBAKtD,OAAO,EAAE,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,UAAU,KAAK,EAAE;wBAC1D,OAAO,KAAK,CAAC,IAAI,KAAK,UAAU,IAAI,KAAK,CAAC,IAAI,KAAK,qBAAqB,IAAI,KAAK,CAAC,MAAM,CAAC,UAAU,CAAC;qBACvG,CAAC,EAAE,YAAY;wBACZ,IAAI,KAAK,GAAG,KAAK,CAAC,kBAAkB,CAAC;wBACrC,IAAI,0BAA0B,GAAG,CAAC,CAAC;wBACnC,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,UAAU,UAAU,EAAE;;4BAElD,0BAA0B,GAAG,UAAU,IAAI,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC;yBAClE,CAAC,CAAC,IAAI,CAAC,YAAY;;4BAEhB,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;;4BAEvB,OAAO,EAAE,CAAC,mBAAmB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC;yBACzE,CAAC,CAAC,IAAI,CAAC,UAAU,kBAAkB,EAAE;4BAClC,OAAO,YAAY,CAAC,kBAAkB,EAAE,CAAC,CAAC,CAAC;yBAC9C,CAAC,CAAC,IAAI,CAAC,YAAY;4BAChB,OAAO,EAAE,CAAC,mBAAmB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC;yBACxE,CAAC,CAAC,IAAI,CAAC,YAAY;;4BAEhB,OAAO,YAAY,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;yBACnC,CAAC,CAAC,IAAI,CAAC,YAAY;;4BAEhB,OAAO,EAAE,CAAC,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;yBAC5C,CAAC,CAAC,IAAI,CAAC,UAAU,UAAU,EAAE;4BAC1B,IAAI,oBAAoB,GAAG,UAAU,IAAI,UAAU,CAAC,GAAG,IAAI,CAAC,CAAC;;4BAE7D,IAAI,CAAC,qBAAqB,GAAG,cAAc,CAAC;4BAC5C,IAAI,CAAC,mBAAmB,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,cAAc,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAC;4BACvF,IAAI,IAAI,CAAC,UAAU,KAAK,0BAA0B,EAAE;;;;gCAIhD,IAAI,CAAC,UAAU,GAAG,oBAAoB,CAAC;6BAC1C;;4BAED,IAAI,IAAI,CAAC,mBAAmB,CAAC,MAAM,GAAG,CAAC,EAAE;gCACrC,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC,mBAAmB,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,EAAE;oCAC1D,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE;wCACtD,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;wCACtC,MAAM;qCACT;iCACJ;6BACJ;4BACD,IAAI,CAAC,IAAI,EAAE,CAAC;;;yBAGf,CAAC,CAAC;;wBAEH,SAAS,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE;;;4BAGnC,IAAI,cAAc,GAAG,CAAC,CAAC;4BACvB,IAAI,iBAAiB,GAAG,IAAI,CAAC;4BAC7B,IAAI,MAAM,IAAI,OAAO,CAAC,MAAM,EAAE,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;4BAC3D,IAAI,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;4BAC7B,IAAI,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;4BACnC,OAAO,MAAM,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE;;;;gCAIrC,IAAI,UAAU,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;gCAC/C,iBAAiB,GAAG,UAAU,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE;oCACrD,OAAO,CAAC,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,iBAAiB,EAAE,UAAU,CAAC,EAAE;wCAClH,OAAO,UAAU,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;qCACjF,CAAC,CAAC;iCACN,CAAC,MAAM,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC;gCAC7B,MAAM,GAAG,OAAO,CAAC,EAAE,MAAM,CAAC,CAAC;gCAC3B,IAAI,MAAM,EAAE,KAAK,GAAG,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;6BAC9C;;4BAED,IAAI,iBAAiB,EAAE;;;gCAGnB,OAAO,iBAAiB,CAAC,IAAI,CAAC,YAAY;oCACtC,OAAO,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,GAAG,IAAI,CAAC;iCACzE,CAAC,CAAC;6BACN;;4BAED,IAAI,MAAM,EAAE;gCACR,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE;oCACxB,OAAO,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,YAAY;;wCAE1D,OAAO,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC,CAAC;qCAC5C,CAAC,CAAC;iCACN;;gCAED,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,EAAE;oCACxB,OAAO,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,YAAY;;wCAE7C,OAAO,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC,CAAC;qCAC5C,CAAC,CAAC;iCACN;6BACJ;;4BAED,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;yBAChC;qBACJ,CAAC,CAAC;iBACN;aACJ;;;;;;;;YAQD,SAAS,sBAAsB,CAAC,YAAY,EAAE;gBAC1C,IAAI,CAAC,UAAU,EAAE,EAAE;;oBAEf,IAAI,YAAY,CAAC,UAAU,EAAE,YAAY,CAAC,UAAU,EAAE,CAAC;oBACvD,OAAO;iBACV;;gBAED,qBAAqB,GAAG,YAAY,CAAC;gBACrC,UAAU,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY;oBACpC,IAAI,qBAAqB,EAAE;wBACvB,IAAI,qBAAqB,CAAC,KAAK,EAAE;4BAC7B,IAAI;;gCAEA,qBAAqB,CAAC,UAAU,EAAE,CAAC;6BACtC,CAAC,OAAO,CAAC,EAAE,EAAE;yBACjB;wBACD,qBAAqB,GAAG,IAAI,CAAC;qBAChC;iBACJ,CAAC,CAAC;;gBAEH,IAAI,YAAY,CAAC,KAAK,EAAE;oBACpB,yBAAyB,CAAC,YAAY,CAAC,CAAC;iBAC3C,MAAM;oBACH,wBAAwB,CAAC,YAAY,CAAC,CAAC;iBAC1C;aACJ;;;YAGD,SAAS,yBAAyB,CAAC,YAAY,EAAE;gBAC7C,IAAI,cAAc;gBAClB,kBAAkB,CAAC;;;gBAGnB,SAAS,SAAS,GAAG;oBACjB,IAAI,qBAAqB,EAAE;wBACvB,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;wBACjC,IAAI,kBAAkB,EAAE,cAAc,GAAG,IAAI,CAAC,KAAK;4BAC/C,cAAc,EAAE,CAAC;yBACpB;qBACJ;iBACJ;;gBAED,EAAE,CAAC,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;;;gBAG5B,UAAU,CAAC,EAAE,CAAC,YAAY,EAAE,YAAY;oBACpC,EAAE,CAAC,EAAE,CAAC,OAAO,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;iBACxC,CAAC,CAAC;;gBAEH,SAAS,cAAc,GAAG;oBACtB,IAAI,CAAC,qBAAqB,EAAE,OAAO;oBACnC,cAAc,GAAG,KAAK,CAAC;oBACvB,kBAAkB,GAAG,IAAI,CAAC;oBAC1B,qCAAqC,CAAC,IAAI,EAAE,UAAU,OAAO,EAAE,kBAAkB,EAAE,OAAO,EAAE,sBAAsB,EAAE;wBAChH,IAAI,CAAC,qBAAqB,EAAE,OAAO;wBACnC,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE;4BACpB,YAAY,CAAC,KAAK,CAAC,OAAO,EAAE,kBAAkB,EAAE,OAAO,EAAE,SAAS,iBAAiB,GAAG;gCAClF,MAAM,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,OAAO,CAAC,UAAU,OAAO,EAAE;oCAC3D,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,sBAAsB,CAAC,OAAO,CAAC,CAAC,CAAC;iCACtE,CAAC,CAAC;gCACH,IAAI,CAAC,IAAI,EAAE,CAAC;;gCAEZ,cAAc,EAAE,CAAC;6BACpB,CAAC,CAAC;yBACN,MAAM;4BACH,kBAAkB,GAAG,KAAK,CAAC;4BAC3B,IAAI,cAAc,EAAE;;;gCAGhB,cAAc,EAAE,CAAC;6BACpB,MAAM;gCACH,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;6BACnC;yBACJ;qBACJ,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;iBAC9B;;gBAED,cAAc,EAAE,CAAC;aACpB;;;YAGD,SAAS,wBAAwB,GAAG;;gBAEhC,SAAS,SAAS,GAAG;oBACjB,qCAAqC,CAAC,IAAI,EAAE,UAAU,OAAO,EAAE,kBAAkB,EAAE,OAAO,EAAE,sBAAsB,EAAE;;wBAEhH,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,EAAE,OAAO,EAAE,kBAAkB,EAAE,IAAI,CAAC,qBAAqB,EAAE,OAAO,EAAE,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;;wBAEnL,SAAS,iBAAiB,GAAG;4BACzB,MAAM,CAAC,IAAI,CAAC,sBAAsB,CAAC,CAAC,OAAO,CAAC,UAAU,OAAO,EAAE;gCAC3D,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,sBAAsB,CAAC,OAAO,CAAC,CAAC,CAAC;6BACtE,CAAC,CAAC;4BACH,IAAI,CAAC,IAAI,EAAE,CAAC;yBACf;;wBAED,SAAS,SAAS,CAAC,YAAY,EAAE;4BAC7B,IAAI,CAAC,qBAAqB,EAAE;;gCAExB,OAAO;6BACV;4BACD,qBAAqB,GAAG,YAAY,CAAC;4BACrC,IAAI,OAAO,EAAE;;gCAET,SAAS,EAAE,CAAC;6BACf,MAAM;;gCAEH,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,IAAI,YAAY,CAAC,KAAK,GAAG,QAAQ,EAAE;;oCAE7D,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;oCAChC,UAAU,CAAC,YAAY;wCACnB,IAAI,qBAAqB,EAAE;4CACvB,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;4CACjC,SAAS,EAAE,CAAC;yCACf;qCACJ,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC;iCAC1B,MAAM;;;oCAGH,UAAU,CAAC,UAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;iCAC3C;6BACJ;yBACJ;;wBAED,SAAS,OAAO,CAAC,KAAK,EAAE,KAAK,EAAE;4BAC3B,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,QAAQ,EAAE;gCACnC,IAAI,qBAAqB,EAAE;oCACvB,UAAU,CAAC,YAAY;wCACnB,IAAI,qBAAqB,EAAE;4CACvB,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;4CACjC,SAAS,EAAE,CAAC;yCACf;qCACJ,EAAE,KAAK,CAAC,CAAC;oCACV,cAAc,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC;iCAC7C;6BACJ,MAAM;gCACH,gBAAgB,CAAC,KAAK,CAAC,CAAC;6BAC3B;yBACJ;qBACJ,CAAC,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;iBAC9B;;gBAED,IAAI,aAAa,EAAE;oBACf,SAAS,EAAE,CAAC;iBACf,MAAM,IAAI,qBAAqB,IAAI,CAAC,KAAK,CAAC,qBAAqB,CAAC,KAAK,CAAC,IAAI,qBAAqB,CAAC,KAAK,GAAG,QAAQ,EAAE;oBAC/G,cAAc,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;oBAChC,UAAU,CAAC,YAAY;wBACnB,IAAI,qBAAqB,EAAE;4BACvB,cAAc,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;4BACjC,SAAS,EAAE,CAAC;yBACf;qBACJ,EAAE,qBAAqB,CAAC,KAAK,CAAC,CAAC;iBACnC;aACJ;SACJ;KACJ;;IAED,EAAE,CAAC,KAAK,GAAG,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,SAAS,EAAE;QAC/C,OAAO,YAAY;YACf,WAAW,CAAC,OAAO,CAAC,UAAU,IAAI,EAAE;gBAChC,IAAI,CAAC,UAAU,EAAE,CAAC;aACrB,CAAC,CAAC;YACH,OAAO,SAAS,CAAC,KAAK,CAAC,IAAI,EAAE,SAAS,CAAC,CAAC;SAC3C,CAAC;KACL,CAAC,CAAC;;IAEH,IAAI,uBAAuB,GAAG,EAAE,CAAC;IACjC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,GAAG,YAAY;QAChD,IAAI,IAAI,GAAG,IAAI,CAAC;QAChB,OAAO,EAAE,CAAC,WAAW,CAAC,KAAK,EAAE,EAAE,CAAC,UAAU,EAAE,YAAY;YACpD,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;SAC3B,CAAC,CAAC;KACN,CAAC;;IAEF,SAAS,KAAK,CAAC,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE;QACpC,SAAS,MAAM,GAAG;YACd,IAAI,CAAC,OAAO,CAAC,gBAAgB,EAAE;gBAC3B,OAAO,CAAC,gBAAgB,GAAG,KAAK,CAAC,iBAAiB,CAAC,YAAY;oBAC3D,OAAO,KAAK,CAAC,GAAG,CAAC,YAAY;wBACzB,OAAO,EAAE,EAAE,CAAC;qBACf,CAAC,CAAC;iBACN,CAAC,CAAC,IAAI,CAAC,UAAU,GAAG,EAAE;oBACnB,OAAO,OAAO,CAAC,gBAAgB,CAAC;oBAChC,OAAO,GAAG,CAAC;iBACd,CAAC,CAAC;aACN,MAAM;gBACH,OAAO,CAAC,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAC,IAAI,CAAC,YAAY;oBACjE,OAAO,KAAK,CAAC,OAAO,EAAE,EAAE,EAAE,UAAU,CAAC,CAAC;iBACzC,CAAC,CAAC;aACN;YACD,OAAO,OAAO,CAAC,gBAAgB,CAAC;SACnC;;QAED,IAAI,CAAC,UAAU,EAAE;;YAEb,IAAI,EAAE,CAAC,MAAM,EAAE,EAAE;gBACb,OAAO,MAAM,EAAE,CAAC;aACnB,MAAM;gBACH,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC,CAAC;aAC3D;SACJ,MAAM,IAAI,EAAE,CAAC,cAAc,IAAI,UAAU,KAAK,EAAE,CAAC,cAAc,CAAC,EAAE,EAAE;;YAEjE,OAAO,MAAM,EAAE,CAAC;SACnB,MAAM;YACH,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,qBAAqB,CAAC,CAAC,CAAC;SAC3D;KACJ;;IAED,SAAS,sBAAsB,CAAC,UAAU,EAAE,UAAU,EAAE;QACpD,IAAI,YAAY,GAAG,KAAK,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QAC/C,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,UAAU,OAAO,EAAE;YACpD,YAAY,CAAC,YAAY,CAAC,GAAG,EAAE,OAAO,EAAE,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;SACrE,CAAC,CAAC;QACH,OAAO,YAAY,CAAC;KACvB;;IAED,SAAS,sBAAsB,CAAC,UAAU,EAAE,UAAU,EAAE;QACpD,IAAI,YAAY,GAAG,KAAK,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QAC/C,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,UAAU,OAAO,EAAE;;YAEpD,IAAI,aAAa,GAAG,KAAK,CAAC;YAC1B,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,UAAU,UAAU,EAAE;gBACtD,OAAO,OAAO,CAAC,OAAO,CAAC,UAAU,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC;aAClD,CAAC,CAAC,OAAO,CAAC,UAAU,UAAU,EAAE;gBAC7B,YAAY,CAAC,YAAY,CAAC,UAAU,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;gBACxG,aAAa,GAAG,IAAI,CAAC;aACxB,CAAC,CAAC;YACH,IAAI,CAAC,aAAa,EAAE;;gBAEhB,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;aACzD;;;YAGD,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,UAAU,OAAO,EAAE;gBACnD,OAAO,OAAO,CAAC,OAAO,CAAC,OAAO,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC;aAC/C,CAAC,CAAC,OAAO,CAAC,UAAU,OAAO,EAAE;gBAC1B,OAAO,YAAY,CAAC,OAAO,CAAC,CAAC;aAChC,CAAC,CAAC;SACN,CAAC,CAAC;QACH,OAAO,YAAY,CAAC;KACvB;CACJ,AAAC;;AAEF,QAAQ,CAAC,QAAQ,GAAG;IAChB,KAAK,EAAE,CAAC,CAAC;IACT,OAAO,EAAE,CAAC;IACV,UAAU,EAAE,CAAC;IACb,MAAM,EAAE,CAAC;IACT,OAAO,EAAE,CAAC;IACV,gBAAgB,EAAE,CAAC;CACtB,CAAC;;AAEF,QAAQ,CAAC,WAAW,GAAG;IACnB,IAAI,EAAE,OAAO;IACb,GAAG,EAAE,SAAS;IACd,GAAG,EAAE,YAAY;IACjB,GAAG,EAAE,QAAQ;IACb,GAAG,EAAE,SAAS;IACd,GAAG,EAAE,kBAAkB;CAC1B,CAAC;;AAEF,QAAQ,CAAC,mBAAmB,GAAG,EAAE,CAAC;;AAElC,QAAQ,CAAC,oBAAoB,GAAG,UAAU,IAAI,EAAE,gBAAgB,EAAE;;;;;;IAM9D,QAAQ,CAAC,mBAAmB,CAAC,IAAI,CAAC,GAAG,gBAAgB,CAAC;CACzD,CAAC;;;AAGF,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAC;AAC1B,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,;;,;;"}
\ No newline at end of file
diff --git a/addons/Dexie.Syncable/dist/dexie-syncable.min.js b/addons/Dexie.Syncable/dist/dexie-syncable.min.js
new file mode 100644
index 000000000..95e572845
--- /dev/null
+++ b/addons/Dexie.Syncable/dist/dexie-syncable.min.js
@@ -0,0 +1,2 @@
+!function(e,n){"object"==typeof exports&&"undefined"!=typeof module?module.exports=n(require("dexie"),require("dexie-observable")):"function"==typeof define&&define.amd?define(["dexie","dexie-observable"],n):(e.Dexie=e.Dexie||{},e.Dexie.Syncable=n(e.Dexie,e.dexieObservable))}(this,function(e,n){"use strict";function t(n){function s(t,o,i,c,s){function p(){return n._localSyncNode&&n._localSyncNode.id===s}function m(t){return n.transaction("rw",n._syncNodes,function(){if(!i)throw new Error("Url cannot be empty");return n._syncNodes.where("url").equalsIgnoreCase(i).first(function(c){function s(n,t){this.nodeID=n,t&&e.extend(this,t)}return s.prototype.save=function(){return e.vip(function(){return c.save()})},c?(c.syncContext=new s(c.id,c.syncContext),c.syncProtocol=o,n._syncNodes.put(c)):(c=new n.observable.SyncNode,c.myRevision=-1,c.appliedRemoteRevision=null,c.remoteBaseRevisions=[],c.type="remote",c.syncProtocol=o,c.url=i,c.syncOptions=t,c.lastHeartBeat=Date.now(),c.dbUploadState=null,r.resolve(function(){if(t.initialUpload===!1)return n._changes.lastKey(function(e){c.myRevision=e})}).then(function(){n._syncNodes.add(c).then(function(e){c.syncContext=new s(e),n._syncNodes.put(c)})})),c})})}function N(o){function f(e){o.status!==e&&(o.status=e,o.save(),n.syncable.on.statusChanged.fire(e,i),n.broadcastMessage("syncStatusChanged",{newStatus:e,url:i},!1))}function m(){return a(m,function(){return g(o,function(n,s,a,u){function l(){Object.keys(u).forEach(function(n){e.setByKeyPath(o,n,u[n])}),o.save(),d.then(C)}var d=new r(function(r,u){O=function(e){u(e)},e.asap(function(){function e(e,n){u(e),p()&&(!isNaN(n)&&n<1/0?(setTimeout(function(){p()&&(f(v.SYNCING),m())},n),f(v.ERROR_WILL_RETRY,e),j&&j.disconnect&&j.disconnect(),j=null):N(e))}try{t.sync(o.syncContext,i,c,s,o.appliedRemoteRevision,n,a,S,l,function(e){r(e)},e)}catch(n){e(n,1/0)}})});return d.then(function(){})})},s)}function N(e){w.disconnect(v.ERROR,e)}function R(e){if(0===e.remoteBaseRevisions.length)return{maxClientRevision:1/0,remoteBaseRevision:null};for(var n=e.remoteBaseRevisions.length-1;n>=0;--n)if(e.myRevision>=e.remoteBaseRevisions[n].local)return{maxClientRevision:n===e.remoteBaseRevisions.length-1?1/0:e.remoteBaseRevisions[n+1].local,remoteBaseRevision:e.remoteBaseRevisions[n].remote};return{maxClientRevision:e.remoteBaseRevisions[0].local,remoteBaseRevision:null}}function g(n,t){return _(n,function o(r,i,c,s){return 0===r.length&&"myRevision"in s&&s.myRevision!==n.myRevision?(Object.keys(s).forEach(function(t){e.setByKeyPath(n,t,s[t])}),n.save(),_(n,o)):t(r,i,c,s)})}function _(t,o){function i(e,r,s){var a=!1;return s.until(function(){if(r.length===b)return a=!0,!0}).each(function(n,t){r.push({type:d,table:e.currentTable,key:t.key,obj:t.value}),e.currentKey=t.key}).then(function(){if(a)return E=!0,o(r,null,!0,{dbUploadState:e});if(0===e.tablesToUpload.length){var s=R(t);return c(e.localBaseRevision,b-r.length,s.maxClientRevision,function(e,n,t){return r=r.concat(e),t.dbUploadState=null,o(r,s.remoteBaseRevision,n,t)})}return e.currentTable=e.tablesToUpload.shift(),i(e,r,n.table(e.currentTable).orderBy(":id"))})}function c(e,o,r,i){var c={},s=0,a=!1,f=t.id,v=e;return n.transaction("r",n._changes,function(){var t=r===1/0?n._changes.where("rev").above(e):n._changes.where("rev").between(e,r,!1,!0);t.until(function(){if(s===o)return a=!0,!0}).each(function(e){if(v=e.rev,e.source!==f){var n={type:e.type,table:e.table,key:e.key};e.type===d?n.obj=e.obj:e.type===y&&(n.mods=e.mods);var t=e.table+":"+e.key,o=c[t];if(o){var r=n,i=function(){switch(o.type){case d:switch(r.type){case d:return r;case y:return u(o,r);case h:return r}break;case y:switch(r.type){case d:return r;case y:return l(o,r);case h:return r}break;case h:switch(r.type){case d:return r;case y:return o;case h:return o}}}();c[t]=i}else c[t]=n,++s}})}).then(function(){var e=Object.keys(c).map(function(e){return c[e]});return E=a,i(e,a,{myRevision:v})})}if(t.myRevision>=0){var s=R(t);return c(t.myRevision,b,s.maxClientRevision,function(e,n,t){return o(e,s.remoteBaseRevision,n,t)})}if(null===t.dbUploadState){var a=n.tables.filter(function(e){return e.schema.observable}).map(function(e){return e.name});if(0===a.length)return r.resolve(o([],null,!1,{}));var f={tablesToUpload:a,currentTable:a.shift(),currentKey:null};return n._changes.orderBy("rev").last(function(e){f.localBaseRevision=e&&e.rev||0;var t=n.table(f.currentTable).orderBy(":id");return i(f,[],t)})}if(t.dbUploadState.currentKey){var v=n.table(t.dbUploadState.currentTable).where(":id").above(t.dbUploadState.currentKey);return i(e.deepClone(t.dbUploadState),[],v)}var v=n.table(f.currentTable).orderBy(":id");return i(e.deepClone(t.dbUploadState),[],v)}function S(t,i,c,u){function l(e){return n.transaction("rw",n._uncommittedChanges,function(){e.forEach(function(e){var t={node:o.id,type:e.type,table:e.table,key:e.key};e.obj&&(t.obj=e.obj),e.mods&&(t.mods=e.mods),n._uncommittedChanges.add(t)})}).then(function(){o.appliedRemoteRevision=i,o.save()})}function f(t,i){return n.transaction("rw",n.tables.filter(function(e){return"_changes"===e.name||"_uncommittedChanges"===e.name||e.schema.observable}),function(){function c(e,t){var o=null;if(t>=e.length)return r.resolve(null);for(var i=e[t],s=n.table(i.table);i&&i.type===d;){var a=!s.schema.primKey.keyPath;o=function(e,n,t){return(t?n.add(e.obj,e.key):n.add(e.obj)).catch("ConstraintError",function(o){return t?n.put(e.obj,e.key):n.put(e.obj)})}(i,s,a),i=e[++t],i&&(s=n.table(i.table))}if(o)return o.then(function(){return t1)for(var t=o.remoteBaseRevisions.length-1;t>0;--t)if(o.myRevision>=o.remoteBaseRevisions[t].local){o.remoteBaseRevisions.splice(0,t);break}o.save()})})}return a(S,function(){return p()?(c?l(t):f(t,i)).catch(function(e){return N(e),r.reject(e)}):r.reject("Database not open")},s)}function C(e){return p()?(j=e,w.on("disconnect",function(){if(j){if(j.react)try{j.disconnect()}catch(e){}j=null}}),void(e.react?B(e):I(e))):void(e.disconnect&&e.disconnect())}function B(t){function r(){j&&(f(v.SYNCING),s?c=!0:i())}function i(){j&&(c=!1,s=!0,g(o,function(n,r,a,u){j&&(n.length>0?t.react(n,r,a,function(){Object.keys(u).forEach(function(n){e.setByKeyPath(o,n,u[n])}),o.save(),i()}):(s=!1,c?i():f(v.ONLINE)))}).catch(N))}var c,s;n.on("changes",r),w.on("disconnect",function(){n.on.changes.unsubscribe(r)}),i()}function I(){function n(){g(o,function(r,s,a,u){function l(){Object.keys(u).forEach(function(n){e.setByKeyPath(o,n,u[n])}),o.save()}function d(e){j&&(j=e,a?n():!isNaN(e.again)&&e.again<1/0?(f(v.ONLINE),setTimeout(function(){j&&(f(v.SYNCING),n())},e.again)):w.disconnect(v.OFFLINE))}function y(e,t){!isNaN(t)&&t<1/0?j&&(setTimeout(function(){j&&(f(v.SYNCING),n())},t),f(v.ERROR_WILL_RETRY)):N(e)}t.sync(o.syncContext,i,c,s,o.appliedRemoteRevision,r,a,S,l,d,y)}).catch(N)}E?n():j&&!isNaN(j.again)&&j.again<1/0&&(f(v.ONLINE),setTimeout(function(){j&&(f(v.SYNCING),n())},j.again))}w.on("disconnect",function(e){isNaN(e)||f(e)});var j;return f(v.CONNECTING),m()}var R=f.filter(function(e){return e.url===i});if(R.length>0)return R[0].connectPromise;var g=m(c).then(function(e){return N(e)}),O=null,_=!1,E=!0,w={url:i,status:v.OFFLINE,connectPromise:g,on:e.Events(null,"disconnect"),disconnect:function(e,n){if(!_){w.on.disconnect.fire(e,n);var t=f.indexOf(w);t>=0&&f.splice(t,1),n&&O&&O(n)}_=!0}};return f.push(w),g}function a(t,o,i){function c(){return t.ongoingOperation?t.ongoingOperation=t.ongoingOperation.then(function(){return a(t,o,i)}):t.ongoingOperation=e.ignoreTransaction(function(){return e.vip(function(){return o()})}).then(function(e){return delete t.ongoingOperation,e}),t.ongoingOperation}return i?n._localSyncNode&&i===n._localSyncNode.id?c():r.reject(new Error("Database was closed")):n.isOpen()?c():r.reject(new Error("Database was closed"))}function u(n,t){var o=e.deepClone(n);return Object.keys(t.mods).forEach(function(e){i(o.obj,e,t.mods[e])}),o}function l(n,t){var o=e.deepClone(n);return Object.keys(t.mods).forEach(function(e){var r=!1;Object.keys(n.mods).filter(function(n){return 0===e.indexOf(n+".")}).forEach(function(n){i(o[n],e.substr(n.length+1),t.mods[e]),r=!0}),r||(o.mods[e]=t.mods[e]),Object.keys(n.mods).filter(function(n){return 0===n.indexOf(e+".")}).forEach(function(e){delete o[e]})}),o}var f=[],d=1,y=2,h=3,v=t.Statuses,b=1e3;n.on("message",function(t){e.vip(function(){"connect"===t.type?n.syncable.connect(t.protocolName,t.url,t.options).then(t.resolve,t.reject):"disconnect"===t.type?n.syncable.disconnect(t.url).then(t.resolve,t.reject):"syncStatusChanged"===t.type&&n.syncable.on.statusChanged.fire(t.newStatus,t.url)})}),n.on("cleanup",function(t){t&&n._syncNodes.where("type").equals("remote").and(function(e){return e.status!==v.OFFLINE&&e.status!==v.ERROR}).each(function(t){e.ignoreTransaction(function(){e.vip(function(){n.syncable.connect(t.syncProtocol,t.url,t.syncOptions)})})})}),n.on("ready",function(){if(n._localSyncNode&&n._localSyncNode.isMaster)return n._syncNodes.where("type").equals("remote").and(function(e){return e.status!==v.OFFLINE&&e.status!==v.ERROR}).toArray(function(e){if(e.length>0)return r.all(e.map(function(e){return n.syncable.connect(e.syncProtocol,e.url,e.syncOptions).catch(function(e){})}))})},!0),n.syncable={},n.syncable.getStatus=function(o,i){return n.isOpen()?e.vip(function(){return n._syncNodes.where("url").equals(o).first(function(e){return e?e.status:v.OFFLINE})}).then(i):r.resolve(t.Statuses.OFFLINE).then(i)},n.syncable.list=function(){return n._syncNodes.where("type").equals("remote").toArray(function(e){return e.map(function(e){return e.url})})},n.syncable.on=e.Events(n,{statusChanged:"asap"}),n.syncable.disconnect=function(e){return n._localSyncNode&&n._localSyncNode.isMaster?f.filter(function(n){return n.url===e}).forEach(function(e){e.disconnect(v.OFFLINE)}):n._syncNodes.where("isMaster").above(0).first(function(t){n.sendMessage("disconnect",{url:e},t.id,{wantReply:!0})}),n._syncNodes.where("url").equals(e).modify(function(e){e.status=v.OFFLINE})},n.syncable.connect=function(o,i,c){c=c||{};var a=t.registeredProtocols[o];if(a)return n.isOpen()&&n._localSyncNode?n._localSyncNode.isMaster?s(a,o,i,c,n._localSyncNode.id):(n.table("_syncNodes").where("isMaster").above(0).first(function(e){return n.sendMessage("connect",{protocolName:o,url:i,options:c},e.id,{wantReply:!0})}),r.resolve()):new r(function(t,r){n.on("ready",function(){return e.vip(function(){return n.syncable.connect(o,i,c).then(t).catch(function(e){r(e)})})})});throw new Error("ISyncProtocol '"+o+"' is not registered in Dexie.Syncable.registerSyncProtocol()")},n.syncable.delete=function(e){return n.transaction("rw",n._syncNodes,n._changes,n._uncommittedChanges,function(){n._syncNodes.where("url").equals(e).toArray(function(e){if(e.length>0){var t=e.map(function(e){return e.id});return n._syncNodes.where("id").anyOf(t).delete().then(function(){return c.deleteOldChanges(),n._uncommittedChanges.where("node").anyOf(t).delete()})}})})},n.syncable.unsyncedChanges=function(e){return n._syncNodes.where("url").equals(e).first(function(e){return n._changes.where("rev").above(e.myRevision).toArray()})},n.close=o(n.close,function(e){return function(){return f.forEach(function(e){e.disconnect()}),e.apply(this,arguments)}});n.observable.SyncNode.prototype.save=function(){var e=this;return n.transaction("rw?",n._syncNodes,function(){n._syncNodes.put(e)})}}e="default"in e?e.default:e;var o=e.override,r=e.Promise,i=e.setByKeyPath,c=e.Observable;return t.Statuses={ERROR:-1,OFFLINE:0,CONNECTING:1,ONLINE:2,SYNCING:3,ERROR_WILL_RETRY:4},t.StatusTexts={"-1":"ERROR",0:"OFFLINE",1:"CONNECTING",2:"ONLINE",3:"SYNCING",4:"ERROR_WILL_RETRY"},t.registeredProtocols={},t.registerSyncProtocol=function(e,n){t.registeredProtocols[e]=n},e.Syncable=t,e.addons.push(t),t});
+//# sourceMappingURL=dexie-syncable.min.js.map
\ No newline at end of file
diff --git a/addons/Dexie.Syncable/dist/dexie-syncable.min.js.map b/addons/Dexie.Syncable/dist/dexie-syncable.min.js.map
new file mode 100644
index 000000000..fd6d4f4c7
--- /dev/null
+++ b/addons/Dexie.Syncable/dist/dexie-syncable.min.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["../tools/tmp/src/Dexie.Syncable.js"],"names":["Syncable","db","connect","protocolInstance","protocolName","url","options","dbAliveID","stillAlive","_localSyncNode","id","getOrCreateSyncNode","transaction","_syncNodes","Error","where","equalsIgnoreCase","first","node","PersistedContext","nodeID","otherProps","this","Dexie","extend","prototype","save","vip","syncContext","syncProtocol","put","observable","SyncNode","myRevision","appliedRemoteRevision","remoteBaseRevisions","type","syncOptions","lastHeartBeat","Date","now","dbUploadState","Promise","resolve","initialUpload","_changes","lastKey","currentRevision","then","add","nodeId","connectProtocol","changeStatusTo","newStatus","status","syncable","on","statusChanged","fire","broadcastMessage","doSync","enque","getLocalChangesForNode_autoAckIfEmpty","changes","remoteBaseRevision","partial","nodeModificationsOnAck","onChangesAccepted","Object","keys","forEach","keyPath","setByKeyPath","finalSyncPromise","continueSendingChanges","reject","rejectConnectPromise","err","asap","onError","error","again","isNaN","Infinity","setTimeout","Statuses","SYNCING","ERROR_WILL_RETRY","connectedContinuation","disconnect","abortTheProvider","sync","applyRemoteChanges","continuation","ex","activePeer","ERROR","getBaseRevisionAndMaxClientRevision","length","maxClientRevision","i","local","remote","cb","getLocalChangesForNode","autoAck","getTableObjectsAsChanges","state","collection","limitReached","until","MAX_CHANGES_PER_CHUNK","each","item","cursor","push","CREATE","table","currentTable","key","obj","value","currentKey","hasMoreToGive","tablesToUpload","brmcr","getChangesSinceRevision","localBaseRevision","additionalChanges","concat","shift","orderBy","revision","maxChanges","maxRevision","changeSet","numChanges","ignoreSource","nextRevision","query","above","between","change","rev","source","changeToSend","UPDATE","mods","prevChange","nextChange","mergedChange","combineCreateAndUpdate","DELETE","combineUpdateAndUpdate","map","tables","filter","schema","name","last","lastChange","deepClone","remoteChanges","remoteRevision","clear","saveToUncommitedChanges","_uncommittedChanges","changeToAdd","finallyCommitAllChanges","applyChanges","offset","lastCreatePromise","specifyKey","primKey","catch","e","update","delete","trans","currentTransaction","localRevisionBeforeChanges","equals","toArray","uncommittedChanges","currentLocalRevision","splice","react","continueUsingReactPattern","continueUsingPollPattern","onChanges","isWaitingForServer","changesWaiting","reactToChanges","ONLINE","unsubscribe","syncAgain","onSuccess","OFFLINE","CONNECTING","existingPeer","activePeers","peer","connectPromise","disconnected","Events","pos","indexOf","context","fn","instanceID","_enque","ongoingOperation","ignoreTransaction","res","isOpen","clonedChange","hadParentPath","parentPath","substr","subPath","msg","weBecameMaster","and","connectedRemoteNode","isMaster","connectedRemoteNodes","all","getStatus","list","a","masterNode","sendMessage","wantReply","modify","registeredProtocols","nodes","nodeIDs","anyOf","Observable","deleteOldChanges","unsyncedChanges","close","override","origClose","apply","arguments","self","StatusTexts","-1","0","1","2","3","4","registerSyncProtocol","addons"],"mappings":"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","file":"dist/dexie-syncable.min.js.map","sourcesContent":["/// \n/// \n/// \n/**\r\n * Dexie.Syncable.js\r\n * ===================\r\n * Dexie addon for syncing indexedDB with remote endpoints.\r\n *\r\n * version: {version} Alpha, {date}\r\n *\r\n * Disclaimber: This addon is in alpha status meaning that\r\n * its API and behavior may change.\r\n *\r\n */\n\nimport Dexie from \"dexie\";\n// Depend on 'dexie-observable'\n// To support both ES6,AMD,CJS and UMD (plain script), we just import it and then access it as \"Dexie.Observable\".\n// That way, our plugin works in all UMD cases.\n// If target platform would only be module based (ES6/AMD/CJS), we could have done 'import Observable from \"dexie-observable\"'.\nimport \"dexie-observable\";\n\nvar override = Dexie.override,\n Promise = Dexie.Promise,\n setByKeyPath = Dexie.setByKeyPath,\n Observable = Dexie.Observable;\n\nexport default function Syncable(db) {\n /// \n\n var activePeers = [];\n\n // Change Types\n var CREATE = 1,\n UPDATE = 2,\n DELETE = 3;\n\n // Statuses\n var Statuses = Syncable.Statuses;\n\n var MAX_CHANGES_PER_CHUNK = 1000;\n\n db.on('message', function (msg) {\n // Message from other local node arrives...\n Dexie.vip(function () {\n if (msg.type === 'connect') {\n // We are master node and another non-master node wants us to do the connect.\n db.syncable.connect(msg.protocolName, msg.url, msg.options).then(msg.resolve, msg.reject);\n } else if (msg.type === 'disconnect') {\n db.syncable.disconnect(msg.url).then(msg.resolve, msg.reject);\n } else if (msg.type === 'syncStatusChanged') {\n // We are client and a master node informs us about syncStatus change.\n // Lookup the connectedProvider and call its event\n db.syncable.on.statusChanged.fire(msg.newStatus, msg.url);\n }\n });\n });\n\n db.on('cleanup', function (weBecameMaster) {\n // A cleanup (done in Dexie.Observable) may result in that a master node is removed and we become master.\n if (weBecameMaster) {\n // We took over the master role in Observable's cleanup method\n db._syncNodes.where('type').equals('remote').and(function (node) {\n return node.status !== Statuses.OFFLINE && node.status !== Statuses.ERROR;\n }).each(function (connectedRemoteNode) {\n // There are connected remote nodes that we must take over\n // Since we may be in the on(ready) event, we must get VIPed to continue\n Dexie.ignoreTransaction(function () {\n Dexie.vip(function () {\n db.syncable.connect(connectedRemoteNode.syncProtocol, connectedRemoteNode.url, connectedRemoteNode.syncOptions);\n });\n });\n });\n }\n });\n\n db.on('ready', function onReady() {\n // Again, in onReady: If we ARE master, make sure to connect to remote servers that is in a connected state.\n if (db._localSyncNode && db._localSyncNode.isMaster) {\n // Make sure to connect to remote servers that is in a connected state (NOT OFFLINE or ERROR!)\n return db._syncNodes.where('type').equals('remote').and(function (node) {\n return node.status !== Statuses.OFFLINE && node.status !== Statuses.ERROR;\n }).toArray(function (connectedRemoteNodes) {\n // There are connected remote nodes that we must take over\n if (connectedRemoteNodes.length > 0) {\n return Promise.all(connectedRemoteNodes.map(function (node) {\n return db.syncable.connect(node.syncProtocol, node.url, node.syncOptions).catch(function (err) {\n return undefined; // If a node fails to connect, don't make db.open() reject. Accept it!\n });\n }));\n }\n });\n }\n }, true); // True means the ready event will survive a db reopen - db.close()/db.open()\n\n\n db.syncable = {};\n\n db.syncable.getStatus = function (url, cb) {\n if (db.isOpen()) {\n return Dexie.vip(function () {\n return db._syncNodes.where('url').equals(url).first(function (node) {\n return node ? node.status : Statuses.OFFLINE;\n });\n }).then(cb);\n } else {\n return Promise.resolve(Syncable.Statuses.OFFLINE).then(cb);\n }\n };\n\n db.syncable.list = function () {\n return db._syncNodes.where('type').equals('remote').toArray(function (a) {\n return a.map(function (node) {\n return node.url;\n });\n });\n };\n\n db.syncable.on = Dexie.Events(db, { statusChanged: \"asap\" });\n\n db.syncable.disconnect = function (url) {\n if (db._localSyncNode && db._localSyncNode.isMaster) {\n activePeers.filter(function (peer) {\n return peer.url === url;\n }).forEach(function (peer) {\n peer.disconnect(Statuses.OFFLINE);\n });\n } else {\n db._syncNodes.where('isMaster').above(0).first(function (masterNode) {\n db.sendMessage('disconnect', { url: url }, masterNode.id, { wantReply: true });\n });\n }\n\n return db._syncNodes.where(\"url\").equals(url).modify(function (node) {\n node.status = Statuses.OFFLINE;\n });\n };\n\n db.syncable.connect = function (protocolName, url, options) {\n options = options || {}; // Make sure options is always an object because 1) Provider expects it to be. 2) We'll be persisting it and you cannot persist undefined.\n var protocolInstance = Syncable.registeredProtocols[protocolName];\n\n if (protocolInstance) {\n if (db.isOpen() && db._localSyncNode) {\n // Database is open\n if (db._localSyncNode.isMaster) {\n // We are master node\n return connect(protocolInstance, protocolName, url, options, db._localSyncNode.id);\n } else {\n // We are not master node\n // Request master node to do the connect:\n db.table('_syncNodes').where('isMaster').above(0).first(function (masterNode) {\n // There will always be a master node. In theory we may self have become master node when we come here. But that's ok. We'll request ourselves.\n return db.sendMessage('connect', { protocolName: protocolName, url: url, options: options }, masterNode.id, { wantReply: true });\n });\n return Promise.resolve();\n }\n } else {\n // Database not yet open\n // Wait for it to open\n return new Promise(function (resolve, reject) {\n db.on(\"ready\", function syncWhenReady() {\n return Dexie.vip(function () {\n return db.syncable.connect(protocolName, url, options).then(resolve).catch(function (err) {\n // Reject the promise returned to the caller of db.syncable.connect():\n reject(err);\n // but resolve the promise that db.on(\"ready\") waits for, because database should succeed to open even if the connect operation fails!\n });\n });\n });\n });\n }\n } else {\n throw new Error(\"ISyncProtocol '\" + protocolName + \"' is not registered in Dexie.Syncable.registerSyncProtocol()\");\n return new Promise(); // For code completion\n }\n };\n\n db.syncable.delete = function (url) {\n // Notice: Caller should call db.syncable.disconnect(url) and wait for it to finish before calling db.syncable.delete(url)\n // Surround with a readwrite-transaction\n return db.transaction('rw', db._syncNodes, db._changes, db._uncommittedChanges, function () {\n // Find the node\n db._syncNodes.where(\"url\").equals(url).toArray(function (nodes) {\n // If it's found (or even several found, as detected by @martindiphoorn),\n // let's delete it (or them) and cleanup _changes and _uncommittedChanges\n // accordingly.\n if (nodes.length > 0) {\n var nodeIDs = nodes.map(function (node) {\n return node.id;\n });\n // The following 'return' statement is not needed right now, but I leave it \n // there because if we would like to add a 'then()' statement to the main ,\n // operation above ( db._syncNodes.where(\"url\").equals(url).toArray(...) ) , \n // this return statement will asure that the whole chain is waited for \n // before entering the then() callback.\n return db._syncNodes.where('id').anyOf(nodeIDs).delete().then(function () {\n // When theese nodes are gone, let's clear the _changes table\n // from all revisions older than the oldest node.\n // Delete all changes older than revision of oldest node:\n Observable.deleteOldChanges();\n // Also don't forget to delete all uncommittedChanges for the deleted node:\n return db._uncommittedChanges.where('node').anyOf(nodeIDs).delete();\n });\n }\n });\n });\n };\n\n db.syncable.unsyncedChanges = function (url) {\n return db._syncNodes.where(\"url\").equals(url).first(function (node) {\n return db._changes.where('rev').above(node.myRevision).toArray();\n });\n };\n\n function connect(protocolInstance, protocolName, url, options, dbAliveID) {\n /// \n var existingPeer = activePeers.filter(function (peer) {\n return peer.url === url;\n });\n if (existingPeer.length > 0) {\n // Never create multiple syncNodes with same protocolName and url. Instead, let the next call to connect() return the same promise that\n // have already been started and eventually also resolved. If promise has already resolved (node connected), calling existing promise.then() will give a callback directly.\n return existingPeer[0].connectPromise;\n }\n\n var connectPromise = getOrCreateSyncNode(options).then(function (node) {\n return connectProtocol(node);\n });\n\n var rejectConnectPromise = null;\n var disconnected = false;\n var hasMoreToGive = true;\n var activePeer = {\n url: url,\n status: Statuses.OFFLINE,\n connectPromise: connectPromise,\n on: Dexie.Events(null, \"disconnect\"),\n disconnect: function (newStatus, error) {\n if (!disconnected) {\n activePeer.on.disconnect.fire(newStatus, error);\n var pos = activePeers.indexOf(activePeer);\n if (pos >= 0) activePeers.splice(pos, 1);\n if (error && rejectConnectPromise) rejectConnectPromise(error);\n }\n disconnected = true;\n }\n };\n activePeers.push(activePeer);\n\n return connectPromise;\n\n function stillAlive() {\n // A better method than doing db.isOpen() because the same db instance may have been reopened, but then this sync call should be dead\n // because the new instance should be considered a fresh instance and will have another local node.\n return db._localSyncNode && db._localSyncNode.id === dbAliveID;\n }\n\n function getOrCreateSyncNode(options) {\n return db.transaction('rw', db._syncNodes, function () {\n if (!url) throw new Error(\"Url cannot be empty\");\n // Returning a promise from transaction scope will make the transaction promise resolve with the value of that promise.\n\n\n return db._syncNodes.where(\"url\").equalsIgnoreCase(url).first(function (node) {\n //\n // PersistedContext : IPersistedContext\n //\n function PersistedContext(nodeID, otherProps) {\n this.nodeID = nodeID;\n if (otherProps) Dexie.extend(this, otherProps);\n }\n\n PersistedContext.prototype.save = function () {\n // Store this instance in the syncContext property of the node it belongs to.\n return Dexie.vip(function () {\n return node.save();\n });\n };\n\n if (node) {\n // Node already there. Make syncContext become an instance of PersistedContext:\n node.syncContext = new PersistedContext(node.id, node.syncContext);\n node.syncProtocol = protocolName; // In case it was changed (would be very strange but...) could happen...\n db._syncNodes.put(node);\n } else {\n // Create new node and sync everything\n node = new db.observable.SyncNode();\n node.myRevision = -1;\n node.appliedRemoteRevision = null;\n node.remoteBaseRevisions = [];\n node.type = \"remote\";\n node.syncProtocol = protocolName;\n node.url = url;\n node.syncOptions = options;\n node.lastHeartBeat = Date.now();\n node.dbUploadState = null;\n Promise.resolve(function () {\n // If options.initialUpload is explicitely false, set myRevision to currentRevision.\n if (options.initialUpload === false) return db._changes.lastKey(function (currentRevision) {\n node.myRevision = currentRevision;\n });\n }).then(function () {\n db._syncNodes.add(node).then(function (nodeId) {\n node.syncContext = new PersistedContext(nodeId); // Update syncContext in db with correct nodeId.\n db._syncNodes.put(node);\n });\n });\n }\n\n return node; // returning node will make the db.transaction()-promise resolve with this value.\n });\n });\n }\n\n function connectProtocol(node) {\n /// \n\n function changeStatusTo(newStatus) {\n if (node.status !== newStatus) {\n node.status = newStatus;\n node.save();\n db.syncable.on.statusChanged.fire(newStatus, url);\n // Also broadcast message to other nodes about the status\n db.broadcastMessage(\"syncStatusChanged\", { newStatus: newStatus, url: url }, false);\n }\n }\n\n activePeer.on('disconnect', function (newStatus) {\n if (!isNaN(newStatus)) changeStatusTo(newStatus);\n });\n\n var connectedContinuation;\n changeStatusTo(Statuses.CONNECTING);\n return doSync();\n\n function doSync() {\n // Use enque() to ensure only a single promise execution at a time.\n return enque(doSync, function () {\n // By returning the Promise returned by getLocalChangesForNode() a final catch() on the sync() method will also catch error occurring in entire sequence.\n return getLocalChangesForNode_autoAckIfEmpty(node, function sendChangesToProvider(changes, remoteBaseRevision, partial, nodeModificationsOnAck) {\n // Create a final Promise for the entire sync() operation that will resolve when provider calls onSuccess().\n // By creating finalPromise before calling protocolInstance.sync() it is possible for provider to call onError() immediately if it wants.\n var finalSyncPromise = new Promise(function (resolve, reject) {\n rejectConnectPromise = function (err) {\n reject(err);\n };\n Dexie.asap(function () {\n try {\n protocolInstance.sync(node.syncContext, url, options, remoteBaseRevision, node.appliedRemoteRevision, changes, partial, applyRemoteChanges, onChangesAccepted, function (continuation) {\n resolve(continuation);\n }, onError);\n } catch (ex) {\n onError(ex, Infinity);\n }\n\n function onError(error, again) {\n reject(error);\n if (stillAlive()) {\n if (!isNaN(again) && again < Infinity) {\n setTimeout(function () {\n if (stillAlive()) {\n changeStatusTo(Statuses.SYNCING);\n doSync();\n }\n }, again);\n changeStatusTo(Statuses.ERROR_WILL_RETRY, error);\n if (connectedContinuation && connectedContinuation.disconnect) connectedContinuation.disconnect();\n connectedContinuation = null;\n } else {\n abortTheProvider(error); // Will fire ERROR on statusChanged event.\n }\n }\n }\n });\n });\n\n return finalSyncPromise.then(function () {\n // Resolve caller of db.syncable.connect() with undefined. Not with continuation!\n });\n\n function onChangesAccepted() {\n Object.keys(nodeModificationsOnAck).forEach(function (keyPath) {\n Dexie.setByKeyPath(node, keyPath, nodeModificationsOnAck[keyPath]);\n });\n node.save();\n // We dont know if onSuccess() was called by provider yet. If it's already called, finalPromise.then() will execute immediately,\n // otherwise it will execute when finalSyncPromise resolves.\n finalSyncPromise.then(continueSendingChanges);\n }\n });\n }, dbAliveID);\n }\n\n function abortTheProvider(error) {\n activePeer.disconnect(Statuses.ERROR, error);\n }\n\n function getBaseRevisionAndMaxClientRevision(node) {\n /// \n if (node.remoteBaseRevisions.length === 0) return {\n // No remoteBaseRevisions have arrived yet. No limit on clientRevision and provide null as remoteBaseRevision:\n maxClientRevision: Infinity,\n remoteBaseRevision: null\n };\n for (var i = node.remoteBaseRevisions.length - 1; i >= 0; --i) {\n if (node.myRevision >= node.remoteBaseRevisions[i].local) {\n // Found a remoteBaseRevision that fits node.myRevision. Return remoteBaseRevision and eventually a roof maxClientRevision pointing out where next remoteBaseRevision bases its changes on.\n return {\n maxClientRevision: i === node.remoteBaseRevisions.length - 1 ? Infinity : node.remoteBaseRevisions[i + 1].local,\n remoteBaseRevision: node.remoteBaseRevisions[i].remote\n };\n }\n }\n // There are at least one item in the list but the server hasnt yet become up-to-date with the 0 revision from client. \n return {\n maxClientRevision: node.remoteBaseRevisions[0].local,\n remoteBaseRevision: null\n };\n }\n\n function getLocalChangesForNode_autoAckIfEmpty(node, cb) {\n return getLocalChangesForNode(node, function autoAck(changes, remoteBaseRevision, partial, nodeModificationsOnAck) {\n if (changes.length === 0 && 'myRevision' in nodeModificationsOnAck && nodeModificationsOnAck.myRevision !== node.myRevision) {\n Object.keys(nodeModificationsOnAck).forEach(function (keyPath) {\n Dexie.setByKeyPath(node, keyPath, nodeModificationsOnAck[keyPath]);\n });\n node.save();\n return getLocalChangesForNode(node, autoAck);\n } else {\n return cb(changes, remoteBaseRevision, partial, nodeModificationsOnAck);\n }\n });\n }\n\n function getLocalChangesForNode(node, cb) {\n /// \n /// Based on given node's current revision and state, this function makes sure to retrieve next chunk of changes\n /// for that node.\n /// \n /// \n /// Callback that will retrieve next chunk of changes and a boolean telling if it's a partial result or not. If truthy, result is partial and there are more changes to come. If falsy, these changes are the final result.\n\n if (node.myRevision >= 0) {\n // Node is based on a revision in our local database and will just need to get the changes that has occurred since that revision.\n var brmcr = getBaseRevisionAndMaxClientRevision(node);\n return getChangesSinceRevision(node.myRevision, MAX_CHANGES_PER_CHUNK, brmcr.maxClientRevision, function (changes, partial, nodeModificationsOnAck) {\n return cb(changes, brmcr.remoteBaseRevision, partial, nodeModificationsOnAck);\n });\n } else {\n // Node hasn't got anything from our local database yet. We will need to upload entire DB to the node in the form of CREATE changes.\n // Check if we're in the middle of already doing that:\n if (node.dbUploadState === null) {\n // Initiatalize dbUploadState\n var tablesToUpload = db.tables.filter(function (table) {\n return table.schema.observable;\n }).map(function (table) {\n return table.name;\n });\n if (tablesToUpload.length === 0) return Promise.resolve(cb([], null, false, {})); // There are no synched tables at all.\n var dbUploadState = {\n tablesToUpload: tablesToUpload,\n currentTable: tablesToUpload.shift(),\n currentKey: null\n };\n return db._changes.orderBy('rev').last(function (lastChange) {\n dbUploadState.localBaseRevision = lastChange && lastChange.rev || 0;\n var collection = db.table(dbUploadState.currentTable).orderBy(':id');\n return getTableObjectsAsChanges(dbUploadState, [], collection);\n });\n } else if (node.dbUploadState.currentKey) {\n var collection = db.table(node.dbUploadState.currentTable).where(':id').above(node.dbUploadState.currentKey);\n return getTableObjectsAsChanges(Dexie.deepClone(node.dbUploadState), [], collection);\n } else {\n var collection = db.table(dbUploadState.currentTable).orderBy(':id');\n return getTableObjectsAsChanges(Dexie.deepClone(node.dbUploadState), [], collection);\n }\n }\n\n function getTableObjectsAsChanges(state, changes, collection) {\n /// \n /// \n /// \n var limitReached = false;\n return collection.until(function () {\n if (changes.length === MAX_CHANGES_PER_CHUNK) {\n limitReached = true;\n return true;\n }\n }).each(function (item, cursor) {\n changes.push({\n type: CREATE,\n table: state.currentTable,\n key: cursor.key,\n obj: cursor.value\n });\n state.currentKey = cursor.key;\n }).then(function () {\n if (limitReached) {\n // Limit reached. Send partial result.\n hasMoreToGive = true;\n return cb(changes, null, true, { dbUploadState: state });\n } else {\n // Done iterating this table. Check if there are more tables to go through:\n if (state.tablesToUpload.length === 0) {\n // Done iterating all tables\n // Now append changes occurred during our dbUpload:\n var brmcr = getBaseRevisionAndMaxClientRevision(node);\n return getChangesSinceRevision(state.localBaseRevision, MAX_CHANGES_PER_CHUNK - changes.length, brmcr.maxClientRevision, function (additionalChanges, partial, nodeModificationsOnAck) {\n changes = changes.concat(additionalChanges);\n nodeModificationsOnAck.dbUploadState = null;\n return cb(changes, brmcr.remoteBaseRevision, partial, nodeModificationsOnAck);\n });\n } else {\n // Not done iterating all tables. Continue on next table:\n state.currentTable = state.tablesToUpload.shift();\n return getTableObjectsAsChanges(state, changes, db.table(state.currentTable).orderBy(':id'));\n }\n }\n });\n }\n\n function getChangesSinceRevision(revision, maxChanges, maxRevision, cb) {\n /// Callback that will retrieve next chunk of changes and a boolean telling if it's a partial result or not. If truthy, result is partial and there are more changes to come. If falsy, these changes are the final result.\n var changeSet = {};\n var numChanges = 0;\n var partial = false;\n var ignoreSource = node.id;\n var nextRevision = revision;\n return db.transaction('r', db._changes, function () {\n var query = maxRevision === Infinity ? db._changes.where('rev').above(revision) : db._changes.where('rev').between(revision, maxRevision, false, true);\n query.until(function () {\n if (numChanges === maxChanges) {\n partial = true;\n return true;\n }\n }).each(function (change) {\n // Note the revision in nextRevision:\n nextRevision = change.rev;\n if (change.source === ignoreSource) return;\n // Our _changes table contains more info than required (old objs, source etc). Just make sure to include the nescessary info:\n var changeToSend = {\n type: change.type,\n table: change.table,\n key: change.key\n };\n if (change.type === CREATE) changeToSend.obj = change.obj;else if (change.type === UPDATE) changeToSend.mods = change.mods;\n\n var id = change.table + \":\" + change.key;\n var prevChange = changeSet[id];\n if (!prevChange) {\n // This is the first change on this key. Add it unless it comes from the source that we are working against\n changeSet[id] = changeToSend;\n ++numChanges;\n } else {\n // Merge the oldchange with the new change\n var nextChange = changeToSend;\n var mergedChange = function () {\n switch (prevChange.type) {\n case CREATE:\n switch (nextChange.type) {\n case CREATE:\n return nextChange; // Another CREATE replaces previous CREATE.\n case UPDATE:\n return combineCreateAndUpdate(prevChange, nextChange); // Apply nextChange.mods into prevChange.obj\n case DELETE:\n return nextChange; // Object created and then deleted. If it wasnt for that we MUST handle resent changes, we would skip entire change here. But what if the CREATE was sent earlier, and then CREATE/DELETE at later stage? It would become a ghost object in DB. Therefore, we MUST keep the delete change! If object doesnt exist, it wont harm!\n }\n break;\n case UPDATE:\n switch (nextChange.type) {\n case CREATE:\n return nextChange; // Another CREATE replaces previous update.\n case UPDATE:\n return combineUpdateAndUpdate(prevChange, nextChange); // Add the additional modifications to existing modification set.\n case DELETE:\n return nextChange; // Only send the delete change. What was updated earlier is no longer of interest.\n }\n break;\n case DELETE:\n switch (nextChange.type) {\n case CREATE:\n return nextChange; // A resurection occurred. Only create change is of interest.\n case UPDATE:\n return prevChange; // Nothing to do. We cannot update an object that doesnt exist. Leave the delete change there.\n case DELETE:\n return prevChange; // Still a delete change. Leave as is.\n }\n break;\n }\n }();\n changeSet[id] = mergedChange;\n }\n });\n }).then(function () {\n var changes = Object.keys(changeSet).map(function (key) {\n return changeSet[key];\n });\n hasMoreToGive = partial;\n return cb(changes, partial, { myRevision: nextRevision });\n });\n }\n }\n\n function applyRemoteChanges(remoteChanges, remoteRevision, partial, clear) {\n return enque(applyRemoteChanges, function () {\n if (!stillAlive()) return Promise.reject(\"Database not open\");\n // FIXTHIS: Check what to do if clear() is true!\n return (partial ? saveToUncommitedChanges(remoteChanges) : finallyCommitAllChanges(remoteChanges, remoteRevision)).catch(function (error) {\n abortTheProvider(error);\n return Promise.reject(error);\n });\n }, dbAliveID);\n\n function saveToUncommitedChanges(changes) {\n return db.transaction('rw', db._uncommittedChanges, function () {\n changes.forEach(function (change) {\n var changeToAdd = {\n node: node.id,\n type: change.type,\n table: change.table,\n key: change.key\n };\n if (change.obj) changeToAdd.obj = change.obj;\n if (change.mods) changeToAdd.mods = change.mods;\n db._uncommittedChanges.add(changeToAdd);\n });\n }).then(function () {\n node.appliedRemoteRevision = remoteRevision;\n node.save();\n });\n }\n\n function finallyCommitAllChanges(changes, remoteRevision) {\n //alert(\"finallyCommitAllChanges() will now start its job.\");\n //var tick = Date.now();\n\n // 1. Open a write transaction on all tables in DB\n return db.transaction('rw', db.tables.filter(function (table) {\n return table.name === '_changes' || table.name === '_uncommittedChanges' || table.schema.observable;\n }), function () {\n var trans = Dexie.currentTransaction;\n var localRevisionBeforeChanges = 0;\n db._changes.orderBy('rev').last(function (lastChange) {\n // Store what revision we were at before committing the changes\n localRevisionBeforeChanges = lastChange && lastChange.rev || 0;\n }).then(function () {\n // Specify the source. Important for the change consumer to ignore changes originated from self!\n trans.source = node.id;\n // 2. Apply uncommitted changes and delete each uncommitted change\n return db._uncommittedChanges.where('node').equals(node.id).toArray();\n }).then(function (uncommittedChanges) {\n return applyChanges(uncommittedChanges, 0);\n }).then(function () {\n return db._uncommittedChanges.where('node').equals(node.id).delete();\n }).then(function () {\n // 3. Apply last chunk of changes\n return applyChanges(changes, 0);\n }).then(function () {\n // Get what revision we are at now:\n return db._changes.orderBy('rev').last();\n }).then(function (lastChange) {\n var currentLocalRevision = lastChange && lastChange.rev || 0;\n // 4. Update node states (appliedRemoteRevision, remoteBaseRevisions and eventually myRevision)\n node.appliedRemoteRevision = remoteRevision;\n node.remoteBaseRevisions.push({ remote: remoteRevision, local: currentLocalRevision });\n if (node.myRevision === localRevisionBeforeChanges) {\n // If server was up-to-date before we added new changes from the server, update myRevision to last change\n // because server is still up-to-date! This is also important in order to prohibit getLocalChangesForNode() from\n // ever sending an empty change list to server, which would otherwise be done every second time it would send changes.\n node.myRevision = currentLocalRevision;\n }\n // Garbage collect remoteBaseRevisions not in use anymore:\n if (node.remoteBaseRevisions.length > 1) {\n for (var i = node.remoteBaseRevisions.length - 1; i > 0; --i) {\n if (node.myRevision >= node.remoteBaseRevisions[i].local) {\n node.remoteBaseRevisions.splice(0, i);\n break;\n }\n }\n }\n node.save(); // We are not including _syncNodes in transaction, so this save() call will execute in its own transaction.\n //var tock = Date.now();\n //alert(\"finallyCommitAllChanges() has done its job. \" + changes.length + \" changes applied in \" + ((tock - tick) / 1000) + \"seconds\");\n });\n\n function applyChanges(changes, offset) {\n /// \n /// \n var lastChangeType = 0;\n var lastCreatePromise = null;\n if (offset >= changes.length) return Promise.resolve(null);\n var change = changes[offset];\n var table = db.table(change.table);\n while (change && change.type === CREATE) {\n // Optimize CREATE changes because on initial sync with server, the entire DB will be downloaded in forms of CREATE changes.\n // Instead of waiting for each change to resolve, do all CREATE changes in bulks until another type of change is stepped upon.\n // This case is the only case that allows i to increment and the for-loop to continue since it does not return anything.\n var specifyKey = !table.schema.primKey.keyPath;\n lastCreatePromise = function (change, table, specifyKey) {\n return (specifyKey ? table.add(change.obj, change.key) : table.add(change.obj)).catch(\"ConstraintError\", function (e) {\n return specifyKey ? table.put(change.obj, change.key) : table.put(change.obj);\n });\n }(change, table, specifyKey);\n change = changes[++offset];\n if (change) table = db.table(change.table);\n }\n\n if (lastCreatePromise) {\n // We did some CREATE changes but now stumbled upon another type of change.\n // Let's wait for the last CREATE change to resolve and then call applyChanges again at current position. Next time, lastCreatePromise will be null and a case below will happen.\n return lastCreatePromise.then(function () {\n return offset < changes.length ? applyChanges(changes, offset) : null;\n });\n }\n\n if (change) {\n if (change.type === UPDATE) {\n return table.update(change.key, change.mods).then(function () {\n // Wait for update to resolve before taking next change. Why? Because it will lock transaction anyway since we are listening to CRUD events here.\n return applyChanges(changes, offset + 1);\n });\n }\n\n if (change.type === DELETE) {\n return table.delete(change.key).then(function () {\n // Wait for delete to resolve before taking next change. Why? Because it will lock transaction anyway since we are listening to CRUD events here.\n return applyChanges(changes, offset + 1);\n });\n }\n }\n\n return Promise.resolve(null); // Will return null or a Promise and make the entire applyChanges promise finally resolve.\n }\n });\n }\n }\n\n //\n //\n // Continuation Patterns Follows\n //\n //\n\n function continueSendingChanges(continuation) {\n if (!stillAlive()) {\n // Database was closed.\n if (continuation.disconnect) continuation.disconnect();\n return;\n }\n\n connectedContinuation = continuation;\n activePeer.on('disconnect', function () {\n if (connectedContinuation) {\n if (connectedContinuation.react) {\n try {\n // react pattern must provide a disconnect function.\n connectedContinuation.disconnect();\n } catch (e) {}\n }\n connectedContinuation = null; // Stop poll() pattern from polling again and abortTheProvider() from being called twice.\n }\n });\n\n if (continuation.react) {\n continueUsingReactPattern(continuation);\n } else {\n continueUsingPollPattern(continuation);\n }\n }\n\n // React Pattern (eager)\n function continueUsingReactPattern(continuation) {\n var changesWaiting, // Boolean\n isWaitingForServer; // Boolean\n\n\n function onChanges() {\n if (connectedContinuation) {\n changeStatusTo(Statuses.SYNCING);\n if (isWaitingForServer) changesWaiting = true;else {\n reactToChanges();\n }\n }\n }\n\n db.on('changes', onChanges);\n\n // Override disconnect() to also unsubscribe to onChanges.\n activePeer.on('disconnect', function () {\n db.on.changes.unsubscribe(onChanges);\n });\n\n function reactToChanges() {\n if (!connectedContinuation) return;\n changesWaiting = false;\n isWaitingForServer = true;\n getLocalChangesForNode_autoAckIfEmpty(node, function (changes, remoteBaseRevision, partial, nodeModificationsOnAck) {\n if (!connectedContinuation) return;\n if (changes.length > 0) {\n continuation.react(changes, remoteBaseRevision, partial, function onChangesAccepted() {\n Object.keys(nodeModificationsOnAck).forEach(function (keyPath) {\n Dexie.setByKeyPath(node, keyPath, nodeModificationsOnAck[keyPath]);\n });\n node.save();\n // More changes may be waiting:\n reactToChanges();\n });\n } else {\n isWaitingForServer = false;\n if (changesWaiting) {\n // A change jumped in between the time-spot of quering _changes and getting called back with zero changes.\n // This is an expreemely rare scenario, and eventually impossible. But need to be here because it could happen in theory.\n reactToChanges();\n } else {\n changeStatusTo(Statuses.ONLINE);\n }\n }\n }).catch(abortTheProvider);\n }\n\n reactToChanges();\n }\n\n // Poll Pattern\n function continueUsingPollPattern() {\n\n function syncAgain() {\n getLocalChangesForNode_autoAckIfEmpty(node, function (changes, remoteBaseRevision, partial, nodeModificationsOnAck) {\n\n protocolInstance.sync(node.syncContext, url, options, remoteBaseRevision, node.appliedRemoteRevision, changes, partial, applyRemoteChanges, onChangesAccepted, onSuccess, onError);\n\n function onChangesAccepted() {\n Object.keys(nodeModificationsOnAck).forEach(function (keyPath) {\n Dexie.setByKeyPath(node, keyPath, nodeModificationsOnAck[keyPath]);\n });\n node.save();\n }\n\n function onSuccess(continuation) {\n if (!connectedContinuation) {\n // Got disconnected before succeeding. Quit.\n return;\n }\n connectedContinuation = continuation;\n if (partial) {\n // We only sent partial changes. Need to do another round asap.\n syncAgain();\n } else {\n // We've sent all changes now (in sync!)\n if (!isNaN(continuation.again) && continuation.again < Infinity) {\n // Provider wants to keep polling. Set Status to ONLINE.\n changeStatusTo(Statuses.ONLINE);\n setTimeout(function () {\n if (connectedContinuation) {\n changeStatusTo(Statuses.SYNCING);\n syncAgain();\n }\n }, continuation.again);\n } else {\n // Provider seems finished polling. Since we are never going to poll again,\n // disconnect provider and set status to OFFLINE until another call to db.syncable.connect().\n activePeer.disconnect(Statuses.OFFLINE);\n }\n }\n }\n\n function onError(error, again) {\n if (!isNaN(again) && again < Infinity) {\n if (connectedContinuation) {\n setTimeout(function () {\n if (connectedContinuation) {\n changeStatusTo(Statuses.SYNCING);\n syncAgain();\n }\n }, again);\n changeStatusTo(Statuses.ERROR_WILL_RETRY);\n } // else status is already changed since we got disconnected.\n } else {\n abortTheProvider(error); // Will fire ERROR on onStatusChanged.\n }\n }\n }).catch(abortTheProvider);\n }\n\n if (hasMoreToGive) {\n syncAgain();\n } else if (connectedContinuation && !isNaN(connectedContinuation.again) && connectedContinuation.again < Infinity) {\n changeStatusTo(Statuses.ONLINE);\n setTimeout(function () {\n if (connectedContinuation) {\n changeStatusTo(Statuses.SYNCING);\n syncAgain();\n }\n }, connectedContinuation.again);\n }\n }\n }\n }\n\n db.close = override(db.close, function (origClose) {\n return function () {\n activePeers.forEach(function (peer) {\n peer.disconnect();\n });\n return origClose.apply(this, arguments);\n };\n });\n\n var syncNodeSaveQueContexts = {};\n db.observable.SyncNode.prototype.save = function () {\n var self = this;\n return db.transaction('rw?', db._syncNodes, function () {\n db._syncNodes.put(self);\n });\n };\n\n function enque(context, fn, instanceID) {\n function _enque() {\n if (!context.ongoingOperation) {\n context.ongoingOperation = Dexie.ignoreTransaction(function () {\n return Dexie.vip(function () {\n return fn();\n });\n }).then(function (res) {\n delete context.ongoingOperation;\n return res;\n });\n } else {\n context.ongoingOperation = context.ongoingOperation.then(function () {\n return enque(context, fn, instanceID);\n });\n }\n return context.ongoingOperation;\n }\n\n if (!instanceID) {\n // Caller wants to enque it until database becomes open.\n if (db.isOpen()) {\n return _enque();\n } else {\n return Promise.reject(new Error(\"Database was closed\"));\n }\n } else if (db._localSyncNode && instanceID === db._localSyncNode.id) {\n // DB is already open but queuer doesnt want it to be queued if database has been closed (request bound to current instance of DB)\n return _enque();\n } else {\n return Promise.reject(new Error(\"Database was closed\"));\n }\n }\n\n function combineCreateAndUpdate(prevChange, nextChange) {\n var clonedChange = Dexie.deepClone(prevChange); // Clone object before modifying since the earlier change in db.changes[] would otherwise be altered.\n Object.keys(nextChange.mods).forEach(function (keyPath) {\n setByKeyPath(clonedChange.obj, keyPath, nextChange.mods[keyPath]);\n });\n return clonedChange;\n }\n\n function combineUpdateAndUpdate(prevChange, nextChange) {\n var clonedChange = Dexie.deepClone(prevChange); // Clone object before modifying since the earlier change in db.changes[] would otherwise be altered.\n Object.keys(nextChange.mods).forEach(function (keyPath) {\n // If prev-change was changing a parent path of this keyPath, we must update the parent path rather than adding this keyPath\n var hadParentPath = false;\n Object.keys(prevChange.mods).filter(function (parentPath) {\n return keyPath.indexOf(parentPath + '.') === 0;\n }).forEach(function (parentPath) {\n setByKeyPath(clonedChange[parentPath], keyPath.substr(parentPath.length + 1), nextChange.mods[keyPath]);\n hadParentPath = true;\n });\n if (!hadParentPath) {\n // Add or replace this keyPath and its new value\n clonedChange.mods[keyPath] = nextChange.mods[keyPath];\n }\n // In case prevChange contained sub-paths to the new keyPath, we must make sure that those sub-paths are removed since\n // we must mimic what would happen if applying the two changes after each other:\n Object.keys(prevChange.mods).filter(function (subPath) {\n return subPath.indexOf(keyPath + '.') === 0;\n }).forEach(function (subPath) {\n delete clonedChange[subPath];\n });\n });\n return clonedChange;\n }\n};\n\nSyncable.Statuses = {\n ERROR: -1, // An irrepairable error occurred and the sync provider is dead.\n OFFLINE: 0, // The sync provider hasnt yet become online, or it has been disconnected.\n CONNECTING: 1, // Trying to connect to server\n ONLINE: 2, // Connected to server and currently in sync with server\n SYNCING: 3, // Syncing with server. For poll pattern, this is every poll call. For react pattern, this is when local changes are being sent to server.\n ERROR_WILL_RETRY: 4 // An error occured such as net down but the sync provider will retry to connect.\n};\n\nSyncable.StatusTexts = {\n \"-1\": \"ERROR\",\n \"0\": \"OFFLINE\",\n \"1\": \"CONNECTING\",\n \"2\": \"ONLINE\",\n \"3\": \"SYNCING\",\n \"4\": \"ERROR_WILL_RETRY\"\n};\n\nSyncable.registeredProtocols = {}; // Map when key is the provider name.\n\nSyncable.registerSyncProtocol = function (name, protocolInstance) {\n /// \n /// Register a syncronization protocol that can syncronize databases with remote servers.\n /// \n /// Provider name\n /// Implementation of ISyncProtocol\n Syncable.registeredProtocols[name] = protocolInstance;\n};\n\n// Register addon in Dexie:\nDexie.Syncable = Syncable;\nDexie.addons.push(Syncable);"]}
\ No newline at end of file
diff --git a/dist/README.md b/dist/README.md
deleted file mode 100644
index edbd0da65..000000000
--- a/dist/README.md
+++ /dev/null
@@ -1,51 +0,0 @@
-## Can't find dexie.js?
-Dexie's dist files are no longer checked in to github except temporarily when tagging
-a release version (just so that bower will continue to work). The reason for this is because
-checking in dist files bloats the commit history and makes it error prone to contribute to the
-repo. To support bower though, we have to continue checking in dist files when releasing,
-but that is now handled by the release.sh script, who also removes them directly afterwards.
-
-If you still just want to download dexie.js to include in a test HTML page, go
-to the following download site:
-
-### Download
-[dexie.min.js](https://unpkg.com/dexie/dist/dexie.min.js)
-
-[dexie.min.js.map](https://unpkg.com/dexie/dist/dexie.min.js.map)
-
-### Typings
-[dexie.d.ts](https://unpkg.com/dexie/dist/dexie.d.ts)
-
-### Optional Stuff
-[dexie.js (non-minified version)](https://unpkg.com/dexie/dist/dexie.js)
-
-[dexie.js.map](https://unpkg.com/dexie/dist/dexie.js.map)
-
-[dexie.min.js.gz (Minified and gzipped)](https://unpkg.com/dexie/dist/dexie.min.js.gz)
-
-## Install from NPM
-```
-npm install dexie --save
-```
-
-## Install from bower
-```
-bower install dexie --save
-```
-
-## How to build
-1. cd to dexie package
-2. npm install
-3. npm run build
-
-## Contributing to Dexie.js?
-
-Watch:
-```
-npm run watch
-```
-
-Test:
-```
-npm test
-```
diff --git a/dist/dexie.d.ts b/dist/dexie.d.ts
new file mode 100644
index 000000000..40e4f3402
--- /dev/null
+++ b/dist/dexie.d.ts
@@ -0,0 +1,454 @@
+// Type definitions for Dexie v1.5.0
+// Project: https://github.com/dfahlander/Dexie.js
+// Definitions by: David Fahlander
+// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
+
+interface Thenable {
+ then(onFulfilled: (value: R) => Thenable, onRejected: (error: any) => Thenable): Thenable;
+ then(onFulfilled: (value: R) => Thenable, onRejected?: (error: any) => U): Thenable;
+ then(onFulfilled: (value: R) => U, onRejected: (error: any) => Thenable): Thenable;
+ then(onFulfilled?: (value: R) => U, onRejected?: (error: any) => U): Thenable;
+}
+
+declare type IndexableType = string | number | Date | Array;
+
+declare class Dexie {
+ constructor(databaseName: string, options?: {
+ addons?: Array<(db: Dexie) => void>,
+ autoOpen?: boolean,
+ indexedDB?: IDBFactory,
+ IDBKeyRange?: IDBKeyRange
+ });
+
+ name: string;
+ tables: Dexie.Table[];
+ verno: number;
+
+ static addons: Array<(db: Dexie) => void>;
+ static version: number;
+ static semVer: string;
+ static currentTransaction: Dexie.Transaction;
+
+ static getDatabaseNames(): Dexie.Promise>;
+
+ static getDatabaseNames(onFulfilled: (value: Array) => Thenable): Dexie.Promise;
+
+ static getDatabaseNames(onFulfilled: (value: Array) => U): Dexie.Promise;
+
+ static override (origFunc:F, overridedFactory: (fn:any)=>any) : F;
+
+ static getByKeyPath(obj: Object, keyPath: string): any;
+
+ static setByKeyPath(obj: Object, keyPath: string, value: any): void;
+
+ static delByKeyPath(obj: Object, keyPath: string): void;
+
+ static shallowClone (obj: T): T;
+
+ static deepClone(obj: T): T;
+
+ static asap(fn: Function) : void;
+
+ static maxKey: Array;
+
+ static dependencies: {
+ indexedDB: IDBFactory,
+ IDBKeyRange: IDBKeyRange,
+ localStorage?: Storage
+ };
+
+ static default: Dexie;
+
+ version(versionNumber: Number): Dexie.Version;
+
+ on: {
+ (eventName: string, subscriber: Function, ...args : any[]): void;
+ (eventName: 'ready', subscriber: () => any, bSticky: boolean): void;
+ (eventName: 'error', subscriber: (error: any) => any): void;
+ (eventName: 'populate', subscriber: () => any): void;
+ (eventName: 'blocked', subscriber: () => any): void;
+ (eventName: 'versionchange', subscriber: (event: IDBVersionChangeEvent) => any): void;
+ ready: Dexie.DexieOnReadyEvent;
+ error: Dexie.DexieErrorEvent;
+ populate: Dexie.DexieEvent;
+ blocked: Dexie.DexieEvent;
+ versionchange: Dexie.DexieVersionChangeEvent;
+ }
+
+ open(): Dexie.Promise;
+
+ table(tableName: string): Dexie.Table;
+
+ table(tableName: string): Dexie.Table;
+
+ table(tableName: string): Dexie.Table;
+
+ transaction(mode: string, table: Dexie.Table, scope: () => Thenable): Dexie.Promise;
+
+ transaction(mode: string, table: Dexie.Table, table2: Dexie.Table, scope: () => Thenable): Dexie.Promise;
+
+ transaction(mode: string, table: Dexie.Table, table2: Dexie.Table, table3: Dexie.Table, scope: () => Thenable): Dexie.Promise;
+
+ transaction(mode: string, tables: Dexie.Table[], scope: () => Thenable): Dexie.Promise;
+
+ transaction(mode: string, table: Dexie.Table, scope: () => U): Dexie.Promise;
+
+ transaction(mode: string, table: Dexie.Table, table2: Dexie.Table, scope: () => U): Dexie.Promise;
+
+ transaction(mode: string, table: Dexie.Table, table2: Dexie.Table, table3: Dexie.Table, scope: () => U): Dexie.Promise;
+
+ transaction(mode: string, tables: Dexie.Table[], scope: () => U): Dexie.Promise;
+
+ close(): void;
+
+ delete(): Dexie.Promise;
+
+ exists(name : string) : Dexie.Promise;
+
+ isOpen(): boolean;
+
+ hasFailed(): boolean;
+
+ backendDB(): IDBDatabase;
+
+ vip(scopeFunction: () => U): U;
+
+ // Make it possible to touch physical class constructors where they reside - as properties on db instance.
+ // For example, checking if (x instanceof db.Table). Can't do (x instanceof Dexie.Table because it's just a virtual interface)
+ Table : new()=>Dexie.Table;
+ WhereClause: new()=>Dexie.WhereClause;
+ Version: new()=>Dexie.Version;
+ WriteableTable: new()=>Dexie.Table;
+ Transaction: new()=>Dexie.Transaction;
+ Collection: new()=>Dexie.Collection;
+ WriteableCollection: new()=>Dexie.Collection;
+}
+
+declare module Dexie {
+
+ class Promise implements Thenable {
+ constructor(callback: (resolve: (value?: Thenable) => void, reject: (error?: any) => void) => void);
+
+ constructor(callback: (resolve: (value?: R) => void, reject: (error?: any) => void) => void);
+
+ then(onFulfilled: (value: R) => Thenable, onRejected: (error: any) => Thenable): Promise;
+
+ then(onFulfilled: (value: R) => Thenable, onRejected?: (error: any) => U): Promise;
+
+ then(onFulfilled: (value: R) => U, onRejected: (error: any) => Thenable): Promise;
+
+ then(onFulfilled?: (value: R) => U, onRejected?: (error: any) => U): Promise;
+
+ catch(onRejected: (error: any) => Thenable): Promise;
+
+ catch(onRejected: (error: any) => U): Promise;
+
+ catch(ExceptionType: (new() => ET), onRejected: (error: ET) => Promise): Promise;
+
+ catch(ExceptionType: (new() => ET), onRejected: (error: ET) => U): Promise;
+
+ catch(errorName: string, onRejected: (error: {name: string}) => Promise): Promise;
+
+ catch(errorName: string, onRejected: (error: {name: string}) => U): Promise;
+
+ finally(onFinally: () => any): Promise;
+
+ onuncatched: () => any;
+ }
+
+ module Promise {
+ function resolve(value?: Thenable): Promise;
+
+ function resolve(value?: R): Promise;
+
+ function reject(error: any): Promise;
+
+ function all(promises: Thenable[]): Promise;
+
+ function all(...promises: Thenable[]): Promise;
+
+ function race(promises: Thenable[]): Promise;
+
+ function newPSD(scope: () => R): R;
+
+ var PSD: any;
+
+ var on: {
+ (eventName: string, subscriber: Function): void;
+ (eventName: 'error', subscriber: (error: any) => any): void;
+ error: DexieErrorEvent;
+ }
+ }
+
+
+ interface Version {
+ stores(schema: { [key: string]: string }): Version;
+ upgrade(fn: (trans: Transaction) => void): Version;
+ }
+
+ interface Transaction {
+ active: boolean;
+ db: Dexie;
+ mode: string;
+ idbtrans: IDBTransaction;
+ tables: { [type: string]: Table };
+ storeNames: Array;
+ on: {
+ (eventName: string, subscriber: () => any): void;
+ (eventName: 'complete', subscriber: () => any): void;
+ (eventName: 'abort', subscriber: () => any): void;
+ (eventName: 'error', subscriber: (error:any) => any): void;
+ complete: DexieEvent;
+ abort: DexieEvent;
+ error: DexieEvent;
+ }
+ abort(): void;
+ table(tableName: string): Table;
+ table(tableName: string): Table;
+ table(tableName: string): Table;
+ }
+
+ interface DexieEvent {
+ subscribe(fn: () => any): void;
+ unsubscribe(fn: () => any): void;
+ fire(): any;
+ }
+
+ interface DexieErrorEvent {
+ subscribe(fn: (error: any) => any): void;
+ unsubscribe(fn: (error: any) => any): void;
+ fire(error: any): any;
+ }
+
+ interface DexieVersionChangeEvent {
+ subscribe(fn: (event: IDBVersionChangeEvent) => any): void;
+ unsubscribe(fn: (event: IDBVersionChangeEvent) => any): void;
+ fire(event: IDBVersionChangeEvent): any;
+ }
+
+ interface DexieOnReadyEvent {
+ subscribe(fn: () => any, bSticky: boolean): void;
+ unsubscribe(fn: () => any): void;
+ fire(): any;
+ }
+
+ interface Table {
+ name: string;
+ schema: TableSchema;
+ hook: {
+ (eventName: string, subscriber: () => any): void;
+ creating: DexieEvent;
+ reading: DexieEvent;
+ updating: DexieEvent;
+ deleting: DexieEvent;
+ }
+
+ get(key: Key): Promise;
+ where(index: string): WhereClause;
+
+ filter(fn: (obj: T) => boolean): Collection;
+
+ count(): Promise;
+ count(onFulfilled: (value: number) => Thenable): Promise;
+ count(onFulfilled: (value: number) => U): Promise;
+
+ offset(n: number): Collection;
+
+ limit(n: number): Collection;
+
+ each(callback: (obj: T, cursor: {key: IndexableType, primaryKey: Key}) => any): Promise;
+
+ toArray(): Promise>;
+ toArray(onFulfilled: (value: Array) => Thenable): Promise;
+ toArray(onFulfilled: (value: Array) => U): Promise;
+
+ toCollection(): Collection;
+ orderBy(index: string): Collection;
+ reverse(): Collection;
+ mapToClass(constructor: Function): Function;
+ add(item: T, key?: Key): Promise;
+ update(key: Key, changes: { [keyPath: string]: any }): Promise;
+ put(item: T, key?: Key): Promise;
+ delete(key: Key): Promise;
+ clear(): Promise;
+ bulkAdd(items: T[], keys?: IndexableType[]): Promise;
+ bulkPut(items: T[], keys?: IndexableType[]): Promise;
+ bulkDelete(keys: IndexableType[]) : Promise;
+ }
+
+ interface WhereClause {
+ above(key: IndexableType): Collection;
+ aboveOrEqual(key: IndexableType): Collection;
+ anyOf(keys: IndexableType[]): Collection;
+ anyOf(...keys: IndexableType[]): Collection;
+ anyOfIgnoreCase(keys: string[]): Collection;
+ anyOfIgnoreCase(...keys: string[]): Collection;
+ below(key: IndexableType): Collection;
+ belowOrEqual(key: IndexableType): Collection;
+ between(lower: IndexableType, upper: IndexableType, includeLower?: boolean, includeUpper?: boolean): Collection;
+ equals(key: IndexableType): Collection;
+ equalsIgnoreCase(key: string): Collection;
+ inAnyRange(ranges: Array): Collection;
+ startsWith(key: string): Collection;
+ startsWithAnyOf(prefixes: string[]): Collection;
+ startsWithAnyOf(...prefixes: string[]): Collection;
+ startsWithIgnoreCase(key: string): Collection;
+ startsWithAnyOfIgnoreCase(prefixes: string[]): Collection;
+ startsWithAnyOfIgnoreCase(...prefixes: string[]): Collection;
+ noneOf(keys: Array): Collection;
+ notEqual(key: IndexableType): Collection;
+ }
+
+ interface Collection {
+ and(filter: (x: T) => boolean): Collection;
+ clone(props?: Object): Collection;
+ count(): Promise;
+ count(onFulfilled: (value: number) => Thenable): Promise;
+ count(onFulfilled: (value: number) => U): Promise;
+ distinct(): Collection;
+ each(callback: (obj: T, cursor: {key: IndexableType, primaryKey: Key}) => any): Promise;
+ eachKey(callback: (key: IndexableType, cursor: {key: IndexableType, primaryKey: Key}) => any): Promise;
+ eachPrimaryKey(callback: (key: Key, cursor: {key: IndexableType, primaryKey: Key}) => any): Promise;
+ eachUniqueKey(callback: (key: IndexableType, cursor: {key: IndexableType, primaryKey: Key}) => any): Promise;
+ filter(filter: (x: T) => boolean): Collection;
+ first(): Promise;
+ first(onFulfilled: (value: T) => Thenable): Promise;
+ first(onFulfilled: (value: T) => U): Promise;
+ keys(): Promise;
+ keys(onFulfilled: (value: IndexableType[]) => Thenable): Promise;
+ keys(onFulfilled: (value: IndexableType[]) => U): Promise;
+ primaryKeys(): Promise;
+ primaryKeys(onFulfilled: (value: Key[]) => Thenable): Promise;
+ primaryKeys(onFulfilled: (value: Key[]) => U): Promise;
+ last(): Promise;
+ last(onFulfilled: (value: T) => Thenable): Promise;
+ last(onFulfilled: (value: T) => U): Promise;
+ limit(n: number): Collection;
+ offset(n: number): Collection;
+ or(indexOrPrimayKey: string): WhereClause;
+ raw(): Collection;
+ reverse(): Collection;
+ sortBy(keyPath: string): Promise;
+ sortBy(keyPath: string, onFulfilled: (value: T[]) => Thenable): Promise;
+ sortBy(keyPath: string, onFulfilled: (value: T[]) => U): Promise;
+ toArray(): Promise>;
+ toArray(onFulfilled: (value: Array) => Thenable): Promise;
+ toArray(onFulfilled: (value: Array) => U): Promise;
+ uniqueKeys(): Promise;
+ uniqueKeys(onFulfilled: (value: IndexableType[]) => Thenable): Promise;
+ uniqueKeys(onFulfilled: (value: IndexableType[]) => U): Promise