@firebase/firestore 3.4.6 → 3.4.7-canary.05dc9d6a0

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 (127) hide show
  1. package/CHANGELOG.md +10 -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/local/index_manager.d.ts +3 -2
  8. package/dist/firestore/src/local/indexeddb_index_manager.d.ts +11 -3
  9. package/dist/firestore/src/local/indexeddb_mutation_batch_impl.d.ts +2 -1
  10. package/dist/firestore/src/local/indexeddb_remote_document_cache.d.ts +7 -15
  11. package/dist/firestore/src/local/indexeddb_schema.d.ts +23 -15
  12. package/dist/firestore/src/local/indexeddb_schema_converter.d.ts +1 -0
  13. package/dist/firestore/src/local/indexeddb_schema_legacy.d.ts +29 -0
  14. package/dist/firestore/src/local/indexeddb_sentinels.d.ts +31 -19
  15. package/dist/firestore/src/local/local_documents_view.d.ts +3 -4
  16. package/dist/firestore/src/local/local_serializer.d.ts +2 -1
  17. package/dist/firestore/src/local/local_store_impl.d.ts +1 -7
  18. package/dist/firestore/src/local/memory_index_manager.d.ts +3 -2
  19. package/dist/firestore/src/local/remote_document_cache.d.ts +13 -4
  20. package/dist/firestore/src/local/shared_client_state.d.ts +6 -3
  21. package/dist/firestore/src/local/shared_client_state_schema.d.ts +1 -1
  22. package/dist/firestore/src/local/shared_client_state_syncer.d.ts +1 -1
  23. package/dist/firestore/src/local/simple_db.d.ts +2 -0
  24. package/dist/firestore/src/model/field_index.d.ts +14 -1
  25. package/dist/firestore/test/unit/local/test_index_manager.d.ts +3 -2
  26. package/dist/firestore/test/unit/local/test_remote_document_cache.d.ts +3 -5
  27. package/dist/index.esm2017.js +3531 -3400
  28. package/dist/index.esm2017.js.map +1 -1
  29. package/dist/index.esm5.js +4157 -3978
  30. package/dist/index.esm5.js.map +1 -1
  31. package/dist/index.node.cjs.js +589 -362
  32. package/dist/index.node.cjs.js.map +1 -1
  33. package/dist/index.node.mjs +589 -362
  34. package/dist/index.node.mjs.map +1 -1
  35. package/dist/index.rn.js +3643 -3512
  36. package/dist/index.rn.js.map +1 -1
  37. package/dist/internal.d.ts +20 -6
  38. package/dist/lite/firestore/src/core/bundle.d.ts +2 -2
  39. package/dist/lite/firestore/src/core/bundle_impl.d.ts +2 -0
  40. package/dist/lite/firestore/src/core/query.d.ts +7 -0
  41. package/dist/lite/firestore/src/core/snapshot_version.d.ts +1 -0
  42. package/dist/lite/firestore/src/core/sync_engine_impl.d.ts +1 -1
  43. package/dist/lite/firestore/src/local/index_manager.d.ts +3 -2
  44. package/dist/lite/firestore/src/local/indexeddb_index_manager.d.ts +11 -3
  45. package/dist/lite/firestore/src/local/indexeddb_mutation_batch_impl.d.ts +2 -1
  46. package/dist/lite/firestore/src/local/indexeddb_remote_document_cache.d.ts +7 -15
  47. package/dist/lite/firestore/src/local/indexeddb_schema.d.ts +23 -15
  48. package/dist/lite/firestore/src/local/indexeddb_schema_converter.d.ts +1 -0
  49. package/dist/lite/firestore/src/local/indexeddb_schema_legacy.d.ts +29 -0
  50. package/dist/lite/firestore/src/local/indexeddb_sentinels.d.ts +31 -19
  51. package/dist/lite/firestore/src/local/local_documents_view.d.ts +3 -4
  52. package/dist/lite/firestore/src/local/local_serializer.d.ts +2 -1
  53. package/dist/lite/firestore/src/local/local_store_impl.d.ts +1 -7
  54. package/dist/lite/firestore/src/local/memory_index_manager.d.ts +3 -2
  55. package/dist/lite/firestore/src/local/remote_document_cache.d.ts +13 -4
  56. package/dist/lite/firestore/src/local/shared_client_state.d.ts +6 -3
  57. package/dist/lite/firestore/src/local/shared_client_state_schema.d.ts +1 -1
  58. package/dist/lite/firestore/src/local/shared_client_state_syncer.d.ts +1 -1
  59. package/dist/lite/firestore/src/local/simple_db.d.ts +2 -0
  60. package/dist/lite/firestore/src/model/field_index.d.ts +14 -1
  61. package/dist/lite/firestore/test/unit/local/test_index_manager.d.ts +3 -2
  62. package/dist/lite/firestore/test/unit/local/test_remote_document_cache.d.ts +3 -5
  63. package/dist/lite/index.browser.esm2017.js +5 -2
  64. package/dist/lite/index.browser.esm2017.js.map +1 -1
  65. package/dist/lite/index.browser.esm5.js +94 -92
  66. package/dist/lite/index.browser.esm5.js.map +1 -1
  67. package/dist/lite/index.node.cjs.js +5 -2
  68. package/dist/lite/index.node.cjs.js.map +1 -1
  69. package/dist/lite/index.node.mjs +5 -2
  70. package/dist/lite/index.node.mjs.map +1 -1
  71. package/dist/lite/index.rn.esm2017.js +5 -2
  72. package/dist/lite/index.rn.esm2017.js.map +1 -1
  73. package/dist/lite/internal.d.ts +1 -0
  74. package/dist/lite/packages/firestore/src/core/bundle.d.ts +2 -2
  75. package/dist/lite/packages/firestore/src/core/bundle_impl.d.ts +2 -0
  76. package/dist/lite/packages/firestore/src/core/query.d.ts +7 -0
  77. package/dist/lite/packages/firestore/src/core/snapshot_version.d.ts +1 -0
  78. package/dist/lite/packages/firestore/src/core/sync_engine_impl.d.ts +1 -1
  79. package/dist/lite/packages/firestore/src/local/index_manager.d.ts +3 -2
  80. package/dist/lite/packages/firestore/src/local/indexeddb_index_manager.d.ts +11 -3
  81. package/dist/lite/packages/firestore/src/local/indexeddb_mutation_batch_impl.d.ts +2 -1
  82. package/dist/lite/packages/firestore/src/local/indexeddb_remote_document_cache.d.ts +7 -15
  83. package/dist/lite/packages/firestore/src/local/indexeddb_schema.d.ts +23 -15
  84. package/dist/lite/packages/firestore/src/local/indexeddb_schema_converter.d.ts +1 -0
  85. package/dist/lite/packages/firestore/src/local/indexeddb_schema_legacy.d.ts +29 -0
  86. package/dist/lite/packages/firestore/src/local/indexeddb_sentinels.d.ts +31 -19
  87. package/dist/lite/packages/firestore/src/local/local_documents_view.d.ts +3 -4
  88. package/dist/lite/packages/firestore/src/local/local_serializer.d.ts +2 -1
  89. package/dist/lite/packages/firestore/src/local/local_store_impl.d.ts +1 -7
  90. package/dist/lite/packages/firestore/src/local/memory_index_manager.d.ts +3 -2
  91. package/dist/lite/packages/firestore/src/local/remote_document_cache.d.ts +13 -4
  92. package/dist/lite/packages/firestore/src/local/shared_client_state.d.ts +6 -3
  93. package/dist/lite/packages/firestore/src/local/shared_client_state_schema.d.ts +1 -1
  94. package/dist/lite/packages/firestore/src/local/shared_client_state_syncer.d.ts +1 -1
  95. package/dist/lite/packages/firestore/src/local/simple_db.d.ts +2 -0
  96. package/dist/lite/packages/firestore/src/model/field_index.d.ts +14 -1
  97. package/dist/lite/packages/firestore/test/unit/local/test_index_manager.d.ts +3 -2
  98. package/dist/lite/packages/firestore/test/unit/local/test_remote_document_cache.d.ts +3 -5
  99. package/dist/lite/private.d.ts +1 -0
  100. package/dist/packages/firestore/dist/index.esm2017.d.ts +180 -180
  101. package/dist/packages/firestore/src/core/bundle.d.ts +2 -2
  102. package/dist/packages/firestore/src/core/bundle_impl.d.ts +2 -0
  103. package/dist/packages/firestore/src/core/query.d.ts +7 -0
  104. package/dist/packages/firestore/src/core/snapshot_version.d.ts +1 -0
  105. package/dist/packages/firestore/src/core/sync_engine_impl.d.ts +1 -1
  106. package/dist/packages/firestore/src/local/index_manager.d.ts +3 -2
  107. package/dist/packages/firestore/src/local/indexeddb_index_manager.d.ts +11 -3
  108. package/dist/packages/firestore/src/local/indexeddb_mutation_batch_impl.d.ts +2 -1
  109. package/dist/packages/firestore/src/local/indexeddb_remote_document_cache.d.ts +7 -15
  110. package/dist/packages/firestore/src/local/indexeddb_schema.d.ts +23 -15
  111. package/dist/packages/firestore/src/local/indexeddb_schema_converter.d.ts +1 -0
  112. package/dist/packages/firestore/src/local/indexeddb_schema_legacy.d.ts +29 -0
  113. package/dist/packages/firestore/src/local/indexeddb_sentinels.d.ts +31 -19
  114. package/dist/packages/firestore/src/local/local_documents_view.d.ts +3 -4
  115. package/dist/packages/firestore/src/local/local_serializer.d.ts +2 -1
  116. package/dist/packages/firestore/src/local/local_store_impl.d.ts +1 -7
  117. package/dist/packages/firestore/src/local/memory_index_manager.d.ts +3 -2
  118. package/dist/packages/firestore/src/local/remote_document_cache.d.ts +13 -4
  119. package/dist/packages/firestore/src/local/shared_client_state.d.ts +6 -3
  120. package/dist/packages/firestore/src/local/shared_client_state_schema.d.ts +1 -1
  121. package/dist/packages/firestore/src/local/shared_client_state_syncer.d.ts +1 -1
  122. package/dist/packages/firestore/src/local/simple_db.d.ts +2 -0
  123. package/dist/packages/firestore/src/model/field_index.d.ts +14 -1
  124. package/dist/packages/firestore/test/unit/local/test_index_manager.d.ts +3 -2
  125. package/dist/packages/firestore/test/unit/local/test_remote_document_cache.d.ts +3 -5
  126. package/dist/private.d.ts +20 -6
  127. package/package.json +9 -9
@@ -8,7 +8,7 @@ import * as grpc from '@grpc/grpc-js';
8
8
  import * as protoLoader from '@grpc/proto-loader';
9
9
 
10
10
  const name = "@firebase/firestore";
11
- const version$1 = "3.4.6";
11
+ const version$1 = "3.4.7-canary.05dc9d6a0";
12
12
 
13
13
  /**
14
14
  * @license
@@ -61,7 +61,7 @@ User.GOOGLE_CREDENTIALS = new User('google-credentials-uid');
61
61
  User.FIRST_PARTY = new User('first-party-uid');
62
62
  User.MOCK_USER = new User('mock-user');
63
63
 
64
- const version = "9.6.9";
64
+ const version = "9.6.10-canary.05dc9d6a0";
65
65
 
66
66
  /**
67
67
  * @license
@@ -1200,6 +1200,24 @@ function decodeResourcePath(path) {
1200
1200
  return new ResourcePath(segments);
1201
1201
  }
1202
1202
 
1203
+ /**
1204
+ * @license
1205
+ * Copyright 2022 Google LLC
1206
+ *
1207
+ * Licensed under the Apache License, Version 2.0 (the "License");
1208
+ * you may not use this file except in compliance with the License.
1209
+ * You may obtain a copy of the License at
1210
+ *
1211
+ * http://www.apache.org/licenses/LICENSE-2.0
1212
+ *
1213
+ * Unless required by applicable law or agreed to in writing, software
1214
+ * distributed under the License is distributed on an "AS IS" BASIS,
1215
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1216
+ * See the License for the specific language governing permissions and
1217
+ * limitations under the License.
1218
+ */
1219
+ const DbRemoteDocumentStore$1 = 'remoteDocuments';
1220
+
1203
1221
  /**
1204
1222
  * @license
1205
1223
  * Copyright 2022 Google LLC
@@ -1270,26 +1288,36 @@ function newDbDocumentMutationKey(userId, path, batchId) {
1270
1288
  */
