UNPKG

142 kBJavaScriptView Raw
1import _ from 'lodash';
2import Vue from 'vue';
3import performanceNow from 'performance-now';
4
5var vue;
6var lastDigestRequest = 0, digestInProgress = false;
7var bareDigest = function() {
8 if (vue.digestRequest > lastDigestRequest) { return; }
9 vue.digestRequest = lastDigestRequest + 1;
10};
11
12var angularProxy = {
13 active: typeof window !== 'undefined' && window.angular
14};
15
16if (angularProxy.active) {
17 initAngular();
18} else {
19 _.forEach(['digest', 'watch', 'defineModule', 'debounceDigest'], function (method) {
20 angularProxy[method] = _.noop;
21 });
22}
23
24function initAngular() {
25 var module = window.angular.module('firetruss', []);
26 angularProxy.digest = bareDigest;
27 angularProxy.watch = function() {throw new Error('Angular watch proxy not yet initialized');};
28 angularProxy.defineModule = function(Truss) {
29 module.constant('Truss', Truss);
30 };
31 angularProxy.debounceDigest = function(wait) {
32 if (wait) {
33 var debouncedDigest = _.debounce(bareDigest, wait);
34 angularProxy.digest = function() {
35 if (vue.digestRequest > lastDigestRequest) { return; }
36 if (digestInProgress) { bareDigest(); } else { debouncedDigest(); }
37 };
38 } else {
39 angularProxy.digest = bareDigest;
40 }
41 };
42
43 module.config(['$provide', function($provide) {
44 $provide.decorator('$rootScope', ['$delegate', '$exceptionHandler',
45 function($delegate, $exceptionHandler) {
46 var rootScope = $delegate;
47 angularProxy.watch = rootScope.$watch.bind(rootScope);
48 var proto = Object.getPrototypeOf(rootScope);
49 var angularDigest = proto.$digest;
50 proto.$digest = bareDigest;
51 proto.$digest.original = angularDigest;
52 vue = new Vue({data: {digestRequest: 0}});
53 vue.$watch(function () { return vue.digestRequest; }, function () {
54 if (vue.digestRequest > lastDigestRequest) {
55 // Make sure we execute the digest outside the Vue task queue, because otherwise if the
56 // client replaced Promise with angular.$q all Truss.nextTick().then() functions will be
57 // executed inside the Angular digest and hence inside the Vue task queue. But
58 // Truss.nextTick() is used precisely to avoid that. Note that it's OK to use
59 // Vue.nextTick() here because even though it will schedule a flush via Promise.then()
60 // it only uses the native Promise, before it could've been monkey-patched by the app.
61 Vue.nextTick(function () {
62 if (vue.digestRequest <= lastDigestRequest) { return; }
63 digestInProgress = true;
64 rootScope.$digest.original.call(rootScope);
65 lastDigestRequest = vue.digestRequest = vue.digestRequest + 1;
66 });
67 } else {
68 digestInProgress = false;
69 }
70 });
71 var watcher = _.last(vue._watchers || vue._scope.effects);
72 watcher.id = Infinity; // make sure watcher is scheduled last
73 patchRenderWatcherGet(Object.getPrototypeOf(watcher));
74 return rootScope;
75 }
76 ]);
77 }]);
78}
79
80// This is a kludge that catches errors that get through render watchers and end up killing the
81// entire Vue event loop (e.g., errors raised in transition callbacks). The state of the DOM may
82// not be consistent after such an error is caught, but the global error handler should stop the
83// world anyway. May be related to https://github.com/vuejs/vue/issues/7653.
84function patchRenderWatcherGet(prototype) {
85 var originalGet = prototype.get;
86 prototype.get = function get() {
87 try {
88 return originalGet.call(this);
89 } catch (e) {
90 if (this.vm._watcher === this && Vue.config.errorHandler) {
91 Vue.config.errorHandler(e, this.vm, 'uncaught render error');
92 } else {
93 throw e;
94 }
95 }
96 };
97}
98
99var LruCacheItem = function LruCacheItem(key, value) {
100 this.key = key;
101 this.value = value;
102 this.touch();
103};
104
105LruCacheItem.prototype.touch = function touch () {
106 this.timestamp = Date.now();
107};
108
109
110var LruCache = function LruCache(maxSize, pruningSize) {
111 this._items = Object.create(null);
112 this._size = 0;
113 this._maxSize = maxSize;
114 this._pruningSize = pruningSize || Math.ceil(maxSize * 0.10);
115};
116
117LruCache.prototype.has = function has (key) {
118 return Boolean(this._items[key]);
119};
120
121LruCache.prototype.get = function get (key) {
122 var item = this._items[key];
123 if (!item) { return; }
124 item.touch();
125 return item.value;
126};
127
128LruCache.prototype.set = function set (key, value) {
129 var item = this._items[key];
130 if (item) {
131 item.value = value;
132 } else {
133 if (this._size >= this._maxSize) { this._prune(); }
134 this._items[key] = new LruCacheItem(key, value);
135 this._size += 1;
136 }
137};
138
139LruCache.prototype.delete = function delete$1 (key) {
140 var item = this._items[key];
141 if (!item) { return; }
142 delete this._items[key];
143 this._size -= 1;
144};
145
146LruCache.prototype._prune = function _prune () {
147 var itemsToPrune =
148 _(this._items).toArray().sortBy('timestamp').take(this._pruningSize).value();
149 for (var i = 0, list = itemsToPrune; i < list.length; i += 1) {
150 var item = list[i];
151
152 this.delete(item.key);
153 }
154};
155
156var pathSegments = new LruCache(1000);
157var pathMatchers = {};
158var maxNumPathMatchers = 1000;
159
160
161function escapeKey(key) {
162 if (!key) { return key; }
163 // eslint-disable-next-line no-control-regex
164 return key.toString().replace(/[\x00-\x1f\\.$#[\]\x7f/]/g, function(char) {
165 return '\\' + _.padStart(char.charCodeAt(0).toString(16), 2, '0');
166 });
167}
168
169function unescapeKey(key) {
170 if (!key) { return key; }
171 return key.toString().replace(/\\[0-9a-f]{2}/gi, function(code) {
172 return String.fromCharCode(parseInt(code.slice(1), 16));
173 });
174}
175
176function escapeKeys(object) {
177 // isExtensible check avoids trying to escape references to Firetruss internals.
178 if (!(_.isObject(object) && Object.isExtensible(object))) { return object; }
179 var result = object;
180 for (var key in object) {
181 if (!object.hasOwnProperty(key)) { continue; }
182 var value = object[key];
183 var escapedKey = escapeKey(key);
184 var escapedValue = escapeKeys(value);
185 if (escapedKey !== key || escapedValue !== value) {
186 if (result === object) { result = _.clone(object); }
187 result[escapedKey] = escapedValue;
188 if (result[key] === value) { delete result[key]; }
189 }
190 }
191 return result;
192}
193
194function joinPath() {
195 var segments = [];
196 for (var i = 0, list = arguments; i < list.length; i += 1) {
197 var segment = list[i];
198
199 if (!_.isString(segment)) { segment = '' + segment; }
200 if (segment.charAt(0) === '/') { segments.splice(0, segments.length); }
201 segments.push(segment);
202 }
203 if (segments[0] === '/') { segments[0] = ''; }
204 return segments.join('/');
205}
206
207function splitPath(path, leaveSegmentsEscaped) {
208 var key = (leaveSegmentsEscaped ? 'esc:' : '') + path;
209 var segments = pathSegments.get(key);
210 if (!segments) {
211 segments = path.split('/');
212 if (!leaveSegmentsEscaped) { segments = _.map(segments, unescapeKey); }
213 pathSegments.set(key, segments);
214 }
215 return segments;
216}
217
218
219var PathMatcher = function PathMatcher(pattern) {
220 var this$1 = this;
221
222 this.variables = [];
223 var prefixMatch = _.endsWith(pattern, '/$*');
224 if (prefixMatch) { pattern = pattern.slice(0, -3); }
225 var pathTemplate = pattern.replace(/\/\$[^/]*/g, function (match) {
226 if (match.length > 1) { this$1.variables.push(match.slice(1)); }
227 return '\u0001';
228 });
229 Object.freeze(this.variables);
230 if (/[.$#[\]]|\\(?![0-9a-f][0-9a-f])/i.test(pathTemplate)) {
231 throw new Error('Path pattern has unescaped keys: ' + pattern);
232 }
233 this._regex = new RegExp(
234 // eslint-disable-next-line no-control-regex
235 '^' + pathTemplate.replace(/\u0001/g, '/([^/]+)') + (prefixMatch ? '($|/)' : '$'));
236};
237
238PathMatcher.prototype.match = function match (path) {
239 this._regex.lastIndex = 0;
240 var match = this._regex.exec(path);
241 if (!match) { return; }
242 var bindings = {};
243 for (var i = 0; i < this.variables.length; i++) {
244 bindings[this.variables[i]] = unescapeKey(match[i + 1]);
245 }
246 return bindings;
247};
248
249PathMatcher.prototype.test = function test (path) {
250 return this._regex.test(path);
251};
252
253PathMatcher.prototype.toString = function toString () {
254 return this._regex.toString();
255};
256
257function makePathMatcher(pattern) {
258 var matcher = pathMatchers[pattern];
259 if (!matcher) {
260 matcher = new PathMatcher(pattern);
261 // Minimal pseudo-LRU behavior, since we don't expect to actually fill up the cache.
262 if (_.size(pathMatchers) === maxNumPathMatchers) { delete pathMatchers[_.keys(pathMatchers)[0]]; }
263 pathMatchers[pattern] = matcher;
264 }
265 return matcher;
266}
267
268var MIN_WORKER_VERSION = '2.3.0';
269
270
271var Snapshot = function Snapshot(ref) {
272 var path = ref.path;
273 var value = ref.value;
274 var exists = ref.exists;
275 var writeSerial = ref.writeSerial;
276
277 this._path = path;
278 this._value = value;
279 this._exists = value === undefined ? exists || false : value !== null;
280 this._writeSerial = writeSerial;
281};
282
283var prototypeAccessors = { path: { configurable: true },exists: { configurable: true },value: { configurable: true },key: { configurable: true },writeSerial: { configurable: true } };
284
285prototypeAccessors.path.get = function () {
286 return this._path;
287};
288
289prototypeAccessors.exists.get = function () {
290 return this._exists;
291};
292
293prototypeAccessors.value.get = function () {
294 if (this._value === undefined) { throw new Error('Value omitted from snapshot'); }
295 return this._value;
296};
297
298prototypeAccessors.key.get = function () {
299 if (this._key === undefined) { this._key = unescapeKey(this._path.replace(/.*\//, '')); }
300 return this._key;
301};
302
303prototypeAccessors.writeSerial.get = function () {
304 return this._writeSerial;
305};
306
307Object.defineProperties( Snapshot.prototype, prototypeAccessors );
308
309
310var Bridge = function Bridge(webWorker) {
311 var this$1 = this;
312
313 this._idCounter = 0;
314 this._deferreds = {};
315 this._suspended = false;
316 this._servers = {};
317 this._callbacks = {};
318 this._log = _.noop;
319 this._inboundMessages = [];
320 this._outboundMessages = [];
321 this._flushMessageQueue = this._flushMessageQueue.bind(this);
322 this._port = webWorker.port || webWorker;
323 this._shared = !!webWorker.port;
324 Object.seal(this);
325 this._port.onmessage = this._receive.bind(this);
326 window.addEventListener('unload', function () {this$1._send({msg: 'destroy'});});
327};
328
329Bridge.prototype.init = function init (webWorker, config) {
330 var items = [];
331 try {
332 var storage = window.localStorage || window.sessionStorage;
333 if (!storage) { throw new Error('localStorage and sessionStorage not available'); }
334 for (var i = 0; i < storage.length; i++) {
335 var key = storage.key(i);
336 items.push({key: key, value: storage.getItem(key)});
337 }
338 } catch (e) {
339 // Some browsers don't like us accessing local storage -- nothing we can do.
340 }
341 return this._send({msg: 'init', storage: items, config: config}).then(function (response) {
342 var workerVersion = response.version.match(/^(\d+)\.(\d+)\.(\d+)(-.*)?$/);
343 if (workerVersion) {
344 var minVersion = MIN_WORKER_VERSION.match(/^(\d+)\.(\d+)\.(\d+)(-.*)?$/);
345 // Major version must match precisely, minor and patch must be greater than or equal.
346 var sufficient = workerVersion[1] === minVersion[1] && (
347 workerVersion[2] > minVersion[2] ||
348 workerVersion[2] === minVersion[2] && workerVersion[3] >= minVersion[3]
349 );
350 if (!sufficient) {
351 return Promise.reject(new Error(
352 "Incompatible Firetruss worker version: " + (response.version) + " " +
353 "(" + MIN_WORKER_VERSION + " or better required)"
354 ));
355 }
356 }
357 return response;
358 });
359};
360
361Bridge.prototype.suspend = function suspend (suspended) {
362 if (suspended === undefined) { suspended = true; }
363 if (this._suspended === suspended) { return; }
364 this._suspended = suspended;
365 if (!suspended) {
366 this._receiveMessages(this._inboundMessages);
367 this._inboundMessages = [];
368 if (this._outboundMessages.length) { Promise.resolve().then(this._flushMessageQueue); }
369 }
370};
371
372Bridge.prototype.enableLogging = function enableLogging (fn) {
373 if (fn) {
374 if (fn === true) {
375 fn = console.log.bind(console);
376 this._send({msg: 'enableFirebaseLogging', value: true});
377 }
378 this._log = fn;
379 } else {
380 this._send({msg: 'enableFirebaseLogging', value: false});
381 this._log = _.noop;
382 }
383};
384
385Bridge.prototype._send = function _send (message) {
386 var this$1 = this;
387
388 message.id = ++this._idCounter;
389 var promise;
390 if (message.oneWay) {
391 promise = Promise.resolve();
392 } else {
393 promise = new Promise(function (resolve, reject) {
394 this$1._deferreds[message.id] = {resolve: resolve, reject: reject};
395 });
396 var deferred = this._deferreds[message.id];
397 deferred.promise = promise;
398 promise.sent = new Promise(function (resolve) {
399 deferred.resolveSent = resolve;
400 });
401 deferred.params = message;
402 }
403 if (!this._outboundMessages.length && !this._suspended) {
404 Promise.resolve().then(this._flushMessageQueue);
405 }
406 this._log('send:', message);
407 this._outboundMessages.push(message);
408 return promise;
409};
410
411Bridge.prototype._flushMessageQueue = function _flushMessageQueue () {
412 try {
413 this._port.postMessage(this._outboundMessages);
414 this._outboundMessages = [];
415 } catch (e) {
416 e.extra = {messages: this._outboundMessages};
417 throw e;
418 }
419};
420
421Bridge.prototype._receive = function _receive (event) {
422 if (this._suspended) {
423 this._inboundMessages = this._inboundMessages.concat(event.data);
424 } else {
425 this._receiveMessages(event.data);
426 }
427};
428
429Bridge.prototype._receiveMessages = function _receiveMessages (messages) {
430 for (var i = 0, list = messages; i < list.length; i += 1) {
431 var message = list[i];
432
433 this._log('recv:', message);
434 var fn = this[message.msg];
435 if (!_.isFunction(fn)) { throw new Error('Unknown message: ' + message.msg); }
436 fn.call(this, message);
437 }
438};
439
440Bridge.prototype.bindExposedFunction = function bindExposedFunction (name) {
441 return (function() {
442 return this._send({msg: 'call', name: name, args: Array.prototype.slice.call(arguments)});
443 }).bind(this);
444};
445
446Bridge.prototype.resolve = function resolve (message) {
447 var deferred = this._deferreds[message.id];
448 if (!deferred) { throw new Error('Received resolution to inexistent Firebase call'); }
449 delete this._deferreds[message.id];
450 deferred.resolve(message.result);
451};
452
453Bridge.prototype.reject = function reject (message) {
454 var deferred = this._deferreds[message.id];
455 if (!deferred) { throw new Error('Received rejection of inexistent Firebase call'); }
456 delete this._deferreds[message.id];
457 deferred.reject(errorFromJson(message.error, deferred.params));
458};
459
460Bridge.prototype.updateLocalStorage = function updateLocalStorage (ref) {
461 var items = ref.items;
462
463 try {
464 var storage = window.localStorage || window.sessionStorage;
465 for (var i = 0, list = items; i < list.length; i += 1) {
466 var item = list[i];
467
468 if (item.value === null) {
469 storage.removeItem(item.key);
470 } else {
471 storage.setItem(item.key, item.value);
472 }
473 }
474 } catch (e) {
475 // If we're denied access, there's nothing we can do.
476 }
477};
478
479Bridge.prototype.trackServer = function trackServer (rootUrl) {
480 if (this._servers.hasOwnProperty(rootUrl)) { return Promise.resolve(); }
481 var server = this._servers[rootUrl] = {authListeners: []};
482 var authCallbackId = this._registerCallback(this._authCallback.bind(this, server));
483 this._send({msg: 'onAuth', url: rootUrl, callbackId: authCallbackId});
484};
485
486Bridge.prototype._authCallback = function _authCallback (server, auth) {
487 server.auth = auth;
488 for (var i = 0, list = server.authListeners; i < list.length; i += 1) {
489 var listener = list[i];
490
491 listener(auth);
492 }
493};
494
495Bridge.prototype.onAuth = function onAuth (rootUrl, callback, context) {
496 var listener = callback.bind(context);
497 listener.callback = callback;
498 listener.context = context;
499 this._servers[rootUrl].authListeners.push(listener);
500 listener(this.getAuth(rootUrl));
501};
502
503Bridge.prototype.offAuth = function offAuth (rootUrl, callback, context) {
504 var authListeners = this._servers[rootUrl].authListeners;
505 for (var i = 0; i < authListeners.length; i++) {
506 var listener = authListeners[i];
507 if (listener.callback === callback && listener.context === context) {
508 authListeners.splice(i, 1);
509 break;
510 }
511 }
512};
513
514Bridge.prototype.getAuth = function getAuth (rootUrl) {
515 return this._servers[rootUrl].auth;
516};
517
518Bridge.prototype.authWithCustomToken = function authWithCustomToken (url, authToken) {
519 return this._send({msg: 'authWithCustomToken', url: url, authToken: authToken});
520};
521
522Bridge.prototype.authAnonymously = function authAnonymously (url) {
523 return this._send({msg: 'authAnonymously', url: url});
524};
525
526Bridge.prototype.unauth = function unauth (url) {
527 return this._send({msg: 'unauth', url: url});
528};
529
530Bridge.prototype.set = function set (url, value, writeSerial) {return this._send({msg: 'set', url: url, value: value, writeSerial: writeSerial});};
531Bridge.prototype.update = function update (url, value, writeSerial) {return this._send({msg: 'update', url: url, value: value, writeSerial: writeSerial});};
532
533Bridge.prototype.once = function once (url, writeSerial) {
534 return this._send({msg: 'once', url: url, writeSerial: writeSerial}).then(function (snapshot) { return new Snapshot(snapshot); });
535};
536
537Bridge.prototype.on = function on (listenerKey, url, spec, eventType, snapshotCallback, cancelCallback, context, options) {
538 var handle = {
539 listenerKey: listenerKey, eventType: eventType, snapshotCallback: snapshotCallback, cancelCallback: cancelCallback, context: context,
540 params: {msg: 'on', listenerKey: listenerKey, url: url, spec: spec, eventType: eventType, options: options}
541 };
542 var callback = this._onCallback.bind(this, handle);
543 this._registerCallback(callback, handle);
544 // Keep multiple IDs to allow the same snapshotCallback to be reused.
545 snapshotCallback.__callbackIds = snapshotCallback.__callbackIds || [];
546 snapshotCallback.__callbackIds.push(handle.id);
547 this._send({
548 msg: 'on', listenerKey: listenerKey, url: url, spec: spec, eventType: eventType, callbackId: handle.id, options: options
549 }).catch(function (error) {
550 callback(error);
551 });
552};
553
554Bridge.prototype.off = function off (listenerKey, url, spec, eventType, snapshotCallback, context) {
555 var this$1 = this;
556
557 var idsToDeregister = [];
558 var callbackId;
559 if (snapshotCallback) {
560 callbackId = this._findAndRemoveCallbackId(
561 snapshotCallback, function (handle) { return _.isMatch(handle, {listenerKey: listenerKey, eventType: eventType, context: context}); }
562 );
563 if (!callbackId) { return Promise.resolve(); }// no-op, never registered or already deregistered
564 idsToDeregister.push(callbackId);
565 } else {
566 for (var i = 0, list = _.keys(this._callbacks); i < list.length; i += 1) {
567 var id = list[i];
568
569 var handle = this._callbacks[id];
570 if (handle.listenerKey === listenerKey && (!eventType || handle.eventType === eventType)) {
571 idsToDeregister.push(id);
572 }
573 }
574 }
575 // Nullify callbacks first, then deregister after off() is complete.We don't want any
576 // callbacks in flight from the worker to be invoked while the off() is processing, but we don't
577 // want them to throw an exception either.
578 for (var i$1 = 0, list$1 = idsToDeregister; i$1 < list$1.length; i$1 += 1) {
579 var id$1 = list$1[i$1];
580
581 this._nullifyCallback(id$1);
582 }
583 return this._send({msg: 'off', listenerKey: listenerKey, url: url, spec: spec, eventType: eventType, callbackId: callbackId}).then(function () {
584 for (var i = 0, list = idsToDeregister; i < list.length; i += 1) {
585 var id = list[i];
586
587 this$1._deregisterCallback(id);
588 }
589 });
590};
591
592Bridge.prototype._onCallback = function _onCallback (handle, error, snapshotJson) {
593 if (error) {
594 this._deregisterCallback(handle.id);
595 var e = errorFromJson(error, handle.params);
596 if (handle.cancelCallback) {
597 handle.cancelCallback.call(handle.context, e);
598 } else {
599 console.error(e);
600 }
601 } else {
602 handle.snapshotCallback.call(handle.context, new Snapshot(snapshotJson));
603 }
604};
605
606Bridge.prototype.transaction = function transaction (url, oldValue, relativeUpdates, writeSerial) {
607 return this._send(
608 {msg: 'transaction', url: url, oldValue: oldValue, relativeUpdates: relativeUpdates, writeSerial: writeSerial}
609 ).then(function (result) {
610 if (result.snapshots) {
611 result.snapshots = _.map(result.snapshots, function (jsonSnapshot) { return new Snapshot(jsonSnapshot); });
612 }
613 return result;
614 });
615};
616
617Bridge.prototype.onDisconnect = function onDisconnect (url, method, value) {
618 return this._send({msg: 'onDisconnect', url: url, method: method, value: value});
619};
620
621Bridge.prototype.bounceConnection = function bounceConnection () {
622 return this._send({msg: 'bounceConnection'});
623};
624
625Bridge.prototype.callback = function callback (ref) {
626 var id = ref.id;
627 var args = ref.args;
628
629 var handle = this._callbacks[id];
630 if (!handle) { throw new Error('Unregistered callback: ' + id); }
631 handle.callback.apply(null, args);
632};
633
634Bridge.prototype._registerCallback = function _registerCallback (callback, handle) {
635 handle = handle || {};
636 handle.callback = callback;
637 handle.id = "cb" + (++this._idCounter);
638 this._callbacks[handle.id] = handle;
639 return handle.id;
640};
641
642Bridge.prototype._nullifyCallback = function _nullifyCallback (id) {
643 this._callbacks[id].callback = _.noop;
644};
645
646Bridge.prototype._deregisterCallback = function _deregisterCallback (id) {
647 delete this._callbacks[id];
648};
649
650Bridge.prototype._findAndRemoveCallbackId = function _findAndRemoveCallbackId (callback, predicate) {
651 if (!callback.__callbackIds) { return; }
652 var i = 0;
653 while (i < callback.__callbackIds.length) {
654 var id = callback.__callbackIds[i];
655 var handle = this._callbacks[id];
656 if (!handle) {
657 callback.__callbackIds.splice(i, 1);
658 continue;
659 }
660 if (predicate(handle)) {
661 callback.__callbackIds.splice(i, 1);
662 return id;
663 }
664 i += 1;
665 }
666};
667
668
669function errorFromJson(json, params) {
670 if (!json || _.isError(json)) { return json; }
671 var error = new Error(json.message);
672 error.params = params;
673 for (var propertyName in json) {
674 if (propertyName === 'message' || !json.hasOwnProperty(propertyName)) { continue; }
675 try {
676 error[propertyName] = json[propertyName];
677 } catch (e) {
678 error.extra = error.extra || {};
679 error.extra[propertyName] = json[propertyName];
680 }
681 }
682 return error;
683}
684
685/* eslint-disable no-use-before-define */
686
687var EMPTY_ANNOTATIONS = {};
688Object.freeze(EMPTY_ANNOTATIONS);
689
690
691var Handle = function Handle(tree, path, annotations) {
692 this._tree = tree;
693 this._path = path.replace(/^\/*/, '/').replace(/\/$/, '') || '/';
694 if (annotations) {
695 this._annotations = annotations;
696 Object.freeze(annotations);
697 }
698};
699
700var prototypeAccessors$1 = { $ref: { configurable: true },key: { configurable: true },path: { configurable: true },_pathPrefix: { configurable: true },parent: { configurable: true },annotations: { configurable: true } };
701
702prototypeAccessors$1.$ref.get = function () {return this;};
703prototypeAccessors$1.key.get = function () {
704 if (!this._key) { this._key = unescapeKey(this._path.replace(/.*\//, '')); }
705 return this._key;
706};
707prototypeAccessors$1.path.get = function () {return this._path;};
708prototypeAccessors$1._pathPrefix.get = function () {return this._path === '/' ? '' : this._path;};
709prototypeAccessors$1.parent.get = function () {
710 return new Reference(this._tree, this._path.replace(/\/[^/]*$/, ''), this._annotations);
711};
712
713prototypeAccessors$1.annotations.get = function () {
714 return this._annotations || EMPTY_ANNOTATIONS;
715};
716
717Handle.prototype.child = function child () {
718 if (!arguments.length) { return this; }
719 var segments = [];
720 for (var i = 0, list = arguments; i < list.length; i += 1) {
721 var key = list[i];
722
723 if (_.isNil(key)) { return; }
724 segments.push(escapeKey(key));
725 }
726 return new Reference(
727 this._tree, ((this._pathPrefix) + "/" + (segments.join('/'))),
728 this._annotations
729 );
730};
731
732Handle.prototype.children = function children () {
733 var arguments$1 = arguments;
734
735 if (!arguments.length) { return this; }
736 var escapedKeys = [];
737 for (var i = 0; i < arguments.length; i++) {
738 var arg = arguments$1[i];
739 if (_.isArray(arg)) {
740 var mapping = {};
741 var subPath = this._pathPrefix + (escapedKeys.length ? ("/" + (escapedKeys.join('/'))) : '');
742 var rest = _.slice(arguments$1, i + 1);
743 for (var i$1 = 0, list = arg; i$1 < list.length; i$1 += 1) {
744 var key = list[i$1];
745
746 var subRef =
747 new Reference(this._tree, (subPath + "/" + (escapeKey(key))), this._annotations);
748 var subMapping = subRef.children.apply(subRef, rest);
749 if (subMapping) { mapping[key] = subMapping; }
750 }
751 return mapping;
752 }
753 if (_.isNil(arg)) { return; }
754 escapedKeys.push(escapeKey(arg));
755 }
756 return new Reference(
757 this._tree, ((this._pathPrefix) + "/" + (escapedKeys.join('/'))), this._annotations);
758};
759
760Handle.prototype.peek = function peek (callback) {
761 return this._tree.truss.peek(this, callback);
762};
763
764Handle.prototype.match = function match (pattern) {
765 return makePathMatcher(pattern).match(this.path);
766};
767
768Handle.prototype.test = function test (pattern) {
769 return makePathMatcher(pattern).test(this.path);
770};
771
772Handle.prototype.isEqual = function isEqual (that) {
773 if (!(that instanceof Handle)) { return false; }
774 return this._tree === that._tree && this.toString() === that.toString() &&
775 _.isEqual(this._annotations, that._annotations);
776};
777
778Handle.prototype.belongsTo = function belongsTo (truss) {
779 return this._tree.truss === truss;
780};
781
782Object.defineProperties( Handle.prototype, prototypeAccessors$1 );
783
784
785var Query = /*@__PURE__*/(function (Handle) {
786 function Query(tree, path, spec, annotations) {
787 Handle.call(this, tree, path, annotations);
788 this._spec = this._copyAndValidateSpec(spec);
789 var queryTerms = _(this._spec)
790 .map(function (value, key) { return (key + "=" + (encodeURIComponent(JSON.stringify(value)))); })
791 .sortBy()
792 .join('&');
793 this._string = (this._path) + "?" + queryTerms;
794 Object.freeze(this);
795 }
796
797 if ( Handle ) Query.__proto__ = Handle;
798 Query.prototype = Object.create( Handle && Handle.prototype );
799 Query.prototype.constructor = Query;
800
801 var prototypeAccessors$1 = { ready: { configurable: true },constraints: { configurable: true } };
802
803 // Vue-bound
804 prototypeAccessors$1.ready.get = function () {
805 return this._tree.isQueryReady(this);
806 };
807
808 prototypeAccessors$1.constraints.get = function () {
809 return this._spec;
810 };
811
812 Query.prototype.annotate = function annotate (annotations) {
813 return new Query(
814 this._tree, this._path, this._spec, _.assign({}, this._annotations, annotations));
815 };
816
817 Query.prototype._copyAndValidateSpec = function _copyAndValidateSpec (spec) {
818 if (!spec.by) { throw new Error('Query needs "by" clause: ' + JSON.stringify(spec)); }
819 if (('at' in spec) + ('from' in spec) + ('to' in spec) > 1) {
820 throw new Error(
821 'Query must contain at most one of "at", "from", or "to" clauses: ' + JSON.stringify(spec));
822 }
823 if (('first' in spec) + ('last' in spec) > 1) {
824 throw new Error(
825 'Query must contain at most one of "first" or "last" clauses: ' + JSON.stringify(spec));
826 }
827 if (!_.some(['at', 'from', 'to', 'first', 'last'], function (clause) { return clause in spec; })) {
828 throw new Error(
829 'Query must contain at least one of "at", "from", "to", "first", or "last" clauses: ' +
830 JSON.stringify(spec));
831 }
832 spec = _.clone(spec);
833 if (spec.by !== '$key' && spec.by !== '$value') {
834 if (!(spec.by instanceof Reference)) {
835 throw new Error('Query "by" value must be a reference: ' + spec.by);
836 }
837 var childPath = spec.by.toString();
838 if (!_.startsWith(childPath, this._path)) {
839 throw new Error(
840 'Query "by" value must be a descendant of target reference: ' + spec.by);
841 }
842 childPath = childPath.slice(this._path.length).replace(/^\/?/, '');
843 if (!_.includes(childPath, '/')) {
844 throw new Error(
845 'Query "by" value must not be a direct child of target reference: ' + spec.by);
846 }
847 spec.by = childPath.replace(/.*?\//, '');
848 }
849 Object.freeze(spec);
850 return spec;
851 };
852
853
854 Query.prototype.toString = function toString () {
855 return this._string;
856 };
857
858 Object.defineProperties( Query.prototype, prototypeAccessors$1 );
859
860 return Query;
861}(Handle));
862
863
864var Reference = /*@__PURE__*/(function (Handle) {
865 function Reference(tree, path, annotations) {
866 Handle.call(this, tree, path, annotations);
867 Object.freeze(this);
868 }
869
870 if ( Handle ) Reference.__proto__ = Handle;
871 Reference.prototype = Object.create( Handle && Handle.prototype );
872 Reference.prototype.constructor = Reference;
873
874 var prototypeAccessors$2 = { ready: { configurable: true },value: { configurable: true } };
875
876 prototypeAccessors$2.ready.get = function () {return this._tree.isReferenceReady(this);}; // Vue-bound
877 prototypeAccessors$2.value.get = function () {return this._tree.getObject(this.path);}; // Vue-bound
878 Reference.prototype.toString = function toString () {return this._path;};
879
880 Reference.prototype.annotate = function annotate (annotations) {
881 return new Reference(this._tree, this._path, _.assign({}, this._annotations, annotations));
882 };
883
884 Reference.prototype.query = function query (spec) {
885 return new Query(this._tree, this._path, spec, this._annotations);
886 };
887
888 Reference.prototype.set = function set (value) {
889 var obj;
890
891 this._checkForUndefinedPath();
892 return this._tree.update(this, 'set', ( obj = {}, obj[this.path] = value, obj ));
893 };
894
895 Reference.prototype.update = function update (values) {
896 this._checkForUndefinedPath();
897 return this._tree.update(this, 'update', values);
898 };
899
900 Reference.prototype.override = function override (value) {
901 var obj;
902
903 this._checkForUndefinedPath();
904 return this._tree.update(this, 'override', ( obj = {}, obj[this.path] = value, obj ));
905 };
906
907 Reference.prototype.commit = function commit (updateFunction) {
908 this._checkForUndefinedPath();
909 return this._tree.commit(this, updateFunction);
910 };
911
912 Reference.prototype._checkForUndefinedPath = function _checkForUndefinedPath () {
913 if (this.path === '/undefined') { throw new Error('Invalid path for operation: ' + this.path); }
914 };
915
916 Object.defineProperties( Reference.prototype, prototypeAccessors$2 );
917
918 return Reference;
919}(Handle));
920
921var SERVER_TIMESTAMP = Object.freeze({'.sv': 'timestamp'});
922
923function isTrussEqual(a, b) {
924 return _.isEqualWith(a, b, isTrussValueEqual);
925}
926
927function isTrussValueEqual(a, b) {
928 if (a === b || a === undefined || a === null || b === undefined || b === null ||
929 a.$truss || b.$truss) { return a === b; }
930 if (a.isEqual) { return a.isEqual(b); }
931}
932
933function copyPrototype(a, b) {
934 for (var i = 0, list = Object.getOwnPropertyNames(a.prototype); i < list.length; i += 1) {
935 var prop = list[i];
936
937 if (prop === 'constructor') { continue; }
938 Object.defineProperty(b.prototype, prop, Object.getOwnPropertyDescriptor(a.prototype, prop));
939 }
940}
941
942var StatItem = function StatItem(name) {
943 _.assign(this, {name: name, numRecomputes: 0, numUpdates: 0, runtime: 0});
944};
945
946var prototypeAccessors$2 = { runtimePerRecompute: { configurable: true } };
947
948StatItem.prototype.add = function add (item) {
949 this.runtime += item.runtime;
950 this.numUpdates += item.numUpdates;
951 this.numRecomputes += item.numRecomputes;
952};
953
954prototypeAccessors$2.runtimePerRecompute.get = function () {
955 return this.numRecomputes ? this.runtime / this.numRecomputes : 0;
956};
957
958StatItem.prototype.toLogParts = function toLogParts (totals) {
959 return [
960 ((this.name) + ":"), (" " + ((this.runtime / 1000).toFixed(2)) + "s"),
961 ("(" + ((this.runtime / totals.runtime * 100).toFixed(1)) + "%)"),
962 (" " + (this.numUpdates) + " upd /"), ((this.numRecomputes) + " runs"),
963 ("(" + ((this.numUpdates / this.numRecomputes * 100).toFixed(1)) + "%)"),
964 (" " + (this.runtimePerRecompute.toFixed(2)) + "ms / run")
965 ];
966};
967
968Object.defineProperties( StatItem.prototype, prototypeAccessors$2 );
969
970var Stats = function Stats() {
971 this._items = {};
972};
973
974var prototypeAccessors$1$1 = { list: { configurable: true } };
975
976Stats.prototype.for = function for$1 (name) {
977 if (!this._items[name]) { this._items[name] = new StatItem(name); }
978 return this._items[name];
979};
980
981prototypeAccessors$1$1.list.get = function () {
982 return _(this._items).values().sortBy(function (item) { return -item.runtime; }).value();
983};
984
985Stats.prototype.log = function log (n) {
986 if ( n === void 0 ) n = 10;
987
988 var stats = this.list;
989 if (!stats.length) { return; }
990 var totals = new StatItem('=== Total');
991 _.forEach(stats, function (stat) {totals.add(stat);});
992 stats = _.take(stats, n);
993 var above = new StatItem('--- Above');
994 _.forEach(stats, function (stat) {above.add(stat);});
995 var lines = _.map(stats, function (item) { return item.toLogParts(totals); });
996 lines.push(above.toLogParts(totals));
997 lines.push(totals.toLogParts(totals));
998 var widths = _.map(_.range(lines[0].length), function (i) { return _(lines).map(function (line) { return line[i].length; }).max(); });
999 _.forEach(lines, function (line) {
1000 console.log(_.map(line, function (column, i) { return _.padStart(column, widths[i]); }).join(' '));
1001 });
1002};
1003
1004Stats.prototype.wrap = function wrap (getter, className, propName) {
1005 var item = this.for((className + "." + propName));
1006 return function() {
1007 /* eslint-disable no-invalid-this */
1008 var startTime = performanceNow();
1009 var oldValue = this._computedWatchers && this._computedWatchers[propName].value;
1010 try {
1011 var newValue = getter.call(this);
1012 if (!isTrussEqual(oldValue, newValue)) { item.numUpdates += 1; }
1013 return newValue;
1014 } finally {
1015 item.runtime += performanceNow() - startTime;
1016 item.numRecomputes += 1;
1017 }
1018 };
1019};
1020
1021Object.defineProperties( Stats.prototype, prototypeAccessors$1$1 );
1022
1023var stats = new Stats();
1024
1025var Connector = function Connector(scope, connections, tree, method, refs) {
1026 var this$1 = this;
1027
1028 Object.freeze(connections);
1029 this._scope = scope;
1030 this._connections = connections;
1031 this._tree = tree;
1032 this._method = method;
1033
1034 this._subConnectors = {};
1035 this._disconnects = {};
1036 this._angularUnwatches = undefined;
1037 this._data = {};
1038 this._vue = new Vue({data: {
1039 descriptors: {},
1040 refs: refs || {},
1041 values: _.mapValues(connections, _.constant(undefined))
1042 }});
1043 // allow instance-level overrides of destroy() method
1044 this.destroy = this.destroy;// eslint-disable-line no-self-assign
1045 Object.seal(this);
1046
1047 this._linkScopeProperties();
1048
1049 _.forEach(connections, function (descriptor, key) {
1050 if (_.isFunction(descriptor)) {
1051 this$1._bindComputedConnection(key, descriptor);
1052 } else {
1053 this$1._connect(key, descriptor);
1054 }
1055 });
1056
1057 if (angularProxy.active && scope && scope.$on && scope.$id) {
1058 scope.$on('$destroy', function () {this$1.destroy();});
1059 }
1060};
1061
1062var prototypeAccessors$3 = { ready: { configurable: true },at: { configurable: true },data: { configurable: true } };
1063
1064prototypeAccessors$3.ready.get = function () {
1065 var this$1 = this;
1066
1067 return _.every(this._connections, function (ignored, key) {
1068 var descriptor = this$1._vue.descriptors[key];
1069 if (!descriptor) { return false; }
1070 if (descriptor instanceof Handle) { return descriptor.ready; }
1071 return this$1._subConnectors[key].ready;
1072 });
1073};
1074
1075prototypeAccessors$3.at.get = function () {
1076 return this._vue.refs;
1077};
1078
1079prototypeAccessors$3.data.get = function () {
1080 return this._data;
1081};
1082
1083Connector.prototype.destroy = function destroy () {
1084 var this$1 = this;
1085
1086 this._unlinkScopeProperties();
1087 _.forEach(this._angularUnwatches, function (unwatch) {unwatch();});
1088 _.forEach(this._connections, function (descriptor, key) {this$1._disconnect(key);});
1089 this._vue.$destroy();
1090};
1091
1092Connector.prototype._linkScopeProperties = function _linkScopeProperties () {
1093 var this$1 = this;
1094
1095 var dataProperties = _.mapValues(this._connections, function (unused, key) { return ({
1096 configurable: true, enumerable: false, get: function () {
1097 var descriptor = this$1._vue.descriptors[key];
1098 if (descriptor instanceof Reference) { return descriptor.value; }
1099 return this$1._vue.values[key];
1100 }
1101 }); });
1102 Object.defineProperties(this._data, dataProperties);
1103 if (this._scope) {
1104 for (var key in this._connections) {
1105 if (key in this._scope) {
1106 throw new Error(("Property already defined on connection target: " + key));
1107 }
1108 }
1109 Object.defineProperties(this._scope, dataProperties);
1110 if (this._scope.__ob__) { this._scope.__ob__.dep.notify(); }
1111 }
1112};
1113
1114Connector.prototype._unlinkScopeProperties = function _unlinkScopeProperties () {
1115 var this$1 = this;
1116
1117 if (!this._scope) { return; }
1118 _.forEach(this._connections, function (descriptor, key) {
1119 delete this$1._scope[key];
1120 });
1121};
1122
1123Connector.prototype._bindComputedConnection = function _bindComputedConnection (key, fn) {
1124 var connectionStats = stats.for(("connection.at." + key));
1125 var getter = this._computeConnection.bind(this, fn, connectionStats);
1126 var update = this._updateComputedConnection.bind(this, key, fn, connectionStats);
1127 var angularWatch = angularProxy.active && !fn.angularWatchSuppressed;
1128 // Use this._vue.$watch instead of truss.observe here so that we can disable the immediate
1129 // callback if we'll get one from Angular anyway.
1130 this._vue.$watch(getter, update, {immediate: !angularWatch});
1131 if (angularWatch) {
1132 if (!this._angularUnwatches) { this._angularUnwatches = []; }
1133 this._angularUnwatches.push(angularProxy.watch(getter, update, true));
1134 }
1135};
1136
1137Connector.prototype._computeConnection = function _computeConnection (fn, connectionStats) {
1138 var startTime = performanceNow();
1139 try {
1140 return flattenRefs(fn.call(this._scope));
1141 } finally {
1142 connectionStats.runtime += performanceNow() - startTime;
1143 connectionStats.numRecomputes += 1;
1144 }
1145};
1146
1147Connector.prototype._updateComputedConnection = function _updateComputedConnection (key, value, connectionStats) {
1148 var newDescriptor = _.isFunction(value) ? value(this._scope) : value;
1149 var oldDescriptor = this._vue.descriptors[key];
1150 var descriptorChanged = !isTrussEqual(oldDescriptor, newDescriptor);
1151 if (!descriptorChanged) { return; }
1152 if (connectionStats && descriptorChanged) { connectionStats.numUpdates += 1; }
1153 if (!newDescriptor) {
1154 this._disconnect(key);
1155 return;
1156 }
1157 if (newDescriptor instanceof Handle || !_.has(this._subConnectors, key)) {
1158 this._disconnect(key);
1159 this._connect(key, newDescriptor);
1160 } else {
1161 this._subConnectors[key]._updateConnections(newDescriptor);
1162 }
1163 Vue.set(this._vue.descriptors, key, newDescriptor);
1164 angularProxy.digest();
1165};
1166
1167Connector.prototype._updateConnections = function _updateConnections (connections) {
1168 var this$1 = this;
1169
1170 _.forEach(connections, function (descriptor, key) {
1171 this$1._updateComputedConnection(key, descriptor);
1172 });
1173 _.forEach(this._connections, function (descriptor, key) {
1174 if (!_.has(connections, key)) { this$1._updateComputedConnection(key); }
1175 });
1176 this._connections = connections;
1177};
1178
1179Connector.prototype._connect = function _connect (key, descriptor) {
1180 var this$1 = this;
1181
1182 Vue.set(this._vue.descriptors, key, descriptor);
1183 angularProxy.digest();
1184 if (!descriptor) { return; }
1185 Vue.set(this._vue.values, key, undefined);
1186 if (descriptor instanceof Reference) {
1187 Vue.set(this._vue.refs, key, descriptor);
1188 this._disconnects[key] = this._tree.connectReference(descriptor, this._method);
1189 } else if (descriptor instanceof Query) {
1190 Vue.set(this._vue.refs, key, descriptor);
1191 var updateFn = this._updateQueryValue.bind(this, key);
1192 this._disconnects[key] = this._tree.connectQuery(descriptor, updateFn, this._method);
1193 } else {
1194 var subScope = {}, subRefs = {};
1195 Vue.set(this._vue.refs, key, subRefs);
1196 var subConnector = this._subConnectors[key] =
1197 new Connector(subScope, descriptor, this._tree, this._method, subRefs);
1198 // Use a truss.observe here instead of this._vue.$watch so that the "immediate" execution
1199 // actually takes place after we've captured the unwatch function, in case the subConnector
1200 // is ready immediately.
1201 var unobserve = this._disconnects[key] = this._tree.truss.observe(
1202 function () { return subConnector.ready; },
1203 function (subReady) {
1204 if (!subReady) { return; }
1205 unobserve();
1206 delete this$1._disconnects[key];
1207 Vue.set(this$1._vue.values, key, subScope);
1208 angularProxy.digest();
1209 }
1210 );
1211 }
1212};
1213
1214Connector.prototype._disconnect = function _disconnect (key) {
1215 Vue.delete(this._vue.refs, key);
1216 this._updateRefValue(key, undefined);
1217 if (_.has(this._subConnectors, key)) {
1218 this._subConnectors[key].destroy();
1219 delete this._subConnectors[key];
1220 }
1221 if (this._disconnects[key]) { this._disconnects[key](); }
1222 delete this._disconnects[key];
1223 Vue.delete(this._vue.descriptors, key);
1224 angularProxy.digest();
1225};
1226
1227Connector.prototype._updateRefValue = function _updateRefValue (key, value) {
1228 if (this._vue.values[key] !== value) {
1229 Vue.set(this._vue.values, key, value);
1230 angularProxy.digest();
1231 }
1232};
1233
1234Connector.prototype._updateQueryValue = function _updateQueryValue (key, childKeys) {
1235 if (!this._vue.values[key]) {
1236 Vue.set(this._vue.values, key, {});
1237 angularProxy.digest();
1238 }
1239 var subScope = this._vue.values[key];
1240 for (var childKey in subScope) {
1241 if (!subScope.hasOwnProperty(childKey)) { continue; }
1242 if (!_.includes(childKeys, childKey)) {
1243 Vue.delete(subScope, childKey);
1244 angularProxy.digest();
1245 }
1246 }
1247 var object = this._tree.getObject(this._vue.descriptors[key].path);
1248 for (var i = 0, list = childKeys; i < list.length; i += 1) {
1249 var childKey$1 = list[i];
1250
1251 if (subScope.hasOwnProperty(childKey$1)) { continue; }
1252 Vue.set(subScope, childKey$1, object[childKey$1]);
1253 angularProxy.digest();
1254 }
1255};
1256
1257Object.defineProperties( Connector.prototype, prototypeAccessors$3 );
1258
1259function flattenRefs(refs) {
1260 if (!refs) { return; }
1261 if (refs instanceof Handle) { return refs.toString(); }
1262 return _.mapValues(refs, flattenRefs);
1263}
1264
1265function wrapPromiseCallback(callback) {
1266 return function() {
1267 try {
1268 // eslint-disable-next-line no-invalid-this
1269 return Promise.resolve(callback.apply(this, arguments));
1270 } catch (e) {
1271 return Promise.reject(e);
1272 }
1273 };
1274}
1275
1276function promiseCancel(promise, cancel) {
1277 promise = promiseFinally(promise, function () {cancel = null;});
1278 promise.cancel = function () {
1279 if (!cancel) { return; }
1280 cancel();
1281 cancel = null;
1282 };
1283 propagatePromiseProperty(promise, 'cancel');
1284 return promise;
1285}
1286
1287function propagatePromiseProperty(promise, propertyName) {
1288 var originalThen = promise.then, originalCatch = promise.catch;
1289 promise.then = function (onResolved, onRejected) {
1290 var derivedPromise = originalThen.call(promise, onResolved, onRejected);
1291 derivedPromise[propertyName] = promise[propertyName];
1292 propagatePromiseProperty(derivedPromise, propertyName);
1293 return derivedPromise;
1294 };
1295 promise.catch = function (onRejected) {
1296 var derivedPromise = originalCatch.call(promise, onRejected);
1297 derivedPromise[propertyName] = promise[propertyName];
1298 propagatePromiseProperty(derivedPromise, propertyName);
1299 return derivedPromise;
1300 };
1301 return promise;
1302}
1303
1304function promiseFinally(promise, onFinally) {
1305 if (!onFinally) { return promise; }
1306 onFinally = wrapPromiseCallback(onFinally);
1307 return promise.then(function (result) {
1308 return onFinally().then(function () { return result; });
1309 }, function (error) {
1310 return onFinally().then(function () { return Promise.reject(error); });
1311 });
1312}
1313
1314var INTERCEPT_KEYS = [
1315 'read', 'write', 'auth', 'set', 'update', 'commit', 'connect', 'peek', 'authenticate',
1316 'unathenticate', 'certify', 'all'
1317];
1318
1319var EMPTY_ARRAY = [];
1320
1321
1322var SlowHandle = function SlowHandle(operation, delay, callback) {
1323 this._operation = operation;
1324 this._delay = delay;
1325 this._callback = callback;
1326 this._fired = false;
1327};
1328
1329SlowHandle.prototype.initiate = function initiate () {
1330 var this$1 = this;
1331
1332 this.cancel();
1333 this._fired = false;
1334 var elapsed = Date.now() - this._operation._startTimestamp;
1335 this._timeoutId = setTimeout(function () {
1336 this$1._fired = true;
1337 this$1._callback(this$1._operation);
1338 }, this._delay - elapsed);
1339};
1340
1341SlowHandle.prototype.cancel = function cancel () {
1342 if (this._fired) { this._callback(this._operation); }
1343 if (this._timeoutId) { clearTimeout(this._timeoutId); }
1344};
1345
1346
1347var Operation = function Operation(type, method, target, operand) {
1348 this._type = type;
1349 this._method = method;
1350 this._target = target;
1351 this._operand = operand;
1352 this._ready = false;
1353 this._running = false;
1354 this._ended = false;
1355 this._tries = 0;
1356 this._startTimestamp = Date.now();
1357 this._slowHandles = [];
1358};
1359
1360var 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 } };
1361
1362prototypeAccessors$4.type.get = function () {return this._type;};
1363prototypeAccessors$4.method.get = function () {return this._method;};
1364prototypeAccessors$4.target.get = function () {return this._target;};
1365prototypeAccessors$4.targets.get = function () {
1366 var this$1 = this;
1367
1368 if (this._method !== 'update') { return [this._target]; }
1369 return _.map(this._operand, function (value, escapedPathFragment) {
1370 return new Reference(
1371 this$1._target._tree, joinPath(this$1._target.path, escapedPathFragment),
1372 this$1._target._annotations);
1373 });
1374};
1375prototypeAccessors$4.operand.get = function () {return this._operand;};
1376prototypeAccessors$4.ready.get = function () {return this._ready;};
1377prototypeAccessors$4.running.get = function () {return this._running;};
1378prototypeAccessors$4.ended.get = function () {return this._ended;};
1379prototypeAccessors$4.tries.get = function () {return this._tries;};
1380prototypeAccessors$4.error.get = function () {return this._error;};
1381
1382Operation.prototype.onSlow = function onSlow (delay, callback) {
1383 var handle = new SlowHandle(this, delay, callback);
1384 this._slowHandles.push(handle);
1385 handle.initiate();
1386};
1387
1388Operation.prototype._setRunning = function _setRunning (value) {
1389 this._running = value;
1390};
1391
1392Operation.prototype._setEnded = function _setEnded (value) {
1393 this._ended = value;
1394};
1395
1396Operation.prototype._markReady = function _markReady (ending) {
1397 this._ready = true;
1398 if (!ending) { this._tries = 0; }
1399 _.forEach(this._slowHandles, function (handle) { return handle.cancel(); });
1400};
1401
1402Operation.prototype._clearReady = function _clearReady () {
1403 this._ready = false;
1404 this._startTimestamp = Date.now();
1405 _.forEach(this._slowHandles, function (handle) { return handle.initiate(); });
1406};
1407
1408Operation.prototype._incrementTries = function _incrementTries () {
1409 this._tries++;
1410};
1411
1412Object.defineProperties( Operation.prototype, prototypeAccessors$4 );
1413
1414
1415var Dispatcher = function Dispatcher(bridge) {
1416 this._bridge = bridge;
1417 this._callbacks = {};
1418 Object.freeze(this);
1419};
1420
1421Dispatcher.prototype.intercept = function intercept (interceptKey, callbacks) {
1422 if (!_.includes(INTERCEPT_KEYS, interceptKey)) {
1423 throw new Error('Unknown intercept operation type: ' + interceptKey);
1424 }
1425 var badCallbackKeys =
1426 _.difference(_.keys(callbacks), ['onBefore', 'onAfter', 'onError', 'onFailure']);
1427 if (badCallbackKeys.length) {
1428 throw new Error('Unknown intercept callback types: ' + badCallbackKeys.join(', '));
1429 }
1430 var wrappedCallbacks = {
1431 onBefore: this._addCallback('onBefore', interceptKey, callbacks.onBefore),
1432 onAfter: this._addCallback('onAfter', interceptKey, callbacks.onAfter),
1433 onError: this._addCallback('onError', interceptKey, callbacks.onError),
1434 onFailure: this._addCallback('onFailure', interceptKey, callbacks.onFailure)
1435 };
1436 return this._removeCallbacks.bind(this, interceptKey, wrappedCallbacks);
1437};
1438
1439Dispatcher.prototype._addCallback = function _addCallback (stage, interceptKey, callback) {
1440 if (!callback) { return; }
1441 var key = this._getCallbacksKey(stage, interceptKey);
1442 var wrappedCallback = wrapPromiseCallback(callback);
1443 (this._callbacks[key] || (this._callbacks[key] = [])).push(wrappedCallback);
1444 return wrappedCallback;
1445};
1446
1447Dispatcher.prototype._removeCallback = function _removeCallback (stage, interceptKey, wrappedCallback) {
1448 if (!wrappedCallback) { return; }
1449 var key = this._getCallbacksKey(stage, interceptKey);
1450 if (this._callbacks[key]) { _.pull(this._callbacks[key], wrappedCallback); }
1451};
1452
1453Dispatcher.prototype._removeCallbacks = function _removeCallbacks (interceptKey, wrappedCallbacks) {
1454 var this$1 = this;
1455
1456 _.forEach(wrappedCallbacks, function (wrappedCallback, stage) {
1457 this$1._removeCallback(stage, interceptKey, wrappedCallback);
1458 });
1459};
1460
1461Dispatcher.prototype._getCallbacks = function _getCallbacks (stage, operationType, method) {
1462 return [].concat(
1463 this._callbacks[this._getCallbacksKey(stage, method)] || EMPTY_ARRAY,
1464 this._callbacks[this._getCallbacksKey(stage, operationType)] || EMPTY_ARRAY,
1465 this._callbacks[this._getCallbacksKey(stage, 'all')] || EMPTY_ARRAY
1466 );
1467};
1468
1469Dispatcher.prototype._getCallbacksKey = function _getCallbacksKey (stage, interceptKey) {
1470 return (stage + "_" + interceptKey);
1471};
1472
1473Dispatcher.prototype.execute = function execute (operationType, method, target, operand, executor) {
1474 var this$1 = this;
1475
1476 executor = wrapPromiseCallback(executor);
1477 var operation = this.createOperation(operationType, method, target, operand);
1478 return this.begin(operation).then(function () {
1479 var executeWithRetries = function () {
1480 return executor().catch(function (e) { return this$1._retryOrEnd(operation, e).then(executeWithRetries); });
1481 };
1482 return executeWithRetries();
1483 }).then(function (result) { return this$1.end(operation).then(function () { return result; }); });
1484};
1485
1486Dispatcher.prototype.createOperation = function createOperation (operationType, method, target, operand) {
1487 return new Operation(operationType, method, target, operand);
1488};
1489
1490Dispatcher.prototype.begin = function begin (operation) {
1491 var this$1 = this;
1492
1493 return Promise.all(_.map(
1494 this._getCallbacks('onBefore', operation.type, operation.method),
1495 function (onBefore) { return onBefore(operation); }
1496 )).then(function () {
1497 if (!operation.ended) { operation._setRunning(true); }
1498 }, function (e) { return this$1.end(operation, e); });
1499};
1500
1501Dispatcher.prototype.markReady = function markReady (operation) {
1502 operation._markReady();
1503};
1504
1505Dispatcher.prototype.clearReady = function clearReady (operation) {
1506 operation._clearReady();
1507};
1508
1509Dispatcher.prototype.retry = function retry (operation, error) {
1510 operation._incrementTries();
1511 operation._error = error;
1512 return Promise.all(_.map(
1513 this._getCallbacks('onError', operation.type, operation.method),
1514 function (onError) { return onError(operation, error); }
1515 )).then(function (results) {
1516 // If the operation ended in the meantime, bail.This will cause the caller to attempt to
1517 // fail the operation, but since it's already ended the call to end() with an error will be a
1518 // no-op.
1519 if (operation.ended) { return; }
1520 var retrying = _.some(results);
1521 if (retrying) { delete operation._error; }
1522 return retrying;
1523 });
1524};
1525
1526Dispatcher.prototype._retryOrEnd = function _retryOrEnd (operation, error) {
1527 var this$1 = this;
1528
1529 return this.retry(operation, error).then(function (result) {
1530 if (!result) { return this$1.end(operation, error); }
1531 }, function (e) { return this$1.end(operation, e); });
1532};
1533
1534Dispatcher.prototype.end = function end (operation, error) {
1535 var this$1 = this;
1536
1537 if (operation.ended) { return Promise.resolve(); }
1538 operation._setRunning(false);
1539 operation._setEnded(true);
1540 if (error) {
1541 operation._error = error;
1542 } else {
1543 // In case we're racing with a retry(), wipe out the error.
1544 delete operation._error;
1545 }
1546 return Promise.all(_.map(
1547 this._getCallbacks('onAfter', operation.type, operation.method),
1548 function (onAfter) { return onAfter(operation); }
1549 )).then(
1550 function () { return this$1._afterEnd(operation); },
1551 function (e) {
1552 operation._error = e;
1553 return this$1._afterEnd(operation);
1554 }
1555 );
1556};
1557
1558Dispatcher.prototype._afterEnd = function _afterEnd (operation) {
1559 operation._markReady(true);
1560 if (!operation.error) { return Promise.resolve(); }
1561 var onFailureCallbacks = this._getCallbacks('onFailure', operation.type, operation.method);
1562 if (onFailureCallbacks) {
1563 setTimeout(function () {
1564 _.forEach(onFailureCallbacks, function (onFailure) { return onFailure(operation); });
1565 }, 0);
1566 }
1567 return Promise.reject(operation.error);
1568};
1569
1570var ALPHABET = '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz';
1571
1572var getRandomValues = window.crypto && window.crypto.getRandomValues &&
1573 window.crypto.getRandomValues.bind(window.crypto);
1574
1575var KeyGenerator = function KeyGenerator() {
1576 this._lastUniqueKeyTime = 0;
1577 this._lastRandomValues = [];
1578};
1579
1580KeyGenerator.prototype.generateUniqueKey = function generateUniqueKey (now) {
1581 now = now || Date.now();
1582 var chars = new Array(20);
1583 var prefix = now;
1584 for (var i = 7; i >= 0; i--) {
1585 chars[i] = ALPHABET.charAt(prefix & 0x3f);// eslint-disable-line no-bitwise
1586 prefix = Math.floor(prefix / 64);
1587 }
1588 if (now === this._lastUniqueKeyTime) {
1589 var i$1 = 11;
1590 while (i$1 >= 0 && this._lastRandomValues[i$1] === 63) {
1591 this._lastRandomValues[i$1] = 0;
1592 i$1 -= 1;
1593 }
1594 if (i$1 === -1) {
1595 throw new Error('Internal assertion failure: ran out of unique IDs for this millisecond');
1596 }
1597 this._lastRandomValues[i$1] += 1;
1598 } else {
1599 this._lastUniqueKeyTime = now;
1600 if (getRandomValues) {
1601 var array = new Uint8Array(12);
1602 getRandomValues(array);
1603 for (var i$2 = 0; i$2 < 12; i$2++) {
1604 // eslint-disable-next-line no-bitwise
1605 this._lastRandomValues[i$2] = array[i$2] & (i$2 ? 0x3f : 0x0f);
1606 }
1607 } else {
1608 for (var i$3 = 0; i$3 < 12; i$3++) {
1609 // Make sure to leave some space for incrementing in the top nibble.
1610 this._lastRandomValues[i$3] = Math.floor(Math.random() * (i$3 ? 64 : 16));
1611 }
1612 }
1613 }
1614 for (var i$4 = 0; i$4 < 12; i$4++) {
1615 chars[i$4 + 8] = ALPHABET[this._lastRandomValues[i$4]];
1616 }
1617 return chars.join('');
1618};
1619
1620var MetaTree = function MetaTree(rootUrl, tree, bridge, dispatcher) {
1621 this._rootUrl = rootUrl;
1622 this._tree = tree;
1623 this._dispatcher = dispatcher;
1624 this._bridge = bridge;
1625 this._vue = new Vue({data: {$root: {
1626 connected: undefined, timeOffset: 0, user: undefined, userid: undefined,
1627 nowAtInterval: function nowAtInterval(intervalMillis) {
1628 var this$1 = this;
1629
1630 var key = 'now' + intervalMillis;
1631 if (!this.hasOwnProperty(key)) {
1632 var update = function () {
1633 Vue.set(this$1, key, Date.now() + this$1.timeOffset);
1634 angularProxy.digest();
1635 };
1636 update();
1637 setInterval(update, intervalMillis);
1638 }
1639 return this[key];
1640 }
1641 }}});
1642
1643 this._auth = {serial: 0, initialAuthChangeReceived: false};
1644
1645 bridge.onAuth(rootUrl, this._handleAuthChange, this);
1646
1647 this._connectInfoProperty('serverTimeOffset', 'timeOffset');
1648 this._connectInfoProperty('connected', 'connected');
1649 Object.freeze(this);
1650};
1651
1652var prototypeAccessors$5 = { root: { configurable: true } };
1653
1654prototypeAccessors$5.root.get = function () {
1655 return this._vue.$data.$root;
1656};
1657
1658MetaTree.prototype.destroy = function destroy () {
1659 this._bridge.offAuth(this._rootUrl, this._handleAuthChange, this);
1660 this._vue.$destroy();
1661};
1662
1663MetaTree.prototype.authenticate = function authenticate (token) {
1664 var this$1 = this;
1665
1666 this._auth.serial++;
1667 return this._dispatcher.execute(
1668 'auth', 'authenticate', new Reference(this._tree, '/'), token, function () {
1669 if (token) { return this$1._bridge.authWithCustomToken(this$1._rootUrl, token); }
1670 return this$1._bridge.authAnonymously(this$1._rootUrl);
1671 }
1672 );
1673};
1674
1675MetaTree.prototype.unauthenticate = function unauthenticate () {
1676 var this$1 = this;
1677
1678 // Signal user change to null pre-emptively.This is what the Firebase SDK does as well, since
1679 // it lets the app tear down user-required connections before the user is actually deauthed,
1680 // which can prevent spurious permission denied errors.
1681 this._auth.serial++;
1682 return this._handleAuthChange(null).then(function (approved) {
1683 // Bail if auth change callback initiated another authentication, since it will have already
1684 // sent the command to the bridge and sending our own now would incorrectly override it.
1685 if (!approved) { return; }
1686 return this$1._dispatcher.execute(
1687 'auth', 'unauthenticate', new Reference(this$1._tree, '/'), undefined, function () {
1688 return this$1._bridge.unauth(this$1._rootUrl);
1689 }
1690 );
1691 });
1692};
1693
1694MetaTree.prototype._handleAuthChange = function _handleAuthChange (user) {
1695 var this$1 = this;
1696
1697 var supersededChange = !this._auth.initialAuthChangeReceived && this._auth.serial;
1698 if (user !== undefined) { this._auth.initialAuthChangeReceived = true; }
1699 if (supersededChange) { return; }
1700 var authSerial = this._auth.serial;
1701 if (this.root.user === user) { return Promise.resolve(false); }
1702 return this._dispatcher.execute('auth', 'certify', new Reference(this._tree, '/'), user, function () {
1703 if (this$1.root.user === user || authSerial !== this$1._auth.serial) { return false; }
1704 if (user) { Object.freeze(user); }
1705 this$1.root.user = user;
1706 this$1.root.userid = user && user.uid;
1707 angularProxy.digest();
1708 return true;
1709 });
1710};
1711
1712MetaTree.prototype._isAuthChangeStale = function _isAuthChangeStale (user) {
1713 return this.root.user === user;
1714};
1715
1716MetaTree.prototype._connectInfoProperty = function _connectInfoProperty (property, attribute) {
1717 var this$1 = this;
1718
1719 var propertyUrl = (this._rootUrl) + "/.info/" + property;
1720 this._bridge.on(propertyUrl, propertyUrl, null, 'value', function (snap) {
1721 this$1.root[attribute] = snap.value;
1722 angularProxy.digest();
1723 });
1724};
1725
1726Object.defineProperties( MetaTree.prototype, prototypeAccessors$5 );
1727
1728// These are defined separately for each object so they're not included in Value below.
1729var RESERVED_VALUE_PROPERTY_NAMES = {$$$trussCheck: true, __ob__: true};
1730
1731// Holds properties that we're going to set on a model object that's being created right now as soon
1732// as it's been created, but that we'd like to be accessible in the constructor. The object
1733// prototype's getters will pick those up until they get overridden in the instance.
1734var creatingObjectProperties;
1735
1736var currentPropertyFrozen;
1737
1738
1739var BaseValue = function BaseValue () {};
1740
1741var prototypeAccessors$6 = { $meta: { configurable: true },$store: { configurable: true },$now: { configurable: true },$$finalizers: { configurable: true } };
1742
1743prototypeAccessors$6.$meta.get = function () {return this.$truss.meta;};
1744prototypeAccessors$6.$store.get = function () {return this.$truss.store;};// access indirectly to leave dependency trace
1745prototypeAccessors$6.$now.get = function () {return this.$truss.now;};
1746
1747BaseValue.prototype.$newKey = function $newKey () {return this.$truss.newKey();};
1748
1749BaseValue.prototype.$intercept = function $intercept (actionType, callbacks) {
1750 var this$1 = this;
1751
1752 if (this.$destroyed) { throw new Error('Object already destroyed'); }
1753 var unintercept = this.$truss.intercept(actionType, callbacks);
1754 var uninterceptAndRemoveFinalizer = function () {
1755 unintercept();
1756 _.pull(this$1.$$finalizers, uninterceptAndRemoveFinalizer);
1757 };
1758 this.$$finalizers.push(uninterceptAndRemoveFinalizer);
1759 return uninterceptAndRemoveFinalizer;
1760};
1761
1762BaseValue.prototype.$connect = function $connect (scope, connections) {
1763 var this$1 = this;
1764
1765 if (this.$destroyed) { throw new Error('Object already destroyed'); }
1766 if (!connections) {
1767 connections = scope;
1768 scope = undefined;
1769 }
1770 var connector = this.$truss.connect(scope, wrapConnections(this, connections));
1771 var originalDestroy = connector.destroy;
1772 var destroy = function () {
1773 _.pull(this$1.$$finalizers, destroy);
1774 return originalDestroy.call(connector);
1775 };
1776 this.$$finalizers.push(destroy);
1777 connector.destroy = destroy;
1778 return connector;
1779};
1780
1781BaseValue.prototype.$peek = function $peek (target, callback) {
1782 var this$1 = this;
1783
1784 if (this.$destroyed) { throw new Error('Object already destroyed'); }
1785 var promise = promiseFinally(
1786 this.$truss.peek(target, callback), function () {_.pull(this$1.$$finalizers, promise.cancel);}
1787 );
1788 this.$$finalizers.push(promise.cancel);
1789 return promise;
1790};
1791
1792BaseValue.prototype.$observe = function $observe (subjectFn, callbackFn, options) {
1793 var this$1 = this;
1794
1795 if (this.$destroyed) { throw new Error('Object already destroyed'); }
1796 var unobserveAndRemoveFinalizer;
1797
1798 var unobserve = this.$truss.observe(function () {
1799 this$1.$$touchThis();
1800 return subjectFn.call(this$1);
1801 }, callbackFn.bind(this), options);
1802
1803 unobserveAndRemoveFinalizer = function () {// eslint-disable-line prefer-const
1804 unobserve();
1805 _.pull(this$1.$$finalizers, unobserveAndRemoveFinalizer);
1806 };
1807 this.$$finalizers.push(unobserveAndRemoveFinalizer);
1808 return unobserveAndRemoveFinalizer;
1809};
1810
1811BaseValue.prototype.$when = function $when (expression, options) {
1812 var this$1 = this;
1813
1814 if (this.$destroyed) { throw new Error('Object already destroyed'); }
1815 var promise = this.$truss.when(function () {
1816 this$1.$$touchThis();
1817 return expression.call(this$1);
1818 }, options);
1819 promiseFinally(promise, function () {_.pull(this$1.$$finalizers, promise.cancel);});
1820 this.$$finalizers.push(promise.cancel);
1821 return promise;
1822};
1823
1824prototypeAccessors$6.$$finalizers.get = function () {
1825 Object.defineProperty(this, '$$finalizers', {
1826 value: [], writable: false, enumerable: false, configurable: false});
1827 return this.$$finalizers;
1828};
1829
1830Object.defineProperties( BaseValue.prototype, prototypeAccessors$6 );
1831
1832
1833var Value = function Value () {};
1834
1835var prototypeAccessors$1$2 = { $parent: { configurable: true },$path: { configurable: true },$truss: { configurable: true },$ref: { configurable: true },$refs: { configurable: true },$key: { configurable: true },$data: { configurable: true },$hidden: { configurable: true },$empty: { configurable: true },$keys: { configurable: true },$values: { configurable: true },$ready: { configurable: true },$overridden: { configurable: true },$$initializers: { configurable: true },$destroyed: { configurable: true } };
1836
1837prototypeAccessors$1$2.$parent.get = function () {return creatingObjectProperties.$parent.value;};
1838prototypeAccessors$1$2.$path.get = function () {return creatingObjectProperties.$path.value;};
1839prototypeAccessors$1$2.$truss.get = function () {
1840 Object.defineProperty(this, '$truss', {value: this.$parent.$truss});
1841 return this.$truss;
1842};
1843prototypeAccessors$1$2.$ref.get = function () {
1844 Object.defineProperty(this, '$ref', {value: new Reference(this.$truss._tree, this.$path)});
1845 return this.$ref;
1846};
1847prototypeAccessors$1$2.$refs.get = function () {return this.$ref;};
1848prototypeAccessors$1$2.$key.get = function () {
1849 Object.defineProperty(
1850 this, '$key', {value: unescapeKey(this.$path.slice(this.$path.lastIndexOf('/') + 1))});
1851 return this.$key;
1852};
1853prototypeAccessors$1$2.$data.get = function () {return this;};
1854prototypeAccessors$1$2.$hidden.get = function () {return false;};// eslint-disable-line lodash/prefer-constant
1855prototypeAccessors$1$2.$empty.get = function () {return _.isEmpty(this.$data);};
1856prototypeAccessors$1$2.$keys.get = function () {return _.keys(this.$data);};
1857prototypeAccessors$1$2.$values.get = function () {return _.values(this.$data);};
1858prototypeAccessors$1$2.$ready.get = function () {return this.$ref.ready;};
1859prototypeAccessors$1$2.$overridden.get = function () {return false;};// eslint-disable-line lodash/prefer-constant
1860
1861Value.prototype.$nextTick = function $nextTick () {
1862 var this$1 = this;
1863
1864 if (this.$destroyed) { throw new Error('Object already destroyed'); }
1865 var promise = this.$truss.nextTick();
1866 promiseFinally(promise, function () {_.pull(this$1.$$finalizers, promise.cancel);});
1867 this.$$finalizers.push(promise.cancel);
1868 return promise;
1869};
1870
1871Value.prototype.$freezeComputedProperty = function $freezeComputedProperty () {
1872 if (!_.isBoolean(currentPropertyFrozen)) {
1873 throw new Error('Cannot freeze a computed property outside of its getter function');
1874 }
1875 currentPropertyFrozen = true;
1876};
1877
1878Value.prototype.$set = function $set (value) {return this.$ref.set(value);};
1879Value.prototype.$update = function $update (values) {return this.$ref.update(values);};
1880Value.prototype.$override = function $override (values) {return this.$ref.override(values);};
1881Value.prototype.$commit = function $commit (options, updateFn) {return this.$ref.commit(options, updateFn);};
1882
1883Value.prototype.$$touchThis = function $$touchThis () {
1884 /* eslint-disable no-unused-expressions */
1885 if (this.__ob__) {
1886 this.__ob__.dep.depend();
1887 } else if (this.$parent) {
1888 (this.$parent.hasOwnProperty('$data') ? this.$parent.$data : this.$parent)[this.$key];
1889 } else {
1890 this.$store;
1891 }
1892 /* eslint-enable no-unused-expressions */
1893};
1894
1895prototypeAccessors$1$2.$$initializers.get = function () {
1896 Object.defineProperty(this, '$$initializers', {
1897 value: [], writable: false, enumerable: false, configurable: true});
1898 return this.$$initializers;
1899};
1900
1901prototypeAccessors$1$2.$destroyed.get = function () {// eslint-disable-line lodash/prefer-constant
1902 return false;
1903};
1904
1905Object.defineProperties( Value.prototype, prototypeAccessors$1$2 );
1906
1907copyPrototype(BaseValue, Value);
1908
1909_.forEach(Value.prototype, function (prop, name) {
1910 Object.defineProperty(
1911 Value.prototype, name, {value: prop, enumerable: false, configurable: false, writable: false});
1912});
1913
1914
1915var ErrorWrapper = function ErrorWrapper(error) {
1916 this.error = error;
1917};
1918
1919
1920var FrozenWrapper = function FrozenWrapper(value) {
1921 this.value = value;
1922};
1923
1924
1925var Modeler = function Modeler(debug) {
1926 this._trie = {Class: Value};
1927 this._debug = debug;
1928 Object.freeze(this);
1929};
1930
1931Modeler.prototype.init = function init (classes, rootAcceptable) {
1932 var this$1 = this;
1933
1934 if (_.isPlainObject(classes)) {
1935 _.forEach(classes, function (Class, path) {
1936 if (Class.$trussMount) { return; }
1937 Class.$$trussMount = Class.$$trussMount || [];
1938 Class.$$trussMount.push(path);
1939 });
1940 classes = _.values(classes);
1941 _.forEach(classes, function (Class) {
1942 if (!Class.$trussMount && Class.$$trussMount) {
1943 Class.$trussMount = Class.$$trussMount;
1944 delete Class.$$trussMount;
1945 }
1946 });
1947 }
1948 classes = _.uniq(classes);
1949 _.forEach(classes, function (Class) { return this$1._mountClass(Class, rootAcceptable); });
1950 this._decorateTrie(this._trie);
1951};
1952
1953Modeler.prototype.destroy = function destroy () {// eslint-disable-line no-empty-function
1954};
1955
1956Modeler.prototype._getMount = function _getMount (path, scaffold, predicate) {
1957 var segments = splitPath(path, true);
1958 var node;
1959 for (var i = 0, list = segments; i < list.length; i += 1) {
1960 var segment = list[i];
1961
1962 var child = segment ?
1963 node.children && (node.children[segment] || !scaffold && node.children.$) : this._trie;
1964 if (!child) {
1965 if (!scaffold) { return; }
1966 node.children = node.children || {};
1967 child = node.children[segment] = {Class: Value};
1968 }
1969 node = child;
1970 if (predicate && predicate(node)) { break; }
1971 }
1972 return node;
1973};
1974
1975Modeler.prototype._findMount = function _findMount (predicate, node) {
1976 if (!node) { node = this._trie; }
1977 if (predicate(node)) { return node; }
1978 for (var i = 0, list = _.keys(node.children); i < list.length; i += 1) {
1979 var childKey = list[i];
1980
1981 var result = this._findMount(predicate, node.children[childKey]);
1982 if (result) { return result; }
1983 }
1984};
1985
1986Modeler.prototype._decorateTrie = function _decorateTrie (node) {
1987 var this$1 = this;
1988
1989 _.forEach(node.children, function (child) {
1990 this$1._decorateTrie(child);
1991 if (child.local || child.localDescendants) { node.localDescendants = true; }
1992 });
1993};
1994
1995Modeler.prototype._augmentClass = function _augmentClass (Class) {
1996 var computedProperties;
1997 var proto = Class.prototype;
1998 while (proto && proto.constructor !== Object) {
1999 for (var i = 0, list = Object.getOwnPropertyNames(proto); i < list.length; i += 1) {
2000 var name = list[i];
2001
2002 var descriptor = Object.getOwnPropertyDescriptor(proto, name);
2003 if (name.charAt(0) === '$') {
2004 if (name === '$finalize') { continue; }
2005 if (_.isEqual(descriptor, Object.getOwnPropertyDescriptor(Value.prototype, name))) {
2006 continue;
2007 }
2008 throw new Error(("Property names starting with \"$\" are reserved: " + (Class.name) + "." + name));
2009 }
2010 if (descriptor.get && !(computedProperties && computedProperties[name])) {
2011 (computedProperties || (computedProperties = {}))[name] = {
2012 name: name, fullName: ((proto.constructor.name) + "." + name), get: descriptor.get,
2013 set: descriptor.set
2014 };
2015 }
2016 }
2017 proto = Object.getPrototypeOf(proto);
2018 }
2019 for (var i$1 = 0, list$1 = Object.getOwnPropertyNames(Value.prototype); i$1 < list$1.length; i$1 += 1) {
2020 var name$1 = list$1[i$1];
2021
2022 if (name$1 === 'constructor' || Class.prototype.hasOwnProperty(name$1)) { continue; }
2023 Object.defineProperty(
2024 Class.prototype, name$1, Object.getOwnPropertyDescriptor(Value.prototype, name$1));
2025 }
2026 return computedProperties;
2027};
2028
2029Modeler.prototype._mountClass = function _mountClass (Class, rootAcceptable) {
2030 var this$1 = this;
2031
2032 var computedProperties = this._augmentClass(Class);
2033 var allVariables = [];
2034 var mounts = Class.$trussMount;
2035 if (!mounts) { throw new Error(("Class " + (Class.name) + " lacks a $trussMount static property")); }
2036 if (!_.isArray(mounts)) { mounts = [mounts]; }
2037 _.forEach(mounts, function (mount) {
2038 if (_.isString(mount)) { mount = {path: mount}; }
2039 if (!rootAcceptable && mount.path === '/') {
2040 throw new Error('Data root already accessed, too late to mount class');
2041 }
2042 var matcher = makePathMatcher(mount.path);
2043 for (var i = 0, list = matcher.variables; i < list.length; i += 1) {
2044 var variable = list[i];
2045
2046 if (variable === '$' || variable.charAt(1) === '$') {
2047 throw new Error(("Invalid variable name: " + variable));
2048 }
2049 if (variable.charAt(0) === '$' && (
2050 _.has(Value.prototype, variable) || RESERVED_VALUE_PROPERTY_NAMES[variable]
2051 )) {
2052 throw new Error(("Variable name conflicts with built-in property or method: " + variable));
2053 }
2054 allVariables.push(variable);
2055 }
2056 var escapedKey = mount.path.match(/\/([^/]*)$/)[1];
2057 if (escapedKey.charAt(0) === '$') {
2058 if (mount.placeholder) {
2059 throw new Error(
2060 ("Class " + (Class.name) + " mounted at wildcard " + escapedKey + " cannot be a placeholder"));
2061 }
2062 } else if (!_.has(mount, 'placeholder')) {
2063 mount.placeholder = {};
2064 }
2065 var targetMount = this$1._getMount(mount.path.replace(/\$[^/]*/g, '$'), true);
2066 if (targetMount.matcher && (
2067 targetMount.escapedKey === escapedKey ||
2068 targetMount.escapedKey.charAt(0) === '$' && escapedKey.charAt(0) === '$'
2069 )) {
2070 throw new Error(
2071 ("Multiple classes mounted at " + (mount.path) + ": " + (targetMount.Class.name) + ", " + (Class.name)));
2072 }
2073 _.assign(
2074 targetMount, {Class: Class, matcher: matcher, computedProperties: computedProperties, escapedKey: escapedKey},
2075 _.pick(mount, 'placeholder', 'local', 'keysUnsafe', 'hidden'));
2076 });
2077 _(allVariables).uniq().forEach(function (variable) {
2078 Object.defineProperty(Class.prototype, variable, {get: function get() {
2079 return creatingObjectProperties ?
2080 creatingObjectProperties[variable] && creatingObjectProperties[variable].value :
2081 undefined;
2082 }});
2083 });
2084};
2085
2086/**
2087 * Creates a Truss object and sets all its basic properties: path segment variables, user-defined
2088 * properties, and computed properties.The latter two will be enumerable so that Vue will pick
2089 * them up and make the reactive.
2090 */
2091Modeler.prototype.createObject = function createObject (path, properties) {
2092 var this$1 = this;
2093
2094 var mount = this._getMount(path) || {Class: Value};
2095 try {
2096 if (mount.matcher) {
2097 var match = mount.matcher.match(path);
2098 for (var variable in match) {
2099 properties[variable] = {value: match[variable]};
2100 }
2101 }
2102
2103 creatingObjectProperties = properties;
2104 var object = new mount.Class();
2105 creatingObjectProperties = null;
2106
2107 if (angularProxy.active) { this._wrapProperties(object); }
2108
2109 if (mount.keysUnsafe) {
2110 properties.$data = {value: Object.create(null), configurable: true, enumerable: true};
2111 }
2112 if (mount.hidden) { properties.$hidden = {value: true}; }
2113 if (mount.computedProperties) {
2114 _.forEach(mount.computedProperties, function (prop) {
2115 properties[prop.name] = this$1._buildComputedPropertyDescriptor(object, prop);
2116 });
2117 }
2118
2119 return object;
2120 } catch (e) {
2121 e.extra = _.assign({mount: mount, properties: properties, className: mount.Class && mount.Class.name}, e.extra);
2122 throw e;
2123 }
2124};
2125
2126Modeler.prototype._wrapProperties = function _wrapProperties (object) {
2127 _.forEach(object, function (value, key) {
2128 var obj;
2129
2130 var valueKey = '$_' + key;
2131 Object.defineProperties(object, ( obj = {}, obj[valueKey] = {value: value, writable: true}, obj[key] = {
2132 get: function () { return object[valueKey]; },
2133 set: function (arg) {object[valueKey] = arg; angularProxy.digest();},
2134 enumerable: true, configurable: true
2135 }, obj ));
2136 });
2137};
2138
2139Modeler.prototype._buildComputedPropertyDescriptor = function _buildComputedPropertyDescriptor (object, prop) {
2140 var this$1 = this;
2141
2142 var propertyStats = stats.for(prop.fullName);
2143
2144 var value, pendingPromise;
2145 var writeAllowed = false;
2146
2147 object.$$initializers.push(function (vue) {
2148 var unwatchNow = false;
2149 var compute = computeValue.bind(object, prop, propertyStats);
2150 if (this$1._debug) { compute.toString = function () {return prop.fullName;}; }
2151 var unwatch = function () {unwatchNow = true;};
2152 unwatch = vue.$watch(compute, function (newValue) {
2153 if (object.$destroyed) {
2154 unwatch();
2155 return;
2156 }
2157 if (pendingPromise) {
2158 if (pendingPromise.cancel) { pendingPromise.cancel(); }
2159 pendingPromise = undefined;
2160 }
2161 if (_.isObject(newValue) && _.isFunction(newValue.then)) {
2162 var promise = newValue.then(function (finalValue) {
2163 if (promise === pendingPromise) { update(finalValue); }
2164 // No need to angular.digest() here, since if we're running under Angular then we expect
2165 // promises to be aliased to its $q service, which triggers digest itself.
2166 }, function (error) {
2167 if (promise === pendingPromise && update(new ErrorWrapper(error)) &&
2168 !error.trussExpectedException) { throw error; }
2169 });
2170 pendingPromise = promise;
2171 } else if (update(newValue)) {
2172 angularProxy.digest();
2173 if (newValue instanceof ErrorWrapper && !newValue.error.trussExpectedException) {
2174 throw newValue.error;
2175 }
2176 }
2177 }, {immediate: true});// use immediate:true since watcher will run computeValue anyway
2178 // Hack to change order of computed property watchers.By flipping their ids to be negative,
2179 // we ensure that they will settle before all other watchers, and also that children
2180 // properties will settle before their parents since values are often aggregated upwards.
2181 var watcher = _.last(vue._watchers || vue._scope.effects);
2182 watcher.id = -watcher.id;
2183
2184 function update(newValue) {
2185 if (newValue instanceof FrozenWrapper) {
2186 newValue = newValue.value;
2187 unwatch();
2188 _.pull(object.$$finalizers, unwatch);
2189 }
2190 if (isTrussEqual(value, newValue)) { return; }
2191 // console.log('updating', object.$key, prop.fullName, 'from', value, 'to', newValue);
2192 propertyStats.numUpdates += 1;
2193 writeAllowed = true;
2194 object[prop.name] = newValue;
2195 writeAllowed = false;
2196 // Freeze the computed value so it can't be accidentally modified by a third party.Ideally
2197 // we'd freeze it before setting it so that Vue wouldn't instrument the object recursively
2198 // (since it can't change anyway), but we actually need the instrumentation in case a client
2199 // tries to access an inexistent property off a computed pointer to an unfrozen value (e.g.,
2200 // a $truss-ified object).When instrumented, Vue will add a dependency on the unfrozen
2201 // value in case the property is later added.If uninstrumented, the dependency won't be
2202 // added and we won't be notified.And Vue only instruments extensible objects...
2203 freeze(newValue);
2204 return true;
2205 }
2206
2207 if (unwatchNow) {
2208 unwatch();
2209 } else {
2210 object.$$finalizers.push(unwatch);
2211 }
2212 });
2213 return {
2214 enumerable: true, configurable: true,
2215 get: function get() {
2216 if (!writeAllowed && value instanceof ErrorWrapper) { throw value.error; }
2217 return value;
2218 },
2219 set: function set(newValue) {
2220 if (writeAllowed) {
2221 value = newValue;
2222 } else if (prop.set) {
2223 prop.set.call(this, newValue);
2224 } else {
2225 throw new Error(("You cannot set a computed property: " + (prop.name)));
2226 }
2227 }
2228 };
2229};
2230
2231Modeler.prototype.destroyObject = function destroyObject (object) {
2232 if (_.has(object, '$$finalizers')) {
2233 // Some finalizers remove themselves from the array, so clone it before iterating.
2234 for (var i = 0, list = _.clone(object.$$finalizers); i < list.length; i += 1) {
2235 var fn = list[i];
2236
2237 fn();
2238 }
2239 }
2240 if (_.isFunction(object.$finalize)) { object.$finalize(); }
2241 Object.defineProperty(
2242 object, '$destroyed', {value: true, enumerable: false, configurable: false});
2243};
2244
2245Modeler.prototype.isPlaceholder = function isPlaceholder (path) {
2246 var mount = this._getMount(path);
2247 return mount && mount.placeholder;
2248};
2249
2250Modeler.prototype.isLocal = function isLocal (path, value) {
2251 // eslint-disable-next-line no-shadow
2252 var mount = this._getMount(path, false, function (mount) { return mount.local; });
2253 if (mount && mount.local) { return true; }
2254 if (this._hasLocalProperties(mount, value)) {
2255 throw new Error('Write on a mix of local and remote tree paths.');
2256 }
2257 return false;
2258};
2259
2260Modeler.prototype._hasLocalProperties = function _hasLocalProperties (mount, value) {
2261 if (!mount) { return false; }
2262 if (mount.local) { return true; }
2263 if (!mount.localDescendants || !_.isObject(value)) { return false; }
2264 for (var key in value) {
2265 var local =
2266 this._hasLocalProperties(mount.children[escapeKey(key)] || mount.children.$, value[key]);
2267 if (local) { return true; }
2268 }
2269 return false;
2270};
2271
2272Modeler.prototype.forEachPlaceholderChild = function forEachPlaceholderChild (path, iteratee) {
2273 var mount = this._getMount(path);
2274 _.forEach(mount && mount.children, function (child) {
2275 if (child.placeholder) { iteratee(child); }
2276 });
2277};
2278
2279Modeler.prototype.checkVueObject = function checkVueObject (object, path, checkedObjects) {
2280 var top = !checkedObjects;
2281 if (top) { checkedObjects = []; }
2282 try {
2283 for (var i = 0, list = Object.getOwnPropertyNames(object); i < list.length; i += 1) {
2284 var key = list[i];
2285
2286 if (RESERVED_VALUE_PROPERTY_NAMES[key] || Value.prototype.hasOwnProperty(key) ||
2287 /^\$_/.test(key)) { continue; }
2288 // eslint-disable-next-line no-shadow
2289 var mount = this._findMount(function (mount) { return mount.Class === object.constructor; });
2290 if (mount && mount.matcher && _.includes(mount.matcher.variables, key)) { continue; }
2291 var value = (void 0);
2292 try {
2293 value = object[key];
2294 } catch (e) {
2295 // Ignore any values that hold exceptions, or otherwise throw on access -- we won't be
2296 // able to check them anyway.
2297 continue;
2298 }
2299 if (!(_.isArray(object) && (/\d+/.test(key) || key === 'length'))) {
2300 var descriptor = Object.getOwnPropertyDescriptor(object, key);
2301 if ('value' in descriptor || !descriptor.get) {
2302 throw new Error(
2303 ("Value at " + path + ", contained in a Firetruss object, has a rogue property: " + key));
2304 }
2305 if (object.$truss && descriptor.enumerable) {
2306 try {
2307 object[key] = value;
2308 throw new Error(
2309 ("Firetruss object at " + path + " has an enumerable non-Firebase property: " + key));
2310 } catch (e$1) {
2311 if (e$1.trussCode !== 'firebase_overwrite') { throw e$1; }
2312 }
2313 }
2314 }
2315 if (_.isObject(value) && !value.$$$trussCheck && Object.isExtensible(value) &&
2316 !(_.isFunction(value) || value instanceof Promise)) {
2317 value.$$$trussCheck = true;
2318 checkedObjects.push(value);
2319 this.checkVueObject(value, joinPath(path, escapeKey(key)), checkedObjects);
2320 }
2321 }
2322 } finally {
2323 if (top) {
2324 for (var i$1 = 0, list$1 = checkedObjects; i$1 < list$1.length; i$1 += 1) {
2325 var item = list$1[i$1];
2326
2327 delete item.$$$trussCheck;
2328 }
2329 }
2330 }
2331};
2332
2333
2334function computeValue(prop, propertyStats) {
2335 /* eslint-disable no-invalid-this */
2336 if (this.$destroyed) { return; }
2337 // Touch this object, since a failed access to a missing property doesn't get captured as a
2338 // dependency.
2339 this.$$touchThis();
2340
2341 var oldPropertyFrozen = currentPropertyFrozen;
2342 currentPropertyFrozen = false;
2343 var startTime = performanceNow();
2344 var value;
2345 try {
2346 try {
2347 value = prop.get.call(this);
2348 } catch (e) {
2349 value = new ErrorWrapper(e);
2350 } finally {
2351 propertyStats.runtime += performanceNow() - startTime;
2352 propertyStats.numRecomputes += 1;
2353 }
2354 if (currentPropertyFrozen) { value = new FrozenWrapper(value); }
2355 return value;
2356 } finally {
2357 currentPropertyFrozen = oldPropertyFrozen;
2358 }
2359 /* eslint-enable no-invalid-this */
2360}
2361
2362function wrapConnections(object, connections) {
2363 if (!connections || connections instanceof Handle) { return connections; }
2364 return _.mapValues(connections, function (descriptor) {
2365 if (descriptor instanceof Handle) { return descriptor; }
2366 if (_.isFunction(descriptor)) {
2367 var fn = function() {
2368 /* eslint-disable no-invalid-this */
2369 object.$$touchThis();
2370 return wrapConnections(object, descriptor.call(this));
2371 /* eslint-enable no-invalid-this */
2372 };
2373 fn.angularWatchSuppressed = true;
2374 return fn;
2375 }
2376 return wrapConnections(object, descriptor);
2377 });
2378}
2379
2380function freeze(object) {
2381 if (_.isNil(object) || !_.isObject(object) || Object.isFrozen(object) || object.$truss) {
2382 return object;
2383 }
2384 object = Object.freeze(object);
2385 if (_.isArray(object)) { return _.map(object, function (value) { return freeze(value); }); }
2386 return _.mapValues(object, function (value) { return freeze(value); });
2387}
2388
2389var QueryHandler = function QueryHandler(coupler, query) {
2390 this._coupler = coupler;
2391 this._query = query;
2392 this._listeners = [];
2393 this._keys = [];
2394 this._url = this._coupler._rootUrl + query.path;
2395 this._segments = splitPath(query.path, true);
2396 this._listening = false;
2397 this.ready = false;
2398};
2399
2400QueryHandler.prototype.attach = function attach (operation, keysCallback) {
2401 this._listen();
2402 this._listeners.push({operation: operation, keysCallback: keysCallback});
2403 if (this.ready) {
2404 this._coupler._dispatcher.markReady(operation);
2405 if (keysCallback) { keysCallback(this._keys); }
2406 }
2407};
2408
2409QueryHandler.prototype.detach = function detach (operation) {
2410 var k = _.findIndex(this._listeners, {operation: operation});
2411 if (k >= 0) { this._listeners.splice(k, 1); }
2412 return this._listeners.length;
2413};
2414
2415QueryHandler.prototype._listen = function _listen () {
2416 if (this._listening) { return; }
2417 this._coupler._bridge.on(
2418 this._query.toString(), this._url, this._query.constraints, 'value',
2419 this._handleSnapshot, this._handleError, this, {sync: true});
2420 this._listening = true;
2421};
2422
2423QueryHandler.prototype.destroy = function destroy () {
2424 this._coupler._bridge.off(
2425 this._query.toString(), this._url, this._query.constraints, 'value', this._handleSnapshot,
2426 this);
2427 this._listening = false;
2428 this.ready = false;
2429 angularProxy.digest();
2430 for (var i = 0, list = this._keys; i < list.length; i += 1) {
2431 var key = list[i];
2432
2433 this._coupler._decoupleSegments(this._segments.concat(key));
2434 }
2435};
2436
2437QueryHandler.prototype._handleSnapshot = function _handleSnapshot (snap) {
2438 var this$1 = this;
2439
2440 this._coupler._queueSnapshotCallback(function () {
2441 // Order is important here: first couple any new subpaths so _handleSnapshot will update the
2442 // tree, then tell the client to update its keys, pulling values from the tree.
2443 if (!this$1._listeners.length || !this$1._listening) { return; }
2444 var updatedKeys = this$1._updateKeysAndApplySnapshot(snap);
2445 if (!this$1.ready) {
2446 this$1.ready = true;
2447 angularProxy.digest();
2448 for (var i = 0, list = this$1._listeners; i < list.length; i += 1) {
2449 var listener = list[i];
2450
2451 this$1._coupler._dispatcher.markReady(listener.operation);
2452 }
2453 }
2454 if (updatedKeys) {
2455 for (var i$1 = 0, list$1 = this$1._listeners; i$1 < list$1.length; i$1 += 1) {
2456 var listener$1 = list$1[i$1];
2457
2458 if (listener$1.keysCallback) { listener$1.keysCallback(updatedKeys); }
2459 }
2460 }
2461 });
2462};
2463
2464QueryHandler.prototype._updateKeysAndApplySnapshot = function _updateKeysAndApplySnapshot (snap) {
2465 var updatedKeys;
2466 if (snap.path === this._query.path) {
2467 updatedKeys = _.keys(snap.value);
2468 updatedKeys.sort();
2469 if (_.isEqual(this._keys, updatedKeys)) {
2470 updatedKeys = null;
2471 } else {
2472 for (var i = 0, list = _.difference(updatedKeys, this._keys); i < list.length; i += 1) {
2473 var key = list[i];
2474
2475 this._coupler._coupleSegments(this._segments.concat(key));
2476 }
2477 for (var i$1 = 0, list$1 = _.difference(this._keys, updatedKeys); i$1 < list$1.length; i$1 += 1) {
2478 // Decoupling a segment will prune the tree at that location is there are no other
2479 // listeners.
2480 var key$1 = list$1[i$1];
2481
2482 this._coupler._decoupleSegments(this._segments.concat(key$1));
2483 }
2484 this._keys = updatedKeys;
2485 }
2486 // The snapshot may be partial, so create synthetic snapshots for subpaths and apply those to
2487 // update / insert values.(Deleted ones got pruned above.)
2488 if (snap.exists) {
2489 var rootValue = snap.value;
2490 var rootPath = snap.path;
2491 for (var i$2 = 0, list$2 = this._keys; i$2 < list$2.length; i$2 += 1) {
2492 var key$2 = list$2[i$2];
2493
2494 snap._path = rootPath + '/' + key$2;
2495 snap._key = undefined;
2496 snap._value = rootValue[key$2];
2497 this._coupler._applySnapshot(snap);
2498 }
2499 // Restore original properties, just in case.
2500 snap._path = rootPath;
2501 snap._key = undefined;
2502 snap._value = rootValue;
2503 }
2504 } else if (snap.path.replace(/\/[^/]+/, '') === this._query.path) {
2505 var hasKey = _.includes(this._keys, snap.key);
2506 if (snap.value) {
2507 if (!hasKey) {
2508 this._coupler._coupleSegments(this._segments.concat(snap.key));
2509 this._keys.push(snap.key);
2510 this._keys.sort();
2511 updatedKeys = this._keys;
2512 }
2513 } else if (hasKey) {
2514 this._coupler._decoupleSegments(this._segments.concat(snap.key));
2515 _.pull(this._keys, snap.key);
2516 this._keys.sort();
2517 updatedKeys = this._keys;
2518 }
2519 // A snapshot under the query's level is guaranteed to be a full snapshot, so we can apply it
2520 // directly.
2521 this._coupler._applySnapshot(snap);
2522 }
2523 return updatedKeys;
2524};
2525
2526QueryHandler.prototype._handleError = function _handleError (error) {
2527 var this$1 = this;
2528
2529 if (!this._listeners.length || !this._listening) { return; }
2530 this._listening = false;
2531 this.ready = false;
2532 angularProxy.digest();
2533 Promise.all(_.map(this._listeners, function (listener) {
2534 this$1._coupler._dispatcher.clearReady(listener.operation);
2535 return this$1._coupler._dispatcher.retry(listener.operation, error).catch(function (e) {
2536 listener.operation._disconnect(e);
2537 return false;
2538 });
2539 })).then(function (results) {
2540 if (_.some(results)) {
2541 if (this$1._listeners.length) { this$1._listen(); }
2542 } else {
2543 for (var i = 0, list = this$1._listeners; i < list.length; i += 1) {
2544 var listener = list[i];
2545
2546 listener.operation._disconnect(error);
2547 }
2548 }
2549 });
2550};
2551
2552
2553var Node = function Node(coupler, path, parent) {
2554 this._coupler = coupler;
2555 this.path = path;
2556 this.parent = parent;
2557 this.url = this._coupler._rootUrl + path;
2558 this.operations = [];
2559 this.queryCount = 0;
2560 this.listening = false;
2561 this.ready = false;
2562 this.children = {};
2563};
2564
2565var prototypeAccessors$7 = { active: { configurable: true },count: { configurable: true } };
2566
2567prototypeAccessors$7.active.get = function () {
2568 return this.count || this.queryCount;
2569};
2570
2571prototypeAccessors$7.count.get = function () {
2572 return this.operations.length;
2573};
2574
2575Node.prototype.listen = function listen (skip) {
2576 var this$1 = this;
2577
2578 if (!skip && this.count) {
2579 if (this.listening) { return; }
2580 _.forEach(this.operations, function (op) {this$1._coupler._dispatcher.clearReady(op);});
2581 this._coupler._bridge.on(
2582 this.url, this.url, null, 'value', this._handleSnapshot, this._handleError, this,
2583 {sync: true});
2584 this.listening = true;
2585 } else {
2586 _.forEach(this.children, function (child) {child.listen();});
2587 }
2588};
2589
2590Node.prototype.unlisten = function unlisten (skip) {
2591 if (!skip && this.listening) {
2592 this._coupler._bridge.off(this.url, this.url, null, 'value', this._handleSnapshot, this);
2593 this.listening = false;
2594 this._forAllDescendants(function (node) {
2595 if (node.listening) { return false; }
2596 if (node.ready) {
2597 node.ready = false;
2598 angularProxy.digest();
2599 }
2600 });
2601 } else {
2602 _.forEach(this.children, function (child) {child.unlisten();});
2603 }
2604};
2605
2606Node.prototype._handleSnapshot = function _handleSnapshot (snap) {
2607 var this$1 = this;
2608
2609 this._coupler._queueSnapshotCallback(function () {
2610 if (!this$1.listening || !this$1._coupler.isTrunkCoupled(snap.path)) { return; }
2611 this$1._coupler._applySnapshot(snap);
2612 if (!this$1.ready && snap.path === this$1.path) {
2613 this$1.ready = true;
2614 angularProxy.digest();
2615 this$1.unlisten(true);
2616 this$1._forAllDescendants(function (node) {
2617 for (var i = 0, list = node.operations; i < list.length; i += 1) {
2618 var op = list[i];
2619
2620 this$1._coupler._dispatcher.markReady(op);
2621 }
2622 });
2623 }
2624 });
2625};
2626
2627Node.prototype._handleError = function _handleError (error) {
2628 var this$1 = this;
2629
2630 if (!this.count || !this.listening) { return; }
2631 this.listening = false;
2632 this._forAllDescendants(function (node) {
2633 if (node.listening) { return false; }
2634 if (node.ready) {
2635 node.ready = false;
2636 angularProxy.digest();
2637 }
2638 for (var i = 0, list = node.operations; i < list.length; i += 1) {
2639 var op = list[i];
2640
2641 this$1._coupler._dispatcher.clearReady(op);
2642 }
2643 });
2644 return Promise.all(_.map(this.operations, function (op) {
2645 return this$1._coupler._dispatcher.retry(op, error).catch(function (e) {
2646 op._disconnect(e);
2647 return false;
2648 });
2649 })).then(function (results) {
2650 if (_.some(results)) {
2651 if (this$1.count) { this$1.listen(); }
2652 } else {
2653 for (var i = 0, list = this$1.operations; i < list.length; i += 1) {
2654 var op = list[i];
2655
2656 op._disconnect(error);
2657 }
2658 // Pulling all the operations will automatically get us listening on descendants.
2659 }
2660 });
2661};
2662
2663Node.prototype._forAllDescendants = function _forAllDescendants (iteratee) {
2664 if (iteratee(this) === false) { return; }
2665 _.forEach(this.children, function (child) { return child._forAllDescendants(iteratee); });
2666};
2667
2668Node.prototype.collectCoupledDescendantPaths = function collectCoupledDescendantPaths (paths) {
2669 if (!paths) { paths = {}; }
2670 paths[this.path] = this.active;
2671 if (!this.active) {
2672 _.forEach(this.children, function (child) {child.collectCoupledDescendantPaths(paths);});
2673 }
2674 return paths;
2675};
2676
2677Object.defineProperties( Node.prototype, prototypeAccessors$7 );
2678
2679
2680var Coupler = function Coupler(rootUrl, bridge, dispatcher, applySnapshot, prunePath) {
2681 this._rootUrl = rootUrl;
2682 this._bridge = bridge;
2683 this._dispatcher = dispatcher;
2684 this._applySnapshot = applySnapshot;
2685 this._pendingSnapshotCallbacks = [];
2686 this._throttled = {processPendingSnapshots: this._processPendingSnapshots};
2687 this._prunePath = prunePath;
2688 this._vue = new Vue({data: {root: undefined, queryHandlers: {}}});
2689 // Prevent Vue from instrumenting rendering since there's actually nothing to render, and the
2690 // warnings cause false positives from Lodash primitives when running tests.
2691 this._vue._renderProxy = this._vue;
2692 this._nodeIndex = Object.create(null);
2693 Object.freeze(this);
2694 // Set root node after freezing Coupler, otherwise it gets vue-ified too.
2695 this._vue.$data.root = new Node(this, '/');
2696 this._nodeIndex['/'] = this._root;
2697};
2698
2699var prototypeAccessors$1$3 = { _root: { configurable: true },_queryHandlers: { configurable: true } };
2700
2701prototypeAccessors$1$3._root.get = function () {
2702 return this._vue.$data.root;
2703};
2704
2705prototypeAccessors$1$3._queryHandlers.get = function () {
2706 return this._vue.$data.queryHandlers;
2707};
2708
2709Coupler.prototype.destroy = function destroy () {
2710 _.forEach(this._queryHandlers, function (queryHandler) {queryHandler.destroy();});
2711 this._root.unlisten();
2712 this._vue.$destroy();
2713};
2714
2715Coupler.prototype.couple = function couple (path, operation) {
2716 return this._coupleSegments(splitPath(path, true), operation);
2717};
2718
2719Coupler.prototype._coupleSegments = function _coupleSegments (segments, operation) {
2720 var node;
2721 var superseded = !operation;
2722 var ready = false;
2723 for (var i = 0, list = segments; i < list.length; i += 1) {
2724 var segment = list[i];
2725
2726 var child = segment ? node.children && node.children[segment] : this._root;
2727 if (!child) {
2728 child = new Node(this, ((node.path === '/' ? '' : node.path) + "/" + segment), node);
2729 Vue.set(node.children, segment, child);
2730 this._nodeIndex[child.path] = child;
2731 }
2732 superseded = superseded || child.listening;
2733 ready = ready || child.ready;
2734 node = child;
2735 }
2736 if (operation) {
2737 node.operations.push(operation);
2738 } else {
2739 node.queryCount++;
2740 }
2741 if (superseded) {
2742 if (operation && ready) { this._dispatcher.markReady(operation); }
2743 } else {
2744 node.listen();// node will call unlisten() on descendants when ready
2745 }
2746};
2747
2748Coupler.prototype.decouple = function decouple (path, operation) {
2749 return this._decoupleSegments(splitPath(path, true), operation);
2750};
2751
2752Coupler.prototype._decoupleSegments = function _decoupleSegments (segments, operation) {
2753 var ancestors = [];
2754 var node;
2755 for (var i$1 = 0, list = segments; i$1 < list.length; i$1 += 1) {
2756 var segment = list[i$1];
2757
2758 node = segment ? node.children && node.children[segment] : this._root;
2759 if (!node) { break; }
2760 ancestors.push(node);
2761 }
2762 if (!node || !(operation ? node.count : node.queryCount)) {
2763 throw new Error(("Path not coupled: " + (segments.join('/') || '/')));
2764 }
2765 if (operation) {
2766 _.pull(node.operations, operation);
2767 } else {
2768 node.queryCount--;
2769 }
2770 if (operation && !node.count) {
2771 // Ideally, we wouldn't resync the full values here since we probably already have the current
2772 // value for all children.But making sure that's true is tricky in an async system (what if
2773 // the node's value changes and the update crosses the 'off' call in transit?) and this
2774 // situation should be sufficiently rare that the optimization is probably not worth it right
2775 // now.
2776 node.listen();
2777 if (node.listening) { node.unlisten(); }
2778 }
2779 if (!node.active) {
2780 for (var i = ancestors.length - 1; i > 0; i--) {
2781 node = ancestors[i];
2782 if (node === this._root || node.active || !_.isEmpty(node.children)) { break; }
2783 Vue.delete(ancestors[i - 1].children, segments[i]);
2784 node.ready = undefined;
2785 delete this._nodeIndex[node.path];
2786 }
2787 var path = segments.join('/') || '/';
2788 this._prunePath(path, this.findCoupledDescendantPaths(path));
2789 }
2790};
2791
2792Coupler.prototype.subscribe = function subscribe (query, operation, keysCallback) {
2793 var queryHandler = this._queryHandlers[query.toString()];
2794 if (!queryHandler) {
2795 queryHandler = new QueryHandler(this, query);
2796 Vue.set(this._queryHandlers, query.toString(), queryHandler);
2797 }
2798 queryHandler.attach(operation, keysCallback);
2799};
2800
2801Coupler.prototype.unsubscribe = function unsubscribe (query, operation) {
2802 var queryHandler = this._queryHandlers[query.toString()];
2803 if (queryHandler && !queryHandler.detach(operation)) {
2804 queryHandler.destroy();
2805 Vue.delete(this._queryHandlers, query.toString());
2806 }
2807};
2808
2809// Return whether the node at path or any ancestors are coupled.
2810Coupler.prototype.isTrunkCoupled = function isTrunkCoupled (path) {
2811 var segments = splitPath(path, true);
2812 var node;
2813 for (var i = 0, list = segments; i < list.length; i += 1) {
2814 var segment = list[i];
2815
2816 node = segment ? node.children && node.children[segment] : this._root;
2817 if (!node) { return false; }
2818 if (node.active) { return true; }
2819 }
2820 return false;
2821};
2822
2823Coupler.prototype.findCoupledDescendantPaths = function findCoupledDescendantPaths (path) {
2824 var obj;
2825
2826 var node;
2827 for (var i = 0, list = splitPath(path, true); i < list.length; i += 1) {
2828 var segment = list[i];
2829
2830 node = segment ? node.children && node.children[segment] : this._root;
2831 if (node && node.active) { return ( obj = {}, obj[path] = node.active, obj ); }
2832 if (!node) { break; }
2833 }
2834 return node && node.collectCoupledDescendantPaths();
2835};
2836
2837Coupler.prototype.isSubtreeReady = function isSubtreeReady (path) {
2838 var node, childSegment;
2839 function extractChildSegment(match) {
2840 childSegment = match.slice(1);
2841 return '';
2842 }
2843 while (!(node = this._nodeIndex[path])) {
2844 path = path.replace(/\/[^/]*$/, extractChildSegment) || '/';
2845 }
2846 if (childSegment) { void node.children; }// state an interest in the closest ancestor's children
2847 while (node) {
2848 if (node.ready) { return true; }
2849 node = node.parent;
2850 }
2851 return false;
2852};
2853
2854Coupler.prototype.isQueryReady = function isQueryReady (query) {
2855 var queryHandler = this._queryHandlers[query.toString()];
2856 return queryHandler && queryHandler.ready;
2857};
2858
2859Coupler.prototype._queueSnapshotCallback = function _queueSnapshotCallback (callback) {
2860 this._pendingSnapshotCallbacks.push(callback);
2861 this._throttled.processPendingSnapshots.call(this);
2862};
2863
2864Coupler.prototype._processPendingSnapshots = function _processPendingSnapshots () {
2865 for (var i = 0, list = this._pendingSnapshotCallbacks; i < list.length; i += 1) {
2866 var callback = list[i];
2867
2868 callback();
2869 }
2870 // Property is frozen, so we need to splice to empty the array.
2871 this._pendingSnapshotCallbacks.splice(0, Infinity);
2872};
2873
2874Coupler.prototype.throttleSnapshots = function throttleSnapshots (delay) {
2875 if (delay) {
2876 this._throttled.processPendingSnapshots =
2877 _.debounce(_.throttle(this._processPendingSnapshots, delay));
2878 } else {
2879 this._throttled.processPendingSnapshots = this._processPendingSnapshots;
2880 }
2881};
2882
2883Object.defineProperties( Coupler.prototype, prototypeAccessors$1$3 );
2884
2885var Transaction = function Transaction(ref) {
2886 this._ref = ref;
2887 this._outcome = undefined;
2888 this._values = undefined;
2889};
2890
2891var prototypeAccessors$8 = { currentValue: { configurable: true },outcome: { configurable: true },values: { configurable: true } };
2892
2893prototypeAccessors$8.currentValue.get = function () {return this._ref.value;};
2894prototypeAccessors$8.outcome.get = function () {return this._outcome;};
2895prototypeAccessors$8.values.get = function () {return this._values;};
2896
2897Transaction.prototype._setOutcome = function _setOutcome (value) {
2898 if (this._outcome) { throw new Error('Transaction already resolved with ' + this._outcome); }
2899 this._outcome = value;
2900};
2901
2902Transaction.prototype.abort = function abort () {
2903 this._setOutcome('abort');
2904};
2905
2906Transaction.prototype.cancel = function cancel () {
2907 this._setOutcome('cancel');
2908};
2909
2910Transaction.prototype.set = function set (value) {
2911 if (value === undefined) { throw new Error('Invalid argument: undefined'); }
2912 this._setOutcome('set');
2913 this._values = {'': value};
2914};
2915
2916Transaction.prototype.update = function update (values) {
2917 if (values === undefined) { throw new Error('Invalid argument: undefined'); }
2918 if (_.isEmpty(values)) { return this.cancel(); }
2919 this._setOutcome('update');
2920 this._values = values;
2921};
2922
2923Object.defineProperties( Transaction.prototype, prototypeAccessors$8 );
2924
2925
2926var Tree = function Tree(truss, rootUrl, bridge, dispatcher) {
2927 this._truss = truss;
2928 this._rootUrl = rootUrl;
2929 this._bridge = bridge;
2930 this._dispatcher = dispatcher;
2931 this._firebasePropertyEditAllowed = false;
2932 this._writeSerial = 0;
2933 this._localWrites = {};
2934 this._localWriteTimestamp = null;
2935 this._initialized = false;
2936 this._modeler = new Modeler(truss.constructor.VERSION === 'dev');
2937 this._coupler = new Coupler(
2938 rootUrl, bridge, dispatcher, this._integrateSnapshot.bind(this), this._prune.bind(this));
2939 this._vue = new Vue({data: {$root: undefined}});
2940 Object.seal(this);
2941 // Call this.init(classes) to complete initialization; we need two phases so that truss can bind
2942 // the tree into its own accessors prior to defining computed functions, which may try to
2943 // access the tree root via truss.
2944};
2945
2946var prototypeAccessors$1$4 = { root: { configurable: true },truss: { configurable: true } };
2947
2948prototypeAccessors$1$4.root.get = function () {
2949 if (!this._vue.$data.$root) {
2950 this._vue.$data.$root = this._createObject('/');
2951 this._fixObject(this._vue.$data.$root);
2952 this._completeCreateObject(this._vue.$data.$root);
2953 angularProxy.digest();
2954 }
2955 return this._vue.$data.$root;
2956};
2957
2958prototypeAccessors$1$4.truss.get = function () {
2959 return this._truss;
2960};
2961
2962Tree.prototype.init = function init (classes) {
2963 if (this._initialized) {
2964 throw new Error('Data objects already created, too late to mount classes');
2965 }
2966 this._initialized = true;
2967 this._modeler.init(classes, !this._vue.$data.$root);
2968 var createdObjects = [];
2969 this._plantPlaceholders(this.root, '/', undefined, createdObjects);
2970 for (var i = 0, list = createdObjects; i < list.length; i += 1) {
2971 var object = list[i];
2972
2973 this._completeCreateObject(object);
2974 }
2975};
2976
2977Tree.prototype.destroy = function destroy () {
2978 this._coupler.destroy();
2979 if (this._modeler) { this._modeler.destroy(); }
2980 this._vue.$destroy();
2981};
2982
2983Tree.prototype.connectReference = function connectReference (ref, method) {
2984 var this$1 = this;
2985
2986 this._checkHandle(ref);
2987 var operation = this._dispatcher.createOperation('read', method, ref);
2988 var unwatch;
2989 operation._disconnect = this._disconnectReference.bind(this, ref, operation, unwatch);
2990 this._dispatcher.begin(operation).then(function () {
2991 if (operation.running && !operation._disconnected) {
2992 this$1._coupler.couple(ref.path, operation);
2993 operation._coupled = true;
2994 }
2995 }).catch(_.noop);// ignore exception, let onFailure handlers deal with it
2996 return operation._disconnect;
2997};
2998
2999Tree.prototype._disconnectReference = function _disconnectReference (ref, operation, unwatch, error) {
3000 if (operation._disconnected) { return; }
3001 operation._disconnected = true;
3002 if (unwatch) { unwatch(); }
3003 if (operation._coupled) {
3004 this._coupler.decouple(ref.path, operation);// will call back to _prune if necessary
3005 operation._coupled = false;
3006 }
3007 this._dispatcher.end(operation, error).catch(_.noop);
3008};
3009
3010Tree.prototype.isReferenceReady = function isReferenceReady (ref) {
3011 this._checkHandle(ref);
3012 return this._coupler.isSubtreeReady(ref.path);
3013};
3014
3015Tree.prototype.connectQuery = function connectQuery (query, keysCallback, method) {
3016 var this$1 = this;
3017
3018 this._checkHandle(query);
3019 var operation = this._dispatcher.createOperation('read', method, query);
3020 operation._disconnect = this._disconnectQuery.bind(this, query, operation);
3021 this._dispatcher.begin(operation).then(function () {
3022 if (operation.running && !operation._disconnected) {
3023 this$1._coupler.subscribe(query, operation, keysCallback);
3024 operation._coupled = true;
3025 }
3026 }).catch(_.noop);// ignore exception, let onFailure handlers deal with it
3027 return operation._disconnect;
3028};
3029
3030Tree.prototype._disconnectQuery = function _disconnectQuery (query, operation, error) {
3031 if (operation._disconnected) { return; }
3032 operation._disconnected = true;
3033 if (operation._coupled) {
3034 this._coupler.unsubscribe(query, operation);// will call back to _prune if necessary
3035 operation._coupled = false;
3036 }
3037 this._dispatcher.end(operation, error).catch(_.noop);
3038};
3039
3040Tree.prototype.isQueryReady = function isQueryReady (query) {
3041 return this._coupler.isQueryReady(query);
3042};
3043
3044Tree.prototype._checkHandle = function _checkHandle (handle) {
3045 if (!handle.belongsTo(this._truss)) {
3046 throw new Error('Reference belongs to another Truss instance');
3047 }
3048};
3049
3050Tree.prototype.throttleRemoteDataUpdates = function throttleRemoteDataUpdates (delay) {
3051 this._coupler.throttleSnapshots(delay);
3052};
3053
3054Tree.prototype.update = function update (ref, method, values) {
3055 var this$1 = this;
3056
3057 values = _.mapValues(values, function (value) { return escapeKeys(value); });
3058 var numValues = _.size(values);
3059 if (!numValues) { return Promise.resolve(); }
3060 if (method === 'update' || method === 'override') {
3061 checkUpdateHasOnlyDescendantsWithNoOverlap(ref.path, values);
3062 }
3063 if (this._applyLocalWrite(values, method === 'override')) { return Promise.resolve(); }
3064 var pathPrefix = extractCommonPathPrefix(values);
3065 relativizePaths(pathPrefix, values);
3066 if (pathPrefix !== ref.path) { ref = new Reference(ref._tree, pathPrefix, ref._annotations); }
3067 var url = this._rootUrl + pathPrefix;
3068 var writeSerial = this._writeSerial;
3069 var set = numValues === 1;
3070 var operand = set ? values[''] : values;
3071 return this._dispatcher.execute('write', set ? 'set' : 'update', ref, operand, function () {
3072 var promise = this$1._bridge[set ? 'set' : 'update'](url, operand, writeSerial);
3073 return promise.catch(function (e) {
3074 if (!e.immediateFailure) { return Promise.reject(e); }
3075 return promiseFinally(this$1._repair(ref, values), function () { return Promise.reject(e); });
3076 });
3077 });
3078};
3079
3080Tree.prototype.commit = function commit (ref, updateFunction) {
3081 var this$1 = this;
3082
3083 var tries = 0;
3084 updateFunction = wrapPromiseCallback(updateFunction);
3085
3086 var attemptTransaction = function () {
3087 if (tries++ >= 25) {
3088 return Promise.reject(new Error('Transaction needed too many retries, giving up'));
3089 }
3090 var txn = new Transaction(ref);
3091 var oldValue;
3092 // Ensure that Vue's watcher queue gets emptied and computed properties are up to date before
3093 // running the updateFunction.
3094 return Vue.nextTick().then(function () {
3095 oldValue = toFirebaseJson(txn.currentValue);
3096 return updateFunction(txn);
3097 }).then(function () {
3098 var obj;
3099
3100 if (!_.isEqual(oldValue, toFirebaseJson(txn.currentValue))) { return attemptTransaction(); }
3101 if (txn.outcome === 'abort') { return txn; }// early return to save time
3102 var values = _.mapValues(txn.values, function (value) { return escapeKeys(value); });
3103 switch (txn.outcome) {
3104 case 'cancel':
3105 break;
3106 case 'set':
3107 if (this$1._applyLocalWrite(( obj = {}, obj[ref.path] = values[''], obj ))) { return Promise.resolve(); }
3108 break;
3109 case 'update':
3110 checkUpdateHasOnlyDescendantsWithNoOverlap(ref.path, values);
3111 if (this$1._applyLocalWrite(values)) { return Promise.resolve(); }
3112 relativizePaths(ref.path, values);
3113 break;
3114 default:
3115 throw new Error('Invalid transaction outcome: ' + (txn.outcome || 'none'));
3116 }
3117 return this$1._bridge.transaction(
3118 this$1._rootUrl + ref.path, oldValue, values, this$1._writeSerial
3119 ).then(function (result) {
3120 _.forEach(result.snapshots, function (snapshot) { return this$1._integrateSnapshot(snapshot); });
3121 return result.committed ? txn : attemptTransaction();
3122 }, function (e) {
3123 if (e.immediateFailure && (txn.outcome === 'set' || txn.outcome === 'update')) {
3124 return promiseFinally(this$1._repair(ref, values), function () { return Promise.reject(e); });
3125 }
3126 return Promise.reject(e);
3127 });
3128 });
3129 };
3130
3131 return this._truss.peek(ref, function () {
3132 return this$1._dispatcher.execute('write', 'commit', ref, undefined, attemptTransaction);
3133 });
3134};
3135
3136Tree.prototype._repair = function _repair (ref, values) {
3137 var this$1 = this;
3138
3139 // If a write fails early -- that is, before it gets applied to the Firebase client's local
3140 // tree -- then we need to repair our own local tree manually since Firebase won't send events
3141 // to unwind the change.This should be very rare since it's always due to a developer mistake
3142 // so we don't need to be particularly efficient.
3143 var basePath = ref.path;
3144 var paths = _(values).keys().flatMap(function (key) {
3145 var path = basePath;
3146 if (key) { path = joinPath(path, key); }
3147 return _.keys(this$1._coupler.findCoupledDescendantPaths(path));
3148 }).value();
3149 return Promise.all(_.map(paths, function (path) {
3150 return this$1._bridge.once(this$1._rootUrl + path).then(function (snap) {
3151 this$1._integrateSnapshot(snap);
3152 });
3153 }));
3154};
3155
3156Tree.prototype._applyLocalWrite = function _applyLocalWrite (values, override) {
3157 var this$1 = this;
3158
3159 // TODO: correctly apply local writes that impact queries.Currently, a local write will update
3160 // any objects currently selected by a query, but won't add or remove results.
3161 this._writeSerial++;
3162 this._localWriteTimestamp = this._truss.now;
3163 var createdObjects = [];
3164 var numLocal = 0;
3165 _.forEach(values, function (value, path) {
3166 var obj;
3167
3168 var local = this$1._modeler.isLocal(path, value);
3169 if (local) { numLocal++; }
3170 var coupledDescendantPaths =
3171 local ? ( obj = {}, obj[path] = true, obj ) : this$1._coupler.findCoupledDescendantPaths(path);
3172 if (_.isEmpty(coupledDescendantPaths)) { return; }
3173 var offset = (path === '/' ? 0 : path.length) + 1;
3174 for (var descendantPath in coupledDescendantPaths) {
3175 var subPath = descendantPath.slice(offset);
3176 var subValue = value;
3177 if (subPath && value !== null && value !== undefined) {
3178 for (var i = 0, list = splitPath(subPath); i < list.length; i += 1) {
3179 var segment = list[i];
3180
3181 subValue = subValue.$data[segment];
3182 if (subValue === undefined) { break; }
3183 }
3184 }
3185 if (_.isNil(subValue)) {
3186 this$1._prune(descendantPath);
3187 } else {
3188 var key = _.last(splitPath(descendantPath));
3189 this$1._plantValue(
3190 descendantPath, key, subValue,
3191 this$1._scaffoldAncestors(descendantPath, false, createdObjects), false, override, local,
3192 createdObjects
3193 );
3194 }
3195 if (!override && !local) { this$1._localWrites[descendantPath] = this$1._writeSerial; }
3196 }
3197 });
3198 for (var i = 0, list = createdObjects; i < list.length; i += 1) {
3199 var object = list[i];
3200
3201 this._completeCreateObject(object);
3202 }
3203 if (numLocal && numLocal < _.size(values)) {
3204 throw new Error('Write on a mix of local and remote tree paths.');
3205 }
3206 return override || !!numLocal;
3207};
3208
3209/**
3210 * Creates a Truss object and sets all its basic properties: path segment variables, user-defined
3211 * properties, and computed properties.The latter two will be enumerable so that Vue will pick
3212 * them up and make the reactive, so you should call _completeCreateObject once it's done so and
3213 * before any Firebase properties are added.
3214 */
3215Tree.prototype._createObject = function _createObject (path, parent) {
3216 if (!this._initialized && path !== '/') { this.init(); }
3217 var properties = {
3218 // We want Vue to wrap this; we'll make it non-enumerable in _fixObject.
3219 $parent: {value: parent, configurable: true, enumerable: true},
3220 $path: {value: path}
3221 };
3222 if (path === '/') { properties.$truss = {value: this._truss}; }
3223
3224 var object = this._modeler.createObject(path, properties);
3225 Object.defineProperties(object, properties);
3226 return object;
3227};
3228
3229// To be called on the result of _createObject after it's been inserted into the _vue hierarchy
3230// and Vue has had a chance to initialize it.
3231Tree.prototype._fixObject = function _fixObject (object) {
3232 for (var i = 0, list = Object.getOwnPropertyNames(object); i < list.length; i += 1) {
3233 var name = list[i];
3234
3235 var descriptor = Object.getOwnPropertyDescriptor(object, name);
3236 if (descriptor.configurable && descriptor.enumerable) {
3237 descriptor.enumerable = false;
3238 if (_.startsWith(name, '$')) { descriptor.configurable = false; }
3239 Object.defineProperty(object, name, descriptor);
3240 }
3241 }
3242};
3243
3244// To be called on the result of _createObject after _fixObject, and after any additional Firebase
3245// properties have been set, to run initialiers.
3246Tree.prototype._completeCreateObject = function _completeCreateObject (object) {
3247 if (object.hasOwnProperty('$$initializers')) {
3248 for (var i = 0, list = object.$$initializers; i < list.length; i += 1) {
3249 var fn = list[i];
3250
3251 fn(this._vue);
3252 }
3253 delete object.$$initializers;
3254 }
3255};
3256
3257Tree.prototype._destroyObject = function _destroyObject (object) {
3258 if (!(object && object.$truss) || object.$destroyed) { return; }
3259 this._modeler.destroyObject(object);
3260 // Normally we'd only destroy enumerable children, which are the Firebase properties.However,
3261 // clients have the option of creating hidden placeholders, so we need to scan non-enumerable
3262 // properties as well.To distinguish such placeholders from the myriad other non-enumerable
3263 // properties (that lead all over tree, e.g. $parent), we check that the property's parent is
3264 // ourselves before destroying.
3265 for (var i = 0, list = Object.getOwnPropertyNames(object.$data); i < list.length; i += 1) {
3266 var key = list[i];
3267
3268 var child = object.$data[key];
3269 if (child && child.$parent === object) { this._destroyObject(child); }
3270 }
3271};
3272
3273Tree.prototype._integrateSnapshot = function _integrateSnapshot (snap) {
3274 var this$1 = this;
3275
3276 _.forEach(this._localWrites, function (writeSerial, path) {
3277 if (snap.writeSerial >= writeSerial) { delete this$1._localWrites[path]; }
3278 });
3279 if (snap.exists) {
3280 var createdObjects = [];
3281 var parent = this._scaffoldAncestors(snap.path, true, createdObjects);
3282 if (parent) {
3283 this._plantValue(
3284 snap.path, snap.key, snap.value, parent, true, false, false, createdObjects);
3285 }
3286 for (var i = 0, list = createdObjects; i < list.length; i += 1) {
3287 var object = list[i];
3288
3289 this._completeCreateObject(object);
3290 }
3291 } else {
3292 this._prune(snap.path, null, true);
3293 }
3294};
3295
3296Tree.prototype._scaffoldAncestors = function _scaffoldAncestors (path, remoteWrite, createdObjects) {
3297 var object;
3298 var segments = _.dropRight(splitPath(path, true));
3299 var ancestorPath = '/';
3300 for (var i = 0; i < segments.length; i++) {
3301 var segment = segments[i];
3302 var key = unescapeKey(segment);
3303 var child = segment ? object.$data[key] : this.root;
3304 if (segment) { ancestorPath += (ancestorPath === '/' ? '' : '/') + segment; }
3305 if (child) {
3306 if (remoteWrite && this._localWrites[ancestorPath]) { return; }
3307 } else {
3308 child = this._plantValue(
3309 ancestorPath, key, {}, object, remoteWrite, false, false, createdObjects);
3310 if (!child) { return; }
3311 }
3312 object = child;
3313 }
3314 return object;
3315};
3316
3317Tree.prototype._plantValue = function _plantValue (path, key, value, parent, remoteWrite, override, local, createdObjects) {
3318 var this$1 = this;
3319
3320 if (remoteWrite && _.isNil(value)) {
3321 throw new Error(("Snapshot includes invalid value at " + path + ": " + value));
3322 }
3323 if (remoteWrite && this._localWrites[path || '/']) { return; }
3324 if (_.isEqual(value, SERVER_TIMESTAMP)) { value = this._localWriteTimestamp; }
3325 var object = parent.$data[key];
3326 if (!_.isArray(value) && !(local ? _.isPlainObject(value) : _.isObject(value))) {
3327 this._destroyObject(object);
3328 if (!local && _.isNil(value)) {
3329 this._deleteFirebaseProperty(parent, key);
3330 } else {
3331 this._setFirebaseProperty(parent, key, value);
3332 }
3333 return;
3334 }
3335 var objectCreated = false;
3336 if (!_.isObject(object)) {
3337 // Need to pre-set the property, so that if the child object attempts to watch any of its own
3338 // properties while being created the $$touchThis method has something to add a dependency on
3339 // as the object's own properties won't be made reactive until *after* it's been created.
3340 this._setFirebaseProperty(parent, key, null);
3341 object = this._createObject(path, parent);
3342 this._setFirebaseProperty(parent, key, object, object.$hidden);
3343 this._fixObject(object);
3344 createdObjects.push(object);
3345 objectCreated = true;
3346 }
3347 if (override) {
3348 Object.defineProperty(object, '$overridden', {get: _.constant(true), configurable: true});
3349 } else if (object.$overridden) {
3350 delete object.$overridden;
3351 }
3352 // Plant hidden placeholders first, so their computed watchers will have a similar precedence to
3353 // the parent object, and the parent object's other children will get computed first.This can
3354 // optimize updates when parts of a complex model are broken out into hidden sub-models, and
3355 // shouldn't risk being overwritten by actual Firebase data since that will rarely (never?) be
3356 // hidden.
3357 if (objectCreated) { this._plantPlaceholders(object, path, true, createdObjects); }
3358 _.forEach(value, function (item, escapedChildKey) {
3359 this$1._plantValue(
3360 joinPath(path, escapedChildKey), unescapeKey(escapedChildKey), item, object, remoteWrite,
3361 override, local, createdObjects
3362 );
3363 });
3364 if (objectCreated) {
3365 this._plantPlaceholders(object, path, false, createdObjects);
3366 } else {
3367 _.forEach(object.$data, function (item, childKey) {
3368 var escapedChildKey = escapeKey(childKey);
3369 if (!value.hasOwnProperty(escapedChildKey)) {
3370 this$1._prune(joinPath(path, escapedChildKey), null, remoteWrite);
3371 }
3372 });
3373 }
3374 return object;
3375};
3376
3377Tree.prototype._plantPlaceholders = function _plantPlaceholders (object, path, hidden, createdObjects) {
3378 var this$1 = this;
3379
3380 this._modeler.forEachPlaceholderChild(path, function (mount) {
3381 if (hidden !== undefined && hidden !== !!mount.hidden) { return; }
3382 var key = unescapeKey(mount.escapedKey);
3383 if (!object.$data.hasOwnProperty(key)) {
3384 this$1._plantValue(
3385 joinPath(path, mount.escapedKey), key, mount.placeholder, object, false, false, false,
3386 createdObjects);
3387 }
3388 });
3389};
3390
3391Tree.prototype._prune = function _prune (path, lockedDescendantPaths, remoteWrite) {
3392 lockedDescendantPaths = lockedDescendantPaths || {};
3393 var object = this.getObject(path);
3394 if (object === undefined) { return; }
3395 if (remoteWrite && this._avoidLocalWritePaths(path, lockedDescendantPaths)) { return; }
3396 if (!(_.isEmpty(lockedDescendantPaths) && this._pruneAncestors(path, object)) &&
3397 _.isObject(object)) {
3398 // The target object is a placeholder, and all ancestors are placeholders or otherwise needed
3399 // as well, so we can't delete it.Instead, dive into its descendants to delete what we can.
3400 this._pruneDescendants(object, lockedDescendantPaths);
3401 }
3402};
3403
3404Tree.prototype._avoidLocalWritePaths = function _avoidLocalWritePaths (path, lockedDescendantPaths) {
3405 for (var localWritePath in this._localWrites) {
3406 if (!this._localWrites.hasOwnProperty(localWritePath)) { continue; }
3407 if (path === localWritePath || localWritePath === '/' ||
3408 _.startsWith(path, localWritePath + '/')) { return true; }
3409 if (path === '/' || _.startsWith(localWritePath, path + '/')) {
3410 var segments = splitPath(localWritePath, true);
3411 for (var i = segments.length; i > 0; i--) {
3412 var subPath = segments.slice(0, i).join('/');
3413 var active = i === segments.length;
3414 if (lockedDescendantPaths[subPath] || lockedDescendantPaths[subPath] === active) { break; }
3415 lockedDescendantPaths[subPath] = active;
3416 if (subPath === path) { break; }
3417 }
3418 }
3419 }
3420};
3421
3422Tree.prototype._pruneAncestors = function _pruneAncestors (targetPath, targetObject) {
3423 // Destroy the child (unless it's a placeholder that's still needed) and any ancestors that
3424 // are no longer needed to keep this child rooted, and have no other reason to exist.
3425 var deleted = false;
3426 var object = targetObject;
3427 // The target object may be a primitive, in which case it won't have $path, $parent and $key
3428 // properties.In that case, use the target path to figure those out instead.Note that all
3429 // ancestors of the target object will necessarily not be primitives and will have those
3430 // properties.
3431 var targetKey;
3432 var targetParentPath = targetPath.replace(/\/[^/]+$/, function (match) {
3433 targetKey = unescapeKey(match.slice(1));
3434 return '';
3435 });
3436 while (object !== undefined && object !== this.root) {
3437 var parent =
3438 object && object.$parent || object === targetObject && this.getObject(targetParentPath);
3439 if (!this._modeler.isPlaceholder(object && object.$path || targetPath)) {
3440 var ghostObjects = deleted ? null : [targetObject];
3441 if (!this._holdsConcreteData(object, ghostObjects)) {
3442 deleted = true;
3443 this._deleteFirebaseProperty(
3444 parent, object && object.$key || object === targetObject && targetKey);
3445 }
3446 }
3447 object = parent;
3448 }
3449 return deleted;
3450};
3451
3452Tree.prototype._holdsConcreteData = function _holdsConcreteData (object, ghostObjects) {
3453 var this$1 = this;
3454
3455 if (_.isNil(object)) { return false; }
3456 if (ghostObjects && _.includes(ghostObjects, object)) { return false; }
3457 if (!_.isObject(object) || !object.$truss) { return true; }
3458 return _.some(object.$data, function (value) { return this$1._holdsConcreteData(value, ghostObjects); });
3459};
3460
3461Tree.prototype._pruneDescendants = function _pruneDescendants (object, lockedDescendantPaths) {
3462 var this$1 = this;
3463
3464 if (lockedDescendantPaths[object.$path]) { return true; }
3465 if (object.$overridden) { delete object.$overridden; }
3466 var coupledDescendantFound = false;
3467 _.forEach(object.$data, function (value, key) {
3468 var shouldDelete = true;
3469 var valueLocked;
3470 if (lockedDescendantPaths[joinPath(object.$path, escapeKey(key))]) {
3471 shouldDelete = false;
3472 valueLocked = true;
3473 } else if (!_.isNil(value) && value.$truss) {
3474 var placeholder = this$1._modeler.isPlaceholder(value.$path);
3475 if (placeholder || _.has(lockedDescendantPaths, value.$path)) {
3476 valueLocked = this$1._pruneDescendants(value, lockedDescendantPaths);
3477 shouldDelete = !placeholder && !valueLocked;
3478 }
3479 }
3480 if (shouldDelete) { this$1._deleteFirebaseProperty(object, key); }
3481 coupledDescendantFound = coupledDescendantFound || valueLocked;
3482 });
3483 return coupledDescendantFound;
3484};
3485
3486Tree.prototype.getObject = function getObject (path) {
3487 var segments = splitPath(path);
3488 var object;
3489 for (var i = 0, list = segments; i < list.length; i += 1) {
3490 var segment = list[i];
3491
3492 object = segment ? object.$data[segment] : this.root;
3493 if (object === undefined) { return; }
3494 }
3495 return object;
3496};
3497
3498Tree.prototype._getFirebasePropertyDescriptor = function _getFirebasePropertyDescriptor (object, data, key) {
3499 var descriptor = Object.getOwnPropertyDescriptor(data, key);
3500 if (descriptor) {
3501 if (!descriptor.enumerable) {
3502 var child = data[key];
3503 if (!child || child.$parent !== object) {
3504 throw new Error(
3505 "Key conflict between Firebase and instance or computed properties at " +
3506 (object.$path) + ": " + key);
3507 }
3508 }
3509 if (!descriptor.get || !descriptor.set) {
3510 throw new Error(("Unbound property at " + (object.$path) + ": " + key));
3511 }
3512 } else if (key in data) {
3513 throw new Error(
3514 ("Key conflict between Firebase and inherited property at " + (object.$path) + ": " + key));
3515 }
3516 return descriptor;
3517};
3518
3519Tree.prototype._setFirebaseProperty = function _setFirebaseProperty (object, key, value, hidden) {
3520 var data = object.hasOwnProperty('$data') ? object.$data : object;
3521 var descriptor = this._getFirebasePropertyDescriptor(object, data, key);
3522 if (descriptor) {
3523 if (hidden) {
3524 // Redefine property as hidden after it's been created, since we usually don't know whether
3525 // it should be hidden until too late.This is a one-way deal -- you can't unhide a
3526 // property later, but that's fine for our purposes.
3527 Object.defineProperty(data, key, {
3528 get: descriptor.get, set: descriptor.set, configurable: true, enumerable: false
3529 });
3530 }
3531 if (data[key] === value) { return; }
3532 this._firebasePropertyEditAllowed = true;
3533 data[key] = value;
3534 this._firebasePropertyEditAllowed = false;
3535 } else {
3536 Vue.set(data, key, value);
3537 descriptor = Object.getOwnPropertyDescriptor(data, key);
3538 Object.defineProperty(data, key, {
3539 get: descriptor.get, set: this._overwriteFirebaseProperty.bind(this, descriptor, key),
3540 configurable: true, enumerable: !hidden
3541 });
3542 }
3543 angularProxy.digest();
3544};
3545
3546Tree.prototype._overwriteFirebaseProperty = function _overwriteFirebaseProperty (descriptor, key, newValue) {
3547 if (!this._firebasePropertyEditAllowed) {
3548 var e = new Error(("Firebase data cannot be mutated directly: " + key));
3549 e.trussCode = 'firebase_overwrite';
3550 throw e;
3551 }
3552 descriptor.set.call(this, newValue);
3553};
3554
3555Tree.prototype._deleteFirebaseProperty = function _deleteFirebaseProperty (object, key) {
3556 var data = object.hasOwnProperty('$data') ? object.$data : object;
3557 // Make sure it's actually a Firebase property.
3558 this._getFirebasePropertyDescriptor(object, data, key);
3559 this._destroyObject(data[key]);
3560 Vue.delete(data, key);
3561 angularProxy.digest();
3562};
3563
3564Tree.prototype.checkVueObject = function checkVueObject (object, path) {
3565 this._modeler.checkVueObject(object, path);
3566};
3567
3568Object.defineProperties( Tree.prototype, prototypeAccessors$1$4 );
3569
3570
3571function checkUpdateHasOnlyDescendantsWithNoOverlap(rootPath, values) {
3572 // First, check all paths for correctness and absolutize them, since there could be a mix of
3573 // absolute paths and relative keys.
3574 _.forEach(_.keys(values), function (path) {
3575 if (path.charAt(0) === '/') {
3576 if (!(path === rootPath || rootPath === '/' ||
3577 _.startsWith(path, rootPath + '/') && path.length > rootPath.length + 1)) {
3578 throw new Error(("Update item is not a descendant of target ref: " + path));
3579 }
3580 } else {
3581 if (_.includes(path, '/')) {
3582 throw new Error(("Update item deep path must be absolute, taken from a reference: " + path));
3583 }
3584 var absolutePath = joinPath(rootPath, escapeKey(path));
3585 if (values.hasOwnProperty(absolutePath)) {
3586 throw new Error(("Update items overlap: " + path + " and " + absolutePath));
3587 }
3588 values[absolutePath] = values[path];
3589 delete values[path];
3590 }
3591 });
3592 // Then check for overlaps;
3593 var allPaths = _(values).keys().map(function (path) { return joinPath(path, ''); }).sortBy('length').value();
3594 _.forEach(values, function (value, path) {
3595 for (var i = 0, list = allPaths; i < list.length; i += 1) {
3596 var otherPath = list[i];
3597
3598 if (otherPath.length > path.length) { break; }
3599 if (path !== otherPath && _.startsWith(path, otherPath)) {
3600 throw new Error(("Update items overlap: " + otherPath + " and " + path));
3601 }
3602 }
3603 });
3604}
3605
3606function extractCommonPathPrefix(values) {
3607 var prefixSegments;
3608 _.forEach(values, function (value, path) {
3609 var segments = path === '/' ? [''] : splitPath(path, true);
3610 if (prefixSegments) {
3611 var firstMismatchIndex = 0;
3612 var maxIndex = Math.min(prefixSegments.length, segments.length);
3613 while (firstMismatchIndex < maxIndex &&
3614 prefixSegments[firstMismatchIndex] === segments[firstMismatchIndex]) {
3615 firstMismatchIndex++;
3616 }
3617 prefixSegments = prefixSegments.slice(0, firstMismatchIndex);
3618 if (!prefixSegments.length) { return false; }
3619 } else {
3620 prefixSegments = segments;
3621 }
3622 });
3623 return prefixSegments.length === 1 ? '/' : prefixSegments.join('/');
3624}
3625
3626function relativizePaths(rootPath, values) {
3627 var offset = rootPath === '/' ? 1 : rootPath.length + 1;
3628 _.forEach(_.keys(values), function (path) {
3629 values[path.slice(offset)] = values[path];
3630 delete values[path];
3631 });
3632}
3633
3634function toFirebaseJson(object) {
3635 if (!_.isObject(object)) { return object; }
3636 var result = {};
3637 var data = object.$data;
3638 for (var key in data) {
3639 if (data.hasOwnProperty(key)) { result[escapeKey(key)] = toFirebaseJson(data[key]); }
3640 }
3641 return result;
3642}
3643
3644var bridge, logging;
3645var workerFunctions = {};
3646// This version is filled in by the build, don't reformat the line.
3647var VERSION = '4.2.2';
3648
3649
3650var Truss = function Truss(rootUrl) {
3651 // TODO: allow rootUrl to be a test database object for testing
3652 if (!bridge) {
3653 throw new Error('Truss worker not connected, please call Truss.connectWorker first');
3654 }
3655 this._rootUrl = rootUrl.replace(/\/$/, '');
3656 this._keyGenerator = new KeyGenerator();
3657 this._dispatcher = new Dispatcher(bridge);
3658 this._vue = new Vue();
3659
3660 bridge.trackServer(this._rootUrl);
3661 this._tree = new Tree(this, this._rootUrl, bridge, this._dispatcher);
3662 this._metaTree = new MetaTree(this._rootUrl, this._tree, bridge, this._dispatcher);
3663
3664 Object.freeze(this);
3665};
3666
3667var prototypeAccessors$9 = { meta: { configurable: true },store: { configurable: true },now: { configurable: true },SERVER_TIMESTAMP: { configurable: true },VERSION: { configurable: true },FIREBASE_SDK_VERSION: { configurable: true } };
3668var staticAccessors = { computedPropertyStats: { configurable: true },worker: { configurable: true } };
3669
3670prototypeAccessors$9.meta.get = function () {return this._metaTree.root;};
3671prototypeAccessors$9.store.get = function () {return this._tree.root;};
3672
3673/**
3674 * Mount a set of classes against the datastore structure.Must be called at most once, and
3675 * cannot be called once any data has been loaded into the tree.
3676 * @param classes {Array<Function> | Object<Function>} A list of the classes to map onto the
3677 * datastore structure.Each class must have a static $trussMount property that is a
3678 * (wildcarded) unescaped datastore path, or an options object
3679 * {path: string, placeholder: object}, or an array of either.If the list is an object then
3680 * the keys serve as default option-less $trussMount paths for classes that don't define an
3681 * explicit $trussMount.
3682 */
3683Truss.prototype.mount = function mount (classes) {
3684 this._tree.init(classes);
3685};
3686
3687Truss.prototype.destroy = function destroy () {
3688 this._vue.$destroy();
3689 this._tree.destroy();
3690 this._metaTree.destroy();
3691};
3692
3693prototypeAccessors$9.now.get = function () {return Date.now() + this.meta.timeOffset;};
3694Truss.prototype.newKey = function newKey () {return this._keyGenerator.generateUniqueKey(this.now);};
3695
3696Truss.prototype.authenticate = function authenticate (token) {
3697 return this._metaTree.authenticate(token);
3698};
3699
3700Truss.prototype.unauthenticate = function unauthenticate () {
3701 return this._metaTree.unauthenticate();
3702};
3703
3704Truss.prototype.intercept = function intercept (actionType, callbacks) {
3705 return this._dispatcher.intercept(actionType, callbacks);
3706};
3707
3708// connections are {key: Query | Object | fn() -> (Query | Object)}
3709Truss.prototype.connect = function connect (scope, connections) {
3710 if (!connections) {
3711 connections = scope;
3712 scope = undefined;
3713 }
3714 if (connections instanceof Handle) { connections = {_: connections}; }
3715 return new Connector(scope, connections, this._tree, 'connect');
3716};
3717
3718// target is Reference, Query, or connection Object like above
3719Truss.prototype.peek = function peek (target, callback) {
3720 var this$1 = this;
3721
3722 callback = wrapPromiseCallback(callback || _.identity);
3723 var cleanup, cancel;
3724 var promise = Promise.resolve().then(function () { return new Promise(function (resolve, reject) {
3725 var scope = {};
3726 var callbackPromise;
3727
3728 var connector = new Connector(scope, {result: target}, this$1._tree, 'peek');
3729
3730 var unintercept = this$1.intercept('peek', {onFailure: function (op) {
3731 function match(descriptor) {
3732 if (!descriptor) { return; }
3733 if (descriptor instanceof Handle) { return op.target.isEqual(descriptor); }
3734 return _.some(descriptor, function (value) { return match(value); });
3735 }
3736 if (match(connector.at)) {
3737 reject(op.error);
3738 cleanup();
3739 }
3740 }});
3741
3742 var unobserve = this$1.observe(function () { return connector.ready; }, function (ready) {
3743 if (!ready) { return; }
3744 unobserve();
3745 unobserve = null;
3746 callbackPromise = promiseFinally(
3747 callback(scope.result), function () {angularProxy.digest(); callbackPromise = null; cleanup();}
3748 ).then(function (result) {resolve(result);}, function (error) {reject(error);});
3749 });
3750
3751 cleanup = function () {
3752 if (unobserve) {unobserve(); unobserve = null;}
3753 if (unintercept) {unintercept(); unintercept = null;}
3754 if (connector) {connector.destroy(); connector = null;}
3755 if (callbackPromise && callbackPromise.cancel) { callbackPromise.cancel(); }
3756 };
3757
3758 cancel = function () {
3759 reject(new Error('Canceled'));
3760 cleanup();
3761 };
3762 }); });
3763 return promiseCancel(promise, cancel);
3764};
3765
3766Truss.prototype.observe = function observe (subjectFn, callbackFn, options) {
3767 var usePreciseDefaults = _.isObject(options && options.precise);
3768 var numCallbacks = 0;
3769 var oldValueClone;
3770 if (usePreciseDefaults) {
3771 oldValueClone = options.deep ? _.cloneDeep(options.precise) : _.clone(options.precise);
3772 }
3773
3774 var unwatch = this._vue.$watch(subjectFn, function (newValue, oldValue) {
3775 if (options && options.precise) {
3776 var newValueClone = usePreciseDefaults ?
3777 (options.deep ?
3778 _.defaultsDeep({}, newValue, options.precise) :
3779 _.defaults({}, newValue, options.precise)) :
3780 (options.deep ? _.cloneDeep(newValue) : _.clone(newValue));
3781 if (_.isEqual(newValueClone, oldValueClone)) { return; }
3782 oldValueClone = newValueClone;
3783 }
3784 numCallbacks++;
3785 if (!unwatch) {
3786 // Delay the immediate callback until we've had a chance to return the unwatch function.
3787 Promise.resolve().then(function () {
3788 if (numCallbacks > 1) { return; }
3789 callbackFn(newValue, oldValue);
3790 // No need to digest since under Angular we'll be using $q as Promise.
3791 });
3792 } else {
3793 callbackFn(newValue, oldValue);
3794 angularProxy.digest();
3795 }
3796 }, {immediate: true, deep: options && options.deep});
3797
3798 if (options && options.scope) { options.scope.$on('$destroy', unwatch); }
3799 return unwatch;
3800};
3801
3802Truss.prototype.when = function when (expression, options) {
3803 var this$1 = this;
3804
3805 var cleanup, timeoutHandle;
3806 var promise = new Promise(function (resolve, reject) {
3807 var unobserve = this$1.observe(expression, function (value) {
3808 if (!value) { return; }
3809 // Wait for computed properties to settle and double-check.
3810 Vue.nextTick(function () {
3811 value = expression();
3812 if (!value) { return; }
3813 resolve(value);
3814 cleanup();
3815 });
3816 });
3817 if (_.has(options, 'timeout')) {
3818 timeoutHandle = setTimeout(function () {
3819 timeoutHandle = null;
3820 reject(new Error(options.timeoutMessage || 'Timeout'));
3821 cleanup();
3822 }, options.timeout);
3823 }
3824 cleanup = function () {
3825 if (unobserve) {unobserve(); unobserve = null;}
3826 if (timeoutHandle) {clearTimeout(timeoutHandle); timeoutHandle = null;}
3827 reject(new Error('Canceled'));
3828 };
3829 });
3830 promise = promiseCancel(promiseFinally(promise, cleanup), cleanup);
3831 if (options && options.scope) { options.scope.$on('$destroy', function () {promise.cancel();}); }
3832 return promise;
3833};
3834
3835Truss.prototype.nextTick = function nextTick () {
3836 var cleanup;
3837 var promise = new Promise(function (resolve, reject) {
3838 Vue.nextTick(resolve);
3839 cleanup = function () {
3840 reject(new Error('Canceled'));
3841 };
3842 });
3843 promise = promiseCancel(promise, cleanup);
3844 return promise;
3845};
3846
3847Truss.prototype.throttleRemoteDataUpdates = function throttleRemoteDataUpdates (delay) {
3848 this._tree.throttleRemoteDataUpdates(delay);
3849};
3850
3851Truss.prototype.checkObjectsForRogueProperties = function checkObjectsForRogueProperties () {
3852 this._tree.checkVueObject(this._tree.root, '/');
3853};
3854
3855staticAccessors.computedPropertyStats.get = function () {
3856 return stats;
3857};
3858
3859Truss.connectWorker = function connectWorker (webWorker, config) {
3860 if (bridge) { throw new Error('Worker already connected'); }
3861 if (_.isString(webWorker)) {
3862 var Worker = window.SharedWorker || window.Worker;
3863 if (!Worker) { throw new Error('Browser does not implement Web Workers'); }
3864 webWorker = new Worker(webWorker);
3865 }
3866 bridge = new Bridge(webWorker);
3867 if (logging) { bridge.enableLogging(logging); }
3868 return bridge.init(webWorker, config).then(
3869 function (ref) {
3870 var exposedFunctionNames = ref.exposedFunctionNames;
3871 var firebaseSdkVersion = ref.firebaseSdkVersion;
3872
3873 Object.defineProperty(Truss, 'FIREBASE_SDK_VERSION', {value: firebaseSdkVersion});
3874 for (var i = 0, list = exposedFunctionNames; i < list.length; i += 1) {
3875 var name = list[i];
3876
3877 Truss.worker[name] = bridge.bindExposedFunction(name);
3878 }
3879 }
3880 );
3881};
3882
3883staticAccessors.worker.get = function () {return workerFunctions;};
3884Truss.preExpose = function preExpose (functionName) {
3885 Truss.worker[functionName] = bridge.bindExposedFunction(functionName);
3886};
3887
3888Truss.bounceConnection = function bounceConnection () {return bridge.bounceConnection();};
3889Truss.suspend = function suspend () {return bridge.suspend();};
3890Truss.debugPermissionDeniedErrors = function debugPermissionDeniedErrors (simulatedTokenGenerator, maxSimulationDuration, callFilter) {
3891 return bridge.debugPermissionDeniedErrors(
3892 simulatedTokenGenerator, maxSimulationDuration, callFilter);
3893};
3894
3895Truss.debounceAngularDigest = function debounceAngularDigest (wait) {
3896 angularProxy.debounceDigest(wait);
3897};
3898
3899Truss.escapeKey = function escapeKey$1 (key) {return escapeKey(key);};
3900Truss.unescapeKey = function unescapeKey$1 (escapedKey) {return unescapeKey(escapedKey);};
3901
3902Truss.enableLogging = function enableLogging (fn) {
3903 logging = fn;
3904 if (bridge) { bridge.enableLogging(fn); }
3905};
3906
3907// Duplicate static constants on instance for convenience.
3908prototypeAccessors$9.SERVER_TIMESTAMP.get = function () {return Truss.SERVER_TIMESTAMP;};
3909prototypeAccessors$9.VERSION.get = function () {return Truss.VERSION;};
3910prototypeAccessors$9.FIREBASE_SDK_VERSION.get = function () {return Truss.FIREBASE_SDK_VERSION;};
3911
3912Object.defineProperties( Truss.prototype, prototypeAccessors$9 );
3913Object.defineProperties( Truss, staticAccessors );
3914
3915Object.defineProperties(Truss, {
3916 SERVER_TIMESTAMP: {value: SERVER_TIMESTAMP},
3917 VERSION: {value: VERSION},
3918
3919 ComponentPlugin: {value: {
3920 install: function install(Vue2, pluginOptions) {
3921 if (Vue !== Vue2) { throw new Error('Multiple versions of Vue detected'); }
3922 if (!pluginOptions.truss) {
3923 throw new Error('Need to pass `truss` instance as an option to use the ComponentPlugin');
3924 }
3925 var prototypeExtension = {
3926 $truss: {value: pluginOptions.truss},
3927 $destroyed: {get: function get() {return this._isBeingDestroyed || this._isDestroyed;}},
3928 $$touchThis: {value: function value() {if (this.__ob__) { this.__ob__.dep.depend(); }}}
3929 };
3930 var conflictingKeys = _(prototypeExtension).keys()
3931 .union(_.keys(BaseValue.prototype)).intersection(_.keys(Vue.prototype)).value();
3932 if (conflictingKeys.length) {
3933 throw new Error(
3934 'Truss extension properties conflict with Vue properties: ' + conflictingKeys.join(', '));
3935 }
3936 Object.defineProperties(Vue.prototype, prototypeExtension);
3937 copyPrototype(BaseValue, Vue);
3938 Vue.mixin({
3939 destroyed: function destroyed() {
3940 if (_.has(this, '$$finalizers')) {
3941 // Some finalizers remove themselves from the array, so clone it before iterating.
3942 for (var i = 0, list = _.clone(this.$$finalizers); i < list.length; i += 1) {
3943 var fn = list[i];
3944
3945 fn();
3946 }
3947 }
3948 }
3949 });
3950 }
3951 }}
3952});
3953
3954angularProxy.defineModule(Truss);
3955
3956export default Truss;
3957
3958//# sourceMappingURL=firetruss.es2015.js.map
\No newline at end of file