UNPKG

firetruss

Version:

Advanced data sync layer for Firebase and Vue.js

1,413 lines (1,204 loc) 149 kB
(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;};