1271
1289
  const DbDocumentMutationPlaceholder = {};
1272
1290
  const DbDocumentMutationStore = 'documentMutations';
1273
- const DbRemoteDocumentStore = 'remoteDocuments';
1291
+ const DbRemoteDocumentStore = 'remoteDocumentsV14';
1274
1292
  /**
1275
- * An index that provides access to all entries sorted by read time (which
1276
- * corresponds to the last modification time of each row).
1277
- *
1278
- * This index is used to provide a changelog for Multi-Tab.
1293
+ * The primary key of the remote documents store, which allows for efficient
1294
+ * access by collection path and read time.
1279
1295
  */
1280
- const DbRemoteDocumentReadTimeIndex = 'readTimeIndex';
1281
- const DbRemoteDocumentReadTimeIndexPath = 'readTime';
1296
+ const DbRemoteDocumentKeyPath = [
1297
+ 'prefixPath',
1298
+ 'collectionGroup',
1299
+ 'readTime',
1300
+ 'documentId'
1301
+ ];
1302
+ /** An index that provides access to documents by key. */
1303
+ const DbRemoteDocumentDocumentKeyIndex = 'documentKeyIndex';
1304
+ const DbRemoteDocumentDocumentKeyIndexPath = [
1305
+ 'prefixPath',
1306
+ 'collectionGroup',
1307
+ 'documentId'
1308
+ ];
1282
1309
  /**
1283
- * An index that provides access to documents in a collection sorted by read
1310
+ * An index that provides access to documents by collection group and read
1284
1311
  * time.
1285
1312
  *
1286
- * This index is used to allow the RemoteDocumentCache to fetch newly changed
1287
- * documents in a collection.
1313
+ * This index is used by the index backfiller.
1288
1314
  */
1289
- const DbRemoteDocumentCollectionReadTimeIndex = 'collectionReadTimeIndex';
1290
- const DbRemoteDocumentCollectionReadTimeIndexPath = [
1291
- 'parentPath',
1292
- 'readTime'
1315
+ const DbRemoteDocumentCollectionGroupIndex = 'collectionGroupIndex';
1316
+ const DbRemoteDocumentCollectionGroupIndexPath = [
1317
+ 'collectionGroup',
1318
+ 'readTime',
1319
+ 'prefixPath',
1320
+ 'documentId'
1293
1321
  ];
1294
1322
  const DbRemoteDocumentGlobalStore = 'remoteDocumentGlobal';
1295
1323
  const DbRemoteDocumentGlobalKey = 'remoteDocumentGlobalKey';
@@ -1365,13 +1393,14 @@ const DbIndexEntryKeyPath = [
1365
1393
  'uid',
1366
1394
  'arrayValue',
1367
1395
  'directionalValue',
1396
+ 'orderedDocumentKey',
1368
1397
  'documentKey'
1369
1398
  ];
1370
1399
  const DbIndexEntryDocumentKeyIndex = 'documentKeyIndex';
1371
1400
  const DbIndexEntryDocumentKeyIndexPath = [
1372
1401
  'indexId',
1373
1402
  'uid',
1374
- 'documentKey'
1403
+ 'orderedDocumentKey'
1375
1404
  ];
1376
1405
  /** Name of the IndexedDb object store. */
1377
1406
  const DbDocumentOverlayStore = 'documentOverlays';
@@ -1397,7 +1426,7 @@ const V1_STORES = [
1397
1426
  DbMutationQueueStore,
1398
1427
  DbMutationBatchStore,
1399
1428
  DbDocumentMutationStore,
1400
- DbRemoteDocumentStore,
1429
+ DbRemoteDocumentStore$1,
1401
1430
  DbTargetStore,
1402
1431
  DbPrimaryClientStore,
1403
1432
  DbTargetGlobalStore,
@@ -1412,14 +1441,33 @@ const V8_STORES = [...V6_STORES, DbCollectionParentStore];
1412
1441
  const V11_STORES = [...V8_STORES, DbBundleStore, DbNamedQueryStore];
1413
1442
  const V12_STORES = [...V11_STORES, DbDocumentOverlayStore];
1414
1443
  const V13_STORES = [
1415
- ...V12_STORES,
1444
+ DbMutationQueueStore,
1445
+ DbMutationBatchStore,
1446
+ DbDocumentMutationStore,
1447
+ DbRemoteDocumentStore,
1448
+ DbTargetStore,
1449
+ DbPrimaryClientStore,
1450
+ DbTargetGlobalStore,
1451
+ DbTargetDocumentStore,
1452
+ DbClientMetadataStore,
1453
+ DbRemoteDocumentGlobalStore,
1454
+ DbCollectionParentStore,
1455
+ DbBundleStore,
1456
+ DbNamedQueryStore,
1457
+ DbDocumentOverlayStore
1458
+ ];
1459
+ const V14_STORES = [
1460
+ ...V13_STORES,
1416
1461
  DbIndexConfigurationStore,
1417
1462
  DbIndexStateStore,
1418
1463
  DbIndexEntryStore
1419
1464
  ];
1420
1465
  /** Returns the object stores for the provided schema. */
1421
1466
  function getObjectStores(schemaVersion) {
1422
- if (schemaVersion === 13) {
1467
+ if (schemaVersion === 14) {
1468
+ return V14_STORES;
1469
+ }
1470
+ else if (schemaVersion === 13) {
1423
1471
  return V13_STORES;
1424
1472
  }
1425
1473
  else if (schemaVersion === 12) {
@@ -2238,9 +2286,7 @@ class SimpleDbStore {
2238
2286
  cursor.continue(controller.skipToKey);
2239
2287
  }
2240
2288
  };
2241
- }).next(() => {
2242
- return PersistencePromise.waitFor(results);
2243
- });
2289
+ }).next(() => PersistencePromise.waitFor(results));
2244
2290
  }
