@firebase/firestore 3.4.5 → 3.4.6-2022216223411

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 (101) hide show
  1. package/CHANGELOG.md +9 -0
  2. package/dist/firestore/src/index/index_entry.d.ts +6 -0
  3. package/dist/firestore/src/local/document_overlay_cache.d.ts +4 -5
  4. package/dist/firestore/src/local/index_manager.d.ts +2 -2
  5. package/dist/firestore/src/local/indexeddb_document_overlay_cache.d.ts +4 -5
  6. package/dist/firestore/src/local/indexeddb_index_manager.d.ts +48 -1
  7. package/dist/firestore/src/local/indexeddb_schema.d.ts +34 -553
  8. package/dist/firestore/src/local/indexeddb_sentinels.d.ts +238 -0
  9. package/dist/firestore/src/local/indexeddb_target_cache.d.ts +2 -1
  10. package/dist/firestore/src/local/local_serializer.d.ts +2 -1
  11. package/dist/firestore/src/local/memory_document_overlay_cache.d.ts +4 -5
  12. package/dist/firestore/src/local/memory_index_manager.d.ts +1 -1
  13. package/dist/firestore/src/local/simple_db.d.ts +11 -0
  14. package/dist/firestore/src/model/collections.d.ts +7 -0
  15. package/dist/firestore/src/model/field_index.d.ts +2 -0
  16. package/dist/firestore/src/model/type_order.d.ts +2 -1
  17. package/dist/firestore/src/platform/node/base64.d.ts +1 -0
  18. package/dist/firestore/src/util/obj_map.d.ts +3 -0
  19. package/dist/firestore/test/unit/local/test_document_overlay_cache.d.ts +4 -4
  20. package/dist/firestore/test/unit/local/test_index_manager.d.ts +1 -1
  21. package/dist/index.esm2017.js +4160 -4201
  22. package/dist/index.esm2017.js.map +1 -1
  23. package/dist/index.esm5.js +4332 -4311
  24. package/dist/index.esm5.js.map +1 -1
  25. package/dist/index.node.cjs.js +1223 -956
  26. package/dist/index.node.cjs.js.map +1 -1
  27. package/dist/index.node.mjs +1223 -956
  28. package/dist/index.node.mjs.map +1 -1
  29. package/dist/index.rn.js +4157 -4198
  30. package/dist/index.rn.js.map +1 -1
  31. package/dist/internal.d.ts +12 -5
  32. package/dist/lite/firestore/src/index/index_entry.d.ts +6 -0
  33. package/dist/lite/firestore/src/local/document_overlay_cache.d.ts +4 -5
  34. package/dist/lite/firestore/src/local/index_manager.d.ts +2 -2
  35. package/dist/lite/firestore/src/local/indexeddb_document_overlay_cache.d.ts +4 -5
  36. package/dist/lite/firestore/src/local/indexeddb_index_manager.d.ts +48 -1
  37. package/dist/lite/firestore/src/local/indexeddb_schema.d.ts +34 -553
  38. package/dist/lite/firestore/src/local/indexeddb_sentinels.d.ts +238 -0
  39. package/dist/lite/firestore/src/local/indexeddb_target_cache.d.ts +2 -1
  40. package/dist/lite/firestore/src/local/local_serializer.d.ts +2 -1
  41. package/dist/lite/firestore/src/local/memory_document_overlay_cache.d.ts +4 -5
  42. package/dist/lite/firestore/src/local/memory_index_manager.d.ts +1 -1
  43. package/dist/lite/firestore/src/local/simple_db.d.ts +11 -0
  44. package/dist/lite/firestore/src/model/collections.d.ts +7 -0
  45. package/dist/lite/firestore/src/model/field_index.d.ts +2 -0
  46. package/dist/lite/firestore/src/model/type_order.d.ts +2 -1
  47. package/dist/lite/firestore/src/platform/node/base64.d.ts +1 -0
  48. package/dist/lite/firestore/src/util/obj_map.d.ts +3 -0
  49. package/dist/lite/firestore/test/unit/local/test_document_overlay_cache.d.ts +4 -4
  50. package/dist/lite/firestore/test/unit/local/test_index_manager.d.ts +1 -1
  51. package/dist/lite/index.browser.esm2017.js +34 -24
  52. package/dist/lite/index.browser.esm2017.js.map +1 -1
  53. package/dist/lite/index.browser.esm5.js +12 -23
  54. package/dist/lite/index.browser.esm5.js.map +1 -1
  55. package/dist/lite/index.node.cjs.js +21 -9
  56. package/dist/lite/index.node.cjs.js.map +1 -1
  57. package/dist/lite/index.node.mjs +21 -9
  58. package/dist/lite/index.node.mjs.map +1 -1
  59. package/dist/lite/index.rn.esm2017.js +34 -24
  60. package/dist/lite/index.rn.esm2017.js.map +1 -1
  61. package/dist/lite/packages/firestore/src/index/index_entry.d.ts +6 -0
  62. package/dist/lite/packages/firestore/src/local/document_overlay_cache.d.ts +4 -5
  63. package/dist/lite/packages/firestore/src/local/index_manager.d.ts +2 -2
  64. package/dist/lite/packages/firestore/src/local/indexeddb_document_overlay_cache.d.ts +4 -5
  65. package/dist/lite/packages/firestore/src/local/indexeddb_index_manager.d.ts +48 -1
  66. package/dist/lite/packages/firestore/src/local/indexeddb_schema.d.ts +34 -553
  67. package/dist/lite/packages/firestore/src/local/indexeddb_sentinels.d.ts +238 -0
  68. package/dist/lite/packages/firestore/src/local/indexeddb_target_cache.d.ts +2 -1
  69. package/dist/lite/packages/firestore/src/local/local_serializer.d.ts +2 -1
  70. package/dist/lite/packages/firestore/src/local/memory_document_overlay_cache.d.ts +4 -5
  71. package/dist/lite/packages/firestore/src/local/memory_index_manager.d.ts +1 -1
  72. package/dist/lite/packages/firestore/src/local/simple_db.d.ts +11 -0
  73. package/dist/lite/packages/firestore/src/model/collections.d.ts +7 -0
  74. package/dist/lite/packages/firestore/src/model/field_index.d.ts +2 -0
  75. package/dist/lite/packages/firestore/src/model/type_order.d.ts +2 -1
  76. package/dist/lite/packages/firestore/src/platform/node/base64.d.ts +1 -0
  77. package/dist/lite/packages/firestore/src/util/obj_map.d.ts +3 -0
  78. package/dist/lite/packages/firestore/test/unit/local/test_document_overlay_cache.d.ts +4 -4
  79. package/dist/lite/packages/firestore/test/unit/local/test_index_manager.d.ts +1 -1
  80. package/dist/packages/firestore/dist/index.esm2017.d.ts +191 -191
  81. package/dist/packages/firestore/src/index/index_entry.d.ts +6 -0
  82. package/dist/packages/firestore/src/local/document_overlay_cache.d.ts +4 -5
  83. package/dist/packages/firestore/src/local/index_manager.d.ts +2 -2
  84. package/dist/packages/firestore/src/local/indexeddb_document_overlay_cache.d.ts +4 -5
  85. package/dist/packages/firestore/src/local/indexeddb_index_manager.d.ts +48 -1
  86. package/dist/packages/firestore/src/local/indexeddb_schema.d.ts +34 -553
  87. package/dist/packages/firestore/src/local/indexeddb_sentinels.d.ts +238 -0
  88. package/dist/packages/firestore/src/local/indexeddb_target_cache.d.ts +2 -1
  89. package/dist/packages/firestore/src/local/local_serializer.d.ts +2 -1
  90. package/dist/packages/firestore/src/local/memory_document_overlay_cache.d.ts +4 -5
  91. package/dist/packages/firestore/src/local/memory_index_manager.d.ts +1 -1
  92. package/dist/packages/firestore/src/local/simple_db.d.ts +11 -0
  93. package/dist/packages/firestore/src/model/collections.d.ts +7 -0
  94. package/dist/packages/firestore/src/model/field_index.d.ts +2 -0
  95. package/dist/packages/firestore/src/model/type_order.d.ts +2 -1
  96. package/dist/packages/firestore/src/platform/node/base64.d.ts +1 -0
  97. package/dist/packages/firestore/src/util/obj_map.d.ts +3 -0
  98. package/dist/packages/firestore/test/unit/local/test_document_overlay_cache.d.ts +4 -4
  99. package/dist/packages/firestore/test/unit/local/test_index_manager.d.ts +1 -1
  100. package/dist/private.d.ts +12 -5
  101. package/package.json +7 -7
@@ -8,7 +8,7 @@ import * as grpc from '@grpc/grpc-js';
8
8
  import * as protoLoader from '@grpc/proto-loader';
9
9
 
10
10
  const name = "@firebase/firestore";
11
- const version$1 = "3.4.5";
11
+ const version$1 = "3.4.6-2022216223411";
12
12
 
13
13
  /**
14
14
  * @license
@@ -61,7 +61,7 @@ User.GOOGLE_CREDENTIALS = new User('google-credentials-uid');
61
61
  User.FIRST_PARTY = new User('first-party-uid');
62
62
  User.MOCK_USER = new User('mock-user');
63
63
 
64
- const version = "9.6.7";
64
+ const version = "9.6.9-2022216223411";
65
65
 
66
66
  /**
67
67
  * @license
@@ -1199,55 +1199,23 @@ function decodeResourcePath(path) {
1199
1199
  }
1200
1200
  return new ResourcePath(segments);
1201
1201
  }
1202
+
1202
1203
  /**
1203
- * Schema Version for the Web client:
1204
- * 1. Initial version including Mutation Queue, Query Cache, and Remote
1205
- * Document Cache
1206
- * 2. Used to ensure a targetGlobal object exists and add targetCount to it. No
1207
- * longer required because migration 3 unconditionally clears it.
1208
- * 3. Dropped and re-created Query Cache to deal with cache corruption related
1209
- * to limbo resolution. Addresses
1210
- * https://github.com/firebase/firebase-ios-sdk/issues/1548
1211
- * 4. Multi-Tab Support.
1212
- * 5. Removal of held write acks.
1213
- * 6. Create document global for tracking document cache size.
1214
- * 7. Ensure every cached document has a sentinel row with a sequence number.
1215
- * 8. Add collection-parent index for Collection Group queries.
1216
- * 9. Change RemoteDocumentChanges store to be keyed by readTime rather than
1217
- * an auto-incrementing ID. This is required for Index-Free queries.
1218
- * 10. Rewrite the canonical IDs to the explicit Protobuf-based format.
1219
- * 11. Add bundles and named_queries for bundle support.
1220
- * 12. Add document overlays.
1221
- * 13. Add indexing support.
1222
- */
1223
- const SCHEMA_VERSION = 12;
1224
- /**
1225
- * Wrapper class to store timestamps (seconds and nanos) in IndexedDb objects.
1226
- */
1227
- class DbTimestamp {
1228
- constructor(seconds, nanoseconds) {
1229
- this.seconds = seconds;
1230
- this.nanoseconds = nanoseconds;
1231
- }
1232
- }
1233
- /**
1234
- * A singleton object to be stored in the 'owner' store in IndexedDb.
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
1235
1210
  *
1236
- * A given database can have a single primary tab assigned at a given time. That
1237
- * tab must validate that it is still holding the primary lease before every
1238
- * operation that requires locked access. The primary tab should regularly
1239
- * write an updated timestamp to this lease to prevent other tabs from
1240
- * "stealing" the primary lease
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.
1241
1218
  */
1242
- class DbPrimaryClient {
1243
- constructor(ownerId,
1244
- /** Whether to allow shared access from multiple tabs. */
1245
- allowTabSynchronization, leaseTimestampMs) {
1246
- this.ownerId = ownerId;
1247
- this.allowTabSynchronization = allowTabSynchronization;
1248
- this.leaseTimestampMs = leaseTimestampMs;
1249
- }
1250
- }
1251
1219
  /**
1252
1220
  * Name of the IndexedDb object store.
1253
1221
  *
@@ -1255,239 +1223,62 @@ class DbPrimaryClient {
1255
1223
  * older clients that only supported single locked access to the persistence
1256
1224
  * layer.
1257
1225
  */
1258
- DbPrimaryClient.store = 'owner';
1226
+ const DbPrimaryClientStore = 'owner';
1259
1227
  /**
1260
1228
  * The key string used for the single object that exists in the
1261
1229
  * DbPrimaryClient store.
1262
1230
  */
1263
- DbPrimaryClient.key = 'owner';
1264
- /**
1265
- * An object to be stored in the 'mutationQueues' store in IndexedDb.
1266
- *
1267
- * Each user gets a single queue of MutationBatches to apply to the server.
1268
- * DbMutationQueue tracks the metadata about the queue.
1269
- */
1270
- class DbMutationQueue {
1271
- constructor(
1272
- /**
1273
- * The normalized user ID to which this queue belongs.
1274
- */
1275
- userId,
1276
- /**
1277
- * An identifier for the highest numbered batch that has been acknowledged
1278
- * by the server. All MutationBatches in this queue with batchIds less
1279
- * than or equal to this value are considered to have been acknowledged by
1280
- * the server.
1281
- *
1282
- * NOTE: this is deprecated and no longer used by the code.
1283
- */
1284
- lastAcknowledgedBatchId,
1285
- /**
1286
- * A stream token that was previously sent by the server.
1287
- *
1288
- * See StreamingWriteRequest in datastore.proto for more details about
1289
- * usage.
1290
- *
1291
- * After sending this token, earlier tokens may not be used anymore so
1292
- * only a single stream token is retained.
1293
- *
1294
- * NOTE: this is deprecated and no longer used by the code.
1295
- */
1296
- lastStreamToken) {
1297
- this.userId = userId;
1298
- this.lastAcknowledgedBatchId = lastAcknowledgedBatchId;
1299
- this.lastStreamToken = lastStreamToken;
1300
- }
1301
- }
1231
+ const DbPrimaryClientKey = 'owner';
1302
1232
  /** Name of the IndexedDb object store. */
1303
- DbMutationQueue.store = 'mutationQueues';
1233
+ const DbMutationQueueStore = 'mutationQueues';
1304
1234
  /** Keys are automatically assigned via the userId property. */
1305
- DbMutationQueue.keyPath = 'userId';
1306
- /**
1307
- * An object to be stored in the 'mutations' store in IndexedDb.
1308
- *
1309
- * Represents a batch of user-level mutations intended to be sent to the server
1310
- * in a single write. Each user-level batch gets a separate DbMutationBatch
1311
- * with a new batchId.
1312
- */
1313
- class DbMutationBatch {
1314
- constructor(
1315
- /**
1316
- * The normalized user ID to which this batch belongs.
1317
- */
1318
- userId,
1319
- /**
1320
- * An identifier for this batch, allocated using an auto-generated key.
1321
- */
1322
- batchId,
1323
- /**
1324
- * The local write time of the batch, stored as milliseconds since the
1325
- * epoch.
1326
- */
1327
- localWriteTimeMs,
1328
- /**
1329
- * A list of "mutations" that represent a partial base state from when this
1330
- * write batch was initially created. During local application of the write
1331
- * batch, these baseMutations are applied prior to the real writes in order
1332
- * to override certain document fields from the remote document cache. This
1333
- * is necessary in the case of non-idempotent writes (e.g. `increment()`
1334
- * transforms) to make sure that the local view of the modified documents
1335
- * doesn't flicker if the remote document cache receives the result of the
1336
- * non-idempotent write before the write is removed from the queue.
1337
- *
1338
- * These mutations are never sent to the backend.
1339
- */
1340
- baseMutations,
1341
- /**
1342
- * A list of mutations to apply. All mutations will be applied atomically.
1343
- *
1344
- * Mutations are serialized via toMutation().
1345
- */
1346
- mutations) {
1347
- this.userId = userId;
1348
- this.batchId = batchId;
1349
- this.localWriteTimeMs = localWriteTimeMs;
1350
- this.baseMutations = baseMutations;
1351
- this.mutations = mutations;
1352
- }
1353
- }
1235
+ const DbMutationQueueKeyPath = 'userId';
1354
1236
  /** Name of the IndexedDb object store. */
1355
- DbMutationBatch.store = 'mutations';
1237
+ const DbMutationBatchStore = 'mutations';
1356
1238
  /** Keys are automatically assigned via the userId, batchId properties. */
1357
- DbMutationBatch.keyPath = 'batchId';
1239
+ const DbMutationBatchKeyPath = 'batchId';
1358
1240
  /** The index name for lookup of mutations by user. */
1359
- DbMutationBatch.userMutationsIndex = 'userMutationsIndex';
1241
+ const DbMutationBatchUserMutationsIndex = 'userMutationsIndex';
1360
1242
  /** The user mutations index is keyed by [userId, batchId] pairs. */
1361
- DbMutationBatch.userMutationsKeyPath = ['userId', 'batchId'];
1243
+ const DbMutationBatchUserMutationsKeyPath = ['userId', 'batchId'];
1362
1244
  /**
1363
- * An object to be stored in the 'documentMutations' store in IndexedDb.
1364
- *
1365
- * A manually maintained index of all the mutation batches that affect a given
1366
- * document key. The rows in this table are references based on the contents of
1367
- * DbMutationBatch.mutations.
1245
+ * Creates a [userId] key for use in the DbDocumentMutations index to iterate
1246
+ * over all of a user's document mutations.
1368
1247
  */
