UNPKG

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