2245
2291
  options(indexOrRange, range) {
2246
2292
  let indexName = undefined;
@@ -2630,6 +2676,9 @@ class SnapshotVersion {
2630
2676
  static min() {
2631
2677
  return new SnapshotVersion(new Timestamp(0, 0));
2632
2678
  }
2679
+ static max() {
2680
+ return new SnapshotVersion(new Timestamp(253402300799, 1e9 - 1));
2681
+ }
2633
2682
  compareTo(other) {
2634
2683
  return this.timestamp._compareTo(other.timestamp);
2635
2684
  }
@@ -4146,6 +4195,17 @@ function fieldIndexGetArraySegment(fieldIndex) {
4146
4195
  function fieldIndexGetDirectionalSegments(fieldIndex) {
4147
4196
  return fieldIndex.fields.filter(s => s.kind !== 2 /* CONTAINS */);
4148
4197
  }
4198
+ /**
4199
+ * Returns the order of the document key component for the given index.
4200
+ *
4201
+ * PORTING NOTE: This is only used in the Web IndexedDb implementation.
4202
+ */
4203
+ function fieldIndexGetKeyOrder(fieldIndex) {
4204
+ const directionalSegments = fieldIndexGetDirectionalSegments(fieldIndex);
4205
+ return directionalSegments.length === 0
4206
+ ? 0 /* ASCENDING */
4207
+ : directionalSegments[directionalSegments.length - 1].kind;
4208
+ }
4149
4209
  /** Returns a debug representation of the field index */
4150
4210
  function fieldIndexToString(fieldIndex) {
4151
4211
  return `id=${fieldIndex.indexId}|cg=${fieldIndex.collectionGroup}|f=${fieldIndex.fields.map(f => `${f.fieldPath}:${f.kind}`).join(',')}`;
@@ -4181,6 +4241,28 @@ class IndexState {
4181
4241
  return new IndexState(INITIAL_SEQUENCE_NUMBER, IndexOffset.min());
4182
4242
  }
4183
4243
  }
4244
+ /**
4245
+ * Creates an offset that matches all documents with a read time higher than
4246
+ * `readTime`.
4247
+ */
4248
+ function newIndexOffsetSuccessorFromReadTime(readTime, largestBatchId) {
4249
+ // We want to create an offset that matches all documents with a read time
4250
+ // greater than the provided read time. To do so, we technically need to
4251
+ // create an offset for `(readTime, MAX_DOCUMENT_KEY)`. While we could use
4252
+ // Unicode codepoints to generate MAX_DOCUMENT_KEY, it is much easier to use
4253
+ // `(readTime + 1, DocumentKey.empty())` since `> DocumentKey.empty()` matches
4254
+ // all valid document IDs.
4255
+ const successorSeconds = readTime.toTimestamp().seconds;
4256
+ const successorNanos = readTime.toTimestamp().nanoseconds + 1;
4257
+ const successor = SnapshotVersion.fromTimestamp(successorNanos === 1e9
4258
+ ? new Timestamp(successorSeconds + 1, 0)
4259
+ : new Timestamp(successorSeconds, successorNanos));
4260
+ return new IndexOffset(successor, DocumentKey.empty(), largestBatchId);
4261
+ }
4262
+ /** Creates a new offset based on the provided document. */
4263
+ function newIndexOffsetFromDocument(document) {
4264
+ return new IndexOffset(document.readTime, document.key, INITIAL_LARGEST_BATCH_ID);
4265
+ }
4184
4266
  /**
4185
4267
  * Stores the latest read time, document and batch ID that were processed for an
4186
4268
  * index.
@@ -4205,10 +4287,25 @@ class IndexOffset {
4205
4287
  this.documentKey = documentKey;
4206
4288
  this.largestBatchId = largestBatchId;
4207
4289
  }
4208
- /** The state of an index that has not yet been backfilled. */
4290
+ /** Returns an offset that sorts before all regular offsets. */
4209
4291
  static min() {
4210
4292
  return new IndexOffset(SnapshotVersion.min(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
4211
4293
  }
4294
+ /** Returns an offset that sorts after all regular offsets. */
4295
+ static max() {
4296
+ return new IndexOffset(SnapshotVersion.max(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
4297
+ }
4298
+ }
4299
+ function indexOffsetComparator(left, right) {
4300
+ let cmp = left.readTime.compareTo(right.readTime);
4301
+ if (cmp !== 0) {
4302
+ return cmp;
4303
+ }
4304
+ cmp = DocumentKey.comparator(left.documentKey, right.documentKey);
4305
+ if (cmp !== 0) {
4306
+ return cmp;
4307
+ }
4308
+ return primitiveComparator(left.largestBatchId, right.largestBatchId);
4212
4309
  }
4213
4310
 
4214
4311
  /**
@@ -4409,58 +4506,15 @@ function targetGetLowerBound(target, fieldIndex) {
4409
4506
  // For each segment, retrieve a lower bound if there is a suitable filter or
4410
4507
  // startAt.
4411
4508
  for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
4412
- let segmentValue = undefined;
4413
- let segmentInclusive = true;
4414
- // Process all filters to find a value for the current field segment
4415
- for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
4416
- let filterValue = undefined;
4417
- let filterInclusive = true;
4418
- switch (fieldFilter.op) {
4419
- case "<" /* LESS_THAN */:
4420
- case "<=" /* LESS_THAN_OR_EQUAL */:
4421
- filterValue = valuesGetLowerBound(fieldFilter.value);
4422
- break;
4423
- case "==" /* EQUAL */:
4424
- case "in" /* IN */:
4425
- case ">=" /* GREATER_THAN_OR_EQUAL */:
4426
- filterValue = fieldFilter.value;
4427
- break;
4428
- case ">" /* GREATER_THAN */:
4429
- filterValue = fieldFilter.value;
4430
- filterInclusive = false;
4431
- break;
4432
- case "!=" /* NOT_EQUAL */:
4433
- case "not-in" /* NOT_IN */:
4434
- filterValue = MIN_VALUE;
4435
- break;
4436
- // Remaining filters cannot be used as lower bounds.
4437
- }
4438
- if (valuesMax(segmentValue, filterValue) === filterValue) {
4439
- segmentValue = filterValue;
4440
- segmentInclusive = filterInclusive;
4441
- }
4442
- }
4443
- // If there is a startAt bound, compare the values against the existing
4444
- // boundary to see if we can narrow the scope.
4445
- if (target.startAt !== null) {
4446
- for (let i = 0; i < target.orderBy.length; ++i) {
4447
- const orderBy = target.orderBy[i];
4448
- if (orderBy.field.isEqual(segment.fieldPath)) {
4449
- const cursorValue = target.startAt.position[i];
4450
- if (valuesMax(segmentValue, cursorValue) === cursorValue) {
4451
- segmentValue = cursorValue;
4452
- segmentInclusive = target.startAt.inclusive;
4453
- }
4454
- break;
4455
- }
4456
- }
4457
- }
4458
- if (segmentValue === undefined) {
4509
+ const segmentBound = segment.kind === 0 /* ASCENDING */
4510
+ ? targetGetLowerBoundForField(target, segment.fieldPath, target.startAt)
4511
+ : targetGetUpperBoundForField(target, segment.fieldPath, target.startAt);
4512
+ if (!segmentBound.value) {
4459
4513
  // No lower bound exists
4460
4514
  return null;
4461
4515
  }
4462
- values.push(segmentValue);
4463
- inclusive && (inclusive = segmentInclusive);
4516
+ values.push(segmentBound.value);
4517
+ inclusive && (inclusive = segmentBound.inclusive);
4464
4518
  }
4465
4519
  return new Bound(values, inclusive);
4466
4520
  }
@@ -4475,62 +4529,125 @@ function targetGetUpperBound(target, fieldIndex) {
4475
4529
  // For each segment, retrieve an upper bound if there is a suitable filter or
4476
4530
  // endAt.
4477
4531
  for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
4478
- let segmentValue = undefined;
4479
- let segmentInclusive = true;
4480
- // Process all filters to find a value for the current field segment
4481
- for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
4482
- let filterValue = undefined;
4483
- let filterInclusive = true;
4484
- switch (fieldFilter.op) {
4485
- case ">=" /* GREATER_THAN_OR_EQUAL */:
4486
- case ">" /* GREATER_THAN */:
4487
- filterValue = valuesGetUpperBound(fieldFilter.value);
4488
- filterInclusive = false;
4489
- break;
4490
- case "==" /* EQUAL */:
4491
- case "in" /* IN */:
4492
- case "<=" /* LESS_THAN_OR_EQUAL */:
4493
- filterValue = fieldFilter.value;
4494
- break;
4495
- case "<" /* LESS_THAN */:
4496
- filterValue = fieldFilter.value;
4497
- filterInclusive = false;
4498
- break;
4499
- case "!=" /* NOT_EQUAL */:
4500
- case "not-in" /* NOT_IN */:
4501
- filterValue = MAX_VALUE;
4502
- break;
4503
- // Remaining filters cannot be used as upper bounds.
4504
- }
4505
- if (valuesMin(segmentValue, filterValue) === filterValue) {
4506
- segmentValue = filterValue;
4507
- segmentInclusive = filterInclusive;
4508
- }
4509
- }
4510
- // If there is a endAt bound, compare the values against the existing
4511
- // boundary to see if we can narrow the scope.
4512
- if (target.endAt !== null) {
4513
- for (let i = 0; i < target.orderBy.length; ++i) {
4514
- const orderBy = target.orderBy[i];
4515
- if (orderBy.field.isEqual(segment.fieldPath)) {
4516
- const cursorValue = target.endAt.position[i];
4517
- if (valuesMin(segmentValue, cursorValue) === cursorValue) {
4518
- segmentValue = cursorValue;
4519
- segmentInclusive = target.endAt.inclusive;
4520
- }
4521
- break;
4522
- }
4523
- }
4524
- }
4525
- if (segmentValue === undefined) {
4532
+ const segmentBound = segment.kind === 0 /* ASCENDING */
4533
+ ? targetGetUpperBoundForField(target, segment.fieldPath, target.endAt)
4534
+ : targetGetLowerBoundForField(target, segment.fieldPath, target.endAt);
4535
+ if (!segmentBound.value) {
4526
4536
  // No upper bound exists
4527
4537
  return null;
4528
4538
  }
4529
- values.push(segmentValue);
4530
- inclusive && (inclusive = segmentInclusive);
4539
+ values.push(segmentBound.value);
4540
+ inclusive && (inclusive = segmentBound.inclusive);
4531
4541
  }
4532
4542
  return new Bound(values, inclusive);
4533
4543
  }
4544
+ /**
4545
+ * Returns the value to use as the lower bound for ascending index segment at
4546
+ * the provided `fieldPath` (or the upper bound for an descending segment).
4547
+ */
4548
+ function targetGetLowerBoundForField(target, fieldPath, bound) {
4549
+ let value = undefined;
4550
+ let inclusive = true;
4551
+ // Process all filters to find a value for the current field segment
4552
+ for (const fieldFilter of targetGetFieldFiltersForPath(target, fieldPath)) {
4553
+ let filterValue = undefined;
4554
+ let filterInclusive = true;
4555
+ switch (fieldFilter.op) {
4556
+ case "<" /* LESS_THAN */:
4557
+ case "<=" /* LESS_THAN_OR_EQUAL */:
4558
+ filterValue = valuesGetLowerBound(fieldFilter.value);
4559
+ break;
4560
+ case "==" /* EQUAL */:
4561
+ case "in" /* IN */:
4562
+ case ">=" /* GREATER_THAN_OR_EQUAL */:
4563
+ filterValue = fieldFilter.value;
4564
+ break;
4565
+ case ">" /* GREATER_THAN */:
4566
+ filterValue = fieldFilter.value;
4567
+ filterInclusive = false;
4568
+ break;
4569
+ case "!=" /* NOT_EQUAL */:
4570
+ case "not-in" /* NOT_IN */:
4571
+ filterValue = MIN_VALUE;
4572
+ break;
4573
+ // Remaining filters cannot be used as lower bounds.
4574
+ }
4575
+ if (valuesMax(value, filterValue) === filterValue) {
4576
+ value = filterValue;
4577
+ inclusive = filterInclusive;
4578
+ }
4579
+ }
4580
+ // If there is an additional bound, compare the values against the existing
4581
+ // range to see if we can narrow the scope.
4582
+ if (bound !== null) {
4583
+ for (let i = 0; i < target.orderBy.length; ++i) {
4584
+ const orderBy = target.orderBy[i];
4585
+ if (orderBy.field.isEqual(fieldPath)) {
4586
+ const cursorValue = bound.position[i];
4587
+ if (valuesMax(value, cursorValue) === cursorValue) {
4588
+ value = cursorValue;
4589
+ inclusive = bound.inclusive;
4590
+ }
4591
+ break;
4592
+ }
4593
+ }
4594
+ }
4595
+ return { value, inclusive };
4596
+ }
4597
+ /**
4598
+ * Returns the value to use as the upper bound for ascending index segment at
4599
+ * the provided `fieldPath` (or the lower bound for an descending segment).
4600
+ */
4601
+ function targetGetUpperBoundForField(target, fieldPath, bound) {
4602
+ let value = undefined;
4603
+ let inclusive = true;
4604
+ // Process all filters to find a value for the current field segment
4605
+ for (const fieldFilter of targetGetFieldFiltersForPath(target, fieldPath)) {
4606
+ let filterValue = undefined;
4607
+ let filterInclusive = true;
4608
+ switch (fieldFilter.op) {
4609
+ case ">=" /* GREATER_THAN_OR_EQUAL */:
4610
+ case ">" /* GREATER_THAN */:
4611
+ filterValue = valuesGetUpperBound(fieldFilter.value);
4612
+ filterInclusive = false;
4613
+ break;
4614
+ case "==" /* EQUAL */:
4615
+ case "in" /* IN */:
4616
+ case "<=" /* LESS_THAN_OR_EQUAL */:
4617
+ filterValue = fieldFilter.value;
4618
+ break;
4619
+ case "<" /* LESS_THAN */:
4620
+ filterValue = fieldFilter.value;
4621
+ filterInclusive = false;
4622
+ break;
4623
+ case "!=" /* NOT_EQUAL */:
4624
+ case "not-in" /* NOT_IN */:
4625
+ filterValue = MAX_VALUE;
4626
+ break;
4627
+ // Remaining filters cannot be used as upper bounds.
4628
+ }
4629
+ if (valuesMin(value, filterValue) === filterValue) {
4630
+ value = filterValue;
4631
+ inclusive = filterInclusive;
4632
+ }
4633
+ }
4634
+ // If there is an additional bound, compare the values against the existing
4635
+ // range to see if we can narrow the scope.
4636
+ if (bound !== null) {
4637
+ for (let i = 0; i < target.orderBy.length; ++i) {
4638
+ const orderBy = target.orderBy[i];
4639
+ if (orderBy.field.isEqual(fieldPath)) {
4640
+ const cursorValue = bound.position[i];
4641
+ if (valuesMin(value, cursorValue) === cursorValue) {
4642
+ value = cursorValue;
4643
+ inclusive = bound.inclusive;
4644
+ }
4645
+ break;
4646
+ }
4647
+ }
4648
+ }
4649
+ return { value, inclusive };
4650
+ }
4534
4651
  class Filter {
4535
4652
  }
4536
4653
  class FieldFilter extends Filter {
@@ -5101,6 +5218,18 @@ function queryMatchesBounds(query, doc) {
5101
5218
  }
5102
5219
  return true;
5103
5220
  }
5221
+ /**
5222
+ * Returns the collection group that this query targets.
5223
+ *
5224
+ * PORTING NOTE: This is only used in the Web SDK to facilitate multi-tab
5225
+ * synchronization for query results.
5226
+ */
5227
+ function queryCollectionGroup(query) {
5228
+ return (query.collectionGroup ||
5229
+ (query.path.length % 2 === 1
5230
+ ? query.path.lastSegment()
5231
+ : query.path.get(query.path.length - 2)));
5232
+ }
5104
5233
  /**
5105
5234
  * Returns a new comparator function that can be used to compare two documents
5106
5235
  * based on the Query's ordering constraint.
@@ -8634,40 +8763,33 @@ function fromDbRemoteDocument(localSerializer, remoteDoc) {
8634
8763
  }
8635
8764
  /** Encodes a document for storage locally. */
8636
8765
  function toDbRemoteDocument(localSerializer, document) {
8637
- const parentPath = document.key.path.popLast().toArray();
8638
- const readTime = toDbTimestampKey(document.readTime);
8766
+ const key = document.key;
8767
+ const remoteDoc = {
8768
+ prefixPath: key.getCollectionPath().popLast().toArray(),
8769
+ collectionGroup: key.collectionGroup,
8770
+ documentId: key.path.lastSegment(),
8771
+ readTime: toDbTimestampKey(document.readTime),
8772
+ hasCommittedMutations: document.hasCommittedMutations
8773
+ };
8639
8774
  if (document.isFoundDocument()) {
8640
- const doc = toDocument(localSerializer.remoteSerializer, document);
8641
- const hasCommittedMutations = document.hasCommittedMutations;
8642
- return {
8643
- document: doc,
8644
- hasCommittedMutations,
8645
- readTime,
8646
- parentPath
8647
- };
8775
+ remoteDoc.document = toDocument(localSerializer.remoteSerializer, document);
8648
8776
  }
8649
8777
  else if (document.isNoDocument()) {
8650
- const path = document.key.path.toArray();
8651
- const hasCommittedMutations = document.hasCommittedMutations;
8652
- return {
8653
- noDocument: { path, readTime: toDbTimestamp(document.version) },
8654
- hasCommittedMutations,
8655
- readTime,
8656
- parentPath
8778
+ remoteDoc.noDocument = {
8779
+ path: key.path.toArray(),
8780
+ readTime: toDbTimestamp(document.version)
8657
8781
  };
8658
8782
  }
8659
8783
  else if (document.isUnknownDocument()) {
8660
- const path = document.key.path.toArray();
8661
- return {
8662
- unknownDocument: { path, version: toDbTimestamp(document.version) },
8663
- hasCommittedMutations: true,
8664
- readTime,
8665
- parentPath
8784
+ remoteDoc.unknownDocument = {
8785
+ path: key.path.toArray(),
8786
+ version: toDbTimestamp(document.version)
8666
8787
  };
8667
8788
  }
8668
8789
  else {
8669
8790
  return fail();
8670
8791
  }
8792
+ return remoteDoc;
8671
8793
  }
8672
8794
  function toDbTimestampKey(snapshotVersion) {
8673
8795
  const timestamp = snapshotVersion.toTimestamp();
@@ -9929,8 +10051,9 @@ const EMPTY_VALUE = new Uint8Array(0);
9929
10051
  * data as it supports multi-tab access.
9930
10052
  */
9931
10053
  class IndexedDbIndexManager {
9932
- constructor(user) {
10054
+ constructor(user, databaseId) {
9933
10055
  this.user = user;
10056
+ this.databaseId = databaseId;
9934
10057
  /**
9935
10058
  * An in-memory copy of the index entries we've already written since the SDK
9936
10059
  * launched. Used to avoid re-writing the same entry repeatedly.
@@ -10026,7 +10149,8 @@ class IndexedDbIndexManager {
10026
10149
  return PersistencePromise.resolve(null);
10027
10150
  }
10028
10151
  else {
10029
- let result = documentKeySet();
10152
+ let existingKeys = documentKeySet();
10153
+ const result = [];
10030
10154
  return PersistencePromise.forEach(indexes, (index, subTarget) => {
10031
10155
  logDebug(LOG_TAG$f, `Using index ${fieldIndexToString(index)} to execute ${canonifyTarget(target)}`);
10032
10156
  const arrayValues = targetGetArrayValues(subTarget, index);
@@ -10042,7 +10166,11 @@ class IndexedDbIndexManager {
10042
10166
  .loadFirst(indexRange, target.limit)
10043
10167
  .next(entries => {
10044
10168
  entries.forEach(entry => {
10045
- result = result.add(new DocumentKey(decodeResourcePath(entry.documentKey)));
10169
+ const documentKey = DocumentKey.fromSegments(entry.documentKey);
10170
+ if (!existingKeys.has(documentKey)) {
10171
+ existingKeys = existingKeys.add(documentKey);
10172
+ result.push(documentKey);
10173
+ }
10046
10174
  });
10047
10175
  });
10048
10176
  });
@@ -10141,6 +10269,15 @@ class IndexedDbIndexManager {
10141
10269
  FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, encoder.forKind(0 /* ASCENDING */));
10142
10270
  return encoder.encodedBytes();
10143
10271
  }
10272
+ /**
10273
+ * Returns an encoded form of the document key that sorts based on the key
10274
+ * ordering of the field index.
10275
+ */
10276
+ encodeDirectionalKey(fieldIndex, documentKey) {
10277
+ const encoder = new IndexByteEncoder();
10278
+ FirestoreIndexValueWriter.INSTANCE.writeIndexValue(refValue(this.databaseId, documentKey), encoder.forKind(fieldIndexGetKeyOrder(fieldIndex)));
10279
+ return encoder.encodedBytes();
10280
+ }
10144
10281
  /**
10145
10282
  * Encodes the given field values according to the specification in `target`.
10146
10283
  * For IN queries, a list of possible values is returned.
@@ -10264,7 +10401,7 @@ class IndexedDbIndexManager {
10264
10401
  return this.getExistingIndexEntries(transaction, key, fieldIndex).next(existingEntries => {
10265
10402
  const newEntries = this.computeIndexEntries(doc, fieldIndex);
10266
10403
  if (!existingEntries.isEqual(newEntries)) {
10267
- return this.updateEntries(transaction, doc, existingEntries, newEntries);
10404
+ return this.updateEntries(transaction, doc, fieldIndex, existingEntries, newEntries);
10268
10405
  }
10269
10406
  return PersistencePromise.resolve();
10270
10407
  });
@@ -10272,24 +10409,26 @@ class IndexedDbIndexManager {
10272
10409
  });
10273
10410
  });
10274
10411
  }
10275
- addIndexEntry(transaction, document, indexEntry) {
10412
+ addIndexEntry(transaction, document, fieldIndex, indexEntry) {
10276
10413
  const indexEntries = indexEntriesStore(transaction);
10277
10414
  return indexEntries.put({
10278
10415
  indexId: indexEntry.indexId,
10279
10416
  uid: this.uid,
10280
10417
  arrayValue: indexEntry.arrayValue,
10281
10418
  directionalValue: indexEntry.directionalValue,
10282
- documentKey: encodeResourcePath(document.key.path)
10419
+ orderedDocumentKey: this.encodeDirectionalKey(fieldIndex, document.key),
10420
+ documentKey: document.key.path.toArray()
10283
10421
  });
10284
10422
  }
10285
- deleteIndexEntry(transaction, document, indexEntry) {
10423
+ deleteIndexEntry(transaction, document, fieldIndex, indexEntry) {
10286
10424
  const indexEntries = indexEntriesStore(transaction);
10287
10425
  return indexEntries.delete([
10288
10426
  indexEntry.indexId,
10289
10427
  this.uid,
10290
10428
  indexEntry.arrayValue,
10291
10429
  indexEntry.directionalValue,
10292
- encodeResourcePath(document.key.path)
10430
+ this.encodeDirectionalKey(fieldIndex, document.key),
10431
+ document.key.path.toArray()
10293
10432
  ]);
10294
10433
  }
10295
10434
  getExistingIndexEntries(transaction, documentKey, fieldIndex) {
@@ -10301,7 +10440,7 @@ class IndexedDbIndexManager {
10301
10440
  range: IDBKeyRange.only([
10302
10441
  fieldIndex.indexId,
10303
10442
  this.uid,
10304
- encodeResourcePath(documentKey.path)
10443
+ this.encodeDirectionalKey(fieldIndex, documentKey)
10305
10444
  ])
10306
10445
  }, (_, entry) => {
10307
10446
  results = results.add(new IndexEntry(fieldIndex.indexId, documentKey, entry.arrayValue, entry.directionalValue));
@@ -10334,15 +10473,15 @@ class IndexedDbIndexManager {
10334
10473
  * that are no longer referenced in `newEntries` and adding all newly added
10335
10474
  * entries.
10336
10475
  */
10337
- updateEntries(transaction, document, existingEntries, newEntries) {
10476
+ updateEntries(transaction, document, fieldIndex, existingEntries, newEntries) {
10338
10477
  logDebug(LOG_TAG$f, "Updating index entries for document '%s'", document.key);
10339
10478
  const promises = [];
10340
10479
  diffSortedSets(existingEntries, newEntries, indexEntryComparator,
10341
10480
  /* onAdd= */ entry => {
10342
- promises.push(this.addIndexEntry(transaction, document, entry));
10481
+ promises.push(this.addIndexEntry(transaction, document, fieldIndex, entry));
10343
10482
  },
10344
10483
  /* onRemove= */ entry => {
10345
- promises.push(this.deleteIndexEntry(transaction, document, entry));
10484
+ promises.push(this.deleteIndexEntry(transaction, document, fieldIndex, entry));
10346
10485
  });
10347
10486
  return PersistencePromise.waitFor(promises);
10348
10487
  }
@@ -10399,13 +10538,15 @@ class IndexedDbIndexManager {
10399
10538
  this.uid,
10400
10539
  bounds[i].arrayValue,
10401
10540
  bounds[i].directionalValue,
10402
- ''
10541
+ EMPTY_VALUE,
10542
+ []
10403
10543
  ], [
10404
10544
  bounds[i + 1].indexId,
10405
10545
  this.uid,
10406
10546
  bounds[i + 1].arrayValue,
10407
10547
  bounds[i + 1].directionalValue,
10408
- ''
10548
+ EMPTY_VALUE,
10549
+ []
10409
10550
  ]));
10410
10551
  }
10411
10552
  return ranges;
@@ -11819,7 +11960,7 @@ class IndexedDbRemoteDocumentCacheImpl {
11819
11960
  */
11820
11961
  addEntry(transaction, key, doc) {
11821
11962
  const documentStore = remoteDocumentsStore(transaction);
11822
- return documentStore.put(dbKey(key), doc);
11963
+ return documentStore.put(doc);
11823
11964
  }
11824
11965
  /**
11825
11966
  * Removes a document from the cache.
@@ -11827,10 +11968,9 @@ class IndexedDbRemoteDocumentCacheImpl {
11827
11968
  * All calls of `removeEntry` are required to go through the RemoteDocumentChangeBuffer
11828
11969
  * returned by `newChangeBuffer()` to ensure proper accounting of metadata.
11829
11970
  */
11830
- removeEntry(transaction, documentKey) {
11971
+ removeEntry(transaction, documentKey, readTime) {
11831
11972
  const store = remoteDocumentsStore(transaction);
11832
- const key = dbKey(documentKey);
11833
- return store.delete(key);
11973
+ return store.delete(dbReadTimeKey(documentKey, readTime));
11834
11974
  }
11835
11975
  /**
11836
11976
  * Updates the current cache size.
@@ -11845,11 +11985,15 @@ class IndexedDbRemoteDocumentCacheImpl {
11845
11985
  });
11846
11986
  }
11847
11987
  getEntry(transaction, documentKey) {
11988
+ let doc = MutableDocument.newInvalidDocument(documentKey);
11848
11989
  return remoteDocumentsStore(transaction)
11849
- .get(dbKey(documentKey))
11850
- .next(dbRemoteDoc => {
11851
- return this.maybeDecodeDocument(documentKey, dbRemoteDoc);
11852
- });
11990
+ .iterate({
11991
+ index: DbRemoteDocumentDocumentKeyIndex,
11992
+ range: IDBKeyRange.only(dbKey(documentKey))
11993
+ }, (_, dbRemoteDoc) => {
11994
+ doc = this.maybeDecodeDocument(documentKey, dbRemoteDoc);
11995
+ })
11996
+ .next(() => doc);
11853
11997
  }
11854
11998
  /**
11855
11999
  * Looks up an entry in the cache.
@@ -11858,15 +12002,21 @@ class IndexedDbRemoteDocumentCacheImpl {
11858
12002
  * @returns The cached document entry and its size.
11859
12003
  */
11860
12004
  getSizedEntry(transaction, documentKey) {
12005
+ let result = {
12006
+ size: 0,
12007
+ document: MutableDocument.newInvalidDocument(documentKey)
12008
+ };
11861
12009
  return remoteDocumentsStore(transaction)
11862
- .get(dbKey(documentKey))
11863
- .next(dbRemoteDoc => {
11864
- const doc = this.maybeDecodeDocument(documentKey, dbRemoteDoc);
11865
- return {
11866
- document: doc,
12010
+ .iterate({
12011
+ index: DbRemoteDocumentDocumentKeyIndex,
12012
+ range: IDBKeyRange.only(dbKey(documentKey))
12013
+ }, (_, dbRemoteDoc) => {
12014
+ result = {
12015
+ document: this.maybeDecodeDocument(documentKey, dbRemoteDoc),
11867
12016
  size: dbDocumentSize(dbRemoteDoc)
11868
12017
  };
11869
- });
12018
+ })
12019
+ .next(() => result);
11870
12020
  }
11871
12021
  getEntries(transaction, documentKeys) {
11872
12022
  let results = mutableDocumentMap();
@@ -11897,14 +12047,20 @@ class IndexedDbRemoteDocumentCacheImpl {
11897
12047
  if (documentKeys.isEmpty()) {
11898
12048
  return PersistencePromise.resolve();
11899
12049
  }
11900
- const range = IDBKeyRange.bound(documentKeys.first().path.toArray(), documentKeys.last().path.toArray());
11901
- const keyIter = documentKeys.getIterator();
12050
+ let sortedKeys = new SortedSet(dbKeyComparator);
12051
+ documentKeys.forEach(e => (sortedKeys = sortedKeys.add(e)));
12052
+ const range = IDBKeyRange.bound(dbKey(sortedKeys.first()), dbKey(sortedKeys.last()));
12053
+ const keyIter = sortedKeys.getIterator();
11902
12054
  let nextKey = keyIter.getNext();
11903
12055
  return remoteDocumentsStore(transaction)
11904
- .iterate({ range }, (potentialKeyRaw, dbRemoteDoc, control) => {
11905
- const potentialKey = DocumentKey.fromSegments(potentialKeyRaw);
12056
+ .iterate({ index: DbRemoteDocumentDocumentKeyIndex, range }, (_, dbRemoteDoc, control) => {
12057
+ const potentialKey = DocumentKey.fromSegments([
12058
+ ...dbRemoteDoc.prefixPath,
12059
+ dbRemoteDoc.collectionGroup,
12060
+ dbRemoteDoc.documentId
12061
+ ]);
11906
12062
  // Go through keys not found in cache.
11907
- while (nextKey && DocumentKey.comparator(nextKey, potentialKey) < 0) {
12063
+ while (nextKey && dbKeyComparator(nextKey, potentialKey) < 0) {
11908
12064
  callback(nextKey, null);
11909
12065
  nextKey = keyIter.getNext();
11910
12066
  }
@@ -11915,7 +12071,7 @@ class IndexedDbRemoteDocumentCacheImpl {
11915
12071
  }
11916
12072
  // Skip to the next key (if there is one).
11917
12073
  if (nextKey) {
11918
- control.skip(nextKey.path.toArray());
12074
+ control.skip(dbKey(nextKey));
11919
12075
  }
11920
12076
  else {
11921
12077
  control.done();
@@ -11930,41 +12086,44 @@ class IndexedDbRemoteDocumentCacheImpl {
11930
12086
  }
11931
12087
  });
11932
12088
  }
11933
- getAll(transaction, collection, sinceReadTime) {
11934
- let results = mutableDocumentMap();
11935
- const immediateChildrenPathLength = collection.length + 1;
11936
- const iterationOptions = {};
11937
- if (sinceReadTime.isEqual(SnapshotVersion.min())) {
11938
- // Documents are ordered by key, so we can use a prefix scan to narrow
11939
- // down the documents we need to match the query against.
11940
- const startKey = collection.toArray();
11941
- iterationOptions.range = IDBKeyRange.lowerBound(startKey);
11942
- }
11943
- else {
11944
- // Execute an index-free query and filter by read time. This is safe
11945
- // since all document changes to queries that have a
11946
- // lastLimboFreeSnapshotVersion (`sinceReadTime`) have a read time set.
11947
- const collectionKey = collection.toArray();
11948
- const readTimeKey = toDbTimestampKey(sinceReadTime);
11949
- iterationOptions.range = IDBKeyRange.lowerBound([collectionKey, readTimeKey],
11950
- /* open= */ true);
11951
- iterationOptions.index = DbRemoteDocumentCollectionReadTimeIndex;
11952
- }
12089
+ getAllFromCollection(transaction, collection, offset) {
12090
+ const startKey = [
12091
+ collection.popLast().toArray(),
12092
+ collection.lastSegment(),
12093
+ toDbTimestampKey(offset.readTime),
12094
+ offset.documentKey.path.isEmpty()
12095
+ ? ''
12096
+ : offset.documentKey.path.lastSegment()
12097
+ ];
12098
+ const endKey = [
12099
+ collection.popLast().toArray(),
12100
+ collection.lastSegment(),
12101
+ [Number.MAX_SAFE_INTEGER, Number.MAX_SAFE_INTEGER],
12102
+ ''
12103
+ ];
11953
12104
  return remoteDocumentsStore(transaction)
11954
- .iterate(iterationOptions, (key, dbRemoteDoc, control) => {
11955
- // The query is actually returning any path that starts with the query
11956
- // path prefix which may include documents in subcollections. For
11957
- // example, a query on 'rooms' will return rooms/abc/messages/xyx but we
11958
- // shouldn't match it. Fix this by discarding rows with document keys
11959
- // more than one segment longer than the query path.
11960
- if (key.length !== immediateChildrenPathLength) {
11961
- return;
11962
- }
11963
- const document = this.maybeDecodeDocument(DocumentKey.fromSegments(key), dbRemoteDoc);
11964
- if (collection.isPrefixOf(document.key.path)) {
12105
+ .loadAll(IDBKeyRange.bound(startKey, endKey, true))
12106
+ .next(dbRemoteDocs => {
12107
+ let results = mutableDocumentMap();
12108
+ for (const dbRemoteDoc of dbRemoteDocs) {
12109
+ const document = this.maybeDecodeDocument(DocumentKey.fromSegments(dbRemoteDoc.prefixPath.concat(dbRemoteDoc.collectionGroup, dbRemoteDoc.documentId)), dbRemoteDoc);
11965
12110
  results = results.insert(document.key, document);
11966
12111
  }
11967
- else {
12112
+ return results;
12113
+ });
12114
+ }
12115
+ getAllFromCollectionGroup(transaction, collectionGroup, offset, limit) {
12116
+ let results = mutableDocumentMap();
12117
+ const startKey = dbCollectionGroupKey(collectionGroup, offset);
12118
+ const endKey = dbCollectionGroupKey(collectionGroup, IndexOffset.max());
12119
+ return remoteDocumentsStore(transaction)
12120
+ .iterate({
12121
+ index: DbRemoteDocumentCollectionGroupIndex,
12122
+ range: IDBKeyRange.bound(startKey, endKey, true)
12123
+ }, (_, dbRemoteDoc, control) => {
12124
+ const document = this.maybeDecodeDocument(DocumentKey.fromSegments(dbRemoteDoc.prefixPath.concat(dbRemoteDoc.collectionGroup, dbRemoteDoc.documentId)), dbRemoteDoc);
12125
+ results = results.insert(document.key, document);
12126
+ if (results.size === limit) {
11968
12127
  control.done();
11969
12128
  }
11970
12129
  })
@@ -12008,50 +12167,6 @@ class IndexedDbRemoteDocumentCacheImpl {
12008
12167
  function newIndexedDbRemoteDocumentCache(serializer) {
12009
12168
  return new IndexedDbRemoteDocumentCacheImpl(serializer);
12010
12169
  }
12011
- /**
12012
- * Returns the set of documents that have changed since the specified read
12013
- * time.
12014
- */
12015
- // PORTING NOTE: This is only used for multi-tab synchronization.
12016
- function remoteDocumentCacheGetNewDocumentChanges(remoteDocumentCache, transaction, sinceReadTime) {
12017
- const remoteDocumentCacheImpl = debugCast(remoteDocumentCache);
12018
- let changedDocs = mutableDocumentMap();
12019
- let lastReadTime = toDbTimestampKey(sinceReadTime);
12020
- const documentsStore = remoteDocumentsStore(transaction);
12021
- const range = IDBKeyRange.lowerBound(lastReadTime, true);
12022
- return documentsStore
12023
- .iterate({ index: DbRemoteDocumentReadTimeIndex, range }, (_, dbRemoteDoc) => {
12024
- // Unlike `getEntry()` and others, `getNewDocumentChanges()` parses
12025
- // the documents directly since we want to keep sentinel deletes.
12026
- const doc = fromDbRemoteDocument(remoteDocumentCacheImpl.serializer, dbRemoteDoc);
12027
- changedDocs = changedDocs.insert(doc.key, doc);
12028
- lastReadTime = dbRemoteDoc.readTime;
12029
- })
12030
- .next(() => {
12031
- return {
12032
- changedDocs,
12033
- readTime: fromDbTimestampKey(lastReadTime)
12034
- };
12035
- });
12036
- }
12037
- /**
12038
- * Returns the read time of the most recently read document in the cache, or
12039
- * SnapshotVersion.min() if not available.
12040
- */
12041
- // PORTING NOTE: This is only used for multi-tab synchronization.
12042
- function remoteDocumentCacheGetLastReadTime(transaction) {
12043
- const documentsStore = remoteDocumentsStore(transaction);
12044
- // If there are no existing entries, we return SnapshotVersion.min().
12045
- let readTime = SnapshotVersion.min();
12046
- return documentsStore
12047
- .iterate({ index: DbRemoteDocumentReadTimeIndex, reverse: true }, (key, dbRemoteDoc, control) => {
12048
- if (dbRemoteDoc.readTime) {
12049
- readTime = fromDbTimestampKey(dbRemoteDoc.readTime);
12050
- }
12051
- control.done();
12052
- })
12053
- .next(() => readTime);
12054
- }
12055
12170
  /**
12056
12171
  * Handles the details of adding and updating documents in the IndexedDbRemoteDocumentCache.
12057
12172
  *
@@ -12069,24 +12184,26 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
12069
12184
  super();
12070
12185
  this.documentCache = documentCache;
12071
12186
  this.trackRemovals = trackRemovals;
12072
- // A map of document sizes prior to applying the changes in this buffer.
12073
- this.documentSizes = new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
12187
+ // A map of document sizes and read times prior to applying the changes in
12188
+ // this buffer.
12189
+ this.documentStates = new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
12074
12190
  }
12075
12191
  applyChanges(transaction) {
12076
12192
  const promises = [];
12077
12193
  let sizeDelta = 0;
12078
12194
  let collectionParents = new SortedSet((l, r) => primitiveComparator(l.canonicalString(), r.canonicalString()));
12079
12195
  this.changes.forEach((key, documentChange) => {
12080
- const previousSize = this.documentSizes.get(key);
12196
+ const previousDoc = this.documentStates.get(key);
12197
+ promises.push(this.documentCache.removeEntry(transaction, key, previousDoc.readTime));
12081
12198
  if (documentChange.isValidDocument()) {
12082
12199
  const doc = toDbRemoteDocument(this.documentCache.serializer, documentChange);
12083
12200
  collectionParents = collectionParents.add(key.path.popLast());
12084
12201
  const size = dbDocumentSize(doc);
12085
- sizeDelta += size - previousSize;
12202
+ sizeDelta += size - previousDoc.size;
12086
12203
  promises.push(this.documentCache.addEntry(transaction, key, doc));
12087
12204
  }
12088
12205
  else {
12089
- sizeDelta -= previousSize;
12206
+ sizeDelta -= previousDoc.size;
12090
12207
  if (this.trackRemovals) {
12091
12208
  // In order to track removals, we store a "sentinel delete" in the
12092
12209
  // RemoteDocumentCache. This entry is represented by a NoDocument
@@ -12095,9 +12212,6 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
12095
12212
  const deletedDoc = toDbRemoteDocument(this.documentCache.serializer, documentChange.convertToNoDocument(SnapshotVersion.min()));
12096
12213
  promises.push(this.documentCache.addEntry(transaction, key, deletedDoc));
12097
12214
  }
12098
- else {
12099
- promises.push(this.documentCache.removeEntry(transaction, key));
12100
- }
12101
12215
  }
12102
12216
  });
12103
12217
  collectionParents.forEach(parent => {
@@ -12111,7 +12225,10 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
12111
12225
  return this.documentCache
12112
12226
  .getSizedEntry(transaction, documentKey)
12113
12227
  .next(getResult => {
12114
- this.documentSizes.set(documentKey, getResult.size);
12228
+ this.documentStates.set(documentKey, {
12229
+ size: getResult.size,
12230
+ readTime: getResult.document.readTime
12231
+ });
12115
12232
  return getResult.document;
12116
12233
  });
12117
12234
  }
@@ -12125,7 +12242,10 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
12125
12242
  // keys to `DocumentSizeEntry`s. This is to allow returning the
12126
12243
  // `MutableDocumentMap` directly, without a conversion.
12127
12244
  sizeMap.forEach((documentKey, size) => {
12128
- this.documentSizes.set(documentKey, size);
12245
+ this.documentStates.set(documentKey, {
12246
+ size,
12247
+ readTime: documents.get(documentKey).readTime
12248
+ });
12129
12249
  });
12130
12250
  return documents;
12131
12251
  });
@@ -12140,8 +12260,71 @@ function documentGlobalStore(txn) {
12140
12260
  function remoteDocumentsStore(txn) {
12141
12261
  return getStore(txn, DbRemoteDocumentStore);
12142
12262
  }
12143
- function dbKey(docKey) {
12144
- return docKey.path.toArray();
12263
+ /**
12264
+ * Returns a key that can be used for document lookups on the
12265
+ * `DbRemoteDocumentDocumentKeyIndex` index.
12266
+ */
12267
+ function dbKey(documentKey) {
12268
+ const path = documentKey.path.toArray();
12269
+ return [
12270
+ /* prefix path */ path.slice(0, path.length - 2),
12271
+ /* collection id */ path[path.length - 2],
12272
+ /* document id */ path[path.length - 1]
12273
+ ];
12274
+ }
12275
+ /**
12276
+ * Returns a key that can be used for document lookups via the primary key of
12277
+ * the DbRemoteDocument object store.
12278
+ */
12279
+ function dbReadTimeKey(documentKey, readTime) {
12280
+ const path = documentKey.path.toArray();
12281
+ return [
12282
+ /* prefix path */ path.slice(0, path.length - 2),
12283
+ /* collection id */ path[path.length - 2],
12284
+ toDbTimestampKey(readTime),
12285
+ /* document id */ path[path.length - 1]
12286
+ ];
12287
+ }
12288
+ /**
12289
+ * Returns a key that can be used for document lookups on the
12290
+ * `DbRemoteDocumentDocumentCollectionGroupIndex` index.
12291
+ */
12292
+ function dbCollectionGroupKey(collectionGroup, offset) {
12293
+ const path = offset.documentKey.path.toArray();
12294
+ return [
12295
+ /* collection id */ collectionGroup,
12296
+ toDbTimestampKey(offset.readTime),
12297
+ /* prefix path */ path.slice(0, path.length - 2),
12298
+ /* document id */ path.length > 0 ? path[path.length - 1] : ''
12299
+ ];
12300
+ }
12301
+ /**
12302
+ * Comparator that compares document keys according to the primary key sorting
12303
+ * used by the `DbRemoteDocumentDocument` store (by prefix path, collection id
12304
+ * and then document ID).
12305
+ *
12306
+ * Visible for testing.
12307
+ */
12308
+ function dbKeyComparator(l, r) {
12309
+ const left = l.path.toArray();
12310
+ const right = r.path.toArray();
12311
+ // The ordering is based on https://chromium.googlesource.com/chromium/blink/+/fe5c21fef94dae71c1c3344775b8d8a7f7e6d9ec/Source/modules/indexeddb/IDBKey.cpp#74
12312
+ let cmp = 0;
12313
+ for (let i = 0; i < left.length - 2 && i < right.length - 2; ++i) {
12314
+ cmp = primitiveComparator(left[i], right[i]);
12315
+ if (cmp) {
12316
+ return cmp;
12317
+ }
12318
+ }
12319
+ cmp = primitiveComparator(left.length, right.length);
12320
+ if (cmp) {
12321
+ return cmp;
12322
+ }
12323
+ cmp = primitiveComparator(left[left.length - 2], right[right.length - 2]);
12324
+ if (cmp) {
12325
+ return cmp;
12326
+ }
12327
+ return primitiveComparator(left[left.length - 1], right[right.length - 1]);
12145
12328
  }
12146
12329
  /**
12147
12330
  * Schema Version for the Web client:
@@ -12162,9 +12345,11 @@ function dbKey(docKey) {
12162
12345
  * 10. Rewrite the canonical IDs to the explicit Protobuf-based format.
12163
12346
  * 11. Add bundles and named_queries for bundle support.
12164
12347
  * 12. Add document overlays.
12165
- * 13. Add indexing support.
12348
+ * 13. Rewrite the keys of the remote document cache to allow for efficient
12349
+ * document lookup via `getAll()`.
12350
+ * 14. Add indexing support.
12166
12351
  */
12167
- const SCHEMA_VERSION = 12;
12352
+ const SCHEMA_VERSION = 13;
12168
12353
 
12169
12354
  /**
12170
12355
  * @license
@@ -12200,7 +12385,7 @@ class SchemaConverter {
12200
12385
  createPrimaryClientStore(db);
12201
12386
  createMutationQueue(db);
12202
12387
  createQueryCache(db);
12203
- createRemoteDocumentCache(db);
12388
+ createLegacyRemoteDocumentCache(db);
12204
12389
  }
12205
12390
  // Migration 2 to populate the targetGlobal object no longer needed since
12206
12391
  // migration 3 unconditionally clears it.
@@ -12249,7 +12434,8 @@ class SchemaConverter {
12249
12434
  // to the DbRemoteDocument object store itself. Since the previous change
12250
12435
  // log only contained transient data, we can drop its object store.
12251
12436
  dropRemoteDocumentChangesStore(db);
12252
- createRemoteDocumentReadTimeIndex(txn);
12437
+ // Note: Schema version 9 used to create a read time index for the
12438
+ // RemoteDocumentCache. This is now done with schema version 13.
12253
12439
  });
12254
12440
  }
12255
12441
  if (fromVersion < 10 && toVersion >= 10) {
@@ -12267,6 +12453,12 @@ class SchemaConverter {
12267
12453
  });
12268
12454
  }
12269
12455
  if (fromVersion < 13 && toVersion >= 13) {
12456
+ p = p
12457
+ .next(() => createRemoteDocumentCache(db))
12458
+ .next(() => this.rewriteRemoteDocumentCache(db, simpleDbTransaction))
12459
+ .next(() => db.deleteObjectStore(DbRemoteDocumentStore$1));
12460
+ }
12461
+ if (fromVersion < 14 && toVersion >= 14) {
12270
12462
  p = p.next(() => {
12271
12463
  createFieldIndex(db);
12272
12464
  });
@@ -12276,7 +12468,7 @@ class SchemaConverter {
12276
12468
  addDocumentGlobal(txn) {
12277
12469
  let byteSize = 0;
12278
12470
  return txn
12279
- .store(DbRemoteDocumentStore)
12471
+ .store(DbRemoteDocumentStore$1)
12280
12472
  .iterate((_, doc) => {
12281
12473
  byteSize += dbDocumentSize(doc);
12282
12474
  })
@@ -12311,7 +12503,7 @@ class SchemaConverter {
12311
12503
  */
12312
12504
  ensureSequenceNumbers(txn) {
12313
12505
  const documentTargetStore = txn.store(DbTargetDocumentStore);
12314
- const documentsStore = txn.store(DbRemoteDocumentStore);
12506
+ const documentsStore = txn.store(DbRemoteDocumentStore$1);
12315
12507
  const globalTargetStore = txn.store(DbTargetGlobalStore);
12316
12508
  return globalTargetStore.get(DbTargetGlobalKey).next(metadata => {
12317
12509
  const writeSentinelKey = (path) => {
@@ -12358,7 +12550,7 @@ class SchemaConverter {
12358
12550
  };
12359
12551
  // Index existing remote documents.
12360
12552
  return txn
12361
- .store(DbRemoteDocumentStore)
12553
+ .store(DbRemoteDocumentStore$1)
12362
12554
  .iterate({ keysOnly: true }, (pathSegments, _) => {
12363
12555
  const path = new ResourcePath(pathSegments);
12364
12556
  return addEntry(path.popLast());
@@ -12381,6 +12573,27 @@ class SchemaConverter {
12381
12573
  return targetStore.put(updatedDbTarget);
12382
12574
  });
12383
12575
  }
12576
+ rewriteRemoteDocumentCache(db, transaction) {
12577
+ const legacyRemoteDocumentStore = transaction.store(DbRemoteDocumentStore$1);
12578
+ const writes = [];
12579
+ return legacyRemoteDocumentStore
12580
+ .iterate((_, legacyDocument) => {
12581
+ const remoteDocumentStore = transaction.store(DbRemoteDocumentStore);
12582
+ const path = extractKey(legacyDocument).path.toArray();
12583
+ const dbRemoteDocument = {
12584
+ prefixPath: path.slice(0, path.length - 2),
12585
+ collectionGroup: path[path.length - 2],
12586
+ documentId: path[path.length - 1],
12587
+ readTime: legacyDocument.readTime || [0, 0],
12588
+ unknownDocument: legacyDocument.unknownDocument,
12589
+ noDocument: legacyDocument.noDocument,
12590
+ document: legacyDocument.document,
12591
+ hasCommittedMutations: !!legacyDocument.hasCommittedMutations
12592
+ };
12593
+ writes.push(remoteDocumentStore.put(dbRemoteDocument));
12594
+ })
12595
+ .next(() => PersistencePromise.waitFor(writes));
12596
+ }
12384
12597
  }
12385
12598
  function sentinelKey(path) {
12386
12599
  return [0, encodeResourcePath(path)];
@@ -12417,8 +12630,15 @@ function upgradeMutationBatchSchemaAndMigrateData(db, txn) {
12417
12630
  return PersistencePromise.waitFor(writeAll);
12418
12631
  });
12419
12632
  }
12633
+ function createLegacyRemoteDocumentCache(db) {
12634
+ db.createObjectStore(DbRemoteDocumentStore$1);
12635
+ }
12420
12636
  function createRemoteDocumentCache(db) {
12421
- db.createObjectStore(DbRemoteDocumentStore);
12637
+ const remoteDocumentStore = db.createObjectStore(DbRemoteDocumentStore, {
12638
+ keyPath: DbRemoteDocumentKeyPath
12639
+ });
12640
+ remoteDocumentStore.createIndex(DbRemoteDocumentDocumentKeyIndex, DbRemoteDocumentDocumentKeyIndexPath);
12641
+ remoteDocumentStore.createIndex(DbRemoteDocumentCollectionGroupIndex, DbRemoteDocumentCollectionGroupIndexPath);
12422
12642
  }
12423
12643
  function createDocumentGlobalStore(db) {
12424
12644
  db.createObjectStore(DbRemoteDocumentGlobalStore);
@@ -12460,15 +12680,6 @@ function writeEmptyTargetGlobalEntry(txn) {
12460
12680
  };
12461
12681
  return globalStore.put(DbTargetGlobalKey, metadata);
12462
12682
  }
12463
- /**
12464
- * Creates indices on the RemoteDocuments store used for both multi-tab
12465
- * and Index-Free queries.
12466
- */
12467
- function createRemoteDocumentReadTimeIndex(txn) {
12468
- const remoteDocumentStore = txn.objectStore(DbRemoteDocumentStore);
12469
- remoteDocumentStore.createIndex(DbRemoteDocumentReadTimeIndex, DbRemoteDocumentReadTimeIndexPath, { unique: false });
12470
- remoteDocumentStore.createIndex(DbRemoteDocumentCollectionReadTimeIndex, DbRemoteDocumentCollectionReadTimeIndexPath, { unique: false });
12471
- }
12472
12683
  function createClientMetadataStore(db) {
12473
12684
  db.createObjectStore(DbClientMetadataStore, {
12474
12685
  keyPath: DbClientMetadataKeyPath
@@ -12485,11 +12696,11 @@ function createNamedQueriesStore(db) {
12485
12696
  });
12486
12697
  }
12487
12698
  function createFieldIndex(db) {
12488
- const indexConfiguratioStore = db.createObjectStore(DbIndexConfigurationStore, {
12699
+ const indexConfigurationStore = db.createObjectStore(DbIndexConfigurationStore, {
12489
12700
  keyPath: DbIndexConfigurationKeyPath,
12490
12701
  autoIncrement: true
12491
12702
  });
12492
- indexConfiguratioStore.createIndex(DbIndexConfigurationCollectionGroupIndex, DbIndexConfigurationCollectionGroupIndexPath, { unique: false });
12703
+ indexConfigurationStore.createIndex(DbIndexConfigurationCollectionGroupIndex, DbIndexConfigurationCollectionGroupIndexPath, { unique: false });
12493
12704
  const indexStateStore = db.createObjectStore(DbIndexStateStore, {
12494
12705
  keyPath: DbIndexStateKeyPath
12495
12706
  });
@@ -12505,6 +12716,20 @@ function createDocumentOverlayStore(db) {
12505
12716
  });
12506
12717
  documentOverlayStore.createIndex(DbDocumentOverlayCollectionPathOverlayIndex, DbDocumentOverlayCollectionPathOverlayIndexPath, { unique: false });
12507
12718
  documentOverlayStore.createIndex(DbDocumentOverlayCollectionGroupOverlayIndex, DbDocumentOverlayCollectionGroupOverlayIndexPath, { unique: false });
12719
+ }
12720
+ function extractKey(remoteDoc) {
12721
+ if (remoteDoc.document) {
12722
+ return new DocumentKey(ResourcePath.fromString(remoteDoc.document.name).popFirst(5));
12723
+ }
12724
+ else if (remoteDoc.noDocument) {
12725
+ return DocumentKey.fromSegments(remoteDoc.noDocument.path);
12726
+ }
12727
+ else if (remoteDoc.unknownDocument) {
12728
+ return DocumentKey.fromSegments(remoteDoc.unknownDocument.path);
12729
+ }
12730
+ else {
12731
+ return fail();
12732
+ }
12508
12733
  }
12509
12734
 
12510
12735
  /**
@@ -13000,7 +13225,7 @@ class IndexedDbPersistence {
13000
13225
  return this.remoteDocumentCache;
13001
13226
  }
13002
13227
  getIndexManager(user) {
13003
- return new IndexedDbIndexManager(user);
13228
+ return new IndexedDbIndexManager(user, this.serializer.remoteSerializer.databaseId);
13004
13229
  }
13005
13230
  getDocumentOverlayCache(user) {
13006
13231
  return IndexedDbDocumentOverlayCache.forUser(this.serializer, user);
@@ -13347,18 +13572,17 @@ class LocalDocumentsView {
13347
13572
  *
13348
13573
  * @param transaction - The persistence transaction.
13349
13574
  * @param query - The query to match documents against.
13350
- * @param sinceReadTime - If not set to SnapshotVersion.min(), return only
13351
- * documents that have been read since this snapshot version (exclusive).
13575
+ * @param offset - Read time and key to start scanning by (exclusive).
13352
13576
  */
13353
- getDocumentsMatchingQuery(transaction, query, sinceReadTime) {
13577
+ getDocumentsMatchingQuery(transaction, query, offset) {
13354
13578
  if (isDocumentQuery$1(query)) {
13355
13579
  return this.getDocumentsMatchingDocumentQuery(transaction, query.path);
13356
13580
  }
13357
13581
  else if (isCollectionGroupQuery(query)) {
13358
- return this.getDocumentsMatchingCollectionGroupQuery(transaction, query, sinceReadTime);
13582
+ return this.getDocumentsMatchingCollectionGroupQuery(transaction, query, offset);
13359
13583
  }
13360
13584
  else {
13361
- return this.getDocumentsMatchingCollectionQuery(transaction, query, sinceReadTime);
13585
+ return this.getDocumentsMatchingCollectionQuery(transaction, query, offset);
13362
13586
  }
13363
13587
  }
13364
13588
  getDocumentsMatchingDocumentQuery(transaction, docPath) {
@@ -13371,7 +13595,7 @@ class LocalDocumentsView {
13371
13595
  return result;
13372
13596
  });
13373
13597
  }
13374
- getDocumentsMatchingCollectionGroupQuery(transaction, query, sinceReadTime) {
13598
+ getDocumentsMatchingCollectionGroupQuery(transaction, query, offset) {
13375
13599
  const collectionId = query.collectionGroup;
13376
13600
  let results = documentMap();
13377
13601
  return this.indexManager
@@ -13381,7 +13605,7 @@ class LocalDocumentsView {
13381
13605
  // collectionId and aggregate the results.
13382
13606
  return PersistencePromise.forEach(parents, (parent) => {
13383
13607
  const collectionQuery = asCollectionQueryAtPath(query, parent.child(collectionId));
13384
- return this.getDocumentsMatchingCollectionQuery(transaction, collectionQuery, sinceReadTime).next(r => {
13608
+ return this.getDocumentsMatchingCollectionQuery(transaction, collectionQuery, offset).next(r => {
13385
13609
  r.forEach((key, doc) => {
13386
13610
  results = results.insert(key, doc);
13387
13611
  });
@@ -13389,11 +13613,11 @@ class LocalDocumentsView {
13389
13613
  }).next(() => results);
13390
13614
  });
13391
13615
  }
13392
- getDocumentsMatchingCollectionQuery(transaction, query, sinceReadTime) {
13616
+ getDocumentsMatchingCollectionQuery(transaction, query, offset) {
13393
13617
  // Query the remote documents and overlay mutations.
13394
13618
  let results;
13395
13619
  return this.remoteDocumentCache
13396
- .getAll(transaction, query.path, sinceReadTime)
13620
+ .getAllFromCollection(transaction, query.path, offset)
13397
13621
  .next(queryResults => {
13398
13622
  results = queryResults;
13399
13623
  return this.mutationQueue.getAllMutationBatchesAffectingQuery(transaction, query);
@@ -13479,11 +13703,12 @@ class LocalStoreImpl {
13479
13703
  // TODO(wuandy): Evaluate if TargetId can be part of Target.
13480
13704
  this.targetIdByTarget = new ObjectMap(t => canonifyTarget(t), targetEquals);
13481
13705
  /**
13482
- * The read time of the last entry processed by `getNewDocumentChanges()`.
13706
+ * A per collection group index of the last read time processed by
13707
+ * `getNewDocumentChanges()`.
13483
13708
  *
13484
13709
  * PORTING NOTE: This is only used for multi-tab synchronization.
13485
13710
  */
13486
- this.lastDocumentChangeReadTime = SnapshotVersion.min();
13711
+ this.collectionGroupReadTime = new Map();
13487
13712
  this.remoteDocuments = persistence.getRemoteDocumentCache();
13488
13713
  this.targetCache = persistence.getTargetCache();
13489
13714
  this.bundleCache = persistence.getBundleCache();
@@ -14018,6 +14243,7 @@ function localStoreExecuteQuery(localStore, query, usePreviousResults) {
14018
14243
  ? lastLimboFreeSnapshotVersion
14019
14244
  : SnapshotVersion.min(), usePreviousResults ? remoteKeys : documentKeySet()))
14020
14245
  .next(documents => {
14246
+ setMaxReadTime(localStoreImpl, queryCollectionGroup(query), documents);
14021
14247
  return { documents, remoteKeys };
14022
14248
  });
14023
14249
  });
@@ -14095,28 +14321,32 @@ function localStoreGetCachedTarget(localStore, targetId) {
14095
14321
  * since the prior call.
14096
14322
  */
14097
14323
  // PORTING NOTE: Multi-Tab only.
14098
- function localStoreGetNewDocumentChanges(localStore) {
14324
+ function localStoreGetNewDocumentChanges(localStore, collectionGroup) {
14099
14325
  const localStoreImpl = debugCast(localStore);
14326
+ // Get the current maximum read time for the collection. This should always
14327
+ // exist, but to reduce the chance for regressions we default to
14328
+ // SnapshotVersion.Min()
14329
+ // TODO(indexing): Consider removing the default value.
14330
+ const readTime = localStoreImpl.collectionGroupReadTime.get(collectionGroup) ||
14331
+ SnapshotVersion.min();
14100
14332
  return localStoreImpl.persistence
14101
- .runTransaction('Get new document changes', 'readonly', txn => remoteDocumentCacheGetNewDocumentChanges(localStoreImpl.remoteDocuments, txn, localStoreImpl.lastDocumentChangeReadTime))
14102
- .then(({ changedDocs, readTime }) => {
14103
- localStoreImpl.lastDocumentChangeReadTime = readTime;
14333
+ .runTransaction('Get new document changes', 'readonly', txn => localStoreImpl.remoteDocuments.getAllFromCollectionGroup(txn, collectionGroup, newIndexOffsetSuccessorFromReadTime(readTime, INITIAL_LARGEST_BATCH_ID),
14334
+ /* limit= */ Number.MAX_SAFE_INTEGER))
14335
+ .then(changedDocs => {
14336
+ setMaxReadTime(localStoreImpl, collectionGroup, changedDocs);
14104
14337
  return changedDocs;
14105
14338
  });
14106
14339
  }
14107
- /**
14108
- * Reads the newest document change from persistence and moves the internal
14109
- * synchronization marker forward so that calls to `getNewDocumentChanges()`
14110
- * only return changes that happened after client initialization.
14111
- */
14340
+ /** Sets the collection group's maximum read time from the given documents. */
14112
14341
  // PORTING NOTE: Multi-Tab only.
14113
- async function localStoreSynchronizeLastDocumentChangeReadTime(localStore) {
14114
- const localStoreImpl = debugCast(localStore);
14115
- return localStoreImpl.persistence
14116
- .runTransaction('Synchronize last document change read time', 'readonly', txn => remoteDocumentCacheGetLastReadTime(txn))
14117
- .then(readTime => {
14118
- localStoreImpl.lastDocumentChangeReadTime = readTime;
14342
+ function setMaxReadTime(localStoreImpl, collectionGroup, changedDocs) {
14343
+ let readTime = SnapshotVersion.min();
14344
+ changedDocs.forEach((_, doc) => {
14345
+ if (doc.readTime.compareTo(readTime) > 0) {
14346
+ readTime = doc.readTime;
14347
+ }
14119
14348
  });
14349
+ localStoreImpl.collectionGroupReadTime.set(collectionGroup, readTime);
14120
14350
  }
14121
14351
  /**
14122
14352
  * Creates a new target using the given bundle name, which will be used to
@@ -14799,7 +15029,7 @@ class MemoryRemoteDocumentCacheImpl {
14799
15029
  });
14800
15030
  return PersistencePromise.resolve(results);
14801
15031
  }
14802
- getAll(transaction, collectionPath, sinceReadTime) {
15032
+ getAllFromCollection(transaction, collectionPath, offset) {
14803
15033
  let results = mutableDocumentMap();
14804
15034
  // Documents are ordered by key, so we can use a prefix scan to narrow down
14805
15035
  // the documents we need to match the query against.
@@ -14814,13 +15044,19 @@ class MemoryRemoteDocumentCacheImpl {
14814
15044
  // Exclude entries from subcollections.
14815
15045
  continue;
14816
15046
  }
14817
- if (document.readTime.compareTo(sinceReadTime) <= 0) {
15047
+ if (indexOffsetComparator(newIndexOffsetFromDocument(document), offset) <= 0) {
15048
+ // The document sorts before the offset.
14818
15049
  continue;
14819
15050
  }
14820
15051
  results = results.insert(document.key, document.mutableCopy());
14821
15052
  }
14822
15053
  return PersistencePromise.resolve(results);
14823
15054
  }
15055
+ getAllFromCollectionGroup(transaction, collectionGroup, offset, limti) {
15056
+ // This method should only be called from the IndexBackfiller if persistence
15057
+ // is enabled.
15058
+ fail();
15059
+ }
14824
15060
  forEachDocumentKey(transaction, f) {
14825
15061
  return PersistencePromise.forEach(this.docs, (key) => f(key));
14826
15062
  }
@@ -15273,7 +15509,7 @@ class QueryEngine {
15273
15509
  }
15274
15510
  // Retrieve all results for documents that were updated since the last
15275
15511
  // limbo-document free remote snapshot.
15276
- return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, lastLimboFreeSnapshotVersion).next(updatedResults => {
15512
+ return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, newIndexOffsetSuccessorFromReadTime(lastLimboFreeSnapshotVersion, INITIAL_LARGEST_BATCH_ID)).next(updatedResults => {
15277
15513
  // We merge `previousResults` into `updateResults`, since
15278
15514
  // `updateResults` is already a DocumentMap. If a document is
15279
15515
  // contained in both lists, then its contents are the same.
@@ -15335,7 +15571,7 @@ class QueryEngine {
15335
15571
  if (getLogLevel() <= LogLevel.DEBUG) {
15336
15572
  logDebug('QueryEngine', 'Using full collection scan to execute query:', stringifyQuery(query));
15337
15573
  }
15338
- return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, SnapshotVersion.min());
15574
+ return this.localDocumentsView.getDocumentsMatchingQuery(transaction, query, IndexOffset.min());
15339
15575
  }
15340
15576
  }
15341
15577
 
@@ -15396,8 +15632,9 @@ function createWebStorageOnlineStateKey(persistenceKey) {
15396
15632
  // The WebStorage prefix that plays as a event to indicate the remote documents
15397
15633
  // might have changed due to some secondary tabs loading a bundle.
15398
15634
  // format of the key is:
15399
- // firestore_bundle_loaded_<persistenceKey>
15400
- const BUNDLE_LOADED_KEY_PREFIX = 'firestore_bundle_loaded';
15635
+ // firestore_bundle_loaded_v2_<persistenceKey>
15636
+ // The version ending with "v2" stores the list of modified collection groups.
15637
+ const BUNDLE_LOADED_KEY_PREFIX = 'firestore_bundle_loaded_v2';
15401
15638
  function createBundleLoadedKey(persistenceKey) {
15402
15639
  return `${BUNDLE_LOADED_KEY_PREFIX}_${persistenceKey}`;
15403
15640
  }
@@ -15775,8 +16012,8 @@ class WebStorageSharedClientState {
15775
16012
  setOnlineState(onlineState) {
15776
16013
  this.persistOnlineState(onlineState);
15777
16014
  }
15778
- notifyBundleLoaded() {
15779
- this.persistBundleLoadedState();
16015
+ notifyBundleLoaded(collectionGroups) {
16016
+ this.persistBundleLoadedState(collectionGroups);
15780
16017
  }
15781
16018
  shutdown() {
15782
16019
  if (this.started) {
@@ -15860,7 +16097,8 @@ class WebStorageSharedClientState {
15860
16097
  }
15861
16098
  }
15862
16099
  else if (storageEvent.key === this.bundleLoadedKey) {
15863
- return this.syncEngine.synchronizeWithChangedDocuments();
16100
+ const collectionGroups = this.fromWebStoreBundleLoadedState(storageEvent.newValue);
16101
+ await Promise.all(collectionGroups.map(cg => this.syncEngine.synchronizeWithChangedDocuments(cg)));
15864
16102
  }
15865
16103
  });
15866
16104
  }
@@ -15892,8 +16130,9 @@ class WebStorageSharedClientState {
15892
16130
  const targetMetadata = new QueryTargetMetadata(targetId, state, error);
15893
16131
  this.setItem(targetKey, targetMetadata.toWebStorageJSON());
15894
16132
  }
15895
- persistBundleLoadedState() {
15896
- this.setItem(this.bundleLoadedKey, 'value-not-used');
16133
+ persistBundleLoadedState(collectionGroups) {
16134
+ const json = JSON.stringify(Array.from(collectionGroups));
16135
+ this.setItem(this.bundleLoadedKey, json);
15897
16136
  }
15898
16137
  /**
15899
16138
  * Parses a client state key in WebStorage. Returns null if the key does not
@@ -15937,6 +16176,9 @@ class WebStorageSharedClientState {
15937
16176
  fromWebStorageOnlineState(value) {
15938
16177
  return SharedOnlineState.fromWebStorageEntry(value);
15939
16178
  }
16179
+ fromWebStoreBundleLoadedState(value) {
16180
+ return JSON.parse(value);
16181
+ }
15940
16182
  async handleMutationBatchEvent(mutationBatch) {
15941
16183
  if (mutationBatch.user.uid !== this.currentUser.uid) {
15942
16184
  logDebug(LOG_TAG$a, `Ignoring mutation for non-active user ${mutationBatch.user.uid}`);
@@ -16053,7 +16295,7 @@ class MemorySharedClientState {
16053
16295
  }
16054
16296
  shutdown() { }
16055
16297
  writeSequenceNumber(sequenceNumber) { }
16056
- notifyBundleLoaded() {
16298
+ notifyBundleLoaded(collectionGroups) {
16057
16299
  // No op.
16058
16300
  }
16059
16301
  }
@@ -21353,29 +21595,6 @@ class LocalViewChanges {
21353
21595
  }
21354
21596
  }
21355
21597
 
21356
- /**
21357
- * @license
21358
- * Copyright 2020 Google LLC
21359
- *
21360
- * Licensed under the Apache License, Version 2.0 (the "License");
21361
- * you may not use this file except in compliance with the License.
21362
- * You may obtain a copy of the License at
21363
- *
21364
- * http://www.apache.org/licenses/LICENSE-2.0
21365
- *
21366
- * Unless required by applicable law or agreed to in writing, software
21367
- * distributed under the License is distributed on an "AS IS" BASIS,
21368
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21369
- * See the License for the specific language governing permissions and
21370
- * limitations under the License.
21371
- */
21372
- class BundleLoadResult {
21373
- constructor(progress, changedDocs) {
21374
- this.progress = progress;
21375
- this.changedDocs = changedDocs;
21376
- }
21377
- }
21378
-
21379
21598
  /**
21380
21599
  * @license
21381
21600
  * Copyright 2020 Google LLC
@@ -21430,6 +21649,8 @@ class BundleLoader {
21430
21649
  this.queries = [];
21431
21650
  /** Batched documents to be saved into storage */
21432
21651
  this.documents = [];
21652
+ /** The collection groups affected by this bundle. */
21653
+ this.collectionGroups = new Set();
21433
21654
  this.progress = bundleInitialProgress(bundleMetadata);
21434
21655
  }
21435
21656
  /**
@@ -21449,6 +21670,8 @@ class BundleLoader {
21449
21670
  if (!element.payload.documentMetadata.exists) {
21450
21671
  ++documentsLoaded;
21451
21672
  }
21673
+ const path = ResourcePath.fromString(element.payload.documentMetadata.name);
21674
+ this.collectionGroups.add(path.get(path.length - 2));
21452
21675
  }
21453
21676
  else if (element.payload.document) {
21454
21677
  this.documents[this.documents.length - 1].document =
@@ -21479,13 +21702,17 @@ class BundleLoader {
21479
21702
  * Update the progress to 'Success' and return the updated progress.
21480
21703
  */
21481
21704
  async complete() {
21482
- const changedDocuments = await localStoreApplyBundledDocuments(this.localStore, new BundleConverterImpl(this.serializer), this.documents, this.bundleMetadata.id);
21705
+ const changedDocs = await localStoreApplyBundledDocuments(this.localStore, new BundleConverterImpl(this.serializer), this.documents, this.bundleMetadata.id);
21483
21706
  const queryDocumentMap = this.getQueryDocumentMapping(this.documents);
21484
21707
  for (const q of this.queries) {
21485
21708
  await localStoreSaveNamedQuery(this.localStore, q, queryDocumentMap.get(q.name));
21486
21709
  }
21487
21710
  this.progress.taskState = 'Success';
21488
- return new BundleLoadResult(Object.assign({}, this.progress), changedDocuments);
21711
+ return {
21712
+ progress: this.progress,
21713
+ changedCollectionGroups: this.collectionGroups,
21714
+ changedDocs
21715
+ };
21489
21716
  }
21490
21717
  }
21491
21718
  /**
@@ -22564,9 +22791,9 @@ async function synchronizeViewAndComputeSnapshot(syncEngine, queryView) {
22564
22791
  * snapshots if needed.
22565
22792
  */
22566
22793
  // PORTING NOTE: Multi-Tab only.
22567
- async function syncEngineSynchronizeWithChangedDocuments(syncEngine) {
22794
+ async function syncEngineSynchronizeWithChangedDocuments(syncEngine, collectionGroup) {
22568
22795
  const syncEngineImpl = debugCast(syncEngine);
22569
- return localStoreGetNewDocumentChanges(syncEngineImpl.localStore).then(changes => syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes));
22796
+ return localStoreGetNewDocumentChanges(syncEngineImpl.localStore, collectionGroup).then(changes => syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes));
22570
22797
  }
22571
22798
  /** Applies a mutation state to an existing batch. */
22572
22799
  // PORTING NOTE: Multi-Tab only.
@@ -22731,11 +22958,12 @@ async function syncEngineApplyTargetState(syncEngine, targetId, state, error) {
22731
22958
  logDebug(LOG_TAG$3, 'Ignoring unexpected query state notification.');
22732
22959
  return;
22733
22960
  }
22734
- if (syncEngineImpl.queriesByTarget.has(targetId)) {
22961
+ const query = syncEngineImpl.queriesByTarget.get(targetId);
22962
+ if (query && query.length > 0) {
22735
22963
  switch (state) {
22736
22964
  case 'current':
22737
22965
  case 'not-current': {
22738
- const changes = await localStoreGetNewDocumentChanges(syncEngineImpl.localStore);
22966
+ const changes = await localStoreGetNewDocumentChanges(syncEngineImpl.localStore, queryCollectionGroup(query[0]));
22739
22967
  const synthesizedRemoteEvent = RemoteEvent.createSynthesizedRemoteEventForCurrentChange(targetId, state === 'current');
22740
22968
  await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngineImpl, changes, synthesizedRemoteEvent);
22741
22969
  break;
@@ -22818,10 +23046,11 @@ function syncEngineEnsureWriteCallbacks(syncEngine) {
22818
23046
  function syncEngineLoadBundle(syncEngine, bundleReader, task) {
22819
23047
  const syncEngineImpl = debugCast(syncEngine);
22820
23048
  // eslint-disable-next-line @typescript-eslint/no-floating-promises
22821
- loadBundleImpl(syncEngineImpl, bundleReader, task).then(() => {
22822
- syncEngineImpl.sharedClientState.notifyBundleLoaded();
23049
+ loadBundleImpl(syncEngineImpl, bundleReader, task).then(collectionGroups => {
23050
+ syncEngineImpl.sharedClientState.notifyBundleLoaded(collectionGroups);
22823
23051
  });
22824
23052
  }
23053
+ /** Loads a bundle and returns the list of affected collection groups. */
22825
23054
  async function loadBundleImpl(syncEngine, reader, task) {
22826
23055
  try {
22827
23056
  const metadata = await reader.getMetadata();
@@ -22829,7 +23058,7 @@ async function loadBundleImpl(syncEngine, reader, task) {
22829
23058
  if (skip) {
22830
23059
  await reader.close();
22831
23060
  task._completeWith(bundleSuccessProgress(metadata));
22832
- return;
23061
+ return Promise.resolve(new Set());
22833
23062
  }
22834
23063
  task._updateProgress(bundleInitialProgress(metadata));
22835
23064
  const loader = new BundleLoader(metadata, syncEngine.localStore, reader.serializer);
@@ -22843,18 +23072,17 @@ async function loadBundleImpl(syncEngine, reader, task) {
22843
23072
  element = await reader.nextElement();
22844
23073
  }
22845
23074
  const result = await loader.complete();
22846
- // TODO(b/160876443): This currently raises snapshots with
22847
- // `fromCache=false` if users already listen to some queries and bundles
22848
- // has newer version.
22849
23075
  await syncEngineEmitNewSnapsAndNotifyLocalStore(syncEngine, result.changedDocs,
22850
23076
  /* remoteEvent */ undefined);
22851
23077
  // Save metadata, so loading the same bundle will skip.
22852
23078
  await localStoreSaveBundle(syncEngine.localStore, metadata);
22853
23079
  task._completeWith(result.progress);
23080
+ return Promise.resolve(result.changedCollectionGroups);
22854
23081
  }
22855
23082
  catch (e) {
22856
23083
  logWarn(LOG_TAG$3, `Loading bundle failed with ${e}`);
22857
23084
  task._failWith(e);
23085
+ return Promise.resolve(new Set());
22858
23086
  }
22859
23087
  }
22860
23088
 
@@ -22923,7 +23151,6 @@ class IndexedDbOfflineComponentProvider extends MemoryOfflineComponentProvider {
22923
23151
  }
22924
23152
  async initialize(cfg) {
22925
23153
  await super.initialize(cfg);
22926
- await localStoreSynchronizeLastDocumentChangeReadTime(this.localStore);
22927
23154
  await this.onlineComponentProvider.initialize(this, cfg);
22928
23155
  // Enqueue writes from a previous session
22929
23156
  await syncEngineEnsureWriteCallbacks(this.onlineComponentProvider.syncEngine);