@firebase/firestore
Version:
The Cloud Firestore component of the Firebase JS SDK.
1,363 lines (1,343 loc) • 736 kB
JavaScript
import { createMockUserToken, getModularInstance } from '@firebase/util';
import { Logger, LogLevel } from '@firebase/logger';
import { inspect, TextEncoder, TextDecoder } from 'util';
import { randomBytes as randomBytes$1 } from 'crypto';
import { credentials, Metadata, loadPackageDefinition } from '@grpc/grpc-js';
import { version as version$1 } from '@grpc/grpc-js/package.json';
import { resolve, join } from 'path';
import { loadSync } from '@grpc/proto-loader';
const version = "8.6.7";
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
let SDK_VERSION = version;
/**
* @license
* Copyright 2018 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* `ListenSequence` is a monotonic sequence. It is initialized with a minimum value to
* exceed. All subsequent calls to next will return increasing values. If provided with a
* `SequenceNumberSyncer`, it will additionally bump its next value when told of a new value, as
* well as write out sequence numbers that it produces via `next()`.
*/
class ListenSequence {
constructor(previousValue, sequenceNumberSyncer) {
this.previousValue = previousValue;
if (sequenceNumberSyncer) {
sequenceNumberSyncer.sequenceNumberHandler = sequenceNumber => this.setPreviousValue(sequenceNumber);
this.writeNewSequenceNumber = sequenceNumber => sequenceNumberSyncer.writeSequenceNumber(sequenceNumber);
}
}
setPreviousValue(externalPreviousValue) {
this.previousValue = Math.max(externalPreviousValue, this.previousValue);
return this.previousValue;
}
next() {
const nextValue = ++this.previousValue;
if (this.writeNewSequenceNumber) {
this.writeNewSequenceNumber(nextValue);
}
return nextValue;
}
}
ListenSequence.INVALID = -1;
/**
* @license
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** Formats an object as a JSON string, suitable for logging. */
function formatJSON(value) {
// util.inspect() results in much more readable output than JSON.stringify()
return inspect(value, { depth: 100 });
}
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const logClient = new Logger('@firebase/firestore');
// Helper methods are needed because variables can't be exported as read/write
function getLogLevel() {
return logClient.logLevel;
}
/**
* Sets the verbosity of Cloud Firestore logs (debug, error, or silent).
*
* @param logLevel - The verbosity you set for activity and error logging. Can
* be any of the following values:
*
* <ul>
* <li>`debug` for the most verbose logging level, primarily for
* debugging.</li>
* <li>`error` to log errors only.</li>
* <li><code>`silent` to turn off logging.</li>
* </ul>
*/
function setLogLevel(logLevel) {
logClient.setLogLevel(logLevel);
}
function logDebug(msg, ...obj) {
if (logClient.logLevel <= LogLevel.DEBUG) {
const args = obj.map(argToString);
logClient.debug(`Firestore (${SDK_VERSION}): ${msg}`, ...args);
}
}
function logError(msg, ...obj) {
if (logClient.logLevel <= LogLevel.ERROR) {
const args = obj.map(argToString);
logClient.error(`Firestore (${SDK_VERSION}): ${msg}`, ...args);
}
}
function logWarn(msg, ...obj) {
if (logClient.logLevel <= LogLevel.WARN) {
const args = obj.map(argToString);
logClient.warn(`Firestore (${SDK_VERSION}): ${msg}`, ...args);
}
}
/**
* Converts an additional log parameter to a string representation.
*/
function argToString(obj) {
if (typeof obj === 'string') {
return obj;
}
else {
try {
return formatJSON(obj);
}
catch (e) {
// Converting to JSON failed, just log the object directly
return obj;
}
}
}
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Unconditionally fails, throwing an Error with the given message.
* Messages are stripped in production builds.
*
* Returns `never` and can be used in expressions:
* @example
* let futureVar = fail('not implemented yet');
*/
function fail(failure = 'Unexpected state') {
// Log the failure in addition to throw an exception, just in case the
// exception is swallowed.
const message = `FIRESTORE (${SDK_VERSION}) INTERNAL ASSERTION FAILED: ` + failure;
logError(message);
// NOTE: We don't use FirestoreError here because these are internal failures
// that cannot be handled by the user. (Also it would create a circular
// dependency between the error and assert modules which doesn't work.)
throw new Error(message);
}
/**
* Fails if the given assertion condition is false, throwing an Error with the
* given message if it did.
*
* Messages are stripped in production builds.
*/
function hardAssert(assertion, message) {
if (!assertion) {
fail();
}
}
/**
* Casts `obj` to `T`. In non-production builds, verifies that `obj` is an
* instance of `T` before casting.
*/
function debugCast(obj,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
constructor) {
return obj;
}
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const Code = {
// Causes are copied from:
// https://github.com/grpc/grpc/blob/bceec94ea4fc5f0085d81235d8e1c06798dc341a/include/grpc%2B%2B/impl/codegen/status_code_enum.h
/** Not an error; returned on success. */
OK: 'ok',
/** The operation was cancelled (typically by the caller). */
CANCELLED: 'cancelled',
/** Unknown error or an error from a different error domain. */
UNKNOWN: 'unknown',
/**
* Client specified an invalid argument. Note that this differs from
* FAILED_PRECONDITION. INVALID_ARGUMENT indicates arguments that are
* problematic regardless of the state of the system (e.g., a malformed file
* name).
*/
INVALID_ARGUMENT: 'invalid-argument',
/**
* Deadline expired before operation could complete. For operations that
* change the state of the system, this error may be returned even if the
* operation has completed successfully. For example, a successful response
* from a server could have been delayed long enough for the deadline to
* expire.
*/
DEADLINE_EXCEEDED: 'deadline-exceeded',
/** Some requested entity (e.g., file or directory) was not found. */
NOT_FOUND: 'not-found',
/**
* Some entity that we attempted to create (e.g., file or directory) already
* exists.
*/
ALREADY_EXISTS: 'already-exists',
/**
* The caller does not have permission to execute the specified operation.
* PERMISSION_DENIED must not be used for rejections caused by exhausting
* some resource (use RESOURCE_EXHAUSTED instead for those errors).
* PERMISSION_DENIED must not be used if the caller can not be identified
* (use UNAUTHENTICATED instead for those errors).
*/
PERMISSION_DENIED: 'permission-denied',
/**
* The request does not have valid authentication credentials for the
* operation.
*/
UNAUTHENTICATED: 'unauthenticated',
/**
* Some resource has been exhausted, perhaps a per-user quota, or perhaps the
* entire file system is out of space.
*/
RESOURCE_EXHAUSTED: 'resource-exhausted',
/**
* Operation was rejected because the system is not in a state required for
* the operation's execution. For example, directory to be deleted may be
* non-empty, an rmdir operation is applied to a non-directory, etc.
*
* A litmus test that may help a service implementor in deciding
* between FAILED_PRECONDITION, ABORTED, and UNAVAILABLE:
* (a) Use UNAVAILABLE if the client can retry just the failing call.
* (b) Use ABORTED if the client should retry at a higher-level
* (e.g., restarting a read-modify-write sequence).
* (c) Use FAILED_PRECONDITION if the client should not retry until
* the system state has been explicitly fixed. E.g., if an "rmdir"
* fails because the directory is non-empty, FAILED_PRECONDITION
* should be returned since the client should not retry unless
* they have first fixed up the directory by deleting files from it.
* (d) Use FAILED_PRECONDITION if the client performs conditional
* REST Get/Update/Delete on a resource and the resource on the
* server does not match the condition. E.g., conflicting
* read-modify-write on the same resource.
*/
FAILED_PRECONDITION: 'failed-precondition',
/**
* The operation was aborted, typically due to a concurrency issue like
* sequencer check failures, transaction aborts, etc.
*
* See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,
* and UNAVAILABLE.
*/
ABORTED: 'aborted',
/**
* Operation was attempted past the valid range. E.g., seeking or reading
* past end of file.
*
* Unlike INVALID_ARGUMENT, this error indicates a problem that may be fixed
* if the system state changes. For example, a 32-bit file system will
* generate INVALID_ARGUMENT if asked to read at an offset that is not in the
* range [0,2^32-1], but it will generate OUT_OF_RANGE if asked to read from
* an offset past the current file size.
*
* There is a fair bit of overlap between FAILED_PRECONDITION and
* OUT_OF_RANGE. We recommend using OUT_OF_RANGE (the more specific error)
* when it applies so that callers who are iterating through a space can
* easily look for an OUT_OF_RANGE error to detect when they are done.
*/
OUT_OF_RANGE: 'out-of-range',
/** Operation is not implemented or not supported/enabled in this service. */
UNIMPLEMENTED: 'unimplemented',
/**
* Internal errors. Means some invariants expected by underlying System has
* been broken. If you see one of these errors, Something is very broken.
*/
INTERNAL: 'internal',
/**
* The service is currently unavailable. This is a most likely a transient
* condition and may be corrected by retrying with a backoff.
*
* See litmus test above for deciding between FAILED_PRECONDITION, ABORTED,
* and UNAVAILABLE.
*/
UNAVAILABLE: 'unavailable',
/** Unrecoverable data loss or corruption. */
DATA_LOSS: 'data-loss'
};
/** An error returned by a Firestore operation. */
class FirestoreError extends Error {
/** @hideconstructor */
constructor(
/**
* The backend error code associated with this error.
*/
code,
/**
* A custom error description.
*/
message) {
super(message);
this.code = code;
this.message = message;
/** The custom name for all FirestoreErrors. */
this.name = 'FirebaseError';
// HACK: We write a toString property directly because Error is not a real
// class and so inheritance does not work correctly. We could alternatively
// do the same "back-door inheritance" trick that FirebaseError does.
this.toString = () => `${this.name}: [code=${this.code}]: ${this.message}`;
}
}
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const DOCUMENT_KEY_NAME = '__name__';
/**
* Path represents an ordered sequence of string segments.
*/
class BasePath {
constructor(segments, offset, length) {
if (offset === undefined) {
offset = 0;
}
else if (offset > segments.length) {
fail();
}
if (length === undefined) {
length = segments.length - offset;
}
else if (length > segments.length - offset) {
fail();
}
this.segments = segments;
this.offset = offset;
this.len = length;
}
get length() {
return this.len;
}
isEqual(other) {
return BasePath.comparator(this, other) === 0;
}
child(nameOrPath) {
const segments = this.segments.slice(this.offset, this.limit());
if (nameOrPath instanceof BasePath) {
nameOrPath.forEach(segment => {
segments.push(segment);
});
}
else {
segments.push(nameOrPath);
}
return this.construct(segments);
}
/** The index of one past the last segment of the path. */
limit() {
return this.offset + this.length;
}
popFirst(size) {
size = size === undefined ? 1 : size;
return this.construct(this.segments, this.offset + size, this.length - size);
}
popLast() {
return this.construct(this.segments, this.offset, this.length - 1);
}
firstSegment() {
return this.segments[this.offset];
}
lastSegment() {
return this.get(this.length - 1);
}
get(index) {
return this.segments[this.offset + index];
}
isEmpty() {
return this.length === 0;
}
isPrefixOf(other) {
if (other.length < this.length) {
return false;
}
for (let i = 0; i < this.length; i++) {
if (this.get(i) !== other.get(i)) {
return false;
}
}
return true;
}
isImmediateParentOf(potentialChild) {
if (this.length + 1 !== potentialChild.length) {
return false;
}
for (let i = 0; i < this.length; i++) {
if (this.get(i) !== potentialChild.get(i)) {
return false;
}
}
return true;
}
forEach(fn) {
for (let i = this.offset, end = this.limit(); i < end; i++) {
fn(this.segments[i]);
}
}
toArray() {
return this.segments.slice(this.offset, this.limit());
}
static comparator(p1, p2) {
const len = Math.min(p1.length, p2.length);
for (let i = 0; i < len; i++) {
const left = p1.get(i);
const right = p2.get(i);
if (left < right) {
return -1;
}
if (left > right) {
return 1;
}
}
if (p1.length < p2.length) {
return -1;
}
if (p1.length > p2.length) {
return 1;
}
return 0;
}
}
/**
* A slash-separated path for navigating resources (documents and collections)
* within Firestore.
*/
class ResourcePath extends BasePath {
construct(segments, offset, length) {
return new ResourcePath(segments, offset, length);
}
canonicalString() {
// NOTE: The client is ignorant of any path segments containing escape
// sequences (e.g. __id123__) and just passes them through raw (they exist
// for legacy reasons and should not be used frequently).
return this.toArray().join('/');
}
toString() {
return this.canonicalString();
}
/**
* Creates a resource path from the given slash-delimited string. If multiple
* arguments are provided, all components are combined. Leading and trailing
* slashes from all components are ignored.
*/
static fromString(...pathComponents) {
// NOTE: The client is ignorant of any path segments containing escape
// sequences (e.g. __id123__) and just passes them through raw (they exist
// for legacy reasons and should not be used frequently).
const segments = [];
for (const path of pathComponents) {
if (path.indexOf('//') >= 0) {
throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid segment (${path}). Paths must not contain // in them.`);
}
// Strip leading and traling slashed.
segments.push(...path.split('/').filter(segment => segment.length > 0));
}
return new ResourcePath(segments);
}
static emptyPath() {
return new ResourcePath([]);
}
}
const identifierRegExp = /^[_a-zA-Z][_a-zA-Z0-9]*$/;
/** A dot-separated path for navigating sub-objects within a document. */
class FieldPath extends BasePath {
construct(segments, offset, length) {
return new FieldPath(segments, offset, length);
}
/**
* Returns true if the string could be used as a segment in a field path
* without escaping.
*/
static isValidIdentifier(segment) {
return identifierRegExp.test(segment);
}
canonicalString() {
return this.toArray()
.map(str => {
str = str.replace(/\\/g, '\\\\').replace(/`/g, '\\`');
if (!FieldPath.isValidIdentifier(str)) {
str = '`' + str + '`';
}
return str;
})
.join('.');
}
toString() {
return this.canonicalString();
}
/**
* Returns true if this field references the key of a document.
*/
isKeyField() {
return this.length === 1 && this.get(0) === DOCUMENT_KEY_NAME;
}
/**
* The field designating the key of a document.
*/
static keyField() {
return new FieldPath([DOCUMENT_KEY_NAME]);
}
/**
* Parses a field string from the given server-formatted string.
*
* - Splitting the empty string is not allowed (for now at least).
* - Empty segments within the string (e.g. if there are two consecutive
* separators) are not allowed.
*
* TODO(b/37244157): we should make this more strict. Right now, it allows
* non-identifier path components, even if they aren't escaped.
*/
static fromServerFormat(path) {
const segments = [];
let current = '';
let i = 0;
const addCurrentSegment = () => {
if (current.length === 0) {
throw new FirestoreError(Code.INVALID_ARGUMENT, `Invalid field path (${path}). Paths must not be empty, begin ` +
`with '.', end with '.', or contain '..'`);
}
segments.push(current);
current = '';
};
let inBackticks = false;
while (i < path.length) {
const c = path[i];
if (c === '\\') {
if (i + 1 === path.length) {
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Path has trailing escape character: ' + path);
}
const next = path[i + 1];
if (!(next === '\\' || next === '.' || next === '`')) {
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Path has invalid escape sequence: ' + path);
}
current += next;
i += 2;
}
else if (c === '`') {
inBackticks = !inBackticks;
i++;
}
else if (c === '.' && !inBackticks) {
addCurrentSegment();
i++;
}
else {
current += c;
i++;
}
}
addCurrentSegment();
if (inBackticks) {
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Unterminated ` in path: ' + path);
}
return new FieldPath(segments);
}
static emptyPath() {
return new FieldPath([]);
}
}
/**
* @license
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
const PRIMARY_LEASE_LOST_ERROR_MSG = 'The current tab is not in the required state to perform this operation. ' +
'It might be necessary to refresh the browser tab.';
/**
* A base class representing a persistence transaction, encapsulating both the
* transaction's sequence numbers as well as a list of onCommitted listeners.
*
* When you call Persistence.runTransaction(), it will create a transaction and
* pass it to your callback. You then pass it to any method that operates
* on persistence.
*/
class PersistenceTransaction {
constructor() {
this.onCommittedListeners = [];
}
addOnCommittedListener(listener) {
this.onCommittedListeners.push(listener);
}
raiseOnCommittedEvent() {
this.onCommittedListeners.forEach(listener => listener());
}
}
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class Deferred {
constructor() {
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
}
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* PersistencePromise is essentially a re-implementation of Promise except
* it has a .next() method instead of .then() and .next() and .catch() callbacks
* are executed synchronously when a PersistencePromise resolves rather than
* asynchronously (Promise implementations use setImmediate() or similar).
*
* This is necessary to interoperate with IndexedDB which will automatically
* commit transactions if control is returned to the event loop without
* synchronously initiating another operation on the transaction.
*
* NOTE: .then() and .catch() only allow a single consumer, unlike normal
* Promises.
*/
class PersistencePromise {
constructor(callback) {
// NOTE: next/catchCallback will always point to our own wrapper functions,
// not the user's raw next() or catch() callbacks.
this.nextCallback = null;
this.catchCallback = null;
// When the operation resolves, we'll set result or error and mark isDone.
this.result = undefined;
this.error = undefined;
this.isDone = false;
// Set to true when .then() or .catch() are called and prevents additional
// chaining.
this.callbackAttached = false;
callback(value => {
this.isDone = true;
this.result = value;
if (this.nextCallback) {
// value should be defined unless T is Void, but we can't express
// that in the type system.
this.nextCallback(value);
}
}, error => {
this.isDone = true;
this.error = error;
if (this.catchCallback) {
this.catchCallback(error);
}
});
}
catch(fn) {
return this.next(undefined, fn);
}
next(nextFn, catchFn) {
if (this.callbackAttached) {
fail();
}
this.callbackAttached = true;
if (this.isDone) {
if (!this.error) {
return this.wrapSuccess(nextFn, this.result);
}
else {
return this.wrapFailure(catchFn, this.error);
}
}
else {
return new PersistencePromise((resolve, reject) => {
this.nextCallback = (value) => {
this.wrapSuccess(nextFn, value).next(resolve, reject);
};
this.catchCallback = (error) => {
this.wrapFailure(catchFn, error).next(resolve, reject);
};
});
}
}
toPromise() {
return new Promise((resolve, reject) => {
this.next(resolve, reject);
});
}
wrapUserFunction(fn) {
try {
const result = fn();
if (result instanceof PersistencePromise) {
return result;
}
else {
return PersistencePromise.resolve(result);
}
}
catch (e) {
return PersistencePromise.reject(e);
}
}
wrapSuccess(nextFn, value) {
if (nextFn) {
return this.wrapUserFunction(() => nextFn(value));
}
else {
// If there's no nextFn, then R must be the same as T
return PersistencePromise.resolve(value);
}
}
wrapFailure(catchFn, error) {
if (catchFn) {
return this.wrapUserFunction(() => catchFn(error));
}
else {
return PersistencePromise.reject(error);
}
}
static resolve(result) {
return new PersistencePromise((resolve, reject) => {
resolve(result);
});
}
static reject(error) {
return new PersistencePromise((resolve, reject) => {
reject(error);
});
}
static waitFor(
// Accept all Promise types in waitFor().
// eslint-disable-next-line @typescript-eslint/no-explicit-any
all) {
return new PersistencePromise((resolve, reject) => {
let expectedCount = 0;
let resolvedCount = 0;
let done = false;
all.forEach(element => {
++expectedCount;
element.next(() => {
++resolvedCount;
if (done && resolvedCount === expectedCount) {
resolve();
}
}, err => reject(err));
});
done = true;
if (resolvedCount === expectedCount) {
resolve();
}
});
}
/**
* Given an array of predicate functions that asynchronously evaluate to a
* boolean, implements a short-circuiting `or` between the results. Predicates
* will be evaluated until one of them returns `true`, then stop. The final
* result will be whether any of them returned `true`.
*/
static or(predicates) {
let p = PersistencePromise.resolve(false);
for (const predicate of predicates) {
p = p.next(isTrue => {
if (isTrue) {
return PersistencePromise.resolve(isTrue);
}
else {
return predicate();
}
});
}
return p;
}
static forEach(collection, f) {
const promises = [];
collection.forEach((r, s) => {
promises.push(f.call(this, r, s));
});
return this.waitFor(promises);
}
}
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** Verifies whether `e` is an IndexedDbTransactionError. */
function isIndexedDbTransactionError(e) {
// Use name equality, as instanceof checks on errors don't work with errors
// that wrap other errors.
return e.name === 'IndexedDbTransactionError';
}
/**
* @license
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Generates `nBytes` of random bytes.
*
* If `nBytes < 0` , an error will be thrown.
*/
function randomBytes(nBytes) {
return randomBytes$1(nBytes);
}
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class AutoId {
static newId() {
// Alphanumeric characters
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
// The largest byte value that is a multiple of `char.length`.
const maxMultiple = Math.floor(256 / chars.length) * chars.length;
let autoId = '';
const targetLength = 20;
while (autoId.length < targetLength) {
const bytes = randomBytes(40);
for (let i = 0; i < bytes.length; ++i) {
// Only accept values that are [0, maxMultiple), this ensures they can
// be evenly mapped to indices of `chars` via a modulo operation.
if (autoId.length < targetLength && bytes[i] < maxMultiple) {
autoId += chars.charAt(bytes[i] % chars.length);
}
}
}
return autoId;
}
}
function primitiveComparator(left, right) {
if (left < right) {
return -1;
}
if (left > right) {
return 1;
}
return 0;
}
/** Helper to compare arrays using isEqual(). */
function arrayEquals(left, right, comparator) {
if (left.length !== right.length) {
return false;
}
return left.every((value, index) => comparator(value, right[index]));
}
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// The earliest date supported by Firestore timestamps (0001-01-01T00:00:00Z).
const MIN_SECONDS = -62135596800;
// Number of nanoseconds in a millisecond.
const MS_TO_NANOS = 1e6;
/**
* A `Timestamp` represents a point in time independent of any time zone or
* calendar, represented as seconds and fractions of seconds at nanosecond
* resolution in UTC Epoch time.
*
* It is encoded using the Proleptic Gregorian Calendar which extends the
* Gregorian calendar backwards to year one. It is encoded assuming all minutes
* are 60 seconds long, i.e. leap seconds are "smeared" so that no leap second
* table is needed for interpretation. Range is from 0001-01-01T00:00:00Z to
* 9999-12-31T23:59:59.999999999Z.
*
* For examples and further specifications, refer to the
* {@link https://github.com/google/protobuf/blob/master/src/google/protobuf/timestamp.proto | Timestamp definition}.
*/
class Timestamp {
/**
* Creates a new timestamp.
*
* @param seconds - The number of seconds of UTC time since Unix epoch
* 1970-01-01T00:00:00Z. Must be from 0001-01-01T00:00:00Z to
* 9999-12-31T23:59:59Z inclusive.
* @param nanoseconds - The non-negative fractions of a second at nanosecond
* resolution. Negative second values with fractions must still have
* non-negative nanoseconds values that count forward in time. Must be
* from 0 to 999,999,999 inclusive.
*/
constructor(
/**
* The number of seconds of UTC time since Unix epoch 1970-01-01T00:00:00Z.
*/
seconds,
/**
* The fractions of a second at nanosecond resolution.*
*/
nanoseconds) {
this.seconds = seconds;
this.nanoseconds = nanoseconds;
if (nanoseconds < 0) {
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp nanoseconds out of range: ' + nanoseconds);
}
if (nanoseconds >= 1e9) {
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp nanoseconds out of range: ' + nanoseconds);
}
if (seconds < MIN_SECONDS) {
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp seconds out of range: ' + seconds);
}
// This will break in the year 10,000.
if (seconds >= 253402300800) {
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Timestamp seconds out of range: ' + seconds);
}
}
/**
* Creates a new timestamp with the current date, with millisecond precision.
*
* @returns a new timestamp representing the current date.
*/
static now() {
return Timestamp.fromMillis(Date.now());
}
/**
* Creates a new timestamp from the given date.
*
* @param date - The date to initialize the `Timestamp` from.
* @returns A new `Timestamp` representing the same point in time as the given
* date.
*/
static fromDate(date) {
return Timestamp.fromMillis(date.getTime());
}
/**
* Creates a new timestamp from the given number of milliseconds.
*
* @param milliseconds - Number of milliseconds since Unix epoch
* 1970-01-01T00:00:00Z.
* @returns A new `Timestamp` representing the same point in time as the given
* number of milliseconds.
*/
static fromMillis(milliseconds) {
const seconds = Math.floor(milliseconds / 1000);
const nanos = Math.floor((milliseconds - seconds * 1000) * MS_TO_NANOS);
return new Timestamp(seconds, nanos);
}
/**
* Converts a `Timestamp` to a JavaScript `Date` object. This conversion
* causes a loss of precision since `Date` objects only support millisecond
* precision.
*
* @returns JavaScript `Date` object representing the same point in time as
* this `Timestamp`, with millisecond precision.
*/
toDate() {
return new Date(this.toMillis());
}
/**
* Converts a `Timestamp` to a numeric timestamp (in milliseconds since
* epoch). This operation causes a loss of precision.
*
* @returns The point in time corresponding to this timestamp, represented as
* the number of milliseconds since Unix epoch 1970-01-01T00:00:00Z.
*/
toMillis() {
return this.seconds * 1000 + this.nanoseconds / MS_TO_NANOS;
}
_compareTo(other) {
if (this.seconds === other.seconds) {
return primitiveComparator(this.nanoseconds, other.nanoseconds);
}
return primitiveComparator(this.seconds, other.seconds);
}
/**
* Returns true if this `Timestamp` is equal to the provided one.
*
* @param other - The `Timestamp` to compare against.
* @returns true if this `Timestamp` is equal to the provided one.
*/
isEqual(other) {
return (other.seconds === this.seconds && other.nanoseconds === this.nanoseconds);
}
/** Returns a textual representation of this Timestamp. */
toString() {
return ('Timestamp(seconds=' +
this.seconds +
', nanoseconds=' +
this.nanoseconds +
')');
}
/** Returns a JSON-serializable representation of this Timestamp. */
toJSON() {
return { seconds: this.seconds, nanoseconds: this.nanoseconds };
}
/**
* Converts this object to a primitive string, which allows Timestamp objects
* to be compared using the `>`, `<=`, `>=` and `>` operators.
*/
valueOf() {
// This method returns a string of the form <seconds>.<nanoseconds> where
// <seconds> is translated to have a non-negative value and both <seconds>
// and <nanoseconds> are left-padded with zeroes to be a consistent length.
// Strings with this format then have a lexiographical ordering that matches
// the expected ordering. The <seconds> translation is done to avoid having
// a leading negative sign (i.e. a leading '-' character) in its string
// representation, which would affect its lexiographical ordering.
const adjustedSeconds = this.seconds - MIN_SECONDS;
// Note: Up to 12 decimal digits are required to represent all valid
// 'seconds' values.
const formattedSeconds = String(adjustedSeconds).padStart(12, '0');
const formattedNanoseconds = String(this.nanoseconds).padStart(9, '0');
return formattedSeconds + '.' + formattedNanoseconds;
}
}
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* A version of a document in Firestore. This corresponds to the version
* timestamp, such as update_time or read_time.
*/
class SnapshotVersion {
constructor(timestamp) {
this.timestamp = timestamp;
}
static fromTimestamp(value) {
return new SnapshotVersion(value);
}
static min() {
return new SnapshotVersion(new Timestamp(0, 0));
}
compareTo(other) {
return this.timestamp._compareTo(other.timestamp);
}
isEqual(other) {
return this.timestamp.isEqual(other.timestamp);
}
/** Returns a number representation of the version for use in spec tests. */
toMicroseconds() {
// Convert to microseconds.
return this.timestamp.seconds * 1e6 + this.timestamp.nanoseconds / 1000;
}
toString() {
return 'SnapshotVersion(' + this.timestamp.toString() + ')';
}
toTimestamp() {
return this.timestamp;
}
}
/**
* @license
* Copyright 2017 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
function objectSize(obj) {
let count = 0;
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
count++;
}
}
return count;
}
function forEach(obj, fn) {
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
fn(key, obj[key]);
}
}
}
function isEmpty(obj) {
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
return false;
}
}
return true;
}
/**
* @license
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Provides a set of fields that can be used to partially patch a document.
* FieldMask is used in conjunction with ObjectValue.
* Examples:
* foo - Overwrites foo entirely with the provided value. If foo is not
* present in the companion ObjectValue, the field is deleted.
* foo.bar - Overwrites only the field bar of the object foo.
* If foo is not an object, foo is replaced with an object
* containing foo
*/
class FieldMask {
constructor(fields) {
this.fields = fields;
// TODO(dimond): validation of FieldMask
// Sort the field mask to support `FieldMask.isEqual()` and assert below.
fields.sort(FieldPath.comparator);
}
/**
* Verifies that `fieldPath` is included by at least one field in this field
* mask.
*
* This is an O(n) operation, where `n` is the size of the field mask.
*/
covers(fieldPath) {
for (const fieldMaskPath of this.fields) {
if (fieldMaskPath.isPrefixOf(fieldPath)) {
return true;
}
}
return false;
}
isEqual(other) {
return arrayEquals(this.fields, other.fields, (l, r) => l.isEqual(r));
}
}
/**
* @license
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
function decodeBase64(encoded) {
// Node actually doesn't validate base64 strings.
// A quick sanity check that is not a fool-proof validation
if (/[^-A-Za-z0-9+/=]/.test(encoded)) {
throw new FirestoreError(Code.INVALID_ARGUMENT, 'Not a valid Base64 string: ' + encoded);
}
return new Buffer(encoded, 'base64').toString('binary');
}
/** Converts a binary string to a Base64 encoded string. */
function encodeBase64(raw) {
return new Buffer(raw, 'binary').toString('base64');
}
/**
* @license
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Immutable class that represents a "proto" byte string.
*
* Proto byte strings can either be Base64-encoded strings or Uint8Arrays when
* sent on the wire. This class abstracts away this differentiation by holding
* the proto byte string in a common class that must be converted into a string
* before being sent as a proto.
*/
class ByteString {
constructor(binaryString) {
this.binaryString = binaryString;
}
static fr