UNPKG

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