@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
@@ -33,7 +33,7 @@ var grpc__namespace = /*#__PURE__*/_interopNamespace(grpc);
33
33
  var protoLoader__namespace = /*#__PURE__*/_interopNamespace(protoLoader);
34
34
 
35
35
  const name = "@firebase/firestore";
36
- const version$1 = "3.4.5";
36
+ const version$1 = "3.4.6-canary.3198d58dc";
37
37
 
38
38
  /**
39
39
  * @license
@@ -86,7 +86,7 @@ User.GOOGLE_CREDENTIALS = new User('google-credentials-uid');
86
86
  User.FIRST_PARTY = new User('first-party-uid');
87
87
  User.MOCK_USER = new User('mock-user');
88
88
 
89
- const version = "9.6.7";
89
+ const version = "9.6.9-canary.3198d58dc";
90
90
 
91
91
  /**
92
92
  * @license
@@ -1224,55 +1224,41 @@ function decodeResourcePath(path) {
1224
1224
  }
1225
1225
  return new ResourcePath(segments);
1226
1226
  }
1227
+
1227
1228
  /**
1228
- * Schema Version for the Web client:
1229
- * 1. Initial version including Mutation Queue, Query Cache, and Remote
1230
- * Document Cache
1231
- * 2. Used to ensure a targetGlobal object exists and add targetCount to it. No
1232
- * longer required because migration 3 unconditionally clears it.
1233
- * 3. Dropped and re-created Query Cache to deal with cache corruption related
1234
- * to limbo resolution. Addresses
1235
- * https://github.com/firebase/firebase-ios-sdk/issues/1548
1236
- * 4. Multi-Tab Support.
1237
- * 5. Removal of held write acks.
1238
- * 6. Create document global for tracking document cache size.
1239
- * 7. Ensure every cached document has a sentinel row with a sequence number.
1240
- * 8. Add collection-parent index for Collection Group queries.
1241
- * 9. Change RemoteDocumentChanges store to be keyed by readTime rather than
1242
- * an auto-incrementing ID. This is required for Index-Free queries.
1243
- * 10. Rewrite the canonical IDs to the explicit Protobuf-based format.
1244
- * 11. Add bundles and named_queries for bundle support.
1245
- * 12. Add document overlays.
1246
- * 13. Add indexing support.
1247
- */
1248
- const SCHEMA_VERSION = 12;
1249
- /**
1250
- * Wrapper class to store timestamps (seconds and nanos) in IndexedDb objects.
1229
+ * @license
1230
+ * Copyright 2022 Google LLC
1231
+ *
1232
+ * Licensed under the Apache License, Version 2.0 (the "License");
1233
+ * you may not use this file except in compliance with the License.
1234
+ * You may obtain a copy of the License at
1235
+ *
1236
+ * http://www.apache.org/licenses/LICENSE-2.0
1237
+ *
1238
+ * Unless required by applicable law or agreed to in writing, software
1239
+ * distributed under the License is distributed on an "AS IS" BASIS,
1240
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1241
+ * See the License for the specific language governing permissions and
1242
+ * limitations under the License.
1251
1243
  */
1252
- class DbTimestamp {
1253
- constructor(seconds, nanoseconds) {
1254
- this.seconds = seconds;
1255
- this.nanoseconds = nanoseconds;
1256
- }
1257
- }
1244
+ const DbRemoteDocumentStore$1 = 'remoteDocuments';
1245
+
1258
1246
  /**
1259
- * A singleton object to be stored in the 'owner' store in IndexedDb.
1247
+ * @license
1248
+ * Copyright 2022 Google LLC
1260
1249
  *
1261
- * A given database can have a single primary tab assigned at a given time. That
1262
- * tab must validate that it is still holding the primary lease before every
1263
- * operation that requires locked access. The primary tab should regularly
1264
- * write an updated timestamp to this lease to prevent other tabs from
1265
- * "stealing" the primary lease
1250
+ * Licensed under the Apache License, Version 2.0 (the "License");
1251
+ * you may not use this file except in compliance with the License.
1252
+ * You may obtain a copy of the License at
1253
+ *
1254
+ * http://www.apache.org/licenses/LICENSE-2.0
1255
+ *
1256
+ * Unless required by applicable law or agreed to in writing, software
1257
+ * distributed under the License is distributed on an "AS IS" BASIS,
1258
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1259
+ * See the License for the specific language governing permissions and
1260
+ * limitations under the License.
1266
1261
  */
1267
- class DbPrimaryClient {
1268
- constructor(ownerId,
1269
- /** Whether to allow shared access from multiple tabs. */
1270
- allowTabSynchronization, leaseTimestampMs) {
1271
- this.ownerId = ownerId;
1272
- this.allowTabSynchronization = allowTabSynchronization;
1273
- this.leaseTimestampMs = leaseTimestampMs;
1274
- }
1275
- }
1276
1262
  /**
1277
1263
  * Name of the IndexedDb object store.
1278
1264
  *
@@ -1280,550 +1266,128 @@ class DbPrimaryClient {
1280
1266
  * older clients that only supported single locked access to the persistence
1281
1267
  * layer.
1282
1268
  */
1283
- DbPrimaryClient.store = 'owner';
1269
+ const DbPrimaryClientStore = 'owner';
1284
1270
  /**
1285
1271
  * The key string used for the single object that exists in the
1286
1272
  * DbPrimaryClient store.
1287
1273
  */
1288
- DbPrimaryClient.key = 'owner';
1289
- /**
1290
- * An object to be stored in the 'mutationQueues' store in IndexedDb.
1291
- *
1292
- * Each user gets a single queue of MutationBatches to apply to the server.
1293
- * DbMutationQueue tracks the metadata about the queue.
1294
- */
1295
- class DbMutationQueue {
1296
- constructor(
1297
- /**
1298
- * The normalized user ID to which this queue belongs.
1299
- */
1300
- userId,
1301
- /**
1302
- * An identifier for the highest numbered batch that has been acknowledged
1303
- * by the server. All MutationBatches in this queue with batchIds less
1304
- * than or equal to this value are considered to have been acknowledged by
1305
- * the server.
1306
- *
1307
- * NOTE: this is deprecated and no longer used by the code.
1308
- */
1309
- lastAcknowledgedBatchId,
1310
- /**
1311
- * A stream token that was previously sent by the server.
1312
- *
1313
- * See StreamingWriteRequest in datastore.proto for more details about
1314
- * usage.
1315
- *
1316
- * After sending this token, earlier tokens may not be used anymore so
1317
- * only a single stream token is retained.
1318
- *
1319
- * NOTE: this is deprecated and no longer used by the code.
1320
- */
1321
- lastStreamToken) {
1322
- this.userId = userId;
1323
- this.lastAcknowledgedBatchId = lastAcknowledgedBatchId;
1324
- this.lastStreamToken = lastStreamToken;
1325
- }
1326
- }
1274
+ const DbPrimaryClientKey = 'owner';
1327
1275
  /** Name of the IndexedDb object store. */
1328
- DbMutationQueue.store = 'mutationQueues';
1276
+ const DbMutationQueueStore = 'mutationQueues';
1329
1277
  /** Keys are automatically assigned via the userId property. */
1330
- DbMutationQueue.keyPath = 'userId';
1331
- /**
1332
- * An object to be stored in the 'mutations' store in IndexedDb.
1333
- *
1334
- * Represents a batch of user-level mutations intended to be sent to the server
1335
- * in a single write. Each user-level batch gets a separate DbMutationBatch
1336
- * with a new batchId.
1337
- */
1338
- class DbMutationBatch {
1339
- constructor(
1340
- /**
1341
- * The normalized user ID to which this batch belongs.
1342
- */
1343
- userId,
1344
- /**
1345
- * An identifier for this batch, allocated using an auto-generated key.
1346
- */
1347
- batchId,
1348
- /**
1349
- * The local write time of the batch, stored as milliseconds since the
1350
- * epoch.
1351
- */
1352
- localWriteTimeMs,
1353
- /**
1354
- * A list of "mutations" that represent a partial base state from when this
1355
- * write batch was initially created. During local application of the write
1356
- * batch, these baseMutations are applied prior to the real writes in order
1357
- * to override certain document fields from the remote document cache. This
1358
- * is necessary in the case of non-idempotent writes (e.g. `increment()`
1359
- * transforms) to make sure that the local view of the modified documents
1360
- * doesn't flicker if the remote document cache receives the result of the
1361
- * non-idempotent write before the write is removed from the queue.
1362
- *
1363
- * These mutations are never sent to the backend.
1364
- */
1365
- baseMutations,
1366
- /**
1367
- * A list of mutations to apply. All mutations will be applied atomically.
1368
- *
1369
- * Mutations are serialized via toMutation().
1370
- */
1371
- mutations) {
1372
- this.userId = userId;
1373
- this.batchId = batchId;
1374
- this.localWriteTimeMs = localWriteTimeMs;
1375
- this.baseMutations = baseMutations;
1376
- this.mutations = mutations;
1377
- }
1378
- }
1278
+ const DbMutationQueueKeyPath = 'userId';
1379
1279
  /** Name of the IndexedDb object store. */
1380
- DbMutationBatch.store = 'mutations';
1280
+ const DbMutationBatchStore = 'mutations';
1381
1281
  /** Keys are automatically assigned via the userId, batchId properties. */
1382
- DbMutationBatch.keyPath = 'batchId';
1282
+ const DbMutationBatchKeyPath = 'batchId';
1383
1283
  /** The index name for lookup of mutations by user. */
1384
- DbMutationBatch.userMutationsIndex = 'userMutationsIndex';
1284
+ const DbMutationBatchUserMutationsIndex = 'userMutationsIndex';
1385
1285
  /** The user mutations index is keyed by [userId, batchId] pairs. */
1386
- DbMutationBatch.userMutationsKeyPath = ['userId', 'batchId'];
1286
+ const DbMutationBatchUserMutationsKeyPath = ['userId', 'batchId'];
1387
1287
  /**
1388
- * An object to be stored in the 'documentMutations' store in IndexedDb.
1389
- *
1390
- * A manually maintained index of all the mutation batches that affect a given
1391
- * document key. The rows in this table are references based on the contents of
1392
- * DbMutationBatch.mutations.
1288
+ * Creates a [userId] key for use in the DbDocumentMutations index to iterate
1289
+ * over all of a user's document mutations.
1393
1290
  */
