@firebase/firestore 3.4.5 → 3.4.6-canary.3198d58dc

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 (167) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/firestore/src/core/bundle.d.ts +2 -2
  3. package/dist/firestore/src/core/bundle_impl.d.ts +2 -0
  4. package/dist/firestore/src/core/query.d.ts +7 -0
  5. package/dist/firestore/src/core/snapshot_version.d.ts +1 -0
  6. package/dist/firestore/src/core/sync_engine_impl.d.ts +1 -1
  7. package/dist/firestore/src/index/index_entry.d.ts +6 -0
  8. package/dist/firestore/src/local/document_overlay_cache.d.ts +4 -5
  9. package/dist/firestore/src/local/index_manager.d.ts +2 -2
  10. package/dist/firestore/src/local/indexeddb_document_overlay_cache.d.ts +4 -5
  11. package/dist/firestore/src/local/indexeddb_index_manager.d.ts +48 -1
  12. package/dist/firestore/src/local/indexeddb_mutation_batch_impl.d.ts +2 -1
  13. package/dist/firestore/src/local/indexeddb_remote_document_cache.d.ts +0 -17
  14. package/dist/firestore/src/local/indexeddb_schema.d.ts +47 -563
  15. package/dist/firestore/src/local/indexeddb_schema_converter.d.ts +1 -0
  16. package/dist/firestore/src/local/indexeddb_schema_legacy.d.ts +29 -0
  17. package/dist/firestore/src/local/indexeddb_sentinels.d.ts +243 -0
  18. package/dist/firestore/src/local/indexeddb_target_cache.d.ts +2 -1
  19. package/dist/firestore/src/local/local_documents_view.d.ts +3 -4
  20. package/dist/firestore/src/local/local_serializer.d.ts +3 -1
  21. package/dist/firestore/src/local/local_store_impl.d.ts +1 -7
  22. package/dist/firestore/src/local/memory_document_overlay_cache.d.ts +4 -5
  23. package/dist/firestore/src/local/memory_index_manager.d.ts +1 -1
  24. package/dist/firestore/src/local/remote_document_cache.d.ts +13 -4
  25. package/dist/firestore/src/local/shared_client_state.d.ts +6 -3
  26. package/dist/firestore/src/local/shared_client_state_schema.d.ts +1 -1
  27. package/dist/firestore/src/local/shared_client_state_syncer.d.ts +1 -1
  28. package/dist/firestore/src/local/simple_db.d.ts +11 -0
  29. package/dist/firestore/src/model/collections.d.ts +7 -0
  30. package/dist/firestore/src/model/field_index.d.ts +10 -1
  31. package/dist/firestore/src/model/type_order.d.ts +2 -1
  32. package/dist/firestore/src/platform/node/base64.d.ts +1 -0
  33. package/dist/firestore/src/util/obj_map.d.ts +3 -0
  34. package/dist/firestore/test/unit/local/test_document_overlay_cache.d.ts +4 -4
  35. package/dist/firestore/test/unit/local/test_index_manager.d.ts +1 -1
  36. package/dist/firestore/test/unit/local/test_remote_document_cache.d.ts +3 -1
  37. package/dist/index.esm2017.js +4533 -4502
  38. package/dist/index.esm2017.js.map +1 -1
  39. package/dist/index.esm5.js +4455 -4308
  40. package/dist/index.esm5.js.map +1 -1
  41. package/dist/index.node.cjs.js +1606 -1180
  42. package/dist/index.node.cjs.js.map +1 -1
  43. package/dist/index.node.mjs +1606 -1180
  44. package/dist/index.node.mjs.map +1 -1
  45. package/dist/index.rn.js +4425 -4394
  46. package/dist/index.rn.js.map +1 -1
  47. package/dist/internal.d.ts +31 -10
  48. package/dist/lite/firestore/src/core/bundle.d.ts +2 -2
  49. package/dist/lite/firestore/src/core/bundle_impl.d.ts +2 -0
  50. package/dist/lite/firestore/src/core/query.d.ts +7 -0
  51. package/dist/lite/firestore/src/core/snapshot_version.d.ts +1 -0
  52. package/dist/lite/firestore/src/core/sync_engine_impl.d.ts +1 -1
  53. package/dist/lite/firestore/src/index/index_entry.d.ts +6 -0
  54. package/dist/lite/firestore/src/local/document_overlay_cache.d.ts +4 -5
  55. package/dist/lite/firestore/src/local/index_manager.d.ts +2 -2
  56. package/dist/lite/firestore/src/local/indexeddb_document_overlay_cache.d.ts +4 -5
  57. package/dist/lite/firestore/src/local/indexeddb_index_manager.d.ts +48 -1
  58. package/dist/lite/firestore/src/local/indexeddb_mutation_batch_impl.d.ts +2 -1
  59. package/dist/lite/firestore/src/local/indexeddb_remote_document_cache.d.ts +0 -17
  60. package/dist/lite/firestore/src/local/indexeddb_schema.d.ts +47 -563
  61. package/dist/lite/firestore/src/local/indexeddb_schema_converter.d.ts +1 -0
  62. package/dist/lite/firestore/src/local/indexeddb_schema_legacy.d.ts +29 -0
  63. package/dist/lite/firestore/src/local/indexeddb_sentinels.d.ts +243 -0
  64. package/dist/lite/firestore/src/local/indexeddb_target_cache.d.ts +2 -1
  65. package/dist/lite/firestore/src/local/local_documents_view.d.ts +3 -4
  66. package/dist/lite/firestore/src/local/local_serializer.d.ts +3 -1
  67. package/dist/lite/firestore/src/local/local_store_impl.d.ts +1 -7
  68. package/dist/lite/firestore/src/local/memory_document_overlay_cache.d.ts +4 -5
  69. package/dist/lite/firestore/src/local/memory_index_manager.d.ts +1 -1
  70. package/dist/lite/firestore/src/local/remote_document_cache.d.ts +13 -4
  71. package/dist/lite/firestore/src/local/shared_client_state.d.ts +6 -3
  72. package/dist/lite/firestore/src/local/shared_client_state_schema.d.ts +1 -1
  73. package/dist/lite/firestore/src/local/shared_client_state_syncer.d.ts +1 -1
  74. package/dist/lite/firestore/src/local/simple_db.d.ts +11 -0
  75. package/dist/lite/firestore/src/model/collections.d.ts +7 -0
  76. package/dist/lite/firestore/src/model/field_index.d.ts +10 -1
  77. package/dist/lite/firestore/src/model/type_order.d.ts +2 -1
  78. package/dist/lite/firestore/src/platform/node/base64.d.ts +1 -0
  79. package/dist/lite/firestore/src/util/obj_map.d.ts +3 -0
  80. package/dist/lite/firestore/test/unit/local/test_document_overlay_cache.d.ts +4 -4
  81. package/dist/lite/firestore/test/unit/local/test_index_manager.d.ts +1 -1
  82. package/dist/lite/firestore/test/unit/local/test_remote_document_cache.d.ts +3 -1
  83. package/dist/lite/index.browser.esm2017.js +37 -24
  84. package/dist/lite/index.browser.esm2017.js.map +1 -1
  85. package/dist/lite/index.browser.esm5.js +104 -113
  86. package/dist/lite/index.browser.esm5.js.map +1 -1
  87. package/dist/lite/index.node.cjs.js +24 -9
  88. package/dist/lite/index.node.cjs.js.map +1 -1
  89. package/dist/lite/index.node.mjs +24 -9
  90. package/dist/lite/index.node.mjs.map +1 -1
  91. package/dist/lite/index.rn.esm2017.js +37 -24
  92. package/dist/lite/index.rn.esm2017.js.map +1 -1
  93. package/dist/lite/internal.d.ts +1 -0
  94. package/dist/lite/packages/firestore/src/core/bundle.d.ts +2 -2
  95. package/dist/lite/packages/firestore/src/core/bundle_impl.d.ts +2 -0
  96. package/dist/lite/packages/firestore/src/core/query.d.ts +7 -0
  97. package/dist/lite/packages/firestore/src/core/snapshot_version.d.ts +1 -0
  98. package/dist/lite/packages/firestore/src/core/sync_engine_impl.d.ts +1 -1
  99. package/dist/lite/packages/firestore/src/index/index_entry.d.ts +6 -0
  100. package/dist/lite/packages/firestore/src/local/document_overlay_cache.d.ts +4 -5
  101. package/dist/lite/packages/firestore/src/local/index_manager.d.ts +2 -2
  102. package/dist/lite/packages/firestore/src/local/indexeddb_document_overlay_cache.d.ts +4 -5
  103. package/dist/lite/packages/firestore/src/local/indexeddb_index_manager.d.ts +48 -1
  104. package/dist/lite/packages/firestore/src/local/indexeddb_mutation_batch_impl.d.ts +2 -1
  105. package/dist/lite/packages/firestore/src/local/indexeddb_remote_document_cache.d.ts +0 -17
  106. package/dist/lite/packages/firestore/src/local/indexeddb_schema.d.ts +47 -563
  107. package/dist/lite/packages/firestore/src/local/indexeddb_schema_converter.d.ts +1 -0
  108. package/dist/lite/packages/firestore/src/local/indexeddb_schema_legacy.d.ts +29 -0
  109. package/dist/lite/packages/firestore/src/local/indexeddb_sentinels.d.ts +243 -0
  110. package/dist/lite/packages/firestore/src/local/indexeddb_target_cache.d.ts +2 -1
  111. package/dist/lite/packages/firestore/src/local/local_documents_view.d.ts +3 -4
  112. package/dist/lite/packages/firestore/src/local/local_serializer.d.ts +3 -1
  113. package/dist/lite/packages/firestore/src/local/local_store_impl.d.ts +1 -7
  114. package/dist/lite/packages/firestore/src/local/memory_document_overlay_cache.d.ts +4 -5
  115. package/dist/lite/packages/firestore/src/local/memory_index_manager.d.ts +1 -1
  116. package/dist/lite/packages/firestore/src/local/remote_document_cache.d.ts +13 -4
  117. package/dist/lite/packages/firestore/src/local/shared_client_state.d.ts +6 -3
  118. package/dist/lite/packages/firestore/src/local/shared_client_state_schema.d.ts +1 -1
  119. package/dist/lite/packages/firestore/src/local/shared_client_state_syncer.d.ts +1 -1
  120. package/dist/lite/packages/firestore/src/local/simple_db.d.ts +11 -0
  121. package/dist/lite/packages/firestore/src/model/collections.d.ts +7 -0
  122. package/dist/lite/packages/firestore/src/model/field_index.d.ts +10 -1
  123. package/dist/lite/packages/firestore/src/model/type_order.d.ts +2 -1
  124. package/dist/lite/packages/firestore/src/platform/node/base64.d.ts +1 -0
  125. package/dist/lite/packages/firestore/src/util/obj_map.d.ts +3 -0
  126. package/dist/lite/packages/firestore/test/unit/local/test_document_overlay_cache.d.ts +4 -4
  127. package/dist/lite/packages/firestore/test/unit/local/test_index_manager.d.ts +1 -1
  128. package/dist/lite/packages/firestore/test/unit/local/test_remote_document_cache.d.ts +3 -1
  129. package/dist/lite/private.d.ts +1 -0
  130. package/dist/packages/firestore/dist/index.esm2017.d.ts +197 -197
  131. package/dist/packages/firestore/src/core/bundle.d.ts +2 -2
  132. package/dist/packages/firestore/src/core/bundle_impl.d.ts +2 -0
  133. package/dist/packages/firestore/src/core/query.d.ts +7 -0
  134. package/dist/packages/firestore/src/core/snapshot_version.d.ts +1 -0
  135. package/dist/packages/firestore/src/core/sync_engine_impl.d.ts +1 -1
  136. package/dist/packages/firestore/src/index/index_entry.d.ts +6 -0
  137. package/dist/packages/firestore/src/local/document_overlay_cache.d.ts +4 -5
  138. package/dist/packages/firestore/src/local/index_manager.d.ts +2 -2
  139. package/dist/packages/firestore/src/local/indexeddb_document_overlay_cache.d.ts +4 -5
  140. package/dist/packages/firestore/src/local/indexeddb_index_manager.d.ts +48 -1
  141. package/dist/packages/firestore/src/local/indexeddb_mutation_batch_impl.d.ts +2 -1
  142. package/dist/packages/firestore/src/local/indexeddb_remote_document_cache.d.ts +0 -17
  143. package/dist/packages/firestore/src/local/indexeddb_schema.d.ts +47 -563
  144. package/dist/packages/firestore/src/local/indexeddb_schema_converter.d.ts +1 -0
  145. package/dist/packages/firestore/src/local/indexeddb_schema_legacy.d.ts +29 -0
  146. package/dist/packages/firestore/src/local/indexeddb_sentinels.d.ts +243 -0
  147. package/dist/packages/firestore/src/local/indexeddb_target_cache.d.ts +2 -1
  148. package/dist/packages/firestore/src/local/local_documents_view.d.ts +3 -4
  149. package/dist/packages/firestore/src/local/local_serializer.d.ts +3 -1
  150. package/dist/packages/firestore/src/local/local_store_impl.d.ts +1 -7
  151. package/dist/packages/firestore/src/local/memory_document_overlay_cache.d.ts +4 -5
  152. package/dist/packages/firestore/src/local/memory_index_manager.d.ts +1 -1
  153. package/dist/packages/firestore/src/local/remote_document_cache.d.ts +13 -4
  154. package/dist/packages/firestore/src/local/shared_client_state.d.ts +6 -3
  155. package/dist/packages/firestore/src/local/shared_client_state_schema.d.ts +1 -1
  156. package/dist/packages/firestore/src/local/shared_client_state_syncer.d.ts +1 -1
  157. package/dist/packages/firestore/src/local/simple_db.d.ts +11 -0
  158. package/dist/packages/firestore/src/model/collections.d.ts +7 -0
  159. package/dist/packages/firestore/src/model/field_index.d.ts +10 -1
  160. package/dist/packages/firestore/src/model/type_order.d.ts +2 -1
  161. package/dist/packages/firestore/src/platform/node/base64.d.ts +1 -0
  162. package/dist/packages/firestore/src/util/obj_map.d.ts +3 -0
  163. package/dist/packages/firestore/test/unit/local/test_document_overlay_cache.d.ts +4 -4
  164. package/dist/packages/firestore/test/unit/local/test_index_manager.d.ts +1 -1
  165. package/dist/packages/firestore/test/unit/local/test_remote_document_cache.d.ts +3 -1
  166. package/dist/private.d.ts +31 -10
  167. package/package.json +9 -9
@@ -8,7 +8,7 @@ import * as grpc from '@grpc/grpc-js';
8
8
  import * as protoLoader from '@grpc/proto-loader';
9
9
 
10
10
  const name = "@firebase/firestore";
11
- const version$1 = "3.4.5";
11
+ const version$1 = "3.4.6-canary.3198d58dc";
12
12
 
13
13
  /**
14
14
  * @license
@@ -61,7 +61,7 @@ User.GOOGLE_CREDENTIALS = new User('google-credentials-uid');
61
61
  User.FIRST_PARTY = new User('first-party-uid');
62
62
  User.MOCK_USER = new User('mock-user');
63
63
 
64
- const version = "9.6.7";
64
+ const version = "9.6.9-canary.3198d58dc";
65
65
 
66
66
  /**
67
67
  * @license
@@ -1199,55 +1199,41 @@ function decodeResourcePath(path) {
1199
1199
  }
1200
1200
  return new ResourcePath(segments);
1201
1201
  }
1202
+
1202
1203
  /**
1203
- * Schema Version for the Web client:
1204
- * 1. Initial version including Mutation Queue, Query Cache, and Remote
1205
- * Document Cache
1206
- * 2. Used to ensure a targetGlobal object exists and add targetCount to it. No
1207
- * longer required because migration 3 unconditionally clears it.
1208
- * 3. Dropped and re-created Query Cache to deal with cache corruption related
1209
- * to limbo resolution. Addresses
1210
- * https://github.com/firebase/firebase-ios-sdk/issues/1548
1211
- * 4. Multi-Tab Support.
1212
- * 5. Removal of held write acks.
1213
- * 6. Create document global for tracking document cache size.
1214
- * 7. Ensure every cached document has a sentinel row with a sequence number.
1215
- * 8. Add collection-parent index for Collection Group queries.
1216
- * 9. Change RemoteDocumentChanges store to be keyed by readTime rather than
1217
- * an auto-incrementing ID. This is required for Index-Free queries.
1218
- * 10. Rewrite the canonical IDs to the explicit Protobuf-based format.
1219
- * 11. Add bundles and named_queries for bundle support.
1220
- * 12. Add document overlays.
1221
- * 13. Add indexing support.
1222
- */
1223
- const SCHEMA_VERSION = 12;
1224
- /**
1225
- * Wrapper class to store timestamps (seconds and nanos) in IndexedDb objects.
1204
+ * @license
1205
+ * Copyright 2022 Google LLC
1206
+ *
1207
+ * Licensed under the Apache License, Version 2.0 (the "License");
1208
+ * you may not use this file except in compliance with the License.
1209
+ * You may obtain a copy of the License at
1210
+ *
1211
+ * http://www.apache.org/licenses/LICENSE-2.0
1212
+ *
1213
+ * Unless required by applicable law or agreed to in writing, software
1214
+ * distributed under the License is distributed on an "AS IS" BASIS,
1215
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1216
+ * See the License for the specific language governing permissions and
1217
+ * limitations under the License.
1226
1218
  */
1227
- class DbTimestamp {
1228
- constructor(seconds, nanoseconds) {
1229
- this.seconds = seconds;
1230
- this.nanoseconds = nanoseconds;
1231
- }
1232
- }
1219
+ const DbRemoteDocumentStore$1 = 'remoteDocuments';
1220
+
1233
1221
  /**
1234
- * A singleton object to be stored in the 'owner' store in IndexedDb.
1222
+ * @license
1223
+ * Copyright 2022 Google LLC
1235
1224
  *
1236
- * A given database can have a single primary tab assigned at a given time. That
1237
- * tab must validate that it is still holding the primary lease before every
1238
- * operation that requires locked access. The primary tab should regularly
1239
- * write an updated timestamp to this lease to prevent other tabs from
1240
- * "stealing" the primary lease
1225
+ * Licensed under the Apache License, Version 2.0 (the "License");
1226
+ * you may not use this file except in compliance with the License.
1227
+ * You may obtain a copy of the License at
1228
+ *
1229
+ * http://www.apache.org/licenses/LICENSE-2.0
1230
+ *
1231
+ * Unless required by applicable law or agreed to in writing, software
1232
+ * distributed under the License is distributed on an "AS IS" BASIS,
1233
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1234
+ * See the License for the specific language governing permissions and
1235
+ * limitations under the License.
1241
1236
  */
1242
- class DbPrimaryClient {
1243
- constructor(ownerId,
1244
- /** Whether to allow shared access from multiple tabs. */
1245
- allowTabSynchronization, leaseTimestampMs) {
1246
- this.ownerId = ownerId;
1247
- this.allowTabSynchronization = allowTabSynchronization;
1248
- this.leaseTimestampMs = leaseTimestampMs;
1249
- }
1250
- }
1251
1237
  /**
1252
1238
  * Name of the IndexedDb object store.
1253
1239
  *
@@ -1255,550 +1241,128 @@ class DbPrimaryClient {
1255
1241
  * older clients that only supported single locked access to the persistence
1256
1242
  * layer.
1257
1243
  */
1258
- DbPrimaryClient.store = 'owner';
1244
+ const DbPrimaryClientStore = 'owner';
1259
1245
  /**
1260
1246
  * The key string used for the single object that exists in the
1261
1247
  * DbPrimaryClient store.
1262
1248
  */
1263
- DbPrimaryClient.key = 'owner';
1264
- /**
1265
- * An object to be stored in the 'mutationQueues' store in IndexedDb.
1266
- *
1267
- * Each user gets a single queue of MutationBatches to apply to the server.
1268
- * DbMutationQueue tracks the metadata about the queue.
1269
- */
1270
- class DbMutationQueue {
1271
- constructor(
1272
- /**
1273
- * The normalized user ID to which this queue belongs.
1274
- */
1275
- userId,
1276
- /**
1277
- * An identifier for the highest numbered batch that has been acknowledged
1278
- * by the server. All MutationBatches in this queue with batchIds less
1279
- * than or equal to this value are considered to have been acknowledged by
1280
- * the server.
1281
- *
1282
- * NOTE: this is deprecated and no longer used by the code.
1283
- */
1284
- lastAcknowledgedBatchId,
1285
- /**
1286
- * A stream token that was previously sent by the server.
1287
- *
1288
- * See StreamingWriteRequest in datastore.proto for more details about
1289
- * usage.
1290
- *
1291
- * After sending this token, earlier tokens may not be used anymore so
1292
- * only a single stream token is retained.
1293
- *
1294
- * NOTE: this is deprecated and no longer used by the code.
1295
- */
1296
- lastStreamToken) {
1297
- this.userId = userId;
1298
- this.lastAcknowledgedBatchId = lastAcknowledgedBatchId;
1299
- this.lastStreamToken = lastStreamToken;
1300
- }
1301
- }
1249
+ const DbPrimaryClientKey = 'owner';
1302
1250
  /** Name of the IndexedDb object store. */
1303
- DbMutationQueue.store = 'mutationQueues';
1251
+ const DbMutationQueueStore = 'mutationQueues';
1304
1252
  /** Keys are automatically assigned via the userId property. */
1305
- DbMutationQueue.keyPath = 'userId';
1306
- /**
1307
- * An object to be stored in the 'mutations' store in IndexedDb.
1308
- *
1309
- * Represents a batch of user-level mutations intended to be sent to the server
1310
- * in a single write. Each user-level batch gets a separate DbMutationBatch
1311
- * with a new batchId.
1312
- */
1313
- class DbMutationBatch {
1314
- constructor(
1315
- /**
1316
- * The normalized user ID to which this batch belongs.
1317
- */
1318
- userId,
1319
- /**
1320
- * An identifier for this batch, allocated using an auto-generated key.
1321
- */
1322
- batchId,
1323
- /**
1324
- * The local write time of the batch, stored as milliseconds since the
1325
- * epoch.
1326
- */
1327
- localWriteTimeMs,
1328
- /**
1329
- * A list of "mutations" that represent a partial base state from when this
1330
- * write batch was initially created. During local application of the write
1331
- * batch, these baseMutations are applied prior to the real writes in order
1332
- * to override certain document fields from the remote document cache. This
1333
- * is necessary in the case of non-idempotent writes (e.g. `increment()`
1334
- * transforms) to make sure that the local view of the modified documents
1335
- * doesn't flicker if the remote document cache receives the result of the
1336
- * non-idempotent write before the write is removed from the queue.
1337
- *
1338
- * These mutations are never sent to the backend.
1339
- */
1340
- baseMutations,
1341
- /**
1342
- * A list of mutations to apply. All mutations will be applied atomically.
1343
- *
1344
- * Mutations are serialized via toMutation().
1345
- */
1346
- mutations) {
1347
- this.userId = userId;
1348
- this.batchId = batchId;
1349
- this.localWriteTimeMs = localWriteTimeMs;
1350
- this.baseMutations = baseMutations;
1351
- this.mutations = mutations;
1352
- }
1353
- }
1253
+ const DbMutationQueueKeyPath = 'userId';
1354
1254
  /** Name of the IndexedDb object store. */
1355
- DbMutationBatch.store = 'mutations';
1255
+ const DbMutationBatchStore = 'mutations';
1356
1256
  /** Keys are automatically assigned via the userId, batchId properties. */
1357
- DbMutationBatch.keyPath = 'batchId';
1257
+ const DbMutationBatchKeyPath = 'batchId';
1358
1258
  /** The index name for lookup of mutations by user. */
1359
- DbMutationBatch.userMutationsIndex = 'userMutationsIndex';
1259
+ const DbMutationBatchUserMutationsIndex = 'userMutationsIndex';
1360
1260
  /** The user mutations index is keyed by [userId, batchId] pairs. */
1361
- DbMutationBatch.userMutationsKeyPath = ['userId', 'batchId'];
1261
+ const DbMutationBatchUserMutationsKeyPath = ['userId', 'batchId'];
1362
1262
  /**
1363
- * An object to be stored in the 'documentMutations' store in IndexedDb.
1364
- *
1365
- * A manually maintained index of all the mutation batches that affect a given
1366
- * document key. The rows in this table are references based on the contents of
1367
- * DbMutationBatch.mutations.
1263
+ * Creates a [userId] key for use in the DbDocumentMutations index to iterate
1264
+ * over all of a user's document mutations.
1368
1265
  */
