UNPKG

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