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