@firebase/firestore 3.4.9-canary.f84cacd70 → 3.4.10

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 (107) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/firestore/src/core/component_provider.d.ts +11 -6
  3. package/dist/firestore/src/local/document_overlay_cache.d.ts +1 -1
  4. package/dist/firestore/src/local/index_backfiller.d.ts +44 -0
  5. package/dist/firestore/src/local/index_manager.d.ts +2 -0
  6. package/dist/firestore/src/local/indexeddb_document_overlay_cache.d.ts +1 -1
  7. package/dist/firestore/src/local/indexeddb_index_manager.d.ts +1 -0
  8. package/dist/firestore/src/local/local_documents_view.d.ts +20 -4
  9. package/dist/firestore/src/local/local_store.d.ts +9 -0
  10. package/dist/firestore/src/local/lru_garbage_collector_impl.d.ts +5 -5
  11. package/dist/firestore/src/local/memory_document_overlay_cache.d.ts +1 -1
  12. package/dist/firestore/src/local/memory_index_manager.d.ts +1 -0
  13. package/dist/firestore/src/local/persistence.d.ts +3 -5
  14. package/dist/firestore/src/local/persistence_promise.d.ts +11 -0
  15. package/dist/firestore/src/model/collections.d.ts +6 -2
  16. package/dist/firestore/src/model/mutation_batch.d.ts +2 -3
  17. package/dist/firestore/src/util/async_queue.d.ts +5 -1
  18. package/dist/firestore/test/unit/local/index_backfiller.test.d.ts +17 -0
  19. package/dist/firestore/test/unit/local/test_document_overlay_cache.d.ts +1 -1
  20. package/dist/firestore/test/unit/specs/spec_test_components.d.ts +5 -3
  21. package/dist/firestore/test/util/helpers.d.ts +1 -0
  22. package/dist/index.esm2017.js +7499 -7351
  23. package/dist/index.esm2017.js.map +1 -1
  24. package/dist/index.esm5.js +8227 -7997
  25. package/dist/index.esm5.js.map +1 -1
  26. package/dist/index.node.cjs.js +1552 -1309
  27. package/dist/index.node.cjs.js.map +1 -1
  28. package/dist/index.node.mjs +1552 -1309
  29. package/dist/index.node.mjs.map +1 -1
  30. package/dist/index.rn.js +7491 -7342
  31. package/dist/index.rn.js.map +1 -1
  32. package/dist/internal.d.ts +146 -16
  33. package/dist/lite/firestore/src/core/component_provider.d.ts +11 -6
  34. package/dist/lite/firestore/src/local/document_overlay_cache.d.ts +1 -1
  35. package/dist/lite/firestore/src/local/index_backfiller.d.ts +44 -0
  36. package/dist/lite/firestore/src/local/index_manager.d.ts +2 -0
  37. package/dist/lite/firestore/src/local/indexeddb_document_overlay_cache.d.ts +1 -1
  38. package/dist/lite/firestore/src/local/indexeddb_index_manager.d.ts +1 -0
  39. package/dist/lite/firestore/src/local/local_documents_view.d.ts +20 -4
  40. package/dist/lite/firestore/src/local/local_store.d.ts +9 -0
  41. package/dist/lite/firestore/src/local/lru_garbage_collector_impl.d.ts +5 -5
  42. package/dist/lite/firestore/src/local/memory_document_overlay_cache.d.ts +1 -1
  43. package/dist/lite/firestore/src/local/memory_index_manager.d.ts +1 -0
  44. package/dist/lite/firestore/src/local/persistence.d.ts +3 -5
  45. package/dist/lite/firestore/src/local/persistence_promise.d.ts +11 -0
  46. package/dist/lite/firestore/src/model/collections.d.ts +6 -2
  47. package/dist/lite/firestore/src/model/mutation_batch.d.ts +2 -3
  48. package/dist/lite/firestore/src/util/async_queue.d.ts +5 -1
  49. package/dist/lite/firestore/test/unit/local/index_backfiller.test.d.ts +17 -0
  50. package/dist/lite/firestore/test/unit/local/test_document_overlay_cache.d.ts +1 -1
  51. package/dist/lite/firestore/test/unit/specs/spec_test_components.d.ts +5 -3
  52. package/dist/lite/firestore/test/util/helpers.d.ts +1 -0
  53. package/dist/lite/index.browser.esm2017.js +2 -2
  54. package/dist/lite/index.browser.esm2017.js.map +1 -1
  55. package/dist/lite/index.browser.esm5.js +2 -2
  56. package/dist/lite/index.browser.esm5.js.map +1 -1
  57. package/dist/lite/index.node.cjs.js +2 -2
  58. package/dist/lite/index.node.cjs.js.map +1 -1
  59. package/dist/lite/index.node.mjs +2 -2
  60. package/dist/lite/index.node.mjs.map +1 -1
  61. package/dist/lite/index.rn.esm2017.js +2 -2
  62. package/dist/lite/index.rn.esm2017.js.map +1 -1
  63. package/dist/lite/internal.d.ts +5 -1
  64. package/dist/lite/packages/firestore/src/core/component_provider.d.ts +11 -6
  65. package/dist/lite/packages/firestore/src/local/document_overlay_cache.d.ts +1 -1
  66. package/dist/lite/packages/firestore/src/local/index_backfiller.d.ts +44 -0
  67. package/dist/lite/packages/firestore/src/local/index_manager.d.ts +2 -0
  68. package/dist/lite/packages/firestore/src/local/indexeddb_document_overlay_cache.d.ts +1 -1
  69. package/dist/lite/packages/firestore/src/local/indexeddb_index_manager.d.ts +1 -0
  70. package/dist/lite/packages/firestore/src/local/local_documents_view.d.ts +20 -4
  71. package/dist/lite/packages/firestore/src/local/local_store.d.ts +9 -0
  72. package/dist/lite/packages/firestore/src/local/lru_garbage_collector_impl.d.ts +5 -5
  73. package/dist/lite/packages/firestore/src/local/memory_document_overlay_cache.d.ts +1 -1
  74. package/dist/lite/packages/firestore/src/local/memory_index_manager.d.ts +1 -0
  75. package/dist/lite/packages/firestore/src/local/persistence.d.ts +3 -5
  76. package/dist/lite/packages/firestore/src/local/persistence_promise.d.ts +11 -0
  77. package/dist/lite/packages/firestore/src/model/collections.d.ts +6 -2
  78. package/dist/lite/packages/firestore/src/model/mutation_batch.d.ts +2 -3
  79. package/dist/lite/packages/firestore/src/util/async_queue.d.ts +5 -1
  80. package/dist/lite/packages/firestore/test/unit/local/index_backfiller.test.d.ts +17 -0
  81. package/dist/lite/packages/firestore/test/unit/local/test_document_overlay_cache.d.ts +1 -1
  82. package/dist/lite/packages/firestore/test/unit/specs/spec_test_components.d.ts +5 -3
  83. package/dist/lite/packages/firestore/test/util/helpers.d.ts +1 -0
  84. package/dist/lite/private.d.ts +5 -1
  85. package/dist/packages/firestore/dist/index.esm2017.d.ts +200 -200
  86. package/dist/packages/firestore/src/core/component_provider.d.ts +11 -6
  87. package/dist/packages/firestore/src/local/document_overlay_cache.d.ts +1 -1
  88. package/dist/packages/firestore/src/local/index_backfiller.d.ts +44 -0
  89. package/dist/packages/firestore/src/local/index_manager.d.ts +2 -0
  90. package/dist/packages/firestore/src/local/indexeddb_document_overlay_cache.d.ts +1 -1
  91. package/dist/packages/firestore/src/local/indexeddb_index_manager.d.ts +1 -0
  92. package/dist/packages/firestore/src/local/local_documents_view.d.ts +20 -4
  93. package/dist/packages/firestore/src/local/local_store.d.ts +9 -0
  94. package/dist/packages/firestore/src/local/lru_garbage_collector_impl.d.ts +5 -5
  95. package/dist/packages/firestore/src/local/memory_document_overlay_cache.d.ts +1 -1
  96. package/dist/packages/firestore/src/local/memory_index_manager.d.ts +1 -0
  97. package/dist/packages/firestore/src/local/persistence.d.ts +3 -5
  98. package/dist/packages/firestore/src/local/persistence_promise.d.ts +11 -0
  99. package/dist/packages/firestore/src/model/collections.d.ts +6 -2
  100. package/dist/packages/firestore/src/model/mutation_batch.d.ts +2 -3
  101. package/dist/packages/firestore/src/util/async_queue.d.ts +5 -1
  102. package/dist/packages/firestore/test/unit/local/index_backfiller.test.d.ts +17 -0
  103. package/dist/packages/firestore/test/unit/local/test_document_overlay_cache.d.ts +1 -1
  104. package/dist/packages/firestore/test/unit/specs/spec_test_components.d.ts +5 -3
  105. package/dist/packages/firestore/test/util/helpers.d.ts +1 -0
  106. package/dist/private.d.ts +146 -16
  107. package/package.json +9 -9
@@ -8,7 +8,7 @@ import * as grpc from '@grpc/grpc-js';
8
8
  import * as protoLoader from '@grpc/proto-loader';
9
9
 
10
10
  const name = "@firebase/firestore";
11
- const version$1 = "3.4.9-canary.f84cacd70";
11
+ const version$1 = "3.4.10";
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.8.2-canary.f84cacd70";
64
+ const version = "9.8.3";
65
65
 
66
66
  /**
67
67
  * @license
@@ -768,7 +768,7 @@ function makeAuthCredentialsProvider(credentials) {
768
768
 
769
769
  /**
770
770
  * @license
771
- * Copyright 2018 Google LLC
771
+ * Copyright 2020 Google LLC
772
772
  *
773
773
  * Licensed under the Apache License, Version 2.0 (the "License");
774
774
  * you may not use this file except in compliance with the License.
@@ -783,32 +783,75 @@ function makeAuthCredentialsProvider(credentials) {
783
783
  * limitations under the License.
784
784
  */
785
785
  /**
786
- * `ListenSequence` is a monotonic sequence. It is initialized with a minimum value to
787
- * exceed. All subsequent calls to next will return increasing values. If provided with a
788
- * `SequenceNumberSyncer`, it will additionally bump its next value when told of a new value, as
789
- * well as write out sequence numbers that it produces via `next()`.
786
+ * Generates `nBytes` of random bytes.
787
+ *
788
+ * If `nBytes < 0` , an error will be thrown.
790
789
  */
