@firebase/firestore 3.4.4 → 3.4.5-canary.8ac8fb099

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.
Files changed (221) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/firestore/src/api/credentials.d.ts +1 -0
  3. package/dist/firestore/src/api/{query.d.ts → filter.d.ts} +0 -0
  4. package/dist/firestore/src/api/snapshot.d.ts +1 -2
  5. package/dist/firestore/src/api.d.ts +1 -1
  6. package/dist/firestore/src/core/database_info.d.ts +1 -0
  7. package/dist/firestore/src/core/target.d.ts +34 -5
  8. package/dist/firestore/src/index/index_byte_encoder.d.ts +18 -1
  9. package/dist/firestore/src/index/index_entry.d.ts +32 -0
  10. package/dist/firestore/src/lite-api/query.d.ts +2 -2
  11. package/dist/firestore/src/local/document_overlay_cache.d.ts +72 -0
  12. package/dist/firestore/src/local/index_manager.d.ts +52 -0
  13. package/dist/firestore/src/local/indexeddb_document_overlay_cache.d.ts +45 -0
  14. package/dist/firestore/src/local/indexeddb_index_manager.d.ts +87 -2
  15. package/dist/firestore/src/local/indexeddb_persistence.d.ts +6 -4
  16. package/dist/firestore/src/local/indexeddb_remote_document_cache.d.ts +2 -8
  17. package/dist/firestore/src/local/indexeddb_schema.d.ts +195 -7
  18. package/dist/firestore/src/local/local_documents_view.d.ts +0 -1
  19. package/dist/firestore/src/local/local_serializer.d.ts +19 -3
  20. package/dist/firestore/src/local/memory_document_overlay_cache.d.ts +37 -0
  21. package/dist/firestore/src/local/memory_index_manager.d.ts +11 -0
  22. package/dist/firestore/src/local/memory_persistence.d.ts +6 -2
  23. package/dist/firestore/src/local/memory_remote_document_cache.d.ts +1 -3
  24. package/dist/firestore/src/local/persistence.d.ts +8 -2
  25. package/dist/firestore/src/local/remote_document_cache.d.ts +7 -9
  26. package/dist/firestore/src/local/remote_document_change_buffer.d.ts +3 -12
  27. package/dist/firestore/src/local/simple_db.d.ts +12 -0
  28. package/dist/firestore/src/model/document.d.ts +7 -0
  29. package/dist/firestore/src/model/document_key.d.ts +6 -0
  30. package/dist/firestore/src/model/field_index.d.ts +89 -7
  31. package/dist/firestore/src/model/overlay.d.ts +32 -0
  32. package/dist/firestore/src/model/target_index_matcher.d.ts +70 -0
  33. package/dist/firestore/src/model/type_order.d.ts +2 -1
  34. package/dist/firestore/src/model/values.d.ts +7 -0
  35. package/dist/firestore/src/platform/node/base64.d.ts +1 -0
  36. package/dist/firestore/src/platform/node/grpc_connection.d.ts +2 -2
  37. package/dist/firestore/src/platform/node/load_protos.d.ts +2 -2
  38. package/dist/firestore/src/util/sorted_set.d.ts +18 -0
  39. package/dist/firestore/test/unit/local/counting_query_engine.d.ts +3 -3
  40. package/dist/firestore/test/unit/local/document_overlay_cache.test.d.ts +1 -0
  41. package/dist/firestore/test/unit/local/persistence_test_helpers.d.ts +1 -0
  42. package/dist/firestore/test/unit/local/test_document_overlay_cache.d.ts +38 -0
  43. package/dist/firestore/test/unit/local/test_index_manager.d.ts +11 -0
  44. package/dist/firestore/test/unit/local/test_remote_document_cache.d.ts +5 -3
  45. package/dist/firestore/test/unit/local/test_remote_document_change_buffer.d.ts +1 -1
  46. package/dist/firestore/test/unit/model/field_index.test.d.ts +17 -0
  47. package/dist/firestore/test/unit/model/target.test.d.ts +17 -0
  48. package/dist/firestore/test/unit/model/target_index_matcher.test.d.ts +17 -0
  49. package/dist/firestore/test/util/helpers.d.ts +8 -2
  50. package/dist/index.d.ts +1 -2
  51. package/dist/index.esm2017.js +5415 -3839
  52. package/dist/index.esm2017.js.map +1 -1
  53. package/dist/index.esm5.js +5039 -3613
  54. package/dist/index.esm5.js.map +1 -1
  55. package/dist/index.node.cjs.js +2908 -592
  56. package/dist/index.node.cjs.js.map +1 -1
  57. package/dist/index.node.mjs +2888 -593
  58. package/dist/index.node.mjs.map +1 -1
  59. package/dist/index.rn.js +5396 -3862
  60. package/dist/index.rn.js.map +1 -1
  61. package/dist/internal.d.ts +265 -26
  62. package/dist/lite/firestore/src/api/credentials.d.ts +1 -0
  63. package/dist/lite/firestore/src/api/{query.d.ts → filter.d.ts} +0 -0
  64. package/dist/lite/firestore/src/api/snapshot.d.ts +1 -2
  65. package/dist/lite/firestore/src/api.d.ts +1 -1
  66. package/dist/lite/firestore/src/core/database_info.d.ts +1 -0
  67. package/dist/lite/firestore/src/core/target.d.ts +34 -5
  68. package/dist/lite/firestore/src/index/index_byte_encoder.d.ts +18 -1
  69. package/dist/lite/firestore/src/index/index_entry.d.ts +32 -0
  70. package/dist/lite/firestore/src/lite-api/query.d.ts +2 -2
  71. package/dist/lite/firestore/src/local/document_overlay_cache.d.ts +72 -0
  72. package/dist/lite/firestore/src/local/index_manager.d.ts +52 -0
  73. package/dist/lite/firestore/src/local/indexeddb_document_overlay_cache.d.ts +45 -0
  74. package/dist/lite/firestore/src/local/indexeddb_index_manager.d.ts +87 -2
  75. package/dist/lite/firestore/src/local/indexeddb_persistence.d.ts +6 -4
  76. package/dist/lite/firestore/src/local/indexeddb_remote_document_cache.d.ts +2 -8
  77. package/dist/lite/firestore/src/local/indexeddb_schema.d.ts +195 -7
  78. package/dist/lite/firestore/src/local/local_documents_view.d.ts +0 -1
  79. package/dist/lite/firestore/src/local/local_serializer.d.ts +19 -3
  80. package/dist/lite/firestore/src/local/memory_document_overlay_cache.d.ts +37 -0
  81. package/dist/lite/firestore/src/local/memory_index_manager.d.ts +11 -0
  82. package/dist/lite/firestore/src/local/memory_persistence.d.ts +6 -2
  83. package/dist/lite/firestore/src/local/memory_remote_document_cache.d.ts +1 -3
  84. package/dist/lite/firestore/src/local/persistence.d.ts +8 -2
  85. package/dist/lite/firestore/src/local/remote_document_cache.d.ts +7 -9
  86. package/dist/lite/firestore/src/local/remote_document_change_buffer.d.ts +3 -12
  87. package/dist/lite/firestore/src/local/simple_db.d.ts +12 -0
  88. package/dist/lite/firestore/src/model/document.d.ts +7 -0
  89. package/dist/lite/firestore/src/model/document_key.d.ts +6 -0
  90. package/dist/lite/firestore/src/model/field_index.d.ts +89 -7
  91. package/dist/lite/firestore/src/model/overlay.d.ts +32 -0
  92. package/dist/lite/firestore/src/model/target_index_matcher.d.ts +70 -0
  93. package/dist/lite/firestore/src/model/type_order.d.ts +2 -1
  94. package/dist/lite/firestore/src/model/values.d.ts +7 -0
  95. package/dist/lite/firestore/src/platform/node/base64.d.ts +1 -0
  96. package/dist/lite/firestore/src/platform/node/grpc_connection.d.ts +2 -2
  97. package/dist/lite/firestore/src/platform/node/load_protos.d.ts +2 -2
  98. package/dist/lite/firestore/src/util/sorted_set.d.ts +18 -0
  99. package/dist/lite/firestore/test/unit/local/counting_query_engine.d.ts +3 -3
  100. package/dist/lite/firestore/test/unit/local/document_overlay_cache.test.d.ts +1 -0
  101. package/dist/lite/firestore/test/unit/local/persistence_test_helpers.d.ts +1 -0
  102. package/dist/lite/firestore/test/unit/local/test_document_overlay_cache.d.ts +38 -0
  103. package/dist/lite/firestore/test/unit/local/test_index_manager.d.ts +11 -0
  104. package/dist/lite/firestore/test/unit/local/test_remote_document_cache.d.ts +5 -3
  105. package/dist/lite/firestore/test/unit/local/test_remote_document_change_buffer.d.ts +1 -1
  106. package/dist/lite/firestore/test/unit/model/field_index.test.d.ts +17 -0
  107. package/dist/lite/firestore/test/unit/model/target.test.d.ts +17 -0
  108. package/dist/lite/firestore/test/unit/model/target_index_matcher.test.d.ts +17 -0
  109. package/dist/lite/firestore/test/util/helpers.d.ts +8 -2
  110. package/dist/lite/index.browser.esm2017.js +1207 -1174
  111. package/dist/lite/index.browser.esm2017.js.map +1 -1
  112. package/dist/lite/index.browser.esm5.js +370 -361
  113. package/dist/lite/index.browser.esm5.js.map +1 -1
  114. package/dist/lite/index.node.cjs.js +83 -41
  115. package/dist/lite/index.node.cjs.js.map +1 -1
  116. package/dist/lite/index.node.mjs +83 -41
  117. package/dist/lite/index.node.mjs.map +1 -1
  118. package/dist/lite/index.rn.esm2017.js +375 -342
  119. package/dist/lite/index.rn.esm2017.js.map +1 -1
  120. package/dist/lite/internal.d.ts +16 -2
  121. package/dist/lite/packages/firestore/dist/lite/index.browser.esm2017.d.ts +125 -124
  122. package/dist/lite/packages/firestore/src/api/credentials.d.ts +1 -0
  123. package/dist/lite/packages/firestore/src/api/{query.d.ts → filter.d.ts} +0 -0
  124. package/dist/lite/packages/firestore/src/api/snapshot.d.ts +1 -2
  125. package/dist/lite/packages/firestore/src/api.d.ts +1 -1
  126. package/dist/lite/packages/firestore/src/core/database_info.d.ts +1 -0
  127. package/dist/lite/packages/firestore/src/core/target.d.ts +34 -5
  128. package/dist/lite/packages/firestore/src/index/index_byte_encoder.d.ts +18 -1
  129. package/dist/lite/packages/firestore/src/index/index_entry.d.ts +32 -0
  130. package/dist/lite/packages/firestore/src/lite-api/query.d.ts +2 -2
  131. package/dist/lite/packages/firestore/src/local/document_overlay_cache.d.ts +72 -0
  132. package/dist/lite/packages/firestore/src/local/index_manager.d.ts +52 -0
  133. package/dist/lite/packages/firestore/src/local/indexeddb_document_overlay_cache.d.ts +45 -0
  134. package/dist/lite/packages/firestore/src/local/indexeddb_index_manager.d.ts +87 -2
  135. package/dist/lite/packages/firestore/src/local/indexeddb_persistence.d.ts +6 -4
  136. package/dist/lite/packages/firestore/src/local/indexeddb_remote_document_cache.d.ts +2 -8
  137. package/dist/lite/packages/firestore/src/local/indexeddb_schema.d.ts +195 -7
  138. package/dist/lite/packages/firestore/src/local/local_documents_view.d.ts +0 -1
  139. package/dist/lite/packages/firestore/src/local/local_serializer.d.ts +19 -3
  140. package/dist/lite/packages/firestore/src/local/memory_document_overlay_cache.d.ts +37 -0
  141. package/dist/lite/packages/firestore/src/local/memory_index_manager.d.ts +11 -0
  142. package/dist/lite/packages/firestore/src/local/memory_persistence.d.ts +6 -2
  143. package/dist/lite/packages/firestore/src/local/memory_remote_document_cache.d.ts +1 -3
  144. package/dist/lite/packages/firestore/src/local/persistence.d.ts +8 -2
  145. package/dist/lite/packages/firestore/src/local/remote_document_cache.d.ts +7 -9
  146. package/dist/lite/packages/firestore/src/local/remote_document_change_buffer.d.ts +3 -12
  147. package/dist/lite/packages/firestore/src/local/simple_db.d.ts +12 -0
  148. package/dist/lite/packages/firestore/src/model/document.d.ts +7 -0
  149. package/dist/lite/packages/firestore/src/model/document_key.d.ts +6 -0
  150. package/dist/lite/packages/firestore/src/model/field_index.d.ts +89 -7
  151. package/dist/lite/packages/firestore/src/model/overlay.d.ts +32 -0
  152. package/dist/lite/packages/firestore/src/model/target_index_matcher.d.ts +70 -0
  153. package/dist/lite/packages/firestore/src/model/type_order.d.ts +2 -1
  154. package/dist/lite/packages/firestore/src/model/values.d.ts +7 -0
  155. package/dist/lite/packages/firestore/src/platform/node/base64.d.ts +1 -0
  156. package/dist/lite/packages/firestore/src/platform/node/grpc_connection.d.ts +2 -2
  157. package/dist/lite/packages/firestore/src/platform/node/load_protos.d.ts +2 -2
  158. package/dist/lite/packages/firestore/src/util/sorted_set.d.ts +18 -0
  159. package/dist/lite/packages/firestore/test/unit/local/counting_query_engine.d.ts +3 -3
  160. package/dist/lite/packages/firestore/test/unit/local/document_overlay_cache.test.d.ts +1 -0
  161. package/dist/lite/packages/firestore/test/unit/local/persistence_test_helpers.d.ts +1 -0
  162. package/dist/lite/packages/firestore/test/unit/local/test_document_overlay_cache.d.ts +38 -0
  163. package/dist/lite/packages/firestore/test/unit/local/test_index_manager.d.ts +11 -0
  164. package/dist/lite/packages/firestore/test/unit/local/test_remote_document_cache.d.ts +5 -3
  165. package/dist/lite/packages/firestore/test/unit/local/test_remote_document_change_buffer.d.ts +1 -1
  166. package/dist/lite/packages/firestore/test/unit/model/field_index.test.d.ts +17 -0
  167. package/dist/lite/packages/firestore/test/unit/model/target.test.d.ts +17 -0
  168. package/dist/lite/packages/firestore/test/unit/model/target_index_matcher.test.d.ts +17 -0
  169. package/dist/lite/packages/firestore/test/util/helpers.d.ts +8 -2
  170. package/dist/lite/private.d.ts +9 -2
  171. package/dist/packages/firestore/dist/index.esm2017.d.ts +199 -194
  172. package/dist/packages/firestore/src/api/credentials.d.ts +1 -0
  173. package/dist/packages/firestore/src/api/{query.d.ts → filter.d.ts} +0 -0
  174. package/dist/packages/firestore/src/api/snapshot.d.ts +1 -2
  175. package/dist/packages/firestore/src/api.d.ts +1 -1
  176. package/dist/packages/firestore/src/core/database_info.d.ts +1 -0
  177. package/dist/packages/firestore/src/core/target.d.ts +34 -5
  178. package/dist/packages/firestore/src/index/index_byte_encoder.d.ts +18 -1
  179. package/dist/packages/firestore/src/index/index_entry.d.ts +32 -0
  180. package/dist/packages/firestore/src/lite-api/query.d.ts +2 -2
  181. package/dist/packages/firestore/src/local/document_overlay_cache.d.ts +72 -0
  182. package/dist/packages/firestore/src/local/index_manager.d.ts +52 -0
  183. package/dist/packages/firestore/src/local/indexeddb_document_overlay_cache.d.ts +45 -0
  184. package/dist/packages/firestore/src/local/indexeddb_index_manager.d.ts +87 -2
  185. package/dist/packages/firestore/src/local/indexeddb_persistence.d.ts +6 -4
  186. package/dist/packages/firestore/src/local/indexeddb_remote_document_cache.d.ts +2 -8
  187. package/dist/packages/firestore/src/local/indexeddb_schema.d.ts +195 -7
  188. package/dist/packages/firestore/src/local/local_documents_view.d.ts +0 -1
  189. package/dist/packages/firestore/src/local/local_serializer.d.ts +19 -3
  190. package/dist/packages/firestore/src/local/memory_document_overlay_cache.d.ts +37 -0
  191. package/dist/packages/firestore/src/local/memory_index_manager.d.ts +11 -0
  192. package/dist/packages/firestore/src/local/memory_persistence.d.ts +6 -2
  193. package/dist/packages/firestore/src/local/memory_remote_document_cache.d.ts +1 -3
  194. package/dist/packages/firestore/src/local/persistence.d.ts +8 -2
  195. package/dist/packages/firestore/src/local/remote_document_cache.d.ts +7 -9
  196. package/dist/packages/firestore/src/local/remote_document_change_buffer.d.ts +3 -12
  197. package/dist/packages/firestore/src/local/simple_db.d.ts +12 -0
  198. package/dist/packages/firestore/src/model/document.d.ts +7 -0
  199. package/dist/packages/firestore/src/model/document_key.d.ts +6 -0
  200. package/dist/packages/firestore/src/model/field_index.d.ts +89 -7
  201. package/dist/packages/firestore/src/model/overlay.d.ts +32 -0
  202. package/dist/packages/firestore/src/model/target_index_matcher.d.ts +70 -0
  203. package/dist/packages/firestore/src/model/type_order.d.ts +2 -1
  204. package/dist/packages/firestore/src/model/values.d.ts +7 -0
  205. package/dist/packages/firestore/src/platform/node/base64.d.ts +1 -0
  206. package/dist/packages/firestore/src/platform/node/grpc_connection.d.ts +2 -2
  207. package/dist/packages/firestore/src/platform/node/load_protos.d.ts +2 -2
  208. package/dist/packages/firestore/src/util/sorted_set.d.ts +18 -0
  209. package/dist/packages/firestore/test/unit/local/counting_query_engine.d.ts +3 -3
  210. package/dist/packages/firestore/test/unit/local/document_overlay_cache.test.d.ts +1 -0
  211. package/dist/packages/firestore/test/unit/local/persistence_test_helpers.d.ts +1 -0
  212. package/dist/packages/firestore/test/unit/local/test_document_overlay_cache.d.ts +38 -0
  213. package/dist/packages/firestore/test/unit/local/test_index_manager.d.ts +11 -0
  214. package/dist/packages/firestore/test/unit/local/test_remote_document_cache.d.ts +5 -3
  215. package/dist/packages/firestore/test/unit/local/test_remote_document_change_buffer.d.ts +1 -1
  216. package/dist/packages/firestore/test/unit/model/field_index.test.d.ts +17 -0
  217. package/dist/packages/firestore/test/unit/model/target.test.d.ts +17 -0
  218. package/dist/packages/firestore/test/unit/model/target_index_matcher.test.d.ts +17 -0
  219. package/dist/packages/firestore/test/util/helpers.d.ts +8 -2
  220. package/dist/private.d.ts +258 -26
  221. package/package.json +9 -9
@@ -8,11 +8,32 @@ var logger = require('@firebase/logger');
8
8
  var util = require('util');
9
9
  var util$1 = require('@firebase/util');
10
10
  var crypto = require('crypto');
11
- var grpcJs = require('@grpc/grpc-js');
11
+ var grpc = require('@grpc/grpc-js');
12
12
  var protoLoader = require('@grpc/proto-loader');
13
13
 
14
+ function _interopNamespace(e) {
15
+ if (e && e.__esModule) return e;
16
+ var n = Object.create(null);
17
+ if (e) {
18
+ Object.keys(e).forEach(function (k) {
19
+ if (k !== 'default') {
20
+ var d = Object.getOwnPropertyDescriptor(e, k);
21
+ Object.defineProperty(n, k, d.get ? d : {
22
+ enumerable: true,
23
+ get: function () { return e[k]; }
24
+ });
25
+ }
26
+ });
27
+ }
28
+ n["default"] = e;
29
+ return Object.freeze(n);
30
+ }
31
+
32
+ var grpc__namespace = /*#__PURE__*/_interopNamespace(grpc);
33
+ var protoLoader__namespace = /*#__PURE__*/_interopNamespace(protoLoader);
34
+
14
35
  const name = "@firebase/firestore";
15
- const version$1 = "3.4.4";
36
+ const version$1 = "3.4.5-canary.8ac8fb099";
16
37
 
17
38
  /**
18
39
  * @license
@@ -65,7 +86,7 @@ User.GOOGLE_CREDENTIALS = new User('google-credentials-uid');
65
86
  User.FIRST_PARTY = new User('first-party-uid');
66
87
  User.MOCK_USER = new User('mock-user');
67
88
 
68
- const version = "9.6.5";
89
+ const version = "9.6.7-canary.8ac8fb099";
69
90
 
70
91
  /**
71
92
  * @license
@@ -670,13 +691,19 @@ class FirebaseAppCheckTokenProvider {
670
691
  this.appCheckProvider = appCheckProvider;
671
692
  this.forceRefresh = false;
672
693
  this.appCheck = null;
694
+ this.latestAppCheckToken = null;
673
695
  }
674
696
  start(asyncQueue, changeListener) {
675
697
  const onTokenChanged = tokenResult => {
676
698
  if (tokenResult.error != null) {
677
699
  logDebug('FirebaseAppCheckTokenProvider', `Error getting App Check token; using placeholder token instead. Error: ${tokenResult.error.message}`);
678
700
  }
679
- return changeListener(tokenResult.token);
701
+ const tokenUpdated = tokenResult.token !== this.latestAppCheckToken;
702
+ this.latestAppCheckToken = tokenResult.token;
703
+ logDebug('FirebaseAppCheckTokenProvider', `Received ${tokenUpdated ? 'new' : 'existing'} token.`);
704
+ return tokenUpdated
705
+ ? changeListener(tokenResult.token)
706
+ : Promise.resolve();
680
707
  };
681
708
  this.tokenListener = (tokenResult) => {
682
709
  asyncQueue.enqueueRetryable(() => onTokenChanged(tokenResult));
@@ -711,6 +738,7 @@ class FirebaseAppCheckTokenProvider {
711
738
  return this.appCheck.getToken(forceRefresh).then(tokenResult => {
712
739
  if (tokenResult) {
713
740
  hardAssert(typeof tokenResult.token === 'string');
741
+ this.latestAppCheckToken = tokenResult.token;
714
742
  return new AppCheckToken(tokenResult.token);
715
743
  }
716
744
  else {
@@ -1196,23 +1224,6 @@ function decodeResourcePath(path) {
1196
1224
  }
1197
1225
  return new ResourcePath(segments);
1198
1226
  }
1199
-
1200
- /**
1201
- * @license
1202
- * Copyright 2017 Google LLC
1203
- *
1204
- * Licensed under the Apache License, Version 2.0 (the "License");
1205
- * you may not use this file except in compliance with the License.
1206
- * You may obtain a copy of the License at
1207
- *
1208
- * http://www.apache.org/licenses/LICENSE-2.0
1209
- *
1210
- * Unless required by applicable law or agreed to in writing, software
1211
- * distributed under the License is distributed on an "AS IS" BASIS,
1212
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1213
- * See the License for the specific language governing permissions and
1214
- * limitations under the License.
1215
- */
1216
1227
  /**
1217
1228
  * Schema Version for the Web client:
1218
1229
  * 1. Initial version including Mutation Queue, Query Cache, and Remote
@@ -1231,8 +1242,10 @@ function decodeResourcePath(path) {
1231
1242
  * an auto-incrementing ID. This is required for Index-Free queries.
1232
1243
  * 10. Rewrite the canonical IDs to the explicit Protobuf-based format.
1233
1244
  * 11. Add bundles and named_queries for bundle support.
1245
+ * 12. Add document overlays.
1246
+ * 13. Add indexing support.
1234
1247
  */
1235
- const SCHEMA_VERSION = 11;
1248
+ const SCHEMA_VERSION = 12;
1236
1249
  /**
1237
1250
  * Wrapper class to store timestamps (seconds and nanos) in IndexedDb objects.
1238
1251
  */