1369
- class DbDocumentMutation {
1370
- constructor() { }
1371
- /**
1372
- * Creates a [userId] key for use in the DbDocumentMutations index to iterate
1373
- * over all of a user's document mutations.
1374
- */
1375
- static prefixForUser(userId) {
1376
- return [userId];
1377
- }
1378
- /**
1379
- * Creates a [userId, encodedPath] key for use in the DbDocumentMutations
1380
- * index to iterate over all at document mutations for a given path or lower.
1381
- */
1382
- static prefixForPath(userId, path) {
1383
- return [userId, encodeResourcePath(path)];
1384
- }
1385
- /**
1386
- * Creates a full index key of [userId, encodedPath, batchId] for inserting
1387
- * and deleting into the DbDocumentMutations index.
1388
- */
1389
- static key(userId, path, batchId) {
1390
- return [userId, encodeResourcePath(path), batchId];
1391
- }
1248
+ function newDbDocumentMutationPrefixForUser(userId) {
1249
+ return [userId];
1392
1250
  }
1393
- DbDocumentMutation.store = 'documentMutations';
1394
1251
  /**
1395
- * Because we store all the useful information for this store in the key,
1396
- * there is no useful information to store as the value. The raw (unencoded)
1397
- * path cannot be stored because IndexedDb doesn't store prototype
1398
- * information.
1399
- */
1400
- DbDocumentMutation.PLACEHOLDER = new DbDocumentMutation();
1401
- /**
1402
- * Represents the known absence of a document at a particular version.
1403
- * Stored in IndexedDb as part of a DbRemoteDocument object.
1252
+ * Creates a [userId, encodedPath] key for use in the DbDocumentMutations
1253
+ * index to iterate over all at document mutations for a given path or lower.
1404
1254
  */
1405
- class DbNoDocument {
1406
- constructor(path, readTime) {
1407
- this.path = path;
1408
- this.readTime = readTime;
1409
- }
1255
+ function newDbDocumentMutationPrefixForPath(userId, path) {
1256
+ return [userId, encodeResourcePath(path)];
1410
1257
  }
1411
1258
  /**
1412
- * Represents a document that is known to exist but whose data is unknown.
1413
- * Stored in IndexedDb as part of a DbRemoteDocument object.
1259
+ * Creates a full index key of [userId, encodedPath, batchId] for inserting
1260
+ * and deleting into the DbDocumentMutations index.
1414
1261
  */
1415
- class DbUnknownDocument {
1416
- constructor(path, version) {
1417
- this.path = path;
1418
- this.version = version;
1419
- }
1262
+ function newDbDocumentMutationKey(userId, path, batchId) {
1263
+ return [userId, encodeResourcePath(path), batchId];
1420
1264
  }
1421
1265
  /**
1422
- * An object to be stored in the 'remoteDocuments' store in IndexedDb.
1423
- * It represents either:
1424
- *
1425
- * - A complete document.
1426
- * - A "no document" representing a document that is known not to exist (at
1427
- * some version).
1428
- * - An "unknown document" representing a document that is known to exist (at
1429
- * some version) but whose contents are unknown.
1430
- *
1431
- * Note: This is the persisted equivalent of a MaybeDocument and could perhaps
1432
- * be made more general if necessary.
1266
+ * Because we store all the useful information for this store in the key,
1267
+ * there is no useful information to store as the value. The raw (unencoded)
1268
+ * path cannot be stored because IndexedDb doesn't store prototype
1269
+ * information.
1433
1270
  */
1434
- class DbRemoteDocument {
1435
- // TODO: We are currently storing full document keys almost three times
1436
- // (once as part of the primary key, once - partly - as `parentPath` and once
1437
- // inside the encoded documents). During our next migration, we should
1438
- // rewrite the primary key as parentPath + document ID which would allow us
1439
- // to drop one value.
1440
- constructor(
1441
- /**
1442
- * Set to an instance of DbUnknownDocument if the data for a document is
1443
- * not known, but it is known that a document exists at the specified
1444
- * version (e.g. it had a successful update applied to it)
1445
- */
1446
- unknownDocument,
1447
- /**
1448
- * Set to an instance of a DbNoDocument if it is known that no document
1449
- * exists.
1450
- */
1451
- noDocument,
1452
- /**
1453
- * Set to an instance of a Document if there's a cached version of the
1454
- * document.
1455
- */
1456
- document,
1457
- /**
1458
- * Documents that were written to the remote document store based on
1459
- * a write acknowledgment are marked with `hasCommittedMutations`. These
1460
- * documents are potentially inconsistent with the backend's copy and use
1461
- * the write's commit version as their document version.
1462
- */
1463
- hasCommittedMutations,
1464
- /**
1465
- * When the document was read from the backend. Undefined for data written
1466
- * prior to schema version 9.
1467
- */
1468
- readTime,
1469
- /**
1470
- * The path of the collection this document is part of. Undefined for data
1471
- * written prior to schema version 9.
1472
- */
1473
- parentPath) {
1474
- this.unknownDocument = unknownDocument;
1475
- this.noDocument = noDocument;
1476
- this.document = document;
1477
- this.hasCommittedMutations = hasCommittedMutations;
1478
- this.readTime = readTime;
1479
- this.parentPath = parentPath;
1480
- }
1481
- }
1482
- DbRemoteDocument.store = 'remoteDocuments';
1271
+ const DbDocumentMutationPlaceholder = {};
1272
+ const DbDocumentMutationStore = 'documentMutations';
1273
+ const DbRemoteDocumentStore = 'remoteDocuments';
1483
1274
  /**
1484
1275
  * An index that provides access to all entries sorted by read time (which
1485
1276
  * corresponds to the last modification time of each row).
1486
1277
  *
1487
1278
  * This index is used to provide a changelog for Multi-Tab.
1488
1279
  */
1489
- DbRemoteDocument.readTimeIndex = 'readTimeIndex';
1490
- DbRemoteDocument.readTimeIndexPath = 'readTime';
1280
+ const DbRemoteDocumentReadTimeIndex = 'readTimeIndex';
1281
+ const DbRemoteDocumentReadTimeIndexPath = 'readTime';
1491
1282
  /**
1492
1283
  * An index that provides access to documents in a collection sorted by read
1493
1284
  * time.
@@ -1495,361 +1286,68 @@ DbRemoteDocument.readTimeIndexPath = 'readTime';
1495
1286
  * This index is used to allow the RemoteDocumentCache to fetch newly changed
1496
1287
  * documents in a collection.
1497
1288
  */
1498
- DbRemoteDocument.collectionReadTimeIndex = 'collectionReadTimeIndex';
1499
- DbRemoteDocument.collectionReadTimeIndexPath = ['parentPath', 'readTime'];
1500
- /**
1501
- * Contains a single entry that has metadata about the remote document cache.
1502
- */
1503
- class DbRemoteDocumentGlobal {
1504
- /**
1505
- * @param byteSize - Approximately the total size in bytes of all the
1506
- * documents in the document cache.
1507
- */
1508
- constructor(byteSize) {
1509
- this.byteSize = byteSize;
1510
- }
1511
- }
1512
- DbRemoteDocumentGlobal.store = 'remoteDocumentGlobal';
1513
- DbRemoteDocumentGlobal.key = 'remoteDocumentGlobalKey';
1514
- /**
1515
- * An object to be stored in the 'targets' store in IndexedDb.
1516
- *
1517
- * This is based on and should be kept in sync with the proto used in the iOS
1518
- * client.
1519
- *
1520
- * Each query the client listens to against the server is tracked on disk so
1521
- * that the query can be efficiently resumed on restart.
1522
- */
1523
- class DbTarget {
1524
- constructor(
1525
- /**
1526
- * An auto-generated sequential numeric identifier for the query.
1527
- *
1528
- * Queries are stored using their canonicalId as the key, but these
1529
- * canonicalIds can be quite long so we additionally assign a unique
1530
- * queryId which can be used by referenced data structures (e.g.
1531
- * indexes) to minimize the on-disk cost.
1532
- */
1533
- targetId,
1534
- /**
1535
- * The canonical string representing this query. This is not unique.
1536
- */
1537
- canonicalId,
1538
- /**
1539
- * The last readTime received from the Watch Service for this query.
1540
- *
1541
- * This is the same value as TargetChange.read_time in the protos.
1542
- */
1543
- readTime,
1544
- /**
1545
- * An opaque, server-assigned token that allows watching a query to be
1546
- * resumed after disconnecting without retransmitting all the data
1547
- * that matches the query. The resume token essentially identifies a
1548
- * point in time from which the server should resume sending results.
1549
- *
1550
- * This is related to the snapshotVersion in that the resumeToken
1551
- * effectively also encodes that value, but the resumeToken is opaque
1552
- * and sometimes encodes additional information.
1553
- *
1554
- * A consequence of this is that the resumeToken should be used when
1555
- * asking the server to reason about where this client is in the watch
1556
- * stream, but the client should use the snapshotVersion for its own
1557
- * purposes.
1558
- *
1559
- * This is the same value as TargetChange.resume_token in the protos.
1560
- */
1561
- resumeToken,
1562
- /**
1563
- * A sequence number representing the last time this query was
1564
- * listened to, used for garbage collection purposes.
1565
- *
1566
- * Conventionally this would be a timestamp value, but device-local
1567
- * clocks are unreliable and they must be able to create new listens
1568
- * even while disconnected. Instead this should be a monotonically
1569
- * increasing number that's incremented on each listen call.
1570
- *
1571
- * This is different from the queryId since the queryId is an
1572
- * immutable identifier assigned to the Query on first use while
1573
- * lastListenSequenceNumber is updated every time the query is
1574
- * listened to.
1575
- */
1576
- lastListenSequenceNumber,
1577
- /**
1578
- * Denotes the maximum snapshot version at which the associated query view
1579
- * contained no limbo documents. Undefined for data written prior to
1580
- * schema version 9.
1581
- */
1582
- lastLimboFreeSnapshotVersion,
1583
- /**
1584
- * The query for this target.
1585
- *
1586
- * Because canonical ids are not unique we must store the actual query. We
1587
- * use the proto to have an object we can persist without having to
1588
- * duplicate translation logic to and from a `Query` object.
1589
- */
1590
- query) {
1591
- this.targetId = targetId;
1592
- this.canonicalId = canonicalId;
1593
- this.readTime = readTime;
1594
- this.resumeToken = resumeToken;
1595
- this.lastListenSequenceNumber = lastListenSequenceNumber;
1596
- this.lastLimboFreeSnapshotVersion = lastLimboFreeSnapshotVersion;
1597
- this.query = query;
1598
- }
1599
- }
1600
- DbTarget.store = 'targets';
1289
+ const DbRemoteDocumentCollectionReadTimeIndex = 'collectionReadTimeIndex';
1290
+ const DbRemoteDocumentCollectionReadTimeIndexPath = [
1291
+ 'parentPath',
1292
+ 'readTime'
1293
+ ];
1294
+ const DbRemoteDocumentGlobalStore = 'remoteDocumentGlobal';
1295
+ const DbRemoteDocumentGlobalKey = 'remoteDocumentGlobalKey';
1296
+ const DbTargetStore = 'targets';
1601
1297
  /** Keys are automatically assigned via the targetId property. */
1602
- DbTarget.keyPath = 'targetId';
1298
+ const DbTargetKeyPath = 'targetId';
1603
1299
  /** The name of the queryTargets index. */
1604
- DbTarget.queryTargetsIndexName = 'queryTargetsIndex';
1300
+ const DbTargetQueryTargetsIndexName = 'queryTargetsIndex';
1605
1301
  /**
1606
1302
  * The index of all canonicalIds to the targets that they match. This is not
1607
1303
  * a unique mapping because canonicalId does not promise a unique name for all
1608
1304
  * possible queries, so we append the targetId to make the mapping unique.
1609
1305
  */
1610
- DbTarget.queryTargetsKeyPath = ['canonicalId', 'targetId'];
1611
- /**
1612
- * An object representing an association between a target and a document, or a
1613
- * sentinel row marking the last sequence number at which a document was used.
1614
- * Each document cached must have a corresponding sentinel row before lru
1615
- * garbage collection is enabled.
1616
- *
1617
- * The target associations and sentinel rows are co-located so that orphaned
1618
- * documents and their sequence numbers can be identified efficiently via a scan
1619
- * of this store.
1620
- */
1621
- class DbTargetDocument {
1622
- constructor(
1623
- /**
1624
- * The targetId identifying a target or 0 for a sentinel row.
1625
- */
1626
- targetId,
1627
- /**
1628
- * The path to the document, as encoded in the key.
1629
- */
1630
- path,
1631
- /**
1632
- * If this is a sentinel row, this should be the sequence number of the last
1633
- * time the document specified by `path` was used. Otherwise, it should be
1634
- * `undefined`.
1635
- */
1636
- sequenceNumber) {
1637
- this.targetId = targetId;
1638
- this.path = path;
1639
- this.sequenceNumber = sequenceNumber;
1640
- }
1641
- }
1306
+ const DbTargetQueryTargetsKeyPath = ['canonicalId', 'targetId'];
1642
1307
  /** Name of the IndexedDb object store. */
1643
- DbTargetDocument.store = 'targetDocuments';
1308
+ const DbTargetDocumentStore = 'targetDocuments';
1644
1309
  /** Keys are automatically assigned via the targetId, path properties. */
1645
- DbTargetDocument.keyPath = ['targetId', 'path'];
1310
+ const DbTargetDocumentKeyPath = ['targetId', 'path'];
1646
1311
  /** The index name for the reverse index. */
1647
- DbTargetDocument.documentTargetsIndex = 'documentTargetsIndex';
1312
+ const DbTargetDocumentDocumentTargetsIndex = 'documentTargetsIndex';
1648
1313
  /** We also need to create the reverse index for these properties. */
1649
- DbTargetDocument.documentTargetsKeyPath = ['path', 'targetId'];
1650
- /**
1651
- * A record of global state tracked across all Targets, tracked separately
1652
- * to avoid the need for extra indexes.
1653
- *
1654
- * This should be kept in-sync with the proto used in the iOS client.
1655
- */
1656
- class DbTargetGlobal {
1657
- constructor(
1658
- /**
1659
- * The highest numbered target id across all targets.
1660
- *
1661
- * See DbTarget.targetId.
1662
- */
1663
- highestTargetId,
1664
- /**
1665
- * The highest numbered lastListenSequenceNumber across all targets.
1666
- *
1667
- * See DbTarget.lastListenSequenceNumber.
1668
- */
1669
- highestListenSequenceNumber,
1670
- /**
1671
- * A global snapshot version representing the last consistent snapshot we
1672
- * received from the backend. This is monotonically increasing and any
1673
- * snapshots received from the backend prior to this version (e.g. for
1674
- * targets resumed with a resumeToken) should be suppressed (buffered)
1675
- * until the backend has caught up to this snapshot version again. This
1676
- * prevents our cache from ever going backwards in time.
1677
- */
1678
- lastRemoteSnapshotVersion,
1679
- /**
1680
- * The number of targets persisted.
1681
- */
1682
- targetCount) {
1683
- this.highestTargetId = highestTargetId;
1684
- this.highestListenSequenceNumber = highestListenSequenceNumber;
1685
- this.lastRemoteSnapshotVersion = lastRemoteSnapshotVersion;
1686
- this.targetCount = targetCount;
1687
- }
1688
- }
1314
+ const DbTargetDocumentDocumentTargetsKeyPath = ['path', 'targetId'];
1689
1315
  /**
1690
1316
  * The key string used for the single object that exists in the
1691
1317
  * DbTargetGlobal store.
1692
1318
  */
1693
- DbTargetGlobal.key = 'targetGlobalKey';
1694
- DbTargetGlobal.store = 'targetGlobal';
1695
- /**
1696
- * An object representing an association between a Collection id (e.g. 'messages')
1697
- * to a parent path (e.g. '/chats/123') that contains it as a (sub)collection.
1698
- * This is used to efficiently find all collections to query when performing
1699
- * a Collection Group query.
1700
- */
1701
- class DbCollectionParent {
1702
- constructor(
1703
- /**
1704
- * The collectionId (e.g. 'messages')
1705
- */
1706
- collectionId,
1707
- /**
1708
- * The path to the parent (either a document location or an empty path for
1709
- * a root-level collection).
1710
- */
1711
- parent) {
1712
- this.collectionId = collectionId;
1713
- this.parent = parent;
1714
- }
1715
- }
1319
+ const DbTargetGlobalKey = 'targetGlobalKey';
1320
+ const DbTargetGlobalStore = 'targetGlobal';
1716
1321
  /** Name of the IndexedDb object store. */
1717
- DbCollectionParent.store = 'collectionParents';
1322
+ const DbCollectionParentStore = 'collectionParents';
1718
1323
  /** Keys are automatically assigned via the collectionId, parent properties. */
1719
- DbCollectionParent.keyPath = ['collectionId', 'parent'];
1720
- /**
1721
- * A record of the metadata state of each client.
1722
- *
1723
- * PORTING NOTE: This is used to synchronize multi-tab state and does not need
1724
- * to be ported to iOS or Android.
1725
- */
1726
- class DbClientMetadata {
1727
- constructor(
1728
- // Note: Previous schema versions included a field
1729
- // "lastProcessedDocumentChangeId". Don't use anymore.
1730
- /** The auto-generated client id assigned at client startup. */
1731
- clientId,
1732
- /** The last time this state was updated. */
1733
- updateTimeMs,
1734
- /** Whether the client's network connection is enabled. */
1735
- networkEnabled,
1736
- /** Whether this client is running in a foreground tab. */
1737
- inForeground) {
1738
- this.clientId = clientId;
1739
- this.updateTimeMs = updateTimeMs;
1740
- this.networkEnabled = networkEnabled;
1741
- this.inForeground = inForeground;
1742
- }
1743
- }
1324
+ const DbCollectionParentKeyPath = ['collectionId', 'parent'];
1744
1325
  /** Name of the IndexedDb object store. */
1745
- DbClientMetadata.store = 'clientMetadata';
1326
+ const DbClientMetadataStore = 'clientMetadata';
1746
1327
  /** Keys are automatically assigned via the clientId properties. */
1747
- DbClientMetadata.keyPath = 'clientId';
1748
- /** An object representing a bundle loaded by the SDK. */
1749
- class DbBundle {
1750
- constructor(
1751
- /** The ID of the loaded bundle. */
1752
- bundleId,
1753
- /** The create time of the loaded bundle. */
1754
- createTime,
1755
- /** The schema version of the loaded bundle. */
1756
- version) {
1757
- this.bundleId = bundleId;
1758
- this.createTime = createTime;
1759
- this.version = version;
1760
- }
1761
- }
1328
+ const DbClientMetadataKeyPath = 'clientId';
1762
1329
  /** Name of the IndexedDb object store. */
1763
- DbBundle.store = 'bundles';
1764
- DbBundle.keyPath = 'bundleId';
1765
- /** An object representing a named query loaded by the SDK via a bundle. */
1766
- class DbNamedQuery {
1767
- constructor(
1768
- /** The name of the query. */
1769
- name,
1770
- /** The read time of the results saved in the bundle from the named query. */
1771
- readTime,
1772
- /** The query saved in the bundle. */
1773
- bundledQuery) {
1774
- this.name = name;
1775
- this.readTime = readTime;
1776
- this.bundledQuery = bundledQuery;
1777
- }
1778
- }
1330
+ const DbBundleStore = 'bundles';
1331
+ const DbBundleKeyPath = 'bundleId';
1779
1332
  /** Name of the IndexedDb object store. */
1780
- DbNamedQuery.store = 'namedQueries';
1781
- DbNamedQuery.keyPath = 'name';
1782
- /** An object representing the global configuration for a field index. */
1783
- class DbIndexConfiguration {
1784
- constructor(
1785
- /**
1786
- * The index id for this entry. Undefined for indexes that are not yet
1787
- * persisted.
1788
- */
1789
- indexId,
1790
- /** The collection group this index belongs to. */
1791
- collectionGroup,
1792
- /** The fields to index for this index. */
1793
- fields) {
1794
- this.indexId = indexId;
1795
- this.collectionGroup = collectionGroup;
1796
- this.fields = fields;
1797
- }
1798
- }
1333
+ const DbNamedQueryStore = 'namedQueries';
1334
+ const DbNamedQueryKeyPath = 'name';
1799
1335
  /** Name of the IndexedDb object store. */
1800
- DbIndexConfiguration.store = 'indexConfiguration';
1801
- DbIndexConfiguration.keyPath = 'indexId';
1336
+ const DbIndexConfigurationStore = 'indexConfiguration';
1337
+ const DbIndexConfigurationKeyPath = 'indexId';
1802
1338
  /**
1803
- * An index that provides access to the index configurations by collection
1804
- * group.
1805
- *
1806
- * PORTING NOTE: iOS and Android maintain this index in-memory, but this is
1807
- * not possible here as the Web client supports concurrent access to
1808
- * persistence via multi-tab.
1809
- */
1810
- DbIndexConfiguration.collectionGroupIndex = 'collectionGroupIndex';
1811
- DbIndexConfiguration.collectionGroupIndexPath = 'collectionGroup';
1812
- /**
1813
- * An object describing how up-to-date the index backfill is for each user and
1814
- * index.
1815
- */
1816
- class DbIndexState {
1817
- constructor(
1818
- /** The index id for this entry. */
1819
- indexId,
1820
- /** The user id for this entry. */
1821
- uid,
1822
- /**
1823
- * A number that indicates when the index was last updated (relative to
1824
- * other indexes).
1825
- */
1826
- sequenceNumber,
1827
- /**
1828
- * The latest read time that has been indexed by Firestore for this field
1829
- * index. Set to `{seconds: 0, nanos: 0}` if no documents have been indexed.
1830
- */
1831
- readTime,
1832
- /**
1833
- * The last document that has been indexed for this field index. Empty if
1834
- * no documents have been indexed.
1835
- */
1836
- documentKey,
1837
- /**
1838
- * The largest mutation batch id that has been processed for this index. -1
1839
- * if no mutations have been indexed.
1840
- */
1841
- largestBatchId) {
1842
- this.indexId = indexId;
1843
- this.uid = uid;
1844
- this.sequenceNumber = sequenceNumber;
1845
- this.readTime = readTime;
1846
- this.documentKey = documentKey;
1847
- this.largestBatchId = largestBatchId;
1848
- }
1849
- }
1339
+ * An index that provides access to the index configurations by collection
1340
+ * group.
1341
+ *
1342
+ * PORTING NOTE: iOS and Android maintain this index in-memory, but this is
1343
+ * not possible here as the Web client supports concurrent access to
1344
+ * persistence via multi-tab.
1345
+ */
1346
+ const DbIndexConfigurationCollectionGroupIndex = 'collectionGroupIndex';
1347
+ const DbIndexConfigurationCollectionGroupIndexPath = 'collectionGroup';
1850
1348
  /** Name of the IndexedDb object store. */
1851
- DbIndexState.store = 'indexState';
1852
- DbIndexState.keyPath = ['indexId', 'uid'];
1349
+ const DbIndexStateStore = 'indexState';
1350
+ const DbIndexStateKeyPath = ['indexId', 'uid'];
1853
1351
  /**
1854
1352
  * An index that provides access to documents in a collection sorted by last
1855
1353
  * update time. Used by the backfiller.
@@ -1858,108 +1356,66 @@ DbIndexState.keyPath = ['indexId', 'uid'];
1858
1356
  * not possible here as the Web client supports concurrent access to
1859
1357
  * persistence via multi-tab.
1860
1358
  */
1861
- DbIndexState.sequenceNumberIndex = 'sequenceNumberIndex';
1862
- DbIndexState.sequenceNumberIndexPath = ['uid', 'sequenceNumber'];
1863
- /** An object that stores the encoded entries for all documents and fields. */
1864
- class DbIndexEntry {
1865
- constructor(
1866
- /** The index id for this entry. */
1867
- indexId,
1868
- /** The user id for this entry. */
1869
- uid,
1870
- /** The encoded array index value for this entry. */
1871
- arrayValue,
1872
- /** The encoded directional value for equality and inequality filters. */
1873
- directionalValue,
1874
- /** The document key this entry points to. */
1875
- documentKey) {
1876
- this.indexId = indexId;
1877
- this.uid = uid;
1878
- this.arrayValue = arrayValue;
1879
- this.directionalValue = directionalValue;
1880
- this.documentKey = documentKey;
1881
- }
1882
- }
1359
+ const DbIndexStateSequenceNumberIndex = 'sequenceNumberIndex';
1360
+ const DbIndexStateSequenceNumberIndexPath = ['uid', 'sequenceNumber'];
1883
1361
  /** Name of the IndexedDb object store. */
1884
- DbIndexEntry.store = 'indexEntries';
1885
- DbIndexEntry.keyPath = [
1362
+ const DbIndexEntryStore = 'indexEntries';
1363
+ const DbIndexEntryKeyPath = [
1886
1364
  'indexId',
1887
1365
  'uid',
1888
1366
  'arrayValue',
1889
1367
  'directionalValue',
1890
1368
  'documentKey'
1891
1369
  ];
1892
- DbIndexEntry.documentKeyIndex = 'documentKeyIndex';
1893
- DbIndexEntry.documentKeyIndexPath = ['indexId', 'uid', 'documentKey'];
1894
- /**
1895
- * An object representing a document overlay.
1896
- */
1897
- class DbDocumentOverlay {
1898
- constructor(
1899
- /** The user ID to whom this overlay belongs. */
1900
- userId,
1901
- /** The path to the collection that contains the document. */
1902
- collectionPath,
1903
- /** The ID (key) of the document within the collection. */
1904
- documentId,
1905
- /** The collection group to which the document belongs. */
1906
- collectionGroup,
1907
- /** The largest batch ID that's been applied for this overlay. */
1908
- largestBatchId,
1909
- /** The overlay mutation. */
1910
- overlayMutation) {
1911
- this.userId = userId;
1912
- this.collectionPath = collectionPath;
1913
- this.documentId = documentId;
1914
- this.collectionGroup = collectionGroup;
1915
- this.largestBatchId = largestBatchId;
1916
- this.overlayMutation = overlayMutation;
1917
- }
1918
- }
1370
+ const DbIndexEntryDocumentKeyIndex = 'documentKeyIndex';
1371
+ const DbIndexEntryDocumentKeyIndexPath = [
1372
+ 'indexId',
1373
+ 'uid',
1374
+ 'documentKey'
1375
+ ];
1919
1376
  /** Name of the IndexedDb object store. */
1920
- DbDocumentOverlay.store = 'documentOverlays';
1921
- DbDocumentOverlay.keyPath = ['userId', 'collectionPath', 'documentId'];
1922
- DbDocumentOverlay.collectionPathOverlayIndex = 'collectionPathOverlayIndex';
1923
- DbDocumentOverlay.collectionPathOverlayIndexPath = [
1377
+ const DbDocumentOverlayStore = 'documentOverlays';
1378
+ const DbDocumentOverlayKeyPath = [
1379
+ 'userId',
1380
+ 'collectionPath',
1381
+ 'documentId'
1382
+ ];
1383
+ const DbDocumentOverlayCollectionPathOverlayIndex = 'collectionPathOverlayIndex';
1384
+ const DbDocumentOverlayCollectionPathOverlayIndexPath = [
1924
1385
  'userId',
1925
1386
  'collectionPath',
1926
1387
  'largestBatchId'
1927
1388
  ];
1928
- DbDocumentOverlay.collectionGroupOverlayIndex = 'collectionGroupOverlayIndex';
1929
- DbDocumentOverlay.collectionGroupOverlayIndexPath = [
1389
+ const DbDocumentOverlayCollectionGroupOverlayIndex = 'collectionGroupOverlayIndex';
1390
+ const DbDocumentOverlayCollectionGroupOverlayIndexPath = [
1930
1391
  'userId',
1931
1392
  'collectionGroup',
1932
1393
  'largestBatchId'
1933
1394
  ];
1934
1395
  // Visible for testing
1935
1396
  const V1_STORES = [
1936
- DbMutationQueue.store,
1937
- DbMutationBatch.store,
1938
- DbDocumentMutation.store,
1939
- DbRemoteDocument.store,
1940
- DbTarget.store,
1941
- DbPrimaryClient.store,
1942
- DbTargetGlobal.store,
1943
- DbTargetDocument.store
1397
+ DbMutationQueueStore,
1398
+ DbMutationBatchStore,
1399
+ DbDocumentMutationStore,
1400
+ DbRemoteDocumentStore,
1401
+ DbTargetStore,
1402
+ DbPrimaryClientStore,
1403
+ DbTargetGlobalStore,
1404
+ DbTargetDocumentStore
1944
1405
  ];
1945
- // V2 is no longer usable (see comment at top of file)
1946
1406
  // Visible for testing
1947
1407
  const V3_STORES = V1_STORES;
1948
1408
  // Note: DbRemoteDocumentChanges is no longer used and dropped with v9.
1949
- const V4_STORES = [...V3_STORES, DbClientMetadata.store];
1950
- // V5 does not change the set of stores.
1951
- const V6_STORES = [...V4_STORES, DbRemoteDocumentGlobal.store];
1952
- // V7 does not change the set of stores.
1953
- const V8_STORES = [...V6_STORES, DbCollectionParent.store];
1954
- // V9 does not change the set of stores.
1955
- // V10 does not change the set of stores.
1956
- const V11_STORES = [...V8_STORES, DbBundle.store, DbNamedQuery.store];
1957
- const V12_STORES = [...V11_STORES, DbDocumentOverlay.store];
1409
+ const V4_STORES = [...V3_STORES, DbClientMetadataStore];
1410
+ const V6_STORES = [...V4_STORES, DbRemoteDocumentGlobalStore];
1411
+ const V8_STORES = [...V6_STORES, DbCollectionParentStore];
1412
+ const V11_STORES = [...V8_STORES, DbBundleStore, DbNamedQueryStore];
1413
+ const V12_STORES = [...V11_STORES, DbDocumentOverlayStore];
1958
1414
  const V13_STORES = [
1959
1415
  ...V12_STORES,
1960
- DbIndexConfiguration.store,
1961
- DbIndexState.store,
1962
- DbIndexEntry.store
1416
+ DbIndexConfigurationStore,
1417
+ DbIndexStateStore,
1418
+ DbIndexEntryStore
1963
1419
  ];
1964
1420
  /** Returns the object stores for the provided schema. */
1965
1421
  function getObjectStores(schemaVersion) {
@@ -2677,6 +2133,21 @@ class SimpleDbStore {
2677
2133
  });
2678
2134
  }
2679
2135
  }
2136
+ /**
2137
+ * Loads the first `count` elements from the provided index range. Loads all
2138
+ * elements if no limit is provided.
2139
+ */
2140
+ loadFirst(range, count) {
2141
+ const request = this.store.getAll(range, count === null ? undefined : count);
2142
+ return new PersistencePromise((resolve, reject) => {
2143
+ request.onerror = (event) => {
2144
+ reject(event.target.error);
2145
+ };
2146
+ request.onsuccess = (event) => {
2147
+ resolve(event.target.result);
2148
+ };
2149
+ });
2150
+ }
2680
2151
  deleteAll(indexOrRange, range) {
2681
2152
  logDebug(LOG_TAG$h, 'DELETE ALL', this.store.name);
2682
2153
  const options = this.options(indexOrRange, range);
@@ -3287,17 +2758,15 @@ class FieldMask {
3287
2758
  * See the License for the specific language governing permissions and
3288
2759
  * limitations under the License.
3289
2760
  */
2761
+ /** Converts a Base64 encoded string to a binary string. */
3290
2762
  function decodeBase64(encoded) {
3291
- // Node actually doesn't validate base64 strings.
3292
- // A quick sanity check that is not a fool-proof validation
3293
- if (/[^-A-Za-z0-9+/=]/.test(encoded)) {
3294
- throw new FirestoreError(Code.INVALID_ARGUMENT, 'Not a valid Base64 string: ' + encoded);
3295
- }
3296
- return new Buffer(encoded, 'base64').toString('binary');
2763
+ // Note: We used to validate the base64 string here via a regular expression.
2764
+ // This was removed to improve the performance of indexing.
2765
+ return Buffer.from(encoded, 'base64').toString('binary');
3297
2766
  }
3298
2767
  /** Converts a binary string to a Base64 encoded string. */
3299
2768
  function encodeBase64(raw) {
3300
- return new Buffer(raw, 'binary').toString('base64');
2769
+ return Buffer.from(raw, 'binary').toString('base64');
3301
2770
  }
3302
2771
  /** True if and only if the Base64 conversion functions are available. */
3303
2772
  function isBase64Available() {
@@ -3338,6 +2807,8 @@ class ByteString {
3338
2807
  return new ByteString(binaryString);
3339
2808
  }
3340
2809
  static fromUint8Array(array) {
2810
+ // TODO(indexing); Remove the copy of the byte string here as this method
2811
+ // is frequently called during indexing.
3341
2812
  const binaryString = binaryStringFromUint8Array(array);
3342
2813
  return new ByteString(binaryString);
3343
2814
  }
@@ -3756,13 +3227,17 @@ class DocumentKey {
3756
3227
  * See the License for the specific language governing permissions and
3757
3228
  * limitations under the License.
3758
3229
  */
3230
+ const MAX_VALUE_TYPE = '__max__';
3759
3231
  const MAX_VALUE = {
3760
3232
  mapValue: {
3761
3233
  fields: {
3762
- '__type__': { stringValue: '__max___' }
3234
+ '__type__': { stringValue: MAX_VALUE_TYPE }
3763
3235
  }
3764
3236
  }
3765
3237
  };
3238
+ const MIN_VALUE = {
3239
+ nullValue: 'NULL_VALUE'
3240
+ };
3766
3241
  /** Extracts the backend's type order for the provided value. */
3767
3242
  function typeOrder(value) {
3768
3243
  if ('nullValue' in value) {
@@ -3796,6 +3271,9 @@ function typeOrder(value) {
3796
3271
  if (isServerTimestamp(value)) {
3797
3272
  return 4 /* ServerTimestampValue */;
3798
3273
  }
3274
+ else if (isMaxValue(value)) {
3275
+ return 9 /* ArrayValue */;
3276
+ }
3799
3277
  return 10 /* ObjectValue */;
3800
3278
  }
3801
3279
  else {
@@ -3835,6 +3313,8 @@ function valueEquals(left, right) {
3835
3313
  return arrayEquals(left.arrayValue.values || [], right.arrayValue.values || [], valueEquals);
3836
3314
  case 10 /* ObjectValue */:
3837
3315
  return objectEquals(left, right);
3316
+ case 9007199254740991 /* MaxValue */:
3317
+ return true;
3838
3318
  default:
3839
3319
  return fail();
3840
3320
  }
@@ -3907,6 +3387,7 @@ function valueCompare(left, right) {
3907
3387
  }
3908
3388
  switch (leftType) {
3909
3389
  case 0 /* NullValue */:
3390
+ case 9007199254740991 /* MaxValue */:
3910
3391
  return 0;
3911
3392
  case 1 /* BooleanValue */:
3912
3393
  return primitiveComparator(left.booleanValue, right.booleanValue);
@@ -4175,7 +3656,102 @@ function deepClone(source) {
4175
3656
  }
4176
3657
  /** Returns true if the Value represents the canonical {@link #MAX_VALUE} . */
4177
3658
  function isMaxValue(value) {
4178
- return valueEquals(value, MAX_VALUE);
3659
+ return ((((value.mapValue || {}).fields || {})['__type__'] || {}).stringValue ===
3660
+ MAX_VALUE_TYPE);
3661
+ }
3662
+ /** Returns the lowest value for the given value type (inclusive). */
3663
+ function valuesGetLowerBound(value) {
3664
+ if ('nullValue' in value) {
3665
+ return MIN_VALUE;
3666
+ }
3667
+ else if ('booleanValue' in value) {
3668
+ return { booleanValue: false };
3669
+ }
3670
+ else if ('integerValue' in value || 'doubleValue' in value) {
3671
+ return { doubleValue: NaN };
3672
+ }
3673
+ else if ('timestampValue' in value) {
3674
+ return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
3675
+ }
3676
+ else if ('stringValue' in value) {
3677
+ return { stringValue: '' };
3678
+ }
3679
+ else if ('bytesValue' in value) {
3680
+ return { bytesValue: '' };
3681
+ }
3682
+ else if ('referenceValue' in value) {
3683
+ return refValue(DatabaseId.empty(), DocumentKey.empty());
3684
+ }
3685
+ else if ('geoPointValue' in value) {
3686
+ return { geoPointValue: { latitude: -90, longitude: -180 } };
3687
+ }
3688
+ else if ('arrayValue' in value) {
3689
+ return { arrayValue: {} };
3690
+ }
3691
+ else if ('mapValue' in value) {
3692
+ return { mapValue: {} };
3693
+ }
3694
+ else {
3695
+ return fail();
3696
+ }
3697
+ }
3698
+ /** Returns the largest value for the given value type (exclusive). */
3699
+ function valuesGetUpperBound(value) {
3700
+ if ('nullValue' in value) {
3701
+ return { booleanValue: false };
3702
+ }
3703
+ else if ('booleanValue' in value) {
3704
+ return { doubleValue: NaN };
3705
+ }
3706
+ else if ('integerValue' in value || 'doubleValue' in value) {
3707
+ return { timestampValue: { seconds: Number.MIN_SAFE_INTEGER } };
3708
+ }
3709
+ else if ('timestampValue' in value) {
3710
+ return { stringValue: '' };
3711
+ }
3712
+ else if ('stringValue' in value) {
3713
+ return { bytesValue: '' };
3714
+ }
3715
+ else if ('bytesValue' in value) {
3716
+ return refValue(DatabaseId.empty(), DocumentKey.empty());
3717
+ }
3718
+ else if ('referenceValue' in value) {
3719
+ return { geoPointValue: { latitude: -90, longitude: -180 } };
3720
+ }
3721
+ else if ('geoPointValue' in value) {
3722
+ return { arrayValue: {} };
3723
+ }
3724
+ else if ('arrayValue' in value) {
3725
+ return { mapValue: {} };
3726
+ }
3727
+ else if ('mapValue' in value) {
3728
+ return MAX_VALUE;
3729
+ }
3730
+ else {
3731
+ return fail();
3732
+ }
3733
+ }
3734
+ function valuesMax(left, right) {
3735
+ if (left === undefined) {
3736
+ return right;
3737
+ }
3738
+ else if (right === undefined) {
3739
+ return left;
3740
+ }
3741
+ else {
3742
+ return valueCompare(left, right) > 0 ? left : right;
3743
+ }
3744
+ }
3745
+ function valuesMin(left, right) {
3746
+ if (left === undefined) {
3747
+ return right;
3748
+ }
3749
+ else if (right === undefined) {
3750
+ return left;
3751
+ }
3752
+ else {
3753
+ return valueCompare(left, right) < 0 ? left : right;
3754
+ }
4179
3755
  }
4180
3756
 
4181
3757
  /**
@@ -4570,6 +4146,10 @@ function fieldIndexGetArraySegment(fieldIndex) {
4570
4146
  function fieldIndexGetDirectionalSegments(fieldIndex) {
4571
4147
  return fieldIndex.fields.filter(s => s.kind !== 2 /* CONTAINS */);
4572
4148
  }
4149
+ /** Returns a debug representation of the field index */
4150
+ function fieldIndexToString(fieldIndex) {
4151
+ return `id=${fieldIndex.indexId}|cg=${fieldIndex.collectionGroup}|f=${fieldIndex.fields.map(f => `${f.fieldPath}:${f.kind}`).join(',')}`;
4152
+ }
4573
4153
  /** An index component consisting of field path and index type. */
4574
4154
  class IndexSegment {
4575
4155
  constructor(
@@ -4766,6 +4346,191 @@ function targetIsDocumentTarget(target) {
4766
4346
  target.collectionGroup === null &&
4767
4347
  target.filters.length === 0);
4768
4348
  }
4349
+ /** Returns the field filters that target the given field path. */
4350
+ function targetGetFieldFiltersForPath(target, path) {
4351
+ return target.filters.filter(f => f instanceof FieldFilter && f.field.isEqual(path));
4352
+ }
4353
+ /**
4354
+ * Returns the values that are used in ARRAY_CONTAINS or ARRAY_CONTAINS_ANY
4355
+ * filters. Returns `null` if there are no such filters.
4356
+ */
4357
+ function targetGetArrayValues(target, fieldIndex) {
4358
+ const segment = fieldIndexGetArraySegment(fieldIndex);
4359
+ if (segment === undefined) {
4360
+ return null;
4361
+ }
4362
+ for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
4363
+ switch (fieldFilter.op) {
4364
+ case "array-contains-any" /* ARRAY_CONTAINS_ANY */:
4365
+ return fieldFilter.value.arrayValue.values || [];
4366
+ case "array-contains" /* ARRAY_CONTAINS */:
4367
+ return [fieldFilter.value];
4368
+ // Remaining filters are not array filters.
4369
+ }
4370
+ }
4371
+ return null;
4372
+ }
4373
+ /**
4374
+ * Returns the list of values that are used in != or NOT_IN filters. Returns
4375
+ * `null` if there are no such filters.
4376
+ */
4377
+ function targetGetNotInValues(target, fieldIndex) {
4378
+ const values = new Map();
4379
+ for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
4380
+ for (const fieldFilter of targetGetFieldFiltersForPath(target, segment.fieldPath)) {
4381
+ switch (fieldFilter.op) {
4382
+ case "==" /* EQUAL */:
4383
+ case "in" /* IN */:
4384
+ // Encode equality prefix, which is encoded in the index value before
4385
+ // the inequality (e.g. `a == 'a' && b != 'b'` is encoded to
4386
+ // `value != 'ab'`).
4387
+ values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
4388
+ break;
4389
+ case "not-in" /* NOT_IN */:
4390
+ case "!=" /* NOT_EQUAL */:
4391
+ // NotIn/NotEqual is always a suffix. There cannot be any remaining
4392
+ // segments and hence we can return early here.
4393
+ values.set(segment.fieldPath.canonicalString(), fieldFilter.value);
4394
+ return Array.from(values.values());
4395
+ // Remaining filters cannot be used as notIn bounds.
4396
+ }
4397
+ }
4398
+ }
4399
+ return null;
4400
+ }
4401
+ /**
4402
+ * Returns a lower bound of field values that can be used as a starting point to
4403
+ * scan the index defined by `fieldIndex`. Returns `null` if no lower bound
4404
+ * exists.
4405
+ */
4406
+ function targetGetLowerBound(target, fieldIndex) {
4407
+ const values = [];
4408
+ let inclusive = true;
4409
+ // For each segment, retrieve a lower bound if there is a suitable filter or
4410
+ // startAt.
4411
+ 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) {
4459
+ // No lower bound exists
4460
+ return null;
4461
+ }
4462
+ values.push(segmentValue);
4463
+ inclusive && (inclusive = segmentInclusive);
4464
+ }
4465
+ return new Bound(values, inclusive);
4466
+ }
4467
+ /**
4468
+ * Returns an upper bound of field values that can be used as an ending point
4469
+ * when scanning the index defined by `fieldIndex`. Returns `null` if no
4470
+ * upper bound exists.
4471
+ */
4472
+ function targetGetUpperBound(target, fieldIndex) {
4473
+ const values = [];
4474
+ let inclusive = true;
4475
+ // For each segment, retrieve an upper bound if there is a suitable filter or
4476
+ // endAt.
4477
+ 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) {
4526
+ // No upper bound exists
4527
+ return null;
4528
+ }
4529
+ values.push(segmentValue);
4530
+ inclusive && (inclusive = segmentInclusive);
4531
+ }
4532
+ return new Bound(values, inclusive);
4533
+ }
4769
4534
  class Filter {
4770
4535
  }
4771
4536
  class FieldFilter extends Filter {
@@ -5367,6 +5132,116 @@ function compareDocs(orderBy, d1, d2) {
5367
5132
  }
5368
5133
  }
5369
5134
 
5135
+ /**
5136
+ * @license
5137
+ * Copyright 2017 Google LLC
5138
+ *
5139
+ * Licensed under the Apache License, Version 2.0 (the "License");
5140
+ * you may not use this file except in compliance with the License.
5141
+ * You may obtain a copy of the License at
5142
+ *
5143
+ * http://www.apache.org/licenses/LICENSE-2.0
5144
+ *
5145
+ * Unless required by applicable law or agreed to in writing, software
5146
+ * distributed under the License is distributed on an "AS IS" BASIS,
5147
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
5148
+ * See the License for the specific language governing permissions and
5149
+ * limitations under the License.
5150
+ */
5151
+ /**
5152
+ * A map implementation that uses objects as keys. Objects must have an
5153
+ * associated equals function and must be immutable. Entries in the map are
5154
+ * stored together with the key being produced from the mapKeyFn. This map
5155
+ * automatically handles collisions of keys.
5156
+ */
5157
+ class ObjectMap {
5158
+ constructor(mapKeyFn, equalsFn) {
5159
+ this.mapKeyFn = mapKeyFn;
5160
+ this.equalsFn = equalsFn;
5161
+ /**
5162
+ * The inner map for a key/value pair. Due to the possibility of collisions we
5163
+ * keep a list of entries that we do a linear search through to find an actual
5164
+ * match. Note that collisions should be rare, so we still expect near
5165
+ * constant time lookups in practice.
5166
+ */
5167
+ this.inner = {};
5168
+ /** The number of entries stored in the map */
5169
+ this.innerSize = 0;
5170
+ }
5171
+ /** Get a value for this key, or undefined if it does not exist. */
5172
+ get(key) {
5173
+ const id = this.mapKeyFn(key);
5174
+ const matches = this.inner[id];
5175
+ if (matches === undefined) {
5176
+ return undefined;
5177
+ }
5178
+ for (const [otherKey, value] of matches) {
5179
+ if (this.equalsFn(otherKey, key)) {
5180
+ return value;
5181
+ }
5182
+ }
5183
+ return undefined;
5184
+ }
5185
+ has(key) {
5186
+ return this.get(key) !== undefined;
5187
+ }
5188
+ /** Put this key and value in the map. */
5189
+ set(key, value) {
5190
+ const id = this.mapKeyFn(key);
5191
+ const matches = this.inner[id];
5192
+ if (matches === undefined) {
5193
+ this.inner[id] = [[key, value]];
5194
+ this.innerSize++;
5195
+ return;
5196
+ }
5197
+ for (let i = 0; i < matches.length; i++) {
5198
+ if (this.equalsFn(matches[i][0], key)) {
5199
+ // This is updating an existing entry and does not increase `innerSize`.
5200
+ matches[i] = [key, value];
5201
+ return;
5202
+ }
5203
+ }
5204
+ matches.push([key, value]);
5205
+ this.innerSize++;
5206
+ }
5207
+ /**
5208
+ * Remove this key from the map. Returns a boolean if anything was deleted.
5209
+ */
5210
+ delete(key) {
5211
+ const id = this.mapKeyFn(key);
5212
+ const matches = this.inner[id];
5213
+ if (matches === undefined) {
5214
+ return false;
5215
+ }
5216
+ for (let i = 0; i < matches.length; i++) {
5217
+ if (this.equalsFn(matches[i][0], key)) {
5218
+ if (matches.length === 1) {
5219
+ delete this.inner[id];
5220
+ }
5221
+ else {
5222
+ matches.splice(i, 1);
5223
+ }
5224
+ this.innerSize--;
5225
+ return true;
5226
+ }
5227
+ }
5228
+ return false;
5229
+ }
5230
+ forEach(fn) {
5231
+ forEach(this.inner, (_, entries) => {
5232
+ for (const [k, v] of entries) {
5233
+ fn(k, v);
5234
+ }
5235
+ });
5236
+ }
5237
+ isEmpty() {
5238
+ return isEmpty(this.inner);
5239
+ }
5240
+ size() {
5241
+ return this.innerSize;
5242
+ }
5243
+ }
5244
+
5370
5245
  /**
5371
5246
  * @license
5372
5247
  * Copyright 2017 Google LLC
@@ -5509,7 +5384,7 @@ class SortedMapIterator {
5509
5384
  while (!node.isEmpty()) {
5510
5385
  cmp = startKey ? comparator(node.key, startKey) : 1;
5511
5386
  // flip the comparison if we're going in reverse
5512
- if (isReverse) {
5387
+ if (startKey && isReverse) {
5513
5388
  cmp *= -1;
5514
5389
  }
5515
5390
  if (cmp < 0) {
@@ -6078,6 +5953,9 @@ const EMPTY_DOCUMENT_MAP = new SortedMap(DocumentKey.comparator);
6078
5953
  function documentMap() {
6079
5954
  return EMPTY_DOCUMENT_MAP;
6080
5955
  }
5956
+ function newOverlayMap() {
5957
+ return new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
5958
+ }
6081
5959
  const EMPTY_DOCUMENT_VERSION_MAP = new SortedMap(DocumentKey.comparator);
6082
5960
  function documentVersionMap() {
6083
5961
  return EMPTY_DOCUMENT_VERSION_MAP;
@@ -8756,30 +8634,36 @@ function fromDbRemoteDocument(localSerializer, remoteDoc) {
8756
8634
  }
8757
8635
  /** Encodes a document for storage locally. */
8758
8636
  function toDbRemoteDocument(localSerializer, document) {
8759
- const dbReadTime = toDbTimestampKey(document.readTime);
8760
8637
  const parentPath = document.key.path.popLast().toArray();
8638
+ const readTime = toDbTimestampKey(document.readTime);
8761
8639
  if (document.isFoundDocument()) {
8762
8640
  const doc = toDocument(localSerializer.remoteSerializer, document);
8763
8641
  const hasCommittedMutations = document.hasCommittedMutations;
8764
- return new DbRemoteDocument(
8765
- /* unknownDocument= */ null,
8766
- /* noDocument= */ null, doc, hasCommittedMutations, dbReadTime, parentPath);
8642
+ return {
8643
+ document: doc,
8644
+ hasCommittedMutations,
8645
+ readTime,
8646
+ parentPath
8647
+ };
8767
8648
  }
8768
8649
  else if (document.isNoDocument()) {
8769
8650
  const path = document.key.path.toArray();
8770
- const version = toDbTimestamp(document.version);
8771
8651
  const hasCommittedMutations = document.hasCommittedMutations;
8772
- return new DbRemoteDocument(
8773
- /* unknownDocument= */ null, new DbNoDocument(path, version),
8774
- /* document= */ null, hasCommittedMutations, dbReadTime, parentPath);
8652
+ return {
8653
+ noDocument: { path, readTime: toDbTimestamp(document.version) },
8654
+ hasCommittedMutations,
8655
+ readTime,
8656
+ parentPath
8657
+ };
8775
8658
  }
8776
8659
  else if (document.isUnknownDocument()) {
8777
8660
  const path = document.key.path.toArray();
8778
- const readTime = toDbTimestamp(document.version);
8779
- return new DbRemoteDocument(new DbUnknownDocument(path, readTime),
8780
- /* noDocument= */ null,
8781
- /* document= */ null,
8782
- /* hasCommittedMutations= */ true, dbReadTime, parentPath);
8661
+ return {
8662
+ unknownDocument: { path, version: toDbTimestamp(document.version) },
8663
+ hasCommittedMutations: true,
8664
+ readTime,
8665
+ parentPath
8666
+ };
8783
8667
  }
8784
8668
  else {
8785
8669
  return fail();
@@ -8795,7 +8679,7 @@ function fromDbTimestampKey(dbTimestampKey) {
8795
8679
  }
8796
8680
  function toDbTimestamp(snapshotVersion) {
8797
8681
  const timestamp = snapshotVersion.toTimestamp();
8798
- return new DbTimestamp(timestamp.seconds, timestamp.nanoseconds);
8682
+ return { seconds: timestamp.seconds, nanoseconds: timestamp.nanoseconds };
8799
8683
  }
8800
8684
  function fromDbTimestamp(dbTimestamp) {
8801
8685
  const timestamp = new Timestamp(dbTimestamp.seconds, dbTimestamp.nanoseconds);
@@ -8805,7 +8689,13 @@ function fromDbTimestamp(dbTimestamp) {
8805
8689
  function toDbMutationBatch(localSerializer, userId, batch) {
8806
8690
  const serializedBaseMutations = batch.baseMutations.map(m => toMutation(localSerializer.remoteSerializer, m));
8807
8691
  const serializedMutations = batch.mutations.map(m => toMutation(localSerializer.remoteSerializer, m));
8808
- return new DbMutationBatch(userId, batch.batchId, batch.localWriteTime.toMillis(), serializedBaseMutations, serializedMutations);
8692
+ return {
8693
+ userId,
8694
+ batchId: batch.batchId,
8695
+ localWriteTimeMs: batch.localWriteTime.toMillis(),
8696
+ baseMutations: serializedBaseMutations,
8697
+ mutations: serializedMutations
8698
+ };
8809
8699
  }
8810
8700
  /** Decodes a DbMutationBatch into a MutationBatch */
8811
8701
  function fromDbMutationBatch(localSerializer, dbBatch) {
@@ -8861,7 +8751,15 @@ function toDbTarget(localSerializer, targetData) {
8861
8751
  // convert it to a base64 string for storage.
8862
8752
  const resumeToken = targetData.resumeToken.toBase64();
8863
8753
  // lastListenSequenceNumber is always 0 until we do real GC.
8864
- return new DbTarget(targetData.targetId, canonifyTarget(targetData.target), dbTimestamp, resumeToken, targetData.sequenceNumber, dbLastLimboFreeTimestamp, queryProto);
8754
+ return {
8755
+ targetId: targetData.targetId,
8756
+ canonicalId: canonifyTarget(targetData.target),
8757
+ readTime: dbTimestamp,
8758
+ resumeToken,
8759
+ lastListenSequenceNumber: targetData.sequenceNumber,
8760
+ lastLimboFreeSnapshotVersion: dbLastLimboFreeTimestamp,
8761
+ query: queryProto
8762
+ };
8865
8763
  }
8866
8764
  /**
8867
8765
  * A helper function for figuring out what kind of query has been stored.
@@ -8940,7 +8838,14 @@ function fromDbDocumentOverlay(localSerializer, dbDocumentOverlay) {
8940
8838
  /** Decodes an Overlay model object into a DbDocumentOverlay object. */
8941
8839
  function toDbDocumentOverlay(localSerializer, userId, overlay) {
8942
8840
  const [_, collectionPath, documentId] = toDbDocumentOverlayKey(userId, overlay.mutation.key);
8943
- return new DbDocumentOverlay(userId, collectionPath, documentId, overlay.mutation.key.getCollectionGroup(), overlay.largestBatchId, toMutation(localSerializer.remoteSerializer, overlay.mutation));
8841
+ return {
8842
+ userId,
8843
+ collectionPath,
8844
+ documentId,
8845
+ collectionGroup: overlay.mutation.key.getCollectionGroup(),
8846
+ largestBatchId: overlay.largestBatchId,
8847
+ overlayMutation: toMutation(localSerializer.remoteSerializer, overlay.mutation)
8848
+ };
8944
8849
  }
8945
8850
  /**
8946
8851
  * Returns the DbDocumentOverlayKey corresponding to the given user and
@@ -8952,7 +8857,11 @@ function toDbDocumentOverlayKey(userId, docKey) {
8952
8857
  return [userId, collectionPath, docId];
8953
8858
  }
8954
8859
  function toDbIndexConfiguration(index) {
8955
- return new DbIndexConfiguration(index.indexId, index.collectionGroup, index.fields.map(s => [s.fieldPath.canonicalString(), s.kind]));
8860
+ return {
8861
+ indexId: index.indexId,
8862
+ collectionGroup: index.collectionGroup,
8863
+ fields: index.fields.map(s => [s.fieldPath.canonicalString(), s.kind])
8864
+ };
8956
8865
  }
8957
8866
  function fromDbIndexConfiguration(index, state) {
8958
8867
  const decodedState = state
@@ -8962,7 +8871,14 @@ function fromDbIndexConfiguration(index, state) {
8962
8871
  return new FieldIndex(index.indexId, index.collectionGroup, decodedSegments, decodedState);
8963
8872
  }
8964
8873
  function toDbIndexState(indexId, user, sequenceNumber, offset) {
8965
- return new DbIndexState(indexId, user.uid || '', sequenceNumber, toDbTimestamp(offset.readTime), encodeResourcePath(offset.documentKey.path), offset.largestBatchId);
8874
+ return {
8875
+ indexId,
8876
+ uid: user.uid || '',
8877
+ sequenceNumber,
8878
+ readTime: toDbTimestamp(offset.readTime),
8879
+ documentKey: encodeResourcePath(offset.documentKey.path),
8880
+ largestBatchId: offset.largestBatchId
8881
+ };
8966
8882
  }
8967
8883
 
8968
8884
  /**
@@ -9013,13 +8929,13 @@ class IndexedDbBundleCache {
9013
8929
  * Helper to get a typed SimpleDbStore for the bundles object store.
9014
8930
  */
9015
8931
  function bundlesStore(txn) {
9016
- return getStore(txn, DbBundle.store);
8932
+ return getStore(txn, DbBundleStore);
9017
8933
  }
9018
8934
  /**
9019
8935
  * Helper to get a typed SimpleDbStore for the namedQueries object store.
9020
8936
  */
9021
8937
  function namedQueriesStore(txn) {
9022
- return getStore(txn, DbNamedQuery.store);
8938
+ return getStore(txn, DbNamedQueryStore);
9023
8939
  }
9024
8940
 
9025
8941
  /**
@@ -9066,7 +8982,7 @@ class IndexedDbDocumentOverlayCache {
9066
8982
  }
9067
8983
  saveOverlays(transaction, largestBatchId, overlays) {
9068
8984
  const promises = [];
9069
- overlays.forEach(mutation => {
8985
+ overlays.forEach((_, mutation) => {
9070
8986
  const overlay = new Overlay(largestBatchId, mutation);
9071
8987
  promises.push(this.saveOverlay(transaction, overlay));
9072
8988
  });
@@ -9081,19 +8997,19 @@ class IndexedDbDocumentOverlayCache {
9081
8997
  const range = IDBKeyRange.bound([this.userId, collectionPath, batchId], [this.userId, collectionPath, batchId + 1],
9082
8998
  /*lowerOpen=*/ false,
9083
8999
  /*upperOpen=*/ true);
9084
- promises.push(documentOverlayStore(transaction).deleteAll(DbDocumentOverlay.collectionPathOverlayIndex, range));
9000
+ promises.push(documentOverlayStore(transaction).deleteAll(DbDocumentOverlayCollectionPathOverlayIndex, range));
9085
9001
  });
9086
9002
  return PersistencePromise.waitFor(promises);
9087
9003
  }
9088
9004
  getOverlaysForCollection(transaction, collection, sinceBatchId) {
9089
- const result = new Map();
9005
+ const result = newOverlayMap();
9090
9006
  const collectionPath = encodeResourcePath(collection);
9091
9007
  // We want batch IDs larger than `sinceBatchId`, and so the lower bound
9092
9008
  // is not inclusive.
9093
9009
  const range = IDBKeyRange.bound([this.userId, collectionPath, sinceBatchId], [this.userId, collectionPath, Number.POSITIVE_INFINITY],
9094
9010
  /*lowerOpen=*/ true);
9095
9011
  return documentOverlayStore(transaction)
9096
- .loadAll(DbDocumentOverlay.collectionPathOverlayIndex, range)
9012
+ .loadAll(DbDocumentOverlayCollectionPathOverlayIndex, range)
9097
9013
  .next(dbOverlays => {
9098
9014
  for (const dbOverlay of dbOverlays) {
9099
9015
  const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
@@ -9103,7 +9019,7 @@ class IndexedDbDocumentOverlayCache {
9103
9019
  });
9104
9020
  }
9105
9021
  getOverlaysForCollectionGroup(transaction, collectionGroup, sinceBatchId, count) {
9106
- const result = new Map();
9022
+ const result = newOverlayMap();
9107
9023
  let currentBatchId = undefined;
9108
9024
  // We want batch IDs larger than `sinceBatchId`, and so the lower bound
9109
9025
  // is not inclusive.
@@ -9111,7 +9027,7 @@ class IndexedDbDocumentOverlayCache {
9111
9027
  /*lowerOpen=*/ true);
9112
9028
  return documentOverlayStore(transaction)
9113
9029
  .iterate({
9114
- index: DbDocumentOverlay.collectionGroupOverlayIndex,
9030
+ index: DbDocumentOverlayCollectionGroupOverlayIndex,
9115
9031
  range
9116
9032
  }, (_, dbOverlay, control) => {
9117
9033
  // We do not want to return partial batch overlays, even if the size
@@ -9119,7 +9035,7 @@ class IndexedDbDocumentOverlayCache {
9119
9035
  // continue to aggregate results even after the result size exceeds
9120
9036
  // `count` if there are more overlays from the `currentBatchId`.
9121
9037
  const overlay = fromDbDocumentOverlay(this.serializer, dbOverlay);
9122
- if (result.size < count ||
9038
+ if (result.size() < count ||
9123
9039
  overlay.largestBatchId === currentBatchId) {
9124
9040
  result.set(overlay.getKey(), overlay);
9125
9041
  currentBatchId = overlay.largestBatchId;
@@ -9138,7 +9054,7 @@ class IndexedDbDocumentOverlayCache {
9138
9054
  * Helper to get a typed SimpleDbStore for the document overlay object store.
9139
9055
  */
9140
9056
  function documentOverlayStore(txn) {
9141
- return getStore(txn, DbDocumentOverlay.store);
9057
+ return getStore(txn, DbDocumentOverlayStore);
9142
9058
  }
9143
9059
 
9144
9060
  /**
@@ -9694,21 +9610,40 @@ class IndexEntry {
9694
9610
  this.arrayValue = arrayValue;
9695
9611
  this.directionalValue = directionalValue;
9696
9612
  }
9613
+ /**
9614
+ * Returns an IndexEntry entry that sorts immediately after the current
9615
+ * directional value.
9616
+ */
9617
+ successor() {
9618
+ const currentLength = this.directionalValue.length;
9619
+ const newLength = currentLength === 0 || this.directionalValue[currentLength - 1] === 255
9620
+ ? currentLength + 1
9621
+ : currentLength;
9622
+ const successor = new Uint8Array(newLength);
9623
+ successor.set(this.directionalValue, 0);
9624
+ if (newLength !== currentLength) {
9625
+ successor.set([0], this.directionalValue.length);
9626
+ }
9627
+ else {
9628
+ ++successor[successor.length - 1];
9629
+ }
9630
+ return new IndexEntry(this.indexId, this.documentKey, this.arrayValue, successor);
9631
+ }
9697
9632
  }
9698
9633
  function indexEntryComparator(left, right) {
9699
9634
  let cmp = left.indexId - right.indexId;
9700
9635
  if (cmp !== 0) {
9701
9636
  return cmp;
9702
9637
  }
9703
- cmp = DocumentKey.comparator(left.documentKey, right.documentKey);
9638
+ cmp = compareByteArrays(left.arrayValue, right.arrayValue);
9704
9639
  if (cmp !== 0) {
9705
9640
  return cmp;
9706
9641
  }
9707
- cmp = compareByteArrays(left.arrayValue, right.arrayValue);
9642
+ cmp = compareByteArrays(left.directionalValue, right.directionalValue);
9708
9643
  if (cmp !== 0) {
9709
9644
  return cmp;
9710
9645
  }
9711
- return compareByteArrays(left.directionalValue, right.directionalValue);
9646
+ return DocumentKey.comparator(left.documentKey, right.documentKey);
9712
9647
  }
9713
9648
  function compareByteArrays(left, right) {
9714
9649
  for (let i = 0; i < left.length && i < right.length; ++i) {
@@ -9720,6 +9655,160 @@ function compareByteArrays(left, right) {
9720
9655
  return left.length - right.length;
9721
9656
  }
9722
9657
 
9658
+ /**
9659
+ * @license
9660
+ * Copyright 2022 Google LLC
9661
+ *
9662
+ * Licensed under the Apache License, Version 2.0 (the "License");
9663
+ * you may not use this file except in compliance with the License.
9664
+ * You may obtain a copy of the License at
9665
+ *
9666
+ * http://www.apache.org/licenses/LICENSE-2.0
9667
+ *
9668
+ * Unless required by applicable law or agreed to in writing, software
9669
+ * distributed under the License is distributed on an "AS IS" BASIS,
9670
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
9671
+ * See the License for the specific language governing permissions and
9672
+ * limitations under the License.
9673
+ */
9674
+ /**
9675
+ * A light query planner for Firestore.
9676
+ *
9677
+ * This class matches a `FieldIndex` against a Firestore Query `Target`. It
9678
+ * determines whether a given index can be used to serve the specified target.
9679
+ *
9680
+ * The following table showcases some possible index configurations:
9681
+ *
9682
+ * Query | Index
9683
+ * -----------------------------------------------------------------------------
9684
+ * where('a', '==', 'a').where('b', '==', 'b') | a ASC, b DESC
9685
+ * where('a', '==', 'a').where('b', '==', 'b') | a ASC
9686
+ * where('a', '==', 'a').where('b', '==', 'b') | b DESC
9687
+ * where('a', '>=', 'a').orderBy('a') | a ASC
9688
+ * where('a', '>=', 'a').orderBy('a', 'desc') | a DESC
9689
+ * where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC, b ASC
9690
+ * where('a', '>=', 'a').orderBy('a').orderBy('b') | a ASC
9691
+ * where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS, b ASCENDING
9692
+ * where('a', 'array-contains', 'a').orderBy('b') | a CONTAINS
9693
+ */
9694
+ class TargetIndexMatcher {
9695
+ constructor(target) {
9696
+ this.collectionId =
9697
+ target.collectionGroup != null
9698
+ ? target.collectionGroup
9699
+ : target.path.lastSegment();
9700
+ this.orderBys = target.orderBy;
9701
+ this.equalityFilters = [];
9702
+ for (const filter of target.filters) {
9703
+ const fieldFilter = filter;
9704
+ if (fieldFilter.isInequality()) {
9705
+ this.inequalityFilter = fieldFilter;
9706
+ }
9707
+ else {
9708
+ this.equalityFilters.push(fieldFilter);
9709
+ }
9710
+ }
9711
+ }
9712
+ /**
9713
+ * Returns whether the index can be used to serve the TargetIndexMatcher's
9714
+ * target.
9715
+ *
9716
+ * An index is considered capable of serving the target when:
9717
+ * - The target uses all index segments for its filters and orderBy clauses.
9718
+ * The target can have additional filter and orderBy clauses, but not
9719
+ * fewer.
9720
+ * - If an ArrayContains/ArrayContainsAnyfilter is used, the index must also
9721
+ * have a corresponding `CONTAINS` segment.
9722
+ * - All directional index segments can be mapped to the target as a series of
9723
+ * equality filters, a single inequality filter and a series of orderBy
9724
+ * clauses.
9725
+ * - The segments that represent the equality filters may appear out of order.
9726
+ * - The optional segment for the inequality filter must appear after all
9727
+ * equality segments.
9728
+ * - The segments that represent that orderBy clause of the target must appear
9729
+ * in order after all equality and inequality segments. Single orderBy
9730
+ * clauses cannot be skipped, but a continuous orderBy suffix may be
9731
+ * omitted.
9732
+ */
9733
+ servedByIndex(index) {
9734
+ // If there is an array element, find a matching filter.
9735
+ const arraySegment = fieldIndexGetArraySegment(index);
9736
+ if (arraySegment !== undefined &&
9737
+ !this.hasMatchingEqualityFilter(arraySegment)) {
9738
+ return false;
9739
+ }
9740
+ const segments = fieldIndexGetDirectionalSegments(index);
9741
+ let segmentIndex = 0;
9742
+ let orderBysIndex = 0;
9743
+ // Process all equalities first. Equalities can appear out of order.
9744
+ for (; segmentIndex < segments.length; ++segmentIndex) {
9745
+ // We attempt to greedily match all segments to equality filters. If a
9746
+ // filter matches an index segment, we can mark the segment as used.
9747
+ // Since it is not possible to use the same field path in both an equality
9748
+ // and inequality/oderBy clause, we do not have to consider the possibility
9749
+ // that a matching equality segment should instead be used to map to an
9750
+ // inequality filter or orderBy clause.
9751
+ if (!this.hasMatchingEqualityFilter(segments[segmentIndex])) {
9752
+ // If we cannot find a matching filter, we need to verify whether the
9753
+ // remaining segments map to the target's inequality and its orderBy
9754
+ // clauses.
9755
+ break;
9756
+ }
9757
+ }
9758
+ // If we already have processed all segments, all segments are used to serve
9759
+ // the equality filters and we do not need to map any segments to the
9760
+ // target's inequality and orderBy clauses.
9761
+ if (segmentIndex === segments.length) {
9762
+ return true;
9763
+ }
9764
+ // If there is an inequality filter, the next segment must match both the
9765
+ // filter and the first orderBy clause.
9766
+ if (this.inequalityFilter !== undefined) {
9767
+ const segment = segments[segmentIndex];
9768
+ if (!this.matchesFilter(this.inequalityFilter, segment) ||
9769
+ !this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
9770
+ return false;
9771
+ }
9772
+ ++segmentIndex;
9773
+ }
9774
+ // All remaining segments need to represent the prefix of the target's
9775
+ // orderBy.
9776
+ for (; segmentIndex < segments.length; ++segmentIndex) {
9777
+ const segment = segments[segmentIndex];
9778
+ if (orderBysIndex >= this.orderBys.length ||
9779
+ !this.matchesOrderBy(this.orderBys[orderBysIndex++], segment)) {
9780
+ return false;
9781
+ }
9782
+ }
9783
+ return true;
9784
+ }
9785
+ hasMatchingEqualityFilter(segment) {
9786
+ for (const filter of this.equalityFilters) {
9787
+ if (this.matchesFilter(filter, segment)) {
9788
+ return true;
9789
+ }
9790
+ }
9791
+ return false;
9792
+ }
9793
+ matchesFilter(filter, segment) {
9794
+ if (filter === undefined || !filter.field.isEqual(segment.fieldPath)) {
9795
+ return false;
9796
+ }
9797
+ const isArrayOperator = filter.op === "array-contains" /* ARRAY_CONTAINS */ ||
9798
+ filter.op === "array-contains-any" /* ARRAY_CONTAINS_ANY */;
9799
+ return (segment.kind === 2 /* CONTAINS */) === isArrayOperator;
9800
+ }
9801
+ matchesOrderBy(orderBy, segment) {
9802
+ if (!orderBy.field.isEqual(segment.fieldPath)) {
9803
+ return false;
9804
+ }
9805
+ return ((segment.kind === 0 /* ASCENDING */ &&
9806
+ orderBy.dir === "asc" /* ASCENDING */) ||
9807
+ (segment.kind === 1 /* DESCENDING */ &&
9808
+ orderBy.dir === "desc" /* DESCENDING */));
9809
+ }
9810
+ }
9811
+
9723
9812
  /**
9724
9813
  * @license
9725
9814
  * Copyright 2019 Google LLC
@@ -9758,9 +9847,9 @@ class MemoryIndexManager {
9758
9847
  // Field indices are not supported with memory persistence.
9759
9848
  return PersistencePromise.resolve();
9760
9849
  }
9761
- getDocumentsMatchingTarget(transaction, fieldIndex, target) {
9850
+ getDocumentsMatchingTarget(transaction, target) {
9762
9851
  // Field indices are not supported with memory persistence.
9763
- return PersistencePromise.resolve(documentKeySet());
9852
+ return PersistencePromise.resolve(null);
9764
9853
  }
9765
9854
  getFieldIndex(transaction, target) {
9766
9855
  // Field indices are not supported with memory persistence.
@@ -9832,6 +9921,7 @@ class MemoryCollectionParentIndex {
9832
9921
  * limitations under the License.
9833
9922
  */
9834
9923
  const LOG_TAG$f = 'IndexedDbIndexManager';
9924
+ const EMPTY_VALUE = new Uint8Array(0);
9835
9925
  /**
9836
9926
  * A persisted implementation of IndexManager.
9837
9927
  *
@@ -9849,6 +9939,11 @@ class IndexedDbIndexManager {
9849
9939
  * used to satisfy reads.
9850
9940
  */
9851
9941
  this.collectionParentsCache = new MemoryCollectionParentIndex();
9942
+ /**
9943
+ * Maps from a target to its equivalent list of sub-targets. Each sub-target
9944
+ * contains only one term from the target's disjunctive normal form (DNF).
9945
+ */
9946
+ this.targetToDnfSubTargets = new ObjectMap(t => canonifyTarget(t), (l, r) => targetEquals(l, r));
9852
9947
  this.uid = user.uid || '';
9853
9948
  }
9854
9949
  /**
@@ -9917,13 +10012,111 @@ class IndexedDbIndexManager {
9917
10012
  /*lowerOpen=*/ false,
9918
10013
  /*upperOpen=*/ true)));
9919
10014
  }
9920
- getDocumentsMatchingTarget(transaction, fieldIndex, target) {
9921
- // TODO(indexing): Implement
9922
- return PersistencePromise.resolve(documentKeySet());
10015
+ getDocumentsMatchingTarget(transaction, target) {
10016
+ const indexEntries = indexEntriesStore(transaction);
10017
+ let canServeTarget = true;
10018
+ const indexes = new Map();
10019
+ return PersistencePromise.forEach(this.getSubTargets(target), (subTarget) => {
10020
+ return this.getFieldIndex(transaction, subTarget).next(index => {
10021
+ canServeTarget && (canServeTarget = !!index);
10022
+ indexes.set(subTarget, index);
10023
+ });
10024
+ }).next(() => {
10025
+ if (!canServeTarget) {
10026
+ return PersistencePromise.resolve(null);
10027
+ }
10028
+ else {
10029
+ let result = documentKeySet();
10030
+ return PersistencePromise.forEach(indexes, (index, subTarget) => {
10031
+ logDebug(LOG_TAG$f, `Using index ${fieldIndexToString(index)} to execute ${canonifyTarget(target)}`);
10032
+ const arrayValues = targetGetArrayValues(subTarget, index);
10033
+ const notInValues = targetGetNotInValues(subTarget, index);
10034
+ const lowerBound = targetGetLowerBound(subTarget, index);
10035
+ const upperBound = targetGetUpperBound(subTarget, index);
10036
+ const lowerBoundEncoded = this.encodeBound(index, subTarget, lowerBound);
10037
+ const upperBoundEncoded = this.encodeBound(index, subTarget, upperBound);
10038
+ const notInEncoded = this.encodeValues(index, subTarget, notInValues);
10039
+ const indexRanges = this.generateIndexRanges(index.indexId, arrayValues, lowerBoundEncoded, !!lowerBound && lowerBound.inclusive, upperBoundEncoded, !!upperBound && upperBound.inclusive, notInEncoded);
10040
+ return PersistencePromise.forEach(indexRanges, (indexRange) => {
10041
+ return indexEntries
10042
+ .loadFirst(indexRange, target.limit)
10043
+ .next(entries => {
10044
+ entries.forEach(entry => {
10045
+ result = result.add(new DocumentKey(decodeResourcePath(entry.documentKey)));
10046
+ });
10047
+ });
10048
+ });
10049
+ }).next(() => result);
10050
+ }
10051
+ });
10052
+ }
10053
+ getSubTargets(target) {
10054
+ let subTargets = this.targetToDnfSubTargets.get(target);
10055
+ if (subTargets) {
10056
+ return subTargets;
10057
+ }
10058
+ // TODO(orquery): Implement DNF transform
10059
+ subTargets = [target];
10060
+ this.targetToDnfSubTargets.set(target, subTargets);
10061
+ return subTargets;
10062
+ }
10063
+ /**
10064
+ * Constructs a key range query on `DbIndexEntryStore` that unions all
10065
+ * bounds.
10066
+ */
10067
+ generateIndexRanges(indexId, arrayValues, lowerBounds, lowerBoundInclusive, upperBounds, upperBoundInclusive, notInValues) {
10068
+ // The number of total index scans we union together. This is similar to a
10069
+ // distributed normal form, but adapted for array values. We create a single
10070
+ // index range per value in an ARRAY_CONTAINS or ARRAY_CONTAINS_ANY filter
10071
+ // combined with the values from the query bounds.
10072
+ const totalScans = (arrayValues != null ? arrayValues.length : 1) *
10073
+ Math.max(lowerBounds != null ? lowerBounds.length : 1, upperBounds != null ? upperBounds.length : 1);
10074
+ const scansPerArrayElement = totalScans / (arrayValues != null ? arrayValues.length : 1);
10075
+ const indexRanges = [];
10076
+ for (let i = 0; i < totalScans; ++i) {
10077
+ const arrayValue = arrayValues
10078
+ ? this.encodeSingleElement(arrayValues[i / scansPerArrayElement])
10079
+ : EMPTY_VALUE;
10080
+ const lowerBound = lowerBounds
10081
+ ? this.generateLowerBound(indexId, arrayValue, lowerBounds[i % scansPerArrayElement], lowerBoundInclusive)
10082
+ : this.generateEmptyBound(indexId);
10083
+ const upperBound = upperBounds
10084
+ ? this.generateUpperBound(indexId, arrayValue, upperBounds[i % scansPerArrayElement], upperBoundInclusive)
10085
+ : this.generateEmptyBound(indexId + 1);
10086
+ indexRanges.push(...this.createRange(lowerBound, upperBound, notInValues.map((notIn // make non-nullable
10087
+ ) => this.generateLowerBound(indexId, arrayValue, notIn,
10088
+ /* inclusive= */ true))));
10089
+ }
10090
+ return indexRanges;
10091
+ }
10092
+ /** Generates the lower bound for `arrayValue` and `directionalValue`. */
10093
+ generateLowerBound(indexId, arrayValue, directionalValue, inclusive) {
10094
+ const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
10095
+ return inclusive ? entry : entry.successor();
10096
+ }
10097
+ /** Generates the upper bound for `arrayValue` and `directionalValue`. */
10098
+ generateUpperBound(indexId, arrayValue, directionalValue, inclusive) {
10099
+ const entry = new IndexEntry(indexId, DocumentKey.empty(), arrayValue, directionalValue);
10100
+ return inclusive ? entry.successor() : entry;
10101
+ }
10102
+ /**
10103
+ * Generates an empty bound that scopes the index scan to the current index
10104
+ * and user.
10105
+ */
10106
+ generateEmptyBound(indexId) {
10107
+ return new IndexEntry(indexId, DocumentKey.empty(), EMPTY_VALUE, EMPTY_VALUE);
9923
10108
  }
9924
10109
  getFieldIndex(transaction, target) {
9925
- // TODO(indexing): Implement
9926
- return PersistencePromise.resolve(null);
10110
+ const targetIndexMatcher = new TargetIndexMatcher(target);
10111
+ const collectionGroup = target.collectionGroup != null
10112
+ ? target.collectionGroup
10113
+ : target.path.lastSegment();
10114
+ return this.getFieldIndexes(transaction, collectionGroup).next(indexes => {
10115
+ const matchingIndexes = indexes.filter(i => targetIndexMatcher.servedByIndex(i));
10116
+ // Return the index that matches the most number of segments.
10117
+ matchingIndexes.sort((l, r) => r.fields.length - l.fields.length);
10118
+ return matchingIndexes.length > 0 ? matchingIndexes[0] : null;
10119
+ });
9927
10120
  }
9928
10121
  /**
9929
10122
  * Returns the byte encoded form of the directional values in the field index.
@@ -9948,11 +10141,79 @@ class IndexedDbIndexManager {
9948
10141
  FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, encoder.forKind(0 /* ASCENDING */));
9949
10142
  return encoder.encodedBytes();
9950
10143
  }
10144
+ /**
10145
+ * Encodes the given field values according to the specification in `target`.
10146
+ * For IN queries, a list of possible values is returned.
10147
+ */
10148
+ encodeValues(fieldIndex, target, bound) {
10149
+ if (bound === null) {
10150
+ return [];
10151
+ }
10152
+ let encoders = [];
10153
+ encoders.push(new IndexByteEncoder());
10154
+ let boundIdx = 0;
10155
+ for (const segment of fieldIndexGetDirectionalSegments(fieldIndex)) {
10156
+ const value = bound[boundIdx++];
10157
+ for (const encoder of encoders) {
10158
+ if (this.isInFilter(target, segment.fieldPath) && isArray(value)) {
10159
+ encoders = this.expandIndexValues(encoders, segment, value);
10160
+ }
10161
+ else {
10162
+ const directionalEncoder = encoder.forKind(segment.kind);
10163
+ FirestoreIndexValueWriter.INSTANCE.writeIndexValue(value, directionalEncoder);
10164
+ }
10165
+ }
10166
+ }
10167
+ return this.getEncodedBytes(encoders);
10168
+ }
10169
+ /**
10170
+ * Encodes the given bounds according to the specification in `target`. For IN
10171
+ * queries, a list of possible values is returned.
10172
+ */
10173
+ encodeBound(fieldIndex, target, bound) {
10174
+ if (bound == null) {
10175
+ return null;
10176
+ }
10177
+ return this.encodeValues(fieldIndex, target, bound.position);
10178
+ }
10179
+ /** Returns the byte representation for the provided encoders. */
10180
+ getEncodedBytes(encoders) {
10181
+ const result = [];
10182
+ for (let i = 0; i < encoders.length; ++i) {
10183
+ result[i] = encoders[i].encodedBytes();
10184
+ }
10185
+ return result;
10186
+ }
10187
+ /**
10188
+ * Creates a separate encoder for each element of an array.
10189
+ *
10190
+ * The method appends each value to all existing encoders (e.g. filter("a",
10191
+ * "==", "a1").filter("b", "in", ["b1", "b2"]) becomes ["a1,b1", "a1,b2"]). A
10192
+ * list of new encoders is returned.
10193
+ */
10194
+ expandIndexValues(encoders, segment, value) {
10195
+ const prefixes = [...encoders];
10196
+ const results = [];
10197
+ for (const arrayElement of value.arrayValue.values || []) {
10198
+ for (const prefix of prefixes) {
10199
+ const clonedEncoder = new IndexByteEncoder();
10200
+ clonedEncoder.seed(prefix.encodedBytes());
10201
+ FirestoreIndexValueWriter.INSTANCE.writeIndexValue(arrayElement, clonedEncoder.forKind(segment.kind));
10202
+ results.push(clonedEncoder);
10203
+ }
10204
+ }
10205
+ return results;
10206
+ }
10207
+ isInFilter(target, fieldPath) {
10208
+ return !!target.filters.find(f => f instanceof FieldFilter &&
10209
+ f.field.isEqual(fieldPath) &&
10210
+ (f.op === "in" /* IN */ || f.op === "not-in" /* NOT_IN */));
10211
+ }
9951
10212
  getFieldIndexes(transaction, collectionGroup) {
9952
10213
  const indexes = indexConfigurationStore(transaction);
9953
10214
  const states = indexStateStore(transaction);
9954
10215
  return (collectionGroup
9955
- ? indexes.loadAll(DbIndexConfiguration.collectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
10216
+ ? indexes.loadAll(DbIndexConfigurationCollectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
9956
10217
  : indexes.loadAll()).next(indexConfigs => {
9957
10218
  const result = [];
9958
10219
  return PersistencePromise.forEach(indexConfigs, (indexConfig) => {
@@ -9969,7 +10230,12 @@ class IndexedDbIndexManager {
9969
10230
  if (indexes.length === 0) {
9970
10231
  return null;
9971
10232
  }
9972
- indexes.sort((l, r) => l.indexState.sequenceNumber - r.indexState.sequenceNumber);
10233
+ indexes.sort((l, r) => {
10234
+ const cmp = l.indexState.sequenceNumber - r.indexState.sequenceNumber;
10235
+ return cmp !== 0
10236
+ ? cmp
10237
+ : primitiveComparator(l.collectionGroup, r.collectionGroup);
10238
+ });
9973
10239
  return indexes[0].collectionGroup;
9974
10240
  });
9975
10241
  }
@@ -9977,7 +10243,7 @@ class IndexedDbIndexManager {
9977
10243
  const indexes = indexConfigurationStore(transaction);
9978
10244
  const states = indexStateStore(transaction);
9979
10245
  return this.getNextSequenceNumber(transaction).next(nextSequenceNumber => indexes
9980
- .loadAll(DbIndexConfiguration.collectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
10246
+ .loadAll(DbIndexConfigurationCollectionGroupIndex, IDBKeyRange.bound(collectionGroup, collectionGroup))
9981
10247
  .next(configs => PersistencePromise.forEach(configs, (config) => states.put(toDbIndexState(config.indexId, this.user, nextSequenceNumber, offset)))));
9982
10248
  }
9983
10249
  updateIndexEntries(transaction, documents) {
@@ -10008,7 +10274,13 @@ class IndexedDbIndexManager {
10008
10274
  }
10009
10275
  addIndexEntry(transaction, document, indexEntry) {
10010
10276
  const indexEntries = indexEntriesStore(transaction);
10011
- return indexEntries.put(new DbIndexEntry(indexEntry.indexId, this.uid, indexEntry.arrayValue, indexEntry.directionalValue, encodeResourcePath(document.key.path)));
10277
+ return indexEntries.put({
10278
+ indexId: indexEntry.indexId,
10279
+ uid: this.uid,
10280
+ arrayValue: indexEntry.arrayValue,
10281
+ directionalValue: indexEntry.directionalValue,
10282
+ documentKey: encodeResourcePath(document.key.path)
10283
+ });
10012
10284
  }
10013
10285
  deleteIndexEntry(transaction, document, indexEntry) {
10014
10286
  const indexEntries = indexEntriesStore(transaction);
@@ -10025,7 +10297,7 @@ class IndexedDbIndexManager {
10025
10297
  let results = new SortedSet(indexEntryComparator);
10026
10298
  return indexEntries
10027
10299
  .iterate({
10028
- index: DbIndexEntry.documentKeyIndex,
10300
+ index: DbIndexEntryDocumentKeyIndex,
10029
10301
  range: IDBKeyRange.only([
10030
10302
  fieldIndex.indexId,
10031
10303
  this.uid,
@@ -10053,7 +10325,7 @@ class IndexedDbIndexManager {
10053
10325
  }
10054
10326
  }
10055
10327
  else {
10056
- results = results.add(new IndexEntry(fieldIndex.indexId, document.key, new Uint8Array(), directionalValue));
10328
+ results = results.add(new IndexEntry(fieldIndex.indexId, document.key, EMPTY_VALUE, directionalValue));
10057
10329
  }
10058
10330
  return results;
10059
10331
  }
@@ -10079,7 +10351,7 @@ class IndexedDbIndexManager {
10079
10351
  const states = indexStateStore(transaction);
10080
10352
  return states
10081
10353
  .iterate({
10082
- index: DbIndexState.sequenceNumberIndex,
10354
+ index: DbIndexStateSequenceNumberIndex,
10083
10355
  reverse: true,
10084
10356
  range: IDBKeyRange.upperBound([this.uid, Number.MAX_SAFE_INTEGER])
10085
10357
  }, (_, state, controller) => {
@@ -10088,31 +10360,81 @@ class IndexedDbIndexManager {
10088
10360
  })
10089
10361
  .next(() => nextSequenceNumber);
10090
10362
  }
10363
+ /**
10364
+ * Returns a new set of IDB ranges that splits the existing range and excludes
10365
+ * any values that match the `notInValue` from these ranges. As an example,
10366
+ * '[foo > 2 && foo != 3]` becomes `[foo > 2 && < 3, foo > 3]`.
10367
+ */
10368
+ createRange(lower, upper, notInValues) {
10369
+ // The notIb values need to be sorted and unique so that we can return a
10370
+ // sorted set of non-overlapping ranges.
10371
+ notInValues = notInValues
10372
+ .sort((l, r) => indexEntryComparator(l, r))
10373
+ .filter((el, i, values) => !i || indexEntryComparator(el, values[i - 1]) !== 0);
10374
+ const bounds = [];
10375
+ bounds.push(lower);
10376
+ for (const notInValue of notInValues) {
10377
+ const cmpToLower = indexEntryComparator(notInValue, lower);
10378
+ const cmpToUpper = indexEntryComparator(notInValue, upper);
10379
+ if (cmpToLower === 0) {
10380
+ // `notInValue` is the lower bound. We therefore need to raise the bound
10381
+ // to the next value.
10382
+ bounds[0] = lower.successor();
10383
+ }
10384
+ else if (cmpToLower > 0 && cmpToUpper < 0) {
10385
+ // `notInValue` is in the middle of the range
10386
+ bounds.push(notInValue);
10387
+ bounds.push(notInValue.successor());
10388
+ }
10389
+ else if (cmpToUpper > 0) {
10390
+ // `notInValue` (and all following values) are out of the range
10391
+ break;
10392
+ }
10393
+ }
10394
+ bounds.push(upper);
10395
+ const ranges = [];
10396
+ for (let i = 0; i < bounds.length; i += 2) {
10397
+ ranges.push(IDBKeyRange.bound([
10398
+ bounds[i].indexId,
10399
+ this.uid,
10400
+ bounds[i].arrayValue,
10401
+ bounds[i].directionalValue,
10402
+ ''
10403
+ ], [
10404
+ bounds[i + 1].indexId,
10405
+ this.uid,
10406
+ bounds[i + 1].arrayValue,
10407
+ bounds[i + 1].directionalValue,
10408
+ ''
10409
+ ]));
10410
+ }
10411
+ return ranges;
10412
+ }
10091
10413
  }
10092
10414
  /**
10093
10415
  * Helper to get a typed SimpleDbStore for the collectionParents
10094
10416
  * document store.
10095
10417
  */
10096
10418
  function collectionParentsStore(txn) {
10097
- return getStore(txn, DbCollectionParent.store);
10419
+ return getStore(txn, DbCollectionParentStore);
10098
10420
  }
10099
10421
  /**
10100
10422
  * Helper to get a typed SimpleDbStore for the index entry object store.
10101
10423
  */
10102
10424
  function indexEntriesStore(txn) {
10103
- return getStore(txn, DbIndexEntry.store);
10425
+ return getStore(txn, DbIndexEntryStore);
10104
10426
  }
10105
10427
  /**
10106
10428
  * Helper to get a typed SimpleDbStore for the index configuration object store.
10107
10429
  */
10108
10430
  function indexConfigurationStore(txn) {
10109
- return getStore(txn, DbIndexConfiguration.store);
10431
+ return getStore(txn, DbIndexConfigurationStore);
10110
10432
  }
10111
10433
  /**
10112
10434
  * Helper to get a typed SimpleDbStore for the index state object store.
10113
10435
  */
10114
10436
  function indexStateStore(txn) {
10115
- return getStore(txn, DbIndexState.store);
10437
+ return getStore(txn, DbIndexStateStore);
10116
10438
  }
10117
10439
 
10118
10440
  /**
@@ -10136,8 +10458,8 @@ function indexStateStore(txn) {
10136
10458
  * @returns A PersistencePromise of the document mutations that were removed.
10137
10459
  */
10138
10460
  function removeMutationBatch(txn, userId, batch) {
10139
- const mutationStore = txn.store(DbMutationBatch.store);
10140
- const indexTxn = txn.store(DbDocumentMutation.store);
10461
+ const mutationStore = txn.store(DbMutationBatchStore);
10462
+ const indexTxn = txn.store(DbDocumentMutationStore);
10141
10463
  const promises = [];
10142
10464
  const range = IDBKeyRange.only(batch.batchId);
10143
10465
  let numDeleted = 0;
@@ -10150,7 +10472,7 @@ function removeMutationBatch(txn, userId, batch) {
10150
10472
  }));
10151
10473
  const removedDocuments = [];
10152
10474
  for (const mutation of batch.mutations) {
10153
- const indexKey = DbDocumentMutation.key(userId, mutation.key.path, batch.batchId);
10475
+ const indexKey = newDbDocumentMutationKey(userId, mutation.key.path, batch.batchId);
10154
10476
  promises.push(indexTxn.delete(indexKey));
10155
10477
  removedDocuments.push(mutation.key);
10156
10478
  }
@@ -10239,7 +10561,7 @@ class IndexedDbMutationQueue {
10239
10561
  let empty = true;
10240
10562
  const range = IDBKeyRange.bound([this.userId, Number.NEGATIVE_INFINITY], [this.userId, Number.POSITIVE_INFINITY]);
10241
10563
  return mutationsStore(transaction)
10242
- .iterate({ index: DbMutationBatch.userMutationsIndex, range }, (key, value, control) => {
10564
+ .iterate({ index: DbMutationBatchUserMutationsIndex, range }, (key, value, control) => {
10243
10565
  empty = false;
10244
10566
  control.done();
10245
10567
  })
@@ -10264,10 +10586,10 @@ class IndexedDbMutationQueue {
10264
10586
  const promises = [];
10265
10587
  let collectionParents = new SortedSet((l, r) => primitiveComparator(l.canonicalString(), r.canonicalString()));
10266
10588
  for (const mutation of mutations) {
10267
- const indexKey = DbDocumentMutation.key(this.userId, mutation.key.path, batchId);
10589
+ const indexKey = newDbDocumentMutationKey(this.userId, mutation.key.path, batchId);
10268
10590
  collectionParents = collectionParents.add(mutation.key.path.popLast());
10269
10591
  promises.push(mutationStore.put(dbBatch));
10270
- promises.push(documentStore.put(indexKey, DbDocumentMutation.PLACEHOLDER));
10592
+ promises.push(documentStore.put(indexKey, DbDocumentMutationPlaceholder));
10271
10593
  }
10272
10594
  collectionParents.forEach(parent => {
10273
10595
  promises.push(this.indexManager.addToCollectionParentIndex(transaction, parent));
@@ -10318,7 +10640,7 @@ class IndexedDbMutationQueue {
10318
10640
  const range = IDBKeyRange.lowerBound([this.userId, nextBatchId]);
10319
10641
  let foundBatch = null;
10320
10642
  return mutationsStore(transaction)
10321
- .iterate({ index: DbMutationBatch.userMutationsIndex, range }, (key, dbBatch, control) => {
10643
+ .iterate({ index: DbMutationBatchUserMutationsIndex, range }, (key, dbBatch, control) => {
10322
10644
  if (dbBatch.userId === this.userId) {
10323
10645
  hardAssert(dbBatch.batchId >= nextBatchId);
10324
10646
  foundBatch = fromDbMutationBatch(this.serializer, dbBatch);
@@ -10334,7 +10656,7 @@ class IndexedDbMutationQueue {
10334
10656
  ]);
10335
10657
  let batchId = BATCHID_UNKNOWN;
10336
10658
  return mutationsStore(transaction)
10337
- .iterate({ index: DbMutationBatch.userMutationsIndex, range, reverse: true }, (key, dbBatch, control) => {
10659
+ .iterate({ index: DbMutationBatchUserMutationsIndex, range, reverse: true }, (key, dbBatch, control) => {
10338
10660
  batchId = dbBatch.batchId;
10339
10661
  control.done();
10340
10662
  })
@@ -10343,13 +10665,13 @@ class IndexedDbMutationQueue {
10343
10665
  getAllMutationBatches(transaction) {
10344
10666
  const range = IDBKeyRange.bound([this.userId, BATCHID_UNKNOWN], [this.userId, Number.POSITIVE_INFINITY]);
10345
10667
  return mutationsStore(transaction)
10346
- .loadAll(DbMutationBatch.userMutationsIndex, range)
10668
+ .loadAll(DbMutationBatchUserMutationsIndex, range)
10347
10669
  .next(dbBatches => dbBatches.map(dbBatch => fromDbMutationBatch(this.serializer, dbBatch)));
10348
10670
  }
10349
10671
  getAllMutationBatchesAffectingDocumentKey(transaction, documentKey) {
10350
10672
  // Scan the document-mutation index starting with a prefix starting with
10351
10673
  // the given documentKey.
10352
- const indexPrefix = DbDocumentMutation.prefixForPath(this.userId, documentKey.path);
10674
+ const indexPrefix = newDbDocumentMutationPrefixForPath(this.userId, documentKey.path);
10353
10675
  const indexStart = IDBKeyRange.lowerBound(indexPrefix);
10354
10676
  const results = [];
10355
10677
  return documentMutationsStore(transaction)
@@ -10384,7 +10706,7 @@ class IndexedDbMutationQueue {
10384
10706
  let uniqueBatchIDs = new SortedSet(primitiveComparator);
10385
10707
  const promises = [];
10386
10708
  documentKeys.forEach(documentKey => {
10387
- const indexStart = DbDocumentMutation.prefixForPath(this.userId, documentKey.path);
10709
+ const indexStart = newDbDocumentMutationPrefixForPath(this.userId, documentKey.path);
10388
10710
  const range = IDBKeyRange.lowerBound(indexStart);
10389
10711
  const promise = documentMutationsStore(transaction).iterate({ range }, (indexKey, _, control) => {
10390
10712
  const [userID, encodedPath, batchID] = indexKey;
@@ -10420,7 +10742,7 @@ class IndexedDbMutationQueue {
10420
10742
  // Since we don't yet index the actual properties in the mutations, our
10421
10743
  // current approach is to just return all mutation batches that affect
10422
10744
  // documents in the collection being queried.
10423
- const indexPrefix = DbDocumentMutation.prefixForPath(this.userId, queryPath);
10745
+ const indexPrefix = newDbDocumentMutationPrefixForPath(this.userId, queryPath);
10424
10746
  const indexStart = IDBKeyRange.lowerBound(indexPrefix);
10425
10747
  // Collect up unique batchIDs encountered during a scan of the index. Use a
10426
10748
  // SortedSet to accumulate batch IDs so they can be traversed in order in a
@@ -10492,7 +10814,7 @@ class IndexedDbMutationQueue {
10492
10814
  }
10493
10815
  // Verify that there are no entries in the documentMutations index if
10494
10816
  // the queue is empty.
10495
- const startRange = IDBKeyRange.lowerBound(DbDocumentMutation.prefixForUser(this.userId));
10817
+ const startRange = IDBKeyRange.lowerBound(newDbDocumentMutationPrefixForUser(this.userId));
10496
10818
  const danglingMutationReferences = [];
10497
10819
  return documentMutationsStore(txn)
10498
10820
  .iterate({ range: startRange }, (key, _, control) => {
@@ -10520,9 +10842,11 @@ class IndexedDbMutationQueue {
10520
10842
  return mutationQueuesStore(transaction)
10521
10843
  .get(this.userId)
10522
10844
  .next((metadata) => {
10523
- return (metadata ||
10524
- new DbMutationQueue(this.userId, BATCHID_UNKNOWN,
10525
- /*lastStreamToken=*/ ''));
10845
+ return (metadata || {
10846
+ userId: this.userId,
10847
+ lastAcknowledgedBatchId: BATCHID_UNKNOWN,
10848
+ lastStreamToken: ''
10849
+ });
10526
10850
  });
10527
10851
  }
10528
10852
  }
@@ -10531,7 +10855,7 @@ class IndexedDbMutationQueue {
10531
10855
  * mutation for the given key.
10532
10856
  */
10533
10857
  function mutationQueueContainsKey(txn, userId, key) {
10534
- const indexKey = DbDocumentMutation.prefixForPath(userId, key.path);
10858
+ const indexKey = newDbDocumentMutationPrefixForPath(userId, key.path);
10535
10859
  const encodedPath = indexKey[1];
10536
10860
  const startRange = IDBKeyRange.lowerBound(indexKey);
10537
10861
  let containsKey = false;
@@ -10563,19 +10887,19 @@ function mutationQueuesContainKey(txn, docKey) {
10563
10887
  * Helper to get a typed SimpleDbStore for the mutations object store.
10564
10888
  */
10565
10889
  function mutationsStore(txn) {
10566
- return getStore(txn, DbMutationBatch.store);
10890
+ return getStore(txn, DbMutationBatchStore);
10567
10891
  }
10568
10892
  /**
10569
10893
  * Helper to get a typed SimpleDbStore for the mutationQueues object store.
10570
10894
  */
10571
10895
  function documentMutationsStore(txn) {
10572
- return getStore(txn, DbDocumentMutation.store);
10896
+ return getStore(txn, DbDocumentMutationStore);
10573
10897
  }
10574
10898
  /**
10575
10899
  * Helper to get a typed SimpleDbStore for the mutationQueues object store.
10576
10900
  */
10577
10901
  function mutationQueuesStore(txn) {
10578
- return getStore(txn, DbMutationQueue.store);
10902
+ return getStore(txn, DbMutationQueueStore);
10579
10903
  }
10580
10904
 
10581
10905
  /**
@@ -10739,14 +11063,14 @@ class IndexedDbTargetCache {
10739
11063
  }
10740
11064
  retrieveMetadata(transaction) {
10741
11065
  return globalTargetStore(transaction)
10742
- .get(DbTargetGlobal.key)
11066
+ .get(DbTargetGlobalKey)
10743
11067
  .next(metadata => {
10744
11068
  hardAssert(metadata !== null);
10745
11069
  return metadata;
10746
11070
  });
10747
11071
  }
10748
11072
  saveMetadata(transaction, metadata) {
10749
- return globalTargetStore(transaction).put(DbTargetGlobal.key, metadata);
11073
+ return globalTargetStore(transaction).put(DbTargetGlobalKey, metadata);
10750
11074
  }
10751
11075
  saveTargetData(transaction, targetData) {
10752
11076
  return targetsStore(transaction).put(toDbTarget(this.serializer, targetData));
@@ -10779,7 +11103,7 @@ class IndexedDbTargetCache {
10779
11103
  const range = IDBKeyRange.bound([canonicalId, Number.NEGATIVE_INFINITY], [canonicalId, Number.POSITIVE_INFINITY]);
10780
11104
  let result = null;
10781
11105
  return targetsStore(transaction)
10782
- .iterate({ range, index: DbTarget.queryTargetsIndexName }, (key, value, control) => {
11106
+ .iterate({ range, index: DbTargetQueryTargetsIndexName }, (key, value, control) => {
10783
11107
  const found = fromDbTarget(value);
10784
11108
  // After finding a potential match, check that the target is
10785
11109
  // actually equal to the requested target.
@@ -10797,7 +11121,7 @@ class IndexedDbTargetCache {
10797
11121
  const store = documentTargetStore(txn);
10798
11122
  keys.forEach(key => {
10799
11123
  const path = encodeResourcePath(key.path);
10800
- promises.push(store.put(new DbTargetDocument(targetId, path)));
11124
+ promises.push(store.put({ targetId, path }));
10801
11125
  promises.push(this.referenceDelegate.addReference(txn, targetId, key));
10802
11126
  });
10803
11127
  return PersistencePromise.waitFor(promises);
@@ -10843,7 +11167,7 @@ class IndexedDbTargetCache {
10843
11167
  let count = 0;
10844
11168
  return documentTargetStore(txn)
10845
11169
  .iterate({
10846
- index: DbTargetDocument.documentTargetsIndex,
11170
+ index: DbTargetDocumentDocumentTargetsIndex,
10847
11171
  keysOnly: true,
10848
11172
  range
10849
11173
  }, ([targetId, path], _, control) => {
@@ -10882,19 +11206,19 @@ class IndexedDbTargetCache {
10882
11206
  * Helper to get a typed SimpleDbStore for the queries object store.
10883
11207
  */
10884
11208
  function targetsStore(txn) {
10885
- return getStore(txn, DbTarget.store);
11209
+ return getStore(txn, DbTargetStore);
10886
11210
  }
10887
11211
  /**
10888
11212
  * Helper to get a typed SimpleDbStore for the target globals object store.
10889
11213
  */
10890
11214
  function globalTargetStore(txn) {
10891
- return getStore(txn, DbTargetGlobal.store);
11215
+ return getStore(txn, DbTargetGlobalStore);
10892
11216
  }
10893
11217
  /**
10894
11218
  * Helper to get a typed SimpleDbStore for the document target object store.
10895
11219
  */
10896
11220
  function documentTargetStore(txn) {
10897
- return getStore(txn, DbTargetDocument.store);
11221
+ return getStore(txn, DbTargetDocumentStore);
10898
11222
  }
10899
11223
 
10900
11224
  /**
@@ -11308,7 +11632,7 @@ class IndexedDbLruDelegateImpl {
11308
11632
  let nextPath;
11309
11633
  return store
11310
11634
  .iterate({
11311
- index: DbTargetDocument.documentTargetsIndex
11635
+ index: DbTargetDocumentDocumentTargetsIndex
11312
11636
  }, ([targetId, docKey], { path, sequenceNumber }) => {
11313
11637
  if (targetId === 0) {
11314
11638
  // if nextToReport is valid, report it, this is a new key so the
@@ -11350,113 +11674,12 @@ function sentinelKey$1(key) {
11350
11674
  * store.
11351
11675
  */
11352
11676
  function sentinelRow(key, sequenceNumber) {
11353
- return new DbTargetDocument(0, encodeResourcePath(key.path), sequenceNumber);
11677
+ return { targetId: 0, path: encodeResourcePath(key.path), sequenceNumber };
11354
11678
  }
11355
11679
  function writeSentinelKey(txn, key) {
11356
11680
  return documentTargetStore(txn).put(sentinelRow(key, txn.currentSequenceNumber));
11357
11681
  }
11358
11682
 
11359
- /**
11360
- * @license
11361
- * Copyright 2017 Google LLC
11362
- *
11363
- * Licensed under the Apache License, Version 2.0 (the "License");
11364
- * you may not use this file except in compliance with the License.
11365
- * You may obtain a copy of the License at
11366
- *
11367
- * http://www.apache.org/licenses/LICENSE-2.0
11368
- *
11369
- * Unless required by applicable law or agreed to in writing, software
11370
- * distributed under the License is distributed on an "AS IS" BASIS,
11371
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11372
- * See the License for the specific language governing permissions and
11373
- * limitations under the License.
11374
- */
11375
- /**
11376
- * A map implementation that uses objects as keys. Objects must have an
11377
- * associated equals function and must be immutable. Entries in the map are
11378
- * stored together with the key being produced from the mapKeyFn. This map
11379
- * automatically handles collisions of keys.
11380
- */
11381
- class ObjectMap {
11382
- constructor(mapKeyFn, equalsFn) {
11383
- this.mapKeyFn = mapKeyFn;
11384
- this.equalsFn = equalsFn;
11385
- /**
11386
- * The inner map for a key/value pair. Due to the possibility of collisions we
11387
- * keep a list of entries that we do a linear search through to find an actual
11388
- * match. Note that collisions should be rare, so we still expect near
11389
- * constant time lookups in practice.
11390
- */
11391
- this.inner = {};
11392
- }
11393
- /** Get a value for this key, or undefined if it does not exist. */
11394
- get(key) {
11395
- const id = this.mapKeyFn(key);
11396
- const matches = this.inner[id];
11397
- if (matches === undefined) {
11398
- return undefined;
11399
- }
11400
- for (const [otherKey, value] of matches) {
11401
- if (this.equalsFn(otherKey, key)) {
11402
- return value;
11403
- }
11404
- }
11405
- return undefined;
11406
- }
11407
- has(key) {
11408
- return this.get(key) !== undefined;
11409
- }
11410
- /** Put this key and value in the map. */
11411
- set(key, value) {
11412
- const id = this.mapKeyFn(key);
11413
- const matches = this.inner[id];
11414
- if (matches === undefined) {
11415
- this.inner[id] = [[key, value]];
11416
- return;
11417
- }
11418
- for (let i = 0; i < matches.length; i++) {
11419
- if (this.equalsFn(matches[i][0], key)) {
11420
- matches[i] = [key, value];
11421
- return;
11422
- }
11423
- }
11424
- matches.push([key, value]);
11425
- }
11426
- /**
11427
- * Remove this key from the map. Returns a boolean if anything was deleted.
11428
- */
11429
- delete(key) {
11430
- const id = this.mapKeyFn(key);
11431
- const matches = this.inner[id];
11432
- if (matches === undefined) {
11433
- return false;
11434
- }
11435
- for (let i = 0; i < matches.length; i++) {
11436
- if (this.equalsFn(matches[i][0], key)) {
11437
- if (matches.length === 1) {
11438
- delete this.inner[id];
11439
- }
11440
- else {
11441
- matches.splice(i, 1);
11442
- }
11443
- return true;
11444
- }
11445
- }
11446
- return false;
11447
- }
11448
- forEach(fn) {
11449
- forEach(this.inner, (_, entries) => {
11450
- for (const [k, v] of entries) {
11451
- fn(k, v);
11452
- }
11453
- });
11454
- }
11455
- isEmpty() {
11456
- return isEmpty(this.inner);
11457
- }
11458
- }
11459
-
11460
11683
  /**
11461
11684
  * @license
11462
11685
  * Copyright 2017 Google LLC
@@ -11725,7 +11948,7 @@ class IndexedDbRemoteDocumentCacheImpl {
11725
11948
  const readTimeKey = toDbTimestampKey(sinceReadTime);
11726
11949
  iterationOptions.range = IDBKeyRange.lowerBound([collectionKey, readTimeKey],
11727
11950
  /* open= */ true);
11728
- iterationOptions.index = DbRemoteDocument.collectionReadTimeIndex;
11951
+ iterationOptions.index = DbRemoteDocumentCollectionReadTimeIndex;
11729
11952
  }
11730
11953
  return remoteDocumentsStore(transaction)
11731
11954
  .iterate(iterationOptions, (key, dbRemoteDoc, control) => {
@@ -11755,14 +11978,14 @@ class IndexedDbRemoteDocumentCacheImpl {
11755
11978
  }
11756
11979
  getMetadata(txn) {
11757
11980
  return documentGlobalStore(txn)
11758
- .get(DbRemoteDocumentGlobal.key)
11981
+ .get(DbRemoteDocumentGlobalKey)
11759
11982
  .next(metadata => {
11760
11983
  hardAssert(!!metadata);
11761
11984
  return metadata;
11762
11985
  });
11763
11986
  }
11764
11987
  setMetadata(txn, metadata) {
11765
- return documentGlobalStore(txn).put(DbRemoteDocumentGlobal.key, metadata);
11988
+ return documentGlobalStore(txn).put(DbRemoteDocumentGlobalKey, metadata);
11766
11989
  }
11767
11990
  /**
11768
11991
  * Decodes `dbRemoteDoc` and returns the document (or an invalid document if
@@ -11797,7 +12020,7 @@ function remoteDocumentCacheGetNewDocumentChanges(remoteDocumentCache, transacti
11797
12020
  const documentsStore = remoteDocumentsStore(transaction);
11798
12021
  const range = IDBKeyRange.lowerBound(lastReadTime, true);
11799
12022
  return documentsStore
11800
- .iterate({ index: DbRemoteDocument.readTimeIndex, range }, (_, dbRemoteDoc) => {
12023
+ .iterate({ index: DbRemoteDocumentReadTimeIndex, range }, (_, dbRemoteDoc) => {
11801
12024
  // Unlike `getEntry()` and others, `getNewDocumentChanges()` parses
11802
12025
  // the documents directly since we want to keep sentinel deletes.
11803
12026
  const doc = fromDbRemoteDocument(remoteDocumentCacheImpl.serializer, dbRemoteDoc);
@@ -11821,7 +12044,7 @@ function remoteDocumentCacheGetLastReadTime(transaction) {
11821
12044
  // If there are no existing entries, we return SnapshotVersion.min().
11822
12045
  let readTime = SnapshotVersion.min();
11823
12046
  return documentsStore
11824
- .iterate({ index: DbRemoteDocument.readTimeIndex, reverse: true }, (key, dbRemoteDoc, control) => {
12047
+ .iterate({ index: DbRemoteDocumentReadTimeIndex, reverse: true }, (key, dbRemoteDoc, control) => {
11825
12048
  if (dbRemoteDoc.readTime) {
11826
12049
  readTime = fromDbTimestampKey(dbRemoteDoc.readTime);
11827
12050
  }
@@ -11909,17 +12132,39 @@ class IndexedDbRemoteDocumentChangeBuffer extends RemoteDocumentChangeBuffer {
11909
12132
  }
11910
12133
  }
11911
12134
  function documentGlobalStore(txn) {
11912
- return getStore(txn, DbRemoteDocumentGlobal.store);
12135
+ return getStore(txn, DbRemoteDocumentGlobalStore);
11913
12136
  }
11914
12137
  /**
11915
12138
  * Helper to get a typed SimpleDbStore for the remoteDocuments object store.
11916
12139
  */
11917
12140
  function remoteDocumentsStore(txn) {
11918
- return getStore(txn, DbRemoteDocument.store);
12141
+ return getStore(txn, DbRemoteDocumentStore);
11919
12142
  }
11920
12143
  function dbKey(docKey) {
11921
12144
  return docKey.path.toArray();
11922
12145
  }
12146
+ /**
12147
+ * Schema Version for the Web client:
12148
+ * 1. Initial version including Mutation Queue, Query Cache, and Remote
12149
+ * Document Cache
12150
+ * 2. Used to ensure a targetGlobal object exists and add targetCount to it. No
12151
+ * longer required because migration 3 unconditionally clears it.
12152
+ * 3. Dropped and re-created Query Cache to deal with cache corruption related
12153
+ * to limbo resolution. Addresses
12154
+ * https://github.com/firebase/firebase-ios-sdk/issues/1548
12155
+ * 4. Multi-Tab Support.
12156
+ * 5. Removal of held write acks.
12157
+ * 6. Create document global for tracking document cache size.
12158
+ * 7. Ensure every cached document has a sentinel row with a sequence number.
12159
+ * 8. Add collection-parent index for Collection Group queries.
12160
+ * 9. Change RemoteDocumentChanges store to be keyed by readTime rather than
12161
+ * an auto-incrementing ID. This is required for Index-Free queries.
12162
+ * 10. Rewrite the canonical IDs to the explicit Protobuf-based format.
12163
+ * 11. Add bundles and named_queries for bundle support.
12164
+ * 12. Add document overlays.
12165
+ * 13. Add indexing support.
12166
+ */
12167
+ const SCHEMA_VERSION = 12;
11923
12168
 
11924
12169
  /**
11925
12170
  * @license
@@ -12029,27 +12274,27 @@ class SchemaConverter {
12029
12274
  return p;
12030
12275
  }
12031
12276
  addDocumentGlobal(txn) {
12032
- let byteCount = 0;
12277
+ let byteSize = 0;
12033
12278
  return txn
12034
- .store(DbRemoteDocument.store)
12279
+ .store(DbRemoteDocumentStore)
12035
12280
  .iterate((_, doc) => {
12036
- byteCount += dbDocumentSize(doc);
12281
+ byteSize += dbDocumentSize(doc);
12037
12282
  })
12038
12283
  .next(() => {
12039
- const metadata = new DbRemoteDocumentGlobal(byteCount);
12284
+ const metadata = { byteSize };
12040
12285
  return txn
12041
- .store(DbRemoteDocumentGlobal.store)
12042
- .put(DbRemoteDocumentGlobal.key, metadata);
12286
+ .store(DbRemoteDocumentGlobalStore)
12287
+ .put(DbRemoteDocumentGlobalKey, metadata);
12043
12288
  });
12044
12289
  }
12045
12290
  removeAcknowledgedMutations(txn) {
12046
- const queuesStore = txn.store(DbMutationQueue.store);
12047
- const mutationsStore = txn.store(DbMutationBatch.store);
12291
+ const queuesStore = txn.store(DbMutationQueueStore);
12292
+ const mutationsStore = txn.store(DbMutationBatchStore);
12048
12293
  return queuesStore.loadAll().next(queues => {
12049
12294
  return PersistencePromise.forEach(queues, (queue) => {
12050
12295
  const range = IDBKeyRange.bound([queue.userId, BATCHID_UNKNOWN], [queue.userId, queue.lastAcknowledgedBatchId]);
12051
12296
  return mutationsStore
12052
- .loadAll(DbMutationBatch.userMutationsIndex, range)
12297
+ .loadAll(DbMutationBatchUserMutationsIndex, range)
12053
12298
  .next(dbBatches => {
12054
12299
  return PersistencePromise.forEach(dbBatches, (dbBatch) => {
12055
12300
  hardAssert(dbBatch.userId === queue.userId);
@@ -12065,12 +12310,16 @@ class SchemaConverter {
12065
12310
  * with a sequence number. Missing rows are given the most recently used sequence number.
12066
12311
  */
12067
12312
  ensureSequenceNumbers(txn) {
12068
- const documentTargetStore = txn.store(DbTargetDocument.store);
12069
- const documentsStore = txn.store(DbRemoteDocument.store);
12070
- const globalTargetStore = txn.store(DbTargetGlobal.store);
12071
- return globalTargetStore.get(DbTargetGlobal.key).next(metadata => {
12313
+ const documentTargetStore = txn.store(DbTargetDocumentStore);
12314
+ const documentsStore = txn.store(DbRemoteDocumentStore);
12315
+ const globalTargetStore = txn.store(DbTargetGlobalStore);
12316
+ return globalTargetStore.get(DbTargetGlobalKey).next(metadata => {
12072
12317
  const writeSentinelKey = (path) => {
12073
- return documentTargetStore.put(new DbTargetDocument(0, encodeResourcePath(path), metadata.highestListenSequenceNumber));
12318
+ return documentTargetStore.put({
12319
+ targetId: 0,
12320
+ path: encodeResourcePath(path),
12321
+ sequenceNumber: metadata.highestListenSequenceNumber
12322
+ });
12074
12323
  };
12075
12324
  const promises = [];
12076
12325
  return documentsStore
@@ -12091,10 +12340,10 @@ class SchemaConverter {
12091
12340
  }
12092
12341
  createCollectionParentIndex(db, txn) {
12093
12342
  // Create the index.
12094
- db.createObjectStore(DbCollectionParent.store, {
12095
- keyPath: DbCollectionParent.keyPath
12343
+ db.createObjectStore(DbCollectionParentStore, {
12344
+ keyPath: DbCollectionParentKeyPath
12096
12345
  });
12097
- const collectionParentsStore = txn.store(DbCollectionParent.store);
12346
+ const collectionParentsStore = txn.store(DbCollectionParentStore);
12098
12347
  // Helper to add an index entry iff we haven't already written it.
12099
12348
  const cache = new MemoryCollectionParentIndex();
12100
12349
  const addEntry = (collectionPath) => {
@@ -12109,7 +12358,7 @@ class SchemaConverter {
12109
12358
  };
12110
12359
  // Index existing remote documents.
12111
12360
  return txn
12112
- .store(DbRemoteDocument.store)
12361
+ .store(DbRemoteDocumentStore)
12113
12362
  .iterate({ keysOnly: true }, (pathSegments, _) => {
12114
12363
  const path = new ResourcePath(pathSegments);
12115
12364
  return addEntry(path.popLast());
@@ -12117,7 +12366,7 @@ class SchemaConverter {
12117
12366
  .next(() => {
12118
12367
  // Index existing mutations.
12119
12368
  return txn
12120
- .store(DbDocumentMutation.store)
12369
+ .store(DbDocumentMutationStore)
12121
12370
  .iterate({ keysOnly: true }, ([userID, encodedPath, batchId], _) => {
12122
12371
  const path = decodeResourcePath(encodedPath);
12123
12372
  return addEntry(path.popLast());
@@ -12125,7 +12374,7 @@ class SchemaConverter {
12125
12374
  });
12126
12375
  }
12127
12376
  rewriteCanonicalIds(txn) {
12128
- const targetStore = txn.store(DbTarget.store);
12377
+ const targetStore = txn.store(DbTargetStore);
12129
12378
  return targetStore.iterate((key, originalDbTarget) => {
12130
12379
  const originalTargetData = fromDbTarget(originalDbTarget);
12131
12380
  const updatedDbTarget = toDbTarget(this.serializer, originalTargetData);
@@ -12137,59 +12386,59 @@ function sentinelKey(path) {
12137
12386
  return [0, encodeResourcePath(path)];
12138
12387
  }
12139
12388
  function createPrimaryClientStore(db) {
12140
- db.createObjectStore(DbPrimaryClient.store);
12389
+ db.createObjectStore(DbPrimaryClientStore);
12141
12390
  }
12142
12391
  function createMutationQueue(db) {
12143
- db.createObjectStore(DbMutationQueue.store, {
12144
- keyPath: DbMutationQueue.keyPath
12392
+ db.createObjectStore(DbMutationQueueStore, {
12393
+ keyPath: DbMutationQueueKeyPath
12145
12394
  });
12146
- const mutationBatchesStore = db.createObjectStore(DbMutationBatch.store, {
12147
- keyPath: DbMutationBatch.keyPath,
12395
+ const mutationBatchesStore = db.createObjectStore(DbMutationBatchStore, {
12396
+ keyPath: DbMutationBatchKeyPath,
12148
12397
  autoIncrement: true
12149
12398
  });
12150
- mutationBatchesStore.createIndex(DbMutationBatch.userMutationsIndex, DbMutationBatch.userMutationsKeyPath, { unique: true });
12151
- db.createObjectStore(DbDocumentMutation.store);
12399
+ mutationBatchesStore.createIndex(DbMutationBatchUserMutationsIndex, DbMutationBatchUserMutationsKeyPath, { unique: true });
12400
+ db.createObjectStore(DbDocumentMutationStore);
12152
12401
  }
12153
12402
  /**
12154
12403
  * Upgrade function to migrate the 'mutations' store from V1 to V3. Loads
12155
12404
  * and rewrites all data.
12156
12405
  */
12157
12406
  function upgradeMutationBatchSchemaAndMigrateData(db, txn) {
12158
- const v1MutationsStore = txn.store(DbMutationBatch.store);
12407
+ const v1MutationsStore = txn.store(DbMutationBatchStore);
12159
12408
  return v1MutationsStore.loadAll().next(existingMutations => {
12160
- db.deleteObjectStore(DbMutationBatch.store);
12161
- const mutationsStore = db.createObjectStore(DbMutationBatch.store, {
12162
- keyPath: DbMutationBatch.keyPath,
12409
+ db.deleteObjectStore(DbMutationBatchStore);
12410
+ const mutationsStore = db.createObjectStore(DbMutationBatchStore, {
12411
+ keyPath: DbMutationBatchKeyPath,
12163
12412
  autoIncrement: true
12164
12413
  });
12165
- mutationsStore.createIndex(DbMutationBatch.userMutationsIndex, DbMutationBatch.userMutationsKeyPath, { unique: true });
12166
- const v3MutationsStore = txn.store(DbMutationBatch.store);
12414
+ mutationsStore.createIndex(DbMutationBatchUserMutationsIndex, DbMutationBatchUserMutationsKeyPath, { unique: true });
12415
+ const v3MutationsStore = txn.store(DbMutationBatchStore);
12167
12416
  const writeAll = existingMutations.map(mutation => v3MutationsStore.put(mutation));
12168
12417
  return PersistencePromise.waitFor(writeAll);
12169
12418
  });
12170
12419
  }
12171
12420
  function createRemoteDocumentCache(db) {
12172
- db.createObjectStore(DbRemoteDocument.store);
12421
+ db.createObjectStore(DbRemoteDocumentStore);
12173
12422
  }
12174
12423
  function createDocumentGlobalStore(db) {
12175
- db.createObjectStore(DbRemoteDocumentGlobal.store);
12424
+ db.createObjectStore(DbRemoteDocumentGlobalStore);
12176
12425
  }
12177
12426
  function createQueryCache(db) {
12178
- const targetDocumentsStore = db.createObjectStore(DbTargetDocument.store, {
12179
- keyPath: DbTargetDocument.keyPath
12427
+ const targetDocumentsStore = db.createObjectStore(DbTargetDocumentStore, {
12428
+ keyPath: DbTargetDocumentKeyPath
12180
12429
  });
12181
- targetDocumentsStore.createIndex(DbTargetDocument.documentTargetsIndex, DbTargetDocument.documentTargetsKeyPath, { unique: true });
12182
- const targetStore = db.createObjectStore(DbTarget.store, {
12183
- keyPath: DbTarget.keyPath
12430
+ targetDocumentsStore.createIndex(DbTargetDocumentDocumentTargetsIndex, DbTargetDocumentDocumentTargetsKeyPath, { unique: true });
12431
+ const targetStore = db.createObjectStore(DbTargetStore, {
12432
+ keyPath: DbTargetKeyPath
12184
12433
  });
12185
12434
  // NOTE: This is unique only because the TargetId is the suffix.
12186
- targetStore.createIndex(DbTarget.queryTargetsIndexName, DbTarget.queryTargetsKeyPath, { unique: true });
12187
- db.createObjectStore(DbTargetGlobal.store);
12435
+ targetStore.createIndex(DbTargetQueryTargetsIndexName, DbTargetQueryTargetsKeyPath, { unique: true });
12436
+ db.createObjectStore(DbTargetGlobalStore);
12188
12437
  }
12189
12438
  function dropQueryCache(db) {
12190
- db.deleteObjectStore(DbTargetDocument.store);
12191
- db.deleteObjectStore(DbTarget.store);
12192
- db.deleteObjectStore(DbTargetGlobal.store);
12439
+ db.deleteObjectStore(DbTargetDocumentStore);
12440
+ db.deleteObjectStore(DbTargetStore);
12441
+ db.deleteObjectStore(DbTargetGlobalStore);
12193
12442
  }
12194
12443
  function dropRemoteDocumentChangesStore(db) {
12195
12444
  if (db.objectStoreNames.contains('remoteDocumentChanges')) {
@@ -12202,58 +12451,60 @@ function dropRemoteDocumentChangesStore(db) {
12202
12451
  * @param txn - The version upgrade transaction for indexeddb
12203
12452
  */
12204
12453
  function writeEmptyTargetGlobalEntry(txn) {
12205
- const globalStore = txn.store(DbTargetGlobal.store);
12206
- const metadata = new DbTargetGlobal(
12207
- /*highestTargetId=*/ 0,
12208
- /*lastListenSequenceNumber=*/ 0, SnapshotVersion.min().toTimestamp(),
12209
- /*targetCount=*/ 0);
12210
- return globalStore.put(DbTargetGlobal.key, metadata);
12454
+ const globalStore = txn.store(DbTargetGlobalStore);
12455
+ const metadata = {
12456
+ highestTargetId: 0,
12457
+ highestListenSequenceNumber: 0,
12458
+ lastRemoteSnapshotVersion: SnapshotVersion.min().toTimestamp(),
12459
+ targetCount: 0
12460
+ };
12461
+ return globalStore.put(DbTargetGlobalKey, metadata);
12211
12462
  }
12212
12463
  /**
12213
12464
  * Creates indices on the RemoteDocuments store used for both multi-tab
12214
12465
  * and Index-Free queries.
12215
12466
  */
12216
12467
  function createRemoteDocumentReadTimeIndex(txn) {
12217
- const remoteDocumentStore = txn.objectStore(DbRemoteDocument.store);
12218
- remoteDocumentStore.createIndex(DbRemoteDocument.readTimeIndex, DbRemoteDocument.readTimeIndexPath, { unique: false });
12219
- remoteDocumentStore.createIndex(DbRemoteDocument.collectionReadTimeIndex, DbRemoteDocument.collectionReadTimeIndexPath, { unique: false });
12468
+ const remoteDocumentStore = txn.objectStore(DbRemoteDocumentStore);
12469
+ remoteDocumentStore.createIndex(DbRemoteDocumentReadTimeIndex, DbRemoteDocumentReadTimeIndexPath, { unique: false });
12470
+ remoteDocumentStore.createIndex(DbRemoteDocumentCollectionReadTimeIndex, DbRemoteDocumentCollectionReadTimeIndexPath, { unique: false });
12220
12471
  }
12221
12472
  function createClientMetadataStore(db) {
12222
- db.createObjectStore(DbClientMetadata.store, {
12223
- keyPath: DbClientMetadata.keyPath
12473
+ db.createObjectStore(DbClientMetadataStore, {
12474
+ keyPath: DbClientMetadataKeyPath
12224
12475
  });
12225
12476
  }
12226
12477
  function createBundlesStore(db) {
12227
- db.createObjectStore(DbBundle.store, {
12228
- keyPath: DbBundle.keyPath
12478
+ db.createObjectStore(DbBundleStore, {
12479
+ keyPath: DbBundleKeyPath
12229
12480
  });
12230
12481
  }
12231
12482
  function createNamedQueriesStore(db) {
12232
- db.createObjectStore(DbNamedQuery.store, {
12233
- keyPath: DbNamedQuery.keyPath
12483
+ db.createObjectStore(DbNamedQueryStore, {
12484
+ keyPath: DbNamedQueryKeyPath
12234
12485
  });
12235
12486
  }
12236
12487
  function createFieldIndex(db) {
12237
- const indexConfiguratioStore = db.createObjectStore(DbIndexConfiguration.store, {
12238
- keyPath: DbIndexConfiguration.keyPath,
12488
+ const indexConfiguratioStore = db.createObjectStore(DbIndexConfigurationStore, {
12489
+ keyPath: DbIndexConfigurationKeyPath,
12239
12490
  autoIncrement: true
12240
12491
  });
12241
- indexConfiguratioStore.createIndex(DbIndexConfiguration.collectionGroupIndex, DbIndexConfiguration.collectionGroupIndexPath, { unique: false });
12242
- const indexStateStore = db.createObjectStore(DbIndexState.store, {
12243
- keyPath: DbIndexState.keyPath
12492
+ indexConfiguratioStore.createIndex(DbIndexConfigurationCollectionGroupIndex, DbIndexConfigurationCollectionGroupIndexPath, { unique: false });
12493
+ const indexStateStore = db.createObjectStore(DbIndexStateStore, {
12494
+ keyPath: DbIndexStateKeyPath
12244
12495
  });
12245
- indexStateStore.createIndex(DbIndexState.sequenceNumberIndex, DbIndexState.sequenceNumberIndexPath, { unique: false });
12246
- const indexEntryStore = db.createObjectStore(DbIndexEntry.store, {
12247
- keyPath: DbIndexEntry.keyPath
12496
+ indexStateStore.createIndex(DbIndexStateSequenceNumberIndex, DbIndexStateSequenceNumberIndexPath, { unique: false });
12497
+ const indexEntryStore = db.createObjectStore(DbIndexEntryStore, {
12498
+ keyPath: DbIndexEntryKeyPath
12248
12499
  });
12249
- indexEntryStore.createIndex(DbIndexEntry.documentKeyIndex, DbIndexEntry.documentKeyIndexPath, { unique: false });
12500
+ indexEntryStore.createIndex(DbIndexEntryDocumentKeyIndex, DbIndexEntryDocumentKeyIndexPath, { unique: false });
12250
12501
  }
12251
12502
  function createDocumentOverlayStore(db) {
12252
- const documentOverlayStore = db.createObjectStore(DbDocumentOverlay.store, {
12253
- keyPath: DbDocumentOverlay.keyPath
12503
+ const documentOverlayStore = db.createObjectStore(DbDocumentOverlayStore, {
12504
+ keyPath: DbDocumentOverlayKeyPath
12254
12505
  });
12255
- documentOverlayStore.createIndex(DbDocumentOverlay.collectionPathOverlayIndex, DbDocumentOverlay.collectionPathOverlayIndexPath, { unique: false });
12256
- documentOverlayStore.createIndex(DbDocumentOverlay.collectionGroupOverlayIndex, DbDocumentOverlay.collectionGroupOverlayIndexPath, { unique: false });
12506
+ documentOverlayStore.createIndex(DbDocumentOverlayCollectionPathOverlayIndex, DbDocumentOverlayCollectionPathOverlayIndexPath, { unique: false });
12507
+ documentOverlayStore.createIndex(DbDocumentOverlayCollectionGroupOverlayIndex, DbDocumentOverlayCollectionGroupOverlayIndexPath, { unique: false });
12257
12508
  }
12258
12509
 
12259
12510
  /**
@@ -12501,7 +12752,12 @@ class IndexedDbPersistence {
12501
12752
  return this.runTransaction('updateClientMetadataAndTryBecomePrimary', 'readwrite', txn => {
12502
12753
  const metadataStore = clientMetadataStore(txn);
12503
12754
  return metadataStore
12504
- .put(new DbClientMetadata(this.clientId, Date.now(), this.networkEnabled, this.inForeground))
12755
+ .put({
12756
+ clientId: this.clientId,
12757
+ updateTimeMs: Date.now(),
12758
+ networkEnabled: this.networkEnabled,
12759
+ inForeground: this.inForeground
12760
+ })
12505
12761
  .next(() => {
12506
12762
  if (this.isPrimary) {
12507
12763
  return this.verifyPrimaryLease(txn).next(success => {
@@ -12547,7 +12803,7 @@ class IndexedDbPersistence {
12547
12803
  }
12548
12804
  verifyPrimaryLease(txn) {
12549
12805
  const store = primaryClientStore(txn);
12550
- return store.get(DbPrimaryClient.key).next(primaryClient => {
12806
+ return store.get(DbPrimaryClientKey).next(primaryClient => {
12551
12807
  return PersistencePromise.resolve(this.isLocalClient(primaryClient));
12552
12808
  });
12553
12809
  }
@@ -12565,7 +12821,7 @@ class IndexedDbPersistence {
12565
12821
  !this.isWithinAge(this.lastGarbageCollectionTime, MAX_CLIENT_AGE_MS)) {
12566
12822
  this.lastGarbageCollectionTime = Date.now();
12567
12823
  const inactiveClients = await this.runTransaction('maybeGarbageCollectMultiClientState', 'readwrite-primary', txn => {
12568
- const metadataStore = getStore(txn, DbClientMetadata.store);
12824
+ const metadataStore = getStore(txn, DbClientMetadataStore);
12569
12825
  return metadataStore.loadAll().next(existingClients => {
12570
12826
  const active = this.filterActiveClients(existingClients, MAX_CLIENT_AGE_MS);
12571
12827
  const inactive = existingClients.filter(client => active.indexOf(client) === -1);
@@ -12619,7 +12875,7 @@ class IndexedDbPersistence {
12619
12875
  }
12620
12876
  const store = primaryClientStore(txn);
12621
12877
  return store
12622
- .get(DbPrimaryClient.key)
12878
+ .get(DbPrimaryClientKey)
12623
12879
  .next(currentPrimary => {
12624
12880
  const currentLeaseIsValid = currentPrimary !== null &&
12625
12881
  this.isWithinAge(currentPrimary.leaseTimestampMs, MAX_PRIMARY_ELIGIBLE_AGE_MS) &&
@@ -12700,7 +12956,7 @@ class IndexedDbPersistence {
12700
12956
  this.detachWindowUnloadHook();
12701
12957
  // Use `SimpleDb.runTransaction` directly to avoid failing if another tab
12702
12958
  // has obtained the primary lease.
12703
- await this.simpleDb.runTransaction('shutdown', 'readwrite', [DbPrimaryClient.store, DbClientMetadata.store], simpleDbTxn => {
12959
+ await this.simpleDb.runTransaction('shutdown', 'readwrite', [DbPrimaryClientStore, DbClientMetadataStore], simpleDbTxn => {
12704
12960
  const persistenceTransaction = new IndexedDbTransaction(simpleDbTxn, ListenSequence.INVALID);
12705
12961
  return this.releasePrimaryLeaseIfHeld(persistenceTransaction).next(() => this.removeClientMetadata(persistenceTransaction));
12706
12962
  });
@@ -12807,7 +13063,7 @@ class IndexedDbPersistence {
12807
13063
  // be turned off.
12808
13064
  verifyAllowTabSynchronization(txn) {
12809
13065
  const store = primaryClientStore(txn);
12810
- return store.get(DbPrimaryClient.key).next(currentPrimary => {
13066
+ return store.get(DbPrimaryClientKey).next(currentPrimary => {
12811
13067
  const currentLeaseIsValid = currentPrimary !== null &&
12812
13068
  this.isWithinAge(currentPrimary.leaseTimestampMs, MAX_PRIMARY_ELIGIBLE_AGE_MS) &&
12813
13069
  !this.isClientZombied(currentPrimary.ownerId);
@@ -12825,8 +13081,12 @@ class IndexedDbPersistence {
12825
13081
  * method does not verify that the client is eligible for this lease.
12826
13082
  */
12827
13083
  acquireOrExtendPrimaryLease(txn) {
12828
- const newPrimary = new DbPrimaryClient(this.clientId, this.allowTabSynchronization, Date.now());
12829
- return primaryClientStore(txn).put(DbPrimaryClient.key, newPrimary);
13084
+ const newPrimary = {
13085
+ ownerId: this.clientId,
13086
+ allowTabSynchronization: this.allowTabSynchronization,
13087
+ leaseTimestampMs: Date.now()
13088
+ };
13089
+ return primaryClientStore(txn).put(DbPrimaryClientKey, newPrimary);
12830
13090
  }
12831
13091
  static isAvailable() {
12832
13092
  return SimpleDb.isAvailable();
@@ -12834,10 +13094,10 @@ class IndexedDbPersistence {
12834
13094
  /** Checks the primary lease and removes it if we are the current primary. */
12835
13095
  releasePrimaryLeaseIfHeld(txn) {
12836
13096
  const store = primaryClientStore(txn);
12837
- return store.get(DbPrimaryClient.key).next(primaryClient => {
13097
+ return store.get(DbPrimaryClientKey).next(primaryClient => {
12838
13098
  if (this.isLocalClient(primaryClient)) {
12839
13099
  logDebug(LOG_TAG$d, 'Releasing primary lease.');
12840
- return store.delete(DbPrimaryClient.key);
13100
+ return store.delete(DbPrimaryClientKey);
12841
13101
  }
12842
13102
  else {
12843
13103
  return PersistencePromise.resolve();
@@ -12971,13 +13231,13 @@ class IndexedDbPersistence {
12971
13231
  * Helper to get a typed SimpleDbStore for the primary client object store.
12972
13232
  */
12973
13233
  function primaryClientStore(txn) {
12974
- return getStore(txn, DbPrimaryClient.store);
13234
+ return getStore(txn, DbPrimaryClientStore);
12975
13235
  }
12976
13236
  /**
12977
13237
  * Helper to get a typed SimpleDbStore for the client metadata object store.
12978
13238
  */
12979
13239
  function clientMetadataStore(txn) {
12980
- return getStore(txn, DbClientMetadata.store);
13240
+ return getStore(txn, DbClientMetadataStore);
12981
13241
  }
12982
13242
  /**
12983
13243
  * Generates a string used as a prefix when storing data in IndexedDB and
@@ -14040,7 +14300,7 @@ class MemoryDocumentOverlayCache {
14040
14300
  return PersistencePromise.resolve(this.overlays.get(key));
14041
14301
  }
14042
14302
  saveOverlays(transaction, largestBatchId, overlays) {
14043
- overlays.forEach(mutation => {
14303
+ overlays.forEach((_, mutation) => {
14044
14304
  this.saveOverlay(transaction, largestBatchId, mutation);
14045
14305
  });
14046
14306
  return PersistencePromise.resolve();
@@ -14054,7 +14314,7 @@ class MemoryDocumentOverlayCache {
14054
14314
  return PersistencePromise.resolve();
14055
14315
  }
14056
14316
  getOverlaysForCollection(transaction, collection, sinceBatchId) {
14057
- const result = new Map();
14317
+ const result = newOverlayMap();
14058
14318
  const immediateChildrenPathLength = collection.length + 1;
14059
14319
  const prefix = new DocumentKey(collection.child(''));
14060
14320
  const iter = this.overlays.getIteratorFrom(prefix);
@@ -14088,19 +14348,19 @@ class MemoryDocumentOverlayCache {
14088
14348
  if (overlay.largestBatchId > sinceBatchId) {
14089
14349
  let overlaysForBatchId = batchIdToOverlays.get(overlay.largestBatchId);
14090
14350
  if (overlaysForBatchId === null) {
14091
- overlaysForBatchId = new Map();
14351
+ overlaysForBatchId = newOverlayMap();
14092
14352
  batchIdToOverlays = batchIdToOverlays.insert(overlay.largestBatchId, overlaysForBatchId);
14093
14353
  }
14094
14354
  overlaysForBatchId.set(overlay.getKey(), overlay);
14095
14355
  }
14096
14356
  }
14097
- const result = new Map();
14357
+ const result = newOverlayMap();
14098
14358
  const batchIter = batchIdToOverlays.getIterator();
14099
14359
  while (batchIter.hasNext()) {
14100
14360
  const entry = batchIter.getNext();
14101
14361
  const overlays = entry.value;
14102
- overlays.forEach((overlay, key) => result.set(key, overlay));
14103
- if (result.size >= count) {
14362
+ overlays.forEach((key, overlay) => result.set(key, overlay));
14363
+ if (result.size() >= count) {
14104
14364
  break;
14105
14365
  }
14106
14366
  }
@@ -14113,16 +14373,19 @@ class MemoryDocumentOverlayCache {
14113
14373
  // Remove the association of the overlay to its batch id.
14114
14374
  const existing = this.overlays.get(mutation.key);
14115
14375
  if (existing !== null) {
14116
- this.overlayByBatchId.get(existing.largestBatchId).delete(mutation.key);
14376
+ const newSet = this.overlayByBatchId
14377
+ .get(existing.largestBatchId)
14378
+ .delete(mutation.key);
14379
+ this.overlayByBatchId.set(existing.largestBatchId, newSet);
14117
14380
  }
14118
14381
  this.overlays = this.overlays.insert(mutation.key, new Overlay(largestBatchId, mutation));
14119
14382
  // Create the association of this overlay to the given largestBatchId.
14120
14383
  let batch = this.overlayByBatchId.get(largestBatchId);
14121
14384
  if (batch === undefined) {
14122
- batch = new Set();
14385
+ batch = documentKeySet();
14123
14386
  this.overlayByBatchId.set(largestBatchId, batch);
14124
14387
  }
14125
- batch.add(mutation.key);
14388
+ this.overlayByBatchId.set(largestBatchId, batch.add(mutation.key));
14126
14389
  }
14127
14390
  }
14128
14391
 
@@ -15973,9 +16236,13 @@ function createMetadata(databasePath, authToken, appCheckToken, appId) {
15973
16236
  metadata.set('X-Firebase-GMPID', appId);
15974
16237
  }
15975
16238
  metadata.set('X-Goog-Api-Client', X_GOOG_API_CLIENT_VALUE);
15976
- // This header is used to improve routing and project isolation by the
16239
+ // These headers are used to improve routing and project isolation by the
15977
16240
  // backend.
16241
+ // TODO(b/199767712): We are keeping 'Google-Cloud-Resource-Prefix' until Emulators can be
16242
+ // released with cl/428820046. Currently blocked because Emulators are now built with Java
16243
+ // 11 from Google3.
15978
16244
  metadata.set('Google-Cloud-Resource-Prefix', databasePath);
16245
+ metadata.set('x-goog-request-params', databasePath);
15979
16246
  return metadata;
15980
16247
  }
15981
16248
  /**