1394
- class DbDocumentMutation {
1395
- constructor() { }
1396
- /**
1397
- * Creates a [userId] key for use in the DbDocumentMutations index to iterate
1398
- * over all of a user's document mutations.
1399
- */
1400
- static prefixForUser(userId) {
1401
- return [userId];
1402
- }
1403
- /**
1404
- * Creates a [userId, encodedPath] key for use in the DbDocumentMutations
1405
- * index to iterate over all at document mutations for a given path or lower.
1406
- */
1407
- static prefixForPath(userId, path) {
1408
- return [userId, encodeResourcePath(path)];
1409
- }
1410
- /**
1411
- * Creates a full index key of [userId, encodedPath, batchId] for inserting
1412
- * and deleting into the DbDocumentMutations index.
1413
- */
1414
- static key(userId, path, batchId) {
1415
- return [userId, encodeResourcePath(path), batchId];
1416
- }
1291
+ function newDbDocumentMutationPrefixForUser(userId) {
1292
+ return [userId];
1417
1293
  }
1418
- DbDocumentMutation.store = 'documentMutations';
1419
- /**
1420
- * Because we store all the useful information for this store in the key,
1421
- * there is no useful information to store as the value. The raw (unencoded)
1422
- * path cannot be stored because IndexedDb doesn't store prototype
1423
- * information.
1424
- */
1425
- DbDocumentMutation.PLACEHOLDER = new DbDocumentMutation();
1426
1294
  /**
1427
- * Represents the known absence of a document at a particular version.
1428
- * Stored in IndexedDb as part of a DbRemoteDocument object.
1295
+ * Creates a [userId, encodedPath] key for use in the DbDocumentMutations
1296
+ * index to iterate over all at document mutations for a given path or lower.
1429
1297
  */
1430
- class DbNoDocument {
1431
- constructor(path, readTime) {
1432
- this.path = path;
1433
- this.readTime = readTime;
1434
- }
1298
+ function newDbDocumentMutationPrefixForPath(userId, path) {
1299
+ return [userId, encodeResourcePath(path)];
1435
1300
  }
1436
1301
  /**
1437
- * Represents a document that is known to exist but whose data is unknown.
1438
- * Stored in IndexedDb as part of a DbRemoteDocument object.
1302
+ * Creates a full index key of [userId, encodedPath, batchId] for inserting
1303
+ * and deleting into the DbDocumentMutations index.
1439
1304
  */
1440
- class DbUnknownDocument {
1441
- constructor(path, version) {
1442
- this.path = path;
1443
- this.version = version;
1444
- }
1305
+ function newDbDocumentMutationKey(userId, path, batchId) {
1306
+ return [userId, encodeResourcePath(path), batchId];
1445
1307
  }
1446
1308
  /**
1447
- * An object to be stored in the 'remoteDocuments' store in IndexedDb.
1448
- * It represents either:
1449
- *
1450
- * - A complete document.
1451
- * - A "no document" representing a document that is known not to exist (at
1452
- * some version).
1453
- * - An "unknown document" representing a document that is known to exist (at
1454
- * some version) but whose contents are unknown.
1455
- *
1456
- * Note: This is the persisted equivalent of a MaybeDocument and could perhaps
1457
- * be made more general if necessary.
1309
+ * Because we store all the useful information for this store in the key,
1310
+ * there is no useful information to store as the value. The raw (unencoded)
1311
+ * path cannot be stored because IndexedDb doesn't store prototype
1312
+ * information.
1458
1313
  */
1459
- class DbRemoteDocument {
1460
- // TODO: We are currently storing full document keys almost three times
1461
- // (once as part of the primary key, once - partly - as `parentPath` and once
1462
- // inside the encoded documents). During our next migration, we should
1463
- // rewrite the primary key as parentPath + document ID which would allow us
1464
- // to drop one value.
1465
- constructor(
1466
- /**
1467
- * Set to an instance of DbUnknownDocument if the data for a document is
1468
- * not known, but it is known that a document exists at the specified
1469
- * version (e.g. it had a successful update applied to it)
1470
- */
1471
- unknownDocument,
1472
- /**
1473
- * Set to an instance of a DbNoDocument if it is known that no document
1474
- * exists.
1475
- */
1476
- noDocument,
1477
- /**
1478
- * Set to an instance of a Document if there's a cached version of the
1479
- * document.
1480
- */
1481
- document,
1482
- /**
1483
- * Documents that were written to the remote document store based on
1484
- * a write acknowledgment are marked with `hasCommittedMutations`. These
1485
- * documents are potentially inconsistent with the backend's copy and use
1486
- * the write's commit version as their document version.
1487
- */
1488
- hasCommittedMutations,
1489
- /**
1490
- * When the document was read from the backend. Undefined for data written
1491
- * prior to schema version 9.
1492
- */
1493
- readTime,
1494
- /**
1495
- * The path of the collection this document is part of. Undefined for data
1496
- * written prior to schema version 9.
1497
- */
1498
- parentPath) {
1499
- this.unknownDocument = unknownDocument;
1500
- this.noDocument = noDocument;
1501
- this.document = document;
1502
- this.hasCommittedMutations = hasCommittedMutations;
1503
- this.readTime = readTime;
1504
- this.parentPath = parentPath;
1505
- }
1506
- }
1507
- DbRemoteDocument.store = 'remoteDocuments';
1314
+ const DbDocumentMutationPlaceholder = {};
1315
+ const DbDocumentMutationStore = 'documentMutations';
1316
+ const DbRemoteDocumentStore = 'remoteDocumentsV14';
1508
1317
  /**
1509
- * An index that provides access to all entries sorted by read time (which
1510
- * corresponds to the last modification time of each row).
1511
- *
1512
- * This index is used to provide a changelog for Multi-Tab.
1318
+ * The primary key of the remote documents store, which allows for efficient
1319
+ * access by collection path and read time.
1513
1320
  */
1514
- DbRemoteDocument.readTimeIndex = 'readTimeIndex';
1515
- DbRemoteDocument.readTimeIndexPath = 'readTime';
1321
+ const DbRemoteDocumentKeyPath = [
1322
+ 'prefixPath',
1323
+ 'collectionGroup',
1324
+ 'readTime',
1325
+ 'documentId'
1326
+ ];
1327
+ /** An index that provides access to documents by key. */
1328
+ const DbRemoteDocumentDocumentKeyIndex = 'documentKeyIndex';
1329
+ const DbRemoteDocumentDocumentKeyIndexPath = [
1330
+ 'prefixPath',
1331
+ 'collectionGroup',
1332
+ 'documentId'
1333
+ ];
1516
1334
  /**
1517
- * An index that provides access to documents in a collection sorted by read
1335
+ * An index that provides access to documents by collection group and read
1518
1336
  * time.
1519
1337
  *
1520
- * This index is used to allow the RemoteDocumentCache to fetch newly changed
1521
- * documents in a collection.
1522
- */
1523
- DbRemoteDocument.collectionReadTimeIndex = 'collectionReadTimeIndex';
1524
- DbRemoteDocument.collectionReadTimeIndexPath = ['parentPath', 'readTime'];
1525
- /**
1526
- * Contains a single entry that has metadata about the remote document cache.
1338
+ * This index is used by the index backfiller.
1527
1339
  */
1528
- class DbRemoteDocumentGlobal {
1529
- /**
1530
- * @param byteSize - Approximately the total size in bytes of all the
1531
- * documents in the document cache.
1532
- */
1533
- constructor(byteSize) {
1534
- this.byteSize = byteSize;
1535
- }
1536
- }
1537
- DbRemoteDocumentGlobal.store = 'remoteDocumentGlobal';
1538
- DbRemoteDocumentGlobal.key = 'remoteDocumentGlobalKey';
1539
- /**
1540
- * An object to be stored in the 'targets' store in IndexedDb.
1541
- *
1542
- * This is based on and should be kept in sync with the proto used in the iOS
1543
- * client.
1544
- *
1545
- * Each query the client listens to against the server is tracked on disk so
1546
- * that the query can be efficiently resumed on restart.
1547
- */
1548
- class DbTarget {
1549
- constructor(
1550
- /**
1551
- * An auto-generated sequential numeric identifier for the query.
1552
- *
1553
- * Queries are stored using their canonicalId as the key, but these
1554
- * canonicalIds can be quite long so we additionally assign a unique
1555
- * queryId which can be used by referenced data structures (e.g.
1556
- * indexes) to minimize the on-disk cost.
1557
- */
1558
- targetId,
1559
- /**
1560
- * The canonical string representing this query. This is not unique.
1561
- */
1562
- canonicalId,
1563
- /**
1564
- * The last readTime received from the Watch Service for this query.
1565
- *
1566
- * This is the same value as TargetChange.read_time in the protos.
1567
- */
1568
- readTime,
1569
- /**
1570
- * An opaque, server-assigned token that allows watching a query to be
1571
- * resumed after disconnecting without retransmitting all the data
1572
- * that matches the query. The resume token essentially identifies a
1573
- * point in time from which the server should resume sending results.
1574
- *
1575
- * This is related to the snapshotVersion in that the resumeToken
1576
- * effectively also encodes that value, but the resumeToken is opaque
1577
- * and sometimes encodes additional information.
1578
- *
1579
- * A consequence of this is that the resumeToken should be used when
1580
- * asking the server to reason about where this client is in the watch
1581
- * stream, but the client should use the snapshotVersion for its own
1582
- * purposes.
1583
- *
1584
- * This is the same value as TargetChange.resume_token in the protos.
1585
- */
1586
- resumeToken,
1587
- /**
1588
- * A sequence number representing the last time this query was
1589
- * listened to, used for garbage collection purposes.
1590
- *
1591
- * Conventionally this would be a timestamp value, but device-local
1592
- * clocks are unreliable and they must be able to create new listens
1593
- * even while disconnected. Instead this should be a monotonically
1594
- * increasing number that's incremented on each listen call.
1595
- *
1596
- * This is different from the queryId since the queryId is an
1597
- * immutable identifier assigned to the Query on first use while
1598
- * lastListenSequenceNumber is updated every time the query is
1599
- * listened to.
1600
- */
1601
- lastListenSequenceNumber,
1602
- /**
1603
- * Denotes the maximum snapshot version at which the associated query view
1604
- * contained no limbo documents. Undefined for data written prior to
1605
- * schema version 9.
1606
- */
1607
- lastLimboFreeSnapshotVersion,
1608
- /**
1609
- * The query for this target.
1610
- *
1611
- * Because canonical ids are not unique we must store the actual query. We
1612
- * use the proto to have an object we can persist without having to
1613
- * duplicate translation logic to and from a `Query` object.
1614
- */
1615
- query) {
1616
- this.targetId = targetId;
1617
- this.canonicalId = canonicalId;
1618
- this.readTime = readTime;
1619
- this.resumeToken = resumeToken;
1620
- this.lastListenSequenceNumber = lastListenSequenceNumber;
1621
- this.lastLimboFreeSnapshotVersion = lastLimboFreeSnapshotVersion;
1622
- this.query = query;
1623
- }
1624
- }
1625
- DbTarget.store = 'targets';
1340
+ const DbRemoteDocumentCollectionGroupIndex = 'collectionGroupIndex';
1341
+ const DbRemoteDocumentCollectionGroupIndexPath = [
1342
+ 'collectionGroup',
1343
+ 'readTime',
1344
+ 'prefixPath',
1345
+ 'documentId'
1346
+ ];
1347
+ const DbRemoteDocumentGlobalStore = 'remoteDocumentGlobal';
1348
+ const DbRemoteDocumentGlobalKey = 'remoteDocumentGlobalKey';
1349
+ const DbTargetStore = 'targets';
1626
1350
  /** Keys are automatically assigned via the targetId property. */
1627
- DbTarget.keyPath = 'targetId';
1351
+ const DbTargetKeyPath = 'targetId';
1628
1352
  /** The name of the queryTargets index. */
1629
- DbTarget.queryTargetsIndexName = 'queryTargetsIndex';
1353
+ const DbTargetQueryTargetsIndexName = 'queryTargetsIndex';
1630
1354
  /**
1631
1355
  * The index of all canonicalIds to the targets that they match. This is not
1632
1356
  * a unique mapping because canonicalId does not promise a unique name for all
1633
1357
  * possible queries, so we append the targetId to make the mapping unique.
1634
1358
  */
1635
- DbTarget.queryTargetsKeyPath = ['canonicalId', 'targetId'];
1636
- /**
1637
- * An object representing an association between a target and a document, or a
1638
- * sentinel row marking the last sequence number at which a document was used.
1639
- * Each document cached must have a corresponding sentinel row before lru
1640
- * garbage collection is enabled.
1641
- *
1642
- * The target associations and sentinel rows are co-located so that orphaned
1643
- * documents and their sequence numbers can be identified efficiently via a scan
1644
- * of this store.
1645
- */
1646
- class DbTargetDocument {
1647
- constructor(
1648
- /**
1649
- * The targetId identifying a target or 0 for a sentinel row.
1650
- */
1651
- targetId,
1652
- /**
1653
- * The path to the document, as encoded in the key.
1654
- */
1655
- path,
1656
- /**
1657
- * If this is a sentinel row, this should be the sequence number of the last
1658
- * time the document specified by `path` was used. Otherwise, it should be
1659
- * `undefined`.
1660
- */
1661
- sequenceNumber) {
1662
- this.targetId = targetId;
1663
- this.path = path;
1664
- this.sequenceNumber = sequenceNumber;
1665
- }
1666
- }
1359
+ const DbTargetQueryTargetsKeyPath = ['canonicalId', 'targetId'];
1667
1360
  /** Name of the IndexedDb object store. */
1668
- DbTargetDocument.store = 'targetDocuments';
1361
+ const DbTargetDocumentStore = 'targetDocuments';
1669
1362
  /** Keys are automatically assigned via the targetId, path properties. */
1670
- DbTargetDocument.keyPath = ['targetId', 'path'];
1363
+ const DbTargetDocumentKeyPath = ['targetId', 'path'];
1671
1364
  /** The index name for the reverse index. */
1672
- DbTargetDocument.documentTargetsIndex = 'documentTargetsIndex';
1365
+ const DbTargetDocumentDocumentTargetsIndex = 'documentTargetsIndex';
1673
1366
  /** We also need to create the reverse index for these properties. */
1674
- DbTargetDocument.documentTargetsKeyPath = ['path', 'targetId'];
1675
- /**
1676
- * A record of global state tracked across all Targets, tracked separately
1677
- * to avoid the need for extra indexes.
1678
- *
1679
- * This should be kept in-sync with the proto used in the iOS client.
1680
- */
1681
- class DbTargetGlobal {
1682
- constructor(
1683
- /**
1684
- * The highest numbered target id across all targets.
1685
- *
1686
- * See DbTarget.targetId.
1687
- */
1688
- highestTargetId,
1689
- /**
1690
- * The highest numbered lastListenSequenceNumber across all targets.
1691
- *
1692
- * See DbTarget.lastListenSequenceNumber.
1693
- */
1694
- highestListenSequenceNumber,
1695
- /**
1696
- * A global snapshot version representing the last consistent snapshot we
1697
- * received from the backend. This is monotonically increasing and any
1698
- * snapshots received from the backend prior to this version (e.g. for
1699
- * targets resumed with a resumeToken) should be suppressed (buffered)
1700
- * until the backend has caught up to this snapshot version again. This
1701
- * prevents our cache from ever going backwards in time.
1702
- */
1703
- lastRemoteSnapshotVersion,
1704
- /**
1705
- * The number of targets persisted.
1706
- */
1707
- targetCount) {
1708
- this.highestTargetId = highestTargetId;
1709
- this.highestListenSequenceNumber = highestListenSequenceNumber;
1710
- this.lastRemoteSnapshotVersion = lastRemoteSnapshotVersion;
1711
- this.targetCount = targetCount;
1712
- }
1713
- }
1367
+ const DbTargetDocumentDocumentTargetsKeyPath = ['path', 'targetId'];
1714
1368
  /**
1715
1369
  * The key string used for the single object that exists in the
1716
1370
  * DbTargetGlobal store.
1717
1371
  */
1718
- DbTargetGlobal.key = 'targetGlobalKey';
1719
- DbTargetGlobal.store = 'targetGlobal';
1720
- /**
1721
- * An object representing an association between a Collection id (e.g. 'messages')
1722
- * to a parent path (e.g. '/chats/123') that contains it as a (sub)collection.
1723
- * This is used to efficiently find all collections to query when performing
1724
- * a Collection Group query.
1725
- */
1726
- class DbCollectionParent {
1727
- constructor(
1728
- /**
1729
- * The collectionId (e.g. 'messages')
1730
- */
1731
- collectionId,
1732
- /**
1733
- * The path to the parent (either a document location or an empty path for
1734
- * a root-level collection).
1735
- */
1736
- parent) {
1737
- this.collectionId = collectionId;
1738
- this.parent = parent;
1739
- }
1740
- }
1372
+ const DbTargetGlobalKey = 'targetGlobalKey';
1373
+ const DbTargetGlobalStore = 'targetGlobal';
1741
1374
  /** Name of the IndexedDb object store. */
1742
- DbCollectionParent.store = 'collectionParents';
1375
+ const DbCollectionParentStore = 'collectionParents';
1743
1376
  /** Keys are automatically assigned via the collectionId, parent properties. */
1744
- DbCollectionParent.keyPath = ['collectionId', 'parent'];
1745
- /**
1746
- * A record of the metadata state of each client.
1747
- *
1748
- * PORTING NOTE: This is used to synchronize multi-tab state and does not need
1749
- * to be ported to iOS or Android.
1750
- */
1751
- class DbClientMetadata {
1752
- constructor(
1753
- // Note: Previous schema versions included a field
1754
- // "lastProcessedDocumentChangeId". Don't use anymore.
1755
- /** The auto-generated client id assigned at client startup. */
1756
- clientId,
1757
- /** The last time this state was updated. */
1758
- updateTimeMs,
1759
- /** Whether the client's network connection is enabled. */
1760
- networkEnabled,
1761
- /** Whether this client is running in a foreground tab. */
1762
- inForeground) {
1763
- this.clientId = clientId;
1764
- this.updateTimeMs = updateTimeMs;
1765
- this.networkEnabled = networkEnabled;
1766
- this.inForeground = inForeground;
1767
- }
1768
- }
1377
+ const DbCollectionParentKeyPath = ['collectionId', 'parent'];
1769
1378
  /** Name of the IndexedDb object store. */
1770
- DbClientMetadata.store = 'clientMetadata';
1379
+ const DbClientMetadataStore = 'clientMetadata';
1771
1380
  /** Keys are automatically assigned via the clientId properties. */
1772
- DbClientMetadata.keyPath = 'clientId';
1773
- /** An object representing a bundle loaded by the SDK. */
1774
- class DbBundle {
1775
- constructor(
1776
- /** The ID of the loaded bundle. */
1777
- bundleId,
1778
- /** The create time of the loaded bundle. */
1779
- createTime,
1780
- /** The schema version of the loaded bundle. */
1781
- version) {
1782
- this.bundleId = bundleId;
1783
- this.createTime = createTime;
1784
- this.version = version;
1785
- }
1786
- }
1381
+ const DbClientMetadataKeyPath = 'clientId';
1787
1382
  /** Name of the IndexedDb object store. */
1788
- DbBundle.store = 'bundles';
1789
- DbBundle.keyPath = 'bundleId';
1790
- /** An object representing a named query loaded by the SDK via a bundle. */
1791
- class DbNamedQuery {
1792
- constructor(
1793
- /** The name of the query. */
1794
- name,
1795
- /** The read time of the results saved in the bundle from the named query. */
1796
- readTime,
1797
- /** The query saved in the bundle. */
1798
- bundledQuery) {
1799
- this.name = name;
1800
- this.readTime = readTime;
1801
- this.bundledQuery = bundledQuery;
1802
- }
1803
- }
1383
+ const DbBundleStore = 'bundles';
1384
+ const DbBundleKeyPath = 'bundleId';
1804
1385
  /** Name of the IndexedDb object store. */
1805
- DbNamedQuery.store = 'namedQueries';
1806
- DbNamedQuery.keyPath = 'name';
1807
- /** An object representing the global configuration for a field index. */
1808
- class DbIndexConfiguration {
1809
- constructor(
1810
- /**
1811
- * The index id for this entry. Undefined for indexes that are not yet
1812
- * persisted.
1813
- */
1814
- indexId,
1815
- /** The collection group this index belongs to. */
1816
- collectionGroup,
1817
- /** The fields to index for this index. */
1818
- fields) {
1819
- this.indexId = indexId;
1820
- this.collectionGroup = collectionGroup;
1821
- this.fields = fields;
1822
- }
1823
- }
1386
+ const DbNamedQueryStore = 'namedQueries';
1387
+ const DbNamedQueryKeyPath = 'name';
1824
1388
  /** Name of the IndexedDb object store. */
1825
- DbIndexConfiguration.store = 'indexConfiguration';
1826
- DbIndexConfiguration.keyPath = 'indexId';
1389
+ const DbIndexConfigurationStore = 'indexConfiguration';
1390
+ const DbIndexConfigurationKeyPath = 'indexId';
1827
1391
  /**
1828
1392
  * An index that provides access to the index configurations by collection
1829
1393
  * group.
@@ -1832,49 +1396,11 @@ DbIndexConfiguration.keyPath = 'indexId';
1832
1396
  * not possible here as the Web client supports concurrent access to
1833
1397
  * persistence via multi-tab.
1834
1398
  */
1835
- DbIndexConfiguration.collectionGroupIndex = 'collectionGroupIndex';
1836
- DbIndexConfiguration.collectionGroupIndexPath = 'collectionGroup';
1837
- /**
1838
- * An object describing how up-to-date the index backfill is for each user and
1839
- * index.
1840
- */
1841
- class DbIndexState {
1842
- constructor(
1843
- /** The index id for this entry. */
1844
- indexId,
1845
- /** The user id for this entry. */
1846
- uid,
1847
- /**
1848
- * A number that indicates when the index was last updated (relative to
1849
- * other indexes).
1850
- */
1851
- sequenceNumber,
1852
- /**
1853
- * The latest read time that has been indexed by Firestore for this field
1854
- * index. Set to `{seconds: 0, nanos: 0}` if no documents have been indexed.
1855
- */
1856
- readTime,
1857
- /**
1858
- * The last document that has been indexed for this field index. Empty if
1859
- * no documents have been indexed.
1860
- */
1861
- documentKey,
1862
- /**
1863
- * The largest mutation batch id that has been processed for this index. -1
1864
- * if no mutations have been indexed.
1865
- */
1866
- largestBatchId) {
1867
- this.indexId = indexId;
1868
- this.uid = uid;
1869
- this.sequenceNumber = sequenceNumber;
1870
- this.readTime = readTime;
1871
- this.documentKey = documentKey;
1872
- this.largestBatchId = largestBatchId;
1873
- }
1874
- }
1399
+ const DbIndexConfigurationCollectionGroupIndex = 'collectionGroupIndex';
1400
+ const DbIndexConfigurationCollectionGroupIndexPath = 'collectionGroup';
1875
1401
  /** Name of the IndexedDb object store. */
1876
- DbIndexState.store = 'indexState';
1877
- DbIndexState.keyPath = ['indexId', 'uid'];
1402
+ const DbIndexStateStore = 'indexState';
1403
+ const DbIndexStateKeyPath = ['indexId', 'uid'];
1878
1404
  /**
1879
1405
  * An index that provides access to documents in a collection sorted by last
1880
1406
  * update time. Used by the backfiller.
@@ -1883,112 +1409,89 @@ DbIndexState.keyPath = ['indexId', 'uid'];
1883
1409
  * not possible here as the Web client supports concurrent access to
1884
1410
  * persistence via multi-tab.
1885
1411
  */
1886
- DbIndexState.sequenceNumberIndex = 'sequenceNumberIndex';
1887
- DbIndexState.sequenceNumberIndexPath = ['uid', 'sequenceNumber'];
1888
- /** An object that stores the encoded entries for all documents and fields. */
1889
- class DbIndexEntry {
1890
- constructor(
1891
- /** The index id for this entry. */
1892
- indexId,
1893
- /** The user id for this entry. */
1894
- uid,
1895
- /** The encoded array index value for this entry. */
1896
- arrayValue,
1897
- /** The encoded directional value for equality and inequality filters. */
1898
- directionalValue,
1899
- /** The document key this entry points to. */
1900
- documentKey) {
1901
- this.indexId = indexId;
1902
- this.uid = uid;
1903
- this.arrayValue = arrayValue;
1904
- this.directionalValue = directionalValue;
1905
- this.documentKey = documentKey;
1906
- }
1907
- }
1412
+ const DbIndexStateSequenceNumberIndex = 'sequenceNumberIndex';
1413
+ const DbIndexStateSequenceNumberIndexPath = ['uid', 'sequenceNumber'];
1908
1414
  /** Name of the IndexedDb object store. */
1909
- DbIndexEntry.store = 'indexEntries';
1910
- DbIndexEntry.keyPath = [
1415
+ const DbIndexEntryStore = 'indexEntries';
1416
+ const DbIndexEntryKeyPath = [
1911
1417
  'indexId',
1912
1418
  'uid',
1913
1419
  'arrayValue',
1914
1420
  'directionalValue',
1915
1421
  'documentKey'
1916
1422
  ];
1917
- DbIndexEntry.documentKeyIndex = 'documentKeyIndex';
1918
- DbIndexEntry.documentKeyIndexPath = ['indexId', 'uid', 'documentKey'];
1919
- /**
1920
- * An object representing a document overlay.
1921
- */
1922
- class DbDocumentOverlay {
1923
- constructor(
1924
- /** The user ID to whom this overlay belongs. */
1925
- userId,
1926
- /** The path to the collection that contains the document. */
1927
- collectionPath,
1928
- /** The ID (key) of the document within the collection. */
1929
- documentId,
1930
- /** The collection group to which the document belongs. */
1931
- collectionGroup,
1932
- /** The largest batch ID that's been applied for this overlay. */
1933
- largestBatchId,
1934
- /** The overlay mutation. */
1935
- overlayMutation) {
1936
- this.userId = userId;
1937
- this.collectionPath = collectionPath;
1938
- this.documentId = documentId;
1939
- this.collectionGroup = collectionGroup;
1940
- this.largestBatchId = largestBatchId;
1941
- this.overlayMutation = overlayMutation;
1942
- }
1943
- }
1423
+ const DbIndexEntryDocumentKeyIndex = 'documentKeyIndex';
1424
+ const DbIndexEntryDocumentKeyIndexPath = [
1425
+ 'indexId',
1426
+ 'uid',
1427
+ 'documentKey'
1428
+ ];
1944
1429
  /** Name of the IndexedDb object store. */
1945
- DbDocumentOverlay.store = 'documentOverlays';
1946
- DbDocumentOverlay.keyPath = ['userId', 'collectionPath', 'documentId'];
1947
- DbDocumentOverlay.collectionPathOverlayIndex = 'collectionPathOverlayIndex';
1948
- DbDocumentOverlay.collectionPathOverlayIndexPath = [
1430
+ const DbDocumentOverlayStore = 'documentOverlays';
1431
+ const DbDocumentOverlayKeyPath = [
1432
+ 'userId',
1433
+ 'collectionPath',
1434
+ 'documentId'
1435
+ ];
1436
+ const DbDocumentOverlayCollectionPathOverlayIndex = 'collectionPathOverlayIndex';
1437
+ const DbDocumentOverlayCollectionPathOverlayIndexPath = [
1949
1438
  'userId',
1950
1439
  'collectionPath',
1951
1440
  'largestBatchId'
1952
1441
  ];
1953
- DbDocumentOverlay.collectionGroupOverlayIndex = 'collectionGroupOverlayIndex';
1954
- DbDocumentOverlay.collectionGroupOverlayIndexPath = [
1442
+ const DbDocumentOverlayCollectionGroupOverlayIndex = 'collectionGroupOverlayIndex';
1443
+ const DbDocumentOverlayCollectionGroupOverlayIndexPath = [
1955
1444
  'userId',
1956
1445
  'collectionGroup',
1957
1446
  'largestBatchId'
1958
1447
  ];
1959
1448
  // Visible for testing
1960
1449
  const V1_STORES = [
1961
- DbMutationQueue.store,
1962
- DbMutationBatch.store,
1963
- DbDocumentMutation.store,
1964
- DbRemoteDocument.store,
1965
- DbTarget.store,
1966
- DbPrimaryClient.store,
1967
- DbTargetGlobal.store,
1968
- DbTargetDocument.store
1450
+ DbMutationQueueStore,
1451
+ DbMutationBatchStore,
1452
+ DbDocumentMutationStore,
1453
+ DbRemoteDocumentStore$1,
1454
+ DbTargetStore,
1455
+ DbPrimaryClientStore,
1456
+ DbTargetGlobalStore,
1457
+ DbTargetDocumentStore
1969
1458
  ];
1970
- // V2 is no longer usable (see comment at top of file)
1971
1459
  // Visible for testing
1972
1460
  const V3_STORES = V1_STORES;
1973
1461
  // Note: DbRemoteDocumentChanges is no longer used and dropped with v9.
1974
- const V4_STORES = [...V3_STORES, DbClientMetadata.store];
1975
- // V5 does not change the set of stores.
1976
- const V6_STORES = [...V4_STORES, DbRemoteDocumentGlobal.store];
1977
- // V7 does not change the set of stores.
1978
- const V8_STORES = [...V6_STORES, DbCollectionParent.store];
1979
- // V9 does not change the set of stores.
1980
- // V10 does not change the set of stores.
1981
- const V11_STORES = [...V8_STORES, DbBundle.store, DbNamedQuery.store];
1982
- const V12_STORES = [...V11_STORES, DbDocumentOverlay.store];
1462
+ const V4_STORES = [...V3_STORES, DbClientMetadataStore];
1463
+ const V6_STORES = [...V4_STORES, DbRemoteDocumentGlobalStore];
1464
+ const V8_STORES = [...V6_STORES, DbCollectionParentStore];
1465
+ const V11_STORES = [...V8_STORES, DbBundleStore, DbNamedQueryStore];
1466
+ const V12_STORES = [...V11_STORES, DbDocumentOverlayStore];
1983
1467
  const V13_STORES = [
1984
- ...V12_STORES,
1985
- DbIndexConfiguration.store,
1986
- DbIndexState.store,
1987
- DbIndexEntry.store
1468
+ DbMutationQueueStore,
1469
+ DbMutationBatchStore,
1470
+ DbDocumentMutationStore,
1471
+ DbRemoteDocumentStore,
1472
+ DbTargetStore,
1473
+ DbPrimaryClientStore,
1474
+ DbTargetGlobalStore,
1475
+ DbTargetDocumentStore,
1476
+ DbClientMetadataStore,
1477
+ DbRemoteDocumentGlobalStore,
1478
+ DbCollectionParentStore,
1479
+ DbBundleStore,
1480
+ DbNamedQueryStore,
1481
+ DbDocumentOverlayStore
1482
+ ];
1483
+ const V14_STORES = [
1484
+ ...V13_STORES,
1485
+ DbIndexConfigurationStore,
1486
+ DbIndexStateStore,
1487
+ DbIndexEntryStore
1988
1488
  ];
1989
1489
  /** Returns the object stores for the provided schema. */
1990
1490
  function getObjectStores(schemaVersion) {
1991
- if (schemaVersion === 13) {
1491
+ if (schemaVersion === 14) {
1492
+ return V14_STORES;
1493
+ }
1494
+ else if (schemaVersion === 13) {
1992
1495
  return V13_STORES;
1993
1496
  }
1994
1497
  else if (schemaVersion === 12) {
@@ -2702,6 +2205,21 @@ class SimpleDbStore {
2702
2205
  });
2703
2206
  }
2704
2207
  }
2208
+ /**
2209
+ * Loads the first `count` elements from the provided index range. Loads all
2210
+ * elements if no limit is provided.
2211
+ */
2212
+ loadFirst(range, count) {
2213
+ const request = this.store.getAll(range, count === null ? undefined : count);
2214
+ return new PersistencePromise((resolve, reject) => {
2215
+ request.onerror = (event) => {
2216
+ reject(event.target.error);
2217
+ };
2218
+ request.onsuccess = (event) => {
2219
+ resolve(event.target.result);
2220
+ };
2221
+ });
2222
+ }
2705
2223
  deleteAll(indexOrRange, range) {
2706
2224
  logDebug(LOG_TAG$h, 'DELETE ALL', this.store.name);
2707
2225
  const options = this.options(indexOrRange, range);
@@ -3184,6 +2702,9 @@ class SnapshotVersion {
3184
2702
  static min() {
3185
2703
  return new SnapshotVersion(new Timestamp(0, 0));
3186
2704
  }
2705
+ static max() {
2706
+ return new SnapshotVersion(new Timestamp(253402300799, 1e9 - 1));
2707
+ }
3187
2708
  compareTo(other) {
3188
2709
  return this.timestamp._compareTo(other.timestamp);
3189
2710
  }
@@ -3312,17 +2833,15 @@ class FieldMask {
3312
2833
  * See the License for the specific language governing permissions and
3313
2834
  * limitations under the License.
3314
2835
  */
2836
+ /** Converts a Base64 encoded string to a binary string. */
3315
2837
  function decodeBase64(encoded) {
3316
- // Node actually doesn't validate base64 strings.
3317
- // A quick sanity check that is not a fool-proof validation
3318
- if (/[^-A-Za-z0-9+/=]/.test(encoded)) {
3319
- throw new FirestoreError(Code.INVALID_ARGUMENT, 'Not a valid Base64 string: ' + encoded);
3320
- }
3321
- return new Buffer(encoded, 'base64').toString('binary');
2838
+ // Note: We used to validate the base64 string here via a regular expression.
2839
+ // This was removed to improve the performance of indexing.
2840
+ return Buffer.from(encoded, 'base64').toString('binary');
3322
2841
  }
3323
2842
  /** Converts a binary string to a Base64 encoded string. */
3324
2843
  function encodeBase64(raw) {
3325
- return new Buffer(raw, 'binary').toString('base64');
2844
+ return Buffer.from(raw, 'binary').toString('base64');
3326
2845
  }
3327
2846
  /** True if and only if the Base64 conversion functions are available. */
3328
2847
  function isBase64Available() {
@@ -3363,6 +2882,8 @@ class ByteString {
3363
2882
  return new ByteString(binaryString);
3364
2883
  }
3365
2884
  static fromUint8Array(array) {
2885
+ // TODO(indexing); Remove the copy of the byte string here as this method
2886
+ // is frequently called during indexing.
3366
2887
  const binaryString = binaryStringFromUint8Array(array);
3367
2888
  return new ByteString(binaryString);
3368
2889
  }
@@ -3781,13 +3302,17 @@ class DocumentKey {
3781
3302
  * See the License for the specific language governing permissions and
3782
3303
  * limitations under the License.
3783
3304
  */
3305
+ const MAX_VALUE_TYPE = '__max__';
3784
3306
  const MAX_VALUE = {
3785
3307
  mapValue: {
3786
3308
  fields: {
3787
- '__type__': { stringValue: '__max___' }
3309
+ '__type__': { stringValue: MAX_VALUE_TYPE }
3788
3310
  }
3789
3311
  }
3790
3312
  };
3313
+ const MIN_VALUE = {
3314
+ nullValue: 'NULL_VALUE'
3315
+ };
3791
3316
  /** Extracts the backend's type order for the provided value. */
3792
3317
  function typeOrder(value) {
3793
3318
  if ('nullValue' in value) {
@@ -3821,6 +3346,9 @@ function typeOrder(value) {
3821
3346
  if (isServerTimestamp(value)) {
3822
3347
  return 4 /* ServerTimestampValue */;
3823
3348
  }
3349
+ else if (isMaxValue(value)) {
3350
+ return 9 /* ArrayValue */;
3351
+ }
3824
3352
  return 10 /* ObjectValue */;
3825
3353
  }
3826
3354
  else {
@@ -3860,6 +3388,8 @@ function valueEquals(left, right) {
3860
3388
  return arrayEquals(left.arrayValue.values || [], right.arrayValue.values || [], valueEquals);
3861
3389
  case 10 /* ObjectValue */:
3862
3390
  return objectEquals(left, right);
3391
+ case 9007199254740991 /* MaxValue */:
3392
+ return true;
3863
3393
  default:
3864
3394
  return fail();
3865
3395
  }
@@ -3932,6 +3462,7 @@ function valueCompare(left, right) {
3932
3462
  }
3933
3463
  switch (leftType) {
3934
3464
  case 0 /* NullValue */:
3465
+ case 9007199254740991 /* MaxValue */:
3935
3466
  return 0;
3936
3467
  case 1 /* BooleanValue */:
3937
3468
  return primitiveComparator(left.booleanValue, right.booleanValue);
@@ -4200,7 +3731,102 @@ function deepClone(source) {
4200
3731
  }
4201
3732
  /** Returns true if the Value represents the canonical {@link #MAX_VALUE} . */
4202
3733
  function isMaxValue(value) {
4203
- return valueEquals(value, MAX_VALUE);
3734
+ return ((((value.mapValue || {}).fields || {})['__type__'] || {}).stringValue ===
3735
+ MAX_VALUE_TYPE);
3736
+ }
3737
+ /** Returns the lowest value for the given value type (inclusive). */
3738
+ function valuesGetLowerBound(value) {
3739
+ if ('nullValue' in value) {
3740
+ return MIN_VALUE;
3741
+ }
3742
+ else if ('booleanValue' in value) {
3743
+ return { booleanValue: false };
3744
+ }
3745
+ else if ('integerValue' in value || 'doubleValue' in value) {
3746
+ return { doubleValue: NaN };
3747
+ }
3748
+ else if ('timestampValue' in value) {
3749
+ return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
3750
+ }
3751
+ else if ('stringValue' in value) {
3752
+ return { stringValue: '' };
3753
+ }
3754
+ else if ('bytesValue' in value) {
3755
+ return { bytesValue: '' };
3756
+ }
3757
+ else if ('referenceValue' in value) {
3758
+ return refValue(DatabaseId.empty(), DocumentKey.empty());
3759
+ }
3760
+ else if ('geoPointValue' in value) {
3761
+ return { geoPointValue: { latitude: -90, longitude: -180 } };
3762
+ }
3763
+ else if ('arrayValue' in value) {
3764
+ return { arrayValue: {} };
3765
+ }
3766
+ else if ('mapValue' in value) {
3767
+ return { mapValue: {} };
3768
+ }
3769
+ else {
3770
+ return fail();
3771
+ }
3772
+ }
3773
+ /** Returns the largest value for the given value type (exclusive). */
3774
+ function valuesGetUpperBound(value) {
3775
+ if ('nullValue' in value) {
3776
+ return { booleanValue: false };
3777
+ }
3778
+ else if ('booleanValue' in value) {
3779
+ return { doubleValue: NaN };
3780
+ }
3781
+ else if ('integerValue' in value || 'doubleValue' in value) {
3782
+ return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
3783
+ }
3784
+ else if ('timestampValue' in value) {
3785
+ return { stringValue: '' };
3786
+ }
3787
+ else if ('stringValue' in value) {
3788
+ return { bytesValue: '' };
3789
+ }
3790
+ else if ('bytesValue' in value) {
3791
+ return refValue(DatabaseId.empty(), DocumentKey.empty());
3792
+ }
3793
+ else if ('referenceValue' in value) {
3794
+ return { geoPointValue: { latitude: -90, longitude: -180 } };
3795
+ }
3796
+ else if ('geoPointValue' in value) {
3797
+ return { arrayValue: {} };
3798
+ }
3799
+ else if ('arrayValue' in value) {
3800
+ return { mapValue: {} };
3801
+ }
3802
+ else if ('mapValue' in value) {
3803
+ return MAX_VALUE;
3804
+ }
3805
+ else {
3806
+ return fail();
3807
+ }
3808
+ }
3809
+ function valuesMax(left, right) {
3810
+ if (left === undefined) {
3811
+ return right;
3812
+ }
3813
+ else if (right === undefined) {
3814
+ return left;
3815
+ }
3816
+ else {
3817
+ return valueCompare(left, right) > 0 ? left : right;
3818
+ }
3819
+ }
3820
+ function valuesMin(left, right) {
3821
+ if (left === undefined) {
3822
+ return right;
3823
+ }
3824
+ else if (right === undefined) {
3825
+ return left;
3826
+ }
3827
+ else {
3828
+ return valueCompare(left, right) < 0 ? left : right;
3829
+ }
4204
3830
  }
4205
3831
 
4206
3832
  /**
@@ -4595,6 +4221,10 @@ function fieldIndexGetArraySegment(fieldIndex) {
4595
4221
  function fieldIndexGetDirectionalSegments(fieldIndex) {
4596
4222
  return fieldIndex.fields.filter(s => s.kind !== 2 /* CONTAINS */);
4597
4223
  }
4224
+ /** Returns a debug representation of the field index */
4225
+ function fieldIndexToString(fieldIndex) {
4226
+ return `id=${fieldIndex.indexId}|cg=${fieldIndex.collectionGroup}|f=${fieldIndex.fields.map(f => `${f.fieldPath}:${f.kind}`).join(',')}`;
4227
+ }
4598
4228
  /** An index component consisting of field path and index type. */
4599
4229
  class IndexSegment {
4600
4230
  constructor(
@@ -4626,6 +4256,28 @@ class IndexState {
4626
4256
  return new IndexState(INITIAL_SEQUENCE_NUMBER, IndexOffset.min());
4627
4257
  }
4628
4258
  }
4259
+ /**
4260
+ * Creates an offset that matches all documents with a read time higher than
4261
+ * `readTime`.
4262
+ */
4263
+ function newIndexOffsetSuccessorFromReadTime(readTime, largestBatchId) {
4264
+ // We want to create an offset that matches all documents with a read time
4265
+ // greater than the provided read time. To do so, we technically need to
4266
+ // create an offset for `(readTime, MAX_DOCUMENT_KEY)`. While we could use
4267
+ // Unicode codepoints to generate MAX_DOCUMENT_KEY, it is much easier to use
4268
+ // `(readTime + 1, DocumentKey.empty())` since `> DocumentKey.empty()` matches
4269
+ // all valid document IDs.
4270
+ const successorSeconds = readTime.toTimestamp().seconds;
4271
+ const successorNanos = readTime.toTimestamp().nanoseconds + 1;
4272
+ const successor = SnapshotVersion.fromTimestamp(successorNanos === 1e9
4273
+ ? new Timestamp(successorSeconds + 1, 0)
4274
+ : new Timestamp(successorSeconds, successorNanos));
4275
+ return new IndexOffset(successor, DocumentKey.empty(), largestBatchId);
4276
+ }
4277
+ /** Creates a new offset based on the provided document. */
4278
+ function newIndexOffsetFromDocument(document) {
4279
+ return new IndexOffset(document.readTime, document.key, INITIAL_LARGEST_BATCH_ID);
4280
+ }
4629
4281
  /**
4630
4282
  * Stores the latest read time, document and batch ID that were processed for an
4631
4283
  * index.
@@ -4650,10 +4302,25 @@ class IndexOffset {
4650
4302
  this.documentKey = documentKey;
4651
4303
  this.largestBatchId = largestBatchId;
4652
4304
  }
4653
- /** The state of an index that has not yet been backfilled. */
4305
+ /** Returns an offset that sorts before all regular offsets. */
4654
4306
  static min() {
4655
4307
  return new IndexOffset(SnapshotVersion.min(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
4656
4308
  }
4309
+ /** Returns an offset that sorts after all regular offsets. */
4310
+ static max() {
4311
+ return new IndexOffset(SnapshotVersion.max(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
4312
+ }
4313
+ }
4314
+ function indexOffsetComparator(left, right) {
4315
+ let cmp = left.readTime.compareTo(right.readTime);
4316
+ if (cmp !== 0) {
4317
+ return cmp;
4318
+ }
4319
+ cmp = DocumentKey.comparator(left.documentKey, right.documentKey);
4320
+ if (cmp !== 0) {
4321
+ return cmp;
4322
+ }
4323
+ return primitiveComparator(left.largestBatchId, right.largestBatchId);
4657
4324
  }
4658
4325
 
4659
4326
  /**
@@ -4766,30 +4433,215 @@ function targetEquals(left, right) {
4766
4433
  if (!orderByEquals(left.orderBy[i], right.orderBy[i])) {
4767
4434
  return false;
4768
4435
  }
4769
- }
4770
- if (left.filters.length !== right.filters.length) {
4771
- return false;
4772
- }
4773
- for (let i = 0; i < left.filters.length; i++) {
4774
- if (!filterEquals(left.filters[i], right.filters[i])) {
4775
- return false;
4436
+ }
4437
+ if (left.filters.length !== right.filters.length) {
4438
+ return false;
4439
+ }
4440
+ for (let i = 0; i < left.filters.length; i++) {
4441
+ if (!filterEquals(left.filters[i], right.filters[i])) {
4442
+ return false;
4443
+ }
4444
+ }
4445
+ if (left.collectionGroup !== right.collectionGroup) {
4446
+ return false;
4447
+ }
4448
+ if (!left.path.isEqual(right.path)) {
4449
+ return false;
4450
+ }
4451
+ if (!boundEquals(left.startAt, right.startAt)) {
4452
+ return false;
4453
+ }
4454
+ return boundEquals(left.endAt, right.endAt);
4455
+ }
4456
+ function targetIsDocumentTarget(target) {
4457
+ return (DocumentKey.isDocumentKey(target.path) &&
4458
+ target.collectionGroup === null &&
4459
+ target.filters.length === 0);
4460
+ }
4461
+ /** Returns the field filters that target the given field path. */
4462
+ function targetGetFieldFiltersForPath(target, path) {
4463
+ return target.filters.filter(f => f instanceof FieldFilter && f.field.isEqual(path));
4464
+ }
4465
+ /**
4466
+ * Returns the values that are used in ARRAY_CONTAINS or ARRAY_CONTAINS_ANY
4467
+ * filters. Returns `null` if there are no such filters.
4468
+ */
4469
+ function targetGetArrayValues(target, fieldIndex) {
4470
+ const segment = fieldIndexGetArraySegment(fieldIndex);
4471
+ if (segment === undefined) {
4472
+ return null;
4473
+ }
4474
+ for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
4475
+ switch (fieldFilter.op) {
4476
+ case "array-contains-any" /* ARRAY_CONTAINS_ANY */:
4477
+ return fieldFilter.value.arrayValue.values || [];
4478
+ case "array-contains" /* ARRAY_CONTAINS */:
4479
+ return [fieldFilter.value];
4480
+ // Remaining filters are not array filters.
4481
+ }
4482
+ }
4483
+ return null;
4484
+ }
4485
+ /**
4486
+ * Returns the list of values that are used in != or NOT_IN filters. Returns
4487
+ * `null` if there are no such filters.
4488
+ */
4489
+ function targetGetNotInValues(target, fieldIndex) {
4490
+ const values = new Map();
4491
+ for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
4492
+ for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
4493
+ switch (fieldFilter.op) {
4494
+ case "==" /* EQUAL */:
4495
+ case "in" /* IN */:
4496
+ // Encode equality prefix, which is encoded in the index value before
4497
+ // the inequality (e.g. `a == 'a' && b != 'b'` is encoded to
4498
+ // `value != 'ab'`).
4499
+ values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
4500
+ break;
4501
+ case "not-in" /* NOT_IN */:
4502
+ case "!=" /* NOT_EQUAL */:
4503
+ // NotIn/NotEqual is always a suffix. There cannot be any remaining
4504
+ // segments and hence we can return early here.
4505
+ values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
4506
+ return Array.from(values.values());
4507
+ // Remaining filters cannot be used as notIn bounds.
4508
+ }
4509
+ }
4510
+ }
4511
+ return null;
4512
+ }
4513
+ /**
4514
+ * Returns a lower bound of field values that can be used as a starting point to
4515
+ * scan the index defined by `fieldIndex`. Returns `null` if no lower bound
4516
+ * exists.
4517
+ */
4518
+ function targetGetLowerBound(target, fieldIndex) {
4519
+ const values = [];
4520
+ let inclusive = true;
4521
+ // For each segment, retrieve a lower bound if there is a suitable filter or
4522
+ // startAt.
4523
+ for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
4524
+ let segmentValue = undefined;
4525
+ let segmentInclusive = true;
4526
+ // Process all filters to find a value for the current field segment
4527
+ for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
4528
+ let filterValue = undefined;
4529
+ let filterInclusive = true;
4530
+ switch (fieldFilter.op) {
4531
+ case "<" /* LESS_THAN */:
4532
+ case "<=" /* LESS_THAN_OR_EQUAL */:
4533
+ filterValue = valuesGetLowerBound(fieldFilter.value);
4534
+ break;
4535
+ case "==" /* EQUAL */:
4536
+ case "in" /* IN */:
4537
+ case ">=" /* GREATER_THAN_OR_EQUAL */:
4538
+ filterValue = fieldFilter.value;
4539
+ break;
4540
+ case ">" /* GREATER_THAN */:
4541
+ filterValue = fieldFilter.value;
4542
+ filterInclusive = false;
4543
+ break;
4544
+ case "!=" /* NOT_EQUAL */:
4545
+ case "not-in" /* NOT_IN */:
4546
+ filterValue = MIN_VALUE;
4547
+ break;
4548
+ // Remaining filters cannot be used as lower bounds.
4549
+ }
4550
+ if (valuesMax(segmentValue, filterValue) === filterValue) {
4551
+ segmentValue = filterValue;
4552
+ segmentInclusive = filterInclusive;
4553
+ }
4554
+ }
4555
+ // If there is a startAt bound, compare the values against the existing
4556
+ // boundary to see if we can narrow the scope.
4557
+ if (target.startAt !== null) {
4558
+ for (let i = 0; i < target.orderBy.length; ++i) {
4559
+ const orderBy = target.orderBy[i];
4560
+ if (orderBy.field.isEqual(segment.fieldPath)) {
4561
+ const cursorValue = target.startAt.position[i];
4562
+ if (valuesMax(segmentValue, cursorValue) === cursorValue) {
4563
+ segmentValue = cursorValue;
4564
+ segmentInclusive = target.startAt.inclusive;
4565
+ }
4566
+ break;
4567
+ }
4568
+ }
4569
+ }
4570
+ if (segmentValue === undefined) {
4571
+ // No lower bound exists
4572
+ return null;
4573
+ }
4574
+ values.push(segmentValue);
4575
+ inclusive && (inclusive = segmentInclusive);
4576
+ }
4577
+ return new Bound(values, inclusive);
4578
+ }
4579
+ /**
4580
+ * Returns an upper bound of field values that can be used as an ending point
4581
+ * when scanning the index defined by `fieldIndex`. Returns `null` if no
4582
+ * upper bound exists.
4583
+ */
4584
+ function targetGetUpperBound(target, fieldIndex) {
4585
+ const values = [];
4586
+ let inclusive = true;
4587
+ // For each segment, retrieve an upper bound if there is a suitable filter or
4588
+ // endAt.
4589
+ for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
4590
+ let segmentValue = undefined;
4591
+ let segmentInclusive = true;
4592
+ // Process all filters to find a value for the current field segment
4593
+ for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
4594
+ let filterValue = undefined;
4595
+ let filterInclusive = true;
4596
+ switch (fieldFilter.op) {
4597
+ case ">=" /* GREATER_THAN_OR_EQUAL */:
4598
+ case ">" /* GREATER_THAN */:
4599
+ filterValue = valuesGetUpperBound(fieldFilter.value);
4600
+ filterInclusive = false;
4601
+ break;
4602
+ case "==" /* EQUAL */:
4603
+ case "in" /* IN */:
4604
+ case "<=" /* LESS_THAN_OR_EQUAL */:
4605
+ filterValue = fieldFilter.value;
4606
+ break;
4607
+ case "<" /* LESS_THAN */:
4608
+ filterValue = fieldFilter.value;
4609
+ filterInclusive = false;
4610
+ break;
4611
+ case "!=" /* NOT_EQUAL */:
4612
+ case "not-in" /* NOT_IN */:
4613
+ filterValue = MAX_VALUE;
4614
+ break;
4615
+ // Remaining filters cannot be used as upper bounds.
4616
+ }
4617
+ if (valuesMin(segmentValue, filterValue) === filterValue) {
4618
+ segmentValue = filterValue;
4619
+ segmentInclusive = filterInclusive;
4620
+ }
4621
+ }
4622
+ // If there is a endAt bound, compare the values against the existing
4623
+ // boundary to see if we can narrow the scope.
4624
+ if (target.endAt !== null) {
4625
+ for (let i = 0; i < target.orderBy.length; ++i) {
4626
+ const orderBy = target.orderBy[i];
4627
+ if (orderBy.field.isEqual(segment.fieldPath)) {
4628
+ const cursorValue = target.endAt.position[i];
4629
+ if (valuesMin(segmentValue, cursorValue) === cursorValue) {
4630
+ segmentValue = cursorValue;
4631
+ segmentInclusive = target.endAt.inclusive;
4632
+ }
4633
+ break;
4634
+ }
4635
+ }
4636
+ }
4637
+ if (segmentValue === undefined) {
4638
+ // No upper bound exists
4639
+ return null;
4776
4640
  }
4641
+ values.push(segmentValue);
4642
+ inclusive && (inclusive = segmentInclusive);
4777
4643
  }
4778
- if (left.collectionGroup !== right.collectionGroup) {
4779
- return false;
4780
- }
4781
- if (!left.path.isEqual(right.path)) {
4782
- return false;
4783
- }
4784
- if (!boundEquals(left.startAt, right.startAt)) {
4785
- return false;
4786
- }
4787
- return boundEquals(left.endAt, right.endAt);
4788
- }
4789
- function targetIsDocumentTarget(target) {
4790
- return (DocumentKey.isDocumentKey(target.path) &&
4791
- target.collectionGroup === null &&
4792
- target.filters.length === 0);
4644
+ return new Bound(values, inclusive);
4793
4645
  }
4794
4646
  class Filter {
4795
4647
  }
@@ -5361,6 +5213,18 @@ function queryMatchesBounds(query, doc) {
5361
5213
  }
5362
5214
  return true;
5363
5215
  }
5216
+ /**
5217
+ * Returns the collection group that this query targets.
5218
+ *
5219
+ * PORTING NOTE: This is only used in the Web SDK to facilitate multi-tab
5220
+ * synchronization for query results.
5221
+ */
5222
+ function queryCollectionGroup(query) {
5223
+ return (query.collectionGroup ||
5224
+ (query.path.length % 2 === 1
5225
+ ? query.path.lastSegment()
5226
+ : query.path.get(query.path.length - 2)));
5227
+ }
5364
5228
  /**
5365
5229
  * Returns a new comparator function that can be used to compare two documents
5366
5230
  * based on the Query's ordering constraint.
@@ -5392,6 +5256,116 @@ function compareDocs(orderBy, d1, d2) {
5392
5256
  }
5393
5257
  }
5394
5258
 
5259
+ /**
5260
+ * @license
5261
+ * Copyright 2017 Google LLC
5262
+ *
5263
+ * Licensed under the Apache License, Version 2.0 (the "License");
5264
+ * you may not use this file except in compliance with the License.
5265
+ * You may obtain a copy of the License at
5266
+ *
5267
+ * http://www.apache.org/licenses/LICENSE-2.0
5268
+ *
5269
+ * Unless required by applicable law or agreed to in writing, software
5270
+ * distributed under the License is distributed on an "AS IS" BASIS,
5271
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5272
+ * See the License for the specific language governing permissions and
5273
+ * limitations under the License.
5274
+ */
5275
+ /**
5276
+ * A map implementation that uses objects as keys. Objects must have an
5277
+ * associated equals function and must be immutable. Entries in the map are
5278
+ * stored together with the key being produced from the mapKeyFn. This map
5279
+ * automatically handles collisions of keys.
5280
+ */
5281
+ class ObjectMap {
5282
+ constructor(mapKeyFn, equalsFn) {
5283
+ this.mapKeyFn = mapKeyFn;
5284
+ this.equalsFn = equalsFn;
5285
+ /**
5286
+ * The inner map for a key/value pair. Due to the possibility of collisions we
5287
+ * keep a list of entries that we do a linear search through to find an actual
5288
+ * match. Note that collisions should be rare, so we still expect near
5289
+ * constant time lookups in practice.
5290
+ */
5291
+ this.inner = {};
5292
+ /** The number of entries stored in the map */
5293
+ this.innerSize = 0;
5294
+ }
5295
+ /** Get a value for this key, or undefined if it does not exist. */
5296
+ get(key) {
5297
+ const id = this.mapKeyFn(key);
5298
+ const matches = this.inner[id];
5299
+ if (matches === undefined) {
5300
+ return undefined;
5301
+ }
5302
+ for (const [otherKey, value] of matches) {
5303
+ if (this.equalsFn(otherKey, key)) {
5304
+ return value;
5305
+ }
5306
+ }
5307
+ return undefined;
5308
+ }
5309
+ has(key) {
5310
+ return this.get(key) !== undefined;
5311
+ }
5312
+ /** Put this key and value in the map. */
5313
+ set(key, value) {
5314
+ const id = this.mapKeyFn(key);
5315
+ const matches = this.inner[id];
5316
+ if (matches === undefined) {
5317
+ this.inner[id] = [[key, value]];
5318
+ this.innerSize++;
5319
+ return;
5320
+ }
5321
+ for (let i = 0; i < matches.length; i++) {
5322
+ if (this.equalsFn(matches[i][0], key)) {
5323
+ // This is updating an existing entry and does not increase `innerSize`.
5324
+ matches[i] = [key, value];
5325
+ return;
5326
+ }
5327
+ }
5328
+ matches.push([key, value]);
5329
+ this.innerSize++;
5330
+ }
5331
+ /**
5332
+ * Remove this key from the map. Returns a boolean if anything was deleted.
5333
+ */
5334
+ delete(key) {
5335
+ const id = this.mapKeyFn(key);
5336
+ const matches = this.inner[id];
5337
+ if (matches === undefined) {
5338
+ return false;
5339
+ }
5340
+ for (let i = 0; i < matches.length; i++) {
5341
+ if (this.equalsFn(matches[i][0], key)) {
5342
+ if (matches.length === 1) {
5343
+ delete this.inner[id];
5344
+ }
5345
+ else {
5346
+ matches.splice(i, 1);
5347
+ }
5348
+ this.innerSize--;
5349
+ return true;
5350
+ }
5351
+ }
5352
+ return false;
5353
+ }
5354
+ forEach(fn) {
5355
+ forEach(this.inner, (_, entries) => {
5356
+ for (const [k, v] of entries) {
5357
+ fn(k, v);
5358
+ }
5359
+ });
5360
+ }
5361
+ isEmpty() {
5362
+ return isEmpty(this.inner);
5363
+ }
5364
+ size() {
5365
+ return this.innerSize;
5366
+ }
5367
+ }
5368
+
5395
5369
  /**
5396
5370
  * @license
5397
5371
  * Copyright 2017 Google LLC
@@ -5534,7 +5508,7 @@ class SortedMapIterator {
5534
5508
  while (!node.isEmpty()) {
5535
5509
  cmp = startKey ? comparator(node.key, startKey) : 1;
5536
5510
  // flip the comparison if we're going in reverse
5537
- if (isReverse) {
5511
+ if (startKey && isReverse) {
5538
5512
  cmp *= -1;
5539
5513
  }
5540
5514
  if (cmp < 0) {
@@ -6103,6 +6077,9 @@ const EMPTY_DOCUMENT_MAP = new SortedMap(DocumentKey.comparator);
6103
6077
  function documentMap() {
6104
6078
  return EMPTY_DOCUMENT_MAP;
6105
6079
  }
6080
+ function newOverlayMap() {
6081
+ return new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
6082
+ }
6106
6083
  const EMPTY_DOCUMENT_VERSION_MAP = new SortedMap(DocumentKey.comparator);
6107
6084
  function documentVersionMap() {
6108
6085
  return EMPTY_DOCUMENT_VERSION_MAP;
@@ -8781,34 +8758,33 @@ function fromDbRemoteDocument(localSerializer, remoteDoc) {
8781
8758
  }
8782
8759
  /** Encodes a document for storage locally. */
8783
8760
  function toDbRemoteDocument(localSerializer, document) {
8784
- const dbReadTime = toDbTimestampKey(document.readTime);
8785
- const parentPath = document.key.path.popLast().toArray();
8761
+ const key = document.key;
8762
+ const remoteDoc = {
8763
+ prefixPath: key.getCollectionPath().popLast().toArray(),
8764
+ collectionGroup: key.collectionGroup,
8765
+ documentId: key.path.lastSegment(),
8766
+ readTime: toDbTimestampKey(document.readTime),
8767
+ hasCommittedMutations: document.hasCommittedMutations
8768
+ };
8786
8769
  if (document.isFoundDocument()) {
8787
- const doc = toDocument(localSerializer.remoteSerializer, document);
8788
- const hasCommittedMutations = document.hasCommittedMutations;
8789
- return new DbRemoteDocument(
8790
- /* unknownDocument= */ null,
8791
- /* noDocument= */ null, doc, hasCommittedMutations, dbReadTime, parentPath);
8770
+ remoteDoc.document = toDocument(localSerializer.remoteSerializer, document);
8792
8771
  }
8793
8772
  else if (document.isNoDocument()) {
8794
- const path = document.key.path.toArray();
8795
- const version = toDbTimestamp(document.version);
8796
- const hasCommittedMutations = document.hasCommittedMutations;
8797
- return new DbRemoteDocument(
8798
- /* unknownDocument= */ null, new DbNoDocument(path, version),
8799
- /* document= */ null, hasCommittedMutations, dbReadTime, parentPath);
8773
+ remoteDoc.noDocument = {
8774
+ path: key.path.toArray(),
8775
+ readTime: toDbTimestamp(document.version)
8776
+ };
8800
8777
  }
8801
8778
  else if (document.isUnknownDocument()) {
8802
- const path = document.key.path.toArray();
8803
- const readTime = toDbTimestamp(document.version);
8804
- return new DbRemoteDocument(new DbUnknownDocument(path, readTime),
8805
- /* noDocument= */ null,
8806
- /* document= */ null,
8807
- /* hasCommittedMutations= */ true, dbReadTime, parentPath);
8779
+ remoteDoc.unknownDocument = {
8780
+ path: key.path.toArray(),
8781
+ version: toDbTimestamp(document.version)
8782
+ };
8808
8783
  }
8809
8784
  else {
8810
8785
  return fail();
8811
8786
  }
8787
+ return remoteDoc;
8812
8788
  }
8813
8789
  function toDbTimestampKey(snapshotVersion) {
8814
8790
  const timestamp = snapshotVersion.toTimestamp();
@@ -8820,7 +8796,7 @@ function fromDbTimestampKey(dbTimestampKey) {
8820
8796
  }
8821
8797
  function toDbTimestamp(snapshotVersion) {
8822
8798
  const timestamp = snapshotVersion.toTimestamp();
8823
- return new DbTimestamp(timestamp.seconds, timestamp.nanoseconds);
8799
+ return { seconds: timestamp.seconds, nanoseconds: timestamp.nanoseconds };
8824
8800
  }
8825
8801
  function fromDbTimestamp(dbTimestamp) {
8826
8802
  const timestamp = new Timestamp(dbTimestamp.seconds, dbTimestamp.nanoseconds);
@@ -8830,7 +8806,13 @@ function fromDbTimestamp(dbTimestamp) {
8830
8806
  function toDbMutationBatch(localSerializer, userId, batch) {
8831
8807
  const serializedBaseMutations = batch.baseMutations.map(m => toMutation(localSerializer.remoteSerializer, m));
8832
8808
  const serializedMutations = batch.mutations.map(m => toMutation(localSerializer.remoteSerializer, m));
8833
- return new DbMutationBatch(userId, batch.batchId, batch.localWriteTime.toMillis(), serializedBaseMutations, serializedMutations);
8809
+ return {
8810
+ userId,
8811
+ batchId: batch.batchId,
8812
+ localWriteTimeMs: batch.localWriteTime.toMillis(),
8813
+ baseMutations: serializedBaseMutations,
8814
+ mutations: serializedMutations
8815
+ };
8834
8816
  }
8835
8817
  /** Decodes a DbMutationBatch into a MutationBatch */
8836
8818
  function fromDbMutationBatch(localSerializer, dbBatch) {
@@ -8886,7 +8868,15 @@ function toDbTarget(localSerializer, targetData) {
8886
8868
  // convert it to a base64 string for storage.
8887
8869
  const resumeToken = targetData.resumeToken.toBase64();
8888
8870
  // lastListenSequenceNumber is always 0 until we do real GC.
8889
- return new DbTarget(targetData.targetId, canonifyTarget(targetData.target), dbTimestamp, resumeToken, targetData.sequenceNumber, dbLastLimboFreeTimestamp, queryProto);
8871
+ return {
8872
+ targetId: targetData.targetId,
8873
+ canonicalId: canonifyTarget(targetData.target),
8874
+ readTime: dbTimestamp,
8875
+ resumeToken,
8876
+ lastListenSequenceNumber: targetData.sequenceNumber,
8877
+ lastLimboFreeSnapshotVersion: dbLastLimboFreeTimestamp,
8878
+ query: queryProto
8879
+ };
8890
8880
  }
8891
8881
  /**
8892
8882
  * A helper function for figuring out what kind of query has been stored.
@@ -8965,7 +8955,14 @@ function fromDbDocumentOverlay(localSerializer, dbDocumentOverlay) {
8965
8955
  /** Decodes an Overlay model object into a DbDocumentOverlay object. */
8966
8956
  function toDbDocumentOverlay(localSerializer, userId, overlay) {
8967
8957
  const [_, collectionPath, documentId] = toDbDocumentOverlayKey(userId, overlay.mutation.key);
8968
- return new DbDocumentOverlay(userId, collectionPath, documentId, overlay.mutation.key.getCollectionGroup(), overlay.largestBatchId, toMutation(localSerializer.remoteSerializer, overlay.mutation));
8958
+ return {
8959
+ userId,
8960
+ collectionPath,
8961
+ documentId,
8962
+ collectionGroup: overlay.mutation.key.getCollectionGroup(),
8963
+ largestBatchId: overlay.largestBatchId,
8964
+ overlayMutation: toMutation(localSerializer.remoteSerializer, overlay.mutation)
8965
+ };
8969
8966
  }
8970
8967
  /**
8971
8968
  * Returns the DbDocumentOverlayKey corresponding to the given user and
@@ -8977,7 +8974,11 @@ function toDbDocumentOverlayKey(userId, docKey) {
8977
8974
  return [userId, collectionPath, docId];
8978
8975
  }
8979
8976
  function toDbIndexConfiguration(index) {
8980
- return new DbIndexConfiguration(index.indexId, index.collectionGroup, index.fields.map(s => [s.fieldPath.canonicalString(), s.kind]));
8977
+ return {
8978
+ indexId: index.indexId,
8979
+ collectionGroup: index.collectionGroup,
8980
+ fields: index.fields.map(s => [s.fieldPath.canonicalString(), s.kind])
8981
+ };
8981
8982
  }
8982
8983
  function fromDbIndexConfiguration(index, state) {
8983
8984
  const decodedState = state
@@ -8987,7 +8988,14 @@ function fromDbIndexConfiguration(index, state) {
8987
8988
  return new FieldIndex(index.indexId, index.collectionGroup, decodedSegments, decodedState);
8988
8989
  }
8989
8990
  function toDbIndexState(indexId, user, sequenceNumber, offset) {
8990
- return new DbIndexState(indexId, user.uid || '', sequenceNumber, toDbTimestamp(offset.readTime), encodeResourcePath(offset.documentKey.path), offset.largestBatchId);
8991
+ return {
8992
+ indexId,
8993
+ uid: user.uid || '',
8994
+ sequenceNumber,
8995
+ readTime: toDbTimestamp(offset.readTime),
8996
+ documentKey: encodeResourcePath(offset.documentKey.path),
8997
+ largestBatchId: offset.largestBatchId
8998
+ };
8991
8999
  }
8992
9000
 
8993
9001
  /**
@@ -9038,13 +9046,13 @@ class IndexedDbBundleCache {
9038
9046
  * Helper to get a typed SimpleDbStore for the bundles object store.
9039
9047
  */
9040
9048
  function bundlesStore(txn) {
9041
- return getStore(txn, DbBundle.store);
9049
+ return getStore(txn, DbBundleStore);
9042
9050
  }
9043
9051
  /**
9044
9052
  * Helper to get a typed SimpleDbStore for the namedQueries object store.
9045
9053
  */
9046
9054
  function namedQueriesStore(txn) {
9047
- return getStore(txn, DbNamedQuery.store);
9055
+ return getStore(txn, DbNamedQueryStore);
9048
9056
  }
9049
9057
 
9050
9058
  /**
@@ -9091,7 +9099,7 @@ class IndexedDbDocumentOverlayCache {
9091
9099
  }
9092
9100
  saveOverlays(transaction, largestBatchId, overlays) {
9093
9101
  const promises = [];
9094
- overlays.forEach(mutation => {
9102
+ overlays.forEach((_, mutation) => {
9095
9103
  const overlay = new Overlay(largestBatchId, mutation);
9096
9104
  promises.push(this.saveOverlay(transaction, overlay));
9097
9105
  });
@@ -9106,19 +9114,19 @@ class IndexedDbDocumentOverlayCache {
9106
9114
  const range = IDBKeyRange.bound([this.userId, collectionPath, batchId], [this.userId, collectionPath, batchId + 1],
9107
9115
  /*lowerOpen=*/ false,
9108
9116
  /*upperOpen=*/ true);
9109
- promises.push(documentOverlayStore(transaction).deleteAll(DbDocumentOverlay.collectionPathOverlayIndex, range));
9117
+ promises.push(documentOverlayStore(transaction).deleteAll(DbDocumentOverlayCollectionPathOverlayIndex, range));
9110
9118
  });
9111
9119
  return PersistencePromise.waitFor(promises);
9112
9120
  }
9113
9121
  getOverlaysForCollection(transaction, collection, sinceBatchId) {
9114
- const result = new Map();
9122
+ const result = newOverlayMap();
9115
9123
  const collectionPath = encodeResourcePath(collection);
9116
9124
  // We want batch IDs larger than `sinceBatchId`, and so the lower bound
9117
9125
  // is not inclusive.
9118
9126
  const range = IDBKeyRange.bound([this.userId, collectionPath, sinceBatchId], [this.userId, collectionPath, Number.POSITIVE_INFINITY],
9119
9127
  /*lowerOpen=*/ true);
9120
9128
  return documentOverlayStore(transaction)
9121
- .loadAll(DbDocumentOverlay.collectionPathOverlayIndex, range)
9129
+ .loadAll(DbDocumentOverlayCollectionPathOverlayIndex, range)
9122
9130
  .next(dbOverlays => {
9123
9131
  for (const dbOverlay of dbOverlays) {
9124
9132
  const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
@@ -9128,7 +9136,7 @@ class IndexedDbDocumentOverlayCache {
9128
9136
  });
9129
9137
  }
9130
9138
  getOverlaysForCollectionGroup(transaction, collectionGroup, sinceBatchId, count) {
9131
- const result = new Map();
9139
+ const result = newOverlayMap();
9132
9140
  let currentBatchId = undefined;
9133
9141
  // We want batch IDs larger than `sinceBatchId`, and so the lower bound
9134
9142
  // is not inclusive.
@@ -9136,7 +9144,7 @@ class IndexedDbDocumentOverlayCache {
9136
9144
  /*lowerOpen=*/ true);
9137
9145
  return documentOverlayStore(transaction)
9138
9146
  .iterate({
9139
- index: DbDocumentOverlay.collectionGroupOverlayIndex,
9147
+ index: DbDocumentOverlayCollectionGroupOverlayIndex,
9140
9148
  range
9141
9149
  }, (_, dbOverlay, control) => {
9142
9150
  // We do not want to return partial batch overlays, even if the size
@@ -9144,7 +9152,7 @@ class IndexedDbDocumentOverlayCache {
9144
9152
  // continue to aggregate results even after the result size exceeds
9145
9153
  // `count` if there are more overlays from the `currentBatchId`.
9146
9154
  const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
9147
- if (result.size < count ||
9155
+ if (result.size() < count ||
9148
9156
  overlay.largestBatchId === currentBatchId) {
9149
9157
  result.set(overlay.getKey(), overlay);
9150
9158
  currentBatchId = overlay.largestBatchId;
@@ -9163,7 +9171,7 @@ class IndexedDbDocumentOverlayCache {
9163
9171
  * Helper to get a typed SimpleDbStore for the document overlay object store.
9164
9172
  */
9165
9173
  function documentOverlayStore(txn) {
9166
- return getStore(txn, DbDocumentOverlay.store);
9174
+ return getStore(txn, DbDocumentOverlayStore);
9167
9175
  }
9168
9176
 
9169
9177
  /**
@@ -9719,21 +9727,40 @@ class IndexEntry {
9719
9727
  this.arrayValue = arrayValue;
9720
9728
  this.directionalValue = directionalValue;
9721
9729
  }
9730
+ /**
9731
+ * Returns an IndexEntry entry that sorts immediately after the current
9732
+ * directional value.
9733
+ */
9734
+ successor() {
9735
+ const currentLength = this.directionalValue.length;
9736
+ const newLength = currentLength === 0 || this.directionalValue[currentLength - 1] === 255
9737
+ ? currentLength + 1
9738
+ : currentLength;
9739
+ const successor = new Uint8Array(newLength);
9740
+ successor.set(this.directionalValue, 0);
9741
+ if (newLength !== currentLength) {
9742
+ successor.set([0], this.directionalValue.length);
9743
+ }
9744
+ else {
9745
+ ++successor[successor.length - 1];
9746
+ }
9747
+ return new IndexEntry(this.indexId, this.documentKey, this.arrayValue, successor);
9748
+ }
9722
9749
  }
9723
9750
  function indexEntryComparator(left, right) {
9724
9751
  let cmp = left.indexId - right.indexId;
9725
9752
  if (cmp !== 0) {
9726
9753
  return cmp;
9727
9754
  }
9728
- cmp = DocumentKey.comparator(left.documentKey, right.documentKey);
9755
+ cmp = compareByteArrays(left.arrayValue, right.arrayValue);
9729
9756
  if (cmp !== 0) {
9730
9757
  return cmp;
9731
9758
  }
9732
- cmp = compareByteArrays(left.arrayValue, right.arrayValue);
9759
+ cmp = compareByteArrays(left.directionalValue, right.directionalValue);
9733
9760
  if (cmp !== 0) {
9734
9761
  return cmp;
9735
9762
  }
9736
- return compareByteArrays(left.directionalValue, right.directionalValue);
9763
+ return DocumentKey.comparator(left.documentKey, right.documentKey);
9737
9764
  }
9738
9765
  function compareByteArrays(left, right) {
9739
9766
  for (let i = 0; i < left.length && i < right.length; ++i) {
@@ -9745,6 +9772,160 @@ function compareByteArrays(left, right) {
9745
9772
  return left.length - right.length;
9746
9773
  }
9747
9774
 
9775
+ /**
9776
+ * @license
9777
+ * Copyright 2022 Google LLC
9778
+ *
9779
+ * Licensed under the Apache License, Version 2.0 (the "License");
9780
+ * you may not use this file except in compliance with the License.
9781
+ * You may obtain a copy of the License at
9782
+ *
9783
+ * http://www.apache.org/licenses/LICENSE-2.0
9784
+ *
9785
+ * Unless required by applicable law or agreed to in writing, software
9786
+ * distributed under the License is distributed on an "AS IS" BASIS,
9787
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9788
+ * See the License for the specific language governing permissions and
9789
+ * limitations under the License.
9790
+ */
9791
+ /**
9792
+ * A light query planner for Firestore.
9793
+ *
9794
+ * This class matches a `FieldIndex` against a Firestore Query `Target`. It
9795
+ * determines whether a given index can be used to serve the specified target.
9796
+ *
9797
+ * The following table showcases some possible index configurations:
9798
+ *
9799
+ * Query | Index
9800
+ * -----------------------------------------------------------------------------
9801
+ * where('a', '==', 'a').where('b', '==', 'b') | a ASC, b DESC
9802
+ * where('a', '==', 'a').where('b', '==', 'b') | a ASC
9803
+ * where('a', '==', 'a').where('b', '==', 'b') | b DESC
9804
+ * where('a', '>=', 'a').orderBy('a') | a ASC
9805
+ * where('a', '>=', 'a').orderBy('a', 'desc') | a DESC
9806
+ * where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC, b ASC
9807
+ * where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC
9808
+ * where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS, b ASCENDING
9809
+ * where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS
9810
+ */
9811
+ class TargetIndexMatcher {
9812
+ constructor(target) {
9813
+ this.collectionId =
9814
+ target.collectionGroup != null
9815
+ ? target.collectionGroup
9816
+ : target.path.lastSegment();
9817
+ this.orderBys = target.orderBy;
9818
+ this.equalityFilters = [];
9819
+ for (const filter of target.filters) {
9820
+ const fieldFilter = filter;
9821
+ if (fieldFilter.isInequality()) {
9822
+ this.inequalityFilter = fieldFilter;
9823
+ }
9824
+ else {
9825
+ this.equalityFilters.push(fieldFilter);
9826
+ }
9827
+ }
9828
+ }
9829
+ /**
9830
+ * Returns whether the index can be used to serve the TargetIndexMatcher's
9831
+ * target.
9832
+ *
9833
+ * An index is considered capable of serving the target when:
9834
+ * - The target uses all index segments for its filters and orderBy clauses.
9835
+ * The target can have additional filter and orderBy clauses, but not
9836
+ * fewer.
9837
+ * - If an ArrayContains/ArrayContainsAnyfilter is used, the index must also
9838
+ * have a corresponding `CONTAINS` segment.
9839
+ * - All directional index segments can be mapped to the target as a series of
9840
+ * equality filters, a single inequality filter and a series of orderBy
9841
+ * clauses.
9842
+ * - The segments that represent the equality filters may appear out of order.
9843
+ * - The optional segment for the inequality filter must appear after all
9844
+ * equality segments.
9845
+ * - The segments that represent that orderBy clause of the target must appear
9846
+ * in order after all equality and inequality segments. Single orderBy
9847
+ * clauses cannot be skipped, but a continuous orderBy suffix may be
9848
+ * omitted.
9849
+ */
9850
+ servedByIndex(index) {
9851
+ // If there is an array element, find a matching filter.
9852
+ const arraySegment = fieldIndexGetArraySegment(index);
9853
+ if (arraySegment !== undefined &&
9854
+ !this.hasMatchingEqualityFilter(arraySegment)) {
9855
+ return false;
9856
+ }
9857
+ const segments = fieldIndexGetDirectionalSegments(index);
9858
+ let segmentIndex = 0;
9859
+ let orderBysIndex = 0;
9860
+ // Process all equalities first. Equalities can appear out of order.
9861
+ for (; segmentIndex < segments.length; ++segmentIndex) {
9862
+ // We attempt to greedily match all segments to equality filters. If a
9863
+ // filter matches an index segment, we can mark the segment as used.
9864
+ // Since it is not possible to use the same field path in both an equality
9865
+ // and inequality/oderBy clause, we do not have to consider the possibility
9866
+ // that a matching equality segment should instead be used to map to an
9867
+ // inequality filter or orderBy clause.
9868
+ if (!this.hasMatchingEqualityFilter(segments[segmentIndex])) {
9869
+ // If we cannot find a matching filter, we need to verify whether the
9870
+ // remaining segments map to the target's inequality and its orderBy
9871
+ // clauses.
9872
+ break;
9873
+ }
9874
+ }
9875
+ // If we already have processed all segments, all segments are used to serve
9876
+ // the equality filters and we do not need to map any segments to the
9877
+ // target's inequality and orderBy clauses.
9878
+ if (segmentIndex === segments.length) {
9879
+ return true;
9880
+ }
9881
+ // If there is an inequality filter, the next segment must match both the
9882
+ // filter and the first orderBy clause.
9883
+ if (this.inequalityFilter !== undefined) {
9884
+ const segment = segments[segmentIndex];
9885
+ if (!this.matchesFilter(this.inequalityFilter, segment) ||
9886
+ !this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
9887
+ return false;
9888
+ }
9889
+ ++segmentIndex;
9890
+ }
9891
+ // All remaining segments need to represent the prefix of the target's
9892
+ // orderBy.
9893
+ for (; segmentIndex < segments.length; ++segmentIndex) {
9894
+ const segment = segments[segmentIndex];
9895
+ if (orderBysIndex >= this.orderBys.length ||
9896
+ !this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
9897
+ return false;
9898
+ }
9899
+ }
9900
+ return true;
9901
+ }
9902
+ hasMatchingEqualityFilter(segment) {
9903
+ for (const filter of this.equalityFilters) {
9904
+ if (this.matchesFilter(filter, segment)) {
9905
+ return true;
9906
+ }
9907
+ }
9908
+ return false;
9909
+ }
9910
+ matchesFilter(filter, segment) {
9911
+ if (filter === undefined || !filter.field.isEqual(segment.fieldPath)) {
9912
+ return false;
9913
+ }
9914
+ const isArrayOperator = filter.op === "array-contains" /* ARRAY_CONTAINS */ ||
9915
+ filter.op === "array-contains-any" /* ARRAY_CONTAINS_ANY */;
9916
+ return (segment.kind === 2 /* CONTAINS */) === isArrayOperator;
9917
+ }
9918
+ matchesOrderBy(orderBy, segment) {
9919
+ if (!orderBy.field.isEqual(segment.fieldPath)) {
9920
+ return false;
9921
+ }
9922
+ return ((segment.kind === 0 /* ASCENDING */ &&
9923
+ orderBy.dir === "asc" /* ASCENDING */) ||
9924
+ (segment.kind === 1 /* DESCENDING */ &&
9925
+ orderBy.dir === "desc" /* DESCENDING */));
9926
+ }
9927
+ }
9928
+
9748
9929
  /**
9749
9930
  * @license
9750
9931
  * Copyright 2019 Google LLC
@@ -9783,9 +9964,9 @@ class MemoryIndexManager {
9783
9964
  // Field indices are not supported with memory persistence.
9784
9965
  return PersistencePromise.resolve();
9785
9966
  }
9786
- getDocumentsMatchingTarget(transaction, fieldIndex, target) {
9967
+ getDocumentsMatchingTarget(transaction, target) {
9787
9968
  // Field indices are not supported with memory persistence.
9788
- return PersistencePromise.resolve(documentKeySet());
9969
+ return PersistencePromise.resolve(null);
9789
9970
  }
9790
9971
  getFieldIndex(transaction, target) {
9791
9972
  // Field indices are not supported with memory persistence.
@@ -9857,6 +10038,7 @@ class MemoryCollectionParentIndex {
9857
10038
  * limitations under the License.
9858
10039
  */
9859
10040
  const LOG_TAG$f = 'IndexedDbIndexManager';
10041
+ const EMPTY_VALUE = new Uint8Array(0);
9860
10042
  /**
9861
10043
  * A persisted implementation of IndexManager.
9862
10044
  *
@@ -9874,6 +10056,11 @@ class IndexedDbIndexManager {
9874
10056
  * used to satisfy reads.
9875
10057
  */
9876
10058
  this.collectionParentsCache = new MemoryCollectionParentIndex();
10059
+ /**
10060
+ * Maps from a target to its equivalent list of sub-targets. Each sub-target
10061
+ * contains only one term from the target's disjunctive normal form (DNF).
10062
+ */
10063
+ this.targetToDnfSubTargets = new ObjectMap(t => canonifyTarget(t), (l, r) => targetEquals(l, r));
9877
10064
  this.uid = user.uid || '';
9878
10065
  }
9879
10066
  /**
@@ -9942,13 +10129,111 @@ class IndexedDbIndexManager {
9942
10129
  /*lowerOpen=*/ false,
9943
10130
  /*upperOpen=*/ true)));
9944
10131
  }
9945
- getDocumentsMatchingTarget(transaction, fieldIndex, target) {
9946
- // TODO(indexing): Implement
9947
- return PersistencePromise.resolve(documentKeySet());
10132
+ getDocumentsMatchingTarget(transaction, target) {
10133
+ const indexEntries = indexEntriesStore(transaction);
10134
+ let canServeTarget = true;
10135
+ const indexes = new Map();
10136
+ return PersistencePromise.forEach(this.getSubTargets(target), (subTarget) => {
10137
+ return this.getFieldIndex(transaction, subTarget).next(index => {
10138
+ canServeTarget && (canServeTarget = !!index);
10139
+ indexes.set(subTarget, index);
10140
+ });
10141
+ }).next(() => {
10142
+ if (!canServeTarget) {
10143
+ return PersistencePromise.resolve(null);
10144
+ }
10145
+ else {
10146
+ let result = documentKeySet();
10147
+ return PersistencePromise.forEach(indexes, (index, subTarget) => {
10148
+ logDebug(LOG_TAG$f, `Using index ${fieldIndexToString(index)} to execute ${canonifyTarget(target)}`);
10149
+ const arrayValues = targetGetArrayValues(subTarget, index);
10150
+ const notInValues = targetGetNotInValues(subTarget, index);
10151
+ const lowerBound = targetGetLowerBound(subTarget, index);
10152
+ const upperBound = targetGetUpperBound(subTarget, index);
10153
+ const lowerBoundEncoded = this.encodeBound(index, subTarget, lowerBound);
10154
+ const upperBoundEncoded = this.encodeBound(index, subTarget, upperBound);
10155
+ const notInEncoded = this.encodeValues(index, subTarget, notInValues);
10156
+ const indexRanges = this.generateIndexRanges(index.indexId, arrayValues, lowerBoundEncoded, !!lowerBound && lowerBound.inclusive, upperBoundEncoded, !!upperBound && upperBound.inclusive, notInEncoded);
10157
+ return PersistencePromise.forEach(indexRanges, (indexRange) => {
10158
+ return indexEntries
10159
+ .loadFirst(indexRange, target.limit)
10160
+ .next(entries => {
10161
+ entries.forEach(entry => {
10162
+ result = result.add(new DocumentKey(decodeResourcePath(entry.documentKey)));
10163
+ });
10164
+ });
10165
+ });
10166
+ }).next(() => result);
10167
+ }
10168
+ });
10169
+ }
10170
+ getSubTargets(target) {
10171
+ let subTargets = this.targetToDnfSubTargets.get(target);
10172
+ if (subTargets) {
10173
+ return subTargets;
10174
+ }
10175
+ // TODO(orquery): Implement DNF transform
10176
+ subTargets = [target];
10177
+ this.targetToDnfSubTargets.set(target, subTargets);
10178
+ return subTargets;
10179
+ }
10180
+ /**
10181
+ * Constructs a key range query on `DbIndexEntryStore` that unions all
10182
+ * bounds.
10183
+ */
10184
+ generateIndexRanges(indexId, arrayValues, lowerBounds, lowerBoundInclusive, upperBounds, upperBoundInclusive, notInValues) {
10185
+ // The number of total index scans we union together. This is similar to a
10186
+ // distributed normal form, but adapted for array values. We create a single
10187
+ // index range per value in an ARRAY_CONTAINS or ARRAY_CONTAINS_ANY filter
10188
+ // combined with the values from the query bounds.
10189
+ const totalScans = (arrayValues != null ? arrayValues.length : 1) *
10190
+ Math.max(lowerBounds != null ? lowerBounds.length : 1, upperBounds != null ? upperBounds.length : 1);
10191
+ const scansPerArrayElement = totalScans / (arrayValues != null ? arrayValues.length : 1);
10192
+ const indexRanges = [];
10193
+ for (let i = 0; i < totalScans; ++i) {
10194
+ const arrayValue = arrayValues
10195
+ ? this.encodeSingleElement(arrayValues[i / scansPerArrayElement])
10196
+ : EMPTY_VALUE;
10197
+ const lowerBound = lowerBounds
10198
+ ? this.generateLowerBound(indexId, arrayValue, lowerBounds[i % scansPerArrayElement], lowerBoundInclusive)
10199
+ : this.generateEmptyBound(indexId);
10200
+ const upperBound = upperBounds
10201
+ ? this.generateUpperBound(indexId, arrayValue, upperBounds[i % scansPerArrayElement], upperBoundInclusive)
10202
+ : this.generateEmptyBound(indexId + 1);
10203
+ indexRanges.push(...this.createRange(lowerBound, upperBound, notInValues.map((notIn // make non-nullable
10204
+ ) => this.generateLowerBound(indexId, arrayValue, notIn,
10205
+ /* inclusive= */ true))));
10206
+ }
10207
+ return indexRanges;
10208
+ }
10209
+ /** Generates the lower bound for `arrayValue` and `directionalValue`. */
10210
+ generateLowerBound(indexId, arrayValue, directionalValue, inclusive) {
10211
+ const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
10212
+ return inclusive ? entry : entry.successor();
10213
+ }
10214
+ /** Generates the upper bound for `arrayValue` and `directionalValue`. */
10215
+ generateUpperBound(indexId, arrayValue, directionalValue, inclusive) {
10216
+ const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
10217
+ return inclusive ? entry.successor() : entry;
10218
+ }
10219
+ /**
10220
+ * Generates an empty bound that scopes the index scan to the current index
10221
+ * and user.
10222
+ */
10223
+ generateEmptyBound(indexId) {
10224
+ return new IndexEntry(indexId, DocumentKey.empty(), EMPTY_VALUE, EMPTY_VALUE);
9948
10225
  }
9949
10226
  getFieldIndex(transaction, target) {
9950
- // TODO(indexing): Implement
9951
- return PersistencePromise.resolve(null);
10227
+ const targetIndexMatcher = new TargetIndexMatcher(target);
10228
+ const collectionGroup = target.collectionGroup != null
10229
+ ? target.collectionGroup
10230
+ : target.path.lastSegment();
10231
+ return this.getFieldIndexes(transaction, collectionGroup).next(indexes => {
10232
+ const matchingIndexes = indexes.filter(i => targetIndexMatcher.servedByIndex(i));
10233
+ // Return the index that matches the most number of segments.
10234
+ matchingIndexes.sort((l, r) => r.fields.length - l.fields.length);
10235
+ return matchingIndexes.length > 0 ? matchingIndexes[0] : null;
10236
+ });
9952
10237
  }
9953
10238
  /**
9954
10239
  * Returns the byte encoded form of the directional values in the field index.
@@ -9962,22 +10247,90 @@ class IndexedDbIndexManager {
9962
10247
  if (field == null) {
9963
10248
  return null;
9964
10249
  }
9965
- const directionalEncoder = encoder.forKind(segment.kind);
9966
- FirestoreIndexValueWriter.INSTANCE.writeIndexValue(field, directionalEncoder);
10250
+ const directionalEncoder = encoder.forKind(segment.kind);
10251
+ FirestoreIndexValueWriter.INSTANCE.writeIndexValue(field, directionalEncoder);
10252
+ }
10253
+ return encoder.encodedBytes();
10254
+ }
10255
+ /** Encodes a single value to the ascending index format. */
10256
+ encodeSingleElement(value) {
10257
+ const encoder = new IndexByteEncoder();
10258
+ FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, encoder.forKind(0 /* ASCENDING */));
10259
+ return encoder.encodedBytes();
10260
+ }
10261
+ /**
10262
+ * Encodes the given field values according to the specification in `target`.
10263
+ * For IN queries, a list of possible values is returned.
10264
+ */
10265
+ encodeValues(fieldIndex, target, bound) {
10266
+ if (bound === null) {
10267
+ return [];
10268
+ }
10269
+ let encoders = [];
10270
+ encoders.push(new IndexByteEncoder());
10271
+ let boundIdx = 0;
10272
+ for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
10273
+ const value = bound[boundIdx++];
10274
+ for (const encoder of encoders) {
10275
+ if (this.isInFilter(target, segment.fieldPath) && isArray(value)) {
10276
+ encoders = this.expandIndexValues(encoders, segment, value);
10277
+ }
10278
+ else {
10279
+ const directionalEncoder = encoder.forKind(segment.kind);
10280
+ FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, directionalEncoder);
10281
+ }
10282
+ }
10283
+ }
10284
+ return this.getEncodedBytes(encoders);
10285
+ }
10286
+ /**
10287
+ * Encodes the given bounds according to the specification in `target`. For IN
10288
+ * queries, a list of possible values is returned.
10289
+ */
10290
+ encodeBound(fieldIndex, target, bound) {
10291
+ if (bound == null) {
10292
+ return null;
10293
+ }
10294
+ return this.encodeValues(fieldIndex, target, bound.position);
10295
+ }
10296
+ /** Returns the byte representation for the provided encoders. */
10297
+ getEncodedBytes(encoders) {
10298
+ const result = [];
10299
+ for (let i = 0; i < encoders.length; ++i) {
10300
+ result[i] = encoders[i].encodedBytes();
10301
+ }
10302
+ return result;
10303
+ }
10304
+ /**
10305
+ * Creates a separate encoder for each element of an array.
10306
+ *
10307
+ * The method appends each value to all existing encoders (e.g. filter("a",
10308
+ * "==", "a1").filter("b", "in", ["b1", "b2"]) becomes ["a1,b1", "a1,b2"]). A
10309
+ * list of new encoders is returned.
10310
+ */
10311
+ expandIndexValues(encoders, segment, value) {
10312
+ const prefixes = [...encoders];
10313
+ const results = [];
10314
+ for (const arrayElement of value.arrayValue.values || []) {
10315
+ for (const prefix of prefixes) {
10316
+ const clonedEncoder = new IndexByteEncoder();
10317
+ clonedEncoder.seed(prefix.encodedBytes());
10318
+ FirestoreIndexValueWriter.INSTANCE.writeIndexValue(arrayElement, clonedEncoder.forKind(segment.kind));
10319
+ results.push(clonedEncoder);
10320
+ }
9967
10321
  }
9968
- return encoder.encodedBytes();
10322
+ return results;
9969
10323
  }
9970
- /** Encodes a single value to the ascending index format. */
9971
- encodeSingleElement(value) {
9972
- const encoder = new IndexByteEncoder();
9973
- FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, encoder.forKind(0 /* ASCENDING */));
9974
- return encoder.encodedBytes();
10324
+ isInFilter(target, fieldPath) {
10325
+ return !!target.filters.find(f => f instanceof FieldFilter &&
10326
+ f.field.isEqual(fieldPath) &&
10327
+ (f.op === "in" /* IN */ || f.op === "not-in" /* NOT_IN */));
9975
10328
  }
9976
10329
  getFieldIndexes(transaction, collectionGroup) {
9977
10330
  const indexes = indexConfigurationStore(transaction);
9978
10331
  const states = indexStateStore(transaction);
9979
10332
  return (collectionGroup
9980
- ? indexes.loadAll(DbIndexConfiguration.collectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
10333
+ ? indexes.loadAll(DbIndexConfigurationCollectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
9981
10334
  : indexes.loadAll()).next(indexConfigs => {
9982
10335
  const result = [];
9983
10336
  return PersistencePromise.forEach(indexConfigs, (indexConfig) => {
@@ -9994,7 +10347,12 @@ class IndexedDbIndexManager {
9994
10347
  if (indexes.length === 0) {
9995
10348
  return null;
9996
10349
  }
9997
- indexes.sort((l, r) => l.indexState.sequenceNumber - r.indexState.sequenceNumber);
10350
+ indexes.sort((l, r) => {
10351
+ const cmp = l.indexState.sequenceNumber - r.indexState.sequenceNumber;
10352
+ return cmp !== 0
10353
+ ? cmp
10354
+ : primitiveComparator(l.collectionGroup, r.collectionGroup);
10355
+ });
9998
10356
  return indexes[0].collectionGroup;
9999
10357
  });
10000
10358
  }
@@ -10002,7 +10360,7 @@ class IndexedDbIndexManager {
10002
10360
  const indexes = indexConfigurationStore(transaction);
10003
10361
  const states = indexStateStore(transaction);
10004
10362
  return this.getNextSequenceNumber(transaction).next(nextSequenceNumber => indexes
10005
- .loadAll(DbIndexConfiguration.collectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
10363
+ .loadAll(DbIndexConfigurationCollectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
10006
10364
  .next(configs => PersistencePromise.forEach(configs, (config) => states.put(toDbIndexState(config.indexId, this.user, nextSequenceNumber, offset)))));
10007
10365
  }
10008
10366
  updateIndexEntries(transaction, documents) {
@@ -10033,7 +10391,13 @@ class IndexedDbIndexManager {
10033
10391
  }
10034
10392
  addIndexEntry(transaction, document, indexEntry) {
10035
10393
  const indexEntries = indexEntriesStore(transaction);
10036
- return indexEntries.put(new DbIndexEntry(indexEntry.indexId, this.uid, indexEntry.arrayValue, indexEntry.directionalValue, encodeResourcePath(document.key.path)));
10394
+ return indexEntries.put({
10395
+ indexId: indexEntry.indexId,
10396
+ uid: this.uid,
10397
+ arrayValue: indexEntry.arrayValue,
10398
+ directionalValue: indexEntry.directionalValue,
10399
+ documentKey: encodeResourcePath(document.key.path)
10400
+ });
10037
10401
  }
10038
10402
  deleteIndexEntry(transaction, document, indexEntry) {
10039
10403
  const indexEntries = indexEntriesStore(transaction);
@@ -10050,7 +10414,7 @@ class IndexedDbIndexManager {
10050
10414
  let results = new SortedSet(indexEntryComparator);
10051
10415
  return indexEntries
10052
10416
  .iterate({
10053
- index: DbIndexEntry.documentKeyIndex,
10417
+ index: DbIndexEntryDocumentKeyIndex,
10054
10418
  range: IDBKeyRange.only([
10055
10419
  fieldIndex.indexId,
10056
10420
  this.uid,
@@ -10078,7 +10442,7 @@ class IndexedDbIndexManager {
10078
10442
  }
10079
10443
  }
10080
10444
  else {
10081
- results = results.add(new IndexEntry(fieldIndex.indexId, document.key, new Uint8Array(), directionalValue));
10445
+ results = results.add(new IndexEntry(fieldIndex.indexId, document.key, EMPTY_VALUE, directionalValue));
10082
10446
  }
10083
10447
  return results;
10084
10448
  }
@@ -10104,7 +10468,7 @@ class IndexedDbIndexManager {
10104
10468
  const states = indexStateStore(transaction);
10105
10469
  return states
10106
10470
  .iterate({
10107
- index: DbIndexState.sequenceNumberIndex,
10471
+ index: DbIndexStateSequenceNumberIndex,
10108
10472
  reverse: true,
10109
10473
  range: IDBKeyRange.upperBound([this.uid, Number.MAX_SAFE_INTEGER])
10110
10474
  }, (_, state, controller) => {
@@ -10113,31 +10477,81 @@ class IndexedDbIndexManager {
10113
10477
  })
10114
10478
  .next(() => nextSequenceNumber);
10115
10479
  }
10480
+ /**
10481
+ * Returns a new set of IDB ranges that splits the existing range and excludes
10482
+ * any values that match the `notInValue` from these ranges. As an example,
10483
+ * '[foo > 2 && foo != 3]` becomes `[foo > 2 && < 3, foo > 3]`.
10484
+ */
10485
+ createRange(lower, upper, notInValues) {
10486
+ // The notIb values need to be sorted and unique so that we can return a
10487
+ // sorted set of non-overlapping ranges.
10488
+ notInValues = notInValues
10489
+ .sort((l, r) => indexEntryComparator(l, r))
10490
+ .filter((el, i, values) => !i || indexEntryComparator(el, values[i - 1]) !== 0);
10491
+ const bounds = [];
10492
+ bounds.push(lower);
10493
+ for (const notInValue of notInValues) {
10494
+ const cmpToLower = indexEntryComparator(notInValue, lower);
10495
+ const cmpToUpper = indexEntryComparator(notInValue, upper);
10496
+ if (cmpToLower === 0) {
10497
+ // `notInValue` is the lower bound. We therefore need to raise the bound
10498
+ // to the next value.
10499
+ bounds[0] = lower.successor();
10500
+ }
10501
+ else if (cmpToLower > 0 && cmpToUpper < 0) {
10502
+ // `notInValue` is in the middle of the range
10503
+ bounds.push(notInValue);
10504
+ bounds.push(notInValue.successor());
10505
+ }
10506
+ else if (cmpToUpper > 0) {
10507
+ // `notInValue` (and all following values) are out of the range
10508
+ break;
10509
+ }
10510
+ }
10511
+ bounds.push(upper);
10512
+ const ranges = [];
10513
+ for (let i = 0; i < bounds.length; i += 2) {
10514
+ ranges.push(IDBKeyRange.bound([
10515
+ bounds[i].indexId,
10516
+ this.uid,
10517
+ bounds[i].arrayValue,
10518
+ bounds[i].directionalValue,
10519
+ ''
10520
+ ], [
10521
+ bounds[i + 1].indexId,
10522
+ this.uid,
10523
+ bounds[i + 1].arrayValue,
10524
+ bounds[i + 1].directionalValue,
10525
+ ''
10526
+ ]));
10527
+ }
10528
+ return ranges;
10529
+ }
10116
10530
  }
10117
10531
  /**
10118
10532
  * Helper to get a typed SimpleDbStore for the collectionParents
10119
10533
  * document store.
10120
10534
  */
10121
10535
  function collectionParentsStore(txn) {
10122
- return getStore(txn, DbCollectionParent.store);
10536
+ return getStore(txn, DbCollectionParentStore);
10123
10537
  }
10124
10538
  /**
10125
10539
  * Helper to get a typed SimpleDbStore for the index entry object store.
10126
10540
  */
10127
10541
  function indexEntriesStore(txn) {
10128
- return getStore(txn, DbIndexEntry.store);
10542
+ return getStore(txn, DbIndexEntryStore);
10129
10543
  }
10130
10544
  /**
10131
10545
  * Helper to get a typed SimpleDbStore for the index configuration object store.
10132
10546
  */
10133
10547
  function indexConfigurationStore(txn) {
10134
- return getStore(txn, DbIndexConfiguration.store);
10548
+ return getStore(txn, DbIndexConfigurationStore);
10135
10549
  }
10136
10550
  /**
10137
10551
  * Helper to get a typed SimpleDbStore for the index state object store.
10138
10552
  */
10139
10553
  function indexStateStore(txn) {
10140
- return getStore(txn, DbIndexState.store);
10554
+ return getStore(txn, DbIndexStateStore);
10141
10555
  }
10142
10556
 
10143
10557
  /**
@@ -10161,8 +10575,8 @@ function indexStateStore(txn) {
10161
10575
  * @returns A PersistencePromise of the document mutations that were removed.
10162
10576
  */
10163
10577
  function removeMutationBatch(txn, userId, batch) {
10164
- const mutationStore = txn.store(DbMutationBatch.store);
10165
- const indexTxn = txn.store(DbDocumentMutation.store);
10578
+ const mutationStore = txn.store(DbMutationBatchStore);
10579
+ const indexTxn = txn.store(DbDocumentMutationStore);
10166
10580
  const promises = [];
10167
10581
  const range = IDBKeyRange.only(batch.batchId);
10168
10582
  let numDeleted = 0;
@@ -10175,7 +10589,7 @@ function removeMutationBatch(txn, userId, batch) {
10175
10589
  }));
10176
10590
  const removedDocuments = [];
10177
10591
  for (const mutation of batch.mutations) {
10178
- const indexKey = DbDocumentMutation.key(userId, mutation.key.path, batch.batchId);
10592
+ const indexKey = newDbDocumentMutationKey(userId, mutation.key.path, batch.batchId);
10179
10593
  promises.push(indexTxn.delete(indexKey));
10180
10594
  removedDocuments.push(mutation.key);
10181
10595
  }
@@ -10264,7 +10678,7 @@ class IndexedDbMutationQueue {
10264
10678
  let empty = true;
10265
10679
  const range = IDBKeyRange.bound([this.userId, Number.NEGATIVE_INFINITY], [this.userId, Number.POSITIVE_INFINITY]);
10266
10680
  return mutationsStore(transaction)
10267
- .iterate({ index: DbMutationBatch.userMutationsIndex, range }, (key, value, control) => {
10681
+ .iterate({ index: DbMutationBatchUserMutationsIndex, range }, (key, value, control) => {
10268
10682
  empty = false;
10269
10683
  control.done();
10270
10684
  })
@@ -10289,10 +10703,10 @@ class IndexedDbMutationQueue {
10289
10703
  const promises = [];
10290
10704
  let collectionParents = new SortedSet((l, r) => primitiveComparator(l.canonicalString(), r.canonicalString()));
10291
10705
  for (const mutation of mutations) {
10292
- const indexKey = DbDocumentMutation.key(this.userId, mutation.key.path, batchId);
10706
+ const indexKey = newDbDocumentMutationKey(this.userId, mutation.key.path, batchId);
10293
10707
  collectionParents = collectionParents.add(mutation.key.path.popLast());
10294
10708
  promises.push(mutationStore.put(dbBatch));
10295
- promises.push(documentStore.put(indexKey, DbDocumentMutation.PLACEHOLDER));
10709
+ promises.push(documentStore.put(indexKey, DbDocumentMutationPlaceholder));
10296
10710
  }
10297
10711
  collectionParents.forEach(parent => {
10298
10712
  promises.push(this.indexManager.addToCollectionParentIndex(transaction, parent));
@@ -10343,7 +10757,7 @@ class IndexedDbMutationQueue {
10343
10757
  const range = IDBKeyRange.lowerBound([this.userId, nextBatchId]);
10344
10758
  let foundBatch = null;
10345
10759
  return mutationsStore(transaction)
10346
- .iterate({ index: DbMutationBatch.userMutationsIndex, range }, (key, dbBatch, control) => {
10760
+ .iterate({ index: DbMutationBatchUserMutationsIndex, range }, (key, dbBatch, control) => {
10347
10761
  if (dbBatch.userId === this.userId) {
10348
10762
  hardAssert(dbBatch.batchId >= nextBatchId);
10349
10763
  foundBatch = fromDbMutationBatch(this.serializer, dbBatch);
@@ -10359,7 +10773,7 @@ class IndexedDbMutationQueue {
10359
10773
  ]);
10360
10774
  let batchId = BATCHID_UNKNOWN;
10361
10775
  return mutationsStore(transaction)
10362
- .iterate({ index: DbMutationBatch.userMutationsIndex, range, reverse: true }, (key, dbBatch, control) => {
10776
+ .iterate({ index: DbMutationBatchUserMutationsIndex, range, reverse: true }, (key, dbBatch, control) => {
10363
10777
  batchId = dbBatch.batchId;
10364
10778
  control.done();
10365
10779
  })
@@ -10368,13 +10782,13 @@ class IndexedDbMutationQueue {
10368
10782
  getAllMutationBatches(transaction) {
10369
10783
  const range = IDBKeyRange.bound([this.userId, BATCHID_UNKNOWN], [this.userId, Number.POSITIVE_INFINITY]);
10370
10784
  return mutationsStore(transaction)
10371
- .loadAll(DbMutationBatch.userMutationsIndex, range)
10785
+ .loadAll(DbMutationBatchUserMutationsIndex, range)
10372
10786
  .next(dbBatches => dbBatches.map(dbBatch => fromDbMutationBatch(this.serializer, dbBatch)));
10373
10787
  }
10374
10788
  getAllMutationBatchesAffectingDocumentKey(transaction, documentKey) {
10375
10789
  // Scan the document-mutation index starting with a prefix starting with
10376
10790
  // the given documentKey.
10377
- const indexPrefix = DbDocumentMutation.prefixForPath(this.userId, documentKey.path);
10791
+ const indexPrefix = newDbDocumentMutationPrefixForPath(this.userId, documentKey.path);
10378
10792
  const indexStart = IDBKeyRange.lowerBound(indexPrefix);
10379
10793
  const results = [];
10380
10794
  return documentMutationsStore(transaction)
@@ -10409,7 +10823,7 @@ class IndexedDbMutationQueue {
10409
10823
  let uniqueBatchIDs = new SortedSet(primitiveComparator);
10410
10824
  const promises = [];
10411
10825
  documentKeys.forEach(documentKey => {
10412
- const indexStart = DbDocumentMutation.prefixForPath(this.userId, documentKey.path);
10826
+ const indexStart = newDbDocumentMutationPrefixForPath(this.userId, documentKey.path);
10413
10827
  const range = IDBKeyRange.lowerBound(indexStart);
10414
10828
  const promise = documentMutationsStore(transaction).iterate({ range }, (indexKey, _, control) => {
10415
10829
  const [userID, encodedPath, batchID] = indexKey;
@@ -10445,7 +10859,7 @@ class IndexedDbMutationQueue {
10445
10859
  // Since we don't yet index the actual properties in the mutations, our
10446
10860
  // current approach is to just return all mutation batches that affect
10447
10861
  // documents in the collection being queried.
10448
- const indexPrefix = DbDocumentMutation.prefixForPath(this.userId, queryPath);
10862
+ const indexPrefix = newDbDocumentMutationPrefixForPath(this.userId, queryPath);
10449
10863
  const indexStart = IDBKeyRange.lowerBound(indexPrefix);
10450
10864
  // Collect up unique batchIDs encountered during a scan of the index. Use a
10451
10865
  // SortedSet to accumulate batch IDs so they can be traversed in order in a
@@ -10517,7 +10931,7 @@ class IndexedDbMutationQueue {
10517
10931
  }
10518
10932
  // Verify that there are no entries in the documentMutations index if
10519
10933
  // the queue is empty.
10520
- const startRange = IDBKeyRange.lowerBound(DbDocumentMutation.prefixForUser(this.userId));
10934
+ const startRange = IDBKeyRange.lowerBound(newDbDocumentMutationPrefixForUser(this.userId));
10521
10935
  const danglingMutationReferences = [];
10522
10936
  return documentMutationsStore(txn)
10523
10937
  .iterate({ range: startRange }, (key, _, control) => {
@@ -10545,9 +10959,11 @@ class IndexedDbMutationQueue {
10545
10959
  return mutationQueuesStore(transaction)
10546
10960
  .get(this.userId)
10547
10961
  .next((metadata) => {
10548
- return (metadata ||
10549
- new DbMutationQueue(this.userId, BATCHID_UNKNOWN,
10550
- /*lastStreamToken=*/ ''));
10962
+ return (metadata || {
10963
+ userId: this.userId,
10964
+ lastAcknowledgedBatchId: BATCHID_UNKNOWN,
10965
+ lastStreamToken: ''
10966
+ });
10551
10967
  });
10552
10968
  }
10553
10969
  }
@@ -10556,7 +10972,7 @@ class IndexedDbMutationQueue {
10556
10972
  * mutation for the given key.
10557
10973
  */
10558
10974
  function mutationQueueContainsKey(txn, userId, key) {
10559
- const indexKey = DbDocumentMutation.prefixForPath(userId, key.path);
10975
+ const indexKey = newDbDocumentMutationPrefixForPath(userId, key.path);
10560
10976
  const encodedPath = indexKey[1];
10561
10977
  const startRange = IDBKeyRange.lowerBound(indexKey);
10562
10978
  let containsKey = false;
@@ -10588,19 +11004,19 @@ function mutationQueuesContainKey(txn, docKey) {
10588
11004
  * Helper to get a typed SimpleDbStore for the mutations object store.
10589
11005
  */
10590
11006
  function mutationsStore(txn) {
10591
- return getStore(txn, DbMutationBatch.store);
11007
+ return getStore(txn, DbMutationBatchStore);
10592
11008
  }
10593
11009
  /**
10594
11010
  * Helper to get a typed SimpleDbStore for the mutationQueues object store.
10595
11011
  */
10596
11012
  function documentMutationsStore(txn) {
10597
- return getStore(txn, DbDocumentMutation.store);
11013
+ return getStore(txn, DbDocumentMutationStore);
10598
11014
  }
10599
11015
  /**
10600
11016
  * Helper to get a typed SimpleDbStore for the mutationQueues object store.
10601
11017
  */
10602
11018
  function mutationQueuesStore(txn) {
10603
- return getStore(txn, DbMutationQueue.store);
11019
+ return getStore(txn, DbMutationQueueStore);
10604
11020
  }
10605
11021
 
10606
11022
  /**
@@ -10764,14 +11180,14 @@ class IndexedDbTargetCache {
10764
11180
  }
10765
11181
  retrieveMetadata(transaction) {
10766
11182
  return globalTargetStore(transaction)
10767
- .get(DbTargetGlobal.key)
11183
+ .get(DbTargetGlobalKey)
10768
11184
  .next(metadata => {
10769
11185
  hardAssert(metadata !== null);
10770
11186
  return metadata;
10771
11187
  });
10772
11188
  }
10773
11189
  saveMetadata(transaction, metadata) {
10774
- return globalTargetStore(transaction).put(DbTargetGlobal.key, metadata);
11190
+ return globalTargetStore(transaction).put(DbTargetGlobalKey, metadata);
10775
11191
  }
10776
11192
  saveTargetData(transaction, targetData) {
10777
11193
  return targetsStore(transaction).put(toDbTarget(this.serializer, targetData));
@@ -10804,7 +11220,7 @@ class IndexedDbTargetCache {
10804
11220
  const range = IDBKeyRange.bound([canonicalId, Number.NEGATIVE_INFINITY], [canonicalId, Number.POSITIVE_INFINITY]);
10805
11221
  let result = null;
10806
11222
  return targetsStore(transaction)
10807
- .iterate({ range, index: DbTarget.queryTargetsIndexName }, (key, value, control) => {
11223
+ .iterate({ range, index: DbTargetQueryTargetsIndexName }, (key, value, control) => {
10808
11224
  const found = fromDbTarget(value);
10809
11225
  // After finding a potential match, check that the target is
10810
11226
  // actually equal to the requested target.
@@ -10822,7 +11238,7 @@ class IndexedDbTargetCache {
10822
11238
  const store = documentTargetStore(txn);
10823
11239
  keys.forEach(key => {
10824
11240
  const path = encodeResourcePath(key.path);
10825
- promises.push(store.put(new DbTargetDocument(targetId, path)));
11241
+ promises.push(store.put({ targetId, path }));
10826
11242
  promises.push(this.referenceDelegate.addReference(txn, targetId, key));
10827
11243
  });
10828
11244
  return PersistencePromise.waitFor(promises);
@@ -10868,7 +11284,7 @@ class IndexedDbTargetCache {
10868
11284
  let count = 0;
10869
11285
  return documentTargetStore(txn)
10870
11286
  .iterate({
10871
- index: DbTargetDocument.documentTargetsIndex,
11287
+ index: DbTargetDocumentDocumentTargetsIndex,
10872
11288
  keysOnly: true,
10873
11289
  range
10874
11290
  }, ([targetId, path], _, control) => {
@@ -10907,19 +11323,19 @@ class IndexedDbTargetCache {
10907
11323
  * Helper to get a typed SimpleDbStore for the queries object store.
10908
11324
  */
10909
11325
  function targetsStore(txn) {
10910
- return getStore(txn, DbTarget.store);
11326
+ return getStore(txn, DbTargetStore);
10911
11327
  }
10912
11328
  /**
10913
11329
  * Helper to get a typed SimpleDbStore for the target globals object store.
10914
11330
  */
10915
11331
  function globalTargetStore(txn) {
10916
- return getStore(txn, DbTargetGlobal.store);
11332
+ return getStore(txn, DbTargetGlobalStore);
10917
11333
  }
10918
11334
  /**
10919
11335
  * Helper to get a typed SimpleDbStore for the document target object store.
10920
11336
  */
10921
11337
  function documentTargetStore(txn) {
10922
- return getStore(txn, DbTargetDocument.store);
11338
+ return getStore(txn, DbTargetDocumentStore);
10923
11339
  }
10924
11340
 
10925
11341
  /**
@@ -11333,7 +11749,7 @@ class IndexedDbLruDelegateImpl {
11333
11749
  let nextPath;
11334
11750
  return store
11335
11751
  .iterate({
11336
- index: DbTargetDocument.documentTargetsIndex
11752
+ index: DbTargetDocumentDocumentTargetsIndex
11337
11753
  }, ([targetId, docKey], { path, sequenceNumber }) => {
11338
11754
  if (targetId === 0) {
11339
11755
  // if nextToReport is valid, report it, this is a new key so the
@@ -11375,113 +11791,12 @@ function sentinelKey$1(key) {
11375
11791
  * store.
11376
11792
  */
11377
11793
  function sentinelRow(key, sequenceNumber) {
11378
- return new DbTargetDocument(0, encodeResourcePath(key.path), sequenceNumber);
11794
+ return { targetId: 0, path: encodeResourcePath(key.path), sequenceNumber };
11379
11795
  }
11380
11796
  function writeSentinelKey(txn, key) {
11381
11797
  return documentTargetStore(txn).put(sentinelRow(key, txn.currentSequenceNumber));
11382
11798
  }
11383
11799
 
11384
- /**
11385
- * @license
11386
- * Copyright 2017 Google LLC
11387
- *
11388
- * Licensed under the Apache License, Version 2.0 (the "License");
11389
- * you may not use this file except in compliance with the License.
11390
- * You may obtain a copy of the License at
11391
- *
11392
- * http://www.apache.org/licenses/LICENSE-2.0
11393
- *
11394
- * Unless required by applicable law or agreed to in writing, software
11395
- * distributed under the License is distributed on an "AS IS" BASIS,
11396
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11397
- * See the License for the specific language governing permissions and
11398
- * limitations under the License.
11399
- */
11400
- /**
11401
- * A map implementation that uses objects as keys. Objects must have an
11402
- * associated equals function and must be immutable. Entries in the map are
11403
- * stored together with the key being produced from the mapKeyFn. This map
11404
- * automatically handles collisions of keys.
11405
- */
11406
- class ObjectMap {
11407
- constructor(mapKeyFn, equalsFn) {
11408
- this.mapKeyFn = mapKeyFn;
11409
- this.equalsFn = equalsFn;
11410
- /**
11411
- * The inner map for a key/value pair. Due to the possibility of collisions we
11412
- * keep a list of entries that we do a linear search through to find an actual
11413
- * match. Note that collisions should be rare, so we still expect near
11414
- * constant time lookups in practice.
11415
- */
11416
- this.inner = {};
11417
- }
11418
- /** Get a value for this key, or undefined if it does not exist. */
11419
- get(key) {
11420
- const id = this.mapKeyFn(key);
11421
- const matches = this.inner[id];
11422
- if (matches === undefined) {
11423
- return undefined;
11424
- }
11425
- for (const [otherKey, value] of matches) {
11426
- if (this.equalsFn(otherKey, key)) {
11427
- return value;
11428
- }
11429
- }
11430
- return undefined;
11431
- }
11432
- has(key) {
11433
- return this.get(key) !== undefined;
11434
- }
11435
- /** Put this key and value in the map. */
11436
- set(key, value) {
11437
- const id = this.mapKeyFn(key);
11438
- const matches = this.inner[id];
11439
- if (matches === undefined) {
11440
- this.inner[id] = [[key, value]];
11441
- return;
11442
- }
11443
- for (let i = 0; i < matches.length; i++) {
11444
- if (this.equalsFn(matches[i][0], key)) {
11445
- matches[i] = [key, value];
11446
- return;
11447
- }
11448
- }
11449
- matches.push([key, value]);
11450
- }
11451
- /**
11452
- * Remove this key from the map. Returns a boolean if anything was deleted.
11453
- */
11454
- delete(key) {
11455
- const id = this.mapKeyFn(key);
11456
- const matches = this.inner[id];
11457
- if (matches === undefined) {
11458
- return false;
11459
- }
11460
- for (let i = 0; i < matches.length; i++) {
11461
- if (this.equalsFn(matches[i][0], key)) {
11462
- if (matches.length === 1) {
11463
- delete this.inner[id];
11464
- }
11465
- else {
11466
- matches.splice(i, 1);
11467
- }
11468
- return true;
11469
- }
11470
- }
11471
- return false;
11472
- }
11473
- forEach(fn) {
11474
- forEach(this.inner, (_, entries) => {
11475
- for (const [k, v] of entries) {
11476
- fn(k, v);
11477
- }
11478
- });
11479
- }
11480
- isEmpty() {
11481
- return isEmpty(this.inner);
11482
- }
11483
- }
11484
-
11485
11800
  /**
11486
11801
  * @license
11487
11802
  * Copyright 2017 Google LLC
@@ -11621,7 +11936,7 @@ class IndexedDbRemoteDocumentCacheImpl {
11621
11936
  */
11622
11937
  addEntry(transaction, key, doc) {
11623
11938
  const documentStore = remoteDocumentsStore(transaction);
11624
- return documentStore.put(dbKey(key), doc);
11939
+ return documentStore.put(doc);
11625
11940
  }
11626
11941
  /**
11627
11942
  * Removes a document from the cache.
@@ -11629,10 +11944,9 @@ class IndexedDbRemoteDocumentCacheImpl {
11629
11944
  * All calls of `removeEntry` are required to go through the RemoteDocumentChangeBuffer
11630
11945
  * returned by `newChangeBuffer()` to ensure proper accounting of metadata.
11631
11946
  */
11632
- removeEntry(transaction, documentKey) {
11947
+ removeEntry(transaction, documentKey, readTime) {
11633
11948
  const store = remoteDocumentsStore(transaction);
11634
- const key = dbKey(documentKey);
11635
- return store.delete(key);
11949
+ return store.delete(dbReadTimeKey(documentKey, readTime));
11636
11950
  }
11637
11951
  /**
11638
11952
  * Updates the current cache size.
@@ -11647,11 +11961,15 @@ class IndexedDbRemoteDocumentCacheImpl {
11647
11961
  });
11648
11962
  }
11649
11963
  getEntry(transaction, documentKey) {
11964
+ let doc = MutableDocument.newInvalidDocument(documentKey);
11650
11965
  return remoteDocumentsStore(transaction)
11651
- .get(dbKey(documentKey))
11652
- .next(dbRemoteDoc => {
11653
- return this.maybeDecodeDocument(documentKey, dbRemoteDoc);
11654
- });
11966
+ .iterate({
11967
+ index: DbRemoteDocumentDocumentKeyIndex,
11968
+ range: IDBKeyRange.only(dbKey(documentKey))
11969
+ }, (_, dbRemoteDoc) => {
11970
+ doc = this.maybeDecodeDocument(documentKey, dbRemoteDoc);
11971
+ })
11972
+ .next(() => doc);
11655
11973
  }
11656
11974
  /**
11657
11975
  * Looks up an entry in the cache.
@@ -11660,15 +11978,21 @@ class IndexedDbRemoteDocumentCacheImpl {
11660
11978
  * @returns The cached document entry and its size.
11661
11979
  */
11662
11980
  getSizedEntry(transaction, documentKey) {
11981
+ let result = {
11982
+ size: 0,
11983
+ document: MutableDocument.newInvalidDocument(documentKey)
11984
+ };
11663
11985
  return remoteDocumentsStore(transaction)
11664
- .get(dbKey(documentKey))
11665
- .next(dbRemoteDoc => {
11666
- const doc = this.maybeDecodeDocument(documentKey, dbRemoteDoc);
11667
- return {
11668
- document: doc,
11986
+ .iterate({
11987
+ index: DbRemoteDocumentDocumentKeyIndex,
11988
+ range: IDBKeyRange.only(dbKey(documentKey))
11989
+ }, (_, dbRemoteDoc) => {
11990
+ result = {
11991
+ document: this.maybeDecodeDocument(documentKey, dbRemoteDoc),
11669
11992
  size: dbDocumentSize(dbRemoteDoc)
11670
11993
  };
11671
- });
11994
+ })
11995
+ .next(() => result);
11672
11996
  }
11673
11997
  getEntries(transaction, documentKeys) {
11674
11998
  let results = mutableDocumentMap();
@@ -11699,14 +12023,20 @@ class IndexedDbRemoteDocumentCacheImpl {
11699
12023
  if (documentKeys.isEmpty()) {
11700
12024
  return PersistencePromise.resolve();
11701
12025
  }
11702
- const range = IDBKeyRange.bound(documentKeys.first().path.toArray(), documentKeys.last().path.toArray());
11703
- const keyIter = documentKeys.getIterator();
12026
+ let sortedKeys = new SortedSet(dbKeyComparator);
12027
+ documentKeys.forEach(e => (sortedKeys = sortedKeys.add(e)));
12028
+ const range = IDBKeyRange.bound(dbKey(sortedKeys.first()), dbKey(sortedKeys.last()));
12029
+ const keyIter = sortedKeys.getIterator();
11704
12030
  let nextKey = keyIter.getNext();
11705
12031
  return remoteDocumentsStore(transaction)
11706
- .iterate({ range }, (potentialKeyRaw, dbRemoteDoc, control) => {
11707
- const potentialKey = DocumentKey.fromSegments(potentialKeyRaw);
12032
+ .iterate({ index: DbRemoteDocumentDocumentKeyIndex, range }, (_, dbRemoteDoc, control) => {
12033
+ const potentialKey = DocumentKey.fromSegments([
12034
+ ...dbRemoteDoc.prefixPath,
12035
+ dbRemoteDoc.collectionGroup,
12036
+ dbRemoteDoc.documentId
12037
+ ]);
11708
12038
  // Go through keys not found in cache.
11709
- while (nextKey && DocumentKey.comparator(nextKey, potentialKey) < 0) {
12039
+ while (nextKey && dbKeyComparator(nextKey, potentialKey) < 0) {
11710
12040
  callback(nextKey, null);
11711
12041
  nextKey = keyIter.getNext();
11712
12042
  }
@@ -11717,7 +12047,7 @@ class IndexedDbRemoteDocumentCacheImpl {
11717
12047
  }
11718
12048
  // Skip to the next key (if there is one).
11719
12049
  if (nextKey) {
11720
- control.skip(nextKey.path.toArray());
12050
+ control.skip(dbKey(nextKey));
11721
12051
  }
11722
12052
  else {
11723
12053
  control.done();
@@ -11732,41 +12062,44 @@ class IndexedDbRemoteDocumentCacheImpl {
11732
12062
  }
11733
12063
  });
11734
12064
  }
11735
- getAll(transaction, collection, sinceReadTime) {
11736
- let results = mutableDocumentMap();
11737
- const immediateChildrenPathLength = collection.length + 1;
11738
- const iterationOptions = {};
11739
- if (sinceReadTime.isEqual(SnapshotVersion.min())) {
11740
- // Documents are ordered by key, so we can use a prefix scan to narrow
11741
- // down the documents we need to match the query against.
11742
- const startKey = collection.toArray();
11743
- iterationOptions.range = IDBKeyRange.lowerBound(startKey);
11744
- }
11745
- else {
11746
- // Execute an index-free query and filter by read time. This is safe
11747
- // since all document changes to queries that have a
11748
- // lastLimboFreeSnapshotVersion (`sinceReadTime`) have a read time set.
11749
- const collectionKey = collection.toArray();
11750
- const readTimeKey = toDbTimestampKey(sinceReadTime);
11751
- iterationOptions.range = IDBKeyRange.lowerBound([collectionKey, readTimeKey],
11752
- /* open= */ true);
11753
- iterationOptions.index = DbRemoteDocument.collectionReadTimeIndex;
11754
- }
12065
+ getAllFromCollection(transaction, collection, offset) {
12066
+ const startKey = [
12067
+ collection.popLast().toArray(),
12068
+ collection.lastSegment(),
12069
+ toDbTimestampKey(offset.readTime),
12070
+ offset.documentKey.path.isEmpty()
12071
+ ? ''
12072
+ : offset.documentKey.path.lastSegment()
12073
+ ];
12074
+ const endKey = [
12075
+ collection.popLast().toArray(),
12076
+ collection.lastSegment(),
12077
+ [Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],
12078
+ ''
12079
+ ];
11755
12080
  return remoteDocumentsStore(transaction)
11756
- .iterate(iterationOptions, (key, dbRemoteDoc, control) => {
11757
- // The query is actually returning any path that starts with the query
11758
- // path prefix which may include documents in subcollections. For
11759
- // example, a query on 'rooms' will return rooms/abc/messages/xyx but we
11760
- // shouldn't match it. Fix this by discarding rows with document keys
11761
- // more than one segment longer than the query path.
11762
- if (key.length !== immediateChildrenPathLength) {
11763
- return;
11764
- }
11765
- const document = this.maybeDecodeDocument(DocumentKey.fromSegments(key), dbRemoteDoc);
11766
- if (collection.isPrefixOf(document.key.path)) {
12081
+ .loadAll(IDBKeyRange.bound(startKey, endKey, true))
12082
+ .next(dbRemoteDocs => {
12083
+ let results = mutableDocumentMap();
12084
+ for (const dbRemoteDoc of dbRemoteDocs) {
12085
+ const document = this.maybeDecodeDocument(DocumentKey.fromSegments(dbRemoteDoc.prefixPath.concat(dbRemoteDoc.collectionGroup, dbRemoteDoc.documentId)), dbRemoteDoc);
11767
12086
  results = results.insert(document.key, document);
11768
12087
  }
11769
- else {
12088
+ return results;
12089
+ });
12090
+ }
12091
+ getAllFromCollectionGroup(transaction, collectionGroup, offset, limit) {
12092
+ let results = mutableDocumentMap();
12093
+ const startKey = dbCollectionGroupKey(collectionGroup, offset);
12094
+ const endKey = dbCollectionGroupKey(collectionGroup, IndexOffset.max());
12095
+ return remoteDocumentsStore(transaction)
12096
+ .iterate({
12097
+ index: DbRemoteDocumentCollectionGroupIndex,
12098
+ range: IDBKeyRange.bound(startKey, endKey, true)
12099
+ }, (_, dbRemoteDoc, control) => {
12100
+ const document = this.maybeDecodeDocument(DocumentKey.fromSegments(dbRemoteDoc.prefixPath.concat(dbRemoteDoc.collectionGroup, dbRemoteDoc.documentId)), dbRemoteDoc);
12101
+ results = results.insert(document.key, document);
12102
+ if (results.size === limit) {
11770
12103
  control.done();
11771
12104
  }
11772
12105
  })
@@ -11780,14 +12113,14 @@ class IndexedDbRemoteDocumentCacheImpl {
11780
12113
  }
11781
12114
  getMetadata(txn) {
11782
12115
  return documentGlobalStore(txn)
11783
- .get(DbRemoteDocumentGlobal.key)
12116
+ .get(DbRemoteDocumentGlobalKey)
11784
12117
  .next(metadata => {
11785
12118
  hardAssert(!!metadata);
11786
12119
  return metadata;
11787
12120
  });
11788
12121
  }
11789
12122
  setMetadata(txn, metadata) {
11790
- return documentGlobalStore(txn).put(DbRemoteDocumentGlobal.key, metadata);
12123
+ return documentGlobalStore(txn).put(DbRemoteDocumentGlobalKey, metadata);
11791
12124
  }
11792
12125
  /**
11793
12126
  * Decodes `dbRemoteDoc` and returns the document (or an invalid document if
@@ -11810,50 +12143,6 @@ class IndexedDbRemoteDocumentCacheImpl {
11810
12143
  function newIndexedDbRemoteDocumentCache(serializer) {
11811
12144
  return new IndexedDbRemoteDocumentCacheImpl(serializer);
11812
12145
  }
11813
- /**
11814
- * Returns the set of documents that have changed since the specified read
11815
- * time.
11816
- */
11817
- // PORTING NOTE: This is only used for multi-tab synchronization.
11818
- function remoteDocumentCacheGetNewDocumentChanges(remoteDocumentCache, transaction, sinceReadTime) {
11819
- const remoteDocumentCacheImpl = debugCast(remoteDocumentCache);
11820
- let changedDocs = mutableDocumentMap();
11821
- let lastReadTime = toDbTimestampKey(sinceReadTime);
11822
- const documentsStore = remoteDocumentsStore(transaction);
11823
- const range = IDBKeyRange.lowerBound(lastReadTime, true);
11824
- return documentsStore
11825
- .iterate({ index: DbRemoteDocument.readTimeIndex, range }, (_, dbRemoteDoc) => {
11826
- // Unlike `getEntry()` and others, `getNewDocumentChanges()` parses
11827
- // the documents directly since we want to keep sentinel deletes.
11828
- const doc = fromDbRemoteDocument(remoteDocumentCacheImpl.serializer, dbRemoteDoc);
11829
- changedDocs = changedDocs.insert(doc.key, doc);
11830
- lastReadTime = dbRemoteDoc.readTime;
11831
- })
11832
- .next(() => {
11833
- return {
11834
- changedDocs,
11835
- readTime: fromDbTimestampKey(lastReadTime)
11836
- };
11837
- });
11838
- }
11839
- /**
11840
- * Returns the read time of the most recently read document in the cache, or
11841
- * SnapshotVersion.min() if not available.
11842
- */
11843
- // PORTING NOTE: This is only used for multi-tab synchronization.
11844
- function remoteDocumentCacheGetLastReadTime(transaction) {
11845
- const documentsStore = remoteDocumentsStore(transaction);
11846
- // If there are no existing entries, we return SnapshotVersion.min().
11847
- let readTime = SnapshotVersion.min();
11848
- return documentsStore
11849
- .iterate({ index: DbRemoteDocument.readTimeIndex, reverse: true }, (key, dbRemoteDoc, control) => {
11850
- if (dbRemoteDoc.readTime) {
11851
- readTime = fromDbTimestampKey(dbRemoteDoc.readTime);
11852
- }
11853
- control.done();
11854
- })
11855
- .next(() => readTime);
11856
- }
11857
12146
  /**
11858
12147
  * Handles the details of adding and updating documents in the IndexedDbRemoteDocumentCache.
11859
12148
  *
@@ -11871,24 +12160,26 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
11871
12160
  super();
11872
12161
  this.documentCache = documentCache;
11873
12162
  this.trackRemovals = trackRemovals;
11874
- // A map of document sizes prior to applying the changes in this buffer.
11875
- this.documentSizes = new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
12163
+ // A map of document sizes and read times prior to applying the changes in
12164
+ // this buffer.
12165
+ this.documentStates = new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
11876
12166
  }
11877
12167
  applyChanges(transaction) {
11878
12168
  const promises = [];
11879
12169
  let sizeDelta = 0;
11880
12170
  let collectionParents = new SortedSet((l, r) => primitiveComparator(l.canonicalString(), r.canonicalString()));
11881
12171
  this.changes.forEach((key, documentChange) => {
11882
- const previousSize = this.documentSizes.get(key);
12172
+ const previousDoc = this.documentStates.get(key);
12173
+ promises.push(this.documentCache.removeEntry(transaction, key, previousDoc.readTime));
11883
12174
  if (documentChange.isValidDocument()) {
11884
12175
  const doc = toDbRemoteDocument(this.documentCache.serializer, documentChange);
11885
12176
  collectionParents = collectionParents.add(key.path.popLast());
11886
12177
  const size = dbDocumentSize(doc);
11887
- sizeDelta += size - previousSize;
12178
+ sizeDelta += size - previousDoc.size;
11888
12179
  promises.push(this.documentCache.addEntry(transaction, key, doc));
11889
12180
  }
11890
12181
  else {
11891
- sizeDelta -= previousSize;
12182
+ sizeDelta -= previousDoc.size;
11892
12183
  if (this.trackRemovals) {
11893
12184
  // In order to track removals, we store a "sentinel delete" in the
11894
12185
  // RemoteDocumentCache. This entry is represented by a NoDocument
@@ -11897,9 +12188,6 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
11897
12188
  const deletedDoc = toDbRemoteDocument(this.documentCache.serializer, documentChange.convertToNoDocument(SnapshotVersion.min()));
11898
12189
  promises.push(this.documentCache.addEntry(transaction, key, deletedDoc));
11899
12190
  }
11900
- else {
11901
- promises.push(this.documentCache.removeEntry(transaction, key));
11902
- }
11903
12191
  }
11904
12192
  });
11905
12193
  collectionParents.forEach(parent => {
@@ -11913,7 +12201,10 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
11913
12201
  return this.documentCache
11914
12202
  .getSizedEntry(transaction, documentKey)
11915
12203
  .next(getResult => {
11916
- this.documentSizes.set(documentKey, getResult.size);
12204
+ this.documentStates.set(documentKey, {
12205
+ size: getResult.size,
12206
+ readTime: getResult.document.readTime
12207
+ });
11917
12208
  return getResult.document;
11918
12209
  });
11919
12210
  }
@@ -11927,24 +12218,95 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
11927
12218
  // keys to `DocumentSizeEntry`s. This is to allow returning the
11928
12219
  // `MutableDocumentMap` directly, without a conversion.
11929
12220
  sizeMap.forEach((documentKey, size) => {
11930
- this.documentSizes.set(documentKey, size);
12221
+ this.documentStates.set(documentKey, {
12222
+ size,
12223
+ readTime: documents.get(documentKey).readTime
12224
+ });
11931
12225
  });
11932
12226
  return documents;
11933
12227
  });
11934
12228
  }
11935
12229
  }
11936
12230
  function documentGlobalStore(txn) {
11937
- return getStore(txn, DbRemoteDocumentGlobal.store);
12231
+ return getStore(txn, DbRemoteDocumentGlobalStore);
11938
12232
  }
11939
12233
  /**
11940
12234
  * Helper to get a typed SimpleDbStore for the remoteDocuments object store.
11941
12235
  */
11942
12236
  function remoteDocumentsStore(txn) {
11943
- return getStore(txn, DbRemoteDocument.store);
12237
+ return getStore(txn, DbRemoteDocumentStore);
12238
+ }
12239
+ /**
12240
+ * Returns a key that can be used for document lookups on the
12241
+ * `DbRemoteDocumentDocumentKeyIndex` index.
12242
+ */
12243
+ function dbKey(documentKey) {
12244
+ const path = documentKey.path.toArray();
12245
+ return [
12246
+ /* prefix path */ path.slice(0, path.length - 2),
12247
+ /* collection id */ path[path.length - 2],
12248
+ /* document id */ path[path.length - 1]
12249
+ ];
12250
+ }
12251
+ /**
12252
+ * Returns a key that can be used for document lookups via the primary key of
12253
+ * the DbRemoteDocument object store.
12254
+ */
12255
+ function dbReadTimeKey(documentKey, readTime) {
12256
+ const path = documentKey.path.toArray();
12257
+ return [
12258
+ /* prefix path */ path.slice(0, path.length - 2),
12259
+ /* collection id */ path[path.length - 2],
12260
+ toDbTimestampKey(readTime),
12261
+ /* document id */ path[path.length - 1]
12262
+ ];
12263
+ }
12264
+ /**
12265
+ * Returns a key that can be used for document lookups on the
12266
+ * `DbRemoteDocumentDocumentCollectionGroupIndex` index.
12267
+ */
12268
+ function dbCollectionGroupKey(collectionGroup, offset) {
12269
+ const path = offset.documentKey.path.toArray();
12270
+ return [
12271
+ /* collection id */ collectionGroup,
12272
+ toDbTimestampKey(offset.readTime),
12273
+ /* prefix path */ path.slice(0, path.length - 2),
12274
+ /* document id */ path.length > 0 ? path[path.length - 1] : ''
12275
+ ];
11944
12276
  }
11945
- function dbKey(docKey) {
11946
- return docKey.path.toArray();
12277
+ /**
12278
+ * Comparator that compares document keys according to the primary key sorting
12279
+ * used by the `DbRemoteDocumentDocument` store (by collection path and then
12280
+ * document ID).
12281
+ */
12282
+ function dbKeyComparator(l, r) {
12283
+ const cmp = l.path.length - r.path.length;
12284
+ return cmp !== 0 ? cmp : DocumentKey.comparator(l, r);
11947
12285
  }
12286
+ /**
12287
+ * Schema Version for the Web client:
12288
+ * 1. Initial version including Mutation Queue, Query Cache, and Remote
12289
+ * Document Cache
12290
+ * 2. Used to ensure a targetGlobal object exists and add targetCount to it. No
12291
+ * longer required because migration 3 unconditionally clears it.
12292
+ * 3. Dropped and re-created Query Cache to deal with cache corruption related
12293
+ * to limbo resolution. Addresses
12294
+ * https://github.com/firebase/firebase-ios-sdk/issues/1548
12295
+ * 4. Multi-Tab Support.
12296
+ * 5. Removal of held write acks.
12297
+ * 6. Create document global for tracking document cache size.
12298
+ * 7. Ensure every cached document has a sentinel row with a sequence number.
12299
+ * 8. Add collection-parent index for Collection Group queries.
12300
+ * 9. Change RemoteDocumentChanges store to be keyed by readTime rather than
12301
+ * an auto-incrementing ID. This is required for Index-Free queries.
12302
+ * 10. Rewrite the canonical IDs to the explicit Protobuf-based format.
12303
+ * 11. Add bundles and named_queries for bundle support.
12304
+ * 12. Add document overlays.
12305
+ * 13. Rewrite the keys of the remote document cache to allow for efficient
12306
+ * document lookup via `getAll()`.
12307
+ * 14. Add indexing support.
12308
+ */
12309
+ const SCHEMA_VERSION = 13;
11948
12310
 
11949
12311
  /**
11950
12312
  * @license
@@ -11980,7 +12342,7 @@ class SchemaConverter {
11980
12342
  createPrimaryClientStore(db);
11981
12343
  createMutationQueue(db);
11982
12344
  createQueryCache(db);
11983
- createRemoteDocumentCache(db);
12345
+ createLegacyRemoteDocumentCache(db);
11984
12346
  }
11985
12347
  // Migration 2 to populate the targetGlobal object no longer needed since
11986
12348
  // migration 3 unconditionally clears it.
@@ -12029,7 +12391,8 @@ class SchemaConverter {
12029
12391
  // to the DbRemoteDocument object store itself. Since the previous change
12030
12392
  // log only contained transient data, we can drop its object store.
12031
12393
  dropRemoteDocumentChangesStore(db);
12032
- createRemoteDocumentReadTimeIndex(txn);
12394
+ // Note: Schema version 9 used to create a read time index for the
12395
+ // RemoteDocumentCache. This is now done with schema version 13.
12033
12396
  });
12034
12397
  }
12035
12398
  if (fromVersion < 10 && toVersion >= 10) {
@@ -12047,6 +12410,12 @@ class SchemaConverter {
12047
12410
  });
12048
12411
  }
12049
12412
  if (fromVersion < 13 && toVersion >= 13) {
12413
+ p = p
12414
+ .next(() => createRemoteDocumentCache(db))
12415
+ .next(() => this.rewriteRemoteDocumentCache(db, simpleDbTransaction))
12416
+ .next(() => db.deleteObjectStore(DbRemoteDocumentStore$1));
12417
+ }
12418
+ if (fromVersion < 14 && toVersion >= 14) {
12050
12419
  p = p.next(() => {
12051
12420
  createFieldIndex(db);
12052
12421
  });
@@ -12054,27 +12423,27 @@ class SchemaConverter {
12054
12423
  return p;
12055
12424
  }
12056
12425
  addDocumentGlobal(txn) {
12057
- let byteCount = 0;
12426
+ let byteSize = 0;
12058
12427
  return txn
12059
- .store(DbRemoteDocument.store)
12428
+ .store(DbRemoteDocumentStore$1)
12060
12429
  .iterate((_, doc) => {
12061
- byteCount += dbDocumentSize(doc);
12430
+ byteSize += dbDocumentSize(doc);
12062
12431
  })
12063
12432
  .next(() => {
12064
- const metadata = new DbRemoteDocumentGlobal(byteCount);
12433
+ const metadata = { byteSize };
12065
12434
  return txn
12066
- .store(DbRemoteDocumentGlobal.store)
12067
- .put(DbRemoteDocumentGlobal.key, metadata);
12435
+ .store(DbRemoteDocumentGlobalStore)
12436
+ .put(DbRemoteDocumentGlobalKey, metadata);
12068
12437
  });
12069
12438
  }
12070
12439
  removeAcknowledgedMutations(txn) {
12071
- const queuesStore = txn.store(DbMutationQueue.store);
12072
- const mutationsStore = txn.store(DbMutationBatch.store);
12440
+ const queuesStore = txn.store(DbMutationQueueStore);
12441
+ const mutationsStore = txn.store(DbMutationBatchStore);
12073
12442
  return queuesStore.loadAll().next(queues => {
12074
12443
  return PersistencePromise.forEach(queues, (queue) => {
12075
12444
  const range = IDBKeyRange.bound([queue.userId, BATCHID_UNKNOWN], [queue.userId, queue.lastAcknowledgedBatchId]);
12076
12445
  return mutationsStore
12077
- .loadAll(DbMutationBatch.userMutationsIndex, range)
12446
+ .loadAll(DbMutationBatchUserMutationsIndex, range)
12078
12447
  .next(dbBatches => {
12079
12448
  return PersistencePromise.forEach(dbBatches, (dbBatch) => {
12080
12449
  hardAssert(dbBatch.userId === queue.userId);
@@ -12090,12 +12459,16 @@ class SchemaConverter {
12090
12459
  * with a sequence number. Missing rows are given the most recently used sequence number.
12091
12460
  */
12092
12461
  ensureSequenceNumbers(txn) {
12093
- const documentTargetStore = txn.store(DbTargetDocument.store);
12094
- const documentsStore = txn.store(DbRemoteDocument.store);
12095
- const globalTargetStore = txn.store(DbTargetGlobal.store);
12096
- return globalTargetStore.get(DbTargetGlobal.key).next(metadata => {
12462
+ const documentTargetStore = txn.store(DbTargetDocumentStore);
12463
+ const documentsStore = txn.store(DbRemoteDocumentStore$1);
12464
+ const globalTargetStore = txn.store(DbTargetGlobalStore);
12465
+ return globalTargetStore.get(DbTargetGlobalKey).next(metadata => {
12097
12466
  const writeSentinelKey = (path) => {
12098
- return documentTargetStore.put(new DbTargetDocument(0, encodeResourcePath(path), metadata.highestListenSequenceNumber));
12467
+ return documentTargetStore.put({
12468
+ targetId: 0,
12469
+ path: encodeResourcePath(path),
12470
+ sequenceNumber: metadata.highestListenSequenceNumber
12471
+ });
12099
12472
  };
12100
12473
  const promises = [];
12101
12474
  return documentsStore
@@ -12116,10 +12489,10 @@ class SchemaConverter {
12116
12489
  }
12117
12490
  createCollectionParentIndex(db, txn) {
12118
12491
  // Create the index.
12119
- db.createObjectStore(DbCollectionParent.store, {
12120
- keyPath: DbCollectionParent.keyPath
12492
+ db.createObjectStore(DbCollectionParentStore, {
12493
+ keyPath: DbCollectionParentKeyPath
12121
12494
  });
12122
- const collectionParentsStore = txn.store(DbCollectionParent.store);
12495
+ const collectionParentsStore = txn.store(DbCollectionParentStore);
12123
12496
  // Helper to add an index entry iff we haven't already written it.
12124
12497
  const cache = new MemoryCollectionParentIndex();
12125
12498
  const addEntry = (collectionPath) => {
@@ -12134,7 +12507,7 @@ class SchemaConverter {
12134
12507
  };
12135
12508
  // Index existing remote documents.
12136
12509
  return txn
12137
- .store(DbRemoteDocument.store)
12510
+ .store(DbRemoteDocumentStore$1)
12138
12511
  .iterate({ keysOnly: true }, (pathSegments, _) => {
12139
12512
  const path = new ResourcePath(pathSegments);
12140
12513
  return addEntry(path.popLast());
@@ -12142,7 +12515,7 @@ class SchemaConverter {
12142
12515
  .next(() => {
12143
12516
  // Index existing mutations.
12144
12517
  return txn
12145
- .store(DbDocumentMutation.store)
12518
+ .store(DbDocumentMutationStore)
12146
12519
  .iterate({ keysOnly: true }, ([userID, encodedPath, batchId], _) => {
12147
12520
  const path = decodeResourcePath(encodedPath);
12148
12521
  return addEntry(path.popLast());
@@ -12150,71 +12523,99 @@ class SchemaConverter {
12150
12523
  });
12151
12524
  }
12152
12525
  rewriteCanonicalIds(txn) {
12153
- const targetStore = txn.store(DbTarget.store);
12526
+ const targetStore = txn.store(DbTargetStore);
12154
12527
  return targetStore.iterate((key, originalDbTarget) => {
12155
12528
  const originalTargetData = fromDbTarget(originalDbTarget);
12156
12529
  const updatedDbTarget = toDbTarget(this.serializer, originalTargetData);
12157
12530
  return targetStore.put(updatedDbTarget);
12158
12531
  });
12159
12532
  }
12533
+ rewriteRemoteDocumentCache(db, transaction) {
12534
+ const legacyRemoteDocumentStore = transaction.store(DbRemoteDocumentStore$1);
12535
+ const writes = [];
12536
+ return legacyRemoteDocumentStore
12537
+ .iterate((_, legacyDocument) => {
12538
+ const remoteDocumentStore = transaction.store(DbRemoteDocumentStore);
12539
+ const path = extractKey(legacyDocument).path.toArray();
12540
+ const dbRemoteDocument = {
12541
+ prefixPath: path.slice(0, path.length - 2),
12542
+ collectionGroup: path[path.length - 2],
12543
+ documentId: path[path.length - 1],
12544
+ readTime: legacyDocument.readTime || [0, 0],
12545
+ unknownDocument: legacyDocument.unknownDocument,
12546
+ noDocument: legacyDocument.noDocument,
12547
+ document: legacyDocument.document,
12548
+ hasCommittedMutations: !!legacyDocument.hasCommittedMutations
12549
+ };
12550
+ writes.push(remoteDocumentStore.put(dbRemoteDocument));
12551
+ })
12552
+ .next(() => PersistencePromise.waitFor(writes));
12553
+ }
12160
12554
  }
12161
12555
  function sentinelKey(path) {
12162
12556
  return [0, encodeResourcePath(path)];
12163
12557
  }
12164
12558
  function createPrimaryClientStore(db) {
12165
- db.createObjectStore(DbPrimaryClient.store);
12559
+ db.createObjectStore(DbPrimaryClientStore);
12166
12560
  }
12167
12561
  function createMutationQueue(db) {
12168
- db.createObjectStore(DbMutationQueue.store, {
12169
- keyPath: DbMutationQueue.keyPath
12562
+ db.createObjectStore(DbMutationQueueStore, {
12563
+ keyPath: DbMutationQueueKeyPath
12170
12564
  });
12171
- const mutationBatchesStore = db.createObjectStore(DbMutationBatch.store, {
12172
- keyPath: DbMutationBatch.keyPath,
12565
+ const mutationBatchesStore = db.createObjectStore(DbMutationBatchStore, {
12566
+ keyPath: DbMutationBatchKeyPath,
12173
12567
  autoIncrement: true
12174
12568
  });
12175
- mutationBatchesStore.createIndex(DbMutationBatch.userMutationsIndex, DbMutationBatch.userMutationsKeyPath, { unique: true });
12176
- db.createObjectStore(DbDocumentMutation.store);
12569
+ mutationBatchesStore.createIndex(DbMutationBatchUserMutationsIndex, DbMutationBatchUserMutationsKeyPath, { unique: true });
12570
+ db.createObjectStore(DbDocumentMutationStore);
12177
12571
  }
12178
12572
  /**
12179
12573
  * Upgrade function to migrate the 'mutations' store from V1 to V3. Loads
12180
12574
  * and rewrites all data.
12181
12575
  */
12182
12576
  function upgradeMutationBatchSchemaAndMigrateData(db, txn) {
12183
- const v1MutationsStore = txn.store(DbMutationBatch.store);
12577
+ const v1MutationsStore = txn.store(DbMutationBatchStore);
12184
12578
  return v1MutationsStore.loadAll().next(existingMutations => {
12185
- db.deleteObjectStore(DbMutationBatch.store);
12186
- const mutationsStore = db.createObjectStore(DbMutationBatch.store, {
12187
- keyPath: DbMutationBatch.keyPath,
12579
+ db.deleteObjectStore(DbMutationBatchStore);
12580
+ const mutationsStore = db.createObjectStore(DbMutationBatchStore, {
12581
+ keyPath: DbMutationBatchKeyPath,
12188
12582
  autoIncrement: true
12189
12583
  });
12190
- mutationsStore.createIndex(DbMutationBatch.userMutationsIndex, DbMutationBatch.userMutationsKeyPath, { unique: true });
12191
- const v3MutationsStore = txn.store(DbMutationBatch.store);
12584
+ mutationsStore.createIndex(DbMutationBatchUserMutationsIndex, DbMutationBatchUserMutationsKeyPath, { unique: true });
12585
+ const v3MutationsStore = txn.store(DbMutationBatchStore);
12192
12586
  const writeAll = existingMutations.map(mutation => v3MutationsStore.put(mutation));
12193
12587
  return PersistencePromise.waitFor(writeAll);
12194
12588
  });
12195
12589
  }
12590
+ function createLegacyRemoteDocumentCache(db) {
12591
+ db.createObjectStore(DbRemoteDocumentStore$1);
12592
+ }
12196
12593
  function createRemoteDocumentCache(db) {
12197
- db.createObjectStore(DbRemoteDocument.store);
12594
+ const remoteDocumentStore = db.createObjectStore(DbRemoteDocumentStore, {
12595
+ keyPath: DbRemoteDocumentKeyPath
12596
+ });
12597
+ remoteDocumentStore.createIndex(DbRemoteDocumentDocumentKeyIndex, DbRemoteDocumentDocumentKeyIndexPath);
12598
+ remoteDocumentStore.createIndex(DbRemoteDocumentCollectionGroupIndex, DbRemoteDocumentCollectionGroupIndexPath);
12198
12599
  }
12199
12600
  function createDocumentGlobalStore(db) {
12200
- db.createObjectStore(DbRemoteDocumentGlobal.store);
12601
+ db.createObjectStore(DbRemoteDocumentGlobalStore);
12201
12602
  }
12202
12603
  function createQueryCache(db) {
12203
- const targetDocumentsStore = db.createObjectStore(DbTargetDocument.store, {
12204
- keyPath: DbTargetDocument.keyPath
12604
+ const targetDocumentsStore = db.createObjectStore(DbTargetDocumentStore, {
12605
+ keyPath: DbTargetDocumentKeyPath
12205
12606
  });
12206
- targetDocumentsStore.createIndex(DbTargetDocument.documentTargetsIndex, DbTargetDocument.documentTargetsKeyPath, { unique: true });
12207
- const targetStore = db.createObjectStore(DbTarget.store, {
12208
- keyPath: DbTarget.keyPath
12607
+ targetDocumentsStore.createIndex(DbTargetDocumentDocumentTargetsIndex, DbTargetDocumentDocumentTargetsKeyPath, { unique: true });
12608
+ const targetStore = db.createObjectStore(DbTargetStore, {
12609
+ keyPath: DbTargetKeyPath
12209
12610
  });
12210
12611
  // NOTE: This is unique only because the TargetId is the suffix.
12211
- targetStore.createIndex(DbTarget.queryTargetsIndexName, DbTarget.queryTargetsKeyPath, { unique: true });
12212
- db.createObjectStore(DbTargetGlobal.store);
12612
+ targetStore.createIndex(DbTargetQueryTargetsIndexName, DbTargetQueryTargetsKeyPath, { unique: true });
12613
+ db.createObjectStore(DbTargetGlobalStore);
12213
12614
  }
12214
12615
  function dropQueryCache(db) {
12215
- db.deleteObjectStore(DbTargetDocument.store);
12216
- db.deleteObjectStore(DbTarget.store);
12217
- db.deleteObjectStore(DbTargetGlobal.store);
12616
+ db.deleteObjectStore(DbTargetDocumentStore);
12617
+ db.deleteObjectStore(DbTargetStore);
12618
+ db.deleteObjectStore(DbTargetGlobalStore);
12218
12619
  }
12219
12620
  function dropRemoteDocumentChangesStore(db) {
12220
12621
  if (db.objectStoreNames.contains('remoteDocumentChanges')) {
@@ -12227,58 +12628,65 @@ function dropRemoteDocumentChangesStore(db) {
12227
12628
  * @param txn - The version upgrade transaction for indexeddb
12228
12629
  */
12229
12630
  function writeEmptyTargetGlobalEntry(txn) {
12230
- const globalStore = txn.store(DbTargetGlobal.store);
12231
- const metadata = new DbTargetGlobal(
12232
- /*highestTargetId=*/ 0,
12233
- /*lastListenSequenceNumber=*/ 0, SnapshotVersion.min().toTimestamp(),
12234
- /*targetCount=*/ 0);
12235
- return globalStore.put(DbTargetGlobal.key, metadata);
12236
- }
12237
- /**
12238
- * Creates indices on the RemoteDocuments store used for both multi-tab
12239
- * and Index-Free queries.
12240
- */
12241
- function createRemoteDocumentReadTimeIndex(txn) {
12242
- const remoteDocumentStore = txn.objectStore(DbRemoteDocument.store);
12243
- remoteDocumentStore.createIndex(DbRemoteDocument.readTimeIndex, DbRemoteDocument.readTimeIndexPath, { unique: false });
12244
- remoteDocumentStore.createIndex(DbRemoteDocument.collectionReadTimeIndex, DbRemoteDocument.collectionReadTimeIndexPath, { unique: false });
12631
+ const globalStore = txn.store(DbTargetGlobalStore);
12632
+ const metadata = {
12633
+ highestTargetId: 0,
12634
+ highestListenSequenceNumber: 0,
12635
+ lastRemoteSnapshotVersion: SnapshotVersion.min().toTimestamp(),
12636
+ targetCount: 0
12637
+ };
12638
+ return globalStore.put(DbTargetGlobalKey, metadata);
12245
12639
  }
12246
12640
  function createClientMetadataStore(db) {
12247
- db.createObjectStore(DbClientMetadata.store, {
12248
- keyPath: DbClientMetadata.keyPath
12641
+ db.createObjectStore(DbClientMetadataStore, {
12642
+ keyPath: DbClientMetadataKeyPath
12249
12643
  });
12250
12644
  }
12251
12645
  function createBundlesStore(db) {
12252
- db.createObjectStore(DbBundle.store, {
12253
- keyPath: DbBundle.keyPath
12646
+ db.createObjectStore(DbBundleStore, {
12647
+ keyPath: DbBundleKeyPath
12254
12648
  });
12255
12649
  }
12256
12650
  function createNamedQueriesStore(db) {
12257
- db.createObjectStore(DbNamedQuery.store, {
12258
- keyPath: DbNamedQuery.keyPath
12651
+ db.createObjectStore(DbNamedQueryStore, {
12652
+ keyPath: DbNamedQueryKeyPath
12259
12653
  });
12260
12654
  }
12261
12655
  function createFieldIndex(db) {
12262
- const indexConfiguratioStore = db.createObjectStore(DbIndexConfiguration.store, {
12263
- keyPath: DbIndexConfiguration.keyPath,
12656
+ const indexConfigurationStore = db.createObjectStore(DbIndexConfigurationStore, {
12657
+ keyPath: DbIndexConfigurationKeyPath,
12264
12658
  autoIncrement: true
12265
12659
  });
12266
- indexConfiguratioStore.createIndex(DbIndexConfiguration.collectionGroupIndex, DbIndexConfiguration.collectionGroupIndexPath, { unique: false });
12267
- const indexStateStore = db.createObjectStore(DbIndexState.store, {
12268
- keyPath: DbIndexState.keyPath
12660
+ indexConfigurationStore.createIndex(DbIndexConfigurationCollectionGroupIndex, DbIndexConfigurationCollectionGroupIndexPath, { unique: false });
12661
+ const indexStateStore = db.createObjectStore(DbIndexStateStore, {
12662
+ keyPath: DbIndexStateKeyPath
12269
12663
  });
12270
- indexStateStore.createIndex(DbIndexState.sequenceNumberIndex, DbIndexState.sequenceNumberIndexPath, { unique: false });
12271
- const indexEntryStore = db.createObjectStore(DbIndexEntry.store, {
12272
- keyPath: DbIndexEntry.keyPath
12664
+ indexStateStore.createIndex(DbIndexStateSequenceNumberIndex, DbIndexStateSequenceNumberIndexPath, { unique: false });
12665
+ const indexEntryStore = db.createObjectStore(DbIndexEntryStore, {
12666
+ keyPath: DbIndexEntryKeyPath
12273
12667
  });
12274
- indexEntryStore.createIndex(DbIndexEntry.documentKeyIndex, DbIndexEntry.documentKeyIndexPath, { unique: false });
12668
+ indexEntryStore.createIndex(DbIndexEntryDocumentKeyIndex, DbIndexEntryDocumentKeyIndexPath, { unique: false });
12275
12669
  }
12276
12670
  function createDocumentOverlayStore(db) {
12277
- const documentOverlayStore = db.createObjectStore(DbDocumentOverlay.store, {
12278
- keyPath: DbDocumentOverlay.keyPath
12671
+ const documentOverlayStore = db.createObjectStore(DbDocumentOverlayStore, {
12672
+ keyPath: DbDocumentOverlayKeyPath
12279
12673
  });
12280
- documentOverlayStore.createIndex(DbDocumentOverlay.collectionPathOverlayIndex, DbDocumentOverlay.collectionPathOverlayIndexPath, { unique: false });
12281
- documentOverlayStore.createIndex(DbDocumentOverlay.collectionGroupOverlayIndex, DbDocumentOverlay.collectionGroupOverlayIndexPath, { unique: false });
12674
+ documentOverlayStore.createIndex(DbDocumentOverlayCollectionPathOverlayIndex, DbDocumentOverlayCollectionPathOverlayIndexPath, { unique: false });
12675
+ documentOverlayStore.createIndex(DbDocumentOverlayCollectionGroupOverlayIndex, DbDocumentOverlayCollectionGroupOverlayIndexPath, { unique: false });
12676
+ }
12677
+ function extractKey(remoteDoc) {
12678
+ if (remoteDoc.document) {
12679
+ return new DocumentKey(ResourcePath.fromString(remoteDoc.document.name).popFirst(5));
12680
+ }
12681
+ else if (remoteDoc.noDocument) {
12682
+ return DocumentKey.fromSegments(remoteDoc.noDocument.path);
12683
+ }
12684
+ else if (remoteDoc.unknownDocument) {
12685
+ return DocumentKey.fromSegments(remoteDoc.unknownDocument.path);
12686
+ }
12687
+ else {
12688
+ return fail();
12689
+ }
12282
12690
  }
12283
12691
 
12284
12692
  /**
@@ -12526,7 +12934,12 @@ class IndexedDbPersistence {
12526
12934
  return this.runTransaction('updateClientMetadataAndTryBecomePrimary', 'readwrite', txn => {
12527
12935
  const metadataStore = clientMetadataStore(txn);
12528
12936
  return metadataStore
12529
- .put(new DbClientMetadata(this.clientId, Date.now(), this.networkEnabled, this.inForeground))
12937
+ .put({
12938
+ clientId: this.clientId,
12939
+ updateTimeMs: Date.now(),
12940
+ networkEnabled: this.networkEnabled,
12941
+ inForeground: this.inForeground
12942
+ })
12530
12943
  .next(() => {
12531
12944
  if (this.isPrimary) {
12532
12945
  return this.verifyPrimaryLease(txn).next(success => {
@@ -12572,7 +12985,7 @@ class IndexedDbPersistence {
12572
12985
  }
12573
12986
  verifyPrimaryLease(txn) {
12574
12987
  const store = primaryClientStore(txn);
12575
- return store.get(DbPrimaryClient.key).next(primaryClient => {
12988
+ return store.get(DbPrimaryClientKey).next(primaryClient => {
12576
12989
  return PersistencePromise.resolve(this.isLocalClient(primaryClient));
12577
12990
  });
12578
12991
  }
@@ -12590,7 +13003,7 @@ class IndexedDbPersistence {
12590
13003
  !this.isWithinAge(this.lastGarbageCollectionTime, MAX_CLIENT_AGE_MS)) {
12591
13004
  this.lastGarbageCollectionTime = Date.now();
12592
13005
  const inactiveClients = await this.runTransaction('maybeGarbageCollectMultiClientState', 'readwrite-primary', txn => {
12593
- const metadataStore = getStore(txn, DbClientMetadata.store);
13006
+ const metadataStore = getStore(txn, DbClientMetadataStore);
12594
13007
  return metadataStore.loadAll().next(existingClients => {
12595
13008
  const active = this.filterActiveClients(existingClients, MAX_CLIENT_AGE_MS);
12596
13009
  const inactive = existingClients.filter(client => active.indexOf(client) === -1);
@@ -12644,7 +13057,7 @@ class IndexedDbPersistence {
12644
13057
  }
12645
13058
  const store = primaryClientStore(txn);
12646
13059
  return store
12647
- .get(DbPrimaryClient.key)
13060
+ .get(DbPrimaryClientKey)
12648
13061
  .next(currentPrimary => {
12649
13062
  const currentLeaseIsValid = currentPrimary !== null &&
12650
13063
  this.isWithinAge(currentPrimary.leaseTimestampMs, MAX_PRIMARY_ELIGIBLE_AGE_MS) &&
@@ -12725,7 +13138,7 @@ class IndexedDbPersistence {
12725
13138
  this.detachWindowUnloadHook();
12726
13139
  // Use `SimpleDb.runTransaction` directly to avoid failing if another tab
12727
13140
  // has obtained the primary lease.
12728
- await this.simpleDb.runTransaction('shutdown', 'readwrite', [DbPrimaryClient.store, DbClientMetadata.store], simpleDbTxn => {
13141
+ await this.simpleDb.runTransaction('shutdown', 'readwrite', [DbPrimaryClientStore, DbClientMetadataStore], simpleDbTxn => {
12729
13142
  const persistenceTransaction = new IndexedDbTransaction(simpleDbTxn, ListenSequence.INVALID);
12730
13143
  return this.releasePrimaryLeaseIfHeld(persistenceTransaction).next(() => this.removeClientMetadata(persistenceTransaction));
12731
13144
  });
@@ -12832,7 +13245,7 @@ class IndexedDbPersistence {
12832
13245
  // be turned off.
12833
13246
  verifyAllowTabSynchronization(txn) {
12834
13247
  const store = primaryClientStore(txn);
12835
- return store.get(DbPrimaryClient.key).next(currentPrimary => {
13248
+ return store.get(DbPrimaryClientKey).next(currentPrimary => {
12836
13249
  const currentLeaseIsValid = currentPrimary !== null &&
12837
13250
  this.isWithinAge(currentPrimary.leaseTimestampMs, MAX_PRIMARY_ELIGIBLE_AGE_MS) &&
12838
13251
  !this.isClientZombied(currentPrimary.ownerId);
@@ -12850,8 +13263,12 @@ class IndexedDbPersistence {
12850
13263
  * method does not verify that the client is eligible for this lease.
12851
13264
  */
12852
13265
  acquireOrExtendPrimaryLease(txn) {
12853
- const newPrimary = new DbPrimaryClient(this.clientId, this.allowTabSynchronization, Date.now());
12854
- return primaryClientStore(txn).put(DbPrimaryClient.key, newPrimary);
13266
+ const newPrimary = {
13267
+ ownerId: this.clientId,
13268
+ allowTabSynchronization: this.allowTabSynchronization,
13269
+ leaseTimestampMs: Date.now()
13270
+ };
13271
+ return primaryClientStore(txn).put(DbPrimaryClientKey, newPrimary);
12855
13272
  }
12856
13273
  static isAvailable() {
12857
13274
  return SimpleDb.isAvailable();
@@ -12859,10 +13276,10 @@ class IndexedDbPersistence {
12859
13276
  /** Checks the primary lease and removes it if we are the current primary. */
12860
13277
  releasePrimaryLeaseIfHeld(txn) {
12861
13278
  const store = primaryClientStore(txn);
12862
- return store.get(DbPrimaryClient.key).next(primaryClient => {
13279
+ return store.get(DbPrimaryClientKey).next(primaryClient => {
12863
13280
  if (this.isLocalClient(primaryClient)) {
12864
13281
  logDebug(LOG_TAG$d, 'Releasing primary lease.');
12865
- return store.delete(DbPrimaryClient.key);
13282
+ return store.delete(DbPrimaryClientKey);
12866
13283
  }
12867
13284
  else {
12868
13285
  return PersistencePromise.resolve();
@@ -12996,13 +13413,13 @@ class IndexedDbPersistence {
12996
13413
  * Helper to get a typed SimpleDbStore for the primary client object store.
12997
13414
  */
12998
13415
  function primaryClientStore(txn) {
12999
- return getStore(txn, DbPrimaryClient.store);
13416
+ return getStore(txn, DbPrimaryClientStore);
13000
13417
  }
13001
13418
  /**
13002
13419
  * Helper to get a typed SimpleDbStore for the client metadata object store.
13003
13420
  */
13004
13421
  function clientMetadataStore(txn) {
13005
- return getStore(txn, DbClientMetadata.store);
13422
+ return getStore(txn, DbClientMetadataStore);
13006
13423
  }
13007
13424
  /**
13008
13425
  * Generates a string used as a prefix when storing data in IndexedDB and
@@ -13112,18 +13529,17 @@ class LocalDocumentsView {
13112
13529
  *
13113
13530
  * @param transaction - The persistence transaction.
13114
13531
  * @param query - The query to match documents against.
13115
- * @param sinceReadTime - If not set to SnapshotVersion.min(), return only
13116
- * documents that have been read since this snapshot version (exclusive).
13532
+ * @param offset - Read time and key to start scanning by (exclusive).
13117
13533
  */
13118
- getDocumentsMatchingQuery(transaction, query, sinceReadTime) {
13534
+ getDocumentsMatchingQuery(transaction, query, offset) {
13119
13535
  if (isDocumentQuery$1(query)) {
13120
13536
  return this.getDocumentsMatchingDocumentQuery(transaction, query.path);
13121
13537
  }
13122
13538
  else if (isCollectionGroupQuery(query)) {
13123
- return this.getDocumentsMatchingCollectionGroupQuery(transaction, query, sinceReadTime);
13539
+ return this.getDocumentsMatchingCollectionGroupQuery(transaction, query, offset);
13124
13540
  }
13125
13541
  else {
13126
- return this.getDocumentsMatchingCollectionQuery(transaction, query, sinceReadTime);
13542
+ return this.getDocumentsMatchingCollectionQuery(transaction, query, offset);
13127
13543
  }
13128
13544
  }
13129
13545
  getDocumentsMatchingDocumentQuery(transaction, docPath) {
@@ -13136,7 +13552,7 @@ class LocalDocumentsView {
13136
13552
  return result;
13137
13553
  });
13138
13554
  }
13139
- getDocumentsMatchingCollectionGroupQuery(transaction, query, sinceReadTime) {
13555
+ getDocumentsMatchingCollectionGroupQuery(transaction, query, offset) {
13140
13556
  const collectionId = query.collectionGroup;
13141
13557
  let results = documentMap();
13142
13558
  return this.indexManager
@@ -13146,7 +13562,7 @@ class LocalDocumentsView {
13146
13562
  // collectionId and aggregate the results.
13147
13563
  return PersistencePromise.forEach(parents, (parent) => {
13148
13564
  const collectionQuery = asCollectionQueryAtPath(query, parent.child(collectionId));
13149
- return this.getDocumentsMatchingCollectionQuery(transaction, collectionQuery, sinceReadTime).next(r => {
13565
+ return this.getDocumentsMatchingCollectionQuery(transaction, collectionQuery, offset).next(r => {
13150
13566
  r.forEach((key, doc) => {
13151
13567
  results = results.insert(key, doc);
13152
13568
  });
@@ -13154,11 +13570,11 @@ class LocalDocumentsView {
13154
13570
  }).next(() => results);
13155
13571
  });
13156
13572
  }
13157
- getDocumentsMatchingCollectionQuery(transaction, query, sinceReadTime) {
13573
+ getDocumentsMatchingCollectionQuery(transaction, query, offset) {
13158
13574
  // Query the remote documents and overlay mutations.
13159
13575
  let results;
13160
13576
  return this.remoteDocumentCache
13161
- .getAll(transaction, query.path, sinceReadTime)
13577
+ .getAllFromCollection(transaction, query.path, offset)
13162
13578
  .next(queryResults => {
13163
13579
  results = queryResults;
13164
13580
  return this.mutationQueue.getAllMutationBatchesAffectingQuery(transaction, query);
@@ -13244,11 +13660,12 @@ class LocalStoreImpl {
13244
13660
  // TODO(wuandy): Evaluate if TargetId can be part of Target.
13245
13661
  this.targetIdByTarget = new ObjectMap(t => canonifyTarget(t), targetEquals);
13246
13662
  /**
13247
- * The read time of the last entry processed by `getNewDocumentChanges()`.
13663
+ * A per collection group index of the last read time processed by
13664
+ * `getNewDocumentChanges()`.
13248
13665
  *
13249
13666
  * PORTING NOTE: This is only used for multi-tab synchronization.
13250
13667
  */
13251
- this.lastDocumentChangeReadTime = SnapshotVersion.min();
13668
+ this.collectionGroupReadTime = new Map();
13252
13669
  this.remoteDocuments = persistence.getRemoteDocumentCache();
13253
13670
  this.targetCache = persistence.getTargetCache();
13254
13671
  this.bundleCache = persistence.getBundleCache();
@@ -13783,6 +14200,7 @@ function localStoreExecuteQuery(localStore, query, usePreviousResults) {
13783
14200
  ? lastLimboFreeSnapshotVersion
13784
14201
  : SnapshotVersion.min(), usePreviousResults ? remoteKeys : documentKeySet()))
13785
14202
  .next(documents => {
14203
+ setMaxReadTime(localStoreImpl, queryCollectionGroup(query), documents);
13786
14204
  return { documents, remoteKeys };
13787
14205
  });
13788
14206
  });
@@ -13860,28 +14278,32 @@ function localStoreGetCachedTarget(localStore, targetId) {
13860
14278
  * since the prior call.
13861
14279
  */
13862
14280
  // PORTING NOTE: Multi-Tab only.
13863
- function localStoreGetNewDocumentChanges(localStore) {
14281
+ function localStoreGetNewDocumentChanges(localStore, collectionGroup) {
13864
14282
  const localStoreImpl = debugCast(localStore);
14283
+ // Get the current maximum read time for the collection. This should always
14284
+ // exist, but to reduce the chance for regressions we default to
14285
+ // SnapshotVersion.Min()
14286
+ // TODO(indexing): Consider removing the default value.
14287
+ const readTime = localStoreImpl.collectionGroupReadTime.get(collectionGroup) ||
14288
+ SnapshotVersion.min();
13865
14289
  return localStoreImpl.persistence
13866
- .runTransaction('Get new document changes', 'readonly', txn => remoteDocumentCacheGetNewDocumentChanges(localStoreImpl.remoteDocuments, txn, localStoreImpl.lastDocumentChangeReadTime))
13867
- .then(({ changedDocs, readTime }) => {
13868
- localStoreImpl.lastDocumentChangeReadTime = readTime;
14290
+ .runTransaction('Get new document changes', 'readonly', txn => localStoreImpl.remoteDocuments.getAllFromCollectionGroup(txn, collectionGroup, newIndexOffsetSuccessorFromReadTime(readTime, INITIAL_LARGEST_BATCH_ID),
14291
+ /* limit= */ Number.MAX_SAFE_INTEGER))
14292
+ .then(changedDocs => {
14293
+ setMaxReadTime(localStoreImpl, collectionGroup, changedDocs);
13869
14294
  return changedDocs;
13870
14295
  });
13871
14296
  }
13872
- /**
13873
- * Reads the newest document change from persistence and moves the internal
13874
- * synchronization marker forward so that calls to `getNewDocumentChanges()`
13875
- * only return changes that happened after client initialization.
13876
- */
14297
+ /** Sets the collection group's maximum read time from the given documents. */
13877
14298
  // PORTING NOTE: Multi-Tab only.
13878
- async function localStoreSynchronizeLastDocumentChangeReadTime(localStore) {
13879
- const localStoreImpl = debugCast(localStore);
13880
- return localStoreImpl.persistence
13881
- .runTransaction('Synchronize last document change read time', 'readonly', txn => remoteDocumentCacheGetLastReadTime(txn))
13882
- .then(readTime => {
13883
- localStoreImpl.lastDocumentChangeReadTime = readTime;
14299
+ function setMaxReadTime(localStoreImpl, collectionGroup, changedDocs) {
14300
+ let readTime = SnapshotVersion.min();
14301
+ changedDocs.forEach((_, doc) => {
14302
+ if (doc.readTime.compareTo(readTime) > 0) {
14303
+ readTime = doc.readTime;
14304
+ }
13884
14305
  });
14306
+ localStoreImpl.collectionGroupReadTime.set(collectionGroup, readTime);
13885
14307
  }
13886
14308
  /**
13887
14309
  * Creates a new target using the given bundle name, which will be used to
@@ -14065,7 +14487,7 @@ class MemoryDocumentOverlayCache {
14065
14487
  return PersistencePromise.resolve(this.overlays.get(key));
14066
14488
  }
14067
14489
  saveOverlays(transaction, largestBatchId, overlays) {
14068
- overlays.forEach(mutation => {
14490
+ overlays.forEach((_, mutation) => {
14069
14491
  this.saveOverlay(transaction, largestBatchId, mutation);
14070
14492
  });
14071
14493
  return PersistencePromise.resolve();
@@ -14079,7 +14501,7 @@ class MemoryDocumentOverlayCache {
14079
14501
  return PersistencePromise.resolve();
14080
14502
  }
14081
14503
  getOverlaysForCollection(transaction, collection, sinceBatchId) {
14082
- const result = new Map();
14504
+ const result = newOverlayMap();
14083
14505
  const immediateChildrenPathLength = collection.length + 1;
14084
14506
  const prefix = new DocumentKey(collection.child(''));
14085
14507
  const iter = this.overlays.getIteratorFrom(prefix);
@@ -14113,19 +14535,19 @@ class MemoryDocumentOverlayCache {
14113
14535
  if (overlay.largestBatchId > sinceBatchId) {
14114
14536
  let overlaysForBatchId = batchIdToOverlays.get(overlay.largestBatchId);
14115
14537
  if (overlaysForBatchId === null) {
14116
- overlaysForBatchId = new Map();
14538
+ overlaysForBatchId = newOverlayMap();
14117
14539
  batchIdToOverlays = batchIdToOverlays.insert(overlay.largestBatchId, overlaysForBatchId);
14118
14540
  }
14119
14541
  overlaysForBatchId.set(overlay.getKey(), overlay);
14120
14542
  }
14121
14543
  }
14122
- const result = new Map();
14544
+ const result = newOverlayMap();
14123
14545
  const batchIter = batchIdToOverlays.getIterator();
14124
14546
  while (batchIter.hasNext()) {
14125
14547
  const entry = batchIter.getNext();
14126
14548
  const overlays = entry.value;
14127
- overlays.forEach((overlay, key) => result.set(key, overlay));
14128
- if (result.size >= count) {
14549
+ overlays.forEach((key, overlay) => result.set(key, overlay));
14550
+ if (result.size() >= count) {
14129
14551
  break;
14130
14552
  }
14131
14553
  }
@@ -14138,16 +14560,19 @@ class MemoryDocumentOverlayCache {
14138
14560
  // Remove the association of the overlay to its batch id.
14139
14561
  const existing = this.overlays.get(mutation.key);
14140
14562
  if (existing !== null) {
14141
- this.overlayByBatchId.get(existing.largestBatchId).delete(mutation.key);
14563
+ const newSet = this.overlayByBatchId
14564
+ .get(existing.largestBatchId)
14565
+ .delete(mutation.key);
14566
+ this.overlayByBatchId.set(existing.largestBatchId, newSet);
14142
14567
  }
14143
14568
  this.overlays = this.overlays.insert(mutation.key, new Overlay(largestBatchId, mutation));
14144
14569
  // Create the association of this overlay to the given largestBatchId.
14145
14570
  let batch = this.overlayByBatchId.get(largestBatchId);
14146
14571
  if (batch === undefined) {
14147
- batch = new Set();
14572
+ batch = documentKeySet();
14148
14573
  this.overlayByBatchId.set(largestBatchId, batch);
14149
14574
  }
14150
- batch.add(mutation.key);
14575
+ this.overlayByBatchId.set(largestBatchId, batch.add(mutation.key));
14151
14576
  }
14152
14577
  }
14153
14578
 
@@ -14561,7 +14986,7 @@ class MemoryRemoteDocumentCacheImpl {
14561
14986
  });
14562
14987
  return PersistencePromise.resolve(results);
14563
14988
  }
14564
- getAll(transaction, collectionPath, sinceReadTime) {
14989
+ getAllFromCollection(transaction, collectionPath, offset) {
14565
14990
  let results = mutableDocumentMap();
14566
14991
  // Documents are ordered by key, so we can use a prefix scan to narrow down
14567
14992
  // the documents we need to match the query against.
@@ -14576,13 +15001,19 @@ class MemoryRemoteDocumentCacheImpl {
14576
15001
  // Exclude entries from subcollections.
14577
15002
  continue;
14578
15003
  }
14579
- if (document.readTime.compareTo(sinceReadTime) <= 0) {
15004
+ if (indexOffsetComparator(newIndexOffsetFromDocument(document), offset) <= 0) {
15005
+ // The document sorts before the offset.
14580
15006
  continue;
14581
15007
  }
14582
15008
  results = results.insert(document.key, document.mutableCopy());
14583
15009
  }
14584
15010
  return PersistencePromise.resolve(results);
14585
15011
  }
15012
+ getAllFromCollectionGroup(transaction, collectionGroup, offset, limti) {
15013
+ // This method should only be called from the IndexBackfiller if persistence
15014
+ // is enabled.
15015
+ fail();
15016
+ }
14586
15017
  forEachDocumentKey(transaction, f) {
14587
15018
  return PersistencePromise.forEach(this.docs, (key) => f(key));
14588
15019
  }
@@ -15035,7 +15466,7 @@ class QueryEngine {
15035
15466
  }
15036
15467
  // Retrieve all results for documents that were updated since the last
15037
15468
  // limbo-document free remote snapshot.
15038
- return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, lastLimboFreeSnapshotVersion).next(updatedResults => {
15469
+ return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, newIndexOffsetSuccessorFromReadTime(lastLimboFreeSnapshotVersion, INITIAL_LARGEST_BATCH_ID)).next(updatedResults => {
15039
15470
  // We merge `previousResults` into `updateResults`, since
15040
15471
  // `updateResults` is already a DocumentMap. If a document is
15041
15472
  // contained in both lists, then its contents are the same.
@@ -15097,7 +15528,7 @@ class QueryEngine {
15097
15528
  if (getLogLevel() <= logger.LogLevel.DEBUG) {
15098
15529
  logDebug('QueryEngine', 'Using full collection scan to execute query:', stringifyQuery(query));
15099
15530
  }
15100
- return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, SnapshotVersion.min());
15531
+ return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, IndexOffset.min());
15101
15532
  }
15102
15533
  }
15103
15534
 
@@ -15158,8 +15589,9 @@ function createWebStorageOnlineStateKey(persistenceKey) {
15158
15589
  // The WebStorage prefix that plays as a event to indicate the remote documents
15159
15590
  // might have changed due to some secondary tabs loading a bundle.
15160
15591
  // format of the key is:
15161
- // firestore_bundle_loaded_<persistenceKey>
15162
- const BUNDLE_LOADED_KEY_PREFIX = 'firestore_bundle_loaded';
15592
+ // firestore_bundle_loaded_v2_<persistenceKey>
15593
+ // The version ending with "v2" stores the list of modified collection groups.
15594
+ const BUNDLE_LOADED_KEY_PREFIX = 'firestore_bundle_loaded_v2';
15163
15595
  function createBundleLoadedKey(persistenceKey) {
15164
15596
  return `${BUNDLE_LOADED_KEY_PREFIX}_${persistenceKey}`;
15165
15597
  }
@@ -15537,8 +15969,8 @@ class WebStorageSharedClientState {
15537
15969
  setOnlineState(onlineState) {
15538
15970
  this.persistOnlineState(onlineState);
15539
15971
  }
15540
- notifyBundleLoaded() {
15541
- this.persistBundleLoadedState();
15972
+ notifyBundleLoaded(collectionGroups) {
15973
+ this.persistBundleLoadedState(collectionGroups);
15542
15974
  }
15543
15975
  shutdown() {
15544
15976
  if (this.started) {
@@ -15622,7 +16054,8 @@ class WebStorageSharedClientState {
15622
16054
  }
15623
16055
  }
15624
16056
  else if (storageEvent.key === this.bundleLoadedKey) {
15625
- return this.syncEngine.synchronizeWithChangedDocuments();
16057
+ const collectionGroups = this.fromWebStoreBundleLoadedState(storageEvent.newValue);
16058
+ await Promise.all(collectionGroups.map(cg => this.syncEngine.synchronizeWithChangedDocuments(cg)));
15626
16059
  }
15627
16060
  });
15628
16061
  }
@@ -15654,8 +16087,9 @@ class WebStorageSharedClientState {
15654
16087
  const targetMetadata = new QueryTargetMetadata(targetId, state, error);
15655
16088
  this.setItem(targetKey, targetMetadata.toWebStorageJSON());
15656
16089
  }
15657
- persistBundleLoadedState() {
15658
- this.setItem(this.bundleLoadedKey, 'value-not-used');
16090
+ persistBundleLoadedState(collectionGroups) {
16091
+ const json = JSON.stringify(Array.from(collectionGroups));
16092
+ this.setItem(this.bundleLoadedKey, json);
15659
16093
  }
15660
16094
  /**
15661
16095
  * Parses a client state key in WebStorage. Returns null if the key does not
@@ -15699,6 +16133,9 @@ class WebStorageSharedClientState {
15699
16133
  fromWebStorageOnlineState(value) {
15700
16134
  return SharedOnlineState.fromWebStorageEntry(value);
15701
16135
  }
16136
+ fromWebStoreBundleLoadedState(value) {
16137
+ return JSON.parse(value);
16138
+ }
15702
16139
  async handleMutationBatchEvent(mutationBatch) {
15703
16140
  if (mutationBatch.user.uid !== this.currentUser.uid) {
15704
16141
  logDebug(LOG_TAG$a, `Ignoring mutation for non-active user ${mutationBatch.user.uid}`);
@@ -15815,7 +16252,7 @@ class MemorySharedClientState {
15815
16252
  }
15816
16253
  shutdown() { }
15817
16254
  writeSequenceNumber(sequenceNumber) { }
15818
- notifyBundleLoaded() {
16255
+ notifyBundleLoaded(collectionGroups) {
15819
16256
  // No op.
15820
16257
  }
15821
16258
  }
@@ -15998,9 +16435,13 @@ function createMetadata(databasePath, authToken, appCheckToken, appId) {
15998
16435
  metadata.set('X-Firebase-GMPID', appId);
15999
16436
  }
16000
16437
  metadata.set('X-Goog-Api-Client', X_GOOG_API_CLIENT_VALUE);
16001
- // This header is used to improve routing and project isolation by the
16438
+ // These headers are used to improve routing and project isolation by the
16002
16439
  // backend.
16440
+ // TODO(b/199767712): We are keeping 'Google-Cloud-Resource-Prefix' until Emulators can be
16441
+ // released with cl/428820046. Currently blocked because Emulators are now built with Java
16442
+ // 11 from Google3.
16003
16443
  metadata.set('Google-Cloud-Resource-Prefix', databasePath);
16444
+ metadata.set('x-goog-request-params', databasePath);
16004
16445
  return metadata;
16005
16446
  }
16006
16447
  /**
@@ -21111,29 +21552,6 @@ class LocalViewChanges {
21111
21552
  }
21112
21553
  }
21113
21554
 
21114
- /**
21115
- * @license
21116
- * Copyright 2020 Google LLC
21117
- *
21118
- * Licensed under the Apache License, Version 2.0 (the "License");
21119
- * you may not use this file except in compliance with the License.
21120
- * You may obtain a copy of the License at
21121
- *
21122
- * http://www.apache.org/licenses/LICENSE-2.0
21123
- *
21124
- * Unless required by applicable law or agreed to in writing, software
21125
- * distributed under the License is distributed on an "AS IS" BASIS,
21126
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21127
- * See the License for the specific language governing permissions and
21128
- * limitations under the License.
21129
- */
21130
- class BundleLoadResult {
21131
- constructor(progress, changedDocs) {
21132
- this.progress = progress;
21133
- this.changedDocs = changedDocs;
21134
- }
21135
- }
21136
-
21137
21555
  /**
21138
21556
  * @license
21139
21557
  * Copyright 2020 Google LLC
@@ -21188,6 +21606,8 @@ class BundleLoader {
21188
21606
  this.queries = [];
21189
21607
  /** Batched documents to be saved into storage */
21190
21608
  this.documents = [];
21609
+ /** The collection groups affected by this bundle. */
21610
+ this.collectionGroups = new Set();
21191
21611
  this.progress = bundleInitialProgress(bundleMetadata);
21192
21612
  }
21193
21613
  /**
@@ -21207,6 +21627,8 @@ class BundleLoader {
21207
21627
  if (!element.payload.documentMetadata.exists) {
21208
21628
  ++documentsLoaded;
21209
21629
  }
21630
+ const path = ResourcePath.fromString(element.payload.documentMetadata.name);
21631
+ this.collectionGroups.add(path.get(path.length - 2));
21210
21632
  }
21211
21633
  else if (element.payload.document) {
21212
21634
  this.documents[this.documents.length - 1].document =
@@ -21237,13 +21659,17 @@ class BundleLoader {
21237
21659
  * Update the progress to 'Success' and return the updated progress.
21238
21660
  */
21239
21661
  async complete() {
21240
- const changedDocuments = await localStoreApplyBundledDocuments(this.localStore, new BundleConverterImpl(this.serializer), this.documents, this.bundleMetadata.id);
21662
+ const changedDocs = await localStoreApplyBundledDocuments(this.localStore, new BundleConverterImpl(this.serializer), this.documents, this.bundleMetadata.id);
21241
21663
  const queryDocumentMap = this.getQueryDocumentMapping(this.documents);
21242
21664
  for (const q of this.queries) {
21243
21665
  await localStoreSaveNamedQuery(this.localStore, q, queryDocumentMap.get(q.name));
21244
21666
  }
21245
21667
  this.progress.taskState = 'Success';
21246
- return new BundleLoadResult(Object.assign({}, this.progress), changedDocuments);
21668
+ return {
21669
+ progress: this.progress,
21670
+ changedCollectionGroups: this.collectionGroups,
21671
+ changedDocs
21672
+ };
21247
21673
  }
21248
21674
  }
21249
21675
  /**
@@ -22322,9 +22748,9 @@ async function synchronizeViewAndComputeSnapshot(syncEngine, queryView) {
22322
22748
  * snapshots if needed.
22323
22749
  */
22324
22750
  // PORTING NOTE: Multi-Tab only.
22325
- async function syncEngineSynchronizeWithChangedDocuments(syncEngine) {
22751
+ async function syncEngineSynchronizeWithChangedDocuments(syncEngine, collectionGroup) {
22326
22752
  const syncEngineImpl = debugCast(syncEngine);
22327
- return localStoreGetNewDocumentChanges(syncEngineImpl.localStore).then(changes => syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes));
22753
+ return localStoreGetNewDocumentChanges(syncEngineImpl.localStore, collectionGroup).then(changes => syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes));
22328
22754
  }
22329
22755
  /** Applies a mutation state to an existing batch. */
22330
22756
  // PORTING NOTE: Multi-Tab only.
@@ -22489,11 +22915,12 @@ async function syncEngineApplyTargetState(syncEngine, targetId, state, error) {
22489
22915
  logDebug(LOG_TAG$3, 'Ignoring unexpected query state notification.');
22490
22916
  return;
22491
22917
  }
22492
- if (syncEngineImpl.queriesByTarget.has(targetId)) {
22918
+ const query = syncEngineImpl.queriesByTarget.get(targetId);
22919
+ if (query && query.length > 0) {
22493
22920
  switch (state) {
22494
22921
  case 'current':
22495
22922
  case 'not-current': {
22496
- const changes = await localStoreGetNewDocumentChanges(syncEngineImpl.localStore);
22923
+ const changes = await localStoreGetNewDocumentChanges(syncEngineImpl.localStore, queryCollectionGroup(query[0]));
22497
22924
  const synthesizedRemoteEvent = RemoteEvent.createSynthesizedRemoteEventForCurrentChange(targetId, state === 'current');
22498
22925
  await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes, synthesizedRemoteEvent);
22499
22926
  break;
@@ -22576,10 +23003,11 @@ function syncEngineEnsureWriteCallbacks(syncEngine) {
22576
23003
  function syncEngineLoadBundle(syncEngine, bundleReader, task) {
22577
23004
  const syncEngineImpl = debugCast(syncEngine);
22578
23005
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
22579
- loadBundleImpl(syncEngineImpl, bundleReader, task).then(() => {
22580
- syncEngineImpl.sharedClientState.notifyBundleLoaded();
23006
+ loadBundleImpl(syncEngineImpl, bundleReader, task).then(collectionGroups => {
23007
+ syncEngineImpl.sharedClientState.notifyBundleLoaded(collectionGroups);
22581
23008
  });
22582
23009
  }
23010
+ /** Loads a bundle and returns the list of affected collection groups. */
22583
23011
  async function loadBundleImpl(syncEngine, reader, task) {
22584
23012
  try {
22585
23013
  const metadata = await reader.getMetadata();
@@ -22587,7 +23015,7 @@ async function loadBundleImpl(syncEngine, reader, task) {
22587
23015
  if (skip) {
22588
23016
  await reader.close();
22589
23017
  task._completeWith(bundleSuccessProgress(metadata));
22590
- return;
23018
+ return Promise.resolve(new Set());
22591
23019
  }
22592
23020
  task._updateProgress(bundleInitialProgress(metadata));
22593
23021
  const loader = new BundleLoader(metadata, syncEngine.localStore, reader.serializer);
@@ -22601,18 +23029,17 @@ async function loadBundleImpl(syncEngine, reader, task) {
22601
23029
  element = await reader.nextElement();
22602
23030
  }
22603
23031
  const result = await loader.complete();
22604
- // TODO(b/160876443): This currently raises snapshots with
22605
- // `fromCache=false` if users already listen to some queries and bundles
22606
- // has newer version.
22607
23032
  await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngine, result.changedDocs,
22608
23033
  /* remoteEvent */ undefined);
22609
23034
  // Save metadata, so loading the same bundle will skip.
22610
23035
  await localStoreSaveBundle(syncEngine.localStore, metadata);
22611
23036
  task._completeWith(result.progress);
23037
+ return Promise.resolve(result.changedCollectionGroups);
22612
23038
  }
22613
23039
  catch (e) {
22614
23040
  logWarn(LOG_TAG$3, `Loading bundle failed with ${e}`);
22615
23041
  task._failWith(e);
23042
+ return Promise.resolve(new Set());
22616
23043
  }
22617
23044
  }
22618
23045
 
@@ -22681,7 +23108,6 @@ class IndexedDbOfflineComponentProvider extends MemoryOfflineComponentProvider {
22681
23108
  }
22682
23109
  async initialize(cfg) {
22683
23110
  await super.initialize(cfg);
22684
- await localStoreSynchronizeLastDocumentChangeReadTime(this.localStore);
22685
23111
  await this.onlineComponentProvider.initialize(this, cfg);
22686
23112
  // Enqueue writes from a previous session
22687
23113
  await syncEngineEnsureWriteCallbacks(this.onlineComponentProvider.syncEngine);