1369
- class DbDocumentMutation {
1370
- constructor() { }
1371
- /**
1372
- * Creates a [userId] key for use in the DbDocumentMutations index to iterate
1373
- * over all of a user's document mutations.
1374
- */
1375
- static prefixForUser(userId) {
1376
- return [userId];
1377
- }
1378
- /**
1379
- * Creates a [userId, encodedPath] key for use in the DbDocumentMutations
1380
- * index to iterate over all at document mutations for a given path or lower.
1381
- */
1382
- static prefixForPath(userId, path) {
1383
- return [userId, encodeResourcePath(path)];
1384
- }
1385
- /**
1386
- * Creates a full index key of [userId, encodedPath, batchId] for inserting
1387
- * and deleting into the DbDocumentMutations index.
1388
- */
1389
- static key(userId, path, batchId) {
1390
- return [userId, encodeResourcePath(path), batchId];
1391
- }
1266
+ function newDbDocumentMutationPrefixForUser(userId) {
1267
+ return [userId];
1392
1268
  }
1393
- DbDocumentMutation.store = 'documentMutations';
1394
- /**
1395
- * Because we store all the useful information for this store in the key,
1396
- * there is no useful information to store as the value. The raw (unencoded)
1397
- * path cannot be stored because IndexedDb doesn't store prototype
1398
- * information.
1399
- */
1400
- DbDocumentMutation.PLACEHOLDER = new DbDocumentMutation();
1401
1269
  /**
1402
- * Represents the known absence of a document at a particular version.
1403
- * Stored in IndexedDb as part of a DbRemoteDocument object.
1270
+ * Creates a [userId, encodedPath] key for use in the DbDocumentMutations
1271
+ * index to iterate over all at document mutations for a given path or lower.
1404
1272
  */
1405
- class DbNoDocument {
1406
- constructor(path, readTime) {
1407
- this.path = path;
1408
- this.readTime = readTime;
1409
- }
1273
+ function newDbDocumentMutationPrefixForPath(userId, path) {
1274
+ return [userId, encodeResourcePath(path)];
1410
1275
  }
1411
1276
  /**
1412
- * Represents a document that is known to exist but whose data is unknown.
1413
- * Stored in IndexedDb as part of a DbRemoteDocument object.
1277
+ * Creates a full index key of [userId, encodedPath, batchId] for inserting
1278
+ * and deleting into the DbDocumentMutations index.
1414
1279
  */
1415
- class DbUnknownDocument {
1416
- constructor(path, version) {
1417
- this.path = path;
1418
- this.version = version;
1419
- }
1280
+ function newDbDocumentMutationKey(userId, path, batchId) {
1281
+ return [userId, encodeResourcePath(path), batchId];
1420
1282
  }
1421
1283
  /**
1422
- * An object to be stored in the 'remoteDocuments' store in IndexedDb.
1423
- * It represents either:
1424
- *
1425
- * - A complete document.
1426
- * - A "no document" representing a document that is known not to exist (at
1427
- * some version).
1428
- * - An "unknown document" representing a document that is known to exist (at
1429
- * some version) but whose contents are unknown.
1430
- *
1431
- * Note: This is the persisted equivalent of a MaybeDocument and could perhaps
1432
- * be made more general if necessary.
1284
+ * Because we store all the useful information for this store in the key,
1285
+ * there is no useful information to store as the value. The raw (unencoded)
1286
+ * path cannot be stored because IndexedDb doesn't store prototype
1287
+ * information.
1433
1288
  */
1434
- class DbRemoteDocument {
1435
- // TODO: We are currently storing full document keys almost three times
1436
- // (once as part of the primary key, once - partly - as `parentPath` and once
1437
- // inside the encoded documents). During our next migration, we should
1438
- // rewrite the primary key as parentPath + document ID which would allow us
1439
- // to drop one value.
1440
- constructor(
1441
- /**
1442
- * Set to an instance of DbUnknownDocument if the data for a document is
1443
- * not known, but it is known that a document exists at the specified
1444
- * version (e.g. it had a successful update applied to it)
1445
- */
1446
- unknownDocument,
1447
- /**
1448
- * Set to an instance of a DbNoDocument if it is known that no document
1449
- * exists.
1450
- */
1451
- noDocument,
1452
- /**
1453
- * Set to an instance of a Document if there's a cached version of the
1454
- * document.
1455
- */
1456
- document,
1457
- /**
1458
- * Documents that were written to the remote document store based on
1459
- * a write acknowledgment are marked with `hasCommittedMutations`. These
1460
- * documents are potentially inconsistent with the backend's copy and use
1461
- * the write's commit version as their document version.
1462
- */
1463
- hasCommittedMutations,
1464
- /**
1465
- * When the document was read from the backend. Undefined for data written
1466
- * prior to schema version 9.
1467
- */
1468
- readTime,
1469
- /**
1470
- * The path of the collection this document is part of. Undefined for data
1471
- * written prior to schema version 9.
1472
- */
1473
- parentPath) {
1474
- this.unknownDocument = unknownDocument;
1475
- this.noDocument = noDocument;
1476
- this.document = document;
1477
- this.hasCommittedMutations = hasCommittedMutations;
1478
- this.readTime = readTime;
1479
- this.parentPath = parentPath;
1480
- }
1481
- }
1482
- DbRemoteDocument.store = 'remoteDocuments';
1289
+ const DbDocumentMutationPlaceholder = {};
1290
+ const DbDocumentMutationStore = 'documentMutations';
1291
+ const DbRemoteDocumentStore = 'remoteDocumentsV14';
1483
1292
  /**
1484
- * An index that provides access to all entries sorted by read time (which
1485
- * corresponds to the last modification time of each row).
1486
- *
1487
- * This index is used to provide a changelog for Multi-Tab.
1293
+ * The primary key of the remote documents store, which allows for efficient
1294
+ * access by collection path and read time.
1488
1295
  */
1489
- DbRemoteDocument.readTimeIndex = 'readTimeIndex';
1490
- DbRemoteDocument.readTimeIndexPath = 'readTime';
1296
+ const DbRemoteDocumentKeyPath = [
1297
+ 'prefixPath',
1298
+ 'collectionGroup',
1299
+ 'readTime',
1300
+ 'documentId'
1301
+ ];
1302
+ /** An index that provides access to documents by key. */
1303
+ const DbRemoteDocumentDocumentKeyIndex = 'documentKeyIndex';
1304
+ const DbRemoteDocumentDocumentKeyIndexPath = [
1305
+ 'prefixPath',
1306
+ 'collectionGroup',
1307
+ 'documentId'
1308
+ ];
1491
1309
  /**
1492
- * An index that provides access to documents in a collection sorted by read
1310
+ * An index that provides access to documents by collection group and read
1493
1311
  * time.
1494
1312
  *
1495
- * This index is used to allow the RemoteDocumentCache to fetch newly changed
1496
- * documents in a collection.
1497
- */
1498
- DbRemoteDocument.collectionReadTimeIndex = 'collectionReadTimeIndex';
1499
- DbRemoteDocument.collectionReadTimeIndexPath = ['parentPath', 'readTime'];
1500
- /**
1501
- * Contains a single entry that has metadata about the remote document cache.
1313
+ * This index is used by the index backfiller.
1502
1314
  */
1503
- class DbRemoteDocumentGlobal {
1504
- /**
1505
- * @param byteSize - Approximately the total size in bytes of all the
1506
- * documents in the document cache.
1507
- */
1508
- constructor(byteSize) {
1509
- this.byteSize = byteSize;
1510
- }
1511
- }
1512
- DbRemoteDocumentGlobal.store = 'remoteDocumentGlobal';
1513
- DbRemoteDocumentGlobal.key = 'remoteDocumentGlobalKey';
1514
- /**
1515
- * An object to be stored in the 'targets' store in IndexedDb.
1516
- *
1517
- * This is based on and should be kept in sync with the proto used in the iOS
1518
- * client.
1519
- *
1520
- * Each query the client listens to against the server is tracked on disk so
1521
- * that the query can be efficiently resumed on restart.
1522
- */
1523
- class DbTarget {
1524
- constructor(
1525
- /**
1526
- * An auto-generated sequential numeric identifier for the query.
1527
- *
1528
- * Queries are stored using their canonicalId as the key, but these
1529
- * canonicalIds can be quite long so we additionally assign a unique
1530
- * queryId which can be used by referenced data structures (e.g.
1531
- * indexes) to minimize the on-disk cost.
1532
- */
1533
- targetId,
1534
- /**
1535
- * The canonical string representing this query. This is not unique.
1536
- */
1537
- canonicalId,
1538
- /**
1539
- * The last readTime received from the Watch Service for this query.
1540
- *
1541
- * This is the same value as TargetChange.read_time in the protos.
1542
- */
1543
- readTime,
1544
- /**
1545
- * An opaque, server-assigned token that allows watching a query to be
1546
- * resumed after disconnecting without retransmitting all the data
1547
- * that matches the query. The resume token essentially identifies a
1548
- * point in time from which the server should resume sending results.
1549
- *
1550
- * This is related to the snapshotVersion in that the resumeToken
1551
- * effectively also encodes that value, but the resumeToken is opaque
1552
- * and sometimes encodes additional information.
1553
- *
1554
- * A consequence of this is that the resumeToken should be used when
1555
- * asking the server to reason about where this client is in the watch
1556
- * stream, but the client should use the snapshotVersion for its own
1557
- * purposes.
1558
- *
1559
- * This is the same value as TargetChange.resume_token in the protos.
1560
- */
1561
- resumeToken,
1562
- /**
1563
- * A sequence number representing the last time this query was
1564
- * listened to, used for garbage collection purposes.
1565
- *
1566
- * Conventionally this would be a timestamp value, but device-local
1567
- * clocks are unreliable and they must be able to create new listens
1568
- * even while disconnected. Instead this should be a monotonically
1569
- * increasing number that's incremented on each listen call.
1570
- *
1571
- * This is different from the queryId since the queryId is an
1572
- * immutable identifier assigned to the Query on first use while
1573
- * lastListenSequenceNumber is updated every time the query is
1574
- * listened to.
1575
- */
1576
- lastListenSequenceNumber,
1577
- /**
1578
- * Denotes the maximum snapshot version at which the associated query view
1579
- * contained no limbo documents. Undefined for data written prior to
1580
- * schema version 9.
1581
- */
1582
- lastLimboFreeSnapshotVersion,
1583
- /**
1584
- * The query for this target.
1585
- *
1586
- * Because canonical ids are not unique we must store the actual query. We
1587
- * use the proto to have an object we can persist without having to
1588
- * duplicate translation logic to and from a `Query` object.
1589
- */
1590
- query) {
1591
- this.targetId = targetId;
1592
- this.canonicalId = canonicalId;
1593
- this.readTime = readTime;
1594
- this.resumeToken = resumeToken;
1595
- this.lastListenSequenceNumber = lastListenSequenceNumber;
1596
- this.lastLimboFreeSnapshotVersion = lastLimboFreeSnapshotVersion;
1597
- this.query = query;
1598
- }
1599
- }
1600
- DbTarget.store = 'targets';
1315
+ const DbRemoteDocumentCollectionGroupIndex = 'collectionGroupIndex';
1316
+ const DbRemoteDocumentCollectionGroupIndexPath = [
1317
+ 'collectionGroup',
1318
+ 'readTime',
1319
+ 'prefixPath',
1320
+ 'documentId'
1321
+ ];
1322
+ const DbRemoteDocumentGlobalStore = 'remoteDocumentGlobal';
1323
+ const DbRemoteDocumentGlobalKey = 'remoteDocumentGlobalKey';
1324
+ const DbTargetStore = 'targets';
1601
1325
  /** Keys are automatically assigned via the targetId property. */
1602
- DbTarget.keyPath = 'targetId';
1326
+ const DbTargetKeyPath = 'targetId';
1603
1327
  /** The name of the queryTargets index. */
1604
- DbTarget.queryTargetsIndexName = 'queryTargetsIndex';
1328
+ const DbTargetQueryTargetsIndexName = 'queryTargetsIndex';
1605
1329
  /**
1606
1330
  * The index of all canonicalIds to the targets that they match. This is not
1607
1331
  * a unique mapping because canonicalId does not promise a unique name for all
1608
1332
  * possible queries, so we append the targetId to make the mapping unique.
1609
1333
  */
1610
- DbTarget.queryTargetsKeyPath = ['canonicalId', 'targetId'];
1611
- /**
1612
- * An object representing an association between a target and a document, or a
1613
- * sentinel row marking the last sequence number at which a document was used.
1614
- * Each document cached must have a corresponding sentinel row before lru
1615
- * garbage collection is enabled.
1616
- *
1617
- * The target associations and sentinel rows are co-located so that orphaned
1618
- * documents and their sequence numbers can be identified efficiently via a scan
1619
- * of this store.
1620
- */
1621
- class DbTargetDocument {
1622
- constructor(
1623
- /**
1624
- * The targetId identifying a target or 0 for a sentinel row.
1625
- */
1626
- targetId,
1627
- /**
1628
- * The path to the document, as encoded in the key.
1629
- */
1630
- path,
1631
- /**
1632
- * If this is a sentinel row, this should be the sequence number of the last
1633
- * time the document specified by `path` was used. Otherwise, it should be
1634
- * `undefined`.
1635
- */
1636
- sequenceNumber) {
1637
- this.targetId = targetId;
1638
- this.path = path;
1639
- this.sequenceNumber = sequenceNumber;
1640
- }
1641
- }
1334
+ const DbTargetQueryTargetsKeyPath = ['canonicalId', 'targetId'];
1642
1335
  /** Name of the IndexedDb object store. */
1643
- DbTargetDocument.store = 'targetDocuments';
1336
+ const DbTargetDocumentStore = 'targetDocuments';
1644
1337
  /** Keys are automatically assigned via the targetId, path properties. */
1645
- DbTargetDocument.keyPath = ['targetId', 'path'];
1338
+ const DbTargetDocumentKeyPath = ['targetId', 'path'];
1646
1339
  /** The index name for the reverse index. */
1647
- DbTargetDocument.documentTargetsIndex = 'documentTargetsIndex';
1340
+ const DbTargetDocumentDocumentTargetsIndex = 'documentTargetsIndex';
1648
1341
  /** We also need to create the reverse index for these properties. */
1649
- DbTargetDocument.documentTargetsKeyPath = ['path', 'targetId'];
1650
- /**
1651
- * A record of global state tracked across all Targets, tracked separately
1652
- * to avoid the need for extra indexes.
1653
- *
1654
- * This should be kept in-sync with the proto used in the iOS client.
1655
- */
1656
- class DbTargetGlobal {
1657
- constructor(
1658
- /**
1659
- * The highest numbered target id across all targets.
1660
- *
1661
- * See DbTarget.targetId.
1662
- */
1663
- highestTargetId,
1664
- /**
1665
- * The highest numbered lastListenSequenceNumber across all targets.
1666
- *
1667
- * See DbTarget.lastListenSequenceNumber.
1668
- */
1669
- highestListenSequenceNumber,
1670
- /**
1671
- * A global snapshot version representing the last consistent snapshot we
1672
- * received from the backend. This is monotonically increasing and any
1673
- * snapshots received from the backend prior to this version (e.g. for
1674
- * targets resumed with a resumeToken) should be suppressed (buffered)
1675
- * until the backend has caught up to this snapshot version again. This
1676
- * prevents our cache from ever going backwards in time.
1677
- */
1678
- lastRemoteSnapshotVersion,
1679
- /**
1680
- * The number of targets persisted.
1681
- */
1682
- targetCount) {
1683
- this.highestTargetId = highestTargetId;
1684
- this.highestListenSequenceNumber = highestListenSequenceNumber;
1685
- this.lastRemoteSnapshotVersion = lastRemoteSnapshotVersion;
1686
- this.targetCount = targetCount;
1687
- }
1688
- }
1342
+ const DbTargetDocumentDocumentTargetsKeyPath = ['path', 'targetId'];
1689
1343
  /**
1690
1344
  * The key string used for the single object that exists in the
1691
1345
  * DbTargetGlobal store.
1692
1346
  */
1693
- DbTargetGlobal.key = 'targetGlobalKey';
1694
- DbTargetGlobal.store = 'targetGlobal';
1695
- /**
1696
- * An object representing an association between a Collection id (e.g. 'messages')
1697
- * to a parent path (e.g. '/chats/123') that contains it as a (sub)collection.
1698
- * This is used to efficiently find all collections to query when performing
1699
- * a Collection Group query.
1700
- */
1701
- class DbCollectionParent {
1702
- constructor(
1703
- /**
1704
- * The collectionId (e.g. 'messages')
1705
- */
1706
- collectionId,
1707
- /**
1708
- * The path to the parent (either a document location or an empty path for
1709
- * a root-level collection).
1710
- */
1711
- parent) {
1712
- this.collectionId = collectionId;
1713
- this.parent = parent;
1714
- }
1715
- }
1347
+ const DbTargetGlobalKey = 'targetGlobalKey';
1348
+ const DbTargetGlobalStore = 'targetGlobal';
1716
1349
  /** Name of the IndexedDb object store. */
1717
- DbCollectionParent.store = 'collectionParents';
1350
+ const DbCollectionParentStore = 'collectionParents';
1718
1351
  /** Keys are automatically assigned via the collectionId, parent properties. */
1719
- DbCollectionParent.keyPath = ['collectionId', 'parent'];
1720
- /**
1721
- * A record of the metadata state of each client.
1722
- *
1723
- * PORTING NOTE: This is used to synchronize multi-tab state and does not need
1724
- * to be ported to iOS or Android.
1725
- */
1726
- class DbClientMetadata {
1727
- constructor(
1728
- // Note: Previous schema versions included a field
1729
- // "lastProcessedDocumentChangeId". Don't use anymore.
1730
- /** The auto-generated client id assigned at client startup. */
1731
- clientId,
1732
- /** The last time this state was updated. */
1733
- updateTimeMs,
1734
- /** Whether the client's network connection is enabled. */
1735
- networkEnabled,
1736
- /** Whether this client is running in a foreground tab. */
1737
- inForeground) {
1738
- this.clientId = clientId;
1739
- this.updateTimeMs = updateTimeMs;
1740
- this.networkEnabled = networkEnabled;
1741
- this.inForeground = inForeground;
1742
- }
1743
- }
1352
+ const DbCollectionParentKeyPath = ['collectionId', 'parent'];
1744
1353
  /** Name of the IndexedDb object store. */
1745
- DbClientMetadata.store = 'clientMetadata';
1354
+ const DbClientMetadataStore = 'clientMetadata';
1746
1355
  /** Keys are automatically assigned via the clientId properties. */
1747
- DbClientMetadata.keyPath = 'clientId';
1748
- /** An object representing a bundle loaded by the SDK. */
1749
- class DbBundle {
1750
- constructor(
1751
- /** The ID of the loaded bundle. */
1752
- bundleId,
1753
- /** The create time of the loaded bundle. */
1754
- createTime,
1755
- /** The schema version of the loaded bundle. */
1756
- version) {
1757
- this.bundleId = bundleId;
1758
- this.createTime = createTime;
1759
- this.version = version;
1760
- }
1761
- }
1356
+ const DbClientMetadataKeyPath = 'clientId';
1762
1357
  /** Name of the IndexedDb object store. */
1763
- DbBundle.store = 'bundles';
1764
- DbBundle.keyPath = 'bundleId';
1765
- /** An object representing a named query loaded by the SDK via a bundle. */
1766
- class DbNamedQuery {
1767
- constructor(
1768
- /** The name of the query. */
1769
- name,
1770
- /** The read time of the results saved in the bundle from the named query. */
1771
- readTime,
1772
- /** The query saved in the bundle. */
1773
- bundledQuery) {
1774
- this.name = name;
1775
- this.readTime = readTime;
1776
- this.bundledQuery = bundledQuery;
1777
- }
1778
- }
1358
+ const DbBundleStore = 'bundles';
1359
+ const DbBundleKeyPath = 'bundleId';
1779
1360
  /** Name of the IndexedDb object store. */
1780
- DbNamedQuery.store = 'namedQueries';
1781
- DbNamedQuery.keyPath = 'name';
1782
- /** An object representing the global configuration for a field index. */
1783
- class DbIndexConfiguration {
1784
- constructor(
1785
- /**
1786
- * The index id for this entry. Undefined for indexes that are not yet
1787
- * persisted.
1788
- */
1789
- indexId,
1790
- /** The collection group this index belongs to. */
1791
- collectionGroup,
1792
- /** The fields to index for this index. */
1793
- fields) {
1794
- this.indexId = indexId;
1795
- this.collectionGroup = collectionGroup;
1796
- this.fields = fields;
1797
- }
1798
- }
1361
+ const DbNamedQueryStore = 'namedQueries';
1362
+ const DbNamedQueryKeyPath = 'name';
1799
1363
  /** Name of the IndexedDb object store. */
1800
- DbIndexConfiguration.store = 'indexConfiguration';
1801
- DbIndexConfiguration.keyPath = 'indexId';
1364
+ const DbIndexConfigurationStore = 'indexConfiguration';
1365
+ const DbIndexConfigurationKeyPath = 'indexId';
1802
1366
  /**
1803
1367
  * An index that provides access to the index configurations by collection
1804
1368
  * group.
@@ -1807,49 +1371,11 @@ DbIndexConfiguration.keyPath = 'indexId';
1807
1371
  * not possible here as the Web client supports concurrent access to
1808
1372
  * persistence via multi-tab.
1809
1373
  */
1810
- DbIndexConfiguration.collectionGroupIndex = 'collectionGroupIndex';
1811
- DbIndexConfiguration.collectionGroupIndexPath = 'collectionGroup';
1812
- /**
1813
- * An object describing how up-to-date the index backfill is for each user and
1814
- * index.
1815
- */
1816
- class DbIndexState {
1817
- constructor(
1818
- /** The index id for this entry. */
1819
- indexId,
1820
- /** The user id for this entry. */
1821
- uid,
1822
- /**
1823
- * A number that indicates when the index was last updated (relative to
1824
- * other indexes).
1825
- */
1826
- sequenceNumber,
1827
- /**
1828
- * The latest read time that has been indexed by Firestore for this field
1829
- * index. Set to `{seconds: 0, nanos: 0}` if no documents have been indexed.
1830
- */
1831
- readTime,
1832
- /**
1833
- * The last document that has been indexed for this field index. Empty if
1834
- * no documents have been indexed.
1835
- */
1836
- documentKey,
1837
- /**
1838
- * The largest mutation batch id that has been processed for this index. -1
1839
- * if no mutations have been indexed.
1840
- */
1841
- largestBatchId) {
1842
- this.indexId = indexId;
1843
- this.uid = uid;
1844
- this.sequenceNumber = sequenceNumber;
1845
- this.readTime = readTime;
1846
- this.documentKey = documentKey;
1847
- this.largestBatchId = largestBatchId;
1848
- }
1849
- }
1374
+ const DbIndexConfigurationCollectionGroupIndex = 'collectionGroupIndex';
1375
+ const DbIndexConfigurationCollectionGroupIndexPath = 'collectionGroup';
1850
1376
  /** Name of the IndexedDb object store. */
1851
- DbIndexState.store = 'indexState';
1852
- DbIndexState.keyPath = ['indexId', 'uid'];
1377
+ const DbIndexStateStore = 'indexState';
1378
+ const DbIndexStateKeyPath = ['indexId', 'uid'];
1853
1379
  /**
1854
1380
  * An index that provides access to documents in a collection sorted by last
1855
1381
  * update time. Used by the backfiller.
@@ -1858,112 +1384,89 @@ DbIndexState.keyPath = ['indexId', 'uid'];
1858
1384
  * not possible here as the Web client supports concurrent access to
1859
1385
  * persistence via multi-tab.
1860
1386
  */
1861
- DbIndexState.sequenceNumberIndex = 'sequenceNumberIndex';
1862
- DbIndexState.sequenceNumberIndexPath = ['uid', 'sequenceNumber'];
1863
- /** An object that stores the encoded entries for all documents and fields. */
1864
- class DbIndexEntry {
1865
- constructor(
1866
- /** The index id for this entry. */
1867
- indexId,
1868
- /** The user id for this entry. */
1869
- uid,
1870
- /** The encoded array index value for this entry. */
1871
- arrayValue,
1872
- /** The encoded directional value for equality and inequality filters. */
1873
- directionalValue,
1874
- /** The document key this entry points to. */
1875
- documentKey) {
1876
- this.indexId = indexId;
1877
- this.uid = uid;
1878
- this.arrayValue = arrayValue;
1879
- this.directionalValue = directionalValue;
1880
- this.documentKey = documentKey;
1881
- }
1882
- }
1387
+ const DbIndexStateSequenceNumberIndex = 'sequenceNumberIndex';
1388
+ const DbIndexStateSequenceNumberIndexPath = ['uid', 'sequenceNumber'];
1883
1389
  /** Name of the IndexedDb object store. */
1884
- DbIndexEntry.store = 'indexEntries';
1885
- DbIndexEntry.keyPath = [
1390
+ const DbIndexEntryStore = 'indexEntries';
1391
+ const DbIndexEntryKeyPath = [
1886
1392
  'indexId',
1887
1393
  'uid',
1888
1394
  'arrayValue',
1889
1395
  'directionalValue',
1890
1396
  'documentKey'
1891
1397
  ];
1892
- DbIndexEntry.documentKeyIndex = 'documentKeyIndex';
1893
- DbIndexEntry.documentKeyIndexPath = ['indexId', 'uid', 'documentKey'];
1894
- /**
1895
- * An object representing a document overlay.
1896
- */
1897
- class DbDocumentOverlay {
1898
- constructor(
1899
- /** The user ID to whom this overlay belongs. */
1900
- userId,
1901
- /** The path to the collection that contains the document. */
1902
- collectionPath,
1903
- /** The ID (key) of the document within the collection. */
1904
- documentId,
1905
- /** The collection group to which the document belongs. */
1906
- collectionGroup,
1907
- /** The largest batch ID that's been applied for this overlay. */
1908
- largestBatchId,
1909
- /** The overlay mutation. */
1910
- overlayMutation) {
1911
- this.userId = userId;
1912
- this.collectionPath = collectionPath;
1913
- this.documentId = documentId;
1914
- this.collectionGroup = collectionGroup;
1915
- this.largestBatchId = largestBatchId;
1916
- this.overlayMutation = overlayMutation;
1917
- }
1918
- }
1398
+ const DbIndexEntryDocumentKeyIndex = 'documentKeyIndex';
1399
+ const DbIndexEntryDocumentKeyIndexPath = [
1400
+ 'indexId',
1401
+ 'uid',
1402
+ 'documentKey'
1403
+ ];
1919
1404
  /** Name of the IndexedDb object store. */
1920
- DbDocumentOverlay.store = 'documentOverlays';
1921
- DbDocumentOverlay.keyPath = ['userId', 'collectionPath', 'documentId'];
1922
- DbDocumentOverlay.collectionPathOverlayIndex = 'collectionPathOverlayIndex';
1923
- DbDocumentOverlay.collectionPathOverlayIndexPath = [
1405
+ const DbDocumentOverlayStore = 'documentOverlays';
1406
+ const DbDocumentOverlayKeyPath = [
1407
+ 'userId',
1408
+ 'collectionPath',
1409
+ 'documentId'
1410
+ ];
1411
+ const DbDocumentOverlayCollectionPathOverlayIndex = 'collectionPathOverlayIndex';
1412
+ const DbDocumentOverlayCollectionPathOverlayIndexPath = [
1924
1413
  'userId',
1925
1414
  'collectionPath',
1926
1415
  'largestBatchId'
1927
1416
  ];
1928
- DbDocumentOverlay.collectionGroupOverlayIndex = 'collectionGroupOverlayIndex';
1929
- DbDocumentOverlay.collectionGroupOverlayIndexPath = [
1417
+ const DbDocumentOverlayCollectionGroupOverlayIndex = 'collectionGroupOverlayIndex';
1418
+ const DbDocumentOverlayCollectionGroupOverlayIndexPath = [
1930
1419
  'userId',
1931
1420
  'collectionGroup',
1932
1421
  'largestBatchId'
1933
1422
  ];
1934
1423
  // Visible for testing
1935
1424
  const V1_STORES = [
1936
- DbMutationQueue.store,
1937
- DbMutationBatch.store,
1938
- DbDocumentMutation.store,
1939
- DbRemoteDocument.store,
1940
- DbTarget.store,
1941
- DbPrimaryClient.store,
1942
- DbTargetGlobal.store,
1943
- DbTargetDocument.store
1425
+ DbMutationQueueStore,
1426
+ DbMutationBatchStore,
1427
+ DbDocumentMutationStore,
1428
+ DbRemoteDocumentStore$1,
1429
+ DbTargetStore,
1430
+ DbPrimaryClientStore,
1431
+ DbTargetGlobalStore,
1432
+ DbTargetDocumentStore
1944
1433
  ];
1945
- // V2 is no longer usable (see comment at top of file)
1946
1434
  // Visible for testing
1947
1435
  const V3_STORES = V1_STORES;
1948
1436
  // Note: DbRemoteDocumentChanges is no longer used and dropped with v9.
1949
- const V4_STORES = [...V3_STORES, DbClientMetadata.store];
1950
- // V5 does not change the set of stores.
1951
- const V6_STORES = [...V4_STORES, DbRemoteDocumentGlobal.store];
1952
- // V7 does not change the set of stores.
1953
- const V8_STORES = [...V6_STORES, DbCollectionParent.store];
1954
- // V9 does not change the set of stores.
1955
- // V10 does not change the set of stores.
1956
- const V11_STORES = [...V8_STORES, DbBundle.store, DbNamedQuery.store];
1957
- const V12_STORES = [...V11_STORES, DbDocumentOverlay.store];
1437
+ const V4_STORES = [...V3_STORES, DbClientMetadataStore];
1438
+ const V6_STORES = [...V4_STORES, DbRemoteDocumentGlobalStore];
1439
+ const V8_STORES = [...V6_STORES, DbCollectionParentStore];
1440
+ const V11_STORES = [...V8_STORES, DbBundleStore, DbNamedQueryStore];
1441
+ const V12_STORES = [...V11_STORES, DbDocumentOverlayStore];
1958
1442
  const V13_STORES = [
1959
- ...V12_STORES,
1960
- DbIndexConfiguration.store,
1961
- DbIndexState.store,
1962
- DbIndexEntry.store
1443
+ DbMutationQueueStore,
1444
+ DbMutationBatchStore,
1445
+ DbDocumentMutationStore,
1446
+ DbRemoteDocumentStore,
1447
+ DbTargetStore,
1448
+ DbPrimaryClientStore,
1449
+ DbTargetGlobalStore,
1450
+ DbTargetDocumentStore,
1451
+ DbClientMetadataStore,
1452
+ DbRemoteDocumentGlobalStore,
1453
+ DbCollectionParentStore,
1454
+ DbBundleStore,
1455
+ DbNamedQueryStore,
1456
+ DbDocumentOverlayStore
1457
+ ];
1458
+ const V14_STORES = [
1459
+ ...V13_STORES,
1460
+ DbIndexConfigurationStore,
1461
+ DbIndexStateStore,
1462
+ DbIndexEntryStore
1963
1463
  ];
1964
1464
  /** Returns the object stores for the provided schema. */
1965
1465
  function getObjectStores(schemaVersion) {
1966
- if (schemaVersion === 13) {
1466
+ if (schemaVersion === 14) {
1467
+ return V14_STORES;
1468
+ }
1469
+ else if (schemaVersion === 13) {
1967
1470
  return V13_STORES;
1968
1471
  }
1969
1472
  else if (schemaVersion === 12) {
@@ -2677,6 +2180,21 @@ class SimpleDbStore {
2677
2180
  });
2678
2181
  }
2679
2182
  }
2183
+ /**
2184
+ * Loads the first `count` elements from the provided index range. Loads all
2185
+ * elements if no limit is provided.
2186
+ */
2187
+ loadFirst(range, count) {
2188
+ const request = this.store.getAll(range, count === null ? undefined : count);
2189
+ return new PersistencePromise((resolve, reject) => {
2190
+ request.onerror = (event) => {
2191
+ reject(event.target.error);
2192
+ };
2193
+ request.onsuccess = (event) => {
2194
+ resolve(event.target.result);
2195
+ };
2196
+ });
2197
+ }
2680
2198
  deleteAll(indexOrRange, range) {
2681
2199
  logDebug(LOG_TAG$h, 'DELETE ALL', this.store.name);
2682
2200
  const options = this.options(indexOrRange, range);
@@ -3159,6 +2677,9 @@ class SnapshotVersion {
3159
2677
  static min() {
3160
2678
  return new SnapshotVersion(new Timestamp(0, 0));
3161
2679
  }
2680
+ static max() {
2681
+ return new SnapshotVersion(new Timestamp(253402300799, 1e9 - 1));
2682
+ }
3162
2683
  compareTo(other) {
3163
2684
  return this.timestamp._compareTo(other.timestamp);
3164
2685
  }
@@ -3287,17 +2808,15 @@ class FieldMask {
3287
2808
  * See the License for the specific language governing permissions and
3288
2809
  * limitations under the License.
3289
2810
  */
2811
+ /** Converts a Base64 encoded string to a binary string. */
3290
2812
  function decodeBase64(encoded) {
3291
- // Node actually doesn't validate base64 strings.
3292
- // A quick sanity check that is not a fool-proof validation
3293
- if (/[^-A-Za-z0-9+/=]/.test(encoded)) {
3294
- throw new FirestoreError(Code.INVALID_ARGUMENT, 'Not a valid Base64 string: ' + encoded);
3295
- }
3296
- return new Buffer(encoded, 'base64').toString('binary');
2813
+ // Note: We used to validate the base64 string here via a regular expression.
2814
+ // This was removed to improve the performance of indexing.
2815
+ return Buffer.from(encoded, 'base64').toString('binary');
3297
2816
  }
3298
2817
  /** Converts a binary string to a Base64 encoded string. */
3299
2818
  function encodeBase64(raw) {
3300
- return new Buffer(raw, 'binary').toString('base64');
2819
+ return Buffer.from(raw, 'binary').toString('base64');
3301
2820
  }
3302
2821
  /** True if and only if the Base64 conversion functions are available. */
3303
2822
  function isBase64Available() {
@@ -3338,6 +2857,8 @@ class ByteString {
3338
2857
  return new ByteString(binaryString);
3339
2858
  }
3340
2859
  static fromUint8Array(array) {
2860
+ // TODO(indexing); Remove the copy of the byte string here as this method
2861
+ // is frequently called during indexing.
3341
2862
  const binaryString = binaryStringFromUint8Array(array);
3342
2863
  return new ByteString(binaryString);
3343
2864
  }
@@ -3756,13 +3277,17 @@ class DocumentKey {
3756
3277
  * See the License for the specific language governing permissions and
3757
3278
  * limitations under the License.
3758
3279
  */
3280
+ const MAX_VALUE_TYPE = '__max__';
3759
3281
  const MAX_VALUE = {
3760
3282
  mapValue: {
3761
3283
  fields: {
3762
- '__type__': { stringValue: '__max___' }
3284
+ '__type__': { stringValue: MAX_VALUE_TYPE }
3763
3285
  }
3764
3286
  }
3765
3287
  };
3288
+ const MIN_VALUE = {
3289
+ nullValue: 'NULL_VALUE'
3290
+ };
3766
3291
  /** Extracts the backend's type order for the provided value. */
3767
3292
  function typeOrder(value) {
3768
3293
  if ('nullValue' in value) {
@@ -3796,6 +3321,9 @@ function typeOrder(value) {
3796
3321
  if (isServerTimestamp(value)) {
3797
3322
  return 4 /* ServerTimestampValue */;
3798
3323
  }
3324
+ else if (isMaxValue(value)) {
3325
+ return 9 /* ArrayValue */;
3326
+ }
3799
3327
  return 10 /* ObjectValue */;
3800
3328
  }
3801
3329
  else {
@@ -3835,6 +3363,8 @@ function valueEquals(left, right) {
3835
3363
  return arrayEquals(left.arrayValue.values || [], right.arrayValue.values || [], valueEquals);
3836
3364
  case 10 /* ObjectValue */:
3837
3365
  return objectEquals(left, right);
3366
+ case 9007199254740991 /* MaxValue */:
3367
+ return true;
3838
3368
  default:
3839
3369
  return fail();
3840
3370
  }
@@ -3907,6 +3437,7 @@ function valueCompare(left, right) {
3907
3437
  }
3908
3438
  switch (leftType) {
3909
3439
  case 0 /* NullValue */:
3440
+ case 9007199254740991 /* MaxValue */:
3910
3441
  return 0;
3911
3442
  case 1 /* BooleanValue */:
3912
3443
  return primitiveComparator(left.booleanValue, right.booleanValue);
@@ -4175,7 +3706,102 @@ function deepClone(source) {
4175
3706
  }
4176
3707
  /** Returns true if the Value represents the canonical {@link #MAX_VALUE} . */
4177
3708
  function isMaxValue(value) {
4178
- return valueEquals(value, MAX_VALUE);
3709
+ return ((((value.mapValue || {}).fields || {})['__type__'] || {}).stringValue ===
3710
+ MAX_VALUE_TYPE);
3711
+ }
3712
+ /** Returns the lowest value for the given value type (inclusive). */
3713
+ function valuesGetLowerBound(value) {
3714
+ if ('nullValue' in value) {
3715
+ return MIN_VALUE;
3716
+ }
3717
+ else if ('booleanValue' in value) {
3718
+ return { booleanValue: false };
3719
+ }
3720
+ else if ('integerValue' in value || 'doubleValue' in value) {
3721
+ return { doubleValue: NaN };
3722
+ }
3723
+ else if ('timestampValue' in value) {
3724
+ return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
3725
+ }
3726
+ else if ('stringValue' in value) {
3727
+ return { stringValue: '' };
3728
+ }
3729
+ else if ('bytesValue' in value) {
3730
+ return { bytesValue: '' };
3731
+ }
3732
+ else if ('referenceValue' in value) {
3733
+ return refValue(DatabaseId.empty(), DocumentKey.empty());
3734
+ }
3735
+ else if ('geoPointValue' in value) {
3736
+ return { geoPointValue: { latitude: -90, longitude: -180 } };
3737
+ }
3738
+ else if ('arrayValue' in value) {
3739
+ return { arrayValue: {} };
3740
+ }
3741
+ else if ('mapValue' in value) {
3742
+ return { mapValue: {} };
3743
+ }
3744
+ else {
3745
+ return fail();
3746
+ }
3747
+ }
3748
+ /** Returns the largest value for the given value type (exclusive). */
3749
+ function valuesGetUpperBound(value) {
3750
+ if ('nullValue' in value) {
3751
+ return { booleanValue: false };
3752
+ }
3753
+ else if ('booleanValue' in value) {
3754
+ return { doubleValue: NaN };
3755
+ }
3756
+ else if ('integerValue' in value || 'doubleValue' in value) {
3757
+ return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
3758
+ }
3759
+ else if ('timestampValue' in value) {
3760
+ return { stringValue: '' };
3761
+ }
3762
+ else if ('stringValue' in value) {
3763
+ return { bytesValue: '' };
3764
+ }
3765
+ else if ('bytesValue' in value) {
3766
+ return refValue(DatabaseId.empty(), DocumentKey.empty());
3767
+ }
3768
+ else if ('referenceValue' in value) {
3769
+ return { geoPointValue: { latitude: -90, longitude: -180 } };
3770
+ }
3771
+ else if ('geoPointValue' in value) {
3772
+ return { arrayValue: {} };
3773
+ }
3774
+ else if ('arrayValue' in value) {
3775
+ return { mapValue: {} };
3776
+ }
3777
+ else if ('mapValue' in value) {
3778
+ return MAX_VALUE;
3779
+ }
3780
+ else {
3781
+ return fail();
3782
+ }
3783
+ }
3784
+ function valuesMax(left, right) {
3785
+ if (left === undefined) {
3786
+ return right;
3787
+ }
3788
+ else if (right === undefined) {
3789
+ return left;
3790
+ }
3791
+ else {
3792
+ return valueCompare(left, right) > 0 ? left : right;
3793
+ }
3794
+ }
3795
+ function valuesMin(left, right) {
3796
+ if (left === undefined) {
3797
+ return right;
3798
+ }
3799
+ else if (right === undefined) {
3800
+ return left;
3801
+ }
3802
+ else {
3803
+ return valueCompare(left, right) < 0 ? left : right;
3804
+ }
4179
3805
  }
4180
3806
 
4181
3807
  /**
@@ -4570,6 +4196,10 @@ function fieldIndexGetArraySegment(fieldIndex) {
4570
4196
  function fieldIndexGetDirectionalSegments(fieldIndex) {
4571
4197
  return fieldIndex.fields.filter(s => s.kind !== 2 /* CONTAINS */);
4572
4198
  }
4199
+ /** Returns a debug representation of the field index */
4200
+ function fieldIndexToString(fieldIndex) {
4201
+ return `id=${fieldIndex.indexId}|cg=${fieldIndex.collectionGroup}|f=${fieldIndex.fields.map(f => `${f.fieldPath}:${f.kind}`).join(',')}`;
4202
+ }
4573
4203
  /** An index component consisting of field path and index type. */
4574
4204
  class IndexSegment {
4575
4205
  constructor(
@@ -4601,6 +4231,28 @@ class IndexState {
4601
4231
  return new IndexState(INITIAL_SEQUENCE_NUMBER, IndexOffset.min());
4602
4232
  }
4603
4233
  }
4234
+ /**
4235
+ * Creates an offset that matches all documents with a read time higher than
4236
+ * `readTime`.
4237
+ */
4238
+ function newIndexOffsetSuccessorFromReadTime(readTime, largestBatchId) {
4239
+ // We want to create an offset that matches all documents with a read time
4240
+ // greater than the provided read time. To do so, we technically need to
4241
+ // create an offset for `(readTime, MAX_DOCUMENT_KEY)`. While we could use
4242
+ // Unicode codepoints to generate MAX_DOCUMENT_KEY, it is much easier to use
4243
+ // `(readTime + 1, DocumentKey.empty())` since `> DocumentKey.empty()` matches
4244
+ // all valid document IDs.
4245
+ const successorSeconds = readTime.toTimestamp().seconds;
4246
+ const successorNanos = readTime.toTimestamp().nanoseconds + 1;
4247
+ const successor = SnapshotVersion.fromTimestamp(successorNanos === 1e9
4248
+ ? new Timestamp(successorSeconds + 1, 0)
4249
+ : new Timestamp(successorSeconds, successorNanos));
4250
+ return new IndexOffset(successor, DocumentKey.empty(), largestBatchId);
4251
+ }
4252
+ /** Creates a new offset based on the provided document. */
4253
+ function newIndexOffsetFromDocument(document) {
4254
+ return new IndexOffset(document.readTime, document.key, INITIAL_LARGEST_BATCH_ID);
4255
+ }
4604
4256
  /**
4605
4257
  * Stores the latest read time, document and batch ID that were processed for an
4606
4258
  * index.
@@ -4625,10 +4277,25 @@ class IndexOffset {
4625
4277
  this.documentKey = documentKey;
4626
4278
  this.largestBatchId = largestBatchId;
4627
4279
  }
4628
- /** The state of an index that has not yet been backfilled. */
4280
+ /** Returns an offset that sorts before all regular offsets. */
4629
4281
  static min() {
4630
4282
  return new IndexOffset(SnapshotVersion.min(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
4631
4283
  }
4284
+ /** Returns an offset that sorts after all regular offsets. */
4285
+ static max() {
4286
+ return new IndexOffset(SnapshotVersion.max(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
4287
+ }
4288
+ }
4289
+ function indexOffsetComparator(left, right) {
4290
+ let cmp = left.readTime.compareTo(right.readTime);
4291
+ if (cmp !== 0) {
4292
+ return cmp;
4293
+ }
4294
+ cmp = DocumentKey.comparator(left.documentKey, right.documentKey);
4295
+ if (cmp !== 0) {
4296
+ return cmp;
4297
+ }
4298
+ return primitiveComparator(left.largestBatchId, right.largestBatchId);
4632
4299
  }
4633
4300
 
4634
4301
  /**
@@ -4741,30 +4408,215 @@ function targetEquals(left, right) {
4741
4408
  if (!orderByEquals(left.orderBy[i], right.orderBy[i])) {
4742
4409
  return false;
4743
4410
  }
4744
- }
4745
- if (left.filters.length !== right.filters.length) {
4746
- return false;
4747
- }
4748
- for (let i = 0; i < left.filters.length; i++) {
4749
- if (!filterEquals(left.filters[i], right.filters[i])) {
4750
- return false;
4411
+ }
4412
+ if (left.filters.length !== right.filters.length) {
4413
+ return false;
4414
+ }
4415
+ for (let i = 0; i < left.filters.length; i++) {
4416
+ if (!filterEquals(left.filters[i], right.filters[i])) {
4417
+ return false;
4418
+ }
4419
+ }
4420
+ if (left.collectionGroup !== right.collectionGroup) {
4421
+ return false;
4422
+ }
4423
+ if (!left.path.isEqual(right.path)) {
4424
+ return false;
4425
+ }
4426
+ if (!boundEquals(left.startAt, right.startAt)) {
4427
+ return false;
4428
+ }
4429
+ return boundEquals(left.endAt, right.endAt);
4430
+ }
4431
+ function targetIsDocumentTarget(target) {
4432
+ return (DocumentKey.isDocumentKey(target.path) &&
4433
+ target.collectionGroup === null &&
4434
+ target.filters.length === 0);
4435
+ }
4436
+ /** Returns the field filters that target the given field path. */
4437
+ function targetGetFieldFiltersForPath(target, path) {
4438
+ return target.filters.filter(f => f instanceof FieldFilter && f.field.isEqual(path));
4439
+ }
4440
+ /**
4441
+ * Returns the values that are used in ARRAY_CONTAINS or ARRAY_CONTAINS_ANY
4442
+ * filters. Returns `null` if there are no such filters.
4443
+ */
4444
+ function targetGetArrayValues(target, fieldIndex) {
4445
+ const segment = fieldIndexGetArraySegment(fieldIndex);
4446
+ if (segment === undefined) {
4447
+ return null;
4448
+ }
4449
+ for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
4450
+ switch (fieldFilter.op) {
4451
+ case "array-contains-any" /* ARRAY_CONTAINS_ANY */:
4452
+ return fieldFilter.value.arrayValue.values || [];
4453
+ case "array-contains" /* ARRAY_CONTAINS */:
4454
+ return [fieldFilter.value];
4455
+ // Remaining filters are not array filters.
4456
+ }
4457
+ }
4458
+ return null;
4459
+ }
4460
+ /**
4461
+ * Returns the list of values that are used in != or NOT_IN filters. Returns
4462
+ * `null` if there are no such filters.
4463
+ */
4464
+ function targetGetNotInValues(target, fieldIndex) {
4465
+ const values = new Map();
4466
+ for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
4467
+ for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
4468
+ switch (fieldFilter.op) {
4469
+ case "==" /* EQUAL */:
4470
+ case "in" /* IN */:
4471
+ // Encode equality prefix, which is encoded in the index value before
4472
+ // the inequality (e.g. `a == 'a' && b != 'b'` is encoded to
4473
+ // `value != 'ab'`).
4474
+ values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
4475
+ break;
4476
+ case "not-in" /* NOT_IN */:
4477
+ case "!=" /* NOT_EQUAL */:
4478
+ // NotIn/NotEqual is always a suffix. There cannot be any remaining
4479
+ // segments and hence we can return early here.
4480
+ values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
4481
+ return Array.from(values.values());
4482
+ // Remaining filters cannot be used as notIn bounds.
4483
+ }
4484
+ }
4485
+ }
4486
+ return null;
4487
+ }
4488
+ /**
4489
+ * Returns a lower bound of field values that can be used as a starting point to
4490
+ * scan the index defined by `fieldIndex`. Returns `null` if no lower bound
4491
+ * exists.
4492
+ */
4493
+ function targetGetLowerBound(target, fieldIndex) {
4494
+ const values = [];
4495
+ let inclusive = true;
4496
+ // For each segment, retrieve a lower bound if there is a suitable filter or
4497
+ // startAt.
4498
+ for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
4499
+ let segmentValue = undefined;
4500
+ let segmentInclusive = true;
4501
+ // Process all filters to find a value for the current field segment
4502
+ for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
4503
+ let filterValue = undefined;
4504
+ let filterInclusive = true;
4505
+ switch (fieldFilter.op) {
4506
+ case "<" /* LESS_THAN */:
4507
+ case "<=" /* LESS_THAN_OR_EQUAL */:
4508
+ filterValue = valuesGetLowerBound(fieldFilter.value);
4509
+ break;
4510
+ case "==" /* EQUAL */:
4511
+ case "in" /* IN */:
4512
+ case ">=" /* GREATER_THAN_OR_EQUAL */:
4513
+ filterValue = fieldFilter.value;
4514
+ break;
4515
+ case ">" /* GREATER_THAN */:
4516
+ filterValue = fieldFilter.value;
4517
+ filterInclusive = false;
4518
+ break;
4519
+ case "!=" /* NOT_EQUAL */:
4520
+ case "not-in" /* NOT_IN */:
4521
+ filterValue = MIN_VALUE;
4522
+ break;
4523
+ // Remaining filters cannot be used as lower bounds.
4524
+ }
4525
+ if (valuesMax(segmentValue, filterValue) === filterValue) {
4526
+ segmentValue = filterValue;
4527
+ segmentInclusive = filterInclusive;
4528
+ }
4529
+ }
4530
+ // If there is a startAt bound, compare the values against the existing
4531
+ // boundary to see if we can narrow the scope.
4532
+ if (target.startAt !== null) {
4533
+ for (let i = 0; i < target.orderBy.length; ++i) {
4534
+ const orderBy = target.orderBy[i];
4535
+ if (orderBy.field.isEqual(segment.fieldPath)) {
4536
+ const cursorValue = target.startAt.position[i];
4537
+ if (valuesMax(segmentValue, cursorValue) === cursorValue) {
4538
+ segmentValue = cursorValue;
4539
+ segmentInclusive = target.startAt.inclusive;
4540
+ }
4541
+ break;
4542
+ }
4543
+ }
4544
+ }
4545
+ if (segmentValue === undefined) {
4546
+ // No lower bound exists
4547
+ return null;
4548
+ }
4549
+ values.push(segmentValue);
4550
+ inclusive && (inclusive = segmentInclusive);
4551
+ }
4552
+ return new Bound(values, inclusive);
4553
+ }
4554
+ /**
4555
+ * Returns an upper bound of field values that can be used as an ending point
4556
+ * when scanning the index defined by `fieldIndex`. Returns `null` if no
4557
+ * upper bound exists.
4558
+ */
4559
+ function targetGetUpperBound(target, fieldIndex) {
4560
+ const values = [];
4561
+ let inclusive = true;
4562
+ // For each segment, retrieve an upper bound if there is a suitable filter or
4563
+ // endAt.
4564
+ for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
4565
+ let segmentValue = undefined;
4566
+ let segmentInclusive = true;
4567
+ // Process all filters to find a value for the current field segment
4568
+ for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
4569
+ let filterValue = undefined;
4570
+ let filterInclusive = true;
4571
+ switch (fieldFilter.op) {
4572
+ case ">=" /* GREATER_THAN_OR_EQUAL */:
4573
+ case ">" /* GREATER_THAN */:
4574
+ filterValue = valuesGetUpperBound(fieldFilter.value);
4575
+ filterInclusive = false;
4576
+ break;
4577
+ case "==" /* EQUAL */:
4578
+ case "in" /* IN */:
4579
+ case "<=" /* LESS_THAN_OR_EQUAL */:
4580
+ filterValue = fieldFilter.value;
4581
+ break;
4582
+ case "<" /* LESS_THAN */:
4583
+ filterValue = fieldFilter.value;
4584
+ filterInclusive = false;
4585
+ break;
4586
+ case "!=" /* NOT_EQUAL */:
4587
+ case "not-in" /* NOT_IN */:
4588
+ filterValue = MAX_VALUE;
4589
+ break;
4590
+ // Remaining filters cannot be used as upper bounds.
4591
+ }
4592
+ if (valuesMin(segmentValue, filterValue) === filterValue) {
4593
+ segmentValue = filterValue;
4594
+ segmentInclusive = filterInclusive;
4595
+ }
4596
+ }
4597
+ // If there is a endAt bound, compare the values against the existing
4598
+ // boundary to see if we can narrow the scope.
4599
+ if (target.endAt !== null) {
4600
+ for (let i = 0; i < target.orderBy.length; ++i) {
4601
+ const orderBy = target.orderBy[i];
4602
+ if (orderBy.field.isEqual(segment.fieldPath)) {
4603
+ const cursorValue = target.endAt.position[i];
4604
+ if (valuesMin(segmentValue, cursorValue) === cursorValue) {
4605
+ segmentValue = cursorValue;
4606
+ segmentInclusive = target.endAt.inclusive;
4607
+ }
4608
+ break;
4609
+ }
4610
+ }
4611
+ }
4612
+ if (segmentValue === undefined) {
4613
+ // No upper bound exists
4614
+ return null;
4751
4615
  }
4616
+ values.push(segmentValue);
4617
+ inclusive && (inclusive = segmentInclusive);
4752
4618
  }
4753
- if (left.collectionGroup !== right.collectionGroup) {
4754
- return false;
4755
- }
4756
- if (!left.path.isEqual(right.path)) {
4757
- return false;
4758
- }
4759
- if (!boundEquals(left.startAt, right.startAt)) {
4760
- return false;
4761
- }
4762
- return boundEquals(left.endAt, right.endAt);
4763
- }
4764
- function targetIsDocumentTarget(target) {
4765
- return (DocumentKey.isDocumentKey(target.path) &&
4766
- target.collectionGroup === null &&
4767
- target.filters.length === 0);
4619
+ return new Bound(values, inclusive);
4768
4620
  }
4769
4621
  class Filter {
4770
4622
  }
@@ -5336,6 +5188,18 @@ function queryMatchesBounds(query, doc) {
5336
5188
  }
5337
5189
  return true;
5338
5190
  }
5191
+ /**
5192
+ * Returns the collection group that this query targets.
5193
+ *
5194
+ * PORTING NOTE: This is only used in the Web SDK to facilitate multi-tab
5195
+ * synchronization for query results.
5196
+ */
5197
+ function queryCollectionGroup(query) {
5198
+ return (query.collectionGroup ||
5199
+ (query.path.length % 2 === 1
5200
+ ? query.path.lastSegment()
5201
+ : query.path.get(query.path.length - 2)));
5202
+ }
5339
5203
  /**
5340
5204
  * Returns a new comparator function that can be used to compare two documents
5341
5205
  * based on the Query's ordering constraint.
@@ -5367,6 +5231,116 @@ function compareDocs(orderBy, d1, d2) {
5367
5231
  }
5368
5232
  }
5369
5233
 
5234
+ /**
5235
+ * @license
5236
+ * Copyright 2017 Google LLC
5237
+ *
5238
+ * Licensed under the Apache License, Version 2.0 (the "License");
5239
+ * you may not use this file except in compliance with the License.
5240
+ * You may obtain a copy of the License at
5241
+ *
5242
+ * http://www.apache.org/licenses/LICENSE-2.0
5243
+ *
5244
+ * Unless required by applicable law or agreed to in writing, software
5245
+ * distributed under the License is distributed on an "AS IS" BASIS,
5246
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5247
+ * See the License for the specific language governing permissions and
5248
+ * limitations under the License.
5249
+ */
5250
+ /**
5251
+ * A map implementation that uses objects as keys. Objects must have an
5252
+ * associated equals function and must be immutable. Entries in the map are
5253
+ * stored together with the key being produced from the mapKeyFn. This map
5254
+ * automatically handles collisions of keys.
5255
+ */
5256
+ class ObjectMap {
5257
+ constructor(mapKeyFn, equalsFn) {
5258
+ this.mapKeyFn = mapKeyFn;
5259
+ this.equalsFn = equalsFn;
5260
+ /**
5261
+ * The inner map for a key/value pair. Due to the possibility of collisions we
5262
+ * keep a list of entries that we do a linear search through to find an actual
5263
+ * match. Note that collisions should be rare, so we still expect near
5264
+ * constant time lookups in practice.
5265
+ */
5266
+ this.inner = {};
5267
+ /** The number of entries stored in the map */
5268
+ this.innerSize = 0;
5269
+ }
5270
+ /** Get a value for this key, or undefined if it does not exist. */
5271
+ get(key) {
5272
+ const id = this.mapKeyFn(key);
5273
+ const matches = this.inner[id];
5274
+ if (matches === undefined) {
5275
+ return undefined;
5276
+ }
5277
+ for (const [otherKey, value] of matches) {
5278
+ if (this.equalsFn(otherKey, key)) {
5279
+ return value;
5280
+ }
5281
+ }
5282
+ return undefined;
5283
+ }
5284
+ has(key) {
5285
+ return this.get(key) !== undefined;
5286
+ }
5287
+ /** Put this key and value in the map. */
5288
+ set(key, value) {
5289
+ const id = this.mapKeyFn(key);
5290
+ const matches = this.inner[id];
5291
+ if (matches === undefined) {
5292
+ this.inner[id] = [[key, value]];
5293
+ this.innerSize++;
5294
+ return;
5295
+ }
5296
+ for (let i = 0; i < matches.length; i++) {
5297
+ if (this.equalsFn(matches[i][0], key)) {
5298
+ // This is updating an existing entry and does not increase `innerSize`.
5299
+ matches[i] = [key, value];
5300
+ return;
5301
+ }
5302
+ }
5303
+ matches.push([key, value]);
5304
+ this.innerSize++;
5305
+ }
5306
+ /**
5307
+ * Remove this key from the map. Returns a boolean if anything was deleted.
5308
+ */
5309
+ delete(key) {
5310
+ const id = this.mapKeyFn(key);
5311
+ const matches = this.inner[id];
5312
+ if (matches === undefined) {
5313
+ return false;
5314
+ }
5315
+ for (let i = 0; i < matches.length; i++) {
5316
+ if (this.equalsFn(matches[i][0], key)) {
5317
+ if (matches.length === 1) {
5318
+ delete this.inner[id];
5319
+ }
5320
+ else {
5321
+ matches.splice(i, 1);
5322
+ }
5323
+ this.innerSize--;
5324
+ return true;
5325
+ }
5326
+ }
5327
+ return false;
5328
+ }
5329
+ forEach(fn) {
5330
+ forEach(this.inner, (_, entries) => {
5331
+ for (const [k, v] of entries) {
5332
+ fn(k, v);
5333
+ }
5334
+ });
5335
+ }
5336
+ isEmpty() {
5337
+ return isEmpty(this.inner);
5338
+ }
5339
+ size() {
5340
+ return this.innerSize;
5341
+ }
5342
+ }
5343
+
5370
5344
  /**
5371
5345
  * @license
5372
5346
  * Copyright 2017 Google LLC
@@ -5509,7 +5483,7 @@ class SortedMapIterator {
5509
5483
  while (!node.isEmpty()) {
5510
5484
  cmp = startKey ? comparator(node.key, startKey) : 1;
5511
5485
  // flip the comparison if we're going in reverse
5512
- if (isReverse) {
5486
+ if (startKey && isReverse) {
5513
5487
  cmp *= -1;
5514
5488
  }
5515
5489
  if (cmp < 0) {
@@ -6078,6 +6052,9 @@ const EMPTY_DOCUMENT_MAP = new SortedMap(DocumentKey.comparator);
6078
6052
  function documentMap() {
6079
6053
  return EMPTY_DOCUMENT_MAP;
6080
6054
  }
6055
+ function newOverlayMap() {
6056
+ return new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
6057
+ }
6081
6058
  const EMPTY_DOCUMENT_VERSION_MAP = new SortedMap(DocumentKey.comparator);
6082
6059
  function documentVersionMap() {
6083
6060
  return EMPTY_DOCUMENT_VERSION_MAP;
@@ -8756,34 +8733,33 @@ function fromDbRemoteDocument(localSerializer, remoteDoc) {
8756
8733
  }
8757
8734
  /** Encodes a document for storage locally. */
8758
8735
  function toDbRemoteDocument(localSerializer, document) {
8759
- const dbReadTime = toDbTimestampKey(document.readTime);
8760
- const parentPath = document.key.path.popLast().toArray();
8736
+ const key = document.key;
8737
+ const remoteDoc = {
8738
+ prefixPath: key.getCollectionPath().popLast().toArray(),
8739
+ collectionGroup: key.collectionGroup,
8740
+ documentId: key.path.lastSegment(),
8741
+ readTime: toDbTimestampKey(document.readTime),
8742
+ hasCommittedMutations: document.hasCommittedMutations
8743
+ };
8761
8744
  if (document.isFoundDocument()) {
8762
- const doc = toDocument(localSerializer.remoteSerializer, document);
8763
- const hasCommittedMutations = document.hasCommittedMutations;
8764
- return new DbRemoteDocument(
8765
- /* unknownDocument= */ null,
8766
- /* noDocument= */ null, doc, hasCommittedMutations, dbReadTime, parentPath);
8745
+ remoteDoc.document = toDocument(localSerializer.remoteSerializer, document);
8767
8746
  }
8768
8747
  else if (document.isNoDocument()) {
8769
- const path = document.key.path.toArray();
8770
- const version = toDbTimestamp(document.version);
8771
- const hasCommittedMutations = document.hasCommittedMutations;
8772
- return new DbRemoteDocument(
8773
- /* unknownDocument= */ null, new DbNoDocument(path, version),
8774
- /* document= */ null, hasCommittedMutations, dbReadTime, parentPath);
8748
+ remoteDoc.noDocument = {
8749
+ path: key.path.toArray(),
8750
+ readTime: toDbTimestamp(document.version)
8751
+ };
8775
8752
  }
8776
8753
  else if (document.isUnknownDocument()) {
8777
- const path = document.key.path.toArray();
8778
- const readTime = toDbTimestamp(document.version);
8779
- return new DbRemoteDocument(new DbUnknownDocument(path, readTime),
8780
- /* noDocument= */ null,
8781
- /* document= */ null,
8782
- /* hasCommittedMutations= */ true, dbReadTime, parentPath);
8754
+ remoteDoc.unknownDocument = {
8755
+ path: key.path.toArray(),
8756
+ version: toDbTimestamp(document.version)
8757
+ };
8783
8758
  }
8784
8759
  else {
8785
8760
  return fail();
8786
8761
  }
8762
+ return remoteDoc;
8787
8763
  }
8788
8764
  function toDbTimestampKey(snapshotVersion) {
8789
8765
  const timestamp = snapshotVersion.toTimestamp();
@@ -8795,7 +8771,7 @@ function fromDbTimestampKey(dbTimestampKey) {
8795
8771
  }
8796
8772
  function toDbTimestamp(snapshotVersion) {
8797
8773
  const timestamp = snapshotVersion.toTimestamp();
8798
- return new DbTimestamp(timestamp.seconds, timestamp.nanoseconds);
8774
+ return { seconds: timestamp.seconds, nanoseconds: timestamp.nanoseconds };
8799
8775
  }
8800
8776
  function fromDbTimestamp(dbTimestamp) {
8801
8777
  const timestamp = new Timestamp(dbTimestamp.seconds, dbTimestamp.nanoseconds);
@@ -8805,7 +8781,13 @@ function fromDbTimestamp(dbTimestamp) {
8805
8781
  function toDbMutationBatch(localSerializer, userId, batch) {
8806
8782
  const serializedBaseMutations = batch.baseMutations.map(m => toMutation(localSerializer.remoteSerializer, m));
8807
8783
  const serializedMutations = batch.mutations.map(m => toMutation(localSerializer.remoteSerializer, m));
8808
- return new DbMutationBatch(userId, batch.batchId, batch.localWriteTime.toMillis(), serializedBaseMutations, serializedMutations);
8784
+ return {
8785
+ userId,
8786
+ batchId: batch.batchId,
8787
+ localWriteTimeMs: batch.localWriteTime.toMillis(),
8788
+ baseMutations: serializedBaseMutations,
8789
+ mutations: serializedMutations
8790
+ };
8809
8791
  }
8810
8792
  /** Decodes a DbMutationBatch into a MutationBatch */
8811
8793
  function fromDbMutationBatch(localSerializer, dbBatch) {
@@ -8861,7 +8843,15 @@ function toDbTarget(localSerializer, targetData) {
8861
8843
  // convert it to a base64 string for storage.
8862
8844
  const resumeToken = targetData.resumeToken.toBase64();
8863
8845
  // lastListenSequenceNumber is always 0 until we do real GC.
8864
- return new DbTarget(targetData.targetId, canonifyTarget(targetData.target), dbTimestamp, resumeToken, targetData.sequenceNumber, dbLastLimboFreeTimestamp, queryProto);
8846
+ return {
8847
+ targetId: targetData.targetId,
8848
+ canonicalId: canonifyTarget(targetData.target),
8849
+ readTime: dbTimestamp,
8850
+ resumeToken,
8851
+ lastListenSequenceNumber: targetData.sequenceNumber,
8852
+ lastLimboFreeSnapshotVersion: dbLastLimboFreeTimestamp,
8853
+ query: queryProto
8854
+ };
8865
8855
  }
8866
8856
  /**
8867
8857
  * A helper function for figuring out what kind of query has been stored.
@@ -8940,7 +8930,14 @@ function fromDbDocumentOverlay(localSerializer, dbDocumentOverlay) {
8940
8930
  /** Decodes an Overlay model object into a DbDocumentOverlay object. */
8941
8931
  function toDbDocumentOverlay(localSerializer, userId, overlay) {
8942
8932
  const [_, collectionPath, documentId] = toDbDocumentOverlayKey(userId, overlay.mutation.key);
8943
- return new DbDocumentOverlay(userId, collectionPath, documentId, overlay.mutation.key.getCollectionGroup(), overlay.largestBatchId, toMutation(localSerializer.remoteSerializer, overlay.mutation));
8933
+ return {
8934
+ userId,
8935
+ collectionPath,
8936
+ documentId,
8937
+ collectionGroup: overlay.mutation.key.getCollectionGroup(),
8938
+ largestBatchId: overlay.largestBatchId,
8939
+ overlayMutation: toMutation(localSerializer.remoteSerializer, overlay.mutation)
8940
+ };
8944
8941
  }
8945
8942
  /**
8946
8943
  * Returns the DbDocumentOverlayKey corresponding to the given user and
@@ -8952,7 +8949,11 @@ function toDbDocumentOverlayKey(userId, docKey) {
8952
8949
  return [userId, collectionPath, docId];
8953
8950
  }
8954
8951
  function toDbIndexConfiguration(index) {
8955
- return new DbIndexConfiguration(index.indexId, index.collectionGroup, index.fields.map(s => [s.fieldPath.canonicalString(), s.kind]));
8952
+ return {
8953
+ indexId: index.indexId,
8954
+ collectionGroup: index.collectionGroup,
8955
+ fields: index.fields.map(s => [s.fieldPath.canonicalString(), s.kind])
8956
+ };
8956
8957
  }
8957
8958
  function fromDbIndexConfiguration(index, state) {
8958
8959
  const decodedState = state
@@ -8962,7 +8963,14 @@ function fromDbIndexConfiguration(index, state) {
8962
8963
  return new FieldIndex(index.indexId, index.collectionGroup, decodedSegments, decodedState);
8963
8964
  }
8964
8965
  function toDbIndexState(indexId, user, sequenceNumber, offset) {
8965
- return new DbIndexState(indexId, user.uid || '', sequenceNumber, toDbTimestamp(offset.readTime), encodeResourcePath(offset.documentKey.path), offset.largestBatchId);
8966
+ return {
8967
+ indexId,
8968
+ uid: user.uid || '',
8969
+ sequenceNumber,
8970
+ readTime: toDbTimestamp(offset.readTime),
8971
+ documentKey: encodeResourcePath(offset.documentKey.path),
8972
+ largestBatchId: offset.largestBatchId
8973
+ };
8966
8974
  }
8967
8975
 
8968
8976
  /**
@@ -9013,13 +9021,13 @@ class IndexedDbBundleCache {
9013
9021
  * Helper to get a typed SimpleDbStore for the bundles object store.
9014
9022
  */
9015
9023
  function bundlesStore(txn) {
9016
- return getStore(txn, DbBundle.store);
9024
+ return getStore(txn, DbBundleStore);
9017
9025
  }
9018
9026
  /**
9019
9027
  * Helper to get a typed SimpleDbStore for the namedQueries object store.
9020
9028
  */
9021
9029
  function namedQueriesStore(txn) {
9022
- return getStore(txn, DbNamedQuery.store);
9030
+ return getStore(txn, DbNamedQueryStore);
9023
9031
  }
9024
9032
 
9025
9033
  /**
@@ -9066,7 +9074,7 @@ class IndexedDbDocumentOverlayCache {
9066
9074
  }
9067
9075
  saveOverlays(transaction, largestBatchId, overlays) {
9068
9076
  const promises = [];
9069
- overlays.forEach(mutation => {
9077
+ overlays.forEach((_, mutation) => {
9070
9078
  const overlay = new Overlay(largestBatchId, mutation);
9071
9079
  promises.push(this.saveOverlay(transaction, overlay));
9072
9080
  });
@@ -9081,19 +9089,19 @@ class IndexedDbDocumentOverlayCache {
9081
9089
  const range = IDBKeyRange.bound([this.userId, collectionPath, batchId], [this.userId, collectionPath, batchId + 1],
9082
9090
  /*lowerOpen=*/ false,
9083
9091
  /*upperOpen=*/ true);
9084
- promises.push(documentOverlayStore(transaction).deleteAll(DbDocumentOverlay.collectionPathOverlayIndex, range));
9092
+ promises.push(documentOverlayStore(transaction).deleteAll(DbDocumentOverlayCollectionPathOverlayIndex, range));
9085
9093
  });
9086
9094
  return PersistencePromise.waitFor(promises);
9087
9095
  }
9088
9096
  getOverlaysForCollection(transaction, collection, sinceBatchId) {
9089
- const result = new Map();
9097
+ const result = newOverlayMap();
9090
9098
  const collectionPath = encodeResourcePath(collection);
9091
9099
  // We want batch IDs larger than `sinceBatchId`, and so the lower bound
9092
9100
  // is not inclusive.
9093
9101
  const range = IDBKeyRange.bound([this.userId, collectionPath, sinceBatchId], [this.userId, collectionPath, Number.POSITIVE_INFINITY],
9094
9102
  /*lowerOpen=*/ true);
9095
9103
  return documentOverlayStore(transaction)
9096
- .loadAll(DbDocumentOverlay.collectionPathOverlayIndex, range)
9104
+ .loadAll(DbDocumentOverlayCollectionPathOverlayIndex, range)
9097
9105
  .next(dbOverlays => {
9098
9106
  for (const dbOverlay of dbOverlays) {
9099
9107
  const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
@@ -9103,7 +9111,7 @@ class IndexedDbDocumentOverlayCache {
9103
9111
  });
9104
9112
  }
9105
9113
  getOverlaysForCollectionGroup(transaction, collectionGroup, sinceBatchId, count) {
9106
- const result = new Map();
9114
+ const result = newOverlayMap();
9107
9115
  let currentBatchId = undefined;
9108
9116
  // We want batch IDs larger than `sinceBatchId`, and so the lower bound
9109
9117
  // is not inclusive.
@@ -9111,7 +9119,7 @@ class IndexedDbDocumentOverlayCache {
9111
9119
  /*lowerOpen=*/ true);
9112
9120
  return documentOverlayStore(transaction)
9113
9121
  .iterate({
9114
- index: DbDocumentOverlay.collectionGroupOverlayIndex,
9122
+ index: DbDocumentOverlayCollectionGroupOverlayIndex,
9115
9123
  range
9116
9124
  }, (_, dbOverlay, control) => {
9117
9125
  // We do not want to return partial batch overlays, even if the size
@@ -9119,7 +9127,7 @@ class IndexedDbDocumentOverlayCache {
9119
9127
  // continue to aggregate results even after the result size exceeds
9120
9128
  // `count` if there are more overlays from the `currentBatchId`.
9121
9129
  const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
9122
- if (result.size < count ||
9130
+ if (result.size() < count ||
9123
9131
  overlay.largestBatchId === currentBatchId) {
9124
9132
  result.set(overlay.getKey(), overlay);
9125
9133
  currentBatchId = overlay.largestBatchId;
@@ -9138,7 +9146,7 @@ class IndexedDbDocumentOverlayCache {
9138
9146
  * Helper to get a typed SimpleDbStore for the document overlay object store.
9139
9147
  */
9140
9148
  function documentOverlayStore(txn) {
9141
- return getStore(txn, DbDocumentOverlay.store);
9149
+ return getStore(txn, DbDocumentOverlayStore);
9142
9150
  }
9143
9151
 
9144
9152
  /**
@@ -9694,21 +9702,40 @@ class IndexEntry {
9694
9702
  this.arrayValue = arrayValue;
9695
9703
  this.directionalValue = directionalValue;
9696
9704
  }
9705
+ /**
9706
+ * Returns an IndexEntry entry that sorts immediately after the current
9707
+ * directional value.
9708
+ */
9709
+ successor() {
9710
+ const currentLength = this.directionalValue.length;
9711
+ const newLength = currentLength === 0 || this.directionalValue[currentLength - 1] === 255
9712
+ ? currentLength + 1
9713
+ : currentLength;
9714
+ const successor = new Uint8Array(newLength);
9715
+ successor.set(this.directionalValue, 0);
9716
+ if (newLength !== currentLength) {
9717
+ successor.set([0], this.directionalValue.length);
9718
+ }
9719
+ else {
9720
+ ++successor[successor.length - 1];
9721
+ }
9722
+ return new IndexEntry(this.indexId, this.documentKey, this.arrayValue, successor);
9723
+ }
9697
9724
  }
9698
9725
  function indexEntryComparator(left, right) {
9699
9726
  let cmp = left.indexId - right.indexId;
9700
9727
  if (cmp !== 0) {
9701
9728
  return cmp;
9702
9729
  }
9703
- cmp = DocumentKey.comparator(left.documentKey, right.documentKey);
9730
+ cmp = compareByteArrays(left.arrayValue, right.arrayValue);
9704
9731
  if (cmp !== 0) {
9705
9732
  return cmp;
9706
9733
  }
9707
- cmp = compareByteArrays(left.arrayValue, right.arrayValue);
9734
+ cmp = compareByteArrays(left.directionalValue, right.directionalValue);
9708
9735
  if (cmp !== 0) {
9709
9736
  return cmp;
9710
9737
  }
9711
- return compareByteArrays(left.directionalValue, right.directionalValue);
9738
+ return DocumentKey.comparator(left.documentKey, right.documentKey);
9712
9739
  }
9713
9740
  function compareByteArrays(left, right) {
9714
9741
  for (let i = 0; i < left.length && i < right.length; ++i) {
@@ -9720,6 +9747,160 @@ function compareByteArrays(left, right) {
9720
9747
  return left.length - right.length;
9721
9748
  }
9722
9749
 
9750
+ /**
9751
+ * @license
9752
+ * Copyright 2022 Google LLC
9753
+ *
9754
+ * Licensed under the Apache License, Version 2.0 (the "License");
9755
+ * you may not use this file except in compliance with the License.
9756
+ * You may obtain a copy of the License at
9757
+ *
9758
+ * http://www.apache.org/licenses/LICENSE-2.0
9759
+ *
9760
+ * Unless required by applicable law or agreed to in writing, software
9761
+ * distributed under the License is distributed on an "AS IS" BASIS,
9762
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9763
+ * See the License for the specific language governing permissions and
9764
+ * limitations under the License.
9765
+ */
9766
+ /**
9767
+ * A light query planner for Firestore.
9768
+ *
9769
+ * This class matches a `FieldIndex` against a Firestore Query `Target`. It
9770
+ * determines whether a given index can be used to serve the specified target.
9771
+ *
9772
+ * The following table showcases some possible index configurations:
9773
+ *
9774
+ * Query | Index
9775
+ * -----------------------------------------------------------------------------
9776
+ * where('a', '==', 'a').where('b', '==', 'b') | a ASC, b DESC
9777
+ * where('a', '==', 'a').where('b', '==', 'b') | a ASC
9778
+ * where('a', '==', 'a').where('b', '==', 'b') | b DESC
9779
+ * where('a', '>=', 'a').orderBy('a') | a ASC
9780
+ * where('a', '>=', 'a').orderBy('a', 'desc') | a DESC
9781
+ * where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC, b ASC
9782
+ * where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC
9783
+ * where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS, b ASCENDING
9784
+ * where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS
9785
+ */
9786
+ class TargetIndexMatcher {
9787
+ constructor(target) {
9788
+ this.collectionId =
9789
+ target.collectionGroup != null
9790
+ ? target.collectionGroup
9791
+ : target.path.lastSegment();
9792
+ this.orderBys = target.orderBy;
9793
+ this.equalityFilters = [];
9794
+ for (const filter of target.filters) {
9795
+ const fieldFilter = filter;
9796
+ if (fieldFilter.isInequality()) {
9797
+ this.inequalityFilter = fieldFilter;
9798
+ }
9799
+ else {
9800
+ this.equalityFilters.push(fieldFilter);
9801
+ }
9802
+ }
9803
+ }
9804
+ /**
9805
+ * Returns whether the index can be used to serve the TargetIndexMatcher's
9806
+ * target.
9807
+ *
9808
+ * An index is considered capable of serving the target when:
9809
+ * - The target uses all index segments for its filters and orderBy clauses.
9810
+ * The target can have additional filter and orderBy clauses, but not
9811
+ * fewer.
9812
+ * - If an ArrayContains/ArrayContainsAnyfilter is used, the index must also
9813
+ * have a corresponding `CONTAINS` segment.
9814
+ * - All directional index segments can be mapped to the target as a series of
9815
+ * equality filters, a single inequality filter and a series of orderBy
9816
+ * clauses.
9817
+ * - The segments that represent the equality filters may appear out of order.
9818
+ * - The optional segment for the inequality filter must appear after all
9819
+ * equality segments.
9820
+ * - The segments that represent that orderBy clause of the target must appear
9821
+ * in order after all equality and inequality segments. Single orderBy
9822
+ * clauses cannot be skipped, but a continuous orderBy suffix may be
9823
+ * omitted.
9824
+ */
9825
+ servedByIndex(index) {
9826
+ // If there is an array element, find a matching filter.
9827
+ const arraySegment = fieldIndexGetArraySegment(index);
9828
+ if (arraySegment !== undefined &&
9829
+ !this.hasMatchingEqualityFilter(arraySegment)) {
9830
+ return false;
9831
+ }
9832
+ const segments = fieldIndexGetDirectionalSegments(index);
9833
+ let segmentIndex = 0;
9834
+ let orderBysIndex = 0;
9835
+ // Process all equalities first. Equalities can appear out of order.
9836
+ for (; segmentIndex < segments.length; ++segmentIndex) {
9837
+ // We attempt to greedily match all segments to equality filters. If a
9838
+ // filter matches an index segment, we can mark the segment as used.
9839
+ // Since it is not possible to use the same field path in both an equality
9840
+ // and inequality/oderBy clause, we do not have to consider the possibility
9841
+ // that a matching equality segment should instead be used to map to an
9842
+ // inequality filter or orderBy clause.
9843
+ if (!this.hasMatchingEqualityFilter(segments[segmentIndex])) {
9844
+ // If we cannot find a matching filter, we need to verify whether the
9845
+ // remaining segments map to the target's inequality and its orderBy
9846
+ // clauses.
9847
+ break;
9848
+ }
9849
+ }
9850
+ // If we already have processed all segments, all segments are used to serve
9851
+ // the equality filters and we do not need to map any segments to the
9852
+ // target's inequality and orderBy clauses.
9853
+ if (segmentIndex === segments.length) {
9854
+ return true;
9855
+ }
9856
+ // If there is an inequality filter, the next segment must match both the
9857
+ // filter and the first orderBy clause.
9858
+ if (this.inequalityFilter !== undefined) {
9859
+ const segment = segments[segmentIndex];
9860
+ if (!this.matchesFilter(this.inequalityFilter, segment) ||
9861
+ !this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
9862
+ return false;
9863
+ }
9864
+ ++segmentIndex;
9865
+ }
9866
+ // All remaining segments need to represent the prefix of the target's
9867
+ // orderBy.
9868
+ for (; segmentIndex < segments.length; ++segmentIndex) {
9869
+ const segment = segments[segmentIndex];
9870
+ if (orderBysIndex >= this.orderBys.length ||
9871
+ !this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
9872
+ return false;
9873
+ }
9874
+ }
9875
+ return true;
9876
+ }
9877
+ hasMatchingEqualityFilter(segment) {
9878
+ for (const filter of this.equalityFilters) {
9879
+ if (this.matchesFilter(filter, segment)) {
9880
+ return true;
9881
+ }
9882
+ }
9883
+ return false;
9884
+ }
9885
+ matchesFilter(filter, segment) {
9886
+ if (filter === undefined || !filter.field.isEqual(segment.fieldPath)) {
9887
+ return false;
9888
+ }
9889
+ const isArrayOperator = filter.op === "array-contains" /* ARRAY_CONTAINS */ ||
9890
+ filter.op === "array-contains-any" /* ARRAY_CONTAINS_ANY */;
9891
+ return (segment.kind === 2 /* CONTAINS */) === isArrayOperator;
9892
+ }
9893
+ matchesOrderBy(orderBy, segment) {
9894
+ if (!orderBy.field.isEqual(segment.fieldPath)) {
9895
+ return false;
9896
+ }
9897
+ return ((segment.kind === 0 /* ASCENDING */ &&
9898
+ orderBy.dir === "asc" /* ASCENDING */) ||
9899
+ (segment.kind === 1 /* DESCENDING */ &&
9900
+ orderBy.dir === "desc" /* DESCENDING */));
9901
+ }
9902
+ }
9903
+
9723
9904
  /**
9724
9905
  * @license
9725
9906
  * Copyright 2019 Google LLC
@@ -9758,9 +9939,9 @@ class MemoryIndexManager {
9758
9939
  // Field indices are not supported with memory persistence.
9759
9940
  return PersistencePromise.resolve();
9760
9941
  }
9761
- getDocumentsMatchingTarget(transaction, fieldIndex, target) {
9942
+ getDocumentsMatchingTarget(transaction, target) {
9762
9943
  // Field indices are not supported with memory persistence.
9763
- return PersistencePromise.resolve(documentKeySet());
9944
+ return PersistencePromise.resolve(null);
9764
9945
  }
9765
9946
  getFieldIndex(transaction, target) {
9766
9947
  // Field indices are not supported with memory persistence.
@@ -9832,6 +10013,7 @@ class MemoryCollectionParentIndex {
9832
10013
  * limitations under the License.
9833
10014
  */
9834
10015
  const LOG_TAG$f = 'IndexedDbIndexManager';
10016
+ const EMPTY_VALUE = new Uint8Array(0);
9835
10017
  /**
9836
10018
  * A persisted implementation of IndexManager.
9837
10019
  *
@@ -9849,6 +10031,11 @@ class IndexedDbIndexManager {
9849
10031
  * used to satisfy reads.
9850
10032
  */
9851
10033
  this.collectionParentsCache = new MemoryCollectionParentIndex();
10034
+ /**
10035
+ * Maps from a target to its equivalent list of sub-targets. Each sub-target
10036
+ * contains only one term from the target's disjunctive normal form (DNF).
10037
+ */
10038
+ this.targetToDnfSubTargets = new ObjectMap(t => canonifyTarget(t), (l, r) => targetEquals(l, r));
9852
10039
  this.uid = user.uid || '';
9853
10040
  }
9854
10041
  /**
@@ -9917,13 +10104,111 @@ class IndexedDbIndexManager {
9917
10104
  /*lowerOpen=*/ false,
9918
10105
  /*upperOpen=*/ true)));
9919
10106
  }
9920
- getDocumentsMatchingTarget(transaction, fieldIndex, target) {
9921
- // TODO(indexing): Implement
9922
- return PersistencePromise.resolve(documentKeySet());
10107
+ getDocumentsMatchingTarget(transaction, target) {
10108
+ const indexEntries = indexEntriesStore(transaction);
10109
+ let canServeTarget = true;
10110
+ const indexes = new Map();
10111
+ return PersistencePromise.forEach(this.getSubTargets(target), (subTarget) => {
10112
+ return this.getFieldIndex(transaction, subTarget).next(index => {
10113
+ canServeTarget && (canServeTarget = !!index);
10114
+ indexes.set(subTarget, index);
10115
+ });
10116
+ }).next(() => {
10117
+ if (!canServeTarget) {
10118
+ return PersistencePromise.resolve(null);
10119
+ }
10120
+ else {
10121
+ let result = documentKeySet();
10122
+ return PersistencePromise.forEach(indexes, (index, subTarget) => {
10123
+ logDebug(LOG_TAG$f, `Using index ${fieldIndexToString(index)} to execute ${canonifyTarget(target)}`);
10124
+ const arrayValues = targetGetArrayValues(subTarget, index);
10125
+ const notInValues = targetGetNotInValues(subTarget, index);
10126
+ const lowerBound = targetGetLowerBound(subTarget, index);
10127
+ const upperBound = targetGetUpperBound(subTarget, index);
10128
+ const lowerBoundEncoded = this.encodeBound(index, subTarget, lowerBound);
10129
+ const upperBoundEncoded = this.encodeBound(index, subTarget, upperBound);
10130
+ const notInEncoded = this.encodeValues(index, subTarget, notInValues);
10131
+ const indexRanges = this.generateIndexRanges(index.indexId, arrayValues, lowerBoundEncoded, !!lowerBound && lowerBound.inclusive, upperBoundEncoded, !!upperBound && upperBound.inclusive, notInEncoded);
10132
+ return PersistencePromise.forEach(indexRanges, (indexRange) => {
10133
+ return indexEntries
10134
+ .loadFirst(indexRange, target.limit)
10135
+ .next(entries => {
10136
+ entries.forEach(entry => {
10137
+ result = result.add(new DocumentKey(decodeResourcePath(entry.documentKey)));
10138
+ });
10139
+ });
10140
+ });
10141
+ }).next(() => result);
10142
+ }
10143
+ });
10144
+ }
10145
+ getSubTargets(target) {
10146
+ let subTargets = this.targetToDnfSubTargets.get(target);
10147
+ if (subTargets) {
10148
+ return subTargets;
10149
+ }
10150
+ // TODO(orquery): Implement DNF transform
10151
+ subTargets = [target];
10152
+ this.targetToDnfSubTargets.set(target, subTargets);
10153
+ return subTargets;
10154
+ }
10155
+ /**
10156
+ * Constructs a key range query on `DbIndexEntryStore` that unions all
10157
+ * bounds.
10158
+ */
10159
+ generateIndexRanges(indexId, arrayValues, lowerBounds, lowerBoundInclusive, upperBounds, upperBoundInclusive, notInValues) {
10160
+ // The number of total index scans we union together. This is similar to a
10161
+ // distributed normal form, but adapted for array values. We create a single
10162
+ // index range per value in an ARRAY_CONTAINS or ARRAY_CONTAINS_ANY filter
10163
+ // combined with the values from the query bounds.
10164
+ const totalScans = (arrayValues != null ? arrayValues.length : 1) *
10165
+ Math.max(lowerBounds != null ? lowerBounds.length : 1, upperBounds != null ? upperBounds.length : 1);
10166
+ const scansPerArrayElement = totalScans / (arrayValues != null ? arrayValues.length : 1);
10167
+ const indexRanges = [];
10168
+ for (let i = 0; i < totalScans; ++i) {
10169
+ const arrayValue = arrayValues
10170
+ ? this.encodeSingleElement(arrayValues[i / scansPerArrayElement])
10171
+ : EMPTY_VALUE;
10172
+ const lowerBound = lowerBounds
10173
+ ? this.generateLowerBound(indexId, arrayValue, lowerBounds[i % scansPerArrayElement], lowerBoundInclusive)
10174
+ : this.generateEmptyBound(indexId);
10175
+ const upperBound = upperBounds
10176
+ ? this.generateUpperBound(indexId, arrayValue, upperBounds[i % scansPerArrayElement], upperBoundInclusive)
10177
+ : this.generateEmptyBound(indexId + 1);
10178
+ indexRanges.push(...this.createRange(lowerBound, upperBound, notInValues.map((notIn // make non-nullable
10179
+ ) => this.generateLowerBound(indexId, arrayValue, notIn,
10180
+ /* inclusive= */ true))));
10181
+ }
10182
+ return indexRanges;
10183
+ }
10184
+ /** Generates the lower bound for `arrayValue` and `directionalValue`. */
10185
+ generateLowerBound(indexId, arrayValue, directionalValue, inclusive) {
10186
+ const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
10187
+ return inclusive ? entry : entry.successor();
10188
+ }
10189
+ /** Generates the upper bound for `arrayValue` and `directionalValue`. */
10190
+ generateUpperBound(indexId, arrayValue, directionalValue, inclusive) {
10191
+ const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
10192
+ return inclusive ? entry.successor() : entry;
10193
+ }
10194
+ /**
10195
+ * Generates an empty bound that scopes the index scan to the current index
10196
+ * and user.
10197
+ */
10198
+ generateEmptyBound(indexId) {
10199
+ return new IndexEntry(indexId, DocumentKey.empty(), EMPTY_VALUE, EMPTY_VALUE);
9923
10200
  }
9924
10201
  getFieldIndex(transaction, target) {
9925
- // TODO(indexing): Implement
9926
- return PersistencePromise.resolve(null);
10202
+ const targetIndexMatcher = new TargetIndexMatcher(target);
10203
+ const collectionGroup = target.collectionGroup != null
10204
+ ? target.collectionGroup
10205
+ : target.path.lastSegment();
10206
+ return this.getFieldIndexes(transaction, collectionGroup).next(indexes => {
10207
+ const matchingIndexes = indexes.filter(i => targetIndexMatcher.servedByIndex(i));
10208
+ // Return the index that matches the most number of segments.
10209
+ matchingIndexes.sort((l, r) => r.fields.length - l.fields.length);
10210
+ return matchingIndexes.length > 0 ? matchingIndexes[0] : null;
10211
+ });
9927
10212
  }
9928
10213
  /**
9929
10214
  * Returns the byte encoded form of the directional values in the field index.
@@ -9937,22 +10222,90 @@ class IndexedDbIndexManager {
9937
10222
  if (field == null) {
9938
10223
  return null;
9939
10224
  }
9940
- const directionalEncoder = encoder.forKind(segment.kind);
9941
- FirestoreIndexValueWriter.INSTANCE.writeIndexValue(field, directionalEncoder);
10225
+ const directionalEncoder = encoder.forKind(segment.kind);
10226
+ FirestoreIndexValueWriter.INSTANCE.writeIndexValue(field, directionalEncoder);
10227
+ }
10228
+ return encoder.encodedBytes();
10229
+ }
10230
+ /** Encodes a single value to the ascending index format. */
10231
+ encodeSingleElement(value) {
10232
+ const encoder = new IndexByteEncoder();
10233
+ FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, encoder.forKind(0 /* ASCENDING */));
10234
+ return encoder.encodedBytes();
10235
+ }
10236
+ /**
10237
+ * Encodes the given field values according to the specification in `target`.
10238
+ * For IN queries, a list of possible values is returned.
10239
+ */
10240
+ encodeValues(fieldIndex, target, bound) {
10241
+ if (bound === null) {
10242
+ return [];
10243
+ }
10244
+ let encoders = [];
10245
+ encoders.push(new IndexByteEncoder());
10246
+ let boundIdx = 0;
10247
+ for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
10248
+ const value = bound[boundIdx++];
10249
+ for (const encoder of encoders) {
10250
+ if (this.isInFilter(target, segment.fieldPath) && isArray(value)) {
10251
+ encoders = this.expandIndexValues(encoders, segment, value);
10252
+ }
10253
+ else {
10254
+ const directionalEncoder = encoder.forKind(segment.kind);
10255
+ FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, directionalEncoder);
10256
+ }
10257
+ }
10258
+ }
10259
+ return this.getEncodedBytes(encoders);
10260
+ }
10261
+ /**
10262
+ * Encodes the given bounds according to the specification in `target`. For IN
10263
+ * queries, a list of possible values is returned.
10264
+ */
10265
+ encodeBound(fieldIndex, target, bound) {
10266
+ if (bound == null) {
10267
+ return null;
10268
+ }
10269
+ return this.encodeValues(fieldIndex, target, bound.position);
10270
+ }
10271
+ /** Returns the byte representation for the provided encoders. */
10272
+ getEncodedBytes(encoders) {
10273
+ const result = [];
10274
+ for (let i = 0; i < encoders.length; ++i) {
10275
+ result[i] = encoders[i].encodedBytes();
10276
+ }
10277
+ return result;
10278
+ }
10279
+ /**
10280
+ * Creates a separate encoder for each element of an array.
10281
+ *
10282
+ * The method appends each value to all existing encoders (e.g. filter("a",
10283
+ * "==", "a1").filter("b", "in", ["b1", "b2"]) becomes ["a1,b1", "a1,b2"]). A
10284
+ * list of new encoders is returned.
10285
+ */
10286
+ expandIndexValues(encoders, segment, value) {
10287
+ const prefixes = [...encoders];
10288
+ const results = [];
10289
+ for (const arrayElement of value.arrayValue.values || []) {
10290
+ for (const prefix of prefixes) {
10291
+ const clonedEncoder = new IndexByteEncoder();
10292
+ clonedEncoder.seed(prefix.encodedBytes());
10293
+ FirestoreIndexValueWriter.INSTANCE.writeIndexValue(arrayElement, clonedEncoder.forKind(segment.kind));
10294
+ results.push(clonedEncoder);
10295
+ }
9942
10296
  }
9943
- return encoder.encodedBytes();
10297
+ return results;
9944
10298
  }
9945
- /** Encodes a single value to the ascending index format. */
9946
- encodeSingleElement(value) {
9947
- const encoder = new IndexByteEncoder();
9948
- FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, encoder.forKind(0 /* ASCENDING */));
9949
- return encoder.encodedBytes();
10299
+ isInFilter(target, fieldPath) {
10300
+ return !!target.filters.find(f => f instanceof FieldFilter &&
10301
+ f.field.isEqual(fieldPath) &&
10302
+ (f.op === "in" /* IN */ || f.op === "not-in" /* NOT_IN */));
9950
10303
  }
9951
10304
  getFieldIndexes(transaction, collectionGroup) {
9952
10305
  const indexes = indexConfigurationStore(transaction);
9953
10306
  const states = indexStateStore(transaction);
9954
10307
  return (collectionGroup
9955
- ? indexes.loadAll(DbIndexConfiguration.collectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
10308
+ ? indexes.loadAll(DbIndexConfigurationCollectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
9956
10309
  : indexes.loadAll()).next(indexConfigs => {
9957
10310
  const result = [];
9958
10311
  return PersistencePromise.forEach(indexConfigs, (indexConfig) => {
@@ -9969,7 +10322,12 @@ class IndexedDbIndexManager {
9969
10322
  if (indexes.length === 0) {
9970
10323
  return null;
9971
10324
  }
9972
- indexes.sort((l, r) => l.indexState.sequenceNumber - r.indexState.sequenceNumber);
10325
+ indexes.sort((l, r) => {
10326
+ const cmp = l.indexState.sequenceNumber - r.indexState.sequenceNumber;
10327
+ return cmp !== 0
10328
+ ? cmp
10329
+ : primitiveComparator(l.collectionGroup, r.collectionGroup);
10330
+ });
9973
10331
  return indexes[0].collectionGroup;
9974
10332
  });
9975
10333
  }
@@ -9977,7 +10335,7 @@ class IndexedDbIndexManager {
9977
10335
  const indexes = indexConfigurationStore(transaction);
9978
10336
  const states = indexStateStore(transaction);
9979
10337
  return this.getNextSequenceNumber(transaction).next(nextSequenceNumber => indexes
9980
- .loadAll(DbIndexConfiguration.collectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
10338
+ .loadAll(DbIndexConfigurationCollectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
9981
10339
  .next(configs => PersistencePromise.forEach(configs, (config) => states.put(toDbIndexState(config.indexId, this.user, nextSequenceNumber, offset)))));
9982
10340
  }
9983
10341
  updateIndexEntries(transaction, documents) {
@@ -10008,7 +10366,13 @@ class IndexedDbIndexManager {
10008
10366
  }
10009
10367
  addIndexEntry(transaction, document, indexEntry) {
10010
10368
  const indexEntries = indexEntriesStore(transaction);
10011
- return indexEntries.put(new DbIndexEntry(indexEntry.indexId, this.uid, indexEntry.arrayValue, indexEntry.directionalValue, encodeResourcePath(document.key.path)));
10369
+ return indexEntries.put({
10370
+ indexId: indexEntry.indexId,
10371
+ uid: this.uid,
10372
+ arrayValue: indexEntry.arrayValue,
10373
+ directionalValue: indexEntry.directionalValue,
10374
+ documentKey: encodeResourcePath(document.key.path)
10375
+ });
10012
10376
  }
10013
10377
  deleteIndexEntry(transaction, document, indexEntry) {
10014
10378
  const indexEntries = indexEntriesStore(transaction);
@@ -10025,7 +10389,7 @@ class IndexedDbIndexManager {
10025
10389
  let results = new SortedSet(indexEntryComparator);
10026
10390
  return indexEntries
10027
10391
  .iterate({
10028
- index: DbIndexEntry.documentKeyIndex,
10392
+ index: DbIndexEntryDocumentKeyIndex,
10029
10393
  range: IDBKeyRange.only([
10030
10394
  fieldIndex.indexId,
10031
10395
  this.uid,
@@ -10053,7 +10417,7 @@ class IndexedDbIndexManager {
10053
10417
  }
10054
10418
  }
10055
10419
  else {
10056
- results = results.add(new IndexEntry(fieldIndex.indexId, document.key, new Uint8Array(), directionalValue));
10420
+ results = results.add(new IndexEntry(fieldIndex.indexId, document.key, EMPTY_VALUE, directionalValue));
10057
10421
  }
10058
10422
  return results;
10059
10423
  }
@@ -10079,7 +10443,7 @@ class IndexedDbIndexManager {
10079
10443
  const states = indexStateStore(transaction);
10080
10444
  return states
10081
10445
  .iterate({
10082
- index: DbIndexState.sequenceNumberIndex,
10446
+ index: DbIndexStateSequenceNumberIndex,
10083
10447
  reverse: true,
10084
10448
  range: IDBKeyRange.upperBound([this.uid, Number.MAX_SAFE_INTEGER])
10085
10449
  }, (_, state, controller) => {
@@ -10088,31 +10452,81 @@ class IndexedDbIndexManager {
10088
10452
  })
10089
10453
  .next(() => nextSequenceNumber);
10090
10454
  }
10455
+ /**
10456
+ * Returns a new set of IDB ranges that splits the existing range and excludes
10457
+ * any values that match the `notInValue` from these ranges. As an example,
10458
+ * '[foo > 2 && foo != 3]` becomes `[foo > 2 && < 3, foo > 3]`.
10459
+ */
10460
+ createRange(lower, upper, notInValues) {
10461
+ // The notIb values need to be sorted and unique so that we can return a
10462
+ // sorted set of non-overlapping ranges.
10463
+ notInValues = notInValues
10464
+ .sort((l, r) => indexEntryComparator(l, r))
10465
+ .filter((el, i, values) => !i || indexEntryComparator(el, values[i - 1]) !== 0);
10466
+ const bounds = [];
10467
+ bounds.push(lower);
10468
+ for (const notInValue of notInValues) {
10469
+ const cmpToLower = indexEntryComparator(notInValue, lower);
10470
+ const cmpToUpper = indexEntryComparator(notInValue, upper);
10471
+ if (cmpToLower === 0) {
10472
+ // `notInValue` is the lower bound. We therefore need to raise the bound
10473
+ // to the next value.
10474
+ bounds[0] = lower.successor();
10475
+ }
10476
+ else if (cmpToLower > 0 && cmpToUpper < 0) {
10477
+ // `notInValue` is in the middle of the range
10478
+ bounds.push(notInValue);
10479
+ bounds.push(notInValue.successor());
10480
+ }
10481
+ else if (cmpToUpper > 0) {
10482
+ // `notInValue` (and all following values) are out of the range
10483
+ break;
10484
+ }
10485
+ }
10486
+ bounds.push(upper);
10487
+ const ranges = [];
10488
+ for (let i = 0; i < bounds.length; i += 2) {
10489
+ ranges.push(IDBKeyRange.bound([
10490
+ bounds[i].indexId,
10491
+ this.uid,
10492
+ bounds[i].arrayValue,
10493
+ bounds[i].directionalValue,
10494
+ ''
10495
+ ], [
10496
+ bounds[i + 1].indexId,
10497
+ this.uid,
10498
+ bounds[i + 1].arrayValue,
10499
+ bounds[i + 1].directionalValue,
10500
+ ''
10501
+ ]));
10502
+ }
10503
+ return ranges;
10504
+ }
10091
10505
  }
10092
10506
  /**
10093
10507
  * Helper to get a typed SimpleDbStore for the collectionParents
10094
10508
  * document store.
10095
10509
  */
10096
10510
  function collectionParentsStore(txn) {
10097
- return getStore(txn, DbCollectionParent.store);
10511
+ return getStore(txn, DbCollectionParentStore);
10098
10512
  }
10099
10513
  /**
10100
10514
  * Helper to get a typed SimpleDbStore for the index entry object store.
10101
10515
  */
10102
10516
  function indexEntriesStore(txn) {
10103
- return getStore(txn, DbIndexEntry.store);
10517
+ return getStore(txn, DbIndexEntryStore);
10104
10518
  }
10105
10519
  /**
10106
10520
  * Helper to get a typed SimpleDbStore for the index configuration object store.
10107
10521
  */
10108
10522
  function indexConfigurationStore(txn) {
10109
- return getStore(txn, DbIndexConfiguration.store);
10523
+ return getStore(txn, DbIndexConfigurationStore);
10110
10524
  }
10111
10525
  /**
10112
10526
  * Helper to get a typed SimpleDbStore for the index state object store.
10113
10527
  */
10114
10528
  function indexStateStore(txn) {
10115
- return getStore(txn, DbIndexState.store);
10529
+ return getStore(txn, DbIndexStateStore);
10116
10530
  }
10117
10531
 
10118
10532
  /**
@@ -10136,8 +10550,8 @@ function indexStateStore(txn) {
10136
10550
  * @returns A PersistencePromise of the document mutations that were removed.
10137
10551
  */
10138
10552
  function removeMutationBatch(txn, userId, batch) {
10139
- const mutationStore = txn.store(DbMutationBatch.store);
10140
- const indexTxn = txn.store(DbDocumentMutation.store);
10553
+ const mutationStore = txn.store(DbMutationBatchStore);
10554
+ const indexTxn = txn.store(DbDocumentMutationStore);
10141
10555
  const promises = [];
10142
10556
  const range = IDBKeyRange.only(batch.batchId);
10143
10557
  let numDeleted = 0;
@@ -10150,7 +10564,7 @@ function removeMutationBatch(txn, userId, batch) {
10150
10564
  }));
10151
10565
  const removedDocuments = [];
10152
10566
  for (const mutation of batch.mutations) {
10153
- const indexKey = DbDocumentMutation.key(userId, mutation.key.path, batch.batchId);
10567
+ const indexKey = newDbDocumentMutationKey(userId, mutation.key.path, batch.batchId);
10154
10568
  promises.push(indexTxn.delete(indexKey));
10155
10569
  removedDocuments.push(mutation.key);
10156
10570
  }
@@ -10239,7 +10653,7 @@ class IndexedDbMutationQueue {
10239
10653
  let empty = true;
10240
10654
  const range = IDBKeyRange.bound([this.userId, Number.NEGATIVE_INFINITY], [this.userId, Number.POSITIVE_INFINITY]);
10241
10655
  return mutationsStore(transaction)
10242
- .iterate({ index: DbMutationBatch.userMutationsIndex, range }, (key, value, control) => {
10656
+ .iterate({ index: DbMutationBatchUserMutationsIndex, range }, (key, value, control) => {
10243
10657
  empty = false;
10244
10658
  control.done();
10245
10659
  })
@@ -10264,10 +10678,10 @@ class IndexedDbMutationQueue {
10264
10678
  const promises = [];
10265
10679
  let collectionParents = new SortedSet((l, r) => primitiveComparator(l.canonicalString(), r.canonicalString()));
10266
10680
  for (const mutation of mutations) {
10267
- const indexKey = DbDocumentMutation.key(this.userId, mutation.key.path, batchId);
10681
+ const indexKey = newDbDocumentMutationKey(this.userId, mutation.key.path, batchId);
10268
10682
  collectionParents = collectionParents.add(mutation.key.path.popLast());
10269
10683
  promises.push(mutationStore.put(dbBatch));
10270
- promises.push(documentStore.put(indexKey, DbDocumentMutation.PLACEHOLDER));
10684
+ promises.push(documentStore.put(indexKey, DbDocumentMutationPlaceholder));
10271
10685
  }
10272
10686
  collectionParents.forEach(parent => {
10273
10687
  promises.push(this.indexManager.addToCollectionParentIndex(transaction, parent));
@@ -10318,7 +10732,7 @@ class IndexedDbMutationQueue {
10318
10732
  const range = IDBKeyRange.lowerBound([this.userId, nextBatchId]);
10319
10733
  let foundBatch = null;
10320
10734
  return mutationsStore(transaction)
10321
- .iterate({ index: DbMutationBatch.userMutationsIndex, range }, (key, dbBatch, control) => {
10735
+ .iterate({ index: DbMutationBatchUserMutationsIndex, range }, (key, dbBatch, control) => {
10322
10736
  if (dbBatch.userId === this.userId) {
10323
10737
  hardAssert(dbBatch.batchId >= nextBatchId);
10324
10738
  foundBatch = fromDbMutationBatch(this.serializer, dbBatch);
@@ -10334,7 +10748,7 @@ class IndexedDbMutationQueue {
10334
10748
  ]);
10335
10749
  let batchId = BATCHID_UNKNOWN;
10336
10750
  return mutationsStore(transaction)
10337
- .iterate({ index: DbMutationBatch.userMutationsIndex, range, reverse: true }, (key, dbBatch, control) => {
10751
+ .iterate({ index: DbMutationBatchUserMutationsIndex, range, reverse: true }, (key, dbBatch, control) => {
10338
10752
  batchId = dbBatch.batchId;
10339
10753
  control.done();
10340
10754
  })
@@ -10343,13 +10757,13 @@ class IndexedDbMutationQueue {
10343
10757
  getAllMutationBatches(transaction) {
10344
10758
  const range = IDBKeyRange.bound([this.userId, BATCHID_UNKNOWN], [this.userId, Number.POSITIVE_INFINITY]);
10345
10759
  return mutationsStore(transaction)
10346
- .loadAll(DbMutationBatch.userMutationsIndex, range)
10760
+ .loadAll(DbMutationBatchUserMutationsIndex, range)
10347
10761
  .next(dbBatches => dbBatches.map(dbBatch => fromDbMutationBatch(this.serializer, dbBatch)));
10348
10762
  }
10349
10763
  getAllMutationBatchesAffectingDocumentKey(transaction, documentKey) {
10350
10764
  // Scan the document-mutation index starting with a prefix starting with
10351
10765
  // the given documentKey.
10352
- const indexPrefix = DbDocumentMutation.prefixForPath(this.userId, documentKey.path);
10766
+ const indexPrefix = newDbDocumentMutationPrefixForPath(this.userId, documentKey.path);
10353
10767
  const indexStart = IDBKeyRange.lowerBound(indexPrefix);
10354
10768
  const results = [];
10355
10769
  return documentMutationsStore(transaction)
@@ -10384,7 +10798,7 @@ class IndexedDbMutationQueue {
10384
10798
  let uniqueBatchIDs = new SortedSet(primitiveComparator);
10385
10799
  const promises = [];
10386
10800
  documentKeys.forEach(documentKey => {
10387
- const indexStart = DbDocumentMutation.prefixForPath(this.userId, documentKey.path);
10801
+ const indexStart = newDbDocumentMutationPrefixForPath(this.userId, documentKey.path);
10388
10802
  const range = IDBKeyRange.lowerBound(indexStart);
10389
10803
  const promise = documentMutationsStore(transaction).iterate({ range }, (indexKey, _, control) => {
10390
10804
  const [userID, encodedPath, batchID] = indexKey;
@@ -10420,7 +10834,7 @@ class IndexedDbMutationQueue {
10420
10834
  // Since we don't yet index the actual properties in the mutations, our
10421
10835
  // current approach is to just return all mutation batches that affect
10422
10836
  // documents in the collection being queried.
10423
- const indexPrefix = DbDocumentMutation.prefixForPath(this.userId, queryPath);
10837
+ const indexPrefix = newDbDocumentMutationPrefixForPath(this.userId, queryPath);
10424
10838
  const indexStart = IDBKeyRange.lowerBound(indexPrefix);
10425
10839
  // Collect up unique batchIDs encountered during a scan of the index. Use a
10426
10840
  // SortedSet to accumulate batch IDs so they can be traversed in order in a
@@ -10492,7 +10906,7 @@ class IndexedDbMutationQueue {
10492
10906
  }
10493
10907
  // Verify that there are no entries in the documentMutations index if
10494
10908
  // the queue is empty.
10495
- const startRange = IDBKeyRange.lowerBound(DbDocumentMutation.prefixForUser(this.userId));
10909
+ const startRange = IDBKeyRange.lowerBound(newDbDocumentMutationPrefixForUser(this.userId));
10496
10910
  const danglingMutationReferences = [];
10497
10911
  return documentMutationsStore(txn)
10498
10912
  .iterate({ range: startRange }, (key, _, control) => {
@@ -10520,9 +10934,11 @@ class IndexedDbMutationQueue {
10520
10934
  return mutationQueuesStore(transaction)
10521
10935
  .get(this.userId)
10522
10936
  .next((metadata) => {
10523
- return (metadata ||
10524
- new DbMutationQueue(this.userId, BATCHID_UNKNOWN,
10525
- /*lastStreamToken=*/ ''));
10937
+ return (metadata || {
10938
+ userId: this.userId,
10939
+ lastAcknowledgedBatchId: BATCHID_UNKNOWN,
10940
+ lastStreamToken: ''
10941
+ });
10526
10942
  });
10527
10943
  }
10528
10944
  }
@@ -10531,7 +10947,7 @@ class IndexedDbMutationQueue {
10531
10947
  * mutation for the given key.
10532
10948
  */
10533
10949
  function mutationQueueContainsKey(txn, userId, key) {
10534
- const indexKey = DbDocumentMutation.prefixForPath(userId, key.path);
10950
+ const indexKey = newDbDocumentMutationPrefixForPath(userId, key.path);
10535
10951
  const encodedPath = indexKey[1];
10536
10952
  const startRange = IDBKeyRange.lowerBound(indexKey);
10537
10953
  let containsKey = false;
@@ -10563,19 +10979,19 @@ function mutationQueuesContainKey(txn, docKey) {
10563
10979
  * Helper to get a typed SimpleDbStore for the mutations object store.
10564
10980
  */
10565
10981
  function mutationsStore(txn) {
10566
- return getStore(txn, DbMutationBatch.store);
10982
+ return getStore(txn, DbMutationBatchStore);
10567
10983
  }
10568
10984
  /**
10569
10985
  * Helper to get a typed SimpleDbStore for the mutationQueues object store.
10570
10986
  */
10571
10987
  function documentMutationsStore(txn) {
10572
- return getStore(txn, DbDocumentMutation.store);
10988
+ return getStore(txn, DbDocumentMutationStore);
10573
10989
  }
10574
10990
  /**
10575
10991
  * Helper to get a typed SimpleDbStore for the mutationQueues object store.
10576
10992
  */
10577
10993
  function mutationQueuesStore(txn) {
10578
- return getStore(txn, DbMutationQueue.store);
10994
+ return getStore(txn, DbMutationQueueStore);
10579
10995
  }
10580
10996
 
10581
10997
  /**
@@ -10739,14 +11155,14 @@ class IndexedDbTargetCache {
10739
11155
  }
10740
11156
  retrieveMetadata(transaction) {
10741
11157
  return globalTargetStore(transaction)
10742
- .get(DbTargetGlobal.key)
11158
+ .get(DbTargetGlobalKey)
10743
11159
  .next(metadata => {
10744
11160
  hardAssert(metadata !== null);
10745
11161
  return metadata;
10746
11162
  });
10747
11163
  }
10748
11164
  saveMetadata(transaction, metadata) {
10749
- return globalTargetStore(transaction).put(DbTargetGlobal.key, metadata);
11165
+ return globalTargetStore(transaction).put(DbTargetGlobalKey, metadata);
10750
11166
  }
10751
11167
  saveTargetData(transaction, targetData) {
10752
11168
  return targetsStore(transaction).put(toDbTarget(this.serializer, targetData));
@@ -10779,7 +11195,7 @@ class IndexedDbTargetCache {
10779
11195
  const range = IDBKeyRange.bound([canonicalId, Number.NEGATIVE_INFINITY], [canonicalId, Number.POSITIVE_INFINITY]);
10780
11196
  let result = null;
10781
11197
  return targetsStore(transaction)
10782
- .iterate({ range, index: DbTarget.queryTargetsIndexName }, (key, value, control) => {
11198
+ .iterate({ range, index: DbTargetQueryTargetsIndexName }, (key, value, control) => {
10783
11199
  const found = fromDbTarget(value);
10784
11200
  // After finding a potential match, check that the target is
10785
11201
  // actually equal to the requested target.
@@ -10797,7 +11213,7 @@ class IndexedDbTargetCache {
10797
11213
  const store = documentTargetStore(txn);
10798
11214
  keys.forEach(key => {
10799
11215
  const path = encodeResourcePath(key.path);
10800
- promises.push(store.put(new DbTargetDocument(targetId, path)));
11216
+ promises.push(store.put({ targetId, path }));
10801
11217
  promises.push(this.referenceDelegate.addReference(txn, targetId, key));
10802
11218
  });
10803
11219
  return PersistencePromise.waitFor(promises);
@@ -10843,7 +11259,7 @@ class IndexedDbTargetCache {
10843
11259
  let count = 0;
10844
11260
  return documentTargetStore(txn)
10845
11261
  .iterate({
10846
- index: DbTargetDocument.documentTargetsIndex,
11262
+ index: DbTargetDocumentDocumentTargetsIndex,
10847
11263
  keysOnly: true,
10848
11264
  range
10849
11265
  }, ([targetId, path], _, control) => {
@@ -10882,19 +11298,19 @@ class IndexedDbTargetCache {
10882
11298
  * Helper to get a typed SimpleDbStore for the queries object store.
10883
11299
  */
10884
11300
  function targetsStore(txn) {
10885
- return getStore(txn, DbTarget.store);
11301
+ return getStore(txn, DbTargetStore);
10886
11302
  }
10887
11303
  /**
10888
11304
  * Helper to get a typed SimpleDbStore for the target globals object store.
10889
11305
  */
10890
11306
  function globalTargetStore(txn) {
10891
- return getStore(txn, DbTargetGlobal.store);
11307
+ return getStore(txn, DbTargetGlobalStore);
10892
11308
  }
10893
11309
  /**
10894
11310
  * Helper to get a typed SimpleDbStore for the document target object store.
10895
11311
  */
10896
11312
  function documentTargetStore(txn) {
10897
- return getStore(txn, DbTargetDocument.store);
11313
+ return getStore(txn, DbTargetDocumentStore);
10898
11314
  }
10899
11315
 
10900
11316
  /**
@@ -11308,7 +11724,7 @@ class IndexedDbLruDelegateImpl {
11308
11724
  let nextPath;
11309
11725
  return store
11310
11726
  .iterate({
11311
- index: DbTargetDocument.documentTargetsIndex
11727
+ index: DbTargetDocumentDocumentTargetsIndex
11312
11728
  }, ([targetId, docKey], { path, sequenceNumber }) => {
11313
11729
  if (targetId === 0) {
11314
11730
  // if nextToReport is valid, report it, this is a new key so the
@@ -11350,113 +11766,12 @@ function sentinelKey$1(key) {
11350
11766
  * store.
11351
11767
  */
11352
11768
  function sentinelRow(key, sequenceNumber) {
11353
- return new DbTargetDocument(0, encodeResourcePath(key.path), sequenceNumber);
11769
+ return { targetId: 0, path: encodeResourcePath(key.path), sequenceNumber };
11354
11770
  }
11355
11771
  function writeSentinelKey(txn, key) {
11356
11772
  return documentTargetStore(txn).put(sentinelRow(key, txn.currentSequenceNumber));
11357
11773
  }
11358
11774
 
11359
- /**
11360
- * @license
11361
- * Copyright 2017 Google LLC
11362
- *
11363
- * Licensed under the Apache License, Version 2.0 (the "License");
11364
- * you may not use this file except in compliance with the License.
11365
- * You may obtain a copy of the License at
11366
- *
11367
- * http://www.apache.org/licenses/LICENSE-2.0
11368
- *
11369
- * Unless required by applicable law or agreed to in writing, software
11370
- * distributed under the License is distributed on an "AS IS" BASIS,
11371
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11372
- * See the License for the specific language governing permissions and
11373
- * limitations under the License.
11374
- */
11375
- /**
11376
- * A map implementation that uses objects as keys. Objects must have an
11377
- * associated equals function and must be immutable. Entries in the map are
11378
- * stored together with the key being produced from the mapKeyFn. This map
11379
- * automatically handles collisions of keys.
11380
- */
11381
- class ObjectMap {
11382
- constructor(mapKeyFn, equalsFn) {
11383
- this.mapKeyFn = mapKeyFn;
11384
- this.equalsFn = equalsFn;
11385
- /**
11386
- * The inner map for a key/value pair. Due to the possibility of collisions we
11387
- * keep a list of entries that we do a linear search through to find an actual
11388
- * match. Note that collisions should be rare, so we still expect near
11389
- * constant time lookups in practice.
11390
- */
11391
- this.inner = {};
11392
- }
11393
- /** Get a value for this key, or undefined if it does not exist. */
11394
- get(key) {
11395
- const id = this.mapKeyFn(key);
11396
- const matches = this.inner[id];
11397
- if (matches === undefined) {
11398
- return undefined;
11399
- }
11400
- for (const [otherKey, value] of matches) {
11401
- if (this.equalsFn(otherKey, key)) {
11402
- return value;
11403
- }
11404
- }
11405
- return undefined;
11406
- }
11407
- has(key) {
11408
- return this.get(key) !== undefined;
11409
- }
11410
- /** Put this key and value in the map. */
11411
- set(key, value) {
11412
- const id = this.mapKeyFn(key);
11413
- const matches = this.inner[id];
11414
- if (matches === undefined) {
11415
- this.inner[id] = [[key, value]];
11416
- return;
11417
- }
11418
- for (let i = 0; i < matches.length; i++) {
11419
- if (this.equalsFn(matches[i][0], key)) {
11420
- matches[i] = [key, value];
11421
- return;
11422
- }
11423
- }
11424
- matches.push([key, value]);
11425
- }
11426
- /**
11427
- * Remove this key from the map. Returns a boolean if anything was deleted.
11428
- */
11429
- delete(key) {
11430
- const id = this.mapKeyFn(key);
11431
- const matches = this.inner[id];
11432
- if (matches === undefined) {
11433
- return false;
11434
- }
11435
- for (let i = 0; i < matches.length; i++) {
11436
- if (this.equalsFn(matches[i][0], key)) {
11437
- if (matches.length === 1) {
11438
- delete this.inner[id];
11439
- }
11440
- else {
11441
- matches.splice(i, 1);
11442
- }
11443
- return true;
11444
- }
11445
- }
11446
- return false;
11447
- }
11448
- forEach(fn) {
11449
- forEach(this.inner, (_, entries) => {
11450
- for (const [k, v] of entries) {
11451
- fn(k, v);
11452
- }
11453
- });
11454
- }
11455
- isEmpty() {
11456
- return isEmpty(this.inner);
11457
- }
11458
- }
11459
-
11460
11775
  /**
11461
11776
  * @license
11462
11777
  * Copyright 2017 Google LLC
@@ -11596,7 +11911,7 @@ class IndexedDbRemoteDocumentCacheImpl {
11596
11911
  */
11597
11912
  addEntry(transaction, key, doc) {
11598
11913
  const documentStore = remoteDocumentsStore(transaction);
11599
- return documentStore.put(dbKey(key), doc);
11914
+ return documentStore.put(doc);
11600
11915
  }
11601
11916
  /**
11602
11917
  * Removes a document from the cache.
@@ -11604,10 +11919,9 @@ class IndexedDbRemoteDocumentCacheImpl {
11604
11919
  * All calls of `removeEntry` are required to go through the RemoteDocumentChangeBuffer
11605
11920
  * returned by `newChangeBuffer()` to ensure proper accounting of metadata.
11606
11921
  */
11607
- removeEntry(transaction, documentKey) {
11922
+ removeEntry(transaction, documentKey, readTime) {
11608
11923
  const store = remoteDocumentsStore(transaction);
11609
- const key = dbKey(documentKey);
11610
- return store.delete(key);
11924
+ return store.delete(dbReadTimeKey(documentKey, readTime));
11611
11925
  }
11612
11926
  /**
11613
11927
  * Updates the current cache size.
@@ -11622,11 +11936,15 @@ class IndexedDbRemoteDocumentCacheImpl {
11622
11936
  });
11623
11937
  }
11624
11938
  getEntry(transaction, documentKey) {
11939
+ let doc = MutableDocument.newInvalidDocument(documentKey);
11625
11940
  return remoteDocumentsStore(transaction)
11626
- .get(dbKey(documentKey))
11627
- .next(dbRemoteDoc => {
11628
- return this.maybeDecodeDocument(documentKey, dbRemoteDoc);
11629
- });
11941
+ .iterate({
11942
+ index: DbRemoteDocumentDocumentKeyIndex,
11943
+ range: IDBKeyRange.only(dbKey(documentKey))
11944
+ }, (_, dbRemoteDoc) => {
11945
+ doc = this.maybeDecodeDocument(documentKey, dbRemoteDoc);
11946
+ })
11947
+ .next(() => doc);
11630
11948
  }
11631
11949
  /**
11632
11950
  * Looks up an entry in the cache.
@@ -11635,15 +11953,21 @@ class IndexedDbRemoteDocumentCacheImpl {
11635
11953
  * @returns The cached document entry and its size.
11636
11954
  */
11637
11955
  getSizedEntry(transaction, documentKey) {
11956
+ let result = {
11957
+ size: 0,
11958
+ document: MutableDocument.newInvalidDocument(documentKey)
11959
+ };
11638
11960
  return remoteDocumentsStore(transaction)
11639
- .get(dbKey(documentKey))
11640
- .next(dbRemoteDoc => {
11641
- const doc = this.maybeDecodeDocument(documentKey, dbRemoteDoc);
11642
- return {
11643
- document: doc,
11961
+ .iterate({
11962
+ index: DbRemoteDocumentDocumentKeyIndex,
11963
+ range: IDBKeyRange.only(dbKey(documentKey))
11964
+ }, (_, dbRemoteDoc) => {
11965
+ result = {
11966
+ document: this.maybeDecodeDocument(documentKey, dbRemoteDoc),
11644
11967
  size: dbDocumentSize(dbRemoteDoc)
11645
11968
  };
11646
- });
11969
+ })
11970
+ .next(() => result);
11647
11971
  }
11648
11972
  getEntries(transaction, documentKeys) {
11649
11973
  let results = mutableDocumentMap();
@@ -11674,14 +11998,20 @@ class IndexedDbRemoteDocumentCacheImpl {
11674
11998
  if (documentKeys.isEmpty()) {
11675
11999
  return PersistencePromise.resolve();
11676
12000
  }
11677
- const range = IDBKeyRange.bound(documentKeys.first().path.toArray(), documentKeys.last().path.toArray());
11678
- const keyIter = documentKeys.getIterator();
12001
+ let sortedKeys = new SortedSet(dbKeyComparator);
12002
+ documentKeys.forEach(e => (sortedKeys = sortedKeys.add(e)));
12003
+ const range = IDBKeyRange.bound(dbKey(sortedKeys.first()), dbKey(sortedKeys.last()));
12004
+ const keyIter = sortedKeys.getIterator();
11679
12005
  let nextKey = keyIter.getNext();
11680
12006
  return remoteDocumentsStore(transaction)
11681
- .iterate({ range }, (potentialKeyRaw, dbRemoteDoc, control) => {
11682
- const potentialKey = DocumentKey.fromSegments(potentialKeyRaw);
12007
+ .iterate({ index: DbRemoteDocumentDocumentKeyIndex, range }, (_, dbRemoteDoc, control) => {
12008
+ const potentialKey = DocumentKey.fromSegments([
12009
+ ...dbRemoteDoc.prefixPath,
12010
+ dbRemoteDoc.collectionGroup,
12011
+ dbRemoteDoc.documentId
12012
+ ]);
11683
12013
  // Go through keys not found in cache.
11684
- while (nextKey && DocumentKey.comparator(nextKey, potentialKey) < 0) {
12014
+ while (nextKey && dbKeyComparator(nextKey, potentialKey) < 0) {
11685
12015
  callback(nextKey, null);
11686
12016
  nextKey = keyIter.getNext();
11687
12017
  }
@@ -11692,7 +12022,7 @@ class IndexedDbRemoteDocumentCacheImpl {
11692
12022
  }
11693
12023
  // Skip to the next key (if there is one).
11694
12024
  if (nextKey) {
11695
- control.skip(nextKey.path.toArray());
12025
+ control.skip(dbKey(nextKey));
11696
12026
  }
11697
12027
  else {
11698
12028
  control.done();
@@ -11707,41 +12037,44 @@ class IndexedDbRemoteDocumentCacheImpl {
11707
12037
  }
11708
12038
  });
11709
12039
  }
11710
- getAll(transaction, collection, sinceReadTime) {
11711
- let results = mutableDocumentMap();
11712
- const immediateChildrenPathLength = collection.length + 1;
11713
- const iterationOptions = {};
11714
- if (sinceReadTime.isEqual(SnapshotVersion.min())) {
11715
- // Documents are ordered by key, so we can use a prefix scan to narrow
11716
- // down the documents we need to match the query against.
11717
- const startKey = collection.toArray();
11718
- iterationOptions.range = IDBKeyRange.lowerBound(startKey);
11719
- }
11720
- else {
11721
- // Execute an index-free query and filter by read time. This is safe
11722
- // since all document changes to queries that have a
11723
- // lastLimboFreeSnapshotVersion (`sinceReadTime`) have a read time set.
11724
- const collectionKey = collection.toArray();
11725
- const readTimeKey = toDbTimestampKey(sinceReadTime);
11726
- iterationOptions.range = IDBKeyRange.lowerBound([collectionKey, readTimeKey],
11727
- /* open= */ true);
11728
- iterationOptions.index = DbRemoteDocument.collectionReadTimeIndex;
11729
- }
12040
+ getAllFromCollection(transaction, collection, offset) {
12041
+ const startKey = [
12042
+ collection.popLast().toArray(),
12043
+ collection.lastSegment(),
12044
+ toDbTimestampKey(offset.readTime),
12045
+ offset.documentKey.path.isEmpty()
12046
+ ? ''
12047
+ : offset.documentKey.path.lastSegment()
12048
+ ];
12049
+ const endKey = [
12050
+ collection.popLast().toArray(),
12051
+ collection.lastSegment(),
12052
+ [Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],
12053
+ ''
12054
+ ];
11730
12055
  return remoteDocumentsStore(transaction)
11731
- .iterate(iterationOptions, (key, dbRemoteDoc, control) => {
11732
- // The query is actually returning any path that starts with the query
11733
- // path prefix which may include documents in subcollections. For
11734
- // example, a query on 'rooms' will return rooms/abc/messages/xyx but we
11735
- // shouldn't match it. Fix this by discarding rows with document keys
11736
- // more than one segment longer than the query path.
11737
- if (key.length !== immediateChildrenPathLength) {
11738
- return;
11739
- }
11740
- const document = this.maybeDecodeDocument(DocumentKey.fromSegments(key), dbRemoteDoc);
11741
- if (collection.isPrefixOf(document.key.path)) {
12056
+ .loadAll(IDBKeyRange.bound(startKey, endKey, true))
12057
+ .next(dbRemoteDocs => {
12058
+ let results = mutableDocumentMap();
12059
+ for (const dbRemoteDoc of dbRemoteDocs) {
12060
+ const document = this.maybeDecodeDocument(DocumentKey.fromSegments(dbRemoteDoc.prefixPath.concat(dbRemoteDoc.collectionGroup, dbRemoteDoc.documentId)), dbRemoteDoc);
11742
12061
  results = results.insert(document.key, document);
11743
12062
  }
11744
- else {
12063
+ return results;
12064
+ });
12065
+ }
12066
+ getAllFromCollectionGroup(transaction, collectionGroup, offset, limit) {
12067
+ let results = mutableDocumentMap();
12068
+ const startKey = dbCollectionGroupKey(collectionGroup, offset);
12069
+ const endKey = dbCollectionGroupKey(collectionGroup, IndexOffset.max());
12070
+ return remoteDocumentsStore(transaction)
12071
+ .iterate({
12072
+ index: DbRemoteDocumentCollectionGroupIndex,
12073
+ range: IDBKeyRange.bound(startKey, endKey, true)
12074
+ }, (_, dbRemoteDoc, control) => {
12075
+ const document = this.maybeDecodeDocument(DocumentKey.fromSegments(dbRemoteDoc.prefixPath.concat(dbRemoteDoc.collectionGroup, dbRemoteDoc.documentId)), dbRemoteDoc);
12076
+ results = results.insert(document.key, document);
12077
+ if (results.size === limit) {
11745
12078
  control.done();
11746
12079
  }
11747
12080
  })
@@ -11755,14 +12088,14 @@ class IndexedDbRemoteDocumentCacheImpl {
11755
12088
  }
11756
12089
  getMetadata(txn) {
11757
12090
  return documentGlobalStore(txn)
11758
- .get(DbRemoteDocumentGlobal.key)
12091
+ .get(DbRemoteDocumentGlobalKey)
11759
12092
  .next(metadata => {
11760
12093
  hardAssert(!!metadata);
11761
12094
  return metadata;
11762
12095
  });
11763
12096
  }
11764
12097
  setMetadata(txn, metadata) {
11765
- return documentGlobalStore(txn).put(DbRemoteDocumentGlobal.key, metadata);
12098
+ return documentGlobalStore(txn).put(DbRemoteDocumentGlobalKey, metadata);
11766
12099
  }
11767
12100
  /**
11768
12101
  * Decodes `dbRemoteDoc` and returns the document (or an invalid document if
@@ -11785,50 +12118,6 @@ class IndexedDbRemoteDocumentCacheImpl {
11785
12118
  function newIndexedDbRemoteDocumentCache(serializer) {
11786
12119
  return new IndexedDbRemoteDocumentCacheImpl(serializer);
11787
12120
  }
11788
- /**
11789
- * Returns the set of documents that have changed since the specified read
11790
- * time.
11791
- */
11792
- // PORTING NOTE: This is only used for multi-tab synchronization.
11793
- function remoteDocumentCacheGetNewDocumentChanges(remoteDocumentCache, transaction, sinceReadTime) {
11794
- const remoteDocumentCacheImpl = debugCast(remoteDocumentCache);
11795
- let changedDocs = mutableDocumentMap();
11796
- let lastReadTime = toDbTimestampKey(sinceReadTime);
11797
- const documentsStore = remoteDocumentsStore(transaction);
11798
- const range = IDBKeyRange.lowerBound(lastReadTime, true);
11799
- return documentsStore
11800
- .iterate({ index: DbRemoteDocument.readTimeIndex, range }, (_, dbRemoteDoc) => {
11801
- // Unlike `getEntry()` and others, `getNewDocumentChanges()` parses
11802
- // the documents directly since we want to keep sentinel deletes.
11803
- const doc = fromDbRemoteDocument(remoteDocumentCacheImpl.serializer, dbRemoteDoc);
11804
- changedDocs = changedDocs.insert(doc.key, doc);
11805
- lastReadTime = dbRemoteDoc.readTime;
11806
- })
11807
- .next(() => {
11808
- return {
11809
- changedDocs,
11810
- readTime: fromDbTimestampKey(lastReadTime)
11811
- };
11812
- });
11813
- }
11814
- /**
11815
- * Returns the read time of the most recently read document in the cache, or
11816
- * SnapshotVersion.min() if not available.
11817
- */
11818
- // PORTING NOTE: This is only used for multi-tab synchronization.
11819
- function remoteDocumentCacheGetLastReadTime(transaction) {
11820
- const documentsStore = remoteDocumentsStore(transaction);
11821
- // If there are no existing entries, we return SnapshotVersion.min().
11822
- let readTime = SnapshotVersion.min();
11823
- return documentsStore
11824
- .iterate({ index: DbRemoteDocument.readTimeIndex, reverse: true }, (key, dbRemoteDoc, control) => {
11825
- if (dbRemoteDoc.readTime) {
11826
- readTime = fromDbTimestampKey(dbRemoteDoc.readTime);
11827
- }
11828
- control.done();
11829
- })
11830
- .next(() => readTime);
11831
- }
11832
12121
  /**
11833
12122
  * Handles the details of adding and updating documents in the IndexedDbRemoteDocumentCache.
11834
12123
  *
@@ -11846,24 +12135,26 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
11846
12135
  super();
11847
12136
  this.documentCache = documentCache;
11848
12137
  this.trackRemovals = trackRemovals;
11849
- // A map of document sizes prior to applying the changes in this buffer.
11850
- this.documentSizes = new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
12138
+ // A map of document sizes and read times prior to applying the changes in
12139
+ // this buffer.
12140
+ this.documentStates = new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
11851
12141
  }
11852
12142
  applyChanges(transaction) {
11853
12143
  const promises = [];
11854
12144
  let sizeDelta = 0;
11855
12145
  let collectionParents = new SortedSet((l, r) => primitiveComparator(l.canonicalString(), r.canonicalString()));
11856
12146
  this.changes.forEach((key, documentChange) => {
11857
- const previousSize = this.documentSizes.get(key);
12147
+ const previousDoc = this.documentStates.get(key);
12148
+ promises.push(this.documentCache.removeEntry(transaction, key, previousDoc.readTime));
11858
12149
  if (documentChange.isValidDocument()) {
11859
12150
  const doc = toDbRemoteDocument(this.documentCache.serializer, documentChange);
11860
12151
  collectionParents = collectionParents.add(key.path.popLast());
11861
12152
  const size = dbDocumentSize(doc);
11862
- sizeDelta += size - previousSize;
12153
+ sizeDelta += size - previousDoc.size;
11863
12154
  promises.push(this.documentCache.addEntry(transaction, key, doc));
11864
12155
  }
11865
12156
  else {
11866
- sizeDelta -= previousSize;
12157
+ sizeDelta -= previousDoc.size;
11867
12158
  if (this.trackRemovals) {
11868
12159
  // In order to track removals, we store a "sentinel delete" in the
11869
12160
  // RemoteDocumentCache. This entry is represented by a NoDocument
@@ -11872,9 +12163,6 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
11872
12163
  const deletedDoc = toDbRemoteDocument(this.documentCache.serializer, documentChange.convertToNoDocument(SnapshotVersion.min()));
11873
12164
  promises.push(this.documentCache.addEntry(transaction, key, deletedDoc));
11874
12165
  }
11875
- else {
11876
- promises.push(this.documentCache.removeEntry(transaction, key));
11877
- }
11878
12166
  }
11879
12167
  });
11880
12168
  collectionParents.forEach(parent => {
@@ -11888,7 +12176,10 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
11888
12176
  return this.documentCache
11889
12177
  .getSizedEntry(transaction, documentKey)
11890
12178
  .next(getResult => {
11891
- this.documentSizes.set(documentKey, getResult.size);
12179
+ this.documentStates.set(documentKey, {
12180
+ size: getResult.size,
12181
+ readTime: getResult.document.readTime
12182
+ });
11892
12183
  return getResult.document;
11893
12184
  });
11894
12185
  }
@@ -11902,24 +12193,95 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
11902
12193
  // keys to `DocumentSizeEntry`s. This is to allow returning the
11903
12194
  // `MutableDocumentMap` directly, without a conversion.
11904
12195
  sizeMap.forEach((documentKey, size) => {
11905
- this.documentSizes.set(documentKey, size);
12196
+ this.documentStates.set(documentKey, {
12197
+ size,
12198
+ readTime: documents.get(documentKey).readTime
12199
+ });
11906
12200
  });
11907
12201
  return documents;
11908
12202
  });
11909
12203
  }
11910
12204
  }
11911
12205
  function documentGlobalStore(txn) {
11912
- return getStore(txn, DbRemoteDocumentGlobal.store);
12206
+ return getStore(txn, DbRemoteDocumentGlobalStore);
11913
12207
  }
11914
12208
  /**
11915
12209
  * Helper to get a typed SimpleDbStore for the remoteDocuments object store.
11916
12210
  */
11917
12211
  function remoteDocumentsStore(txn) {
11918
- return getStore(txn, DbRemoteDocument.store);
12212
+ return getStore(txn, DbRemoteDocumentStore);
12213
+ }
12214
+ /**
12215
+ * Returns a key that can be used for document lookups on the
12216
+ * `DbRemoteDocumentDocumentKeyIndex` index.
12217
+ */
12218
+ function dbKey(documentKey) {
12219
+ const path = documentKey.path.toArray();
12220
+ return [
12221
+ /* prefix path */ path.slice(0, path.length - 2),
12222
+ /* collection id */ path[path.length - 2],
12223
+ /* document id */ path[path.length - 1]
12224
+ ];
12225
+ }
12226
+ /**
12227
+ * Returns a key that can be used for document lookups via the primary key of
12228
+ * the DbRemoteDocument object store.
12229
+ */
12230
+ function dbReadTimeKey(documentKey, readTime) {
12231
+ const path = documentKey.path.toArray();
12232
+ return [
12233
+ /* prefix path */ path.slice(0, path.length - 2),
12234
+ /* collection id */ path[path.length - 2],
12235
+ toDbTimestampKey(readTime),
12236
+ /* document id */ path[path.length - 1]
12237
+ ];
12238
+ }
12239
+ /**
12240
+ * Returns a key that can be used for document lookups on the
12241
+ * `DbRemoteDocumentDocumentCollectionGroupIndex` index.
12242
+ */
12243
+ function dbCollectionGroupKey(collectionGroup, offset) {
12244
+ const path = offset.documentKey.path.toArray();
12245
+ return [
12246
+ /* collection id */ collectionGroup,
12247
+ toDbTimestampKey(offset.readTime),
12248
+ /* prefix path */ path.slice(0, path.length - 2),
12249
+ /* document id */ path.length > 0 ? path[path.length - 1] : ''
12250
+ ];
11919
12251
  }
11920
- function dbKey(docKey) {
11921
- return docKey.path.toArray();
12252
+ /**
12253
+ * Comparator that compares document keys according to the primary key sorting
12254
+ * used by the `DbRemoteDocumentDocument` store (by collection path and then
12255
+ * document ID).
12256
+ */
12257
+ function dbKeyComparator(l, r) {
12258
+ const cmp = l.path.length - r.path.length;
12259
+ return cmp !== 0 ? cmp : DocumentKey.comparator(l, r);
11922
12260
  }
12261
+ /**
12262
+ * Schema Version for the Web client:
12263
+ * 1. Initial version including Mutation Queue, Query Cache, and Remote
12264
+ * Document Cache
12265
+ * 2. Used to ensure a targetGlobal object exists and add targetCount to it. No
12266
+ * longer required because migration 3 unconditionally clears it.
12267
+ * 3. Dropped and re-created Query Cache to deal with cache corruption related
12268
+ * to limbo resolution. Addresses
12269
+ * https://github.com/firebase/firebase-ios-sdk/issues/1548
12270
+ * 4. Multi-Tab Support.
12271
+ * 5. Removal of held write acks.
12272
+ * 6. Create document global for tracking document cache size.
12273
+ * 7. Ensure every cached document has a sentinel row with a sequence number.
12274
+ * 8. Add collection-parent index for Collection Group queries.
12275
+ * 9. Change RemoteDocumentChanges store to be keyed by readTime rather than
12276
+ * an auto-incrementing ID. This is required for Index-Free queries.
12277
+ * 10. Rewrite the canonical IDs to the explicit Protobuf-based format.
12278
+ * 11. Add bundles and named_queries for bundle support.
12279
+ * 12. Add document overlays.
12280
+ * 13. Rewrite the keys of the remote document cache to allow for efficient
12281
+ * document lookup via `getAll()`.
12282
+ * 14. Add indexing support.
12283
+ */
12284
+ const SCHEMA_VERSION = 13;
11923
12285
 
11924
12286
  /**
11925
12287
  * @license
@@ -11955,7 +12317,7 @@ class SchemaConverter {
11955
12317
  createPrimaryClientStore(db);
11956
12318
  createMutationQueue(db);
11957
12319
  createQueryCache(db);
11958
- createRemoteDocumentCache(db);
12320
+ createLegacyRemoteDocumentCache(db);
11959
12321
  }
11960
12322
  // Migration 2 to populate the targetGlobal object no longer needed since
11961
12323
  // migration 3 unconditionally clears it.
@@ -12004,7 +12366,8 @@ class SchemaConverter {
12004
12366
  // to the DbRemoteDocument object store itself. Since the previous change
12005
12367
  // log only contained transient data, we can drop its object store.
12006
12368
  dropRemoteDocumentChangesStore(db);
12007
- createRemoteDocumentReadTimeIndex(txn);
12369
+ // Note: Schema version 9 used to create a read time index for the
12370
+ // RemoteDocumentCache. This is now done with schema version 13.
12008
12371
  });
12009
12372
  }
12010
12373
  if (fromVersion < 10 && toVersion >= 10) {
@@ -12022,6 +12385,12 @@ class SchemaConverter {
12022
12385
  });
12023
12386
  }
12024
12387
  if (fromVersion < 13 && toVersion >= 13) {
12388
+ p = p
12389
+ .next(() => createRemoteDocumentCache(db))
12390
+ .next(() => this.rewriteRemoteDocumentCache(db, simpleDbTransaction))
12391
+ .next(() => db.deleteObjectStore(DbRemoteDocumentStore$1));
12392
+ }
12393
+ if (fromVersion < 14 && toVersion >= 14) {
12025
12394
  p = p.next(() => {
12026
12395
  createFieldIndex(db);
12027
12396
  });
@@ -12029,27 +12398,27 @@ class SchemaConverter {
12029
12398
  return p;
12030
12399
  }
12031
12400
  addDocumentGlobal(txn) {
12032
- let byteCount = 0;
12401
+ let byteSize = 0;
12033
12402
  return txn
12034
- .store(DbRemoteDocument.store)
12403
+ .store(DbRemoteDocumentStore$1)
12035
12404
  .iterate((_, doc) => {
12036
- byteCount += dbDocumentSize(doc);
12405
+ byteSize += dbDocumentSize(doc);
12037
12406
  })
12038
12407
  .next(() => {
12039
- const metadata = new DbRemoteDocumentGlobal(byteCount);
12408
+ const metadata = { byteSize };
12040
12409
  return txn
12041
- .store(DbRemoteDocumentGlobal.store)
12042
- .put(DbRemoteDocumentGlobal.key, metadata);
12410
+ .store(DbRemoteDocumentGlobalStore)
12411
+ .put(DbRemoteDocumentGlobalKey, metadata);
12043
12412
  });
12044
12413
  }
12045
12414
  removeAcknowledgedMutations(txn) {
12046
- const queuesStore = txn.store(DbMutationQueue.store);
12047
- const mutationsStore = txn.store(DbMutationBatch.store);
12415
+ const queuesStore = txn.store(DbMutationQueueStore);
12416
+ const mutationsStore = txn.store(DbMutationBatchStore);
12048
12417
  return queuesStore.loadAll().next(queues => {
12049
12418
  return PersistencePromise.forEach(queues, (queue) => {
12050
12419
  const range = IDBKeyRange.bound([queue.userId, BATCHID_UNKNOWN], [queue.userId, queue.lastAcknowledgedBatchId]);
12051
12420
  return mutationsStore
12052
- .loadAll(DbMutationBatch.userMutationsIndex, range)
12421
+ .loadAll(DbMutationBatchUserMutationsIndex, range)
12053
12422
  .next(dbBatches => {
12054
12423
  return PersistencePromise.forEach(dbBatches, (dbBatch) => {
12055
12424
  hardAssert(dbBatch.userId === queue.userId);
@@ -12065,12 +12434,16 @@ class SchemaConverter {
12065
12434
  * with a sequence number. Missing rows are given the most recently used sequence number.
12066
12435
  */
12067
12436
  ensureSequenceNumbers(txn) {
12068
- const documentTargetStore = txn.store(DbTargetDocument.store);
12069
- const documentsStore = txn.store(DbRemoteDocument.store);
12070
- const globalTargetStore = txn.store(DbTargetGlobal.store);
12071
- return globalTargetStore.get(DbTargetGlobal.key).next(metadata => {
12437
+ const documentTargetStore = txn.store(DbTargetDocumentStore);
12438
+ const documentsStore = txn.store(DbRemoteDocumentStore$1);
12439
+ const globalTargetStore = txn.store(DbTargetGlobalStore);
12440
+ return globalTargetStore.get(DbTargetGlobalKey).next(metadata => {
12072
12441
  const writeSentinelKey = (path) => {
12073
- return documentTargetStore.put(new DbTargetDocument(0, encodeResourcePath(path), metadata.highestListenSequenceNumber));
12442
+ return documentTargetStore.put({
12443
+ targetId: 0,
12444
+ path: encodeResourcePath(path),
12445
+ sequenceNumber: metadata.highestListenSequenceNumber
12446
+ });
12074
12447
  };
12075
12448
  const promises = [];
12076
12449
  return documentsStore
@@ -12091,10 +12464,10 @@ class SchemaConverter {
12091
12464
  }
12092
12465
  createCollectionParentIndex(db, txn) {
12093
12466
  // Create the index.
12094
- db.createObjectStore(DbCollectionParent.store, {
12095
- keyPath: DbCollectionParent.keyPath
12467
+ db.createObjectStore(DbCollectionParentStore, {
12468
+ keyPath: DbCollectionParentKeyPath
12096
12469
  });
12097
- const collectionParentsStore = txn.store(DbCollectionParent.store);
12470
+ const collectionParentsStore = txn.store(DbCollectionParentStore);
12098
12471
  // Helper to add an index entry iff we haven't already written it.
12099
12472
  const cache = new MemoryCollectionParentIndex();
12100
12473
  const addEntry = (collectionPath) => {
@@ -12109,7 +12482,7 @@ class SchemaConverter {
12109
12482
  };
12110
12483
  // Index existing remote documents.
12111
12484
  return txn
12112
- .store(DbRemoteDocument.store)
12485
+ .store(DbRemoteDocumentStore$1)
12113
12486
  .iterate({ keysOnly: true }, (pathSegments, _) => {
12114
12487
  const path = new ResourcePath(pathSegments);
12115
12488
  return addEntry(path.popLast());
@@ -12117,7 +12490,7 @@ class SchemaConverter {
12117
12490
  .next(() => {
12118
12491
  // Index existing mutations.
12119
12492
  return txn
12120
- .store(DbDocumentMutation.store)
12493
+ .store(DbDocumentMutationStore)
12121
12494
  .iterate({ keysOnly: true }, ([userID, encodedPath, batchId], _) => {
12122
12495
  const path = decodeResourcePath(encodedPath);
12123
12496
  return addEntry(path.popLast());
@@ -12125,71 +12498,99 @@ class SchemaConverter {
12125
12498
  });
12126
12499
  }
12127
12500
  rewriteCanonicalIds(txn) {
12128
- const targetStore = txn.store(DbTarget.store);
12501
+ const targetStore = txn.store(DbTargetStore);
12129
12502
  return targetStore.iterate((key, originalDbTarget) => {
12130
12503
  const originalTargetData = fromDbTarget(originalDbTarget);
12131
12504
  const updatedDbTarget = toDbTarget(this.serializer, originalTargetData);
12132
12505
  return targetStore.put(updatedDbTarget);
12133
12506
  });
12134
12507
  }
12508
+ rewriteRemoteDocumentCache(db, transaction) {
12509
+ const legacyRemoteDocumentStore = transaction.store(DbRemoteDocumentStore$1);
12510
+ const writes = [];
12511
+ return legacyRemoteDocumentStore
12512
+ .iterate((_, legacyDocument) => {
12513
+ const remoteDocumentStore = transaction.store(DbRemoteDocumentStore);
12514
+ const path = extractKey(legacyDocument).path.toArray();
12515
+ const dbRemoteDocument = {
12516
+ prefixPath: path.slice(0, path.length - 2),
12517
+ collectionGroup: path[path.length - 2],
12518
+ documentId: path[path.length - 1],
12519
+ readTime: legacyDocument.readTime || [0, 0],
12520
+ unknownDocument: legacyDocument.unknownDocument,
12521
+ noDocument: legacyDocument.noDocument,
12522
+ document: legacyDocument.document,
12523
+ hasCommittedMutations: !!legacyDocument.hasCommittedMutations
12524
+ };
12525
+ writes.push(remoteDocumentStore.put(dbRemoteDocument));
12526
+ })
12527
+ .next(() => PersistencePromise.waitFor(writes));
12528
+ }
12135
12529
  }
12136
12530
  function sentinelKey(path) {
12137
12531
  return [0, encodeResourcePath(path)];
12138
12532
  }
12139
12533
  function createPrimaryClientStore(db) {
12140
- db.createObjectStore(DbPrimaryClient.store);
12534
+ db.createObjectStore(DbPrimaryClientStore);
12141
12535
  }
12142
12536
  function createMutationQueue(db) {
12143
- db.createObjectStore(DbMutationQueue.store, {
12144
- keyPath: DbMutationQueue.keyPath
12537
+ db.createObjectStore(DbMutationQueueStore, {
12538
+ keyPath: DbMutationQueueKeyPath
12145
12539
  });
12146
- const mutationBatchesStore = db.createObjectStore(DbMutationBatch.store, {
12147
- keyPath: DbMutationBatch.keyPath,
12540
+ const mutationBatchesStore = db.createObjectStore(DbMutationBatchStore, {
12541
+ keyPath: DbMutationBatchKeyPath,
12148
12542
  autoIncrement: true
12149
12543
  });
12150
- mutationBatchesStore.createIndex(DbMutationBatch.userMutationsIndex, DbMutationBatch.userMutationsKeyPath, { unique: true });
12151
- db.createObjectStore(DbDocumentMutation.store);
12544
+ mutationBatchesStore.createIndex(DbMutationBatchUserMutationsIndex, DbMutationBatchUserMutationsKeyPath, { unique: true });
12545
+ db.createObjectStore(DbDocumentMutationStore);
12152
12546
  }
12153
12547
  /**
12154
12548
  * Upgrade function to migrate the 'mutations' store from V1 to V3. Loads
12155
12549
  * and rewrites all data.
12156
12550
  */
12157
12551
  function upgradeMutationBatchSchemaAndMigrateData(db, txn) {
12158
- const v1MutationsStore = txn.store(DbMutationBatch.store);
12552
+ const v1MutationsStore = txn.store(DbMutationBatchStore);
12159
12553
  return v1MutationsStore.loadAll().next(existingMutations => {
12160
- db.deleteObjectStore(DbMutationBatch.store);
12161
- const mutationsStore = db.createObjectStore(DbMutationBatch.store, {
12162
- keyPath: DbMutationBatch.keyPath,
12554
+ db.deleteObjectStore(DbMutationBatchStore);
12555
+ const mutationsStore = db.createObjectStore(DbMutationBatchStore, {
12556
+ keyPath: DbMutationBatchKeyPath,
12163
12557
  autoIncrement: true
12164
12558
  });
12165
- mutationsStore.createIndex(DbMutationBatch.userMutationsIndex, DbMutationBatch.userMutationsKeyPath, { unique: true });
12166
- const v3MutationsStore = txn.store(DbMutationBatch.store);
12559
+ mutationsStore.createIndex(DbMutationBatchUserMutationsIndex, DbMutationBatchUserMutationsKeyPath, { unique: true });
12560
+ const v3MutationsStore = txn.store(DbMutationBatchStore);
12167
12561
  const writeAll = existingMutations.map(mutation => v3MutationsStore.put(mutation));
12168
12562
  return PersistencePromise.waitFor(writeAll);
12169
12563
  });
12170
12564
  }
12565
+ function createLegacyRemoteDocumentCache(db) {
12566
+ db.createObjectStore(DbRemoteDocumentStore$1);
12567
+ }
12171
12568
  function createRemoteDocumentCache(db) {
12172
- db.createObjectStore(DbRemoteDocument.store);
12569
+ const remoteDocumentStore = db.createObjectStore(DbRemoteDocumentStore, {
12570
+ keyPath: DbRemoteDocumentKeyPath
12571
+ });
12572
+ remoteDocumentStore.createIndex(DbRemoteDocumentDocumentKeyIndex, DbRemoteDocumentDocumentKeyIndexPath);
12573
+ remoteDocumentStore.createIndex(DbRemoteDocumentCollectionGroupIndex, DbRemoteDocumentCollectionGroupIndexPath);
12173
12574
  }
12174
12575
  function createDocumentGlobalStore(db) {
12175
- db.createObjectStore(DbRemoteDocumentGlobal.store);
12576
+ db.createObjectStore(DbRemoteDocumentGlobalStore);
12176
12577
  }
12177
12578
  function createQueryCache(db) {
12178
- const targetDocumentsStore = db.createObjectStore(DbTargetDocument.store, {
12179
- keyPath: DbTargetDocument.keyPath
12579
+ const targetDocumentsStore = db.createObjectStore(DbTargetDocumentStore, {
12580
+ keyPath: DbTargetDocumentKeyPath
12180
12581
  });
12181
- targetDocumentsStore.createIndex(DbTargetDocument.documentTargetsIndex, DbTargetDocument.documentTargetsKeyPath, { unique: true });
12182
- const targetStore = db.createObjectStore(DbTarget.store, {
12183
- keyPath: DbTarget.keyPath
12582
+ targetDocumentsStore.createIndex(DbTargetDocumentDocumentTargetsIndex, DbTargetDocumentDocumentTargetsKeyPath, { unique: true });
12583
+ const targetStore = db.createObjectStore(DbTargetStore, {
12584
+ keyPath: DbTargetKeyPath
12184
12585
  });
12185
12586
  // NOTE: This is unique only because the TargetId is the suffix.
12186
- targetStore.createIndex(DbTarget.queryTargetsIndexName, DbTarget.queryTargetsKeyPath, { unique: true });
12187
- db.createObjectStore(DbTargetGlobal.store);
12587
+ targetStore.createIndex(DbTargetQueryTargetsIndexName, DbTargetQueryTargetsKeyPath, { unique: true });
12588
+ db.createObjectStore(DbTargetGlobalStore);
12188
12589
  }
12189
12590
  function dropQueryCache(db) {
12190
- db.deleteObjectStore(DbTargetDocument.store);
12191
- db.deleteObjectStore(DbTarget.store);
12192
- db.deleteObjectStore(DbTargetGlobal.store);
12591
+ db.deleteObjectStore(DbTargetDocumentStore);
12592
+ db.deleteObjectStore(DbTargetStore);
12593
+ db.deleteObjectStore(DbTargetGlobalStore);
12193
12594
  }
12194
12595
  function dropRemoteDocumentChangesStore(db) {
12195
12596
  if (db.objectStoreNames.contains('remoteDocumentChanges')) {
@@ -12202,58 +12603,65 @@ function dropRemoteDocumentChangesStore(db) {
12202
12603
  * @param txn - The version upgrade transaction for indexeddb
12203
12604
  */
12204
12605
  function writeEmptyTargetGlobalEntry(txn) {
12205
- const globalStore = txn.store(DbTargetGlobal.store);
12206
- const metadata = new DbTargetGlobal(
12207
- /*highestTargetId=*/ 0,
12208
- /*lastListenSequenceNumber=*/ 0, SnapshotVersion.min().toTimestamp(),
12209
- /*targetCount=*/ 0);
12210
- return globalStore.put(DbTargetGlobal.key, metadata);
12211
- }
12212
- /**
12213
- * Creates indices on the RemoteDocuments store used for both multi-tab
12214
- * and Index-Free queries.
12215
- */
12216
- function createRemoteDocumentReadTimeIndex(txn) {
12217
- const remoteDocumentStore = txn.objectStore(DbRemoteDocument.store);
12218
- remoteDocumentStore.createIndex(DbRemoteDocument.readTimeIndex, DbRemoteDocument.readTimeIndexPath, { unique: false });
12219
- remoteDocumentStore.createIndex(DbRemoteDocument.collectionReadTimeIndex, DbRemoteDocument.collectionReadTimeIndexPath, { unique: false });
12606
+ const globalStore = txn.store(DbTargetGlobalStore);
12607
+ const metadata = {
12608
+ highestTargetId: 0,
12609
+ highestListenSequenceNumber: 0,
12610
+ lastRemoteSnapshotVersion: SnapshotVersion.min().toTimestamp(),
12611
+ targetCount: 0
12612
+ };
12613
+ return globalStore.put(DbTargetGlobalKey, metadata);
12220
12614
  }
12221
12615
  function createClientMetadataStore(db) {
12222
- db.createObjectStore(DbClientMetadata.store, {
12223
- keyPath: DbClientMetadata.keyPath
12616
+ db.createObjectStore(DbClientMetadataStore, {
12617
+ keyPath: DbClientMetadataKeyPath
12224
12618
  });
12225
12619
  }
12226
12620
  function createBundlesStore(db) {
12227
- db.createObjectStore(DbBundle.store, {
12228
- keyPath: DbBundle.keyPath
12621
+ db.createObjectStore(DbBundleStore, {
12622
+ keyPath: DbBundleKeyPath
12229
12623
  });
12230
12624
  }
12231
12625
  function createNamedQueriesStore(db) {
12232
- db.createObjectStore(DbNamedQuery.store, {
12233
- keyPath: DbNamedQuery.keyPath
12626
+ db.createObjectStore(DbNamedQueryStore, {
12627
+ keyPath: DbNamedQueryKeyPath
12234
12628
  });
12235
12629
  }
12236
12630
  function createFieldIndex(db) {
12237
- const indexConfiguratioStore = db.createObjectStore(DbIndexConfiguration.store, {
12238
- keyPath: DbIndexConfiguration.keyPath,
12631
+ const indexConfigurationStore = db.createObjectStore(DbIndexConfigurationStore, {
12632
+ keyPath: DbIndexConfigurationKeyPath,
12239
12633
  autoIncrement: true
12240
12634
  });
12241
- indexConfiguratioStore.createIndex(DbIndexConfiguration.collectionGroupIndex, DbIndexConfiguration.collectionGroupIndexPath, { unique: false });
12242
- const indexStateStore = db.createObjectStore(DbIndexState.store, {
12243
- keyPath: DbIndexState.keyPath
12635
+ indexConfigurationStore.createIndex(DbIndexConfigurationCollectionGroupIndex, DbIndexConfigurationCollectionGroupIndexPath, { unique: false });
12636
+ const indexStateStore = db.createObjectStore(DbIndexStateStore, {
12637
+ keyPath: DbIndexStateKeyPath
12244
12638
  });
12245
- indexStateStore.createIndex(DbIndexState.sequenceNumberIndex, DbIndexState.sequenceNumberIndexPath, { unique: false });
12246
- const indexEntryStore = db.createObjectStore(DbIndexEntry.store, {
12247
- keyPath: DbIndexEntry.keyPath
12639
+ indexStateStore.createIndex(DbIndexStateSequenceNumberIndex, DbIndexStateSequenceNumberIndexPath, { unique: false });
12640
+ const indexEntryStore = db.createObjectStore(DbIndexEntryStore, {
12641
+ keyPath: DbIndexEntryKeyPath
12248
12642
  });
12249
- indexEntryStore.createIndex(DbIndexEntry.documentKeyIndex, DbIndexEntry.documentKeyIndexPath, { unique: false });
12643
+ indexEntryStore.createIndex(DbIndexEntryDocumentKeyIndex, DbIndexEntryDocumentKeyIndexPath, { unique: false });
12250
12644
  }
12251
12645
  function createDocumentOverlayStore(db) {
12252
- const documentOverlayStore = db.createObjectStore(DbDocumentOverlay.store, {
12253
- keyPath: DbDocumentOverlay.keyPath
12646
+ const documentOverlayStore = db.createObjectStore(DbDocumentOverlayStore, {
12647
+ keyPath: DbDocumentOverlayKeyPath
12254
12648
  });
12255
- documentOverlayStore.createIndex(DbDocumentOverlay.collectionPathOverlayIndex, DbDocumentOverlay.collectionPathOverlayIndexPath, { unique: false });
12256
- documentOverlayStore.createIndex(DbDocumentOverlay.collectionGroupOverlayIndex, DbDocumentOverlay.collectionGroupOverlayIndexPath, { unique: false });
12649
+ documentOverlayStore.createIndex(DbDocumentOverlayCollectionPathOverlayIndex, DbDocumentOverlayCollectionPathOverlayIndexPath, { unique: false });
12650
+ documentOverlayStore.createIndex(DbDocumentOverlayCollectionGroupOverlayIndex, DbDocumentOverlayCollectionGroupOverlayIndexPath, { unique: false });
12651
+ }
12652
+ function extractKey(remoteDoc) {
12653
+ if (remoteDoc.document) {
12654
+ return new DocumentKey(ResourcePath.fromString(remoteDoc.document.name).popFirst(5));
12655
+ }
12656
+ else if (remoteDoc.noDocument) {
12657
+ return DocumentKey.fromSegments(remoteDoc.noDocument.path);
12658
+ }
12659
+ else if (remoteDoc.unknownDocument) {
12660
+ return DocumentKey.fromSegments(remoteDoc.unknownDocument.path);
12661
+ }
12662
+ else {
12663
+ return fail();
12664
+ }
12257
12665
  }
12258
12666
 
12259
12667
  /**
@@ -12501,7 +12909,12 @@ class IndexedDbPersistence {
12501
12909
  return this.runTransaction('updateClientMetadataAndTryBecomePrimary', 'readwrite', txn => {
12502
12910
  const metadataStore = clientMetadataStore(txn);
12503
12911
  return metadataStore
12504
- .put(new DbClientMetadata(this.clientId, Date.now(), this.networkEnabled, this.inForeground))
12912
+ .put({
12913
+ clientId: this.clientId,
12914
+ updateTimeMs: Date.now(),
12915
+ networkEnabled: this.networkEnabled,
12916
+ inForeground: this.inForeground
12917
+ })
12505
12918
  .next(() => {
12506
12919
  if (this.isPrimary) {
12507
12920
  return this.verifyPrimaryLease(txn).next(success => {
@@ -12547,7 +12960,7 @@ class IndexedDbPersistence {
12547
12960
  }
12548
12961
  verifyPrimaryLease(txn) {
12549
12962
  const store = primaryClientStore(txn);
12550
- return store.get(DbPrimaryClient.key).next(primaryClient => {
12963
+ return store.get(DbPrimaryClientKey).next(primaryClient => {
12551
12964
  return PersistencePromise.resolve(this.isLocalClient(primaryClient));
12552
12965
  });
12553
12966
  }
@@ -12565,7 +12978,7 @@ class IndexedDbPersistence {
12565
12978
  !this.isWithinAge(this.lastGarbageCollectionTime, MAX_CLIENT_AGE_MS)) {
12566
12979
  this.lastGarbageCollectionTime = Date.now();
12567
12980
  const inactiveClients = await this.runTransaction('maybeGarbageCollectMultiClientState', 'readwrite-primary', txn => {
12568
- const metadataStore = getStore(txn, DbClientMetadata.store);
12981
+ const metadataStore = getStore(txn, DbClientMetadataStore);
12569
12982
  return metadataStore.loadAll().next(existingClients => {
12570
12983
  const active = this.filterActiveClients(existingClients, MAX_CLIENT_AGE_MS);
12571
12984
  const inactive = existingClients.filter(client => active.indexOf(client) === -1);
@@ -12619,7 +13032,7 @@ class IndexedDbPersistence {
12619
13032
  }
12620
13033
  const store = primaryClientStore(txn);
12621
13034
  return store
12622
- .get(DbPrimaryClient.key)
13035
+ .get(DbPrimaryClientKey)
12623
13036
  .next(currentPrimary => {
12624
13037
  const currentLeaseIsValid = currentPrimary !== null &&
12625
13038
  this.isWithinAge(currentPrimary.leaseTimestampMs, MAX_PRIMARY_ELIGIBLE_AGE_MS) &&
@@ -12700,7 +13113,7 @@ class IndexedDbPersistence {
12700
13113
  this.detachWindowUnloadHook();
12701
13114
  // Use `SimpleDb.runTransaction` directly to avoid failing if another tab
12702
13115
  // has obtained the primary lease.
12703
- await this.simpleDb.runTransaction('shutdown', 'readwrite', [DbPrimaryClient.store, DbClientMetadata.store], simpleDbTxn => {
13116
+ await this.simpleDb.runTransaction('shutdown', 'readwrite', [DbPrimaryClientStore, DbClientMetadataStore], simpleDbTxn => {
12704
13117
  const persistenceTransaction = new IndexedDbTransaction(simpleDbTxn, ListenSequence.INVALID);
12705
13118
  return this.releasePrimaryLeaseIfHeld(persistenceTransaction).next(() => this.removeClientMetadata(persistenceTransaction));
12706
13119
  });
@@ -12807,7 +13220,7 @@ class IndexedDbPersistence {
12807
13220
  // be turned off.
12808
13221
  verifyAllowTabSynchronization(txn) {
12809
13222
  const store = primaryClientStore(txn);
12810
- return store.get(DbPrimaryClient.key).next(currentPrimary => {
13223
+ return store.get(DbPrimaryClientKey).next(currentPrimary => {
12811
13224
  const currentLeaseIsValid = currentPrimary !== null &&
12812
13225
  this.isWithinAge(currentPrimary.leaseTimestampMs, MAX_PRIMARY_ELIGIBLE_AGE_MS) &&
12813
13226
  !this.isClientZombied(currentPrimary.ownerId);
@@ -12825,8 +13238,12 @@ class IndexedDbPersistence {
12825
13238
  * method does not verify that the client is eligible for this lease.
12826
13239
  */
12827
13240
  acquireOrExtendPrimaryLease(txn) {
12828
- const newPrimary = new DbPrimaryClient(this.clientId, this.allowTabSynchronization, Date.now());
12829
- return primaryClientStore(txn).put(DbPrimaryClient.key, newPrimary);
13241
+ const newPrimary = {
13242
+ ownerId: this.clientId,
13243
+ allowTabSynchronization: this.allowTabSynchronization,
13244
+ leaseTimestampMs: Date.now()
13245
+ };
13246
+ return primaryClientStore(txn).put(DbPrimaryClientKey, newPrimary);
12830
13247
  }
12831
13248
  static isAvailable() {
12832
13249
  return SimpleDb.isAvailable();
@@ -12834,10 +13251,10 @@ class IndexedDbPersistence {
12834
13251
  /** Checks the primary lease and removes it if we are the current primary. */
12835
13252
  releasePrimaryLeaseIfHeld(txn) {
12836
13253
  const store = primaryClientStore(txn);
12837
- return store.get(DbPrimaryClient.key).next(primaryClient => {
13254
+ return store.get(DbPrimaryClientKey).next(primaryClient => {
12838
13255
  if (this.isLocalClient(primaryClient)) {
12839
13256
  logDebug(LOG_TAG$d, 'Releasing primary lease.');
12840
- return store.delete(DbPrimaryClient.key);
13257
+ return store.delete(DbPrimaryClientKey);
12841
13258
  }
12842
13259
  else {
12843
13260
  return PersistencePromise.resolve();
@@ -12971,13 +13388,13 @@ class IndexedDbPersistence {
12971
13388
  * Helper to get a typed SimpleDbStore for the primary client object store.
12972
13389
  */
12973
13390
  function primaryClientStore(txn) {
12974
- return getStore(txn, DbPrimaryClient.store);
13391
+ return getStore(txn, DbPrimaryClientStore);
12975
13392
  }
12976
13393
  /**
12977
13394
  * Helper to get a typed SimpleDbStore for the client metadata object store.
12978
13395
  */
12979
13396
  function clientMetadataStore(txn) {
12980
- return getStore(txn, DbClientMetadata.store);
13397
+ return getStore(txn, DbClientMetadataStore);
12981
13398
  }
12982
13399
  /**
12983
13400
  * Generates a string used as a prefix when storing data in IndexedDB and
@@ -13087,18 +13504,17 @@ class LocalDocumentsView {
13087
13504
  *
13088
13505
  * @param transaction - The persistence transaction.
13089
13506
  * @param query - The query to match documents against.
13090
- * @param sinceReadTime - If not set to SnapshotVersion.min(), return only
13091
- * documents that have been read since this snapshot version (exclusive).
13507
+ * @param offset - Read time and key to start scanning by (exclusive).
13092
13508
  */
13093
- getDocumentsMatchingQuery(transaction, query, sinceReadTime) {
13509
+ getDocumentsMatchingQuery(transaction, query, offset) {
13094
13510
  if (isDocumentQuery$1(query)) {
13095
13511
  return this.getDocumentsMatchingDocumentQuery(transaction, query.path);
13096
13512
  }
13097
13513
  else if (isCollectionGroupQuery(query)) {
13098
- return this.getDocumentsMatchingCollectionGroupQuery(transaction, query, sinceReadTime);
13514
+ return this.getDocumentsMatchingCollectionGroupQuery(transaction, query, offset);
13099
13515
  }
13100
13516
  else {
13101
- return this.getDocumentsMatchingCollectionQuery(transaction, query, sinceReadTime);
13517
+ return this.getDocumentsMatchingCollectionQuery(transaction, query, offset);
13102
13518
  }
13103
13519
  }
13104
13520
  getDocumentsMatchingDocumentQuery(transaction, docPath) {
@@ -13111,7 +13527,7 @@ class LocalDocumentsView {
13111
13527
  return result;
13112
13528
  });
13113
13529
  }
13114
- getDocumentsMatchingCollectionGroupQuery(transaction, query, sinceReadTime) {
13530
+ getDocumentsMatchingCollectionGroupQuery(transaction, query, offset) {
13115
13531
  const collectionId = query.collectionGroup;
13116
13532
  let results = documentMap();
13117
13533
  return this.indexManager
@@ -13121,7 +13537,7 @@ class LocalDocumentsView {
13121
13537
  // collectionId and aggregate the results.
13122
13538
  return PersistencePromise.forEach(parents, (parent) => {
13123
13539
  const collectionQuery = asCollectionQueryAtPath(query, parent.child(collectionId));
13124
- return this.getDocumentsMatchingCollectionQuery(transaction, collectionQuery, sinceReadTime).next(r => {
13540
+ return this.getDocumentsMatchingCollectionQuery(transaction, collectionQuery, offset).next(r => {
13125
13541
  r.forEach((key, doc) => {
13126
13542
  results = results.insert(key, doc);
13127
13543
  });
@@ -13129,11 +13545,11 @@ class LocalDocumentsView {
13129
13545
  }).next(() => results);
13130
13546
  });
13131
13547
  }
13132
- getDocumentsMatchingCollectionQuery(transaction, query, sinceReadTime) {
13548
+ getDocumentsMatchingCollectionQuery(transaction, query, offset) {
13133
13549
  // Query the remote documents and overlay mutations.
13134
13550
  let results;
13135
13551
  return this.remoteDocumentCache
13136
- .getAll(transaction, query.path, sinceReadTime)
13552
+ .getAllFromCollection(transaction, query.path, offset)
13137
13553
  .next(queryResults => {
13138
13554
  results = queryResults;
13139
13555
  return this.mutationQueue.getAllMutationBatchesAffectingQuery(transaction, query);
@@ -13219,11 +13635,12 @@ class LocalStoreImpl {
13219
13635
  // TODO(wuandy): Evaluate if TargetId can be part of Target.
13220
13636
  this.targetIdByTarget = new ObjectMap(t => canonifyTarget(t), targetEquals);
13221
13637
  /**
13222
- * The read time of the last entry processed by `getNewDocumentChanges()`.
13638
+ * A per collection group index of the last read time processed by
13639
+ * `getNewDocumentChanges()`.
13223
13640
  *
13224
13641
  * PORTING NOTE: This is only used for multi-tab synchronization.
13225
13642
  */
13226
- this.lastDocumentChangeReadTime = SnapshotVersion.min();
13643
+ this.collectionGroupReadTime = new Map();
13227
13644
  this.remoteDocuments = persistence.getRemoteDocumentCache();
13228
13645
  this.targetCache = persistence.getTargetCache();
13229
13646
  this.bundleCache = persistence.getBundleCache();
@@ -13758,6 +14175,7 @@ function localStoreExecuteQuery(localStore, query, usePreviousResults) {
13758
14175
  ? lastLimboFreeSnapshotVersion
13759
14176
  : SnapshotVersion.min(), usePreviousResults ? remoteKeys : documentKeySet()))
13760
14177
  .next(documents => {
14178
+ setMaxReadTime(localStoreImpl, queryCollectionGroup(query), documents);
13761
14179
  return { documents, remoteKeys };
13762
14180
  });
13763
14181
  });
@@ -13835,28 +14253,32 @@ function localStoreGetCachedTarget(localStore, targetId) {
13835
14253
  * since the prior call.
13836
14254
  */
13837
14255
  // PORTING NOTE: Multi-Tab only.
13838
- function localStoreGetNewDocumentChanges(localStore) {
14256
+ function localStoreGetNewDocumentChanges(localStore, collectionGroup) {
13839
14257
  const localStoreImpl = debugCast(localStore);
14258
+ // Get the current maximum read time for the collection. This should always
14259
+ // exist, but to reduce the chance for regressions we default to
14260
+ // SnapshotVersion.Min()
14261
+ // TODO(indexing): Consider removing the default value.
14262
+ const readTime = localStoreImpl.collectionGroupReadTime.get(collectionGroup) ||
14263
+ SnapshotVersion.min();
13840
14264
  return localStoreImpl.persistence
13841
- .runTransaction('Get new document changes', 'readonly', txn => remoteDocumentCacheGetNewDocumentChanges(localStoreImpl.remoteDocuments, txn, localStoreImpl.lastDocumentChangeReadTime))
13842
- .then(({ changedDocs, readTime }) => {
13843
- localStoreImpl.lastDocumentChangeReadTime = readTime;
14265
+ .runTransaction('Get new document changes', 'readonly', txn => localStoreImpl.remoteDocuments.getAllFromCollectionGroup(txn, collectionGroup, newIndexOffsetSuccessorFromReadTime(readTime, INITIAL_LARGEST_BATCH_ID),
14266
+ /* limit= */ Number.MAX_SAFE_INTEGER))
14267
+ .then(changedDocs => {
14268
+ setMaxReadTime(localStoreImpl, collectionGroup, changedDocs);
13844
14269
  return changedDocs;
13845
14270
  });
13846
14271
  }
13847
- /**
13848
- * Reads the newest document change from persistence and moves the internal
13849
- * synchronization marker forward so that calls to `getNewDocumentChanges()`
13850
- * only return changes that happened after client initialization.
13851
- */
14272
+ /** Sets the collection group's maximum read time from the given documents. */
13852
14273
  // PORTING NOTE: Multi-Tab only.
13853
- async function localStoreSynchronizeLastDocumentChangeReadTime(localStore) {
13854
- const localStoreImpl = debugCast(localStore);
13855
- return localStoreImpl.persistence
13856
- .runTransaction('Synchronize last document change read time', 'readonly', txn => remoteDocumentCacheGetLastReadTime(txn))
13857
- .then(readTime => {
13858
- localStoreImpl.lastDocumentChangeReadTime = readTime;
14274
+ function setMaxReadTime(localStoreImpl, collectionGroup, changedDocs) {
14275
+ let readTime = SnapshotVersion.min();
14276
+ changedDocs.forEach((_, doc) => {
14277
+ if (doc.readTime.compareTo(readTime) > 0) {
14278
+ readTime = doc.readTime;
14279
+ }
13859
14280
  });
14281
+ localStoreImpl.collectionGroupReadTime.set(collectionGroup, readTime);
13860
14282
  }
13861
14283
  /**
13862
14284
  * Creates a new target using the given bundle name, which will be used to
@@ -14040,7 +14462,7 @@ class MemoryDocumentOverlayCache {
14040
14462
  return PersistencePromise.resolve(this.overlays.get(key));
14041
14463
  }
14042
14464
  saveOverlays(transaction, largestBatchId, overlays) {
14043
- overlays.forEach(mutation => {
14465
+ overlays.forEach((_, mutation) => {
14044
14466
  this.saveOverlay(transaction, largestBatchId, mutation);
14045
14467
  });
14046
14468
  return PersistencePromise.resolve();
@@ -14054,7 +14476,7 @@ class MemoryDocumentOverlayCache {
14054
14476
  return PersistencePromise.resolve();
14055
14477
  }
14056
14478
  getOverlaysForCollection(transaction, collection, sinceBatchId) {
14057
- const result = new Map();
14479
+ const result = newOverlayMap();
14058
14480
  const immediateChildrenPathLength = collection.length + 1;
14059
14481
  const prefix = new DocumentKey(collection.child(''));
14060
14482
  const iter = this.overlays.getIteratorFrom(prefix);
@@ -14088,19 +14510,19 @@ class MemoryDocumentOverlayCache {
14088
14510
  if (overlay.largestBatchId > sinceBatchId) {
14089
14511
  let overlaysForBatchId = batchIdToOverlays.get(overlay.largestBatchId);
14090
14512
  if (overlaysForBatchId === null) {
14091
- overlaysForBatchId = new Map();
14513
+ overlaysForBatchId = newOverlayMap();
14092
14514
  batchIdToOverlays = batchIdToOverlays.insert(overlay.largestBatchId, overlaysForBatchId);
14093
14515
  }
14094
14516
  overlaysForBatchId.set(overlay.getKey(), overlay);
14095
14517
  }
14096
14518
  }
14097
- const result = new Map();
14519
+ const result = newOverlayMap();
14098
14520
  const batchIter = batchIdToOverlays.getIterator();
14099
14521
  while (batchIter.hasNext()) {
14100
14522
  const entry = batchIter.getNext();
14101
14523
  const overlays = entry.value;
14102
- overlays.forEach((overlay, key) => result.set(key, overlay));
14103
- if (result.size >= count) {
14524
+ overlays.forEach((key, overlay) => result.set(key, overlay));
14525
+ if (result.size() >= count) {
14104
14526
  break;
14105
14527
  }
14106
14528
  }
@@ -14113,16 +14535,19 @@ class MemoryDocumentOverlayCache {
14113
14535
  // Remove the association of the overlay to its batch id.
14114
14536
  const existing = this.overlays.get(mutation.key);
14115
14537
  if (existing !== null) {
14116
- this.overlayByBatchId.get(existing.largestBatchId).delete(mutation.key);
14538
+ const newSet = this.overlayByBatchId
14539
+ .get(existing.largestBatchId)
14540
+ .delete(mutation.key);
14541
+ this.overlayByBatchId.set(existing.largestBatchId, newSet);
14117
14542
  }
14118
14543
  this.overlays = this.overlays.insert(mutation.key, new Overlay(largestBatchId, mutation));
14119
14544
  // Create the association of this overlay to the given largestBatchId.
14120
14545
  let batch = this.overlayByBatchId.get(largestBatchId);
14121
14546
  if (batch === undefined) {
14122
- batch = new Set();
14547
+ batch = documentKeySet();
14123
14548
  this.overlayByBatchId.set(largestBatchId, batch);
14124
14549
  }
14125
- batch.add(mutation.key);
14550
+ this.overlayByBatchId.set(largestBatchId, batch.add(mutation.key));
14126
14551
  }
14127
14552
  }
14128
14553
 
@@ -14536,7 +14961,7 @@ class MemoryRemoteDocumentCacheImpl {
14536
14961
  });
14537
14962
  return PersistencePromise.resolve(results);
14538
14963
  }
14539
- getAll(transaction, collectionPath, sinceReadTime) {
14964
+ getAllFromCollection(transaction, collectionPath, offset) {
14540
14965
  let results = mutableDocumentMap();
14541
14966
  // Documents are ordered by key, so we can use a prefix scan to narrow down
14542
14967
  // the documents we need to match the query against.
@@ -14551,13 +14976,19 @@ class MemoryRemoteDocumentCacheImpl {
14551
14976
  // Exclude entries from subcollections.
14552
14977
  continue;
14553
14978
  }
14554
- if (document.readTime.compareTo(sinceReadTime) <= 0) {
14979
+ if (indexOffsetComparator(newIndexOffsetFromDocument(document), offset) <= 0) {
14980
+ // The document sorts before the offset.
14555
14981
  continue;
14556
14982
  }
14557
14983
  results = results.insert(document.key, document.mutableCopy());
14558
14984
  }
14559
14985
  return PersistencePromise.resolve(results);
14560
14986
  }
14987
+ getAllFromCollectionGroup(transaction, collectionGroup, offset, limti) {
14988
+ // This method should only be called from the IndexBackfiller if persistence
14989
+ // is enabled.
14990
+ fail();
14991
+ }
14561
14992
  forEachDocumentKey(transaction, f) {
14562
14993
  return PersistencePromise.forEach(this.docs, (key) => f(key));
14563
14994
  }
@@ -15010,7 +15441,7 @@ class QueryEngine {
15010
15441
  }
15011
15442
  // Retrieve all results for documents that were updated since the last
15012
15443
  // limbo-document free remote snapshot.
15013
- return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, lastLimboFreeSnapshotVersion).next(updatedResults => {
15444
+ return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, newIndexOffsetSuccessorFromReadTime(lastLimboFreeSnapshotVersion, INITIAL_LARGEST_BATCH_ID)).next(updatedResults => {
15014
15445
  // We merge `previousResults` into `updateResults`, since
15015
15446
  // `updateResults` is already a DocumentMap. If a document is
15016
15447
  // contained in both lists, then its contents are the same.
@@ -15072,7 +15503,7 @@ class QueryEngine {
15072
15503
  if (getLogLevel() <= LogLevel.DEBUG) {
15073
15504
  logDebug('QueryEngine', 'Using full collection scan to execute query:', stringifyQuery(query));
15074
15505
  }
15075
- return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, SnapshotVersion.min());
15506
+ return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, IndexOffset.min());
15076
15507
  }
15077
15508
  }
15078
15509
 
@@ -15133,8 +15564,9 @@ function createWebStorageOnlineStateKey(persistenceKey) {
15133
15564
  // The WebStorage prefix that plays as a event to indicate the remote documents
15134
15565
  // might have changed due to some secondary tabs loading a bundle.
15135
15566
  // format of the key is:
15136
- // firestore_bundle_loaded_<persistenceKey>
15137
- const BUNDLE_LOADED_KEY_PREFIX = 'firestore_bundle_loaded';
15567
+ // firestore_bundle_loaded_v2_<persistenceKey>
15568
+ // The version ending with "v2" stores the list of modified collection groups.
15569
+ const BUNDLE_LOADED_KEY_PREFIX = 'firestore_bundle_loaded_v2';
15138
15570
  function createBundleLoadedKey(persistenceKey) {
15139
15571
  return `${BUNDLE_LOADED_KEY_PREFIX}_${persistenceKey}`;
15140
15572
  }
@@ -15512,8 +15944,8 @@ class WebStorageSharedClientState {
15512
15944
  setOnlineState(onlineState) {
15513
15945
  this.persistOnlineState(onlineState);
15514
15946
  }
15515
- notifyBundleLoaded() {
15516
- this.persistBundleLoadedState();
15947
+ notifyBundleLoaded(collectionGroups) {
15948
+ this.persistBundleLoadedState(collectionGroups);
15517
15949
  }
15518
15950
  shutdown() {
15519
15951
  if (this.started) {
@@ -15597,7 +16029,8 @@ class WebStorageSharedClientState {
15597
16029
  }
15598
16030
  }
15599
16031
  else if (storageEvent.key === this.bundleLoadedKey) {
15600
- return this.syncEngine.synchronizeWithChangedDocuments();
16032
+ const collectionGroups = this.fromWebStoreBundleLoadedState(storageEvent.newValue);
16033
+ await Promise.all(collectionGroups.map(cg => this.syncEngine.synchronizeWithChangedDocuments(cg)));
15601
16034
  }
15602
16035
  });
15603
16036
  }
@@ -15629,8 +16062,9 @@ class WebStorageSharedClientState {
15629
16062
  const targetMetadata = new QueryTargetMetadata(targetId, state, error);
15630
16063
  this.setItem(targetKey, targetMetadata.toWebStorageJSON());
15631
16064
  }
15632
- persistBundleLoadedState() {
15633
- this.setItem(this.bundleLoadedKey, 'value-not-used');
16065
+ persistBundleLoadedState(collectionGroups) {
16066
+ const json = JSON.stringify(Array.from(collectionGroups));
16067
+ this.setItem(this.bundleLoadedKey, json);
15634
16068
  }
15635
16069
  /**
15636
16070
  * Parses a client state key in WebStorage. Returns null if the key does not
@@ -15674,6 +16108,9 @@ class WebStorageSharedClientState {
15674
16108
  fromWebStorageOnlineState(value) {
15675
16109
  return SharedOnlineState.fromWebStorageEntry(value);
15676
16110
  }
16111
+ fromWebStoreBundleLoadedState(value) {
16112
+ return JSON.parse(value);
16113
+ }
15677
16114
  async handleMutationBatchEvent(mutationBatch) {
15678
16115
  if (mutationBatch.user.uid !== this.currentUser.uid) {
15679
16116
  logDebug(LOG_TAG$a, `Ignoring mutation for non-active user ${mutationBatch.user.uid}`);
@@ -15790,7 +16227,7 @@ class MemorySharedClientState {
15790
16227
  }
15791
16228
  shutdown() { }
15792
16229
  writeSequenceNumber(sequenceNumber) { }
15793
- notifyBundleLoaded() {
16230
+ notifyBundleLoaded(collectionGroups) {
15794
16231
  // No op.
15795
16232
  }
15796
16233
  }
@@ -15973,9 +16410,13 @@ function createMetadata(databasePath, authToken, appCheckToken, appId) {
15973
16410
  metadata.set('X-Firebase-GMPID', appId);
15974
16411
  }
15975
16412
  metadata.set('X-Goog-Api-Client', X_GOOG_API_CLIENT_VALUE);
15976
- // This header is used to improve routing and project isolation by the
16413
+ // These headers are used to improve routing and project isolation by the
15977
16414
  // backend.
16415
+ // TODO(b/199767712): We are keeping 'Google-Cloud-Resource-Prefix' until Emulators can be
16416
+ // released with cl/428820046. Currently blocked because Emulators are now built with Java
16417
+ // 11 from Google3.
15978
16418
  metadata.set('Google-Cloud-Resource-Prefix', databasePath);
16419
+ metadata.set('x-goog-request-params', databasePath);
15979
16420
  return metadata;
15980
16421
  }
15981
16422
  /**
@@ -21086,29 +21527,6 @@ class LocalViewChanges {
21086
21527
  }
21087
21528
  }
21088
21529
 
21089
- /**
21090
- * @license
21091
- * Copyright 2020 Google LLC
21092
- *
21093
- * Licensed under the Apache License, Version 2.0 (the "License");
21094
- * you may not use this file except in compliance with the License.
21095
- * You may obtain a copy of the License at
21096
- *
21097
- * http://www.apache.org/licenses/LICENSE-2.0
21098
- *
21099
- * Unless required by applicable law or agreed to in writing, software
21100
- * distributed under the License is distributed on an "AS IS" BASIS,
21101
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21102
- * See the License for the specific language governing permissions and
21103
- * limitations under the License.
21104
- */
21105
- class BundleLoadResult {
21106
- constructor(progress, changedDocs) {
21107
- this.progress = progress;
21108
- this.changedDocs = changedDocs;
21109
- }
21110
- }
21111
-
21112
21530
  /**
21113
21531
  * @license
21114
21532
  * Copyright 2020 Google LLC
@@ -21163,6 +21581,8 @@ class BundleLoader {
21163
21581
  this.queries = [];
21164
21582
  /** Batched documents to be saved into storage */
21165
21583
  this.documents = [];
21584
+ /** The collection groups affected by this bundle. */
21585
+ this.collectionGroups = new Set();
21166
21586
  this.progress = bundleInitialProgress(bundleMetadata);
21167
21587
  }
21168
21588
  /**
@@ -21182,6 +21602,8 @@ class BundleLoader {
21182
21602
  if (!element.payload.documentMetadata.exists) {
21183
21603
  ++documentsLoaded;
21184
21604
  }
21605
+ const path = ResourcePath.fromString(element.payload.documentMetadata.name);
21606
+ this.collectionGroups.add(path.get(path.length - 2));
21185
21607
  }
21186
21608
  else if (element.payload.document) {
21187
21609
  this.documents[this.documents.length - 1].document =
@@ -21212,13 +21634,17 @@ class BundleLoader {
21212
21634
  * Update the progress to 'Success' and return the updated progress.
21213
21635
  */
21214
21636
  async complete() {
21215
- const changedDocuments = await localStoreApplyBundledDocuments(this.localStore, new BundleConverterImpl(this.serializer), this.documents, this.bundleMetadata.id);
21637
+ const changedDocs = await localStoreApplyBundledDocuments(this.localStore, new BundleConverterImpl(this.serializer), this.documents, this.bundleMetadata.id);
21216
21638
  const queryDocumentMap = this.getQueryDocumentMapping(this.documents);
21217
21639
  for (const q of this.queries) {
21218
21640
  await localStoreSaveNamedQuery(this.localStore, q, queryDocumentMap.get(q.name));
21219
21641
  }
21220
21642
  this.progress.taskState = 'Success';
21221
- return new BundleLoadResult(Object.assign({}, this.progress), changedDocuments);
21643
+ return {
21644
+ progress: this.progress,
21645
+ changedCollectionGroups: this.collectionGroups,
21646
+ changedDocs
21647
+ };
21222
21648
  }
21223
21649
  }
21224
21650
  /**
@@ -22297,9 +22723,9 @@ async function synchronizeViewAndComputeSnapshot(syncEngine, queryView) {
22297
22723
  * snapshots if needed.
22298
22724
  */
22299
22725
  // PORTING NOTE: Multi-Tab only.
22300
- async function syncEngineSynchronizeWithChangedDocuments(syncEngine) {
22726
+ async function syncEngineSynchronizeWithChangedDocuments(syncEngine, collectionGroup) {
22301
22727
  const syncEngineImpl = debugCast(syncEngine);
22302
- return localStoreGetNewDocumentChanges(syncEngineImpl.localStore).then(changes => syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes));
22728
+ return localStoreGetNewDocumentChanges(syncEngineImpl.localStore, collectionGroup).then(changes => syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes));
22303
22729
  }
22304
22730
  /** Applies a mutation state to an existing batch. */
22305
22731
  // PORTING NOTE: Multi-Tab only.
@@ -22464,11 +22890,12 @@ async function syncEngineApplyTargetState(syncEngine, targetId, state, error) {
22464
22890
  logDebug(LOG_TAG$3, 'Ignoring unexpected query state notification.');
22465
22891
  return;
22466
22892
  }
22467
- if (syncEngineImpl.queriesByTarget.has(targetId)) {
22893
+ const query = syncEngineImpl.queriesByTarget.get(targetId);
22894
+ if (query && query.length > 0) {
22468
22895
  switch (state) {
22469
22896
  case 'current':
22470
22897
  case 'not-current': {
22471
- const changes = await localStoreGetNewDocumentChanges(syncEngineImpl.localStore);
22898
+ const changes = await localStoreGetNewDocumentChanges(syncEngineImpl.localStore, queryCollectionGroup(query[0]));
22472
22899
  const synthesizedRemoteEvent = RemoteEvent.createSynthesizedRemoteEventForCurrentChange(targetId, state === 'current');
22473
22900
  await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes, synthesizedRemoteEvent);
22474
22901
  break;
@@ -22551,10 +22978,11 @@ function syncEngineEnsureWriteCallbacks(syncEngine) {
22551
22978
  function syncEngineLoadBundle(syncEngine, bundleReader, task) {
22552
22979
  const syncEngineImpl = debugCast(syncEngine);
22553
22980
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
22554
- loadBundleImpl(syncEngineImpl, bundleReader, task).then(() => {
22555
- syncEngineImpl.sharedClientState.notifyBundleLoaded();
22981
+ loadBundleImpl(syncEngineImpl, bundleReader, task).then(collectionGroups => {
22982
+ syncEngineImpl.sharedClientState.notifyBundleLoaded(collectionGroups);
22556
22983
  });
22557
22984
  }
22985
+ /** Loads a bundle and returns the list of affected collection groups. */
22558
22986
  async function loadBundleImpl(syncEngine, reader, task) {
22559
22987
  try {
22560
22988
  const metadata = await reader.getMetadata();
@@ -22562,7 +22990,7 @@ async function loadBundleImpl(syncEngine, reader, task) {
22562
22990
  if (skip) {
22563
22991
  await reader.close();
22564
22992
  task._completeWith(bundleSuccessProgress(metadata));
22565
- return;
22993
+ return Promise.resolve(new Set());
22566
22994
  }
22567
22995
  task._updateProgress(bundleInitialProgress(metadata));
22568
22996
  const loader = new BundleLoader(metadata, syncEngine.localStore, reader.serializer);
@@ -22576,18 +23004,17 @@ async function loadBundleImpl(syncEngine, reader, task) {
22576
23004
  element = await reader.nextElement();
22577
23005
  }
22578
23006
  const result = await loader.complete();
22579
- // TODO(b/160876443): This currently raises snapshots with
22580
- // `fromCache=false` if users already listen to some queries and bundles
22581
- // has newer version.
22582
23007
  await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngine, result.changedDocs,
22583
23008
  /* remoteEvent */ undefined);
22584
23009
  // Save metadata, so loading the same bundle will skip.
22585
23010
  await localStoreSaveBundle(syncEngine.localStore, metadata);
22586
23011
  task._completeWith(result.progress);
23012
+ return Promise.resolve(result.changedCollectionGroups);
22587
23013
  }
22588
23014
  catch (e) {
22589
23015
  logWarn(LOG_TAG$3, `Loading bundle failed with ${e}`);
22590
23016
  task._failWith(e);
23017
+ return Promise.resolve(new Set());
22591
23018
  }
22592
23019
  }
22593
23020
 
@@ -22656,7 +23083,6 @@ class IndexedDbOfflineComponentProvider extends MemoryOfflineComponentProvider {
22656
23083
  }
22657
23084
  async initialize(cfg) {
22658
23085
  await super.initialize(cfg);
22659
- await localStoreSynchronizeLastDocumentChangeReadTime(this.localStore);
22660
23086
  await this.onlineComponentProvider.initialize(this, cfg);
22661
23087
  // Enqueue writes from a previous session
22662
23088
  await syncEngineEnsureWriteCallbacks(this.onlineComponentProvider.syncEngine);