@@ -1757,9 +1770,7 @@ class DbClientMetadata {
1757
1770
  DbClientMetadata.store = 'clientMetadata';
1758
1771
  /** Keys are automatically assigned via the clientId properties. */
1759
1772
  DbClientMetadata.keyPath = 'clientId';
1760
- /**
1761
- * A object representing a bundle loaded by the SDK.
1762
- */
1773
+ /** An object representing a bundle loaded by the SDK. */
1763
1774
  class DbBundle {
1764
1775
  constructor(
1765
1776
  /** The ID of the loaded bundle. */
@@ -1776,9 +1787,7 @@ class DbBundle {
1776
1787
  /** Name of the IndexedDb object store. */
1777
1788
  DbBundle.store = 'bundles';
1778
1789
  DbBundle.keyPath = 'bundleId';
1779
- /**
1780
- * A object representing a named query loaded by the SDK via a bundle.
1781
- */
1790
+ /** An object representing a named query loaded by the SDK via a bundle. */
1782
1791
  class DbNamedQuery {
1783
1792
  constructor(
1784
1793
  /** The name of the query. */
@@ -1795,6 +1804,158 @@ class DbNamedQuery {
1795
1804
  /** Name of the IndexedDb object store. */
1796
1805
  DbNamedQuery.store = 'namedQueries';
1797
1806
  DbNamedQuery.keyPath = 'name';
1807
+ /** An object representing the global configuration for a field index. */
1808
+ class DbIndexConfiguration {
1809
+ constructor(
1810
+ /**
1811
+ * The index id for this entry. Undefined for indexes that are not yet
1812
+ * persisted.
1813
+ */
1814
+ indexId,
1815
+ /** The collection group this index belongs to. */
1816
+ collectionGroup,
1817
+ /** The fields to index for this index. */
1818
+ fields) {
1819
+ this.indexId = indexId;
1820
+ this.collectionGroup = collectionGroup;
1821
+ this.fields = fields;
1822
+ }
1823
+ }
1824
+ /** Name of the IndexedDb object store. */
1825
+ DbIndexConfiguration.store = 'indexConfiguration';
1826
+ DbIndexConfiguration.keyPath = 'indexId';
1827
+ /**
1828
+ * An index that provides access to the index configurations by collection
1829
+ * group.
1830
+ *
1831
+ * PORTING NOTE: iOS and Android maintain this index in-memory, but this is
1832
+ * not possible here as the Web client supports concurrent access to
1833
+ * persistence via multi-tab.
1834
+ */
1835
+ DbIndexConfiguration.collectionGroupIndex = 'collectionGroupIndex';
1836
+ DbIndexConfiguration.collectionGroupIndexPath = 'collectionGroup';
1837
+ /**
1838
+ * An object describing how up-to-date the index backfill is for each user and
1839
+ * index.
1840
+ */
1841
+ class DbIndexState {
1842
+ constructor(
1843
+ /** The index id for this entry. */
1844
+ indexId,
1845
+ /** The user id for this entry. */
1846
+ uid,
1847
+ /**
1848
+ * A number that indicates when the index was last updated (relative to
1849
+ * other indexes).
1850
+ */
1851
+ sequenceNumber,
1852
+ /**
1853
+ * The latest read time that has been indexed by Firestore for this field
1854
+ * index. Set to `{seconds: 0, nanos: 0}` if no documents have been indexed.
1855
+ */
1856
+ readTime,
1857
+ /**
1858
+ * The last document that has been indexed for this field index. Empty if
1859
+ * no documents have been indexed.
1860
+ */
1861
+ documentKey,
1862
+ /**
1863
+ * The largest mutation batch id that has been processed for this index. -1
1864
+ * if no mutations have been indexed.
1865
+ */
1866
+ largestBatchId) {
1867
+ this.indexId = indexId;
1868
+ this.uid = uid;
1869
+ this.sequenceNumber = sequenceNumber;
1870
+ this.readTime = readTime;
1871
+ this.documentKey = documentKey;
1872
+ this.largestBatchId = largestBatchId;
1873
+ }
1874
+ }
1875
+ /** Name of the IndexedDb object store. */
1876
+ DbIndexState.store = 'indexState';
1877
+ DbIndexState.keyPath = ['indexId', 'uid'];
1878
+ /**
1879
+ * An index that provides access to documents in a collection sorted by last
1880
+ * update time. Used by the backfiller.
1881
+ *
1882
+ * PORTING NOTE: iOS and Android maintain this index in-memory, but this is
1883
+ * not possible here as the Web client supports concurrent access to
1884
+ * persistence via multi-tab.
1885
+ */
1886
+ DbIndexState.sequenceNumberIndex = 'sequenceNumberIndex';
1887
+ DbIndexState.sequenceNumberIndexPath = ['uid', 'sequenceNumber'];
1888
+ /** An object that stores the encoded entries for all documents and fields. */
1889
+ class DbIndexEntry {
1890
+ constructor(
1891
+ /** The index id for this entry. */
1892
+ indexId,
1893
+ /** The user id for this entry. */
1894
+ uid,
1895
+ /** The encoded array index value for this entry. */
1896
+ arrayValue,
1897
+ /** The encoded directional value for equality and inequality filters. */
1898
+ directionalValue,
1899
+ /** The document key this entry points to. */
1900
+ documentKey) {
1901
+ this.indexId = indexId;
1902
+ this.uid = uid;
1903
+ this.arrayValue = arrayValue;
1904
+ this.directionalValue = directionalValue;
1905
+ this.documentKey = documentKey;
1906
+ }
1907
+ }
1908
+ /** Name of the IndexedDb object store. */
1909
+ DbIndexEntry.store = 'indexEntries';
1910
+ DbIndexEntry.keyPath = [
1911
+ 'indexId',
1912
+ 'uid',
1913
+ 'arrayValue',
1914
+ 'directionalValue',
1915
+ 'documentKey'
1916
+ ];
1917
+ DbIndexEntry.documentKeyIndex = 'documentKeyIndex';
1918
+ DbIndexEntry.documentKeyIndexPath = ['indexId', 'uid', 'documentKey'];
1919
+ /**
1920
+ * An object representing a document overlay.
1921
+ */
1922
+ class DbDocumentOverlay {
1923
+ constructor(
1924
+ /** The user ID to whom this overlay belongs. */
1925
+ userId,
1926
+ /** The path to the collection that contains the document. */
1927
+ collectionPath,
1928
+ /** The ID (key) of the document within the collection. */
1929
+ documentId,
1930
+ /** The collection group to which the document belongs. */
1931
+ collectionGroup,
1932
+ /** The largest batch ID that's been applied for this overlay. */
1933
+ largestBatchId,
1934
+ /** The overlay mutation. */
1935
+ overlayMutation) {
1936
+ this.userId = userId;
1937
+ this.collectionPath = collectionPath;
1938
+ this.documentId = documentId;
1939
+ this.collectionGroup = collectionGroup;
1940
+ this.largestBatchId = largestBatchId;
1941
+ this.overlayMutation = overlayMutation;
1942
+ }
1943
+ }
1944
+ /** Name of the IndexedDb object store. */
1945
+ DbDocumentOverlay.store = 'documentOverlays';
1946
+ DbDocumentOverlay.keyPath = ['userId', 'collectionPath', 'documentId'];
1947
+ DbDocumentOverlay.collectionPathOverlayIndex = 'collectionPathOverlayIndex';
1948
+ DbDocumentOverlay.collectionPathOverlayIndexPath = [
1949
+ 'userId',
1950
+ 'collectionPath',
1951
+ 'largestBatchId'
1952
+ ];
1953
+ DbDocumentOverlay.collectionGroupOverlayIndex = 'collectionGroupOverlayIndex';
1954
+ DbDocumentOverlay.collectionGroupOverlayIndexPath = [
1955
+ 'userId',
1956
+ 'collectionGroup',
1957
+ 'largestBatchId'
1958
+ ];
1798
1959
  // Visible for testing
1799
1960
  const V1_STORES = [
1800
1961
  DbMutationQueue.store,
@@ -1809,7 +1970,6 @@ const V1_STORES = [
1809
1970
  // V2 is no longer usable (see comment at top of file)
1810
1971
  // Visible for testing
1811
1972
  const V3_STORES = V1_STORES;
1812
- // Visible for testing
1813
1973
  // Note: DbRemoteDocumentChanges is no longer used and dropped with v9.
1814
1974
  const V4_STORES = [...V3_STORES, DbClientMetadata.store];
1815
1975
  // V5 does not change the set of stores.
@@ -1819,12 +1979,28 @@ const V8_STORES = [...V6_STORES, DbCollectionParent.store];
1819
1979
  // V9 does not change the set of stores.
1820
1980
  // V10 does not change the set of stores.
1821
1981
  const V11_STORES = [...V8_STORES, DbBundle.store, DbNamedQuery.store];
1822
- /**
1823
- * The list of all default IndexedDB stores used throughout the SDK. This is
1824
- * used when creating transactions so that access across all stores is done
1825
- * atomically.
1826
- */
1827
- const ALL_STORES = V11_STORES;
1982
+ const V12_STORES = [...V11_STORES, DbDocumentOverlay.store];
1983
+ const V13_STORES = [
1984
+ ...V12_STORES,
1985
+ DbIndexConfiguration.store,
1986
+ DbIndexState.store,
1987
+ DbIndexEntry.store
1988
+ ];
1989
+ /** Returns the object stores for the provided schema. */
1990
+ function getObjectStores(schemaVersion) {
1991
+ if (schemaVersion === 13) {
1992
+ return V13_STORES;
1993
+ }
1994
+ else if (schemaVersion === 12) {
1995
+ return V12_STORES;
1996
+ }
1997
+ else if (schemaVersion === 11) {
1998
+ return V11_STORES;
1999
+ }
2000
+ else {
2001
+ fail();
2002
+ }
2003
+ }
1828
2004
 
1829
2005
  /**
1830
2006
  * @license
@@ -2065,7 +2241,7 @@ class PersistencePromise {
2065
2241
  */
2066
2242
  // References to `window` are guarded by SimpleDb.isAvailable()
2067
2243
  /* eslint-disable no-restricted-globals */
2068
- const LOG_TAG$g = 'SimpleDb';
2244
+ const LOG_TAG$h = 'SimpleDb';
2069
2245
  /**
2070
2246
  * The maximum number of retry attempts for an IndexedDb transaction that fails
2071
2247
  * with a DOMException.
@@ -2116,11 +2292,20 @@ class SimpleDbTransaction {
2116
2292
  this.completionDeferred.reject(error);
2117
2293
  }
2118
2294
  if (!this.aborted) {
2119
- logDebug(LOG_TAG$g, 'Aborting transaction:', error ? error.message : 'Client-initiated abort');
2295
+ logDebug(LOG_TAG$h, 'Aborting transaction:', error ? error.message : 'Client-initiated abort');
2120
2296
  this.aborted = true;
2121
2297
  this.transaction.abort();
2122
2298
  }
2123
2299
  }
2300
+ maybeCommit() {
2301
+ // If the browser supports V3 IndexedDB, we invoke commit() explicitly to
2302
+ // speed up index DB processing if the event loop remains blocks.
2303
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2304
+ const maybeV3IndexedDb = this.transaction;
2305
+ if (!this.aborted && typeof maybeV3IndexedDb.commit === 'function') {
2306
+ maybeV3IndexedDb.commit();
2307
+ }
2308
+ }
2124
2309
  /**
2125
2310
  * Returns a SimpleDbStore<KeyType, ValueType> for the specified store. All
2126
2311
  * operations performed on the SimpleDbStore happen within the context of this
@@ -2170,7 +2355,7 @@ class SimpleDb {
2170
2355
  }
2171
2356
  /** Deletes the specified database. */
2172
2357
  static delete(name) {
2173
- logDebug(LOG_TAG$g, 'Removing database:', name);
2358
+ logDebug(LOG_TAG$h, 'Removing database:', name);
2174
2359
  return wrapRequest(window.indexedDB.deleteDatabase(name)).toPromise();
2175
2360
  }
2176
2361
  /** Returns true if IndexedDB is available in the current environment. */
@@ -2249,7 +2434,7 @@ class SimpleDb {
2249
2434
  */
2250
2435
  async ensureDb(action) {
2251
2436
  if (!this.db) {
2252
- logDebug(LOG_TAG$g, 'Opening database:', this.name);
2437
+ logDebug(LOG_TAG$h, 'Opening database:', this.name);
2253
2438
  this.db = await new Promise((resolve, reject) => {
2254
2439
  // TODO(mikelehen): Investigate browser compatibility.
2255
2440
  // https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB
@@ -2285,12 +2470,12 @@ class SimpleDb {
2285
2470
  }
2286
2471
  };
2287
2472
  request.onupgradeneeded = (event) => {
2288
- logDebug(LOG_TAG$g, 'Database "' + this.name + '" requires upgrade from version:', event.oldVersion);
2473
+ logDebug(LOG_TAG$h, 'Database "' + this.name + '" requires upgrade from version:', event.oldVersion);
2289
2474
  const db = event.target.result;
2290
2475
  this.schemaConverter
2291
2476
  .createOrUpgrade(db, request.transaction, event.oldVersion, this.version)
2292
2477
  .next(() => {
2293
- logDebug(LOG_TAG$g, 'Database upgrade to version ' + this.version + ' complete');
2478
+ logDebug(LOG_TAG$h, 'Database upgrade to version ' + this.version + ' complete');
2294
2479
  });
2295
2480
  };
2296
2481
  });
@@ -2317,6 +2502,10 @@ class SimpleDb {
2317
2502
  this.db = await this.ensureDb(action);
2318
2503
  const transaction = SimpleDbTransaction.open(this.db, action, readonly ? 'readonly' : 'readwrite', objectStores);
2319
2504
  const transactionFnResult = transactionFn(transaction)
2505
+ .next(result => {
2506
+ transaction.maybeCommit();
2507
+ return result;
2508
+ })
2320
2509
  .catch(error => {
2321
2510
  // Abort the transaction if there was an error.
2322
2511
  transaction.abort(error);
@@ -2344,7 +2533,7 @@ class SimpleDb {
2344
2533
  // exception is wrapped in a generic error by our async/await handling.
2345
2534
  const retryable = error.name !== 'FirebaseError' &&
2346
2535
  attemptNumber < TRANSACTION_RETRY_COUNT;
2347
- logDebug(LOG_TAG$g, 'Transaction failed with error:', error.message, 'Retrying:', retryable);
2536
+ logDebug(LOG_TAG$h, 'Transaction failed with error:', error.message, 'Retrying:', retryable);
2348
2537
  this.close();
2349
2538
  if (!retryable) {
2350
2539
  return Promise.reject(error);
@@ -2431,11 +2620,11 @@ class SimpleDbStore {
2431
2620
  put(keyOrValue, value) {
2432
2621
  let request;
2433
2622
  if (value !== undefined) {
2434
- logDebug(LOG_TAG$g, 'PUT', this.store.name, keyOrValue, value);
2623
+ logDebug(LOG_TAG$h, 'PUT', this.store.name, keyOrValue, value);
2435
2624
  request = this.store.put(value, keyOrValue);
2436
2625
  }
2437
2626
  else {
2438
- logDebug(LOG_TAG$g, 'PUT', this.store.name, '<auto-key>', keyOrValue);
2627
+ logDebug(LOG_TAG$h, 'PUT', this.store.name, '<auto-key>', keyOrValue);
2439
2628
  request = this.store.put(keyOrValue);
2440
2629
  }
2441
2630
  return wrapRequest(request);
@@ -2448,7 +2637,7 @@ class SimpleDbStore {
2448
2637
  * @returns The key of the value to add.
2449
2638
  */
2450
2639
  add(value) {
2451
- logDebug(LOG_TAG$g, 'ADD', this.store.name, value, value);
2640
+ logDebug(LOG_TAG$h, 'ADD', this.store.name, value, value);
2452
2641
  const request = this.store.add(value);
2453
2642
  return wrapRequest(request);
2454
2643
  }
@@ -2468,12 +2657,12 @@ class SimpleDbStore {
2468
2657
  if (result === undefined) {
2469
2658
  result = null;
2470
2659
  }
2471
- logDebug(LOG_TAG$g, 'GET', this.store.name, key, result);
2660
+ logDebug(LOG_TAG$h, 'GET', this.store.name, key, result);
2472
2661
  return result;
2473
2662
  });
2474
2663
  }
2475
2664
  delete(key) {
2476
- logDebug(LOG_TAG$g, 'DELETE', this.store.name, key);
2665
+ logDebug(LOG_TAG$h, 'DELETE', this.store.name, key);
2477
2666
  const request = this.store.delete(key);
2478
2667
  return wrapRequest(request);
2479
2668
  }
@@ -2484,21 +2673,52 @@ class SimpleDbStore {
2484
2673
  * Returns the number of rows in the store.
2485
2674
  */
2486
2675
  count() {
2487
- logDebug(LOG_TAG$g, 'COUNT', this.store.name);
2676
+ logDebug(LOG_TAG$h, 'COUNT', this.store.name);
2488
2677
  const request = this.store.count();
2489
2678
  return wrapRequest(request);
2490
2679
  }
2491
2680
  loadAll(indexOrRange, range) {
2492
- const cursor = this.cursor(this.options(indexOrRange, range));
2493
- const results = [];
2494
- return this.iterateCursor(cursor, (key, value) => {
2495
- results.push(value);
2496
- }).next(() => {
2497
- return results;
2681
+ const iterateOptions = this.options(indexOrRange, range);
2682
+ // Use `getAll()` if the browser supports IndexedDB v3, as it is roughly
2683
+ // 20% faster. Unfortunately, getAll() does not support custom indices.
2684
+ if (!iterateOptions.index && typeof this.store.getAll === 'function') {
2685
+ const request = this.store.getAll(iterateOptions.range);
2686
+ return new PersistencePromise((resolve, reject) => {
2687
+ request.onerror = (event) => {
2688
+ reject(event.target.error);
2689
+ };
2690
+ request.onsuccess = (event) => {
2691
+ resolve(event.target.result);
2692
+ };
2693
+ });
2694
+ }
2695
+ else {
2696
+ const cursor = this.cursor(iterateOptions);
2697
+ const results = [];
2698
+ return this.iterateCursor(cursor, (key, value) => {
2699
+ results.push(value);
2700
+ }).next(() => {
2701
+ return results;
2702
+ });
2703
+ }
2704
+ }
2705
+ /**
2706
+ * Loads the first `count` elements from the provided index range. Loads all
2707
+ * elements if no limit is provided.
2708
+ */
2709
+ loadFirst(range, count) {
2710
+ const request = this.store.getAll(range, count === null ? undefined : count);
2711
+ return new PersistencePromise((resolve, reject) => {
2712
+ request.onerror = (event) => {
2713
+ reject(event.target.error);
2714
+ };
2715
+ request.onsuccess = (event) => {
2716
+ resolve(event.target.result);
2717
+ };
2498
2718
  });
2499
2719
  }
2500
2720
  deleteAll(indexOrRange, range) {
2501
- logDebug(LOG_TAG$g, 'DELETE ALL', this.store.name);
2721
+ logDebug(LOG_TAG$h, 'DELETE ALL', this.store.name);
2502
2722
  const options = this.options(indexOrRange, range);
2503
2723
  options.keysOnly = false;
2504
2724
  const cursor = this.cursor(options);
@@ -3107,17 +3327,15 @@ class FieldMask {
3107
3327
  * See the License for the specific language governing permissions and
3108
3328
  * limitations under the License.
3109
3329
  */
3330
+ /** Converts a Base64 encoded string to a binary string. */
3110
3331
  function decodeBase64(encoded) {
3111
- // Node actually doesn't validate base64 strings.
3112
- // A quick sanity check that is not a fool-proof validation
3113
- if (/[^-A-Za-z0-9+/=]/.test(encoded)) {
3114
- throw new FirestoreError(Code.INVALID_ARGUMENT, 'Not a valid Base64 string: ' + encoded);
3115
- }
3116
- return new Buffer(encoded, 'base64').toString('binary');
3332
+ // Note: We used to validate the base64 string here via a regular expression.
3333
+ // This was removed to improve the performance of indexing.
3334
+ return Buffer.from(encoded, 'base64').toString('binary');
3117
3335
  }
3118
3336
  /** Converts a binary string to a Base64 encoded string. */
3119
3337
  function encodeBase64(raw) {
3120
- return new Buffer(raw, 'binary').toString('base64');
3338
+ return Buffer.from(raw, 'binary').toString('base64');
3121
3339
  }
3122
3340
  /** True if and only if the Base64 conversion functions are available. */
3123
3341
  function isBase64Available() {
@@ -3158,6 +3376,8 @@ class ByteString {
3158
3376
  return new ByteString(binaryString);
3159
3377
  }
3160
3378
  static fromUint8Array(array) {
3379
+ // TODO(indexing); Remove the copy of the byte string here as this method
3380
+ // is frequently called during indexing.
3161
3381
  const binaryString = binaryStringFromUint8Array(array);
3162
3382
  return new ByteString(binaryString);
3163
3383
  }
@@ -3378,6 +3598,75 @@ function getLocalWriteTime(value) {
3378
3598
  return new Timestamp(localWriteTime.seconds, localWriteTime.nanos);
3379
3599
  }
3380
3600
 
3601
+ /**
3602
+ * @license
3603
+ * Copyright 2017 Google LLC
3604
+ *
3605
+ * Licensed under the Apache License, Version 2.0 (the "License");
3606
+ * you may not use this file except in compliance with the License.
3607
+ * You may obtain a copy of the License at
3608
+ *
3609
+ * http://www.apache.org/licenses/LICENSE-2.0
3610
+ *
3611
+ * Unless required by applicable law or agreed to in writing, software
3612
+ * distributed under the License is distributed on an "AS IS" BASIS,
3613
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3614
+ * See the License for the specific language governing permissions and
3615
+ * limitations under the License.
3616
+ */
3617
+ class DatabaseInfo {
3618
+ /**
3619
+ * Constructs a DatabaseInfo using the provided host, databaseId and
3620
+ * persistenceKey.
3621
+ *
3622
+ * @param databaseId - The database to use.
3623
+ * @param appId - The Firebase App Id.
3624
+ * @param persistenceKey - A unique identifier for this Firestore's local
3625
+ * storage (used in conjunction with the databaseId).
3626
+ * @param host - The Firestore backend host to connect to.
3627
+ * @param ssl - Whether to use SSL when connecting.
3628
+ * @param forceLongPolling - Whether to use the forceLongPolling option
3629
+ * when using WebChannel as the network transport.
3630
+ * @param autoDetectLongPolling - Whether to use the detectBufferingProxy
3631
+ * option when using WebChannel as the network transport.
3632
+ * @param useFetchStreams Whether to use the Fetch API instead of
3633
+ * XMLHTTPRequest
3634
+ */
3635
+ constructor(databaseId, appId, persistenceKey, host, ssl, forceLongPolling, autoDetectLongPolling, useFetchStreams) {
3636
+ this.databaseId = databaseId;
3637
+ this.appId = appId;
3638
+ this.persistenceKey = persistenceKey;
3639
+ this.host = host;
3640
+ this.ssl = ssl;
3641
+ this.forceLongPolling = forceLongPolling;
3642
+ this.autoDetectLongPolling = autoDetectLongPolling;
3643
+ this.useFetchStreams = useFetchStreams;
3644
+ }
3645
+ }
3646
+ /** The default database name for a project. */
3647
+ const DEFAULT_DATABASE_NAME = '(default)';
3648
+ /**
3649
+ * Represents the database ID a Firestore client is associated with.
3650
+ * @internal
3651
+ */
3652
+ class DatabaseId {
3653
+ constructor(projectId, database) {
3654
+ this.projectId = projectId;
3655
+ this.database = database ? database : DEFAULT_DATABASE_NAME;
3656
+ }
3657
+ static empty() {
3658
+ return new DatabaseId('', '');
3659
+ }
3660
+ get isDefaultDatabase() {
3661
+ return this.database === DEFAULT_DATABASE_NAME;
3662
+ }
3663
+ isEqual(other) {
3664
+ return (other instanceof DatabaseId &&
3665
+ other.projectId === this.projectId &&
3666
+ other.database === this.database);
3667
+ }
3668
+ }
3669
+
3381
3670
  /**
3382
3671
  * @license
3383
3672
  * Copyright 2017 Google LLC
@@ -3449,11 +3738,25 @@ class DocumentKey {
3449
3738
  static fromName(name) {
3450
3739
  return new DocumentKey(ResourcePath.fromString(name).popFirst(5));
3451
3740
  }
3741
+ static empty() {
3742
+ return new DocumentKey(ResourcePath.emptyPath());
3743
+ }
3744
+ get collectionGroup() {
3745
+ return this.path.popLast().lastSegment();
3746
+ }
3452
3747
  /** Returns true if the document is in the specified collectionId. */
3453
3748
  hasCollectionId(collectionId) {
3454
3749
  return (this.path.length >= 2 &&
3455
3750
  this.path.get(this.path.length - 2) === collectionId);
3456
3751
  }
3752
+ /** Returns the collection group (i.e. the name of the parent collection) for this key. */
3753
+ getCollectionGroup() {
3754
+ return this.path.get(this.path.length - 2);
3755
+ }
3756
+ /** Returns the fully qualified path to the parent collection. */
3757
+ getCollectionPath() {
3758
+ return this.path.popLast();
3759
+ }
3457
3760
  isEqual(other) {
3458
3761
  return (other !== null && ResourcePath.comparator(this.path, other.path) === 0);
3459
3762
  }
@@ -3493,6 +3796,17 @@ class DocumentKey {
3493
3796
  * See the License for the specific language governing permissions and
3494
3797
  * limitations under the License.
3495
3798
  */
3799
+ const MAX_VALUE_TYPE = '__max__';
3800
+ const MAX_VALUE = {
3801
+ mapValue: {
3802
+ fields: {
3803
+ '__type__': { stringValue: MAX_VALUE_TYPE }
3804
+ }
3805
+ }
3806
+ };
3807
+ const MIN_VALUE = {
3808
+ nullValue: 'NULL_VALUE'
3809
+ };
3496
3810
  /** Extracts the backend's type order for the provided value. */
3497
3811
  function typeOrder(value) {
3498
3812
  if ('nullValue' in value) {
@@ -3526,6 +3840,9 @@ function typeOrder(value) {
3526
3840
  if (isServerTimestamp(value)) {
3527
3841
  return 4 /* ServerTimestampValue */;
3528
3842
  }
3843
+ else if (isMaxValue(value)) {
3844
+ return 9 /* ArrayValue */;
3845
+ }
3529
3846
  return 10 /* ObjectValue */;
3530
3847
  }
3531
3848
  else {
@@ -3565,6 +3882,8 @@ function valueEquals(left, right) {
3565
3882
  return arrayEquals(left.arrayValue.values || [], right.arrayValue.values || [], valueEquals);
3566
3883
  case 10 /* ObjectValue */:
3567
3884
  return objectEquals(left, right);
3885
+ case 9007199254740991 /* MaxValue */:
3886
+ return true;
3568
3887
  default:
3569
3888
  return fail();
3570
3889
  }
@@ -3637,6 +3956,7 @@ function valueCompare(left, right) {
3637
3956
  }
3638
3957
  switch (leftType) {
3639
3958
  case 0 /* NullValue */:
3959
+ case 9007199254740991 /* MaxValue */:
3640
3960
  return 0;
3641
3961
  case 1 /* BooleanValue */:
3642
3962
  return primitiveComparator(left.booleanValue, right.booleanValue);
@@ -3902,6 +4222,105 @@ function deepClone(source) {
3902
4222
  else {
3903
4223
  return Object.assign({}, source);
3904
4224
  }
4225
+ }
4226
+ /** Returns true if the Value represents the canonical {@link #MAX_VALUE} . */
4227
+ function isMaxValue(value) {
4228
+ return ((((value.mapValue || {}).fields || {})['__type__'] || {}).stringValue ===
4229
+ MAX_VALUE_TYPE);
4230
+ }
4231
+ /** Returns the lowest value for the given value type (inclusive). */
4232
+ function valuesGetLowerBound(value) {
4233
+ if ('nullValue' in value) {
4234
+ return MIN_VALUE;
4235
+ }
4236
+ else if ('booleanValue' in value) {
4237
+ return { booleanValue: false };
4238
+ }
4239
+ else if ('integerValue' in value || 'doubleValue' in value) {
4240
+ return { doubleValue: NaN };
4241
+ }
4242
+ else if ('timestampValue' in value) {
4243
+ return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
4244
+ }
4245
+ else if ('stringValue' in value) {
4246
+ return { stringValue: '' };
4247
+ }
4248
+ else if ('bytesValue' in value) {
4249
+ return { bytesValue: '' };
4250
+ }
4251
+ else if ('referenceValue' in value) {
4252
+ return refValue(DatabaseId.empty(), DocumentKey.empty());
4253
+ }
4254
+ else if ('geoPointValue' in value) {
4255
+ return { geoPointValue: { latitude: -90, longitude: -180 } };
4256
+ }
4257
+ else if ('arrayValue' in value) {
4258
+ return { arrayValue: {} };
4259
+ }
4260
+ else if ('mapValue' in value) {
4261
+ return { mapValue: {} };
4262
+ }
4263
+ else {
4264
+ return fail();
4265
+ }
4266
+ }
4267
+ /** Returns the largest value for the given value type (exclusive). */
4268
+ function valuesGetUpperBound(value) {
4269
+ if ('nullValue' in value) {
4270
+ return { booleanValue: false };
4271
+ }
4272
+ else if ('booleanValue' in value) {
4273
+ return { doubleValue: NaN };
4274
+ }
4275
+ else if ('integerValue' in value || 'doubleValue' in value) {
4276
+ return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
4277
+ }
4278
+ else if ('timestampValue' in value) {
4279
+ return { stringValue: '' };
4280
+ }
4281
+ else if ('stringValue' in value) {
4282
+ return { bytesValue: '' };
4283
+ }
4284
+ else if ('bytesValue' in value) {
4285
+ return refValue(DatabaseId.empty(), DocumentKey.empty());
4286
+ }
4287
+ else if ('referenceValue' in value) {
4288
+ return { geoPointValue: { latitude: -90, longitude: -180 } };
4289
+ }
4290
+ else if ('geoPointValue' in value) {
4291
+ return { arrayValue: {} };
4292
+ }
4293
+ else if ('arrayValue' in value) {
4294
+ return { mapValue: {} };
4295
+ }
4296
+ else if ('mapValue' in value) {
4297
+ return MAX_VALUE;
4298
+ }
4299
+ else {
4300
+ return fail();
4301
+ }
4302
+ }
4303
+ function valuesMax(left, right) {
4304
+ if (left === undefined) {
4305
+ return right;
4306
+ }
4307
+ else if (right === undefined) {
4308
+ return left;
4309
+ }
4310
+ else {
4311
+ return valueCompare(left, right) > 0 ? left : right;
4312
+ }
4313
+ }
4314
+ function valuesMin(left, right) {
4315
+ if (left === undefined) {
4316
+ return right;
4317
+ }
4318
+ else if (right === undefined) {
4319
+ return left;
4320
+ }
4321
+ else {
4322
+ return valueCompare(left, right) < 0 ? left : right;
4323
+ }
3905
4324
  }
3906
4325
 
3907
4326
  /**
@@ -4097,10 +4516,11 @@ function extractFieldMask(value) {
4097
4516
  * from all views.
4098
4517
  */
4099
4518
  class MutableDocument {
4100
- constructor(key, documentType, version, data, documentState) {
4519
+ constructor(key, documentType, version, readTime, data, documentState) {
4101
4520
  this.key = key;
4102
4521
  this.documentType = documentType;
4103
4522
  this.version = version;
4523
+ this.readTime = readTime;
4104
4524
  this.data = data;
4105
4525
  this.documentState = documentState;
4106
4526
  }
@@ -4109,18 +4529,18 @@ class MutableDocument {
4109
4529
  * base document for mutations.
4110
4530
  */
4111
4531
  static newInvalidDocument(documentKey) {
4112
- return new MutableDocument(documentKey, 0 /* INVALID */, SnapshotVersion.min(), ObjectValue.empty(), 0 /* SYNCED */);
4532
+ return new MutableDocument(documentKey, 0 /* INVALID */, SnapshotVersion.min(), SnapshotVersion.min(), ObjectValue.empty(), 0 /* SYNCED */);
4113
4533
  }
4114
4534
  /**
4115
4535
  * Creates a new document that is known to exist with the given data at the
4116
4536
  * given version.
4117
4537
  */
4118
4538
  static newFoundDocument(documentKey, version, value) {
4119
- return new MutableDocument(documentKey, 1 /* FOUND_DOCUMENT */, version, value, 0 /* SYNCED */);
4539
+ return new MutableDocument(documentKey, 1 /* FOUND_DOCUMENT */, version, SnapshotVersion.min(), value, 0 /* SYNCED */);
4120
4540
  }
4121
4541
  /** Creates a new document that is known to not exist at the given version. */
4122
4542
  static newNoDocument(documentKey, version) {
4123
- return new MutableDocument(documentKey, 2 /* NO_DOCUMENT */, version, ObjectValue.empty(), 0 /* SYNCED */);
4543
+ return new MutableDocument(documentKey, 2 /* NO_DOCUMENT */, version, SnapshotVersion.min(), ObjectValue.empty(), 0 /* SYNCED */);
4124
4544
  }
4125
4545
  /**
4126
4546
  * Creates a new document that is known to exist at the given version but
@@ -4128,7 +4548,7 @@ class MutableDocument {
4128
4548
  * base document).
4129
4549
  */
4130
4550
  static newUnknownDocument(documentKey, version) {
4131
- return new MutableDocument(documentKey, 3 /* UNKNOWN_DOCUMENT */, version, ObjectValue.empty(), 2 /* HAS_COMMITTED_MUTATIONS */);
4551
+ return new MutableDocument(documentKey, 3 /* UNKNOWN_DOCUMENT */, version, SnapshotVersion.min(), ObjectValue.empty(), 2 /* HAS_COMMITTED_MUTATIONS */);
4132
4552
  }
4133
4553
  /**
4134
4554
  * Changes the document type to indicate that it exists and that its version
@@ -4172,6 +4592,10 @@ class MutableDocument {
4172
4592
  this.documentState = 1 /* HAS_LOCAL_MUTATIONS */;
4173
4593
  return this;
4174
4594
  }
4595
+ setReadTime(readTime) {
4596
+ this.readTime = readTime;
4597
+ return this;
4598
+ }
4175
4599
  get hasLocalMutations() {
4176
4600
  return this.documentState === 1 /* HAS_LOCAL_MUTATIONS */;
4177
4601
  }
@@ -4202,7 +4626,7 @@ class MutableDocument {
4202
4626
  this.data.isEqual(other.data));
4203
4627
  }
4204
4628
  mutableCopy() {
4205
- return new MutableDocument(this.key, this.documentType, this.version, this.data.clone(), this.documentState);
4629
+ return new MutableDocument(this.key, this.documentType, this.version, this.readTime, this.data.clone(), this.documentState);
4206
4630
  }
4207
4631
  toString() {
4208
4632
  return (`Document(${this.key}, ${this.version}, ${JSON.stringify(this.data.value)}, ` +
@@ -4227,7 +4651,138 @@ function compareDocumentsByField(field, d1, d2) {
4227
4651
 
4228
4652
  /**
4229
4653
  * @license
4230
- * Copyright 2019 Google LLC
4654
+ * Copyright 2021 Google LLC
4655
+ *
4656
+ * Licensed under the Apache License, Version 2.0 (the "License");
4657
+ * you may not use this file except in compliance with the License.
4658
+ * You may obtain a copy of the License at
4659
+ *
4660
+ * http://www.apache.org/licenses/LICENSE-2.0
4661
+ *
4662
+ * Unless required by applicable law or agreed to in writing, software
4663
+ * distributed under the License is distributed on an "AS IS" BASIS,
4664
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4665
+ * See the License for the specific language governing permissions and
4666
+ * limitations under the License.
4667
+ */
4668
+ /**
4669
+ * The initial mutation batch id for each index. Gets updated during index
4670
+ * backfill.
4671
+ */
4672
+ const INITIAL_LARGEST_BATCH_ID = -1;
4673
+ /**
4674
+ * The initial sequence number for each index. Gets updated during index
4675
+ * backfill.
4676
+ */
4677
+ const INITIAL_SEQUENCE_NUMBER = 0;
4678
+ /**
4679
+ * An index definition for field indexes in Firestore.
4680
+ *
4681
+ * Every index is associated with a collection. The definition contains a list
4682
+ * of fields and their index kind (which can be `ASCENDING`, `DESCENDING` or
4683
+ * `CONTAINS` for ArrayContains/ArrayContainsAny queries).
4684
+ *
4685
+ * Unlike the backend, the SDK does not differentiate between collection or
4686
+ * collection group-scoped indices. Every index can be used for both single
4687
+ * collection and collection group queries.
4688
+ */
4689
+ class FieldIndex {
4690
+ constructor(
4691
+ /**
4692
+ * The index ID. Returns -1 if the index ID is not available (e.g. the index
4693
+ * has not yet been persisted).
4694
+ */
4695
+ indexId,
4696
+ /** The collection ID this index applies to. */
4697
+ collectionGroup,
4698
+ /** The field segments for this index. */
4699
+ fields,
4700
+ /** Shows how up-to-date the index is for the current user. */
4701
+ indexState) {
4702
+ this.indexId = indexId;
4703
+ this.collectionGroup = collectionGroup;
4704
+ this.fields = fields;
4705
+ this.indexState = indexState;
4706
+ }
4707
+ }
4708
+ /** An ID for an index that has not yet been added to persistence. */
4709
+ FieldIndex.UNKNOWN_ID = -1;
4710
+ /** Returns the ArrayContains/ArrayContainsAny segment for this index. */
4711
+ function fieldIndexGetArraySegment(fieldIndex) {
4712
+ return fieldIndex.fields.find(s => s.kind === 2 /* CONTAINS */);
4713
+ }
4714
+ /** Returns all directional (ascending/descending) segments for this index. */
4715
+ function fieldIndexGetDirectionalSegments(fieldIndex) {
4716
+ return fieldIndex.fields.filter(s => s.kind !== 2 /* CONTAINS */);
4717
+ }
4718
+ /** Returns a debug representation of the field index */
4719
+ function fieldIndexToString(fieldIndex) {
4720
+ return `id=${fieldIndex.indexId}|cg=${fieldIndex.collectionGroup}|f=${fieldIndex.fields.map(f => `${f.fieldPath}:${f.kind}`).join(',')}`;
4721
+ }
4722
+ /** An index component consisting of field path and index type. */
4723
+ class IndexSegment {
4724
+ constructor(
4725
+ /** The field path of the component. */
4726
+ fieldPath,
4727
+ /** The fields sorting order. */
4728
+ kind) {
4729
+ this.fieldPath = fieldPath;
4730
+ this.kind = kind;
4731
+ }
4732
+ }
4733
+ /**
4734
+ * Stores the "high water mark" that indicates how updated the Index is for the
4735
+ * current user.
4736
+ */
4737
+ class IndexState {
4738
+ constructor(
4739
+ /**
4740
+ * Indicates when the index was last updated (relative to other indexes).
4741
+ */
4742
+ sequenceNumber,
4743
+ /** The the latest indexed read time, document and batch id. */
4744
+ offset) {
4745
+ this.sequenceNumber = sequenceNumber;
4746
+ this.offset = offset;
4747
+ }
4748
+ /** The state of an index that has not yet been backfilled. */
4749
+ static empty() {
4750
+ return new IndexState(INITIAL_SEQUENCE_NUMBER, IndexOffset.min());
4751
+ }
4752
+ }
4753
+ /**
4754
+ * Stores the latest read time, document and batch ID that were processed for an
4755
+ * index.
4756
+ */
4757
+ class IndexOffset {
4758
+ constructor(
4759
+ /**
4760
+ * The latest read time version that has been indexed by Firestore for this
4761
+ * field index.
4762
+ */
4763
+ readTime,
4764
+ /**
4765
+ * The key of the last document that was indexed for this query. Use
4766
+ * `DocumentKey.empty()` if no document has been indexed.
4767
+ */
4768
+ documentKey,
4769
+ /*
4770
+ * The largest mutation batch id that's been processed by Firestore.
4771
+ */
4772
+ largestBatchId) {
4773
+ this.readTime = readTime;
4774
+ this.documentKey = documentKey;
4775
+ this.largestBatchId = largestBatchId;
4776
+ }
4777
+ /** The state of an index that has not yet been backfilled. */
4778
+ static min() {
4779
+ return new IndexOffset(SnapshotVersion.min(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
4780
+ }
4781
+ }
4782
+
4783
+ /**
4784
+ * @license
4785
+ * Copyright 2019 Google LLC
4231
4786
  *
4232
4787
  * Licensed under the Apache License, Version 2.0 (the "License");
4233
4788
  * you may not use this file except in compliance with the License.
@@ -4268,27 +4823,29 @@ function newTarget(path, collectionGroup = null, orderBy = [], filters = [], lim
4268
4823
  function canonifyTarget(target) {
4269
4824
  const targetImpl = debugCast(target);
4270
4825
  if (targetImpl.memoizedCanonicalId === null) {
4271
- let canonicalId = targetImpl.path.canonicalString();
4826
+ let str = targetImpl.path.canonicalString();
4272
4827
  if (targetImpl.collectionGroup !== null) {
4273
- canonicalId += '|cg:' + targetImpl.collectionGroup;
4828
+ str += '|cg:' + targetImpl.collectionGroup;
4274
4829
  }
4275
- canonicalId += '|f:';
4276
- canonicalId += targetImpl.filters.map(f => canonifyFilter(f)).join(',');
4277
- canonicalId += '|ob:';
4278
- canonicalId += targetImpl.orderBy.map(o => canonifyOrderBy(o)).join(',');
4830
+ str += '|f:';
4831
+ str += targetImpl.filters.map(f => canonifyFilter(f)).join(',');
4832
+ str += '|ob:';
4833
+ str += targetImpl.orderBy.map(o => canonifyOrderBy(o)).join(',');
4279
4834
  if (!isNullOrUndefined(targetImpl.limit)) {
4280
- canonicalId += '|l:';
4281
- canonicalId += targetImpl.limit;
4835
+ str += '|l:';
4836
+ str += targetImpl.limit;
4282
4837
  }
4283
4838
  if (targetImpl.startAt) {
4284
- canonicalId += '|lb:';
4285
- canonicalId += canonifyBound(targetImpl.startAt);
4839
+ str += '|lb:';
4840
+ str += targetImpl.startAt.inclusive ? 'b:' : 'a:';
4841
+ str += targetImpl.startAt.position.map(p => canonicalId(p)).join(',');
4286
4842
  }
4287
4843
  if (targetImpl.endAt) {
4288
- canonicalId += '|ub:';
4289
- canonicalId += canonifyBound(targetImpl.endAt);
4844
+ str += '|ub:';
4845
+ str += targetImpl.endAt.inclusive ? 'a:' : 'b:';
4846
+ str += targetImpl.endAt.position.map(p => canonicalId(p)).join(',');
4290
4847
  }
4291
- targetImpl.memoizedCanonicalId = canonicalId;
4848
+ targetImpl.memoizedCanonicalId = str;
4292
4849
  }
4293
4850
  return targetImpl.memoizedCanonicalId;
4294
4851
  }
@@ -4311,10 +4868,14 @@ function stringifyTarget(target) {
4311
4868
  .join(', ')}]`;
4312
4869
  }
4313
4870
  if (target.startAt) {
4314
- str += ', startAt: ' + canonifyBound(target.startAt);
4871
+ str += ', startAt: ';
4872
+ str += target.startAt.inclusive ? 'b:' : 'a:';
4873
+ str += target.startAt.position.map(p => canonicalId(p)).join(',');
4315
4874
  }
4316
4875
  if (target.endAt) {
4317
- str += ', endAt: ' + canonifyBound(target.endAt);
4876
+ str += ', endAt: ';
4877
+ str += target.endAt.inclusive ? 'a:' : 'b:';
4878
+ str += target.endAt.position.map(p => canonicalId(p)).join(',');
4318
4879
  }
4319
4880
  return `Target(${str})`;
4320
4881
  }
@@ -4349,11 +4910,196 @@ function targetEquals(left, right) {
4349
4910
  }
4350
4911
  return boundEquals(left.endAt, right.endAt);
4351
4912
  }
4352
- function isDocumentTarget(target) {
4913
+ function targetIsDocumentTarget(target) {
4353
4914
  return (DocumentKey.isDocumentKey(target.path) &&
4354
4915
  target.collectionGroup === null &&
4355
4916
  target.filters.length === 0);
4356
4917
  }
4918
+ /** Returns the field filters that target the given field path. */
4919
+ function targetGetFieldFiltersForPath(target, path) {
4920
+ return target.filters.filter(f => f instanceof FieldFilter && f.field.isEqual(path));
4921
+ }
4922
+ /**
4923
+ * Returns the values that are used in ARRAY_CONTAINS or ARRAY_CONTAINS_ANY
4924
+ * filters. Returns `null` if there are no such filters.
4925
+ */
4926
+ function targetGetArrayValues(target, fieldIndex) {
4927
+ const segment = fieldIndexGetArraySegment(fieldIndex);
4928
+ if (segment === undefined) {
4929
+ return null;
4930
+ }
4931
+ for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
4932
+ switch (fieldFilter.op) {
4933
+ case "array-contains-any" /* ARRAY_CONTAINS_ANY */:
4934
+ return fieldFilter.value.arrayValue.values || [];
4935
+ case "array-contains" /* ARRAY_CONTAINS */:
4936
+ return [fieldFilter.value];
4937
+ // Remaining filters are not array filters.
4938
+ }
4939
+ }
4940
+ return null;
4941
+ }
4942
+ /**
4943
+ * Returns the list of values that are used in != or NOT_IN filters. Returns
4944
+ * `null` if there are no such filters.
4945
+ */
4946
+ function targetGetNotInValues(target, fieldIndex) {
4947
+ const values = new Map();
4948
+ for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
4949
+ for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
4950
+ switch (fieldFilter.op) {
4951
+ case "==" /* EQUAL */:
4952
+ case "in" /* IN */:
4953
+ // Encode equality prefix, which is encoded in the index value before
4954
+ // the inequality (e.g. `a == 'a' && b != 'b'` is encoded to
4955
+ // `value != 'ab'`).
4956
+ values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
4957
+ break;
4958
+ case "not-in" /* NOT_IN */:
4959
+ case "!=" /* NOT_EQUAL */:
4960
+ // NotIn/NotEqual is always a suffix. There cannot be any remaining
4961
+ // segments and hence we can return early here.
4962
+ values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
4963
+ return Array.from(values.values());
4964
+ // Remaining filters cannot be used as notIn bounds.
4965
+ }
4966
+ }
4967
+ }
4968
+ return null;
4969
+ }
4970
+ /**
4971
+ * Returns a lower bound of field values that can be used as a starting point to
4972
+ * scan the index defined by `fieldIndex`. Returns `null` if no lower bound
4973
+ * exists.
4974
+ */
4975
+ function targetGetLowerBound(target, fieldIndex) {
4976
+ const values = [];
4977
+ let inclusive = true;
4978
+ // For each segment, retrieve a lower bound if there is a suitable filter or
4979
+ // startAt.
4980
+ for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
4981
+ let segmentValue = undefined;
4982
+ let segmentInclusive = true;
4983
+ // Process all filters to find a value for the current field segment
4984
+ for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
4985
+ let filterValue = undefined;
4986
+ let filterInclusive = true;
4987
+ switch (fieldFilter.op) {
4988
+ case "<" /* LESS_THAN */:
4989
+ case "<=" /* LESS_THAN_OR_EQUAL */:
4990
+ filterValue = valuesGetLowerBound(fieldFilter.value);
4991
+ break;
4992
+ case "==" /* EQUAL */:
4993
+ case "in" /* IN */:
4994
+ case ">=" /* GREATER_THAN_OR_EQUAL */:
4995
+ filterValue = fieldFilter.value;
4996
+ break;
4997
+ case ">" /* GREATER_THAN */:
4998
+ filterValue = fieldFilter.value;
4999
+ filterInclusive = false;
5000
+ break;
5001
+ case "!=" /* NOT_EQUAL */:
5002
+ case "not-in" /* NOT_IN */:
5003
+ filterValue = MIN_VALUE;
5004
+ break;
5005
+ // Remaining filters cannot be used as lower bounds.
5006
+ }
5007
+ if (valuesMax(segmentValue, filterValue) === filterValue) {
5008
+ segmentValue = filterValue;
5009
+ segmentInclusive = filterInclusive;
5010
+ }
5011
+ }
5012
+ // If there is a startAt bound, compare the values against the existing
5013
+ // boundary to see if we can narrow the scope.
5014
+ if (target.startAt !== null) {
5015
+ for (let i = 0; i < target.orderBy.length; ++i) {
5016
+ const orderBy = target.orderBy[i];
5017
+ if (orderBy.field.isEqual(segment.fieldPath)) {
5018
+ const cursorValue = target.startAt.position[i];
5019
+ if (valuesMax(segmentValue, cursorValue) === cursorValue) {
5020
+ segmentValue = cursorValue;
5021
+ segmentInclusive = target.startAt.inclusive;
5022
+ }
5023
+ break;
5024
+ }
5025
+ }
5026
+ }
5027
+ if (segmentValue === undefined) {
5028
+ // No lower bound exists
5029
+ return null;
5030
+ }
5031
+ values.push(segmentValue);
5032
+ inclusive && (inclusive = segmentInclusive);
5033
+ }
5034
+ return new Bound(values, inclusive);
5035
+ }
5036
+ /**
5037
+ * Returns an upper bound of field values that can be used as an ending point
5038
+ * when scanning the index defined by `fieldIndex`. Returns `null` if no
5039
+ * upper bound exists.
5040
+ */
5041
+ function targetGetUpperBound(target, fieldIndex) {
5042
+ const values = [];
5043
+ let inclusive = true;
5044
+ // For each segment, retrieve an upper bound if there is a suitable filter or
5045
+ // endAt.
5046
+ for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
5047
+ let segmentValue = undefined;
5048
+ let segmentInclusive = true;
5049
+ // Process all filters to find a value for the current field segment
5050
+ for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
5051
+ let filterValue = undefined;
5052
+ let filterInclusive = true;
5053
+ switch (fieldFilter.op) {
5054
+ case ">=" /* GREATER_THAN_OR_EQUAL */:
5055
+ case ">" /* GREATER_THAN */:
5056
+ filterValue = valuesGetUpperBound(fieldFilter.value);
5057
+ filterInclusive = false;
5058
+ break;
5059
+ case "==" /* EQUAL */:
5060
+ case "in" /* IN */:
5061
+ case "<=" /* LESS_THAN_OR_EQUAL */:
5062
+ filterValue = fieldFilter.value;
5063
+ break;
5064
+ case "<" /* LESS_THAN */:
5065
+ filterValue = fieldFilter.value;
5066
+ filterInclusive = false;
5067
+ break;
5068
+ case "!=" /* NOT_EQUAL */:
5069
+ case "not-in" /* NOT_IN */:
5070
+ filterValue = MAX_VALUE;
5071
+ break;
5072
+ // Remaining filters cannot be used as upper bounds.
5073
+ }
5074
+ if (valuesMin(segmentValue, filterValue) === filterValue) {
5075
+ segmentValue = filterValue;
5076
+ segmentInclusive = filterInclusive;
5077
+ }
5078
+ }
5079
+ // If there is a endAt bound, compare the values against the existing
5080
+ // boundary to see if we can narrow the scope.
5081
+ if (target.endAt !== null) {
5082
+ for (let i = 0; i < target.orderBy.length; ++i) {
5083
+ const orderBy = target.orderBy[i];
5084
+ if (orderBy.field.isEqual(segment.fieldPath)) {
5085
+ const cursorValue = target.endAt.position[i];
5086
+ if (valuesMin(segmentValue, cursorValue) === cursorValue) {
5087
+ segmentValue = cursorValue;
5088
+ segmentInclusive = target.endAt.inclusive;
5089
+ }
5090
+ break;
5091
+ }
5092
+ }
5093
+ }
5094
+ if (segmentValue === undefined) {
5095
+ // No upper bound exists
5096
+ return null;
5097
+ }
5098
+ values.push(segmentValue);
5099
+ inclusive && (inclusive = segmentInclusive);
5100
+ }
5101
+ return new Bound(values, inclusive);
5102
+ }
4357
5103
  class Filter {
4358
5104
  }
4359
5105
  class FieldFilter extends Filter {
@@ -4552,17 +5298,11 @@ class ArrayContainsAnyFilter extends FieldFilter {
4552
5298
  * just after the provided values.
4553
5299
  */
4554
5300
  class Bound {
4555
- constructor(position, before) {
5301
+ constructor(position, inclusive) {
4556
5302
  this.position = position;
4557
- this.before = before;
5303
+ this.inclusive = inclusive;
4558
5304
  }
4559
5305
  }
4560
- function canonifyBound(bound) {
4561
- // TODO(b/29183165): Make this collision robust.
4562
- return `${bound.before ? 'b' : 'a'}:${bound.position
4563
- .map(p => canonicalId(p))
4564
- .join(',')}`;
4565
- }
4566
5306
  /**
4567
5307
  * An ordering on a field, in some Direction. Direction defaults to ASCENDING.
4568
5308
  */
@@ -4582,11 +5322,7 @@ function stringifyOrderBy(orderBy) {
4582
5322
  function orderByEquals(left, right) {
4583
5323
  return left.dir === right.dir && left.field.isEqual(right.field);
4584
5324
  }
4585
- /**
4586
- * Returns true if a document sorts before a bound using the provided sort
4587
- * order.
4588
- */
4589
- function sortsBeforeDocument(bound, orderBy, doc) {
5325
+ function boundCompareToDocument(bound, orderBy, doc) {
4590
5326
  let comparison = 0;
4591
5327
  for (let i = 0; i < bound.position.length; i++) {
4592
5328
  const orderByComponent = orderBy[i];
@@ -4605,7 +5341,23 @@ function sortsBeforeDocument(bound, orderBy, doc) {
4605
5341
  break;
4606
5342
  }
4607
5343
  }
4608
- return bound.before ? comparison <= 0 : comparison < 0;
5344
+ return comparison;
5345
+ }
5346
+ /**
5347
+ * Returns true if a document sorts after a bound using the provided sort
5348
+ * order.
5349
+ */
5350
+ function boundSortsAfterDocument(bound, orderBy, doc) {
5351
+ const comparison = boundCompareToDocument(bound, orderBy, doc);
5352
+ return bound.inclusive ? comparison >= 0 : comparison > 0;
5353
+ }
5354
+ /**
5355
+ * Returns true if a document sorts before a bound using the provided sort
5356
+ * order.
5357
+ */
5358
+ function boundSortsBeforeDocument(bound, orderBy, doc) {
5359
+ const comparison = boundCompareToDocument(bound, orderBy, doc);
5360
+ return bound.inclusive ? comparison <= 0 : comparison < 0;
4609
5361
  }
4610
5362
  function boundEquals(left, right) {
4611
5363
  if (left === null) {
@@ -4614,7 +5366,7 @@ function boundEquals(left, right) {
4614
5366
  else if (right === null) {
4615
5367
  return false;
4616
5368
  }
4617
- if (left.before !== right.before ||
5369
+ if (left.inclusive !== right.inclusive ||
4618
5370
  left.position.length !== right.position.length) {
4619
5371
  return false;
4620
5372
  }
@@ -4818,10 +5570,10 @@ function queryToTarget(query) {
4818
5570
  }
4819
5571
  // We need to swap the cursors to match the now-flipped query ordering.
4820
5572
  const startAt = queryImpl.endAt
4821
- ? new Bound(queryImpl.endAt.position, !queryImpl.endAt.before)
5573
+ ? new Bound(queryImpl.endAt.position, !queryImpl.endAt.inclusive)
4822
5574
  : null;
4823
5575
  const endAt = queryImpl.startAt
4824
- ? new Bound(queryImpl.startAt.position, !queryImpl.startAt.before)
5576
+ ? new Bound(queryImpl.startAt.position, !queryImpl.startAt.inclusive)
4825
5577
  : null;
4826
5578
  // Now return as a LimitType.First query.
4827
5579
  queryImpl.memoizedTarget = newTarget(queryImpl.path, queryImpl.collectionGroup, orderBys, queryImpl.filters, queryImpl.limit, startAt, endAt);
@@ -4909,11 +5661,11 @@ function queryMatchesFilters(query, doc) {
4909
5661
  /** Makes sure a document is within the bounds, if provided. */
4910
5662
  function queryMatchesBounds(query, doc) {
4911
5663
  if (query.startAt &&
4912
- !sortsBeforeDocument(query.startAt, queryOrderBy(query), doc)) {
5664
+ !boundSortsBeforeDocument(query.startAt, queryOrderBy(query), doc)) {
4913
5665
  return false;
4914
5666
  }
4915
5667
  if (query.endAt &&
4916
- sortsBeforeDocument(query.endAt, queryOrderBy(query), doc)) {
5668
+ !boundSortsAfterDocument(query.endAt, queryOrderBy(query), doc)) {
4917
5669
  return false;
4918
5670
  }
4919
5671
  return true;
@@ -5091,7 +5843,7 @@ class SortedMapIterator {
5091
5843
  while (!node.isEmpty()) {
5092
5844
  cmp = startKey ? comparator(node.key, startKey) : 1;
5093
5845
  // flip the comparison if we're going in reverse
5094
- if (isReverse) {
5846
+ if (startKey && isReverse) {
5095
5847
  cmp *= -1;
5096
5848
  }
5097
5849
  if (cmp < 0) {
@@ -5568,6 +6320,72 @@ class SortedSetIterator {
5568
6320
  hasNext() {
5569
6321
  return this.iter.hasNext();
5570
6322
  }
6323
+ }
6324
+ /**
6325
+ * Compares two sorted sets for equality using their natural ordering. The
6326
+ * method computes the intersection and invokes `onAdd` for every element that
6327
+ * is in `after` but not `before`. `onRemove` is invoked for every element in
6328
+ * `before` but missing from `after`.
6329
+ *
6330
+ * The method creates a copy of both `before` and `after` and runs in O(n log
6331
+ * n), where n is the size of the two lists.
6332
+ *
6333
+ * @param before - The elements that exist in the original set.
6334
+ * @param after - The elements to diff against the original set.
6335
+ * @param comparator - The comparator for the elements in before and after.
6336
+ * @param onAdd - A function to invoke for every element that is part of `
6337
+ * after` but not `before`.
6338
+ * @param onRemove - A function to invoke for every element that is part of
6339
+ * `before` but not `after`.
6340
+ */
6341
+ function diffSortedSets(before, after, comparator, onAdd, onRemove) {
6342
+ const beforeIt = before.getIterator();
6343
+ const afterIt = after.getIterator();
6344
+ let beforeValue = advanceIterator(beforeIt);
6345
+ let afterValue = advanceIterator(afterIt);
6346
+ // Walk through the two sets at the same time, using the ordering defined by
6347
+ // `comparator`.
6348
+ while (beforeValue || afterValue) {
6349
+ let added = false;
6350
+ let removed = false;
6351
+ if (beforeValue && afterValue) {
6352
+ const cmp = comparator(beforeValue, afterValue);
6353
+ if (cmp < 0) {
6354
+ // The element was removed if the next element in our ordered
6355
+ // walkthrough is only in `before`.
6356
+ removed = true;
6357
+ }
6358
+ else if (cmp > 0) {
6359
+ // The element was added if the next element in our ordered walkthrough
6360
+ // is only in `after`.
6361
+ added = true;
6362
+ }
6363
+ }
6364
+ else if (beforeValue != null) {
6365
+ removed = true;
6366
+ }
6367
+ else {
6368
+ added = true;
6369
+ }
6370
+ if (added) {
6371
+ onAdd(afterValue);
6372
+ afterValue = advanceIterator(afterIt);
6373
+ }
6374
+ else if (removed) {
6375
+ onRemove(beforeValue);
6376
+ beforeValue = advanceIterator(beforeIt);
6377
+ }
6378
+ else {
6379
+ beforeValue = advanceIterator(beforeIt);
6380
+ afterValue = advanceIterator(afterIt);
6381
+ }
6382
+ }
6383
+ }
6384
+ /**
6385
+ * Returns the next element from the iterator or `undefined` if none available.
6386
+ */
6387
+ function advanceIterator(it) {
6388
+ return it.hasNext() ? it.getNext() : undefined;
5571
6389
  }
5572
6390
 
5573
6391
  /**
@@ -6406,6 +7224,47 @@ class MutationBatchResult {
6406
7224
  }
6407
7225
  }
6408
7226
 
7227
+ /**
7228
+ * @license
7229
+ * Copyright 2022 Google LLC
7230
+ *
7231
+ * Licensed under the Apache License, Version 2.0 (the "License");
7232
+ * you may not use this file except in compliance with the License.
7233
+ * You may obtain a copy of the License at
7234
+ *
7235
+ * http://www.apache.org/licenses/LICENSE-2.0
7236
+ *
7237
+ * Unless required by applicable law or agreed to in writing, software
7238
+ * distributed under the License is distributed on an "AS IS" BASIS,
7239
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
7240
+ * See the License for the specific language governing permissions and
7241
+ * limitations under the License.
7242
+ */
7243
+ /**
7244
+ * Representation of an overlay computed by Firestore.
7245
+ *
7246
+ * Holds information about a mutation and the largest batch id in Firestore when
7247
+ * the mutation was created.
7248
+ */
7249
+ class Overlay {
7250
+ constructor(largestBatchId, mutation) {
7251
+ this.largestBatchId = largestBatchId;
7252
+ this.mutation = mutation;
7253
+ }
7254
+ getKey() {
7255
+ return this.mutation.key;
7256
+ }
7257
+ isEqual(other) {
7258
+ return other !== null && this.mutation === other.mutation;
7259
+ }
7260
+ toString() {
7261
+ return `Overlay{
7262
+ largestBatchId: ${this.largestBatchId},
7263
+ mutation: ${this.mutation.toString()}
7264
+ }`;
7265
+ }
7266
+ }
7267
+
6409
7268
  /**
6410
7269
  * @license
6411
7270
  * Copyright 2017 Google LLC
@@ -6878,7 +7737,7 @@ class TargetState {
6878
7737
  this._current = true;
6879
7738
  }
6880
7739
  }
6881
- const LOG_TAG$f = 'WatchChangeAggregator';
7740
+ const LOG_TAG$g = 'WatchChangeAggregator';
6882
7741
  /**
6883
7742
  * A helper class to accumulate watch changes into a RemoteEvent.
6884
7743
  */
@@ -6994,7 +7853,7 @@ class WatchChangeAggregator {
6994
7853
  const targetData = this.targetDataForActiveTarget(targetId);
6995
7854
  if (targetData) {
6996
7855
  const target = targetData.target;
6997
- if (isDocumentTarget(target)) {
7856
+ if (targetIsDocumentTarget(target)) {
6998
7857
  if (expectedCount === 0) {
6999
7858
  // The existence filter told us the document does not exist. We deduce
7000
7859
  // that this document does not exist and apply a deleted document to
@@ -7029,7 +7888,7 @@ class WatchChangeAggregator {
7029
7888
  this.targetStates.forEach((targetState, targetId) => {
7030
7889
  const targetData = this.targetDataForActiveTarget(targetId);
7031
7890
  if (targetData) {
7032
- if (targetState.current && isDocumentTarget(targetData.target)) {
7891
+ if (targetState.current && targetIsDocumentTarget(targetData.target)) {
7033
7892
  // Document queries for document that don't exist can produce an empty
7034
7893
  // result set. To update our local cache, we synthesize a document
7035
7894
  // delete if we have not previously received the document. This
@@ -7072,6 +7931,7 @@ class WatchChangeAggregator {
7072
7931
  resolvedLimboDocuments = resolvedLimboDocuments.add(key);
7073
7932
  }
7074
7933
  });
7934
+ this.pendingDocumentUpdates.forEach((_, doc) => doc.setReadTime(snapshotVersion));
7075
7935
  const remoteEvent = new RemoteEvent(snapshotVersion, targetChanges, this.pendingTargetResets, this.pendingDocumentUpdates, resolvedLimboDocuments);
7076
7936
  this.pendingDocumentUpdates = mutableDocumentMap();
7077
7937
  this.pendingDocumentTargetMapping = documentTargetMap();
@@ -7172,7 +8032,7 @@ class WatchChangeAggregator {
7172
8032
  isActiveTarget(targetId) {
7173
8033
  const targetActive = this.targetDataForActiveTarget(targetId) !== null;
7174
8034
  if (!targetActive) {
7175
- logDebug(LOG_TAG$f, 'Detected inactive target', targetId);
8035
+ logDebug(LOG_TAG$g, 'Detected inactive target', targetId);
7176
8036
  }
7177
8037
  return targetActive;
7178
8038
  }
@@ -7791,10 +8651,10 @@ function toQueryTarget(serializer, target) {
7791
8651
  result.structuredQuery.limit = limit;
7792
8652
  }
7793
8653
  if (target.startAt) {
7794
- result.structuredQuery.startAt = toCursor(target.startAt);
8654
+ result.structuredQuery.startAt = toStartAtCursor(target.startAt);
7795
8655
  }
7796
8656
  if (target.endAt) {
7797
- result.structuredQuery.endAt = toCursor(target.endAt);
8657
+ result.structuredQuery.endAt = toEndAtCursor(target.endAt);
7798
8658
  }
7799
8659
  return result;
7800
8660
  }
@@ -7827,11 +8687,11 @@ function convertQueryTargetToQuery(target) {
7827
8687
  }
7828
8688
  let startAt = null;
7829
8689
  if (query.startAt) {
7830
- startAt = fromCursor(query.startAt);
8690
+ startAt = fromStartAtCursor(query.startAt);
7831
8691
  }
7832
8692
  let endAt = null;
7833
8693
  if (query.endAt) {
7834
- endAt = fromCursor(query.endAt);
8694
+ endAt = fromEndAtCursor(query.endAt);
7835
8695
  }
7836
8696
  return newQuery(path, collectionGroup, orderBy, filterBy, limit, "F" /* First */, startAt, endAt);
7837
8697
  }
@@ -7864,7 +8724,7 @@ function toLabel(serializer, purpose) {
7864
8724
  function toTarget(serializer, targetData) {
7865
8725
  let result;
7866
8726
  const target = targetData.target;
7867
- if (isDocumentTarget(target)) {
8727
+ if (targetIsDocumentTarget(target)) {
7868
8728
  result = { documents: toDocumentsTarget(serializer, target) };
7869
8729
  }
7870
8730
  else {
@@ -7922,16 +8782,27 @@ function toOrder(orderBys) {
7922
8782
  function fromOrder(orderBys) {
7923
8783
  return orderBys.map(order => fromPropertyOrder(order));
7924
8784
  }
7925
- function toCursor(cursor) {
8785
+ function toStartAtCursor(cursor) {
8786
+ return {
8787
+ before: cursor.inclusive,
8788
+ values: cursor.position
8789
+ };
8790
+ }
8791
+ function toEndAtCursor(cursor) {
7926
8792
  return {
7927
- before: cursor.before,
8793
+ before: !cursor.inclusive,
7928
8794
  values: cursor.position
7929
8795
  };
7930
8796
  }
7931
- function fromCursor(cursor) {
7932
- const before = !!cursor.before;
8797
+ function fromStartAtCursor(cursor) {
8798
+ const inclusive = !!cursor.before;
8799
+ const position = cursor.values || [];
8800
+ return new Bound(position, inclusive);
8801
+ }
8802
+ function fromEndAtCursor(cursor) {
8803
+ const inclusive = !cursor.before;
7933
8804
  const position = cursor.values || [];
7934
- return new Bound(position, before);
8805
+ return new Bound(position, inclusive);
7935
8806
  }
7936
8807
  // visible for testing
7937
8808
  function toDirection(dir) {
@@ -8192,29 +9063,34 @@ class LocalSerializer {
8192
9063
  }
8193
9064
  /** Decodes a remote document from storage locally to a Document. */
8194
9065
  function fromDbRemoteDocument(localSerializer, remoteDoc) {
9066
+ let doc;
8195
9067
  if (remoteDoc.document) {
8196
- return fromDocument(localSerializer.remoteSerializer, remoteDoc.document, !!remoteDoc.hasCommittedMutations);
9068
+ doc = fromDocument(localSerializer.remoteSerializer, remoteDoc.document, !!remoteDoc.hasCommittedMutations);
8197
9069
  }
8198
9070
  else if (remoteDoc.noDocument) {
8199
9071
  const key = DocumentKey.fromSegments(remoteDoc.noDocument.path);
8200
9072
  const version = fromDbTimestamp(remoteDoc.noDocument.readTime);
8201
- const document = MutableDocument.newNoDocument(key, version);
8202
- return remoteDoc.hasCommittedMutations
8203
- ? document.setHasCommittedMutations()
8204
- : document;
9073
+ doc = MutableDocument.newNoDocument(key, version);
9074
+ if (remoteDoc.hasCommittedMutations) {
9075
+ doc.setHasCommittedMutations();
9076
+ }
8205
9077
  }
8206
9078
  else if (remoteDoc.unknownDocument) {
8207
9079
  const key = DocumentKey.fromSegments(remoteDoc.unknownDocument.path);
8208
9080
  const version = fromDbTimestamp(remoteDoc.unknownDocument.version);
8209
- return MutableDocument.newUnknownDocument(key, version);
9081
+ doc = MutableDocument.newUnknownDocument(key, version);
8210
9082
  }
8211
9083
  else {
8212
9084
  return fail();
8213
9085
  }
9086
+ if (remoteDoc.readTime) {
9087
+ doc.setReadTime(fromDbTimestampKey(remoteDoc.readTime));
9088
+ }
9089
+ return doc;
8214
9090
  }
8215
9091
  /** Encodes a document for storage locally. */
8216
- function toDbRemoteDocument(localSerializer, document, readTime) {
8217
- const dbReadTime = toDbTimestampKey(readTime);
9092
+ function toDbRemoteDocument(localSerializer, document) {
9093
+ const dbReadTime = toDbTimestampKey(document.readTime);
8218
9094
  const parentPath = document.key.path.popLast().toArray();
8219
9095
  if (document.isFoundDocument()) {
8220
9096
  const doc = toDocument(localSerializer.remoteSerializer, document);
@@ -8225,10 +9101,10 @@ function toDbRemoteDocument(localSerializer, document, readTime) {
8225
9101
  }
8226
9102
  else if (document.isNoDocument()) {
8227
9103
  const path = document.key.path.toArray();
8228
- const readTime = toDbTimestamp(document.version);
9104
+ const version = toDbTimestamp(document.version);
8229
9105
  const hasCommittedMutations = document.hasCommittedMutations;
8230
9106
  return new DbRemoteDocument(
8231
- /* unknownDocument= */ null, new DbNoDocument(path, readTime),
9107
+ /* unknownDocument= */ null, new DbNoDocument(path, version),
8232
9108
  /* document= */ null, hasCommittedMutations, dbReadTime, parentPath);
8233
9109
  }
8234
9110
  else if (document.isUnknownDocument()) {
@@ -8309,7 +9185,7 @@ function toDbTarget(localSerializer, targetData) {
8309
9185
  const dbTimestamp = toDbTimestamp(targetData.snapshotVersion);
8310
9186
  const dbLastLimboFreeTimestamp = toDbTimestamp(targetData.lastLimboFreeSnapshotVersion);
8311
9187
  let queryProto;
8312
- if (isDocumentTarget(targetData.target)) {
9188
+ if (targetIsDocumentTarget(targetData.target)) {
8313
9189
  queryProto = toDocumentsTarget(localSerializer.remoteSerializer, targetData.target);
8314
9190
  }
8315
9191
  else {
@@ -8390,6 +9266,37 @@ function fromBundleMetadata(metadata) {
8390
9266
  version: metadata.version,
8391
9267
  createTime: fromVersion(metadata.createTime)
8392
9268
  };
9269
+ }
9270
+ /** Encodes a DbDocumentOverlay object to an Overlay model object. */
9271
+ function fromDbDocumentOverlay(localSerializer, dbDocumentOverlay) {
9272
+ return new Overlay(dbDocumentOverlay.largestBatchId, fromMutation(localSerializer.remoteSerializer, dbDocumentOverlay.overlayMutation));
9273
+ }
9274
+ /** Decodes an Overlay model object into a DbDocumentOverlay object. */
9275
+ function toDbDocumentOverlay(localSerializer, userId, overlay) {
9276
+ const [_, collectionPath, documentId] = toDbDocumentOverlayKey(userId, overlay.mutation.key);
9277
+ return new DbDocumentOverlay(userId, collectionPath, documentId, overlay.mutation.key.getCollectionGroup(), overlay.largestBatchId, toMutation(localSerializer.remoteSerializer, overlay.mutation));
9278
+ }
9279
+ /**
9280
+ * Returns the DbDocumentOverlayKey corresponding to the given user and
9281
+ * document key.
9282
+ */
9283
+ function toDbDocumentOverlayKey(userId, docKey) {
9284
+ const docId = docKey.path.lastSegment();
9285
+ const collectionPath = encodeResourcePath(docKey.path.popLast());
9286
+ return [userId, collectionPath, docId];
9287
+ }
9288
+ function toDbIndexConfiguration(index) {
9289
+ return new DbIndexConfiguration(index.indexId, index.collectionGroup, index.fields.map(s => [s.fieldPath.canonicalString(), s.kind]));
9290
+ }
9291
+ function fromDbIndexConfiguration(index, state) {
9292
+ const decodedState = state
9293
+ ? new IndexState(state.sequenceNumber, new IndexOffset(fromDbTimestamp(state.readTime), new DocumentKey(decodeResourcePath(state.documentKey)), state.largestBatchId))
9294
+ : IndexState.empty();
9295
+ const decodedSegments = index.fields.map(([fieldPath, kind]) => new IndexSegment(FieldPath$1.fromServerFormat(fieldPath), kind));
9296
+ return new FieldIndex(index.indexId, index.collectionGroup, decodedSegments, decodedState);
9297
+ }
9298
+ function toDbIndexState(indexId, user, sequenceNumber, offset) {
9299
+ return new DbIndexState(indexId, user.uid || '', sequenceNumber, toDbTimestamp(offset.readTime), encodeResourcePath(offset.documentKey.path), offset.largestBatchId);
8393
9300
  }
8394
9301
 
8395
9302
  /**
@@ -8451,7 +9358,7 @@ function namedQueriesStore(txn) {
8451
9358
 
8452
9359
  /**
8453
9360
  * @license
8454
- * Copyright 2019 Google LLC
9361
+ * Copyright 2022 Google LLC
8455
9362
  *
8456
9363
  * Licensed under the Apache License, Version 2.0 (the "License");
8457
9364
  * you may not use this file except in compliance with the License.
@@ -8466,56 +9373,1060 @@ function namedQueriesStore(txn) {
8466
9373
  * limitations under the License.
8467
9374
  */
8468
9375
  /**
8469
- * An in-memory implementation of IndexManager.
9376
+ * Implementation of DocumentOverlayCache using IndexedDb.
8470
9377
  */
8471
- class MemoryIndexManager {
8472
- constructor() {
8473
- this.collectionParentIndex = new MemoryCollectionParentIndex();
8474
- }
8475
- addToCollectionParentIndex(transaction, collectionPath) {
8476
- this.collectionParentIndex.add(collectionPath);
8477
- return PersistencePromise.resolve();
9378
+ class IndexedDbDocumentOverlayCache {
9379
+ /**
9380
+ * @param serializer - The document serializer.
9381
+ * @param userId - The userId for which we are accessing overlays.
9382
+ */
9383
+ constructor(serializer, userId) {
9384
+ this.serializer = serializer;
9385
+ this.userId = userId;
8478
9386
  }
8479
- getCollectionParents(transaction, collectionId) {
8480
- return PersistencePromise.resolve(this.collectionParentIndex.getEntries(collectionId));
9387
+ static forUser(serializer, user) {
9388
+ const userId = user.uid || '';
9389
+ return new IndexedDbDocumentOverlayCache(serializer, userId);
8481
9390
  }
8482
- }
8483
- /**
8484
- * Internal implementation of the collection-parent index exposed by MemoryIndexManager.
8485
- * Also used for in-memory caching by IndexedDbIndexManager and initial index population
8486
- * in indexeddb_schema.ts
8487
- */
8488
- class MemoryCollectionParentIndex {
8489
- constructor() {
8490
- this.index = {};
9391
+ getOverlay(transaction, key) {
9392
+ return documentOverlayStore(transaction)
9393
+ .get(toDbDocumentOverlayKey(this.userId, key))
9394
+ .next(dbOverlay => {
9395
+ if (dbOverlay) {
9396
+ return fromDbDocumentOverlay(this.serializer, dbOverlay);
9397
+ }
9398
+ return null;
9399
+ });
8491
9400
  }
8492
- // Returns false if the entry already existed.
8493
- add(collectionPath) {
8494
- const collectionId = collectionPath.lastSegment();
8495
- const parentPath = collectionPath.popLast();
8496
- const existingParents = this.index[collectionId] ||
8497
- new SortedSet(ResourcePath.comparator);
8498
- const added = !existingParents.has(parentPath);
8499
- this.index[collectionId] = existingParents.add(parentPath);
8500
- return added;
9401
+ saveOverlays(transaction, largestBatchId, overlays) {
9402
+ const promises = [];
9403
+ overlays.forEach(mutation => {
9404
+ const overlay = new Overlay(largestBatchId, mutation);
9405
+ promises.push(this.saveOverlay(transaction, overlay));
9406
+ });
9407
+ return PersistencePromise.waitFor(promises);
8501
9408
  }
8502
- has(collectionPath) {
8503
- const collectionId = collectionPath.lastSegment();
8504
- const parentPath = collectionPath.popLast();
8505
- const existingParents = this.index[collectionId];
8506
- return existingParents && existingParents.has(parentPath);
9409
+ removeOverlaysForBatchId(transaction, documentKeys, batchId) {
9410
+ const collectionPaths = new Set();
9411
+ // Get the set of unique collection paths.
9412
+ documentKeys.forEach(key => collectionPaths.add(encodeResourcePath(key.getCollectionPath())));
9413
+ const promises = [];
9414
+ collectionPaths.forEach(collectionPath => {
9415
+ const range = IDBKeyRange.bound([this.userId, collectionPath, batchId], [this.userId, collectionPath, batchId + 1],
9416
+ /*lowerOpen=*/ false,
9417
+ /*upperOpen=*/ true);
9418
+ promises.push(documentOverlayStore(transaction).deleteAll(DbDocumentOverlay.collectionPathOverlayIndex, range));
9419
+ });
9420
+ return PersistencePromise.waitFor(promises);
8507
9421
  }
8508
- getEntries(collectionId) {
8509
- const parentPaths = this.index[collectionId] ||
8510
- new SortedSet(ResourcePath.comparator);
8511
- return parentPaths.toArray();
9422
+ getOverlaysForCollection(transaction, collection, sinceBatchId) {
9423
+ const result = new Map();
9424
+ const collectionPath = encodeResourcePath(collection);
9425
+ // We want batch IDs larger than `sinceBatchId`, and so the lower bound
9426
+ // is not inclusive.
9427
+ const range = IDBKeyRange.bound([this.userId, collectionPath, sinceBatchId], [this.userId, collectionPath, Number.POSITIVE_INFINITY],
9428
+ /*lowerOpen=*/ true);
9429
+ return documentOverlayStore(transaction)
9430
+ .loadAll(DbDocumentOverlay.collectionPathOverlayIndex, range)
9431
+ .next(dbOverlays => {
9432
+ for (const dbOverlay of dbOverlays) {
9433
+ const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
9434
+ result.set(overlay.getKey(), overlay);
9435
+ }
9436
+ return result;
9437
+ });
8512
9438
  }
8513
- }
8514
-
8515
- /**
8516
- * @license
8517
- * Copyright 2019 Google LLC
8518
- *
9439
+ getOverlaysForCollectionGroup(transaction, collectionGroup, sinceBatchId, count) {
9440
+ const result = new Map();
9441
+ let currentBatchId = undefined;
9442
+ // We want batch IDs larger than `sinceBatchId`, and so the lower bound
9443
+ // is not inclusive.
9444
+ const range = IDBKeyRange.bound([this.userId, collectionGroup, sinceBatchId], [this.userId, collectionGroup, Number.POSITIVE_INFINITY],
9445
+ /*lowerOpen=*/ true);
9446
+ return documentOverlayStore(transaction)
9447
+ .iterate({
9448
+ index: DbDocumentOverlay.collectionGroupOverlayIndex,
9449
+ range
9450
+ }, (_, dbOverlay, control) => {
9451
+ // We do not want to return partial batch overlays, even if the size
9452
+ // of the result set exceeds the given `count` argument. Therefore, we
9453
+ // continue to aggregate results even after the result size exceeds
9454
+ // `count` if there are more overlays from the `currentBatchId`.
9455
+ const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
9456
+ if (result.size < count ||
9457
+ overlay.largestBatchId === currentBatchId) {
9458
+ result.set(overlay.getKey(), overlay);
9459
+ currentBatchId = overlay.largestBatchId;
9460
+ }
9461
+ else {
9462
+ control.done();
9463
+ }
9464
+ })
9465
+ .next(() => result);
9466
+ }
9467
+ saveOverlay(transaction, overlay) {
9468
+ return documentOverlayStore(transaction).put(toDbDocumentOverlay(this.serializer, this.userId, overlay));
9469
+ }
9470
+ }
9471
+ /**
9472
+ * Helper to get a typed SimpleDbStore for the document overlay object store.
9473
+ */
9474
+ function documentOverlayStore(txn) {
9475
+ return getStore(txn, DbDocumentOverlay.store);
9476
+ }
9477
+
9478
+ /**
9479
+ * @license
9480
+ * Copyright 2021 Google LLC
9481
+ *
9482
+ * Licensed under the Apache License, Version 2.0 (the "License");
9483
+ * you may not use this file except in compliance with the License.
9484
+ * You may obtain a copy of the License at
9485
+ *
9486
+ * http://www.apache.org/licenses/LICENSE-2.0
9487
+ *
9488
+ * Unless required by applicable law or agreed to in writing, software
9489
+ * distributed under the License is distributed on an "AS IS" BASIS,
9490
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9491
+ * See the License for the specific language governing permissions and
9492
+ * limitations under the License.
9493
+ */
9494
+ // Note: This code is copied from the backend. Code that is not used by
9495
+ // Firestore was removed.
9496
+ const INDEX_TYPE_NULL = 5;
9497
+ const INDEX_TYPE_BOOLEAN = 10;
9498
+ const INDEX_TYPE_NAN = 13;
9499
+ const INDEX_TYPE_NUMBER = 15;
9500
+ const INDEX_TYPE_TIMESTAMP = 20;
9501
+ const INDEX_TYPE_STRING = 25;
9502
+ const INDEX_TYPE_BLOB = 30;
9503
+ const INDEX_TYPE_REFERENCE = 37;
9504
+ const INDEX_TYPE_GEOPOINT = 45;
9505
+ const INDEX_TYPE_ARRAY = 50;
9506
+ const INDEX_TYPE_MAP = 55;
9507
+ const INDEX_TYPE_REFERENCE_SEGMENT = 60;
9508
+ // A terminator that indicates that a truncatable value was not truncated.
9509
+ // This must be smaller than all other type labels.
9510
+ const NOT_TRUNCATED = 2;
9511
+ /** Firestore index value writer. */
9512
+ class FirestoreIndexValueWriter {
9513
+ constructor() { }
9514
+ // The write methods below short-circuit writing terminators for values
9515
+ // containing a (terminating) truncated value.
9516
+ //
9517
+ // As an example, consider the resulting encoding for:
9518
+ //
9519
+ // ["bar", [2, "foo"]] -> (STRING, "bar", TERM, ARRAY, NUMBER, 2, STRING, "foo", TERM, TERM, TERM)
9520
+ // ["bar", [2, truncated("foo")]] -> (STRING, "bar", TERM, ARRAY, NUMBER, 2, STRING, "foo", TRUNC)
9521
+ // ["bar", truncated(["foo"])] -> (STRING, "bar", TERM, ARRAY. STRING, "foo", TERM, TRUNC)
9522
+ /** Writes an index value. */
9523
+ writeIndexValue(value, encoder) {
9524
+ this.writeIndexValueAux(value, encoder);
9525
+ // Write separator to split index values
9526
+ // (see go/firestore-storage-format#encodings).
9527
+ encoder.writeInfinity();
9528
+ }
9529
+ writeIndexValueAux(indexValue, encoder) {
9530
+ if ('nullValue' in indexValue) {
9531
+ this.writeValueTypeLabel(encoder, INDEX_TYPE_NULL);
9532
+ }
9533
+ else if ('booleanValue' in indexValue) {
9534
+ this.writeValueTypeLabel(encoder, INDEX_TYPE_BOOLEAN);
9535
+ encoder.writeNumber(indexValue.booleanValue ? 1 : 0);
9536
+ }
9537
+ else if ('integerValue' in indexValue) {
9538
+ this.writeValueTypeLabel(encoder, INDEX_TYPE_NUMBER);
9539
+ encoder.writeNumber(normalizeNumber(indexValue.integerValue));
9540
+ }
9541
+ else if ('doubleValue' in indexValue) {
9542
+ const n = normalizeNumber(indexValue.doubleValue);
9543
+ if (isNaN(n)) {
9544
+ this.writeValueTypeLabel(encoder, INDEX_TYPE_NAN);
9545
+ }
9546
+ else {
9547
+ this.writeValueTypeLabel(encoder, INDEX_TYPE_NUMBER);
9548
+ if (isNegativeZero(n)) {
9549
+ // -0.0, 0 and 0.0 are all considered the same
9550
+ encoder.writeNumber(0.0);
9551
+ }
9552
+ else {
9553
+ encoder.writeNumber(n);
9554
+ }
9555
+ }
9556
+ }
9557
+ else if ('timestampValue' in indexValue) {
9558
+ const timestamp = indexValue.timestampValue;
9559
+ this.writeValueTypeLabel(encoder, INDEX_TYPE_TIMESTAMP);
9560
+ if (typeof timestamp === 'string') {
9561
+ encoder.writeString(timestamp);
9562
+ }
9563
+ else {
9564
+ encoder.writeString(`${timestamp.seconds || ''}`);
9565
+ encoder.writeNumber(timestamp.nanos || 0);
9566
+ }
9567
+ }
9568
+ else if ('stringValue' in indexValue) {
9569
+ this.writeIndexString(indexValue.stringValue, encoder);
9570
+ this.writeTruncationMarker(encoder);
9571
+ }
9572
+ else if ('bytesValue' in indexValue) {
9573
+ this.writeValueTypeLabel(encoder, INDEX_TYPE_BLOB);
9574
+ encoder.writeBytes(normalizeByteString(indexValue.bytesValue));
9575
+ this.writeTruncationMarker(encoder);
9576
+ }
9577
+ else if ('referenceValue' in indexValue) {
9578
+ this.writeIndexEntityRef(indexValue.referenceValue, encoder);
9579
+ }
9580
+ else if ('geoPointValue' in indexValue) {
9581
+ const geoPoint = indexValue.geoPointValue;
9582
+ this.writeValueTypeLabel(encoder, INDEX_TYPE_GEOPOINT);
9583
+ encoder.writeNumber(geoPoint.latitude || 0);
9584
+ encoder.writeNumber(geoPoint.longitude || 0);
9585
+ }
9586
+ else if ('mapValue' in indexValue) {
9587
+ if (isMaxValue(indexValue)) {
9588
+ this.writeValueTypeLabel(encoder, Number.MAX_SAFE_INTEGER);
9589
+ }
9590
+ else {
9591
+ this.writeIndexMap(indexValue.mapValue, encoder);
9592
+ this.writeTruncationMarker(encoder);
9593
+ }
9594
+ }
9595
+ else if ('arrayValue' in indexValue) {
9596
+ this.writeIndexArray(indexValue.arrayValue, encoder);
9597
+ this.writeTruncationMarker(encoder);
9598
+ }
9599
+ else {
9600
+ fail();
9601
+ }
9602
+ }
9603
+ writeIndexString(stringIndexValue, encoder) {
9604
+ this.writeValueTypeLabel(encoder, INDEX_TYPE_STRING);
9605
+ this.writeUnlabeledIndexString(stringIndexValue, encoder);
9606
+ }
9607
+ writeUnlabeledIndexString(stringIndexValue, encoder) {
9608
+ encoder.writeString(stringIndexValue);
9609
+ }
9610
+ writeIndexMap(mapIndexValue, encoder) {
9611
+ const map = mapIndexValue.fields || {};
9612
+ this.writeValueTypeLabel(encoder, INDEX_TYPE_MAP);
9613
+ for (const key of Object.keys(map)) {
9614
+ this.writeIndexString(key, encoder);
9615
+ this.writeIndexValueAux(map[key], encoder);
9616
+ }
9617
+ }
9618
+ writeIndexArray(arrayIndexValue, encoder) {
9619
+ const values = arrayIndexValue.values || [];
9620
+ this.writeValueTypeLabel(encoder, INDEX_TYPE_ARRAY);
9621
+ for (const element of values) {
9622
+ this.writeIndexValueAux(element, encoder);
9623
+ }
9624
+ }
9625
+ writeIndexEntityRef(referenceValue, encoder) {
9626
+ this.writeValueTypeLabel(encoder, INDEX_TYPE_REFERENCE);
9627
+ const path = DocumentKey.fromName(referenceValue).path;
9628
+ path.forEach(segment => {
9629
+ this.writeValueTypeLabel(encoder, INDEX_TYPE_REFERENCE_SEGMENT);
9630
+ this.writeUnlabeledIndexString(segment, encoder);
9631
+ });
9632
+ }
9633
+ writeValueTypeLabel(encoder, typeOrder) {
9634
+ encoder.writeNumber(typeOrder);
9635
+ }
9636
+ writeTruncationMarker(encoder) {
9637
+ // While the SDK does not implement truncation, the truncation marker is
9638
+ // used to terminate all variable length values (which are strings, bytes,
9639
+ // references, arrays and maps).
9640
+ encoder.writeNumber(NOT_TRUNCATED);
9641
+ }
9642
+ }
9643
+ FirestoreIndexValueWriter.INSTANCE = new FirestoreIndexValueWriter();
9644
+
9645
+ /**
9646
+ * @license
9647
+ * Copyright 2021 Google LLC
9648
+ *
9649
+ * Licensed under the Apache License, Version 2.0 (the "License");
9650
+ * you may not use this file except in compliance with the License.
9651
+ * You may obtain a copy of the License at
9652
+ *
9653
+ * http://www.apache.org/licenses/LICENSE-2.0
9654
+ *
9655
+ * Unless required by applicable law | agreed to in writing, software
9656
+ * distributed under the License is distributed on an "AS IS" BASIS,
9657
+ * WITHOUT WARRANTIES | CONDITIONS OF ANY KIND, either express | implied.
9658
+ * See the License for the specific language governing permissions and
9659
+ * limitations under the License.
9660
+ */
9661
+ /** These constants are taken from the backend. */
9662
+ const MIN_SURROGATE = '\uD800';
9663
+ const MAX_SURROGATE = '\uDBFF';
9664
+ const ESCAPE1 = 0x00;
9665
+ const NULL_BYTE = 0xff; // Combined with ESCAPE1
9666
+ const SEPARATOR = 0x01; // Combined with ESCAPE1
9667
+ const ESCAPE2 = 0xff;
9668
+ const INFINITY = 0xff; // Combined with ESCAPE2
9669
+ const FF_BYTE = 0x00; // Combined with ESCAPE2
9670
+ const LONG_SIZE = 64;
9671
+ const BYTE_SIZE = 8;
9672
+ /**
9673
+ * The default size of the buffer. This is arbitrary, but likely larger than
9674
+ * most index values so that less copies of the underlying buffer will be made.
9675
+ * For large values, a single copy will made to double the buffer length.
9676
+ */
9677
+ const DEFAULT_BUFFER_SIZE = 1024;
9678
+ /** Converts a JavaScript number to a byte array (using big endian encoding). */
9679
+ function doubleToLongBits(value) {
9680
+ const dv = new DataView(new ArrayBuffer(8));
9681
+ dv.setFloat64(0, value, /* littleEndian= */ false);
9682
+ return new Uint8Array(dv.buffer);
9683
+ }
9684
+ /**
9685
+ * Counts the number of zeros in a byte.
9686
+ *
9687
+ * Visible for testing.
9688
+ */
9689
+ function numberOfLeadingZerosInByte(x) {
9690
+ if (x === 0) {
9691
+ return 8;
9692
+ }
9693
+ let zeros = 0;
9694
+ if (x >> 4 === 0) {
9695
+ // Test if the first four bits are zero.
9696
+ zeros += 4;
9697
+ x = x << 4;
9698
+ }
9699
+ if (x >> 6 === 0) {
9700
+ // Test if the first two (or next two) bits are zero.
9701
+ zeros += 2;
9702
+ x = x << 2;
9703
+ }
9704
+ if (x >> 7 === 0) {
9705
+ // Test if the remaining bit is zero.
9706
+ zeros += 1;
9707
+ }
9708
+ return zeros;
9709
+ }
9710
+ /** Counts the number of leading zeros in the given byte array. */
9711
+ function numberOfLeadingZeros(bytes) {
9712
+ let leadingZeros = 0;
9713
+ for (let i = 0; i < 8; ++i) {
9714
+ const zeros = numberOfLeadingZerosInByte(bytes[i] & 0xff);
9715
+ leadingZeros += zeros;
9716
+ if (zeros !== 8) {
9717
+ break;
9718
+ }
9719
+ }
9720
+ return leadingZeros;
9721
+ }
9722
+ /**
9723
+ * Returns the number of bytes required to store "value". Leading zero bytes
9724
+ * are skipped.
9725
+ */
9726
+ function unsignedNumLength(value) {
9727
+ // This is just the number of bytes for the unsigned representation of the number.
9728
+ const numBits = LONG_SIZE - numberOfLeadingZeros(value);
9729
+ return Math.ceil(numBits / BYTE_SIZE);
9730
+ }
9731
+ /**
9732
+ * OrderedCodeWriter is a minimal-allocation implementation of the writing
9733
+ * behavior defined by the backend.
9734
+ *
9735
+ * The code is ported from its Java counterpart.
9736
+ */
9737
+ class OrderedCodeWriter {
9738
+ constructor() {
9739
+ this.buffer = new Uint8Array(DEFAULT_BUFFER_SIZE);
9740
+ this.position = 0;
9741
+ }
9742
+ writeBytesAscending(value) {
9743
+ const it = value[Symbol.iterator]();
9744
+ let byte = it.next();
9745
+ while (!byte.done) {
9746
+ this.writeByteAscending(byte.value);
9747
+ byte = it.next();
9748
+ }
9749
+ this.writeSeparatorAscending();
9750
+ }
9751
+ writeBytesDescending(value) {
9752
+ const it = value[Symbol.iterator]();
9753
+ let byte = it.next();
9754
+ while (!byte.done) {
9755
+ this.writeByteDescending(byte.value);
9756
+ byte = it.next();
9757
+ }
9758
+ this.writeSeparatorDescending();
9759
+ }
9760
+ /** Writes utf8 bytes into this byte sequence, ascending. */
9761
+ writeUtf8Ascending(sequence) {
9762
+ for (const c of sequence) {
9763
+ const charCode = c.charCodeAt(0);
9764
+ if (charCode < 0x80) {
9765
+ this.writeByteAscending(charCode);
9766
+ }
9767
+ else if (charCode < 0x800) {
9768
+ this.writeByteAscending((0x0f << 6) | (charCode >>> 6));
9769
+ this.writeByteAscending(0x80 | (0x3f & charCode));
9770
+ }
9771
+ else if (c < MIN_SURROGATE || MAX_SURROGATE < c) {
9772
+ this.writeByteAscending((0x0f << 5) | (charCode >>> 12));
9773
+ this.writeByteAscending(0x80 | (0x3f & (charCode >>> 6)));
9774
+ this.writeByteAscending(0x80 | (0x3f & charCode));
9775
+ }
9776
+ else {
9777
+ const codePoint = c.codePointAt(0);
9778
+ this.writeByteAscending((0x0f << 4) | (codePoint >>> 18));
9779
+ this.writeByteAscending(0x80 | (0x3f & (codePoint >>> 12)));
9780
+ this.writeByteAscending(0x80 | (0x3f & (codePoint >>> 6)));
9781
+ this.writeByteAscending(0x80 | (0x3f & codePoint));
9782
+ }
9783
+ }
9784
+ this.writeSeparatorAscending();
9785
+ }
9786
+ /** Writes utf8 bytes into this byte sequence, descending */
9787
+ writeUtf8Descending(sequence) {
9788
+ for (const c of sequence) {
9789
+ const charCode = c.charCodeAt(0);
9790
+ if (charCode < 0x80) {
9791
+ this.writeByteDescending(charCode);
9792
+ }
9793
+ else if (charCode < 0x800) {
9794
+ this.writeByteDescending((0x0f << 6) | (charCode >>> 6));
9795
+ this.writeByteDescending(0x80 | (0x3f & charCode));
9796
+ }
9797
+ else if (c < MIN_SURROGATE || MAX_SURROGATE < c) {
9798
+ this.writeByteDescending((0x0f << 5) | (charCode >>> 12));
9799
+ this.writeByteDescending(0x80 | (0x3f & (charCode >>> 6)));
9800
+ this.writeByteDescending(0x80 | (0x3f & charCode));
9801
+ }
9802
+ else {
9803
+ const codePoint = c.codePointAt(0);
9804
+ this.writeByteDescending((0x0f << 4) | (codePoint >>> 18));
9805
+ this.writeByteDescending(0x80 | (0x3f & (codePoint >>> 12)));
9806
+ this.writeByteDescending(0x80 | (0x3f & (codePoint >>> 6)));
9807
+ this.writeByteDescending(0x80 | (0x3f & codePoint));
9808
+ }
9809
+ }
9810
+ this.writeSeparatorDescending();
9811
+ }
9812
+ writeNumberAscending(val) {
9813
+ // Values are encoded with a single byte length prefix, followed by the
9814
+ // actual value in big-endian format with leading 0 bytes dropped.
9815
+ const value = this.toOrderedBits(val);
9816
+ const len = unsignedNumLength(value);
9817
+ this.ensureAvailable(1 + len);
9818
+ this.buffer[this.position++] = len & 0xff; // Write the length
9819
+ for (let i = value.length - len; i < value.length; ++i) {
9820
+ this.buffer[this.position++] = value[i] & 0xff;
9821
+ }
9822
+ }
9823
+ writeNumberDescending(val) {
9824
+ // Values are encoded with a single byte length prefix, followed by the
9825
+ // inverted value in big-endian format with leading 0 bytes dropped.
9826
+ const value = this.toOrderedBits(val);
9827
+ const len = unsignedNumLength(value);
9828
+ this.ensureAvailable(1 + len);
9829
+ this.buffer[this.position++] = ~(len & 0xff); // Write the length
9830
+ for (let i = value.length - len; i < value.length; ++i) {
9831
+ this.buffer[this.position++] = ~(value[i] & 0xff);
9832
+ }
9833
+ }
9834
+ /**
9835
+ * Writes the "infinity" byte sequence that sorts after all other byte
9836
+ * sequences written in ascending order.
9837
+ */
9838
+ writeInfinityAscending() {
9839
+ this.writeEscapedByteAscending(ESCAPE2);
9840
+ this.writeEscapedByteAscending(INFINITY);
9841
+ }
9842
+ /**
9843
+ * Writes the "infinity" byte sequence that sorts before all other byte
9844
+ * sequences written in descending order.
9845
+ */
9846
+ writeInfinityDescending() {
9847
+ this.writeEscapedByteDescending(ESCAPE2);
9848
+ this.writeEscapedByteDescending(INFINITY);
9849
+ }
9850
+ /**
9851
+ * Resets the buffer such that it is the same as when it was newly
9852
+ * constructed.
9853
+ */
9854
+ reset() {
9855
+ this.position = 0;
9856
+ }
9857
+ seed(encodedBytes) {
9858
+ this.ensureAvailable(encodedBytes.length);
9859
+ this.buffer.set(encodedBytes, this.position);
9860
+ this.position += encodedBytes.length;
9861
+ }
9862
+ /** Makes a copy of the encoded bytes in this buffer. */
9863
+ encodedBytes() {
9864
+ return this.buffer.slice(0, this.position);
9865
+ }
9866
+ /**
9867
+ * Encodes `val` into an encoding so that the order matches the IEEE 754
9868
+ * floating-point comparison results with the following exceptions:
9869
+ * -0.0 < 0.0
9870
+ * all non-NaN < NaN
9871
+ * NaN = NaN
9872
+ */
9873
+ toOrderedBits(val) {
9874
+ const value = doubleToLongBits(val);
9875
+ // Check if the first bit is set. We use a bit mask since value[0] is
9876
+ // encoded as a number from 0 to 255.
9877
+ const isNegative = (value[0] & 0x80) !== 0;
9878
+ // Revert the two complement to get natural ordering
9879
+ value[0] ^= isNegative ? 0xff : 0x80;
9880
+ for (let i = 1; i < value.length; ++i) {
9881
+ value[i] ^= isNegative ? 0xff : 0x00;
9882
+ }
9883
+ return value;
9884
+ }
9885
+ /** Writes a single byte ascending to the buffer. */
9886
+ writeByteAscending(b) {
9887
+ const masked = b & 0xff;
9888
+ if (masked === ESCAPE1) {
9889
+ this.writeEscapedByteAscending(ESCAPE1);
9890
+ this.writeEscapedByteAscending(NULL_BYTE);
9891
+ }
9892
+ else if (masked === ESCAPE2) {
9893
+ this.writeEscapedByteAscending(ESCAPE2);
9894
+ this.writeEscapedByteAscending(FF_BYTE);
9895
+ }
9896
+ else {
9897
+ this.writeEscapedByteAscending(masked);
9898
+ }
9899
+ }
9900
+ /** Writes a single byte descending to the buffer. */
9901
+ writeByteDescending(b) {
9902
+ const masked = b & 0xff;
9903
+ if (masked === ESCAPE1) {
9904
+ this.writeEscapedByteDescending(ESCAPE1);
9905
+ this.writeEscapedByteDescending(NULL_BYTE);
9906
+ }
9907
+ else if (masked === ESCAPE2) {
9908
+ this.writeEscapedByteDescending(ESCAPE2);
9909
+ this.writeEscapedByteDescending(FF_BYTE);
9910
+ }
9911
+ else {
9912
+ this.writeEscapedByteDescending(b);
9913
+ }
9914
+ }
9915
+ writeSeparatorAscending() {
9916
+ this.writeEscapedByteAscending(ESCAPE1);
9917
+ this.writeEscapedByteAscending(SEPARATOR);
9918
+ }
9919
+ writeSeparatorDescending() {
9920
+ this.writeEscapedByteDescending(ESCAPE1);
9921
+ this.writeEscapedByteDescending(SEPARATOR);
9922
+ }
9923
+ writeEscapedByteAscending(b) {
9924
+ this.ensureAvailable(1);
9925
+ this.buffer[this.position++] = b;
9926
+ }
9927
+ writeEscapedByteDescending(b) {
9928
+ this.ensureAvailable(1);
9929
+ this.buffer[this.position++] = ~b;
9930
+ }
9931
+ ensureAvailable(bytes) {
9932
+ const minCapacity = bytes + this.position;
9933
+ if (minCapacity <= this.buffer.length) {
9934
+ return;
9935
+ }
9936
+ // Try doubling.
9937
+ let newLength = this.buffer.length * 2;
9938
+ // Still not big enough? Just allocate the right size.
9939
+ if (newLength < minCapacity) {
9940
+ newLength = minCapacity;
9941
+ }
9942
+ // Create the new buffer.
9943
+ const newBuffer = new Uint8Array(newLength);
9944
+ newBuffer.set(this.buffer); // copy old data
9945
+ this.buffer = newBuffer;
9946
+ }
9947
+ }
9948
+
9949
+ class AscendingIndexByteEncoder {
9950
+ constructor(orderedCode) {
9951
+ this.orderedCode = orderedCode;
9952
+ }
9953
+ writeBytes(value) {
9954
+ this.orderedCode.writeBytesAscending(value);
9955
+ }
9956
+ writeString(value) {
9957
+ this.orderedCode.writeUtf8Ascending(value);
9958
+ }
9959
+ writeNumber(value) {
9960
+ this.orderedCode.writeNumberAscending(value);
9961
+ }
9962
+ writeInfinity() {
9963
+ this.orderedCode.writeInfinityAscending();
9964
+ }
9965
+ }
9966
+ class DescendingIndexByteEncoder {
9967
+ constructor(orderedCode) {
9968
+ this.orderedCode = orderedCode;
9969
+ }
9970
+ writeBytes(value) {
9971
+ this.orderedCode.writeBytesDescending(value);
9972
+ }
9973
+ writeString(value) {
9974
+ this.orderedCode.writeUtf8Descending(value);
9975
+ }
9976
+ writeNumber(value) {
9977
+ this.orderedCode.writeNumberDescending(value);
9978
+ }
9979
+ writeInfinity() {
9980
+ this.orderedCode.writeInfinityDescending();
9981
+ }
9982
+ }
9983
+ /**
9984
+ * Implements `DirectionalIndexByteEncoder` using `OrderedCodeWriter` for the
9985
+ * actual encoding.
9986
+ */
9987
+ class IndexByteEncoder {
9988
+ constructor() {
9989
+ this.orderedCode = new OrderedCodeWriter();
9990
+ this.ascending = new AscendingIndexByteEncoder(this.orderedCode);
9991
+ this.descending = new DescendingIndexByteEncoder(this.orderedCode);
9992
+ }
9993
+ seed(encodedBytes) {
9994
+ this.orderedCode.seed(encodedBytes);
9995
+ }
9996
+ forKind(kind) {
9997
+ return kind === 0 /* ASCENDING */ ? this.ascending : this.descending;
9998
+ }
9999
+ encodedBytes() {
10000
+ return this.orderedCode.encodedBytes();
10001
+ }
10002
+ reset() {
10003
+ this.orderedCode.reset();
10004
+ }
10005
+ }
10006
+
10007
+ /**
10008
+ * @license
10009
+ * Copyright 2022 Google LLC
10010
+ *
10011
+ * Licensed under the Apache License, Version 2.0 (the "License");
10012
+ * you may not use this file except in compliance with the License.
10013
+ * You may obtain a copy of the License at
10014
+ *
10015
+ * http://www.apache.org/licenses/LICENSE-2.0
10016
+ *
10017
+ * Unless required by applicable law or agreed to in writing, software
10018
+ * distributed under the License is distributed on an "AS IS" BASIS,
10019
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10020
+ * See the License for the specific language governing permissions and
10021
+ * limitations under the License.
10022
+ */
10023
+ /** Represents an index entry saved by the SDK in persisted storage. */
10024
+ class IndexEntry {
10025
+ constructor(indexId, documentKey, arrayValue, directionalValue) {
10026
+ this.indexId = indexId;
10027
+ this.documentKey = documentKey;
10028
+ this.arrayValue = arrayValue;
10029
+ this.directionalValue = directionalValue;
10030
+ }
10031
+ /**
10032
+ * Returns an IndexEntry entry that sorts immediately after the current
10033
+ * directional value.
10034
+ */
10035
+ successor() {
10036
+ const currentLength = this.directionalValue.length;
10037
+ const newLength = currentLength === 0 || this.directionalValue[currentLength - 1] === 255
10038
+ ? currentLength + 1
10039
+ : currentLength;
10040
+ const successor = new Uint8Array(newLength);
10041
+ successor.set(this.directionalValue, 0);
10042
+ if (newLength !== currentLength) {
10043
+ successor.set([0], this.directionalValue.length);
10044
+ }
10045
+ else {
10046
+ ++successor[successor.length - 1];
10047
+ }
10048
+ return new IndexEntry(this.indexId, this.documentKey, this.arrayValue, successor);
10049
+ }
10050
+ }
10051
+ function indexEntryComparator(left, right) {
10052
+ let cmp = left.indexId - right.indexId;
10053
+ if (cmp !== 0) {
10054
+ return cmp;
10055
+ }
10056
+ cmp = compareByteArrays(left.arrayValue, right.arrayValue);
10057
+ if (cmp !== 0) {
10058
+ return cmp;
10059
+ }
10060
+ cmp = compareByteArrays(left.directionalValue, right.directionalValue);
10061
+ if (cmp !== 0) {
10062
+ return cmp;
10063
+ }
10064
+ return DocumentKey.comparator(left.documentKey, right.documentKey);
10065
+ }
10066
+ function compareByteArrays(left, right) {
10067
+ for (let i = 0; i < left.length && i < right.length; ++i) {
10068
+ const compare = left[i] - right[i];
10069
+ if (compare !== 0) {
10070
+ return compare;
10071
+ }
10072
+ }
10073
+ return left.length - right.length;
10074
+ }
10075
+
10076
+ /**
10077
+ * @license
10078
+ * Copyright 2022 Google LLC
10079
+ *
10080
+ * Licensed under the Apache License, Version 2.0 (the "License");
10081
+ * you may not use this file except in compliance with the License.
10082
+ * You may obtain a copy of the License at
10083
+ *
10084
+ * http://www.apache.org/licenses/LICENSE-2.0
10085
+ *
10086
+ * Unless required by applicable law or agreed to in writing, software
10087
+ * distributed under the License is distributed on an "AS IS" BASIS,
10088
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10089
+ * See the License for the specific language governing permissions and
10090
+ * limitations under the License.
10091
+ */
10092
+ /**
10093
+ * A light query planner for Firestore.
10094
+ *
10095
+ * This class matches a `FieldIndex` against a Firestore Query `Target`. It
10096
+ * determines whether a given index can be used to serve the specified target.
10097
+ *
10098
+ * The following table showcases some possible index configurations:
10099
+ *
10100
+ * Query | Index
10101
+ * -----------------------------------------------------------------------------
10102
+ * where('a', '==', 'a').where('b', '==', 'b') | a ASC, b DESC
10103
+ * where('a', '==', 'a').where('b', '==', 'b') | a ASC
10104
+ * where('a', '==', 'a').where('b', '==', 'b') | b DESC
10105
+ * where('a', '>=', 'a').orderBy('a') | a ASC
10106
+ * where('a', '>=', 'a').orderBy('a', 'desc') | a DESC
10107
+ * where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC, b ASC
10108
+ * where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC
10109
+ * where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS, b ASCENDING
10110
+ * where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS
10111
+ */
10112
+ class TargetIndexMatcher {
10113
+ constructor(target) {
10114
+ this.collectionId =
10115
+ target.collectionGroup != null
10116
+ ? target.collectionGroup
10117
+ : target.path.lastSegment();
10118
+ this.orderBys = target.orderBy;
10119
+ this.equalityFilters = [];
10120
+ for (const filter of target.filters) {
10121
+ const fieldFilter = filter;
10122
+ if (fieldFilter.isInequality()) {
10123
+ this.inequalityFilter = fieldFilter;
10124
+ }
10125
+ else {
10126
+ this.equalityFilters.push(fieldFilter);
10127
+ }
10128
+ }
10129
+ }
10130
+ /**
10131
+ * Returns whether the index can be used to serve the TargetIndexMatcher's
10132
+ * target.
10133
+ *
10134
+ * An index is considered capable of serving the target when:
10135
+ * - The target uses all index segments for its filters and orderBy clauses.
10136
+ * The target can have additional filter and orderBy clauses, but not
10137
+ * fewer.
10138
+ * - If an ArrayContains/ArrayContainsAnyfilter is used, the index must also
10139
+ * have a corresponding `CONTAINS` segment.
10140
+ * - All directional index segments can be mapped to the target as a series of
10141
+ * equality filters, a single inequality filter and a series of orderBy
10142
+ * clauses.
10143
+ * - The segments that represent the equality filters may appear out of order.
10144
+ * - The optional segment for the inequality filter must appear after all
10145
+ * equality segments.
10146
+ * - The segments that represent that orderBy clause of the target must appear
10147
+ * in order after all equality and inequality segments. Single orderBy
10148
+ * clauses cannot be skipped, but a continuous orderBy suffix may be
10149
+ * omitted.
10150
+ */
10151
+ servedByIndex(index) {
10152
+ // If there is an array element, find a matching filter.
10153
+ const arraySegment = fieldIndexGetArraySegment(index);
10154
+ if (arraySegment !== undefined &&
10155
+ !this.hasMatchingEqualityFilter(arraySegment)) {
10156
+ return false;
10157
+ }
10158
+ const segments = fieldIndexGetDirectionalSegments(index);
10159
+ let segmentIndex = 0;
10160
+ let orderBysIndex = 0;
10161
+ // Process all equalities first. Equalities can appear out of order.
10162
+ for (; segmentIndex < segments.length; ++segmentIndex) {
10163
+ // We attempt to greedily match all segments to equality filters. If a
10164
+ // filter matches an index segment, we can mark the segment as used.
10165
+ // Since it is not possible to use the same field path in both an equality
10166
+ // and inequality/oderBy clause, we do not have to consider the possibility
10167
+ // that a matching equality segment should instead be used to map to an
10168
+ // inequality filter or orderBy clause.
10169
+ if (!this.hasMatchingEqualityFilter(segments[segmentIndex])) {
10170
+ // If we cannot find a matching filter, we need to verify whether the
10171
+ // remaining segments map to the target's inequality and its orderBy
10172
+ // clauses.
10173
+ break;
10174
+ }
10175
+ }
10176
+ // If we already have processed all segments, all segments are used to serve
10177
+ // the equality filters and we do not need to map any segments to the
10178
+ // target's inequality and orderBy clauses.
10179
+ if (segmentIndex === segments.length) {
10180
+ return true;
10181
+ }
10182
+ // If there is an inequality filter, the next segment must match both the
10183
+ // filter and the first orderBy clause.
10184
+ if (this.inequalityFilter !== undefined) {
10185
+ const segment = segments[segmentIndex];
10186
+ if (!this.matchesFilter(this.inequalityFilter, segment) ||
10187
+ !this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
10188
+ return false;
10189
+ }
10190
+ ++segmentIndex;
10191
+ }
10192
+ // All remaining segments need to represent the prefix of the target's
10193
+ // orderBy.
10194
+ for (; segmentIndex < segments.length; ++segmentIndex) {
10195
+ const segment = segments[segmentIndex];
10196
+ if (orderBysIndex >= this.orderBys.length ||
10197
+ !this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
10198
+ return false;
10199
+ }
10200
+ }
10201
+ return true;
10202
+ }
10203
+ hasMatchingEqualityFilter(segment) {
10204
+ for (const filter of this.equalityFilters) {
10205
+ if (this.matchesFilter(filter, segment)) {
10206
+ return true;
10207
+ }
10208
+ }
10209
+ return false;
10210
+ }
10211
+ matchesFilter(filter, segment) {
10212
+ if (filter === undefined || !filter.field.isEqual(segment.fieldPath)) {
10213
+ return false;
10214
+ }
10215
+ const isArrayOperator = filter.op === "array-contains" /* ARRAY_CONTAINS */ ||
10216
+ filter.op === "array-contains-any" /* ARRAY_CONTAINS_ANY */;
10217
+ return (segment.kind === 2 /* CONTAINS */) === isArrayOperator;
10218
+ }
10219
+ matchesOrderBy(orderBy, segment) {
10220
+ if (!orderBy.field.isEqual(segment.fieldPath)) {
10221
+ return false;
10222
+ }
10223
+ return ((segment.kind === 0 /* ASCENDING */ &&
10224
+ orderBy.dir === "asc" /* ASCENDING */) ||
10225
+ (segment.kind === 1 /* DESCENDING */ &&
10226
+ orderBy.dir === "desc" /* DESCENDING */));
10227
+ }
10228
+ }
10229
+
10230
+ /**
10231
+ * @license
10232
+ * Copyright 2017 Google LLC
10233
+ *
10234
+ * Licensed under the Apache License, Version 2.0 (the "License");
10235
+ * you may not use this file except in compliance with the License.
10236
+ * You may obtain a copy of the License at
10237
+ *
10238
+ * http://www.apache.org/licenses/LICENSE-2.0
10239
+ *
10240
+ * Unless required by applicable law or agreed to in writing, software
10241
+ * distributed under the License is distributed on an "AS IS" BASIS,
10242
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10243
+ * See the License for the specific language governing permissions and
10244
+ * limitations under the License.
10245
+ */
10246
+ /**
10247
+ * A map implementation that uses objects as keys. Objects must have an
10248
+ * associated equals function and must be immutable. Entries in the map are
10249
+ * stored together with the key being produced from the mapKeyFn. This map
10250
+ * automatically handles collisions of keys.
10251
+ */
10252
+ class ObjectMap {
10253
+ constructor(mapKeyFn, equalsFn) {
10254
+ this.mapKeyFn = mapKeyFn;
10255
+ this.equalsFn = equalsFn;
10256
+ /**
10257
+ * The inner map for a key/value pair. Due to the possibility of collisions we
10258
+ * keep a list of entries that we do a linear search through to find an actual
10259
+ * match. Note that collisions should be rare, so we still expect near
10260
+ * constant time lookups in practice.
10261
+ */
10262
+ this.inner = {};
10263
+ }
10264
+ /** Get a value for this key, or undefined if it does not exist. */
10265
+ get(key) {
10266
+ const id = this.mapKeyFn(key);
10267
+ const matches = this.inner[id];
10268
+ if (matches === undefined) {
10269
+ return undefined;
10270
+ }
10271
+ for (const [otherKey, value] of matches) {
10272
+ if (this.equalsFn(otherKey, key)) {
10273
+ return value;
10274
+ }
10275
+ }
10276
+ return undefined;
10277
+ }
10278
+ has(key) {
10279
+ return this.get(key) !== undefined;
10280
+ }
10281
+ /** Put this key and value in the map. */
10282
+ set(key, value) {
10283
+ const id = this.mapKeyFn(key);
10284
+ const matches = this.inner[id];
10285
+ if (matches === undefined) {
10286
+ this.inner[id] = [[key, value]];
10287
+ return;
10288
+ }
10289
+ for (let i = 0; i < matches.length; i++) {
10290
+ if (this.equalsFn(matches[i][0], key)) {
10291
+ matches[i] = [key, value];
10292
+ return;
10293
+ }
10294
+ }
10295
+ matches.push([key, value]);
10296
+ }
10297
+ /**
10298
+ * Remove this key from the map. Returns a boolean if anything was deleted.
10299
+ */
10300
+ delete(key) {
10301
+ const id = this.mapKeyFn(key);
10302
+ const matches = this.inner[id];
10303
+ if (matches === undefined) {
10304
+ return false;
10305
+ }
10306
+ for (let i = 0; i < matches.length; i++) {
10307
+ if (this.equalsFn(matches[i][0], key)) {
10308
+ if (matches.length === 1) {
10309
+ delete this.inner[id];
10310
+ }
10311
+ else {
10312
+ matches.splice(i, 1);
10313
+ }
10314
+ return true;
10315
+ }
10316
+ }
10317
+ return false;
10318
+ }
10319
+ forEach(fn) {
10320
+ forEach(this.inner, (_, entries) => {
10321
+ for (const [k, v] of entries) {
10322
+ fn(k, v);
10323
+ }
10324
+ });
10325
+ }
10326
+ isEmpty() {
10327
+ return isEmpty(this.inner);
10328
+ }
10329
+ }
10330
+
10331
+ /**
10332
+ * @license
10333
+ * Copyright 2019 Google LLC
10334
+ *
10335
+ * Licensed under the Apache License, Version 2.0 (the "License");
10336
+ * you may not use this file except in compliance with the License.
10337
+ * You may obtain a copy of the License at
10338
+ *
10339
+ * http://www.apache.org/licenses/LICENSE-2.0
10340
+ *
10341
+ * Unless required by applicable law or agreed to in writing, software
10342
+ * distributed under the License is distributed on an "AS IS" BASIS,
10343
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10344
+ * See the License for the specific language governing permissions and
10345
+ * limitations under the License.
10346
+ */
10347
+ /**
10348
+ * An in-memory implementation of IndexManager.
10349
+ */
10350
+ class MemoryIndexManager {
10351
+ constructor() {
10352
+ this.collectionParentIndex = new MemoryCollectionParentIndex();
10353
+ }
10354
+ addToCollectionParentIndex(transaction, collectionPath) {
10355
+ this.collectionParentIndex.add(collectionPath);
10356
+ return PersistencePromise.resolve();
10357
+ }
10358
+ getCollectionParents(transaction, collectionId) {
10359
+ return PersistencePromise.resolve(this.collectionParentIndex.getEntries(collectionId));
10360
+ }
10361
+ addFieldIndex(transaction, index) {
10362
+ // Field indices are not supported with memory persistence.
10363
+ return PersistencePromise.resolve();
10364
+ }
10365
+ deleteFieldIndex(transaction, index) {
10366
+ // Field indices are not supported with memory persistence.
10367
+ return PersistencePromise.resolve();
10368
+ }
10369
+ getDocumentsMatchingTarget(transaction, target) {
10370
+ // Field indices are not supported with memory persistence.
10371
+ return PersistencePromise.resolve(null);
10372
+ }
10373
+ getFieldIndex(transaction, target) {
10374
+ // Field indices are not supported with memory persistence.
10375
+ return PersistencePromise.resolve(null);
10376
+ }
10377
+ getFieldIndexes(transaction, collectionGroup) {
10378
+ // Field indices are not supported with memory persistence.
10379
+ return PersistencePromise.resolve([]);
10380
+ }
10381
+ getNextCollectionGroupToUpdate(transaction) {
10382
+ // Field indices are not supported with memory persistence.
10383
+ return PersistencePromise.resolve(null);
10384
+ }
10385
+ updateCollectionGroup(transaction, collectionGroup, offset) {
10386
+ // Field indices are not supported with memory persistence.
10387
+ return PersistencePromise.resolve();
10388
+ }
10389
+ updateIndexEntries(transaction, documents) {
10390
+ // Field indices are not supported with memory persistence.
10391
+ return PersistencePromise.resolve();
10392
+ }
10393
+ }
10394
+ /**
10395
+ * Internal implementation of the collection-parent index exposed by MemoryIndexManager.
10396
+ * Also used for in-memory caching by IndexedDbIndexManager and initial index population
10397
+ * in indexeddb_schema.ts
10398
+ */
10399
+ class MemoryCollectionParentIndex {
10400
+ constructor() {
10401
+ this.index = {};
10402
+ }
10403
+ // Returns false if the entry already existed.
10404
+ add(collectionPath) {
10405
+ const collectionId = collectionPath.lastSegment();
10406
+ const parentPath = collectionPath.popLast();
10407
+ const existingParents = this.index[collectionId] ||
10408
+ new SortedSet(ResourcePath.comparator);
10409
+ const added = !existingParents.has(parentPath);
10410
+ this.index[collectionId] = existingParents.add(parentPath);
10411
+ return added;
10412
+ }
10413
+ has(collectionPath) {
10414
+ const collectionId = collectionPath.lastSegment();
10415
+ const parentPath = collectionPath.popLast();
10416
+ const existingParents = this.index[collectionId];
10417
+ return existingParents && existingParents.has(parentPath);
10418
+ }
10419
+ getEntries(collectionId) {
10420
+ const parentPaths = this.index[collectionId] ||
10421
+ new SortedSet(ResourcePath.comparator);
10422
+ return parentPaths.toArray();
10423
+ }
10424
+ }
10425
+
10426
+ /**
10427
+ * @license
10428
+ * Copyright 2019 Google LLC
10429
+ *
8519
10430
  * Licensed under the Apache License, Version 2.0 (the "License");
8520
10431
  * you may not use this file except in compliance with the License.
8521
10432
  * You may obtain a copy of the License at
@@ -8528,19 +10439,31 @@ class MemoryCollectionParentIndex {
8528
10439
  * See the License for the specific language governing permissions and
8529
10440
  * limitations under the License.
8530
10441
  */
10442
+ const LOG_TAG$f = 'IndexedDbIndexManager';
10443
+ const EMPTY_VALUE = new Uint8Array(0);
8531
10444
  /**
8532
10445
  * A persisted implementation of IndexManager.
10446
+ *
10447
+ * PORTING NOTE: Unlike iOS and Android, the Web SDK does not memoize index
10448
+ * data as it supports multi-tab access.
8533
10449
  */
8534
10450
  class IndexedDbIndexManager {
8535
- constructor() {
10451
+ constructor(user) {
10452
+ this.user = user;
8536
10453
  /**
8537
10454
  * An in-memory copy of the index entries we've already written since the SDK
8538
10455
  * launched. Used to avoid re-writing the same entry repeatedly.
8539
10456
  *
8540
- * This is *NOT* a complete cache of what's in persistence and so can never be used to
8541
- * satisfy reads.
10457
+ * This is *NOT* a complete cache of what's in persistence and so can never be
10458
+ * used to satisfy reads.
8542
10459
  */
8543
10460
  this.collectionParentsCache = new MemoryCollectionParentIndex();
10461
+ /**
10462
+ * Maps from a target to its equivalent list of sub-targets. Each sub-target
10463
+ * contains only one term from the target's disjunctive normal form (DNF).
10464
+ */
10465
+ this.targetToDnfSubTargets = new ObjectMap(t => canonifyTarget(t), (l, r) => targetEquals(l, r));
10466
+ this.uid = user.uid || '';
8544
10467
  }
8545
10468
  /**
8546
10469
  * Adds a new entry to the collection parent index.
@@ -8587,6 +10510,414 @@ class IndexedDbIndexManager {
8587
10510
  return parentPaths;
8588
10511
  });
8589
10512
  }
10513
+ addFieldIndex(transaction, index) {
10514
+ // TODO(indexing): Verify that the auto-incrementing index ID works in
10515
+ // Safari & Firefox.
10516
+ const indexes = indexConfigurationStore(transaction);
10517
+ const dbIndex = toDbIndexConfiguration(index);
10518
+ delete dbIndex.indexId; // `indexId` is auto-populated by IndexedDb
10519
+ return indexes.add(dbIndex).next();
10520
+ }
10521
+ deleteFieldIndex(transaction, index) {
10522
+ const indexes = indexConfigurationStore(transaction);
10523
+ const states = indexStateStore(transaction);
10524
+ const entries = indexEntriesStore(transaction);
10525
+ return indexes
10526
+ .delete(index.indexId)
10527
+ .next(() => states.delete(IDBKeyRange.bound([index.indexId], [index.indexId + 1],
10528
+ /*lowerOpen=*/ false,
10529
+ /*upperOpen=*/ true)))
10530
+ .next(() => entries.delete(IDBKeyRange.bound([index.indexId], [index.indexId + 1],
10531
+ /*lowerOpen=*/ false,
10532
+ /*upperOpen=*/ true)));
10533
+ }
10534
+ getDocumentsMatchingTarget(transaction, target) {
10535
+ const indexEntries = indexEntriesStore(transaction);
10536
+ let canServeTarget = true;
10537
+ const indexes = new Map();
10538
+ return PersistencePromise.forEach(this.getSubTargets(target), (subTarget) => {
10539
+ return this.getFieldIndex(transaction, subTarget).next(index => {
10540
+ canServeTarget && (canServeTarget = !!index);
10541
+ indexes.set(subTarget, index);
10542
+ });
10543
+ }).next(() => {
10544
+ if (!canServeTarget) {
10545
+ return PersistencePromise.resolve(null);
10546
+ }
10547
+ else {
10548
+ let result = documentKeySet();
10549
+ return PersistencePromise.forEach(indexes, (index, subTarget) => {
10550
+ logDebug(LOG_TAG$f, `Using index ${fieldIndexToString(index)} to execute ${canonifyTarget(target)}`);
10551
+ const arrayValues = targetGetArrayValues(subTarget, index);
10552
+ const notInValues = targetGetNotInValues(subTarget, index);
10553
+ const lowerBound = targetGetLowerBound(subTarget, index);
10554
+ const upperBound = targetGetUpperBound(subTarget, index);
10555
+ const lowerBoundEncoded = this.encodeBound(index, subTarget, lowerBound);
10556
+ const upperBoundEncoded = this.encodeBound(index, subTarget, upperBound);
10557
+ const notInEncoded = this.encodeValues(index, subTarget, notInValues);
10558
+ const indexRanges = this.generateIndexRanges(index.indexId, arrayValues, lowerBoundEncoded, !!lowerBound && lowerBound.inclusive, upperBoundEncoded, !!upperBound && upperBound.inclusive, notInEncoded);
10559
+ return PersistencePromise.forEach(indexRanges, (indexRange) => {
10560
+ return indexEntries
10561
+ .loadFirst(indexRange, target.limit)
10562
+ .next(entries => {
10563
+ entries.forEach(entry => {
10564
+ result = result.add(new DocumentKey(decodeResourcePath(entry.documentKey)));
10565
+ });
10566
+ });
10567
+ });
10568
+ }).next(() => result);
10569
+ }
10570
+ });
10571
+ }
10572
+ getSubTargets(target) {
10573
+ let subTargets = this.targetToDnfSubTargets.get(target);
10574
+ if (subTargets) {
10575
+ return subTargets;
10576
+ }
10577
+ // TODO(orquery): Implement DNF transform
10578
+ subTargets = [target];
10579
+ this.targetToDnfSubTargets.set(target, subTargets);
10580
+ return subTargets;
10581
+ }
10582
+ /**
10583
+ * Constructs a key range query on `DbIndexEntry.store` that unions all
10584
+ * bounds.
10585
+ */
10586
+ generateIndexRanges(indexId, arrayValues, lowerBounds, lowerBoundInclusive, upperBounds, upperBoundInclusive, notInValues) {
10587
+ // The number of total index scans we union together. This is similar to a
10588
+ // distributed normal form, but adapted for array values. We create a single
10589
+ // index range per value in an ARRAY_CONTAINS or ARRAY_CONTAINS_ANY filter
10590
+ // combined with the values from the query bounds.
10591
+ const totalScans = (arrayValues != null ? arrayValues.length : 1) *
10592
+ Math.max(lowerBounds != null ? lowerBounds.length : 1, upperBounds != null ? upperBounds.length : 1);
10593
+ const scansPerArrayElement = totalScans / (arrayValues != null ? arrayValues.length : 1);
10594
+ const indexRanges = [];
10595
+ for (let i = 0; i < totalScans; ++i) {
10596
+ const arrayValue = arrayValues
10597
+ ? this.encodeSingleElement(arrayValues[i / scansPerArrayElement])
10598
+ : EMPTY_VALUE;
10599
+ const lowerBound = lowerBounds
10600
+ ? this.generateLowerBound(indexId, arrayValue, lowerBounds[i % scansPerArrayElement], lowerBoundInclusive)
10601
+ : this.generateEmptyBound(indexId);
10602
+ const upperBound = upperBounds
10603
+ ? this.generateUpperBound(indexId, arrayValue, upperBounds[i % scansPerArrayElement], upperBoundInclusive)
10604
+ : this.generateEmptyBound(indexId + 1);
10605
+ indexRanges.push(...this.createRange(lowerBound, upperBound, notInValues.map((notIn // make non-nullable
10606
+ ) => this.generateLowerBound(indexId, arrayValue, notIn,
10607
+ /* inclusive= */ true))));
10608
+ }
10609
+ return indexRanges;
10610
+ }
10611
+ /** Generates the lower bound for `arrayValue` and `directionalValue`. */
10612
+ generateLowerBound(indexId, arrayValue, directionalValue, inclusive) {
10613
+ const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
10614
+ return inclusive ? entry : entry.successor();
10615
+ }
10616
+ /** Generates the upper bound for `arrayValue` and `directionalValue`. */
10617
+ generateUpperBound(indexId, arrayValue, directionalValue, inclusive) {
10618
+ const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
10619
+ return inclusive ? entry.successor() : entry;
10620
+ }
10621
+ /**
10622
+ * Generates an empty bound that scopes the index scan to the current index
10623
+ * and user.
10624
+ */
10625
+ generateEmptyBound(indexId) {
10626
+ return new IndexEntry(indexId, DocumentKey.empty(), EMPTY_VALUE, EMPTY_VALUE);
10627
+ }
10628
+ getFieldIndex(transaction, target) {
10629
+ const targetIndexMatcher = new TargetIndexMatcher(target);
10630
+ const collectionGroup = target.collectionGroup != null
10631
+ ? target.collectionGroup
10632
+ : target.path.lastSegment();
10633
+ return this.getFieldIndexes(transaction, collectionGroup).next(indexes => {
10634
+ const matchingIndexes = indexes.filter(i => targetIndexMatcher.servedByIndex(i));
10635
+ // Return the index that matches the most number of segments.
10636
+ matchingIndexes.sort((l, r) => r.fields.length - l.fields.length);
10637
+ return matchingIndexes.length > 0 ? matchingIndexes[0] : null;
10638
+ });
10639
+ }
10640
+ /**
10641
+ * Returns the byte encoded form of the directional values in the field index.
10642
+ * Returns `null` if the document does not have all fields specified in the
10643
+ * index.
10644
+ */
10645
+ encodeDirectionalElements(fieldIndex, document) {
10646
+ const encoder = new IndexByteEncoder();
10647
+ for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
10648
+ const field = document.data.field(segment.fieldPath);
10649
+ if (field == null) {
10650
+ return null;
10651
+ }
10652
+ const directionalEncoder = encoder.forKind(segment.kind);
10653
+ FirestoreIndexValueWriter.INSTANCE.writeIndexValue(field, directionalEncoder);
10654
+ }
10655
+ return encoder.encodedBytes();
10656
+ }
10657
+ /** Encodes a single value to the ascending index format. */
10658
+ encodeSingleElement(value) {
10659
+ const encoder = new IndexByteEncoder();
10660
+ FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, encoder.forKind(0 /* ASCENDING */));
10661
+ return encoder.encodedBytes();
10662
+ }
10663
+ /**
10664
+ * Encodes the given field values according to the specification in `target`.
10665
+ * For IN queries, a list of possible values is returned.
10666
+ */
10667
+ encodeValues(fieldIndex, target, bound) {
10668
+ if (bound === null) {
10669
+ return [];
10670
+ }
10671
+ let encoders = [];
10672
+ encoders.push(new IndexByteEncoder());
10673
+ let boundIdx = 0;
10674
+ for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
10675
+ const value = bound[boundIdx++];
10676
+ for (const encoder of encoders) {
10677
+ if (this.isInFilter(target, segment.fieldPath) && isArray(value)) {
10678
+ encoders = this.expandIndexValues(encoders, segment, value);
10679
+ }
10680
+ else {
10681
+ const directionalEncoder = encoder.forKind(segment.kind);
10682
+ FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, directionalEncoder);
10683
+ }
10684
+ }
10685
+ }
10686
+ return this.getEncodedBytes(encoders);
10687
+ }
10688
+ /**
10689
+ * Encodes the given bounds according to the specification in `target`. For IN
10690
+ * queries, a list of possible values is returned.
10691
+ */
10692
+ encodeBound(fieldIndex, target, bound) {
10693
+ if (bound == null) {
10694
+ return null;
10695
+ }
10696
+ return this.encodeValues(fieldIndex, target, bound.position);
10697
+ }
10698
+ /** Returns the byte representation for the provided encoders. */
10699
+ getEncodedBytes(encoders) {
10700
+ const result = [];
10701
+ for (let i = 0; i < encoders.length; ++i) {
10702
+ result[i] = encoders[i].encodedBytes();
10703
+ }
10704
+ return result;
10705
+ }
10706
+ /**
10707
+ * Creates a separate encoder for each element of an array.
10708
+ *
10709
+ * The method appends each value to all existing encoders (e.g. filter("a",
10710
+ * "==", "a1").filter("b", "in", ["b1", "b2"]) becomes ["a1,b1", "a1,b2"]). A
10711
+ * list of new encoders is returned.
10712
+ */
10713
+ expandIndexValues(encoders, segment, value) {
10714
+ const prefixes = [...encoders];
10715
+ const results = [];
10716
+ for (const arrayElement of value.arrayValue.values || []) {
10717
+ for (const prefix of prefixes) {
10718
+ const clonedEncoder = new IndexByteEncoder();
10719
+ clonedEncoder.seed(prefix.encodedBytes());
10720
+ FirestoreIndexValueWriter.INSTANCE.writeIndexValue(arrayElement, clonedEncoder.forKind(segment.kind));
10721
+ results.push(clonedEncoder);
10722
+ }
10723
+ }
10724
+ return results;
10725
+ }
10726
+ isInFilter(target, fieldPath) {
10727
+ return !!target.filters.find(f => f instanceof FieldFilter &&
10728
+ f.field.isEqual(fieldPath) &&
10729
+ (f.op === "in" /* IN */ || f.op === "not-in" /* NOT_IN */));
10730
+ }
10731
+ getFieldIndexes(transaction, collectionGroup) {
10732
+ const indexes = indexConfigurationStore(transaction);
10733
+ const states = indexStateStore(transaction);
10734
+ return (collectionGroup
10735
+ ? indexes.loadAll(DbIndexConfiguration.collectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
10736
+ : indexes.loadAll()).next(indexConfigs => {
10737
+ const result = [];
10738
+ return PersistencePromise.forEach(indexConfigs, (indexConfig) => {
10739
+ return states
10740
+ .get([indexConfig.indexId, this.uid])
10741
+ .next(indexState => {
10742
+ result.push(fromDbIndexConfiguration(indexConfig, indexState));
10743
+ });
10744
+ }).next(() => result);
10745
+ });
10746
+ }
10747
+ getNextCollectionGroupToUpdate(transaction) {
10748
+ return this.getFieldIndexes(transaction).next(indexes => {
10749
+ if (indexes.length === 0) {
10750
+ return null;
10751
+ }
10752
+ indexes.sort((l, r) => l.indexState.sequenceNumber - r.indexState.sequenceNumber);
10753
+ return indexes[0].collectionGroup;
10754
+ });
10755
+ }
10756
+ updateCollectionGroup(transaction, collectionGroup, offset) {
10757
+ const indexes = indexConfigurationStore(transaction);
10758
+ const states = indexStateStore(transaction);
10759
+ return this.getNextSequenceNumber(transaction).next(nextSequenceNumber => indexes
10760
+ .loadAll(DbIndexConfiguration.collectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
10761
+ .next(configs => PersistencePromise.forEach(configs, (config) => states.put(toDbIndexState(config.indexId, this.user, nextSequenceNumber, offset)))));
10762
+ }
10763
+ updateIndexEntries(transaction, documents) {
10764
+ // Porting Note: `getFieldIndexes()` on Web does not cache index lookups as
10765
+ // it could be used across different IndexedDB transactions. As any cached
10766
+ // data might be invalidated by other multi-tab clients, we can only trust
10767
+ // data within a single IndexedDB transaction. We therefore add a cache
10768
+ // here.
10769
+ const memoizedIndexes = new Map();
10770
+ return PersistencePromise.forEach(documents, (key, doc) => {
10771
+ const memoizedCollectionIndexes = memoizedIndexes.get(key.collectionGroup);
10772
+ const fieldIndexes = memoizedCollectionIndexes
10773
+ ? PersistencePromise.resolve(memoizedCollectionIndexes)
10774
+ : this.getFieldIndexes(transaction, key.collectionGroup);
10775
+ return fieldIndexes.next(fieldIndexes => {
10776
+ memoizedIndexes.set(key.collectionGroup, fieldIndexes);
10777
+ return PersistencePromise.forEach(fieldIndexes, (fieldIndex) => {
10778
+ return this.getExistingIndexEntries(transaction, key, fieldIndex).next(existingEntries => {
10779
+ const newEntries = this.computeIndexEntries(doc, fieldIndex);
10780
+ if (!existingEntries.isEqual(newEntries)) {
10781
+ return this.updateEntries(transaction, doc, existingEntries, newEntries);
10782
+ }
10783
+ return PersistencePromise.resolve();
10784
+ });
10785
+ });
10786
+ });
10787
+ });
10788
+ }
10789
+ addIndexEntry(transaction, document, indexEntry) {
10790
+ const indexEntries = indexEntriesStore(transaction);
10791
+ return indexEntries.put(new DbIndexEntry(indexEntry.indexId, this.uid, indexEntry.arrayValue, indexEntry.directionalValue, encodeResourcePath(document.key.path)));
10792
+ }
10793
+ deleteIndexEntry(transaction, document, indexEntry) {
10794
+ const indexEntries = indexEntriesStore(transaction);
10795
+ return indexEntries.delete([
10796
+ indexEntry.indexId,
10797
+ this.uid,
10798
+ indexEntry.arrayValue,
10799
+ indexEntry.directionalValue,
10800
+ encodeResourcePath(document.key.path)
10801
+ ]);
10802
+ }
10803
+ getExistingIndexEntries(transaction, documentKey, fieldIndex) {
10804
+ const indexEntries = indexEntriesStore(transaction);
10805
+ let results = new SortedSet(indexEntryComparator);
10806
+ return indexEntries
10807
+ .iterate({
10808
+ index: DbIndexEntry.documentKeyIndex,
10809
+ range: IDBKeyRange.only([
10810
+ fieldIndex.indexId,
10811
+ this.uid,
10812
+ encodeResourcePath(documentKey.path)
10813
+ ])
10814
+ }, (_, entry) => {
10815
+ results = results.add(new IndexEntry(fieldIndex.indexId, documentKey, entry.arrayValue, entry.directionalValue));
10816
+ })
10817
+ .next(() => results);
10818
+ }
10819
+ /** Creates the index entries for the given document. */
10820
+ computeIndexEntries(document, fieldIndex) {
10821
+ let results = new SortedSet(indexEntryComparator);
10822
+ const directionalValue = this.encodeDirectionalElements(fieldIndex, document);
10823
+ if (directionalValue == null) {
10824
+ return results;
10825
+ }
10826
+ const arraySegment = fieldIndexGetArraySegment(fieldIndex);
10827
+ if (arraySegment != null) {
10828
+ const value = document.data.field(arraySegment.fieldPath);
10829
+ if (isArray(value)) {
10830
+ for (const arrayValue of value.arrayValue.values || []) {
10831
+ results = results.add(new IndexEntry(fieldIndex.indexId, document.key, this.encodeSingleElement(arrayValue), directionalValue));
10832
+ }
10833
+ }
10834
+ }
10835
+ else {
10836
+ results = results.add(new IndexEntry(fieldIndex.indexId, document.key, EMPTY_VALUE, directionalValue));
10837
+ }
10838
+ return results;
10839
+ }
10840
+ /**
10841
+ * Updates the index entries for the provided document by deleting entries
10842
+ * that are no longer referenced in `newEntries` and adding all newly added
10843
+ * entries.
10844
+ */
10845
+ updateEntries(transaction, document, existingEntries, newEntries) {
10846
+ logDebug(LOG_TAG$f, "Updating index entries for document '%s'", document.key);
10847
+ const promises = [];
10848
+ diffSortedSets(existingEntries, newEntries, indexEntryComparator,
10849
+ /* onAdd= */ entry => {
10850
+ promises.push(this.addIndexEntry(transaction, document, entry));
10851
+ },
10852
+ /* onRemove= */ entry => {
10853
+ promises.push(this.deleteIndexEntry(transaction, document, entry));
10854
+ });
10855
+ return PersistencePromise.waitFor(promises);
10856
+ }
10857
+ getNextSequenceNumber(transaction) {
10858
+ let nextSequenceNumber = 1;
10859
+ const states = indexStateStore(transaction);
10860
+ return states
10861
+ .iterate({
10862
+ index: DbIndexState.sequenceNumberIndex,
10863
+ reverse: true,
10864
+ range: IDBKeyRange.upperBound([this.uid, Number.MAX_SAFE_INTEGER])
10865
+ }, (_, state, controller) => {
10866
+ controller.done();
10867
+ nextSequenceNumber = state.sequenceNumber + 1;
10868
+ })
10869
+ .next(() => nextSequenceNumber);
10870
+ }
10871
+ /**
10872
+ * Returns a new set of IDB ranges that splits the existing range and excludes
10873
+ * any values that match the `notInValue` from these ranges. As an example,
10874
+ * '[foo > 2 && foo != 3]` becomes `[foo > 2 && < 3, foo > 3]`.
10875
+ */
10876
+ createRange(lower, upper, notInValues) {
10877
+ // The notIb values need to be sorted and unique so that we can return a
10878
+ // sorted set of non-overlapping ranges.
10879
+ notInValues = notInValues
10880
+ .sort((l, r) => indexEntryComparator(l, r))
10881
+ .filter((el, i, values) => !i || indexEntryComparator(el, values[i - 1]) !== 0);
10882
+ const bounds = [];
10883
+ bounds.push(lower);
10884
+ for (const notInValue of notInValues) {
10885
+ const cmpToLower = indexEntryComparator(notInValue, lower);
10886
+ const cmpToUpper = indexEntryComparator(notInValue, upper);
10887
+ if (cmpToLower === 0) {
10888
+ // `notInValue` is the lower bound. We therefore need to raise the bound
10889
+ // to the next value.
10890
+ bounds[0] = lower.successor();
10891
+ }
10892
+ else if (cmpToLower > 0 && cmpToUpper < 0) {
10893
+ // `notInValue` is in the middle of the range
10894
+ bounds.push(notInValue);
10895
+ bounds.push(notInValue.successor());
10896
+ }
10897
+ else if (cmpToUpper > 0) {
10898
+ // `notInValue` (and all following values) are out of the range
10899
+ break;
10900
+ }
10901
+ }
10902
+ bounds.push(upper);
10903
+ const ranges = [];
10904
+ for (let i = 0; i < bounds.length; i += 2) {
10905
+ ranges.push(IDBKeyRange.bound([
10906
+ bounds[i].indexId,
10907
+ this.uid,
10908
+ bounds[i].arrayValue,
10909
+ bounds[i].directionalValue,
10910
+ ''
10911
+ ], [
10912
+ bounds[i + 1].indexId,
10913
+ this.uid,
10914
+ bounds[i + 1].arrayValue,
10915
+ bounds[i + 1].directionalValue,
10916
+ ''
10917
+ ]));
10918
+ }
10919
+ return ranges;
10920
+ }
8590
10921
  }
8591
10922
  /**
8592
10923
  * Helper to get a typed SimpleDbStore for the collectionParents
@@ -8594,6 +10925,24 @@ class IndexedDbIndexManager {
8594
10925
  */
8595
10926
  function collectionParentsStore(txn) {
8596
10927
  return getStore(txn, DbCollectionParent.store);
10928
+ }
10929
+ /**
10930
+ * Helper to get a typed SimpleDbStore for the index entry object store.
10931
+ */
10932
+ function indexEntriesStore(txn) {
10933
+ return getStore(txn, DbIndexEntry.store);
10934
+ }
10935
+ /**
10936
+ * Helper to get a typed SimpleDbStore for the index configuration object store.
10937
+ */
10938
+ function indexConfigurationStore(txn) {
10939
+ return getStore(txn, DbIndexConfiguration.store);
10940
+ }
10941
+ /**
10942
+ * Helper to get a typed SimpleDbStore for the index state object store.
10943
+ */
10944
+ function indexStateStore(txn) {
10945
+ return getStore(txn, DbIndexState.store);
8597
10946
  }
8598
10947
 
8599
10948
  /**
@@ -9757,7 +12106,7 @@ class IndexedDbLruDelegateImpl {
9757
12106
  // Our size accounting requires us to read all documents before
9758
12107
  // removing them.
9759
12108
  return changeBuffer.getEntry(txn, docKey).next(() => {
9760
- changeBuffer.removeEntry(docKey);
12109
+ changeBuffer.removeEntry(docKey, SnapshotVersion.min());
9761
12110
  return documentTargetStore(txn).delete(sentinelKey$1(docKey));
9762
12111
  });
9763
12112
  }
@@ -9779,163 +12128,62 @@ class IndexedDbLruDelegateImpl {
9779
12128
  }
9780
12129
  /**
9781
12130
  * Call provided function for each document in the cache that is 'orphaned'. Orphaned
9782
- * means not a part of any target, so the only entry in the target-document index for
9783
- * that document will be the sentinel row (targetId 0), which will also have the sequence
9784
- * number for the last time the document was accessed.
9785
- */
9786
- forEachOrphanedDocument(txn, f) {
9787
- const store = documentTargetStore(txn);
9788
- let nextToReport = ListenSequence.INVALID;
9789
- let nextPath;
9790
- return store
9791
- .iterate({
9792
- index: DbTargetDocument.documentTargetsIndex
9793
- }, ([targetId, docKey], { path, sequenceNumber }) => {
9794
- if (targetId === 0) {
9795
- // if nextToReport is valid, report it, this is a new key so the
9796
- // last one must not be a member of any targets.
9797
- if (nextToReport !== ListenSequence.INVALID) {
9798
- f(new DocumentKey(decodeResourcePath(nextPath)), nextToReport);
9799
- }
9800
- // set nextToReport to be this sequence number. It's the next one we
9801
- // might report, if we don't find any targets for this document.
9802
- // Note that the sequence number must be defined when the targetId
9803
- // is 0.
9804
- nextToReport = sequenceNumber;
9805
- nextPath = path;
9806
- }
9807
- else {
9808
- // set nextToReport to be invalid, we know we don't need to report
9809
- // this one since we found a target for it.
9810
- nextToReport = ListenSequence.INVALID;
9811
- }
9812
- })
9813
- .next(() => {
9814
- // Since we report sequence numbers after getting to the next key, we
9815
- // need to check if the last key we iterated over was an orphaned
9816
- // document and report it.
9817
- if (nextToReport !== ListenSequence.INVALID) {
9818
- f(new DocumentKey(decodeResourcePath(nextPath)), nextToReport);
9819
- }
9820
- });
9821
- }
9822
- getCacheSize(txn) {
9823
- return this.db.getRemoteDocumentCache().getSize(txn);
9824
- }
9825
- }
9826
- function sentinelKey$1(key) {
9827
- return [0, encodeResourcePath(key.path)];
9828
- }
9829
- /**
9830
- * @returns A value suitable for writing a sentinel row in the target-document
9831
- * store.
9832
- */
9833
- function sentinelRow(key, sequenceNumber) {
9834
- return new DbTargetDocument(0, encodeResourcePath(key.path), sequenceNumber);
9835
- }
9836
- function writeSentinelKey(txn, key) {
9837
- return documentTargetStore(txn).put(sentinelRow(key, txn.currentSequenceNumber));
9838
- }
9839
-
9840
- /**
9841
- * @license
9842
- * Copyright 2017 Google LLC
9843
- *
9844
- * Licensed under the Apache License, Version 2.0 (the "License");
9845
- * you may not use this file except in compliance with the License.
9846
- * You may obtain a copy of the License at
9847
- *
9848
- * http://www.apache.org/licenses/LICENSE-2.0
9849
- *
9850
- * Unless required by applicable law or agreed to in writing, software
9851
- * distributed under the License is distributed on an "AS IS" BASIS,
9852
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9853
- * See the License for the specific language governing permissions and
9854
- * limitations under the License.
9855
- */
9856
- /**
9857
- * A map implementation that uses objects as keys. Objects must have an
9858
- * associated equals function and must be immutable. Entries in the map are
9859
- * stored together with the key being produced from the mapKeyFn. This map
9860
- * automatically handles collisions of keys.
9861
- */
9862
- class ObjectMap {
9863
- constructor(mapKeyFn, equalsFn) {
9864
- this.mapKeyFn = mapKeyFn;
9865
- this.equalsFn = equalsFn;
9866
- /**
9867
- * The inner map for a key/value pair. Due to the possibility of collisions we
9868
- * keep a list of entries that we do a linear search through to find an actual
9869
- * match. Note that collisions should be rare, so we still expect near
9870
- * constant time lookups in practice.
9871
- */
9872
- this.inner = {};
9873
- }
9874
- /** Get a value for this key, or undefined if it does not exist. */
9875
- get(key) {
9876
- const id = this.mapKeyFn(key);
9877
- const matches = this.inner[id];
9878
- if (matches === undefined) {
9879
- return undefined;
9880
- }
9881
- for (const [otherKey, value] of matches) {
9882
- if (this.equalsFn(otherKey, key)) {
9883
- return value;
9884
- }
9885
- }
9886
- return undefined;
9887
- }
9888
- has(key) {
9889
- return this.get(key) !== undefined;
9890
- }
9891
- /** Put this key and value in the map. */
9892
- set(key, value) {
9893
- const id = this.mapKeyFn(key);
9894
- const matches = this.inner[id];
9895
- if (matches === undefined) {
9896
- this.inner[id] = [[key, value]];
9897
- return;
9898
- }
9899
- for (let i = 0; i < matches.length; i++) {
9900
- if (this.equalsFn(matches[i][0], key)) {
9901
- matches[i] = [key, value];
9902
- return;
9903
- }
9904
- }
9905
- matches.push([key, value]);
9906
- }
9907
- /**
9908
- * Remove this key from the map. Returns a boolean if anything was deleted.
9909
- */
9910
- delete(key) {
9911
- const id = this.mapKeyFn(key);
9912
- const matches = this.inner[id];
9913
- if (matches === undefined) {
9914
- return false;
9915
- }
9916
- for (let i = 0; i < matches.length; i++) {
9917
- if (this.equalsFn(matches[i][0], key)) {
9918
- if (matches.length === 1) {
9919
- delete this.inner[id];
9920
- }
9921
- else {
9922
- matches.splice(i, 1);
12131
+ * means not a part of any target, so the only entry in the target-document index for
12132
+ * that document will be the sentinel row (targetId 0), which will also have the sequence
12133
+ * number for the last time the document was accessed.
12134
+ */
12135
+ forEachOrphanedDocument(txn, f) {
12136
+ const store = documentTargetStore(txn);
12137
+ let nextToReport = ListenSequence.INVALID;
12138
+ let nextPath;
12139
+ return store
12140
+ .iterate({
12141
+ index: DbTargetDocument.documentTargetsIndex
12142
+ }, ([targetId, docKey], { path, sequenceNumber }) => {
12143
+ if (targetId === 0) {
12144
+ // if nextToReport is valid, report it, this is a new key so the
12145
+ // last one must not be a member of any targets.
12146
+ if (nextToReport !== ListenSequence.INVALID) {
12147
+ f(new DocumentKey(decodeResourcePath(nextPath)), nextToReport);
9923
12148
  }
9924
- return true;
12149
+ // set nextToReport to be this sequence number. It's the next one we
12150
+ // might report, if we don't find any targets for this document.
12151
+ // Note that the sequence number must be defined when the targetId
12152
+ // is 0.
12153
+ nextToReport = sequenceNumber;
12154
+ nextPath = path;
9925
12155
  }
9926
- }
9927
- return false;
9928
- }
9929
- forEach(fn) {
9930
- forEach(this.inner, (_, entries) => {
9931
- for (const [k, v] of entries) {
9932
- fn(k, v);
12156
+ else {
12157
+ // set nextToReport to be invalid, we know we don't need to report
12158
+ // this one since we found a target for it.
12159
+ nextToReport = ListenSequence.INVALID;
12160
+ }
12161
+ })
12162
+ .next(() => {
12163
+ // Since we report sequence numbers after getting to the next key, we
12164
+ // need to check if the last key we iterated over was an orphaned
12165
+ // document and report it.
12166
+ if (nextToReport !== ListenSequence.INVALID) {
12167
+ f(new DocumentKey(decodeResourcePath(nextPath)), nextToReport);
9933
12168
  }
9934
12169
  });
9935
12170
  }
9936
- isEmpty() {
9937
- return isEmpty(this.inner);
12171
+ getCacheSize(txn) {
12172
+ return this.db.getRemoteDocumentCache().getSize(txn);
9938
12173
  }
12174
+ }
12175
+ function sentinelKey$1(key) {
12176
+ return [0, encodeResourcePath(key.path)];
12177
+ }
12178
+ /**
12179
+ * @returns A value suitable for writing a sentinel row in the target-document
12180
+ * store.
12181
+ */
12182
+ function sentinelRow(key, sequenceNumber) {
12183
+ return new DbTargetDocument(0, encodeResourcePath(key.path), sequenceNumber);
12184
+ }
12185
+ function writeSentinelKey(txn, key) {
12186
+ return documentTargetStore(txn).put(sentinelRow(key, txn.currentSequenceNumber));
9939
12187
  }
9940
12188
 
9941
12189
  /**
@@ -9970,27 +12218,19 @@ class ObjectMap {
9970
12218
  */
9971
12219
  class RemoteDocumentChangeBuffer {
9972
12220
  constructor() {
9973
- // A mapping of document key to the new cache entry that should be written (or null if any
9974
- // existing cache entry should be removed).
12221
+ // A mapping of document key to the new cache entry that should be written.
9975
12222
  this.changes = new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
9976
12223
  this.changesApplied = false;
9977
12224
  }
9978
- getReadTime(key) {
9979
- const change = this.changes.get(key);
9980
- if (change) {
9981
- return change.readTime;
9982
- }
9983
- return SnapshotVersion.min();
9984
- }
9985
12225
  /**
9986
12226
  * Buffers a `RemoteDocumentCache.addEntry()` call.
9987
12227
  *
9988
12228
  * You can only modify documents that have already been retrieved via
9989
12229
  * `getEntry()/getEntries()` (enforced via IndexedDbs `apply()`).
9990
12230
  */
9991
- addEntry(document, readTime) {
12231
+ addEntry(document) {
9992
12232
  this.assertNotApplied();
9993
- this.changes.set(document.key, { document, readTime });
12233
+ this.changes.set(document.key, document);
9994
12234
  }
9995
12235
  /**
9996
12236
  * Buffers a `RemoteDocumentCache.removeEntry()` call.
@@ -9998,12 +12238,9 @@ class RemoteDocumentChangeBuffer {
9998
12238
  * You can only remove documents that have already been retrieved via
9999
12239
  * `getEntry()/getEntries()` (enforced via IndexedDbs `apply()`).
10000
12240
  */
10001
- removeEntry(key, readTime = null) {
12241
+ removeEntry(key, readTime) {
10002
12242
  this.assertNotApplied();
10003
- this.changes.set(key, {
10004
- document: MutableDocument.newInvalidDocument(key),
10005
- readTime
10006
- });
12243
+ this.changes.set(key, MutableDocument.newInvalidDocument(key).setReadTime(readTime));
10007
12244
  }
10008
12245
  /**
10009
12246
  * Looks up an entry in the cache. The buffered changes will first be checked,
@@ -10020,7 +12257,7 @@ class RemoteDocumentChangeBuffer {
10020
12257
  this.assertNotApplied();
10021
12258
  const bufferedEntry = this.changes.get(documentKey);
10022
12259
  if (bufferedEntry !== undefined) {
10023
- return PersistencePromise.resolve(bufferedEntry.document);
12260
+ return PersistencePromise.resolve(bufferedEntry);
10024
12261
  }
10025
12262
  else {
10026
12263
  return this.getFromCache(transaction, documentKey);
@@ -10074,12 +12311,10 @@ class RemoteDocumentChangeBuffer {
10074
12311
  * `newIndexedDbRemoteDocumentCache()`.
10075
12312
  */
10076
12313
  class IndexedDbRemoteDocumentCacheImpl {
10077
- /**
10078
- * @param serializer - The document serializer.
10079
- * @param indexManager - The query indexes that need to be maintained.
10080
- */
10081
- constructor(serializer, indexManager) {
12314
+ constructor(serializer) {
10082
12315
  this.serializer = serializer;
12316
+ }
12317
+ setIndexManager(indexManager) {
10083
12318
  this.indexManager = indexManager;
10084
12319
  }
10085
12320
  /**
@@ -10201,21 +12436,21 @@ class IndexedDbRemoteDocumentCacheImpl {
10201
12436
  }
10202
12437
  });
10203
12438
  }
10204
- getDocumentsMatchingQuery(transaction, query, sinceReadTime) {
12439
+ getAll(transaction, collection, sinceReadTime) {
10205
12440
  let results = mutableDocumentMap();
10206
- const immediateChildrenPathLength = query.path.length + 1;
12441
+ const immediateChildrenPathLength = collection.length + 1;
10207
12442
  const iterationOptions = {};
10208
12443
  if (sinceReadTime.isEqual(SnapshotVersion.min())) {
10209
12444
  // Documents are ordered by key, so we can use a prefix scan to narrow
10210
12445
  // down the documents we need to match the query against.
10211
- const startKey = query.path.toArray();
12446
+ const startKey = collection.toArray();
10212
12447
  iterationOptions.range = IDBKeyRange.lowerBound(startKey);
10213
12448
  }
10214
12449
  else {
10215
12450
  // Execute an index-free query and filter by read time. This is safe
10216
12451
  // since all document changes to queries that have a
10217
12452
  // lastLimboFreeSnapshotVersion (`sinceReadTime`) have a read time set.
10218
- const collectionKey = query.path.toArray();
12453
+ const collectionKey = collection.toArray();
10219
12454
  const readTimeKey = toDbTimestampKey(sinceReadTime);
10220
12455
  iterationOptions.range = IDBKeyRange.lowerBound([collectionKey, readTimeKey],
10221
12456
  /* open= */ true);
@@ -10231,13 +12466,13 @@ class IndexedDbRemoteDocumentCacheImpl {
10231
12466
  if (key.length !== immediateChildrenPathLength) {
10232
12467
  return;
10233
12468
  }
10234
- const document = fromDbRemoteDocument(this.serializer, dbRemoteDoc);
10235
- if (!query.path.isPrefixOf(document.key.path)) {
10236
- control.done();
10237
- }
10238
- else if (queryMatches(query, document)) {
12469
+ const document = this.maybeDecodeDocument(DocumentKey.fromSegments(key), dbRemoteDoc);
12470
+ if (collection.isPrefixOf(document.key.path)) {
10239
12471
  results = results.insert(document.key, document);
10240
12472
  }
12473
+ else {
12474
+ control.done();
12475
+ }
10241
12476
  })
10242
12477
  .next(() => results);
10243
12478
  }
@@ -10259,8 +12494,8 @@ class IndexedDbRemoteDocumentCacheImpl {
10259
12494
  return documentGlobalStore(txn).put(DbRemoteDocumentGlobal.key, metadata);
10260
12495
  }
10261
12496
  /**
10262
- * Decodes `remoteDoc` and returns the document (or null, if the document
10263
- * corresponds to the format used for sentinel deletes).
12497
+ * Decodes `dbRemoteDoc` and returns the document (or an invalid document if
12498
+ * the document corresponds to the format used for sentinel deletes).
10264
12499
  */
10265
12500
  maybeDecodeDocument(documentKey, dbRemoteDoc) {
10266
12501
  if (dbRemoteDoc) {
@@ -10275,14 +12510,9 @@ class IndexedDbRemoteDocumentCacheImpl {
10275
12510
  return MutableDocument.newInvalidDocument(documentKey);
10276
12511
  }
10277
12512
  }
10278
- /**
10279
- * Creates a new IndexedDbRemoteDocumentCache.
10280
- *
10281
- * @param serializer - The document serializer.
10282
- * @param indexManager - The query indexes that need to be maintained.
10283
- */
10284
- function newIndexedDbRemoteDocumentCache(serializer, indexManager) {
10285
- return new IndexedDbRemoteDocumentCacheImpl(serializer, indexManager);
12513
+ /** Creates a new IndexedDbRemoteDocumentCache. */
12514
+ function newIndexedDbRemoteDocumentCache(serializer) {
12515
+ return new IndexedDbRemoteDocumentCacheImpl(serializer);
10286
12516
  }
10287
12517
  /**
10288
12518
  * Returns the set of documents that have changed since the specified read
@@ -10354,8 +12584,8 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
10354
12584
  let collectionParents = new SortedSet((l, r) => primitiveComparator(l.canonicalString(), r.canonicalString()));
10355
12585
  this.changes.forEach((key, documentChange) => {
10356
12586
  const previousSize = this.documentSizes.get(key);
10357
- if (documentChange.document.isValidDocument()) {
10358
- const doc = toDbRemoteDocument(this.documentCache.serializer, documentChange.document, this.getReadTime(key));
12587
+ if (documentChange.isValidDocument()) {
12588
+ const doc = toDbRemoteDocument(this.documentCache.serializer, documentChange);
10359
12589
  collectionParents = collectionParents.add(key.path.popLast());
10360
12590
  const size = dbDocumentSize(doc);
10361
12591
  sizeDelta += size - previousSize;
@@ -10368,7 +12598,7 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
10368
12598
  // RemoteDocumentCache. This entry is represented by a NoDocument
10369
12599
  // with a version of 0 and ignored by `maybeDecodeDocument()` but
10370
12600
  // preserved in `getNewDocumentChanges()`.
10371
- const deletedDoc = toDbRemoteDocument(this.documentCache.serializer, MutableDocument.newNoDocument(key, SnapshotVersion.min()), this.getReadTime(key));
12601
+ const deletedDoc = toDbRemoteDocument(this.documentCache.serializer, documentChange.convertToNoDocument(SnapshotVersion.min()));
10372
12602
  promises.push(this.documentCache.addEntry(transaction, key, deletedDoc));
10373
12603
  }
10374
12604
  else {
@@ -10449,9 +12679,6 @@ class SchemaConverter {
10449
12679
  * and local feature development.
10450
12680
  */
10451
12681
  createOrUpgrade(db, txn, fromVersion, toVersion) {
10452
- hardAssert(fromVersion < toVersion &&
10453
- fromVersion >= 0 &&
10454
- toVersion <= SCHEMA_VERSION);
10455
12682
  const simpleDbTransaction = new SimpleDbTransaction('createOrUpgrade', txn);
10456
12683
  if (fromVersion < 1 && toVersion >= 1) {
10457
12684
  createPrimaryClientStore(db);
@@ -10518,6 +12745,16 @@ class SchemaConverter {
10518
12745
  createNamedQueriesStore(db);
10519
12746
  });
10520
12747
  }
12748
+ if (fromVersion < 12 && toVersion >= 12) {
12749
+ p = p.next(() => {
12750
+ createDocumentOverlayStore(db);
12751
+ });
12752
+ }
12753
+ if (fromVersion < 13 && toVersion >= 13) {
12754
+ p = p.next(() => {
12755
+ createFieldIndex(db);
12756
+ });
12757
+ }
10521
12758
  return p;
10522
12759
  }
10523
12760
  addDocumentGlobal(txn) {
@@ -10724,6 +12961,28 @@ function createNamedQueriesStore(db) {
10724
12961
  db.createObjectStore(DbNamedQuery.store, {
10725
12962
  keyPath: DbNamedQuery.keyPath
10726
12963
  });
12964
+ }
12965
+ function createFieldIndex(db) {
12966
+ const indexConfiguratioStore = db.createObjectStore(DbIndexConfiguration.store, {
12967
+ keyPath: DbIndexConfiguration.keyPath,
12968
+ autoIncrement: true
12969
+ });
12970
+ indexConfiguratioStore.createIndex(DbIndexConfiguration.collectionGroupIndex, DbIndexConfiguration.collectionGroupIndexPath, { unique: false });
12971
+ const indexStateStore = db.createObjectStore(DbIndexState.store, {
12972
+ keyPath: DbIndexState.keyPath
12973
+ });
12974
+ indexStateStore.createIndex(DbIndexState.sequenceNumberIndex, DbIndexState.sequenceNumberIndexPath, { unique: false });
12975
+ const indexEntryStore = db.createObjectStore(DbIndexEntry.store, {
12976
+ keyPath: DbIndexEntry.keyPath
12977
+ });
12978
+ indexEntryStore.createIndex(DbIndexEntry.documentKeyIndex, DbIndexEntry.documentKeyIndexPath, { unique: false });
12979
+ }
12980
+ function createDocumentOverlayStore(db) {
12981
+ const documentOverlayStore = db.createObjectStore(DbDocumentOverlay.store, {
12982
+ keyPath: DbDocumentOverlay.keyPath
12983
+ });
12984
+ documentOverlayStore.createIndex(DbDocumentOverlay.collectionPathOverlayIndex, DbDocumentOverlay.collectionPathOverlayIndexPath, { unique: false });
12985
+ documentOverlayStore.createIndex(DbDocumentOverlay.collectionGroupOverlayIndex, DbDocumentOverlay.collectionGroupOverlayIndexPath, { unique: false });
10727
12986
  }
10728
12987
 
10729
12988
  /**
@@ -10835,7 +13094,7 @@ class IndexedDbPersistence {
10835
13094
  * If set to true, forcefully obtains database access. Existing tabs will
10836
13095
  * no longer be able to access IndexedDB.
10837
13096
  */
10838
- forceOwningTab) {
13097
+ forceOwningTab, schemaVersion = SCHEMA_VERSION) {
10839
13098
  this.allowTabSynchronization = allowTabSynchronization;
10840
13099
  this.persistenceKey = persistenceKey;
10841
13100
  this.clientId = clientId;
@@ -10844,6 +13103,7 @@ class IndexedDbPersistence {
10844
13103
  this.document = document;
10845
13104
  this.sequenceNumberSyncer = sequenceNumberSyncer;
10846
13105
  this.forceOwningTab = forceOwningTab;
13106
+ this.schemaVersion = schemaVersion;
10847
13107
  this.listenSequence = null;
10848
13108
  this._started = false;
10849
13109
  this.isPrimary = false;
@@ -10865,10 +13125,9 @@ class IndexedDbPersistence {
10865
13125
  this.referenceDelegate = new IndexedDbLruDelegateImpl(this, lruParams);
10866
13126
  this.dbName = persistenceKey + MAIN_DATABASE;
10867
13127
  this.serializer = new LocalSerializer(serializer);
10868
- this.simpleDb = new SimpleDb(this.dbName, SCHEMA_VERSION, new SchemaConverter(this.serializer));
13128
+ this.simpleDb = new SimpleDb(this.dbName, this.schemaVersion, new SchemaConverter(this.serializer));
10869
13129
  this.targetCache = new IndexedDbTargetCache(this.referenceDelegate, this.serializer);
10870
- this.indexManager = new IndexedDbIndexManager();
10871
- this.remoteDocumentCache = newIndexedDbRemoteDocumentCache(this.serializer, this.indexManager);
13130
+ this.remoteDocumentCache = newIndexedDbRemoteDocumentCache(this.serializer);
10872
13131
  this.bundleCache = new IndexedDbBundleCache();
10873
13132
  if (this.window && this.window.localStorage) {
10874
13133
  this.webStorage = this.window.localStorage;
@@ -11204,8 +13463,8 @@ class IndexedDbPersistence {
11204
13463
  get started() {
11205
13464
  return this._started;
11206
13465
  }
11207
- getMutationQueue(user) {
11208
- return IndexedDbMutationQueue.forUser(user, this.serializer, this.indexManager, this.referenceDelegate);
13466
+ getMutationQueue(user, indexManager) {
13467
+ return IndexedDbMutationQueue.forUser(user, this.serializer, indexManager, this.referenceDelegate);
11209
13468
  }
11210
13469
  getTargetCache() {
11211
13470
  return this.targetCache;
@@ -11213,8 +13472,11 @@ class IndexedDbPersistence {
11213
13472
  getRemoteDocumentCache() {
11214
13473
  return this.remoteDocumentCache;
11215
13474
  }
11216
- getIndexManager() {
11217
- return this.indexManager;
13475
+ getIndexManager(user) {
13476
+ return new IndexedDbIndexManager(user);
13477
+ }
13478
+ getDocumentOverlayCache(user) {
13479
+ return IndexedDbDocumentOverlayCache.forUser(this.serializer, user);
11218
13480
  }
11219
13481
  getBundleCache() {
11220
13482
  return this.bundleCache;
@@ -11222,11 +13484,12 @@ class IndexedDbPersistence {
11222
13484
  runTransaction(action, mode, transactionOperation) {
11223
13485
  logDebug(LOG_TAG$d, 'Starting transaction:', action);
11224
13486
  const simpleDbMode = mode === 'readonly' ? 'readonly' : 'readwrite';
13487
+ const objectStores = getObjectStores(this.schemaVersion);
11225
13488
  let persistenceTransaction;
11226
13489
  // Do all transactions as readwrite against all object stores, since we
11227
13490
  // are the only reader/writer.
11228
13491
  return this.simpleDb
11229
- .runTransaction(action, simpleDbMode, ALL_STORES, simpleDbTxn => {
13492
+ .runTransaction(action, simpleDbMode, objectStores, simpleDbTxn => {
11230
13493
  persistenceTransaction = new IndexedDbTransaction(simpleDbTxn, this.listenSequence
11231
13494
  ? this.listenSequence.next()
11232
13495
  : ListenSequence.INVALID);
@@ -11598,39 +13861,28 @@ class LocalDocumentsView {
11598
13861
  getDocumentsMatchingCollectionQuery(transaction, query, sinceReadTime) {
11599
13862
  // Query the remote documents and overlay mutations.
11600
13863
  let results;
11601
- let mutationBatches;
11602
13864
  return this.remoteDocumentCache
11603
- .getDocumentsMatchingQuery(transaction, query, sinceReadTime)
13865
+ .getAll(transaction, query.path, sinceReadTime)
11604
13866
  .next(queryResults => {
11605
13867
  results = queryResults;
11606
13868
  return this.mutationQueue.getAllMutationBatchesAffectingQuery(transaction, query);
11607
13869
  })
11608
- .next(matchingMutationBatches => {
11609
- mutationBatches = matchingMutationBatches;
11610
- // It is possible that a PatchMutation can make a document match a query, even if
11611
- // the version in the RemoteDocumentCache is not a match yet (waiting for server
11612
- // to ack). To handle this, we find all document keys affected by the PatchMutations
11613
- // that are not in `result` yet, and back fill them via `remoteDocumentCache.getEntries`,
11614
- // otherwise those `PatchMutations` will be ignored because no base document can be found,
11615
- // and lead to missing result for the query.
11616
- return this.addMissingBaseDocuments(transaction, mutationBatches, results).next(mergedDocuments => {
11617
- results = mergedDocuments;
11618
- for (const batch of mutationBatches) {
11619
- for (const mutation of batch.mutations) {
11620
- const key = mutation.key;
11621
- let document = results.get(key);
11622
- if (document == null) {
11623
- // Create invalid document to apply mutations on top of
11624
- document = MutableDocument.newInvalidDocument(key);
11625
- results = results.insert(key, document);
11626
- }
11627
- mutationApplyToLocalView(mutation, document, batch.localWriteTime);
11628
- if (!document.isFoundDocument()) {
11629
- results = results.remove(key);
11630
- }
13870
+ .next(mutationBatches => {
13871
+ for (const batch of mutationBatches) {
13872
+ for (const mutation of batch.mutations) {
13873
+ const key = mutation.key;
13874
+ let document = results.get(key);
13875
+ if (document == null) {
13876
+ // Create invalid document to apply mutations on top of
13877
+ document = MutableDocument.newInvalidDocument(key);
13878
+ results = results.insert(key, document);
13879
+ }
13880
+ mutationApplyToLocalView(mutation, document, batch.localWriteTime);
13881
+ if (!document.isFoundDocument()) {
13882
+ results = results.remove(key);
11631
13883
  }
11632
13884
  }
11633
- });
13885
+ }
11634
13886
  })
11635
13887
  .next(() => {
11636
13888
  // Finally, filter out any documents that don't actually match
@@ -11643,29 +13895,6 @@ class LocalDocumentsView {
11643
13895
  return results;
11644
13896
  });
11645
13897
  }
11646
- addMissingBaseDocuments(transaction, matchingMutationBatches, existingDocuments) {
11647
- let missingBaseDocEntriesForPatching = documentKeySet();
11648
- for (const batch of matchingMutationBatches) {
11649
- for (const mutation of batch.mutations) {
11650
- if (mutation instanceof PatchMutation &&
11651
- existingDocuments.get(mutation.key) === null) {
11652
- missingBaseDocEntriesForPatching =
11653
- missingBaseDocEntriesForPatching.add(mutation.key);
11654
- }
11655
- }
11656
- }
11657
- let mergedDocuments = existingDocuments;
11658
- return this.remoteDocumentCache
11659
- .getEntries(transaction, missingBaseDocEntriesForPatching)
11660
- .next(missingBaseDocs => {
11661
- missingBaseDocs.forEach((key, doc) => {
11662
- if (doc.isFoundDocument()) {
11663
- mergedDocuments = mergedDocuments.insert(key, doc);
11664
- }
11665
- });
11666
- return mergedDocuments;
11667
- });
11668
- }
11669
13898
  }
11670
13899
 
11671
13900
  /**
@@ -11724,11 +13953,18 @@ class LocalStoreImpl {
11724
13953
  * PORTING NOTE: This is only used for multi-tab synchronization.
11725
13954
  */
11726
13955
  this.lastDocumentChangeReadTime = SnapshotVersion.min();
11727
- this.mutationQueue = persistence.getMutationQueue(initialUser);
11728
13956
  this.remoteDocuments = persistence.getRemoteDocumentCache();
11729
13957
  this.targetCache = persistence.getTargetCache();
11730
- this.localDocuments = new LocalDocumentsView(this.remoteDocuments, this.mutationQueue, this.persistence.getIndexManager());
11731
13958
  this.bundleCache = persistence.getBundleCache();
13959
+ this.initializeUserComponents(initialUser);
13960
+ }
13961
+ initializeUserComponents(user) {
13962
+ // TODO(indexing): Add spec tests that test these components change after a
13963
+ // user change
13964
+ this.indexManager = this.persistence.getIndexManager(user);
13965
+ this.mutationQueue = this.persistence.getMutationQueue(user, this.indexManager);
13966
+ this.localDocuments = new LocalDocumentsView(this.remoteDocuments, this.mutationQueue, this.indexManager);
13967
+ this.remoteDocuments.setIndexManager(this.indexManager);
11732
13968
  this.queryEngine.setLocalDocumentsView(this.localDocuments);
11733
13969
  }
11734
13970
  collectGarbage(garbageCollector) {
@@ -11750,8 +13986,6 @@ persistence, queryEngine, initialUser, serializer) {
11750
13986
  // change.
11751
13987
  async function localStoreHandleUserChange(localStore, user) {
11752
13988
  const localStoreImpl = debugCast(localStore);
11753
- let newMutationQueue = localStoreImpl.mutationQueue;
11754
- let newLocalDocuments = localStoreImpl.localDocuments;
11755
13989
  const result = await localStoreImpl.persistence.runTransaction('Handle user change', 'readonly', txn => {
11756
13990
  // Swap out the mutation queue, grabbing the pending mutation batches
11757
13991
  // before and after.
@@ -11760,11 +13994,8 @@ async function localStoreHandleUserChange(localStore, user) {
11760
13994
  .getAllMutationBatches(txn)
11761
13995
  .next(promisedOldBatches => {
11762
13996
  oldBatches = promisedOldBatches;
11763
- newMutationQueue = localStoreImpl.persistence.getMutationQueue(user);
11764
- // Recreate our LocalDocumentsView using the new
11765
- // MutationQueue.
11766
- newLocalDocuments = new LocalDocumentsView(localStoreImpl.remoteDocuments, newMutationQueue, localStoreImpl.persistence.getIndexManager());
11767
- return newMutationQueue.getAllMutationBatches(txn);
13997
+ localStoreImpl.initializeUserComponents(user);
13998
+ return localStoreImpl.mutationQueue.getAllMutationBatches(txn);
11768
13999
  })
11769
14000
  .next(newBatches => {
11770
14001
  const removedBatchIds = [];
@@ -11785,7 +14016,7 @@ async function localStoreHandleUserChange(localStore, user) {
11785
14016
  }
11786
14017
  // Return the set of all (potentially) changed documents and the list
11787
14018
  // of mutation batch IDs that were affected by change.
11788
- return newLocalDocuments
14019
+ return localStoreImpl.localDocuments
11789
14020
  .getDocuments(txn, changedKeys)
11790
14021
  .next(affectedDocuments => {
11791
14022
  return {
@@ -11796,9 +14027,6 @@ async function localStoreHandleUserChange(localStore, user) {
11796
14027
  });
11797
14028
  });
11798
14029
  });
11799
- localStoreImpl.mutationQueue = newMutationQueue;
11800
- localStoreImpl.localDocuments = newLocalDocuments;
11801
- localStoreImpl.queryEngine.setLocalDocumentsView(localStoreImpl.localDocuments);
11802
14030
  return result;
11803
14031
  }
11804
14032
  /* Accepts locally generated Mutations and commit them to storage. */
@@ -11955,14 +14183,14 @@ function localStoreApplyRemoteEventToLocalCache(localStore, remoteEvent) {
11955
14183
  }
11956
14184
  });
11957
14185
  let changedDocs = mutableDocumentMap();
11958
- remoteEvent.documentUpdates.forEach((key, doc) => {
14186
+ remoteEvent.documentUpdates.forEach(key => {
11959
14187
  if (remoteEvent.resolvedLimboDocuments.has(key)) {
11960
14188
  promises.push(localStoreImpl.persistence.referenceDelegate.updateLimboDocument(txn, key));
11961
14189
  }
11962
14190
  });
11963
14191
  // Each loop iteration only affects its "own" doc, so it's safe to get all the remote
11964
14192
  // documents in advance in a single call.
11965
- promises.push(populateDocumentChangeBuffer(txn, documentBuffer, remoteEvent.documentUpdates, remoteVersion, undefined).next(result => {
14193
+ promises.push(populateDocumentChangeBuffer(txn, documentBuffer, remoteEvent.documentUpdates).next(result => {
11966
14194
  changedDocs = result;
11967
14195
  }));
11968
14196
  // HACK: The only reason we allow a null snapshot version is so that we
@@ -12003,17 +14231,13 @@ function localStoreApplyRemoteEventToLocalCache(localStore, remoteEvent) {
12003
14231
  * Note: this function will use `documentVersions` if it is defined;
12004
14232
  * when it is not defined, resorts to `globalVersion`.
12005
14233
  */
12006
- function populateDocumentChangeBuffer(txn, documentBuffer, documents, globalVersion,
12007
- // TODO(wuandy): We could add `readTime` to MaybeDocument instead to remove
12008
- // this parameter.
12009
- documentVersions) {
14234
+ function populateDocumentChangeBuffer(txn, documentBuffer, documents) {
12010
14235
  let updatedKeys = documentKeySet();
12011
14236
  documents.forEach(k => (updatedKeys = updatedKeys.add(k)));
12012
14237
  return documentBuffer.getEntries(txn, updatedKeys).next(existingDocs => {
12013
14238
  let changedDocs = mutableDocumentMap();
12014
14239
  documents.forEach((key, doc) => {
12015
14240
  const existingDoc = existingDocs.get(key);
12016
- const docReadTime = (documentVersions === null || documentVersions === void 0 ? void 0 : documentVersions.get(key)) || globalVersion;
12017
14241
  // Note: The order of the steps below is important, since we want
12018
14242
  // to ensure that rejected limbo resolutions (which fabricate
12019
14243
  // NoDocuments with SnapshotVersion.min()) never add documents to
@@ -12022,14 +14246,14 @@ documentVersions) {
12022
14246
  // NoDocuments with SnapshotVersion.min() are used in manufactured
12023
14247
  // events. We remove these documents from cache since we lost
12024
14248
  // access.
12025
- documentBuffer.removeEntry(key, docReadTime);
14249
+ documentBuffer.removeEntry(key, doc.readTime);
12026
14250
  changedDocs = changedDocs.insert(key, doc);
12027
14251
  }
12028
14252
  else if (!existingDoc.isValidDocument() ||
12029
14253
  doc.version.compareTo(existingDoc.version) > 0 ||
12030
14254
  (doc.version.compareTo(existingDoc.version) === 0 &&
12031
14255
  existingDoc.hasPendingWrites)) {
12032
- documentBuffer.addEntry(doc, docReadTime);
14256
+ documentBuffer.addEntry(doc);
12033
14257
  changedDocs = changedDocs.insert(key, doc);
12034
14258
  }
12035
14259
  else {
@@ -12283,7 +14507,8 @@ function applyWriteToRemoteDocuments(localStoreImpl, txn, batchResult, documentB
12283
14507
  // We use the commitVersion as the readTime rather than the
12284
14508
  // document's updateTime since the updateTime is not advanced
12285
14509
  // for updates that do not modify the underlying document.
12286
- documentBuffer.addEntry(doc, batchResult.commitVersion);
14510
+ doc.setReadTime(batchResult.commitVersion);
14511
+ documentBuffer.addEntry(doc);
12287
14512
  }
12288
14513
  }
12289
14514
  });
@@ -12384,14 +14609,14 @@ async function localStoreApplyBundledDocuments(localStore, bundleConverter, docu
12384
14609
  const localStoreImpl = debugCast(localStore);
12385
14610
  let documentKeys = documentKeySet();
12386
14611
  let documentMap = mutableDocumentMap();
12387
- let versionMap = documentVersionMap();
12388
14612
  for (const bundleDoc of documents) {
12389
14613
  const documentKey = bundleConverter.toDocumentKey(bundleDoc.metadata.name);
12390
14614
  if (bundleDoc.document) {
12391
14615
  documentKeys = documentKeys.add(documentKey);
12392
14616
  }
12393
- documentMap = documentMap.insert(documentKey, bundleConverter.toMutableDocument(bundleDoc));
12394
- versionMap = versionMap.insert(documentKey, bundleConverter.toSnapshotVersion(bundleDoc.metadata.readTime));
14617
+ const doc = bundleConverter.toMutableDocument(bundleDoc);
14618
+ doc.setReadTime(bundleConverter.toSnapshotVersion(bundleDoc.metadata.readTime));
14619
+ documentMap = documentMap.insert(documentKey, doc);
12395
14620
  }
12396
14621
  const documentBuffer = localStoreImpl.remoteDocuments.newChangeBuffer({
12397
14622
  trackRemovals: true // Make sure document removals show up in `getNewDocumentChanges()`
@@ -12400,7 +14625,7 @@ async function localStoreApplyBundledDocuments(localStore, bundleConverter, docu
12400
14625
  // they will not get garbage collected right away.
12401
14626
  const umbrellaTargetData = await localStoreAllocateTarget(localStoreImpl, umbrellaTarget(bundleName));
12402
14627
  return localStoreImpl.persistence.runTransaction('Apply bundle documents', 'readwrite', txn => {
12403
- return populateDocumentChangeBuffer(txn, documentBuffer, documentMap, SnapshotVersion.min(), versionMap)
14628
+ return populateDocumentChangeBuffer(txn, documentBuffer, documentMap)
12404
14629
  .next(changedDocs => {
12405
14630
  documentBuffer.apply(txn);
12406
14631
  return changedDocs;
@@ -12514,6 +14739,122 @@ class MemoryBundleCache {
12514
14739
  }
12515
14740
  }
12516
14741
 
14742
+ /**
14743
+ * @license
14744
+ * Copyright 2022 Google LLC
14745
+ *
14746
+ * Licensed under the Apache License, Version 2.0 (the "License");
14747
+ * you may not use this file except in compliance with the License.
14748
+ * You may obtain a copy of the License at
14749
+ *
14750
+ * http://www.apache.org/licenses/LICENSE-2.0
14751
+ *
14752
+ * Unless required by applicable law or agreed to in writing, software
14753
+ * distributed under the License is distributed on an "AS IS" BASIS,
14754
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14755
+ * See the License for the specific language governing permissions and
14756
+ * limitations under the License.
14757
+ */
14758
+ /**
14759
+ * An in-memory implementation of DocumentOverlayCache.
14760
+ */
14761
+ class MemoryDocumentOverlayCache {
14762
+ constructor() {
14763
+ // A map sorted by DocumentKey, whose value is a pair of the largest batch id
14764
+ // for the overlay and the overlay itself.
14765
+ this.overlays = new SortedMap(DocumentKey.comparator);
14766
+ this.overlayByBatchId = new Map();
14767
+ }
14768
+ getOverlay(transaction, key) {
14769
+ return PersistencePromise.resolve(this.overlays.get(key));
14770
+ }
14771
+ saveOverlays(transaction, largestBatchId, overlays) {
14772
+ overlays.forEach(mutation => {
14773
+ this.saveOverlay(transaction, largestBatchId, mutation);
14774
+ });
14775
+ return PersistencePromise.resolve();
14776
+ }
14777
+ removeOverlaysForBatchId(transaction, documentKeys, batchId) {
14778
+ const keys = this.overlayByBatchId.get(batchId);
14779
+ if (keys !== undefined) {
14780
+ keys.forEach(key => (this.overlays = this.overlays.remove(key)));
14781
+ this.overlayByBatchId.delete(batchId);
14782
+ }
14783
+ return PersistencePromise.resolve();
14784
+ }
14785
+ getOverlaysForCollection(transaction, collection, sinceBatchId) {
14786
+ const result = new Map();
14787
+ const immediateChildrenPathLength = collection.length + 1;
14788
+ const prefix = new DocumentKey(collection.child(''));
14789
+ const iter = this.overlays.getIteratorFrom(prefix);
14790
+ while (iter.hasNext()) {
14791
+ const entry = iter.getNext();
14792
+ const overlay = entry.value;
14793
+ const key = overlay.getKey();
14794
+ if (!collection.isPrefixOf(key.path)) {
14795
+ break;
14796
+ }
14797
+ // Documents from sub-collections
14798
+ if (key.path.length !== immediateChildrenPathLength) {
14799
+ continue;
14800
+ }
14801
+ if (overlay.largestBatchId > sinceBatchId) {
14802
+ result.set(overlay.getKey(), overlay);
14803
+ }
14804
+ }
14805
+ return PersistencePromise.resolve(result);
14806
+ }
14807
+ getOverlaysForCollectionGroup(transaction, collectionGroup, sinceBatchId, count) {
14808
+ let batchIdToOverlays = new SortedMap((key1, key2) => key1 - key2);
14809
+ const iter = this.overlays.getIterator();
14810
+ while (iter.hasNext()) {
14811
+ const entry = iter.getNext();
14812
+ const overlay = entry.value;
14813
+ const key = overlay.getKey();
14814
+ if (key.getCollectionGroup() !== collectionGroup) {
14815
+ continue;
14816
+ }
14817
+ if (overlay.largestBatchId > sinceBatchId) {
14818
+ let overlaysForBatchId = batchIdToOverlays.get(overlay.largestBatchId);
14819
+ if (overlaysForBatchId === null) {
14820
+ overlaysForBatchId = new Map();
14821
+ batchIdToOverlays = batchIdToOverlays.insert(overlay.largestBatchId, overlaysForBatchId);
14822
+ }
14823
+ overlaysForBatchId.set(overlay.getKey(), overlay);
14824
+ }
14825
+ }
14826
+ const result = new Map();
14827
+ const batchIter = batchIdToOverlays.getIterator();
14828
+ while (batchIter.hasNext()) {
14829
+ const entry = batchIter.getNext();
14830
+ const overlays = entry.value;
14831
+ overlays.forEach((overlay, key) => result.set(key, overlay));
14832
+ if (result.size >= count) {
14833
+ break;
14834
+ }
14835
+ }
14836
+ return PersistencePromise.resolve(result);
14837
+ }
14838
+ saveOverlay(transaction, largestBatchId, mutation) {
14839
+ if (mutation === null) {
14840
+ return;
14841
+ }
14842
+ // Remove the association of the overlay to its batch id.
14843
+ const existing = this.overlays.get(mutation.key);
14844
+ if (existing !== null) {
14845
+ this.overlayByBatchId.get(existing.largestBatchId).delete(mutation.key);
14846
+ }
14847
+ this.overlays = this.overlays.insert(mutation.key, new Overlay(largestBatchId, mutation));
14848
+ // Create the association of this overlay to the given largestBatchId.
14849
+ let batch = this.overlayByBatchId.get(largestBatchId);
14850
+ if (batch === undefined) {
14851
+ batch = new Set();
14852
+ this.overlayByBatchId.set(largestBatchId, batch);
14853
+ }
14854
+ batch.add(mutation.key);
14855
+ }
14856
+ }
14857
+
12517
14858
  /**
12518
14859
  * @license
12519
14860
  * Copyright 2017 Google LLC
@@ -12867,29 +15208,30 @@ class MemoryRemoteDocumentCacheImpl {
12867
15208
  * expected to just return 0 to avoid unnecessarily doing the work of
12868
15209
  * calculating the size.
12869
15210
  */
12870
- constructor(indexManager, sizer) {
12871
- this.indexManager = indexManager;
15211
+ constructor(sizer) {
12872
15212
  this.sizer = sizer;
12873
15213
  /** Underlying cache of documents and their read times. */
12874
15214
  this.docs = documentEntryMap();
12875
15215
  /** Size of all cached documents. */
12876
15216
  this.size = 0;
12877
15217
  }
15218
+ setIndexManager(indexManager) {
15219
+ this.indexManager = indexManager;
15220
+ }
12878
15221
  /**
12879
15222
  * Adds the supplied entry to the cache and updates the cache size as appropriate.
12880
15223
  *
12881
15224
  * All calls of `addEntry` are required to go through the RemoteDocumentChangeBuffer
12882
15225
  * returned by `newChangeBuffer()`.
12883
15226
  */
12884
- addEntry(transaction, doc, readTime) {
15227
+ addEntry(transaction, doc) {
12885
15228
  const key = doc.key;
12886
15229
  const entry = this.docs.get(key);
12887
15230
  const previousSize = entry ? entry.size : 0;
12888
15231
  const currentSize = this.sizer(doc);
12889
15232
  this.docs = this.docs.insert(key, {
12890
15233
  document: doc.mutableCopy(),
12891
- size: currentSize,
12892
- readTime
15234
+ size: currentSize
12893
15235
  });
12894
15236
  this.size += currentSize - previousSize;
12895
15237
  return this.indexManager.addToCollectionParentIndex(transaction, key.path.popLast());
@@ -12923,21 +15265,22 @@ class MemoryRemoteDocumentCacheImpl {
12923
15265
  });
12924
15266
  return PersistencePromise.resolve(results);
12925
15267
  }
12926
- getDocumentsMatchingQuery(transaction, query, sinceReadTime) {
15268
+ getAll(transaction, collectionPath, sinceReadTime) {
12927
15269
  let results = mutableDocumentMap();
12928
15270
  // Documents are ordered by key, so we can use a prefix scan to narrow down
12929
15271
  // the documents we need to match the query against.
12930
- const prefix = new DocumentKey(query.path.child(''));
15272
+ const prefix = new DocumentKey(collectionPath.child(''));
12931
15273
  const iterator = this.docs.getIteratorFrom(prefix);
12932
15274
  while (iterator.hasNext()) {
12933
- const { key, value: { document, readTime } } = iterator.getNext();
12934
- if (!query.path.isPrefixOf(key.path)) {
15275
+ const { key, value: { document } } = iterator.getNext();
15276
+ if (!collectionPath.isPrefixOf(key.path)) {
12935
15277
  break;
12936
15278
  }
12937
- if (readTime.compareTo(sinceReadTime) <= 0) {
15279
+ if (key.path.length > collectionPath.length + 1) {
15280
+ // Exclude entries from subcollections.
12938
15281
  continue;
12939
15282
  }
12940
- if (!queryMatches(query, document)) {
15283
+ if (document.readTime.compareTo(sinceReadTime) <= 0) {
12941
15284
  continue;
12942
15285
  }
12943
15286
  results = results.insert(document.key, document.mutableCopy());
@@ -12959,13 +15302,12 @@ class MemoryRemoteDocumentCacheImpl {
12959
15302
  /**
12960
15303
  * Creates a new memory-only RemoteDocumentCache.
12961
15304
  *
12962
- * @param indexManager - A class that manages collection group indices.
12963
15305
  * @param sizer - Used to assess the size of a document. For eager GC, this is
12964
15306
  * expected to just return 0 to avoid unnecessarily doing the work of
12965
15307
  * calculating the size.
12966
15308
  */
12967
- function newMemoryRemoteDocumentCache(indexManager, sizer) {
12968
- return new MemoryRemoteDocumentCacheImpl(indexManager, sizer);
15309
+ function newMemoryRemoteDocumentCache(sizer) {
15310
+ return new MemoryRemoteDocumentCacheImpl(sizer);
12969
15311
  }
12970
15312
  /**
12971
15313
  * Handles the details of adding and updating documents in the MemoryRemoteDocumentCache.
@@ -12978,8 +15320,8 @@ class MemoryRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
12978
15320
  applyChanges(transaction) {
12979
15321
  const promises = [];
12980
15322
  this.changes.forEach((key, doc) => {
12981
- if (doc.document.isValidDocument()) {
12982
- promises.push(this.documentCache.addEntry(transaction, doc.document, this.getReadTime(key)));
15323
+ if (doc.isValidDocument()) {
15324
+ promises.push(this.documentCache.addEntry(transaction, doc));
12983
15325
  }
12984
15326
  else {
12985
15327
  this.documentCache.removeEntry(key);
@@ -13159,6 +15501,7 @@ class MemoryPersistence {
13159
15501
  */
13160
15502
  constructor(referenceDelegateFactory, serializer) {
13161
15503
  this.mutationQueues = {};
15504
+ this.overlays = {};
13162
15505
  this.listenSequence = new ListenSequence(0);
13163
15506
  this._started = false;
13164
15507
  this._started = true;
@@ -13166,7 +15509,7 @@ class MemoryPersistence {
13166
15509
  this.targetCache = new MemoryTargetCache(this);
13167
15510
  const sizer = (doc) => this.referenceDelegate.documentSize(doc);
13168
15511
  this.indexManager = new MemoryIndexManager();
13169
- this.remoteDocumentCache = newMemoryRemoteDocumentCache(this.indexManager, sizer);
15512
+ this.remoteDocumentCache = newMemoryRemoteDocumentCache(sizer);
13170
15513
  this.serializer = new LocalSerializer(serializer);
13171
15514
  this.bundleCache = new MemoryBundleCache(this.serializer);
13172
15515
  }
@@ -13187,13 +15530,23 @@ class MemoryPersistence {
13187
15530
  setNetworkEnabled() {
13188
15531
  // No op.
13189
15532
  }
13190
- getIndexManager() {
15533
+ getIndexManager(user) {
15534
+ // We do not currently support indices for memory persistence, so we can
15535
+ // return the same shared instance of the memory index manager.
13191
15536
  return this.indexManager;
13192
15537
  }
13193
- getMutationQueue(user) {
15538
+ getDocumentOverlayCache(user) {
15539
+ let overlay = this.overlays[user.toKey()];
15540
+ if (!overlay) {
15541
+ overlay = new MemoryDocumentOverlayCache();
15542
+ this.overlays[user.toKey()] = overlay;
15543
+ }
15544
+ return overlay;
15545
+ }
15546
+ getMutationQueue(user, indexManager) {
13194
15547
  let queue = this.mutationQueues[user.toKey()];
13195
15548
  if (!queue) {
13196
- queue = new MemoryMutationQueue(this.indexManager, this.referenceDelegate);
15549
+ queue = new MemoryMutationQueue(indexManager, this.referenceDelegate);
13197
15550
  this.mutationQueues[user.toKey()] = queue;
13198
15551
  }
13199
15552
  return queue;
@@ -13292,7 +15645,7 @@ class MemoryEagerDelegate {
13292
15645
  const key = DocumentKey.fromPath(path);
13293
15646
  return this.isReferenced(txn, key).next(isReferenced => {
13294
15647
  if (!isReferenced) {
13295
- changeBuffer.removeEntry(key);
15648
+ changeBuffer.removeEntry(key, SnapshotVersion.min());
13296
15649
  }
13297
15650
  });
13298
15651
  }).next(() => {
@@ -14338,7 +16691,7 @@ const LOG_TAG$9 = 'Connection';
14338
16691
  const X_GOOG_API_CLIENT_VALUE = `gl-node/${process.versions.node} fire/${SDK_VERSION} grpc/${grpcVersion}`;
14339
16692
  function createMetadata(databasePath, authToken, appCheckToken, appId) {
14340
16693
  hardAssert(authToken === null || authToken.type === 'OAuth');
14341
- const metadata = new grpcJs.Metadata();
16694
+ const metadata = new grpc__namespace.Metadata();
14342
16695
  if (authToken) {
14343
16696
  authToken.headers.forEach((value, key) => metadata.set(key, value));
14344
16697
  }
@@ -14369,10 +16722,10 @@ class GrpcConnection {
14369
16722
  ensureActiveStub() {
14370
16723
  if (!this.cachedStub) {
14371
16724
  logDebug(LOG_TAG$9, 'Creating Firestore stub.');
14372
- const credentials$1 = this.databaseInfo.ssl
14373
- ? grpcJs.credentials.createSsl()
14374
- : grpcJs.credentials.createInsecure();
14375
- this.cachedStub = new this.firestore.Firestore(this.databaseInfo.host, credentials$1);
16725
+ const credentials = this.databaseInfo.ssl
16726
+ ? grpc__namespace.credentials.createSsl()
16727
+ : grpc__namespace.credentials.createInsecure();
16728
+ this.cachedStub = new this.firestore.Firestore(this.databaseInfo.host, credentials);
14376
16729
  }
14377
16730
  return this.cachedStub;
14378
16731
  }
@@ -17072,6 +19425,40 @@ const nested = {
17072
19425
  id: 2
17073
19426
  }
17074
19427
  }
19428
+ },
19429
+ methodSignature: {
19430
+ rule: "repeated",
19431
+ type: "string",
19432
+ id: 1051,
19433
+ extend: "google.protobuf.MethodOptions"
19434
+ },
19435
+ defaultHost: {
19436
+ type: "string",
19437
+ id: 1049,
19438
+ extend: "google.protobuf.ServiceOptions"
19439
+ },
19440
+ oauthScopes: {
19441
+ type: "string",
19442
+ id: 1050,
19443
+ extend: "google.protobuf.ServiceOptions"
19444
+ },
19445
+ fieldBehavior: {
19446
+ rule: "repeated",
19447
+ type: "google.api.FieldBehavior",
19448
+ id: 1052,
19449
+ extend: "google.protobuf.FieldOptions"
19450
+ },
19451
+ FieldBehavior: {
19452
+ values: {
19453
+ FIELD_BEHAVIOR_UNSPECIFIED: 0,
19454
+ OPTIONAL: 1,
19455
+ REQUIRED: 2,
19456
+ OUTPUT_ONLY: 3,
19457
+ INPUT_ONLY: 4,
19458
+ IMMUTABLE: 5,
19459
+ UNORDERED_LIST: 6,
19460
+ NON_EMPTY_DEFAULT: 7
19461
+ }
17075
19462
  }
17076
19463
  }
17077
19464
  },
@@ -17170,8 +19557,8 @@ const protoLoaderOptions = {
17170
19557
  * @returns The GrpcObject representing our protos.
17171
19558
  */
17172
19559
  function loadProtos() {
17173
- const packageDefinition = protoLoader.fromJSON(protos$1, protoLoaderOptions);
17174
- return grpcJs.loadPackageDefinition(packageDefinition);
19560
+ const packageDefinition = protoLoader__namespace.fromJSON(protos$1, protoLoaderOptions);
19561
+ return grpc__namespace.loadPackageDefinition(packageDefinition);
17175
19562
  }
17176
19563
 
17177
19564
  /**
@@ -20122,12 +22509,12 @@ async function syncEngineListen(syncEngine, query) {
20122
22509
  }
20123
22510
  else {
20124
22511
  const targetData = await localStoreAllocateTarget(syncEngineImpl.localStore, queryToTarget(query));
20125
- const status = syncEngineImpl.sharedClientState.addLocalQueryTarget(targetData.targetId);
20126
- targetId = targetData.targetId;
20127
- viewSnapshot = await initializeViewAndComputeSnapshot(syncEngineImpl, query, targetId, status === 'current');
20128
22512
  if (syncEngineImpl.isPrimaryClient) {
20129
22513
  remoteStoreListen(syncEngineImpl.remoteStore, targetData);
20130
22514
  }
22515
+ const status = syncEngineImpl.sharedClientState.addLocalQueryTarget(targetData.targetId);
22516
+ targetId = targetData.targetId;
22517
+ viewSnapshot = await initializeViewAndComputeSnapshot(syncEngineImpl, query, targetId, status === 'current');
20131
22518
  }
20132
22519
  return viewSnapshot;
20133
22520
  }
@@ -20315,6 +22702,9 @@ async function syncEngineRejectListen(syncEngine, targetId, err) {
20315
22702
  // store to purge a document. However, it would be tricky to keep all of
20316
22703
  // the local store's invariants with another method.
20317
22704
  let documentUpdates = new SortedMap(DocumentKey.comparator);
22705
+ // TODO(b/217189216): This limbo document should ideally have a read time,
22706
+ // so that it is picked up by any read-time based scans. The backend,
22707
+ // however, does not send a read time for target removals.
20318
22708
  documentUpdates = documentUpdates.insert(limboKey, MutableDocument.newNoDocument(limboKey, SnapshotVersion.min()));
20319
22709
  const resolvedLimboDocuments = documentKeySet().add(limboKey);
20320
22710
  const event = new RemoteEvent(SnapshotVersion.min(),
@@ -22259,72 +24649,6 @@ function createBundleReader(data, serializer) {
22259
24649
  return newBundleReader(toByteStreamReader(content), serializer);
22260
24650
  }
22261
24651
 
22262
- /**
22263
- * @license
22264
- * Copyright 2017 Google LLC
22265
- *
22266
- * Licensed under the Apache License, Version 2.0 (the "License");
22267
- * you may not use this file except in compliance with the License.
22268
- * You may obtain a copy of the License at
22269
- *
22270
- * http://www.apache.org/licenses/LICENSE-2.0
22271
- *
22272
- * Unless required by applicable law or agreed to in writing, software
22273
- * distributed under the License is distributed on an "AS IS" BASIS,
22274
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
22275
- * See the License for the specific language governing permissions and
22276
- * limitations under the License.
22277
- */
22278
- class DatabaseInfo {
22279
- /**
22280
- * Constructs a DatabaseInfo using the provided host, databaseId and
22281
- * persistenceKey.
22282
- *
22283
- * @param databaseId - The database to use.
22284
- * @param appId - The Firebase App Id.
22285
- * @param persistenceKey - A unique identifier for this Firestore's local
22286
- * storage (used in conjunction with the databaseId).
22287
- * @param host - The Firestore backend host to connect to.
22288
- * @param ssl - Whether to use SSL when connecting.
22289
- * @param forceLongPolling - Whether to use the forceLongPolling option
22290
- * when using WebChannel as the network transport.
22291
- * @param autoDetectLongPolling - Whether to use the detectBufferingProxy
22292
- * option when using WebChannel as the network transport.
22293
- * @param useFetchStreams Whether to use the Fetch API instead of
22294
- * XMLHTTPRequest
22295
- */
22296
- constructor(databaseId, appId, persistenceKey, host, ssl, forceLongPolling, autoDetectLongPolling, useFetchStreams) {
22297
- this.databaseId = databaseId;
22298
- this.appId = appId;
22299
- this.persistenceKey = persistenceKey;
22300
- this.host = host;
22301
- this.ssl = ssl;
22302
- this.forceLongPolling = forceLongPolling;
22303
- this.autoDetectLongPolling = autoDetectLongPolling;
22304
- this.useFetchStreams = useFetchStreams;
22305
- }
22306
- }
22307
- /** The default database name for a project. */
22308
- const DEFAULT_DATABASE_NAME = '(default)';
22309
- /**
22310
- * Represents the database ID a Firestore client is associated with.
22311
- * @internal
22312
- */
22313
- class DatabaseId {
22314
- constructor(projectId, database) {
22315
- this.projectId = projectId;
22316
- this.database = database ? database : DEFAULT_DATABASE_NAME;
22317
- }
22318
- get isDefaultDatabase() {
22319
- return this.database === DEFAULT_DATABASE_NAME;
22320
- }
22321
- isEqual(other) {
22322
- return (other instanceof DatabaseId &&
22323
- other.projectId === this.projectId &&
22324
- other.database === this.database);
22325
- }
22326
- }
22327
-
22328
24652
  /**
22329
24653
  * @license
22330
24654
  * Copyright 2020 Google LLC
@@ -24698,8 +27022,7 @@ class DocumentSnapshot extends DocumentSnapshot$1 {
24698
27022
  this.metadata = metadata;
24699
27023
  }
24700
27024
  /**
24701
- * Property of the `DocumentSnapshot` that signals whether or not the data
24702
- * exists. True if the document exists.
27025
+ * Returns whether or not the data exists. True if the document exists.
24703
27026
  */
24704
27027
  exists() {
24705
27028
  return super.exists();
@@ -25073,51 +27396,53 @@ function limitToLast(limit) {
25073
27396
  return new QueryLimitConstraint('limitToLast', limit, "L" /* Last */);
25074
27397
  }
25075
27398
  class QueryStartAtConstraint extends QueryConstraint {
25076
- constructor(type, _docOrFields, _before) {
27399
+ constructor(type, _docOrFields, _inclusive) {
25077
27400
  super();
25078
27401
  this.type = type;
25079
27402
  this._docOrFields = _docOrFields;
25080
- this._before = _before;
27403
+ this._inclusive = _inclusive;
25081
27404
  }
25082
27405
  _apply(query) {
25083
- const bound = newQueryBoundFromDocOrFields(query, this.type, this._docOrFields, this._before);
27406
+ const bound = newQueryBoundFromDocOrFields(query, this.type, this._docOrFields, this._inclusive);
25084
27407
  return new Query(query.firestore, query.converter, queryWithStartAt(query._query, bound));
25085
27408
  }
25086
27409
  }
25087
27410
  function startAt(...docOrFields) {
25088
- return new QueryStartAtConstraint('startAt', docOrFields, /*before=*/ true);
27411
+ return new QueryStartAtConstraint('startAt', docOrFields,
27412
+ /*inclusive=*/ true);
25089
27413
  }
25090
27414
  function startAfter(...docOrFields) {
25091
27415
  return new QueryStartAtConstraint('startAfter', docOrFields,
25092
- /*before=*/ false);
27416
+ /*inclusive=*/ false);
25093
27417
  }
25094
27418
  class QueryEndAtConstraint extends QueryConstraint {
25095
- constructor(type, _docOrFields, _before) {
27419
+ constructor(type, _docOrFields, _inclusive) {
25096
27420
  super();
25097
27421
  this.type = type;
25098
27422
  this._docOrFields = _docOrFields;
25099
- this._before = _before;
27423
+ this._inclusive = _inclusive;
25100
27424
  }
25101
27425
  _apply(query) {
25102
- const bound = newQueryBoundFromDocOrFields(query, this.type, this._docOrFields, this._before);
27426
+ const bound = newQueryBoundFromDocOrFields(query, this.type, this._docOrFields, this._inclusive);
25103
27427
  return new Query(query.firestore, query.converter, queryWithEndAt(query._query, bound));
25104
27428
  }
25105
27429
  }
25106
27430
  function endBefore(...docOrFields) {
25107
- return new QueryEndAtConstraint('endBefore', docOrFields, /*before=*/ true);
27431
+ return new QueryEndAtConstraint('endBefore', docOrFields,
27432
+ /*inclusive=*/ false);
25108
27433
  }
25109
27434
  function endAt(...docOrFields) {
25110
- return new QueryEndAtConstraint('endAt', docOrFields, /*before=*/ false);
27435
+ return new QueryEndAtConstraint('endAt', docOrFields, /*inclusive=*/ true);
25111
27436
  }
25112
27437
  /** Helper function to create a bound from a document or fields */
25113
- function newQueryBoundFromDocOrFields(query, methodName, docOrFields, before) {
27438
+ function newQueryBoundFromDocOrFields(query, methodName, docOrFields, inclusive) {
25114
27439
  docOrFields[0] = util$1.getModularInstance(docOrFields[0]);
25115
27440
  if (docOrFields[0] instanceof DocumentSnapshot$1) {
25116
- return newQueryBoundFromDocument(query._query, query.firestore._databaseId, methodName, docOrFields[0]._document, before);
27441
+ return newQueryBoundFromDocument(query._query, query.firestore._databaseId, methodName, docOrFields[0]._document, inclusive);
25117
27442
  }
25118
27443
  else {
25119
27444
  const reader = newUserDataReader(query.firestore);
25120
- return newQueryBoundFromFields(query._query, query.firestore._databaseId, reader, methodName, docOrFields, before);
27445
+ return newQueryBoundFromFields(query._query, query.firestore._databaseId, reader, methodName, docOrFields, inclusive);
25121
27446
  }
25122
27447
  }
25123
27448
  function newQueryFilter(query, methodName, dataReader, databaseId, fieldPath, op, value) {
@@ -25175,7 +27500,7 @@ function newQueryOrderBy(query, fieldPath, direction) {
25175
27500
  * of the query or if any of the fields in the order by are an uncommitted
25176
27501
  * server timestamp.
25177
27502
  */
25178
- function newQueryBoundFromDocument(query, databaseId, methodName, doc, before) {
27503
+ function newQueryBoundFromDocument(query, databaseId, methodName, doc, inclusive) {
25179
27504
  if (!doc) {
25180
27505
  throw new FirestoreError(Code.NOT_FOUND, `Can't use a DocumentSnapshot that doesn't exist for ` +
25181
27506
  `${methodName}().`);
@@ -25212,12 +27537,12 @@ function newQueryBoundFromDocument(query, databaseId, methodName, doc, before) {
25212
27537
  }
25213
27538
  }
25214
27539
  }
25215
- return new Bound(components, before);
27540
+ return new Bound(components, inclusive);
25216
27541
  }
25217
27542
  /**
25218
27543
  * Converts a list of field values to a `Bound` for the given query.
25219
27544
  */
25220
- function newQueryBoundFromFields(query, databaseId, dataReader, methodName, values, before) {
27545
+ function newQueryBoundFromFields(query, databaseId, dataReader, methodName, values, inclusive) {
25221
27546
  // Use explicit order by's because it has to match the query the user made
25222
27547
  const orderBy = query.explicitOrderBy;
25223
27548
  if (values.length > orderBy.length) {
@@ -25254,7 +27579,7 @@ function newQueryBoundFromFields(query, databaseId, dataReader, methodName, valu
25254
27579
  components.push(wrapped);
25255
27580
  }
25256
27581
  }
25257
- return new Bound(components, before);
27582
+ return new Bound(components, inclusive);
25258
27583
  }
25259
27584
  /**
25260
27585
  * Parses the given `documentIdValue` into a `ReferenceValue`, throwing
@@ -26238,17 +28563,6 @@ function writeBatch(firestore) {
26238
28563
  ensureFirestoreConfigured(firestore);
26239
28564
  return new WriteBatch(firestore, mutations => executeWrite(firestore, mutations));
26240
28565
  }
26241
- /** An index component consisting of field path and index type. */
26242
- class Segment {
26243
- constructor(
26244
- /** The field path of the component. */
26245
- fieldPath,
26246
- /** The fields sorting order. */
26247
- kind) {
26248
- this.fieldPath = fieldPath;
26249
- this.kind = kind;
26250
- }
26251
- }
26252
28566
 
26253
28567
  /**
26254
28568
  * @license
@@ -26272,27 +28586,29 @@ function setIndexConfiguration(firestore, jsonOrConfiguration) {
26272
28586
  const indexConfiguration = typeof jsonOrConfiguration === 'string'
26273
28587
  ? tryParseJson(jsonOrConfiguration)
26274
28588
  : jsonOrConfiguration;
28589
+ const parsedIndexes = [];
26275
28590
  // PORTING NOTE: We don't return an error if the user has not enabled
26276
28591
  // persistence since `enableIndexeddbPersistence()` can fail on the Web.
26277
28592
  if (Array.isArray(indexConfiguration.indexes)) {
26278
28593
  for (const index of indexConfiguration.indexes) {
26279
- tryGetString(index, 'collectionGroup');
28594
+ const collectionGroup = tryGetString(index, 'collectionGroup');
26280
28595
  const segments = [];
26281
28596
  if (Array.isArray(index.fields)) {
26282
28597
  for (const field of index.fields) {
26283
28598
  const fieldPathString = tryGetString(field, 'fieldPath');
26284
28599
  const fieldPath = fieldPathFromDotSeparatedString('setIndexConfiguration', fieldPathString);
26285
28600
  if (field.arrayConfig === 'CONTAINS') {
26286
- segments.push(new Segment(fieldPath, 2 /* CONTAINS */));
28601
+ segments.push(new IndexSegment(fieldPath, 2 /* CONTAINS */));
26287
28602
  }
26288
28603
  else if (field.order === 'ASCENDING') {
26289
- segments.push(new Segment(fieldPath, 0 /* ASCENDING */));
28604
+ segments.push(new IndexSegment(fieldPath, 0 /* ASCENDING */));
26290
28605
  }
26291
28606
  else if (field.order === 'DESCENDING') {
26292
- segments.push(new Segment(fieldPath, 1 /* DESCENDING */));
28607
+ segments.push(new IndexSegment(fieldPath, 1 /* DESCENDING */));
26293
28608
  }
26294
28609
  }
26295
28610
  }
28611
+ parsedIndexes.push(new FieldIndex(FieldIndex.UNKNOWN_ID, collectionGroup, segments, IndexState.empty()));
26296
28612
  }
26297
28613
  }
26298
28614
  // TODO(indexing): Configure indexes