@firebase/database 0.13.4 → 0.13.5-20220818183912

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Unreleased
2
2
 
3
+ ## 0.13.5-20220818183912
4
+
5
+ ### Patch Changes
6
+
7
+ - [`9f1e3c667`](https://github.com/firebase/firebase-js-sdk/commit/9f1e3c66747126c8e24894d73f7fa27480bec08d) [#6536](https://github.com/firebase/firebase-js-sdk/pull/6536) - Revert "Updated type of action parameter for DataSnapshot#forEach"
8
+
9
+ * [`a5d9e1083`](https://github.com/firebase/firebase-js-sdk/commit/a5d9e10831c2877e9d15c8a33b15557e4251c4de) [#6497](https://github.com/firebase/firebase-js-sdk/pull/6497) - Fix issue with how get results for filtered queries are added to cache.
10
+ Fix issue with events not getting propagated to listeners by get.
11
+
12
+ - [`fcd4b8ac3`](https://github.com/firebase/firebase-js-sdk/commit/fcd4b8ac36636a60d83cd3370969ff9192f9e6ad) [#6508](https://github.com/firebase/firebase-js-sdk/pull/6508) - Fixed faulty transaction bug causing filtered index queries to override default queries.
13
+
3
14
  ## 0.13.4
4
15
 
5
16
  ### Patch Changes
@@ -4,7 +4,7 @@ import { stringify, jsonEval, contains, assert, isNodeSdk, base64, stringToByteA
4
4
  import { Logger, LogLevel } from '@firebase/logger';
5
5
 
6
6
  const name = "@firebase/database";
7
- const version = "0.13.4";
7
+ const version = "0.13.5-20220818183912";
8
8
 
9
9
  /**
10
10
  * @license
@@ -9135,7 +9135,7 @@ function viewProcessorApplyServerMerge(viewProcessor, viewCache, path, changedCh
9135
9135
  });
9136
9136
  viewMergeTree.children.inorderTraversal((childKey, childMergeTree) => {
9137
9137
  const isUnknownDeepMerge = !viewCache.serverCache.isCompleteForChild(childKey) &&
9138
- childMergeTree.value === undefined;
9138
+ childMergeTree.value === null;
9139
9139
  if (!serverNode.hasChild(childKey) && !isUnknownDeepMerge) {
9140
9140
  const serverChild = viewCache.serverCache
9141
9141
  .getNode()
@@ -9774,9 +9774,11 @@ function syncTreeApplyTaggedListenComplete(syncTree, path, tag) {
9774
9774
  *
9775
9775
  * @param eventRegistration - If null, all callbacks are removed.
9776
9776
  * @param cancelError - If a cancelError is provided, appropriate cancel events will be returned.
9777
+ * @param skipListenerDedup - When performing a `get()`, we don't add any new listeners, so no
9778
+ * deduping needs to take place. This flag allows toggling of that behavior
9777
9779
  * @returns Cancel events, if cancelError was provided.
9778
9780
  */
9779
- function syncTreeRemoveEventRegistration(syncTree, query, eventRegistration, cancelError) {
9781
+ function syncTreeRemoveEventRegistration(syncTree, query, eventRegistration, cancelError, skipListenerDedup = false) {
9780
9782
  // Find the syncPoint first. Then deal with whether or not it has matching listeners
9781
9783
  const path = query._path;
9782
9784
  const maybeSyncPoint = syncTree.syncPointTree_.get(path);
@@ -9793,48 +9795,52 @@ function syncTreeRemoveEventRegistration(syncTree, query, eventRegistration, can
9793
9795
  }
9794
9796
  const removed = removedAndEvents.removed;
9795
9797
  cancelEvents = removedAndEvents.events;
9796
- // We may have just removed one of many listeners and can short-circuit this whole process
9797
- // We may also not have removed a default listener, in which case all of the descendant listeners should already be
9798
- // properly set up.
9799
- //
9800
- // Since indexed queries can shadow if they don't have other query constraints, check for loadsAllData(), instead of
9801
- // queryId === 'default'
9802
- const removingDefault = -1 !==
9803
- removed.findIndex(query => {
9804
- return query._queryParams.loadsAllData();
9805
- });
9806
- const covered = syncTree.syncPointTree_.findOnPath(path, (relativePath, parentSyncPoint) => syncPointHasCompleteView(parentSyncPoint));
9807
- if (removingDefault && !covered) {
9808
- const subtree = syncTree.syncPointTree_.subtree(path);
9809
- // There are potentially child listeners. Determine what if any listens we need to send before executing the
9810
- // removal
9811
- if (!subtree.isEmpty()) {
9812
- // We need to fold over our subtree and collect the listeners to send
9813
- const newViews = syncTreeCollectDistinctViewsForSubTree_(subtree);
9814
- // Ok, we've collected all the listens we need. Set them up.
9815
- for (let i = 0; i < newViews.length; ++i) {
9816
- const view = newViews[i], newQuery = view.query;
9817
- const listener = syncTreeCreateListenerForView_(syncTree, view);
9818
- syncTree.listenProvider_.startListening(syncTreeQueryForListening_(newQuery), syncTreeTagForQuery_(syncTree, newQuery), listener.hashFn, listener.onComplete);
9819
- }
9820
- }
9821
- }
9822
- // If we removed anything and we're not covered by a higher up listen, we need to stop listening on this query
9823
- // The above block has us covered in terms of making sure we're set up on listens lower in the tree.
9824
- // Also, note that if we have a cancelError, it's already been removed at the provider level.
9825
- if (!covered && removed.length > 0 && !cancelError) {
9826
- // If we removed a default, then we weren't listening on any of the other queries here. Just cancel the one
9827
- // default. Otherwise, we need to iterate through and cancel each individual query
9828
- if (removingDefault) {
9829
- // We don't tag default listeners
9830
- const defaultTag = null;
9831
- syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(query), defaultTag);
9832
- }
9833
- else {
9834
- removed.forEach((queryToRemove) => {
9835
- const tagToRemove = syncTree.queryToTagMap.get(syncTreeMakeQueryKey_(queryToRemove));
9836
- syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(queryToRemove), tagToRemove);
9798
+ if (!skipListenerDedup) {
9799
+ /**
9800
+ * We may have just removed one of many listeners and can short-circuit this whole process
9801
+ * We may also not have removed a default listener, in which case all of the descendant listeners should already be
9802
+ * properly set up.
9803
+ */
9804
+ // Since indexed queries can shadow if they don't have other query constraints, check for loadsAllData(), instead of
9805
+ // queryId === 'default'
9806
+ const removingDefault = -1 !==
9807
+ removed.findIndex(query => {
9808
+ return query._queryParams.loadsAllData();
9837
9809
  });
9810
+ const covered = syncTree.syncPointTree_.findOnPath(path, (relativePath, parentSyncPoint) => syncPointHasCompleteView(parentSyncPoint));
9811
+ if (removingDefault && !covered) {
9812
+ const subtree = syncTree.syncPointTree_.subtree(path);
9813
+ // There are potentially child listeners. Determine what if any listens we need to send before executing the
9814
+ // removal
9815
+ if (!subtree.isEmpty()) {
9816
+ // We need to fold over our subtree and collect the listeners to send
9817
+ const newViews = syncTreeCollectDistinctViewsForSubTree_(subtree);
9818
+ // Ok, we've collected all the listens we need. Set them up.
9819
+ for (let i = 0; i < newViews.length; ++i) {
9820
+ const view = newViews[i], newQuery = view.query;
9821
+ const listener = syncTreeCreateListenerForView_(syncTree, view);
9822
+ syncTree.listenProvider_.startListening(syncTreeQueryForListening_(newQuery), syncTreeTagForQuery(syncTree, newQuery), listener.hashFn, listener.onComplete);
9823
+ }
9824
+ }
9825
+ // Otherwise there's nothing below us, so nothing we need to start listening on
9826
+ }
9827
+ // If we removed anything and we're not covered by a higher up listen, we need to stop listening on this query
9828
+ // The above block has us covered in terms of making sure we're set up on listens lower in the tree.
9829
+ // Also, note that if we have a cancelError, it's already been removed at the provider level.
9830
+ if (!covered && removed.length > 0 && !cancelError) {
9831
+ // If we removed a default, then we weren't listening on any of the other queries here. Just cancel the one
9832
+ // default. Otherwise, we need to iterate through and cancel each individual query
9833
+ if (removingDefault) {
9834
+ // We don't tag default listeners
9835
+ const defaultTag = null;
9836
+ syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(query), defaultTag);
9837
+ }
9838
+ else {
9839
+ removed.forEach((queryToRemove) => {
9840
+ const tagToRemove = syncTree.queryToTagMap.get(syncTreeMakeQueryKey_(queryToRemove));
9841
+ syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(queryToRemove), tagToRemove);
9842
+ });
9843
+ }
9838
9844
  }
9839
9845
  }
9840
9846
  // Now, clear all of the tags we're tracking for the removed listens
@@ -9842,30 +9848,6 @@ function syncTreeRemoveEventRegistration(syncTree, query, eventRegistration, can
9842
9848
  }
9843
9849
  return cancelEvents;
9844
9850
  }
9845
- /**
9846
- * This function was added to support non-listener queries,
9847
- * specifically for use in repoGetValue. It sets up all the same
9848
- * local cache data-structures (SyncPoint + View) that are
9849
- * needed for listeners without installing an event registration.
9850
- * If `query` is not `loadsAllData`, it will also provision a tag for
9851
- * the query so that query results can be merged into the sync
9852
- * tree using existing logic for tagged listener queries.
9853
- *
9854
- * @param syncTree - Synctree to add the query to.
9855
- * @param query - Query to register
9856
- * @returns tag as a string if query is not a default query, null if query is not.
9857
- */
9858
- function syncTreeRegisterQuery(syncTree, query) {
9859
- const { syncPoint, serverCache, writesCache, serverCacheComplete } = syncTreeRegisterSyncPoint(query, syncTree);
9860
- const view = syncPointGetView(syncPoint, query, writesCache, serverCache, serverCacheComplete);
9861
- if (!syncPoint.views.has(query._queryIdentifier)) {
9862
- syncPoint.views.set(query._queryIdentifier, view);
9863
- }
9864
- if (!query._queryParams.loadsAllData()) {
9865
- return syncTreeTagForQuery_(syncTree, query);
9866
- }
9867
- return null;
9868
- }
9869
9851
  /**
9870
9852
  * Apply new server data for the specified tagged query.
9871
9853
  *
@@ -9906,11 +9888,11 @@ function syncTreeApplyTaggedQueryMerge(syncTree, path, changedChildren, tag) {
9906
9888
  }
9907
9889
  }
9908
9890
  /**
9909
- * Creates a new syncpoint for a query and creates a tag if the view doesn't exist.
9910
- * Extracted from addEventRegistration to allow `repoGetValue` to properly set up the SyncTree
9911
- * without actually listening on a query.
9891
+ * Add an event callback for the specified query.
9892
+ *
9893
+ * @returns Events to raise.
9912
9894
  */
9913
- function syncTreeRegisterSyncPoint(query, syncTree) {
9895
+ function syncTreeAddEventRegistration(syncTree, query, eventRegistration, skipSetupListener = false) {
9914
9896
  const path = query._path;
9915
9897
  let serverCache = null;
9916
9898
  let foundAncestorDefaultView = false;
@@ -9959,24 +9941,8 @@ function syncTreeRegisterSyncPoint(query, syncTree) {
9959
9941
  syncTree.tagToQueryMap.set(tag, queryKey);
9960
9942
  }
9961
9943
  const writesCache = writeTreeChildWrites(syncTree.pendingWriteTree_, path);
9962
- return {
9963
- syncPoint,
9964
- writesCache,
9965
- serverCache,
9966
- serverCacheComplete,
9967
- foundAncestorDefaultView,
9968
- viewAlreadyExists
9969
- };
9970
- }
9971
- /**
9972
- * Add an event callback for the specified query.
9973
- *
9974
- * @returns Events to raise.
9975
- */
9976
- function syncTreeAddEventRegistration(syncTree, query, eventRegistration) {
9977
- const { syncPoint, serverCache, writesCache, serverCacheComplete, viewAlreadyExists, foundAncestorDefaultView } = syncTreeRegisterSyncPoint(query, syncTree);
9978
9944
  let events = syncPointAddEventRegistration(syncPoint, query, eventRegistration, writesCache, serverCache, serverCacheComplete);
9979
- if (!viewAlreadyExists && !foundAncestorDefaultView) {
9945
+ if (!viewAlreadyExists && !foundAncestorDefaultView && !skipSetupListener) {
9980
9946
  const view = syncPointViewForQuery(syncPoint, query);
9981
9947
  events = events.concat(syncTreeSetupListener_(syncTree, query, view));
9982
9948
  }
@@ -10106,7 +10072,7 @@ function syncTreeApplyOperationDescendantsHelper_(operation, syncPointTree, serv
10106
10072
  }
10107
10073
  function syncTreeCreateListenerForView_(syncTree, view) {
10108
10074
  const query = view.query;
10109
- const tag = syncTreeTagForQuery_(syncTree, query);
10075
+ const tag = syncTreeTagForQuery(syncTree, query);
10110
10076
  return {
10111
10077
  hashFn: () => {
10112
10078
  const cache = viewGetServerCache(view) || ChildrenNode.EMPTY_NODE;
@@ -10134,7 +10100,7 @@ function syncTreeCreateListenerForView_(syncTree, view) {
10134
10100
  /**
10135
10101
  * Return the tag associated with the given query.
10136
10102
  */
10137
- function syncTreeTagForQuery_(syncTree, query) {
10103
+ function syncTreeTagForQuery(syncTree, query) {
10138
10104
  const queryKey = syncTreeMakeQueryKey_(query);
10139
10105
  return syncTree.queryToTagMap.get(queryKey);
10140
10106
  }
@@ -10234,7 +10200,7 @@ function syncTreeGetNextQueryTag_() {
10234
10200
  */
10235
10201
  function syncTreeSetupListener_(syncTree, query, view) {
10236
10202
  const path = query._path;
10237
- const tag = syncTreeTagForQuery_(syncTree, query);
10203
+ const tag = syncTreeTagForQuery(syncTree, query);
10238
10204
  const listener = syncTreeCreateListenerForView_(syncTree, view);
10239
10205
  const events = syncTree.listenProvider_.startListening(syncTreeQueryForListening_(query), tag, listener.hashFn, listener.onComplete);
10240
10206
  const subtree = syncTree.syncPointTree_.subtree(path);
@@ -10265,7 +10231,7 @@ function syncTreeSetupListener_(syncTree, query, view) {
10265
10231
  });
10266
10232
  for (let i = 0; i < queriesToStop.length; ++i) {
10267
10233
  const queryToStop = queriesToStop[i];
10268
- syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(queryToStop), syncTreeTagForQuery_(syncTree, queryToStop));
10234
+ syncTree.listenProvider_.stopListening(syncTreeQueryForListening_(queryToStop), syncTreeTagForQuery(syncTree, queryToStop));
10269
10235
  }
10270
10236
  }
10271
10237
  return events;
@@ -11190,14 +11156,14 @@ function repoGetNextWriteId(repo) {
11190
11156
  * belonging to active listeners. If they are found, such values
11191
11157
  * are considered to be the most up-to-date.
11192
11158
  *
11193
- * If the client is not connected, this method will try to
11194
- * establish a connection and request the value for `query`. If
11195
- * the client is not able to retrieve the query result, it reports
11196
- * an error.
11159
+ * If the client is not connected, this method will wait until the
11160
+ * repo has established a connection and then request the value for `query`.
11161
+ * If the client is not able to retrieve the query result for another reason,
11162
+ * it reports an error.
11197
11163
  *
11198
11164
  * @param query - The query to surface a value for.
11199
11165
  */
11200
- function repoGetValue(repo, query) {
11166
+ function repoGetValue(repo, query, eventRegistration) {
11201
11167
  // Only active queries are cached. There is no persisted cache.
11202
11168
  const cached = syncTreeGetServerValue(repo.serverSyncTree_, query);
11203
11169
  if (cached != null) {
@@ -11205,24 +11171,34 @@ function repoGetValue(repo, query) {
11205
11171
  }
11206
11172
  return repo.server_.get(query).then(payload => {
11207
11173
  const node = nodeFromJSON(payload).withIndex(query._queryParams.getIndex());
11208
- // if this is a filtered query, then overwrite at path
11174
+ /**
11175
+ * Below we simulate the actions of an `onlyOnce` `onValue()` event where:
11176
+ * Add an event registration,
11177
+ * Update data at the path,
11178
+ * Raise any events,
11179
+ * Cleanup the SyncTree
11180
+ */
11181
+ syncTreeAddEventRegistration(repo.serverSyncTree_, query, eventRegistration, true);
11182
+ let events;
11209
11183
  if (query._queryParams.loadsAllData()) {
11210
- syncTreeApplyServerOverwrite(repo.serverSyncTree_, query._path, node);
11184
+ events = syncTreeApplyServerOverwrite(repo.serverSyncTree_, query._path, node);
11211
11185
  }
11212
11186
  else {
11213
- // Simulate `syncTreeAddEventRegistration` without events/listener setup.
11214
- // We do this (along with the syncTreeRemoveEventRegistration` below) so that
11215
- // `repoGetValue` results have the same cache effects as initial listener(s)
11216
- // updates.
11217
- const tag = syncTreeRegisterQuery(repo.serverSyncTree_, query);
11218
- syncTreeApplyTaggedQueryOverwrite(repo.serverSyncTree_, query._path, node, tag);
11219
- // Call `syncTreeRemoveEventRegistration` with a null event registration, since there is none.
11220
- // Note: The below code essentially unregisters the query and cleans up any views/syncpoints temporarily created above.
11221
- }
11222
- const cancels = syncTreeRemoveEventRegistration(repo.serverSyncTree_, query, null);
11223
- if (cancels.length > 0) {
11224
- repoLog(repo, 'unexpected cancel events in repoGetValue');
11187
+ const tag = syncTreeTagForQuery(repo.serverSyncTree_, query);
11188
+ events = syncTreeApplyTaggedQueryOverwrite(repo.serverSyncTree_, query._path, node, tag);
11225
11189
  }
11190
+ /*
11191
+ * We need to raise events in the scenario where `get()` is called at a parent path, and
11192
+ * while the `get()` is pending, `onValue` is called at a child location. While get() is waiting
11193
+ * for the data, `onValue` will register a new event. Then, get() will come back, and update the syncTree
11194
+ * and its corresponding serverCache, including the child location where `onValue` is called. Then,
11195
+ * `onValue` will receive the event from the server, but look at the syncTree and see that the data received
11196
+ * from the server is already at the SyncPoint, and so the `onValue` callback will never get fired.
11197
+ * Calling `eventQueueRaiseEventsForChangedPath()` is the correct way to propagate the events and
11198
+ * ensure the corresponding child events will get fired.
11199
+ */
11200
+ eventQueueRaiseEventsForChangedPath(repo.eventQueue_, query._path, events);
11201
+ syncTreeRemoveEventRegistration(repo.serverSyncTree_, query, eventRegistration, null, true);
11226
11202
  return node;
11227
11203
  }, err => {
11228
11204
  repoLog(repo, 'get for query ' + stringify(query) + ' failed: ' + err);
@@ -12905,7 +12881,9 @@ function update(ref, values) {
12905
12881
  */
12906
12882
  function get(query) {
12907
12883
  query = getModularInstance(query);
12908
- return repoGetValue(query._repo, query).then(node => {
12884
+ const callbackContext = new CallbackContext(() => { });
12885
+ const container = new ValueEventRegistration(callbackContext);
12886
+ return repoGetValue(query._repo, query, container).then(node => {
12909
12887
  return new DataSnapshot(node, new ReferenceImpl(query._repo, query._path), query._queryParams.getIndex());
12910
12888
  });
12911
12889
  }