import { Observable, merge, of, from } from 'rxjs'; import { delay, map, switchMap, scan, distinctUntilChanged, withLatestFrom, skipWhile } from 'rxjs/operators'; import { onChildAdded, onChildRemoved, onChildChanged, onChildMoved, onValue, off, get as get$1 } from 'firebase/database'; /** * @license * Copyright 2021 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var _a; var ListenEvent; (function (ListenEvent) { ListenEvent["added"] = "child_added"; ListenEvent["removed"] = "child_removed"; ListenEvent["changed"] = "child_changed"; ListenEvent["moved"] = "child_moved"; ListenEvent["value"] = "value"; })(ListenEvent || (ListenEvent = {})); var ListenerMethods = Object.freeze((_a = {}, _a[ListenEvent.added] = onChildAdded, _a[ListenEvent.removed] = onChildRemoved, _a[ListenEvent.changed] = onChildChanged, _a[ListenEvent.moved] = onChildMoved, _a[ListenEvent.value] = onValue, _a)); /** * @license * Copyright 2021 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Create an observable from a Database Reference or Database Query. * @param ref Database Reference * @param event Listen event type ('value', 'added', 'changed', 'removed', 'moved') */ function fromRef(ref, event) { return new Observable(function (subscriber) { var fn = ListenerMethods[event](ref, function (snapshot, prevKey) { subscriber.next({ snapshot: snapshot, prevKey: prevKey, event: event }); }, subscriber.error.bind(subscriber)); return { unsubscribe: function () { off(ref, event, fn); }, }; }).pipe( // Ensures subscribe on observable is async. This handles // a quirk in the SDK where on/once callbacks can happen // synchronously. delay(0)); } /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __spreadArray(to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; /** * @license * Copyright 2021 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Check the length of the provided array. If it is empty return an array * that is populated with all the Realtime Database child events. * @param events */ function validateEventsArray(events) { if (events == null || events.length === 0) { events = [ ListenEvent.added, ListenEvent.removed, ListenEvent.changed, ListenEvent.moved, ]; } return events; } /** * @license * Copyright 2021 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * Get the snapshot changes of an object * @param query */ function object(query) { return fromRef(query, ListenEvent.value); } /** * Get an array of object values, optionally with a mapped key * @param query object ref or query * @param keyField map the object key to a specific field */ function objectVal(query, options) { if (options === void 0) { options = {}; } return fromRef(query, ListenEvent.value).pipe(map(function (change) { return changeToData(change, options); })); } function changeToData(change, options) { var _a; if (options === void 0) { options = {}; } var val = change.snapshot.val(); // match the behavior of the JS SDK when the snapshot doesn't exist if (!change.snapshot.exists()) { return val; } // val can be a primitive type if (typeof val !== 'object') { return val; } return __assign(__assign({}, val), (options.keyField ? (_a = {}, _a[options.keyField] = change.snapshot.key, _a) : null)); } /** * @license * Copyright 2021 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ function stateChanges(query, options) { if (options === void 0) { options = {}; } var events = validateEventsArray(options.events); var childEvent$ = events.map(function (event) { return fromRef(query, event); }); return merge.apply(void 0, childEvent$); } function get(query) { return from(get$1(query)).pipe(map(function (snapshot) { var event = ListenEvent.value; return { snapshot: snapshot, prevKey: null, event: event }; })); } function list(query, options) { if (options === void 0) { options = {}; } var events = validateEventsArray(options.events); return get(query).pipe(switchMap(function (change) { var childEvent$ = [of(change)]; events.forEach(function (event) { childEvent$.push(fromRef(query, event)); }); return merge.apply(void 0, childEvent$).pipe(scan(buildView, [])); }), distinctUntilChanged()); } /** * Get an object mapped to its value, and optionally its key * @param query object ref or query * @param keyField map the object key to a specific field */ function listVal(query, options) { if (options === void 0) { options = {}; } return list(query).pipe(map(function (arr) { return arr.map(function (change) { return changeToData(change, options); }); })); } function positionFor(changes, key) { var len = changes.length; for (var i = 0; i < len; i++) { if (changes[i].snapshot.key === key) { return i; } } return -1; } function positionAfter(changes, prevKey) { if (prevKey == null) { return 0; } else { var i = positionFor(changes, prevKey); if (i === -1) { return changes.length; } else { return i + 1; } } } function buildView(current, change) { var snapshot = change.snapshot, prevKey = change.prevKey, event = change.event; var key = snapshot.key; var currentKeyPosition = positionFor(current, key); var afterPreviousKeyPosition = positionAfter(current, prevKey || undefined); switch (event) { case ListenEvent.value: if (change.snapshot && change.snapshot.exists()) { var prevKey_1 = null; change.snapshot.forEach(function (snapshot) { var action = { snapshot: snapshot, event: ListenEvent.value, prevKey: prevKey_1, }; prevKey_1 = snapshot.key; current = __spreadArray(__spreadArray([], current, true), [action], false); return false; }); } return current; case ListenEvent.added: if (currentKeyPosition > -1) { // check that the previouskey is what we expect, else reorder var previous = current[currentKeyPosition - 1]; if (((previous && previous.snapshot.key) || null) !== prevKey) { current = current.filter(function (x) { return x.snapshot.key !== snapshot.key; }); current.splice(afterPreviousKeyPosition, 0, change); } } else if (prevKey == null) { return __spreadArray([change], current, true); } else { current = current.slice(); current.splice(afterPreviousKeyPosition, 0, change); } return current; case ListenEvent.removed: return current.filter(function (x) { return x.snapshot.key !== snapshot.key; }); case ListenEvent.changed: return current.map(function (x) { return (x.snapshot.key === key ? change : x); }); case ListenEvent.moved: if (currentKeyPosition > -1) { var data = current.splice(currentKeyPosition, 1)[0]; current = current.slice(); current.splice(afterPreviousKeyPosition, 0, data); return current; } return current; // default will also remove null results default: return current; } } /** * @license * Copyright 2021 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ function auditTrail(query, options) { if (options === void 0) { options = {}; } var auditTrail$ = stateChanges(query, options).pipe(scan(function (current, changes) { return __spreadArray(__spreadArray([], current, true), [changes], false); }, [])); return waitForLoaded(query, auditTrail$); } function loadedData(query) { // Create an observable of loaded values to retrieve the // known dataset. This will allow us to know what key to // emit the "whole" array at when listening for child events. return fromRef(query, ListenEvent.value).pipe(map(function (data) { // Store the last key in the data set var lastKeyToLoad; // Loop through loaded dataset to find the last key data.snapshot.forEach(function (child) { lastKeyToLoad = child.key; return false; }); // return data set and the current last key loaded return { data: data, lastKeyToLoad: lastKeyToLoad }; })); } function waitForLoaded(query, snap$) { var loaded$ = loadedData(query); return loaded$.pipe(withLatestFrom(snap$), // Get the latest values from the "loaded" and "child" datasets // We can use both datasets to form an array of the latest values. map(function (_a) { var loaded = _a[0], changes = _a[1]; // Store the last key in the data set var lastKeyToLoad = loaded.lastKeyToLoad; // Store all child keys loaded at this point var loadedKeys = changes.map(function (change) { return change.snapshot.key; }); return { changes: changes, lastKeyToLoad: lastKeyToLoad, loadedKeys: loadedKeys }; }), // This is the magical part, only emit when the last load key // in the dataset has been loaded by a child event. At this point // we can assume the dataset is "whole". skipWhile(function (meta) { return meta.loadedKeys.indexOf(meta.lastKeyToLoad) === -1; }), // Pluck off the meta data because the user only cares // to iterate through the snapshots map(function (meta) { return meta.changes; })); } export { ListenEvent, ListenerMethods, auditTrail, changeToData, fromRef, list, listVal, object, objectVal, stateChanges }; //# sourceMappingURL=index.esm.js.map