@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.
- package/CHANGELOG.md +14 -0
- package/dist/firestore/src/core/component_provider.d.ts +11 -6
- package/dist/firestore/src/local/document_overlay_cache.d.ts +1 -1
- package/dist/firestore/src/local/index_backfiller.d.ts +44 -0
- package/dist/firestore/src/local/index_manager.d.ts +2 -0
- package/dist/firestore/src/local/indexeddb_document_overlay_cache.d.ts +1 -1
- package/dist/firestore/src/local/indexeddb_index_manager.d.ts +1 -0
- package/dist/firestore/src/local/local_documents_view.d.ts +20 -4
- package/dist/firestore/src/local/local_store.d.ts +9 -0
- package/dist/firestore/src/local/lru_garbage_collector_impl.d.ts +5 -5
- package/dist/firestore/src/local/memory_document_overlay_cache.d.ts +1 -1
- package/dist/firestore/src/local/memory_index_manager.d.ts +1 -0
- package/dist/firestore/src/local/persistence.d.ts +3 -5
- package/dist/firestore/src/local/persistence_promise.d.ts +11 -0
- package/dist/firestore/src/model/collections.d.ts +6 -2
- package/dist/firestore/src/model/mutation_batch.d.ts +2 -3
- package/dist/firestore/src/util/async_queue.d.ts +5 -1
- package/dist/firestore/test/unit/local/index_backfiller.test.d.ts +17 -0
- package/dist/firestore/test/unit/local/test_document_overlay_cache.d.ts +1 -1
- package/dist/firestore/test/unit/specs/spec_test_components.d.ts +5 -3
- package/dist/firestore/test/util/helpers.d.ts +1 -0
- package/dist/index.esm2017.js +7499 -7351
- package/dist/index.esm2017.js.map +1 -1
- package/dist/index.esm5.js +8227 -7997
- package/dist/index.esm5.js.map +1 -1
- package/dist/index.node.cjs.js +1552 -1309
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/index.node.mjs +1552 -1309
- package/dist/index.node.mjs.map +1 -1
- package/dist/index.rn.js +7491 -7342
- package/dist/index.rn.js.map +1 -1
- package/dist/internal.d.ts +146 -16
- package/dist/lite/firestore/src/core/component_provider.d.ts +11 -6
- package/dist/lite/firestore/src/local/document_overlay_cache.d.ts +1 -1
- package/dist/lite/firestore/src/local/index_backfiller.d.ts +44 -0
- package/dist/lite/firestore/src/local/index_manager.d.ts +2 -0
- package/dist/lite/firestore/src/local/indexeddb_document_overlay_cache.d.ts +1 -1
- package/dist/lite/firestore/src/local/indexeddb_index_manager.d.ts +1 -0
- package/dist/lite/firestore/src/local/local_documents_view.d.ts +20 -4
- package/dist/lite/firestore/src/local/local_store.d.ts +9 -0
- package/dist/lite/firestore/src/local/lru_garbage_collector_impl.d.ts +5 -5
- package/dist/lite/firestore/src/local/memory_document_overlay_cache.d.ts +1 -1
- package/dist/lite/firestore/src/local/memory_index_manager.d.ts +1 -0
- package/dist/lite/firestore/src/local/persistence.d.ts +3 -5
- package/dist/lite/firestore/src/local/persistence_promise.d.ts +11 -0
- package/dist/lite/firestore/src/model/collections.d.ts +6 -2
- package/dist/lite/firestore/src/model/mutation_batch.d.ts +2 -3
- package/dist/lite/firestore/src/util/async_queue.d.ts +5 -1
- package/dist/lite/firestore/test/unit/local/index_backfiller.test.d.ts +17 -0
- package/dist/lite/firestore/test/unit/local/test_document_overlay_cache.d.ts +1 -1
- package/dist/lite/firestore/test/unit/specs/spec_test_components.d.ts +5 -3
- package/dist/lite/firestore/test/util/helpers.d.ts +1 -0
- package/dist/lite/index.browser.esm2017.js +2 -2
- package/dist/lite/index.browser.esm2017.js.map +1 -1
- package/dist/lite/index.browser.esm5.js +2 -2
- package/dist/lite/index.browser.esm5.js.map +1 -1
- package/dist/lite/index.node.cjs.js +2 -2
- package/dist/lite/index.node.cjs.js.map +1 -1
- package/dist/lite/index.node.mjs +2 -2
- package/dist/lite/index.node.mjs.map +1 -1
- package/dist/lite/index.rn.esm2017.js +2 -2
- package/dist/lite/index.rn.esm2017.js.map +1 -1
- package/dist/lite/internal.d.ts +5 -1
- package/dist/lite/packages/firestore/src/core/component_provider.d.ts +11 -6
- package/dist/lite/packages/firestore/src/local/document_overlay_cache.d.ts +1 -1
- package/dist/lite/packages/firestore/src/local/index_backfiller.d.ts +44 -0
- package/dist/lite/packages/firestore/src/local/index_manager.d.ts +2 -0
- package/dist/lite/packages/firestore/src/local/indexeddb_document_overlay_cache.d.ts +1 -1
- package/dist/lite/packages/firestore/src/local/indexeddb_index_manager.d.ts +1 -0
- package/dist/lite/packages/firestore/src/local/local_documents_view.d.ts +20 -4
- package/dist/lite/packages/firestore/src/local/local_store.d.ts +9 -0
- package/dist/lite/packages/firestore/src/local/lru_garbage_collector_impl.d.ts +5 -5
- package/dist/lite/packages/firestore/src/local/memory_document_overlay_cache.d.ts +1 -1
- package/dist/lite/packages/firestore/src/local/memory_index_manager.d.ts +1 -0
- package/dist/lite/packages/firestore/src/local/persistence.d.ts +3 -5
- package/dist/lite/packages/firestore/src/local/persistence_promise.d.ts +11 -0
- package/dist/lite/packages/firestore/src/model/collections.d.ts +6 -2
- package/dist/lite/packages/firestore/src/model/mutation_batch.d.ts +2 -3
- package/dist/lite/packages/firestore/src/util/async_queue.d.ts +5 -1
- package/dist/lite/packages/firestore/test/unit/local/index_backfiller.test.d.ts +17 -0
- package/dist/lite/packages/firestore/test/unit/local/test_document_overlay_cache.d.ts +1 -1
- package/dist/lite/packages/firestore/test/unit/specs/spec_test_components.d.ts +5 -3
- package/dist/lite/packages/firestore/test/util/helpers.d.ts +1 -0
- package/dist/lite/private.d.ts +5 -1
- package/dist/packages/firestore/dist/index.esm2017.d.ts +200 -200
- package/dist/packages/firestore/src/core/component_provider.d.ts +11 -6
- package/dist/packages/firestore/src/local/document_overlay_cache.d.ts +1 -1
- package/dist/packages/firestore/src/local/index_backfiller.d.ts +44 -0
- package/dist/packages/firestore/src/local/index_manager.d.ts +2 -0
- package/dist/packages/firestore/src/local/indexeddb_document_overlay_cache.d.ts +1 -1
- package/dist/packages/firestore/src/local/indexeddb_index_manager.d.ts +1 -0
- package/dist/packages/firestore/src/local/local_documents_view.d.ts +20 -4
- package/dist/packages/firestore/src/local/local_store.d.ts +9 -0
- package/dist/packages/firestore/src/local/lru_garbage_collector_impl.d.ts +5 -5
- package/dist/packages/firestore/src/local/memory_document_overlay_cache.d.ts +1 -1
- package/dist/packages/firestore/src/local/memory_index_manager.d.ts +1 -0
- package/dist/packages/firestore/src/local/persistence.d.ts +3 -5
- package/dist/packages/firestore/src/local/persistence_promise.d.ts +11 -0
- package/dist/packages/firestore/src/model/collections.d.ts +6 -2
- package/dist/packages/firestore/src/model/mutation_batch.d.ts +2 -3
- package/dist/packages/firestore/src/util/async_queue.d.ts +5 -1
- package/dist/packages/firestore/test/unit/local/index_backfiller.test.d.ts +17 -0
- package/dist/packages/firestore/test/unit/local/test_document_overlay_cache.d.ts +1 -1
- package/dist/packages/firestore/test/unit/specs/spec_test_components.d.ts +5 -3
- package/dist/packages/firestore/test/util/helpers.d.ts +1 -0
- package/dist/private.d.ts +146 -16
- package/package.json +9 -9
package/dist/index.node.cjs.js
CHANGED
|
@@ -33,7 +33,7 @@ var grpc__namespace = /*#__PURE__*/_interopNamespace(grpc);
|
|
|
33
33
|
var protoLoader__namespace = /*#__PURE__*/_interopNamespace(protoLoader);
|
|
34
34
|
|
|
35
35
|
const name = "@firebase/firestore";
|
|
36
|
-
const version$1 = "3.4.
|
|
36
|
+
const version$1 = "3.4.10";
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
39
|
* @license
|
|
@@ -86,7 +86,7 @@ User.GOOGLE_CREDENTIALS = new User('google-credentials-uid');
|
|
|
86
86
|
User.FIRST_PARTY = new User('first-party-uid');
|
|
87
87
|
User.MOCK_USER = new User('mock-user');
|
|
88
88
|
|
|
89
|
-
const version = "9.8.
|
|
89
|
+
const version = "9.8.3";
|
|
90
90
|
|
|
91
91
|
/**
|
|
92
92
|
* @license
|
|
@@ -793,7 +793,7 @@ function makeAuthCredentialsProvider(credentials) {
|
|
|
793
793
|
|
|
794
794
|
/**
|
|
795
795
|
* @license
|
|
796
|
-
* Copyright
|
|
796
|
+
* Copyright 2020 Google LLC
|
|
797
797
|
*
|
|
798
798
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
799
799
|
* you may not use this file except in compliance with the License.
|
|
@@ -808,32 +808,75 @@ function makeAuthCredentialsProvider(credentials) {
|
|
|
808
808
|
* limitations under the License.
|
|
809
809
|
*/
|
|
810
810
|
/**
|
|
811
|
-
* `
|
|
812
|
-
*
|
|
813
|
-
* `
|
|
814
|
-
* well as write out sequence numbers that it produces via `next()`.
|
|
811
|
+
* Generates `nBytes` of random bytes.
|
|
812
|
+
*
|
|
813
|
+
* If `nBytes < 0` , an error will be thrown.
|
|
815
814
|
*/
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
815
|
+
function randomBytes(nBytes) {
|
|
816
|
+
return crypto.randomBytes(nBytes);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* @license
|
|
821
|
+
* Copyright 2017 Google LLC
|
|
822
|
+
*
|
|
823
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
824
|
+
* you may not use this file except in compliance with the License.
|
|
825
|
+
* You may obtain a copy of the License at
|
|
826
|
+
*
|
|
827
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
828
|
+
*
|
|
829
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
830
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
831
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
832
|
+
* See the License for the specific language governing permissions and
|
|
833
|
+
* limitations under the License.
|
|
834
|
+
*/
|
|
835
|
+
class AutoId {
|
|
836
|
+
static newId() {
|
|
837
|
+
// Alphanumeric characters
|
|
838
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
|
839
|
+
// The largest byte value that is a multiple of `char.length`.
|
|
840
|
+
const maxMultiple = Math.floor(256 / chars.length) * chars.length;
|
|
841
|
+
let autoId = '';
|
|
842
|
+
const targetLength = 20;
|
|
843
|
+
while (autoId.length < targetLength) {
|
|
844
|
+
const bytes = randomBytes(40);
|
|
845
|
+
for (let i = 0; i < bytes.length; ++i) {
|
|
846
|
+
// Only accept values that are [0, maxMultiple), this ensures they can
|
|
847
|
+
// be evenly mapped to indices of `chars` via a modulo operation.
|
|
848
|
+
if (autoId.length < targetLength && bytes[i] < maxMultiple) {
|
|
849
|
+
autoId += chars.charAt(bytes[i] % chars.length);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
822
852
|
}
|
|
853
|
+
return autoId;
|
|
823
854
|
}
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
855
|
+
}
|
|
856
|
+
function primitiveComparator(left, right) {
|
|
857
|
+
if (left < right) {
|
|
858
|
+
return -1;
|
|
827
859
|
}
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
if (this.writeNewSequenceNumber) {
|
|
831
|
-
this.writeNewSequenceNumber(nextValue);
|
|
832
|
-
}
|
|
833
|
-
return nextValue;
|
|
860
|
+
if (left > right) {
|
|
861
|
+
return 1;
|
|
834
862
|
}
|
|
863
|
+
return 0;
|
|
835
864
|
}
|
|
836
|
-
|
|
865
|
+
/** Helper to compare arrays using isEqual(). */
|
|
866
|
+
function arrayEquals(left, right, comparator) {
|
|
867
|
+
if (left.length !== right.length) {
|
|
868
|
+
return false;
|
|
869
|
+
}
|
|
870
|
+
return left.every((value, index) => comparator(value, right[index]));
|
|
871
|
+
}
|
|
872
|
+
/**
|
|
873
|
+
* Returns the immediate lexicographically-following string. This is useful to
|
|
874
|
+
* construct an inclusive range for indexeddb iterators.
|
|
875
|
+
*/
|
|
876
|
+
function immediateSuccessor(s) {
|
|
877
|
+
// Return the input string, with an additional NUL byte appended.
|
|
878
|
+
return s + '\0';
|
|
879
|
+
}
|
|
837
880
|
|
|
838
881
|
/**
|
|
839
882
|
* @license
|
|
@@ -851,75 +894,298 @@ ListenSequence.INVALID = -1;
|
|
|
851
894
|
* See the License for the specific language governing permissions and
|
|
852
895
|
* limitations under the License.
|
|
853
896
|
*/
|
|
854
|
-
|
|
897
|
+
// The earliest date supported by Firestore timestamps (0001-01-01T00:00:00Z).
|
|
898
|
+
const MIN_SECONDS = -62135596800;
|
|
899
|
+
// Number of nanoseconds in a millisecond.
|
|
900
|
+
const MS_TO_NANOS = 1e6;
|
|
855
901
|
/**
|
|
856
|
-
*
|
|
902
|
+
* A `Timestamp` represents a point in time independent of any time zone or
|
|
903
|
+
* calendar, represented as seconds and fractions of seconds at nanosecond
|
|
904
|
+
* resolution in UTC Epoch time.
|
|
905
|
+
*
|
|
906
|
+
* It is encoded using the Proleptic Gregorian Calendar which extends the
|
|
907
|
+
* Gregorian calendar backwards to year one. It is encoded assuming all minutes
|
|
908
|
+
* are 60 seconds long, i.e. leap seconds are "smeared" so that no leap second
|
|
909
|
+
* table is needed for interpretation. Range is from 0001-01-01T00:00:00Z to
|
|
910
|
+
* 9999-12-31T23:59:59.999999999Z.
|
|
911
|
+
*
|
|
912
|
+
* For examples and further specifications, refer to the
|
|
913
|
+
* {@link https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto | Timestamp definition}.
|
|
857
914
|
*/
|
|
858
|
-
class
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
915
|
+
class Timestamp {
|
|
916
|
+
/**
|
|
917
|
+
* Creates a new timestamp.
|
|
918
|
+
*
|
|
919
|
+
* @param seconds - The number of seconds of UTC time since Unix epoch
|
|
920
|
+
* 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
|
|
921
|
+
* 9999-12-31T23:59:59Z inclusive.
|
|
922
|
+
* @param nanoseconds - The non-negative fractions of a second at nanosecond
|
|
923
|
+
* resolution. Negative second values with fractions must still have
|
|
924
|
+
* non-negative nanoseconds values that count forward in time. Must be
|
|
925
|
+
* from 0 to 999,999,999 inclusive.
|
|
926
|
+
*/
|
|
927
|
+
constructor(
|
|
928
|
+
/**
|
|
929
|
+
* The number of seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z.
|
|
930
|
+
*/
|
|
931
|
+
seconds,
|
|
932
|
+
/**
|
|
933
|
+
* The fractions of a second at nanosecond resolution.*
|
|
934
|
+
*/
|
|
935
|
+
nanoseconds) {
|
|
936
|
+
this.seconds = seconds;
|
|
937
|
+
this.nanoseconds = nanoseconds;
|
|
938
|
+
if (nanoseconds < 0) {
|
|
939
|
+
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp nanoseconds out of range: ' + nanoseconds);
|
|
862
940
|
}
|
|
863
|
-
|
|
864
|
-
|
|
941
|
+
if (nanoseconds >= 1e9) {
|
|
942
|
+
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp nanoseconds out of range: ' + nanoseconds);
|
|
865
943
|
}
|
|
866
|
-
if (
|
|
867
|
-
|
|
944
|
+
if (seconds < MIN_SECONDS) {
|
|
945
|
+
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp seconds out of range: ' + seconds);
|
|
868
946
|
}
|
|
869
|
-
|
|
870
|
-
|
|
947
|
+
// This will break in the year 10,000.
|
|
948
|
+
if (seconds >= 253402300800) {
|
|
949
|
+
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp seconds out of range: ' + seconds);
|
|
871
950
|
}
|
|
872
|
-
this.segments = segments;
|
|
873
|
-
this.offset = offset;
|
|
874
|
-
this.len = length;
|
|
875
951
|
}
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
952
|
+
/**
|
|
953
|
+
* Creates a new timestamp with the current date, with millisecond precision.
|
|
954
|
+
*
|
|
955
|
+
* @returns a new timestamp representing the current date.
|
|
956
|
+
*/
|
|
957
|
+
static now() {
|
|
958
|
+
return Timestamp.fromMillis(Date.now());
|
|
881
959
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
}
|
|
892
|
-
return this.construct(segments);
|
|
960
|
+
/**
|
|
961
|
+
* Creates a new timestamp from the given date.
|
|
962
|
+
*
|
|
963
|
+
* @param date - The date to initialize the `Timestamp` from.
|
|
964
|
+
* @returns A new `Timestamp` representing the same point in time as the given
|
|
965
|
+
* date.
|
|
966
|
+
*/
|
|
967
|
+
static fromDate(date) {
|
|
968
|
+
return Timestamp.fromMillis(date.getTime());
|
|
893
969
|
}
|
|
894
|
-
/**
|
|
895
|
-
|
|
896
|
-
|
|
970
|
+
/**
|
|
971
|
+
* Creates a new timestamp from the given number of milliseconds.
|
|
972
|
+
*
|
|
973
|
+
* @param milliseconds - Number of milliseconds since Unix epoch
|
|
974
|
+
* 1970-01-01T00:00:00Z.
|
|
975
|
+
* @returns A new `Timestamp` representing the same point in time as the given
|
|
976
|
+
* number of milliseconds.
|
|
977
|
+
*/
|
|
978
|
+
static fromMillis(milliseconds) {
|
|
979
|
+
const seconds = Math.floor(milliseconds / 1000);
|
|
980
|
+
const nanos = Math.floor((milliseconds - seconds * 1000) * MS_TO_NANOS);
|
|
981
|
+
return new Timestamp(seconds, nanos);
|
|
897
982
|
}
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
983
|
+
/**
|
|
984
|
+
* Converts a `Timestamp` to a JavaScript `Date` object. This conversion
|
|
985
|
+
* causes a loss of precision since `Date` objects only support millisecond
|
|
986
|
+
* precision.
|
|
987
|
+
*
|
|
988
|
+
* @returns JavaScript `Date` object representing the same point in time as
|
|
989
|
+
* this `Timestamp`, with millisecond precision.
|
|
990
|
+
*/
|
|
991
|
+
toDate() {
|
|
992
|
+
return new Date(this.toMillis());
|
|
901
993
|
}
|
|
902
|
-
|
|
903
|
-
|
|
994
|
+
/**
|
|
995
|
+
* Converts a `Timestamp` to a numeric timestamp (in milliseconds since
|
|
996
|
+
* epoch). This operation causes a loss of precision.
|
|
997
|
+
*
|
|
998
|
+
* @returns The point in time corresponding to this timestamp, represented as
|
|
999
|
+
* the number of milliseconds since Unix epoch 1970-01-01T00:00:00Z.
|
|
1000
|
+
*/
|
|
1001
|
+
toMillis() {
|
|
1002
|
+
return this.seconds * 1000 + this.nanoseconds / MS_TO_NANOS;
|
|
904
1003
|
}
|
|
905
|
-
|
|
906
|
-
|
|
1004
|
+
_compareTo(other) {
|
|
1005
|
+
if (this.seconds === other.seconds) {
|
|
1006
|
+
return primitiveComparator(this.nanoseconds, other.nanoseconds);
|
|
1007
|
+
}
|
|
1008
|
+
return primitiveComparator(this.seconds, other.seconds);
|
|
907
1009
|
}
|
|
908
|
-
|
|
909
|
-
|
|
1010
|
+
/**
|
|
1011
|
+
* Returns true if this `Timestamp` is equal to the provided one.
|
|
1012
|
+
*
|
|
1013
|
+
* @param other - The `Timestamp` to compare against.
|
|
1014
|
+
* @returns true if this `Timestamp` is equal to the provided one.
|
|
1015
|
+
*/
|
|
1016
|
+
isEqual(other) {
|
|
1017
|
+
return (other.seconds === this.seconds && other.nanoseconds === this.nanoseconds);
|
|
910
1018
|
}
|
|
911
|
-
|
|
912
|
-
|
|
1019
|
+
/** Returns a textual representation of this `Timestamp`. */
|
|
1020
|
+
toString() {
|
|
1021
|
+
return ('Timestamp(seconds=' +
|
|
1022
|
+
this.seconds +
|
|
1023
|
+
', nanoseconds=' +
|
|
1024
|
+
this.nanoseconds +
|
|
1025
|
+
')');
|
|
913
1026
|
}
|
|
914
|
-
|
|
915
|
-
|
|
1027
|
+
/** Returns a JSON-serializable representation of this `Timestamp`. */
|
|
1028
|
+
toJSON() {
|
|
1029
|
+
return { seconds: this.seconds, nanoseconds: this.nanoseconds };
|
|
916
1030
|
}
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
1031
|
+
/**
|
|
1032
|
+
* Converts this object to a primitive string, which allows `Timestamp` objects
|
|
1033
|
+
* to be compared using the `>`, `<=`, `>=` and `>` operators.
|
|
1034
|
+
*/
|
|
1035
|
+
valueOf() {
|
|
1036
|
+
// This method returns a string of the form <seconds>.<nanoseconds> where
|
|
1037
|
+
// <seconds> is translated to have a non-negative value and both <seconds>
|
|
1038
|
+
// and <nanoseconds> are left-padded with zeroes to be a consistent length.
|
|
1039
|
+
// Strings with this format then have a lexiographical ordering that matches
|
|
1040
|
+
// the expected ordering. The <seconds> translation is done to avoid having
|
|
1041
|
+
// a leading negative sign (i.e. a leading '-' character) in its string
|
|
1042
|
+
// representation, which would affect its lexiographical ordering.
|
|
1043
|
+
const adjustedSeconds = this.seconds - MIN_SECONDS;
|
|
1044
|
+
// Note: Up to 12 decimal digits are required to represent all valid
|
|
1045
|
+
// 'seconds' values.
|
|
1046
|
+
const formattedSeconds = String(adjustedSeconds).padStart(12, '0');
|
|
1047
|
+
const formattedNanoseconds = String(this.nanoseconds).padStart(9, '0');
|
|
1048
|
+
return formattedSeconds + '.' + formattedNanoseconds;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
/**
|
|
1053
|
+
* @license
|
|
1054
|
+
* Copyright 2017 Google LLC
|
|
1055
|
+
*
|
|
1056
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1057
|
+
* you may not use this file except in compliance with the License.
|
|
1058
|
+
* You may obtain a copy of the License at
|
|
1059
|
+
*
|
|
1060
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1061
|
+
*
|
|
1062
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1063
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1064
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1065
|
+
* See the License for the specific language governing permissions and
|
|
1066
|
+
* limitations under the License.
|
|
1067
|
+
*/
|
|
1068
|
+
/**
|
|
1069
|
+
* A version of a document in Firestore. This corresponds to the version
|
|
1070
|
+
* timestamp, such as update_time or read_time.
|
|
1071
|
+
*/
|
|
1072
|
+
class SnapshotVersion {
|
|
1073
|
+
constructor(timestamp) {
|
|
1074
|
+
this.timestamp = timestamp;
|
|
1075
|
+
}
|
|
1076
|
+
static fromTimestamp(value) {
|
|
1077
|
+
return new SnapshotVersion(value);
|
|
1078
|
+
}
|
|
1079
|
+
static min() {
|
|
1080
|
+
return new SnapshotVersion(new Timestamp(0, 0));
|
|
1081
|
+
}
|
|
1082
|
+
static max() {
|
|
1083
|
+
return new SnapshotVersion(new Timestamp(253402300799, 1e9 - 1));
|
|
1084
|
+
}
|
|
1085
|
+
compareTo(other) {
|
|
1086
|
+
return this.timestamp._compareTo(other.timestamp);
|
|
1087
|
+
}
|
|
1088
|
+
isEqual(other) {
|
|
1089
|
+
return this.timestamp.isEqual(other.timestamp);
|
|
1090
|
+
}
|
|
1091
|
+
/** Returns a number representation of the version for use in spec tests. */
|
|
1092
|
+
toMicroseconds() {
|
|
1093
|
+
// Convert to microseconds.
|
|
1094
|
+
return this.timestamp.seconds * 1e6 + this.timestamp.nanoseconds / 1000;
|
|
1095
|
+
}
|
|
1096
|
+
toString() {
|
|
1097
|
+
return 'SnapshotVersion(' + this.timestamp.toString() + ')';
|
|
1098
|
+
}
|
|
1099
|
+
toTimestamp() {
|
|
1100
|
+
return this.timestamp;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
/**
|
|
1105
|
+
* @license
|
|
1106
|
+
* Copyright 2017 Google LLC
|
|
1107
|
+
*
|
|
1108
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1109
|
+
* you may not use this file except in compliance with the License.
|
|
1110
|
+
* You may obtain a copy of the License at
|
|
1111
|
+
*
|
|
1112
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1113
|
+
*
|
|
1114
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
1115
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1116
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1117
|
+
* See the License for the specific language governing permissions and
|
|
1118
|
+
* limitations under the License.
|
|
1119
|
+
*/
|
|
1120
|
+
const DOCUMENT_KEY_NAME = '__name__';
|
|
1121
|
+
/**
|
|
1122
|
+
* Path represents an ordered sequence of string segments.
|
|
1123
|
+
*/
|
|
1124
|
+
class BasePath {
|
|
1125
|
+
constructor(segments, offset, length) {
|
|
1126
|
+
if (offset === undefined) {
|
|
1127
|
+
offset = 0;
|
|
1128
|
+
}
|
|
1129
|
+
else if (offset > segments.length) {
|
|
1130
|
+
fail();
|
|
1131
|
+
}
|
|
1132
|
+
if (length === undefined) {
|
|
1133
|
+
length = segments.length - offset;
|
|
1134
|
+
}
|
|
1135
|
+
else if (length > segments.length - offset) {
|
|
1136
|
+
fail();
|
|
1137
|
+
}
|
|
1138
|
+
this.segments = segments;
|
|
1139
|
+
this.offset = offset;
|
|
1140
|
+
this.len = length;
|
|
1141
|
+
}
|
|
1142
|
+
get length() {
|
|
1143
|
+
return this.len;
|
|
1144
|
+
}
|
|
1145
|
+
isEqual(other) {
|
|
1146
|
+
return BasePath.comparator(this, other) === 0;
|
|
1147
|
+
}
|
|
1148
|
+
child(nameOrPath) {
|
|
1149
|
+
const segments = this.segments.slice(this.offset, this.limit());
|
|
1150
|
+
if (nameOrPath instanceof BasePath) {
|
|
1151
|
+
nameOrPath.forEach(segment => {
|
|
1152
|
+
segments.push(segment);
|
|
1153
|
+
});
|
|
1154
|
+
}
|
|
1155
|
+
else {
|
|
1156
|
+
segments.push(nameOrPath);
|
|
1157
|
+
}
|
|
1158
|
+
return this.construct(segments);
|
|
1159
|
+
}
|
|
1160
|
+
/** The index of one past the last segment of the path. */
|
|
1161
|
+
limit() {
|
|
1162
|
+
return this.offset + this.length;
|
|
1163
|
+
}
|
|
1164
|
+
popFirst(size) {
|
|
1165
|
+
size = size === undefined ? 1 : size;
|
|
1166
|
+
return this.construct(this.segments, this.offset + size, this.length - size);
|
|
1167
|
+
}
|
|
1168
|
+
popLast() {
|
|
1169
|
+
return this.construct(this.segments, this.offset, this.length - 1);
|
|
1170
|
+
}
|
|
1171
|
+
firstSegment() {
|
|
1172
|
+
return this.segments[this.offset];
|
|
1173
|
+
}
|
|
1174
|
+
lastSegment() {
|
|
1175
|
+
return this.get(this.length - 1);
|
|
1176
|
+
}
|
|
1177
|
+
get(index) {
|
|
1178
|
+
return this.segments[this.offset + index];
|
|
1179
|
+
}
|
|
1180
|
+
isEmpty() {
|
|
1181
|
+
return this.length === 0;
|
|
1182
|
+
}
|
|
1183
|
+
isPrefixOf(other) {
|
|
1184
|
+
if (other.length < this.length) {
|
|
1185
|
+
return false;
|
|
1186
|
+
}
|
|
1187
|
+
for (let i = 0; i < this.length; i++) {
|
|
1188
|
+
if (this.get(i) !== other.get(i)) {
|
|
923
1189
|
return false;
|
|
924
1190
|
}
|
|
925
1191
|
}
|
|
@@ -1125,109 +1391,64 @@ class FieldPath$1 extends BasePath {
|
|
|
1125
1391
|
* See the License for the specific language governing permissions and
|
|
1126
1392
|
* limitations under the License.
|
|
1127
1393
|
*/
|
|
1128
|
-
const escapeChar = '\u0001';
|
|
1129
|
-
const encodedSeparatorChar = '\u0001';
|
|
1130
|
-
const encodedNul = '\u0010';
|
|
1131
|
-
const encodedEscape = '\u0011';
|
|
1132
1394
|
/**
|
|
1133
|
-
*
|
|
1395
|
+
* @internal
|
|
1134
1396
|
*/
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
if (result.length > 0) {
|
|
1139
|
-
result = encodeSeparator(result);
|
|
1140
|
-
}
|
|
1141
|
-
result = encodeSegment(path.get(i), result);
|
|
1397
|
+
class DocumentKey {
|
|
1398
|
+
constructor(path) {
|
|
1399
|
+
this.path = path;
|
|
1142
1400
|
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
/** Encodes a single segment of a resource path into the given result */
|
|
1146
|
-
function encodeSegment(segment, resultBuf) {
|
|
1147
|
-
let result = resultBuf;
|
|
1148
|
-
const length = segment.length;
|
|
1149
|
-
for (let i = 0; i < length; i++) {
|
|
1150
|
-
const c = segment.charAt(i);
|
|
1151
|
-
switch (c) {
|
|
1152
|
-
case '\0':
|
|
1153
|
-
result += escapeChar + encodedNul;
|
|
1154
|
-
break;
|
|
1155
|
-
case escapeChar:
|
|
1156
|
-
result += escapeChar + encodedEscape;
|
|
1157
|
-
break;
|
|
1158
|
-
default:
|
|
1159
|
-
result += c;
|
|
1160
|
-
}
|
|
1401
|
+
static fromPath(path) {
|
|
1402
|
+
return new DocumentKey(ResourcePath.fromString(path));
|
|
1161
1403
|
}
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
/** Encodes a path separator into the given result */
|
|
1165
|
-
function encodeSeparator(result) {
|
|
1166
|
-
return result + escapeChar + encodedSeparatorChar;
|
|
1167
|
-
}
|
|
1168
|
-
/**
|
|
1169
|
-
* Decodes the given IndexedDb-compatible string form of a resource path into
|
|
1170
|
-
* a ResourcePath instance. Note that this method is not suitable for use with
|
|
1171
|
-
* decoding resource names from the server; those are One Platform format
|
|
1172
|
-
* strings.
|
|
1173
|
-
*/
|
|
1174
|
-
function decodeResourcePath(path) {
|
|
1175
|
-
// Event the empty path must encode as a path of at least length 2. A path
|
|
1176
|
-
// with exactly 2 must be the empty path.
|
|
1177
|
-
const length = path.length;
|
|
1178
|
-
hardAssert(length >= 2);
|
|
1179
|
-
if (length === 2) {
|
|
1180
|
-
hardAssert(path.charAt(0) === escapeChar && path.charAt(1) === encodedSeparatorChar);
|
|
1181
|
-
return ResourcePath.emptyPath();
|
|
1404
|
+
static fromName(name) {
|
|
1405
|
+
return new DocumentKey(ResourcePath.fromString(name).popFirst(5));
|
|
1182
1406
|
}
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
}
|
|
1223
|
-
start = end + 2;
|
|
1407
|
+
static empty() {
|
|
1408
|
+
return new DocumentKey(ResourcePath.emptyPath());
|
|
1409
|
+
}
|
|
1410
|
+
get collectionGroup() {
|
|
1411
|
+
return this.path.popLast().lastSegment();
|
|
1412
|
+
}
|
|
1413
|
+
/** Returns true if the document is in the specified collectionId. */
|
|
1414
|
+
hasCollectionId(collectionId) {
|
|
1415
|
+
return (this.path.length >= 2 &&
|
|
1416
|
+
this.path.get(this.path.length - 2) === collectionId);
|
|
1417
|
+
}
|
|
1418
|
+
/** Returns the collection group (i.e. the name of the parent collection) for this key. */
|
|
1419
|
+
getCollectionGroup() {
|
|
1420
|
+
return this.path.get(this.path.length - 2);
|
|
1421
|
+
}
|
|
1422
|
+
/** Returns the fully qualified path to the parent collection. */
|
|
1423
|
+
getCollectionPath() {
|
|
1424
|
+
return this.path.popLast();
|
|
1425
|
+
}
|
|
1426
|
+
isEqual(other) {
|
|
1427
|
+
return (other !== null && ResourcePath.comparator(this.path, other.path) === 0);
|
|
1428
|
+
}
|
|
1429
|
+
toString() {
|
|
1430
|
+
return this.path.toString();
|
|
1431
|
+
}
|
|
1432
|
+
static comparator(k1, k2) {
|
|
1433
|
+
return ResourcePath.comparator(k1.path, k2.path);
|
|
1434
|
+
}
|
|
1435
|
+
static isDocumentKey(path) {
|
|
1436
|
+
return path.length % 2 === 0;
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Creates and returns a new document key with the given segments.
|
|
1440
|
+
*
|
|
1441
|
+
* @param segments - The segments of the path to the document
|
|
1442
|
+
* @returns A new instance of DocumentKey
|
|
1443
|
+
*/
|
|
1444
|
+
static fromSegments(segments) {
|
|
1445
|
+
return new DocumentKey(new ResourcePath(segments.slice()));
|
|
1224
1446
|
}
|
|
1225
|
-
return new ResourcePath(segments);
|
|
1226
1447
|
}
|
|
1227
1448
|
|
|
1228
1449
|
/**
|
|
1229
1450
|
* @license
|
|
1230
|
-
* Copyright
|
|
1451
|
+
* Copyright 2021 Google LLC
|
|
1231
1452
|
*
|
|
1232
1453
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1233
1454
|
* you may not use this file except in compliance with the License.
|
|
@@ -1241,11 +1462,197 @@ function decodeResourcePath(path) {
|
|
|
1241
1462
|
* See the License for the specific language governing permissions and
|
|
1242
1463
|
* limitations under the License.
|
|
1243
1464
|
*/
|
|
1244
|
-
|
|
1465
|
+
/**
|
|
1466
|
+
* The initial mutation batch id for each index. Gets updated during index
|
|
1467
|
+
* backfill.
|
|
1468
|
+
*/
|
|
1469
|
+
const INITIAL_LARGEST_BATCH_ID = -1;
|
|
1470
|
+
/**
|
|
1471
|
+
* The initial sequence number for each index. Gets updated during index
|
|
1472
|
+
* backfill.
|
|
1473
|
+
*/
|
|
1474
|
+
const INITIAL_SEQUENCE_NUMBER = 0;
|
|
1475
|
+
/**
|
|
1476
|
+
* An index definition for field indexes in Firestore.
|
|
1477
|
+
*
|
|
1478
|
+
* Every index is associated with a collection. The definition contains a list
|
|
1479
|
+
* of fields and their index kind (which can be `ASCENDING`, `DESCENDING` or
|
|
1480
|
+
* `CONTAINS` for ArrayContains/ArrayContainsAny queries).
|
|
1481
|
+
*
|
|
1482
|
+
* Unlike the backend, the SDK does not differentiate between collection or
|
|
1483
|
+
* collection group-scoped indices. Every index can be used for both single
|
|
1484
|
+
* collection and collection group queries.
|
|
1485
|
+
*/
|
|
1486
|
+
class FieldIndex {
|
|
1487
|
+
constructor(
|
|
1488
|
+
/**
|
|
1489
|
+
* The index ID. Returns -1 if the index ID is not available (e.g. the index
|
|
1490
|
+
* has not yet been persisted).
|
|
1491
|
+
*/
|
|
1492
|
+
indexId,
|
|
1493
|
+
/** The collection ID this index applies to. */
|
|
1494
|
+
collectionGroup,
|
|
1495
|
+
/** The field segments for this index. */
|
|
1496
|
+
fields,
|
|
1497
|
+
/** Shows how up-to-date the index is for the current user. */
|
|
1498
|
+
indexState) {
|
|
1499
|
+
this.indexId = indexId;
|
|
1500
|
+
this.collectionGroup = collectionGroup;
|
|
1501
|
+
this.fields = fields;
|
|
1502
|
+
this.indexState = indexState;
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
/** An ID for an index that has not yet been added to persistence. */
|
|
1506
|
+
FieldIndex.UNKNOWN_ID = -1;
|
|
1507
|
+
/** Returns the ArrayContains/ArrayContainsAny segment for this index. */
|
|
1508
|
+
function fieldIndexGetArraySegment(fieldIndex) {
|
|
1509
|
+
return fieldIndex.fields.find(s => s.kind === 2 /* CONTAINS */);
|
|
1510
|
+
}
|
|
1511
|
+
/** Returns all directional (ascending/descending) segments for this index. */
|
|
1512
|
+
function fieldIndexGetDirectionalSegments(fieldIndex) {
|
|
1513
|
+
return fieldIndex.fields.filter(s => s.kind !== 2 /* CONTAINS */);
|
|
1514
|
+
}
|
|
1515
|
+
/**
|
|
1516
|
+
* Returns the order of the document key component for the given index.
|
|
1517
|
+
*
|
|
1518
|
+
* PORTING NOTE: This is only used in the Web IndexedDb implementation.
|
|
1519
|
+
*/
|
|
1520
|
+
function fieldIndexGetKeyOrder(fieldIndex) {
|
|
1521
|
+
const directionalSegments = fieldIndexGetDirectionalSegments(fieldIndex);
|
|
1522
|
+
return directionalSegments.length === 0
|
|
1523
|
+
? 0 /* ASCENDING */
|
|
1524
|
+
: directionalSegments[directionalSegments.length - 1].kind;
|
|
1525
|
+
}
|
|
1526
|
+
/** Returns a debug representation of the field index */
|
|
1527
|
+
function fieldIndexToString(fieldIndex) {
|
|
1528
|
+
return `id=${fieldIndex.indexId}|cg=${fieldIndex.collectionGroup}|f=${fieldIndex.fields.map(f => `${f.fieldPath}:${f.kind}`).join(',')}`;
|
|
1529
|
+
}
|
|
1530
|
+
/** An index component consisting of field path and index type. */
|
|
1531
|
+
class IndexSegment {
|
|
1532
|
+
constructor(
|
|
1533
|
+
/** The field path of the component. */
|
|
1534
|
+
fieldPath,
|
|
1535
|
+
/** The fields sorting order. */
|
|
1536
|
+
kind) {
|
|
1537
|
+
this.fieldPath = fieldPath;
|
|
1538
|
+
this.kind = kind;
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
/**
|
|
1542
|
+
* Stores the "high water mark" that indicates how updated the Index is for the
|
|
1543
|
+
* current user.
|
|
1544
|
+
*/
|
|
1545
|
+
class IndexState {
|
|
1546
|
+
constructor(
|
|
1547
|
+
/**
|
|
1548
|
+
* Indicates when the index was last updated (relative to other indexes).
|
|
1549
|
+
*/
|
|
1550
|
+
sequenceNumber,
|
|
1551
|
+
/** The the latest indexed read time, document and batch id. */
|
|
1552
|
+
offset) {
|
|
1553
|
+
this.sequenceNumber = sequenceNumber;
|
|
1554
|
+
this.offset = offset;
|
|
1555
|
+
}
|
|
1556
|
+
/** The state of an index that has not yet been backfilled. */
|
|
1557
|
+
static empty() {
|
|
1558
|
+
return new IndexState(INITIAL_SEQUENCE_NUMBER, IndexOffset.min());
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
/**
|
|
1562
|
+
* Creates an offset that matches all documents with a read time higher than
|
|
1563
|
+
* `readTime`.
|
|
1564
|
+
*/
|
|
1565
|
+
function newIndexOffsetSuccessorFromReadTime(readTime, largestBatchId) {
|
|
1566
|
+
// We want to create an offset that matches all documents with a read time
|
|
1567
|
+
// greater than the provided read time. To do so, we technically need to
|
|
1568
|
+
// create an offset for `(readTime, MAX_DOCUMENT_KEY)`. While we could use
|
|
1569
|
+
// Unicode codepoints to generate MAX_DOCUMENT_KEY, it is much easier to use
|
|
1570
|
+
// `(readTime + 1, DocumentKey.empty())` since `> DocumentKey.empty()` matches
|
|
1571
|
+
// all valid document IDs.
|
|
1572
|
+
const successorSeconds = readTime.toTimestamp().seconds;
|
|
1573
|
+
const successorNanos = readTime.toTimestamp().nanoseconds + 1;
|
|
1574
|
+
const successor = SnapshotVersion.fromTimestamp(successorNanos === 1e9
|
|
1575
|
+
? new Timestamp(successorSeconds + 1, 0)
|
|
1576
|
+
: new Timestamp(successorSeconds, successorNanos));
|
|
1577
|
+
return new IndexOffset(successor, DocumentKey.empty(), largestBatchId);
|
|
1578
|
+
}
|
|
1579
|
+
/** Creates a new offset based on the provided document. */
|
|
1580
|
+
function newIndexOffsetFromDocument(document) {
|
|
1581
|
+
return new IndexOffset(document.readTime, document.key, INITIAL_LARGEST_BATCH_ID);
|
|
1582
|
+
}
|
|
1583
|
+
/**
|
|
1584
|
+
* Stores the latest read time, document and batch ID that were processed for an
|
|
1585
|
+
* index.
|
|
1586
|
+
*/
|
|
1587
|
+
class IndexOffset {
|
|
1588
|
+
constructor(
|
|
1589
|
+
/**
|
|
1590
|
+
* The latest read time version that has been indexed by Firestore for this
|
|
1591
|
+
* field index.
|
|
1592
|
+
*/
|
|
1593
|
+
readTime,
|
|
1594
|
+
/**
|
|
1595
|
+
* The key of the last document that was indexed for this query. Use
|
|
1596
|
+
* `DocumentKey.empty()` if no document has been indexed.
|
|
1597
|
+
*/
|
|
1598
|
+
documentKey,
|
|
1599
|
+
/*
|
|
1600
|
+
* The largest mutation batch id that's been processed by Firestore.
|
|
1601
|
+
*/
|
|
1602
|
+
largestBatchId) {
|
|
1603
|
+
this.readTime = readTime;
|
|
1604
|
+
this.documentKey = documentKey;
|
|
1605
|
+
this.largestBatchId = largestBatchId;
|
|
1606
|
+
}
|
|
1607
|
+
/** Returns an offset that sorts before all regular offsets. */
|
|
1608
|
+
static min() {
|
|
1609
|
+
return new IndexOffset(SnapshotVersion.min(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
|
|
1610
|
+
}
|
|
1611
|
+
/** Returns an offset that sorts after all regular offsets. */
|
|
1612
|
+
static max() {
|
|
1613
|
+
return new IndexOffset(SnapshotVersion.max(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
|
|
1614
|
+
}
|
|
1615
|
+
}
|
|
1616
|
+
function indexOffsetComparator(left, right) {
|
|
1617
|
+
let cmp = left.readTime.compareTo(right.readTime);
|
|
1618
|
+
if (cmp !== 0) {
|
|
1619
|
+
return cmp;
|
|
1620
|
+
}
|
|
1621
|
+
cmp = DocumentKey.comparator(left.documentKey, right.documentKey);
|
|
1622
|
+
if (cmp !== 0) {
|
|
1623
|
+
return cmp;
|
|
1624
|
+
}
|
|
1625
|
+
return primitiveComparator(left.largestBatchId, right.largestBatchId);
|
|
1626
|
+
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Schema Version for the Web client:
|
|
1629
|
+
* 1. Initial version including Mutation Queue, Query Cache, and Remote
|
|
1630
|
+
* Document Cache
|
|
1631
|
+
* 2. Used to ensure a targetGlobal object exists and add targetCount to it. No
|
|
1632
|
+
* longer required because migration 3 unconditionally clears it.
|
|
1633
|
+
* 3. Dropped and re-created Query Cache to deal with cache corruption related
|
|
1634
|
+
* to limbo resolution. Addresses
|
|
1635
|
+
* https://github.com/firebase/firebase-ios-sdk/issues/1548
|
|
1636
|
+
* 4. Multi-Tab Support.
|
|
1637
|
+
* 5. Removal of held write acks.
|
|
1638
|
+
* 6. Create document global for tracking document cache size.
|
|
1639
|
+
* 7. Ensure every cached document has a sentinel row with a sequence number.
|
|
1640
|
+
* 8. Add collection-parent index for Collection Group queries.
|
|
1641
|
+
* 9. Change RemoteDocumentChanges store to be keyed by readTime rather than
|
|
1642
|
+
* an auto-incrementing ID. This is required for Index-Free queries.
|
|
1643
|
+
* 10. Rewrite the canonical IDs to the explicit Protobuf-based format.
|
|
1644
|
+
* 11. Add bundles and named_queries for bundle support.
|
|
1645
|
+
* 12. Add document overlays.
|
|
1646
|
+
* 13. Rewrite the keys of the remote document cache to allow for efficient
|
|
1647
|
+
* document lookup via `getAll()`.
|
|
1648
|
+
* 14. Add overlays.
|
|
1649
|
+
* 15. Add indexing support.
|
|
1650
|
+
*/
|
|
1651
|
+
const SCHEMA_VERSION = 14;
|
|
1245
1652
|
|
|
1246
1653
|
/**
|
|
1247
1654
|
* @license
|
|
1248
|
-
* Copyright
|
|
1655
|
+
* Copyright 2020 Google LLC
|
|
1249
1656
|
*
|
|
1250
1657
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1251
1658
|
* you may not use this file except in compliance with the License.
|
|
@@ -1259,260 +1666,31 @@ const DbRemoteDocumentStore$1 = 'remoteDocuments';
|
|
|
1259
1666
|
* See the License for the specific language governing permissions and
|
|
1260
1667
|
* limitations under the License.
|
|
1261
1668
|
*/
|
|
1669
|
+
const PRIMARY_LEASE_LOST_ERROR_MSG = 'The current tab is not in the required state to perform this operation. ' +
|
|
1670
|
+
'It might be necessary to refresh the browser tab.';
|
|
1262
1671
|
/**
|
|
1263
|
-
*
|
|
1672
|
+
* A base class representing a persistence transaction, encapsulating both the
|
|
1673
|
+
* transaction's sequence numbers as well as a list of onCommitted listeners.
|
|
1264
1674
|
*
|
|
1265
|
-
*
|
|
1266
|
-
*
|
|
1267
|
-
*
|
|
1268
|
-
*/
|
|
1269
|
-
const DbPrimaryClientStore = 'owner';
|
|
1270
|
-
/**
|
|
1271
|
-
* The key string used for the single object that exists in the
|
|
1272
|
-
* DbPrimaryClient store.
|
|
1675
|
+
* When you call Persistence.runTransaction(), it will create a transaction and
|
|
1676
|
+
* pass it to your callback. You then pass it to any method that operates
|
|
1677
|
+
* on persistence.
|
|
1273
1678
|
*/
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
/** Keys are automatically assigned via the userId property. */
|
|
1278
|
-
const DbMutationQueueKeyPath = 'userId';
|
|
1279
|
-
/** Name of the IndexedDb object store. */
|
|
1280
|
-
const DbMutationBatchStore = 'mutations';
|
|
1281
|
-
/** Keys are automatically assigned via the userId, batchId properties. */
|
|
1282
|
-
const DbMutationBatchKeyPath = 'batchId';
|
|
1283
|
-
/** The index name for lookup of mutations by user. */
|
|
1284
|
-
const DbMutationBatchUserMutationsIndex = 'userMutationsIndex';
|
|
1285
|
-
/** The user mutations index is keyed by [userId, batchId] pairs. */
|
|
1286
|
-
const DbMutationBatchUserMutationsKeyPath = ['userId', 'batchId'];
|
|
1287
|
-
/**
|
|
1288
|
-
* Creates a [userId] key for use in the DbDocumentMutations index to iterate
|
|
1289
|
-
* over all of a user's document mutations.
|
|
1290
|
-
*/
|
|
1291
|
-
function newDbDocumentMutationPrefixForUser(userId) {
|
|
1292
|
-
return [userId];
|
|
1293
|
-
}
|
|
1294
|
-
/**
|
|
1295
|
-
* Creates a [userId, encodedPath] key for use in the DbDocumentMutations
|
|
1296
|
-
* index to iterate over all at document mutations for a given path or lower.
|
|
1297
|
-
*/
|
|
1298
|
-
function newDbDocumentMutationPrefixForPath(userId, path) {
|
|
1299
|
-
return [userId, encodeResourcePath(path)];
|
|
1300
|
-
}
|
|
1301
|
-
/**
|
|
1302
|
-
* Creates a full index key of [userId, encodedPath, batchId] for inserting
|
|
1303
|
-
* and deleting into the DbDocumentMutations index.
|
|
1304
|
-
*/
|
|
1305
|
-
function newDbDocumentMutationKey(userId, path, batchId) {
|
|
1306
|
-
return [userId, encodeResourcePath(path), batchId];
|
|
1307
|
-
}
|
|
1308
|
-
/**
|
|
1309
|
-
* Because we store all the useful information for this store in the key,
|
|
1310
|
-
* there is no useful information to store as the value. The raw (unencoded)
|
|
1311
|
-
* path cannot be stored because IndexedDb doesn't store prototype
|
|
1312
|
-
* information.
|
|
1313
|
-
*/
|
|
1314
|
-
const DbDocumentMutationPlaceholder = {};
|
|
1315
|
-
const DbDocumentMutationStore = 'documentMutations';
|
|
1316
|
-
const DbRemoteDocumentStore = 'remoteDocumentsV14';
|
|
1317
|
-
/**
|
|
1318
|
-
* The primary key of the remote documents store, which allows for efficient
|
|
1319
|
-
* access by collection path and read time.
|
|
1320
|
-
*/
|
|
1321
|
-
const DbRemoteDocumentKeyPath = [
|
|
1322
|
-
'prefixPath',
|
|
1323
|
-
'collectionGroup',
|
|
1324
|
-
'readTime',
|
|
1325
|
-
'documentId'
|
|
1326
|
-
];
|
|
1327
|
-
/** An index that provides access to documents by key. */
|
|
1328
|
-
const DbRemoteDocumentDocumentKeyIndex = 'documentKeyIndex';
|
|
1329
|
-
const DbRemoteDocumentDocumentKeyIndexPath = [
|
|
1330
|
-
'prefixPath',
|
|
1331
|
-
'collectionGroup',
|
|
1332
|
-
'documentId'
|
|
1333
|
-
];
|
|
1334
|
-
/**
|
|
1335
|
-
* An index that provides access to documents by collection group and read
|
|
1336
|
-
* time.
|
|
1337
|
-
*
|
|
1338
|
-
* This index is used by the index backfiller.
|
|
1339
|
-
*/
|
|
1340
|
-
const DbRemoteDocumentCollectionGroupIndex = 'collectionGroupIndex';
|
|
1341
|
-
const DbRemoteDocumentCollectionGroupIndexPath = [
|
|
1342
|
-
'collectionGroup',
|
|
1343
|
-
'readTime',
|
|
1344
|
-
'prefixPath',
|
|
1345
|
-
'documentId'
|
|
1346
|
-
];
|
|
1347
|
-
const DbRemoteDocumentGlobalStore = 'remoteDocumentGlobal';
|
|
1348
|
-
const DbRemoteDocumentGlobalKey = 'remoteDocumentGlobalKey';
|
|
1349
|
-
const DbTargetStore = 'targets';
|
|
1350
|
-
/** Keys are automatically assigned via the targetId property. */
|
|
1351
|
-
const DbTargetKeyPath = 'targetId';
|
|
1352
|
-
/** The name of the queryTargets index. */
|
|
1353
|
-
const DbTargetQueryTargetsIndexName = 'queryTargetsIndex';
|
|
1354
|
-
/**
|
|
1355
|
-
* The index of all canonicalIds to the targets that they match. This is not
|
|
1356
|
-
* a unique mapping because canonicalId does not promise a unique name for all
|
|
1357
|
-
* possible queries, so we append the targetId to make the mapping unique.
|
|
1358
|
-
*/
|
|
1359
|
-
const DbTargetQueryTargetsKeyPath = ['canonicalId', 'targetId'];
|
|
1360
|
-
/** Name of the IndexedDb object store. */
|
|
1361
|
-
const DbTargetDocumentStore = 'targetDocuments';
|
|
1362
|
-
/** Keys are automatically assigned via the targetId, path properties. */
|
|
1363
|
-
const DbTargetDocumentKeyPath = ['targetId', 'path'];
|
|
1364
|
-
/** The index name for the reverse index. */
|
|
1365
|
-
const DbTargetDocumentDocumentTargetsIndex = 'documentTargetsIndex';
|
|
1366
|
-
/** We also need to create the reverse index for these properties. */
|
|
1367
|
-
const DbTargetDocumentDocumentTargetsKeyPath = ['path', 'targetId'];
|
|
1368
|
-
/**
|
|
1369
|
-
* The key string used for the single object that exists in the
|
|
1370
|
-
* DbTargetGlobal store.
|
|
1371
|
-
*/
|
|
1372
|
-
const DbTargetGlobalKey = 'targetGlobalKey';
|
|
1373
|
-
const DbTargetGlobalStore = 'targetGlobal';
|
|
1374
|
-
/** Name of the IndexedDb object store. */
|
|
1375
|
-
const DbCollectionParentStore = 'collectionParents';
|
|
1376
|
-
/** Keys are automatically assigned via the collectionId, parent properties. */
|
|
1377
|
-
const DbCollectionParentKeyPath = ['collectionId', 'parent'];
|
|
1378
|
-
/** Name of the IndexedDb object store. */
|
|
1379
|
-
const DbClientMetadataStore = 'clientMetadata';
|
|
1380
|
-
/** Keys are automatically assigned via the clientId properties. */
|
|
1381
|
-
const DbClientMetadataKeyPath = 'clientId';
|
|
1382
|
-
/** Name of the IndexedDb object store. */
|
|
1383
|
-
const DbBundleStore = 'bundles';
|
|
1384
|
-
const DbBundleKeyPath = 'bundleId';
|
|
1385
|
-
/** Name of the IndexedDb object store. */
|
|
1386
|
-
const DbNamedQueryStore = 'namedQueries';
|
|
1387
|
-
const DbNamedQueryKeyPath = 'name';
|
|
1388
|
-
/** Name of the IndexedDb object store. */
|
|
1389
|
-
const DbIndexConfigurationStore = 'indexConfiguration';
|
|
1390
|
-
const DbIndexConfigurationKeyPath = 'indexId';
|
|
1391
|
-
/**
|
|
1392
|
-
* An index that provides access to the index configurations by collection
|
|
1393
|
-
* group.
|
|
1394
|
-
*
|
|
1395
|
-
* PORTING NOTE: iOS and Android maintain this index in-memory, but this is
|
|
1396
|
-
* not possible here as the Web client supports concurrent access to
|
|
1397
|
-
* persistence via multi-tab.
|
|
1398
|
-
*/
|
|
1399
|
-
const DbIndexConfigurationCollectionGroupIndex = 'collectionGroupIndex';
|
|
1400
|
-
const DbIndexConfigurationCollectionGroupIndexPath = 'collectionGroup';
|
|
1401
|
-
/** Name of the IndexedDb object store. */
|
|
1402
|
-
const DbIndexStateStore = 'indexState';
|
|
1403
|
-
const DbIndexStateKeyPath = ['indexId', 'uid'];
|
|
1404
|
-
/**
|
|
1405
|
-
* An index that provides access to documents in a collection sorted by last
|
|
1406
|
-
* update time. Used by the backfiller.
|
|
1407
|
-
*
|
|
1408
|
-
* PORTING NOTE: iOS and Android maintain this index in-memory, but this is
|
|
1409
|
-
* not possible here as the Web client supports concurrent access to
|
|
1410
|
-
* persistence via multi-tab.
|
|
1411
|
-
*/
|
|
1412
|
-
const DbIndexStateSequenceNumberIndex = 'sequenceNumberIndex';
|
|
1413
|
-
const DbIndexStateSequenceNumberIndexPath = ['uid', 'sequenceNumber'];
|
|
1414
|
-
/** Name of the IndexedDb object store. */
|
|
1415
|
-
const DbIndexEntryStore = 'indexEntries';
|
|
1416
|
-
const DbIndexEntryKeyPath = [
|
|
1417
|
-
'indexId',
|
|
1418
|
-
'uid',
|
|
1419
|
-
'arrayValue',
|
|
1420
|
-
'directionalValue',
|
|
1421
|
-
'orderedDocumentKey',
|
|
1422
|
-
'documentKey'
|
|
1423
|
-
];
|
|
1424
|
-
const DbIndexEntryDocumentKeyIndex = 'documentKeyIndex';
|
|
1425
|
-
const DbIndexEntryDocumentKeyIndexPath = [
|
|
1426
|
-
'indexId',
|
|
1427
|
-
'uid',
|
|
1428
|
-
'orderedDocumentKey'
|
|
1429
|
-
];
|
|
1430
|
-
/** Name of the IndexedDb object store. */
|
|
1431
|
-
const DbDocumentOverlayStore = 'documentOverlays';
|
|
1432
|
-
const DbDocumentOverlayKeyPath = [
|
|
1433
|
-
'userId',
|
|
1434
|
-
'collectionPath',
|
|
1435
|
-
'documentId'
|
|
1436
|
-
];
|
|
1437
|
-
const DbDocumentOverlayCollectionPathOverlayIndex = 'collectionPathOverlayIndex';
|
|
1438
|
-
const DbDocumentOverlayCollectionPathOverlayIndexPath = [
|
|
1439
|
-
'userId',
|
|
1440
|
-
'collectionPath',
|
|
1441
|
-
'largestBatchId'
|
|
1442
|
-
];
|
|
1443
|
-
const DbDocumentOverlayCollectionGroupOverlayIndex = 'collectionGroupOverlayIndex';
|
|
1444
|
-
const DbDocumentOverlayCollectionGroupOverlayIndexPath = [
|
|
1445
|
-
'userId',
|
|
1446
|
-
'collectionGroup',
|
|
1447
|
-
'largestBatchId'
|
|
1448
|
-
];
|
|
1449
|
-
// Visible for testing
|
|
1450
|
-
const V1_STORES = [
|
|
1451
|
-
DbMutationQueueStore,
|
|
1452
|
-
DbMutationBatchStore,
|
|
1453
|
-
DbDocumentMutationStore,
|
|
1454
|
-
DbRemoteDocumentStore$1,
|
|
1455
|
-
DbTargetStore,
|
|
1456
|
-
DbPrimaryClientStore,
|
|
1457
|
-
DbTargetGlobalStore,
|
|
1458
|
-
DbTargetDocumentStore
|
|
1459
|
-
];
|
|
1460
|
-
// Visible for testing
|
|
1461
|
-
const V3_STORES = V1_STORES;
|
|
1462
|
-
// Note: DbRemoteDocumentChanges is no longer used and dropped with v9.
|
|
1463
|
-
const V4_STORES = [...V3_STORES, DbClientMetadataStore];
|
|
1464
|
-
const V6_STORES = [...V4_STORES, DbRemoteDocumentGlobalStore];
|
|
1465
|
-
const V8_STORES = [...V6_STORES, DbCollectionParentStore];
|
|
1466
|
-
const V11_STORES = [...V8_STORES, DbBundleStore, DbNamedQueryStore];
|
|
1467
|
-
const V12_STORES = [...V11_STORES, DbDocumentOverlayStore];
|
|
1468
|
-
const V13_STORES = [
|
|
1469
|
-
DbMutationQueueStore,
|
|
1470
|
-
DbMutationBatchStore,
|
|
1471
|
-
DbDocumentMutationStore,
|
|
1472
|
-
DbRemoteDocumentStore,
|
|
1473
|
-
DbTargetStore,
|
|
1474
|
-
DbPrimaryClientStore,
|
|
1475
|
-
DbTargetGlobalStore,
|
|
1476
|
-
DbTargetDocumentStore,
|
|
1477
|
-
DbClientMetadataStore,
|
|
1478
|
-
DbRemoteDocumentGlobalStore,
|
|
1479
|
-
DbCollectionParentStore,
|
|
1480
|
-
DbBundleStore,
|
|
1481
|
-
DbNamedQueryStore,
|
|
1482
|
-
DbDocumentOverlayStore
|
|
1483
|
-
];
|
|
1484
|
-
const V14_STORES = V13_STORES;
|
|
1485
|
-
const V15_STORES = [
|
|
1486
|
-
...V14_STORES,
|
|
1487
|
-
DbIndexConfigurationStore,
|
|
1488
|
-
DbIndexStateStore,
|
|
1489
|
-
DbIndexEntryStore
|
|
1490
|
-
];
|
|
1491
|
-
/** Returns the object stores for the provided schema. */
|
|
1492
|
-
function getObjectStores(schemaVersion) {
|
|
1493
|
-
if (schemaVersion === 15) {
|
|
1494
|
-
return V15_STORES;
|
|
1495
|
-
}
|
|
1496
|
-
else if (schemaVersion === 14) {
|
|
1497
|
-
return V14_STORES;
|
|
1498
|
-
}
|
|
1499
|
-
else if (schemaVersion === 13) {
|
|
1500
|
-
return V13_STORES;
|
|
1501
|
-
}
|
|
1502
|
-
else if (schemaVersion === 12) {
|
|
1503
|
-
return V12_STORES;
|
|
1679
|
+
class PersistenceTransaction {
|
|
1680
|
+
constructor() {
|
|
1681
|
+
this.onCommittedListeners = [];
|
|
1504
1682
|
}
|
|
1505
|
-
|
|
1506
|
-
|
|
1683
|
+
addOnCommittedListener(listener) {
|
|
1684
|
+
this.onCommittedListeners.push(listener);
|
|
1507
1685
|
}
|
|
1508
|
-
|
|
1509
|
-
|
|
1686
|
+
raiseOnCommittedEvent() {
|
|
1687
|
+
this.onCommittedListeners.forEach(listener => listener());
|
|
1510
1688
|
}
|
|
1511
1689
|
}
|
|
1512
1690
|
|
|
1513
1691
|
/**
|
|
1514
1692
|
* @license
|
|
1515
|
-
* Copyright
|
|
1693
|
+
* Copyright 2017 Google LLC
|
|
1516
1694
|
*
|
|
1517
1695
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1518
1696
|
* you may not use this file except in compliance with the License.
|
|
@@ -1526,25 +1704,23 @@ function getObjectStores(schemaVersion) {
|
|
|
1526
1704
|
* See the License for the specific language governing permissions and
|
|
1527
1705
|
* limitations under the License.
|
|
1528
1706
|
*/
|
|
1529
|
-
const PRIMARY_LEASE_LOST_ERROR_MSG = 'The current tab is not in the required state to perform this operation. ' +
|
|
1530
|
-
'It might be necessary to refresh the browser tab.';
|
|
1531
1707
|
/**
|
|
1532
|
-
*
|
|
1533
|
-
*
|
|
1708
|
+
* Verifies the error thrown by a LocalStore operation. If a LocalStore
|
|
1709
|
+
* operation fails because the primary lease has been taken by another client,
|
|
1710
|
+
* we ignore the error (the persistence layer will immediately call
|
|
1711
|
+
* `applyPrimaryLease` to propagate the primary state change). All other errors
|
|
1712
|
+
* are re-thrown.
|
|
1534
1713
|
*
|
|
1535
|
-
*
|
|
1536
|
-
*
|
|
1537
|
-
* on persistence.
|
|
1714
|
+
* @param err - An error returned by a LocalStore operation.
|
|
1715
|
+
* @returns A Promise that resolves after we recovered, or the original error.
|
|
1538
1716
|
*/
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
addOnCommittedListener(listener) {
|
|
1544
|
-
this.onCommittedListeners.push(listener);
|
|
1717
|
+
async function ignoreIfPrimaryLeaseLoss(err) {
|
|
1718
|
+
if (err.code === Code.FAILED_PRECONDITION &&
|
|
1719
|
+
err.message === PRIMARY_LEASE_LOST_ERROR_MSG) {
|
|
1720
|
+
logDebug('LocalStore', 'Unexpectedly lost primary lease');
|
|
1545
1721
|
}
|
|
1546
|
-
|
|
1547
|
-
|
|
1722
|
+
else {
|
|
1723
|
+
throw err;
|
|
1548
1724
|
}
|
|
1549
1725
|
}
|
|
1550
1726
|
|
|
@@ -1729,6 +1905,47 @@ class PersistencePromise {
|
|
|
1729
1905
|
});
|
|
1730
1906
|
return this.waitFor(promises);
|
|
1731
1907
|
}
|
|
1908
|
+
/**
|
|
1909
|
+
* Concurrently map all array elements through asynchronous function.
|
|
1910
|
+
*/
|
|
1911
|
+
static mapArray(array, f) {
|
|
1912
|
+
return new PersistencePromise((resolve, reject) => {
|
|
1913
|
+
const expectedCount = array.length;
|
|
1914
|
+
const results = new Array(expectedCount);
|
|
1915
|
+
let resolvedCount = 0;
|
|
1916
|
+
for (let i = 0; i < expectedCount; i++) {
|
|
1917
|
+
const current = i;
|
|
1918
|
+
f(array[current]).next(result => {
|
|
1919
|
+
results[current] = result;
|
|
1920
|
+
++resolvedCount;
|
|
1921
|
+
if (resolvedCount === expectedCount) {
|
|
1922
|
+
resolve(results);
|
|
1923
|
+
}
|
|
1924
|
+
}, err => reject(err));
|
|
1925
|
+
}
|
|
1926
|
+
});
|
|
1927
|
+
}
|
|
1928
|
+
/**
|
|
1929
|
+
* An alternative to recursive PersistencePromise calls, that avoids
|
|
1930
|
+
* potential memory problems from unbounded chains of promises.
|
|
1931
|
+
*
|
|
1932
|
+
* The `action` will be called repeatedly while `condition` is true.
|
|
1933
|
+
*/
|
|
1934
|
+
static doWhile(condition, action) {
|
|
1935
|
+
return new PersistencePromise((resolve, reject) => {
|
|
1936
|
+
const process = () => {
|
|
1937
|
+
if (condition() === true) {
|
|
1938
|
+
action().next(() => {
|
|
1939
|
+
process();
|
|
1940
|
+
}, reject);
|
|
1941
|
+
}
|
|
1942
|
+
else {
|
|
1943
|
+
resolve();
|
|
1944
|
+
}
|
|
1945
|
+
};
|
|
1946
|
+
process();
|
|
1947
|
+
});
|
|
1948
|
+
}
|
|
1732
1949
|
}
|
|
1733
1950
|
|
|
1734
1951
|
/**
|
|
@@ -1749,7 +1966,7 @@ class PersistencePromise {
|
|
|
1749
1966
|
*/
|
|
1750
1967
|
// References to `window` are guarded by SimpleDb.isAvailable()
|
|
1751
1968
|
/* eslint-disable no-restricted-globals */
|
|
1752
|
-
const LOG_TAG$
|
|
1969
|
+
const LOG_TAG$i = 'SimpleDb';
|
|
1753
1970
|
/**
|
|
1754
1971
|
* The maximum number of retry attempts for an IndexedDb transaction that fails
|
|
1755
1972
|
* with a DOMException.
|
|
@@ -1800,7 +2017,7 @@ class SimpleDbTransaction {
|
|
|
1800
2017
|
this.completionDeferred.reject(error);
|
|
1801
2018
|
}
|
|
1802
2019
|
if (!this.aborted) {
|
|
1803
|
-
logDebug(LOG_TAG$
|
|
2020
|
+
logDebug(LOG_TAG$i, 'Aborting transaction:', error ? error.message : 'Client-initiated abort');
|
|
1804
2021
|
this.aborted = true;
|
|
1805
2022
|
this.transaction.abort();
|
|
1806
2023
|
}
|
|
@@ -1863,7 +2080,7 @@ class SimpleDb {
|
|
|
1863
2080
|
}
|
|
1864
2081
|
/** Deletes the specified database. */
|
|
1865
2082
|
static delete(name) {
|
|
1866
|
-
logDebug(LOG_TAG$
|
|
2083
|
+
logDebug(LOG_TAG$i, 'Removing database:', name);
|
|
1867
2084
|
return wrapRequest(window.indexedDB.deleteDatabase(name)).toPromise();
|
|
1868
2085
|
}
|
|
1869
2086
|
/** Returns true if IndexedDB is available in the current environment. */
|
|
@@ -1942,7 +2159,7 @@ class SimpleDb {
|
|
|
1942
2159
|
*/
|
|
1943
2160
|
async ensureDb(action) {
|
|
1944
2161
|
if (!this.db) {
|
|
1945
|
-
logDebug(LOG_TAG$
|
|
2162
|
+
logDebug(LOG_TAG$i, 'Opening database:', this.name);
|
|
1946
2163
|
this.db = await new Promise((resolve, reject) => {
|
|
1947
2164
|
// TODO(mikelehen): Investigate browser compatibility.
|
|
1948
2165
|
// https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB
|
|
@@ -1978,12 +2195,12 @@ class SimpleDb {
|
|
|
1978
2195
|
}
|
|
1979
2196
|
};
|
|
1980
2197
|
request.onupgradeneeded = (event) => {
|
|
1981
|
-
logDebug(LOG_TAG$
|
|
2198
|
+
logDebug(LOG_TAG$i, 'Database "' + this.name + '" requires upgrade from version:', event.oldVersion);
|
|
1982
2199
|
const db = event.target.result;
|
|
1983
2200
|
this.schemaConverter
|
|
1984
2201
|
.createOrUpgrade(db, request.transaction, event.oldVersion, this.version)
|
|
1985
2202
|
.next(() => {
|
|
1986
|
-
logDebug(LOG_TAG$
|
|
2203
|
+
logDebug(LOG_TAG$i, 'Database upgrade to version ' + this.version + ' complete');
|
|
1987
2204
|
});
|
|
1988
2205
|
};
|
|
1989
2206
|
});
|
|
@@ -2041,7 +2258,7 @@ class SimpleDb {
|
|
|
2041
2258
|
// exception is wrapped in a generic error by our async/await handling.
|
|
2042
2259
|
const retryable = error.name !== 'FirebaseError' &&
|
|
2043
2260
|
attemptNumber < TRANSACTION_RETRY_COUNT;
|
|
2044
|
-
logDebug(LOG_TAG$
|
|
2261
|
+
logDebug(LOG_TAG$i, 'Transaction failed with error:', error.message, 'Retrying:', retryable);
|
|
2045
2262
|
this.close();
|
|
2046
2263
|
if (!retryable) {
|
|
2047
2264
|
return Promise.reject(error);
|
|
@@ -2128,11 +2345,11 @@ class SimpleDbStore {
|
|
|
2128
2345
|
put(keyOrValue, value) {
|
|
2129
2346
|
let request;
|
|
2130
2347
|
if (value !== undefined) {
|
|
2131
|
-
logDebug(LOG_TAG$
|
|
2348
|
+
logDebug(LOG_TAG$i, 'PUT', this.store.name, keyOrValue, value);
|
|
2132
2349
|
request = this.store.put(value, keyOrValue);
|
|
2133
2350
|
}
|
|
2134
2351
|
else {
|
|
2135
|
-
logDebug(LOG_TAG$
|
|
2352
|
+
logDebug(LOG_TAG$i, 'PUT', this.store.name, '<auto-key>', keyOrValue);
|
|
2136
2353
|
request = this.store.put(keyOrValue);
|
|
2137
2354
|
}
|
|
2138
2355
|
return wrapRequest(request);
|
|
@@ -2145,7 +2362,7 @@ class SimpleDbStore {
|
|
|
2145
2362
|
* @returns The key of the value to add.
|
|
2146
2363
|
*/
|
|
2147
2364
|
add(value) {
|
|
2148
|
-
logDebug(LOG_TAG$
|
|
2365
|
+
logDebug(LOG_TAG$i, 'ADD', this.store.name, value, value);
|
|
2149
2366
|
const request = this.store.add(value);
|
|
2150
2367
|
return wrapRequest(request);
|
|
2151
2368
|
}
|
|
@@ -2165,12 +2382,12 @@ class SimpleDbStore {
|
|
|
2165
2382
|
if (result === undefined) {
|
|
2166
2383
|
result = null;
|
|
2167
2384
|
}
|
|
2168
|
-
logDebug(LOG_TAG$
|
|
2385
|
+
logDebug(LOG_TAG$i, 'GET', this.store.name, key, result);
|
|
2169
2386
|
return result;
|
|
2170
2387
|
});
|
|
2171
2388
|
}
|
|
2172
2389
|
delete(key) {
|
|
2173
|
-
logDebug(LOG_TAG$
|
|
2390
|
+
logDebug(LOG_TAG$i, 'DELETE', this.store.name, key);
|
|
2174
2391
|
const request = this.store.delete(key);
|
|
2175
2392
|
return wrapRequest(request);
|
|
2176
2393
|
}
|
|
@@ -2181,7 +2398,7 @@ class SimpleDbStore {
|
|
|
2181
2398
|
* Returns the number of rows in the store.
|
|
2182
2399
|
*/
|
|
2183
2400
|
count() {
|
|
2184
|
-
logDebug(LOG_TAG$
|
|
2401
|
+
logDebug(LOG_TAG$i, 'COUNT', this.store.name);
|
|
2185
2402
|
const request = this.store.count();
|
|
2186
2403
|
return wrapRequest(request);
|
|
2187
2404
|
}
|
|
@@ -2226,7 +2443,7 @@ class SimpleDbStore {
|
|
|
2226
2443
|
});
|
|
2227
2444
|
}
|
|
2228
2445
|
deleteAll(indexOrRange, range) {
|
|
2229
|
-
logDebug(LOG_TAG$
|
|
2446
|
+
logDebug(LOG_TAG$i, 'DELETE ALL', this.store.name);
|
|
2230
2447
|
const options = this.options(indexOrRange, range);
|
|
2231
2448
|
options.keysOnly = false;
|
|
2232
2449
|
const cursor = this.cursor(options);
|
|
@@ -2389,37 +2606,126 @@ function checkForAndReportiOSError(error) {
|
|
|
2389
2606
|
return error;
|
|
2390
2607
|
}
|
|
2391
2608
|
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2609
|
+
const LOG_TAG$h = 'IndexBackiller';
|
|
2610
|
+
/** Minimum amount of time between backfill checks, after the first one. */
|
|
2611
|
+
const REGULAR_BACKFILL_DELAY_MS = 1;
|
|
2612
|
+
/** The maximum number of documents to process each time backfill() is called. */
|
|
2613
|
+
const MAX_DOCUMENTS_TO_PROCESS = 50;
|
|
2614
|
+
/** This class is responsible for the scheduling of Index Backfiller. */
|
|
2615
|
+
class IndexBackfillerScheduler {
|
|
2616
|
+
constructor(asyncQueue, backfiller) {
|
|
2617
|
+
this.asyncQueue = asyncQueue;
|
|
2618
|
+
this.backfiller = backfiller;
|
|
2619
|
+
this.task = null;
|
|
2620
|
+
}
|
|
2621
|
+
start() {
|
|
2622
|
+
}
|
|
2623
|
+
stop() {
|
|
2624
|
+
if (this.task) {
|
|
2625
|
+
this.task.cancel();
|
|
2626
|
+
this.task = null;
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
get started() {
|
|
2630
|
+
return this.task !== null;
|
|
2631
|
+
}
|
|
2632
|
+
schedule(delay) {
|
|
2633
|
+
logDebug(LOG_TAG$h, `Scheduled in ${delay}ms`);
|
|
2634
|
+
this.task = this.asyncQueue.enqueueAfterDelay("index_backfill" /* IndexBackfill */, delay, async () => {
|
|
2635
|
+
this.task = null;
|
|
2636
|
+
try {
|
|
2637
|
+
const documentsProcessed = await this.backfiller.backfill();
|
|
2638
|
+
logDebug(LOG_TAG$h, `Documents written: ${documentsProcessed}`);
|
|
2639
|
+
}
|
|
2640
|
+
catch (e) {
|
|
2641
|
+
if (isIndexedDbTransactionError(e)) {
|
|
2642
|
+
logDebug(LOG_TAG$h, 'Ignoring IndexedDB error during index backfill: ', e);
|
|
2643
|
+
}
|
|
2644
|
+
else {
|
|
2645
|
+
await ignoreIfPrimaryLeaseLoss(e);
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
await this.schedule(REGULAR_BACKFILL_DELAY_MS);
|
|
2649
|
+
});
|
|
2413
2650
|
}
|
|
2414
2651
|
}
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2652
|
+
/** Implements the steps for backfilling indexes. */
|
|
2653
|
+
class IndexBackfiller {
|
|
2654
|
+
constructor(
|
|
2655
|
+
/**
|
|
2656
|
+
* LocalStore provides access to IndexManager and LocalDocumentView.
|
|
2657
|
+
* These properties will update when the user changes. Consequently,
|
|
2658
|
+
* making a local copy of IndexManager and LocalDocumentView will require
|
|
2659
|
+
* updates over time. The simpler solution is to rely on LocalStore to have
|
|
2660
|
+
* an up-to-date references to IndexManager and LocalDocumentStore.
|
|
2661
|
+
*/
|
|
2662
|
+
localStore, persistence) {
|
|
2663
|
+
this.localStore = localStore;
|
|
2664
|
+
this.persistence = persistence;
|
|
2665
|
+
}
|
|
2666
|
+
async backfill(maxDocumentsToProcess = MAX_DOCUMENTS_TO_PROCESS) {
|
|
2667
|
+
return this.persistence.runTransaction('Backfill Indexes', 'readwrite-primary', txn => this.writeIndexEntries(txn, maxDocumentsToProcess));
|
|
2668
|
+
}
|
|
2669
|
+
/** Writes index entries until the cap is reached. Returns the number of documents processed. */
|
|
2670
|
+
writeIndexEntries(transation, maxDocumentsToProcess) {
|
|
2671
|
+
const processedCollectionGroups = new Set();
|
|
2672
|
+
let documentsRemaining = maxDocumentsToProcess;
|
|
2673
|
+
let continueLoop = true;
|
|
2674
|
+
return PersistencePromise.doWhile(() => continueLoop === true && documentsRemaining > 0, () => {
|
|
2675
|
+
return this.localStore.indexManager
|
|
2676
|
+
.getNextCollectionGroupToUpdate(transation)
|
|
2677
|
+
.next((collectionGroup) => {
|
|
2678
|
+
if (collectionGroup === null ||
|
|
2679
|
+
processedCollectionGroups.has(collectionGroup)) {
|
|
2680
|
+
continueLoop = false;
|
|
2681
|
+
}
|
|
2682
|
+
else {
|
|
2683
|
+
logDebug(LOG_TAG$h, `Processing collection: ${collectionGroup}`);
|
|
2684
|
+
return this.writeEntriesForCollectionGroup(transation, collectionGroup, documentsRemaining).next(documentsProcessed => {
|
|
2685
|
+
documentsRemaining -= documentsProcessed;
|
|
2686
|
+
processedCollectionGroups.add(collectionGroup);
|
|
2687
|
+
});
|
|
2688
|
+
}
|
|
2689
|
+
});
|
|
2690
|
+
}).next(() => maxDocumentsToProcess - documentsRemaining);
|
|
2691
|
+
}
|
|
2692
|
+
/**
|
|
2693
|
+
* Writes entries for the provided collection group. Returns the number of documents processed.
|
|
2694
|
+
*/
|
|
2695
|
+
writeEntriesForCollectionGroup(transaction, collectionGroup, documentsRemainingUnderCap) {
|
|
2696
|
+
// Use the earliest offset of all field indexes to query the local cache.
|
|
2697
|
+
return this.localStore.indexManager
|
|
2698
|
+
.getMinOffsetFromCollectionGroup(transaction, collectionGroup)
|
|
2699
|
+
.next(existingOffset => this.localStore.localDocuments
|
|
2700
|
+
.getNextDocuments(transaction, collectionGroup, existingOffset, documentsRemainingUnderCap)
|
|
2701
|
+
.next(nextBatch => {
|
|
2702
|
+
const docs = nextBatch.changes;
|
|
2703
|
+
return this.localStore.indexManager
|
|
2704
|
+
.updateIndexEntries(transaction, docs)
|
|
2705
|
+
.next(() => this.getNewOffset(existingOffset, nextBatch))
|
|
2706
|
+
.next(newOffset => {
|
|
2707
|
+
logDebug(LOG_TAG$h, `Updating offset: ${newOffset}`);
|
|
2708
|
+
return this.localStore.indexManager.updateCollectionGroup(transaction, collectionGroup, newOffset);
|
|
2709
|
+
})
|
|
2710
|
+
.next(() => docs.size);
|
|
2711
|
+
}));
|
|
2712
|
+
}
|
|
2713
|
+
/** Returns the next offset based on the provided documents. */
|
|
2714
|
+
getNewOffset(existingOffset, lookupResult) {
|
|
2715
|
+
let maxOffset = existingOffset;
|
|
2716
|
+
lookupResult.changes.forEach((key, document) => {
|
|
2717
|
+
const newOffset = newIndexOffsetFromDocument(document);
|
|
2718
|
+
if (indexOffsetComparator(newOffset, maxOffset) > 0) {
|
|
2719
|
+
maxOffset = newOffset;
|
|
2720
|
+
}
|
|
2721
|
+
});
|
|
2722
|
+
return new IndexOffset(maxOffset.readTime, maxOffset.documentKey, Math.max(lookupResult.batchId, existingOffset.largestBatchId));
|
|
2723
|
+
}
|
|
2418
2724
|
}
|
|
2419
2725
|
|
|
2420
2726
|
/**
|
|
2421
2727
|
* @license
|
|
2422
|
-
* Copyright
|
|
2728
|
+
* Copyright 2018 Google LLC
|
|
2423
2729
|
*
|
|
2424
2730
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
2425
2731
|
* you may not use this file except in compliance with the License.
|
|
@@ -2434,13 +2740,32 @@ function getStore(txn, store) {
|
|
|
2434
2740
|
* limitations under the License.
|
|
2435
2741
|
*/
|
|
2436
2742
|
/**
|
|
2437
|
-
*
|
|
2438
|
-
*
|
|
2439
|
-
*
|
|
2743
|
+
* `ListenSequence` is a monotonic sequence. It is initialized with a minimum value to
|
|
2744
|
+
* exceed. All subsequent calls to next will return increasing values. If provided with a
|
|
2745
|
+
* `SequenceNumberSyncer`, it will additionally bump its next value when told of a new value, as
|
|
2746
|
+
* well as write out sequence numbers that it produces via `next()`.
|
|
2440
2747
|
*/
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2748
|
+
class ListenSequence {
|
|
2749
|
+
constructor(previousValue, sequenceNumberSyncer) {
|
|
2750
|
+
this.previousValue = previousValue;
|
|
2751
|
+
if (sequenceNumberSyncer) {
|
|
2752
|
+
sequenceNumberSyncer.sequenceNumberHandler = sequenceNumber => this.setPreviousValue(sequenceNumber);
|
|
2753
|
+
this.writeNewSequenceNumber = sequenceNumber => sequenceNumberSyncer.writeSequenceNumber(sequenceNumber);
|
|
2754
|
+
}
|
|
2755
|
+
}
|
|
2756
|
+
setPreviousValue(externalPreviousValue) {
|
|
2757
|
+
this.previousValue = Math.max(externalPreviousValue, this.previousValue);
|
|
2758
|
+
return this.previousValue;
|
|
2759
|
+
}
|
|
2760
|
+
next() {
|
|
2761
|
+
const nextValue = ++this.previousValue;
|
|
2762
|
+
if (this.writeNewSequenceNumber) {
|
|
2763
|
+
this.writeNewSequenceNumber(nextValue);
|
|
2764
|
+
}
|
|
2765
|
+
return nextValue;
|
|
2766
|
+
}
|
|
2767
|
+
}
|
|
2768
|
+
ListenSequence.INVALID = -1;
|
|
2444
2769
|
|
|
2445
2770
|
/**
|
|
2446
2771
|
* @license
|
|
@@ -2458,55 +2783,109 @@ function randomBytes(nBytes) {
|
|
|
2458
2783
|
* See the License for the specific language governing permissions and
|
|
2459
2784
|
* limitations under the License.
|
|
2460
2785
|
*/
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
// be evenly mapped to indices of `chars` via a modulo operation.
|
|
2474
|
-
if (autoId.length < targetLength && bytes[i] < maxMultiple) {
|
|
2475
|
-
autoId += chars.charAt(bytes[i] % chars.length);
|
|
2476
|
-
}
|
|
2477
|
-
}
|
|
2786
|
+
const escapeChar = '\u0001';
|
|
2787
|
+
const encodedSeparatorChar = '\u0001';
|
|
2788
|
+
const encodedNul = '\u0010';
|
|
2789
|
+
const encodedEscape = '\u0011';
|
|
2790
|
+
/**
|
|
2791
|
+
* Encodes a resource path into a IndexedDb-compatible string form.
|
|
2792
|
+
*/
|
|
2793
|
+
function encodeResourcePath(path) {
|
|
2794
|
+
let result = '';
|
|
2795
|
+
for (let i = 0; i < path.length; i++) {
|
|
2796
|
+
if (result.length > 0) {
|
|
2797
|
+
result = encodeSeparator(result);
|
|
2478
2798
|
}
|
|
2479
|
-
|
|
2799
|
+
result = encodeSegment(path.get(i), result);
|
|
2480
2800
|
}
|
|
2801
|
+
return encodeSeparator(result);
|
|
2481
2802
|
}
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2803
|
+
/** Encodes a single segment of a resource path into the given result */
|
|
2804
|
+
function encodeSegment(segment, resultBuf) {
|
|
2805
|
+
let result = resultBuf;
|
|
2806
|
+
const length = segment.length;
|
|
2807
|
+
for (let i = 0; i < length; i++) {
|
|
2808
|
+
const c = segment.charAt(i);
|
|
2809
|
+
switch (c) {
|
|
2810
|
+
case '\0':
|
|
2811
|
+
result += escapeChar + encodedNul;
|
|
2812
|
+
break;
|
|
2813
|
+
case escapeChar:
|
|
2814
|
+
result += escapeChar + encodedEscape;
|
|
2815
|
+
break;
|
|
2816
|
+
default:
|
|
2817
|
+
result += c;
|
|
2818
|
+
}
|
|
2488
2819
|
}
|
|
2489
|
-
return
|
|
2820
|
+
return result;
|
|
2490
2821
|
}
|
|
2491
|
-
/**
|
|
2492
|
-
function
|
|
2493
|
-
|
|
2494
|
-
return false;
|
|
2495
|
-
}
|
|
2496
|
-
return left.every((value, index) => comparator(value, right[index]));
|
|
2822
|
+
/** Encodes a path separator into the given result */
|
|
2823
|
+
function encodeSeparator(result) {
|
|
2824
|
+
return result + escapeChar + encodedSeparatorChar;
|
|
2497
2825
|
}
|
|
2498
2826
|
/**
|
|
2499
|
-
*
|
|
2500
|
-
*
|
|
2827
|
+
* Decodes the given IndexedDb-compatible string form of a resource path into
|
|
2828
|
+
* a ResourcePath instance. Note that this method is not suitable for use with
|
|
2829
|
+
* decoding resource names from the server; those are One Platform format
|
|
2830
|
+
* strings.
|
|
2501
2831
|
*/
|
|
2502
|
-
function
|
|
2503
|
-
//
|
|
2504
|
-
|
|
2832
|
+
function decodeResourcePath(path) {
|
|
2833
|
+
// Event the empty path must encode as a path of at least length 2. A path
|
|
2834
|
+
// with exactly 2 must be the empty path.
|
|
2835
|
+
const length = path.length;
|
|
2836
|
+
hardAssert(length >= 2);
|
|
2837
|
+
if (length === 2) {
|
|
2838
|
+
hardAssert(path.charAt(0) === escapeChar && path.charAt(1) === encodedSeparatorChar);
|
|
2839
|
+
return ResourcePath.emptyPath();
|
|
2840
|
+
}
|
|
2841
|
+
// Escape characters cannot exist past the second-to-last position in the
|
|
2842
|
+
// source value.
|
|
2843
|
+
const lastReasonableEscapeIndex = length - 2;
|
|
2844
|
+
const segments = [];
|
|
2845
|
+
let segmentBuilder = '';
|
|
2846
|
+
for (let start = 0; start < length;) {
|
|
2847
|
+
// The last two characters of a valid encoded path must be a separator, so
|
|
2848
|
+
// there must be an end to this segment.
|
|
2849
|
+
const end = path.indexOf(escapeChar, start);
|
|
2850
|
+
if (end < 0 || end > lastReasonableEscapeIndex) {
|
|
2851
|
+
fail();
|
|
2852
|
+
}
|
|
2853
|
+
const next = path.charAt(end + 1);
|
|
2854
|
+
switch (next) {
|
|
2855
|
+
case encodedSeparatorChar:
|
|
2856
|
+
const currentPiece = path.substring(start, end);
|
|
2857
|
+
let segment;
|
|
2858
|
+
if (segmentBuilder.length === 0) {
|
|
2859
|
+
// Avoid copying for the common case of a segment that excludes \0
|
|
2860
|
+
// and \001
|
|
2861
|
+
segment = currentPiece;
|
|
2862
|
+
}
|
|
2863
|
+
else {
|
|
2864
|
+
segmentBuilder += currentPiece;
|
|
2865
|
+
segment = segmentBuilder;
|
|
2866
|
+
segmentBuilder = '';
|
|
2867
|
+
}
|
|
2868
|
+
segments.push(segment);
|
|
2869
|
+
break;
|
|
2870
|
+
case encodedNul:
|
|
2871
|
+
segmentBuilder += path.substring(start, end);
|
|
2872
|
+
segmentBuilder += '\0';
|
|
2873
|
+
break;
|
|
2874
|
+
case encodedEscape:
|
|
2875
|
+
// The escape character can be used in the output to encode itself.
|
|
2876
|
+
segmentBuilder += path.substring(start, end + 1);
|
|
2877
|
+
break;
|
|
2878
|
+
default:
|
|
2879
|
+
fail();
|
|
2880
|
+
}
|
|
2881
|
+
start = end + 2;
|
|
2882
|
+
}
|
|
2883
|
+
return new ResourcePath(segments);
|
|
2505
2884
|
}
|
|
2506
2885
|
|
|
2507
2886
|
/**
|
|
2508
2887
|
* @license
|
|
2509
|
-
* Copyright
|
|
2888
|
+
* Copyright 2022 Google LLC
|
|
2510
2889
|
*
|
|
2511
2890
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
2512
2891
|
* you may not use this file except in compliance with the License.
|
|
@@ -2520,164 +2899,278 @@ function immediateSuccessor(s) {
|
|
|
2520
2899
|
* See the License for the specific language governing permissions and
|
|
2521
2900
|
* limitations under the License.
|
|
2522
2901
|
*/
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
// Number of nanoseconds in a millisecond.
|
|
2526
|
-
const MS_TO_NANOS = 1e6;
|
|
2902
|
+
const DbRemoteDocumentStore$1 = 'remoteDocuments';
|
|
2903
|
+
|
|
2527
2904
|
/**
|
|
2528
|
-
*
|
|
2529
|
-
*
|
|
2530
|
-
* resolution in UTC Epoch time.
|
|
2905
|
+
* @license
|
|
2906
|
+
* Copyright 2022 Google LLC
|
|
2531
2907
|
*
|
|
2532
|
-
*
|
|
2533
|
-
*
|
|
2534
|
-
*
|
|
2535
|
-
* table is needed for interpretation. Range is from 0001-01-01T00:00:00Z to
|
|
2536
|
-
* 9999-12-31T23:59:59.999999999Z.
|
|
2908
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
2909
|
+
* you may not use this file except in compliance with the License.
|
|
2910
|
+
* You may obtain a copy of the License at
|
|
2537
2911
|
*
|
|
2538
|
-
*
|
|
2539
|
-
*
|
|
2912
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
2913
|
+
*
|
|
2914
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
2915
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
2916
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
2917
|
+
* See the License for the specific language governing permissions and
|
|
2918
|
+
* limitations under the License.
|
|
2540
2919
|
*/
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
|
|
2920
|
+
/**
|
|
2921
|
+
* Name of the IndexedDb object store.
|
|
2922
|
+
*
|
|
2923
|
+
* Note that the name 'owner' is chosen to ensure backwards compatibility with
|
|
2924
|
+
* older clients that only supported single locked access to the persistence
|
|
2925
|
+
* layer.
|
|
2926
|
+
*/
|
|
2927
|
+
const DbPrimaryClientStore = 'owner';
|
|
2928
|
+
/**
|
|
2929
|
+
* The key string used for the single object that exists in the
|
|
2930
|
+
* DbPrimaryClient store.
|
|
2931
|
+
*/
|
|
2932
|
+
const DbPrimaryClientKey = 'owner';
|
|
2933
|
+
/** Name of the IndexedDb object store. */
|
|
2934
|
+
const DbMutationQueueStore = 'mutationQueues';
|
|
2935
|
+
/** Keys are automatically assigned via the userId property. */
|
|
2936
|
+
const DbMutationQueueKeyPath = 'userId';
|
|
2937
|
+
/** Name of the IndexedDb object store. */
|
|
2938
|
+
const DbMutationBatchStore = 'mutations';
|
|
2939
|
+
/** Keys are automatically assigned via the userId, batchId properties. */
|
|
2940
|
+
const DbMutationBatchKeyPath = 'batchId';
|
|
2941
|
+
/** The index name for lookup of mutations by user. */
|
|
2942
|
+
const DbMutationBatchUserMutationsIndex = 'userMutationsIndex';
|
|
2943
|
+
/** The user mutations index is keyed by [userId, batchId] pairs. */
|
|
2944
|
+
const DbMutationBatchUserMutationsKeyPath = ['userId', 'batchId'];
|
|
2945
|
+
/**
|
|
2946
|
+
* Creates a [userId] key for use in the DbDocumentMutations index to iterate
|
|
2947
|
+
* over all of a user's document mutations.
|
|
2948
|
+
*/
|
|
2949
|
+
function newDbDocumentMutationPrefixForUser(userId) {
|
|
2950
|
+
return [userId];
|
|
2951
|
+
}
|
|
2952
|
+
/**
|
|
2953
|
+
* Creates a [userId, encodedPath] key for use in the DbDocumentMutations
|
|
2954
|
+
* index to iterate over all at document mutations for a given path or lower.
|
|
2955
|
+
*/
|
|
2956
|
+
function newDbDocumentMutationPrefixForPath(userId, path) {
|
|
2957
|
+
return [userId, encodeResourcePath(path)];
|
|
2958
|
+
}
|
|
2959
|
+
/**
|
|
2960
|
+
* Creates a full index key of [userId, encodedPath, batchId] for inserting
|
|
2961
|
+
* and deleting into the DbDocumentMutations index.
|
|
2962
|
+
*/
|
|
2963
|
+
function newDbDocumentMutationKey(userId, path, batchId) {
|
|
2964
|
+
return [userId, encodeResourcePath(path), batchId];
|
|
2965
|
+
}
|
|
2966
|
+
/**
|
|
2967
|
+
* Because we store all the useful information for this store in the key,
|
|
2968
|
+
* there is no useful information to store as the value. The raw (unencoded)
|
|
2969
|
+
* path cannot be stored because IndexedDb doesn't store prototype
|
|
2970
|
+
* information.
|
|
2971
|
+
*/
|
|
2972
|
+
const DbDocumentMutationPlaceholder = {};
|
|
2973
|
+
const DbDocumentMutationStore = 'documentMutations';
|
|
2974
|
+
const DbRemoteDocumentStore = 'remoteDocumentsV14';
|
|
2975
|
+
/**
|
|
2976
|
+
* The primary key of the remote documents store, which allows for efficient
|
|
2977
|
+
* access by collection path and read time.
|
|
2978
|
+
*/
|
|
2979
|
+
const DbRemoteDocumentKeyPath = [
|
|
2980
|
+
'prefixPath',
|
|
2981
|
+
'collectionGroup',
|
|
2982
|
+
'readTime',
|
|
2983
|
+
'documentId'
|
|
2984
|
+
];
|
|
2985
|
+
/** An index that provides access to documents by key. */
|
|
2986
|
+
const DbRemoteDocumentDocumentKeyIndex = 'documentKeyIndex';
|
|
2987
|
+
const DbRemoteDocumentDocumentKeyIndexPath = [
|
|
2988
|
+
'prefixPath',
|
|
2989
|
+
'collectionGroup',
|
|
2990
|
+
'documentId'
|
|
2991
|
+
];
|
|
2992
|
+
/**
|
|
2993
|
+
* An index that provides access to documents by collection group and read
|
|
2994
|
+
* time.
|
|
2995
|
+
*
|
|
2996
|
+
* This index is used by the index backfiller.
|
|
2997
|
+
*/
|
|
2998
|
+
const DbRemoteDocumentCollectionGroupIndex = 'collectionGroupIndex';
|
|
2999
|
+
const DbRemoteDocumentCollectionGroupIndexPath = [
|
|
3000
|
+
'collectionGroup',
|
|
3001
|
+
'readTime',
|
|
3002
|
+
'prefixPath',
|
|
3003
|
+
'documentId'
|
|
3004
|
+
];
|
|
3005
|
+
const DbRemoteDocumentGlobalStore = 'remoteDocumentGlobal';
|
|
3006
|
+
const DbRemoteDocumentGlobalKey = 'remoteDocumentGlobalKey';
|
|
3007
|
+
const DbTargetStore = 'targets';
|
|
3008
|
+
/** Keys are automatically assigned via the targetId property. */
|
|
3009
|
+
const DbTargetKeyPath = 'targetId';
|
|
3010
|
+
/** The name of the queryTargets index. */
|
|
3011
|
+
const DbTargetQueryTargetsIndexName = 'queryTargetsIndex';
|
|
3012
|
+
/**
|
|
3013
|
+
* The index of all canonicalIds to the targets that they match. This is not
|
|
3014
|
+
* a unique mapping because canonicalId does not promise a unique name for all
|
|
3015
|
+
* possible queries, so we append the targetId to make the mapping unique.
|
|
3016
|
+
*/
|
|
3017
|
+
const DbTargetQueryTargetsKeyPath = ['canonicalId', 'targetId'];
|
|
3018
|
+
/** Name of the IndexedDb object store. */
|
|
3019
|
+
const DbTargetDocumentStore = 'targetDocuments';
|
|
3020
|
+
/** Keys are automatically assigned via the targetId, path properties. */
|
|
3021
|
+
const DbTargetDocumentKeyPath = ['targetId', 'path'];
|
|
3022
|
+
/** The index name for the reverse index. */
|
|
3023
|
+
const DbTargetDocumentDocumentTargetsIndex = 'documentTargetsIndex';
|
|
3024
|
+
/** We also need to create the reverse index for these properties. */
|
|
3025
|
+
const DbTargetDocumentDocumentTargetsKeyPath = ['path', 'targetId'];
|
|
3026
|
+
/**
|
|
3027
|
+
* The key string used for the single object that exists in the
|
|
3028
|
+
* DbTargetGlobal store.
|
|
3029
|
+
*/
|
|
3030
|
+
const DbTargetGlobalKey = 'targetGlobalKey';
|
|
3031
|
+
const DbTargetGlobalStore = 'targetGlobal';
|
|
3032
|
+
/** Name of the IndexedDb object store. */
|
|
3033
|
+
const DbCollectionParentStore = 'collectionParents';
|
|
3034
|
+
/** Keys are automatically assigned via the collectionId, parent properties. */
|
|
3035
|
+
const DbCollectionParentKeyPath = ['collectionId', 'parent'];
|
|
3036
|
+
/** Name of the IndexedDb object store. */
|
|
3037
|
+
const DbClientMetadataStore = 'clientMetadata';
|
|
3038
|
+
/** Keys are automatically assigned via the clientId properties. */
|
|
3039
|
+
const DbClientMetadataKeyPath = 'clientId';
|
|
3040
|
+
/** Name of the IndexedDb object store. */
|
|
3041
|
+
const DbBundleStore = 'bundles';
|
|
3042
|
+
const DbBundleKeyPath = 'bundleId';
|
|
3043
|
+
/** Name of the IndexedDb object store. */
|
|
3044
|
+
const DbNamedQueryStore = 'namedQueries';
|
|
3045
|
+
const DbNamedQueryKeyPath = 'name';
|
|
3046
|
+
/** Name of the IndexedDb object store. */
|
|
3047
|
+
const DbIndexConfigurationStore = 'indexConfiguration';
|
|
3048
|
+
const DbIndexConfigurationKeyPath = 'indexId';
|
|
3049
|
+
/**
|
|
3050
|
+
* An index that provides access to the index configurations by collection
|
|
3051
|
+
* group.
|
|
3052
|
+
*
|
|
3053
|
+
* PORTING NOTE: iOS and Android maintain this index in-memory, but this is
|
|
3054
|
+
* not possible here as the Web client supports concurrent access to
|
|
3055
|
+
* persistence via multi-tab.
|
|
3056
|
+
*/
|
|
3057
|
+
const DbIndexConfigurationCollectionGroupIndex = 'collectionGroupIndex';
|
|
3058
|
+
const DbIndexConfigurationCollectionGroupIndexPath = 'collectionGroup';
|
|
3059
|
+
/** Name of the IndexedDb object store. */
|
|
3060
|
+
const DbIndexStateStore = 'indexState';
|
|
3061
|
+
const DbIndexStateKeyPath = ['indexId', 'uid'];
|
|
3062
|
+
/**
|
|
3063
|
+
* An index that provides access to documents in a collection sorted by last
|
|
3064
|
+
* update time. Used by the backfiller.
|
|
3065
|
+
*
|
|
3066
|
+
* PORTING NOTE: iOS and Android maintain this index in-memory, but this is
|
|
3067
|
+
* not possible here as the Web client supports concurrent access to
|
|
3068
|
+
* persistence via multi-tab.
|
|
3069
|
+
*/
|
|
3070
|
+
const DbIndexStateSequenceNumberIndex = 'sequenceNumberIndex';
|
|
3071
|
+
const DbIndexStateSequenceNumberIndexPath = ['uid', 'sequenceNumber'];
|
|
3072
|
+
/** Name of the IndexedDb object store. */
|
|
3073
|
+
const DbIndexEntryStore = 'indexEntries';
|
|
3074
|
+
const DbIndexEntryKeyPath = [
|
|
3075
|
+
'indexId',
|
|
3076
|
+
'uid',
|
|
3077
|
+
'arrayValue',
|
|
3078
|
+
'directionalValue',
|
|
3079
|
+
'orderedDocumentKey',
|
|
3080
|
+
'documentKey'
|
|
3081
|
+
];
|
|
3082
|
+
const DbIndexEntryDocumentKeyIndex = 'documentKeyIndex';
|
|
3083
|
+
const DbIndexEntryDocumentKeyIndexPath = [
|
|
3084
|
+
'indexId',
|
|
3085
|
+
'uid',
|
|
3086
|
+
'orderedDocumentKey'
|
|
3087
|
+
];
|
|
3088
|
+
/** Name of the IndexedDb object store. */
|
|
3089
|
+
const DbDocumentOverlayStore = 'documentOverlays';
|
|
3090
|
+
const DbDocumentOverlayKeyPath = [
|
|
3091
|
+
'userId',
|
|
3092
|
+
'collectionPath',
|
|
3093
|
+
'documentId'
|
|
3094
|
+
];
|
|
3095
|
+
const DbDocumentOverlayCollectionPathOverlayIndex = 'collectionPathOverlayIndex';
|
|
3096
|
+
const DbDocumentOverlayCollectionPathOverlayIndexPath = [
|
|
3097
|
+
'userId',
|
|
3098
|
+
'collectionPath',
|
|
3099
|
+
'largestBatchId'
|
|
3100
|
+
];
|
|
3101
|
+
const DbDocumentOverlayCollectionGroupOverlayIndex = 'collectionGroupOverlayIndex';
|
|
3102
|
+
const DbDocumentOverlayCollectionGroupOverlayIndexPath = [
|
|
3103
|
+
'userId',
|
|
3104
|
+
'collectionGroup',
|
|
3105
|
+
'largestBatchId'
|
|
3106
|
+
];
|
|
3107
|
+
// Visible for testing
|
|
3108
|
+
const V1_STORES = [
|
|
3109
|
+
DbMutationQueueStore,
|
|
3110
|
+
DbMutationBatchStore,
|
|
3111
|
+
DbDocumentMutationStore,
|
|
3112
|
+
DbRemoteDocumentStore$1,
|
|
3113
|
+
DbTargetStore,
|
|
3114
|
+
DbPrimaryClientStore,
|
|
3115
|
+
DbTargetGlobalStore,
|
|
3116
|
+
DbTargetDocumentStore
|
|
3117
|
+
];
|
|
3118
|
+
// Visible for testing
|
|
3119
|
+
const V3_STORES = V1_STORES;
|
|
3120
|
+
// Note: DbRemoteDocumentChanges is no longer used and dropped with v9.
|
|
3121
|
+
const V4_STORES = [...V3_STORES, DbClientMetadataStore];
|
|
3122
|
+
const V6_STORES = [...V4_STORES, DbRemoteDocumentGlobalStore];
|
|
3123
|
+
const V8_STORES = [...V6_STORES, DbCollectionParentStore];
|
|
3124
|
+
const V11_STORES = [...V8_STORES, DbBundleStore, DbNamedQueryStore];
|
|
3125
|
+
const V12_STORES = [...V11_STORES, DbDocumentOverlayStore];
|
|
3126
|
+
const V13_STORES = [
|
|
3127
|
+
DbMutationQueueStore,
|
|
3128
|
+
DbMutationBatchStore,
|
|
3129
|
+
DbDocumentMutationStore,
|
|
3130
|
+
DbRemoteDocumentStore,
|
|
3131
|
+
DbTargetStore,
|
|
3132
|
+
DbPrimaryClientStore,
|
|
3133
|
+
DbTargetGlobalStore,
|
|
3134
|
+
DbTargetDocumentStore,
|
|
3135
|
+
DbClientMetadataStore,
|
|
3136
|
+
DbRemoteDocumentGlobalStore,
|
|
3137
|
+
DbCollectionParentStore,
|
|
3138
|
+
DbBundleStore,
|
|
3139
|
+
DbNamedQueryStore,
|
|
3140
|
+
DbDocumentOverlayStore
|
|
3141
|
+
];
|
|
3142
|
+
const V14_STORES = V13_STORES;
|
|
3143
|
+
const V15_STORES = [
|
|
3144
|
+
...V14_STORES,
|
|
3145
|
+
DbIndexConfigurationStore,
|
|
3146
|
+
DbIndexStateStore,
|
|
3147
|
+
DbIndexEntryStore
|
|
3148
|
+
];
|
|
3149
|
+
/** Returns the object stores for the provided schema. */
|
|
3150
|
+
function getObjectStores(schemaVersion) {
|
|
3151
|
+
if (schemaVersion === 15) {
|
|
3152
|
+
return V15_STORES;
|
|
2629
3153
|
}
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
return primitiveComparator(this.nanoseconds, other.nanoseconds);
|
|
2633
|
-
}
|
|
2634
|
-
return primitiveComparator(this.seconds, other.seconds);
|
|
3154
|
+
else if (schemaVersion === 14) {
|
|
3155
|
+
return V14_STORES;
|
|
2635
3156
|
}
|
|
2636
|
-
|
|
2637
|
-
|
|
2638
|
-
*
|
|
2639
|
-
* @param other - The `Timestamp` to compare against.
|
|
2640
|
-
* @returns true if this `Timestamp` is equal to the provided one.
|
|
2641
|
-
*/
|
|
2642
|
-
isEqual(other) {
|
|
2643
|
-
return (other.seconds === this.seconds && other.nanoseconds === this.nanoseconds);
|
|
3157
|
+
else if (schemaVersion === 13) {
|
|
3158
|
+
return V13_STORES;
|
|
2644
3159
|
}
|
|
2645
|
-
|
|
2646
|
-
|
|
2647
|
-
return ('Timestamp(seconds=' +
|
|
2648
|
-
this.seconds +
|
|
2649
|
-
', nanoseconds=' +
|
|
2650
|
-
this.nanoseconds +
|
|
2651
|
-
')');
|
|
3160
|
+
else if (schemaVersion === 12) {
|
|
3161
|
+
return V12_STORES;
|
|
2652
3162
|
}
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
return { seconds: this.seconds, nanoseconds: this.nanoseconds };
|
|
3163
|
+
else if (schemaVersion === 11) {
|
|
3164
|
+
return V11_STORES;
|
|
2656
3165
|
}
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
* to be compared using the `>`, `<=`, `>=` and `>` operators.
|
|
2660
|
-
*/
|
|
2661
|
-
valueOf() {
|
|
2662
|
-
// This method returns a string of the form <seconds>.<nanoseconds> where
|
|
2663
|
-
// <seconds> is translated to have a non-negative value and both <seconds>
|
|
2664
|
-
// and <nanoseconds> are left-padded with zeroes to be a consistent length.
|
|
2665
|
-
// Strings with this format then have a lexiographical ordering that matches
|
|
2666
|
-
// the expected ordering. The <seconds> translation is done to avoid having
|
|
2667
|
-
// a leading negative sign (i.e. a leading '-' character) in its string
|
|
2668
|
-
// representation, which would affect its lexiographical ordering.
|
|
2669
|
-
const adjustedSeconds = this.seconds - MIN_SECONDS;
|
|
2670
|
-
// Note: Up to 12 decimal digits are required to represent all valid
|
|
2671
|
-
// 'seconds' values.
|
|
2672
|
-
const formattedSeconds = String(adjustedSeconds).padStart(12, '0');
|
|
2673
|
-
const formattedNanoseconds = String(this.nanoseconds).padStart(9, '0');
|
|
2674
|
-
return formattedSeconds + '.' + formattedNanoseconds;
|
|
3166
|
+
else {
|
|
3167
|
+
fail();
|
|
2675
3168
|
}
|
|
2676
3169
|
}
|
|
2677
3170
|
|
|
2678
3171
|
/**
|
|
2679
3172
|
* @license
|
|
2680
|
-
* Copyright
|
|
3173
|
+
* Copyright 2020 Google LLC
|
|
2681
3174
|
*
|
|
2682
3175
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
2683
3176
|
* you may not use this file except in compliance with the License.
|
|
@@ -2691,40 +3184,16 @@ class Timestamp {
|
|
|
2691
3184
|
* See the License for the specific language governing permissions and
|
|
2692
3185
|
* limitations under the License.
|
|
2693
3186
|
*/
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
constructor(timestamp) {
|
|
2700
|
-
this.timestamp = timestamp;
|
|
2701
|
-
}
|
|
2702
|
-
static fromTimestamp(value) {
|
|
2703
|
-
return new SnapshotVersion(value);
|
|
2704
|
-
}
|
|
2705
|
-
static min() {
|
|
2706
|
-
return new SnapshotVersion(new Timestamp(0, 0));
|
|
2707
|
-
}
|
|
2708
|
-
static max() {
|
|
2709
|
-
return new SnapshotVersion(new Timestamp(253402300799, 1e9 - 1));
|
|
2710
|
-
}
|
|
2711
|
-
compareTo(other) {
|
|
2712
|
-
return this.timestamp._compareTo(other.timestamp);
|
|
2713
|
-
}
|
|
2714
|
-
isEqual(other) {
|
|
2715
|
-
return this.timestamp.isEqual(other.timestamp);
|
|
2716
|
-
}
|
|
2717
|
-
/** Returns a number representation of the version for use in spec tests. */
|
|
2718
|
-
toMicroseconds() {
|
|
2719
|
-
// Convert to microseconds.
|
|
2720
|
-
return this.timestamp.seconds * 1e6 + this.timestamp.nanoseconds / 1000;
|
|
2721
|
-
}
|
|
2722
|
-
toString() {
|
|
2723
|
-
return 'SnapshotVersion(' + this.timestamp.toString() + ')';
|
|
2724
|
-
}
|
|
2725
|
-
toTimestamp() {
|
|
2726
|
-
return this.timestamp;
|
|
3187
|
+
class IndexedDbTransaction extends PersistenceTransaction {
|
|
3188
|
+
constructor(simpleDbTransaction, currentSequenceNumber) {
|
|
3189
|
+
super();
|
|
3190
|
+
this.simpleDbTransaction = simpleDbTransaction;
|
|
3191
|
+
this.currentSequenceNumber = currentSequenceNumber;
|
|
2727
3192
|
}
|
|
3193
|
+
}
|
|
3194
|
+
function getStore(txn, store) {
|
|
3195
|
+
const indexedDbTransaction = debugCast(txn);
|
|
3196
|
+
return SimpleDb.getStore(indexedDbTransaction.simpleDbTransaction, store);
|
|
2728
3197
|
}
|
|
2729
3198
|
|
|
2730
3199
|
/**
|
|
@@ -3893,104 +4362,33 @@ class DatabaseId {
|
|
|
3893
4362
|
* Unless required by applicable law or agreed to in writing, software
|
|
3894
4363
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
3895
4364
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
3896
|
-
* See the License for the specific language governing permissions and
|
|
3897
|
-
* limitations under the License.
|
|
3898
|
-
*/
|
|
3899
|
-
/** Sentinel value that sorts before any Mutation Batch ID. */
|
|
3900
|
-
const BATCHID_UNKNOWN = -1;
|
|
3901
|
-
/**
|
|
3902
|
-
* Returns whether a variable is either undefined or null.
|
|
3903
|
-
*/
|
|
3904
|
-
function isNullOrUndefined(value) {
|
|
3905
|
-
return value === null || value === undefined;
|
|
3906
|
-
}
|
|
3907
|
-
/** Returns whether the value represents -0. */
|
|
3908
|
-
function isNegativeZero(value) {
|
|
3909
|
-
// Detect if the value is -0.0. Based on polyfill from
|
|
3910
|
-
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
|
|
3911
|
-
return value === 0 && 1 / value === 1 / -0;
|
|
3912
|
-
}
|
|
3913
|
-
/**
|
|
3914
|
-
* Returns whether a value is an integer and in the safe integer range
|
|
3915
|
-
* @param value - The value to test for being an integer and in the safe range
|
|
3916
|
-
*/
|
|
3917
|
-
function isSafeInteger(value) {
|
|
3918
|
-
return (typeof value === 'number' &&
|
|
3919
|
-
Number.isInteger(value) &&
|
|
3920
|
-
!isNegativeZero(value) &&
|
|
3921
|
-
value <= Number.MAX_SAFE_INTEGER &&
|
|
3922
|
-
value >= Number.MIN_SAFE_INTEGER);
|
|
3923
|
-
}
|
|
3924
|
-
|
|
3925
|
-
/**
|
|
3926
|
-
* @license
|
|
3927
|
-
* Copyright 2017 Google LLC
|
|
3928
|
-
*
|
|
3929
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
3930
|
-
* you may not use this file except in compliance with the License.
|
|
3931
|
-
* You may obtain a copy of the License at
|
|
3932
|
-
*
|
|
3933
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
3934
|
-
*
|
|
3935
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
3936
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
3937
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
3938
|
-
* See the License for the specific language governing permissions and
|
|
3939
|
-
* limitations under the License.
|
|
3940
|
-
*/
|
|
3941
|
-
/**
|
|
3942
|
-
* @internal
|
|
3943
|
-
*/
|
|
3944
|
-
class DocumentKey {
|
|
3945
|
-
constructor(path) {
|
|
3946
|
-
this.path = path;
|
|
3947
|
-
}
|
|
3948
|
-
static fromPath(path) {
|
|
3949
|
-
return new DocumentKey(ResourcePath.fromString(path));
|
|
3950
|
-
}
|
|
3951
|
-
static fromName(name) {
|
|
3952
|
-
return new DocumentKey(ResourcePath.fromString(name).popFirst(5));
|
|
3953
|
-
}
|
|
3954
|
-
static empty() {
|
|
3955
|
-
return new DocumentKey(ResourcePath.emptyPath());
|
|
3956
|
-
}
|
|
3957
|
-
get collectionGroup() {
|
|
3958
|
-
return this.path.popLast().lastSegment();
|
|
3959
|
-
}
|
|
3960
|
-
/** Returns true if the document is in the specified collectionId. */
|
|
3961
|
-
hasCollectionId(collectionId) {
|
|
3962
|
-
return (this.path.length >= 2 &&
|
|
3963
|
-
this.path.get(this.path.length - 2) === collectionId);
|
|
3964
|
-
}
|
|
3965
|
-
/** Returns the collection group (i.e. the name of the parent collection) for this key. */
|
|
3966
|
-
getCollectionGroup() {
|
|
3967
|
-
return this.path.get(this.path.length - 2);
|
|
3968
|
-
}
|
|
3969
|
-
/** Returns the fully qualified path to the parent collection. */
|
|
3970
|
-
getCollectionPath() {
|
|
3971
|
-
return this.path.popLast();
|
|
3972
|
-
}
|
|
3973
|
-
isEqual(other) {
|
|
3974
|
-
return (other !== null && ResourcePath.comparator(this.path, other.path) === 0);
|
|
3975
|
-
}
|
|
3976
|
-
toString() {
|
|
3977
|
-
return this.path.toString();
|
|
3978
|
-
}
|
|
3979
|
-
static comparator(k1, k2) {
|
|
3980
|
-
return ResourcePath.comparator(k1.path, k2.path);
|
|
3981
|
-
}
|
|
3982
|
-
static isDocumentKey(path) {
|
|
3983
|
-
return path.length % 2 === 0;
|
|
3984
|
-
}
|
|
3985
|
-
/**
|
|
3986
|
-
* Creates and returns a new document key with the given segments.
|
|
3987
|
-
*
|
|
3988
|
-
* @param segments - The segments of the path to the document
|
|
3989
|
-
* @returns A new instance of DocumentKey
|
|
3990
|
-
*/
|
|
3991
|
-
static fromSegments(segments) {
|
|
3992
|
-
return new DocumentKey(new ResourcePath(segments.slice()));
|
|
3993
|
-
}
|
|
4365
|
+
* See the License for the specific language governing permissions and
|
|
4366
|
+
* limitations under the License.
|
|
4367
|
+
*/
|
|
4368
|
+
/** Sentinel value that sorts before any Mutation Batch ID. */
|
|
4369
|
+
const BATCHID_UNKNOWN = -1;
|
|
4370
|
+
/**
|
|
4371
|
+
* Returns whether a variable is either undefined or null.
|
|
4372
|
+
*/
|
|
4373
|
+
function isNullOrUndefined(value) {
|
|
4374
|
+
return value === null || value === undefined;
|
|
4375
|
+
}
|
|
4376
|
+
/** Returns whether the value represents -0. */
|
|
4377
|
+
function isNegativeZero(value) {
|
|
4378
|
+
// Detect if the value is -0.0. Based on polyfill from
|
|
4379
|
+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
|
|
4380
|
+
return value === 0 && 1 / value === 1 / -0;
|
|
4381
|
+
}
|
|
4382
|
+
/**
|
|
4383
|
+
* Returns whether a value is an integer and in the safe integer range
|
|
4384
|
+
* @param value - The value to test for being an integer and in the safe range
|
|
4385
|
+
*/
|
|
4386
|
+
function isSafeInteger(value) {
|
|
4387
|
+
return (typeof value === 'number' &&
|
|
4388
|
+
Number.isInteger(value) &&
|
|
4389
|
+
!isNegativeZero(value) &&
|
|
4390
|
+
value <= Number.MAX_SAFE_INTEGER &&
|
|
4391
|
+
value >= Number.MIN_SAFE_INTEGER);
|
|
3994
4392
|
}
|
|
3995
4393
|
|
|
3996
4394
|
/**
|
|
@@ -4611,274 +5009,113 @@ class ObjectValue {
|
|
|
4611
5009
|
/**
|
|
4612
5010
|
* Sets the provided fields to the provided values.
|
|
4613
5011
|
*
|
|
4614
|
-
* @param data - A map of fields to values (or null for deletes).
|
|
4615
|
-
*/
|
|
4616
|
-
setAll(data) {
|
|
4617
|
-
let parent = FieldPath$1.emptyPath();
|
|
4618
|
-
let upserts = {};
|
|
4619
|
-
let deletes = [];
|
|
4620
|
-
data.forEach((value, path) => {
|
|
4621
|
-
if (!parent.isImmediateParentOf(path)) {
|
|
4622
|
-
// Insert the accumulated changes at this parent location
|
|
4623
|
-
const fieldsMap = this.getFieldsMap(parent);
|
|
4624
|
-
this.applyChanges(fieldsMap, upserts, deletes);
|
|
4625
|
-
upserts = {};
|
|
4626
|
-
deletes = [];
|
|
4627
|
-
parent = path.popLast();
|
|
4628
|
-
}
|
|
4629
|
-
if (value) {
|
|
4630
|
-
upserts[path.lastSegment()] = deepClone(value);
|
|
4631
|
-
}
|
|
4632
|
-
else {
|
|
4633
|
-
deletes.push(path.lastSegment());
|
|
4634
|
-
}
|
|
4635
|
-
});
|
|
4636
|
-
const fieldsMap = this.getFieldsMap(parent);
|
|
4637
|
-
this.applyChanges(fieldsMap, upserts, deletes);
|
|
4638
|
-
}
|
|
4639
|
-
/**
|
|
4640
|
-
* Removes the field at the specified path. If there is no field at the
|
|
4641
|
-
* specified path, nothing is changed.
|
|
4642
|
-
*
|
|
4643
|
-
* @param path - The field path to remove.
|
|
4644
|
-
*/
|
|
4645
|
-
delete(path) {
|
|
4646
|
-
const nestedValue = this.field(path.popLast());
|
|
4647
|
-
if (isMapValue(nestedValue) && nestedValue.mapValue.fields) {
|
|
4648
|
-
delete nestedValue.mapValue.fields[path.lastSegment()];
|
|
4649
|
-
}
|
|
4650
|
-
}
|
|
4651
|
-
isEqual(other) {
|
|
4652
|
-
return valueEquals(this.value, other.value);
|
|
4653
|
-
}
|
|
4654
|
-
/**
|
|
4655
|
-
* Returns the map that contains the leaf element of `path`. If the parent
|
|
4656
|
-
* entry does not yet exist, or if it is not a map, a new map will be created.
|
|
4657
|
-
*/
|
|
4658
|
-
getFieldsMap(path) {
|
|
4659
|
-
let current = this.value;
|
|
4660
|
-
if (!current.mapValue.fields) {
|
|
4661
|
-
current.mapValue = { fields: {} };
|
|
4662
|
-
}
|
|
4663
|
-
for (let i = 0; i < path.length; ++i) {
|
|
4664
|
-
let next = current.mapValue.fields[path.get(i)];
|
|
4665
|
-
if (!isMapValue(next) || !next.mapValue.fields) {
|
|
4666
|
-
next = { mapValue: { fields: {} } };
|
|
4667
|
-
current.mapValue.fields[path.get(i)] = next;
|
|
4668
|
-
}
|
|
4669
|
-
current = next;
|
|
4670
|
-
}
|
|
4671
|
-
return current.mapValue.fields;
|
|
4672
|
-
}
|
|
4673
|
-
/**
|
|
4674
|
-
* Modifies `fieldsMap` by adding, replacing or deleting the specified
|
|
4675
|
-
* entries.
|
|
4676
|
-
*/
|
|
4677
|
-
applyChanges(fieldsMap, inserts, deletes) {
|
|
4678
|
-
forEach(inserts, (key, val) => (fieldsMap[key] = val));
|
|
4679
|
-
for (const field of deletes) {
|
|
4680
|
-
delete fieldsMap[field];
|
|
4681
|
-
}
|
|
4682
|
-
}
|
|
4683
|
-
clone() {
|
|
4684
|
-
return new ObjectValue(deepClone(this.value));
|
|
4685
|
-
}
|
|
4686
|
-
}
|
|
4687
|
-
/**
|
|
4688
|
-
* Returns a FieldMask built from all fields in a MapValue.
|
|
4689
|
-
*/
|
|
4690
|
-
function extractFieldMask(value) {
|
|
4691
|
-
const fields = [];
|
|
4692
|
-
forEach(value.fields, (key, value) => {
|
|
4693
|
-
const currentPath = new FieldPath$1([key]);
|
|
4694
|
-
if (isMapValue(value)) {
|
|
4695
|
-
const nestedMask = extractFieldMask(value.mapValue);
|
|
4696
|
-
const nestedFields = nestedMask.fields;
|
|
4697
|
-
if (nestedFields.length === 0) {
|
|
4698
|
-
// Preserve the empty map by adding it to the FieldMask.
|
|
4699
|
-
fields.push(currentPath);
|
|
4700
|
-
}
|
|
4701
|
-
else {
|
|
4702
|
-
// For nested and non-empty ObjectValues, add the FieldPath of the
|
|
4703
|
-
// leaf nodes.
|
|
4704
|
-
for (const nestedPath of nestedFields) {
|
|
4705
|
-
fields.push(currentPath.child(nestedPath));
|
|
4706
|
-
}
|
|
4707
|
-
}
|
|
4708
|
-
}
|
|
4709
|
-
else {
|
|
4710
|
-
// For nested and non-empty ObjectValues, add the FieldPath of the leaf
|
|
4711
|
-
// nodes.
|
|
4712
|
-
fields.push(currentPath);
|
|
4713
|
-
}
|
|
4714
|
-
});
|
|
4715
|
-
return new FieldMask(fields);
|
|
4716
|
-
}
|
|
4717
|
-
|
|
4718
|
-
/**
|
|
4719
|
-
* @license
|
|
4720
|
-
* Copyright 2017 Google LLC
|
|
4721
|
-
*
|
|
4722
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4723
|
-
* you may not use this file except in compliance with the License.
|
|
4724
|
-
* You may obtain a copy of the License at
|
|
4725
|
-
*
|
|
4726
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
4727
|
-
*
|
|
4728
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
4729
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
4730
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
4731
|
-
* See the License for the specific language governing permissions and
|
|
4732
|
-
* limitations under the License.
|
|
4733
|
-
*/
|
|
4734
|
-
/**
|
|
4735
|
-
* Represents a document in Firestore with a key, version, data and whether it
|
|
4736
|
-
* has local mutations applied to it.
|
|
4737
|
-
*
|
|
4738
|
-
* Documents can transition between states via `convertToFoundDocument()`,
|
|
4739
|
-
* `convertToNoDocument()` and `convertToUnknownDocument()`. If a document does
|
|
4740
|
-
* not transition to one of these states even after all mutations have been
|
|
4741
|
-
* applied, `isValidDocument()` returns false and the document should be removed
|
|
4742
|
-
* from all views.
|
|
4743
|
-
*/
|
|
4744
|
-
class MutableDocument {
|
|
4745
|
-
constructor(key, documentType, version, readTime, data, documentState) {
|
|
4746
|
-
this.key = key;
|
|
4747
|
-
this.documentType = documentType;
|
|
4748
|
-
this.version = version;
|
|
4749
|
-
this.readTime = readTime;
|
|
4750
|
-
this.data = data;
|
|
4751
|
-
this.documentState = documentState;
|
|
4752
|
-
}
|
|
4753
|
-
/**
|
|
4754
|
-
* Creates a document with no known version or data, but which can serve as
|
|
4755
|
-
* base document for mutations.
|
|
4756
|
-
*/
|
|
4757
|
-
static newInvalidDocument(documentKey) {
|
|
4758
|
-
return new MutableDocument(documentKey, 0 /* INVALID */, SnapshotVersion.min(), SnapshotVersion.min(), ObjectValue.empty(), 0 /* SYNCED */);
|
|
4759
|
-
}
|
|
4760
|
-
/**
|
|
4761
|
-
* Creates a new document that is known to exist with the given data at the
|
|
4762
|
-
* given version.
|
|
4763
|
-
*/
|
|
4764
|
-
static newFoundDocument(documentKey, version, value) {
|
|
4765
|
-
return new MutableDocument(documentKey, 1 /* FOUND_DOCUMENT */, version, SnapshotVersion.min(), value, 0 /* SYNCED */);
|
|
4766
|
-
}
|
|
4767
|
-
/** Creates a new document that is known to not exist at the given version. */
|
|
4768
|
-
static newNoDocument(documentKey, version) {
|
|
4769
|
-
return new MutableDocument(documentKey, 2 /* NO_DOCUMENT */, version, SnapshotVersion.min(), ObjectValue.empty(), 0 /* SYNCED */);
|
|
4770
|
-
}
|
|
4771
|
-
/**
|
|
4772
|
-
* Creates a new document that is known to exist at the given version but
|
|
4773
|
-
* whose data is not known (e.g. a document that was updated without a known
|
|
4774
|
-
* base document).
|
|
4775
|
-
*/
|
|
4776
|
-
static newUnknownDocument(documentKey, version) {
|
|
4777
|
-
return new MutableDocument(documentKey, 3 /* UNKNOWN_DOCUMENT */, version, SnapshotVersion.min(), ObjectValue.empty(), 2 /* HAS_COMMITTED_MUTATIONS */);
|
|
4778
|
-
}
|
|
4779
|
-
/**
|
|
4780
|
-
* Changes the document type to indicate that it exists and that its version
|
|
4781
|
-
* and data are known.
|
|
4782
|
-
*/
|
|
4783
|
-
convertToFoundDocument(version, value) {
|
|
4784
|
-
this.version = version;
|
|
4785
|
-
this.documentType = 1 /* FOUND_DOCUMENT */;
|
|
4786
|
-
this.data = value;
|
|
4787
|
-
this.documentState = 0 /* SYNCED */;
|
|
4788
|
-
return this;
|
|
4789
|
-
}
|
|
4790
|
-
/**
|
|
4791
|
-
* Changes the document type to indicate that it doesn't exist at the given
|
|
4792
|
-
* version.
|
|
4793
|
-
*/
|
|
4794
|
-
convertToNoDocument(version) {
|
|
4795
|
-
this.version = version;
|
|
4796
|
-
this.documentType = 2 /* NO_DOCUMENT */;
|
|
4797
|
-
this.data = ObjectValue.empty();
|
|
4798
|
-
this.documentState = 0 /* SYNCED */;
|
|
4799
|
-
return this;
|
|
4800
|
-
}
|
|
4801
|
-
/**
|
|
4802
|
-
* Changes the document type to indicate that it exists at a given version but
|
|
4803
|
-
* that its data is not known (e.g. a document that was updated without a known
|
|
4804
|
-
* base document).
|
|
4805
|
-
*/
|
|
4806
|
-
convertToUnknownDocument(version) {
|
|
4807
|
-
this.version = version;
|
|
4808
|
-
this.documentType = 3 /* UNKNOWN_DOCUMENT */;
|
|
4809
|
-
this.data = ObjectValue.empty();
|
|
4810
|
-
this.documentState = 2 /* HAS_COMMITTED_MUTATIONS */;
|
|
4811
|
-
return this;
|
|
4812
|
-
}
|
|
4813
|
-
setHasCommittedMutations() {
|
|
4814
|
-
this.documentState = 2 /* HAS_COMMITTED_MUTATIONS */;
|
|
4815
|
-
return this;
|
|
4816
|
-
}
|
|
4817
|
-
setHasLocalMutations() {
|
|
4818
|
-
this.documentState = 1 /* HAS_LOCAL_MUTATIONS */;
|
|
4819
|
-
this.version = SnapshotVersion.min();
|
|
4820
|
-
return this;
|
|
4821
|
-
}
|
|
4822
|
-
setReadTime(readTime) {
|
|
4823
|
-
this.readTime = readTime;
|
|
4824
|
-
return this;
|
|
4825
|
-
}
|
|
4826
|
-
get hasLocalMutations() {
|
|
4827
|
-
return this.documentState === 1 /* HAS_LOCAL_MUTATIONS */;
|
|
4828
|
-
}
|
|
4829
|
-
get hasCommittedMutations() {
|
|
4830
|
-
return this.documentState === 2 /* HAS_COMMITTED_MUTATIONS */;
|
|
4831
|
-
}
|
|
4832
|
-
get hasPendingWrites() {
|
|
4833
|
-
return this.hasLocalMutations || this.hasCommittedMutations;
|
|
4834
|
-
}
|
|
4835
|
-
isValidDocument() {
|
|
4836
|
-
return this.documentType !== 0 /* INVALID */;
|
|
4837
|
-
}
|
|
4838
|
-
isFoundDocument() {
|
|
4839
|
-
return this.documentType === 1 /* FOUND_DOCUMENT */;
|
|
4840
|
-
}
|
|
4841
|
-
isNoDocument() {
|
|
4842
|
-
return this.documentType === 2 /* NO_DOCUMENT */;
|
|
5012
|
+
* @param data - A map of fields to values (or null for deletes).
|
|
5013
|
+
*/
|
|
5014
|
+
setAll(data) {
|
|
5015
|
+
let parent = FieldPath$1.emptyPath();
|
|
5016
|
+
let upserts = {};
|
|
5017
|
+
let deletes = [];
|
|
5018
|
+
data.forEach((value, path) => {
|
|
5019
|
+
if (!parent.isImmediateParentOf(path)) {
|
|
5020
|
+
// Insert the accumulated changes at this parent location
|
|
5021
|
+
const fieldsMap = this.getFieldsMap(parent);
|
|
5022
|
+
this.applyChanges(fieldsMap, upserts, deletes);
|
|
5023
|
+
upserts = {};
|
|
5024
|
+
deletes = [];
|
|
5025
|
+
parent = path.popLast();
|
|
5026
|
+
}
|
|
5027
|
+
if (value) {
|
|
5028
|
+
upserts[path.lastSegment()] = deepClone(value);
|
|
5029
|
+
}
|
|
5030
|
+
else {
|
|
5031
|
+
deletes.push(path.lastSegment());
|
|
5032
|
+
}
|
|
5033
|
+
});
|
|
5034
|
+
const fieldsMap = this.getFieldsMap(parent);
|
|
5035
|
+
this.applyChanges(fieldsMap, upserts, deletes);
|
|
4843
5036
|
}
|
|
4844
|
-
|
|
4845
|
-
|
|
5037
|
+
/**
|
|
5038
|
+
* Removes the field at the specified path. If there is no field at the
|
|
5039
|
+
* specified path, nothing is changed.
|
|
5040
|
+
*
|
|
5041
|
+
* @param path - The field path to remove.
|
|
5042
|
+
*/
|
|
5043
|
+
delete(path) {
|
|
5044
|
+
const nestedValue = this.field(path.popLast());
|
|
5045
|
+
if (isMapValue(nestedValue) && nestedValue.mapValue.fields) {
|
|
5046
|
+
delete nestedValue.mapValue.fields[path.lastSegment()];
|
|
5047
|
+
}
|
|
4846
5048
|
}
|
|
4847
5049
|
isEqual(other) {
|
|
4848
|
-
return (other
|
|
4849
|
-
this.key.isEqual(other.key) &&
|
|
4850
|
-
this.version.isEqual(other.version) &&
|
|
4851
|
-
this.documentType === other.documentType &&
|
|
4852
|
-
this.documentState === other.documentState &&
|
|
4853
|
-
this.data.isEqual(other.data));
|
|
5050
|
+
return valueEquals(this.value, other.value);
|
|
4854
5051
|
}
|
|
4855
|
-
|
|
4856
|
-
|
|
5052
|
+
/**
|
|
5053
|
+
* Returns the map that contains the leaf element of `path`. If the parent
|
|
5054
|
+
* entry does not yet exist, or if it is not a map, a new map will be created.
|
|
5055
|
+
*/
|
|
5056
|
+
getFieldsMap(path) {
|
|
5057
|
+
let current = this.value;
|
|
5058
|
+
if (!current.mapValue.fields) {
|
|
5059
|
+
current.mapValue = { fields: {} };
|
|
5060
|
+
}
|
|
5061
|
+
for (let i = 0; i < path.length; ++i) {
|
|
5062
|
+
let next = current.mapValue.fields[path.get(i)];
|
|
5063
|
+
if (!isMapValue(next) || !next.mapValue.fields) {
|
|
5064
|
+
next = { mapValue: { fields: {} } };
|
|
5065
|
+
current.mapValue.fields[path.get(i)] = next;
|
|
5066
|
+
}
|
|
5067
|
+
current = next;
|
|
5068
|
+
}
|
|
5069
|
+
return current.mapValue.fields;
|
|
4857
5070
|
}
|
|
4858
|
-
|
|
4859
|
-
|
|
4860
|
-
|
|
4861
|
-
|
|
5071
|
+
/**
|
|
5072
|
+
* Modifies `fieldsMap` by adding, replacing or deleting the specified
|
|
5073
|
+
* entries.
|
|
5074
|
+
*/
|
|
5075
|
+
applyChanges(fieldsMap, inserts, deletes) {
|
|
5076
|
+
forEach(inserts, (key, val) => (fieldsMap[key] = val));
|
|
5077
|
+
for (const field of deletes) {
|
|
5078
|
+
delete fieldsMap[field];
|
|
5079
|
+
}
|
|
5080
|
+
}
|
|
5081
|
+
clone() {
|
|
5082
|
+
return new ObjectValue(deepClone(this.value));
|
|
4862
5083
|
}
|
|
4863
5084
|
}
|
|
4864
5085
|
/**
|
|
4865
|
-
*
|
|
4866
|
-
* the field does not exist in both documents.
|
|
5086
|
+
* Returns a FieldMask built from all fields in a MapValue.
|
|
4867
5087
|
*/
|
|
4868
|
-
function
|
|
4869
|
-
const
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
5088
|
+
function extractFieldMask(value) {
|
|
5089
|
+
const fields = [];
|
|
5090
|
+
forEach(value.fields, (key, value) => {
|
|
5091
|
+
const currentPath = new FieldPath$1([key]);
|
|
5092
|
+
if (isMapValue(value)) {
|
|
5093
|
+
const nestedMask = extractFieldMask(value.mapValue);
|
|
5094
|
+
const nestedFields = nestedMask.fields;
|
|
5095
|
+
if (nestedFields.length === 0) {
|
|
5096
|
+
// Preserve the empty map by adding it to the FieldMask.
|
|
5097
|
+
fields.push(currentPath);
|
|
5098
|
+
}
|
|
5099
|
+
else {
|
|
5100
|
+
// For nested and non-empty ObjectValues, add the FieldPath of the
|
|
5101
|
+
// leaf nodes.
|
|
5102
|
+
for (const nestedPath of nestedFields) {
|
|
5103
|
+
fields.push(currentPath.child(nestedPath));
|
|
5104
|
+
}
|
|
5105
|
+
}
|
|
5106
|
+
}
|
|
5107
|
+
else {
|
|
5108
|
+
// For nested and non-empty ObjectValues, add the FieldPath of the leaf
|
|
5109
|
+
// nodes.
|
|
5110
|
+
fields.push(currentPath);
|
|
5111
|
+
}
|
|
5112
|
+
});
|
|
5113
|
+
return new FieldMask(fields);
|
|
4877
5114
|
}
|
|
4878
5115
|
|
|
4879
5116
|
/**
|
|
4880
5117
|
* @license
|
|
4881
|
-
* Copyright
|
|
5118
|
+
* Copyright 2017 Google LLC
|
|
4882
5119
|
*
|
|
4883
5120
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4884
5121
|
* you may not use this file except in compliance with the License.
|
|
@@ -4893,166 +5130,148 @@ function compareDocumentsByField(field, d1, d2) {
|
|
|
4893
5130
|
* limitations under the License.
|
|
4894
5131
|
*/
|
|
4895
5132
|
/**
|
|
4896
|
-
*
|
|
4897
|
-
*
|
|
4898
|
-
*/
|
|
4899
|
-
const INITIAL_LARGEST_BATCH_ID = -1;
|
|
4900
|
-
/**
|
|
4901
|
-
* The initial sequence number for each index. Gets updated during index
|
|
4902
|
-
* backfill.
|
|
4903
|
-
*/
|
|
4904
|
-
const INITIAL_SEQUENCE_NUMBER = 0;
|
|
4905
|
-
/**
|
|
4906
|
-
* An index definition for field indexes in Firestore.
|
|
4907
|
-
*
|
|
4908
|
-
* Every index is associated with a collection. The definition contains a list
|
|
4909
|
-
* of fields and their index kind (which can be `ASCENDING`, `DESCENDING` or
|
|
4910
|
-
* `CONTAINS` for ArrayContains/ArrayContainsAny queries).
|
|
4911
|
-
*
|
|
4912
|
-
* Unlike the backend, the SDK does not differentiate between collection or
|
|
4913
|
-
* collection group-scoped indices. Every index can be used for both single
|
|
4914
|
-
* collection and collection group queries.
|
|
4915
|
-
*/
|
|
4916
|
-
class FieldIndex {
|
|
4917
|
-
constructor(
|
|
4918
|
-
/**
|
|
4919
|
-
* The index ID. Returns -1 if the index ID is not available (e.g. the index
|
|
4920
|
-
* has not yet been persisted).
|
|
4921
|
-
*/
|
|
4922
|
-
indexId,
|
|
4923
|
-
/** The collection ID this index applies to. */
|
|
4924
|
-
collectionGroup,
|
|
4925
|
-
/** The field segments for this index. */
|
|
4926
|
-
fields,
|
|
4927
|
-
/** Shows how up-to-date the index is for the current user. */
|
|
4928
|
-
indexState) {
|
|
4929
|
-
this.indexId = indexId;
|
|
4930
|
-
this.collectionGroup = collectionGroup;
|
|
4931
|
-
this.fields = fields;
|
|
4932
|
-
this.indexState = indexState;
|
|
4933
|
-
}
|
|
4934
|
-
}
|
|
4935
|
-
/** An ID for an index that has not yet been added to persistence. */
|
|
4936
|
-
FieldIndex.UNKNOWN_ID = -1;
|
|
4937
|
-
/** Returns the ArrayContains/ArrayContainsAny segment for this index. */
|
|
4938
|
-
function fieldIndexGetArraySegment(fieldIndex) {
|
|
4939
|
-
return fieldIndex.fields.find(s => s.kind === 2 /* CONTAINS */);
|
|
4940
|
-
}
|
|
4941
|
-
/** Returns all directional (ascending/descending) segments for this index. */
|
|
4942
|
-
function fieldIndexGetDirectionalSegments(fieldIndex) {
|
|
4943
|
-
return fieldIndex.fields.filter(s => s.kind !== 2 /* CONTAINS */);
|
|
4944
|
-
}
|
|
4945
|
-
/**
|
|
4946
|
-
* Returns the order of the document key component for the given index.
|
|
5133
|
+
* Represents a document in Firestore with a key, version, data and whether it
|
|
5134
|
+
* has local mutations applied to it.
|
|
4947
5135
|
*
|
|
4948
|
-
*
|
|
5136
|
+
* Documents can transition between states via `convertToFoundDocument()`,
|
|
5137
|
+
* `convertToNoDocument()` and `convertToUnknownDocument()`. If a document does
|
|
5138
|
+
* not transition to one of these states even after all mutations have been
|
|
5139
|
+
* applied, `isValidDocument()` returns false and the document should be removed
|
|
5140
|
+
* from all views.
|
|
4949
5141
|
*/
|
|
4950
|
-
|
|
4951
|
-
|
|
4952
|
-
|
|
4953
|
-
|
|
4954
|
-
|
|
4955
|
-
|
|
4956
|
-
|
|
4957
|
-
|
|
4958
|
-
return `id=${fieldIndex.indexId}|cg=${fieldIndex.collectionGroup}|f=${fieldIndex.fields.map(f => `${f.fieldPath}:${f.kind}`).join(',')}`;
|
|
4959
|
-
}
|
|
4960
|
-
/** An index component consisting of field path and index type. */
|
|
4961
|
-
class IndexSegment {
|
|
4962
|
-
constructor(
|
|
4963
|
-
/** The field path of the component. */
|
|
4964
|
-
fieldPath,
|
|
4965
|
-
/** The fields sorting order. */
|
|
4966
|
-
kind) {
|
|
4967
|
-
this.fieldPath = fieldPath;
|
|
4968
|
-
this.kind = kind;
|
|
5142
|
+
class MutableDocument {
|
|
5143
|
+
constructor(key, documentType, version, readTime, data, documentState) {
|
|
5144
|
+
this.key = key;
|
|
5145
|
+
this.documentType = documentType;
|
|
5146
|
+
this.version = version;
|
|
5147
|
+
this.readTime = readTime;
|
|
5148
|
+
this.data = data;
|
|
5149
|
+
this.documentState = documentState;
|
|
4969
5150
|
}
|
|
4970
|
-
}
|
|
4971
|
-
/**
|
|
4972
|
-
* Stores the "high water mark" that indicates how updated the Index is for the
|
|
4973
|
-
* current user.
|
|
4974
|
-
*/
|
|
4975
|
-
class IndexState {
|
|
4976
|
-
constructor(
|
|
4977
5151
|
/**
|
|
4978
|
-
*
|
|
5152
|
+
* Creates a document with no known version or data, but which can serve as
|
|
5153
|
+
* base document for mutations.
|
|
4979
5154
|
*/
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
offset) {
|
|
4983
|
-
this.sequenceNumber = sequenceNumber;
|
|
4984
|
-
this.offset = offset;
|
|
4985
|
-
}
|
|
4986
|
-
/** The state of an index that has not yet been backfilled. */
|
|
4987
|
-
static empty() {
|
|
4988
|
-
return new IndexState(INITIAL_SEQUENCE_NUMBER, IndexOffset.min());
|
|
5155
|
+
static newInvalidDocument(documentKey) {
|
|
5156
|
+
return new MutableDocument(documentKey, 0 /* INVALID */, SnapshotVersion.min(), SnapshotVersion.min(), ObjectValue.empty(), 0 /* SYNCED */);
|
|
4989
5157
|
}
|
|
4990
|
-
}
|
|
4991
|
-
/**
|
|
4992
|
-
* Creates an offset that matches all documents with a read time higher than
|
|
4993
|
-
* `readTime`.
|
|
4994
|
-
*/
|
|
4995
|
-
function newIndexOffsetSuccessorFromReadTime(readTime, largestBatchId) {
|
|
4996
|
-
// We want to create an offset that matches all documents with a read time
|
|
4997
|
-
// greater than the provided read time. To do so, we technically need to
|
|
4998
|
-
// create an offset for `(readTime, MAX_DOCUMENT_KEY)`. While we could use
|
|
4999
|
-
// Unicode codepoints to generate MAX_DOCUMENT_KEY, it is much easier to use
|
|
5000
|
-
// `(readTime + 1, DocumentKey.empty())` since `> DocumentKey.empty()` matches
|
|
5001
|
-
// all valid document IDs.
|
|
5002
|
-
const successorSeconds = readTime.toTimestamp().seconds;
|
|
5003
|
-
const successorNanos = readTime.toTimestamp().nanoseconds + 1;
|
|
5004
|
-
const successor = SnapshotVersion.fromTimestamp(successorNanos === 1e9
|
|
5005
|
-
? new Timestamp(successorSeconds + 1, 0)
|
|
5006
|
-
: new Timestamp(successorSeconds, successorNanos));
|
|
5007
|
-
return new IndexOffset(successor, DocumentKey.empty(), largestBatchId);
|
|
5008
|
-
}
|
|
5009
|
-
/** Creates a new offset based on the provided document. */
|
|
5010
|
-
function newIndexOffsetFromDocument(document) {
|
|
5011
|
-
return new IndexOffset(document.readTime, document.key, INITIAL_LARGEST_BATCH_ID);
|
|
5012
|
-
}
|
|
5013
|
-
/**
|
|
5014
|
-
* Stores the latest read time, document and batch ID that were processed for an
|
|
5015
|
-
* index.
|
|
5016
|
-
*/
|
|
5017
|
-
class IndexOffset {
|
|
5018
|
-
constructor(
|
|
5019
5158
|
/**
|
|
5020
|
-
*
|
|
5021
|
-
*
|
|
5159
|
+
* Creates a new document that is known to exist with the given data at the
|
|
5160
|
+
* given version.
|
|
5022
5161
|
*/
|
|
5023
|
-
|
|
5162
|
+
static newFoundDocument(documentKey, version, value) {
|
|
5163
|
+
return new MutableDocument(documentKey, 1 /* FOUND_DOCUMENT */, version, SnapshotVersion.min(), value, 0 /* SYNCED */);
|
|
5164
|
+
}
|
|
5165
|
+
/** Creates a new document that is known to not exist at the given version. */
|
|
5166
|
+
static newNoDocument(documentKey, version) {
|
|
5167
|
+
return new MutableDocument(documentKey, 2 /* NO_DOCUMENT */, version, SnapshotVersion.min(), ObjectValue.empty(), 0 /* SYNCED */);
|
|
5168
|
+
}
|
|
5024
5169
|
/**
|
|
5025
|
-
*
|
|
5026
|
-
*
|
|
5170
|
+
* Creates a new document that is known to exist at the given version but
|
|
5171
|
+
* whose data is not known (e.g. a document that was updated without a known
|
|
5172
|
+
* base document).
|
|
5027
5173
|
*/
|
|
5028
|
-
documentKey,
|
|
5029
|
-
|
|
5030
|
-
|
|
5174
|
+
static newUnknownDocument(documentKey, version) {
|
|
5175
|
+
return new MutableDocument(documentKey, 3 /* UNKNOWN_DOCUMENT */, version, SnapshotVersion.min(), ObjectValue.empty(), 2 /* HAS_COMMITTED_MUTATIONS */);
|
|
5176
|
+
}
|
|
5177
|
+
/**
|
|
5178
|
+
* Changes the document type to indicate that it exists and that its version
|
|
5179
|
+
* and data are known.
|
|
5031
5180
|
*/
|
|
5032
|
-
|
|
5181
|
+
convertToFoundDocument(version, value) {
|
|
5182
|
+
this.version = version;
|
|
5183
|
+
this.documentType = 1 /* FOUND_DOCUMENT */;
|
|
5184
|
+
this.data = value;
|
|
5185
|
+
this.documentState = 0 /* SYNCED */;
|
|
5186
|
+
return this;
|
|
5187
|
+
}
|
|
5188
|
+
/**
|
|
5189
|
+
* Changes the document type to indicate that it doesn't exist at the given
|
|
5190
|
+
* version.
|
|
5191
|
+
*/
|
|
5192
|
+
convertToNoDocument(version) {
|
|
5193
|
+
this.version = version;
|
|
5194
|
+
this.documentType = 2 /* NO_DOCUMENT */;
|
|
5195
|
+
this.data = ObjectValue.empty();
|
|
5196
|
+
this.documentState = 0 /* SYNCED */;
|
|
5197
|
+
return this;
|
|
5198
|
+
}
|
|
5199
|
+
/**
|
|
5200
|
+
* Changes the document type to indicate that it exists at a given version but
|
|
5201
|
+
* that its data is not known (e.g. a document that was updated without a known
|
|
5202
|
+
* base document).
|
|
5203
|
+
*/
|
|
5204
|
+
convertToUnknownDocument(version) {
|
|
5205
|
+
this.version = version;
|
|
5206
|
+
this.documentType = 3 /* UNKNOWN_DOCUMENT */;
|
|
5207
|
+
this.data = ObjectValue.empty();
|
|
5208
|
+
this.documentState = 2 /* HAS_COMMITTED_MUTATIONS */;
|
|
5209
|
+
return this;
|
|
5210
|
+
}
|
|
5211
|
+
setHasCommittedMutations() {
|
|
5212
|
+
this.documentState = 2 /* HAS_COMMITTED_MUTATIONS */;
|
|
5213
|
+
return this;
|
|
5214
|
+
}
|
|
5215
|
+
setHasLocalMutations() {
|
|
5216
|
+
this.documentState = 1 /* HAS_LOCAL_MUTATIONS */;
|
|
5217
|
+
this.version = SnapshotVersion.min();
|
|
5218
|
+
return this;
|
|
5219
|
+
}
|
|
5220
|
+
setReadTime(readTime) {
|
|
5033
5221
|
this.readTime = readTime;
|
|
5034
|
-
this
|
|
5035
|
-
this.largestBatchId = largestBatchId;
|
|
5222
|
+
return this;
|
|
5036
5223
|
}
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
return new IndexOffset(SnapshotVersion.min(), DocumentKey.empty(), INITIAL_LARGEST_BATCH_ID);
|
|
5224
|
+
get hasLocalMutations() {
|
|
5225
|
+
return this.documentState === 1 /* HAS_LOCAL_MUTATIONS */;
|
|
5040
5226
|
}
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5227
|
+
get hasCommittedMutations() {
|
|
5228
|
+
return this.documentState === 2 /* HAS_COMMITTED_MUTATIONS */;
|
|
5229
|
+
}
|
|
5230
|
+
get hasPendingWrites() {
|
|
5231
|
+
return this.hasLocalMutations || this.hasCommittedMutations;
|
|
5232
|
+
}
|
|
5233
|
+
isValidDocument() {
|
|
5234
|
+
return this.documentType !== 0 /* INVALID */;
|
|
5235
|
+
}
|
|
5236
|
+
isFoundDocument() {
|
|
5237
|
+
return this.documentType === 1 /* FOUND_DOCUMENT */;
|
|
5238
|
+
}
|
|
5239
|
+
isNoDocument() {
|
|
5240
|
+
return this.documentType === 2 /* NO_DOCUMENT */;
|
|
5241
|
+
}
|
|
5242
|
+
isUnknownDocument() {
|
|
5243
|
+
return this.documentType === 3 /* UNKNOWN_DOCUMENT */;
|
|
5244
|
+
}
|
|
5245
|
+
isEqual(other) {
|
|
5246
|
+
return (other instanceof MutableDocument &&
|
|
5247
|
+
this.key.isEqual(other.key) &&
|
|
5248
|
+
this.version.isEqual(other.version) &&
|
|
5249
|
+
this.documentType === other.documentType &&
|
|
5250
|
+
this.documentState === other.documentState &&
|
|
5251
|
+
this.data.isEqual(other.data));
|
|
5252
|
+
}
|
|
5253
|
+
mutableCopy() {
|
|
5254
|
+
return new MutableDocument(this.key, this.documentType, this.version, this.readTime, this.data.clone(), this.documentState);
|
|
5255
|
+
}
|
|
5256
|
+
toString() {
|
|
5257
|
+
return (`Document(${this.key}, ${this.version}, ${JSON.stringify(this.data.value)}, ` +
|
|
5258
|
+
`{documentType: ${this.documentType}}), ` +
|
|
5259
|
+
`{documentState: ${this.documentState}})`);
|
|
5044
5260
|
}
|
|
5045
5261
|
}
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5262
|
+
/**
|
|
5263
|
+
* Compares the value for field `field` in the provided documents. Throws if
|
|
5264
|
+
* the field does not exist in both documents.
|
|
5265
|
+
*/
|
|
5266
|
+
function compareDocumentsByField(field, d1, d2) {
|
|
5267
|
+
const v1 = d1.data.field(field);
|
|
5268
|
+
const v2 = d2.data.field(field);
|
|
5269
|
+
if (v1 !== null && v2 !== null) {
|
|
5270
|
+
return valueCompare(v1, v2);
|
|
5050
5271
|
}
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
return cmp;
|
|
5272
|
+
else {
|
|
5273
|
+
return fail();
|
|
5054
5274
|
}
|
|
5055
|
-
return primitiveComparator(left.largestBatchId, right.largestBatchId);
|
|
5056
5275
|
}
|
|
5057
5276
|
|
|
5058
5277
|
/**
|
|
@@ -6165,11 +6384,19 @@ function documentMap(...docs) {
|
|
|
6165
6384
|
}
|
|
6166
6385
|
return map;
|
|
6167
6386
|
}
|
|
6387
|
+
function newOverlayedDocumentMap() {
|
|
6388
|
+
return newDocumentKeyMap();
|
|
6389
|
+
}
|
|
6390
|
+
function convertOverlayedDocumentMapToDocumentMap(collection) {
|
|
6391
|
+
let documents = EMPTY_DOCUMENT_MAP;
|
|
6392
|
+
collection.forEach((k, v) => (documents = documents.insert(k, v.overlayedDocument)));
|
|
6393
|
+
return documents;
|
|
6394
|
+
}
|
|
6168
6395
|
function newOverlayMap() {
|
|
6169
|
-
return
|
|
6396
|
+
return newDocumentKeyMap();
|
|
6170
6397
|
}
|
|
6171
6398
|
function newMutationMap() {
|
|
6172
|
-
return
|
|
6399
|
+
return newDocumentKeyMap();
|
|
6173
6400
|
}
|
|
6174
6401
|
function newDocumentKeyMap() {
|
|
6175
6402
|
return new ObjectMap(key => key.toString(), (l, r) => l.isEqual(r));
|
|
@@ -9270,15 +9497,13 @@ class IndexedDbDocumentOverlayCache {
|
|
|
9270
9497
|
}
|
|
9271
9498
|
getOverlays(transaction, keys) {
|
|
9272
9499
|
const result = newOverlayMap();
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
promises.push(this.getOverlay(transaction, key).next(overlay => {
|
|
9500
|
+
return PersistencePromise.forEach(keys, (key) => {
|
|
9501
|
+
return this.getOverlay(transaction, key).next(overlay => {
|
|
9276
9502
|
if (overlay !== null) {
|
|
9277
9503
|
result.set(key, overlay);
|
|
9278
9504
|
}
|
|
9279
|
-
})
|
|
9280
|
-
});
|
|
9281
|
-
return PersistencePromise.waitFor(promises).next(() => result);
|
|
9505
|
+
});
|
|
9506
|
+
}).next(() => result);
|
|
9282
9507
|
}
|
|
9283
9508
|
saveOverlays(transaction, largestBatchId, overlays) {
|
|
9284
9509
|
const promises = [];
|
|
@@ -10166,6 +10391,9 @@ class MemoryIndexManager {
|
|
|
10166
10391
|
getMinOffset(transaction, target) {
|
|
10167
10392
|
return PersistencePromise.resolve(IndexOffset.min());
|
|
10168
10393
|
}
|
|
10394
|
+
getMinOffsetFromCollectionGroup(transaction, collectionGroup) {
|
|
10395
|
+
return PersistencePromise.resolve(IndexOffset.min());
|
|
10396
|
+
}
|
|
10169
10397
|
updateCollectionGroup(transaction, collectionGroup, offset) {
|
|
10170
10398
|
// Field indices are not supported with memory persistence.
|
|
10171
10399
|
return PersistencePromise.resolve();
|
|
@@ -10301,7 +10529,16 @@ class IndexedDbIndexManager {
|
|
|
10301
10529
|
const indexes = indexConfigurationStore(transaction);
|
|
10302
10530
|
const dbIndex = toDbIndexConfiguration(index);
|
|
10303
10531
|
delete dbIndex.indexId; // `indexId` is auto-populated by IndexedDb
|
|
10304
|
-
|
|
10532
|
+
const result = indexes.add(dbIndex);
|
|
10533
|
+
if (index.indexState) {
|
|
10534
|
+
const states = indexStateStore(transaction);
|
|
10535
|
+
return result.next(indexId => {
|
|
10536
|
+
states.put(toDbIndexState(indexId, this.user, index.indexState.sequenceNumber, index.indexState.offset));
|
|
10537
|
+
});
|
|
10538
|
+
}
|
|
10539
|
+
else {
|
|
10540
|
+
return result.next();
|
|
10541
|
+
}
|
|
10305
10542
|
}
|
|
10306
10543
|
deleteFieldIndex(transaction, index) {
|
|
10307
10544
|
const indexes = indexConfigurationStore(transaction);
|
|
@@ -10738,19 +10975,11 @@ class IndexedDbIndexManager {
|
|
|
10738
10975
|
}
|
|
10739
10976
|
return ranges;
|
|
10740
10977
|
}
|
|
10978
|
+
getMinOffsetFromCollectionGroup(transaction, collectionGroup) {
|
|
10979
|
+
return this.getFieldIndexes(transaction, collectionGroup).next(getMinOffsetFromFieldIndexes);
|
|
10980
|
+
}
|
|
10741
10981
|
getMinOffset(transaction, target) {
|
|
10742
|
-
|
|
10743
|
-
return PersistencePromise.forEach(this.getSubTargets(target), (target) => {
|
|
10744
|
-
return this.getFieldIndex(transaction, target).next(index => {
|
|
10745
|
-
if (!index) {
|
|
10746
|
-
offset = IndexOffset.min();
|
|
10747
|
-
}
|
|
10748
|
-
else if (!offset ||
|
|
10749
|
-
indexOffsetComparator(index.indexState.offset, offset) < 0) {
|
|
10750
|
-
offset = index.indexState.offset;
|
|
10751
|
-
}
|
|
10752
|
-
});
|
|
10753
|
-
}).next(() => offset);
|
|
10982
|
+
return PersistencePromise.mapArray(this.getSubTargets(target), (subTarget) => this.getFieldIndex(transaction, subTarget).next(index => index ? index : fail())).next(getMinOffsetFromFieldIndexes);
|
|
10754
10983
|
}
|
|
10755
10984
|
}
|
|
10756
10985
|
/**
|
|
@@ -10777,6 +11006,21 @@ function indexConfigurationStore(txn) {
|
|
|
10777
11006
|
*/
|
|
10778
11007
|
function indexStateStore(txn) {
|
|
10779
11008
|
return getStore(txn, DbIndexStateStore);
|
|
11009
|
+
}
|
|
11010
|
+
function getMinOffsetFromFieldIndexes(fieldIndexes) {
|
|
11011
|
+
hardAssert(fieldIndexes.length !== 0);
|
|
11012
|
+
let minOffset = fieldIndexes[0].indexState.offset;
|
|
11013
|
+
let maxBatchId = minOffset.largestBatchId;
|
|
11014
|
+
for (let i = 1; i < fieldIndexes.length; i++) {
|
|
11015
|
+
const newOffset = fieldIndexes[i].indexState.offset;
|
|
11016
|
+
if (indexOffsetComparator(newOffset, minOffset) < 0) {
|
|
11017
|
+
minOffset = newOffset;
|
|
11018
|
+
}
|
|
11019
|
+
if (maxBatchId < newOffset.largestBatchId) {
|
|
11020
|
+
maxBatchId = newOffset.largestBatchId;
|
|
11021
|
+
}
|
|
11022
|
+
}
|
|
11023
|
+
return new IndexOffset(minOffset.readTime, minOffset.documentKey, maxBatchId);
|
|
10780
11024
|
}
|
|
10781
11025
|
|
|
10782
11026
|
/**
|
|
@@ -11563,42 +11807,6 @@ function documentTargetStore(txn) {
|
|
|
11563
11807
|
return getStore(txn, DbTargetDocumentStore);
|
|
11564
11808
|
}
|
|
11565
11809
|
|
|
11566
|
-
/**
|
|
11567
|
-
* @license
|
|
11568
|
-
* Copyright 2017 Google LLC
|
|
11569
|
-
*
|
|
11570
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
11571
|
-
* you may not use this file except in compliance with the License.
|
|
11572
|
-
* You may obtain a copy of the License at
|
|
11573
|
-
*
|
|
11574
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11575
|
-
*
|
|
11576
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
11577
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11578
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11579
|
-
* See the License for the specific language governing permissions and
|
|
11580
|
-
* limitations under the License.
|
|
11581
|
-
*/
|
|
11582
|
-
/**
|
|
11583
|
-
* Verifies the error thrown by a LocalStore operation. If a LocalStore
|
|
11584
|
-
* operation fails because the primary lease has been taken by another client,
|
|
11585
|
-
* we ignore the error (the persistence layer will immediately call
|
|
11586
|
-
* `applyPrimaryLease` to propagate the primary state change). All other errors
|
|
11587
|
-
* are re-thrown.
|
|
11588
|
-
*
|
|
11589
|
-
* @param err - An error returned by a LocalStore operation.
|
|
11590
|
-
* @returns A Promise that resolves after we recovered, or the original error.
|
|
11591
|
-
*/
|
|
11592
|
-
async function ignoreIfPrimaryLeaseLoss(err) {
|
|
11593
|
-
if (err.code === Code.FAILED_PRECONDITION &&
|
|
11594
|
-
err.message === PRIMARY_LEASE_LOST_ERROR_MSG) {
|
|
11595
|
-
logDebug('LocalStore', 'Unexpectedly lost primary lease');
|
|
11596
|
-
}
|
|
11597
|
-
else {
|
|
11598
|
-
throw err;
|
|
11599
|
-
}
|
|
11600
|
-
}
|
|
11601
|
-
|
|
11602
11810
|
/**
|
|
11603
11811
|
* @license
|
|
11604
11812
|
* Copyright 2018 Google LLC
|
|
@@ -11720,16 +11928,16 @@ class RollingSequenceNumberBuffer {
|
|
|
11720
11928
|
* whether or not GC is enabled, as well as which delay to use before the next run.
|
|
11721
11929
|
*/
|
|
11722
11930
|
class LruScheduler {
|
|
11723
|
-
constructor(garbageCollector, asyncQueue) {
|
|
11931
|
+
constructor(garbageCollector, asyncQueue, localStore) {
|
|
11724
11932
|
this.garbageCollector = garbageCollector;
|
|
11725
11933
|
this.asyncQueue = asyncQueue;
|
|
11726
|
-
this.
|
|
11934
|
+
this.localStore = localStore;
|
|
11727
11935
|
this.gcTask = null;
|
|
11728
11936
|
}
|
|
11729
|
-
start(
|
|
11937
|
+
start() {
|
|
11730
11938
|
if (this.garbageCollector.params.cacheSizeCollectionThreshold !==
|
|
11731
11939
|
LRU_COLLECTION_DISABLED) {
|
|
11732
|
-
this.scheduleGC(
|
|
11940
|
+
this.scheduleGC(INITIAL_GC_DELAY_MS);
|
|
11733
11941
|
}
|
|
11734
11942
|
}
|
|
11735
11943
|
stop() {
|
|
@@ -11741,14 +11949,12 @@ class LruScheduler {
|
|
|
11741
11949
|
get started() {
|
|
11742
11950
|
return this.gcTask !== null;
|
|
11743
11951
|
}
|
|
11744
|
-
scheduleGC(
|
|
11745
|
-
|
|
11746
|
-
logDebug('LruGarbageCollector', `Garbage collection scheduled in ${delay}ms`);
|
|
11952
|
+
scheduleGC(delay) {
|
|
11953
|
+
logDebug(LOG_TAG$e, `Garbage collection scheduled in ${delay}ms`);
|
|
11747
11954
|
this.gcTask = this.asyncQueue.enqueueAfterDelay("lru_garbage_collection" /* LruGarbageCollection */, delay, async () => {
|
|
11748
11955
|
this.gcTask = null;
|
|
11749
|
-
this.hasRun = true;
|
|
11750
11956
|
try {
|
|
11751
|
-
await localStore.collectGarbage(this.garbageCollector);
|
|
11957
|
+
await this.localStore.collectGarbage(this.garbageCollector);
|
|
11752
11958
|
}
|
|
11753
11959
|
catch (e) {
|
|
11754
11960
|
if (isIndexedDbTransactionError(e)) {
|
|
@@ -11758,7 +11964,7 @@ class LruScheduler {
|
|
|
11758
11964
|
await ignoreIfPrimaryLeaseLoss(e);
|
|
11759
11965
|
}
|
|
11760
11966
|
}
|
|
11761
|
-
await this.scheduleGC(
|
|
11967
|
+
await this.scheduleGC(REGULAR_GC_DELAY_MS);
|
|
11762
11968
|
});
|
|
11763
11969
|
}
|
|
11764
11970
|
}
|
|
@@ -12527,31 +12733,6 @@ function dbKeyComparator(l, r) {
|
|
|
12527
12733
|
}
|
|
12528
12734
|
return primitiveComparator(left[left.length - 1], right[right.length - 1]);
|
|
12529
12735
|
}
|
|
12530
|
-
/**
|
|
12531
|
-
* Schema Version for the Web client:
|
|
12532
|
-
* 1. Initial version including Mutation Queue, Query Cache, and Remote
|
|
12533
|
-
* Document Cache
|
|
12534
|
-
* 2. Used to ensure a targetGlobal object exists and add targetCount to it. No
|
|
12535
|
-
* longer required because migration 3 unconditionally clears it.
|
|
12536
|
-
* 3. Dropped and re-created Query Cache to deal with cache corruption related
|
|
12537
|
-
* to limbo resolution. Addresses
|
|
12538
|
-
* https://github.com/firebase/firebase-ios-sdk/issues/1548
|
|
12539
|
-
* 4. Multi-Tab Support.
|
|
12540
|
-
* 5. Removal of held write acks.
|
|
12541
|
-
* 6. Create document global for tracking document cache size.
|
|
12542
|
-
* 7. Ensure every cached document has a sentinel row with a sequence number.
|
|
12543
|
-
* 8. Add collection-parent index for Collection Group queries.
|
|
12544
|
-
* 9. Change RemoteDocumentChanges store to be keyed by readTime rather than
|
|
12545
|
-
* an auto-incrementing ID. This is required for Index-Free queries.
|
|
12546
|
-
* 10. Rewrite the canonical IDs to the explicit Protobuf-based format.
|
|
12547
|
-
* 11. Add bundles and named_queries for bundle support.
|
|
12548
|
-
* 12. Add document overlays.
|
|
12549
|
-
* 13. Rewrite the keys of the remote document cache to allow for efficient
|
|
12550
|
-
* document lookup via `getAll()`.
|
|
12551
|
-
* 14. Add overlays.
|
|
12552
|
-
* 15. Add indexing support.
|
|
12553
|
-
*/
|
|
12554
|
-
const SCHEMA_VERSION = 14;
|
|
12555
12736
|
|
|
12556
12737
|
/**
|
|
12557
12738
|
* @license
|
|
@@ -12682,10 +12863,10 @@ class LocalDocumentsView {
|
|
|
12682
12863
|
* if the map does not already contain an entry for the given document key.
|
|
12683
12864
|
*/
|
|
12684
12865
|
populateOverlays(transaction, overlays, docs) {
|
|
12685
|
-
|
|
12866
|
+
const missingOverlays = [];
|
|
12686
12867
|
docs.forEach(key => {
|
|
12687
12868
|
if (!overlays.has(key)) {
|
|
12688
|
-
missingOverlays
|
|
12869
|
+
missingOverlays.push(key);
|
|
12689
12870
|
}
|
|
12690
12871
|
});
|
|
12691
12872
|
return this.documentOverlayCache
|
|
@@ -12711,7 +12892,7 @@ class LocalDocumentsView {
|
|
|
12711
12892
|
computeViews(transaction, docs, overlays, existenceStateChanged) {
|
|
12712
12893
|
let recalculateDocuments = mutableDocumentMap();
|
|
12713
12894
|
const mutatedFields = newDocumentKeyMap();
|
|
12714
|
-
const results =
|
|
12895
|
+
const results = newOverlayedDocumentMap();
|
|
12715
12896
|
docs.forEach((_, doc) => {
|
|
12716
12897
|
const overlay = overlays.get(doc.key);
|
|
12717
12898
|
// Recalculate an overlay if the document's existence state changed due to
|
|
@@ -12813,6 +12994,55 @@ class LocalDocumentsView {
|
|
|
12813
12994
|
return this.getDocumentsMatchingCollectionQuery(transaction, query, offset);
|
|
12814
12995
|
}
|
|
12815
12996
|
}
|
|
12997
|
+
/**
|
|
12998
|
+
* Given a collection group, returns the next documents that follow the provided offset, along
|
|
12999
|
+
* with an updated batch ID.
|
|
13000
|
+
*
|
|
13001
|
+
* <p>The documents returned by this method are ordered by remote version from the provided
|
|
13002
|
+
* offset. If there are no more remote documents after the provided offset, documents with
|
|
13003
|
+
* mutations in order of batch id from the offset are returned. Since all documents in a batch are
|
|
13004
|
+
* returned together, the total number of documents returned can exceed {@code count}.
|
|
13005
|
+
*
|
|
13006
|
+
* @param transaction
|
|
13007
|
+
* @param collectionGroup The collection group for the documents.
|
|
13008
|
+
* @param offset The offset to index into.
|
|
13009
|
+
* @param count The number of documents to return
|
|
13010
|
+
* @return A LocalWriteResult with the documents that follow the provided offset and the last processed batch id.
|
|
13011
|
+
*/
|
|
13012
|
+
getNextDocuments(transaction, collectionGroup, offset, count) {
|
|
13013
|
+
return this.remoteDocumentCache
|
|
13014
|
+
.getAllFromCollectionGroup(transaction, collectionGroup, offset, count)
|
|
13015
|
+
.next((originalDocs) => {
|
|
13016
|
+
const overlaysPromise = count - originalDocs.size > 0
|
|
13017
|
+
? this.documentOverlayCache.getOverlaysForCollectionGroup(transaction, collectionGroup, offset.largestBatchId, count - originalDocs.size)
|
|
13018
|
+
: PersistencePromise.resolve(newOverlayMap());
|
|
13019
|
+
// The callsite will use the largest batch ID together with the latest read time to create
|
|
13020
|
+
// a new index offset. Since we only process batch IDs if all remote documents have been read,
|
|
13021
|
+
// no overlay will increase the overall read time. This is why we only need to special case
|
|
13022
|
+
// the batch id.
|
|
13023
|
+
let largestBatchId = INITIAL_LARGEST_BATCH_ID;
|
|
13024
|
+
let modifiedDocs = originalDocs;
|
|
13025
|
+
return overlaysPromise.next(overlays => {
|
|
13026
|
+
return PersistencePromise.forEach(overlays, (key, overlay) => {
|
|
13027
|
+
if (largestBatchId < overlay.largestBatchId) {
|
|
13028
|
+
largestBatchId = overlay.largestBatchId;
|
|
13029
|
+
}
|
|
13030
|
+
if (originalDocs.get(key)) {
|
|
13031
|
+
return PersistencePromise.resolve();
|
|
13032
|
+
}
|
|
13033
|
+
return this.getBaseDocument(transaction, key, overlay).next(doc => {
|
|
13034
|
+
modifiedDocs = modifiedDocs.insert(key, doc);
|
|
13035
|
+
});
|
|
13036
|
+
})
|
|
13037
|
+
.next(() => this.populateOverlays(transaction, overlays, originalDocs))
|
|
13038
|
+
.next(() => this.computeViews(transaction, modifiedDocs, overlays, documentKeySet()))
|
|
13039
|
+
.next(localDocs => ({
|
|
13040
|
+
batchId: largestBatchId,
|
|
13041
|
+
changes: convertOverlayedDocumentMapToDocumentMap(localDocs)
|
|
13042
|
+
}));
|
|
13043
|
+
});
|
|
13044
|
+
});
|
|
13045
|
+
}
|
|
12816
13046
|
getDocumentsMatchingDocumentQuery(transaction, docPath) {
|
|
12817
13047
|
// Just do a simple document lookup.
|
|
12818
13048
|
return this.getDocument(transaction, new DocumentKey(docPath)).next(document => {
|
|
@@ -12876,7 +13106,7 @@ class LocalDocumentsView {
|
|
|
12876
13106
|
}
|
|
12877
13107
|
/** Returns a base document that can be used to apply `overlay`. */
|
|
12878
13108
|
getBaseDocument(transaction, key, overlay) {
|
|
12879
|
-
return overlay === null || overlay.mutation
|
|
13109
|
+
return overlay === null || overlay.mutation.type === 1 /* Patch */
|
|
12880
13110
|
? this.remoteDocumentCache.getEntry(transaction, key)
|
|
12881
13111
|
: PersistencePromise.resolve(MutableDocument.newInvalidDocument(key));
|
|
12882
13112
|
}
|
|
@@ -12951,15 +13181,13 @@ class MemoryDocumentOverlayCache {
|
|
|
12951
13181
|
}
|
|
12952
13182
|
getOverlays(transaction, keys) {
|
|
12953
13183
|
const result = newOverlayMap();
|
|
12954
|
-
|
|
12955
|
-
|
|
12956
|
-
promises.push(this.getOverlay(transaction, key).next(overlay => {
|
|
13184
|
+
return PersistencePromise.forEach(keys, (key) => {
|
|
13185
|
+
return this.getOverlay(transaction, key).next(overlay => {
|
|
12957
13186
|
if (overlay !== null) {
|
|
12958
13187
|
result.set(key, overlay);
|
|
12959
13188
|
}
|
|
12960
|
-
})
|
|
12961
|
-
});
|
|
12962
|
-
return PersistencePromise.waitFor(promises).next(() => result);
|
|
13189
|
+
});
|
|
13190
|
+
}).next(() => result);
|
|
12963
13191
|
}
|
|
12964
13192
|
saveOverlays(transaction, largestBatchId, overlays) {
|
|
12965
13193
|
overlays.forEach((_, mutation) => {
|
|
@@ -14123,11 +14351,8 @@ class SchemaConverter {
|
|
|
14123
14351
|
const mutationsStore = transaction.store(DbMutationBatchStore);
|
|
14124
14352
|
const remoteDocumentCache = newIndexedDbRemoteDocumentCache(this.serializer);
|
|
14125
14353
|
const memoryPersistence = new MemoryPersistence(MemoryEagerDelegate.factory, this.serializer.remoteSerializer);
|
|
14126
|
-
|
|
14127
|
-
|
|
14128
|
-
return mutationsStore
|
|
14129
|
-
.loadAll()
|
|
14130
|
-
.next(dbBatches => {
|
|
14354
|
+
return mutationsStore.loadAll().next(dbBatches => {
|
|
14355
|
+
const userToDocumentSet = new Map();
|
|
14131
14356
|
dbBatches.forEach(dbBatch => {
|
|
14132
14357
|
var _a;
|
|
14133
14358
|
let documentSet = (_a = userToDocumentSet.get(dbBatch.userId)) !== null && _a !== void 0 ? _a : documentKeySet();
|
|
@@ -14135,9 +14360,7 @@ class SchemaConverter {
|
|
|
14135
14360
|
batch.keys().forEach(key => (documentSet = documentSet.add(key)));
|
|
14136
14361
|
userToDocumentSet.set(dbBatch.userId, documentSet);
|
|
14137
14362
|
});
|
|
14138
|
-
|
|
14139
|
-
.next(() => {
|
|
14140
|
-
userToDocumentSet.forEach((allDocumentKeysForUser, userId) => {
|
|
14363
|
+
return PersistencePromise.forEach(userToDocumentSet, (allDocumentKeysForUser, userId) => {
|
|
14141
14364
|
const user = new User(userId);
|
|
14142
14365
|
const documentOverlayCache = IndexedDbDocumentOverlayCache.forUser(this.serializer, user);
|
|
14143
14366
|
// NOTE: The index manager and the reference delegate are
|
|
@@ -14147,10 +14370,11 @@ class SchemaConverter {
|
|
|
14147
14370
|
const indexManager = memoryPersistence.getIndexManager(user);
|
|
14148
14371
|
const mutationQueue = IndexedDbMutationQueue.forUser(user, this.serializer, indexManager, memoryPersistence.referenceDelegate);
|
|
14149
14372
|
const localDocumentsView = new LocalDocumentsView(remoteDocumentCache, mutationQueue, documentOverlayCache, indexManager);
|
|
14150
|
-
|
|
14373
|
+
return localDocumentsView
|
|
14374
|
+
.recalculateAndSaveOverlaysForDocumentKeys(new IndexedDbTransaction(transaction, ListenSequence.INVALID), allDocumentKeysForUser)
|
|
14375
|
+
.next();
|
|
14151
14376
|
});
|
|
14152
|
-
})
|
|
14153
|
-
.next(() => PersistencePromise.waitFor(promises));
|
|
14377
|
+
});
|
|
14154
14378
|
}
|
|
14155
14379
|
}
|
|
14156
14380
|
function sentinelKey(path) {
|
|
@@ -15215,7 +15439,7 @@ function localStoreWriteLocally(localStore, mutations) {
|
|
|
15215
15439
|
// any additional user-provided writes.
|
|
15216
15440
|
return localStoreImpl.localDocuments.getOverlayedDocuments(txn, remoteDocs);
|
|
15217
15441
|
})
|
|
15218
|
-
.next(docs => {
|
|
15442
|
+
.next((docs) => {
|
|
15219
15443
|
overlayedDocuments = docs;
|
|
15220
15444
|
// For non-idempotent mutations (such as `FieldValue.increment()`),
|
|
15221
15445
|
// we record the base state in a separate patch mutation. This is
|
|
@@ -15240,11 +15464,10 @@ function localStoreWriteLocally(localStore, mutations) {
|
|
|
15240
15464
|
return localStoreImpl.documentOverlayCache.saveOverlays(txn, batch.batchId, overlays);
|
|
15241
15465
|
});
|
|
15242
15466
|
})
|
|
15243
|
-
.then(() => {
|
|
15244
|
-
|
|
15245
|
-
|
|
15246
|
-
|
|
15247
|
-
});
|
|
15467
|
+
.then(() => ({
|
|
15468
|
+
batchId: mutationBatch.batchId,
|
|
15469
|
+
changes: convertOverlayedDocumentMapToDocumentMap(overlayedDocuments)
|
|
15470
|
+
}));
|
|
15248
15471
|
}
|
|
15249
15472
|
/**
|
|
15250
15473
|
* Acknowledges the given batch.
|
|
@@ -23628,10 +23851,14 @@ class MemoryOfflineComponentProvider {
|
|
|
23628
23851
|
this.sharedClientState = this.createSharedClientState(cfg);
|
|
23629
23852
|
this.persistence = this.createPersistence(cfg);
|
|
23630
23853
|
await this.persistence.start();
|
|
23631
|
-
this.gcScheduler = this.createGarbageCollectionScheduler(cfg);
|
|
23632
23854
|
this.localStore = this.createLocalStore(cfg);
|
|
23855
|
+
this.gcScheduler = this.createGarbageCollectionScheduler(cfg, this.localStore);
|
|
23856
|
+
this.indexBackfillerScheduler = this.createIndexBackfillerScheduler(cfg, this.localStore);
|
|
23633
23857
|
}
|
|
23634
|
-
createGarbageCollectionScheduler(cfg) {
|
|
23858
|
+
createGarbageCollectionScheduler(cfg, localStore) {
|
|
23859
|
+
return null;
|
|
23860
|
+
}
|
|
23861
|
+
createIndexBackfillerScheduler(cfg, localStore) {
|
|
23635
23862
|
return null;
|
|
23636
23863
|
}
|
|
23637
23864
|
createLocalStore(cfg) {
|
|
@@ -23672,7 +23899,11 @@ class IndexedDbOfflineComponentProvider extends MemoryOfflineComponentProvider {
|
|
|
23672
23899
|
// set it after localStore / remoteStore are started.
|
|
23673
23900
|
await this.persistence.setPrimaryStateListener(() => {
|
|
23674
23901
|
if (this.gcScheduler && !this.gcScheduler.started) {
|
|
23675
|
-
this.gcScheduler.start(
|
|
23902
|
+
this.gcScheduler.start();
|
|
23903
|
+
}
|
|
23904
|
+
if (this.indexBackfillerScheduler &&
|
|
23905
|
+
!this.indexBackfillerScheduler.started) {
|
|
23906
|
+
this.indexBackfillerScheduler.start();
|
|
23676
23907
|
}
|
|
23677
23908
|
return Promise.resolve();
|
|
23678
23909
|
});
|
|
@@ -23680,9 +23911,13 @@ class IndexedDbOfflineComponentProvider extends MemoryOfflineComponentProvider {
|
|
|
23680
23911
|
createLocalStore(cfg) {
|
|
23681
23912
|
return newLocalStore(this.persistence, new QueryEngine(), cfg.initialUser, this.serializer);
|
|
23682
23913
|
}
|
|
23683
|
-
createGarbageCollectionScheduler(cfg) {
|
|
23914
|
+
createGarbageCollectionScheduler(cfg, localStore) {
|
|
23684
23915
|
const garbageCollector = this.persistence.referenceDelegate.garbageCollector;
|
|
23685
|
-
return new LruScheduler(garbageCollector, cfg.asyncQueue);
|
|
23916
|
+
return new LruScheduler(garbageCollector, cfg.asyncQueue, localStore);
|
|
23917
|
+
}
|
|
23918
|
+
createIndexBackfillerScheduler(cfg, localStore) {
|
|
23919
|
+
const indexBackfiller = new IndexBackfiller(localStore, this.persistence);
|
|
23920
|
+
return new IndexBackfillerScheduler(cfg.asyncQueue, indexBackfiller);
|
|
23686
23921
|
}
|
|
23687
23922
|
createPersistence(cfg) {
|
|
23688
23923
|
const persistenceKey = indexedDbStoragePrefix(cfg.databaseInfo.databaseId, cfg.databaseInfo.persistenceKey);
|
|
@@ -23729,12 +23964,20 @@ class MultiTabOfflineComponentProvider extends IndexedDbOfflineComponentProvider
|
|
|
23729
23964
|
await syncEngineApplyPrimaryState(this.onlineComponentProvider.syncEngine, isPrimary);
|
|
23730
23965
|
if (this.gcScheduler) {
|
|
23731
23966
|
if (isPrimary && !this.gcScheduler.started) {
|
|
23732
|
-
this.gcScheduler.start(
|
|
23967
|
+
this.gcScheduler.start();
|
|
23733
23968
|
}
|
|
23734
23969
|
else if (!isPrimary) {
|
|
23735
23970
|
this.gcScheduler.stop();
|
|
23736
23971
|
}
|
|
23737
23972
|
}
|
|
23973
|
+
if (this.indexBackfillerScheduler) {
|
|
23974
|
+
if (isPrimary && !this.indexBackfillerScheduler.started) {
|
|
23975
|
+
this.indexBackfillerScheduler.start();
|
|
23976
|
+
}
|
|
23977
|
+
else if (!isPrimary) {
|
|
23978
|
+
this.indexBackfillerScheduler.stop();
|
|
23979
|
+
}
|
|
23980
|
+
}
|
|
23738
23981
|
});
|
|
23739
23982
|
}
|
|
23740
23983
|
createSharedClientState(cfg) {
|