firetruss
Version:
Advanced data sync layer for Firebase and Vue.js
1,413 lines (1,204 loc) • 149 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('lodash'), require('vue')) :
typeof null === 'function' && null.amd ? null(['lodash', 'vue'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Truss = factory(global._, global.Vue));
}(this, (function (_, Vue) { 'use strict';
_ = _ && Object.prototype.hasOwnProperty.call(_, 'default') ? _['default'] : _;
Vue = Vue && Object.prototype.hasOwnProperty.call(Vue, 'default') ? Vue['default'] : Vue;
var vue;
var lastDigestRequest = 0, digestInProgress = false;
var bareDigest = function() {
if (vue.digestRequest > lastDigestRequest) { return; }
vue.digestRequest = lastDigestRequest + 1;
};
var angularProxy = {
active: typeof window !== 'undefined' && window.angular
};
if (angularProxy.active) {
initAngular();
} else {
_.forEach(['digest', 'watch', 'defineModule', 'debounceDigest'], function (method) {
angularProxy[method] = _.noop;
});
}
function initAngular() {
var module = window.angular.module('firetruss', []);
angularProxy.digest = bareDigest;
angularProxy.watch = function() {throw new Error('Angular watch proxy not yet initialized');};
angularProxy.defineModule = function(Truss) {
module.constant('Truss', Truss);
};
angularProxy.debounceDigest = function(wait) {
if (wait) {
var debouncedDigest = _.debounce(bareDigest, wait);
angularProxy.digest = function() {
if (vue.digestRequest > lastDigestRequest) { return; }
if (digestInProgress) { bareDigest(); } else { debouncedDigest(); }
};
} else {
angularProxy.digest = bareDigest;
}
};
module.config(['$provide', function($provide) {
$provide.decorator('$rootScope', ['$delegate', '$exceptionHandler',
function($delegate, $exceptionHandler) {
var rootScope = $delegate;
angularProxy.watch = rootScope.$watch.bind(rootScope);
var proto = Object.getPrototypeOf(rootScope);
var angularDigest = proto.$digest;
proto.$digest = bareDigest;
proto.$digest.original = angularDigest;
vue = new Vue({data: {digestRequest: 0}});
vue.$watch(function () { return vue.digestRequest; }, function () {
if (vue.digestRequest > lastDigestRequest) {
// Make sure we execute the digest outside the Vue task queue, because otherwise if the
// client replaced Promise with angular.$q all Truss.nextTick().then() functions will be
// executed inside the Angular digest and hence inside the Vue task queue. But
// Truss.nextTick() is used precisely to avoid that. Note that it's OK to use
// Vue.nextTick() here because even though it will schedule a flush via Promise.then()
// it only uses the native Promise, before it could've been monkey-patched by the app.
Vue.nextTick(function () {
if (vue.digestRequest <= lastDigestRequest) { return; }
digestInProgress = true;
rootScope.$digest.original.call(rootScope);
lastDigestRequest = vue.digestRequest = vue.digestRequest + 1;
});
} else {
digestInProgress = false;
}
});
_.last(vue._watchers).id = Infinity; // make sure watcher is scheduled last
patchRenderWatcherGet(Object.getPrototypeOf(_.last(vue._watchers)));
return rootScope;
}
]);
}]);
}
// This is a kludge that catches errors that get through render watchers and end up killing the
// entire Vue event loop (e.g., errors raised in transition callbacks). The state of the DOM may
// not be consistent after such an error is caught, but the global error handler should stop the
// world anyway. May be related to https://github.com/vuejs/vue/issues/7653.
function patchRenderWatcherGet(prototype) {
var originalGet = prototype.get;
prototype.get = function get() {
try {
return originalGet.call(this);
} catch (e) {
if (this.vm._watcher === this && Vue.config.errorHandler) {
Vue.config.errorHandler(e, this.vm, 'uncaught render error');
} else {
throw e;
}
}
};
}
var LruCacheItem = function LruCacheItem(key, value) {
this.key = key;
this.value = value;
this.touch();
};
LruCacheItem.prototype.touch = function touch () {
this.timestamp = Date.now();
};
var LruCache = function LruCache(maxSize, pruningSize) {
this._items = Object.create(null);
this._size = 0;
this._maxSize = maxSize;
this._pruningSize = pruningSize || Math.ceil(maxSize * 0.10);
};
LruCache.prototype.has = function has (key) {
return Boolean(this._items[key]);
};
LruCache.prototype.get = function get (key) {
var item = this._items[key];
if (!item) { return; }
item.touch();
return item.value;
};
LruCache.prototype.set = function set (key, value) {
var item = this._items[key];
if (item) {
item.value = value;
} else {
if (this._size >= this._maxSize) { this._prune(); }
this._items[key] = new LruCacheItem(key, value);
this._size += 1;
}
};
LruCache.prototype.delete = function delete$1 (key) {
var item = this._items[key];
if (!item) { return; }
delete this._items[key];
this._size -= 1;
};
LruCache.prototype._prune = function _prune () {
var itemsToPrune =
_(this._items).toArray().sortBy('timestamp').take(this._pruningSize).value();
for (var i = 0, list = itemsToPrune; i < list.length; i += 1) {
var item = list[i];
this.delete(item.key);
}
};
var pathSegments = new LruCache(1000);
var pathMatchers = {};
var maxNumPathMatchers = 1000;
function escapeKey(key) {
if (!key) { return key; }
return key.toString().replace(/[\\.$#[\]/]/g, function(char) {
return '\\' + char.charCodeAt(0).toString(16);
});
}
function unescapeKey(key) {
if (!key) { return key; }
return key.toString().replace(/\\[0-9a-f]{2}/gi, function(code) {
return String.fromCharCode(parseInt(code.slice(1), 16));
});
}
function escapeKeys(object) {
// isExtensible check avoids trying to escape references to Firetruss internals.
if (!(_.isObject(object) && Object.isExtensible(object))) { return object; }
var result = object;
for (var key in object) {
if (!object.hasOwnProperty(key)) { continue; }
var value = object[key];
var escapedKey = escapeKey(key);
var escapedValue = escapeKeys(value);
if (escapedKey !== key || escapedValue !== value) {
if (result === object) { result = _.clone(object); }
result[escapedKey] = escapedValue;
if (result[key] === value) { delete result[key]; }
}
}
return result;
}
function joinPath() {
var segments = [];
for (var i = 0, list = arguments; i < list.length; i += 1) {
var segment = list[i];
if (!_.isString(segment)) { segment = '' + segment; }
if (segment.charAt(0) === '/') { segments.splice(0, segments.length); }
segments.push(segment);
}
if (segments[0] === '/') { segments[0] = ''; }
return segments.join('/');
}
function splitPath(path, leaveSegmentsEscaped) {
var key = (leaveSegmentsEscaped ? 'esc:' : '') + path;
var segments = pathSegments.get(key);
if (!segments) {
segments = path.split('/');
if (!leaveSegmentsEscaped) { segments = _.map(segments, unescapeKey); }
pathSegments.set(key, segments);
}
return segments;
}
var PathMatcher = function PathMatcher(pattern) {
var this$1 = this;
this.variables = [];
var prefixMatch = _.endsWith(pattern, '/$*');
if (prefixMatch) { pattern = pattern.slice(0, -3); }
var pathTemplate = pattern.replace(/\/\$[^/]*/g, function (match) {
if (match.length > 1) { this$1.variables.push(match.slice(1)); }
return '\u0001';
});
Object.freeze(this.variables);
if (/[.$#[\]]|\\(?![0-9a-f][0-9a-f])/i.test(pathTemplate)) {
throw new Error('Path pattern has unescaped keys: ' + pattern);
}
this._regex = new RegExp(
// eslint-disable-next-line no-control-regex
'^' + pathTemplate.replace(/\u0001/g, '/([^/]+)') + (prefixMatch ? '($|/)' : '$'));
};
PathMatcher.prototype.match = function match (path) {
this._regex.lastIndex = 0;
var match = this._regex.exec(path);
if (!match) { return; }
var bindings = {};
for (var i = 0; i < this.variables.length; i++) {
bindings[this.variables[i]] = unescapeKey(match[i + 1]);
}
return bindings;
};
PathMatcher.prototype.test = function test (path) {
return this._regex.test(path);
};
PathMatcher.prototype.toString = function toString () {
return this._regex.toString();
};
function makePathMatcher(pattern) {
var matcher = pathMatchers[pattern];
if (!matcher) {
matcher = new PathMatcher(pattern);
// Minimal pseudo-LRU behavior, since we don't expect to actually fill up the cache.
if (_.size(pathMatchers) === maxNumPathMatchers) { delete pathMatchers[_.keys(pathMatchers)[0]]; }
pathMatchers[pattern] = matcher;
}
return matcher;
}
var MIN_WORKER_VERSION = '2.0.0';
var Snapshot = function Snapshot(ref) {
var path = ref.path;
var value = ref.value;
var exists = ref.exists;
var writeSerial = ref.writeSerial;
this._path = path;
this._value = value;
this._exists = value === undefined ? exists || false : value !== null;
this._writeSerial = writeSerial;
};
var prototypeAccessors = { path: { configurable: true },exists: { configurable: true },value: { configurable: true },key: { configurable: true },writeSerial: { configurable: true } };
prototypeAccessors.path.get = function () {
return this._path;
};
prototypeAccessors.exists.get = function () {
return this._exists;
};
prototypeAccessors.value.get = function () {
if (this._value === undefined) { throw new Error('Value omitted from snapshot'); }
return this._value;
};
prototypeAccessors.key.get = function () {
if (this._key === undefined) { this._key = unescapeKey(this._path.replace(/.*\//, '')); }
return this._key;
};
prototypeAccessors.writeSerial.get = function () {
return this._writeSerial;
};
Object.defineProperties( Snapshot.prototype, prototypeAccessors );
var Bridge = function Bridge(webWorker) {
var this$1 = this;
this._idCounter = 0;
this._deferreds = {};
this._suspended = false;
this._servers = {};
this._callbacks = {};
this._log = _.noop;
this._inboundMessages = [];
this._outboundMessages = [];
this._flushMessageQueue = this._flushMessageQueue.bind(this);
this._port = webWorker.port || webWorker;
this._shared = !!webWorker.port;
Object.seal(this);
this._port.onmessage = this._receive.bind(this);
window.addEventListener('unload', function () {this$1._send({msg: 'destroy'});});
};
Bridge.prototype.init = function init (webWorker, config) {
var items = [];
try {
var storage = window.localStorage || window.sessionStorage;
if (!storage) { throw new Error('localStorage and sessionStorage not available'); }
for (var i = 0; i < storage.length; i++) {
var key = storage.key(i);
items.push({key: key, value: storage.getItem(key)});
}
} catch (e) {
// Some browsers don't like us accessing local storage -- nothing we can do.
}
return this._send({msg: 'init', storage: items, config: config}).then(function (response) {
var workerVersion = response.version.match(/^(\d+)\.(\d+)\.(\d+)(-.*)?$/);
if (workerVersion) {
var minVersion = MIN_WORKER_VERSION.match(/^(\d+)\.(\d+)\.(\d+)(-.*)?$/);
// Major version must match precisely, minor and patch must be greater than or equal.
var sufficient = workerVersion[1] === minVersion[1] && (
workerVersion[2] > minVersion[2] ||
workerVersion[2] === minVersion[2] && workerVersion[3] >= minVersion[3]
);
if (!sufficient) {
return Promise.reject(new Error(
"Incompatible Firetruss worker version: " + (response.version) + " " +
"(" + MIN_WORKER_VERSION + " or better required)"
));
}
}
return response;
});
};
Bridge.prototype.suspend = function suspend (suspended) {
if (suspended === undefined) { suspended = true; }
if (this._suspended === suspended) { return; }
this._suspended = suspended;
if (!suspended) {
this._receiveMessages(this._inboundMessages);
this._inboundMessages = [];
if (this._outboundMessages.length) { Promise.resolve().then(this._flushMessageQueue); }
}
};
Bridge.prototype.enableLogging = function enableLogging (fn) {
if (fn) {
if (fn === true) { fn = console.log.bind(console); }
this._log = fn;
} else {
this._log = _.noop;
}
};
Bridge.prototype._send = function _send (message) {
var this$1 = this;
message.id = ++this._idCounter;
var promise;
if (message.oneWay) {
promise = Promise.resolve();
} else {
promise = new Promise(function (resolve, reject) {
this$1._deferreds[message.id] = {resolve: resolve, reject: reject};
});
var deferred = this._deferreds[message.id];
deferred.promise = promise;
promise.sent = new Promise(function (resolve) {
deferred.resolveSent = resolve;
});
deferred.params = message;
}
if (!this._outboundMessages.length && !this._suspended) {
Promise.resolve().then(this._flushMessageQueue);
}
this._log('send:', message);
this._outboundMessages.push(message);
return promise;
};
Bridge.prototype._flushMessageQueue = function _flushMessageQueue () {
try {
this._port.postMessage(this._outboundMessages);
this._outboundMessages = [];
} catch (e) {
e.extra = {messages: this._outboundMessages};
throw e;
}
};
Bridge.prototype._receive = function _receive (event) {
if (this._suspended) {
this._inboundMessages = this._inboundMessages.concat(event.data);
} else {
this._receiveMessages(event.data);
}
};
Bridge.prototype._receiveMessages = function _receiveMessages (messages) {
for (var i = 0, list = messages; i < list.length; i += 1) {
var message = list[i];
this._log('recv:', message);
var fn = this[message.msg];
if (!_.isFunction(fn)) { throw new Error('Unknown message: ' + message.msg); }
fn.call(this, message);
}
};
Bridge.prototype.bindExposedFunction = function bindExposedFunction (name) {
return (function() {
return this._send({msg: 'call', name: name, args: Array.prototype.slice.call(arguments)});
}).bind(this);
};
Bridge.prototype.resolve = function resolve (message) {
var deferred = this._deferreds[message.id];
if (!deferred) { throw new Error('Received resolution to inexistent Firebase call'); }
delete this._deferreds[message.id];
deferred.resolve(message.result);
};
Bridge.prototype.reject = function reject (message) {
var deferred = this._deferreds[message.id];
if (!deferred) { throw new Error('Received rejection of inexistent Firebase call'); }
delete this._deferreds[message.id];
deferred.reject(errorFromJson(message.error, deferred.params));
};
Bridge.prototype.updateLocalStorage = function updateLocalStorage (ref) {
var items = ref.items;
try {
var storage = window.localStorage || window.sessionStorage;
for (var i = 0, list = items; i < list.length; i += 1) {
var item = list[i];
if (item.value === null) {
storage.removeItem(item.key);
} else {
storage.setItem(item.key, item.value);
}
}
} catch (e) {
// If we're denied access, there's nothing we can do.
}
};
Bridge.prototype.trackServer = function trackServer (rootUrl) {
if (this._servers.hasOwnProperty(rootUrl)) { return Promise.resolve(); }
var server = this._servers[rootUrl] = {authListeners: []};
var authCallbackId = this._registerCallback(this._authCallback.bind(this, server));
this._send({msg: 'onAuth', url: rootUrl, callbackId: authCallbackId});
};
Bridge.prototype._authCallback = function _authCallback (server, auth) {
server.auth = auth;
for (var i = 0, list = server.authListeners; i < list.length; i += 1) {
var listener = list[i];
listener(auth);
}
};
Bridge.prototype.onAuth = function onAuth (rootUrl, callback, context) {
var listener = callback.bind(context);
listener.callback = callback;
listener.context = context;
this._servers[rootUrl].authListeners.push(listener);
listener(this.getAuth(rootUrl));
};
Bridge.prototype.offAuth = function offAuth (rootUrl, callback, context) {
var authListeners = this._servers[rootUrl].authListeners;
for (var i = 0; i < authListeners.length; i++) {
var listener = authListeners[i];
if (listener.callback === callback && listener.context === context) {
authListeners.splice(i, 1);
break;
}
}
};
Bridge.prototype.getAuth = function getAuth (rootUrl) {
return this._servers[rootUrl].auth;
};
Bridge.prototype.authWithCustomToken = function authWithCustomToken (url, authToken) {
return this._send({msg: 'authWithCustomToken', url: url, authToken: authToken});
};
Bridge.prototype.unauth = function unauth (url) {
return this._send({msg: 'unauth', url: url});
};
Bridge.prototype.set = function set (url, value, writeSerial) {return this._send({msg: 'set', url: url, value: value, writeSerial: writeSerial});};
Bridge.prototype.update = function update (url, value, writeSerial) {return this._send({msg: 'update', url: url, value: value, writeSerial: writeSerial});};
Bridge.prototype.once = function once (url, writeSerial) {
return this._send({msg: 'once', url: url, writeSerial: writeSerial}).then(function (snapshot) { return new Snapshot(snapshot); });
};
Bridge.prototype.on = function on (listenerKey, url, spec, eventType, snapshotCallback, cancelCallback, context, options) {
var handle = {
listenerKey: listenerKey, eventType: eventType, snapshotCallback: snapshotCallback, cancelCallback: cancelCallback, context: context,
params: {msg: 'on', listenerKey: listenerKey, url: url, spec: spec, eventType: eventType, options: options}
};
var callback = this._onCallback.bind(this, handle);
this._registerCallback(callback, handle);
// Keep multiple IDs to allow the same snapshotCallback to be reused.
snapshotCallback.__callbackIds = snapshotCallback.__callbackIds || [];
snapshotCallback.__callbackIds.push(handle.id);
this._send({
msg: 'on', listenerKey: listenerKey, url: url, spec: spec, eventType: eventType, callbackId: handle.id, options: options
}).catch(function (error) {
callback(error);
});
};
Bridge.prototype.off = function off (listenerKey, url, spec, eventType, snapshotCallback, context) {
var this$1 = this;
var idsToDeregister = [];
var callbackId;
if (snapshotCallback) {
callbackId = this._findAndRemoveCallbackId(
snapshotCallback, function (handle) { return _.isMatch(handle, {listenerKey: listenerKey, eventType: eventType, context: context}); }
);
if (!callbackId) { return Promise.resolve(); }// no-op, never registered or already deregistered
idsToDeregister.push(callbackId);
} else {
for (var i = 0, list = _.keys(this._callbacks); i < list.length; i += 1) {
var id = list[i];
var handle = this._callbacks[id];
if (handle.listenerKey === listenerKey && (!eventType || handle.eventType === eventType)) {
idsToDeregister.push(id);
}
}
}
// Nullify callbacks first, then deregister after off() is complete.We don't want any
// callbacks in flight from the worker to be invoked while the off() is processing, but we don't
// want them to throw an exception either.
for (var i$1 = 0, list$1 = idsToDeregister; i$1 < list$1.length; i$1 += 1) {
var id$1 = list$1[i$1];
this._nullifyCallback(id$1);
}
return this._send({msg: 'off', listenerKey: listenerKey, url: url, spec: spec, eventType: eventType, callbackId: callbackId}).then(function () {
for (var i = 0, list = idsToDeregister; i < list.length; i += 1) {
var id = list[i];
this$1._deregisterCallback(id);
}
});
};
Bridge.prototype._onCallback = function _onCallback (handle, error, snapshotJson) {
if (error) {
this._deregisterCallback(handle.id);
var e = errorFromJson(error, handle.params);
if (handle.cancelCallback) {
handle.cancelCallback.call(handle.context, e);
} else {
console.error(e);
}
} else {
handle.snapshotCallback.call(handle.context, new Snapshot(snapshotJson));
}
};
Bridge.prototype.transaction = function transaction (url, oldValue, relativeUpdates, writeSerial) {
return this._send(
{msg: 'transaction', url: url, oldValue: oldValue, relativeUpdates: relativeUpdates, writeSerial: writeSerial}
).then(function (result) {
if (result.snapshots) {
result.snapshots = _.map(result.snapshots, function (jsonSnapshot) { return new Snapshot(jsonSnapshot); });
}
return result;
});
};
Bridge.prototype.onDisconnect = function onDisconnect (url, method, value) {
return this._send({msg: 'onDisconnect', url: url, method: method, value: value});
};
Bridge.prototype.bounceConnection = function bounceConnection () {
return this._send({msg: 'bounceConnection'});
};
Bridge.prototype.callback = function callback (ref) {
var id = ref.id;
var args = ref.args;
var handle = this._callbacks[id];
if (!handle) { throw new Error('Unregistered callback: ' + id); }
handle.callback.apply(null, args);
};
Bridge.prototype._registerCallback = function _registerCallback (callback, handle) {
handle = handle || {};
handle.callback = callback;
handle.id = "cb" + (++this._idCounter);
this._callbacks[handle.id] = handle;
return handle.id;
};
Bridge.prototype._nullifyCallback = function _nullifyCallback (id) {
this._callbacks[id].callback = _.noop;
};
Bridge.prototype._deregisterCallback = function _deregisterCallback (id) {
delete this._callbacks[id];
};
Bridge.prototype._findAndRemoveCallbackId = function _findAndRemoveCallbackId (callback, predicate) {
if (!callback.__callbackIds) { return; }
var i = 0;
while (i < callback.__callbackIds.length) {
var id = callback.__callbackIds[i];
var handle = this._callbacks[id];
if (!handle) {
callback.__callbackIds.splice(i, 1);
continue;
}
if (predicate(handle)) {
callback.__callbackIds.splice(i, 1);
return id;
}
i += 1;
}
};
function errorFromJson(json, params) {
if (!json || _.isError(json)) { return json; }
var error = new Error(json.message);
error.params = params;
for (var propertyName in json) {
if (propertyName === 'message' || !json.hasOwnProperty(propertyName)) { continue; }
try {
error[propertyName] = json[propertyName];
} catch (e) {
error.extra = error.extra || {};
error.extra[propertyName] = json[propertyName];
}
}
return error;
}
/* eslint-disable no-use-before-define */
var EMPTY_ANNOTATIONS = {};
Object.freeze(EMPTY_ANNOTATIONS);
var Handle = function Handle(tree, path, annotations) {
this._tree = tree;
this._path = path.replace(/^\/*/, '/').replace(/\/$/, '') || '/';
if (annotations) {
this._annotations = annotations;
Object.freeze(annotations);
}
};
var prototypeAccessors$1 = { $ref: { configurable: true },key: { configurable: true },path: { configurable: true },_pathPrefix: { configurable: true },parent: { configurable: true },annotations: { configurable: true } };
prototypeAccessors$1.$ref.get = function () {return this;};
prototypeAccessors$1.key.get = function () {
if (!this._key) { this._key = unescapeKey(this._path.replace(/.*\//, '')); }
return this._key;
};
prototypeAccessors$1.path.get = function () {return this._path;};
prototypeAccessors$1._pathPrefix.get = function () {return this._path === '/' ? '' : this._path;};
prototypeAccessors$1.parent.get = function () {
return new Reference(this._tree, this._path.replace(/\/[^/]*$/, ''), this._annotations);
};
prototypeAccessors$1.annotations.get = function () {
return this._annotations || EMPTY_ANNOTATIONS;
};
Handle.prototype.child = function child () {
if (!arguments.length) { return this; }
var segments = [];
for (var i = 0, list = arguments; i < list.length; i += 1) {
var key = list[i];
if (_.isNil(key)) { return; }
segments.push(escapeKey(key));
}
return new Reference(
this._tree, ((this._pathPrefix) + "/" + (segments.join('/'))),
this._annotations
);
};
Handle.prototype.children = function children () {
var arguments$1 = arguments;
if (!arguments.length) { return this; }
var escapedKeys = [];
for (var i = 0; i < arguments.length; i++) {
var arg = arguments$1[i];
if (_.isArray(arg)) {
var mapping = {};
var subPath = this._pathPrefix + (escapedKeys.length ? ("/" + (escapedKeys.join('/'))) : '');
var rest = _.slice(arguments$1, i + 1);
for (var i$1 = 0, list = arg; i$1 < list.length; i$1 += 1) {
var key = list[i$1];
var subRef =
new Reference(this._tree, (subPath + "/" + (escapeKey(key))), this._annotations);
var subMapping = subRef.children.apply(subRef, rest);
if (subMapping) { mapping[key] = subMapping; }
}
return mapping;
}
if (_.isNil(arg)) { return; }
escapedKeys.push(escapeKey(arg));
}
return new Reference(
this._tree, ((this._pathPrefix) + "/" + (escapedKeys.join('/'))), this._annotations);
};
Handle.prototype.peek = function peek (callback) {
return this._tree.truss.peek(this, callback);
};
Handle.prototype.match = function match (pattern) {
return makePathMatcher(pattern).match(this.path);
};
Handle.prototype.test = function test (pattern) {
return makePathMatcher(pattern).test(this.path);
};
Handle.prototype.isEqual = function isEqual (that) {
if (!(that instanceof Handle)) { return false; }
return this._tree === that._tree && this.toString() === that.toString() &&
_.isEqual(this._annotations, that._annotations);
};
Handle.prototype.belongsTo = function belongsTo (truss) {
return this._tree.truss === truss;
};
Object.defineProperties( Handle.prototype, prototypeAccessors$1 );
var Query = /*@__PURE__*/(function (Handle) {
function Query(tree, path, spec, annotations) {
Handle.call(this, tree, path, annotations);
this._spec = this._copyAndValidateSpec(spec);
var queryTerms = _(this._spec)
.map(function (value, key) { return (key + "=" + (encodeURIComponent(JSON.stringify(value)))); })
.sortBy()
.join('&');
this._string = (this._path) + "?" + queryTerms;
Object.freeze(this);
}
if ( Handle ) Query.__proto__ = Handle;
Query.prototype = Object.create( Handle && Handle.prototype );
Query.prototype.constructor = Query;
var prototypeAccessors$1 = { ready: { configurable: true },constraints: { configurable: true } };
// Vue-bound
prototypeAccessors$1.ready.get = function () {
return this._tree.isQueryReady(this);
};
prototypeAccessors$1.constraints.get = function () {
return this._spec;
};
Query.prototype.annotate = function annotate (annotations) {
return new Query(
this._tree, this._path, this._spec, _.assign({}, this._annotations, annotations));
};
Query.prototype._copyAndValidateSpec = function _copyAndValidateSpec (spec) {
if (!spec.by) { throw new Error('Query needs "by" clause: ' + JSON.stringify(spec)); }
if (('at' in spec) + ('from' in spec) + ('to' in spec) > 1) {
throw new Error(
'Query must contain at most one of "at", "from", or "to" clauses: ' + JSON.stringify(spec));
}
if (('first' in spec) + ('last' in spec) > 1) {
throw new Error(
'Query must contain at most one of "first" or "last" clauses: ' + JSON.stringify(spec));
}
if (!_.some(['at', 'from', 'to', 'first', 'last'], function (clause) { return clause in spec; })) {
throw new Error(
'Query must contain at least one of "at", "from", "to", "first", or "last" clauses: ' +
JSON.stringify(spec));
}
spec = _.clone(spec);
if (spec.by !== '$key' && spec.by !== '$value') {
if (!(spec.by instanceof Reference)) {
throw new Error('Query "by" value must be a reference: ' + spec.by);
}
var childPath = spec.by.toString();
if (!_.startsWith(childPath, this._path)) {
throw new Error(
'Query "by" value must be a descendant of target reference: ' + spec.by);
}
childPath = childPath.slice(this._path.length).replace(/^\/?/, '');
if (!_.includes(childPath, '/')) {
throw new Error(
'Query "by" value must not be a direct child of target reference: ' + spec.by);
}
spec.by = childPath.replace(/.*?\//, '');
}
Object.freeze(spec);
return spec;
};
Query.prototype.toString = function toString () {
return this._string;
};
Object.defineProperties( Query.prototype, prototypeAccessors$1 );
return Query;
}(Handle));
var Reference = /*@__PURE__*/(function (Handle) {
function Reference(tree, path, annotations) {
Handle.call(this, tree, path, annotations);
Object.freeze(this);
}
if ( Handle ) Reference.__proto__ = Handle;
Reference.prototype = Object.create( Handle && Handle.prototype );
Reference.prototype.constructor = Reference;
var prototypeAccessors$2 = { ready: { configurable: true },value: { configurable: true } };
prototypeAccessors$2.ready.get = function () {return this._tree.isReferenceReady(this);}; // Vue-bound
prototypeAccessors$2.value.get = function () {return this._tree.getObject(this.path);}; // Vue-bound
Reference.prototype.toString = function toString () {return this._path;};
Reference.prototype.annotate = function annotate (annotations) {
return new Reference(this._tree, this._path, _.assign({}, this._annotations, annotations));
};
Reference.prototype.query = function query (spec) {
return new Query(this._tree, this._path, spec, this._annotations);
};
Reference.prototype.set = function set (value) {
var obj;
this._checkForUndefinedPath();
return this._tree.update(this, 'set', ( obj = {}, obj[this.path] = value, obj ));
};
Reference.prototype.update = function update (values) {
this._checkForUndefinedPath();
return this._tree.update(this, 'update', values);
};
Reference.prototype.override = function override (value) {
var obj;
this._checkForUndefinedPath();
return this._tree.update(this, 'override', ( obj = {}, obj[this.path] = value, obj ));
};
Reference.prototype.commit = function commit (updateFunction) {
this._checkForUndefinedPath();
return this._tree.commit(this, updateFunction);
};
Reference.prototype._checkForUndefinedPath = function _checkForUndefinedPath () {
if (this.path === '/undefined') { throw new Error('Invalid path for operation: ' + this.path); }
};
Object.defineProperties( Reference.prototype, prototypeAccessors$2 );
return Reference;
}(Handle));
var SERVER_TIMESTAMP = Object.freeze({'.sv': 'timestamp'});
function isTrussEqual(a, b) {
return _.isEqualWith(a, b, isTrussValueEqual);
}
function isTrussValueEqual(a, b) {
if (a === b || a === undefined || a === null || b === undefined || b === null ||
a.$truss || b.$truss) { return a === b; }
if (a.isEqual) { return a.isEqual(b); }
}
function copyPrototype(a, b) {
for (var i = 0, list = Object.getOwnPropertyNames(a.prototype); i < list.length; i += 1) {
var prop = list[i];
if (prop === 'constructor') { continue; }
Object.defineProperty(b.prototype, prop, Object.getOwnPropertyDescriptor(a.prototype, prop));
}
}
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
function createCommonjsModule(fn, basedir, module) {
return module = {
path: basedir,
exports: {},
require: function (path, base) {
return commonjsRequire(path, (base === undefined || base === null) ? module.path : base);
}
}, fn(module, module.exports), module.exports;
}
function commonjsRequire () {
throw new Error('Dynamic requires are not currently supported by @rollup/plugin-commonjs');
}
var performanceNow = createCommonjsModule(function (module) {
// Generated by CoffeeScript 1.12.2
(function() {
var getNanoSeconds, hrtime, loadTime, moduleLoadTime, nodeLoadTime, upTime;
if ((typeof performance !== "undefined" && performance !== null) && performance.now) {
module.exports = function() {
return performance.now();
};
} else if ((typeof process !== "undefined" && process !== null) && process.hrtime) {
module.exports = function() {
return (getNanoSeconds() - nodeLoadTime) / 1e6;
};
hrtime = process.hrtime;
getNanoSeconds = function() {
var hr;
hr = hrtime();
return hr[0] * 1e9 + hr[1];
};
moduleLoadTime = getNanoSeconds();
upTime = process.uptime() * 1e9;
nodeLoadTime = moduleLoadTime - upTime;
} else if (Date.now) {
module.exports = function() {
return Date.now() - loadTime;
};
loadTime = Date.now();
} else {
module.exports = function() {
return new Date().getTime() - loadTime;
};
loadTime = new Date().getTime();
}
}).call(commonjsGlobal);
});
var StatItem = function StatItem(name) {
_.assign(this, {name: name, numRecomputes: 0, numUpdates: 0, runtime: 0});
};
var prototypeAccessors$2 = { runtimePerRecompute: { configurable: true } };
StatItem.prototype.add = function add (item) {
this.runtime += item.runtime;
this.numUpdates += item.numUpdates;
this.numRecomputes += item.numRecomputes;
};
prototypeAccessors$2.runtimePerRecompute.get = function () {
return this.numRecomputes ? this.runtime / this.numRecomputes : 0;
};
StatItem.prototype.toLogParts = function toLogParts (totals) {
return [
((this.name) + ":"), (" " + ((this.runtime / 1000).toFixed(2)) + "s"),
("(" + ((this.runtime / totals.runtime * 100).toFixed(1)) + "%)"),
(" " + (this.numUpdates) + " upd /"), ((this.numRecomputes) + " runs"),
("(" + ((this.numUpdates / this.numRecomputes * 100).toFixed(1)) + "%)"),
(" " + (this.runtimePerRecompute.toFixed(2)) + "ms / run")
];
};
Object.defineProperties( StatItem.prototype, prototypeAccessors$2 );
var Stats = function Stats() {
this._items = {};
};
var prototypeAccessors$1$1 = { list: { configurable: true } };
Stats.prototype.for = function for$1 (name) {
if (!this._items[name]) { this._items[name] = new StatItem(name); }
return this._items[name];
};
prototypeAccessors$1$1.list.get = function () {
return _(this._items).values().sortBy(function (item) { return -item.runtime; }).value();
};
Stats.prototype.log = function log (n) {
if ( n === void 0 ) n = 10;
var stats = this.list;
if (!stats.length) { return; }
var totals = new StatItem('=== Total');
_.forEach(stats, function (stat) {totals.add(stat);});
stats = _.take(stats, n);
var above = new StatItem('--- Above');
_.forEach(stats, function (stat) {above.add(stat);});
var lines = _.map(stats, function (item) { return item.toLogParts(totals); });
lines.push(above.toLogParts(totals));
lines.push(totals.toLogParts(totals));
var widths = _.map(_.range(lines[0].length), function (i) { return _(lines).map(function (line) { return line[i].length; }).max(); });
_.forEach(lines, function (line) {
console.log(_.map(line, function (column, i) { return _.padLeft(column, widths[i]); }).join(' '));
});
};
Stats.prototype.wrap = function wrap (getter, className, propName) {
var item = this.for((className + "." + propName));
return function() {
/* eslint-disable no-invalid-this */
var startTime = performanceNow();
var oldValue = this._computedWatchers && this._computedWatchers[propName].value;
try {
var newValue = getter.call(this);
if (!isTrussEqual(oldValue, newValue)) { item.numUpdates += 1; }
return newValue;
} finally {
item.runtime += performanceNow() - startTime;
item.numRecomputes += 1;
}
};
};
Object.defineProperties( Stats.prototype, prototypeAccessors$1$1 );
var stats = new Stats();
var Connector = function Connector(scope, connections, tree, method, refs) {
var this$1 = this;
Object.freeze(connections);
this._scope = scope;
this._connections = connections;
this._tree = tree;
this._method = method;
this._subConnectors = {};
this._disconnects = {};
this._angularUnwatches = undefined;
this._data = {};
this._vue = new Vue({data: {
descriptors: {},
refs: refs || {},
values: _.mapValues(connections, _.constant(undefined))
}});
// allow instance-level overrides of destroy() method
this.destroy = this.destroy;// eslint-disable-line no-self-assign
Object.seal(this);
this._linkScopeProperties();
_.forEach(connections, function (descriptor, key) {
if (_.isFunction(descriptor)) {
this$1._bindComputedConnection(key, descriptor);
} else {
this$1._connect(key, descriptor);
}
});
if (angularProxy.active && scope && scope.$on && scope.$id) {
scope.$on('$destroy', function () {this$1.destroy();});
}
};
var prototypeAccessors$3 = { ready: { configurable: true },at: { configurable: true },data: { configurable: true } };
prototypeAccessors$3.ready.get = function () {
var this$1 = this;
return _.every(this._connections, function (ignored, key) {
var descriptor = this$1._vue.descriptors[key];
if (!descriptor) { return false; }
if (descriptor instanceof Handle) { return descriptor.ready; }
return this$1._subConnectors[key].ready;
});
};
prototypeAccessors$3.at.get = function () {
return this._vue.refs;
};
prototypeAccessors$3.data.get = function () {
return this._data;
};
Connector.prototype.destroy = function destroy () {
var this$1 = this;
this._unlinkScopeProperties();
_.forEach(this._angularUnwatches, function (unwatch) {unwatch();});
_.forEach(this._connections, function (descriptor, key) {this$1._disconnect(key);});
this._vue.$destroy();
};
Connector.prototype._linkScopeProperties = function _linkScopeProperties () {
var this$1 = this;
var dataProperties = _.mapValues(this._connections, function (unused, key) { return ({
configurable: true, enumerable: false, get: function () {
var descriptor = this$1._vue.descriptors[key];
if (descriptor instanceof Reference) { return descriptor.value; }
return this$1._vue.values[key];
}
}); });
Object.defineProperties(this._data, dataProperties);
if (this._scope) {
for (var key in this._connections) {
if (key in this._scope) {
throw new Error(("Property already defined on connection target: " + key));
}
}
Object.defineProperties(this._scope, dataProperties);
if (this._scope.__ob__) { this._scope.__ob__.dep.notify(); }
}
};
Connector.prototype._unlinkScopeProperties = function _unlinkScopeProperties () {
var this$1 = this;
if (!this._scope) { return; }
_.forEach(this._connections, function (descriptor, key) {
delete this$1._scope[key];
});
};
Connector.prototype._bindComputedConnection = function _bindComputedConnection (key, fn) {
var connectionStats = stats.for(("connection.at." + key));
var getter = this._computeConnection.bind(this, fn, connectionStats);
var update = this._updateComputedConnection.bind(this, key, fn, connectionStats);
var angularWatch = angularProxy.active && !fn.angularWatchSuppressed;
// Use this._vue.$watch instead of truss.observe here so that we can disable the immediate
// callback if we'll get one from Angular anyway.
this._vue.$watch(getter, update, {immediate: !angularWatch});
if (angularWatch) {
if (!this._angularUnwatches) { this._angularUnwatches = []; }
this._angularUnwatches.push(angularProxy.watch(getter, update, true));
}
};
Connector.prototype._computeConnection = function _computeConnection (fn, connectionStats) {
var startTime = performanceNow();
try {
return flattenRefs(fn.call(this._scope));
} finally {
connectionStats.runtime += performanceNow() - startTime;
connectionStats.numRecomputes += 1;
}
};
Connector.prototype._updateComputedConnection = function _updateComputedConnection (key, value, connectionStats) {
var newDescriptor = _.isFunction(value) ? value(this._scope) : value;
var oldDescriptor = this._vue.descriptors[key];
var descriptorChanged = !isTrussEqual(oldDescriptor, newDescriptor);
if (!descriptorChanged) { return; }
if (connectionStats && descriptorChanged) { connectionStats.numUpdates += 1; }
if (!newDescriptor) {
this._disconnect(key);
return;
}
if (newDescriptor instanceof Handle || !_.has(this._subConnectors, key)) {
this._disconnect(key);
this._connect(key, newDescriptor);
} else {
this._subConnectors[key]._updateConnections(newDescriptor);
}
Vue.set(this._vue.descriptors, key, newDescriptor);
angularProxy.digest();
};
Connector.prototype._updateConnections = function _updateConnections (connections) {
var this$1 = this;
_.forEach(connections, function (descriptor, key) {
this$1._updateComputedConnection(key, descriptor);
});
_.forEach(this._connections, function (descriptor, key) {
if (!_.has(connections, key)) { this$1._updateComputedConnection(key); }
});
this._connections = connections;
};
Connector.prototype._connect = function _connect (key, descriptor) {
var this$1 = this;
Vue.set(this._vue.descriptors, key, descriptor);
angularProxy.digest();
if (!descriptor) { return; }
Vue.set(this._vue.values, key, undefined);
if (descriptor instanceof Reference) {
Vue.set(this._vue.refs, key, descriptor);
this._disconnects[key] = this._tree.connectReference(descriptor, this._method);
} else if (descriptor instanceof Query) {
Vue.set(this._vue.refs, key, descriptor);
var updateFn = this._updateQueryValue.bind(this, key);
this._disconnects[key] = this._tree.connectQuery(descriptor, updateFn, this._method);
} else {
var subScope = {}, subRefs = {};
Vue.set(this._vue.refs, key, subRefs);
var subConnector = this._subConnectors[key] =
new Connector(subScope, descriptor, this._tree, this._method, subRefs);
// Use a truss.observe here instead of this._vue.$watch so that the "immediate" execution
// actually takes place after we've captured the unwatch function, in case the subConnector
// is ready immediately.
var unobserve = this._disconnects[key] = this._tree.truss.observe(
function () { return subConnector.ready; },
function (subReady) {
if (!subReady) { return; }
unobserve();
delete this$1._disconnects[key];
Vue.set(this$1._vue.values, key, subScope);
angularProxy.digest();
}
);
}
};
Connector.prototype._disconnect = function _disconnect (key) {
Vue.delete(this._vue.refs, key);
this._updateRefValue(key, undefined);
if (_.has(this._subConnectors, key)) {
this._subConnectors[key].destroy();
delete this._subConnectors[key];
}
if (this._disconnects[key]) { this._disconnects[key](); }
delete this._disconnects[key];
Vue.delete(this._vue.descriptors, key);
angularProxy.digest();
};
Connector.prototype._updateRefValue = function _updateRefValue (key, value) {
if (this._vue.values[key] !== value) {
Vue.set(this._vue.values, key, value);
angularProxy.digest();
}
};
Connector.prototype._updateQueryValue = function _updateQueryValue (key, childKeys) {
if (!this._vue.values[key]) {
Vue.set(this._vue.values, key, {});
angularProxy.digest();
}
var subScope = this._vue.values[key];
for (var childKey in subScope) {
if (!subScope.hasOwnProperty(childKey)) { continue; }
if (!_.includes(childKeys, childKey)) {
Vue.delete(subScope, childKey);
angularProxy.digest();
}
}
var object = this._tree.getObject(this._vue.descriptors[key].path);
for (var i = 0, list = childKeys; i < list.length; i += 1) {
var childKey$1 = list[i];
if (subScope.hasOwnProperty(childKey$1)) { continue; }
Vue.set(subScope, childKey$1, object[childKey$1]);
angularProxy.digest();
}
};
Object.defineProperties( Connector.prototype, prototypeAccessors$3 );
function flattenRefs(refs) {
if (!refs) { return; }
if (refs instanceof Handle) { return refs.toString(); }
return _.mapValues(refs, flattenRefs);
}
function wrapPromiseCallback(callback) {
return function() {
try {
// eslint-disable-next-line no-invalid-this
return Promise.resolve(callback.apply(this, arguments));
} catch (e) {
return Promise.reject(e);
}
};
}
function promiseCancel(promise, cancel) {
promise = promiseFinally(promise, function () {cancel = null;});
promise.cancel = function () {
if (!cancel) { return; }
cancel();
cancel = null;
};
propagatePromiseProperty(promise, 'cancel');
return promise;
}
function propagatePromiseProperty(promise, propertyName) {
var originalThen = promise.then, originalCatch = promise.catch;
promise.then = function (onResolved, onRejected) {
var derivedPromise = originalThen.call(promise, onResolved, onRejected);
derivedPromise[propertyName] = promise[propertyName];
propagatePromiseProperty(derivedPromise, propertyName);
return derivedPromise;
};
promise.catch = function (onRejected) {
var derivedPromise = originalCatch.call(promise, onRejected);
derivedPromise[propertyName] = promise[propertyName];
propagatePromiseProperty(derivedPromise, propertyName);
return derivedPromise;
};
return promise;
}
function promiseFinally(promise, onFinally) {
if (!onFinally) { return promise; }
onFinally = wrapPromiseCallback(onFinally);
return promise.then(function (result) {
return onFinally().then(function () { return result; });
}, function (error) {
return onFinally().then(function () { return Promise.reject(error); });
});
}
var INTERCEPT_KEYS = [
'read', 'write', 'auth', 'set', 'update', 'commit', 'connect', 'peek', 'authenticate',
'unathenticate', 'certify', 'all'
];
var EMPTY_ARRAY = [];
var SlowHandle = function SlowHandle(operation, delay, callback) {
this._operation = operation;
this._delay = delay;
this._callback = callback;
this._fired = false;
};
SlowHandle.prototype.initiate = function initiate () {
var this$1 = this;
this.cancel();
this._fired = false;
var elapsed = Date.now() - this._operation._startTimestamp;
this._timeoutId = setTimeout(function () {
this$1._fired = true;
this$1._callback(this$1._operation);
}, this._delay - elapsed);
};
SlowHandle.prototype.cancel = function cancel () {
if (this._fired) { this._callback(this._operation); }
if (this._timeoutId) { clearTimeout(this._timeoutId); }
};
var Operation = function Operation(type, method, target, operand) {
this._type = type;
this._method = method;
this._target = target;
this._operand = operand;
this._ready = false;
this._running = false;
this._ended = false;
this._tries = 0;
this._startTimestamp = Date.now();
this._slowHandles = [];
};
var prototypeAccessors$4 = { type: { configurable: true },method: { configurable: true },target: { configurable: true },targets: { configurable: true },operand: { configurable: true },ready: { configurable: true },running: { configurable: true },ended: { configurable: true },tries: { configurable: true },error: { configurable: true } };
prototypeAccessors$4.type.get = function () {return this._type;};