791
- class ListenSequence {
792
- constructor(previousValue, sequenceNumberSyncer) {
793
- this.previousValue = previousValue;
794
- if (sequenceNumberSyncer) {
795
- sequenceNumberSyncer.sequenceNumberHandler = sequenceNumber => this.setPreviousValue(sequenceNumber);
796
- this.writeNewSequenceNumber = sequenceNumber => sequenceNumberSyncer.writeSequenceNumber(sequenceNumber);
790
+ function randomBytes(nBytes) {
791
+ return randomBytes$1(nBytes);
792
+ }
793
+
794
+ /**
795
+ * @license
796
+ * Copyright 2017 Google LLC
797
+ *
798
+ * Licensed under the Apache License, Version 2.0 (the "License");
799
+ * you may not use this file except in compliance with the License.
800
+ * You may obtain a copy of the License at
801
+ *
802
+ * http://www.apache.org/licenses/LICENSE-2.0
803
+ *
804
+ * Unless required by applicable law or agreed to in writing, software
805
+ * distributed under the License is distributed on an "AS IS" BASIS,
806
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
807
+ * See the License for the specific language governing permissions and
808
+ * limitations under the License.
809
+ */
810
+ class AutoId {
811
+ static newId() {
812
+ // Alphanumeric characters
813
+ const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
814
+ // The largest byte value that is a multiple of `char.length`.
815
+ const maxMultiple = Math.floor(256 / chars.length) * chars.length;
816
+ let autoId = '';
817
+ const targetLength = 20;
818
+ while (autoId.length < targetLength) {
819
+ const bytes = randomBytes(40);
820
+ for (let i = 0; i < bytes.length; ++i) {
821
+ // Only accept values that are [0, maxMultiple), this ensures they can
822
+ // be evenly mapped to indices of `chars` via a modulo operation.
823
+ if (autoId.length < targetLength && bytes[i] < maxMultiple) {
824
+ autoId += chars.charAt(bytes[i] % chars.length);
825
+ }
826
+ }
797
827
  }
828
+ return autoId;
798
829
  }
799
- setPreviousValue(externalPreviousValue) {
800
- this.previousValue = Math.max(externalPreviousValue, this.previousValue);
801
- return this.previousValue;
830
+ }
831
+ function primitiveComparator(left, right) {
832
+ if (left < right) {
833
+ return -1;
802
834
  }
803
- next() {
804
- const nextValue = ++this.previousValue;
805
- if (this.writeNewSequenceNumber) {
806
- this.writeNewSequenceNumber(nextValue);
807
- }
808
- return nextValue;
835
+ if (left > right) {
836
+ return 1;
809
837
  }
838
+ return 0;
810
839
  }
811
- ListenSequence.INVALID = -1;
840
+ /** Helper to compare arrays using isEqual(). */
841
+ function arrayEquals(left, right, comparator) {
842
+ if (left.length !== right.length) {
843
+ return false;
844
+ }
845
+ return left.every((value, index) => comparator(value, right[index]));
846
+ }
847
+ /**
848
+ * Returns the immediate lexicographically-following string. This is useful to
849
+ * construct an inclusive range for indexeddb iterators.
850
+ */
851
+ function immediateSuccessor(s) {
852
+ // Return the input string, with an additional NUL byte appended.
853
+ return s + '\0';
854
+ }
812
855
 
813
856
  /**
814
857
  * @license
@@ -826,75 +869,298 @@ ListenSequence.INVALID = -1;
826
869
  * See the License for the specific language governing permissions and
827
870
  * limitations under the License.
828
871
  */
829
- const DOCUMENT_KEY_NAME = '__name__';
872
+ // The earliest date supported by Firestore timestamps (0001-01-01T00:00:00Z).
873
+ const MIN_SECONDS = -62135596800;
874
+ // Number of nanoseconds in a millisecond.
875
+ const MS_TO_NANOS = 1e6;
830
876
  /**
831
- * Path represents an ordered sequence of string segments.
877
+ * A `Timestamp` represents a point in time independent of any time zone or
878
+ * calendar, represented as seconds and fractions of seconds at nanosecond
879
+ * resolution in UTC Epoch time.
880
+ *
881
+ * It is encoded using the Proleptic Gregorian Calendar which extends the
882
+ * Gregorian calendar backwards to year one. It is encoded assuming all minutes
883
+ * are 60 seconds long, i.e. leap seconds are "smeared" so that no leap second
884
+ * table is needed for interpretation. Range is from 0001-01-01T00:00:00Z to
885
+ * 9999-12-31T23:59:59.999999999Z.
886
+ *
887
+ * For examples and further specifications, refer to the
888
+ * {@link https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto | Timestamp definition}.
832
889
  */
833
- class BasePath {
834
- constructor(segments, offset, length) {
835
- if (offset === undefined) {
836
- offset = 0;
890
+ class Timestamp {
891
+ /**
892
+ * Creates a new timestamp.
893
+ *
894
+ * @param seconds - The number of seconds of UTC time since Unix epoch
895
+ * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
896
+ * 9999-12-31T23:59:59Z inclusive.
897
+ * @param nanoseconds - The non-negative fractions of a second at nanosecond
898
+ * resolution. Negative second values with fractions must still have
899
+ * non-negative nanoseconds values that count forward in time. Must be
900
+ * from 0 to 999,999,999 inclusive.
901
+ */
902
+ constructor(
903
+ /**
904
+ * The number of seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z.
905
+ */
906
+ seconds,
907
+ /**
908
+ * The fractions of a second at nanosecond resolution.*
909
+ */
910
+ nanoseconds) {
911
+ this.seconds = seconds;
912
+ this.nanoseconds = nanoseconds;
913
+ if (nanoseconds < 0) {
914
+ throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp nanoseconds out of range: ' + nanoseconds);
837
915
  }
838
- else if (offset > segments.length) {
839
- fail();
916
+ if (nanoseconds >= 1e9) {
917
+ throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp nanoseconds out of range: ' + nanoseconds);
840
918
  }
841
- if (length === undefined) {
842
- length = segments.length - offset;
919
+ if (seconds < MIN_SECONDS) {
920
+ throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp seconds out of range: ' + seconds);
843
921
  }
844
- else if (length > segments.length - offset) {
845
- fail();
922
+ // This will break in the year 10,000.
923
+ if (seconds >= 253402300800) {
924
+ throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp seconds out of range: ' + seconds);
846
925
  }
847
- this.segments = segments;
848
- this.offset = offset;
849
- this.len = length;
850
926
  }
851
- get length() {
852
- return this.len;
853
- }
854
- isEqual(other) {
855
- return BasePath.comparator(this, other) === 0;
927
+ /**
928
+ * Creates a new timestamp with the current date, with millisecond precision.
929
+ *
930
+ * @returns a new timestamp representing the current date.
931
+ */
932
+ static now() {
933
+ return Timestamp.fromMillis(Date.now());
856
934
  }
857
- child(nameOrPath) {
858
- const segments = this.segments.slice(this.offset, this.limit());
859
- if (nameOrPath instanceof BasePath) {
860
- nameOrPath.forEach(segment => {
861
- segments.push(segment);
862
- });
863
- }
864
- else {
865
- segments.push(nameOrPath);
866
- }
867
- return this.construct(segments);
935
+ /**
936
+ * Creates a new timestamp from the given date.
937
+ *
938
+ * @param date - The date to initialize the `Timestamp` from.
939
+ * @returns A new `Timestamp` representing the same point in time as the given
940
+ * date.
941
+ */
942
+ static fromDate(date) {
943
+ return Timestamp.fromMillis(date.getTime());
868
944
  }
869
- /** The index of one past the last segment of the path. */
870
- limit() {
871
- return this.offset + this.length;
945
+ /**
946
+ * Creates a new timestamp from the given number of milliseconds.
947
+ *
948
+ * @param milliseconds - Number of milliseconds since Unix epoch
949
+ * 1970-01-01T00:00:00Z.
950
+ * @returns A new `Timestamp` representing the same point in time as the given
951
+ * number of milliseconds.
952
+ */
953
+ static fromMillis(milliseconds) {
954
+ const seconds = Math.floor(milliseconds / 1000);
955
+ const nanos = Math.floor((milliseconds - seconds * 1000) * MS_TO_NANOS);
956
+ return new Timestamp(seconds, nanos);
872
957
  }
873
- popFirst(size) {
874
- size = size === undefined ? 1 : size;
875
- return this.construct(this.segments, this.offset + size, this.length - size);
958
+ /**
959
+ * Converts a `Timestamp` to a JavaScript `Date` object. This conversion
960
+ * causes a loss of precision since `Date` objects only support millisecond
961
+ * precision.
962
+ *
963
+ * @returns JavaScript `Date` object representing the same point in time as
964
+ * this `Timestamp`, with millisecond precision.
965
+ */
966
+ toDate() {
967
+ return new Date(this.toMillis());
876
968
  }
877
- popLast() {
878
- return this.construct(this.segments, this.offset, this.length - 1);
969
+ /**
970
+ * Converts a `Timestamp` to a numeric timestamp (in milliseconds since
971
+ * epoch). This operation causes a loss of precision.
972
+ *
973
+ * @returns The point in time corresponding to this timestamp, represented as
974
+ * the number of milliseconds since Unix epoch 1970-01-01T00:00:00Z.
975
+ */
976
+ toMillis() {
977
+ return this.seconds * 1000 + this.nanoseconds / MS_TO_NANOS;
879
978
  }
880
- firstSegment() {
881
- return this.segments[this.offset];
979
+ _compareTo(other) {
980
+ if (this.seconds === other.seconds) {
981
+ return primitiveComparator(this.nanoseconds, other.nanoseconds);
982
+ }
983
+ return primitiveComparator(this.seconds, other.seconds);
882
984
  }
883
- lastSegment() {
884
- return this.get(this.length - 1);
985
+ /**
986
+ * Returns true if this `Timestamp` is equal to the provided one.
987
+ *
988
+ * @param other - The `Timestamp` to compare against.
989
+ * @returns true if this `Timestamp` is equal to the provided one.
990
+ */
991
+ isEqual(other) {
992
+ return (other.seconds === this.seconds && other.nanoseconds === this.nanoseconds);
885
993
  }
886
- get(index) {
887
- return this.segments[this.offset + index];
994
+ /** Returns a textual representation of this `Timestamp`. */
995
+ toString() {
996
+ return ('Timestamp(seconds=' +
997
+ this.seconds +
998
+ ', nanoseconds=' +
999
+ this.nanoseconds +
1000
+ ')');
888
1001
  }
889
- isEmpty() {
890
- return this.length === 0;
1002
+ /** Returns a JSON-serializable representation of this `Timestamp`. */
1003
+ toJSON() {
1004
+ return { seconds: this.seconds, nanoseconds: this.nanoseconds };
891
1005
  }
892
- isPrefixOf(other) {
893
- if (other.length < this.length) {
894
- return false;
895
- }
896
- for (let i = 0; i < this.length; i++) {
897
- if (this.get(i) !== other.get(i)) {
1006
+ /**
1007
+ * Converts this object to a primitive string, which allows `Timestamp` objects
1008
+ * to be compared using the `>`, `<=`, `>=` and `>` operators.
1009
+ */
1010
+ valueOf() {
1011
+ // This method returns a string of the form <seconds>.<nanoseconds> where
1012
+ // <seconds> is translated to have a non-negative value and both <seconds>
1013
+ // and <nanoseconds> are left-padded with zeroes to be a consistent length.
1014
+ // Strings with this format then have a lexiographical ordering that matches
1015
+ // the expected ordering. The <seconds> translation is done to avoid having
1016
+ // a leading negative sign (i.e. a leading '-' character) in its string
1017
+ // representation, which would affect its lexiographical ordering.
1018
+ const adjustedSeconds = this.seconds - MIN_SECONDS;
1019
+ // Note: Up to 12 decimal digits are required to represent all valid
1020
+ // 'seconds' values.
1021
+ const formattedSeconds = String(adjustedSeconds).padStart(12, '0');
1022
+ const formattedNanoseconds = String(this.nanoseconds).padStart(9, '0');
1023
+ return formattedSeconds + '.' + formattedNanoseconds;
1024
+ }
1025
+ }
1026
+
1027
+ /**
1028
+ * @license
1029
+ * Copyright 2017 Google LLC
1030
+ *
1031
+ * Licensed under the Apache License, Version 2.0 (the "License");
1032
+ * you may not use this file except in compliance with the License.
1033
+ * You may obtain a copy of the License at
1034
+ *
1035
+ * http://www.apache.org/licenses/LICENSE-2.0
1036
+ *
1037
+ * Unless required by applicable law or agreed to in writing, software
1038
+ * distributed under the License is distributed on an "AS IS" BASIS,
1039
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1040
+ * See the License for the specific language governing permissions and
1041
+ * limitations under the License.
1042
+ */
1043
+ /**
1044
+ * A version of a document in Firestore. This corresponds to the version
1045
+ * timestamp, such as update_time or read_time.
1046
+ */
1047
+ class SnapshotVersion {
1048
+ constructor(timestamp) {
1049
+ this.timestamp = timestamp;
1050
+ }
1051
+ static fromTimestamp(value) {
1052
+ return new SnapshotVersion(value);
1053
+ }
1054
+ static min() {
1055
+ return new SnapshotVersion(new Timestamp(0, 0));
1056
+ }
1057
+ static max() {
1058
+ return new SnapshotVersion(new Timestamp(253402300799, 1e9 - 1));
1059
+ }
1060
+ compareTo(other) {
1061
+ return this.timestamp._compareTo(other.timestamp);
1062
+ }
1063
+ isEqual(other) {
1064
+ return this.timestamp.isEqual(other.timestamp);
1065
+ }
1066
+ /** Returns a number representation of the version for use in spec tests. */
1067
+ toMicroseconds() {
1068
+ // Convert to microseconds.
1069
+ return this.timestamp.seconds * 1e6 + this.timestamp.nanoseconds / 1000;
1070
+ }
1071
+ toString() {
1072
+ return 'SnapshotVersion(' + this.timestamp.toString() + ')';
1073
+ }
1074
+ toTimestamp() {
1075
+ return this.timestamp;
1076
+ }
1077
+ }
1078
+
1079
+ /**
1080
+ * @license
1081
+ * Copyright 2017 Google LLC
1082
+ *
1083
+ * Licensed under the Apache License, Version 2.0 (the "License");
1084
+ * you may not use this file except in compliance with the License.
1085
+ * You may obtain a copy of the License at
1086
+ *
1087
+ * http://www.apache.org/licenses/LICENSE-2.0
1088
+ *
1089
+ * Unless required by applicable law or agreed to in writing, software
1090
+ * distributed under the License is distributed on an "AS IS" BASIS,
1091
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1092
+ * See the License for the specific language governing permissions and
1093
+ * limitations under the License.
1094
+ */
1095
+ const DOCUMENT_KEY_NAME = '__name__';
1096
+ /**
1097
+ * Path represents an ordered sequence of string segments.
1098
+ */
1099
+ class BasePath {
1100
+ constructor(segments, offset, length) {
1101
+ if (offset === undefined) {
1102
+ offset = 0;
1103
+ }
1104
+ else if (offset > segments.length) {
1105
+ fail();
1106
+ }
1107
+ if (length === undefined) {
1108
+ length = segments.length - offset;
1109
+ }
1110
+ else if (length > segments.length - offset) {
1111
+ fail();
1112
+ }
1113
+ this.segments = segments;
1114
+ this.offset = offset;
1115
+ this.len = length;
1116
+ }
1117
+ get length() {
1118
+ return this.len;
1119
+ }
1120
+ isEqual(other) {
1121
+ return BasePath.comparator(this, other) === 0;
1122
+ }
1123
+ child(nameOrPath) {
1124
+ const segments = this.segments.slice(this.offset, this.limit());
1125
+ if (nameOrPath instanceof BasePath) {
1126
+ nameOrPath.forEach(segment => {
1127
+ segments.push(segment);
1128
+ });
1129
+ }
1130
+ else {
1131
+ segments.push(nameOrPath);
1132
+ }
1133
+ return this.construct(segments);
1134
+ }
1135
+ /** The index of one past the last segment of the path. */
1136
+ limit() {
1137
+ return this.offset + this.length;
1138
+ }
1139
+ popFirst(size) {
1140
+ size = size === undefined ? 1 : size;
1141
+ return this.construct(this.segments, this.offset + size, this.length - size);
1142
+ }
1143
+ popLast() {
1144
+ return this.construct(this.segments, this.offset, this.length - 1);
1145
+ }
1146
+ firstSegment() {
1147
+ return this.segments[this.offset];
1148
+ }
1149
+ lastSegment() {
1150
+ return this.get(this.length - 1);
1151
+ }
1152
+ get(index) {
1153
+ return this.segments[this.offset + index];
1154
+ }
1155
+ isEmpty() {
1156
+ return this.length === 0;
1157
+ }
1158
+ isPrefixOf(other) {
1159
+ if (other.length < this.length) {
1160
+ return false;
1161
+ }
1162
+ for (let i = 0; i < this.length; i++) {
1163
+ if (this.get(i) !== other.get(i)) {
898
1164
  return false;
899
1165
  }
900
1166
  }
@@ -1100,109 +1366,64 @@ class FieldPath$1 extends BasePath {
1100
1366
  * See the License for the specific language governing permissions and
1101
1367
  * limitations under the License.
1102
1368
  */
1103
- const escapeChar = '\u0001';
1104
- const encodedSeparatorChar = '\u0001';
1105
- const encodedNul = '\u0010';
1106
- const encodedEscape = '\u0011';
1107
1369
  /**
1108
- * Encodes a resource path into a IndexedDb-compatible string form.
1370
+ * @internal
1109
1371
  */
1110
- function encodeResourcePath(path) {
1111
- let result = '';
1112
- for (let i = 0; i < path.length; i++) {
1113
- if (result.length > 0) {
1114
- result = encodeSeparator(result);
1115
- }
1116
- result = encodeSegment(path.get(i), result);
1372
+ class DocumentKey {
1373
+ constructor(path) {
1374
+ this.path = path;
1117
1375
  }
1118
- return encodeSeparator(result);
1119
- }
1120
- /** Encodes a single segment of a resource path into the given result */
1121
- function encodeSegment(segment, resultBuf) {
1122
- let result = resultBuf;
1123
- const length = segment.length;
1124
- for (let i = 0; i < length; i++) {
1125
- const c = segment.charAt(i);
1126
- switch (c) {
1127
- case '\0':
1128
- result += escapeChar + encodedNul;
1129
- break;
1130
- case escapeChar:
1131
- result += escapeChar + encodedEscape;
1132
- break;
1133
- default:
1134
- result += c;
1135
- }
1376
+ static fromPath(path) {
1377
+ return new DocumentKey(ResourcePath.fromString(path));
1136
1378
  }
1137
- return result;
1138
- }
1139
- /** Encodes a path separator into the given result */
1140
- function encodeSeparator(result) {
1141
- return result + escapeChar + encodedSeparatorChar;
1142
- }
1143
- /**
1144
- * Decodes the given IndexedDb-compatible string form of a resource path into
1145
- * a ResourcePath instance. Note that this method is not suitable for use with
1146
- * decoding resource names from the server; those are One Platform format
1147
- * strings.
1148
- */
1149
- function decodeResourcePath(path) {
1150
- // Event the empty path must encode as a path of at least length 2. A path
1151
- // with exactly 2 must be the empty path.
1152
- const length = path.length;
1153
- hardAssert(length >= 2);
1154
- if (length === 2) {
1155
- hardAssert(path.charAt(0) === escapeChar && path.charAt(1) === encodedSeparatorChar);
1156
- return ResourcePath.emptyPath();
1379
+ static fromName(name) {
1380
+ return new DocumentKey(ResourcePath.fromString(name).popFirst(5));
1157
1381
  }
1158
- // Escape characters cannot exist past the second-to-last position in the
1159
- // source value.
1160
- const lastReasonableEscapeIndex = length - 2;
1161
- const segments = [];
1162
- let segmentBuilder = '';
1163
- for (let start = 0; start < length;) {
1164
- // The last two characters of a valid encoded path must be a separator, so
1165
- // there must be an end to this segment.
1166
- const end = path.indexOf(escapeChar, start);
1167
- if (end < 0 || end > lastReasonableEscapeIndex) {
1168
- fail();
1169
- }
1170
- const next = path.charAt(end + 1);
1171
- switch (next) {
1172
- case encodedSeparatorChar:
1173
- const currentPiece = path.substring(start, end);
1174
- let segment;
1175
- if (segmentBuilder.length === 0) {
1176
- // Avoid copying for the common case of a segment that excludes \0
1177
- // and \001
1178
- segment = currentPiece;
1179
- }
1180
- else {
1181
- segmentBuilder += currentPiece;
1182
- segment = segmentBuilder;
1183
- segmentBuilder = '';
1184
- }
1185
- segments.push(segment);
1186
- break;
1187
- case encodedNul:
1188
- segmentBuilder += path.substring(start, end);
1189
- segmentBuilder += '\0';
1190
- break;
1191
- case encodedEscape:
1192
- // The escape character can be used in the output to encode itself.
1193
- segmentBuilder += path.substring(start, end + 1);
1194
- break;
1195
- default:
1196
- fail();
1197
- }
1198
- start = end + 2;
1382
+ static empty() {
1383
+ return new DocumentKey(ResourcePath.emptyPath());
1384
+ }
1385
+ get collectionGroup() {
1386
+ return this.path.popLast().lastSegment();
1387
+ }
1388
+ /** Returns true if the document is in the specified collectionId. */
1389
+ hasCollectionId(collectionId) {
1390
+ return (this.path.length >= 2 &&
1391
+ this.path.get(this.path.length - 2) === collectionId);
1392
+ }
1393
+ /** Returns the collection group (i.e. the name of the parent collection) for this key. */
1394
+ getCollectionGroup() {
1395
+ return this.path.get(this.path.length - 2);
1396
+ }
1397
+ /** Returns the fully qualified path to the parent collection. */
1398
+ getCollectionPath() {
1399
+ return this.path.popLast();
1400
+ }
1401
+ isEqual(other) {
1402
+ return (other !== null && ResourcePath.comparator(this.path, other.path) === 0);
1403
+ }
1404
+ toString() {
1405
+ return this.path.toString();
1406
+ }
1407
+ static comparator(k1, k2) {
1408
+ return ResourcePath.comparator(k1.path, k2.path);
1409
+ }
1410
+ static isDocumentKey(path) {
1411
+ return path.length % 2 === 0;
1412
+ }
1413
+ /**
1414
+ * Creates and returns a new document key with the given segments.
1415
+ *
1416
+ * @param segments - The segments of the path to the document
1417
+ * @returns A new instance of DocumentKey
1418
+ */
1419
+ static fromSegments(segments) {
1420
+ return new DocumentKey(new ResourcePath(segments.slice()));
1199
1421
  }
1200
- return new ResourcePath(segments);
1201
1422
  }
1202
1423
 
1203
1424
  /**
1204
1425
  * @license
1205
- * Copyright 2022 Google LLC
1426
+ * Copyright 2021 Google LLC
1206
1427
  *
1207
1428
  * Licensed under the Apache License, Version 2.0 (the "License");
1208
1429
  * you may not use this file except in compliance with the License.
@@ -1216,11 +1437,197 @@ function decodeResourcePath(path) {
1216
1437
  * See the License for the specific language governing permissions and
1217
1438
  * limitations under the License.
1218
1439
  */
1219
- const DbRemoteDocumentStore$1 = 'remoteDocuments';
1440
+ /**
1441
+ * The initial mutation batch id for each index. Gets updated during index
1442
+ * backfill.
1443
+ */
1444
+ const INITIAL_LARGEST_BATCH_ID = -1;
1445
+ /**
1446
+ * The initial sequence number for each index. Gets updated during index
1447
+ * backfill.
1448
+ */
1449
+ const INITIAL_SEQUENCE_NUMBER = 0;
1450
+ /**
1451
+ * An index definition for field indexes in Firestore.
1452
+ *
1453
+ * Every index is associated with a collection. The definition contains a list
1454
+ * of fields and their index kind (which can be `ASCENDING`, `DESCENDING` or
1455
+ * `CONTAINS` for ArrayContains/ArrayContainsAny queries).
1456
+ *
1457
+ * Unlike the backend, the SDK does not differentiate between collection or
1458
+ * collection group-scoped indices. Every index can be used for both single
1459
+ * collection and collection group queries.
1460
+ */
1461
+ class FieldIndex {
1462
+ constructor(
1463
+ /**
1464
+ * The index ID. Returns -1 if the index ID is not available (e.g. the index
1465
+ * has not yet been persisted).
1466
+ */
1467
+ indexId,
1468
+ /** The collection ID this index applies to. */
1469
+ collectionGroup,
1470
+ /** The field segments for this index. */
1471
+ fields,
1472
+ /** Shows how up-to-date the index is for the current user. */
1473
+ indexState) {
1474
+ this.indexId = indexId;
1475
+ this.collectionGroup = collectionGroup;
1476
+ this.fields = fields;
1477
+ this.indexState = indexState;
1478
+ }
1479
+ }
1480
+ /** An ID for an index that has not yet been added to persistence. */
1481
+ FieldIndex.UNKNOWN_ID = -1;
1482
+ /** Returns the ArrayContains/ArrayContainsAny segment for this index. */
1483
+ function fieldIndexGetArraySegment(fieldIndex) {
1484
+ return fieldIndex.fields.find(s => s.kind === 2 /* CONTAINS */);
1485
+ }
1486
+ /** Returns all directional (ascending/descending) segments for this index. */
1487
+ function fieldIndexGetDirectionalSegments(fieldIndex) {
1488
+ return fieldIndex.fields.filter(s => s.kind !== 2 /* CONTAINS */);
1489
+ }
1490
+ /**
1491
+ * Returns the order of the document key component for the given index.
1492
+ *
1493
+ * PORTING NOTE: This is only used in the Web IndexedDb implementation.
1494
+ */
1495
+ function fieldIndexGetKeyOrder(fieldIndex) {
1496
+ const directionalSegments = fieldIndexGetDirectionalSegments(fieldIndex);
1497
+ return directionalSegments.length === 0
1498
+ ? 0 /* ASCENDING */
1499
+ : directionalSegments[directionalSegments.length - 1].kind;
1500
+ }
1501
+ /** Returns a debug representation of the field index */
1502
+ function fieldIndexToString(fieldIndex) {
1503
+ return `id=${fieldIndex.indexId}|cg=${fieldIndex.collectionGroup}|f=${fieldIndex.fields.map(f => `${f.fieldPath}:${f.kind}`).join(',')}`;
1504
+ }
1505
+ /** An index component consisting of field path and index type. */
1506
+ class IndexSegment {
1507
+ constructor(
1508
+ /** The field path of the component. */
1509
+ fieldPath,
1510
+ /** The fields sorting order. */
1511
+ kind) {
1512
+ this.fieldPath = fieldPath;
1513
+ this.kind = kind;
1514
+ }
1515
+ }
1516
+ /**
1517
+ * Stores the "high water mark" that indicates how updated the Index is for the
1518
+ * current user.
1519
+ */
1520
+ class IndexState {
1521
+ constructor(
1522
+ /**
1523
+ * Indicates when the index was last updated (relative to other indexes).
1524
+ */
1525
+ sequenceNumber,
1526
+ /** The the latest indexed read time, document and batch id. */
1527
+ offset) {
1528
+ this.sequenceNumber = sequenceNumber;
1529
+ this.offset = offset;
1530
+ }
1531
+ /** The state of an index that has not yet been backfilled. */
1532
+ static empty() {
1533
+ return new IndexState(INITIAL_SEQUENCE_NUMBER, IndexOffset.min());
1534
+ }
1535
+ }
1536
+ /**
1537
+ * Creates an offset that matches all documents with a read time higher than
1538
+ * `readTime`.
1539
+ */
1540
+ function newIndexOffsetSuccessorFromReadTime(readTime, largestBatchId) {
1541
+ // We want to create an offset that matches all documents with a read time
1542
+ // greater than the provided read time. To do so, we technically need to
1543
+ // create an offset for `(readTime, MAX_DOCUMENT_KEY)`. While we could use
1544
+ // Unicode codepoints to generate MAX_DOCUMENT_KEY, it is much easier to use
1545
+ // `(readTime + 1, DocumentKey.empty())` since `> DocumentKey.empty()` matches
1546
+ // all valid document IDs.
1547
+ const successorSeconds = readTime.toTimestamp().seconds;
1548
+ const successorNanos = readTime.toTimestamp().nanoseconds + 1;
1549
+ const successor = SnapshotVersion.fromTimestamp(successorNanos === 1e9
1550
+ ? new Timestamp(successorSeconds + 1, 0)
1551
+ : new Timestamp(successorSeconds, successorNanos));
1552
+ return new IndexOffset(successor, DocumentKey.empty(), largestBatchId);
1553
+ }
1554
+ /** Creates a new offset based on the provided document. */
1555
+ function newIndexOffsetFromDocument(document) {
1556
+ return new IndexOffset(document.readTime, document.key, INITIAL_LARGEST_BATCH_ID);
1557
+ }
1558
+ /**
1559
+ * Stores the latest read time, document and batch ID that were processed for an
1560
+ * index.
1561
+ */
1562
+ class IndexOffset {
1563
+ constructor(
1564
+ /**
1565
+ * The latest read time version that has been indexed by Firestore for this
1566
+ * field index.
1567
+ */
1568
+ readTime,
1569
+ /**
1570
+ * The key of the last document that was indexed for this query. Use
1571
+ * `DocumentKey.empty()` if no document has been indexed.
1572
+ */
1573
+ documentKey,
1574
+ /*
1575
+ * The largest mutation batch id that's been processed by Firestore.
1576
+ */
1577
+ largestBatchId) {
1578
+ this.readTime = readTime;
1579
+ this.documentKey = documentKey;
1580
+ this.largestBatchId = largestBatchId;
1581
+ }
1582
+ /** Returns an offset that sorts before all regular offsets. */
1583
+ static min() {
1584
+ return new IndexOffset(SnapshotVersion.min(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
1585
+ }
1586
+ /** Returns an offset that sorts after all regular offsets. */
1587
+ static max() {
1588
+ return new IndexOffset(SnapshotVersion.max(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
1589
+ }
1590
+ }
1591
+ function indexOffsetComparator(left, right) {
1592
+ let cmp = left.readTime.compareTo(right.readTime);
1593
+ if (cmp !== 0) {
1594
+ return cmp;
1595
+ }
1596
+ cmp = DocumentKey.comparator(left.documentKey, right.documentKey);
1597
+ if (cmp !== 0) {
1598
+ return cmp;
1599
+ }
1600
+ return primitiveComparator(left.largestBatchId, right.largestBatchId);
1601
+ }
1602
+ /**
1603
+ * Schema Version for the Web client:
1604
+ * 1. Initial version including Mutation Queue, Query Cache, and Remote
1605
+ * Document Cache
1606
+ * 2. Used to ensure a targetGlobal object exists and add targetCount to it. No
1607
+ * longer required because migration 3 unconditionally clears it.
1608
+ * 3. Dropped and re-created Query Cache to deal with cache corruption related
1609
+ * to limbo resolution. Addresses
1610
+ * https://github.com/firebase/firebase-ios-sdk/issues/1548
1611
+ * 4. Multi-Tab Support.
1612
+ * 5. Removal of held write acks.
1613
+ * 6. Create document global for tracking document cache size.
1614
+ * 7. Ensure every cached document has a sentinel row with a sequence number.
1615
+ * 8. Add collection-parent index for Collection Group queries.
1616
+ * 9. Change RemoteDocumentChanges store to be keyed by readTime rather than
1617
+ * an auto-incrementing ID. This is required for Index-Free queries.
1618
+ * 10. Rewrite the canonical IDs to the explicit Protobuf-based format.
1619
+ * 11. Add bundles and named_queries for bundle support.
1620
+ * 12. Add document overlays.
1621
+ * 13. Rewrite the keys of the remote document cache to allow for efficient
1622
+ * document lookup via `getAll()`.
1623
+ * 14. Add overlays.
1624
+ * 15. Add indexing support.
1625
+ */
1626
+ const SCHEMA_VERSION = 14;
1220
1627
 
1221
1628
  /**
1222
1629
  * @license
1223
- * Copyright 2022 Google LLC
1630
+ * Copyright 2020 Google LLC
1224
1631
  *
1225
1632
  * Licensed under the Apache License, Version 2.0 (the "License");
1226
1633
  * you may not use this file except in compliance with the License.
@@ -1234,260 +1641,31 @@ const DbRemoteDocumentStore$1 = 'remoteDocuments';
1234
1641
  * See the License for the specific language governing permissions and
1235
1642
  * limitations under the License.
1236
1643
  */
1644
+ const PRIMARY_LEASE_LOST_ERROR_MSG = 'The current tab is not in the required state to perform this operation. ' +
1645
+ 'It might be necessary to refresh the browser tab.';
1237
1646
  /**
1238
- * Name of the IndexedDb object store.
1647
+ * A base class representing a persistence transaction, encapsulating both the
1648
+ * transaction's sequence numbers as well as a list of onCommitted listeners.
1239
1649
  *
1240
- * Note that the name 'owner' is chosen to ensure backwards compatibility with
1241
- * older clients that only supported single locked access to the persistence
1242
- * layer.
1243
- */
1244
- const DbPrimaryClientStore = 'owner';
1245
- /**
1246
- * The key string used for the single object that exists in the
1247
- * DbPrimaryClient store.
1650
+ * When you call Persistence.runTransaction(), it will create a transaction and
1651
+ * pass it to your callback. You then pass it to any method that operates
1652
+ * on persistence.
1248
1653
  */
1249
- const DbPrimaryClientKey = 'owner';
1250
- /** Name of the IndexedDb object store. */
1251
- const DbMutationQueueStore = 'mutationQueues';
1252
- /** Keys are automatically assigned via the userId property. */
1253
- const DbMutationQueueKeyPath = 'userId';
1254
- /** Name of the IndexedDb object store. */
1255
- const DbMutationBatchStore = 'mutations';
1256
- /** Keys are automatically assigned via the userId, batchId properties. */
1257
- const DbMutationBatchKeyPath = 'batchId';
1258
- /** The index name for lookup of mutations by user. */
1259
- const DbMutationBatchUserMutationsIndex = 'userMutationsIndex';
1260
- /** The user mutations index is keyed by [userId, batchId] pairs. */
1261
- const DbMutationBatchUserMutationsKeyPath = ['userId', 'batchId'];
1262
- /**
1263
- * Creates a [userId] key for use in the DbDocumentMutations index to iterate
1264
- * over all of a user's document mutations.
1265
- */
1266
- function newDbDocumentMutationPrefixForUser(userId) {
1267
- return [userId];
1268
- }
1269
- /**
1270
- * Creates a [userId, encodedPath] key for use in the DbDocumentMutations
1271
- * index to iterate over all at document mutations for a given path or lower.
1272
- */
1273
- function newDbDocumentMutationPrefixForPath(userId, path) {
1274
- return [userId, encodeResourcePath(path)];
1275
- }
1276
- /**
1277
- * Creates a full index key of [userId, encodedPath, batchId] for inserting
1278
- * and deleting into the DbDocumentMutations index.
1279
- */
1280
- function newDbDocumentMutationKey(userId, path, batchId) {
1281
- return [userId, encodeResourcePath(path), batchId];
1282
- }
1283
- /**
1284
- * Because we store all the useful information for this store in the key,
1285
- * there is no useful information to store as the value. The raw (unencoded)
1286
- * path cannot be stored because IndexedDb doesn't store prototype
1287
- * information.
1288
- */
1289
- const DbDocumentMutationPlaceholder = {};
1290
- const DbDocumentMutationStore = 'documentMutations';
1291
- const DbRemoteDocumentStore = 'remoteDocumentsV14';
1292
- /**
1293
- * The primary key of the remote documents store, which allows for efficient
1294
- * access by collection path and read time.
1295
- */
1296
- const DbRemoteDocumentKeyPath = [
1297
- 'prefixPath',
1298
- 'collectionGroup',
1299
- 'readTime',
1300
- 'documentId'
1301
- ];
1302
- /** An index that provides access to documents by key. */
1303
- const DbRemoteDocumentDocumentKeyIndex = 'documentKeyIndex';
1304
- const DbRemoteDocumentDocumentKeyIndexPath = [
1305
- 'prefixPath',
1306
- 'collectionGroup',
1307
- 'documentId'
1308
- ];
1309
- /**
1310
- * An index that provides access to documents by collection group and read
1311
- * time.
1312
- *
1313
- * This index is used by the index backfiller.
1314
- */
1315
- const DbRemoteDocumentCollectionGroupIndex = 'collectionGroupIndex';
1316
- const DbRemoteDocumentCollectionGroupIndexPath = [
1317
- 'collectionGroup',
1318
- 'readTime',
1319
- 'prefixPath',
1320
- 'documentId'
1321
- ];
1322
- const DbRemoteDocumentGlobalStore = 'remoteDocumentGlobal';
1323
- const DbRemoteDocumentGlobalKey = 'remoteDocumentGlobalKey';
1324
- const DbTargetStore = 'targets';
1325
- /** Keys are automatically assigned via the targetId property. */
1326
- const DbTargetKeyPath = 'targetId';
1327
- /** The name of the queryTargets index. */
1328
- const DbTargetQueryTargetsIndexName = 'queryTargetsIndex';
1329
- /**
1330
- * The index of all canonicalIds to the targets that they match. This is not
1331
- * a unique mapping because canonicalId does not promise a unique name for all
1332
- * possible queries, so we append the targetId to make the mapping unique.
1333
- */
1334
- const DbTargetQueryTargetsKeyPath = ['canonicalId', 'targetId'];
1335
- /** Name of the IndexedDb object store. */
1336
- const DbTargetDocumentStore = 'targetDocuments';
1337
- /** Keys are automatically assigned via the targetId, path properties. */
1338
- const DbTargetDocumentKeyPath = ['targetId', 'path'];
1339
- /** The index name for the reverse index. */
1340
- const DbTargetDocumentDocumentTargetsIndex = 'documentTargetsIndex';
1341
- /** We also need to create the reverse index for these properties. */
1342
- const DbTargetDocumentDocumentTargetsKeyPath = ['path', 'targetId'];
1343
- /**
1344
- * The key string used for the single object that exists in the
1345
- * DbTargetGlobal store.
1346
- */
1347
- const DbTargetGlobalKey = 'targetGlobalKey';
1348
- const DbTargetGlobalStore = 'targetGlobal';
1349
- /** Name of the IndexedDb object store. */
1350
- const DbCollectionParentStore = 'collectionParents';
1351
- /** Keys are automatically assigned via the collectionId, parent properties. */
1352
- const DbCollectionParentKeyPath = ['collectionId', 'parent'];
1353
- /** Name of the IndexedDb object store. */
1354
- const DbClientMetadataStore = 'clientMetadata';
1355
- /** Keys are automatically assigned via the clientId properties. */
1356
- const DbClientMetadataKeyPath = 'clientId';
1357
- /** Name of the IndexedDb object store. */
1358
- const DbBundleStore = 'bundles';
1359
- const DbBundleKeyPath = 'bundleId';
1360
- /** Name of the IndexedDb object store. */
1361
- const DbNamedQueryStore = 'namedQueries';
1362
- const DbNamedQueryKeyPath = 'name';
1363
- /** Name of the IndexedDb object store. */
1364
- const DbIndexConfigurationStore = 'indexConfiguration';
1365
- const DbIndexConfigurationKeyPath = 'indexId';
1366
- /**
1367
- * An index that provides access to the index configurations by collection
1368
- * group.
1369
- *
1370
- * PORTING NOTE: iOS and Android maintain this index in-memory, but this is
1371
- * not possible here as the Web client supports concurrent access to
1372
- * persistence via multi-tab.
1373
- */
1374
- const DbIndexConfigurationCollectionGroupIndex = 'collectionGroupIndex';
1375
- const DbIndexConfigurationCollectionGroupIndexPath = 'collectionGroup';
1376
- /** Name of the IndexedDb object store. */
1377
- const DbIndexStateStore = 'indexState';
1378
- const DbIndexStateKeyPath = ['indexId', 'uid'];
1379
- /**
1380
- * An index that provides access to documents in a collection sorted by last
1381
- * update time. Used by the backfiller.
1382
- *
1383
- * PORTING NOTE: iOS and Android maintain this index in-memory, but this is
1384
- * not possible here as the Web client supports concurrent access to
1385
- * persistence via multi-tab.
1386
- */
1387
- const DbIndexStateSequenceNumberIndex = 'sequenceNumberIndex';
1388
- const DbIndexStateSequenceNumberIndexPath = ['uid', 'sequenceNumber'];
1389
- /** Name of the IndexedDb object store. */
1390
- const DbIndexEntryStore = 'indexEntries';
1391
- const DbIndexEntryKeyPath = [
1392
- 'indexId',
1393
- 'uid',
1394
- 'arrayValue',
1395
- 'directionalValue',
1396
- 'orderedDocumentKey',
1397
- 'documentKey'
1398
- ];
1399
- const DbIndexEntryDocumentKeyIndex = 'documentKeyIndex';
1400
- const DbIndexEntryDocumentKeyIndexPath = [
1401
- 'indexId',
1402
- 'uid',
1403
- 'orderedDocumentKey'
1404
- ];
1405
- /** Name of the IndexedDb object store. */
1406
- const DbDocumentOverlayStore = 'documentOverlays';
1407
- const DbDocumentOverlayKeyPath = [
1408
- 'userId',
1409
- 'collectionPath',
1410
- 'documentId'
1411
- ];
1412
- const DbDocumentOverlayCollectionPathOverlayIndex = 'collectionPathOverlayIndex';
1413
- const DbDocumentOverlayCollectionPathOverlayIndexPath = [
1414
- 'userId',
1415
- 'collectionPath',
1416
- 'largestBatchId'
1417
- ];
1418
- const DbDocumentOverlayCollectionGroupOverlayIndex = 'collectionGroupOverlayIndex';
1419
- const DbDocumentOverlayCollectionGroupOverlayIndexPath = [
1420
- 'userId',
1421
- 'collectionGroup',
1422
- 'largestBatchId'
1423
- ];
1424
- // Visible for testing
1425
- const V1_STORES = [
1426
- DbMutationQueueStore,
1427
- DbMutationBatchStore,
1428
- DbDocumentMutationStore,
1429
- DbRemoteDocumentStore$1,
1430
- DbTargetStore,
1431
- DbPrimaryClientStore,
1432
- DbTargetGlobalStore,
1433
- DbTargetDocumentStore
1434
- ];
1435
- // Visible for testing
1436
- const V3_STORES = V1_STORES;
1437
- // Note: DbRemoteDocumentChanges is no longer used and dropped with v9.
1438
- const V4_STORES = [...V3_STORES, DbClientMetadataStore];
1439
- const V6_STORES = [...V4_STORES, DbRemoteDocumentGlobalStore];
1440
- const V8_STORES = [...V6_STORES, DbCollectionParentStore];
1441
- const V11_STORES = [...V8_STORES, DbBundleStore, DbNamedQueryStore];
1442
- const V12_STORES = [...V11_STORES, DbDocumentOverlayStore];
1443
- const V13_STORES = [
1444
- DbMutationQueueStore,
1445
- DbMutationBatchStore,
1446
- DbDocumentMutationStore,
1447
- DbRemoteDocumentStore,
1448
- DbTargetStore,
1449
- DbPrimaryClientStore,
1450
- DbTargetGlobalStore,
1451
- DbTargetDocumentStore,
1452
- DbClientMetadataStore,
1453
- DbRemoteDocumentGlobalStore,
1454
- DbCollectionParentStore,
1455
- DbBundleStore,
1456
- DbNamedQueryStore,
1457
- DbDocumentOverlayStore
1458
- ];
1459
- const V14_STORES = V13_STORES;
1460
- const V15_STORES = [
1461
- ...V14_STORES,
1462
- DbIndexConfigurationStore,
1463
- DbIndexStateStore,
1464
- DbIndexEntryStore
1465
- ];
1466
- /** Returns the object stores for the provided schema. */
1467
- function getObjectStores(schemaVersion) {
1468
- if (schemaVersion === 15) {
1469
- return V15_STORES;
1470
- }
1471
- else if (schemaVersion === 14) {
1472
- return V14_STORES;
1473
- }
1474
- else if (schemaVersion === 13) {
1475
- return V13_STORES;
1476
- }
1477
- else if (schemaVersion === 12) {
1478
- return V12_STORES;
1654
+ class PersistenceTransaction {
1655
+ constructor() {
1656
+ this.onCommittedListeners = [];
1479
1657
  }
1480
- else if (schemaVersion === 11) {
1481
- return V11_STORES;
1658
+ addOnCommittedListener(listener) {
1659
+ this.onCommittedListeners.push(listener);
1482
1660
  }
1483
- else {
1484
- fail();
1661
+ raiseOnCommittedEvent() {
1662
+ this.onCommittedListeners.forEach(listener => listener());
1485
1663
  }
1486
1664
  }
1487
1665
 
1488
1666
  /**
1489
1667
  * @license
1490
- * Copyright 2020 Google LLC
1668
+ * Copyright 2017 Google LLC
1491
1669
  *
1492
1670
  * Licensed under the Apache License, Version 2.0 (the "License");
1493
1671
  * you may not use this file except in compliance with the License.
@@ -1501,25 +1679,23 @@ function getObjectStores(schemaVersion) {
1501
1679
  * See the License for the specific language governing permissions and
1502
1680
  * limitations under the License.
1503
1681
  */
1504
- const PRIMARY_LEASE_LOST_ERROR_MSG = 'The current tab is not in the required state to perform this operation. ' +
1505
- 'It might be necessary to refresh the browser tab.';
1506
1682
  /**
1507
- * A base class representing a persistence transaction, encapsulating both the
1508
- * transaction's sequence numbers as well as a list of onCommitted listeners.
1683
+ * Verifies the error thrown by a LocalStore operation. If a LocalStore
1684
+ * operation fails because the primary lease has been taken by another client,
1685
+ * we ignore the error (the persistence layer will immediately call
1686
+ * `applyPrimaryLease` to propagate the primary state change). All other errors
1687
+ * are re-thrown.
1509
1688
  *
1510
- * When you call Persistence.runTransaction(), it will create a transaction and
1511
- * pass it to your callback. You then pass it to any method that operates
1512
- * on persistence.
1689
+ * @param err - An error returned by a LocalStore operation.
1690
+ * @returns A Promise that resolves after we recovered, or the original error.
1513
1691
  */
1514
- class PersistenceTransaction {
1515
- constructor() {
1516
- this.onCommittedListeners = [];
1517
- }
1518
- addOnCommittedListener(listener) {
1519
- this.onCommittedListeners.push(listener);
1692
+ async function ignoreIfPrimaryLeaseLoss(err) {
1693
+ if (err.code === Code.FAILED_PRECONDITION &&
1694
+ err.message === PRIMARY_LEASE_LOST_ERROR_MSG) {
1695
+ logDebug('LocalStore', 'Unexpectedly lost primary lease');
1520
1696
  }
1521
- raiseOnCommittedEvent() {
1522
- this.onCommittedListeners.forEach(listener => listener());
1697
+ else {
1698
+ throw err;
1523
1699
  }
1524
1700
  }
1525
1701
 
@@ -1704,6 +1880,47 @@ class PersistencePromise {
1704
1880
  });
1705
1881
  return this.waitFor(promises);
1706
1882
  }
1883
+ /**
1884
+ * Concurrently map all array elements through asynchronous function.
1885
+ */
1886
+ static mapArray(array, f) {
1887
+ return new PersistencePromise((resolve, reject) => {
1888
+ const expectedCount = array.length;
1889
+ const results = new Array(expectedCount);
1890
+ let resolvedCount = 0;
1891
+ for (let i = 0; i < expectedCount; i++) {
1892
+ const current = i;
1893
+ f(array[current]).next(result => {
1894
+ results[current] = result;
1895
+ ++resolvedCount;
1896
+ if (resolvedCount === expectedCount) {
1897
+ resolve(results);
1898
+ }
1899
+ }, err => reject(err));
1900
+ }
1901
+ });
1902
+ }
1903
+ /**
1904
+ * An alternative to recursive PersistencePromise calls, that avoids
1905
+ * potential memory problems from unbounded chains of promises.
1906
+ *
1907
+ * The `action` will be called repeatedly while `condition` is true.
1908
+ */
1909
+ static doWhile(condition, action) {
1910
+ return new PersistencePromise((resolve, reject) => {
1911
+ const process = () => {
1912
+ if (condition() === true) {
1913
+ action().next(() => {
1914
+ process();
1915
+ }, reject);
1916
+ }
1917
+ else {
1918
+ resolve();
1919
+ }
1920
+ };
1921
+ process();
1922
+ });
1923
+ }
1707
1924
  }
1708
1925
 
1709
1926
  /**
@@ -1724,7 +1941,7 @@ class PersistencePromise {
1724
1941
  */
1725
1942
  // References to `window` are guarded by SimpleDb.isAvailable()
1726
1943
  /* eslint-disable no-restricted-globals */
1727
- const LOG_TAG$h = 'SimpleDb';
1944
+ const LOG_TAG$i = 'SimpleDb';
1728
1945
  /**
1729
1946
  * The maximum number of retry attempts for an IndexedDb transaction that fails
1730
1947
  * with a DOMException.
@@ -1775,7 +1992,7 @@ class SimpleDbTransaction {
1775
1992
  this.completionDeferred.reject(error);
1776
1993
  }
1777
1994
  if (!this.aborted) {
1778
- logDebug(LOG_TAG$h, 'Aborting transaction:', error ? error.message : 'Client-initiated abort');
1995
+ logDebug(LOG_TAG$i, 'Aborting transaction:', error ? error.message : 'Client-initiated abort');
1779
1996
  this.aborted = true;
1780
1997
  this.transaction.abort();
1781
1998
  }
@@ -1838,7 +2055,7 @@ class SimpleDb {
1838
2055
  }
1839
2056
  /** Deletes the specified database. */
1840
2057
  static delete(name) {
1841
- logDebug(LOG_TAG$h, 'Removing database:', name);
2058
+ logDebug(LOG_TAG$i, 'Removing database:', name);
1842
2059
  return wrapRequest(window.indexedDB.deleteDatabase(name)).toPromise();
1843
2060
  }
1844
2061
  /** Returns true if IndexedDB is available in the current environment. */
@@ -1917,7 +2134,7 @@ class SimpleDb {
1917
2134
  */
1918
2135
  async ensureDb(action) {
1919
2136
  if (!this.db) {
1920
- logDebug(LOG_TAG$h, 'Opening database:', this.name);
2137
+ logDebug(LOG_TAG$i, 'Opening database:', this.name);
1921
2138
  this.db = await new Promise((resolve, reject) => {
1922
2139
  // TODO(mikelehen): Investigate browser compatibility.
1923
2140
  // https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB
@@ -1953,12 +2170,12 @@ class SimpleDb {
1953
2170
  }
1954
2171
  };
1955
2172
  request.onupgradeneeded = (event) => {
1956
- logDebug(LOG_TAG$h, 'Database "' + this.name + '" requires upgrade from version:', event.oldVersion);
2173
+ logDebug(LOG_TAG$i, 'Database "' + this.name + '" requires upgrade from version:', event.oldVersion);
1957
2174
  const db = event.target.result;
1958
2175
  this.schemaConverter
1959
2176
  .createOrUpgrade(db, request.transaction, event.oldVersion, this.version)
1960
2177
  .next(() => {
1961
- logDebug(LOG_TAG$h, 'Database upgrade to version ' + this.version + ' complete');
2178
+ logDebug(LOG_TAG$i, 'Database upgrade to version ' + this.version + ' complete');
1962
2179
  });
1963
2180
  };
1964
2181
  });
@@ -2016,7 +2233,7 @@ class SimpleDb {
2016
2233
  // exception is wrapped in a generic error by our async/await handling.
2017
2234
  const retryable = error.name !== 'FirebaseError' &&
2018
2235
  attemptNumber < TRANSACTION_RETRY_COUNT;
2019
- logDebug(LOG_TAG$h, 'Transaction failed with error:', error.message, 'Retrying:', retryable);
2236
+ logDebug(LOG_TAG$i, 'Transaction failed with error:', error.message, 'Retrying:', retryable);
2020
2237
  this.close();
2021
2238
  if (!retryable) {
2022
2239
  return Promise.reject(error);
@@ -2103,11 +2320,11 @@ class SimpleDbStore {
2103
2320
  put(keyOrValue, value) {
2104
2321
  let request;
2105
2322
  if (value !== undefined) {
2106
- logDebug(LOG_TAG$h, 'PUT', this.store.name, keyOrValue, value);
2323
+ logDebug(LOG_TAG$i, 'PUT', this.store.name, keyOrValue, value);
2107
2324
  request = this.store.put(value, keyOrValue);
2108
2325
  }
2109
2326
  else {
2110
- logDebug(LOG_TAG$h, 'PUT', this.store.name, '<auto-key>', keyOrValue);
2327
+ logDebug(LOG_TAG$i, 'PUT', this.store.name, '<auto-key>', keyOrValue);
2111
2328
  request = this.store.put(keyOrValue);
2112
2329
  }
2113
2330
  return wrapRequest(request);
@@ -2120,7 +2337,7 @@ class SimpleDbStore {
2120
2337
  * @returns The key of the value to add.
2121
2338
  */
2122
2339
  add(value) {
2123
- logDebug(LOG_TAG$h, 'ADD', this.store.name, value, value);
2340
+ logDebug(LOG_TAG$i, 'ADD', this.store.name, value, value);
2124
2341
  const request = this.store.add(value);
2125
2342
  return wrapRequest(request);
2126
2343
  }
@@ -2140,12 +2357,12 @@ class SimpleDbStore {
2140
2357
  if (result === undefined) {
2141
2358
  result = null;
2142
2359
  }
2143
- logDebug(LOG_TAG$h, 'GET', this.store.name, key, result);
2360
+ logDebug(LOG_TAG$i, 'GET', this.store.name, key, result);
2144
2361
  return result;
2145
2362
  });
2146
2363
  }
2147
2364
  delete(key) {
2148
- logDebug(LOG_TAG$h, 'DELETE', this.store.name, key);
2365
+ logDebug(LOG_TAG$i, 'DELETE', this.store.name, key);
2149
2366
  const request = this.store.delete(key);
2150
2367
  return wrapRequest(request);
2151
2368
  }
@@ -2156,7 +2373,7 @@ class SimpleDbStore {
2156
2373
  * Returns the number of rows in the store.
2157
2374
  */
2158
2375
  count() {
2159
- logDebug(LOG_TAG$h, 'COUNT', this.store.name);
2376
+ logDebug(LOG_TAG$i, 'COUNT', this.store.name);
2160
2377
  const request = this.store.count();
2161
2378
  return wrapRequest(request);
2162
2379
  }
@@ -2201,7 +2418,7 @@ class SimpleDbStore {
2201
2418
  });
2202
2419
  }
2203
2420
  deleteAll(indexOrRange, range) {
2204
- logDebug(LOG_TAG$h, 'DELETE ALL', this.store.name);
2421
+ logDebug(LOG_TAG$i, 'DELETE ALL', this.store.name);
2205
2422
  const options = this.options(indexOrRange, range);
2206
2423
  options.keysOnly = false;
2207
2424
  const cursor = this.cursor(options);
@@ -2364,37 +2581,126 @@ function checkForAndReportiOSError(error) {
2364
2581
  return error;
2365
2582
  }
2366
2583
 
2367
- /**
2368
- * @license
2369
- * Copyright 2020 Google LLC
2370
- *
2371
- * Licensed under the Apache License, Version 2.0 (the "License");
2372
- * you may not use this file except in compliance with the License.
2373
- * You may obtain a copy of the License at
2374
- *
2375
- * http://www.apache.org/licenses/LICENSE-2.0
2376
- *
2377
- * Unless required by applicable law or agreed to in writing, software
2378
- * distributed under the License is distributed on an "AS IS" BASIS,
2379
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2380
- * See the License for the specific language governing permissions and
2381
- * limitations under the License.
2382
- */
2383
- class IndexedDbTransaction extends PersistenceTransaction {
2384
- constructor(simpleDbTransaction, currentSequenceNumber) {
2385
- super();
2386
- this.simpleDbTransaction = simpleDbTransaction;
2387
- this.currentSequenceNumber = currentSequenceNumber;
2584
+ const LOG_TAG$h = 'IndexBackiller';
2585
+ /** Minimum amount of time between backfill checks, after the first one. */
2586
+ const REGULAR_BACKFILL_DELAY_MS = 1;
2587
+ /** The maximum number of documents to process each time backfill() is called. */
2588
+ const MAX_DOCUMENTS_TO_PROCESS = 50;
2589
+ /** This class is responsible for the scheduling of Index Backfiller. */
2590
+ class IndexBackfillerScheduler {
2591
+ constructor(asyncQueue, backfiller) {
2592
+ this.asyncQueue = asyncQueue;
2593
+ this.backfiller = backfiller;
2594
+ this.task = null;
2595
+ }
2596
+ start() {
2597
+ }
2598
+ stop() {
2599
+ if (this.task) {
2600
+ this.task.cancel();
2601
+ this.task = null;
2602
+ }
2603
+ }
2604
+ get started() {
2605
+ return this.task !== null;
2606
+ }
2607
+ schedule(delay) {
2608
+ logDebug(LOG_TAG$h, `Scheduled in ${delay}ms`);
2609
+ this.task = this.asyncQueue.enqueueAfterDelay("index_backfill" /* IndexBackfill */, delay, async () => {
2610
+ this.task = null;
2611
+ try {
2612
+ const documentsProcessed = await this.backfiller.backfill();
2613
+ logDebug(LOG_TAG$h, `Documents written: ${documentsProcessed}`);
2614
+ }
2615
+ catch (e) {
2616
+ if (isIndexedDbTransactionError(e)) {
2617
+ logDebug(LOG_TAG$h, 'Ignoring IndexedDB error during index backfill: ', e);
2618
+ }
2619
+ else {
2620
+ await ignoreIfPrimaryLeaseLoss(e);
2621
+ }
2622
+ }
2623
+ await this.schedule(REGULAR_BACKFILL_DELAY_MS);
2624
+ });
2388
2625
  }
2389
2626
  }
2390
- function getStore(txn, store) {
2391
- const indexedDbTransaction = debugCast(txn);
2392
- return SimpleDb.getStore(indexedDbTransaction.simpleDbTransaction, store);
2627
+ /** Implements the steps for backfilling indexes. */
2628
+ class IndexBackfiller {
2629
+ constructor(
2630
+ /**
2631
+ * LocalStore provides access to IndexManager and LocalDocumentView.
2632
+ * These properties will update when the user changes. Consequently,
2633
+ * making a local copy of IndexManager and LocalDocumentView will require
2634
+ * updates over time. The simpler solution is to rely on LocalStore to have
2635
+ * an up-to-date references to IndexManager and LocalDocumentStore.
2636
+ */
2637
+ localStore, persistence) {
2638
+ this.localStore = localStore;
2639
+ this.persistence = persistence;
2640
+ }
2641
+ async backfill(maxDocumentsToProcess = MAX_DOCUMENTS_TO_PROCESS) {
2642
+ return this.persistence.runTransaction('Backfill Indexes', 'readwrite-primary', txn => this.writeIndexEntries(txn, maxDocumentsToProcess));
2643
+ }
2644
+ /** Writes index entries until the cap is reached. Returns the number of documents processed. */
2645
+ writeIndexEntries(transation, maxDocumentsToProcess) {
2646
+ const processedCollectionGroups = new Set();
2647
+ let documentsRemaining = maxDocumentsToProcess;
2648
+ let continueLoop = true;
2649
+ return PersistencePromise.doWhile(() => continueLoop === true && documentsRemaining > 0, () => {
2650
+ return this.localStore.indexManager
2651
+ .getNextCollectionGroupToUpdate(transation)
2652
+ .next((collectionGroup) => {
2653
+ if (collectionGroup === null ||
2654
+ processedCollectionGroups.has(collectionGroup)) {
2655
+ continueLoop = false;
2656
+ }
2657
+ else {
2658
+ logDebug(LOG_TAG$h, `Processing collection: ${collectionGroup}`);
2659
+ return this.writeEntriesForCollectionGroup(transation, collectionGroup, documentsRemaining).next(documentsProcessed => {
2660
+ documentsRemaining -= documentsProcessed;
2661
+ processedCollectionGroups.add(collectionGroup);
2662
+ });
2663
+ }
2664
+ });
2665
+ }).next(() => maxDocumentsToProcess - documentsRemaining);
2666
+ }
2667
+ /**
2668
+ * Writes entries for the provided collection group. Returns the number of documents processed.
2669
+ */
2670
+ writeEntriesForCollectionGroup(transaction, collectionGroup, documentsRemainingUnderCap) {
2671
+ // Use the earliest offset of all field indexes to query the local cache.
2672
+ return this.localStore.indexManager
2673
+ .getMinOffsetFromCollectionGroup(transaction, collectionGroup)
2674
+ .next(existingOffset => this.localStore.localDocuments
2675
+ .getNextDocuments(transaction, collectionGroup, existingOffset, documentsRemainingUnderCap)
2676
+ .next(nextBatch => {
2677
+ const docs = nextBatch.changes;
2678
+ return this.localStore.indexManager
2679
+ .updateIndexEntries(transaction, docs)
2680
+ .next(() => this.getNewOffset(existingOffset, nextBatch))
2681
+ .next(newOffset => {
2682
+ logDebug(LOG_TAG$h, `Updating offset: ${newOffset}`);
2683
+ return this.localStore.indexManager.updateCollectionGroup(transaction, collectionGroup, newOffset);
2684
+ })
2685
+ .next(() => docs.size);
2686
+ }));
2687
+ }
2688
+ /** Returns the next offset based on the provided documents. */
2689
+ getNewOffset(existingOffset, lookupResult) {
2690
+ let maxOffset = existingOffset;
2691
+ lookupResult.changes.forEach((key, document) => {
2692
+ const newOffset = newIndexOffsetFromDocument(document);
2693
+ if (indexOffsetComparator(newOffset, maxOffset) > 0) {
2694
+ maxOffset = newOffset;
2695
+ }
2696
+ });
2697
+ return new IndexOffset(maxOffset.readTime, maxOffset.documentKey, Math.max(lookupResult.batchId, existingOffset.largestBatchId));
2698
+ }
2393
2699
  }
2394
2700
 
2395
2701
  /**
2396
2702
  * @license
2397
- * Copyright 2020 Google LLC
2703
+ * Copyright 2018 Google LLC
2398
2704
  *
2399
2705
  * Licensed under the Apache License, Version 2.0 (the "License");
2400
2706
  * you may not use this file except in compliance with the License.
@@ -2409,13 +2715,32 @@ function getStore(txn, store) {
2409
2715
  * limitations under the License.
2410
2716
  */
2411
2717
  /**
2412
- * Generates `nBytes` of random bytes.
2413
- *
2414
- * If `nBytes < 0` , an error will be thrown.
2718
+ * `ListenSequence` is a monotonic sequence. It is initialized with a minimum value to
2719
+ * exceed. All subsequent calls to next will return increasing values. If provided with a
2720
+ * `SequenceNumberSyncer`, it will additionally bump its next value when told of a new value, as
2721
+ * well as write out sequence numbers that it produces via `next()`.
2415
2722
  */
2416
- function randomBytes(nBytes) {
2417
- return randomBytes$1(nBytes);
2418
- }
2723
+ class ListenSequence {
2724
+ constructor(previousValue, sequenceNumberSyncer) {
2725
+ this.previousValue = previousValue;
2726
+ if (sequenceNumberSyncer) {
2727
+ sequenceNumberSyncer.sequenceNumberHandler = sequenceNumber => this.setPreviousValue(sequenceNumber);
2728
+ this.writeNewSequenceNumber = sequenceNumber => sequenceNumberSyncer.writeSequenceNumber(sequenceNumber);
2729
+ }
2730
+ }
2731
+ setPreviousValue(externalPreviousValue) {
2732
+ this.previousValue = Math.max(externalPreviousValue, this.previousValue);
2733
+ return this.previousValue;
2734
+ }
2735
+ next() {
2736
+ const nextValue = ++this.previousValue;
2737
+ if (this.writeNewSequenceNumber) {
2738
+ this.writeNewSequenceNumber(nextValue);
2739
+ }
2740
+ return nextValue;
2741
+ }
2742
+ }
2743
+ ListenSequence.INVALID = -1;
2419
2744
 
2420
2745
  /**
2421
2746
  * @license
@@ -2433,55 +2758,109 @@ function randomBytes(nBytes) {
2433
2758
  * See the License for the specific language governing permissions and
2434
2759
  * limitations under the License.
2435
2760
  */
2436
- class AutoId {
2437
- static newId() {
2438
- // Alphanumeric characters
2439
- const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
2440
- // The largest byte value that is a multiple of `char.length`.
2441
- const maxMultiple = Math.floor(256 / chars.length) * chars.length;
2442
- let autoId = '';
2443
- const targetLength = 20;
2444
- while (autoId.length < targetLength) {
2445
- const bytes = randomBytes(40);
2446
- for (let i = 0; i < bytes.length; ++i) {
2447
- // Only accept values that are [0, maxMultiple), this ensures they can
2448
- // be evenly mapped to indices of `chars` via a modulo operation.
2449
- if (autoId.length < targetLength && bytes[i] < maxMultiple) {
2450
- autoId += chars.charAt(bytes[i] % chars.length);
2451
- }
2452
- }
2761
+ const escapeChar = '\u0001';
2762
+ const encodedSeparatorChar = '\u0001';
2763
+ const encodedNul = '\u0010';
2764
+ const encodedEscape = '\u0011';
2765
+ /**
2766
+ * Encodes a resource path into a IndexedDb-compatible string form.
2767
+ */
2768
+ function encodeResourcePath(path) {
2769
+ let result = '';
2770
+ for (let i = 0; i < path.length; i++) {
2771
+ if (result.length > 0) {
2772
+ result = encodeSeparator(result);
2453
2773
  }
2454
- return autoId;
2774
+ result = encodeSegment(path.get(i), result);
2455
2775
  }
2776
+ return encodeSeparator(result);
2456
2777
  }
2457
- function primitiveComparator(left, right) {
2458
- if (left < right) {
2459
- return -1;
2460
- }
2461
- if (left > right) {
2462
- return 1;
2778
+ /** Encodes a single segment of a resource path into the given result */
2779
+ function encodeSegment(segment, resultBuf) {
2780
+ let result = resultBuf;
2781
+ const length = segment.length;
2782
+ for (let i = 0; i < length; i++) {
2783
+ const c = segment.charAt(i);
2784
+ switch (c) {
2785
+ case '\0':
2786
+ result += escapeChar + encodedNul;
2787
+ break;
2788
+ case escapeChar:
2789
+ result += escapeChar + encodedEscape;
2790
+ break;
2791
+ default:
2792
+ result += c;
2793
+ }
2463
2794
  }
2464
- return 0;
2795
+ return result;
2465
2796
  }
2466
- /** Helper to compare arrays using isEqual(). */
2467
- function arrayEquals(left, right, comparator) {
2468
- if (left.length !== right.length) {
2469
- return false;
2470
- }
2471
- return left.every((value, index) => comparator(value, right[index]));
2797
+ /** Encodes a path separator into the given result */
2798
+ function encodeSeparator(result) {
2799
+ return result + escapeChar + encodedSeparatorChar;
2472
2800
  }
2473
2801
  /**
2474
- * Returns the immediate lexicographically-following string. This is useful to
2475
- * construct an inclusive range for indexeddb iterators.
2802
+ * Decodes the given IndexedDb-compatible string form of a resource path into
2803
+ * a ResourcePath instance. Note that this method is not suitable for use with
2804
+ * decoding resource names from the server; those are One Platform format
2805
+ * strings.
2476
2806
  */
2477
- function immediateSuccessor(s) {
2478
- // Return the input string, with an additional NUL byte appended.
2479
- return s + '\0';
2807
+ function decodeResourcePath(path) {
2808
+ // Event the empty path must encode as a path of at least length 2. A path
2809
+ // with exactly 2 must be the empty path.
2810
+ const length = path.length;
2811
+ hardAssert(length >= 2);
2812
+ if (length === 2) {
2813
+ hardAssert(path.charAt(0) === escapeChar && path.charAt(1) === encodedSeparatorChar);
2814
+ return ResourcePath.emptyPath();
2815
+ }
2816
+ // Escape characters cannot exist past the second-to-last position in the
2817
+ // source value.
2818
+ const lastReasonableEscapeIndex = length - 2;
2819
+ const segments = [];
2820
+ let segmentBuilder = '';
2821
+ for (let start = 0; start < length;) {
2822
+ // The last two characters of a valid encoded path must be a separator, so
2823
+ // there must be an end to this segment.
2824
+ const end = path.indexOf(escapeChar, start);
2825
+ if (end < 0 || end > lastReasonableEscapeIndex) {
2826
+ fail();
2827
+ }
2828
+ const next = path.charAt(end + 1);
2829
+ switch (next) {
2830
+ case encodedSeparatorChar:
2831
+ const currentPiece = path.substring(start, end);
2832
+ let segment;
2833
+ if (segmentBuilder.length === 0) {
2834
+ // Avoid copying for the common case of a segment that excludes \0
2835
+ // and \001
2836
+ segment = currentPiece;
2837
+ }
2838
+ else {
2839
+ segmentBuilder += currentPiece;
2840
+ segment = segmentBuilder;
2841
+ segmentBuilder = '';
2842
+ }
2843
+ segments.push(segment);
2844
+ break;
2845
+ case encodedNul:
2846
+ segmentBuilder += path.substring(start, end);
2847
+ segmentBuilder += '\0';
2848
+ break;
2849
+ case encodedEscape:
2850
+ // The escape character can be used in the output to encode itself.
2851
+ segmentBuilder += path.substring(start, end + 1);
2852
+ break;
2853
+ default:
2854
+ fail();
2855
+ }
2856
+ start = end + 2;
2857
+ }
2858
+ return new ResourcePath(segments);
2480
2859
  }
2481
2860
 
2482
2861
  /**
2483
2862
  * @license
2484
- * Copyright 2017 Google LLC
2863
+ * Copyright 2022 Google LLC
2485
2864
  *
2486
2865
  * Licensed under the Apache License, Version 2.0 (the "License");
2487
2866
  * you may not use this file except in compliance with the License.
@@ -2495,164 +2874,278 @@ function immediateSuccessor(s) {
2495
2874
  * See the License for the specific language governing permissions and
2496
2875
  * limitations under the License.
2497
2876
  */
2498
- // The earliest date supported by Firestore timestamps (0001-01-01T00:00:00Z).
2499
- const MIN_SECONDS = -62135596800;
2500
- // Number of nanoseconds in a millisecond.
2501
- const MS_TO_NANOS = 1e6;
2877
+ const DbRemoteDocumentStore$1 = 'remoteDocuments';
2878
+
2502
2879
  /**
2503
- * A `Timestamp` represents a point in time independent of any time zone or
2504
- * calendar, represented as seconds and fractions of seconds at nanosecond
2505
- * resolution in UTC Epoch time.
2880
+ * @license
2881
+ * Copyright 2022 Google LLC
2506
2882
  *
2507
- * It is encoded using the Proleptic Gregorian Calendar which extends the
2508
- * Gregorian calendar backwards to year one. It is encoded assuming all minutes
2509
- * are 60 seconds long, i.e. leap seconds are "smeared" so that no leap second
2510
- * table is needed for interpretation. Range is from 0001-01-01T00:00:00Z to
2511
- * 9999-12-31T23:59:59.999999999Z.
2883
+ * Licensed under the Apache License, Version 2.0 (the "License");
2884
+ * you may not use this file except in compliance with the License.
2885
+ * You may obtain a copy of the License at
2512
2886
  *
2513
- * For examples and further specifications, refer to the
2514
- * {@link https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto | Timestamp definition}.
2887
+ * http://www.apache.org/licenses/LICENSE-2.0
2888
+ *
2889
+ * Unless required by applicable law or agreed to in writing, software
2890
+ * distributed under the License is distributed on an "AS IS" BASIS,
2891
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2892
+ * See the License for the specific language governing permissions and
2893
+ * limitations under the License.
2515
2894
  */
2516
- class Timestamp {
2517
- /**
2518
- * Creates a new timestamp.
2519
- *
2520
- * @param seconds - The number of seconds of UTC time since Unix epoch
2521
- * 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
2522
- * 9999-12-31T23:59:59Z inclusive.
2523
- * @param nanoseconds - The non-negative fractions of a second at nanosecond
2524
- * resolution. Negative second values with fractions must still have
2525
- * non-negative nanoseconds values that count forward in time. Must be
2526
- * from 0 to 999,999,999 inclusive.
2527
- */
2528
- constructor(
2529
- /**
2530
- * The number of seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z.
2531
- */
2532
- seconds,
2533
- /**
2534
- * The fractions of a second at nanosecond resolution.*
2535
- */
2536
- nanoseconds) {
2537
- this.seconds = seconds;
2538
- this.nanoseconds = nanoseconds;
2539
- if (nanoseconds < 0) {
2540
- throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp nanoseconds out of range: ' + nanoseconds);
2541
- }
2542
- if (nanoseconds >= 1e9) {
2543
- throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp nanoseconds out of range: ' + nanoseconds);
2544
- }
2545
- if (seconds < MIN_SECONDS) {
2546
- throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp seconds out of range: ' + seconds);
2547
- }
2548
- // This will break in the year 10,000.
2549
- if (seconds >= 253402300800) {
2550
- throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp seconds out of range: ' + seconds);
2551
- }
2552
- }
2553
- /**
2554
- * Creates a new timestamp with the current date, with millisecond precision.
2555
- *
2556
- * @returns a new timestamp representing the current date.
2557
- */
2558
- static now() {
2559
- return Timestamp.fromMillis(Date.now());
2560
- }
2561
- /**
2562
- * Creates a new timestamp from the given date.
2563
- *
2564
- * @param date - The date to initialize the `Timestamp` from.
2565
- * @returns A new `Timestamp` representing the same point in time as the given
2566
- * date.
2567
- */
2568
- static fromDate(date) {
2569
- return Timestamp.fromMillis(date.getTime());
2570
- }
2571
- /**
2572
- * Creates a new timestamp from the given number of milliseconds.
2573
- *
2574
- * @param milliseconds - Number of milliseconds since Unix epoch
2575
- * 1970-01-01T00:00:00Z.
2576
- * @returns A new `Timestamp` representing the same point in time as the given
2577
- * number of milliseconds.
2578
- */
2579
- static fromMillis(milliseconds) {
2580
- const seconds = Math.floor(milliseconds / 1000);
2581
- const nanos = Math.floor((milliseconds - seconds * 1000) * MS_TO_NANOS);
2582
- return new Timestamp(seconds, nanos);
2583
- }
2584
- /**
2585
- * Converts a `Timestamp` to a JavaScript `Date` object. This conversion
2586
- * causes a loss of precision since `Date` objects only support millisecond
2587
- * precision.
2588
- *
2589
- * @returns JavaScript `Date` object representing the same point in time as
2590
- * this `Timestamp`, with millisecond precision.
2591
- */
2592
- toDate() {
2593
- return new Date(this.toMillis());
2594
- }
2595
- /**
2596
- * Converts a `Timestamp` to a numeric timestamp (in milliseconds since
2597
- * epoch). This operation causes a loss of precision.
2598
- *
2599
- * @returns The point in time corresponding to this timestamp, represented as
2600
- * the number of milliseconds since Unix epoch 1970-01-01T00:00:00Z.
2601
- */
2602
- toMillis() {
2603
- return this.seconds * 1000 + this.nanoseconds / MS_TO_NANOS;
2895
+ /**
2896
+ * Name of the IndexedDb object store.
2897
+ *
2898
+ * Note that the name 'owner' is chosen to ensure backwards compatibility with
2899
+ * older clients that only supported single locked access to the persistence
2900
+ * layer.
2901
+ */
2902
+ const DbPrimaryClientStore = 'owner';
2903
+ /**
2904
+ * The key string used for the single object that exists in the
2905
+ * DbPrimaryClient store.
2906
+ */
2907
+ const DbPrimaryClientKey = 'owner';
2908
+ /** Name of the IndexedDb object store. */
2909
+ const DbMutationQueueStore = 'mutationQueues';
2910
+ /** Keys are automatically assigned via the userId property. */
2911
+ const DbMutationQueueKeyPath = 'userId';
2912
+ /** Name of the IndexedDb object store. */
2913
+ const DbMutationBatchStore = 'mutations';
2914
+ /** Keys are automatically assigned via the userId, batchId properties. */
2915
+ const DbMutationBatchKeyPath = 'batchId';
2916
+ /** The index name for lookup of mutations by user. */
2917
+ const DbMutationBatchUserMutationsIndex = 'userMutationsIndex';
2918
+ /** The user mutations index is keyed by [userId, batchId] pairs. */
2919
+ const DbMutationBatchUserMutationsKeyPath = ['userId', 'batchId'];
2920
+ /**
2921
+ * Creates a [userId] key for use in the DbDocumentMutations index to iterate
2922
+ * over all of a user's document mutations.
2923
+ */
2924
+ function newDbDocumentMutationPrefixForUser(userId) {
2925
+ return [userId];
2926
+ }
2927
+ /**
2928
+ * Creates a [userId, encodedPath] key for use in the DbDocumentMutations
2929
+ * index to iterate over all at document mutations for a given path or lower.
2930
+ */
2931
+ function newDbDocumentMutationPrefixForPath(userId, path) {
2932
+ return [userId, encodeResourcePath(path)];
2933
+ }
2934
+ /**
2935
+ * Creates a full index key of [userId, encodedPath, batchId] for inserting
2936
+ * and deleting into the DbDocumentMutations index.
2937
+ */
2938
+ function newDbDocumentMutationKey(userId, path, batchId) {
2939
+ return [userId, encodeResourcePath(path), batchId];
2940
+ }
2941
+ /**
2942
+ * Because we store all the useful information for this store in the key,
2943
+ * there is no useful information to store as the value. The raw (unencoded)
2944
+ * path cannot be stored because IndexedDb doesn't store prototype
2945
+ * information.
2946
+ */
2947
+ const DbDocumentMutationPlaceholder = {};
2948
+ const DbDocumentMutationStore = 'documentMutations';
2949
+ const DbRemoteDocumentStore = 'remoteDocumentsV14';
2950
+ /**
2951
+ * The primary key of the remote documents store, which allows for efficient
2952
+ * access by collection path and read time.
2953
+ */
2954
+ const DbRemoteDocumentKeyPath = [
2955
+ 'prefixPath',
2956
+ 'collectionGroup',
2957
+ 'readTime',
2958
+ 'documentId'
2959
+ ];
2960
+ /** An index that provides access to documents by key. */
2961
+ const DbRemoteDocumentDocumentKeyIndex = 'documentKeyIndex';
2962
+ const DbRemoteDocumentDocumentKeyIndexPath = [
2963
+ 'prefixPath',
2964
+ 'collectionGroup',
2965
+ 'documentId'
2966
+ ];
2967
+ /**
2968
+ * An index that provides access to documents by collection group and read
2969
+ * time.
2970
+ *
2971
+ * This index is used by the index backfiller.
2972
+ */
2973
+ const DbRemoteDocumentCollectionGroupIndex = 'collectionGroupIndex';
2974
+ const DbRemoteDocumentCollectionGroupIndexPath = [
2975
+ 'collectionGroup',
2976
+ 'readTime',
2977
+ 'prefixPath',
2978
+ 'documentId'
2979
+ ];
2980
+ const DbRemoteDocumentGlobalStore = 'remoteDocumentGlobal';
2981
+ const DbRemoteDocumentGlobalKey = 'remoteDocumentGlobalKey';
2982
+ const DbTargetStore = 'targets';
2983
+ /** Keys are automatically assigned via the targetId property. */
2984
+ const DbTargetKeyPath = 'targetId';
2985
+ /** The name of the queryTargets index. */
2986
+ const DbTargetQueryTargetsIndexName = 'queryTargetsIndex';
2987
+ /**
2988
+ * The index of all canonicalIds to the targets that they match. This is not
2989
+ * a unique mapping because canonicalId does not promise a unique name for all
2990
+ * possible queries, so we append the targetId to make the mapping unique.
2991
+ */
2992
+ const DbTargetQueryTargetsKeyPath = ['canonicalId', 'targetId'];
2993
+ /** Name of the IndexedDb object store. */
2994
+ const DbTargetDocumentStore = 'targetDocuments';
2995
+ /** Keys are automatically assigned via the targetId, path properties. */
2996
+ const DbTargetDocumentKeyPath = ['targetId', 'path'];
2997
+ /** The index name for the reverse index. */
2998
+ const DbTargetDocumentDocumentTargetsIndex = 'documentTargetsIndex';
2999
+ /** We also need to create the reverse index for these properties. */
3000
+ const DbTargetDocumentDocumentTargetsKeyPath = ['path', 'targetId'];
3001
+ /**
3002
+ * The key string used for the single object that exists in the
3003
+ * DbTargetGlobal store.
3004
+ */
3005
+ const DbTargetGlobalKey = 'targetGlobalKey';
3006
+ const DbTargetGlobalStore = 'targetGlobal';
3007
+ /** Name of the IndexedDb object store. */
3008
+ const DbCollectionParentStore = 'collectionParents';
3009
+ /** Keys are automatically assigned via the collectionId, parent properties. */
3010
+ const DbCollectionParentKeyPath = ['collectionId', 'parent'];
3011
+ /** Name of the IndexedDb object store. */
3012
+ const DbClientMetadataStore = 'clientMetadata';
3013
+ /** Keys are automatically assigned via the clientId properties. */
3014
+ const DbClientMetadataKeyPath = 'clientId';
3015
+ /** Name of the IndexedDb object store. */
3016
+ const DbBundleStore = 'bundles';
3017
+ const DbBundleKeyPath = 'bundleId';
3018
+ /** Name of the IndexedDb object store. */
3019
+ const DbNamedQueryStore = 'namedQueries';
3020
+ const DbNamedQueryKeyPath = 'name';
3021
+ /** Name of the IndexedDb object store. */
3022
+ const DbIndexConfigurationStore = 'indexConfiguration';
3023
+ const DbIndexConfigurationKeyPath = 'indexId';
3024
+ /**
3025
+ * An index that provides access to the index configurations by collection
3026
+ * group.
3027
+ *
3028
+ * PORTING NOTE: iOS and Android maintain this index in-memory, but this is
3029
+ * not possible here as the Web client supports concurrent access to
3030
+ * persistence via multi-tab.
3031
+ */
3032
+ const DbIndexConfigurationCollectionGroupIndex = 'collectionGroupIndex';
3033
+ const DbIndexConfigurationCollectionGroupIndexPath = 'collectionGroup';
3034
+ /** Name of the IndexedDb object store. */
3035
+ const DbIndexStateStore = 'indexState';
3036
+ const DbIndexStateKeyPath = ['indexId', 'uid'];
3037
+ /**
3038
+ * An index that provides access to documents in a collection sorted by last
3039
+ * update time. Used by the backfiller.
3040
+ *
3041
+ * PORTING NOTE: iOS and Android maintain this index in-memory, but this is
3042
+ * not possible here as the Web client supports concurrent access to
3043
+ * persistence via multi-tab.
3044
+ */
3045
+ const DbIndexStateSequenceNumberIndex = 'sequenceNumberIndex';
3046
+ const DbIndexStateSequenceNumberIndexPath = ['uid', 'sequenceNumber'];
3047
+ /** Name of the IndexedDb object store. */
3048
+ const DbIndexEntryStore = 'indexEntries';
3049
+ const DbIndexEntryKeyPath = [
3050
+ 'indexId',
3051
+ 'uid',
3052
+ 'arrayValue',
3053
+ 'directionalValue',
3054
+ 'orderedDocumentKey',
3055
+ 'documentKey'
3056
+ ];
3057
+ const DbIndexEntryDocumentKeyIndex = 'documentKeyIndex';
3058
+ const DbIndexEntryDocumentKeyIndexPath = [
3059
+ 'indexId',
3060
+ 'uid',
3061
+ 'orderedDocumentKey'
3062
+ ];
3063
+ /** Name of the IndexedDb object store. */
3064
+ const DbDocumentOverlayStore = 'documentOverlays';
3065
+ const DbDocumentOverlayKeyPath = [
3066
+ 'userId',
3067
+ 'collectionPath',
3068
+ 'documentId'
3069
+ ];
3070
+ const DbDocumentOverlayCollectionPathOverlayIndex = 'collectionPathOverlayIndex';
3071
+ const DbDocumentOverlayCollectionPathOverlayIndexPath = [
3072
+ 'userId',
3073
+ 'collectionPath',
3074
+ 'largestBatchId'
3075
+ ];
3076
+ const DbDocumentOverlayCollectionGroupOverlayIndex = 'collectionGroupOverlayIndex';
3077
+ const DbDocumentOverlayCollectionGroupOverlayIndexPath = [
3078
+ 'userId',
3079
+ 'collectionGroup',
3080
+ 'largestBatchId'
3081
+ ];
3082
+ // Visible for testing
3083
+ const V1_STORES = [
3084
+ DbMutationQueueStore,
3085
+ DbMutationBatchStore,
3086
+ DbDocumentMutationStore,
3087
+ DbRemoteDocumentStore$1,
3088
+ DbTargetStore,
3089
+ DbPrimaryClientStore,
3090
+ DbTargetGlobalStore,
3091
+ DbTargetDocumentStore
3092
+ ];
3093
+ // Visible for testing
3094
+ const V3_STORES = V1_STORES;
3095
+ // Note: DbRemoteDocumentChanges is no longer used and dropped with v9.
3096
+ const V4_STORES = [...V3_STORES, DbClientMetadataStore];
3097
+ const V6_STORES = [...V4_STORES, DbRemoteDocumentGlobalStore];
3098
+ const V8_STORES = [...V6_STORES, DbCollectionParentStore];
3099
+ const V11_STORES = [...V8_STORES, DbBundleStore, DbNamedQueryStore];
3100
+ const V12_STORES = [...V11_STORES, DbDocumentOverlayStore];
3101
+ const V13_STORES = [
3102
+ DbMutationQueueStore,
3103
+ DbMutationBatchStore,
3104
+ DbDocumentMutationStore,
3105
+ DbRemoteDocumentStore,
3106
+ DbTargetStore,
3107
+ DbPrimaryClientStore,
3108
+ DbTargetGlobalStore,
3109
+ DbTargetDocumentStore,
3110
+ DbClientMetadataStore,
3111
+ DbRemoteDocumentGlobalStore,
3112
+ DbCollectionParentStore,
3113
+ DbBundleStore,
3114
+ DbNamedQueryStore,
3115
+ DbDocumentOverlayStore
3116
+ ];
3117
+ const V14_STORES = V13_STORES;
3118
+ const V15_STORES = [
3119
+ ...V14_STORES,
3120
+ DbIndexConfigurationStore,
3121
+ DbIndexStateStore,
3122
+ DbIndexEntryStore
3123
+ ];
3124
+ /** Returns the object stores for the provided schema. */
3125
+ function getObjectStores(schemaVersion) {
3126
+ if (schemaVersion === 15) {
3127
+ return V15_STORES;
2604
3128
  }
2605
- _compareTo(other) {
2606
- if (this.seconds === other.seconds) {
2607
- return primitiveComparator(this.nanoseconds, other.nanoseconds);
2608
- }
2609
- return primitiveComparator(this.seconds, other.seconds);
3129
+ else if (schemaVersion === 14) {
3130
+ return V14_STORES;
2610
3131
  }
2611
- /**
2612
- * Returns true if this `Timestamp` is equal to the provided one.
2613
- *
2614
- * @param other - The `Timestamp` to compare against.
2615
- * @returns true if this `Timestamp` is equal to the provided one.
2616
- */
2617
- isEqual(other) {
2618
- return (other.seconds === this.seconds && other.nanoseconds === this.nanoseconds);
3132
+ else if (schemaVersion === 13) {
3133
+ return V13_STORES;
2619
3134
  }
2620
- /** Returns a textual representation of this `Timestamp`. */
2621
- toString() {
2622
- return ('Timestamp(seconds=' +
2623
- this.seconds +
2624
- ', nanoseconds=' +
2625
- this.nanoseconds +
2626
- ')');
3135
+ else if (schemaVersion === 12) {
3136
+ return V12_STORES;
2627
3137
  }
2628
- /** Returns a JSON-serializable representation of this `Timestamp`. */
2629
- toJSON() {
2630
- return { seconds: this.seconds, nanoseconds: this.nanoseconds };
3138
+ else if (schemaVersion === 11) {
3139
+ return V11_STORES;
2631
3140
  }
2632
- /**
2633
- * Converts this object to a primitive string, which allows `Timestamp` objects
2634
- * to be compared using the `>`, `<=`, `>=` and `>` operators.
2635
- */
2636
- valueOf() {
2637
- // This method returns a string of the form <seconds>.<nanoseconds> where
2638
- // <seconds> is translated to have a non-negative value and both <seconds>
2639
- // and <nanoseconds> are left-padded with zeroes to be a consistent length.
2640
- // Strings with this format then have a lexiographical ordering that matches
2641
- // the expected ordering. The <seconds> translation is done to avoid having
2642
- // a leading negative sign (i.e. a leading '-' character) in its string
2643
- // representation, which would affect its lexiographical ordering.
2644
- const adjustedSeconds = this.seconds - MIN_SECONDS;
2645
- // Note: Up to 12 decimal digits are required to represent all valid
2646
- // 'seconds' values.
2647
- const formattedSeconds = String(adjustedSeconds).padStart(12, '0');
2648
- const formattedNanoseconds = String(this.nanoseconds).padStart(9, '0');
2649
- return formattedSeconds + '.' + formattedNanoseconds;
3141
+ else {
3142
+ fail();
2650
3143
  }
2651
3144
  }
2652
3145
 
2653
3146
  /**
2654
3147
  * @license
2655
- * Copyright 2017 Google LLC
3148
+ * Copyright 2020 Google LLC
2656
3149
  *
2657
3150
  * Licensed under the Apache License, Version 2.0 (the "License");
2658
3151
  * you may not use this file except in compliance with the License.
@@ -2666,40 +3159,16 @@ class Timestamp {
2666
3159
  * See the License for the specific language governing permissions and
2667
3160
  * limitations under the License.
2668
3161
  */
2669
- /**
2670
- * A version of a document in Firestore. This corresponds to the version
2671
- * timestamp, such as update_time or read_time.
2672
- */
2673
- class SnapshotVersion {
2674
- constructor(timestamp) {
2675
- this.timestamp = timestamp;
2676
- }
2677
- static fromTimestamp(value) {
2678
- return new SnapshotVersion(value);
2679
- }
2680
- static min() {
2681
- return new SnapshotVersion(new Timestamp(0, 0));
2682
- }
2683
- static max() {
2684
- return new SnapshotVersion(new Timestamp(253402300799, 1e9 - 1));
2685
- }
2686
- compareTo(other) {
2687
- return this.timestamp._compareTo(other.timestamp);
2688
- }
2689
- isEqual(other) {
2690
- return this.timestamp.isEqual(other.timestamp);
2691
- }
2692
- /** Returns a number representation of the version for use in spec tests. */
2693
- toMicroseconds() {
2694
- // Convert to microseconds.
2695
- return this.timestamp.seconds * 1e6 + this.timestamp.nanoseconds / 1000;
2696
- }
2697
- toString() {
2698
- return 'SnapshotVersion(' + this.timestamp.toString() + ')';
2699
- }
2700
- toTimestamp() {
2701
- return this.timestamp;
3162
+ class IndexedDbTransaction extends PersistenceTransaction {
3163
+ constructor(simpleDbTransaction, currentSequenceNumber) {
3164
+ super();
3165
+ this.simpleDbTransaction = simpleDbTransaction;
3166
+ this.currentSequenceNumber = currentSequenceNumber;
2702
3167
  }
3168
+ }
3169
+ function getStore(txn, store) {
3170
+ const indexedDbTransaction = debugCast(txn);
3171
+ return SimpleDb.getStore(indexedDbTransaction.simpleDbTransaction, store);
2703
3172
  }
2704
3173
 
2705
3174
  /**
@@ -3868,104 +4337,33 @@ class DatabaseId {
3868
4337
  * Unless required by applicable law or agreed to in writing, software
3869
4338
  * distributed under the License is distributed on an "AS IS" BASIS,
3870
4339
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3871
- * See the License for the specific language governing permissions and
3872
- * limitations under the License.
3873
- */
3874
- /** Sentinel value that sorts before any Mutation Batch ID. */
3875
- const BATCHID_UNKNOWN = -1;
3876
- /**
3877
- * Returns whether a variable is either undefined or null.
3878
- */
3879
- function isNullOrUndefined(value) {
3880
- return value === null || value === undefined;
3881
- }
3882
- /** Returns whether the value represents -0. */
3883
- function isNegativeZero(value) {
3884
- // Detect if the value is -0.0. Based on polyfill from
3885
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
3886
- return value === 0 && 1 / value === 1 / -0;
3887
- }
3888
- /**
3889
- * Returns whether a value is an integer and in the safe integer range
3890
- * @param value - The value to test for being an integer and in the safe range
3891
- */
3892
- function isSafeInteger(value) {
3893
- return (typeof value === 'number' &&
3894
- Number.isInteger(value) &&
3895
- !isNegativeZero(value) &&
3896
- value <= Number.MAX_SAFE_INTEGER &&
3897
- value >= Number.MIN_SAFE_INTEGER);
3898
- }
3899
-
3900
- /**
3901
- * @license
3902
- * Copyright 2017 Google LLC
3903
- *
3904
- * Licensed under the Apache License, Version 2.0 (the "License");
3905
- * you may not use this file except in compliance with the License.
3906
- * You may obtain a copy of the License at
3907
- *
3908
- * http://www.apache.org/licenses/LICENSE-2.0
3909
- *
3910
- * Unless required by applicable law or agreed to in writing, software
3911
- * distributed under the License is distributed on an "AS IS" BASIS,
3912
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
3913
- * See the License for the specific language governing permissions and
3914
- * limitations under the License.
3915
- */
3916
- /**
3917
- * @internal
3918
- */
3919
- class DocumentKey {
3920
- constructor(path) {
3921
- this.path = path;
3922
- }
3923
- static fromPath(path) {
3924
- return new DocumentKey(ResourcePath.fromString(path));
3925
- }
3926
- static fromName(name) {
3927
- return new DocumentKey(ResourcePath.fromString(name).popFirst(5));
3928
- }
3929
- static empty() {
3930
- return new DocumentKey(ResourcePath.emptyPath());
3931
- }
3932
- get collectionGroup() {
3933
- return this.path.popLast().lastSegment();
3934
- }
3935
- /** Returns true if the document is in the specified collectionId. */
3936
- hasCollectionId(collectionId) {
3937
- return (this.path.length >= 2 &&
3938
- this.path.get(this.path.length - 2) === collectionId);
3939
- }
3940
- /** Returns the collection group (i.e. the name of the parent collection) for this key. */
3941
- getCollectionGroup() {
3942
- return this.path.get(this.path.length - 2);
3943
- }
3944
- /** Returns the fully qualified path to the parent collection. */
3945
- getCollectionPath() {
3946
- return this.path.popLast();
3947
- }
3948
- isEqual(other) {
3949
- return (other !== null && ResourcePath.comparator(this.path, other.path) === 0);
3950
- }
3951
- toString() {
3952
- return this.path.toString();
3953
- }
3954
- static comparator(k1, k2) {
3955
- return ResourcePath.comparator(k1.path, k2.path);
3956
- }
3957
- static isDocumentKey(path) {
3958
- return path.length % 2 === 0;
3959
- }
3960
- /**
3961
- * Creates and returns a new document key with the given segments.
3962
- *
3963
- * @param segments - The segments of the path to the document
3964
- * @returns A new instance of DocumentKey
3965
- */
3966
- static fromSegments(segments) {
3967
- return new DocumentKey(new ResourcePath(segments.slice()));
3968
- }
4340
+ * See the License for the specific language governing permissions and
4341
+ * limitations under the License.
4342
+ */
4343
+ /** Sentinel value that sorts before any Mutation Batch ID. */
4344
+ const BATCHID_UNKNOWN = -1;
4345
+ /**
4346
+ * Returns whether a variable is either undefined or null.
4347
+ */
4348
+ function isNullOrUndefined(value) {
4349
+ return value === null || value === undefined;
4350
+ }
4351
+ /** Returns whether the value represents -0. */
4352
+ function isNegativeZero(value) {
4353
+ // Detect if the value is -0.0. Based on polyfill from
4354
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
4355
+ return value === 0 && 1 / value === 1 / -0;
4356
+ }
4357
+ /**
4358
+ * Returns whether a value is an integer and in the safe integer range
4359
+ * @param value - The value to test for being an integer and in the safe range
4360
+ */
4361
+ function isSafeInteger(value) {
4362
+ return (typeof value === 'number' &&
4363
+ Number.isInteger(value) &&
4364
+ !isNegativeZero(value) &&
4365
+ value <= Number.MAX_SAFE_INTEGER &&
4366
+ value >= Number.MIN_SAFE_INTEGER);
3969
4367
  }
3970
4368
 
3971
4369
  /**
@@ -4586,274 +4984,113 @@ class ObjectValue {
4586
4984
  /**
4587
4985
  * Sets the provided fields to the provided values.
4588
4986
  *
4589
- * @param data - A map of fields to values (or null for deletes).
4590
- */
4591
- setAll(data) {
4592
- let parent = FieldPath$1.emptyPath();
4593
- let upserts = {};
4594
- let deletes = [];
4595
- data.forEach((value, path) => {
4596
- if (!parent.isImmediateParentOf(path)) {
4597
- // Insert the accumulated changes at this parent location
4598
- const fieldsMap = this.getFieldsMap(parent);
4599
- this.applyChanges(fieldsMap, upserts, deletes);
4600
- upserts = {};
4601
- deletes = [];
4602
- parent = path.popLast();
4603
- }
4604
- if (value) {
4605
- upserts[path.lastSegment()] = deepClone(value);
4606
- }
4607
- else {
4608
- deletes.push(path.lastSegment());
4609
- }
4610
- });
4611
- const fieldsMap = this.getFieldsMap(parent);
4612
- this.applyChanges(fieldsMap, upserts, deletes);
4613
- }
4614
- /**
4615
- * Removes the field at the specified path. If there is no field at the
4616
- * specified path, nothing is changed.
4617
- *
4618
- * @param path - The field path to remove.
4619
- */
4620
- delete(path) {
4621
- const nestedValue = this.field(path.popLast());
4622
- if (isMapValue(nestedValue) && nestedValue.mapValue.fields) {
4623
- delete nestedValue.mapValue.fields[path.lastSegment()];
4624
- }
4625
- }
4626
- isEqual(other) {
4627
- return valueEquals(this.value, other.value);
4628
- }
4629
- /**
4630
- * Returns the map that contains the leaf element of `path`. If the parent
4631
- * entry does not yet exist, or if it is not a map, a new map will be created.
4632
- */
4633
- getFieldsMap(path) {
4634
- let current = this.value;
4635
- if (!current.mapValue.fields) {
4636
- current.mapValue = { fields: {} };
4637
- }
4638
- for (let i = 0; i < path.length; ++i) {
4639
- let next = current.mapValue.fields[path.get(i)];
4640
- if (!isMapValue(next) || !next.mapValue.fields) {
4641
- next = { mapValue: { fields: {} } };
4642
- current.mapValue.fields[path.get(i)] = next;
4643
- }
4644
- current = next;
4645
- }
4646
- return current.mapValue.fields;
4647
- }
4648
- /**
4649
- * Modifies `fieldsMap` by adding, replacing or deleting the specified
4650
- * entries.
4651
- */
4652
- applyChanges(fieldsMap, inserts, deletes) {
4653
- forEach(inserts, (key, val) => (fieldsMap[key] = val));
4654
- for (const field of deletes) {
4655
- delete fieldsMap[field];
4656
- }
4657
- }
4658
- clone() {
4659
- return new ObjectValue(deepClone(this.value));
4660
- }
4661
- }
4662
- /**
4663
- * Returns a FieldMask built from all fields in a MapValue.
4664
- */
4665
- function extractFieldMask(value) {
4666
- const fields = [];
4667
- forEach(value.fields, (key, value) => {
4668
- const currentPath = new FieldPath$1([key]);
4669
- if (isMapValue(value)) {
4670
- const nestedMask = extractFieldMask(value.mapValue);
4671
- const nestedFields = nestedMask.fields;
4672
- if (nestedFields.length === 0) {
4673
- // Preserve the empty map by adding it to the FieldMask.
4674
- fields.push(currentPath);
4675
- }
4676
- else {
4677
- // For nested and non-empty ObjectValues, add the FieldPath of the
4678
- // leaf nodes.
4679
- for (const nestedPath of nestedFields) {
4680
- fields.push(currentPath.child(nestedPath));
4681
- }
4682
- }
4683
- }
4684
- else {
4685
- // For nested and non-empty ObjectValues, add the FieldPath of the leaf
4686
- // nodes.
4687
- fields.push(currentPath);
4688
- }
4689
- });
4690
- return new FieldMask(fields);
4691
- }
4692
-
4693
- /**
4694
- * @license
4695
- * Copyright 2017 Google LLC
4696
- *
4697
- * Licensed under the Apache License, Version 2.0 (the "License");
4698
- * you may not use this file except in compliance with the License.
4699
- * You may obtain a copy of the License at
4700
- *
4701
- * http://www.apache.org/licenses/LICENSE-2.0
4702
- *
4703
- * Unless required by applicable law or agreed to in writing, software
4704
- * distributed under the License is distributed on an "AS IS" BASIS,
4705
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
4706
- * See the License for the specific language governing permissions and
4707
- * limitations under the License.
4708
- */
4709
- /**
4710
- * Represents a document in Firestore with a key, version, data and whether it
4711
- * has local mutations applied to it.
4712
- *
4713
- * Documents can transition between states via `convertToFoundDocument()`,
4714
- * `convertToNoDocument()` and `convertToUnknownDocument()`. If a document does
4715
- * not transition to one of these states even after all mutations have been
4716
- * applied, `isValidDocument()` returns false and the document should be removed
4717
- * from all views.
4718
- */
4719
- class MutableDocument {
4720
- constructor(key, documentType, version, readTime, data, documentState) {
4721
- this.key = key;
4722
- this.documentType = documentType;
4723
- this.version = version;
4724
- this.readTime = readTime;
4725
- this.data = data;
4726
- this.documentState = documentState;
4727
- }
4728
- /**
4729
- * Creates a document with no known version or data, but which can serve as
4730
- * base document for mutations.
4731
- */
4732
- static newInvalidDocument(documentKey) {
4733
- return new MutableDocument(documentKey, 0 /* INVALID */, SnapshotVersion.min(), SnapshotVersion.min(), ObjectValue.empty(), 0 /* SYNCED */);
4734
- }
4735
- /**
4736
- * Creates a new document that is known to exist with the given data at the
4737
- * given version.
4738
- */
4739
- static newFoundDocument(documentKey, version, value) {
4740
- return new MutableDocument(documentKey, 1 /* FOUND_DOCUMENT */, version, SnapshotVersion.min(), value, 0 /* SYNCED */);
4741
- }
4742
- /** Creates a new document that is known to not exist at the given version. */
4743
- static newNoDocument(documentKey, version) {
4744
- return new MutableDocument(documentKey, 2 /* NO_DOCUMENT */, version, SnapshotVersion.min(), ObjectValue.empty(), 0 /* SYNCED */);
4745
- }
4746
- /**
4747
- * Creates a new document that is known to exist at the given version but
4748
- * whose data is not known (e.g. a document that was updated without a known
4749
- * base document).
4750
- */
4751
- static newUnknownDocument(documentKey, version) {
4752
- return new MutableDocument(documentKey, 3 /* UNKNOWN_DOCUMENT */, version, SnapshotVersion.min(), ObjectValue.empty(), 2 /* HAS_COMMITTED_MUTATIONS */);
4753
- }
4754
- /**
4755
- * Changes the document type to indicate that it exists and that its version
4756
- * and data are known.
4757
- */
4758
- convertToFoundDocument(version, value) {
4759
- this.version = version;
4760
- this.documentType = 1 /* FOUND_DOCUMENT */;
4761
- this.data = value;
4762
- this.documentState = 0 /* SYNCED */;
4763
- return this;
4764
- }
4765
- /**
4766
- * Changes the document type to indicate that it doesn't exist at the given
4767
- * version.
4768
- */
4769
- convertToNoDocument(version) {
4770
- this.version = version;
4771
- this.documentType = 2 /* NO_DOCUMENT */;
4772
- this.data = ObjectValue.empty();
4773
- this.documentState = 0 /* SYNCED */;
4774
- return this;
4775
- }
4776
- /**
4777
- * Changes the document type to indicate that it exists at a given version but
4778
- * that its data is not known (e.g. a document that was updated without a known
4779
- * base document).
4780
- */
4781
- convertToUnknownDocument(version) {
4782
- this.version = version;
4783
- this.documentType = 3 /* UNKNOWN_DOCUMENT */;
4784
- this.data = ObjectValue.empty();
4785
- this.documentState = 2 /* HAS_COMMITTED_MUTATIONS */;
4786
- return this;
4787
- }
4788
- setHasCommittedMutations() {
4789
- this.documentState = 2 /* HAS_COMMITTED_MUTATIONS */;
4790
- return this;
4791
- }
4792
- setHasLocalMutations() {
4793
- this.documentState = 1 /* HAS_LOCAL_MUTATIONS */;
4794
- this.version = SnapshotVersion.min();
4795
- return this;
4796
- }
4797
- setReadTime(readTime) {
4798
- this.readTime = readTime;
4799
- return this;
4800
- }
4801
- get hasLocalMutations() {
4802
- return this.documentState === 1 /* HAS_LOCAL_MUTATIONS */;
4803
- }
4804
- get hasCommittedMutations() {
4805
- return this.documentState === 2 /* HAS_COMMITTED_MUTATIONS */;
4806
- }
4807
- get hasPendingWrites() {
4808
- return this.hasLocalMutations || this.hasCommittedMutations;
4809
- }
4810
- isValidDocument() {
4811
- return this.documentType !== 0 /* INVALID */;
4812
- }
4813
- isFoundDocument() {
4814
- return this.documentType === 1 /* FOUND_DOCUMENT */;
4815
- }
4816
- isNoDocument() {
4817
- return this.documentType === 2 /* NO_DOCUMENT */;
4987
+ * @param data - A map of fields to values (or null for deletes).
4988
+ */
4989
+ setAll(data) {
4990
+ let parent = FieldPath$1.emptyPath();
4991
+ let upserts = {};
4992
+ let deletes = [];
4993
+ data.forEach((value, path) => {
4994
+ if (!parent.isImmediateParentOf(path)) {
4995
+ // Insert the accumulated changes at this parent location
4996
+ const fieldsMap = this.getFieldsMap(parent);
4997
+ this.applyChanges(fieldsMap, upserts, deletes);
4998
+ upserts = {};
4999
+ deletes = [];
5000
+ parent = path.popLast();
5001
+ }
5002
+ if (value) {
5003
+ upserts[path.lastSegment()] = deepClone(value);
5004
+ }
5005
+ else {
5006
+ deletes.push(path.lastSegment());
5007
+ }
5008
+ });
5009
+ const fieldsMap = this.getFieldsMap(parent);
5010
+ this.applyChanges(fieldsMap, upserts, deletes);
4818
5011
  }
4819
- isUnknownDocument() {
4820
- return this.documentType === 3 /* UNKNOWN_DOCUMENT */;
5012
+ /**
5013
+ * Removes the field at the specified path. If there is no field at the
5014
+ * specified path, nothing is changed.
5015
+ *
5016
+ * @param path - The field path to remove.
5017
+ */
5018
+ delete(path) {
5019
+ const nestedValue = this.field(path.popLast());
5020
+ if (isMapValue(nestedValue) && nestedValue.mapValue.fields) {
5021
+ delete nestedValue.mapValue.fields[path.lastSegment()];
5022
+ }
4821
5023
  }
4822
5024
  isEqual(other) {
4823
- return (other instanceof MutableDocument &&
4824
- this.key.isEqual(other.key) &&
4825
- this.version.isEqual(other.version) &&
4826
- this.documentType === other.documentType &&
4827
- this.documentState === other.documentState &&
4828
- this.data.isEqual(other.data));
5025
+ return valueEquals(this.value, other.value);
4829
5026
  }
4830
- mutableCopy() {
4831
- return new MutableDocument(this.key, this.documentType, this.version, this.readTime, this.data.clone(), this.documentState);
5027
+ /**
5028
+ * Returns the map that contains the leaf element of `path`. If the parent
5029
+ * entry does not yet exist, or if it is not a map, a new map will be created.
5030
+ */
5031
+ getFieldsMap(path) {
5032
+ let current = this.value;
5033
+ if (!current.mapValue.fields) {
5034
+ current.mapValue = { fields: {} };
5035
+ }
5036
+ for (let i = 0; i < path.length; ++i) {
5037
+ let next = current.mapValue.fields[path.get(i)];
5038
+ if (!isMapValue(next) || !next.mapValue.fields) {
5039
+ next = { mapValue: { fields: {} } };
5040
+ current.mapValue.fields[path.get(i)] = next;
5041
+ }
5042
+ current = next;
5043
+ }
5044
+ return current.mapValue.fields;
4832
5045
  }
4833
- toString() {
4834
- return (`Document(${this.key}, ${this.version}, ${JSON.stringify(this.data.value)}, ` +
4835
- `{documentType: ${this.documentType}}), ` +
4836
- `{documentState: ${this.documentState}})`);
5046
+ /**
5047
+ * Modifies `fieldsMap` by adding, replacing or deleting the specified
5048
+ * entries.
5049
+ */
5050
+ applyChanges(fieldsMap, inserts, deletes) {
5051
+ forEach(inserts, (key, val) => (fieldsMap[key] = val));
5052
+ for (const field of deletes) {
5053
+ delete fieldsMap[field];
5054
+ }
5055
+ }
5056
+ clone() {
5057
+ return new ObjectValue(deepClone(this.value));
4837
5058
  }
4838
5059
  }
4839
5060
  /**
4840
- * Compares the value for field `field` in the provided documents. Throws if
4841
- * the field does not exist in both documents.
5061
+ * Returns a FieldMask built from all fields in a MapValue.
4842
5062
  */
4843
- function compareDocumentsByField(field, d1, d2) {
4844
- const v1 = d1.data.field(field);
4845
- const v2 = d2.data.field(field);
4846
- if (v1 !== null && v2 !== null) {
4847
- return valueCompare(v1, v2);
4848
- }
4849
- else {
4850
- return fail();
4851
- }
5063
+ function extractFieldMask(value) {
5064
+ const fields = [];
5065
+ forEach(value.fields, (key, value) => {
5066
+ const currentPath = new FieldPath$1([key]);
5067
+ if (isMapValue(value)) {
5068
+ const nestedMask = extractFieldMask(value.mapValue);
5069
+ const nestedFields = nestedMask.fields;
5070
+ if (nestedFields.length === 0) {
5071
+ // Preserve the empty map by adding it to the FieldMask.
5072
+ fields.push(currentPath);
5073
+ }
5074
+ else {
5075
+ // For nested and non-empty ObjectValues, add the FieldPath of the
5076
+ // leaf nodes.
5077
+ for (const nestedPath of nestedFields) {
5078
+ fields.push(currentPath.child(nestedPath));
5079
+ }
5080
+ }
5081
+ }
5082
+ else {
5083
+ // For nested and non-empty ObjectValues, add the FieldPath of the leaf
5084
+ // nodes.
5085
+ fields.push(currentPath);
5086
+ }
5087
+ });
5088
+ return new FieldMask(fields);
4852
5089
  }
4853
5090
 
4854
5091
  /**
4855
5092
  * @license
4856
- * Copyright 2021 Google LLC
5093
+ * Copyright 2017 Google LLC
4857
5094
  *
4858
5095
  * Licensed under the Apache License, Version 2.0 (the "License");
4859
5096
  * you may not use this file except in compliance with the License.
@@ -4868,166 +5105,148 @@ function compareDocumentsByField(field, d1, d2) {
4868
5105
  * limitations under the License.
4869
5106
  */
4870
5107
  /**
4871
- * The initial mutation batch id for each index. Gets updated during index
4872
- * backfill.
4873
- */
4874
- const INITIAL_LARGEST_BATCH_ID = -1;
4875
- /**
4876
- * The initial sequence number for each index. Gets updated during index
4877
- * backfill.
4878
- */
4879
- const INITIAL_SEQUENCE_NUMBER = 0;
4880
- /**
4881
- * An index definition for field indexes in Firestore.
4882
- *
4883
- * Every index is associated with a collection. The definition contains a list
4884
- * of fields and their index kind (which can be `ASCENDING`, `DESCENDING` or
4885
- * `CONTAINS` for ArrayContains/ArrayContainsAny queries).
4886
- *
4887
- * Unlike the backend, the SDK does not differentiate between collection or
4888
- * collection group-scoped indices. Every index can be used for both single
4889
- * collection and collection group queries.
4890
- */
4891
- class FieldIndex {
4892
- constructor(
4893
- /**
4894
- * The index ID. Returns -1 if the index ID is not available (e.g. the index
4895
- * has not yet been persisted).
4896
- */
4897
- indexId,
4898
- /** The collection ID this index applies to. */
4899
- collectionGroup,
4900
- /** The field segments for this index. */
4901
- fields,
4902
- /** Shows how up-to-date the index is for the current user. */
4903
- indexState) {
4904
- this.indexId = indexId;
4905
- this.collectionGroup = collectionGroup;
4906
- this.fields = fields;
4907
- this.indexState = indexState;
4908
- }
4909
- }
4910
- /** An ID for an index that has not yet been added to persistence. */
4911
- FieldIndex.UNKNOWN_ID = -1;
4912
- /** Returns the ArrayContains/ArrayContainsAny segment for this index. */
4913
- function fieldIndexGetArraySegment(fieldIndex) {
4914
- return fieldIndex.fields.find(s => s.kind === 2 /* CONTAINS */);
4915
- }
4916
- /** Returns all directional (ascending/descending) segments for this index. */
4917
- function fieldIndexGetDirectionalSegments(fieldIndex) {
4918
- return fieldIndex.fields.filter(s => s.kind !== 2 /* CONTAINS */);
4919
- }
4920
- /**
4921
- * Returns the order of the document key component for the given index.
5108
+ * Represents a document in Firestore with a key, version, data and whether it
5109
+ * has local mutations applied to it.
4922
5110
  *
4923
- * PORTING NOTE: This is only used in the Web IndexedDb implementation.
5111
+ * Documents can transition between states via `convertToFoundDocument()`,
5112
+ * `convertToNoDocument()` and `convertToUnknownDocument()`. If a document does
5113
+ * not transition to one of these states even after all mutations have been
5114
+ * applied, `isValidDocument()` returns false and the document should be removed
5115
+ * from all views.
4924
5116
  */
4925
- function fieldIndexGetKeyOrder(fieldIndex) {
4926
- const directionalSegments = fieldIndexGetDirectionalSegments(fieldIndex);
4927
- return directionalSegments.length === 0
4928
- ? 0 /* ASCENDING */
4929
- : directionalSegments[directionalSegments.length - 1].kind;
4930
- }
4931
- /** Returns a debug representation of the field index */
4932
- function fieldIndexToString(fieldIndex) {
4933
- return `id=${fieldIndex.indexId}|cg=${fieldIndex.collectionGroup}|f=${fieldIndex.fields.map(f => `${f.fieldPath}:${f.kind}`).join(',')}`;
4934
- }
4935
- /** An index component consisting of field path and index type. */
4936
- class IndexSegment {
4937
- constructor(
4938
- /** The field path of the component. */
4939
- fieldPath,
4940
- /** The fields sorting order. */
4941
- kind) {
4942
- this.fieldPath = fieldPath;
4943
- this.kind = kind;
5117
+ class MutableDocument {
5118
+ constructor(key, documentType, version, readTime, data, documentState) {
5119
+ this.key = key;
5120
+ this.documentType = documentType;
5121
+ this.version = version;
5122
+ this.readTime = readTime;
5123
+ this.data = data;
5124
+ this.documentState = documentState;
4944
5125
  }
4945
- }
4946
- /**
4947
- * Stores the "high water mark" that indicates how updated the Index is for the
4948
- * current user.
4949
- */
4950
- class IndexState {
4951
- constructor(
4952
5126
  /**
4953
- * Indicates when the index was last updated (relative to other indexes).
5127
+ * Creates a document with no known version or data, but which can serve as
5128
+ * base document for mutations.
4954
5129
  */
4955
- sequenceNumber,
4956
- /** The the latest indexed read time, document and batch id. */
4957
- offset) {
4958
- this.sequenceNumber = sequenceNumber;
4959
- this.offset = offset;
4960
- }
4961
- /** The state of an index that has not yet been backfilled. */
4962
- static empty() {
4963
- return new IndexState(INITIAL_SEQUENCE_NUMBER, IndexOffset.min());
5130
+ static newInvalidDocument(documentKey) {
5131
+ return new MutableDocument(documentKey, 0 /* INVALID */, SnapshotVersion.min(), SnapshotVersion.min(), ObjectValue.empty(), 0 /* SYNCED */);
4964
5132
  }
4965
- }
4966
- /**
4967
- * Creates an offset that matches all documents with a read time higher than
4968
- * `readTime`.
4969
- */
4970
- function newIndexOffsetSuccessorFromReadTime(readTime, largestBatchId) {
4971
- // We want to create an offset that matches all documents with a read time
4972
- // greater than the provided read time. To do so, we technically need to
4973
- // create an offset for `(readTime, MAX_DOCUMENT_KEY)`. While we could use
4974
- // Unicode codepoints to generate MAX_DOCUMENT_KEY, it is much easier to use
4975
- // `(readTime + 1, DocumentKey.empty())` since `> DocumentKey.empty()` matches
4976
- // all valid document IDs.
4977
- const successorSeconds = readTime.toTimestamp().seconds;
4978
- const successorNanos = readTime.toTimestamp().nanoseconds + 1;
4979
- const successor = SnapshotVersion.fromTimestamp(successorNanos === 1e9
4980
- ? new Timestamp(successorSeconds + 1, 0)
4981
- : new Timestamp(successorSeconds, successorNanos));
4982
- return new IndexOffset(successor, DocumentKey.empty(), largestBatchId);
4983
- }
4984
- /** Creates a new offset based on the provided document. */
4985
- function newIndexOffsetFromDocument(document) {
4986
- return new IndexOffset(document.readTime, document.key, INITIAL_LARGEST_BATCH_ID);
4987
- }
4988
- /**
4989
- * Stores the latest read time, document and batch ID that were processed for an
4990
- * index.
4991
- */
4992
- class IndexOffset {
4993
- constructor(
4994
5133
  /**
4995
- * The latest read time version that has been indexed by Firestore for this
4996
- * field index.
5134
+ * Creates a new document that is known to exist with the given data at the
5135
+ * given version.
4997
5136
  */
4998
- readTime,
5137
+ static newFoundDocument(documentKey, version, value) {
5138
+ return new MutableDocument(documentKey, 1 /* FOUND_DOCUMENT */, version, SnapshotVersion.min(), value, 0 /* SYNCED */);
5139
+ }
5140
+ /** Creates a new document that is known to not exist at the given version. */
5141
+ static newNoDocument(documentKey, version) {
5142
+ return new MutableDocument(documentKey, 2 /* NO_DOCUMENT */, version, SnapshotVersion.min(), ObjectValue.empty(), 0 /* SYNCED */);
5143
+ }
4999
5144
  /**
5000
- * The key of the last document that was indexed for this query. Use
5001
- * `DocumentKey.empty()` if no document has been indexed.
5145
+ * Creates a new document that is known to exist at the given version but
5146
+ * whose data is not known (e.g. a document that was updated without a known
5147
+ * base document).
5002
5148
  */
5003
- documentKey,
5004
- /*
5005
- * The largest mutation batch id that's been processed by Firestore.
5149
+ static newUnknownDocument(documentKey, version) {
5150
+ return new MutableDocument(documentKey, 3 /* UNKNOWN_DOCUMENT */, version, SnapshotVersion.min(), ObjectValue.empty(), 2 /* HAS_COMMITTED_MUTATIONS */);
5151
+ }
5152
+ /**
5153
+ * Changes the document type to indicate that it exists and that its version
5154
+ * and data are known.
5006
5155
  */
5007
- largestBatchId) {
5156
+ convertToFoundDocument(version, value) {
5157
+ this.version = version;
5158
+ this.documentType = 1 /* FOUND_DOCUMENT */;
5159
+ this.data = value;
5160
+ this.documentState = 0 /* SYNCED */;
5161
+ return this;
5162
+ }
5163
+ /**
5164
+ * Changes the document type to indicate that it doesn't exist at the given
5165
+ * version.
5166
+ */
5167
+ convertToNoDocument(version) {
5168
+ this.version = version;
5169
+ this.documentType = 2 /* NO_DOCUMENT */;
5170
+ this.data = ObjectValue.empty();
5171
+ this.documentState = 0 /* SYNCED */;
5172
+ return this;
5173
+ }
5174
+ /**
5175
+ * Changes the document type to indicate that it exists at a given version but
5176
+ * that its data is not known (e.g. a document that was updated without a known
5177
+ * base document).
5178
+ */
5179
+ convertToUnknownDocument(version) {
5180
+ this.version = version;
5181
+ this.documentType = 3 /* UNKNOWN_DOCUMENT */;
5182
+ this.data = ObjectValue.empty();
5183
+ this.documentState = 2 /* HAS_COMMITTED_MUTATIONS */;
5184
+ return this;
5185
+ }
5186
+ setHasCommittedMutations() {
5187
+ this.documentState = 2 /* HAS_COMMITTED_MUTATIONS */;
5188
+ return this;
5189
+ }
5190
+ setHasLocalMutations() {
5191
+ this.documentState = 1 /* HAS_LOCAL_MUTATIONS */;
5192
+ this.version = SnapshotVersion.min();
5193
+ return this;
5194
+ }
5195
+ setReadTime(readTime) {
5008
5196
  this.readTime = readTime;
5009
- this.documentKey = documentKey;
5010
- this.largestBatchId = largestBatchId;
5197
+ return this;
5011
5198
  }
5012
- /** Returns an offset that sorts before all regular offsets. */
5013
- static min() {
5014
- return new IndexOffset(SnapshotVersion.min(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
5199
+ get hasLocalMutations() {
5200
+ return this.documentState === 1 /* HAS_LOCAL_MUTATIONS */;
5015
5201
  }
5016
- /** Returns an offset that sorts after all regular offsets. */
5017
- static max() {
5018
- return new IndexOffset(SnapshotVersion.max(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
5202
+ get hasCommittedMutations() {
5203
+ return this.documentState === 2 /* HAS_COMMITTED_MUTATIONS */;
5204
+ }
5205
+ get hasPendingWrites() {
5206
+ return this.hasLocalMutations || this.hasCommittedMutations;
5207
+ }
5208
+ isValidDocument() {
5209
+ return this.documentType !== 0 /* INVALID */;
5210
+ }
5211
+ isFoundDocument() {
5212
+ return this.documentType === 1 /* FOUND_DOCUMENT */;
5213
+ }
5214
+ isNoDocument() {
5215
+ return this.documentType === 2 /* NO_DOCUMENT */;
5216
+ }
5217
+ isUnknownDocument() {
5218
+ return this.documentType === 3 /* UNKNOWN_DOCUMENT */;
5219
+ }
5220
+ isEqual(other) {
5221
+ return (other instanceof MutableDocument &&
5222
+ this.key.isEqual(other.key) &&
5223
+ this.version.isEqual(other.version) &&
5224
+ this.documentType === other.documentType &&
5225
+ this.documentState === other.documentState &&
5226
+ this.data.isEqual(other.data));
5227
+ }
5228
+ mutableCopy() {
5229
+ return new MutableDocument(this.key, this.documentType, this.version, this.readTime, this.data.clone(), this.documentState);
5230
+ }
5231
+ toString() {
5232
+ return (`Document(${this.key}, ${this.version}, ${JSON.stringify(this.data.value)}, ` +
5233
+ `{documentType: ${this.documentType}}), ` +
5234
+ `{documentState: ${this.documentState}})`);
5019
5235
  }
5020
5236
  }
5021
- function indexOffsetComparator(left, right) {
5022
- let cmp = left.readTime.compareTo(right.readTime);
5023
- if (cmp !== 0) {
5024
- return cmp;
5237
+ /**
5238
+ * Compares the value for field `field` in the provided documents. Throws if
5239
+ * the field does not exist in both documents.
5240
+ */
5241
+ function compareDocumentsByField(field, d1, d2) {
5242
+ const v1 = d1.data.field(field);
5243
+ const v2 = d2.data.field(field);
5244
+ if (v1 !== null && v2 !== null) {
5245
+ return valueCompare(v1, v2);
5025
5246
  }
5026
- cmp = DocumentKey.comparator(left.documentKey, right.documentKey);
5027
- if (cmp !== 0) {
5028
- return cmp;
5247
+ else {
5248
+ return fail();
5029
5249
  }
5030
- return primitiveComparator(left.largestBatchId, right.largestBatchId);
5031
5250
  }
5032
5251
 
5033
5252
  /**
@@ -6140,11 +6359,19 @@ function documentMap(...docs) {
6140
6359
  }
6141
6360
  return map;
6142
6361
  }
6362
+ function newOverlayedDocumentMap() {
6363
+ return newDocumentKeyMap();
6364
+ }
6365
+ function convertOverlayedDocumentMapToDocumentMap(collection) {
6366
+ let documents = EMPTY_DOCUMENT_MAP;
6367
+ collection.forEach((k, v) => (documents = documents.insert(k, v.overlayedDocument)));
6368
+ return documents;
6369
+ }
6143
6370
  function newOverlayMap() {
6144
- return new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
6371
+ return newDocumentKeyMap();
6145
6372
  }
6146
6373
  function newMutationMap() {
6147
- return new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
6374
+ return newDocumentKeyMap();
6148
6375
  }
6149
6376
  function newDocumentKeyMap() {
6150
6377
  return new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
@@ -9245,15 +9472,13 @@ class IndexedDbDocumentOverlayCache {
9245
9472
  }
9246
9473
  getOverlays(transaction, keys) {
9247
9474
  const result = newOverlayMap();
9248
- const promises = [];
9249
- keys.forEach(key => {
9250
- promises.push(this.getOverlay(transaction, key).next(overlay => {
9475
+ return PersistencePromise.forEach(keys, (key) => {
9476
+ return this.getOverlay(transaction, key).next(overlay => {
9251
9477
  if (overlay !== null) {
9252
9478
  result.set(key, overlay);
9253
9479
  }
9254
- }));
9255
- });
9256
- return PersistencePromise.waitFor(promises).next(() => result);
9480
+ });
9481
+ }).next(() => result);
9257
9482
  }
9258
9483
  saveOverlays(transaction, largestBatchId, overlays) {
9259
9484
  const promises = [];
@@ -10141,6 +10366,9 @@ class MemoryIndexManager {
10141
10366
  getMinOffset(transaction, target) {
10142
10367
  return PersistencePromise.resolve(IndexOffset.min());
10143
10368
  }
10369
+ getMinOffsetFromCollectionGroup(transaction, collectionGroup) {
10370
+ return PersistencePromise.resolve(IndexOffset.min());
10371
+ }
10144
10372
  updateCollectionGroup(transaction, collectionGroup, offset) {
10145
10373
  // Field indices are not supported with memory persistence.
10146
10374
  return PersistencePromise.resolve();
@@ -10276,7 +10504,16 @@ class IndexedDbIndexManager {
10276
10504
  const indexes = indexConfigurationStore(transaction);
10277
10505
  const dbIndex = toDbIndexConfiguration(index);
10278
10506
  delete dbIndex.indexId; // `indexId` is auto-populated by IndexedDb
10279
- return indexes.add(dbIndex).next();
10507
+ const result = indexes.add(dbIndex);
10508
+ if (index.indexState) {
10509
+ const states = indexStateStore(transaction);
10510
+ return result.next(indexId => {
10511
+ states.put(toDbIndexState(indexId, this.user, index.indexState.sequenceNumber, index.indexState.offset));
10512
+ });
10513
+ }
10514
+ else {
10515
+ return result.next();
10516
+ }
10280
10517
  }
10281
10518
  deleteFieldIndex(transaction, index) {
10282
10519
  const indexes = indexConfigurationStore(transaction);
@@ -10713,19 +10950,11 @@ class IndexedDbIndexManager {
10713
10950
  }
10714
10951
  return ranges;
10715
10952
  }
10953
+ getMinOffsetFromCollectionGroup(transaction, collectionGroup) {
10954
+ return this.getFieldIndexes(transaction, collectionGroup).next(getMinOffsetFromFieldIndexes);
10955
+ }
10716
10956
  getMinOffset(transaction, target) {
10717
- let offset;
10718
- return PersistencePromise.forEach(this.getSubTargets(target), (target) => {
10719
- return this.getFieldIndex(transaction, target).next(index => {
10720
- if (!index) {
10721
- offset = IndexOffset.min();
10722
- }
10723
- else if (!offset ||
10724
- indexOffsetComparator(index.indexState.offset, offset) < 0) {
10725
- offset = index.indexState.offset;
10726
- }
10727
- });
10728
- }).next(() => offset);
10957
+ return PersistencePromise.mapArray(this.getSubTargets(target), (subTarget) => this.getFieldIndex(transaction, subTarget).next(index => index ? index : fail())).next(getMinOffsetFromFieldIndexes);
10729
10958
  }
10730
10959
  }
10731
10960
  /**
@@ -10752,6 +10981,21 @@ function indexConfigurationStore(txn) {
10752
10981
  */
10753
10982
  function indexStateStore(txn) {
10754
10983
  return getStore(txn, DbIndexStateStore);
10984
+ }
10985
+ function getMinOffsetFromFieldIndexes(fieldIndexes) {
10986
+ hardAssert(fieldIndexes.length !== 0);
10987
+ let minOffset = fieldIndexes[0].indexState.offset;
10988
+ let maxBatchId = minOffset.largestBatchId;
10989
+ for (let i = 1; i < fieldIndexes.length; i++) {
10990
+ const newOffset = fieldIndexes[i].indexState.offset;
10991
+ if (indexOffsetComparator(newOffset, minOffset) < 0) {
10992
+ minOffset = newOffset;
10993
+ }
10994
+ if (maxBatchId < newOffset.largestBatchId) {
10995
+ maxBatchId = newOffset.largestBatchId;
10996
+ }
10997
+ }
10998
+ return new IndexOffset(minOffset.readTime, minOffset.documentKey, maxBatchId);
10755
10999
  }
10756
11000
 
10757
11001
  /**
@@ -11538,42 +11782,6 @@ function documentTargetStore(txn) {
11538
11782
  return getStore(txn, DbTargetDocumentStore);
11539
11783
  }
11540
11784
 
11541
- /**
11542
- * @license
11543
- * Copyright 2017 Google LLC
11544
- *
11545
- * Licensed under the Apache License, Version 2.0 (the "License");
11546
- * you may not use this file except in compliance with the License.
11547
- * You may obtain a copy of the License at
11548
- *
11549
- * http://www.apache.org/licenses/LICENSE-2.0
11550
- *
11551
- * Unless required by applicable law or agreed to in writing, software
11552
- * distributed under the License is distributed on an "AS IS" BASIS,
11553
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11554
- * See the License for the specific language governing permissions and
11555
- * limitations under the License.
11556
- */
11557
- /**
11558
- * Verifies the error thrown by a LocalStore operation. If a LocalStore
11559
- * operation fails because the primary lease has been taken by another client,
11560
- * we ignore the error (the persistence layer will immediately call
11561
- * `applyPrimaryLease` to propagate the primary state change). All other errors
11562
- * are re-thrown.
11563
- *
11564
- * @param err - An error returned by a LocalStore operation.
11565
- * @returns A Promise that resolves after we recovered, or the original error.
11566
- */
11567
- async function ignoreIfPrimaryLeaseLoss(err) {
11568
- if (err.code === Code.FAILED_PRECONDITION &&
11569
- err.message === PRIMARY_LEASE_LOST_ERROR_MSG) {
11570
- logDebug('LocalStore', 'Unexpectedly lost primary lease');
11571
- }
11572
- else {
11573
- throw err;
11574
- }
11575
- }
11576
-
11577
11785
  /**
11578
11786
  * @license
11579
11787
  * Copyright 2018 Google LLC
@@ -11695,16 +11903,16 @@ class RollingSequenceNumberBuffer {
11695
11903
  * whether or not GC is enabled, as well as which delay to use before the next run.
11696
11904
  */
11697
11905
  class LruScheduler {
11698
- constructor(garbageCollector, asyncQueue) {
11906
+ constructor(garbageCollector, asyncQueue, localStore) {
11699
11907
  this.garbageCollector = garbageCollector;
11700
11908
  this.asyncQueue = asyncQueue;
11701
- this.hasRun = false;
11909
+ this.localStore = localStore;
11702
11910
  this.gcTask = null;
11703
11911
  }
11704
- start(localStore) {
11912
+ start() {
11705
11913
  if (this.garbageCollector.params.cacheSizeCollectionThreshold !==
11706
11914
  LRU_COLLECTION_DISABLED) {
11707
- this.scheduleGC(localStore);
11915
+ this.scheduleGC(INITIAL_GC_DELAY_MS);
11708
11916
  }
11709
11917
  }
11710
11918
  stop() {
@@ -11716,14 +11924,12 @@ class LruScheduler {
11716
11924
  get started() {
11717
11925
  return this.gcTask !== null;
11718
11926
  }
11719
- scheduleGC(localStore) {
11720
- const delay = this.hasRun ? REGULAR_GC_DELAY_MS : INITIAL_GC_DELAY_MS;
11721
- logDebug('LruGarbageCollector', `Garbage collection scheduled in ${delay}ms`);
11927
+ scheduleGC(delay) {
11928
+ logDebug(LOG_TAG$e, `Garbage collection scheduled in ${delay}ms`);
11722
11929
  this.gcTask = this.asyncQueue.enqueueAfterDelay("lru_garbage_collection" /* LruGarbageCollection */, delay, async () => {
11723
11930
  this.gcTask = null;
11724
- this.hasRun = true;
11725
11931
  try {
11726
- await localStore.collectGarbage(this.garbageCollector);
11932
+ await this.localStore.collectGarbage(this.garbageCollector);
11727
11933
  }
11728
11934
  catch (e) {
11729
11935
  if (isIndexedDbTransactionError(e)) {
@@ -11733,7 +11939,7 @@ class LruScheduler {
11733
11939
  await ignoreIfPrimaryLeaseLoss(e);
11734
11940
  }
11735
11941
  }
11736
- await this.scheduleGC(localStore);
11942
+ await this.scheduleGC(REGULAR_GC_DELAY_MS);
11737
11943
  });
11738
11944
  }
11739
11945
  }
@@ -12502,31 +12708,6 @@ function dbKeyComparator(l, r) {
12502
12708
  }
12503
12709
  return primitiveComparator(left[left.length - 1], right[right.length - 1]);
12504
12710
  }
12505
- /**
12506
- * Schema Version for the Web client:
12507
- * 1. Initial version including Mutation Queue, Query Cache, and Remote
12508
- * Document Cache
12509
- * 2. Used to ensure a targetGlobal object exists and add targetCount to it. No
12510
- * longer required because migration 3 unconditionally clears it.
12511
- * 3. Dropped and re-created Query Cache to deal with cache corruption related
12512
- * to limbo resolution. Addresses
12513
- * https://github.com/firebase/firebase-ios-sdk/issues/1548
12514
- * 4. Multi-Tab Support.
12515
- * 5. Removal of held write acks.
12516
- * 6. Create document global for tracking document cache size.
12517
- * 7. Ensure every cached document has a sentinel row with a sequence number.
12518
- * 8. Add collection-parent index for Collection Group queries.
12519
- * 9. Change RemoteDocumentChanges store to be keyed by readTime rather than
12520
- * an auto-incrementing ID. This is required for Index-Free queries.
12521
- * 10. Rewrite the canonical IDs to the explicit Protobuf-based format.
12522
- * 11. Add bundles and named_queries for bundle support.
12523
- * 12. Add document overlays.
12524
- * 13. Rewrite the keys of the remote document cache to allow for efficient
12525
- * document lookup via `getAll()`.
12526
- * 14. Add overlays.
12527
- * 15. Add indexing support.
12528
- */
12529
- const SCHEMA_VERSION = 14;
12530
12711
 
12531
12712
  /**
12532
12713
  * @license
@@ -12657,10 +12838,10 @@ class LocalDocumentsView {
12657
12838
  * if the map does not already contain an entry for the given document key.
12658
12839
  */
12659
12840
  populateOverlays(transaction, overlays, docs) {
12660
- let missingOverlays = documentKeySet();
12841
+ const missingOverlays = [];
12661
12842
  docs.forEach(key => {
12662
12843
  if (!overlays.has(key)) {
12663
- missingOverlays = missingOverlays.add(key);
12844
+ missingOverlays.push(key);
12664
12845
  }
12665
12846
  });
12666
12847
  return this.documentOverlayCache
@@ -12686,7 +12867,7 @@ class LocalDocumentsView {
12686
12867
  computeViews(transaction, docs, overlays, existenceStateChanged) {
12687
12868
  let recalculateDocuments = mutableDocumentMap();
12688
12869
  const mutatedFields = newDocumentKeyMap();
12689
- const results = newDocumentKeyMap();
12870
+ const results = newOverlayedDocumentMap();
12690
12871
  docs.forEach((_, doc) => {
12691
12872
  const overlay = overlays.get(doc.key);
12692
12873
  // Recalculate an overlay if the document's existence state changed due to
@@ -12788,6 +12969,55 @@ class LocalDocumentsView {
12788
12969
  return this.getDocumentsMatchingCollectionQuery(transaction, query, offset);
12789
12970
  }
12790
12971
  }
12972
+ /**
12973
+ * Given a collection group, returns the next documents that follow the provided offset, along
12974
+ * with an updated batch ID.
12975
+ *
12976
+ * <p>The documents returned by this method are ordered by remote version from the provided
12977
+ * offset. If there are no more remote documents after the provided offset, documents with
12978
+ * mutations in order of batch id from the offset are returned. Since all documents in a batch are
12979
+ * returned together, the total number of documents returned can exceed {@code count}.
12980
+ *
12981
+ * @param transaction
12982
+ * @param collectionGroup The collection group for the documents.
12983
+ * @param offset The offset to index into.
12984
+ * @param count The number of documents to return
12985
+ * @return A LocalWriteResult with the documents that follow the provided offset and the last processed batch id.
12986
+ */
12987
+ getNextDocuments(transaction, collectionGroup, offset, count) {
12988
+ return this.remoteDocumentCache
12989
+ .getAllFromCollectionGroup(transaction, collectionGroup, offset, count)
12990
+ .next((originalDocs) => {
12991
+ const overlaysPromise = count - originalDocs.size > 0
12992
+ ? this.documentOverlayCache.getOverlaysForCollectionGroup(transaction, collectionGroup, offset.largestBatchId, count - originalDocs.size)
12993
+ : PersistencePromise.resolve(newOverlayMap());
12994
+ // The callsite will use the largest batch ID together with the latest read time to create
12995
+ // a new index offset. Since we only process batch IDs if all remote documents have been read,
12996
+ // no overlay will increase the overall read time. This is why we only need to special case
12997
+ // the batch id.
12998
+ let largestBatchId = INITIAL_LARGEST_BATCH_ID;
12999
+ let modifiedDocs = originalDocs;
13000
+ return overlaysPromise.next(overlays => {
13001
+ return PersistencePromise.forEach(overlays, (key, overlay) => {
13002
+ if (largestBatchId < overlay.largestBatchId) {
13003
+ largestBatchId = overlay.largestBatchId;
13004
+ }
13005
+ if (originalDocs.get(key)) {
13006
+ return PersistencePromise.resolve();
13007
+ }
13008
+ return this.getBaseDocument(transaction, key, overlay).next(doc => {
13009
+ modifiedDocs = modifiedDocs.insert(key, doc);
13010
+ });
13011
+ })
13012
+ .next(() => this.populateOverlays(transaction, overlays, originalDocs))
13013
+ .next(() => this.computeViews(transaction, modifiedDocs, overlays, documentKeySet()))
13014
+ .next(localDocs => ({
13015
+ batchId: largestBatchId,
13016
+ changes: convertOverlayedDocumentMapToDocumentMap(localDocs)
13017
+ }));
13018
+ });
13019
+ });
13020
+ }
12791
13021
  getDocumentsMatchingDocumentQuery(transaction, docPath) {
12792
13022
  // Just do a simple document lookup.
12793
13023
  return this.getDocument(transaction, new DocumentKey(docPath)).next(document => {
@@ -12851,7 +13081,7 @@ class LocalDocumentsView {
12851
13081
  }
12852
13082
  /** Returns a base document that can be used to apply `overlay`. */
12853
13083
  getBaseDocument(transaction, key, overlay) {
12854
- return overlay === null || overlay.mutation instanceof PatchMutation
13084
+ return overlay === null || overlay.mutation.type === 1 /* Patch */
12855
13085
  ? this.remoteDocumentCache.getEntry(transaction, key)
12856
13086
  : PersistencePromise.resolve(MutableDocument.newInvalidDocument(key));
12857
13087
  }
@@ -12926,15 +13156,13 @@ class MemoryDocumentOverlayCache {
12926
13156
  }
12927
13157
  getOverlays(transaction, keys) {
12928
13158
  const result = newOverlayMap();
12929
- const promises = [];
12930
- keys.forEach(key => {
12931
- promises.push(this.getOverlay(transaction, key).next(overlay => {
13159
+ return PersistencePromise.forEach(keys, (key) => {
13160
+ return this.getOverlay(transaction, key).next(overlay => {
12932
13161
  if (overlay !== null) {
12933
13162
  result.set(key, overlay);
12934
13163
  }
12935
- }));
12936
- });
12937
- return PersistencePromise.waitFor(promises).next(() => result);
13164
+ });
13165
+ }).next(() => result);
12938
13166
  }
12939
13167
  saveOverlays(transaction, largestBatchId, overlays) {
12940
13168
  overlays.forEach((_, mutation) => {
@@ -14098,11 +14326,8 @@ class SchemaConverter {
14098
14326
  const mutationsStore = transaction.store(DbMutationBatchStore);
14099
14327
  const remoteDocumentCache = newIndexedDbRemoteDocumentCache(this.serializer);
14100
14328
  const memoryPersistence = new MemoryPersistence(MemoryEagerDelegate.factory, this.serializer.remoteSerializer);
14101
- const promises = [];
14102
- const userToDocumentSet = new Map();
14103
- return mutationsStore
14104
- .loadAll()
14105
- .next(dbBatches => {
14329
+ return mutationsStore.loadAll().next(dbBatches => {
14330
+ const userToDocumentSet = new Map();
14106
14331
  dbBatches.forEach(dbBatch => {
14107
14332
  var _a;
14108
14333
  let documentSet = (_a = userToDocumentSet.get(dbBatch.userId)) !== null && _a !== void 0 ? _a : documentKeySet();
@@ -14110,9 +14335,7 @@ class SchemaConverter {
14110
14335
  batch.keys().forEach(key => (documentSet = documentSet.add(key)));
14111
14336
  userToDocumentSet.set(dbBatch.userId, documentSet);
14112
14337
  });
14113
- })
14114
- .next(() => {
14115
- userToDocumentSet.forEach((allDocumentKeysForUser, userId) => {
14338
+ return PersistencePromise.forEach(userToDocumentSet, (allDocumentKeysForUser, userId) => {
14116
14339
  const user = new User(userId);
14117
14340
  const documentOverlayCache = IndexedDbDocumentOverlayCache.forUser(this.serializer, user);
14118
14341
  // NOTE: The index manager and the reference delegate are
@@ -14122,10 +14345,11 @@ class SchemaConverter {
14122
14345
  const indexManager = memoryPersistence.getIndexManager(user);
14123
14346
  const mutationQueue = IndexedDbMutationQueue.forUser(user, this.serializer, indexManager, memoryPersistence.referenceDelegate);
14124
14347
  const localDocumentsView = new LocalDocumentsView(remoteDocumentCache, mutationQueue, documentOverlayCache, indexManager);
14125
- promises.push(localDocumentsView.recalculateAndSaveOverlaysForDocumentKeys(new IndexedDbTransaction(transaction, ListenSequence.INVALID), allDocumentKeysForUser));
14348
+ return localDocumentsView
14349
+ .recalculateAndSaveOverlaysForDocumentKeys(new IndexedDbTransaction(transaction, ListenSequence.INVALID), allDocumentKeysForUser)
14350
+ .next();
14126
14351
  });
14127
- })
14128
- .next(() => PersistencePromise.waitFor(promises));
14352
+ });
14129
14353
  }
14130
14354
  }
14131
14355
  function sentinelKey(path) {
@@ -15190,7 +15414,7 @@ function localStoreWriteLocally(localStore, mutations) {
15190
15414
  // any additional user-provided writes.
15191
15415
  return localStoreImpl.localDocuments.getOverlayedDocuments(txn, remoteDocs);
15192
15416
  })
15193
- .next(docs => {
15417
+ .next((docs) => {
15194
15418
  overlayedDocuments = docs;
15195
15419
  // For non-idempotent mutations (such as `FieldValue.increment()`),
15196
15420
  // we record the base state in a separate patch mutation. This is
@@ -15215,11 +15439,10 @@ function localStoreWriteLocally(localStore, mutations) {
15215
15439
  return localStoreImpl.documentOverlayCache.saveOverlays(txn, batch.batchId, overlays);
15216
15440
  });
15217
15441
  })
15218
- .then(() => {
15219
- let documents = documentMap();
15220
- overlayedDocuments.forEach((key, val) => (documents = documents.insert(key, val.overlayedDocument)));
15221
- return { batchId: mutationBatch.batchId, changes: documents };
15222
- });
15442
+ .then(() => ({
15443
+ batchId: mutationBatch.batchId,
15444
+ changes: convertOverlayedDocumentMapToDocumentMap(overlayedDocuments)
15445
+ }));
15223
15446
  }
15224
15447
  /**
15225
15448
  * Acknowledges the given batch.
@@ -23603,10 +23826,14 @@ class MemoryOfflineComponentProvider {
23603
23826
  this.sharedClientState = this.createSharedClientState(cfg);
23604
23827
  this.persistence = this.createPersistence(cfg);
23605
23828
  await this.persistence.start();
23606
- this.gcScheduler = this.createGarbageCollectionScheduler(cfg);
23607
23829
  this.localStore = this.createLocalStore(cfg);
23830
+ this.gcScheduler = this.createGarbageCollectionScheduler(cfg, this.localStore);
23831
+ this.indexBackfillerScheduler = this.createIndexBackfillerScheduler(cfg, this.localStore);
23608
23832
  }
23609
- createGarbageCollectionScheduler(cfg) {
23833
+ createGarbageCollectionScheduler(cfg, localStore) {
23834
+ return null;
23835
+ }
23836
+ createIndexBackfillerScheduler(cfg, localStore) {
23610
23837
  return null;
23611
23838
  }
23612
23839
  createLocalStore(cfg) {
@@ -23647,7 +23874,11 @@ class IndexedDbOfflineComponentProvider extends MemoryOfflineComponentProvider {
23647
23874
  // set it after localStore / remoteStore are started.
23648
23875
  await this.persistence.setPrimaryStateListener(() => {
23649
23876
  if (this.gcScheduler && !this.gcScheduler.started) {
23650
- this.gcScheduler.start(this.localStore);
23877
+ this.gcScheduler.start();
23878
+ }
23879
+ if (this.indexBackfillerScheduler &&
23880
+ !this.indexBackfillerScheduler.started) {
23881
+ this.indexBackfillerScheduler.start();
23651
23882
  }
23652
23883
  return Promise.resolve();
23653
23884
  });
@@ -23655,9 +23886,13 @@ class IndexedDbOfflineComponentProvider extends MemoryOfflineComponentProvider {
23655
23886
  createLocalStore(cfg) {
23656
23887
  return newLocalStore(this.persistence, new QueryEngine(), cfg.initialUser, this.serializer);
23657
23888
  }
23658
- createGarbageCollectionScheduler(cfg) {
23889
+ createGarbageCollectionScheduler(cfg, localStore) {
23659
23890
  const garbageCollector = this.persistence.referenceDelegate.garbageCollector;
23660
- return new LruScheduler(garbageCollector, cfg.asyncQueue);
23891
+ return new LruScheduler(garbageCollector, cfg.asyncQueue, localStore);
23892
+ }
23893
+ createIndexBackfillerScheduler(cfg, localStore) {
23894
+ const indexBackfiller = new IndexBackfiller(localStore, this.persistence);
23895
+ return new IndexBackfillerScheduler(cfg.asyncQueue, indexBackfiller);
23661
23896
  }
23662
23897
  createPersistence(cfg) {
23663
23898
  const persistenceKey = indexedDbStoragePrefix(cfg.databaseInfo.databaseId, cfg.databaseInfo.persistenceKey);
@@ -23704,12 +23939,20 @@ class MultiTabOfflineComponentProvider extends IndexedDbOfflineComponentProvider
23704
23939
  await syncEngineApplyPrimaryState(this.onlineComponentProvider.syncEngine, isPrimary);
23705
23940
  if (this.gcScheduler) {
23706
23941
  if (isPrimary && !this.gcScheduler.started) {
23707
- this.gcScheduler.start(this.localStore);
23942
+ this.gcScheduler.start();
23708
23943
  }
23709
23944
  else if (!isPrimary) {
23710
23945
  this.gcScheduler.stop();
23711
23946
  }
23712
23947
  }
23948
+ if (this.indexBackfillerScheduler) {
23949
+ if (isPrimary && !this.indexBackfillerScheduler.started) {
23950
+ this.indexBackfillerScheduler.start();
23951
+ }
23952
+ else if (!isPrimary) {
23953
+ this.indexBackfillerScheduler.stop();
23954
+ }
23955
+ }
23713
23956
  });
23714
23957
  }
23715
23958
  createSharedClientState(cfg) {